Document private MQTT topics and the 1.1 upgrade

This commit is contained in:
zorruno committed 2026-10-03 15:32:43 +13:00
1 parent 593f86e6dd
commit 6ba2656c9d
3 files changed
+316 -1

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
# Changelog
## 1.1 — 2026-10-03
- Full command, state and availability topics are required in the private INI.
- Subscription, state publication, availability and Last Will use those settings.
- Generic shared topic examples; no installation-specific topic prefixes in the current source.
- MQTT client ID can also be configured in the INI.
- Missing, blank, duplicate, wildcard and oversized topics are rejected at startup.
- Upgrade instructions preserve existing topics and the existing Home Assistant entity.
- 24 behaviour/configuration tests with mocked MQTT and Docker.
## 1.0 — 2026-10-03
- MQTT-controlled handover between MeshCore WebUI and a native phone app.
- Independent systemd controller; stops/starts only `meshcore-webui`.
- Confirmed Docker state, retained JSON and MQTT availability/Last Will.
- Automatic MQTT reconnect without changing container state.
- Non-retained ON/OFF commands and idempotent container operations.
- Home Assistant package, Debian installer and example credentials config.
- 16 behaviour tests; initial installation and switching confirmed working.
+294
View File
@@ -0,0 +1,294 @@
# MeshCore Phone App Mode
An MQTT switch for handing a MeshCore companion radio between **MeshCore WebUI**
and the **native MeshCore phone app**.
MeshCore WebUI holds a persistent TCP connection from its backend, even when
all browser tabs are closed. On a companion that only accepts one direct client,
this can prevent a phone app from connecting or completing repeater admin
requests. This controller temporarily stops the WebUI container and restarts
it when you have finished with the phone app.
Version **1.1**, by **Zorruno**, 2026-10-03. Initially deployed and confirmed
working on Debian 12 with Python 3.11, Eclipse Mosquitto, Docker, MeshCore WebUI
and Home Assistant. Licensed under MIT.
## What the switch means
| Phone App Mode | WebUI container | What to do |
|---|---|---|
| ON | Stopped | Connect the native phone app to the companion |
| OFF | Running | Use WebUI from your browser |
**Disconnect the native phone app before switching OFF.** Stop any other
software that connects directly to this companion too.
The controller runs as a separate systemd service on the Docker host. It:
- Accepts only plain ON/OFF commands on the MQTT topic configured in your private INI.
- Starts/stops only the container named `meshcore-webui`.
- Inspects actual Docker state and reports retained JSON every 15 seconds.
- Publishes online/offline availability, with an MQTT Last Will.
- Reconnects to MQTT without changing container state.
- Ignores retained commands received when subscribing.
- Performs no start/stop action on controller, broker or Home Assistant restart.
Home Assistant waits for confirmed state instead of assuming a command worked.
No SSH setup or Docker API connection from Home Assistant is required.
This is a connection handover, not a repeater watchdog. A running WebUI
container does not prove that it connected to the radio. A stopped WebUI
container does not prove that every other client disconnected or that a remote
repeater is reachable.
## Files
| File | Purpose |
|---|---|
| `controller.py` | MQTT listener, Docker start/stop and state reporting |
| `meshcore-phone-mode.service` | Independent systemd service |
| `install.sh` | Debian/Ubuntu installation helper |
| `meshcore-phone-mode.ini.example` | Broker settings and full topic names, with blank credentials |
| `meshcore_phone_app_mode.yaml` | Complete Home Assistant package |
| `requirements.txt` | Pinned Python MQTT dependency |
| `test_controller.py` | Behaviour tests with mocked MQTT and Docker |
| `CHANGELOG.md` | Version history |
## Requirements
- A Debian/Ubuntu Docker host using systemd, with Python 3 and internet access
for installation packages.
- A container named `meshcore-webui` on that host.
- An MQTT broker reachable by both this controller and Home Assistant, or an
existing bridge carrying the topics below.
- Home Assistant's MQTT integration and an existing packages configuration.
The service runs as root to control the local Docker daemon, but MQTT payloads
cannot select arbitrary commands or container names. Only the exact ON/OFF
commands are accepted, and Docker is called using separate argument values.
Keep access to the MQTT command topic limited to clients that should control
WebUI. Do not include your real broker configuration in Git.
## Installation on the Docker host
Clone this repository using its clone URL from Gitea. A suitable working
location is `/dockervolumes/meshcore-phone-mode`.
```bash
git clone https://home.fox.co.nz/gitea/zorruno/meshcore-phone-mode.git /dockervolumes/meshcore-phone-mode
cd /dockervolumes/meshcore-phone-mode
sudo bash install.sh
sudo nano /etc/meshcore-phone-mode.ini
```
The installer installs Python venv support and paho-mqtt 2.1.0, puts the runtime
in `/opt/meshcore-phone-mode`, creates a root-only example config at
`/etc/meshcore-phone-mode.ini`, and installs the systemd service. It does not
start the service or change the WebUI container. It preserves an existing
config file. A rerun validates that config before stopping an active controller
or replacing its runtime. If the config is invalid, the existing controller is
left running. A valid update leaves the service stopped until you start it.
Configure the **same broker Home Assistant already uses**. If it runs on this
Docker host with host port 1883 published, use `127.0.0.1:1883`. Otherwise enter
the existing broker's address and port. Each machine's localhost is its own
machine. Do not change HA's existing MQTT broker just to add this switch.
Enter username/password and all three full MQTT topics locally in the INI
file, without surrounding quotes. The shared example uses generic topic names;
replace them with your own if desired. The three topics are required, must be
different, and cannot contain `+` or `#` wildcards. There is no hidden prefix
or fallback to the original installation's topics. Set a unique `client_id`
if several controllers share a broker.
Enter values without surrounding quotes.
Leave both blank only if anonymous access is permitted. Values are literal;
`%` and `#` within values are supported. Credentials are not passed as CLI
arguments and are not included in this repository.
The private INI is created with permissions `0600` and is excluded from Git.
Home Assistant topic settings must also stay in your local configuration.
Topic names are configuration, not passwords: MQTT authentication and broker
permissions still control who can use them.
The broker account needs read access to the command topic and write access to
the state and availability topics. A denied subscription keeps the controller
unavailable until permissions are corrected and the service is restarted.
Start and verify:
```bash
sudo systemctl enable --now meshcore-phone-mode.service
sudo journalctl -u meshcore-phone-mode.service -n 30 --no-pager
```
Expected log: `MQTT subscribed; checking current WebUI state`. The first state
reflects whether WebUI was already running or stopped; starting the controller
does not change it.
## Home Assistant package
Copy `meshcore_phone_app_mode.yaml` into:
```text
/config/packages/meshcore_phone_app_mode.yaml
```
Before installing the package, match its topic fields to your private INI:
| Home Assistant field | INI setting in `[mqtt]` |
|---|---|
| `command_topic` | `command_topic` |
| `state_topic` | `state_topic` |
| `json_attributes_topic` | `state_topic` |
| `availability_topic` | `availability_topic` |
Home Assistant does not read the controller's INI. Edit these fields in the
local HA package; do not commit that customised package back to this repo.
The supplied file assumes an existing package include such as:
```yaml
homeassistant:
packages: !include_dir_named packages
```
Keep the existing include; do not create duplicate `homeassistant:` or `mqtt:`
keys in the main configuration file. If you use `!include_dir_merge_named`,
wrap this file in a top-level package name as described in HA's documentation.
Check the configuration, then restart Home Assistant. Find the switch named
**MeshCore Phone App Mode**, normally `switch.meshcore_phone_app_mode`.
The package has a unique ID, so the entity can be renamed in HA. If you deploy
several controllers, give each its own client ID, MQTT topics and HA unique ID.
## Check the handover
1. Disconnect the native phone app.
2. Turn Phone App Mode OFF. Confirm WebUI starts and HA reports OFF.
3. Turn Phone App Mode ON. Confirm WebUI stops and HA reports ON.
4. Connect the native phone app and test repeater admin without resetting the
companion or repeater.
5. Disconnect the native app before switching OFF to restore WebUI.
6. Restart the controller while mode is ON. WebUI should remain stopped.
Inspect the actual container state at any time:
```bash
sudo docker inspect --format '{{.State.Status}}' meshcore-webui
```
## MQTT topics
| Purpose | Topic | Payload | Retained |
|---|---|---|---|
| Command | `meshcore/phone_app_mode/set` | Plain ON or OFF | No |
| State | `meshcore/phone_app_mode/state` | JSON | Yes |
| Availability | `meshcore/phone_app_mode/availability` | online / offline | Yes |
Example state:
```json
{"phone_app_mode":"ON","container":"meshcore-webui","container_status":"exited","observed_at":"2026-10-03T01:30:00+00:00"}
```
These are generic example topics. Configure the full topic names under `[mqtt]`
in `/etc/meshcore-phone-mode.ini`, and use the same values in your local HA
package. Source edits are not needed. The example MQTT client ID is
`meshcore-phone-mode`; set `client_id` in the INI to make it unique if needed.
```ini
[mqtt]
command_topic = meshcore/phone_app_mode/set
state_topic = meshcore/phone_app_mode/state
availability_topic = meshcore/phone_app_mode/availability
```
Restart the controller after changing its INI, and reload/restart HA after
changing its package. The subscription, state reports, online/offline reports
and Last Will all use these configured values.
Never publish retained commands. The controller uses a clean session and clears
pending commands on disconnect. QoS 1 duplicates are idempotent once the
container is already in the requested state. Commands are not run through a
shell, and no command parameters are accepted from MQTT.
## Updates, backup and recovery
**Upgrading from 1.0:** before installing 1.1, add `command_topic`, `state_topic`
and `availability_topic` to the existing `[mqtt]` section of your private INI.
Use the exact values from your working HA package. Keep that existing HA
package (including its `unique_id`) unchanged. This preserves your switch and
current topic scheme. Do not replace your private INI with the generic example.
If any topic is missing or invalid, 1.1 refuses to start rather than subscribing
to an unexpected topic.
To update from Git, pull your chosen version, edit the private INI if needed,
rerun the installer, then restart:
```bash
cd /dockervolumes/meshcore-phone-mode
git pull --ff-only
sudo nano /etc/meshcore-phone-mode.ini
sudo bash install.sh
sudo systemctl start meshcore-phone-mode.service
```
The installer preserves `/etc/meshcore-phone-mode.ini`. Back that file up
privately if you need to restore broker credentials and custom topics; Git
contains only its generic example. Keep your customised HA package in your
private HA backup too. The source repository plus that private config are enough
to reinstall the controller. WebUI's own data/config need their normal backup.
Diagnostics:
```bash
sudo systemctl status meshcore-phone-mode.service --no-pager
sudo journalctl -u meshcore-phone-mode.service -n 50 --no-pager
```
If HA shows unavailable, check the controller log, broker credentials and
permissions, and whether HA receives the availability topic. Keep optimistic
mode disabled so a failed Docker operation is not hidden.
Manual handover:
```bash
sudo docker stop meshcore-webui
```
After disconnecting the native phone app:
```bash
sudo docker start meshcore-webui
```
To disable the controller:
```bash
sudo systemctl disable --now meshcore-phone-mode.service
```
Remove the HA package if desired. Disabling the controller leaves WebUI in its
current state. Logs use the system journal, with no separate unbounded log file.
## Validation
```bash
python3 -m unittest -q test_controller.py
```
24 tests cover retained/invalid commands, denied subscriptions, reconnects with
no container changes, confirmed state reporting, failed stops, duplicate
commands, exact container arguments, missing/paused containers and MQTT
publication failures, custom topics throughout the controller and Last Will,
missing/invalid/duplicate topic settings, and matching shared examples. Tests mock MQTT/Docker and do not change live services.
Python syntax, installer shell syntax and YAML settings were checked; the
original live installation and HA handover were confirmed working.
## References
- [MeshCore WebUI](https://github.com/adradr/meshcore-webui)
- [Home Assistant MQTT Switch](https://www.home-assistant.io/integrations/switch.mqtt/)
- [Home Assistant packages](https://www.home-assistant.io/docs/configuration/packages/)
- [Eclipse Paho MQTT client API](https://eclipse.dev/paho/files/paho.mqtt.python/html/client.html)
+1 -1
View File
@@ -1,4 +1,4 @@
# Zorruno — MeshCore Phone App Mode v1.0 — 2026-10-03 # Zorruno — MeshCore Phone App Mode v1.1 — 2026-10-03
[Unit] [Unit]
Description=MeshCore WebUI phone-app handover via MQTT Description=MeshCore WebUI phone-app handover via MQTT
After=network-online.target docker.service After=network-online.target docker.service