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::{Args, AtResult, AtError};
41/// use osal_rs::utils::Bytes;
42///
43/// const SIZE: usize = 64;
44///
45/// struct ResetModule;
46///
47/// impl AtContext<SIZE> for ResetModule {
48/// fn exec(&mut self) -> AtResult<'_, SIZE> {
49/// // AT+RST performs a system reset
50/// Ok(Bytes::from_str("OK"))
51/// }
52/// }
53/// ```
54///
55/// # Example — full handler with all forms
56///
57/// ```rust,no_run
58/// use at_parser_rs::context::AtContext;
59/// use at_parser_rs::{Args, AtResult, AtError};
60/// use osal_rs::utils::Bytes;
61///
62/// const SIZE: usize = 64;
63///
64/// struct EchoModule { enabled: bool }
65///
66/// impl AtContext<SIZE> for EchoModule {
67/// fn exec(&mut self) -> AtResult<'_, SIZE> {
68/// let s = if self.enabled { "ECHO: ON" } else { "ECHO: OFF" };
69/// Ok(Bytes::from_str(s))
70/// }
71/// fn query(&mut self) -> AtResult<'_, SIZE> {
72/// Ok(Bytes::from_str(if self.enabled { "1" } else { "0" }))
73/// }
74/// fn test(&mut self) -> AtResult<'_, SIZE> {
75/// Ok(Bytes::from_str("(0,1)"))
76/// }
77/// fn set(&mut self, args: Args) -> AtResult<'_, SIZE> {
78/// let value = args.get(0).ok_or(AtError::InvalidArgs)?;
79/// match value.as_ref() {
80/// "0" => { self.enabled = false; Ok(Bytes::from_str("OK")) }
81/// "1" => { self.enabled = true; Ok(Bytes::from_str("OK")) }
82/// _ => Err(AtError::InvalidArgs),
83/// }
84/// }
85/// }
86/// ```
87pub trait AtContext<const SIZE: usize> {
88
89 /// Execute command (`AT+CMD`)
90 ///
91 /// Called when the command is invoked without any suffix.
92 /// Use this to implement an action that does not require parameters.
93 ///
94 /// # Returns
95 ///
96 /// * `Ok(Bytes<SIZE>)` — response to send back to the caller
97 /// * `Err(AtError::NotSupported)` — default when not overridden
98 ///
99 /// # Example
100 ///
101 /// ```rust,no_run
102 /// # use at_parser_rs::context::AtContext;
103 /// # use at_parser_rs::{AtResult};
104 /// # use osal_rs::utils::Bytes;
105 /// # const SIZE: usize = 64;
106 /// struct PingModule;
107 ///
108 /// impl AtContext<SIZE> for PingModule {
109 /// fn exec(&mut self) -> AtResult<'_, SIZE> {
110 /// Ok(Bytes::from_str("PONG"))
111 /// }
112 /// }
113 /// // AT+PING → "PONG"
114 /// ```
115 fn exec(&mut self) -> AtResult<'_, SIZE> {
116 Err(AtError::NotSupported)
117 }
118
119 /// Query command (`AT+CMD?`)
120 ///
121 /// Called to retrieve the current value or state of the command.
122 ///
123 /// # Returns
124 ///
125 /// * `Ok(Bytes<SIZE>)` — current value/state
126 /// * `Err(AtError::NotSupported)` — default when not overridden
127 ///
128 /// # Example
129 ///
130 /// ```rust,no_run
131 /// # use at_parser_rs::context::AtContext;
132 /// # use at_parser_rs::{AtResult};
133 /// # use osal_rs::utils::Bytes;
134 /// # const SIZE: usize = 64;
135 /// struct VolumeModule { level: u8 }
136 ///
137 /// impl AtContext<SIZE> for VolumeModule {
138 /// fn query(&mut self) -> AtResult<'_, SIZE> {
139 /// let mut buf = Bytes::new();
140 /// buf.format(core::format_args!("{}", self.level));
141 /// Ok(buf)
142 /// }
143 /// }
144 /// // AT+VOL? → "75" (if level == 75)
145 /// ```
146 fn query(&mut self) -> AtResult<'_, SIZE> {
147 Err(AtError::NotSupported)
148 }
149
150 /// Test command (`AT+CMD=?`)
151 ///
152 /// Called to report whether a command is supported or to return the
153 /// valid parameter ranges accepted by [`set`](AtContext::set).
154 ///
155 /// # Returns
156 ///
157 /// * `Ok(Bytes<SIZE>)` — human-readable description of valid parameters
158 /// * `Err(AtError::NotSupported)` — default when not overridden
159 ///
160 /// # Example
161 ///
162 /// ```rust,no_run
163 /// # use at_parser_rs::context::AtContext;
164 /// # use at_parser_rs::{AtResult};
165 /// # use osal_rs::utils::Bytes;
166 /// # const SIZE: usize = 64;
167 /// struct VolumeModule { level: u8 }
168 ///
169 /// impl AtContext<SIZE> for VolumeModule {
170 /// fn test(&mut self) -> AtResult<'_, SIZE> {
171 /// Ok(Bytes::from_str("(0-100)"))
172 /// }
173 /// }
174 /// // AT+VOL=? → "(0-100)"
175 /// ```
176 fn test(&mut self) -> AtResult<'_, SIZE> {
177 Err(AtError::NotSupported)
178 }
179
180 /// Set command (`AT+CMD=<args>`)
181 ///
182 /// Called to configure the command with one or more parameters.
183 /// Arguments are accessible via [`Args::get`](crate::Args::get) using a
184 /// 0-based comma-separated index. Quoted arguments are unquoted and
185 /// escape sequences such as `\"` are decoded automatically.
186 ///
187 /// # Arguments
188 ///
189 /// * `args` — parsed argument list; use `args.get(n)` to retrieve the
190 /// n-th comma-separated token (0-indexed)
191 ///
192 /// # Returns
193 ///
194 /// * `Ok(Bytes<SIZE>)` — confirmation/response
195 /// * `Err(AtError::InvalidArgs)` — when arguments are missing or invalid
196 /// * `Err(AtError::NotSupported)` — default when not overridden
197 ///
198 /// # Example
199 ///
200 /// ```rust,no_run
201 /// # use at_parser_rs::context::AtContext;
202 /// # use at_parser_rs::{Args, AtResult, AtError};
203 /// # use osal_rs::utils::Bytes;
204 /// # const SIZE: usize = 64;
205 /// struct VolumeModule { level: u8 }
206 ///
207 /// impl AtContext<SIZE> for VolumeModule {
208 /// fn set(&mut self, args: Args) -> AtResult<'_, SIZE> {
209 /// let val: u8 = args.get(0)
210 /// .ok_or(AtError::InvalidArgs)?
211 /// .parse()
212 /// .map_err(|_| AtError::InvalidArgs)?;
213 /// if val > 100 { return Err(AtError::InvalidArgs); }
214 /// self.level = val;
215 /// Ok(Bytes::from_str("OK"))
216 /// }
217 /// }
218 /// // AT+VOL=75 → "OK" (sets level to 75)
219 /// // AT+VOL=200 → Err(InvalidArgs)
220 /// // AT+VOL= → Err(InvalidArgs)
221 /// ```
222 fn set(&mut self, _args: Args) -> AtResult<'_, SIZE> {
223 Err(AtError::NotSupported)
224 }
225
226}