matc/clusters/codec/
commodity_price.rs

1//! Matter TLV encoders and decoders for Commodity Price Cluster
2//! Cluster ID: 0x0095
3//!
4//! This file is automatically generated from CommodityPrice.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Bitmap definitions
14
15/// CommodityPriceDetail bitmap type
16pub type CommodityPriceDetail = u8;
17
18/// Constants for CommodityPriceDetail
19pub mod commoditypricedetail {
20    /// A textual description of a price; e.g. the name of a rate plan.
21    pub const DESCRIPTION: u8 = 0x01;
22    /// A breakdown of the component parts of a price; e.g. generation, delivery, etc.
23    pub const COMPONENTS: u8 = 0x02;
24}
25
26// Struct definitions
27
28#[derive(Debug, serde::Serialize)]
29pub struct CommodityPriceComponent {
30    pub price: Option<u8>,
31    pub source: Option<u8>,
32    pub description: Option<String>,
33    pub tariff_component_id: Option<u32>,
34}
35
36#[derive(Debug, serde::Serialize)]
37pub struct CommodityPrice {
38    pub period_start: Option<u64>,
39    pub period_end: Option<u64>,
40    pub price: Option<u8>,
41    pub price_level: Option<i16>,
42    pub description: Option<String>,
43    pub components: Option<Vec<CommodityPriceComponent>>,
44}
45
46// Command encoders
47
48/// Encode GetDetailedPriceRequest command (0x00)
49pub fn encode_get_detailed_price_request(details: CommodityPriceDetail) -> anyhow::Result<Vec<u8>> {
50    let tlv = tlv::TlvItemEnc {
51        tag: 0,
52        value: tlv::TlvItemValueEnc::StructInvisible(vec![
53        (0, tlv::TlvItemValueEnc::UInt8(details)).into(),
54        ]),
55    };
56    Ok(tlv.encode()?)
57}
58
59/// Encode GetDetailedForecastRequest command (0x02)
60pub fn encode_get_detailed_forecast_request(details: CommodityPriceDetail) -> anyhow::Result<Vec<u8>> {
61    let tlv = tlv::TlvItemEnc {
62        tag: 0,
63        value: tlv::TlvItemValueEnc::StructInvisible(vec![
64        (0, tlv::TlvItemValueEnc::UInt8(details)).into(),
65        ]),
66    };
67    Ok(tlv.encode()?)
68}
69
70// Attribute decoders
71
72/// Decode TariffUnit attribute (0x0000)
73pub fn decode_tariff_unit(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
74    if let tlv::TlvItemValue::Int(v) = inp {
75        Ok(*v as u8)
76    } else {
77        Err(anyhow::anyhow!("Expected UInt8"))
78    }
79}
80
81/// Decode Currency attribute (0x0001)
82pub fn decode_currency(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
83    if let tlv::TlvItemValue::Int(v) = inp {
84        Ok(Some(*v as u8))
85    } else {
86        Ok(None)
87    }
88}
89
90/// Decode CurrentPrice attribute (0x0002)
91pub fn decode_current_price(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<CommodityPrice>> {
92    if let tlv::TlvItemValue::List(_fields) = inp {
93        // Struct with fields
94        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
95        Ok(Some(CommodityPrice {
96                period_start: item.get_int(&[0]),
97                period_end: item.get_int(&[1]),
98                price: item.get_int(&[2]).map(|v| v as u8),
99                price_level: item.get_int(&[3]).map(|v| v as i16),
100                description: item.get_string_owned(&[4]),
101                components: {
102                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[5]) {
103                        let mut items = Vec::new();
104                        for list_item in l {
105                            items.push(CommodityPriceComponent {
106                price: list_item.get_int(&[0]).map(|v| v as u8),
107                source: list_item.get_int(&[1]).map(|v| v as u8),
108                description: list_item.get_string_owned(&[2]),
109                tariff_component_id: list_item.get_int(&[3]).map(|v| v as u32),
110                            });
111                        }
112                        Some(items)
113                    } else {
114                        None
115                    }
116                },
117        }))
118    //} else if let tlv::TlvItemValue::Null = inp {
119    //    // Null value for nullable struct
120    //    Ok(None)
121    } else {
122    Ok(None)
123    //    Err(anyhow::anyhow!("Expected struct fields or null"))
124    }
125}
126
127/// Decode PriceForecast attribute (0x0003)
128pub fn decode_price_forecast(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<CommodityPrice>> {
129    let mut res = Vec::new();
130    if let tlv::TlvItemValue::List(v) = inp {
131        for item in v {
132            res.push(CommodityPrice {
133                period_start: item.get_int(&[0]),
134                period_end: item.get_int(&[1]),
135                price: item.get_int(&[2]).map(|v| v as u8),
136                price_level: item.get_int(&[3]).map(|v| v as i16),
137                description: item.get_string_owned(&[4]),
138                components: {
139                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[5]) {
140                        let mut items = Vec::new();
141                        for list_item in l {
142                            items.push(CommodityPriceComponent {
143                price: list_item.get_int(&[0]).map(|v| v as u8),
144                source: list_item.get_int(&[1]).map(|v| v as u8),
145                description: list_item.get_string_owned(&[2]),
146                tariff_component_id: list_item.get_int(&[3]).map(|v| v as u32),
147                            });
148                        }
149                        Some(items)
150                    } else {
151                        None
152                    }
153                },
154            });
155        }
156    }
157    Ok(res)
158}
159
160
161// JSON dispatcher function
162
163/// Decode attribute value and return as JSON string
164///
165/// # Parameters
166/// * `cluster_id` - The cluster identifier
167/// * `attribute_id` - The attribute identifier
168/// * `tlv_value` - The TLV value to decode
169///
170/// # Returns
171/// JSON string representation of the decoded value or error
172pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
173    // Verify this is the correct cluster
174    if cluster_id != 0x0095 {
175        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0095, got {}\"}}", cluster_id);
176    }
177
178    match attribute_id {
179        0x0000 => {
180            match decode_tariff_unit(tlv_value) {
181                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
182                Err(e) => format!("{{\"error\": \"{}\"}}", e),
183            }
184        }
185        0x0001 => {
186            match decode_currency(tlv_value) {
187                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
188                Err(e) => format!("{{\"error\": \"{}\"}}", e),
189            }
190        }
191        0x0002 => {
192            match decode_current_price(tlv_value) {
193                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
194                Err(e) => format!("{{\"error\": \"{}\"}}", e),
195            }
196        }
197        0x0003 => {
198            match decode_price_forecast(tlv_value) {
199                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
200                Err(e) => format!("{{\"error\": \"{}\"}}", e),
201            }
202        }
203        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
204    }
205}
206
207/// Get list of all attributes supported by this cluster
208///
209/// # Returns
210/// Vector of tuples containing (attribute_id, attribute_name)
211pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
212    vec![
213        (0x0000, "TariffUnit"),
214        (0x0001, "Currency"),
215        (0x0002, "CurrentPrice"),
216        (0x0003, "PriceForecast"),
217    ]
218}
219
220// Command listing
221
222pub fn get_command_list() -> Vec<(u32, &'static str)> {
223    vec![
224        (0x00, "GetDetailedPriceRequest"),
225        (0x02, "GetDetailedForecastRequest"),
226    ]
227}
228
229pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
230    match cmd_id {
231        0x00 => Some("GetDetailedPriceRequest"),
232        0x02 => Some("GetDetailedForecastRequest"),
233        _ => None,
234    }
235}
236
237pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
238    match cmd_id {
239        0x00 => Some(vec![
240            crate::clusters::codec::CommandField { tag: 0, name: "details", kind: crate::clusters::codec::FieldKind::Bitmap { name: "CommodityPriceDetail", bits: &[(1, "DESCRIPTION"), (2, "COMPONENTS")] }, optional: false, nullable: false },
241        ]),
242        0x02 => Some(vec![
243            crate::clusters::codec::CommandField { tag: 0, name: "details", kind: crate::clusters::codec::FieldKind::Bitmap { name: "CommodityPriceDetail", bits: &[(1, "DESCRIPTION"), (2, "COMPONENTS")] }, optional: false, nullable: false },
244        ]),
245        _ => None,
246    }
247}
248
249pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
250    match cmd_id {
251        0x00 => {
252        let details = crate::clusters::codec::json_util::get_u8(args, "details")?;
253        encode_get_detailed_price_request(details)
254        }
255        0x02 => {
256        let details = crate::clusters::codec::json_util::get_u8(args, "details")?;
257        encode_get_detailed_forecast_request(details)
258        }
259        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
260    }
261}
262
263#[derive(Debug, serde::Serialize)]
264pub struct GetDetailedPriceResponse {
265    pub current_price: Option<CommodityPrice>,
266}
267
268#[derive(Debug, serde::Serialize)]
269pub struct GetDetailedForecastResponse {
270    pub price_forecast: Option<Vec<CommodityPrice>>,
271}
272
273// Command response decoders
274
275/// Decode GetDetailedPriceResponse command response (01)
276pub fn decode_get_detailed_price_response(inp: &tlv::TlvItemValue) -> anyhow::Result<GetDetailedPriceResponse> {
277    if let tlv::TlvItemValue::List(_fields) = inp {
278        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
279        Ok(GetDetailedPriceResponse {
280                current_price: {
281                    if let Some(nested_tlv) = item.get(&[0]) {
282                        if let tlv::TlvItemValue::List(_) = nested_tlv {
283                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
284                            Some(CommodityPrice {
285                period_start: nested_item.get_int(&[0]),
286                period_end: nested_item.get_int(&[1]),
287                price: nested_item.get_int(&[2]).map(|v| v as u8),
288                price_level: nested_item.get_int(&[3]).map(|v| v as i16),
289                description: nested_item.get_string_owned(&[4]),
290                components: {
291                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[5]) {
292                        let mut items = Vec::new();
293                        for list_item in l {
294                            items.push(CommodityPriceComponent {
295                price: list_item.get_int(&[0]).map(|v| v as u8),
296                source: list_item.get_int(&[1]).map(|v| v as u8),
297                description: list_item.get_string_owned(&[2]),
298                tariff_component_id: list_item.get_int(&[3]).map(|v| v as u32),
299                            });
300                        }
301                        Some(items)
302                    } else {
303                        None
304                    }
305                },
306                            })
307                        } else {
308                            None
309                        }
310                    } else {
311                        None
312                    }
313                },
314        })
315    } else {
316        Err(anyhow::anyhow!("Expected struct fields"))
317    }
318}
319
320/// Decode GetDetailedForecastResponse command response (03)
321pub fn decode_get_detailed_forecast_response(inp: &tlv::TlvItemValue) -> anyhow::Result<GetDetailedForecastResponse> {
322    if let tlv::TlvItemValue::List(_fields) = inp {
323        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
324        Ok(GetDetailedForecastResponse {
325                price_forecast: {
326                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[0]) {
327                        let mut items = Vec::new();
328                        for list_item in l {
329                            items.push(CommodityPrice {
330                period_start: list_item.get_int(&[0]),
331                period_end: list_item.get_int(&[1]),
332                price: list_item.get_int(&[2]).map(|v| v as u8),
333                price_level: list_item.get_int(&[3]).map(|v| v as i16),
334                description: list_item.get_string_owned(&[4]),
335                components: {
336                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[5]) {
337                        let mut items = Vec::new();
338                        for list_item in l {
339                            items.push(CommodityPriceComponent {
340                price: list_item.get_int(&[0]).map(|v| v as u8),
341                source: list_item.get_int(&[1]).map(|v| v as u8),
342                description: list_item.get_string_owned(&[2]),
343                tariff_component_id: list_item.get_int(&[3]).map(|v| v as u32),
344                            });
345                        }
346                        Some(items)
347                    } else {
348                        None
349                    }
350                },
351                            });
352                        }
353                        Some(items)
354                    } else {
355                        None
356                    }
357                },
358        })
359    } else {
360        Err(anyhow::anyhow!("Expected struct fields"))
361    }
362}
363
364// Typed facade (invokes + reads)
365
366/// Invoke `GetDetailedPriceRequest` command on cluster `Commodity Price`.
367pub async fn get_detailed_price_request(conn: &crate::controller::Connection, endpoint: u16, details: CommodityPriceDetail) -> anyhow::Result<GetDetailedPriceResponse> {
368    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_COMMODITY_PRICE, crate::clusters::defs::CLUSTER_COMMODITY_PRICE_CMD_ID_GETDETAILEDPRICEREQUEST, &encode_get_detailed_price_request(details)?).await?;
369    decode_get_detailed_price_response(&tlv)
370}
371
372/// Invoke `GetDetailedForecastRequest` command on cluster `Commodity Price`.
373pub async fn get_detailed_forecast_request(conn: &crate::controller::Connection, endpoint: u16, details: CommodityPriceDetail) -> anyhow::Result<GetDetailedForecastResponse> {
374    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_COMMODITY_PRICE, crate::clusters::defs::CLUSTER_COMMODITY_PRICE_CMD_ID_GETDETAILEDFORECASTREQUEST, &encode_get_detailed_forecast_request(details)?).await?;
375    decode_get_detailed_forecast_response(&tlv)
376}
377
378/// Read `TariffUnit` attribute from cluster `Commodity Price`.
379pub async fn read_tariff_unit(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
380    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_COMMODITY_PRICE, crate::clusters::defs::CLUSTER_COMMODITY_PRICE_ATTR_ID_TARIFFUNIT).await?;
381    decode_tariff_unit(&tlv)
382}
383
384/// Read `Currency` attribute from cluster `Commodity Price`.
385pub async fn read_currency(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
386    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_COMMODITY_PRICE, crate::clusters::defs::CLUSTER_COMMODITY_PRICE_ATTR_ID_CURRENCY).await?;
387    decode_currency(&tlv)
388}
389
390/// Read `CurrentPrice` attribute from cluster `Commodity Price`.
391pub async fn read_current_price(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<CommodityPrice>> {
392    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_COMMODITY_PRICE, crate::clusters::defs::CLUSTER_COMMODITY_PRICE_ATTR_ID_CURRENTPRICE).await?;
393    decode_current_price(&tlv)
394}
395
396/// Read `PriceForecast` attribute from cluster `Commodity Price`.
397pub async fn read_price_forecast(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<CommodityPrice>> {
398    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_COMMODITY_PRICE, crate::clusters::defs::CLUSTER_COMMODITY_PRICE_ATTR_ID_PRICEFORECAST).await?;
399    decode_price_forecast(&tlv)
400}
401
402#[derive(Debug, serde::Serialize)]
403pub struct PriceChangeEvent {
404    pub current_price: Option<CommodityPrice>,
405}
406
407// Event decoders
408
409/// Decode PriceChange event (0x00, priority: info)
410pub fn decode_price_change_event(inp: &tlv::TlvItemValue) -> anyhow::Result<PriceChangeEvent> {
411    if let tlv::TlvItemValue::List(_fields) = inp {
412        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
413        Ok(PriceChangeEvent {
414                                current_price: {
415                    if let Some(nested_tlv) = item.get(&[0]) {
416                        if let tlv::TlvItemValue::List(_) = nested_tlv {
417                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
418                            Some(CommodityPrice {
419                period_start: nested_item.get_int(&[0]),
420                period_end: nested_item.get_int(&[1]),
421                price: nested_item.get_int(&[2]).map(|v| v as u8),
422                price_level: nested_item.get_int(&[3]).map(|v| v as i16),
423                description: nested_item.get_string_owned(&[4]),
424                components: {
425                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[5]) {
426                        let mut items = Vec::new();
427                        for list_item in l {
428                            items.push(CommodityPriceComponent {
429                price: list_item.get_int(&[0]).map(|v| v as u8),
430                source: list_item.get_int(&[1]).map(|v| v as u8),
431                description: list_item.get_string_owned(&[2]),
432                tariff_component_id: list_item.get_int(&[3]).map(|v| v as u32),
433                            });
434                        }
435                        Some(items)
436                    } else {
437                        None
438                    }
439                },
440                            })
441                        } else {
442                            None
443                        }
444                    } else {
445                        None
446                    }
447                },
448        })
449    } else {
450        Err(anyhow::anyhow!("Expected struct fields"))
451    }
452}
453