- PVSM.RU - https://www.pvsm.ru -
В своей первой статье [1] я описал предысторию появления системы удаленного управления отоплением в загородном доме через Telegram-бота, которым я и моя семья пользовались долгое время.
С выходом iOS 10, Apple представила пользователям приложение Дом [2] — свою реализацию интерфейса управления умным домом через HomeKit [3]. Меня весьма заинтересовала данная тема и, потратив несколько вечеров на изучение доступного материала, я решил реализовать интеграцию данного продукта с моей системой. В статье я подробно изложу процесс ее установки и настройки, а также поделюсь видео с результатами того, что получилось в итоге.
Для понимания исходных данных и начальной конфигурации умного дома, советую ознакомится с первой статьей [1].
Первой задачей был поиск готовых свободных решений в автоматизации домашнего оборудования с поддержкой HomeKit. Тут я сразу вспомнил несколько статей об open source продукте OpenHab. Почитав документацию и немного погуглив, действительно нашел аддон поддержки протокола HomeKit. На данный момент готовится к выходу вторая версия OpenHab и проводится активное бета тестирование. Последней доступной версией на тот момент была beta4 [11], которую и решил использовать.
Процесс интеграции можно разделить на 4 этапа:
В итоге должна была получится следующая схема:
[12]
У OpenHab 2 есть довольно подробная документация [13], где помимо основных платформ есть и туториал [14] по установке на Raspberry Pi. Не буду копировать сюда все шаги, так как никаких проблем с установкой не возникло.
После установки web-интерфейс OpenHab был доступен в браузере по адресу: http://<адрес_устройства_с_openhab>:8080.
Сразу были доступны:
Пока Basic UI был пустой:
Исходя из документации, для подключения нового устройства к OpenHab нужно было:
В items мы описываем конечные управляемые устройства, к примеру силовой блок NooLite и "учим" OpenHab работать с ним:
itemtype itemname ["labeltext"] [<iconname>] [(group1, group2, ...)] [{bindingconfig}]
где:
Самое интересное здесь — это биндинг. То, каким образом будет осуществляться взаимодействие с управляемым устройством. Немного поискав информацию по работе OpenHab с NooLite, нашел готовый вариант [20]. Но он был написан для работы с NooLite USB адаптерами [21] PC и RX серий. В моей же системе управление силовыми блоками работало через ethernet-шлюз PR1132, поэтому нужно было искать другие варианты работы.
Посмотрев на доступные биндинги, нашел универсальные Http binding [22] и Exec binding [23], с помощью которых можно было реализовать общение с NooLite:
На тот момент я думал, что будет необходима дополнительная логика пре/пост обработки запросов к NooLite, поэтому выбрал второй вариант.
При разработке Telegram-бота, предыдущей реализации интерфейса общения с умным домом, я уже написал простенький класс-обертку над API вызовами к NooLite:
"""
NooLite API wrapper
"""
import requests
from requests.auth import HTTPBasicAuth
from requests.exceptions import ConnectTimeout, ConnectionError
import xml.etree.ElementTree as ET
class NooLiteSens:
"""Класс хранения и обработки информации, полученной с датчиков
Пока как таковой обработки нет
"""
def __init__(self, temperature, humidity, state):
self.temperature = float(temperature.replace(',', '.')) if temperature != '-' else None
self.humidity = int(humidity) if humidity != '-' else None
self.state = state
class NooLiteApi:
"""Базовый враппер для общения с NooLite"""
def __init__(self, login, password, base_api_url, request_timeout=10):
self.login = login
self.password = password
self.base_api_url = base_api_url
self.request_timeout = request_timeout
def get_sens_data(self):
"""Получение и прасинг xml данных с датчиков
:return: список NooLiteSens объектов для каждого датчика
:rtype: list
"""
response = self._send_request('{}/sens.xml'.format(self.base_api_url))
sens_states = {
0: 'Датчик привязан, ожидается обновление информации',
1: 'Датчик не привязан',
2: 'Нет сигнала с датчика',
3: 'Необходимо заменить элемент питания в датчике'
}
response_xml_root = ET.fromstring(response.text)
sens_list = []
for sens_number in range(4):
sens_list.append(NooLiteSens(
response_xml_root.find('snst{}'.format(sens_number)).text,
response_xml_root.find('snsh{}'.format(sens_number)).text,
sens_states.get(int(response_xml_root.find('snt{}'.format(sens_number)).text))
))
return sens_list
def send_command_to_channel(self, data):
"""Отправка запроса к NooLite
Отправляем запрос к NooLite с url параметрами из data
:param data: url параметры
:type data: dict
:return: response
"""
return self._send_request('{}/api.htm'.format(self.base_api_url), params=data)
def _send_request(self, url, **kwargs):
"""Отправка запроса к NooLite и обработка возвращаемого ответа
Отправка запроса к url с параметрами из kwargs
:param url: url для запроса
:type url: str
:return: response от NooLite или исключение
"""
try:
response = requests.get(url, auth=HTTPBasicAuth(self.login, self.password),
timeout=self.request_timeout, **kwargs)
except ConnectTimeout as e:
print(e)
raise NooLiteConnectionTimeout('Connection timeout: {}'.format(self.request_timeout))
except ConnectionError as e:
print(e)
raise NooLiteConnectionError('Connection timeout: {}'.format(self.request_timeout))
if response.status_code != 200:
raise NooLiteBadResponse('Bad response: {}'.format(response))
else:
return response
# Кастомные исключения
NooLiteConnectionTimeout = type('NooLiteConnectionTimeout', (Exception,), {})
NooLiteConnectionError = type('NooLiteConnectionError', (Exception,), {})
NooLiteBadResponse = type('NooLiteBadResponse', (Exception,), {})
NooLiteBadRequestMethod = type('NooLiteBadRequestMethod', (Exception,), {})
Используя его, в несколько строк python кода я набросал NooLite CLI [25], с помощью которого появилась возможность управлять NooLite из командной строки:
"""
NooLite PR1132 command line interface
"""
import os
import json
import logging
import argparse
import yaml
from noolite_api import NooLiteApi
SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__))
# Logging config
logger = logging.getLogger()
formatter = logging.Formatter(
'%(asctime)s - %(filename)s:%(lineno)s - %(levelname)s - %(message)s'
)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
def get_args():
"""Получение аргументов запуска
:return: словарь вида {название: значение} для переданных аргументов.
:rtype: dict
"""
parser = argparse.ArgumentParser()
parser.add_argument('-sns', type=int, help='Получить данные с указанного датчика')
parser.add_argument('-ch', type=int, help='Адрес канала')
parser.add_argument('-cmd', type=int, help='Команда')
parser.add_argument('-br', type=int, help='Абсолютная яркость')
parser.add_argument('-fmt', type=int, help='Формат')
parser.add_argument('-d0', type=int, help='Байт данных 0')
parser.add_argument('-d1', type=int, help='Байт данных 1')
parser.add_argument('-d2', type=int, help='Байт данных 2')
parser.add_argument('-d3', type=int, help='Байт данных 3')
return {key: value for key, value in vars(parser.parse_args()).items()
if value is not None}
if __name__ == '__main__':
# Получаем конфиг из файла
config = yaml.load(open(os.path.join(SCRIPT_PATH, 'conf_cli.yaml')))
# Создаем объект для работы с NooLite
noolite_api = NooLiteApi(
config['noolite']['login'],
config['noolite']['password'],
config['noolite']['api_url']
)
# Получаем аргументы запуска
args = get_args()
logger.debug('Args: {}'.format(args))
# Если есть аргумент sns, то возвращаем информацию с датчиков
if 'sns' in args:
sens_list = noolite_api.get_sens_data()
send_data = sens_list[args['sns']]
print(json.dumps({
'temperature': send_data.temperature,
'humidity': send_data.humidity,
'state': send_data.state,
}))
else:
logger.info('Send command to noolite: {}'.format(args))
print(noolite_api.send_command_to_channel(args))
Аргументы имеют такие же названия, как и в API ethernet-шлюза PR1132 [24], единственное, что добавил — аргумент sns
для получения информации с датчиков.
# Помощь
$ python noolite_cli.py -h
usage: noolite_cli.py [-h] [-sns SNS] [-ch CH] [-cmd CMD] [-br BR] [-fmt FMT]
[-d0 D0] [-d1 D1] [-d2 D2] [-d3 D3]
optional arguments:
-h, --help show this help message and exit
-sns SNS Получить данные с указанного датчика
-ch CH Адрес канала
-cmd CMD Команда
-br BR Абсолютная яркость
-fmt FMT Формат
-d0 D0 Байт данных 0
-d1 D1 Байт данных 1
-d2 D2 Байт данных 2
-d3 D3 Байт данных 3
# Включение силового блока, привязанного к 0 каналу
$ python noolite_cli.py -ch 0 -cmd 2
OK
# Выключение силового блока, привязанного к 0 каналу
$ python noolite_cli.py -ch 0 -cmd 0
OK
# Получение информации с датчика, привязанного к 0 каналу
$ python noolite_cli.py -sns 0
{"state": "Датчик привязан, ожидается обновление информации", "temperature": 21.1, "humidity": 56}
# Посложнее - задать RGB-контроллеру SD111-180 (3 канал) соответствующую яркость
# на каждый из цветовых каналов: d0 - красный, d1 - зеленый, d2 - синий
$ python noolite_cli.py -ch 3 -cmd 6 -fmt 3 -d0 247 -d1 255 -d2 247
Теперь я мог описать все силовые блоки NooLite в items:
# /etc/openhab2/items/noolite.items
Number FFTemperature "Температура [%.1f °C]" <temperature> {exec="<[python noolite_cli.py -sns 0:5000:JSONPATH($.temperature)]"}
Number FFHumidity "Влажность [%d %%]" <temperature> {exec="<[python noolite_cli.py -sns 0:5000:JSONPATH($.humidity)]"}
Switch Heaters1 "Обогреватели" { exec=">[OFF:python noolite_cli.py -ch 0 -cmd 0] >[ON:python noolite_cli.py -ch 0 -cmd 2]"}
Switch Light1 "Освещение" { exec=">[OFF:python noolite_cli.py -ch 2 -cmd 0] >[ON:python noolite_cli.py -ch 2 -cmd 2]"}
Color RGBLight "Светодиодная лента" <slider>
где:
python noolite_cli.py
с параметром -sns 0 и извлекает значения temperature из json ответа. Аналогично для влажностиpython noolite_cli.py -ch 0 -cmd 2
, для выключения (команда "OFF") — python noolite_cli.py -ch 0 -cmd 0
.Управление светодиодной лентой уже не умещалось в одну команду, так как нужна была дополнительная логика для получения значений яркости каждого из RGB каналов. Поэтому обработку изменения состояния я описал в rules:
# /etc/openhab2/rules/noolite.rules
import org.openhab.core.library.types.*
var HSBType hsbValue
var String redValue
var String greenValue
var String blueValue
rule "Set RGB value"
when
Item RGBLight changed
then
val hsbValue = RGBLight.state as HSBType
val brightness = hsbValue.brightness.intValue
val redValue = ((((hsbValue.red.intValue * 255) / 100) * brightness) / 100).toString
val greenValue = ((((hsbValue.green.intValue * 255) / 100) * brightness) / 100).toString
val blueValue = ((((hsbValue.blue.intValue *255) / 100) * brightness) / 100).toString
var String cmd = "python noolite_cli.py -ch 3 -cmd 6 -fmt 3 -d0 " + redValue + " -d1 " + greenValue + " -d2 " + blueValue
executeCommandLine(cmd)
end
Я описал одно правило, которое срабатывало при изменении состояния RGBLight, где получал значения каждого канала (0-255), формировал строку python noolite_cli.py -ch 3 -cmd 6 -fmt 3 -d0 redValue -d1 greenValue -d2 blueValue
и выполнял ее.
Теперь, когда OpenHab "видел" все мои силовые блоки и умел ими управлять, оставалось описать, как нужно их отображать в панели управления OpenHab (Basic UI, Classic UI). Делается это в sitemap:
# /etc/openhab2/sitemap/noolite.sitemap
sitemap noolite label="Дача" {
Frame label="Первый этаж" {
Text item=FFTemperature
Text item=FFHumidity
Switch item=Heaters1
Switch item=Light1
}
Frame label="Второй этаж" {
Colorpicker item=RGBLight icon="colorwheel" label="Светодиодная лента"
}
}
После сохранения этого файла в Basic UI появились все устройства:
Также протестировал работу с умным домом через приложение OpenHab для iOS устройств, где тоже все отлично работало:
Установку HomeKit аддона [29] для OpenHab я произвел в пару кликов через Paper UI (интерфейс администрирования):
Для его корректной работы, следуя документации, для каждого элемента items прописал тип (Lighting, Switchable, CurrentTemperature, CurrentHumidity, Thermostat). После этого файл noolite.items имел следующий вид:
Number FFTemperature "Температура [%.1f °C]" <temperature> [ "CurrentTemperature" ] {exec="<[python noolite_cli.py -sns 0:5000:JSONPATH($.temperature)]"}
Number FFHumidity "Влажность [%d %%]" <temperature> [ "CurrentHumidity" ] {exec="<[python noolite_cli.py -sns 0:5000:JSONPATH($.humidity)]"}
Switch Heaters1 "Обогреватели" [ "Switchable" ] { exec=">[OFF:python noolite_cli.py -ch 0 -cmd 0] >[ON:python noolite_cli.py -ch 0 -cmd 2]"}
Switch Light1 "Освещение" [ "Switchable" ] { exec=">[OFF:python noolite_cli.py -ch 2 -cmd 0] >[ON:python noolite_cli.py -ch 2 -cmd 2]"}
Color RGBLight "Светодиодная лента" <slider> [ "Lighting" ]
Затем в настройках аддона прописал локальный адрес устройства c OpenHab (в моем случае Raspberry Pi) и посмотрел pin-код сопряжения:
После этого с настройками OpenHab было закончено, и я приступил к конфигурированию умного дома на iphone в приложении "Дом".
[33]
Первый запуск приложения "Дом"
[34]
Нажал "Добавить аксессуар"
[35]
iPhone увидел в локальной сети устройство с поддерждок HomeKit
[37]
При добавлении указал pin-код из настроек HomeKit аддона
iPhone увидел все мои устройства, описанные в items. Далее я переименовал некоторые устройства, создал "комнаты" (первый этаж, второй этаж, улица) и раскидал все устройства по ним.
Здесь я хочу пояснить один момент. На скриншоте выше виден элемент "Температура на улице" (первый элемент с показателем 2 градуса), находящийся в комнате "Улица". Этот элемент реализован с использованием биндинга YahooWeather Binding — по сути просто прогноз погоды от yahoo для конкретного места.
К NooLite он не относится, поэтому я не затронул подробности его установки и настройки. Сделать это можно опять же через Paper UI, все подробно изложено в документации [40].
В моей локальной сети находился Apple TV, который без дополнительных настроек сам определился как "Домашний центр". Как я позже выяснил, домашний центр необходим для удаленного доступа к умному дому и настройки автоматизации (действия по расписанию, действия на основе вашей геопозиции и т.д.). В качестве домашнего центра может выступать Apple TV 3 или 4 поколения (в 3 поколении работает только удаленный доступ, для автоматизации нужно 4 поколение) или iPad с iOS 10. Это устройство должно быть постоянно включено и находится в локальной сети умного дома.
Приятно порадовало то, что никаких дополнительных настроек с Apple TV я не делал. Все, что нужно — это войти в iCloud под своей учетной записью.
Нагляднее всего процесс работы можно показать с помощью видео. Ниже приведу несколько примеров работы приложения "Дом" и голосового управления через Siri:
В щитке 2 крайних правых места нанимают контакторы, управляемые силовыми блоками NooLite серии SL. Через них подключены линии обогревателей на первом и втором этаже дома. На видео слышно, как они щелкают при включении/выключении. К сожалению нет более наглядной индикации их работы.
В начале следующего видео я отключаюсь от дачной Wi-Fi сети и вся дальнейшая работа с умным домом происходит через мобильный 3G интернет.
Интеграция с HomeKit позволила добавить к умному дому удобный интерфейс управления с iOS устройств через приложение "Дом", а так же расширила его функционал:
Подробный обзор самого приложения "Дом" и его применения для домашней автоматизации уже выходит за рамки данной темы и думаю, что заслуживает отдельной статьи. Но для меня самым интересным является геолокация, на основе которой можно реализовать интересные сценарии автоматизации. Например когда все пользователи дома уходят из него, то можно выключить везде свет. Если пользователи удалились еще дальше (уехали в город), то выключаем некоторые потребители, например розетки и электричество в подвале (у меня там расположена насосная станция).
Работа над данной статьей позволила взглянуть на систему управления загородным домом со стороны и отметить те места, которые в ближайшем будущем буду дорабатывать:
Ссылки:
Автор: AlekseevAV
Источник [43]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/200639
Ссылки в тексте:
[1] первой статье: https://habrahabr.ru/post/312328/
[2] Дом: http://www.apple.com/ru/ios/home/
[3] HomeKit: https://developer.apple.com/homekit/
[4] Введение: #vvedenie
[5] Установка и настройка OpenHab: #OpenHab_install
[6] Подключение NooLite к OpenHab: #NooLite_OpenHab
[7] Установка и настройка HomeKit для OpenHab: #HomeKit_OpenHab
[8] Настройка приложения Дом: #apple_Home_app
[9] Результат: #results
[10] Заключение: #zakluchenie
[11] beta4: https://community.openhab.org/t/openhab-2-0-beta4-has-been-released/14110
[12] Image: https://habrastorage.org/files/e8d/e27/5ae/e8de275ae30a44bbb092f4c1389206db.png
[13] документация: http://docs.openhab.org/index.html
[14] туториал: http://docs.openhab.org/installation/rasppi.html
[15] Image: https://habrastorage.org/files/35e/ed5/051/35eed5051764495da69060f46a63a8a6.png
[16] Image: https://habrastorage.org/files/01b/695/aab/01b695aab85b4a1baab0052a5ad391b6.png
[17] items: https://github.com/openhab/openhab/wiki/Explanation-of-items
[18] sitemap: https://github.com/openhab/openhab/wiki/Explanation-of-Sitemaps
[19] rules: https://github.com/openhab/openhab/wiki/Rules
[20] вариант: https://github.com/Pshatsillo/OpenHABNoolite
[21] NooLite USB адаптерами: http://www.noo.com.by/adapter-noolite-pc.html
[22] Http binding: https://github.com/openhab/openhab/wiki/Http-Binding
[23] Exec binding: https://github.com/openhab/openhab/wiki/Exec-Binding
[24] API в PR1132: http://www.noo.com.by/assets/files/PDF/PR1132.pdf
[25] NooLite CLI: https://github.com/AlekseevAV/NooLite-PR1132-CLI
[26] Image: https://habrastorage.org/files/a5f/3b9/d38/a5f3b9d38e4545fdaf5335251adc4eef.png
[27] Image: https://habrastorage.org/files/d19/8a9/4c4/d198a94c458e4e6688236cb1804566a3.PNG
[28] Image: https://habrastorage.org/files/b5c/047/209/b5c04720978246eaac40c543c64c66ef.PNG
[29] HomeKit аддона: http://docs.openhab.org/addons/io/homekit/readme.html
[30] Image: https://habrastorage.org/files/4fb/8ce/d47/4fb8ced4735d4f2a895013623b2aff60.png
[31] Image: https://habrastorage.org/files/b13/a21/6e7/b13a216e7ce54268b5c1cf1f2f69317a.png
[32] Image: https://habrastorage.org/files/76e/f92/2d3/76ef922d3c924a8c9c299391c7576b47.png
[33] Image: https://habrastorage.org/files/d8d/e9f/f74/d8de9ff741fc4884914e8e8d6c54fecb.PNG
[34] Image: https://habrastorage.org/files/cbc/fc6/db6/cbcfc6db6b2a4a43bc74502340b5f7ee.PNG
[35] Image: https://habrastorage.org/files/d97/3ab/751/d973ab751c2347fd862f7bec0f81d88a.PNG
[36] Image: https://habrastorage.org/files/7c1/d7e/9c1/7c1d7e9c19a54d21b3fc31ec977a4d48.PNG
[37] Image: https://habrastorage.org/files/d8e/b9d/272/d8eb9d2720bc445f970ffae9be29ce60.PNG
[38] Image: https://habrastorage.org/files/3e5/802/4c5/3e58024c5f90449db228f9cac36976ef.PNG
[39] Image: https://habrastorage.org/files/902/7b8/a6d/9027b8a6d0674effaebbac16d2970e46.png
[40] документации: http://docs.openhab.org/addons/bindings/yahooweather/readme.html
[41] Система NooLite: http://www.noo.com.by/sistema-noolite.html
[42] OpenHab 2 документация: http://docs.openhab.org
[43] Источник: https://habrahabr.ru/post/312668/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.