P25 conventional decode mode + conventional-channel attribution (node-26#24, #20)
op25-container
- generate-config takes p25_mode "conventional" + conv_channels [{frequency,
name, nac}]. It builds fixed fsk4 channels (up to 2 on one non-tunable SDR,
DC spike kept off the channels) with no control channel to hunt. Two
channels send audio to UDP 23456/23457, and audio.py -2 + amplify(2) keeps
levels.
- tk_drb_conv.py (a tk_p25.rx_ctl subclass, copied into OP25 apps) reports
voice / NAC / encryption (LC service options + LDU2 ESS algid/keyid) for
conventional channels and applies the per-channel NAC filter, which stock
tk_p25 does not. Analog uses it too.
- Trunked P25 config is unchanged when p25_mode is absent.
drb-edge-node
- radio_config: system kind from type + config.mode (missing = trunked).
- conventional.py normalizes the console: a distinctive LC TGID, else the
channel's last TGID (30 s), else a synthetic per-channel id (freq/100 Hz).
Encrypted rows are never talkers. With exactly one conventional channel,
orphan audio is attributed to it at close, which fixes analog recording
(#20). The synthetic id and the real TGID on one channel never split.
- Health/dashboard: P25 conventional listen tab (/api/listen p25conv), and
conventional decode + encryption checks. The checkin carries radio_channels.
- Tests: edge 296 pass; op25-container 18 (new CI job). Reviewed by
drb-audio-review (findings fixed; simultaneous two-channel mixdown filed
as #26) and drb-qa-review.
Not verified on SDR hardware.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1e1dab66b1
commit
d07d312e92
@@ -167,6 +167,7 @@
|
||||
|
||||
<div class="tabs" role="radiogroup" aria-label="Source type">
|
||||
<label><input type="radio" name="mode" value="p25" checked> P25 trunked</label>
|
||||
<label><input type="radio" name="mode" value="p25conv"> P25 conventional</label>
|
||||
<label><input type="radio" name="mode" value="analog"> Analog FM</label>
|
||||
<label><input type="radio" name="mode" value="system" id="mode-system"> Server's systems</label>
|
||||
</div>
|
||||
@@ -188,6 +189,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pane" data-pane="p25conv" hidden>
|
||||
<div class="field">
|
||||
<label for="pc-freqs">Channel frequencies (MHz)</label>
|
||||
<input type="text" id="pc-freqs" class="mono" placeholder="460.4000, 460.4250" inputmode="decimal">
|
||||
<span class="hint">One or two voice channels, comma-separated, within about 1.5 MHz of each other. No control channel — RadioReference lists these as conventional frequencies with mode P25 (or "DE").</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="pc-name">Name <span class="hint">(optional)</span></label>
|
||||
<input type="text" id="pc-name" placeholder="City PD">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="pc-nac">NAC <span class="hint">(optional, hex)</span></label>
|
||||
<input type="text" id="pc-nac" class="mono" placeholder="293">
|
||||
<span class="hint">RadioReference's NAC for these channels. Leave empty to accept any.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pane" data-pane="analog" hidden>
|
||||
<div class="field">
|
||||
<label for="an-freq">Frequency (MHz)</label>
|
||||
@@ -307,7 +325,7 @@
|
||||
$('node-name').textContent = d.node_name || 'DRB Edge Node';
|
||||
$('node-id').textContent = d.node_id || '';
|
||||
document.title = `${d.node_name || 'DRB Edge Node'} — DRB`;
|
||||
$('now-system').textContent = d.system_name ? `${d.system_name} (${d.system_type})` : 'Nothing chosen yet';
|
||||
$('now-system').textContent = d.system_name ? `${d.system_name} (${d.system_kind || d.system_type})` : 'Nothing chosen yet';
|
||||
$('now-tg').innerHTML = d.active_tgid
|
||||
? `<span class="live">${esc(d.active_tgid_name || 'Talkgroup')}</span> <span class="mono">TG ${esc(d.active_tgid)}</span>`
|
||||
: 'No call right now';
|
||||
@@ -335,7 +353,7 @@
|
||||
actions.hidden = true;
|
||||
return;
|
||||
}
|
||||
cur.innerHTML = `Now: <strong>${esc(d.system_name)}</strong> (${esc(d.system_type)}) — ` +
|
||||
cur.innerHTML = `Now: <strong>${esc(d.system_name)}</strong> (${esc(d.system_kind || d.system_type)}) — ` +
|
||||
(d.local_choice ? 'chosen on this dashboard.' : 'assigned by the DRB server.');
|
||||
actions.hidden = !d.local_choice;
|
||||
$('revert-btn').textContent = d.has_c2_system ? "Go back to the server's system" : 'Clear and stop';
|
||||
@@ -387,7 +405,9 @@
|
||||
try { systems = await api('/api/systems'); } catch (e) { systems = []; }
|
||||
const pick = $('sys-pick');
|
||||
const usable = systems.filter(s => String(s.type).toUpperCase() !== 'DMR');
|
||||
pick.innerHTML = usable.map(s => `<option value="${esc(s.system_id)}">${esc(s.name)} (${esc(s.type)})</option>`).join('');
|
||||
const kind = s => String(s.type).toUpperCase() === 'P25' && s.config && s.config.mode === 'conventional'
|
||||
? 'P25 conventional' : s.type;
|
||||
pick.innerHTML = usable.map(s => `<option value="${esc(s.system_id)}">${esc(s.name)} (${esc(kind(s))})</option>`).join('');
|
||||
const none = usable.length === 0;
|
||||
$('mode-system').disabled = none;
|
||||
pick.disabled = none;
|
||||
@@ -416,6 +436,10 @@
|
||||
tgs.push({ id: Number(mm[1]), name: mm[2] });
|
||||
}
|
||||
body = { mode: 'p25', frequencies: freqs, name: $('p25-name').value, talkgroups: tgs };
|
||||
} else if (m === 'p25conv') {
|
||||
const freqs = parseFreqs($('pc-freqs').value);
|
||||
if (!freqs.length || freqs.length > 2 || freqs.some(isNaN)) { msg.className = 'msg err'; msg.textContent = 'Enter one or two channel frequencies in MHz, e.g. 460.4000, 460.4250'; return; }
|
||||
body = { mode: 'p25conv', frequencies: freqs, name: $('pc-name').value, nac: $('pc-nac').value.trim() || null };
|
||||
} else {
|
||||
const freqs = parseFreqs($('an-freq').value);
|
||||
if (freqs.length !== 1 || isNaN(freqs[0])) { msg.className = 'msg err'; msg.textContent = 'Enter one frequency in MHz, e.g. 154.4300'; return; }
|
||||
|
||||
Reference in New Issue
Block a user