structured callsign data
This commit is contained in:
@ -33,7 +33,7 @@ thirdpartyeRegex = re.compile("^([a-zA-Z0-9-]+)>((([a-zA-Z0-9-]+\\*?,)*)([a-zA-Z
|
||||
messageIdRegex = re.compile("^(.*){([0-9]{1,5})$")
|
||||
|
||||
# regex to filter pseudo "WIDE" path elements
|
||||
widePattern = re.compile("^WIDE[0-9]-[0-9]$")
|
||||
widePattern = re.compile("^WIDE[0-9]$")
|
||||
|
||||
|
||||
def decodeBase91(input):
|
||||
@ -67,12 +67,13 @@ class Ax25Parser(PickleModule):
|
||||
logger.exception("error parsing ax25 frame")
|
||||
|
||||
def extractCallsign(self, input):
|
||||
cs = bytes([b >> 1 for b in input[0:6]]).decode(encoding, "replace").strip()
|
||||
cs = {
|
||||
"callsign": bytes([b >> 1 for b in input[0:6]]).decode(encoding, "replace").strip(),
|
||||
}
|
||||
ssid = (input[6] & 0b00011110) >> 1
|
||||
if ssid > 0:
|
||||
return "{callsign}-{ssid}".format(callsign=cs, ssid=ssid)
|
||||
else:
|
||||
return cs
|
||||
cs["ssid"] = ssid
|
||||
return cs
|
||||
|
||||
|
||||
class WeatherMapping(object):
|
||||
@ -178,7 +179,7 @@ class AprsParser(PickleModule):
|
||||
|
||||
def isDirect(self, aprsData):
|
||||
if "path" in aprsData and len(aprsData["path"]) > 0:
|
||||
hops = [host for host in aprsData["path"] if widePattern.match(host) is None]
|
||||
hops = [host for host in aprsData["path"] if widePattern.match(host["callsign"]) is None]
|
||||
if len(hops) > 0:
|
||||
return False
|
||||
if "type" in aprsData and aprsData["type"] in ["thirdparty", "item", "object"]:
|
||||
@ -207,12 +208,13 @@ class AprsParser(PickleModule):
|
||||
mapData = mapData["data"]
|
||||
if "lat" in mapData and "lon" in mapData:
|
||||
loc = AprsLocation(mapData)
|
||||
source = mapData["source"]
|
||||
source = mapData["source"].copy()
|
||||
# these are special packets, sent on behalf of other entities
|
||||
if "type" in mapData:
|
||||
if mapData["type"] == "item":
|
||||
source = mapData["item"]
|
||||
elif mapData["type"] == "object":
|
||||
source = mapData["object"]
|
||||
if mapData["type"] == "item" and "item" in mapData:
|
||||
source["item"] = mapData["item"]
|
||||
elif mapData["type"] == "object" and "object" in mapData:
|
||||
source["object"] = mapData["object"]
|
||||
Map.getSharedInstance().updateLocation(source, loc, "APRS", self.band)
|
||||
|
||||
def hasCompressedCoordinates(self, raw):
|
||||
@ -345,15 +347,24 @@ class AprsParser(PickleModule):
|
||||
return result
|
||||
|
||||
def parseThirdpartyAprsData(self, information):
|
||||
# in thirdparty packets, the callsign is passed as a string with -SSID suffix...
|
||||
# this seems to be the only case where parsing is necessary, hence this function is inline
|
||||
def parseCallsign(callsign):
|
||||
el = callsign.split('-')
|
||||
result = {"callsign": el[0]}
|
||||
if len(el) > 1:
|
||||
result["ssid"] = int(el[1])
|
||||
return result
|
||||
|
||||
matches = thirdpartyeRegex.match(information)
|
||||
if matches:
|
||||
path = matches.group(2).split(",")
|
||||
destination = next((c.strip("*").upper() for c in path if c.endswith("*")), None)
|
||||
data = self.parseAprsData(
|
||||
{
|
||||
"source": matches.group(1).upper(),
|
||||
"destination": destination,
|
||||
"path": path,
|
||||
"source": parseCallsign(matches.group(1).upper()),
|
||||
"destination": parseCallsign(destination),
|
||||
"path": [parseCallsign(c) for c in path],
|
||||
"data": matches.group(6).encode(encoding),
|
||||
}
|
||||
)
|
||||
@ -531,7 +542,7 @@ class MicEParser(object):
|
||||
|
||||
def parse(self, data):
|
||||
information = data["data"]
|
||||
destination = data["destination"]
|
||||
destination = data["destination"]["callsign"]
|
||||
|
||||
rawLatitude = [self.extractNumber(c) for c in destination[0:6]]
|
||||
lat = self.listToNumber(rawLatitude[0:2]) + self.listToNumber(rawLatitude[2:6]) / 6000
|
||||
|
@ -103,11 +103,11 @@ class Js8Parser(AudioChopperParser):
|
||||
|
||||
if (isinstance(frame, Js8FrameHeartbeat) or isinstance(frame, Js8FrameCompound)) and frame.grid:
|
||||
Map.getSharedInstance().updateLocation(
|
||||
frame.callsign, LocatorLocation(frame.grid), "JS8", band
|
||||
frame.source, LocatorLocation(frame.grid), "JS8", band
|
||||
)
|
||||
ReportingEngine.getSharedInstance().spot(
|
||||
{
|
||||
"callsign": frame.callsign,
|
||||
"source": frame.source,
|
||||
"mode": "JS8",
|
||||
"locator": frame.grid,
|
||||
"freq": freq + frame.freq,
|
||||
|
35
owrx/map.py
35
owrx/map.py
@ -61,13 +61,13 @@ class Map(object):
|
||||
client.write_update(
|
||||
[
|
||||
{
|
||||
"callsign": callsign,
|
||||
"source": record["source"],
|
||||
"location": record["location"].__dict__(),
|
||||
"lastseen": record["updated"].timestamp() * 1000,
|
||||
"mode": record["mode"],
|
||||
"band": record["band"].getName() if record["band"] is not None else None,
|
||||
}
|
||||
for (callsign, record) in self.positions.items()
|
||||
for record in self.positions.values()
|
||||
]
|
||||
)
|
||||
|
||||
@ -77,14 +77,20 @@ class Map(object):
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def updateLocation(self, callsign, loc: Location, mode: str, band: Band = None):
|
||||
def _sourceToKey(self, source):
|
||||
if "ssid" in source:
|
||||
return "{callsign}-{ssid}".format(**source)
|
||||
return source["callsign"]
|
||||
|
||||
def updateLocation(self, source, loc: Location, mode: str, band: Band = None):
|
||||
ts = datetime.now()
|
||||
key = self._sourceToKey(source)
|
||||
with self.positionsLock:
|
||||
self.positions[callsign] = {"location": loc, "updated": ts, "mode": mode, "band": band}
|
||||
self.positions[key] = {"source": source, "location": loc, "updated": ts, "mode": mode, "band": band}
|
||||
self.broadcast(
|
||||
[
|
||||
{
|
||||
"callsign": callsign,
|
||||
"source": source,
|
||||
"location": loc.__dict__(),
|
||||
"lastseen": ts.timestamp() * 1000,
|
||||
"mode": mode,
|
||||
@ -93,17 +99,18 @@ class Map(object):
|
||||
]
|
||||
)
|
||||
|
||||
def touchLocation(self, callsign):
|
||||
def touchLocation(self, source):
|
||||
# not implemented on the client side yet, so do not use!
|
||||
ts = datetime.now()
|
||||
key = self._sourceToKey(source)
|
||||
with self.positionsLock:
|
||||
if callsign in self.positions:
|
||||
self.positions[callsign]["updated"] = ts
|
||||
self.broadcast([{"callsign": callsign, "lastseen": ts.timestamp() * 1000}])
|
||||
if key in self.positions:
|
||||
self.positions[key]["updated"] = ts
|
||||
self.broadcast([{"source": source, "lastseen": ts.timestamp() * 1000}])
|
||||
|
||||
def removeLocation(self, callsign):
|
||||
def removeLocation(self, key):
|
||||
with self.positionsLock:
|
||||
del self.positions[callsign]
|
||||
del self.positions[key]
|
||||
# TODO broadcast removal to clients
|
||||
|
||||
def removeOldPositions(self):
|
||||
@ -111,9 +118,9 @@ class Map(object):
|
||||
retention = timedelta(seconds=pm["map_position_retention_time"])
|
||||
cutoff = datetime.now() - retention
|
||||
|
||||
to_be_removed = [callsign for (callsign, pos) in self.positions.items() if pos["updated"] < cutoff]
|
||||
for callsign in to_be_removed:
|
||||
self.removeLocation(callsign)
|
||||
to_be_removed = [key for (key, pos) in self.positions.items() if pos["updated"] < cutoff]
|
||||
for key in to_be_removed:
|
||||
self.removeLocation(key)
|
||||
|
||||
def rebuildPositions(self):
|
||||
logger.debug("rebuilding map storage; size before: %i", sys.getsizeof(self.positions))
|
||||
|
@ -129,7 +129,7 @@ class DigihamEnricher(Enricher, metaclass=ABCMeta):
|
||||
callsign = self.getCallsign(meta)
|
||||
if callsign is not None and "lat" in meta and "lon" in meta:
|
||||
loc = LatLngLocation(meta["lat"], meta["lon"])
|
||||
Map.getSharedInstance().updateLocation(callsign, loc, mode, self.parser.getBand())
|
||||
Map.getSharedInstance().updateLocation({"callsign": callsign}, loc, mode, self.parser.getBand())
|
||||
return meta
|
||||
|
||||
@abstractmethod
|
||||
@ -202,7 +202,7 @@ class DStarEnricher(DigihamEnricher):
|
||||
if "ourcall" in meta:
|
||||
# send location info to map as well (it will show up with the correct symbol there!)
|
||||
loc = AprsLocation(data)
|
||||
Map.getSharedInstance().updateLocation(meta["ourcall"], loc, "DPRS", self.parser.getBand())
|
||||
Map.getSharedInstance().updateLocation({"callsign": meta["ourcall"]}, loc, "DPRS", self.parser.getBand())
|
||||
except Exception:
|
||||
logger.exception("Error while parsing DPRS data")
|
||||
|
||||
|
@ -56,7 +56,7 @@ class PskReporter(Reporter):
|
||||
self.timer.start()
|
||||
|
||||
def spotEquals(self, s1, s2):
|
||||
keys = ["callsign", "timestamp", "locator", "mode", "msg"]
|
||||
keys = ["source", "timestamp", "locator", "mode", "msg"]
|
||||
|
||||
return reduce(and_, map(lambda key: s1[key] == s2[key], keys))
|
||||
|
||||
@ -141,7 +141,7 @@ class Uploader(object):
|
||||
def encodeSpot(self, spot):
|
||||
try:
|
||||
return bytes(
|
||||
self.encodeString(spot["callsign"])
|
||||
self.encodeString(spot["source"]["callsign"])
|
||||
+ list(int(spot["freq"]).to_bytes(4, "big"))
|
||||
+ list(int(spot["db"]).to_bytes(1, "big", signed=True))
|
||||
+ self.encodeString(spot["mode"])
|
||||
|
@ -56,7 +56,7 @@ class Worker(threading.Thread):
|
||||
# FST4W does not have drift
|
||||
"drift": spot["drift"] if "drift" in spot else 0,
|
||||
"tqrg": spot["freq"] / 1e6,
|
||||
"tcall": spot["callsign"],
|
||||
"tcall": spot["source"]["callsign"],
|
||||
"tgrid": spot["locator"],
|
||||
"dbm": spot["dbm"],
|
||||
"version": openwebrx_version,
|
||||
|
10
owrx/wsjt.py
10
owrx/wsjt.py
@ -276,9 +276,9 @@ class WsjtParser(AudioChopperParser):
|
||||
out["interval"] = profile.getInterval()
|
||||
|
||||
self.pushDecode(mode, band)
|
||||
if "callsign" in out and "locator" in out:
|
||||
if "source" in out and "locator" in out:
|
||||
Map.getSharedInstance().updateLocation(
|
||||
out["callsign"], LocatorLocation(out["locator"]), mode, band
|
||||
out["source"], LocatorLocation(out["locator"]), mode, band
|
||||
)
|
||||
ReportingEngine.getSharedInstance().spot(out)
|
||||
|
||||
@ -342,8 +342,8 @@ class QsoMessageParser(MessageParser):
|
||||
# this is a valid locator in theory, but it's somewhere in the arctic ocean, near the north pole, so it's very
|
||||
# likely this just means roger roger goodbye.
|
||||
if m.group(3) == "RR73":
|
||||
return {"callsign": m.group(1)}
|
||||
return {"callsign": m.group(1), "locator": m.group(3)}
|
||||
return {"source": {"callsign": m.group(1)}}
|
||||
return {"source": {"callsign": m.group(1)}, "locator": m.group(3)}
|
||||
|
||||
|
||||
# Used in propagation reporting / beacon modes (WSPR / FST4W)
|
||||
@ -354,7 +354,7 @@ class BeaconMessageParser(MessageParser):
|
||||
m = BeaconMessageParser.wspr_splitter_pattern.match(msg)
|
||||
if m is None:
|
||||
return {}
|
||||
return {"callsign": m.group(1), "locator": m.group(2), "dbm": m.group(3)}
|
||||
return {"source": {"callsign": m.group(1)}, "locator": m.group(2), "dbm": m.group(3)}
|
||||
|
||||
|
||||
class Jt9Decoder(Decoder):
|
||||
|
Reference in New Issue
Block a user