docs: add bridges vs subscriptions, performance guide, and Mermaid diagrams (#6)
Document 2.x routing model with a concrete TG 52090 example, operator-focused performance gains (indexes, reporting, integrated proxy), and MkDocs Mermaid support across server and monitor architecture pages.pull/8/head
parent
748e214192
commit
8574e1e42f
@ -0,0 +1,208 @@
|
||||
# BRIDGES (legacy) vs Subscriptions (servidor nuevo)
|
||||
|
||||
**adn-dmr-server** y **adn-server 2.x** reenvían voz de grupo igual en el wire: una “tabla de bridge” por talkgroup decide qué **systems** reciben copia del stream, con **reescritura LC** por pata. Lo que cambia en 2.x es **cómo se representa esa tabla en código** — no las reglas visibles para el operador.
|
||||
|
||||
## Para el operador
|
||||
|
||||
En runtime el comportamiento de bridges es el mismo: TG, slots, OPTIONS, UA, TG 4000, OpenBridge. **Subscriptions** es el nombre interno del motor de enrutado en 2.x; no es un modo de operación distinto ni algo que configures aparte.
|
||||
|
||||
**Sin cambios operativos**
|
||||
|
||||
- Configuración: **`SYSTEMS`**, OPTIONS del hotspot, **`SELF_SERVICE`** / MariaDB — igual que con **adn-dmr-server**. No hay bloque `BRIDGES` en YAML en ninguno de los dos.
|
||||
- Paridad de reglas: fila origen ACTIVE, tablas `#…`, timers UA, `GEN_STAT_BRIDGES`, etc.
|
||||
|
||||
**Mejoras reales en 2.x**
|
||||
|
||||
| Área | Efecto |
|
||||
|------|--------|
|
||||
| **Estabilidad del enrutado** | El estado de bridges vive en un store dedicado; informes y timers ya no comparten la misma estructura mutable que el forward de voz. Menos desvíos entre lo que reenvía el servidor y lo que muestra el panel bajo carga. |
|
||||
| **Monitor** | **Informe v2** (`routing_table`, `topology`) hacia **adn-monitor 2.x** sustituye pickle/CSV; el BTABLE refleja mejor el estado del peer. |
|
||||
| **TG dinámicos** | Con **`DATABASE`**, los dinámicos por peer se **persisten** y se restauran al reconectar (≥ 2.0.0-rc.3). |
|
||||
| **Evolución** | Parches de timers, OpenBridge, ACL o self-service no pasan por un dict global compartido con todo el proceso. |
|
||||
|
||||
El término **subscription** solo importa si lees código o esta guía; en el panel y en el aire sigues hablando de **bridges** y **talkgroups**.
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| | **Legacy (`adn-dmr-server`)** | **Nuevo (`adn-server` 2.x)** |
|
||||
|---|------------------------------|------------------------------|
|
||||
| **Autoridad en runtime** | Dict global `BRIDGES` (`bridge_master.py`) | **`SubscriptionStore`** (objetos `Subscription` de dominio) |
|
||||
| **Estructura** | `bridge_key → [ fila, fila, … ]` | Una **subscription** por pata de system en un canal |
|
||||
| **Resolución de reenvío** | Recorrer filas, llamar `to_target` | **`SubscriptionRouter.resolve()`** → `ForwardLeg` |
|
||||
| **Wire monitor / informes** | Pickle `BRIDGE_SND` = `BRIDGES` | JSON `routing_table` (v2) o export `BRIDGES` (compat v1) |
|
||||
| **Bloque YAML `BRIDGES:`** | No se carga desde config en ninguno; filas en runtime | Igual — filas desde OPTIONS, UA, STAT, OpenBridge, bootstrap echo |
|
||||
|
||||
El comportamiento observable (guardia de fila origen, UA dinámico, TG estática, claves reflector `#…`, match OpenBridge TS1, timers) sigue **paridad legacy** con `bridge_master.py`.
|
||||
|
||||
## Legacy: dict `BRIDGES`
|
||||
|
||||
En **adn-dmr-server**, el estado de enrutado es un **diccionario global**:
|
||||
|
||||
```text
|
||||
BRIDGES["52090"] = [
|
||||
{ "SYSTEM": "MASTER-A", "TS": 2, "TGID": b'...', "ACTIVE": True, "TO_TYPE": "ON", "TIMER": …, … },
|
||||
{ "SYSTEM": "MASTER-B", "TS": 2, "TGID": b'...', "ACTIVE": True, "TO_TYPE": "ON", … },
|
||||
{ "SYSTEM": "OBP-UK", "TS": 1, "TGID": b'...', "ACTIVE": False, … },
|
||||
]
|
||||
BRIDGES["#310"] = [ … ] # tablas reflector / marcado
|
||||
```
|
||||
|
||||
Cada **fila** es una pata. Campos importantes:
|
||||
|
||||
- **`SYSTEM`** — nombre del system configurado (master HBP o pata OpenBridge).
|
||||
- **`TS`** — slot 1 o 2 (fuentes OpenBridge usan **TS 1** en el match).
|
||||
- **`TGID`** — bytes de destino para **reescritura LC** hacia esa pata.
|
||||
- **`ACTIVE`** — la pata participa en el reenvío si es true.
|
||||
- **`TIMEOUT` / `TIMER` / `TO_TYPE` / `ON` / `OFF` / `RESET`** — timers UA, static/stat, reglas VTERM in-band.
|
||||
|
||||
**Camino de voz (`dmrd_received`):**
|
||||
|
||||
1. Obtener **clave de bridge** desde la TG destino (y tablas `#…` cuando aplica).
|
||||
2. Crear tabla dinámica si no existe (UA / STAT / OPTIONS estáticas — mismos disparadores que legacy).
|
||||
3. Buscar **fila origen ACTIVE** que coincida con **system + slot + TGID** actuales.
|
||||
4. Por cada otra fila **ACTIVE** en **esa misma tabla**, llamar **`to_target`** (contención, ACL, LC/TA, control de bucle OpenBridge).
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
IN[Ingreso DMRD / DMRE] --> DM[dmrd_received]
|
||||
DM --> KEY{¿existe bridge key?}
|
||||
KEY -->|no| CREATE[ensure dynamic / stat / static]
|
||||
CREATE --> BR
|
||||
KEY -->|si| BR[(dict BRIDGES)]
|
||||
BR --> SRC{¿fila origen ACTIVE\nSYSTEM + TS + TGID?}
|
||||
SRC -->|si| SCAN[otras patas ACTIVE\nmisma tabla]
|
||||
SCAN --> TT[to_target por pata]
|
||||
TT --> OUT[Salida HBP / OpenBridge]
|
||||
SRC -->|no| DROP[sin reenvío]
|
||||
```
|
||||
|
||||
El monitor lee el **mismo dict** vía pickle **`BRIDGE_SND`**.
|
||||
|
||||
## Servidor nuevo: `Subscription` + `SubscriptionStore`
|
||||
|
||||
En **adn-server 2.x**, el **modelo de dominio** sustituye filas ad hoc:
|
||||
|
||||
- **`AudioChannel`** — TG lógica + slot `(tgid, slot)`.
|
||||
- **`Subscription`** — participación de un system: **role**, **política de activación**, **state** (fase, timer), **target_tgid** (LC), **`relay_table_key`** opcional (tablas `#…`).
|
||||
- **`SubscriptionStore`** — única **autoridad de enrutado** en runtime (sin mutar `BRIDGES` en paralelo).
|
||||
|
||||
**Camino de voz** (misma semántica, otros tipos):
|
||||
|
||||
1. `RoutingUseCases.dmrd_received` actualiza el store (crear relay, TG estática, timeout UA — hooks legacy).
|
||||
2. **`SubscriptionRouter.relay_tables_with_active_source`** — tablas donde el system de ingreso tiene subscription **ACTIVE** para slot/TG.
|
||||
3. **`SubscriptionRouter.resolve`** — devuelve **`ForwardLeg`** (system, slot, tgid) del resto de subscriptions **ACTIVE** en esas tablas.
|
||||
4. Mixins de forward envían paquetes (paridad `to_target`).
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
IN[Ingreso DMRD / DMRE] --> RU[RoutingUseCases.dmrd_received]
|
||||
RU --> SS[(SubscriptionStore)]
|
||||
RU --> SR[SubscriptionRouter]
|
||||
SS --> SR
|
||||
SR --> LEGS[Lista ForwardLeg]
|
||||
LEGS --> FWD[Mixins HBP / OBP]
|
||||
SS --> EXP[RoutingTableLegacyView\nsolo export]
|
||||
EXP --> MON[Pickle BRIDGE_SND\no JSON routing_table]
|
||||
```
|
||||
|
||||
**Importante:** `routing_table_for_report()` / **`BRIDGE_SND`** es un **export unidireccional** para paneles (`RoutingTableLegacyView`). **No** decide reenvíos. Evita el patrón legacy de mutar un dict global compartido con informes.
|
||||
|
||||
## Mapeo fila → subscription
|
||||
|
||||
| Fila legacy `BRIDGES` | `Subscription` de dominio |
|
||||
|-----------------------|---------------------------|
|
||||
| Clave tabla (`"52090"`, `"#310"`) | `relay_table_key` + TG del canal |
|
||||
| `SYSTEM` | `system` (`SystemId`) |
|
||||
| `TS` + contexto TG | `channel.slot` / `channel.tgid` |
|
||||
| `TGID` (bytes) | `target_tgid` (reescritura LC) |
|
||||
| `ACTIVE` | `state.phase` (`ACTIVE` / `IDLE`) |
|
||||
| `TIMER` | `state.timer_expires_at` |
|
||||
| `TIMEOUT` | `timeout_seconds` |
|
||||
| `TO_TYPE` (`ON`, `OFF`, `STAT`, `NONE`) | `role` + `policy` |
|
||||
| `ON` / `OFF` / `RESET` | `triggers` (`InbandTriggers`) |
|
||||
|
||||
Helpers import/export: `routing_table_import.py`, `routing_table_export.py` (espejo de `bridges_export` legacy).
|
||||
|
||||
## Comparación extremo a extremo (un frame de voz)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Radio
|
||||
participant Server
|
||||
participant Peer as Otro system
|
||||
|
||||
Radio->>Server: Voz grupo TG 52090 TS2
|
||||
|
||||
rect rgb(40,40,50)
|
||||
note right of Server: Legacy
|
||||
Server->>Server: ¿Fila origen ACTIVE en BRIDGES["52090"]?
|
||||
Server->>Server: foreach pata ACTIVE en tabla
|
||||
Server->>Peer: to_target (reescribe LC)
|
||||
end
|
||||
|
||||
rect rgb(30,50,40)
|
||||
note right of Server: Servidor nuevo (mismas reglas)
|
||||
Server->>Server: SubscriptionRouter.resolve()
|
||||
Server->>Peer: forward ForwardLeg(s)
|
||||
end
|
||||
```
|
||||
|
||||
## Lo que **no** cambió
|
||||
|
||||
- **Claves** de bridge (`52090`, `#reflector`, …) y tablas **multi-pata**.
|
||||
- **Guardia de fila origen** — reenviar solo desde una tabla donde **este** system es origen ACTIVE para ese contexto TG/slot.
|
||||
- **UA dinámico**, **OPTIONS estáticas**, **`GEN_STAT_BRIDGES`**, **TG 4000**, bootstrap **echo 9990**.
|
||||
- Pasadas de timer (`rule_timer`, `bridgeDebug`, …) — misma tabla lógica, implementada sobre el store en 2.x.
|
||||
|
||||
## Ejemplo concreto
|
||||
|
||||
**Bridge** (concepto de red): “la TG 52090 une estos systems y reenvía voz entre ellos”.
|
||||
**Subscription** (solo en código 2.x): **una pata** de esa tabla — p. ej. “MASTER-A en TG 52090, slot 2, ACTIVE, con su LC y timer”.
|
||||
|
||||
No es bridge *o* subscription: en 2.x un bridge **es** un conjunto de subscriptions sobre el mismo canal (TG + slot).
|
||||
|
||||
### Escenario
|
||||
|
||||
Alguien habla en **TG 52090** y deben oírlo **MASTER-A**, **MASTER-B** y una pata **OpenBridge**. En el panel y en el aire eso es un **bridge** (tabla de la TG 52090).
|
||||
|
||||
**Legacy (`adn-dmr-server`)** — todo en un dict global:
|
||||
|
||||
```text
|
||||
BRIDGES["52090"] = [
|
||||
{ SYSTEM: "MASTER-A", TS: 2, ACTIVE: True, TGID: …, TIMER: … },
|
||||
{ SYSTEM: "MASTER-B", TS: 2, ACTIVE: True, TGID: … },
|
||||
{ SYSTEM: "OBP-UK", TS: 1, ACTIVE: False, TGID: … },
|
||||
]
|
||||
```
|
||||
|
||||
Cuando llega un frame de voz:
|
||||
|
||||
1. Buscar la fila donde **este** system es origen **ACTIVE** (mismo TG/slot).
|
||||
2. Recorrer las **demás filas ACTIVE** de la misma tabla.
|
||||
3. Por cada una, **`to_target`** → reenvío con LC reescrito.
|
||||
|
||||
El monitor lee **el mismo dict** (pickle **`BRIDGE_SND`**).
|
||||
|
||||
**Servidor nuevo (`adn-server` 2.x)** — misma tabla, otro formato interno:
|
||||
|
||||
```text
|
||||
SubscriptionStore — TG 52090 / slot 2:
|
||||
- subscription MASTER-A (ACTIVE, target_tgid, timer…)
|
||||
- subscription MASTER-B (ACTIVE, …)
|
||||
- subscription OBP-UK (IDLE, …)
|
||||
```
|
||||
|
||||
Cuando llega voz, **`SubscriptionRouter.resolve()`** aplica las **mismas reglas** (origen ACTIVE, resto de patas ACTIVE) y devuelve **`ForwardLeg`** para reenviar. El monitor recibe una **vista exportada** (`BRIDGE_SND` o JSON **`routing_table`**); esa exportación **no** decide el reenvío.
|
||||
|
||||
## Dónde leer código
|
||||
|
||||
| Tema | Legacy | Nuevo |
|
||||
|------|--------|-------|
|
||||
| Ingreso voz | `adn-dmr-server/bridge_master.py` (`routerHBP.dmrd_received`) | `application/routing_use_cases.py` |
|
||||
| Reenvío a pata | `to_target` | `application/routing/hbp_forward.py`, `obp_forward.py` |
|
||||
| Estado tabla | `BRIDGES` global | `application/subscription/` (`store`, `router`, ops) |
|
||||
| Export monitor | `send_routing_table` / pickle | `routing_table_legacy_view.py`, informe v2 `routing_table` |
|
||||
|
||||
Ver también: [Bridges y talkgroups](../user-guide/bridges-and-talkgroups.md), [Arquitectura](architecture.md), [Rendimiento (2.x)](performance.md), [Protocolo de informes v2](../protocols/report-v2.md#routing_table).
|
||||
Loading…
Reference in new issue