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 AdjustmentCause {
16 Localoptimization = 0,
18 Gridoptimization = 1,
20}
21
22impl AdjustmentCause {
23 pub fn from_u8(value: u8) -> Option<Self> {
25 match value {
26 0 => Some(AdjustmentCause::Localoptimization),
27 1 => Some(AdjustmentCause::Gridoptimization),
28 _ => None,
29 }
30 }
31
32 pub fn to_u8(self) -> u8 {
34 self as u8
35 }
36}
37
38impl From<AdjustmentCause> for u8 {
39 fn from(val: AdjustmentCause) -> Self {
40 val as u8
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45#[repr(u8)]
46pub enum Cause {
47 Normalcompletion = 0,
49 Offline = 1,
51 Fault = 2,
53 Useroptout = 3,
55 Cancelled = 4,
57}
58
59impl Cause {
60 pub fn from_u8(value: u8) -> Option<Self> {
62 match value {
63 0 => Some(Cause::Normalcompletion),
64 1 => Some(Cause::Offline),
65 2 => Some(Cause::Fault),
66 3 => Some(Cause::Useroptout),
67 4 => Some(Cause::Cancelled),
68 _ => None,
69 }
70 }
71
72 pub fn to_u8(self) -> u8 {
74 self as u8
75 }
76}
77
78impl From<Cause> for u8 {
79 fn from(val: Cause) -> Self {
80 val as u8
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
85#[repr(u8)]
86pub enum CostType {
87 Financial = 0,
89 Ghgemissions = 1,
91 Comfort = 2,
93 Temperature = 3,
95}
96
97impl CostType {
98 pub fn from_u8(value: u8) -> Option<Self> {
100 match value {
101 0 => Some(CostType::Financial),
102 1 => Some(CostType::Ghgemissions),
103 2 => Some(CostType::Comfort),
104 3 => Some(CostType::Temperature),
105 _ => None,
106 }
107 }
108
109 pub fn to_u8(self) -> u8 {
111 self as u8
112 }
113}
114
115impl From<CostType> for u8 {
116 fn from(val: CostType) -> Self {
117 val as u8
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
122#[repr(u8)]
123pub enum ESAState {
124 Offline = 0,
126 Online = 1,
128 Fault = 2,
130 Poweradjustactive = 3,
132 Paused = 4,
134}
135
136impl ESAState {
137 pub fn from_u8(value: u8) -> Option<Self> {
139 match value {
140 0 => Some(ESAState::Offline),
141 1 => Some(ESAState::Online),
142 2 => Some(ESAState::Fault),
143 3 => Some(ESAState::Poweradjustactive),
144 4 => Some(ESAState::Paused),
145 _ => None,
146 }
147 }
148
149 pub fn to_u8(self) -> u8 {
151 self as u8
152 }
153}
154
155impl From<ESAState> for u8 {
156 fn from(val: ESAState) -> Self {
157 val as u8
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
162#[repr(u8)]
163pub enum ESAType {
164 Evse = 0,
166 Spaceheating = 1,
168 Waterheating = 2,
170 Spacecooling = 3,
172 Spaceheatingcooling = 4,
174 Batterystorage = 5,
176 Solarpv = 6,
178 Fridgefreezer = 7,
180 Washingmachine = 8,
182 Dishwasher = 9,
184 Cooking = 10,
186 Homewaterpump = 11,
188 Irrigationwaterpump = 12,
190 Poolpump = 13,
192 Other = 255,
194}
195
196impl ESAType {
197 pub fn from_u8(value: u8) -> Option<Self> {
199 match value {
200 0 => Some(ESAType::Evse),
201 1 => Some(ESAType::Spaceheating),
202 2 => Some(ESAType::Waterheating),
203 3 => Some(ESAType::Spacecooling),
204 4 => Some(ESAType::Spaceheatingcooling),
205 5 => Some(ESAType::Batterystorage),
206 6 => Some(ESAType::Solarpv),
207 7 => Some(ESAType::Fridgefreezer),
208 8 => Some(ESAType::Washingmachine),
209 9 => Some(ESAType::Dishwasher),
210 10 => Some(ESAType::Cooking),
211 11 => Some(ESAType::Homewaterpump),
212 12 => Some(ESAType::Irrigationwaterpump),
213 13 => Some(ESAType::Poolpump),
214 255 => Some(ESAType::Other),
215 _ => None,
216 }
217 }
218
219 pub fn to_u8(self) -> u8 {
221 self as u8
222 }
223}
224
225impl From<ESAType> for u8 {
226 fn from(val: ESAType) -> Self {
227 val as u8
228 }
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
232#[repr(u8)]
233pub enum ForecastUpdateReason {
234 Internaloptimization = 0,
236 Localoptimization = 1,
238 Gridoptimization = 2,
240}
241
242impl ForecastUpdateReason {
243 pub fn from_u8(value: u8) -> Option<Self> {
245 match value {
246 0 => Some(ForecastUpdateReason::Internaloptimization),
247 1 => Some(ForecastUpdateReason::Localoptimization),
248 2 => Some(ForecastUpdateReason::Gridoptimization),
249 _ => None,
250 }
251 }
252
253 pub fn to_u8(self) -> u8 {
255 self as u8
256 }
257}
258
259impl From<ForecastUpdateReason> for u8 {
260 fn from(val: ForecastUpdateReason) -> Self {
261 val as u8
262 }
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
266#[repr(u8)]
267pub enum OptOutState {
268 Nooptout = 0,
270 Localoptout = 1,
272 Gridoptout = 2,
274 Optout = 3,
276}
277
278impl OptOutState {
279 pub fn from_u8(value: u8) -> Option<Self> {
281 match value {
282 0 => Some(OptOutState::Nooptout),
283 1 => Some(OptOutState::Localoptout),
284 2 => Some(OptOutState::Gridoptout),
285 3 => Some(OptOutState::Optout),
286 _ => None,
287 }
288 }
289
290 pub fn to_u8(self) -> u8 {
292 self as u8
293 }
294}
295
296impl From<OptOutState> for u8 {
297 fn from(val: OptOutState) -> Self {
298 val as u8
299 }
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
303#[repr(u8)]
304pub enum PowerAdjustReason {
305 Noadjustment = 0,
307 Localoptimizationadjustment = 1,
309 Gridoptimizationadjustment = 2,
311}
312
313impl PowerAdjustReason {
314 pub fn from_u8(value: u8) -> Option<Self> {
316 match value {
317 0 => Some(PowerAdjustReason::Noadjustment),
318 1 => Some(PowerAdjustReason::Localoptimizationadjustment),
319 2 => Some(PowerAdjustReason::Gridoptimizationadjustment),
320 _ => None,
321 }
322 }
323
324 pub fn to_u8(self) -> u8 {
326 self as u8
327 }
328}
329
330impl From<PowerAdjustReason> for u8 {
331 fn from(val: PowerAdjustReason) -> Self {
332 val as u8
333 }
334}
335
336#[derive(Debug, serde::Serialize)]
339pub struct Constraints {
340 pub start_time: Option<u64>,
341 pub duration: Option<u32>,
342 pub nominal_power: Option<u32>,
343 pub maximum_energy: Option<u64>,
344 pub load_control: Option<i8>,
345}
346
347#[derive(Debug, serde::Serialize)]
348pub struct Cost {
349 pub cost_type: Option<CostType>,
350 pub value: Option<i32>,
351 pub decimal_points: Option<u8>,
352 pub currency: Option<u16>,
353}
354
355#[derive(Debug, serde::Serialize)]
356pub struct Forecast {
357 pub forecast_id: Option<u32>,
358 pub active_slot_number: Option<u16>,
359 pub start_time: Option<u64>,
360 pub end_time: Option<u64>,
361 pub earliest_start_time: Option<u64>,
362 pub latest_end_time: Option<u64>,
363 pub is_pausable: Option<bool>,
364 pub slots: Option<Vec<Slot>>,
365 pub forecast_update_reason: Option<ForecastUpdateReason>,
366}
367
368#[derive(Debug, serde::Serialize)]
369pub struct PowerAdjustCapability {
370 pub power_adjust_capability: Option<Vec<PowerAdjust>>,
371 pub cause: Option<PowerAdjustReason>,
372}
373
374#[derive(Debug, serde::Serialize)]
375pub struct PowerAdjust {
376 pub min_power: Option<u32>,
377 pub max_power: Option<u32>,
378 pub min_duration: Option<u32>,
379 pub max_duration: Option<u32>,
380}
381
382#[derive(Debug, serde::Serialize)]
383pub struct SlotAdjustment {
384 pub slot_index: Option<u8>,
385 pub nominal_power: Option<u32>,
386 pub duration: Option<u32>,
387}
388
389#[derive(Debug, serde::Serialize)]
390pub struct Slot {
391 pub min_duration: Option<u32>,
392 pub max_duration: Option<u32>,
393 pub default_duration: Option<u32>,
394 pub elapsed_slot_time: Option<u32>,
395 pub remaining_slot_time: Option<u32>,
396 pub slot_is_pausable: Option<bool>,
397 pub min_pause_duration: Option<u32>,
398 pub max_pause_duration: Option<u32>,
399 pub manufacturer_esa_state: Option<u16>,
400 pub nominal_power: Option<u32>,
401 pub min_power: Option<u32>,
402 pub max_power: Option<u32>,
403 pub nominal_energy: Option<u64>,
404 pub costs: Option<Vec<Cost>>,
405 pub min_power_adjustment: Option<u32>,
406 pub max_power_adjustment: Option<u32>,
407 pub min_duration_adjustment: Option<u32>,
408 pub max_duration_adjustment: Option<u32>,
409}
410
411pub fn encode_power_adjust_request(power: u32, duration: u32, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
415 let tlv = tlv::TlvItemEnc {
416 tag: 0,
417 value: tlv::TlvItemValueEnc::StructInvisible(vec![
418 (0, tlv::TlvItemValueEnc::UInt32(power)).into(),
419 (1, tlv::TlvItemValueEnc::UInt32(duration)).into(),
420 (2, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
421 ]),
422 };
423 Ok(tlv.encode()?)
424}
425
426pub fn encode_start_time_adjust_request(requested_start_time: u64, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
428 let tlv = tlv::TlvItemEnc {
429 tag: 0,
430 value: tlv::TlvItemValueEnc::StructInvisible(vec![
431 (0, tlv::TlvItemValueEnc::UInt64(requested_start_time)).into(),
432 (1, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
433 ]),
434 };
435 Ok(tlv.encode()?)
436}
437
438pub fn encode_pause_request(duration: u32, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
440 let tlv = tlv::TlvItemEnc {
441 tag: 0,
442 value: tlv::TlvItemValueEnc::StructInvisible(vec![
443 (0, tlv::TlvItemValueEnc::UInt32(duration)).into(),
444 (1, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
445 ]),
446 };
447 Ok(tlv.encode()?)
448}
449
450pub fn encode_modify_forecast_request(forecast_id: u32, slot_adjustments: Vec<SlotAdjustment>, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
452 let tlv = tlv::TlvItemEnc {
453 tag: 0,
454 value: tlv::TlvItemValueEnc::StructInvisible(vec![
455 (0, tlv::TlvItemValueEnc::UInt32(forecast_id)).into(),
456 (1, tlv::TlvItemValueEnc::Array(slot_adjustments.into_iter().map(|v| {
457 let mut fields = Vec::new();
458 if let Some(x) = v.slot_index { fields.push((0, tlv::TlvItemValueEnc::UInt8(x)).into()); }
459 if let Some(x) = v.nominal_power { fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
460 if let Some(x) = v.duration { fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
461 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
462 }).collect())).into(),
463 (2, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
464 ]),
465 };
466 Ok(tlv.encode()?)
467}
468
469pub fn encode_request_constraint_based_forecast(constraints: Vec<Constraints>, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
471 let tlv = tlv::TlvItemEnc {
472 tag: 0,
473 value: tlv::TlvItemValueEnc::StructInvisible(vec![
474 (0, tlv::TlvItemValueEnc::Array(constraints.into_iter().map(|v| {
475 let mut fields = Vec::new();
476 if let Some(x) = v.start_time { fields.push((0, tlv::TlvItemValueEnc::UInt64(x)).into()); }
477 if let Some(x) = v.duration { fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
478 if let Some(x) = v.nominal_power { fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
479 if let Some(x) = v.maximum_energy { fields.push((3, tlv::TlvItemValueEnc::UInt64(x)).into()); }
480 if let Some(x) = v.load_control { fields.push((4, tlv::TlvItemValueEnc::Int8(x)).into()); }
481 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
482 }).collect())).into(),
483 (1, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
484 ]),
485 };
486 Ok(tlv.encode()?)
487}
488
489pub fn decode_esa_type(inp: &tlv::TlvItemValue) -> anyhow::Result<ESAType> {
493 if let tlv::TlvItemValue::Int(v) = inp {
494 ESAType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
495 } else {
496 Err(anyhow::anyhow!("Expected Integer"))
497 }
498}
499
500pub fn decode_esa_can_generate(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
502 if let tlv::TlvItemValue::Bool(v) = inp {
503 Ok(*v)
504 } else {
505 Err(anyhow::anyhow!("Expected Bool"))
506 }
507}
508
509pub fn decode_esa_state(inp: &tlv::TlvItemValue) -> anyhow::Result<ESAState> {
511 if let tlv::TlvItemValue::Int(v) = inp {
512 ESAState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
513 } else {
514 Err(anyhow::anyhow!("Expected Integer"))
515 }
516}
517
518pub fn decode_abs_min_power(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
520 if let tlv::TlvItemValue::Int(v) = inp {
521 Ok(*v as u32)
522 } else {
523 Err(anyhow::anyhow!("Expected UInt32"))
524 }
525}
526
527pub fn decode_abs_max_power(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
529 if let tlv::TlvItemValue::Int(v) = inp {
530 Ok(*v as u32)
531 } else {
532 Err(anyhow::anyhow!("Expected UInt32"))
533 }
534}
535
536pub fn decode_power_adjustment_capability(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<PowerAdjustCapability>> {
538 if let tlv::TlvItemValue::List(_fields) = inp {
539 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
541 Ok(Some(PowerAdjustCapability {
542 power_adjust_capability: {
543 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[0]) {
544 let mut items = Vec::new();
545 for list_item in l {
546 items.push(PowerAdjust {
547 min_power: list_item.get_int(&[0]).map(|v| v as u32),
548 max_power: list_item.get_int(&[1]).map(|v| v as u32),
549 min_duration: list_item.get_int(&[2]).map(|v| v as u32),
550 max_duration: list_item.get_int(&[3]).map(|v| v as u32),
551 });
552 }
553 Some(items)
554 } else {
555 None
556 }
557 },
558 cause: item.get_int(&[1]).and_then(|v| PowerAdjustReason::from_u8(v as u8)),
559 }))
560 } else {
564 Ok(None)
565 }
567}
568
569pub fn decode_forecast(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Forecast>> {
571 if let tlv::TlvItemValue::List(_fields) = inp {
572 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
574 Ok(Some(Forecast {
575 forecast_id: item.get_int(&[0]).map(|v| v as u32),
576 active_slot_number: item.get_int(&[1]).map(|v| v as u16),
577 start_time: item.get_int(&[2]),
578 end_time: item.get_int(&[3]),
579 earliest_start_time: item.get_int(&[4]),
580 latest_end_time: item.get_int(&[5]),
581 is_pausable: item.get_bool(&[6]),
582 slots: {
583 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[7]) {
584 let mut items = Vec::new();
585 for list_item in l {
586 items.push(Slot {
587 min_duration: list_item.get_int(&[0]).map(|v| v as u32),
588 max_duration: list_item.get_int(&[1]).map(|v| v as u32),
589 default_duration: list_item.get_int(&[2]).map(|v| v as u32),
590 elapsed_slot_time: list_item.get_int(&[3]).map(|v| v as u32),
591 remaining_slot_time: list_item.get_int(&[4]).map(|v| v as u32),
592 slot_is_pausable: list_item.get_bool(&[5]),
593 min_pause_duration: list_item.get_int(&[6]).map(|v| v as u32),
594 max_pause_duration: list_item.get_int(&[7]).map(|v| v as u32),
595 manufacturer_esa_state: list_item.get_int(&[8]).map(|v| v as u16),
596 nominal_power: list_item.get_int(&[9]).map(|v| v as u32),
597 min_power: list_item.get_int(&[10]).map(|v| v as u32),
598 max_power: list_item.get_int(&[11]).map(|v| v as u32),
599 nominal_energy: list_item.get_int(&[12]),
600 costs: {
601 if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[13]) {
602 let mut items = Vec::new();
603 for list_item in l {
604 items.push(Cost {
605 cost_type: list_item.get_int(&[0]).and_then(|v| CostType::from_u8(v as u8)),
606 value: list_item.get_int(&[1]).map(|v| v as i32),
607 decimal_points: list_item.get_int(&[2]).map(|v| v as u8),
608 currency: list_item.get_int(&[3]).map(|v| v as u16),
609 });
610 }
611 Some(items)
612 } else {
613 None
614 }
615 },
616 min_power_adjustment: list_item.get_int(&[14]).map(|v| v as u32),
617 max_power_adjustment: list_item.get_int(&[15]).map(|v| v as u32),
618 min_duration_adjustment: list_item.get_int(&[16]).map(|v| v as u32),
619 max_duration_adjustment: list_item.get_int(&[17]).map(|v| v as u32),
620 });
621 }
622 Some(items)
623 } else {
624 None
625 }
626 },
627 forecast_update_reason: item.get_int(&[8]).and_then(|v| ForecastUpdateReason::from_u8(v as u8)),
628 }))
629 } else {
633 Ok(None)
634 }
636}
637
638pub fn decode_opt_out_state(inp: &tlv::TlvItemValue) -> anyhow::Result<OptOutState> {
640 if let tlv::TlvItemValue::Int(v) = inp {
641 OptOutState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
642 } else {
643 Err(anyhow::anyhow!("Expected Integer"))
644 }
645}
646
647
648pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
660 if cluster_id != 0x0098 {
662 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0098, got {}\"}}", cluster_id);
663 }
664
665 match attribute_id {
666 0x0000 => {
667 match decode_esa_type(tlv_value) {
668 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
669 Err(e) => format!("{{\"error\": \"{}\"}}", e),
670 }
671 }
672 0x0001 => {
673 match decode_esa_can_generate(tlv_value) {
674 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
675 Err(e) => format!("{{\"error\": \"{}\"}}", e),
676 }
677 }
678 0x0002 => {
679 match decode_esa_state(tlv_value) {
680 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
681 Err(e) => format!("{{\"error\": \"{}\"}}", e),
682 }
683 }
684 0x0003 => {
685 match decode_abs_min_power(tlv_value) {
686 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
687 Err(e) => format!("{{\"error\": \"{}\"}}", e),
688 }
689 }
690 0x0004 => {
691 match decode_abs_max_power(tlv_value) {
692 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
693 Err(e) => format!("{{\"error\": \"{}\"}}", e),
694 }
695 }
696 0x0005 => {
697 match decode_power_adjustment_capability(tlv_value) {
698 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
699 Err(e) => format!("{{\"error\": \"{}\"}}", e),
700 }
701 }
702 0x0006 => {
703 match decode_forecast(tlv_value) {
704 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
705 Err(e) => format!("{{\"error\": \"{}\"}}", e),
706 }
707 }
708 0x0007 => {
709 match decode_opt_out_state(tlv_value) {
710 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
711 Err(e) => format!("{{\"error\": \"{}\"}}", e),
712 }
713 }
714 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
715 }
716}
717
718pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
723 vec![
724 (0x0000, "ESAType"),
725 (0x0001, "ESACanGenerate"),
726 (0x0002, "ESAState"),
727 (0x0003, "AbsMinPower"),
728 (0x0004, "AbsMaxPower"),
729 (0x0005, "PowerAdjustmentCapability"),
730 (0x0006, "Forecast"),
731 (0x0007, "OptOutState"),
732 ]
733}
734
735#[derive(Debug, serde::Serialize)]
736pub struct PowerAdjustEndEvent {
737 pub cause: Option<Cause>,
738 pub duration: Option<u32>,
739 pub energy_use: Option<u64>,
740}
741
742#[derive(Debug, serde::Serialize)]
743pub struct ResumedEvent {
744 pub cause: Option<Cause>,
745}
746
747pub fn decode_power_adjust_end_event(inp: &tlv::TlvItemValue) -> anyhow::Result<PowerAdjustEndEvent> {
751 if let tlv::TlvItemValue::List(_fields) = inp {
752 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
753 Ok(PowerAdjustEndEvent {
754 cause: item.get_int(&[0]).and_then(|v| Cause::from_u8(v as u8)),
755 duration: item.get_int(&[1]).map(|v| v as u32),
756 energy_use: item.get_int(&[2]),
757 })
758 } else {
759 Err(anyhow::anyhow!("Expected struct fields"))
760 }
761}
762
763pub fn decode_resumed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ResumedEvent> {
765 if let tlv::TlvItemValue::List(_fields) = inp {
766 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
767 Ok(ResumedEvent {
768 cause: item.get_int(&[0]).and_then(|v| Cause::from_u8(v as u8)),
769 })
770 } else {
771 Err(anyhow::anyhow!("Expected struct fields"))
772 }
773}
774