Принудительное обновление данных сенсоров из Home Assistant в InfluxDB для красивых графиков в Grafana

09 сентября 2021, 13:32

В этой статье я опишу результат долгих изысканий и решения довольно нетривиальной задачи, которая в Node RED решается за три минуты, а в Home Assistant на её решение ушло почти 6 часов поисков в интернете и переделывания найденных решений.

Исходное состояние системы:

  • есть котел, который управляется реле Sonoff через сухой контакт;
  • к этому реле подключен датчик температуры, который измеряет температуру теплоносителя;
  • в доме находится еще одно реле Sonoff с подключенным датчиком температуры;
  • есть показания уличной температуры, которые берутся из стандартного сенсора Weather, который штатно есть в HA.

Задача:

  • Отобразить показания всех датчиков температуры и состояние котла (включен или выключен) на графиках, с длительной историей и возможностью масштабирования в необходимых пределах.

Казалось бы, что может быть проще? Однако не так все просто, как думалось изначально.

Sonoff подключены через Sonoff LAN и интеграцию от уважаемого AlexxIT. Все отлично интегрируется, с оговорками на косяки родных прошивок Sonoff и невозможности локально отдавать данные датчиков, которые приходится подтягивать из облака.

Данные обновляются с периодичностью в 10 секунд, так как изменения температуры теплоносителя довольно стремительны. А вот показания температуры в помещении изменяются довольно редко. И выяснилась одна проблема. Если данные состояния датчика не менялись - система считает, что ничего не происходит, и не записывает точки в базы. Ни в родные, ни во внешние. И мы видим довольно грустную картину:

1600x_image.png?1631191288

К тому же хочется, чтобы на одном графике были видны совместные состояния всех датчиков и котла. Для этого придется поставить InfluxDB и Grafana. Как их настраивать я тут описывать не буду, но там все просто и понятно из стандартных мануалов.

После развёртывания Grafana и просмотра базы выяснилось, что такая же ситуация с отдачей данных в InfluxDB и, соответственно, в Grafana графики тоже "рваные". Правда можно соединить нулевые сегменты между собой и получить переходную линию, но если, например, вы строите график за час, а в течение этого часа показания датчика не менялись - его просто не будет видно на графике и вы не узнаете, в каком состоянии он находился.

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

Путём очень долгих изучений, тестов и различных изменений конфигурации, был найден вариант, который делает то, что нужно в требуемых рамках.

Для начала нам потребуются искусственно созданные сенсоры на базе темплейтов. Для этого в файл configuration.yaml необходимо добавить:

template:
  - sensor:
      - name: kotel_temp
        unit_of_measurement: C
        state: 0
        device_class: "temperature"
        attributes:
          temp: >
            {{ state_attr('sensor.sonoff_kotel_temperature', 'temperature') }}
  - sensor:
      - name: floor_temp
        unit_of_measurement: C
        state: 0
        device_class: "temperature"
        attributes:
          temp: >
            {{ state_attr('sensor.sonoff_floor_temperature', 'temperature') }}
  - sensor:
      - name: street_temp
        unit_of_measurement: C
        state: 0
        device_class: "temperature"
        attributes:
          temp: >
            {{ state_attr('weather.home', 'temperature') }}
  - sensor:
      - name: street_temperature
        unit_of_measurement: C
        state: >
          {{ state_attr('weather.home', 'temperature') }}
        device_class: "temperature"
  - sensor:
      - name: boiler_state
        state: 0
        attributes:
          on_state:  >
            {{ 0 if is_state('switch.sonoff_kotel', 'off') else 50 }}

Где sensor.sonoff_kotel_temperature и другие датчики - это реальные сенсоры, которые уже есть в системе. В моем случае - добавленные интеграцией Sonoff LAN. Мы берем реальные данные из них и присваиваем их значение в поле атрибута temp (или on_state в случае с реле) в создаваемых сенсорах.

И да, тут потребуется несколько пояснений.

Во-первых, зачем два сенсора уличной температуры? По причине того, что на главном экране Home Assistant мы хотим видеть показания температуры в чистом виде. А со всеми этими сенсорами, кроме street_temperature, мы будем производить манипуляции, которые этого сделать не позволят.

Во-вторых, у нас появляется сенсор boiler_state, который нужен нам для отражения состояния включенности котла. Так как графики строятся в пределах комнатной температуры (плюс-минус), что нам дает разброс по оси Y от 0 до 30 и выше (в летнее время), то отражение включенного котла в режиме: 0 - выключен, 1 - включен, на графике будет читаться очень плохо. 

Поэтому мы меняем состояние датчика в момент включение котла и присваиваем ему значение равное 50, чтобы на графиках была четкая и понятная картина, что происходило, когда котел включен или выключен.

А дальше мы делаем хитрый ход. Дело в том, что Home Assistant отдает данные в InfluxDB только при изменении основного состояния датчика. То есть если даже меняются атрибуты, но не меняются основные показания - данные не передаются. Именно поэтому нам нужен механизм принудительного изменения показаний датчика, чтобы этому ничего не мешало.

И именно поэтому, фактические показания датчиков температуры из Sonoff, в данных виртуальных сенсорах прописываются в атрибуты, а не в основной state, так как мы именно его и будем менять.

Для того, чтобы их менять, нам потребуется скрипт на питоне и автоматизация, которая будет его использовать.

Скрипт необходимо разметить в папке python_scripts в основном каталоге Home Assistant. Если папки нет, её нужно просто создать.

Далее необходимо создать внутри этой папки пустой файл set_state.py и добавить в него следующее содержимое:

inputEntity = data.get('entity_id')
if inputEntity is None:
    logger.warning("===== entity_id is required if you want to set something.")
else:    
    inputStateObject = hass.states.get(inputEntity)
    if inputStateObject is None and not data.get('allow_create'):
        logger.warning("===== unknown entity_id: %s", inputEntity)
    else:
        if not inputStateObject is None:
            inputState = inputStateObject.state
            inputAttributesObject = inputStateObject.attributes.copy()
        else:
            inputAttributesObject = {}
    
        for item in data:
            newAttribute = data.get(item)
            logger.debug("===== item = {0}; value = {1}".format(item,newAttribute))
            if item == 'entity_id':
                continue            # already handled
            elif item == 'allow_create':
                continue            # already handled
            elif item == 'state':
                inputState = newAttribute
            else:
                inputAttributesObject[item] = newAttribute
            
        hass.states.set(inputEntity, inputState, inputAttributesObject, force_update=True)

Далее мы должны в основном файле конфигурации Home Assistant configuration.yaml, добавить строку:

python_script:

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

Автоматизация запускается каждые 10 секунд с момента старта системы и прописывает в поле state перечисленных сенсоров текущее время с точностью до секунд. Этого точно достаточно для того, чтобы система распознала изменение.

В итоге мы получаем кучу сенсоров, у которых в основном поле у нас будет текущее время и оно будет меняться постоянно, а в поле атрибута temp для датчиков температуры или on_state для реле - будут данные, которые может давно и не менялись, но они в системе есть.

Добавляем в файл automations.yaml следующий текст:

- alias: 'sensor_update'
  trigger:
    - platform: time_pattern
      seconds: "/10"
  condition: []
  action:
    - service: python_script.set_state
      data_template:
        entity_id: sensor.floor_temp
        state: '{{ now().strftime("%H:%M:%S") }}'
        temp: >
          {{ state_attr('sensor.sonoff_floor_temperature', 'temperature') }}
    - service: python_script.set_state
      data_template:
        entity_id: sensor.kotel_temp
        state: '{{ now().strftime("%H:%M:%S") }}'
        temp: >
          {{ state_attr('sensor.sonoff_kotel_temperature', 'temperature') }}
    - service: python_script.set_state
      data_template:
        entity_id: sensor.boiler_State
        state: '{{ now().strftime("%H:%M:%S") }}'
        on_state: >
          {{ 0 if is_state('switch.sonoff_kotel', 'off') else 50 }}
    - service: python_script.set_state
      data_template:
        entity_id: sensor.street_temp
        state: '{{ now().strftime("%H:%M:%S") }}'
        temp: >
          {{ state_attr('weather.home', 'temperature') }}

Кстати заодно, раз уж мы всё равно производим манипуляции с сенсорами, мы заново запрашиваем показания реальных датчиков и тоже их загружаем скриптом в виртуальные. Ну так, на всякий случай :)

В итоге мы получаем картину, при которой у нас в Grafana поступают данные каждые 10 секунд.

Правда при именно такой настройке надо очень внимательно настраивать запрос данных в Grafana. Дело в том, что все созданные датчики с указанными "unit_of_measurement: C", попадают в раздел C, который необходимо выбирать в источнике данных справа от поля autogen. Если единицы измерения не указывать, то они попадут в группу state там же.

1600x_image.png?1631191288

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

1600x_image.png?1631191289

Для тех, кто не любит всякие там Grafana и подобное, в Home Assistant появилась карточка Lovelace, которая называется График статистики, которая тоже призвана строить красивые графики. Но он пока сырой и не все типы датчиков, да и вообще датчики, туда можно добавить.

Ну и напоследок, данный метод с использованием этого скрипта можно использовать для изменения любых параметров любых устройств внутри HA, просто вызывая его или автоматизацией или обычным скриптом. Очень полезно для всяких там виртуальных устройств, которыми нужно управлять или добавлять им сущности.

И чтобы вы понимали, в NodeRED данная операция делается с помощью трех нод. Внутри функции всего-навсего берутся данные из глобальных переменных и форматируются в нужном виде для InfluxDB.

Но это так... лирика.

1600x_image.png?1631192293

Зачем все так усложнять, если есть штатные средства InfluxDB и Grafana умеет ими пользоваться? Ведь можно немного откорректировать настройки в Grafana и она сама будет заполнять отсутствующие элементы. Давайте посмотрим на примере показаний с реальных, а не виртуальных сенсоров.

1600x_image.png?1631199042

Если мы в запросе параметра укажем группировку по времени, например 10 секунд, и укажем - заполнить отсутствующие данные предыдущими значениями, это позволит нам решить проблему?

Но нет.

Если за выбранный период вообще не было показаний, или предыдущие показания находятся за пределами выбранного диапазона времени - они в график не попадают.

1600x_image.png?1631199042

Хотя если мы возьмем диапазон пошире, то данные есть. Хотя тоже неполные.

1600x_image.png?1631199042

Возможно, стоит рассмотреть гибридный вариант, когда мы запрашиваем и меняем данные в НА реже, чтобы хотя бы три из измерения попадали в возможный выбранный период времени. То есть не раз в 10 секунд, а например, раз в 10 минут. Это позволит снизить объем базы данных при большом наборе хранимых там параметров, но это снизит уровень детализации, если один сенсор меняет свои значения очень часто, а другие делают это в разы реже.

Но тут выбирать уже придется самому. Главное, что оба варианта рассмотрены и их можно реализовать.


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

Хочешь умный дом но нет времени разбираться?
Посмотри примеры работ и выбери себе интегратора.
К списку статей

Скидки для сообщества

Z-Wave Ukraine

+380 68 641 9670
Промокод:
Sprut-UA
Размер скидки:
15%

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

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

17 декабря 2019, 17:49
Универсальный привод для автоматического удаленного открытия окон с простой интеграцией в умные дома.
27 октября 2018, 12:20
Нативный Термостат для котла на ESP8266 с поддержкой Apple HomeKit
25 сентября 2020, 11:49
Сенсорный монитор для управления умным домом. Настройка и использование.
20 октября 2018, 22:57
Теоретические основы протокола MQTT и описание того, как он работает и для чего используется
02 июля 2020, 07:24
Я хочу поделиться своей разработкой: "умным открывателем пластикового окна". В статье вы найдете все необходимое, чтобы повторить проект и изготовить собственный привод для открывания окна с использованием штатной фурнитуры окна.
24 августа 2018, 12:18
Пошаговая установка HomeAssistant
15 ноября 2018, 09:42
Способы автоматизации механических ворот
27 августа 2018, 10:14
Интегрируем ХА в HomeKit
03 октября 2018, 22:03
Как собрать и настроить Hyperion Ambilight - адаптивную подсветку ТВ.
29 октября 2019, 07:59
Умный домофон на базе nodeMCU с прошивкой ESPHome.