diff --git a/atat/Cargo.toml b/atat/Cargo.toml index edb41143..dfc460f8 100644 --- a/atat/Cargo.toml +++ b/atat/Cargo.toml @@ -37,6 +37,9 @@ defmt = { version = "^0.3", optional = true } [dev-dependencies] embassy-time = { version = "0.2", features = ["std", "generic-queue"] } critical-section = { version = "1.1", features = ["std"] } +serde_at = { path = "../serde_at", version = "^0.20.0", features = [ + "heapless", +] } tokio = { version = "1", features = ["macros", "rt"] } [features] diff --git a/atat/src/asynch/client.rs b/atat/src/asynch/client.rs index 5bde8046..87e5a5f5 100644 --- a/atat/src/asynch/client.rs +++ b/atat/src/asynch/client.rs @@ -14,6 +14,7 @@ pub struct Client<'a, W: Write, const INGRESS_BUF_SIZE: usize> { res_channel: &'a ResponseChannel, config: Config, cooldown_timer: Option, + buf: &'a mut [u8], } impl<'a, W: Write, const INGRESS_BUF_SIZE: usize> Client<'a, W, INGRESS_BUF_SIZE> { @@ -21,19 +22,21 @@ impl<'a, W: Write, const INGRESS_BUF_SIZE: usize> Client<'a, W, INGRESS_BUF_SIZE writer: W, res_channel: &'a ResponseChannel, config: Config, + buf: &'a mut [u8], ) -> Self { Self { writer, res_channel, config, cooldown_timer: None, + buf, } } - async fn send_command(&mut self, cmd: &[u8]) -> Result<(), Error> { + async fn send_command(&mut self, len: usize) -> Result<(), Error> { self.wait_cooldown_timer().await; - self.send_inner(cmd).await?; + self.send_inner(len).await?; self.start_cooldown_timer(); Ok(()) @@ -41,13 +44,13 @@ impl<'a, W: Write, const INGRESS_BUF_SIZE: usize> Client<'a, W, INGRESS_BUF_SIZE async fn send_request( &mut self, - cmd: &[u8], + len: usize, timeout: Duration, ) -> Result, Error> { self.wait_cooldown_timer().await; let mut response_subscription = self.res_channel.subscriber().unwrap(); - self.send_inner(cmd).await?; + self.send_inner(len).await?; let response = self .with_timeout(timeout, response_subscription.next_message_pure()) @@ -58,14 +61,17 @@ impl<'a, W: Write, const INGRESS_BUF_SIZE: usize> Client<'a, W, INGRESS_BUF_SIZE response } - async fn send_inner(&mut self, cmd: &[u8]) -> Result<(), Error> { - if cmd.len() < 50 { - debug!("Sending command: {:?}", LossyStr(cmd)); + async fn send_inner(&mut self, len: usize) -> Result<(), Error> { + if len < 50 { + debug!("Sending command: {:?}", LossyStr(&self.buf[..len])); } else { - debug!("Sending command with long payload ({} bytes)", cmd.len(),); + debug!("Sending command with long payload ({} bytes)", len); } - self.writer.write_all(cmd).await.map_err(|_| Error::Write)?; + self.writer + .write_all(&self.buf[..len]) + .await + .map_err(|_| Error::Write)?; self.writer.flush().await.map_err(|_| Error::Write)?; Ok(()) } @@ -107,18 +113,14 @@ impl<'a, W: Write, const INGRESS_BUF_SIZE: usize> Client<'a, W, INGRESS_BUF_SIZE } impl AtatClient for Client<'_, W, INGRESS_BUF_SIZE> { - async fn send, const LEN: usize>( - &mut self, - cmd: &Cmd, - ) -> Result { - let cmd_vec = cmd.as_bytes(); - let cmd_slice = cmd.get_slice(&cmd_vec); + async fn send<'a, Cmd: AtatCmd>(&'a mut self, cmd: &'a Cmd) -> Result { + let len = cmd.write(&mut self.buf); if !Cmd::EXPECTS_RESPONSE_CODE { - self.send_command(cmd_slice).await?; + self.send_command(len).await?; cmd.parse(Ok(&[])) } else { let response = self - .send_request(cmd_slice, Duration::from_millis(Cmd::MAX_TIMEOUT_MS.into())) + .send_request(len, Duration::from_millis(Cmd::MAX_TIMEOUT_MS.into())) .await?; cmd.parse((&response).into()) } @@ -179,10 +181,11 @@ mod tests { static TX_CHANNEL: PubSubChannel, 1, 1, 1> = PubSubChannel::new(); static RES_CHANNEL: ResponseChannel = ResponseChannel::new(); + static mut BUF: [u8; 1000] = [0; 1000]; let tx_mock = crate::tx_mock::TxMock::new(TX_CHANNEL.publisher().unwrap()); let client: Client = - Client::new(tx_mock, &RES_CHANNEL, $config); + Client::new(tx_mock, &RES_CHANNEL, $config, unsafe { BUF.as_mut() }); ( client, TX_CHANNEL.subscriber().unwrap(), diff --git a/atat/src/asynch/mod.rs b/atat/src/asynch/mod.rs index d2978014..96976926 100644 --- a/atat/src/asynch/mod.rs +++ b/atat/src/asynch/mod.rs @@ -12,14 +12,11 @@ pub trait AtatClient { /// This function will also make sure that at least `self.config.cmd_cooldown` /// has passed since the last response or URC has been received, to allow /// the slave AT device time to deliver URC's. - async fn send, const LEN: usize>( - &mut self, - cmd: &Cmd, - ) -> Result; + async fn send<'a, Cmd: AtatCmd>(&'a mut self, cmd: &'a Cmd) -> Result; - async fn send_retry, const LEN: usize>( - &mut self, - cmd: &Cmd, + async fn send_retry<'a, Cmd: AtatCmd>( + &'a mut self, + cmd: &'a Cmd, ) -> Result { for attempt in 1..=Cmd::ATTEMPTS { if attempt > 1 { diff --git a/atat/src/blocking/client.rs b/atat/src/blocking/client.rs index 90ffddc9..c4c41f4d 100644 --- a/atat/src/blocking/client.rs +++ b/atat/src/blocking/client.rs @@ -19,6 +19,7 @@ where res_channel: &'a ResponseChannel, cooldown_timer: Option, config: Config, + buf: &'a mut [u8], } impl<'a, W, const INGRESS_BUF_SIZE: usize> Client<'a, W, INGRESS_BUF_SIZE> @@ -29,19 +30,21 @@ where writer: W, res_channel: &'a ResponseChannel, config: Config, + buf: &'a mut [u8], ) -> Self { Self { writer, res_channel, cooldown_timer: None, config, + buf, } } - fn send_command(&mut self, cmd: &[u8]) -> Result<(), Error> { + fn send_command(&mut self, len: usize) -> Result<(), Error> { self.wait_cooldown_timer(); - self.send_inner(cmd)?; + self.send_inner(len)?; self.start_cooldown_timer(); Ok(()) @@ -49,13 +52,13 @@ where fn send_request( &mut self, - cmd: &[u8], + len: usize, timeout: Duration, ) -> Result, Error> { self.wait_cooldown_timer(); let mut response_subscription = self.res_channel.subscriber().unwrap(); - self.send_inner(cmd)?; + self.send_inner(len)?; let response = self .with_timeout(timeout, || response_subscription.try_next_message_pure()) @@ -65,14 +68,16 @@ where response } - fn send_inner(&mut self, cmd: &[u8]) -> Result<(), Error> { - if cmd.len() < 50 { - debug!("Sending command: {:?}", LossyStr(cmd)); + fn send_inner(&mut self, len: usize) -> Result<(), Error> { + if len < 50 { + debug!("Sending command: {:?}", LossyStr(&self.buf[..len])); } else { - debug!("Sending command with long payload ({} bytes)", cmd.len(),); + debug!("Sending command with long payload ({} bytes)", len,); } - self.writer.write_all(cmd).map_err(|_| Error::Write)?; + self.writer + .write_all(&self.buf[..len]) + .map_err(|_| Error::Write)?; self.writer.flush().map_err(|_| Error::Write)?; Ok(()) } @@ -109,18 +114,14 @@ impl AtatClient for Client<'_, W, INGRESS_BUF_ where W: Write, { - fn send, const LEN: usize>( - &mut self, - cmd: &Cmd, - ) -> Result { - let cmd_vec = cmd.as_bytes(); - let cmd_slice = cmd.get_slice(&cmd_vec); + fn send(&mut self, cmd: &Cmd) -> Result { + let len = cmd.write(&mut self.buf); if !Cmd::EXPECTS_RESPONSE_CODE { - self.send_command(cmd_slice)?; + self.send_command(len)?; cmd.parse(Ok(&[])) } else { let response = - self.send_request(cmd_slice, Duration::from_millis(Cmd::MAX_TIMEOUT_MS.into()))?; + self.send_request(len, Duration::from_millis(Cmd::MAX_TIMEOUT_MS.into()))?; cmd.parse((&response).into()) } } @@ -263,10 +264,11 @@ mod test { static TX_CHANNEL: PubSubChannel, 1, 1, 1> = PubSubChannel::new(); static RES_CHANNEL: ResponseChannel = ResponseChannel::new(); + static mut BUF: [u8; 1000] = [0; 1000]; let tx_mock = crate::tx_mock::TxMock::new(TX_CHANNEL.publisher().unwrap()); let client: Client = - Client::new(tx_mock, &RES_CHANNEL, $config); + Client::new(tx_mock, &RES_CHANNEL, $config, unsafe { BUF.as_mut() }); ( client, TX_CHANNEL.subscriber().unwrap(), diff --git a/atat/src/blocking/mod.rs b/atat/src/blocking/mod.rs index 48566120..e98820f0 100644 --- a/atat/src/blocking/mod.rs +++ b/atat/src/blocking/mod.rs @@ -18,12 +18,9 @@ pub trait AtatClient { /// This function will also make sure that at least `self.config.cmd_cooldown` /// has passed since the last response or URC has been received, to allow /// the slave AT device time to deliver URC's. - fn send, const LEN: usize>(&mut self, cmd: &A) -> Result; + fn send(&mut self, cmd: &A) -> Result; - fn send_retry, const LEN: usize>( - &mut self, - cmd: &A, - ) -> Result { + fn send_retry(&mut self, cmd: &A) -> Result { for attempt in 1..=A::ATTEMPTS { if attempt > 1 { debug!("Attempt {}:", attempt); diff --git a/atat/src/buffers.rs b/atat/src/buffers.rs index 4e19ef56..de124863 100644 --- a/atat/src/buffers.rs +++ b/atat/src/buffers.rs @@ -38,14 +38,15 @@ impl< > Buffers { #[cfg(feature = "async")] - pub fn split( - &self, + pub fn split<'a, W: embedded_io_async::Write, D: Digester>( + &'a self, writer: W, digester: D, config: Config, + buf: &'a mut [u8], ) -> ( - Ingress, - crate::asynch::Client, + Ingress<'a, D, Urc, INGRESS_BUF_SIZE, URC_CAPACITY, URC_SUBSCRIBERS>, + crate::asynch::Client<'a, W, INGRESS_BUF_SIZE>, ) { ( Ingress::new( @@ -53,15 +54,16 @@ impl< self.res_channel.publisher().unwrap(), self.urc_channel.publisher(), ), - crate::asynch::Client::new(writer, &self.res_channel, config), + crate::asynch::Client::new(writer, &self.res_channel, config, buf), ) } - pub fn split_blocking( - &self, + pub fn split_blocking<'a, W: Write, D: Digester>( + &'a self, writer: W, digester: D, config: Config, + buf: &'a mut [u8], ) -> ( Ingress, crate::blocking::Client, @@ -72,7 +74,7 @@ impl< self.res_channel.publisher().unwrap(), self.urc_channel.publisher(), ), - crate::blocking::Client::new(writer, &self.res_channel, config), + crate::blocking::Client::new(writer, &self.res_channel, config, buf), ) } } diff --git a/atat/src/derive.rs b/atat/src/derive.rs index d48f71d9..16eb0ad3 100644 --- a/atat/src/derive.rs +++ b/atat/src/derive.rs @@ -204,18 +204,20 @@ mod tests { #[test] fn test_length_serialize() { + let mut buf = [0; 360]; + let len = LengthTester { + x: 8, + y: String::try_from("SomeString").unwrap(), + z: 2, + w: "whatup", + a: SimpleEnum::A, + b: SimpleEnumU32::A, + c: SimpleEnumU32::B, + // d: Vec::new() + } + .write(&mut buf); assert_eq!( - LengthTester { - x: 8, - y: String::try_from("SomeString").unwrap(), - z: 2, - w: "whatup", - a: SimpleEnum::A, - b: SimpleEnumU32::A, - c: SimpleEnumU32::B, - // d: Vec::new() - } - .as_bytes(), + &buf[..len], Vec::::from_slice(b"AT+CFUN=8,\"SomeString\",2,\"whatup\",0,0,1\r\n").unwrap() ); } diff --git a/atat/src/lib.rs b/atat/src/lib.rs index caec3ef7..4566c7dd 100644 --- a/atat/src/lib.rs +++ b/atat/src/lib.rs @@ -35,13 +35,14 @@ //! //! impl AtatResp for GreetingText {}; //! -//! impl<'a> AtatCmd<64> for SetGreetingText<'a> { +//! impl<'a> AtatCmd for SetGreetingText<'a> { //! type Response = NoResponse; //! -//! fn as_bytes(&self) -> Vec { -//! let mut buf: Vec = Vec::new(); +//! fn write(&self, mut buf: &mut [u8]) -> usize { +//! let buf_len = buf.len(); +//! use embedded_io::Write; //! write!(buf, "AT+CSGT={}", self.text); -//! buf +//! buf_len - buf.len() //! } //! //! fn parse(&self, resp: Result<&[u8], InternalError>) -> Result { @@ -49,11 +50,14 @@ //! } //! } //! -//! impl AtatCmd<8> for GetGreetingText { +//! impl AtatCmd for GetGreetingText { //! type Response = GreetingText; //! -//! fn as_bytes(&self) -> Vec { -//! Vec::from_slice(b"AT+CSGT?").unwrap() +//! fn write(&self, mut buf: &mut [u8]) -> usize { +//! let cmd = b"AT+CSGT?"; +//! let len = cmd.len(); +//! buf[..len].copy_from_slice(cmd); +//! len //! } //! //! fn parse(&self, resp: Result<&[u8], InternalError>) -> Result { diff --git a/atat/src/traits.rs b/atat/src/traits.rs index 1acbd3c5..dbd94cdb 100644 --- a/atat/src/traits.rs +++ b/atat/src/traits.rs @@ -43,13 +43,14 @@ pub trait AtatUrc { /// /// impl AtatResp for NoResponse {}; /// -/// impl<'a> AtatCmd<64> for SetGreetingText<'a> { +/// impl<'a> AtatCmd for SetGreetingText<'a> { /// type Response = NoResponse; /// -/// fn as_bytes(&self) -> Vec { -/// let mut buf: Vec = Vec::new(); +/// fn write(&self, mut buf: &mut [u8]) -> usize { +/// let buf_len = buf.len(); +/// use embedded_io::Write; /// write!(buf, "AT+CSGT={}", self.text); -/// buf +/// buf_len - buf.len() /// } /// /// fn parse(&self, resp: Result<&[u8], InternalError>) -> Result { @@ -57,7 +58,7 @@ pub trait AtatUrc { /// } /// } /// ``` -pub trait AtatCmd { +pub trait AtatCmd { /// The type of the response. Must implement the `AtatResp` trait. type Response: AtatResp; @@ -80,26 +81,25 @@ pub trait AtatCmd { /// Implemented to enhance expandability of ATAT const EXPECTS_RESPONSE_CODE: bool = true; - /// Return the command as a heapless `Vec` of bytes. - fn as_bytes(&self) -> Vec; - - fn get_slice<'a>(&'a self, bytes: &'a Vec) -> &'a [u8] { - bytes - } + /// Write the command and return the number of written bytes. + fn write(&self, buf: &mut [u8]) -> usize; /// Parse the response into a `Self::Response` or `Error` instance. fn parse(&self, resp: Result<&[u8], InternalError>) -> Result; } -impl AtatResp for Vec where T: AtatResp {} +impl<'a, T, const L: usize> AtatResp for Vec where T: AtatResp {} impl AtatResp for String {} -impl AtatCmd for String { +impl<'a, const L: usize> AtatCmd for String { type Response = String<256>; - fn as_bytes(&self) -> Vec { - self.clone().into_bytes() + fn write(&self, buf: &mut [u8]) -> usize { + let bytes = self.as_bytes(); + let len = bytes.len(); + buf[..len].copy_from_slice(bytes); + len } fn parse(&self, resp: Result<&[u8], InternalError>) -> Result { diff --git a/atat_derive/src/cmd.rs b/atat_derive/src/cmd.rs index 3a4212e0..29132582 100644 --- a/atat_derive/src/cmd.rs +++ b/atat_derive/src/cmd.rs @@ -69,16 +69,6 @@ pub fn atat_cmd(input: TokenStream) -> TokenStream { None => quote! {}, }; - // let quote_escape_strings = !matches!(quote_escape_strings, Some(false)); - - let mut cmd_len = cmd_prefix.len() + cmd.len() + termination.len(); - if value_sep { - cmd_len += 1; - } - if quote_escape_strings { - cmd_len += 2; - } - let (field_names, field_names_str): (Vec<_>, Vec<_>) = variants .iter() .map(|f| { @@ -100,7 +90,7 @@ pub fn atat_cmd(input: TokenStream) -> TokenStream { const #ident_len: usize = #struct_len; #[automatically_derived] - impl #impl_generics atat::AtatCmd<{ #ident_len + #cmd_len }> for #ident #ty_generics #where_clause { + impl #impl_generics atat::AtatCmd for #ident #ty_generics #where_clause { type Response = #resp; #timeout @@ -112,8 +102,8 @@ pub fn atat_cmd(input: TokenStream) -> TokenStream { #reattempt_on_parse_err #[inline] - fn as_bytes(&self) -> atat::heapless::Vec { - match atat::serde_at::to_vec(self, #cmd, atat::serde_at::SerializeOptions { + fn write(&self, buf: &mut [u8]) -> usize { + match atat::serde_at::to_slice(self, #cmd, buf, atat::serde_at::SerializeOptions { value_sep: #value_sep, cmd_prefix: #cmd_prefix, termination: #termination,