use crate::config::{Gain, IntegrationTime, MeasurementConfig};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MicroLux(u64);
impl MicroLux {
pub const fn from_micro_lux(value: u64) -> Self {
Self(value)
}
pub const fn as_micro_lux(self) -> u64 {
self.0
}
pub const fn whole_lux_floor(self) -> u64 {
self.0 / 1_000_000
}
pub const fn milli_lux_rounded(self) -> u64 {
self.0 / 1_000 + (self.0 % 1_000 + 500) / 1_000
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NominalScale {
micro_lux_per_count: u32,
}
impl NominalScale {
pub const fn for_config(config: MeasurementConfig) -> Self {
let base_gain2_800 = 4_200_u32;
let integration_multiplier = match config.integration_time() {
IntegrationTime::Ms800 => 1,
IntegrationTime::Ms400 => 2,
IntegrationTime::Ms200 => 4,
IntegrationTime::Ms100 => 8,
IntegrationTime::Ms50 => 16,
IntegrationTime::Ms25 => 32,
};
let gain_multiplier = match config.gain() {
Gain::X2 => 1,
Gain::X1 => 2,
Gain::Div4 => 8,
Gain::Div8 => 16,
};
Self {
micro_lux_per_count: base_gain2_800 * integration_multiplier * gain_multiplier,
}
}
pub const fn micro_lux_per_count(self) -> u32 {
self.micro_lux_per_count
}
pub const fn scale_counts(self, counts: u16) -> MicroLux {
MicroLux::from_micro_lux(counts as u64 * self.micro_lux_per_count as u64)
}
pub const fn full_scale_micro_lux(self) -> MicroLux {
self.scale_counts(u16::MAX)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_documented_gain_and_integration_pairs_match_the_nominal_table() {
let rows = [
(IntegrationTime::Ms800, [4_200, 8_400, 33_600, 67_200]),
(IntegrationTime::Ms400, [8_400, 16_800, 67_200, 134_400]),
(IntegrationTime::Ms200, [16_800, 33_600, 134_400, 268_800]),
(IntegrationTime::Ms100, [33_600, 67_200, 268_800, 537_600]),
(IntegrationTime::Ms50, [67_200, 134_400, 537_600, 1_075_200]),
(
IntegrationTime::Ms25,
[134_400, 268_800, 1_075_200, 2_150_400],
),
];
let gains = [Gain::X2, Gain::X1, Gain::Div4, Gain::Div8];
for (integration_time, expected) in rows {
for (gain, expected_micro_lux) in gains.into_iter().zip(expected) {
let scale =
NominalScale::for_config(MeasurementConfig::new(gain, integration_time));
assert_eq!(scale.micro_lux_per_count(), expected_micro_lux);
}
}
}
#[test]
fn milli_lux_rounds_to_nearest_and_survives_the_whole_input_range() {
for (micro_lux, expected_milli_lux) in [
(0_u64, 0_u64),
(499, 0),
(500, 1),
(1_499, 1),
(1_500, 2),
(u64::MAX, 18_446_744_073_709_552),
] {
assert_eq!(
MicroLux::from_micro_lux(micro_lux).milli_lux_rounded(),
expected_milli_lux
);
}
}
#[test]
fn maximum_nominal_range_fits_u64() {
let scale =
NominalScale::for_config(MeasurementConfig::new(Gain::Div8, IntegrationTime::Ms25));
assert_eq!(
scale.scale_counts(u16::MAX).as_micro_lux(),
140926464000_u64
);
}
}