diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 078c7e7..fbd52f9 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -4378,73 +4378,100 @@ sudo make install return div.innerHTML; } + // Unwrap dumpvdl2 JSON: data may have fields at top level or nested under data.vdl2 + function unwrapVdl2(data) { + if (data.vdl2 && typeof data.vdl2 === 'object') return data.vdl2; + return data; + } + function showVdl2Modal(data, timeStr) { // Remove any existing modal const existing = document.querySelector('.vdl2-modal-overlay'); if (existing) existing.remove(); - const avlc = data.avlc || {}; + const inner = unwrapVdl2(data); + const avlc = inner.avlc || {}; const src = avlc.src || {}; const dst = avlc.dst || {}; const acars = avlc.acars || {}; const xid = avlc.xid || {}; const flight = acars.flight || ''; - const freq = data.freq ? (data.freq / 1000000).toFixed(3) + ' MHz' : ''; + const freq = inner.freq ? (inner.freq / 1000000).toFixed(3) + ' MHz' : ''; const title = flight || src.addr || 'VDL2 Message'; - // Build connection fields - let connectionHtml = ''; - const connFields = []; - if (data.station) connFields.push(['Station', String(data.station)]); - if (freq) connFields.push(['Frequency', freq]); - if (src.addr) connFields.push(['Source', String(src.addr) + (src.type ? ` (${src.type})` : '')]); - if (dst.addr) connFields.push(['Destination', String(dst.addr) + (dst.type ? ` (${dst.type})` : '')]); - if (avlc.cr) connFields.push(['Cmd/Response', avlc.cr]); - if (avlc.frame_type) connFields.push(['Frame Type', avlc.frame_type]); - if (data.agent_name) connFields.push(['Agent', data.agent_name]); - if (connFields.length) { - connectionHtml = `
-
Connection
-
${connFields.map(([l, v]) => - `
${escapeHtml(l)}${escapeHtml(v)}
` + // Build section helper + function buildSection(titleText, fields) { + if (!fields.length) return ''; + return `
+
${titleText}
+
${fields.map(([l, v]) => + `
${escapeHtml(l)}${escapeHtml(String(v))}
` ).join('')}
`; } - // Build ACARS fields - let acarsHtml = ''; + // Radio / signal fields + const radioFields = []; + if (freq) radioFields.push(['Frequency', freq]); + if (inner.sig_level != null) radioFields.push(['Signal', inner.sig_level.toFixed(1) + ' dB']); + if (inner.noise_level != null) radioFields.push(['Noise', inner.noise_level.toFixed(1) + ' dB']); + if (inner.sig_level != null && inner.noise_level != null) radioFields.push(['SNR', (inner.sig_level - inner.noise_level).toFixed(1) + ' dB']); + if (inner.freq_skew != null) radioFields.push(['Freq Skew', inner.freq_skew.toFixed(2) + ' Hz']); + if (inner.burst_len_octets != null) radioFields.push(['Burst Length', inner.burst_len_octets + ' octets']); + if (inner.octets_corrected_by_fec != null) radioFields.push(['FEC Corrections', String(inner.octets_corrected_by_fec)]); + if (inner.hdr_bits_fixed != null) radioFields.push(['Header Bits Fixed', String(inner.hdr_bits_fixed)]); + if (inner.idx != null) radioFields.push(['SDR Index', String(inner.idx)]); + if (inner.station) radioFields.push(['Station', String(inner.station)]); + if (data.agent_name) radioFields.push(['Agent', data.agent_name]); + const radioHtml = buildSection('Radio', radioFields); + + // Timestamp from dumpvdl2 + let tsStr = ''; + if (inner.t) { + const d = new Date(inner.t.sec * 1000); + tsStr = d.toISOString().replace('T', ' ').replace('Z', '') + ' UTC'; + } + + // AVLC frame fields + const frameFields = []; + if (src.addr) frameFields.push(['Source', String(src.addr) + (src.type ? ' (' + src.type + ')' : '')]); + if (dst.addr) frameFields.push(['Destination', String(dst.addr) + (dst.type ? ' (' + dst.type + ')' : '')]); + if (avlc.cr) frameFields.push(['Cmd/Response', avlc.cr]); + if (avlc.frame_type) frameFields.push(['Frame Type', avlc.frame_type]); + if (avlc.rseq != null) frameFields.push(['R-Seq', String(avlc.rseq)]); + if (avlc.sseq != null) frameFields.push(['S-Seq', String(avlc.sseq)]); + if (avlc.poll != null) frameFields.push(['Poll/Final', avlc.poll ? 'Yes' : 'No']); + const frameHtml = buildSection('AVLC Frame', frameFields); + + // ACARS fields const acarsFields = []; if (acars.reg) acarsFields.push(['Registration', acars.reg]); if (acars.flight) acarsFields.push(['Flight', acars.flight]); if (acars.mode) acarsFields.push(['Mode', acars.mode]); if (acars.label) acarsFields.push(['Label', acars.label]); + if (acars.sublabel) acarsFields.push(['Sublabel', acars.sublabel]); if (acars.blk_id) acarsFields.push(['Block ID', acars.blk_id]); - if (acars.ack) acarsFields.push(['ACK', acars.ack]); + if (acars.ack) acarsFields.push(['ACK', acars.ack === '!' ? 'NAK' : acars.ack]); if (acars.msg_num) acarsFields.push(['Msg Number', acars.msg_num]); if (acars.msg_num_seq) acarsFields.push(['Msg Seq', acars.msg_num_seq]); - if (acarsFields.length) { - acarsHtml = `
-
ACARS
-
${acarsFields.map(([l, v]) => - `
${escapeHtml(l)}${escapeHtml(v)}
` - ).join('')}
-
`; - } + if (acars.more != null) acarsFields.push(['More Follows', acars.more ? 'Yes' : 'No']); + const acarsHtml = buildSection('ACARS', acarsFields); - // Position from XID - let posHtml = ''; - if (xid.vdl_params?.ac_location) { - const loc = xid.vdl_params.ac_location; - if (loc.lat !== undefined && loc.lon !== undefined) { - posHtml = `
-
Position
-
-
Latitude${loc.lat.toFixed(4)}
-
Longitude${loc.lon.toFixed(4)}
-
-
`; + // XID fields + const xidFields = []; + if (xid.type_descr) xidFields.push(['Type', xid.type_descr]); + if (xid.vdl_params) { + const params = xid.vdl_params; + if (params.ac_location) { + const loc = params.ac_location; + if (loc.lat != null) xidFields.push(['Latitude', loc.lat.toFixed(4)]); + if (loc.lon != null) xidFields.push(['Longitude', loc.lon.toFixed(4)]); + if (loc.alt != null) xidFields.push(['Altitude', loc.alt + ' ft']); } + if (params.dst_airport) xidFields.push(['Destination', params.dst_airport]); + if (params.modulation_support) xidFields.push(['Modulation', params.modulation_support]); } + const xidHtml = buildSection('XID', xidFields); // Message body const msgText = acars.msg_text || ''; @@ -4471,14 +4498,15 @@ sudo make install
${escapeHtml(title)}
-
${escapeHtml(timeStr)}${freq ? ' · ' + escapeHtml(freq) : ''}
+
${escapeHtml(tsStr || timeStr)}${freq ? ' · ' + escapeHtml(freq) : ''}
- ${connectionHtml} + ${radioHtml} + ${frameHtml} ${acarsHtml} - ${posHtml} + ${xidHtml} ${bodyHtml} ${rawHtml}
@@ -4514,14 +4542,15 @@ sudo make install const msg = document.createElement('div'); msg.className = 'vdl2-message-item'; - const avlc = data.avlc || {}; + const inner = unwrapVdl2(data); + const avlc = inner.avlc || {}; const src = avlc.src?.addr || ''; const acars = avlc.acars || {}; const flight = acars.flight || ''; const msgText = acars.msg_text || ''; const time = new Date().toLocaleTimeString(); - const freq = data.freq ? (data.freq / 1000000).toFixed(3) : ''; - const label = flight || src || 'VDL2'; + const freq = inner.freq ? (inner.freq / 1000000).toFixed(3) : ''; + const label = flight || src || (avlc.frame_type || 'VDL2'); msg.innerHTML = `