Погреб за гаражом захотел своим датчиком: температура, влажность, публикация раз в час — климат там меняется медленнее, чем я вспоминаю про него. Розетки рядом нет, и это сразу переписывает привычный скетч: узел, который просто живёт в сети и публикует раз в минуту, съест любую батарейку за дни. Прикинем на пальцах: в бодрствовании с включённым Wi-Fi плата тянет десятки миллиампер, на пиках передатчика — до ста семидесяти; в дремоте с выключенной радиочастью — десятки микроампер. Разница в тысячу раз, и вся батарейная архитектура держится на том, чтобы тысячекратное состояние было основным. И сразу оговорка: речь про узел-отправитель; исполнять команды батарейный узел почти не умеет — чтобы слушать брокера, надо не спать.
Схема, которая работает у меня годами: проснулся — подключил Wi-Fi — connect к брокеру — одна публикация — disconnect — глубокий сон на час. ESP.deepSleep(3600e6): аргумент в микросекундах, 3600e6 — это и есть час. Во сне ESP8266 по даташиту берёт двадцать микроампер; реальная плата с регулятором и светодиодами — семьдесят-восемьдесят, и это тоже честное число, если не повезло с обвязкой.
Разберём сам цикл и его тайминги, посчитаем бюджет от 18650 и CR2032 по-честному — с пиками и внутренним сопротивлением, — и главную ловушку сна: прощание с брокером, без которого панель будет хоронить живой датчик каждый час. Про то, как воля ломается у дремлющих узлов, я писал в уроке про Last Will — здесь практическая сторона: как уснуть и не наделать ложных похорон.
Цикл: проснулся — Wi-Fi — publish — прощание — сон
Порядок операций важнее, чем кажется. Wi-Fi поднимается два-четыре секунды, connect к брокеру ложится следом — на типовом узле с mosquitto рукопожатие занимает ещё около четырёх, итого бодрствование выходит шесть-семь секунд. Сама публикация — сотые доли секунды: пакет на двадцать байтов уезжает одним заходом. Вся батарейная драма — в рукопожатиях: радиомодуль гоняет ассоциацию с точкой доступа, DHCP, TCP-коннект, CONNECT-пакет, и каждый этап ест по сотне миллиампер. Сокращать надо не публикацию — её и сокращать нечего, — а время до неё.
Первая экономия бесплатная: WiFi.mode(WIFI_STA) до begin — без режима точки доступа ядро не тратит секунды на сканирование канала под AP. Вторая: запомнить канал и BSSID роутера и передать их в пятиарговый begin(ssid, pass, channel, bssid) — на свежих ядрах подключение сокращается с трёх-четырёх секунд до одной-двух. Третья — не упорствовать: если сеть не поймана за десять секунд, всё равно засыпай, следующий час начнётся с чистого листа.
И про датчики: DHT22 просит две секунды после подачи питания, DS18B20 — 750 миллисекунд на конвертацию. Эти времена складываются с сетевыми и честно кладутся в активную фазу: скетч, который читает датчик мгновенно после пробуждения, публикует мусор или прошлую конверсию. Сам датчик тоже можно усыпить: адресные датчики с режимом shutdown выключаю шиной перед сном — их собственный ток покоя в миллиамперах обесценит все ваши микроамперы.
#include <ESP8266WiFi.h>#include <PubSubClient.h> WiFiClient net;PubSubClient client(net); const char* WIFI_SSID = "DachaNet";const char* WIFI_PASS = "12345678";const char* BROKER = "192.168.1.50";const char* ID = "pogreb-datchik";const int PIN_LED = 2; // встроенный светодиод void setup() { Serial.begin(115200); pinMode(PIN_LED, OUTPUT); digitalWrite(PIN_LED, LOW); // горим только в активной фазе WiFi.mode(WIFI_STA); // без AP: быстрее и тише WiFi.begin(WIFI_SSID, WIFI_PASS); unsigned long t0 = millis(); while (WiFi.status() != WL_CONNECTED) { delay(100); if (millis() - t0 > 10000) { break; } // сети нет - всё равно спим } client.setServer(BROKER, 1883); if (client.connect(ID)) { int t = analogRead(A0) / 16; // условные единицы датчика char msg[16]; snprintf(msg, sizeof(msg), "{\"t\":%d}", t); client.publish("dacha/pogreb/temp", msg); delay(200); // пакету нужно уйти в сокет client.disconnect(); // прощание обязательно delay(100); // и уехать из буфера тоже } digitalWrite(PIN_LED, HIGH); ESP.deepSleep(3600e6); // микросекунды: 3600e6 = один час} void loop() {} // сюда не доходим: сон из setupОбратите внимание, где стоит сон: в конце setup, а не в loop. После ESP.deepSleep плата не возвращается из функции — она выключается и просыпается как после нажатия reset: заново setup, заново Wi-Fi. loop остаётся пустым декоративным элементом. Вторая железная мелочь: чтобы проснуться по таймеру, GPIO16 надо соединить с RST — на Wemos D1 mini это нога D0 к RST; без перемычки плата уснёт навсегда, и я однажды увёз такой узел обратно с дачи в кармане.
Пауза после публикации — не паранойя. publish у PubSubClient кладёт пакет в буфер, а не в эфир: отправку делает client.loop() или следующий вызов библиотеки. Две сотни миллисекунд delay дают стеку время выдавить пакет в сокет; без них payload может уехать уже после disconnect — или не уехать вовсе. Тот же зазор после disconnect: прощальный пакет должен уйти до deepSleep, иначе брокер увидит обрыв, а не вежливое прощание.
Важно понимать, что именно забывает плата во сне: глубокий сон — не модем-слип. ОЗУ умирает целиком, живут только регистры RTC и таймер; просыпаемся мы с нулевым состоянием переменных, и всё, что должно пережить час, — счётчик циклов, калибровка, номер последней отправки — кладётся либо в EEPROM, либо в RTC-память ESP8266. Отсюда и привычка держать тридцать два слова состояния в RTC: они доживают до пробуждения при токе, который по-прежнему считается сном. Счётчик циклов из модели ниже — ровно этот случай, только в движке он живёт в EEPROM.
Бюджет по-честному
Считаем цикл часа. Активная фаза: Wi-Fi три секунды по сто миллиампер в среднем — пики выше, на коротких паузах ниже, — это 0,08 миллиампер-часа; connect и публикация ещё три секунды по семьдесят пять — 0,06. Сон: пятьдесят девять минут пятьдесят четыре секунды по семидесяти восьми микроамперам — 0,08. Цикл целиком — около 0,22 миллиампер-часа, сутки из двадцати четырёх циклов — примерно пять. Отсюда жизнь батарейки: 18650 на две с половиной тысячи — по счёту больше четырёхсот дней; у моего погребного узла вышло четырнадцать месяцев с учётом зимних потерь ёмкости: литиевая банка на морозе отдаёт на двадцать-тридцать процентов меньше, и январская неделя съедала заметнее июльской.
CR2032 — отдельная песня. Теоретические 220 миллиампер-часов делятся на пять в сутки — сорок дней. Практически узел умирает раньше: литиевая таблетка держит единицы миллиампер постоянного тока, а наш узел требует пиков по сто-сто семьдесят. Внутреннее сопротивление таблетки — десятки ом: сто миллиампер просаживают три вольта на десятые, плата уходит в brownout на самом интересном месте — на передаче; пики Wi-Fi и как с ними жить считались в уроке про пики питания. Лечение одно: конденсатор 470-1000 микрофарад параллельно питанию — он отдаёт пики, таблетка между ними восстанавливает заряд. С конденсатором узел на таблетке у меня жил около двенадцати недель: работоспособно, но для постоянных узлов я таблетки больше не беру.
Спорить с самим собой умею: раз в час — консервативный выбор. Раз в десять минут те же секунды бодрствования дают уже двадцать миллиампер-часов в сутки — 18650 живёт месяца четыре, для дачи нормально. Раз в минуту — двести: две недели, и всё; на таком интервале батарейный узел не строится, либо меняется платформа — ESP32 во сне берёт около десяти микроампер против двадцати-восьмидесяти у ESP8266, но и его минутный цикл не спасёт. Как я раскладывал энергобюджет ESP32 по статьям — в уроке про батарейку 18650, таймерный сон с бюджетом — в уроке про глубокий сон ESP32.
| Этап цикла | Ток | Время | Расход за цикл |
|---|---|---|---|
| Сон, deep sleep | 20 мкА чип / 70-80 мкА плата | 59 мин 54 с | около 0,08 мА·ч |
| Подключение Wi-Fi | пики 170 мА, среднее 100 | 2-4 с | около 0,08 мА·ч |
| MQTT connect + publish | 70-80 мА среднее | 2-3 с | около 0,06 мА·ч |
| Итого за час | — | 6-7 с бодрствования | около 0,22 мА·ч, 5 мА·ч в сутки |
Мнение, к которому я пришёл на трёх батарейных узлах: батарейным узлам воля не нужна — дремлющий цикл рождает offline при каждой тишине дольше полутора keepalive, и панель мигает статусом чаще, чем датчик публикует. Статус держит публикация online с retain при каждом пробуждении, а приёмник следит за свежестью данных: молчание дольше двух-трёх циклов — значит, пора идти смотреть; сам приёмник — простая автоматизация на малине, без единого LWT. Подробно оба подхода взвешены в уроке про Last Will и retain, здесь вывод один: волю у спящего узла снимаю.
Частота публикаций — главный рычаг бюджета: с часа до десяти минут жизнь батарейки падает вчетверо. Механика периодических публикаций на millis та же, что в уроке про публикацию по millis, только период задаёт не loop, а таймер сна. Discovery и тяжёлые конфиги — при смене прошивки, а не при каждом пробуждении: четыреста байтов конфига раз в час — четверть суточного трафика узла, потраченная на повторение того, что брокер и так знает — retained-кэш это его работа, не платы.
- 1. Что съедает больше всего энергии за цикл батарейного узла?
- 2. Сколько микросекунд передать ESP.deepSleep для часа сна?
- 3. Зачем delay(100) после disconnect() перед сном?
- 4. Узел уснул без disconnect, у него была воля. Что увидит панель?
- 5. Почему CR2032 плохо тянет ESP8266 с Wi-Fi?
- Цикл узла: Wi-Fi — connect — publish — пауза — disconnect — delay(100) — deepSleep(3600e6); сон из setup, loop пустой.
- Рукопожатия, 3-7 секунд, едят больше энергии, чем публикация; WIFI_STA и запомненный BSSID сокращают их вдвое.
- 0,22 мА·ч за цикл часа: 18650 — больше года, CR2032 — недели и только с конденсатором 470-1000 мкФ.
- Уснул без disconnect — брокер держит сессию и досчитывает окно воли: панель хоронит живой узел каждый час.
- Обвязка съедает сон: HT7333 вместо AMS1117 и выпаянный power-LED, иначе 20 мкА превращаются в 5000.
Частые вопросы по теме урока
- Что съедает больше всего энергии за цикл батарейного узла?
- Секунды рукопожатий по сотне миллиампер против миллисекунд публикации: экономить надо время до публикации, а не пакет. Рукопожатие Wi-Fi и MQTT connect
- Сколько микросекунд передать ESP.deepSleep для часа сна?
- Аргумент в микросекундах: 3600e6 — это 3,6 миллиарда микросекунд, ровно час. 3600000 — только минута. 3600e6
- Зачем delay(100) после disconnect() перед сном?
- disconnect кладёт пакет в буфер, а не в эфир: без паузы плата уснёт раньше, брокер увидит обрыв вместо вежливого прощания. Пакет прощания должен успеть уйти из буфера
- Узел уснул без disconnect, у него была воля. Что увидит панель?
- Обрыв TCP без прощания: брокер держит сессию, досчитывает полтора keepalive и раздаёт волю — каждый час. offline через полтора keepalive — ложная похорона живого узла
- Почему CR2032 плохо тянет ESP8266 с Wi-Fi?
- Десятки ом внутреннего сопротивления против пиков в сотню миллиампер: питание проваливается в brownout на передаче. Спасает конденсатор 470-1000 мкФ. Высокое внутреннее сопротивление: пики 100+ мА просаживают питание