1use crate::config::{ConfigDecodeError, IntegrationTime};
4use crate::measurement::MeasurementCapture;
5use crate::power::PowerSavingDecodeError;
6use crate::threshold::ThresholdStatusDecodeError;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11#[non_exhaustive]
12pub enum Operation {
13 Inspect,
15 Snapshot,
17 MeasureOnce,
19 Configure,
21 ThresholdMonitor,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27#[cfg_attr(feature = "defmt", derive(defmt::Format))]
28#[non_exhaustive]
29pub enum BusContext {
30 ReadConfiguration,
32 WriteConfiguration,
34 ReadPowerSaving,
36 WritePowerSaving,
38 ReadAls,
40 ReadWhite,
42 ReadDeviceId,
44 ReadThresholdStatus,
46 ReadLowThreshold,
48 ReadHighThreshold,
50 WriteLowThreshold,
52 WriteHighThreshold,
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58#[cfg_attr(feature = "defmt", derive(defmt::Format))]
59#[non_exhaustive]
60pub enum ConfigurationError {
61 ConfigurationDecode(ConfigDecodeError),
63 PowerSavingDecode(PowerSavingDecodeError),
65 ThresholdStatusDecode(ThresholdStatusDecodeError),
67 ReversedThresholds,
69 ThresholdMonitorOwnsDomain,
71 TimingIntegrationMismatch {
73 measurement: IntegrationTime,
75 timing: IntegrationTime,
77 },
78}
79
80#[derive(Debug, PartialEq, Eq)]
123#[cfg_attr(feature = "defmt", derive(defmt::Format))]
124#[non_exhaustive]
125pub enum Error<E> {
126 Bus {
128 operation: Operation,
130 context: BusContext,
132 source: E,
134 },
135 Configuration(ConfigurationError),
137}
138
139#[derive(Debug, PartialEq, Eq)]
141#[cfg_attr(feature = "defmt", derive(defmt::Format))]
142#[non_exhaustive]
143pub enum ProbeError<E> {
144 NotPresent,
146 Bus(E),
148 WrongDevice {
150 observed: u16,
152 },
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq)]
157#[cfg_attr(feature = "defmt", derive(defmt::Format))]
158#[non_exhaustive]
159pub enum MeasureStage {
160 ValidateTiming,
162 ObserveConfiguration,
164 ObservePowerSaving,
166 EnterShutdown,
171 DisablePowerSaving,
173 PrepareMeasurement,
175 ActivateMeasurement,
177 FreezeResult,
179 ReadAls,
181 ReadWhite,
183 RestoreConfiguration,
185 RestorePowerSaving,
187}
188
189#[derive(Debug, PartialEq, Eq)]
191#[cfg_attr(feature = "defmt", derive(defmt::Format))]
192#[non_exhaustive]
193pub enum MeasureOnceError<E> {
194 Operation {
196 stage: MeasureStage,
198 source: Error<E>,
200 },
201 RecoveryFailed {
203 failed_stage: MeasureStage,
205 source: Error<E>,
207 recovery_stage: MeasureStage,
209 recovery_source: Error<E>,
211 },
212 RestoreFailed {
214 sample: MeasurementCapture,
216 stage: MeasureStage,
218 source: Error<E>,
220 },
221}
222
223#[derive(Clone, Copy, Debug, PartialEq, Eq)]
225#[cfg_attr(feature = "defmt", derive(defmt::Format))]
226#[non_exhaustive]
227pub enum ThresholdMonitorStage {
228 ObserveConfiguration,
230 EnterShutdown,
236 DisableMonitor,
238 WriteLowThreshold,
240 WriteHighThreshold,
242 ApplyPowerSaving,
244 EnableMonitor,
246}
247
248#[derive(Debug, PartialEq, Eq)]
280#[cfg_attr(feature = "defmt", derive(defmt::Format))]
281#[non_exhaustive]
282pub struct ThresholdMonitorError<E> {
283 pub stage: ThresholdMonitorStage,
286 pub confirmed: Option<ThresholdMonitorStage>,
288 pub source: Error<E>,
290}
291
292impl core::fmt::Display for Operation {
313 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
314 f.write_str(match self {
315 Self::Inspect => "inspection",
316 Self::Snapshot => "snapshot",
317 Self::MeasureOnce => "one-shot measurement",
318 Self::Configure => "configuration change",
319 Self::ThresholdMonitor => "threshold-monitor programming",
320 })
321 }
322}
323
324impl core::fmt::Display for BusContext {
325 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
326 f.write_str(match self {
327 Self::ReadConfiguration => "a configuration read",
328 Self::WriteConfiguration => "a configuration write",
329 Self::ReadPowerSaving => "a power-saving read",
330 Self::WritePowerSaving => "a power-saving write",
331 Self::ReadAls => "an ALS read",
332 Self::ReadWhite => "a white-channel read",
333 Self::ReadDeviceId => "a device-ID read",
334 Self::ReadThresholdStatus => "a threshold-status read",
335 Self::ReadLowThreshold => "a low-threshold read",
336 Self::ReadHighThreshold => "a high-threshold read",
337 Self::WriteLowThreshold => "a low-threshold write",
338 Self::WriteHighThreshold => "a high-threshold write",
339 })
340 }
341}
342
343impl core::fmt::Display for MeasureStage {
344 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
345 f.write_str(match self {
346 Self::ValidateTiming => "timing validation",
347 Self::ObserveConfiguration => "observing configuration",
348 Self::ObservePowerSaving => "observing power saving",
349 Self::EnterShutdown => "entering shutdown",
350 Self::DisablePowerSaving => "disabling power saving",
351 Self::PrepareMeasurement => "installing the measurement domain",
352 Self::ActivateMeasurement => "activating",
353 Self::FreezeResult => "freezing the result",
354 Self::ReadAls => "reading ALS",
355 Self::ReadWhite => "reading white",
356 Self::RestoreConfiguration => "restoring configuration",
357 Self::RestorePowerSaving => "restoring power saving",
358 })
359 }
360}
361
362impl core::fmt::Display for ThresholdMonitorStage {
363 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
364 f.write_str(match self {
365 Self::ObserveConfiguration => "observing configuration",
366 Self::EnterShutdown => "entering shutdown",
367 Self::DisableMonitor => "disabling the monitor",
368 Self::WriteLowThreshold => "writing the low threshold",
369 Self::WriteHighThreshold => "writing the high threshold",
370 Self::ApplyPowerSaving => "applying power saving",
371 Self::EnableMonitor => "enabling the monitor",
372 })
373 }
374}
375
376impl core::fmt::Display for ConfigurationError {
377 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
378 match self {
379 Self::ConfigurationDecode(_) => f.write_str("configuration register did not decode"),
380 Self::PowerSavingDecode(_) => f.write_str("power-saving register did not decode"),
381 Self::ThresholdStatusDecode(_) => {
382 f.write_str("threshold-status register did not decode")
383 }
384 Self::ReversedThresholds => f.write_str("thresholds were reversed"),
385 Self::ThresholdMonitorOwnsDomain => {
386 f.write_str("an enabled threshold monitor owns this domain")
387 }
388 Self::TimingIntegrationMismatch {
389 measurement,
390 timing,
391 } => write!(
392 f,
393 "timing was derived for {} ms but the measurement selects {} ms",
394 timing.milliseconds(),
395 measurement.milliseconds()
396 ),
397 }
398 }
399}
400
401impl core::error::Error for ConfigurationError {
402 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
403 match self {
404 Self::ConfigurationDecode(source) => Some(source),
405 Self::PowerSavingDecode(source) => Some(source),
406 Self::ThresholdStatusDecode(source) => Some(source),
407 _ => None,
408 }
409 }
410}
411
412impl<E> core::fmt::Display for Error<E> {
413 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
414 match self {
415 Self::Bus {
416 operation, context, ..
417 } => write!(f, "{operation} failed during {context}"),
418 Self::Configuration(_) => f.write_str("configuration was rejected"),
423 }
424 }
425}
426
427impl<E: core::error::Error + 'static> core::error::Error for Error<E> {
428 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
429 match self {
430 Self::Bus { source, .. } => Some(source),
431 Self::Configuration(source) => Some(source),
432 }
433 }
434}
435
436impl<E> core::fmt::Display for ProbeError<E> {
437 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
438 match self {
439 Self::NotPresent => f.write_str("no device acknowledged the fixed address"),
440 Self::Bus(_) => f.write_str("probe transaction failed"),
441 Self::WrongDevice { observed } => {
442 write!(f, "identity {observed:#06x} is not a supported VEML7700")
443 }
444 }
445 }
446}
447
448impl<E: core::error::Error + 'static> core::error::Error for ProbeError<E> {
449 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
450 match self {
451 Self::Bus(source) => Some(source),
452 _ => None,
455 }
456 }
457}
458
459impl<E> core::fmt::Display for MeasureOnceError<E> {
460 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
461 match self {
462 Self::Operation { stage, .. } => write!(f, "one-shot measurement failed at {stage}"),
463 Self::RecoveryFailed {
464 failed_stage,
465 recovery_stage,
466 ..
467 } => write!(
468 f,
469 "one-shot measurement failed at {failed_stage} and restoration failed at \
470 {recovery_stage}; device state is uncertain"
471 ),
472 Self::RestoreFailed { stage, .. } => write!(
473 f,
474 "a sample was captured but restoration failed at {stage}; device state is uncertain"
475 ),
476 }
477 }
478}
479
480impl<E: core::error::Error + 'static> core::error::Error for MeasureOnceError<E> {
481 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
490 match self {
491 Self::Operation { source, .. }
492 | Self::RecoveryFailed { source, .. }
493 | Self::RestoreFailed { source, .. } => Some(source),
494 }
495 }
496}
497
498impl<E> core::fmt::Display for ThresholdMonitorError<E> {
499 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
500 match self.confirmed {
501 Some(confirmed) => write!(
502 f,
503 "threshold programming failed at {}; {} was the last confirmed write",
504 self.stage, confirmed
505 ),
506 None => write!(
507 f,
508 "threshold programming failed at {}; no write was confirmed",
509 self.stage
510 ),
511 }
512 }
513}
514
515impl<E: core::error::Error + 'static> core::error::Error for ThresholdMonitorError<E> {
516 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
517 Some(&self.source)
518 }
519}
520
521#[cfg(test)]
522mod standard_error_tests {
523 use super::*;
524 use crate::config::{Gain, IntegrationTime, MeasurementConfig};
525 use crate::measurement::{AlsCounts, MeasurementPairCoherence, WhiteCounts};
526 use core::error::Error as _;
527 use core::fmt::Write as _;
528
529 #[derive(Debug, PartialEq, Eq)]
531 struct ReportableBusFault;
532
533 impl core::fmt::Display for ReportableBusFault {
534 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
535 f.write_str("arbitration lost")
536 }
537 }
538
539 impl core::error::Error for ReportableBusFault {}
540
541 #[derive(Debug, PartialEq, Eq)]
544 struct BareBusFault;
545
546 struct Sink {
548 buffer: [u8; 256],
549 used: usize,
550 }
551
552 impl Sink {
553 const fn new() -> Self {
554 Self {
555 buffer: [0; 256],
556 used: 0,
557 }
558 }
559
560 fn as_str(&self) -> &str {
561 core::str::from_utf8(&self.buffer[..self.used]).expect("valid UTF-8")
562 }
563 }
564
565 impl core::fmt::Write for Sink {
566 fn write_str(&mut self, text: &str) -> core::fmt::Result {
567 let bytes = text.as_bytes();
568 let end = self.used + bytes.len();
569 if end > self.buffer.len() {
570 return Err(core::fmt::Error);
571 }
572 self.buffer[self.used..end].copy_from_slice(bytes);
573 self.used = end;
574 Ok(())
575 }
576 }
577
578 fn report(error: &dyn core::error::Error) -> Sink {
580 let mut sink = Sink::new();
581 write!(sink, "{error}").expect("fits");
582 let mut cause = error.source();
583 while let Some(next) = cause {
584 write!(sink, ": {next}").expect("fits");
585 cause = next.source();
586 }
587 sink
588 }
589
590 fn bus_failure<E>(source: E) -> Error<E> {
591 Error::Bus {
592 operation: Operation::MeasureOnce,
593 context: BusContext::WriteConfiguration,
594 source,
595 }
596 }
597
598 #[test]
599 fn a_reportable_bus_error_reaches_the_end_of_the_chain() {
600 let error = bus_failure(ReportableBusFault);
601 assert_eq!(
602 report(&error).as_str(),
603 "one-shot measurement failed during a configuration write: arbitration lost"
604 );
605 }
606
607 #[test]
608 fn a_bus_error_that_is_not_a_standard_error_still_works() {
609 let error = bus_failure(BareBusFault);
613 let mut sink = Sink::new();
614 write!(sink, "{error}").expect("fits");
615 assert_eq!(
616 sink.as_str(),
617 "one-shot measurement failed during a configuration write"
618 );
619 }
620
621 #[test]
622 fn a_configuration_failure_chains_to_its_decode_cause() {
623 let error: Error<ReportableBusFault> =
624 Error::Configuration(ConfigurationError::ConfigurationDecode(
625 ConfigDecodeError::ReservedBits { observed: 0x2000 },
626 ));
627 assert_eq!(
628 report(&error).as_str(),
629 concat!(
630 "configuration was rejected: configuration register did not decode: ",
631 "reserved configuration bits were set: 0x2000"
632 )
633 );
634 }
635
636 #[test]
637 fn a_conclusion_this_driver_reached_has_no_cause() {
638 let absent: ProbeError<ReportableBusFault> = ProbeError::NotPresent;
641 assert!(absent.source().is_none());
642 assert_eq!(
643 report(&absent).as_str(),
644 "no device acknowledged the fixed address"
645 );
646
647 let mismatch: ProbeError<ReportableBusFault> = ProbeError::WrongDevice { observed: 0x1234 };
648 assert!(mismatch.source().is_none());
649 }
650
651 #[test]
652 fn a_nested_recovery_failure_reports_the_primary_cause() {
653 let error = MeasureOnceError::RecoveryFailed {
654 failed_stage: MeasureStage::ActivateMeasurement,
655 source: bus_failure(ReportableBusFault),
656 recovery_stage: MeasureStage::RestoreConfiguration,
657 recovery_source: bus_failure(ReportableBusFault),
658 };
659 assert_eq!(
662 report(&error).as_str(),
663 "one-shot measurement failed at activating and restoration failed at restoring \
664 configuration; device state is uncertain: one-shot measurement failed during a \
665 configuration write: arbitration lost"
666 );
667 let MeasureOnceError::RecoveryFailed {
668 recovery_source, ..
669 } = &error
670 else {
671 unreachable!()
672 };
673 assert!(matches!(recovery_source, Error::Bus { .. }));
674 }
675
676 #[test]
677 fn a_captured_sample_survives_a_reported_restoration_failure() {
678 let configuration = MeasurementConfig::new(Gain::Div8, IntegrationTime::Ms100);
679 let error = MeasureOnceError::RestoreFailed {
680 sample: MeasurementCapture {
681 als: AlsCounts::from_counts(0x1234),
682 white: WhiteCounts::from_counts(0x5678),
683 configuration,
684 nominal_illuminance: AlsCounts::from_counts(0x1234)
685 .nominal_micro_lux(configuration),
686 requested_wait_us: 133_500,
687 coherence: MeasurementPairCoherence::FrozenAfterRequestedWait,
688 },
689 stage: MeasureStage::RestorePowerSaving,
690 source: bus_failure(ReportableBusFault),
691 };
692 assert!(
693 report(&error).as_str().starts_with(
694 "a sample was captured but restoration failed at restoring power saving"
695 )
696 );
697 let MeasureOnceError::RestoreFailed { sample, .. } = &error else {
698 unreachable!()
699 };
700 assert_eq!(sample.als, AlsCounts::from_counts(0x1234));
701 }
702
703 #[test]
704 fn threshold_failures_report_confirmed_progress() {
705 let unconfirmed = ThresholdMonitorError {
706 stage: ThresholdMonitorStage::DisableMonitor,
707 confirmed: None,
708 source: bus_failure(ReportableBusFault),
709 };
710 assert_eq!(
711 report(&unconfirmed).as_str(),
712 "threshold programming failed at disabling the monitor; no write was confirmed: \
713 one-shot measurement failed during a configuration write: arbitration lost"
714 );
715
716 let partial = ThresholdMonitorError {
717 stage: ThresholdMonitorStage::ApplyPowerSaving,
718 confirmed: Some(ThresholdMonitorStage::WriteHighThreshold),
719 source: bus_failure(ReportableBusFault),
720 };
721 assert!(report(&partial).as_str().starts_with(
722 "threshold programming failed at applying power saving; writing the high threshold \
723 was the last confirmed write"
724 ));
725 }
726}