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
@@ -49,12 +49,28 @@ class IcecastConfig(BaseModel):
|
||||
icecast_genre: Optional[str] = "Public Safety"
|
||||
|
||||
|
||||
class P25Mode(str, Enum):
|
||||
TRUNKED = "trunked"
|
||||
CONVENTIONAL = "conventional"
|
||||
|
||||
|
||||
class ConventionalChannel(BaseModel):
|
||||
"""One fixed P25 conventional voice channel (node-26#24)."""
|
||||
frequency: int # Hz
|
||||
name: str = ""
|
||||
nac: Optional[int] = None # 0x001-0xFFF; None = accept any NAC
|
||||
|
||||
|
||||
class ConfigGenerator(BaseModel):
|
||||
type: DecodeMode
|
||||
systemName: str
|
||||
# P25: control channel list (Hz). NBFM: channels[0] is the one analog
|
||||
# frequency to listen to (Hz).
|
||||
channels: List[Union[int, str]]
|
||||
# P25 trunked: control channel list (Hz). NBFM: channels[0] is the one
|
||||
# analog frequency to listen to (Hz). P25 conventional: unused — see
|
||||
# conv_channels.
|
||||
channels: List[Union[int, str]] = []
|
||||
# P25 only. Missing = trunked, so every existing caller is unchanged.
|
||||
p25_mode: P25Mode = P25Mode.TRUNKED
|
||||
conv_channels: List[ConventionalChannel] = []
|
||||
tags: Optional[List[TalkgroupTag]] = None
|
||||
whitelist: Optional[List[int]] = None
|
||||
icecastConfig: Optional[IcecastConfig] = None
|
||||
@@ -84,6 +100,13 @@ class ChannelConfig(BaseModel):
|
||||
# Key name op25_nbfm.py actually reads (from_dict(config, 'nbfm_squelch_threshold', -60)).
|
||||
nbfm_squelch_threshold: Optional[float] = None
|
||||
destination: Optional[str] = "udp://127.0.0.1:23456"
|
||||
# P25 conventional only, read by tk_drb_conv.py (not by multi_rx): the NAC
|
||||
# to lock the frame assembler to. multi_rx passes the whole channel dict
|
||||
# through, so an extra key is harmless.
|
||||
nac: Optional[int] = None
|
||||
# Set on the channel itself for conventional (multi_rx forwards it to the
|
||||
# frame assembler). 2 = drop encrypted voice instead of decoding noise.
|
||||
crypt_behavior: Optional[int] = None
|
||||
tracking_threshold: Optional[int] = 120
|
||||
tracking_feedback: Optional[float] = 0.75
|
||||
excess_bw: Optional[float] = 0.2
|
||||
|
||||
Reference in New Issue
Block a user