#!/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, 0, properties=pubproperties)
            if debug:
                print("*  client.publish rc=", rc)
            lastmsg = msg

print("gpsd stream ended", file=sys.stderr)
sys.exit(1)
