diff --git a/drb-edge-node/app/main.py b/drb-edge-node/app/main.py index 7dd0a5e..69b372a 100644 --- a/drb-edge-node/app/main.py +++ b/drb-edge-node/app/main.py @@ -107,6 +107,7 @@ def _to_hz(freq) -> int: async def _generate_op25_config(config: SystemConfig) -> bool: """Translate a SystemConfig (Firestore format) into OP25 active.cfg.json + op25.liq.""" from app.internal.op25_client import op25_client + node_cfg = load_node_config() raw = config.config payload = { "type": config.type, @@ -124,12 +125,16 @@ async def _generate_op25_config(config: SystemConfig) -> bool: "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 {}), } return await op25_client.generate_config(payload) async def on_config_push(payload: dict): """C2 pushes a system config — translate it to OP25 format and restart OP25.""" + hardware_preset = payload.pop("hardware_preset", None) + ppm_override = payload.pop("ppm_override", None) try: config = SystemConfig(**payload) except Exception as e: @@ -140,6 +145,10 @@ async def on_config_push(payload: dict): node_cfg.assigned_system_id = config.system_id node_cfg.system_config = config node_cfg.configured = True + if hardware_preset is not None: + node_cfg.hardware_preset = hardware_preset + if ppm_override is not None: + node_cfg.ppm_override = float(ppm_override) save_node_config(node_cfg) from app.internal.op25_client import op25_client diff --git a/drb-edge-node/app/models.py b/drb-edge-node/app/models.py index 17496be..f6a8b31 100644 --- a/drb-edge-node/app/models.py +++ b/drb-edge-node/app/models.py @@ -31,6 +31,8 @@ class NodeConfig(BaseModel): assigned_system_id: Optional[str] = None system_config: Optional[SystemConfig] = None configured: bool = False + hardware_preset: str = "rtl-sdr-v3" + ppm_override: Optional[float] = None class CallEvent(BaseModel): diff --git a/op25-container/app/models.py b/op25-container/app/models.py index d04052b..fb4596b 100644 --- a/op25-container/app/models.py +++ b/op25-container/app/models.py @@ -2,6 +2,29 @@ from pydantic import BaseModel from typing import List, Optional, Union from enum import Enum +# Preset device settings for common RTL-SDR hardware. +# gains: OP25 gain string passed to the device block. +# ppm: frequency offset correction (user should calibrate and override). +# cqpsk_tracking: disable for TCXO dongles (RTL-SDR v3, NESDR) once PPM is dialled in; +# leave enabled for unknown hardware as a safety net. +HARDWARE_PRESETS: dict = { + "rtl-sdr-v3": { + "gains": "lna:34", + "ppm": 0.0, + "cqpsk_tracking": False, + }, + "nesdr-smart-v4": { + "gains": "lna:32", + "ppm": 0.0, + "cqpsk_tracking": False, + }, + "other": { + "gains": "lna:32", + "ppm": 0.0, + "cqpsk_tracking": True, + }, +} + class DecodeMode(str, Enum): P25 = "P25" DMR = "DMR" @@ -18,6 +41,8 @@ class ConfigGenerator(BaseModel): tags: Optional[List[TalkgroupTag]] whitelist: Optional[List[int]] icecastConfig: Optional[IcecastConfig] + hardware_preset: str = "rtl-sdr-v3" + ppm_override: Optional[float] = None # when set, overrides the preset's default PPM class DemodType(str, Enum): CQPSK = "cqpsk" diff --git a/op25-container/app/routers/op25_controller.py b/op25-container/app/routers/op25_controller.py index 714f1dd..c236570 100644 --- a/op25-container/app/routers/op25_controller.py +++ b/op25-container/app/routers/op25_controller.py @@ -3,7 +3,7 @@ import subprocess import os import signal import json -from models import ConfigGenerator, DecodeMode, ChannelConfig, DeviceConfig, TrunkingConfig, TrunkingChannelConfig, TerminalConfig, MetadataConfig, MetadataStreamConfig +from models import ConfigGenerator, DecodeMode, ChannelConfig, DeviceConfig, TrunkingConfig, TrunkingChannelConfig, TerminalConfig, MetadataConfig, MetadataStreamConfig, HARDWARE_PRESETS from internal.logger import create_logger from internal.op25_config_utls import save_talkgroup_tags, save_whitelist, del_none_in_dict, get_current_system_from_config from internal.liquidsoap_config_utils import generate_liquid_script @@ -73,16 +73,18 @@ def create_op25_router(): async def generate_config(generator: ConfigGenerator): try: if generator.type == DecodeMode.P25: + preset = HARDWARE_PRESETS.get(generator.hardware_preset, HARDWARE_PRESETS["other"]) + ppm = generator.ppm_override if generator.ppm_override is not None else preset["ppm"] channels = [ChannelConfig( name=generator.systemName, trunking_sysname=generator.systemName, enable_analog="off", demod_type="cqpsk", - cqpsk_tracking=True, + cqpsk_tracking=preset["cqpsk_tracking"], filter_type="rc", meta_stream_name="stream_0" )] - devices = [DeviceConfig()] + devices = [DeviceConfig(gains=preset["gains"], ppm=ppm)] save_talkgroup_tags(generator.tags) save_whitelist(generator.whitelist) has_talkgroups = bool(generator.whitelist)