op25: pin gr310 to 5b58335; analog defaults to OP25 noise squelch (node-26#46, node-26#29)
- op25-container/Dockerfile: clone gr310 then check out 5b58335 (OP25_COMMIT build arg). That is the gr310 merge tk_drb_conv was verified against for #29 and the first carrying PR #296's NBFM noise/voice squelch. Changing the RUN line also busts CI's cached unpinned clone. - Keys verified from op25_nbfm.py at that commit: nbfm_squelch_mode (power|noise|voice), nbfm_noise_squelch_db, nbfm_squelch_threshold, read from the channel dict. - op25-container: ConfigGenerator.nbfm_squelch_mode (enum, default noise) and nbfm_noise_squelch_db, written onto the analog channel. - edge-node: system config squelch_mode / squelch_db / squelch_threshold -> generate payload (validated, noise when absent); /api/listen and the dashboard's analog pane expose mode and level. #29's CI test of tk_drb_conv against the real tk_p25.py is not done here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6b884d5433
commit
a4a575ec9f
+73
-65
@@ -1,66 +1,74 @@
|
||||
# OP25 Core Container
|
||||
# Pinned to a Python major version deliberately. The bare `slim-trixie` tag
|
||||
# carries no version at all, so a rebuild could move the interpreter across a
|
||||
# major release -- which this repo has already been bitten by once, when
|
||||
# app/models.py only ran because trixie happened to ship 3.14 and PEP 649
|
||||
# defers annotation evaluation. Matches drb-edge-node, which is already 3.14.
|
||||
FROM python:3.14-slim
|
||||
|
||||
# Set environment variables
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade -y && \
|
||||
apt-get install git pulseaudio pulseaudio-utils liquidsoap usbutils -y
|
||||
|
||||
# Install custom PulseAudio system config (enables anonymous access for edge-node)
|
||||
COPY system.pa /etc/pulse/system.pa
|
||||
|
||||
# Clone the boatbod op25 repository
|
||||
RUN git clone -b gr310 https://github.com/boatbod/op25 /op25
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /op25
|
||||
|
||||
# Run the install script to set up op25
|
||||
RUN sed -i 's/sudo //g' install.sh
|
||||
RUN ./install.sh -f
|
||||
|
||||
# Install Python dependencies
|
||||
COPY requirements.txt /tmp/requirements.txt
|
||||
RUN pip3 install --no-cache-dir -r /tmp/requirements.txt
|
||||
|
||||
# DRB's trunking module for P25 conventional channels (node-26#24): reports
|
||||
# encryption + NAC and applies the NAC filter, which stock tk_p25 does not.
|
||||
COPY op25_addons/tk_drb_conv.py /op25/op25/gr-op25_repeater/apps/tk_drb_conv.py
|
||||
|
||||
# Create the run_multi-rx_service.sh script
|
||||
COPY run_multi-rx_service.sh /op25/op25/gr-op25_repeater/apps/run_multi-rx_service.sh
|
||||
RUN chmod +x /op25/op25/gr-op25_repeater/apps/run_multi-rx_service.sh
|
||||
|
||||
# Expose ports for HTTP control as needed, for example:
|
||||
EXPOSE 8001 8081
|
||||
|
||||
# Create and set up the configuration directory
|
||||
VOLUME ["/configs"]
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the rest of the directory contents into the container at /app
|
||||
COPY ./app /app
|
||||
|
||||
# 1. Copy the wrapper script and make it executable
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# 2. Update ENTRYPOINT to use the wrapper script
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
|
||||
# 3. Use CMD to pass the launch command as arguments to the ENTRYPOINT script.
|
||||
# main.py starts uvicorn itself (see `if __name__ == "__main__"`) so the bind
|
||||
# address can be driven by OP25_DEBUG_EXPOSE at runtime instead of being baked
|
||||
# into this image at build time.
|
||||
# OP25 Core Container
|
||||
# Pinned to a Python major version deliberately. The bare `slim-trixie` tag
|
||||
# carries no version at all, so a rebuild could move the interpreter across a
|
||||
# major release -- which this repo has already been bitten by once, when
|
||||
# app/models.py only ran because trixie happened to ship 3.14 and PEP 649
|
||||
# defers annotation evaluation. Matches drb-edge-node, which is already 3.14.
|
||||
FROM python:3.14-slim
|
||||
|
||||
# Set environment variables
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade -y && \
|
||||
apt-get install git pulseaudio pulseaudio-utils liquidsoap usbutils -y
|
||||
|
||||
# Install custom PulseAudio system config (enables anonymous access for edge-node)
|
||||
COPY system.pa /etc/pulse/system.pa
|
||||
|
||||
# Clone the boatbod op25 repository, PINNED (node-26#29, #46). An unpinned
|
||||
# clone plus CI's layer cache meant the image held whatever gr310 HEAD was at
|
||||
# the last cache miss. 5b58335 (gr310, 2026-08-23) is the commit tk_drb_conv
|
||||
# was verified against for node-26#29 and the first gr310 merge that carries
|
||||
# the 2026-08-03 NBFM noise/voice squelch (PR #296, op25_nbfm.py
|
||||
# nbfm_squelch_mode). Bump deliberately, re-checking tk_drb_conv against
|
||||
# tk_p25.py at the new commit.
|
||||
ARG OP25_COMMIT=5b583357240e0172bfae905f0c966474e0d0f094
|
||||
RUN git clone -b gr310 https://github.com/boatbod/op25 /op25 && \
|
||||
git -C /op25 checkout --detach "${OP25_COMMIT}"
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /op25
|
||||
|
||||
# Run the install script to set up op25
|
||||
RUN sed -i 's/sudo //g' install.sh
|
||||
RUN ./install.sh -f
|
||||
|
||||
# Install Python dependencies
|
||||
COPY requirements.txt /tmp/requirements.txt
|
||||
RUN pip3 install --no-cache-dir -r /tmp/requirements.txt
|
||||
|
||||
# DRB's trunking module for P25 conventional channels (node-26#24): reports
|
||||
# encryption + NAC and applies the NAC filter, which stock tk_p25 does not.
|
||||
COPY op25_addons/tk_drb_conv.py /op25/op25/gr-op25_repeater/apps/tk_drb_conv.py
|
||||
|
||||
# Create the run_multi-rx_service.sh script
|
||||
COPY run_multi-rx_service.sh /op25/op25/gr-op25_repeater/apps/run_multi-rx_service.sh
|
||||
RUN chmod +x /op25/op25/gr-op25_repeater/apps/run_multi-rx_service.sh
|
||||
|
||||
# Expose ports for HTTP control as needed, for example:
|
||||
EXPOSE 8001 8081
|
||||
|
||||
# Create and set up the configuration directory
|
||||
VOLUME ["/configs"]
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the rest of the directory contents into the container at /app
|
||||
COPY ./app /app
|
||||
|
||||
# 1. Copy the wrapper script and make it executable
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# 2. Update ENTRYPOINT to use the wrapper script
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
|
||||
# 3. Use CMD to pass the launch command as arguments to the ENTRYPOINT script.
|
||||
# main.py starts uvicorn itself (see `if __name__ == "__main__"`) so the bind
|
||||
# address can be driven by OP25_DEBUG_EXPOSE at runtime instead of being baked
|
||||
# into this image at build time.
|
||||
CMD ["python", "main.py"]
|
||||
@@ -54,6 +54,13 @@ class P25Mode(str, Enum):
|
||||
CONVENTIONAL = "conventional"
|
||||
|
||||
|
||||
class NbfmSquelchMode(str, Enum):
|
||||
"""Values op25_nbfm.py accepts for nbfm_squelch_mode (gr310, 2026-08-03)."""
|
||||
POWER = "power"
|
||||
NOISE = "noise"
|
||||
VOICE = "voice"
|
||||
|
||||
|
||||
class ConventionalChannel(BaseModel):
|
||||
"""One fixed P25 conventional voice channel (node-26#24)."""
|
||||
frequency: int # Hz
|
||||
@@ -77,7 +84,16 @@ class ConfigGenerator(BaseModel):
|
||||
hardware_preset: str = "rtl-sdr-v3"
|
||||
ppm_override: Optional[float] = None # when set, overrides the preset's default PPM
|
||||
# NBFM only: power squelch threshold in dB (op25_nbfm default is -60).
|
||||
# Used only in "power" squelch mode.
|
||||
nbfm_squelch: Optional[float] = None
|
||||
# NBFM only (node-26#46): OP25 gr310's squelch algorithm — "power" (the
|
||||
# absolute-level squelch, needs per-site calibration), "noise" (FM
|
||||
# quieting, no calibration) or "voice" (noise + speech detector).
|
||||
# Defaults to noise: power mode left analog open on static.
|
||||
nbfm_squelch_mode: NbfmSquelchMode = NbfmSquelchMode.NOISE
|
||||
# dB of quieting needed to open in noise/voice mode (OP25 default 8;
|
||||
# 12-15 opens only on solid signals, ~5 chases weak ones).
|
||||
nbfm_noise_squelch_db: Optional[float] = None
|
||||
|
||||
class DemodType(str, Enum):
|
||||
CQPSK = "cqpsk"
|
||||
@@ -99,6 +115,9 @@ class ChannelConfig(BaseModel):
|
||||
frequency: Optional[int] = None
|
||||
# Key name op25_nbfm.py actually reads (from_dict(config, 'nbfm_squelch_threshold', -60)).
|
||||
nbfm_squelch_threshold: Optional[float] = None
|
||||
# Also read by op25_nbfm.py from the channel dict (node-26#46).
|
||||
nbfm_squelch_mode: Optional[str] = None
|
||||
nbfm_noise_squelch_db: 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
|
||||
|
||||
@@ -262,6 +262,8 @@ def create_op25_router():
|
||||
frequency=freq,
|
||||
meta_stream_name="",
|
||||
nbfm_squelch_threshold=generator.nbfm_squelch,
|
||||
nbfm_squelch_mode=generator.nbfm_squelch_mode.value,
|
||||
nbfm_noise_squelch_db=generator.nbfm_noise_squelch_db,
|
||||
)]
|
||||
devices = [DeviceConfig(
|
||||
gains=preset["gains"], ppm=ppm, tunable=False,
|
||||
|
||||
@@ -149,6 +149,23 @@ def test_generate_config_route(tmp_path, monkeypatch):
|
||||
cfg = json.loads((tmp_path / "active.cfg.json").read_text())
|
||||
assert cfg["trunking"]["module"] == "tk_drb_conv.py"
|
||||
assert cfg["channels"][0]["frequency"] == 154430000
|
||||
# node-26#46: analog defaults to OP25's noise squelch, not power.
|
||||
assert cfg["channels"][0]["nbfm_squelch_mode"] == "noise"
|
||||
assert "nbfm_noise_squelch_db" not in cfg["channels"][0]
|
||||
|
||||
r = client.post("/op25/generate-config", json={
|
||||
"type": "NBFM", "systemName": "Fire", "channels": [154430000], "icecastConfig": ice,
|
||||
"nbfm_squelch_mode": "power", "nbfm_squelch": -55, "nbfm_noise_squelch_db": 12,
|
||||
})
|
||||
assert r.status_code == 200, r.text
|
||||
ch = json.loads((tmp_path / "active.cfg.json").read_text())["channels"][0]
|
||||
assert ch["nbfm_squelch_mode"] == "power" and ch["nbfm_squelch_threshold"] == -55
|
||||
assert ch["nbfm_noise_squelch_db"] == 12
|
||||
|
||||
r = client.post("/op25/generate-config", json={
|
||||
"type": "NBFM", "systemName": "Fire", "channels": [154430000], "nbfm_squelch_mode": "loud",
|
||||
})
|
||||
assert r.status_code == 422
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user