parent
d94bc0965e
commit
4d7a8c136c
@ -1,135 +0,0 @@
|
|||||||
# ADN Parrot (Playback)
|
|
||||||
|
|
||||||
Exact port of the legacy `playback.py` to clean architecture.
|
|
||||||
Records incoming group voice calls and plays them back to the sender.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
Hotspot ──► adn-server (MASTER :56400)
|
|
||||||
│
|
|
||||||
├── bridge routes TG 9990 to ECHO
|
|
||||||
│
|
|
||||||
└── ECHO (PEER :54917) ──► adn-parrot (MASTER :54915)
|
|
||||||
│
|
|
||||||
├── records DMRD packets
|
|
||||||
├── waits 2 s
|
|
||||||
└── plays back with new stream ID
|
|
||||||
```
|
|
||||||
|
|
||||||
The parrot runs a **MASTER** system. The `ECHO` **PEER** (defined in `adn-server.yaml`)
|
|
||||||
connects to it. When a user transmits to TG 9990, the bridge forwards the call to
|
|
||||||
the ECHO peer, which relays it to the parrot master. The parrot records all packets,
|
|
||||||
then replays them back through the same path.
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
| File | Description |
|
|
||||||
|---|---|
|
|
||||||
| `adn-parrot.py` | Launcher script (like `adn-server.py`) |
|
|
||||||
| `adn-parrot.example.yaml` | Example config (copy to `adn-parrot.yaml`) |
|
|
||||||
| `src/adn_server/parrot_main.py` | Entrypoint: config loading, protocol setup, reactor |
|
|
||||||
| `src/adn_server/application/playback_use_cases.py` | Recording/playback logic (port of `playback.py`) |
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Copy the example and set your passphrase:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp adn-parrot.example.yaml adn-parrot.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
The config defines a single MASTER system (`PARROT`) that listens on `127.0.0.1:54915`.
|
|
||||||
The passphrase must match the one used by the ECHO peer in `adn-server.yaml`.
|
|
||||||
|
|
||||||
Key settings:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
SYSTEMS:
|
|
||||||
PARROT:
|
|
||||||
MODE: MASTER
|
|
||||||
PORT: 54915 # must match ECHO.MASTER_PORT in adn-server.yaml
|
|
||||||
PASSPHRASE: passw0rd # must match ECHO.PASSPHRASE in adn-server.yaml
|
|
||||||
MAX_PEERS: 1
|
|
||||||
ALLOW_UNREG_ID: true
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Direct
|
|
||||||
python adn-parrot.py
|
|
||||||
python adn-parrot.py -c /path/to/adn-parrot.yaml
|
|
||||||
python adn-parrot.py --logging DEBUG
|
|
||||||
|
|
||||||
# systemd
|
|
||||||
sudo systemctl start adn-parrot
|
|
||||||
sudo systemctl enable adn-parrot
|
|
||||||
```
|
|
||||||
|
|
||||||
### systemd service
|
|
||||||
|
|
||||||
Create `/etc/systemd/system/adn-parrot.service`:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[Unit]
|
|
||||||
Description=ADN DMR Parrot (playback)
|
|
||||||
After=multi-user.target adn-server.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=root
|
|
||||||
Type=simple
|
|
||||||
Restart=always
|
|
||||||
RestartSec=3
|
|
||||||
SyslogIdentifier=adn-parrot
|
|
||||||
WorkingDirectory=/opt/new-adn-server
|
|
||||||
ExecStart=/usr/bin/python3 /opt/adn-server/adn-parrot.py -c /opt/adn-server/adn-parrot.yaml
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
|
||||||
|
|
||||||
Then:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable --now adn-parrot
|
|
||||||
```
|
|
||||||
|
|
||||||
## Playback flow
|
|
||||||
|
|
||||||
1. ECHO peer receives DMRD from master, forwards to parrot
|
|
||||||
2. Parrot detects new `stream_id` → starts recording
|
|
||||||
3. Parrot receives voice terminator → stops recording
|
|
||||||
4. Waits 2 seconds
|
|
||||||
5. Generates new `stream_id`, replays all packets with `sleep(0.06)` between each
|
|
||||||
6. ECHO peer receives playback, master repeats to hotspot
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
|
|
||||||
The parrot uses the standard HBP protocol. Any legacy `playback.py` instance running
|
|
||||||
on another server can connect to our MASTER systems, and our ECHO peer can connect to
|
|
||||||
any legacy parrot master. The protocol is identical.
|
|
||||||
|
|
||||||
## Logs
|
|
||||||
|
|
||||||
Default log file: `/var/log/adn-server/parrot.log`
|
|
||||||
|
|
||||||
Successful startup looks like:
|
|
||||||
|
|
||||||
```
|
|
||||||
INFO ADN Parrot -- SYSTEM STARTING...
|
|
||||||
DEBUG MASTER instance created: PARROT, <HBPProtocol ...>
|
|
||||||
INFO (PARROT) Repeater Logging in with Radio ID: 9990, 127.0.0.1:54917
|
|
||||||
INFO (PARROT) Peer 9990 has completed the login exchange successfully
|
|
||||||
INFO (PARROT) Peer b'ECHO ' (9990) has sent repeater configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
Recording/playback:
|
|
||||||
|
|
||||||
```
|
|
||||||
INFO (PARROT) *START RECORDING* STREAM ID: 12345 SUB: 7301001 TGID 9990, TS 2
|
|
||||||
INFO (PARROT) *END RECORDING* STREAM ID: 12345
|
|
||||||
INFO (PARROT) *START PLAYBACK* STREAM ID: 67890 Duration: 3.2
|
|
||||||
INFO (PARROT) *END PLAYBACK* STREAM ID: 67890
|
|
||||||
```
|
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# Origen de la documentación
|
||||||
|
|
||||||
|
El Markdown publicado con **MkDocs** está en **`en/`** (inglés) y **`es/`** (español). La **entrada por idioma** para GitHub y el sitio generado es **`docs/en/README.md`** o **`docs/es/README.md`**.
|
||||||
|
|
||||||
|
| Idioma | `docs_dir` | Salida típica | Comando |
|
||||||
|
|--------|------------|---------------|---------|
|
||||||
|
| Inglés | `docs/en` | `site/en/` | `mkdocs build` (usa `mkdocs.yml`) |
|
||||||
|
| Español | `docs/es` | `site/es/` | `mkdocs build -f mkdocs.es.yml` |
|
||||||
|
|
||||||
|
- **Estructura:** `en/server/` y `es/server/` — peer server; `en/monitor/` y `es/monitor/` — ADN Monitor. Las rutas relativas bajo cada locale deben coincidir para facilitar traducciones. Más detalle en [Traducciones](en/server/contributing/translations.md) (EN) / [Traducciones](es/server/contributing/translations.md) (ES).
|
||||||
@ -1,76 +0,0 @@
|
|||||||
# TTS (Text-to-Speech) Setup
|
|
||||||
|
|
||||||
TTS announcements convert text files to AMBE and play them on a talkgroup at intervals or hourly.
|
|
||||||
|
|
||||||
## Pipeline
|
|
||||||
|
|
||||||
```
|
|
||||||
.txt → gTTS → .mp3 → ffmpeg → .wav (8kHz mono) → vocoder/AMBEServer → .ambe → broadcast
|
|
||||||
```
|
|
||||||
|
|
||||||
## First-time setup
|
|
||||||
|
|
||||||
1. **Create the text file** in `Audio/<LANGUAGE>/ondemand/<FILE>.txt`
|
|
||||||
|
|
||||||
Example for `FILE: texto1` and `LANGUAGE: es_ES`:
|
|
||||||
```
|
|
||||||
Audio/es_ES/ondemand/texto1.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Put the text you want spoken in that file (UTF-8).
|
|
||||||
|
|
||||||
2. **Configure encoding** — choose one:
|
|
||||||
- **Physical AMBE (DV3000)**: set `TTS_AMBESERVER_HOST` to the host where [AMBEServer](https://github.com/marrold/AMBEServer) runs (connected to the DV3000).
|
|
||||||
- **Software vocoder**: set `TTS_VOCODER_CMD` with a command like `/usr/local/bin/md380-vocoder -e {wav} {ambe}`.
|
|
||||||
|
|
||||||
3. **Enable the TTS item** in `TTS_ANNOUNCEMENTS`:
|
|
||||||
```yaml
|
|
||||||
TTS_ANNOUNCEMENTS:
|
|
||||||
- ENABLED: true
|
|
||||||
FILE: texto1
|
|
||||||
TG: 2
|
|
||||||
MODE: interval
|
|
||||||
INTERVAL: 60
|
|
||||||
LANGUAGE: es_ES
|
|
||||||
```
|
|
||||||
|
|
||||||
## How it works
|
|
||||||
|
|
||||||
- **First run**: The system reads `texto1.txt`, generates speech (gTTS), converts to WAV, encodes to AMBE via the configured vocoder/AMBEServer, saves `texto1.ambe`, and broadcasts it.
|
|
||||||
- **Later runs**: If `texto1.ambe` exists and is newer than `texto1.txt`, it uses the cached AMBE file directly (no conversion).
|
|
||||||
- **Update text**: Edit `texto1.txt` and save. On the next interval, the system will regenerate `texto1.ambe` because the .txt is newer.
|
|
||||||
|
|
||||||
## Physical AMBE (AMBEServer)
|
|
||||||
|
|
||||||
When using a DV3000 or similar hardware via AMBEServer:
|
|
||||||
|
|
||||||
1. Run AMBEServer on a host with the DV3000 connected (USB/serial).
|
|
||||||
2. In `adn-voice.yaml`:
|
|
||||||
```yaml
|
|
||||||
TTS_AMBESERVER_HOST: "192.168.1.10" # host where AMBEServer runs
|
|
||||||
TTS_AMBESERVER_PORT: 2460 # default
|
|
||||||
```
|
|
||||||
3. Ensure the ADN server can reach that host:port (UDP).
|
|
||||||
|
|
||||||
### Troubleshooting physical AMBE
|
|
||||||
|
|
||||||
| Symptom | Check |
|
|
||||||
|--------|-------|
|
|
||||||
| "Text file not found" | Create `Audio/<LANG>/ondemand/<FILE>.txt` (e.g. `Audio/es_ES/ondemand/texto1.txt`) |
|
|
||||||
| "AMBEServer failed, trying external vocoder..." | AMBEServer unreachable or timeout. Verify host/port, firewall, AMBEServer running. |
|
|
||||||
| "Could not encode to AMBE" | Neither AMBEServer nor TTS_VOCODER_CMD worked. Set one of them. |
|
|
||||||
| "Timeout connecting to AMBEServer" | Network/firewall; AMBEServer not listening; DV3000 not connected. |
|
|
||||||
| No audio plays | Check logs for "(TTS) Playing TTS file" — if present, broadcast succeeded; check TG and bridge targets. |
|
|
||||||
|
|
||||||
### Log messages
|
|
||||||
|
|
||||||
- `(TTS) Converting text to AMBE: ...` — first-time conversion in progress
|
|
||||||
- `(TTS) Using AMBEServer host:port` — using physical AMBE
|
|
||||||
- `(TTS) Using cached AMBE` — .ambe exists and is newer than .txt
|
|
||||||
- `(TTS) AMBEServer failed, trying external vocoder...` — AMBEServer failed; fallback to TTS_VOCODER_CMD if set
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
- **gTTS**: `pip install gTTS`
|
|
||||||
- **ffmpeg**: system package (`apt install ffmpeg` etc.)
|
|
||||||
- **AMBEServer** (physical AMBE): [marrold/AMBEServer](https://github.com/marrold/AMBEServer) on a host with DV3000
|
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
# ADN Systems documentation
|
||||||
|
|
||||||
|
This site covers **ADN DMR Peer Server** and **ADN Monitor** as one operational stack. Content is organized by **product** (`server/` vs `monitor/`) under the active **locale**.
|
||||||
|
|
||||||
|
- **English:** [`docs/en/`](README.md) (this tree) — build with `mkdocs.yml` → `site/en/`.
|
||||||
|
- **Spanish:** same content under `docs/es/` — `mkdocs build -f mkdocs.es.yml` → `site/es/` (see `docs/es/README.md` in the repository).
|
||||||
|
|
||||||
|
## ADN DMR Peer Server
|
||||||
|
|
||||||
|
The **ADN DMR Peer Server** is a [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.html) conference bridge for digital mobile radio (DMR). It is structured in **clean architecture** layers (domain, application, infrastructure).
|
||||||
|
|
||||||
|
### What the server does
|
||||||
|
|
||||||
|
- Terminates **HBP** (HomeBrew Protocol) links to **MASTER** and **PEER** systems (hotspots, repeaters).
|
||||||
|
- Terminates **OpenBridge** links to other servers over UDP — **DMRE v5** (recommended on ADN) or legacy **DMRD** v1.
|
||||||
|
- Runs **bridge routing** (`BRIDGES`): forwards group voice, loop control, ACLs, optional **BCSQ** / **BCKA**.
|
||||||
|
- Supports **private calls** (`SUB_MAP`), **voice**, **TTS**, **recording**, and **TCP reporting** to the monitor.
|
||||||
|
|
||||||
|
### Where to start (server)
|
||||||
|
|
||||||
|
| I want to… | Start here |
|
||||||
|
|------------|------------|
|
||||||
|
| Run and configure | [Introduction](server/user-guide/introduction.md), [Configuration](server/user-guide/configuration.md) |
|
||||||
|
| TG 4000, 999x, echo | [Special numbers](server/user-guide/special-numbers.md) |
|
||||||
|
| Private calls | [Private calls](server/user-guide/private-calls.md) |
|
||||||
|
| Voice / TTS | [Voice, announcements, and TTS](server/user-guide/voice-and-tts.md) |
|
||||||
|
| OpenBridge / DMRE | [OpenBridge](server/protocols/openbridge.md), [DMRE v5](server/protocols/dmre-v5.md) |
|
||||||
|
| HBP | [HBP](server/protocols/hbp.md) |
|
||||||
|
| Code layout | [Architecture](server/development/architecture.md), [Behaviour and timers](server/development/behaviour-and-timers.md) |
|
||||||
|
| Credits, license, lineage | [Credits & license](server/user-guide/attribution.md) |
|
||||||
|
|
||||||
|
### Quick start (server)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
cp adn-server.example.yaml adn-server.yaml
|
||||||
|
python adn-server.py -c adn-server.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
More: [Introduction](server/user-guide/introduction.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADN Monitor
|
||||||
|
|
||||||
|
Dashboard, WebSocket live view, optional **PHP API**, **MySQL** self-service, and **hotspot proxy** — see [Monitor overview](monitor/index.md).
|
||||||
|
|
||||||
|
| I want to… | Start here |
|
||||||
|
|------------|------------|
|
||||||
|
| `adn-mon.yaml` and layout | [Monitor configuration](monitor/configuration.md) |
|
||||||
|
| Hotspot proxy (UDP, `PROXY`, peer port range) | [Hotspot proxy](monitor/hotspot-proxy.md) |
|
||||||
|
| Self-service | [Self-service](monitor/self-service.md) |
|
||||||
|
| How it connects to the server | [Monitoring and reports](server/user-guide/monitoring.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Locales
|
||||||
|
|
||||||
|
- **English** — **`docs/en/`** (this build).
|
||||||
|
- **Spanish** — **`docs/es/`**.
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
# Architecture and deployment
|
||||||
|
|
||||||
|
## Clean architecture (Python monitor)
|
||||||
|
|
||||||
|
Under `monitor/src/adn_monitor/`:
|
||||||
|
|
||||||
|
- **Domain** — value objects, errors, opcode types.
|
||||||
|
- **Application** — `MonitorState`, `process_message` in `monitor_controller.py`, alias service, Last Heard / TG count use cases, time formatting.
|
||||||
|
- **Infrastructure** — YAML `load_config`, Twisted **TCP client** (`ReportClientFactory`) to the peer server, **WebSocket** factory for the dashboard, MySQL repositories, pickle/json decoders for `CONFIG_SND` / `BRIDGE_SND`.
|
||||||
|
|
||||||
|
The monitor **connects outbound** to **`ADN_CONNECTION.ADN_IP:ADN_PORT`** and receives length-prefixed (netstring-style) messages. It updates in-memory **CTABLE** (masters/peers/OpenBridge) and **BTABLE** (bridges), and persists **BRDG_EVENT** outcomes when MySQL is configured.
|
||||||
|
|
||||||
|
## Report protocol (from the peer server)
|
||||||
|
|
||||||
|
Same opcodes as documented for the server: **CONFIG_SND**, **BRIDGE_SND**, **BRDG_EVENT**, etc. The monitor decodes and applies them in `process_message` — see [Monitoring and reports](../server/user-guide/monitoring.md).
|
||||||
|
|
||||||
|
## WebSocket
|
||||||
|
|
||||||
|
`monitor.py` runs a Twisted **WebSocket** on **`WEBSOCKET_SERVER.WEBSOCKET_PORT`**, pushing JSON snapshots at **`FREQUENCY`** so the React app updates without polling for core state.
|
||||||
|
|
||||||
|
## PHP backend
|
||||||
|
|
||||||
|
- **Slim 4** front controller: `backend/public/index.php`.
|
||||||
|
- Loads **`adn-mon.yaml`** via **`ADN_CONFIG_PATH`** (same as monitor).
|
||||||
|
- **`/api/config/dashboard`** — title, language, feature flags (`selfService`, `showConsole`, …) from **`DASHBOARD`**.
|
||||||
|
- **`/api/auth/*`** — session cookie auth when **SELF_SERVICE** DB is available.
|
||||||
|
- **`/api/self-service/*`** — device options (see [Self-service](self-service.md)).
|
||||||
|
- **`/api/aliases/*`** — optional proxy to TG/bridge list URLs from **ALIASES**.
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
- **Vite + React** under `frontend/`; build produces static assets served by nginx/Apache or similar.
|
||||||
|
- Uses **`API_BASE`** (build-time) to reach the PHP API and **WebSocket URL** for live data.
|
||||||
|
|
||||||
|
## Hotspot proxy
|
||||||
|
|
||||||
|
- Entry: `proxy/proxy.py`; package `src/adn_proxy/` (domain / application / infrastructure).
|
||||||
|
- Reads **`PROXY`** and **`SELF_SERVICE`** from the same YAML.
|
||||||
|
- For each hotspot client, allocates a port in **`DESTPORT_START`…`DEST_PORT_END`** and forwards UDP to **`MASTER`**.
|
||||||
|
- When **self-service** updates **`Clients.options`** and sets **`modified=1`**, the proxy sends **RPTO** to the **master** on a timer (~10 s). The **peer server** then applies options to the hotspot path (see [Self-service](self-service.md)).
|
||||||
|
|
||||||
|
**Why it is not part of the peer server binary:** it shares **`adn-mon.yaml`**, MySQL self-service, and deployment with the monitor stack — see [Why it ships with the monitor](hotspot-proxy.md#why-it-ships-with-the-monitor-not-inside-the-peer-server).
|
||||||
|
|
||||||
|
**Details:** [Hotspot proxy](hotspot-proxy.md) (config keys, peer server port range, startup).
|
||||||
|
|
||||||
|
## Typical deployment topology
|
||||||
|
|
||||||
|
```text
|
||||||
|
[Hotspots] --UDP--> [Proxy :LISTEN_PORT] --UDP--> [Peer server :DESTPORT range]
|
||||||
|
|
|
||||||
|
v
|
||||||
|
MySQL (Clients)
|
||||||
|
|
||||||
|
[Peer server :REPORT_PORT] <--- TCP --- [monitor.py : connects as client]
|
||||||
|
|
||||||
|
[Browser] --HTTPS--> [PHP API + static frontend]
|
||||||
|
[Browser] --WS----> [monitor WebSocket :9000]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Documentation home](../README.md)
|
||||||
|
- [Configuration](configuration.md)
|
||||||
|
- [Self-service](self-service.md)
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
# Self-service
|
||||||
|
|
||||||
|
**Self-service** lets a hotspot owner **log in** to the dashboard, **edit their device options** (static TG lists, default reflector, timer, language, etc.), and have those options **pushed to the ADN DMR Peer Server** without manually editing YAML on the server.
|
||||||
|
|
||||||
|
It involves **four** pieces: **MySQL** (`Clients` table), **PHP API** (session + REST), **React** (`/self-service` page), and the **hotspot proxy** (periodic **RPTO** to the master). The **peer server** is the component that ultimately applies **RPTO** / **OPTIONS** to the running bridge and hotspot behaviour.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **`SELF_SERVICE`** block in **`adn-mon.yaml`** with valid **MySQL** credentials and **PBKDF2** parameters matching your password tooling (same salt/iterations as **`hotspot_proxy_self_service.py`** when used).
|
||||||
|
2. **`DASHBOARD.SELF_SERVICE: true`** so the UI shows the **Self-service** menu entry (and the backend exposes `/api/self-service/*` when DB connects).
|
||||||
|
3. **`Clients`** table populated with rows: **`callsign`**, **`int_id`** (DMR ID), **`psswd`** (PBKDF2-SHA256 hex), **`options`** (semicolon-separated `KEY=value`), **`logged_in`**, **`host`**, **`modified`**, etc. (see adn-monitor DB schema / migration scripts in the repo).
|
||||||
|
4. Hotspot traffic should pass through the **proxy** if you rely on **`modified`** and **RPTO** push (see flow below).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
| Endpoint | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| **`POST /api/auth/login`** | Body: `callsign`, `password`. Verifies **PBKDF2** hash against **`Clients.psswd`** for rows with **`logged_in = 1`**. On success: PHP session with **`user_id`**, **`int_ids`** (all DMR IDs for that callsign). |
|
||||||
|
| **`GET /api/auth/login-by-ip`** | Optional: single user match for **`Clients.host`** = client IP (same session shape). |
|
||||||
|
| **`POST /api/auth/logout`** | Clears session. |
|
||||||
|
| **`GET /api/auth/me`** | Returns `{ callsign, int_ids, selected_int_id }` for the React app. |
|
||||||
|
|
||||||
|
Session lifetime is extended on activity (**SelfServiceController** uses a long inactivity timeout).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Device API (after login)
|
||||||
|
|
||||||
|
| Method | Path | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| **GET** | `/api/self-service/device?int_id=` | Load **`Clients`** row for that **`int_id`** (must be in session **`int_ids`**). Returns JSON: **`int_id`**, **`callsign`**, **`mode`**, **`options`** parsed into TS1/TS2 lists, DIAL, VOICE, LANG, SINGLE, TIMER. |
|
||||||
|
| **POST** | `/api/self-service/device/options` | Body: **`int_id`**, **`options`** string (Homebrew **OPTIONS** line). Must end with **`;`**. Max length **4096**. Updates DB: **`Clients.options`**, sets **`modified = 1`**. |
|
||||||
|
| **GET** | `/api/self-service/device/modified?int_id=` | Returns **`{ modified: 0|1 }`** from **`Clients.modified`** (UI can poll until the proxy clears it). |
|
||||||
|
| **POST** | `/api/self-service/device/select` | Body: **`int_id`** — set session **`selected_int_id`** when the user has multiple devices. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## End-to-end flow (why options reach the hotspot)
|
||||||
|
|
||||||
|
1. User saves options in the web UI → **PHP** writes **`Clients.options`** and **`modified = 1`**.
|
||||||
|
2. The **hotspot proxy** runs **`send_opts`** on a loop (~every **10 s**). For rows with **`modified = 1`**, it reads options from the DB, sends **RPTO** (options) **to the peer server** at **`(MASTER, assigned_dest_port)`**, then clears **`modified`** in the DB.
|
||||||
|
3. The **ADN DMR Peer Server** receives **RPTO** on the MASTER leg and updates its **OPTIONS** / bridge state (same path as a normal hotspot registration refresh).
|
||||||
|
4. The server pushes the appropriate signalling to the **hotspot** so static TG / reflector / timer settings take effect **without** a full hotspot restart (exact behaviour matches the HBP **OPTIONS** flow in the server).
|
||||||
|
|
||||||
|
Important: the proxy sends **RPTO to the master only**, not to the hotspot directly. If the proxy is not in the path, you must ensure another mechanism applies **OPTIONS** or you run the server without this proxy path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Password hashing
|
||||||
|
|
||||||
|
**`AuthenticateUser`** uses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
hash_pbkdf2('sha256', password, PBKDF2_SALT, PBKDF2_ITERATIONS)
|
||||||
|
```
|
||||||
|
|
||||||
|
stored as hex in **`Clients.psswd`**. The same parameters must be used wherever passwords are **registered** (e.g. **hotspot_proxy_self_service.py** in the **adn-dmr-server** / tooling repo).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UI
|
||||||
|
|
||||||
|
- Route **`/self-service`** in the React app (`SelfService.tsx`): loads **`/api/auth/me`**, then device details for **`selected_int_id`**, edit options string, save.
|
||||||
|
- External **SelfCare** link (e.g. `selfcare.adn.systems`) may appear in the nav as a separate product — not the same as this **local** self-service.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Documentation home](../README.md)
|
||||||
|
- [Configuration](configuration.md) — `SELF_SERVICE`, `DASHBOARD`, `PROXY`
|
||||||
|
- [Architecture](architecture.md) — proxy + report path
|
||||||
|
- Hotspot proxy details: **adn-monitor** `proxy/README.md` (RPTO timing table)
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
# Translations
|
||||||
|
|
||||||
|
## Current layout
|
||||||
|
|
||||||
|
**MkDocs** pages exist in parallel trees:
|
||||||
|
|
||||||
|
| Locale | Path | Build |
|
||||||
|
|--------|------|-------|
|
||||||
|
| English | **`docs/en/`** | `mkdocs.yml` → **`site/en/`** |
|
||||||
|
| Spanish | **`docs/es/`** | **`mkdocs.es.yml`** → **`site/es/`** |
|
||||||
|
|
||||||
|
- **`docs/en/server/`** — ADN DMR Peer Server (user guide, protocols, development, contributing).
|
||||||
|
- **`docs/en/monitor/`** — ADN Monitor (dashboard, `adn-mon.yaml`, self-service).
|
||||||
|
|
||||||
|
Spanish mirrors the same relative paths under **`docs/es/`**.
|
||||||
|
|
||||||
|
`mkdocs.yml` sets **`docs_dir: docs/en`** and **`theme.language: en`**. **`mkdocs.es.yml`** sets **`docs_dir: docs/es`** and **`theme.language: es`**.
|
||||||
|
|
||||||
|
Build **both** (`mkdocs build` and `mkdocs build -f mkdocs.es.yml`). The outputs land in **`site/en/`** and **`site/es/`**. Publish the combined **`site/`** directory as your HTTP server layout requires.
|
||||||
|
|
||||||
|
For a quick local check: `cd site && python -m http.server` then open **`/en/`** and **`/es/`**. Optional: use **`mkdocs-static-i18n`** later for a single build with page-level language pairs.
|
||||||
|
|
||||||
|
## Adding or updating a locale
|
||||||
|
|
||||||
|
1. Keep **navigation structure** aligned across locales (same relative paths: `server/user-guide/introduction.md`, etc.).
|
||||||
|
2. Headings that are cross-linked use **`attr_list`** explicit anchors `{#id}` where slugs must stay stable — see Spanish pages for examples.
|
||||||
|
3. When adding a third locale, add another MkDocs config and output directory following the same pattern.
|
||||||
|
|
||||||
|
## Writing for translators
|
||||||
|
|
||||||
|
- Use **short, clear sentences**.
|
||||||
|
- Avoid idioms and culture-specific jokes.
|
||||||
|
- Keep **terminology** consistent (OpenBridge, BCSQ, TG, `BRIDGES`).
|
||||||
|
- Put **code identifiers** and **YAML keys** in backticks.
|
||||||
|
|
||||||
|
## Not translated by default
|
||||||
|
|
||||||
|
- The **repository root** `README.md` may stay English-only or link to the published docs site.
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
# Architecture (clean layering)
|
||||||
|
|
||||||
|
## Layers
|
||||||
|
|
||||||
|
1. **Domain** (`src/adn_server/domain/`) — entities, value objects, errors, `Result`. No I/O, no Twisted.
|
||||||
|
2. **Application** (`src/adn_server/application/`) — use cases (`BridgeUseCases`, `VoiceUseCases`, …) and **ports** (interfaces).
|
||||||
|
3. **Infrastructure** (`src/adn_server/infrastructure/`) — YAML config, Twisted UDP/TCP, voice, persistence, security adapters.
|
||||||
|
|
||||||
|
**Dependency rule:** infrastructure → application → domain (inward only).
|
||||||
|
|
||||||
|
## Entrypoint
|
||||||
|
|
||||||
|
`main.py` wires configuration, **LoopingCall** timers, factories for **HBPProtocol**, report client, and injects use cases.
|
||||||
|
|
||||||
|
## Where to read code
|
||||||
|
|
||||||
|
| Topic | Location |
|
||||||
|
|-------|----------|
|
||||||
|
| Bridge routing, `dmrd_received`, OpenBridge loop | `application/bridge_use_cases.py` |
|
||||||
|
| HBP / OpenBridge UDP | `infrastructure/twisted_adapters/udp_hbp.py` |
|
||||||
|
| Report TCP | `infrastructure/twisted_adapters/report_server.py` (factory), bridge events from use cases |
|
||||||
|
| Voice / TTS | `application/voice_use_cases.py`, `infrastructure/voice/` |
|
||||||
|
|
||||||
|
## Configuration as shared state
|
||||||
|
|
||||||
|
A **mutable `config` dict** is passed through adapters; runtime updates (options, `SUB_MAP`, OpenBridge `_bcsq`) remain visible globally for the lifetime of the process.
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
# Behaviour and timers
|
||||||
|
|
||||||
|
## Stable control loops
|
||||||
|
|
||||||
|
The server uses **Twisted** `LoopingCall` tasks for periodic work: bridge rules, stream trimming, OpenBridge options refresh, alias reload, voice config reload, security downloads, reporting, and maintenance pings.
|
||||||
|
|
||||||
|
Intervals are part of the **observable behaviour** of the product (operators and integrators may rely on timing for troubleshooting). Avoid adding **extra** refreshes or duplicate work inside **hot paths** (for example per-packet handlers for OpenBridge) when the same concern is already covered by the scheduled loop—this keeps load predictable and avoids double application of rules.
|
||||||
|
|
||||||
|
## Configuration visibility
|
||||||
|
|
||||||
|
Runtime state lives in a shared **`config`** dict: options from peers, `SUB_MAP`, OpenBridge control-plane fields (`_bcsq`, `_bcka`), and similar. Adapters update this structure; use cases read it. This matches how the running process is inspected in logs and support scenarios.
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
# DMRE v5 frame layout
|
||||||
|
|
||||||
|
This page describes the **OpenBridge DMRE** wire format when the **embedded protocol version** is **5** (often called **“OpenBridge v5”** in operator docs — same as **DMRE v5**; see [OpenBridge](openbridge.md#dmre-and-openbridge-v5)).
|
||||||
|
|
||||||
|
Extended **OpenBridge** datagrams use the **`DMRE`** opcode (bytes `D`,`M`,`R`,`E`) and a **version** field. When the embedded version byte is **> 4**, the packet is **89 bytes** with **hops** at byte **72** and **BLAKE2b** from **73** to **89**.
|
||||||
|
|
||||||
|
## Short form (85 bytes)
|
||||||
|
|
||||||
|
When the embedded version is **≤ 4**, **source repeater** is omitted; **hops** and **MAC** shift (see implementation in `udp_hbp.py`).
|
||||||
|
|
||||||
|
## Field summary (89-byte v5)
|
||||||
|
|
||||||
|
| Region | Content |
|
||||||
|
|--------|---------|
|
||||||
|
| 0:4 | Opcode `DMRE` |
|
||||||
|
| 4:5 | Sequence |
|
||||||
|
| 5:8 | RF source |
|
||||||
|
| 8:11 | Destination ID |
|
||||||
|
| 11:15 | Server ID |
|
||||||
|
| 15:16 | Bits (slot, call type, frame type, dtype/vseq) |
|
||||||
|
| 16:20 | Stream ID |
|
||||||
|
| 20:53 | Voice payload |
|
||||||
|
| 53:55 | BER, RSSI |
|
||||||
|
| 55:56 | Embedded protocol version |
|
||||||
|
| 56:64 | Timestamp (ns, big-endian) |
|
||||||
|
| 64:68 | Source server ID |
|
||||||
|
| 68:72 | Source repeater (v5 extended) |
|
||||||
|
| 72:73 | Hops |
|
||||||
|
| 73:89 | BLAKE2b MAC (16 bytes) |
|
||||||
|
|
||||||
|
**Integrity:** BLAKE2b-128 with the **passphrase** as key; MAC covers bytes **before** the MAC field.
|
||||||
|
|
||||||
|
The byte layout in code (`infrastructure/twisted_adapters/udp_hbp.py`) is authoritative and may gain minor clarifications over time.
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
# HBP (HomeBrew Protocol) — DMRD
|
||||||
|
|
||||||
|
## Role
|
||||||
|
|
||||||
|
**HBP** is the UDP framing used between this server and **MASTER** / **PEER** systems. Payloads use the **`DMRD`** opcode (four ASCII bytes) followed by DMR voice/data fields (RF source, destination, stream ID, AMBE block, etc.).
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
- **MASTER** side: RPTL → salt → RPTK → config; peer options (**RPTO**) refresh bridge options.
|
||||||
|
- **PEER** side: connects upstream, repeats authentication, maintenance pings.
|
||||||
|
|
||||||
|
Implementation: `infrastructure/twisted_adapters/udp_hbp.py` (`HBPProtocol`).
|
||||||
|
|
||||||
|
## OpenBridge vs HBP
|
||||||
|
|
||||||
|
OpenBridge uses **`DMRD`** v1 (HMAC-SHA1) or **`DMRE`** (extended); see [OpenBridge](openbridge.md). HBP **MASTER/PEER** links use classic **DMRD** rules.
|
||||||
|
|
||||||
|
## BER / RSSI
|
||||||
|
|
||||||
|
For non-OpenBridge sources, optional **BER/RSSI** bytes may be appended after the 53-byte voice payload in ingress; forwarding to OpenBridge may strip or preserve fields depending on destination and `to_target` rules.
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Credits, license, and project history
|
||||||
|
|
||||||
|
## Credits and lineage
|
||||||
|
|
||||||
|
**ADN Systems DMR Peer Server** is derived from **FreeDMR**, reorganised with **clean architecture** (domain, application, infrastructure) and ADN-specific features: scheduled announcements, **TTS**, on-demand voice, **`adn-voice.yaml`** merge, TCP reporting to **ADN Monitor**, integration with **`adn-mon.yaml`**, self-service, hotspot proxy, and more.
|
||||||
|
|
||||||
|
**[FreeDMR Peer Server](https://gitlab.hacknix.net/hacknix/FreeDMR)** is by **Simon Adlem, G7RZU** ([hacknix](https://gitlab.hacknix.net/hacknix/FreeDMR)).
|
||||||
|
|
||||||
|
**[hblink3](https://github.com/HBLink-org/hblink3)** — HBLink for Python3. Copyright (C) 2016-2020 Cortney T. Buffington, N0MJS n0mjs@me.com.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is distributed under the **GNU General Public License v3** (or later), consistent with FreeDMR and related stacks. The full legal text is in the repository (`LICENSE`, and `COPYING` where present).
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Introduction](introduction.md)
|
||||||
|
- [Architecture](../development/architecture.md)
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
# Bridges and talkgroups
|
||||||
|
|
||||||
|
## `BRIDGES` model
|
||||||
|
|
||||||
|
The bridge table maps **talkgroup keys** (strings, e.g. `"26811"`, `"#reflector"`) to **rows**. Each row describes:
|
||||||
|
|
||||||
|
- **`SYSTEM`** — which configured system originates or receives this leg.
|
||||||
|
- **`TS`** — timeslot (1 or 2). OpenBridge sources are normalised to **TS1** in routing (`bridge_match_slot`).
|
||||||
|
- **`TGID`** — destination ID bytes for LC rewrite toward that leg.
|
||||||
|
- **`ACTIVE`** — whether this leg participates.
|
||||||
|
- **`TIMEOUT`**, **`TO_TYPE`**, **`ON`/`OFF`/`RESET`** — activation semantics (user-activated bridges, reflectors, etc.).
|
||||||
|
|
||||||
|
The router scans `BRIDGES` for an **ACTIVE** row matching the **current source system**, **slot**, and **destination TG** before forwarding (`dmrd_received` → `to_target`).
|
||||||
|
|
||||||
|
## Dynamic vs static
|
||||||
|
|
||||||
|
- **User-activated** bridges are created when a user keys a TG without a pre-built row (subject to `DEFAULT_UA_TIMER` and options).
|
||||||
|
- **Static** TGs and **STAT** bridges are created from **OPTIONS** / `make_static_tg` / `GEN_STAT_BRIDGES` flows.
|
||||||
|
|
||||||
|
## OpenBridge and TG display
|
||||||
|
|
||||||
|
For OpenBridge, the **DMR destination** in the packet may differ from the **TGID** in a bridge row (remap). Monitoring may show **RX TG** (as received) vs **TX TG** (as rewritten for a destination); correlate by **`stream_id`**, not TG alone.
|
||||||
|
|
||||||
|
## Contention
|
||||||
|
|
||||||
|
Group voice uses **hang time**, **`STREAM_TO`**, and slot `TX_*` / `RX_*` state to avoid colliding transmissions on the same resources.
|
||||||
|
|
||||||
|
See also: [Special numbers](special-numbers.md), [OpenBridge protocol](../protocols/openbridge.md).
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
# Parrot (playback)
|
||||||
|
|
||||||
|
## What it is
|
||||||
|
|
||||||
|
**Parrot** is a **separate entrypoint** (`adn-parrot.py` / `parrot_main`) that records incoming **group** voice and plays it back (echo / parrot), independent of the main bridge process.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- Copy **`adn-parrot.example.yaml`** → **`adn-parrot.yaml`** (not committed).
|
||||||
|
- Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python adn-parrot.py -c adn-parrot.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relation to TG 9990 / ECHO
|
||||||
|
|
||||||
|
The main server may expose an **ECHO** bridge on **TG 9990** for in-band echo. **Parrot** is a **standalone** service with its own config — use one or the other according to your deployment.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
This page is the summary shipped with the repository; extend your deployment notes locally as needed.
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
# Private calls
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
**Unit (private)** calls use a different path than **group** voice. The router uses **`SUB_MAP`** (subscriber → last known system/slot/time) and collision rules to decide whether and where to forward.
|
||||||
|
|
||||||
|
## SUB_MAP
|
||||||
|
|
||||||
|
- Populated when stations register traffic; persisted via configured **`SUB_MAP`** pickle path under **`ALIASES`**.
|
||||||
|
- Used to resolve **destination radio ID** to a **target system** and **slot** for private forwarding.
|
||||||
|
|
||||||
|
## OpenBridge vs MASTER
|
||||||
|
|
||||||
|
Private handling uses CSBK/data/unit branches, `SUB_MAP` lookup, and busy-slot checks where applicable (see `BridgeUseCases` in source).
|
||||||
|
|
||||||
|
## TG / ID 4000 (unit)
|
||||||
|
|
||||||
|
As documented in [Special numbers](special-numbers.md), a **private** call to **4000** disconnects dynamics and is **not** treated as a normal private call route.
|
||||||
|
|
||||||
|
## Reporting
|
||||||
|
|
||||||
|
Private **START/END** events may be emitted to the report TCP client when **`REPORTS.REPORT`** is enabled, analogous to group voice (shape `PRIVATE VOICE,...` where implemented).
|
||||||
|
|
||||||
|
For protocol ingress details, see [HBP](../protocols/hbp.md) and the bridge use cases in source (`BridgeUseCases._pvt_call_received`).
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
# Documentación ADN Systems
|
||||||
|
|
||||||
|
Este sitio describe el **ADN DMR Peer Server** y el **ADN Monitor** como una pila operativa unida. El contenido se organiza por **producto** (`server/` frente a `monitor/`) en este **locale** español.
|
||||||
|
|
||||||
|
- **English:** mismo contenido bajo `docs/en/` — `mkdocs build -f mkdocs.yml` → `site/en/` (ver `docs/en/README.md` en el repositorio).
|
||||||
|
|
||||||
|
## ADN DMR Peer Server
|
||||||
|
|
||||||
|
El **ADN DMR Peer Server** es un puente de conferencia [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.html) para radio móvil digital (DMR). Está estructurado en capas de **arquitectura limpia** (dominio, aplicación, infraestructura).
|
||||||
|
|
||||||
|
### Qué hace el servidor
|
||||||
|
|
||||||
|
- Termina enlaces **HBP** (HomeBrew Protocol) hacia sistemas **MASTER** y **PEER** (hotspots, repetidores).
|
||||||
|
- Termina enlaces **OpenBridge** hacia otros servidores por UDP — **DMRE v5** (recomendado en ADN) o **DMRD** v1 heredado.
|
||||||
|
- Ejecuta **enrutado de bridges** (`BRIDGES`): voz de grupo, control de bucle, ACL, **BCSQ** / **BCKA** opcionales.
|
||||||
|
- Soporta **llamadas privadas** (`SUB_MAP`), **voz**, **TTS**, **grabación** e **informes TCP** al monitor.
|
||||||
|
|
||||||
|
### Por dónde empezar (servidor)
|
||||||
|
|
||||||
|
| Quiero… | Empieza aquí |
|
||||||
|
|---------|----------------|
|
||||||
|
| Ejecutar y configurar | [Introducción](server/user-guide/introduction.md), [Configuración](server/user-guide/configuration.md) |
|
||||||
|
| TG 4000, 999x, eco | [Números especiales](server/user-guide/special-numbers.md) |
|
||||||
|
| Llamadas privadas | [Llamadas privadas](server/user-guide/private-calls.md) |
|
||||||
|
| Voz / TTS | [Voz, anuncios y TTS](server/user-guide/voice-and-tts.md) |
|
||||||
|
| OpenBridge / DMRE | [OpenBridge](server/protocols/openbridge.md), [DMRE v5](server/protocols/dmre-v5.md) |
|
||||||
|
| HBP | [HBP](server/protocols/hbp.md) |
|
||||||
|
| Código | [Arquitectura](server/development/architecture.md), [Comportamiento y temporizadores](server/development/behaviour-and-timers.md) |
|
||||||
|
| Créditos, licencia, linaje | [Créditos y licencia](server/user-guide/attribution.md) |
|
||||||
|
|
||||||
|
### Inicio rápido (servidor)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
cp adn-server.example.yaml adn-server.yaml
|
||||||
|
python adn-server.py -c adn-server.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Más: [Introducción](server/user-guide/introduction.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADN Monitor
|
||||||
|
|
||||||
|
Panel, **WebSocket** en vivo, **API PHP** opcional, **MySQL** self-service y **proxy hotspot** — ver [Descripción general del monitor](monitor/index.md).
|
||||||
|
|
||||||
|
| Quiero… | Empieza aquí |
|
||||||
|
|---------|----------------|
|
||||||
|
| `adn-mon.yaml` y despliegue | [Configuración del monitor](monitor/configuration.md) |
|
||||||
|
| Proxy hotspot (UDP, `PROXY`, rango de puertos) | [Proxy hotspot](monitor/hotspot-proxy.md) |
|
||||||
|
| Self-service | [Self-service](monitor/self-service.md) |
|
||||||
|
| Cómo encaja con el servidor | [Monitor e informes](server/user-guide/monitoring.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Locales
|
||||||
|
|
||||||
|
- **Español** — **`docs/es/`** (este árbol).
|
||||||
|
- **English** — **`docs/en/`**.
|
||||||
|
|
||||||
|
Ver [Traducciones](server/contributing/translations.md).
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
# Arquitectura e implantación
|
||||||
|
|
||||||
|
## Arquitectura limpia (monitor Python)
|
||||||
|
|
||||||
|
Bajo `monitor/src/adn_monitor/`:
|
||||||
|
|
||||||
|
- **Dominio** — objetos de valor, errores, tipos de opcode.
|
||||||
|
- **Aplicación** — `MonitorState`, `process_message` en `monitor_controller.py`, servicio de alias, casos de uso Last Heard / conteo TG, formato de hora.
|
||||||
|
- **Infraestructura** — `load_config` YAML, cliente Twisted **TCP** (`ReportClientFactory`) al peer server, fábrica **WebSocket** para el panel, repositorios MySQL, decodificadores pickle/json para `CONFIG_SND` / `BRIDGE_SND`.
|
||||||
|
|
||||||
|
El monitor **sale hacia** **`ADN_CONNECTION.ADN_IP:ADN_PORT`** y recibe mensajes con prefijo de longitud (estilo netstring). Actualiza **CTABLE** (masters/peers/OpenBridge) y **BTABLE** (bridges) en memoria, y persiste resultados de **BRDG_EVENT** cuando MySQL está configurado.
|
||||||
|
|
||||||
|
## Protocolo de informes (desde el peer server)
|
||||||
|
|
||||||
|
Los mismos opcodes que en la documentación del servidor: **CONFIG_SND**, **BRIDGE_SND**, **BRDG_EVENT**, etc. El monitor los decodifica y aplica en `process_message` — ver [Monitor e informes](../server/user-guide/monitoring.md).
|
||||||
|
|
||||||
|
## WebSocket
|
||||||
|
|
||||||
|
`monitor.py` ejecuta un **WebSocket** Twisted en **`WEBSOCKET_SERVER.WEBSOCKET_PORT`**, enviando instantáneas JSON a **`FREQUENCY`** para que la app React actualice sin polling del estado principal.
|
||||||
|
|
||||||
|
## Backend PHP
|
||||||
|
|
||||||
|
- **Slim 4** front controller: `backend/public/index.php`.
|
||||||
|
- Carga **`adn-mon.yaml`** vía **`ADN_CONFIG_PATH`** (igual que el monitor).
|
||||||
|
- **`/api/config/dashboard`** — título, idioma, flags (`selfService`, `showConsole`, …) desde **`DASHBOARD`**.
|
||||||
|
- **`/api/auth/*`** — sesión por cookie cuando hay BD **SELF_SERVICE**.
|
||||||
|
- **`/api/self-service/*`** — opciones de dispositivo (ver [Self-service](self-service.md)).
|
||||||
|
- **`/api/aliases/*`** — proxy opcional a URLs de listas TG/bridge desde **ALIASES**.
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
- **Vite + React** bajo `frontend/`; el build genera estáticos servidos por nginx/Apache o similar.
|
||||||
|
- Usa **`API_BASE`** (build) para la API PHP y **URL del WebSocket** para datos en vivo.
|
||||||
|
|
||||||
|
## Proxy hotspot
|
||||||
|
|
||||||
|
- Entrada: `proxy/proxy.py`; paquete `src/adn_proxy/` (dominio / aplicación / infraestructura).
|
||||||
|
- Lee **`PROXY`** y **`SELF_SERVICE`** del mismo YAML.
|
||||||
|
- Por cada cliente hotspot, asigna un puerto en **`DESTPORT_START`…`DEST_PORT_END`** y reenvía UDP a **`MASTER`**.
|
||||||
|
- Cuando **self-service** actualiza **`Clients.options`** y pone **`modified=1`**, el proxy envía **RPTO** al **master** en un temporizador (~10 s). El **peer server** aplica entonces las opciones al camino del hotspot (ver [Self-service](self-service.md)).
|
||||||
|
|
||||||
|
**Por qué no forma parte del binario del peer server:** comparte **`adn-mon.yaml`**, self-service MySQL e implantación con la pila del monitor — ver [Por qué va con el monitor](hotspot-proxy.md#why-it-ships-with-the-monitor-not-inside-the-peer-server).
|
||||||
|
|
||||||
|
**Detalle:** [Proxy hotspot](hotspot-proxy.md) (claves de config, rango de puertos del peer, arranque).
|
||||||
|
|
||||||
|
## Topología típica de despliegue
|
||||||
|
|
||||||
|
```text
|
||||||
|
[Hotspots] --UDP--> [Proxy :LISTEN_PORT] --UDP--> [Peer server :rango DESTPORT]
|
||||||
|
|
|
||||||
|
v
|
||||||
|
MySQL (Clients)
|
||||||
|
|
||||||
|
[Peer server :REPORT_PORT] <--- TCP --- [monitor.py : cliente que conecta]
|
||||||
|
|
||||||
|
[Navegador] --HTTPS--> [API PHP + frontend estático]
|
||||||
|
[Navegador] --WS----> [WebSocket del monitor :9000]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ver también
|
||||||
|
|
||||||
|
- [Inicio de la documentación](../README.md)
|
||||||
|
- [Configuración](configuration.md)
|
||||||
|
- [Self-service](self-service.md)
|
||||||
@ -0,0 +1,120 @@
|
|||||||
|
# Configuración (`adn-mon.yaml`)
|
||||||
|
|
||||||
|
Todos los componentes leen el **mismo** YAML (ruta por defecto a menudo `monitor/adn-mon.yaml`; se puede sobrescribir con **`ADN_CONFIG_PATH`**). El ejemplo del repositorio **adn-monitor** es la plantilla autoritativa; las claves siguientes coinciden con `monitor/adn-mon.yaml` y `monitor/src/adn_monitor/infrastructure/config_loader.py` (los nombres internos pueden diferir).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `GLOBAL`
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **BRIDGES_INC** | Mostrar estado de bridges en el panel si es `true`. |
|
||||||
|
| **HOMEBREW_INC** | Incluir estado HBP peer/master. |
|
||||||
|
| **LASTHEARD_INC** | Activar funciones / tablas Last Heard. |
|
||||||
|
| **LASTHEARD_ROWS** | Filas para widgets Last Heard. |
|
||||||
|
| **EMPTY_MASTERS** | Mostrar masters sin peers. |
|
||||||
|
| **TGCOUNT_INC** | Activar página / estadísticas de conteo de TG. |
|
||||||
|
| **TGCOUNT_ROWS** | Filas para el conteo de TG. |
|
||||||
|
| **TIMEZONE** | Zona horaria IANA (p. ej. `America/Santiago`) para mostrar; vacío usa la hora local del servidor. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `ADN_CONNECTION` {#adn_connection}
|
||||||
|
|
||||||
|
Debe coincidir con la configuración de informes del **ADN DMR Peer Server**.
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **ADN_IP** | Host/IP donde está el **listener TCP de informes** del peer server (vista de red desde el monitor). |
|
||||||
|
| **ADN_PORT** | Puerto TCP — debe ser igual a **`REPORTS.REPORT_PORT`** en el servidor y ser alcanzable. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `SELF_SERVICE`
|
||||||
|
|
||||||
|
Credenciales MySQL y parámetros PBKDF2 para **login** y acceso a la tabla **`Clients`**. **PBKDF2_SALT** y **PBKDF2_ITERATIONS** deben coincidir con **`hotspot_proxy_self_service.py`** (o tu herramienta de registro de contraseñas) para que los hashes verifiquen en PHP y Python.
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **USE_SELFSERVICE** | Lo usa el cargador de config del **proxy** para rutas con BD / self-service (ver README del proxy). |
|
||||||
|
| **DB_SERVER**, **DB_USERNAME**, **DB_PASSWORD**, **DB_NAME**, **DB_PORT** | Conexión MySQL para **`Clients`** (y tablas relacionadas). |
|
||||||
|
|
||||||
|
Si el backend PHP no puede conectar, las rutas de **auth** y **self-service** no se registran (ver `backend/public/index.php`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `PROXY`
|
||||||
|
|
||||||
|
**Proxy UDP hotspot** — guía completa: [Proxy hotspot](hotspot-proxy.md). Resumen: reenvía cada cliente a **`MASTER:DESTPORT_START`…`DEST_PORT_END`**; el peer server debe **escuchar** en esa IP y rango de puertos (ver también [Arquitectura](architecture.md)).
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **MASTER** | Host del peer server (IP o DNS; resuelto al arrancar el proxy). |
|
||||||
|
| **LISTEN_PORT** / **LISTEN_IP** | Dónde el proxy acepta UDP del hotspot (IP vacía suele significar todas las interfaces). |
|
||||||
|
| **DESTPORT_START** / **DEST_PORT_END** | Un puerto por cliente proxy hacia **`MASTER`**. |
|
||||||
|
| **TIMEOUT**, **STATS**, **DEBUG**, **CLIENT_INFO** | Comportamiento y registro. |
|
||||||
|
| **BLACK_LIST** / **IP_BLACK_LIST** | Listas de bloqueo opcionales. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `OPB_FILTER`
|
||||||
|
|
||||||
|
**Network IDs** separados por comas (como cadenas). El tráfico desde esas fuentes OpenBridge puede **ocultarse** de ciertas rutas de persistencia del panel (ver manejo de `OPB_FILTER` en el controlador del monitor).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `ALIASES`
|
||||||
|
|
||||||
|
Misma idea que en el peer server: descargar JSON de **peer / subscriber / TGID** y checksums opcionales. Claves: **PATH**, **\*_FILE**, **\*_URL**, **STALE_HOURS**, **REVIEW_INTERVAL_MINUTES**, **CHECKSUM_***, **TG_LIST_URL**, **BRIDGE_LIST_URL** (proxy del backend para páginas del frontend).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `LOGGER`
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **LOG_PATH** | Directorio de ficheros de log. |
|
||||||
|
| **LOG_FILE** | Nombre del log del monitor (p. ej. `adn-mon.log`). |
|
||||||
|
| **PROXY_LOG_FILE** | Nombre de log separado para el proxy (cuando se ejecuta con logging del proxy). |
|
||||||
|
| **LOG_LEVEL** | p. ej. `INFO`, `DEBUG`. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `WEBSOCKET_SERVER`
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **WEBSOCKET_PORT** | Puerto del WebSocket Twisted que envía estado JSON a los navegadores. |
|
||||||
|
| **FREQUENCY** | Intervalo de envío (segundos). |
|
||||||
|
| **CLIENT_TIMEOUT** | Cerrar clientes WS inactivos tras N segundos (`0` = desactivado). |
|
||||||
|
| **USE_SSL**, **SSL_PATH**, **SSL_CERTIFICATE**, **SSL_PRIVATEKEY** | WSS opcional. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `DASHBOARD`
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **DASHTITLE** | Título de cabecera. |
|
||||||
|
| **BACKGROUND** | Usar fondo `bk.jpg` si es `true`. |
|
||||||
|
| **LANGUAGE** | Idioma por defecto de la UI (`en`, `es`, …). |
|
||||||
|
| **SELF_SERVICE** | Si es `true`, la UI puede mostrar la entrada **Self-service** (el backend debe exponer API + BD). |
|
||||||
|
| **SHOW_CONSOLE** | Mostrar página consola (mensajes inicio/fin de llamada). |
|
||||||
|
| **MIN_DURATION** | Duración mínima de llamada (segundos) para la tabla Last Heard del **panel** (la página Last Heard puede seguir mostrando más cortas). |
|
||||||
|
| **nav_links**, **footer**, **news** | Enlaces estructurados / marquee opcionales. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Entorno
|
||||||
|
|
||||||
|
- **`ADN_CONFIG_PATH`**: ruta absoluta a `adn-mon.yaml` para monitor, backend y proxy.
|
||||||
|
- El backend puede usar **`API_BASE_PATH`** si la API va bajo un prefijo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ver también
|
||||||
|
|
||||||
|
- [Inicio de la documentación](../README.md)
|
||||||
|
- [Arquitectura](architecture.md)
|
||||||
|
- [Self-service](self-service.md)
|
||||||
|
- **`REPORTS`** en el peer server: [Monitor e informes](../server/user-guide/monitoring.md), [Configuración del servidor](../server/user-guide/configuration.md) (sección **`REPORTS`**).
|
||||||
@ -0,0 +1,114 @@
|
|||||||
|
# Proxy hotspot
|
||||||
|
|
||||||
|
El **proxy hotspot** forma parte del repositorio **adn-monitor**. Es un **relé UDP** entre **hotspots DMR** (Homebrew / HBP) y el **MASTER** del **ADN DMR Peer Server**: cada hotspot conectado se asigna a un **puerto de destino** dedicado en el host del peer server, de modo que muchos hotspots puedan compartir una IP pública sin choques de puertos.
|
||||||
|
|
||||||
|
Estructura: `proxy/proxy.py`, paquete `proxy/src/adn_proxy/` (arquitectura limpia). **GPL v3** (derivado del proxy original de Simon Adlem, G7RZU).
|
||||||
|
|
||||||
|
### Por qué va con el monitor (y no dentro del peer server) {#why-it-ships-with-the-monitor-not-inside-the-peer-server}
|
||||||
|
|
||||||
|
No hay un despliegue obligatorio único, pero **hoy el proxy vive en el repo adn-monitor** a propósito:
|
||||||
|
|
||||||
|
- **Misma config y operativa** que el panel: **`adn-mon.yaml`**, **`ADN_CONFIG_PATH`**, y normalmente el mismo host que **PHP** y **MySQL**.
|
||||||
|
- El **self-service** (**`Clients`**, RPTO, **`modified`**) está montado sobre ese ecosistema; el binario del peer server no posee esa base de datos ni el bloque **`PROXY`**.
|
||||||
|
- **División de roles:** el **ADN DMR Peer Server** es el **núcleo de radio** (HBP/OpenBridge, bridges, voz, informes TCP). El proxy hotspot es un **frente UDP opcional** hacia un MASTER que ya escucha en un **rango** de puertos — útil cuando muchos hotspots comparten una dirección pública.
|
||||||
|
|
||||||
|
**Integrar el proxy en el peer server** (un binario, un `adn-server.yaml`) es imaginable para empaquetado, pero implica **unificar configuración**, **replantear el cableado de self-service** y más mantenimiento — solo compensa si quieres explícitamente un servidor “todo en uno” desplegable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fichero de configuración (igual que el monitor)
|
||||||
|
|
||||||
|
El proxy **no** usa `adn-server.yaml`. Lee el YAML del **monitor**:
|
||||||
|
|
||||||
|
| Origen | Uso |
|
||||||
|
|--------|-----|
|
||||||
|
| **`ADN_CONFIG_PATH`** | Variable de entorno: ruta absoluta a **`adn-mon.yaml`** (compartida con **monitor**, **backend PHP**, **`.env`** opcional en la raíz del repo). |
|
||||||
|
| **`python proxy/proxy.py --config /ruta/a/adn-mon.yaml`** | Sobrescribe la ruta solo para este proceso. |
|
||||||
|
| **Por defecto** (sin definir) | `../monitor/adn-mon.yaml` relativo al directorio `proxy/` al ejecutar desde el árbol adn-monitor. |
|
||||||
|
|
||||||
|
Secciones usadas:
|
||||||
|
|
||||||
|
- **`PROXY`** — dirección de escucha, host master, **rango** de puertos de destino, timeouts, debug, listas negras.
|
||||||
|
- **`SELF_SERVICE`** — MySQL y **`USE_SELFSERVICE`** (tabla **`Clients`**, RPTO / opciones).
|
||||||
|
- **`LOGGER`** — **`LOG_PATH`** y **`PROXY_LOG_FILE`** (log del proxy separado de **`LOG_FILE`** de `monitor.py`).
|
||||||
|
|
||||||
|
**Entorno** opcional (ver `proxy/README.md` en el repo): p. ej. **`ADN_PROXY_DEBUG`**, **`ADN_PROXY_LISTENPORT`**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Claves `PROXY` (`adn-mon.yaml`)
|
||||||
|
|
||||||
|
| Clave | Rol |
|
||||||
|
|-------|-----|
|
||||||
|
| **MASTER** | IP o **nombre de host** del **ADN DMR Peer Server**. Se resuelve a IPv4 al arrancar (Twisted necesita IP para `write()`). |
|
||||||
|
| **LISTEN_PORT** | Puerto UDP donde los **hotspots** se conectan **al proxy** (lo que configuran en el hotspot). |
|
||||||
|
| **LISTEN_IP** | Vacío suele significar todas las interfaces; si no, enlazar a esa dirección. |
|
||||||
|
| **DESTPORT_START** / **DEST_PORT_END** | Rango inclusivo de puertos UDP en **`MASTER`**, **uno por hotspot** proxy (asignación secuencial dentro del proxy). |
|
||||||
|
| **TIMEOUT** | Tiempo de inactividad / sesión (segundos). |
|
||||||
|
| **STATS** | Registro extra de estadísticas. |
|
||||||
|
| **DEBUG** | Log detallado de paquetes (o **`ADN_PROXY_DEBUG=1`**). |
|
||||||
|
| **CLIENT_INFO** | Información por cliente en logs. |
|
||||||
|
| **BLACK_LIST** / **IP_BLACK_LIST** | Bloquear IDs de radio o IPs de origen. |
|
||||||
|
|
||||||
|
Las claves internas tras la carga usan nombres mixtos (`Master`, `ListenPort`, …) — ver `adn_proxy.infrastructure.config_loader`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## El peer server (`adn-server.yaml`) debe cubrir el rango de puertos
|
||||||
|
|
||||||
|
El proxy reenvía tráfico a **`MASTER:DESTPORT`** por cliente, con **DESTPORT** en **[DESTPORT_START, DEST_PORT_END]**.
|
||||||
|
|
||||||
|
El **ADN DMR Peer Server** debe **escuchar UDP** en **ese host** en **cada puerto** del rango que vayas a usar (un listener **MASTER** por puerto, o equivalente).
|
||||||
|
|
||||||
|
- Un único `MODE: MASTER` con un solo **`PORT`** **no** basta para varios clientes proxy si usan **DESTPORT** distintos — hacen falta **varios listeners** en el rango.
|
||||||
|
- Enfoques típicos: **`GENERATOR`** en un sistema MASTER (se parte en `NAME-0`, `NAME-1`, … con **PORT** consecutivos — ver [Configuración del servidor](../server/user-guide/configuration.md)), y/o varias entradas **`SYSTEMS`**, alineadas con **`DESTPORT_START`…`DEST_PORT_END`** en **`PROXY`**.
|
||||||
|
|
||||||
|
Si el servidor solo escucha p. ej. en **56400** pero el proxy envía a **56401**, ese cliente no se registrará.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cómo arranca el proceso
|
||||||
|
|
||||||
|
1. Resolver ruta de config (`ADN_CONFIG_PATH`, `--config`, o por defecto).
|
||||||
|
2. **`load_config()`** parsea YAML → **`PROXY`**, **`SELF_SERVICE`**, **`LOG`**.
|
||||||
|
3. Pool **MySQL** opcional si self-service / funciones de BD están activas.
|
||||||
|
4. El **reactor** Twisted ejecuta UDP **ProxyProtocol** en **`LISTEN_IP:LISTEN_PORT`**, reenviando a **`MASTER:puerto_destino_asignado`**.
|
||||||
|
|
||||||
|
Ejecución (desde la raíz de adn-monitor, con entorno):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export ADN_CONFIG_PATH=/opt/adn-monitor/monitor/adn-mon.yaml
|
||||||
|
python proxy/proxy.py
|
||||||
|
# o
|
||||||
|
python proxy/proxy.py --config /ruta/a/adn-mon.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Usar **systemd** u otro supervisor junto a **`monitor.py`** y la pila **PHP**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RPTO, opciones y self-service
|
||||||
|
|
||||||
|
El proxy **nunca** envía **RPTO** directamente al hotspot para actualizaciones self-service. Envía **RPTO al MASTER** (peer server); el servidor actualiza bridges/opciones y aplica el flujo HBP normal.
|
||||||
|
|
||||||
|
| Evento | Comportamiento del proxy |
|
||||||
|
|--------|---------------------------|
|
||||||
|
| ~**10 s** tras login del hotspot (**RPTC**) | Leer **`Clients.options`** de la BD → **RPTO** → master `(MASTER, dport)`. |
|
||||||
|
| Cada ~**10 s** | Filas con **`modified = 1`** → **RPTO** → master, luego limpiar **`modified`**. |
|
||||||
|
| El hotspot envía **RPTO** | Reenvío al master; actualizaciones de BD según implementación. |
|
||||||
|
|
||||||
|
Detalle: [Self-service](self-service.md) y **`proxy/README.md`** en adn-monitor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Visibilidad en el monitor
|
||||||
|
|
||||||
|
Los hotspots aparecen en el panel solo si el **peer server** envía **informes TCP** al mismo host/puerto que **`ADN_CONNECTION`** en **`adn-mon.yaml`**. Alinea **`REPORTS`** en el servidor con **`ADN_IP` / `ADN_PORT`**. Ver [Monitor e informes](../server/user-guide/monitoring.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ver también
|
||||||
|
|
||||||
|
- [Configuración del monitor](configuration.md) — referencia **`adn-mon.yaml`** (resumen PROXY).
|
||||||
|
- [Arquitectura](architecture.md) — dónde encaja el proxy en la pila.
|
||||||
|
- [Self-service](self-service.md) — BD, **`modified`**, temporización RPTO.
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
# ADN Monitor (descripción general)
|
||||||
|
|
||||||
|
**ADN Monitor** es un proyecto distinto del **ADN DMR Peer Server**, pero ambos suelen desplegarse **juntos**: el servidor envía **informes TCP** (config, bridges, eventos de llamada) al monitor; el monitor alimenta el **panel web** (React) y las actualizaciones **WebSocket**. Los componentes opcionales incluyen la **API PHP** (Slim), **MySQL** (self-service / registro de dispositivos) y el **proxy hotspot** (UDP entre hotspots y el peer server).
|
||||||
|
|
||||||
|
Este capítulo documenta la pila **adn-monitor** con el mismo nivel de detalle que las guías del servidor. El código fuente está en el repositorio **adn-monitor**, no en el repositorio **adn-server** (donde se mantiene esta documentación).
|
||||||
|
|
||||||
|
## Qué hace cada parte
|
||||||
|
|
||||||
|
| Parte | Rol |
|
||||||
|
|-------|-----|
|
||||||
|
| **`monitor/monitor.py`** | Python (Twisted): se conecta al **puerto TCP de informes** del peer server, decodifica cargas netstring (`CONFIG_SND`, `BRIDGE_SND`, `BRDG_EVENT`), mantiene **CTABLE** / **BTABLE**, escribe **Last Heard** / estadísticas de TG en **MySQL** si está configurado, sirve JSON **WebSocket** al panel. |
|
||||||
|
| **`backend/`** | PHP **Slim**: `/api/config/dashboard`, auth, APIs **self-service** opcionales, proxy de alias. Lee el **mismo** `adn-mon.yaml` vía **`ADN_CONFIG_PATH`**. |
|
||||||
|
| **`frontend/`** | React (Vite): UI del panel; consume API del backend + WebSocket. |
|
||||||
|
| **`proxy/`** | Python (Twisted): **proxy hotspot** UDP; reenvía Homebrew entre hotspots y el peer server; lee **`Clients`** en MySQL para opciones **RPTO** (self-service). |
|
||||||
|
|
||||||
|
## Un solo fichero de configuración
|
||||||
|
|
||||||
|
**`adn-mon.yaml`** (ruta a menudo con **`ADN_CONFIG_PATH`** en `.env`) lo comparten:
|
||||||
|
|
||||||
|
- Monitor Python (`monitor.py`)
|
||||||
|
- Backend PHP (`backend/public/index.php`)
|
||||||
|
- Proxy hotspot (`proxy/proxy.py`)
|
||||||
|
|
||||||
|
Un **único** YAML define direcciones de informes, textos del panel, puerto WebSocket, credenciales **SELF_SERVICE** de la BD y ajustes **PROXY** de escucha/rango.
|
||||||
|
|
||||||
|
## Enlace con el peer server
|
||||||
|
|
||||||
|
| Servidor (`adn-server.yaml`) | Monitor (`adn-mon.yaml`) |
|
||||||
|
|------------------------------|---------------------------|
|
||||||
|
| **`REPORTS.REPORT_CLIENTS`** — lista de IPs permitidas para conectar **al** listener de informes, o el host del monitor | **`ADN_CONNECTION.ADN_IP`** / **`ADN_PORT`** — a dónde **se conecta** el monitor (debe coincidir con el bind y puerto de informes del servidor). |
|
||||||
|
| **`REPORTS.REPORT_PORT`** — puerto TCP en el que el **servidor escucha** conexiones de informes | El mismo puerto que **`ADN_PORT`**. |
|
||||||
|
|
||||||
|
Ver [Monitor e informes](../server/user-guide/monitoring.md) para los opcodes de informes y [Configuración del monitor](configuration.md) para cada sección de `adn-mon.yaml`.
|
||||||
|
|
||||||
|
## Ver también
|
||||||
|
|
||||||
|
- [Proxy hotspot](hotspot-proxy.md) — `PROXY`, rango de puertos del peer, carga de config y arranque
|
||||||
|
- [Arquitectura e implantación](architecture.md)
|
||||||
|
- [Configuración (`adn-mon.yaml`)](configuration.md)
|
||||||
|
- [Self-service](self-service.md)
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
# Self-service
|
||||||
|
|
||||||
|
**Self-service** permite al dueño de un hotspot **iniciar sesión** en el panel, **editar las opciones de su dispositivo** (listas estáticas de TG, reflector por defecto, temporizador, idioma, etc.) y que esas opciones se **envíen al ADN DMR Peer Server** sin editar YAML a mano en el servidor.
|
||||||
|
|
||||||
|
Intervienen **cuatro** piezas: **MySQL** (tabla `Clients`), **API PHP** (sesión + REST), **React** (página `/self-service`) y el **proxy hotspot** (**RPTO** periódico al master). El **peer server** es quien aplica al final **RPTO** / **OPTIONS** al bridge en ejecución y al comportamiento del hotspot.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requisitos previos
|
||||||
|
|
||||||
|
1. Bloque **`SELF_SERVICE`** en **`adn-mon.yaml`** con credenciales **MySQL** válidas y parámetros **PBKDF2** alineados con tu herramienta de contraseñas (misma sal/iteraciones que **`hotspot_proxy_self_service.py`** cuando se use).
|
||||||
|
2. **`DASHBOARD.SELF_SERVICE: true`** para que la UI muestre la entrada **Self-service** (y el backend exponga `/api/self-service/*` cuando la BD conecta).
|
||||||
|
3. Tabla **`Clients`** con filas: **`callsign`**, **`int_id`** (ID DMR), **`psswd`** (hex PBKDF2-SHA256), **`options`** (línea `KEY=value` separada por `;`), **`logged_in`**, **`host`**, **`modified`**, etc. (ver esquema / migraciones en el repo adn-monitor).
|
||||||
|
4. El tráfico del hotspot debe pasar por el **proxy** si dependes de **`modified`** y del envío **RPTO** (ver flujo abajo).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Autenticación
|
||||||
|
|
||||||
|
| Endpoint | Uso |
|
||||||
|
|----------|-----|
|
||||||
|
| **`POST /api/auth/login`** | Cuerpo: `callsign`, `password`. Verifica hash **PBKDF2** contra **`Clients.psswd`** en filas con **`logged_in = 1`**. Éxito: sesión PHP con **`user_id`**, **`int_ids`** (todos los DMR ID de ese indicativo). |
|
||||||
|
| **`GET /api/auth/login-by-ip`** | Opcional: una coincidencia de usuario por **`Clients.host`** = IP del cliente (misma forma de sesión). |
|
||||||
|
| **`POST /api/auth/logout`** | Cierra sesión. |
|
||||||
|
| **`GET /api/auth/me`** | Devuelve `{ callsign, int_ids, selected_int_id }` para React. |
|
||||||
|
|
||||||
|
La sesión se prolonga con actividad (**SelfServiceController** usa un timeout largo de inactividad).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API de dispositivo (tras login)
|
||||||
|
|
||||||
|
| Método | Ruta | Uso |
|
||||||
|
|--------|------|-----|
|
||||||
|
| **GET** | `/api/self-service/device?int_id=` | Carga fila **`Clients`** para ese **`int_id`** (debe estar en **`int_ids`** de sesión). JSON: **`int_id`**, **`callsign`**, **`mode`**, **`options`** parseadas en listas TS1/TS2, DIAL, VOICE, LANG, SINGLE, TIMER. |
|
||||||
|
| **POST** | `/api/self-service/device/options` | Cuerpo: **`int_id`**, cadena **`options`** (línea Homebrew **OPTIONS**). Debe terminar en **`;`**. Longitud máx. **4096**. Actualiza BD: **`Clients.options`**, **`modified = 1`**. |
|
||||||
|
| **GET** | `/api/self-service/device/modified?int_id=` | Devuelve **`{ modified: 0|1 }`** desde **`Clients.modified`** (la UI puede hacer polling hasta que el proxy lo limpie). |
|
||||||
|
| **POST** | `/api/self-service/device/select` | Cuerpo: **`int_id`** — fija **`selected_int_id`** en sesión si el usuario tiene varios dispositivos. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flujo extremo a extremo (cómo llegan las opciones al hotspot)
|
||||||
|
|
||||||
|
1. El usuario guarda opciones en la web → **PHP** escribe **`Clients.options`** y **`modified = 1`**.
|
||||||
|
2. El **proxy hotspot** ejecuta **`send_opts`** en bucle (~cada **10 s**). Para filas con **`modified = 1`**, lee opciones de la BD, envía **RPTO** **al peer server** en **`(MASTER, puerto_destino_asignado)`**, luego limpia **`modified`** en la BD.
|
||||||
|
3. El **ADN DMR Peer Server** recibe **RPTO** en la pata MASTER y actualiza su estado **OPTIONS** / bridge (mismo camino que un refresco normal de registro del hotspot).
|
||||||
|
4. El servidor envía la señalización adecuada al **hotspot** para que TG estáticos / reflector / temporizador surtan efecto **sin** reinicio completo del hotspot (el comportamiento coincide con el flujo HBP **OPTIONS** del servidor).
|
||||||
|
|
||||||
|
Importante: el proxy envía **RPTO solo al master**, no al hotspot directamente. Si el proxy no está en el camino, necesitas otro mecanismo que aplique **OPTIONS** o ejecutas el servidor sin este camino proxy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hash de contraseñas
|
||||||
|
|
||||||
|
**`AuthenticateUser`** usa:
|
||||||
|
|
||||||
|
```text
|
||||||
|
hash_pbkdf2('sha256', password, PBKDF2_SALT, PBKDF2_ITERATIONS)
|
||||||
|
```
|
||||||
|
|
||||||
|
almacenado en hex en **`Clients.psswd`**. Los mismos parámetros deben usarse donde se **registran** contraseñas (p. ej. **hotspot_proxy_self_service.py** en el repo de tooling / adn-dmr-server).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interfaz
|
||||||
|
|
||||||
|
- Ruta **`/self-service`** en React (`SelfService.tsx`): carga **`/api/auth/me`**, luego detalle del dispositivo para **`selected_int_id`**, edita la cadena de opciones, guarda.
|
||||||
|
- Un enlace externo **SelfCare** (p. ej. `selfcare.adn.systems`) puede aparecer en el nav como producto aparte — no es el mismo que este **self-service local**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ver también
|
||||||
|
|
||||||
|
- [Inicio de la documentación](../README.md)
|
||||||
|
- [Configuración](configuration.md) — `SELF_SERVICE`, `DASHBOARD`, `PROXY`
|
||||||
|
- [Arquitectura](architecture.md) — proxy + canal de informes
|
||||||
|
- Detalle del proxy: **`proxy/README.md`** en adn-monitor (tabla de temporización RPTO)
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
# Traducciones
|
||||||
|
|
||||||
|
## Diseño actual
|
||||||
|
|
||||||
|
Las páginas **MkDocs** existen en árboles paralelos:
|
||||||
|
|
||||||
|
| Idioma | Ruta | Build |
|
||||||
|
|--------|------|-------|
|
||||||
|
| Inglés | **`docs/en/`** | `mkdocs.yml` → **`site/en/`** |
|
||||||
|
| Español | **`docs/es/`** | **`mkdocs.es.yml`** → **`site/es/`** |
|
||||||
|
|
||||||
|
- **`docs/en/server/`** — ADN DMR Peer Server (guía de usuario, protocolos, desarrollo, contribución).
|
||||||
|
- **`docs/en/monitor/`** — ADN Monitor (panel, `adn-mon.yaml`, self-service).
|
||||||
|
|
||||||
|
El español replica las mismas rutas relativas bajo **`docs/es/`**.
|
||||||
|
|
||||||
|
`mkdocs.yml` usa **`docs_dir: docs/en`** e **`theme.language: en`**. **`mkdocs.es.yml`** usa **`docs_dir: docs/es`** e **`theme.language: es`**.
|
||||||
|
|
||||||
|
Genera **las dos** salidas (`mkdocs build` y `mkdocs build -f mkdocs.es.yml`). Quedan en **`site/en/`** y **`site/es/`**. Publica la carpeta **`site/`** según el mapeo de tu servidor HTTP.
|
||||||
|
|
||||||
|
Prueba local rápida: `cd site && python -m http.server` y abre **`/en/`** y **`/es/`**. Opcional: **`mkdocs-static-i18n`** más adelante para un solo build con pares de idioma por página.
|
||||||
|
|
||||||
|
## Añadir o actualizar un idioma
|
||||||
|
|
||||||
|
1. Mantén la **estructura de navegación** alineada entre locales (las mismas rutas relativas: `server/user-guide/introduction.md`, etc.).
|
||||||
|
2. Los encabezados con enlaces cruzados usan anclas explícitas **`attr_list`** `{#id}` cuando los slugs deben permanecer estables — ver páginas en español.
|
||||||
|
3. Si añades un tercer idioma, añade otro fichero MkDocs y directorio de salida siguiendo el mismo esquema.
|
||||||
|
|
||||||
|
## Escritura para traductores
|
||||||
|
|
||||||
|
- Frases **cortas y claras**.
|
||||||
|
- Evitar modismos y humor cultural.
|
||||||
|
- **Terminología** coherente (OpenBridge, BCSQ, TG, `BRIDGES`).
|
||||||
|
- **Identificadores de código** y **claves YAML** entre backticks.
|
||||||
|
|
||||||
|
## No traducido por defecto
|
||||||
|
|
||||||
|
- El **`README.md` de la raíz** del repositorio puede permanecer solo en inglés o enlazar al sitio de documentación publicado.
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
# Arquitectura (capas limpias)
|
||||||
|
|
||||||
|
## Capas
|
||||||
|
|
||||||
|
1. **Dominio** (`src/adn_server/domain/`) — entidades, objetos de valor, errores, `Result`. Sin E/S, sin Twisted.
|
||||||
|
2. **Aplicación** (`src/adn_server/application/`) — casos de uso (`BridgeUseCases`, `VoiceUseCases`, …) y **ports** (interfaces).
|
||||||
|
3. **Infraestructura** (`src/adn_server/infrastructure/`) — config YAML, Twisted UDP/TCP, voz, persistencia, adaptadores de seguridad.
|
||||||
|
|
||||||
|
**Regla de dependencias:** infraestructura → aplicación → dominio (solo hacia dentro).
|
||||||
|
|
||||||
|
## Punto de entrada
|
||||||
|
|
||||||
|
`main.py` cablea configuración, temporizadores **LoopingCall**, fábricas para **HBPProtocol**, servidor de informes e inyecta casos de uso.
|
||||||
|
|
||||||
|
## Dónde leer código
|
||||||
|
|
||||||
|
| Tema | Ubicación |
|
||||||
|
|------|-----------|
|
||||||
|
| Enrutado de bridge, `dmrd_received`, bucle OpenBridge | `application/bridge_use_cases.py` |
|
||||||
|
| HBP / OpenBridge UDP | `infrastructure/twisted_adapters/udp_hbp.py` |
|
||||||
|
| Informes TCP | `infrastructure/twisted_adapters/report_server.py` (fábrica), eventos de bridge desde casos de uso |
|
||||||
|
| Voz / TTS | `application/voice_use_cases.py`, `infrastructure/voice/` |
|
||||||
|
|
||||||
|
## Configuración como estado compartido
|
||||||
|
|
||||||
|
Un **`config` dict** mutable se pasa por adaptadores; actualizaciones en tiempo de ejecución (opciones, `SUB_MAP`, `_bcsq` OpenBridge) permanecen visibles globalmente durante la vida del proceso.
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
# Comportamiento y temporizadores
|
||||||
|
|
||||||
|
## Bucles de control estables
|
||||||
|
|
||||||
|
El servidor usa tareas **Twisted** `LoopingCall` para trabajo periódico: reglas de bridge, poda de flujos, refresco de opciones OpenBridge, recarga de alias, recarga de config de voz, descargas de seguridad, informes y pings de mantenimiento.
|
||||||
|
|
||||||
|
Los intervalos forman parte del **comportamiento observable** del producto (operadores e integradores pueden apoyarse en la temporización para diagnóstico). Evita **refrescos extra** o trabajo duplicado en **rutas calientes** (p. ej. manejadores por paquete para OpenBridge) cuando la misma preocupación ya la cubre el bucle programado — mantiene la carga predecible y evita aplicar reglas dos veces.
|
||||||
|
|
||||||
|
## Visibilidad de la configuración
|
||||||
|
|
||||||
|
El estado en tiempo de ejecución vive en un **`config` dict** compartido: opciones de peers, `SUB_MAP`, campos de plano de control OpenBridge (`_bcsq`, `_bcka`), y similares. Los adaptadores actualizan esta estructura; los casos de uso la leen. Así coincide con cómo se inspecciona el proceso en ejecución en logs y escenarios de soporte.
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
# Disposición de trama DMRE v5
|
||||||
|
|
||||||
|
Esta página describe el formato en cable **OpenBridge DMRE** cuando el **byte de versión de protocolo embebido** es **5** (a menudo llamado **«OpenBridge v5»** en documentación de operadores — igual que **DMRE v5**; ver [OpenBridge](openbridge.md#dmre-and-openbridge-v5)).
|
||||||
|
|
||||||
|
Los datagramas **OpenBridge** extendidos usan el opcode **`DMRE`** (bytes `D`,`M`,`R`,`E`) y un campo **versión**. Cuando el byte de versión embebido es **> 4**, el paquete tiene **89 bytes** con **saltos** en el byte **72** y **BLAKE2b** de **73** a **89**.
|
||||||
|
|
||||||
|
## Forma corta (85 bytes)
|
||||||
|
|
||||||
|
Cuando la versión embebida es **≤ 4**, se omite **repetidor fuente**; **saltos** y **MAC** se desplazan (ver implementación en `udp_hbp.py`).
|
||||||
|
|
||||||
|
## Resumen de campos (v5 de 89 bytes)
|
||||||
|
|
||||||
|
| Región | Contenido |
|
||||||
|
|--------|-----------|
|
||||||
|
| 0:4 | Opcode `DMRE` |
|
||||||
|
| 4:5 | Secuencia |
|
||||||
|
| 5:8 | Fuente RF |
|
||||||
|
| 8:11 | ID destino |
|
||||||
|
| 11:15 | ID servidor |
|
||||||
|
| 15:16 | Bits (slot, tipo llamada, tipo trama, dtype/vseq) |
|
||||||
|
| 16:20 | Stream ID |
|
||||||
|
| 20:53 | Carga de voz |
|
||||||
|
| 53:55 | BER, RSSI |
|
||||||
|
| 55:56 | Versión de protocolo embebida |
|
||||||
|
| 56:64 | Marca de tiempo (ns, big-endian) |
|
||||||
|
| 64:68 | ID servidor de origen |
|
||||||
|
| 68:72 | Repetidor fuente (extendido v5) |
|
||||||
|
| 72:73 | Saltos |
|
||||||
|
| 73:89 | MAC BLAKE2b (16 bytes) |
|
||||||
|
|
||||||
|
**Integridad:** BLAKE2b-128 con la **passphrase** como clave; la MAC cubre los bytes **antes** del campo MAC.
|
||||||
|
|
||||||
|
La disposición de bytes en código (`infrastructure/twisted_adapters/udp_hbp.py`) es autoritativa y puede recibir aclaraciones menores con el tiempo.
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
# HBP (HomeBrew Protocol) — DMRD
|
||||||
|
|
||||||
|
## Rol
|
||||||
|
|
||||||
|
**HBP** es el encapsulado UDP entre este servidor y sistemas **MASTER** / **PEER**. Las cargas usan el opcode **`DMRD`** (cuatro bytes ASCII) seguido de campos de voz/datos DMR (fuente RF, destino, ID de flujo, bloque AMBE, etc.).
|
||||||
|
|
||||||
|
## Autenticación
|
||||||
|
|
||||||
|
- Lado **MASTER**: RPTL → salt → RPTK → config; opciones de peer (**RPTO**) refrescan opciones del bridge.
|
||||||
|
- Lado **PEER**: conecta aguas arriba, repite autenticación, pings de mantenimiento.
|
||||||
|
|
||||||
|
Implementación: `infrastructure/twisted_adapters/udp_hbp.py` (`HBPProtocol`).
|
||||||
|
|
||||||
|
## OpenBridge frente a HBP
|
||||||
|
|
||||||
|
OpenBridge usa **`DMRD`** v1 (HMAC-SHA1) o **`DMRE`** (extendido); ver [OpenBridge](openbridge.md). Los enlaces HBP **MASTER/PEER** usan reglas clásicas **DMRD**.
|
||||||
|
|
||||||
|
## BER / RSSI
|
||||||
|
|
||||||
|
Para fuentes que no son OpenBridge, bytes opcionales **BER/RSSI** pueden añadirse tras la carga de voz de 53 bytes en ingreso; el reenvío a OpenBridge puede recortar o conservar campos según destino y reglas `to_target`.
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
# Créditos, licencia e historia del proyecto
|
||||||
|
|
||||||
|
## Créditos y origen
|
||||||
|
|
||||||
|
**ADN Systems DMR Peer Server** parte de **FreeDMR**, reorganizado con **arquitectura limpia** (dominio, aplicación, infraestructura) y con funciones propias de ADN: anuncios programados, **TTS**, voz bajo demanda, fusión de **`adn-voice.yaml`**, informes TCP al **ADN Monitor**, integración con **`adn-mon.yaml`**, self-service y proxy hotspot, entre otras.
|
||||||
|
|
||||||
|
**[FreeDMR Peer Server](https://gitlab.hacknix.net/hacknix/FreeDMR)** es obra de **Simon Adlem, G7RZU** ([hacknix](https://gitlab.hacknix.net/hacknix/FreeDMR)). El proyecto se describe así:
|
||||||
|
|
||||||
|
|
||||||
|
**[hblink3](https://github.com/HBLink-org/hblink3)** — HBLink para Python3. Copyright (C) 2016-2020 Cortney T. Buffington, N0MJS n0mjs@me.com (según licencia y `README` del proyecto original). FreeDMR se define como un fork de esa base de código.
|
||||||
|
|
||||||
|
## Licencia
|
||||||
|
|
||||||
|
Este proyecto se publica bajo **GNU General Public License v3** (o posterior), en la misma línea que FreeDMR y el ecosistema asociado. El texto legal completo está en el repositorio (`LICENSE`, `COPYING` si aplica) junto a las condiciones de uso y redistribución.
|
||||||
|
|
||||||
|
## Ver también
|
||||||
|
|
||||||
|
- [Introducción](introduction.md)
|
||||||
|
- [Arquitectura](../development/architecture.md)
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
# Bridges y talkgroups
|
||||||
|
|
||||||
|
## Modelo `BRIDGES`
|
||||||
|
|
||||||
|
La tabla de bridges asocia **claves de talkgroup** (cadenas, p. ej. `"26811"`, `"#reflector"`) con **filas**. Cada fila describe:
|
||||||
|
|
||||||
|
- **`SYSTEM`** — qué sistema configurado origina o recibe esta pata.
|
||||||
|
- **`TS`** — slot temporal (1 o 2). Las fuentes OpenBridge se normalizan a **TS1** en el enrutado (`bridge_match_slot`).
|
||||||
|
- **`TGID`** — bytes de ID de destino para reescritura LC hacia esa pata.
|
||||||
|
- **`ACTIVE`** — si esta pata participa.
|
||||||
|
- **`TIMEOUT`**, **`TO_TYPE`**, **`ON`/`OFF`/`RESET`** — semántica de activación (bridges activados por usuario, reflectores, etc.).
|
||||||
|
|
||||||
|
El router recorre `BRIDGES` buscando una fila **ACTIVE** que coincida con el **sistema de origen actual**, **slot** y **TG de destino** antes de reenviar (`dmrd_received` → `to_target`).
|
||||||
|
|
||||||
|
## Dinámico frente a estático
|
||||||
|
|
||||||
|
- Los bridges **activados por usuario** se crean cuando alguien pulsa una TG sin fila previa (sujeto a `DEFAULT_UA_TIMER` y opciones).
|
||||||
|
- Las TG **estáticas** y bridges **STAT** se crean desde flujos **OPTIONS** / `make_static_tg` / `GEN_STAT_BRIDGES`.
|
||||||
|
|
||||||
|
## OpenBridge y visualización de TG
|
||||||
|
|
||||||
|
En OpenBridge, el **destino DMR** en el paquete puede diferir del **TGID** en una fila de bridge (remap). El monitor puede mostrar **TG RX** (recibida) frente a **TG TX** (reescrita para un destino); correlaciona por **`stream_id`**, no solo por TG.
|
||||||
|
|
||||||
|
## Contención
|
||||||
|
|
||||||
|
La voz de grupo usa **hang time**, **`STREAM_TO`** y estado de slot `TX_*` / `RX_*` para evitar transmisiones simultáneas en los mismos recursos.
|
||||||
|
|
||||||
|
Ver también: [Números especiales](special-numbers.md), [Protocolo OpenBridge](../protocols/openbridge.md).
|
||||||
@ -0,0 +1,219 @@
|
|||||||
|
# Configuración
|
||||||
|
|
||||||
|
## Ficheros y flujo de trabajo
|
||||||
|
|
||||||
|
| Fichero | En el repo | Rol |
|
||||||
|
|---------|-------------|-----|
|
||||||
|
| `adn-server.example.yaml` | Sí | Plantilla — copiar a `adn-server.yaml` y editar. |
|
||||||
|
| `adn-server.yaml` | **No** (gitignored) | Servidor principal: sistemas, globales, logging, alias, informes. |
|
||||||
|
| `adn-voice.example.yaml` | Sí | Plantilla de voz — copiar a `adn-voice.yaml`. |
|
||||||
|
| `adn-voice.yaml` | **No** (habitual) | Voz/TTS/grabación; se fusiona en `config["VOICE"]` al arranque y **recarga en caliente** (~cada 15 s) si cambia el fichero. |
|
||||||
|
|
||||||
|
Ejecución:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python adn-server.py -c /ruta/a/adn-server.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Opcional: `--logging LEVEL` sobrescribe `LOGGER.LOG_LEVEL`.
|
||||||
|
|
||||||
|
Si `adn-voice.yaml` está junto a `adn-server.yaml`, se carga automáticamente. También puedes poner un bloque `VOICE:` dentro de `adn-server.yaml`; el fichero separado es la forma habitual de cambiar anuncios sin tocar la config principal.
|
||||||
|
|
||||||
|
**Secretos:** no versionar passphrases reales, URLs de seguridad ni `user_passwords.json` / `encryption_key.secret`. Usa placeholders en plantillas y mantén producción en local.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arquitectura: ¿qué es un «sistema»?
|
||||||
|
|
||||||
|
Cada entrada bajo **`SYSTEMS`** es un **enlace lógico** con nombre (endpoint UDP) que habla **HBP** (HomeBrew Protocol) con hotspots/repetidores, u **OpenBridge** con otros servidores. Los nombres son libres (`SYSTEM`, `ECHO`, `OBP-UK`, …) y se usan en logs y en la **tabla de bridges** (`BRIDGES`) para identificar por dónde entra o sale el tráfico.
|
||||||
|
|
||||||
|
Existen tres **modos**:
|
||||||
|
|
||||||
|
| Modo | Uso típico | Escucha | Conecta aguas arriba |
|
||||||
|
|------|------------|---------|----------------------|
|
||||||
|
| **MASTER** | Servidor de conferencia para uno o más hotspots/repetidores | **Sí** — `IP` / `PORT`, los peers se registran con passphrase | No (los peers se conectan a ti) |
|
||||||
|
| **PEER** | Hotspot/repetidor o servicio (p. ej. parrot) como **cliente** de un MASTER | **Sí** — `IP` / `PORT` local | **Sí** — `MASTER_IP` / `MASTER_PORT` deben apuntar al MASTER |
|
||||||
|
| **OPENBRIDGE** | Enlace a otro **servidor** por OpenBridge (DMRD v1 / DMRE) | **Sí** — `IP` / `PORT` | **Sí** — `TARGET_IP` / `TARGET_PORT` (servidor par) |
|
||||||
|
|
||||||
|
**MASTER** mantiene la tabla **`PEERS`** en tiempo de ejecución (hotspots autenticados). **PEER** mantiene **STATS** (conexión, pings). **OPENBRIDGE** usa **NETWORK_ID**, **PASSPHRASE**, **TARGET_***, **PROTO_VER** / **VER**, y opcionalmente **ENHANCED_OBP**, **RELAX_CHECKS**, **TGID_ACL**.
|
||||||
|
|
||||||
|
Un solo proceso puede ejecutar **varios** sistemas a la vez (p. ej. un MASTER para usuarios, un ECHO para parrot, un OBP hacia una red asociada).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `GLOBAL`
|
||||||
|
|
||||||
|
Valores por defecto de todo el servidor. Muchas claves pueden sobrescribirse por sistema si `USE_ACL` (o similar) está activo en ese sistema.
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **PING_TIME** | Intervalo (segundos) para ping / keepalive del PEER hacia MASTER. |
|
||||||
|
| **MAX_MISSED** | Cuántos pings perdidos antes de considerar el enlace PEER no saludable (depende de la implementación con STATS). |
|
||||||
|
| **USE_ACL** | Si es true, se aplican **REG_ACL**, **SUB_ACL**, **TGID_TS1_ACL**, **TGID_TS2_ACL** (tras procesarlas en tuplas internas). |
|
||||||
|
| **REG_ACL** | Control de acceso para **registro** / IDs de peer (`PERMIT:…` / `DENY:…`; ver [Cadenas ACL](#cadenas-acl)). |
|
||||||
|
| **SUB_ACL** | ACL para IDs de **suscriptor** (radio) en tráfico recibido. |
|
||||||
|
| **TGID_TS1_ACL** | ACL para **talkgroup** en **slot temporal 1**. |
|
||||||
|
| **TGID_TS2_ACL** | ACL para **talkgroup** en **slot temporal 2**. |
|
||||||
|
| **GEN_STAT_BRIDGES** | Si es true, OpenBridge puede disparar filas de bridges **estáticos** para ciertas TG (ver [Bridges y talkgroups](bridges-and-talkgroups.md)). |
|
||||||
|
| **SERVER_ID** | ID numérico del servidor; valor de 4 bytes para OpenBridge / metadatos de voz. |
|
||||||
|
| **VALIDATE_SERVER_IDS** | Si es true (ruta DMRE), los IDs de **servidor de origen** pueden comprobarse contra una lista descargada (`ALIASES` **SERVER_ID_**\*). |
|
||||||
|
| **URL_SECURITY** / **PORT_SECURITY** / **PASS_SECURITY** | Si están definidos, habilitan descarga de claves/contraseñas desde el endpoint de seguridad (ver comentarios del ejemplo). Vacío = desactivado. |
|
||||||
|
| **USERS_PASS** | Nombre de fichero JSON de contraseñas por radio (opcional). |
|
||||||
|
| **HASH_ENCRYPT** | Ruta a la clave de cifrado para el manejo del fichero de contraseñas. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `SYSTEMS` — campos comunes
|
||||||
|
|
||||||
|
Aparecen principalmente en **MASTER** (y a menudo en **PEER**). OpenBridge usa un subconjunto distinto.
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **MODE** | `MASTER`, `PEER` u `OPENBRIDGE`. |
|
||||||
|
| **ENABLED** | Si es `false`, el sistema se omite. |
|
||||||
|
| **IP** / **PORT** | Dirección UDP de escucha para el listener HBP u OpenBridge de este sistema. |
|
||||||
|
| **PASSPHRASE** | Secreto compartido para autenticación HBP (MASTER ↔ PEER). Debe coincidir entre PEER y su MASTER. |
|
||||||
|
| **USE_ACL** | Sobrescribe ACL por sistema si es true (usa `REG_ACL` / `SUB_ACL` / `TGID_TS*_ACL` del sistema). |
|
||||||
|
| **GROUP_HANGTIME** | Tiempo de hang (segundos) para el estado de voz de grupo. |
|
||||||
|
| **DEFAULT_UA_TIMER** | Tiempo de espera por defecto (minutos en muchos sitios) para bridges **activados por usuario**. |
|
||||||
|
| **ANNOUNCEMENT_LANGUAGE** | Carpeta de idioma por defecto bajo `Audio/<lang>/` para mensajes en este sistema. |
|
||||||
|
| **ALLOW_UNREG_ID** | Si se permiten IDs de suscriptor no registrados (MASTER). |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `SYSTEMS` — MASTER
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **REPEAT** | Si es true, el tráfico recibido puede **repetirse** a otros peers conectados al MASTER (comportamiento típico de conferencia). |
|
||||||
|
| **MAX_PEERS** | Máximo de hotspots conectados. |
|
||||||
|
| **EXPORT_AMBE** | Flag de exportación AMBE (si está habilitado en el build). |
|
||||||
|
| **SINGLE_MODE** | Afecta a OPTIONS / expansión del generador (estilo un solo usuario). |
|
||||||
|
| **VOICE_IDENT** | Habilita **identificación por voz** periódica cuando se cumplen condiciones (ver `IdentUseCases`). |
|
||||||
|
| **TS1_STATIC** / **TS2_STATIC** | Listas estáticas de TG separadas por comas, enviadas vía manejo OPTIONS (ver `options_config`). |
|
||||||
|
| **DEFAULT_REFLECTOR** | Número de **reflector** por defecto para bridges de marcado `#` (0 = ninguno). |
|
||||||
|
| **OVERRIDE_IDENT_TG** | TG opcional para ident por voz en lugar de all-call. |
|
||||||
|
| **GENERATOR** | Si es **> 1**, este MASTER se expande en **`NAME-0`**, **`NAME-1`**, … con puertos consecutivos (ver `expand_generator` en código). |
|
||||||
|
|
||||||
|
**MASTER** escucha conexiones PEER; cada peer autenticado se guarda en **`PEERS`** en tiempo de ejecución.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `SYSTEMS` — PEER
|
||||||
|
|
||||||
|
Un **PEER** conecta **saliente** hacia un **MASTER** y escucha localmente para la radio o la app.
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **MASTER_IP** / **MASTER_PORT** | Dirección del **MASTER** con el que registrarse (debe coincidir con `IP`/`PORT` de ese MASTER). |
|
||||||
|
| **RADIO_ID** | ID de este peer en HBP (4 bytes). |
|
||||||
|
| **CALLSIGN**, **RX_FREQ**, **TX_FREQ**, **COLORCODE**, **LATITUDE**, … | Campos del payload RPT enviados al MASTER en el registro (anchos fijos en protocolo). |
|
||||||
|
| **OPTIONS** | Cadena / línea de opciones (p. ej. `TS2=9990;`) para TG estáticas / comportamiento. |
|
||||||
|
| **LOOSE** | Flag de manejo relajado donde aplique. |
|
||||||
|
|
||||||
|
El ejemplo **parrot** (`adn-parrot.example.yaml`) es un PEER que se une al MASTER **ECHO**: mismo **PASSPHRASE**, **MASTER_PORT** = **PORT** del ECHO. Ver [Parrot](parrot.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `SYSTEMS` — OPENBRIDGE
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **NETWORK_ID** | Debe coincidir con el **NETWORK_ID** del par en paquetes OpenBridge. |
|
||||||
|
| **TARGET_IP** / **TARGET_PORT** | Par OpenBridge remoto (UDP). |
|
||||||
|
| **TGID_ACL** (o **TG1_ACL**) | ACL de talkgroup para OpenBridge (a menudo estilo `DENY:0-82,…` por rangos). |
|
||||||
|
| **RELAX_CHECKS** | Permitir paquetes cuando el socket del par no coincide estrictamente con `TARGET` (usar con cuidado). |
|
||||||
|
| **ENHANCED_OBP** | Habilita **BCSQ** / **BCKA** y control de bucle multipath — **debería ser `true`** en enlaces inter-servidor ADN Systems (ver abajo). |
|
||||||
|
| **PROTO_VER** | Versión de protocolo **DMRE** embebida; **`5`** selecciona **DMRE / OpenBridge v5** (trama de 89 bytes, BLAKE2b). El valor por defecto en código es **5**; usa **5** en nuevos despliegues ADN. |
|
||||||
|
|
||||||
|
**Recomendación ADN Systems:** para cada par OpenBridge en la malla ADN, configura **`PROTO_VER: 5`** (DMRE v5) y **`ENHANCED_OBP: true`**. Alinea la misma configuración en **ambos** extremos. Los pares solo **DMRD** v1 siguen siendo posibles por compatibilidad, pero no son el modo preferido para la red.
|
||||||
|
|
||||||
|
Filtros de ingreso y control de bucle: [Protocolo OpenBridge](../protocols/openbridge.md) (incl. [DMRE frente a OpenBridge v5](../protocols/openbridge.md#dmre-and-openbridge-v5)) y [Números especiales — ingreso OpenBridge](special-numbers.md#openbridge-ingress--group-tg-filters).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `BRIDGES` (tiempo de ejecución)
|
||||||
|
|
||||||
|
La tabla de bridges asocia claves TG con filas de enrutado. Está **en memoria** en el proceso en ejecución.
|
||||||
|
|
||||||
|
El cargador YAML **no** carga un bloque `BRIDGES:` de nivel superior desde `adn-server.yaml` en el router hoy — las filas iniciales se crean en código (p. ej. arranque **9990 / ECHO** cuando existe un sistema **ECHO**), luego **OPTIONS**, bridges **activados por usuario**, bridges **estáticos** y la lógica **OpenBridge** añaden filas con el tiempo.
|
||||||
|
|
||||||
|
Para el modelo conceptual (ACTIVE, TS, TGID, timeouts): [Bridges y talkgroups](bridges-and-talkgroups.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `REPORTS`
|
||||||
|
|
||||||
|
Canal TCP de informes para **adn-monitor** (o paneles compatibles).
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **REPORT** | Activar/desactivar envío. |
|
||||||
|
| **REPORT_INTERVAL** | Intervalo de envío periódico (segundos). |
|
||||||
|
| **REPORT_PORT** | Puerto local en el que el **servidor escucha** clientes de informes. |
|
||||||
|
| **REPORT_CLIENTS** | Lista separada por comas o lista de IPs de clientes permitidos (ver ejemplo). |
|
||||||
|
|
||||||
|
Detalle: [Monitor e informes](monitoring.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `LOGGER`
|
||||||
|
|
||||||
|
Implementado en `infrastructure/logging_config.py` (`setup_logging`). Los valores se leen del bloque **`LOGGER`** (o `--logging` solo para **LOG_LEVEL**).
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **LOG_HANDLERS** | Lista separada por comas de **tokens** de manejador (espacios alrededor de las comas están bien). Cada token elige salidas; puedes combinar varios. Valores reconocidos: **`console-timed`** o **`console`** — log a **stderr** con formato `LEVEL asctime message`; **`file-timed`** o **`file`** — log a **LOG_FILE** con el mismo formato (UTF-8). **Por defecto** si falta: `console-timed`. Ejemplos: solo `console-timed`; solo `file-timed`; `console-timed,file-timed` para consola y fichero. |
|
||||||
|
| **LOG_FILE** | Ruta usada cuando **`file-timed`** o **`file`** está en **LOG_HANDLERS**. Si falta, el código usa por defecto `/dev/null`. Si la ruta es **`/dev/null`**, los manejadores de fichero **no** se adjuntan aunque estén listados. Si no se puede abrir el fichero (permisos, directorio inexistente), se escribe un aviso a stderr y el logging continúa sin ese manejador de fichero. |
|
||||||
|
| **LOG_LEVEL** | Nivel del logger raíz: **`DEBUG`**, **`INFO`**, **`WARNING`**, **`ERROR`**, **`CRITICAL`** (sin distinguir mayúsculas; por defecto **INFO**). Nombres desconocidos caen en **INFO**. Puedes sobrescribir al arranque con **`python adn-server.py --logging LEVEL`** (mismos nombres). Hay un nivel personalizado **`TRACE`** registrado para llamadas ocasionales `logger.trace(...)`; usa **`DEBUG`** para diagnóstico detallado en operación normal. |
|
||||||
|
| **LOG_NAME** | Nombre del logger devuelto a la aplicación (por defecto **`ADN`**). No cambia la lista de manejadores; selecciona qué logger nombrado recibe el nivel configurado. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `ALIASES`
|
||||||
|
|
||||||
|
Descargas y ficheros locales para **IDs de peer**, **IDs de suscriptor**, **etiquetas de talkgroup**, lista opcional de **IDs de servidor**, **checksums** y **claves**. Usados en paneles, validación y descargas de seguridad opcionales.
|
||||||
|
|
||||||
|
| Clave | Significado |
|
||||||
|
|-------|-------------|
|
||||||
|
| **PATH** | Directorio base para JSON/TSV/pickle. |
|
||||||
|
| **TRY_DOWNLOAD** | Si se deben obtener desde URLs cuando están obsoletos. |
|
||||||
|
| **PEER_FILE** / **SUBSCRIBER_FILE** / **TGID_FILE** | Nombres de ficheros locales. |
|
||||||
|
| **\*_URL** | Orígenes remotos para descargas. |
|
||||||
|
| **SUB_MAP_FILE** | Ruta pickle para **SUB_MAP** (enrutado de llamadas privadas); nombre por defecto si está vacío. |
|
||||||
|
| **STALE_DAYS** | Umbral de refresco para descargas. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `VOICE` (desde `adn-voice.yaml` o inline)
|
||||||
|
|
||||||
|
Fusionado en `config["VOICE"]`. Ver [Voz, anuncios y TTS](voice-and-tts.md) y `adn-voice.example.yaml`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cadenas ACL
|
||||||
|
|
||||||
|
Procesadas por `acl_build`: `PERMIT:` o `DENY:` seguido de IDs o rangos separados por comas.
|
||||||
|
|
||||||
|
Ejemplos:
|
||||||
|
|
||||||
|
- `PERMIT:ALL` — permitir todos los IDs en rango.
|
||||||
|
- `DENY:1` — denegar solo el ID 1.
|
||||||
|
- `DENY:0-82,9990-9999` — denegar los rangos listados.
|
||||||
|
|
||||||
|
Las ACL globales aplican cuando `USE_ACL` es true; OpenBridge puede usar **TGID_ACL** en el sistema OBP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Entorno Python
|
||||||
|
|
||||||
|
Usa el intérprete del proyecto (ver reglas del workspace), p. ej. `python3.11` de pyenv, para un comportamiento alineado con producción.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ver también
|
||||||
|
|
||||||
|
- [Introducción](introduction.md) — rol del servidor.
|
||||||
|
- [Bridges y talkgroups](bridges-and-talkgroups.md) — semántica de `BRIDGES`.
|
||||||
|
- [Números especiales](special-numbers.md) — TG e IDs reservados.
|
||||||
|
- [Parrot](parrot.md) — ejemplo PEER (proceso parrot).
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
# Monitor e informes
|
||||||
|
|
||||||
|
## Canal TCP de informes
|
||||||
|
|
||||||
|
Cuando **`REPORTS`** está habilitado en la config del servidor, el **ADN DMR Peer Server** escucha en TCP y los **clientes de informes** (típicamente **adn-monitor**) se conectan y reciben:
|
||||||
|
|
||||||
|
- **CONFIG_SND** / **BRIDGE_SND** — instantáneas pickle de sistemas y bridges.
|
||||||
|
- **BRDG_EVENT** — eventos de texto para llamadas (`GROUP VOICE`, `PRIVATE VOICE`, etc.).
|
||||||
|
|
||||||
|
El **monitor** decodifica estos mensajes, actualiza **CTABLE** / **BTABLE** y (con MySQL configurado) persiste Last Heard / estadísticas.
|
||||||
|
|
||||||
|
**Pila completa:** [Descripción general del ADN Monitor](../../monitor/index.md) (monitor Python, WebSocket, API PHP, proxy y self-service opcionales).
|
||||||
|
|
||||||
|
## Semántica del monitor en OpenBridge
|
||||||
|
|
||||||
|
- **`GROUP VOICE,INGRESS,RX`** — primera aparición de un flujo en una pata OpenBridge (depuración; visibilidad completa en logs).
|
||||||
|
- **`GROUP VOICE,START,RX`** — inicio **canónico** tras **control de bucle** (alimenta chips del panel / CTABLE).
|
||||||
|
- **`GROUP VOICE,END,…`** — fin de llamada; variantes RX/TX según dirección.
|
||||||
|
|
||||||
|
El panel muestra el estado **operativo** desde **START** (canónico); el **log del Monitor** muestra **INGRESS** más **START** para depurar duplicados en malla.
|
||||||
|
|
||||||
|
## Requisitos
|
||||||
|
|
||||||
|
- Conectividad de red desde el **host del monitor** al **`REPORTS.REPORT_PORT`** del servidor (y la lista **`REPORT_CLIENTS`** del servidor debe incluir al monitor si se usa).
|
||||||
|
- **adn-monitor** `ADN_CONNECTION.ADN_IP` / **`ADN_PORT`** debe coincidir con el servidor — ver [Configuración del monitor](../../monitor/configuration.md#adn_connection).
|
||||||
|
|
||||||
|
## Self-service y hotspots
|
||||||
|
|
||||||
|
Los operadores que editan **opciones de dispositivo** desde el panel usan el flujo **self-service** (MySQL **`Clients`**, proxy **RPTO**). Está documentado en [Self-service](../../monitor/self-service.md); **no** forma parte solo del binario del peer server. Para la configuración del **proxy hotspot** (`PROXY` en `adn-mon.yaml`), enlace al rango **UDP** del peer server y arranque del proceso, ver [Proxy hotspot](../../monitor/hotspot-proxy.md).
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
# Parrot (reproducción)
|
||||||
|
|
||||||
|
## Qué es
|
||||||
|
|
||||||
|
**Parrot** es un **punto de entrada separado** (`adn-parrot.py` / `parrot_main`) que graba voz de **grupo** entrante y la reproduce (eco / parrot), independiente del proceso principal del bridge.
|
||||||
|
|
||||||
|
## Configuración
|
||||||
|
|
||||||
|
- Copiar **`adn-parrot.example.yaml`** → **`adn-parrot.yaml`** (no versionada).
|
||||||
|
- Ejecutar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python adn-parrot.py -c adn-parrot.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relación con TG 9990 / ECHO
|
||||||
|
|
||||||
|
El servidor principal puede exponer un bridge **ECHO** en **TG 9990** para eco en banda. **Parrot** es un **servicio independiente** con su propia config — usa uno u otro según el despliegue.
|
||||||
|
|
||||||
|
## Documentación
|
||||||
|
|
||||||
|
Esta página es el resumen incluido en el repositorio; amplía las notas de despliegue localmente si hace falta.
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
# Llamadas privadas
|
||||||
|
|
||||||
|
## Descripción general
|
||||||
|
|
||||||
|
Las llamadas **unitarias (privadas)** usan un camino distinto a la voz de **grupo**. El router usa **`SUB_MAP`** (suscriptor → último sistema/slot/tiempo conocido) y reglas de colisión para decidir si y dónde reenviar.
|
||||||
|
|
||||||
|
## SUB_MAP
|
||||||
|
|
||||||
|
- Se rellena cuando las estaciones registran tráfico; persiste vía ruta pickle **`SUB_MAP`** configurada bajo **`ALIASES`**.
|
||||||
|
- Sirve para resolver **ID de radio de destino** a un **sistema destino** y **slot** para el reenvío privado.
|
||||||
|
|
||||||
|
## OpenBridge frente a MASTER
|
||||||
|
|
||||||
|
El manejo privado usa ramas CSBK/datos/unit, búsqueda `SUB_MAP` y comprobaciones de slot ocupado donde aplique (ver `BridgeUseCases` en el código).
|
||||||
|
|
||||||
|
## TG / ID 4000 (unitaria)
|
||||||
|
|
||||||
|
Como en [Números especiales](special-numbers.md), una llamada **privada** a **4000** desactiva dinámicos y **no** se trata como ruta privada normal.
|
||||||
|
|
||||||
|
## Informes
|
||||||
|
|
||||||
|
Los eventos privados **START/END** pueden emitirse al cliente TCP de informes si **`REPORTS.REPORT`** está habilitado, análogo a voz de grupo (forma `PRIVATE VOICE,...` donde esté implementado).
|
||||||
|
|
||||||
|
Para detalles de ingreso de protocolo, ver [HBP](../protocols/hbp.md) y los casos de uso de bridge en código (`BridgeUseCases._pvt_call_received`).
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
# MkDocs — documentación en español. Build: mkdocs build -f mkdocs.es.yml
|
||||||
|
site_name: ADN Systems
|
||||||
|
site_description: ADN DMR Peer Server, ADN Monitor, HBP, OpenBridge, self-service.
|
||||||
|
site_author: ADN Server contributors
|
||||||
|
|
||||||
|
docs_dir: docs/es
|
||||||
|
site_dir: site/es
|
||||||
|
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
language: es
|
||||||
|
features:
|
||||||
|
- navigation.tabs
|
||||||
|
- navigation.sections
|
||||||
|
- navigation.expand
|
||||||
|
- content.code.copy
|
||||||
|
- search.suggest
|
||||||
|
- search.highlight
|
||||||
|
palette:
|
||||||
|
- media: "(prefers-color-scheme: light)"
|
||||||
|
scheme: default
|
||||||
|
primary: indigo
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-7
|
||||||
|
name: Modo oscuro
|
||||||
|
- media: "(prefers-color-scheme: dark)"
|
||||||
|
scheme: slate
|
||||||
|
primary: indigo
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-4
|
||||||
|
name: Modo claro
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- search
|
||||||
|
|
||||||
|
markdown_extensions:
|
||||||
|
- attr_list
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.tabbed:
|
||||||
|
alternate_style: true
|
||||||
|
- admonition
|
||||||
|
- tables
|
||||||
|
- toc:
|
||||||
|
permalink: true
|
||||||
|
- pymdownx.details
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- Inicio: README.md
|
||||||
|
- Servidor (peer):
|
||||||
|
- Guía de usuario:
|
||||||
|
- Introducción: server/user-guide/introduction.md
|
||||||
|
- Configuración: server/user-guide/configuration.md
|
||||||
|
- Bridges y talkgroups: server/user-guide/bridges-and-talkgroups.md
|
||||||
|
- Números especiales (4000, 999x, eco): server/user-guide/special-numbers.md
|
||||||
|
- Llamadas privadas: server/user-guide/private-calls.md
|
||||||
|
- Voz, anuncios y TTS: server/user-guide/voice-and-tts.md
|
||||||
|
- Monitor e informes: server/user-guide/monitoring.md
|
||||||
|
- Parrot (reproducción): server/user-guide/parrot.md
|
||||||
|
- Créditos y licencia: server/user-guide/attribution.md
|
||||||
|
- Protocolos:
|
||||||
|
- HBP (DMRD): server/protocols/hbp.md
|
||||||
|
- OpenBridge (DMRE): server/protocols/openbridge.md
|
||||||
|
- Trama DMRE v5: server/protocols/dmre-v5.md
|
||||||
|
- Desarrollo:
|
||||||
|
- Arquitectura: server/development/architecture.md
|
||||||
|
- Comportamiento y temporizadores: server/development/behaviour-and-timers.md
|
||||||
|
- Contribuir:
|
||||||
|
- Traducciones: server/contributing/translations.md
|
||||||
|
- Monitor:
|
||||||
|
- Descripción general: monitor/index.md
|
||||||
|
- Configuración (adn-mon.yaml): monitor/configuration.md
|
||||||
|
- Proxy hotspot: monitor/hotspot-proxy.md
|
||||||
|
- Arquitectura e implantación: monitor/architecture.md
|
||||||
|
- Self-service: monitor/self-service.md
|
||||||
|
|
||||||
|
extra:
|
||||||
|
alternate:
|
||||||
|
- name: English
|
||||||
|
link: /en/
|
||||||
|
lang: en
|
||||||
|
- name: Español
|
||||||
|
link: /es/
|
||||||
|
lang: es
|
||||||
|
social: []
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
# MkDocs site — public documentation (English under docs/en). Build: pip install -e '.[docs]' && mkdocs build
|
||||||
|
site_name: ADN Systems
|
||||||
|
site_description: ADN DMR Peer Server, ADN Monitor, HBP, OpenBridge, self-service.
|
||||||
|
site_author: ADN Server contributors
|
||||||
|
|
||||||
|
docs_dir: docs/en
|
||||||
|
site_dir: site/en
|
||||||
|
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
language: en
|
||||||
|
features:
|
||||||
|
- navigation.tabs
|
||||||
|
- navigation.sections
|
||||||
|
- navigation.expand
|
||||||
|
- content.code.copy
|
||||||
|
- search.suggest
|
||||||
|
- search.highlight
|
||||||
|
palette:
|
||||||
|
- media: "(prefers-color-scheme: light)"
|
||||||
|
scheme: default
|
||||||
|
primary: indigo
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-7
|
||||||
|
name: Switch to dark mode
|
||||||
|
- media: "(prefers-color-scheme: dark)"
|
||||||
|
scheme: slate
|
||||||
|
primary: indigo
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-4
|
||||||
|
name: Switch to light mode
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- search
|
||||||
|
|
||||||
|
markdown_extensions:
|
||||||
|
- attr_list
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.tabbed:
|
||||||
|
alternate_style: true
|
||||||
|
- admonition
|
||||||
|
- tables
|
||||||
|
- toc:
|
||||||
|
permalink: true
|
||||||
|
- pymdownx.details
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- Home: README.md
|
||||||
|
- Server (peer):
|
||||||
|
- User guide:
|
||||||
|
- Introduction: server/user-guide/introduction.md
|
||||||
|
- Configuration: server/user-guide/configuration.md
|
||||||
|
- Bridges and talkgroups: server/user-guide/bridges-and-talkgroups.md
|
||||||
|
- Special numbers (4000, 999x, echo): server/user-guide/special-numbers.md
|
||||||
|
- Private calls: server/user-guide/private-calls.md
|
||||||
|
- Voice, announcements, and TTS: server/user-guide/voice-and-tts.md
|
||||||
|
- Monitoring and reports: server/user-guide/monitoring.md
|
||||||
|
- Parrot (playback): server/user-guide/parrot.md
|
||||||
|
- Credits & license: server/user-guide/attribution.md
|
||||||
|
- Protocols:
|
||||||
|
- HBP (DMRD): server/protocols/hbp.md
|
||||||
|
- OpenBridge (DMRE): server/protocols/openbridge.md
|
||||||
|
- DMRE v5 frame layout: server/protocols/dmre-v5.md
|
||||||
|
- Development:
|
||||||
|
- Architecture: server/development/architecture.md
|
||||||
|
- Behaviour and timers: server/development/behaviour-and-timers.md
|
||||||
|
- Contributing:
|
||||||
|
- Translations: server/contributing/translations.md
|
||||||
|
- Monitor:
|
||||||
|
- Overview: monitor/index.md
|
||||||
|
- Configuration (adn-mon.yaml): monitor/configuration.md
|
||||||
|
- Hotspot proxy: monitor/hotspot-proxy.md
|
||||||
|
- Architecture and deployment: monitor/architecture.md
|
||||||
|
- Self-service: monitor/self-service.md
|
||||||
|
|
||||||
|
extra:
|
||||||
|
alternate:
|
||||||
|
- name: English
|
||||||
|
link: /en/
|
||||||
|
lang: en
|
||||||
|
- name: Español
|
||||||
|
link: /es/
|
||||||
|
lang: es
|
||||||
|
social: []
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
# Optional: documentation site (MkDocs). Install: pip install -r requirements-docs.txt
|
||||||
|
mkdocs>=1.6
|
||||||
|
mkdocs-material>=9.5
|
||||||
|
pymdown-extensions>=10.3
|
||||||
Loading…
Reference in new issue