Отправка данных из Home Assistant на сервис narodmon.ru

08 мая 2020, 17:20

Вступление

Прочитав статью Обмен данными между NarodMon.ru и Умным домом, я вспомнил, что когда-то тоже активно участвовал в проекте и отправлял данные на этот сервис, почти с самого его основания. Сначала с помощью устройства, заказанного у одного из участников проекта, потом с помощью ESP8266 и прошивки WiFi-IoT, а потом я это дело забросил. В основном из-за неудобств в его обслуживании, необходимости подачи питания, нагрева датчиков от ESP8266 (и безуспешных попыток их термоизоляции), периодических проблем с отвалом wifi и так далее.

Все эти проблемы отпали после того, как я вынес на улицу один из zigbee датчиков xiaomi, датчик проработал в течение года, исправно показывая температуру и влажность за бортом. Единственный минус - из home assistant этими данными нельзя было делиться с сообществом народного мониторинга. 

После статьи Дмитрия, я решил найти способ восполнить этот пробел, тем более что готовых решений в интернете нет. Надеюсь, мой труд не пропадет даром и многие, у кого в home assistant есть данные, которыми они готовы поделиться, воспользуются этим.

Поиск решения

Сервис narodmon поддерживает прием показаний несколькими способами: TCP/UDP, HTTP GET/POST, а также MQTT. 

TCP/UDP является предпочтительным вариантом, по мнению авторов сервиса, но для пользователя home assistant это означает необходимость написания кастомных скриптов.

HTTP рекомендуется использовать только в случае, если доступ в Интернет осуществляется через прокси-сервер, т.к. возможны проблемы с кеширующими прокси и выше риск перехвата / подмены данных, а также увеличивается время обработки данных в "часы пик" из-за большого числа посетителей сайта и мобильных клиентов. В home assistant можно реализовать с помощью RESTful command интеграции, но все же хочется придерживаться рекомендаций сервиса и не использовать HTTP.

MQTT - надеялся, что получится легко и просто, но оказалось что home assistant может работать только с одним MQTT брокером и для реализации задачи необходимо костылить связку из двух MQTT брокеров, не захотел с этим связываться.

Реализация

В результате, единственным вариантом для меня остался вариант TCP/UDP. В описании API narodmon есть несколько примеров скриптов для передачи показаний по TCP/UDP и один из них - на python. Соответственно, первым делом хотел использовать встроенную в home assistant интеграцию python scripts, но к сожалению, оказалось что возможности там урезанные - нет поддержки импорта библиотек, а без этого никак. Гугление привело меня к AppDaemon, на тот момент - абсолютно непонятная для меня вещь (всегда удивлялся, когда в чате обсуждалось его удобство и возможность сделать все, что угодно). Взяв пример от narodmon, документацию appdaemon и статьи Что это такое AppDaemon? и AppDaemon. Часть 2 за несколько самоизоляционных вечеров сделал то, что хотел.

Настройка

Для тех кто захочет воспользоваться моим скриптом, первым делом необходимо установить AppDaemon. Самый простой способ для пользователей hassio - установка аддона AppDaemon 4 из состава Home Assistant Community Add-ons, для пользователей чистого home assistant могу лишь предложить ссылку на статью Установка AppDaemon.

В AppDaemon описание всех пользовательских скриптов и их параметров выполняется в файле apps.yaml, либо, для удобства можно создать отдельный файл с расширением yaml и поместить его вместе с файлом скрипта в папку внутри конфигурационного каталога, AppDaemon сам все найдет и запустит. Кстати, AppDaemon отслеживает изменения файлов и как только вы измените любой существующий или появится новый файл скрипта *.py и его конфигурация правильно описана в *.yaml, скрипт автоматически запустится / перезапустится. Это очень удобно в процессе отладки.

Итак, создаем файл с конфигурацией / вставляем ее в дефолтный apps.yaml:

#apps\ad_narodmon_sender\config.yaml
narodmon_sender:
  module: narodmon_sender
  class: narodmon_sender
  narodmon_device_mac: AABBCCDDEEFF
  narodmon_device_name: Aqara_WSDCGQ11LM
  hass_coordinates_entity: zone.home
  hass_sensor_entities: sensor.outside_temperature,sensor.outside_humidity,sensor.outside_pressure

Опишу параметры. Первые два обязательны и нужны для того, чтобы скрипт в принципе запустился:

module: - имя файла скрипта (путь к файлу и расширение .py здесь не указывается).

class: - название класса, должно совпадать с названием класса в скрипте.

Дальше мои "пользовательские" параметры, которые передаются в переменные внутри скрипта:

narodmon_device_mac: MAC-адрес, идентифицирующий ваше устройство на narodmon.ru, 12-18 символов A-Z и 0-9 без разделителей или разделенных '-' или ':' (обязательный параметр).

narodmon_device_name: Имя вашего устройства, оно будет видно тем, кто захочет посмотреть ваше устройство на сайте (не обязательный параметр).

hass_coordinates_entity: зона home assistant для получения координат, нужно для автоматического размещения устройства на карте (не обязательный параметр).

hass_sensor_entities: список сенсоров из home assistant, данные которых будем передавать, разделенный запятыми, без пробелов (обязательный параметр).

Теперь сам скрипт. В нем никаких изменений делать не надо. Но если найдутся специалисты в python, буду рад услышать комментарии, для меня это первый опыт с питоном.

Если кратко, алгоритм следующий:

  1. Получаем исходные данные из параметров, указанных в конфигурации.
  2. Из списка entity отфильтровываем только домен сенсоров и проверяем, существуют ли таковой в home assistant.
  3. Получаем имена и тип сенсоров из атрибутов home assistant (friendly name и device_class).
  4. На основе типов сенсоров формируем идентификаторы сенсоров (чтобы narodmon автоматически определял тип).
  5. Если сенсоров одного типа несколько, нумеруем их по порядку.
  6. Если атрибут device_class отсутствует, идентификатор будет просто SENSOR1, SENSOR2 и т.д. В таком случае, на narodmon в настройках датчика вручную необходимо будет задать его тип. Либо просто добавьте device_class в разделе кастомизации home assistant.
  7. Каждые 5 минут формируем данные для отправки (отфильтровывая недоступные в данный момент сенсоры) и отправляем их на сервер narodmon.ru, записывая в лог все, что отправляем.
Постарался предусмотреть все необходимые проверки, чтобы не отправлять на сервер ошибочные данные или не отправлять вовсе ничего, если отсутствуют обязательные параметры.
После отладки можно закомментировать строку 
"self.log('Data for send to narodmon.ru:\n' str(data))", чтобы не мусорить в лог отправляемыми данными. 

Весь скрипт усыпан моими комментариями, думаю будет полезно новичкам.

import appdaemon.plugins.hass.hassapi as hass
import socket
import datetime
import collections

class narodmon_sender(hass.Hass):
    #метод запускаемый однократно при старте программы
    def initialize(self):
        # объявляем переменные:
        # список сенсоров для отправки
        self.sensors = []
        # словарь имен для сенсоров (берем из friendly_name)
        self.sensors_name = {}
        # словарь типов сенсоров (берем из device_class, если есть)
        self.sensors_type = {}
        # форматированные данные для отправки
        self.device_data = None
        # словарь замены типов для автоопределения типа сенсора на narodmon.ru, 
        # исходные данные берутся из параметра device_class сенсора, при отсутстви датчики будут именованы как SENSOR#, тип нужно вручную определить на сайте.
        replace = {
            'temperature': 'TEMP',
            'humidity': 'RH',
            'pressure': 'PRESS',
            'battery': 'BATCHARGE',
            'power': 'W',
            'illuminance': 'LIGHT',
            'signal_strength': 'RSSI',
             None: 'SENSOR'
            }
        # проверяем, есть ли переменная с MAC-адресом в параметрах скрипта
        if 'narodmon_device_mac' in self.args:
            if self.args['narodmon_device_mac'] != None:
                # начинаем формировать данные для отправки
                self.device_data = '#'   self.args['narodmon_device_mac']
                # проверяем наличие названия устройства в параметрах скрипта, добавляем к MAC-адресу, если есть
                if 'narodmon_device_name' in self.args:
                    if self.args['narodmon_device_name'] != None:
                        self.device_data  = '#'   self.args['narodmon_device_name']
                # проверяем наличие зоны в параметрах скрипта для определения координат
                if 'hass_coordinates_entity' in self.args:
                    if self.entity_exists(self.args['hass_coordinates_entity']):
                        lat = self.get_state(self.args['hass_coordinates_entity'], 'latitude')
                        lng = self.get_state(self.args['hass_coordinates_entity'], 'longitude')
                        if lat != None and lng != None:
                            self.device_data  = '\n#LAT#'   str(lat)   '\n#LNG#'   str(lng)
            else:
                exit('Please, define narodmon_device_mac value in /config/appdaemon/apps/narodmon_sender/config.yaml')
        else:
            exit('Please, specify narodmon_device_mac variable in /config/appdaemon/apps/narodmon_sender/config.yaml')
        # проверка наличия перечня сенсоров в парамерах скрипта
        if 'hass_sensor_entities' in self.args:
            for entity in self.split_device_list(self.args['hass_sensor_entities']):
                # проверка существования объекта в home assistant
                if self.entity_exists(entity):
                    domain, sensor_id = self.split_entity(entity)
                    # отфильтровываем все кроме сенсоров
                    if domain == 'sensor':
                        # заполняем список сенсоров для отправки
                        self.sensors.append(sensor_id)
                        # заполняем словари имен и типов
                        self.sensors_name[sensor_id] = self.get_state(entity, 'friendly_name')
                        self.sensors_type[sensor_id] = self.get_state(entity, 'device_class')
            # на основе словаря типов переименовываем и нумеруем по порядку повторяющиеся
            for sensor_id in self.sensors_type:
                if self.sensors_type[sensor_id] in replace:
                    self.sensors_type[sensor_id] = replace[self.sensors_type[sensor_id]]
            count = collections.Counter(self.sensors_type.values())
            for type in count:
                if count[type] > 1:
                    num = count[type]
                    sel = 1
                    for sensor_id in self.sensors_type:
                        if self.sensors_type[sensor_id] == type:
                            self.sensors_type[sensor_id] = type   str(range(num   1)[sel])
                            sel = sel   1
        else:
            exit('Please, specify hass_sensor_entities variable in /config/appdaemon/apps/narodmon_sender/config.yaml')
        # вызвываем метод отправки данных каждые 5 минут, начиная с текушего времени 
        self.run_every(self.send_data, datetime.datetime.now()   datetime.timedelta(seconds=2), 300)
    # метод для отправки данных
    def send_data(self, kwargs):
        sensors_data = '\n'
        # проверяем, есть ли сформированные данные об устройстве
        if self.device_data != None:
            for sensor_id in self.sensors:
                # отбрасываем недоступные датчики
                if self.get_state('sensor.'   sensor_id) != 'unavailable':
                    # формируем строку с данными всех рабочих сенсоров
                    sensors_data  = '#'   self.sensors_type[sensor_id]   '#'   self.get_state('sensor.'   sensor_id)   '#'   self.sensors_name[sensor_id]   '\n'
            # собираем пакет данных для отправки: информация об устройстве   данные сенсоров   символ окончания пакета данных
            data = self.device_data   sensors_data   '##'
            # вывод в лог информации которая будет отправлена
            self.log('Data for send to narodmon.ru:\n'   str(data))
            # создаем сокет для подключения к narodmon.ru
            sock = socket.socket()
            try:
                # пробуем подключиться
                sock.connect(('narodmon.ru', 8213))
                # пишем в сокет значения датчиков
                sock.send(data.encode('utf-8'))
                # читаем ответ сервера
                reply = str(sock.recv(1024))
                sock.close()
                self.log('Server reply: '   reply.strip("bn\'\\"))
            except socket.error as err:
                self.error('Got error when connecting to narodmon.ru: '   str(err))
        else:
            exit('No device data for send to narodmon.ru')

Файлы также можно скачать с github, там я готовлю его для включения в репозиторий HACS.

Но в принципе, если идея будет востребована, готов написать кастомный модуль для home assistant, чтобы не связываться с AppDaemon тем, кому он не нужен.

Жду ваших комментариев, замечаний, предложений!


Все новости мира умных домов - t.me/SprutAI_News или Instagram
Остались вопросы? Мы в Telegram - @Soprut

Хочешь умный дом но нет времени разбираться?
Посмотри примеры работ и выбери себе интегратора.
  1. Дмитрий Батюшин (ReD)
    Дмитрий Батюшин (ReD) 20 дней назад

    Отличное продолжение моей статьи. Приятно было что упомянул. Лайк!

  2. (sinoptic)
    (sinoptic) 20 дней назад
    Если оформить в  HACS , будет законченное, правильное решение.

  3. (meta11ist87)
    (meta11ist87) 19 дней назад

    Без appdaemon было бы круто. Я чёт всё смотрю на него и очкую подходить к нему)) По сути и нет потребности. Поэтому ставить аддон только из-за одного скрипта не хочется.

    Модуль для ха было бы круто получить.


    Расскажите, как у вас расположен датчик сяоми чтоб он передавал достоверную инфу? Вы в какой полосе живёте, насколько суровые у вас зимы?

    • Алексей Мокренок (Lefey)

      Датчик по возможности нужно размещать в тени, в проветриваемом месте. У меня он находится под козырьком балкона. В идеале разместить его в специальный небольшой корпус, по типу метеорологической будки (другое название Stevenson screen) , я планирую распечатать такую модель: https://www.thingiverse.com/th... 

      Живу в Приморском крае, зимы у нас до -20 в среднем. Но эти датчики (xiaomi/aqara) и в более суровые минуса работают, помню в чате люди отписывались о работе в -40

    • Алексей Мокренок (Lefey)
      Алексей Мокренок (Lefey) отредактировано день назад

      Распечатал и собрал корпус для датчика, как и ожидалось разница в солнечный день довольно существенна, по температуре пиковая разность около 4 градусов, по влажности около 10%.

      Температура:

      1000x_image.png?1590545435

      Влажность:

      1000x_image.png?1590545407

      Красный - график открытого датчика, синий - датчик внутри защитного корпуса:

      1000x_image.jpg?1590547169
  4. Эдуард Захаревский (drdic)

    Спасибо! Всё работает. Прокинул свою метеостанцию: WeMos D1 mini pro+BME280+BH1750 --> HA --> narodmon.ru. Месяц мучался, пытался через MQTT сделать. По вашему методу все быстро, просто и никаких бубнов...

К списку статей

Устройства в материале

ESP8266

Производитель: Espressif Systems

Тематические чаты

Похожие статьи

17 декабря 2019, 17:49
Универсальный привод для автоматического удаленного открытия окон с простой интеграцией в умные дома.
15 ноября 2018, 09:42
Способы автоматизации механических ворот
24 августа 2018, 12:18
Пошаговая установка HomeAssistant
30 августа 2019, 07:28
Несколько нетривиальных способов использования датчика открытия окон/дверей от Xiaomi/Aqara.
20 октября 2018, 22:57
Теоретические основы протокола MQTT и описание того, как он работает и для чего используется
27 августа 2018, 10:14
Интегрируем ХА в HomeKit
03 октября 2018, 22:03
Как собрать и настроить Hyperion Ambilight - адаптивную подсветку ТВ.
01 ноября 2018, 09:27
Настройка Deconz USB стика ConBee от Dresden Elektronik в Hass.io и некоторые особенности эксплуатации
18 февраля 2020, 17:06
В данной статье описывается сборка слаботочного эл. щита на базе Wiren Board 6, а так же силового щита для небольшой квартиры.
29 октября 2019, 07:59
Умный домофон на базе nodeMCU с прошивкой ESPHome.