You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ADN-DMR-Peer-Server/docs/es/server/development/routing-and-contention.md

381 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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
```mermaid
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
1. **Autenticación HBP** — el peer debe estar registrado con passphrase válida.
2. **ACL** — `SUB_ACL`, `TGID_TS1_ACL`, `TGID_TS2_ACL` (si `USE_ACL`).
3. **`dmrd_received`** — actualiza `STATUS[slot]`: `RX_TIME`, `RX_TGID`,
`RX_STREAM_ID`, `RX_TYPE`, `RX_PEER`.
4. **Contención global** — si el TG ya tiene un stream activo desde otra
fuente, se bloquea o activa silenciosamente.
5. **Source row ACTIVE** — debe existir una pata de bridge `ACTIVE` para ese
`system/slot/TG`. Si no existe, se crea una dinámica.
6. **Fan-out** — para cada peer del MASTER, el gate de downlink decide si
recibe el paquete.
7. **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_TGID` **no 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 `CONTENTION` del 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](behaviour-and-timers.md) para los
intervalos de los bucles periódicos (`rule_timer`, `stream_trimmer`, etc.).
### Estados de un stream
```mermaid
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**.
```mermaid
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 `RPTO` actualizado al MASTER.
- **Startup/reload** — `apply_startup_bridges` aplica los TG al arranque y en
`SIGHUP`.
- **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.
```mermaid
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)](../user-guide/bridges-and-talkgroups.md#persistencia-tg-dinamicos-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:
1. **No rechaza** la TX.
2. **Activa** ese TG como dinámico.
3. **No sube el uplink** del usuario (no molesta el QSO activo).
4. **Sí entrega el downlink** del QSO activo al usuario inmediatamente.
```mermaid
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.
```mermaid
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.
```mermaid
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 — cuando `RX_TGID` coincide con el TG especial.
- Si `RX_TGID` aún no coincide (p. ej. el primer VHEAD de una transmisión a
9990 llega antes de que se actualice `RX_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](../user-guide/echo.md).
### 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](../protocols/openbridge.md) 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](../user-guide/bridges-and-talkgroups.md) — el modelo
`BRIDGES` / subscriptions.
- [Números especiales](../user-guide/special-numbers.md) — TG 4000, 999x, eco.
- [Comportamiento y temporizadores](behaviour-and-timers.md) — intervalos de
bucles periódicos.
- [BRIDGES vs Subscriptions](bridges-vs-subscriptions.md) — modelo interno.
- [Protocolo OpenBridge](../protocols/openbridge.md) — DMRE v5, filtros de
ingreso.
- [Proxy hotspot](../user-guide/hotspot-proxy.md) — PROXY / self-service
integrados.

Powered by TurnKey Linux.