blob: 6ae0ea8a90f0354aba37fa758a82ffffbd4d6d9a [file]
// Licensed under the Apache-2.0 license
//! # I2C Device Implementation Traits
//!
//! This module provides application-level traits for implementing I2C devices such as sensors,
//! EEPROMs, display controllers, ADCs, DACs, and other peripheral devices that respond
//! to I2C master/controller requests.
//!
//! ## Purpose
//!
//! These traits focus on the **application logic** of how devices should respond to I2C
//! transactions initiated by a master/controller. They are **not** for controlling I2C
//! hardware controllers - that's handled by hardware abstraction layer (HAL) traits.
//!
//! ## Layer Distinction
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────┐
//! │ Application Layer │
//! │ i2c_device.rs - Implement device behavior (this module) │
//! │ Examples: Sensor readings, EEPROM storage, register access │
//! ├─────────────────────────────────────────────────────────────────┤
//! │ Hardware Abstraction Layer │
//! │ i2c_hardware.rs - Control I2C controller peripherals │
//! │ Examples: Buffer management, interrupt handling, bus control │
//! └─────────────────────────────────────────────────────────────────┘
//! ```
//!
//! ## Design Philosophy
//!
//! The traits in this module are designed around **device behavior patterns**:
//!
//! - **Event-driven**: Devices respond to master-initiated transactions
//! - **Callback-based**: Use `on_*` methods to handle different transaction types
//! - **Stateful**: Devices can maintain internal state between transactions
//! - **Protocol-aware**: Support common I2C device patterns (register access, etc.)
//!
//! ## Common Use Cases
//!
//! ### Temperature Sensor
//! ```rust,ignore
//! struct TemperatureSensor {
//! temperature: f32,
//! address: u8,
//! }
//!
//! impl I2CCoreTarget for TemperatureSensor {
//! fn on_read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
//! let temp_bytes = self.temperature.to_le_bytes();
//! buffer[..4].copy_from_slice(&temp_bytes);
//! Ok(4)
//! }
//! }
//! ```
//!
//! ### EEPROM Device
//! ```rust,ignore
//! struct EepromDevice {
//! memory: [u8; 256],
//! address_pointer: u8,
//! }
//!
//! impl WriteTarget for EepromDevice {
//! fn on_write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
//! if let Some(&addr) = data.first() {
//! self.address_pointer = addr;
//! // Write remaining data to memory starting at addr
//! for (i, &byte) in data[1..].iter().enumerate() {
//! if let Some(mem_slot) = self.memory.get_mut(addr as usize + i) {
//! *mem_slot = byte;
//! }
//! }
//! }
//! Ok(())
//! }
//! }
//!
//! impl ReadTarget for EepromDevice {
//! fn on_read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
//! let start = self.address_pointer as usize;
//! let end = (start + buffer.len()).min(self.memory.len());
//! let bytes_to_read = end - start;
//! buffer[..bytes_to_read].copy_from_slice(&self.memory[start..end]);
//! Ok(bytes_to_read)
//! }
//! }
//! ```
//!
//! ## Trait Overview
//!
//! The traits are organized in a composable hierarchy:
//!
//! - `I2CCoreTarget`: Core transaction lifecycle (required for all devices)
//! - `WriteTarget`: Handle write operations from master
//! - `ReadTarget`: Handle read operations from master
//! - `WriteReadTarget`: Handle combined write-read transactions
//! - `RegisterAccess`: Higher-level register-based access patterns
//! - `I2CTarget`: Convenience trait combining all capabilities
//!
//! ## Transaction Flow
//!
//! A typical I2C transaction from the device perspective:
//!
//! 1. **Address Match**: `on_address_match(address)` - Should this device respond?
//! 2. **Transaction Start**: `on_transaction_start(direction, repeated)` - Initialize for transaction
//! 3. **Data Phase**: `on_write(data)` or `on_read(buffer)` - Handle the actual data
//! 4. **Transaction End**: `on_stop()` - Clean up after transaction
//!
//! ## Error Handling
//!
//! All traits use associated `Error` types that must implement `embedded_hal::i2c::Error`,
//! providing standard I2C error conditions while allowing device-specific error extensions.
#![allow(clippy::doc_overindented_list_items)]
use embedded_hal::i2c::ErrorType as I2CErrorType;
/// Transaction response
pub enum TransactionResponse {
/// For read transactions with immediate data
ByteToSend(u8),
/// For transactions without immediate data
NoImmediateData,
}
/// Direction of an I2C transaction.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransactionDirection {
/// Write transaction (controller writing to target).
Write,
/// Read transaction (controller reading from target).
Read,
}
/// A convenience trait alias that represents a fully-featured I2C device implementation.
///
/// This trait combines all the core and extended I2C device traits into a single interface:
///
/// - [`I2CCoreTarget`]: Handles transaction lifecycle and address matching.
/// - [`ReadTarget`]: Supports reading data from the device.
/// - [`WriteTarget`]: Supports writing data to the device.
/// - [`WriteReadTarget`]: Supports combined write-read transactions.
/// - [`RegisterAccess`]: Supports register-level read/write operations.
///
/// Implementing this trait means the device is capable of handling all standard I2C device behaviors,
/// making it suitable for use in generic drivers or frameworks that require full I2C functionality.
///
/// ## When to Use
///
/// Use this trait when you need a device that supports all I2C interaction patterns:
/// - Simple read/write operations
/// - Register-based access (common for sensors, configuration devices)
/// - Complex transaction sequences
/// - Generic device drivers that work with multiple device types
///
/// ## Implementation Pattern
///
/// Most implementations will implement the individual traits and get `I2CTarget` automatically:
///
/// ```rust,ignore
/// struct MyComplexDevice {
/// registers: [u8; 256],
/// current_register: u8,
/// }
///
/// impl I2CCoreTarget for MyComplexDevice { /* ... */ }
/// impl ReadTarget for MyComplexDevice { /* ... */ }
/// impl WriteTarget for MyComplexDevice { /* ... */ }
/// impl WriteReadTarget for MyComplexDevice { /* ... */ }
/// impl RegisterAccess for MyComplexDevice { /* ... */ }
///
/// // Now MyComplexDevice automatically implements I2CTarget
/// fn use_any_device<T: I2CTarget>(device: &mut T) {
/// // Can use all I2C device capabilities
/// }
/// ```
pub trait I2CTarget:
I2CCoreTarget + ReadTarget + WriteTarget + WriteReadTarget + RegisterAccess
{
}
impl<T> I2CTarget for T where
T: I2CCoreTarget + ReadTarget + WriteTarget + WriteReadTarget + RegisterAccess
{
}
/// Trait representing core I2C device behavior.
///
/// This trait defines the fundamental methods that an I2C device must implement to handle
/// transactions initiated by an I2C master/controller. It covers the essential lifecycle
/// of I2C transactions from the device's perspective.
///
/// ## Core Responsibilities
///
/// - **Address matching**: Decide whether to respond to a specific I2C address
/// - **Transaction initialization**: Set up device state for incoming transactions
/// - **Transaction lifecycle**: Handle start conditions, repeated starts, and stop conditions
///
/// ## Implementation Notes
///
/// This trait focuses on **device logic**, not hardware control. Implementations should:
/// - Maintain device state (registers, memory, sensor readings, etc.)
/// - Implement device-specific address matching logic
/// - Handle transaction setup and teardown
/// - Prepare for data exchange (actual data handling is in `ReadTarget`/`WriteTarget`)
///
/// For hardware I2C controller management, use hardware abstraction layer traits instead.
pub trait I2CCoreTarget: I2CErrorType {
/// Initialize the target with a specific address.
fn init(&mut self, address: u8) -> Result<(), Self::Error>;
/// Called when a new I2C transaction begins.
///
/// This method is invoked at the start of a transaction initiated by the controller (master).
/// It provides a flag indicating whether the transaction is a repeated start, which allows
/// the target (slave) device to preserve or reset internal state accordingly.
///
/// # Arguments
///
/// * `direction` - Indicates whether the upcoming transaction is a Read or Write.
/// * `repeated` - A boolean flag indicating whether this transaction is a repeated start (`true`)
/// or a fresh start (`false`). A repeated start means the controller has not
/// released the bus between transactions.
///
/// # Return
///
/// * `Ok(TransactionResponse::ByteToSend(byte))` - For read transactions, the first byte to send to the controller
/// * `Ok(TransactionResponse::NoImmediateData)` - No immediate data to send or this is a write transaction
/// * `Err(e)` - An error occurred during transaction setup
/// # Usage Model
///
/// This method is distinct from `on_address_match(address: u8) -> bool`:
///
/// - `on_address_match` is called during the address phase to decide whether the target should respond.
/// - `on_transaction_start` is called after the address is matched and before the data phase begins.
///
/// ## Typical Use Cases:
/// - Reset internal state if `repeated == false`.
/// - Preserve context (e.g., register pointer) if `repeated == true`.
/// - Prepare buffers or state machines for read/write.
///
/// ## When it's called:
/// - After `on_address_match` returns `true`.
/// - Before `on_read` or `on_write` is invoked.
///
/// # Example
///
/// ```rust,ignore
/// fn on_transaction_start(&mut self, direction: TransactionDirection, repeated: bool) {
/// if repeated {
/// // Continue using previous state
/// } else {
/// // Reset internal state
/// }
/// }
/// ```
fn on_transaction_start(
&mut self,
direction: TransactionDirection,
repeated: bool,
) -> Result<TransactionResponse, Self::Error>;
/// Optional: handle stop condition or reset.
///
/// This method is called when the master sends a stop condition, indicating the end of a transaction.
fn on_stop(&mut self);
/// Optional: handle address match event.
///
/// # Arguments
///
/// * `address` - The address sent by the master.
///
/// # Returns
///
/// * `bool` - Returns `true` if the address matches the target's address, `false` otherwise.
fn on_address_match(&mut self, address: u8) -> bool;
}
/// Trait for I2C targets that support write operations.
pub trait WriteTarget: I2CCoreTarget {
/// Called when the master initiates a write to this target.
///
/// # Arguments
///
/// * `data` - A slice containing the data to be written to the target.
///
/// # Returns
///
/// * `Result<(), I2CError>` - Returns `Ok(())` if the write is successful, or an `I2CError` if an error occurs.
fn on_write(&mut self, data: &[u8]) -> Result<(), Self::Error>;
}
/// Trait for I2C targets that support read operations.
pub trait ReadTarget: I2CCoreTarget {
/// Called when the master initiates a read from this target.
///
/// # Arguments
///
/// * `buffer` - A mutable slice where the read data will be stored.
///
/// # Returns
///
/// * `Result<usize, I2CError>` - Returns the number of bytes read if successful, or an `I2CError` if an error occurs.
fn on_read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error>;
}
/// Trait for I2C targets that support combined write-read transactions.
pub trait WriteReadTarget: WriteTarget + ReadTarget {
/// Performs a combined write-read transaction on the device.
///
/// This method writes data from `write_buffer` and then reads data into `read_buffer`
/// in a single, atomic operation (if supported by the underlying hardware).
///
/// # Parameters
/// - `write_buffer`: The buffer containing data to write.
/// - `read_buffer`: The buffer to store the data read from the device.
///
/// # Returns
/// - `Ok(usize)`: The number of bytes read into `read_buffer`.
/// - `Err(Self::Error)`: If the transaction fails.
///
/// # Errors
/// This function returns an error if the write or read operation fails.
///
/// # Example
/// ```ignore
/// device.on_write_read(&mut [0x01, 0x02], &mut [0; 4])?;
/// ```
fn on_write_read(
&mut self,
write_buffer: &mut [u8],
read_buffer: &mut [u8],
) -> Result<usize, Self::Error> {
self.on_write(write_buffer)?;
self.on_read(read_buffer)
}
}
/// Provides register-level access to a device that supports both reading and writing operations.
///
/// This trait combines the capabilities of `WriteTarget` and `ReadTarget` to provide a higher-level
/// interface for accessing device registers. It simplifies common operations like reading and writing
/// individual registers by address.
pub trait RegisterAccess: WriteTarget + ReadTarget {
/// Writes a single byte to a specified register address.
///
/// # Parameters
///
/// * `address` - The register address to write to
/// * `data` - The byte value to write to the register
///
/// # Returns
///
/// * `Ok(())` - The write operation was successful
/// * `Err(e)` - An error occurred during the write operation
fn write_register(&mut self, address: u8, data: u8) -> Result<(), Self::Error>;
/// Reads data from a specified register address into a buffer.
///
/// # Parameters
///
/// * `address` - The register address to read from
/// * `buffer` - The buffer to store the read data
///
/// # Returns
///
/// * `Ok(usize)` - The number of bytes successfully read into the buffer
/// * `Err(e)` - An error occurred during the read operation
fn read_register(&mut self, address: u8, buffer: &mut [u8]) -> Result<usize, Self::Error>;
}