xy_modbus/types/status.rs
1//! Live readings, setpoints, and cumulative counters.
2
3use super::enums::{ProtectionStatus, RegMode};
4
5// ─── Setpoints ───────────────────────────────────────────────────────────────
6
7/// Output voltage / current setpoints (registers 0x0000–0x0001).
8#[derive(Copy, Clone, Debug, PartialEq)]
9#[cfg_attr(feature = "defmt", derive(defmt::Format))]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct Setpoints {
12 pub v_set: f32,
13 pub i_set: f32,
14}
15
16// ─── Status ──────────────────────────────────────────────────────────────────
17
18/// Live + control snapshot covering registers 0x0000–0x0012 in a single
19/// 19-register transaction. Returns everything a supervisor needs each
20/// tick (live readings, regulation mode, latched protection cause,
21/// output-enable flag) in one Modbus round-trip.
22#[derive(Copy, Clone, Debug, PartialEq)]
23#[cfg_attr(feature = "defmt", derive(defmt::Format))]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Status {
26 pub v_set: f32,
27 pub i_set: f32,
28 pub v_out: f32,
29 pub i_out: f32,
30 pub p_out: f32,
31 pub v_in: f32,
32 /// `PROTECT` register (0x0010). Necessarily `Normal` while
33 /// [`Self::output_on`] is true.
34 pub protection: ProtectionStatus,
35 /// `CVCC` register (0x0011) — current regulation mode.
36 pub reg_mode: RegMode,
37 /// `OUTPUT_EN` register (0x0012).
38 pub output_on: bool,
39}
40
41// ─── OnTime ──────────────────────────────────────────────────────────────────
42
43/// Output-on time as reported by the device (h/m/s).
44#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub struct OnTime {
48 pub hours: u16,
49 pub minutes: u16,
50 pub seconds: u16,
51}
52
53impl OnTime {
54 pub const fn total_seconds(self) -> u32 {
55 self.hours as u32 * 3600 + self.minutes as u32 * 60 + self.seconds as u32
56 }
57}
58
59// ─── Totals ──────────────────────────────────────────────────────────────────
60
61/// Cumulative output counters and on-time (registers 0x0006–0x000C).
62///
63/// Charge and energy are composed from 32-bit low/high register pairs.
64/// The high words are flagged as untested in community docs — verify
65/// against your hardware before trusting them at high totals.
66#[derive(Copy, Clone, Debug, PartialEq)]
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
69pub struct Totals {
70 /// Cumulative output charge in Ah.
71 pub charge_ah: f32,
72 /// Cumulative output energy in Wh.
73 pub energy_wh: f32,
74 /// Output-on time, accumulated.
75 pub on_time: OnTime,
76}
77
78// ─── Temperatures ────────────────────────────────────────────────────────────
79
80/// Temperature readings from registers `0x000D` (T-IN) and `0x000E` (T-EX),
81/// in the unit selected by [`super::TempUnit`].
82///
83/// `internal` is the on-board sensor — verified on XY7025 hardware.
84///
85/// `_external_unverified` is the optional external probe input. With no
86/// thermistor connected the field reads `888.8` as a sentinel; the
87/// decoding scale for a *connected* probe has not been verified on real
88/// hardware. The leading underscore is a deliberate marker — treat the
89/// value as advisory until you've cross-checked it against a known
90/// reference temperature on your unit.
91#[derive(Copy, Clone, Debug, PartialEq)]
92#[cfg_attr(feature = "defmt", derive(defmt::Format))]
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94pub struct Temperatures {
95 pub internal: f32,
96 pub _external_unverified: f32,
97}
98
99// ─── SafetyLimits ────────────────────────────────────────────────────────────
100
101/// Hard trip limits programmed into the buck's protection registers.
102#[derive(Copy, Clone, Debug, PartialEq)]
103#[cfg_attr(feature = "defmt", derive(defmt::Format))]
104#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
105pub struct SafetyLimits {
106 pub lvp_v: f32,
107 pub ovp_v: f32,
108 pub ocp_a: f32,
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn on_time_total_seconds() {
117 assert_eq!(OnTime::default().total_seconds(), 0);
118 assert_eq!(
119 OnTime {
120 hours: 0,
121 minutes: 0,
122 seconds: 1,
123 }
124 .total_seconds(),
125 1
126 );
127 // 1h 23m 45s = 3600 + 1380 + 45.
128 assert_eq!(
129 OnTime {
130 hours: 1,
131 minutes: 23,
132 seconds: 45,
133 }
134 .total_seconds(),
135 5025
136 );
137 // No overflow with full u16 hours: 65535 * 3600 = 235_926_000 < u32::MAX.
138 assert_eq!(
139 OnTime {
140 hours: u16::MAX,
141 minutes: 0,
142 seconds: 0,
143 }
144 .total_seconds(),
145 65535u32 * 3600
146 );
147 }
148}