Files
vanlink/gps/van-gps-owntracks
2026-08-02 17:09:51 -04:00

162 lines
5.0 KiB
Python

#!/usr/bin/python3 -u
# Publish gpsd fixes as OwnTracks location messages over MQTT.
# Ported from gps_to_owntracks.py (wayback era): gpsdclient -> python3-gps
# (apt-only deps), paho 1.x -> 2.x callback API, hard-coded broker secrets ->
# /etc/van-gps/config.json. Exits on MQTT disconnect/DNS failure by design —
# systemd Restart=always reconnects with a fresh session.
import sys
import json
import signal
import os
import time
import socket
import gps as gpsd
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes
from datetime import datetime
from math import radians, sin, cos, acos
debug = False # toggle at runtime with SIGHUP
config_path = sys.argv[1] if len(sys.argv) > 1 else "/etc/van-gps/config.json"
with open(config_path) as f:
cfg = json.load(f)
version = cfg.get("mqtt_version", "3") # '3' or '5'
mytransport = "tcp" # or 'websockets'
broker = cfg["broker"]
myport = cfg.get("port", 1883)
mq_user = cfg["username"]
mq_pw = cfg["password"]
mq_id = cfg.get("client_id", "vanq-wan")
gpsd_host = cfg.get("gpsd_host", "127.0.0.1")
idle_delay = cfg.get("idle_delay_s", 600) # seconds between messages when not moving
keep_alive = idle_delay + 60
distance_move_trigger = cfg.get("move_trigger_m", 250) # meters of movement that triggers a message
mytopic = cfg.get("topic", "owntracks/rv/gps")
tid = cfg.get("tid", "rv")
def handler(signum, frame):
global debug
debug = not debug
print('debug is now', debug)
def great_circle(llon1, llat1, llon2, llat2):
lon1, lat1, lon2, lat2 = map(radians, [llon1, llat1, llon2, llat2])
a = sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon1 - lon2)
if a >= 1.0:
return 0
try:
res = int(6371000 * (acos(a)))
except ValueError as e:
print("gc error: ", e)
print(" coords: ", llon1, llat1, llon2, llat2)
return 0
return res
def on_disconnect(client, userdata, flags, reason_code, properties):
print("mqtt disconnect:", reason_code)
os._exit(1)
def connect():
if version == '5':
properties = Properties(PacketTypes.CONNECT)
properties.SessionExpiryInterval = 30 * 60 # in seconds
client.connect(broker,
port=myport,
clean_start=mqtt.MQTT_CLEAN_START_FIRST_ONLY,
properties=properties,
keepalive=keep_alive)
elif version == '3':
client.connect(broker, port=myport, keepalive=keep_alive)
print("mqtt connect")
#
# Main
#
print("van-gps-owntracks start", time.asctime(), debug, os.getpid())
signal.signal(signal.SIGHUP, handler)
if version == '5':
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=mq_id,
transport=mytransport,
protocol=mqtt.MQTTv5)
if version == '3':
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=mq_id,
transport=mytransport,
protocol=mqtt.MQTTv311,
clean_session=True)
client.username_pw_set(mq_user, mq_pw)
client.on_disconnect = on_disconnect
try:
connect()
except socket.gaierror as e:
print("could not connect mqtt, %s" % e)
sys.exit(1)
client.loop_start()
pubproperties = Properties(PacketTypes.PUBLISH)
pubproperties.MessageExpiryInterval = keep_alive # in seconds; MQTT5 only
session = gpsd.gps(host=gpsd_host)
session.stream(gpsd.WATCH_ENABLE)
lastmsg = {'tst': 0, 'tid': tid, 'lat': 0, 'lon': 0}
while session.read() == 0:
result = session.data
if not result or result.get('class') != 'TPV':
continue
msg = {'_type': 'location', 'tid': tid, 't': 'p'}
if result.get('mode', 0) <= 1:
continue
if not ('lat' in result and 'lon' in result):
continue
msg['lat'] = result['lat']
msg['lon'] = result['lon']
if 'alt' in result:
msg['alt'] = int(result['alt'])
if 'speed' in result:
msg['vel'] = int(result['speed']) * 3.6
if 'eph' in result:
msg['acc'] = int(result['eph'])
if 'track' in result:
msg['cog'] = int(result['track'])
else:
continue
if 'time' not in result:
continue
msg['tst'] = int(datetime.fromisoformat(result['time']).timestamp())
jmsg = json.dumps(msg)
if lastmsg != msg:
gc = great_circle(lastmsg['lat'], lastmsg['lon'], msg['lat'], msg['lon'])
if debug:
print("\rgc is ", gc, ' age is ', msg['tst'] - lastmsg['tst'], " ", end="")
if msg['tst'] - lastmsg['tst'] >= idle_delay or gc > distance_move_trigger:
if debug:
print('* ', gc, jmsg)
print('* ', mytopic, pubproperties)
rc = client.publish(mytopic, jmsg, qos=1, retain=True, properties=pubproperties)
if debug:
print("* client.publish rc=", rc)
lastmsg = msg
print("gpsd stream ended", file=sys.stderr)
sys.exit(1)