Большой брат следит за… собой или карта с историей перемещений в HomeAssistant

habr.png

Вступление.
Для своей домашней автоматизации я уже давно использую HomeAssistant. Однажды товарищ у меня спросил, мол, почему у HomeAssistant есть возможность указывать только текущее положение трекера на карте, но нельзя отобразить весь маршрут следования? С тех пор данная идея захватила меня. И однажды я понял, что и сам очень хочу иметь эту функцию вот прямо сейчас. Всем кому интересно, что из этого вышло, добро пожаловать под кат…

Разведка.
Собственно, чтобы отобразить маршрут нужно иметь набор точек с координатами, поэтому первым шагом было выяснить где HomeAssistant хранит нужные данные (если вообще хранит) и как их оттуда достать. Недолгое изучение первоисточника сразу привело к решению: необходим включенный модуль recorder для записи состояний нужных датчиков в БД в различные моменты времени, а также модуль history, который позволяет получать данные из БД в красивом виде. У модуля history есть хорошо документированный REST API. То, что нужно!
Далее необходимо полученные данные как-то отобразить на карте. Существует множество различных сервисов, позволяющих отображать историю перемещений. Я наверняка перепробовал далеко не все из них, однако позволю себе пару слов о проверенных мною:
А. yandex и google. Собственно для моих нужд там есть все и даже больше, однако в силу платности сервисов и сильных ограничений бесплатных версий, они мне сразу не подошли. Yandex, например, разрешает бесплатное использование только для открытых проектов (то есть любой человек должен иметь возможность в любое время открыть твой ресурс и воспользоваться его возможностями), не говоря уже о других ограничениях в количестве запросов. Про изменения в политике Google по отношению к api не писал только ленивый. На текущий момент, насколько я понял, каждый запрос к maps api или directions api оплачивается, чем больше запросов — тем дешевле. Однако каждому пользователю с подключенной к аккаунту банковской картой дается бесплатный лимит на 200$ в месяц. Все что сверху сразу оплачивается с вашей карты. Привязка карты к аккаунту — это не наш путь.
Поправьте, если я ошибся где то по поводу google и/или yandex.
Б. Связка OpenRouteService и OpenRouteService maps. В принципе по возможностям мало чем отличается от google или yandex (во всяком случае я не заметил). Полностью бесплатен (есть ограничения по количеству запросов в день и в минуту при превышении которых советуют обратиться в поддержку… описания каких-либо платных тарифов нет вообще). Однако использование ресурса OpenRouteService maps оказалось неудобным (долгая загрузка приложения и назойливое широкое меню слева, открывающееся по умолчанию и не отключаемое средствами API, к тому же сервис не совсем корректно открывается с мобильных устройств). Справедливости ради, OpenRouteService maps можно поставить на свой сервер и вполне возможно, что там позволено сконфигурировать все под себя.
В. Mapbbcode. Наткнулся на хабре на интересную реализацию карт в простом формате. В принципе для моей задачи проект абсолютно подходит, однако из этой статьи я узнал о Leaflet и решил обратиться к первоисточнику. На ней в итоге и остановился…
Г. Leaflet. Очень хорошая open-source js библиотека для карт, простая в освоении и хорошо документированная. Из фишек: позволяет использовать тайлы от многих сервисов (openstreetmaps, yandex, google, mapbox, microsoft и тд и тп). Дополнительно я использовал плагин leaflet.polylineDecorator для указания направления движения на карте.

Стоит упомянуть, что последние два рассматриваемых ресурса не поддерживают «маршруты», то есть не умеют соединять точки вдоль существующих дорог и/или тротуаров, а просто соединяют точки прямой линией. Лично для меня это не проблема, а осознанный шаг. Если нужна именно навигация по дорогам, то нужно смотреть в сторону платных 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 метров, то не отображать точку на карте. Для расчета расстояний между двумя соседними точками используем упрощенную формулу с равноугольным приближением. Приближение применимо, когда расстояние между соседними точками не превышает нескольких км:

    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 здесь не вижу смысла. За этим — на официальный сайт. Модуль работает следующим образом: Каждые 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. Если для вас это не критично, то можете пропускать данный раздел. Я же пошел немного дальше и прикрутил таки авторизацию к странице с маршрутами. Для этого я использовал nginx (вы можете выбрать другой веб сервер с поддержкой реверсивного прокси) в качестве прокси сервера. На сайте HomeAssistant есть официальная инструкция по настройке данной конфигурации. После настройки прокси и проверки работы в конфигурацию 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.

Заключение.
Пожалуй и все, что можно рассказать о данном модуле.
Кого заинтересовало, вот ссылка на модуль. Файл расположить по пути: 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. При желании вы можете добавить ее в меню HA с помощью panel_iframe или в любое окно HA через lovelace card «iframe».
На этом все, спасибо за внимание.

© Habrahabr.ru