«Привіт, Siri. Включи обігрівачі» — Інтеграція розумного будинку на базі NooLite з Apple HomeKit

image
У своїй першої статті я описав передісторію появи системи віддаленого керування опаленням в заміському будинку через Telegram-бота, яким я і моя сім'я користувалися довгий час.
З виходом iOS 10, Apple представила користувачам додаток Будинок — свою реалізацію інтерфейсу управління розумним будинком через HomeKit. Мене дуже зацікавила ця тема і, витративши кілька вечорів на вивчення доступного матеріалу, я вирішив реалізувати інтеграцію даного продукту з моєю системою. У статті я детально викладу процес встановлення та налаштування, а також поділюся відео з результатами того, що вийшло в результаті.
Зміст
  1. Введення
  2. Установка і настройка OpenHab
  3. Підключення NooLite до OpenHab
  4. Установка і настройка HomeKit для OpenHab
  5. Налаштування програми Будинок
  6. Результат
  7. Висновок

Введення
Для розуміння вихідних даних та початкової конфігурації розумного будинку, раджу ознайомиться з першої статті.
першим завданням був пошук готових вільних рішень в автоматизації домашнього обладнання з підтримкою HomeKit. Тут я відразу згадав кілька статей про open source продукті OpenHab. Почитавши документацію і трохи погугливши, дійсно знайшов аддон підтримки протоколу HomeKit. На даний момент готується до виходу друга версія OpenHab і проводиться активне бета тестування. Останньою доступною версією на той момент була beta4, яку і вирішив використовувати.
Процес інтеграції можна розділити на 4 етапи:
  1. Установка і настройка OpenHab
  2. Підключення NooLite до OpenHab
  3. Установка і настройка HomeKit для OpenHab
  4. Налаштування програми "Дім"
У підсумку повинна була вийде наступна схема:
image



Установка і настройка OpenHab
У OpenHab 2 є досить докладна документація, де крім основних платформ є і туторіал установці на Raspberry Pi. Не буду копіювати сюди всі кроки, так як ніяких проблем з установкою не виникло.
Після встановлення web-інтерфейс OpenHab був доступний у браузері за адресою: <адрес_устройства_с_openhab>:8080.
image
Одразу були доступні:
  1. Basic UI, Classic UI — панелі керування пристроями, підключеними до OpenHab
  2. Rest API — власне rest API
  3. Paper UI — інтерфейс администирования OpenHab, через який можна налаштувати
Поки Basic UI був порожній:
image



Підключення NooLite до OpenHab
Виходячи з документації, для підключення нового пристрою до OpenHab потрібно було:
  1. Додати items
  2. Додати sitemap, щоб воно функціонувало в OpenHab панелі управління розумним будинком (Basic UI, Classic UI). Даний крок можна пропустити, до роботи з HomeKit він не відноситься, але з його допомогою можна перевірити, що OpenHab бачить NooLite і правильно з ним працює.
  3. Додати правила rules, якщо потрібна автоматизація або додаткова логіка обробки подій

Items

items ми описуємо кінцеві керовані пристрої, наприклад силовий блок NooLite і "вчимо" OpenHab працювати з ним:
itemtype ім'я елемента ["labeltext"] [<iconname>] [(group1, group2, ...)] [{bindingconfig}]

де:
  • itemtype — тип пристрою (Switch, Dimmer і т. д.)
  • ім'я елемента — ім'я пристрою
  • labeltext — текстове відображення. Наприклад, для датчика: "Температура [%.1f °C]", для блоку просто "Обігрівачі"
  • iconname — відображається іконка
  • group1 — групи
  • bindingconfig — те, як управляти цим пристроєм
найцікавіше тут — це биндинг. Те, яким чином буде здійснюватися взаємодія з керованим пристроєм. Трохи пошукавши інформацію по роботі OpenHab з NooLite, знайшов готовий варіант. Але він був написаний для роботи з NooLite USB адаптерами PC і RX серій. У моїй же системі управління силовими блоками працювало через ethernet-шлюз PR1132, тому потрібно було шукати інші варіанти роботи.
Подивившись на доступні биндинги, знайшов універсальні Http binding і Exec binding, за допомогою яких можна було реалізувати спілкування з NooLite:
  1. Http binding дозволяє виконувати мережевий запит при виникненні якої-небудь події (включення/вимикання, отримання інформації з датчика). Використовуючи API в PR1132, можна було реалізувати пряме спілкування OpenHab з NooLite.
  2. Exec binding — на подію виконується яка-небудь команда.
На той момент я думав, що буде необхідна додаткова логіка пре/пост обробки запитів до NooLite, тому вибрав другий варіант.

NooLite CLI

При розробці Telegram-бота, попередньої реалізації інтерфейсу спілкування з розумним будинком, я вже написав простенький клас-обгортку над API викликами до NooLite:
noolite_api.py
"""
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 параметри
:data type: 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, за допомогою якого з'явилася можливість управляти NooLite з командного рядка:
noolite_cli.py
"""
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, єдине, що додав — аргумент
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>

де:
  • FFTemperature, FFHumidity — температура і вологість датчика першого поверху. OpenHab для отримання інформації кожні 5 секунд виконує
    python noolite_cli.py
    з параметром -sns 0 і отримує значення temperature з json відповіді. Аналогічно для вологості
  • Heaters1 — лінія обігрівачів першого поверху. Для включення (команда "ON") OpenHab повинен виконати
    python noolite_cli.py -ch 0 -cmd 2
    , для виключення (команда "OFF") —
    python noolite_cli.py -ch 0 -cmd 0
    .
  • Light1 — вуличний прожектор, команди аналогічні обігрівачів, тільки на другому каналі -ch 2
  • RGBLight — світлодіодна стрічка.

Rules

Управління світлодіодною стрічкою вже не вміщувалося в одну команду, так як потрібна була додаткова логіка для отримання значень яскравості кожного з 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
і виконував її.

Sitemap

Тепер, коли 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 з'явилися всі пристрої:
image


Також протестував роботу з розумним будинком через додаток OpenHab для iOS пристроїв, де теж все відмінно працювало:
image image



Установка і настройка HomeKit для OpenHab
Установку HomeKit аддона для OpenHab я зробив в пару кліків через Paper UI (інтерфейс адміністрування):
image


Для його коректної роботи, дотримуючись документації, для кожного елемента 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-код сполучення:
image

image

Після цього з налаштуваннями OpenHab було закінчено, і я приступив до конфігурування розумного будинку на iphone в додатку "Дім".



Настройка програми «Дім»
image
Перший запуск програми "Дім"
image
Натиснув "Додати аксесуар"
image
iPhone побачив в локальній мережі пристрій з поддерждок HomeKit
image
Ввести код вручну
image
При додаванні вказав pin-код з налаштувань HomeKit аддона
image
Всі пристрої
iPhone побачив всі мої пристрої, описані в items. Далі я перейменував деякі пристрої, створив "кімнати" (перший поверх, другий поверх, вулиця) і розкидав всі пристрої з ним.
Тут я хочу пояснити один момент. На скріншоті вище видно елемент "Температура на вулиці" (перший елемент з показником 2 градуси), що знаходиться в кімнаті "Вулиця". Цей елемент реалізований з використанням биндинга YahooWeather Binding — по суті просто прогноз погоди від yahoo для конкретного місця.
image


До NooLite він не відноситься, тому я не торкнувся подробиці його установки і налаштування. Зробити це можна знову ж таки через Paper UI, все докладно викладено в документації.

Віддалений доступ і автоматизація

У моїй локальній мережі перебував Apple TV, який без додаткових налаштувань сам визначився як "Домашній центр". Як я пізніше з'ясував, домашній центр необхідний для віддаленого доступу до розумному будинку і налаштування автоматизації (дії за розкладом, дії на основі вашої геопозиции тощо). В якості домашнього центру може виступати Apple TV 3 або 4 покоління (в 3 поколінні працює лише віддалений доступ, для автоматизації потрібно 4 покоління) або iPad з iOS 10. Це пристрій повинен бути постійно увімкнений і знаходиться в локальній мережі розумного будинку.
Приємно порадувало те, що ніяких додаткових налаштувань з Apple TV я не робив. Все, що потрібно, це увійти в iCloud під своїм обліковим записом.



Результат
найнаочніше процес роботи можна показати за допомогою відео. Нижче наведу кілька прикладів роботи програми "Дім" і голосового управління через Siri:



В щитку 2 крайніх правих місця наймають контактори, керовані силовими блоками NooLite серії SL. Через них підключені лінії обігрівачів на першому і другому поверсі будинку. На відео чути, як вони клацають при включенні/виключенні. На жаль, немає більш наочною індикації їх роботи.
На початку наступного відео я відключаюся від дачної Wi-Fi мережі і вся подальша робота з розумним будинком відбувається через мобільний 3G інтернет.






Висновок
Інтеграція з HomeKit дозволила додати до розумного дому зручний інтерфейс управління з iOS пристроїв через додаток "Дім", а так само розширила його функціонал:
  • повноцінне віддалене керування всіма пристроями вдома, замість Telegram-бота
  • голосове управління
  • автоматизацію на основі: часу доби, геолокаціі, показань датчиків (наприклад, виявлення руху)
  • зручне додавання користувачів до управління розумним будинком через запрошення (потрібна наявність iOS пристрої та облікового запису Apple) з розмежуванням прав доступу
Докладний огляд самого додатка "Дім" і його застосування для домашньої автоматизації вже виходить за рамки даної теми і думаю, що заслуговує окремої статті. Але для мене найбільш цікавим є геолокація, на основі якої можна реалізувати цікаві сценарії автоматизації. Наприклад, коли всі користувачі дому йдуть з нього, то можна вимкнути скрізь світло. Якщо користувачі пішли ще далі (поїхали в місто), то вимикаємо деякі споживачі, наприклад розетки і електрику в підвалі (у мене там розташована насосна станція).
Робота над даною статтею дозволила поглянути на систему управління заміським будинком з боку і відзначити ті місця, які в найближчому майбутньому буду допрацьовувати:
  • для спрощення системи замість використання ethernet-шлюзу PR1132 і управління NooLite блоками через HTTP API можна використовувати USB-адаптери RX і PC серій і безпосередньо віддавати команди
  • наскільки мені відомо, найближчим часом NooLite планує випустити новий USB-приймально-передавальний адаптер і силові блоки із зворотним зв'язком. Буде дуже цікаво спробувати їх у справі.
Посилання:
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.