AT-Parser-RS
A lightweight, no_std AT command parser library for embedded Rust applications.
Overview
AT-Parser-RS provides a flexible framework for implementing AT command interfaces in embedded systems. It supports the standard AT command syntax including execution, query, test, and set operations.
Features
no_stdcompatible - suitable for bare-metal and embedded environments- Fixed-size response buffers via
Bytes<SIZE>— no heap allocation - Support for all AT command forms:
AT+CMD- Execute commandAT+CMD?- Query current valueAT+CMD=?- Test supported valuesAT+CMD=<args>- Set new value(s)
- Type-safe command registration via traits
- Static command definitions (suitable for embedded/RTOS)
Feature Flags
The library supports the following optional features:
freertos(default) — Enable FreeRTOS support via osal-rs.posix— Enable POSIX (Linux/macOS) threading support via osal-rs.std— Enable standard library support via osal-rs.disable_panic— Pass-through feature to osal-rs; disables the built-in panic handler.
By default the freertos feature is enabled.
# Build with FreeRTOS support (default)
# Build with POSIX support
# Build with std support
# Disable the default panic handler
Command Forms
The parser supports four standard AT command forms:
| Form | Syntax | Purpose | Example |
|---|---|---|---|
| Execute | AT+CMD |
Execute an action | AT+RST |
| Query | AT+CMD? |
Get current setting | AT+ECHO? |
| Test | AT+CMD=? |
Get supported values | AT+ECHO=? |
| Set | AT+CMD=<args> |
Set new value(s) | AT+ECHO=1 |
Note: All commands must start with the
ATprefix (e.g.,AT+CMD, not just+CMD). The parser expects the full AT command syntax.
Core Types
AtContext<SIZE> Trait
The main trait for implementing command handlers. The const generic SIZE defines the response buffer size in bytes. Override only the methods your command needs:
The at_response parameter is the AT response prefix string (e.g. "+ECHO: ") that was
registered alongside the command. Pass it through to Ok(...) / Err(...) so the caller
can format the full response line. Use the at_response! macro for
convenient formatting.
All methods return Err((at_response, AtError::NotSupported)) by default.
AtResult<'a, SIZE> and AtError<'a>
// Both Ok and Err carry the AT response prefix together with the payload
pub type AtResult<'a, const SIZE: usize> =
;
The first element of the tuple is always the AT response prefix (at_response) received from
the parser, so callers can reconstruct the full response line regardless of whether the
call succeeded or failed.
Use Unhandled when you have a static string literal, and UnhandledOwned when you need
to construct an error message dynamically at runtime.
Bytes<SIZE>
Bytes<SIZE> is a fixed-size byte buffer from osal-rs (re-exported by this crate) used to return responses without heap allocation:
use Bytes;
// Create from a string slice (truncated to SIZE if longer)
let response = from_str;
AtParser<T, SIZE>
The parser is generic over both the handler type T and the response buffer size SIZE:
;
Commands are registered as 3-tuples: (at_command, at_response, handler) where
at_command is the string the parser matches against (e.g. "AT+ECHO") and
at_response is the prefix forwarded to the handler (e.g. "+ECHO: "). These can be the
same string or different—choose whatever your protocol requires.
Args Structure
Provides access to comma-separated arguments:
Usage Examples
1. Define Command Modules
Implement the AtContext<SIZE> trait for your command handlers. Choose a buffer size that fits your largest response string.
Every method receives the at_response prefix that was registered for this command so you
can include it in the response (use the at_response! macro for convenience):
use AtContext;
use ;
use Bytes;
const SIZE: usize = 64;
/// Echo command - returns/sets echo state
/// Reset command - executes system reset
;
2. Create Module Instances
For standard applications, create instances on the stack:
let mut echo = EchoModule ;
let mut reset = ResetModule;
For embedded/no_std environments with static mut (single-threaded only):
static mut ECHO: EchoModule = EchoModule ;
static mut RESET: ResetModule = ResetModule;
Note:
static mutrequiresunsafeblocks and is only safe in single-threaded contexts. For RTOS or multi-threaded applications, use proper synchronization primitives.
3. Initialize Parser and Register Commands
Commands are registered as 3-tuples: (at_command, at_response_prefix, handler).
use AtParser;
use AtContext;
const SIZE: usize = 64;
let mut parser: = new;
let commands: &mut = &mut ;
parser.set_commands;
4. Execute Commands
execute returns Ok((prefix, bytes)) on success or Err((prefix, error)) on failure,
where prefix is the AT response prefix registered for that command.
// Execute: return current state
match parser.execute
// Test: show valid values
match parser.execute
// Set: enable echo
match parser.execute
// Query: get current value
match parser.execute
// Unknown command → Err(("" , AtError::UnknownCommand))
match parser.execute
Bytes<SIZE> implements Display, so it can be printed directly with {} or converted
to a string via .to_string().
Advanced Example: UART Module
use ;
use AtContext;
const SIZE: usize = 64;
Usage:
// Register: ("AT+UART", "+UART: ", &mut uart)
parser.execute; // Ok(("+UART: ", "<baudrate: 9600-115200>,<data_bits: 7|8>"))
parser.execute; // Ok(("+UART: ", "OK"))
parser.execute; // Ok(("+UART: ", "115200,8"))
Parsing Arguments
The Args structure provides a simple interface for accessing comma-separated arguments.
Quoted values are treated as a single argument, so commas inside "..." do not split the field.
When a quoted argument contains \", Args::get() returns the decoded " character:
Important: Args::get() uses 0-based indexing. For a command like AT+CMD=foo,bar,baz:
args.get(0).as_deref()returnsSome("foo")args.get(1).as_deref()returnsSome("bar")args.get(2).as_deref()returnsSome("baz")args.get(3)returnsNone
For a command like AT+SESS=i,"ciao, sono \"antonio\"",mysecretpassword:
args.get(0).as_deref()returnsSome("i")args.get(1).as_deref()returnsSome("ciao, sono \"antonio\"")args.get_raw(1)returnsSome("ciao, sono \\\"antonio\\\"")args.get(2).as_deref()returnsSome("mysecretpassword")
For numeric arguments:
let value = args.get
.ok_or?
.
.map_err?;
Use Args::get_raw() only when you explicitly need the original escaped content from a
quoted argument:
let name = args.get
.ok_or?;
assert_eq!;
Thread Safety
Single-threaded (bare-metal)
static mut MODULE: MyModule = new;
// Safe in single-threaded context
Multi-threaded (RTOS)
use RefCell;
use Mutex;
static MODULE: = new;
at_response! Macro
Constructs an Ok((&'static str, Bytes<SIZE>)) value from a response prefix and 1–6
comma-separated arguments:
use at_response;
const SIZE: usize = 64;
// Single value
let r = at_response!; // ("+ECHO: ", "1")
// Two values
let r = at_response!; // ("+LED: ", "1,75")
// Three values
let r = at_response!;
at_quoted! Macro
Wraps a value in double-quote characters, useful inside at_response! when the
protocol requires quoted strings:
use ;
const SIZE: usize = 64;
let ssid = "MyNetwork";
let r = at_response!;
// ("+WIFI: ", "\"MyNetwork\",-70")
Using the at_modules! Macro
The library provides an at_modules! macro for defining static command arrays.
Each entry is a 3-tuple: (at_command, at_response) => HANDLER.
use at_modules;
use AtContext;
const SIZE: usize = 64;
static mut ECHO: EchoModule = EchoModule ;
static mut RESET: ResetModule = ResetModule;
at_modules!
// COMMANDS is now available: parser.set_commands(COMMANDS);
Limitations and Considerations
⚠️ Important: This macro has significant limitations:
- Unsafe: The macro creates mutable references to static data, requiring
unsafeblocks - Single-threaded only: Not suitable for multi-threaded or RTOS environments
- Limited flexibility: Cannot mix different command handler types
Recommended Alternative
For most applications, the manual slice approach is preferred:
use AtContext;
use AtParser;
const SIZE: usize = 64;
let mut echo = EchoModule ;
let mut reset = ResetModule;
let commands: &mut = &mut ;
parser.set_commands;
This approach is safer, more flexible, and works in all contexts (stack, heap, RTOS).
Best Practices
- Choose an appropriate
SIZE: Pick a buffer size that fits your largest response string; responses longer thanSIZEare silently truncated - Validate arguments: Always check argument count and validity before processing
- Handle errors gracefully: Use appropriate
AtErrorvariants for different failure modes. UseAtError::Unhandled("msg")for static string descriptions andAtError::UnhandledOwned(string)for dynamically constructed messages - Document test responses: Use
test()to provide clear usage information - Minimize state: Keep module state simple and thread-safe
Examples
The library includes several example files demonstrating different usage patterns:
Standard Examples
complete_usage.rs- Complete demonstration with multiple command types (Echo, Reset, Info, LED)basic_parser.rs- Shows direct usage of theAtParserwith comprehensive test cases
Embedded/no_std Examples
These examples demonstrate code patterns suitable for no_std environments:
embedded_basic.rs- Basic patterns and error handling for no_std/embedded environmentsembedded_error_handling.rs- Patterns for custom error handling and type conversionsembedded_uart_config.rs- UART and device configuration patterns withAtContextimplementation
Note: The embedded examples are designed to show code patterns and best practices rather than being fully functional standalone programs. They demonstrate how to structure code for embedded/no_std contexts.
Run examples with:
# Standard examples (fully functional)
# Embedded examples (demonstrate patterns)
License
This project is licensed under the GNU Lesser General Public License v2.1 or later (LGPL-2.1-or-later) - see the LICENSE file for details.