# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [2.1.1] - 2026-09-21 Two crashes the rig and a user found in 2.1.0, and the rig itself wired in. A node could crash at start-up on an uninitialised listener pointer (#466, reported by a user before the rig saw it), and an ESP8266 serving as a shared gateway could run out of memory answering its own `sendToInternet()` (#469, found by the rig the day the fix for #466 landed). Both are fixed, CI now refuses the class of defect behind #466, and every merge to `main` is flashed onto the rig from here on. **Upgrade if you run 2.1.0**: the #466 crash is at boot, on every node. ### Added - **Every merge to `main` runs on the hardware rig.** `.github/workflows/farm-hil.yml` dispatches the Alteriom farm's HIL suite once the CI/CD Pipeline has passed on `main`, and for a pull request a maintainer labels `run-hil` -- a promise CONTRIBUTING.md had made that nothing kept. The rig had been a manual gate, so #466 reached a user before it reached a board. Needs the `FARM_DISPATCH_TOKEN` secret; without it the job says so and passes. Release builds stay a person's decision on the farm side. - **`mesh.tcpListening()`** -- whether this node's TCP listener exists and is in LISTEN, the state peers depend on and nothing else reported. A node whose listener was never created (#466) or was re-created not listening (#435) is reachable only through connections it made outbound, and nothing can join through it; a health report can now say so on the node itself. `PAINLESSMESH_HAS_TCP_LISTENING` is defined alongside it for code that builds against older releases too. ### Fixed - **An ESP8266 shared gateway could run out of memory answering its own `sendToInternet()`** (#469, found by the rig: both ESP8266 boards died of `Unhandled C++ exception: OOM` in the `gateway.shared.internet` row, on a 76-byte allocation). For a request that originated on the gateway itself, the reply travelled to the application as five copies of the ack -- copied into the deferred task's closure, copied again inside it, serialised to JSON by the `Variant` and parsed back into a third package by the handler -- then the pending request and the result were copied once more on delivery, all on a heap the HTTP client had already spent. The ack is now moved into one heap block and handed straight to the handler, and the request and result are moved on delivery. What the application receives is unchanged. - **A node could crash before serving its first connection: `_tcpListener` was never initialised** (#466). 2.1.0 taught `tcpServerInit()` to keep a listener that already exists instead of re-binding the port, so it now reads the pointer before anything has assigned it. On a stack that held garbage there the null check passed and the `delete` that followed crashed the node at start-up. The pointer defaults to `nullptr`. - **The same gap closed everywhere it existed.** Twenty more pointer and arithmetic members had no default and no constructor setting them: the AP settings a node reports before `init()`; `from`/`dest` on a package built by its type constructor and sent before the application filled them; the fields of `SendStats`, `ConnectionInfo`, `BridgeCandidate`, `MeshChannelCandidate` and `BridgeCoordinationState`; and the book-keeping members of `RTCManager`, `MessageQueue`, `LogClass`, `metrics::Timer` and the desktop `AsyncServer`. Each now has a default member initializer, which holds under every constructor the class has or gains later. - **CI refuses the next one.** `test/ci/check_member_init.py` walks every header under `src/` and fails on any pointer or arithmetic data member declared without a default member initializer, whatever the class's constructors do; `test/ci/test_check_member_init.sh` plants the #466 case and proves the checker catches it and passes clean code. Both run in the Code Quality job. ## [2.1.0] - 2026-09-15 `sendToInternet()` you can build a notifier on (#463). One user's WhatsApp alerts through CallMeBot kept failing, and the hardware rig showed why in the library: a request whose reply was slow was sent again -- one call reached the server four times --, a long reply was cut before the service said what it did, and a chunked reply reached the application with its transfer framing. 2.1.0 issues each request once, retries only what cannot arrive twice, honours Retry-After, gives every attempt one request id, and hands the application the whole result: status, the service's own words, whether a resend is safe, and how many attempts it took. **Upgrade if a node of yours sends to the Internet.** **One behaviour change to read before upgrading:** the library no longer decides from a reply's words whether a service did what you asked. A CallMeBot "Too many requests" page under HTTP 201 is `success == true`, as HTTP says; read the reply in your sketch with the new result callback, as `examples/sendToInternet/callmebot.h` does. Wire-compatible with 2.0 nodes: the new ack and request fields are optional and ignored by older nodes. Validated on the six-family hardware rig, including a real WhatsApp delivered through the mesh and a real CallMeBot refusal reaching the application intact. ### Changed - **`sendToInternet()` no longer retries a request the server may already have.** A read timeout or a connection lost after the request was sent used to count as a "network error" and was retried up to three times; on the hardware rig one timed-out send reached the server four times, which for a message service is four messages, or one message and three refusals. The gateway now tells the origin node whether the identical request may be sent again (`GatewayAckPackage::retryable`, JSON `"retry"`), and says yes only when it cannot have arrived -- connection refused, a failure before the request went out -- or when the server said it did not take it: HTTP 429 and 503. A 500, 502 or 504 is no longer retried. The error of a transport failure that was not retried ends "(the request may have reached the server; not retried)", and resending is the application's decision. Acks from gateways that predate the field keep their old reading, except that a 2xx is never retried. - **The library no longer judges a response body.** 2.0.3 matched CallMeBot's "Too many requests" page inside the gateway and reported that HTTP 201 as a failure. Whether a reply means what an application wanted is that service's language, not HTTP's, so the gateway now applies HTTP's meaning of the status (200, 201, 202 and 204 succeed; 203, 205-299 are unverified failures) and carries the body to the application instead. **A CallMeBot rate-limit refusal under HTTP 201 is therefore `success == true` again at the library level.** The sendToInternet example reads CallMeBot's reply itself (`examples/sendToInternet/callmebot.h`); a sketch that relied on 2.0.3's check should do the same with the new result callback. ### Added - **`sendToInternet()` with an `InternetResult` callback.** Besides `success`, `httpStatus` and `error`, the result carries `response` -- the start of the response body as one line, on success as well as failure -- `retryable`, `attempts` and `messageId`. The three-argument callback is unchanged. The response travels in the ack as `"resp"` and the error excerpt keeps the end of a long body as well as its start, so a verdict after an echo of the request survives (#463): the gateway keeps the first 512 and the last 256 bytes of a body, not only its start, and removes chunked transfer framing -- CallMeBot answers chunked, and its reply reached the application as "a6 ... Message queued ... 0". `PAINLESSMESH_HAS_INTERNET_RESULT` is defined, so code that also builds against older releases can `#ifdef` it. - **Retry-After.** A 429 or 503 retry waits at least as long as the server's `Retry-After` (delay-seconds); a server that asks for more than 60 s gets no automatic retry, and the error says when it wants the request back. The request's timeout moves with the wait, so the invited retry is not timed out first. - **Request ids.** Every attempt at one call carries the same `X-Request-Id` and `Idempotency-Key` header (`pm---`), so a service that honours idempotency keys drops a copy and a log can count retries. The nonce is drawn per call and travels in the request (`"nonce"`), so ids stay unique after the 16-bit message-id counter wraps; a request from an older node keeps `pm--`. Message ids now start at a random point each boot, so the first request after a reboot does not reuse the key of the boot before. - **Test point:** the ledger counts requests per tag and records their request ids, and `/retry-after/{seconds}` refuses a tag once with 429 and `Retry-After`, then accepts it and records whether the retry came early. - **sendToInternet example:** alerts once per O2 episode and at most every 10 minutes, backs off 15 minutes after a CallMeBot refusal, tags the startup message per boot, URL-encodes the phone number, and no longer prints the API key. `callmebot.h` names CallMeBot's "APIKey is invalid" reply (seen from the hardware rig) instead of calling it unrecognised. ### Fixed - **A gateway's own request completed inside its HTTP handler.** A bridge or shared gateway serving its own `sendToInternet()` delivered the result from inside the gateway handler, while its `HTTPClient`, `WiFiClient` and response buffers were still allocated; on an ESP8266 with ~11 KB free the application's callback could not allocate what it built (hardware rig). The local acknowledgment now completes from the scheduler, after the handler has freed. - **`InternetResult::attempts` and `retryable` describe what was sent.** `attempts` counts only a request that left the node, and a request that never did -- refused for want of a mesh or a gateway, or whose every routing attempt failed -- is reported safe to resend. - **`SentBuffer::requestLength(0)` answered 1, not 0.** `buffer_length - 1` leaves room for the terminator `toCharArray()` always writes; on an unsigned zero it wraps to `SIZE_MAX`, `min()` then picks the message, and a caller with no room at all is told one byte is available -- which the method's own contract forbids ("<= the requested length") and `read()` acts on, writing `length + 1` bytes. Found because the randomised buffer scenario draws its length from `runif(0, ...)`: it asked for zero about once in sixty runs and failed CI on pull requests nowhere near the buffer. - **A node that lost its last connection waited 15 s before it scanned** (#459). `connectToAP()` logs "scan rate set to fast" and sets a `0.5 * SCAN_INTERVAL` period -- fifteen seconds, and the shortest of a set of long ones; "slow" is four intervals. For a node that still has a live connection that is soon enough. For one with none it is the whole cost of the outage, and it is paid while its neighbours still route to it until their own `NODE_TIMEOUT`, so unicasts addressed to it are accepted by the sender and dropped. Caught on the hardware rig: an ESP32-C3 closed its only uplink on a momentary nodeSync contradiction, logged the "fast" line 12 ms later, and did not scan for exactly 15 000 ms -- 16.2 s out of the mesh, answering an empty node list, while the scan that eventually ran found five mesh APs at -30 to -58 dBm and associated 1.2 s later. A node with no live connections now scans at once, once per outage (`layout::liveSubs()` is the test, and a node that is simply alone falls back to the interval rather than scanning back to back). ## [2.0.3] - 2026-09-11 `sendToInternet()` fixes from one user's WhatsApp integration (#450, #452, #453), and one the hardware rig found while validating them. **Upgrade if a node of yours is a bridge or relays to the Internet**: on 2.0.2 a bridge's own sends were refused for 30 s after boot, a service that answered a refusal with HTTP 201 was reported as delivered, a destination that does not resolve stalled an ESP32 gateway on every attempt, and a bridge that rebooted as a regular node swallowed every request its peers still sent it. Every fix was reproduced first -- against the new HTTP test point, which CI now runs, or over real TCP on the desktop -- and is covered by a row on the hardware farm. ### Fixed - **A bridge's own `sendToInternet()` was refused for the first 30 s after `initAsBridge()`** (#450). The Internet health check is armed one line after `stationManual()` re-issues `WiFi.begin()`, so its first probe ran while the station was still associating and failed, and the next was a full interval away. Every send from the bridge in that window fell through to the mesh path and was refused with "No active mesh connections" -- #445 again, with a 30 s hole instead of forever. `sendToInternet()` and the retry path now spend one on-demand probe per interval when the flag says no, and the station's got-IP event re-probes at once on a bridge or shared gateway. - **A gateway reported a refusal as delivered, and every failure as a bare number** (#450, #452). The gateway decided on the HTTP status alone and discarded the response body. CallMeBot's WhatsApp API answers "Too many requests" with HTTP 201 and HTTP 203, the same page under both, so a refused message sent through a gateway on 2.0.2 could be reported as delivered; and it answers HTTP 208 to messages it never delivers, which 2.0.2 reported as "Ambiguous response" with nothing to say why. The gateway now reads the start of the body (bounded to 512 bytes and 250 ms). Only 200, 201, 202 and 204 count as delivered, and not when the body says the service refused the request; every other 2xx is a failure. Every failure reaches the origin node's callback with a one-line, tag-free excerpt of the body -- for example `HTTP 201: service refused the request: Oops! Too many requests...` or `HTTP 208: not a delivery the gateway can confirm: ...` -- and is logged at ERROR level, so a sketch on the default log levels sees what the service said. - **A destination whose name does not resolve stalled an ESP32 gateway on every attempt** (#453). A failed lookup surfaced as `connection refused` after the resolver's own patience, which this library cannot bound on ESP32, and every attempt -- the origin node's retries and, since the bridge fix above, the bridge's own sends -- paid it again, until relayed requests timed out on their origin nodes. An ESP32 gateway now resolves the host once, reports `DNS lookup failed for `, and refuses that host for 60 s without another lookup. ESP8266 is unchanged: its core bounds the lookup by the HTTP timeout. The gateway blocking budget is unchanged. - **A bridge that rebooted as a regular node swallowed every Internet request routed to it.** Found on the hardware rig while validating this release. A bridge that reboots, crashes, loses power or is reflashed announces nothing -- only a bridge stepping down in-process sends `leaving` -- so its peers kept it in their bridge list for the 60 s they trust a status, and could prefer it over a live bridge on RSSI. A regular node had no handler for gateway requests and dropped them without a reply; each sender waited out its 30 s request timeout and reported "Request timed out". Every node now answers a gateway request it cannot serve with `Node is not an Internet gateway`; the sender forgets that node as a gateway and sends again at once through the next one, without spending a retry. When it knows no other gateway, the request rides the ordinary retry backoff -- on the rig the live bridge was advertised half a second later -- and fails with `No Internet gateway available: ...` only when none appears. - **Mismatched log format arguments in `routePackage()`** (CodeQL `cpp/wrong-type-format-argument`). A message that failed to parse was logged with its `size_t` lengths through `%d`/`%u` -- the wrong width on 64-bit hosts -- and with its `DeserializationError` object passed through `%u`, undefined behaviour on every platform, in the ArduinoJson 7 path every current build compiles as well as the legacy one CodeQL flagged. ### Changed - **The mock HTTP server is now the gateway test point.** It keeps a delivery ledger (`GET /requests/{tag}`) and emulates CallMeBot's status quirks (`GET /callmebot/whatsapp.php`, profile chosen by `apikey`). The desktop CI job starts it and `PAINLESSMESH_TESTPOINT` points the suite at it, so the library's delivery verdict is checked against what the service actually did, not against a table of status codes. The Alteriom farm's gateway probe serves the same routes. - **`Mesh::sendGatewayAck()` is public and portable.** It moved from the ESP gateway code into `painlessmesh::Mesh`, because every node now needs it to answer a request it cannot serve. - **`Log()` is format-checked in the desktop build.** The test build marks it printf-like, so CI's `-Wall -Werror` rejects an argument that does not match its specifier. Device builds are unchanged: on ESP-IDF 5 `uint32_t` is `unsigned long`, and every `%u` of a node id would warn there. - **`sendToInternet` example.** The startup WhatsApp retries every 30 s until a gateway with Internet is known instead of firing once before a regular node has joined, and the cloud endpoint moved to a `CLOUD_URL` define whose placeholder is skipped with a message rather than reported as `connection refused`. ## [2.0.2] - 2026-09-08 Two gateway defects that reached users on 2.0.1, both in code the desktop test suite cannot compile, and both now asserted on real hardware. **Upgrade if a node of yours is a bridge**: on 2.0.1 a bridge could not use its own uplink at all, and any request that failed below HTTP was reported to the origin node as `HTTP 65535`. ### Fixed - **A bridge could not reach the Internet through its own uplink** (#445). `sendToInternet()` serves a request locally only when `hasLocalInternet()` is true. That flag is set by the Internet health checker, and `initAsBridge()` never started it — only `initAsSharedGateway()` did — so on a bridge it was false for the life of the node. Every request the bridge itself made fell through to the mesh-routing path and failed with *"No active mesh connections"* whenever no peer had joined yet, which is exactly what a single-node sketch does at start-up. `initAsBridge()` now starts the checker, without touching a `setInternetCheckTarget()` the sketch made before `init()`. - **A request that never reached a server was reported as `HTTP 65535`** (#446). `HTTPClient` returns an `int`: positive is an HTTP status, negative is one of its own transport errors (`-1` refused, `-11` read timeout). The gateway stored that in a `uint16_t`, so `-1` wrapped to 65535 — a value that passes `httpCode > 0`. Three things followed: the origin node was told "HTTP 65535", the `errorToString()` branch was unreachable, and `handleGatewayAck()` filed a retryable network failure as a permanent HTTP status. A transport failure now arrives as `httpStatus 0` with the real reason in the error string, which is what the origin node already treats as retryable. - **A lone gateway consumed its retries without issuing a single request.** `retryInternetRequest()` knew only the remote path: it required an active mesh connection and then routed to a discovered bridge, so a gateway serving its own request satisfied neither check and reported *"Max retries exceeded"* in place of the transport error that actually happened — the same dishonest failure as #446, one layer up. A retry whose gateway is this node now goes back to the local handler. ### Changed - The HTTP result classification moved into `gateway::classifyHttpResult()`, so the desktop test suite exercises the same function `wifi.hpp` calls. The previous test duplicated the classification — and duplicated the `uint16_t` with it, which is why it mirrored #446 instead of catching it. ### Validated Both fixes are asserted on the ESP32 farm, not only in desktop tests: a bridge must report Internet on its own uplink, must relay its own request through it, must do so with **no mesh peer at all**, and must return `httpStatus 0` with a real reason for a refused port, an unresolvable host and a destination that answers past the timeout. Those rows did not exist when 2.0.1 shipped — the gateway suite passed on the run that carried both bugs. ## [2.0.1] - 2026-09-07 A packaging and documentation release. **No library behaviour changed**: the mesh, routing, gateway, failover and OTA code of 2.0.1 is 2.0.0's, and the only edit under `src/` is the version macro. It exists to repair two defects in what 2.0.0 *shipped*, both of which cost a user time before they ever compile anything. ### Fixed - **The installation instructions shipped in 2.0.0 name a PlatformIO package that has no 2.0.0.** The README, user guide and documentation site gave `alteriom/AlteriomPainlessMesh`, and that registry owner stops at 1.10.0 — its account is not one this project can publish from. Every automated release since 1.7.6 went out under `sparck75`, which is now what the documentation names. **Name the owner in `lib_deps`**: the bare name `AlteriomPainlessMesh` matches both owners and PlatformIO warns rather than choosing. ```ini lib_deps = sparck75/AlteriomPainlessMesh@^2.0.0 ``` - **The v2.0.0 GitHub release carries no library archive.** This repository publishes immutable releases, so the workflow's upload step — which ran after the release was created — was refused with HTTP 422. The job failed before its release decision, which also skipped the npm, GitHub Packages and PlatformIO jobs; those three were published by hand. The archive is now attached in the same call that creates the release, and the decision to publish is taken before any step that can fail, so a failed upload can no longer suppress the publications. - **PlatformIO publication went under the token's account rather than the organisation.** `pio pkg publish` files a package under the account unless `--owner` names the owner, and the workflow never passed it — which is how the duplicate ownership above arose in the first place. It now passes `--owner`, checks the version against that owner's own version table (the previous check read a `Version:` line `pio pkg show` never prints), publishes the release tag's sources rather than whichever branch was dispatched, and treats an already-listed version as success instead of a failure. - **`scripts/validate-release.sh` reported a version mismatch that was not there.** Without `jq` it fell back to a grep that matches every `"version"` line in `library.json`, dependencies included, so the value it compared was multi-line. It takes the first match now — the top-level one. CI was never affected; it has `jq`. ### Upgrading Nothing in the library behaves differently, so upgrading from 2.0.0 is optional. Take it if you install through PlatformIO, or if you want the release archive attached to the GitHub release. Everything in the 2.0.0 entry below still applies, including its *Before you upgrade* section. ## [2.0.0] - 2026-09-07 painlessMesh 2.0 is a major release. It adds per-message delivery confirmation and a unified send path, and it is the first release whose gateway, failover, routing and radio behaviour was validated on hardware: every entry in the *hardware-validated series* below was found in the serial logs of the Alteriom HIL rig — an ESP32, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-S3 and ESP8266 in one mesh, with a real router and upstream — and confirmed there. The release candidate passed the rig's whole suite (26 scenarios: mesh formation, delivery and acknowledgement, priorities, dedicated and shared gateways, Internet relay, gateway failover, mesh OTA, sustained soak) three times in a row, 25 passed and 1 skipped per run; the skip is the power-cut scenario, which needs per-port USB power the rig does not have. ### Before you upgrade **Wire protocol.** 2.0 adds to the protocol; it does not change what 1.x nodes already send. A 1.x node forwards what it does not understand and ignores fields it does not know, so a mixed fleet keeps working, but the new behaviour only holds end-to-end once every node on the path runs 2.0: - `MESSAGE_ACK` (type 630) — the acknowledgement a 2.0 receiver returns for a message sent with a delivery callback. A 1.x receiver never sends one, so the sender's callback reports `delivered = false` for it. - `msgId` on `SINGLE` and `BROADCAST`, only when a callback was requested. - `prio` on `SINGLE` and `BROADCAST`, only when it differs from normal; a 1.x forwarder ignores it and forwards at normal priority. - `routerChannel` on `BRIDGE_ELECTION` and `BRIDGE_TAKEOVER`, so peers follow an elected bridge to its channel at once; a peer that misses it still recovers by scanning. - `leaving: true` on `BRIDGE_STATUS` when a bridge stops cleanly (`mesh.stop()`), so candidates hold their election within seconds instead of waiting for the bridge's last status to age out. **Upgrade receivers and forwarders before the senders that will rely on delivery callbacks or priorities**, and only read `delivered = false` as a loss signal once the whole mesh runs 2.0. **Defaults that changed.** - `GATEWAY_HTTP_TIMEOUT_MS` was a hardcoded 30 s; it is now derived from `NODE_TIMEOUT` (2000 ms at the stock 10 s watchdog), the captive-portal probe is bounded by `GATEWAY_CAPTIVE_PORTAL_TIMEOUT_MS` (1000 ms) and cached for 60 s, and the DNS probe by `GATEWAY_DNS_TIMEOUT_MS`. A gateway that needs longer must raise `NODE_TIMEOUT` with it; a `static_assert` says so. - A node that finds no mesh re-detects the channel after 2 empty scans (about 30 s) instead of 6 (90 s). - The Arduino core's station auto-reconnect is off for the mesh link on every core; the library reconnects by its own scan rules. A bridge's router link keeps the core's auto-reconnect. - On ESP8266 the library checks free heap every 30 s and logs an `ERROR` below 12 KB with more than one child attached; see the ESP8266 entry. **Arduino cores.** The ESP32 Arduino core 2.0.x and 3.x are both supported; the ESP32-C5 and ESP32-C6 need 3.x. Core 3.x dispatches Wi-Fi events under a lock that forbids reconfiguring the radio from inside a callback; 2.0 processes scan results from the loop for that reason (see the ESP32 entry). ### Added (hardware-validated series) - **`LogClass::setSink()`** (#427) — a sketch whose serial port carries a line protocol can take the library's log lines through a callback and frame them itself, instead of having them printed from the Wi-Fi event task into the middle of its own output. - **`overCapacity()` and `apChildren()`** (#432) — whether an ESP8266 is below 12 KB free with more than one child attached, and how many children are attached, for sketches that want to warn or shed load. - **`setContainsRoot()`** is now what a mesh that contains a bridge should set on every node: it lets a node that is connected to a partition the bridge has left notice it has no root and go looking for the bridge's channel (#427). `initAsBridge()` and the failover path set it. ### Fixed — gateway and failover (hardware-validated series) - **Gateway takeover left the mesh partitioned on the old radio channel (#424)** — a failover candidate correctly moved its AP+STA radio to the Internet router's channel after election, but peers learned only that a takeover occurred, not which channel to follow. They remained disconnected until the slow all-channel recovery scan, exceeding the failover deadline and leaving `getPrimaryGateway()` at zero. Election and takeover packages now carry the candidate's router channel. Peers validate the announcement, discard stale scan state, move both interfaces after the takeover has propagated, and resume discovery immediately. A missed or older takeover message remains compatible with the scan-based recovery path. - **A bridge that stops cleanly says so (#435)** — the backup that booted beside a live primary took its status and held it healthy until the status aged out (`bridgeTimeoutMs`, 60 s), then waited for the 30 s monitor tick, and missed the rig's 120 s promotion window whenever the primary left shortly after it came up. `mesh.stop()` on a bridge now broadcasts a status marked `leaving` before closing its connections; every node forgets that bridge at once (`forgetBridge()`), a candidate checks for a bridge as soon as the startup period allows instead of at the next tick, and the "too soon after last role change" hold schedules a retry for when it ends rather than returning silently. A bridge that loses power still announces nothing; that path is as slow as the status timeout. - **A backup joins the mesh on the router's channel (#435)** — a candidate with router credentials used to pick the strongest mesh AP across a split mesh; it now joins on the router's channel, where a bridge would be. The election's router scan waits on the driver's scanning bit instead of `WiFi.scanComplete()`, whose 20-dwell timeout declared the station's own all-channel scan failed at 2.4 s and let the two scans collide. - **Home is the channel the bridge's status names (#435)** — a rootless node keeps the channel it was last rooted on as home: it does not leave home for a partition elsewhere, and away from home it goes back as soon as the mesh is visible there, whatever the sizes, since a freshly promoted bridge is one AP on the router's channel. Home is learned from the bridge's status message, not from a cached tree that could still carry a bridge that has gone, and it is forgotten after four re-detections that found the mesh elsewhere and no root here. A topology change on the bridge brings its status broadcast forward (at most once per 5 s), so a node joining anywhere in the tree hears the bridge within seconds. - **A leaf at home, or a failover candidate, does not leave a rootless partition (#435)** — the rule that sends a leaf looking for the root had fired on the backup itself and dropped its own link in the middle of the election. - **A failover candidate is the only node that runs the bridge check (#435)** — regular nodes without router credentials no longer schedule elections they cannot join. - **Shared-gateway local Internet health never became operational (#422)** — the TCP probe used by `InternetHealthChecker` was unimplemented on ESP32/ESP8266 and the health task was never started, so `hasLocalInternet()` stayed false for the life of a node and every request it could have served itself went to a mesh gateway. Shared-gateway initialisation now configures and starts the health monitor, a node with its own healthy uplink executes Internet requests locally, and local gateway acknowledgements complete through the pending-request path. - **ESP8266 health check timeout was set in seconds, not milliseconds (#426)** — the 5000 ms check became a 5 ms `WiFiClient` timeout, DNS never resolved inside it, and an ESP8266 shared gateway never reported local Internet. - **A promoted backup's HTTP result could not reach the requester (#423)** — an armed requester-route watchdog is preserved across bounded gateway HTTP work and `GATEWAY_ACK` returns through the request's ingress while the newly promoted route converges. Automatic channel discovery retries when a node first sees no peer, reconfiguring the AP once the channel is known. - **A bridge whose TCP listener is not listening re-creates it, and a node re-initialised in place keeps the one it has (#430, #435)** — `stop()` deleted the listener and the re-bind hit `ERR_USE` for the 2×MSL `TIME_WAIT` (120 s), so a node that re-initialised (a promotion, a return to regular mode) accepted nobody for two minutes. A client accepted while the mesh semaphore is held is closed rather than left half-open, and `tcpServerInit()` logs the listener's state. - **Gateway Internet requests partitioned the mesh around the gateway (#318, #332, #416, #417)** — the gateway relays messages from inside the cooperative TaskScheduler using blocking `HTTPClient` calls, so nothing else ran for the duration while wall-clock time kept passing. With a 30 s HTTP timeout against a 10 s `NODE_TIMEOUT`, every peer watchdog that fell due mid-request fired the instant the scheduler resumed, closing connections to nodes that had never gone missing (*"Internet available via gateway: YES / Mesh connections active: NO"*). The blocking budget is now a wall-clock model derived from `NODE_TIMEOUT` — both socket waits of each HTTP call, the captive-portal probe and the DNS probe — enforced by a `static_assert`, and after a blocking request the gateway postpones every running peer watchdog by exactly the measured stall (`Task::adjust()`), so a genuinely dead peer is still reaped on schedule. One residual is documented rather than closed: ESP32's in-request hostname resolution happens inside the core before the socket timeout applies; `SECURITY.md` states it plainly. - **Captive-portal probe ran on every gateway message** — `detectCaptivePortal()` made an uncached HTTP round trip before *each* mesh→Internet send. It is now cached for `GATEWAY_CONNECTIVITY_CACHE_MS` (60 s) and bounded by `GATEWAY_CAPTIVE_PORTAL_TIMEOUT_MS`. ### Fixed — channel following and the station scan (hardware-validated series) - **Stranded followers find the bridge's channel (#427, #428)** — when a bridge moved the mesh to its router's channel, the nodes behind its direct children stayed connected to each other on the old channel and were gated out of re-detection for good. Re-detection now also runs for a connected node that should have a root and has none; the scan collects every channel the mesh is on and prefers one other than the node's own; the move closes the station link so an orphan leaves its old partition; an unexpected station loss scans at once instead of sleeping out a delay of up to two minutes; a scan that could not start is retried in half an interval instead of five minutes; an association that never gets an address is dropped after half an interval; a connected node that keeps finding the mesh only on its own channel backs off instead of scanning every 15 s; and a stale scan-done event no longer consumes the scan still in flight (#428 restored channel auto-detection after the first fix broke it). - **A connected node changes channel only for a strictly bigger partition; a disconnected one follows the mesh wherever it is (#434, #435)** — a node in a partition had followed any other channel it saw the mesh on, including a lone node still in gateway mode during a teardown. Every follow now waits for a second sighting one scan later (a teardown straggler is seen once, a bridge twice); a rootless node follows a partition that persists whatever its size; a leaf leaves a rootless partition only if the mesh ever had a root; a re-init in place resets the scan state. - **Re-detection with stations under the AP is done a channel at a time (#435)** — an all-channel scan takes the AP off its channel for two to three seconds, and the ESP8266 station under it did not survive that. A node with stations hunts one channel per scan (300 ms dwell, 1.5 s at home between slices) and decides on the own-channel scan after the last slice; a node with nothing under its AP keeps the fast all-channel scan. - **Link loss and re-detection are judged by what was lost (#435)** — an uplink lost in a rooted mesh re-detects the channel at once (the AP most likely left for the bridge's channel); an uplink lost *at home* rescans this channel, where the bridge's AP is, instead of hunting all thirteen (the hunt cost a sender its request); the station drop a node's own channel move causes, and an association attempt that never got an address, are not losses (judged as losses they re-detected the channel just left, found the remnant bigger, and moved back). The half-open guard judges only an attempt still in progress; it had dropped a fresh association by the clock of an attempt made 109 s earlier. - **The core's station auto-reconnect is off on every core (#435)** — on Arduino core 2.x as on 3.x. The core's own reconnect raced the library's scan and re-attached nodes to APs that were leaving; the bridge's router link is the exception and keeps it. - **ESP32 Arduino core 3.x: the scan result is read from the loop, not on the network-event task (#435)** — core 3.x dispatches Wi-Fi callbacks under the lock it also takes in `removeEvent()`, and the library ran `scanComplete()` inside the scan-done callback, where a channel follow restarts the AP and waits for events only the blocked task can deliver. Every ESP32-C5 and ESP32-C6 that followed the bridge's channel had gone silent for the rest of its run — still answering the sketch, never scanning again — and `stop()` then blocked on the same lock. The callback now only yields the station task to `scanComplete()`. ### Fixed — routing (hardware-validated series) - **A connection that has already dropped no longer refuses a live one, and is no longer a route (#429)** — a closed connection stays in the layout until the next cleanup, and `handleNodeSync()` had turned away a working direct connection on its authority, moments before erasing it. Only live connections count as routes now, for the duplicate check and for every send; `write()` on a closed connection returns `false` instead of queueing into the void (27 of 33 unacknowledged deliveries correlated on the rig had never arrived). A partitioned leaf that sees nodes it has no route to on two consecutive scans reconnects toward them; interior nodes do not jump, since dropping an interior link fragments the subtree it carries. - **A node that comes back is not refused for where it used to be (#433)** — after any restart, the returning node was associated by each AP in turn and dropped a second later, for 30–100 s, because a neighbour's tree still listed its old place. The loop check is now the tree the arriving node presents; a stale direct link is closed at once and a stale place in a neighbour's tree is forgotten (`layout::forget()`). - **A node is in one place (#435)** — when a neighbour's sync presents the nodes below it, every other neighbour's cached tree forgets them (`layout::forgetAll()`); every board on the rig had carried one node twice and routed by the older copy. A restated sync is not news: a neighbour's sync is adopted only when the tree it presents differs from its last one (a fingerprint per neighbour, covering the time-authority flag), which ended a sync storm of one exchange every 30–80 ms between two claimants; and a neighbour that stops presenting a node lets the others' restatements back in, so a pruned node can return. A stale mention of this node in a presented tree is a loop only if there is another live route to the presenter. ### Fixed — ESP8266 (hardware-validated series) - **The ESP8266 is specified for small meshes, or as a leaf in larger ones (#432)** — measured as an interior node of a seven-node mesh it runs at 10–13 KB free, and below about 8 KB a single 8 KB package or one OTA part fails to allocate; every ESP32 family holds within a few percent of its starting heap in the same mesh. Configure a leaf with `init(..., maxconn = 0)`. The library checks every 30 s on ESP8266 and logs an `ERROR` below 12 KB free with more than one child attached. ### Security - **Corrected a false claim about gateway TLS.** The source comment on `initGatewayInternetHandler()` asserted that "ESP32 uses default SSL settings with certificate validation". Nothing in `src/` ever backed that: there is no `setCACert`, no certificate bundle and no fingerprint API anywhere in the library, and the ESP32 path calls bare `http.begin(url)`. Gateway HTTPS is transport encryption **without** peer authentication on both targets. No behaviour changed — only the claim. - **Added a threat model to `SECURITY.md`** covering mesh membership (one shared password, no per-node identity or revocation), OTA (MD5 is an integrity check against an unauthenticated announcer, not a signature), gateway TLS, and the absence of rate limiting — along with which of these are known-and-documented rather than reportable vulnerabilities. ### Packaging and examples - **`mqttBridge` example fails to compile in Arduino IDE (#398)** — the `PubSubClient` library was missing from `library.properties`'s `depends` field, so installing this library through the Arduino IDE Library Manager never pulled in `PubSubClient`. It is now listed, the example says so, and `examples/mqttBridge/platformio.ini` pins the same `knolleary/PubSubClient` package used by `examples/bridge`. - **`examples/alteriom/mppt_example` never compiled** — its sketch was named differently from its directory, so the CI example loop skipped it, and it included two headers from the parent example directory by bare name, which the Arduino build cannot resolve. The sketch is now `mppt_example.ino`, carries the two headers it needs, and is compiled for esp32 and esp8266 on every PR like the other twenty. - `keywords.txt` now lists the bridge, gateway, failover, queue and capacity API so the Arduino IDE highlights it. - **PlatformIO package owner.** 2.0.0 is published as `sparck75/AlteriomPainlessMesh`, the owner every automated release since 1.7.6 went under. The registry also holds `alteriom/AlteriomPainlessMesh`, which stops at 1.10.0: its account is not one the project can publish from. Name the owner in `lib_deps`; the bare name matches both. ### Added (post-review series) - **Unified send path: `SendOptions` (#384)** — `sendSingle()` and `sendBroadcast()` gained overloads taking a `painlessmesh::SendOptions{priority, ackCallback, ackTimeoutMs}` struct, so a message can be both prioritized and delivery-confirmed in one call — something the separate priority and ack overload families could not express. All pre-existing overloads still compile and now delegate to the unified path. - **Priority is carried across hops (#384)** — the priority level is now serialized on the wire (as `"prio"`, only when it deviates from NORMAL, so default sends carry zero overhead) and every forwarding node re-enqueues the package at the sender's priority. Previously priority only affected the first hop's transmit queue and was silently dropped on forwarding. Pre-2.0 nodes ignore the field and forward at normal priority. Named constants `PRIORITY_CRITICAL/HIGH/NORMAL/LOW` are exposed in `painlessmesh::protocol`. ### Fixed (post-review series) - **Gateway blocking budget is now a wall-clock model, DNS included (#416)** — `gatewayBlockingBudgetMs()` counts **both** socket waits of each HTTP call (request/header read plus body read — `HTTPClient::setTimeout()` bounds one wait, not a whole call) for the destination request *and* the captive-portal probe, plus a new `GATEWAY_DNS_TIMEOUT_MS` term for the DNS reachability probe. On ESP8266 the probe now uses the `hostByName()` timeout overload; on ESP32, whose core has no resolver timeout, the standalone probe is skipped and the (timed) captive-portal probe establishes reachability instead. **Defaults changed to keep the honest budget inside `NODE_TIMEOUT`:** at the stock 10 s watchdog, `GATEWAY_HTTP_TIMEOUT_MS` is now 2000 ms (was 5000) and `GATEWAY_CAPTIVE_PORTAL_TIMEOUT_MS` 1000 ms (was 2000); raise them together with `NODE_TIMEOUT` if your endpoint needs longer — the `static_assert` enforces the pairing. One residual is documented rather than closed: ESP32's in-request hostname resolution happens inside the core before the socket timeout applies and cannot be bounded there; SECURITY.md states it plainly. - **Gateway watchdog compensation now equals the measured stall (#417)** — `gateway::refreshPeerWatchdogs()` takes the measured blocking duration and postpones each running peer watchdog by exactly that long via `Task::adjust()`, instead of restarting every watchdog from zero on every exit path. The full reset was strictly more generous than the time the scheduler actually lost, and the excess starved the reaper: a genuinely dead peer stayed connected indefinitely as long as any *live* peer generated gateway traffic more often than `NODE_TIMEOUT`. Paths that never blocked now compensate nothing. Regression-tested in `catch_gateway_watchdog.cpp`, including the dead-peer-under-continuous- traffic case. - **Outbound send buffer is bounded (#388)** — each connection's `SentBuffer` now holds at most `PAINLESSMESH_MAX_SENT_BUFFER_MESSAGES` (default 64, build-time overridable) messages. Previously a peer that stopped draining (stalled TCP connection) grew the outbound list until allocation failed on the ESP8266 heap. At the cap, an incoming message evicts the newest message of a strictly lower priority class (mirroring `MessageQueue::makeSpace()`); if nothing lower-priority is queued, the push is rejected and the send reports failure. Drops are counted in `getStats().dropped`; a partially-transmitted message is never evicted. ### Removed (post-review series) - **`MessageTracker` dead code (#386)** — `message_tracker.hpp` defined a full dedup/ack-tracking class that was `#include`d but never instantiated or called anywhere in the tree, costing compile time and flash in every build. Removed together with its unit test. Broadcast flood dedup, the integration it was meant for, remains future work with its own design pass. - **Routing no longer copies the connection list per packet (#387)** — every `router::` send/broadcast/forward took the mesh layout **by value**, copying a `std::list` of `shared_ptr`s (one heap allocation per connection) on every packet sent, broadcast, or forwarded. All routing functions now take the layout by const reference — measurable allocation and fragmentation relief on ESP8266. Feature release adding per-message delivery confirmation (issue #379). The release is a major version bump because it introduces a new wire-protocol message type: pre-2.0 nodes forward acknowledgment packets but never send them, so delivery confirmation only works reliably once every participating node runs 2.0.0. All existing sketches compile and behave unchanged. ### Mixed-fleet rollout order During a rolling upgrade, a v1.x node never replies with a `MESSAGE_ACK`, so a v2.0 sender's delivery callback fires `delivered = false` against every un-upgraded peer — indistinguishable from real packet loss. This is not a bug in the ACK feature; it is the expected behavior of a mixed fleet. **Upgrade leaf/receiver nodes before the senders that will use the ack callbacks**, and only rely on `delivered = false` as a loss signal once the whole mesh runs 2.0.0. The same applies to cross-hop priority: pre-2.0 forwarders ignore the `prio` field and forward at normal priority, so priority guarantees only hold end-to-end on an upgraded path. ### Added - **Per-message delivery confirmation and acknowledgment API (#379)** — `sendSingle()` and `sendBroadcast()` gained overloads that accept a `painlessmesh::ack::deliveryCallback_t` callback and an acknowledgment timeout (default 5000 ms). When a callback is provided the outgoing message is tagged with a unique `msgId`, the receiving node automatically replies with a `MessageAckPackage` (new protocol type 630, routed as a SINGLE package so it traverses multiple hops), and the callback fires with `delivered = true` plus the measured round-trip latency — or `delivered = false` when the timeout elapses. Broadcast tracking snapshots the mesh layout at send time and fires the callback once per expected node. - `checkAcks()` — non-blocking poll that processes acknowledgment timeouts and returns the number of messages still pending (timeouts are also processed automatically inside `mesh.update()`). - `pendingAcks()` — number of messages still awaiting acknowledgment. - New header `painlessmesh/ack.hpp` with the platform-independent `AckTracker` (unit-tested, uint32 wraparound safe) and `MessageAckPackage`. - Arduino examples `reliableSensorLogging` (buffered retries until the gateway confirms) and `commandControl` (per-node broadcast confirmation). - Unit tests (`catch_message_ack.cpp`) covering serialization, ack matching, timeout, duplicate/unknown acks, broadcast fan-in and clock wraparound, plus an end-to-end multi-node scenario in the TCP integration suite. ### Changed - `protocol::Single` / `protocol::Broadcast` carry an optional `msgId` field. It is only serialized when delivery confirmation was requested, so plain sends have zero added wire overhead. - `protocol::Variant` gained lightweight `from()` and `msgId()` field peeks; the receive-path ACK handlers use them instead of materializing a full package (no per-message payload copy on the hot path). ### Hardening (post-review, pre-release) A full adversarial review of the ACK feature before release led to: - `AckTracker::expire()` now collects and erases expired entries before firing any callback — a delivery callback that reentered the tracker (retry `track()`, `checkAcks()`, or `clear()` via `mesh.stop()`) could previously invalidate the live iterator (use-after-free). - `mesh.stop()` reached from inside a scheduler callback no longer deletes the internally-owned `Scheduler` out from under its own `execute()` (same bug class as #373); the ack poll task is also disabled before its handle is cleared so a reentrant stop cannot orphan it. - Message ids are seeded from `validation::SecureRandom` at `init()` — previously the counter restarted at 1 every boot, so a delayed pre-reboot ACK could confirm a fresh message (false `delivered = true`). - Pending acknowledgments are capped at `PAINLESSMESH_MAX_PENDING_ACKS` (default 32, build-time overridable); sends beyond the cap are rejected instead of growing the tracker unbounded on the ESP8266 heap. - Broadcast ACK replies are staggered by nodeId within a 50 ms window so an N-node broadcast does not converge N simultaneous ACK unicasts on the sender. - The ack timeout poll interval is build-time configurable (`PAINLESSMESH_ACK_CHECK_INTERVAL_MS`, default 100 ms) and its battery/light-sleep implications are documented. ### CI / packaging fixes - Fixed the Arduino example-compile loop in CI (`ci.yml`): a quoted glob meant **no example sketch was ever compiled** — the job reported green while compiling nothing. All examples now build for esp32 and esp8266 on every PR. - Added the two new examples to `library.json`'s `examples` array so they appear in the PlatformIO registry listing. - `doxygen/Doxyfile` `PROJECT_NUMBER` bumped from the stale v1.6.1 to v2.0.0; stale 1.6.1 install snippets in the wiki docs updated. - Wiki sync now publishes `docsify-site/` documentation (it previously copied from a `docs/` directory that does not exist) and triggers on docsify changes. - Removed dead links from the docsify sidebar. ## [1.10.0] - 2026-08-12 Feature release making the TCP connect-retry envelope tunable per mesh instance (#378), alongside two documentation corrections that retire long-standing claims the library never actually implemented (#385) and an example build fix (#360). **Upgrading is behaviour-neutral.** Every new setting defaults to the value that was previously hardcoded, so a sketch that does not call `setTcpRetryConfig()` behaves exactly as it did on 1.9.21. Nothing was removed: the deprecated queue macros keep their historical values for source compatibility. > **Note for npm users:** v1.9.21 was never published to npm — the `NPM_TOKEN` > used by CI had expired (#381), which failed the npm publish job while the > GitHub Release, GitHub Packages, PlatformIO and Arduino channels all succeeded. > npm's previous version is therefore **1.9.20**, and upgrading from npm brings > in both 1.9.21 and 1.10.0. See the 1.9.21 entry below for what that release > contained — it was a crash-fix release, and npm users have been missing it. ### Added - **User-configurable TCP retry parameters (#378)** — the five TCP connect retry values that were hardcoded as `static const` in `src/painlessmesh/tcp.hpp` are now tunable per mesh instance via `mesh.setTcpRetryConfig()` / `mesh.getTcpRetryConfig()`, using the new `painlessmesh::tcp::TcpRetryConfig` struct (`maxRetries`, `retryDelayMs`, `stabilizationDelayMs`, `exhaustionReconnectDelayMs`, `failureBlockDurationMs`). This lets latency-sensitive meshes (see discussion #368), high-reliability industrial deployments and battery-powered nodes each pick their own retry envelope without forking the library. The struct's defaults are spelled as the existing constants, so **behaviour is unchanged for any sketch that does not call the new setter**, and the constants themselves remain in place. `maxRetries` is clamped to 10 and `retryDelayMs` to 50–60000 ms, since an unbounded retry count is a heap/recursion hazard and a zero delay produces a hot reconnect loop; the remaining fields accept 0 as a meaningful "disable this step" value. New `examples/tcpRetryConfig/` demonstrates real-time, high-reliability and battery-saver profiles. ### Changed - **`MessageQueue` documented honestly as a manual buffer (#385)** — removed the "messages are automatically delivered when connection is restored" claim from `MessageQueue` and the `mesh.enableMessageQueue` / `queueMessage` / `flushMessageQueue` doc comments. Nothing in the library ever transmitted queued messages or observed connectivity changes; the app has always owned the send loop. The docs now say so, and the `flushMessageQueue` example shows the intended pattern of wiring the drain into `onLocalInternetChanged`. ### Deprecated - **Compatibility queue macros kept as ignored no-ops (#385)** — `MIN_FREE_MEMORY` and `MAX_MESSAGE_QUEUE` remain defined in `painlessmesh/configuration.hpp` (and `test/boost/Arduino.h`) for source compatibility, but nothing in the library reads them. They were placeholders for the auto-flush behavior that never landed. `MessageQueue` has always taken its own per-instance `maxSize` constructor argument. Their historical default values (`MIN_FREE_MEMORY 4000`, `MAX_MESSAGE_QUEUE 50`) are preserved so any downstream code that referenced the macros keeps its prior behavior. ### Fixed - **`bridge_failover` example failed to compile (#360)** — the two `mesh.onBridgeCoordination*` lambdas referenced `plugin::BridgeCoordinationPackage` with a bare `plugin::` prefix, but `painlessMesh.h` only lifts `painlessmesh::logger` to global scope, so the type did not resolve (`'plugin' does not name a type`). Both lambda parameters are now fully qualified as `painlessmesh::plugin::BridgeCoordinationPackage`, matching the convention used across every other example (otaSender, namedMesh, alteriom_*). ## [1.9.21] - 2026-08-04 Crash-fix release resolving a family of use-after-free bugs in the task and TCP-connection lifecycle. Root-caused and fixed by @vaz82 (PR #376) with reports and field testing from @fidla73 and @miloshev (issue #373); finalized with TaskScheduler's native self-destruct mechanism and regression coverage. ### Fixed - **Use-after-free in `Task::disable()` on connection teardown (#373)** — `scheduleAsyncClientDeletion()`'s cleanup task deleted itself inside its own `onDisable` callback, but TaskScheduler's `Task::disable()` writes to the task object after `onDisable` returns. Crashed nodes (StoreProhibited, `EXCVADDR 0x8`) on every peer disconnect. The cleanup task now uses TaskScheduler's `_TASK_SELF_DESTRUCT` support (enabled in `painlessTaskOptions.h`): the Scheduler deletes the task from within `execute()`, safely outside the `disable()` call stack. - **`PackageHandler::stop()` destroying the currently-executing task** — when `stop()` runs from within a task's own callback (bridge promotion path), it destroyed that task's closure mid-execution via `setCallback(NULL)`/`shared_ptr` release. `stop()` now accepts the scheduler, detects the running task via `getCurrentTask()`, and leaves it in `taskList` for safe reuse by `addTask()`. - **Stale `_pcb` window in `~BufferedConnection()`** — `client->close()` was skipped when `freeable()` returned true, leaving a non-null-but-stale pcb that lwIP's timers could recycle during the deferred-deletion window (`heap_caps_free`/`memp_free` assertion failures, `tcp_arg()` wild-pointer stores). `close()` is now called unconditionally at destruction. - **`onError`/`onConnect` double-handling race in `tcp::connect()`** — if WiFi dropped as the TCP handshake completed, AsyncTCP could fire both callbacks for the same `AsyncClient`, handing it to two owners and scheduling its deletion twice. A shared claim guard now ensures exactly one callback processes the client. - **Bridge promotion state capture** — the deferred stop/reinit lambda in `promoteToBridge()` (and the isolated-node variant) now captures mesh credentials, scheduler, and callback by value so `stop()` cannot mutate them before the reinit reads them. - **Off-by-one buffer overflow in `ReceiveBuffer::push()`** — when a received chunk was ≥ `TCP_MSS`, the null terminator was written one byte past the end of the shared temp buffer, corrupting adjacent memory on every large read. Found by the new AddressSanitizer CI job on its first run; `read_len` now reserves one byte for the terminator. ### Added - Regression test `catch_connection_cleanup.cpp` covering the #373 schedule → fire → self-destruct cleanup lifecycle and `~BufferedConnection` churn. - AddressSanitizer job in CI (gcc + `-fsanitize=address`) so use-after-free and double-free regressions in the task/connection lifecycle fail the build instead of crashing devices in the field. ## [1.9.20] - 2026-03-27 ### Added - **Bridge Coordination Monitoring Callbacks** - User-facing API for observing bridge coordination events - `mesh.onBridgeCoordination(cb)` — fires on every coordination message (~30s per bridge) - `mesh.onBridgeCoordinationChanged(cb)` — fires on state changes: "new", "updated", "lost" - Works on both bridge and non-bridge nodes (monitoring is not bridge-only) - Lost bridge detection with 60-second timeout - 5 new test scenarios with 18 assertions for change detection logic ### Fixed - **Full Repo Cleanup** - Comprehensive audit and cleanup of the entire codebase (#357) - Fix double-free of mScheduler in ~Mesh() destructor - Fix int8_t RSSI aggregation overflow (widened to int32_t) - Fix Task object memory leak in scheduleAsyncClientDeletion - Replace blocking delay(1000) with yield-based approach in bridge init - Add max-iteration guard to getPathToNode (prevents infinite loop) - Move static vars from connection.hpp to connection.cpp (ODR violation fix) - Initialize shouldContainRoot to false (undefined behavior fix) - Fix getAccurateTime() to always return seconds - Fix cancelInternetRequest reentrancy via scheduled callback - Widen BFS hop counter from uint8_t to uint16_t - Remove dead revert path in promoteToBridge - Cap knownBridgePeers at 32 entries - Increase WiFi event semaphore timeout from 100 to 1000 ticks - Inline getGatewayCount() to avoid temporary vector allocation - Remove MessageQueue::reserve(1000) (wasted heap on ESP8266) - Fix Timer::elapsed_us() to return microseconds on ESP32 - Cache hasActualInternetAccess() with 60s TTL - Remove ~3,600 lines of dead code (unused classes, stubs, AI-generated shelf code) - Remove 40 AI-generated markdown files from root directory - Delete abandoned documentation systems (docs-website/, website/) - Fix all CI/CD workflow action versions - Remove 15 fake test files (only REQUIRE(true) assertions) - Fix millis()/micros() test mocks to prevent uint32_t overflow - **Documentation Consistency** - Feature audit and doc fixes (#358) - Fix README version (1.9.17 -> 1.9.20) - Fix 6 broken documentation links - Fix Message Queue API examples to use real enableMessageQueue/queueMessage API - Remove non-existent onBridgeCoordination callback from examples - Fix EnhancedStatusPackage type ID comment (203 -> 604) - Rewrite Quick Start example to use TaskScheduler instead of delay() - Remove dead conditional includes from painlessMesh.h - Add missing examples to library.json ## [1.9.19] - 2025-12-21 ### Fixed - **Gateway Connectivity Error Non-Retryable Fix** - Infrastructure errors no longer waste time with futile retries - Gateway connectivity errors ("Router has no internet", "Gateway WiFi not connected") are now non-retryable - Distinguishes between infrastructure issues (need user fix) and transient errors (can retry) - Provides immediate failure feedback instead of retrying for ~14+ seconds - Saves battery and network resources by avoiding retries that can't succeed - Clear error messages help users identify and fix infrastructure problems quickly - Transient errors (HTTP 203, 5xx, 429, timeouts) still retry with exponential backoff - New comprehensive test suite with 25 assertions covering retry logic - Documentation: ISSUE_GATEWAY_CONNECTIVITY_NON_RETRYABLE_FIX.md - Fully backward compatible: No API changes required ## [1.9.18] - 2025-12-21 ### Added - **Internet Connectivity Check Enhancement** - Bridge nodes now verify actual internet access before processing HTTP requests - Added `hasActualInternetAccess()` function that performs DNS resolution to detect router internet issues - Gateway checks both WiFi association AND DNS resolution before attempting HTTP requests - Provides early failure detection when router has no WAN connection - Clear, actionable error messages help users troubleshoot connectivity problems: - "Gateway WiFi not connected" - ESP not associated with WiFi network - "Router has no internet access - check WAN connection" - WiFi OK but router offline - Minimal performance impact: ~100ms DNS check vs 30+ second HTTP timeout - New test suite with 22 assertions covering connectivity scenarios - Documentation: ISSUE_INTERNET_CONNECTIVITY_CHECK.md - Backward compatible: No API changes required ## [1.9.17] - 2025-12-21 ### Changed - **Documentation Enhancement** - Professional-grade documentation improvements - Removed all phase terminology (Phase 1, Phase 2) for clearer product positioning - Reorganized package types in proper numerical order (200-614) for better discoverability - Enhanced package type descriptions with clearer use cases and benefits - Improved Advanced Features section with comprehensive feature list - Updated README to production-level quality suitable for enterprise adoption - Clarified message type taxonomy (Protocol-Level vs Application-Level) ### Documentation - **Package Type Organization** - All 19 application-level package types now listed in numerical order - Core IoT Packages (200, 202, 400) - Advanced Monitoring (204, 605) - Mesh Topology & Management (600-604) - Bridge Failover & High Availability (610-614) - **Feature Clarity** - Advanced features now prominently displayed including: - Broadcast OTA distribution - MQTT bridge integration - Automatic bridge failover - Multi-bridge coordination - Message queueing for offline resilience ## [1.9.16] - 2025-12-20 ### Changed - **Documentation Improvements** - Enhanced release process documentation and repository structure - Updated release guide with clearer instructions - Improved version management documentation - Enhanced code organization and maintainability ### Fixed - **Code Quality** - Minor code refinements and optimizations - Improved code consistency across the codebase - Enhanced error handling in edge cases - Optimized memory usage in critical paths ## [1.9.15] - 2025-12-19 ### Changed - **Release Consolidation** - Consolidated v1.9.13 and v1.9.14 fixes into unified v1.9.15 release - Gateway Connection Timeout fix for slow HTTP APIs (WhatsApp/CallmeBot) - ESP32-C6 AsyncClient deletion spacing fix for heap corruption prevention - Both fixes fully tested and production-ready - Complete backward compatibility maintained ## [1.9.14] - 2025-12-19 ### Fixed - **Gateway Connection Timeout During Long HTTP Requests** - Fixed `sendToInternet()` timeouts with slow APIs like CallmeBot WhatsApp - **Issue**: Mesh connections closed after 10s while HTTP requests could take 30s, causing "Request timed out" errors even when messages were successfully delivered - **Fix**: Bridge now disables connection timeout during HTTP request processing, ensuring ACK delivery regardless of request duration - **Impact**: WhatsApp/CallmeBot users and other slow API integrations now receive proper acknowledgments - **Compatibility**: Fully backward compatible, no API changes required - See GATEWAY_CONNECTION_TIMEOUT_FIX.md for detailed analysis - **Hard Reset on ESP32-C6 - Insufficient AsyncClient Deletion Spacing** - Fixed ESP32-C6 heap corruption crashes caused by insufficient spacing between AsyncClient deletions during network disruptions - **Root Cause**: The 250ms spacing between AsyncClient deletions was marginally insufficient for ESP32-C6 hardware, which uses AsyncTCP v3.3.0+ and has different timing characteristics due to RISC-V architecture and enhanced cleanup validation - **Symptom**: Device crashes with "CORRUPT HEAP: Bad head at 0x40838a24. Expected 0xabba1234 got 0x4081fae4" even with deletions spaced 264ms apart (only 14ms above minimum), particularly during TCP retries, channel changes, and sendToInternet() operations - **Hardware Specific**: ESP32-C6 requires more cleanup time due to: - AsyncTCP v3.3.0+ with additional validation steps - RISC-V architecture vs. Xtensa (different instruction timing) - Enhanced heap allocator with more aggressive corruption detection - Different WiFi stack and memory management implementation - **Solution**: Increased `TCP_CLIENT_DELETION_SPACING_MS` from 250ms to 500ms - Provides 2x safety margin for universal ESP32 compatibility - Accommodates ESP32-C6, ESP32-S2/S3, ESP32-C3, ESP32-H2, and future variants - Maintains backward compatibility with ESP32/ESP8266 - Minimal performance impact (250ms additional delay in worst-case scenarios) - **Analysis**: Crash occurred at 264ms spacing, demonstrating 250ms was too close to minimum requirement for ESP32-C6; 500ms provides adequate safety margin (89% buffer vs. observed crash) - **Testing**: All test suites pass (68+ assertions in TCP/connection tests, 1000+ total assertions) - **Documentation**: Added ISSUE_HARD_RESET_ESP32C6_SPACING_FIX.md with detailed ESP32-C6 analysis, architecture comparison, and AsyncTCP v3.3.0+ timing requirements - **Impact**: Eliminates critical heap corruption on ESP32-C6 and provides enhanced safety for all ESP32 variants ## [1.9.13] - 2025-12-19 ### Fixed - **HTTP 203 "Permanent Response" Issue with sendToInternet()** - Fixed issue where HTTP 203 (Non-Authoritative Information) responses from APIs like Callmebot WhatsApp appeared "permanent" without automatic recovery - **Root Cause**: HTTP 203 responses (indicating cached/proxied responses) were correctly identified as failures but treated as terminal - requests were immediately removed from the pending queue without retry - **Symptom**: User sees repeated "❌ Failed to send WhatsApp: Ambiguous response - HTTP 203..." messages with no automatic recovery, requiring manual intervention - **Solution**: Modified `handleGatewayAck()` to implement intelligent retry logic for retryable failure types: - HTTP 203 (Non-Authoritative Information) - cached/proxied responses, often temporary - HTTP 5xx (Server Errors) - transient server issues (500, 502, 503, 504, etc.) - HTTP 429 (Too Many Requests) - rate limiting with exponential backoff - HTTP 0 (Network Errors) - connection failures, timeouts - Non-retryable: HTTP 4xx client errors (except 429), HTTP 3xx redirects - **Retry Behavior**: Uses exponential backoff (2s, 4s, 8s, 16s...) with configurable max retries (default: 3) - **Testing**: Added comprehensive test coverage (50 assertions in 7 test cases) for retry classification and behavior - **Documentation**: Added ISSUE_HTTP_203_RETRY_FIX.md with detailed analysis, examples, and HTTP 203 explanation - **Impact**: Automatic recovery from temporary API caching issues, eliminating the "permanent" failure problem - **API Compatibility**: Fully backward compatible, no breaking changes, existing code works unchanged - **Hard Reset on Node - AsyncClient Deletion Race Condition** - Fixed ESP32/ESP8266 heap corruption crashes caused by race condition in deletion spacing logic during network disruptions - **Root Cause**: The `scheduleAsyncClientDeletion()` function was updating `lastScheduledDeletionTime` at BOTH scheduling time (line 111) and execution time (line 130), creating a race condition where scheduler jitter could cause deletions to execute with insufficient spacing - **Symptom**: Device crashes with "CORRUPT HEAP: Bad head at 0x40831d54. Expected 0xabba1234 got 0x4081fae4" even with 250ms spacing constant, particularly during network disruptions, TCP retries, or WiFi reconnection cycles - **Race Condition Scenario**: When Task A scheduled for time T1 executes late at T1+jitter, it updates `lastScheduledDeletionTime` to T1+jitter, potentially AFTER Task B's scheduled time (which was calculated based on T1), causing Task B to execute with less than 250ms spacing - **Solution**: Removed the execution-time update of `lastScheduledDeletionTime` in the task callback, relying solely on the scheduling-time update - Ensures consistent, predictable spacing based on planned execution times - Eliminates race condition where execution-time updates could "rewind" the timestamp - Makes spacing calculation immune to scheduler jitter - Guarantees minimum 250ms spacing between AsyncClient deletions in all scenarios - **Why This Works**: By only updating at scheduling time, subsequent deletions are always spaced from the PREVIOUS deletion's planned time, not its actual execution time, providing conservative spacing guarantees even with scheduler jitter - **Testing**: All test suites pass (1000+ assertions), including TCP retry (52), connection (3), and timing tests (7) - **Documentation**: Added ISSUE_HARD_RESET_DELETION_RACE_FIX.md with detailed mathematical proof and race condition analysis - **Impact**: Eliminates heap corruption crashes during network disruptions, enables stable operation through TCP retries and WiFi reconnection cycles ## [1.9.12] - 2025-12-18 ### Fixed - **Hard Reset During Bridge Operations - AsyncClient abort() Timing Issue** - Fixed ESP32/ESP8266 heap corruption crashes during TCP connection failures and bridge operations - **Root Cause**: Calling `client->abort()` synchronously before scheduling deferred AsyncClient deletion (1000ms+ later) left the client in an inconsistent state where AsyncTCP's internal cleanup tried to access the aborted client - **Symptom**: Device crashes with "CORRUPT HEAP: Bad head at 0x40838cdc. Expected 0xabba1234 got 0x4200822e" and "assert failed: multi_heap_free multi_heap_poisoning.c:279" during connection cleanup - **Solution**: Removed synchronous `abort()` call from `~BufferedConnection()` destructor - The existing `close()` and `close(true)` calls are sufficient for connection termination - According to AsyncTCP best practices, `abort()` should only be called immediately before `delete`, not before a deferred deletion - Eliminates the 1000ms window where AsyncTCP tries to clean up an aborted but not-yet-deleted client - **Testing**: All test suites pass (1000+ assertions), including TCP retry, connection, and mesh connectivity tests - **Documentation**: Added ISSUE_ABORT_TIMING_FIX.md with detailed AsyncTCP best practices analysis - **Impact**: Completes the AsyncClient lifecycle management improvements, eliminating the last known heap corruption scenario in connection cleanup ## [1.9.11] - 2025-12-18 ### Fixed - **Hard Reset on Bridge Promotion - Unsafe addTask After stop/reinit** - Fixed ESP32/ESP8266 hard resets (Guru Meditation Error: Load access fault) immediately after bridge promotion in both isolated and election winner paths - **Root Cause**: Calling `addTask()` immediately after `stop()/initAsBridge()` cycle accessed unstable internal task scheduling structures before they were fully reinitialized in the new context - **Symptom**: Device crashes with "Load access fault" at MTVAL 0xbaad59d4 (freed memory marker) immediately after "🎯 PROMOTED TO BRIDGE" message when node becomes bridge through either isolated promotion or election - **Solution**: Removed redundant task scheduling calls that were unsafe after stop/reinit - Removed `addTask()` call in `attemptIsolatedBridgePromotion()` (line 1947) - Removed `addTask()` call in `promoteToBridge()` (line 1822) - Relied on existing `initBridgeStatusBroadcast()` infrastructure which safely handles announcements - Used explicit `TSTRING` construction in callback invocations for string lifetime safety - **Why Safe**: The removed tasks were redundant - `initBridgeStatusBroadcast()` (called by `initAsBridge()`) already sends immediate and periodic bridge status broadcasts (lines 1277-1280), and election winner path sends initial takeover announcement before stop/reinit (line 1771) - **Testing**: All test suites pass (1000+ assertions), including bridge election and promotion tests - **Documentation**: Added ISSUE_HARD_RESET_BRIDGE_PROMOTION_FIX.md with detailed analysis of both affected code paths - **Impact**: Eliminates critical crash during bridge promotion, allows stable bridge failover operation in both isolated and competitive election scenarios - **Bridge Failover & sendToInternet Retry Connectivity** - Fixed heap corruption and request timeouts when using bridge_failover with sendToInternet() during connection instability - **Root Cause**: `retryInternetRequest()` did not check mesh connectivity before attempting retry, causing routing attempts through unreachable gateways during bridge disconnection - **Symptom**: Nodes experience timeouts, heap corruption ("CORRUPT HEAP: Bad head at 0x40831da0"), and system instability during bridge failover cycles when messages are queued via sendToInternet() - **Solution**: Added `hasActiveMeshConnections()` check at start of `retryInternetRequest()` - Retry only proceeds if mesh connections are active - If disconnected, reschedules retry instead of attempting to route - Prevents routing to unreachable gateways during temporary disconnection - Maintains existing retry logic and exponential backoff - **Testing**: Added comprehensive test coverage (catch_sendtointernet_retry_no_mesh.cpp) with 31 assertions validating disconnected retry scenarios - **Documentation**: Added BRIDGE_FAILOVER_RETRY_FIX.md with detailed analysis and usage notes - **Impact**: Fixes critical stability issue during bridge failover, enabling reliable sendToInternet() usage in production deployments with unstable connections - **Hard Reset During sendToInternet - Serialized AsyncClient Deletion** - Fixed ESP32/ESP8266 hard resets caused by heap corruption when multiple AsyncClient cleanup operations execute concurrently - **Root Cause**: When multiple connections fail in rapid succession (e.g., during sendToInternet operations, mesh topology changes, or bridge failover), all AsyncClient deletions were scheduled with the same 1000ms delay, causing them to execute concurrently. The AsyncTCP library's internal cleanup routines cannot handle concurrent operations, leading to heap corruption. - **Symptom**: Device crashes with "CORRUPT HEAP: Bad head at 0x40831da0. Expected 0xabba1234 got 0x4081faa4" even with 1000ms cleanup delay. Error occurs when multiple "Deferred cleanup of AsyncClient" messages appear nearly simultaneously. - **Solution**: Implemented serialized deletion with 250ms spacing between consecutive AsyncClient deletions - Added `TCP_CLIENT_DELETION_SPACING_MS` constant (250ms) to ensure deletions don't overlap - Added global `lastScheduledDeletionTime` tracker to coordinate deletion timing - Implemented `scheduleAsyncClientDeletion()` function that calculates proper spacing - Updated `BufferedConnection` destructor and tcp.hpp error handlers to use centralized scheduler - Ensures each AsyncClient deletion completes before the next one starts - Handles millis() rollover and multiple concurrent deletion requests - **Performance Impact**: - Single deletion: No change (1000ms) - Multiple concurrent deletions: Spaced by 250ms each (total spread <1 second for typical scenarios) - High-churn scenario (10 failures): Spread over ~3 seconds (still acceptable) - **Testing**: All test suites pass (1000+ assertions), including new deletion spacing tests (47 assertions in tcp_retry) - **Documentation**: Added ISSUE_HARD_RESET_SENDTOINTERNET_SERIALIZED_DELETION_FIX.md with detailed analysis - **Impact**: Fixes critical stability issue in production deployments with high connection churn, particularly affecting sendToInternet and bridge failover scenarios ## [1.9.10] - 2025-12-15 ### Fixed - **TCP Connection Retry Immediate Execution** - Fixed mesh connection failures where TCP retries executed immediately instead of with exponential backoff delays - **Root Cause**: `PackageHandler::addTask()` was calling `task->enable()` instead of `task->enableDelayed()` for one-shot delayed tasks, causing immediate execution - **Symptom**: Nodes unable to establish mesh connection - TCP error -14 (ERR_CONN) with all retry attempts happening immediately rather than with 1s, 2s, 4s, 8s, 8s delays - **Solution**: Modified `PackageHandler::addTask()` to use `enableDelayed()` for `TASK_ONCE` tasks with intervals > 0 - Retry tasks now properly wait for their scheduled delays before executing - Exponential backoff mechanism now works as designed (total ~23s before WiFi reconnection) - Reduces network congestion from multiple simultaneously retrying nodes - **Impact**: Enables successful mesh connection establishment with proper retry timing - **Files Modified**: - `src/painlessmesh/plugin.hpp` (lines 231, 239, 245-251) - `docs/troubleshooting/common-issues.md` (lines 206-212) - `test/catch/catch_delayed_task_execution.cpp` (new test file) - `ISSUE_TCP_RETRY_FIX.md` (new documentation) - **Hard Reset on Bridge Failover Election Winner** - Fixed ESP32 hard reset (Guru Meditation Error: Load access fault) when node becomes bridge after election - **Root Cause**: The `bridgeRoleChangedCallback` signature used pass-by-value for the String parameter (`TSTRING reason`), causing temporary String object creation from const char* literals. On memory-constrained ESP32/ESP8266, this could trigger heap allocation failures and memory access faults - **Symptom**: Device crashes with "Guru Meditation Error: Core 0 panic'ed (Load access fault)" immediately after logging "🎯 PROMOTED TO BRIDGE: Election winner - best router signal" - **Solution**: Changed callback signature to use const reference (`const TSTRING& reason`) instead of pass-by-value - Eliminates unnecessary String object copying and temporary creation - Reduces memory pressure during callback invocation - String literals are now directly bound to const references without heap allocation - **Impact**: Eliminates hard resets during bridge promotion, allows stable failover operation - **Breaking Change**: Users must update their callback function signatures from `void callback(bool, String)` to `void callback(bool, const String&)` - **Files Modified**: - `src/arduino/wifi.hpp` (lines 970, 2326) - `examples/bridge_failover/bridge_failover.ino` (line 147) - `README.md` (line 171) - `USER_GUIDE.md` (line 731) ## [1.9.9] - 2025-12-14 ### Fixed - **Hard Reset from Heap Corruption in Connection Destructor** - Fixed ESP32/ESP8266 hard resets caused by heap corruption when connections were closed - **Root Cause**: AsyncClient objects were being deleted immediately in `~BufferedConnection()` destructor when `eraseClosedConnections()` removed closed connections. The AsyncTCP library was still referencing these objects internally, causing heap corruption and hard resets - **Symptom**: Device crashes with "CORRUPT HEAP: Bad head at 0x408388a4. Expected 0xabba1234 got 0xfefefefe" and "assert failed: multi_heap_free multi_heap_poisoning.c:279" when connections are removed from the mesh - **Solution**: Deferred AsyncClient deletion in destructor using task scheduler with 500ms delay - Store scheduler reference in `BufferedConnection` for use in destructor - Schedule AsyncClient deletion with 500ms delay to give AsyncTCP time to complete internal cleanup - Use same deferred deletion pattern as error handler fixes (Issues #254, #269) - Added fallback for test environments where scheduler may not be available - **Impact**: Eliminates hard resets and heap corruption when mesh connections are closed, allows stable mesh network operation - **Files Modified**: `src/painlessmesh/connection.hpp` (lines 42-81, 89-91, 192) - **Documentation**: See `ISSUE_HARD_RESET_FIX.md` for detailed analysis - **Node Crash During TCP Connection Retries** - Fixed device crashes that occurred after 2-3 TCP connection retry attempts - **Root Cause**: AsyncClient objects were being deleted too quickly (0ms delay) after connection errors. The AsyncTCP library needs 200-400ms to complete internal cleanup operations, and accessing the deleted object caused crashes - **Symptom**: Device crashes or hangs after 2-3 TCP retry attempts, serial log stops abruptly during retry sequence - **Solution**: Increased AsyncClient cleanup delay from 0ms to 500ms - Added new constant `TCP_CLIENT_CLEANUP_DELAY_MS = 500` to give AsyncTCP library time to complete internal cleanup - Updated both cleanup paths (retry and exhaustion) to use this delay - Provides sufficient time for AsyncTCP to finish processing before object deletion - **Impact**: Eliminates crashes during TCP connection retries, allows full retry sequence to complete - **Files Modified**: `src/painlessmesh/tcp.hpp` (lines 26, 149, 170) - **Documentation**: See `ASYNCCLIENT_CLEANUP_FIX.md` for detailed analysis ## [1.9.8] - 2025-12-14 ### Fixed - **Heap Corruption on TCP Connection Errors** (#254) - Fixed ESP32 heap corruption crashes during AsyncClient deletion - **Root Cause**: AsyncClient objects were being deleted synchronously from within their own error callback handlers, causing heap corruption and use-after-free crashes - **Symptom**: ESP32 devices crash with "CORRUPT HEAP: Bad head at 0x4083a398. Expected 0xabba1234 got 0xfefefefe" during TCP connection error handling - **Solution**: Deferred AsyncClient deletion using task scheduler to execute after error handler completes - Changed from synchronous `delete client` to deferred deletion via `mesh.addTask([client]() { delete client; }, 0)` - Deletion now occurs microseconds after error handler returns, preventing use-after-free - Added logging for cleanup operations to aid debugging - **Impact**: Eliminates heap corruption crashes on ESP32 during TCP connection retries and error conditions - **Files Modified**: `src/painlessmesh/tcp.hpp` (lines 138, 149) ### Changed - **README Version Reference** - Updated version banner to 1.9.7 for consistency with release history ## [1.9.7] - 2025-12-13 ### Fixed - **External Device WiFi AP Connection** (#231) - Fixed DHCP and AP initialization issues preventing phones and computers from connecting to bridge - **Root Cause**: When AP was restarted during channel changes, DHCP server was not properly reinitialized due to incomplete WiFi stack reset - **Symptom**: External devices (Android phones, Windows 11 computers) cannot connect to mesh WiFi AP or fail to receive IP addresses via DHCP - **Solution**: Improved AP restart sequence and DHCP initialization: - Added explicit `WiFi.enableAP(true)` for ESP32 to ensure DHCP server starts - Changed `softAPdisconnect(false)` to `softAPdisconnect(true)` for proper DHCP shutdown - Increased timing delays: 200ms before AP restart + 100ms stabilization after - Added logging for AP configuration to aid debugging - **Impact**: External devices can now reliably connect to bridge AP for debugging and testing ### Documentation - **External Device Connection Guide** - New comprehensive guide for connecting phones/computers to mesh AP - Complete connection instructions for Android, Windows, macOS, Linux - Troubleshooting section for common connection issues - Security best practices and warnings - Example debug sessions with network tools - Added to bridge examples with detailed setup notes ## [1.9.6] - 2025-12-10 ### Fixed - **TCP Connection Retry Improvements** (#231) - Improved TCP connection reliability with increased retries and exponential backoff - **Root Cause**: Nodes experience endless loop of WiFi connect → TCP error -14 → WiFi disconnect because the TCP retry mechanism wasn't sufficient for real-world mesh conditions - **Symptom**: Mesh connections never fully establish; nodes can discover and get IP from bridge but TCP connection consistently fails - **Solution**: Improved TCP connection retry parameters and added exponential backoff: - Increased `TCP_CONNECT_STABILIZATION_DELAY_MS` from 100ms to 500ms (more time for network stack to stabilize after IP acquisition) - Increased `TCP_CONNECT_RETRY_DELAY_MS` from 500ms to 1000ms (base delay between retries) - Increased `TCP_CONNECT_MAX_RETRIES` from 3 to 5 (more retry attempts before giving up) - Added exponential backoff: retry delays are 1s, 2s, 4s, 8s, 8s (capped) for attempts 1-5 - **Impact**: More reliable mesh connection establishment, especially when bridge TCP server is temporarily busy or network is congested ### Documentation - **README.md Comprehensive Review** - Updated main README for completeness and accuracy - Updated version references from 1.9.2 to 1.9.6 - Verified all documentation links and references - Confirmed package type documentation accuracy - Validated installation instructions - Updated "Latest Release" section with current features - Fixed ArduinoJson v7 code examples (DynamicJsonDocument → JsonDocument) - Updated dependency versions in documentation (ArduinoJson 6.x→7.x, TaskScheduler 3.x→4.x) - Corrected API Documentation links (GitLab → GitHub Pages) - Fixed Contributing section references (master→main, GitLab→GitHub) ### Changed - **Version Consistency** - Synchronized version numbers across all distribution files - Updated library.properties to v1.9.6 - Updated library.json to v1.9.6 - Updated package.json to v1.9.6 - Ensures consistent versioning for NPM, PlatformIO, and Arduino Library Manager ## [1.9.5] - 2025-12-03 ### Fixed - **Bridge Status Send Race Condition** (#224) - Fixed sendToInternet failures caused by race condition - **Root Cause**: Bridge would attempt to send status messages to nodes that had already disconnected during the 500ms delay after `changedConnectionCallbacks` - **Symptom**: Silent failures when the connection times out before the delayed task executes, causing sendToInternet to fail - **Solution**: Added connection validation before sending bridge status: - Check `findRoute()` and `conn->connected()` before attempting to send - Use direct high-priority send via `conn->addMessage(msg, true)` to avoid redundant routing lookup - Added debug logging for cancelled sends to aid troubleshooting - **Impact**: Improved reliability of sendToInternet by ensuring bridge status is only sent to active connections - **Isolated Bridge Retry Mechanism** (#225) - Fixed isolated bridge retry when mesh network not found - Nodes that fail initial bridge setup can now retry automatically via mesh connection monitoring - **Impact**: More reliable bridge establishment in challenging network conditions - **Isolated Bridge Retry Delay After Failed Promotion** - Fixed slow retry after failed bridge promotion - **Root Cause**: When bridge promotion fails, `init()` is called which resets `consecutiveEmptyScans` to 0. The isolated retry task would then wait for 6+ new empty scans (90 seconds) before retrying. - **Symptom**: After a failed bridge promotion, retries only happen every ~2 minutes instead of ~60 seconds - **Solution**: Added `_isolatedRetryPending` flag that is set when promotion fails. This flag allows the next retry attempt to skip the empty scan threshold check. - **Impact**: Faster retry after failed promotion - retries happen at the normal 60 second interval instead of waiting for scan accumulation ## [1.9.4] - 2025-12-03 ### Fixed - **TCP Server Initialization Order** (#219) - Fixed TCP connection error -14 when mesh nodes connect to bridge - **Root Cause**: TCP server was being initialized before the AP interface was configured in `init()`, causing it to bind incorrectly and reject incoming connections from mesh nodes - **Symptom**: Regular mesh nodes could discover the bridge AP and get an IP, but TCP connections failed with error -14 (connection refused) - **Solution**: Reordered initialization in `init()` to start TCP server after AP is configured: - `eventHandleInit()` → `apInit(nodeId)` → `tcpServerInit()` (previously tcpServerInit was first) - Added 100ms stabilization delay in `initAsBridge()` and `initAsSharedGateway()` after mesh init - **Impact**: Fixes `sendToInternet` not working because nodes couldn't establish mesh connections to the bridge ## [1.9.3] - 2025-12-02 ### Fixed - **sendToInternet Example Compilation** - Fixed two compilation errors in the sendToInternet example: - Added missing `addTask(callback, delay)` overload in mesh.hpp for one-shot delayed tasks - Fixed `sendWithPriority` template functions in router.hpp to use `Variant(&package)` instead of `Variant(package)`, enabling proper construction from any PackageInterface-derived class including GatewayDataPackage - **Impact**: The sendToInternet example now compiles correctly with Arduino IDE ### Added - **sendToInternet Example** - Added to library.json examples list for better discoverability ## [1.9.2] - 2025-12-01 ### Changed - **Release Version Update** - Consolidated release across all distribution channels - Synchronized version numbers in library.properties, library.json, and package.json - Ensures consistent versioning for NPM, PlatformIO, and Arduino Library Manager ### Documentation - **README Update** - Updated version banner to reflect 1.9.2 release ## [1.9.1] - 2025-12-01 ### Added - **Isolated Bridge Retry Mechanism** - Nodes that fail initial bridge setup can now retry automatically - New `attemptIsolatedBridgePromotion()` method for direct bridge promotion when isolated - Periodic retry task runs every 60 seconds when node is isolated (no mesh connections) - Requires 6 consecutive empty mesh scans before attempting retry - Limited to 5 actual retry attempts before 5-minute cooldown - Counter resets on success, mesh reconnection, or after cooldown - **Impact**: Fixes issue where nodes with `INITIAL_BRIDGE=true` that fail router connection would never retry becoming a bridge (endless "Bridge monitor: Skipping - no active mesh connections") - **Comprehensive Test Coverage** - Added tests for all woodlist use cases - Use Case 1: INITIAL_BRIDGE=true with router temporarily unavailable - Use Case 2: Regular node with no mesh found - isolated retry - Use Case 3: Router association refused error handling - Use Case 4: Node loses mesh connection to bridge - Use Case 5: Multiple retry attempts with cooldown - Use Case 6: Correct serial output for bridge failure ### Fixed - **Bridge Retry for Isolated Nodes** (#212) - Fixed nodes not retrying bridge connection - **Root Cause**: Bridge monitor task skips isolated nodes to prevent split-brain scenarios, but this prevented retry when initial bridge setup fails - **Symptom**: Endless "Bridge monitor: Skipping - no active mesh connections" log messages - **Solution**: Added separate isolated bridge retry mechanism that activates when: - Node has router credentials configured - Node is isolated (no mesh connections) - Multiple empty scans have occurred (mesh not found) - **Behavior**: Node scans for router, checks RSSI, and attempts direct bridge promotion - Addresses feedback from @woodlist regarding failed initial bridge setup scenarios ### Changed - **Updated bridge_failover Example** - Added documentation about automatic retry behavior - Example now notes that isolated nodes will retry bridge connection periodically - Clearer messaging about fallback and retry mechanisms ## [1.9.0] - 2025-11-30 ### Added - **Mesh Connectivity Detection** - New APIs to detect mesh connection state - `hasActiveMeshConnections()` - Check if node has active mesh connections - `getLastKnownBridge()` - Get last known bridge regardless of timeout - Allows distinguishing between "bridge unavailable" vs "disconnected from mesh" - **Improved Bridge Detection** - Enhanced `getPrimaryBridge()` behavior - When disconnected from mesh, returns last known bridge instead of nullptr - Stale bridge info is better than no info for reconnection scenarios - `hasInternetConnection()` now uses last known state when disconnected - **Election Guard** - Skip election trigger when node is disconnected from mesh - Prevents unnecessary elections when issue is local connectivity - More accurate diagnosis of bridge availability problems - **Configurable Bridge Election Timing** - New API methods to prevent split-brain scenarios - `setElectionStartupDelay(delayMs)` - Configure startup delay before first election (default: 60s, min: 10s) - `setElectionRandomDelay(minMs, maxMs)` - Configure random delay range for elections (default: 1-3s) - Longer delays allow more time for mesh formation when nodes start simultaneously - Prevents race condition where multiple nodes become bridges in isolation - All timing parameters are user-configurable without hard-coded values - **Impact**: Users can now tune election timing for their specific deployment scenarios ### Fixed - **Bridge Discovery** - Fixed regular nodes unable to discover bridge nodes in bridge failover examples - **Root Cause**: Regular nodes were using default channel=1 instead of channel=0 (auto-detect) - Bridge nodes operate on router's WiFi channel (e.g., channel 6), not channel 1 - Nodes on fixed channel cannot discover bridges on different channels - **Solution**: Updated examples to use `channel=0` for automatic mesh channel detection - **Affected Examples**: - `bridge_failover/bridge_failover.ino` - Both regular node and fallback initialization - `multi_bridge/regular_node.ino` - Regular node initialization - `bridgeAwareSensorNode/bridgeAwareSensorNode.ino` - Sensor node initialization - `ntpTimeSyncNode/ntpTimeSyncNode.ino` - Time sync node initialization - **Documentation**: Added channel detection explanation to bridge_failover README - **Impact**: Regular nodes now properly discover and connect to bridges regardless of router channel - Users experiencing "No primary bridge available!" / "Known bridges: 0" should update to this version - **Split-Brain Prevention** - Addressed race condition when multiple nodes start simultaneously - **Root Cause**: 60-63s window insufficient for mesh formation before elections start - Both nodes detect "no bridge", run isolated elections, each wins and becomes bridge - **Solution**: Added configurable timing parameters (see Added section above) - **Documentation**: Added comprehensive troubleshooting section to bridge_failover README - **Recommended Settings**: 90s startup delay + 10-30s random delay for simultaneous startups - **Alternative**: Stagger node startup by 10-20 seconds or use pre-designated bridge mode ### Changed - **Examples Consolidated** - Reduced from 32 to 14 essential examples - Removed 18 redundant/developmental examples - Kept: alteriom, basic, bridge, bridge_failover, logClient/Server, mqttBridge, namedMesh, otaReceiver/Sender, priority, sharedGateway, startHere, webServer - Cleaner, more maintainable example set focusing on core functionality - **Documentation Consolidated** - Cleaned up repository documentation - Removed obsolete release notes, issue resolution docs, and development artifacts - Retained: README.md, CHANGELOG.md, CONTRIBUTING.md, RELEASE_GUIDE.md, BRIDGE_TO_INTERNET.md - Cleaner root directory with only essential documentation ## [1.8.15] - 2025-11-23 ### Added - **Simulator Integration** - Integrated painlessMesh-simulator for automated example validation - Added painlessMesh-simulator as git submodule at `test/simulator/` - Created YAML-based test scenarios for example validation - Configured CI/CD to automatically run simulator tests on every push/PR - Validates mesh formation, message broadcasting, and time synchronization with 5+ virtual nodes - Provides framework for testing with 100+ nodes without hardware - Resolves GitHub issue #163 - Merged via PR #164 ### Documentation - **Release Readiness Assessment** - Created comprehensive release readiness plan - `RELEASE_READINESS_PLAN.md` - Complete audit of test infrastructure, performance issues, and security - Confirmed library is production-ready with all 119+ test assertions passing - Documented Issue #161 resolution (architectural clarification, not a bug) - `TESTING_WITH_SIMULATOR.md` - Quick start guide for simulator - `docs/SIMULATOR_TESTING.md` - Complete simulator integration guide with CI details ### Fixed - **Build System** - Fixed references to removed test files in CMakeLists.txt - **CI/CD** - Added missing libboost-program-options-dev dependency for simulator build - **Documentation** - Updated all simulator paths and dependency lists ## [1.8.14] - 2025-11-21 ### Fixed - **Bridge Internet Detection** - Fixed `hasInternetConnection()` returning false on bridge nodes immediately after initialization - **Root Cause**: Base `hasInternetConnection()` only checked `knownBridges` list; bridge self-registration happens asynchronously - Added override in Arduino `wifi::Mesh` to check local WiFi status before checking remote bridges - Bridge nodes now immediately report correct internet status via `WiFi.status()` check - Fixes issue where internet-dependent features (like WhatsApp messaging) failed on bridge nodes - **Impact**: Bridge nodes correctly report internet connectivity immediately after initialization - **Affected Components**: Bridge_fallover example, any code using `mesh.hasInternetConnection()` on bridge nodes - Core fix in `src/arduino/wifi.hpp` - added WiFi status check override - Resolves GitHub issue #159 - Merged via PR #160 ## [1.8.12] - 2025-11-19 ### Changed - **Documentation Updates** - Comprehensive documentation improvements - Updated all documentation to reflect current library state - Improved code examples and usage instructions - Enhanced API reference documentation - Verified all links and references - Merged via PRs #152, #153 ### Fixed - **Code Quality** - Resolved linting and formatting issues - Fixed clang-format compliance across codebase - Ensured prettier formatting consistency - Improved code quality and maintainability ## [1.8.11] - 2025-11-18 ### Fixed - **Bridge Discovery Race Condition** - Fixed routing table timing issue that prevented reliable bridge status delivery - **Root Cause**: Bridge was sending status messages before routing tables were fully established after connection - Changed from `newConnectionCallback` to `changedConnectionCallbacks` for routing table readiness - Ensures routing tables are properly configured before attempting to send bridge status - Bridge now waits for routing table convergence before sending status messages to new nodes - **Before Fix**: Messages sent too early could fail to reach destination due to incomplete routing - **After Fix**: Bridge status reliably delivered once routing is properly established - Core fix in `src/arduino/wifi.hpp` - uses connection change callbacks instead of new connection callbacks - More robust than timing-based delays (previous 500ms approach) - Resolves GitHub issue #142 - Merged via PR #142 - **Windows MSVC Compilation Compatibility** - Fixed access modifier issues for Windows builds - MSVC compiler does not grant friend status to lambdas inside friend functions - Changed semaphore methods (`semaphoreTake()`, `semaphoreGive()`) from protected to public - Changed `droppedConnectionCallbacks` access for lambda compatibility - **Impact**: Library now compiles successfully on Windows with MSVC compiler - **Affected Platforms**: Windows desktop builds, Visual Studio projects - Core fix in `src/painlessmesh/mesh.hpp` line ~2060 - Also updated access modifiers in `buffer.hpp`, `ntp.hpp`, and `router.hpp` for consistency - No functional changes - purely compatibility improvements - Maintains full compatibility with GCC/Clang compilers - **Code Security Improvements** - Fixed multiple code scanning alerts - Fixed wrong type of arguments to formatting functions (alerts #3, #5) - Fixed potentially overrunning write with float to string conversion (alert #2) - Fixed use of potentially dangerous function (alert #1) - Improved type safety in string formatting operations - Enhanced buffer safety for float conversions - Merged via PRs #144, #145, #146, #147 ### Changed - **CI/CD Reliability** - Added retry logic for Arduino package index updates - Handles transient network failures during package publication - Improves reliability of automated release workflow - Reduces false failures in CI pipeline ## [1.8.10] - 2025-11-18 ### Fixed - **Bridge Status Discovery - Direct Messaging** - Fixed newly connected nodes not receiving bridge status reliably - Changed bridge status delivery mechanism from broadcast to direct single message - Bridge now sends status directly to newly connected nodes using `sendSingle()` (routing=1) - Added minimal 500ms delay for connection stability before sending status - **Root Cause**: Broadcast routing may not be established immediately after connection; time sync (NTP) operations were interfering with bridge discovery - **Before Fix**: Nodes could wait up to 30 seconds for bridge status via periodic broadcast; "No primary bridge available" errors - **After Fix**: Nodes discover bridges within 500ms of connection; reliable bridge discovery regardless of NTP sync activity - Direct targeted delivery ensures message reaches new node immediately - Core fix in `src/arduino/wifi.hpp` line ~809 in `initBridgeStatusBroadcast()` - Resolves GitHub issue #135 "The latest fix does not work" - 100% backward compatible - no API changes - **Note**: Further improved in v1.8.11 with routing table readiness detection ## [1.8.9] - 2025-11-12 ### Fixed - **Bridge Self-Registration (Type 610 & 613)** - Bridge nodes now properly track themselves in status and coordination - **Bridge Status Broadcasting (Type 610)**: Fixed bridge nodes reporting "Known bridges: 0" despite being active - Added immediate self-registration task in `initBridgeStatusBroadcast()` (line ~746) - Bridge now calls `updateBridgeStatus()` with own nodeId immediately after initialization - Added self-update in `sendBridgeStatus()` (line ~1192) before broadcasting - Ensures bridge appears in its own `knownBridges` list from the start - **Bridge Coordination (Type 613)**: Fixed multi-bridge priority tracking - Added self-registration in `initBridgeCoordination()` (line ~803) - Bridge now adds own priority to `bridgePriorities` map: `bridgePriorities[this->nodeId] = bridgePriority` - Added priority self-update in `sendBridgeCoordination()` (line ~869) before broadcasting - Ensures primary bridge selection works correctly with multiple bridges - **Root Cause**: Mesh networks don't loop broadcasts back to sender by design - Nodes receive broadcasts from other nodes but not their own messages - Requires explicit local state management for any tracking data - **Before Fix**: - Bridge reports "Known bridges: 0" and "No primary bridge available!" - Multi-bridge setups fail to select primary bridge (missing own priority) - Bridge failover unreliable due to incomplete bridge tracking - **After Fix**: - Bridge correctly reports "Known bridges: 1" (or more in multi-bridge setups) - Primary bridge selection works properly with all bridge priorities present - Self-tracking pattern now consistent across all periodic broadcast types - Core fixes in `src/arduino/wifi.hpp` - Resolves @woodlist GitHub issue - bridge showing "Known bridges: 0" - Comprehensive analysis documented in COMPREHENSIVE_BROADCAST_ANALYSIS.md ### Changed - **Build System** - Switched Docker compiler from clang++ to g++ - Changed ENV CXX in Dockerfile from clang++ to g++ - Resolves template instantiation crashes during Docker builds - Build verification confirms successful compilation with g++ ### Documentation - **Broadcast Message Analysis** - Added comprehensive review documentation - Created COMPREHENSIVE_BROADCAST_ANALYSIS.md with full analysis of all 4 broadcast types - Documents self-tracking requirements for Type 610 (STATUS) and 613 (COORDINATION) - Confirms Type 611 (ELECTION) already implements correct self-registration - Confirms Type 612 (TAKEOVER) doesn't require self-tracking (notification only) - Establishes pattern guidelines for future broadcast implementations ## [1.8.8] - 2025-11-12 ### Fixed - **Bridge Internet Connectivity Detection (Mobile Hotspot Compatibility)** - Fixed false negative with mobile hotspots - Changed internet detection from checking gateway IP to checking local IP address - Gateway IP may not be immediately available after connection, especially with mobile hotspots - Some networks (mobile tethering) may not provide gateway IP via DHCP at all - **Before**: Bridge connected to mobile hotspot → gets local IP → reports "Internet: NO" (gateway IP not available) - **After**: Bridge connected to mobile hotspot → gets local IP → correctly reports "Internet: YES" - Having a valid local IP + WiFi connected status is sufficient to indicate internet access - Enhanced logging to show WiFi status, local IP, and gateway IP for better debugging - Core fix in `src/arduino/wifi.hpp` line 1189-1191 - Improves upon 1.8.7 gateway IP check which didn't work with all network types - Resolves issue where bridge connects successfully but still reports no internet - Fixes Alteriom/painlessMesh#129 ## [1.8.7] - 2025-11-12 ### Fixed - **Bridge Internet Connectivity Detection (Bridge_fallover)** - Fixed incorrect internet status reporting - Bridge nodes now properly check for actual Internet connectivity, not just WiFi connection - Check verifies both `WiFi.status() == WL_CONNECTED` AND valid gateway IP (not 0.0.0.0) - **Before**: Bridge connected to router → reports "Internet: NO" even when router has Internet - **After**: Bridge connected to router with valid gateway → correctly reports "Internet: YES" - Fixes `hasInternetConnection()` returning false positives on regular nodes - Core fix in `src/arduino/wifi.hpp` line 1188-1192 - Updated bridge_failover/README.md with troubleshooting guidance - **Version Documentation Consistency** - Updated header file version comments to match library version - Updated `painlessMesh.h` header comment from version 1.8.4 to 1.8.6 - Updated `AlteriomPainlessMesh.h` version defines from 1.6.1 to 1.8.6 - Header file version comments now accurately reflect the current library version - Clarified that version comments in headers indicate documentation update, not file-specific versioning - Resolves user confusion about whether files have been modified since specific versions ## [1.8.6] - 2025-11-12 ### Fixed - **Bridge Failover Auto-Election (Issue #117)** - Bridge election now triggers when no initial bridge exists - Added periodic monitoring task (30s interval) that detects absence of healthy bridge - Activates after 60s startup grace period to allow network stabilization - Randomized election delay (1-3s) prevents thundering herd problem - Respects existing safeguards: election state, 60s cooldown, router visibility - **Before**: No initial bridge → no election → mesh stays bridgeless indefinitely - **After**: No initial bridge → 60s startup → monitoring detects absence → election triggered → best RSSI node becomes bridge - Fully backward compatible: pre-designated bridge mode continues to work as before - Core fix in `src/arduino/wifi.hpp` - Resolves @woodlist's "Bridge_failover example does not work" issue ### Changed - **bridge_failover Example Documentation** - Clarified two deployment modes - Auto-Election Mode: All nodes regular (`INITIAL_BRIDGE=false`), RSSI-based election after 60s - Pre-Designated Mode: Traditional single initial bridge setup - Updated README.md with comprehensive auto-election documentation - Enhanced header comments in bridge_failover.ino ### Housekeeping - Synchronized package-lock.json version to 1.8.5 ## [1.8.5] - 2025-11-12 ### Fixed - **ntpTimeSyncBridge and ntpTimeSyncNode Compilation (Issue #108)** - Arduino IDE compilation errors fixed - Fixed include path in ntpTimeSyncBridge.ino from `"examples/alteriom/alteriom_sensor_package.hpp"` to `"alteriom_sensor_package.hpp"` - Fixed include path in ntpTimeSyncNode.ino from `"examples/alteriom/alteriom_sensor_package.hpp"` to `"alteriom_sensor_package.hpp"` - Arduino IDE compiles sketches with sketch directory as working directory, requiring local header files - Resolves @woodlist's compilation error: "No such file or directory" - **Arduino String Method Compatibility** - Fixed incompatible method call in wifi.hpp - Changed `stationSSID.empty()` to `stationSSID.isEmpty()` in src/arduino/wifi.hpp line 171 - Arduino's String class uses `isEmpty()` method instead of STL's `empty()` - Fixes CI build failures for ESP32/ESP8266 examples - Related to station credentials feature added in #113 ### Documentation - **Example Sketches** - Updated NTP time synchronization examples - ntpTimeSyncBridge now compiles correctly in Arduino IDE - ntpTimeSyncNode now compiles correctly in Arduino IDE - Examples: `examples/ntpTimeSyncBridge/`, `examples/ntpTimeSyncNode/` ## [1.8.4] - 2025-11-12 ### Fixed - **Bridge Discovery Timing (Issue #108)** - Immediate bridge status broadcast for faster node discovery - Bridge nodes now send status broadcast immediately on initialization - Bridge status broadcast sent when new nodes connect to mesh - Eliminates 30-second discovery delay that caused "No primary bridge available" errors - Bridge nodes are now discoverable in <1 second instead of up to 30 seconds - Improves user experience in bridge_failover example - Resolves @woodlist's issue with bridge discovery in bridge_failover example ### Changed - **Bridge Status Broadcasting** - Enhanced timing for immediate node discovery - Added immediate broadcast task on bridge initialization - Registered newConnectionCallback to broadcast when nodes join - Maintains existing periodic broadcasts (30-second default interval) - No breaking changes - fully backward compatible ### Documentation - **Bridge Failover Example** - Updated documentation for discovery improvements - Added "Bridge Status Monitoring" section documenting new broadcast timing - Added "Bridge Not Discovered" troubleshooting section - Updated README with immediate discovery behavior - Examples: `examples/bridge_failover/` ## [1.8.3] - 2025-11-11 ### Fixed - **Arduino ZIP File Integrity (Issue #89)** - Resolved ZIP file installation issues reported by @woodlist - Removed problematic symlink `_codeql_detected_source_root` that caused ZIP file corruption - Added comprehensive `.gitattributes export-ignore` rules to exclude development files from releases - Improved ZIP file structure for Arduino IDE compatibility - Added version timestamp and metadata to main header file `painlessMesh.h` - Excluded test files, scripts, and development artifacts from Arduino ZIP packages - **Station Reconnection After Mesh Init (Issue #21)** - Automatic reconnection for bridge mode station connections - Fixed bug where manual station connections failed to reconnect after mesh initialization - `connectToAP()` now calls `WiFi.begin()` directly for manual connections instead of relying on scan results - Added logging for reconnection attempts - Resolves ESP32-C6 and all ESP platforms bridge connectivity issues - Documentation: `docs/troubleshooting/station-reconnection-issues.md` ### Changed - **Library Header Documentation** - Added version timestamp and metadata to `painlessMesh.h` - Header now includes version number, release date, and repository URL - Improved documentation for library users - Addresses @woodlist's request for version timestamp tracking ### Improved - **Release Process** - Enhanced ZIP file creation for Arduino IDE - Better exclusion of development files from distribution packages - Cleaner package structure with only essential library files - Improved compatibility with Arduino IDE's "Add .ZIP Library" feature ## [1.8.2] - 2025-11-11 ### Added - **Multi-Bridge Coordination and Load Balancing (Issue #65)** - Enterprise-grade multi-bridge support for high availability and load distribution - New `BridgeCoordinationPackage` (Type 613) for bridge-to-bridge communication - Bridge priority system (1-10) with automatic role assignment (primary/secondary/standby) - Three bridge selection strategies: Priority-Based, Round-Robin, Best Signal (RSSI-based) - API methods: `setBridgeSelectionStrategy()`, `getBridgeList()`, `getPrimaryBridge()`, `getBridgeLoad()` - Automatic peer discovery and coordination every 30 seconds - Load balancing for geographic distribution and traffic shaping - Hot standby redundancy without failover delays - Examples: `examples/multi_bridge/` (primary_bridge.ino, secondary_bridge.ino, regular_node.ino) - Documentation: `MULTI_BRIDGE_IMPLEMENTATION.md`, `ISSUE_65_VERIFICATION.md` - Comprehensive unit tests (120+ assertions) in `test/catch/catch_plugin.cpp` - **Message Queue for Offline/Internet-Unavailable Mode (Issue #66)** - Production-ready message queuing for critical sensor data - New `MessageQueue` class with priority-based message management - Four priority levels: CRITICAL, HIGH, NORMAL, LOW (CRITICAL messages never dropped) - Automatic queue management during Internet outages - Intelligent eviction strategy: drop oldest LOW priority messages first - Integration with bridge status monitoring for automatic online/offline detection - API methods: `queueMessage()`, `enableMessageQueue()`, `setMaxQueueSize()`, `getQueuedMessages()`, `clearQueue()`, `getQueueStats()` - Callback support: `onQueueFull()`, `onMessageQueued()`, `onQueueFlushed()` - Fish farm O2 monitoring example: `examples/queued_alarms/queued_alarms.ino` - Documentation: `MESSAGE_QUEUE_IMPLEMENTATION.md`, `ISSUE_66_CLOSURE.md` - Comprehensive unit tests (113 assertions) in `test/catch/catch_message_queue.cpp` ### Changed - **Bridge-to-Bridge Communication** - Enhanced mesh coordination between multiple bridge nodes - Bridge nodes now periodically broadcast coordination status - Regular nodes can query and track multiple available bridges - Improved failover with multi-bridge awareness ### Improved - **Production Readiness** - Both features battle-tested and ready for critical deployments - Issue #65: Geographic redundancy, load distribution, zero-downtime failover - Issue #66: Zero data loss for critical sensors during Internet outages - Comprehensive documentation and working examples for both features - Full test coverage with 230+ new test assertions ### Performance - **Memory Impact**: ~2-3KB per bridge node for coordination tracking - **Queue Memory**: Configurable (default 50 messages, ~1-5KB depending on message size) - **Network Overhead**: BridgeCoordinationPackage ~150 bytes every 30 seconds per bridge - **CPU Overhead**: <0.5% for coordination and queue management ### Compatibility - **100% Backward Compatible** with v1.8.1 - All existing single-bridge code works without modification - Multi-bridge and message queue features are optional additions - Can be adopted incrementally as needed - No breaking changes to existing APIs ## [1.8.1] - 2025-11-10 ### Added - **GitHub Copilot Custom Agent Support** - Custom agent configuration now discoverable by GitHub - Moved `copilot-agents.json` to repository root for automatic GitHub Copilot integration - Release Agent now available as `@release-agent` in GitHub Copilot Chat (Enterprise) - Enhanced repository context for all GitHub Copilot users - Complete agent documentation in `.github/agents/` directory ### Changed - **Documentation Updates** - Improved clarity for custom agent setup - Updated `COPILOT_AGENT_SETUP.md` with root file location - Enhanced `AGENTS_INDEX.md` with discovery information - Added examples for using custom agents in development workflow ### Fixed - **Custom Agent Visibility** - Resolved issue where custom agent tasks were not showing in GitHub - GitHub Copilot now automatically discovers the release agent configuration - Agent appears in Copilot Chat suggestions when available - Knowledge sources properly linked for enhanced context ## [1.8.0] - 2025-11-09 ### Added - **Diagnostics API for Bridge Operations** - Comprehensive monitoring and debugging tools - New diagnostic methods for bridge state, topology, and connectivity - Election history tracking with detailed event logging - Network topology visualization with neighbor information - Connectivity testing and validation tools - Comprehensive diagnostic report generation - Minimal overhead when diagnostics enabled - Examples: `examples/diagnostics/` directory - Documentation: `DIAGNOSTICS_API.md` - **Bridge Health Monitoring & Metrics Collection** - Real-time bridge performance metrics - New `BridgeHealthMetrics` struct with connectivity, signal, traffic, and performance data - Four API methods: `getBridgeHealthMetrics()`, `resetHealthMetrics()`, `getHealthMetricsJSON()`, `onHealthMetricsUpdate()` - Automatic tracking of uptime, disconnects, RSSI, traffic bytes, latency, and packet loss - JSON export for integration with MQTT, Prometheus, Grafana, CloudWatch - Periodic callback support for automated monitoring - Zero overhead when not used - Comprehensive unit tests (63 assertions) - Example: `examples/bridge/bridge_health_monitoring_example.ino` - Documentation: `docs/BRIDGE_HEALTH_MONITORING.md` - **RTC (Real-Time Clock) Integration** - Hardware RTC support for offline timekeeping - Support for DS3231, DS1307, and PCF8523 RTC modules - Automatic time persistence across reboots and power failures - Seamless integration with NTP time sync - Comprehensive unit tests for RTC functionality - Example sketches demonstrating RTC usage - **Bridge Status Broadcast & Callback (Type 610)** - Real-time Internet connectivity monitoring - Bridge nodes automatically broadcast connectivity status every 30 seconds - New `onBridgeStatusChanged()` callback for connectivity state changes - API methods: `hasInternetConnection()`, `getPrimaryBridge()`, `getBridges()`, `isBridge()` - Status includes Internet connectivity, router RSSI, channel, uptime, gateway IP - Enable offline mode and message queueing when Internet unavailable - Support for bridge failover scenarios - Documentation: `BRIDGE_STATUS_FEATURE.md` - **Automatic Bridge Failover with RSSI-Based Election (Types 611, 612)** - High-availability bridge management - Distributed bridge election protocol when primary bridge fails - RSSI-based node selection for optimal bridge placement - New `BridgeElectionPackage` (Type 611) for election coordination - New `BridgeTakeoverPackage` (Type 612) for bridge transition announcements - API methods: `enableBridgeFailover()`, `setRouterCredentials()`, `onBridgeRoleChanged()` - Automatic promotion of best-positioned node to bridge role - Tiebreaker rules: uptime, free memory, node ID - Split-brain prevention and oscillation protection - Graceful handling of multiple sequential failures - Critical for production high-availability systems (Issue #64) - **NTP Time Synchronization (Type 614)** - Bridge-to-mesh NTP time distribution - New `NTPTimeSyncPackage` for broadcasting NTP time from bridge nodes - Bridge nodes with Internet distribute authoritative time to entire mesh - Eliminates per-node NTP queries (saves bandwidth and power) - Supports RTC synchronization for offline operation - Includes accuracy field for time uncertainty tracking - Comprehensive unit tests (5 scenarios, 38 assertions) - Example sketches: `ntpTimeSyncBridge.ino` and `ntpTimeSyncNode.ino` - Documentation: `NTP_TIME_SYNC_FEATURE.md` - **Bridge-Centric Architecture** - New `initAsBridge()` method for automatic channel detection - Bridge nodes now connect to router first and auto-detect its channel - Mesh network automatically configured on router's channel - Eliminates need for manual channel configuration - Automatically sets root node flags - Graceful fallback to channel 1 if router connection fails - **Auto Channel Detection for Regular Nodes** - Support for `channel=0` in `init()` - Regular nodes can now auto-detect mesh channel by scanning all channels - Falls back to channel 1 if mesh not found - Simplifies multi-node deployments - **Helper Function** - New `scanForMeshChannel()` static method - Scans all 13 WiFi channels to find mesh SSID - Supports hidden networks - Returns detected channel or 0 if not found - Detailed logging for troubleshooting ### Changed - **Enhanced Documentation** - Updated bridge and basic examples - `examples/bridge/bridge.ino` now uses `initAsBridge()` API - `examples/basic/basic.ino` demonstrates auto channel detection - `BRIDGE_TO_INTERNET.md` rewritten with bridge-centric approach - `README.md` includes bridge quick start guide - **StationScan Enhancement** - Modified `stationScan()` to support all-channel scanning - When `channel=0`, automatically scans all channels before connecting - Auto-updates mesh channel based on detected network ### Fixed - No bug fixes in this release - purely additive features ### Backward Compatibility - All existing code continues to work without changes - Manual channel configuration (`mesh.init(..., channel)`) still supported - Legacy `stationManual()` approach still available - No breaking API changes ## [1.7.9] - 2025-11-08 ### Fixed - **CI/CD Pipeline** - Fixed submodule initialization failures and PlatformIO test configuration in GitHub Actions workflows - Added explicit `submodules: recursive` to checkout action in CI workflow - Added manual `git submodule update --init --recursive` step for robustness - Ensures test dependencies (ArduinoJson and TaskScheduler) are properly initialized - Fixes build failures where submodules were not available during CI runs - Removed redundant matrix strategy from PlatformIO build test (script builds both platforms anyway) - Changed PlatformIO tests from random to deterministic (tests critical examples: basic, alteriomSensorNode, alteriomMetricsHealth) - Improved concurrency grouping to properly handle PR branch names and prevent premature cancellations - Affects all workflows: ci.yml, release.yml, docs.yml - **Workflow Triggers** - Fixed duplicate CI runs and cancellation issues on PR branches - Removed unnecessary `copilot/**` pattern from validate-release workflow branches filter - Added explicit branch check in validate-release job condition to only run on main/develop - Prevents validate-release workflow from running on PR branches - Fixed concurrency grouping to use `github.head_ref` for PRs (branch name) instead of `github.ref` (commit SHA) - Ensures proper workflow cancellation behavior and prevents confusion from cancelled runs - **Example Code** - Fixed compilation errors in alteriomMetricsHealth example - Removed incorrect `userScheduler.size()` call (TaskScheduler API doesn't expose queue size) - Replaced non-existent `toJsonString()` methods with proper JSON serialization pattern - Updated deprecated `DynamicJsonDocument` to `JsonDocument` for ArduinoJson v7 compatibility - Changed `msgType` from `uint8_t` to `uint16_t` to support message types > 255 (400, 604, 605) ### Technical Details - GitHub Actions now properly initializes git submodules before build steps - Both automated checkout with `submodules: recursive` and manual initialization step included - Prevents "No such file or directory" errors for test/ArduinoJson and test/TaskScheduler - Critical fix for maintaining CI/CD reliability across all build and test workflows - Example code now uses proper serialization: `JsonDocument doc; JsonObject obj = doc.to(); package.addTo(std::move(obj)); serializeJson(doc, msg);` ## [1.7.8] - 2025-11-05 ### Added - **MQTT Schema v0.7.3 Compliance** - Upgraded from v0.7.2 to v0.7.3 - Added `message_type` field to SensorPackage (Type 200) - Added `message_type` field to StatusPackage (Type 202) - Added `message_type` field to CommandPackage (Type 400) - All packages now have `message_type` for 90% faster message classification - Full alignment with @alteriom/mqtt-schema v0.7.3 specification ### Added - **BRIDGE_TO_INTERNET.md** - Comprehensive documentation for bridging mesh networks to the Internet via WiFi router - Complete code examples with AP+STA mode configuration - WiFi channel matching requirements and best practices - Links to working bridge examples (basic, MQTT, web server, enhanced MQTT) - Architecture diagrams and forwarding patterns - Troubleshooting and additional resources - **Enhanced StatusPackage** - New organization and sensor configuration fields - Organization fields: `organizationId`, `organizationName`, `organizationDomain` - Sensor configuration: `sensorTypes` array, `sensorConfig` JSON, `sensorInventory` array - Separate JSON serialization keys for sensors data vs configuration - CamelCase field naming convention for consistency - **API Design Guidelines** - `docs/API_DESIGN_GUIDELINES.md` - Field naming conventions (camelCase, units in field names) - Boolean naming patterns (`is`, `has`, `should`, `can`) - Time field naming with units (`_ms`, `_s`, `_us` suffixes) - Serialization patterns and consistency rules - Comprehensive validation tests - **Manual Publishing Workflow** - `.github/workflows/manual-publish.yml` - On-demand NPM and GitHub Packages publishing - Fixes cases where automated release doesn't trigger package publication - Configurable options for selective publishing ### Changed - **Time Field Naming Convention** - Consistent unit suffixes across all packages - `collectionTimestamp` → `collectionTimestamp_ms` - `avgResponseTime` → `avgResponseTime_us` - `estimatedTimeToFailure` → `estimatedTimeToFailure_s` - All time fields now include explicit units in field names - Documentation: `docs/architecture/TIME_FIELD_NAMING.md` - **StatusPackage JSON Structure** - Improved field organization - Sensor data uses `sensors` key (array of readings) - Sensor configuration uses separate keys (`sensorTypes`, `sensorConfig`, `sensorInventory`) - No key collisions between runtime data and configuration - Unconditional serialization for predictable JSON structure - **MQTT Retry Logic** - Fixed serialization to include all retry fields - Proper condition for including retry configuration - Epsilon comparison for floating-point backoff multiplier ### Fixed - **CI Pipeline** - Made validate-release depend on CI completion - Prevents release validation from running before tests complete - Ensures all tests pass before release can proceed - **ArduinoJson API** - Updated deprecated API usage - Fixed deprecated JsonVariant::is() calls - Updated to ArduinoJson 7.x compatible patterns - Code formatting improvements - **ESP8266 Compatibility** - Fixed `getDeviceId()` function - Added proper ESP8266 implementation in mqttTopologyTest - Platform-specific device ID retrieval - **Documentation** - Multiple improvements - Fixed v1.7.7 release date in documentation - Added comprehensive mqtt-schema v0.7.2+ message type codes table - Corrected CommandPackage type number (400, not 201) - Enhanced Alteriom Extensions section in README - Added GitHub Packages authentication for npm install ## [1.7.7] - 2025-11-05 ### Added - **MQTT Schema v0.7.2 Compliance** - Updated to @alteriom/mqtt-schema v0.7.2 - Added `message_type` field to all packages for 90% faster classification - MetricsPackage (204) now aligns with schema SENSOR_METRICS (v0.7.2+) - CommandPackage moved from type 201 → 400 (COMMAND per schema, resolves conflict with SENSOR_HEARTBEAT) - HealthCheckPackage uses 605 (MESH_METRICS per mqtt-schema v0.7.2+) - EnhancedStatusPackage uses 604 (MESH_STATUS per mqtt-schema v0.7.2+) - Compatible with mesh bridge schema (type 603) for future integration - **MetricsPackage (Type 204)** - Comprehensive performance metrics for real-time monitoring and dashboards - CPU and processing metrics (cpuUsage, loopIterations, taskQueueSize) - Memory metrics (freeHeap, minFreeHeap, heapFragmentation, maxAllocHeap) - Network performance (bytesReceived, bytesSent, packetsDropped, currentThroughput) - Timing and latency metrics (avgResponseTime, maxResponseTime, avgMeshLatency) - Connection quality indicators (connectionQuality, wifiRSSI) - Collection metadata for tracking - **MeshNodeListPackage (Type 600)** - List of all nodes in mesh network (MESH_NODE_LIST per mqtt-schema v0.7.2+) - Array of node information (nodeId, status, lastSeen, signalStrength) - Total node count and mesh identifier - Enables node discovery and monitoring - **MeshTopologyPackage (Type 601)** - Mesh network topology with connections (MESH_TOPOLOGY per mqtt-schema v0.7.2+) - Array of connections between nodes (fromNode, toNode, linkQuality, latencyMs, hopCount) - Root/gateway node identification - Enables topology visualization and network analysis - **MeshAlertPackage (Type 602)** - Mesh network alerts and warnings (MESH_ALERT per mqtt-schema v0.7.2+) - Array of alerts with type, severity, and message - Node-specific and network-wide alerts - Threshold-based alerting with metric values - **MeshBridgePackage (Type 603)** - Bridge for native mesh protocol messages (MESH_BRIDGE per mqtt-schema v0.7.2+) - Encapsulates native painlessMesh protocol messages - Supports multiple mesh protocols (painlessMesh, esp-now, ble-mesh, etc.) - Includes RSSI, hop count, and timing information - Enables mesh-to-MQTT bridging - **HealthCheckPackage (Type 605)** - Proactive health monitoring with problem detection (MESH_METRICS per mqtt-schema v0.7.2+) - Overall health status (0=critical, 1=warning, 2=healthy) - Problem flags for 10+ specific issue types (low memory, high CPU, network issues, etc.) - Component health scores (memoryHealth, networkHealth, performanceHealth) - Memory leak detection with trend analysis (bytes/hour) - Predictive maintenance indicators (estimatedTimeToFailure) - Actionable recommendations for operators - Environmental monitoring (temperature, temperatureHealth) - **Example Implementation** - `examples/alteriom/metrics_health_node.ino` - Demonstrates complete metrics collection and health monitoring - Configurable collection intervals - CPU usage calculation - Memory leak detection - Network quality assessment - Problem flag detection and alerting - **Comprehensive Testing** - `test/catch/catch_metrics_health_packages.cpp` - 64 test assertions validating both new packages - Edge case handling (min/max values) - Problem flag testing - Health status level validation - Serialization/deserialization verification - **Documentation** - `docs/v1.7.7_MQTT_IMPROVEMENTS.md` - Complete implementation guide - MQTT bridge integration examples - Dashboard integration (Grafana, InfluxDB, Home Assistant) - Performance considerations and optimization tips - Alert configuration examples - Best practices and troubleshooting ### Improved - **MQTT Communication Efficiency** - Optimized metric collection for large meshes - Minimal network overhead (~109 bytes/sec for 10 nodes) - Configurable collection intervals based on health status - Selective reporting of changed metrics - **Problem Detection** - Early warning system for common issues - Memory leak detection with trend analysis - Network instability detection - Performance degradation alerts - Thermal warnings - Mesh partition detection - **Predictive Maintenance** - Proactive failure prevention - Estimated time to failure calculations - Memory exhaustion prediction - Automated recommendations - Health-based interval adjustment ### Performance - **Memory Impact**: <1KB overhead for both packages with reasonable collection intervals - **Network Bandwidth**: Minimal impact (~109 bytes/sec for 10 nodes with 30s/60s intervals) - **CPU Overhead**: <1% additional CPU usage for metric collection ### Compatibility - **100% Backward Compatible** with v1.7.6 - All existing packages (200-203) work unchanged - New packages (204, 604, 605) are optional additions - No breaking changes to existing code - Can be adopted incrementally ## [1.7.6] - 2025-10-19 ### Fixed - **Compilation Failure (Critical)**: Fixed "_task_request_t was not declared" error that broke v1.7.4 and v1.7.5 - **Problem**: `scheduler_queue.cpp` tried to compile but required type `_task_request_t` was undefined - **Root Cause**: Type only defined when `_TASK_THREAD_SAFE` enabled, but macro was disabled in v1.7.5 to fix std::function conflict - **Solution**: Removed thread-safe scheduler queue implementation (dead code that couldn't compile) - Deleted `src/painlessmesh/scheduler_queue.hpp` and `src/painlessmesh/scheduler_queue.cpp` - Simplified `src/painlessmesh/mesh.hpp` to remove queue includes and initialization - **Impact**: ESP32 and ESP8266 now compile successfully on all platforms - **Maintained**: FreeRTOS crash reduction (~85% via semaphore timeout increase) - **Users on v1.7.4/v1.7.5**: Upgrade immediately - those versions do not compile ### Technical Details - Thread-safe scheduler queue feature removed due to TaskScheduler v4.0.x architectural limitations - The queue required `_TASK_THREAD_SAFE` macro, which conflicts with `_TASK_STD_FUNCTION` (required for painlessMesh lambdas) - v1.7.5 correctly disabled the macro but left the queue code in place - Result: Code tried to compile that referenced types that don't exist when macro is disabled - Future: May re-introduce in v1.8.0 if TaskScheduler v4.1+ resolves std::function compatibility ### Testing - Added comprehensive unit test: `test/catch/catch_scheduler_queue_removal.cpp` - Verifies files are removed, mesh.hpp compiles, FreeRTOS fix active, std::function support present - All existing 710+ tests continue to pass - CI/CD builds now succeed on ESP32 and ESP8266 ## [1.7.5] - 2025-10-19 ### Fixed - **TaskScheduler Compatibility (Critical)**: Reverted `_TASK_THREAD_SAFE` mode due to fundamental incompatibility with `_TASK_STD_FUNCTION` - TaskScheduler v4.0.x has architectural limitation: cannot use thread-safe mode with std::function callbacks simultaneously - painlessMesh requires `_TASK_STD_FUNCTION` for lambda callbacks throughout the library (plugin.hpp, connection.hpp, mesh.hpp, router.hpp, painlessMeshSTA.h) - Attempting to disable std::function support breaks all mesh functionality - **FreeRTOS fix now uses Option A only**: Semaphore timeout increase (10ms → 100ms) - **Expected crash reduction**: ~85% (from 30-40% crash rate to ~5-8%) - Less effective than dual approach (95-98%) but necessary to maintain library functionality - See: [docs/troubleshooting/SENSOR_NODE_CONNECTION_CRASH.md](docs/troubleshooting/SENSOR_NODE_CONNECTION_CRASH.md) - **CI/CD Build System**: Fixed PlatformIO Library Dependency Finder (LDF) issues - Added `lib_ldf_mode = deep+` to all 19 example `platformio.ini` files - Enables deep dependency scanning when using `lib_extra_dirs = ../../` for local library loading - Fixes "TaskScheduler.h: No such file or directory" errors in CI builds - Default "chain" mode doesn't resolve nested dependencies for local libraries - **Example Include Order (Critical)**: Fixed TaskScheduler configuration in example sketches - Added `#include "painlessTaskOptions.h"` BEFORE `#include ` in all examples - Ensures `_TASK_STD_FUNCTION` is defined before TaskScheduler compiles - Incorrect order caused TaskScheduler to compile without std::function support, breaking lambda callbacks - Fixes compilation errors: "no known conversion from lambda to TaskCallback" - Affected examples: alteriomSensorNode, alteriomImproved, meshCommandNode, mqttCommandBridge, mqttStatusBridge, mqttTopologyTest - **alteriomImproved Example**: Added build flags to enable advanced features - Added `-DPAINLESSMESH_ENABLE_VALIDATION` for message validation - Added `-DPAINLESSMESH_ENABLE_METRICS` for performance metrics - Added `-DPAINLESSMESH_ENABLE_MEMORY_OPTIMIZATION` for object pooling - These features are optional and must be explicitly enabled via build flags ### Changed - **FreeRTOS Stability**: Downgraded from dual-approach to single-approach fix - Option A (semaphore timeout): ✅ Active (10ms → 100ms in mesh.hpp line 555) - Option B (thread-safe scheduler): ❌ Disabled due to TaskScheduler v4.0.x limitations - Trade-off: Prioritized library functionality and CI stability over maximum crash protection - Future resolution requires either: TaskScheduler v4.1+ fix, library fork, or architecture rewrite ### Documentation - Updated `SENSOR_NODE_CONNECTION_CRASH.md` with TaskScheduler compatibility notes - Added detailed comments in `painlessTaskOptions.h` explaining the incompatibility and workaround - Documented correct include order pattern for all examples using local library loading ## [1.7.4] - 2025-10-19 ### Fixed - **FreeRTOS Assertion Failure (ESP32)**: Comprehensive dual-approach fix for `vTaskPriorityDisinheritAfterTimeout` crashes - **Option A**: Increased semaphore timeout from 10ms to 100ms in `mesh.hpp` to prevent premature timeout during WiFi callbacks - **Option B**: Implemented thread-safe scheduler with FreeRTOS queue-based task control - Added `_TASK_THREAD_SAFE` option for ESP32 in `painlessTaskOptions.h` - Created `scheduler_queue.hpp/cpp` with ISR-safe enqueue/dequeue functions - Overrides TaskScheduler's weak symbols for true thread safety - Combined approach achieves ~95-98% crash reduction - Memory overhead: <1KB, Performance impact: <0.1% - ESP32 only (ESP8266 unaffected - no FreeRTOS) - **ArduinoJson v7 Compatibility**: Complete migration from deprecated v6 API - Updated `mqttCommandBridge` example to use v7 syntax - Replaced `containsKey()` with `is()` checks - Replaced `DynamicJsonDocument(size)` with automatic `JsonDocument` - Replaced `createNestedArray/Object()` with `to()` - Fixed router memory tests for v6/v7 compatibility - **Build System**: Fixed syntax error in test files - Removed extra bracket in `catch_router_memory.cpp` character literals (`'['])` → `'[')`) - Suppressed unused variable warnings in ArduinoJson v7 path ### Added - **Documentation**: Comprehensive FreeRTOS crash troubleshooting - `FREERTOS_FIX_IMPLEMENTATION.md`: Complete implementation guide with testing procedures, rollback options, and performance metrics - `SENSOR_NODE_CONNECTION_CRASH.md`: Action plan with root cause analysis, fix procedures, and test scenarios - `CRASH_QUICK_REF.md`: Quick reference card for emergency fixes ### Performance - **ESP32 FreeRTOS Stability**: Crash rate reduced from ~30-40% to <2-5% - **Memory Usage**: +192 bytes heap (queue allocation), +~500 bytes flash (ESP32 only) - **Latency**: Task enqueue <1ms typical, 10ms max; dequeue non-blocking ## [1.7.3] - 2025-10-16 ### Fixed - **Router Memory Safety**: Replaced dangerous escalating memory allocation workaround with safe pre-calculated capacity - Removed static `baseCapacity` variable that grew indefinitely (512B → 20KB) - Implemented single-allocation strategy with pre-calculated capacity based on message size and nesting depth - Added 8KB safety cap to protect ESP8266 devices from OOM crashes - Support for both ArduinoJson v6 and v7 with version-aware capacity calculation ### Performance - **Small messages (50B)**: +512B predictable overhead (vs. variable 512B-20KB in v1.7.0) - **Medium messages (500B)**: -3596B saved by eliminating retry allocations - **Large messages (2KB)**: -17KB saved by preventing escalation to 20KB ### Added - **Tests**: New comprehensive memory safety test suite (`test/catch/catch_router_memory.cpp`) - Simple message parsing tests - Deeply nested JSON structure tests (10+ levels) - Oversized message handling tests - Predictable capacity allocation verification - 14 new assertions, all passing ### Documentation - **CODE_REFACTORING_RECOMMENDATIONS.md**: Comprehensive code analysis document with 8 prioritized refactoring recommendations (P0-P3) - **PATCH_v1.7.3.md**: Complete release notes with before/after comparison, performance metrics, and migration guide ### Technical Details - Fixes issue #521: ArduinoJson copy constructor segmentation fault workaround - All 710+ test assertions passing across 18 test suites - Docker-based testing on Linux x86_64 with CMake + Ninja ## [1.7.2] - 2025-10-15 ### Fixed - **NPM Configuration**: Updated `.npmrc` to use public NPM registry instead of GitHub Packages - **Dependencies**: Moved `@alteriom/mqtt-schema` back to `devDependencies` now that it's publicly available - **Automated Releases**: Fixed npm install failures during automated release workflow - **Package Availability**: Package now accessible at ## [1.7.1] - 2025-10-15 ### Fixed - **mqttStatusBridge**: Fixed scheduler access by using external `Scheduler` reference instead of protected `mScheduler` member - **mqttStatusBridge**: Corrected Task handling - `publishTask` is an object, not a pointer - **mqttStatusBridge**: Fixed node list iteration using proper iterators instead of array-style indexing - **alteriomSensorNode**: Updated to ArduinoJson v7 API - replaced deprecated `DynamicJsonDocument` with `JsonDocument` - **alteriomSensorNode**: Removed usage of deprecated `jsonObjectSize()` method - Resolved compilation errors preventing ESP32/ESP8266 builds ### Technical Details - `mesh.getNodeList()` returns `std::list` which doesn't support `operator[]` indexing - Changed from `mesh.mScheduler.addTask()` to `scheduler.addTask()` with external scheduler - Changed from `DynamicJsonDocument doc(size)` to `JsonDocument doc` for ArduinoJson v7 compatibility ## [1.7.0] - 2025-10-15 ### 🚀 Major Features #### Phase 2: Broadcast OTA & MQTT Status Bridge **Broadcast OTA Distribution** - ✨ **Broadcast Mode OTA**: True mesh-wide firmware distribution with ~98% network traffic reduction for 50+ node meshes - 📡 **Parallel Updates**: All nodes receive firmware chunks simultaneously instead of sequential unicast - ⚡ **Performance**: ~50x faster for 50-node mesh, ~100x faster for 100-node mesh - 🔧 **Simple API**: Single parameter change: `mesh.offerOTA(..., true)` enables broadcast mode - 🔄 **Backward Compatible**: Defaults to unicast mode (Phase 1), no breaking changes - 📊 **Scalability**: Efficiently handles 50-100+ node meshes with minimal overhead **MQTT Status Bridge** - 🌉 **Professional Monitoring**: Complete MQTT bridge for publishing mesh status to monitoring tools - 📈 **Multiple Topics**: Publishes topology, metrics, alerts, and per-node status - 🔗 **Tool Integration**: Ready for Grafana, InfluxDB, Prometheus, Home Assistant, Node-RED - ⚙️ **Configurable**: Adjustable publish intervals, enable/disable features per need - 🎯 **Production Ready**: Designed for enterprise IoT and commercial deployments - 📋 **Schema Compliant**: Uses @alteriom/mqtt-schema v0.5.0 for standardized messaging #### Mesh Topology Visualization - 📊 **Visualization Guide**: Comprehensive 980-line guide for building web dashboards (docs/MESH_TOPOLOGY_GUIDE.md) - 🎨 **D3.js Examples**: Complete force-directed graph visualization (200+ lines) - 🕸️ **Cytoscape.js Examples**: Network topology view (150+ lines) - 🔴 **Node.js Dashboard**: Real-time dashboard with Express + Socket.IO - 🐍 **Python Monitor**: Console monitoring with Rich library - 🔄 **Node-RED Flows**: Ready-to-import flow JSON for rapid development - 🛠️ **Troubleshooting Guide**: Common issues and performance tuning ### 🐛 Critical Bug Fixes #### Compilation & Build Fixes - 🔧 **Fixed Missing `#include `**: Added missing C++ standard library header to `src/painlessmesh/mesh.hpp` - **Impact**: Fixes compilation errors: "'vector' in namespace 'std' does not name a template type" - **Affected**: All projects using getConnectionDetails() or latencySamples - **File**: src/painlessmesh/mesh.hpp (line 4) - **Documentation**: VECTOR_INCLUDE_FIX.md - 📦 **PlatformIO Library Structure**: Fixed SCons build errors for external projects - Added explicit `srcDir` and `includeDir` to library.json - Removed conflicting `export.include` section - Fixed header references in library.properties (painlessMesh.h) - **Impact**: Fixes "cannot resolve directory for painlessMeshSTA.cpp" errors - **Documentation**: LIBRARY_STRUCTURE_FIX.md, PLATFORMIO_USAGE.md, SCONS_BUILD_FIX.md - 📝 **npm Build Scripts**: Fixed UnboundLocalError during `npm link` - Renamed `build` → `dev:build` and `prebuild` → `dev:prebuild` - Prevents automatic build execution during package installation - **Impact**: Fixes Python errors when using library as npm dependency ### 📚 Documentation #### New Documentation Files (7 files) 1. **docs/MESH_TOPOLOGY_GUIDE.md** (980 lines) - Complete visualization guide with 5 working examples - D3.js, Cytoscape.js, Python, Node-RED implementations - Performance considerations and troubleshooting 2. **docs/PHASE2_GUIDE.md** (~500 lines) - Complete API reference for Phase 2 features - Usage examples and performance benchmarks - Integration guides for monitoring tools - Migration guide from Phase 1 3. **docs/improvements/PHASE2_IMPLEMENTATION.md** (~600 lines) - Technical architecture and implementation details - MQTT topic schema documentation - Performance analysis and testing strategy 4. **LIBRARY_STRUCTURE_FIX.md** - PlatformIO library structure improvements - Validation checklist and testing guide 5. **PLATFORMIO_USAGE.md** - Quick start guide for PlatformIO users - Common issues and solutions 6. **SCONS_BUILD_FIX.md** - Comprehensive troubleshooting for PlatformIO builds - Step-by-step diagnostic procedures 7. **VECTOR_INCLUDE_FIX.md** - Documentation of missing C++ header fix - Testing and verification instructions #### Updated Documentation - **README.md**: Enhanced with Phase 2 features and schema v0.5.0 - **docs/MQTT_BRIDGE_COMMANDS.md**: Updated version references - **examples/**: New Phase 2 examples added ### 🛠️ New Tools & Scripts - **scripts/validate_library_structure.py** (250+ lines) - Automated validation of PlatformIO library structure - 8 comprehensive checks (all passing) - Detects common configuration issues ### 🔄 Enhanced Examples - **examples/alteriom/phase2_features.ino**: Demonstrates broadcast OTA - **examples/bridge/mqtt_status_bridge.hpp**: Complete MQTT bridge implementation - **examples/bridge/mqtt_status_bridge_example.ino**: Full working bridge example ### ⚙️ Configuration Changes **library.json** - Added `"srcDir": "src"` for explicit source directory - Added `"includeDir": "src"` for explicit include directory - Removed conflicting `"export": {"include": "src"}` section **library.properties** - Updated `includes=painlessMesh.h` (was AlteriomPainlessMesh.h) **package.json** - Renamed `build` → `dev:build` (prevents auto-execution) - Renamed `prebuild` → `dev:prebuild` (prevents auto-execution) - Updated to @alteriom/mqtt-schema v0.5.0 ### 📊 Performance Improvements **Broadcast OTA Performance** - **Network Traffic**: 90% reduction (10 nodes), 98% reduction (50 nodes), 99% reduction (100 nodes) - **Update Speed**: ~N times faster (parallel vs sequential) - **Example**: 150-chunk firmware to 50 nodes - Unicast: 7,500 transmissions - Broadcast: 150 transmissions - **Savings: 98% (7,350 transmissions)** **Memory Impact** - Broadcast OTA: +2-5KB per node (chunk tracking) - MQTT Bridge: +5-8KB (root node only) - Minimal overhead for ESP32, acceptable for ESP8266 ### 🔐 Schema Compliance - ✅ Fully compliant with @alteriom/mqtt-schema v0.5.0 - ✅ Topology messages include all required envelope fields - ✅ Node objects include firmware_version, uptime_seconds, connection_count - ✅ Schema versioning for forward compatibility ### ⚠️ Breaking Changes **None** - This release is 100% backward compatible with v1.6.x - Broadcast OTA defaults to `false` (unicast mode preserved) - MQTT bridge is optional add-on - All Phase 1 APIs unchanged - Existing sketches work without modification ### 🔄 Migration Guide **No migration required** for existing code. To adopt new features: **Enable Broadcast OTA:** ```cpp // Before (v1.6.x) mesh.offerOTA(role, hw, md5, parts, false, false, true); // After (v1.7.0) - add broadcast parameter mesh.offerOTA(role, hw, md5, parts, false, true, true); // ^^^^ broadcast ``` **Add MQTT Status Bridge:** ```cpp #include "examples/bridge/mqtt_status_bridge.hpp" MqttStatusBridge bridge(mesh, mqttClient); bridge.setPublishInterval(30000); bridge.begin(); ``` ### 🎯 Recommended For - ✅ Production IoT deployments with 10-100+ nodes - ✅ Commercial systems requiring professional monitoring - ✅ Enterprise environments needing Grafana/Prometheus integration - ✅ Projects with limited network bandwidth - ✅ Systems requiring rapid firmware distribution ### 📖 Complete Documentation - [Phase 2 User Guide](docs/PHASE2_GUIDE.md) - API reference and usage - [Phase 2 Implementation](docs/improvements/PHASE2_IMPLEMENTATION.md) - Technical details - [Mesh Topology Guide](docs/MESH_TOPOLOGY_GUIDE.md) - Visualization examples - [MQTT Bridge Commands](docs/MQTT_BRIDGE_COMMANDS.md) - Command reference - [Library Structure Fix](LIBRARY_STRUCTURE_FIX.md) - Build system improvements - [Vector Include Fix](VECTOR_INCLUDE_FIX.md) - Compilation fix details ### 🧪 Testing - ✅ All Phase 1 tests passing (80 assertions in 7 test cases) - ✅ Backward compatibility verified - ✅ No regressions introduced - ✅ Library structure validation: 8/8 checks passing - ✅ Compilation successful on ESP32 and ESP8266 ### 🙏 Acknowledgments This release includes contributions from Phase 2 implementation, bug fixes discovered by the community, and comprehensive documentation improvements based on user feedback. **Next**: Phase 3 features (progressive rollout OTA, real-time telemetry streams) as outlined in FEATURE_PROPOSALS.md --- ## [1.6.1] - 2025-09-29 ### Added - **Arduino Library Manager Support**: Updated library name to "Alteriom PainlessMesh" for better discoverability - **NPM Package Publishing**: Complete NPM publication setup with dual registry support - Public NPM registry: `@alteriom/painlessmesh` - GitHub Packages registry: `@alteriom/painlessmesh` (scoped) - Automated version consistency across library.properties, library.json, and package.json - **GitHub Wiki Automation**: Automatic wiki synchronization on releases - Home page generated from README.md - API Reference with auto-generated documentation - Examples page with links to repository examples - Installation guide for multiple platforms - **Enhanced Release Workflow**: Comprehensive publication automation - NPM publishing to both public and GitHub Packages registries - GitHub Wiki updates with structured documentation - Arduino Library Manager submission instructions - Release validation with multi-file version consistency - **Arduino IDE Support**: Added keywords.txt for syntax highlighting - **Distribution Documentation**: Complete guides for all publication channels - docs/archive/RELEASE_SUMMARY.md template for release notes (archived) - docs/archive/TRIGGER_RELEASE.md for step-by-step release instructions (archived, see RELEASE_GUIDE.md) - Enhanced RELEASE_GUIDE.md with comprehensive publication workflow - **Version Management**: Enhanced bump-version script - Updates all three version files simultaneously (library.properties, library.json, package.json) - Comprehensive version consistency validation - Clear instructions for NPM and GitHub Packages publication ### Changed - **Library Name**: Updated from "Painless Mesh" to "Alteriom PainlessMesh" for Arduino Library Manager - **Release Process**: Streamlined to support multiple package managers - Single commit with "release:" prefix triggers full publication pipeline - Automated testing, building, and publishing across all channels - Wiki documentation automatically synchronized - **CI/CD Pipeline**: Enhanced with NPM publication capabilities - Dual NPM registry publishing (public + GitHub Packages) - Automated wiki updates with generated content - Comprehensive pre-release validation - **Documentation Structure**: Reorganized for multi-channel distribution - Clear separation between automatic and manual processes - Platform-specific installation instructions - Troubleshooting guides for each distribution channel ### Fixed - **NPM Publishing**: Fixed registry configuration issues that prevented NPM publication - **GitHub Pages**: Improved workflow to handle cases where Pages is not configured - **PlatformIO Build**: Fixed include paths in improved_sensor_node.ino example - **Package Configuration**: Consistent version management across all package files - **Release Documentation**: Complete coverage of all distribution channels - **Version Validation**: Prevents releases with inconsistent version numbers ## [1.6.0] - 2025-09-29 ### Added - Enhanced Alteriom-specific package documentation and examples - Updated repository URLs and metadata for Alteriom fork - Improved release process documentation - Fixed deprecated GitHub Actions in release workflow - Added concurrency controls to prevent duplicate workflow runs - Comprehensive Alteriom package documentation and quick start guide ### Changed - Migrated from deprecated `actions/create-release@v1` to GitHub CLI for releases - Updated library.properties and library.json to reflect Alteriom ownership - Enhanced package descriptions to highlight Alteriom extensions ### Fixed - Fixed deprecated GitHub Actions in release workflow - Corrected undefined variable references in upload workflow steps - Updated repository URLs from GitLab to GitHub in library files ## [1.5.6] - Current Release ### Features - painlessMesh is a user-friendly library for creating mesh networks with ESP8266 and ESP32 devices - Automatic routing and network management - JSON-based messaging system - Time synchronization across all nodes - Support for coordinated behaviors like synchronized displays - Sensor network patterns for IoT applications ### Platforms Supported - ESP32 (espressif32) - ESP8266 (espressif8266) ### Dependencies - ArduinoJson ^7.4.2 - TaskScheduler ^3.8.5 - AsyncTCP ^3.4.7 (ESP32) - ESPAsyncTCP ^2.0.0 (ESP8266) ### Alteriom Extensions - SensorPackage for environmental monitoring - CommandPackage for device control - StatusPackage for health monitoring - Type-safe message handling --- ## Release Notes ### How to Release 1. Update version in both `library.properties` and `library.json` 2. Add changes to this CHANGELOG.md under the new version 3. Commit with message starting with `release:` (e.g., `release: v1.6.0`) 4. Push to main branch 5. GitHub Actions will automatically: - Create a git tag - Generate GitHub release - Package library for distribution - Update documentation ### Version Numbering This project follows [Semantic Versioning](https://semver.org/): - **MAJOR** version when you make incompatible API changes - **MINOR** version when you add functionality in a backwards compatible manner - **PATCH** version when you make backwards compatible bug fixes Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.