parse and display some information

This commit is contained in:
Jakob Ketterl 2023-08-23 00:40:24 +02:00
parent 10337f7db8
commit 25ae3359ea
4 changed files with 157 additions and 17 deletions

View file

@ -1,7 +1,8 @@
from pycsdr.modules import Convert
from pycsdr.types import Format
from csdr.chain.demodulator import ServiceDemodulator
from owrx.adsb.dump1090 import Dump1090Module, RawDeframer, ModeSParser
from owrx.adsb.dump1090 import Dump1090Module, RawDeframer
from owrx.adsb.modes import ModeSParser
class Dump1090(ServiceDemodulator):

View file

@ -272,6 +272,7 @@ $.fn.pocsagMessagePanel = function() {
AdsbMessagePanel = function(el) {
MessagePanel.call(this, el);
this.aircraft = {}
this.initClearTimer();
}
@ -286,7 +287,11 @@ AdsbMessagePanel.prototype.render = function() {
'<table>' +
'<thead><tr>' +
'<th class="address">ICAO</th>' +
'<th class="message">Message</th>' +
'<th class="callsign">Callsign</th>' +
'<th class="altitude">Altitude</th>' +
'<th class="speed">Speed</th>' +
'<th class="track">Track</th>' +
'<th class="messages">Messages</th>' +
'</tr></thead>' +
'<tbody></tbody>' +
'</table>'
@ -295,16 +300,64 @@ AdsbMessagePanel.prototype.render = function() {
AdsbMessagePanel.prototype.pushMessage = function(message) {
if (!('icao' in message)) return;
if (!(message.icao in this.aircraft)) {
var el = $("<tr>");
$(this.el).find('tbody').append(el);
this.aircraft[message.icao] = {
el: el,
messages: 0
}
}
var state = this.aircraft[message.icao];
Object.assign(state, message);
state.lastSeen = Date.now();
state.messages += 1;
var ifDefined = function(input, formatter) {
if (typeof(input) !== 'undefined') {
if (formatter) return formatter(input);
return input;
}
return "";
}
state.el.html(
'<td>' + state.icao + '</td>' +
'<td>' + ifDefined(state.identification) + '</td>' +
'<td>' + ifDefined(state.altitude) + '</td>' +
'<td>' + ifDefined(state.groundspeed, Math.round) + '</td>' +
'<td>' + ifDefined(state.groundtrack, Math.round) + '</td>' +
'<td>' + state.messages + '</td>'
);
var $b = $(this.el).find('tbody');
$b.append($(
'<tr>' +
'<td class="address"></td>' +
'<td class="message">' + JSON.stringify(message) + '</td>' +
'</tr>'
));
$b.scrollTop($b[0].scrollHeight);
};
AdsbMessagePanel.prototype.clearMessages = function(toRemain) {
console.info("clearing old aircraft...");
var now = Date.now();
var me = this;
Object.entries(this.aircraft).forEach(function(e) {
if (now - e[1].lastSeen > toRemain) {
console.info("removing " + e[0]);
delete me.aircraft[e[0]];
e[1].el.remove();
}
})
console.info("done; tracking " + Object.keys(this.aircraft).length + " aircraft");
};
AdsbMessagePanel.prototype.initClearTimer = function() {
var me = this;
if (me.removalInterval) clearInterval(me.removalInterval);
me.removalInterval = setInterval(function () {
me.clearMessages(30000);
}, 15000);
};
$.fn.adsbMessagePanel = function () {
if (!this.data('panel')) {
this.data('panel', new AdsbMessagePanel(this));

View file

@ -1,6 +1,6 @@
from pycsdr.modules import ExecModule, Writer, TcpSource
from pycsdr.types import Format
from csdr.module import LogWriter, ThreadModule, PickleModule
from csdr.module import LogWriter, ThreadModule
from owrx.socket import getAvailablePort
import time
import pickle
@ -81,11 +81,3 @@ class RawDeframer(ThreadModule):
return bytes.fromhex(line[1:-1].decode())
else:
logger.warning("invalid raw message: %s", line)
class ModeSParser(PickleModule):
def process(self, input):
return {
"mode": "ADSB",
"df": (input[0] & 0b11111000) >> 3
}

94
owrx/adsb/modes.py Normal file
View file

@ -0,0 +1,94 @@
from csdr.module import PickleModule
from math import sqrt, atan2, pi
import logging
logger = logging.getLogger(__name__)
FEET_PER_METER = 3.28084
class ModeSParser(PickleModule):
def process(self, input):
format = (input[0] & 0b11111000) >> 3
message = {
"mode": "ADSB",
"format": format
}
if format == 17:
message["capability"] = input[0] & 0b111
message["icao"] = input[1:4].hex()
type = (input[4] & 0b11111000) >> 3
message["type"] = type
if type in range(1, 5):
# identification message
id = [
(input[5] & 0b11111100) >> 2,
((input[5] & 0b00000011) << 4) | ((input[6] & 0b11110000) >> 4),
((input[6] & 0b00001111) << 2) | ((input[7] & 0b11000000) >> 6),
input[7] & 0b00111111,
(input[8] & 0b11111100) >> 2,
((input[8] & 0b00000011) << 4) | ((input[9] & 0b11110000) >> 4),
((input[9] & 0b00001111) << 2) | ((input[10] & 0b11000000) >> 6),
input[10] & 0b00111111
]
message["identification"] = bytes(b + (0x40 if b < 27 else 0) for b in id).decode("ascii")
elif type in range(5, 9):
# surface position
pass
elif type in range(9, 19):
# airborne position (w/ baro altitude)
q = (input[5] & 0b1)
altitude = ((input[5] & 0b11111110) << 3) | ((input[6] & 0b1111) >> 4)
if q:
message["altitude"] = altitude * 25 - 1000
else:
# TODO: it's gray encoded
message["altitude"] = altitude * 100
elif type == 19:
# airborne velocity
subtype = input[4] & 0b111
if subtype in range(1, 3):
dew = (input[5] & 0b00000100) >> 2
vew = ((input[5] & 0b00000011) << 8) | input[6]
dns = (input[7] & 0b10000000) >> 7
vns = ((input[7] & 0b01111111) << 3) | ((input[8] & 0b1110000000) >> 5)
vx = vew - 1
if dew:
vx *= -1
vy = vns - 1
if dns:
vy *= -1
# supersonic
if subtype == 2:
vx *= 4
vy *= 4
message["groundspeed"] = sqrt(vx ** 2 + vy ** 2)
message["groundtrack"] = (atan2(vx, vy) * 360 / (2 * pi)) % 360
else:
logger.debug("subtype: %i", subtype)
elif type in range(20, 23):
# airborne position (w/GNSS height)
altitude = (input[5] << 4) | ((input[6] & 0b1111) >> 4)
message["altitude"] = altitude * FEET_PER_METER
elif type == 28:
# aircraft status
pass
elif type == 29:
# target state and status information
pass
elif type == 31:
# aircraft operation status
pass
return message