{
  "id": "zephcore",
  "name": "ZephCore",
  "type": "custom",
  "maintainer": "liquidraver",
  "description": "A ground-up port of MeshCore from Arduino to the Zephyr RTOS. Replaces the Arduino loop with Zephyr's event-driven primitives so the CPU sleeps between events, while aiming for full protocol compatibility with stock MeshCore firmware and the MeshCore mobile apps.\n",
  "repository": "https://github.com/liquidraver/ZephCore",
  "website": "",
  "license": "MIT",
  "status": "active",
  "scope": "universal",
  "lifecycle": "active",
  "maturity": "beta",
  "distribution": "community",
  "lineage": {
    "kind": "reimplementation",
    "upstreamFirmwareId": "meshcore",
    "upstreamRepository": "https://github.com/meshcore-dev/MeshCore"
  },
  "runtime": {
    "framework": "zephyr",
    "language": "cpp"
  },
  "roles": [
    "companion",
    "repeater",
    "room-server",
    "observer"
  ],
  "features": [
    "Zephyr RTOS event-driven architecture",
    "CAD-based RX power saving",
    "Adaptive contention window",
    "Adaptive Power Control",
    "DFU / OTA update support",
    "Listen-only observer (WiFi + MQTT)",
    "Protocol-compatible with MeshCore apps"
  ],
  "capabilities": {
    "protocol": {
      "meshcoreCompatible": true,
      "rawPacketSend": true,
      "rawPacketObserve": true
    },
    "transports": {
      "ble": true,
      "usbSerial": true,
      "nativeTcp": true,
      "wifiAp": true
    },
    "operations": {
      "ota": true,
      "bleDfu": true,
      "webFlasher": false
    },
    "networking": {
      "repeater": true,
      "roomServer": true,
      "observer": true,
      "mqtt": true,
      "kissModem": false
    },
    "hardware": {
      "gps": true,
      "display": true,
      "sensors": true,
      "lowPowerRx": true
    }
  },
  "devices": [
    {
      "id": "rak-4631",
      "status": "supported"
    },
    {
      "id": "atlavox-m1",
      "status": "supported"
    },
    {
      "id": "atlavox-beacon",
      "status": "supported"
    },
    {
      "id": "rak-wismesh-repeater-mini",
      "status": "supported"
    },
    {
      "id": "yetiwurks-basestation",
      "status": "supported"
    },
    {
      "id": "uart-solar-node-station",
      "status": "supported"
    },
    {
      "id": "lilygo-techo",
      "status": "supported"
    },
    {
      "id": "wio-tracker-l1",
      "status": "supported"
    },
    {
      "id": "wio-tracker-l1-pro",
      "status": "supported"
    },
    {
      "id": "heltec-v3",
      "status": "supported"
    },
    {
      "id": "heltec-wsl3",
      "status": "supported"
    },
    {
      "id": "heltec-v4",
      "status": "supported"
    },
    {
      "id": "heltec-v4-exp",
      "status": "supported"
    },
    {
      "id": "heltec-t114",
      "status": "supported"
    },
    {
      "id": "heltec-wt3",
      "status": "supported"
    },
    {
      "id": "lilygo-t-beam-sx1262",
      "status": "supported"
    },
    {
      "id": "lilygo-tlora-c6",
      "status": "supported"
    },
    {
      "id": "rak-13302",
      "status": "supported"
    },
    {
      "id": "rak-wismesh-tag",
      "status": "supported"
    },
    {
      "id": "sensecap-solar-p1",
      "status": "supported"
    },
    {
      "id": "sensecap-solar-p1-pro",
      "status": "supported"
    },
    {
      "id": "sensecap-t1000e",
      "status": "supported"
    },
    {
      "id": "station-g2",
      "status": "supported"
    },
    {
      "id": "thinknode-m1",
      "status": "supported"
    },
    {
      "id": "thinknode-m3",
      "status": "supported"
    },
    {
      "id": "thinknode-m6",
      "status": "supported"
    },
    {
      "id": "faketec",
      "status": "supported"
    },
    {
      "id": "gatiot-gat562-30s",
      "status": "supported"
    },
    {
      "id": "xiao-esp32c3",
      "status": "supported"
    },
    {
      "id": "xiao-esp32c6",
      "status": "supported"
    },
    {
      "id": "xiao-esp32s3",
      "status": "supported"
    },
    {
      "id": "xiao-esp32s3-plain",
      "status": "supported"
    },
    {
      "id": "xiao-nrf52",
      "status": "supported"
    },
    {
      "id": "xiao-mg24",
      "status": "supported"
    },
    {
      "id": "xiao-nrf54l15",
      "status": "partial",
      "notes": "Early bring-up; not all peripherals wired."
    }
  ],
  "popularity": {
    "githubStars": 129,
    "githubForks": 31,
    "githubWatchers": 8,
    "githubOpenIssues": 14,
    "githubContributors": 14,
    "releaseDownloads": 5937,
    "latestReleaseDownloads": 53,
    "lastChecked": "2026-08-05"
  },
  "verification": {
    "sourceAvailable": true,
    "releasesAvailable": true,
    "ciBuilds": true,
    "lastChecked": "2026-08-05"
  },
  "source": {
    "path": "data/firmwares/zephcore/firmware.yaml",
    "updatedAt": "2026-08-05T08:12:38Z"
  },
  "latest_version": "1.16.8-zephcore",
  "released": "2026-07-31",
  "releases": [
    {
      "version": "1.16.8-zephcore",
      "name": "Firmware 1.16.8-zephcore",
      "datetime": "2026-07-31T15:45:30Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/1.16.8-zephcore",
      "prerelease": true,
      "notes": "# ZephCore 1.16.8-zephcore\n\n> [!CAUTION]\n> **This is a pre-release.** ESP32 light sleep is enabled by default on Heltec repeaters and has not\n> been validated on hardware yet — see the light-sleep section for what to check and how to build it\n> out. It is deliberately **not published to the apps.meshamerica.com configurator**, which keeps\n> offering v1.16.7; flash this one manually from the assets below if you want to help test it.\n> The nRF based boards should be fine.\n\n> [!IMPORTANT]\n> ## Before you upgrade\n>\n> **From v1.16.5 / v1.16.6 / v1.16.7** — clean flash, no re-bond, bonds and data survive.\n>\n> **From v1.16.2 / v1.16.3 / v1.16.4** — clean flash, bonds and data survive. If you are on an\n> **ESP32-S3 / ESP32-C board** and have *not* yet taken the v1.16.5 update, you still owe the one-time\n> serial reflash: the app moved to flash offset `0x10000` in v1.16.5, and a board on the old layout\n> cannot cross that update over WiFi-OTA or the browser flasher — **flash the `-merged.bin` once over\n> USB/serial.** Affected boards: *Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO\n> ESP32-S3 / C3 / C6, LilyGo T-Lora C6, ThinkNode M9.* Identity, contacts, channels, prefs, and BLE\n> bonds are preserved. nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native\n> Linux are unaffected.\n>\n> **From v1.16.1 or older** — flash it; on first boot it clears BLE bonds automatically (identity,\n> contacts, channels, prefs preserved). Re-bond your phone/desktop once.\n>\n> **Coming from Arduino MeshCore** — flash it; auto-formats on first boot (new identity, clean storage).\n>\n> **Heltec ESP32 repeaters now light-sleep**, and their USB CLI stops answering 10 minutes after boot\n> as a result. Reconnecting your terminal normally resets the node and gives you another 10 minutes;\n> if it doesn't, power-cycle. Details in the light-sleep section below.\n>\n> **One CLI rename:** `set/get cad.probe.interval` is now `set/get probe.interval` — the same setting,\n> renamed because one measurement now feeds both the noise floor and the CAD probe. Your stored value\n> carries over; only the command name changed. `set/get agc.reset.interval` is gone (see below).\n>\n> Take this with a grain of salt — try the formatters if anything anomalous happens with your node.\n\n---\n\nA **power and radio-measurement release**. Repeaters no longer wake on a fixed 5 s tick — they sleep\nuntil their next real deadline. Every ESP32 build drops to 80 MHz, and Heltec repeaters now light-sleep\nbetween packets. The noise-floor sampler and CAD probe were unified into one measurement, the\nSX126x RSSI/AGC calibration is now band-correct, and the observer role got a batch of fixes for settings\nthat silently never reached the hardware.\n\n## Highlights\n\n### Repeaters are now deadline-driven — the periodic housekeeping tick is gone\n\nEvery repeater woke five times a second-and-a-bit forever: a fixed 5 s \"housekeeping\" timer that ran the\nnoise-floor sample, the CAD probe, advert timers and the tempradio revert. None of that actually needs a\npoll — each item is either a deadline or a sampler with its own interval.\n\nThe new `mesh/Maintenance.h` contract lets the event loop ask *\"when does maintenance next have work?\"*\nand arm **one one-shot wake for exactly that moment**. An idle repeater now sleeps until its soonest real\ndeadline instead of on a fixed cadence. A hard ceiling (`CONFIG_ZEPHCORE_MAINTENANCE_BACKSTOP_MS`,\ndefault **60 s**) clamps the wake as a safety net against a missed deadline — it should never bind in\nnormal operation.\n\nCompanions and room servers keep their periodic tick deliberately: the companion block contains genuine\npollers (contact-dump progress, BLE advertising watchdog) with no deadline to arm, and the room server is\npaced by its own 500 ms push timer regardless.\n\n### ESP32 power: 80 MHz everywhere, and light sleep on Heltec repeaters\n\nEspressif parts have no equivalent of the nRF52's \"idle at microamps in WFI\" — `WAITI` gates the core\nclock but le\n…",
      "notesHtml": "<h1>ZephCore 1.16.8-zephcore</h1>\n<blockquote>\n<p>[!CAUTION]\n<strong>This is a pre-release.</strong> ESP32 light sleep is enabled by default on Heltec repeaters and has not\nbeen validated on hardware yet — see the light-sleep section for what to check and how to build it\nout. It is deliberately <strong>not published to the apps.meshamerica.com configurator</strong>, which keeps\noffering v1.16.7; flash this one manually from the assets below if you want to help test it.\nThe nRF based boards should be fine.</p>\n</blockquote>\n<blockquote>\n<p>[!IMPORTANT]</p>\n<h2>Before you upgrade</h2>\n<p><strong>From v1.16.5 / v1.16.6 / v1.16.7</strong> — clean flash, no re-bond, bonds and data survive.</p>\n<p><strong>From v1.16.2 / v1.16.3 / v1.16.4</strong> — clean flash, bonds and data survive. If you are on an\n<strong>ESP32-S3 / ESP32-C board</strong> and have <em>not</em> yet taken the v1.16.5 update, you still owe the one-time\nserial reflash: the app moved to flash offset <code>0x10000</code> in v1.16.5, and a board on the old layout\ncannot cross that update over WiFi-OTA or the browser flasher — <strong>flash the <code>-merged.bin</code> once over\nUSB/serial.</strong> Affected boards: <em>Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO\nESP32-S3 / C3 / C6, LilyGo T-Lora C6, ThinkNode M9.</em> Identity, contacts, channels, prefs, and BLE\nbonds are preserved. nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native\nLinux are unaffected.</p>\n<p><strong>From v1.16.1 or older</strong> — flash it; on first boot it clears BLE bonds automatically (identity,\ncontacts, channels, prefs preserved). Re-bond your phone/desktop once.</p>\n<p><strong>Coming from Arduino MeshCore</strong> — flash it; auto-formats on first boot (new identity, clean storage).</p>\n<p><strong>Heltec ESP32 repeaters now light-sleep</strong>, and their USB CLI stops answering 10 minutes after boot\nas a result. Reconnecting your terminal normally resets the node and gives you another 10 minutes;\nif it doesn't, power-cycle. Details in the light-sleep section below.</p>\n<p><strong>One CLI rename:</strong> <code>set/get cad.probe.interval</code> is now <code>set/get probe.interval</code> — the same setting,\nrenamed because one measurement now feeds both the noise floor and the CAD probe. Your stored value\ncarries over; only the command name changed. <code>set/get agc.reset.interval</code> is gone (see below).</p>\n<p>Take this with a grain of salt — try the formatters if anything anomalous happens with your node.</p>\n</blockquote>\n<hr />\n<p>A <strong>power and radio-measurement release</strong>. Repeaters no longer wake on a fixed 5 s tick — they sleep\nuntil their next real deadline. Every ESP32 build drops to 80 MHz, and Heltec repeaters now light-sleep\nbetween packets. The noise-floor sampler and CAD probe were unified into one measurement, the\nSX126x RSSI/AGC calibration is now band-correct, and the observer role got a batch of fixes for settings\nthat silently never reached the hardware.</p>\n<h2>Highlights</h2>\n<h3>Repeaters are now deadline-driven — the periodic housekeeping tick is gone</h3>\n<p>Every repeater woke five times a second-and-a-bit forever: a fixed 5 s \"housekeeping\" timer that ran the\nnoise-floor sample, the CAD probe, advert timers and the tempradio revert. None of that actually needs a\npoll — each item is either a deadline or a sampler with its own interval.</p>\n<p>The new <code>mesh/Maintenance.h</code> contract lets the event loop ask <em>\"when does maintenance next have work?\"</em>\nand arm <strong>one one-shot wake for exactly that moment</strong>. An idle repeater now sleeps until its soonest real\ndeadline instead of on a fixed cadence. A hard ceiling (<code>CONFIG_ZEPHCORE_MAINTENANCE_BACKSTOP_MS</code>,\ndefault <strong>60 s</strong>) clamps the wake as a safety net against a missed deadline — it should never bind in\nnormal operation.</p>\n<p>Companions and room servers keep their periodic tick deliberately: the companion block contains genuine\npollers (contact-dump progress, BLE advertising watchdog) with no deadline to arm, and the room server is\npaced by its own 500 ms push timer regardless.</p>\n<h3>ESP32 power: 80 MHz everywhere, and light sleep on Heltec repeaters</h3>\n<p>Espressif parts have no equivalent of the nRF52's \"idle at microamps in WFI\" — <code>WAITI</code> gates the core\nclock but le\n…</p>\n"
    },
    {
      "version": "1.16.7-zephcore",
      "name": "Firmware 1.16.7-zephcore",
      "datetime": "2026-07-25T13:15:13Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/1.16.7-zephcore",
      "prerelease": false,
      "notes": "# ZephCore 1.16.7-zephcore\n\n> [!IMPORTANT]\n> ## Before you upgrade\n>\n> **From v1.16.6 / v1.16.5** — clean flash, no re-bond, bonds and data survive.\n>\n> **From v1.16.2 / v1.16.3 / v1.16.4** — clean flash, bonds and data survive. If you are on an\n> **ESP32-S3 / ESP32-C board** and have *not* yet taken the v1.16.5 update, you still owe the one-time\n> serial reflash: the app moved to flash offset `0x10000` in v1.16.5, and a board on the old layout\n> cannot cross that update over WiFi-OTA or the browser flasher — **flash the `-merged.bin` once over\n> USB/serial.** Affected boards: *Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO\n> ESP32-S3 / C3 / C6, LilyGo T-Lora C6.* Identity, contacts, channels, prefs, and BLE bonds are\n> preserved. nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are\n> unaffected.\n>\n> **From v1.16.1 or older** — flash it; on first boot it clears BLE bonds automatically (identity,\n> contacts, channels, prefs preserved). Re-bond your phone/desktop once.\n>\n> **Coming from Arduino MeshCore** — flash it; auto-formats on first boot (new identity, clean storage).\n>\n> Take this with a grain of salt — try the formatters if anything anomalous happens with your node.\n\n---\n\nA stability-and-hardware release. The headline is a **whole-node wedge fixed** in the GPS power-management\npath that shipped in v1.16.6, plus **RX duty cycle finally working on LR1110 boards** and a new safety\nmargin that stops signal-independent duty-cycle packet drops on every radio. Rounded out by joystick-UI\nfixes and a color-display improvement for the Heltec T114.\n\n## Highlights\n\n### Fixed: a GPS node could wedge completely on standby (regression in v1.16.6)\n\nv1.16.6 introduced GPS-UART suspend to save power (an armed nRF UARTE receiver costs ~0.5–1 mA even with\nthe GPS module powered down). It had a race that could **hang the entire node** — no LoRa, no USB, no BLE,\nCLI answering `-> busy`, recoverable only by reboot.\n\nThe upstream nRF UARTE suspend path spins forever waiting for an `RXTO` event after issuing `STOPRX`. In\ninterrupt-driven mode the receiver runs one byte at a time and re-arms from its ISR; if a byte happened to\nfinish in the narrow window where the driver had already disabled the `ENDRX` interrupt, the receiver was\nleft stopped-and-un-rearmed, `STOPRX` produced no `RXTO`, and the wait never returned — with the main mesh\nthread stuck inside it. Any nRF board that powers its GPS module down and suspends the port on standby\ncould hit this.\n\nFixed in two independent layers, so neither alone is load-bearing:\n\n- **`ZephyrGPSManager` settles ~5 ms before suspending** the GPS UART, letting the last in-flight byte\n  finish and the RX ISR re-arm, so the receiver is armed-and-idle when `STOPRX` fires — the state that\n  reliably yields `RXTO`. Standby happens at most every few minutes, so the cost is negligible.\n- **New patch `0010` bounds the driver's `RXTO` wait** to 4 ms instead of spinning forever. On timeout it\n  falls through to the unconditional `nrf_uarte_disable()` that force-stops the receiver anyway — so even\n  if a byte still lands in the race window, the node can never hang.\n\n### RX duty cycle now works on LR1110 boards\n\nLR1110 boards (T1000-E, ThinkNode M9, and other LR11xx radios) now get real **RX duty-cycle sniff mode**\ninstead of sitting in continuous receive — a substantial receive-power saving on battery nodes, the same\nmechanism SX126x boards have had. This took a significant rework of the LR11xx LoRa driver to land\ncorrectly.\n\n### New: duty-cycle safety margin stops signal-independent packet drops\n\n`set`-able via `CONFIG_ZEPHCORE_LORA_DC_MARGIN_PCT` (default **15%**), shared by every radio\n(SX126x / LR11xx / LR20xx).\n\nThe theoretical per-cycle \"deaf-time\" budget that duty cycle is computed from assumes the sleep clock and\nthe wake transition are exact. They are not: the chip's sleep timer runs on an internal RC oscillator\n(RC64k on SX126x, RTC on LR11xx) that drifts several\n…",
      "notesHtml": "<h1>ZephCore 1.16.7-zephcore</h1>\n<blockquote>\n<p>[!IMPORTANT]</p>\n<h2>Before you upgrade</h2>\n<p><strong>From v1.16.6 / v1.16.5</strong> — clean flash, no re-bond, bonds and data survive.</p>\n<p><strong>From v1.16.2 / v1.16.3 / v1.16.4</strong> — clean flash, bonds and data survive. If you are on an\n<strong>ESP32-S3 / ESP32-C board</strong> and have <em>not</em> yet taken the v1.16.5 update, you still owe the one-time\nserial reflash: the app moved to flash offset <code>0x10000</code> in v1.16.5, and a board on the old layout\ncannot cross that update over WiFi-OTA or the browser flasher — <strong>flash the <code>-merged.bin</code> once over\nUSB/serial.</strong> Affected boards: <em>Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO\nESP32-S3 / C3 / C6, LilyGo T-Lora C6.</em> Identity, contacts, channels, prefs, and BLE bonds are\npreserved. nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are\nunaffected.</p>\n<p><strong>From v1.16.1 or older</strong> — flash it; on first boot it clears BLE bonds automatically (identity,\ncontacts, channels, prefs preserved). Re-bond your phone/desktop once.</p>\n<p><strong>Coming from Arduino MeshCore</strong> — flash it; auto-formats on first boot (new identity, clean storage).</p>\n<p>Take this with a grain of salt — try the formatters if anything anomalous happens with your node.</p>\n</blockquote>\n<hr />\n<p>A stability-and-hardware release. The headline is a <strong>whole-node wedge fixed</strong> in the GPS power-management\npath that shipped in v1.16.6, plus <strong>RX duty cycle finally working on LR1110 boards</strong> and a new safety\nmargin that stops signal-independent duty-cycle packet drops on every radio. Rounded out by joystick-UI\nfixes and a color-display improvement for the Heltec T114.</p>\n<h2>Highlights</h2>\n<h3>Fixed: a GPS node could wedge completely on standby (regression in v1.16.6)</h3>\n<p>v1.16.6 introduced GPS-UART suspend to save power (an armed nRF UARTE receiver costs ~0.5–1 mA even with\nthe GPS module powered down). It had a race that could <strong>hang the entire node</strong> — no LoRa, no USB, no BLE,\nCLI answering <code>-&gt; busy</code>, recoverable only by reboot.</p>\n<p>The upstream nRF UARTE suspend path spins forever waiting for an <code>RXTO</code> event after issuing <code>STOPRX</code>. In\ninterrupt-driven mode the receiver runs one byte at a time and re-arms from its ISR; if a byte happened to\nfinish in the narrow window where the driver had already disabled the <code>ENDRX</code> interrupt, the receiver was\nleft stopped-and-un-rearmed, <code>STOPRX</code> produced no <code>RXTO</code>, and the wait never returned — with the main mesh\nthread stuck inside it. Any nRF board that powers its GPS module down and suspends the port on standby\ncould hit this.</p>\n<p>Fixed in two independent layers, so neither alone is load-bearing:</p>\n<ul>\n<li><strong><code>ZephyrGPSManager</code> settles ~5 ms before suspending</strong> the GPS UART, letting the last in-flight byte\nfinish and the RX ISR re-arm, so the receiver is armed-and-idle when <code>STOPRX</code> fires — the state that\nreliably yields <code>RXTO</code>. Standby happens at most every few minutes, so the cost is negligible.</li>\n<li><strong>New patch <code>0010</code> bounds the driver's <code>RXTO</code> wait</strong> to 4 ms instead of spinning forever. On timeout it\nfalls through to the unconditional <code>nrf_uarte_disable()</code> that force-stops the receiver anyway — so even\nif a byte still lands in the race window, the node can never hang.</li>\n</ul>\n<h3>RX duty cycle now works on LR1110 boards</h3>\n<p>LR1110 boards (T1000-E, ThinkNode M9, and other LR11xx radios) now get real <strong>RX duty-cycle sniff mode</strong>\ninstead of sitting in continuous receive — a substantial receive-power saving on battery nodes, the same\nmechanism SX126x boards have had. This took a significant rework of the LR11xx LoRa driver to land\ncorrectly.</p>\n<h3>New: duty-cycle safety margin stops signal-independent packet drops</h3>\n<p><code>set</code>-able via <code>CONFIG_ZEPHCORE_LORA_DC_MARGIN_PCT</code> (default <strong>15%</strong>), shared by every radio\n(SX126x / LR11xx / LR20xx).</p>\n<p>The theoretical per-cycle \"deaf-time\" budget that duty cycle is computed from assumes the sleep clock and\nthe wake transition are exact. They are not: the chip's sleep timer runs on an internal RC oscillator\n(RC64k on SX126x, RTC on LR11xx) that drifts several\n…</p>\n"
    },
    {
      "version": "1.16.6-zephcore",
      "name": "Firmware 1.16.6-zephcore",
      "datetime": "2026-07-23T15:33:05Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/1.16.6-zephcore",
      "prerelease": false,
      "notes": "# ZephCore 1.16.6-zephcore\r\n\r\n> [!IMPORTANT]\r\n> ## Before you upgrade\r\n>\r\n> **From v1.16.5** — clean flash, no re-bond, bonds and data survive.\r\n>\r\n> **From v1.16.2 / v1.16.3 / v1.16.4** — clean flash, bonds and data survive. If you are on an\r\n> **ESP32-S3 / ESP32-C board** and have *not* yet taken the v1.16.5 update, you still owe the one-time\r\n> serial reflash described there: the app moved to flash offset `0x10000` in v1.16.5, and a board on the\r\n> old layout cannot cross that update over WiFi-OTA or the browser flasher — **flash the `-merged.bin`\r\n> once over USB/serial.** Affected boards: *Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2,\r\n> XIAO ESP32-S3 / C3 / C6, LilyGo T-Lora C6.* Identity, contacts, channels, prefs, and BLE bonds are\r\n> preserved. nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are\r\n> unaffected.\r\n>\r\n> **From v1.16.1 or older** — flash it; on first boot it clears BLE bonds automatically (identity,\r\n> contacts, channels, prefs preserved). Re-bond your phone/desktop once.\r\n>\r\n> **Coming from Arduino MeshCore** — flash it; auto-formats on first boot (new identity, clean storage).\r\n>\r\n> Take this with a grain of salt — try the formatters if anything anomalous happens with your node.\r\n\r\n---\r\n\r\nA maintenance release: an important **admin-password fix**, a substantial **identity-key randomness\r\nhardening** on ESP32, **GPS standby power savings**, and the removal of **Adaptive Power Control** —\r\nplus two new boards, **muzi works R1 Neo** and an experimental first cut at the **ThinkNode M9** in dev.\r\n\r\n## Highlights\r\n\r\n### 2-tap and 5-tap are switched\r\nAfter a lot of inadvertently sent adverts, the functions of the 2 and 5 clicks were switched, so a double click with the user button now only enables/disables the LEDs and no longer accidentally sends a flood advert.\r\n\r\n### Fixed: uppercase letters in an admin password were silently lowercased\r\n\r\n**If you set or changed an admin password on v1.16.5 and can no longer log in, this is why.** v1.16.5\r\nintroduced case-insensitive CLI keywords by lowercasing the first two words of every command before\r\nmatching them. That is safe for `get cad` — but `password <value>` puts the *value* in the second word,\r\nso `password MyPass` was stored as `mypass`, and the password you typed afterwards never matched.\r\n(`set guest.password <value>` has three words and was never affected.)\r\n\r\nThe normalizer has been removed and CLI commands are case-sensitive again, matching upstream Arduino\r\nMeshCore. **If you are locked out, re-flash and reconfigure, or log in with the all-lowercase form of the\r\npassword you set.**\r\n\r\nThe one place autocapitalization genuinely hurts — typing commands to the V-Contact from a phone keyboard,\r\nwhich capitalizes the first letter of every line — is handled narrowly instead: only character 0 of a\r\nV-Contact chat line is folded. No command takes an argument at position 0, so no value can be touched.\r\n\r\n### Identity keys are now generated with hardened, hardware-verified randomness\r\n\r\nYour node's identity — the Ed25519 keypair that *is* your node on the mesh — is generated once, on first\r\nboot, and kept for life. An investigation into how the randomness behind that key is gathered found that\r\n**ESP32 boards were generating identities with far less entropy than intended.** Two independent problems,\r\nboth fixed in this release:\r\n\r\n- **The ESP32 hardware RNG was running unseeded.** It only produces true random numbers while the radio\r\n  (WiFi/BT) is active — and identity generation runs before the radio comes up (on repeaters, the radio\r\n  never comes up at all). Key generation now feeds the RNG from the chip's ADC noise source for its\r\n  duration, Espressif's documented method for exactly this pre-radio situation. Verified on hardware:\r\n  the RNG's output went from a fixed pattern to statistically ideal.\r\n- **The timing-jitter backup source measured nothing on ESP32.** It sampled the CPU's cycle counter\r\n  against work\n…",
      "notesHtml": "<h1>ZephCore 1.16.6-zephcore</h1>\n<blockquote>\n<p>[!IMPORTANT]</p>\n<h2>Before you upgrade</h2>\n<p><strong>From v1.16.5</strong> — clean flash, no re-bond, bonds and data survive.</p>\n<p><strong>From v1.16.2 / v1.16.3 / v1.16.4</strong> — clean flash, bonds and data survive. If you are on an\n<strong>ESP32-S3 / ESP32-C board</strong> and have <em>not</em> yet taken the v1.16.5 update, you still owe the one-time\nserial reflash described there: the app moved to flash offset <code>0x10000</code> in v1.16.5, and a board on the\nold layout cannot cross that update over WiFi-OTA or the browser flasher — <strong>flash the <code>-merged.bin</code>\nonce over USB/serial.</strong> Affected boards: <em>Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2,\nXIAO ESP32-S3 / C3 / C6, LilyGo T-Lora C6.</em> Identity, contacts, channels, prefs, and BLE bonds are\npreserved. nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are\nunaffected.</p>\n<p><strong>From v1.16.1 or older</strong> — flash it; on first boot it clears BLE bonds automatically (identity,\ncontacts, channels, prefs preserved). Re-bond your phone/desktop once.</p>\n<p><strong>Coming from Arduino MeshCore</strong> — flash it; auto-formats on first boot (new identity, clean storage).</p>\n<p>Take this with a grain of salt — try the formatters if anything anomalous happens with your node.</p>\n</blockquote>\n<hr />\n<p>A maintenance release: an important <strong>admin-password fix</strong>, a substantial <strong>identity-key randomness\nhardening</strong> on ESP32, <strong>GPS standby power savings</strong>, and the removal of <strong>Adaptive Power Control</strong> —\nplus two new boards, <strong>muzi works R1 Neo</strong> and an experimental first cut at the <strong>ThinkNode M9</strong> in dev.</p>\n<h2>Highlights</h2>\n<h3>2-tap and 5-tap are switched</h3>\n<p>After a lot of inadvertently sent adverts, the functions of the 2 and 5 clicks were switched, so a double click with the user button now only enables/disables the LEDs and no longer accidentally sends a flood advert.</p>\n<h3>Fixed: uppercase letters in an admin password were silently lowercased</h3>\n<p><strong>If you set or changed an admin password on v1.16.5 and can no longer log in, this is why.</strong> v1.16.5\nintroduced case-insensitive CLI keywords by lowercasing the first two words of every command before\nmatching them. That is safe for <code>get cad</code> — but <code>password &lt;value&gt;</code> puts the <em>value</em> in the second word,\nso <code>password MyPass</code> was stored as <code>mypass</code>, and the password you typed afterwards never matched.\n(<code>set guest.password &lt;value&gt;</code> has three words and was never affected.)</p>\n<p>The normalizer has been removed and CLI commands are case-sensitive again, matching upstream Arduino\nMeshCore. <strong>If you are locked out, re-flash and reconfigure, or log in with the all-lowercase form of the\npassword you set.</strong></p>\n<p>The one place autocapitalization genuinely hurts — typing commands to the V-Contact from a phone keyboard,\nwhich capitalizes the first letter of every line — is handled narrowly instead: only character 0 of a\nV-Contact chat line is folded. No command takes an argument at position 0, so no value can be touched.</p>\n<h3>Identity keys are now generated with hardened, hardware-verified randomness</h3>\n<p>Your node's identity — the Ed25519 keypair that <em>is</em> your node on the mesh — is generated once, on first\nboot, and kept for life. An investigation into how the randomness behind that key is gathered found that\n<strong>ESP32 boards were generating identities with far less entropy than intended.</strong> Two independent problems,\nboth fixed in this release:</p>\n<ul>\n<li><strong>The ESP32 hardware RNG was running unseeded.</strong> It only produces true random numbers while the radio\n(WiFi/BT) is active — and identity generation runs before the radio comes up (on repeaters, the radio\nnever comes up at all). Key generation now feeds the RNG from the chip's ADC noise source for its\nduration, Espressif's documented method for exactly this pre-radio situation. Verified on hardware:\nthe RNG's output went from a fixed pattern to statistically ideal.</li>\n<li><strong>The timing-jitter backup source measured nothing on ESP32.</strong> It sampled the CPU's cycle counter\nagainst work\n…</li>\n</ul>\n"
    },
    {
      "version": "1.16.5-zephcore",
      "name": "Firmware 1.16.5-zephcore",
      "datetime": "2026-07-16T17:38:21Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/1.16.5-zephcore",
      "prerelease": false,
      "notes": "# ZephCore 1.16.5-zephcore\n\n> [!IMPORTANT]\n> ## Before you upgrade\n>\n> **ESP32-S3 / ESP32-C boards — one-time serial reflash required; WiFi-OTA cannot bridge this update.**\n> This release moves the application to a new flash location (`0x10000`). A board on the old layout can't\n> update to this build over WiFi-OTA or the browser flasher — **flash the `-merged.bin` once over USB/serial.**\n> Affected boards: *Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO ESP32-S3 / C3 / C6, LilyGo T-Lora C6.*\n> Your identity, contacts, channels, prefs, and BLE bonds are preserved. After this reflash, OTA and\n> app-updates work again.\n>\n> **nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are unaffected** — upgrade as usual.\n>\n> **From v1.16.2 / v1.16.3 / v1.16.4** — clean flash, no re-bond, bonds and data survive.\n>\n> **From v1.16.1 or older** — flash it; on first boot it clears BLE bonds automatically (identity, contacts, channels, prefs preserved). Re-bond your phone/desktop once.\n>\n> **Coming from Arduino MeshCore** — flash it; auto-formats on first boot (new identity, clean storage).\n>\n> Take this with a grain of salt — try the formatters if anything anomalous happens with your node.\n\n---\n\nThe headline of this release:\n**Adaptive CAD**, a self-tuning listen-before-talk that calibrates itself to each site;\n**V-Contact**, an in-app admin \"channel\" for companions; and a batch of LoRa-driver reliability fixes.\n\n## Highlights\n\n### Fixed: ESP32 WiFi-OTA silently reverted — upload hit 100%, then booted the *old* firmware\nOn ESP32-S3 / ESP32-C boards, the app and MCUboot bootloader were using different flash partition tables,\nso each OTA update landed at an address the bootloader never checked. Every upload \"succeeded\" and then\nquietly did nothing. Both images now share one partition map, and OTA works correctly. (The XIAO C3/C6 and\nT-Lora C6 used the stock layout and were never affected.)\n\n### ESP32 app slot moved to `0x10000`\nThe ESP32 app slot moved from `0x20000` to `0x10000` — matching the Arduino / ESP-IDF offset. This is the\nchange that fixes OTA above, and it also enables the configurator's fast **app-only update** (rewrites just\nthe app, keeps your settings). Requires the one-time serial reflash (see *Before you upgrade*).\n\n### New: Adaptive CAD — listen-before-talk that tunes itself to your site\nEvery transmission is preceded by a Channel Activity Detection (CAD) check that listens for other LoRa\ntraffic and holds off if the channel is busy. How sensitive that check is has always been a single\nhardcoded number — but the right value depends on where the node lives. A quiet valley node and a\n50-network hilltop need very different thresholds: too sensitive and the node keeps deferring for phantom\ntraffic (wasted airtime, latency); not sensitive enough and it transmits over real packets (collisions).\n\nNodes now **measure their own RF environment and adjust automatically**. In the background, between\ntransmissions, the radio runs short calibration probes, tracks how often each sensitivity level produces a\nfalse \"busy\", and settles on the lowest false-positive threshold for *that* site — responding to a\nchanging environment within an hour or two. It runs on repeaters and companions, and it is **on by\ndefault**; there is nothing to configure.\n\n- `get cad` — watch what it's doing: the current threshold and per-level false-positive statistics.\n- `set cad.auto off` — turn off the auto-adjust to observe or hand-tune with `set cad.offset`.\n\nQuiet sites end up *more* sensitive than the old fixed default (fewer stomped packets); noisy sites back\noff until the false alarms stop. CAD also now uses 4 detection symbols everywhere, which improves detection\nof an in-progress packet's payload (not just its preamble). Full guide, including the honest limitation\nthat the \"missed detection\" side isn't locally observable, is in\n**[ADAPTIVE_CAD.md](https://github.com/liquidraver/ZephCore/blob/master/zephcore/ADAPTIVE\n…",
      "notesHtml": "<h1>ZephCore 1.16.5-zephcore</h1>\n<blockquote>\n<p>[!IMPORTANT]</p>\n<h2>Before you upgrade</h2>\n<p><strong>ESP32-S3 / ESP32-C boards — one-time serial reflash required; WiFi-OTA cannot bridge this update.</strong>\nThis release moves the application to a new flash location (<code>0x10000</code>). A board on the old layout can't\nupdate to this build over WiFi-OTA or the browser flasher — <strong>flash the <code>-merged.bin</code> once over USB/serial.</strong>\nAffected boards: <em>Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO ESP32-S3 / C3 / C6, LilyGo T-Lora C6.</em>\nYour identity, contacts, channels, prefs, and BLE bonds are preserved. After this reflash, OTA and\napp-updates work again.</p>\n<p><strong>nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are unaffected</strong> — upgrade as usual.</p>\n<p><strong>From v1.16.2 / v1.16.3 / v1.16.4</strong> — clean flash, no re-bond, bonds and data survive.</p>\n<p><strong>From v1.16.1 or older</strong> — flash it; on first boot it clears BLE bonds automatically (identity, contacts, channels, prefs preserved). Re-bond your phone/desktop once.</p>\n<p><strong>Coming from Arduino MeshCore</strong> — flash it; auto-formats on first boot (new identity, clean storage).</p>\n<p>Take this with a grain of salt — try the formatters if anything anomalous happens with your node.</p>\n</blockquote>\n<hr />\n<p>The headline of this release:\n<strong>Adaptive CAD</strong>, a self-tuning listen-before-talk that calibrates itself to each site;\n<strong>V-Contact</strong>, an in-app admin \"channel\" for companions; and a batch of LoRa-driver reliability fixes.</p>\n<h2>Highlights</h2>\n<h3>Fixed: ESP32 WiFi-OTA silently reverted — upload hit 100%, then booted the <em>old</em> firmware</h3>\n<p>On ESP32-S3 / ESP32-C boards, the app and MCUboot bootloader were using different flash partition tables,\nso each OTA update landed at an address the bootloader never checked. Every upload \"succeeded\" and then\nquietly did nothing. Both images now share one partition map, and OTA works correctly. (The XIAO C3/C6 and\nT-Lora C6 used the stock layout and were never affected.)</p>\n<h3>ESP32 app slot moved to <code>0x10000</code></h3>\n<p>The ESP32 app slot moved from <code>0x20000</code> to <code>0x10000</code> — matching the Arduino / ESP-IDF offset. This is the\nchange that fixes OTA above, and it also enables the configurator's fast <strong>app-only update</strong> (rewrites just\nthe app, keeps your settings). Requires the one-time serial reflash (see <em>Before you upgrade</em>).</p>\n<h3>New: Adaptive CAD — listen-before-talk that tunes itself to your site</h3>\n<p>Every transmission is preceded by a Channel Activity Detection (CAD) check that listens for other LoRa\ntraffic and holds off if the channel is busy. How sensitive that check is has always been a single\nhardcoded number — but the right value depends on where the node lives. A quiet valley node and a\n50-network hilltop need very different thresholds: too sensitive and the node keeps deferring for phantom\ntraffic (wasted airtime, latency); not sensitive enough and it transmits over real packets (collisions).</p>\n<p>Nodes now <strong>measure their own RF environment and adjust automatically</strong>. In the background, between\ntransmissions, the radio runs short calibration probes, tracks how often each sensitivity level produces a\nfalse \"busy\", and settles on the lowest false-positive threshold for <em>that</em> site — responding to a\nchanging environment within an hour or two. It runs on repeaters and companions, and it is <strong>on by\ndefault</strong>; there is nothing to configure.</p>\n<ul>\n<li><code>get cad</code> — watch what it's doing: the current threshold and per-level false-positive statistics.</li>\n<li><code>set cad.auto off</code> — turn off the auto-adjust to observe or hand-tune with <code>set cad.offset</code>.</li>\n</ul>\n<p>Quiet sites end up <em>more</em> sensitive than the old fixed default (fewer stomped packets); noisy sites back\noff until the false alarms stop. CAD also now uses 4 detection symbols everywhere, which improves detection\nof an in-progress packet's payload (not just its preamble). Full guide, including the honest limitation\nthat the \"missed detection\" side isn't locally observable, is in\n**[ADAPTIVE_CAD.md](<a href=\"https://github.com/liquidraver/ZephCore/blob/master/zephcore/ADAPTIVE\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/liquidraver/ZephCore/blob/master/zephcore/ADAPTIVE</a>\n…</p>\n"
    },
    {
      "version": "v20260714.095706",
      "name": "Firmware v20260714.095706",
      "datetime": "2026-07-14T09:57:21Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260714.095706",
      "prerelease": false,
      "notes": "# ZephCore v1.16.5-zephyr\r\n\r\n(re-release because vContact caused regression in settings import / update)\r\n\r\n> [!IMPORTANT]\r\n> ## Before you upgrade\r\n>\r\n> **ESP32-S3 / ESP32-C boards — one-time serial reflash required; WiFi-OTA cannot bridge this update.**\r\n> This release moves the application to a new flash location (`0x10000`). A board on the old layout can't\r\n> update to this build over WiFi-OTA or the browser flasher — **flash the `-merged.bin` once over USB/serial.**\r\n> Affected boards: *Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO ESP32-S3 / C3 / C6, LilyGo T-Lora C6.*\r\n> Your identity, contacts, channels, prefs, and BLE bonds are preserved. After this reflash, OTA and\r\n> app-updates work again.\r\n>\r\n> **nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are unaffected** — upgrade as usual.\r\n>\r\n> **From v1.16.2 / v1.16.3 / v1.16.4** — clean flash, no re-bond, bonds and data survive.\r\n>\r\n> **From v1.16.1 or older** — flash it; on first boot it clears BLE bonds automatically (identity, contacts, channels, prefs preserved). Re-bond your phone/desktop once.\r\n>\r\n> **Coming from Arduino MeshCore** — flash it; auto-formats on first boot (new identity, clean storage).\r\n>\r\n> Take this with a grain of salt — try the formatters if anything anomalous happens with your node.\r\n\r\n---\r\n\r\nThe headline of this release:\r\n\r\n### New: flash ZephCore from your browser — Mesh America configurator\r\nZephCore is now a listed provider in the **[Mesh America Device Configurator](https://apps.meshamerica.com)**.\r\nPick your board and flash directly — companion (BLE and USB) or repeater — no toolchain, no drag-and-drop.\r\n\r\n**Adaptive CAD**, a self-tuning listen-before-talk that calibrates itself to each site;\r\n\r\n**V-Contact**, an in-app admin \"channel\" for companions; and a batch of LoRa-driver reliability fixes.\r\n\r\n## Highlights\r\n\r\n### Fixed: ESP32 WiFi-OTA silently reverted — upload hit 100%, then booted the *old* firmware\r\nOn ESP32-S3 / ESP32-C boards, the app and MCUboot bootloader were using different flash partition tables,\r\nso each OTA update landed at an address the bootloader never checked. Every upload \"succeeded\" and then\r\nquietly did nothing. Both images now share one partition map, and OTA works correctly. (The XIAO C3/C6 and\r\nT-Lora C6 used the stock layout and were never affected.)\r\n\r\n### ESP32 app slot moved to `0x10000`\r\nThe ESP32 app slot moved from `0x20000` to `0x10000` — matching the Arduino / ESP-IDF offset. This is the\r\nchange that fixes OTA above, and it also enables the configurator's fast **app-only update** (rewrites just\r\nthe app, keeps your settings). Requires the one-time serial reflash (see *Before you upgrade*).\r\n\r\n### New: Adaptive CAD — listen-before-talk that tunes itself to your site\r\nEvery transmission is preceded by a Channel Activity Detection (CAD) check that listens for other LoRa\r\ntraffic and holds off if the channel is busy. How sensitive that check is has always been a single\r\nhardcoded number — but the right value depends on where the node lives. A quiet valley node and a\r\n50-network hilltop need very different thresholds: too sensitive and the node keeps deferring for phantom\r\ntraffic (wasted airtime, latency); not sensitive enough and it transmits over real packets (collisions).\r\n\r\nNodes now **measure their own RF environment and adjust automatically**. In the background, between\r\ntransmissions, the radio runs short calibration probes, tracks how often each sensitivity level produces a\r\nfalse \"busy\", and settles on the lowest false-positive threshold for *that* site — responding to a\r\nchanging environment within an hour or two. It runs on repeaters and companions, and it is **on by\r\ndefault**; there is nothing to configure.\r\n\r\n- `get cad` — watch what it's doing: the current threshold and per-level false-positive statistics.\r\n- `set cad.auto off` — turn off the auto-adjust to observe or hand-tune with `set cad.offset`.\r\n\r\nQuiet sites end up *more* sensitive tha\n…",
      "notesHtml": "<h1>ZephCore v1.16.5-zephyr</h1>\n<p>(re-release because vContact caused regression in settings import / update)</p>\n<blockquote>\n<p>[!IMPORTANT]</p>\n<h2>Before you upgrade</h2>\n<p><strong>ESP32-S3 / ESP32-C boards — one-time serial reflash required; WiFi-OTA cannot bridge this update.</strong>\nThis release moves the application to a new flash location (<code>0x10000</code>). A board on the old layout can't\nupdate to this build over WiFi-OTA or the browser flasher — <strong>flash the <code>-merged.bin</code> once over USB/serial.</strong>\nAffected boards: <em>Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO ESP32-S3 / C3 / C6, LilyGo T-Lora C6.</em>\nYour identity, contacts, channels, prefs, and BLE bonds are preserved. After this reflash, OTA and\napp-updates work again.</p>\n<p><strong>nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are unaffected</strong> — upgrade as usual.</p>\n<p><strong>From v1.16.2 / v1.16.3 / v1.16.4</strong> — clean flash, no re-bond, bonds and data survive.</p>\n<p><strong>From v1.16.1 or older</strong> — flash it; on first boot it clears BLE bonds automatically (identity, contacts, channels, prefs preserved). Re-bond your phone/desktop once.</p>\n<p><strong>Coming from Arduino MeshCore</strong> — flash it; auto-formats on first boot (new identity, clean storage).</p>\n<p>Take this with a grain of salt — try the formatters if anything anomalous happens with your node.</p>\n</blockquote>\n<hr />\n<p>The headline of this release:</p>\n<h3>New: flash ZephCore from your browser — Mesh America configurator</h3>\n<p>ZephCore is now a listed provider in the <strong><a href=\"https://apps.meshamerica.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Mesh America Device Configurator</a></strong>.\nPick your board and flash directly — companion (BLE and USB) or repeater — no toolchain, no drag-and-drop.</p>\n<p><strong>Adaptive CAD</strong>, a self-tuning listen-before-talk that calibrates itself to each site;</p>\n<p><strong>V-Contact</strong>, an in-app admin \"channel\" for companions; and a batch of LoRa-driver reliability fixes.</p>\n<h2>Highlights</h2>\n<h3>Fixed: ESP32 WiFi-OTA silently reverted — upload hit 100%, then booted the <em>old</em> firmware</h3>\n<p>On ESP32-S3 / ESP32-C boards, the app and MCUboot bootloader were using different flash partition tables,\nso each OTA update landed at an address the bootloader never checked. Every upload \"succeeded\" and then\nquietly did nothing. Both images now share one partition map, and OTA works correctly. (The XIAO C3/C6 and\nT-Lora C6 used the stock layout and were never affected.)</p>\n<h3>ESP32 app slot moved to <code>0x10000</code></h3>\n<p>The ESP32 app slot moved from <code>0x20000</code> to <code>0x10000</code> — matching the Arduino / ESP-IDF offset. This is the\nchange that fixes OTA above, and it also enables the configurator's fast <strong>app-only update</strong> (rewrites just\nthe app, keeps your settings). Requires the one-time serial reflash (see <em>Before you upgrade</em>).</p>\n<h3>New: Adaptive CAD — listen-before-talk that tunes itself to your site</h3>\n<p>Every transmission is preceded by a Channel Activity Detection (CAD) check that listens for other LoRa\ntraffic and holds off if the channel is busy. How sensitive that check is has always been a single\nhardcoded number — but the right value depends on where the node lives. A quiet valley node and a\n50-network hilltop need very different thresholds: too sensitive and the node keeps deferring for phantom\ntraffic (wasted airtime, latency); not sensitive enough and it transmits over real packets (collisions).</p>\n<p>Nodes now <strong>measure their own RF environment and adjust automatically</strong>. In the background, between\ntransmissions, the radio runs short calibration probes, tracks how often each sensitivity level produces a\nfalse \"busy\", and settles on the lowest false-positive threshold for <em>that</em> site — responding to a\nchanging environment within an hour or two. It runs on repeaters and companions, and it is <strong>on by\ndefault</strong>; there is nothing to configure.</p>\n<ul>\n<li><code>get cad</code> — watch what it's doing: the current threshold and per-level false-positive statistics.</li>\n<li><code>set cad.auto off</code> — turn off the auto-adjust to observe or hand-tune with <code>set cad.offset</code>.</li>\n</ul>\n<p>Quiet sites end up <em>more</em> sensitive tha\n…</p>\n"
    },
    {
      "version": "v20260704.220543",
      "name": "Firmware v20260704.220543",
      "datetime": "2026-07-04T22:06:03Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260704.220543",
      "prerelease": false,
      "notes": "# ZephCore v1.16.4-zephyr\r\n\r\n> [!IMPORTANT]\r\n> ## Before you upgrade\r\n>\r\n> **Updating from v1.16.2 or v1.16.3** — clean flash, no re-bond required, bonds and data survive.\r\n>\r\n> **Updating from v1.16.1 or older** — flash the firmware; on first boot it detects the old installation and clears the BLE bond storage automatically. Your identity, contacts, channels, and prefs are preserved. You will need to **re-bond** your phone/desktop once.\r\n>\r\n> **Coming from Arduino MeshCore** — flash the firmware; it detects the incompatible filesystem on first boot and formats automatically (new identity, clean storage). No formatter tool required.\r\n>\r\n> Take this with a grain of salt though — try the formatters if anything anomalous happens with your node.\r\n\r\n---\r\n\r\n(re-release because meshtimesync command's reply needed a facelift, UI lost uptime and had some offset quirk, nothing more)\r\n\r\nThe headline of this release is **Mesh Time Sync**: an optional, off-by-default feature that lets a node fix its own clock from the mesh — no GPS, no phone. Alongside it: two new board ports, a color-TFT UI pass, a flash-wear fix on GPS nodes, and a batch of multi-byte-path-hash bug fixes.\r\n\r\n## Highlights\r\n\r\n### New: Mesh Time Sync — self-correcting clocks from the mesh (simulated from EmpireMesh live data)\r\nYour node can now listen to the timestamps inside other nodes' signed adverts and, if its own clock is clearly wrong, gently correct itself. No GPS and no phone required. It works on every role (repeater, room server, observer, companion).\r\n\r\nWhy it matters: a node with a wrong clock shows garbage \"last heard\" times, and after a reboot without a time sync its own adverts can be silently ignored by every node that already knows it. On the live mesh today, almost half of all repeaters are more than an hour off — this fixes that whole class of problem automatically.\r\n\r\n**It is OFF by default. Nothing changes unless you enable it.**\r\n\r\n- `set meshtimesync on` — enable (USB serial CLI or remote admin on repeaters/room servers/observers; on companions toggle the `meshtimesync` custom variable from the app, or use the same command over the USB text CLI).\r\n- `get meshtimesync` — live view, even while the feature is off: how many trustworthy senders it sees, how many agree, and what correction it *would* make. Worth watching this dry-run for a day before (and after) you enable.\r\n\r\nGuardrails, in short: any clock set protects the node for 7 days — a GPS fix and a manual `time`/`clock sync` use the exact same 7-day suppression window, so a GPS-synced node is left alone just like a manually-set one (a live GPS re-arms it on every fix); it steps at most ±1 h at a time and at most once per 6 h; it does nothing without a quorum (default 6) of tenured, agreeing senders; and room servers/companions never step backward.\r\n\r\nFull documentation — how to switch it on, the honest limitations, and the recovery recipe — is in **[MESHTIMESYNC.md](https://github.com/liquidraver/ZephCore/blob/master/MESHTIMESYNC.md)**. Design details (consensus algorithm, security model, why the limits are what they are) are in ARCHITECTURE.md §4.9.\r\n\r\n### New boards\r\n- **Heltec Mesh Node T096** (nRF52840) — SX1262 + KCT8103L PA/FEM, UC6580 GNSS, ST7735S 160×80 color TFT, button, LED, battery ADC.\r\n- **Seeed LoRa-E5 mini** (STM32WLE5JC) — integrated sub-GHz radio (SX1262-class). No BLE/USB: runs as a companion + CLI over USART1 (serial companion transport).\r\n- **TTGO LoRa32** (ESP32 PICO-D4, **SX1276**) — added as the SX127x reference board (loramac-node backend), USB-UART CLI.\r\n- **LilyGo T-Impulse Plus** (nRF52840) — LoRa wristband: SX1262 (S62F module, embedded 32 MHz TCXO, GPIO antenna switch), u-blox MIA-M10Q GNSS, SSD1315 128×64 OLED, TTP223 touch button, haptic vibration motor, ZD25WQ32C 4 MB QSPI flash, battery ADC. Companion + repeater. Ported from LilyGo's official sources and builds clean; on-device bring-up pending (see the board README for the hardware-verify checklist).\r\n- **Helte\n…",
      "notesHtml": "<h1>ZephCore v1.16.4-zephyr</h1>\n<blockquote>\n<p>[!IMPORTANT]</p>\n<h2>Before you upgrade</h2>\n<p><strong>Updating from v1.16.2 or v1.16.3</strong> — clean flash, no re-bond required, bonds and data survive.</p>\n<p><strong>Updating from v1.16.1 or older</strong> — flash the firmware; on first boot it detects the old installation and clears the BLE bond storage automatically. Your identity, contacts, channels, and prefs are preserved. You will need to <strong>re-bond</strong> your phone/desktop once.</p>\n<p><strong>Coming from Arduino MeshCore</strong> — flash the firmware; it detects the incompatible filesystem on first boot and formats automatically (new identity, clean storage). No formatter tool required.</p>\n<p>Take this with a grain of salt though — try the formatters if anything anomalous happens with your node.</p>\n</blockquote>\n<hr />\n<p>(re-release because meshtimesync command's reply needed a facelift, UI lost uptime and had some offset quirk, nothing more)</p>\n<p>The headline of this release is <strong>Mesh Time Sync</strong>: an optional, off-by-default feature that lets a node fix its own clock from the mesh — no GPS, no phone. Alongside it: two new board ports, a color-TFT UI pass, a flash-wear fix on GPS nodes, and a batch of multi-byte-path-hash bug fixes.</p>\n<h2>Highlights</h2>\n<h3>New: Mesh Time Sync — self-correcting clocks from the mesh (simulated from EmpireMesh live data)</h3>\n<p>Your node can now listen to the timestamps inside other nodes' signed adverts and, if its own clock is clearly wrong, gently correct itself. No GPS and no phone required. It works on every role (repeater, room server, observer, companion).</p>\n<p>Why it matters: a node with a wrong clock shows garbage \"last heard\" times, and after a reboot without a time sync its own adverts can be silently ignored by every node that already knows it. On the live mesh today, almost half of all repeaters are more than an hour off — this fixes that whole class of problem automatically.</p>\n<p><strong>It is OFF by default. Nothing changes unless you enable it.</strong></p>\n<ul>\n<li><code>set meshtimesync on</code> — enable (USB serial CLI or remote admin on repeaters/room servers/observers; on companions toggle the <code>meshtimesync</code> custom variable from the app, or use the same command over the USB text CLI).</li>\n<li><code>get meshtimesync</code> — live view, even while the feature is off: how many trustworthy senders it sees, how many agree, and what correction it <em>would</em> make. Worth watching this dry-run for a day before (and after) you enable.</li>\n</ul>\n<p>Guardrails, in short: any clock set protects the node for 7 days — a GPS fix and a manual <code>time</code>/<code>clock sync</code> use the exact same 7-day suppression window, so a GPS-synced node is left alone just like a manually-set one (a live GPS re-arms it on every fix); it steps at most ±1 h at a time and at most once per 6 h; it does nothing without a quorum (default 6) of tenured, agreeing senders; and room servers/companions never step backward.</p>\n<p>Full documentation — how to switch it on, the honest limitations, and the recovery recipe — is in <strong><a href=\"https://github.com/liquidraver/ZephCore/blob/master/MESHTIMESYNC.md\" target=\"_blank\" rel=\"noopener noreferrer\">MESHTIMESYNC.md</a></strong>. Design details (consensus algorithm, security model, why the limits are what they are) are in ARCHITECTURE.md §4.9.</p>\n<h3>New boards</h3>\n<ul>\n<li><strong>Heltec Mesh Node T096</strong> (nRF52840) — SX1262 + KCT8103L PA/FEM, UC6580 GNSS, ST7735S 160×80 color TFT, button, LED, battery ADC.</li>\n<li><strong>Seeed LoRa-E5 mini</strong> (STM32WLE5JC) — integrated sub-GHz radio (SX1262-class). No BLE/USB: runs as a companion + CLI over USART1 (serial companion transport).</li>\n<li><strong>TTGO LoRa32</strong> (ESP32 PICO-D4, <strong>SX1276</strong>) — added as the SX127x reference board (loramac-node backend), USB-UART CLI.</li>\n<li><strong>LilyGo T-Impulse Plus</strong> (nRF52840) — LoRa wristband: SX1262 (S62F module, embedded 32 MHz TCXO, GPIO antenna switch), u-blox MIA-M10Q GNSS, SSD1315 128×64 OLED, TTP223 touch button, haptic vibration motor, ZD25WQ32C 4 MB QSPI flash, battery ADC. Companion + repeater. Ported from LilyGo's official sources and builds clean; on-device bring-up pending (see the board README for the hardware-verify checklist).</li>\n<li>**Helte\n…</li>\n</ul>\n"
    },
    {
      "version": "v20260619.094253",
      "name": "Firmware v20260619.094253",
      "datetime": "2026-06-19T09:43:10Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260619.094253",
      "prerelease": false,
      "notes": "# ZephCore v1.16.3-zephyr\r\n\r\n> (re-re-re-release(last time I swear) with the \"smearing fix on eink after the full update\" and \"automount error on a freshly formatted node\")\r\n\r\n> [!IMPORTANT]\r\n> ## Before you upgrade\r\n>\r\n> **Updating from v1.16.2** — clean flash, no re-bond required, bonds survive.\r\n>\r\n> **Updating from v1.16.1 or older** — flash the firmware; the firmware detects the old installation on first boot and clears the BLE bond storage automatically. Your identity, contacts, channels, and prefs are preserved. You will need to **re-bond** your phone/desktop once (old pairings were stored in a different location that v1.16.2 moved away from). No formatter tool required.\r\n>\r\n> **Coming from Arduino MeshCore** — flash the firmware; it detects the incompatible filesystem on first boot and formats automatically. Everything starts fresh (new identity, clean storage). No formatter tool required.\r\n>\r\n> Take this with a grain of salt tho… try the formatters if anything anomalous happens with your node.\r\n\r\n---\r\n\r\nThis is a GPS-focused release: it stops a battery-killing bug on GPS trackers, puts the GPS duty cycle under your control, and follows up the v1.16.2 ESP32 pairing work with a fix for recent Android phones.\r\n\r\n## Highlights\r\n\r\n### Fixed: ThinkNode M1 draining its battery in a day\r\nOn the ThinkNode M1, the GPS module was never actually powered down between fixes — the reset line was mislabeled in the board definition and held permanently released, so the receiver kept drawing current even when the firmware thought it was asleep. A full battery could disappear in roughly a day with GPS enabled.\r\n\r\nThe M1 now asserts the GPS hardware reset on standby, so the GPS genuinely stops between fixes. If you run an M1 with GPS on, this is the fix you want.\r\n\r\n### New: configurable GPS duty cycle — `set gps duty <seconds>`\r\nYou can now decide how your node handles GPS, instead of it being baked in:\r\n\r\n- **`set gps duty <seconds>`** — how long the GPS sleeps between fixes. Persists to flash and applies immediately.\r\n- **`set gps duty 0`** — **always-on**: the GPS never sleeps. It streams fresh fixes (so telemetry is always current) and stays on long enough to download a full almanac.\r\n- **`set gps duty default`** — reset to the role default.\r\n- **`get gps duty`** — show the now-effective interval.\r\n\r\nThis unifies repeaters and companions behind one knob. **Repeaters default to 48 h** (GPS wakes only for periodic time-sync) and **companions to 5 minutes** — both now adjustable. Existing repeaters are migrated automatically; nothing to do.\r\n\r\n> Bad input is rejected with a usage hint, and the value is clamped to a sane range (10 s … 1 week, or 0 for always-on).\r\n\r\n### Fixed: GPS no longer pins itself on waiting for a first fix\r\nPreviously, after enabling GPS a companion would keep the receiver powered **indefinitely** until it got its first fix — so a node left somewhere without sky view (indoors, in a bag) could quietly drain its battery forever. The first cold-start acquisition is now bounded (5 minutes by default, enough for a genuine cold start), after which the node drops into its normal duty cycle whether or not it got a fix.\r\n\r\n### Fixed: ESP32 Secure Connections pairing (Pixel 7 / recent Android)\r\nFollowing the v1.16.2 ESP32 pairing fix, SC-capable centrals — notably recent Android phones like the Pixel 7 — still failed to bond: the controller's encryption table was left uninitialized because the relevant code was guarded on an ESP-IDF symbol that never exists in a Zephyr build, so the link dropped with a MIC failure at encryption start. The guard is corrected (carried as a managed `hal_espressif` patch), and SC pairing now completes. Legacy-pairing devices were unaffected and continue to work. Thanks @seagull9000.\r\n\r\n## Other fixes and improvements\r\n\r\n- **Fixed: GPS always-on on a freshly flashed companion** — a new install would default `gps_interval` to 0 (always-on) instead of the intended 5-minute duty cycle, causing the \n…",
      "notesHtml": "<h1>ZephCore v1.16.3-zephyr</h1>\n<blockquote>\n<p>(re-re-re-release(last time I swear) with the \"smearing fix on eink after the full update\" and \"automount error on a freshly formatted node\")</p>\n</blockquote>\n<blockquote>\n<p>[!IMPORTANT]</p>\n<h2>Before you upgrade</h2>\n<p><strong>Updating from v1.16.2</strong> — clean flash, no re-bond required, bonds survive.</p>\n<p><strong>Updating from v1.16.1 or older</strong> — flash the firmware; the firmware detects the old installation on first boot and clears the BLE bond storage automatically. Your identity, contacts, channels, and prefs are preserved. You will need to <strong>re-bond</strong> your phone/desktop once (old pairings were stored in a different location that v1.16.2 moved away from). No formatter tool required.</p>\n<p><strong>Coming from Arduino MeshCore</strong> — flash the firmware; it detects the incompatible filesystem on first boot and formats automatically. Everything starts fresh (new identity, clean storage). No formatter tool required.</p>\n<p>Take this with a grain of salt tho… try the formatters if anything anomalous happens with your node.</p>\n</blockquote>\n<hr />\n<p>This is a GPS-focused release: it stops a battery-killing bug on GPS trackers, puts the GPS duty cycle under your control, and follows up the v1.16.2 ESP32 pairing work with a fix for recent Android phones.</p>\n<h2>Highlights</h2>\n<h3>Fixed: ThinkNode M1 draining its battery in a day</h3>\n<p>On the ThinkNode M1, the GPS module was never actually powered down between fixes — the reset line was mislabeled in the board definition and held permanently released, so the receiver kept drawing current even when the firmware thought it was asleep. A full battery could disappear in roughly a day with GPS enabled.</p>\n<p>The M1 now asserts the GPS hardware reset on standby, so the GPS genuinely stops between fixes. If you run an M1 with GPS on, this is the fix you want.</p>\n<h3>New: configurable GPS duty cycle — <code>set gps duty &lt;seconds&gt;</code></h3>\n<p>You can now decide how your node handles GPS, instead of it being baked in:</p>\n<ul>\n<li><strong><code>set gps duty &lt;seconds&gt;</code></strong> — how long the GPS sleeps between fixes. Persists to flash and applies immediately.</li>\n<li><strong><code>set gps duty 0</code></strong> — <strong>always-on</strong>: the GPS never sleeps. It streams fresh fixes (so telemetry is always current) and stays on long enough to download a full almanac.</li>\n<li><strong><code>set gps duty default</code></strong> — reset to the role default.</li>\n<li><strong><code>get gps duty</code></strong> — show the now-effective interval.</li>\n</ul>\n<p>This unifies repeaters and companions behind one knob. <strong>Repeaters default to 48 h</strong> (GPS wakes only for periodic time-sync) and <strong>companions to 5 minutes</strong> — both now adjustable. Existing repeaters are migrated automatically; nothing to do.</p>\n<blockquote>\n<p>Bad input is rejected with a usage hint, and the value is clamped to a sane range (10 s … 1 week, or 0 for always-on).</p>\n</blockquote>\n<h3>Fixed: GPS no longer pins itself on waiting for a first fix</h3>\n<p>Previously, after enabling GPS a companion would keep the receiver powered <strong>indefinitely</strong> until it got its first fix — so a node left somewhere without sky view (indoors, in a bag) could quietly drain its battery forever. The first cold-start acquisition is now bounded (5 minutes by default, enough for a genuine cold start), after which the node drops into its normal duty cycle whether or not it got a fix.</p>\n<h3>Fixed: ESP32 Secure Connections pairing (Pixel 7 / recent Android)</h3>\n<p>Following the v1.16.2 ESP32 pairing fix, SC-capable centrals — notably recent Android phones like the Pixel 7 — still failed to bond: the controller's encryption table was left uninitialized because the relevant code was guarded on an ESP-IDF symbol that never exists in a Zephyr build, so the link dropped with a MIC failure at encryption start. The guard is corrected (carried as a managed <code>hal_espressif</code> patch), and SC pairing now completes. Legacy-pairing devices were unaffected and continue to work. Thanks @seagull9000.</p>\n<h2>Other fixes and improvements</h2>\n<ul>\n<li><strong>Fixed: GPS always-on on a freshly flashed companion</strong> — a new install would default <code>gps_interval</code> to 0 (always-on) instead of the intended 5-minute duty cycle, causing the \n…</li>\n</ul>\n"
    },
    {
      "version": "v20260616.202853",
      "name": "Firmware v20260616.202853",
      "datetime": "2026-06-16T20:29:09Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260616.202853",
      "prerelease": false,
      "notes": "# ZephCore v1.16.2-zephyr\r\n\r\n> [!IMPORTANT]\r\n> ## Before you upgrade — please read\r\n>\r\n> This release changes where devices store their BLE pairing data. The change affects **all platforms**, but the upgrade steps differ:\r\n>\r\n> **All boards (nRF52 *and* ESP32): you must re-bond.** Existing phone/desktop pairings will not carry over. After flashing, remove the old pairing from your device's Bluetooth settings (and from the ZephCore app), then pair again from scratch.\r\n>\r\n> nRF **should** update cleanly, but I recommend doing a flash erase + flash fw + import backup to be absolutely sure. Formatters can be found in the repo's [formatter folder](https://github.com/liquidraver/ZephCore/tree/master/formatter). Check your softdevice version before using one.\r\n\r\n---\r\n\r\n## Highlights\r\n\r\n### Critical fix: ESP32 Bluetooth pairing restored\r\nOn the updated Zephyr 4.4.1 base, ESP32 boards could advertise but **never completed pairing** — the Espressif Bluetooth controller would assert the moment a phone began to pair. This release restores the controller's link-layer encryption so pairing works again. If you have an ESP32-based node (Heltec, XIAO, Station G2, T-Beam, etc.), this is the fix you've been waiting for.\r\n\r\nAdditionally, **Android \"connect from app\" now works on ESP32.** Advertising now always exposes the stable identity address, so the Android companion app's in-app connect flow succeeds while iOS pairing continues to work.\r\n\r\n### BLE pairing data moved to its own storage area\r\nBluetooth bonds previously shared the same filesystem as your identity, preferences, and contacts. They now live in a dedicated, isolated storage partition (NVS) on **both nRF52 and ESP32**. This means a busy pairing store can no longer corrupt your contacts or settings, and pairing writes never touch your user data.\r\n\r\nA factory reset (`erase`) now also clears stored BLE bonds, then reboots automatically so everything comes back up clean — previously bonds could survive an erase.\r\n\r\n### New board: Heltec Wireless Tracker V1.1 (ESP32-S3 + SX1262), thanks to @seagull9000\r\nFull support for the Heltec Wireless Tracker V1.1 companion (ESP32-S3 + SX1262 radio + ST7735R TFT + UC6580 GPS), including:\r\n- Correct battery ADC routing (avoids a wiring conflict in the upstream board definition that otherwise broke all LoRa SPI reads)\r\n- Battery voltage/percentage readings\r\n- Onboard GNSS\r\n- Status-LED heartbeat\r\n- OLED/TFT display and BLE companion transport\r\n\r\n\r\n### New board: LilyGo T-Beam v1.2 (SX1262)\r\nFull support for the classic-ESP32 LilyGo T-Beam v1.2 with the SX1262 radio, including:\r\n- AXP2101 power management (LoRa + GPS rails brought up automatically at boot)\r\n- **Battery fuel-gauge readings** over I2C — accurate battery voltage and percentage, even though this board has no battery ADC\r\n- Onboard GNSS (NEO-6M / NEO-M8N)\r\n- The user button wired up (short press = page/select, long press = action)\r\n- OLED display, BLE companion transport, and CLI-based repeater mode\r\n\r\n> Note: classic-ESP32 repeaters (e.g. T-Beam) run CLI-only and do not include WiFi-AP OTA — the WiFi driver buffers plus OTA heap don't fit the classic ESP32's RAM. ESP32-S3 / C-series repeaters keep WiFi OTA.\r\n\r\n### E-paper anti-ghosting\r\nE-paper displays now perform a periodic full refresh (every 8 partial updates by default) to clear the faint \"ghost\" images that build up where text changes most often, like the status bar. The interval is configurable build-time, and the feature is automatically ignored on OLED/TFT screens that don't ghost.\r\n\r\n### More reliable companion connection (Linux / native transport)\r\nThe native TCP transport now applies the same congestion control already used over Bluetooth. Instead of silently dropping data when the connection is briefly saturated (for example, an incoming message arriving during a large contact/channel import), it now back-pressures and retries, and important protocol responses are never dropped. A stalled peer that stops reading is cleanly dis\n…",
      "notesHtml": "<h1>ZephCore v1.16.2-zephyr</h1>\n<blockquote>\n<p>[!IMPORTANT]</p>\n<h2>Before you upgrade — please read</h2>\n<p>This release changes where devices store their BLE pairing data. The change affects <strong>all platforms</strong>, but the upgrade steps differ:</p>\n<p><strong>All boards (nRF52 <em>and</em> ESP32): you must re-bond.</strong> Existing phone/desktop pairings will not carry over. After flashing, remove the old pairing from your device's Bluetooth settings (and from the ZephCore app), then pair again from scratch.</p>\n<p>nRF <strong>should</strong> update cleanly, but I recommend doing a flash erase + flash fw + import backup to be absolutely sure. Formatters can be found in the repo's <a href=\"https://github.com/liquidraver/ZephCore/tree/master/formatter\" target=\"_blank\" rel=\"noopener noreferrer\">formatter folder</a>. Check your softdevice version before using one.</p>\n</blockquote>\n<hr />\n<h2>Highlights</h2>\n<h3>Critical fix: ESP32 Bluetooth pairing restored</h3>\n<p>On the updated Zephyr 4.4.1 base, ESP32 boards could advertise but <strong>never completed pairing</strong> — the Espressif Bluetooth controller would assert the moment a phone began to pair. This release restores the controller's link-layer encryption so pairing works again. If you have an ESP32-based node (Heltec, XIAO, Station G2, T-Beam, etc.), this is the fix you've been waiting for.</p>\n<p>Additionally, <strong>Android \"connect from app\" now works on ESP32.</strong> Advertising now always exposes the stable identity address, so the Android companion app's in-app connect flow succeeds while iOS pairing continues to work.</p>\n<h3>BLE pairing data moved to its own storage area</h3>\n<p>Bluetooth bonds previously shared the same filesystem as your identity, preferences, and contacts. They now live in a dedicated, isolated storage partition (NVS) on <strong>both nRF52 and ESP32</strong>. This means a busy pairing store can no longer corrupt your contacts or settings, and pairing writes never touch your user data.</p>\n<p>A factory reset (<code>erase</code>) now also clears stored BLE bonds, then reboots automatically so everything comes back up clean — previously bonds could survive an erase.</p>\n<h3>New board: Heltec Wireless Tracker V1.1 (ESP32-S3 + SX1262), thanks to @seagull9000</h3>\n<p>Full support for the Heltec Wireless Tracker V1.1 companion (ESP32-S3 + SX1262 radio + ST7735R TFT + UC6580 GPS), including:</p>\n<ul>\n<li>Correct battery ADC routing (avoids a wiring conflict in the upstream board definition that otherwise broke all LoRa SPI reads)</li>\n<li>Battery voltage/percentage readings</li>\n<li>Onboard GNSS</li>\n<li>Status-LED heartbeat</li>\n<li>OLED/TFT display and BLE companion transport</li>\n</ul>\n<h3>New board: LilyGo T-Beam v1.2 (SX1262)</h3>\n<p>Full support for the classic-ESP32 LilyGo T-Beam v1.2 with the SX1262 radio, including:</p>\n<ul>\n<li>AXP2101 power management (LoRa + GPS rails brought up automatically at boot)</li>\n<li><strong>Battery fuel-gauge readings</strong> over I2C — accurate battery voltage and percentage, even though this board has no battery ADC</li>\n<li>Onboard GNSS (NEO-6M / NEO-M8N)</li>\n<li>The user button wired up (short press = page/select, long press = action)</li>\n<li>OLED display, BLE companion transport, and CLI-based repeater mode</li>\n</ul>\n<blockquote>\n<p>Note: classic-ESP32 repeaters (e.g. T-Beam) run CLI-only and do not include WiFi-AP OTA — the WiFi driver buffers plus OTA heap don't fit the classic ESP32's RAM. ESP32-S3 / C-series repeaters keep WiFi OTA.</p>\n</blockquote>\n<h3>E-paper anti-ghosting</h3>\n<p>E-paper displays now perform a periodic full refresh (every 8 partial updates by default) to clear the faint \"ghost\" images that build up where text changes most often, like the status bar. The interval is configurable build-time, and the feature is automatically ignored on OLED/TFT screens that don't ghost.</p>\n<h3>More reliable companion connection (Linux / native transport)</h3>\n<p>The native TCP transport now applies the same congestion control already used over Bluetooth. Instead of silently dropping data when the connection is briefly saturated (for example, an incoming message arriving during a large contact/channel import), it now back-pressures and retries, and important protocol responses are never dropped. A stalled peer that stops reading is cleanly dis\n…</p>\n"
    },
    {
      "version": "v20260613.205222",
      "name": "Firmware v20260613.205222",
      "datetime": "2026-06-13T20:52:38Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260613.205222",
      "prerelease": false,
      "notes": "DO NOT DOWNLOAD THIS RELEASE JUST TO UPDATE, YOU NEED A FLASH ERASE FOR IT TO WORK PROPERLY.\r\nI'LL RE-RELEASE IT SOON AS I CATCH THE BUG!\r\n(it seems devices with multiple bondings are affected only)\r\n\r\nSo if something is broken: delete bond, erase flash and try again.\r\n\r\n\r\n\r\n\r\n\r\n## What's New\r\n\r\n- **RX duty cycle (power saving) reworked** — now properly tested against the datasheet, gives ~40-55% radio-off time with no packet loss on up-to-date nodes. Toggle with `set rxduty on/off`. (Mixed old/new meshes: keep it off on infra nodes for now. Needs preamble 32 on adjacent nodes)\r\n- **Joystick UI now shows accented characters** (Hungarian/Slovak/Czech/Polish/etc. names display correctly), emojis are stripped instead of showing garbage. Unlock gesture changed to Left -> OK -> Right (easier/faster than the old Back-OK-Back).\r\n- `set rxduty` / `set radio.rxgain` now also accept `on` / `off`, not just `1`/`0`.\r\n\r\n## Fixes\r\n\r\n- WisMesh Tag battery percentage was reading high — calibration corrected.\r\n- RTC clock could get permanently stuck blank after battery loss — now recovers and re-syncs.\r\n- Clock source indicator (GPS/App/Local) and display now update correctly/immediately.\r\n- Auto-shutdown no longer triggers on a brief voltage dip during transmit (needs 3 low readings in a row now).\r\n- Fixed a race in MQTT \"online\" status reporting on repeaters/observers.\r\n- Several small crash/overflow hardening fixes from ongoing code audit (no user-visible behavior change).",
      "notesHtml": "<p>DO NOT DOWNLOAD THIS RELEASE JUST TO UPDATE, YOU NEED A FLASH ERASE FOR IT TO WORK PROPERLY.\nI'LL RE-RELEASE IT SOON AS I CATCH THE BUG!\n(it seems devices with multiple bondings are affected only)</p>\n<p>So if something is broken: delete bond, erase flash and try again.</p>\n<h2>What's New</h2>\n<ul>\n<li><strong>RX duty cycle (power saving) reworked</strong> — now properly tested against the datasheet, gives ~40-55% radio-off time with no packet loss on up-to-date nodes. Toggle with <code>set rxduty on/off</code>. (Mixed old/new meshes: keep it off on infra nodes for now. Needs preamble 32 on adjacent nodes)</li>\n<li><strong>Joystick UI now shows accented characters</strong> (Hungarian/Slovak/Czech/Polish/etc. names display correctly), emojis are stripped instead of showing garbage. Unlock gesture changed to Left -&gt; OK -&gt; Right (easier/faster than the old Back-OK-Back).</li>\n<li><code>set rxduty</code> / <code>set radio.rxgain</code> now also accept <code>on</code> / <code>off</code>, not just <code>1</code>/<code>0</code>.</li>\n</ul>\n<h2>Fixes</h2>\n<ul>\n<li>WisMesh Tag battery percentage was reading high — calibration corrected.</li>\n<li>RTC clock could get permanently stuck blank after battery loss — now recovers and re-syncs.</li>\n<li>Clock source indicator (GPS/App/Local) and display now update correctly/immediately.</li>\n<li>Auto-shutdown no longer triggers on a brief voltage dip during transmit (needs 3 low readings in a row now).</li>\n<li>Fixed a race in MQTT \"online\" status reporting on repeaters/observers.</li>\n<li>Several small crash/overflow hardening fixes from ongoing code audit (no user-visible behavior change).</li>\n</ul>\n"
    },
    {
      "version": "v20260608.105434",
      "name": "Firmware v20260608.105434",
      "datetime": "2026-06-08T10:54:48Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260608.105434",
      "prerelease": false,
      "notes": "## New\r\n\r\n### Hardware RTC auto-discovery\r\nOut-of-the-box nodes with a battery/cap-backed I2C RTC (**DS3231, PCF8563,\r\nRV3028, RX8130CE**) now restore the clock at boot — shown tagged **`L`** until\r\nthe next GPS / app / CLI sync — and write the time back on every sync so it\r\nsurvives power-off. Boards opt in via `boards/common/rtc-i2c.dtsi`; on boards\r\nwithout it, this compiles to nothing.\r\n\r\n> First wired up on **LilyGo T-Echo** and **ThinkNode M1**.\r\n\r\n### Buttonless BLE DFU (legacy Nordic/Adafruit OTA jump)\r\nA paired phone can now trigger the jump into the bootloader's BLE OTA mode\r\ndirectly from the DFU Firmware Upgrade app — no button press needed (**nRF52 only**). The\r\nservice exposes the full legacy DFU characteristic set (Control Point, Packet,\r\nRevision) so iOS's DFU library can discover and identify the device correctly.\r\n\r\nCompanion needs disconnecting from MeshCore app first!\r\n\r\nThis was only tested with a handful of nodes and an iOS! This change can introduce errors in other phones I'm not aware of. A BLE bond delete + re-pair should solve it.\r\n\r\n### BLE TX power bumped to +8 dBm\r\nRaised from **+4 dBm → +8 dBm**, the max output the nRF52840 / nRF54L\r\nhigh-power radio front-end supports — better range and link margin for the\r\ncompanion connection.\r\nThis is still under test, but should not cause excessive/noticeable battery drain.\r\n\r\n### MIT license formalized\r\nAdded `zephcore/LICENSE` (MIT, same as upstream MeshCore) and flipped the\r\n`SPDX-License-Identifier` header across the source tree from `Apache-2.0` to\r\n`MIT`. Vendored dependencies that carry their own compatible licenses\r\n(Monocypher, Zephyr patches) are noted at the bottom of the license file.\r\n\r\n## Fixed\r\n\r\n- **Companion startup races** — initial-advert handling and contact-table\r\n  iteration were running on `sysworkq` while the main thread mutated the same\r\n  mesh/contact state, a race that could corrupt state or wedge the contact\r\n  dump. Both now run on the main thread, driven through the event loop\r\n  (`MESH_EVENT_CONTACT_ITER`), same as the rest of the mesh state machine.\r\n\r\n- **Hardware-RTC writes no longer race the main loop** — `zephcore_rtc_save()`\r\n  is now routed through the same deferred-to-main-thread mechanism as prefs\r\n  flushes (`MESH_EVENT_RTC_SAVE`). GPS fixes land on the GNSS modem-chat thread\r\n  and just stash the latest epoch; the actual blocking I2C write happens on the\r\n  main thread, coalescing multiple pending fixes into one save.\r\n\r\n- **`MAX_ANON_CONTACTS` cap fixed** — transient/anon contact slots (e.g.\r\n  created for BLE direct-message lookups to non-contact pubkeys) were being\r\n  capped by `MAX_CONTACTS` instead of their own larger allotment, causing\r\n  premature slot exhaustion/eviction.\r\n\r\n- **XIAO nRF54L15 build fixed.**\r\n\r\n## Internals\r\n\r\n- **ZephyrCompanionUSB** — assembled V3 frames now wake the main thread via\r\n  `k_event` instead of `k_work_submit` on `sysworkq`, matching the BLE RX path\r\n  and removing a parse-vs-`loop()` race.\r\n\r\n- **Buttonless DFU** now hard-depends on `SOC_SERIES_NRF52` (was a soft\r\n  `default y`). Other nRF52-only paths (bootloader-version scan, VBUS detect,\r\n  formatter/updater tools) were cleaned up to drop the redundant\r\n  `SOC_SERIES_NRF52X` checks.\r\n\r\n- **West Update** — Zephyr updated to 4.4.1-rc1",
      "notesHtml": "<h2>New</h2>\n<h3>Hardware RTC auto-discovery</h3>\n<p>Out-of-the-box nodes with a battery/cap-backed I2C RTC (<strong>DS3231, PCF8563,\nRV3028, RX8130CE</strong>) now restore the clock at boot — shown tagged <strong><code>L</code></strong> until\nthe next GPS / app / CLI sync — and write the time back on every sync so it\nsurvives power-off. Boards opt in via <code>boards/common/rtc-i2c.dtsi</code>; on boards\nwithout it, this compiles to nothing.</p>\n<blockquote>\n<p>First wired up on <strong>LilyGo T-Echo</strong> and <strong>ThinkNode M1</strong>.</p>\n</blockquote>\n<h3>Buttonless BLE DFU (legacy Nordic/Adafruit OTA jump)</h3>\n<p>A paired phone can now trigger the jump into the bootloader's BLE OTA mode\ndirectly from the DFU Firmware Upgrade app — no button press needed (<strong>nRF52 only</strong>). The\nservice exposes the full legacy DFU characteristic set (Control Point, Packet,\nRevision) so iOS's DFU library can discover and identify the device correctly.</p>\n<p>Companion needs disconnecting from MeshCore app first!</p>\n<p>This was only tested with a handful of nodes and an iOS! This change can introduce errors in other phones I'm not aware of. A BLE bond delete + re-pair should solve it.</p>\n<h3>BLE TX power bumped to +8 dBm</h3>\n<p>Raised from <strong>+4 dBm → +8 dBm</strong>, the max output the nRF52840 / nRF54L\nhigh-power radio front-end supports — better range and link margin for the\ncompanion connection.\nThis is still under test, but should not cause excessive/noticeable battery drain.</p>\n<h3>MIT license formalized</h3>\n<p>Added <code>zephcore/LICENSE</code> (MIT, same as upstream MeshCore) and flipped the\n<code>SPDX-License-Identifier</code> header across the source tree from <code>Apache-2.0</code> to\n<code>MIT</code>. Vendored dependencies that carry their own compatible licenses\n(Monocypher, Zephyr patches) are noted at the bottom of the license file.</p>\n<h2>Fixed</h2>\n<ul>\n<li><p><strong>Companion startup races</strong> — initial-advert handling and contact-table\niteration were running on <code>sysworkq</code> while the main thread mutated the same\nmesh/contact state, a race that could corrupt state or wedge the contact\ndump. Both now run on the main thread, driven through the event loop\n(<code>MESH_EVENT_CONTACT_ITER</code>), same as the rest of the mesh state machine.</p>\n</li>\n<li><p><strong>Hardware-RTC writes no longer race the main loop</strong> — <code>zephcore_rtc_save()</code>\nis now routed through the same deferred-to-main-thread mechanism as prefs\nflushes (<code>MESH_EVENT_RTC_SAVE</code>). GPS fixes land on the GNSS modem-chat thread\nand just stash the latest epoch; the actual blocking I2C write happens on the\nmain thread, coalescing multiple pending fixes into one save.</p>\n</li>\n<li><p><strong><code>MAX_ANON_CONTACTS</code> cap fixed</strong> — transient/anon contact slots (e.g.\ncreated for BLE direct-message lookups to non-contact pubkeys) were being\ncapped by <code>MAX_CONTACTS</code> instead of their own larger allotment, causing\npremature slot exhaustion/eviction.</p>\n</li>\n<li><p><strong>XIAO nRF54L15 build fixed.</strong></p>\n</li>\n</ul>\n<h2>Internals</h2>\n<ul>\n<li><p><strong>ZephyrCompanionUSB</strong> — assembled V3 frames now wake the main thread via\n<code>k_event</code> instead of <code>k_work_submit</code> on <code>sysworkq</code>, matching the BLE RX path\nand removing a parse-vs-<code>loop()</code> race.</p>\n</li>\n<li><p><strong>Buttonless DFU</strong> now hard-depends on <code>SOC_SERIES_NRF52</code> (was a soft\n<code>default y</code>). Other nRF52-only paths (bootloader-version scan, VBUS detect,\nformatter/updater tools) were cleaned up to drop the redundant\n<code>SOC_SERIES_NRF52X</code> checks.</p>\n</li>\n<li><p><strong>West Update</strong> — Zephyr updated to 4.4.1-rc1</p>\n</li>\n</ul>\n"
    },
    {
      "version": "v20260606.150211",
      "name": "Firmware v20260606.150211",
      "datetime": "2026-06-06T15:02:26Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260606.150211",
      "prerelease": false,
      "notes": "Synced with upstream vanilla MeshCore v1.16.0",
      "notesHtml": "<p>Synced with upstream vanilla MeshCore v1.16.0</p>\n"
    },
    {
      "version": "v20260605.132226",
      "name": "Firmware v20260605.132226",
      "datetime": "2026-06-05T13:22:43Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260605.132226",
      "prerelease": false,
      "notes": "## New\r\n\r\n**USB serial console for companions**\r\nConfigure companion devices (e.g. Wio Tracker L1) by typing commands over USB, no phone app needed.\r\nWorks in the flasher.meshcore.io serial console (or any terminal), just like the repeater CLI. Stays out of the way of the Bluetooth/USB app — only one connection is active at a time.\r\n\r\n**Battery gauge calibration (`adc.multiplier`)**\r\nCorrect the battery voltage reading so the % is accurate for your device:\r\n- `set adc.multiplier full` — calibrate at full charge (easiest)\r\n- `set adc.multiplier target <mV>` — calibrate to a multimeter reading (most accurate)\r\n- `set adc.multiplier 0` — reset to default\r\n- `get adc.multiplier` — show current\r\n\r\n**Per-device battery curves**\r\nBattery % now uses a real, device-specific voltage curve instead of a rough estimate, so readings are far more accurate.\r\nIncluded for Wio Tracker L1, T1000-E, SenseCAP Solar, ThinkNode M6, and RAK WisMesh Tag (others use a sensible default).\r\n\r\n**Low-battery auto-shutdown**\r\nBattery-powered companions now power down automatically when the cell runs low (default 3.3 V, ~10% on the new curves) to protect it from over-discharge.\r\nShows a brief \"Low Battery — Shutting Down\" notice first (3 s on OLED; e-paper just leaves it on screen), and is skipped while charging or on USB power so it never cuts out a plugged-in device.\r\nOn by default for nRF52 companions.\r\nTune it over the USB console:\r\n- `set autoshutdown <mV>` — set the cutoff (1–5000 mV)\r\n- `set autoshutdown 0` — turn it off\r\n- `get autoshutdown` — show current\r\n\r\n**Clock source indicator**\r\nThe time in the top bar now shows a tiny letter telling you where it was last synced from: **G** (GPS), **A** (phone app), **N** (network), or **L** (local).\r\nIf the device hasn't heard from GPS or the app in the last 12 hours, it falls back to **L** — the clock is just free-running on the device's own crystal at that point (drift is only a few seconds a day, so it stays accurate, but you know it isn't being checked against an outside source).\r\nAfter a restart it shows **L** until the first sync.\r\n\r\n## Fixed\r\n\r\n**Device name no longer overlaps the clock**\r\nOn the home screen, a long device name used to run underneath the time and battery in the top bar, making both unreadable.\r\nThe name is now trimmed to fit the space before the clock, on any screen size.",
      "notesHtml": "<h2>New</h2>\n<p><strong>USB serial console for companions</strong>\nConfigure companion devices (e.g. Wio Tracker L1) by typing commands over USB, no phone app needed.\nWorks in the flasher.meshcore.io serial console (or any terminal), just like the repeater CLI. Stays out of the way of the Bluetooth/USB app — only one connection is active at a time.</p>\n<p><strong>Battery gauge calibration (<code>adc.multiplier</code>)</strong>\nCorrect the battery voltage reading so the % is accurate for your device:</p>\n<ul>\n<li><code>set adc.multiplier full</code> — calibrate at full charge (easiest)</li>\n<li><code>set adc.multiplier target &lt;mV&gt;</code> — calibrate to a multimeter reading (most accurate)</li>\n<li><code>set adc.multiplier 0</code> — reset to default</li>\n<li><code>get adc.multiplier</code> — show current</li>\n</ul>\n<p><strong>Per-device battery curves</strong>\nBattery % now uses a real, device-specific voltage curve instead of a rough estimate, so readings are far more accurate.\nIncluded for Wio Tracker L1, T1000-E, SenseCAP Solar, ThinkNode M6, and RAK WisMesh Tag (others use a sensible default).</p>\n<p><strong>Low-battery auto-shutdown</strong>\nBattery-powered companions now power down automatically when the cell runs low (default 3.3 V, ~10% on the new curves) to protect it from over-discharge.\nShows a brief \"Low Battery — Shutting Down\" notice first (3 s on OLED; e-paper just leaves it on screen), and is skipped while charging or on USB power so it never cuts out a plugged-in device.\nOn by default for nRF52 companions.\nTune it over the USB console:</p>\n<ul>\n<li><code>set autoshutdown &lt;mV&gt;</code> — set the cutoff (1–5000 mV)</li>\n<li><code>set autoshutdown 0</code> — turn it off</li>\n<li><code>get autoshutdown</code> — show current</li>\n</ul>\n<p><strong>Clock source indicator</strong>\nThe time in the top bar now shows a tiny letter telling you where it was last synced from: <strong>G</strong> (GPS), <strong>A</strong> (phone app), <strong>N</strong> (network), or <strong>L</strong> (local).\nIf the device hasn't heard from GPS or the app in the last 12 hours, it falls back to <strong>L</strong> — the clock is just free-running on the device's own crystal at that point (drift is only a few seconds a day, so it stays accurate, but you know it isn't being checked against an outside source).\nAfter a restart it shows <strong>L</strong> until the first sync.</p>\n<h2>Fixed</h2>\n<p><strong>Device name no longer overlaps the clock</strong>\nOn the home screen, a long device name used to run underneath the time and battery in the top bar, making both unreadable.\nThe name is now trimmed to fit the space before the clock, on any screen size.</p>\n"
    },
    {
      "version": "v20260603.123327",
      "name": "Firmware v20260603.123327",
      "datetime": "2026-06-03T12:33:43Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260603.123327",
      "prerelease": false,
      "notes": "(Re-Released because of native linux bugfixes)\r\n\r\nNew\r\n\r\n- Room Server (BBS) role (no auto-build, no repeater function)\r\n- GAT562 30S Mesh Kit board support (nRF52840 + SX1262, RAK4631 core, 30 dBm PA) — one image for both the full kit and the screenless solar repeater.\r\n- Production is now the default build — no more prod.conf; debug.conf is the opt-in for logging.\r\n- USB companion decoupled from logging — CDC ACM transport now compiles independently of CONFIG_LOG. (companions are USB+BLE hybrid from now, only one type can be connected at a time)\r\n- Linux native target — built and shipped as executables (companion + repeater).\r\n- Battery reading should be more precise with board specific curve support and a standard lipo curve for devices that does not have any data on the internet about it's curve\r\n\r\nBug Fixes\r\n\r\n- USB cable-yank no longer strands the companion on USB and blocks BLE until reboot.\r\n- BLE/USB transport handoff reworked to first-come-first-served, made thread-safe.\r\n- Zero-delay direct/zero-hop sends over BLE/USB no longer stall.\r\n- Channel reply from the Unread screen now sends instead of silently failing.\r\n- Repeater anon reply with a malformed return path now floods (safe fallback) instead of emitting a corrupt direct packet.\r\n- Header-error frames now counted as receive errors (and no more zombie packet receptions triggered)\r\n- Airtime/LDRO estimate fixed to track the driver across every SF/BW.\r\n- RNG seed derivation hardened against hash failure.\r\n\r\nInternals\r\n\r\n- Crypto: orlp/ed25519 → Monocypher 4.0.2 — audited, single-file. Existing identities load/sign/verify unchanged — no re-key, no migration, full wire compatibility.\r\n- Bumped west / pinned Zephyr tree.\r\n- Synced with vanilla MeshCore dev.\r\n- Split RepeaterMesh into Uplink + RegionCLI; deduped app-layer response/telemetry/JSON builders.\r\n- Simplified the packet manager, dispatcher, and radio config paths.",
      "notesHtml": "<p>(Re-Released because of native linux bugfixes)</p>\n<p>New</p>\n<ul>\n<li>Room Server (BBS) role (no auto-build, no repeater function)</li>\n<li>GAT562 30S Mesh Kit board support (nRF52840 + SX1262, RAK4631 core, 30 dBm PA) — one image for both the full kit and the screenless solar repeater.</li>\n<li>Production is now the default build — no more prod.conf; debug.conf is the opt-in for logging.</li>\n<li>USB companion decoupled from logging — CDC ACM transport now compiles independently of CONFIG_LOG. (companions are USB+BLE hybrid from now, only one type can be connected at a time)</li>\n<li>Linux native target — built and shipped as executables (companion + repeater).</li>\n<li>Battery reading should be more precise with board specific curve support and a standard lipo curve for devices that does not have any data on the internet about it's curve</li>\n</ul>\n<p>Bug Fixes</p>\n<ul>\n<li>USB cable-yank no longer strands the companion on USB and blocks BLE until reboot.</li>\n<li>BLE/USB transport handoff reworked to first-come-first-served, made thread-safe.</li>\n<li>Zero-delay direct/zero-hop sends over BLE/USB no longer stall.</li>\n<li>Channel reply from the Unread screen now sends instead of silently failing.</li>\n<li>Repeater anon reply with a malformed return path now floods (safe fallback) instead of emitting a corrupt direct packet.</li>\n<li>Header-error frames now counted as receive errors (and no more zombie packet receptions triggered)</li>\n<li>Airtime/LDRO estimate fixed to track the driver across every SF/BW.</li>\n<li>RNG seed derivation hardened against hash failure.</li>\n</ul>\n<p>Internals</p>\n<ul>\n<li>Crypto: orlp/ed25519 → Monocypher 4.0.2 — audited, single-file. Existing identities load/sign/verify unchanged — no re-key, no migration, full wire compatibility.</li>\n<li>Bumped west / pinned Zephyr tree.</li>\n<li>Synced with vanilla MeshCore dev.</li>\n<li>Split RepeaterMesh into Uplink + RegionCLI; deduped app-layer response/telemetry/JSON builders.</li>\n<li>Simplified the packet manager, dispatcher, and radio config paths.</li>\n</ul>\n"
    },
    {
      "version": "v20260528.133858",
      "name": "Firmware v20260528.133858",
      "datetime": "2026-05-28T13:39:14Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260528.133858",
      "prerelease": false,
      "notes": "Security\r\n- Hardened login password comparison against timing side-channel attacks\r\n- Fixed weak entropy at first boot — initial device identity is now properly random\r\n- Fixed 3 RNG edge cases that could produce predictable output under certain startup conditions\r\n\r\nBug Fixes\r\n- Fixed repeater + observer mode combo not working together\r\n- USB: unified CDC ACM init across companion and repeater, removed unnecessary boot delays\r\n\r\nNew\r\n- Native Linux target — initial port for desktop testing",
      "notesHtml": "<p>Security</p>\n<ul>\n<li>Hardened login password comparison against timing side-channel attacks</li>\n<li>Fixed weak entropy at first boot — initial device identity is now properly random</li>\n<li>Fixed 3 RNG edge cases that could produce predictable output under certain startup conditions</li>\n</ul>\n<p>Bug Fixes</p>\n<ul>\n<li>Fixed repeater + observer mode combo not working together</li>\n<li>USB: unified CDC ACM init across companion and repeater, removed unnecessary boot delays</li>\n</ul>\n<p>New</p>\n<ul>\n<li>Native Linux target — initial port for desktop testing</li>\n</ul>\n"
    },
    {
      "version": "v20260522.140428",
      "name": "Firmware v20260522.140428",
      "datetime": "2026-05-22T14:04:43Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260522.140428",
      "prerelease": false,
      "notes": "Wio Tracker — completely new joystick UI (thanks [@Calvario](https://github.com/Calvario)) — new screen lifecycle, DM retry, channel send with feedback, GPS screen with altitude, lock screen, time sync, path hash bytes setting, and more.\r\n\r\nHeltec T114 board support (thanks [@Calvario](https://github.com/Calvario)) — with and without screen.\r\n\r\nPSRAM activation on capable devices — enables OTA on boards that support it.\r\n\r\nSync with vanilla MeshCore dev\r\n\r\nBug Fixes\r\nBLE disabled state now correctly honoured — watchdog was silently re-enabling BLE on every housekeeping tick\r\nFixed out-of-bounds read in path-decoding (BLE + LoRa-anon paths)\r\nNull-termination fix in Add/Update Contact command\r\nFixed overflow frame being clobbered under BLE congestion\r\nFixed T114 long press\r\nSeveral BLE audit hardening fixes (error codes, length checks, buffer sizing)",
      "notesHtml": "<p>Wio Tracker — completely new joystick UI (thanks <a href=\"https://github.com/Calvario\" target=\"_blank\" rel=\"noopener noreferrer\">@Calvario</a>) — new screen lifecycle, DM retry, channel send with feedback, GPS screen with altitude, lock screen, time sync, path hash bytes setting, and more.</p>\n<p>Heltec T114 board support (thanks <a href=\"https://github.com/Calvario\" target=\"_blank\" rel=\"noopener noreferrer\">@Calvario</a>) — with and without screen.</p>\n<p>PSRAM activation on capable devices — enables OTA on boards that support it.</p>\n<p>Sync with vanilla MeshCore dev</p>\n<p>Bug Fixes\nBLE disabled state now correctly honoured — watchdog was silently re-enabling BLE on every housekeeping tick\nFixed out-of-bounds read in path-decoding (BLE + LoRa-anon paths)\nNull-termination fix in Add/Update Contact command\nFixed overflow frame being clobbered under BLE congestion\nFixed T114 long press\nSeveral BLE audit hardening fixes (error codes, length checks, buffer sizing)</p>\n"
    },
    {
      "version": "v20260514.205836",
      "name": "Firmware v20260514.205836",
      "datetime": "2026-05-14T20:58:46Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260514.205836",
      "prerelease": false,
      "notes": "- New experimental RX-busy latch: ZephCore now tracks an in-progress reception across the full packet, so the transmitter won't step on a packet that's already being decoded.\r\n- Improved CAD (Listen-Before-Talk) behavior: the radio re-enters listening mode faster between TX retries on busy channels, and a stuck channel-busy state recovers automatically instead of requiring a reset.\r\n- updated default prefs to advert less and loop detect moderate",
      "notesHtml": "<ul>\n<li>New experimental RX-busy latch: ZephCore now tracks an in-progress reception across the full packet, so the transmitter won't step on a packet that's already being decoded.</li>\n<li>Improved CAD (Listen-Before-Talk) behavior: the radio re-enters listening mode faster between TX retries on busy channels, and a stuck channel-busy state recovers automatically instead of requiring a reset.</li>\n<li>updated default prefs to advert less and loop detect moderate</li>\n</ul>\n"
    },
    {
      "version": "v20260511.125830",
      "name": "Firmware v20260511.125830",
      "datetime": "2026-05-11T12:58:40Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260511.125830",
      "prerelease": false,
      "notes": "- Channel deletion safety: fixed bug where deleting the public channel could wipe other channels after reboot.\r\n- LBT retry priority: preserved flood packet priority during LBT-based retransmissions.\r\n- CAD/LBT mode switching: fixed radio reconfiguration so mode changes are fully applied.\r\n- rx_boost init: set deterministic startup state to avoid undefined boot behavior.\r\n- Atomic contact saves (QSPI): contacts now use temp+sync+rename on external flash devices.\r\n- Atomic write refactor: unified temp-file atomic write flow used by datastore save paths.\r\n- promicro_sx1262 build support: fixed/added board build wiring and related project metadata.\r\n- Vanilla sync (CompanionMesh): aligned local logic with upstream baseline changes.\r\n- SX1262 patch updates: refined Zephyr SX126x native patch and related ignore rules.",
      "notesHtml": "<ul>\n<li>Channel deletion safety: fixed bug where deleting the public channel could wipe other channels after reboot.</li>\n<li>LBT retry priority: preserved flood packet priority during LBT-based retransmissions.</li>\n<li>CAD/LBT mode switching: fixed radio reconfiguration so mode changes are fully applied.</li>\n<li>rx_boost init: set deterministic startup state to avoid undefined boot behavior.</li>\n<li>Atomic contact saves (QSPI): contacts now use temp+sync+rename on external flash devices.</li>\n<li>Atomic write refactor: unified temp-file atomic write flow used by datastore save paths.</li>\n<li>promicro_sx1262 build support: fixed/added board build wiring and related project metadata.</li>\n<li>Vanilla sync (CompanionMesh): aligned local logic with upstream baseline changes.</li>\n<li>SX1262 patch updates: refined Zephyr SX126x native patch and related ignore rules.</li>\n</ul>\n"
    },
    {
      "version": "v20260507.103840",
      "name": "Firmware v20260507.103840",
      "datetime": "2026-05-07T10:38:49Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260507.103840",
      "prerelease": false,
      "notes": "In one of my ravings trying to shorten rx-rx and tx-rx path I accidentally disabled hardware CAD.\r\nThis release just does that: re-enables it.\r\n\r\nAlso added formatters for nRF here: https://github.com/liquidraver/ZephCore/tree/master/formatter\r\n\r\n\r\nHardware CAD test:\r\n\r\nNothing catched in that 2 symbol CAD check:\r\n\r\nsx126x_irq_work_handler: IRQ status: 0x0080\r\nsx126x_irq_work_handler: CAD done: free\r\nsx126x_irq_work_handler: IRQ status: 0x0001\r\nsx126x_handle_irq_tx_done: TX done\r\nlora_radio_base: TX complete, RX restarted\r\n\r\nSomething was in our channel during CAD check:\r\n\r\nsx126x_irq_work_handler: IRQ status: 0x0180←[0m\r\nsx126x_irq_work_handler: CAD done: activity←[0m\r\nsx126x_lora_send_async: LBT: channel busy←[0m\r\nlora_radio_base: hwSendAsync failed: -16←[0m\r\nzephcore_dispatcher: checkSend: startSendRaw failed! re-queuing delay=240\r\n....same goes on for 3-4 times\r\nthen:\r\nsx126x_irq_work_handler: IRQ status: 0x0080\r\nsx126x_irq_work_handler: CAD done: free\r\nsx126x_irq_work_handler: IRQ status: 0x0001\r\nsx126x_handle_irq_tx_done: TX done\r\nlora_radio_base: TX complete, RX restarted",
      "notesHtml": "<p>In one of my ravings trying to shorten rx-rx and tx-rx path I accidentally disabled hardware CAD.\nThis release just does that: re-enables it.</p>\n<p>Also added formatters for nRF here: <a href=\"https://github.com/liquidraver/ZephCore/tree/master/formatter\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/liquidraver/ZephCore/tree/master/formatter</a></p>\n<p>Hardware CAD test:</p>\n<p>Nothing catched in that 2 symbol CAD check:</p>\n<p>sx126x_irq_work_handler: IRQ status: 0x0080\nsx126x_irq_work_handler: CAD done: free\nsx126x_irq_work_handler: IRQ status: 0x0001\nsx126x_handle_irq_tx_done: TX done\nlora_radio_base: TX complete, RX restarted</p>\n<p>Something was in our channel during CAD check:</p>\n<p>sx126x_irq_work_handler: IRQ status: 0x0180←[0m\nsx126x_irq_work_handler: CAD done: activity←[0m\nsx126x_lora_send_async: LBT: channel busy←[0m\nlora_radio_base: hwSendAsync failed: -16←[0m\nzephcore_dispatcher: checkSend: startSendRaw failed! re-queuing delay=240\n....same goes on for 3-4 times\nthen:\nsx126x_irq_work_handler: IRQ status: 0x0080\nsx126x_irq_work_handler: CAD done: free\nsx126x_irq_work_handler: IRQ status: 0x0001\nsx126x_handle_irq_tx_done: TX done\nlora_radio_base: TX complete, RX restarted</p>\n"
    },
    {
      "version": "v20260506.102120",
      "name": "Firmware v20260506.102120",
      "datetime": "2026-05-06T10:21:30Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260506.102120",
      "prerelease": false,
      "notes": "- Duty cycling is now disabled by default. \r\n  Further research showed that preamble 16 is NOT enough for a reliable duty cycle in noisy environments, so we'll wait for most of the mesh switching to preamble 32 to test it further\r\n\r\n- SX driver had some serious issues with CAD so it is recommended to update to this version as soon as possible\r\n\r\n- GPS will not be disabled if no GNSS message comes in (on repeaters), that was a test-code that stuck",
      "notesHtml": "<ul>\n<li><p>Duty cycling is now disabled by default. \nFurther research showed that preamble 16 is NOT enough for a reliable duty cycle in noisy environments, so we'll wait for most of the mesh switching to preamble 32 to test it further</p>\n</li>\n<li><p>SX driver had some serious issues with CAD so it is recommended to update to this version as soon as possible</p>\n</li>\n<li><p>GPS will not be disabled if no GNSS message comes in (on repeaters), that was a test-code that stuck</p>\n</li>\n</ul>\n"
    },
    {
      "version": "v20260505.131914",
      "name": "Firmware v20260505.131914",
      "datetime": "2026-05-05T13:19:23Z",
      "url": "https://github.com/liquidraver/ZephCore/releases/tag/v20260505.131914",
      "prerelease": false,
      "notes": "Kistarcsa-edition\r\n\r\nTested in the depths of the tarcsa-cauldron with weak/noisy/who-knows-whats-the-problem-of-that-heltecv3 repeater, optimized for the aforementioned use-case.\r\n\r\n- Duty cycle is 13/3 now. Slightly more battery consumption, but better RX error margin. No use for extra battery time if packets are lost in noisy/weak link environments.\r\n- hwconfig now has a fail-safe so no more edge-case unsaved radio settings\r\n- SX TX Ramp time increased to 800µs from 200µs. expect very heavy latency increase if you can sense time in a different way normal humans do! \r\n  (also expect cleaner spectral mask at 22 dBm. nobody cares about it, doesn't change anything in a meaningful way, but it's a playground, we optimize even things that doesn't really need optimizing)\r\n  (ask your favourite AI what are the benefits of 800µs vs 200µs if interested)\r\n- Companions had too much initial jitter with zero benefit, so it was tuned down to RNG20-150msec\r\n- Claude noticed some line ending stuff by itself, without asking, so I gave him credit (heh)",
      "notesHtml": "<p>Kistarcsa-edition</p>\n<p>Tested in the depths of the tarcsa-cauldron with weak/noisy/who-knows-whats-the-problem-of-that-heltecv3 repeater, optimized for the aforementioned use-case.</p>\n<ul>\n<li>Duty cycle is 13/3 now. Slightly more battery consumption, but better RX error margin. No use for extra battery time if packets are lost in noisy/weak link environments.</li>\n<li>hwconfig now has a fail-safe so no more edge-case unsaved radio settings</li>\n<li>SX TX Ramp time increased to 800µs from 200µs. expect very heavy latency increase if you can sense time in a different way normal humans do! \n(also expect cleaner spectral mask at 22 dBm. nobody cares about it, doesn't change anything in a meaningful way, but it's a playground, we optimize even things that doesn't really need optimizing)\n(ask your favourite AI what are the benefits of 800µs vs 200µs if interested)</li>\n<li>Companions had too much initial jitter with zero benefit, so it was tuned down to RNG20-150msec</li>\n<li>Claude noticed some line ending stuff by itself, without asking, so I gave him credit (heh)</li>\n</ul>\n"
    }
  ],
  "changelogSource": "github",
  "changelogUpdatedAt": "2026-07-31T17:41:27.266Z",
  "deviceFamilies": [
    {
      "id": "heltec-wifi-lora-32",
      "label": "Heltec"
    },
    {
      "id": "rak-wisblock",
      "label": "RAK"
    },
    {
      "id": "seeed-sensecap-solar",
      "label": "Seeed Studio SenseCAP Solar Node"
    },
    {
      "id": "seeed-wio-tracker-l1",
      "label": "Wio Tracker L1"
    },
    {
      "id": "atlavox-beacon",
      "label": "Atlavox Beacon Solar"
    },
    {
      "id": "atlavox-m1",
      "label": "Atlavox M1"
    },
    {
      "id": "elecrow-elecrow-thinknode-m1",
      "label": "Elecrow ThinkNode M1"
    },
    {
      "id": "elecrow-elecrow-thinknode-m3",
      "label": "Elecrow ThinkNode M3"
    },
    {
      "id": "elecrow-elecrow-thinknode-m6",
      "label": "Elecrow ThinkNode M6"
    },
    {
      "id": "gatiot-gat562",
      "label": "GAT-IoT GAT562"
    },
    {
      "id": "heltec-t114",
      "label": "Heltec T114"
    },
    {
      "id": "heltec-wireless-tracker",
      "label": "Heltec Wireless Tracker"
    },
    {
      "id": "heltec-wsl3",
      "label": "Heltec WSL3"
    },
    {
      "id": "lilygo-t-beam",
      "label": "LilyGo T-Beam"
    },
    {
      "id": "lilygo-t-echo",
      "label": "LilyGo T-Echo"
    },
    {
      "id": "lilygo-t-lora-c6",
      "label": "LilyGo T-LoRa C6"
    },
    {
      "id": "promicro-nrf52",
      "label": "ProMicro nrf52 (faketec)"
    },
    {
      "id": "rak-wismesh-repeater",
      "label": "RAK WisMesh Repeater Mini"
    },
    {
      "id": "rak-wismesh",
      "label": "RAK WisMesh Tag"
    },
    {
      "id": "seeed-sensecap-t1000-e",
      "label": "Seeed Studio SenseCAP T1000-E"
    },
    {
      "id": "uniteng-station-g2",
      "label": "UnitEng Station G2"
    },
    {
      "id": "seeed-xiao",
      "label": "Xiao"
    },
    {
      "id": "seeed-xiao-c3",
      "label": "Xiao C3"
    },
    {
      "id": "seeed-xiao-c6",
      "label": "Xiao C6"
    },
    {
      "id": "seeed-xiao-mg24-sx1262",
      "label": "Xiao MG24 + SX1262"
    },
    {
      "id": "seeed-xiao-wio",
      "label": "Xiao nRF52 WIO"
    },
    {
      "id": "seeed-xiao-nrf54l15-sx1262",
      "label": "Xiao nRF54L15 + SX1262"
    },
    {
      "id": "seeed-xiao-s3-wio",
      "label": "Xiao S3 WIO"
    },
    {
      "id": "yetiwurks-basestation",
      "label": "Yeti Wurks Base Station"
    },
    {
      "id": "uart-cz-solar-node",
      "label": "μArt.cz Solar"
    }
  ],
  "primaryFamily": {
    "id": "heltec-wifi-lora-32",
    "label": "Heltec"
  },
  "scopeGroup": null
}
