No description
  • Rust 95.5%
  • Shell 4.5%
Find a file
faicel 1dcc3f2c3f
All checks were successful
Create tag on dev / rust-crate-checks (push) Successful in 19s
Create tag on dev / checks (push) Successful in 0s
Publish release on tag / rust-crate-checks (push) Successful in 18s
Create tag on dev / tag_prerelease (push) Successful in 8s
Create tag on dev / tag (push) Successful in 0s
Publish release on tag / checks (push) Successful in 0s
Publish release on tag / release_on_tag (push) Successful in 13s
Publish release on tag / publish (push) Successful in 0s
Merge pull request 'fix/audit-perf' (#23) from fix/audit-perf into dev
Reviewed-on: #23
2026-09-05 22:47:08 +00:00
.cargo [0.3.1] - 2026-08-04 2026-08-04 11:55:28 +02:00
.forgejo - Documentation: add a README "Security characteristics" section stating 2026-09-06 00:43:35 +02:00
scripts - Documentation: add a README "Security characteristics" section stating 2026-09-06 00:43:35 +02:00
src - Documentation: add a README "Security characteristics" section stating 2026-09-06 00:43:35 +02:00
.gitignore - **Breaking**: migrate to the tools 1.x register API. The legacy 2026-09-05 15:50:53 +02:00
Cargo.lock - Documentation: add a README "Security characteristics" section stating 2026-09-06 00:43:35 +02:00
Cargo.toml - Documentation: add a README "Security characteristics" section stating 2026-09-06 00:43:35 +02:00
CHANGELOG.md - Documentation: add a README "Security characteristics" section stating 2026-09-06 00:43:35 +02:00
deny.toml - Documentation: add a README "Security characteristics" section stating 2026-09-06 00:43:35 +02:00
LICENSE init 2026-03-08 13:02:41 +01:00
README.md - Documentation: add a README "Security characteristics" section stating 2026-09-06 00:43:35 +02:00

lora

Important

The canonical repository for this project lives on Forgejo: https://code.bhk-itsolutions.com/homeiot/lora. This GitHub repository is only a mirror and is not the primary git remote.

no_std Rust driver for LoRa radio modules based on the SX1278 chip. It provides SPI-based control for transmission and reception with configurable operating modes, optional payload compression, and cooperative TX cancellation.

Features

  • no_std — Suitable for embedded targets without the standard library.
  • SX1278 support — Register-level driver for frequency, modem config, FIFO, and IRQ handling.
  • Operating modes — Transmitter, Receiver, Transceiver, Manual, and ReceiverSingle.
  • Deep sleep — deep_sleep() and wake_from_deep_sleep() for minimal chip power consumption (~0.2 µA).
  • TX/RX — Blocking send() and receive() with configurable timeouts; RSSI and SNR in receive results.
  • Compression — Payload compression/decompression (via the tools crate) for smaller over-the-air packets.
  • Cooperative cancellation — Optional atomic flag to cancel an ongoing transmission from another task or IRQ.
  • DIO0 IRQ — Optional binding of an atomic flag for TX_DONE/RX_DONE to integrate with your GPIO interrupt handler.

Cargo features

Feature Description
sx1278 Gates the sx1278 module (Controller and its API); lora::error is always available.

Enable at least sx1278 for normal use:

[dependencies]
# Stable (prod):
lora = { version = "0.4", registry = "forgejo", features = ["sx1278"] }
# Unstable (client staging), after a `dev` publish:
# lora = { version = "0.4", registry = "forgejorc", features = ["sx1278"] }

For publishing or depending from a Forgejo registry, configure forgejo / forgejorc as in this crate’s .cargo/config.toml. Publish / release flow: see .forgejo/README.md.

Usage example

use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::SpiBus;
use lora::sx1278::{Controller, Mode};

// SPI, NSS, and RST are your embedded_hal implementations (owned by the controller).
let mut controller = Controller::new(spi, nss, rst, &mut delay, Mode::Transceiver)?;
controller.set_frequency(433_000_000, &mut delay)?; // 433 MHz

// Send a message (blocking until TX_DONE or timeout).
controller.send(&message[..], &mut delay)?;

// Receive into a buffer (blocking until RX_DONE or timeout).
let result = controller.receive(&mut buffer[..], &mut delay)?;
// result.size, result.rssi (dBm), result.snr (dB)

// Put the radio in deep sleep when idle (configuration is retained).
controller.deep_sleep(&mut delay)?;

// send(), receive(), and handle_dio0_irq() wake the chip automatically.
// Call wake_from_deep_sleep() only if you need RX active before the next operation.
controller.wake_from_deep_sleep(&mut delay)?;

Stack and RAM usage

Controller embeds one 2048-byte LZSS window (lzss_window) shared by send (compression) and receive (decompression):

  • Persistent RAM: Controller is 2048 bytes larger than a struct without the window.
  • Transient stack: send and receive each keep the codec's LZSS window off the call stack — 2048 bytes less transient stack in send and 1024 bytes less in receive than the stack-window codec calls. The 255-byte local FIFO payload buffers remain on the stack in both paths.
  • Sharing is safe: the window is never active in both directions at the same time (both paths run under &mut self), and the tools codec fully re-initializes the window on every call.

With a statically placed Controller (RTIC resource / static) total peak RAM is roughly unchanged; a stack-allocated Controller grows its construction frame by 2048 bytes but loses the matching 2048-byte window spike inside send.

Blocking delays

The driver is blocking: construction, send, and receive busy-wait fixed intervals. Every interval is a named constant in src/sx1278/params.rs, and every one of them is empirical; origin unclear — measure before changing:

Constant Value Purpose
RESET_PULSE_MS 100 ms RST held low during the hardware reset in Controller::new
POST_RESET_SETTLE_MS 100 ms Settle after releasing RST, before the first register write
SLEEP_ENTRY_SETTLE_MS 100 ms Settle after switching the chip to LoRa sleep mode, before configuration
BOOT_VERSION_DELAY_MS 1000 ms Unconditional boot wait before the first version read in Controller::new
TX_START_DELAY_MS 1 ms Settle before switching the radio to TX mode in send
POST_TX_GUARD_MS 10 ms Guard after TX completes and the mode is restored in send
RX_ENTRY_SETTLE_MS 50 ms Settle after entering RX, before the IRQ flags are sampled in receive
ABORT_SETTLE_MS 5 ms Settle after clearing IRQ flags and FIFO pointers in the abort path, before the mode is restored
OSC_STARTUP_MS 5 ms Wait after leaving deep sleep for the oscillator to restart, before the mode is restored

Controller::new runs the first four delays, so initialization takes about 1.3 s in total. Every send ends with the 10 ms post-TX guard, and every receive call waits the RX poll interval before sampling the IRQ flags. That interval is runtime-configurable via Controller::set_rx_poll_interval_ms(ms) (getter: rx_poll_interval_ms()): it defaults to 50 ms (~20 polls/s) and 0 removes the pre-poll wait entirely, for IRQ-driven callers that only poll after a DIO0 interrupt.

Note that handle_dio0_irq wakes the chip automatically: when invoked while the chip is in deep sleep it runs the full wake sequence (a register write, the 5 ms oscillator wait, and a mode restore), not just the flag read.

To measure a delay on hardware, toggle a GPIO high immediately before the delay_ms call and low immediately after, then measure the high pulse with an oscilloscope or logic analyzer. Repeat for every constant you intend to change, and adjust the value only after measuring: the current figures were raised for stability and their origin is undocumented.

Security characteristics

The driver sends and receives payloads without message authentication or confidentiality: protection is limited to the LoRa PHY and the chip's payload CRC, which is an integrity check, not a cryptographic measure. The sync word (chip default 0x12) distinguishes traffic but is not an access control. Any application-level authentication or encryption layer is the application's responsibility.

Regulatory compliance is likewise the application's responsibility: the driver rejects frequencies outside the SX1278 LF band (137-525 MHz), but duty cycle and TX power are not enforced. The power defaults are conservative: PA_BOOST with output level 3, the normal PA_DAC setting, and over-current protection enabled.

Received-payload integrity is always on: the payload CRC is forced enabled by the driver and cannot be disabled through the public API; a packet that fails it raises LoRaError::CrcError instead of being delivered.

Project structure

lora/
├── src/
│   ├── lib.rs           # Crate root; re-exports error and sx1278
│   ├── error/           # LoRaError and error types
│   │   ├── mod.rs
│   │   ├── model.rs
│   │   └── tests/
│   │       └── mod.rs
│   └── sx1278/          # SX1278 driver (feature-gated)
│       ├── mod.rs       # Controller, Mode, ReceiveResult, TxTimeoutConfig
│       ├── params.rs    # Named timing constants and FIFO/payload limits
│       ├── registers.rs # Register addresses, modes, IRQ masks, constants
│       └── tests/       # Unit tests (send, receive, set_frequency, set_mode, version, etc.)
├── scripts/
│   └── test.sh          # Runs cargo test with sx1278 feature
├── Cargo.toml
├── LICENSE
└── README.md

Dependencies

  • embedded-hal 1.x — SPI, delay, and output pin traits.
  • log (default-features = false) — no_std-compatible logging.
  • tools (Forgejo registry) — Register bus access (register feature) and payload compression (compression feature); requires a configured Forgejo Cargo registry.

Building and testing

# Build with SX1278 support
cargo build --features sx1278

# Run tests (uses embedded-hal-mock)
cargo test --features sx1278

# Or use the project script
./scripts/test.sh

Status

  • Version: 0.4.0
  • Driver: SX1278 register interface for the 137-525 MHz band, blocking API: init, send, receive, set_frequency, set_mode, deep sleep, read_version.
  • Tests: 47 unit tests covering init, send (including timeout and cancellation), receive (including CRC, buffer size, and RSSI/SNR values), set_frequency, set_mode, deep sleep, and version read.

License

Personal Use Only — Non-Commercial. See LICENSE for full terms and disclaimers. Commercial use requires written permission from the copyright holder.