So this thread is a WIP but I’ve got a working configuration and example to share with the Community. I’m hoping this will help someone. It was a learning process for me and in the end, quite enjoyable (even if I’m waaay off base here) but then I really like problem solving and the satisfaction of resolving problems.
Being an old Perl guy, learning Python wasn’t too hard but I did have to Google a lot to find what I was Perl-izing in my head to work. Please be gentle
Requirements:
- Zanzito (w/o OwnTracks emulation enabled.) My test devices also include iOS and works fine with it too.
- At least one zone named ‘home’
- HomeAssistant 0.68.0 (probably backwards compatible to a point)
Problem:
So like everyone else, I was using OwnTracks but got tired of the app registering Zone transitions most of the time but not passing the coordinates to HomeAssistant consistently. I would get false positives (even with max_gps_accuracy) or it simply wouldn’t register sometimes.
You may be asking yourself why not using OwnTracks emulation. Couple of reasons; if I wanted OwnTracks, I’d use it and two, documentation stated it was no longer necessary to use it so why use it?
Like some of you, Home Automation started off as a curiosity but became addictive to the point where Wife Approval became important. It was working for us to the point where my Wife began to expect it to work reliably. Was a challenge for me to get this working to her approval. I tried GPSLogger but since I have Android and she has iPhone, it was a challenge finding something cross platform that worked reliably.
I had seen people talking about Zanzito and the iOS HomeAssistant application and tried it. Surprisingly, the iOS application worked pretty darn reliably and Zanzito was too. This was back (heh… talking like it was a lifetime ago…) with Home Assistant 0.67.x – As I started upgrading, I noticed I was getting a lot of false positives (or lack of zone acknowledgement altogether) and once again I found myself staring down the business-end of my Wife’s approval.
So amid promises of “I know what’s happening and how to fix it” (I didn’t), I began to investigate the problem itself (and highly motivated to figure it out) and discovered two issues, mostly with me:
- All my device_trackers were lumped under one known_device entry (WiFi and GPS) and resolved as a binary_sensor. This in itself wasn’t an issue but I ended up separating them to help diagnose into individual trackers.
- GPS drift with Android. I have an ASUS Zenfone 3 running Oreo 8.0.0 - while not uncommon was causing me uncommon grief. Sometimes the drift (mostly indoors and never while moving) would be only a few meters up to a kilometre or more. Definitely an issue for me and how my configuration works which is based entirely around Zones and home vs not_home.
So I pursued this issue to no end. It may sound like I solved this in day but be assured this resolution was easily a couple of weeks in effort, mostly around identifying and troubleshooting the issue.
Whew. It was a heavy lift but I believe I have a working solution which I present to you, the Home Assistant Community!
Resolution:
Below you will see reference to device_tracker.chris - This is a meta tracker, it exists in my known_devices.yaml as seen below. There is also a device_tracker.chris_ping and device_tracker.chris_gps - All three have a friendly_name of ‘Chris’ (this is important for the script to work - so keep them consistent if you use something similar.)
chris:
hide_if_away: false
icon: mdi:cellphone-android
mac:
name: Chris
picture:
track: true
vendor: Meta Tracker
- Enable Python scripts in configuration.yaml
python_script:
- Create Zanzito MQTT device_tracker to read GPS data in from my phone:
- platform: mqtt_json
qos: 0
devices:
chris_gps: zanzito/chris/location
- Create automation for zones. Mine are combined and maybe a bit fancy. I send notifications to FB Messenger in mine (Wife and I) as well, I configure Zanzito to use high-precision GPS when leaving a zone and low-precision GPS when I enter a zone (credit goes to @scorpio862 for this part and to @anon43302295 for helping me with action-conditions) to save battery:
#### Presence: Zone
- alias: Tracker Enter Zone
trigger:
- platform: zone
event: enter
zone: zone.home
entity_id: device_tracker.chris, device_tracker.val
- platform: zone
event: enter
zone: zone.work_chris
entity_id: device_tracker.chris, device_tracker.val
- platform: zone
event: enter
zone: zone.work_val
entity_id: device_tracker.chris, device_tracker.val
action:
- service: python_script.calc_gps_coords
data_template:
entity_id: '{{ trigger.entity_id }}'
meta_entity: '{{ trigger.to_state.attributes.friendly_name }}'
zone_entity: '{{ trigger.zone.entity_id }}'
zone_data: '{{ trigger.event }}'
- service: notify.fbmsg
data_template:
message: "{{ trigger.to_state.attributes.friendly_name }} arrived at {{ trigger.zone.attributes.friendly_name }} at {{ now().strftime('%Y-%m-%d %H:%M') }}"
target:
- !secret fbmsg_chris
- !secret fbmsg_val
- condition: state
entity_id: device_tracker.chris
state: 'home'
- service: mqtt.publish
data_template:
topic: "zanzito/chris/set_prefs"
payload: >-
{
"location_high_precision": false
}
- alias: Tracker Leave Zone
trigger:
- platform: zone
event: leave
zone: zone.home
entity_id: device_tracker.chris, device_tracker.val
- platform: zone
event: leave
zone: zone.work_chris
entity_id: device_tracker.chris, device_tracker.val
- platform: zone
event: leave
zone: zone.work_val
entity_id: device_tracker.chris, device_tracker.val
action:
- service: python_script.calc_gps_coords
data_template:
entity_id: '{{ trigger.entity_id }}'
meta_entity: '{{ trigger.to_state.attributes.friendly_name }}'
zone_entity: '{{ trigger.zone.entity_id }}'
zone_data: '{{ trigger.event }}'
- service: notify.fbmsg
data_template:
message: "{{ trigger.to_state.attributes.friendly_name }} left {{ trigger.zone.attributes.friendly_name }} at {{ now().strftime('%Y-%m-%d %H:%M') }}"
target:
- !secret fbmsg_chris
- !secret fbmsg_val
- condition: state
entity_id: device_tracker.chris
state: 'not_home'
- service: mqtt.publish
data_template:
topic: "zanzito/chris/set_prefs"
payload: >-
{
"location_high_precision": true
}
- Create an automation that will update GPS data when received:
- alias: GPS Filter
trigger:
- platform: state
entity_id: device_tracker.chris_gps, device_tracker.val
action:
- service: python_script.calc_gps_coords
data_template:
entity_id: '{{ trigger.entity_id }}'
meta_entity: '{{ trigger.to_state.attributes.friendly_name }}'
zone_entity: 'None'
zone_data: 'None'
-
If it doesn’t exist already, create a directory named python_scripts in your ~/.homeassistant directory. Assumptions are made that permissions jives, etc. Copy the script to this directory (at bottom of post.)
-
Restart Home Assistant.
-
Troubleshoot (if need be,) and Profit.
What it does:
- When a zone transition occurs, GPS data is updated via the zone Enter and Leave automations.
- When GPS data is available, the automation “GPS Filter” will execute the script to update the meta tracker(s).
When the tracker is updated, two considerations are taken into account:
- Is this a zone transition? If so, lock the meta tracker to exact coorindates of this zone when entering it. Create meta data called ‘meters’ and set gps_accuracy to 1 (absolute accurate without being 0.)
- Is a GPS coordinates update? We will only accept differences in meters if the difference is less-than existing meter calculation. This prevents drift and as differences get smaller, it becomes harder and harder to drift away from the zones actual coordinates. So when you’re ‘home’ you stay ‘home’ until the difference in meters exceeds a pre-determined amount (200m–based off recommendations for max_gps_accuracy for OwnTracks. I believe iOS uses 500m for ‘significant movement’ measurements which is pre-defined by the OS itself – I realize the intent of max_gps_accuracy is exactly the opposite of how I use it here.)
Measurements are made using the Haversine formula. This math is beyond my understanding (beyond basic grasp of what it does) and credit here goes to an Internet author named Nathan Rooy who published the Python code of this formula on his website, here:
Additional credit goes go @oakbrad who posted about a meta_tracker python script that I came across and then proceeded to pick apart his Git in cobbling this solution together:
Without both of these authors hard work, I wouldn’t be in the position I find myself today. Many thanks to these folks who shared their work with the world (and ultimately, me.)
For more information on the logic I used for this, please read the script in detail. For a finale before the script itself, I present some logging (sanitized for privacy) to validate my claims and demonstrates GPS drift when stationary without line-of-site of satellites
[homeassistant.components.python_script.calc_gps_coords.py] GPS met threshold for Chris: 371.38 meters (200) [new=x,y old=x,y acc=72 state=not_home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS zone transition: Chris (leave) [zone=Home lat=x long=y radius=25]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 186.91 meters (200) [new=x,y old=x,y acc=72 meters=1000.00 state=Home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 23.78 meters (200) [new=x,y old=x,y acc=26 meters=186.91 state=not_home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS met threshold for Chris: 365.60 meters (200) [new=x,y old=x,y acc=46 state=not_home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 145.16 meters (200) [new=x,y old=x,y acc=46 meters=365.60 state=not_home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 22.79 meters (200) [new=x,y old=x,y acc=24 meters=145.16 state=not_home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS zone transition: Chris (enter) [zone=Home lat=x long=y radius=25]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 58.63 meters (200) [new=x,y old=x,y acc=1 meters=0.50 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 52.29 meters (200) [new=x,y old=x,y acc=1 meters=0.50 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 3.08 meters (200) [new=x,y old=x,y acc=1 meters=0.50 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 2.04 meters (200) [new=x,y old=x,y acc=1 meters=0.50 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 3.17 meters (200) [new=x,y old=x,y acc=1 meters=0.50 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 0.14 meters (200) [new=x,y old=x,y acc=1 meters=0.50 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS zone transition: Val (leave) [zone=Home lat=x long=y radius=25]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 27.78 meters (200) [new=x,y old=x,y acc=23 meters=0.14 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 17.75 meters (200) [new=x,y old=x,y acc=23 meters=0.14 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 8.73 meters (200) [new=x,y old=x,y acc=23 meters=0.14 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 11.60 meters (200) [new=x,y old=x,y acc=23 meters=0.14 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 7.49 meters (200) [new=x,y old=x,y acc=23 meters=0.14 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 12.76 meters (200) [new=x,y old=x,y acc=23 meters=0.14 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 18.01 meters (200) [new=x,y old=x,y acc=23 meters=0.14 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 0.03 meters (200) [new=x,y old=x,y acc=23 meters=0.14 state=home]
[homeassistant.components.python_script.calc_gps_coords.py] GPS below threshold for Chris: 3.84 meters (200) [new=x,y old=x,y acc=22 meters=0.03 state=home]
And finally, the script: