1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! Application Support Sublayer (APS) module.
use std::fmt::Display;
use bitflags::bitflags;
use le_stream::{FromLeStream, ToLeStream};
/// Ember APS options.
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, FromLeStream, ToLeStream)]
#[repr(transparent)]
pub struct Options(u16);
bitflags! {
impl Options: u16 {
/// No options.
const NONE = 0x0000;
/// Send the message using APS Encryption, using the Link Key shared with the
/// destination node to encrypt the data at the APS Level.
const ENCRYPTION = 0x0020;
/// Resend the message using the APS retry mechanism.
const RETRY = 0x0040;
/// Causes a route discovery to be initiated if no route to the destination is known.
const ENABLE_ROUTE_DISCOVERY = 0x0100;
/// Causes a route discovery to be initiated even if one is known.
const FORCE_ROUTE_DISCOVERY = 0x0200;
/// Include the source EUI64 in the network frame.
const SOURCE_EUI64 = 0x0400;
/// Include the destination EUI64 in the network frame.
const DESTINATION_EUI64 = 0x0800;
/// Send a ZDO request to discover the node ID of the destination if it is not already know.
const ENABLE_ADDRESS_DISCOVERY = 0x1000;
/// Reserved.
const POLL_RESPONSE = 0x2000;
/// This incoming message is a ZDO request not handled by the `EmberZNet` stack,
/// and the application is responsible for sending a ZDO response.
///
/// This flag is used only when the ZDO is configured to have requests handled by the application.
/// See the [`ApplicationZdoFlags`](crate::ezsp::config::Id::ApplicationZdoFlags) configuration parameter for more information.
const ZDO_RESPONSE_REQUIRED = 0x4000;
/// This message is part of a fragmented message. This option may only be set for unicasts.
///
/// The `groupId` field gives the index of this fragment in the low-order byte.
/// If the low-order byte is zero, this is the first fragment, and the high-order byte
/// contains the number of fragments in the message.
const FRAGMENT = 0x8000;
}
}
impl From<Options> for u16 {
fn from(options: Options) -> Self {
options.0
}
}
impl From<u16> for Options {
fn from(value: u16) -> Self {
Self::from_bits_truncate(value)
}
}
/// Zigbee APS frame parameters.
#[derive(Clone, Debug, Eq, PartialEq, Hash, FromLeStream, ToLeStream)]
pub struct Frame {
profile_id: u16,
cluster_id: u16,
source_endpoint: u8,
destination_endpoint: u8,
options: Options,
group_id: u16,
sequence: u8,
}
impl Frame {
/// Create a new APS frame.
#[must_use]
pub const fn new(
profile_id: u16,
cluster_id: u16,
source_endpoint: u8,
destination_endpoint: u8,
options: Options,
group_id: u16,
sequence: u8,
) -> Self {
Self {
profile_id,
cluster_id,
source_endpoint,
destination_endpoint,
options,
group_id,
sequence,
}
}
/// Return the application profile ID that describes the format of the message.
#[must_use]
pub const fn profile_id(&self) -> u16 {
self.profile_id
}
/// Return the cluster ID for this message.
#[must_use]
pub const fn cluster_id(&self) -> u16 {
self.cluster_id
}
/// Return the source endpoint.
#[must_use]
pub const fn source_endpoint(&self) -> u8 {
self.source_endpoint
}
/// Return the destination endpoint.
#[must_use]
pub const fn destination_endpoint(&self) -> u8 {
self.destination_endpoint
}
/// Return a list of options.
#[must_use]
pub const fn options(&self) -> Options {
self.options
}
/// Return the group ID for this message if it is a multicast mode.
#[must_use]
pub const fn group_id(&self) -> u16 {
self.group_id
}
/// Return the sequence number.
#[must_use]
pub const fn sequence(&self) -> u8 {
self.sequence
}
/// Return fragmentation information if the message is fragmented.
///
/// # Returns
///
/// - `Some((index, Some(total_fragments)))` if this is the first fragment, where `index` is 0.
/// - `Some((index, None))` if this is a subsequent fragment.
/// - `None` if the message is not fragmented.
#[must_use]
pub const fn fragmentation(&self) -> Option<(u8, Option<u8>)> {
if self.options.contains(Options::FRAGMENT) {
let index = (self.group_id & 0x00FF) as u8;
if index == 0 {
Some((index, Some(((self.group_id & 0xFF00) >> 8) as u8)))
} else {
Some((index, None))
}
} else {
None
}
}
}
impl Display for Frame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Frame {{ profile_id: {:#06X}, cluster_id: {:#06X}, source_endpoint: {:#04X}, destination_endpoint: {:#04X}, options: {:#06X}, group_id: {:#06X}, sequence: {:#04X} }}",
self.profile_id,
self.cluster_id,
self.source_endpoint,
self.destination_endpoint,
u16::from(self.options),
self.group_id,
self.sequence,
)
}
}