Привет, Хабр!

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

Дисклеймер: я просто описываю личный опыт, все совпадения случайны!

Опыт с готовыми системами

Во-первых, я убежден, что большая часть девайсов большинству людей просто не нужна. Ну вот, зачем нужно смотреть, какая у тебя влажность дома, пока ты не дома? Уж молчу про «умную» бытовую технику и прочие утюги с Wi-Fi. Поэтому мой комплект был такой:

  • Тындекс Станция Мини Плюс (00020)

  • Тындекс Лампа 3 (00501) – 4 штуки

  • Тындекс Розетка (0007)

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

Для тех, кто не в курсе, сразу объясню принцип работы лампочек: после того как ты вкрутил лампочки в люстру, выключатель на стене не нужно трогать вообще! То есть ты подаешь питание на лампочки, они подключаются к сети, а дальше — всеми любимая магия: «Анфиса, включи свет!»

Достаточно удобно: просто сказать — и всё. Вот только иногда приходится здорово подождать! Иногда ожидание доходило до 5 секунд, и это уже начинало бесить. Более того, случаются ситуации, когда ты не можешь разговаривать (и шептать тоже). В таком случае всё еще лучше: достаешь телефон -> запускаешь приложение -> ждешь, пока оно загрузится -> нажимаешь на кнопку. Классно? — Ну еще бы!

Также отмечу, что каждый девайс любил просто взять и потерять сеть. В моем случае из 4 лампочек иногда загорались 3, а иногда 2. С чем это связано — большой вопрос, но факт остается фактом. Причем подобные вещи случались частенько.

А еще вот момент, смоделируем ситуацию! Вы познакомились с девушкой, у вас с успехом прошли, допустим, 17 свиданий, и вот вы пригласили ее вечером к себе на ужин. Она зайдет в комнату и автоматически нажмет на выключатель. Догадываетесь? Верно! Она размыкает цепь, все 4 лампочки теряют интернет, и даже при попытке быстро всё вернуть вам всё равно нужно будет ждать минуты 3, пока каждая лампочка придет в себя. То есть даже если снова «включить» выключатель, лампочки не загорятся — они запомнили, что были выключены, и верно будут нести это состояние при любом положении выключателя.
Впрочем, в данной ситуации это, конечно, сыграет на руку, ведь ужин можно пропустить и сразу перейти к предполагаемому финалу.

Однажды у меня произошел интересный случай, я забыл оплатить интернет. Не спрашивайте как, забыл и всё. Вернулся с работы очень поздно, голодный был, как собака.
Пока я его оплатил и пока эти «умные» ребята поняли, что произошло... Короче, 10 минут я ооооочень злой сидел в темноте. Ну очень злой. Очень.

Пользуясь случаем расскажу еще одну занимательную историю.
У Тындекс станций есть две шкалы громкости: 0 – 10 и 0 – 100. Однажды, около 2-х часов ночи, когда я ложился спать, я решил сбавить громкость с 15 до 10. Добился ли я желаемого результата? Ну... произошло небольшое недопонимание со стороны Анфисы.
Ладно, это всё лирика. Я рассказываю про дом, а не про колонки.

В общем, по результатам своего опыта я сделал всего 2, но очень важных вывода:

  1. Основное освещение не должно быть «умным».

  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(), "Готово! ✨");
  }
}