fix: default TALKER_ALIAS_SEND_DMRA off for legacy wire parity

Radios use embedded LC in DMRD B–E; standalone DMRA UDP is optional
and off by default to match typical legacy captures.
pull/55/head
Rodrigo Pérez 2 months ago
parent 37d637239b
commit 169467ecbb

@ -17,12 +17,14 @@ GLOBAL:
PASS_SECURITY: "<set-in-adn-server.yaml>"
USERS_PASS: user_passwords.json
HASH_ENCRYPT: encryption_key.secret
# Talker Alias (HBP DMRA): see docs/en/server/user-guide/talker-alias.md
# Talker Alias (embedded LC + optional HBP DMRA): see docs/en/server/user-guide/talker-alias.md
TALKER_ALIAS: false
TALKER_ALIAS_MODE: both
TALKER_ALIAS_FORMAT: "{callsign} {fname}"
# utf8 (Motorola), iso8 (Hytera), 7bit; both vendors by default
TALKER_ALIAS_TEXT_FORMAT: "utf8,iso8"
# Standalone DMRA UDP (default false — legacy-like; radios use embedded LC in DMRD)
TALKER_ALIAS_SEND_DMRA: false
# UDP SO_RCVBUF for voice listeners (bytes); default 4 MB avoids kernel RcvbufErrors under load
# UDP_RCVBUF: 4194304

@ -84,6 +84,7 @@ Optional DMR Talker Alias on HBP (`DMRA` packets). Full guide: [Talker Alias](ta
| **TALKER_ALIAS** | Enable server TA inject/passthrough (`false` default). |
| **TALKER_ALIAS_MODE** | `both` (default), `passthrough`, or `inject`. |
| **TALKER_ALIAS_FORMAT** | Template, e.g. `{callsign} {fname}`. Max **29** chars (protocol limit, not YAML). |
| **TALKER_ALIAS_SEND_DMRA** | Send standalone HBP `DMRA` UDP (`false` default; radios use embedded LC). |
| **TALKER_ALIAS_TEXT_FORMAT** | `utf8`, `iso8`, `7bit`, or comma list (e.g. `utf8,iso8` for Motorola + Hytera). Default `utf8`. |
---

@ -18,7 +18,7 @@ When enabled on a **MASTER** system, the server can:
| **`passthrough`** | Relay the source's TA only (buffered `DMRA` and the source embedded LC, both untouched). Never inject the template. |
| **`inject`** | Always build TA from the configured template and alias data, overwriting the embedded LC and sending `DMRA` packets. |
On **bridge forward** at voice header (`VHEAD`), the server sends four `DMRA` packets to each HBP target (**MASTER** peers or **PEER** upstream) once per stream, then forwards `DMRD` as usual.
On **bridge forward** at voice header (`VHEAD`), when **`TALKER_ALIAS_SEND_DMRA`** is `true`, the server may send up to four `DMRA` packets to each HBP target (**MASTER** peers or **PEER** upstream) once per stream, then forwards `DMRD` as usual. With the default (`false`), only embedded LC is used — closer to legacy ADN masters that often show **no** `DMRA` on the wire.
**MMDVMHost / DMRGateway (Pi-Star, WPSD):** stock MMDVMHost does **not** consume standalone downlink `DMRA` UDP; it decodes Talker Alias from **embedded LC inside `DMRD` voice** (FLCO 4–7).
@ -43,6 +43,7 @@ GLOBAL:
TALKER_ALIAS_MODE: both
TALKER_ALIAS_FORMAT: "TA {callsign} {fname}"
TALKER_ALIAS_TEXT_FORMAT: "utf8,iso8"
TALKER_ALIAS_SEND_DMRA: false
```
| Key | Meaning |
@ -51,6 +52,7 @@ GLOBAL:
| **TALKER_ALIAS_MODE** | `both`, `passthrough`, or `inject`. Default **`both`** if omitted. |
| **TALKER_ALIAS_FORMAT** | Python format string; fields: `{callsign}`, `{fname}`, `{surname}`, `{id}`. |
| **TALKER_ALIAS_TEXT_FORMAT** | TA payload encoding: `utf8` (Motorola / MMDVMHost default), `iso8` (ISO-8859-1, many Hytera models), or `7bit` (oldest radios). Comma-separated list (e.g. `utf8,iso8`) emits **both** encodings back-to-back in embedded LC so each vendor can pick the format it displays; standalone `DMRA` UDP uses the **first** format only. Default **`utf8`**. |
| **TALKER_ALIAS_SEND_DMRA** | Send standalone HBP `DMRA` UDP on VHEAD (`false` by default). Radios / MMDVMHost use **embedded LC** in `DMRD`; leaving this off matches typical legacy wire (no `DMRA` toward the peer). Set `true` only if a client needs the UDP packets. |
Maximum string length is **29 characters** (ETSI / MMDVMHost). This limit is fixed in code and is **not** configurable, to avoid incompatible payloads on radios and hotspots.

@ -84,6 +84,7 @@ Talker Alias DMR opcional en HBP (paquetes `DMRA`). Guía completa: [Talker Alia
| **TALKER_ALIAS** | Activa inyección/passthrough de TA (`false` por defecto). |
| **TALKER_ALIAS_MODE** | `both` (por defecto), `passthrough` o `inject`. |
| **TALKER_ALIAS_FORMAT** | Plantilla, p. ej. `{callsign} {fname}`. Máx. **29** caracteres (límite de protocolo, no YAML). |
| **TALKER_ALIAS_SEND_DMRA** | Enviar `DMRA` UDP autónomo (`false` por defecto; las radios usan LC embebida). |
| **TALKER_ALIAS_TEXT_FORMAT** | `utf8`, `iso8`, `7bit` o lista con comas (p. ej. `utf8,iso8`). Por defecto `utf8`. |
---

@ -18,9 +18,9 @@ Con la función activada en un sistema **MASTER**, el servidor puede:
| **`passthrough`** | Solo reenvía `DMRA` bufferizado. |
| **`inject`** | Siempre genera TA desde la plantilla y datos de alias. |
En el **reenvío por bridge** en cabecera de voz (`VHEAD`), el servidor envía cuatro paquetes `DMRA` a cada destino HBP (**MASTER** peers o **PEER** upstream) una vez por stream, y después reenvía `DMRD` como siempre.
En el **reenvío por bridge** en cabecera de voz (`VHEAD`), si **`TALKER_ALIAS_SEND_DMRA`** es `true`, el servidor puede enviar hasta cuatro paquetes `DMRA` a cada destino HBP una vez por stream. Con el valor por defecto (`false`) solo se usa la LC embebida — más parecido a masters legacy que a menudo no muestran `DMRA` en el cable.
**MMDVMHost / DMRGateway (Pi-Star, WPSD):** el MMDVMHost estándar **no** procesa `DMRA` UDP independiente en downlink; decodifica Talker Alias desde la **LC embebida en voz `DMRD`** (FLCO 4–7). Con TA activado, ADN inyecta el TA en la LC embebida de los bursts **B–E** (dtype 1–4) en el **reenvío por bridge** hacia destinos HBP, alternando LC de grupo y bloques TA durante todo el stream, además de los paquetes `DMRA` opcionales para clientes que los soporten.
**MMDVMHost / DMRGateway (Pi-Star, WPSD):** el MMDVMHost estándar **no** procesa `DMRA` UDP independiente en downlink; decodifica Talker Alias desde la **LC embebida en voz `DMRD`** (FLCO 4–7). Con TA activado, ADN inyecta el TA en la LC embebida de los bursts **B–E** (dtype 1–4) en el **reenvío por bridge** hacia destinos HBP, alternando LC de grupo y bloques TA durante todo el stream; los paquetes `DMRA` son opcionales (`TALKER_ALIAS_SEND_DMRA`).
En el **mismo MASTER**, cuando **`REPEAT`** copia voz de grupo a otros hotspots registrados, el servidor también envía esos cuatro `DMRA` en `VHEAD` (excluyendo el peer que transmite). Si el bridge apunta al mismo system, comparten la misma deduplicación por stream y el TA no se envía dos veces.
@ -38,6 +38,7 @@ GLOBAL:
TALKER_ALIAS_MODE: both
TALKER_ALIAS_FORMAT: "{callsign} {fname}"
TALKER_ALIAS_TEXT_FORMAT: "utf8,iso8"
TALKER_ALIAS_SEND_DMRA: false
```
| Clave | Significado |
@ -46,6 +47,7 @@ GLOBAL:
| **TALKER_ALIAS_MODE** | `both`, `passthrough` o `inject`. Por defecto **`both`** si se omite. |
| **TALKER_ALIAS_FORMAT** | Plantilla tipo Python; campos: `{callsign}`, `{fname}`, `{surname}`, `{id}`. |
| **TALKER_ALIAS_TEXT_FORMAT** | Codificación del TA: `utf8` (Motorola), `iso8` (Hytera), `7bit`. Lista separada por comas (p. ej. `utf8,iso8`) emite ambas en LC embebido; `DMRA` UDP usa solo el **primer** formato. Por defecto **`utf8`**. |
| **TALKER_ALIAS_SEND_DMRA** | Enviar `DMRA` UDP autónomo en VHEAD (`false` por defecto). Las radios / MMDVMHost usan la **LC embebida** en `DMRD`; dejarlo en off se acerca al cable legacy (sin `DMRA` hacia el peer). Pon `true` solo si un cliente necesita esos paquetes UDP. |
La longitud máxima es **29 caracteres** (ETSI / MMDVMHost). Este límite está fijado en código y **no** es configurable, para evitar payloads incompatibles en radios y hotspots.

@ -326,6 +326,11 @@ class LcTaMixin:
"""Emit DMRA to an HBP target on VHEAD (once per target stream)."""
if not self._send_dmra_to_system:
return
settings = talker_alias_settings(self._config, source_system)
if not settings["enabled"] or not settings["send_dmra"]:
# Embedded TA in DMRD B–E still runs via _rewrite_embed_lc; this gate is
# standalone DMRA UDP only (legacy parity default: off).
return
tgt_mode = self._config.get("SYSTEMS", {}).get(target_system, {}).get("MODE")
if tgt_mode not in ("MASTER", "PEER"):
return

@ -65,11 +65,16 @@ def talker_alias_settings(config: dict[str, Any], system_name: str | None = None
tfmt = sys_cfg.get("TALKER_ALIAS_TEXT_FORMAT")
if tfmt is None:
tfmt = global_cfg.get("TALKER_ALIAS_TEXT_FORMAT", "utf8")
send_dmra = sys_cfg.get("TALKER_ALIAS_SEND_DMRA")
if send_dmra is None:
send_dmra = global_cfg.get("TALKER_ALIAS_SEND_DMRA", False)
return {
"enabled": bool(enabled),
"mode": mode,
"format": str(fmt),
"text_formats": parse_ta_text_formats(tfmt),
# Standalone HBP DMRA UDP (legacy wire often omits this; radios use embedded LC).
"send_dmra": bool(send_dmra),
}

@ -94,6 +94,8 @@ def apply_talker_alias_defaults(config: dict) -> None:
g.setdefault("TALKER_ALIAS_MODE", "both")
g.setdefault("TALKER_ALIAS_FORMAT", "{callsign} {fname}")
g.setdefault("TALKER_ALIAS_TEXT_FORMAT", "utf8")
# Standalone DMRA UDP off by default (radios use embedded LC; matches legacy wire).
g.setdefault("TALKER_ALIAS_SEND_DMRA", False)
def normalize_peer_config(config: dict) -> None:

@ -145,6 +145,7 @@ def _validate_global(global_cfg: dict[str, Any], errors: list[str]) -> None:
"DEBUG_BRIDGES",
"ENABLE_API",
"TALKER_ALIAS",
"TALKER_ALIAS_SEND_DMRA",
):
if key in global_cfg:
_expect_bool(f"GLOBAL.{key}", global_cfg[key], errors)

@ -49,6 +49,8 @@ def talker_alias_config() -> dict[str, Any]:
config["GLOBAL"]["TALKER_ALIAS"] = True
config["GLOBAL"]["TALKER_ALIAS_MODE"] = "inject"
config["GLOBAL"]["TALKER_ALIAS_FORMAT"] = "{callsign} {fname}"
# Tests that assert dmra_capture need explicit opt-in (production default is false).
config["GLOBAL"]["TALKER_ALIAS_SEND_DMRA"] = True
rid = 3120001
config["_SUB_PROFILES"] = {
rid: {"callsign": "CE5RPY", "fname": "Rodrigo", "surname": "Perez"},

@ -50,6 +50,21 @@ def test_talker_alias_inject_dmra_on_bridge_vhead() -> None:
assert b"CE5RPY" in payload
def test_talker_alias_send_dmra_false_skips_standalone_dmra() -> None:
"""TALKER_ALIAS_SEND_DMRA false: no DMRA UDP; embedded TA path still armed."""
config = talker_alias_config()
config["GLOBAL"]["TALKER_ALIAS_SEND_DMRA"] = False
bridges = active_routing_table(91, (("MASTER-A", 2), ("MASTER-B", 2)))
scenario = DeterministicScenario(config=config, routing_table=bridges)
base = PacketSpec(dst_id=91, rf_src=3120001, stream_id=0x90909091)
scenario.inject_hbp("MASTER-A", DeterministicScenario.voice_head_spec(base))
assert scenario.dmra_capture == []
ts_st = scenario.protocols["MASTER-B"].STATUS[2]
assert ts_st.get("TX_TA_EMB") is not None
def test_talker_alias_vhead_sent_once_per_target_stream() -> None:
bridges = active_routing_table(91, (("MASTER-A", 2), ("MASTER-B", 2)))
scenario = DeterministicScenario(config=talker_alias_config(), routing_table=bridges)

Loading…
Cancel
Save

Powered by TurnKey Linux.