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
@@ -2,7 +2,26 @@ import os
|
||||
from internal.op25_liq_template import liquidsoap_config_template
|
||||
from models import IcecastConfig
|
||||
|
||||
def generate_liquid_script(config: IcecastConfig):
|
||||
# P25 conventional with two channels (node-26#24): OP25 sends channel A's
|
||||
# audio to UDP 23456 and channel B's to 23457, and audio.py -2 puts them on the
|
||||
# left and right of the stereo stream. Every output below is mean(input) — mono
|
||||
# — so a lone talker on one side would come out 6 dB down; doubling first keeps
|
||||
# the common case (one channel keyed) at the same level as single-channel mode.
|
||||
# Liquidsoap mixes in float, so the doubling cannot clip before the output.
|
||||
_TWO_CHANNEL_AUDIO_ARGS = " -2"
|
||||
_TWO_CHANNEL_MIX = "input = amplify(2.0, input) # two conventional channels, L=A R=B"
|
||||
|
||||
|
||||
def render_liquid_script(config: IcecastConfig, two_channel: bool = False) -> str:
|
||||
content = liquidsoap_config_template
|
||||
for key, value in config.model_dump().items():
|
||||
content = content.replace(f"${{{key}}}", str(value))
|
||||
content = content.replace("${audio_py_args}", _TWO_CHANNEL_AUDIO_ARGS if two_channel else "")
|
||||
content = content.replace("${two_channel_mix}", _TWO_CHANNEL_MIX if two_channel else "")
|
||||
return content
|
||||
|
||||
|
||||
def generate_liquid_script(config: IcecastConfig, two_channel: bool = False):
|
||||
"""
|
||||
Generates the "*.liq" file that's run by OP25 on startup.
|
||||
|
||||
@@ -13,13 +32,7 @@ def generate_liquid_script(config: IcecastConfig):
|
||||
Keys should match the variable names in the template (e.g., 'icecast_host').
|
||||
"""
|
||||
try:
|
||||
content = liquidsoap_config_template
|
||||
# Replace variables
|
||||
for key, value in config.model_dump().items():
|
||||
placeholder = f"${{{key}}}"
|
||||
# Ensure the value is converted to string for replacement
|
||||
content = content.replace(placeholder, str(value))
|
||||
print(f" - Replaced placeholder {placeholder}")
|
||||
content = render_liquid_script(config, two_channel)
|
||||
|
||||
# Write the processed content to the output path
|
||||
output_path = "/configs/op25.liq"
|
||||
|
||||
Reference in New Issue
Block a user