agessaman b4daf8ca43 fix(mqtt): close the review's P1 and P2 findings on the F01/F04/F06 work
Review of the branch found one merge-blocking lifecycle hole and three
correctness gaps where the implementation stopped short of contracts the design
had already written down. All four are real; each was confirmed against the
source (two of them against hardware) before anything changed.

**P1 — a quarantined client could still produce a clean bridge stop.** The
cooperative teardown set `_teardown_complete` unconditionally, so a slot whose
`esp_mqtt_client_stop()` had not completed — deliberately skipped by
`destroySlotClients()` and marked Quarantined — still let the trampoline publish
the acknowledgement. The owner then freed the queue and buffers and allowed a
restart while that SDK task might still be running: exactly the ownership
ambiguity StopUnproven exists to remove. The ack is now withheld unless EVERY
client is proven stopped, so one unproven client leaves the whole bridge
unproven. The rule lives in MQTTClientState.h (`mqttStopMayBeAcknowledged`) with
host tests, alongside the state predicates moved out of the bridge.

**P2 — the F04 protection did not cover a client that was still connecting.**
`softDisconnect()` returns ESP_OK immediately when the client is not connected,
so for a slot mid-DNS/TLS/CONNECT it cancelled nothing: the attempt ran on and
its CONNECTED event arrived after the new configuration was applied, and with
callbacks registered once per client and esp-mqtt events carrying no generation,
nothing could tell it from the new attempt's. A reconfigure that lands on a
`Starting` client now stops it, joining its SDK task, before applying the new
configuration. A Connected client still takes the cheap softDisconnect path,
which is where the fragmentation argument applies. One helper
(`closeLiveClientForReconfigure`) so the two call sites cannot drift.

**P2 — a failed renewal bounce still advanced the effective expiry.** Minting
updates `token_expires_at` immediately and the renewal decision read it, so a
bounce that failed looked complete: the next pass saw a fresh future expiry and
never retried, and clearing `last_token_renewal` re-armed nothing. Slots now
carry `applied_token_expires_at` — the expiry of the credential the CONNECTION
is using — which only advances when a connect or reconnect has carried it. A
failed bounce leaves it on the old credential, so the renewal stays due.

**P2 — config-committed and start-accepted were conflated.** `connect()`
returned one result for both, so a start that failed after the configuration had
committed left `applied_config` describing the previous configuration, and the
next recreate-or-reuse decision could reuse a client whose trust policy was not
the one it believed. `applyConfig()` is now its own wrapper operation;
`applied_config` records the commit, activation records the start. The reconnect
ladder resets there too rather than in `teardownSlot()` — the old endpoint's
history still applies until a replacement configuration actually commits.

Two of my own bugs surfaced on hardware while testing this, both fixed here:

- `recreateSlotClient()` called the full `teardownSlot()`, which cleared
  `broker_uri`, the just-minted token and both expiries out from under a
  configuration that had already been decided, so a recreate handed the SDK an
  empty URI and an empty token. It now stops the client and swaps the object,
  touching nothing else, and the apply step refuses to configure a URI that
  changed under it rather than passing it on.
- `stopSlotClient()` quarantined on any non-OK result, but `ESP_FAIL` from
  `esp_mqtt_client_stop()` means "client is in invalid state", i.e. not started:
  there was no task to join, the safest state there is. It was observed
  quarantining healthy clients on hardware. The case that genuinely cannot be
  proven is a stop that never RETURNS, which cannot surface here at all — it
  hangs the task, which is what the bridge-level timeout contains.

Hardware (Heltec V4, 5 live slots): a reconfigure landing on a connecting client
logs `reconfigure during connect - stopping to cancel the attempt`; a broker
holding the CONNACK sees the client close the socket and the disabled slot never
connects; `wss`→`mqtt`→`wss` recreate cycles reconnect each way; a blackholed
endpoint recovers. 499/499 native tests, four envs clean.
2026-09-09 20:03:13 -07:00
2026-06-05 09:49:57 -07:00
2026-04-28 21:39:34 +10:00
2025-01-13 14:07:48 +11:00
2025-06-05 20:35:40 +12:00
2026-02-03 13:47:43 +13:00
2026-03-23 14:26:56 +01:00
2025-01-20 10:20:42 +11:00
2025-01-25 23:09:09 +11:00
2026-03-31 00:51:15 +13:00
2025-03-03 18:08:00 +13:00
2026-06-05 21:25:25 +12:00

About MeshCore

MeshCore is a lightweight, portable C++ library that enables multi-hop packet routing for embedded projects using LoRa and other packet radios. It is designed for developers who want to create resilient, decentralized communication networks that work without the internet.

🔍 What is MeshCore?

MeshCore now supports a range of LoRa devices, allowing for easy flashing without the need to compile firmware manually. Users can flash a pre-built binary using tools like Adafruit ESPTool and interact with the network through a serial console. MeshCore provides the ability to create wireless mesh networks, similar to Meshtastic and Reticulum but with a focus on lightweight multi-hop packet routing for embedded projects. Unlike Meshtastic, which is tailored for casual LoRa communication, or Reticulum, which offers advanced networking, MeshCore balances simplicity with scalability, making it ideal for custom embedded solutions, where devices (nodes) can communicate over long distances by relaying messages through intermediate nodes. This is especially useful in off-grid, emergency, or tactical situations where traditional communication infrastructure is unavailable.

MQTT Observer Setup — Prebuilt observer firmware, docs, and a changelog are at observer.gessaman.com. See the MQTT Implementation Guide for configuration, CLI commands, and troubleshooting.

⚡ Key Features

  • Multi-Hop Packet Routing
    • Devices can forward messages across multiple nodes, extending range beyond a single radio's reach.
    • Supports up to a configurable number of hops to balance network efficiency and prevent excessive traffic.
    • Nodes use fixed roles where "Companion" nodes are not repeating messages at all to prevent adverse routing paths from being used.
  • Supports LoRa Radios – Works with Heltec, RAK Wireless, and other LoRa-based hardware.
  • Decentralized & Resilient – No central server or internet required; the network is self-healing.
  • Low Power Consumption – Ideal for battery-powered or solar-powered devices.
  • Simple to Deploy – Pre-built example applications make it easy to get started.

🎯 What Can You Use MeshCore For?

  • Off-Grid Communication: Stay connected even in remote areas.
  • Emergency Response & Disaster Recovery: Set up instant networks where infrastructure is down.
  • Outdoor Activities: Hiking, camping, and adventure racing communication.
  • Tactical & Security Applications: Military, law enforcement, and private security use cases.
  • IoT & Sensor Networks: Collect data from remote sensors and relay it back to a central location.

🚀 How to Get Started

For developers:

The Simple Secure Chat example can be interacted with through the Serial Monitor in Visual Studio Code, or with a Serial USB Terminal on Android.

⚡️ MeshCore Flasher

We have prebuilt firmware ready to flash on supported devices.

  • Launch https://meshcore.io/flasher
  • Select a supported device
  • Flash one of the firmware types:
    • Companion, Repeater or Room Server
  • Once flashing is complete, you can connect with one of the MeshCore clients below.

📱 MeshCore Clients

Companion Firmware

The companion firmware can be connected to via BLE, USB or Wi-Fi depending on the firmware type you flashed.

Repeater and Room Server Firmware

The repeater and room server firmware can be set up via USB in the web config tool.

They can also be managed via LoRa in the mobile app by using the Remote Management feature.

🛠 Hardware Compatibility

MeshCore is designed for devices listed in the MeshCore Flasher

📜 License

MeshCore is open-source software released under the MIT License. You are free to use, modify, and distribute it for personal and commercial projects.

Contributing

Please submit PR's using 'dev' as the base branch! For minor changes just submit your PR and we'll try to review it, but for anything more 'impactful' please open an Issue first and start a discussion. It is better to sound out what it is you want to achieve first, and try to come to a consensus on what the best approach is, especially when it impacts the structure or architecture of this codebase.

Here are some general principles you should try to adhere to:

  • Keep it simple. Please, don't think like a high-level lang programmer. Think embedded, and keep code concise, without any unnecessary layers.
  • No dynamic memory allocation, except during setup/begin functions.
  • Use the same brace and indenting style that's in the core source modules. (A .clang-format is probably going to be added soon, but please do NOT retroactively re-format existing code. This just creates unnecessary diffs that make finding problems harder)

Help us prioritize! Please react with thumbs-up to issues/PRs you care about most. We look at reaction counts when planning work.

Running unit tests

To run unit tests, run the following command:

pio test --environment native --verbose

Road-Map / To-Do

There are a number of fairly major features in the pipeline, with no particular time-frames attached yet. In very rough chronological order:

  • Companion radio: UI redesign
  • Repeater + Room Server: add ACL's (like Sensor Node has)
  • Standardise Bridge mode for repeaters
  • Repeater/Bridge: Standardise the Transport Codes for zoning/filtering
  • Core + Repeater: enhanced zero-hop neighbour discovery
  • Core: round-trip manual path support
  • Companion + Apps: support for multiple sub-meshes (and 'off-grid' client repeat mode)
  • Core + Apps: support for LZW message compression
  • Core: dynamic CR (Coding Rate) for weak vs strong hops
  • Core: new framework for hosting multiple virtual nodes on one physical device
  • V2 protocol spec: discussion and consensus around V2 packet protocol, including path hashes, new encryption specs, etc

📞 Get Support

  • Report bugs and request features on the GitHub Issues page.
  • Find additional guides and components on my site.
  • Join MeshCore Discord to chat with the developers and get help from the community.
S
Description
No description provided
Readme MIT
39 MiB
0 Stars 1 Watchers 0 Forks
Languages
C++ 42.9%
Python 41%
C 11.8%
Shell 2.3%
JavaScript 1%
Other 0.9%