1use std::time::Duration;
49
50use crate::types::{
51 ColorTemperature, Dimmer, FadeDuration, HsbColor, PowerState, Scheme, TasmotaDateTime,
52 WakeupDuration,
53};
54
55use super::StateChange;
56
57#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
81pub struct SystemInfo {
82 #[serde(
84 serialize_with = "serialize_duration_as_secs",
85 deserialize_with = "deserialize_duration_from_secs"
86 )]
87 uptime: Option<Duration>,
88 wifi_rssi: Option<i8>,
90 heap: Option<u32>,
92}
93
94#[allow(clippy::ref_option)]
96fn serialize_duration_as_secs<S>(
97 duration: &Option<Duration>,
98 serializer: S,
99) -> Result<S::Ok, S::Error>
100where
101 S: serde::Serializer,
102{
103 match duration {
104 Some(d) => serializer.serialize_some(&d.as_secs()),
105 None => serializer.serialize_none(),
106 }
107}
108
109fn deserialize_duration_from_secs<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
110where
111 D: serde::Deserializer<'de>,
112{
113 let opt: Option<u64> = serde::Deserialize::deserialize(deserializer)?;
114 Ok(opt.map(Duration::from_secs))
115}
116
117impl SystemInfo {
118 #[must_use]
120 pub fn new() -> Self {
121 Self::default()
122 }
123
124 #[must_use]
136 pub fn with_uptime(mut self, duration: Duration) -> Self {
137 self.uptime = Some(duration);
138 self
139 }
140
141 #[must_use]
143 pub fn with_wifi_rssi(mut self, rssi: i8) -> Self {
144 self.wifi_rssi = Some(rssi);
145 self
146 }
147
148 #[must_use]
150 pub fn with_heap(mut self, heap_kb: u32) -> Self {
151 self.heap = Some(heap_kb);
152 self
153 }
154
155 #[must_use]
169 pub fn uptime(&self) -> Option<Duration> {
170 self.uptime
171 }
172
173 #[must_use]
178 pub fn wifi_rssi(&self) -> Option<i8> {
179 self.wifi_rssi
180 }
181
182 #[must_use]
184 pub fn heap(&self) -> Option<u32> {
185 self.heap
186 }
187
188 pub fn merge(&mut self, other: &SystemInfo) {
191 if other.uptime.is_some() {
192 self.uptime = other.uptime;
193 }
194 if other.wifi_rssi.is_some() {
195 self.wifi_rssi = other.wifi_rssi;
196 }
197 if other.heap.is_some() {
198 self.heap = other.heap;
199 }
200 }
201
202 #[must_use]
204 pub fn is_empty(&self) -> bool {
205 self.uptime.is_none() && self.wifi_rssi.is_none() && self.heap.is_none()
206 }
207}
208
209#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
231pub struct DeviceState {
232 power: [Option<PowerState>; 8],
234 dimmer: Option<Dimmer>,
236 hsb_color: Option<HsbColor>,
238 color_temperature: Option<ColorTemperature>,
240 scheme: Option<Scheme>,
242 wakeup_duration: Option<WakeupDuration>,
244 fade_enabled: Option<bool>,
246 fade_duration: Option<FadeDuration>,
248 power_consumption: Option<f32>,
250 voltage: Option<f32>,
252 current: Option<f32>,
254 apparent_power: Option<f32>,
256 reactive_power: Option<f32>,
258 power_factor: Option<f32>,
260 energy_today: Option<f32>,
262 energy_yesterday: Option<f32>,
264 energy_total: Option<f32>,
266 total_start_time: Option<TasmotaDateTime>,
268 frequency: Option<f32>,
270 system_info: Option<SystemInfo>,
274}
275
276impl DeviceState {
277 #[must_use]
279 pub fn new() -> Self {
280 Self::default()
281 }
282
283 #[must_use]
296 pub fn power(&self, index: u8) -> Option<PowerState> {
297 if index == 0 || index > 8 {
298 return None;
299 }
300 self.power[usize::from(index - 1)]
301 }
302
303 pub fn set_power(&mut self, index: u8, state: PowerState) {
312 if index > 0 && index <= 8 {
313 self.power[usize::from(index - 1)] = Some(state);
314 }
315 }
316
317 pub fn clear_power(&mut self, index: u8) {
319 if index > 0 && index <= 8 {
320 self.power[usize::from(index - 1)] = None;
321 }
322 }
323
324 #[must_use]
326 pub fn all_power_states(&self) -> Vec<(u8, PowerState)> {
327 self.power
328 .iter()
329 .enumerate()
330 .filter_map(|(i, state)| {
331 state.map(|s| {
332 #[allow(clippy::cast_possible_truncation)]
334 let index = (i + 1) as u8;
335 (index, s)
336 })
337 })
338 .collect()
339 }
340
341 #[must_use]
343 pub fn is_any_on(&self) -> bool {
344 self.power.iter().any(|s| matches!(s, Some(PowerState::On)))
345 }
346
347 #[must_use]
351 pub fn dimmer(&self) -> Option<Dimmer> {
352 self.dimmer
353 }
354
355 pub fn set_dimmer(&mut self, value: Dimmer) {
357 self.dimmer = Some(value);
358 }
359
360 pub fn clear_dimmer(&mut self) {
362 self.dimmer = None;
363 }
364
365 #[must_use]
369 pub fn hsb_color(&self) -> Option<HsbColor> {
370 self.hsb_color
371 }
372
373 pub fn set_hsb_color(&mut self, color: HsbColor) {
375 self.hsb_color = Some(color);
376 }
377
378 pub fn clear_hsb_color(&mut self) {
380 self.hsb_color = None;
381 }
382
383 #[must_use]
387 pub fn color_temperature(&self) -> Option<ColorTemperature> {
388 self.color_temperature
389 }
390
391 pub fn set_color_temperature(&mut self, ct: ColorTemperature) {
393 self.color_temperature = Some(ct);
394 }
395
396 pub fn clear_color_temperature(&mut self) {
398 self.color_temperature = None;
399 }
400
401 #[must_use]
405 pub fn scheme(&self) -> Option<Scheme> {
406 self.scheme
407 }
408
409 pub fn set_scheme(&mut self, scheme: Scheme) {
411 self.scheme = Some(scheme);
412 }
413
414 pub fn clear_scheme(&mut self) {
416 self.scheme = None;
417 }
418
419 #[must_use]
423 pub fn wakeup_duration(&self) -> Option<WakeupDuration> {
424 self.wakeup_duration
425 }
426
427 pub fn set_wakeup_duration(&mut self, duration: WakeupDuration) {
429 self.wakeup_duration = Some(duration);
430 }
431
432 pub fn clear_wakeup_duration(&mut self) {
434 self.wakeup_duration = None;
435 }
436
437 #[must_use]
441 pub fn fade_enabled(&self) -> Option<bool> {
442 self.fade_enabled
443 }
444
445 pub fn set_fade_enabled(&mut self, enabled: bool) {
447 self.fade_enabled = Some(enabled);
448 }
449
450 pub fn clear_fade_enabled(&mut self) {
452 self.fade_enabled = None;
453 }
454
455 #[must_use]
457 pub fn fade_duration(&self) -> Option<FadeDuration> {
458 self.fade_duration
459 }
460
461 pub fn set_fade_duration(&mut self, duration: FadeDuration) {
463 self.fade_duration = Some(duration);
464 }
465
466 pub fn clear_fade_duration(&mut self) {
468 self.fade_duration = None;
469 }
470
471 #[must_use]
475 pub fn power_consumption(&self) -> Option<f32> {
476 self.power_consumption
477 }
478
479 pub fn set_power_consumption(&mut self, watts: f32) {
481 self.power_consumption = Some(watts);
482 }
483
484 #[must_use]
486 pub fn voltage(&self) -> Option<f32> {
487 self.voltage
488 }
489
490 pub fn set_voltage(&mut self, volts: f32) {
492 self.voltage = Some(volts);
493 }
494
495 #[must_use]
497 pub fn current(&self) -> Option<f32> {
498 self.current
499 }
500
501 pub fn set_current(&mut self, amps: f32) {
503 self.current = Some(amps);
504 }
505
506 #[must_use]
508 pub fn energy_total(&self) -> Option<f32> {
509 self.energy_total
510 }
511
512 pub fn set_energy_total(&mut self, kwh: f32) {
514 self.energy_total = Some(kwh);
515 }
516
517 #[must_use]
519 pub fn apparent_power(&self) -> Option<f32> {
520 self.apparent_power
521 }
522
523 pub fn set_apparent_power(&mut self, va: f32) {
525 self.apparent_power = Some(va);
526 }
527
528 #[must_use]
530 pub fn reactive_power(&self) -> Option<f32> {
531 self.reactive_power
532 }
533
534 pub fn set_reactive_power(&mut self, var: f32) {
536 self.reactive_power = Some(var);
537 }
538
539 #[must_use]
541 pub fn power_factor(&self) -> Option<f32> {
542 self.power_factor
543 }
544
545 pub fn set_power_factor(&mut self, factor: f32) {
547 self.power_factor = Some(factor);
548 }
549
550 #[must_use]
552 pub fn energy_today(&self) -> Option<f32> {
553 self.energy_today
554 }
555
556 pub fn set_energy_today(&mut self, kwh: f32) {
558 self.energy_today = Some(kwh);
559 }
560
561 #[must_use]
563 pub fn energy_yesterday(&self) -> Option<f32> {
564 self.energy_yesterday
565 }
566
567 pub fn set_energy_yesterday(&mut self, kwh: f32) {
569 self.energy_yesterday = Some(kwh);
570 }
571
572 #[must_use]
578 pub fn total_start_time(&self) -> Option<&TasmotaDateTime> {
579 self.total_start_time.as_ref()
580 }
581
582 pub fn set_total_start_time(&mut self, time: TasmotaDateTime) {
584 self.total_start_time = Some(time);
585 }
586
587 #[must_use]
589 pub fn frequency(&self) -> Option<f32> {
590 self.frequency
591 }
592
593 pub fn set_frequency(&mut self, hz: f32) {
595 self.frequency = Some(hz);
596 }
597
598 #[must_use]
620 pub fn system_info(&self) -> Option<&SystemInfo> {
621 self.system_info.as_ref()
622 }
623
624 pub fn set_system_info(&mut self, info: SystemInfo) {
626 self.system_info = Some(info);
627 }
628
629 pub fn update_system_info(&mut self, info: &SystemInfo) {
633 if let Some(existing) = &mut self.system_info {
634 existing.merge(info);
635 } else {
636 self.system_info = Some(info.clone());
637 }
638 }
639
640 #[must_use]
658 pub fn uptime(&self) -> Option<Duration> {
659 self.system_info.as_ref().and_then(SystemInfo::uptime)
660 }
661
662 #[allow(clippy::too_many_lines)]
671 pub fn apply(&mut self, change: &StateChange) -> bool {
674 match change {
675 StateChange::Power { index, state } => {
676 let current = self.power(*index);
677 if current == Some(*state) {
678 false
679 } else {
680 self.set_power(*index, *state);
681 true
682 }
683 }
684 StateChange::Dimmer(value) => {
685 if self.dimmer == Some(*value) {
686 false
687 } else {
688 self.dimmer = Some(*value);
689 true
690 }
691 }
692 StateChange::HsbColor(color) => {
693 if self.hsb_color == Some(*color) {
694 false
695 } else {
696 self.hsb_color = Some(*color);
697 true
698 }
699 }
700 StateChange::ColorTemperature(ct) => {
701 if self.color_temperature == Some(*ct) {
702 false
703 } else {
704 self.color_temperature = Some(*ct);
705 true
706 }
707 }
708 StateChange::Scheme(scheme) => {
709 if self.scheme == Some(*scheme) {
710 false
711 } else {
712 self.scheme = Some(*scheme);
713 true
714 }
715 }
716 StateChange::WakeupDuration(duration) => {
717 if self.wakeup_duration == Some(*duration) {
718 false
719 } else {
720 self.wakeup_duration = Some(*duration);
721 true
722 }
723 }
724 StateChange::FadeEnabled(enabled) => {
725 if self.fade_enabled == Some(*enabled) {
726 false
727 } else {
728 self.fade_enabled = Some(*enabled);
729 true
730 }
731 }
732 StateChange::FadeDuration(duration) => {
733 if self.fade_duration == Some(*duration) {
734 false
735 } else {
736 self.fade_duration = Some(*duration);
737 true
738 }
739 }
740 StateChange::Energy {
741 power,
742 voltage,
743 current,
744 apparent_power,
745 reactive_power,
746 power_factor,
747 energy_today,
748 energy_yesterday,
749 energy_total,
750 total_start_time,
751 frequency,
752 } => {
753 let mut changed = false;
754
755 macro_rules! update_if_some {
757 ($field:ident, $value:expr) => {
758 if let Some(v) = $value {
759 if self.$field != Some(*v) {
760 self.$field = Some(*v);
761 changed = true;
762 }
763 }
764 };
765 }
766
767 update_if_some!(power_consumption, power);
768 update_if_some!(voltage, voltage);
769 update_if_some!(current, current);
770 update_if_some!(apparent_power, apparent_power);
771 update_if_some!(reactive_power, reactive_power);
772 update_if_some!(power_factor, power_factor);
773 update_if_some!(energy_today, energy_today);
774 update_if_some!(energy_yesterday, energy_yesterday);
775 update_if_some!(energy_total, energy_total);
776 update_if_some!(frequency, frequency);
777
778 if let Some(time) = total_start_time
780 && self.total_start_time.as_ref() != Some(time)
781 {
782 self.total_start_time = Some(time.clone());
783 changed = true;
784 }
785
786 changed
787 }
788 StateChange::Batch(changes) => {
789 let mut any_changed = false;
790 for c in changes {
791 if self.apply(c) {
792 any_changed = true;
793 }
794 }
795 any_changed
796 }
797 }
798 }
799
800 pub fn clear(&mut self) {
802 *self = Self::new();
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809
810 #[test]
811 fn new_state_is_empty() {
812 let state = DeviceState::new();
813 assert!(state.power(1).is_none());
814 assert!(state.dimmer().is_none());
815 assert!(state.hsb_color().is_none());
816 assert!(state.color_temperature().is_none());
817 assert!(state.power_consumption().is_none());
818 }
819
820 #[test]
821 fn power_state_management() {
822 let mut state = DeviceState::new();
823
824 state.set_power(1, PowerState::On);
825 assert_eq!(state.power(1), Some(PowerState::On));
826 assert!(state.power(2).is_none());
827
828 state.set_power(2, PowerState::Off);
829 assert_eq!(state.power(2), Some(PowerState::Off));
830
831 state.clear_power(1);
832 assert!(state.power(1).is_none());
833 }
834
835 #[test]
836 fn power_index_bounds() {
837 let mut state = DeviceState::new();
838
839 state.set_power(0, PowerState::On);
841 assert!(state.power(0).is_none());
842
843 state.set_power(9, PowerState::On);
845 assert!(state.power(9).is_none());
846
847 state.set_power(8, PowerState::On);
849 assert_eq!(state.power(8), Some(PowerState::On));
850 }
851
852 #[test]
853 fn all_power_states() {
854 let mut state = DeviceState::new();
855 state.set_power(1, PowerState::On);
856 state.set_power(3, PowerState::Off);
857 state.set_power(5, PowerState::On);
858
859 let states = state.all_power_states();
860 assert_eq!(states.len(), 3);
861 assert!(states.contains(&(1, PowerState::On)));
862 assert!(states.contains(&(3, PowerState::Off)));
863 assert!(states.contains(&(5, PowerState::On)));
864 }
865
866 #[test]
867 fn is_any_on() {
868 let mut state = DeviceState::new();
869 assert!(!state.is_any_on());
870
871 state.set_power(1, PowerState::Off);
872 assert!(!state.is_any_on());
873
874 state.set_power(2, PowerState::On);
875 assert!(state.is_any_on());
876 }
877
878 #[test]
879 fn apply_power_change() {
880 let mut state = DeviceState::new();
881
882 let change = StateChange::Power {
883 index: 1,
884 state: PowerState::On,
885 };
886 assert!(state.apply(&change));
887 assert_eq!(state.power(1), Some(PowerState::On));
888
889 assert!(!state.apply(&change));
891 }
892
893 #[test]
894 fn apply_dimmer_change() {
895 let mut state = DeviceState::new();
896 let dimmer = Dimmer::new(75).unwrap();
897
898 let change = StateChange::Dimmer(dimmer);
899 assert!(state.apply(&change));
900 assert_eq!(state.dimmer(), Some(dimmer));
901 }
902
903 #[test]
904 fn apply_batch_changes() {
905 let mut state = DeviceState::new();
906
907 let changes = StateChange::Batch(vec![
908 StateChange::Power {
909 index: 1,
910 state: PowerState::On,
911 },
912 StateChange::Dimmer(Dimmer::new(50).unwrap()),
913 ]);
914
915 assert!(state.apply(&changes));
916 assert_eq!(state.power(1), Some(PowerState::On));
917 assert_eq!(state.dimmer(), Some(Dimmer::new(50).unwrap()));
918 }
919
920 #[test]
921 fn clear_resets_state() {
922 let mut state = DeviceState::new();
923 state.set_power(1, PowerState::On);
924 state.set_dimmer(Dimmer::new(75).unwrap());
925
926 state.clear();
927
928 assert!(state.power(1).is_none());
929 assert!(state.dimmer().is_none());
930 }
931
932 #[test]
933 fn apply_batch_with_hsb_color() {
934 use crate::types::HsbColor;
935
936 let mut state = DeviceState::new();
937 let hsb = HsbColor::new(360, 100, 100).unwrap();
938
939 let changes = StateChange::Batch(vec![
940 StateChange::Power {
941 index: 1,
942 state: PowerState::Off,
943 },
944 StateChange::Dimmer(Dimmer::new(100).unwrap()),
945 StateChange::HsbColor(hsb),
946 ]);
947
948 assert!(state.apply(&changes));
949 assert_eq!(state.power(1), Some(PowerState::Off));
950 assert_eq!(state.dimmer(), Some(Dimmer::new(100).unwrap()));
951
952 let applied_hsb = state.hsb_color().expect("HsbColor should be set");
954 assert_eq!(applied_hsb.hue(), 360);
955 assert_eq!(applied_hsb.saturation(), 100);
956 assert_eq!(applied_hsb.brightness(), 100);
957 }
958
959 #[test]
960 fn apply_state_from_tasmota_telemetry() {
961 use crate::telemetry::TelemetryState;
962
963 let json = r#"{
965 "Time":"2025-12-24T14:24:03",
966 "Uptime":"1T23:46:58",
967 "UptimeSec":172018,
968 "Heap":25,
969 "SleepMode":"Dynamic",
970 "Sleep":50,
971 "LoadAvg":19,
972 "MqttCount":1,
973 "POWER":"OFF",
974 "Dimmer":100,
975 "Color":"FF00000000",
976 "HSBColor":"360,100,100",
977 "White":0,
978 "CT":153,
979 "Channel":[100,0,0,0,0],
980 "Scheme":0,
981 "Fade":"ON",
982 "Speed":2,
983 "LedTable":"ON",
984 "Wifi":{"AP":1}
985 }"#;
986
987 let telemetry: TelemetryState = serde_json::from_str(json).unwrap();
989 let changes = telemetry.to_state_changes();
990
991 let mut state = DeviceState::new();
993 for change in changes {
994 state.apply(&change);
995 }
996
997 assert_eq!(state.power(1), Some(PowerState::Off));
999 assert_eq!(state.dimmer(), Some(Dimmer::new(100).unwrap()));
1000
1001 let hsb = state
1003 .hsb_color()
1004 .expect("HSBColor should be set from telemetry");
1005 assert_eq!(hsb.hue(), 360);
1006 assert_eq!(hsb.saturation(), 100);
1007 assert_eq!(hsb.brightness(), 100);
1008
1009 assert!(state.color_temperature().is_some());
1011 assert_eq!(state.color_temperature().unwrap().value(), 153);
1012
1013 assert_eq!(state.fade_enabled(), Some(true));
1015
1016 assert_eq!(state.fade_duration().map(|s| s.value()), Some(2));
1018 }
1019
1020 #[test]
1021 fn fade_getters_setters() {
1022 let mut state = DeviceState::new();
1023
1024 assert!(state.fade_enabled().is_none());
1026 assert!(state.fade_duration().is_none());
1027
1028 state.set_fade_enabled(true);
1030 assert_eq!(state.fade_enabled(), Some(true));
1031
1032 state.set_fade_enabled(false);
1033 assert_eq!(state.fade_enabled(), Some(false));
1034
1035 let duration = FadeDuration::from_raw(15).unwrap();
1037 state.set_fade_duration(duration);
1038 assert_eq!(state.fade_duration(), Some(duration));
1039
1040 state.clear_fade_enabled();
1042 state.clear_fade_duration();
1043 assert!(state.fade_enabled().is_none());
1044 assert!(state.fade_duration().is_none());
1045 }
1046
1047 #[test]
1048 fn apply_fade_changes() {
1049 let mut state = DeviceState::new();
1050
1051 let change = StateChange::FadeEnabled(true);
1053 assert!(state.apply(&change));
1054 assert_eq!(state.fade_enabled(), Some(true));
1055
1056 assert!(!state.apply(&change));
1058
1059 let duration = FadeDuration::from_raw(20).unwrap();
1061 let change = StateChange::FadeDuration(duration);
1062 assert!(state.apply(&change));
1063 assert_eq!(state.fade_duration(), Some(duration));
1064 }
1065
1066 #[test]
1069 fn system_info_new_is_empty() {
1070 let info = SystemInfo::new();
1071 assert!(info.is_empty());
1072 assert!(info.uptime().is_none());
1073 assert!(info.wifi_rssi().is_none());
1074 assert!(info.heap().is_none());
1075 }
1076
1077 #[test]
1078 fn system_info_builder_pattern() {
1079 let info = SystemInfo::new()
1080 .with_uptime(Duration::from_secs(172800))
1081 .with_wifi_rssi(-55)
1082 .with_heap(25000);
1083
1084 assert!(!info.is_empty());
1085 assert_eq!(info.uptime(), Some(Duration::from_secs(172800)));
1086 assert_eq!(info.wifi_rssi(), Some(-55));
1087 assert_eq!(info.heap(), Some(25000));
1088 }
1089
1090 #[test]
1091 fn system_info_merge_preserves_existing() {
1092 let mut info = SystemInfo::new()
1093 .with_uptime(Duration::from_secs(100))
1094 .with_wifi_rssi(-50);
1095
1096 let update = SystemInfo::new().with_heap(30000);
1098 info.merge(&update);
1099
1100 assert_eq!(info.uptime(), Some(Duration::from_secs(100)));
1102 assert_eq!(info.wifi_rssi(), Some(-50));
1103 assert_eq!(info.heap(), Some(30000));
1104 }
1105
1106 #[test]
1107 fn system_info_merge_updates_values() {
1108 let mut info = SystemInfo::new()
1109 .with_uptime(Duration::from_secs(100))
1110 .with_wifi_rssi(-50);
1111
1112 let update = SystemInfo::new()
1114 .with_uptime(Duration::from_secs(200))
1115 .with_heap(30000);
1116 info.merge(&update);
1117
1118 assert_eq!(info.uptime(), Some(Duration::from_secs(200)));
1120 assert_eq!(info.wifi_rssi(), Some(-50)); assert_eq!(info.heap(), Some(30000));
1122 }
1123
1124 #[test]
1125 fn device_state_system_info_getters_setters() {
1126 let mut state = DeviceState::new();
1127
1128 assert!(state.system_info().is_none());
1130 assert!(state.uptime().is_none());
1131
1132 let info = SystemInfo::new().with_uptime(Duration::from_secs(172800));
1134 state.set_system_info(info);
1135
1136 assert!(state.system_info().is_some());
1137 assert_eq!(state.uptime(), Some(Duration::from_secs(172800)));
1138 }
1139
1140 #[test]
1141 fn device_state_update_system_info() {
1142 let mut state = DeviceState::new();
1143
1144 let info1 = SystemInfo::new().with_uptime(Duration::from_secs(100));
1146 state.update_system_info(&info1);
1147 assert_eq!(state.uptime(), Some(Duration::from_secs(100)));
1148
1149 let info2 = SystemInfo::new().with_wifi_rssi(-55);
1151 state.update_system_info(&info2);
1152
1153 let sys_info = state.system_info().unwrap();
1154 assert_eq!(sys_info.uptime(), Some(Duration::from_secs(100))); assert_eq!(sys_info.wifi_rssi(), Some(-55)); }
1157
1158 #[test]
1159 fn device_state_clear_clears_system_info() {
1160 let mut state = DeviceState::new();
1161 state.set_system_info(SystemInfo::new().with_uptime(Duration::from_secs(172800)));
1162
1163 state.clear();
1164
1165 assert!(state.system_info().is_none());
1166 }
1167
1168 #[test]
1169 fn system_info_serialization() {
1170 let info = SystemInfo::new()
1171 .with_uptime(Duration::from_secs(172800))
1172 .with_wifi_rssi(-55)
1173 .with_heap(25000);
1174
1175 let json = serde_json::to_string(&info).unwrap();
1176 let deserialized: SystemInfo = serde_json::from_str(&json).unwrap();
1177
1178 assert_eq!(info, deserialized);
1179 }
1180
1181 #[test]
1182 fn device_state_with_system_info_serialization() {
1183 let mut state = DeviceState::new();
1184 state.set_power(1, PowerState::On);
1185 state.set_system_info(
1186 SystemInfo::new()
1187 .with_uptime(Duration::from_secs(172800))
1188 .with_wifi_rssi(-55),
1189 );
1190
1191 let json = serde_json::to_string(&state).unwrap();
1192 let deserialized: DeviceState = serde_json::from_str(&json).unwrap();
1193
1194 assert_eq!(state, deserialized);
1195 assert_eq!(deserialized.uptime(), Some(Duration::from_secs(172800)));
1196 }
1197}