Skip to main content

ph_veml7700_als/
threshold.rs

1//! Threshold-monitor semantic types.
2
3use crate::config::{MeasurementConfig, Persistence};
4use crate::measurement::AlsCounts;
5use crate::power::PowerSavingConfig;
6
7/// Raw ALS low/high thresholds.
8///
9/// Fields are private so that `low <= high` cannot be bypassed by a struct
10/// literal. [`Thresholds::new`] is the only way to build this type, and the
11/// driver therefore cannot program a reversed pair that
12/// [`Veml7700::read_thresholds`](crate::Veml7700::read_thresholds) would reject
13/// when read back.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15#[cfg_attr(feature = "defmt", derive(defmt::Format))]
16pub struct Thresholds {
17    low: AlsCounts,
18    high: AlsCounts,
19}
20
21impl Thresholds {
22    /// Construct ordered thresholds, rejecting `low > high`.
23    pub const fn new(low: AlsCounts, high: AlsCounts) -> Option<Self> {
24        if low.counts() <= high.counts() {
25            Some(Self { low, high })
26        } else {
27            None
28        }
29    }
30
31    /// Return the low threshold in raw ALS counts.
32    pub const fn low(self) -> AlsCounts {
33        self.low
34    }
35
36    /// Return the high threshold in raw ALS counts.
37    pub const fn high(self) -> AlsCounts {
38        self.high
39    }
40}
41
42/// Raw decoded threshold-flag observation (`S-38`).
43///
44/// The driver performs no explicit read-to-clear, write-to-clear, arm-time, or
45/// disable-time clearing action and promises no flag history (`S-42`, `S-53`,
46/// `S-54`). A set or clear field reports only what that register read returned;
47/// it does not establish a reset point or a "since arming" interval. Reading and
48/// discarding one value does not make the next value fresh.
49///
50/// The driver also promises no flag-assertion time for any persistence setting
51/// (`S-39`, `S-49`, `S-50`).
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53#[cfg_attr(feature = "defmt", derive(defmt::Format))]
54pub struct ThresholdStatus {
55    /// Low threshold flag, register bit 15.
56    pub low: bool,
57    /// High threshold flag, register bit 14.
58    pub high: bool,
59}
60
61impl ThresholdStatus {
62    pub(crate) const fn decode(word: u16) -> Result<Self, ThresholdStatusDecodeError> {
63        let reserved = word & 0x3FFF;
64        if reserved != 0 {
65            return Err(ThresholdStatusDecodeError::ReservedBits { observed: reserved });
66        }
67        Ok(Self {
68            low: word & (1 << 15) != 0,
69            high: word & (1 << 14) != 0,
70        })
71    }
72}
73
74/// Failure decoding the threshold-status register.
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76#[cfg_attr(feature = "defmt", derive(defmt::Format))]
77#[non_exhaustive]
78pub enum ThresholdStatusDecodeError {
79    /// Reserved status bits were observed set.
80    ReservedBits {
81        /// Reserved bits that were observed set.
82        observed: u16,
83    },
84}
85
86/// Complete monitored domain for the VEML7700's polled threshold feature.
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88#[cfg_attr(feature = "defmt", derive(defmt::Format))]
89pub struct ThresholdMonitorConfig {
90    /// Gain and integration time that define threshold count meaning.
91    pub measurement: MeasurementConfig,
92    /// Raw low/high thresholds in that measurement domain.
93    pub thresholds: Thresholds,
94    /// Persistence protect number. See [`Persistence`] for the absence of an
95    /// assertion-timing promise at every value.
96    pub persistence: Persistence,
97    /// Cadence selection owned by the monitored domain.
98    ///
99    /// The driver programs this value but makes no wall-clock qualification
100    /// promise. Enabled cadence at 25 ms or 50 ms also has no documented refresh
101    /// time (`S-44`).
102    pub power_saving: PowerSavingConfig,
103}
104
105impl ThresholdMonitorConfig {
106    /// Construct a complete monitored domain.
107    ///
108    /// Construction validates no cross-field timing semantics. Programming is
109    /// supported even where refresh or threshold qualification remains
110    /// undefined; the result carries no assertion-time promise.
111    pub const fn new(
112        measurement: MeasurementConfig,
113        thresholds: Thresholds,
114        persistence: Persistence,
115        power_saving: PowerSavingConfig,
116    ) -> Self {
117        Self {
118            measurement,
119            thresholds,
120            persistence,
121            power_saving,
122        }
123    }
124}
125
126impl core::fmt::Display for ThresholdStatusDecodeError {
127    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
128        match self {
129            Self::ReservedBits { observed } => {
130                write!(
131                    f,
132                    "reserved threshold-status bits were set: {observed:#06x}"
133                )
134            }
135        }
136    }
137}
138
139impl core::error::Error for ThresholdStatusDecodeError {}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn every_reserved_status_bit_is_rejected() {
147        for bit in 0_u32..14 {
148            let observed = 1_u16 << bit;
149            assert_eq!(
150                ThresholdStatus::decode(observed),
151                Err(ThresholdStatusDecodeError::ReservedBits { observed })
152            );
153        }
154    }
155
156    #[test]
157    fn all_documented_status_flag_combinations_decode() {
158        for (word, low, high) in [
159            (0x0000, false, false),
160            (0x4000, false, true),
161            (0x8000, true, false),
162            (0xC000, true, true),
163        ] {
164            assert_eq!(
165                ThresholdStatus::decode(word),
166                Ok(ThresholdStatus { low, high })
167            );
168        }
169    }
170
171    #[test]
172    fn thresholds_accept_equal_endpoints_and_reject_reversal() {
173        let equal = AlsCounts::from_counts(42);
174        let ordered = Thresholds::new(equal, equal).expect("equal endpoints are ordered");
175        assert_eq!(ordered.low(), equal);
176        assert_eq!(ordered.high(), equal);
177
178        let ascending =
179            Thresholds::new(AlsCounts::from_counts(42), AlsCounts::from_counts(43)).unwrap();
180        assert_eq!(ascending.low().counts(), 42);
181        assert_eq!(ascending.high().counts(), 43);
182
183        assert_eq!(
184            Thresholds::new(AlsCounts::from_counts(43), AlsCounts::from_counts(42)),
185            None
186        );
187    }
188}