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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//! Application Support Sublayer (APS) module.
use std::fmt::Display;
use std::num::NonZero;
use bitflags::bitflags;
use le_stream::{FromLeStream, ToLeStream};
/// Selects the block-control byte in the high byte of an APS frame's group ID.
///
/// Fragmented APS frames overload the group ID: the low byte contains the
/// fragment index, while the high byte contains fragment block information.
/// This mask selects that high-byte field without selecting the fragment index.
pub const BLOCK_MASK: u16 = 0xFF00;
/// 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
}
/// Sets the raw APS group ID field.
///
/// For multicast frames this identifies the destination group. Fragmented
/// frames overload the field with the fragment index in the low byte and
/// block information in the high byte. This method does not modify
/// [`Options::FRAGMENT`] or validate that encoding; use
/// [`Frame::set_first_fragment`] or [`Frame::set_followup_fragment`] when
/// constructing fragmented frames.
pub const fn set_group_id(&mut self, group_id: u16) {
self.group_id = group_id;
}
/// Return the sequence number.
#[must_use]
pub const fn sequence(&self) -> u8 {
self.sequence
}
/// Set the APS sequence number.
pub const fn set_sequence(&mut self, sequence: u8) {
self.sequence = sequence;
}
/// Enable APS retry for this frame.
pub fn enable_retry(&mut self) {
self.options.insert(Options::RETRY);
}
/// 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, blocks] = self.group_id.to_le_bytes();
if index == 0 {
Some((index, Some(blocks)))
} else {
Some((index, None))
}
} else {
None
}
}
/// Mark this frame as the first fragment of a fragmented APS message.
pub fn set_first_fragment(&mut self, blocks: u8) {
self.options.insert(Options::FRAGMENT);
self.group_id = u16::from(blocks) << BLOCK_MASK.trailing_zeros();
}
/// Mark this frame as a follow-up fragment of a fragmented APS message.
pub fn set_followup_fragment(&mut self, index: NonZero<u8>) {
self.options.insert(Options::FRAGMENT);
self.group_id = u16::from(index.get());
}
/// Removes APS fragmentation metadata from this frame.
pub fn clear_fragmentation(&mut self) {
self.options.remove(Options::FRAGMENT);
self.group_id = 0;
}
}
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,
)
}
}