diff --git a/.gitignore b/.gitignore index 1336d02..047bf97 100644 --- a/.gitignore +++ b/.gitignore @@ -32,8 +32,12 @@ data/*.bak !data/.gitkeep json/* !json/.gitkeep -docs/* -!docs/PARROT.md + +# Internal +docs-priv/ + +# MkDocs build output (site/en, site/es) +site/ # ----------------------------------------------------------------------------- # Python diff --git a/README.md b/README.md index 61fd749..e8b443e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ADN DMR Peer Server -Clean Architecture rewrite of the ADN DMR conference bridge. Same behaviour as the original server; configuration is YAML. +ADN DMR conference bridge server. Configuration is YAML; the codebase follows clean architecture (domain, application, infrastructure). ## License @@ -25,7 +25,21 @@ Voice features (announcements, TTS, recording) use a separate config file. Copy - **Each item** (ANNOUNCEMENTS, TTS_ANNOUNCEMENTS) has its own `LANGUAGE` and `ENABLED: true` to activate. - **ANNOUNCEMENT_LANGUAGES** is optional (for voice ident only); announcements/TTS work without it. -- **TTS** requires ffmpeg + vocoder (TTS_VOCODER_CMD or TTS_AMBESERVER_HOST). Pipeline: `.txt` → gTTS → `.mp3` → ffmpeg → `.wav` → vocoder → `.ambe`. First time: create `Audio//ondemand/.txt` with the text. See [docs/TTS_SETUP.md](docs/TTS_SETUP.md). +- **TTS** requires ffmpeg + vocoder (TTS_VOCODER_CMD or TTS_AMBESERVER_HOST). Pipeline: `.txt` → gTTS → `.mp3` → ffmpeg → `.wav` → vocoder → `.ambe`. First time: create `Audio//ondemand/.txt` with the text. See [Voice, announcements, and TTS](docs/en/server/user-guide/voice-and-tts.md). + +## Documentation (MkDocs) + +Documentation (MkDocs): English under **`docs/en/`**, Spanish under **`docs/es/`** (`server/` and `monitor/` in each). **Install the doc stack first** (includes **Material**); otherwise you may see `Unrecognised theme name: 'material'` if `mkdocs` on your `PATH` is not the same environment. + +```bash +python3 -m pip install -r requirements-docs.txt +python3 -m mkdocs build -f mkdocs.yml # → site/en/ +python3 -m mkdocs build -f mkdocs.es.yml # → site/es/ +``` + +Use the same `python3` you use for the project (e.g. pyenv’s `3.11.8`). Preview the combined tree: `cd site && python3 -m http.server` then open **`/en/`** and **`/es/`**, or run `python3 -m mkdocs serve -f mkdocs.yml` for English only. + +Output: **`site/en/`** and **`site/es/`** under gitignored **`site/`**. ## Run @@ -50,4 +64,4 @@ cp adn-parrot.example.yaml adn-parrot.yaml python adn-parrot.py ``` -See [docs/PARROT.md](docs/PARROT.md) for architecture, configuration and systemd setup. +See [Parrot (playback)](docs/en/server/user-guide/parrot.md) in the docs site for an overview; extended notes may exist in private `docs-priv/` checkouts. diff --git a/adn-server.example.yaml b/adn-server.example.yaml index caa97f6..1b90b7f 100644 --- a/adn-server.example.yaml +++ b/adn-server.example.yaml @@ -131,6 +131,7 @@ SYSTEMS: PROXY_CONTROL: false OVERRIDE_IDENT_TG: "" + # OpenBridge: use DMRE embedded version 5 ("OpenBridge v5") + ENHANCED_OBP on all ADN Systems peers. OBP-TEST: MODE: OPENBRIDGE ENABLED: false @@ -144,5 +145,5 @@ SYSTEMS: SUB_ACL: DENY:1 TGID_ACL: DENY:0-82,92-199,800-899,9990-9999,730999 RELAX_CHECKS: true - ENHANCED_OBP: true - PROTO_VER: 5 + ENHANCED_OBP: true # BCSQ/BCKA + loop control — recommended true for ADN mesh + PROTO_VER: 5 # DMRE v5 (89-byte BLAKE2b); match remote peer diff --git a/adn-voice.example.yaml b/adn-voice.example.yaml index 532a0ce..f9554a2 100644 --- a/adn-voice.example.yaml +++ b/adn-voice.example.yaml @@ -56,7 +56,7 @@ VOICE: # The system encodes it to .ambe (via AMBEServer or vocoder) and plays it. # Later: if .ambe exists and is newer than .txt, uses cached .ambe directly. # ENABLED: true to activate; requires TTS_VOCODER_CMD or TTS_AMBESERVER_HOST - # See docs/TTS_SETUP.md for full setup and troubleshooting. + # See docs/en/server/user-guide/voice-and-tts.md for the TTS pipeline and troubleshooting. # --------------------------------------------------------------------------- # External vocoder command ({wav} and {ambe} are replaced at runtime). diff --git a/docs/PARROT.md b/docs/PARROT.md deleted file mode 100644 index 5db1e0b..0000000 --- a/docs/PARROT.md +++ /dev/null @@ -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, -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 -``` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2197760 --- /dev/null +++ b/docs/README.md @@ -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). diff --git a/docs/TTS_SETUP.md b/docs/TTS_SETUP.md deleted file mode 100644 index 68e8ef9..0000000 --- a/docs/TTS_SETUP.md +++ /dev/null @@ -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//ondemand/.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//ondemand/.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 diff --git a/docs/en/README.md b/docs/en/README.md new file mode 100644 index 0000000..2a30308 --- /dev/null +++ b/docs/en/README.md @@ -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/`**. diff --git a/docs/en/monitor/architecture.md b/docs/en/monitor/architecture.md new file mode 100644 index 0000000..022141f --- /dev/null +++ b/docs/en/monitor/architecture.md @@ -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) diff --git a/docs/en/monitor/configuration.md b/docs/en/monitor/configuration.md new file mode 100644 index 0000000..4c5bf27 --- /dev/null +++ b/docs/en/monitor/configuration.md @@ -0,0 +1,120 @@ +# Configuration (`adn-mon.yaml`) + +All components read the **same** YAML (default path often `monitor/adn-mon.yaml`; override with **`ADN_CONFIG_PATH`**). The example shipped in the **adn-monitor** repo is the authoritative template; keys below match `monitor/adn-mon.yaml` and `monitor/src/adn_monitor/infrastructure/config_loader.py` (internal names may differ). + +--- + +## `GLOBAL` + +| Key | Meaning | +|-----|---------| +| **BRIDGES_INC** | Show bridge status in the dashboard when `true`. | +| **HOMEBREW_INC** | Include Homebrew (HBP) peer/master status. | +| **LASTHEARD_INC** | Enable Last Heard features / tables. | +| **LASTHEARD_ROWS** | Row count for Last Heard widgets. | +| **EMPTY_MASTERS** | Whether to show masters with no peers. | +| **TGCOUNT_INC** | Enable TG count page / stats. | +| **TGCOUNT_ROWS** | Rows for TG count display. | +| **TIMEZONE** | IANA timezone name (e.g. `America/Santiago`) for display; empty uses server local time. | + +--- + +## `ADN_CONNECTION` + +Must match the **ADN DMR Peer Server** reporting configuration. + +| Key | Meaning | +|-----|---------| +| **ADN_IP** | Host/IP where the **peer server’s report TCP listener** is bound (from the monitor’s network view). | +| **ADN_PORT** | TCP port — must equal **`REPORTS.REPORT_PORT`** on the server and be reachable. | + +--- + +## `SELF_SERVICE` + +MySQL credentials and PBKDF2 parameters for **login** and **`Clients`** table access. **PBKDF2_SALT** and **PBKDF2_ITERATIONS** must match **`hotspot_proxy_self_service.py`** (or your password-registration tool) so stored password hashes verify in PHP and Python. + +| Key | Meaning | +|-----|---------| +| **USE_SELFSERVICE** | Used by the **proxy** config loader to enable DB-backed options / self-service paths (see proxy README). | +| **DB_SERVER**, **DB_USERNAME**, **DB_PASSWORD**, **DB_NAME**, **DB_PORT** | MySQL connection for **`Clients`** (and related) tables. | + +If the PHP backend cannot connect, **auth** and **self-service** API routes are not registered (see `backend/public/index.php`). + +--- + +## `PROXY` + +Hotspot **UDP proxy** — full guide: [Hotspot proxy](hotspot-proxy.md). Summary: forwards each client to **`MASTER:DESTPORT_START`…`DEST_PORT_END`**; the peer server must **listen** on that IP and port range (see also [Architecture](architecture.md)). + +| Key | Meaning | +|-----|---------| +| **MASTER** | Peer server host (IP or DNS; resolved at proxy startup). | +| **LISTEN_PORT** / **LISTEN_IP** | Where the proxy accepts hotspot UDP (empty IP often means all interfaces). | +| **DESTPORT_START** / **DEST_PORT_END** | One port per proxied client toward **`MASTER`**. | +| **TIMEOUT**, **STATS**, **DEBUG**, **CLIENT_INFO** | Behaviour and logging. | +| **BLACK_LIST** / **IP_BLACK_LIST** | Optional block lists. | + +--- + +## `OPB_FILTER` + +Comma-separated **network IDs** (as strings). Traffic from those OpenBridge sources can be **hidden** from certain dashboard persistence paths (see monitor controller `OPB_FILTER` handling). + +--- + +## `ALIASES` + +Similar idea to the peer server: download **peer / subscriber / TGID** JSON and optional checksums. Keys include **PATH**, **\*_FILE**, **\*_URL**, **STALE_HOURS**, **REVIEW_INTERVAL_MINUTES**, **CHECKSUM_***, **TG_LIST_URL**, **BRIDGE_LIST_URL** (backend proxy for frontend pages). + +--- + +## `LOGGER` + +| Key | Meaning | +|-----|---------| +| **LOG_PATH** | Directory for log files. | +| **LOG_FILE** | Monitor log filename (e.g. `adn-mon.log`). | +| **PROXY_LOG_FILE** | Separate log name for the proxy (when run with proxy logging). | +| **LOG_LEVEL** | e.g. `INFO`, `DEBUG`. | + +--- + +## `WEBSOCKET_SERVER` + +| Key | Meaning | +|-----|---------| +| **WEBSOCKET_PORT** | Port for Twisted WebSocket pushing JSON state to browsers. | +| **FREQUENCY** | Push interval (seconds). | +| **CLIENT_TIMEOUT** | Drop idle WS clients after N seconds (`0` = disable). | +| **USE_SSL**, **SSL_PATH**, **SSL_CERTIFICATE**, **SSL_PRIVATEKEY** | Optional WSS. | + +--- + +## `DASHBOARD` + +| Key | Meaning | +|-----|---------| +| **DASHTITLE** | Header title. | +| **BACKGROUND** | Use `bk.jpg` background if `true`. | +| **LANGUAGE** | Default UI language (`en`, `es`, …). | +| **SELF_SERVICE** | If `true`, the UI can show the **Self-service** nav entry (backend must expose API + DB). | +| **SHOW_CONSOLE** | Show console page (call start/end messages). | +| **MIN_DURATION** | Minimum call duration (seconds) for **dashboard** Last Heard table (Last Heard page may still show shorter). | +| **nav_links**, **footer**, **news** | Optional structured links / marquee items. | + +--- + +## Environment + +- **`ADN_CONFIG_PATH`**: Absolute path to `adn-mon.yaml` for monitor, backend, and proxy. +- Backend may use **`API_BASE_PATH`** if the API is mounted under a prefix. + +--- + +## See also + +- [Documentation home](../README.md) +- [Architecture](architecture.md) +- [Self-service](self-service.md) +- Peer server **`REPORTS`**: [Monitoring and reports](../server/user-guide/monitoring.md), [Server configuration](../server/user-guide/configuration.md) (section **`REPORTS`**). diff --git a/docs/en/monitor/hotspot-proxy.md b/docs/en/monitor/hotspot-proxy.md new file mode 100644 index 0000000..c61b6e0 --- /dev/null +++ b/docs/en/monitor/hotspot-proxy.md @@ -0,0 +1,114 @@ +# Hotspot proxy + +The **hotspot proxy** is part of the **adn-monitor** repository. It is a **UDP relay** between **DMR hotspots** (Homebrew / HBP) and the **ADN DMR Peer Server** MASTER: each connected hotspot is mapped to a **dedicated destination port** on the peer server host so many hotspots can share one public IP without port clashes. + +Source layout: `proxy/proxy.py`, package `proxy/src/adn_proxy/` (clean architecture). **GPL v3** (derivative of Simon Adlem, G7RZU’s original proxy). + +### Why it ships with the monitor (not inside the peer server) + +There is no single mandatory layout for every deployment, but **today the proxy lives in the adn-monitor repo** on purpose: + +- **Same config and ops** as the dashboard stack: **`adn-mon.yaml`**, **`ADN_CONFIG_PATH`**, and usually the same host as **PHP** and **MySQL**. +- **Self-service** ( **`Clients`**, RPTO, **`modified`**) is built around that ecosystem; the peer server binary does not own that database or the **`PROXY`** block. +- **Role split:** the **ADN DMR Peer Server** is the **radio core** (HBP/OpenBridge, bridges, voice, TCP reports). The hotspot proxy is an **optional UDP front** toward a MASTER that already listens on a port **range** — useful when many hotspots share one public address. + +**Bundling the proxy into the peer server** (one binary, one `adn-server.yaml`) is conceivable for packaging, but it implies **merging configuration**, **rethinking self-service wiring**, and extra maintenance — only worth it if you explicitly want a single deployable “all-in-one” server. + +--- + +## Configuration file (same as monitor) + +The proxy does **not** use `adn-server.yaml`. It reads the **monitor** YAML: + +| Source | Purpose | +|--------|---------| +| **`ADN_CONFIG_PATH`** | Environment variable: absolute path to **`adn-mon.yaml`** (shared with **monitor**, **PHP backend**, optional **`.env`** in repo root). | +| **`python proxy/proxy.py --config /path/to/adn-mon.yaml`** | Overrides the path for this process only. | +| **Default** (if unset) | `../monitor/adn-mon.yaml` relative to the `proxy/` directory when run from the adn-monitor tree. | + +Sections used: + +- **`PROXY`** — listen address, master host, destination port **range**, timeouts, debug, block lists. +- **`SELF_SERVICE`** — MySQL and **`USE_SELFSERVICE`** (for **`Clients`** table, RPTO / options). +- **`LOGGER`** — **`LOG_PATH`** and **`PROXY_LOG_FILE`** (proxy log is separate from **`LOG_FILE`** used by `monitor.py`). + +Optional **environment** overrides (see `proxy/README.md` in the repo): e.g. **`ADN_PROXY_DEBUG`**, **`ADN_PROXY_LISTENPORT`**. + +--- + +## `PROXY` keys (`adn-mon.yaml`) + +| Key | Role | +|-----|------| +| **MASTER** | IP or **hostname** of the **ADN DMR Peer Server** host. Resolved to an IPv4 address at startup (Twisted requires an IP for `write()`). | +| **LISTEN_PORT** | UDP port where **hotspots** connect **to the proxy** (the address users configure on the hotspot). | +| **LISTEN_IP** | Empty often means all interfaces; otherwise bind to this address. | +| **DESTPORT_START** / **DEST_PORT_END** | Inclusive range of UDP ports on **`MASTER`** used **one per proxied hotspot** (sequential allocation inside the proxy). | +| **TIMEOUT** | Idle / session timeout (seconds). | +| **STATS** | Extra statistics logging. | +| **DEBUG** | Verbose packet logging (or use **`ADN_PROXY_DEBUG=1`**). | +| **CLIENT_INFO** | Per-client info in logs. | +| **BLACK_LIST** / **IP_BLACK_LIST** | Block radio IDs or source IPs. | + +Internal config keys (after load) use mixed-case names (`Master`, `ListenPort`, …) — see `adn_proxy.infrastructure.config_loader`. + +--- + +## Peer server (`adn-server.yaml`) must cover the port range + +The proxy forwards traffic to **`MASTER:DESTPORT`** for each client, where **DESTPORT** is chosen inside **[DESTPORT_START, DEST_PORT_END]**. + +The **ADN DMR Peer Server** must therefore **listen on UDP** on **that host** for **every port** in the range that you intend to use (one **MASTER** listener per port, or equivalent). + +- A **single** `MODE: MASTER` with **one** `PORT` is **not** enough for multiple proxy clients if they map to different **DESTPORT** values — you need **multiple listeners** on the range. +- Typical approaches: **`GENERATOR`** on a MASTER system (splits into `NAME-0`, `NAME-1`, … with consecutive **PORT** values — see [Server configuration](../server/user-guide/configuration.md)), and/or multiple **`SYSTEMS`** entries, aligned with **`DESTPORT_START`…`DEST_PORT_END`** in **`PROXY`**. + +If the server only listens on e.g. **56400** but the proxy sends to **56401**, that client will not register. + +--- + +## How the process starts + +1. Resolve config path (`ADN_CONFIG_PATH`, `--config`, or default). +2. **`load_config()`** parses YAML → **`PROXY`**, **`SELF_SERVICE`**, **`LOG`**. +3. Optional **MySQL** pool if self-service / DB features are enabled. +4. Twisted **reactor** runs UDP **ProxyProtocol** on **`LISTEN_IP:LISTEN_PORT`**, forwarding to **`MASTER:assigned_dest_port`**. + +Run (from adn-monitor root, with env set): + +```bash +export ADN_CONFIG_PATH=/opt/adn-monitor/monitor/adn-mon.yaml +python proxy/proxy.py +# or +python proxy/proxy.py --config /path/to/adn-mon.yaml +``` + +Use **systemd** or another supervisor to run alongside **`monitor.py`** and the **PHP** stack. + +--- + +## RPTO, options, and self-service + +The proxy **never** sends **RPTO** directly to the hotspot for self-service updates. It sends **RPTO to the MASTER** (peer server); the server updates bridge/options and the normal HBP path applies. + +| Event | Proxy behaviour | +|-------|------------------| +| ~**10 s** after hotspot login (**RPTC**) | Read **`Clients.options`** from DB → **RPTO** → master `(MASTER, dport)`. | +| Every ~**10 s** | Rows with **`modified = 1`** → **RPTO** → master, then clear **`modified`**. | +| Hotspot sends **RPTO** | Forward to master; DB updates as implemented. | + +Details: [Self-service](self-service.md) and the **adn-monitor** `proxy/README.md`. + +--- + +## Monitor visibility + +Hotspots appear on the dashboard only if the **peer server** sends **TCP reports** to the same host/port as **`ADN_CONNECTION`** in **`adn-mon.yaml`**. Align **`REPORTS`** on the server with **`ADN_IP` / `ADN_PORT`**. See [Monitoring and reports](../server/user-guide/monitoring.md). + +--- + +## See also + +- [Monitor configuration](configuration.md) — full **`adn-mon.yaml`** reference (PROXY section summary). +- [Architecture](architecture.md) — where the proxy sits in the stack. +- [Self-service](self-service.md) — DB, **`modified`**, RPTO timing. diff --git a/docs/en/monitor/index.md b/docs/en/monitor/index.md new file mode 100644 index 0000000..bf81c3f --- /dev/null +++ b/docs/en/monitor/index.md @@ -0,0 +1,40 @@ +# ADN Monitor (overview) + +**ADN Monitor** is a separate project from the **ADN DMR Peer Server**, but the two are normally deployed **together**: the server sends **TCP reports** (config, bridges, call events) to the monitor; the monitor drives the **web dashboard** (React) and **WebSocket** live updates. Optional components include the **PHP API** (Slim), **MySQL** (self-service / device registry), and the **hotspot proxy** (UDP between hotspots and the peer server). + +This chapter documents the **adn-monitor** stack at the same level of detail as the server guides. Source code lives in the **adn-monitor** repository, not in the **adn-server** repository (where this documentation is maintained). + +## What each part does + +| Part | Role | +|------|------| +| **`monitor/monitor.py`** | Python (Twisted): connects to the peer server’s **report TCP** port, decodes netstring payloads (`CONFIG_SND`, `BRIDGE_SND`, `BRDG_EVENT`), maintains **CTABLE** / **BTABLE**, writes **Last Heard** / TG stats to **MySQL** when configured, serves **WebSocket** JSON to the dashboard. | +| **`backend/`** | PHP **Slim** app: `/api/config/dashboard`, auth, optional **self-service** APIs, alias proxies. Reads the **same** `adn-mon.yaml` via **`ADN_CONFIG_PATH`**. | +| **`frontend/`** | React (Vite): dashboard UI; consumes backend API + WebSocket. | +| **`proxy/`** | Python (Twisted): UDP **hotspot proxy**; forwards Homebrew between hotspots and the peer server; reads **`Clients`** in MySQL for **RPTO** options (self-service). | + +## Single configuration file + +**`adn-mon.yaml`** (path often set with **`ADN_CONFIG_PATH`** in `.env`) is shared by: + +- Python monitor (`monitor.py`) +- PHP backend (`backend/public/index.php`) +- Hotspot proxy (`proxy/proxy.py`) + +So **one** YAML drives reporting addresses, dashboard strings, WebSocket port, **SELF_SERVICE** DB credentials, and **PROXY** listen/range settings. + +## Link to the peer server + +| Server (`adn-server.yaml`) | Monitor (`adn-mon.yaml`) | +|----------------------------|---------------------------| +| **`REPORTS.REPORT_CLIENTS`** — list of IPs allowed to connect **to** the report listener, or the monitor host | **`ADN_CONNECTION.ADN_IP`** / **`ADN_PORT`** — where the **monitor connects** (must match the server’s report bind and port). | +| **`REPORTS.REPORT_PORT`** — TCP port the **server listens on** for incoming report connections | Same port as **`ADN_PORT`**. | + +See [Monitoring and reports](../server/user-guide/monitoring.md) for report opcodes and [Monitor configuration](configuration.md) for every `adn-mon.yaml` section. + +## See also + +- [Hotspot proxy](hotspot-proxy.md) — `PROXY`, peer server port range, how the process loads config and runs +- [Architecture and deployment](architecture.md) +- [Configuration (`adn-mon.yaml`)](configuration.md) +- [Self-service](self-service.md) diff --git a/docs/en/monitor/self-service.md b/docs/en/monitor/self-service.md new file mode 100644 index 0000000..1a7ffec --- /dev/null +++ b/docs/en/monitor/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) diff --git a/docs/en/server/contributing/translations.md b/docs/en/server/contributing/translations.md new file mode 100644 index 0000000..2ae11b9 --- /dev/null +++ b/docs/en/server/contributing/translations.md @@ -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. diff --git a/docs/en/server/development/architecture.md b/docs/en/server/development/architecture.md new file mode 100644 index 0000000..6738f4f --- /dev/null +++ b/docs/en/server/development/architecture.md @@ -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. diff --git a/docs/en/server/development/behaviour-and-timers.md b/docs/en/server/development/behaviour-and-timers.md new file mode 100644 index 0000000..6b679a0 --- /dev/null +++ b/docs/en/server/development/behaviour-and-timers.md @@ -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. diff --git a/docs/en/server/protocols/dmre-v5.md b/docs/en/server/protocols/dmre-v5.md new file mode 100644 index 0000000..c74ce53 --- /dev/null +++ b/docs/en/server/protocols/dmre-v5.md @@ -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. diff --git a/docs/en/server/protocols/hbp.md b/docs/en/server/protocols/hbp.md new file mode 100644 index 0000000..1f0311a --- /dev/null +++ b/docs/en/server/protocols/hbp.md @@ -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. diff --git a/docs/en/server/protocols/openbridge.md b/docs/en/server/protocols/openbridge.md new file mode 100644 index 0000000..9899d72 --- /dev/null +++ b/docs/en/server/protocols/openbridge.md @@ -0,0 +1,70 @@ +# OpenBridge (FreeBridge / DMRE) + +## DMRE and “OpenBridge v5” + +On the wire, extended OpenBridge uses the **`DMRE`** opcode. The **embedded protocol version** byte inside the frame (see [DMRE v5 layout](dmre-v5.md)) selects the layout: **version > 4** is the **89-byte v5** format (hops, source repeater field, BLAKE2b MAC). In documentation and operator discussions, **“DMRE v5”** and **“OpenBridge v5”** refer to the same thing: **DMRE frames with embedded version 5** (not the older short DMRD-only path). + +**Recommendation (ADN Systems network):** All inter-server links that participate in the **ADN Systems** mesh should use **DMRE v5** (`PROTO_VER: 5` in YAML, which sets the negotiated **VER** / embedded version) and **`ENHANCED_OBP: true`** so **BCSQ**, **BCKA**, and multi-path loop control behave consistently. **Peers** (other servers) should be configured the same way. **DMRD v1** (HMAC-only) remains supported for interoperability with older stacks, but it is **not** the preferred mode for new ADN deployments. + +## What OpenBridge is + +**OpenBridge** is a UDP protocol between **servers** (and some gateways). It carries DMR voice using: + +- **`DMRD`** version 1 — HMAC-SHA1 authenticated payload (legacy interop); or +- **`DMRE`** — extended frame with **BLAKE2b** MAC, embedded version, timestamps, **hops**, source server/repeater IDs, etc. (**DMRE v5** = embedded version 5, recommended above). + +This stack implements the **OPENBRIDGE** peer mode in **`udp_hbp.py`** and bridge routing in **`BridgeUseCases`**. + +## Ingress (DMRE) + +1. Verify **BLAKE2b** over the authenticated prefix. +2. Check **NETWORK_ID**, **TARGET** socket / `RELAX_CHECKS`, **slot** (TS1 for OBP ingress). +3. Increment **hops**; if **> 10**, drop and optionally send **BCSQ**. +4. Rebuild a pseudo-**DMRD** for the bridge and call **`dmrd_received`** with hop metadata. + +## Egress (`send_system`) + +- Build **DMRE** or **DMRD** v1 depending on negotiated **VER** and embedded version. +- Preserve **hops**, **BER/RSSI**, **source_server** / **source_rptr** as required for interop. + +## Loop control (multi-path mesh) + +For **group** voice on OpenBridge: + +1. **Finished** / **timeout 180 s** — drop stale streams. +2. **Echo HBP** — if a non-OBP system already has this `stream_id` in RX, this OBP path is treated as echo. +3. **Multiple OBP** — among OBP legs with the same `stream_id` and TG, **only the earliest `1ST` time** (`min(perf_counter)` ) **forwards**; others stop and may send **BCSQ** if **`ENHANCED_OBP`** is true. + +## BCSQ (Bridge Control — Source Quench) + +- **Meaning:** “Do not forward this **`stream_id`** on this **TG** to my leg.” +- **Sent by** the **losing** OBP in loop control to its **peer** (not a global broadcast). +- **Honoured** when **forwarding** to another OBP: if the destination’s `_bcsq` matches, **skip** `send_system`. + +## BCKA (keepalive) + +- **ENHANCED_OBP** — if peer keepalive is stale, block forward until refreshed. + +## Bridge forwarding (`to_target`) + +Per destination row: dedupe `(SYSTEM, TS)`, check **BCSQ**, **BCKA**, **ACL**, rewrite **LC** / **TGID**, force **TS1** bit pattern for OBP, call **`send_system`**. + +## Ingress filters (group TG) + +For **group** (and **vcsbk**) traffic, OpenBridge applies **TG filters** before traffic reaches the bridge router. Dropped streams may trigger **BCSQ** back to the peer. The exact rules differ between **DMRD v1** and **DMRE** paths in `udp_hbp.py`; in general they reject traffic that is treated as **local-only** or **wrong server** for the destination, for example: + +- Low TG numbers (e.g. **≤ 79** on DMRE; DMRD v1 also combines **9990–9999**, **92–199**, **900999** in one check). +- **9990–9999** and **900999** (service / local-server ranges on DMRE). +- **92–199** unless the **source server** ID matches your server’s main ID (DMRE). +- **80–89** and **800–899** unless the **MCC** prefix matches (DMRE). + +**Private (unit)** calls are not subject to the same group-TG filter block in the same way; configure **ACL** separately. + +Operator-oriented summary: [Special numbers — OpenBridge ingress](../user-guide/special-numbers.md#openbridge-ingress--group-tg-filters). + +## Monitor events (adn-monitor) + +- **INGRESS** — debug-only first sight per leg. +- **START** — canonical **after** loop win (dashboard state). + +See also: [DMRE v5 layout](dmre-v5.md), [Monitoring](../user-guide/monitoring.md). diff --git a/docs/en/server/user-guide/attribution.md b/docs/en/server/user-guide/attribution.md new file mode 100644 index 0000000..e3d3002 --- /dev/null +++ b/docs/en/server/user-guide/attribution.md @@ -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) diff --git a/docs/en/server/user-guide/bridges-and-talkgroups.md b/docs/en/server/user-guide/bridges-and-talkgroups.md new file mode 100644 index 0000000..48479ab --- /dev/null +++ b/docs/en/server/user-guide/bridges-and-talkgroups.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). diff --git a/docs/en/server/user-guide/configuration.md b/docs/en/server/user-guide/configuration.md new file mode 100644 index 0000000..f45835b --- /dev/null +++ b/docs/en/server/user-guide/configuration.md @@ -0,0 +1,219 @@ +# Configuration + +## Files and workflow + +| File | Committed | Role | +|------|-----------|------| +| `adn-server.example.yaml` | Yes | Template — copy to `adn-server.yaml` and edit. | +| `adn-server.yaml` | **No** (gitignored) | Main server: systems, globals, logging, aliases, reports. | +| `adn-voice.example.yaml` | Yes | Template for voice — copy to `adn-voice.yaml`. | +| `adn-voice.yaml` | **No** (typical) | Voice/TTS/recording; merged into `config["VOICE"]` at startup and **hot-reloaded** (~every 15 s) if the file changes. | + +Run: + +```bash +python adn-server.py -c /path/to/adn-server.yaml +``` + +Optional: `--logging LEVEL` overrides `LOGGER.LOG_LEVEL`. + +If `adn-voice.yaml` sits next to `adn-server.yaml`, it is loaded automatically. You can also put a `VOICE:` block inside `adn-server.yaml`; the separate file is the usual way to change announcements without touching the main config. + +**Secrets:** Never commit real passphrases, security URLs, or `user_passwords.json` / `encryption_key.secret`. Use placeholders in templates and keep production files local. + +--- + +## Architecture: what is a “system”? + +Each entry under **`SYSTEMS`** is a named **logical link** (UDP endpoint) that speaks **HBP** (HomeBrew Protocol) to hotspots/repeaters, or **OpenBridge** to other servers. Names are free-form strings (`SYSTEM`, `ECHO`, `OBP-UK`, …) and are used in logs and in the **bridge table** (`BRIDGES`) to identify where traffic enters or leaves. + +Three **modes** exist: + +| Mode | Typical use | Listens | Connects upstream | +|------|-------------|---------|-------------------| +| **MASTER** | Conference server for one or more hotspots/repeaters | **Yes** — `IP` / `PORT`, peers register with passphrase | No (peers connect to you) | +| **PEER** | Hotspot/repeater or service (e.g. parrot) behaving as a **client** of a MASTER | **Yes** — local `IP` / `PORT` | **Yes** — `MASTER_IP` / `MASTER_PORT` must point at a MASTER | +| **OPENBRIDGE** | Link to another **server** over OpenBridge (DMRD v1 / DMRE) | **Yes** — `IP` / `PORT` | **Yes** — `TARGET_IP` / `TARGET_PORT` (peer server) | + +**MASTER** holds the **`PEERS`** table at runtime (hotspots that authenticated). **PEER** maintains **STATS** (connection, pings). **OPENBRIDGE** uses **NETWORK_ID**, **PASSPHRASE**, **TARGET_***, **PROTO_VER** / **VER**, and optional **ENHANCED_OBP**, **RELAX_CHECKS**, **TGID_ACL**. + +A single process can run **several** systems at once (e.g. one MASTER for users, one ECHO for parrot, one OBP to a partner network). + +--- + +## `GLOBAL` + +Server-wide defaults. Many keys can be overridden per system if `USE_ACL` (or similar) is set on that system. + +| Key | Meaning | +|-----|---------| +| **PING_TIME** | Interval (seconds) for PEER keepalive / ping logic toward MASTER. | +| **MAX_MISSED** | How many missed pings before treating the PEER link as unhealthy (depends on implementation with STATS). | +| **USE_ACL** | If true, **REG_ACL**, **SUB_ACL**, **TGID_TS1_ACL**, **TGID_TS2_ACL** are applied (after processing into internal tuples). | +| **REG_ACL** | Access control for **registration** / peer IDs (`PERMIT:…` / `DENY:…`; see [ACL strings](#acl-strings)). | +| **SUB_ACL** | ACL for **subscriber** (radio) IDs on received traffic. | +| **TGID_TS1_ACL** | ACL for **talkgroup** on **timeslot 1**. | +| **TGID_TS2_ACL** | ACL for **talkgroup** on **timeslot 2**. | +| **GEN_STAT_BRIDGES** | If true, OpenBridge can trigger creation of **static** bridge rows for certain TGs (see [Bridges and talkgroups](bridges-and-talkgroups.md)). | +| **SERVER_ID** | Numeric server ID; stored as 4-byte value for OpenBridge / voice metadata. | +| **VALIDATE_SERVER_IDS** | If true (DMRE path), **source server** IDs may be checked against a downloaded list (`ALIASES` **SERVER_ID_**\*). | +| **URL_SECURITY** / **PORT_SECURITY** / **PASS_SECURITY** | If set, enables download of keys/password material from the security endpoint (see example comments). Empty = disabled. | +| **USERS_PASS** | Filename for per-radio password JSON (optional). | +| **HASH_ENCRYPT** | Path to encryption key for password file handling. | + +--- + +## `SYSTEMS` — common fields + +These appear mainly on **MASTER** (and often on **PEER**). OpenBridge uses a different subset. + +| Key | Meaning | +|-----|---------| +| **MODE** | `MASTER`, `PEER`, or `OPENBRIDGE`. | +| **ENABLED** | If `false`, the system is skipped. | +| **IP** / **PORT** | UDP bind address for this system’s HBP or OpenBridge listener. | +| **PASSPHRASE** | Shared secret for HBP authentication (MASTER ↔ PEER). Must match between a PEER and its MASTER. | +| **USE_ACL** | Per-system ACL override when true (uses system-level `REG_ACL` / `SUB_ACL` / `TGID_TS*_ACL`). | +| **GROUP_HANGTIME** | Hang time (seconds) for group voice state. | +| **DEFAULT_UA_TIMER** | Default timeout (minutes in many places) for **user-activated** bridges. | +| **ANNOUNCEMENT_LANGUAGE** | Default language folder under `Audio//` for prompts on this system. | +| **ALLOW_UNREG_ID** | Whether unregistered subscriber IDs are allowed (MASTER). | + +--- + +## `SYSTEMS` — MASTER + +| Key | Meaning | +|-----|---------| +| **REPEAT** | If true, received traffic can be **repeated** to other connected peers on the MASTER (typical conference behaviour). | +| **MAX_PEERS** | Maximum connected hotspots. | +| **EXPORT_AMBE** | Feature flag for AMBE export (if enabled in build). | +| **SINGLE_MODE** | Affects OPTIONS / generator expansion (single-user style). | +| **VOICE_IDENT** | Enables periodic **voice ident** when conditions are met (see `IdentUseCases`). | +| **TS1_STATIC** / **TS2_STATIC** | Comma-separated static TG lists pushed via OPTIONS handling (see `options_config`). | +| **DEFAULT_REFLECTOR** | Default **reflector** number for `#` dial bridges (0 = none). | +| **OVERRIDE_IDENT_TG** | Optional TG for voice ident instead of all-call. | +| **GENERATOR** | If **> 1**, this MASTER is expanded into **`NAME-0`**, **`NAME-1`**, … with consecutive ports (see `expand_generator` in code). | + +**MASTER** listens for PEER connections; each authenticated peer is stored under **`PEERS`** at runtime. + +--- + +## `SYSTEMS` — PEER + +A **PEER** connects **outbound** to a **MASTER** and listens locally for the radio or app. + +| Key | Meaning | +|-----|---------| +| **MASTER_IP** / **MASTER_PORT** | Address of the **MASTER** to register with (must match that MASTER’s `IP`/`PORT`). | +| **RADIO_ID** | This peer’s ID in HBP (4-byte). | +| **CALLSIGN**, **RX_FREQ**, **TX_FREQ**, **COLORCODE**, **LATITUDE**, … | RPT payload fields sent to the MASTER during registration (fixed widths in protocol). | +| **OPTIONS** | Byte string / options line (e.g. `TS2=9990;`) for static TG / behaviour. | +| **LOOSE** | Relaxed handling flag where applicable. | + +The **parrot** example (`adn-parrot.example.yaml`) is a PEER that attaches to the **ECHO** MASTER: same **PASSPHRASE**, **MASTER_PORT** = ECHO’s **PORT**. See [Parrot](parrot.md). + +--- + +## `SYSTEMS` — OPENBRIDGE + +| Key | Meaning | +|-----|---------| +| **NETWORK_ID** | Must match the peer’s **NETWORK_ID** in OpenBridge packets. | +| **TARGET_IP** / **TARGET_PORT** | Remote OpenBridge peer (UDP). | +| **TGID_ACL** (or **TG1_ACL**) | Talkgroup ACL for OpenBridge (often `DENY:0-82,…` style ranges). | +| **RELAX_CHECKS** | Allow packets when peer socket does not match `TARGET` strictly (use with care). | +| **ENHANCED_OBP** | Enables **BCSQ** / **BCKA** and multi-path loop control — **should be `true`** for ADN Systems inter-server links (see below). | +| **PROTO_VER** | Embedded **DMRE** protocol version; **`5`** selects **DMRE / OpenBridge v5** (89-byte frame, BLAKE2b). Default in code is **5**; use **5** for new ADN deployments. | + +**ADN Systems recommendation:** For every OpenBridge peer in the ADN mesh, set **`PROTO_VER: 5`** (DMRE v5) and **`ENHANCED_OBP: true`**. Align the same settings on **both** ends. Older **DMRD v1**-only peers are possible for backward compatibility but are not the recommended mode for the network. + +Ingress filters and loop control: [OpenBridge protocol](../protocols/openbridge.md) (including [DMRE vs OpenBridge v5](../protocols/openbridge.md#dmre-and-openbridge-v5)) and [Special numbers — OpenBridge ingress](special-numbers.md#openbridge-ingress--group-tg-filters). + +--- + +## `BRIDGES` (runtime) + +The **bridge table** maps TG keys to routing rows. It is **in memory** for the running process. + +The YAML loader does **not** load a top-level `BRIDGES:` block from `adn-server.yaml` into the router today — initial rows are created in code (e.g. **9990 / ECHO** bootstrap when an **ECHO** system exists), then **OPTIONS**, **user-activated** bridges, **static** bridges, and **OpenBridge** logic add rows over time. + +For the conceptual model (ACTIVE, TS, TGID, timeouts): [Bridges and talkgroups](bridges-and-talkgroups.md). + +--- + +## `REPORTS` + +TCP report channel for **adn-monitor** (or compatible dashboards). + +| Key | Meaning | +|-----|---------| +| **REPORT** | Enable/disable sending. | +| **REPORT_INTERVAL** | Periodic push interval (seconds). | +| **REPORT_PORT** | Local port the **server listens on** for report clients. | +| **REPORT_CLIENTS** | Comma-separated or list of allowed client IPs (see example). | + +Details: [Monitoring and reports](monitoring.md). + +--- + +## `LOGGER` + +Implemented in `infrastructure/logging_config.py` (`setup_logging`). Values are read from the **`LOGGER`** block (or overridden by `--logging` for **LOG_LEVEL** only). + +| Key | Meaning | +|-----|---------| +| **LOG_HANDLERS** | Comma-separated list of handler **tokens** (whitespace around commas is fine). Each token selects outputs; you can combine several. Recognised values: **`console-timed`** or **`console`** — log to **stderr** with format `LEVEL asctime message`; **`file-timed`** or **`file`** — log to **LOG_FILE** with the same format (UTF-8). **Default** if omitted: `console-timed`. Examples: `console-timed` only; `file-timed` only; `console-timed,file-timed` for both console and file. | +| **LOG_FILE** | Path used when **`file-timed`** or **`file`** is in **LOG_HANDLERS**. If missing, the code defaults to `/dev/null`. If the path is **`/dev/null`**, file handlers are **not** attached even if listed. If the file cannot be opened (permissions, missing directory), a warning is written to stderr and logging continues without that file handler. | +| **LOG_LEVEL** | Root logger level: **`DEBUG`**, **`INFO`**, **`WARNING`**, **`ERROR`**, **`CRITICAL`** (case-insensitive; default **INFO**). Unknown names fall back to **INFO**. You can override at startup with **`python adn-server.py --logging LEVEL`** (same names). A custom **`TRACE`** level is registered for occasional `logger.trace(...)` calls; use **`DEBUG`** for verbose diagnostics in normal operation. | +| **LOG_NAME** | Name of the logger returned to the application (default **`ADN`**). Does not change the list of handlers; it selects which named logger gets the configured level. | + +--- + +## `ALIASES` + +Downloads and local files for **peer IDs**, **subscriber IDs**, **talkgroup labels**, optional **server ID list**, **checksums**, and **keys**. Used for dashboards, validation, and optional security downloads. + +| Key | Meaning | +|-----|---------| +| **PATH** | Base directory for JSON/TSV/pickle files. | +| **TRY_DOWNLOAD** | Whether to fetch from URLs when stale. | +| **PEER_FILE** / **SUBSCRIBER_FILE** / **TGID_FILE** | Local filenames. | +| **\*_URL** | Remote sources for downloads. | +| **SUB_MAP_FILE** | Pickle path for **SUB_MAP** (private call routing); default name if empty. | +| **STALE_DAYS** | Refresh threshold for downloads. | + +--- + +## `VOICE` (from `adn-voice.yaml` or inline) + +Merged into `config["VOICE"]`. See [Voice, announcements, and TTS](voice-and-tts.md) and `adn-voice.example.yaml`. + +--- + +## ACL strings + +Processed by `acl_build`: `PERMIT:` or `DENY:` followed by comma-separated IDs or ranges. + +Examples: + +- `PERMIT:ALL` — allow all IDs in range. +- `DENY:1` — deny ID 1 only. +- `DENY:0-82,9990-9999` — deny listed ranges. + +Global ACLs apply when `USE_ACL` is true; OpenBridge may use **TGID_ACL** on the OBP system. + +--- + +## Python environment + +Use the project interpreter (see workspace rules), e.g. `python3.11` from pyenv, for consistent behaviour with production. + +--- + +## See also + +- [Introduction](introduction.md) — role of the server. +- [Bridges and talkgroups](bridges-and-talkgroups.md) — `BRIDGES` semantics. +- [Special numbers](special-numbers.md) — reserved TGs and server IDs. +- [Parrot](parrot.md) — PEER example (parrot process). diff --git a/docs/en/server/user-guide/introduction.md b/docs/en/server/user-guide/introduction.md new file mode 100644 index 0000000..f412882 --- /dev/null +++ b/docs/en/server/user-guide/introduction.md @@ -0,0 +1,36 @@ +# Introduction + +## Purpose + +This service is a **DMR peer and bridge**. It implements: + +- **HBP** over UDP to **MASTER** and **PEER** systems (DMRD frames, authentication, pings). +- **OpenBridge** over UDP to other networks — **DMRE v5** (embedded version 5, BLAKE2b, hops) is the **recommended** inter-server mode on ADN; **DMRD** v1 remains for legacy interop (see [OpenBridge](../protocols/openbridge.md#dmre-and-openbridge-v5)). + +Configuration is **YAML** (`adn-server.yaml`), merged at runtime with optional voice settings (`adn-voice.yaml`). The shipped template is `adn-server.example.yaml`. + +## Design + +Routing, timers, OpenBridge loop control, and protocol handling are implemented in **application** and **infrastructure** modules behind stable **ports**; the **domain** layer holds types and rules without I/O. This keeps the system easier to reason about and extend. + +## Major subsystems + +| Subsystem | Role | +|-----------|------| +| **Bridge router** | `BRIDGES` table: which systems forward which TG on which slot; dynamic bridges; static/stat bridges. | +| **HBP protocol** | Authentication, DMRD ingress/egress, repeat to peers, TG filters. | +| **OpenBridge** | DMRE ingress, hop limit, loop control (`min(1ST)`), BCSQ/BCKA when enabled. | +| **Voice** | AMBE files, scheduled announcements, TTS pipeline, on-demand playback (TG 9991–9999). | +| **Reporting** | TCP netstring channel to **adn-monitor** (and compatible dashboards): config, bridge state, `BRDG_EVENT` call events. | + +## Related programs + +- **Parrot / playback** — separate entrypoint (`adn-parrot.py`) for record-and-playback; see [Parrot](parrot.md). + +## Next steps + +- [Configuration](configuration.md) — files, `GLOBAL`, **MASTER** / **PEER** / **OPENBRIDGE**, ACLs, reports, aliases, voice merge. +- [Bridges and talkgroups](bridges-and-talkgroups.md) — how `BRIDGES` works. +- [Special numbers](special-numbers.md) — TG 4000, information services, echo. +- [ADN Monitor](../../monitor/index.md) — dashboard, `adn-mon.yaml`, self-service (separate repo, deployed with the server). +- [Credits & license](attribution.md) — ADN → FreeDMR → hblink3, license. diff --git a/docs/en/server/user-guide/monitoring.md b/docs/en/server/user-guide/monitoring.md new file mode 100644 index 0000000..b4a138c --- /dev/null +++ b/docs/en/server/user-guide/monitoring.md @@ -0,0 +1,29 @@ +# Monitoring and reports + +## TCP report channel + +When **`REPORTS`** is enabled in the server config, the **ADN DMR Peer Server** listens on TCP and **report clients** (typically **adn-monitor**) connect and receive: + +- **CONFIG_SND** / **BRIDGE_SND** — pickled snapshots of systems and bridges. +- **BRDG_EVENT** — text events for calls (`GROUP VOICE`, `PRIVATE VOICE`, etc.). + +The **monitor** decodes these messages, updates its **CTABLE** / **BTABLE**, and (when MySQL is configured) persists Last Heard / statistics. + +**Full stack:** [ADN Monitor overview](../../monitor/index.md) (Python monitor, WebSocket, PHP API, optional proxy and self-service). + +## OpenBridge monitor semantics + +- **`GROUP VOICE,INGRESS,RX`** — first sight of a stream on an OpenBridge **leg** (debug; full visibility in logs). +- **`GROUP VOICE,START,RX`** — **canonical** start after **loop control** (feeds dashboard chips / CTABLE). +- **`GROUP VOICE,END,…`** — call end; RX/TX variants depending on direction. + +The dashboard shows **operational** state from **START** (canonical); the **Monitor** log shows **INGRESS** plus **START** for troubleshooting mesh duplicates. + +## Requirements + +- Network reachability from the **monitor host** to the server’s **`REPORTS.REPORT_PORT`** (and the server’s **`REPORT_CLIENTS`** allow list must include the monitor if used). +- **adn-monitor** `ADN_CONNECTION.ADN_IP` / **`ADN_PORT`** must match the server — see [Monitor configuration](../../monitor/configuration.md#adn_connection). + +## Self-service and hotspots + +Operators editing **device options** from the dashboard use the **self-service** flow (MySQL **`Clients`**, proxy **RPTO**). That is documented under [Self-service](../../monitor/self-service.md); it is **not** part of the peer server binary alone. For **hotspot proxy** configuration (`PROXY` in `adn-mon.yaml`), how it binds to the peer server **UDP port range**, and how the process starts, see [Hotspot proxy](../../monitor/hotspot-proxy.md). diff --git a/docs/en/server/user-guide/parrot.md b/docs/en/server/user-guide/parrot.md new file mode 100644 index 0000000..6214f2f --- /dev/null +++ b/docs/en/server/user-guide/parrot.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. diff --git a/docs/en/server/user-guide/private-calls.md b/docs/en/server/user-guide/private-calls.md new file mode 100644 index 0000000..3f9ecf5 --- /dev/null +++ b/docs/en/server/user-guide/private-calls.md @@ -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`). diff --git a/docs/en/server/user-guide/special-numbers.md b/docs/en/server/user-guide/special-numbers.md new file mode 100644 index 0000000..8c538ed --- /dev/null +++ b/docs/en/server/user-guide/special-numbers.md @@ -0,0 +1,103 @@ +# Special numbers (TG / IDs) + +Several **destination IDs** are reserved for **control or services**. They are handled in protocol layers and/or the bridge router, not as normal group traffic. + +## ID 5000 — server voice source (not “announcement TG”) {#id-5000--server-voice-source-not-announcement-tg} + +**Important:** **5000** is the **RF source ID** the server uses when it **transmits** automated voice. Radios and dashboards show **caller ID 5000** for that traffic. + +| Traffic | Destination in the DMR packet | Notes | +|---------|----------------------------------|--------| +| **Scheduled AMBE** (`ANNOUNCEMENTS`) | Whatever **`TG`** you set in `adn-voice.yaml` | Source ID **5000**. | +| **TTS** (`TTS_ANNOUNCEMENTS`) | Same — configured **`TG`** | Source ID **5000**. | +| **On-demand** (TG **9991–9999**) | **TG 9** | Short info clips; source **5000** (see [Voice, announcements, and TTS](voice-and-tts.md)). | +| **Disconnected / reflector prompts** | **TG 9** | Source **5000**. | +| **Voice ident** | **All-call** (`16777215`) or **`OVERRIDE_IDENT_TG`** if set | Source **5000**. | + +You do **not** “monitor TG 5000” to hear scheduled announcements: you monitor the **configured announcement TG** (e.g. 2, 9, 26811). **5000** appears as the **transmitter ID** on those calls. + +### Destination TG 5000 (inbound group) + +If a group call arrives with **destination TG 5000** and there is **no** existing `BRIDGES` entry, the server does **not** auto-create a user-activated bridge for that TG (same class as IDs **0–4**, **9**, **4000**). To carry traffic to TG 5000 you need an **explicit** bridge row. + +## TG 9 — local service lane (prompts and bridge plumbing) {#tg-9-local-service-lane-prompts-and-bridge-plumbing} + +**TG 9** is used in two different ways: what **operators hear** from short server prompts, and how **internal bridge rows** are wired. + +### What you hear (outbound server voice) + +For **on-demand** playback (after you key **9991–9999**) and for **disconnected / reflector** voice lines, the server transmits **group** packets with: + +- **Source ID 5000** +- **Destination TG 9** +- **Timeslot 2** (the code drives the **TS2** slot for that hotspot) + +That follows a common **HomeBrew / conference** convention: keep short **local service** audio on **TG 9 / TS2** so it stays separate from normal QSO traffic on your main TG (often on TS1). Hotspots must pass **TS2** and be on a configuration where **TG 9** is not blocked, or those prompts will not be heard. + +**Red vs local:** On **OpenBridge**, **inbound group** traffic to **TG 9** is in the **≤ 79** “local / repeater” range and is **not** brought into the bridge from the IP mesh (it is dropped at ingress). Server prompts use the **local HBP path** to the hotspot/repeater, not a wide-area bridged TG. Exception: only if you **explicitly** add `BRIDGES` rows that forward TG 9 could that traffic be sent elsewhere — not the default. + +**Scheduled** announcements and **TTS** use the **`TG`** you set in `adn-voice.yaml` — they are **not** forced to TG 9 unless you configure that TG yourself. + +### Router behaviour (reserved TG) + +- **No automatic user-activated bridge** if someone transmits to **TG 9** and no `BRIDGES` row exists (same class as **0–4**, **4000**, **5000**, etc.). +- With **`GEN_STAT_BRIDGES`**, automatic **STAT** bridge creation from OpenBridge does **not** apply to destination **TG 9** (it is excluded on purpose). +- The **bridge debug** loop removes invalid conference bridges keyed **`9`** (and **`0`–`8`**) so single-digit stray bridges do not accumulate. + +### Bridge table (advanced) + +Many **reflector / dial** rows store **`TGID` = 9** on **TS2** as the **leg destination** used internally to attach the dynamic path to the real talkgroup — that is wiring inside `BRIDGES`, not something you “call” like a normal national TG. + +## TG / ID 4000 — deactivate dynamic bridges + +**Purpose:** Clear **user-activated (dynamic) bridges** for the system that receives the call. + +**Behaviour:** + +- Implemented for **group** traffic to destination **4000** (and related checks in the router). +- Runs **before** normal TG ACL in the OpenBridge path so the command is not blocked by allow lists. +- Invokes **`deactivate_all_dynamic_bridges`**: deactivates non-stat, non-reflector dynamic bridge rows. + +Use this when operators need to **reset** dynamic routing without restarting the server. + +## TG 9991–9999 — information / on-demand audio + +**Purpose:** **Play back** pre-generated AMBE (“ondemand”) files (e.g. station info, help). + +**Behaviour:** + +- Triggers **`playFileOnRequest`**-style handling: maps the last digits to a file name under the configured audio tree. +- Works from **MASTER** and **PEER** paths. + +The **audio** is sent with **source ID 5000** and **destination TG 9** in the generated stream. File layout: [Voice, announcements, and TTS](voice-and-tts.md). + +## TG 9990 — echo / parrot (in-band) + +**Purpose:** Bridge rows for **echo** often use **9990** with the **ECHO** system (see `BRIDGES` and options in your YAML). + +**Note:** A **standalone parrot** is also available as a separate process — [Parrot](parrot.md). + +## Private call to ID 4000 + +A **unit** call to **4000** is treated as **disconnect dynamics** only; it is **not** routed as a normal private call. + +## OpenBridge ingress — group TG filters {#openbridge-ingress--group-tg-filters} + +On **OpenBridge** (**DMRD** v1 and **DMRE**), **non-unit** group traffic to certain TGs may be **dropped** before bridging (with **BCSQ** where applicable). Rules differ slightly between DMRD and DMRE; they include low-number TGs (e.g. **≤ 79**), **9990–9999**, **900999**, ranges **92–199** (vs source server), and MCC-related ranges (**80–89**, **800–899**). Details: [OpenBridge protocol](../protocols/openbridge.md#ingress-filters-group-tg). + +## Prohibited / reserved TGs (bridge creation) + +Many small IDs (0–5, 9, etc.) and the **999x service** range are excluded from certain **automatic** bridge-creation paths; **5000** and **4000** are in the “no auto UA bridge” set when no row exists. Exact sets are defined in the bridge router and options handling in source. + +## Summary table + +| ID / range | Role | +|------------|------| +| **5000** (source) | Server-generated voice (announcements, TTS, prompts, ident) — **caller ID** on receivers | +| **5000** (destination) | No auto user-activated bridge if missing from `BRIDGES` | +| **4000** (group) | Deactivate dynamic bridges | +| **4000** (unit) | Disconnect dynamics; not routed as PC | +| **9991–9999** | On-demand / information audio (trigger TG); playback uses src **5000** → TG **9** (TS2) | +| **9** | Service/prompt lane (short server audio); reserved for auto-bridges; internal **TGID** on TS2 legs | +| **9990** | Echo bridge TG (with ECHO system) | +| **16777215** | All-call (default voice-ident destination unless overridden) | diff --git a/docs/en/server/user-guide/voice-and-tts.md b/docs/en/server/user-guide/voice-and-tts.md new file mode 100644 index 0000000..2845cc3 --- /dev/null +++ b/docs/en/server/user-guide/voice-and-tts.md @@ -0,0 +1,56 @@ +# Voice, announcements, and TTS + +## Configuration files + +- **`adn-voice.yaml`** (optional, not committed) — merged into `config["VOICE"]`. +- Hot reload on file **mtime** (~15 s loop). + +Template: `adn-voice.example.yaml`. + +## Server voice identity (ID 5000) + +All **server-originated** voice uses **RF source ID 5000** in the DMR stream so clients can tell infrastructure traffic from user radios: + +- Scheduled **ANNOUNCEMENTS** and **TTS_ANNOUNCEMENTS** (destination = the **`TG`** field in each item). +- **On-demand** clips triggered by dialling **9991–9999** (playback uses destination **TG 9** on **TS2**; you still key **999x** to request the file). +- **Disconnected / reflector** voice lines (same: **TG 9** / **TS2**). + +See [TG 9 — local service lane](special-numbers.md#tg-9-local-service-lane-prompts-and-bridge-plumbing) for why TG 9 is used and what must be enabled on the hotspot. +- **Voice ident** (destination **all-call** or **`OVERRIDE_IDENT_TG`**). + +See [Special numbers — ID 5000](special-numbers.md#id-5000--server-voice-source-not-announcement-tg) for the full table. + +## Features + +| Feature | Description | +|---------|-------------| +| **Scheduled announcements** | AMBE phrases from `Audio//ondemand/` on a schedule (hourly / interval). | +| **TTS announcements** | Text → gTTS → MP3 → ffmpeg → WAV → vocoder → AMBE (see pipeline below). | +| **Voice ident** | Periodic identification (optional); uses `VOICE_IDENT` in main server config and `OVERRIDE_IDENT_TG` / all-call. | +| **Recording** | When enabled, records AMBE to disk for traffic on **`RECORDING_TG`** / **`RECORDING_TIMESLOT`** (see template `adn-voice.example.yaml`). | +| **On-demand (9991–9999)** | See [Special numbers](special-numbers.md). | + +## TTS pipeline (summary) + +1. Source text in `.txt` under `Audio//ondemand/`. +2. **gTTS** produces **MP3**. +3. **ffmpeg** converts to **WAV**. +4. Vocoder command or **AMBEServer** produces **AMBE** for transmission. + +**ffmpeg** must be installed on the host OS. + +Configure **`TTS_VOCODER_CMD`** or **`TTS_AMBESERVER_HOST`** / **`TTS_AMBESERVER_PORT`** in `adn-voice.yaml` for encoding; see comments in `adn-voice.example.yaml`. + +## Anti-collision (QSO) with announcements + +When scheduling announcements, the server may **wait** if target slots are busy, **drop** targets if a live QSO appears mid-stream, and only mark **hourly** announcement state after a successful target list — this avoids clobbering live traffic. + +## Broadcast queue + +Parallel **broadcasts** on **different** TGs may run concurrently; **same-TG** broadcasts are **serialised** so one announcement completes before another on that TG. + +## See also + +- [Configuration](configuration.md) — voice file paths. +- [Parrot](parrot.md) — separate playback service. +- [Special numbers](special-numbers.md) — 5000, 999x, recording-related behaviour. diff --git a/docs/es/README.md b/docs/es/README.md new file mode 100644 index 0000000..977af58 --- /dev/null +++ b/docs/es/README.md @@ -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). diff --git a/docs/es/monitor/architecture.md b/docs/es/monitor/architecture.md new file mode 100644 index 0000000..456fb52 --- /dev/null +++ b/docs/es/monitor/architecture.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) diff --git a/docs/es/monitor/configuration.md b/docs/es/monitor/configuration.md new file mode 100644 index 0000000..a922257 --- /dev/null +++ b/docs/es/monitor/configuration.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`**). diff --git a/docs/es/monitor/hotspot-proxy.md b/docs/es/monitor/hotspot-proxy.md new file mode 100644 index 0000000..8841730 --- /dev/null +++ b/docs/es/monitor/hotspot-proxy.md @@ -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. diff --git a/docs/es/monitor/index.md b/docs/es/monitor/index.md new file mode 100644 index 0000000..34948a7 --- /dev/null +++ b/docs/es/monitor/index.md @@ -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) diff --git a/docs/es/monitor/self-service.md b/docs/es/monitor/self-service.md new file mode 100644 index 0000000..af50c81 --- /dev/null +++ b/docs/es/monitor/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) diff --git a/docs/es/server/contributing/translations.md b/docs/es/server/contributing/translations.md new file mode 100644 index 0000000..885877e --- /dev/null +++ b/docs/es/server/contributing/translations.md @@ -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. diff --git a/docs/es/server/development/architecture.md b/docs/es/server/development/architecture.md new file mode 100644 index 0000000..45fe416 --- /dev/null +++ b/docs/es/server/development/architecture.md @@ -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. diff --git a/docs/es/server/development/behaviour-and-timers.md b/docs/es/server/development/behaviour-and-timers.md new file mode 100644 index 0000000..dda843f --- /dev/null +++ b/docs/es/server/development/behaviour-and-timers.md @@ -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. diff --git a/docs/es/server/protocols/dmre-v5.md b/docs/es/server/protocols/dmre-v5.md new file mode 100644 index 0000000..176ba58 --- /dev/null +++ b/docs/es/server/protocols/dmre-v5.md @@ -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. diff --git a/docs/es/server/protocols/hbp.md b/docs/es/server/protocols/hbp.md new file mode 100644 index 0000000..c55fa52 --- /dev/null +++ b/docs/es/server/protocols/hbp.md @@ -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`. diff --git a/docs/es/server/protocols/openbridge.md b/docs/es/server/protocols/openbridge.md new file mode 100644 index 0000000..7906493 --- /dev/null +++ b/docs/es/server/protocols/openbridge.md @@ -0,0 +1,70 @@ +# OpenBridge (FreeBridge / DMRE) + +## DMRE y «OpenBridge v5» {#dmre-and-openbridge-v5} + +En cable, OpenBridge extendido usa el opcode **`DMRE`**. El **byte de versión de protocolo embebido** dentro de la trama (ver [disposición DMRE v5](dmre-v5.md)) selecciona el diseño: **versión > 4** es el formato **v5 de 89 bytes** (saltos, campo repetidor fuente, MAC BLAKE2b). En documentación y conversaciones de operadores, **«DMRE v5»** y **«OpenBridge v5»** son lo mismo: **tramas DMRE con versión embebida 5** (no el camino corto solo DMRD antiguo). + +**Recomendación (red ADN Systems):** todos los enlaces inter-servidor que participen en la malla **ADN Systems** deberían usar **DMRE v5** (`PROTO_VER: 5` en YAML, que fija **VER** / versión embebida negociada) y **`ENHANCED_OBP: true`** para que **BCSQ**, **BCKA** y el control de bucle multipath se comporten igual. Los **pares** (otros servidores) deben configurarse igual. **DMRD v1** (solo HMAC) sigue soportado para interoperar con pilas antiguas, pero **no** es el modo preferido para nuevos despliegues ADN. + +## Qué es OpenBridge + +**OpenBridge** es un protocolo UDP entre **servidores** (y algunos gateways). Transporta voz DMR usando: + +- **`DMRD`** versión 1 — carga autenticada HMAC-SHA1 (interop heredada); o +- **`DMRE`** — trama extendida con MAC **BLAKE2b**, versión embebida, marcas de tiempo, **saltos**, IDs servidor/repetidor de origen, etc. (**DMRE v5** = versión embebida 5, recomendada arriba). + +Esta pila implementa el modo par **OPENBRIDGE** en **`udp_hbp.py`** y el enrutado de bridges en **`BridgeUseCases`**. + +## Ingreso (DMRE) + +1. Verificar **BLAKE2b** sobre el prefijo autenticado. +2. Comprobar **NETWORK_ID**, socket **TARGET** / `RELAX_CHECKS`, **slot** (TS1 para ingreso OBP). +3. Incrementar **saltos**; si **> 10**, descartar y opcionalmente enviar **BCSQ**. +4. Reconstruir un pseudo-**DMRD** para el bridge y llamar **`dmrd_received`** con metadatos de salto. + +## Egreso (`send_system`) + +- Construir **DMRE** o **DMRD** v1 según **VER** negociada y versión embebida. +- Preservar **saltos**, **BER/RSSI**, **source_server** / **source_rptr** según haga falta para interoperar. + +## Control de bucle (malla multipath) + +Para voz de **grupo** en OpenBridge: + +1. **Finalizado** / **timeout 180 s** — descartar flujos obsoletos. +2. **Eco HBP** — si un sistema no-OBP ya tiene este `stream_id` en RX, esta pata OBP se trata como eco. +3. **Varios OBP** — entre patas OBP con el mismo `stream_id` y TG, **solo el `1ST` más temprano** (`min(perf_counter)`) **reenvía**; las demás paran y pueden enviar **BCSQ** si **`ENHANCED_OBP`** es true. + +## BCSQ (Bridge Control — Source Quench) + +- **Significado:** «No reenvíes este **`stream_id`** en esta **TG** hacia mi pata.» +- **Lo envía** el OBP **perdedor** en control de bucle hacia su **par** (no es broadcast global). +- **Se respeta** al reenviar a otro OBP: si el `_bcsq` del destino coincide, **omitir** `send_system`. + +## BCKA (keepalive) + +- **ENHANCED_OBP** — si el keepalive del par está obsoleto, bloquear reenvío hasta que se refresque. + +## Reenvío de bridge (`to_target`) + +Por fila de destino: deduplicar `(SYSTEM, TS)`, comprobar **BCSQ**, **BCKA**, **ACL**, reescribir **LC** / **TGID**, forzar patrón de bit **TS1** para OBP, llamar **`send_system`**. + +## Filtros de ingreso (TG de grupo) {#ingress-filters-group-tg} + +Para tráfico de **grupo** (y **vcsbk**), OpenBridge aplica **filtros TG** antes de que el flujo llegue al router de bridges. Los flujos descartados pueden disparar **BCSQ** hacia el par. Las reglas exactas difieren entre **DMRD v1** y **DMRE** en `udp_hbp.py`; en general rechazan tráfico tratado como **solo local** o **servidor incorrecto** para el destino, por ejemplo: + +- TG bajas (p. ej. **≤ 79** en DMRE; DMRD v1 también combina **9990–9999**, **92–199**, **900999** en una comprobación). +- **9990–9999** y **900999** (rangos de servicio / servidor local en DMRE). +- **92–199** salvo que el ID de **servidor de origen** coincida con el ID principal de tu servidor (DMRE). +- **80–89** y **800–899** salvo que el prefijo **MCC** coincida (DMRE). + +Las llamadas **privadas (unitarias)** no están sujetas al mismo bloqueo de filtro TG de grupo de la misma forma; configura **ACL** por separado. + +Resumen orientado al operador: [Números especiales — ingreso OpenBridge](../user-guide/special-numbers.md#openbridge-ingress--group-tg-filters). + +## Eventos de monitor (adn-monitor) + +- **INGRESS** — primera vista de depuración por pata. +- **START** — canónico **después** de ganar el bucle (estado del panel). + +Ver también: [disposición DMRE v5](dmre-v5.md), [Monitor](../user-guide/monitoring.md). diff --git a/docs/es/server/user-guide/attribution.md b/docs/es/server/user-guide/attribution.md new file mode 100644 index 0000000..bcf135b --- /dev/null +++ b/docs/es/server/user-guide/attribution.md @@ -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) diff --git a/docs/es/server/user-guide/bridges-and-talkgroups.md b/docs/es/server/user-guide/bridges-and-talkgroups.md new file mode 100644 index 0000000..50bc074 --- /dev/null +++ b/docs/es/server/user-guide/bridges-and-talkgroups.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). diff --git a/docs/es/server/user-guide/configuration.md b/docs/es/server/user-guide/configuration.md new file mode 100644 index 0000000..7849b0c --- /dev/null +++ b/docs/es/server/user-guide/configuration.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//` 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). diff --git a/docs/es/server/user-guide/introduction.md b/docs/es/server/user-guide/introduction.md new file mode 100644 index 0000000..ddc708f --- /dev/null +++ b/docs/es/server/user-guide/introduction.md @@ -0,0 +1,36 @@ +# Introducción + +## Propósito + +Este servicio es un **peer y bridge DMR**. Implementa: + +- **HBP** por UDP hacia sistemas **MASTER** y **PEER** (tramas DMRD, autenticación, pings). +- **OpenBridge** por UDP hacia otras redes — **DMRE v5** (versión embebida 5, BLAKE2b, saltos) es el modo **recomendado** entre servidores en ADN; **DMRD** v1 sigue para interoperabilidad heredada (ver [OpenBridge](../protocols/openbridge.md#dmre-and-openbridge-v5)). + +La configuración es **YAML** (`adn-server.yaml`), fusionada en tiempo de ejecución con ajustes de voz opcionales (`adn-voice.yaml`). La plantilla incluida es `adn-server.example.yaml`. + +## Diseño + +Enrutado, temporizadores, control de bucle OpenBridge y manejo de protocolo están en módulos de **aplicación** e **infraestructura** detrás de **ports** estables; la capa de **dominio** contiene tipos y reglas sin E/S. Así el sistema es más fácil de seguir y extender. + +## Subsistemas principales + +| Subsistema | Rol | +|------------|-----| +| **Bridge router** | Tabla `BRIDGES`: qué sistemas reenvían qué TG en qué slot; bridges dinámicos; bridges estáticos/stat. | +| **Protocolo HBP** | Autenticación, ingreso/salida DMRD, repetición a peers, filtros TG. | +| **OpenBridge** | Ingreso DMRE, límite de saltos, control de bucle (`min(1ST)`), BCSQ/BCKA si están habilitados. | +| **Voz** | Ficheros AMBE, anuncios programados, tubería TTS, reproducción bajo demanda (TG 9991–9999). | +| **Informes** | Canal TCP netstring hacia **adn-monitor** (y paneles compatibles): config, estado de bridges, eventos de llamada `BRDG_EVENT`. | + +## Programas relacionados + +- **Parrot / reproducción** — punto de entrada aparte (`adn-parrot.py`) para grabar y reproducir; ver [Parrot](parrot.md). + +## Siguientes pasos + +- [Configuración](configuration.md) — ficheros, `GLOBAL`, **MASTER** / **PEER** / **OPENBRIDGE**, ACL, informes, alias, fusión de voz. +- [Bridges y talkgroups](bridges-and-talkgroups.md) — cómo funciona `BRIDGES`. +- [Números especiales](special-numbers.md) — TG 4000, servicios de información, eco. +- [ADN Monitor](../../monitor/index.md) — panel, `adn-mon.yaml`, self-service (repo aparte, desplegado con el servidor). +- [Créditos y licencia](attribution.md) — ADN → FreeDMR → hblink3, licencia. diff --git a/docs/es/server/user-guide/monitoring.md b/docs/es/server/user-guide/monitoring.md new file mode 100644 index 0000000..2d0c092 --- /dev/null +++ b/docs/es/server/user-guide/monitoring.md @@ -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). diff --git a/docs/es/server/user-guide/parrot.md b/docs/es/server/user-guide/parrot.md new file mode 100644 index 0000000..65e5a98 --- /dev/null +++ b/docs/es/server/user-guide/parrot.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. diff --git a/docs/es/server/user-guide/private-calls.md b/docs/es/server/user-guide/private-calls.md new file mode 100644 index 0000000..4848931 --- /dev/null +++ b/docs/es/server/user-guide/private-calls.md @@ -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`). diff --git a/docs/es/server/user-guide/special-numbers.md b/docs/es/server/user-guide/special-numbers.md new file mode 100644 index 0000000..71310c9 --- /dev/null +++ b/docs/es/server/user-guide/special-numbers.md @@ -0,0 +1,103 @@ +# Números especiales (TG / IDs) + +Varios **IDs de destino** están reservados para **control o servicios**. Se gestionan en capas de protocolo y/o en el router de bridges, no como tráfico de grupo normal. + +## ID 5000 — fuente de voz del servidor (no es «TG de anuncio») {#id-5000--server-voice-source-not-announcement-tg} + +**Importante:** **5000** es el **ID de fuente RF** que el servidor usa cuando **transmite** voz automatizada. Las radios y paneles muestran **ID de llamada 5000** en ese tráfico. + +| Tráfico | Destino en el paquete DMR | Notas | +|---------|---------------------------|--------| +| **AMBE programado** (`ANNOUNCEMENTS`) | El **`TG`** que definas en `adn-voice.yaml` | ID de fuente **5000**. | +| **TTS** (`TTS_ANNOUNCEMENTS`) | Igual — **`TG`** configurado | ID de fuente **5000**. | +| **Bajo demanda** (TG **9991–9999**) | **TG 9** | Clips informativos cortos; fuente **5000** (ver [Voz, anuncios y TTS](voice-and-tts.md)). | +| **Desconectado / reflector** | **TG 9** | Fuente **5000**. | +| **Ident por voz** | **All-call** (`16777215`) o **`OVERRIDE_IDENT_TG`** si está definido | Fuente **5000**. | + +**No** «monitorizas TG 5000» para oír anuncios programados: monitorizas el **TG de anuncio configurado** (p. ej. 2, 9, 26811). **5000** aparece como **ID del transmisor** en esas llamadas. + +### TG de destino 5000 (grupo entrante) + +Si llega una llamada de grupo con **TG de destino 5000** y **no** hay fila `BRIDGES` existente, el servidor **no** crea automáticamente un bridge activado por usuario para esa TG (misma clase que IDs **0–4**, **9**, **4000**). Para llevar tráfico a TG 5000 necesitas una fila de bridge **explícita**. + +## TG 9 — carril de servicio local (mensajes y cableado de bridge) {#tg-9-local-service-lane-prompts-and-bridge-plumbing} + +**TG 9** se usa de dos formas: lo que **oyen** los operadores con mensajes cortos del servidor, y cómo se **conectan** filas internas del bridge. + +### Lo que oyes (voz de salida del servidor) + +Para reproducción **bajo demanda** (tras marcar **9991–9999**) y para líneas de voz de **desconectado / reflector**, el servidor transmite paquetes de **grupo** con: + +- **ID de fuente 5000** +- **TG de destino 9** +- **Timeslot 2** (el código usa el slot **TS2** para ese hotspot) + +Sigue una convención habitual **HomeBrew / conferencia**: mantener el audio **local de servicio** corto en **TG 9 / TS2** para separarlo del tráfico QSO normal en tu TG principal (a menudo TS1). Los hotspots deben pasar **TS2** y estar en una configuración donde **TG 9** no esté bloqueado, o esos mensajes no se oirán. + +**Rojo frente a local:** En **OpenBridge**, el tráfico de **grupo entrante** a **TG 9** está en el rango **≤ 79** «local / repetidor» y **no** entra al bridge desde la malla IP (se descarta en ingreso). Los mensajes del servidor usan el **camino HBP local** hacia el hotspot/repetidor, no un TG de área amplia puenteado. Excepción: solo si **añades** filas `BRIDGES` que reenvíen TG 9 podría salir ese tráfico a otro sitio — no es lo por defecto. + +Los **anuncios programados** y **TTS** usan el **`TG`** que configures en `adn-voice.yaml` — **no** se fuerzan a TG 9 salvo que tú configures ese TG. + +### Comportamiento del router (TG reservada) + +- **No** hay bridge activado por usuario automático si alguien transmite a **TG 9** y no existe fila `BRIDGES` (misma clase que **0–4**, **4000**, **5000**, etc.). +- Con **`GEN_STAT_BRIDGES`**, la creación automática de bridge **STAT** desde OpenBridge **no** aplica al TG de destino **9** (excluido a propósito). +- El **bucle de depuración de bridges** elimina bridges de conferencia inválidos keyed en **9** (y **0**–**8**) para que no se acumulen bridges de un dígito erróneos. + +### Tabla de bridges (avanzado) + +Muchas filas **reflector / marcado** guardan **`TGID` = 9** en **TS2** como **destino de pata** interno para enganchar el camino dinámico al TG real — es cableado dentro de `BRIDGES`, no un «número al que llamar» como un TG nacional normal. + +## TG / ID 4000 — desactivar bridges dinámicos + +**Propósito:** borrar **bridges dinámicos activados por usuario** para el sistema que recibe la llamada. + +**Comportamiento:** + +- Implementado para tráfico de **grupo** con destino **4000** (y comprobaciones relacionadas en el router). +- Se ejecuta **antes** que la ACL normal de TG en la ruta OpenBridge para que el comando no quede bloqueado por listas permitidas. +- Invoca **`deactivate_all_dynamic_bridges`**: desactiva filas dinámicas que no sean stat ni reflector. + +Úsalo cuando los operadores necesiten **reiniciar** el enrutado dinámico sin reiniciar el servidor. + +## TG 9991–9999 — audio informativo / bajo demanda + +**Propósito:** **reproducir** ficheros AMBE pregenerados («ondemand») (p. ej. información de la estación, ayuda). + +**Comportamiento:** + +- Dispara manejo tipo **`playFileOnRequest`**: mapea los últimos dígitos al nombre de fichero bajo el árbol de audio configurado. +- Funciona desde rutas **MASTER** y **PEER**. + +El **audio** se envía con **ID de fuente 5000** y **TG de destino 9** en el flujo generado. Estructura de ficheros: [Voz, anuncios y TTS](voice-and-tts.md). + +## TG 9990 — eco / parrot (en banda) + +**Propósito:** las filas de bridge para **eco** suelen usar **9990** con el sistema **ECHO** (ver `BRIDGES` y opciones en tu YAML). + +**Nota:** Un **parrot independiente** también está disponible como proceso aparte — [Parrot](parrot.md). + +## Llamada privada al ID 4000 + +Una llamada **unitaria** a **4000** se trata solo como **desconexión de dinámicos**; **no** se enruta como llamada privada normal. + +## Ingreso OpenBridge — filtros de TG de grupo {#openbridge-ingress--group-tg-filters} + +En **OpenBridge** (**DMRD** v1 y **DMRE**), el tráfico de **grupo** **no unitario** hacia ciertas TG puede **descartarse** antes del bridge (con **BCSQ** donde aplique). Las reglas difieren ligeramente entre DMRD y DMRE; incluyen TG de número bajo (p. ej. **≤ 79**), **9990–9999**, **900999**, rangos **92–199** (frente al servidor de origen), y rangos relacionados con MCC (**80–89**, **800–899**). Detalle: [Protocolo OpenBridge](../protocols/openbridge.md#ingress-filters-group-tg). + +## TG prohibidos / reservados (creación de bridges) + +Muchos IDs pequeños (0–5, 9, etc.) y el rango de **servicio 999x** quedan excluidos de ciertos caminos **automáticos** de creación de bridges; **5000** y **4000** están en el conjunto «sin bridge UA automático» si no existe fila. Los conjuntos exactos están definidos en el router de bridges y el manejo de opciones en el código. + +## Tabla resumen + +| ID / rango | Rol | +|------------|-----| +| **5000** (fuente) | Voz generada por el servidor (anuncios, TTS, mensajes, ident) — **ID de llamada** en receptores | +| **5000** (destino) | Sin bridge UA automático si falta en `BRIDGES` | +| **4000** (grupo) | Desactivar bridges dinámicos | +| **4000** (unitaria) | Desconectar dinámicos; no enrutada como PC | +| **9991–9999** | Audio informativo / bajo demanda (TG de disparo); reproducción usa fuente **5000** → TG **9** (TS2) | +| **9** | Carril de servicio/mensajes (audio corto del servidor); reservada para auto-bridges; **TGID** interno en patas TS2 | +| **9990** | TG de bridge de eco (con sistema ECHO) | +| **16777215** | All-call (destino por defecto de ident por voz salvo sobrescritura) | diff --git a/docs/es/server/user-guide/voice-and-tts.md b/docs/es/server/user-guide/voice-and-tts.md new file mode 100644 index 0000000..a2e385a --- /dev/null +++ b/docs/es/server/user-guide/voice-and-tts.md @@ -0,0 +1,57 @@ +# Voz, anuncios y TTS + +## Ficheros de configuración + +- **`adn-voice.yaml`** (opcional, no versionada) — fusionada en `config["VOICE"]`. +- Recarga en caliente por **mtime** del fichero (~bucle de 15 s). + +Plantilla: `adn-voice.example.yaml`. + +## Identidad de voz del servidor (ID 5000) + +Toda la voz **originada por el servidor** usa **ID de fuente RF 5000** en el flujo DMR para que los clientes distingan el tráfico de infraestructura del de usuarios: + +- **ANNOUNCEMENTS** y **TTS_ANNOUNCEMENTS** programados (destino = el campo **`TG`** de cada ítem). +- Clips **bajo demanda** disparados marcando **9991–9999** (la reproducción usa TG de destino **9** en **TS2**; sigues marcando **999x** para solicitar el fichero). +- Líneas de voz de **desconectado / reflector** (igual: **TG 9** / **TS2**). + +Ver [TG 9 — carril de servicio local](special-numbers.md#tg-9-local-service-lane-prompts-and-bridge-plumbing) por qué se usa TG 9 y qué debe estar habilitado en el hotspot. + +- **Ident por voz** (destino **all-call** o **`OVERRIDE_IDENT_TG`**). + +Ver [Números especiales — ID 5000](special-numbers.md#id-5000--server-voice-source-not-announcement-tg) para la tabla completa. + +## Funciones + +| Función | Descripción | +|---------|-------------| +| **Anuncios programados** | Frases AMBE desde `Audio//ondemand/` según horario / intervalo. | +| **Anuncios TTS** | Texto → gTTS → MP3 → ffmpeg → WAV → vocoder → AMBE (ver tubería abajo). | +| **Ident por voz** | Identificación periódica (opcional); usa `VOICE_IDENT` en la config principal del servidor y `OVERRIDE_IDENT_TG` / all-call. | +| **Grabación** | Si está habilitada, graba AMBE a disco para tráfico en **`RECORDING_TG`** / **`RECORDING_TIMESLOT`** (ver plantilla `adn-voice.example.yaml`). | +| **Bajo demanda (9991–9999)** | Ver [Números especiales](special-numbers.md). | + +## Tubería TTS (resumen) + +1. Texto fuente en `.txt` bajo `Audio//ondemand/`. +2. **gTTS** produce **MP3**. +3. **ffmpeg** convierte a **WAV**. +4. Comando vocoder o **AMBEServer** produce **AMBE** para transmitir. + +**ffmpeg** debe estar instalado en el sistema operativo. + +Configura **`TTS_VOCODER_CMD`** o **`TTS_AMBESERVER_HOST`** / **`TTS_AMBESERVER_PORT`** en `adn-voice.yaml` para codificación; ver comentarios en `adn-voice.example.yaml`. + +## Anti-colisión (QSO) con anuncios + +Al programar anuncios, el servidor puede **esperar** si los slots objetivo están ocupados, **descartar** objetivos si aparece un QSO en vivo a mitad, y solo marcar estado de anuncio **horario** tras una lista de objetivos con éxito — evita pisar tráfico en vivo. + +## Cola de emisión + +Las **emisiones** en paralelo en **TG distintas** pueden ejecutarse a la vez; las emisiones en el **mismo TG** se **serializan** para que un anuncio termine antes que otro en ese TG. + +## Ver también + +- [Configuración](configuration.md) — rutas de ficheros de voz. +- [Parrot](parrot.md) — servicio de reproducción separado. +- [Números especiales](special-numbers.md) — 5000, 999x, comportamiento relacionado con grabación. diff --git a/mkdocs.es.yml b/mkdocs.es.yml new file mode 100644 index 0000000..54df403 --- /dev/null +++ b/mkdocs.es.yml @@ -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: [] diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..8f18fc1 --- /dev/null +++ b/mkdocs.yml @@ -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: [] diff --git a/pyproject.toml b/pyproject.toml index dcbfc7a..8e9d011 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ [project.optional-dependencies] dev = ["pytest>=7", "pytest-cov"] +docs = ["mkdocs>=1.6", "mkdocs-material>=9.5", "pymdown-extensions>=10.3"] [tool.setuptools.packages.find] where = ["src"] diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..71a9270 --- /dev/null +++ b/requirements-docs.txt @@ -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