FREE RAG Converter Online -- RAGconverter.com
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 | 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 station on the open MAC (rusty_esp_signal-open, XIAO ESP32-S3) |
a WPA2 join, DHCP, 100/100 pings, the camera page and 1,500 stream frames; heap at radio start 53 KB on the blob, 0.1 KB here |
| an access point on the open MAC | a laptop joined as 802.11n (65 Mbit/s), through power save, group-key rotation and re-association; the camera cell hosting it took a signed 538 KB update over its own network |
| the link on raw frames on the open MAC | 1,000 of 1,000 round trips, three runs, against esp-radio's ESP-NOW on an ESP32-CAM; a signed 540 KB update through a relay in 35–41 s; a laptop streaming at 14.92 fps from the same radio meanwhile |
Known gaps, stated plainly: channel-state presence is verified against captures and against the host, not on a chip; S1 as written (C6 to C6) has not run, for want of C6 boards. The ledger says so rather than implying otherwise.
rusty_esp_signal-open (off by default) is a station, an access point and
the link on raw frames for the ESP32-S3, with no esp-radio. Images built
on it carry no C except the PHY: Espressif's libphy, 18 members,
33.5 KB, which brings the radio up, calibrates it and tunes it. Our code
calls ten of its functions and none that sets a transmit power or a
regulatory table. The transmit power is capped where esp-radio caps it,
5 dBm, through the PHY's own init data (the vendored esp-phy's
phy_max_tx_power, quarter dBm; ESP_PHY_CONFIG_PHY_MAX_TX_POWER = "80"
for ESP-IDF's 20 dBm), so a firmware moving from esp-radio transmits as it
did. The umbrella's tools/e6-phy.py checks all three for every open-MAC
image. Not Wi-Fi certified.
Every number, with the run that produced it:
docs/LEDGER.md.
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);
}| 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 |
Additions only; nothing 0.3.0 offered has changed.
rusty_esp_signal-esp: a station and an access point at once on esp-radio (hal::netstack::access_point_station_config, one radio, both halves built where WPA2 is decided), and a DHCP server that hands its clients the upstream lease's DNS servers (dhcp_server_with_dns_task), for a board relaying for its clients.rusty_esp_signal-esp: BLE setup fixes found on a C6 --ble::setup_address(a fresh random address per boot, so a central's stale per-address state cannot hide services) andble::ANSWER_GRACE.rusty_esp_signal-core:setup::page, the setup session over a device's own open network with no Bluetooth (featuresetup-page, ~76 KB of flash).- The family siblings are taken by published version (core 0.1.2, dsp 0.1.1, mid-core 0.1.2), not by git.
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).
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.
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.
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.
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.
MIT OR Apache-2.0, at your option. See LICENSE-MIT and LICENSE-APACHE.