Zliczanie dziennego dystansu w terenie - jak obliczyć?

Czy ktoś z Was ma pomysł lub doświadczenie w jaki sposób można zliczać w HA dzienny dystans pokonywany przez kogoś w terenie (a raczej przez jego urządzenie mobilne zrejestrowane w HA)?
Np. jeśli wybiorę się na rowerową wycieczkę, to HA bardzo ładnie pokazuje moją trasę na mapie, dodając do każdego punktu pośredniego nawet stempel czasu. Chciałbym z tych informacji wyciągnąć (lub obliczyć) długość trasy pokonanej od godziny A do godziny B sumując wszystkie odległości między kolejnymi punktami pośrednimi.
Integracja proximity wskazuje moją odległość zawsze w kierunku od domu (lub od innej wskazanej wcześniej strefy), ale nie między pośrednimi markerami. Jak wobec tego wyznaczyć sumę odległości między markerami? Wynik takiego działania byłby dokładnie rozwiązaniem zadania…

Działanie:

Odczytuje aktualne współrzędne GPS z urządzenia (telefonu).
Bierze poprzednie współrzędne zapisane w input_number (to jest „pamięć” pozycji z ostatniego pomiaru).
Oblicza dystans między starą a nową pozycją.
Dodaje ten dystans do już zarejestrowanego dziennego dystansu (też w input_number).
Zapisuje aktualne współrzędne do input_number, żeby przy następnym wywołaniu móc porównać z nową pozycją.

Input_number
input_number:
  poprzednia_szerokosc:
    name: Poprzednia szerokość GPS
    initial: 0.0
    min: -90
    max: 90
    step: 0.000001

  poprzednia_dlugosc:
    name: Poprzednia długość GPS
    initial: 0.0
    min: -180
    max: 180
    step: 0.000001

  dystans_dzienny_m:
    name: Dystans dzienny (m)
    initial: 0
    min: 0
    max: 10000000   # 10 000 km limit w metrach
    step: 0.01

Automatyzacja:

alias: Aktualizacja  zliczania dystansu –  Poco
description: Liczy dystans na podstawie zmiany pozycji GPS
trigger:
  - platform: state
    entity_id: device_tracker.poco
condition:
  - condition: template
    value_template: >
      {{ state_attr('device_tracker.poco', 'latitude') is not none and
         state_attr('device_tracker.poco', 'longitude') is not none }}
action:
  - variables:
      aktualna_szerokosc: "{{ state_attr('device_tracker.poco', 'latitude') }}"
      aktualna_dlugosc: "{{ state_attr('device_tracker.poco', 'longitude') }}"
      poprzednia_szerokosc: "{{ states('input_number.poprzednia_szerokosc') | float }}"
      poprzednia_dlugosc: "{{ states('input_number.poprzednia_dlugosc') | float }}"
      dystans_m: >
        {{ (distance(poprzednia_szerokosc, poprzednia_dlugosc,
        aktualna_szerokosc, aktualna_dlugosc) * 1000) | float }}
  - service: input_number.set_value
    target:
      entity_id: input_number.poprzednia_szerokosc
    data:
      value: "{{ aktualna_szerokosc }}"
  - service: input_number.set_value
    target:
      entity_id: input_number.poprzednia_dlugosc
    data:
      value: "{{ aktualna_dlugosc }}"
  - service: input_number.set_value
    target:
      entity_id: input_number.dystans_dzienny_m
    data:
      value: >
        {% if dystans_m < 1 %}
          {{ states('input_number.dystans_dzienny_m') | float | round(0) }}
        {% else %}
          {{ ((states('input_number.dystans_dzienny_m') | float) + dystans_m) | round(0) }}
        {% endif %}
mode: single

“Ślady”


Tak zlicza dystans:

Wartość podaje w metrach

Można to zrobić tak:
Po naciśnięciu przycisku na telefonie ( HA , włącznik) włącza automatyzacje, która to zlicza dystans.
By bardziej dokładnie można dodać “włączenie bardziej dokładnej lokalizacji”

Po zakończeniu “pomiaru”/ jazdy następnym wirtualnym włącznikiem wyłączyć automatyzacje i dokładną lokalizacje.

To jest wersja testowa, kilka rzeczy jest do poprawki np. teraz podaje w metrach 0.1 trzeba by zaokrąglić do 0, a może w km było by lepiej.

:grinning:Sprawdzone i działa na małym dystansie
:information_source:
Zmiana współrzędnych w HA nie działa na automatyzacje wyzwalacz musi otrzymać nowe dane.
Odświeżanie danych z GPS jest ważne zwłaszcza na drodze z wieloma zakrętami.
Dokładność jest zależna od odświeżania współrzędnych, jakości połączenia z HA (Internet)

PS To było na niemieckiej autostradzie :upside_down_face:


/
GPS włączenie/ wyłączenie dokładnej lokalizacji

Przykład
    action:
      - action: notify.mobile_app_<your_device_id_here>
        data:
          message: "command_high_accuracy_mode"
          data:
            command: "turn_off"
2 polubienia

No, no, no! Trochę się narobiłeś przy tym! :slight_smile:
Kod jest zacny, więc z pewnością będę się na nim wzorował.
Bardzo Ci dziękuję za pomysł i udostępnienie
:heart_decoration:

Pewnie tę automatykę da się jakość dodatkowo zautomatyzować, np. żeby triggerowała się sama gdy wyjadę rowerem poza zonę miejską… Oczywiście uruchomienie manualne na żądanie też musi być. Zastanawiam się jeszcze nad samoczynnym resetowaniem się licznika input_number.dystans_dzienny_m po jakimś czasie (np. po 24. godzinach), żeby kazdą dzienną trasę samodzielnie liczyło od zera.

Albo takim trigger-based template sensorem:

Kod
- trigger:
  - trigger: event
    event_type: event_template_reloaded
    id: reload
  - trigger: state
    entity_id:
      - device_tracker.iphone_maciej
    id: tracker
  # event aby odświeżyć atrybuty 'today' i 'last7' najnowszą wartością dystansu
  - trigger: event
    event_type: probably_a_new_distance
    id: new_dist
  # odświeżenie atrybutów today/last7 po północy i usunięcie zbyt starych danych
  - trigger: time_pattern
    hours: 0
    minutes: 1
    id: time
  action:
    - variables:
        # filtrowanie "pływania" GPS: ignoruj zmiany dystansu mniejsze niż tyle metrów
        MIN_DISTANCE: 10
        # ilość dni przechowywanych na tym sensorze (ustaw 0 aby kasować po każdym dniu)
        HISTORY_DAYS: 30
        # na pewno nieprawidłowa wartość długości/szerokości geograficznej
        BAD_VAL: 200
        pass1: "{{ trigger.id == 'tracker' }}"
        newLat: "{{ trigger.to_state.attributes.get('latitude',BAD_VAL)|float(BAD_VAL) if pass1 }}"
        newLon: "{{ trigger.to_state.attributes.get('longitude',BAD_VAL)|float(BAD_VAL) if pass1 }}"
        pass2: "{{ pass1 and newLat != BAD_VAL and newLon != BAD_VAL }}"
    - if: "{{ pass2 }}"
      then:
        # event dla atrybutów 'today','last7', aby się odświeżyły po nowej wartości, gdyż w bieżącym
        # przebiegu (od trackera) widziałyby stan atrubutu 'data' sprzed bieżącej aktualizacji
        - event: probably_a_new_distance
  sensor:
    - name: Dystans, Maciej
      unique_id: cda5ae8b-8386-4ca7-b917-f87c5203e64d
      state: Rejestr przebytego dystansu
      attributes:
        # suma dzisiejszego dystansu
        today: >-
          {% if trigger.id in ['new_dist','time'] %}
            {% set start = today_at('00:00').timestamp()|string %}
            {% set end = today_at('23:59:59').timestamp()|string %}
            {% set data = this.attributes.get('data',{}) or {} %}
            {% set distances = data.Distances if data.Distances is defined else {} %}
            {{ distances.items()|selectattr('0','ge',start)|selectattr('0','le',end)|map(attribute='1.Dist')|sum }}
          {% else %}
            {{ this.attributes.get('today',0) }}
          {% endif %}
        # suma z ostatnich 7 dni
        last7: >-
          {% if trigger.id in ['new_dist','time'] %}
            {% set delta = timedelta(days= 6) %}
            {% set start = (today_at('00:00') - delta).timestamp()|string %}
            {% set end = today_at('23:59:59').timestamp()|string %}
            {% set data = this.attributes.get('data',{}) or {} %}
            {% set distances = data.Distances if data.Distances is defined else {} %}
            {{ distances.items()|selectattr('0','ge',start)|selectattr('0','le',end)|map(attribute='1.Dist')|sum }}
          {% else %}
            {{ this.attributes.get('last7',0) }}
          {% endif %}
        data: >-
          {% set current = this.attributes.get('data', {
            'LastLatitude': state_attr('device_tracker.iphone_maciej','latitude')|float(BAD_VAL),
            'LastLongitude': state_attr('device_tracker.iphone_maciej','longitude')|float(BAD_VAL),
            'LastUpdate': 'n/a',
            'Distances': {},
          }) %}
          {% if pass2 %}
            {% if current.LastLatitude != BAD_VAL and current.LastLongitude != BAD_VAL %}
              {% set newDist = (distance(current.LastLatitude, current.LastLongitude, newLat, newLon)*1000)|round(0) %}
              {% if newDist >= MIN_DISTANCE %}
                {% set k = now().timestamp()|string %}
                {% set v = {
                  'Lat': newLat,
                  'Lon': newLon,
                  'Dist': newDist,
                } %}
                {% set newEntry = {k: v} %}
                {{
                  {
                    'LastLatitude': newLat,
                    'LastLongitude': newLon,
                    'LastUpdate': now()|string,
                    'Distances': dict(current.Distances, **newEntry),
                  }
                }}
              {% else %}
                {{ current }}
              {% endif %}
            {% else %}
              {{
                {
                  'LastLatitude': newLat,
                  'LastLongitude': newLon,
                  'LastUpdate': current.LastUpdate,
                  'Distances': current.Distances,
                }
              }}
            {% endif %}
          {% elif trigger.id == 'time' %}
            {% set delta = timedelta(days= HISTORY_DAYS) %}
            {% set minDate = (today_at('00:00') - delta).timestamp()|string %}
            {{
              {
                'LastLatitude': current.LastLatitude,
                'LastLongitude': current.LastLongitude,
                'LastUpdate': current.LastUpdate,
                'Distances': dict(current.Distances.items()|rejectattr('0','lt',minDate)),
              }
            }}
          {% else %}
            {{ current }}
          {% endif %}

Oczywiście świeża sprawa, mogą być błędy. Poszedłem do Lidla i z powrotem i dobrze policzyło!
Oprócz zmiany nazwy sensora i device_trackera, zwróć uwagę na MIN_DISTANCE i HISTORY_DAYS. Sensor ma własną historię, niezależną od ilości dni w ustawieniach recordera i spokojnie można to ustawić nawet na 3 lata :slight_smile:


Ponieważ atrybut sensora może potencjalnie zawierać bardzo dużo danych, zdecydowanie polecam sensor wykluczyć z recordera, czyli ze standardowej historii Home Assistanta.

2 polubienia

Wystarczy że suwak input_number.dystans_dzienny_m - przesuniesz do zera
lub automacja, że o 24.00 wstaw wartość “0” doinput_number.dystans_dzienny_m

Dzienna - to nazwa z pierwszego założenia (które porzuciłem) w rzeczywistości wartość będzie dodawana bez limitu ( limit jest jedynie w “input max”)

Kod zmieniłem sobie na km, juto przetestuje na większym zasięgu i porównam z wskazaniami licznika w samochodzie.
/
18:15:30 - 4462,0 m
18:15:31 - 4537,0 m
Różnica: 75 m w 1 sekundę, a nie jechałem 270 km/h :grinning:

Jeżeli niezależne od rekordera to gdzie zapisują się te 3 Lata?

No w atrybucie ‘data’ w sensorze. Mówiąc “niezależne” miałem precyzyjnie na myśli, że sensor tak stworzony nie musi być zapisywany przez recorder, a nawet nie powinien, zwłaszcza jeśli ktoś trzyma długą historię. To trafia do bazy, ale tylko w 1 kopii.
Te 3 lata to może przesada - nie wiem ile danych można maksymalnie tak trzymać, ale prowadzę w tej kwestii eksperyment :slight_smile: Mam w podobnym sensorze historię okresów włączeń kotła od ogrzewania, też zapisywaną z kluczem=timestamp i wartością w postaci liczby i tekstu, więc zbliżony rozmiar co tu. Obecnie liczy prawie 6800 pozycji.

Sam Rejestrator nie jest dobrym wyborem do długoterminowego przechowywania danych. Łatwo ulega uszkodzeniu i potrafi się nieźle rozrosnąć.
Dane z rejestratora są agregowane i utrwalane w jednogodzinnych odstępach w tabelach statystycznych w tej samej bazie danych. Dane w tabelach statystycznych nie są usuwane, tak jak zwykłe dane rejestratora.

Kod który to opisałem (wyżej) działa poprawnie. ( usunięcie initial: 0.0, spwoduje że po restarcie HA nie będzie zliczał od restartu HA)

Dokładność wskazań ok 100-200 metrów różnicy na dystansie 20km. ( prawdopodobnie, różnica przez ronda i wiele zakrętów)

Po 2 dniach testów, :upside_down_face: znalazłem “gotowiec” nazywa się movement

Wystarczy wypełnić:

Otrzymałem:

2 polubienia

To mocno zaskajująca treść… Czy to oznacza, że w HA mogą istnieć sensory niepodlegające prawom purge_keep_days czy include/exclude zdefiniowanym na sztywno w recorderze?? Jeśli tak jest, to jak je odróżniać od tysięcy innych sensorów?

Gotowe i …genialne!
Dziękuję za podsunięcie rozwiązania :beer:. Muszę to jeszcze potestować ale wygląda na to, że chyba wyczerpuje moje potrzeby w tym zakresie.

Długoterminowe statystyki LTS

  1. Takie sensory muszą zawierać state_class.
template:
  - sensor:
      - name: "Dzienna energia"
        unit_of_measurement: "kWh"
        device_class: energy
        state_class: total_increasing
        state: "{{ states('sensor.daily_energy_offpeak')|float + states('sensor.daily_energy_peak')|float }}"
        availability: "{{ has_value('sensor.daily_energy_offpeak') and has_value('sensor.daily_energy_peak') }}"

  1. Nie ma procesu, który usuwa LTS.
  2. LTS znajdziesz w Narzędzia deweloperskie → Statystyki.
1 polubienie

To konsekwencja faktu, że w atrybucie sensora można stworzyć własny magazyn danych.

Ale to jest IMO opcja dla “użytkownika końcowego”, żeby sobie napisać taki sensor i wyłączyć go z Rejestratora. Sensorów od integracji tak się nie robi, bo duża objętość atrybutów + duża częstotliwość aktualizacji sensora, to najlepszy przepis na “wysadzenie” bazy danych.

Osobiście bardzo lubię tę możliwość: trigger-based template sensor to w praktyce automatyzacja z własną pamięcią na dane Odpada konieczność tworzenia pomocników, które mają tylko coś zapamiętać (bez wprowadzania danych przez użytkownika). Wiele kart ma wsparcie do pokazywania atrybutu zamiast stanu; w ostateczności można zastąpić tekst card_mod’em.

3 polubienia