#![no_std]
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate alloc;
pub mod device;
#[cfg(feature = "native-serial")]
#[cfg_attr(docsrs, doc(cfg(feature = "native-serial")))]
pub mod serial;
pub use embedded_io_async;
use core::{
fmt::{Debug, Display, Formatter},
num::Wrapping,
};
use embedded_io_async::{Read, ReadExactError, Write};
use log::debug;
use strum::FromRepr;
pub type Result<T, E> = core::result::Result<T, Error<E>>;
#[non_exhaustive]
#[derive(PartialEq, Eq, Debug)]
pub enum Error<E> {
InvalidArgument,
IncorrectChecksum,
InvalidCommand,
UnknownResponseCode,
UnexpectedEof,
Io(E),
}
impl<E: core::error::Error> Display for Error<E> {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
match self {
Self::InvalidArgument => write!(f, "invalid argument"),
Self::IncorrectChecksum => write!(f, "incorrect checksum"),
Self::InvalidCommand => write!(f, "invalid command"),
Self::UnknownResponseCode => write!(f, "unknown response code"),
Self::UnexpectedEof => write!(f, "unexpected end-of-file"),
Self::Io(err) => write!(f, "input/output error: {err}"),
}
}
}
impl<E: core::error::Error> core::error::Error for Error<E> {}
impl<E> From<E> for Error<E> {
fn from(err: E) -> Self {
Self::Io(err)
}
}
impl<E> From<ReadExactError<E>> for Error<E> {
fn from(err: ReadExactError<E>) -> Self {
match err {
ReadExactError::UnexpectedEof => Self::UnexpectedEof,
ReadExactError::Other(err) => Self::Io(err),
}
}
}
#[derive(Debug)]
#[repr(u8)]
enum Command {
Lock = 0x10,
QuerySoftwareId = 0x11,
UnlockReadAccess = 0x20,
ReadMemory = 0x30,
ReadEeprom = 0x31,
UnlockFullAccess = 0x32,
WriteMemory = 0x40,
WriteEeprom = 0x41,
JumpToSubroutine = 0x42,
Halt = 0x45,
SetBaudRate2400 = 0x46,
SetBaudRate9600 = 0x47,
}
#[derive(Debug)]
struct Request {
cmd: Command,
param: u16,
len: u8,
}
impl Request {
pub fn new(cmd: Command, param: u16, len: u8) -> Self {
let req = Self { cmd, param, len };
debug!("New request: {req:x?}");
req
}
}
impl From<Request> for Payload<4> {
fn from(req: Request) -> Self {
let mut buf = [0x00; 4];
buf[0] = req.cmd as u8;
buf[1..3].copy_from_slice(&req.param.to_le_bytes());
buf[3] = req.len;
Self(buf)
}
}
#[derive(FromRepr, Debug)]
#[repr(u8)]
enum ResponseCode {
Success,
IncorrectChecksum,
InvalidCommand,
}
#[derive(Debug)]
pub struct Payload<const N: usize>([u8; N]);
impl<const N: usize> From<[u8; N]> for Payload<N> {
fn from(data: [u8; N]) -> Self {
Self(data)
}
}
impl From<u8> for Payload<1> {
fn from(val: u8) -> Self {
Self(val.to_le_bytes())
}
}
impl From<u16> for Payload<2> {
fn from(val: u16) -> Self {
Self(val.to_le_bytes())
}
}
impl From<u32> for Payload<4> {
fn from(val: u32) -> Self {
Self(val.to_le_bytes())
}
}
impl<const N: usize> From<Payload<N>> for [u8; N] {
fn from(payload: Payload<N>) -> Self {
payload.0
}
}
impl From<Payload<1>> for u8 {
fn from(payload: Payload<1>) -> Self {
Self::from_le_bytes(payload.0)
}
}
impl From<Payload<2>> for u16 {
fn from(payload: Payload<2>) -> Self {
Self::from_le_bytes(payload.0)
}
}
impl From<Payload<4>> for u32 {
fn from(payload: Payload<4>) -> Self {
Self::from_le_bytes(payload.0)
}
}
fn compute_checksum(data: &[u8]) -> u8 {
data.iter().map(|&x| Wrapping(x)).sum::<Wrapping<_>>().0
}
#[derive(Debug)]
pub struct Interface<P> {
port: P,
send_dummy_bytes: bool,
}
impl<P: Read + Write> Interface<P> {
pub fn new(port: P) -> Self {
Self {
port,
send_dummy_bytes: false,
}
}
pub async fn enable_dummy_bytes(&mut self) -> Result<(), P::Error> {
self.send_dummy_bytes = true;
self.write(&[0x00, 0x00, 0x00, 0x00]).await
}
pub async fn lock(&mut self) -> Result<(), P::Error> {
self.send(Request::new(Command::Lock, 0x0000, 0x00).into())
.await
}
pub async fn query_software_id(&mut self) -> Result<u16, P::Error> {
self.send(Request::new(Command::QuerySoftwareId, 0x0000, 0x02).into())
.await?;
Ok(self.receive().await?.into())
}
pub async fn unlock_read_access(&mut self, key: u16) -> Result<(), P::Error> {
self.send(Request::new(Command::UnlockReadAccess, key, 0x00).into())
.await
}
pub async fn read_memory<L: From<Payload<N>>, const N: usize>(
&mut self,
addr: u16,
) -> Result<L, P::Error> {
let Ok(len) = N.try_into() else {
return Err(Error::InvalidArgument);
};
self.send(Request::new(Command::ReadMemory, addr, len).into())
.await?;
Ok(self.receive().await?.into())
}
pub async fn read_eeprom<L: From<Payload<N>>, const N: usize>(
&mut self,
addr: u16,
) -> Result<L, P::Error> {
let len = match N.try_into() {
Ok(n) if n % 2 == 0 => n,
_ => return Err(Error::InvalidArgument),
};
self.send(Request::new(Command::ReadEeprom, addr, len).into())
.await?;
Ok(self.receive().await?.into())
}
pub async fn unlock_full_access(&mut self, key: u16) -> Result<(), P::Error> {
self.send(Request::new(Command::UnlockFullAccess, key, 0x00).into())
.await
}
pub async fn write_memory<L: Into<Payload<N>>, const N: usize>(
&mut self,
addr: u16,
payload: L,
) -> Result<(), P::Error> {
let Ok(len) = N.try_into() else {
return Err(Error::InvalidArgument);
};
self.send(Request::new(Command::WriteMemory, addr, len).into())
.await?;
self.send(payload.into()).await
}
pub async fn write_eeprom<L: Into<Payload<N>>, const N: usize>(
&mut self,
addr: u16,
payload: L,
) -> Result<(), P::Error> {
let len = match N.try_into() {
Ok(n) if n % 2 == 0 => n,
_ => return Err(Error::InvalidArgument),
};
self.send(Request::new(Command::WriteEeprom, addr, len).into())
.await?;
self.send(payload.into()).await
}
pub async fn jump_to_subroutine(&mut self, addr: u16) -> Result<(), P::Error> {
self.send(Request::new(Command::JumpToSubroutine, addr, 0x00).into())
.await?;
self.read(&mut [0x00]).await
}
pub async fn halt(&mut self) -> Result<(), P::Error> {
self.send(Request::new(Command::Halt, 0x0000, 0x00).into())
.await
}
pub async fn set_baud_rate_2400(&mut self) -> Result<(), P::Error> {
self.send(Request::new(Command::SetBaudRate2400, 0x0000, 0x00).into())
.await
}
pub async fn set_baud_rate_9600(&mut self) -> Result<(), P::Error> {
self.send(Request::new(Command::SetBaudRate9600, 0x0000, 0x00).into())
.await
}
async fn send<const N: usize>(&mut self, payload: Payload<N>) -> Result<(), P::Error> {
for chunk in payload.0.chunks(4) {
let checksum = compute_checksum(chunk);
let mut resp = [0xff];
self.write(chunk).await?;
self.write(&[checksum]).await?;
self.read(&mut resp).await?;
match ResponseCode::from_repr(resp[0]) {
Some(ResponseCode::Success) => Ok(()),
Some(ResponseCode::IncorrectChecksum) => Err(Error::IncorrectChecksum),
Some(ResponseCode::InvalidCommand) => Err(Error::InvalidCommand),
None => Err(Error::UnknownResponseCode),
}?;
if self.send_dummy_bytes {
self.write(&[0x00]).await?;
}
}
Ok(())
}
async fn receive<const N: usize>(&mut self) -> Result<Payload<N>, P::Error> {
let mut payload = Payload([0x00; N]);
for chunk in payload.0.chunks_mut(4) {
let mut checksum = [0x00];
self.read(chunk).await?;
self.read(&mut checksum).await?;
if checksum[0] != compute_checksum(chunk) {
return Err(Error::IncorrectChecksum);
}
if self.send_dummy_bytes {
for _ in 0..=chunk.len() {
self.write(&[0x00]).await?;
}
}
self.write(&[ResponseCode::Success as u8]).await?;
}
Ok(payload)
}
async fn read(&mut self, buf: &mut [u8]) -> Result<(), P::Error> {
self.port.read_exact(buf).await?;
debug!("Read from port: {buf:02x?}");
Ok(())
}
async fn write(&mut self, buf: &[u8]) -> Result<(), P::Error> {
debug!("Write to port: {buf:02x?}");
self.port.write_all(buf).await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::collections::vec_deque::VecDeque;
use core::convert::Infallible;
pub fn init_logger() {
let _ = env_logger::builder()
.filter_level(log::LevelFilter::max())
.is_test(true)
.try_init();
}
#[tokio::test]
async fn enable_dummy_bytes() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00, 0xa3, 0x01, 0xa4, 0x00, 0x11, 0x22, 0x33]);
let mut intf = Interface::new(&mut deque);
let id = intf.query_software_id().await?;
intf.enable_dummy_bytes().await?;
let data: [u8; 2] = intf.read_memory(0xabcd).await?;
assert_eq!(id, 419, "software ID should be correct");
assert_eq!(
deque,
[
0x11, 0x00, 0x00, 0x02, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xcd, 0xab, 0x02,
0xaa, 0x00, 0x00, 0x00, 0x00, 0x00
],
"deque contents should be correct"
);
assert_eq!(data, [0x11, 0x22], "memory contents should be correct");
Ok(())
}
#[tokio::test]
async fn lock() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00]);
let mut intf = Interface::new(&mut deque);
intf.lock().await?;
assert_eq!(
deque,
[0x10, 0x00, 0x00, 0x00, 0x10],
"deque contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn query_software_id() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00, 0x75, 0x02, 0x77]);
let mut intf = Interface::new(&mut deque);
let id = intf.query_software_id().await?;
assert_eq!(id, 629, "software ID should be correct");
assert_eq!(
deque,
[0x11, 0x00, 0x00, 0x02, 0x13, 0x00],
"deque contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn unlock_read_access() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00]);
let mut intf = Interface::new(&mut deque);
intf.unlock_read_access(0xabcd).await?;
assert_eq!(
deque,
[0x20, 0xcd, 0xab, 0x00, 0x98],
"deque contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn read_memory() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([
0x00, 0x11, 0x22, 0x33, 0x44, 0xaa, 0xab, 0xcd, 0xef, 0x99, 0x00, 0xde, 0xad, 0x8b,
]);
let mut intf = Interface::new(&mut deque);
let data: [u8; 10] = intf.read_memory(0xabcd).await?;
assert_eq!(
deque,
[0x30, 0xcd, 0xab, 0x0a, 0xb2, 0x00, 0x00, 0x00],
"deque contents should be correct"
);
assert_eq!(
data,
[0x11, 0x22, 0x33, 0x44, 0xab, 0xcd, 0xef, 0x99, 0xde, 0xad],
"memory contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn read_eeprom() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([
0x00, 0x11, 0x22, 0x33, 0x44, 0xaa, 0xab, 0xcd, 0xef, 0x99, 0x00, 0xde, 0xad, 0x8b,
]);
let mut intf = Interface::new(&mut deque);
let data: [u8; 10] = intf.read_eeprom(0xabcd).await?;
assert_eq!(
deque,
[0x31, 0xcd, 0xab, 0x0a, 0xb3, 0x00, 0x00, 0x00],
"deque contents should be correct"
);
assert_eq!(
data,
[0x11, 0x22, 0x33, 0x44, 0xab, 0xcd, 0xef, 0x99, 0xde, 0xad],
"EEPROM contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn unlock_full_access() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00]);
let mut intf = Interface::new(&mut deque);
intf.unlock_full_access(0xabcd).await?;
assert_eq!(
deque,
[0x32, 0xcd, 0xab, 0x00, 0xaa],
"deque contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn write_memory() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00, 0x00, 0x00, 0x00]);
let mut intf = Interface::new(&mut deque);
intf.write_memory(
0xabcd,
[0x11, 0x22, 0x33, 0x44, 0xab, 0xcd, 0xef, 0x99, 0xde, 0xad],
)
.await?;
assert_eq!(
deque,
[
0x40, 0xcd, 0xab, 0x0a, 0xc2, 0x11, 0x22, 0x33, 0x44, 0xaa, 0xab, 0xcd, 0xef, 0x99,
0x00, 0xde, 0xad, 0x8b
],
"deque contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn write_eeprom() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00, 0x00, 0x00, 0x00]);
let mut intf = Interface::new(&mut deque);
intf.write_eeprom(
0xabcd,
[0x11, 0x22, 0x33, 0x44, 0xab, 0xcd, 0xef, 0x99, 0xde, 0xad],
)
.await?;
assert_eq!(
deque,
[
0x41, 0xcd, 0xab, 0x0a, 0xc3, 0x11, 0x22, 0x33, 0x44, 0xaa, 0xab, 0xcd, 0xef, 0x99,
0x00, 0xde, 0xad, 0x8b
],
"deque contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn jump_to_subroutine() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00, 0x00]);
let mut intf = Interface::new(&mut deque);
intf.jump_to_subroutine(0xabcd).await?;
assert_eq!(
deque,
[0x42, 0xcd, 0xab, 0x00, 0xba],
"deque contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn halt() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00]);
let mut intf = Interface::new(&mut deque);
intf.halt().await?;
assert_eq!(
deque,
[0x45, 0x00, 0x00, 0x00, 0x45],
"deque contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn set_baud_rate_2400() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00]);
let mut intf = Interface::new(&mut deque);
intf.set_baud_rate_2400().await?;
assert_eq!(
deque,
[0x46, 0x00, 0x00, 0x00, 0x46],
"deque contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn set_baud_rate_9600() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00]);
let mut intf = Interface::new(&mut deque);
intf.set_baud_rate_9600().await?;
assert_eq!(
deque,
[0x47, 0x00, 0x00, 0x00, 0x47],
"deque contents should be correct"
);
Ok(())
}
#[tokio::test]
async fn error_invalid_argument() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([]);
let mut intf = Interface::new(&mut deque);
let res: Result<[u8; 256], _> = intf.read_memory(0xabcd).await;
assert_eq!(
res.unwrap_err(),
Error::InvalidArgument,
"result should be invalid argument error"
);
let res: Result<u8, _> = intf.read_eeprom(0xabcd).await;
assert_eq!(
res.unwrap_err(),
Error::InvalidArgument,
"result should be invalid argument error"
);
let res = intf.write_memory(0xabcd, [0x00; 256]).await;
assert_eq!(
res.unwrap_err(),
Error::InvalidArgument,
"result should be invalid argument error"
);
let res = intf.write_eeprom(0xabcd, 0x11u8).await;
assert_eq!(
res.unwrap_err(),
Error::InvalidArgument,
"result should be invalid argument error"
);
Ok(())
}
#[tokio::test]
async fn error_incorrect_checksum() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x01, 0x00, 0x11, 0xff]);
let mut intf = Interface::new(&mut deque);
let res = intf.lock().await;
assert_eq!(
res.unwrap_err(),
Error::IncorrectChecksum,
"result should be incorrect checksum error"
);
let res: Result<u8, _> = intf.read_memory(0xabcd).await;
assert_eq!(
res.unwrap_err(),
Error::IncorrectChecksum,
"result should be incorrect checksum error"
);
Ok(())
}
#[tokio::test]
async fn error_invalid_command() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x02]);
let mut intf = Interface::new(&mut deque);
let res = intf.lock().await;
assert_eq!(
res.unwrap_err(),
Error::InvalidCommand,
"result should be invalid command error"
);
Ok(())
}
#[tokio::test]
async fn error_unknown_response_code() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0xff]);
let mut intf = Interface::new(&mut deque);
let res = intf.halt().await;
assert_eq!(
res.unwrap_err(),
Error::UnknownResponseCode,
"result should be unknown response code error"
);
Ok(())
}
#[tokio::test]
async fn error_unexpected_eof() -> Result<(), Infallible> {
init_logger();
let mut deque = VecDeque::from([0x00]);
let mut intf = Interface::new(&mut deque);
let res: Result<[u8; 5], _> = intf.read_memory(0xabcd).await;
assert_eq!(
res.unwrap_err(),
Error::UnexpectedEof,
"result should be unexpected end-of-file error"
);
Ok(())
}
}