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.
209 lines
10 KiB
209 lines
10 KiB
# 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).
|