It’s quite some sensor…
- name: Runway 14/32
<< : &runway_sensor
icon: >
{%- set runway = states(this.entity_id) | int(-9999) %}
mdi:{{ 'arrow-top-thin-circle-outline' if runway > 33.75 else
'arrow-top-left-thin-circle-outline' if runway > 29.25 else
'arrow-left-thin-circle-outline' if runway > 24.75 else
'arrow-bottom-left-thin-circle-outline' if runway > 20.25 else
'arrow-bottom-thin-circle-outline' if runway > 15.75 else
'arrow-bottom-right-thin-circle-outline' if runway > 11.25 else
'arrow-right-thin-circle-outline' if runway > 6.75 else
'arrow-top-right-thin-circle-outline' if runway > 2.25 else
'arrow-top-thin-circle-outline' if runway > -999 else
'crosshairs-question' }}
state: >
{%- set data = state_attr(this.entity_id, 'data') %}
{{- data.runway if data and states(this.entity_id) == 'unknown' else None }}
attributes:
direction: 316
<< : &runway_attributes
data: >
{%- set value_json = {
"time": state_attr('sensor.opensky', 'time') | default(None, true),
"states": state_attr('sensor.opensky', 'states') | default([], true),
} %}
{%- set direction = state_attr(this.entity_id, 'direction') | int(0) %}
{%- set name = (direction/10) | round %}
{%- set ns = namespace({'runway': None}) %}
{%- for aircraft in value_json.states %}
{#- Calculate speed in runway direction #}
{%- set callsign = aircraft[1] | trim %}
{%- set altitude = aircraft[7] | float(0) %}
{%- set latitude = aircraft[6] | float(0) %}
{%- set longitude = aircraft[5] | float(0) %}
{%- set vertical = aircraft[11] | float(0) %}
{%- set velocity = aircraft[9] | float(0) %}
{%- set heading = aircraft[10] | float(0) %}
{%- set lateral = velocity * cos(heading/180*pi - direction/180*pi) %}
{%- if true
and (latitude-50.87) |abs < 0.05
and (longitude-7.14) |abs < 0.1
and (heading-direction)|abs < 5
and altitude > 0
and altitude < 150
and lateral | abs > 50 %}
{%- set ns.callsign = callsign %}
{%- set ns.vertical = vertical %}
{%- set ns.lateral = lateral %}
{%- set ns.velocity = velocity %}
{%- set ns.altitude = altitude %}
{%- set ns.heading = heading %}
{%- set ns.runway = name|string if lateral > 50 else ((name+18)%36)|string %}
{%- endif %}
{%- endfor %}
{{- {
'timestamp': value_json.time,
'datetime': value_json.time | as_datetime | as_local | string,
'callsign': ns.callsign,
'altitude': ns.altitude,
'heading': ns.heading,
'velocity': ns.velocity,
'vertical': ns.vertical,
'lateral': ns.lateral,
'runway': ns.runway,
} if ns.runway else state_attr(this.entity_id, 'data')}}
The idea is to find out, which runway direction is in use, based on a list of aircraft in the area from an opensky sensor.
I am suspecting that the sensor reacts to single helicopters flying in the opposite direction. So, I send mails to myself, when the runway changes:
automation:
- alias: Runway change
id: runway
trigger:
- platform: state
entity_id: sensor.runway_14_32
to: '14'
to: '32'
action:
- service: notify.email
data_template:
title: Runway changed
message: >
{{trigger}}
Ideally, I would see the same callsign twice: first in the erroneous change as the to_state, and second as the correcting change in the from_state. But the from_state of the second change already hold the new callsign in its attribute.
I think, I could achieve my goal, if I trigger not on the state but on the attribute data.runway
itself. And I will try that.
However, that led me to the general issue of: what is the from_state really? I worked with a modelling language called Modelica quite some time. It features an operator pre()
, which returns the state prior to something called the state iteration: when a state changes and triggers immediate change of another state, all those changes are considered as one change altogether. Obviously, pre
and from_state
are currently different things. But I would like to know, if there is a way to achieve the same result. After all, the from_state in my case is something very artificial and actually just a relict of the way my sensor is technically implemented.
I’m not saying, the semantics of from_state
should be changed. Maybe they should. But I’m interested, if there is a way to get the last state before a series of instantaneous state changes started.