Skip to main content

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}