1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum AdjustmentCause {
18 Localoptimization = 0,
20 Gridoptimization = 1,
22}
23
24impl AdjustmentCause {
25 pub fn from_u8(value: u8) -> Option<Self> {
27 match value {
28 0 => Some(AdjustmentCause::Localoptimization),
29 1 => Some(AdjustmentCause::Gridoptimization),
30 _ => None,
31 }
32 }
33
34 pub fn to_u8(self) -> u8 {
36 self as u8
37 }
38}
39
40impl From<AdjustmentCause> for u8 {
41 fn from(val: AdjustmentCause) -> Self {
42 val as u8
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
47#[repr(u8)]
48pub enum Cause {
49 Normalcompletion = 0,
51 Offline = 1,
53 Fault = 2,
55 Useroptout = 3,
57 Cancelled = 4,
59}
60
61impl Cause {
62 pub fn from_u8(value: u8) -> Option<Self> {
64 match value {
65 0 => Some(Cause::Normalcompletion),
66 1 => Some(Cause::Offline),
67 2 => Some(Cause::Fault),
68 3 => Some(Cause::Useroptout),
69 4 => Some(Cause::Cancelled),
70 _ => None,
71 }
72 }
73
74 pub fn to_u8(self) -> u8 {
76 self as u8
77 }
78}
79
80impl From<Cause> for u8 {
81 fn from(val: Cause) -> Self {
82 val as u8
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
87#[repr(u8)]
88pub enum CostType {
89 Financial = 0,
91 Ghgemissions = 1,
93 Comfort = 2,
95 Temperature = 3,
97}
98
99impl CostType {
100 pub fn from_u8(value: u8) -> Option<Self> {
102 match value {
103 0 => Some(CostType::Financial),
104 1 => Some(CostType::Ghgemissions),
105 2 => Some(CostType::Comfort),
106 3 => Some(CostType::Temperature),
107 _ => None,
108 }
109 }
110
111 pub fn to_u8(self) -> u8 {
113 self as u8
114 }
115}
116
117impl From<CostType> for u8 {
118 fn from(val: CostType) -> Self {
119 val as u8
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
124#[repr(u8)]
125pub enum ESAState {
126 Offline = 0,
128 Online = 1,
130 Fault = 2,
132 Poweradjustactive = 3,
134 Paused = 4,
136}
137
138impl ESAState {
139 pub fn from_u8(value: u8) -> Option<Self> {
141 match value {
142 0 => Some(ESAState::Offline),
143 1 => Some(ESAState::Online),
144 2 => Some(ESAState::Fault),
145 3 => Some(ESAState::Poweradjustactive),
146 4 => Some(ESAState::Paused),
147 _ => None,
148 }
149 }
150
151 pub fn to_u8(self) -> u8 {
153 self as u8
154 }
155}
156
157impl From<ESAState> for u8 {
158 fn from(val: ESAState) -> Self {
159 val as u8
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
164#[repr(u8)]
165pub enum ESAType {
166 Evse = 0,
168 Spaceheating = 1,
170 Waterheating = 2,
172 Spacecooling = 3,
174 Spaceheatingcooling = 4,
176 Batterystorage = 5,
178 Solarpv = 6,
180 Fridgefreezer = 7,
182 Washingmachine = 8,
184 Dishwasher = 9,
186 Cooking = 10,
188 Homewaterpump = 11,
190 Irrigationwaterpump = 12,
192 Poolpump = 13,
194 Other = 255,
196}
197
198impl ESAType {
199 pub fn from_u8(value: u8) -> Option<Self> {
201 match value {
202 0 => Some(ESAType::Evse),
203 1 => Some(ESAType::Spaceheating),
204 2 => Some(ESAType::Waterheating),
205 3 => Some(ESAType::Spacecooling),
206 4 => Some(ESAType::Spaceheatingcooling),
207 5 => Some(ESAType::Batterystorage),
208 6 => Some(ESAType::Solarpv),
209 7 => Some(ESAType::Fridgefreezer),
210 8 => Some(ESAType::Washingmachine),
211 9 => Some(ESAType::Dishwasher),
212 10 => Some(ESAType::Cooking),
213 11 => Some(ESAType::Homewaterpump),
214 12 => Some(ESAType::Irrigationwaterpump),
215 13 => Some(ESAType::Poolpump),
216 255 => Some(ESAType::Other),
217 _ => None,
218 }
219 }
220
221 pub fn to_u8(self) -> u8 {
223 self as u8
224 }
225}
226
227impl From<ESAType> for u8 {
228 fn from(val: ESAType) -> Self {
229 val as u8
230 }
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
234#[repr(u8)]
235pub enum ForecastUpdateReason {
236 Internaloptimization = 0,
238 Localoptimization = 1,
240 Gridoptimization = 2,
242}
243
244impl ForecastUpdateReason {
245 pub fn from_u8(value: u8) -> Option<Self> {
247 match value {
248 0 => Some(ForecastUpdateReason::Internaloptimization),
249 1 => Some(ForecastUpdateReason::Localoptimization),
250 2 => Some(ForecastUpdateReason::Gridoptimization),
251 _ => None,
252 }
253 }
254
255 pub fn to_u8(self) -> u8 {
257 self as u8
258 }
259}
260
261impl From<ForecastUpdateReason> for u8 {
262 fn from(val: ForecastUpdateReason) -> Self {
263 val as u8
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
268#[repr(u8)]
269pub enum OptOutState {
270 Nooptout = 0,
272 Localoptout = 1,
274 Gridoptout = 2,
276 Optout = 3,
278}
279
280impl OptOutState {
281 pub fn from_u8(value: u8) -> Option<Self> {
283 match value {
284 0 => Some(OptOutState::Nooptout),
285 1 => Some(OptOutState::Localoptout),
286 2 => Some(OptOutState::Gridoptout),
287 3 => Some(OptOutState::Optout),
288 _ => None,
289 }
290 }
291
292 pub fn to_u8(self) -> u8 {
294 self as u8
295 }
296}
297
298impl From<OptOutState> for u8 {
299 fn from(val: OptOutState) -> Self {
300 val as u8
301 }
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
305#[repr(u8)]
306pub enum PowerAdjustReason {
307 Noadjustment = 0,
309 Localoptimizationadjustment = 1,
311 Gridoptimizationadjustment = 2,
313}
314
315impl PowerAdjustReason {
316 pub fn from_u8(value: u8) -> Option<Self> {
318 match value {
319 0 => Some(PowerAdjustReason::Noadjustment),
320 1 => Some(PowerAdjustReason::Localoptimizationadjustment),
321 2 => Some(PowerAdjustReason::Gridoptimizationadjustment),
322 _ => None,
323 }
324 }
325
326 pub fn to_u8(self) -> u8 {
328 self as u8
329 }
330}
331
332impl From<PowerAdjustReason> for u8 {
333 fn from(val: PowerAdjustReason) -> Self {
334 val as u8
335 }
336}
337
338#[derive(Debug, serde::Serialize)]
341pub struct Constraints {
342 pub start_time: Option<u64>,
343 pub duration: Option<u32>,
344 pub nominal_power: Option<u32>,
345 pub maximum_energy: Option<u64>,
346 pub load_control: Option<i8>,
347}
348
349#[derive(Debug, serde::Serialize)]
350pub struct Cost {
351 pub cost_type: Option<CostType>,
352 pub value: Option<i32>,
353 pub decimal_points: Option<u8>,
354 pub currency: Option<u16>,
355}
356
357#[derive(Debug, serde::Serialize)]
358pub struct Forecast {
359 pub forecast_id: Option<u32>,
360 pub active_slot_number: Option<u16>,
361 pub start_time: Option<u64>,
362 pub end_time: Option<u64>,
363 pub earliest_start_time: Option<u64>,
364 pub latest_end_time: Option<u64>,
365 pub is_pausable: Option<bool>,
366 pub slots: Option<Vec<Slot>>,
367 pub forecast_update_reason: Option<ForecastUpdateReason>,
368}
369
370#[derive(Debug, serde::Serialize)]
371pub struct PowerAdjustCapability {
372 pub power_adjust_capability: Option<Vec<PowerAdjust>>,
373 pub cause: Option<PowerAdjustReason>,
374}
375
376#[derive(Debug, serde::Serialize)]
377pub struct PowerAdjust {
378 pub min_power: Option<u32>,
379 pub max_power: Option<u32>,
380 pub min_duration: Option<u32>,
381 pub max_duration: Option<u32>,
382}
383
384#[derive(Debug, serde::Serialize)]
385pub struct SlotAdjustment {
386 pub slot_index: Option<u8>,
387 pub nominal_power: Option<u32>,
388 pub duration: Option<u32>,
389}
390
391#[derive(Debug, serde::Serialize)]
392pub struct Slot {
393 pub min_duration: Option<u32>,
394 pub max_duration: Option<u32>,
395 pub default_duration: Option<u32>,
396 pub elapsed_slot_time: Option<u32>,
397 pub remaining_slot_time: Option<u32>,
398 pub slot_is_pausable: Option<bool>,
399 pub min_pause_duration: Option<u32>,
400 pub max_pause_duration: Option<u32>,
401 pub manufacturer_esa_state: Option<u16>,
402 pub nominal_power: Option<u32>,
403 pub min_power: Option<u32>,
404 pub max_power: Option<u32>,
405 pub nominal_energy: Option<u64>,
406 pub costs: Option<Vec<Cost>>,
407 pub min_power_adjustment: Option<u32>,
408 pub max_power_adjustment: Option<u32>,
409 pub min_duration_adjustment: Option<u32>,
410 pub max_duration_adjustment: Option<u32>,
411}
412
413pub fn encode_power_adjust_request(power: u32, duration: u32, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
417 let tlv = tlv::TlvItemEnc {
418 tag: 0,
419 value: tlv::TlvItemValueEnc::StructInvisible(vec![
420 (0, tlv::TlvItemValueEnc::UInt32(power)).into(),
421 (1, tlv::TlvItemValueEnc::UInt32(duration)).into(),
422 (2, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
423 ]),
424 };
425 Ok(tlv.encode()?)
426}
427
428pub fn encode_start_time_adjust_request(requested_start_time: u64, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
430 let tlv = tlv::TlvItemEnc {
431 tag: 0,
432 value: tlv::TlvItemValueEnc::StructInvisible(vec![
433 (0, tlv::TlvItemValueEnc::UInt64(requested_start_time)).into(),
434 (1, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
435 ]),
436 };
437 Ok(tlv.encode()?)
438}
439
440pub fn encode_pause_request(duration: u32, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
442 let tlv = tlv::TlvItemEnc {
443 tag: 0,
444 value: tlv::TlvItemValueEnc::StructInvisible(vec![
445 (0, tlv::TlvItemValueEnc::UInt32(duration)).into(),
446 (1, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
447 ]),
448 };
449 Ok(tlv.encode()?)
450}
451
452pub fn encode_modify_forecast_request(forecast_id: u32, slot_adjustments: Vec<SlotAdjustment>, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
454 let tlv = tlv::TlvItemEnc {
455 tag: 0,
456 value: tlv::TlvItemValueEnc::StructInvisible(vec![
457 (0, tlv::TlvItemValueEnc::UInt32(forecast_id)).into(),
458 (1, tlv::TlvItemValueEnc::Array(slot_adjustments.into_iter().map(|v| {
459 let mut fields = Vec::new();
460 if let Some(x) = v.slot_index { fields.push((0, tlv::TlvItemValueEnc::UInt8(x)).into()); }
461 if let Some(x) = v.nominal_power { fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
462 if let Some(x) = v.duration { fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
463 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
464 }).collect())).into(),
465 (2, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
466 ]),
467 };
468 Ok(tlv.encode()?)
469}
470
471pub fn encode_request_constraint_based_forecast(constraints: Vec<Constraints>, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
473 let tlv = tlv::TlvItemEnc {
474 tag: 0,
475 value: tlv::TlvItemValueEnc::StructInvisible(vec![
476 (0, tlv::TlvItemValueEnc::Array(constraints.into_iter().map(|v| {
477 let mut fields = Vec::new();
478 if let Some(x) = v.start_time { fields.push((0, tlv::TlvItemValueEnc::UInt64(x)).into()); }
479 if let Some(x) = v.duration { fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
480 if let Some(x) = v.nominal_power { fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
481 if let Some(x) = v.maximum_energy { fields.push((3, tlv::TlvItemValueEnc::UInt64(x)).into()); }
482 if let Some(x) = v.load_control { fields.push((4, tlv::TlvItemValueEnc::Int8(x)).into()); }
483 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
484 }).collect())).into(),
485 (1, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
486 ]),
487 };
488 Ok(tlv.encode()?)
489}
490
491pub fn decode_esa_type(inp: &tlv::TlvItemValue) -> anyhow::Result<ESAType> {
495 if let tlv::TlvItemValue::Int(v) = inp {
496 ESAType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
497 } else {
498 Err(anyhow::anyhow!("Expected Integer"))
499 }
500}
501
502pub fn decode_esa_can_generate(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
504 if let tlv::TlvItemValue::Bool(v) = inp {
505 Ok(*v)
506 } else {
507 Err(anyhow::anyhow!("Expected Bool"))
508 }
509}
510
511pub fn decode_esa_state(inp: &tlv::TlvItemValue) -> anyhow::Result<ESAState> {
513 if let tlv::TlvItemValue::Int(v) = inp {
514 ESAState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
515 } else {
516 Err(anyhow::anyhow!("Expected Integer"))
517 }
518}
519
520pub fn decode_abs_min_power(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
522 if let tlv::TlvItemValue::Int(v) = inp {
523 Ok(*v as u32)
524 } else {
525 Err(anyhow::anyhow!("Expected UInt32"))
526 }
527}
528
529pub fn decode_abs_max_power(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
531 if let tlv::TlvItemValue::Int(v) = inp {
532 Ok(*v as u32)
533 } else {
534 Err(anyhow::anyhow!("Expected UInt32"))
535 }
536}
537
538pub fn decode_power_adjustment_capability(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<PowerAdjustCapability>> {
540 if let tlv::TlvItemValue::List(_fields) = inp {
541 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
543 Ok(Some(PowerAdjustCapability {
544 power_adjust_capability: {
545 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[0]) {
546 let mut items = Vec::new();
547 for list_item in l {
548 items.push(PowerAdjust {
549 min_power: list_item.get_int(&[0]).map(|v| v as u32),
550 max_power: list_item.get_int(&[1]).map(|v| v as u32),
551 min_duration: list_item.get_int(&[2]).map(|v| v as u32),
552 max_duration: list_item.get_int(&[3]).map(|v| v as u32),
553 });
554 }
555 Some(items)
556 } else {
557 None
558 }
559 },
560 cause: item.get_int(&[1]).and_then(|v| PowerAdjustReason::from_u8(v as u8)),
561 }))
562 } else {
566 Ok(None)
567 }
569}
570
571pub fn decode_forecast(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Forecast>> {
573 if let tlv::TlvItemValue::List(_fields) = inp {
574 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
576 Ok(Some(Forecast {
577 forecast_id: item.get_int(&[0]).map(|v| v as u32),
578 active_slot_number: item.get_int(&[1]).map(|v| v as u16),
579 start_time: item.get_int(&[2]),
580 end_time: item.get_int(&[3]),
581 earliest_start_time: item.get_int(&[4]),
582 latest_end_time: item.get_int(&[5]),
583 is_pausable: item.get_bool(&[6]),
584 slots: {
585 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[7]) {
586 let mut items = Vec::new();
587 for list_item in l {
588 items.push(Slot {
589 min_duration: list_item.get_int(&[0]).map(|v| v as u32),
590 max_duration: list_item.get_int(&[1]).map(|v| v as u32),
591 default_duration: list_item.get_int(&[2]).map(|v| v as u32),
592 elapsed_slot_time: list_item.get_int(&[3]).map(|v| v as u32),
593 remaining_slot_time: list_item.get_int(&[4]).map(|v| v as u32),
594 slot_is_pausable: list_item.get_bool(&[5]),
595 min_pause_duration: list_item.get_int(&[6]).map(|v| v as u32),
596 max_pause_duration: list_item.get_int(&[7]).map(|v| v as u32),
597 manufacturer_esa_state: list_item.get_int(&[8]).map(|v| v as u16),
598 nominal_power: list_item.get_int(&[9]).map(|v| v as u32),
599 min_power: list_item.get_int(&[10]).map(|v| v as u32),
600 max_power: list_item.get_int(&[11]).map(|v| v as u32),
601 nominal_energy: list_item.get_int(&[12]),
602 costs: {
603 if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[13]) {
604 let mut items = Vec::new();
605 for list_item in l {
606 items.push(Cost {
607 cost_type: list_item.get_int(&[0]).and_then(|v| CostType::from_u8(v as u8)),
608 value: list_item.get_int(&[1]).map(|v| v as i32),
609 decimal_points: list_item.get_int(&[2]).map(|v| v as u8),
610 currency: list_item.get_int(&[3]).map(|v| v as u16),
611 });
612 }
613 Some(items)
614 } else {
615 None
616 }
617 },
618 min_power_adjustment: list_item.get_int(&[14]).map(|v| v as u32),
619 max_power_adjustment: list_item.get_int(&[15]).map(|v| v as u32),
620 min_duration_adjustment: list_item.get_int(&[16]).map(|v| v as u32),
621 max_duration_adjustment: list_item.get_int(&[17]).map(|v| v as u32),
622 });
623 }
624 Some(items)
625 } else {
626 None
627 }
628 },
629 forecast_update_reason: item.get_int(&[8]).and_then(|v| ForecastUpdateReason::from_u8(v as u8)),
630 }))
631 } else {
635 Ok(None)
636 }
638}
639
640pub fn decode_opt_out_state(inp: &tlv::TlvItemValue) -> anyhow::Result<OptOutState> {
642 if let tlv::TlvItemValue::Int(v) = inp {
643 OptOutState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
644 } else {
645 Err(anyhow::anyhow!("Expected Integer"))
646 }
647}
648
649
650pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
662 if cluster_id != 0x0098 {
664 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0098, got {}\"}}", cluster_id);
665 }
666
667 match attribute_id {
668 0x0000 => {
669 match decode_esa_type(tlv_value) {
670 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
671 Err(e) => format!("{{\"error\": \"{}\"}}", e),
672 }
673 }
674 0x0001 => {
675 match decode_esa_can_generate(tlv_value) {
676 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
677 Err(e) => format!("{{\"error\": \"{}\"}}", e),
678 }
679 }
680 0x0002 => {
681 match decode_esa_state(tlv_value) {
682 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
683 Err(e) => format!("{{\"error\": \"{}\"}}", e),
684 }
685 }
686 0x0003 => {
687 match decode_abs_min_power(tlv_value) {
688 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
689 Err(e) => format!("{{\"error\": \"{}\"}}", e),
690 }
691 }
692 0x0004 => {
693 match decode_abs_max_power(tlv_value) {
694 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
695 Err(e) => format!("{{\"error\": \"{}\"}}", e),
696 }
697 }
698 0x0005 => {
699 match decode_power_adjustment_capability(tlv_value) {
700 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
701 Err(e) => format!("{{\"error\": \"{}\"}}", e),
702 }
703 }
704 0x0006 => {
705 match decode_forecast(tlv_value) {
706 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
707 Err(e) => format!("{{\"error\": \"{}\"}}", e),
708 }
709 }
710 0x0007 => {
711 match decode_opt_out_state(tlv_value) {
712 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
713 Err(e) => format!("{{\"error\": \"{}\"}}", e),
714 }
715 }
716 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
717 }
718}
719
720pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
725 vec![
726 (0x0000, "ESAType"),
727 (0x0001, "ESACanGenerate"),
728 (0x0002, "ESAState"),
729 (0x0003, "AbsMinPower"),
730 (0x0004, "AbsMaxPower"),
731 (0x0005, "PowerAdjustmentCapability"),
732 (0x0006, "Forecast"),
733 (0x0007, "OptOutState"),
734 ]
735}
736
737pub fn get_command_list() -> Vec<(u32, &'static str)> {
740 vec![
741 (0x00, "PowerAdjustRequest"),
742 (0x01, "CancelPowerAdjustRequest"),
743 (0x02, "StartTimeAdjustRequest"),
744 (0x03, "PauseRequest"),
745 (0x04, "ResumeRequest"),
746 (0x05, "ModifyForecastRequest"),
747 (0x06, "RequestConstraintBasedForecast"),
748 (0x07, "CancelRequest"),
749 ]
750}
751
752pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
753 match cmd_id {
754 0x00 => Some("PowerAdjustRequest"),
755 0x01 => Some("CancelPowerAdjustRequest"),
756 0x02 => Some("StartTimeAdjustRequest"),
757 0x03 => Some("PauseRequest"),
758 0x04 => Some("ResumeRequest"),
759 0x05 => Some("ModifyForecastRequest"),
760 0x06 => Some("RequestConstraintBasedForecast"),
761 0x07 => Some("CancelRequest"),
762 _ => None,
763 }
764}
765
766pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
767 match cmd_id {
768 0x00 => Some(vec![
769 crate::clusters::codec::CommandField { tag: 0, name: "power", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
770 crate::clusters::codec::CommandField { tag: 1, name: "duration", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
771 crate::clusters::codec::CommandField { tag: 2, name: "cause", kind: crate::clusters::codec::FieldKind::Enum { name: "AdjustmentCause", variants: &[(0, "Localoptimization"), (1, "Gridoptimization")] }, optional: false, nullable: false },
772 ]),
773 0x01 => Some(vec![]),
774 0x02 => Some(vec![
775 crate::clusters::codec::CommandField { tag: 0, name: "requested_start_time", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
776 crate::clusters::codec::CommandField { tag: 1, name: "cause", kind: crate::clusters::codec::FieldKind::Enum { name: "AdjustmentCause", variants: &[(0, "Localoptimization"), (1, "Gridoptimization")] }, optional: false, nullable: false },
777 ]),
778 0x03 => Some(vec![
779 crate::clusters::codec::CommandField { tag: 0, name: "duration", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
780 crate::clusters::codec::CommandField { tag: 1, name: "cause", kind: crate::clusters::codec::FieldKind::Enum { name: "AdjustmentCause", variants: &[(0, "Localoptimization"), (1, "Gridoptimization")] }, optional: false, nullable: false },
781 ]),
782 0x04 => Some(vec![]),
783 0x05 => Some(vec![
784 crate::clusters::codec::CommandField { tag: 0, name: "forecast_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
785 crate::clusters::codec::CommandField { tag: 1, name: "slot_adjustments", kind: crate::clusters::codec::FieldKind::List { entry_type: "SlotAdjustmentStruct" }, optional: false, nullable: false },
786 crate::clusters::codec::CommandField { tag: 2, name: "cause", kind: crate::clusters::codec::FieldKind::Enum { name: "AdjustmentCause", variants: &[(0, "Localoptimization"), (1, "Gridoptimization")] }, optional: false, nullable: false },
787 ]),
788 0x06 => Some(vec![
789 crate::clusters::codec::CommandField { tag: 0, name: "constraints", kind: crate::clusters::codec::FieldKind::List { entry_type: "ConstraintsStruct" }, optional: false, nullable: false },
790 crate::clusters::codec::CommandField { tag: 1, name: "cause", kind: crate::clusters::codec::FieldKind::Enum { name: "AdjustmentCause", variants: &[(0, "Localoptimization"), (1, "Gridoptimization")] }, optional: false, nullable: false },
791 ]),
792 0x07 => Some(vec![]),
793 _ => None,
794 }
795}
796
797pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
798 match cmd_id {
799 0x00 => {
800 let power = crate::clusters::codec::json_util::get_u32(args, "power")?;
801 let duration = crate::clusters::codec::json_util::get_u32(args, "duration")?;
802 let cause = {
803 let n = crate::clusters::codec::json_util::get_u64(args, "cause")?;
804 AdjustmentCause::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid AdjustmentCause: {}", n))?
805 };
806 encode_power_adjust_request(power, duration, cause)
807 }
808 0x01 => Ok(vec![]),
809 0x02 => {
810 let requested_start_time = crate::clusters::codec::json_util::get_u64(args, "requested_start_time")?;
811 let cause = {
812 let n = crate::clusters::codec::json_util::get_u64(args, "cause")?;
813 AdjustmentCause::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid AdjustmentCause: {}", n))?
814 };
815 encode_start_time_adjust_request(requested_start_time, cause)
816 }
817 0x03 => {
818 let duration = crate::clusters::codec::json_util::get_u32(args, "duration")?;
819 let cause = {
820 let n = crate::clusters::codec::json_util::get_u64(args, "cause")?;
821 AdjustmentCause::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid AdjustmentCause: {}", n))?
822 };
823 encode_pause_request(duration, cause)
824 }
825 0x04 => Ok(vec![]),
826 0x05 => Err(anyhow::anyhow!("command \"ModifyForecastRequest\" has complex args: use raw mode")),
827 0x06 => Err(anyhow::anyhow!("command \"RequestConstraintBasedForecast\" has complex args: use raw mode")),
828 0x07 => Ok(vec![]),
829 _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
830 }
831}
832
833pub async fn power_adjust_request(conn: &crate::controller::Connection, endpoint: u16, power: u32, duration: u32, cause: AdjustmentCause) -> anyhow::Result<()> {
837 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_POWERADJUSTREQUEST, &encode_power_adjust_request(power, duration, cause)?).await?;
838 Ok(())
839}
840
841pub async fn cancel_power_adjust_request(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
843 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_CANCELPOWERADJUSTREQUEST, &[]).await?;
844 Ok(())
845}
846
847pub async fn start_time_adjust_request(conn: &crate::controller::Connection, endpoint: u16, requested_start_time: u64, cause: AdjustmentCause) -> anyhow::Result<()> {
849 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_STARTTIMEADJUSTREQUEST, &encode_start_time_adjust_request(requested_start_time, cause)?).await?;
850 Ok(())
851}
852
853pub async fn pause_request(conn: &crate::controller::Connection, endpoint: u16, duration: u32, cause: AdjustmentCause) -> anyhow::Result<()> {
855 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_PAUSEREQUEST, &encode_pause_request(duration, cause)?).await?;
856 Ok(())
857}
858
859pub async fn resume_request(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
861 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_RESUMEREQUEST, &[]).await?;
862 Ok(())
863}
864
865pub async fn modify_forecast_request(conn: &crate::controller::Connection, endpoint: u16, forecast_id: u32, slot_adjustments: Vec<SlotAdjustment>, cause: AdjustmentCause) -> anyhow::Result<()> {
867 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_MODIFYFORECASTREQUEST, &encode_modify_forecast_request(forecast_id, slot_adjustments, cause)?).await?;
868 Ok(())
869}
870
871pub async fn request_constraint_based_forecast(conn: &crate::controller::Connection, endpoint: u16, constraints: Vec<Constraints>, cause: AdjustmentCause) -> anyhow::Result<()> {
873 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_REQUESTCONSTRAINTBASEDFORECAST, &encode_request_constraint_based_forecast(constraints, cause)?).await?;
874 Ok(())
875}
876
877pub async fn cancel_request(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
879 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_CANCELREQUEST, &[]).await?;
880 Ok(())
881}
882
883pub async fn read_esa_type(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ESAType> {
885 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_ESATYPE).await?;
886 decode_esa_type(&tlv)
887}
888
889pub async fn read_esa_can_generate(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
891 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_ESACANGENERATE).await?;
892 decode_esa_can_generate(&tlv)
893}
894
895pub async fn read_esa_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ESAState> {
897 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_ESASTATE).await?;
898 decode_esa_state(&tlv)
899}
900
901pub async fn read_abs_min_power(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u32> {
903 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_ABSMINPOWER).await?;
904 decode_abs_min_power(&tlv)
905}
906
907pub async fn read_abs_max_power(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u32> {
909 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_ABSMAXPOWER).await?;
910 decode_abs_max_power(&tlv)
911}
912
913pub async fn read_power_adjustment_capability(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<PowerAdjustCapability>> {
915 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_POWERADJUSTMENTCAPABILITY).await?;
916 decode_power_adjustment_capability(&tlv)
917}
918
919pub async fn read_forecast(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<Forecast>> {
921 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_FORECAST).await?;
922 decode_forecast(&tlv)
923}
924
925pub async fn read_opt_out_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OptOutState> {
927 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_OPTOUTSTATE).await?;
928 decode_opt_out_state(&tlv)
929}
930
931#[derive(Debug, serde::Serialize)]
932pub struct PowerAdjustEndEvent {
933 pub cause: Option<Cause>,
934 pub duration: Option<u32>,
935 pub energy_use: Option<u64>,
936}
937
938#[derive(Debug, serde::Serialize)]
939pub struct ResumedEvent {
940 pub cause: Option<Cause>,
941}
942
943pub fn decode_power_adjust_end_event(inp: &tlv::TlvItemValue) -> anyhow::Result<PowerAdjustEndEvent> {
947 if let tlv::TlvItemValue::List(_fields) = inp {
948 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
949 Ok(PowerAdjustEndEvent {
950 cause: item.get_int(&[0]).and_then(|v| Cause::from_u8(v as u8)),
951 duration: item.get_int(&[1]).map(|v| v as u32),
952 energy_use: item.get_int(&[2]),
953 })
954 } else {
955 Err(anyhow::anyhow!("Expected struct fields"))
956 }
957}
958
959pub fn decode_resumed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ResumedEvent> {
961 if let tlv::TlvItemValue::List(_fields) = inp {
962 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
963 Ok(ResumedEvent {
964 cause: item.get_int(&[0]).and_then(|v| Cause::from_u8(v as u8)),
965 })
966 } else {
967 Err(anyhow::anyhow!("Expected struct fields"))
968 }
969}
970