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}