use core::ops::Deref;
use crate::{
error::{EepromError, Error},
fmt,
};
use embedded_io_async::{ErrorType, ReadExactError};
pub mod device_provider;
pub mod types;
#[cfg(feature = "std")]
pub mod file_provider;
pub const STATION_ALIAS_POSITION: core::ops::Range<usize> = 8..10;
pub const CHECKSUM_POSITION: core::ops::Range<usize> = 14..16;
const ECAT_CRC_ALGORITHM: crc::Algorithm<u8> = crc::Algorithm {
width: 8,
poly: 0x07,
init: 0xff,
refin: false,
refout: false,
xorout: 0x00,
check: 0x80,
residue: 0x00,
};
pub const STATION_ALIAS_CRC: crc::Crc<u8> = crc::Crc::<u8>::new(&ECAT_CRC_ALGORITHM);
pub trait EepromDataProvider: Clone {
async fn read_chunk(&mut self, start_word: u16) -> Result<impl Deref<Target = [u8]>, Error>;
async fn write_word(&mut self, start_word: u16, data: [u8; 2]) -> Result<(), Error>;
async fn clear_errors(&self) -> Result<(), Error>;
}
impl embedded_io_async::Error for Error {
fn kind(&self) -> embedded_io_async::ErrorKind {
embedded_io_async::ErrorKind::Other
}
}
impl From<ReadExactError<Error>> for Error {
fn from(value: ReadExactError<Error>) -> Self {
match value {
ReadExactError::UnexpectedEof => Error::Eeprom(EepromError::SectionOverrun),
ReadExactError::Other(e) => e,
}
}
}
#[derive(Debug)]
pub struct EepromRange<P> {
reader: P,
byte_pos: u16,
end: u16,
}
impl<P> EepromRange<P>
where
P: EepromDataProvider,
{
pub fn new(reader: P, start_word: u16, len_words: u16) -> Self {
Self {
reader,
byte_pos: start_word * 2,
end: start_word * 2 + len_words * 2,
}
}
pub fn skip_ahead_bytes(&mut self, skip: u16) -> Result<(), EepromError> {
fmt::trace!(
"Skip EEPROM from pos {:#06x} by {} bytes to {:#06x}",
self.byte_pos,
skip,
self.byte_pos + skip,
);
if self.byte_pos + skip >= self.end {
return Err(EepromError::SectionOverrun);
}
self.byte_pos += skip;
Ok(())
}
pub async fn read_byte(&mut self) -> Result<u8, Error> {
self.reader.clear_errors().await?;
let res = self.reader.read_chunk(self.byte_pos / 2).await?;
let skip = usize::from(self.byte_pos % 2);
self.byte_pos += 1;
res.get(skip).copied().ok_or(Error::Internal)
}
#[allow(unused)]
pub(crate) fn into_inner(self) -> P {
self.reader
}
}
impl<P> embedded_io_async::Read for EepromRange<P>
where
P: EepromDataProvider,
{
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
fmt::trace!("Read EEPROM chunk from byte {:#06x}", self.byte_pos);
let requested_read_len = buf.len();
let max_read = usize::from(self.end.saturating_sub(self.byte_pos));
let mut bytes_read = 0;
if max_read == 0 {
return Ok(0);
}
let mut buf = buf
.get_mut(0..requested_read_len.min(max_read))
.ok_or(Error::Internal)?;
self.reader.clear_errors().await?;
while !buf.is_empty() {
let res = self.reader.read_chunk(self.byte_pos / 2).await?;
let chunk = &*res;
let skip = usize::from(self.byte_pos % 2);
let chunk = chunk.get(skip..).ok_or(Error::Internal)?;
if buf.len() < chunk.len() {
let (chunk, _rest) = chunk.split_at(buf.len());
bytes_read += chunk.len();
self.byte_pos += chunk.len() as u16;
buf.copy_from_slice(chunk);
break;
}
bytes_read += chunk.len();
self.byte_pos += chunk.len() as u16;
let (buf_start, buf_rest) = buf.split_at_mut(chunk.len());
buf_start.copy_from_slice(chunk);
fmt::trace!("--> Buf for next iter {}", buf_rest.len());
buf = buf_rest;
}
fmt::trace!(
"--> Done. Read {} of requested {} B, pos is now {:#06x}",
bytes_read,
requested_read_len,
self.byte_pos
);
Ok(bytes_read)
}
}
impl<P> embedded_io_async::Write for EepromRange<P>
where
P: EepromDataProvider,
{
async fn write(&mut self, mut buf: &[u8]) -> Result<usize, Self::Error> {
fmt::trace!(
"Write EEPROM word from byte position {:#06x}",
self.byte_pos
);
let len = buf.len();
let mut written = 0;
loop {
if self.end.saturating_sub(self.byte_pos) == 0 {
break;
}
let Some((word, rest)) = buf
.split_first_chunk::<2>()
.map(|(word, rest)| (*word, rest))
.or_else(|| {
buf.split_first()
.map(|(first, rest)| ([*first, 0x00], rest))
})
else {
break;
};
self.reader.write_word(self.byte_pos / 2, word).await?;
written += word.len();
self.byte_pos += word.len() as u16;
buf = rest;
}
fmt::trace!(
"--> Done. Wrote {} of requested {} B, position is now {:#06x}",
written,
len,
self.byte_pos
);
Ok(written)
}
}
impl<P> ErrorType for EepromRange<P> {
type Error = Error;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eeprom::file_provider::EepromFile;
use embedded_io_async::{Read, Write};
#[tokio::test]
async fn skip_past_end() {
crate::test_logger();
let mut r = EepromRange::new(
EepromFile::new(include_bytes!("../../dumps/eeprom/akd.hex")),
0,
32,
);
assert_eq!(r.skip_ahead_bytes(63), Ok(()), "63 bytes");
let mut r = EepromRange::new(
EepromFile::new(include_bytes!("../../dumps/eeprom/akd.hex")),
0,
32,
);
assert_eq!(
r.skip_ahead_bytes(64),
Err(EepromError::SectionOverrun),
"64 bytes"
);
let mut r = EepromRange::new(
EepromFile::new(include_bytes!("../../dumps/eeprom/akd.hex")),
0,
32,
);
assert_eq!(
r.skip_ahead_bytes(65),
Err(EepromError::SectionOverrun),
"65 bytes"
);
let mut r = EepromRange::new(
EepromFile::new(include_bytes!("../../dumps/eeprom/akd.hex")),
0,
32,
);
assert_eq!(
r.skip_ahead_bytes(10000),
Err(EepromError::SectionOverrun),
"10000 bytes"
);
}
#[tokio::test]
async fn read_single_bytes() {
crate::test_logger();
let mut r = EepromRange::new(
EepromFile::new(include_bytes!("../../dumps/eeprom/el2828.hex")),
0,
32,
);
let expected = [
0x04u8, 0x01, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2, 0x00, ];
let actual = vec![
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
r.read_byte().await.unwrap(),
];
assert_eq!(
expected,
actual.as_slice(),
"Expected:\n{:#04x?}\n\nActual: \n{:#04x?}",
expected,
actual
);
}
#[tokio::test]
async fn read_checksum_el2828() {
let mut r = EepromRange::new(
EepromFile::new(include_bytes!("../../dumps/eeprom/el2828.hex")),
0,
8,
);
let mut all = [0u8; 16];
r.read_exact(&mut all).await.expect("Read");
let (rest, checksum) = all.split_last_chunk::<2>().unwrap();
assert_eq!(rest.len(), 14);
let checksum = u16::from_le_bytes(*checksum);
let expected_checksum = 0x00e2u16;
assert_eq!(checksum, expected_checksum);
const ECAT_CRC: crc::Algorithm<u8> = crc::Algorithm {
width: 8,
poly: 0x07,
init: 0xff,
refin: false,
refout: false,
xorout: 0x00,
check: 0x80,
residue: 0x00,
};
const EEPROM_CRC: crc::Crc<u8> = crc::Crc::<u8>::new(&ECAT_CRC);
let cs = u16::from(EEPROM_CRC.checksum(rest));
assert_eq!(
cs, expected_checksum,
"{:#04x} {:#04x}",
cs, expected_checksum
);
}
#[tokio::test]
async fn read_checksum_akd() {
let mut r = EepromRange::new(
EepromFile::new(include_bytes!("../../dumps/eeprom/akd.hex")),
0,
8,
);
let mut all = [0u8; 16];
r.read_exact(&mut all).await.expect("Read");
let (rest, checksum) = all.split_last_chunk::<2>().unwrap();
assert_eq!(rest.len(), 14);
let checksum = u16::from_le_bytes(*checksum);
let expected_checksum = 0x0010u16;
assert_eq!(checksum, expected_checksum);
const ECAT_CRC: crc::Algorithm<u8> = crc::Algorithm {
width: 8,
poly: 0x07,
init: 0xff,
refin: false,
refout: false,
xorout: 0x00,
check: 0x80,
residue: 0x00,
};
const EEPROM_CRC: crc::Crc<u8> = crc::Crc::<u8>::new(&ECAT_CRC);
let cs = u16::from(EEPROM_CRC.checksum(rest));
assert_eq!(
cs, expected_checksum,
"{:#04x} {:#04x}",
cs, expected_checksum
);
}
#[tokio::test]
async fn write_station_alias() {
let mut r = EepromRange::new(
EepromFile::new(include_bytes!("../../dumps/eeprom/akd.hex")),
0,
8,
);
let mut all = [0u8; 16];
r.read_exact(&mut all).await.expect("Read");
let existing_alias = u16::from_le_bytes(all[STATION_ALIAS_POSITION].try_into().unwrap());
let new_alias = 0xabcd_u16;
assert_eq!(existing_alias, 0x0000);
assert_ne!(new_alias, existing_alias);
all[STATION_ALIAS_POSITION].copy_from_slice(&new_alias.to_le_bytes());
let checksum = u16::from(STATION_ALIAS_CRC.checksum(&all[0..CHECKSUM_POSITION.start]));
all[CHECKSUM_POSITION].copy_from_slice(&checksum.to_le_bytes());
let expected = [
0x09, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0xcd, 0xab, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, ];
assert_eq!(all, expected);
let mut w = EepromRange::new(
EepromFile::new(include_bytes!("../../dumps/eeprom/akd.hex")),
0,
8,
);
w.write_all(&all).await.expect("Write failed");
assert_eq!(w.into_inner().write_cache[0..16], expected);
}
}