An ESP32 running QuietInstrument becomes a participant of a space: it has its own keys, declares what it measures, and its readings appear in the Instruments panel of every member — sealed to the space’s instrument plane, unreadable by relays, never mixed with the human conversation. Below is the exact path we walked with a board on a shelf: sketch → USB enrollment → Wi-Fi, no cable.
Try it without hardware first
Every node ships a virtual greenhouse — the behavioral contract. Attach it to any space you own and watch the panel breathe: a board that declares the same channels is indistinguishable from it in the browser. (It is honestly badged SIMULATED.)

A real board: Heltec WiFi LoRa 32 V3 + DHT22
The board from this release’s note — the one on our shelf. One command from sketch to a running board — build, flash, serial monitor:
sdk/instrument-arduino/qi-flash.sh ClimateDHT heltec_wifi_lora_32_V3
Wiring on the Heltec: DHT22 DATA → GPIO 7, VCC 3V3, GND (the pin is the env’s -DDHT_PIN; the OLED pins are already set). The board’s OLED shows the quite.space mark at boot, then the two readings and the instrument’s state: waiting for owner → enrolling… → in the space.
The same example runs on a LilyGo T3-S3 — it is the default env (qi-flash.sh ClimateDHT, DATA → GPIO 38), and PORT=/dev/cu.usbmodemXXXX forces a port when several boards are plugged in.
The sketch is a dozen lines
#include <QuietInstrument.h>
QuietInstrument qi;
float readTemp(); bool readDoor();
void setup() {
qi.begin("Greenhouse");
qi.numberSensor("temperature", "Температура", "°C", readTemp,
/*decimals*/1, /*sampleEvery*/10, /*staleAfter*/60);
qi.booleanSensor("door", "Дверь", readDoor, /*heartbeat*/60, /*staleAfter*/120);
}
void loop() { qi.loop(); delay(50); }
Three clocks, deliberately separate: sampleEvery — how often your callback is asked; staleAfter — after how long the reading is no longer honest as “now” (it rides in every frame); heartbeat — how often an unchanged value is re-published. A changed value publishes at once; freshness never decides airtime. And return NAN when the sensor has nothing honest to say — the channel stays silent rather than lying.
Plug in, click
Enrollment lives in the desktop app: open a space, plug the board in over USB, press Connect USB instrument. The node enrolls it, provisions it (certificate + current key epoch), and remembers the choice across restarts. The board keeps its identity and chain state in flash, crash-consistently: a frame is persisted before it leaves and re-sent after a reboot, so the device’s chain never has a hole.
Then unplug the cable — the Wi-Fi courier
A provisioned board carries its own frames over the local network; key rotations and the clock travel back down the same connection. USB becomes the rescue road, not the only one.
QuietWiFiBearer wifiBearer(QI_WIFI_SSID, QI_WIFI_PASS);
BearerChain chain(clockMs); // several roads, one honest answer
chain.add(&wifiBearer, "wifi");
chain.add(&serialBearer, "serial"); // the rescue that also feeds time
qi.setBearer(&chain);
What we learned living with it, so you don’t rediscover it:
- Credentials never enter the repository —
secrets.inibeside the sketch, git-ignored; PlatformIO folds them into build flags. - ESP32 radios speak 2.4 GHz only. A 5 GHz-only network is invisible, and the failure looks exactly like a wrong password.
- No time, no readings — and no discovery either. The board finds the node by a hint derived from the space id and the clock; a board that does not know the time stays honestly deaf (
wifi: no-time) until a bearer feeds it — the USB stand does, on connect; after that, NTP. - Some networks filter multicast between wireless clients. Name the node statically (
-DQI_NODE_HOST=… -DQI_NODE_PORT=…); after three failed dials the board forgets the address and listens for announcements again. - The courier keeps the radio awake (~40 mA) — fine on mains, a real trade on battery.
The board’s face names its state the whole way: joining · no-time · listening · dialing · knocked, then via wifi · Ns after every delivered frame. Unplug the network and the chain falls back: via serial.
Honesty, built in
No time → no readings. No current epoch → no readings. A detached board’s frames are refused by every member even if it still holds an old key — an instrument is a member while it speaks under the current epoch, so cards appear with the first reading and leave with the key. Nothing is ever simulated unless the driver says so.
Still true: a real board publishes into sealed spaces only — plaintext telemetry into a broadcast space is the next episode. The full engineering guide (NVS journal, interop gates, bearer chain) lives in the repository; the wire-level rules are in protocol updates.