matc/clusters/codec/
energy_evse.rs

1//! Matter TLV encoders and decoders for Energy EVSE Cluster
2//! Cluster ID: 0x0099
3//!
4//! This file is automatically generated from EnergyEVSE.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Import serialization helpers for octet strings
14use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16// Enum definitions
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[repr(u8)]
20pub enum EnergyTransferStoppedReason {
21    /// The EV decided to stop
22    Evstopped = 0,
23    /// The EVSE decided to stop
24    Evsestopped = 1,
25    /// An other unknown reason
26    Other = 2,
27}
28
29impl EnergyTransferStoppedReason {
30    /// Convert from u8 value
31    pub fn from_u8(value: u8) -> Option<Self> {
32        match value {
33            0 => Some(EnergyTransferStoppedReason::Evstopped),
34            1 => Some(EnergyTransferStoppedReason::Evsestopped),
35            2 => Some(EnergyTransferStoppedReason::Other),
36            _ => None,
37        }
38    }
39
40    /// Convert to u8 value
41    pub fn to_u8(self) -> u8 {
42        self as u8
43    }
44}
45
46impl From<EnergyTransferStoppedReason> for u8 {
47    fn from(val: EnergyTransferStoppedReason) -> Self {
48        val as u8
49    }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
53#[repr(u8)]
54pub enum FaultState {
55    /// The EVSE is not in an error state.
56    Noerror = 0,
57    /// The EVSE is unable to obtain electrical measurements.
58    Meterfailure = 1,
59    /// The EVSE input voltage level is too high.
60    Overvoltage = 2,
61    /// The EVSE input voltage level is too low.
62    Undervoltage = 3,
63    /// The EVSE detected charging current higher than allowed by charger.
64    Overcurrent = 4,
65    /// The EVSE detected voltage on charging pins when the contactor is open.
66    Contactwetfailure = 5,
67    /// The EVSE detected absence of voltage after enabling contactor.
68    Contactdryfailure = 6,
69    /// The EVSE has an unbalanced current supply.
70    Groundfault = 7,
71    /// The EVSE has detected a loss in power.
72    Powerloss = 8,
73    /// The EVSE has detected another power quality issue (e.g. phase imbalance).
74    Powerquality = 9,
75    /// The EVSE pilot signal amplitude short circuited to ground.
76    Pilotshortcircuit = 10,
77    /// The emergency stop button was pressed.
78    Emergencystop = 11,
79    /// The EVSE detected that the cable has been disconnected.
80    Evdisconnected = 12,
81    /// The EVSE could not determine proper power supply level.
82    Wrongpowersupply = 13,
83    /// The EVSE detected Live and Neutral are swapped.
84    Liveneutralswap = 14,
85    /// The EVSE internal temperature is too high.
86    Overtemperature = 15,
87    /// Any other reason.
88    Other = 255,
89}
90
91impl FaultState {
92    /// Convert from u8 value
93    pub fn from_u8(value: u8) -> Option<Self> {
94        match value {
95            0 => Some(FaultState::Noerror),
96            1 => Some(FaultState::Meterfailure),
97            2 => Some(FaultState::Overvoltage),
98            3 => Some(FaultState::Undervoltage),
99            4 => Some(FaultState::Overcurrent),
100            5 => Some(FaultState::Contactwetfailure),
101            6 => Some(FaultState::Contactdryfailure),
102            7 => Some(FaultState::Groundfault),
103            8 => Some(FaultState::Powerloss),
104            9 => Some(FaultState::Powerquality),
105            10 => Some(FaultState::Pilotshortcircuit),
106            11 => Some(FaultState::Emergencystop),
107            12 => Some(FaultState::Evdisconnected),
108            13 => Some(FaultState::Wrongpowersupply),
109            14 => Some(FaultState::Liveneutralswap),
110            15 => Some(FaultState::Overtemperature),
111            255 => Some(FaultState::Other),
112            _ => None,
113        }
114    }
115
116    /// Convert to u8 value
117    pub fn to_u8(self) -> u8 {
118        self as u8
119    }
120}
121
122impl From<FaultState> for u8 {
123    fn from(val: FaultState) -> Self {
124        val as u8
125    }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
129#[repr(u8)]
130pub enum State {
131    /// The EV is not plugged in.
132    Notpluggedin = 0,
133    /// The EV is plugged in, but not demanding current.
134    Pluggedinnodemand = 1,
135    /// The EV is plugged in and is demanding current, but EVSE is not allowing current to flow.
136    Pluggedindemand = 2,
137    /// The EV is plugged in, charging is in progress, and current is flowing
138    Pluggedincharging = 3,
139    /// The EV is plugged in, discharging is in progress, and current is flowing
140    Pluggedindischarging = 4,
141    /// The EVSE is transitioning from any plugged-in state to NotPluggedIn
142    Sessionending = 5,
143    /// There is a fault, further details in the FaultState attribute
144    Fault = 6,
145}
146
147impl State {
148    /// Convert from u8 value
149    pub fn from_u8(value: u8) -> Option<Self> {
150        match value {
151            0 => Some(State::Notpluggedin),
152            1 => Some(State::Pluggedinnodemand),
153            2 => Some(State::Pluggedindemand),
154            3 => Some(State::Pluggedincharging),
155            4 => Some(State::Pluggedindischarging),
156            5 => Some(State::Sessionending),
157            6 => Some(State::Fault),
158            _ => None,
159        }
160    }
161
162    /// Convert to u8 value
163    pub fn to_u8(self) -> u8 {
164        self as u8
165    }
166}
167
168impl From<State> for u8 {
169    fn from(val: State) -> Self {
170        val as u8
171    }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
175#[repr(u8)]
176pub enum SupplyState {
177    /// The EV is not currently allowed to charge or discharge
178    Disabled = 0,
179    /// The EV is currently allowed to charge
180    Chargingenabled = 1,
181    /// The EV is currently allowed to discharge
182    Dischargingenabled = 2,
183    /// The EV is not currently allowed to charge or discharge due to an error. The error must be cleared before operation can continue.
184    Disablederror = 3,
185    /// The EV is not currently allowed to charge or discharge due to self-diagnostics mode.
186    Disableddiagnostics = 4,
187    /// The EV is currently allowed to charge and discharge
188    Enabled = 5,
189}
190
191impl SupplyState {
192    /// Convert from u8 value
193    pub fn from_u8(value: u8) -> Option<Self> {
194        match value {
195            0 => Some(SupplyState::Disabled),
196            1 => Some(SupplyState::Chargingenabled),
197            2 => Some(SupplyState::Dischargingenabled),
198            3 => Some(SupplyState::Disablederror),
199            4 => Some(SupplyState::Disableddiagnostics),
200            5 => Some(SupplyState::Enabled),
201            _ => None,
202        }
203    }
204
205    /// Convert to u8 value
206    pub fn to_u8(self) -> u8 {
207        self as u8
208    }
209}
210
211impl From<SupplyState> for u8 {
212    fn from(val: SupplyState) -> Self {
213        val as u8
214    }
215}
216
217// Bitmap definitions
218
219/// TargetDayOfWeek bitmap type
220pub type TargetDayOfWeek = u8;
221
222/// Constants for TargetDayOfWeek
223pub mod targetdayofweek {
224    /// Sunday
225    pub const SUNDAY: u8 = 0x01;
226    /// Monday
227    pub const MONDAY: u8 = 0x02;
228    /// Tuesday
229    pub const TUESDAY: u8 = 0x04;
230    /// Wednesday
231    pub const WEDNESDAY: u8 = 0x08;
232    /// Thursday
233    pub const THURSDAY: u8 = 0x10;
234    /// Friday
235    pub const FRIDAY: u8 = 0x20;
236    /// Saturday
237    pub const SATURDAY: u8 = 0x40;
238}
239
240// Struct definitions
241
242#[derive(Debug, serde::Serialize)]
243pub struct ChargingTargetSchedule {
244    pub day_of_week_for_sequence: Option<TargetDayOfWeek>,
245    pub charging_targets: Option<Vec<ChargingTarget>>,
246}
247
248#[derive(Debug, serde::Serialize)]
249pub struct ChargingTarget {
250    pub target_time_minutes_past_midnight: Option<u16>,
251    pub target_so_c: Option<u8>,
252    pub added_energy: Option<u64>,
253}
254
255// Command encoders
256
257/// Encode EnableCharging command (0x02)
258pub fn encode_enable_charging(charging_enabled_until: Option<u64>, minimum_charge_current: u8, maximum_charge_current: u8) -> anyhow::Result<Vec<u8>> {
259    let tlv = tlv::TlvItemEnc {
260        tag: 0,
261        value: tlv::TlvItemValueEnc::StructInvisible(vec![
262        (0, tlv::TlvItemValueEnc::UInt64(charging_enabled_until.unwrap_or(0))).into(),
263        (1, tlv::TlvItemValueEnc::UInt8(minimum_charge_current)).into(),
264        (2, tlv::TlvItemValueEnc::UInt8(maximum_charge_current)).into(),
265        ]),
266    };
267    Ok(tlv.encode()?)
268}
269
270/// Encode EnableDischarging command (0x03)
271pub fn encode_enable_discharging(discharging_enabled_until: Option<u64>, maximum_discharge_current: u8) -> anyhow::Result<Vec<u8>> {
272    let tlv = tlv::TlvItemEnc {
273        tag: 0,
274        value: tlv::TlvItemValueEnc::StructInvisible(vec![
275        (0, tlv::TlvItemValueEnc::UInt64(discharging_enabled_until.unwrap_or(0))).into(),
276        (1, tlv::TlvItemValueEnc::UInt8(maximum_discharge_current)).into(),
277        ]),
278    };
279    Ok(tlv.encode()?)
280}
281
282/// Encode SetTargets command (0x05)
283pub fn encode_set_targets(charging_target_schedules: Vec<ChargingTargetSchedule>) -> anyhow::Result<Vec<u8>> {
284    let tlv = tlv::TlvItemEnc {
285        tag: 0,
286        value: tlv::TlvItemValueEnc::StructInvisible(vec![
287        (0, tlv::TlvItemValueEnc::Array(charging_target_schedules.into_iter().map(|v| {
288                    let mut fields = Vec::new();
289                    if let Some(x) = v.day_of_week_for_sequence { fields.push((0, tlv::TlvItemValueEnc::UInt8(x)).into()); }
290                    if let Some(listv) = v.charging_targets {
291                        let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
292                            let mut nested_fields = Vec::new();
293                                if let Some(x) = inner.target_time_minutes_past_midnight { nested_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
294                                // TODO: encoding for field target_so_c (percent) not implemented
295                                if let Some(x) = inner.added_energy { nested_fields.push((2, tlv::TlvItemValueEnc::UInt64(x)).into()); }
296                            (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
297                        }).collect();
298                        fields.push((1, tlv::TlvItemValueEnc::Array(inner_vec)).into());
299                    }
300                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
301                }).collect())).into(),
302        ]),
303    };
304    Ok(tlv.encode()?)
305}
306
307// Attribute decoders
308
309/// Decode State attribute (0x0000)
310pub fn decode_state(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<State>> {
311    if let tlv::TlvItemValue::Int(v) = inp {
312        Ok(State::from_u8(*v as u8))
313    } else {
314        Ok(None)
315    }
316}
317
318/// Decode SupplyState attribute (0x0001)
319pub fn decode_supply_state(inp: &tlv::TlvItemValue) -> anyhow::Result<SupplyState> {
320    if let tlv::TlvItemValue::Int(v) = inp {
321        SupplyState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
322    } else {
323        Err(anyhow::anyhow!("Expected Integer"))
324    }
325}
326
327/// Decode FaultState attribute (0x0002)
328pub fn decode_fault_state(inp: &tlv::TlvItemValue) -> anyhow::Result<FaultState> {
329    if let tlv::TlvItemValue::Int(v) = inp {
330        FaultState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
331    } else {
332        Err(anyhow::anyhow!("Expected Integer"))
333    }
334}
335
336/// Decode ChargingEnabledUntil attribute (0x0003)
337pub fn decode_charging_enabled_until(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
338    if let tlv::TlvItemValue::Int(v) = inp {
339        Ok(Some(*v))
340    } else {
341        Ok(None)
342    }
343}
344
345/// Decode DischargingEnabledUntil attribute (0x0004)
346pub fn decode_discharging_enabled_until(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
347    if let tlv::TlvItemValue::Int(v) = inp {
348        Ok(Some(*v))
349    } else {
350        Ok(None)
351    }
352}
353
354/// Decode CircuitCapacity attribute (0x0005)
355pub fn decode_circuit_capacity(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
356    if let tlv::TlvItemValue::Int(v) = inp {
357        Ok(*v as u8)
358    } else {
359        Err(anyhow::anyhow!("Expected UInt8"))
360    }
361}
362
363/// Decode MinimumChargeCurrent attribute (0x0006)
364pub fn decode_minimum_charge_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
365    if let tlv::TlvItemValue::Int(v) = inp {
366        Ok(*v as u8)
367    } else {
368        Err(anyhow::anyhow!("Expected UInt8"))
369    }
370}
371
372/// Decode MaximumChargeCurrent attribute (0x0007)
373pub fn decode_maximum_charge_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
374    if let tlv::TlvItemValue::Int(v) = inp {
375        Ok(*v as u8)
376    } else {
377        Err(anyhow::anyhow!("Expected UInt8"))
378    }
379}
380
381/// Decode MaximumDischargeCurrent attribute (0x0008)
382pub fn decode_maximum_discharge_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
383    if let tlv::TlvItemValue::Int(v) = inp {
384        Ok(*v as u8)
385    } else {
386        Err(anyhow::anyhow!("Expected UInt8"))
387    }
388}
389
390/// Decode UserMaximumChargeCurrent attribute (0x0009)
391pub fn decode_user_maximum_charge_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
392    if let tlv::TlvItemValue::Int(v) = inp {
393        Ok(*v as u8)
394    } else {
395        Err(anyhow::anyhow!("Expected UInt8"))
396    }
397}
398
399/// Decode RandomizationDelayWindow attribute (0x000A)
400pub fn decode_randomization_delay_window(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
401    if let tlv::TlvItemValue::Int(v) = inp {
402        Ok(*v as u32)
403    } else {
404        Err(anyhow::anyhow!("Expected UInt32"))
405    }
406}
407
408/// Decode NextChargeStartTime attribute (0x0023)
409pub fn decode_next_charge_start_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
410    if let tlv::TlvItemValue::Int(v) = inp {
411        Ok(Some(*v))
412    } else {
413        Ok(None)
414    }
415}
416
417/// Decode NextChargeTargetTime attribute (0x0024)
418pub fn decode_next_charge_target_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
419    if let tlv::TlvItemValue::Int(v) = inp {
420        Ok(Some(*v))
421    } else {
422        Ok(None)
423    }
424}
425
426/// Decode NextChargeRequiredEnergy attribute (0x0025)
427pub fn decode_next_charge_required_energy(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
428    if let tlv::TlvItemValue::Int(v) = inp {
429        Ok(Some(*v))
430    } else {
431        Ok(None)
432    }
433}
434
435/// Decode NextChargeTargetSoC attribute (0x0026)
436pub fn decode_next_charge_target_so_c(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
437    if let tlv::TlvItemValue::Int(v) = inp {
438        Ok(Some(*v as u8))
439    } else {
440        Ok(None)
441    }
442}
443
444/// Decode ApproximateEVEfficiency attribute (0x0027)
445pub fn decode_approximate_ev_efficiency(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u16>> {
446    if let tlv::TlvItemValue::Int(v) = inp {
447        Ok(Some(*v as u16))
448    } else {
449        Ok(None)
450    }
451}
452
453/// Decode StateOfCharge attribute (0x0030)
454pub fn decode_state_of_charge(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
455    if let tlv::TlvItemValue::Int(v) = inp {
456        Ok(Some(*v as u8))
457    } else {
458        Ok(None)
459    }
460}
461
462/// Decode BatteryCapacity attribute (0x0031)
463pub fn decode_battery_capacity(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
464    if let tlv::TlvItemValue::Int(v) = inp {
465        Ok(Some(*v))
466    } else {
467        Ok(None)
468    }
469}
470
471/// Decode VehicleID attribute (0x0032)
472pub fn decode_vehicle_id(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<String>> {
473    if let tlv::TlvItemValue::String(v) = inp {
474        Ok(Some(v.clone()))
475    } else {
476        Ok(None)
477    }
478}
479
480/// Decode SessionID attribute (0x0040)
481pub fn decode_session_id(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
482    if let tlv::TlvItemValue::Int(v) = inp {
483        Ok(Some(*v as u32))
484    } else {
485        Ok(None)
486    }
487}
488
489/// Decode SessionDuration attribute (0x0041)
490pub fn decode_session_duration(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
491    if let tlv::TlvItemValue::Int(v) = inp {
492        Ok(Some(*v as u32))
493    } else {
494        Ok(None)
495    }
496}
497
498/// Decode SessionEnergyCharged attribute (0x0042)
499pub fn decode_session_energy_charged(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
500    if let tlv::TlvItemValue::Int(v) = inp {
501        Ok(Some(*v))
502    } else {
503        Ok(None)
504    }
505}
506
507/// Decode SessionEnergyDischarged attribute (0x0043)
508pub fn decode_session_energy_discharged(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
509    if let tlv::TlvItemValue::Int(v) = inp {
510        Ok(Some(*v))
511    } else {
512        Ok(None)
513    }
514}
515
516
517// JSON dispatcher function
518
519/// Decode attribute value and return as JSON string
520///
521/// # Parameters
522/// * `cluster_id` - The cluster identifier
523/// * `attribute_id` - The attribute identifier
524/// * `tlv_value` - The TLV value to decode
525///
526/// # Returns
527/// JSON string representation of the decoded value or error
528pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
529    // Verify this is the correct cluster
530    if cluster_id != 0x0099 {
531        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0099, got {}\"}}", cluster_id);
532    }
533
534    match attribute_id {
535        0x0000 => {
536            match decode_state(tlv_value) {
537                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
538                Err(e) => format!("{{\"error\": \"{}\"}}", e),
539            }
540        }
541        0x0001 => {
542            match decode_supply_state(tlv_value) {
543                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
544                Err(e) => format!("{{\"error\": \"{}\"}}", e),
545            }
546        }
547        0x0002 => {
548            match decode_fault_state(tlv_value) {
549                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
550                Err(e) => format!("{{\"error\": \"{}\"}}", e),
551            }
552        }
553        0x0003 => {
554            match decode_charging_enabled_until(tlv_value) {
555                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
556                Err(e) => format!("{{\"error\": \"{}\"}}", e),
557            }
558        }
559        0x0004 => {
560            match decode_discharging_enabled_until(tlv_value) {
561                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
562                Err(e) => format!("{{\"error\": \"{}\"}}", e),
563            }
564        }
565        0x0005 => {
566            match decode_circuit_capacity(tlv_value) {
567                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
568                Err(e) => format!("{{\"error\": \"{}\"}}", e),
569            }
570        }
571        0x0006 => {
572            match decode_minimum_charge_current(tlv_value) {
573                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
574                Err(e) => format!("{{\"error\": \"{}\"}}", e),
575            }
576        }
577        0x0007 => {
578            match decode_maximum_charge_current(tlv_value) {
579                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
580                Err(e) => format!("{{\"error\": \"{}\"}}", e),
581            }
582        }
583        0x0008 => {
584            match decode_maximum_discharge_current(tlv_value) {
585                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
586                Err(e) => format!("{{\"error\": \"{}\"}}", e),
587            }
588        }
589        0x0009 => {
590            match decode_user_maximum_charge_current(tlv_value) {
591                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
592                Err(e) => format!("{{\"error\": \"{}\"}}", e),
593            }
594        }
595        0x000A => {
596            match decode_randomization_delay_window(tlv_value) {
597                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
598                Err(e) => format!("{{\"error\": \"{}\"}}", e),
599            }
600        }
601        0x0023 => {
602            match decode_next_charge_start_time(tlv_value) {
603                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
604                Err(e) => format!("{{\"error\": \"{}\"}}", e),
605            }
606        }
607        0x0024 => {
608            match decode_next_charge_target_time(tlv_value) {
609                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
610                Err(e) => format!("{{\"error\": \"{}\"}}", e),
611            }
612        }
613        0x0025 => {
614            match decode_next_charge_required_energy(tlv_value) {
615                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
616                Err(e) => format!("{{\"error\": \"{}\"}}", e),
617            }
618        }
619        0x0026 => {
620            match decode_next_charge_target_so_c(tlv_value) {
621                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
622                Err(e) => format!("{{\"error\": \"{}\"}}", e),
623            }
624        }
625        0x0027 => {
626            match decode_approximate_ev_efficiency(tlv_value) {
627                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
628                Err(e) => format!("{{\"error\": \"{}\"}}", e),
629            }
630        }
631        0x0030 => {
632            match decode_state_of_charge(tlv_value) {
633                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
634                Err(e) => format!("{{\"error\": \"{}\"}}", e),
635            }
636        }
637        0x0031 => {
638            match decode_battery_capacity(tlv_value) {
639                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
640                Err(e) => format!("{{\"error\": \"{}\"}}", e),
641            }
642        }
643        0x0032 => {
644            match decode_vehicle_id(tlv_value) {
645                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
646                Err(e) => format!("{{\"error\": \"{}\"}}", e),
647            }
648        }
649        0x0040 => {
650            match decode_session_id(tlv_value) {
651                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
652                Err(e) => format!("{{\"error\": \"{}\"}}", e),
653            }
654        }
655        0x0041 => {
656            match decode_session_duration(tlv_value) {
657                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
658                Err(e) => format!("{{\"error\": \"{}\"}}", e),
659            }
660        }
661        0x0042 => {
662            match decode_session_energy_charged(tlv_value) {
663                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
664                Err(e) => format!("{{\"error\": \"{}\"}}", e),
665            }
666        }
667        0x0043 => {
668            match decode_session_energy_discharged(tlv_value) {
669                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
670                Err(e) => format!("{{\"error\": \"{}\"}}", e),
671            }
672        }
673        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
674    }
675}
676
677/// Get list of all attributes supported by this cluster
678///
679/// # Returns
680/// Vector of tuples containing (attribute_id, attribute_name)
681pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
682    vec![
683        (0x0000, "State"),
684        (0x0001, "SupplyState"),
685        (0x0002, "FaultState"),
686        (0x0003, "ChargingEnabledUntil"),
687        (0x0004, "DischargingEnabledUntil"),
688        (0x0005, "CircuitCapacity"),
689        (0x0006, "MinimumChargeCurrent"),
690        (0x0007, "MaximumChargeCurrent"),
691        (0x0008, "MaximumDischargeCurrent"),
692        (0x0009, "UserMaximumChargeCurrent"),
693        (0x000A, "RandomizationDelayWindow"),
694        (0x0023, "NextChargeStartTime"),
695        (0x0024, "NextChargeTargetTime"),
696        (0x0025, "NextChargeRequiredEnergy"),
697        (0x0026, "NextChargeTargetSoC"),
698        (0x0027, "ApproximateEVEfficiency"),
699        (0x0030, "StateOfCharge"),
700        (0x0031, "BatteryCapacity"),
701        (0x0032, "VehicleID"),
702        (0x0040, "SessionID"),
703        (0x0041, "SessionDuration"),
704        (0x0042, "SessionEnergyCharged"),
705        (0x0043, "SessionEnergyDischarged"),
706    ]
707}
708
709// Command listing
710
711pub fn get_command_list() -> Vec<(u32, &'static str)> {
712    vec![
713        (0x01, "Disable"),
714        (0x02, "EnableCharging"),
715        (0x03, "EnableDischarging"),
716        (0x04, "StartDiagnostics"),
717        (0x05, "SetTargets"),
718        (0x06, "GetTargets"),
719        (0x07, "ClearTargets"),
720    ]
721}
722
723pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
724    match cmd_id {
725        0x01 => Some("Disable"),
726        0x02 => Some("EnableCharging"),
727        0x03 => Some("EnableDischarging"),
728        0x04 => Some("StartDiagnostics"),
729        0x05 => Some("SetTargets"),
730        0x06 => Some("GetTargets"),
731        0x07 => Some("ClearTargets"),
732        _ => None,
733    }
734}
735
736pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
737    match cmd_id {
738        0x01 => Some(vec![]),
739        0x02 => Some(vec![
740            crate::clusters::codec::CommandField { tag: 0, name: "charging_enabled_until", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: true },
741            crate::clusters::codec::CommandField { tag: 1, name: "minimum_charge_current", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
742            crate::clusters::codec::CommandField { tag: 2, name: "maximum_charge_current", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
743        ]),
744        0x03 => Some(vec![
745            crate::clusters::codec::CommandField { tag: 0, name: "discharging_enabled_until", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: true },
746            crate::clusters::codec::CommandField { tag: 1, name: "maximum_discharge_current", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
747        ]),
748        0x04 => Some(vec![]),
749        0x05 => Some(vec![
750            crate::clusters::codec::CommandField { tag: 0, name: "charging_target_schedules", kind: crate::clusters::codec::FieldKind::List { entry_type: "ChargingTargetScheduleStruct" }, optional: false, nullable: false },
751        ]),
752        0x06 => Some(vec![]),
753        0x07 => Some(vec![]),
754        _ => None,
755    }
756}
757
758pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
759    match cmd_id {
760        0x01 => Ok(vec![]),
761        0x02 => {
762        let charging_enabled_until = crate::clusters::codec::json_util::get_opt_u64(args, "charging_enabled_until")?;
763        let minimum_charge_current = crate::clusters::codec::json_util::get_u8(args, "minimum_charge_current")?;
764        let maximum_charge_current = crate::clusters::codec::json_util::get_u8(args, "maximum_charge_current")?;
765        encode_enable_charging(charging_enabled_until, minimum_charge_current, maximum_charge_current)
766        }
767        0x03 => {
768        let discharging_enabled_until = crate::clusters::codec::json_util::get_opt_u64(args, "discharging_enabled_until")?;
769        let maximum_discharge_current = crate::clusters::codec::json_util::get_u8(args, "maximum_discharge_current")?;
770        encode_enable_discharging(discharging_enabled_until, maximum_discharge_current)
771        }
772        0x04 => Ok(vec![]),
773        0x05 => Err(anyhow::anyhow!("command \"SetTargets\" has complex args: use raw mode")),
774        0x06 => Ok(vec![]),
775        0x07 => Ok(vec![]),
776        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
777    }
778}
779
780#[derive(Debug, serde::Serialize)]
781pub struct GetTargetsResponse {
782    pub charging_target_schedules: Option<Vec<ChargingTargetSchedule>>,
783}
784
785// Command response decoders
786
787/// Decode GetTargetsResponse command response (00)
788pub fn decode_get_targets_response(inp: &tlv::TlvItemValue) -> anyhow::Result<GetTargetsResponse> {
789    if let tlv::TlvItemValue::List(_fields) = inp {
790        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
791        Ok(GetTargetsResponse {
792                charging_target_schedules: {
793                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[0]) {
794                        let mut items = Vec::new();
795                        for list_item in l {
796                            items.push(ChargingTargetSchedule {
797                day_of_week_for_sequence: list_item.get_int(&[0]).map(|v| v as u8),
798                charging_targets: {
799                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[1]) {
800                        let mut items = Vec::new();
801                        for list_item in l {
802                            items.push(ChargingTarget {
803                target_time_minutes_past_midnight: list_item.get_int(&[0]).map(|v| v as u16),
804                target_so_c: list_item.get_int(&[1]).map(|v| v as u8),
805                added_energy: list_item.get_int(&[2]),
806                            });
807                        }
808                        Some(items)
809                    } else {
810                        None
811                    }
812                },
813                            });
814                        }
815                        Some(items)
816                    } else {
817                        None
818                    }
819                },
820        })
821    } else {
822        Err(anyhow::anyhow!("Expected struct fields"))
823    }
824}
825
826// Typed facade (invokes + reads)
827
828/// Invoke `Disable` command on cluster `Energy EVSE`.
829pub async fn disable(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
830    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_CMD_ID_DISABLE, &[]).await?;
831    Ok(())
832}
833
834/// Invoke `EnableCharging` command on cluster `Energy EVSE`.
835pub async fn enable_charging(conn: &crate::controller::Connection, endpoint: u16, charging_enabled_until: Option<u64>, minimum_charge_current: u8, maximum_charge_current: u8) -> anyhow::Result<()> {
836    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_CMD_ID_ENABLECHARGING, &encode_enable_charging(charging_enabled_until, minimum_charge_current, maximum_charge_current)?).await?;
837    Ok(())
838}
839
840/// Invoke `EnableDischarging` command on cluster `Energy EVSE`.
841pub async fn enable_discharging(conn: &crate::controller::Connection, endpoint: u16, discharging_enabled_until: Option<u64>, maximum_discharge_current: u8) -> anyhow::Result<()> {
842    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_CMD_ID_ENABLEDISCHARGING, &encode_enable_discharging(discharging_enabled_until, maximum_discharge_current)?).await?;
843    Ok(())
844}
845
846/// Invoke `StartDiagnostics` command on cluster `Energy EVSE`.
847pub async fn start_diagnostics(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
848    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_CMD_ID_STARTDIAGNOSTICS, &[]).await?;
849    Ok(())
850}
851
852/// Invoke `SetTargets` command on cluster `Energy EVSE`.
853pub async fn set_targets(conn: &crate::controller::Connection, endpoint: u16, charging_target_schedules: Vec<ChargingTargetSchedule>) -> anyhow::Result<()> {
854    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_CMD_ID_SETTARGETS, &encode_set_targets(charging_target_schedules)?).await?;
855    Ok(())
856}
857
858/// Invoke `GetTargets` command on cluster `Energy EVSE`.
859pub async fn get_targets(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<GetTargetsResponse> {
860    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_CMD_ID_GETTARGETS, &[]).await?;
861    decode_get_targets_response(&tlv)
862}
863
864/// Invoke `ClearTargets` command on cluster `Energy EVSE`.
865pub async fn clear_targets(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
866    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_CMD_ID_CLEARTARGETS, &[]).await?;
867    Ok(())
868}
869
870/// Read `State` attribute from cluster `Energy EVSE`.
871pub async fn read_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<State>> {
872    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_STATE).await?;
873    decode_state(&tlv)
874}
875
876/// Read `SupplyState` attribute from cluster `Energy EVSE`.
877pub async fn read_supply_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<SupplyState> {
878    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_SUPPLYSTATE).await?;
879    decode_supply_state(&tlv)
880}
881
882/// Read `FaultState` attribute from cluster `Energy EVSE`.
883pub async fn read_fault_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<FaultState> {
884    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_FAULTSTATE).await?;
885    decode_fault_state(&tlv)
886}
887
888/// Read `ChargingEnabledUntil` attribute from cluster `Energy EVSE`.
889pub async fn read_charging_enabled_until(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
890    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_CHARGINGENABLEDUNTIL).await?;
891    decode_charging_enabled_until(&tlv)
892}
893
894/// Read `DischargingEnabledUntil` attribute from cluster `Energy EVSE`.
895pub async fn read_discharging_enabled_until(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
896    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_DISCHARGINGENABLEDUNTIL).await?;
897    decode_discharging_enabled_until(&tlv)
898}
899
900/// Read `CircuitCapacity` attribute from cluster `Energy EVSE`.
901pub async fn read_circuit_capacity(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
902    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_CIRCUITCAPACITY).await?;
903    decode_circuit_capacity(&tlv)
904}
905
906/// Read `MinimumChargeCurrent` attribute from cluster `Energy EVSE`.
907pub async fn read_minimum_charge_current(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
908    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_MINIMUMCHARGECURRENT).await?;
909    decode_minimum_charge_current(&tlv)
910}
911
912/// Read `MaximumChargeCurrent` attribute from cluster `Energy EVSE`.
913pub async fn read_maximum_charge_current(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
914    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_MAXIMUMCHARGECURRENT).await?;
915    decode_maximum_charge_current(&tlv)
916}
917
918/// Read `MaximumDischargeCurrent` attribute from cluster `Energy EVSE`.
919pub async fn read_maximum_discharge_current(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
920    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_MAXIMUMDISCHARGECURRENT).await?;
921    decode_maximum_discharge_current(&tlv)
922}
923
924/// Read `UserMaximumChargeCurrent` attribute from cluster `Energy EVSE`.
925pub async fn read_user_maximum_charge_current(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
926    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_USERMAXIMUMCHARGECURRENT).await?;
927    decode_user_maximum_charge_current(&tlv)
928}
929
930/// Read `RandomizationDelayWindow` attribute from cluster `Energy EVSE`.
931pub async fn read_randomization_delay_window(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u32> {
932    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_RANDOMIZATIONDELAYWINDOW).await?;
933    decode_randomization_delay_window(&tlv)
934}
935
936/// Read `NextChargeStartTime` attribute from cluster `Energy EVSE`.
937pub async fn read_next_charge_start_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
938    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_NEXTCHARGESTARTTIME).await?;
939    decode_next_charge_start_time(&tlv)
940}
941
942/// Read `NextChargeTargetTime` attribute from cluster `Energy EVSE`.
943pub async fn read_next_charge_target_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
944    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_NEXTCHARGETARGETTIME).await?;
945    decode_next_charge_target_time(&tlv)
946}
947
948/// Read `NextChargeRequiredEnergy` attribute from cluster `Energy EVSE`.
949pub async fn read_next_charge_required_energy(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
950    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_NEXTCHARGEREQUIREDENERGY).await?;
951    decode_next_charge_required_energy(&tlv)
952}
953
954/// Read `NextChargeTargetSoC` attribute from cluster `Energy EVSE`.
955pub async fn read_next_charge_target_so_c(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
956    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_NEXTCHARGETARGETSOC).await?;
957    decode_next_charge_target_so_c(&tlv)
958}
959
960/// Read `ApproximateEVEfficiency` attribute from cluster `Energy EVSE`.
961pub async fn read_approximate_ev_efficiency(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u16>> {
962    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_APPROXIMATEEVEFFICIENCY).await?;
963    decode_approximate_ev_efficiency(&tlv)
964}
965
966/// Read `StateOfCharge` attribute from cluster `Energy EVSE`.
967pub async fn read_state_of_charge(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u8>> {
968    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_STATEOFCHARGE).await?;
969    decode_state_of_charge(&tlv)
970}
971
972/// Read `BatteryCapacity` attribute from cluster `Energy EVSE`.
973pub async fn read_battery_capacity(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
974    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_BATTERYCAPACITY).await?;
975    decode_battery_capacity(&tlv)
976}
977
978/// Read `VehicleID` attribute from cluster `Energy EVSE`.
979pub async fn read_vehicle_id(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<String>> {
980    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_VEHICLEID).await?;
981    decode_vehicle_id(&tlv)
982}
983
984/// Read `SessionID` attribute from cluster `Energy EVSE`.
985pub async fn read_session_id(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u32>> {
986    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_SESSIONID).await?;
987    decode_session_id(&tlv)
988}
989
990/// Read `SessionDuration` attribute from cluster `Energy EVSE`.
991pub async fn read_session_duration(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u32>> {
992    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_SESSIONDURATION).await?;
993    decode_session_duration(&tlv)
994}
995
996/// Read `SessionEnergyCharged` attribute from cluster `Energy EVSE`.
997pub async fn read_session_energy_charged(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
998    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_SESSIONENERGYCHARGED).await?;
999    decode_session_energy_charged(&tlv)
1000}
1001
1002/// Read `SessionEnergyDischarged` attribute from cluster `Energy EVSE`.
1003pub async fn read_session_energy_discharged(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
1004    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ENERGY_EVSE, crate::clusters::defs::CLUSTER_ENERGY_EVSE_ATTR_ID_SESSIONENERGYDISCHARGED).await?;
1005    decode_session_energy_discharged(&tlv)
1006}
1007
1008#[derive(Debug, serde::Serialize)]
1009pub struct EVConnectedEvent {
1010    pub session_id: Option<u32>,
1011}
1012
1013#[derive(Debug, serde::Serialize)]
1014pub struct EVNotDetectedEvent {
1015    pub session_id: Option<u32>,
1016    pub state: Option<State>,
1017    pub session_duration: Option<u32>,
1018    pub session_energy_charged: Option<u64>,
1019    pub session_energy_discharged: Option<u64>,
1020}
1021
1022#[derive(Debug, serde::Serialize)]
1023pub struct EnergyTransferStartedEvent {
1024    pub session_id: Option<u32>,
1025    pub state: Option<State>,
1026    pub maximum_current: Option<u8>,
1027    pub maximum_discharge_current: Option<u8>,
1028}
1029
1030#[derive(Debug, serde::Serialize)]
1031pub struct EnergyTransferStoppedEvent {
1032    pub session_id: Option<u32>,
1033    pub state: Option<State>,
1034    pub reason: Option<EnergyTransferStoppedReason>,
1035    pub energy_transferred: Option<u64>,
1036    pub energy_discharged: Option<u64>,
1037}
1038
1039#[derive(Debug, serde::Serialize)]
1040pub struct FaultEvent {
1041    pub session_id: Option<u32>,
1042    pub state: Option<State>,
1043    pub fault_state_previous_state: Option<FaultState>,
1044    pub fault_state_current_state: Option<FaultState>,
1045}
1046
1047#[derive(Debug, serde::Serialize)]
1048pub struct RFIDEvent {
1049    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
1050    pub uid: Option<Vec<u8>>,
1051}
1052
1053// Event decoders
1054
1055/// Decode EVConnected event (0x00, priority: info)
1056pub fn decode_ev_connected_event(inp: &tlv::TlvItemValue) -> anyhow::Result<EVConnectedEvent> {
1057    if let tlv::TlvItemValue::List(_fields) = inp {
1058        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1059        Ok(EVConnectedEvent {
1060                                session_id: item.get_int(&[0]).map(|v| v as u32),
1061        })
1062    } else {
1063        Err(anyhow::anyhow!("Expected struct fields"))
1064    }
1065}
1066
1067/// Decode EVNotDetected event (0x01, priority: info)
1068pub fn decode_ev_not_detected_event(inp: &tlv::TlvItemValue) -> anyhow::Result<EVNotDetectedEvent> {
1069    if let tlv::TlvItemValue::List(_fields) = inp {
1070        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1071        Ok(EVNotDetectedEvent {
1072                                session_id: item.get_int(&[0]).map(|v| v as u32),
1073                                state: item.get_int(&[1]).and_then(|v| State::from_u8(v as u8)),
1074                                session_duration: item.get_int(&[2]).map(|v| v as u32),
1075                                session_energy_charged: item.get_int(&[3]),
1076                                session_energy_discharged: item.get_int(&[4]),
1077        })
1078    } else {
1079        Err(anyhow::anyhow!("Expected struct fields"))
1080    }
1081}
1082
1083/// Decode EnergyTransferStarted event (0x02, priority: info)
1084pub fn decode_energy_transfer_started_event(inp: &tlv::TlvItemValue) -> anyhow::Result<EnergyTransferStartedEvent> {
1085    if let tlv::TlvItemValue::List(_fields) = inp {
1086        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1087        Ok(EnergyTransferStartedEvent {
1088                                session_id: item.get_int(&[0]).map(|v| v as u32),
1089                                state: item.get_int(&[1]).and_then(|v| State::from_u8(v as u8)),
1090                                maximum_current: item.get_int(&[2]).map(|v| v as u8),
1091                                maximum_discharge_current: item.get_int(&[3]).map(|v| v as u8),
1092        })
1093    } else {
1094        Err(anyhow::anyhow!("Expected struct fields"))
1095    }
1096}
1097
1098/// Decode EnergyTransferStopped event (0x03, priority: info)
1099pub fn decode_energy_transfer_stopped_event(inp: &tlv::TlvItemValue) -> anyhow::Result<EnergyTransferStoppedEvent> {
1100    if let tlv::TlvItemValue::List(_fields) = inp {
1101        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1102        Ok(EnergyTransferStoppedEvent {
1103                                session_id: item.get_int(&[0]).map(|v| v as u32),
1104                                state: item.get_int(&[1]).and_then(|v| State::from_u8(v as u8)),
1105                                reason: item.get_int(&[2]).and_then(|v| EnergyTransferStoppedReason::from_u8(v as u8)),
1106                                energy_transferred: item.get_int(&[4]),
1107                                energy_discharged: item.get_int(&[5]),
1108        })
1109    } else {
1110        Err(anyhow::anyhow!("Expected struct fields"))
1111    }
1112}
1113
1114/// Decode Fault event (0x04, priority: critical)
1115pub fn decode_fault_event(inp: &tlv::TlvItemValue) -> anyhow::Result<FaultEvent> {
1116    if let tlv::TlvItemValue::List(_fields) = inp {
1117        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1118        Ok(FaultEvent {
1119                                session_id: item.get_int(&[0]).map(|v| v as u32),
1120                                state: item.get_int(&[1]).and_then(|v| State::from_u8(v as u8)),
1121                                fault_state_previous_state: item.get_int(&[2]).and_then(|v| FaultState::from_u8(v as u8)),
1122                                fault_state_current_state: item.get_int(&[4]).and_then(|v| FaultState::from_u8(v as u8)),
1123        })
1124    } else {
1125        Err(anyhow::anyhow!("Expected struct fields"))
1126    }
1127}
1128
1129/// Decode RFID event (0x05, priority: info)
1130pub fn decode_rfid_event(inp: &tlv::TlvItemValue) -> anyhow::Result<RFIDEvent> {
1131    if let tlv::TlvItemValue::List(_fields) = inp {
1132        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1133        Ok(RFIDEvent {
1134                                uid: item.get_octet_string_owned(&[0]),
1135        })
1136    } else {
1137        Err(anyhow::anyhow!("Expected struct fields"))
1138    }
1139}
1140