ESP32 с QuietInstrument становится участником пространства: у него свои ключи, декларация того, что он измеряет, и показания в панели Instruments у каждого участника — запечатанные на инструментальную плоскость пространства, нечитаемые для реле и никогда не смешанные с человеческим разговором. Ниже — ровно тот путь, который мы прошли с платой на полке: скетч → зачисление по USB → Wi-Fi, без кабеля.
Сначала попробуйте без железа
С каждым узлом едет виртуальная теплица — поведенческий эталон. Подключите её к любому своему пространству и смотрите, как панель дышит: плата, декларирующая те же каналы, неотличима от неё в браузере. (Она честно помечена SIMULATED.)

Настоящая плата: Heltec WiFi LoRa 32 V3 + DHT22
Та самая плата из релиз-ноты — которая у нас на полке. Одна команда от скетча до работающей платы — сборка, прошивка, серийный монитор:
sdk/instrument-arduino/qi-flash.sh ClimateDHT heltec_wifi_lora_32_V3
Проводка на Heltec: DHT22 DATA → GPIO 7, VCC 3V3, GND (пин задан в env через -DDHT_PIN; пины OLED уже настроены). OLED платы показывает знак quite.space при загрузке, затем два показания и состояние инструмента: waiting for owner → enrolling… → in the space.
Тот же пример работает на LilyGo T3-S3 — это env по умолчанию (qi-flash.sh ClimateDHT, DATA → GPIO 38), а PORT=/dev/cu.usbmodemXXXX принудительно выбирает порт, когда воткнуто несколько плат.
Скетч — дюжина строк
#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); }
Три часа, сознательно разделённые: sampleEvery — как часто спрашивают ваш колбэк; staleAfter — через сколько показание перестаёт быть честным «сейчас» (едет в каждом кадре); heartbeat — как часто переиздаётся неизменившееся значение. Изменившееся публикуется сразу; свежесть никогда не решает эфирное время. И возвращайте NAN, когда сенсору нечего честно сказать, — канал промолчит, а не соврёт.
Воткнул — кликнул
Зачисление живёт в десктопном приложении: откройте пространство, воткните плату по USB, нажмите Connect USB instrument. Узел зачислит её, подготовит (сертификат + текущая эпоха ключа) и запомнит выбор между перезапусками. Свою личность и состояние цепочки плата держит во флеше, устойчиво к сбоям: кадр сохраняется до отправки и переотправляется после перезагрузки — в цепочке устройства никогда нет дыры.
Потом выдерните кабель — Wi-Fi-курьер
Подготовленная плата носит свои кадры по локальной сети сама; ротации ключей и часы едут обратно по тому же соединению. USB становится дорогой спасения, а не единственной.
QuietWiFiBearer wifiBearer(QI_WIFI_SSID, QI_WIFI_PASS);
BearerChain chain(clockMs); // несколько дорог, один честный ответ
chain.add(&wifiBearer, "wifi");
chain.add(&serialBearer, "serial"); // спасение, которое ещё и кормит временем
qi.setBearer(&chain);
Чему мы научились, живя с этим, — чтобы вам не открывать заново:
- Креды никогда не попадают в репозиторий —
secrets.iniрядом со скетчем, в git-ignore; PlatformIO складывает их в build-флаги. - Радио ESP32 говорит только на 2.4 ГГц. Сеть только-5-ГГц невидима, и отказ выглядит ровно как неверный пароль.
- Нет времени — нет показаний, и discovery тоже нет. Плата находит узел по подсказке, выведенной из id пространства и часов; плата, не знающая времени, честно глуха (
wifi: no-time), пока носитель не накормит её — USB-стенд делает это при подключении; дальше NTP. - Некоторые сети фильтруют multicast между беспроводными клиентами. Назовите узел статически (
-DQI_NODE_HOST=… -DQI_NODE_PORT=…); после трёх неудачных дозвонов плата забывает адрес и снова слушает объявления. - Курьер держит радио бодрым (~40 мА) — нормально от розетки, настоящий размен на батарейке.
Лицо платы называет своё состояние всю дорогу: joining · no-time · listening · dialing · knocked, затем via wifi · Ns после каждого доставленного кадра. Выдерните сеть — цепочка откатится: via serial.
Честность встроена
Нет времени → нет показаний. Нет текущей эпохи → нет показаний. Кадры отсоединённой платы отвергает каждый участник, даже если у неё остался старый ключ: инструмент — участник, пока говорит под текущей эпохой, — карточки появляются с первым показанием и уходят вместе с ключом. Ничто никогда не симулируется, если драйвер об этом не сказал.
По-прежнему верно: настоящая плата публикует только в запечатанные пространства — телеметрия открытым текстом в broadcast-пространство — следующая серия. Полный инженерный гайд (NVS-журнал, interop-гейты, цепочка носителей) — в репозитории; проводные правила — в апдейтах протокола.