#![feature(type_changing_struct_update)]
#![cfg_attr(not(test), no_std)]
use core::convert::Infallible;
use core::marker::PhantomData;
#[allow(unused_imports)]
use defmt::{debug, error, info, trace, warn};
use embedded_hal::blocking::delay::DelayMs;
use embedded_hal::blocking::i2c::{Read, SevenBitAddress, Write};
use heapless::{String, Vec};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub mod card;
pub mod dfu;
pub mod hub;
pub mod note;
pub mod web;
pub mod ntn;
const RESPONSE_DELAY: u16 = 25;
pub const DEFAULT_BUF_SIZE: usize = 18 * 1024;
#[derive(Debug, defmt::Format)]
pub struct NotecardConfig {
pub i2c_addr: u8,
pub response_timeout: u16,
pub chunk_delay: u16,
pub segment_delay: u16,
}
impl Default for NotecardConfig {
fn default() -> Self {
NotecardConfig {
i2c_addr: 0x17,
response_timeout: 5000,
chunk_delay: 20,
segment_delay: 250,
}
}
}
#[derive(Debug, defmt::Format)]
pub enum NoteState {
Handshake,
Request,
Poll(usize),
Response(usize),
ResponseReady,
}
#[derive(Debug, defmt::Format, Clone)]
pub enum NoteError {
I2cWriteError,
I2cReadError,
DeserError(String<256>),
SerError,
InvalidRequest,
RemainingData,
TimeOut,
BufOverflow,
WrongState,
DFUInProgress,
NonPortNoteInPackageMode,
FileStorageFull(String<256>),
ErrorAddingNote(String<256>),
NotecardErr(String<256>),
}
impl NoteError {
pub fn new_desererror(msg: &[u8]) -> NoteError {
let msg = core::str::from_utf8(msg).unwrap_or("[invalid utf-8]");
let mut s = String::new();
s.push_str(msg).ok();
NoteError::DeserError(s)
}
pub fn string_err(_e: Infallible) -> NoteError {
NoteError::BufOverflow
}
}
pub(crate) fn str_string<const N: usize>(
a: Option<&str>,
) -> Result<Option<heapless::String<N>>, NoteError> {
a.map(heapless::String::try_from)
.transpose()
.map_err(NoteError::string_err)
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct NotecardError {
err: String<256>,
}
impl From<NotecardError> for NoteError {
fn from(n: NotecardError) -> NoteError {
if n.err.contains("{dfu-in-progress}") {
NoteError::DFUInProgress
} else if n.err.contains("adding notes to a non-uplinked port is not allowed") {
NoteError::NonPortNoteInPackageMode
} else if n.err.contains("{file-storage-full}") {
NoteError::FileStorageFull(n.err)
} else if n.err.contains("error adding note") {
NoteError::ErrorAddingNote(n.err)
} else {
NoteError::NotecardErr(n.err)
}
}
}
pub struct Notecard<
IOM: Write<SevenBitAddress> + Read<SevenBitAddress>,
const BUF_SIZE: usize = DEFAULT_BUF_SIZE,
> {
i2c: IOM,
addr: u8,
state: NoteState,
buf: Vec<u8, BUF_SIZE>,
response_timeout: u16,
chunk_delay: u16,
segment_delay: u16,
}
pub struct SuspendState<const BUF_SIZE: usize> {
addr: u8,
state: NoteState,
buf: Vec<u8, BUF_SIZE>,
response_timeout: u16,
chunk_delay: u16,
segment_delay: u16,
}
impl<IOM: Write<SevenBitAddress> + Read<SevenBitAddress>, const BUF_SIZE: usize>
Notecard<IOM, BUF_SIZE>
{
pub fn new(i2c: IOM) -> Notecard<IOM, BUF_SIZE> {
Self::new_with_config(i2c, NotecardConfig::default())
}
pub fn new_with_config(i2c: IOM, c: NotecardConfig) -> Notecard<IOM, BUF_SIZE> {
Notecard {
i2c,
addr: c.i2c_addr,
state: NoteState::Handshake,
buf: Vec::new(),
response_timeout: c.response_timeout,
chunk_delay: c.chunk_delay,
segment_delay: c.segment_delay,
}
}
pub fn resize_buf<const B: usize>(self) -> Result<Notecard<IOM, B>, NoteError> {
if B < self.buf.len() {
Err(NoteError::BufOverflow)
} else {
Ok(Notecard {
buf: Vec::<_, B>::from_slice(&self.buf).unwrap(),
..self
})
}
}
pub fn suspend(self) -> (IOM, SuspendState<BUF_SIZE>) {
(
self.i2c,
SuspendState {
state: self.state,
buf: self.buf,
addr: self.addr,
response_timeout: self.response_timeout,
chunk_delay: self.chunk_delay,
segment_delay: self.segment_delay,
},
)
}
pub fn resume(i2c: IOM, state: SuspendState<BUF_SIZE>) -> Notecard<IOM, BUF_SIZE> {
Notecard {
i2c,
addr: state.addr,
state: state.state,
buf: state.buf,
response_timeout: state.response_timeout,
chunk_delay: state.chunk_delay,
segment_delay: state.segment_delay,
}
}
pub fn initialize(&mut self, delay: &mut impl DelayMs<u16>) -> Result<(), NoteError> {
info!("note: initializing.");
self.reset(delay)
}
pub fn ping(&mut self) -> bool {
self.i2c.write(self.addr, &[]).is_ok()
}
pub fn data_query(&mut self) -> Result<usize, NoteError> {
trace!("note: data_query: {:?}", self.state);
if !matches!(self.state, NoteState::Response(_)) {
self.i2c
.write(self.addr, &[0, 0])
.map_err(|_| NoteError::I2cWriteError)?;
let mut buf = [0u8; 2];
self.i2c
.read(self.addr, &mut buf)
.map_err(|_| NoteError::I2cReadError)?;
let available = buf[0] as usize;
let sent = buf[1] as usize;
if available > 0 {
self.buf.clear();
self.state = NoteState::Response(available);
}
trace!("avail = {}, sent = {}", available, sent);
if sent > 0 {
error!(
"data query: bytes sent when querying available bytes: {}",
sent
);
Err(NoteError::RemainingData)
} else {
Ok(available)
}
} else {
error!("note: data_query called while reading response.");
Err(NoteError::WrongState)
}
}
fn read(&mut self) -> Result<usize, NoteError> {
if let NoteState::Response(avail) = self.state {
let mut bytes = Vec::<u8, 128>::new();
let sz = (bytes.capacity() - 2).min(avail);
bytes.resize(sz + 2, 0).unwrap();
debug!("asking to read: {} of available {} bytes", sz, avail);
self.i2c
.write(self.addr, &[0, sz as u8])
.map_err(|_| NoteError::I2cWriteError)?;
self.i2c
.read(self.addr, &mut bytes)
.map_err(|_| NoteError::I2cReadError)?;
let available = bytes[0] as usize;
let sent = bytes[1] as usize;
self.buf.extend_from_slice(&bytes[2..]).unwrap();
trace!("read: {}", unsafe {
core::str::from_utf8_unchecked(&bytes)
});
trace!("avail = {}, sent = {}", available, sent);
if available > 0 {
self.state = NoteState::Response(available);
} else {
self.state = NoteState::ResponseReady;
}
Ok(available)
} else {
error!("read: called when not waiting for response");
Err(NoteError::WrongState)
}
}
fn take_response(&mut self) -> Result<&[u8], NoteError> {
if matches!(self.state, NoteState::ResponseReady) {
self.state = NoteState::Request;
Ok(&self.buf)
} else {
error!("take response called when response not ready");
Err(NoteError::WrongState)
}
}
fn poll(&mut self) -> Result<Option<&[u8]>, NoteError> {
trace!("note: poll: {:?}", self.state);
match self.state {
NoteState::Poll(_) => {
let sz = self.data_query()?;
if sz > 0 {
debug!("response ready: {} bytes..", sz);
self.poll()
} else {
Ok(None)
}
}
NoteState::Response(_) => {
let avail = self.read()?;
if avail == 0 {
self.poll()
} else {
Ok(None)
}
}
NoteState::ResponseReady => {
debug!("response read, deserializing.");
Ok(Some(self.take_response()?))
}
_ => {
error!("poll called when not receiving response");
Err(NoteError::WrongState)
}
}
}
unsafe fn consume_response(&mut self, delay: &mut impl DelayMs<u16>) -> Result<(), NoteError> {
warn!("note: trying to consume any left-over response.");
let mut waited = 0;
while waited < self.response_timeout {
if (self.poll()?).is_some() {
self.buf.clear();
return Ok(());
}
delay.delay_ms(RESPONSE_DELAY);
waited += RESPONSE_DELAY;
}
self.buf.clear();
error!("response timed out (>= {}).", self.response_timeout);
Err(NoteError::TimeOut)
}
pub fn reset(&mut self, delay: &mut impl DelayMs<u16>) -> Result<(), NoteError> {
warn!("resetting: consuming any left-over response and perform a new handshake.");
self.buf.clear(); self.state = NoteState::Handshake;
self.handshake(delay)
}
fn handshake(&mut self, delay: &mut impl DelayMs<u16>) -> Result<(), NoteError> {
if matches!(self.state, NoteState::Handshake) {
debug!("note: handshake");
if self.data_query()? > 0 {
error!("note: handshake: remaining data in queue, consuming..");
unsafe { self.consume_response(delay)? };
}
self.state = NoteState::Request;
}
Ok(())
}
fn send_request(&mut self, delay: &mut impl DelayMs<u16>) -> Result<(), NoteError> {
const CHUNK_LENGTH_MAX: usize = 127;
const CHUNK_LENGTH_I: usize = 30;
const CHUNK_LENGTH: usize = if CHUNK_LENGTH_I < CHUNK_LENGTH_MAX {
CHUNK_LENGTH_I
} else {
CHUNK_LENGTH_MAX
};
const SEGMENT_LENGTH: usize = (250 / CHUNK_LENGTH) * CHUNK_LENGTH;
if !matches!(self.state, NoteState::Request) {
warn!("note: request: wrong-state, resetting before new request.");
let buf = self.buf.clone();
self.reset(delay)?;
self.buf.clear();
self.buf
.resize(buf.len(), 0)
.map_err(|_| NoteError::BufOverflow)?;
self.buf.copy_from_slice(&buf);
}
if self.buf.last() != Some(&b'\n') {
return Err(NoteError::InvalidRequest);
}
trace!("note: making request: {}", unsafe {
core::str::from_utf8_unchecked(&self.buf)
});
let mut buf = Vec::<u8, { CHUNK_LENGTH + 1 }>::new();
for segment in self.buf.chunks(SEGMENT_LENGTH) {
for c in segment.chunks(buf.capacity() - 1) {
buf.push(c.len() as u8).unwrap();
buf.extend_from_slice(c).unwrap();
trace!("note: sending chunk: {} => {}", &buf, unsafe {
core::str::from_utf8_unchecked(&buf)
});
self.i2c
.write(self.addr, &buf)
.map_err(|_| NoteError::I2cWriteError)?;
buf.clear();
delay.delay_ms(self.chunk_delay);
}
delay.delay_ms(self.segment_delay);
}
self.state = NoteState::Poll(0);
Ok(())
}
pub(crate) fn request_raw(
&mut self,
delay: &mut impl DelayMs<u16>,
cmd: &[u8],
) -> Result<(), NoteError> {
self.buf.clear();
self.buf
.resize(cmd.len(), 0)
.map_err(|_| NoteError::BufOverflow)?;
self.buf.copy_from_slice(cmd);
self.send_request(delay)
}
pub(crate) fn request<T: Serialize>(
&mut self,
delay: &mut impl DelayMs<u16>,
cmd: T,
) -> Result<(), NoteError> {
self.buf.clear();
self.buf.resize(self.buf.capacity(), 0).unwrap();
let sz = serde_json_core::to_slice(&cmd, &mut self.buf).map_err(|_| NoteError::SerError)?;
self.buf.truncate(sz);
self.buf.push(b'\n').map_err(|_| NoteError::SerError)?;
self.send_request(delay)
}
pub fn card(&mut self) -> card::Card<'_, IOM, BUF_SIZE> {
card::Card::from(self)
}
pub fn note(&mut self) -> note::Note<'_, IOM, BUF_SIZE> {
note::Note::from(self)
}
pub fn web(&mut self) -> web::Web<'_, IOM, BUF_SIZE> {
web::Web::from(self)
}
pub fn hub(&mut self) -> hub::Hub<'_, IOM, BUF_SIZE> {
hub::Hub::from(self)
}
pub fn dfu(&mut self) -> dfu::DFU<'_, IOM, BUF_SIZE> {
dfu::DFU::from(self)
}
pub fn ntn(&mut self) -> ntn::NTN<'_, IOM, BUF_SIZE> {
ntn::NTN::from(self)
}
}
#[must_use = "The response must be waited for and consumed, otherwise the notecard is left in an inconsistent state"]
pub struct FutureResponse<
'a,
T: DeserializeOwned,
IOM: Write<SevenBitAddress> + Read<SevenBitAddress>,
const BUF_SIZE: usize,
> {
note: &'a mut Notecard<IOM, BUF_SIZE>,
_r: PhantomData<T>,
}
impl<
'a,
T: DeserializeOwned,
IOM: Write<SevenBitAddress> + Read<SevenBitAddress>,
const BUF_SIZE: usize,
> FutureResponse<'a, T, IOM, BUF_SIZE>
{
fn from(note: &'a mut Notecard<IOM, BUF_SIZE>) -> FutureResponse<'a, T, IOM, BUF_SIZE> {
FutureResponse {
note,
_r: PhantomData,
}
}
pub fn poll(&mut self) -> Result<Option<T>, NoteError> {
match self.note.poll()? {
Some(body) if body.starts_with(br##"{"err":"##) => {
debug!(
"response is error response, parsing error..: {}",
core::str::from_utf8(body).unwrap_or("[invalid utf-8]")
);
Err(
serde_json_core::from_slice::<NotecardError>(body).map_or_else(
|_| {
error!(
"failed to deserialize: {}",
core::str::from_utf8(body).unwrap_or("[invalid utf-8]")
);
NoteError::new_desererror(body)
},
|(e, _)| NoteError::from(e),
),
)
}
Some(body) => {
trace!("response is regular, parsing..");
Ok(Some(
serde_json_core::from_slice::<T>(body)
.map_err(|_| {
error!(
"failed to deserialize: {}",
core::str::from_utf8(body).unwrap_or("[invalid utf-8]")
);
NoteError::new_desererror(body)
})?
.0,
))
}
None => Ok(None),
}
}
pub fn wait_raw(mut self, delay: &mut impl DelayMs<u16>) -> Result<&'a [u8], NoteError> {
let mut waited = 0;
while waited < self.note.response_timeout {
if self.poll()?.is_some() {
return self.note.take_response()
}
delay.delay_ms(RESPONSE_DELAY);
waited += RESPONSE_DELAY;
}
error!("response timed out (>= {}).", self.note.response_timeout);
Err(NoteError::TimeOut)
}
pub fn wait(mut self, delay: &mut impl DelayMs<u16>) -> Result<T, NoteError> {
let mut waited = 0;
while waited < self.note.response_timeout {
if let Some(r) = self.poll()? {
return Ok(r)
}
delay.delay_ms(RESPONSE_DELAY);
waited += RESPONSE_DELAY;
}
error!("response timed out (>= {}).", self.note.response_timeout);
Err(NoteError::TimeOut)
}
}
#[cfg(test)]
mod tests {
use super::*;
use embedded_hal_mock::eh0::delay::StdSleep;
use embedded_hal_mock::eh0::i2c::{Mock, Transaction};
pub fn new_mock() -> Notecard<Mock> {
let i2c = Mock::new(&[]);
Notecard::new(i2c)
}
#[test]
fn resize_buf() {
let c = new_mock();
assert_eq!(c.buf.capacity(), DEFAULT_BUF_SIZE);
let mut c = c.resize_buf::<1024>().unwrap();
assert_eq!(c.buf.capacity(), 1024);
c.i2c.done();
}
#[test]
fn raw_request() {
let mut expect = b"{\"req\":\"card.location\"}\n".to_vec();
expect.insert(0, 24);
let exp = [
Transaction::write(0x17, vec![0, 0]),
Transaction::read(0x17, vec![0, 0]),
Transaction::write(0x17, expect),
];
let i2c = Mock::new(&exp);
let mut c: Notecard<Mock> = Notecard::new(i2c);
let mut delay = StdSleep::new();
c.request_raw(&mut delay, b"{\"req\":\"card.location\"}\n")
.unwrap();
c.i2c.done();
}
}