From 86e606a22e527b058b2dea3a9c3508cbbbafe0e6 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 9 Sep 2023 00:45:33 +0200 Subject: [PATCH 01/40] make sure we are working with the right type --- owrx/adsb/modes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/owrx/adsb/modes.py b/owrx/adsb/modes.py index 9fc37c8b..802f553a 100644 --- a/owrx/adsb/modes.py +++ b/owrx/adsb/modes.py @@ -30,9 +30,13 @@ class AdsbLocation(IncrementalUpdate, AirplaneLocation): super().__init__(message) def update(self, previousLocation: Location): - history = previousLocation.history - now = datetime.now() - history = [p for p in history if now - p["timestamp"] < self.getTTL()] + if isinstance(previousLocation, AdsbLocation): + history = previousLocation.history + now = datetime.now() + history = [p for p in history if now - p["timestamp"] < self.getTTL()] + else: + history = [] + history += [{ "timestamp": self.timestamp, "props": self.props, From 24a04e297a2e205ff7b5078b7a99a71243cab894 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 9 Sep 2023 00:45:58 +0200 Subject: [PATCH 02/40] normalize icaos as uppercase --- owrx/aeronautical.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owrx/aeronautical.py b/owrx/aeronautical.py index 3665ef13..61056c13 100644 --- a/owrx/aeronautical.py +++ b/owrx/aeronautical.py @@ -20,7 +20,7 @@ class AirplaneLocation(LatLngLocation): class IcaoSource(Source): def __init__(self, icao: str, humanReadable: str = None): - self.icao = icao + self.icao = icao.upper() self.humanReadable = humanReadable def getKey(self) -> str: From 2595f92883a1601888d3263c6131f1d1e9c39e97 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 9 Sep 2023 18:54:56 +0200 Subject: [PATCH 03/40] respect timestamps for hfdl locations --- owrx/hfdl/dumphfdl.py | 18 +++++++++++++++++- owrx/map.py | 19 ++++++++++++------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/owrx/hfdl/dumphfdl.py b/owrx/hfdl/dumphfdl.py index 80e1cb9e..52028d6d 100644 --- a/owrx/hfdl/dumphfdl.py +++ b/owrx/hfdl/dumphfdl.py @@ -2,6 +2,7 @@ from pycsdr.modules import ExecModule from pycsdr.types import Format from owrx.aeronautical import AirplaneLocation, AcarsProcessor, IcaoSource from owrx.map import Map, Source +from datetime import datetime, timezone, timedelta import logging @@ -83,4 +84,19 @@ class HFDLMessageParser(AcarsProcessor): source = HfdlSource(hfnpdu["flight_id"]) else: source = IcaoSource(icao, humanReadable=hfnpdu["flight_id"]) - Map.getSharedInstance().updateLocation(source, HfdlAirplaneLocation(msg), "HFDL") + if "utc_time" in hfnpdu: + ts = self.processTimestamp(**hfnpdu["utc_time"]) + elif "time" in hfnpdu: + ts = self.processTimestamp(**hfnpdu["time"]) + else: + ts = None + Map.getSharedInstance().updateLocation(source, HfdlAirplaneLocation(msg), "HFDL", timestamp=ts) + + def processTimestamp(self, hour, min, sec) -> datetime: + now = datetime.now(timezone.utc) + t = now.replace(hour=hour, minute=min, second=sec, microsecond=0) + # if we have moved the time to the future, it's most likely that we're close to midnight and the time + # we received actually refers to yesterday + if t > now: + t -= timedelta(days=1) + return t diff --git a/owrx/map.py b/owrx/map.py index 52a718e9..785c4909 100644 --- a/owrx/map.py +++ b/owrx/map.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from owrx.config import Config from owrx.bands import Band from abc import abstractmethod, ABC, ABCMeta @@ -92,19 +92,24 @@ class Map(object): except ValueError: pass - def updateLocation(self, source: Source, loc: Location, mode: str, band: Band = None): - ts = datetime.now() + def updateLocation(self, source: Source, loc: Location, mode: str, band: Band = None, timestamp: datetime = None): + if timestamp is None: + timestamp = datetime.now(timezone.utc) + else: + # if we get an external timestamp, make sure it's not already expired + if datetime.now(timezone.utc) - loc.getTTL() > timestamp: + return key = source.getKey() with self.positionsLock: if isinstance(loc, IncrementalUpdate) and key in self.positions: loc.update(self.positions[key]["location"]) - self.positions[key] = {"source": source, "location": loc, "updated": ts, "mode": mode, "band": band} + self.positions[key] = {"source": source, "location": loc, "updated": timestamp, "mode": mode, "band": band} self.broadcast( [ { "source": source.__dict__(), "location": loc.__dict__(), - "lastseen": ts.timestamp() * 1000, + "lastseen": timestamp.timestamp() * 1000, "mode": mode, "band": band.getName() if band is not None else None, } @@ -113,7 +118,7 @@ class Map(object): def touchLocation(self, source: Source): # not implemented on the client side yet, so do not use! - ts = datetime.now() + ts = datetime.now(timezone.utc) key = source.getKey() with self.positionsLock: if key in self.positions: @@ -126,7 +131,7 @@ class Map(object): # TODO broadcast removal to clients def removeOldPositions(self): - now = datetime.now() + now = datetime.now(timezone.utc) with self.positionsLock: to_be_removed = [ From 32ee7904f901c9d0fbd0c1d89f7ee8a00f68df1c Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 9 Sep 2023 19:29:13 +0200 Subject: [PATCH 04/40] add recommended packages --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index ee4108fd..b97f6ae5 100644 --- a/debian/control +++ b/debian/control @@ -11,6 +11,6 @@ Vcs-Git: https://github.com/jketterl/openwebrx.git Package: openwebrx Architecture: all Depends: adduser, python3 (>= 3.5), python3-pkg-resources, owrx-connector (>= 0.7), python3-csdr (>= 0.18), ${python3:Depends}, ${misc:Depends} -Recommends: python3-digiham (>= 0.6), direwolf (>= 1.4), wsjtx, js8call, runds-connector (>= 0.2), hpsdrconnector, aprs-symbols, m17-demod, js8call, python3-js8py (>= 0.2), nmux (>= 0.18), codecserver (>= 0.1), msk144decoder, dump1090-fa-minimal +Recommends: python3-digiham (>= 0.6), direwolf (>= 1.4), wsjtx, js8call, runds-connector (>= 0.2), hpsdrconnector, aprs-symbols, m17-demod, js8call, python3-js8py (>= 0.2), nmux (>= 0.18), codecserver (>= 0.1), msk144decoder, dump1090-fa-minimal, dumphfdl, dumpvdl2, rtl-433 Description: multi-user web sdr Open source, multi-user SDR receiver with a web interface From c3caa1290d410b6332590032d1305c9587d8642c Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 10 Sep 2023 00:09:49 +0200 Subject: [PATCH 05/40] don't differentiate between "human-readable" and "flight" --- htdocs/map.js | 14 +++++++++----- owrx/aeronautical.py | 10 +++++----- owrx/hfdl/dumphfdl.py | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/htdocs/map.js b/htdocs/map.js index 3e213f36..33fe8d74 100644 --- a/htdocs/map.js +++ b/htdocs/map.js @@ -404,17 +404,21 @@ $(function(){ }; var linkifyAircraft = function(source, identification) { - var aircraftString = identification || source.humanReadable || source.flight || source.icao; + var aircraftString = identification || source.flight || source.icao; var link = false; switch (aircraft_tracking_service) { case 'flightaware': - if (!source.icao) break; - link = 'https://flightaware.com/live/modes/' + source.icao; - if (identification) link += "/ident/" + identification - link += '/redirect'; + if (source.icao) { + link = 'https://flightaware.com/live/modes/' + source.icao; + if (identification) link += "/ident/" + identification + link += '/redirect'; + } else if (source.flight) { + link = 'https://flightaware.com/live/flight/' + source.flight; + } break; case 'planefinder': if (identification) link = 'https://planefinder.net/flight/' + identification; + if (source.flight) link = 'https://planefinder.net/flight/' + source.flight; break; } if (link) { diff --git a/owrx/aeronautical.py b/owrx/aeronautical.py index 61056c13..22bb6f93 100644 --- a/owrx/aeronautical.py +++ b/owrx/aeronautical.py @@ -19,17 +19,17 @@ class AirplaneLocation(LatLngLocation): class IcaoSource(Source): - def __init__(self, icao: str, humanReadable: str = None): + def __init__(self, icao: str, flight: str = None): self.icao = icao.upper() - self.humanReadable = humanReadable + self.flight = flight def getKey(self) -> str: return "icao:{}".format(self.icao) def __dict__(self): d = {"icao": self.icao} - if self.humanReadable is not None: - d["humanReadable"] = self.humanReadable + if self.flight is not None: + d["flight"] = self.flight return d @@ -67,7 +67,7 @@ class AcarsProcessor(JsonParser, metaclass=ABCMeta): "altitude": basic_report["alt"] } if icao is not None: - source = IcaoSource(icao, humanReadable=flight_id) + source = IcaoSource(icao, flight=flight_id) else: source = AcarsSource(flight_id) Map.getSharedInstance().updateLocation( diff --git a/owrx/hfdl/dumphfdl.py b/owrx/hfdl/dumphfdl.py index 52028d6d..fb38df2d 100644 --- a/owrx/hfdl/dumphfdl.py +++ b/owrx/hfdl/dumphfdl.py @@ -83,7 +83,7 @@ class HFDLMessageParser(AcarsProcessor): if icao is None: source = HfdlSource(hfnpdu["flight_id"]) else: - source = IcaoSource(icao, humanReadable=hfnpdu["flight_id"]) + source = IcaoSource(icao, flight=hfnpdu["flight_id"]) if "utc_time" in hfnpdu: ts = self.processTimestamp(**hfnpdu["utc_time"]) elif "time" in hfnpdu: From 3459d00a8aafc511c8bc281f9d5a13b5a300d67d Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 10 Sep 2023 03:16:47 +0200 Subject: [PATCH 06/40] try to parse flight numbers better --- owrx/aeronautical.py | 8 +++++++- owrx/hfdl/dumphfdl.py | 7 ++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/owrx/aeronautical.py b/owrx/aeronautical.py index 22bb6f93..44c0f240 100644 --- a/owrx/aeronautical.py +++ b/owrx/aeronautical.py @@ -1,6 +1,7 @@ from owrx.map import Map, LatLngLocation, Source from csdr.module import JsonParser from abc import ABCMeta +import re class AirplaneLocation(LatLngLocation): @@ -45,9 +46,11 @@ class AcarsSource(Source): class AcarsProcessor(JsonParser, metaclass=ABCMeta): + flightRegex = re.compile("^([0-9A-Z]{2})0*([0-9A-Z]+$)") + def processAcars(self, acars: dict, icao: str = None): if "flight" in acars: - flight_id = acars["flight"] + flight_id = self.processFlight(acars["flight"]) elif "reg" in acars: flight_id = acars['reg'].lstrip(".") else: @@ -73,3 +76,6 @@ class AcarsProcessor(JsonParser, metaclass=ABCMeta): Map.getSharedInstance().updateLocation( source, AirplaneLocation(msg), "ACARS over {}".format(self.mode) ) + + def processFlight(self, raw): + return self.flightRegex.sub(r"\g<1>\g<2>", raw) diff --git a/owrx/hfdl/dumphfdl.py b/owrx/hfdl/dumphfdl.py index fb38df2d..2eb1d96f 100644 --- a/owrx/hfdl/dumphfdl.py +++ b/owrx/hfdl/dumphfdl.py @@ -75,15 +75,16 @@ class HFDLMessageParser(AcarsProcessor): if "pos" in hfnpdu: pos = hfnpdu["pos"] if abs(pos['lat']) <= 90 and abs(pos['lon']) <= 180: + flight = self.processFlight(hfnpdu["flight_id"]) msg = { "lat": pos["lat"], "lon": pos["lon"], - "flight": hfnpdu["flight_id"] + "flight": flight } if icao is None: - source = HfdlSource(hfnpdu["flight_id"]) + source = HfdlSource(flight) else: - source = IcaoSource(icao, flight=hfnpdu["flight_id"]) + source = IcaoSource(icao, flight=flight) if "utc_time" in hfnpdu: ts = self.processTimestamp(**hfnpdu["utc_time"]) elif "time" in hfnpdu: From af5b16672a7c292fd901017a60978c0ba891230b Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 10 Sep 2023 18:40:44 +0200 Subject: [PATCH 07/40] don't send location to map if we have no id at all --- owrx/hfdl/dumphfdl.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/owrx/hfdl/dumphfdl.py b/owrx/hfdl/dumphfdl.py index 2eb1d96f..204696b0 100644 --- a/owrx/hfdl/dumphfdl.py +++ b/owrx/hfdl/dumphfdl.py @@ -76,22 +76,27 @@ class HFDLMessageParser(AcarsProcessor): pos = hfnpdu["pos"] if abs(pos['lat']) <= 90 and abs(pos['lon']) <= 180: flight = self.processFlight(hfnpdu["flight_id"]) - msg = { - "lat": pos["lat"], - "lon": pos["lon"], - "flight": flight - } - if icao is None: + + if icao is not None: + source = IcaoSource(icao, flight=flight) + elif flight: source = HfdlSource(flight) else: - source = IcaoSource(icao, flight=flight) - if "utc_time" in hfnpdu: - ts = self.processTimestamp(**hfnpdu["utc_time"]) - elif "time" in hfnpdu: - ts = self.processTimestamp(**hfnpdu["time"]) - else: - ts = None - Map.getSharedInstance().updateLocation(source, HfdlAirplaneLocation(msg), "HFDL", timestamp=ts) + source = None + + if source: + msg = { + "lat": pos["lat"], + "lon": pos["lon"], + "flight": flight + } + if "utc_time" in hfnpdu: + ts = self.processTimestamp(**hfnpdu["utc_time"]) + elif "time" in hfnpdu: + ts = self.processTimestamp(**hfnpdu["time"]) + else: + ts = None + Map.getSharedInstance().updateLocation(source, HfdlAirplaneLocation(msg), "HFDL", timestamp=ts) def processTimestamp(self, hour, min, sec) -> datetime: now = datetime.now(timezone.utc) From 1b1a710c32f956a5105b4f980b582bf6da4bc206 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 10 Sep 2023 18:41:21 +0200 Subject: [PATCH 08/40] fix media advisory display --- htdocs/lib/MessagePanel.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/htdocs/lib/MessagePanel.js b/htdocs/lib/MessagePanel.js index 25df9b98..037a8e2b 100644 --- a/htdocs/lib/MessagePanel.js +++ b/htdocs/lib/MessagePanel.js @@ -479,11 +479,12 @@ AircraftMessagePanel.prototype.renderAcars = function(acars) { details += '
Registration: ' + acars['reg'].replace(/^\.+/g, '') + '
'; if ('media-adv' in acars) { details += '
Media advisory
'; - if ('current_link' in acars) { - details += '
Current link: ' + acars['current_link']['descr']; + var mediaadv = acars['media-adv']; + if ('current_link' in mediaadv) { + details += '
Current link: ' + mediaadv['current_link']['descr']; } - if ('links_avail' in acars) { - details += '
Available links: ' + acars['links_avail'].map(function (l) { + if ('links_avail' in mediaadv) { + details += '
Available links: ' + mediaadv['links_avail'].map(function (l) { return l['descr']; }).join(', ') + '
'; } From f19a979339a4ddf4a8f22ff71d40c8464ba6d072 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 10 Sep 2023 21:00:45 +0200 Subject: [PATCH 09/40] get a bit more info --- htdocs/lib/MessagePanel.js | 52 ++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/htdocs/lib/MessagePanel.js b/htdocs/lib/MessagePanel.js index 037a8e2b..e0215de0 100644 --- a/htdocs/lib/MessagePanel.js +++ b/htdocs/lib/MessagePanel.js @@ -550,6 +550,33 @@ HfdlMessagePanel.prototype.pushMessage = function(message) { return a['id']; } + var renderPosition = function(hfnpdu) { + if ('pos' in hfnpdu) { + var pos = hfnpdu['pos']; + var lat = pos['lat'] || 180; + var lon = pos['lon'] || 180; + if (Math.abs(lat) <= 90 && Math.abs(lon) <= 180) { + return '
Position: ' + pos['lat'] + ', ' + pos['lon'] + '
'; + } + } + return ''; + } + + var renderLogon = function(lpdu) { + var details = '' + if (lpdu['ac_info'] && lpdu['ac_info']['icao']) { + details += '
ICAO: ' + lpdu['ac_info']['icao'] + '
'; + } + if (lpdu['hfnpdu']) { + var hfnpdu = lpdu['hfnpdu']; + if (hfnpdu['flight_id'] && hfnpdu['flight_id'] !== '') { + details += '
Flight: ' + lpdu['hfnpdu']['flight_id'] + '
' + } + details += renderPosition(hfnpdu); + } + return details; + } + // TODO remove safety net once parsing is complete try { var payload = message['hfdl']; @@ -575,14 +602,7 @@ HfdlMessagePanel.prototype.pushMessage = function(message) { // performance data details = '

Performance data

'; details += '
Flight: ' + hfnpdu['flight_id'] + '
'; - if ('pos' in hfnpdu) { - var pos = hfnpdu['pos']; - var lat = pos['lat'] || 180; - var lon = pos['lon'] || 180; - if (Math.abs(lat) <= 90 && Math.abs(lon) <= 180) { - details += '
Position: ' + pos['lat'] + ', ' + pos['lon'] + '
'; - } - } + details += renderPosition(hnpdu); } else if (hfnpdu['type']['id'] === 255) { // enveloped data if ('acars' in hfnpdu) { @@ -595,33 +615,27 @@ HfdlMessagePanel.prototype.pushMessage = function(message) { } else if (lpdu['type']['id'] === 63) { details = '

Logoff request

'; if (lpdu['ac_info'] && lpdu['ac_info']['icao']) { - details += '
ICAO: ' + lpdu['ac_info']['icao']; + details += '
ICAO: ' + lpdu['ac_info']['icao'] + '
'; } } else if (lpdu['type']['id'] === 79) { details = '

Logon resume

'; - if (lpdu['ac_info'] && lpdu['ac_info']['icao']) { - details += '
ICAO: ' + lpdu['ac_info']['icao']; - } + details += renderLogon(lpdu); } else if (lpdu['type']['id'] === 95) { details = '

Logon resume confirmation

'; } else if (lpdu['type']['id'] === 143) { details = '

Logon request

'; - if (lpdu['ac_info'] && lpdu['ac_info']['icao']) { - details += '
ICAO: ' + lpdu['ac_info']['icao']; - } + details += renderLogon(lpdu); } else if (lpdu['type']['id'] === 159) { details = '

Logon confirmation

'; if (lpdu['ac_info'] && lpdu['ac_info']['icao']) { - details += '
ICAO: ' + lpdu['ac_info']['icao']; + details += '
ICAO: ' + lpdu['ac_info']['icao'] + '
'; } if (lpdu['assigned_ac_id']) { details += '
Assigned aircraft ID: ' + lpdu['assigned_ac_id'] + '
'; } } else if (lpdu['type']['id'] === 191) { details = '

Logon request (DLS)

'; - if (lpdu['ac_info'] && lpdu['ac_info']['icao']) { - details += '
ICAO: ' + lpdu['ac_info']['icao']; - } + details += renderLogon(lpdu); } } } catch (e) { From 7d58e7d27ef9e7ea2ea6a8270e9ea602619836a0 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Mon, 11 Sep 2023 00:16:30 +0200 Subject: [PATCH 10/40] parse flight numbers in javascript, too --- htdocs/lib/MessagePanel.js | 78 +++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/htdocs/lib/MessagePanel.js b/htdocs/lib/MessagePanel.js index e0215de0..988f1453 100644 --- a/htdocs/lib/MessagePanel.js +++ b/htdocs/lib/MessagePanel.js @@ -474,7 +474,7 @@ AircraftMessagePanel.prototype.renderAcars = function(acars) { } var details = '

ACARS message

'; if ('flight' in acars) { - details += '
Flight: ' + acars['flight'] + '
'; + details += '
Flight: ' + this.handleFlight(acars['flight']) + '
'; } details += '
Registration: ' + acars['reg'].replace(/^\.+/g, '') + '
'; if ('media-adv' in acars) { @@ -496,9 +496,13 @@ AircraftMessagePanel.prototype.renderAcars = function(acars) { adsc['tags'].forEach(function(tag) { if ('basic_report' in tag) { var basic_report = tag['basic_report']; - details += '
Basic report
'; + details += '
Basic ADS-C report
'; details += '
Position: ' + basic_report['lat'] + ', ' + basic_report['lon'] + '
'; details += '
Altitude: ' + basic_report['alt'] + '
'; + } else if ('cancel_all_contracts' in tag) { + details += '
Cancel all ADS-C contracts
'; + } else if ('cancel_contract' in tag) { + details += '
Cancel ADS-C contract
'; } else { details += '
Unsupported tag
'; } @@ -513,7 +517,11 @@ AircraftMessagePanel.prototype.renderAcars = function(acars) { details += '
' + acars['msg_text'] + '
'; } return details; -} +}; + +AircraftMessagePanel.prototype.handleFlight = function(raw) { + return raw.replace(/^([0-9A-Z]{2})0*([0-9A-Z]+$)/, '$1$2'); +}; HfdlMessagePanel = function(el) { AircraftMessagePanel.call(this, el); @@ -539,6 +547,33 @@ HfdlMessagePanel.prototype.supportsMessage = function(message) { return message['mode'] === 'HFDL'; }; +HfdlMessagePanel.prototype.renderPosition = function(hfnpdu) { + if ('pos' in hfnpdu) { + var pos = hfnpdu['pos']; + var lat = pos['lat'] || 180; + var lon = pos['lon'] || 180; + if (Math.abs(lat) <= 90 && Math.abs(lon) <= 180) { + return '
Position: ' + pos['lat'] + ', ' + pos['lon'] + '
'; + } + } + return ''; +}; + +HfdlMessagePanel.prototype.renderLogon = function(lpdu) { + var details = '' + if (lpdu['ac_info'] && lpdu['ac_info']['icao']) { + details += '
ICAO: ' + lpdu['ac_info']['icao'] + '
'; + } + if (lpdu['hfnpdu']) { + var hfnpdu = lpdu['hfnpdu']; + if (hfnpdu['flight_id'] && hfnpdu['flight_id'] !== '') { + details += '
Flight: ' + this.handleFlight(lpdu['hfnpdu']['flight_id']) + '
' + } + details += this.renderPosition(hfnpdu); + } + return details; +}; + HfdlMessagePanel.prototype.pushMessage = function(message) { var $b = $(this.el).find('tbody'); @@ -550,33 +585,6 @@ HfdlMessagePanel.prototype.pushMessage = function(message) { return a['id']; } - var renderPosition = function(hfnpdu) { - if ('pos' in hfnpdu) { - var pos = hfnpdu['pos']; - var lat = pos['lat'] || 180; - var lon = pos['lon'] || 180; - if (Math.abs(lat) <= 90 && Math.abs(lon) <= 180) { - return '
Position: ' + pos['lat'] + ', ' + pos['lon'] + '
'; - } - } - return ''; - } - - var renderLogon = function(lpdu) { - var details = '' - if (lpdu['ac_info'] && lpdu['ac_info']['icao']) { - details += '
ICAO: ' + lpdu['ac_info']['icao'] + '
'; - } - if (lpdu['hfnpdu']) { - var hfnpdu = lpdu['hfnpdu']; - if (hfnpdu['flight_id'] && hfnpdu['flight_id'] !== '') { - details += '
Flight: ' + lpdu['hfnpdu']['flight_id'] + '
' - } - details += renderPosition(hfnpdu); - } - return details; - } - // TODO remove safety net once parsing is complete try { var payload = message['hfdl']; @@ -601,8 +609,8 @@ HfdlMessagePanel.prototype.pushMessage = function(message) { if (hfnpdu['type']['id'] === 209) { // performance data details = '

Performance data

'; - details += '
Flight: ' + hfnpdu['flight_id'] + '
'; - details += renderPosition(hnpdu); + details += '
Flight: ' + this.handleFlight(hfnpdu['flight_id']) + '
'; + details += this.renderPosition(hfnpdu); } else if (hfnpdu['type']['id'] === 255) { // enveloped data if ('acars' in hfnpdu) { @@ -619,12 +627,12 @@ HfdlMessagePanel.prototype.pushMessage = function(message) { } } else if (lpdu['type']['id'] === 79) { details = '

Logon resume

'; - details += renderLogon(lpdu); + details += this.renderLogon(lpdu); } else if (lpdu['type']['id'] === 95) { details = '

Logon resume confirmation

'; } else if (lpdu['type']['id'] === 143) { details = '

Logon request

'; - details += renderLogon(lpdu); + details += this.renderLogon(lpdu); } else if (lpdu['type']['id'] === 159) { details = '

Logon confirmation

'; if (lpdu['ac_info'] && lpdu['ac_info']['icao']) { @@ -635,7 +643,7 @@ HfdlMessagePanel.prototype.pushMessage = function(message) { } } else if (lpdu['type']['id'] === 191) { details = '

Logon request (DLS)

'; - details += renderLogon(lpdu); + details += this.renderLogon(lpdu); } } } catch (e) { From 71e91aef536c54fef2d8d36ff71da5dc8b1f9003 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Mon, 11 Sep 2023 02:25:48 +0200 Subject: [PATCH 11/40] add a flush so that the intro doesn't get lost --- owrx/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/owrx/__main__.py b/owrx/__main__.py index 468d4912..b5c852c9 100644 --- a/owrx/__main__.py +++ b/owrx/__main__.py @@ -97,7 +97,8 @@ Author contact info: Jakob Ketterl, DD5JFK Documentation: https://github.com/jketterl/openwebrx/wiki Support and info: https://groups.io/g/openwebrx - """ + """, + flush=True ) logger.info("OpenWebRX version {0} starting up...".format(openwebrx_version)) From ef0cbdfeb9cb19b1049ebe83c055a509044ee361 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Mon, 11 Sep 2023 03:41:34 +0200 Subject: [PATCH 12/40] add a message to indicate we're ready to reduce confusion --- owrx/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/owrx/__main__.py b/owrx/__main__.py index b5c852c9..5ed8b569 100644 --- a/owrx/__main__.py +++ b/owrx/__main__.py @@ -136,6 +136,7 @@ Support and info: https://groups.io/g/openwebrx try: server = ThreadedHttpServer(coreConfig.get_web_port(), RequestHandler, coreConfig.get_web_ipv6()) + logger.info("Ready to serve requests.") server.serve_forever() except SignalException: pass From 81b51360d8fcd56663dc5f49f81e8e548e31d0df Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 12 Sep 2023 00:17:40 +0200 Subject: [PATCH 13/40] fix initial opacity --- htdocs/map.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/map.js b/htdocs/map.js index 33fe8d74..ed7cdcff 100644 --- a/htdocs/map.js +++ b/htdocs/map.js @@ -152,7 +152,7 @@ $(function(){ marker.setOptions($.extend({ position: pos, title: sourceToString(update.source) - }, aprsOptions, getMarkerOpacityOptions(update.lastseen) )); + }, aprsOptions, getMarkerOpacityOptions(update.lastseen, update.location.ttl) )); marker.source = update.source; marker.lastseen = update.lastseen; marker.mode = update.mode; @@ -212,7 +212,7 @@ $(function(){ west: lon, east: lon + 2 } - }, getRectangleOpacityOptions(update.lastseen) )); + }, getRectangleOpacityOptions(update.lastseen, update.location.ttl) )); if (expectedLocator && expectedLocator === update.location.locator) { map.panTo(center); From 26b8c53374c68429387adaa11b93bca656fc9abc Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 12 Sep 2023 16:46:05 +0200 Subject: [PATCH 14/40] introduce metrics for HFDL and VDL2 --- owrx/hfdl/dumphfdl.py | 8 ++++++++ owrx/vdl2/dumpvdl2.py | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/owrx/hfdl/dumphfdl.py b/owrx/hfdl/dumphfdl.py index 204696b0..bd7ea8e6 100644 --- a/owrx/hfdl/dumphfdl.py +++ b/owrx/hfdl/dumphfdl.py @@ -2,6 +2,7 @@ from pycsdr.modules import ExecModule from pycsdr.types import Format from owrx.aeronautical import AirplaneLocation, AcarsProcessor, IcaoSource from owrx.map import Map, Source +from owrx.metrics import Metrics, CounterMetric from datetime import datetime, timezone, timedelta import logging @@ -43,6 +44,11 @@ class DumpHFDLModule(ExecModule): class HFDLMessageParser(AcarsProcessor): def __init__(self): + name = "dumphfdl.decodes.hfdl" + self.metrics = Metrics.getSharedInstance().getMetric(name) + if self.metrics is None: + self.metrics = CounterMetric() + Metrics.getSharedInstance().addMetric(name, self.metrics) super().__init__("HFDL") def process(self, line): @@ -69,6 +75,8 @@ class HFDLMessageParser(AcarsProcessor): except Exception: logger.exception("error processing HFDL data") + self.metrics.inc() + return msg def processPosition(self, hfnpdu, icao=None): diff --git a/owrx/vdl2/dumpvdl2.py b/owrx/vdl2/dumpvdl2.py index c01918ac..c28da9c3 100644 --- a/owrx/vdl2/dumpvdl2.py +++ b/owrx/vdl2/dumpvdl2.py @@ -3,6 +3,7 @@ from pycsdr.types import Format from owrx.aeronautical import AcarsProcessor from owrx.map import Map from owrx.aeronautical import AirplaneLocation, IcaoSource +from owrx.metrics import Metrics, CounterMetric import logging @@ -26,6 +27,11 @@ class DumpVDL2Module(ExecModule): class VDL2MessageParser(AcarsProcessor): def __init__(self): + name = "dumpvdl2.decodes.vdl2" + self.metrics = Metrics.getSharedInstance().getMetric(name) + if self.metrics is None: + self.metrics = CounterMetric() + Metrics.getSharedInstance().addMetric(name, self.metrics) super().__init__("VDL2") def process(self, line): @@ -55,6 +61,7 @@ class VDL2MessageParser(AcarsProcessor): self.processReport(report_data, src) except Exception: logger.exception("error processing VDL2 data") + self.metrics.inc() return msg def processReport(self, report, icao): From 97a9a4f8c97486f667da2b50f5c51ce4320791ef Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 12 Sep 2023 18:57:47 +0200 Subject: [PATCH 15/40] remove a few superfluous intermediate classes --- owrx/aeronautical.py | 6 +++--- owrx/hfdl/dumphfdl.py | 23 ++++------------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/owrx/aeronautical.py b/owrx/aeronautical.py index 44c0f240..b62cc6e5 100644 --- a/owrx/aeronautical.py +++ b/owrx/aeronautical.py @@ -34,12 +34,12 @@ class IcaoSource(Source): return d -class AcarsSource(Source): +class FlightSource(Source): def __init__(self, flight): self.flight = flight def getKey(self) -> str: - return "acars:{}".format(self.flight) + return "flight:{}".format(self.flight) def __dict__(self): return {"flight": self.flight} @@ -72,7 +72,7 @@ class AcarsProcessor(JsonParser, metaclass=ABCMeta): if icao is not None: source = IcaoSource(icao, flight=flight_id) else: - source = AcarsSource(flight_id) + source = FlightSource(flight_id) Map.getSharedInstance().updateLocation( source, AirplaneLocation(msg), "ACARS over {}".format(self.mode) ) diff --git a/owrx/hfdl/dumphfdl.py b/owrx/hfdl/dumphfdl.py index bd7ea8e6..db20ed20 100644 --- a/owrx/hfdl/dumphfdl.py +++ b/owrx/hfdl/dumphfdl.py @@ -1,7 +1,7 @@ from pycsdr.modules import ExecModule from pycsdr.types import Format -from owrx.aeronautical import AirplaneLocation, AcarsProcessor, IcaoSource -from owrx.map import Map, Source +from owrx.aeronautical import AirplaneLocation, AcarsProcessor, IcaoSource, FlightSource +from owrx.map import Map from owrx.metrics import Metrics, CounterMetric from datetime import datetime, timezone, timedelta @@ -10,21 +10,6 @@ import logging logger = logging.getLogger(__name__) -class HfdlAirplaneLocation(AirplaneLocation): - pass - - -class HfdlSource(Source): - def __init__(self, flight): - self.flight = flight - - def getKey(self) -> str: - return "hfdl:{}".format(self.flight) - - def __dict__(self): - return {"flight": self.flight} - - class DumpHFDLModule(ExecModule): def __init__(self): super().__init__( @@ -88,7 +73,7 @@ class HFDLMessageParser(AcarsProcessor): if icao is not None: source = IcaoSource(icao, flight=flight) elif flight: - source = HfdlSource(flight) + source = FlightSource(flight) else: source = None @@ -104,7 +89,7 @@ class HFDLMessageParser(AcarsProcessor): ts = self.processTimestamp(**hfnpdu["time"]) else: ts = None - Map.getSharedInstance().updateLocation(source, HfdlAirplaneLocation(msg), "HFDL", timestamp=ts) + Map.getSharedInstance().updateLocation(source, AirplaneLocation(msg), "HFDL", timestamp=ts) def processTimestamp(self, hour, min, sec) -> datetime: now = datetime.now(timezone.utc) From 156d1adc69955f2005359d68ef83116344d1c4f8 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 12 Sep 2023 18:58:25 +0200 Subject: [PATCH 16/40] parse event reports and extract timestamps --- owrx/vdl2/dumpvdl2.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/owrx/vdl2/dumpvdl2.py b/owrx/vdl2/dumpvdl2.py index c28da9c3..cea797c0 100644 --- a/owrx/vdl2/dumpvdl2.py +++ b/owrx/vdl2/dumpvdl2.py @@ -4,6 +4,7 @@ from owrx.aeronautical import AcarsProcessor from owrx.map import Map from owrx.aeronautical import AirplaneLocation, IcaoSource from owrx.metrics import Metrics, CounterMetric +from datetime import datetime, date, time, timezone import logging @@ -55,9 +56,12 @@ class VDL2MessageParser(AcarsProcessor): adsc_v2 = cotp["adsc_v2"] if "adsc_report" in adsc_v2: adsc_report = adsc_v2["adsc_report"] - if "periodic_report" in adsc_report["data"]: - periodic_report = adsc_report["data"]["periodic_report"] - report_data = periodic_report["report_data"] + data = adsc_report["data"] + if "periodic_report" in data: + report_data = data["periodic_report"]["report_data"] + self.processReport(report_data, src) + elif "event_report" in data: + report_data = data["event_report"]["report_data"] self.processReport(report_data, src) except Exception: logger.exception("error processing VDL2 data") @@ -81,7 +85,11 @@ class VDL2MessageParser(AcarsProcessor): msg.update({ "verticalspeed": report["air_vector"]["vertical_rate"]["val"], }) - Map.getSharedInstance().updateLocation(IcaoSource(icao), AirplaneLocation(msg), "VDL2") + if "timestamp" in report: + timestamp = self.convertTimestamp(**report["timestamp"]) + else: + timestamp = None + Map.getSharedInstance().updateLocation(IcaoSource(icao), AirplaneLocation(msg), "VDL2", timestamp=timestamp) def convertLatitude(self, dir, **args) -> float: coord = self.convertCoordinate(**args) @@ -97,3 +105,12 @@ class VDL2MessageParser(AcarsProcessor): def convertCoordinate(self, deg, min, sec) -> float: return deg + float(min) / 60 + float(sec) / 3600 + + def convertTimestamp(self, date, time): + return datetime.combine(self.convertDate(**date), self.convertTime(**time), tzinfo=timezone.utc) + + def convertDate(self, year, month, day): + return date(year=year, month=month, day=day) + + def convertTime(self, hour, min, sec): + return time(hour=hour, minute=min, second=sec, microsecond=0, tzinfo=timezone.utc) From e2541780627485d14c32d015dd75f3f46f1b0b16 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 12 Sep 2023 21:53:51 +0200 Subject: [PATCH 17/40] give some information about ads-c v2 reports --- htdocs/lib/MessagePanel.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/htdocs/lib/MessagePanel.js b/htdocs/lib/MessagePanel.js index 988f1453..6ba7525d 100644 --- a/htdocs/lib/MessagePanel.js +++ b/htdocs/lib/MessagePanel.js @@ -741,6 +741,21 @@ Vdl2MessagePanel.prototype.pushMessage = function(message) { } } } + if ('adsc_v2' in cotp) { + var adsc_v2 = cotp['adsc_v2'] + details = '

ADS-C v2 Frame

'; + if ('adsc_report' in adsc_v2) { + var adsc_report = adsc_v2['adsc_report']; + var data = adsc_report['data']; + if ('periodic_report' in data) { + details += '
Periodic report
'; + details += this.processReport(data['periodic_report']); + } else if ('event_report' in data) { + details += '
Event report
'; + details += this.processReport(data['event_report']); + } + } + } } } } else { @@ -769,6 +784,20 @@ Vdl2MessagePanel.prototype.pushMessage = function(message) { this.scrollToBottom(); }; +Vdl2MessagePanel.prototype.processReport = function(report) { + var details = ''; + if ('position' in report) { + var lat = position['lat'] + var lon = position['lon'] + details += '
Position: ' + + lat['deg'] + '° ' + lat['min'] + '\' ' + lat['sec'] + '" ' + lat['dir'] + ',' + + lon['deg'] + '° ' + lat['min'] + '\' ' + lat['sec'] + '" ' + lat['dir'] + + '
'; + details += '
Altitude: ' + position['alt']['val'] + '
'; + } + return details; +} + $.fn.vdl2MessagePanel = function() { if (!this.data('panel')) { this.data('panel', new Vdl2MessagePanel(this)); From b29547f1ac3622784e69093100e8e5f895ec2f24 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 12 Sep 2023 21:55:18 +0200 Subject: [PATCH 18/40] update changelog --- CHANGELOG.md | 1 + debian/changelog | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e612e678..dca5c80f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - SDR device log messages are now available in the web configuration to simplify troubleshooting - Added support for the MSK144 digimode - Added support for decoding ADS-B with dump1090 +- Added support for decoding HFDL and VDL2 aircraft communications - Added IPv6 support **1.2.1** diff --git a/debian/changelog b/debian/changelog index 4175ba93..cc34f234 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,6 +3,7 @@ openwebrx (1.3.0) UNRELEASED; urgency=low simplify troubleshooting * Added support for the MSK144 digimode * Added support for decoding ADS-B with dump1090 + * Added support for decoding HFDL and VDL2 aircraft communications * Added IPv6 support -- Jakob Ketterl Fri, 30 Sep 2022 16:47:00 +0000 From eaea291c1c6264c87d6d71be5c406e830fd5e7fb Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 12 Sep 2023 23:24:49 +0200 Subject: [PATCH 19/40] update changelog with rtl_433 --- CHANGELOG.md | 1 + debian/changelog | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dca5c80f..f1fd0a93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Added support for the MSK144 digimode - Added support for decoding ADS-B with dump1090 - Added support for decoding HFDL and VDL2 aircraft communications +- Added decoding of ISM band transmissions using rtl_433 - Added IPv6 support **1.2.1** diff --git a/debian/changelog b/debian/changelog index cc34f234..12ae6931 100644 --- a/debian/changelog +++ b/debian/changelog @@ -4,6 +4,7 @@ openwebrx (1.3.0) UNRELEASED; urgency=low * Added support for the MSK144 digimode * Added support for decoding ADS-B with dump1090 * Added support for decoding HFDL and VDL2 aircraft communications + * Added decoding of ISM band transmissions using rtl_433 * Added IPv6 support -- Jakob Ketterl Fri, 30 Sep 2022 16:47:00 +0000 From 5f7c7a6bbc1e8959f6bb4bdfe58d2a34dc75071c Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 13 Sep 2023 01:23:18 +0200 Subject: [PATCH 20/40] update rtl-sdr dependencies for rtlsdr blog v4 support --- docker/scripts/install-dependencies-rtlsdr-soapy.sh | 7 ++++--- docker/scripts/install-dependencies-rtlsdr.sh | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docker/scripts/install-dependencies-rtlsdr-soapy.sh b/docker/scripts/install-dependencies-rtlsdr-soapy.sh index 695f31d0..9cea76f0 100755 --- a/docker/scripts/install-dependencies-rtlsdr-soapy.sh +++ b/docker/scripts/install-dependencies-rtlsdr-soapy.sh @@ -25,11 +25,12 @@ apt-get update apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES git clone https://github.com/osmocom/rtl-sdr.git -# latest from master as of 2020-09-04 -cmakebuild rtl-sdr ed0317e6a58c098874ac58b769cf2e609c18d9a5 +# latest from master as of 2023-09-13 (integration of rtlsdr blog v4 dongle) +cmakebuild rtl-sdr 1261fbb285297da08f4620b18871b6d6d9ec2a7b git clone https://github.com/pothosware/SoapyRTLSDR.git -cmakebuild SoapyRTLSDR soapy-rtl-sdr-0.3.1 +# latest from master as of 2023-09-13 +cmakebuild SoapyRTLSDR 068aa77a4c938b239c9d80cd42c4ee7986458e8f apt-get -y purge --autoremove $BUILD_PACKAGES apt-get clean diff --git a/docker/scripts/install-dependencies-rtlsdr.sh b/docker/scripts/install-dependencies-rtlsdr.sh index 942241ab..5501a670 100755 --- a/docker/scripts/install-dependencies-rtlsdr.sh +++ b/docker/scripts/install-dependencies-rtlsdr.sh @@ -25,8 +25,8 @@ apt-get update apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES git clone https://github.com/osmocom/rtl-sdr.git -# latest from master as of 2020-09-04 -cmakebuild rtl-sdr ed0317e6a58c098874ac58b769cf2e609c18d9a5 +# latest from master as of 2023-09-13 (integration of rtlsdr blog v4 dongle) +cmakebuild rtl-sdr 1261fbb285297da08f4620b18871b6d6d9ec2a7b apt-get -y purge --autoremove $BUILD_PACKAGES apt-get clean From 5807a315247d4e970688c1b3d12df2ad63f8cc38 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 13 Sep 2023 03:50:38 +0200 Subject: [PATCH 21/40] fix the report display --- htdocs/lib/MessagePanel.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/htdocs/lib/MessagePanel.js b/htdocs/lib/MessagePanel.js index 6ba7525d..cf7eee72 100644 --- a/htdocs/lib/MessagePanel.js +++ b/htdocs/lib/MessagePanel.js @@ -749,10 +749,10 @@ Vdl2MessagePanel.prototype.pushMessage = function(message) { var data = adsc_report['data']; if ('periodic_report' in data) { details += '
Periodic report
'; - details += this.processReport(data['periodic_report']); + details += this.processReport(data['periodic_report']['report_data']); } else if ('event_report' in data) { details += '
Event report
'; - details += this.processReport(data['event_report']); + details += this.processReport(data['event_report']['report_data']); } } } @@ -787,13 +787,14 @@ Vdl2MessagePanel.prototype.pushMessage = function(message) { Vdl2MessagePanel.prototype.processReport = function(report) { var details = ''; if ('position' in report) { + var position = report['position']; var lat = position['lat'] var lon = position['lon'] details += '
Position: ' + - lat['deg'] + '° ' + lat['min'] + '\' ' + lat['sec'] + '" ' + lat['dir'] + ',' + - lon['deg'] + '° ' + lat['min'] + '\' ' + lat['sec'] + '" ' + lat['dir'] + + lat['deg'] + '° ' + lat['min'] + '\' ' + lat['sec'] + '" ' + lat['dir'] + ', ' + + lon['deg'] + '° ' + lon['min'] + '\' ' + lon['sec'] + '" ' + lon['dir'] + '
'; - details += '
Altitude: ' + position['alt']['val'] + '
'; + details += '
Altitude: ' + position['alt']['val'] + ' ' + position['alt']['unit'] + '
'; } return details; } From dab683aac27389d4559d31bc9ad3c936186c7bcf Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 13 Sep 2023 22:40:09 +0200 Subject: [PATCH 22/40] fix chown warnings during package installation --- debian/openwebrx.postinst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/debian/openwebrx.postinst b/debian/openwebrx.postinst index 935a0fe6..84044909 100755 --- a/debian/openwebrx.postinst +++ b/debian/openwebrx.postinst @@ -16,23 +16,23 @@ case "$1" in # create OpenWebRX data directory and set the correct permissions if [ ! -d "${OWRX_DATADIR}" ] && [ ! -L "${OWRX_DATADIR}" ]; then mkdir "${OWRX_DATADIR}"; fi - chown "${OWRX_USER}". ${OWRX_DATADIR} + chown "${OWRX_USER}": ${OWRX_DATADIR} # create empty config files now to avoid permission problems later if [ ! -e "${OWRX_USERS_FILE}" ]; then echo "[]" > "${OWRX_USERS_FILE}" - chown "${OWRX_USER}". "${OWRX_USERS_FILE}" + chown "${OWRX_USER}": "${OWRX_USERS_FILE}" chmod 0600 "${OWRX_USERS_FILE}" fi if [ ! -e "${OWRX_SETTINGS_FILE}" ]; then echo "{}" > "${OWRX_SETTINGS_FILE}" - chown "${OWRX_USER}". "${OWRX_SETTINGS_FILE}" + chown "${OWRX_USER}": "${OWRX_SETTINGS_FILE}" fi if [ ! -e "${OWRX_BOOKMARKS_FILE}" ]; then touch "${OWRX_BOOKMARKS_FILE}" - chown "${OWRX_USER}". "${OWRX_BOOKMARKS_FILE}" + chown "${OWRX_USER}": "${OWRX_BOOKMARKS_FILE}" fi db_get openwebrx/admin_user_password From a2522146b55e51c682c6d18e67d5a9c0a02c4915 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Thu, 14 Sep 2023 19:08:13 +0200 Subject: [PATCH 23/40] remove superfluous import --- owrx/aprs/direwolf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owrx/aprs/direwolf.py b/owrx/aprs/direwolf.py index 7e0eae90..f551595c 100644 --- a/owrx/aprs/direwolf.py +++ b/owrx/aprs/direwolf.py @@ -1,5 +1,5 @@ from pycsdr.types import Format -from pycsdr.modules import Writer, TcpSource, ExecModule, CallbackWriter +from pycsdr.modules import Writer, TcpSource, ExecModule from csdr.module import LogWriter from owrx.config.core import CoreConfig from owrx.config import Config From 4a4e305ab5621a9f669d70b9b79ec04d0962cfa2 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Thu, 14 Sep 2023 22:23:09 +0200 Subject: [PATCH 24/40] add PopenModules timeout, courtesy of @luarvique --- csdr/module/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/csdr/module/__init__.py b/csdr/module/__init__.py index c53556ca..131d6c16 100644 --- a/csdr/module/__init__.py +++ b/csdr/module/__init__.py @@ -4,7 +4,7 @@ from pycsdr.types import Format from abc import ABCMeta, abstractmethod from threading import Thread from io import BytesIO -from subprocess import Popen, PIPE +from subprocess import Popen, PIPE, TimeoutExpired from functools import partial import pickle import logging @@ -185,8 +185,12 @@ class PopenModule(AutoStartModule, metaclass=ABCMeta): def stop(self): if self.process is not None: - self.process.terminate() - self.process.wait() + # Try terminating normally, kill if failed to terminate + try: + self.process.terminate() + self.process.wait(3) + except TimeoutExpired: + self.process.kill() self.process = None self.reader.stop() From 6ad24f64d820955923c9c436e7dc74ec8b476939 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Thu, 14 Sep 2023 22:32:33 +0200 Subject: [PATCH 25/40] handle errors on write, too, courtesy of @luarvique --- csdr/module/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/csdr/module/__init__.py b/csdr/module/__init__.py index 131d6c16..1e1b3525 100644 --- a/csdr/module/__init__.py +++ b/csdr/module/__init__.py @@ -45,7 +45,10 @@ class Module(BaseModule, metaclass=ABCMeta): break if data is None or isinstance(data, bytes) and len(data) == 0: break - write(data) + try: + write(data) + except BrokenPipeError: + break return copy From f6e54118b6bddd4e74f945e870d8f708deeb932b Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Thu, 14 Sep 2023 23:07:12 +0200 Subject: [PATCH 26/40] fixes to bandpass visual rendering by @luarvique --- htdocs/lib/Demodulator.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/htdocs/lib/Demodulator.js b/htdocs/lib/Demodulator.js index 6d695e06..b55cbdcf 100644 --- a/htdocs/lib/Demodulator.js +++ b/htdocs/lib/Demodulator.js @@ -59,7 +59,6 @@ Envelope.prototype.draw = function(visible_range){ from_px -= (env_att_w + env_bounding_line_w); to_px += (env_att_w + env_bounding_line_w); // do drawing: - scale_ctx.lineWidth = 3; var color = this.color || '#ffff00'; // yellow scale_ctx.strokeStyle = color; scale_ctx.fillStyle = color; @@ -77,11 +76,14 @@ Envelope.prototype.draw = function(visible_range){ scale_ctx.lineTo(to_px - env_bounding_line_w - env_att_w, env_h2); scale_ctx.lineTo(to_px - env_bounding_line_w, env_h1); scale_ctx.lineTo(to_px, env_h1); + scale_ctx.lineWidth = 3; scale_ctx.globalAlpha = 0.3; scale_ctx.fill(); scale_ctx.globalAlpha = 1; scale_ctx.stroke(); scale_ctx.lineWidth = 1; + scale_ctx.font = "bold 11px sans-serif"; + scale_ctx.textBaseline = "top"; scale_ctx.textAlign = "left"; if (typeof(this.demodulator.high_cut) === 'number') { scale_ctx.fillText(this.demodulator.high_cut.toString(), to_px + env_att_w, env_h2); @@ -90,7 +92,6 @@ Envelope.prototype.draw = function(visible_range){ if (typeof(this.demodulator.low_cut) === 'number') { scale_ctx.fillText(this.demodulator.low_cut.toString(), from_px - env_att_w, env_h2); } - scale_ctx.lineWidth = 3; } if (typeof line !== "undefined") // out of screen? { @@ -100,6 +101,7 @@ Envelope.prototype.draw = function(visible_range){ drag_ranges.line_on_screen = true; scale_ctx.moveTo(line_px, env_h1 + env_lineplus); scale_ctx.lineTo(line_px, env_h2 - env_lineplus); + scale_ctx.lineWidth = 3; scale_ctx.stroke(); } } From 6f0f705242c4e1c091a8129a83734d6a69953272 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Thu, 14 Sep 2023 23:58:42 +0200 Subject: [PATCH 27/40] double check on frequency entry courtesy of @luarvique --- htdocs/lib/FrequencyDisplay.js | 1 + 1 file changed, 1 insertion(+) diff --git a/htdocs/lib/FrequencyDisplay.js b/htdocs/lib/FrequencyDisplay.js index b6eb1964..13390772 100644 --- a/htdocs/lib/FrequencyDisplay.js +++ b/htdocs/lib/FrequencyDisplay.js @@ -120,6 +120,7 @@ TuneableFrequencyDisplay.prototype.setupEvents = function() { submit(); }); $inputs.on('blur', function(e){ + if (!me.input.is(':visible')) return; if ($inputs.toArray().indexOf(e.relatedTarget) >= 0) { return; } From 3ac15bf1d3ae5bfbace43300fcd80e78ca4f2c99 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 15 Sep 2023 01:31:19 +0200 Subject: [PATCH 28/40] add some warnings to wave file handling --- owrx/audio/wav.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/owrx/audio/wav.py b/owrx/audio/wav.py index d7849784..f801128a 100644 --- a/owrx/audio/wav.py +++ b/owrx/audio/wav.py @@ -29,6 +29,10 @@ class WaveFile(object): self.waveFile.setsampwidth(2) self.waveFile.setframerate(12000) + def __del__(self): + if self.waveFile is not None: + logger.warning("WaveFile going out of scope but not unlinked!") + def close(self): self.waveFile.close() @@ -77,14 +81,18 @@ class AudioWriter(object): def _scheduleNextSwitch(self): self.cancelTimer() delta = self.getNextDecodingTime() - datetime.utcnow() - self.timer = threading.Timer(delta.total_seconds(), self.switchFiles) + self.timer = threading.Timer(delta.total_seconds(), self._switchFiles) self.timer.start() - def switchFiles(self): + def _switchFiles(self): with self.switchingLock: file = self.wavefile self.wavefile = self.getWaveFile() + if file is None: + logger.warning("switchfiles() with no wave file. sequencing problem?") + return + file.close() tmp_dir = CoreConfig().get_temporary_directory() @@ -117,6 +125,8 @@ class AudioWriter(object): self._scheduleNextSwitch() def start(self): + if self.wavefile is not None: + logger.warning("wavefile is not none on startup, sequencing problem?") self.wavefile = self.getWaveFile() self._scheduleNextSwitch() From 1a8d1dcb8bcbd5029e3e51dbe3bd3202d21ef5a8 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 15 Sep 2023 20:31:52 +0200 Subject: [PATCH 29/40] fix typing --- csdr/module/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csdr/module/__init__.py b/csdr/module/__init__.py index 1e1b3525..f1ed9282 100644 --- a/csdr/module/__init__.py +++ b/csdr/module/__init__.py @@ -204,7 +204,7 @@ class LogWriter(CallbackWriter): self.retained = bytes() super().__init__(Format.CHAR) - def write(self, data: bytes) -> None: + def write(self, data: memoryview) -> None: self.retained += data lines = self.retained.split(b"\n") From e201ca07e3e93e8edafed3f824eafa5b53c3864b Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 15 Sep 2023 20:44:17 +0200 Subject: [PATCH 30/40] get dump1090 on STDOUT instead of a tcp socket --- owrx/adsb/dump1090.py | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/owrx/adsb/dump1090.py b/owrx/adsb/dump1090.py index 827b5e09..dfac3583 100644 --- a/owrx/adsb/dump1090.py +++ b/owrx/adsb/dump1090.py @@ -1,8 +1,6 @@ -from pycsdr.modules import ExecModule, Writer, TcpSource +from pycsdr.modules import ExecModule from pycsdr.types import Format -from csdr.module import LogWriter, LineBasedModule -from owrx.socket import getAvailablePort -import time +from csdr.module import LineBasedModule import logging @@ -11,43 +9,15 @@ logger = logging.getLogger(__name__) class Dump1090Module(ExecModule): def __init__(self): - self.tcpSource = None - self.writer = None - self.port = getAvailablePort() - super().__init__( Format.COMPLEX_SHORT, Format.CHAR, - ["dump1090", "--ifile", "-", "--iformat", "SC16", "--quiet", "--net-ro-port", str(self.port)], + ["dump1090", "--ifile", "-", "--iformat", "SC16", "--raw"], # send some data on decoder shutdown since the dump1090 internal reader locks up otherwise # dump1090 reads chunks of 100ms, which equals to 240k samples at 2.4MS/s # some extra should not hurt flushSize=300000 ) - super().setWriter(LogWriter(__name__)) - - self.start() - - def start(self): - delay = 0.5 - retries = 0 - while True: - try: - self.tcpSource = TcpSource(self.port, Format.CHAR) - if self.writer: - self.tcpSource.setWriter(self.writer) - break - except ConnectionError: - if retries > 20: - logger.error("maximum number of connection attempts reached. did dump1090 start up correctly?") - raise - retries += 1 - time.sleep(delay) - - def setWriter(self, writer: Writer) -> None: - self.writer = writer - if self.tcpSource is not None: - self.tcpSource.setWriter(writer) class RawDeframer(LineBasedModule): From 5058a91db0f1ff13291208bf668a8248b0cbc8df Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 16 Sep 2023 00:39:00 +0200 Subject: [PATCH 31/40] switch out logreader with a buffer-based implementation (again) --- csdr/module/__init__.py | 38 ++++++++++++++++++++++++-------------- owrx/aprs/direwolf.py | 10 +++++++--- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/csdr/module/__init__.py b/csdr/module/__init__.py index f1ed9282..02ba5ece 100644 --- a/csdr/module/__init__.py +++ b/csdr/module/__init__.py @@ -1,5 +1,5 @@ from pycsdr.modules import Module as BaseModule -from pycsdr.modules import Reader, Writer, CallbackWriter +from pycsdr.modules import Reader, Writer, Buffer from pycsdr.types import Format from abc import ABCMeta, abstractmethod from threading import Thread @@ -198,21 +198,31 @@ class PopenModule(AutoStartModule, metaclass=ABCMeta): self.reader.stop() -class LogWriter(CallbackWriter): - def __init__(self, prefix: str): +class LogReader(Thread): + def __init__(self, prefix: str, buffer: Buffer): + self.reader = buffer.getReader() self.logger = logging.getLogger(prefix) self.retained = bytes() - super().__init__(Format.CHAR) + super().__init__() + self.start() - def write(self, data: memoryview) -> None: - self.retained += data - lines = self.retained.split(b"\n") + def run(self) -> None: + while True: + data = self.reader.read() + if data is None: + return - # keep the last line - # this should either be empty if the last char was \n - # or an incomplete line if the read returned early - self.retained = lines[-1] + self.retained += data + lines = self.retained.split(b"\n") - # log all completed lines - for line in lines[0:-1]: - self.logger.info("{}: {}".format("STDOUT", line.strip(b'\n').decode())) + # keep the last line + # this should either be empty if the last char was \n + # or an incomplete line if the read returned early + self.retained = lines[-1] + + # log all completed lines + for line in lines[0:-1]: + self.logger.info("{}: {}".format("STDOUT", line.strip(b'\n').decode())) + + def stop(self): + self.reader.stop() diff --git a/owrx/aprs/direwolf.py b/owrx/aprs/direwolf.py index f551595c..9a533ea2 100644 --- a/owrx/aprs/direwolf.py +++ b/owrx/aprs/direwolf.py @@ -1,6 +1,6 @@ from pycsdr.types import Format -from pycsdr.modules import Writer, TcpSource, ExecModule -from csdr.module import LogWriter +from pycsdr.modules import Writer, TcpSource, ExecModule, Buffer +from csdr.module import LogReader from owrx.config.core import CoreConfig from owrx.config import Config from abc import ABC, abstractmethod @@ -160,7 +160,9 @@ class DirewolfModule(ExecModule, DirewolfConfigSubscriber): super().__init__(Format.SHORT, Format.CHAR, ["direwolf", "-c", self.direwolfConfigPath, "-r", "48000", "-t", "0", "-q", "d", "-q", "h"]) # direwolf supplies the data via a socket which we tap into in start() # the output on its STDOUT is informative, but we still want to log it - super().setWriter(LogWriter(__name__)) + buffer = Buffer(Format.CHAR) + self.logReader = LogReader(__name__, buffer) + super().setWriter(buffer) self.start() def __writeConfig(self): @@ -199,6 +201,8 @@ class DirewolfModule(ExecModule, DirewolfConfigSubscriber): def stop(self) -> None: super().stop() + self.logReader.stop() + self.logReader = None os.unlink(self.direwolfConfigPath) self.direwolfConfig.unwire(self) self.direwolfConfig = None From 1f3c011057e6a726db11cf3aaed978af3a516f1d Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 16 Sep 2023 00:39:23 +0200 Subject: [PATCH 32/40] update dependencies --- docker/scripts/install-owrx-tools.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/scripts/install-owrx-tools.sh b/docker/scripts/install-owrx-tools.sh index 5d96d28b..0999cc7a 100755 --- a/docker/scripts/install-owrx-tools.sh +++ b/docker/scripts/install-owrx-tools.sh @@ -32,13 +32,13 @@ popd rm -rf js8py git clone https://github.com/jketterl/csdr.git -# latest develop as of 2023-09-08 (various specializations) -cmakebuild csdr 93dc0fa0c6b1a57ea72579828f47c97cf1682c06 +# latest develop as of 2023-09-08 (execmodule writer logging) +cmakebuild csdr b6a638e612b01232b1b7ef60d97c62a68f0a92c8 git clone https://github.com/jketterl/pycsdr.git cd pycsdr -# latest develop as of 2023-08-21 (various specializations)) -git checkout 37bb90174c23567ac419b45804f2d0cc5bb6a883 +# latest develop as of 2023-08-21 (death of CallbackWriter)) +git checkout 77b3709c545f510b52c7cea2e300e2e1613038e2 ./setup.py install install_headers cd .. rm -rf pycsdr From 6df80829d042e09293c96b6f38f849bad2500761 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 16 Sep 2023 02:41:28 +0200 Subject: [PATCH 33/40] updated list of "facing east" markers by @luarvique --- htdocs/lib/AprsMarker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/lib/AprsMarker.js b/htdocs/lib/AprsMarker.js index 381c4ea9..e1673e29 100644 --- a/htdocs/lib/AprsMarker.js +++ b/htdocs/lib/AprsMarker.js @@ -6,10 +6,10 @@ AprsMarker.prototype.isFacingEast = function(symbol) { var candidates = '' if (symbol.table === '/') { // primary table - candidates = '(*<=>CFPUXYabefghjkpsuv['; + candidates = '(*<=>CFPUXYZabefgjkpsuv['; } else { // alternate table - candidates = 'hkluv'; + candidates = '(T`efhjktuvw'; } return candidates.includes(symbol.symbol); }; From faa74c6790a7522ff9e0b4e6b0c45aa4e54b15ee Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 16 Sep 2023 06:14:36 +0200 Subject: [PATCH 34/40] disable squelch on a number of modes --- csdr/chain/dump1090.py | 3 +++ csdr/chain/dumphfdl.py | 3 +++ csdr/chain/dumpvdl2.py | 3 +++ csdr/chain/rtl433.py | 3 +++ 4 files changed, 12 insertions(+) diff --git a/csdr/chain/dump1090.py b/csdr/chain/dump1090.py index ef4989a2..04a29421 100644 --- a/csdr/chain/dump1090.py +++ b/csdr/chain/dump1090.py @@ -22,3 +22,6 @@ class Dump1090(ServiceDemodulator): def isSecondaryFftShown(self): return False + + def supportsSquelch(self) -> bool: + return False diff --git a/csdr/chain/dumphfdl.py b/csdr/chain/dumphfdl.py index f6a29c43..413b6150 100644 --- a/csdr/chain/dumphfdl.py +++ b/csdr/chain/dumphfdl.py @@ -11,3 +11,6 @@ class DumpHFDL(ServiceDemodulator): def getFixedAudioRate(self) -> int: return 12000 + + def supportsSquelch(self) -> bool: + return False diff --git a/csdr/chain/dumpvdl2.py b/csdr/chain/dumpvdl2.py index 809a690a..cae271f5 100644 --- a/csdr/chain/dumpvdl2.py +++ b/csdr/chain/dumpvdl2.py @@ -14,3 +14,6 @@ class DumpVDL2(ServiceDemodulator): def getFixedAudioRate(self) -> int: return 105000 + + def supportsSquelch(self) -> bool: + return False diff --git a/csdr/chain/rtl433.py b/csdr/chain/rtl433.py index f1f95341..e0fa6850 100644 --- a/csdr/chain/rtl433.py +++ b/csdr/chain/rtl433.py @@ -14,3 +14,6 @@ class Rtl433(ServiceDemodulator): JsonParser("ISM"), ] ) + + def supportsSquelch(self) -> bool: + return False From 97701bbb7d1e5f397de4976ad129e3b8eb9ae375 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 17 Sep 2023 00:48:18 +0200 Subject: [PATCH 35/40] remove extra strip() --- csdr/module/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csdr/module/__init__.py b/csdr/module/__init__.py index 02ba5ece..11e897db 100644 --- a/csdr/module/__init__.py +++ b/csdr/module/__init__.py @@ -222,7 +222,7 @@ class LogReader(Thread): # log all completed lines for line in lines[0:-1]: - self.logger.info("{}: {}".format("STDOUT", line.strip(b'\n').decode())) + self.logger.info("{}: {}".format("STDOUT", line.decode())) def stop(self): self.reader.stop() From 0dc4419876e0f118e8aa1c61374d30a0daeafccd Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 17 Sep 2023 00:49:12 +0200 Subject: [PATCH 36/40] update csdr --- docker/scripts/install-owrx-tools.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/scripts/install-owrx-tools.sh b/docker/scripts/install-owrx-tools.sh index 0999cc7a..0c9db6d9 100755 --- a/docker/scripts/install-owrx-tools.sh +++ b/docker/scripts/install-owrx-tools.sh @@ -32,8 +32,8 @@ popd rm -rf js8py git clone https://github.com/jketterl/csdr.git -# latest develop as of 2023-09-08 (execmodule writer logging) -cmakebuild csdr b6a638e612b01232b1b7ef60d97c62a68f0a92c8 +# latest develop as of 2023-09-08 (execmodule improvements) +cmakebuild csdr 764767933ed2b242190285f8ff56b11d80c7d530 git clone https://github.com/jketterl/pycsdr.git cd pycsdr From 31a6d2a1b7c75dac36cd32033e3a0e610768be16 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 17 Sep 2023 01:36:33 +0200 Subject: [PATCH 37/40] fix selector for rtty --- htdocs/css/openwebrx.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/css/openwebrx.css b/htdocs/css/openwebrx.css index ff7d1075..f7c8115b 100644 --- a/htdocs/css/openwebrx.css +++ b/htdocs/css/openwebrx.css @@ -1272,7 +1272,7 @@ img.openwebrx-mirror-img #openwebrx-panel-digimodes[data-mode^="bpsk"] #openwebrx-digimode-content-container, #openwebrx-panel-digimodes[data-mode^="rtty"] #openwebrx-digimode-content-container, #openwebrx-panel-digimodes[data-mode^="bpsk"] #openwebrx-digimode-select-channel, -#openwebrx-panel-digimodes[data-mode^="bpsk"] #openwebrx-digimode-select-channel +#openwebrx-panel-digimodes[data-mode^="rtty"] #openwebrx-digimode-select-channel { display: block; } From 4c2f9a67f64d735ffec8b7786b1479076ca86707 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 17 Sep 2023 04:15:08 +0200 Subject: [PATCH 38/40] activate UTF8 replacement handling --- csdr/module/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csdr/module/__init__.py b/csdr/module/__init__.py index 11e897db..189a75ea 100644 --- a/csdr/module/__init__.py +++ b/csdr/module/__init__.py @@ -222,7 +222,7 @@ class LogReader(Thread): # log all completed lines for line in lines[0:-1]: - self.logger.info("{}: {}".format("STDOUT", line.decode())) + self.logger.info("{}: {}".format("STDOUT", line.decode(errors="replace"))) def stop(self): self.reader.stop() From f1995d3c6bf1a1ed756b100042f389bde3c701e5 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Mon, 18 Sep 2023 23:39:02 +0200 Subject: [PATCH 39/40] handle potentially invalid ascii characters --- owrx/dsp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/owrx/dsp.py b/owrx/dsp.py index 5dd59410..fb33fe8f 100644 --- a/owrx/dsp.py +++ b/owrx/dsp.py @@ -699,7 +699,7 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient) b = data.tobytes() # If we know it's not pickled, let us not unpickle if len(b) < 2 or b[0] != 0x80 or not 3 <= b[1] <= pickle.HIGHEST_PROTOCOL: - callback(b.decode("ascii")) + callback(b.decode("ascii", errors="replace")) return io = BytesIO(b) @@ -709,7 +709,7 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient) except EOFError: pass except pickle.UnpicklingError: - callback(b.decode("ascii")) + callback(b.decode("ascii", errors="replace")) return unpickler From 285afb14d77bc0e5c90980626e892ae090239988 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Thu, 21 Sep 2023 20:51:35 +0200 Subject: [PATCH 40/40] parse additional position data, if available --- htdocs/lib/MessagePanel.js | 5 +++++ owrx/aeronautical.py | 30 +++++++++++++++++++----------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/htdocs/lib/MessagePanel.js b/htdocs/lib/MessagePanel.js index cf7eee72..0bb2d5ac 100644 --- a/htdocs/lib/MessagePanel.js +++ b/htdocs/lib/MessagePanel.js @@ -499,6 +499,11 @@ AircraftMessagePanel.prototype.renderAcars = function(acars) { details += '
Basic ADS-C report
'; details += '
Position: ' + basic_report['lat'] + ', ' + basic_report['lon'] + '
'; details += '
Altitude: ' + basic_report['alt'] + '
'; + } else if ('earth_ref_data' in tag) { + var earth_ref_data = tag['earth_ref_data']; + details += '
Track: ' + earth_ref_data['true_trk_deg'] + '
'; + details += '
Speed: ' + earth_ref_data['gnd_spd_kts'] + ' kt
'; + details += '
Vertical speed: ' + earth_ref_data['vspd_ftmin'] + ' ft/min
'; } else if ('cancel_all_contracts' in tag) { details += '
Cancel all ADS-C contracts
'; } else if ('cancel_contract' in tag) { diff --git a/owrx/aeronautical.py b/owrx/aeronautical.py index b62cc6e5..49ce56f9 100644 --- a/owrx/aeronautical.py +++ b/owrx/aeronautical.py @@ -60,22 +60,30 @@ class AcarsProcessor(JsonParser, metaclass=ABCMeta): arinc622 = acars["arinc622"] if "adsc" in arinc622: adsc = arinc622["adsc"] - if "tags" in adsc: + if "tags" in adsc and adsc["tags"]: + msg = {} for tag in adsc["tags"]: if "basic_report" in tag: basic_report = tag["basic_report"] - msg = { + msg.update({ "lat": basic_report["lat"], "lon": basic_report["lon"], - "altitude": basic_report["alt"] - } - if icao is not None: - source = IcaoSource(icao, flight=flight_id) - else: - source = FlightSource(flight_id) - Map.getSharedInstance().updateLocation( - source, AirplaneLocation(msg), "ACARS over {}".format(self.mode) - ) + "altitude": basic_report["alt"], + }) + if "earth_ref_data" in tag: + earth_ref_data = tag["earth_ref_data"] + msg.update({ + "groundtrack": earth_ref_data["true_trk_deg"], + "groundspeed": earth_ref_data["gnd_spd_kts"], + "verticalspeed": earth_ref_data["vspd_ftmin"], + }) + if icao is not None: + source = IcaoSource(icao, flight=flight_id) + else: + source = FlightSource(flight_id) + Map.getSharedInstance().updateLocation( + source, AirplaneLocation(msg), "ACARS over {}".format(self.mode) + ) def processFlight(self, raw): return self.flightRegex.sub(r"\g<1>\g<2>", raw)