Архитектура независимого умного дома: паттерны аппаратного проектирования и Embedded-разработка на ESP32: Сенсорбокс

от автора

Почему я не доверил свой дом покупным умным коробочкам (доверил, но не до конца)

Преподавая в школе «Интеграл» в 2018 году, я наблюдал, как коллеги-педагоги массово скупали Яндекс.Станции и умные лампочки. Один из них, обычный учитель (не IT-специалист), с гордостью рассказывал, как настроил автоматическое включение света через мобильное приложение не только на кухне, но даже в Ванной комнате. А еще он поставил умную RGB-лампу в спальню. Всё работало «из коробки» — нажал кнопку, точнее зажал на некоторое время, подключил, готово.

Меня же это беспокоило. Как учитель информатики и робототехники, я не мог принять идею «черного ящика», который:

  • Требует интернет для базовых функций

  • Превращается в кирпич при уходе вендора с рынка

  • Не даёт понимания, что происходит на уровне железа

Проблема: как создать умный дом, который остаётся под твоим полным контролем, работает локально и не зависит от облаков?

Фото Sensorbox в сборке

Фото Sensorbox в сборке

Я в то время, однозначно, не очень любил мудреные устройства с no-code интерфейсами, где надо просто нажать на кнопочку, подождать подключения и все. Несмотря на то, что в то время я прошел путь вопросов вида «а как пользователю избежать необходимость вручную через Arduino IDE вводить SSID и пароль WiFi?», мне в целом казалось странным невозможность настраивать каждое новое устройство в своем доме именно так, а не подключать его через продуманное разработчиками мобильное приложение для «далеких от программирования» пользователей.

Устройства умного дома – коммерческие образцы, подключаемые в экосистему этого дома через мобильное приложение

Устройства умного дома – коммерческие образцы, подключаемые в экосистему этого дома через мобильное приложение

Решение: Сенсорбокс — универсальная DIY-платформа на ESP32 с открытым железом и софтом.

По факту я решил, что совсем открывать железо, софт и конкретные кейсы не очень интересно, однако в статье охота погрузить читателя немного в базовые подходы при использовании такой коробочки и в некоторые результаты.

Работа с собственным сервером для хранения и обработки данных как способ независимости от компаний и обновления их ПО

Локальное наличие дома такой коробочки без подключения к WiFi даже в то время уже было лишено всякого смысла. А сегодня же вообще актуален вопрос сборки своих персональных датасетов. так и начал создаваться сервер iot.tfeya.ru, где я пришел к некоторым своим удобствам, если создавать умный дом не по принципу «подключаем готовое решение без кода», а «DIY и понимай все детали». Кстати, этим я занялся еще в 2017 году, но сейчас уже нет смысла хранить это где-то внутри и я бы хотел поделиться тем, что в итоге я имею сегодня, на 2026 год, в результате развития этого DIY-проекта.

Итак, первый джуниорский вопрос, который здесь встанет у любого: нужно заморачиваться с MQTT или достаточно https? На практике для реального умного дома с датчиками по всему дому, переключением света, управлением умными устройствами, самодельным ИК-пультом ситуация чуть сложнее получилась: вообще большинство устройств удобнее подключать умными розетками, купленными на маркетплейсе. И использовать для создания расписания или сценариев готовые приложения от разработчиков данных устройств с интеграцией через Яндекс.

Но вот все, что касается сенсоров (влажность, освещенность, ик-датчики и так далее) – это оказалось гораздо менее мудрено делать на базе коробочек собственной разработки, которые были названы «Sensorbox». Это повод упаковки электроники в миниатюрный DIY-корпус с возможностью выбора произвольных датчиков, но хотелось еще в те времена зафиксировать сам подход, который, казалось бы, и так известный и используемый, но с другой стороны разработчиков же что-то и сейчас останавливает создавать подобные устройства, несмотря на то, что «все все знают».

Видео о том, какие вообще мысли на весь этот счет по поводу Sensorbox: https://rutube.ru/video/23242ec1538cd34494afaaf4528e5c84/

Несмотря на то, что сейчас MQTT-серверов много, вопрос не теряет особой актуальности. Суть вопроса – это как принципы DRY, SOLID, KISS и тп в программировании – я просто в применении к железу захотел иметь универсальный подход, которым можно в сообществе разработчиков пользоваться при проектировании устройств такого рода. 

Если надо собрать, например, автоматизировнное устройство для выращивания растений на Умном Подоконнике – есть в результате рассуждений и эмпирического проектирования такой относительно универсальный паттерн: Сенсорбокс. Америку не открыл, но это не коммерческая замудренная магия, а результат конкретной воспроизводимой последовательности технологических действий.

Еще один момент, который меня напрягает даже при использовании SensorBox: я уже включал и выключал ТВ простыми командами к Яндекс-станции вида «включи телевизор в спальне», но интеграция сбилась и я день провозился в попытках ее восстановить. 

Управление умным телевизором

Управление умным телевизором

И, возможно, Вы бы не увидели подобных строк текста с негативом от сотен людей, оказавшихся в похожей ситуации, если трата целого для их жизни дала бы плоды. Но на этот раз я действительно просто потратил время, причем ни один день. И потратил ни раз несколько дней переписки с поддержкой, которая в итоге, когда ситуация уже ранее решалась, писала «вследствие ухода из России компаний и обновлениях вашего телевизора пока наблюдаются проблемы с подключением, попробуйте позже». Честно – я уже побаиваюсь тратить время и снова писать в поддержку.

Это – вечная дилемма: использовать развивающийся продукт с большим комьюнити или же иметь свою систему, которая разработана четко и работает без сбоев под твои нужды. Практика показывает, что нельзя выбирать между этим, решения нужно комбинировать и постоянно сравнивать, делая упор именно на скорость решения бытовых задач (а уж дорабатывать можно затем первый прототип вечно).

Переход от «потребителя» к «инженеру»: Низкоуровневый подход

Когда мы с учениками (а позже и для своего дома) начинали работать с Сенсорбоксом, мы уходили от высокоуровневых абстракций («просто скажи Алисе включить свет») к суровой, но честной инженерии.

Сегодня концепт полностью пересмотрен, когда в качестве корпуса уже испробованы и распределительная коробка, и лоток из супермаркета под мелкие детали, и корпуса из Чип Дипа.

Суть в том, что с помощью сервиса вида Make a box сегодня любой может сделать .cdr файл или файл в подобном формате, в котором будут подготовлены детали для 3D-принтера. 

Короб непрозрачный, но это если не сделать из акрила, поэтому уже появляется две крутые возможности: если проект не для тестирования, то можно смело делать корпус из 3мм фанеры. Если же нужно смотреть, чтобы внутри что-то не отвалилось – идеально поместить в акриловый корпус.

Самая простая коробочка, полученная менее чем за пару минут, может выглядеть так:

Однако после этого лучше зайти в Corel Draw и доделать отверстия и разрезы под датчики, а также нанести логотип для гравировки.

Детали для сборки Сенсорбокса

Детали для сборки Сенсорбокса

Таким образом, мы вообще не ограничены тем, какой микроконтроллер поставить внутрь, потому что ставится он на латунных стойках, а при несоответствии отверстий их всегда можно подогнать напильником.

ESP32 DEVKIT как одна из плат, которые ставились в Сенсорбокс

ESP32 DEVKIT как одна из плат, которые ставились в Сенсорбокс

То есть при проектировании своей коробочки, вырезанной на лазерном гравере, не возникает вообще проблемы, поставить ли микроконтроллерную плату на 36 пинов или на 48 пинов, главное, чтобы в корпусе было чуть запаса на такие варианты.

В такую коробку можно буквально приклеить и более компактный вариант, если важны компактность и функционирование без заморочек (недопаянный конденсатор, внешнее питание и тп).

Можно сделать решение еще компактнее

Можно сделать решение еще компактнее

По факту во время очередной сборки мне даже пришлось это сделать не очень аккуратно, особо проблем с использованием коробочки из-за этого нет.

Отверстие можно слегка расширить напильником

Отверстие можно слегка расширить напильником

Итоговое устройство выглядит, например, так:

Сенсорбокс в сборе

Сенсорбокс в сборе

Здесь один из датчиков, DHT22, подключен по аудиоразъему, именно это позволяет подключить чуть больше датчиков, например — в виде 10 отдельных модулей. Негерметичность некоторых датчиков можно для своих домашних целей решить применением термоусадки на негерметичную часть датчика.

DHT22, подключенный к Сенсорбоксу по аудиоразъему с применением термоусадки

DHT22, подключенный к Сенсорбоксу по аудиоразъему с применением термоусадки

Теперь первый интересный лайфхак для дома: я не использую болты для того, чтобы закрыть последние 2 стенки коробочки. Если остальные стенки склеиваются между собой клеевым пистолетом, то последние две можно просто закрепить на резинке.

Процесс склеивания стенок клеевым пистолетом

Процесс склеивания стенок клеевым пистолетом

Пример перечня покупок для использования в качестве начинки Sensorbox 

По факту удобнее всего кажется использование в одной такой коробочке 3-4 датчиков.

Компонент

Кол-во

Цена (₽)

Где купить

Примечание

ESP32 DevKit v1

1

450

ЧипДип/AliExpress

36 пинов

DHT22

1

350

ЧипДип

Температура+влажность

MQ-2

1

280

ЧипДип

Датчик газа/дыма

PIR HC-SR501

1

200

AliExpress

Датчик движения

ИК-приемник TSOP1738

1

80

ЧипДип

NEC/RC5 протоколы

ИК-диод 940nm

1

30

ЧипДип

Для управления техникой

OLED 0.96″ I2C

1

250

AliExpress

SSD1306

Резистор 10k

5

10

ЧипДип

Подтяжка линий

Конденсатор 100uF

2

20

ЧипДип

Фильтрация питания

Латунные стойки M3

4

50

ЧипДип

Монтаж платы

CR123A батарея

1

150

Любой магазин

Питание

Корпус (фанера 3мм)

1

100

Лазерная резка

Или акрил

ИТОГО

~2000₽

Вот как выглядит изнанка Умного дома на базе Сенсорбокса:

1. Отказ от облаков в пользу локальных протоколов (MQTT, HTTPS)

Вместо того чтобы отправлять данные на серверы Яндекса, мы настраивали локальный брокер сообщений MQTT (например, Mosquitto) на домашнем сервере или Raspberry Pi. Низкоуровневое программирование здесь проявляется в том, что мы сами писали на C++ (в Arduino IDE) или MicroPython логику подключения к Wi-Fi, удержания сессии, публикации (publish) телеметрии и подписки (subscribe) на команды. Мы сами решали, как часто опрашивать датчики, чтобы не спамить сеть, и как обрабатывать обрывы связи. По факту удобнее пользоваться типовым HTTPS, запросы с подтверждением для домашнего WiFi, несмотря на все заявляемые недостатки, – это нечто более привычное, удобное и используемое ежедневно.

2. Работа с «железом» и прерываниями

Готовый датчик движения просто шлет сигнал. На Сенсорбоксе мы изучали, как работает PIR-датчик на уровне GPIO. Мы настраивали аппаратные прерывания, чтобы микроконтроллер просыпался от движения за миллисекунды, а не опрашивал пин в бесконечном цикле, сжигая батарею. Мы писали алгоритмы дебаунсинга (устранения дребезга) для кнопок и калибровали аналоговые показания фоторезистора под конкретное освещение в комнате.

3. Энергоэффективность (Deep Sleep)

Это, пожалуй, самый интересный аспект низкоуровневой разработки. Как заставить датчик на батарейке работать год? Мы погружали ESP32 в режим глубокого сна (Deep Sleep), отключая периферию и оставляя работать только RTC-контроллер и ULP-сопроцессор. Прошивка писалась так, чтобы плата «спала», просыпалась раз в 5 минут, быстро отправляла пакет данных по MQTT и снова уходила в сон. Понимание того, как микроамперы тока влияют на время жизни от элемента CR123A, дает совершенно иное чувство контроля над устройством.

4. ИК-универсальность

Сенсорбокс с ИК-диодом позволял нам не покупать «умные ИК-пульты». Мы считывали сырые HEX-коды с пульта от старого кондиционера, анализировали их протокол (NEC, RC5) и писали прошивку, которая позволяла Сенсорбоксу выступать в роли моста: если датчик температуры на плате фиксирует >24°C, плата сама отправляет ИК-сигнал на включение кондиционера. Полная локальная автоматизация без привязки к бренду техники.

Принципы «железного» программирования — к которым я пришел

В начале статьи я упомянул, что мне хотелось зафиксировать универсальный подход к проектированию таких устройств. Спустя годы работы с Сенсорбоксом я сформулировал для себя несколько принципов, которые стали для меня аналогом SOLID, но для IoT-железа:

  1. Принцип локальности. Устройство должно быть способно выполнять свою базовую функцию без интернета и без внешнего сервера. Датчик протечки должен пищать сам по себе, даже если сервер упал. Реле должно иметь физическую кнопку для ручного включения.

  1. Принцип открытости протокола. Никаких проприетарных облачных API. Только MQTT, HTTP, CoAP в приоретете над покупными решениями — то, что можно прочитать и отправить с любого устройства. Если завтра я захочу заменить сервер, устройства не должны превратиться в кирпичи.

  1. Полный контроль своих данных и надежность и независимость системы. Завтра опять обновят ПО телевизора Samsung, а еще и при этом слетит интеграция Яндекса. Имея свой пульт c четко понятными hex-командами, можно не волноваться, что устройство вообще когда-либо даст сбой в работе, если разработано на качество.

Принцип наблюдаемости. Устройство должно уметь сообщать о своем состоянии: уровень сигнала Wi-Fi, напряжение питания, uptime, количество перезагрузок. Без этого отладка превращается в гадание на кофейной гуще.

Принцип перепрошиваемости «по воздуху» (OTA). Менять прошивку, распаивая каждый раз корпус и подключая USB-кабель — это путь в никуда. ESP32 поддерживает OTA, и мы обязательно настраивали этот механизм, чтобы обновления могли прилетать по Wi-Fi.

Принцип минимальной достаточности. Не нужно пихать в устройство все датчики мира. Сенсорбокс хорош тем, что на его базе можно собрать конкретное устройство под конкретную задачу: либо датчик протечки, либо датчик движения, либо ИК-пульт. Универсальность достигается не на уровне железа, а на уровне стандартизированной прошивки и протокола общения.

Датчики газа серии MQ

Если подключить датчик газа (например, популярный MQ-2 для обнаружения метана, пропана или дыма) — это, на первый взгляд, тривиальная задача: чтение аналогового порта микроконтроллера. В ESP32 DevKit, кстати, количество таких каналов может ввести в заблуждение — их целых 16 (8 на ADC1 и 10 на ADC2), но дьявол, как всегда, в деталях.

Датчик газа с креплением к корпусу на латунных стойках

Датчик газа с креплением к корпусу на латунных стойках

ADC2 на ESP32 нельзя использовать, если включен Wi-Fi. А поскольку наш Сенсорбокс по определению общается с сервером по беспроводной сети, у вас остаются только 8 каналов ADC1. Это первое, с чем сталкиваешься, когда пытаешься подключить пяток аналоговых датчиков и понимаешь, что аппаратные ограничения реально существуют. Но это и повод использовать маленькую коробочку для работы с логически объединенными 3-10 датчиками.

#include <Arduino.h>#define MQ7_PIN 34          // Только ADC1 (GPIO 32-39), чтобы не конфликтовать с WiFi#define CALIBRATION_AIR_VALUE 1800 // Значение ADC в чистом воздухе (нужно калибровать!)// Переменные для скользящего среднегоconst int NUM_READINGS = 20;int readings[NUM_READINGS];int readIndex = 0;long total = 0;int average = 0;void setup() {  Serial.begin(115200);    // Настройка ADC ESP32  analogReadResolution(12); // 12 бит (0-4095)  analogSetAttenuation(ADC_11db); // Диапазон 0-3.9V (безопасно для 5V модуля с делителем)  // Инициализация массива для скользящего среднего  for (int i = 0; i < NUM_READINGS; i++) {    readings[i] = 0;  }    Serial.println("MQ-7 прогревается (требуется минимум 24 часа для первой калибровки!)");  delay(2000); // Задержка перед первым чтением}void loop() {  // 1. Чтение сырых данных  int sensorValue = analogRead(MQ7_PIN);    // 2. Алгоритм скользящего среднего (устранение шумов)  total = total - readings[readIndex];  readings[readIndex] = sensorValue;  total = total + readings[readIndex];  readIndex = readIndex + 1;    if (readIndex >= NUM_READINGS) {    readIndex = 0;  }  average = total / NUM_READINGS;    // 3. Расчет соотношения Rs/R0  // Rs - сопротивление сенсора сейчас, R0 - сопротивление в чистом воздухе  float rs_ro_ratio = (float)average / (float)CALIBRATION_AIR_VALUE;    // 4. Приблизительный расчет PPM (формула аппроксимирована из графика даташита MQ-7)  // Для CO: ppm = a * (rs/ro)^b, где a и b - коэффициенты из графика  float ppm = 100.0 * pow(rs_ro_ratio, -1.5);     // Ограничиваем вывод, чтобы не было отрицательных или абсурдных значений  if (ppm < 0) ppm = 0;  if (ppm > 1000) ppm = 1000; // Условный потолок для бытового датчика  Serial.print("ADC Raw: ");  Serial.print(average);  Serial.print(" | Rs/R0: ");  Serial.print(rs_ro_ratio, 2);  Serial.print(" | CO est: ");  Serial.print(ppm, 1);  Serial.println(" ppm");  delay(1000); // Чтение раз в секунду}

Вывод данных с датчиков

В целом это удобно подключить устройство к питанию и убеждаться не в том, что данные доходят до сервера, а для начала увидеть их на дисплее. Вот так это выглядит на практике. Выводятся: температура, влажность, процент содержания угарного газа.

Вывод параметров на OLED-дисплей

Вывод параметров на OLED-дисплей

Пример кода для передачи значений с датчиков по WiFi.

#include <WiFi.h>#include <PubSubClient.h>#include <ArduinoJson.h>#include <Adafruit_SSD1306.h>#include <DHT.h>#include <IRremote.h>// Пиновка#define MQ2_PIN 34        // ADC1 Channel 6#define DHT_PIN 4#define PIR_PIN 5#define IR_RECV_PIN 15#define IR_LED_PIN 2#define OLED_SDA 21#define OLED_SCL 22// Настройки WiFi и MQTTconst char* ssid = "YOUR_SSID";const char* password = "YOUR_PASSWORD";const char* mqtt_server = "192.168.1.100";const int mqtt_port = 1883;const char* mqtt_user = "sensorbox";const char* mqtt_pass = "your_password";const char* device_id = "sensorbox_livingroom_01";const char* topic_telemetry = "home/livingroom/sensorbox_01/telemetry";const char* topic_status = "home/livingroom/sensorbox_01/status";// ОбъектыWiFiClient espClient;PubSubClient client(espClient);Adafruit_SSD1306 display(128, 64, &Wire, -1);DHT dht(DHT_PIN, DHT22);IRrecv irrecv(IR_RECV_PIN);IRsend irsend(IR_LED_PIN);// Переменныеunsigned long lastMsg = 0;#define MSG_BUFFER_SIZE (50)char msg[MSG_BUFFER_SIZE];uint16_t mq2_baseline = 0;void setup() {  Serial.begin(115200);    // Инициализация дисплея  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {    Serial.println(F("SSD1306 allocation failed"));  }  display.display();  delay(2000);  display.clearDisplay();    // Настройка ADC  analogReadResolution(12);  // 12-bit (0-4095)  analogSetAttenuation(ADC_11db);  // Диапазон 0-3.9V    // Калибровка MQ-2 (базовое значение в чистом воздухе)  Serial.println("Calibrating MQ-2... Wait 30 seconds");  display.setTextSize(1);  display.setTextColor(SSD1306_WHITE);  display.setCursor(0,0);  display.println("Calibrating MQ-2");  display.display();    uint32_t sum = 0;  for(int i=0; i<100; i++) {    sum += analogRead(MQ2_PIN);    delay(300);  }  mq2_baseline = sum / 100;  Serial.print("MQ-2 baseline: ");  Serial.println(mq2_baseline);    // Инициализация датчиков  dht.begin();  irrecv.enableIRIn();  irsend.begin();    // Подключение WiFi  setup_wifi();    // Подключение MQTT  client.setServer(mqtt_server, mqtt_port);  client.setCallback(callback);    display.clearDisplay();  display.println("System ready");  display.display();}void setup_wifi() {  delay(10);  Serial.println();  Serial.print("Connecting to ");  Serial.println(ssid);    WiFi.mode(WIFI_STA);  WiFi.begin(ssid, password);    while (WiFi.status() != WL_CONNECTED) {    delay(500);    Serial.print(".");  }    Serial.println("");  Serial.println("WiFi connected");  Serial.println("IP address: ");  Serial.println(WiFi.localIP());}void callback(char* topic, byte* payload, unsigned int length) {  // Обработка входящих команд  Serial.print("Message arrived [");  Serial.print(topic);  Serial.print("] ");    char message[length + 1];  for (int i = 0; i < length; i++) {    message[i] = (char)payload[i];  }  message[length] = '\0';    Serial.println(message);    // Парсинг JSON команды  StaticJsonDocument<200> doc;  DeserializationError error = deserializeJson(doc, message);    if (error) {    Serial.print("deserializeJson() failed: ");    Serial.println(error.f_str());    return;  }    // Пример: команда включения ИК-сигнала  if (strcmp(topic, "home/livingroom/sensorbox_01/command") == 0) {    const char* action = doc["action"];    if (strcmp(action, "tv_power") == 0) {      irsend.sendNEC(0xE0E040BF, 32);      Serial.println("TV POWER sent");    }  }}void reconnect() {  // Loop until we're reconnected  while (!client.connected()) {    Serial.print("Attempting MQTT connection...");        String clientId = String(device_id) + "-" + String(random(0xffff), HEX);        if (client.connect(clientId.c_str(), mqtt_user, mqtt_pass)) {      Serial.println("connected");            // Публикация статуса      client.publish(topic_status, "online", true);            // Подписка на команды      client.subscribe("home/livingroom/sensorbox_01/command");    } else {      Serial.print("failed, rc=");      Serial.print(client.state());      Serial.println(" try again in 5 seconds");      delay(5000);    }  }}// Чтение MQ-2 с фильтрациейuint16_t readMQ2() {  uint32_t sum = 0;  for(int i=0; i<10; i++) {    sum += analogRead(MQ2_PIN);    delay(10);  }  return sum / 10;  // Скользящее среднее}// Расчет концентрации газа (упрощенно)float calculateGasRatio(uint16_t raw_value) {  return (float)raw_value / (float)mq2_baseline;}float getGasPPM(uint16_t raw_value) {  float ratio = calculateGasRatio(raw_value);  // Упрощенная формула (для MQ-2)  // В реальности нужна калибровка по графикам из даташита  return 1000 * pow(ratio, -1.5);}void loop() {  if (!client.connected()) {    reconnect();  }  client.loop();    unsigned long now = millis();  if (now - lastMsg > 60000) {  // Отправка раз в минуту    lastMsg = now;        // Чтение датчиков    float temperature = dht.readTemperature();    float humidity = dht.readHumidity();    uint16_t mq2_raw = readMQ2();    float gas_ppm = getGasPPM(mq2_raw);    int rssi = WiFi.RSSI();        // Формирование JSON    StaticJsonDocument<256> doc;    doc["temperature"] = temperature;    doc["humidity"] = humidity;    doc["mq2_raw"] = mq2_raw;    doc["gas_ppm"] = gas_ppm;    doc["battery_mv"] = analogReadMilliVolts(35);  // Внутренний ADC    doc["rssi"] = rssi;    doc["uptime_sec"] = millis() / 1000;    doc["timestamp"] = time(nullptr);        char jsonBuffer[512];    serializeJson(doc, jsonBuffer);        // Публикация    if (client.publish(topic_telemetry, jsonBuffer)) {      Serial.println("Data published");            // Вывод на дисплей      display.clearDisplay();      display.setTextSize(1);      display.setTextColor(SSD1306_WHITE);      display.setCursor(0,0);      display.print("Temp: "); display.print(temperature); display.println(" C");      display.print("Hum: "); display.print(humidity); display.println(" %");      display.print("Gas: "); display.print(gas_ppm); display.println(" ppm");      display.print("RSSI: "); display.print(rssi); display.println(" dBm");      display.display();    } else {      Serial.println("Failed to publish");    }  }    // Обработка ИК-сигналов  decodeIR();    delay(100);}void decodeIR() {  decode_results results;  if (irrecv.decode(&results)) {    Serial.print("IR Code: 0x");    Serial.println(results.value, HEX);        // Сохранение кодов для последующего использования    // Записать в EEPROM или отправить в MQTT        irrecv.resume();  }}

Код перехода в Deep Sleep

#include "esp_sleep.h"#define uS_TO_S_FACTOR 1000000  // Conversion factor for micro seconds to seconds#define TIME_TO_SLEEP  300      // Sleep for 5 minutesvoid goToSleep() {  Serial.println("Going to sleep for 5 minutes");    // Отправка финального сообщения  client.publish(topic_status, "sleeping", true);  client.disconnect();  WiFi.disconnect();    // Настройка пробуждения по таймеру  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);    // ИЛИ пробуждение по GPIO (PIR датчик)  // gpio_set_pull_mode(GPIO_NUM_5, GPIO_PULLUP_ONLY);  // esp_sleep_enable_ext0_wakeup(GPIO_NUM_5, 1);  // Wake on HIGH    Serial.flush();  esp_deep_sleep_start();}// В setup() проверяем причину пробужденияesp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();switch(wakeup_reason) {  case ESP_SLEEP_WAKEUP_EXT0:    Serial.println("Wakeup by external signal using RTC GPIO");    break;  case ESP_SLEEP_WAKEUP_TIMER:    Serial.println("Wakeup by timer");    break;  default:    Serial.println("Wakeup by other reason");}

Потребление в разных режимах

Режим

Ток

Время работы от CR123A (2400mAh)

Active + WiFi TX

240 mA

10 часов

Active + WiFi idle

80 mA

30 часов

Modem-sleep

20 mA

5 дней

Light-sleep

0.8 mA

125 дней

Deep-sleep + RTC

10 μA

27 лет*

Deep-sleep + PIR wake

150 μA

1.8 года

Управление телевизором

С помощью ИК-приемника можно узнать все коды с пульта.

Так я узнал сразу и записал некоторые из них:

const uint32_t TV_POWER = 0xE0E040BF; // POWER

const uint32_t TV_SOURCE = 0xE0E0807F; // SOURCE

const uint32_t TV_RIGHT = 0xE0E046B9; // СТРЕЛКА ВПРАВО

const uint32_t TV_ENTER = 0xE0E016E9; // ENTER/OK

Но если надо просто менять источник с HDMI1 на HDMI2, то для этого есть специальная команда. На моем телевизоре всего 2 источника, и я могу переключаться, просто один раз нажав кнопку на пульте.

0xE0E0D12E

После того, как я для себя записал все эти команды, я быстро навайбкодил (до 2022 года писал только руками) программу для управления телевизором.

Результат получился вот такой: https://rutube.ru/video/a80de90332bac1da78626a7f647992ee/ 

Код для самодельного ИК-пульта на базе Сенсорбокса

// Записанные коды для Samsung TVconst uint32_t TV_CODES[] = {  0xE0E040BF,  // POWER  0xE0E0807F,  // SOURCE  0xE0E0D12E,  // HDMI switch  0xE0E046B9,  // RIGHT  0xE0E016E9,  // ENTER  0xE0E0A05F,  // VOL+  0xE0E0B04F,  // VOL-};void sendIRCommand(uint32_t code) {  irsend.sendNEC(code, 32);  delay(100);  // Повтор для надежности  irsend.sendNEC(code, 32);}// Автоматизация: включение кондиционера при температуре >24°Cvoid checkTemperatureAutomation(float temp) {  static bool ac_on = false;    if (temp > 24.0 && !ac_on) {    Serial.println("Temperature high, turning on AC");    sendIRCommand(0xA05F906F);  // AC POWER ON    sendIRCommand(0xA05F10EF);  // AC 22C    ac_on = true;        // Уведомление в MQTT    client.publish("home/livingroom/ac/status", "on");  }  else if (temp < 22.0 && ac_on) {    Serial.println("Temperature low, turning off AC");    sendIRCommand(0xA05F906F);  // AC POWER OFF    ac_on = false;        client.publish("home/livingroom/ac/status", "off");  }}

В антирекламных целях привожу с примерами адресов.

Заключение

Arduino IDE – это далеко не игрушка для задач Вашего умного дома. Такие «коробочки» можно ставить хоть в каждый угол дома, они держатся даже при отключении Интернета в локальной сети при соответственном программном обеспечении, которое на раз-два пишет LLM. Трафик поступает через сервер Интернета вещей, но через самописный, и если его поставить локально – устройство вообще полностью автономно собирает данные по Вашему дому и хранит их на этом сервере.

За более чем 8 лет эксплуатации Sensorbox в моём доме:

  • 0 зависимостей от облачных сервисов (точнее процентов 60, но я «могу легко остановиться и слезть»)

  • 100% локальная работа даже при отключенном интернете (но по факту после эксперимента я понял, что Интернет пока стабильный и оно мне не надо)

  • ~15000₽ экономии по сравнению с коммерческими аналогами

  • Бесценный опыт для учеников, которые видят, как код меняет физический мир

ссылка на оригинал статьи https://habr.com/ru/articles/1059612/