umsh_ux_tracker/
state.rs

1//! Persistent user-facing tracker preferences.
2//!
3//! The representation is deliberately tiny so board support can keep it in a
4//! retained register such as the nRF52840's `GPREGRET2` without flash wear.
5
6const MAGIC: u8 = 0xA0;
7const MAGIC_MASK: u8 = 0xF8;
8const ASLEEP: u8 = 1 << 0;
9const SILENT: u8 = 1 << 1;
10const BATTERY_CRITICAL: u8 = 1 << 2;
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
13pub struct UserPreferences {
14    pub asleep: bool,
15    pub silent: bool,
16    /// Protective shutdown reason, kept separate from user-requested Sleep.
17    pub battery_critical: bool,
18}
19
20impl UserPreferences {
21    pub const fn try_decode(value: u8) -> Option<Self> {
22        if value & MAGIC_MASK != MAGIC {
23            return None;
24        }
25        Some(Self {
26            asleep: value & ASLEEP != 0,
27            silent: value & SILENT != 0,
28            battery_critical: value & BATTERY_CRITICAL != 0,
29        })
30    }
31
32    pub const fn encode(self) -> u8 {
33        MAGIC
34            | (self.asleep as u8) * ASLEEP
35            | (self.silent as u8) * SILENT
36            | (self.battery_critical as u8) * BATTERY_CRITICAL
37    }
38
39    pub const fn decode(value: u8) -> Self {
40        match Self::try_decode(value) {
41            Some(preferences) => preferences,
42            None => Self {
43                asleep: false,
44                silent: false,
45                battery_critical: false,
46            },
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn every_preference_combination_round_trips() {
57        for asleep in [false, true] {
58            for silent in [false, true] {
59                for battery_critical in [false, true] {
60                    let expected = UserPreferences {
61                        asleep,
62                        silent,
63                        battery_critical,
64                    };
65                    assert_eq!(UserPreferences::decode(expected.encode()), expected);
66                }
67            }
68        }
69    }
70
71    #[test]
72    fn erased_or_foreign_register_defaults_awake_and_noisy() {
73        for value in [0x00, 0x57, 0xff] {
74            assert_eq!(UserPreferences::decode(value), UserPreferences::default());
75        }
76    }
77}