diff --git a/RELEASE.md b/RELEASE.md index 09e74dd38..dfe6fd066 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -73,8 +73,38 @@ release title/body, and the final asset inventory after publication. Preserve existing assets when making a documentation-only release edit. The current trigger explicitly excludes `repeater-room-*`. -Use role-specific introductions for Companion, Repeater/Room Server, Sensor, -LoRa OTA Repeater, and expanded Full infrastructure pages. Link the exact -board/storage [OTAFIX 2.4.12 release](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/v0.11.0-OTAFIX2.4.12) -where nRF52 bootloader requirements apply. Later documentation commits can be -linked from release notes without moving the firmware tag or rebuilding assets. +## Release notes + +Write one public release page at `docs/releases/VERSION.md`. Make it the +starting point from the documentation home page and every GitHub download page. +Use the same format for each revision: + +1. **Status and purpose:** stable or testing, version, region, and one sentence + explaining the user benefit. +2. **New:** a short list of actual additions since the previous stable release. + Say who each change helps; link setup instructions beside the feature. +3. **Fixed:** name affected boards and the behavior corrected. +4. **Choose a download:** picker first, then a short table of role-specific + GitHub pages. Explain Full/Reduced sensors and included bootloaders once. +5. **Before updating:** only required actions, changed defaults and compatibility + limits. Link the installation guides instead of copying them. +6. **Tested and limitations:** separate build checks from physical tests. Name + important remaining limitations and link source-specific evidence. + +Compare the actual previous release's source and notes. A long commit list can +include old work imported by merges; do not present it all as new. Do not relist +existing features as additions. Keep tuning commands, build counts, memory +layouts and implementation details in the reference guides or test reports. + +The main GitHub release should summarize New, Fixed and Tested in a few bullets +and link the public release page. Other download pages should lead with their +purpose and link the same notes. Use readable link names such as "ESP32 +Repeater, Room Server and Sensor" rather than internal keys like `full-profiles`. +Label split pages "part 1 of 3" and explain that they contain the same version, +not different feature levels. The picker should find the exact board for users. + +Keep required bootloader guidance linked to the exact board/storage +[OTAFIX release](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/v0.11.0-OTAFIX2.4.12). +A notes-only correction may update public titles, bodies and documentation. +Preserve published tags, binaries, attached build notes and checksum files; +verify release status, Latest and the asset inventory remain unchanged. diff --git a/docs/index.md b/docs/index.md index 7fcf09958..d43c8d782 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,14 +4,21 @@ # MeshCore documentation -Choose firmware with the [firmware picker](firmware_picker.md). For help with a device already in service, start with the [FAQ](faq.md). +## Start here -## Current release +**Current stable release: [1.18.0.1 USA Cascade](releases/1.18.0.1.md).** +Read what changed, which boards benefit and the known limitations in one place. -- [1.18.0.1 USA Cascade release notes](releases/1.18.0.1.md) -- [Test Results](test-results/index.md) - source-specific validation archive +| I want to... | Start here | +| --- | --- | +| See what is new or fixed | [1.18.0.1 release notes](releases/1.18.0.1.md) | +| Download firmware for my board | [Firmware picker](firmware_picker.md) | +| Install an update | [Update guide](ota_user_guide.md) | +| Turn a feature on or off | [Feature controls by role](role_feature_switches.md) | +| Find a command | [CLI reference](cli_commands.md) | +| Troubleshoot a device | [FAQ](faq.md) | -## Documentation +## Detailed guides - [SolarXiao Base/Advanced/MPPT detection and battery controls](solarxiao_power.md) @@ -32,6 +39,7 @@ The navigation groups the remaining guides under Documentation, including board- ## Engineering records +- [Test Results](test-results/index.md) - hardware and build evidence, with the tested versions - [Research](research/index.md) - design notes, diagnostics, and historical update-chain studies - [Old Releases](old-releases/index.md) - historical release notes and supporting files diff --git a/docs/ota_user_guide.md b/docs/ota_user_guide.md index ead2a2fad..6d3f8d0d5 100644 --- a/docs/ota_user_guide.md +++ b/docs/ota_user_guide.md @@ -4,13 +4,15 @@ # Updating your node over the air (OTA) - user guide -For **1.17.1.5**, use the exact board/storage profile from -[OTAFIX 2.4.6](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/0.11.0-OTAFIX2.4.6) -for nRF52 OTAFIX installations. New internal-flash hybrid receivers require -its 64 KiB retained-RAM handoff; QSPI and microSD targets require their own -matching bootloader profiles. Earlier preview versions mentioned below -describe compatibility/migration history, not the current recommended download. -Full Companion is a MOTA source and normally updates itself over USB. +For **[1.18.0.1 USA Cascade](releases/1.18.0.1.md)**, choose the exact board, +role and storage layout in the [firmware picker](firmware_picker.md). Supported +nRF52 Repeater and Room Server images can include the matching OTAFIX 2.4.12 +bootloader; see the [embedded bootloader guide](nrf52_embedded_bootloader.md). +Ordinary application images preserve the installed bootloader. Standalone +bootloader downloads require an exact board/storage match from +[OTAFIX 2.4.12](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/v0.11.0-OTAFIX2.4.12). +Full Companion sends LoRa updates to other nodes and normally updates itself +over USB. Older versions mentioned below describe compatibility history. Before a Companion upgrade, export its contacts when possible. ESP32, RP2040 and STM32 can retain complete legacy records ahead of an interrupted final @@ -60,90 +62,19 @@ preserves the RX benefit without risking missed packets from a legacy > application image alone is not enough. An intermediate repeater only relays packets and needs neither an > install-capable image nor OTAFIX. Check the bootloader release for an exact board match before attempting an update. -The following nRF52 repeater families gained firmware-side LoRa OTA targets in -this release. Their ordinary repeater keeps its complete declared sensor recipe; -the install-capable `lora_ota_no_external_sensors` sibling is smaller: - -- Heltec Mesh Solar, T1, and Tower V2 -- Keepteen LT1, LilyGo T-Impulse Plus, Mesh Pocket, and Nano G2 Ultra -- Minewsemi ME25LS01, RAK3401, SenseCAP Solar, and Wio WM1110 - -The RAK3401 `RAK_3401_repeater_lora_ota_no_external_sensors` image trims selected -optional environmental/ranging drivers but does not disable I2C. It retains the -INA219, INA226, INA260, and INA3221 voltage/current monitors, board display and -RTC support, and both RAK12500 I2C and RAK12501/L76K UART GPS paths. The four -INA drivers are only the retained voltage/current entries in the optional -environmental-sensor table; they are not the only I2C users. The SSD1306 OLED, -autodiscovered DS3231/RV3028/PCF8563/RX8130CE RTCs, and RAK12500 GPS use I2C as -separate board peripherals. Install one GPS module in sensor slot A. Slot D's -reset/PPS lines conflict with the RAK13302 radio's BUSY/DIO1 lines. - -The firmware-configured INA3221 address and the RAK12500 address are both -`0x42`, so those devices cannot share one bus at those addresses. To install -both, leave the RAK12500 at `0x42`, strap INA3221 A0 to SCL for `0x43`, and use -a firmware build with `-DTELEM_INA3221_ADDRESS=0x43`. - -The reduced RAK4631 profiles likewise retain the four INA monitors. The plain -repeater and Serial2 bridge retain GPS: a RAK12501 can use sensor slot A or D, -and a RAK12500 can use slot A or C. The runtime RS-232 bridge defaults to -Serial2 so the UART RAK12501 can retain Serial1. The merged runtime image always -reserves Serial1: a bounded silent probe cannot prove that a cold RAK12501 is -absent, and turning the GPS setting off cannot stop a fitted module from driving -the shared UART while WB_IO2/3V3_S remains powered. Use Serial2. Serial1 requires -an explicit no-GPS/dedicated image. The reservation remains fail-closed even -when an I2C RAK12500 is detected. The explicitly compiled -`RAK_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors` target -omits the combined GPS provider because its bridge owns the UART used by -RAK12501; consequently that legacy image does not expose the I2C RAK12500 path -either, even though RAK12500 itself does not use the UART. - -Selected nRF52 repeaters with dedicated external QSPI can now stage the -complete package off-chip, so their normal full-sensor repeater build can -install a full image or an in-place delta. The current matched families are -XIAO nRF52840 and its XIAO-module derivatives, original LilyGo T-Echo, -ThinkNode M1/M6, Wio Tracker L1, SenseCAP Solar, and the dedicated RAK4631 + -RAK15001 slot-C target. The list also includes a separately wired W25Q16JV on -RAK19007 with either RAK4631 or RAK3401 + RAK13302. These require the -corresponding exact QSPI-aware OTAFIX bootloader; see -[the nRF52 QSPI guide](ota_nrf52_qspi.md). - -The W25Q16 option uses one permanent RAK19007 wiring for either core: CLK/MISO/ -MOSI tap IO-connector pins 26/27/28, flash CS uses J11 AIN1, and power uses J12 -VDD (3.3 V) and GND. Add an approximately 10 kOhm CS-to-VDD pull-up and never -power the flash from VBAT. The three SPI signals require underside pads or an -IO-connector interposer; the easy J10/J11/J12 headers do not expose them. The -flash consumes no WisBlock slot, so a supported GPS can remain in slot A. - -The unified RAK repeater images additionally support a header-only W25Q16 -layout, using J10 `TX1`/`RX1` and J11 `IO1`/`AIN1`. This layout needs the -matching compatible OTAFIX 2.4.10 bootloader and gives up UART1 GPS/PPS; I2C -sensors remain available. See the [pin table](ota_nrf52_qspi.md#header-only-w25q16-wiring-for-the-unified-rak-images). - -The ordinary full-sensor `RAK_4631_repeater` image remains too large for the -safe internal in-place update limit. Without external flash, use -`RAK_4631_repeater_lora_ota_no_external_sensors`; it trims selected optional -environmental/ranging drivers while retaining generic I2C, battery monitoring, -the four INA monitors, and target-compatible GPS. A RAK4631 fitted with -RAK15001 in sensor slot C can instead use -`RAK_4631_repeater_rak15001_slot_c_lora_ota` to retain the full sensor/GPS set -and stage full images or deltas off-chip. - -For the external Winbond breakout, use -`RAK_4631_repeater_w25q16_lora_ota` on RAK4631 or -`RAK_3401_repeater_rak13302_w25q16_lora_ota` on RAK3401 + RAK13302. They retain -the full sensor/GPS recipe and require exactly a 2 MiB `EF4015` W25Q16 plus the -bootloader for that same core/radio/flash combination. Install -`wiscore_rak4631_w25q16` for RAK4631 or -`wiscore_rak3401_rak13302_w25q16` for RAK3401 + RAK13302 as a one-time -prerequisite. The physical wiring is shared, but the two firmware identities -are deliberately different and cannot be substituted for one another. +For nRF52 storage and wiring requirements, see the +[QSPI hardware guide](ota_nrf52_qspi.md). Older image names and board-specific +constraints are collected in the [hardware reference](#hardware-and-older-image-layouts) +at the end of this page; use the picker for current downloads. --- -## The important part first: it's safe +## Before you start -- **Nothing installs by itself.** Your node can *discover* and download an update, but - it only **installs** when you say so (unless you deliberately turn on auto-install - see below). +- **Application updates require installation approval** unless you enable + auto-install. Images labelled **with bootloader** have a separate automatic + bootloader check after 70 seconds; read the [bootloader guide](nrf52_embedded_bootloader.md) + before choosing one. - **Bad downloads can't sneak in.** Every piece of the firmware is checked against a cryptographic fingerprint as it arrives, and the whole image is verified again before install. A corrupt or tampered download is rejected, not installed. @@ -152,8 +83,9 @@ are deliberately different and cannot be substituted for one another. - **Discovery stays quiet; a transfer is deliberate.** Periodic update discovery uses background priority. Once a download starts, its transfer packets are primary traffic across every relay hop, so use TempRadio as an OTA maintenance window when delaying unrelated mesh traffic would matter. -- **It can recover.** If an install ever fails, the node falls back to a safe recovery mode (you can - re-flash a known-good firmware over USB) - it won't be left bricked. +- **Know the recovery route.** Keep local USB/DFU or SWD access available where + practical. Recovery depends on the board, installed bootloader and failure; + a bootloader update can require a debugger if interrupted. --- @@ -402,7 +334,8 @@ complete target inventory, storage layouts, and safety contract. without valid live `EndF`, they refuse every internal pull before erase because their normal application can extend through `0xED000`. Recover one of those builds over USB/BLE DFU or SWD. -- If an **install** fails, the node won't boot a broken image - it lands in **recovery mode**: +- If an **install** fails, recovery depends on the board and bootloader. When + its recovery mode remains available: - **nRF52:** it appears as a USB drive; drag a known-good firmware `.uf2` for that exact board onto it to recover. - **ESP32:** it keeps the previous firmware in the other slot and rolls back. @@ -710,3 +643,88 @@ that only contains what changed). You get them by: For the full technical details (the file format and the radio protocol), see [the OTA protocol spec](ota_protocol.md). + +## Hardware and older image layouts + +Use the [firmware picker](firmware_picker.md) for current downloads. These +notes retain hardware constraints and older image names for existing nodes. +The 1.18.0.1 RAK release has separately checked Full/Reduced sensor images; +older size limits below are not instructions to choose a Reduced build today. + +Older firmware used separate `lora_ota_no_external_sensors` images on the +following families. These names explain older installations; use the current +picker's Full/Reduced sensor choices for 1.18.0.1: + +- Heltec Mesh Solar, T1, and Tower V2 +- Keepteen LT1, LilyGo T-Impulse Plus, Mesh Pocket, and Nano G2 Ultra +- Minewsemi ME25LS01, RAK3401, SenseCAP Solar, and Wio WM1110 + +The RAK3401 `RAK_3401_repeater_lora_ota_no_external_sensors` image trims selected +optional environmental/ranging drivers but does not disable I2C. It retains the +INA219, INA226, INA260, and INA3221 voltage/current monitors, board display and +RTC support, and both RAK12500 I2C and RAK12501/L76K UART GPS paths. The four +INA drivers are only the retained voltage/current entries in the optional +environmental-sensor table; they are not the only I2C users. The SSD1306 OLED, +autodiscovered DS3231/RV3028/PCF8563/RX8130CE RTCs, and RAK12500 GPS use I2C as +separate board peripherals. Install one GPS module in sensor slot A. Slot D's +reset/PPS lines conflict with the RAK13302 radio's BUSY/DIO1 lines. + +The firmware-configured INA3221 address and the RAK12500 address are both +`0x42`, so those devices cannot share one bus at those addresses. To install +both, leave the RAK12500 at `0x42`, strap INA3221 A0 to SCL for `0x43`, and use +a firmware build with `-DTELEM_INA3221_ADDRESS=0x43`. + +The reduced RAK4631 profiles likewise retain the four INA monitors. The plain +repeater and Serial2 bridge retain GPS: a RAK12501 can use sensor slot A or D, +and a RAK12500 can use slot A or C. The runtime RS-232 bridge defaults to +Serial2 so the UART RAK12501 can retain Serial1. The merged runtime image always +reserves Serial1: a bounded silent probe cannot prove that a cold RAK12501 is +absent, and turning the GPS setting off cannot stop a fitted module from driving +the shared UART while WB_IO2/3V3_S remains powered. Use Serial2. Serial1 requires +an explicit no-GPS/dedicated image. The reservation remains fail-closed even +when an I2C RAK12500 is detected. The explicitly compiled +`RAK_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors` target +omits the combined GPS provider because its bridge owns the UART used by +RAK12501; consequently that legacy image does not expose the I2C RAK12500 path +either, even though RAK12500 itself does not use the UART. + +Selected nRF52 repeaters with dedicated external QSPI can stage the +complete package off-chip, so their normal full-sensor repeater build can +install a full image or an in-place delta. The current matched families are +XIAO nRF52840 and its XIAO-module derivatives, original LilyGo T-Echo, +ThinkNode M1/M6, Wio Tracker L1, SenseCAP Solar, and the dedicated RAK4631 + +RAK15001 slot-C target. The list also includes a separately wired W25Q16JV on +RAK19007 with either RAK4631 or RAK3401 + RAK13302. These require the +corresponding exact QSPI-aware OTAFIX bootloader; see +[the nRF52 QSPI guide](ota_nrf52_qspi.md). + +The W25Q16 option uses one permanent RAK19007 wiring for either core: CLK/MISO/ +MOSI tap IO-connector pins 26/27/28, flash CS uses J11 AIN1, and power uses J12 +VDD (3.3 V) and GND. Add an approximately 10 kOhm CS-to-VDD pull-up and never +power the flash from VBAT. The three SPI signals require underside pads or an +IO-connector interposer; the easy J10/J11/J12 headers do not expose them. The +flash consumes no WisBlock slot, so a supported GPS can remain in slot A. + +The unified RAK repeater images additionally support a header-only W25Q16 +layout, using J10 `TX1`/`RX1` and J11 `IO1`/`AIN1`. This layout needs the +matching compatible OTAFIX 2.4.10 bootloader and gives up UART1 GPS/PPS; I2C +sensors remain available. See the [pin table](ota_nrf52_qspi.md#header-only-w25q16-wiring-for-the-unified-rak-images). + +Older full-sensor `RAK_4631_repeater` images exceeded the safe internal +in-place update limit. Their smaller sibling, +`RAK_4631_repeater_lora_ota_no_external_sensors`, trims selected optional +environmental/ranging drivers while retaining generic I2C, battery monitoring, +the four INA monitors, and target-compatible GPS. A RAK4631 fitted with +RAK15001 in sensor slot C can instead use +`RAK_4631_repeater_rak15001_slot_c_lora_ota` to retain the full sensor/GPS set +and stage full images or deltas off-chip. + +For the external Winbond breakout, use +`RAK_4631_repeater_w25q16_lora_ota` on RAK4631 or +`RAK_3401_repeater_rak13302_w25q16_lora_ota` on RAK3401 + RAK13302. They retain +the full sensor/GPS recipe and require exactly a 2 MiB `EF4015` W25Q16 plus the +bootloader for that same core/radio/flash combination. Install +`wiscore_rak4631_w25q16` for RAK4631 or +`wiscore_rak3401_rak13302_w25q16` for RAK3401 + RAK13302 as a one-time +prerequisite. The physical wiring is shared, but the two firmware identities +are deliberately different and cannot be substituted for one another. diff --git a/docs/releases/1.18.0.1.md b/docs/releases/1.18.0.1.md index 6ddf5e82c..0a028289c 100644 --- a/docs/releases/1.18.0.1.md +++ b/docs/releases/1.18.0.1.md @@ -2,105 +2,84 @@ -# MeshCore 1.18.0.1 USA Cascade +# MeshCore 1.18.0.1 - USA Cascade -Firmware source: `03214a16df2bd09d3272dfb001d2d322c6740f60`. +**Stable release.** Meshtext chat, simpler ESP32 administration and new repeater +controls. Changes below are relative to **1.17.1.9**. -USA Cascade defaults are **910.525 MHz, 62.5 kHz bandwidth, SF7 and CR5**. -Saved radio settings take precedence. Choose the exact board, radio, storage -layout and role in the [firmware picker](../firmware_picker.md). +[Choose your firmware](../firmware_picker.md) | [Update instructions](../ota_user_guide.md) -Download the [1.18.0.1 release family](https://github.com/mikecarper/MeshCore/releases/tag/v1.18.0.1-halo-keymind-cascade-dev-03214a16) -and its [migration and bootloader utilities](https://github.com/mikecarper/MeshCore/releases/tag/utility-v1.18.0.1-halo-keymind-cascade-dev-03214a16). -The picker, Mesh America and the Keymind choices in meshfirmware select these -same exact release assets. +## New -## Changes since 1.17.1.9 +- **Meshtext chat on supported Full Companions**, including T1000-E. + Use channels such as `#mt-longfast`; see [Meshtext setup](../radio_profiles.md#meshtext-channel-chat). +- **WiFi repeater bridge:** connect up to eight peers by IP on an existing + network. [WiFi setup](../udp_bridge_setup.md). + [ESP-NOW](../espnow_bridge_setup.md) and [RS232](../rs232_bridge_setup.md) + also gain status, counters and link tests. +- **One ESP32 admin password** for device setup, browser administration and + WiFi firmware uploads. Router WiFi passwords and Bluetooth PINs stay separate. + [Password details](../WiFi.md) +- **Telemetry choices:** turn individual packet types on or off. All types + are selected by default; sending telemetry still requires enabling it. + [Telemetry controls](../cli_commands.md#read-repeater-telemetry-history) +- **Low-battery forwarding limits:** optionally stop repeating below 10% and + flooding below 20%, or set your own thresholds. Both limits default off. + [Battery controls](../cli_commands.md#pause-forwarding-when-the-battery-is-low) +- **Radio2 setup warns about overlapping channels** and suggests a clearer + center frequency. [Channel spacing](../radio_profiles.md#channel-spacing). + Supported boards also gain [faster switching and saved tuning controls](../radio_profiles.md#saved-switching-controls). +- **T114 automatic screen detection:** one image for boards with or without + the supported display. +- **Optional nRF52 bootloader update included** in supported Repeater and + Room Server images. These carry the exact board's OTAFIX 2.4.12 bootloader. + [Installation requirements](../nrf52_embedded_bootloader.md#install-and-activate) -- Supported nRF52 Repeater and Room Server targets have separate application - images carrying their exact-board **OTAFIX 2.4.12** bootloader. Ordinary - applications remain available, together with standalone bootloader files. -- Full Companion includes Meshtext on all supported board families. Message - retries are bounded; repeated traffic does not become a new local message. -- [ESP-NOW](../espnow_bridge_setup.md) and - [RS232](../rs232_bridge_setup.md) bridges provide runtime status, counters - and link probes. The [WiFi UDP bridge](../udp_bridge_setup.md) supports eight - authenticated IP peers per repeater on an existing station network. -- [Telemetry packet selections](../cli_commands.md#read-repeater-telemetry-history) - independently control temperature, battery, external I2C voltage, main-radio - noise and radio2 noise snapshots. All five selections default on; the - telemetry transmitter itself remains off until enabled. -- Optional [battery forwarding thresholds](../cli_commands.md) can stop repeat - and flood forwarding independently. Both controls default off; their - percentages and supported voltage calibration are saved settings. -- Supported radios expose saved `radio.fast` acceleration controls and - `radio.fast.spi` choices. A `9` MHz selection uses approximately 8.889 MHz - where the controller supports that rate. Read `get radio.fast.caps` for the - actual board's options. -- Board profiles consolidate supported display and transport choices, including - T114 automatic display detection. Exact V4 hardware and storage variants - remain distinct where their wiring requires a different image. -- T1000-E Companion gives an enumerated USB data connection priority over - Bluetooth. Bluetooth resumes after USB disconnects if it is enabled; - battery and a charge-only wall supply allow Bluetooth. Pairing and the saved - Bluetooth setting are retained. -- Release capacity fixes retain the qualified feature sets with compact help, - shared buffers and board-specific memory placement. Every image includes its - own capability and memory reports; capacities still differ between boards. +## Fixed -See [feature controls](../role_feature_switches.md), the -[Full Companion guide](../full_companion_features.md) and -[CLI reference](../cli_commands.md) for supported commands and defaults. +- G3 startup and radio pin/slot handling. +- XIAO nRF52 external-flash startup, storage alignment and Bluetooth handling. +- V4 USB logging idle/reopen failures and ESP32 Bluetooth/power-saving recovery. -## Choose an installation +## Choose a download -For ESP32, the merged `.bin` installs bootloader, partitions and application -over USB. An app-only `.bin` requires a compatible existing partition layout. -When expansion is needed over WiFi or LoRa, use the exact board/role migration -ZIP in this release family's utility page and install its supplied Full -application. Follow the included README; an app-only image cannot expand -partitions. `get storage.layout` reports the live layout where supported. +Use the **[firmware picker](../firmware_picker.md)** to match your exact board +and role. The GitHub pages below contain the **same version**, split to fit +GitHub's download limits. -Ordinary nRF52 UF2 and Serial DFU ZIP files install the application and preserve -the installed bootloader. Repeater, Room Server and Sensor retain separately -qualified **Full supported sensors + LoRa OTA** and -**Reduced sensors + LoRa OTA** choices. These keep the exact OTA target and -storage layout; sensor policy is not a different radio identity. +| What you run | Download page | +| --- | --- | +| A radio connected to your phone or computer | [Companion](https://github.com/mikecarper/MeshCore/releases/tag/v1.18.0.1-halo-keymind-cascade-dev-03214a16) | +| An ESP32 Repeater, Room Server or Sensor | [ESP32 infrastructure](https://github.com/mikecarper/MeshCore/releases/tag/full-profiles-v1.18.0.1-halo-keymind-cascade-dev-03214a16) | +| An nRF52 Repeater, Room Server or Sensor | [Part 1](https://github.com/mikecarper/MeshCore/releases/tag/lora-ota-v1.18.0.1-halo-keymind-cascade-dev-03214a16), [Part 2](https://github.com/mikecarper/MeshCore/releases/tag/lora-ota-2-v1.18.0.1-halo-keymind-cascade-dev-03214a16), [Part 3](https://github.com/mikecarper/MeshCore/releases/tag/lora-ota-3-v1.18.0.1-halo-keymind-cascade-dev-03214a16) - use the picker to find your board | +| A partition migration or separate bootloader update | [Update utilities](https://github.com/mikecarper/MeshCore/releases/tag/utility-v1.18.0.1-halo-keymind-cascade-dev-03214a16) | -Separate `with-bootloader-2.4.12` Repeater and Room Server applications carry a -board-matched bootloader. Leave the application powered for at least 70 seconds -after installation. It checks once when radio and OTA activity are idle and -updates a recognized compatible older bootloader; matching current or newer -versions are skipped. Recovery or unrecognized bootloaders require their -documented local migration route. Check `get bootloader.ver` afterward. See -[embedded bootloader installation](../nrf52_embedded_bootloader.md). +**Full Companion** combines supported connections and features in one image. +**Full / Reduced sensors** selects optional sensor drivers on nRF52 infrastructure. +**With bootloader** includes the matched bootloader update. -The utility release also supplies exact-board bootloader-update UF2, -SoftDevice/bootloader DFU ZIP and SWD HEX files for 23 published OTAFIX profiles -covering 40 hardware aliases. The SoftDevice/bootloader DFU package requires -reinstalling the application. Pending, blocked and retired board mappings are -identified rather than replaced by another board's image. +## Before updating -LoRa OTA reception requires the matching bootloader, target identity, storage -layout and radio settings. Full Companion can send a host-supplied update to -another node; it does not install that LoRa transfer onto itself. +- Match the exact board and storage layout. ESP32 USB installs use a merged + image; WiFi updates use an app-only image and a compatible partition layout. + [Update guide](../ota_user_guide.md) +- An included nRF52 bootloader checks for a compatible older version after + 70 seconds. It is not a recovery tool for an unrecognized bootloader. + [Bootloader guide](../nrf52_embedded_bootloader.md) +- Fresh USA Cascade radio defaults: **910.525 MHz / BW62.5 / SF7 / CR5**. + Existing saved radio settings take precedence. -## Verification scope +## Tested and limitations -The complete release has **589 qualified firmware packages**: 435 ordinary -packages and 154 embedded-bootloader variants. The latter cover 77 logical -Repeater/Room targets with both sensor policies. It also includes **69 -standalone bootloader files**, **96 exact ESP32 board/role migration packages** -and **26 migration utility builds**. +The complete release passed build, memory and package checks, and +[release-source CI](https://github.com/mikecarper/MeshCore/actions/runs/40101490838). +[Hardware reports](../test-results/index.md) cover USB, Bluetooth, contact sync, +LoRa and updates at the revisions recorded. The final build has not been +physically tested on every board. -The completed matrix passed exact target inventory, source provenance, -checksums, packaged OTA identity, native DFU, firmware size, runtime RAM, -sensor-pair, bootloader mapping and migration-layout validation. All 14 jobs in -the [source revision's CI run](https://github.com/mikecarper/MeshCore/actions/runs/40101490838) -passed. +- **T1000-E:** Bluetooth pauses while connected to a USB data host and resumes + after unplugging if enabled. Bluetooth works on battery or a charge-only supply. +- **Radio2:** two profiles share one radio; simultaneous reception is not guaranteed. +- **Morse receive:** noise can still produce false letters. -Physical results remain tied to the hardware, revision and installation route -recorded in the [test reports](../test-results/index.md). Building every target -does not establish every board's physical USB, Bluetooth, WiFi, RF, sleep or -update behavior. The release does not qualify every starting firmware or -bootloader migration path. +Firmware source: [03214a16](https://github.com/mikecarper/MeshCore/commit/03214a16df2bd09d3272dfb001d2d322c6740f60). diff --git a/docs/role_feature_switches.md b/docs/role_feature_switches.md index 455391ca1..00cee2d6e 100644 --- a/docs/role_feature_switches.md +++ b/docs/role_feature_switches.md @@ -2,9 +2,10 @@ -# Feature switches by role - 1.17.1.5 USA Cascade +# Feature controls by role -Use these settings with the exact board's canonical release image. Former +Use these settings with the exact board's image from the +[current release](releases/1.18.0.1.md). Former logging, power-saving, FEM-gain, and rotated-display variants are now runtime choices where the hardware supports them. A setting cannot add missing radio hardware, MQTT code, storage, or an OTA partition. Check the download's diff --git a/mkdocs.yml b/mkdocs.yml index 837234464..558851cec 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -104,12 +104,12 @@ nav: - docs.md - Research: research/index.md - Releases: - - Current release: - - 1.18.0.1 USA Cascade: releases/1.18.0.1.md + - Current - 1.18.0.1 USA Cascade: releases/1.18.0.1.md + - Previous releases: - 1.17.1.9 USA Cascade: releases/1.17.1.9.md - 1.17.1.8 USA Cascade: releases/1.17.1.8.md - - Test Results: test-results/index.md - - Old Releases: old-releases/index.md + - Older releases: old-releases/index.md + - Test Results: test-results/index.md theme: name: material diff --git a/scripts/package_cascade_release.py b/scripts/package_cascade_release.py index 019eb7b45..966192329 100644 --- a/scripts/package_cascade_release.py +++ b/scripts/package_cascade_release.py @@ -345,6 +345,65 @@ def category(record): return "repeater-room" +def joined_labels(values, conjunction="and"): + if len(values) < 2: + return "".join(values) + return ", ".join(values[:-1]) + " " + conjunction + " " + values[-1] + + +def release_group_description(name, records, extras=()): + """Describe actual page contents without assuming every matrix is current.""" + manifests = [record["manifest"] for record in records] + platforms = {manifest["platform"] for manifest in manifests} + platform = ({"NRF52_PLATFORM": "nRF52", "ESP32_PLATFORM": "ESP32"} + .get(next(iter(platforms)), "") if len(platforms) == 1 else "") + role_names = [] + for pattern, label in ((r"_repeater(?:[_-]|$)", "Repeater"), + (r"_room_(?:server|svr)(?:[_-]|$)", "Room Server"), + (r"_sensor(?:[_-]|$)", "Sensor"), + (r"_terminal(?:[_-]|$)", "Terminal")): + if any(re.search(pattern, manifest["target"], re.I) for manifest in manifests): + role_names.append(label) + roles = joined_labels(role_names) or "Radio" + operating_roles = joined_labels(["a " + role.lower() for role in role_names], "or") + radio = ("an " if platform == "ESP32" else "a ") + platform + " radio" if platform else "a radio" + if platform == "nRF52": + radio = "an nRF52 radio" + if name == "companion": + return "Companion firmware", "Connect a radio to a phone or computer using the Companion mode supported by its hardware." + if name == "lora-ota": + lora = bool(manifests) and all("lora" in manifest.get("ota_update_methods", []) for manifest in manifests) + title = platform + " " + roles + " firmware" if platform else ( + "LoRa OTA firmware" if lora else "Wireless update firmware") + purpose = ("Run " + radio + " as " + operating_roles if operating_roles + else "Firmware for your exact radio") + return title, purpose + (" with LoRa OTA reception." if lora else " with wireless updates.") + if name == "full-profiles": + title = (platform + " " if platform else "Full ") + roles + " firmware" + return title, ("Full firmware for " + radio + " running as " + operating_roles + "." if operating_roles + else "Choose the Full firmware for your exact radio and role.") + if name == "logging": + return "USB packet logging firmware", "Use these images when the chosen radio role needs USB packet logging." + if name == "utility": + names = [path.name.lower() for path in extras] + migration = any(filename.endswith("-migration.zip") or filename.startswith("esp32-partition-migration-") + for filename in names) + bootloader = any("bootloader" in filename for filename in names) + tools = joined_labels((["migration"] if migration else []) + (["bootloader"] if bootloader else [])) + if records: + title = roles + " firmware" + (" and upgrade tools" if extras else "") + return title, "Applications for " + (operating_roles or "your radio") + ( + ", plus exact-board " + (tools or "upgrade") + " tools." if extras else ".") + return (tools.capitalize() if tools else "Upgrade") + " tools", ( + "Use the exact board/role migration package or board/storage bootloader file for an update or recovery." + if migration and bootloader else + "Use the exact board and role package to migrate an ESP32 partition layout." if migration else + "Use the exact board/storage bootloader file for an update or recovery." if bootloader else + "Use the exact hardware package and follow its included instructions.") + return roles + " firmware", ("Run " + radio + " as " + operating_roles + "." if operating_roles + else "Firmware for the exact radio hardware and role selected in the picker.") + + def sha256(path): digest = hashlib.sha256() with path.open("rb") as stream: @@ -554,10 +613,8 @@ def main(): raise ValueError("staging directory must be empty; existing releases are never overwritten") base_tag = version groups = [] - titles = {"companion": "Companion builds", "repeater-room": "Repeater and Room Server builds", - "utility": "Sensor and Terminal utilities", "logging": "USB packet logging builds", - "lora-ota": "LoRa OTA builds", "full-profiles": "Expanded FULL ESP32 profiles"} - for name in titles: + descriptions = {} + for name in ("companion", "repeater-room", "utility", "logging", "lora-ota", "full-profiles"): current = [] count = 0 chunks = [] @@ -572,16 +629,25 @@ def main(): for index, chunk in enumerate(chunks): key = name + (f"-{index + 1}" if index else "") tag = base_tag if key == "companion" else f"{key}-{base_tag}" - title = f"MeshCore {args.version}{'' if args.stable else ' Dev'} - {titles[name]}" - if index: - title += f" (part {index + 1})" + label, purpose = release_group_description(name, chunk, extras if key == "utility" else []) + if len(chunks) > 1: + label += f" (part {index + 1} of {len(chunks)})" + title = f"MeshCore {args.version}{'' if args.stable else ' Dev'} - {label}" + descriptions[key] = {"label": label, "purpose": purpose, "parts": len(chunks)} groups.append({"key": key, "tag": tag, "title": title, "prerelease": not args.stable, "records": chunk}) if extras and not any(g['key'] == 'utility' for g in groups): + label, purpose = release_group_description("utility", [], extras) groups.append({'key': 'utility', 'tag': 'utility-' + base_tag, - 'title': f'MeshCore {args.version} - Migration utilities', + 'title': f'MeshCore {args.version}{"" if args.stable else " Dev"} - {label}', 'prerelease': not args.stable, 'records': []}) - links = "\n".join(f"- [{g['key']}](https://github.com/{args.repo}/releases/tag/{g['tag']})" for g in groups) + descriptions["utility"] = {"label": label, "purpose": purpose, "parts": 1} + links = "\n".join(f"- [{descriptions[g['key']]['label']}](https://github.com/{args.repo}/releases/tag/{g['tag']})" + for g in groups) source_url = f"https://github.com/{args.repo}/blob/{args.commit}" + owner, repository = args.repo.split("/", 1) + docs_url = f"https://{owner}.github.io/" + ("" if repository.lower() == owner.lower() + ".github.io" + else quote(repository, safe="") + "/") + release_guide = docs_url + f"releases/{args.version}/" rows = [] for group in groups: destination = args.output / group["key"] @@ -656,15 +722,26 @@ def main(): partial_note = (f"**Partial matrix:** {len(failures)} build attempt(s) failed and await repair. " f"Only individually qualified firmware is published. [Missing builds]({report_url}).\n\n") channel = 'Stable release' if args.stable else 'Development prerelease' - body = (f"> **{channel}.** Firmware identifier: **{base_tag}**.\n\n" - f"# MeshCore {args.version} - USA Cascade\n\n" - f"Source: `{args.commit}`. USA/Canada: **{radio['frequency']} MHz, BW{radio['bandwidth']}, SF{radio['sf']}, CR{radio['cr']}**; Cascade defaults.\n\n" - f"This page contains **{len(summaries)} qualified firmware profiles**. Full Companions send MOTA and normally update over USB. Infrastructure has a verified wireless update path; see each capability manifest.\n\n" - f"[Feature on/off and update directions]({source_url}/docs/full_companion_features.md) | " - f"[Release details]({source_url}/docs/releases/{args.version}.md)\n\n" - "Match the exact board, radio, display, and storage variant. ESP32 WiFi updates use the application `.bin`; the merged image installs boot/partition data over USB. nRF52 firmware `.zip` files are native application DFU packages. Separate `with-bootloader-2.4.12` firmware embeds its exact-board OTAFIX image and checks it for installation after 70 seconds; ordinary firmware preserves the installed bootloader. Standalone bootloader downloads are separate utilities. LoRa MOTA installation requires an exact destination package and matching bootloader. Full Companions do not LoRa-install onto themselves.\n\n" - "Firmware and capability checks passed in the build matrix. Hardware update testing across every board was not performed.\n\n" - + partial_note + portable_note + links + "\n") + description = descriptions[group["key"]] + split_note = ("These pages are split for download limits and contain the same firmware version; " + "the picker finds the correct page for your board.\n\n" if description["parts"] > 1 else "") + body = (f"[Open the firmware picker]({docs_url}firmware_picker/) | " + f"[What's new in {args.version}]({release_guide})\n\n" + f"# {group['title']}\n\n" + f"> **{channel}.** USA Cascade.\n\n" + + description["purpose"] + "\n\n" + "Select the exact board, radio, storage and role in the picker.\n\n" + + split_note + partial_note + portable_note + + f"[Installation and upgrade guide]({release_guide}) | " + f"[Feature controls]({docs_url}role_feature_switches/) | " + f"[Full Companion guide]({docs_url}full_companion_features/)\n\n" + "**Testing:** Published images passed build, capability and memory checks. " + "Physical coverage varies by board and update route; " + f"see the [release guide]({release_guide}).\n\n" + "Other firmware and tools:\n\n" + links + "\n\n" + f"USA Cascade defaults: **{radio['frequency']} MHz, BW{radio['bandwidth']}, " + f"SF{radio['sf']}, CR{radio['cr']}**. Saved radio settings take precedence.\n\n" + f"Firmware identifier: `{base_tag}`. Source: `{args.commit}`.\n") (args.output / (group["key"] + "-notes.md")).write_text(body) (destination / "BUILD-NOTES.txt").write_text(body) picker = ("" diff --git a/test/test_release_publication.py b/test/test_release_publication.py index cd3c01d29..aafca0032 100644 --- a/test/test_release_publication.py +++ b/test/test_release_publication.py @@ -326,6 +326,111 @@ class ReleasePublicationTest(unittest.TestCase): self.assertRaisesRegex(ValueError, "another source"): package.main() + def test_page_descriptions_follow_actual_platforms_roles_and_tool_types(self): + def record(target, platform="ESP32_PLATFORM", methods=("lora",)): + return {"manifest": {"target": target, "platform": platform, + "ota_update_methods": list(methods)}} + + nrf = [record("sample_repeater_lora_ota", "NRF52_PLATFORM"), + record("sample_room_server_lora_ota", "NRF52_PLATFORM"), + record("sample_sensor_lora_ota", "NRF52_PLATFORM")] + label, purpose = package.release_group_description("lora-ota", nrf) + self.assertEqual(label, "nRF52 Repeater, Room Server and Sensor firmware") + self.assertIn("LoRa OTA reception", purpose) + # Historical matrices also had ESP32 and mixed-platform LoRa pages. + esp = [record("legacy_repeater_lora_ota")] + self.assertEqual(package.release_group_description("lora-ota", esp)[0], "ESP32 Repeater firmware") + self.assertEqual(package.release_group_description("lora-ota", esp + nrf)[0], "LoRa OTA firmware") + self.assertNotIn("LoRa", package.release_group_description("lora-ota", [ + record("legacy_repeater_lora_ota", methods=("wifi",))])[1]) + full = [record("sample_repeater"), record("sample_room_server"), record("sample_sensor")] + self.assertEqual(package.release_group_description("full-profiles", full)[0], + "ESP32 Repeater, Room Server and Sensor firmware") + for files, title in (([Path("board-migration.zip")], "Migration tools"), + ([Path("board_bootloader.hex")], "Bootloader tools"), + ([Path("board-migration.zip"), Path("board_bootloader.hex")], + "Migration and bootloader tools")): + with self.subTest(title=title): + self.assertEqual(package.release_group_description("utility", [], files)[0], title) + utility = [record("sample_sensor"), record("sample_terminal_chat")] + self.assertEqual(package.release_group_description("utility", utility)[0], "Sensor and Terminal firmware") + self.assertEqual(package.release_group_description("utility", utility, [Path("board-migration.zip")])[0], + "Sensor and Terminal firmware and upgrade tools") + + def test_staged_notes_lead_with_picker_and_link_human_role_choices(self): + with tempfile.TemporaryDirectory() as temp: + base = Path(temp) + companion, companion_files = self.record(base / "input", "Station_G2_companion_radio_full") + repeater, repeater_files = self.record(base / "input", "Station_G2_repeater", build_profile="full") + records = [{"manifest": companion, "files": companion_files}, + {"manifest": repeater, "files": repeater_files}] + extras = [base / "board-migration.zip", base / "board_bootloader.hex"] + for path in extras: + path.write_bytes(b"exact-board tool fixture\n") + before = {path: path.read_bytes() for path in companion_files + repeater_files + extras} + output = base / "stage" + argv = ["package", "--input", str(base / "input"), "--output", str(output), + "--local-release", "--stable", "--version", "1.17.1.9", "--commit", SOURCE] + with patch.object(sys, "argv", argv), patch.object(package, "collect_local_release", + return_value=(records, extras, + {"frequency": "910.525", "bandwidth": "62.5", "sf": "7", "cr": "5"})): + package.main() + plan = json.loads((output / "release-plan.json").read_text()) + self.assertEqual([group["key"] for group in plan["groups"]], ["companion", "full-profiles", "utility"]) + guide = "https://mikecarper.github.io/MeshCore/releases/1.17.1.9/" + for group in plan["groups"]: + notes = (output / (group["key"] + "-notes.md")).read_text(encoding="ascii") + self.assertTrue(notes.startswith("[Open the firmware picker](https://mikecarper.github.io/MeshCore/firmware_picker/)")) + self.assertIn("[What's new in 1.17.1.9](" + guide + ")", notes) + self.assertIn("[Installation and upgrade guide](" + guide + ")", notes) + self.assertIn("[Companion firmware](https://github.com/mikecarper/MeshCore/releases/tag/" + FAMILY + ")", notes) + self.assertIn("[ESP32 Repeater firmware](https://github.com/mikecarper/MeshCore/releases/tag/full-profiles-" + FAMILY + ")", notes) + self.assertIn("[Migration and bootloader tools](https://github.com/mikecarper/MeshCore/releases/tag/utility-" + FAMILY + ")", notes) + self.assertNotIn("[full-profiles]", notes) + self.assertNotIn("qualified firmware profiles", notes) + self.assertIn("**Testing:**", notes) + self.assertIn("Physical coverage varies by board and update route", notes) + self.assertIn("Saved radio settings take precedence", notes) + self.assertIn(SOURCE, notes) + self.assertIn(FAMILY, notes) + self.assertEqual((output / group["key"] / "BUILD-NOTES.txt").read_text(), notes) + for line in (output / group["key"] / "SHA256SUMS.txt").read_text().splitlines(): + expected, name = line.split(" ", 1) + self.assertEqual(hashlib.sha256((output / group["key"] / name).read_bytes()).hexdigest(), expected) + self.assertEqual({path: path.read_bytes() for path in before}, before) + + def test_split_pages_keep_tags_and_explain_one_version_with_human_links(self): + with tempfile.TemporaryDirectory() as temp: + base = Path(temp) + records = [] + for index in range(3): + target = f"board_{index}_repeater_lora_ota" + row, files = self.record(base / "input", target, platform="NRF52_PLATFORM") + # Exercise the real 900-file split with many tiny report assets. + for number in range(447): + path = base / "input" / f"{target}-report-{number}.txt" + path.write_bytes(b"qualification report fixture\n") + files.append(path) + self.assertEqual(len(files), 451) + records.append({"manifest": row, "files": files}) + output = base / "stage" + argv = ["package", "--input", str(base / "input"), "--output", str(output), + "--local-release", "--stable", "--version", "1.17.1.9", "--commit", SOURCE] + with patch.object(sys, "argv", argv), patch.object(package, "collect_local_release", + return_value=(records, [], + {"frequency": "910.525", "bandwidth": "62.5", "sf": "7", "cr": "5"})): + package.main() + plan = json.loads((output / "release-plan.json").read_text()) + self.assertEqual([group["key"] for group in plan["groups"]], ["lora-ota", "lora-ota-2", "lora-ota-3"]) + for number, group in enumerate(plan["groups"], 1): + self.assertEqual(group["tag"], group["key"] + "-" + FAMILY) + self.assertEqual(group["title"], f"MeshCore 1.17.1.9 - nRF52 Repeater firmware (part {number} of 3)") + notes = (output / (group["key"] + "-notes.md")).read_text(encoding="ascii") + self.assertIn("split for download limits and contain the same firmware version", notes) + for other in range(1, 4): + self.assertIn(f"[nRF52 Repeater firmware (part {other} of 3)]", notes) + self.assertEqual(group["asset_count"], 457) + def test_local_release_preserves_selected_profiles_and_verifies_complete_inventory(self): with tempfile.TemporaryDirectory() as temp: directory = Path(temp)