use core::fmt;
use embedded_hal::i2c::I2c;
use crate::DeviceStatus;
use crate::types::{MicrosIsr, NoDelay};
#[derive(Debug, Clone, Copy, PartialEq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error<E> {
#[error("I2C bus error: {0:?}")]
I2c(E),
#[error("invalid manufacturer ID: 0x{0:04X} (expected 0x5449)")]
InvalidManufacturerId(u16),
#[error("device version mismatch: expected 0x{expected:02X}, got 0x{got:02X}")]
VersionMismatch {
expected: u8,
got: u8,
},
#[error("CRC mismatch: device sent 0x{expected:02X}, computed 0x{computed:02X}")]
CrcMismatch {
expected: u8,
computed: u8,
},
#[error("conversion did not complete within the poll limit")]
ConversionTimeout,
#[error("angle calculation is not enabled; configure it in ConfigBuilder")]
AngleNotEnabled,
#[error("address change from 0x{old_address:02X} to 0x{new_address:02X} could not be verified")]
AddressChangeFailed {
old_address: u8,
new_address: u8,
},
#[error("register 0x{register:02X} returned invalid value 0x{value:02X}")]
InvalidRegisterValue {
register: u8,
value: u8,
},
#[error("read methods require Standard I2C mode, current mode is 0x{mode:02X}")]
NonStandardReadMode {
mode: u8,
},
#[error("temperature channel is disabled; enable it in ConfigBuilder")]
TempDisabled,
#[error("enabling sensor CRC requires the `crc` Cargo feature")]
CrcFeatureRequired,
#[error("sensor diagnostic failure: {0:?}")]
DiagnosticFailure(DeviceStatus),
}
#[derive(Debug, Clone, Copy, PartialEq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ConfigError {
#[error("angle calculation requires at least two magnetic channels")]
AngleRequiresTwoChannels,
#[error("sleep duration ({sleep} µs) < conversion time ({conversion} µs)")]
SleepShorterThanConversion {
sleep: MicrosIsr,
conversion: MicrosIsr,
},
#[error("TriggerMode::IntPin requires OperatingMode::Standby")]
IntPinTriggerRequiresStandby,
#[error("YZ/XZ angle calculation requires xy_range == z_range")]
AngleMixedRanges,
}
impl<E> From<E> for Error<E> {
#[inline]
fn from(e: E) -> Self {
Error::I2c(e)
}
}
#[non_exhaustive]
pub struct InitError<I2C: I2c, D = NoDelay> {
pub error: Error<I2C::Error>,
pub i2c: I2C,
pub delay: D,
}
impl<I2C: I2c, D> fmt::Debug for InitError<I2C, D>
where
I2C::Error: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InitError")
.field("error", &self.error)
.field("i2c", &"<I2C bus>")
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, Copy, PartialEq)]
struct FakeI2cError(u8);
#[test]
fn from_i2c_error() {
let raw = FakeI2cError(7);
let err: Error<FakeI2cError> = Error::from(raw);
assert_eq!(err, Error::I2c(FakeI2cError(7)));
}
#[test]
fn variant_invalid_manufacturer_id() {
let err: Error<FakeI2cError> = Error::InvalidManufacturerId(0x0000);
assert_eq!(err, Error::InvalidManufacturerId(0x0000));
}
#[test]
fn variant_version_mismatch() {
let err: Error<FakeI2cError> = Error::VersionMismatch {
expected: 0x01,
got: 0x02,
};
assert_eq!(
err,
Error::VersionMismatch {
expected: 0x01,
got: 0x02,
}
);
}
#[test]
fn variant_crc_mismatch() {
let err: Error<FakeI2cError> = Error::CrcMismatch {
expected: 0xAB,
computed: 0xCD,
};
assert_eq!(
err,
Error::CrcMismatch {
expected: 0xAB,
computed: 0xCD,
}
);
}
#[test]
fn variant_conversion_timeout() {
let err: Error<FakeI2cError> = Error::ConversionTimeout;
assert_eq!(err, Error::ConversionTimeout);
}
#[test]
fn variant_angle_not_enabled() {
let err: Error<FakeI2cError> = Error::AngleNotEnabled;
assert_eq!(err, Error::AngleNotEnabled);
}
#[test]
fn variant_address_change_failed() {
let err: Error<FakeI2cError> = Error::AddressChangeFailed {
old_address: 0x22,
new_address: 0x30,
};
assert_eq!(
err,
Error::AddressChangeFailed {
old_address: 0x22,
new_address: 0x30,
}
);
}
#[test]
fn variant_invalid_register_value() {
let err: Error<FakeI2cError> = Error::InvalidRegisterValue {
register: 0x00,
value: 0xFF,
};
assert_eq!(
err,
Error::InvalidRegisterValue {
register: 0x00,
value: 0xFF,
}
);
}
#[test]
fn variant_non_standard_read_mode() {
let err: Error<FakeI2cError> = Error::NonStandardReadMode { mode: 0x01 };
assert_eq!(err, Error::NonStandardReadMode { mode: 0x01 });
}
#[test]
fn variant_temp_disabled() {
let err: Error<FakeI2cError> = Error::TempDisabled;
assert_eq!(err, Error::TempDisabled);
}
#[test]
fn variant_crc_feature_required() {
let err: Error<FakeI2cError> = Error::CrcFeatureRequired;
assert_eq!(err, Error::CrcFeatureRequired);
}
#[test]
fn error_is_copy() {
let err: Error<FakeI2cError> = Error::ConversionTimeout;
let copy = err;
assert_eq!(err, copy);
}
#[test]
fn error_clone() {
let err: Error<FakeI2cError> = Error::I2c(FakeI2cError(1));
#[expect(clippy::clone_on_copy, reason = "explicitly testing Clone impl")]
let cloned = err.clone();
assert_eq!(err, cloned);
}
#[test]
fn config_error_angle_requires_two_channels() {
let err = ConfigError::AngleRequiresTwoChannels;
assert_eq!(err, ConfigError::AngleRequiresTwoChannels);
}
#[test]
fn config_error_sleep_shorter_than_conversion() {
let err = ConfigError::SleepShorterThanConversion {
sleep: MicrosIsr(1_000),
conversion: MicrosIsr(3_225),
};
assert_eq!(
err,
ConfigError::SleepShorterThanConversion {
sleep: MicrosIsr(1_000),
conversion: MicrosIsr(3_225),
}
);
}
#[test]
fn config_error_is_copy() {
let err = ConfigError::AngleRequiresTwoChannels;
let copy = err;
assert_eq!(err, copy);
}
#[test]
fn init_error_debug_impl() {
extern crate alloc;
use alloc::format;
use embedded_hal_mock::eh1::i2c::Mock as I2cMock;
let expectations: &[embedded_hal_mock::eh1::i2c::Transaction] = &[];
let i2c = I2cMock::new(expectations);
let mut init_err: InitError<I2cMock, crate::NoDelay> = InitError {
error: Error::InvalidManufacturerId(0x1234),
i2c,
delay: crate::NoDelay,
};
let debug = format!("{:?}", init_err);
assert!(debug.contains("InitError"));
assert!(debug.contains("InvalidManufacturerId"));
assert!(debug.contains("<I2C bus>"));
init_err.i2c.done();
}
}