Skip to main content

at_parser_rs/
context.rs

1/***************************************************************************
2 *
3 * AT Command Parser
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
18 *
19 ***************************************************************************/
20 
21use crate::{Args, AtError, AtResult};
22
23/// Trait that defines the context for AT command execution.
24///
25/// Implement this trait for each AT command your device exposes.
26/// Each method corresponds to one of the four standard AT command forms.
27/// All methods have a default implementation that returns
28/// [`AtError::NotSupported`], so you only need to override the forms your
29/// command actually needs.
30///
31/// The const generic `SIZE` defines the capacity (in bytes) of the
32/// [`Bytes`](osal_rs::utils::Bytes) response buffer returned by each handler.
33/// All handlers registered in the same [`AtParser`](crate::parser::AtParser)
34/// must use the same `SIZE`.
35///
36/// # Example — minimal handler
37///
38/// ```rust,no_run
39/// use at_parser_rs::context::AtContext;
40/// use at_parser_rs::{AtResult, at_response};
41///
42/// const SIZE: usize = 64;
43///
44/// struct ResetModule;
45///
46/// impl AtContext<SIZE> for ResetModule {
47///     fn exec(&mut self, at_response: &'static str) -> AtResult<'_, SIZE> {
48///         // AT+RST performs a system reset
49///         Ok(at_response!(SIZE, at_response; "OK"))
50///     }
51/// }
52/// ```
53///
54/// # Example — full handler with all forms
55///
56/// ```rust,no_run
57/// use at_parser_rs::context::AtContext;
58/// use at_parser_rs::{Args, AtResult, AtError, at_response};
59///
60/// const SIZE: usize = 64;
61///
62/// struct EchoModule { enabled: bool }
63///
64/// impl AtContext<SIZE> for EchoModule {
65///     fn exec(&mut self, at_response: &'static str) -> AtResult<'_, SIZE> {
66///         Ok(at_response!(SIZE, at_response; if self.enabled { "ON" } else { "OFF" }))
67///     }
68///     fn query(&mut self, at_response: &'static str) -> AtResult<'_, SIZE> {
69///         Ok(at_response!(SIZE, at_response; if self.enabled { 1u8 } else { 0u8 }))
70///     }
71///     fn test(&mut self, at_response: &'static str) -> AtResult<'_, SIZE> {
72///         Ok(at_response!(SIZE, at_response; "(0,1)"))
73///     }
74///     fn set(&mut self, at_response: &'static str, args: Args) -> AtResult<'_, SIZE> {
75///         let value = args.get(0).ok_or((at_response, AtError::InvalidArgs))?;
76///         match value.as_ref() {
77///             "0" => { self.enabled = false; Ok(at_response!(SIZE, at_response; "OK")) }
78///             "1" => { self.enabled = true;  Ok(at_response!(SIZE, at_response; "OK")) }
79///             _ => Err((at_response, AtError::InvalidArgs)),
80///         }
81///     }
82/// }
83/// ```
84pub trait AtContext<const SIZE: usize> {
85
86    /// Execute command (`AT+CMD`)
87    ///
88    /// Called when the command is invoked without any suffix.
89    /// Use this to implement an action that does not require parameters.
90    ///
91    /// # Arguments
92    ///
93    /// * `at_response` — AT response prefix registered for this command (e.g. `"+RST: "`)
94    ///
95    /// # Returns
96    ///
97    /// * `Ok((at_response, Bytes<SIZE>))` — response to send back to the caller
98    /// * `Err((at_response, AtError::NotSupported))` — default when not overridden
99    ///
100    /// # Example
101    ///
102    /// ```rust,no_run
103    /// # use at_parser_rs::context::AtContext;
104    /// # use at_parser_rs::{AtResult, at_response};
105    /// # const SIZE: usize = 64;
106    /// struct PingModule;
107    ///
108    /// impl AtContext<SIZE> for PingModule {
109    ///     fn exec(&mut self, at_response: &'static str) -> AtResult<'_, SIZE> {
110    ///         Ok(at_response!(SIZE, at_response; "PONG"))
111    ///     }
112    /// }
113    /// // AT+PING  →  Ok(("+PING: ", "PONG"))
114    /// ```
115    fn exec(&mut self, at_response: &'static str) -> AtResult<'_, SIZE> {
116        Err((at_response, AtError::NotSupported))
117    }
118
119    /// Query command (`AT+CMD?`)
120    ///
121    /// Called to retrieve the current value or state of the command.
122    ///
123    /// # Arguments
124    ///
125    /// * `at_response` — AT response prefix registered for this command
126    ///
127    /// # Returns
128    ///
129    /// * `Ok((at_response, Bytes<SIZE>))` — current value/state
130    /// * `Err((at_response, AtError::NotSupported))` — default when not overridden
131    ///
132    /// # Example
133    ///
134    /// ```rust,no_run
135    /// # use at_parser_rs::context::AtContext;
136    /// # use at_parser_rs::{AtResult, at_response};
137    /// # const SIZE: usize = 64;
138    /// struct VolumeModule { level: u8 }
139    ///
140    /// impl AtContext<SIZE> for VolumeModule {
141    ///     fn query(&mut self, at_response: &'static str) -> AtResult<'_, SIZE> {
142    ///         Ok(at_response!(SIZE, at_response; self.level))
143    ///     }
144    /// }
145    /// // AT+VOL?  →  Ok(("+VOL: ", "75"))  (if level == 75)
146    /// ```
147    fn query(&mut self, at_response: &'static str) -> AtResult<'_, SIZE> {
148        Err((at_response, AtError::NotSupported))
149    }
150    
151    /// Test command (`AT+CMD=?`)
152    ///
153    /// Called to report whether a command is supported or to return the
154    /// valid parameter ranges accepted by [`set`](AtContext::set).
155    ///
156    /// # Arguments
157    ///
158    /// * `at_response` — AT response prefix registered for this command
159    ///
160    /// # Returns
161    ///
162    /// * `Ok((at_response, Bytes<SIZE>))` — human-readable description of valid parameters
163    /// * `Err((at_response, AtError::NotSupported))` — default when not overridden
164    ///
165    /// # Example
166    ///
167    /// ```rust,no_run
168    /// # use at_parser_rs::context::AtContext;
169    /// # use at_parser_rs::{AtResult, at_response};
170    /// # const SIZE: usize = 64;
171    /// struct VolumeModule { level: u8 }
172    ///
173    /// impl AtContext<SIZE> for VolumeModule {
174    ///     fn test(&mut self, at_response: &'static str) -> AtResult<'_, SIZE> {
175    ///         Ok(at_response!(SIZE, at_response; "(0-100)"))
176    ///     }
177    /// }
178    /// // AT+VOL=?  →  Ok(("+VOL: ", "(0-100)"))
179    /// ```
180    fn test(&mut self, at_response: &'static str) -> AtResult<'_, SIZE> {
181        Err((at_response, AtError::NotSupported))
182    }
183
184    /// Set command (`AT+CMD=<args>`)
185    ///
186    /// Called to configure the command with one or more parameters.
187    /// Arguments are accessible via [`Args::get`](crate::Args::get) using a
188    /// 0-based comma-separated index. Quoted arguments are unquoted and
189    /// escape sequences such as `\"` are decoded automatically.
190    ///
191    /// # Arguments
192    ///
193    /// * `at_response` — AT response prefix registered for this command
194    /// * `args` — parsed argument list; use `args.get(n)` to retrieve the
195    ///   n-th comma-separated token (0-indexed)
196    ///
197    /// # Returns
198    ///
199    /// * `Ok((at_response, Bytes<SIZE>))` — confirmation/response
200    /// * `Err((at_response, AtError::InvalidArgs))` — when arguments are missing or invalid
201    /// * `Err((at_response, AtError::NotSupported))` — default when not overridden
202    ///
203    /// # Example
204    ///
205    /// ```rust,no_run
206    /// # use at_parser_rs::context::AtContext;
207    /// # use at_parser_rs::{Args, AtResult, AtError, at_response};
208    /// # const SIZE: usize = 64;
209    /// struct VolumeModule { level: u8 }
210    ///
211    /// impl AtContext<SIZE> for VolumeModule {
212    ///     fn set(&mut self, at_response: &'static str, args: Args) -> AtResult<'_, SIZE> {
213    ///         let val: u8 = args.get(0)
214    ///             .ok_or((at_response, AtError::InvalidArgs))?
215    ///             .parse()
216    ///             .map_err(|_| (at_response, AtError::InvalidArgs))?;
217    ///         if val > 100 { return Err((at_response, AtError::InvalidArgs)); }
218    ///         self.level = val;
219    ///         Ok(at_response!(SIZE, at_response; "OK"))
220    ///     }
221    /// }
222    /// // AT+VOL=75   →  Ok(("+VOL: ", "OK"))
223    /// // AT+VOL=200  →  Err(("+VOL: ", InvalidArgs))
224    /// // AT+VOL=     →  Err(("+VOL: ", InvalidArgs))
225    /// ```
226    /// # use at_parser_rs::context::AtContext;
227    /// # use at_parser_rs::{Args, AtResult, AtError};
228    /// # use osal_rs::utils::Bytes;
229    /// # const SIZE: usize = 64;
230    /// struct VolumeModule { level: u8 }
231    ///
232    /// impl AtContext<SIZE> for VolumeModule {
233    ///     fn set(&mut self, at_response: &'static str, args: Args) -> AtResult<'_, SIZE> {
234    ///         let val: u8 = args.get(0)
235    ///             .ok_or(AtError::InvalidArgs)?
236    ///             .parse()
237    ///             .map_err(|_| AtError::InvalidArgs)?;
238    ///         if val > 100 { return Err(AtError::InvalidArgs); }
239    ///         self.level = val;
240    ///         Ok(Bytes::from_str("OK"))
241    ///     }
242    /// }
243    /// // AT+VOL=75   →  "OK"      (sets level to 75)
244    /// // AT+VOL=200  →  Err(InvalidArgs)
245    /// // AT+VOL=     →  Err(InvalidArgs)
246    /// ```
247    fn set(&mut self, at_response: &'static str, _args: Args) -> AtResult<'_, SIZE> {
248        Err((at_response, AtError::NotSupported))
249    }
250
251}