MultiObserver is an open-source observer extension for MeshCore Repeater. It is designed as a reusable, application-level library that adds Wi-Fi and dual-broker MQTT observation to a MeshCore repeater without replacing or forking MeshCore core functionality.
The observer receives repeater traffic, publishes packet and status telemetry to up to two independently configured MQTT brokers, and exposes persistent network configuration through the standard MeshCore CLI. It supports secure MQTT-over-WebSocket connections (WSS/TLS), making it suitable for community or private telemetry backends.
MultiObserver is installed into a clean MeshCore repeater source tree and has
been validated with the Heltec_v3_repeater environment for:
| MeshCore Repeater release | Status |
|---|---|
| 1.15.0 | Installer verified and firmware builds successfully |
| 1.16.0 | Installer verified and firmware builds successfully |
| 1.17.1 | Installer verified and firmware builds successfully |
The integration deliberately keeps MeshCore core code intact. The installer
adds only the minimal build and application hooks; MOBridge is the thin
adapter between the repeater application and the MultiObserver library.
- Two independent MQTT broker profiles with separate host, port, transport, username, password and enable/disable settings.
- Wi-Fi and MQTT configuration through the MeshCore CLI, stored persistently on the device.
- MQTT over TCP or WSS/TLS, with connection startup gated on usable Wi-Fi and valid system time.
- Packet, RAW and periodic status publication to every enabled broker.
- Serial diagnostics for Wi-Fi, SNTP, MQTT state changes and received/transmitted packets.
- Heltec V3 integration: Wi-Fi IP shown on the OLED, an EastMesh-style cached battery measurement, and a non-blocking white LED pulse for packet activity.
- A staged Wi-Fi/MQTT watchdog with a configurable startup grace period, bounded recovery actions and loop-safe post-reboot silent monitoring.
- An encrypted private AlertChannel that uses MeshCore's native outbound queue for startup, outage and recovery notifications.
- Optional Remote CLI access through that private channel, backed by the repeater's existing command handler and outbound queue.
The V2 implementation is active and builds successfully for the supported MeshCore Repeater releases listed above.
Implemented runtime mechanisms include:
- persistent WiFi and dual-MQTT WSS/TLS configuration through the MeshCore CLI
- explicit WiFi, SNTP and MQTT state machines with transition diagnostics
- MQTT connection gating on both WiFi connectivity and sane system time
- bounded reconnect backoff, heap headroom checks and ESP-TLS error reporting
- packet, RAW and periodic status publication to both enabled brokers
- one-line RX/TX packet diagnostics in the serial monitor
- EastMesh-style 60-second battery sampling cache for Heltec V3 GPIO37
- non-blocking visible packet pulse on the Heltec V3 white GPIO35 LED
- WiFi IP address on the Heltec OLED status screen
- staged Wi-Fi, MQTT1 and MQTT2 supervision with one-shot reboot attribution
- a current-day watchdog event log with the last five entries available by CLI
- 128-bit and 256-bit private MeshCore AlertChannel keys
- private-channel Remote CLI without a duplicate parser or radio queue
Integration architecture:
- MeshCore 1.17.1 core remains unchanged in
src/ - repeater integration lives in
examples/simple_repeater/ - MultiObserver functionality lives in
lib/MultiObserver/ MOBridgeprovides the application-level adapter between the repeater and MultiObserver
MultiObserver/
├── examples/
│ └── simple_repeater/
│ ├── MOBridge.cpp
│ └── MOBridge.h
├── installer/
│ ├── mo_installer/
│ │ ├── cli.py
│ │ ├── common.py
│ │ ├── patches.py
│ │ ├── storage.py
│ │ ├── validation.py
│ │ └── workflow.py
│ ├── tests/
│ ├── install.py
│ ├── multiobserver_cpp17.py
│ └── verify.py
├── lib/
│ └── MultiObserver/
│ ├── library.json
│ └── src/
│ ├── MO.cpp
│ ├── MO.h
│ ├── MOConfig.cpp
│ ├── MOConfig.h
│ ├── MOWifi.cpp
│ ├── MOWifi.h
│ ├── MOWifiPrefs.cpp
│ ├── MOWifiPrefs.h
│ ├── MOMQTT.cpp
│ ├── MOMQTT.h
│ ├── MOMQTTPrefs.cpp
│ ├── MOMQTTPrefs.h
│ ├── MOCli.cpp
│ ├── MOCli.h
│ ├── MOEtap2Prefs.cpp
│ ├── MOEtap2Prefs.h
│ ├── MOAlertChannel.cpp
│ ├── MOAlertChannel.h
│ ├── MOWatchdog.cpp
│ └── MOWatchdog.h
├── install.ps1
└── Run-Installer.cmd
- preserve MeshCore compatibility
- make the smallest safe integration changes
- avoid unnecessary dependencies
- keep WiFi and MQTT lifecycle deterministic
- keep runtime configuration persistent
- do not duplicate existing MeshCore functionality without a reason
- keep secrets and local credentials out of Git
- keep
install.pyas a thin entry point and isolate patching, validation, backup and workflow responsibilities
See LICENSE.
For Heltec_v3_repeater, the installer adds the local MultiObserver library,
enables the ESP32 certificate bundle and compiles the extension as C++17. The
extension uses the ESP-IDF MQTT client, so the unused Arduino
WiFiClientSecure library is excluded while the framework WiFi library
remains available to MOWifi.
The installer verifies that all 18 MeshCore 1.17.1 Heltec V3 environments are preserved and creates a rollback backup before changing the target tree.
The installer never keeps a prepared device identity in the MO source tree.
When identity arguments are supplied, it writes the generated identity and
name directly into the selected MeshCore target's data/ directory.
This is the recommended complete path for a new device. Commands marked
MultiObserver directory are run in the cloned MultiObserver repository.
Commands marked prepared MeshCore directory are run in the MeshCore source
tree selected by the installer.
- A clean MeshCore repeater source tree. MeshCore 1.15.0, 1.16.0 and 1.17.1 have been installed and compiled successfully with this release.
- Python 3 and PlatformIO (
py -3 -m platformio). - A USB-connected Heltec V3 board when you are ready to flash it.
Open PowerShell and clone the repository where you keep your projects:
git clone https://github.com/beepoo304/MultiObserver.git
cd .\MultiObserverIf you already downloaded or cloned MultiObserver, simply open PowerShell in that directory instead.
In the MultiObserver directory, start the interactive installer:
.\Run-Installer.cmdWhen prompted, enter:
- The full path to a clean MeshCore Repeater source tree (for example,
C:\Projects\MeshCore-repeater-v1.17.1). - The repeater name.
- The 64-character public identity key.
- The 128-character private identity key.
The installer creates a rollback backup, adds the minimal application/build
hooks, prepares the MeshCore filesystem data (data/identity/_main.id and
data/prefs.json), then verifies the resulting tree. Keep the identity keys
private: never commit or paste them into a public issue.
For non-interactive or troubleshooting use:
python installer/install.py <MeshCore-path> --source-root <MultiObserver-path>
python installer/verify.py <MeshCore-path>In PowerShell, change to the prepared MeshCore directory selected in step 1. Replace the example path with your own:
cd "C:\Projects\MeshCore-repeater-v1.17.1"
py -3 -m platformio run -e Heltec_v3_repeaterWait for SUCCESS before continuing.
With the Heltec V3 connected by USB, and still in the prepared MeshCore directory, upload both the firmware and the filesystem:
py -3 -m platformio run -e Heltec_v3_repeater -t upload
py -3 -m platformio run -e Heltec_v3_repeater -t uploadfsTo completely erase the connected board first:
py -3 -m platformio run -e Heltec_v3_repeater -t eraseupload flashes the program. uploadfs writes the identity, repeater name
and persistent configuration prepared by the installer; it is therefore also
required after an erase. Do not publish identity keys, Wi-Fi passwords or MQTT
credentials in a public repository.
Still in the prepared MeshCore directory, open the serial monitor at 115200 baud:
py -3 -m platformio device monitor -b 115200Press Ctrl+C to leave the monitor.
Use the MeshCore CLI in the serial monitor to configure Wi-Fi and each broker. The values below are placeholders: use your own network and broker values.
set wifi.ssid YourWiFiName
set wifi.pwd YourWiFiPassword
set mqtt1.on
set mqtt1.host mqtt.example.org
set mqtt1.port 443
set mqtt1.transport wss
set mqtt1.username YourMqttUser
set mqtt1.pwd YourMqttPassword
set mqtt2.on
set mqtt2.host backup-mqtt.example.org
set mqtt2.port 8883
set mqtt2.transport tcp
set mqtt2.username YourMqttUser
set mqtt2.pwd YourMqttPassword
Verify the current configuration and connection state with:
get wifi.status
get mqtt1.status
get mqtt2.status
Configuration is retained on the device. Restart Wi-Fi or an individual broker after changing settings when needed:
restart.wifi
restart.mqtt1
restart.mqtt2
Enter these commands in the MeshCore serial monitor. All settings are saved persistently. Commands and values are case-sensitive where shown.
For a standalone operational reference containing all 41 MultiObserver commands, replies, validation rules and a safe first-configuration sequence, see CLI_REFERENCE.md.
| Command | Description |
|---|---|
set wifi.ssid <SSID> |
Save the Wi-Fi network name and reconnect. |
set wifi.pwd <password> |
Save the Wi-Fi password and reconnect. |
get wifi.ssid |
Show the configured Wi-Fi network name. |
get wifi.status |
Show Wi-Fi state, IP address and diagnostic details. |
restart.wifi |
Restart Wi-Fi using the saved credentials. |
| Command | Description |
|---|---|
set mqtt1.on |
Enable broker 1 and start connecting. |
set mqtt1.off |
Disable broker 1 and disconnect it. |
set mqtt1.host <hostname> |
Set the broker hostname. |
set mqtt1.port <1-65535> |
Set the broker port. |
set mqtt1.transport tcp |
Use direct MQTT over TCP. |
set mqtt1.transport wss |
Use secure MQTT over WebSocket (WSS/TLS). |
set mqtt1.username <username> |
Set the MQTT username. |
set mqtt1.pwd <password> |
Set the MQTT password. |
get mqtt1.status |
Show enabled/disabled state, connection state, host, failures and last error. |
restart.mqtt1 |
Restart broker 1 using the saved settings. |
Broker 2 uses the same commands as broker 1. Replace mqtt1 with mqtt2:
| Command | Description |
|---|---|
set mqtt2.on / set mqtt2.off |
Enable or disable broker 2. |
set mqtt2.host <hostname> |
Set the broker hostname. |
set mqtt2.port <1-65535> |
Set the broker port. |
set mqtt2.transport tcp |
Use direct MQTT over TCP. |
set mqtt2.transport wss |
Use secure MQTT over WebSocket (WSS/TLS). |
set mqtt2.username <username> |
Set the MQTT username. |
set mqtt2.pwd <password> |
Set the MQTT password. |
get mqtt2.status |
Show broker 2 connection and diagnostic state. |
restart.mqtt2 |
Restart broker 2 using the saved settings. |
| Command | Description |
|---|---|
set mqtt.iata <ABC> |
Set the three-letter uppercase IATA location code used by MQTT telemetry. |
get mqtt.iata |
Show the saved IATA location code. |
For example, use set mqtt.iata KTW for Katowice. The code must contain
exactly three uppercase letters.
The watchdog starts after a configurable 120-300 second grace period. Wi-Fi health requires a connected station, a non-zero local address and a non-zero default gateway; no Internet probe is generated. Enabled, fully configured MQTT brokers are supervised only while Wi-Fi is healthy. MQTT health requires both an active session and fresh acknowledgement of a periodic status publish.
| Command | Description |
|---|---|
get wdg.status |
Show WDG mode, phase, service health and pending reboot state. |
set wdg.on |
Enable WDG persistently and start a fresh grace period. |
set wdg.off |
Disable WDG, cancel escalation and cancel a pending WDG reboot. |
get wdg.grace |
Show the saved startup grace period in seconds. |
set wdg.grace <120-300> |
Save the grace period and restart WDG grace. |
restart.wdg |
Reset only the WDG state machine and grace timer; do not reboot the ESP. |
get wdg.log |
Return the last five short events from the current local repeater day. |
set wdg.log 1 |
Clear the WDG event log. |
The persistent WDG log is deliberately small: it keeps at most the five most
recent short events and their local repeater times. Events may include WDG
startup, a service becoming unavailable or recovering, staged restarts,
silent-mode transitions and reboot scheduling/cancellation. When the local
calendar day changes, the previous day's entries are discarded automatically;
set wdg.log 1 clears them immediately. If the clock is not yet valid, WDG
continues operating but does not invent a timestamped log entry.
Wi-Fi recovery escalates after 3, 5 and 30 minutes. MQTT1 and MQTT2 are
supervised independently and escalate after 3, 5, 15 and 30 minutes. The last
stage queues an encrypted alert and schedules one ESP reboot three minutes
later. A persistent one-shot marker distinguishes that reboot from manual
restart or power loss. If the failed service is still down after the next
grace period, WDG enters silent mode and checks every minute without
further restarts or reboot loops. A *.restore alert is sent only after
recovery from silent mode; its duration starts at entry into silent mode.
WDG always gives Wi-Fi priority. If Wi-Fi is down, MQTT1 and MQTT2 are treated as dependent services and their independent escalation timers do not run. Every successful recovery clears that service's current escalation state.
Wi-Fi is checked every 5 seconds:
| Consecutive stage | Action if Wi-Fi is still down |
|---|---|
| Monitor for 3 minutes | Call the existing MultiObserver Wi-Fi restart. |
| Monitor for another 5 minutes | Restart Wi-Fi a second time. |
| Monitor for another 30 minutes | Queue Restart.ESP.wifi.down. |
| Wait another 3 minutes | Persist the one-shot WDG marker and reboot the ESP once. |
Each enabled and correctly configured MQTT broker is checked every 15 seconds while Wi-Fi is healthy. MQTT1 and MQTT2 have separate state machines:
| Consecutive stage | Action if that MQTT broker is still unhealthy |
|---|---|
| Monitor for 3 minutes | Restart that MQTT client. |
| Monitor for another 5 minutes | Restart that MQTT client again. |
| Monitor for another 15 minutes | Restart that MQTT client a third time. |
| Monitor for another 30 minutes | Queue Restart.ESP.mqtt1.down or Restart.ESP.mqtt2.down. |
| Wait another 3 minutes | Persist the one-shot WDG marker and reboot the ESP once. |
Only one reboot can be pending globally. Disabling WDG or running
restart.wdg cancels a pending reboot and clears all escalation states;
restart.wdg then starts a new grace period without rebooting the ESP.
A successful recovery during the final three-minute delay also cancels the
pending reboot owned by that service.
On boot, WDG consumes and deletes its reboot marker immediately. After grace,
AlertChannel newStart is queued. If a supervised service is still down and
that consumed marker identified this as the WDG-requested reboot, WDG enters
silent mode. Silent mode performs no service restart and schedules no further
ESP reboot. It checks every minute. Once a failed service recovers, AC
queues wifi.restore, mqtt1.restore or mqtt2.restore with a duration
measured only from entry into silent mode. Normal/manual boot and power loss do
not activate silent mode.
Create a private channel in a MeshCore client and copy its secret key. Standard MeshCore private channels normally expose a 32-hex-character (128-bit) key; 64-hex-character (256-bit) keys are accepted as well. Treat this key as a secret. MultiObserver never publishes it to serial diagnostics.
| Command | Description |
|---|---|
get channel.status |
Show whether AlertChannel is off, ready or missing a key/sender. |
set channel.on |
Enable AlertChannel persistently. |
set channel.off |
Disable AlertChannel persistently. |
get channel.key |
Return the saved channel key over the authenticated MeshCore CLI. |
set channel.key <32-or-64-hex> |
Save a private MeshCore channel key. |
test.channel |
Queue test alertchannel, time and concise WDG state (maximum 130 characters). |
After each grace period AlertChannel attempts to queue AlertChannel newStart
with repeater time. Alert messages are encrypted as MeshCore group datagrams
and handed directly to the repeater's existing scoped outbound queue.
MultiObserver does not maintain a second queue, retry alerts, wait for ACKs or
control the radio directly.
test.channel returns QUEUED when MeshCore's native outbound queue accepts
the encrypted message. This is intentionally not reported as SENT or
DELIVERED: actual airtime remains under MeshCore control and AlertChannel
does not wait for a delivery acknowledgement.
Human-readable AlertChannel and WDG-log timestamps use the local repeater time.
The default is Central European time with automatic CET/CEST daylight-saving
changes. Override the POSIX timezone at build time with
-D MULTIOBSERVER_TZ=\"your-POSIX-TZ\". MeshCore packet epochs and MQTT
timestamps remain UTC.
set channel.keyvalidates and persistently stores a 128-bit or 256-bit MeshCore private-channel secret. The key is never printed in serial logs.set channel.onenables delivery. A missing/invalid key, disabled channel or unavailable MeshCore sender makes an alert fail immediately and produces only a short diagnostic; the watchdog itself never blocks.- AC adds the repeater-clock date and time and limits the complete alert text to 130 characters.
- The thin
MOBridgepasses the decoded key and text toMyMesh. MyMeshcalculates the standard channel hash and calls MeshCore'screateGroupDatagram(PAYLOAD_TYPE_GRP_TXT, ...).- The encrypted packet is submitted with
sendFloodScoped(...), so ordering, airtime and actual transmission remain owned by MeshCore's normal outbound dispatcher.
AC intentionally does not know whether the radio later transmitted the packet. There is no AC retry, delivery ACK or parallel queue. This prevents an alert failure from creating a second watchdog/reboot loop and keeps MultiObserver a thin application layer over MeshCore.
Remote CLI uses the configured AlertChannel key and is off by default:
set rcli.on
get rcli.status
set rcli.off
When RCLI READY is shown, send any normal repeater or MultiObserver CLI
command as a message on that private channel, for example get status or
get wifi.status. The existing MeshCore command handler creates one reply,
which is returned through the native outbound queue. MultiObserver adds no
second command table, response queue or retransmission mechanism.
The channel key is the authorization boundary. Every person who has it can run configuration-changing and restart commands, so use a dedicated secret channel.
EastMesh v2026.8.2 is used as a behavior reference, not as the integration architecture. MultiObserver matches its essential V1 network mechanisms: WiFi state transitions, SNTP readiness, MQTT network gating, event/error diagnostics, connection backoff and one-broker-at-a-time startup. EastMesh-only features such as gateway ARP watchdogs, WiFi channel hints and WSS preflight remain outside the current V1 scope.
The Heltec fixes reproduce the observed EastMesh behavior without moving
business logic into MeshCore core: the installer adds only verified
application/build hooks, while MOBridge remains the thin adapter to MO.