From 40a7ddad3a8acf81e160461bf089c06fdbcc813c Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 8 Feb 2021 16:21:29 +0100 Subject: [PATCH] Migrate to embedded-time. Fixes #56 --- .gitignore | 1 - .vscode/settings.json | 15 ++++ atat/Cargo.toml | 3 +- atat/src/client.rs | 132 ++++++++++++++++++++++++------------ atat/src/ingress_manager.rs | 2 +- atat/src/lib.rs | 33 +++++---- atat/src/traits.rs | 21 ++++++ 7 files changed, 146 insertions(+), 61 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index fffce7fc..ecefe18c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ target/ *.o application/Debug/bin/ application/Debug/obj/ -.vscode lcov.info # Ignore lock file in libraries (https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-have-cargolock-in-version-control-but-not-libraries) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..9d572666 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + // override the default setting (`cargo check --all-targets`) which produces the following error + // "can't find crate for `test`" when the default compilation target is a no_std target + // with these changes RA will call `cargo check --bins` on save + "rust-analyzer.checkOnSave.allTargets": false, + "rust-analyzer.checkOnSave.extraArgs": [ + "-bins", + "--target", + "x86_64-unknown-linux-gnu" + ], + "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", + "rust-analyzer.diagnostics.disabled": [ + "unresolved-import" + ] +} diff --git a/atat/Cargo.toml b/atat/Cargo.toml index 47cc8e82..470ac716 100644 --- a/atat/Cargo.toml +++ b/atat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "atat" -version = "0.7.2-alpha.0" +version = "0.7.1" authors = ["Mathias Koch "] description = "AT Parser for serial based device crates" readme = "../README.md" @@ -25,6 +25,7 @@ serde_at = { path = "../serde_at", version = "^0.7.2-alpha.0"} atat_derive = { path = "../atat_derive", version = "^0.7.2-alpha.0", optional = true } serde = { version = "^1", default-features = false } typenum = "^1" +embedded-time = "0.10.1" log = { version = "^0.4", default-features = false, optional = true } defmt = { version = "^0.1" } diff --git a/atat/src/client.rs b/atat/src/client.rs index 2d074b77..0df39c36 100644 --- a/atat/src/client.rs +++ b/atat/src/client.rs @@ -1,4 +1,7 @@ -use embedded_hal::{serial, timer::CountDown}; +use core::convert::TryInto; + +use embedded_hal::serial; +use embedded_time::{duration::*, Clock, Instant}; use crate::atat_log; use crate::error::Error; @@ -20,14 +23,15 @@ enum ClientState { /// `clearBuffer` to the ingress-manager. pub struct Client< Tx, - T, + CLK, BufLen = consts::U256, ComCapacity = consts::U3, ResCapacity = consts::U5, UrcCapacity = consts::U10, > where Tx: serial::Write, - T: CountDown, + CLK: Clock, + Generic: TryInto, BufLen: ArrayLength, ComCapacity: ArrayLength, ResCapacity: ArrayLength>, @@ -43,17 +47,20 @@ pub struct Client< /// The command producer can send commands to the ingress manager com_p: ComProducer, + last_receive_time: Instant, + cmd_send_time: Option>, + state: ClientState, - timer: T, + clock: CLK, config: Config, } -impl - Client +impl + Client where Tx: serial::Write, - T: CountDown, - T::Time: From, + CLK: Clock, + Generic: TryInto, BufLen: ArrayLength, ComCapacity: ArrayLength, ResCapacity: ArrayLength>, @@ -64,27 +71,65 @@ where res_c: ResConsumer, urc_c: UrcConsumer, com_p: ComProducer, - timer: T, + clock: CLK, config: Config, ) -> Self { + let last_receive_time = clock + .try_now() + .map_err(|_| defmt::error!("Failed to obtain initial clock!")) + .unwrap(); + Self { + last_receive_time, + cmd_send_time: None, tx, res_c, urc_c, com_p, state: ClientState::Idle, config, - timer, + clock, } } + + fn set_last_receive_time(&mut self) -> Result<(), Error> { + self.last_receive_time = self.clock.try_now().map_err(|_| Error::Overflow)?; + Ok(()) + } + + fn set_cmd_send_time(&mut self) -> Result<(), Error> { + self.cmd_send_time = Some(self.clock.try_now().map_err(|_| Error::Overflow)?); + Ok(()) + } + + fn last_receive_elapsed(&self) -> Milliseconds { + self.clock + .try_now() + .ok() + .and_then(|now| now.checked_duration_since(&self.last_receive_time)) + .and_then(|dur| dur.try_into().ok()) + .unwrap_or_else(|| Milliseconds(0)) + } + + fn cmd_send_elapsed(&self) -> Option> { + self.cmd_send_time + .as_ref() + .and_then(|started| { + self.clock + .try_now() + .ok() + .and_then(|now| now.checked_duration_since(started)) + }) + .and_then(|dur| dur.try_into().ok()) + } } -impl AtatClient - for Client +impl AtatClient + for Client where Tx: serial::Write, - T: CountDown, - T::Time: From, + CLK: Clock, + Generic: TryInto, BufLen: ArrayLength, ComCapacity: ArrayLength, ResCapacity: ArrayLength>, @@ -110,7 +155,7 @@ where // compare the time of the last response or URC and ensure at least // `self.config.cmd_cooldown` ms have passed before sending a new // command - nb::block!(self.timer.try_wait()).ok(); + while self.last_receive_elapsed() < self.config.cmd_cooldown {} let cmd_buf = cmd.as_bytes(); match core::str::from_utf8(&cmd_buf) { @@ -136,22 +181,21 @@ where nb::block!(self.tx.try_write(c)).map_err(|_e| Error::Write)?; } nb::block!(self.tx.try_flush()).map_err(|_e| Error::Write)?; + self.set_cmd_send_time()?; + self.state = ClientState::AwaitingResponse; } match self.config.mode { Mode::Blocking => Ok(nb::block!(self.check_response(cmd))?), Mode::NonBlocking => self.check_response(cmd), - Mode::Timeout => { - self.timer.try_start(cmd.max_timeout_ms()).ok(); - Ok(nb::block!(self.check_response(cmd))?) - } + Mode::Timeout => Ok(nb::block!(self.check_response(cmd))?), } } fn peek_urc_with bool>(&mut self, f: F) { if let Some(urc) = self.urc_c.peek() { - self.timer.try_start(self.config.cmd_cooldown).ok(); + self.last_receive_time = self.clock.try_now().unwrap(); if let Ok(urc) = URC::parse(urc) { if !f(urc) { return; @@ -166,7 +210,7 @@ where return match result { Ok(ref resp) => { if let ClientState::AwaitingResponse = self.state { - self.timer.try_start(self.config.cmd_cooldown).ok(); + self.set_last_receive_time()?; self.state = ClientState::Idle; Ok(cmd.parse(resp).map_err(nb::Error::Other)?) } else { @@ -174,20 +218,23 @@ where } } Err(e) => { - self.timer.try_start(self.config.cmd_cooldown).ok(); + self.set_last_receive_time()?; self.state = ClientState::Idle; Err(nb::Error::Other(e)) } }; } else if let Mode::Timeout = self.config.mode { - if self.timer.try_wait().is_ok() { - self.state = ClientState::Idle; - // Tell the parser to clear the buffer due to timeout - if self.com_p.enqueue(Command::ClearBuffer).is_err() { - // TODO: Consider how to act in this situation. - atat_log!(error, "Failed to signal parser to clear buffer on timeout!"); + match self.cmd_send_elapsed() { + Some(elapsed) if elapsed >= Milliseconds::(cmd.max_timeout_ms()) => { + self.state = ClientState::Idle; + // Tell the parser to clear the buffer due to timeout + if self.com_p.enqueue(Command::ClearBuffer).is_err() { + // TODO: Consider how to act in this situation. + atat_log!(error, "Failed to signal parser to clear buffer on timeout!"); + } + return Err(nb::Error::Other(Error::Timeout)); } - return Err(nb::Error::Other(Error::Timeout)); + _ => {} } } Err(nb::Error::WouldBlock) @@ -208,21 +255,18 @@ mod test { use nb; struct CdMock { - time: u32, + time: core::cell::Cell, } - impl CountDown for CdMock { - type Error = core::convert::Infallible; - type Time = u32; - fn try_start(&mut self, count: T) -> Result<(), Self::Error> - where - T: Into, - { - self.time = count.into(); - Ok(()) - } - fn try_wait(&mut self) -> nb::Result<(), Self::Error> { - Ok(()) + impl Clock for CdMock { + type T = u32; + + const SCALING_FACTOR: Fraction = Fraction::new(1, 1); + + fn try_now(&self) -> Result, embedded_time::clock::Error> { + let new_time = self.time.get() + 1; + self.time.set(new_time); + Ok(Instant::new(new_time)) } } @@ -377,7 +421,9 @@ mod test { static mut COM_Q: queues::ComQueue = Queue(heapless::i::Queue::u8()); let (com_p, _com_c) = unsafe { COM_Q.split() }; - let timer = CdMock { time: 0 }; + let timer = CdMock { + time: core::cell::Cell::new(0), + }; let tx_mock = TxMock::new(String::new()); let client: Client< diff --git a/atat/src/ingress_manager.rs b/atat/src/ingress_manager.rs index 565ca7dd..d44a07ff 100644 --- a/atat/src/ingress_manager.rs +++ b/atat/src/ingress_manager.rs @@ -438,7 +438,6 @@ where /// This function should be called regularly for the ingress manager to work pub fn digest(&mut self) { // Handle commands - self.handle_com(); // Trim leading whitespace if self.buf.starts_with(&[self.line_term_char]) || self.buf.starts_with(&[self.format_char]) @@ -618,6 +617,7 @@ where self.state = State::Idle; } } + self.handle_com(); } } diff --git a/atat/src/lib.rs b/atat/src/lib.rs index 75628a67..d34a23dd 100644 --- a/atat/src/lib.rs +++ b/atat/src/lib.rs @@ -239,8 +239,11 @@ mod ingress_manager; mod queues; mod traits; +use core::convert::TryInto; + #[cfg(feature = "derive")] pub use atat_derive; +use embedded_time::{Clock, duration::*}; #[cfg(feature = "derive")] pub mod derive; @@ -389,7 +392,7 @@ pub struct Config { line_term_char: u8, format_char: u8, at_echo_enabled: bool, - cmd_cooldown: u32, + cmd_cooldown: Milliseconds, } impl Default for Config { @@ -399,7 +402,7 @@ impl Default for Config { line_term_char: b'\r', format_char: b'\n', at_echo_enabled: true, - cmd_cooldown: 20, + cmd_cooldown: Milliseconds(20), } } } @@ -432,14 +435,14 @@ impl Config { } #[must_use] - pub const fn cmd_cooldown(mut self, ms: u32) -> Self { + pub const fn cmd_cooldown(mut self, ms: Milliseconds) -> Self { self.cmd_cooldown = ms; self } } -type ClientParser = ( - Client, +type ClientParser = ( + Client, IngressManager, ); @@ -468,21 +471,21 @@ where /// [`Client`]: struct.Client.html /// [`IngressManager`]: struct.IngressManager.html /// [`new`]: #method.new -pub struct ClientBuilder { +pub struct ClientBuilder { serial_tx: Tx, - timer: T, + clock: CLK, config: Config, custom_urc_matcher: Option, #[doc(hidden)] _internal: core::marker::PhantomData<(BufLen, ComCapacity, ResCapacity, UrcCapacity)>, } -impl - ClientBuilder +impl + ClientBuilder where Tx: embedded_hal::serial::Write, - T: embedded_hal::timer::CountDown, - T::Time: From, + CLK: Clock, + Generic: TryInto, U: UrcMatcher, BufLen: ArrayLength, ComCapacity: ArrayLength, @@ -497,10 +500,10 @@ where /// /// [serialwrite]: ../embedded_hal/serial/trait.Write.html /// [timercountdown]: ../embedded_hal/timer/trait.CountDown.html - pub fn new(serial_tx: Tx, timer: T, config: Config) -> Self { + pub fn new(serial_tx: Tx, clock: CLK, config: Config) -> Self { Self { serial_tx, - timer, + clock, config, custom_urc_matcher: None, #[doc(hidden)] @@ -523,7 +526,7 @@ where pub fn build( self, queues: Queues, - ) -> ClientParser { + ) -> ClientParser { let parser = IngressManager::with_custom_urc_matcher( queues.res_queue.0, queues.urc_queue.0, @@ -536,7 +539,7 @@ where queues.res_queue.1, queues.urc_queue.1, queues.com_queue.0, - self.timer, + self.clock, self.config, ); diff --git a/atat/src/traits.rs b/atat/src/traits.rs index 76e41410..ca887ea1 100644 --- a/atat/src/traits.rs +++ b/atat/src/traits.rs @@ -176,6 +176,27 @@ where { } +impl AtatResp for heapless::String where L: ArrayLength {} + +impl AtatCmd for heapless::String +where + L: ArrayLength, +{ + type CommandLen = L; + + type Response = heapless::String; + + fn as_bytes(&self) -> Vec { + self.clone().into_bytes() + } + + fn parse(&self, resp: &[u8]) -> Result { + heapless::String::from_utf8( + Vec::from_slice(resp).map_err(|_| Error::ParseString)?, + ).map_err(|_| Error::ParseString) + } +} + #[cfg(all(test, feature = "derive"))] mod test { use super::*;