blues_notecard/
lib.rs

1//! Protocol for transmitting: <https://dev.blues.io/notecard/notecard-guides/serial-over-i2c-protocol/>
2//! API: <https://dev.blues.io/reference/notecard-api/introduction/>
3//!
4#![feature(type_changing_struct_update)]
5#![cfg_attr(not(test), no_std)]
6
7use core::convert::Infallible;
8use core::marker::PhantomData;
9
10#[allow(unused_imports)]
11use defmt::{debug, error, info, trace, warn};
12
13use embedded_hal::blocking::delay::DelayMs;
14use embedded_hal::blocking::i2c::{Read, SevenBitAddress, Write};
15use heapless::{String, Vec};
16use serde::{de::DeserializeOwned, Deserialize, Serialize};
17
18pub mod card;
19pub mod dfu;
20pub mod hub;
21pub mod note;
22pub mod web;
23pub mod ntn;
24
25/// Delay between polling for new response.
26const RESPONSE_DELAY: u16 = 25;
27
28/// The size of the shared request and receive buffer. Requests and responses may not serialize to
29/// any greater value than this.
30pub const DEFAULT_BUF_SIZE: usize = 18 * 1024;
31
32#[derive(Debug, defmt::Format)]
33pub struct NotecardConfig {
34    /// I2C address of Notecard.
35    pub i2c_addr: u8,
36
37    /// Timeout while waiting for response (ms).
38    pub response_timeout: u16,
39
40    /// Delay between chunks when transmitting (ms).
41    ///
42    /// See note on `segment_delay`.
43    ///
44    /// > `note-c`: https://github.com/blues/note-c/blob/master/n_lib.h#L52
45    /// > Original: 20 ms
46    pub chunk_delay: u16,
47
48    /// Delay between segments when transmitting (ms).
49    ///
50    /// > These delay may be almost eliminated for Notecard firmware version 3.4 (and presumably
51    /// > above).
52    ///
53    /// > `note-c`: https://github.com/blues/note-c/blob/master/n_lib.h#L46
54    /// > Original: 250 ms.
55    pub segment_delay: u16,
56}
57
58impl Default for NotecardConfig {
59    fn default() -> Self {
60        NotecardConfig {
61            i2c_addr: 0x17,
62            response_timeout: 5000,
63            chunk_delay: 20,
64            segment_delay: 250,
65        }
66    }
67}
68
69#[derive(Debug, defmt::Format)]
70pub enum NoteState {
71    /// Perform handshake with Notecard.
72    Handshake,
73
74    /// Ready to make request.
75    Request,
76
77    /// Waiting for response to become ready, value is tries made.
78    Poll(usize),
79
80    /// Reading response, value is remaining bytes.
81    Response(usize),
82
83    /// Full response has been read into `buf`.
84    ResponseReady,
85}
86
87#[derive(Debug, defmt::Format, Clone)]
88pub enum NoteError {
89    I2cWriteError,
90
91    I2cReadError,
92
93    DeserError(String<256>),
94
95    SerError,
96
97    /// Request does not end with '\n'.
98    InvalidRequest,
99
100    RemainingData,
101
102    TimeOut,
103
104    BufOverflow,
105
106    /// Method called when notecarrier is in invalid state.
107    WrongState,
108
109    /// Notecard firmware is being updated.
110    DFUInProgress,
111
112    /// Notecard is in NTN mode and regular packages can't be added.
113    NonPortNoteInPackageMode,
114
115    /// Notecard filesystem full
116    FileStorageFull(String<256>),
117
118    /// Error Adding Note
119    ErrorAddingNote(String<256>),
120
121    NotecardErr(String<256>),
122}
123
124impl NoteError {
125    pub fn new_desererror(msg: &[u8]) -> NoteError {
126        let msg = core::str::from_utf8(msg).unwrap_or("[invalid utf-8]");
127        let mut s = String::new();
128        s.push_str(msg).ok();
129        NoteError::DeserError(s)
130    }
131
132    pub fn string_err(_e: Infallible) -> NoteError {
133        NoteError::BufOverflow
134    }
135}
136
137pub(crate) fn str_string<const N: usize>(
138    a: Option<&str>,
139) -> Result<Option<heapless::String<N>>, NoteError> {
140    a.map(heapless::String::try_from)
141        .transpose()
142        .map_err(NoteError::string_err)
143}
144
145#[derive(Deserialize, Debug, defmt::Format)]
146pub struct NotecardError {
147    err: String<256>,
148}
149
150impl From<NotecardError> for NoteError {
151    fn from(n: NotecardError) -> NoteError {
152        if n.err.contains("{dfu-in-progress}") {
153            NoteError::DFUInProgress
154        } else if n.err.contains("adding notes to a non-uplinked port is not allowed") {
155            NoteError::NonPortNoteInPackageMode
156        } else if n.err.contains("{file-storage-full}") {
157            NoteError::FileStorageFull(n.err)
158        } else if n.err.contains("error adding note") {
159            NoteError::ErrorAddingNote(n.err)
160        } else {
161            NoteError::NotecardErr(n.err)
162        }
163    }
164}
165
166/// The driver for the Notecard. Must be intialized before making any requests.
167pub struct Notecard<
168    IOM: Write<SevenBitAddress> + Read<SevenBitAddress>,
169    const BUF_SIZE: usize = DEFAULT_BUF_SIZE,
170> {
171    i2c: IOM,
172    addr: u8,
173    state: NoteState,
174
175    /// The receive buffer. Must be large enough to hold the largest response that will be received.
176    buf: Vec<u8, BUF_SIZE>,
177
178    response_timeout: u16,
179    chunk_delay: u16,
180    segment_delay: u16,
181}
182
183pub struct SuspendState<const BUF_SIZE: usize> {
184    addr: u8,
185    state: NoteState,
186    buf: Vec<u8, BUF_SIZE>,
187    response_timeout: u16,
188    chunk_delay: u16,
189    segment_delay: u16,
190}
191
192impl<IOM: Write<SevenBitAddress> + Read<SevenBitAddress>, const BUF_SIZE: usize>
193    Notecard<IOM, BUF_SIZE>
194{
195    pub fn new(i2c: IOM) -> Notecard<IOM, BUF_SIZE> {
196        Self::new_with_config(i2c, NotecardConfig::default())
197    }
198
199    pub fn new_with_config(i2c: IOM, c: NotecardConfig) -> Notecard<IOM, BUF_SIZE> {
200        Notecard {
201            i2c,
202            addr: c.i2c_addr,
203            state: NoteState::Handshake,
204            buf: Vec::new(),
205
206            response_timeout: c.response_timeout,
207            chunk_delay: c.chunk_delay,
208            segment_delay: c.segment_delay,
209        }
210    }
211
212    /// Resize the internal buffer, consuming the existing, and returning a new Notecard
213    /// instance.
214    pub fn resize_buf<const B: usize>(self) -> Result<Notecard<IOM, B>, NoteError> {
215        if B < self.buf.len() {
216            Err(NoteError::BufOverflow)
217        } else {
218            Ok(Notecard {
219                buf: Vec::<_, B>::from_slice(&self.buf).unwrap(),
220                ..self
221            })
222        }
223    }
224
225    /// Free the IOM device and return the driver state so that it can be quickly resumed. It is
226    /// not safe to change the state of the Notecard in the meantime, or create a second driver
227    /// without using this state.
228    pub fn suspend(self) -> (IOM, SuspendState<BUF_SIZE>) {
229        (
230            self.i2c,
231            SuspendState {
232                state: self.state,
233                buf: self.buf,
234                addr: self.addr,
235                response_timeout: self.response_timeout,
236                chunk_delay: self.chunk_delay,
237                segment_delay: self.segment_delay,
238            },
239        )
240    }
241
242    /// Resume a previously [`suspend`]ed Notecard driver.
243    pub fn resume(i2c: IOM, state: SuspendState<BUF_SIZE>) -> Notecard<IOM, BUF_SIZE> {
244        Notecard {
245            i2c,
246            addr: state.addr,
247            state: state.state,
248            buf: state.buf,
249            response_timeout: state.response_timeout,
250            chunk_delay: state.chunk_delay,
251            segment_delay: state.segment_delay,
252        }
253    }
254
255    /// Initialize the notecard driver by performing handshake with notecard.
256    pub fn initialize(&mut self, delay: &mut impl DelayMs<u16>) -> Result<(), NoteError> {
257        info!("note: initializing.");
258        self.reset(delay)
259    }
260
261    /// Check if notecarrier is connected and responding.
262    ///
263    /// > This is allowed no matter the state.
264    pub fn ping(&mut self) -> bool {
265        self.i2c.write(self.addr, &[]).is_ok()
266    }
267
268    /// Query the notecard for available bytes.
269    pub fn data_query(&mut self) -> Result<usize, NoteError> {
270        trace!("note: data_query: {:?}", self.state);
271        if !matches!(self.state, NoteState::Response(_)) {
272            // Ask for reading, but with zero bytes allocated.
273            self.i2c
274                .write(self.addr, &[0, 0])
275                .map_err(|_| NoteError::I2cWriteError)?;
276
277            let mut buf = [0u8; 2];
278
279            // Read available bytes to read
280            self.i2c
281                .read(self.addr, &mut buf)
282                .map_err(|_| NoteError::I2cReadError)?;
283
284            let available = buf[0] as usize;
285            let sent = buf[1] as usize;
286
287            if available > 0 {
288                self.buf.clear();
289                self.state = NoteState::Response(available);
290            }
291
292            trace!("avail = {}, sent = {}", available, sent);
293
294            if sent > 0 {
295                error!(
296                    "data query: bytes sent when querying available bytes: {}",
297                    sent
298                );
299                Err(NoteError::RemainingData)
300            } else {
301                Ok(available)
302            }
303        } else {
304            error!("note: data_query called while reading response.");
305            Err(NoteError::WrongState)
306        }
307    }
308
309    /// Read until empty.
310    fn read(&mut self) -> Result<usize, NoteError> {
311        if let NoteState::Response(avail) = self.state {
312            // Chunk to read + notecard header (2 bytes)
313            let mut bytes = Vec::<u8, 128>::new();
314
315            let sz = (bytes.capacity() - 2).min(avail);
316            bytes.resize(sz + 2, 0).unwrap();
317
318            debug!("asking to read: {} of available {} bytes", sz, avail);
319
320            // Ask for reading `sz` bytes
321            self.i2c
322                .write(self.addr, &[0, sz as u8])
323                .map_err(|_| NoteError::I2cWriteError)?;
324
325            // Read bytes
326            self.i2c
327                .read(self.addr, &mut bytes)
328                .map_err(|_| NoteError::I2cReadError)?;
329
330            let available = bytes[0] as usize;
331            let sent = bytes[1] as usize;
332
333            self.buf.extend_from_slice(&bytes[2..]).unwrap(); // XXX: check enough space
334
335            trace!("read:  {}", unsafe {
336                core::str::from_utf8_unchecked(&bytes)
337            });
338
339            trace!("avail = {}, sent = {}", available, sent);
340
341            if available > 0 {
342                self.state = NoteState::Response(available);
343            } else {
344                self.state = NoteState::ResponseReady;
345            }
346
347            Ok(available)
348        } else {
349            error!("read: called when not waiting for response");
350            Err(NoteError::WrongState)
351        }
352    }
353
354    /// Take the response from the buffer. Once this function has been called, the state is reset
355    /// and it is no longer safe to read the buffer.
356    ///
357    /// Safety:
358    ///
359    /// This function returns an immutable reference to the buffer, but new requests require a
360    /// mutable reference to `Note`. This is not granted before the immutable reference is
361    /// released.
362    fn take_response(&mut self) -> Result<&[u8], NoteError> {
363        if matches!(self.state, NoteState::ResponseReady) {
364            self.state = NoteState::Request;
365
366            Ok(&self.buf)
367        } else {
368            error!("take response called when response not ready");
369            Err(NoteError::WrongState)
370        }
371    }
372
373    /// Poll for data.
374    fn poll(&mut self) -> Result<Option<&[u8]>, NoteError> {
375        trace!("note: poll: {:?}", self.state);
376        match self.state {
377            NoteState::Poll(_) => {
378                // 1. Check for available data
379                let sz = self.data_query()?;
380                if sz > 0 {
381                    debug!("response ready: {} bytes..", sz);
382
383                    self.poll()
384                } else {
385                    // sleep and wait for ready.
386                    Ok(None)
387                }
388            }
389            NoteState::Response(_) => {
390                let avail = self.read()?;
391                if avail == 0 {
392                    self.poll()
393                } else {
394                    // sleep and wait for more data.
395                    Ok(None)
396                }
397            }
398            NoteState::ResponseReady => {
399                debug!("response read, deserializing.");
400                Ok(Some(self.take_response()?))
401            }
402            _ => {
403                error!("poll called when not receiving response");
404                Err(NoteError::WrongState)
405            }
406        }
407    }
408
409    /// Read any remaining data from the Notecarrier. This will cancel any waiting responses, and
410    /// waiting for a response after this call will time-out.
411    unsafe fn consume_response(&mut self, delay: &mut impl DelayMs<u16>) -> Result<(), NoteError> {
412        warn!("note: trying to consume any left-over response.");
413        let mut waited = 0;
414
415        while waited < self.response_timeout {
416            if (self.poll()?).is_some() {
417                self.buf.clear();
418                return Ok(());
419            }
420
421            delay.delay_ms(RESPONSE_DELAY);
422            waited += RESPONSE_DELAY;
423        }
424
425        self.buf.clear();
426
427        error!("response timed out (>= {}).", self.response_timeout);
428        Err(NoteError::TimeOut)
429    }
430
431    /// Reset notecard driver and state. Any waiting responses will be invalidated
432    /// and time-out. However, you won't be able to get a mutable reference without having
433    /// dropped the `FutureResponse`.
434    pub fn reset(&mut self, delay: &mut impl DelayMs<u16>) -> Result<(), NoteError> {
435        warn!("resetting: consuming any left-over response and perform a new handshake.");
436
437        self.buf.clear(); // clear in case data_query() is 0.
438        self.state = NoteState::Handshake;
439        self.handshake(delay)
440    }
441
442    fn handshake(&mut self, delay: &mut impl DelayMs<u16>) -> Result<(), NoteError> {
443        if matches!(self.state, NoteState::Handshake) {
444            debug!("note: handshake");
445            if self.data_query()? > 0 {
446                error!("note: handshake: remaining data in queue, consuming..");
447                unsafe { self.consume_response(delay)? };
448            }
449
450            self.state = NoteState::Request;
451        }
452        Ok(())
453    }
454
455    /// Sends request from buffer.
456    fn send_request(&mut self, delay: &mut impl DelayMs<u16>) -> Result<(), NoteError> {
457        // This is presumably limited by the notecard firmware.
458        const CHUNK_LENGTH_MAX: usize = 127;
459        // This is a limit that was required on some Arduinos. Can probably be increased up to
460        // `CHUNK_LENGTH_MAX`. Should maybe be configurable.
461        const CHUNK_LENGTH_I: usize = 30;
462        const CHUNK_LENGTH: usize = if CHUNK_LENGTH_I < CHUNK_LENGTH_MAX {
463            CHUNK_LENGTH_I
464        } else {
465            CHUNK_LENGTH_MAX
466        };
467
468        // `note-c` uses `250` for `SEGMENT_LENGTH`. Round to closest divisible
469        // by CHUNK_LENGTH so that we don't end up with unnecessarily fragmented
470        // chunks. https://github.com/blues/note-c/blob/master/n_lib.h#L40 .
471        const SEGMENT_LENGTH: usize = (250 / CHUNK_LENGTH) * CHUNK_LENGTH;
472
473        if !matches!(self.state, NoteState::Request) {
474            warn!("note: request: wrong-state, resetting before new request.");
475            let buf = self.buf.clone();
476
477            self.reset(delay)?; // this clears the buffer - and may use it for other stuff!
478
479            self.buf.clear();
480            self.buf
481                .resize(buf.len(), 0)
482                .map_err(|_| NoteError::BufOverflow)?;
483            self.buf.copy_from_slice(&buf);
484        }
485
486        if self.buf.last() != Some(&b'\n') {
487            return Err(NoteError::InvalidRequest);
488        }
489
490        trace!("note: making request: {}", unsafe {
491            core::str::from_utf8_unchecked(&self.buf)
492        });
493
494        let mut buf = Vec::<u8, { CHUNK_LENGTH + 1 }>::new();
495        for segment in self.buf.chunks(SEGMENT_LENGTH) {
496            for c in segment.chunks(buf.capacity() - 1) {
497                buf.push(c.len() as u8).unwrap();
498                buf.extend_from_slice(c).unwrap();
499
500                trace!("note: sending chunk: {} => {}", &buf, unsafe {
501                    core::str::from_utf8_unchecked(&buf)
502                });
503
504                self.i2c
505                    .write(self.addr, &buf)
506                    .map_err(|_| NoteError::I2cWriteError)?;
507
508                buf.clear();
509                delay.delay_ms(self.chunk_delay);
510            }
511            delay.delay_ms(self.segment_delay);
512        }
513
514        self.state = NoteState::Poll(0);
515
516        Ok(())
517    }
518
519    /// Make a raw request. The byte slice must end with `\n`. After making a request a
520    /// [FutureResponse] must be created and consumed.
521    pub(crate) fn request_raw(
522        &mut self,
523        delay: &mut impl DelayMs<u16>,
524        cmd: &[u8],
525    ) -> Result<(), NoteError> {
526        self.buf.clear();
527        self.buf
528            .resize(cmd.len(), 0)
529            .map_err(|_| NoteError::BufOverflow)?;
530
531        self.buf.copy_from_slice(cmd);
532
533        self.send_request(delay)
534    }
535
536    /// Make a request. After making a request a [FutureResponse] must be created and consumed
537    /// before making any new requests. This method is usually called through the API methods like
538    /// `[card]`.
539    pub(crate) fn request<T: Serialize>(
540        &mut self,
541        delay: &mut impl DelayMs<u16>,
542        cmd: T,
543    ) -> Result<(), NoteError> {
544        self.buf.clear();
545        self.buf.resize(self.buf.capacity(), 0).unwrap(); // unsafe { set_len } ?
546
547        let sz = serde_json_core::to_slice(&cmd, &mut self.buf).map_err(|_| NoteError::SerError)?;
548        self.buf.truncate(sz);
549
550        // Add new-line, this separator tells the Notecard that the request is done.
551        self.buf.push(b'\n').map_err(|_| NoteError::SerError)?;
552        self.send_request(delay)
553    }
554
555    /// [card Requests](https://dev.blues.io/reference/notecard-api/card-requests/)
556    pub fn card(&mut self) -> card::Card<'_, IOM, BUF_SIZE> {
557        card::Card::from(self)
558    }
559
560    /// [note Requests](https://dev.blues.io/reference/notecard-api/note-requests/)
561    pub fn note(&mut self) -> note::Note<'_, IOM, BUF_SIZE> {
562        note::Note::from(self)
563    }
564
565    /// [web Requests](https://dev.blues.io/reference/notecard-api/web-requests/)
566    pub fn web(&mut self) -> web::Web<'_, IOM, BUF_SIZE> {
567        web::Web::from(self)
568    }
569
570    /// [hub Requests](https://dev.blues.io/reference/notecard-api/hub-requests/)
571    pub fn hub(&mut self) -> hub::Hub<'_, IOM, BUF_SIZE> {
572        hub::Hub::from(self)
573    }
574
575    /// [dfu Requests](https://dev.blues.io/api-reference/notecard-api/dfu-requests/)
576    pub fn dfu(&mut self) -> dfu::DFU<'_, IOM, BUF_SIZE> {
577        dfu::DFU::from(self)
578    }
579
580    /// [NtN Requests](https://dev.blues.io/reference/notecard-api/ntn-requests/)
581    pub fn ntn(&mut self) -> ntn::NTN<'_, IOM, BUF_SIZE> {
582        ntn::NTN::from(self)
583    }
584}
585
586/// A future response.
587///
588/// It will not be possible to make any new requests before this has been consumed. If you drop
589/// this future before consuming the response the Notecard and driver will be left in inconsistent
590/// state. It is not safe to make new requests to the Notecard before the previous response has
591/// been read.
592#[must_use = "The response must be waited for and consumed, otherwise the notecard is left in an inconsistent state"]
593pub struct FutureResponse<
594    'a,
595    T: DeserializeOwned,
596    IOM: Write<SevenBitAddress> + Read<SevenBitAddress>,
597    const BUF_SIZE: usize,
598> {
599    note: &'a mut Notecard<IOM, BUF_SIZE>,
600    _r: PhantomData<T>,
601}
602
603impl<
604        'a,
605        T: DeserializeOwned,
606        IOM: Write<SevenBitAddress> + Read<SevenBitAddress>,
607        const BUF_SIZE: usize,
608    > FutureResponse<'a, T, IOM, BUF_SIZE>
609{
610    fn from(note: &'a mut Notecard<IOM, BUF_SIZE>) -> FutureResponse<'a, T, IOM, BUF_SIZE> {
611        FutureResponse {
612            note,
613            _r: PhantomData,
614        }
615    }
616
617    /// Reads remaining data and returns the deserialized object if it is ready.
618    pub fn poll(&mut self) -> Result<Option<T>, NoteError> {
619        match self.note.poll()? {
620            Some(body) if body.starts_with(br##"{"err":"##) => {
621                debug!(
622                    "response is error response, parsing error..: {}",
623                    core::str::from_utf8(body).unwrap_or("[invalid utf-8]")
624                );
625                Err(
626                    serde_json_core::from_slice::<NotecardError>(body).map_or_else(
627                        |_| {
628                            error!(
629                                "failed to deserialize: {}",
630                                core::str::from_utf8(body).unwrap_or("[invalid utf-8]")
631                            );
632                            NoteError::new_desererror(body)
633                        },
634                        |(e, _)| NoteError::from(e),
635                    ),
636                )
637            }
638            Some(body) => {
639                trace!("response is regular, parsing..");
640                Ok(Some(
641                    serde_json_core::from_slice::<T>(body)
642                        .map_err(|_| {
643                            error!(
644                                "failed to deserialize: {}",
645                                core::str::from_utf8(body).unwrap_or("[invalid utf-8]")
646                            );
647                            NoteError::new_desererror(body)
648                        })?
649                        .0,
650                ))
651            }
652            None => Ok(None),
653        }
654    }
655
656    /// Wait for response and return raw bytes. These may change on next response,
657    /// so this method is probably not staying as it is.
658    pub fn wait_raw(mut self, delay: &mut impl DelayMs<u16>) -> Result<&'a [u8], NoteError> {
659        let mut waited = 0;
660
661        while waited < self.note.response_timeout {
662            if self.poll()?.is_some() {
663                return self.note.take_response()
664            }
665
666            delay.delay_ms(RESPONSE_DELAY);
667            waited += RESPONSE_DELAY;
668        }
669
670        error!("response timed out (>= {}).", self.note.response_timeout);
671        Err(NoteError::TimeOut)
672    }
673
674    /// Wait for response and return deserialized object.
675    pub fn wait(mut self, delay: &mut impl DelayMs<u16>) -> Result<T, NoteError> {
676        let mut waited = 0;
677
678        while waited < self.note.response_timeout {
679            if let Some(r) = self.poll()? {
680                return Ok(r)
681            }
682
683            delay.delay_ms(RESPONSE_DELAY);
684            waited += RESPONSE_DELAY;
685        }
686
687        error!("response timed out (>= {}).", self.note.response_timeout);
688        Err(NoteError::TimeOut)
689    }
690}
691
692#[cfg(test)]
693mod tests {
694    use super::*;
695    use embedded_hal_mock::eh0::delay::StdSleep;
696    use embedded_hal_mock::eh0::i2c::{Mock, Transaction};
697
698    pub fn new_mock() -> Notecard<Mock> {
699        // let exp = [ Transaction::write(0x17, vec![]) ];
700        let i2c = Mock::new(&[]);
701        Notecard::new(i2c)
702    }
703
704    #[test]
705    fn resize_buf() {
706        let c = new_mock();
707        assert_eq!(c.buf.capacity(), DEFAULT_BUF_SIZE);
708
709        let mut c = c.resize_buf::<1024>().unwrap();
710        assert_eq!(c.buf.capacity(), 1024);
711
712        c.i2c.done();
713    }
714
715    #[test]
716    fn raw_request() {
717        let mut expect = b"{\"req\":\"card.location\"}\n".to_vec();
718        expect.insert(0, 24);
719        let exp = [
720            Transaction::write(0x17, vec![0, 0]),
721            Transaction::read(0x17, vec![0, 0]),
722            Transaction::write(0x17, expect),
723        ];
724        let i2c = Mock::new(&exp);
725        let mut c: Notecard<Mock> = Notecard::new(i2c);
726        let mut delay = StdSleep::new();
727        c.request_raw(&mut delay, b"{\"req\":\"card.location\"}\n")
728            .unwrap();
729
730        c.i2c.done();
731    }
732}