Статья

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

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

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

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

Задача:

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

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

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

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

К тому же хочется, чтобы на одном графике были видны совместные состояния всех датчиков и котла. Для этого придется поставить 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 = ; value = ".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 там же.

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

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

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

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

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

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

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

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


Если честно, я не понял, зачем так извращаться – есть же параметр 

force_update: true

– который можно прописать прямо в configuration.yaml для нужных сенсоров. И тогда HA будет создавать точки даже если данные не менялись.

А что-то там не работало с этим параметром. Я уже не помню что, но графики строились неправильно в итоге.


Вернуться назад
Вернуться назад