1use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11pub type CommodityPriceDetail = u8;
15
16pub mod commoditypricedetail {
18 pub const DESCRIPTION: u8 = 0x01;
20 pub const COMPONENTS: u8 = 0x02;
22}
23
24#[derive(Debug, serde::Serialize)]
27pub struct CommodityPriceComponent {
28 pub price: Option<u8>,
29 pub source: Option<u8>,
30 pub description: Option<String>,
31 pub tariff_component_id: Option<u32>,
32}
33
34#[derive(Debug, serde::Serialize)]
35pub struct CommodityPrice {
36 pub period_start: Option<u64>,
37 pub period_end: Option<u64>,
38 pub price: Option<u8>,
39 pub price_level: Option<i16>,
40 pub description: Option<String>,
41 pub components: Option<Vec<CommodityPriceComponent>>,
42}
43
44pub fn encode_get_detailed_price_request(details: CommodityPriceDetail) -> anyhow::Result<Vec<u8>> {
48 let tlv = tlv::TlvItemEnc {
49 tag: 0,
50 value: tlv::TlvItemValueEnc::StructInvisible(vec![
51 (0, tlv::TlvItemValueEnc::UInt8(details)).into(),
52 ]),
53 };
54 Ok(tlv.encode()?)
55}
56
57pub fn encode_get_detailed_forecast_request(details: CommodityPriceDetail) -> anyhow::Result<Vec<u8>> {
59 let tlv = tlv::TlvItemEnc {
60 tag: 0,
61 value: tlv::TlvItemValueEnc::StructInvisible(vec![
62 (0, tlv::TlvItemValueEnc::UInt8(details)).into(),
63 ]),
64 };
65 Ok(tlv.encode()?)
66}
67
68pub fn decode_tariff_unit(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
72 if let tlv::TlvItemValue::Int(v) = inp {
73 Ok(*v as u8)
74 } else {
75 Err(anyhow::anyhow!("Expected UInt8"))
76 }
77}
78
79pub fn decode_currency(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
81 if let tlv::TlvItemValue::Int(v) = inp {
82 Ok(Some(*v as u8))
83 } else {
84 Ok(None)
85 }
86}
87
88pub fn decode_current_price(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<CommodityPrice>> {
90 if let tlv::TlvItemValue::List(_fields) = inp {
91 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
93 Ok(Some(CommodityPrice {
94 period_start: item.get_int(&[0]),
95 period_end: item.get_int(&[1]),
96 price: item.get_int(&[2]).map(|v| v as u8),
97 price_level: item.get_int(&[3]).map(|v| v as i16),
98 description: item.get_string_owned(&[4]),
99 components: {
100 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[5]) {
101 let mut items = Vec::new();
102 for list_item in l {
103 items.push(CommodityPriceComponent {
104 price: list_item.get_int(&[0]).map(|v| v as u8),
105 source: list_item.get_int(&[1]).map(|v| v as u8),
106 description: list_item.get_string_owned(&[2]),
107 tariff_component_id: list_item.get_int(&[3]).map(|v| v as u32),
108 });
109 }
110 Some(items)
111 } else {
112 None
113 }
114 },
115 }))
116 } else {
120 Ok(None)
121 }
123}
124
125pub fn decode_price_forecast(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<CommodityPrice>> {
127 let mut res = Vec::new();
128 if let tlv::TlvItemValue::List(v) = inp {
129 for item in v {
130 res.push(CommodityPrice {
131 period_start: item.get_int(&[0]),
132 period_end: item.get_int(&[1]),
133 price: item.get_int(&[2]).map(|v| v as u8),
134 price_level: item.get_int(&[3]).map(|v| v as i16),
135 description: item.get_string_owned(&[4]),
136 components: {
137 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[5]) {
138 let mut items = Vec::new();
139 for list_item in l {
140 items.push(CommodityPriceComponent {
141 price: list_item.get_int(&[0]).map(|v| v as u8),
142 source: list_item.get_int(&[1]).map(|v| v as u8),
143 description: list_item.get_string_owned(&[2]),
144 tariff_component_id: list_item.get_int(&[3]).map(|v| v as u32),
145 });
146 }
147 Some(items)
148 } else {
149 None
150 }
151 },
152 });
153 }
154 }
155 Ok(res)
156}
157
158
159pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
171 if cluster_id != 0x0095 {
173 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0095, got {}\"}}", cluster_id);
174 }
175
176 match attribute_id {
177 0x0000 => {
178 match decode_tariff_unit(tlv_value) {
179 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
180 Err(e) => format!("{{\"error\": \"{}\"}}", e),
181 }
182 }
183 0x0001 => {
184 match decode_currency(tlv_value) {
185 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
186 Err(e) => format!("{{\"error\": \"{}\"}}", e),
187 }
188 }
189 0x0002 => {
190 match decode_current_price(tlv_value) {
191 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
192 Err(e) => format!("{{\"error\": \"{}\"}}", e),
193 }
194 }
195 0x0003 => {
196 match decode_price_forecast(tlv_value) {
197 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
198 Err(e) => format!("{{\"error\": \"{}\"}}", e),
199 }
200 }
201 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
202 }
203}
204
205pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
210 vec![
211 (0x0000, "TariffUnit"),
212 (0x0001, "Currency"),
213 (0x0002, "CurrentPrice"),
214 (0x0003, "PriceForecast"),
215 ]
216}
217
218#[derive(Debug, serde::Serialize)]
219pub struct GetDetailedPriceResponse {
220 pub current_price: Option<CommodityPrice>,
221}
222
223#[derive(Debug, serde::Serialize)]
224pub struct GetDetailedForecastResponse {
225 pub price_forecast: Option<Vec<CommodityPrice>>,
226}
227
228pub fn decode_get_detailed_price_response(inp: &tlv::TlvItemValue) -> anyhow::Result<GetDetailedPriceResponse> {
232 if let tlv::TlvItemValue::List(_fields) = inp {
233 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
234 Ok(GetDetailedPriceResponse {
235 current_price: {
236 if let Some(nested_tlv) = item.get(&[0]) {
237 if let tlv::TlvItemValue::List(_) = nested_tlv {
238 let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
239 Some(CommodityPrice {
240 period_start: nested_item.get_int(&[0]),
241 period_end: nested_item.get_int(&[1]),
242 price: nested_item.get_int(&[2]).map(|v| v as u8),
243 price_level: nested_item.get_int(&[3]).map(|v| v as i16),
244 description: nested_item.get_string_owned(&[4]),
245 components: {
246 if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[5]) {
247 let mut items = Vec::new();
248 for list_item in l {
249 items.push(CommodityPriceComponent {
250 price: list_item.get_int(&[0]).map(|v| v as u8),
251 source: list_item.get_int(&[1]).map(|v| v as u8),
252 description: list_item.get_string_owned(&[2]),
253 tariff_component_id: list_item.get_int(&[3]).map(|v| v as u32),
254 });
255 }
256 Some(items)
257 } else {
258 None
259 }
260 },
261 })
262 } else {
263 None
264 }
265 } else {
266 None
267 }
268 },
269 })
270 } else {
271 Err(anyhow::anyhow!("Expected struct fields"))
272 }
273}
274
275pub fn decode_get_detailed_forecast_response(inp: &tlv::TlvItemValue) -> anyhow::Result<GetDetailedForecastResponse> {
277 if let tlv::TlvItemValue::List(_fields) = inp {
278 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
279 Ok(GetDetailedForecastResponse {
280 price_forecast: {
281 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[0]) {
282 let mut items = Vec::new();
283 for list_item in l {
284 items.push(CommodityPrice {
285 period_start: list_item.get_int(&[0]),
286 period_end: list_item.get_int(&[1]),
287 price: list_item.get_int(&[2]).map(|v| v as u8),
288 price_level: list_item.get_int(&[3]).map(|v| v as i16),
289 description: list_item.get_string_owned(&[4]),
290 components: {
291 if let Some(tlv::TlvItemValue::List(l)) = list_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 }
308 Some(items)
309 } else {
310 None
311 }
312 },
313 })
314 } else {
315 Err(anyhow::anyhow!("Expected struct fields"))
316 }
317}
318
319#[derive(Debug, serde::Serialize)]
320pub struct PriceChangeEvent {
321 pub current_price: Option<CommodityPrice>,
322}
323
324pub fn decode_price_change_event(inp: &tlv::TlvItemValue) -> anyhow::Result<PriceChangeEvent> {
328 if let tlv::TlvItemValue::List(_fields) = inp {
329 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
330 Ok(PriceChangeEvent {
331 current_price: {
332 if let Some(nested_tlv) = item.get(&[0]) {
333 if let tlv::TlvItemValue::List(_) = nested_tlv {
334 let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
335 Some(CommodityPrice {
336 period_start: nested_item.get_int(&[0]),
337 period_end: nested_item.get_int(&[1]),
338 price: nested_item.get_int(&[2]).map(|v| v as u8),
339 price_level: nested_item.get_int(&[3]).map(|v| v as i16),
340 description: nested_item.get_string_owned(&[4]),
341 components: {
342 if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[5]) {
343 let mut items = Vec::new();
344 for list_item in l {
345 items.push(CommodityPriceComponent {
346 price: list_item.get_int(&[0]).map(|v| v as u8),
347 source: list_item.get_int(&[1]).map(|v| v as u8),
348 description: list_item.get_string_owned(&[2]),
349 tariff_component_id: list_item.get_int(&[3]).map(|v| v as u32),
350 });
351 }
352 Some(items)
353 } else {
354 None
355 }
356 },
357 })
358 } else {
359 None
360 }
361 } else {
362 None
363 }
364 },
365 })
366 } else {
367 Err(anyhow::anyhow!("Expected struct fields"))
368 }
369}
370