Document private MQTT topics and the 1.1 upgrade
This commit is contained in:
1 parent
593f86e6dd
commit
6ba2656c9d
3 files changed
+316
-1
No files matched your search
@@ -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.
|
||||
@@ -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,4 +1,4 @@
|
||||
# Zorruno — MeshCore Phone App Mode v1.0 — 2026-10-03
|
||||
# Zorruno — MeshCore Phone App Mode v1.1 — 2026-10-03
|
||||
[Unit]
|
||||
Description=MeshCore WebUI phone-app handover via MQTT
|
||||
After=network-online.target docker.service
|
||||
|
||||
Reference in new issue
Block a user