From 92bea91a5b5135145557fa2a6c4a7f9862deb947 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 31 May 2026 14:43:30 -0400 Subject: [PATCH] several dvmcfggen fixes: correct HEX vs DEC issues reported by digilink/TAC-10; correct indentation issue for voiceChNo reported by digilink/TAC-10; fix missing configuration parameter; --- tools/dvmcfggen/config_manager.py | 59 ++++++++++++++++++++++++++++++- tools/dvmcfggen/templates.py | 4 +++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/tools/dvmcfggen/config_manager.py b/tools/dvmcfggen/config_manager.py index 2dd9e155..c69c507b 100644 --- a/tools/dvmcfggen/config_manager.py +++ b/tools/dvmcfggen/config_manager.py @@ -35,6 +35,55 @@ from typing import Dict, Any, Optional, List import re +class HexInt(int): + """Marker type for integers that should be emitted in hexadecimal.""" + + +class DVMConfigDumper(yaml.SafeDumper): + """Custom YAML dumper with consistent list indentation and NAC hex output.""" + + def increase_indent(self, flow=False, indentless=False): + # Force sequence items to be indented under mapping keys. + return super().increase_indent(flow, False) + + +def _represent_hex_int(dumper, data): + """Represent marked integers as YAML int scalars in hex format.""" + hex_value = f'{int(data):X}' + # YAML only accepts decimal/octal/0x-prefixed ints; bare A-F text must be a string. + if any(ch in 'ABCDEF' for ch in hex_value): + return dumper.represent_scalar('tag:yaml.org,2002:str', hex_value) + return dumper.represent_scalar('tag:yaml.org,2002:int', hex_value) + + +DVMConfigDumper.add_representer(HexInt, _represent_hex_int) + + +def _mark_hex_fields(value: Any, key: Optional[str] = None) -> Any: + """Recursively mark NAC-like fields for hexadecimal YAML serialization.""" + hex_keys = { + 'nac', + 'txNAC', + 'p25NAC', + 'netId', + 'p25NetId', + 'sysId', + 'rfssId', + 'siteId', + 'dmrNetId', + 'channelNo', + 'rxChannelNo', + } + + if isinstance(value, dict): + return {k: _mark_hex_fields(v, k) for k, v in value.items()} + if isinstance(value, list): + return [_mark_hex_fields(item) for item in value] + if key in hex_keys and isinstance(value, int): + return HexInt(value) + return value + + class ConfigValidator: """Validates DVMHost configuration values""" @@ -111,8 +160,16 @@ class DVMConfig: backup_path = path.with_suffix(path.suffix + '.bak') path.rename(backup_path) + config_to_write = _mark_hex_fields(self.config) + with open(path, 'w') as f: - yaml.dump(self.config, f, default_flow_style=False, sort_keys=False) + yaml.dump( + config_to_write, + f, + Dumper=DVMConfigDumper, + default_flow_style=False, + sort_keys=False, + ) def get(self, key_path: str, default: Any = None) -> Any: """ diff --git a/tools/dvmcfggen/templates.py b/tools/dvmcfggen/templates.py index 4cae9f88..cd7df94d 100644 --- a/tools/dvmcfggen/templates.py +++ b/tools/dvmcfggen/templates.py @@ -76,6 +76,7 @@ def load_base_config() -> Dict[str, Any]: def get_minimal_fallback_config() -> Dict[str, Any]: """Minimal fallback configuration if config.example.yml is not available""" return { + 'iAgreeNotToBeStupid': True, 'daemon': True, 'log': { 'displayLevel': 1, @@ -186,6 +187,9 @@ def apply_template_customizations(config: Dict[str, Any], template_type: str) -> # Make a deep copy to avoid modifying the original import copy config = copy.deepcopy(config) + + # Generated configs must explicitly include license acknowledgement. + config['iAgreeNotToBeStupid'] = True if template_type == 'control-channel-p25': # P25 dedicated control channel