Датчик и телеметрия¶
В этой главе фильтр начинает мерить воздух и отправлять данные в облако. Ключевой приём — своё поле в телеметрии: словарь экосистемы про VOC ничего не знает, но ядро позволяет добавить в телеметрию любое поле.
1. Библиотека датчика¶
В platformio.ini добавьте к lib_deps:
2. Чтение SGP40¶
В src/main.cpp (пины — из схемы):
#include <Wire.h>
#include <Adafruit_SGP40.h>
static Adafruit_SGP40 s_sgp;
static int32_t g_vocIndex = -1; // -1 = данных ещё нет
static void initVocSensor() {
Wire.begin(/*SDA=*/8, /*SCL=*/9);
if (!s_sgp.begin()) {
Serial.println("[VOC] SGP40 not found, check wiring");
}
}
static void readVocSensor() {
// measureVocIndex() сам ведёт внутреннюю компенсацию датчика.
// Индекс: ~100 = обычный воздух, выше = грязнее (макс 500).
g_vocIndex = s_sgp.measureVocIndex();
}
Вызов initVocSensor() добавьте в setup() после s_link.begin(), а readVocSensor() — в loop() раз в секунду (по millis-таймеру, не через delay!):
static uint32_t s_lastReadMs = 0;
void loop() {
s_link.loop();
uint32_t now = millis();
if (now - s_lastReadMs >= 1000) {
s_lastReadMs = now;
readVocSensor();
}
}
Никаких delay() в loop
s_link.loop() должен вызываться постоянно — на нём держатся Wi-Fi, MQTT и команды с портала. delay(1000) заморозит всё это. Только millis-таймеры.
3. Своё поле в телеметрии¶
Каждые telemetryPeriodMs ядро само собирает JSON-сообщение телеметрии и отправляет его в облако. Для нашего устройства (один юнит, из словарных навыков только вентилятор) ядро собирает вот такое сообщение:
Разберём структуру:
units— массив юнитов (камер) устройства. Серийная сушилка iDryer может иметь до четырёх независимых камер, поэтому телеметрия — всегда массив, даже если камера одна;units[0]— первый (и у нас единственный) юнит: мы указалиunitsCount = 1вConfig;fanStatus— словарное поле, появилось из-заhasFan = true;rssi,uptime— уровень Wi-Fi и время работы, ядро добавляет всегда.
Про VOC в этом сообщении ничего нет — ядро не знает о нашем датчике. Но прямо перед отправкой ядро даёт вашему коду возможность дописать в сообщение свои поля. Для этого вы регистрируете колбэк (callback, «обратный вызов») — функцию, которую отдаёте ядру, а ядро само вызывает её на каждой публикации, передавая внутрь собранный JSON (аргумент doc — это он и есть).
В setup():
s_link.onTelemetryPublish([](JsonObject doc) {
// doc — то самое сообщение телеметрии, собранное ядром (см. JSON выше).
// Дописываем в первый юнит наше поле vocIndex.
if (g_vocIndex >= 0) {
doc["units"][0]["vocIndex"] = g_vocIndex;
}
});
Строка doc["units"][0]["vocIndex"] = g_vocIndex; читается так: «в сообщении doc возьми массив units, в нём элемент 0 (наш единственный юнит) и запиши туда поле vocIndex». Имя поля вы придумываете сами — в следующей главе вы сошлётесь на него, чтобы показать значение на карточке.
Если встретите слово hook
В исходниках ядра этот колбэк называется PublishHook — «хук» (hook, «крючок») означает то же самое: точку, куда библиотека даёт «подцепить» вашу функцию. Термины взаимозаменяемы; в этой документации мы говорим «колбэк».
Лямбда и почему она «пустая»
Конструкция [](JsonObject doc) { ... } называется лямбдой — это функция без имени, записанная прямо в месте использования, чтобы не выносить её отдельно и не придумывать имя.
Квадратные скобки в начале — «список захвата»: в них перечисляют локальные переменные, которые функция берёт с собой. Правило ядра: скобки всегда пустые ([]) — лямбда ничего не захватывает и не таскает с собой никакого состояния (это и называют словом stateless, «без состояния»).
Причина техническая: лямбды с захватом требуют динамической памяти, а на ESP32 её частые выделения дробят кучу и в худшем случае роняют Wi-Fi. Поэтому ядро принимает только простые функции.
Практический вывод один: всё, что нужно колбэку, храните в глобальных переменных — как наша g_vocIndex. Это правило действует для всех колбэков idryer-core.
Состояние вентилятора публикуется словарным путём — просто пишите его в поле ядра, когда включаете/выключаете (логика — в главе 7):
4. Проверка¶
После прошивки в MQTT-потоке устройства (или в Serial-логе публикаций) телеметрия выглядит так:
{
"units": [ { "unitId": "U1", "fanStatus": false, "vocIndex": 103 } ],
"rssi": -52,
"uptime": 120
}
vocIndex — ваше собственное поле, поехавшее в облако рядом со словарным fanStatus. Портал его уже получает и сохраняет, но пока не знает, что с ним делать: покажите ему это в следующей главе.
Подышите на датчик или поднесите маркер — индекс должен заметно вырасти за секунды.