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.modules import Convert
from pycsdr.types import Format from pycsdr.types import Format
from csdr.chain.demodulator import ServiceDemodulator 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): class Dump1090(ServiceDemodulator):

View File

@ -272,6 +272,7 @@ $.fn.pocsagMessagePanel = function() {
AdsbMessagePanel = function(el) { AdsbMessagePanel = function(el) {
MessagePanel.call(this, el); MessagePanel.call(this, el);
this.aircraft = {}
this.initClearTimer(); this.initClearTimer();
} }
@ -286,7 +287,11 @@ AdsbMessagePanel.prototype.render = function() {
'<table>' + '<table>' +
'<thead><tr>' + '<thead><tr>' +
'<th class="address">ICAO</th>' + '<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>' + '</tr></thead>' +
'<tbody></tbody>' + '<tbody></tbody>' +
'</table>' '</table>'
@ -295,16 +300,64 @@ AdsbMessagePanel.prototype.render = function() {
AdsbMessagePanel.prototype.pushMessage = function(message) { 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'); 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); $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 () { $.fn.adsbMessagePanel = function () {
if (!this.data('panel')) { if (!this.data('panel')) {
this.data('panel', new AdsbMessagePanel(this)); this.data('panel', new AdsbMessagePanel(this));

View File

@ -1,6 +1,6 @@
from pycsdr.modules import ExecModule, Writer, TcpSource from pycsdr.modules import ExecModule, Writer, TcpSource
from pycsdr.types import Format from pycsdr.types import Format
from csdr.module import LogWriter, ThreadModule, PickleModule from csdr.module import LogWriter, ThreadModule
from owrx.socket import getAvailablePort from owrx.socket import getAvailablePort
import time import time
import pickle import pickle
@ -81,11 +81,3 @@ class RawDeframer(ThreadModule):
return bytes.fromhex(line[1:-1].decode()) return bytes.fromhex(line[1:-1].decode())
else: else:
logger.warning("invalid raw message: %s", line) 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