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 system_info: Option<SystemInfo>,
272}
273
274impl DeviceState {
275 #[must_use]
277 pub fn new() -> Self {
278 Self::default()
279 }
280
281 #[must_use]
294 pub fn power(&self, index: u8) -> Option<PowerState> {
295 if index == 0 || index > 8 {
296 return None;
297 }
298 self.power[usize::from(index - 1)]
299 }
300
301 pub fn set_power(&mut self, index: u8, state: PowerState) {
310 if index > 0 && index <= 8 {
311 self.power[usize::from(index - 1)] = Some(state);
312 }
313 }
314
315 pub fn clear_power(&mut self, index: u8) {
317 if index > 0 && index <= 8 {
318 self.power[usize::from(index - 1)] = None;
319 }
320 }
321
322 #[must_use]
324 pub fn all_power_states(&self) -> Vec<(u8, PowerState)> {
325 self.power
326 .iter()
327 .enumerate()
328 .filter_map(|(i, state)| {
329 state.map(|s| {
330 #[allow(clippy::cast_possible_truncation)]
332 let index = (i + 1) as u8;
333 (index, s)
334 })
335 })
336 .collect()
337 }
338
339 #[must_use]
341 pub fn is_any_on(&self) -> bool {
342 self.power.iter().any(|s| matches!(s, Some(PowerState::On)))
343 }
344
345 #[must_use]
349 pub fn dimmer(&self) -> Option<Dimmer> {
350 self.dimmer
351 }
352
353 pub fn set_dimmer(&mut self, value: Dimmer) {
355 self.dimmer = Some(value);
356 }
357
358 pub fn clear_dimmer(&mut self) {
360 self.dimmer = None;
361 }
362
363 #[must_use]
367 pub fn hsb_color(&self) -> Option<HsbColor> {
368 self.hsb_color
369 }
370
371 pub fn set_hsb_color(&mut self, color: HsbColor) {
373 self.hsb_color = Some(color);
374 }
375
376 pub fn clear_hsb_color(&mut self) {
378 self.hsb_color = None;
379 }
380
381 #[must_use]
385 pub fn color_temperature(&self) -> Option<ColorTemperature> {
386 self.color_temperature
387 }
388
389 pub fn set_color_temperature(&mut self, ct: ColorTemperature) {
391 self.color_temperature = Some(ct);
392 }
393
394 pub fn clear_color_temperature(&mut self) {
396 self.color_temperature = None;
397 }
398
399 #[must_use]
403 pub fn scheme(&self) -> Option<Scheme> {
404 self.scheme
405 }
406
407 pub fn set_scheme(&mut self, scheme: Scheme) {
409 self.scheme = Some(scheme);
410 }
411
412 pub fn clear_scheme(&mut self) {
414 self.scheme = None;
415 }
416
417 #[must_use]
421 pub fn wakeup_duration(&self) -> Option<WakeupDuration> {
422 self.wakeup_duration
423 }
424
425 pub fn set_wakeup_duration(&mut self, duration: WakeupDuration) {
427 self.wakeup_duration = Some(duration);
428 }
429
430 pub fn clear_wakeup_duration(&mut self) {
432 self.wakeup_duration = None;
433 }
434
435 #[must_use]
439 pub fn fade_enabled(&self) -> Option<bool> {
440 self.fade_enabled
441 }
442
443 pub fn set_fade_enabled(&mut self, enabled: bool) {
445 self.fade_enabled = Some(enabled);
446 }
447
448 pub fn clear_fade_enabled(&mut self) {
450 self.fade_enabled = None;
451 }
452
453 #[must_use]
455 pub fn fade_duration(&self) -> Option<FadeDuration> {
456 self.fade_duration
457 }
458
459 pub fn set_fade_duration(&mut self, duration: FadeDuration) {
461 self.fade_duration = Some(duration);
462 }
463
464 pub fn clear_fade_duration(&mut self) {
466 self.fade_duration = None;
467 }
468
469 #[must_use]
473 pub fn power_consumption(&self) -> Option<f32> {
474 self.power_consumption
475 }
476
477 pub fn set_power_consumption(&mut self, watts: f32) {
479 self.power_consumption = Some(watts);
480 }
481
482 #[must_use]
484 pub fn voltage(&self) -> Option<f32> {
485 self.voltage
486 }
487
488 pub fn set_voltage(&mut self, volts: f32) {
490 self.voltage = Some(volts);
491 }
492
493 #[must_use]
495 pub fn current(&self) -> Option<f32> {
496 self.current
497 }
498
499 pub fn set_current(&mut self, amps: f32) {
501 self.current = Some(amps);
502 }
503
504 #[must_use]
506 pub fn energy_total(&self) -> Option<f32> {
507 self.energy_total
508 }
509
510 pub fn set_energy_total(&mut self, kwh: f32) {
512 self.energy_total = Some(kwh);
513 }
514
515 #[must_use]
517 pub fn apparent_power(&self) -> Option<f32> {
518 self.apparent_power
519 }
520
521 pub fn set_apparent_power(&mut self, va: f32) {
523 self.apparent_power = Some(va);
524 }
525
526 #[must_use]
528 pub fn reactive_power(&self) -> Option<f32> {
529 self.reactive_power
530 }
531
532 pub fn set_reactive_power(&mut self, var: f32) {
534 self.reactive_power = Some(var);
535 }
536
537 #[must_use]
539 pub fn power_factor(&self) -> Option<f32> {
540 self.power_factor
541 }
542
543 pub fn set_power_factor(&mut self, factor: f32) {
545 self.power_factor = Some(factor);
546 }
547
548 #[must_use]
550 pub fn energy_today(&self) -> Option<f32> {
551 self.energy_today
552 }
553
554 pub fn set_energy_today(&mut self, kwh: f32) {
556 self.energy_today = Some(kwh);
557 }
558
559 #[must_use]
561 pub fn energy_yesterday(&self) -> Option<f32> {
562 self.energy_yesterday
563 }
564
565 pub fn set_energy_yesterday(&mut self, kwh: f32) {
567 self.energy_yesterday = Some(kwh);
568 }
569
570 #[must_use]
576 pub fn total_start_time(&self) -> Option<&TasmotaDateTime> {
577 self.total_start_time.as_ref()
578 }
579
580 pub fn set_total_start_time(&mut self, time: TasmotaDateTime) {
582 self.total_start_time = Some(time);
583 }
584
585 #[must_use]
607 pub fn system_info(&self) -> Option<&SystemInfo> {
608 self.system_info.as_ref()
609 }
610
611 pub fn set_system_info(&mut self, info: SystemInfo) {
613 self.system_info = Some(info);
614 }
615
616 pub fn update_system_info(&mut self, info: &SystemInfo) {
620 if let Some(existing) = &mut self.system_info {
621 existing.merge(info);
622 } else {
623 self.system_info = Some(info.clone());
624 }
625 }
626
627 #[must_use]
645 pub fn uptime(&self) -> Option<Duration> {
646 self.system_info.as_ref().and_then(SystemInfo::uptime)
647 }
648
649 #[allow(clippy::too_many_lines)]
658 pub fn apply(&mut self, change: &StateChange) -> bool {
661 match change {
662 StateChange::Power { index, state } => {
663 let current = self.power(*index);
664 if current == Some(*state) {
665 false
666 } else {
667 self.set_power(*index, *state);
668 true
669 }
670 }
671 StateChange::Dimmer(value) => {
672 if self.dimmer == Some(*value) {
673 false
674 } else {
675 self.dimmer = Some(*value);
676 true
677 }
678 }
679 StateChange::HsbColor(color) => {
680 if self.hsb_color == Some(*color) {
681 false
682 } else {
683 self.hsb_color = Some(*color);
684 true
685 }
686 }
687 StateChange::ColorTemperature(ct) => {
688 if self.color_temperature == Some(*ct) {
689 false
690 } else {
691 self.color_temperature = Some(*ct);
692 true
693 }
694 }
695 StateChange::Scheme(scheme) => {
696 if self.scheme == Some(*scheme) {
697 false
698 } else {
699 self.scheme = Some(*scheme);
700 true
701 }
702 }
703 StateChange::WakeupDuration(duration) => {
704 if self.wakeup_duration == Some(*duration) {
705 false
706 } else {
707 self.wakeup_duration = Some(*duration);
708 true
709 }
710 }
711 StateChange::FadeEnabled(enabled) => {
712 if self.fade_enabled == Some(*enabled) {
713 false
714 } else {
715 self.fade_enabled = Some(*enabled);
716 true
717 }
718 }
719 StateChange::FadeDuration(duration) => {
720 if self.fade_duration == Some(*duration) {
721 false
722 } else {
723 self.fade_duration = Some(*duration);
724 true
725 }
726 }
727 StateChange::Energy {
728 power,
729 voltage,
730 current,
731 apparent_power,
732 reactive_power,
733 power_factor,
734 energy_today,
735 energy_yesterday,
736 energy_total,
737 total_start_time,
738 } => {
739 let mut changed = false;
740
741 macro_rules! update_if_some {
743 ($field:ident, $value:expr) => {
744 if let Some(v) = $value {
745 if self.$field != Some(*v) {
746 self.$field = Some(*v);
747 changed = true;
748 }
749 }
750 };
751 }
752
753 update_if_some!(power_consumption, power);
754 update_if_some!(voltage, voltage);
755 update_if_some!(current, current);
756 update_if_some!(apparent_power, apparent_power);
757 update_if_some!(reactive_power, reactive_power);
758 update_if_some!(power_factor, power_factor);
759 update_if_some!(energy_today, energy_today);
760 update_if_some!(energy_yesterday, energy_yesterday);
761 update_if_some!(energy_total, energy_total);
762
763 if let Some(time) = total_start_time
765 && self.total_start_time.as_ref() != Some(time)
766 {
767 self.total_start_time = Some(time.clone());
768 changed = true;
769 }
770
771 changed
772 }
773 StateChange::Batch(changes) => {
774 let mut any_changed = false;
775 for c in changes {
776 if self.apply(c) {
777 any_changed = true;
778 }
779 }
780 any_changed
781 }
782 }
783 }
784
785 pub fn clear(&mut self) {
787 *self = Self::new();
788 }
789}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794
795 #[test]
796 fn new_state_is_empty() {
797 let state = DeviceState::new();
798 assert!(state.power(1).is_none());
799 assert!(state.dimmer().is_none());
800 assert!(state.hsb_color().is_none());
801 assert!(state.color_temperature().is_none());
802 assert!(state.power_consumption().is_none());
803 }
804
805 #[test]
806 fn power_state_management() {
807 let mut state = DeviceState::new();
808
809 state.set_power(1, PowerState::On);
810 assert_eq!(state.power(1), Some(PowerState::On));
811 assert!(state.power(2).is_none());
812
813 state.set_power(2, PowerState::Off);
814 assert_eq!(state.power(2), Some(PowerState::Off));
815
816 state.clear_power(1);
817 assert!(state.power(1).is_none());
818 }
819
820 #[test]
821 fn power_index_bounds() {
822 let mut state = DeviceState::new();
823
824 state.set_power(0, PowerState::On);
826 assert!(state.power(0).is_none());
827
828 state.set_power(9, PowerState::On);
830 assert!(state.power(9).is_none());
831
832 state.set_power(8, PowerState::On);
834 assert_eq!(state.power(8), Some(PowerState::On));
835 }
836
837 #[test]
838 fn all_power_states() {
839 let mut state = DeviceState::new();
840 state.set_power(1, PowerState::On);
841 state.set_power(3, PowerState::Off);
842 state.set_power(5, PowerState::On);
843
844 let states = state.all_power_states();
845 assert_eq!(states.len(), 3);
846 assert!(states.contains(&(1, PowerState::On)));
847 assert!(states.contains(&(3, PowerState::Off)));
848 assert!(states.contains(&(5, PowerState::On)));
849 }
850
851 #[test]
852 fn is_any_on() {
853 let mut state = DeviceState::new();
854 assert!(!state.is_any_on());
855
856 state.set_power(1, PowerState::Off);
857 assert!(!state.is_any_on());
858
859 state.set_power(2, PowerState::On);
860 assert!(state.is_any_on());
861 }
862
863 #[test]
864 fn apply_power_change() {
865 let mut state = DeviceState::new();
866
867 let change = StateChange::Power {
868 index: 1,
869 state: PowerState::On,
870 };
871 assert!(state.apply(&change));
872 assert_eq!(state.power(1), Some(PowerState::On));
873
874 assert!(!state.apply(&change));
876 }
877
878 #[test]
879 fn apply_dimmer_change() {
880 let mut state = DeviceState::new();
881 let dimmer = Dimmer::new(75).unwrap();
882
883 let change = StateChange::Dimmer(dimmer);
884 assert!(state.apply(&change));
885 assert_eq!(state.dimmer(), Some(dimmer));
886 }
887
888 #[test]
889 fn apply_batch_changes() {
890 let mut state = DeviceState::new();
891
892 let changes = StateChange::Batch(vec![
893 StateChange::Power {
894 index: 1,
895 state: PowerState::On,
896 },
897 StateChange::Dimmer(Dimmer::new(50).unwrap()),
898 ]);
899
900 assert!(state.apply(&changes));
901 assert_eq!(state.power(1), Some(PowerState::On));
902 assert_eq!(state.dimmer(), Some(Dimmer::new(50).unwrap()));
903 }
904
905 #[test]
906 fn clear_resets_state() {
907 let mut state = DeviceState::new();
908 state.set_power(1, PowerState::On);
909 state.set_dimmer(Dimmer::new(75).unwrap());
910
911 state.clear();
912
913 assert!(state.power(1).is_none());
914 assert!(state.dimmer().is_none());
915 }
916
917 #[test]
918 fn apply_batch_with_hsb_color() {
919 use crate::types::HsbColor;
920
921 let mut state = DeviceState::new();
922 let hsb = HsbColor::new(360, 100, 100).unwrap();
923
924 let changes = StateChange::Batch(vec![
925 StateChange::Power {
926 index: 1,
927 state: PowerState::Off,
928 },
929 StateChange::Dimmer(Dimmer::new(100).unwrap()),
930 StateChange::HsbColor(hsb),
931 ]);
932
933 assert!(state.apply(&changes));
934 assert_eq!(state.power(1), Some(PowerState::Off));
935 assert_eq!(state.dimmer(), Some(Dimmer::new(100).unwrap()));
936
937 let applied_hsb = state.hsb_color().expect("HsbColor should be set");
939 assert_eq!(applied_hsb.hue(), 360);
940 assert_eq!(applied_hsb.saturation(), 100);
941 assert_eq!(applied_hsb.brightness(), 100);
942 }
943
944 #[test]
945 fn apply_state_from_tasmota_telemetry() {
946 use crate::telemetry::TelemetryState;
947
948 let json = r#"{
950 "Time":"2025-12-24T14:24:03",
951 "Uptime":"1T23:46:58",
952 "UptimeSec":172018,
953 "Heap":25,
954 "SleepMode":"Dynamic",
955 "Sleep":50,
956 "LoadAvg":19,
957 "MqttCount":1,
958 "POWER":"OFF",
959 "Dimmer":100,
960 "Color":"FF00000000",
961 "HSBColor":"360,100,100",
962 "White":0,
963 "CT":153,
964 "Channel":[100,0,0,0,0],
965 "Scheme":0,
966 "Fade":"ON",
967 "Speed":2,
968 "LedTable":"ON",
969 "Wifi":{"AP":1}
970 }"#;
971
972 let telemetry: TelemetryState = serde_json::from_str(json).unwrap();
974 let changes = telemetry.to_state_changes();
975
976 let mut state = DeviceState::new();
978 for change in changes {
979 state.apply(&change);
980 }
981
982 assert_eq!(state.power(1), Some(PowerState::Off));
984 assert_eq!(state.dimmer(), Some(Dimmer::new(100).unwrap()));
985
986 let hsb = state
988 .hsb_color()
989 .expect("HSBColor should be set from telemetry");
990 assert_eq!(hsb.hue(), 360);
991 assert_eq!(hsb.saturation(), 100);
992 assert_eq!(hsb.brightness(), 100);
993
994 assert!(state.color_temperature().is_some());
996 assert_eq!(state.color_temperature().unwrap().value(), 153);
997
998 assert_eq!(state.fade_enabled(), Some(true));
1000
1001 assert_eq!(state.fade_duration().map(|s| s.value()), Some(2));
1003 }
1004
1005 #[test]
1006 fn fade_getters_setters() {
1007 let mut state = DeviceState::new();
1008
1009 assert!(state.fade_enabled().is_none());
1011 assert!(state.fade_duration().is_none());
1012
1013 state.set_fade_enabled(true);
1015 assert_eq!(state.fade_enabled(), Some(true));
1016
1017 state.set_fade_enabled(false);
1018 assert_eq!(state.fade_enabled(), Some(false));
1019
1020 let duration = FadeDuration::from_raw(15).unwrap();
1022 state.set_fade_duration(duration);
1023 assert_eq!(state.fade_duration(), Some(duration));
1024
1025 state.clear_fade_enabled();
1027 state.clear_fade_duration();
1028 assert!(state.fade_enabled().is_none());
1029 assert!(state.fade_duration().is_none());
1030 }
1031
1032 #[test]
1033 fn apply_fade_changes() {
1034 let mut state = DeviceState::new();
1035
1036 let change = StateChange::FadeEnabled(true);
1038 assert!(state.apply(&change));
1039 assert_eq!(state.fade_enabled(), Some(true));
1040
1041 assert!(!state.apply(&change));
1043
1044 let duration = FadeDuration::from_raw(20).unwrap();
1046 let change = StateChange::FadeDuration(duration);
1047 assert!(state.apply(&change));
1048 assert_eq!(state.fade_duration(), Some(duration));
1049 }
1050
1051 #[test]
1054 fn system_info_new_is_empty() {
1055 let info = SystemInfo::new();
1056 assert!(info.is_empty());
1057 assert!(info.uptime().is_none());
1058 assert!(info.wifi_rssi().is_none());
1059 assert!(info.heap().is_none());
1060 }
1061
1062 #[test]
1063 fn system_info_builder_pattern() {
1064 let info = SystemInfo::new()
1065 .with_uptime(Duration::from_secs(172800))
1066 .with_wifi_rssi(-55)
1067 .with_heap(25000);
1068
1069 assert!(!info.is_empty());
1070 assert_eq!(info.uptime(), Some(Duration::from_secs(172800)));
1071 assert_eq!(info.wifi_rssi(), Some(-55));
1072 assert_eq!(info.heap(), Some(25000));
1073 }
1074
1075 #[test]
1076 fn system_info_merge_preserves_existing() {
1077 let mut info = SystemInfo::new()
1078 .with_uptime(Duration::from_secs(100))
1079 .with_wifi_rssi(-50);
1080
1081 let update = SystemInfo::new().with_heap(30000);
1083 info.merge(&update);
1084
1085 assert_eq!(info.uptime(), Some(Duration::from_secs(100)));
1087 assert_eq!(info.wifi_rssi(), Some(-50));
1088 assert_eq!(info.heap(), Some(30000));
1089 }
1090
1091 #[test]
1092 fn system_info_merge_updates_values() {
1093 let mut info = SystemInfo::new()
1094 .with_uptime(Duration::from_secs(100))
1095 .with_wifi_rssi(-50);
1096
1097 let update = SystemInfo::new()
1099 .with_uptime(Duration::from_secs(200))
1100 .with_heap(30000);
1101 info.merge(&update);
1102
1103 assert_eq!(info.uptime(), Some(Duration::from_secs(200)));
1105 assert_eq!(info.wifi_rssi(), Some(-50)); assert_eq!(info.heap(), Some(30000));
1107 }
1108
1109 #[test]
1110 fn device_state_system_info_getters_setters() {
1111 let mut state = DeviceState::new();
1112
1113 assert!(state.system_info().is_none());
1115 assert!(state.uptime().is_none());
1116
1117 let info = SystemInfo::new().with_uptime(Duration::from_secs(172800));
1119 state.set_system_info(info);
1120
1121 assert!(state.system_info().is_some());
1122 assert_eq!(state.uptime(), Some(Duration::from_secs(172800)));
1123 }
1124
1125 #[test]
1126 fn device_state_update_system_info() {
1127 let mut state = DeviceState::new();
1128
1129 let info1 = SystemInfo::new().with_uptime(Duration::from_secs(100));
1131 state.update_system_info(&info1);
1132 assert_eq!(state.uptime(), Some(Duration::from_secs(100)));
1133
1134 let info2 = SystemInfo::new().with_wifi_rssi(-55);
1136 state.update_system_info(&info2);
1137
1138 let sys_info = state.system_info().unwrap();
1139 assert_eq!(sys_info.uptime(), Some(Duration::from_secs(100))); assert_eq!(sys_info.wifi_rssi(), Some(-55)); }
1142
1143 #[test]
1144 fn device_state_clear_clears_system_info() {
1145 let mut state = DeviceState::new();
1146 state.set_system_info(SystemInfo::new().with_uptime(Duration::from_secs(172800)));
1147
1148 state.clear();
1149
1150 assert!(state.system_info().is_none());
1151 }
1152
1153 #[test]
1154 fn system_info_serialization() {
1155 let info = SystemInfo::new()
1156 .with_uptime(Duration::from_secs(172800))
1157 .with_wifi_rssi(-55)
1158 .with_heap(25000);
1159
1160 let json = serde_json::to_string(&info).unwrap();
1161 let deserialized: SystemInfo = serde_json::from_str(&json).unwrap();
1162
1163 assert_eq!(info, deserialized);
1164 }
1165
1166 #[test]
1167 fn device_state_with_system_info_serialization() {
1168 let mut state = DeviceState::new();
1169 state.set_power(1, PowerState::On);
1170 state.set_system_info(
1171 SystemInfo::new()
1172 .with_uptime(Duration::from_secs(172800))
1173 .with_wifi_rssi(-55),
1174 );
1175
1176 let json = serde_json::to_string(&state).unwrap();
1177 let deserialized: DeviceState = serde_json::from_str(&json).unwrap();
1178
1179 assert_eq!(state, deserialized);
1180 assert_eq!(deserialized.uptime(), Some(Duration::from_secs(172800)));
1181 }
1182}