Надежный физический интерфейс для управления умным домом
Компоненты и расходные материалы
| Сервопривод TowerPro SG90 | × | 1 |
| Маленькие петли | × | 1 комплект |
| Кнопки | × | 1 комплект |
| Arduino Yun | × | 1 |
| Трансформатор тока 100А | × | 1 |
| Резисторы и конденсаторы | × | 7 шт. |
О проекте
Смотрите два видео ниже для быстрого понимания проекта.
Физический интерфейс домашней автоматизации — управление реальным миром
Интерфейс передает информацию и позволяет управлять. Большинство систем умного дома используют «виртуальный интерфейс»: достаете телефон, открываете приложение, смотрите состояние дома, включаете/выключаете свет. Это работает, когда вы вне дома. Но виртуальное представление требует фокуса, неинтуитивно.
Я хотел сделать «физический интерфейс» — маленькую модель дома, которая физически копирует состояния реального дома. Если гараж открыт — ворота модели тоже открыты. Модель на столе — взглянете перед сном: гараж открыт? На работе — глянете: входная дверь закрыта? Физический интерфейс может быть таким творческим или утилитарным, каким вы его сделаете.
План:
- Построить модель дома: положение дверей, потребление энергии, свет.
- Сделать монитор энергии (библиотека OpenEnergyMonitor), слать данные в модель и в OpenHAB.
- Способы передачи положения дверей/окон. Использование Wink Hub и датчика Wink Tripper в DIY-системе.
- Использование Wink + Arduino для выходов: открытие реальных ворот гаража, включение света.
Модель имеет сервоприводы и LED, подключенные к Arduino. Контроллер подписан на MQTT-топики (положение дверей, энергия) и двигает сервы. То же для LED индикации света. Есть варианты доставки данных датчиков к MQTT-брокеру — детально в следующих шагах. В центре — Raspberry Pi с Mosquitto (MQTT) и OpenHAB. OpenHAB не обязателен для модели, но нужен для приложения на телефоне и удаленного мониторинга/управления. Физический интерфейс не значит отказ от виртуального.
На модели две кнопки: одна переключает Zigbee-лампочку, вторая — открывает/закрывает реальные ворота гаража.
Часть 1: Постройка модели дома
Шаг 1: Конструкция модели
Не претендую на прескриптивность. Я новичок в поделках — просто покажу как сделал. Стройте под свой дом и заботы. Много места для креатива. Некоторые компоненты (трансформатор тока) используются в следующих шагах.
Компоненты:
- Коробка (любой размер/пропорции);
- 3× Tower Pro SG90 сервопривод ссылка;
- 2 маленькие петли ссылка;
- Клейкий материал (я использовал 3M двухсторонний скотч от хуков);
- Arduino Yun или Arduino Uno + Ethernet Shield;
- Кнопки ссылка;
- Хлам по дому.
Крыша
Вырезал боковой клапан коробки, прикрепил к другому (оставшемуся) для образования крыши. Маленькая коробка для прямого угла, позже усилил LEGO-деталью. Можно импровизировать.
Двери / Окна
Вырезайте отверстия для дверей. Петля на двери (двухсторонний скотч) → к дому. Использовал 3M пенные полоски. Сервоприводы тоже на пенном скотче. Гараж: серва открывает, гравитация закрывает. Входная дверь: нитка на петле, серва тянет за нитку. Желтая LEGO-деталь — отступ сервы от петли.
Счетчик энергии
Стрелка на руке сервы. Кустик с шкалой киловатт, серва втиснута между коробкой и кустиком.
Над сервой энергии — красный LED.
Две кнопки управления светом и воротами гаража. Моментальные, резьба с гайкой снаружи для фиксации к картону. Снаружи металлические контакты для пайки проводов.
Шаг 2: Проводка и код
Проводка
Схема на фото. Если нужно просто соответствие пинов:
- Status LED (MQTT): D7
- Lighting indicator LED: D4
- Servo 1 (входная дверь): D3
- Servo 2 (ворота гаража): D5
- Servo 3 (энергोसчетчик): D6
- Button 1 (свет вкл/выкл): D8
- Button 2 (ворота гаража): D9
Варианты контроллера
Arduino Uno клон + Ethernet Shield (~$20) — дешевле, но привязывает к кабелю. Перешел на Arduino Yun (~$65) — дороже, но Wi-Fi свобода. Yun прост: подключить к роутеру кабелем → веб-интерфейс Yun → пароли/статический IP → загрузить скетч.
Скетчи для Uno и Yun во вложениях. Похожи, кроме Ethernet/Yun bridge. Кнопки добавил после перехода на Yun, поэтому в скетче Uno их нет.
Видео с Yun (в шаге 1). Видео с Uno + Ethernet Shield (кнопок еще не было): YouTube
Файлы: smarthome_ethernet.ino, smarthome_yun.ino
Часть 2: Входы (Датчики)
Шаг 3: Датчик: Монитор энергии
Теперь нужен сенсорный узел для чтения потребления дома и публикации в MQTT. Способов несколько. Я использую Arduino Yun — проще, но не дешевле. Альтернативы: Uno + Ethernet Shield + Wi-Fi роутер-бридж, или Raspberry Pi + OpenEnergyMonitor. Опишу Yun.
Трансформатор тока $7 с eBay (Yhdc SCT-013-000 100A). Схема подключения выше, скетч Yun внизу шага. Поменяйте IP MQTT-брокера. Референс: OpenEnergyMonitor. Идеальные значения компонентов:
- Нагрузочный резистор = 33 Ом
- Резисторы делителя напряжения = 10 кОм
- Конденсатор = 10 мкФ
Моя схема не совпадает с идеальной: не было 33 Ом — поставил 2×68 Ом параллельно. Не было 10 мкФ — 2×22 мкФ последовательно (эквивалент ~11 мкФ, достаточно).
Трансформатор на одной входящей фазе. Только один 100А трансформатор — мониторю одну фазу. Хочу добавить больше для второй фазы и групповых цепей. По амперметру: моя схема всегда показывает +1А выше реального — просто вычитаю в коде.
OpenHAB
Данные идут — выведем в OpenHAB для графиков. Конфиг:
Items:
Number itm_smarthome_energy_amps "Energy (amps) [%.1f]" {mqtt="<[mymosquitto:2853:state:default]"}
Number itm_smarthome_energy_watts "Energy (Watts) [%.1f]" {mqtt="<[mymosquitto:2852:state:default]"}
Sitemap:
Text label="Energy" icon="firstfloor"
{
Frame label="Energy Usage" {
Text item=itm_smarthome_energy_amps
Text item=itm_smarthome_energy_watts
Chart item=itm_smarthome_energy_watts period=h refresh=5000
}
}
Persistence (RRD4J):
Strategies {
everyMinute : "0 * * * * ?"
}
Items {
DemoSwitch,NoOfLights,Window_GF_Toilet,Heating* : strategy = everyChange, everyMinute, restoreOnStartup
Temperature*,Weather_Chart* : strategy = everyMinute, restoreOnStartup
itm_smarthome_energy_watts : strategy = everyUpdate
}
Экран энергии в OpenHAB:
Файл: energy_monitor_yun.ino
Шаг 4: Датчик: DIY беспроводные сенсорные узлы
Варианты беспроводных датчиков открытия/закрытия.
В прошлом проекте использовал $5 радио-трансивер для отправки данных через MQTT-шлюз. Еще использую для статуса ворот гаража в OpenHAB — в демо видео именно так модель отражает реальные ворота. Код и схема в том проекте (в гараже, эстетика не важна).
Также датчик света (фоторезистор) — показывает, оставлен ли свет включенным. Часть «Uber sensor» из того же проекта. Здесь просто подписываюсь на MQTT-топик уровня света.
Сделал батарейный рид-свитч на том же трансивере/шлюзе. Но выглядит ужасно — не поставишь на внутреннюю дверь/окно. Поэтому использую красивые потребительские датчики Wink — следующий шаг.
Шаг 5: Датчик: Wink Hub
Нашел блог раннего пользователя Wink Hub, взломавшего хаб для доступа к PHP-эксплойту. Утилита «aprontest» позволяет управлять устройствами, спаренными с Wink. Так я управляю светом из OpenHAB.
Главное преимущество рутированного Wink Hub — локальное управление светом и чтение датчиков без интернета (Wink API всегда ходит на сервер Wink). Теперь операции остаются в локальной LAN.
PHP-скрипт (внизу шага) запускается на Raspberry Pi, опрашивает Wink Hub о статусе двух Wink Tripper рид-свитчей. Статус → OpenHAB через REST API. OpenHAB публикует MQTT-топики положения дверей. Контроллер модели подписывается и двигает двери.
Три конфига OpenHAB (items, sitemap, rules) во вложениях — нужны для работы. Определяют контактные items для polling-скрипта через REST. Есть пример скрипта управления Zigbee-лампочкой через OpenHAB.
Видео: YouTube
Скрипты и конфиги: sitemap.txt, rules.txt, items.txt, demo_1_general.sh, polling.php
Шаг 6: Выход: Свет
Конфиги из шага 5 уже дают всё для управления «умной лампочкой» через физический интерфейс и OpenHAB. Лампочки, поддерживаемые Wink: тестировал Cree Connected и GE Wink — работают. Видео работы датчика света и кнопок: YouTube
Шаг 7: Выход: Открыватель ворот гаража
Конфиги шага 5 покрывают OpenHAB items для ворот. Нужно устройство для физического открытия. Использую модификацию прошлого проекта:
- Arduino клон ($10)
- Ethernet shield ($7)
- Рид-реле ($2)
- Запасной пульт от ворот ($22)
Arduino управляет рид-реле (замыкает/размыкает сухие контакты). Кнопка пульта запаяна параллельно контактам реле. Pin 5 и GND Arduino → катушка реле (два внешних пина), внутренние контакты реле → кнопка пульта.
Arduino подписан на MQTT-топик, ждет команду open/close. При получении — кратковременно заряжает реле, замыкая кнопку пульта. Скетч во вложениях. Добавьте garage_monitor_rules.txt в rules (дополнительно к rules шага 5). Если не используете ультразвуковой датчик — адаптируйте.
Видео: YouTube
Если ворота тихие или нужен звуковой алерт в другом месте дома — правило выше воспроизводит аудиофайл при открытии. Слышно в первом демо ~1:30. YouTube
Файлы: garage_monitor_rules.txt, grage_opener.ino
Шаг 8: Другие мысли
Решения отражают мои инструменты, запчасти и приоритеты. У вас — другие. MQTT + OpenHAB дают гибкость — не обязательно копировать точно.
- Контроллер модели: любой Arduino, Raspberry Pi, Spark Core;
- DIY датчики: Arduino или отдельный ESP8266;
- Единственное ограничение: данные датчиков должны попасть в MQTT-брокер;
- USB Z-Wave стик на OpenHAB → Z-Wave to MQTT мост → Z-Wave датчики;
- Wink датчики без рута: Wink API → OpenHAB или прямо в MQTT (требует интернет, но не требует рута).
Много способов. Я тяну к «наименьшему общему знаменателю» — Arduino и картонные коробки.
Код
- smarthome_yun.ino
- smarthome_ethernet.ino
- energy_monitor_yun.ino
smarthome_yun.ino
// Eric Tsai // 2015-04-13 // Arduino Yun code for controlling physical smart home interface // Modify MQTT topic names to match your use // Modify MQTT broker IP address #include#include #include #include unsigned long keepalivetime=0; unsigned long MQTT_reconnect=0; YunClient yun; PubSubClient client("192.168.1.101", 1883, callback, yun); bool conn_ok; int ledPin = 4; // indicate lights on int statusPin = 7; // indicate MQTT conn status int servo1Pin = 3; // front door int servo2Pin = 5; // garage door int servo3Pin = 6; // energy usage meter Servo servo1, servo2, servo3; int button1 = 8; // button over front door int button2 = 9; // button over garage door int servo1_target = 5; // door, closed int servo2_target = 5; // garage, closed int servo3_target = 180; // energy, 180 = "0" watts (motor mounted backwards) int servo1_pos_cur = 5; int servo2_pos_cur = 5; int servo3_pos_cur = 180; int servo1Opened = 5; int servo1Closed = 130; int servo2Opened = 5; int servo2Closed = 150; unsigned long servo1_time = 0; unsigned long servo2_time = 0; unsigned long servo3_time = 0; unsigned long button1_db = 0; unsigned long button2_db = 0; int button = 0; int callback_flag = 0; char buff_message[12]; void callback(char* topic, byte* payload, unsigned int length) { int mytopic = atoi(topic); payload[length] = '\0'; float mymsg = atof((const char*)payload); if (mytopic == 2822) { // LED digitalWrite(ledPin, mymsg == 1 ? HIGH : LOW); } if (mytopic == 2832) { // servo 1, front door servo1_target = (mymsg == 1) ? servo1Opened : servo1Closed; } if (mytopic == 2842) { // servo 2, garage door servo2_target = (mymsg == 1) ? servo2Opened : servo2Closed; } if (mytopic == 2852) { // servo 3, energy meter if (mymsg > 6000) mymsg = 6000; if (mymsg < 0) mymsg = 0; servo3_target = 180 - ((1 - ((6000 - mymsg) / 6000)) * 180) - 25; callback_flag = 1; } } void setup() { pinMode(ledPin, OUTPUT); pinMode(statusPin, OUTPUT); pinMode(button1, INPUT_PULLUP); pinMode(button2, INPUT_PULLUP); digitalWrite(ledPin, HIGH); Bridge.begin(); client.connect("yun smarthome"); client.publish("watch", "Smart Home Connected!"); keepalivetime = millis(); MQTT_reconnect = millis(); client.subscribe("2822"); client.subscribe("2832"); client.subscribe("2842"); client.subscribe("2852"); digitalWrite(ledPin, LOW); servo1.attach(servo1Pin); servo1.write(90); servo2.attach(servo2Pin); servo2.write(90); servo3.attach(servo3Pin); servo3.write(5); } void loop() { client.loop(); if ((millis() - servo1_time) > 20) { if (servo1_pos_cur > servo1_target) servo1_pos_cur--; if (servo1_pos_cur < servo1_target) servo1_pos_cur++; servo1.write(servo1_pos_cur); servo1_time = millis(); } if ((millis() - servo2_time) > 20) { if (servo2_pos_cur > servo2_target) servo2_pos_cur--; if (servo2_pos_cur < servo2_target) servo2_pos_cur++; servo2.write(servo2_pos_cur); servo2_time = millis(); } if ((millis() - servo3_time) > 20) { if (servo3_pos_cur > servo3_target) servo3_pos_cur--; if (servo3_pos_cur < servo3_target) servo3_pos_cur++; servo3.write(servo3_pos_cur); servo3_time = millis(); } if (callback_flag == 1) { callback_flag = 0; dtostrf(servo3_target, 4, 5, buff_message); client.publish("watch", buff_message); } button = digitalRead(button1); if (button == 0) { if ((millis() - button1_db > 3000) || (button1_db > millis())) { button1_db = millis(); client.publish("2862", "button1 yaya"); } } button = digitalRead(button2); if (button == 0) { if ((millis() - button2_db > 3000) || (button2_db > millis())) { button2_db = millis(); client.publish("2872", "button2 yaya"); } } if ((millis() - MQTT_reconnect) > 60000) { conn_ok = client.connected(); digitalWrite(statusPin, conn_ok ? HIGH : LOW); if (!conn_ok) { client.disconnect(); delay(5000); while (client.connect("smarthouse") != 1) { digitalWrite(statusPin, LOW); delay(4000); } digitalWrite(statusPin, HIGH); } MQTT_reconnect = millis(); client.publish("watch", "smart home heartbeat!"); } }
smarthome_ethernet.ino
// Eric Tsai // 2015-04-1 // Arduino Uno w/ Ethernet, code for controlling physical smart home interface #include#include #include #include byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x43, 0x13 }; byte server[] = { 192, 168, 1, 101 }; EthernetClient ethClient; PubSubClient client(server, 1883, callback, ethClient); unsigned long keepalivetime=0; unsigned long MQTT_reconnect=0; bool conn_ok; int ledPin = 4; int statusPin = 7; int servo1Pin = 3; int servo2Pin = 5; int servo3Pin = 6; Servo servo1, servo2, servo3; int servo1_target = 5; int servo2_target = 5; int servo3_target = 180; int servo1_pos_cur = 5; int servo2_pos_cur = 5; int servo3_pos_cur = 180; int servo1Opened = 5; int servo1Closed = 120; int servo2Opened = 5; int servo2Closed = 150; unsigned long servo1_time = 0; unsigned long servo2_time = 0; unsigned long servo3_time = 0; void callback(char* topic, byte* payload, unsigned int length) { Serial.println("received MQTT"); int mytopic = atoi(topic); payload[length] = '\0'; float mymsg = atof((const char*)payload); Serial.print("("); Serial.print(mytopic); Serial.print(", "); Serial.print(mymsg); Serial.println(")"); if (mytopic == 2822) { digitalWrite(ledPin, mymsg == 1 ? HIGH : LOW); } if (mytopic == 2832) { servo1_target = (mymsg == 1) ? servo1Opened : servo1Closed; Serial.println(servo1_target); } if (mytopic == 2842) { servo2_target = (mymsg == 1) ? servo2Opened : servo2Closed; } if (mytopic == 2852) { if (mymsg > 6000) mymsg = 6000; if (mymsg < 0) mymsg = 0; servo3_target = 180 - ((1 - ((6000 - mymsg) / 6000)) * 180); } } void setup() { Serial.begin(9600); Serial.println("starting"); pinMode(ledPin, OUTPUT); pinMode(statusPin, OUTPUT); digitalWrite(ledPin, HIGH); while (Ethernet.begin(mac) != 1) { Serial.println("Error getting IP via DHCP, retry..."); delay(5000); } Serial.println("ethernet OK"); keepalivetime = millis(); while (client.connect("smarthouse") != 1) { Serial.println("Error connecting to MQTT"); delay(4000); } MQTT_reconnect = millis(); Serial.println("setup complete"); client.publish("smarthouse", "hello world"); client.subscribe("2822"); client.subscribe("2832"); client.subscribe("2842"); client.subscribe("2852"); digitalWrite(ledPin, LOW); servo1.attach(servo1Pin); servo1.write(90); servo2.attach(servo2Pin); servo2.write(90); servo3.attach(servo3Pin); servo3.write(5); } void loop() { client.loop(); if ((millis() - servo1_time) > 20) { if (servo1_pos_cur > servo1_target) servo1_pos_cur--; if (servo1_pos_cur < servo1_target) servo1_pos_cur++; servo1.write(servo1_pos_cur); servo1_time = millis(); } if ((millis() - servo2_time) > 20) { if (servo2_pos_cur > servo2_target) servo2_pos_cur--; if (servo2_pos_cur < servo2_target) servo2_pos_cur++; servo2.write(servo2_pos_cur); servo2_time = millis(); } if ((millis() - servo3_time) > 20) { if (servo3_pos_cur > servo3_target) servo3_pos_cur--; if (servo3_pos_cur < servo3_target) servo3_pos_cur++; servo3.write(servo3_pos_cur); servo3_time = millis(); } if ((millis() - MQTT_reconnect) > 60000) { conn_ok = client.connected(); digitalWrite(statusPin, conn_ok ? HIGH : LOW); Serial.println(conn_ok ? "MQTT connected OK" : "MQTT NOT connected OK"); if (!conn_ok) { client.disconnect(); delay(5000); while (client.connect("smarthouse") != 1) { digitalWrite(statusPin, LOW); delay(4000); } digitalWrite(statusPin, HIGH); } MQTT_reconnect = millis(); client.publish("smarthouse", "heartbeat every minute!"); } }
energy_monitor_yun.ino
/* Eric Tsai 2015-04-22 Arduino Yun code for publishing energy use to MQTT broker Modify the "PubSubClient client" for your broker IP address */ #include#include #include #include "EmonLib.h" int LED = 3; EnergyMonitor emon1; unsigned long MQTT_reconnect = 0; unsigned long read_energy = 0; double Irms; bool conn_ok; YunClient yun; void callback(char* topic, byte* payload, unsigned int length) { digitalWrite(LED, HIGH); delay(500); digitalWrite(LED, LOW); } PubSubClient client("192.168.1.101", 1883, callback, yun); void setup() { pinMode(LED, OUTPUT); digitalWrite(LED, HIGH); delay(1000); digitalWrite(LED, LOW); Bridge.begin(); client.connect("yun_energy"); client.publish("watch", "Energy Monitor Connected!"); client.subscribe("yun_energy"); digitalWrite(LED, HIGH); delay(3000); digitalWrite(LED, LOW); emon1.current(5, 60); // pin, calibration } void loop() { char buff_message[12]; float value; client.loop(); Irms = emon1.calcIrms(1480); if (millis() - read_energy > 10000) { value = Irms; dtostrf(value, 4, 5, buff_message); client.publish("2853", buff_message); // amps value = Irms * 115; dtostrf(value, 4, 5, buff_message); client.publish("2852", buff_message); // watts read_energy = millis(); } if ((millis() - MQTT_reconnect) > 60000) { conn_ok = client.connected(); digitalWrite(LED, conn_ok ? HIGH : LOW); if (!conn_ok) { client.disconnect(); delay(5000); while (client.connect("yun_energy") != 1) delay(4000); digitalWrite(LED, HIGH); client.publish("watch", "Energy Monitor reconnected"); } MQTT_reconnect = millis(); } }
Схемы
Производственный процесс
- Домашний тест на беременность: принцип действия, дизайн и будущее
- Освоение интерфейсов в C#: определение, реализация и практические примеры
- Интерфейсы в Java: концепции, реализация и лучшие практики
- Как технологии умного дома трансформируют повседневную жизнь
- Создайте систему домашней автоматизации на Raspberry Pi для удаленного управления
- Labman Automation: пионерские роботизированные решения для промышленности
- IoT и домашняя автоматизация: формирование умной жизни завтрашнего дня
- Интерфейсы в C#: контракты для надежного ООП
- Промышленная автоматизация 4.0: революционизируем производственную эффективность
- Автоматизация прессов: технологии, безопасность и эффективность