rusty_esp_signal 0.3.0

The radio-application layer remade in Rust: Wi-Fi CSI radar (presence/motion), LoRa point-to-point over lora-phy, BLE provisioning and telemetry over trouble-host, Wi-Fi station/AP lifecycle and ESP-NOW framing — every frame mID-signed. Memory safe, no_std core.
Documentation

In The Wild with 39 Active Installs

FREE RAG Converter Online -- RAGconverter.com

rusty_esp_signal

Remade With Rust By Mata Network crates.io docs.rs License: MIT OR Apache-2.0

Radios and sensing for the Janus ESP32 family: Wi-Fi station policy, provisioning a device that has no network yet, a peer-to-peer link for chips with no access point, long-range radio, presence from channel state, and a millimetre-wave radar. Pure Rust, no C, no FFI, no_std by default.

  • A device with no network can still be given one. A browser hands an ESP32 its network over Bluetooth, on a page with no app and no server, inside an encrypted session that a setup code unlocks (SPAKE2+, the device proving its did:mata): the passphrase never crosses the air in the clear and never reaches a server, a log, or a command line. Verified from Chrome on an ESP32-S3 and an ESP32-CAM: a whole session costs the device 0.27 s on the S3 and 0.82 s on the original ESP32, and the board joins in about a second.
  • Presence from the Wi-Fi channel itself, judged against a labelled public capture rather than our own recording: an empty room reads 17.4% occupancy and a walking person 86.1%, with a held-out pair at 60.8%.
  • A link where there is no access point. Peer-to-peer framing sized to fit: 227 bytes of payload inside a 250-byte datagram, with hello, accept and confirm at 84, 100 and 18 bytes.
  • The radar's own documentation is wrong, and the tests say where. Field offsets in the vendor datasheet do not match the vendor's tool; ours follow the wire.

What has run on hardware

what measured
the setup session over Bluetooth, from Chrome (XIAO ESP32-S3, trouble-host) the device's time 0.27 s a session (Start 260 ms); joined in 995 ms, restarted without Bluetooth, the camera page on its token
the same on the ESP32-CAM (Bluedroid) 0.82 s a session (Start 695-790 ms, the portable P-256 path); a mistyped passphrase offers setup again; the page 200 on its token, 403 without
refusals, from an independent central (tools/setup_central.py) a wrong code, another device, a replayed session and a second writer refused; five wrong codes lock the window, and on the next power-on one more guess
provisioning over Bluetooth (the unencrypted version, now retired) joined in 7.5 s; the reboot joins alone in 9.5 s with no Bluetooth
the identity behind it the same did:mata held across six reflashes
the advertisement budget 31 bytes, which is what forced the name and service layout
a failed join must not spend the modem — a device that cannot join has to stay askable

A known gap, stated plainly: channel-state presence and the peer-to-peer link are verified against captures and against the host, not yet on two chips talking to each other. Those rows need boards that are not on this bench, and the ledger says so rather than implying otherwise.

Every number, with the run that produced it: docs/LEDGER.md.

Using it

use rusty_esp_signal::prelude::*;

use rusty_esp_signal::provision::Env;
use rusty_esp_signal::setup::Reset;
use rusty_esp_signal::wifi::{Action, Event};

// The owner's settings, the identity store, randomness and the device key.
let env = Env { settings, identity, rng, signer };
let mut provisioner = Provisioner::new(StationPolicy::default(), devpub, Reset::PowerOn, now, env)?;

// A stored network is joined at once; without one, the setup window is open
// and the BLE backend routes the `setup` and `discover` characteristics here.
// A setup code unlocks the session; only then does a network go in.
if provisioner.boot(now).action == Action::Connect {
    let event = match wifi.join(provisioner.credentials().unwrap()) {
        Ok(()) => Event::Connected,
        // A refusal has to leave the device askable, not spent.
        Err(_) => Event::Disconnected,
    };
    provisioner.on_event(event, now);
}

Two tracks

track what it is this crate
A std on ESP-IDF — Wi-Fi, Bluetooth provisioning, the radar's serial port rusty_esp_signal-esp --features esp-idf
B no_std on esp-hal — the protocols, the detector, the framing rusty_esp_signal-core, default

0.3.0 is a breaking release

The provisioning service carries the setup session (setup and discover); the plaintext credentials write and the public scan are gone, and Provisioner is the session's GATT router over the stores a platform lends it.

-  let mut p: Provisioner = Provisioner::new(StationPolicy::default());
-  p.restore(credentials, now);
+  let env = Env { settings, identity, rng, signer };
+  let mut p = Provisioner::new(StationPolicy::default(), devpub, Reset::PowerOn, now, env)?;
+  p.boot(now);

A device takes a session once a setup code's verifier (setup.v) is in its settings, written at flash time (espino's provision --setup-code-out, or the portal). The page is docs/provision.html, its wasm rusty_esp_signal-web. 0.3.0 also carries the CSI work (breathing and a heart band, the presence record at version 2, the CSI stream, a fall as a shape in the wander) and the link's HMAC on the S3's SHA unit (sha-accel).

0.2.0 was a breaking release

EspNowLink no longer takes a lifetime parameter. esp-radio 1.0.0-beta.1 dropped it from EspNowSender and EspNowReceiver, and carrying one here would have claimed a borrow the type does not hold — it owns the halves.

-  let link: EspNowLink<'_> = EspNowLink::new(sender, receiver, peer);
+  let link: EspNowLink    = EspNowLink::new(sender, receiver, peer);

The whole signal set moves to 0.2.0 together, the way it has always published, even though only -esp broke.

The chip backends also move to the esp-hal 1.2 companion set, and the library's dependency pins are ranges now rather than =: a firmware must not mix companion sets, but it is the firmware that picks which, and an = pin in a library took that choice away from every consumer at once.

S1, as a firmware you can run

firmware/c6-s1-link is the C6 ↔ C6 kill test, self-driving: one source, two images, 1,000 authenticated ESP-NOW frames each way, loss / replay / bad-tag counters straight from Session::counters(), and an unadopted identity that must be refused.

It needs two boards, not three — what has to be turned away is an unadopted identity, not a third piece of silicon, so the initiator mints a second key and tries again. Same radio, same distance: a refusal cannot be blamed on range.

Part of Janus

Janus rebuilds the Espressif ESP32 and Arduino application portfolio as independent, memory-safe Rust packages — so a hardware maker can ship a device that the MATA home computer discovers, catalogs honestly, adopts under its own identity, and pays for. Ten packages, three layers, and the dependency direction never reverses.

layer packages
0 — the vocabulary rusty_esp_core · rusty_esp_dsp
1 — the functions rusty_esp_image · rusty_esp_video · rusty_esp_audio · rusty_esp_signal · rusty_esp_mid · rusty_esp_iroh
2 — the surfaces rusty_esp_arduino — the sketch facade · espino — the maker's CLI (not published)

Every package is host-verified against an external oracle and keeps a ledger in which no number appears without the run that produced it. Five of seven device profiles have now run their kill tests on real silicon, three of them over a Wi-Fi network the board hosts itself.

Also check out the rest of Remade With Rust — including rusty_alloc, the pure-Rust rebuild of mimalloc that these firmwares run on, and rusty_jpeg, the JPEG engine behind the camera path — and our sister project remade_ffmpeg_rs, a ground-up Rust rebuild of FFmpeg.

About Mata Network

Mata Network builds sovereign, self-hostable infrastructure. Remade With Rust is our open-source home for the permissively-licensed building blocks that work depends on.

License

MIT OR Apache-2.0, at your option. See LICENSE-MIT and LICENSE-APACHE.