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
+21
-27
@@ -191,36 +191,30 @@ async def on_api_key(payload: dict):
|
||||
logger.info("Node API key received and saved.")
|
||||
|
||||
|
||||
def _to_hz(freq) -> int:
|
||||
"""Convert a frequency to Hz. Accepts MHz floats (< 1e6) or Hz ints."""
|
||||
f = float(freq)
|
||||
return int(f * 1_000_000) if f < 1_000_000 else int(f)
|
||||
|
||||
|
||||
async def _generate_op25_config(config: SystemConfig) -> bool:
|
||||
"""Translate a SystemConfig (Firestore format) into OP25 active.cfg.json + op25.liq."""
|
||||
"""Translate a SystemConfig (Firestore format) into OP25 active.cfg.json + op25.liq.
|
||||
|
||||
Trunked / conventional / analog are told apart in radio_config (node-26#24);
|
||||
a system saved before that has no `mode` and is trunked, as it always was.
|
||||
"""
|
||||
from app.internal.op25_client import op25_client
|
||||
from app.internal.radio_config import build_generate_payload
|
||||
node_cfg = load_node_config()
|
||||
raw = config.config
|
||||
payload = {
|
||||
"type": config.type,
|
||||
"systemName": config.name,
|
||||
"channels": [_to_hz(ch) for ch in raw.get("control_channels", [])],
|
||||
"tags": [
|
||||
{"talkgroup": str(tg.get("name", "")), "tagDec": int(tg["id"])}
|
||||
for tg in raw.get("talkgroups", [])
|
||||
if tg.get("id") is not None
|
||||
],
|
||||
"whitelist": [int(tg["id"]) for tg in raw.get("talkgroups", []) if tg.get("id") is not None],
|
||||
"icecastConfig": {
|
||||
"icecast_host": settings.icecast_host,
|
||||
"icecast_port": settings.icecast_port,
|
||||
"icecast_mountpoint": settings.icecast_mount,
|
||||
"icecast_password": settings.icecast_source_password,
|
||||
},
|
||||
"hardware_preset": node_cfg.hardware_preset,
|
||||
**({"ppm_override": node_cfg.ppm_override} if node_cfg.ppm_override is not None else {}),
|
||||
}
|
||||
try:
|
||||
payload = build_generate_payload(
|
||||
config.type, config.name, config.config,
|
||||
icecast={
|
||||
"icecast_host": settings.icecast_host,
|
||||
"icecast_port": settings.icecast_port,
|
||||
"icecast_mountpoint": settings.icecast_mount,
|
||||
"icecast_password": settings.icecast_source_password,
|
||||
},
|
||||
hardware_preset=node_cfg.hardware_preset,
|
||||
ppm_override=node_cfg.ppm_override,
|
||||
)
|
||||
except ValueError as e:
|
||||
logger.error(f"System {config.name!r} has an unusable channel list: {e}")
|
||||
return False
|
||||
return await op25_client.generate_config(payload)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user