63. Примеры кода на Python для запуска в Home Assistant.


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

API - Application Programming Interface.

Существует огромное количество программ в мире, и каждая из них может выдавать результат своей работы в каком-то определённом формате. Как правило, такой формат будет понятен только оригинальной программе источнику, или же похожей ей. Чтобы упростить обмен данными между программами была придумана такая штука как API. Это задокументированный набор правил, согласно которым, та или иная программа выдаёт информацию и на какие запросы реагирует. Соответственно тот, кто хочет получать и обрабатывать такую информацию, строит систему запросов и обработки ответов и может работать с информацией в нужном виде. Есть несколько форматов вывода информации: json, csv, xml и т.д. Согласно этим форматам, принимающая сторона "знает" как выстраивать логику запроса и обработки информации.

Курсы валют с сайта Центрального Банка.

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

Задача:

Получать в Home Assistant актуальную информацию о курсе доллара и евро. Желательно в 2 разных объекта (сенсора).  Желательно из первоисточника.

План по реализации:

Первоисточником конечно же является орган контролирующий и регулирующий этот рынок - Центральный Банк.
У сайта Центрального Банка Израиля есть API, благодаря которому он предоставляет информацию о стоимости валюты. Для желающих почитать и ознакомиться с документацией, всё есть в открытом доступе на английском и иврите.

Исполнение:

Интерфейс выдающий все курсы находится по адресу: https://boi.org.il/PublicApi/GetExchangeRates
он доступен для отображения прямо из браузера.
и его ответ выглядит следующим образом: {"exchangeRates":[{"key":"USD","currentExchangeRate":3.844,"currentChange":-0.103950103950103950103950100,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"GBP","currentExchangeRate":4.8014,"currentChange":0.1439149024924392533110856200,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"JPY","currentExchangeRate":2.607,"currentChange":-0.1799594134088907608071371100,"unit":100,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"EUR","currentExchangeRate":4.1176,"currentChange":-0.0291346994270175779353209700,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"AUD","currentExchangeRate":2.4582,"currentChange":0.0855014046659337974838158100,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"CAD","currentExchangeRate":2.8134,"currentChange":-0.1313407404777963153597671400,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"DKK","currentExchangeRate":0.552,"currentChange":-0.0362187613183629119884100,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"NOK","currentExchangeRate":0.3598,"currentChange":0.2787068004459308807134894100,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"ZAR","currentExchangeRate":0.2014,"currentChange":0.6496751624187906046976511700,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"SEK","currentExchangeRate":0.3459,"currentChange":0.0289184499710815500289184500,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"CHF","currentExchangeRate":4.3137,"currentChange":0.0881690990510220654771572400,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"JOD","currentExchangeRate":5.4216,"currentChange":-0.1031839622641509433962264200,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"LBP","currentExchangeRate":0.0026,"currentChange":0,"unit":10,"lastUpdate":"2023-09-08T09:25:03.5510063Z"},{"key":"EGP","currentExchangeRate":0.1244,"currentChange":0.0804505229283990345937248600,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"}]}.

К этому же интерфейсу можно обратиться с запросом только определённой валюты.

Для доллара: https://boi.org.il/PublicApi/GetExchangeRate?key=USD
ответ будет таким: {"key":"USD","currentExchangeRate":3.844,"currentChange":-0.103950103950103950103950100,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"}
Для евро: https://boi.org.il/PublicApi/GetExchangeRate?key=EUR
Ответ будет таким: {"key":"EUR","currentExchangeRate":4.1176,"currentChange":-0.0291346994270175779353209700,"unit":1,"lastUpdate":"2023-09-08T09:25:03.5510063Z"}
  • Если внимательно изучить ответы, то видна некая закономерность в параметрах, это и есть формат вывода. Т.к. задача была получить только курсы двух валют, то нет смысла обрабатывать все данные. Лучше сформировать точечные запросы чтобы получить только необходимую информацию.
  • Т.к. валют 2, то получается что надо делать 2 запроса. Неплохо бы объединить их в один, чтобы сократить количество обращений к серверу.
  • Информация предоставляется до десятитысячных долей (4 знака после запятой), т.к. в деньгах нет таких единиц, то нужно будет округлить результат до сотых. Именно округлить, а не отрезать 2 последних знака.
"Скармливаем" все условия чат боту и получаем рабочий код:

 
Теперь есть рабочая версия от которой можно отталкиваться и идти дальше, а именно - переносить этот код в Home Assistant.

В ХА будут нужны некоторые дополнения для этого кода:
  • Т.к. речь идёт о достаточно динамических данных, то неплохо было бы сделать регулярное автоматическое выполнение таких запросов.
  • Оригинальная версия просто выводила текст с данными на экран. В ХА, вместо этого, надо будет записать эти данные в сенсоры.
  • Т.к. таких сенсоров нет, их надо создать вручную.
Начнём с последнего пункта, чтобы подготовить платформу где всё будет происходить, и чтобы избежать лишних ошибок при запуске скрипта.

Создадим два номерных хелпера. Тип, "слайдер" или "поле ввода", - не важен. Главное выбрать шаг до тысячных - 0.001 (на всякий случай с запасом, не смотря на то, что данные округляются до сотых). Минимальное значение ноль, максимальное - насколько совести хватит 😀
Выбираем соответствующую иконку, указываем единицу измерения и сохраняем.

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

Переходим к предфинальной части - Appdaemon.

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


После установки необходимо настроить дополнение. Об этом чуть подробнее.
Дело в том, что у языка программирования Python есть очень много дополнений и модулей не включенных в базовый пакет установки. Эти модули позволяют коду выполнять разные функции. Когда питон установлен на компе, то нужный модуль ставится командой pip install имя_модуля и после этого код может использовать его функционал. Т.к. Аппдемон представляет собой закрытую среду для выполнения кода, то в настройках этой самой среды стоит указать какие модули надо скачать и установить для работы скриптов. Кстати скачиваться и устанавливаться они будут каждый раз после запуска дополнения.
 
Пишем имя модуля используемого в коде, в разделе Python packages. Узнать какой модуль нужен для каждого конкретного кода можно всегда у автора этого самого кода.
И ещё один несомненный плюс этого дополнения. После его установки, любой файл с кодом, созданный в папке для приложений автоматически запускается при каждом сохранении/изменении.

Крайне рекомендуется перед использованием и запуском заглянуть в основной файл конфигурации этого дополнения: /config/appdaemon/appdaemon.yaml
Здесь стоит отредактировать и указать свой часовой пояс и координаты (долготу и широту) любой точки внутри страны в этом же часовом поясе.

Ну и наконец долгожданный финал - объяснить новому бестелесному другу как и для чего нужно модифицировать имеющийся код, какие новые условия появились и т.д.
Кстати, проверять работу кода необходимо в логах самого дополнения, а не в логах системы.
Когда получим и запустим рабочий код, то желаемые сенсоры будут обновлены:

Пример рабочего кода:


Ну и конечно же, чтобы всё это работало, не забываем прописать скрипт в файле приложений:

Дата ближайшей игры с сайта стадиона.

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

Задача:

Получить сенсор с датой и временем ближайшей игры от первоисточника. В данном случае сайт городского стадиона. Так же, получать оповещения в день игры (по желанию за Х времени до неё).

План по реализации:

Создание хелпера для записи в него результата работы кода.
На основании данных из хелпера создавать автоматизацию оповещающую об этом событии.
Для корректной работы кода и постановки задачи чат боту, определить какую информацию брать с сайта и как её обрабатывать.
Для этого оценить код сайта (API не нашёлся) и понять логику его работы.

Исполнение:

Два первых пункта делаются достаточно просто.
Сенсор типа input_datetime создаём с помощью хелпера.

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

Код шаблона будет выглядеть следующим образом: {{ states('input_datetime.igra_na_stadione')[:10] == now().date().strftime('%Y-%m-%d') }}
(не забываем указать в условии имя своего сенсора вместо указанного).

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

Для начала откроем в любом хромоподобном браузере инструмент разработчика. Естественно когда сайт уже загружен. Вызывается этот инструмент по нажатию кнопки F12.

Теперь нам нужен Инспектор элементов (Elements Inspector), Он включается через сочетание горячих клавиш Ctrl+Shift+C, либо при нажатии на значок с рамкой и стрелкой в верхней панели инструментов. Водя мышкой по сайту, можно увидеть как подсвечиваются разные участки и блоки. Вместе с тем, справа выделяются участки кода страницы, отвечающие за эти блоки.
 
Зная адрес блока с нужной информацией, можно указать его в коде будущего скрипта для обработки. Бинго!

Однако, изучив таким образом код сайта, было обнаружено что все записи находятся в блоках, которые имеют названия явно не похожие на созданные человеком. А это говорит только том, что названия этих блоков не статичны, и генерируются движком сайта каждый раз при обновлении таблицы. Новые игры добавляются, старые исчезают. Если сегодня была игра и она была первой в списке, то завтра её уже там не будет. И бывшая раньше второй, другая игра станет первой.
И поэтому, именно в данном случае можно воспользоваться другим способом.
Задача стоит вытащить время и дату из каждой записи.
Все записи отвечающие условиям этой задачи имеют одинаковый формат: двузначное число-слэш-двузначное число-слэш-двузначное число-пробел-двузначное число-двоеточие-двузначное число. А это уже закономерность, с которой можно работать.
Теперь скрипт может парсить эту страничку вытаскивая из неё все даты.

Parsing - процесс анализа и извлечения информации из текста или данных, которые находятся в структурированном или полуструктурированном формате. Слово "парсинг" произошло от английского глагола "parse", который означает "анализировать" или "разбирать". 

После того как скрипт отсеет всё лишнее, оставшиеся данные всё равно будут просто набором символов, которые необходимо перевести в формат даты/времени, для дальнейшей работы.

И только когда все полученные записи переведены в такой формат и отсортированы, можно сравнить их с текущей датой чтобы выбрать ближайшую. Вот такая интересная задача может быть - объяснить это всё чат боту. Однако проявляя терпение и чудеса дипломатии и напрягая голову для формулирования объяснения, можно добиться желаемого результата


Последний этап это изменить код для запуска в AppDaemon добавив ему функционал по проверке информации на сайте нужное количество раз в день, и вывод результата не на экран, а в созданный в начале сенсор даты/времени. И как показывает практика, это тоже возможно.


Пример и результат диалога с ИИ.

Как уже было сказано - необходимо максимально точно и в полном количестве дать вводных данных. И максимально точно сформулировать вопрос. В этом примере был просто скопирован весь текст сенсора как он есть прямо со вкладки  "Состояния".



Берём только шаблон (то, что в кавычках) и проверяем его во встроенном в ХА редакторе шаблонов
Как видно, код шаблона рабочий, и результат работы кода - "ложь" т.к. дата в сенсоре не соответствует условиям (текущая дата минус сутки). Теперь этот код можно использовать хоть в автоматизациях как шаблонное условие, хоть для создания ещё одного бинарного сенсора.

Какой вывод можно сделать по итогу такого небольшого эксперимента?
Ну наверное что развивающийся ИИ даже в бесплатной версии очень хороший помощник.
Но при этом не стоит забывать что результат его советов прямо пропорционален точности и подробности запросов. Сам он не додумает за пользователя многие вещи, об этом надо позаботиться самому. А с другой стороны, он позволяет начать использовать несколько скриптов человеку, очень далёкому от программирования.
А какие вещи с помощью ИИ делаете вы?

Комментарии