diff --git a/CHANGELOG.md b/CHANGELOG.md index 53b7857..ea9a2b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Ver response, the age of the last successful poll and the last error. Fields are rendered from the response itself rather than a fixed list, so a field a future firmware adds appears without a code change. +- `docs/examples/repeaters.json.example` — annotated example + configuration, plus README section 8.2 covering the file location, + the fields, the permissions and the archive format. - `REPEATER_POLL_ENABLED`, `REPEATER_POLL_INTERVAL` (900 s), `REPEATER_POLL_CHECK_INTERVAL`, `REPEATER_LOGIN_TIMEOUT`, `REPEATER_STATUS_TIMEOUT`, `REPEATER_STATS_RETENTION_DAYS` and diff --git a/README.md b/README.md index 9a9e702..81e427b 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ A full-featured desktop platform for MeshCore mesh radio devices. Connects via U - [7.9. Raspberry Pi 5 Notes](#79-raspberry-pi-5-notes) - [8. Configuration](#8-configuration) - [8.1. Data Directory (`~/.meshcore-gui/`)](#81-data-directory-meshcore-gui) + - [8.2. Repeater Statistics Polling](#82-repeater-statistics-polling) - [9. Functionality](#9-functionality) - [9.1. Device Info](#91-device-info) - [9.2. Contacts](#92-contacts) @@ -731,6 +732,9 @@ All persistent data is stored under `~/.meshcore-gui/` in your home directory. E ├── channel_backups/ │ └── __channels.json # Local channel backup (names + PSKs) per device │ # Created on demand from 💾 Backup Channels; never transmitted +├── repeaters/ +│ └──
_repeaters.json # Repeaters polled for statistics (see 8.2) +│ # Contains a login password: dir 0700, file 0600 └── logs/ └──
_meshcore_gui.log # Rotating debug log (max 20 MB, only with --debug-on) ``` @@ -752,6 +756,96 @@ cat ~/.meshcore-gui/bbs/bbs_config.json > **Tip:** When moving to a new machine or a different Raspberry Pi, copy the entire `~/.meshcore-gui/` directory to preserve your pinned contacts, room passwords, bot configuration and message history. +## 8.2. Repeater Statistics Polling + +The REPEATERS panel shows the statistics a repeater reports: battery, uptime, +airtime, packet counters, noise floor, RSSI and SNR. Every poll is archived +with its timestamp, so battery voltage can be followed over multiple days. + +Polling stays idle until you create a configuration file, so nothing is sent +over the air until you ask for it. + +### Configuration file + +An annotated example ships with the project at +[`docs/examples/repeaters.json.example`](docs/examples/repeaters.json.example). + +```bash +mkdir -p ~/.meshcore-gui/repeaters +chmod 700 ~/.meshcore-gui/repeaters + +#
follows the same rule as every per-device file: +# /dev/ttyUSB1 → _dev_ttyUSB1 +cp docs/examples/repeaters.json.example \ + ~/.meshcore-gui/repeaters/_dev_ttyUSB1_repeaters.json +chmod 600 ~/.meshcore-gui/repeaters/_dev_ttyUSB1_repeaters.json + +nano ~/.meshcore-gui/repeaters/_dev_ttyUSB1_repeaters.json +``` + +```json +{ + "version": 1, + "repeaters": { + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef": { + "name": "NoodNet Zwolle", + "password": "change-me", + "poll_interval": 900, + "enabled": true + } + } +} +``` + +| Field | Meaning | +|-------|---------| +| key | The **full 32-byte** public key as 64 hex characters. A prefix is rejected — the meshcore library needs the complete key for login, status and logout. Copy it from the CONTACTS panel. | +| `name` | Display name in the REPEATERS panel. | +| `password` | Login password. Omit the field when none is configured; use `""` when the repeater accepts a blank one. An omitted password means the repeater is skipped. | +| `poll_interval` | Seconds between polls. Default 900 (`REPEATER_POLL_INTERVAL` in `config.py`). | +| `enabled` | Set to `false` to keep the entry but stop polling it. | + +Restart the instance after editing; the file is read once at startup. + +### Behaviour + +- One repeater is polled per check, each with its own offset inside the + interval, so two repeaters are never queried back to back. Two repeaters on + the default interval land roughly 7.5 minutes apart. +- Each poll is a login, a status request and a logout. The logout is always + sent once a login has been attempted, including after a timeout. +- A failed poll is archived too, with `ok: false` and a reason, so a gap in the + message archive can be matched against the poll moments. +- Values are stored and shown exactly as the repeater reports them — no + conversion, no rounding, no averaging. +- Only the instance that has a repeater in its own file polls that repeater, so + two instances never query the same node. + +### Password handling + +The file holds a repeater login password and is created `0600` inside a `0700` +directory. The application logs an explicit warning when the permissions are +wider than that and keeps running. + +The password is reachable through a single method that only the poller calls. +It never appears in the REPEATERS panel, the archive, the REST API, a log line +or an error message. + +### Archive + +``` +~/.meshcore-gui/archive/
_repeater_stats.jsonl +``` + +One JSON object per line, appended immediately: + +```json +{"polled_at": "2026-09-01T10:15:00+00:00", "pubkey": "0123…", "name": "NoodNet Zwolle", "ok": true, "error": null, "status": {"bat": 4021, "uptime": 91234, "…": "…"}} +``` + +Retention is `REPEATER_STATS_RETENTION_DAYS` in `config.py` (default 90) and is +applied by the existing daily cleanup task. + ## 9. Functionality ### 9.1. Device Info diff --git a/docs/examples/repeaters.json.example b/docs/examples/repeaters.json.example new file mode 100644 index 0000000..caf7a67 --- /dev/null +++ b/docs/examples/repeaters.json.example @@ -0,0 +1,60 @@ +{ + "_comment": [ + "Example repeater configuration for the REPEATERS panel.", + "", + "Copy this file to:", + " ~/.meshcore-gui/repeaters/
_repeaters.json", + "", + "
is derived from the device argument, the same way as every", + "other per-device file: /dev/ttyUSB1 becomes _dev_ttyUSB1, and a BLE", + "address AA:BB:CC:DD:EE:FF becomes AA_BB_CC_DD_EE_FF. So for", + "/dev/ttyUSB1 the filename is _dev_ttyUSB1_repeaters.json.", + "", + "Only the instance that has a repeater in its own file polls that", + "repeater, which keeps two instances from querying the same node.", + "", + "This file contains a login password, so it must be mode 0600 inside a", + "0700 directory:", + " mkdir -p ~/.meshcore-gui/repeaters", + " chmod 700 ~/.meshcore-gui/repeaters", + " chmod 600 ~/.meshcore-gui/repeaters/
_repeaters.json", + "", + "The application warns in the log when the permissions are wider than", + "that, and keeps running.", + "", + "Remove this _comment key in the real file, or leave it — unknown", + "top-level keys are ignored.", + "", + "Field reference, per entry under \"repeaters\":", + " key The FULL 32-byte public key of the repeater, as 64", + " hex characters. A prefix is rejected: the meshcore", + " library requires the complete key for login, status", + " and logout. Find it in the CONTACTS panel.", + " name Display name shown in the REPEATERS panel.", + " password Login password. Omit the field entirely when no", + " password is configured; use \"\" when the repeater", + " accepts a blank one. These are different: an omitted", + " password means the repeater is skipped.", + " poll_interval Seconds between polls of this repeater. Default 900", + " (15 minutes) from REPEATER_POLL_INTERVAL in config.py.", + " Repeater statistics change slowly; polling more often", + " costs airtime and makes the local node deaf for longer", + " without adding information.", + " enabled Set to false to keep the entry but stop polling it." + ], + "version": 1, + "repeaters": { + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef": { + "name": "NoodNet Zwolle", + "password": "change-me", + "poll_interval": 900, + "enabled": true + }, + "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210": { + "name": "Dalfsen", + "password": "change-me", + "poll_interval": 900, + "enabled": true + } + } +}