matc/clusters/codec/
ota_requestor.rs

1//! Matter TLV encoders and decoders for OTA Software Update Requestor Cluster
2//! Cluster ID: 0x002A
3//!
4//! This file is automatically generated from OTARequestor.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Enum definitions
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum AnnouncementReason {
18    /// An OTA Provider is announcing its presence.
19    Simpleannouncement = 0,
20    /// An OTA Provider is announcing, either to a single Node or to a group of Nodes, that a new Software Image MAY be available.
21    Updateavailable = 1,
22    /// An OTA Provider is announcing, either to a single Node or to a group of Nodes, that a new Software Image MAY be available, which contains an update that needs to be applied urgently.
23    Urgentupdateavailable = 2,
24}
25
26impl AnnouncementReason {
27    /// Convert from u8 value
28    pub fn from_u8(value: u8) -> Option<Self> {
29        match value {
30            0 => Some(AnnouncementReason::Simpleannouncement),
31            1 => Some(AnnouncementReason::Updateavailable),
32            2 => Some(AnnouncementReason::Urgentupdateavailable),
33            _ => None,
34        }
35    }
36
37    /// Convert to u8 value
38    pub fn to_u8(self) -> u8 {
39        self as u8
40    }
41}
42
43impl From<AnnouncementReason> for u8 {
44    fn from(val: AnnouncementReason) -> Self {
45        val as u8
46    }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
50#[repr(u8)]
51pub enum ChangeReason {
52    /// The reason for a state change is unknown.
53    Unknown = 0,
54    /// The reason for a state change is the success of a prior operation.
55    Success = 1,
56    /// The reason for a state change is the failure of a prior operation.
57    Failure = 2,
58    /// The reason for a state change is a time-out.
59    Timeout = 3,
60    /// The reason for a state change is a request by the OTA Provider to wait.
61    Delaybyprovider = 4,
62}
63
64impl ChangeReason {
65    /// Convert from u8 value
66    pub fn from_u8(value: u8) -> Option<Self> {
67        match value {
68            0 => Some(ChangeReason::Unknown),
69            1 => Some(ChangeReason::Success),
70            2 => Some(ChangeReason::Failure),
71            3 => Some(ChangeReason::Timeout),
72            4 => Some(ChangeReason::Delaybyprovider),
73            _ => None,
74        }
75    }
76
77    /// Convert to u8 value
78    pub fn to_u8(self) -> u8 {
79        self as u8
80    }
81}
82
83impl From<ChangeReason> for u8 {
84    fn from(val: ChangeReason) -> Self {
85        val as u8
86    }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
90#[repr(u8)]
91pub enum UpdateState {
92    /// Current state is not yet determined.
93    Unknown = 0,
94    /// Indicate a Node not yet in the process of software update.
95    Idle = 1,
96    /// Indicate a Node in the process of querying an OTA Provider.
97    Querying = 2,
98    /// Indicate a Node waiting after a Busy response.
99    Delayedonquery = 3,
100    /// Indicate a Node currently in the process of downloading a software update.
101    Downloading = 4,
102    /// Indicate a Node currently in the process of verifying and applying a software update.
103    Applying = 5,
104    /// Indicate a Node waiting caused by AwaitNextAction response.
105    Delayedonapply = 6,
106    /// Indicate a Node in the process of recovering to a previous version.
107    Rollingback = 7,
108    /// Indicate a Node is capable of user consent.
109    Delayedonuserconsent = 8,
110}
111
112impl UpdateState {
113    /// Convert from u8 value
114    pub fn from_u8(value: u8) -> Option<Self> {
115        match value {
116            0 => Some(UpdateState::Unknown),
117            1 => Some(UpdateState::Idle),
118            2 => Some(UpdateState::Querying),
119            3 => Some(UpdateState::Delayedonquery),
120            4 => Some(UpdateState::Downloading),
121            5 => Some(UpdateState::Applying),
122            6 => Some(UpdateState::Delayedonapply),
123            7 => Some(UpdateState::Rollingback),
124            8 => Some(UpdateState::Delayedonuserconsent),
125            _ => None,
126        }
127    }
128
129    /// Convert to u8 value
130    pub fn to_u8(self) -> u8 {
131        self as u8
132    }
133}
134
135impl From<UpdateState> for u8 {
136    fn from(val: UpdateState) -> Self {
137        val as u8
138    }
139}
140
141// Struct definitions
142
143#[derive(Debug, serde::Serialize)]
144pub struct ProviderLocation {
145    pub provider_node_id: Option<u64>,
146    pub endpoint: Option<u16>,
147}
148
149// Command encoders
150
151/// Encode AnnounceOTAProvider command (0x00)
152pub fn encode_announce_ota_provider(provider_node_id: u64, vendor_id: u16, announcement_reason: AnnouncementReason, metadata_for_node: Vec<u8>, endpoint: u16) -> anyhow::Result<Vec<u8>> {
153    let tlv = tlv::TlvItemEnc {
154        tag: 0,
155        value: tlv::TlvItemValueEnc::StructInvisible(vec![
156        (0, tlv::TlvItemValueEnc::UInt64(provider_node_id)).into(),
157        (1, tlv::TlvItemValueEnc::UInt16(vendor_id)).into(),
158        (2, tlv::TlvItemValueEnc::UInt8(announcement_reason.to_u8())).into(),
159        (3, tlv::TlvItemValueEnc::OctetString(metadata_for_node)).into(),
160        (4, tlv::TlvItemValueEnc::UInt16(endpoint)).into(),
161        ]),
162    };
163    Ok(tlv.encode()?)
164}
165
166// Attribute decoders
167
168/// Decode DefaultOTAProviders attribute (0x0000)
169pub fn decode_default_ota_providers(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ProviderLocation>> {
170    let mut res = Vec::new();
171    if let tlv::TlvItemValue::List(v) = inp {
172        for item in v {
173            res.push(ProviderLocation {
174                provider_node_id: item.get_int(&[1]),
175                endpoint: item.get_int(&[2]).map(|v| v as u16),
176            });
177        }
178    }
179    Ok(res)
180}
181
182/// Decode UpdatePossible attribute (0x0001)
183pub fn decode_update_possible(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
184    if let tlv::TlvItemValue::Bool(v) = inp {
185        Ok(*v)
186    } else {
187        Err(anyhow::anyhow!("Expected Bool"))
188    }
189}
190
191/// Decode UpdateState attribute (0x0002)
192pub fn decode_update_state(inp: &tlv::TlvItemValue) -> anyhow::Result<UpdateState> {
193    if let tlv::TlvItemValue::Int(v) = inp {
194        UpdateState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
195    } else {
196        Err(anyhow::anyhow!("Expected Integer"))
197    }
198}
199
200/// Decode UpdateStateProgress attribute (0x0003)
201pub fn decode_update_state_progress(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
202    if let tlv::TlvItemValue::Int(v) = inp {
203        Ok(Some(*v as u8))
204    } else {
205        Ok(None)
206    }
207}
208
209
210// JSON dispatcher function
211
212/// Decode attribute value and return as JSON string
213///
214/// # Parameters
215/// * `cluster_id` - The cluster identifier
216/// * `attribute_id` - The attribute identifier
217/// * `tlv_value` - The TLV value to decode
218///
219/// # Returns
220/// JSON string representation of the decoded value or error
221pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
222    // Verify this is the correct cluster
223    if cluster_id != 0x002A {
224        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x002A, got {}\"}}", cluster_id);
225    }
226
227    match attribute_id {
228        0x0000 => {
229            match decode_default_ota_providers(tlv_value) {
230                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
231                Err(e) => format!("{{\"error\": \"{}\"}}", e),
232            }
233        }
234        0x0001 => {
235            match decode_update_possible(tlv_value) {
236                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
237                Err(e) => format!("{{\"error\": \"{}\"}}", e),
238            }
239        }
240        0x0002 => {
241            match decode_update_state(tlv_value) {
242                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
243                Err(e) => format!("{{\"error\": \"{}\"}}", e),
244            }
245        }
246        0x0003 => {
247            match decode_update_state_progress(tlv_value) {
248                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
249                Err(e) => format!("{{\"error\": \"{}\"}}", e),
250            }
251        }
252        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
253    }
254}
255
256/// Get list of all attributes supported by this cluster
257///
258/// # Returns
259/// Vector of tuples containing (attribute_id, attribute_name)
260pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
261    vec![
262        (0x0000, "DefaultOTAProviders"),
263        (0x0001, "UpdatePossible"),
264        (0x0002, "UpdateState"),
265        (0x0003, "UpdateStateProgress"),
266    ]
267}
268
269// Command listing
270
271pub fn get_command_list() -> Vec<(u32, &'static str)> {
272    vec![
273        (0x00, "AnnounceOTAProvider"),
274    ]
275}
276
277pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
278    match cmd_id {
279        0x00 => Some("AnnounceOTAProvider"),
280        _ => None,
281    }
282}
283
284pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
285    match cmd_id {
286        0x00 => Some(vec![
287            crate::clusters::codec::CommandField { tag: 0, name: "provider_node_id", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
288            crate::clusters::codec::CommandField { tag: 1, name: "vendor_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
289            crate::clusters::codec::CommandField { tag: 2, name: "announcement_reason", kind: crate::clusters::codec::FieldKind::Enum { name: "AnnouncementReason", variants: &[(0, "Simpleannouncement"), (1, "Updateavailable"), (2, "Urgentupdateavailable")] }, optional: false, nullable: false },
290            crate::clusters::codec::CommandField { tag: 3, name: "metadata_for_node", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
291            crate::clusters::codec::CommandField { tag: 4, name: "endpoint", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
292        ]),
293        _ => None,
294    }
295}
296
297pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
298    match cmd_id {
299        0x00 => {
300        let provider_node_id = crate::clusters::codec::json_util::get_u64(args, "provider_node_id")?;
301        let vendor_id = crate::clusters::codec::json_util::get_u16(args, "vendor_id")?;
302        let announcement_reason = {
303            let n = crate::clusters::codec::json_util::get_u64(args, "announcement_reason")?;
304            AnnouncementReason::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid AnnouncementReason: {}", n))?
305        };
306        let metadata_for_node = crate::clusters::codec::json_util::get_octstr(args, "metadata_for_node")?;
307        let endpoint = crate::clusters::codec::json_util::get_u16(args, "endpoint")?;
308        encode_announce_ota_provider(provider_node_id, vendor_id, announcement_reason, metadata_for_node, endpoint)
309        }
310        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
311    }
312}
313
314// Typed facade (invokes + reads)
315
316/// Invoke `AnnounceOTAProvider` command on cluster `OTA Software Update Requestor`.
317pub async fn announce_ota_provider(conn: &crate::controller::Connection, endpoint: u16, provider_node_id: u64, vendor_id: u16, announcement_reason: AnnouncementReason, metadata_for_node: Vec<u8>, endpoint_: u16) -> anyhow::Result<()> {
318    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_OTA_SOFTWARE_UPDATE_REQUESTOR, crate::clusters::defs::CLUSTER_OTA_SOFTWARE_UPDATE_REQUESTOR_CMD_ID_ANNOUNCEOTAPROVIDER, &encode_announce_ota_provider(provider_node_id, vendor_id, announcement_reason, metadata_for_node, endpoint_)?).await?;
319    Ok(())
320}
321
322/// Read `DefaultOTAProviders` attribute from cluster `OTA Software Update Requestor`.
323pub async fn read_default_ota_providers(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<ProviderLocation>> {
324    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OTA_SOFTWARE_UPDATE_REQUESTOR, crate::clusters::defs::CLUSTER_OTA_SOFTWARE_UPDATE_REQUESTOR_ATTR_ID_DEFAULTOTAPROVIDERS).await?;
325    decode_default_ota_providers(&tlv)
326}
327
328/// Read `UpdatePossible` attribute from cluster `OTA Software Update Requestor`.
329pub async fn read_update_possible(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
330    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OTA_SOFTWARE_UPDATE_REQUESTOR, crate::clusters::defs::CLUSTER_OTA_SOFTWARE_UPDATE_REQUESTOR_ATTR_ID_UPDATEPOSSIBLE).await?;
331    decode_update_possible(&tlv)
332}
333
334/// Read `UpdateState` attribute from cluster `OTA Software Update Requestor`.
335pub async fn read_update_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<UpdateState> {
336    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OTA_SOFTWARE_UPDATE_REQUESTOR, crate::clusters::defs::CLUSTER_OTA_SOFTWARE_UPDATE_REQUESTOR_ATTR_ID_UPDATESTATE).await?;
337    decode_update_state(&tlv)
338}
339
340/// Read `UpdateStateProgress` attribute from cluster `OTA Software Update Requestor`.
341pub async fn read_update_state_progress(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
342    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OTA_SOFTWARE_UPDATE_REQUESTOR, crate::clusters::defs::CLUSTER_OTA_SOFTWARE_UPDATE_REQUESTOR_ATTR_ID_UPDATESTATEPROGRESS).await?;
343    decode_update_state_progress(&tlv)
344}
345
346#[derive(Debug, serde::Serialize)]
347pub struct StateTransitionEvent {
348    pub previous_state: Option<UpdateState>,
349    pub new_state: Option<UpdateState>,
350    pub reason: Option<ChangeReason>,
351    pub target_software_version: Option<u32>,
352}
353
354#[derive(Debug, serde::Serialize)]
355pub struct VersionAppliedEvent {
356    pub software_version: Option<u32>,
357    pub product_id: Option<u16>,
358}
359
360#[derive(Debug, serde::Serialize)]
361pub struct DownloadErrorEvent {
362    pub software_version: Option<u32>,
363    pub bytes_downloaded: Option<u64>,
364    pub progress_percent: Option<u8>,
365    pub platform_code: Option<i64>,
366}
367
368// Event decoders
369
370/// Decode StateTransition event (0x00, priority: info)
371pub fn decode_state_transition_event(inp: &tlv::TlvItemValue) -> anyhow::Result<StateTransitionEvent> {
372    if let tlv::TlvItemValue::List(_fields) = inp {
373        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
374        Ok(StateTransitionEvent {
375                                previous_state: item.get_int(&[0]).and_then(|v| UpdateState::from_u8(v as u8)),
376                                new_state: item.get_int(&[1]).and_then(|v| UpdateState::from_u8(v as u8)),
377                                reason: item.get_int(&[2]).and_then(|v| ChangeReason::from_u8(v as u8)),
378                                target_software_version: item.get_int(&[3]).map(|v| v as u32),
379        })
380    } else {
381        Err(anyhow::anyhow!("Expected struct fields"))
382    }
383}
384
385/// Decode VersionApplied event (0x01, priority: critical)
386pub fn decode_version_applied_event(inp: &tlv::TlvItemValue) -> anyhow::Result<VersionAppliedEvent> {
387    if let tlv::TlvItemValue::List(_fields) = inp {
388        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
389        Ok(VersionAppliedEvent {
390                                software_version: item.get_int(&[0]).map(|v| v as u32),
391                                product_id: item.get_int(&[1]).map(|v| v as u16),
392        })
393    } else {
394        Err(anyhow::anyhow!("Expected struct fields"))
395    }
396}
397
398/// Decode DownloadError event (0x02, priority: info)
399pub fn decode_download_error_event(inp: &tlv::TlvItemValue) -> anyhow::Result<DownloadErrorEvent> {
400    if let tlv::TlvItemValue::List(_fields) = inp {
401        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
402        Ok(DownloadErrorEvent {
403                                software_version: item.get_int(&[0]).map(|v| v as u32),
404                                bytes_downloaded: item.get_int(&[1]),
405                                progress_percent: item.get_int(&[2]).map(|v| v as u8),
406                                platform_code: item.get_int(&[3]).map(|v| v as i64),
407        })
408    } else {
409        Err(anyhow::anyhow!("Expected struct fields"))
410    }
411}
412