- PVSM.RU - https://www.pvsm.ru -
Вступление.
Для своей домашней автоматизации я уже давно использую HomeAssistant. Однажды товарищ у меня спросил, мол, почему у HomeAssistant есть возможность указывать только текущее положение трекера на карте, но нельзя отобразить весь маршрут следования? С тех пор данная идея захватила меня. И однажды я понял, что и сам очень хочу иметь эту функцию вот прямо сейчас. Всем кому интересно, что из этого вышло, добро пожаловать под кат…
Разведка.
Собственно, чтобы отобразить маршрут нужно иметь набор точек с координатами, поэтому первым шагом было выяснить где HomeAssistant хранит нужные данные (если вообще хранит) и как их оттуда достать. Недолгое изучение первоисточника сразу привело к решению: необходим включенный модуль recorder для записи состояний нужных датчиков в БД в различные моменты времени, а также модуль history, который позволяет получать данные из БД в красивом виде. У модуля history есть хорошо документированный REST API [1]. То, что нужно!
Далее необходимо полученные данные как-то отобразить на карте. Существует множество различных сервисов, позволяющих отображать историю перемещений. Я наверняка перепробовал далеко не все из них, однако позволю себе пару слов о проверенных мною:
А. yandex и google. Собственно для моих нужд там есть все и даже больше, однако в силу платности сервисов и сильных ограничений бесплатных версий, они мне сразу не подошли. Yandex, например, разрешает бесплатное использование только для открытых проектов (то есть любой человек должен иметь возможность в любое время открыть твой ресурс и воспользоваться его возможностями), не говоря уже о других ограничениях в количестве запросов. Про изменения в политике Google по отношению к api не писал только ленивый. На текущий момент, насколько я понял, каждый запрос к maps api или directions api оплачивается, чем больше запросов — тем дешевле. Однако каждому пользователю с подключенной к аккаунту банковской картой дается бесплатный лимит на 200$ в месяц. Все что сверху сразу оплачивается с вашей карты. Привязка карты к аккаунту — это не наш путь.
Поправьте, если я ошибся где то по поводу google и/или yandex.
Б. Связка OpenRouteService [2] и OpenRouteService maps. В принципе по возможностям мало чем отличается от google или yandex (во всяком случае я не заметил). Полностью бесплатен (есть ограничения по количеству запросов в день и в минуту при превышении которых советуют обратиться в поддержку… описания каких-либо платных тарифов нет вообще). Однако использование ресурса OpenRouteService maps оказалось неудобным (долгая загрузка приложения и назойливое широкое меню слева, открывающееся по умолчанию и не отключаемое средствами API, к тому же сервис не совсем корректно открывается с мобильных устройств). Справедливости ради, OpenRouteService maps можно поставить на свой сервер и вполне возможно, что там позволено сконфигурировать все под себя.
В. Mapbbcode [3]. Наткнулся на хабре [4] на интересную реализацию карт в простом формате. В принципе для моей задачи проект абсолютно подходит, однако из этой статьи я узнал о Leaflet [5] и решил обратиться к первоисточнику. На ней в итоге и остановился…
Г. Leaflet. Очень хорошая open-source js библиотека для карт, простая в освоении и хорошо документированная. Из фишек: позволяет использовать тайлы от многих сервисов (openstreetmaps, yandex, google, mapbox, microsoft и тд и тп). Дополнительно я использовал плагин leaflet.polylineDecorator [6] для указания направления движения на карте.
Стоит упомянуть, что последние два рассматриваемых ресурса не поддерживают «маршруты», то есть не умеют соединять точки вдоль существующих дорог и/или тротуаров, а просто соединяют точки прямой линией. Лично для меня это не проблема, а осознанный шаг. Если нужна именно навигация по дорогам, то нужно смотреть в сторону платных google, yandex или бесплатного openrouteservice.
Реализация.
Запрос к модулю history через REST-API довольно прост (здесь и далее код будет на языке HomeAssistant, т.е. python) и позволяет получить ответ в виде простого для понимания JSON:
response = requests.get(self._haddr + '/api/history/period/' + dayBegin + '?filter_entity_id=' + self._myid, headers={'Authorization': 'Bearer ' + self._token, 'content-type': 'application/json'})
data = response.json()[0]
здесь self._haddr — это адрес вашего HA такой же, как указан в настройках frontend, self._myid — это ид устройства device_tracker, чей маршрут мы будем строить, dayBegin – это начало периода для отображения маршрута, я по умолчанию выбрал начало текущего дня, self._token – это long-life токен для доступа к апи, который можно получить в интерфейсе HomeAssistant.
Когда объект, чью историю мы пытаемся отобразить на карте, долгое время находится неподвижно или передвигается крайне медленно, мы получим кучу точек, близко расположенных и забивающих карту. Для исправления ситуации пропустим полученный массив координат через фильтр: если расстояние между предыдущей точкой и следующей менее 100 метров, то не отображать точку на карте. Для расчета расстояний между двумя соседними точками используем упрощенную формулу с равноугольным приближением [7]. Приближение применимо, когда расстояние между соседними точками не превышает нескольких км:
def getDistance(self, latA, lonA, latB, lonB):
dst = 0
latRadA = math.radians(latA)
lonRadA = math.radians(lonA)
latRadB = math.radians(latB)
lonRadB = math.radians(lonB)
x = latRadB - latRadA
y = (lonRadB-lonRadA)*math.cos((latRadB+latRadA)*0.5)
dst = 6371*math.sqrt(x*x+y*y)
return dst
Здесь dst расстояние в км.
Описывать API Leaflet здесь не вижу смысла. За этим — на официальный сайт [8]. Модуль работает следующим образом: Каждые n секунд (у меня настроено на 300) делается запрос к сегодняшней истории интересующего меня объекта. Полученный массив координат прогоняется через фильтр расстояний, уменьшая количество точек. Далее в папке с конфигурацией HomeAssistant в папке www формируются 2 файла: index.html и route.html. В файле route.html прописана вся логика по созданию карты. А файл index.html — это лайфхак по предупреждению кэширования страницы. По умолчанию HomeAssistant кэширует все, что только можно, и только сброс кэша помогал актуализировать данные на карте, что, конечно, неприемлемо. В файле index.html происходит вызов содержимого route.html однако с рендомным динамически формируемым параметром, что позволяет всегда запрашивать с сервера актуальную версию файла route.html:
src = 'route.html?datetime=' + (new Date()).getTime() + Math.floor(Math.random() * 1000000)
Немного о безопасности.
HomeAssistant устроен так, что все файлы внутри директории www являются публичными, то есть любой файл внутри директории www можно открыть в любом браузере безо всякой авторизации, зная прямую ссылку. В случае с моим модулем эта ссылка такая: your_address_homeassistant/local/route/index.html [9]. Если для вас это не критично, то можете пропускать данный раздел. Я же пошел немного дальше и прикрутил таки авторизацию к странице с маршрутами. Для этого я использовал nginx (вы можете выбрать другой веб сервер с поддержкой реверсивного прокси) в качестве прокси сервера. На сайте HomeAssistant есть официальная инструкция [10] по настройке данной конфигурации. После настройки прокси и проверки работы в конфигурацию nginx нужно добавить авторизацию для нужных страниц:
location /local/route/route.html {
proxy_pass http://localhost:8123/local/route/route.html;
proxy_set_header Host $host;
proxy_redirect http:// https://;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
auth_basic "Unauthorized";
auth_basic_user_file /etc/nginx/.htpasswd;
}
Затем создать файл "/etc/nginx/.htpasswd", и в консоли выполнить последовательно команды:
sh -c "echo -n 'admin:' >> /etc/nginx/.htpasswd"
sh -c "openssl passwd -apr1 >> /etc/nginx/.htpasswd"
admin – заменить на желаемый логин.
После этого перезапускаем nginx и проверяем: при попытке открыть страницу с маршрутом браузер должен запрашивать логин и пароль. Замечу, что это отдельная авторизация, никак не связанная с авторизацией самого HomeAssistant.
Заключение.
Пожалуй и все, что можно рассказать о данном модуле.
Кого заинтересовало, вот ссылка [11] на модуль. Файл расположить по пути: config_folder_homeassistant/custom_components/route/sensor.py, не забывайте про права.
Если не существует, то создать папку config_folder_homeassistant/www и выдать на нее соответствующие права.
В конфигурационном файле configuration.yaml прописать следующие строки:
sensor:
- platform: route
name: route
entityid: your_device_tracker_entity_id
haddr: your_address_homeassistant
token: your_long_life_token
здесь your_device_tracker_entity_id – это ID вашего устройства device_tracker, your_address_homeassistant – внешний адрес вашего HomeAssistant, your_long_life_token – предварительно полученный во фронтенде HomeAssistant токен доступа для использования REST API.
После этого перезапустить HomeAssistant и наслаждаться. Карта будет доступна по прямой ссылке: your_address_homeassistant/local/route/index.html [9]. При желании вы можете добавить ее в меню HA с помощью panel_iframe или в любое окно HA через lovelace card “iframe”.
На этом все, спасибо за внимание.
Автор: mavrikk
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/umny-j-dom/315081
Ссылки в тексте:
[1] REST API: https://developers.home-assistant.io/docs/en/external_api_rest.html
[2] OpenRouteService: https://openrouteservice.org/
[3] Mapbbcode: https://github.com/MapBBCode/mapbbcode
[4] хабре : https://habr.com/en/post/207232/
[5] Leaflet: https://leafletjs.com/
[6] leaflet.polylineDecorator: https://github.com/bbecquet/Leaflet.PolylineDecorator
[7] равноугольным приближением: http://www.movable-type.co.uk/scripts/latlong.html
[8] официальный сайт: https://leafletjs.com/reference-1.4.0.html
[9] your_address_homeassistant/local/route/index.html: https://your_address_homeassistant/local/route/index.html
[10] инструкция: https://www.home-assistant.io/docs/ecosystem/nginx/
[11] вот ссылка: https://yadi.sk/d/buRKuNkJ7xDLzg
[12] Источник: https://habr.com/ru/post/448656/?utm_campaign=448656
Нажмите здесь для печати.