From e4a3022c820421cfa61b3af1ade50a414d1a91ad Mon Sep 17 00:00:00 2001 From: Ville Ranki Date: Sun, 3 May 2020 23:27:39 +0300 Subject: [PATCH] Added flog module --- README.md | 36 ++++++ modules/common/pollingservice.py | 7 +- modules/flog.py | 188 +++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 modules/flog.py diff --git a/README.md b/README.md index 4257b97..a9b3681 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,42 @@ Commands: * !wa [query] - Query wolfram alpha * !wa appid [appid] - Set appid (must be done as bot owner) +### OGN Field Log (FLOG) + +Open Glider Network maintains a unified tracking platform for gliders, drones and other aircraft. + +Read more about OGN at https://www.glidernet.org/ + +FLOG module supports showing field logs for OGN receivers and can display live +field log in a room. + +It uses KTrax service. Please read https://ktrax.kisstech.ch/api-usage and request a +API key for yourself. + +Real life field log output looks something like: + +``` +Flights at ESGE today: +12:02-12:13 0:11 290m Ultralight SE-VSL +12:17-13:18 1:01 1080m ASG-32 MI SE-SKV VH +12:26-13:52 1:25 1090m DG-1000 SE-UMY VF +12:34-16:32 3:57 1710m Duo Discus xlt SE-UXF XF +``` + +Commands and examples: + +(You must be room admin for all commands, except apikey which requires +bot ownership) + +* !flog apikey FLOG-123456 - Set api key +* !flog station EFJM - set the default station to track for this room +* !flog rmstation - remove station from this room +* !flog - Show field flog for the room's station (can be used by any user) +* !flog status - print status of this room +* !flog live - enable live field log for this room +* !flog rmlive - disable live field log for this room +* !flog timezone 3 - set timezone (relative to UTC, see API docs) + ## Bot setup * Create a Matrix user diff --git a/modules/common/pollingservice.py b/modules/common/pollingservice.py index ff357d9..6108df3 100644 --- a/modules/common/pollingservice.py +++ b/modules/common/pollingservice.py @@ -39,8 +39,11 @@ class PollingService(BotModule): else: self.logger.warning(f'Bot is no longer in room {roomid} - deleting it from {self.service_name} room list') delete_rooms.append(roomid) - for roomid in delete_rooms: - self.account_rooms.pop(roomid, None) + + if len(delete_rooms): + for roomid in delete_rooms: + self.account_rooms.pop(roomid, None) + bot.save_settings() self.first_run = False diff --git a/modules/flog.py b/modules/flog.py new file mode 100644 index 0000000..2ec8b28 --- /dev/null +++ b/modules/flog.py @@ -0,0 +1,188 @@ +import sys +import traceback +import urllib.request +import json +import time +import datetime + +from datetime import datetime, timedelta +from random import randrange + +from modules.common.module import BotModule + +class MatrixModule(BotModule): + def __init__(self, name): + super().__init__(name) + self.service_name = 'FLOG' + self.station_rooms = dict() # Roomid -> ogn station + self.live_rooms = [] # Roomid's with live enabled + self.room_timezones = dict() # Roomid -> timezone + self.api_key = '' + self.logged_flights = [] + self.logged_flights_date = "" + self.first_poll = True + + async def matrix_poll(self, bot, pollcount): + if len(self.api_key) > 0: + if pollcount % (6 * 5) == 0: # Poll every 5 min + await self.poll_implementation(bot) + + async def poll_implementation(self, bot): + for roomid in self.live_rooms: + station = self.station_rooms[roomid] + data = self.get_flights(station, self.room_timezones.get(roomid, 0)) + + # Date changed - reset flight count + if data["begin_date"] != self.logged_flights_date: + self.logged_flights = [] + self.logged_flights_date = data["begin_date"] + + flights = [] + + for sortie in data["sorties"]: + # Don't show towplanes + if sortie["type"] != 2: + # Count only landed gliders + if sortie["ldg"]["time"] != "": + flights.append( + { + "takeoff": sortie["tkof"]["time"], + "landing": sortie["ldg"]["time"], + "duration": sortie["dt"], + "glider": self.glider2string(sortie), + "altitude": str(sortie["dalt"]), + "seq": sortie["seq"] + }) + for flight in flights: + if flight["seq"] not in self.logged_flights: + if not self.first_poll: + await bot.send_text(bot.get_room_by_id(roomid), flight["takeoff"] + "-" + flight["landing"] + " (" + flight["duration"] + ") - " + flight["altitude"] + "m " + flight["glider"]) + self.logged_flights.append(flight["seq"]) + self.first_poll = False + + def get_flights(self, station, timezone): + timenow = time.localtime(time.time()) + today = str(timenow[0]) + "-" + str(timenow[1]) + "-" + str(timenow[2]) + # Example 'https://ktrax.kisstech.ch/backend/logbook?db=sortie&query_type=ap&tz=3&id=ESGE&dbeg=2020-05-03&dend=2020-05-03' + log_url = f'https://ktrax.kisstech.ch/backend/logbook?db=sortie&query_type=ap&tz={timezone}&id={station}&dbeg={today}&dend={today}&apikey={self.api_key}' + response = urllib.request.urlopen(log_url) + data = json.loads(response.read().decode("utf-8")) + # print(json.dumps(data, sort_keys=True, indent=4)) + return data + + def glider2string(self, sortie): + actype = sortie["actype"] + cs = sortie["cs"] + cn = sortie["cn"] + if cs == "-": + cs = "" + if cn == "-": + cn = "" + + if actype == "" and cs == "" and cn == "": + return "????" + return (actype + " " + cs + " " + cn).strip() + + + async def matrix_message(self, bot, room, event): + args = event.body.split() + if len(args) == 1: + if room.room_id in self.station_rooms: + station = self.station_rooms[room.room_id] + data = self.get_flights(station, self.room_timezones.get(room.room_id, 0)) + out = "" + if len(data["sorties"]) == 0: + out = "No known flights today at " + station + else: + out = "Flights at " + station.upper() + " today:\n" + for sortie in data["sorties"]: + # Don't show towplanes + if sortie["type"] != 2: + if sortie["ldg"]["time"] == "": + sortie["ldg"]["time"] = u" \u2708 " + else: + sortie["ldg"]["time"] = "-" + sortie["ldg"]["time"] + if sortie["ldg"]["loc"] != sortie["tkof"]["loc"]: + sortie["tkof"]["time"] = sortie["tkof"]["time"] + "(" + sortie["tkof"]["loc"] + ")" + sortie["ldg"]["time"] = sortie["ldg"]["time"] + "(" + sortie["ldg"]["loc"] + ") " + + out = out + sortie["tkof"]["time"] + sortie["ldg"]["time"] + " " + sortie["dt"] + " " + str(sortie["dalt"]) + "m " + self.glider2string(sortie) + "\n" + await bot.send_text(room, out) + else: + await bot.send_text(room, 'No OGN station set for this room - set it first.') + + elif len(args) == 2: + if args[1] == 'rmstation': + bot.must_be_admin(room, event) + del self.station_rooms[room.room_id] + self.live_rooms.remove(room.room_id) + await bot.send_text(room, f'Cleared OGN station for this room') + + if args[1] == 'status': + bot.must_be_admin(room, event) + await bot.send_text(room, f'OGN station for this room: {self.station_rooms.get(room.room_id)}, live updates enabled: {room.room_id in self.live_rooms}, timezone: {self.room_timezones.get(room.room_id, 0)} api key is set: {len(self.api_key) > 0}') + + if args[1] == 'poll': + bot.must_be_admin(room, event) + await self.poll_implementation(bot) + + if args[1] == 'live': + bot.must_be_admin(room, event) + self.live_rooms.append(room.room_id) + bot.save_settings() + await bot.send_text(room, f'Sending live updates for station {self.station_rooms.get(room.room_id)} to this room') + + if args[1] == 'rmlive': + bot.must_be_admin(room, event) + self.live_rooms.remove(room.room_id) + bot.save_settings() + await bot.send_text(room, f'Not sending live updates for station {self.station_rooms.get(room.room_id)} to this room anymore') + + elif len(args) == 3: + if args[1] == 'station': + bot.must_be_admin(room, event) + + station = args[2] + self.station_rooms[room.room_id] = station + self.logger.info(f'Station now for this room {self.station_rooms.get(room.room_id)}') + + bot.save_settings() + await bot.send_text(room, f'Set OGN station {station} to this room') + + elif args[1] == 'apikey': + bot.must_be_owner(event) + + self.api_key = args[2] + bot.save_settings() + await bot.send_text(room, 'Api key set') + + elif args[1] == 'timezone': + bot.must_be_admin(room, event) + tz = int(args[2]) + self.room_timezones[room.room_id] = tz + bot.save_settings() + await bot.send_text(room, f'Timezone set to {tz}') + + def get_settings(self): + data = super().get_settings() + data['apikey'] = self.api_key + data['station_rooms'] = self.station_rooms + data['live_rooms'] = self.live_rooms + data['room_timezones'] = self.room_timezones + return data + + def set_settings(self, data): + super().set_settings(data) + if data.get('station_rooms'): + self.station_rooms = data['station_rooms'] + if data.get('live_rooms'): + self.live_rooms = data['live_rooms'] + if data.get('room_timezones'): + self.room_timezones = data['room_timezones'] + if data.get('apikey'): + self.api_key = data['apikey'] + if self.api_key and len(self.api_key) == 0: + self.api_key = None + + def help(self): + return ('Open Glider Network Field Log')