В сентябре яблоки едут в холодильную камеру, ягода — с поля на предварительное охлаждение и дальше в хранение. Датчик на стене при этом спокойно врёт: у испарителя уже четыре градуса, а в середке штабеля ящик всё ещё тёплый. Или наоборот — малину два часа держали на рампе «ну сейчас занесём».
Живые данные тут нужны. Но «живые» — это не Wi‑Fi из ящика сквозь сэндвич-панель. Мы уже собирали метеостанцию на ESP32 и питание от 18650. В теплице это работает. В камере хранения — нет: ноль градусов сажает батарею на каждом импульсе, утеплённая дверь глотает радио, а сами плоды — почти вода, штабель глушит сигнал лучше любой стены.
За выходные собираем другое. В ящике — маленький маяк на nRF52840: раз в 10–30 минут снял температуру и влажность, коротко пискнул по Bluetooth и снова спит. В камере, на розетке, стоит ESP32 и слушает. На улицу из камеры идёт один провод, не десять антенн.
Отдельно — про датчики. В этой сырости первым дохнет не радио и не батарея, а головка влажности. Если купить «как в прошлый раз, DHT22», логгер начнёт врать раньше, чем вы съедите первую партию.
Это не камера с РГС и не прибор за восемьдесят тысяч. Это способ не врать себе про ящик. Список деталей — ниже, без него инструкция не инструкция.
Два прибора, не один «универсальный»
В ящик кладём узел: nRF52840, датчик с мембраной, литиевый пальчиковый элемент. Он почти всё время спит. Раз в десять–тридцать минут просыпается, меряет, несколько раз подряд отправляет короткий BLE‑маяк и гаснет. Никакого Wi‑Fi, никакого «подключиться к телефону», никакой зарядки банки в камере.
В самой камере — шлюз. Обычный ESP32, который у многих уже лежит с прошлых самоделок. Он стоит там же, где ящики, и питается от щитка освещения или розетки в камере. Сэндвич-панель проходит не каждый ящик, а один кабель шлюза наружу: витая пара в коридор или хвостик антенны через уплотнение двери.
Почему не ESP32 в ящике? Даже новая C3 для редкого писка жирновата. Спит она нормально, орёт в эфир — нет. На холоде импульс тока просаживает слабую батарею, контроллер моргает и в логе дыра как раз тогда, когда яблоки пошли греться. nRF52840 на маяке ест единицы миллиампер, во сне — доли микроампера. Seeed XIAO BLE и дешёвый SuperMini — это тот же чип, паять BGA не нужно.
Почему шлюз не повесить в коридоре «чтобы в тепле»? Утеплённая металлическая дверь камеры для Bluetooth — почти банка. Вешайте приёмник на потолок над проходом между штабелями, чтобы смотрел вдоль рядов, а не сквозь яблоки. Камера большая и середка глухая — второй ESP в дальнем проходе, не «более мощный чип в ящике». Про сеть Zigbee у нас уже есть отдельная статья; для первой сборки хватит простого маяка.
Кухню на тёплой стороне не изобретаем: ESP32 уже умеет слушать BLE‑термометры Xiaomi, дальше те же MQTT и Grafana, что в теплице.
Как часто спрашивать ящик
Ящик яблок тяжёлый. Градус в середке не прыгает каждую минуту. Пока партия остывает после сбора — хватает замера раз в 5–10 минут. Когда уже лежит в камере хранения — раз в полчаса. Если температура почти не двигается, можно ещё реже.
Живые данные здесь — свежая точка за этот интервал, а не пульс каждые пятнадцать секунд. Постоянный маяк только греет плату и жрёт элемент. Зато один короткий писк шлюз легко пропустит. Поэтому после замера узел орёт не один раз, а пачкой: три–пять одинаковых сообщений за секунду–две. Шлюз потом склеит их в одну точку.
В прошивке два режима, без философии:
- Остывание — ягода, тёплый ящик, предкамера: каждые 10 минут;
- Хранение — яблоки: каждые 30 минут.
У ягод после сбора счёт на первые два часа: с поля до четырёх градусов. Поэтому логгер лежит в ящике, а не на стене. Стена покажет воздух у испарителя. Ящик — что получила партия. В кабинете Фермозавра холодовая цепь как раз про партии, но температуру туда часто вбивают руками. Этот узел — чтобы перестать вбивать.
Какой датчик переживёт холодильную камеру
Нужны и температура, и влажность. Яблокам обычно 0–4 °C и очень сыро, 90–95%. Суше — морщатся, теплее — гниют, капля на кожице — тоже плохо. Ягоде в ящике «идеальные 93%» не так важны, как не греться выше восьми градусов по дороге.
Почти все дешёвые датчики влажности устроены одинаково: тонкий полимер, который набирает воду. Температуру с того же кристалла ещё можно читать весь сезон. Полимер — нет. В теплице мы спокойно ставили DHT22. Здесь этот опыт вредный.
Производитель пишет: нормальная жизнь датчика — примерно 20–80% влажности. Холодильная камера с яблоками из этого коридора не выходит никогда: ноль градусов и 90–95% RH — рабочий режим хранения, не авария. Полимер «заплывает»: через десятки часов он показывает всё более мокро и сам не откатывается, пока кристалл не прогреть. Открыли дверь — на датчике конденсат, точка росы почти совпадает с воздухом. Плюс пыль, сок, мойка тары. Плёночка на датчике спасает от капель, не от постоянной сырости камеры и не от химии.
Коротко: голая плата «как на макетке» здесь расходник. Корпус и мембрана важнее надписи на чипе.
Что не класть в ящик
- DHT11 и DHT22. В сухой мастерской ещё поживут. В конденсате камеры плывут. Не берите «потому что в статье про теплицу так было».
- BME280 и BME680. В сырости влажность часто умирает за недели. Для фруктов их нет.
- AHT10 и безымянный «SHT» без крышки. Лотерея. Градус ещё шевелится, проценты уже мусор.
- Комнатный Xiaomi на таблетке. Удобно отладить шлюз дома. В ящик с яблоками — нет: нет защиты от конденсата, таблетка на нуле врёт и дохнет.
Что живёт дольше
Чудес нет. Есть три вещи: мембрана, подогрев кристалла и два отсека в корпусе.
| Датчик | Температура | Влажность | Защита / подогрев | В камере 0 °C, 90–95% RH |
|---|---|---|---|---|
| DHT22 | пока жив — нормально | быстро плывёт | нет / нет | Не ставить |
| BME280 | нормальная | часто дохнет первой | нет / нет | Не ставить |
| SHT31 голая плата | точная | на бумаге точная | нет / слабый подогрев | Сезон, если повезёт |
| SHT31 с заводским фильтром | точная | точная | фильтр / подогрев | На один сезон — можно |
| SHT41 с крышкой | точная | точная | крышка / нормальный подогрев | Брать для самоделки |
| SHT85 | точная | ещё точнее | плёнка как у промышленных / подогрев | Если не хотите менять головку весной |
| HDC3022 | очень точная | очень точная | постоянная плёнка / подогрев | Тот же класс; открытую версию 3020 в конденсат не берите |
| DS18B20 в гильзе | живёт годами | нет, только градус | нержавейка | Страховка: влажность всё равно умрёт раньше |
У SHT41 подогрев — не игрушка для гиков. Без коротких прогревов любой «точный» датчик в 95% через месяц рисует одну и ту же мокроту. Промышленные зонды за десятки тысяч живут годами — мы на них не претендуем. Нам нужен честный сезон–два.
Два кармана в коробке
Не запихивайте датчик, плату и батарею в один герметичный бокс. Внутри станет сто процентов влажности навсегда — и вы мерите климат коробки, не ящика.
- Мокрый нос. Окошко датчика через плёнку, смотрит вниз: капли стекают, не стоят на кристалле. Лак на окошко не мазать.
- Сухой карман. Плата, элемент, разъёмы. Плату можно покрыть лаком. Провод с петлёй, чтобы конденсат не тёк на пайку.
Сок и мойка убивают датчик быстрее сырости. Головку делайте сменной: пяток–двадцать сантиметров провода и разъём, не припаивайте намертво к плате.
Когда греть датчик
Каждый замер греть не надо: сожжёте ток и запишете «температуру грелки». Рабочий замер — холодный. Подогрев — если влажность уже под 90%, или резко прыгнула температура (открыли дверь), или с прошлого прогрева прошло полдня. Импульс около секунды, потом пауза 20–30 секунд, потом настоящий замер. В хранении такой цикл раз в 6–12 часов, не чаще. По батарее это копейки. По правде графика — нет.
Какую батарею класть на ноль градусов
Обычный 18650 при нуле уже вялый, а заряжать его ниже нуля нельзя. Схему с солнечной панелью и TP4056 сюда не переносите. Круглая таблетка в камере тоже подводит: в момент писка напряжение проседает, чип моргает — снова дыра.
Берите литиевый пальчиковый AA вроде Energizer Ultimate Lithium: до минус сорока живёт и импульс держит. Или специальный «холодный» элемент Li‑SOCl₂, только не перепутайте с обычным 14500, который заряжается. Между замерами чип лучше совсем выключать, а будить крошечным таймером TPL5110 — тогда сезон на одном элементе реален. Первую прошивку можно собрать с обычной паузой в коде: за вечер заведётся, но батарея протянет дни, не зиму. Это лучше сказать сразу, чем потом ругаться на «дохлый nRF».
Что купить
Цены — порядок с Ozon и AliExpress, не счёт.
- Seeed XIAO nRF52840 или SuperMini — 500–1 200 ₽
- SHT41 с крышкой, или SHT85, или HDC3022 — 600–2 500 ₽. Голый SHT31 — только если согласны менять весной
- По желанию DS18B20 в гильзе — 150–300 ₽, в кучу яблок, не в воздух у платы
- Литиевый AA и холдер — 250–400 ₽
- Таймер TPL5110 — 150–250 ₽, для нормального сна
- Корпус с двумя карманами или бокс плюс выносной нос датчика — 300–600 ₽
- ESP32 для шлюза — скорее всего, уже есть, 350–550 ₽ если нет
- Провода, разъёмы, термоусадка, лак на сухую плату — сотня–другая
Один узел — примерно 2 000–4 500 ₽, смотря какую головку взяли. Шлюз на камеру один. Десять ящиков — десять маяков, не десять антенн на улицу.
Как соединить
ЯЩИК (холод и сырость)
литиевый AA ──► плата nRF 3,3 В
датчик SDA/SCL ──► пины D4 и D5, питание 3,3 В
окошко датчика ──► мокрый карман, вниз
DS18B20 ──► пин D6 и резистор 4,7 кОм (по желанию)
таймер ──► будит плату раз в 10–30 минут
КАМЕРА (тот же холод, но есть розетка)
ESP32 слушает Bluetooth → MQTT → график
кабель из камеры в коридор / на щиток
НЕ ДЕЛАТЬ: Wi‑Fi из ящика, 18650, зарядка банки в камере,
шлюз за утеплённой дверью, датчик внутри глухой банки.
На XIAO линия датчика: D4 — данные, D5 — такты, земля общая. Сам датчик — на шлейфе сантиметров десять–двадцать, не на плате: иначе после писка вы мерите нагрев чипа, не воздух ящика. Сначала замер, потом радио. Не наоборот.
Прошивка логгера: что он делает по кругу
На чём писать — не святое. Arduino, Zephyr, голый чип. Святой — порядок. Иначе получите либо севшую батарею, либо «температуру процессора» вместо яблок.
Один круг жизни маяка:
- Проснулся. Светодиоды погасить. Радио пока не включать.
- Снял температуру и влажность, ещё без подогрева. Если есть щуп в яблоках — его тоже. Запомнил напряжение батареи.
- Подумал, греть ли датчик. Да — если уже очень сыро, или градус скакнул (дверь), или с прошлого раза прошло полдня. Нет — сразу к сборке сообщения.
- Если грел: секунда нагрева, полминуты остыть, замер ещё раз. Пометил в сообщении: «сегодня грел». Батарея это переживёт, ложный градус — нет.
- Сложил короткое сообщение: номер ящика, температура, влажность, батарея и счётчик. Счётчик нужен шлюзу: пять одинаковых писков — это одна точка, не пять.
- Включил маяк на полторы секунды, несколько копий подряд. Мощность обычная, не «выкрутить на максимум» — сквозь яблоки всё равно не пробьёт, только элемент съест. К телефону не подключается, руку не жмёт: на каждое рукопожатие уйдёт ток, а цифры те же.
- Радио выключил сразу. Дальше пищать до следующего круга незачем.
- Решил, сколько спать: остывание — 10 минут, хранение — 30. В камере эфир лучше всё-таки послать: редкий писк дешевле дыры на графике.
- Уснул по-настоящему (таймер снаружи и полное выключение) или, на столе, просто подождал в коде — тогда батарея на дни.
Номер ящика запишите в прошивку и на бирке. Не надейтесь на «адрес Bluetooth»: после смены батареи он может смениться, номер ящика — нет.
Как упакованы цифры в писк (восемь байт):
номер ящика два байта
температура два байта, в сотых долях градуса
влажность два байта, в сотых долях процента
флаги один байт: грел или нет, счётчик
батарея один байт, например 33 = 3,3 В
Ниже — один рабочий скетч под плату XIAO и датчик SHT41. Это не единственный способ, это чтобы было с чего скопировать.
#include <bluefruit.h>
#include <Adafruit_SHT4x.h>
#define CRATE_ID 17
#define ADV_BURST_MS 1500
#define MODE_STORAGE_MS (30UL * 60UL * 1000UL)
Adafruit_SHT4x sht4 = Adafruit_SHT4x();
uint8_t payload[8];
int16_t lastT = 0;
uint8_t seq = 0;
void fillPayload(float t, float rh, bool heated) {
int16_t t100 = (int16_t)(t * 100.0f);
uint16_t rh100 = (uint16_t)constrain(rh * 100.0f, 0, 10000);
uint8_t flags = (heated ? 0x01 : 0x00) | ((seq & 0x0F) << 4);
payload[0] = CRATE_ID & 0xFF;
payload[1] = (CRATE_ID >> 8) & 0xFF;
payload[2] = t100 & 0xFF;
payload[3] = (t100 >> 8) & 0xFF;
payload[4] = rh100 & 0xFF;
payload[5] = (rh100 >> 8) & 0xFF;
payload[6] = flags;
payload[7] = 33; // потом подставьте замер батареи
}
void burstAdvertise() {
Bluefruit.Advertising.clearData();
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addManufacturerData(payload, sizeof(payload));
Bluefruit.Advertising.setInterval(32, 32); // часто, но только на полторы секунды
Bluefruit.Advertising.setFastTimeout(1);
Bluefruit.Advertising.start(0);
delay(ADV_BURST_MS);
Bluefruit.Advertising.stop();
}
void setup() {
Bluefruit.begin();
Bluefruit.setTxPower(0); // громче не поможет сквозь яблоки
Bluefruit.autoConnLed(false);
sht4.begin();
sht4.setPrecision(SHT4X_HIGH_PRECISION);
sht4.setHeater(SHT4X_NO_HEATER);
}
void loop() {
sensors_event_t h, t;
bool heated = false;
sht4.setHeater(SHT4X_NO_HEATER);
sht4.getEvent(&h, &t);
if (h.relative_humidity >= 90.0f || fabs(t.temperature * 100 - lastT) > 80) {
sht4.setHeater(SHT4X_HIGH_HEATER_1S);
delay(1200);
sht4.setHeater(SHT4X_NO_HEATER);
delay(25000); // остыть, иначе в лог попадёт грелка
sht4.getEvent(&h, &t);
heated = true;
}
lastT = (int16_t)(t.temperature * 100);
fillPayload(t.temperature, h.relative_humidity, heated);
burstAdvertise();
seq = (seq + 1) & 0x0F;
// Стол: пауза. Сезон: внешний таймер и полное выключение.
delay(MODE_STORAGE_MS);
}
В коде тот же круг: датчик, если надо — подогрев, сообщение, писк, сон. После подогрева полминуты ждать обязательно. Для зимы последнюю паузу в коде лучше выкинуть и поставить внешний таймер: сам по себе чип во сне часы не считает.
Прошивка шлюза: слушать, не здороваться, не плодить копии
Шлюзу ток не жалко — он в розетке. К логгеру он не подключается. Он просто слушает писки, склеивает пачку в одну точку и орёт, если ящик вдруг замолчал.
Круг шлюза:
- Всё время слушает, сам никого не тормошит. Окно слушания широкое, иначе короткую пачку легко проспать.
- Чужой Bluetooth в камере (телефон, часы) выкидывает. Наш писк узнаёт по метке и длине.
- Достаёт номер ящика, температуру, влажность, батарею и силу сигнала. Сила сигнала — не про яблоки, а про «ящик уехал за штабель».
- Помнит последний счётчик каждого ящика. Пришёл тот же счётчик в течение трёх секунд — это копия, график не трогаем. Новый счётчик — одна точка.
- Пишет в MQTT, дальше как обычно: хаб и график.
- Раз в минуту смотрит: кто из живых ящиков молчит слишком долго (для хранения — час, для остывания — минут двадцать пять). Замолчал — алерт. Села батарея, глухой угол, шлюз смотрит не туда. Это важнее красивой линии.
- Пороги «уже плохо» живут на шлюзе, не в ящике. Яблоки: теплее пяти градусов или влажность уехала из 85–98%. Ягода: выше восьми дольше часа. Узел только меряет.
- Если интернет шлюза моргнул, точки пусть полежат у него в памяти и уедут потом. Ящику облако не нужно.
Скетч ниже закрывает слушание, склейку пачки и алерт тишины.
#include <WiFi.h>
#include <PubSubClient.h>
#include <BLEDevice.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#define MAX_CRATES 32
#define SILENCE_MS (60UL * 60UL * 1000UL) // час без писка — уже подозрительно
WiFiClient wifi;
PubSubClient mqtt(wifi);
struct Slot {
uint16_t crate;
uint8_t seq;
uint32_t lastMs;
bool used;
};
Slot table[MAX_CRATES];
bool takePoint(uint16_t crate, uint8_t seq) {
uint32_t now = millis();
int freeIdx = -1;
for (int i = 0; i < MAX_CRATES; i++) {
if (!table[i].used) { if (freeIdx < 0) freeIdx = i; continue; }
if (table[i].crate != crate) continue;
if (table[i].seq == seq && (now - table[i].lastMs) < 3000) return false;
table[i].seq = seq;
table[i].lastMs = now;
return true;
}
if (freeIdx < 0) return true;
table[freeIdx] = {crate, seq, now, true};
return true;
}
class CrateSink : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice d) override {
if (!d.haveManufacturerData()) return;
std::string m = d.getManufacturerData();
if (m.size() < 10) return;
uint16_t cid = (uint8_t)m[0] | ((uint8_t)m[1] << 8);
if (cid != 0xFFFF) return;
uint16_t crate = (uint8_t)m[2] | ((uint8_t)m[3] << 8);
uint16_t rawT = (uint8_t)m[4] | ((uint16_t)(uint8_t)m[5] << 8);
int16_t t100 = (int16_t)rawT;
uint16_t rh100 = (uint8_t)m[6] | ((uint8_t)m[7] << 8);
uint8_t flags = (uint8_t)m[8];
uint8_t seq = (flags >> 4) & 0x0F;
if (!takePoint(crate, seq)) return;
char buf[192];
snprintf(buf, sizeof(buf),
"{\"crate\":%u,\"t\":%.2f,\"rh\":%.2f,\"heat\":%d,\"bat\":%.1f,\"rssi\":%d}",
crate, t100 / 100.0, rh100 / 100.0, flags & 1,
(uint8_t)m[9] / 10.0, d.getRSSI());
char topic[32];
snprintf(topic, sizeof(topic), "crate/%u", crate);
mqtt.publish(topic, buf, true);
}
};
void setup() {
WiFi.begin("SSID", "PASS");
mqtt.setServer("192.168.1.10", 1883);
BLEDevice::init("crate-gw");
BLEScan* scan = BLEDevice::getScan();
scan->setAdvertisedDeviceCallbacks(new CrateSink());
scan->setActiveScan(false);
scan->setInterval(100);
scan->setWindow(80);
scan->start(0, nullptr, false);
}
void loop() {
if (!mqtt.connected()) mqtt.connect("crate-gw");
mqtt.loop();
static uint32_t lastGuard;
if (millis() - lastGuard < 60000) return;
lastGuard = millis();
for (int i = 0; i < MAX_CRATES; i++) {
if (!table[i].used) continue;
if (millis() - table[i].lastMs < SILENCE_MS) continue;
char topic[40], buf[80];
snprintf(topic, sizeof(topic), "crate/%u/alert", table[i].crate);
snprintf(buf, sizeof(buf), "{\"silent\":true,\"crate\":%u}", table[i].crate);
mqtt.publish(topic, buf, true);
}
}
Точка на графике — ящик жив. Алерт — ящик замолчал. Шлюз в этот момент не имеет права спать, поэтому он в розетке внутри камеры, не на батарейке за дверью.
Как понять, что датчик уже врёт
Влажность врёт красиво: три недели подряд 99,9%. Это не «идеальная камера». Это набухший полимер или капля на окошке. Смотрите так:
- влажность прилипла к одному числу, а температура ещё дышит;
- открыли дверь — градус дёрнулся, влажность нет;
- два маяка в одном проходе разошлись по влажности больше чем на пять–семь процентов, градус почти одинаковый.
Сначала прогрейте датчик вручную. Не отпустило — меняйте головку. Не пишите в прошивке «минус восемь процентов»: это прячет болезнь на неделю. Щуп в яблоках при этом может оставаться честным — поэтому он в списке как страховка по градусу, не как замена влажности.
Перед закладкой раз в сезон сверьте маяк с нормальным термометром и хотя бы с одним свежим датчиком, который в камере не жил. Ошибка в пару процентов влажности — жить можно. В десять — уже не график, а замена.
Чего этот ящик не умеет
Не заменяет РГС, промышленный увлажнитель и дорогой зонд. Не орёт сквозь сэндвич-панель. Не считает сахар в яблоке и не сортирует по цвету — нейросети в рубрике недавно закрыли целой серией, сюда их не тащим.
Ягоду он не спасёт, если два часа грели на солнце: честно нарисует, и всё. Если ящики из камеры не выезжают, можно ещё проще: тот же датчик на проводе внутри штабеля, ESP32 в коридоре у щитка, без Bluetooth вообще.
Запасной слух — весы на приёмке: если шлюз в камере почему-то молчал, у весов писк ящика ещё бывает слышно. Это подстраховка. Живые данные мы хотели, пока партия лежит, а не потом.
Суббота и воскресенье
- На столе, без корпуса: плата и датчик, в прошивке пауза 30 секунд. Телефон с приложением nRF Connect — в писке видны номер ящика, градус и влажность.
- Рядом ESP32: график рисует одну точку на пачку, не пять. Выключили маяк — через время приходит «замолчал».
- Дыхните на датчик: влажность уехала и после подогрева вернулась, а не прилипла к сотне.
- Соберите корпус: мокрый нос вниз, сухой карман, лак на плату, не на окошко.
- Ночь в бытовом холодильнике дома — это только проверка, не модель хранения. Рядом банка с водой, чтобы был конденсат. Утром лог живой, не вечные 100%.
- Боевые 10 и 30 минут, литиевый пальчик, шлюз на потолке прохода между штабелями — не в коридоре за дверью камеры.
Если за ночь в бытовом холодильнике влажность уже умерла — в камеру эту головку не везите. Смените на SHT85 или HDC3022 ещё на столе.
Сам узел стоит как пара пластиковых ящиков. Ошибка — тонна Антоновки или малина, которую везли «в холод», а грели в середке. Логгер климат не делает. Он не даёт врать себе, какой климат вы уже сделали.





