blob: 3e2937c1fbe03a87dc292c710661598b3b2da358 [file]
// Licensed under the Apache-2.0 license
//! I2C Error types
//!
//! This module defines error types for I2C operations, providing both
//! low-level hardware errors and higher-level service response codes.
use core::fmt;
/// Response codes from the I2C service.
///
/// These codes indicate the result of an I2C operation and are designed
/// to be compatible with IPC/RPC response handling.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum ResponseCode {
/// Operation completed successfully.
Success = 0,
/// No device acknowledged at the given address (NACK on address byte).
NoDevice = 1,
/// Device NACKed during data transfer.
NackData = 2,
/// Bus arbitration was lost to another master.
ArbitrationLost = 3,
/// Bus is stuck (SDA or SCL held low).
BusStuck = 4,
/// Operation timed out.
Timeout = 5,
/// Invalid bus index specified.
InvalidBus = 6,
/// Invalid address specified.
InvalidAddress = 7,
/// Buffer too small for the requested operation.
BufferTooSmall = 8,
/// Buffer too large for the hardware to handle.
BufferTooLarge = 9,
/// The I2C controller is not initialized.
NotInitialized = 10,
/// The I2C controller is busy with another operation.
Busy = 11,
/// Permission denied - task not authorized for this bus.
Unauthorized = 12,
/// General I/O error.
IoError = 13,
/// Internal server error.
ServerError = 14,
}
impl ResponseCode {
/// Returns `true` if this represents a successful operation.
#[inline]
pub const fn is_success(self) -> bool {
matches!(self, ResponseCode::Success)
}
/// Returns `true` if this represents an error condition.
#[inline]
pub const fn is_error(self) -> bool {
!self.is_success()
}
/// Converts from a raw u8 value.
pub const fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(ResponseCode::Success),
1 => Some(ResponseCode::NoDevice),
2 => Some(ResponseCode::NackData),
3 => Some(ResponseCode::ArbitrationLost),
4 => Some(ResponseCode::BusStuck),
5 => Some(ResponseCode::Timeout),
6 => Some(ResponseCode::InvalidBus),
7 => Some(ResponseCode::InvalidAddress),
8 => Some(ResponseCode::BufferTooSmall),
9 => Some(ResponseCode::BufferTooLarge),
10 => Some(ResponseCode::NotInitialized),
11 => Some(ResponseCode::Busy),
12 => Some(ResponseCode::Unauthorized),
13 => Some(ResponseCode::IoError),
14 => Some(ResponseCode::ServerError),
_ => None,
}
}
}
impl fmt::Display for ResponseCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResponseCode::Success => write!(f, "success"),
ResponseCode::NoDevice => write!(f, "no device at address"),
ResponseCode::NackData => write!(f, "device NACKed data"),
ResponseCode::ArbitrationLost => write!(f, "bus arbitration lost"),
ResponseCode::BusStuck => write!(f, "bus stuck"),
ResponseCode::Timeout => write!(f, "operation timed out"),
ResponseCode::InvalidBus => write!(f, "invalid bus index"),
ResponseCode::InvalidAddress => write!(f, "invalid address"),
ResponseCode::BufferTooSmall => write!(f, "buffer too small"),
ResponseCode::BufferTooLarge => write!(f, "buffer too large"),
ResponseCode::NotInitialized => write!(f, "controller not initialized"),
ResponseCode::Busy => write!(f, "controller busy"),
ResponseCode::Unauthorized => write!(f, "unauthorized"),
ResponseCode::IoError => write!(f, "I/O error"),
ResponseCode::ServerError => write!(f, "server error"),
}
}
}
/// Specific I2C error conditions at the hardware level.
///
/// These errors are compatible with `embedded_hal::i2c::ErrorKind`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum I2cErrorKind {
/// I2C bus error (misplaced start/stop conditions or external interference).
Bus,
/// Lost arbitration to another master on the bus.
ArbitrationLoss,
/// No acknowledgment received from the target device.
NoAcknowledge(NoAcknowledgeSource),
/// Data overrun/underrun error.
Overrun,
/// An unspecified error occurred.
Other,
}
/// Source of a NoAcknowledge error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoAcknowledgeSource {
/// NACK received during address phase.
Address,
/// NACK received during data phase.
Data,
/// Unknown source of NACK.
Unknown,
}
impl fmt::Display for I2cErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
I2cErrorKind::Bus => write!(f, "bus error"),
I2cErrorKind::ArbitrationLoss => write!(f, "arbitration loss"),
I2cErrorKind::NoAcknowledge(src) => match src {
NoAcknowledgeSource::Address => write!(f, "NACK on address"),
NoAcknowledgeSource::Data => write!(f, "NACK on data"),
NoAcknowledgeSource::Unknown => write!(f, "NACK (unknown source)"),
},
I2cErrorKind::Overrun => write!(f, "overrun"),
I2cErrorKind::Other => write!(f, "other error"),
}
}
}
/// I2C operation error.
///
/// This is the main error type returned by I2C client operations,
/// combining response codes with additional context.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct I2cError {
/// The response code from the service.
pub code: ResponseCode,
/// The specific error kind, if known.
pub kind: Option<I2cErrorKind>,
}
impl I2cError {
/// Creates a new error from a response code.
pub const fn from_code(code: ResponseCode) -> Self {
let kind = match code {
ResponseCode::NoDevice => Some(I2cErrorKind::NoAcknowledge(NoAcknowledgeSource::Address)),
ResponseCode::NackData => Some(I2cErrorKind::NoAcknowledge(NoAcknowledgeSource::Data)),
ResponseCode::ArbitrationLost => Some(I2cErrorKind::ArbitrationLoss),
ResponseCode::BusStuck => Some(I2cErrorKind::Bus),
_ => None,
};
I2cError { code, kind }
}
/// Creates a new error with explicit kind.
pub const fn new(code: ResponseCode, kind: I2cErrorKind) -> Self {
I2cError { code, kind: Some(kind) }
}
/// Returns `true` if this is a no-device error.
#[inline]
pub const fn is_no_device(&self) -> bool {
matches!(self.code, ResponseCode::NoDevice)
}
/// Returns `true` if this is a timeout error.
#[inline]
pub const fn is_timeout(&self) -> bool {
matches!(self.code, ResponseCode::Timeout)
}
}
impl fmt::Display for I2cError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "I2C error: {}", self.code)?;
if let Some(kind) = &self.kind {
write!(f, " ({kind})")?;
}
Ok(())
}
}
impl From<ResponseCode> for I2cError {
fn from(code: ResponseCode) -> Self {
I2cError::from_code(code)
}
}
// Implement embedded_hal::i2c::Error trait for interoperability
impl embedded_hal::i2c::Error for I2cError {
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
match self.kind {
Some(I2cErrorKind::Bus) => embedded_hal::i2c::ErrorKind::Bus,
Some(I2cErrorKind::ArbitrationLoss) => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
Some(I2cErrorKind::NoAcknowledge(src)) => {
let nack_src = match src {
NoAcknowledgeSource::Address => {
embedded_hal::i2c::NoAcknowledgeSource::Address
}
NoAcknowledgeSource::Data => {
embedded_hal::i2c::NoAcknowledgeSource::Data
}
NoAcknowledgeSource::Unknown => {
embedded_hal::i2c::NoAcknowledgeSource::Unknown
}
};
embedded_hal::i2c::ErrorKind::NoAcknowledge(nack_src)
}
Some(I2cErrorKind::Overrun) => embedded_hal::i2c::ErrorKind::Overrun,
Some(I2cErrorKind::Other) | None => embedded_hal::i2c::ErrorKind::Other,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_response_code_roundtrip() {
for i in 0..=14 {
let code = ResponseCode::from_u8(i).expect("valid code");
assert_eq!(code as u8, i);
}
}
#[test]
fn test_error_from_code() {
let err = I2cError::from_code(ResponseCode::NoDevice);
assert!(err.is_no_device());
assert_eq!(
err.kind,
Some(I2cErrorKind::NoAcknowledge(NoAcknowledgeSource::Address))
);
}
#[test]
fn test_error_display() {
let err = I2cError::from_code(ResponseCode::Timeout);
assert!(err.is_timeout());
assert!(!err.is_no_device());
}
}