use crate::config::{ConfigDecodeError, IntegrationTime};
use crate::measurement::MeasurementCapture;
use crate::power::PowerSavingDecodeError;
use crate::threshold::ThresholdStatusDecodeError;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Operation {
Inspect,
Snapshot,
MeasureOnce,
Configure,
ThresholdMonitor,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum BusContext {
ReadConfiguration,
WriteConfiguration,
ReadPowerSaving,
WritePowerSaving,
ReadAls,
ReadWhite,
ReadDeviceId,
ReadThresholdStatus,
ReadLowThreshold,
ReadHighThreshold,
WriteLowThreshold,
WriteHighThreshold,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum ConfigurationError {
ConfigurationDecode(ConfigDecodeError),
PowerSavingDecode(PowerSavingDecodeError),
ThresholdStatusDecode(ThresholdStatusDecodeError),
ReversedThresholds,
ThresholdMonitorOwnsDomain,
TimingIntegrationMismatch {
measurement: IntegrationTime,
timing: IntegrationTime,
},
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error<E> {
Bus {
operation: Operation,
context: BusContext,
source: E,
},
Configuration(ConfigurationError),
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum ProbeError<E> {
NotPresent,
Bus(E),
WrongDevice {
observed: u16,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum MeasureStage {
ValidateTiming,
ObserveConfiguration,
ObservePowerSaving,
EnterShutdown,
DisablePowerSaving,
PrepareMeasurement,
ActivateMeasurement,
FreezeResult,
ReadAls,
ReadWhite,
RestoreConfiguration,
RestorePowerSaving,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum MeasureOnceError<E> {
Operation {
stage: MeasureStage,
source: Error<E>,
},
RecoveryFailed {
failed_stage: MeasureStage,
source: Error<E>,
recovery_stage: MeasureStage,
recovery_source: Error<E>,
},
RestoreFailed {
sample: MeasurementCapture,
stage: MeasureStage,
source: Error<E>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum ThresholdMonitorStage {
ObserveConfiguration,
EnterShutdown,
DisableMonitor,
WriteLowThreshold,
WriteHighThreshold,
ApplyPowerSaving,
EnableMonitor,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct ThresholdMonitorError<E> {
pub stage: ThresholdMonitorStage,
pub confirmed: Option<ThresholdMonitorStage>,
pub source: Error<E>,
}
impl core::fmt::Display for Operation {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::Inspect => "inspection",
Self::Snapshot => "snapshot",
Self::MeasureOnce => "one-shot measurement",
Self::Configure => "configuration change",
Self::ThresholdMonitor => "threshold-monitor programming",
})
}
}
impl core::fmt::Display for BusContext {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::ReadConfiguration => "a configuration read",
Self::WriteConfiguration => "a configuration write",
Self::ReadPowerSaving => "a power-saving read",
Self::WritePowerSaving => "a power-saving write",
Self::ReadAls => "an ALS read",
Self::ReadWhite => "a white-channel read",
Self::ReadDeviceId => "a device-ID read",
Self::ReadThresholdStatus => "a threshold-status read",
Self::ReadLowThreshold => "a low-threshold read",
Self::ReadHighThreshold => "a high-threshold read",
Self::WriteLowThreshold => "a low-threshold write",
Self::WriteHighThreshold => "a high-threshold write",
})
}
}
impl core::fmt::Display for MeasureStage {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::ValidateTiming => "timing validation",
Self::ObserveConfiguration => "observing configuration",
Self::ObservePowerSaving => "observing power saving",
Self::EnterShutdown => "entering shutdown",
Self::DisablePowerSaving => "disabling power saving",
Self::PrepareMeasurement => "installing the measurement domain",
Self::ActivateMeasurement => "activating",
Self::FreezeResult => "freezing the result",
Self::ReadAls => "reading ALS",
Self::ReadWhite => "reading white",
Self::RestoreConfiguration => "restoring configuration",
Self::RestorePowerSaving => "restoring power saving",
})
}
}
impl core::fmt::Display for ThresholdMonitorStage {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::ObserveConfiguration => "observing configuration",
Self::EnterShutdown => "entering shutdown",
Self::DisableMonitor => "disabling the monitor",
Self::WriteLowThreshold => "writing the low threshold",
Self::WriteHighThreshold => "writing the high threshold",
Self::ApplyPowerSaving => "applying power saving",
Self::EnableMonitor => "enabling the monitor",
})
}
}
impl core::fmt::Display for ConfigurationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ConfigurationDecode(_) => f.write_str("configuration register did not decode"),
Self::PowerSavingDecode(_) => f.write_str("power-saving register did not decode"),
Self::ThresholdStatusDecode(_) => {
f.write_str("threshold-status register did not decode")
}
Self::ReversedThresholds => f.write_str("thresholds were reversed"),
Self::ThresholdMonitorOwnsDomain => {
f.write_str("an enabled threshold monitor owns this domain")
}
Self::TimingIntegrationMismatch {
measurement,
timing,
} => write!(
f,
"timing was derived for {} ms but the measurement selects {} ms",
timing.milliseconds(),
measurement.milliseconds()
),
}
}
}
impl core::error::Error for ConfigurationError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::ConfigurationDecode(source) => Some(source),
Self::PowerSavingDecode(source) => Some(source),
Self::ThresholdStatusDecode(source) => Some(source),
_ => None,
}
}
}
impl<E> core::fmt::Display for Error<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Bus {
operation, context, ..
} => write!(f, "{operation} failed during {context}"),
Self::Configuration(_) => f.write_str("configuration was rejected"),
}
}
}
impl<E: core::error::Error + 'static> core::error::Error for Error<E> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Bus { source, .. } => Some(source),
Self::Configuration(source) => Some(source),
}
}
}
impl<E> core::fmt::Display for ProbeError<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NotPresent => f.write_str("no device acknowledged the fixed address"),
Self::Bus(_) => f.write_str("probe transaction failed"),
Self::WrongDevice { observed } => {
write!(f, "identity {observed:#06x} is not a supported VEML7700")
}
}
}
}
impl<E: core::error::Error + 'static> core::error::Error for ProbeError<E> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Bus(source) => Some(source),
_ => None,
}
}
}
impl<E> core::fmt::Display for MeasureOnceError<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Operation { stage, .. } => write!(f, "one-shot measurement failed at {stage}"),
Self::RecoveryFailed {
failed_stage,
recovery_stage,
..
} => write!(
f,
"one-shot measurement failed at {failed_stage} and restoration failed at \
{recovery_stage}; device state is uncertain"
),
Self::RestoreFailed { stage, .. } => write!(
f,
"a sample was captured but restoration failed at {stage}; device state is uncertain"
),
}
}
}
impl<E: core::error::Error + 'static> core::error::Error for MeasureOnceError<E> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Operation { source, .. }
| Self::RecoveryFailed { source, .. }
| Self::RestoreFailed { source, .. } => Some(source),
}
}
}
impl<E> core::fmt::Display for ThresholdMonitorError<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.confirmed {
Some(confirmed) => write!(
f,
"threshold programming failed at {}; {} was the last confirmed write",
self.stage, confirmed
),
None => write!(
f,
"threshold programming failed at {}; no write was confirmed",
self.stage
),
}
}
}
impl<E: core::error::Error + 'static> core::error::Error for ThresholdMonitorError<E> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.source)
}
}
#[cfg(test)]
mod standard_error_tests {
use super::*;
use crate::config::{Gain, IntegrationTime, MeasurementConfig};
use crate::measurement::{AlsCounts, MeasurementPairCoherence, WhiteCounts};
use core::error::Error as _;
use core::fmt::Write as _;
#[derive(Debug, PartialEq, Eq)]
struct ReportableBusFault;
impl core::fmt::Display for ReportableBusFault {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("arbitration lost")
}
}
impl core::error::Error for ReportableBusFault {}
#[derive(Debug, PartialEq, Eq)]
struct BareBusFault;
struct Sink {
buffer: [u8; 256],
used: usize,
}
impl Sink {
const fn new() -> Self {
Self {
buffer: [0; 256],
used: 0,
}
}
fn as_str(&self) -> &str {
core::str::from_utf8(&self.buffer[..self.used]).expect("valid UTF-8")
}
}
impl core::fmt::Write for Sink {
fn write_str(&mut self, text: &str) -> core::fmt::Result {
let bytes = text.as_bytes();
let end = self.used + bytes.len();
if end > self.buffer.len() {
return Err(core::fmt::Error);
}
self.buffer[self.used..end].copy_from_slice(bytes);
self.used = end;
Ok(())
}
}
fn report(error: &dyn core::error::Error) -> Sink {
let mut sink = Sink::new();
write!(sink, "{error}").expect("fits");
let mut cause = error.source();
while let Some(next) = cause {
write!(sink, ": {next}").expect("fits");
cause = next.source();
}
sink
}
fn bus_failure<E>(source: E) -> Error<E> {
Error::Bus {
operation: Operation::MeasureOnce,
context: BusContext::WriteConfiguration,
source,
}
}
#[test]
fn a_reportable_bus_error_reaches_the_end_of_the_chain() {
let error = bus_failure(ReportableBusFault);
assert_eq!(
report(&error).as_str(),
"one-shot measurement failed during a configuration write: arbitration lost"
);
}
#[test]
fn a_bus_error_that_is_not_a_standard_error_still_works() {
let error = bus_failure(BareBusFault);
let mut sink = Sink::new();
write!(sink, "{error}").expect("fits");
assert_eq!(
sink.as_str(),
"one-shot measurement failed during a configuration write"
);
}
#[test]
fn a_configuration_failure_chains_to_its_decode_cause() {
let error: Error<ReportableBusFault> =
Error::Configuration(ConfigurationError::ConfigurationDecode(
ConfigDecodeError::ReservedBits { observed: 0x2000 },
));
assert_eq!(
report(&error).as_str(),
concat!(
"configuration was rejected: configuration register did not decode: ",
"reserved configuration bits were set: 0x2000"
)
);
}
#[test]
fn a_conclusion_this_driver_reached_has_no_cause() {
let absent: ProbeError<ReportableBusFault> = ProbeError::NotPresent;
assert!(absent.source().is_none());
assert_eq!(
report(&absent).as_str(),
"no device acknowledged the fixed address"
);
let mismatch: ProbeError<ReportableBusFault> = ProbeError::WrongDevice { observed: 0x1234 };
assert!(mismatch.source().is_none());
}
#[test]
fn a_nested_recovery_failure_reports_the_primary_cause() {
let error = MeasureOnceError::RecoveryFailed {
failed_stage: MeasureStage::ActivateMeasurement,
source: bus_failure(ReportableBusFault),
recovery_stage: MeasureStage::RestoreConfiguration,
recovery_source: bus_failure(ReportableBusFault),
};
assert_eq!(
report(&error).as_str(),
"one-shot measurement failed at activating and restoration failed at restoring \
configuration; device state is uncertain: one-shot measurement failed during a \
configuration write: arbitration lost"
);
let MeasureOnceError::RecoveryFailed {
recovery_source, ..
} = &error
else {
unreachable!()
};
assert!(matches!(recovery_source, Error::Bus { .. }));
}
#[test]
fn a_captured_sample_survives_a_reported_restoration_failure() {
let configuration = MeasurementConfig::new(Gain::Div8, IntegrationTime::Ms100);
let error = MeasureOnceError::RestoreFailed {
sample: MeasurementCapture {
als: AlsCounts::from_counts(0x1234),
white: WhiteCounts::from_counts(0x5678),
configuration,
nominal_illuminance: AlsCounts::from_counts(0x1234)
.nominal_micro_lux(configuration),
requested_wait_us: 133_500,
coherence: MeasurementPairCoherence::FrozenAfterRequestedWait,
},
stage: MeasureStage::RestorePowerSaving,
source: bus_failure(ReportableBusFault),
};
assert!(
report(&error).as_str().starts_with(
"a sample was captured but restoration failed at restoring power saving"
)
);
let MeasureOnceError::RestoreFailed { sample, .. } = &error else {
unreachable!()
};
assert_eq!(sample.als, AlsCounts::from_counts(0x1234));
}
#[test]
fn threshold_failures_report_confirmed_progress() {
let unconfirmed = ThresholdMonitorError {
stage: ThresholdMonitorStage::DisableMonitor,
confirmed: None,
source: bus_failure(ReportableBusFault),
};
assert_eq!(
report(&unconfirmed).as_str(),
"threshold programming failed at disabling the monitor; no write was confirmed: \
one-shot measurement failed during a configuration write: arbitration lost"
);
let partial = ThresholdMonitorError {
stage: ThresholdMonitorStage::ApplyPowerSaving,
confirmed: Some(ThresholdMonitorStage::WriteHighThreshold),
source: bus_failure(ReportableBusFault),
};
assert!(report(&partial).as_str().starts_with(
"threshold programming failed at applying power saving; writing the high threshold \
was the last confirmed write"
));
}
}