Привет, Хабр!
Меня всегда интересовала тематика умных домов, и поскольку разработка — это мое хобби, я всегда хотел создать что-то своё, уникальное, и при этом не потратить много денег. Также я давал себе отчет, что все современные системы по большей части имеют скорее маркетинговую цель, но всё же решил попробовать одно из популярных решений.
Дисклеймер: я просто описываю личный опыт, все совпадения случайны!
Опыт с готовыми системами
Во-первых, я убежден, что большая часть девайсов большинству людей просто не нужна. Ну вот, зачем нужно смотреть, какая у тебя влажность дома, пока ты не дома? Уж молчу про «умную» бытовую технику и прочие утюги с 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(), "Готово! ✨");
}
}
