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
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Import serialization helpers for octet strings
12use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
13
14// Enum definitions
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17#[repr(u8)]
18pub enum EnergyTransferStoppedReason {
19    /// The EV decided to stop
20    Evstopped = 0,
21    /// The EVSE decided to stop
22    Evsestopped = 1,
23    /// An other unknown reason
24    Other = 2,
25}
26
27impl EnergyTransferStoppedReason {
28    /// Convert from u8 value
29    pub fn from_u8(value: u8) -> Option<Self> {
30        match value {
31            0 => Some(EnergyTransferStoppedReason::Evstopped),
32            1 => Some(EnergyTransferStoppedReason::Evsestopped),
33            2 => Some(EnergyTransferStoppedReason::Other),
34            _ => None,
35        }
36    }
37
38    /// Convert to u8 value
39    pub fn to_u8(self) -> u8 {
40        self as u8
41    }
42}
43
44impl From<EnergyTransferStoppedReason> for u8 {
45    fn from(val: EnergyTransferStoppedReason) -> Self {
46        val as u8
47    }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
51#[repr(u8)]
52pub enum FaultState {
53    /// The EVSE is not in an error state.
54    Noerror = 0,
55    /// The EVSE is unable to obtain electrical measurements.
56    Meterfailure = 1,
57    /// The EVSE input voltage level is too high.
58    Overvoltage = 2,
59    /// The EVSE input voltage level is too low.
60    Undervoltage = 3,
61    /// The EVSE detected charging current higher than allowed by charger.
62    Overcurrent = 4,
63    /// The EVSE detected voltage on charging pins when the contactor is open.
64    Contactwetfailure = 5,
65    /// The EVSE detected absence of voltage after enabling contactor.
66    Contactdryfailure = 6,
67    /// The EVSE has an unbalanced current supply.
68    Groundfault = 7,
69    /// The EVSE has detected a loss in power.
70    Powerloss = 8,
71    /// The EVSE has detected another power quality issue (e.g. phase imbalance).
72    Powerquality = 9,
73    /// The EVSE pilot signal amplitude short circuited to ground.
74    Pilotshortcircuit = 10,
75    /// The emergency stop button was pressed.
76    Emergencystop = 11,
77    /// The EVSE detected that the cable has been disconnected.
78    Evdisconnected = 12,
79    /// The EVSE could not determine proper power supply level.
80    Wrongpowersupply = 13,
81    /// The EVSE detected Live and Neutral are swapped.
82    Liveneutralswap = 14,
83    /// The EVSE internal temperature is too high.
84    Overtemperature = 15,
85    /// Any other reason.
86    Other = 255,
87}
88
89impl FaultState {
90    /// Convert from u8 value
91    pub fn from_u8(value: u8) -> Option<Self> {
92        match value {
93            0 => Some(FaultState::Noerror),
94            1 => Some(FaultState::Meterfailure),
95            2 => Some(FaultState::Overvoltage),
96            3 => Some(FaultState::Undervoltage),
97            4 => Some(FaultState::Overcurrent),
98            5 => Some(FaultState::Contactwetfailure),
99            6 => Some(FaultState::Contactdryfailure),
100            7 => Some(FaultState::Groundfault),
101            8 => Some(FaultState::Powerloss),
102            9 => Some(FaultState::Powerquality),
103            10 => Some(FaultState::Pilotshortcircuit),
104            11 => Some(FaultState::Emergencystop),
105            12 => Some(FaultState::Evdisconnected),
106            13 => Some(FaultState::Wrongpowersupply),
107            14 => Some(FaultState::Liveneutralswap),
108            15 => Some(FaultState::Overtemperature),
109            255 => Some(FaultState::Other),
110            _ => None,
111        }
112    }
113
114    /// Convert to u8 value
115    pub fn to_u8(self) -> u8 {
116        self as u8
117    }
118}
119
120impl From<FaultState> for u8 {
121    fn from(val: FaultState) -> Self {
122        val as u8
123    }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
127#[repr(u8)]
128pub enum State {
129    /// The EV is not plugged in.
130    Notpluggedin = 0,
131    /// The EV is plugged in, but not demanding current.
132    Pluggedinnodemand = 1,
133    /// The EV is plugged in and is demanding current, but EVSE is not allowing current to flow.
134    Pluggedindemand = 2,
135    /// The EV is plugged in, charging is in progress, and current is flowing
136    Pluggedincharging = 3,
137    /// The EV is plugged in, discharging is in progress, and current is flowing
138    Pluggedindischarging = 4,
139    /// The EVSE is transitioning from any plugged-in state to NotPluggedIn
140    Sessionending = 5,
141    /// There is a fault, further details in the FaultState attribute
142    Fault = 6,
143}
144
145impl State {
146    /// Convert from u8 value
147    pub fn from_u8(value: u8) -> Option<Self> {
148        match value {
149            0 => Some(State::Notpluggedin),
150            1 => Some(State::Pluggedinnodemand),
151            2 => Some(State::Pluggedindemand),
152            3 => Some(State::Pluggedincharging),
153            4 => Some(State::Pluggedindischarging),
154            5 => Some(State::Sessionending),
155            6 => Some(State::Fault),
156            _ => None,
157        }
158    }
159
160    /// Convert to u8 value
161    pub fn to_u8(self) -> u8 {
162        self as u8
163    }
164}
165
166impl From<State> for u8 {
167    fn from(val: State) -> Self {
168        val as u8
169    }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
173#[repr(u8)]
174pub enum SupplyState {
175    /// The EV is not currently allowed to charge or discharge
176    Disabled = 0,
177    /// The EV is currently allowed to charge
178    Chargingenabled = 1,
179    /// The EV is currently allowed to discharge
180    Dischargingenabled = 2,
181    /// The EV is not currently allowed to charge or discharge due to an error. The error must be cleared before operation can continue.
182    Disablederror = 3,
183    /// The EV is not currently allowed to charge or discharge due to self-diagnostics mode.
184    Disableddiagnostics = 4,
185    /// The EV is currently allowed to charge and discharge
186    Enabled = 5,
187}
188
189impl SupplyState {
190    /// Convert from u8 value
191    pub fn from_u8(value: u8) -> Option<Self> {
192        match value {
193            0 => Some(SupplyState::Disabled),
194            1 => Some(SupplyState::Chargingenabled),
195            2 => Some(SupplyState::Dischargingenabled),
196            3 => Some(SupplyState::Disablederror),
197            4 => Some(SupplyState::Disableddiagnostics),
198            5 => Some(SupplyState::Enabled),
199            _ => None,
200        }
201    }
202
203    /// Convert to u8 value
204    pub fn to_u8(self) -> u8 {
205        self as u8
206    }
207}
208
209impl From<SupplyState> for u8 {
210    fn from(val: SupplyState) -> Self {
211        val as u8
212    }
213}
214
215// Bitmap definitions
216
217/// TargetDayOfWeek bitmap type
218pub type TargetDayOfWeek = u8;
219
220/// Constants for TargetDayOfWeek
221pub mod targetdayofweek {
222    /// Sunday
223    pub const SUNDAY: u8 = 0x01;
224    /// Monday
225    pub const MONDAY: u8 = 0x02;
226    /// Tuesday
227    pub const TUESDAY: u8 = 0x04;
228    /// Wednesday
229    pub const WEDNESDAY: u8 = 0x08;
230    /// Thursday
231    pub const THURSDAY: u8 = 0x10;
232    /// Friday
233    pub const FRIDAY: u8 = 0x20;
234    /// Saturday
235    pub const SATURDAY: u8 = 0x40;
236}
237
238// Struct definitions
239
240#[derive(Debug, serde::Serialize)]
241pub struct ChargingTargetSchedule {
242    pub day_of_week_for_sequence: Option<TargetDayOfWeek>,
243    pub charging_targets: Option<Vec<ChargingTarget>>,
244}
245
246#[derive(Debug, serde::Serialize)]
247pub struct ChargingTarget {
248    pub target_time_minutes_past_midnight: Option<u16>,
249    pub target_so_c: Option<u8>,
250    pub added_energy: Option<u64>,
251}
252
253// Command encoders
254
255/// Encode EnableCharging command (0x02)
256pub fn encode_enable_charging(charging_enabled_until: Option<u64>, minimum_charge_current: u8, maximum_charge_current: u8) -> anyhow::Result<Vec<u8>> {
257    let tlv = tlv::TlvItemEnc {
258        tag: 0,
259        value: tlv::TlvItemValueEnc::StructInvisible(vec![
260        (0, tlv::TlvItemValueEnc::UInt64(charging_enabled_until.unwrap_or(0))).into(),
261        (1, tlv::TlvItemValueEnc::UInt8(minimum_charge_current)).into(),
262        (2, tlv::TlvItemValueEnc::UInt8(maximum_charge_current)).into(),
263        ]),
264    };
265    Ok(tlv.encode()?)
266}
267
268/// Encode EnableDischarging command (0x03)
269pub fn encode_enable_discharging(discharging_enabled_until: Option<u64>, maximum_discharge_current: u8) -> anyhow::Result<Vec<u8>> {
270    let tlv = tlv::TlvItemEnc {
271        tag: 0,
272        value: tlv::TlvItemValueEnc::StructInvisible(vec![
273        (0, tlv::TlvItemValueEnc::UInt64(discharging_enabled_until.unwrap_or(0))).into(),
274        (1, tlv::TlvItemValueEnc::UInt8(maximum_discharge_current)).into(),
275        ]),
276    };
277    Ok(tlv.encode()?)
278}
279
280/// Encode SetTargets command (0x05)
281pub fn encode_set_targets(charging_target_schedules: Vec<ChargingTargetSchedule>) -> anyhow::Result<Vec<u8>> {
282    let tlv = tlv::TlvItemEnc {
283        tag: 0,
284        value: tlv::TlvItemValueEnc::StructInvisible(vec![
285        (0, tlv::TlvItemValueEnc::Array(charging_target_schedules.into_iter().map(|v| {
286                    let mut fields = Vec::new();
287                    if let Some(x) = v.day_of_week_for_sequence { fields.push((0, tlv::TlvItemValueEnc::UInt8(x)).into()); }
288                    if let Some(listv) = v.charging_targets {
289                        let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
290                            let mut nested_fields = Vec::new();
291                                if let Some(x) = inner.target_time_minutes_past_midnight { nested_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
292                                // TODO: encoding for field target_so_c (percent) not implemented
293                                if let Some(x) = inner.added_energy { nested_fields.push((2, tlv::TlvItemValueEnc::UInt64(x)).into()); }
294                            (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
295                        }).collect();
296                        fields.push((1, tlv::TlvItemValueEnc::Array(inner_vec)).into());
297                    }
298                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
299                }).collect())).into(),
300        ]),
301    };
302    Ok(tlv.encode()?)
303}
304
305// Attribute decoders
306
307/// Decode State attribute (0x0000)
308pub fn decode_state(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<State>> {
309    if let tlv::TlvItemValue::Int(v) = inp {
310        Ok(State::from_u8(*v as u8))
311    } else {
312        Ok(None)
313    }
314}
315
316/// Decode SupplyState attribute (0x0001)
317pub fn decode_supply_state(inp: &tlv::TlvItemValue) -> anyhow::Result<SupplyState> {
318    if let tlv::TlvItemValue::Int(v) = inp {
319        SupplyState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
320    } else {
321        Err(anyhow::anyhow!("Expected Integer"))
322    }
323}
324
325/// Decode FaultState attribute (0x0002)
326pub fn decode_fault_state(inp: &tlv::TlvItemValue) -> anyhow::Result<FaultState> {
327    if let tlv::TlvItemValue::Int(v) = inp {
328        FaultState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
329    } else {
330        Err(anyhow::anyhow!("Expected Integer"))
331    }
332}
333
334/// Decode ChargingEnabledUntil attribute (0x0003)
335pub fn decode_charging_enabled_until(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
336    if let tlv::TlvItemValue::Int(v) = inp {
337        Ok(Some(*v))
338    } else {
339        Ok(None)
340    }
341}
342
343/// Decode DischargingEnabledUntil attribute (0x0004)
344pub fn decode_discharging_enabled_until(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
345    if let tlv::TlvItemValue::Int(v) = inp {
346        Ok(Some(*v))
347    } else {
348        Ok(None)
349    }
350}
351
352/// Decode CircuitCapacity attribute (0x0005)
353pub fn decode_circuit_capacity(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
354    if let tlv::TlvItemValue::Int(v) = inp {
355        Ok(*v as u8)
356    } else {
357        Err(anyhow::anyhow!("Expected UInt8"))
358    }
359}
360
361/// Decode MinimumChargeCurrent attribute (0x0006)
362pub fn decode_minimum_charge_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
363    if let tlv::TlvItemValue::Int(v) = inp {
364        Ok(*v as u8)
365    } else {
366        Err(anyhow::anyhow!("Expected UInt8"))
367    }
368}
369
370/// Decode MaximumChargeCurrent attribute (0x0007)
371pub fn decode_maximum_charge_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
372    if let tlv::TlvItemValue::Int(v) = inp {
373        Ok(*v as u8)
374    } else {
375        Err(anyhow::anyhow!("Expected UInt8"))
376    }
377}
378
379/// Decode MaximumDischargeCurrent attribute (0x0008)
380pub fn decode_maximum_discharge_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
381    if let tlv::TlvItemValue::Int(v) = inp {
382        Ok(*v as u8)
383    } else {
384        Err(anyhow::anyhow!("Expected UInt8"))
385    }
386}
387
388/// Decode UserMaximumChargeCurrent attribute (0x0009)
389pub fn decode_user_maximum_charge_current(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
390    if let tlv::TlvItemValue::Int(v) = inp {
391        Ok(*v as u8)
392    } else {
393        Err(anyhow::anyhow!("Expected UInt8"))
394    }
395}
396
397/// Decode RandomizationDelayWindow attribute (0x000A)
398pub fn decode_randomization_delay_window(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
399    if let tlv::TlvItemValue::Int(v) = inp {
400        Ok(*v as u32)
401    } else {
402        Err(anyhow::anyhow!("Expected UInt32"))
403    }
404}
405
406/// Decode NextChargeStartTime attribute (0x0023)
407pub fn decode_next_charge_start_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
408    if let tlv::TlvItemValue::Int(v) = inp {
409        Ok(Some(*v))
410    } else {
411        Ok(None)
412    }
413}
414
415/// Decode NextChargeTargetTime attribute (0x0024)
416pub fn decode_next_charge_target_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
417    if let tlv::TlvItemValue::Int(v) = inp {
418        Ok(Some(*v))
419    } else {
420        Ok(None)
421    }
422}
423
424/// Decode NextChargeRequiredEnergy attribute (0x0025)
425pub fn decode_next_charge_required_energy(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
426    if let tlv::TlvItemValue::Int(v) = inp {
427        Ok(Some(*v))
428    } else {
429        Ok(None)
430    }
431}
432
433/// Decode NextChargeTargetSoC attribute (0x0026)
434pub fn decode_next_charge_target_so_c(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
435    if let tlv::TlvItemValue::Int(v) = inp {
436        Ok(Some(*v as u8))
437    } else {
438        Ok(None)
439    }
440}
441
442/// Decode ApproximateEVEfficiency attribute (0x0027)
443pub fn decode_approximate_ev_efficiency(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u16>> {
444    if let tlv::TlvItemValue::Int(v) = inp {
445        Ok(Some(*v as u16))
446    } else {
447        Ok(None)
448    }
449}
450
451/// Decode StateOfCharge attribute (0x0030)
452pub fn decode_state_of_charge(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
453    if let tlv::TlvItemValue::Int(v) = inp {
454        Ok(Some(*v as u8))
455    } else {
456        Ok(None)
457    }
458}
459
460/// Decode BatteryCapacity attribute (0x0031)
461pub fn decode_battery_capacity(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
462    if let tlv::TlvItemValue::Int(v) = inp {
463        Ok(Some(*v))
464    } else {
465        Ok(None)
466    }
467}
468
469/// Decode VehicleID attribute (0x0032)
470pub fn decode_vehicle_id(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<String>> {
471    if let tlv::TlvItemValue::String(v) = inp {
472        Ok(Some(v.clone()))
473    } else {
474        Ok(None)
475    }
476}
477
478/// Decode SessionID attribute (0x0040)
479pub fn decode_session_id(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
480    if let tlv::TlvItemValue::Int(v) = inp {
481        Ok(Some(*v as u32))
482    } else {
483        Ok(None)
484    }
485}
486
487/// Decode SessionDuration attribute (0x0041)
488pub fn decode_session_duration(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
489    if let tlv::TlvItemValue::Int(v) = inp {
490        Ok(Some(*v as u32))
491    } else {
492        Ok(None)
493    }
494}
495
496/// Decode SessionEnergyCharged attribute (0x0042)
497pub fn decode_session_energy_charged(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
498    if let tlv::TlvItemValue::Int(v) = inp {
499        Ok(Some(*v))
500    } else {
501        Ok(None)
502    }
503}
504
505/// Decode SessionEnergyDischarged attribute (0x0043)
506pub fn decode_session_energy_discharged(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
507    if let tlv::TlvItemValue::Int(v) = inp {
508        Ok(Some(*v))
509    } else {
510        Ok(None)
511    }
512}
513
514
515// JSON dispatcher function
516
517/// Decode attribute value and return as JSON string
518///
519/// # Parameters
520/// * `cluster_id` - The cluster identifier
521/// * `attribute_id` - The attribute identifier
522/// * `tlv_value` - The TLV value to decode
523///
524/// # Returns
525/// JSON string representation of the decoded value or error
526pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
527    // Verify this is the correct cluster
528    if cluster_id != 0x0099 {
529        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0099, got {}\"}}", cluster_id);
530    }
531
532    match attribute_id {
533        0x0000 => {
534            match decode_state(tlv_value) {
535                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
536                Err(e) => format!("{{\"error\": \"{}\"}}", e),
537            }
538        }
539        0x0001 => {
540            match decode_supply_state(tlv_value) {
541                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
542                Err(e) => format!("{{\"error\": \"{}\"}}", e),
543            }
544        }
545        0x0002 => {
546            match decode_fault_state(tlv_value) {
547                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
548                Err(e) => format!("{{\"error\": \"{}\"}}", e),
549            }
550        }
551        0x0003 => {
552            match decode_charging_enabled_until(tlv_value) {
553                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
554                Err(e) => format!("{{\"error\": \"{}\"}}", e),
555            }
556        }
557        0x0004 => {
558            match decode_discharging_enabled_until(tlv_value) {
559                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
560                Err(e) => format!("{{\"error\": \"{}\"}}", e),
561            }
562        }
563        0x0005 => {
564            match decode_circuit_capacity(tlv_value) {
565                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
566                Err(e) => format!("{{\"error\": \"{}\"}}", e),
567            }
568        }
569        0x0006 => {
570            match decode_minimum_charge_current(tlv_value) {
571                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
572                Err(e) => format!("{{\"error\": \"{}\"}}", e),
573            }
574        }
575        0x0007 => {
576            match decode_maximum_charge_current(tlv_value) {
577                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
578                Err(e) => format!("{{\"error\": \"{}\"}}", e),
579            }
580        }
581        0x0008 => {
582            match decode_maximum_discharge_current(tlv_value) {
583                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
584                Err(e) => format!("{{\"error\": \"{}\"}}", e),
585            }
586        }
587        0x0009 => {
588            match decode_user_maximum_charge_current(tlv_value) {
589                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
590                Err(e) => format!("{{\"error\": \"{}\"}}", e),
591            }
592        }
593        0x000A => {
594            match decode_randomization_delay_window(tlv_value) {
595                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
596                Err(e) => format!("{{\"error\": \"{}\"}}", e),
597            }
598        }
599        0x0023 => {
600            match decode_next_charge_start_time(tlv_value) {
601                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
602                Err(e) => format!("{{\"error\": \"{}\"}}", e),
603            }
604        }
605        0x0024 => {
606            match decode_next_charge_target_time(tlv_value) {
607                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
608                Err(e) => format!("{{\"error\": \"{}\"}}", e),
609            }
610        }
611        0x0025 => {
612            match decode_next_charge_required_energy(tlv_value) {
613                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
614                Err(e) => format!("{{\"error\": \"{}\"}}", e),
615            }
616        }
617        0x0026 => {
618            match decode_next_charge_target_so_c(tlv_value) {
619                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
620                Err(e) => format!("{{\"error\": \"{}\"}}", e),
621            }
622        }
623        0x0027 => {
624            match decode_approximate_ev_efficiency(tlv_value) {
625                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
626                Err(e) => format!("{{\"error\": \"{}\"}}", e),
627            }
628        }
629        0x0030 => {
630            match decode_state_of_charge(tlv_value) {
631                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
632                Err(e) => format!("{{\"error\": \"{}\"}}", e),
633            }
634        }
635        0x0031 => {
636            match decode_battery_capacity(tlv_value) {
637                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
638                Err(e) => format!("{{\"error\": \"{}\"}}", e),
639            }
640        }
641        0x0032 => {
642            match decode_vehicle_id(tlv_value) {
643                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
644                Err(e) => format!("{{\"error\": \"{}\"}}", e),
645            }
646        }
647        0x0040 => {
648            match decode_session_id(tlv_value) {
649                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
650                Err(e) => format!("{{\"error\": \"{}\"}}", e),
651            }
652        }
653        0x0041 => {
654            match decode_session_duration(tlv_value) {
655                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
656                Err(e) => format!("{{\"error\": \"{}\"}}", e),
657            }
658        }
659        0x0042 => {
660            match decode_session_energy_charged(tlv_value) {
661                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
662                Err(e) => format!("{{\"error\": \"{}\"}}", e),
663            }
664        }
665        0x0043 => {
666            match decode_session_energy_discharged(tlv_value) {
667                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
668                Err(e) => format!("{{\"error\": \"{}\"}}", e),
669            }
670        }
671        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
672    }
673}
674
675/// Get list of all attributes supported by this cluster
676///
677/// # Returns
678/// Vector of tuples containing (attribute_id, attribute_name)
679pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
680    vec![
681        (0x0000, "State"),
682        (0x0001, "SupplyState"),
683        (0x0002, "FaultState"),
684        (0x0003, "ChargingEnabledUntil"),
685        (0x0004, "DischargingEnabledUntil"),
686        (0x0005, "CircuitCapacity"),
687        (0x0006, "MinimumChargeCurrent"),
688        (0x0007, "MaximumChargeCurrent"),
689        (0x0008, "MaximumDischargeCurrent"),
690        (0x0009, "UserMaximumChargeCurrent"),
691        (0x000A, "RandomizationDelayWindow"),
692        (0x0023, "NextChargeStartTime"),
693        (0x0024, "NextChargeTargetTime"),
694        (0x0025, "NextChargeRequiredEnergy"),
695        (0x0026, "NextChargeTargetSoC"),
696        (0x0027, "ApproximateEVEfficiency"),
697        (0x0030, "StateOfCharge"),
698        (0x0031, "BatteryCapacity"),
699        (0x0032, "VehicleID"),
700        (0x0040, "SessionID"),
701        (0x0041, "SessionDuration"),
702        (0x0042, "SessionEnergyCharged"),
703        (0x0043, "SessionEnergyDischarged"),
704    ]
705}
706
707#[derive(Debug, serde::Serialize)]
708pub struct GetTargetsResponse {
709    pub charging_target_schedules: Option<Vec<ChargingTargetSchedule>>,
710}
711
712// Command response decoders
713
714/// Decode GetTargetsResponse command response (00)
715pub fn decode_get_targets_response(inp: &tlv::TlvItemValue) -> anyhow::Result<GetTargetsResponse> {
716    if let tlv::TlvItemValue::List(_fields) = inp {
717        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
718        Ok(GetTargetsResponse {
719                charging_target_schedules: {
720                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[0]) {
721                        let mut items = Vec::new();
722                        for list_item in l {
723                            items.push(ChargingTargetSchedule {
724                day_of_week_for_sequence: list_item.get_int(&[0]).map(|v| v as u8),
725                charging_targets: {
726                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[1]) {
727                        let mut items = Vec::new();
728                        for list_item in l {
729                            items.push(ChargingTarget {
730                target_time_minutes_past_midnight: list_item.get_int(&[0]).map(|v| v as u16),
731                target_so_c: list_item.get_int(&[1]).map(|v| v as u8),
732                added_energy: list_item.get_int(&[2]),
733                            });
734                        }
735                        Some(items)
736                    } else {
737                        None
738                    }
739                },
740                            });
741                        }
742                        Some(items)
743                    } else {
744                        None
745                    }
746                },
747        })
748    } else {
749        Err(anyhow::anyhow!("Expected struct fields"))
750    }
751}
752
753#[derive(Debug, serde::Serialize)]
754pub struct EVConnectedEvent {
755    pub session_id: Option<u32>,
756}
757
758#[derive(Debug, serde::Serialize)]
759pub struct EVNotDetectedEvent {
760    pub session_id: Option<u32>,
761    pub state: Option<State>,
762    pub session_duration: Option<u32>,
763    pub session_energy_charged: Option<u64>,
764    pub session_energy_discharged: Option<u64>,
765}
766
767#[derive(Debug, serde::Serialize)]
768pub struct EnergyTransferStartedEvent {
769    pub session_id: Option<u32>,
770    pub state: Option<State>,
771    pub maximum_current: Option<u8>,
772    pub maximum_discharge_current: Option<u8>,
773}
774
775#[derive(Debug, serde::Serialize)]
776pub struct EnergyTransferStoppedEvent {
777    pub session_id: Option<u32>,
778    pub state: Option<State>,
779    pub reason: Option<EnergyTransferStoppedReason>,
780    pub energy_transferred: Option<u64>,
781    pub energy_discharged: Option<u64>,
782}
783
784#[derive(Debug, serde::Serialize)]
785pub struct FaultEvent {
786    pub session_id: Option<u32>,
787    pub state: Option<State>,
788    pub fault_state_previous_state: Option<FaultState>,
789    pub fault_state_current_state: Option<FaultState>,
790}
791
792#[derive(Debug, serde::Serialize)]
793pub struct RFIDEvent {
794    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
795    pub uid: Option<Vec<u8>>,
796}
797
798// Event decoders
799
800/// Decode EVConnected event (0x00, priority: info)
801pub fn decode_ev_connected_event(inp: &tlv::TlvItemValue) -> anyhow::Result<EVConnectedEvent> {
802    if let tlv::TlvItemValue::List(_fields) = inp {
803        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
804        Ok(EVConnectedEvent {
805                                session_id: item.get_int(&[0]).map(|v| v as u32),
806        })
807    } else {
808        Err(anyhow::anyhow!("Expected struct fields"))
809    }
810}
811
812/// Decode EVNotDetected event (0x01, priority: info)
813pub fn decode_ev_not_detected_event(inp: &tlv::TlvItemValue) -> anyhow::Result<EVNotDetectedEvent> {
814    if let tlv::TlvItemValue::List(_fields) = inp {
815        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
816        Ok(EVNotDetectedEvent {
817                                session_id: item.get_int(&[0]).map(|v| v as u32),
818                                state: item.get_int(&[1]).and_then(|v| State::from_u8(v as u8)),
819                                session_duration: item.get_int(&[2]).map(|v| v as u32),
820                                session_energy_charged: item.get_int(&[3]),
821                                session_energy_discharged: item.get_int(&[4]),
822        })
823    } else {
824        Err(anyhow::anyhow!("Expected struct fields"))
825    }
826}
827
828/// Decode EnergyTransferStarted event (0x02, priority: info)
829pub fn decode_energy_transfer_started_event(inp: &tlv::TlvItemValue) -> anyhow::Result<EnergyTransferStartedEvent> {
830    if let tlv::TlvItemValue::List(_fields) = inp {
831        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
832        Ok(EnergyTransferStartedEvent {
833                                session_id: item.get_int(&[0]).map(|v| v as u32),
834                                state: item.get_int(&[1]).and_then(|v| State::from_u8(v as u8)),
835                                maximum_current: item.get_int(&[2]).map(|v| v as u8),
836                                maximum_discharge_current: item.get_int(&[3]).map(|v| v as u8),
837        })
838    } else {
839        Err(anyhow::anyhow!("Expected struct fields"))
840    }
841}
842
843/// Decode EnergyTransferStopped event (0x03, priority: info)
844pub fn decode_energy_transfer_stopped_event(inp: &tlv::TlvItemValue) -> anyhow::Result<EnergyTransferStoppedEvent> {
845    if let tlv::TlvItemValue::List(_fields) = inp {
846        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
847        Ok(EnergyTransferStoppedEvent {
848                                session_id: item.get_int(&[0]).map(|v| v as u32),
849                                state: item.get_int(&[1]).and_then(|v| State::from_u8(v as u8)),
850                                reason: item.get_int(&[2]).and_then(|v| EnergyTransferStoppedReason::from_u8(v as u8)),
851                                energy_transferred: item.get_int(&[4]),
852                                energy_discharged: item.get_int(&[5]),
853        })
854    } else {
855        Err(anyhow::anyhow!("Expected struct fields"))
856    }
857}
858
859/// Decode Fault event (0x04, priority: critical)
860pub fn decode_fault_event(inp: &tlv::TlvItemValue) -> anyhow::Result<FaultEvent> {
861    if let tlv::TlvItemValue::List(_fields) = inp {
862        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
863        Ok(FaultEvent {
864                                session_id: item.get_int(&[0]).map(|v| v as u32),
865                                state: item.get_int(&[1]).and_then(|v| State::from_u8(v as u8)),
866                                fault_state_previous_state: item.get_int(&[2]).and_then(|v| FaultState::from_u8(v as u8)),
867                                fault_state_current_state: item.get_int(&[4]).and_then(|v| FaultState::from_u8(v as u8)),
868        })
869    } else {
870        Err(anyhow::anyhow!("Expected struct fields"))
871    }
872}
873
874/// Decode RFID event (0x05, priority: info)
875pub fn decode_rfid_event(inp: &tlv::TlvItemValue) -> anyhow::Result<RFIDEvent> {
876    if let tlv::TlvItemValue::List(_fields) = inp {
877        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
878        Ok(RFIDEvent {
879                                uid: item.get_octet_string_owned(&[0]),
880        })
881    } else {
882        Err(anyhow::anyhow!("Expected struct fields"))
883    }
884}
885