Привет, Хабр!
Меня всегда интересовала тематика умных домов, и поскольку разработка — это мое хобби, я всегда хотел создать что-то своё, уникальное, и при этом не потратить много денег. Также я давал себе отчет, что все современные системы по большей части имеют скорее маркетинговую цель, но всё же решил попробовать одно из популярных решений.
Дисклеймер: я просто описываю личный опыт, все совпадения случайны!
Опыт с готовыми системами
Во-первых, я убежден, что большая часть девайсов большинству людей просто не нужна. Ну вот, зачем нужно смотреть, какая у тебя влажность дома, пока ты не дома? Уж молчу про «умную» бытовую технику и прочие утюги с Wi-Fi. Поэтому мой комплект был такой:
Тындекс Станция Мини Плюс (00020)
Тындекс Лампа 3 (00501) – 4 штуки
Тындекс Розетка (0007)
Соглашусь, не самый богатый набор, но большего мне и не нужно было. Лампочки — в люстру, в розетку — гирлянда. Как я и сказал в начале, важно понимать, чем ты точно будешь пользоваться, а чем нет. Сначала мне все нравилось, однако...
Для тех, кто не в курсе, сразу объясню принцип работы лампочек: после того как ты вкрутил лампочки в люстру, выключатель на стене не нужно трогать вообще! То есть ты подаешь питание на лампочки, они подключаются к сети, а дальше — всеми любимая магия: «Анфиса, включи свет!»
Достаточно удобно: просто сказать — и всё. Вот только иногда приходится здорово подождать! Иногда ожидание доходило до 5 секунд, и это уже начинало бесить. Более того, случаются ситуации, когда ты не можешь разговаривать (и шептать тоже). В таком случае всё еще лучше: достаешь телефон -> запускаешь приложение -> ждешь, пока оно загрузится -> нажимаешь на кнопку. Классно? — Ну еще бы!
Также отмечу, что каждый девайс любил просто взять и потерять сеть. В моем случае из 4 лампочек иногда загорались 3, а иногда 2. С чем это связано — большой вопрос, но факт остается фактом. Причем подобные вещи случались частенько.
А еще вот момент, смоделируем ситуацию! Вы познакомились с девушкой, у вас с успехом прошли, допустим, 17 свиданий, и вот вы пригласили ее вечером к себе на ужин. Она зайдет в комнату и автоматически нажмет на выключатель. Догад��ваетесь? Верно! Она размыкает цепь, все 4 лампочки теряют интернет, и даже при попытке быстро всё вернуть вам всё равно нужно будет ждать минуты 3, пока каждая лампочка придет в себя. То есть даже если снова «включить» выключатель, лампочки не загорятся — они запомнили, что были выключены, и верно будут нести это состояние при любом положении выключателя.
Впрочем, в данной ситуации это, конечно, сыграет на руку, ведь ужин можно пропустить и сразу перейти к предполагаемому финалу.
Однажды у меня произошел интересный случай, я забыл оплатить интернет. Не спрашивайте как, забыл и всё. Вернулся с работы очень поздно, голодный был, как собака.
Пока я его оплатил и пока эти «умные» ребята поняли, что произошло... Короче, 10 минут я ооооочень злой сидел в темноте. Ну очень злой. Очень.
Пользуясь случаем расскажу еще одну занимательную историю.
У Тындекс станций есть две шкалы громкости: 0 – 10 и 0 – 100. Однажды, около 2-х часов ночи, когда я ложился спать, я решил сбавить громкость с 15 до 10. Добился ли я желаемого результата? Ну... произошло небольшое недопонимание со стороны Анфисы.
Ладно, это всё лирика. Я рассказываю про дом, а не про колонки.
В общем, по результатам своего опыта я сделал всего 2, но очень важных вывода:
Основное освещение не должно быть «умным».
Любой девайс должен обязательно иметь ручной режим.
И да, я в курсе, что технологии развиваются, появляются более новые модели девайсов и прочее, но вы видели их цену? В общем, в моих реалиях с моим комплектом я был очень разочарован.
Разработка собственной системы
Как я и упомянул в начале, я всё равно хотел что-то свое. Управлять мне нужно было совсем немногим: лампочки над столом, свет на балконе и вентилятор в вентшахту (далее — вытяжка). Все три устройства просто подключались в розетку и не имели собственного выключателя.
Выбирая технологии, я опирался на следующие аспекты: скорость, надежность, простота использования и цена. В детстве я любил экспериментировать с ардуино, поэтому решил не отступать от традиций, а в качестве передачи информации по воздуху я выбрал старый добрый радиосигнал на 433 частоте.
Итак, у меня получился следующий набор:
ESP8266 – 270 рублей
Радиореле для шлагбаумов – 3 штуки, 840 рублей
Комплект SYN115 + SYN480R – 360 рублей

В комплекте к каждому реле шло два пульта, так что «ручной режим» был предусмотрен. Что бы ни произошло, всегда имеется возможность быстро тыкнуть по пульту.

Размышляя над панелью управления, я решил не писать сложных приложений и просто сделать небольшой веб-сервер. Он отдавал страницу кнопками управления, а кнопки отправляли сигнал на реле — всё просто!
Почти сразу я понял, что заходить на сайт чудовищно неудобно, и пришел к встроенным средствам своего смартфона. В iOS есть встроенное приложение «Команды», а в них — функция «Получить содержимое из URL» (по сути сделать обычный http-запрос). Настроив несколько быстрых команд, я вынес их в виджет на экран «Домой». Благодаря быстрым командам я также открыл возможность управления голосом. Siri распознавала быструю команду и управляла устройством.

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

Надо сказать, я был приятно удивлен результатом. Всё работало достаточно быстро, стабильно и без потерь сигнала. Я проводил некоторые эксперименты с отключением интернета, и ESP подхватывала сигнал через несколько секунд после его восстановления (уж точно быстрее глупых лампочек).
Выводы
Таким образом, я сделал максимально простую, дешевую (1500р.) систему, которая подходила именно мне. При этом сохранил важное преимущество умного дома — голосовое управление. Также ничего не мешает присоединить датчики, ИК-передатчики и много других модулей.
Неважно, от какой фирмы ваш «умный дом» или сколько у вас устройств, — главное, чтобы он покрывал ваши цели, был по-настоящему полезен и не раздражал вас!
Возможно кому-то моя реализация будет полезна, поэтому код я оставлю ниже.
Спасибо, что читаете!
#include <Arduino.h> #include <RCSwitch.h> #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <FastBot2.h> #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> #include <ArduinoJson.h> #define WIFI_SSID *YOUR_WIFI_NAME* #define WIFI_PASS *YOUR_WIFI_PASSWORD* #define BOT_TOKEN *YOUR_BOT_TOKEN* #define ADMIN_ID *YOUR_TG_ID* #define RADIO_PIN 4 // D2 ESP8266WebServer server(80); RCSwitch mySwitch = RCSwitch(); FastBot2 bot; JsonDocument access_ids; JsonDocument radio_signals; JsonDocument device_states; void setup(void) { delay(2000); Serial.begin(115200); mySwitch.enableTransmit(RADIO_PIN); connectWiFi(); dataSetup(); botSetup(); server.on("/setState", []() { String device = server.arg("device"); String command = server.arg("command"); switcher(device, command); server.send(200, "text/plain", "Done!"); }); server.begin(); Serial.println("Настрока завершена, сервер и бот запущены ✅"); } void loop(void) { server.handleClient(); MDNS.update(); bot.tick(); } // Подключение к Wi-Fi void connectWiFi() { Serial.println(); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); if (millis() > 15000) ESP.restart(); } Serial.println("Connected! IP address: "); Serial.println(WiFi.localIP()); } // Настройка бота void botSetup(void) { bot.setToken(F(BOT_TOKEN)); bot.attachUpdate(update); bot.setPollMode(fb::Poll::Long, 60000); } // Стартовая установка данных в объекты void dataSetup(void) { access_ids[ADMIN_ID] = true; radio_signals["lamp_on"] = 6063777; radio_signals["lamp_off"] = 6063778; radio_signals["wind_on"] = 7720097; radio_signals["wind_off"] = 7720098; radio_signals["balcony_on"] = 3437089; radio_signals["balcony_off"] = 3437090; device_states["lamp"] = false; device_states["wind"] = false; device_states["balcony"] = false; } void sendRadioSignal(int code) { mySwitch.send(code, 24); } void turnOn(String device) { int signalCode = radio_signals[device + "_on"]; sendRadioSignal(signalCode); device_states[device] = true; } void turnOff(String device) { int signalCode = radio_signals[device + "_off"]; sendRadioSignal(signalCode); device_states[device] = false; } // Контроллер команд переключения void switcher(String device, String command) { if (command == "on") { turnOn(device); } if (command == "off") { turnOff(device); } if (command == "switch") { if (!device_states[device]) { turnOn(device); } else { turnOff(device); } } } // Отправка кнопок управления void sendHomeControls(String userId) { fb::Message homeMessage("My home 🏠", userId); fb::InlineMenu controlsMenu("Кухня 💡; Вытяжка 💨; Балкон 👨💻 \n Выключить всё ❌", "lamp;wind;balcony;shut_down"); homeMessage.setInlineMenu(controlsMenu); bot.sendMessage(homeMessage); } // Главный обработчик запросов к боту void update(fb::Update& u) { String user_id = String(u.message().from().id()); String user_name = u.message().from().username(); Text user_text = u.message().text(); // Обработка команды /start и /request if (user_text == "/start" || user_text == "/request") { if (user_id == ADMIN_ID) { sendHomeControls(ADMIN_ID); return; }; fb::Message requestMessage("Запрос отправлен ✉️", user_id); bot.sendMessage(requestMessage); fb::Message adminMessage("Новый запрос доступа от @" + user_name + " (" + user_id + ")", ADMIN_ID); fb::InlineMenu accessMenu("Разрешить доступ 🔓", "access" + user_id); adminMessage.setInlineMenu(accessMenu); bot.sendMessage(adminMessage); }; // Обработка команды /home if (user_text == "/home") sendHomeControls(user_id); // Обработка inline кнопок if (u.isQuery()) { String user_id = String(u.message().from().id()); Text user_data = u.query().data(); // Запрещаем любые действия без доступа if (!access_ids[user_id]) { fb::Message rejectMessage("Доступ запрещен❗️\nНажмите /request для запроса", user_id); bot.sendMessage(rejectMessage); return; }; // Обработка кнопки Разрешить доступ 🔓 if (user_data.startsWith("access")) { String new_user_id = user_data.substring(6); access_ids[new_user_id] = true; fb::Message allowMessage("Доступ разрешен ✅", new_user_id); bot.sendMessage(allowMessage); sendHomeControls(new_user_id); fb::Message adminMessage("Доступ для " + new_user_id + " разрешен ✅", ADMIN_ID); bot.sendMessage(adminMessage); }; if (user_data.startsWith("shut_down")) { switcher("lamp", "off"); switcher("wind", "off"); switcher("balcony", "off"); } else { switcher(user_data, "switch"); }; bot.answerCallbackQuery(u.query().id(), "Готово! ✨"); } }
