function MessagePanel(el) {
this.el = el;
this.render();
this.initClearButton();
}
MessagePanel.prototype.supportsMessage = function(message) {
return false;
};
MessagePanel.prototype.render = function() {
};
MessagePanel.prototype.pushMessage = function(message) {
};
// automatic clearing is not enabled by default. call this method from the constructor to enable
MessagePanel.prototype.initClearTimer = function() {
var me = this;
if (me.removalInterval) clearInterval(me.removalInterval);
me.removalInterval = setInterval(function () {
me.clearMessages(1000);
}, 15000);
};
MessagePanel.prototype.clearMessages = function(toRemain) {
var $elements = $(this.el).find('tbody tr');
// limit to 1000 entries in the list since browsers get laggy at some point
var toRemove = $elements.length - toRemain;
if (toRemove <= 0) return;
$elements.slice(0, toRemove).remove();
};
MessagePanel.prototype.initClearButton = function() {
var me = this;
me.clearButton = $(
'
");
$(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 "";
}
var coordRound = function(i) {
return Math.round(i * 1000) / 1000;
}
var getPosition = function(state) {
if (!('lat' in state) || !('lon') in state) return '';
return '' + coordRound(state.lat) + ', ' + coordRound(state.lon) + '';
}
state.el.html(
'| ' + this.linkify(state, state.icao) + ' | ' +
'' + this.linkify(state, ifDefined(state.identification)) + ' | ' +
'' + ifDefined(state.altitude) + ' | ' +
'' + ifDefined(state.groundspeed || state.IAS || state.TAS, Math.round) + ' | ' +
'' + ifDefined(state.groundtrack || state.heading, Math.round) + ' | ' +
'' + ifDefined(state.verticalspeed) + ' | ' +
'' + getPosition(state) + ' | ' +
'' + state.messages + ' | '
);
};
AdsbMessagePanel.prototype.clearMessages = function(toRemain) {
var now = Date.now();
var me = this;
Object.entries(this.aircraft).forEach(function(e) {
if (now - e[1].lastSeen > toRemain) {
delete me.aircraft[e[0]];
e[1].el.remove();
}
})
};
AdsbMessagePanel.prototype.initClearTimer = function() {
var me = this;
if (me.removalInterval) clearInterval(me.removalInterval);
me.removalInterval = setInterval(function () {
me.clearMessages(30000);
}, 15000);
};
AdsbMessagePanel.prototype.setAircraftTrackingService = function(service) {
this.aircraftTrackingService = service;
};
AdsbMessagePanel.prototype.linkify = function(state, text) {
var link = false;
switch (this.aircraftTrackingService) {
case 'flightaware':
link = 'https://flightaware.com/live/modes/' + state.icao;
if (state.identification) link += "/ident/" + state.identification
link += '/redirect';
break;
case 'planefinder':
if (state.identification) link = 'https://planefinder.net/flight/' + state.identification;
break;
}
if (link) {
return '' + text + '';
}
return text;
};
$.fn.adsbMessagePanel = function () {
if (!this.data('panel')) {
this.data('panel', new AdsbMessagePanel(this));
}
return this.data('panel');
};
IsmMessagePanel = function(el) {
MessagePanel.call(this, el);
this.initClearTimer();
};
IsmMessagePanel.prototype = Object.create(MessagePanel.prototype);
IsmMessagePanel.prototype.supportsMessage = function(message) {
return message['mode'] === 'ISM';
};
IsmMessagePanel.prototype.render = function() {
$(this.el).append($(
'' +
'' +
'| Model | ' +
'ID | ' +
'Channel | ' +
'Data | ' +
'
' +
'' +
'
'
));
};
IsmMessagePanel.prototype.pushMessage = function(message) {
var $b = $(this.el).find('tbody');
var ifDefined = function(input, formatter) {
if (typeof(input) !== 'undefined') {
if (formatter) return formatter(input);
return input;
}
return "";
}
var mergeRemainingMessage = function(input, exclude) {
return Object.entries(input).map(function(entry) {
if (exclude.includes(entry[0])) return '';
return entry[0] + ': ' + entry[1] + ';';
}).join(' ');
}
$b.append($(
'
Current link: ' + mediaadv['current_link']['descr'];
}
if ('links_avail' in mediaadv) {
details += '
Available links: ' + mediaadv['links_avail'].map(function (l) {
return l['descr'];
}).join(', ') + '
';
}
} else if ('arinc622' in acars) {
var arinc622 = acars['arinc622'];
if ('adsc' in arinc622) {
var adsc = arinc622['adsc'];
if ('tags' in adsc) {
adsc['tags'].forEach(function(tag) {
if ('basic_report' in tag) {
var basic_report = tag['basic_report'];
details += '
Basic report
';
details += '
Position: ' + basic_report['lat'] + ', ' + basic_report['lon'] + '
';
details += '
Altitude: ' + basic_report['alt'] + '
';
} else {
details += '
Unsupported tag
';
}
});
} else {
details += '
Other ADS-C data
';
}
}
} else {
// plain text
details += '
Label: ' + acars['label'] + '
';
details += '
' + acars['msg_text'] + '
';
}
return details;
}
HfdlMessagePanel = function(el) {
AircraftMessagePanel.call(this, el);
this.initClearTimer();
}
HfdlMessagePanel.prototype = Object.create(AircraftMessagePanel.prototype);
HfdlMessagePanel.prototype.render = function() {
$(this.el).append($(
'
' +
'' +
'| Source | ' +
'Destination | ' +
'Details | ' +
'
' +
'' +
'
'
));
};
HfdlMessagePanel.prototype.supportsMessage = function(message) {
return message['mode'] === 'HFDL';
};
HfdlMessagePanel.prototype.pushMessage = function(message) {
var $b = $(this.el).find('tbody');
var src = '';
var dst = '';
var details = JSON.stringify(message);
var renderAddress = function(a) {
return a['id'];
}
// TODO remove safety net once parsing is complete
try {
var payload = message['hfdl'];
if ('spdu' in payload) {
var spdu = payload['spdu'];
src = renderAddress(spdu['src']);
details = '
HFDL Squitter message
'
details += '
Systable version: ' + spdu['systable_version'] + '
';
if ('gs_status' in spdu) {
details += spdu['gs_status'].map(function(gs){
return '
Ground station ' + gs['gs']['id'] + ' is operating on frequency ids ' + gs['freqs'].map(function(f) {return f['id']; }).join(', ') + '
';
}).join('')
}
} else if ('lpdu' in payload) {
var lpdu = payload['lpdu'];
src = renderAddress(lpdu['src']);
dst = renderAddress(lpdu['dst']);
if (lpdu['type']['id'] === 13 || lpdu['type']['id'] === 29) {
// unnumbered data
var hfnpdu = lpdu['hfnpdu'];
if (hfnpdu['type']['id'] === 209) {
// 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'] + '
';
}
}
} else if (hfnpdu['type']['id'] === 255) {
// enveloped data
if ('acars' in hfnpdu) {
details = this.renderAcars(hfnpdu['acars']);
}
}
} else if (lpdu['type']['id'] === 47) {
// logon denied
details = '
Logon denied
';
} else if (lpdu['type']['id'] === 63) {
details = '
Logoff request
';
if (lpdu['ac_info'] && 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'];
}
} 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'];
}
} else if (lpdu['type']['id'] === 159) {
details = '
Logon confirmation
';
if (lpdu['ac_info'] && 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'];
}
}
}
} catch (e) {
console.error(e, e.stack);
}
$b.append($(
'
' +
'| ' + src + ' | ' +
'' + dst + ' | ' +
'' + details + ' | ' +
'
'
));
this.scrollToBottom();
};
$.fn.hfdlMessagePanel = function() {
if (!this.data('panel')) {
this.data('panel', new HfdlMessagePanel(this));
}
return this.data('panel');
};
Vdl2MessagePanel = function(el) {
AircraftMessagePanel.call(this, el);
this.initClearTimer();
}
Vdl2MessagePanel.prototype = Object.create(AircraftMessagePanel.prototype);
Vdl2MessagePanel.prototype.render = function() {
$(this.el).append($(
'
' +
'' +
'| Source | ' +
'Destination | ' +
'Details | ' +
'
' +
'' +
'
'
));
};
Vdl2MessagePanel.prototype.supportsMessage = function(message) {
return message['mode'] === 'VDL2';
};
Vdl2MessagePanel.prototype.pushMessage = function(message) {
var $b = $(this.el).find('tbody');
var src = '';
var dst = '';
var details = JSON.stringify(message);
var renderAddress = function(a) {
return '
' + a['addr'] + '
' + a['type'] + ( 'status' in a ? ' (' + a['status'] + ')' : '' ) + '
'
}
// TODO remove safety net once parsing is complete
try {
var payload = message['vdl2'];
if ('avlc' in payload) {
var avlc = payload['avlc'];
src = renderAddress(avlc['src']);
dst = renderAddress(avlc['dst']);
if (avlc['frame_type'] === 'S') {
details = '
Supervisory frame
';
if (avlc['cmd'] === 'Receive Ready') {
details = '
Receive Ready
';
}
} else if (avlc['frame_type'] === 'I') {
details = '
Information frame
';
if ('acars' in avlc) {
details = this.renderAcars(avlc['acars']);
} else if ('x25' in avlc) {
var x25 = avlc['x25'];
if (!('reasm_status' in x25) || ['skipped', 'complete'].includes(x25['reasm_status'])) {
details = '
X.25 frame
';
if ('clnp' in x25) {
var clnp = x25['clnp']
if ('cotp' in clnp) {
var cotp = clnp['cotp'];
if ('cpdlc' in cotp) {
var cpdlc = cotp['cpdlc'];
details = '
CPDLC
';
if ('atc_downlink_message' in cpdlc) {
var atc_downlink_message = cpdlc['atc_downlink_message'];
if ('msg_data' in atc_downlink_message) {
var msg_data = atc_downlink_message['msg_data'];
if ('msg_elements' in msg_data) {
details += '
' + msg_data['msg_elements'].map(function(e) { return e['msg_element']['choice_label']; }).join(', ') + '
';
}
} else {
details += '
' + JSON.stringify(cpdlc) + '
';
}
}
}
}
}
} else {
details = '
Partial X.25 frame
';
}
}
} else if (avlc['frame_type'] === 'U') {
details = '
Unnumbered frame
';
if ('xid' in avlc) {
var xid = avlc['xid'];
details = '
' + xid['type_descr'] + '
';
}
}
}
} catch (e) {
console.error(e, e.stack);
}
$b.append($(
'
' +
'| ' + src + ' | ' +
'' + dst + ' | ' +
'' + details + ' | ' +
'
'
));
this.scrollToBottom();
};
$.fn.vdl2MessagePanel = function() {
if (!this.data('panel')) {
this.data('panel', new Vdl2MessagePanel(this));
}
return this.data('panel');
};