1use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[repr(u8)]
15pub enum CommissioningError {
16 Ok = 0,
18 Valueoutsiderange = 1,
20 Invalidauthentication = 2,
22 Nofailsafe = 3,
24 Busywithotheradmin = 4,
26 Requiredtcnotaccepted = 5,
28 Tcacknowledgementsnotreceived = 6,
30 Tcminversionnotmet = 7,
32}
33
34impl CommissioningError {
35 pub fn from_u8(value: u8) -> Option<Self> {
37 match value {
38 0 => Some(CommissioningError::Ok),
39 1 => Some(CommissioningError::Valueoutsiderange),
40 2 => Some(CommissioningError::Invalidauthentication),
41 3 => Some(CommissioningError::Nofailsafe),
42 4 => Some(CommissioningError::Busywithotheradmin),
43 5 => Some(CommissioningError::Requiredtcnotaccepted),
44 6 => Some(CommissioningError::Tcacknowledgementsnotreceived),
45 7 => Some(CommissioningError::Tcminversionnotmet),
46 _ => None,
47 }
48 }
49
50 pub fn to_u8(self) -> u8 {
52 self as u8
53 }
54}
55
56impl From<CommissioningError> for u8 {
57 fn from(val: CommissioningError) -> Self {
58 val as u8
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
63#[repr(u8)]
64pub enum NetworkRecoveryReason {
65 Unspecified = 0,
67 Auth = 1,
69 Visibility = 2,
71}
72
73impl NetworkRecoveryReason {
74 pub fn from_u8(value: u8) -> Option<Self> {
76 match value {
77 0 => Some(NetworkRecoveryReason::Unspecified),
78 1 => Some(NetworkRecoveryReason::Auth),
79 2 => Some(NetworkRecoveryReason::Visibility),
80 _ => None,
81 }
82 }
83
84 pub fn to_u8(self) -> u8 {
86 self as u8
87 }
88}
89
90impl From<NetworkRecoveryReason> for u8 {
91 fn from(val: NetworkRecoveryReason) -> Self {
92 val as u8
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
97#[repr(u8)]
98pub enum RegulatoryLocationType {
99 Indoor = 0,
101 Outdoor = 1,
103 Indooroutdoor = 2,
105}
106
107impl RegulatoryLocationType {
108 pub fn from_u8(value: u8) -> Option<Self> {
110 match value {
111 0 => Some(RegulatoryLocationType::Indoor),
112 1 => Some(RegulatoryLocationType::Outdoor),
113 2 => Some(RegulatoryLocationType::Indooroutdoor),
114 _ => None,
115 }
116 }
117
118 pub fn to_u8(self) -> u8 {
120 self as u8
121 }
122}
123
124impl From<RegulatoryLocationType> for u8 {
125 fn from(val: RegulatoryLocationType) -> Self {
126 val as u8
127 }
128}
129
130#[derive(Debug, serde::Serialize)]
133pub struct BasicCommissioningInfo {
134 pub fail_safe_expiry_length_seconds: Option<u16>,
135 pub max_cumulative_failsafe_seconds: Option<u16>,
136}
137
138pub fn encode_arm_fail_safe(expiry_length_seconds: u16, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
142 let tlv = tlv::TlvItemEnc {
143 tag: 0,
144 value: tlv::TlvItemValueEnc::StructInvisible(vec![
145 (0, tlv::TlvItemValueEnc::UInt16(expiry_length_seconds)).into(),
146 (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
147 ]),
148 };
149 Ok(tlv.encode()?)
150}
151
152pub fn encode_set_regulatory_config(new_regulatory_config: RegulatoryLocationType, country_code: String, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
154 let tlv = tlv::TlvItemEnc {
155 tag: 0,
156 value: tlv::TlvItemValueEnc::StructInvisible(vec![
157 (0, tlv::TlvItemValueEnc::UInt8(new_regulatory_config.to_u8())).into(),
158 (1, tlv::TlvItemValueEnc::String(country_code)).into(),
159 (2, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
160 ]),
161 };
162 Ok(tlv.encode()?)
163}
164
165pub fn encode_set_tc_acknowledgements(tc_version: u16, tc_user_response: u8) -> anyhow::Result<Vec<u8>> {
167 let tlv = tlv::TlvItemEnc {
168 tag: 0,
169 value: tlv::TlvItemValueEnc::StructInvisible(vec![
170 (0, tlv::TlvItemValueEnc::UInt16(tc_version)).into(),
171 (1, tlv::TlvItemValueEnc::UInt8(tc_user_response)).into(),
172 ]),
173 };
174 Ok(tlv.encode()?)
175}
176
177pub fn decode_breadcrumb(inp: &tlv::TlvItemValue) -> anyhow::Result<u64> {
181 if let tlv::TlvItemValue::Int(v) = inp {
182 Ok(*v)
183 } else {
184 Err(anyhow::anyhow!("Expected UInt64"))
185 }
186}
187
188pub fn decode_basic_commissioning_info(inp: &tlv::TlvItemValue) -> anyhow::Result<BasicCommissioningInfo> {
190 if let tlv::TlvItemValue::List(_fields) = inp {
191 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
193 Ok(BasicCommissioningInfo {
194 fail_safe_expiry_length_seconds: item.get_int(&[0]).map(|v| v as u16),
195 max_cumulative_failsafe_seconds: item.get_int(&[1]).map(|v| v as u16),
196 })
197 } else {
198 Err(anyhow::anyhow!("Expected struct fields"))
199 }
200}
201
202pub fn decode_regulatory_config(inp: &tlv::TlvItemValue) -> anyhow::Result<RegulatoryLocationType> {
204 if let tlv::TlvItemValue::Int(v) = inp {
205 RegulatoryLocationType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
206 } else {
207 Err(anyhow::anyhow!("Expected Integer"))
208 }
209}
210
211pub fn decode_location_capability(inp: &tlv::TlvItemValue) -> anyhow::Result<RegulatoryLocationType> {
213 if let tlv::TlvItemValue::Int(v) = inp {
214 RegulatoryLocationType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
215 } else {
216 Err(anyhow::anyhow!("Expected Integer"))
217 }
218}
219
220pub fn decode_supports_concurrent_connection(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
222 if let tlv::TlvItemValue::Bool(v) = inp {
223 Ok(*v)
224 } else {
225 Err(anyhow::anyhow!("Expected Bool"))
226 }
227}
228
229pub fn decode_tc_accepted_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
231 if let tlv::TlvItemValue::Int(v) = inp {
232 Ok(*v as u16)
233 } else {
234 Err(anyhow::anyhow!("Expected UInt16"))
235 }
236}
237
238pub fn decode_tc_min_required_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
240 if let tlv::TlvItemValue::Int(v) = inp {
241 Ok(*v as u16)
242 } else {
243 Err(anyhow::anyhow!("Expected UInt16"))
244 }
245}
246
247pub fn decode_tc_acknowledgements(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
249 if let tlv::TlvItemValue::Int(v) = inp {
250 Ok(*v as u8)
251 } else {
252 Err(anyhow::anyhow!("Expected UInt8"))
253 }
254}
255
256pub fn decode_tc_acknowledgements_required(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
258 if let tlv::TlvItemValue::Bool(v) = inp {
259 Ok(*v)
260 } else {
261 Err(anyhow::anyhow!("Expected Bool"))
262 }
263}
264
265pub fn decode_tc_update_deadline(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
267 if let tlv::TlvItemValue::Int(v) = inp {
268 Ok(Some(*v as u32))
269 } else {
270 Ok(None)
271 }
272}
273
274pub fn decode_recovery_identifier(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<u8>> {
276 if let tlv::TlvItemValue::OctetString(v) = inp {
277 Ok(v.clone())
278 } else {
279 Err(anyhow::anyhow!("Expected OctetString"))
280 }
281}
282
283pub fn decode_network_recovery_reason(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<NetworkRecoveryReason>> {
285 if let tlv::TlvItemValue::Int(v) = inp {
286 Ok(NetworkRecoveryReason::from_u8(*v as u8))
287 } else {
288 Ok(None)
289 }
290}
291
292pub fn decode_is_commissioning_without_power(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
294 if let tlv::TlvItemValue::Bool(v) = inp {
295 Ok(*v)
296 } else {
297 Err(anyhow::anyhow!("Expected Bool"))
298 }
299}
300
301
302pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
314 if cluster_id != 0x0030 {
316 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0030, got {}\"}}", cluster_id);
317 }
318
319 match attribute_id {
320 0x0000 => {
321 match decode_breadcrumb(tlv_value) {
322 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
323 Err(e) => format!("{{\"error\": \"{}\"}}", e),
324 }
325 }
326 0x0001 => {
327 match decode_basic_commissioning_info(tlv_value) {
328 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
329 Err(e) => format!("{{\"error\": \"{}\"}}", e),
330 }
331 }
332 0x0002 => {
333 match decode_regulatory_config(tlv_value) {
334 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
335 Err(e) => format!("{{\"error\": \"{}\"}}", e),
336 }
337 }
338 0x0003 => {
339 match decode_location_capability(tlv_value) {
340 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
341 Err(e) => format!("{{\"error\": \"{}\"}}", e),
342 }
343 }
344 0x0004 => {
345 match decode_supports_concurrent_connection(tlv_value) {
346 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
347 Err(e) => format!("{{\"error\": \"{}\"}}", e),
348 }
349 }
350 0x0005 => {
351 match decode_tc_accepted_version(tlv_value) {
352 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
353 Err(e) => format!("{{\"error\": \"{}\"}}", e),
354 }
355 }
356 0x0006 => {
357 match decode_tc_min_required_version(tlv_value) {
358 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
359 Err(e) => format!("{{\"error\": \"{}\"}}", e),
360 }
361 }
362 0x0007 => {
363 match decode_tc_acknowledgements(tlv_value) {
364 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
365 Err(e) => format!("{{\"error\": \"{}\"}}", e),
366 }
367 }
368 0x0008 => {
369 match decode_tc_acknowledgements_required(tlv_value) {
370 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
371 Err(e) => format!("{{\"error\": \"{}\"}}", e),
372 }
373 }
374 0x0009 => {
375 match decode_tc_update_deadline(tlv_value) {
376 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
377 Err(e) => format!("{{\"error\": \"{}\"}}", e),
378 }
379 }
380 0x000A => {
381 match decode_recovery_identifier(tlv_value) {
382 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
383 Err(e) => format!("{{\"error\": \"{}\"}}", e),
384 }
385 }
386 0x000B => {
387 match decode_network_recovery_reason(tlv_value) {
388 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
389 Err(e) => format!("{{\"error\": \"{}\"}}", e),
390 }
391 }
392 0x000C => {
393 match decode_is_commissioning_without_power(tlv_value) {
394 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
395 Err(e) => format!("{{\"error\": \"{}\"}}", e),
396 }
397 }
398 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
399 }
400}
401
402pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
407 vec![
408 (0x0000, "Breadcrumb"),
409 (0x0001, "BasicCommissioningInfo"),
410 (0x0002, "RegulatoryConfig"),
411 (0x0003, "LocationCapability"),
412 (0x0004, "SupportsConcurrentConnection"),
413 (0x0005, "TCAcceptedVersion"),
414 (0x0006, "TCMinRequiredVersion"),
415 (0x0007, "TCAcknowledgements"),
416 (0x0008, "TCAcknowledgementsRequired"),
417 (0x0009, "TCUpdateDeadline"),
418 (0x000A, "RecoveryIdentifier"),
419 (0x000B, "NetworkRecoveryReason"),
420 (0x000C, "IsCommissioningWithoutPower"),
421 ]
422}
423
424#[derive(Debug, serde::Serialize)]
425pub struct ArmFailSafeResponse {
426 pub error_code: Option<CommissioningError>,
427 pub debug_text: Option<String>,
428}
429
430#[derive(Debug, serde::Serialize)]
431pub struct SetRegulatoryConfigResponse {
432 pub error_code: Option<CommissioningError>,
433 pub debug_text: Option<String>,
434}
435
436#[derive(Debug, serde::Serialize)]
437pub struct CommissioningCompleteResponse {
438 pub error_code: Option<CommissioningError>,
439 pub debug_text: Option<String>,
440}
441
442#[derive(Debug, serde::Serialize)]
443pub struct SetTCAcknowledgementsResponse {
444 pub error_code: Option<CommissioningError>,
445}
446
447pub fn decode_arm_fail_safe_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ArmFailSafeResponse> {
451 if let tlv::TlvItemValue::List(_fields) = inp {
452 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
453 Ok(ArmFailSafeResponse {
454 error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
455 debug_text: item.get_string_owned(&[1]),
456 })
457 } else {
458 Err(anyhow::anyhow!("Expected struct fields"))
459 }
460}
461
462pub fn decode_set_regulatory_config_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetRegulatoryConfigResponse> {
464 if let tlv::TlvItemValue::List(_fields) = inp {
465 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
466 Ok(SetRegulatoryConfigResponse {
467 error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
468 debug_text: item.get_string_owned(&[1]),
469 })
470 } else {
471 Err(anyhow::anyhow!("Expected struct fields"))
472 }
473}
474
475pub fn decode_commissioning_complete_response(inp: &tlv::TlvItemValue) -> anyhow::Result<CommissioningCompleteResponse> {
477 if let tlv::TlvItemValue::List(_fields) = inp {
478 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
479 Ok(CommissioningCompleteResponse {
480 error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
481 debug_text: item.get_string_owned(&[1]),
482 })
483 } else {
484 Err(anyhow::anyhow!("Expected struct fields"))
485 }
486}
487
488pub fn decode_set_tc_acknowledgements_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetTCAcknowledgementsResponse> {
490 if let tlv::TlvItemValue::List(_fields) = inp {
491 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
492 Ok(SetTCAcknowledgementsResponse {
493 error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
494 })
495 } else {
496 Err(anyhow::anyhow!("Expected struct fields"))
497 }
498}
499