Skip to main content

at_parser_rs/
lib.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
21//! AT Command Parser Library
22//!
23//! This library provides a flexible parser for AT commands, commonly used in
24//! embedded systems and communication devices. It supports `no_std` environments.
25//!
26//! # Architecture
27//!
28//! The library is built around three core components:
29//!
30//! - **[`AtParser`](parser::AtParser)** - The main parser that processes AT command strings
31//! - **[`AtContext`](context::AtContext)** - Trait for implementing command handlers
32//! - **[`Args`]** - Structure for accessing command arguments
33//!
34//! # Command Forms
35//!
36//! Supports all standard AT command forms:
37//! - `AT+CMD` - Execute (action without parameters)
38//! - `AT+CMD?` - Query (get current value/state)
39//! - `AT+CMD=?` - Test (get supported values/ranges)
40//! - `AT+CMD=<args>` - Set (configure with parameters)
41//!
42//! # Quick Start
43//!
44//! ```rust,no_run
45//! use at_parser_rs::context::AtContext;
46//! use at_parser_rs::parser::AtParser;
47//! use at_parser_rs::{Args, AtResult, AtError, Bytes};
48//!
49//! const SIZE: usize = 64;
50//!
51//! // 1. Define a command handler
52//! struct EchoModule { echo: bool }
53//!
54//! impl AtContext<SIZE> for EchoModule {
55//!     fn query(&mut self) -> AtResult<SIZE> {
56//!         if self.echo { Ok(Bytes::from_str("1")) } else { Ok(Bytes::from_str("0")) }
57//!     }
58//!     
59//!     fn set(&mut self, args: Args) -> AtResult<SIZE> {
60//!         match args.get(0) {
61//!             Some("0") => { self.echo = false; Ok(Bytes::from_str("OK")) }
62//!             Some("1") => { self.echo = true; Ok(Bytes::from_str("OK")) }
63//!             _ => Err(AtError::InvalidArgs),
64//!         }
65//!     }
66//! }
67//!
68//! // 2. Create parser and register commands
69//! let mut echo = EchoModule { echo: false };
70//! let mut parser: AtParser<EchoModule, SIZE> = AtParser::new();
71//!
72//! let commands: &mut [(&str, &mut dyn AtContext<SIZE>)] = &mut [
73//!     ("AT+ECHO", &mut echo),
74//! ];
75//! parser.set_commands(commands);
76//!
77//! // 3. Execute commands
78//! parser.execute("AT+ECHO=1");  // Set echo on
79//! parser.execute("AT+ECHO?");   // Query current state
80//! ```
81//!
82//! # Features
83//!
84//! - **`freertos`** (default) - Enable FreeRTOS support via osal-rs
85//! - **`posix`** - Enable POSIX support via osal-rs
86//! - **`std`** - Enable standard library support via osal-rs
87//! - **`disable_panic`** - Pass-through feature to osal-rs for panic handling
88//!
89//! # Thread Safety
90//!
91//! The library can be used in single-threaded (bare-metal) or multi-threaded (RTOS)
92//! environments. For RTOS, use appropriate synchronization primitives around
93//! command handlers (e.g., `Mutex<RefCell<Handler>>`).
94
95#![no_std]
96
97extern crate alloc;
98extern crate osal_rs;
99
100use core::iter::Iterator;
101use core::option::Option;
102use core::result::Result;
103
104pub use osal_rs::utils::Bytes;
105
106pub mod context;
107pub mod parser;
108
109
110/// Error types that can occur during AT command processing
111#[derive(Debug)]
112pub enum AtError {
113    /// The command is not recognized
114    UnknownCommand,
115    /// The command is recognized but not supported
116    NotSupported,
117    /// The command arguments are invalid
118    InvalidArgs,
119}
120
121/// Result type for AT command operations
122/// Returns either a `Bytes<SIZE>` response buffer or an `AtError`
123pub type AtResult<const SIZE: usize> = Result<Bytes<SIZE>, AtError>;
124
125/// Structure holding the arguments passed to an AT command
126pub struct Args<'a> {
127    /// Raw argument string (comma-separated values)
128    pub raw: &'a str,
129}
130
131impl<'a> Args<'a> {
132    /// Get an argument by index (0-based)
133    /// Arguments are separated by commas
134    pub fn get(&self, index: usize) -> Option<&'a str> {
135        self.raw.split(',').nth(index)
136    }
137}
138
139
140/// Macro to define AT command modules
141///
142/// Creates a static array of command names and their associated context handlers.
143///
144/// # Warning
145///
146/// This macro uses `unsafe` to create mutable references to static data.
147/// It is only suitable for single-threaded embedded contexts.
148///
149/// # Limitations
150///
151/// - **Unsafe**: Requires `unsafe` blocks to use
152/// - **Single-threaded only**: Not safe for RTOS or multi-threaded environments
153/// - **Limited flexibility**: Cannot mix different handler types
154///
155/// # Example
156///
157/// ```rust,no_run
158/// use at_parser_rs::at_modules;
159/// use at_parser_rs::context::AtContext;
160///
161/// const SIZE: usize = 64;
162///
163/// static mut ECHO: EchoModule = EchoModule { echo: false };
164/// static mut RESET: ResetModule = ResetModule;
165///
166/// at_modules! {
167///     SIZE;
168///     "AT+ECHO" => ECHO,
169///     "AT+RST" => RESET,
170/// }
171/// ```
172///
173/// # Recommended Alternative
174///
175/// For most use cases, prefer the manual approach:
176///
177/// ```rust,no_run
178/// const SIZE: usize = 64;
179/// let commands: &mut [(&str, &mut dyn AtContext<SIZE>)] = &mut [
180///     ("AT+ECHO", &mut echo_handler),
181///     ("AT+RST", &mut reset_handler),
182/// ];
183/// parser.set_commands(commands);
184/// ```
185#[macro_export]
186macro_rules! at_modules {
187    (
188        $size:expr;
189        $( $name:expr => $module:ident ),* $(,)?
190    ) => {
191        static COMMANDS: &[(&'static str, &mut dyn AtContext<$size>)] = unsafe {
192            &[
193                $(
194                    ($name, &mut $module),
195                )*
196            ]
197        };
198    };
199}