atat/
traits.rs

1use crate::error::{Error, InternalError};
2use heapless::{String, Vec};
3
4/// This trait needs to be implemented for every response type.
5///
6/// Example:
7/// ```
8/// use atat::AtatResp;
9///
10/// pub struct GreetingText {
11///     pub text: heapless::String<64>,
12/// }
13///
14/// impl AtatResp for GreetingText {}
15/// ```
16pub trait AtatResp {}
17
18pub trait AtatUrc {
19    /// The type of the response. Usually the enum this trait is implemented on.
20    type Response: Clone;
21
22    /// Parse the response into a `Self::Response` instance.
23    fn parse(resp: &[u8]) -> Option<Self::Response>;
24}
25
26/// This trait needs to be implemented for every command type.
27///
28/// It can also be derived by the [`atat_derive`] crate.
29///
30/// [`atat_derive`]: https://crates.io/crates/atat_derive
31///
32/// Example:
33/// ```
34/// use atat::{AtatCmd, AtatResp, Error, InternalError};
35/// use core::fmt::Write;
36/// use heapless::Vec;
37///
38/// pub struct SetGreetingText<'a> {
39///     pub text: &'a str,
40/// }
41///
42/// pub struct NoResponse;
43///
44/// impl AtatResp for NoResponse {};
45///
46/// impl<'a> AtatCmd for SetGreetingText<'a> {
47///     type Response = NoResponse;
48///     const MAX_LEN: usize = 64;
49///
50///     fn write(&self, mut buf: &mut [u8]) -> usize {
51///         assert!(buf.len() >= Self::MAX_LEN);
52///         let buf_len = buf.len();
53///         use embedded_io::Write;
54///         write!(buf, "AT+CSGT={}", self.text);
55///         buf_len - buf.len()
56///     }
57///
58///     fn parse(&self, resp: Result<&[u8], InternalError>) -> Result<Self::Response, Error> {
59///         Ok(NoResponse)
60///     }
61/// }
62/// ```
63pub trait AtatCmd {
64    /// The type of the response. Must implement the `AtatResp` trait.
65    type Response: AtatResp;
66
67    /// The size of the buffer required to write the request.
68    const MAX_LEN: usize;
69
70    /// Whether or not this command can be aborted.
71    const CAN_ABORT: bool = false;
72
73    /// The max timeout in milliseconds.
74    const MAX_TIMEOUT_MS: u32 = 1000;
75
76    /// The max number of times to attempt a command with automatic retries if
77    /// using `send_retry`.
78    const ATTEMPTS: u8 = 1;
79
80    /// Whether or not to reattempt a command on a parse error
81    /// using `send_retry`.
82    const REATTEMPT_ON_PARSE_ERR: bool = true;
83
84    /// Force client to look for a response.
85    /// Empty slice is then passed to parse by client.
86    /// Implemented to enhance expandability of ATAT
87    const EXPECTS_RESPONSE_CODE: bool = true;
88
89    /// Write the command and return the number of written bytes.
90    fn write(&self, buf: &mut [u8]) -> usize;
91
92    /// Parse the response into a `Self::Response` or `Error` instance.
93    fn parse(&self, resp: Result<&[u8], InternalError>) -> Result<Self::Response, Error>;
94}
95
96impl<T, const L: usize> AtatResp for Vec<T, L> where T: AtatResp {}
97
98impl<const L: usize> AtatResp for String<L> {}
99
100impl<const L: usize> AtatCmd for String<L> {
101    type Response = String<256>;
102    const MAX_LEN: usize = L;
103
104    fn write(&self, buf: &mut [u8]) -> usize {
105        let bytes = self.as_bytes();
106        let len = bytes.len();
107        buf[..len].copy_from_slice(bytes);
108        len
109    }
110
111    fn parse(&self, resp: Result<&[u8], InternalError>) -> Result<Self::Response, Error> {
112        let utf8_string =
113            core::str::from_utf8(resp.map_err(Error::from)?).map_err(|_| Error::Parse)?;
114        String::try_from(utf8_string).map_err(|_| Error::Parse)
115    }
116}
117
118#[cfg(all(test, feature = "derive"))]
119mod test {
120    use super::*;
121    use crate as atat;
122    use atat_derive::{AtatEnum, AtatResp};
123    use heapless::String;
124
125    #[derive(Debug, Clone, PartialEq, AtatEnum)]
126    pub enum PDPContextStatus {
127        /// 0: deactivated
128        Deactivated = 0,
129        /// 1: activated
130        Activated = 1,
131    }
132
133    #[derive(Debug, Clone, AtatResp, PartialEq)]
134    pub struct PDPContextState {
135        #[at_arg(position = 0)]
136        pub cid: u8,
137        #[at_arg(position = 1)]
138        pub status: PDPContextStatus,
139    }
140
141    #[derive(Debug, Clone, AtatResp, PartialEq)]
142    pub struct PDPContextDefinition {
143        #[at_arg(position = 0)]
144        pub cid: u8,
145        #[at_arg(position = 1)]
146        pub pdp_type: String<6>,
147        #[at_arg(position = 2)]
148        pub apn: String<99>,
149        #[at_arg(position = 3)]
150        pub pdp_addr: String<99>,
151        #[at_arg(position = 4)]
152        pub d_comp: u8,
153        #[at_arg(position = 5)]
154        pub h_comp: u8,
155        #[at_arg(position = 6)]
156        pub ipv4_addr_alloc: Option<u8>,
157        #[at_arg(position = 7)]
158        pub emergency_indication: Option<u8>,
159        #[at_arg(position = 8)]
160        pub p_cscf_discovery: Option<u8>,
161        #[at_arg(position = 9)]
162        pub im_cn_signalling_flag_ind: Option<u8>,
163        /* #[at_arg(position = 10)]
164         * pub nslpi: Option<u8>, */
165    }
166
167    #[test]
168    fn single_multi_response() {
169        let mut v = Vec::<_, 1>::from_slice(&[PDPContextState {
170            cid: 1,
171            status: PDPContextStatus::Deactivated,
172        }])
173        .unwrap();
174
175        let mut resp: Vec<PDPContextState, 1> = serde_at::from_slice(b"+CGACT: 1,0\r\n").unwrap();
176
177        assert_eq!(resp.pop(), v.pop());
178        assert_eq!(resp.pop(), None);
179    }
180
181    #[test]
182    fn multi_response() {
183        let mut v = Vec::<_, 3>::from_slice(&[
184            PDPContextState {
185                cid: 1,
186                status: PDPContextStatus::Deactivated,
187            },
188            PDPContextState {
189                cid: 2,
190                status: PDPContextStatus::Activated,
191            },
192            PDPContextState {
193                cid: 3,
194                status: PDPContextStatus::Deactivated,
195            },
196        ])
197        .unwrap();
198
199        let mut resp: Vec<PDPContextState, 3> =
200            serde_at::from_slice(b"+CGACT: 1,0\r\n+CGACT: 2,1\r\n+CGACT: 3,0").unwrap();
201
202        assert_eq!(resp.pop(), v.pop());
203        assert_eq!(resp.pop(), v.pop());
204        assert_eq!(resp.pop(), v.pop());
205        assert_eq!(resp.pop(), None);
206    }
207
208    #[test]
209    fn multi_response_advanced() {
210        let mut v = Vec::<_, 3>::from_slice(&[
211            PDPContextDefinition {
212                cid: 2,
213                pdp_type: String::try_from("IP").unwrap(),
214                apn: String::try_from("em").unwrap(),
215                pdp_addr: String::try_from("100.92.188.66").unwrap(),
216                d_comp: 0,
217                h_comp: 0,
218                ipv4_addr_alloc: Some(0),
219                emergency_indication: Some(0),
220                p_cscf_discovery: Some(0),
221                im_cn_signalling_flag_ind: Some(0),
222            },
223            PDPContextDefinition {
224                cid: 1,
225                pdp_type: String::try_from("IP").unwrap(),
226                apn: String::try_from("STATREAL").unwrap(),
227                pdp_addr: String::try_from("0.0.0.0").unwrap(),
228                d_comp: 0,
229                h_comp: 0,
230                ipv4_addr_alloc: None,
231                emergency_indication: None,
232                p_cscf_discovery: None,
233                im_cn_signalling_flag_ind: None,
234            },
235            PDPContextDefinition {
236                cid: 3,
237                pdp_type: String::try_from("IP").unwrap(),
238                apn: String::try_from("tim.ibox.it").unwrap(),
239                pdp_addr: String::try_from("0.0.0.0").unwrap(),
240                d_comp: 0,
241                h_comp: 0,
242                ipv4_addr_alloc: None,
243                emergency_indication: None,
244                p_cscf_discovery: None,
245                im_cn_signalling_flag_ind: None,
246            },
247        ])
248        .unwrap();
249
250        let mut resp: Vec<PDPContextDefinition, 3> =
251            serde_at::from_slice(b"+CGDCONT: 2,\"IP\",\"em\",\"100.92.188.66\",0,0,0,0,0,0\r\n+CGDCONT: 1,\"IP\",\"STATREAL\",\"0.0.0.0\",0,0\r\n+CGDCONT: 3,\"IP\",\"tim.ibox.it\",\"0.0.0.0\",0,0").unwrap();
252
253        assert_eq!(resp.pop(), v.pop());
254        assert_eq!(resp.pop(), v.pop());
255        assert_eq!(resp.pop(), v.pop());
256        assert_eq!(resp.pop(), None);
257    }
258}