use crate::config::{Gain, IntegrationTime, MeasurementConfig};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PowerSavingMode {
Mode1,
Mode2,
Mode3,
Mode4,
}
impl PowerSavingMode {
pub(crate) const fn bits(self) -> u16 {
match self {
Self::Mode1 => 0b00 << 1,
Self::Mode2 => 0b01 << 1,
Self::Mode3 => 0b10 << 1,
Self::Mode4 => 0b11 << 1,
}
}
pub(crate) const fn from_word(word: u16) -> Self {
match (word >> 1) & 0b11 {
0 => Self::Mode1,
1 => Self::Mode2,
2 => Self::Mode3,
_ => Self::Mode4,
}
}
pub const fn nominal_refresh_time_ms(self, measurement: MeasurementConfig) -> Option<u32> {
if !matches!(measurement.gain(), Gain::X2) {
return None;
}
let base = match measurement.integration_time() {
IntegrationTime::Ms100 => 100,
IntegrationTime::Ms200 => 200,
IntegrationTime::Ms400 => 400,
IntegrationTime::Ms800 => 800,
IntegrationTime::Ms25 | IntegrationTime::Ms50 => return None,
};
let sleep = match self {
Self::Mode1 => 500,
Self::Mode2 => 1000,
Self::Mode3 => 2000,
Self::Mode4 => 4000,
};
Some(base + sleep)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PowerSavingConfig {
pub enabled: bool,
pub mode: PowerSavingMode,
}
impl PowerSavingConfig {
pub const fn new(enabled: bool, mode: PowerSavingMode) -> Self {
Self { enabled, mode }
}
pub const fn disabled() -> Self {
Self::new(false, PowerSavingMode::Mode1)
}
pub(crate) const fn encode(self) -> u16 {
self.mode.bits() | self.enabled as u16
}
}
impl Default for PowerSavingConfig {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PowerSavingSnapshot {
pub enabled: bool,
pub mode: PowerSavingMode,
}
impl PowerSavingSnapshot {
pub(crate) const fn as_config(self) -> PowerSavingConfig {
PowerSavingConfig::new(self.enabled, self.mode)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum PowerSavingDecodeError {
ReservedBits {
observed: u16,
},
}
pub(crate) const fn decode_power_saving(
word: u16,
) -> Result<PowerSavingSnapshot, PowerSavingDecodeError> {
let reserved = word & !0b111;
if reserved != 0 {
return Err(PowerSavingDecodeError::ReservedBits { observed: reserved });
}
Ok(PowerSavingSnapshot {
enabled: word & 1 != 0,
mode: PowerSavingMode::from_word(word),
})
}
impl core::fmt::Display for PowerSavingDecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ReservedBits { observed } => {
write!(f, "reserved power-saving bits were set: {observed:#06x}")
}
}
}
}
impl core::error::Error for PowerSavingDecodeError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn power_saving_fields_occupy_the_contract_bit_positions() {
for (mode, bits) in [
(PowerSavingMode::Mode1, 0b00_u16),
(PowerSavingMode::Mode2, 0b01),
(PowerSavingMode::Mode3, 0b10),
(PowerSavingMode::Mode4, 0b11),
] {
assert_eq!(
PowerSavingConfig::new(false, mode).encode(),
bits << 1,
"{mode:?} must occupy bits 2:1"
);
}
assert_eq!(
PowerSavingConfig::new(true, PowerSavingMode::Mode1).encode(),
0b001
);
assert_eq!(
PowerSavingConfig::new(true, PowerSavingMode::Mode4).encode(),
0b111
);
assert_eq!(
decode_power_saving(0x0000).unwrap().as_config(),
PowerSavingConfig::new(false, PowerSavingMode::Mode1)
);
}
#[test]
fn every_power_saving_configuration_round_trips() {
for mode in [
PowerSavingMode::Mode1,
PowerSavingMode::Mode2,
PowerSavingMode::Mode3,
PowerSavingMode::Mode4,
] {
for enabled in [false, true] {
let config = PowerSavingConfig::new(enabled, mode);
let decoded = decode_power_saving(config.encode()).unwrap();
assert_eq!(decoded.as_config(), config);
}
}
}
#[test]
fn every_reserved_power_saving_bit_is_rejected() {
for bit in 3_u32..16 {
let observed = 1_u16 << bit;
assert_eq!(
decode_power_saving(observed),
Err(PowerSavingDecodeError::ReservedBits { observed })
);
}
}
#[test]
fn documented_refresh_table_is_exact() {
let integrations = [
IntegrationTime::Ms100,
IntegrationTime::Ms200,
IntegrationTime::Ms400,
IntegrationTime::Ms800,
];
let rows = [
(PowerSavingMode::Mode1, [600, 700, 900, 1_300]),
(PowerSavingMode::Mode2, [1_100, 1_200, 1_400, 1_800]),
(PowerSavingMode::Mode3, [2_100, 2_200, 2_400, 2_800]),
(PowerSavingMode::Mode4, [4_100, 4_200, 4_400, 4_800]),
];
for (mode, expected) in rows {
for (integration, expected_ms) in integrations.into_iter().zip(expected) {
assert_eq!(
mode.nominal_refresh_time_ms(MeasurementConfig::new(Gain::X2, integration)),
Some(expected_ms)
);
}
assert_eq!(
mode.nominal_refresh_time_ms(MeasurementConfig::new(
Gain::X2,
IntegrationTime::Ms25
)),
None
);
assert_eq!(
mode.nominal_refresh_time_ms(MeasurementConfig::new(
Gain::X2,
IntegrationTime::Ms50
)),
None
);
for gain in [Gain::X1, Gain::Div4, Gain::Div8] {
assert_eq!(
mode.nominal_refresh_time_ms(MeasurementConfig::new(
gain,
IntegrationTime::Ms100
)),
None
);
}
}
}
}