16 KiB
Enrutado de voz y reglas de contención
Esta página documenta cómo viajan los paquetes de voz por el servidor y las reglas de contención / slot que determinan quién escucha qué. Es la referencia para sysops e integradores que necesitan entender o diagnosticar el comportamiento de enrutado sin leer el código fuente.
El servidor mantiene paridad legada con adn-dmr-server (bridge_master.py)
salvo las divergencias explícitamente documentadas al final de esta página.
Regla de oro: una conversación por TG
Sólo una conversación por TG puede existir en el servidor a la vez. Esta regla es global — aplica sin importar el slot, el peer o el origen del tráfico (OBP o HBP). Tiene la prioridad más alta; todas las demás reglas se subordinan a ella.
Un TG está ocupado cuando tiene un stream de voz activo (frames dentro
de STREAM_TO del último paquete) en cualquier slot, venga de donde venga.
| Origen del nuevo tráfico | Comportamiento |
|---|---|
| Otro OBP envía el mismo TG | Rechazar — el TG ya está ocupado |
| Un hotspot (HBP) transmite al mismo TG | Rechazar, o activación silenciosa (ver abajo) |
La única excepción es la activación silenciosa, que no crea una segunda conversación — sólo permite al usuario escuchar la existente.
Flujo de paquetes de extremo a extremo
flowchart TD
PTT[Usuario pulsa PTT<br/>en TG 730500 slot 2] --> HBP[Hotspot envía DMRD<br/>por UDP HBP al PROXY]
HBP --> INJ[PROXY inyecta en MASTER 'SYSTEM']
INJ --> AUTH{Auth HBP OK?}
AUTH -->|No| DROP1[Drop: peer no autenticado]
AUTH -->|Sí| ACL{ACL de SUB/TG<br/>permite?}
ACL -->|No| DROP2[Drop: ACL]
ACL -->|Sí| DMRD_RX[dmrd_received<br/>actualiza STATUS slot]
DMRD_RX --> CONT{Contención global:<br/>TG ocupado?}
CONT -->|Ocupado OBP/HBP| SILENT{Activación silenciosa?<br/>ver abajo}
CONT -->|Libre| SRC[source row ACTIVE?<br/>bridge leg]
SRC -->|No| NODRV[No hay bridge leg<br/>ver: TG dinámico]
SRC -->|Sí| FWD[forward a to_target]
NODRV --> FWD
FWD --> FANOUT[Iterar peers destino]
FANOUT --> P1{Para cada peer:<br/>¿OPTIONS tiene TG?}
P1 -->|Sí o dinámico activo| SLOT{¿Slot destino<br/>libre?}
P1 -->|No| SKIP[Skip peer]
SLOT -->|Ocupado| SKIP
SLOT -->|Libre| REWRITE[Rewrite LC<br/>+ slot mapping]
REWRITE --> TX[Enviar DMRD<br/>al peer en su slot]
TX --> NEXT[Próximo peer / frame]
style DMRD_RX fill:#bfb,stroke:#333
style FWD fill:#bbf,stroke:#333
style TX fill:#fbb,stroke:#333
Puntos de decisión en orden
- Autenticación HBP — el peer debe estar registrado con passphrase válida.
- ACL —
SUB_ACL,TGID_TS1_ACL,TGID_TS2_ACL(siUSE_ACL). dmrd_received— actualizaSTATUS[slot]:RX_TIME,RX_TGID,RX_STREAM_ID,RX_TYPE,RX_PEER.- Contención global — si el TG ya tiene un stream activo desde otra fuente, se bloquea o activa silenciosamente.
- Source row ACTIVE — debe existir una pata de bridge
ACTIVEpara esesystem/slot/TG. Si no existe, se crea una dinámica. - Fan-out — para cada peer del MASTER, el gate de downlink decide si recibe el paquete.
- Rewrite LC + slot mapping — el slot destino es el del peer, no el de origen.
Las cuatro reglas de contención (paridad legada)
Evaluadas paquete a paquete en to_target contra el STATUS[slot] del
sistema destino. Coinciden con bridge_master.py líneas ~2076–2104.
| Regla | Condición | Acción |
|---|---|---|
| 1. RX hangtime | TG ≠ RX_TGID y (now - RX_TIME) < GROUP_HANGTIME |
continue (no enruta) |
| 2. TX hangtime | TG ≠ TX_TGID y (now - TX_TIME) < GROUP_HANGTIME |
continue |
| 3. mismo TG RX activo | TG == RX_TGID y (now - RX_TIME) < STREAM_TO y stream distinto |
continue |
| 4. mismo TG TX, otro sub | TG == TX_TGID y (now - TX_TIME) < STREAM_TO y otro suscriptor |
continue |
Hechos clave:
RX_TIME,RX_TGID,TX_TIME,TX_TGIDno se borran en VTERM. Conservan el último valor hasta que otro QSO los sobrescribe — por eso el hangtime cuenta desde el último paquete.- La contención se evalúa por frame, no por stream. Si un stream fue bloqueado por hangtime y luego el hangtime expira, los siguientes frames del mismo stream sí se reenvían.
- El flag
CONTENTIONdel legado es sólo un debounce de log; no bloquea.
Timeouts y constantes críticas
Estos valores definen el comportamiento observable. Cambiarlos afecta contención, hangtime y reconexión.
| Constante | Valor | Ubicación | Rol |
|---|---|---|---|
STREAM_TO |
0.36 s | domain/hbp_protocol.py |
Ventana para considerar un stream "activo" (entre paquetes). |
_STALE_PEER_SESSION_TIMEOUT |
5.0 s | routing/helpers.py |
Una sesión per-peer sin frames se considera muerta (VTERM perdido). |
GROUP_HANGTIME |
5 s (default config) | por system en YAML | Bloqueo tras fin de QSO antes de aceptar otro TG en ese slot. |
DEFAULT_UA_TIMER |
configurable (minutos) | por system en YAML | Duración de bridges dinámicos (User Activated). |
Ver Comportamiento y temporizadores para los
intervalos de los bucles periódicos (rule_timer, stream_trimmer, etc.).
Estados de un stream
stateDiagram-v2
[*] --> IDLE: slot libre
IDLE --> VHEAD: llega voice header (VHEAD)
VHEAD --> ACTIVE: frames de voz dentro de STREAM_TO
ACTIVE --> ACTIVE: cada frame renueva RX_TIME/TX_TIME
ACTIVE --> VTERM: llega voice terminator (VTERM)
ACTIVE --> TIMEOUT: sin frames > STREAM_TO
VTERM --> HANGTIME: GROUP_HANGTIME segundos
TIMEOUT --> HANGTIME: GROUP_HANGTIME segundos
HANGTIME --> IDLE: expira hangtime
HANGTIME --> ACTIVE: mismo TG reanuda (renueva)
VTERM perdido: si el MMDVM nunca envía el terminator, la sesión per-peer
expira tras _STALE_PEER_SESSION_TIMEOUT (5 s), liberando el slot.
SINGLE=1 vs SINGLE=0 (escucha exclusiva)
SINGLE se configura en la línea OPTIONS del hotspot (ej: SINGLE=1;).
Controla cuántos TG dinámicos puede tener activos un peer por slot.
flowchart LR
subgraph SINGLE1["SINGLE=1 (escucha exclusiva)"]
S1A[1 TG exclusivo por slot]
S1B[PTT en nuevo TG<br/>= reemplaza el lock]
S1C[Timer DEFAULT_UA_TIMER<br/>expira → libera]
S1D[TG 4000 → borra lock]
end
subgraph SINGLE0["SINGLE=0 (multi-dinámico)"]
S0A[Varios TG dinámicos<br/>acumulados por slot]
S0B[GROUP_HANGTIME rige<br/>entre TGs]
S0C[Sólo 1 stream downlink<br/>a la vez por slot]
end
| Aspecto | SINGLE=1 | SINGLE=0 |
|---|---|---|
| TGs dinámicos por slot | 1 exclusivo | Varios acumulados (_PEER_UA_MULTI_TGS) |
| Almacenamiento | _PEER_UA_SESSIONS[peer][slot] |
_PEER_UA_MULTI_TGS[peer][slot] |
| Cambiar de TG | PTT en nuevo TG reemplaza el lock | Se suma al set; no reemplaza |
| TG 4000 | Borra la sesión del slot | Borra todos los dinámicos del peer |
| Timer | DEFAULT_UA_TIMER expira → libera |
No expira individualmente; purga por GROUP_HANGTIME |
| Desactivación in-band | Agresiva: OFF/RESET/TG4000/tráfico no matching | Conservadora: principalmente TG 4000 |
Excepciones a SINGLE
- TG 9990 (eco): no crea lock SINGLE. El eco vuelve siempre al llamante.
- TG 4000: no crea sesión UA. Es sólo comando de reset.
- TG 9991–9999 (bajo demanda): no crean lock SINGLE.
- UA session propia: un peer SINGLE=1 que activó el TG T dinámicamente debe recibir downlink de T (no se auto-bloquea).
Talkgroups estáticos vs dinámicos
TG estático
Definido en la línea OPTIONS del hotspot (TS1_STATIC / TS2_STATIC). El
peer siempre escucha ese TG en ese slot mientras esté conectado.
OPTIONS: TS1=730500;TS2=730502,730508;SINGLE=1;TIMER=60;
Origen de los TG estáticos:
- OPTIONS al login (RPTO) — el hotspot reporta su línea.
- Self-service (panel web) — el usuario cambia sus TG desde el navegador;
el servidor envía un
RPTOactualizado al MASTER. - Startup/reload —
apply_startup_bridgesaplica los TG al arranque y enSIGHUP. - D-28 (divergencia): no hay loop periódico de 26 s
(
options_config_loop). El refresco es por evento (RPTO, startup, fallback dmrd sin source).
TG dinámico (User Activated)
Activado cuando un usuario transmite en un TG que no tiene estático.
flowchart TD
PTT[PTT en TG no estático] --> CHECK{TG libre?}
CHECK -->|Sí| ACT1[Activar TG dinámico<br/>+ pasar voz en 1 TX<br/>divergencia vs legacy]
CHECK -->|No, ocupado| SILENT[Activación silenciosa]
ACT1 --> STORE1[_PEER_UA_SESSIONS o<br/>_PEER_UA_MULTI_TGS]
STORE1 --> PERSIST[Upsert asíncrono<br/>peer_dynamic_tgs MariaDB]
ACT1 --> BRIDGE1[Crear bridge leg dinámica<br/>ensure_dynamic_relay]
BRIDGE1 --> DL[Peer queda escuchando<br/>ese TG]
Diferencia vs legacy (adn-dmr-server):
- Legacy: se necesitan 2 TX — la 1ª activa, la 2ª pasa voz.
- Este servidor: la 1ª TX hace ambas cosas (activar + pasar voz).
Ver Persistencia TG dinámicos (MariaDB) para la supervivencia tras reconexión.
Activación silenciosa (TG ocupado con QSO activo)
Divergencia intencionada. Si un usuario transmite en un TG que tiene una conversación activa, el servidor:
- No rechaza la TX.
- Activa ese TG como dinámico.
- No sube el uplink del usuario (no molesta el QSO activo).
- Sí entrega el downlink del QSO activo al usuario inmediatamente.
flowchart TD
TX[Usuario TX en TG ocupado] --> Q1{TG tiene QSO<br/>activo?}
Q1 -->|No| NORMAL[Flujo normal]
Q1 -->|Sí| ACT[Activar TG dinámico]
ACT --> NOUL[Suprimir uplink<br/>no reenviar voz del usuario]
NOUL --> DL2[Entregar downlink<br/>del QSO activo al usuario]
DL2 --> HANG[Respetar GROUP_HANGTIME<br/>para otros TGs en ese slot]
Aplica a:
- TG no estático que el usuario quiere escuchar.
- SINGLE=1 cambiando de TG activo a otro con QSO en curso.
Mapeo de slot en downlink
El slot por el que un peer recibe un TG lo determina su OPTIONS (si es
estático) o el slot donde lo activó (si es dinámico). No es el slot de
origen de la transmisión.
flowchart LR
OBP1[OBP envía TG 730500<br/>en slot 1] --> MAP{Slot mapping}
PEA[HS-A tiene 730500<br/>en TS2] --> MAP
PEB[HS-B tiene 730500<br/>en TS1] --> MAP
MAP --> OUTA[HS-A recibe en TS2]
MAP --> OUTB[HS-B recibe en TS1]
- OBP siempre viaja en slot 1 del paquete. El slot es informativo del origen; la entrega se hace al slot del peer destino.
- Dos peers con el mismo TG en slots distintos pueden ambos escuchar la misma transmisión, cada uno en su slot.
- En simplex (DMO), MMDVMHost descarta paquetes con el bit TS1; el servidor entrega voz de downlink en TS2 para peers simplex.
Gate de downlink: ¿un peer recibe el paquete?
Para cada peer y cada frame de voz de grupo, el servidor evalúa una cadena de filtros. Todos deben pasar para que el paquete se entregue.
flowchart TD
PKT[DMRD hacia peer] --> F1{OPTIONS tiene TG<br/>o dinámico activo?}
F1 -->|No| DROP[No entrega]
F1 -->|Sí| F2{TG especial 9990-9999?}
F2 -->|Sí| PASS1[Punto-a-punto:<br/>sólo RX_PEER (o peer único)]
F2 -->|No| F3{¿Slot destino<br/>libre?}
F3 -->|Peer TXing ingress| DROP
F3 -->|Bridge hold activo<br/>TG distinto| DROP
F3 -->|Stream activo<br/>en ese slot, TG distinto| DROP
F3 -->|Libre| F4{GROUP_HANGTIME<br/>respeta?}
F4 -->|No| DROP
F4 -->|Sí| F5{SINGLE lock<br/>compatible?}
F5 -->|Bloqueado por otro TG| DROP
F5 -->|OK| SEND[Entregar DMRD al peer]
PASS1 --> SEND
Condiciones que bloquean downlink (per-peer)
| Condición | Detalle |
|---|---|
| Ingress activo | El peer está transmitiendo en ese slot → no recibe hasta terminar TX. |
| Bridge hold | El slot tiene un bridge_hold (ingress propio o listener) que bloquea TGs ajenos por GROUP_HANGTIME. |
| Stream activo, TG distinto | Ya hay un stream downlink activo en ese slot con otro TG → espera a que termine. |
| GROUP_HANGTIME | Si el último TG en ese slot fue distinto y está dentro del hangtime → bloquea. |
| SINGLE lock incompatible | SINGLE=1 con lock en otro TG → bloquea salvo que sea el TG del lock o el peer lo activara. |
| Sesión stale | Si la sesión per-peer lleva _STALE_PEER_SESSION_TIMEOUT sin frames, se purga y el slot se libera. |
TG especiales (9990–9999): entrega punto-a-punto
El eco (9990) y los TG de servicio bajo demanda (9991–9999) omiten la contención de slot per-peer y los filtros OPTIONS descritos arriba, pero no se difunden a todos los peers. En un MASTER inject-only multi-peer se entregan punto-a-punto:
- El paquete va sólo a
RX_PEER— el peer exacto que originó la llamada en ese slot — cuandoRX_TGIDcoincide con el TG especial. - Si
RX_TGIDaún no coincide (p. ej. el primer VHEAD de una transmisión a 9990 llega antes de que se actualiceRX_TGID) y sólo hay un peer conectado, se entrega a ese peer único (fallback legado). - No hay matching difuso del ID DMR de origen: otros hotspots del mismo usuario nunca reciben el eco ni la reproducción de servicio.
Esto coincide con el legado adn-dmr-server, donde cada MASTER tenía un solo
peer y el eco volvía naturalmente sólo al llamante. Ver Echo.
Mid-call join
Cuando un stream downlink termina (VTERM o timeout), el slot del peer queda libre. El siguiente frame de cualquier otro TG que el peer tenga activo (estático o dinámico) y que esté en curso en la red se entrega sin requerir nuevo PTT.
Esto no relaja ninguna regla: GROUP_HANGTIME, SINGLE y contención per-peer se evalúan igual que para cualquier stream. Es simplemente el comportamiento normal cuando el slot queda libre.
OpenBridge y TGs dinámicos
Si un hotspot en el servidor 7302 activó el TG 7300 dinámicamente y un
hotspot en 7301 transmite ese TG, el tráfico OBP debe llegar al hotspot 7302
aunque 7300 no esté en sus OPTIONS. La pata de bridge se activa por la sesión
UA (vía master_dynamic_tg_slots), que consulta tanto _PEER_UA_SESSIONS
(SINGLE=1) como _PEER_UA_MULTI_TGS (SINGLE=0).
Ver Protocolo OpenBridge para filtros de ingreso, BCSQ/BCKA y detalles de DMRE v5.
Divergencias vs legacy
| Comportamiento | Legacy (adn-dmr-server) |
Este servidor |
|---|---|---|
| Activar TG dinámico (TG libre) | 2 TX: 1ª activa, 2ª pasa voz | 1 TX: activa + pasa voz |
| TX en TG ocupado | Se bloquea por contención (reglas 1–4) | Activación silenciosa: activa TG, no sube uplink, entrega downlink |
| Fin de stream → join TG activo | El peer queda en hangtime; no hay join mid-stream | Al terminar el stream, el siguiente TG activo se entrega sin nuevo PTT |
| OPTIONS refresh (loop 26 s) | options_config_loop cada 26 s |
D-28: por evento (RPTO, startup, fallback dmrd) — sin loop periódico |
GROUP_HANGTIME |
RX + TX, por sistema, sin reset en VTERM | Paridad (mismo comportamiento) |
OPTIONS sobrescribe GROUP_HANGTIME |
No | Paridad (no se puede) |
Ver también
- Bridges y talkgroups — el modelo
BRIDGES/ subscriptions. - Números especiales — TG 4000, 999x, eco.
- Comportamiento y temporizadores — intervalos de bucles periódicos.
- BRIDGES vs Subscriptions — modelo interno.
- Protocolo OpenBridge — DMRE v5, filtros de ingreso.
- Proxy hotspot — PROXY / self-service integrados.