simx00x 0.1.0

A no-std and no-alloc driver for SIM800L GSM modules (and probably similar modules)
Documentation

simx00x - A no_std driver for SIM800L GSM module

Crates.io Documentation License

A no_std, no_alloc driver for the SIM800L GSM module. Provides support for SMS messaging, voice calls, and USSD operations through a state‑machine based architecture.

Compatibility and Disclaimer

At the moment, this crate has only been tested with SIM800L. Similar modules like SIM900L should work as well, but they have to be tested.

This software is provided "as is," without warranty of any kind. Use this crate at your own risk. The SIM800L module is sensitive to power supply fluctuations and logic levels; incorrect wiring or software configuration can result in permanent hardware damage.

The author(s) and contributors shall not be held liable for any damage to your hardware, data loss, or any other misfortunes resulting from the use of this library. By using this crate, you acknowledge that you are responsible for ensuring your hardware setup (especially power regulation and level shifting) is safe and within the manufacturer's specified operating conditions.

Features

  • no_std and no_alloc: Suitable for embedded systems without heap allocation.
  • SMS Support: Send and receive SMS messages with queueing.
  • Voice Calls: Make and receive calls, answer/hang up.
  • USSD Operations: Send USSD codes and receive responses.
  • Event‑driven: Callbacks for incoming SMS, calls, USSD responses, and module stalls.
  • Configurable: SIM PIN, auto‑reset on stall, SMS queue size, and all callbacks.
  • Embedded‑io Compatible: Works with any UART implementing embedded_io traits.

Dependencies

Add to your Cargo.toml:

[dependencies]
simx00x = "0.1"
simx00x = { version = "0.1", features = ["defmt"] } # With `defmt` logging feature enabled

The defmt feature enables structured logging through the defmt framework.

Quick Start

use embedded_io::{Read, ReadReady, Write, WriteReady};
use simx00x::{Config, SimX00X};

// 1. Define a UART that implements embedded_io traits.
struct MyUart { /* ... */ }
impl embedded_io::ErrorType for MyUart { type Error = MyUartError; }
impl Read for MyUart { /* ... */ }
impl Write for MyUart { /* ... */ }
impl ReadReady for MyUart { /* ... */ }
impl WriteReady for MyUart { /* ... */ }

// 2. Prepare callbacks (they must live at least as long as the Config).
let mut stalled = || {
    // Return true to auto-reset, false otherwise.
    true
};
let mut sms_cb = |sender: &str, message: &str| { /* handle incoming SMS */ };
let mut call_cb = |caller: &str| -> bool {
    // return true to auto‑answer, false to ignore
    true
};
let mut ussd_cb = |response: &str| { /* handle USSD response */ };
let mut conn_cb = || { /* call connected */ };
let mut disc_cb = || { /* call disconnected */ };

// 3. Create configuration with callbacks and a queue size of 10.
let config = Config::<10>::new()
    .with_sim_card_pin("1234")                 // optional SIM PIN
    .with_auto_reset_when_stalled(true)
    .with_module_stalled_callback(&mut stalled)
    .with_received_sms_callback(&mut sms_cb)
    .with_received_call_callback(&mut call_cb)
    .with_received_ussd_callback(&mut ussd_cb)
    .with_call_connected_callback(&mut conn_cb)
    .with_call_disconnected_callback(&mut disc_cb);

// 4. Initialise the driver.
let uart = MyUart::new();
let mut sim = SimX00X::new(uart, config);

// 5. Main loop.
loop {
    // Process incoming UART data.
    sim.tick().unwrap();
    
    // Advance the state machine.
    sim.update().unwrap();
    
    // Small delay (e.g., `cortex_m::asm::delay` or timer).
}

Usage

Initialisation

use simx00x::{Config, SimX00X};

// Your UART must implement:
//   embedded_io::Write + embedded_io::WriteReady
//   embedded_io::Read  + embedded_io::ReadReady
let uart = /* ... */;

// Prepare callbacks (must outlive the Config).
let mut sms_callback = |sender: &str, message: &str| { /* ... */ };
// Return true here to answer the incoming call.
let mut call_callback = |caller: &str| -> bool { /* ... */ };

// Build configuration with queue size 20.
let config = Config::<20>::new()
    .with_sim_card_pin("0000")      // if SIM has PIN protection
    .with_auto_reset_when_stalled(true)
    .with_received_sms_callback(&mut sms_callback)
    .with_received_call_callback(&mut call_callback);

let mut sim = SimX00X::new(uart, config);

Sending an SMS

// Enqueue an SMS for sending.
sim.enqueue_sms("+1234567890", "Hello from Rust!").unwrap();

The SMS will be sent when the module is ready. Multiple SMSes can be queued up to the configured queue size.

Making a Call

// Dial a number.
sim.dial("+1234567890").unwrap();

Answering/Hanging Up Calls

// Answer incoming call (usually triggered by the received‑call callback).
sim.answer_call();

// Hang up ongoing call.
sim.hang_up_call();

Sending USSD

// Send a USSD code.
sim.send_ussd("*123#").unwrap();

API Overview

SimX00X Struct

The main driver struct:

pub struct SimX00X<
    'a,
    U: embedded_io::Write + embedded_io::WriteReady
       + embedded_io::Read  + embedded_io::ReadReady,
    const QUEUE_LEN: usize,
>

All callbacks are supplied via the Config; the driver itself has no registration methods.

Key Methods

  • new(uart: U, config: Config<'a, QUEUE_LEN>) -> Self – create a new instance.
  • tick(&mut self) – process incoming UART data.
  • update(&mut self) – advance the internal state machine.
  • enqueue_sms(&mut self, recipient: &str, sms: &str) – queue an SMS for sending.
  • dial(&mut self, number: &str) – initiate a call.
  • answer_call(&mut self) – answer an incoming call.
  • hang_up_call(&mut self) – hang up the ongoing call.
  • send_ussd(&mut self, ussd: &str) – send a USSD code.
  • reset(&mut self) – reset the driver state.

Configuration

The Config struct holds all settings and callbacks:

pub struct Config<'a, const QUEUE_LEN: usize> {
    pub sim_card_pin: Option<&'a str>,
    pub auto_reset_when_stalled: bool,
    // ... callbacks stored as `Option<&'a mut dyn FnMut(...)>`
}

Builder methods:

  • with_sim_card_pin(pin: &'a str)
  • with_auto_reset_when_stalled(auto_reset: bool)
  • with_module_stalled_callback(callback: &'a mut impl FnMut() -> bool)
  • with_received_sms_callback(callback: &'a mut impl FnMut(&str, &str))
  • with_received_call_callback(callback: &'a mut impl FnMut(&str) -> bool)
  • with_call_connected_callback(callback: &'a mut impl FnMut())
  • with_call_disconnected_callback(callback: &'a mut impl FnMut())
  • with_received_ussd_callback(callback: &'a mut impl FnMut(&str))

A default configuration for queue size 10 is available as Config::<10>::default().

Error Handling

The driver returns Result<(), SimError<U>> where U is your UART type. Error variants:

  • IOError – UART communication failure.
  • FmtError – String formatting error.
  • StringCapacityError – String length exceeds capacity.
  • SmsQueueLimitReached – SMS queue is full.
  • ModuleStalled – Module not responding.
  • PinNeeded – SIM requires PIN but none provided.
  • AtError – AT command failed (includes the state where it occurred).

State Machine

The driver uses an internal state machine to manage module interactions. States include initialisation, PIN checking, SMS sending, call handling, and error recovery. The update() method progresses the state machine, while tick() processes responses.

State machine

Examples

See the examples/ directory for a mock example that demonstrates the driver usage without hardware.

Testing

The crate includes unit tests for core functionality. Run with:

cargo test --features defmt   # with defmt logging
cargo test                    # without defmt

License

Licensed under either of:

at your option.

Acknowledgements

This crate is heavily inspired by the module integrated in esphome. The original code in C++ can be found here.