From 8f413b0610869a77eeaed8d067a1b068e319f804 Mon Sep 17 00:00:00 2001 From: mikecarper Date: Mon, 31 Aug 2026 18:29:38 -0700 Subject: [PATCH] Add SenseCAP Indicator N16R2 profile --- build.sh | 17 +++++++++++-- scripts/esp32_full_partition.py | 1 + test/test_build_profiles.sh | 8 ++++++ test/test_esp32_full_partition.py | 25 +++++++++++++++++++ test/test_indicator_display_profile.py | 18 +++++++++++++ test/test_indicator_render_profile.py | 5 ++++ variants/sensecap_indicator-espnow/README.md | 21 ++++++++++------ .../dual_ota_6400k_preserve_spiffs.csv | 9 +++++++ .../sensecap_indicator-espnow/platformio.ini | 13 ++++++++++ 9 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 variants/sensecap_indicator-espnow/dual_ota_6400k_preserve_spiffs.csv diff --git a/build.sh b/build.sh index 28725174..3e57a211 100755 --- a/build.sh +++ b/build.sh @@ -480,6 +480,7 @@ for section, options in data: 'Heltec_T190_companion_radio_usb_ble_|Heltec_T190_companion_radio_full_' 'SenseCapIndicator-ESPNow_comp_radio_usb|SenseCapIndicator-ESPNow_companion_radio_full' 'SenseCapIndicator-LoRa_comp_radio_usb_wifi|SenseCapIndicator-LoRa_companion_radio_full' + 'SenseCapIndicator-LoRa-N16R2_comp_radio_usb_wifi|SenseCapIndicator-LoRa-N16R2_companion_radio_full' ) local -a qualified_nrf52_full_companion_bases=( GAT562_Mesh_Watch13_companion_radio_ble @@ -3420,6 +3421,13 @@ apply_companion_radio_full_profile() { # than to whichever historical USB/WiFi environment supplies the recipe. export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DINDICATOR_WIFI_FONT_RECOVERY=1" ;; + sensecapindicator-lora-n16r2_companion_radio_full) + export MESHCORE_ESP32_FULL_PARTITION_TABLE="variants/sensecap_indicator-espnow/dual_ota_6400k_preserve_spiffs.csv" + # Both physical Indicator radio layouts gain ordinary WiFi in their Full + # overlay. Keep font recovery tied to that effective capability rather + # than to whichever historical USB/WiFi environment supplies the recipe. + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DINDICATOR_WIFI_FONT_RECOVERY=1" + ;; esac # Both Indicator Full layouts select exactly one secondary Companion @@ -3428,7 +3436,8 @@ apply_companion_radio_full_profile() { # mode can release Bluetooth memory without disturbing ESP-NOW. case "${env_name,,}" in sensecapindicator-espnow_companion_radio_full|\ - sensecapindicator-lora_companion_radio_full) + sensecapindicator-lora_companion_radio_full|\ + sensecapindicator-lora-n16r2_companion_radio_full) export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DCOMPANION_EXCLUSIVE_WIFI_BLE=1 -DINDICATOR_TRANSPORT_RENDER_PROFILE=1 -DUI_WIFI_SETUP_HOME_PAGE=1 -DWEBCONFIG_AP_PREFIX='\"MC-Set\"'" ;; esac @@ -3438,7 +3447,8 @@ apply_companion_radio_full_profile() { # every other combination retains native 480x480 rendering. case "${env_name,,}" in sensecapindicator-espnow_companion_radio_full|\ - sensecapindicator-lora_companion_radio_full) + sensecapindicator-lora_companion_radio_full|\ + sensecapindicator-lora-n16r2_companion_radio_full) export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UUI_ZOOM -DUI_ZOOM=1.0f -UUI_COORD_SCALE -DUI_COORD_SCALE=3" ;; esac @@ -4166,6 +4176,9 @@ get_esp32_full_companion_replacement() { sensecapindicator-espnow_comp_radio_*) full_env=SenseCapIndicator-ESPNow_companion_radio_full ;; + sensecapindicator-lora-n16r2_comp_radio_*) + full_env=SenseCapIndicator-LoRa-N16R2_companion_radio_full + ;; sensecapindicator-lora_comp_radio_*) full_env=SenseCapIndicator-LoRa_companion_radio_full ;; diff --git a/scripts/esp32_full_partition.py b/scripts/esp32_full_partition.py index e40069bd..153976d4 100644 --- a/scripts/esp32_full_partition.py +++ b/scripts/esp32_full_partition.py @@ -9,6 +9,7 @@ import os # board's persistent data. PRESERVED_FULL_PARTITION_TABLES = { "variants/sensecap_indicator-espnow/dual_ota_2560k_preserve_spiffs.csv", + "variants/sensecap_indicator-espnow/dual_ota_6400k_preserve_spiffs.csv", } diff --git a/test/test_build_profiles.sh b/test/test_build_profiles.sh index b00cc6bb..db9e1854 100644 --- a/test/test_build_profiles.sh +++ b/test/test_build_profiles.sh @@ -339,6 +339,7 @@ Heltec_E290_companion_usb_ble|Heltec_E290_companion_radio_full|Heltec_E290_compa Heltec_T190_companion_radio_usb_ble_|Heltec_T190_companion_radio_full_|Heltec_T190_companion_radio_usb_ble_ SenseCapIndicator-ESPNow_comp_radio_usb|SenseCapIndicator-ESPNow_companion_radio_full|SenseCapIndicator-ESPNow_comp_radio_usb SenseCapIndicator-LoRa_comp_radio_usb_wifi|SenseCapIndicator-LoRa_companion_radio_full|SenseCapIndicator-LoRa_comp_radio_usb_wifi +SenseCapIndicator-LoRa-N16R2_comp_radio_usb_wifi|SenseCapIndicator-LoRa-N16R2_companion_radio_full|SenseCapIndicator-LoRa-N16R2_comp_radio_usb_wifi FULL_COMPANION_SPECS # The Full overlay, not the historical transport used as its build base, owns @@ -378,6 +379,13 @@ for full_env in "${SUPPORTED_PIO_ENVS[@]}"; do [[ "$PLATFORMIO_BUILD_FLAGS" == *"INDICATOR_WIFI_FONT_RECOVERY=1"* ]] \ || fail "$full_env omitted WiFi font recovery" ;; + sensecapindicator-lora-n16r2_companion_radio_full) + [ "${MESHCORE_ESP32_FULL_PARTITION_TABLE:-}" \ + = "variants/sensecap_indicator-espnow/dual_ota_6400k_preserve_spiffs.csv" ] \ + || fail "$full_env omitted the N16R2 preserve-SPIFFS table" + [[ "$PLATFORMIO_BUILD_FLAGS" == *"INDICATOR_WIFI_FONT_RECOVERY=1"* ]] \ + || fail "$full_env omitted WiFi font recovery" + ;; *) [ -z "${MESHCORE_ESP32_FULL_PARTITION_TABLE:-}" ] \ || fail "$full_env inherited another board's required partition table" diff --git a/test/test_esp32_full_partition.py b/test/test_esp32_full_partition.py index 04cd9d46..c10ffc28 100644 --- a/test/test_esp32_full_partition.py +++ b/test/test_esp32_full_partition.py @@ -18,6 +18,10 @@ INDICATOR_PARTITIONS = ( "variants/sensecap_indicator-espnow/" "dual_ota_2560k_preserve_spiffs.csv" ) +INDICATOR_N16R2_PARTITIONS = ( + "variants/sensecap_indicator-espnow/" + "dual_ota_6400k_preserve_spiffs.csv" +) class FakeBoardConfig: @@ -90,6 +94,27 @@ class Esp32FullPartitionTest(unittest.TestCase): self.assertEqual(board.values["build.flash_mode"], "dio") self.assertIn(INDICATOR_PARTITIONS, output) + def test_n16r2_retains_factory_six_megabyte_ota_slots(self): + profile = INDICATOR_PROFILE.read_text() + self.assertIn( + f"board_build.partitions = {INDICATOR_N16R2_PARTITIONS}", profile + ) + table = (ROOT / INDICATOR_N16R2_PARTITIONS).read_text() + self.assertIn("app0, app, ota_0, 0x10000, 0x640000", table) + self.assertIn("app1, app, ota_1, 0x650000, 0x640000", table) + self.assertIn("spiffs, data, spiffs, 0xc90000, 0x360000", table) + board, output = apply_policy( + { + "upload.flash_size": "16MB", + "build.mcu": "esp32s3", + "build.partitions": INDICATOR_N16R2_PARTITIONS, + } + ) + self.assertEqual( + board.values["build.partitions"], INDICATOR_N16R2_PARTITIONS + ) + self.assertIn(INDICATOR_N16R2_PARTITIONS, output) + def test_absolute_indicator_partition_path_is_also_preserved(self): absolute = str(ROOT / INDICATOR_PARTITIONS) board, _output = apply_policy( diff --git a/test/test_indicator_display_profile.py b/test/test_indicator_display_profile.py index 84e46f55..d067d272 100644 --- a/test/test_indicator_display_profile.py +++ b/test/test_indicator_display_profile.py @@ -36,6 +36,24 @@ def numeric_flag(name: str) -> float: class IndicatorDisplayProfileTest(unittest.TestCase): + def test_preserves_original_indicator_memory_profile(self): + profile = base_profile() + self.assertIn("board_build.arduino.memory_type = qio_opi", profile) + self.assertIn("board_build.psram_type = opi", profile) + self.assertIn("board_upload.flash_size = 8MB", profile) + + def test_n16r2_profile_uses_quad_psram_and_full_flash(self): + profile = PROFILE.read_text() + start = profile.index( + "[env:SenseCapIndicator-LoRa-N16R2_comp_radio_usb_wifi]" + ) + n16r2 = profile[start:] + self.assertIn("board_build.arduino.memory_type = qio_qspi", n16r2) + self.assertIn("board_build.psram_type = qspi", n16r2) + self.assertIn("board_upload.flash_size = 16MB", n16r2) + self.assertIn("board_upload.maximum_size = 16777216", n16r2) + self.assertIn("dual_ota_6400k_preserve_spiffs.csv", n16r2) + def test_preserves_logical_ui_and_fills_panel(self): panel_size = 480 zoom = numeric_flag("UI_ZOOM") diff --git a/test/test_indicator_render_profile.py b/test/test_indicator_render_profile.py index 6bfcb84b..37ccf3e3 100644 --- a/test/test_indicator_render_profile.py +++ b/test/test_indicator_render_profile.py @@ -318,6 +318,11 @@ printf '%s\n' "$PLATFORMIO_BUILD_FLAGS" "SenseCapIndicator-LoRa_companion_radio_full'", build, ) + self.assertIn( + "'SenseCapIndicator-LoRa-N16R2_comp_radio_usb_wifi|" + "SenseCapIndicator-LoRa-N16R2_companion_radio_full'", + build, + ) if __name__ == "__main__": diff --git a/variants/sensecap_indicator-espnow/README.md b/variants/sensecap_indicator-espnow/README.md index 9f18de06..d0e0a79f 100644 --- a/variants/sensecap_indicator-espnow/README.md +++ b/variants/sensecap_indicator-espnow/README.md @@ -178,10 +178,17 @@ agreeing sources from signed adverts or authenticated Public-channel messages; one repeated sender cannot set the clock alone. A successful Companion or local CLI time update always wins and disables the LoRa fallback until reboot. -The combined image uses two 2.5 MiB OTA application slots. Its partition map -keeps the prior NVS and SPIFFS addresses, so installing the partition table and -application over an existing Indicator preserves identity, channels, radio -settings, and saved WiFi credentials. +The original combined image uses two 2.5 MiB OTA application slots. Its +partition map keeps the prior NVS and SPIFFS addresses, so installing the +partition table and application over an existing Indicator preserves identity, +channels, radio settings, and saved WiFi credentials. + +Some later LoRa units, including Mercer, report an ESP32-S3-WROOM-1-N16R2: +16 MiB flash with 2 MiB quad PSRAM. Use +`SenseCapIndicator-LoRa-N16R2_companion_radio_full` for those boards. Its +factory-compatible map provides two 6.25 MiB OTA slots while retaining the +NVS, SPIFFS, and coredump offsets. Do not install this image on the original +8 MiB/octal-PSRAM Indicator; PSRAM line mode cannot be runtime-detected. ## SD-backed font @@ -194,9 +201,9 @@ per second. See [`../../tools/sensecap_indicator_rp2040/README.md`](../../tools/sensecap_indicator_rp2040/README.md) for installation and diagnostics. -Both Full Companion layouts, `SenseCapIndicator-LoRa_companion_radio_full` and -`SenseCapIndicator-ESPNow_companion_radio_full`, can repair a missing, corrupt, -or older font after station WiFi connects. The LoRa USB/WiFi base profile has +All Indicator Full Companion layouts, including the N16R2 variant, can repair +a missing, corrupt, or older font after station WiFi connects. The LoRa +USB/WiFi base profile has the same recovery support; USB-only images omit it. Startup never waits for the network: the UI uses its built-in font while a bounded background task fetches the 1,302,608-byte asset from GitHub's official, content-addressed Git Blob REST diff --git a/variants/sensecap_indicator-espnow/dual_ota_6400k_preserve_spiffs.csv b/variants/sensecap_indicator-espnow/dual_ota_6400k_preserve_spiffs.csv new file mode 100644 index 00000000..c29bfb44 --- /dev/null +++ b/variants/sensecap_indicator-espnow/dual_ota_6400k_preserve_spiffs.csv @@ -0,0 +1,9 @@ +# Factory-compatible 16 MiB SenseCAP Indicator layout. +# Keep every offset stable across application and merged-image updates. +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x640000, +app1, app, ota_1, 0x650000, 0x640000, +spiffs, data, spiffs, 0xc90000, 0x360000, +coredump, data, coredump,0xff0000, 0x10000, diff --git a/variants/sensecap_indicator-espnow/platformio.ini b/variants/sensecap_indicator-espnow/platformio.ini index 3fa4d329..eb4a289f 100644 --- a/variants/sensecap_indicator-espnow/platformio.ini +++ b/variants/sensecap_indicator-espnow/platformio.ini @@ -169,3 +169,16 @@ build_src_filter = + lib_deps = ${env:SenseCapIndicator-LoRa_comp_radio_usb.lib_deps} + +; Later Indicator LoRa boards can carry an ESP32-S3-WROOM-1-N16R2 instead of +; the original 8 MiB flash / 8 MiB octal-PSRAM arrangement. Keep this as an +; explicit image: PSRAM line mode is selected before application startup and +; cannot be safely auto-detected by one binary. +[env:SenseCapIndicator-LoRa-N16R2_comp_radio_usb_wifi] +extends = env:SenseCapIndicator-LoRa_comp_radio_usb_wifi +board_build.arduino.memory_type = qio_qspi +board_build.flash_mode = qio +board_build.psram_type = qspi +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 +board_build.partitions = variants/sensecap_indicator-espnow/dual_ota_6400k_preserve_spiffs.csv