i2cdriver 0.1.0

Use Excamera I2CDriver and I2CMini devices from Rust, optionally via embeddded-hal traits.
Documentation
#![doc = include_str!("../README.md")]

#[macro_use]
extern crate scan_fmt;

use std::io::prelude::*;
use std::time::Duration;
use std::marker::PhantomData;

mod error;
pub use crate::error::{Error, ProtocolError};

mod status;
pub use crate::status::Status;

pub struct I2CDriver<MODE> {
   mode: PhantomData<MODE>,
    serial: Box<dyn serialport::SerialPort>,
}

impl I2CDriver<NormalMode> {
    /// Accepts the name of the serial port to use and opens and configures it as needed.
    pub fn open(port: &str) -> Result<Self, Error> {
        let serial = serialport::new(port, 1_000_000)
            .data_bits(serialport::DataBits::Eight)
            .flow_control(serialport::FlowControl::None)
            .parity(serialport::Parity::None)
            .stop_bits(serialport::StopBits::One)
            .timeout(Duration::from_millis(1000))
            .open()
            .map_err(|source| Error::Open {
                path: port.to_string(),
                source,
            })?;
        let mut d = I2CDriver::<NormalMode>{mode: PhantomData, serial};
        d.reset_driver()?;
        Ok(d)
    }

    /// Test the I2CDriver connection by sending a single byte and reading it back.
    pub fn echo(&mut self, char: u8) -> Result<u8, Error> {
        self.serial.write_all(&[b'e', char])?;
        self.serial.flush()?;
        let mut buf = [0u8; 1];
        self.serial.read_exact(&mut buf)?;
        Ok(buf[0])
    }

    /// Reboot the I2CDriver. This is not normally necessary and here just for testing.
    pub fn reboot(&mut self) -> Result<(), Error> {
        self.serial.write_all(&[b'_'])?;
        self.serial.flush()?;
        Ok(())
    }

    /// Reset the I2C bus.
    pub fn reset(&mut self) -> Result<(), Error> {
        self.serial.write_all(&[b'x'])?;
        self.serial.flush()?;
        let mut buf = [0u8; 1];
        self.serial.read_exact(&mut buf)?;
        if buf[0] & 0b11 != 0b11 {
            Err(Error::Arbitration)
        } else {
            Ok(())
        }
    }

    /// Collect the I2CDriver status report.
    ///
    ///  eg `[i2cdriver1 DB000000 000000065 5.183 000 31.4 I 1 1 100 24 ffff                ]`
    pub fn status(&mut self) -> Result<Status, Error> {
        self.serial.write_all(&[b'?'])?;
        self.serial.flush()?;
        let mut buf = [0u8; 80];
        self.serial.read_exact(&mut buf)?;
        let st = std::str::from_utf8(&buf).map_err(|e| Error::Protocol(e.into()))?;
        st.parse()
    }

    /// Set the I2C bus speed to 100kHz.
    pub fn speed100k(&mut self) -> Result<(), Error> {
        self.serial.write_all(&[b'1'])?;
        self.serial.flush()?;
        Ok(())
    }

    /// Set the I2C bus speed to 400kHz.
    pub fn speed400k(&mut self) -> Result<(), Error> {
        self.serial.write_all(&[b'4'])?;
        self.serial.flush()?;
        Ok(())
    }

    /// Scan the I2C bus for devices, returning a list of addresses which ACK'd the start condition.
    pub fn scan(&mut self) -> Result<Vec<u8>, Error> {
        self.serial.write_all(&[b'd'])?;
        self.serial.flush()?;
        let mut buf = [0u8; 112];
        self.serial.read_exact(&mut buf)?;

        Ok(buf
            .into_iter()
            .enumerate()
            .filter_map(|(i, r)| {
                if r == b'1' {
                    Some(<usize as TryInto<u8>>::try_into(i).unwrap() + 8)
                } else {
                    None
                }
            })
            .collect())
    }

    /// Put the I2CDriver into monitor mode. When in monitor mode, the I2CDriver will display bus traffic. It will not respond to commands except `stop_monitor`.
    pub fn start_monitor(mut self) -> Result<I2CDriver<MonitorMode>, Error> {
        self.serial.write_all(&[b'm'])?;
        self.serial.flush()?;
        Ok(I2CDriver::<MonitorMode>{mode: PhantomData, serial: self.serial})
    }

    /// Start an I2C transaction. Set `read` to false to start a write.
    ///
    /// _This is a low-level function and you probably want to use the `embedded-hal` traits instead._
    pub fn start(&mut self, addr7: u8, read: bool) -> Result<(), Error> {
        if addr7 & 0x80 != 0 {
            return Err(Error::Address(addr7));
        }
        self.serial.write_all(&[b's', addr7 << 1 | read as u8])?;
        self.serial.flush()?;
        let mut buf = [0u8; 1];
        self.serial.read_exact(&mut buf)?;
        match buf[0] & 0b111 {
            1 => Ok(()),
            0b100 => Err(Error::Arbitration),
            0b010 => Err(Error::Timeout),
            0 => Err(Error::MissingAddrAck),
            _ => Err(ProtocolError::Parse(buf.to_vec()).into()),
        }
    }

    /// Stop an I2C transaction.
    ///
    /// _This is a low-level function and you probably want to use the `embedded-hal` traits instead._
    pub fn stop(&mut self) -> Result<(), Error> {
        self.serial.write_all(&[b'p'])?;
        self.serial.flush()?;
        Ok(())
    }

    /// Write a buffer of data to the I2C bus.
    ///
    /// _This is a low-level function and you probably want to use the `embedded-hal` traits instead._
    pub fn raw_write(&mut self, buf: &[u8]) -> Result<(), Error> {
        for chunk in buf.chunks(64) {
            self.serial
                .write_all(&[0b11000000 | (chunk.len() - 1) as u8])?;
            self.serial.write_all(chunk)?;
            self.serial.flush()?;
            let mut buf = [0u8; 1];
            self.serial.read_exact(&mut buf)?;
            match buf[0] & 0b111 {
                0b100 => return Err(Error::Arbitration),
                0b010 => return Err(Error::Timeout),
                0 => return Err(Error::MissingDataAck),
                _ => continue,
            }
        }
        Ok(())
    }

    /// Read a buffer of data from the I2C bus.
    /// In a typical usage scenario, `nak` should be true on the last read.
    ///
    /// _This is a low-level function and you probably want to use the `embedded-hal` traits instead._
    pub fn raw_read_into(&mut self, buf: &mut [u8], nak: bool) -> Result<(), Error> {
        let mut iter: std::iter::Peekable<std::slice::ChunksMut<'_, u8>> =
            buf.chunks_mut(64).peekable();
        while let Some(chunk) = iter.next() {
            // nak is only sent on the last chunk (if any).
            if iter.peek().is_none() && nak {
                self.serial
                    .write_all(&[0b10000000 | (chunk.len() - 1) as u8])?;
            } else {
                self.serial.write_all(&[b'a', chunk.len() as u8])?;
            }
            self.serial.flush()?;
            self.serial.read_exact(chunk)?;
        }
        Ok(())
    }
}

impl<T> I2CDriver<T> {
        /// Resets the state of the driver if it was in bitbang or monitor mode.
        fn reset_driver(&mut self) -> Result<(), Error> {
            // Exit monitor mode if necessary & enter i2c mode.
            self.serial.write_all(&[0x20, b'i'])?;
            self.serial.flush()?;
            Ok(())
        }
}

impl I2CDriver<MonitorMode> {
        /// Take the I2CDriver out of monitor mode.
        pub fn stop_monitor(mut self) -> Result<I2CDriver<NormalMode>, Error> {
            self.reset_driver()?;
            Ok(I2CDriver::<NormalMode>{mode: PhantomData, serial: self.serial})
        }
}

/// Marker type for Monitor Mode.
pub struct MonitorMode;
/// Marker type for Normal Mode.
pub struct NormalMode;

/// Embedded HAL 1.x error handling support.
#[cfg(feature = "eh1")]
impl embedded_hal_1::i2c::ErrorType for I2CDriver<NormalMode> {
    type Error = Error;
}

/// Embedded HAL 1.x support.
#[cfg(feature = "eh1")]
impl embedded_hal_1::i2c::I2c<embedded_hal_1::i2c::SevenBitAddress> for I2CDriver<NormalMode> {
    fn transaction(
        &mut self,
        address: u8,
        operations: &mut [embedded_hal_1::i2c::Operation<'_>],
    ) -> Result<(), Self::Error> {
        for op in operations {
            match op {
                embedded_hal_1::i2c::Operation::Write(bytes) => {
                    self.start(address, false)?;
                    self.raw_write(bytes)?;
                }
                embedded_hal_1::i2c::Operation::Read(buffer) => {
                    self.start(address, true)?;
                    self.raw_read_into(buffer, true)?;
                }
            }
        }
        self.stop()?;
        Ok(())
    }
}

#[cfg(feature = "async")]
/// Faux async support for embedded HAL 1.x
impl embedded_hal_async::i2c::I2c<embedded_hal_1::i2c::SevenBitAddress> for I2CDriver<NormalMode> {
    async fn transaction(
        &mut self,
        address: u8,
        operations: &mut [embedded_hal_1::i2c::Operation<'_>],
    ) -> Result<(), Self::Error> {
        // Should use tokio::task::spawn_blocking?
        embedded_hal_1::i2c::I2c::<embedded_hal_1::i2c::SevenBitAddress>::transaction(self, address, operations)
    }
}
/// Embedded HAL 0.2.x `Write` support.
#[cfg(feature = "eh0")]
impl embedded_hal_0::blocking::i2c::Write for I2CDriver<NormalMode> {
    type Error = Error;

    fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> {
        self.start(addr, false)?;
        self.raw_write(bytes)?;
        self.stop()?;
        Ok(())
    }
}

/// Embedded HAL 0.2.x `Read` support.
#[cfg(feature = "eh0")]
impl embedded_hal_0::blocking::i2c::Read for I2CDriver<NormalMode> {
    type Error = Error;

    fn read(
        &mut self,
        address: u8,
        buffer: &mut [u8],
    ) -> Result<(), Self::Error> {
        self.start(address, true)?;
        self.raw_read_into(buffer, true)?;
        self.stop()?;
        Ok(())
    }
}

/// Embedded HAL 0.2.x `WriteRead` support. (typically used for "register read" type operations.)
#[cfg(feature = "eh0")]
impl embedded_hal_0::blocking::i2c::WriteRead for I2CDriver<NormalMode> {
    type Error = Error;

    fn write_read(
        &mut self,
        address: u8,
        bytes: &[u8],
        buffer: &mut [u8],
    ) -> Result<(), Self::Error> {
        if !bytes.is_empty() {
            self.start(address, false)?;
            self.raw_write(bytes)?;
        }
        if !buffer.is_empty() {
            self.start(address, true)?;
            self.raw_read_into(buffer, true)?;
        }
        self.stop()?;
        Ok(())
    }
}