rusty_esp_signal 0.3.1

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 129 Active Installs

FREE RAG Converter Online -- <a href="https://RAGconverter.com">RAGconverter.com</a>

# rusty_esp_signal

[![Remade With Rust](https://img.shields.io/badge/Remade%20With-Rust-000?logo=rust&logoColor=fff)](https://github.com/remade-with-rust) [![By Mata Network](https://img.shields.io/badge/by-Mata%20Network-5b2be0)](https://www.mata.network) [![crates.io](https://img.shields.io/crates/v/rusty_esp_signal.svg)](https://crates.io/crates/rusty_esp_signal) [![docs.rs](https://docs.rs/rusty_esp_signal/badge.svg)](https://docs.rs/rusty_esp_signal) [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](https://github.com/Remade-With-Rust/rusty_esp_signal/blob/main/LICENSE-MIT)

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 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.

## The open MAC

`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`](https://github.com/Remade-With-Rust/rusty_esp_signal/blob/main/docs/LEDGER.md).

## Using it

```rust
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.1

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) and `ble::ANSWER_GRACE`.
- `rusty_esp_signal-core`: `setup::page`, the setup session over a device's
  own open network with no Bluetooth (feature `setup-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.

## 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.

```rust
-  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.

```rust
-  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](https://www.mata.network) 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`](https://crates.io/crates/rusty_esp_core) · [`rusty_esp_dsp`](https://crates.io/crates/rusty_esp_dsp) |
| **1 — the functions** | [`rusty_esp_image`](https://crates.io/crates/rusty_esp_image) · [`rusty_esp_video`](https://crates.io/crates/rusty_esp_video) · [`rusty_esp_audio`](https://crates.io/crates/rusty_esp_audio) · [`rusty_esp_signal`](https://crates.io/crates/rusty_esp_signal) · [`rusty_esp_mid`](https://crates.io/crates/rusty_esp_mid) · [`rusty_esp_iroh`](https://crates.io/crates/rusty_esp_iroh) |
| **2 — the surfaces** | [`rusty_esp_arduino`](https://crates.io/crates/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](https://github.com/remade-with-rust) — including
[`rusty_alloc`](https://crates.io/crates/rusty_alloc), the pure-Rust rebuild of
mimalloc that these firmwares run on, and
[`rusty_jpeg`](https://crates.io/crates/rusty_jpeg), the JPEG engine behind the
camera path — and our sister project
[remade_ffmpeg_rs](https://github.com/Remade-With-Rust/remade_ffmpeg_rs), a ground-up Rust rebuild of FFmpeg.

## About Mata Network

[Mata Network](https://www.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](https://github.com/Remade-With-Rust/rusty_esp_signal/blob/main/LICENSE-MIT)
and [LICENSE-APACHE](https://github.com/Remade-With-Rust/rusty_esp_signal/blob/main/LICENSE-APACHE).