xy_modbus/types/model.rs
1//! Hardware variant presets and per-model register scales.
2
3/// Hardware variant. Selected at construction (`Xy::new`) and used to
4/// scale the registers whose resolution differs across the family —
5/// I-SET, IOUT, S-OCP, POWER, S-OPP. See `DATASHEET.md` §3 for the
6/// scale table.
7///
8/// Cross-check by reading `MODEL` (`0x0016`): `0x6500` is XY7025
9/// (newer firmware revision observed in 2024+ batches; older vendor
10/// docs cite `0x6100` for the same protocol). The crate does not
11/// probe automatically — pick the variant that matches your hardware.
12#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "defmt", derive(defmt::Format))]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub enum Model {
16 Xy7025,
17 /// Escape hatch for hardware not covered by the preset variants.
18 /// Each scale is the integer denominator the firmware uses on the
19 /// wire — e.g. `current_scale = 100` means a raw register value of
20 /// `1234` represents `12.34 A`. The XY firmware uses integer
21 /// denominators on every known variant; cross-check against the
22 /// vendor docs for your unit.
23 Custom {
24 current_scale: u16,
25 power_scale: u16,
26 opp_scale: u16,
27 },
28}
29
30impl Model {
31 /// Scale for I-SET, IOUT, S-OCP. 100 on XY7025 (10 mA).
32 pub const fn current_scale(self) -> f32 {
33 match self {
34 Self::Xy7025 => 100.0,
35 Self::Custom { current_scale, .. } => current_scale as f32,
36 }
37 }
38
39 /// Scale for POWER (`0x0004`). 10 on XY7025 (100 mW).
40 pub const fn power_scale(self) -> f32 {
41 match self {
42 Self::Xy7025 => 10.0,
43 Self::Custom { power_scale, .. } => power_scale as f32,
44 }
45 }
46
47 /// Scale for S-OPP in memory groups (`0x0055`). 1 W on XY7025.
48 pub const fn opp_scale(self) -> f32 {
49 match self {
50 Self::Xy7025 => 1.0,
51 Self::Custom { opp_scale, .. } => opp_scale as f32,
52 }
53 }
54
55 /// Expected value of the device's `MODEL` register (`0x0016`) for
56 /// this variant, if known. Used by [`crate::Xy::verify_model`] to
57 /// catch wrong-scale-family misconfiguration.
58 ///
59 /// XY7025 returns `0x6500`. `Custom` returns `None` (no canonical code).
60 pub const fn expected_model_code(self) -> Option<u16> {
61 match self {
62 Self::Xy7025 => Some(0x6500),
63 Self::Custom { .. } => None,
64 }
65 }
66}
67
68// ─── ModelCheck ──────────────────────────────────────────────────────────────
69
70/// Outcome of [`crate::Xy::verify_model`]. `Mismatch` is the dangerous
71/// case — readings WILL be off by 10× until the configured [`Model`] is
72/// changed to match the hardware.
73#[derive(Copy, Clone, Debug, PartialEq, Eq)]
74#[cfg_attr(feature = "defmt", derive(defmt::Format))]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub enum ModelCheck {
77 /// Device's `MODEL` register matches the configured model's family.
78 Match { device_code: u16 },
79 /// Device reports a code mapped to a different scale family. The
80 /// configured [`Model`] is wrong for this hardware; readings will
81 /// be off until it's corrected.
82 Mismatch {
83 expected_code: u16,
84 device_code: u16,
85 },
86 /// Verification was not possible: either the device returned a
87 /// code outside the documented set, or the configured model is
88 /// `Custom` (no canonical expected code).
89 Inconclusive { device_code: u16 },
90}