1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13pub type CommodityPriceDetail = u8;
17
18pub mod commoditypricedetail {
20 pub const DESCRIPTION: u8 = 0x01;
22 pub const COMPONENTS: u8 = 0x02;
24}
25
26#[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
46pub 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
59pub 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
70pub 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
81pub 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
90pub fn decode_current_price(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<CommodityPrice>> {
92 if let tlv::TlvItemValue::List(_fields) = inp {
93 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 {
122 Ok(None)
123 }
125}
126
127pub 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
161pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
173 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
207pub 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
220pub 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
273pub 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
320pub 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
364pub 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
372pub 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
378pub 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
384pub 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
390pub 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
396pub 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
407pub 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