From fce419b335e33066d5f1cbea56542e36b230e3f5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 06:43:06 -0500 Subject: [PATCH 01/21] Upgrade trunk (#10476) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index ad264bd76..9c4409b8a 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,7 +8,7 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.528 + - checkov@3.2.529 - renovate@43.150.0 - prettier@3.8.3 - trufflehog@3.95.3 From 1c05633fcdd91b2d5688930f2cd7153108c7d8c6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 15 May 2026 05:59:15 -0500 Subject: [PATCH 02/21] Add more support for small fonts in screen resolution determination (#10480) --- src/graphics/SharedUIDisplay.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index ec50654ae..032b14dfa 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -27,6 +27,12 @@ ScreenResolution determineScreenResolution(int16_t screenheight, int16_t screenw return ScreenResolution::UltraLow; } +#ifdef DISPLAY_FORCE_SMALL_FONTS + if (screenwidth <= 160 && screenheight <= 80) { + return ScreenResolution::Low; + } +#endif + // Standard OLED screens if (screenwidth > 128 && screenheight <= 64) { return ScreenResolution::Low; From 05707079bd218c0e7ca6968d3006e96a475e7528 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 19:30:04 -0500 Subject: [PATCH 03/21] Update libch341-spi-userspace digest to 2e5ff75 (#10485) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/native/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index 9ab45d1ab..2fa8e8658 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -29,7 +29,7 @@ lib_deps = # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.21 ; # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main - https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip + https://github.com/pine64/libch341-spi-userspace/archive/2e5ff751d0c39667993df672cb683740ed5c9394.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main From 502c5af524a2338e5afe834c3a401c8c99b03312 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 20:03:50 -0500 Subject: [PATCH 04/21] Upgrade trunk (#10481) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 9c4409b8a..e055a6d50 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,7 +4,7 @@ cli: plugins: sources: - id: trunk - ref: v1.9.0 + ref: v1.10.0 uri: https://github.com/trunk-io/plugins lint: enabled: @@ -16,7 +16,7 @@ lint: - bandit@1.9.4 - trivy@0.70.0 - taplo@0.10.0 - - ruff@0.15.12 + - ruff@0.15.13 - isort@8.0.1 - markdownlint@0.48.0 - oxipng@10.1.1 From 4a1ff18f57b06fd2a88895b5e2cb749e8adafd32 Mon Sep 17 00:00:00 2001 From: Carlos Valdes Date: Sat, 16 May 2026 13:16:11 +0200 Subject: [PATCH 05/21] feat: add Nordic nRF54L15-DK variant (Zephyr + BLE + LoRa) (#10193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add Nordic nRF54L15-DK variant (Zephyr + BLE + LoRa) Adds a community hardware variant for the Nordic nRF54L15-DK (PCA10156) with an external EBYTE E22-900M30S (SX1262) LoRa module. First Meshtastic port running on the Zephyr RTOS; all other Nordic targets use the nRF5 SoftDevice stack. Scope ----- - New Zephyr-based platform layer under src/platform/nrf54l15/ providing Arduino-compatible shims (Arduino.h, SPI, Wire, Print, Stream) over the Zephyr APIs plus a LittleFS-backed InternalFileSystem on SPIM20. - Bluetooth LE peripheral (NRF54L15Bluetooth.*) built on the Zephyr BT host stack, exposing the Meshtastic GATT service with legacy connectable advertising, just-works pairing, dynamic MTU exchange (up to 247 bytes), and iOS connection-parameter tweaks. - Variant directory variants/nrf54l15/nrf54l15dk/ with pin map for the E22 module on connector J1, PlatformIO env (nrf54l15dk), Zephyr DT overlay and a wiring README. - Zephyr project config (zephyr/prj.conf + board overlay) tuned for BT + LoRa: 16 KB main stack, 4 KB BT RX thread, RTT logging in immediate mode, newlib-nano heap sized to leave room for the GATT pools while still allowing ATT MTU=247. - extra_scripts/nrf54l15_linker.py works around a PlatformIO + old Ninja issue where Zephyr's two-pass linker script generation does not run automatically; the post-script parses build.ninja and invokes the gcc -E step directly before the final link. - boards/nrf54l15dk.json board definition (PlatformIO needs it for the DK; the Seeed platform only ships the XIAO variants). - variants/rp2350/rp2350.ini excludes platform/nrf54l15/ from RP2350 build_src_filter so the shared platform tree does not leak between targets. - .gitignore: add nRF J-Link / RTT debug artifacts (flash.jlink, rtt_*.txt). Shared source changes --------------------- - src/main.{cpp,h}, src/RedirectablePrint.cpp, src/FSCommon.{cpp,h}, src/mesh/{Channels,NodeDB,RadioLibInterface,MeshService,PhoneAPI}.cpp, src/mesh/RadioLibInterface.h, src/modules/AdminModule.cpp: add small guards / helpers so the Zephyr build compiles alongside the Arduino targets. Behavior on existing boards is unchanged. Hardware model -------------- HW_VENDOR maps to meshtastic_HardwareModel_PRIVATE_HW until a dedicated protobuf enum value is assigned upstream. The variant declares custom_meshtastic_hw_model = 132 so the maintainers can wire the new enum value through the protobufs repo after merge. Hardware note ------------- The E22-900M30S does not connect its DIO2 pin to TXEN internally — a wire/solder bridge between DIO2 and TXEN on the module is required for TX to work. Details and full pin map are in the variant README. Validation ---------- Built clean against develop. On real hardware (April 2026) the port passes end-to-end: iOS companion app pairs and connects, configuration round-trip works, LoRa TX/RX reaches a canonical tbeam on the same mesh channel, NodeDB updates propagate both ways, and traceroute completes. * fix(nrf54l15): use atomic fs_rename instead of copy fallback Zephyr LittleFS on nrf54l15 supports fs_rename natively, so route it through the same atomic path as ESP32. The previous copyFile+remove fallback truncated the destination before copying, leaving 0-byte files if interrupted mid-write. Co-Authored-By: Claude Opus 4.7 (1M context) * fix(nrf54l15): expand storage_partition from 36KB to 700KB LittleFS on the default 9-block (36KB) storage_partition ran out of space during copy-on-write of config.proto, causing fs_write to return ENOSPC and pb_encode to surface "io error" when saving configuration via the mobile app. Reclaim slot1_partition (the MCUboot secondary slot — unused since we flash directly via J-Link) and grow storage_partition to span 0xb6000..0x165000 (~175 blocks). Co-Authored-By: Claude Opus 4.7 (1M context) * fix(nrf54l15): drop USERPREFS_LORACONFIG_* so LoRa config stays mutable NodeDB rewrites LoRa config from USERPREFS_LORACONFIG_* on every boot, which prevented reconfiguration via the BLE/serial app. Drop the variant-level defaults; users configure region and modem preset through the app like every other variant. Co-Authored-By: Claude Opus 4.7 (1M context) * fix(nrf54l15): enforce MITM passkey pairing on GATT service - Add MESH_PERM_READ/MESH_PERM_WRITE macros (READ_AUTHEN/WRITE_AUTHEN) on all mesh service characteristics so clients must complete passkey exchange before accessing fromNum/fromRadio/toRadio/logRadio. - Wire FIXED_PIN mode to bt_passkey_set() so the device advertises a known PIN (config.bluetooth.fixed_pin); RANDOM_PIN keeps default per-pairing random passkey. - Reduce BleDeferredThread HARD_WATCHDOG_MS from 3min to 1min. - prj.conf: CONFIG_BT_SMP_ENFORCE_MITM=y, CONFIG_BT_FIXED_PASSKEY=y, CONFIG_BT_SMP_SC_PAIR_ONLY=n (legacy fallback for clients that abort SC pairing with reason 0x01 within 150ms). * fix(nrf54l15): resolve develop-merge conflict + cppcheck warnings The `Merge branch 'develop'` left two ~RadioLibInterface() declarations in src/mesh/RadioLibInterface.h: the inline version added upstream by PR #10254 (which independently applied the same UAF guard this PR was carrying) and the out-of-line version this PR introduced. GCC rejects the duplicate, breaking every platform build. Drop the out-of-line declaration + definition; keep upstream's inline form. Also silence the 13 cppcheck low warnings introduced by the new nrf54l15 Arduino shim — Arduino's `String`/`SPISettings` API contract relies on implicit single-arg constructors used pervasively by existing Meshtastic code, so suppress `noExplicitConstructor` inline with a comment instead of breaking the API. The few mechanical wins (`const tmp[2]`, `const uint32_t *sp`) are applied directly. * fmt: fix Trunk Check lint issues on nrf54l15-port - extra_scripts/nrf54l15_linker.py: move regular imports above Import("env") to silence E402, add trunk-ignore-all(F821) for the PIO/SCons SConstruct injection (matches esp32_pre.py / nrf52_extra.py convention) - src/platform/nrf54l15/NRF54L15Bluetooth.cpp: clang-format 16.0.3 - boards/nrf54l15dk.json + variants/nrf54l15/nrf54l15dk/README.md: prettier 3.8.3 (also resolves markdownlint MD060 on README tables) No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) * fix(nrf54l15): address Copilot review comments + correct clang-format style Six review threads from the 2026-04-30 Copilot review: - src/platform/nrf54l15/nrf54l15_main.cpp: validate PSP against the nRF54L15 SRAM range (0x20000000..0x20040000) and 4-byte alignment before walking the faulting thread's stack, and clamp the walk so it never reads past the end of RAM. Prevents a second fault inside the fatal handler when PSP is corrupted (common in real faults). - src/platform/nrf54l15/nrf54l15_arduino.cpp: gate the bring-up printk traces in digitalWrite/digitalRead (CS/NRESET toggle log, BUSY-before-NRESET snapshot, BUSY periodic timeline) behind a new -DNRF54L15_GPIO_DEBUG flag that is off by default. The "dev NOT READY" message stays unconditional — it indicates a genuine hardware/DTS misconfig. - src/modules/AdminModule.cpp: don't mutate config.device.output_gpio_enabled from handleGetConfig(). Reflect the live pin state in the response payload only — a getter must not write back to disk-persisted state. - src/platform/nrf54l15/InternalFileSystem.h: derive totalBytes() from FIXED_PARTITION_SIZE(storage_partition) at compile time so it tracks the DK overlay's ~700 KB partition instead of the stale 36 KB hard-coded value. Updated the file header comment accordingly. - extra_scripts/nrf54l15_linker.py: make _extract_gcc_command() handle the POSIX Ninja COMMAND format (no `cmd.exe /C "..."` wrapper) in addition to the Windows form, so the script doesn't hard-fail on Linux/macOS hosts. - src/platform/nrf54l15/NRF54L15Bluetooth.cpp: clamp NO_PIN to RANDOM_PIN with a one-shot LOG_WARN. The mesh GATT permissions are declared with BT_GATT_PERM_*_AUTHEN and prj.conf sets CONFIG_BT_SMP_ENFORCE_MITM=y, so NO_PIN with no auth callbacks would leave every characteristic returning BT_ATT_ERR_AUTHENTICATION. Falling back to RANDOM_PIN keeps the link usable instead of silently broken. Also re-formatted this file with the project's .trunk/configs/.clang-format (Linux braces, 4-space indent, 130-col) — the previous lint-fix commit a2aca3234 accidentally used the default LLVM style here, which CI's clang-format would have rejected. Build verified: pio run -e nrf54l15dk passes, RAM 47.4%, Flash 28.6%. Co-Authored-By: Claude Opus 4.7 (1M context) * fix(nrf54l15): address remaining Copilot review threads Round 2/3 review fixes — bugs first, then docs/portability: BLE concurrency (NRF54L15Bluetooth.cpp): - onNowHasData / sendLog / BleDeferredThread / shutdown: acquire active_conn under ble_mutex via new acquire_active_conn() helper so disconnected_cb can't free the conn between the null check and bt_conn_ref/bt_gatt_notify (use-after-unref). - write_toradio: reject writes that exceed MAX_TO_FROM_RADIO_SIZE with ATT_ERR_INVALID_ATTRIBUTE_LEN instead of returning success and silently dropping the payload (would hide failed config writes from the phone). - start_advertising: truncate the device name to fit the 31-byte legacy scan-response limit and switch to BT_DATA_NAME_SHORTENED so bt_le_adv_start() doesn't reject the payload when the name approaches CONFIG_BT_DEVICE_NAME_MAX=32. Linker / portability: - main.h: drop the rp2040Loop() forward declaration that had no definition and no callers — would surface as a link error if any RP2040 build added a call to the symbol. - nrf54l15_arduino.cpp: transfer16() now uses static __aligned(4) DMA buffers (matching transfer()), removing the EasyDMA-reachability hazard of caller-stack buffers on this part. Filesystem (InternalFileSystem.h): - usedBytes(): return real usage from fs_statvfs() instead of 0 so OTA / range-test free-space guards work. - rewindDirectory(): close the dir before reopening — Zephyr fs_dir_t has no rewind, and re-fs_opendir on an open handle leaks LittleFS state. Crash handler (nrf54l15_main.cpp): - After the stack walk, busy-wait 50 ms to flush RTT/printk and call sys_reboot(SYS_REBOOT_COLD) directly so the saved_crash record is actually reported on the next boot. Default Zephyr config has RESET_ON_FATAL_ERROR=n, so the previous k_fatal_halt() spun forever. Generalization / config: - PhoneAPI.cpp: replace the NRF54L15_DK ifdef with a MESHTASTIC_EXCLUDE_FILES_MANIFEST capability flag (defined in the nrf54l15dk env) so future variants can opt in/out without touching shared code. - variants/nrf54l15/nrf54l15.ini: parameterize libdeps include paths via ${PIOENV} so additional nRF54L15 envs sharing nrf54l15_base don't break. - prj.conf: drop the stale "36 KB storage_partition" comments — the DK overlay reclaims slot1 to ~700 KB and runtime size comes from FIXED_PARTITION_SIZE. - nrf54l15dk overlay: remove the zephyr,console / zephyr,shell-uart chosen entries that conflicted with CONFIG_UART_CONSOLE=n + RTT console; keep uart30 enabled so swapping the console is one Kconfig flip away. Build: nrf54l15dk SUCCESS (flash 28.6%, RAM 47.4%); wiznet_5500_evb_pico2 SUCCESS (verifies the shared main.h change). Co-Authored-By: Claude Opus 4.7 (1M context) * fix(nrf54l15dk): use PRIVATE_HW (255) for custom_meshtastic_hw_model Per @thebentern's review: the nRF54L15-DK is a development kit, not a canonical Meshtastic SKU, so it falls under HardwareModel::PRIVATE_HW (255) — the same enum value already used at runtime via HW_VENDOR. The placeholder 132 is removed; no dedicated enum number will be assigned for DK boards. Slug stays NRF54L15_DK as a human-readable identifier. Co-Authored-By: Claude Opus 4.7 (1M context) * fix(nrf54l15): unstarve bt_long_wq so SC pairing completes bt_pub_key_gen() runs the ECC P256 key generation on bt_long_wq. At default prio=10 (preemptible) and stack=1400 it gets starved by Meshtastic app threads at boot — sc_public_key stays NULL for minutes, smp_public_key() defers with SMP_FLAG_PKEY_SEND, and every SC pairing attempt stalls right after the public-key exchange. iOS shows "Connecting…" forever with no PIN prompt; bleak/CLI fails the first CCC notify write with "Protocol Error 0x05: Insufficient Authentication". Set CONFIG_BT_LONG_WQ_PRIO=0 (highest preemptible, ties with main) and CONFIG_BT_LONG_WQ_STACK_SIZE=4096 (margin for the P256M driver frames). Validated E2E with iOS Meshtastic app: bt_smp_pkey_ready fires within ~40 s of boot, 20 SC Passkey Entry rounds complete with matching pcnf/cfm, encrypt 0x01 / sec_level 0x04 (Authenticated MITM), bonded=1. * feat(nrf54l15): hardware I2C bus via TWIM30 + sensor telemetry Adds the Arduino TwoWire layer for the nRF54L15-DK so Meshtastic's sensor drivers can talk to external I2C devices over the hardware TWIM30 peripheral. Bus binding: - &uart30 disabled in the board overlay (peripheral instance 30 is shared between UARTE30 / TWIM30 / SPIM30 — pick one). Console stays on RTT via CONFIG_RTT_CONSOLE. - New i2c30_default / i2c30_sleep pinctrl with SDA=P0.03 / SCL=P0.04. External 4.7 kOhm pull-ups required on both lines. - &i2c30 enabled at I2C_BITRATE_FAST (400 kHz). - button_3 (SW3, P0.04) deleted from DTS so the pad can be claimed by i2c30 pinctrl; SW3 is still wired to the pad on the DK, do not press it during I2C use or it will short SCL to GND. Arduino layer: - src/platform/nrf54l15/Wire.cpp resolves the DT node at compile time via DEVICE_DT_GET(DT_NODELABEL(i2c30)) and dispatches Arduino's beginTransmission / write / endTransmission / requestFrom to i2c_write / i2c_write_read / i2c_read. Buffer is sized to 256 bytes for forward compatibility with the SE050 secure element on the custom PCB. - Wire.h drops the prior compile-only stubs and exposes the real TwoWire surface. - Arduino.h: BitOrder becomes an enum (not #define) so Adafruit_BusIO's `typedef BitOrder BusIOBitOrder;` compiles. Variant + build flags: - nrf54l15.ini flips HAS_WIRE / HAS_SENSOR / HAS_TELEMETRY from 0 to 1 and cherry-picks the sensor libs Meshtastic needs (BusIO, Sensor, BMP280, BME280, INA219/226/260/3221, SHT4X). The full environmental_base group is avoided because it pulls Adafruit_SSD1306 / Adafruit_GFX which rely on Arduino pin macros the Zephyr shim does not implement. - nrf54l15dk variant.h defines PIN_WIRE_SDA / PIN_WIRE_SCL for parity with the Arduino convention used by other variants. The actual bus wiring is fixed by the overlay pinctrl above. Validated 2026-05-14/15 on the DK with BMP280 @ 0x76 (temperature + barometric pressure) and INA3221 @ 0x42 (rail voltage / current); EnvironmentTelemetry / PowerTelemetry packets transmit successfully over LoRa. Footprint cost on nrf54l15dk: +45 KB flash, +1.7 KB RAM. * feat(nodedb): honor USERPREFS for environment telemetry on first boot installDefaultConfig() now respects two new compile-time prefs: USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED The mobile apps enforce a 30 min floor on environment_update_interval in the settings UI, which makes short-interval bring-up testing of new sensor hardware painful — you have to wait half an hour for the first LoRa packet to confirm wiring + driver. With these prefs baked into the variant, the firmware can ship a freshly-flashed device that broadcasts on a shorter cadence (e.g. 900 s) the moment storage_partition is empty. Both prefs are gated on #ifdef so the behavior is unchanged for any variant that does not opt in. Documented in userPrefs.jsonc with the existing telemetry-interval pref block. * fix(nrf54l15): allow multiple bonded BLE peers CONFIG_BT_MAX_PAIRED defaults to 1, so once the first peer (e.g. an iOS phone) has paired and bonded, every subsequent pairing attempt from a different MAC fails inside bt_keys_get_addr() with no free key slot — the host returns BT_SECURITY_ERR_KEY_DOES_NOT_EXIST and the second peer never gets past SMP. Raise the slot count to 4 so the device can simultaneously hold an iOS phone, a Windows host, a Linux host, and one spare bond. Add BT_KEYS_OVERWRITE_OLDEST so that once the table fills, the LRU peer is evicted on the next pairing rather than rejecting the new peer. This matches the behavior other Meshtastic ports already provide (nRF52 uses CONFIG_BT_PERIPHERAL_PRIO_CONN with similar semantics). Discovered while bringing up the Python CLI on Windows alongside the existing iOS bond. * fix(nrf54l15): zero-initialize TwoWire buffers + clang-format Wire cppcheck on every CI target (esp32s3, rp2040, rp2350, nrf52840, ...) was failing the build with two `uninitMemberVar` warnings on TwoWire's constructor: `txBuf` and `rxBuf` (256-byte arrays) were not initialized. Even though the buffers are only read after txLen/rxLen is set, leaving them uninitialized is a footgun if any future caller bypasses the len-set step. Use C++11 value-initialization in the member initializer list — costs ~512 B of memset at boot, gains a clean cppcheck pass and defensive-against-future-changes semantics. Also reformat Wire.{cpp,h} with the project's `.trunk/configs/.clang-format` config so the Trunk Check Runner passes — clang-format moved the `` include before the Zephyr-namespaced ones in Wire.cpp and collapsed two inline overloads to single lines in Wire.h. * fix(AdminModule): remove dead OUTPUT_GPIO_PIN/GpioOutputModule references OUTPUT_GPIO_PIN is never defined and modules/GpioOutputModule.h doesn't exist in the codebase; all #ifdef OUTPUT_GPIO_PIN branches were dead code introduced by the nRF54L15-DK variant commit. Strips the include, the output_gpio_enabled OFF→ON/ON→OFF transition logic in handleSetConfig(), and the digitalRead() reflection in handleGetConfig(). Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) Co-authored-by: Ben Meadors --- .gitignore | 4 + boards/nrf54l15dk.json | 26 + extra_scripts/nrf54l15_linker.py | 140 +++ platformio.ini | 1 + src/FSCommon.h | 8 + src/RedirectablePrint.cpp | 4 + src/main.cpp | 15 + src/main.h | 4 + src/mesh/Channels.cpp | 17 + src/mesh/MeshService.cpp | 10 +- src/mesh/NodeDB.cpp | 53 ++ src/mesh/PhoneAPI.cpp | 8 + src/mesh/RadioLibInterface.cpp | 21 +- src/mesh/RadioLibInterface.h | 15 +- src/modules/AdminModule.cpp | 10 +- src/platform/nrf54l15/Arduino.h | 824 ++++++++++++++++++ src/platform/nrf54l15/IPAddress.h | 34 + src/platform/nrf54l15/InternalFileSystem.cpp | 274 ++++++ src/platform/nrf54l15/InternalFileSystem.h | 212 +++++ src/platform/nrf54l15/NRF52Bluetooth.h | 18 + src/platform/nrf54l15/NRF54L15Bluetooth.cpp | 805 +++++++++++++++++ src/platform/nrf54l15/NRF54L15Bluetooth.h | 29 + src/platform/nrf54l15/Nrf52SaadcLock.h | 17 + src/platform/nrf54l15/Print.h | 4 + src/platform/nrf54l15/SPI.h | 62 ++ src/platform/nrf54l15/Stream.h | 5 + src/platform/nrf54l15/Tone.h | 4 + src/platform/nrf54l15/WProgram.h | 5 + src/platform/nrf54l15/Wire.cpp | 219 +++++ src/platform/nrf54l15/Wire.h | 83 ++ src/platform/nrf54l15/architecture.h | 79 ++ src/platform/nrf54l15/bluefruit.h | 19 + src/platform/nrf54l15/main-nrf54l15.cpp | 210 +++++ src/platform/nrf54l15/nrf54l15_arduino.cpp | 557 ++++++++++++ src/platform/nrf54l15/nrf54l15_main.cpp | 121 +++ src/platform/nrf54l15/utility/bonding.h | 11 + userPrefs.jsonc | 2 + variants/esp32/esp32-common.ini | 2 +- variants/native/portduino.ini | 5 +- variants/nrf52840/nrf52.ini | 2 +- variants/nrf54l15/nrf54l15.ini | 74 ++ variants/nrf54l15/nrf54l15dk/README.md | 108 +++ variants/nrf54l15/nrf54l15dk/platformio.ini | 24 + variants/nrf54l15/nrf54l15dk/variant.cpp | 9 + variants/nrf54l15/nrf54l15dk/variant.h | 86 ++ variants/rp2040/rp2040.ini | 2 +- variants/rp2350/rp2350.ini | 4 +- variants/stm32/stm32.ini | 2 +- .../boards/nrf54l15dk_nrf54l15_cpuapp.overlay | 117 +++ zephyr/prj.conf | 299 +++++++ 50 files changed, 4635 insertions(+), 29 deletions(-) create mode 100644 boards/nrf54l15dk.json create mode 100644 extra_scripts/nrf54l15_linker.py create mode 100644 src/platform/nrf54l15/Arduino.h create mode 100644 src/platform/nrf54l15/IPAddress.h create mode 100644 src/platform/nrf54l15/InternalFileSystem.cpp create mode 100644 src/platform/nrf54l15/InternalFileSystem.h create mode 100644 src/platform/nrf54l15/NRF52Bluetooth.h create mode 100644 src/platform/nrf54l15/NRF54L15Bluetooth.cpp create mode 100644 src/platform/nrf54l15/NRF54L15Bluetooth.h create mode 100644 src/platform/nrf54l15/Nrf52SaadcLock.h create mode 100644 src/platform/nrf54l15/Print.h create mode 100644 src/platform/nrf54l15/SPI.h create mode 100644 src/platform/nrf54l15/Stream.h create mode 100644 src/platform/nrf54l15/Tone.h create mode 100644 src/platform/nrf54l15/WProgram.h create mode 100644 src/platform/nrf54l15/Wire.cpp create mode 100644 src/platform/nrf54l15/Wire.h create mode 100644 src/platform/nrf54l15/architecture.h create mode 100644 src/platform/nrf54l15/bluefruit.h create mode 100644 src/platform/nrf54l15/main-nrf54l15.cpp create mode 100644 src/platform/nrf54l15/nrf54l15_arduino.cpp create mode 100644 src/platform/nrf54l15/nrf54l15_main.cpp create mode 100644 src/platform/nrf54l15/utility/bonding.h create mode 100644 variants/nrf54l15/nrf54l15.ini create mode 100644 variants/nrf54l15/nrf54l15dk/README.md create mode 100644 variants/nrf54l15/nrf54l15dk/platformio.ini create mode 100644 variants/nrf54l15/nrf54l15dk/variant.cpp create mode 100644 variants/nrf54l15/nrf54l15dk/variant.h create mode 100644 zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay create mode 100644 zephyr/prj.conf diff --git a/.gitignore b/.gitignore index eebd94ef9..55e90a8f2 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,10 @@ data/boot/logo.* managed_components/* arduino-lib-builder* dependencies.lock + +# JLink / RTT debug artifacts (nRF SoCs) +flash.jlink +rtt_*.txt idf_component.yml CMakeLists.txt /sdkconfig.* diff --git a/boards/nrf54l15dk.json b/boards/nrf54l15dk.json new file mode 100644 index 000000000..863ad290b --- /dev/null +++ b/boards/nrf54l15dk.json @@ -0,0 +1,26 @@ +{ + "build": { + "cpu": "cortex-m33", + "f_cpu": "128000000L", + "mcu": "nrf54l15", + "zephyr": { + "variant": "nrf54l15dk/nrf54l15/cpuapp" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "default_tools": ["jlink"], + "jlink_device": "nRF54L15_M33", + "svd_path": "nrf54l15.svd" + }, + "frameworks": ["zephyr"], + "name": "Nordic nRF54L15-DK (PCA10156)", + "upload": { + "maximum_ram_size": 262144, + "maximum_size": 1572864, + "protocol": "jlink", + "protocols": ["jlink"] + }, + "url": "https://www.nordicsemi.com/Products/nRF54L15", + "vendor": "Nordic Semiconductor" +} diff --git a/extra_scripts/nrf54l15_linker.py b/extra_scripts/nrf54l15_linker.py new file mode 100644 index 000000000..fa1041553 --- /dev/null +++ b/extra_scripts/nrf54l15_linker.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# trunk-ignore-all(ruff/F821) +# trunk-ignore-all(flake8/F821): For SConstruct imports +# +# post:extra_scripts/nrf54l15_linker.py +# +# Fix for Zephyr two-pass link on nRF54L15: +# platformio-build.py registers env.Depends("$PROG_PATH", final_ld_script) but +# the SCons dependency chain is broken (final_ld_script Command never runs). +# This script adds a PreAction on the final firmware binary that runs the gcc +# preprocessing command directly (extracted from build.ninja) to generate +# zephyr/linker.cmd before the link step. +# +# PlatformIO bundles an old Ninja that can't handle multi-output depslog rules, +# so we parse the COMMAND line from build.ninja and run just the gcc -E part, +# skipping the cmake_transform_depfile step (only needed for Ninja deps tracking). + +import os +import re +import subprocess + +Import("env") + +if env.get("PIOENV") != "nrf54l15dk": + pass # Only for the nrf54l15dk environment +else: + + def _extract_gcc_command(ninja_build): + """Parse build.ninja to find the gcc -E command that generates linker.cmd. + + The rule format depends on the host: + Windows (CMake's RunCMake wraps every command): + COMMAND = cmd.exe /C "cd /D DIR && arm-none-eabi-gcc.exe ... -o linker.cmd && cmake.exe -E cmake_transform_depfile ..." + POSIX (Linux/macOS — no wrapper): + COMMAND = cd DIR && arm-none-eabi-gcc ... -o linker.cmd && cmake -E cmake_transform_depfile ... + + Returns (gcc_cmd_string, cwd_path) or raises RuntimeError. + """ + in_rule = False + with open(ninja_build, "r", encoding="utf-8", errors="replace") as f: + for line in f: + # Detect start of the linker.cmd custom command rule + if not in_rule: + if "build zephyr/linker.cmd" in line and "CUSTOM_COMMAND" in line: + in_rule = True + continue + + stripped = line.strip() + if not stripped.startswith("COMMAND = "): + continue + + command_val = stripped[len("COMMAND = ") :] + + # On Windows the value is wrapped in `cmd.exe /C "..."` — strip + # the wrapper. On POSIX hosts the inner sequence is the value + # itself (no quoting layer). + m = re.search(r'/C\s+"(.*)"\s*$', command_val) + inner = m.group(1) if m else command_val + parts = inner.split(" && ") + + cwd = None + gcc_cmd = None + for part in parts: + part = part.strip() + if part.startswith("cd /D "): # Windows form + cwd = part[len("cd /D ") :] + elif part.startswith("cd "): # POSIX form + cwd = part[len("cd ") :] + elif "arm-none-eabi-gcc" in part: + gcc_cmd = part + + if not gcc_cmd: + raise RuntimeError( + "nRF54L15 linker fix: arm-none-eabi-gcc command not found in:\n%s" + % inner[:400] + ) + + return gcc_cmd, cwd + + raise RuntimeError( + "nRF54L15 linker fix: 'build zephyr/linker.cmd' rule not found in build.ninja" + ) + + def _generate_linker_cmd(target, source, env): + """Generate zephyr/linker.cmd via direct gcc invocation before the final link.""" + build_dir = env.subst("$BUILD_DIR") + zephyr_dir = os.path.join(build_dir, "zephyr") + linker_cmd = os.path.join(zephyr_dir, "linker.cmd") + + if os.path.exists(linker_cmd): + return # Already present — nothing to do + + ninja_build = os.path.join(build_dir, "build.ninja") + if not os.path.exists(ninja_build): + raise RuntimeError( + "nRF54L15 linker fix: build.ninja not found at %s\n" + "Run a full build first so CMake generates the Ninja files." + % ninja_build + ) + + gcc_cmd, cwd = _extract_gcc_command(ninja_build) + run_cwd = cwd if cwd else zephyr_dir + + print( + "==> nRF54L15: Generating zephyr/linker.cmd (LINKER_ZEPHYR_FINAL) via GCC" + ) + # gcc_cmd comes verbatim from our own build.ninja (never user input) and + # contains Windows-style paths with spaces that cannot be safely argv-split + # with shlex, so we run it via the platform shell. nosec/nosemgrep below + # acknowledge this deliberate, scoped use of shell=True. + result = subprocess.run( # nosec B602 + gcc_cmd, + shell=True, # nosemgrep: python.lang.security.audit.subprocess-shell-true.subprocess-shell-true + cwd=run_cwd, + capture_output=True, + text=True, + ) + if result.returncode != 0: + print("GCC stdout:", result.stdout[:2000]) + print("GCC stderr:", result.stderr[:2000]) + raise RuntimeError( + "nRF54L15 linker fix: GCC failed to generate linker.cmd (rc=%d)" + % result.returncode + ) + if not os.path.exists(linker_cmd): + raise RuntimeError( + "nRF54L15 linker fix: GCC returned 0 but linker.cmd was not created at %s" + % linker_cmd + ) + print("==> linker.cmd generated successfully") + + # Use PIOMAINPROG (set by ZephyrBuildProgram) to get the exact SCons node + prog = env.get("PIOMAINPROG") + if prog: + env.AddPreAction(prog, _generate_linker_cmd) + else: + print( + "[nrf54l15_linker] WARNING: PIOMAINPROG not set, falling back to $PROG_PATH" + ) + env.AddPreAction(env.subst("$PROG_PATH"), _generate_linker_cmd) diff --git a/platformio.ini b/platformio.ini index db689d8bb..c306eed7b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,6 +17,7 @@ test_build_src = true extra_scripts = pre:bin/platformio-pre.py bin/platformio-custom.py + post:extra_scripts/nrf54l15_linker.py ; note: we add src to our include search path so that lmic_project_config can override ; note: TINYGPS_OPTION_NO_CUSTOM_FIELDS is VERY important. We don't use custom fields and somewhere in that pile ; of code is a heap corruption bug! diff --git a/src/FSCommon.h b/src/FSCommon.h index 9fe71e47b..e2e77da9a 100644 --- a/src/FSCommon.h +++ b/src/FSCommon.h @@ -48,6 +48,14 @@ using namespace STM32_LittleFS_Namespace; using namespace Adafruit_LittleFS_Namespace; #endif +#if defined(ARCH_NRF54L15) +// nRF54L15 — Zephyr LittleFS on 36 KB storage_partition (internal RRAM) +#include "InternalFileSystem.h" +#define FSCom InternalFS +#define FSBegin() FSCom.begin() +using namespace Adafruit_LittleFS_Namespace; +#endif + void fsInit(); void fsListFiles(); bool copyFile(const char *from, const char *to); diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 2d6cc13ec..e887f01ee 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -225,6 +225,8 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); #elif defined(ARCH_NRF52) isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected(); +#elif defined(ARCH_NRF54L15) + isBleConnected = nrf54l15Bluetooth != nullptr && nrf54l15Bluetooth->isConnected(); #endif if (isBleConnected) { auto thread = concurrency::OSThread::currentThread; @@ -241,6 +243,8 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ nimbleBluetooth->sendLog(buffer.get(), size); #elif defined(ARCH_NRF52) nrf52Bluetooth->sendLog(buffer.get(), size); +#elif defined(ARCH_NRF54L15) + nrf54l15Bluetooth->sendLog(buffer.get(), size); #endif } } diff --git a/src/main.cpp b/src/main.cpp index 28842734c..304712e6f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,9 @@ #if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" #endif +#if !MESHTASTIC_EXCLUDE_INPUTBROKER +#include "input/InputBroker.h" +#endif #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -59,6 +62,12 @@ NimbleBluetooth *nimbleBluetooth = nullptr; NRF52Bluetooth *nrf52Bluetooth = nullptr; #endif +#ifdef ARCH_NRF54L15 +void nrf54l15Setup(); +void nrf54l15Loop(); +NRF54L15Bluetooth *nrf54l15Bluetooth = nullptr; +#endif + #if HAS_WIFI || defined(USE_WS5500) || defined(USE_CH390D) #include "mesh/api/WiFiServerAPI.h" #include "mesh/wifi/WiFiAPClient.h" @@ -696,6 +705,9 @@ void setup() #ifdef ARCH_NRF52 nrf52Setup(); #endif +#ifdef ARCH_NRF54L15 + nrf54l15Setup(); +#endif #ifdef ARCH_RP2040 rp2040Setup(); @@ -1126,6 +1138,9 @@ void loop() #endif #ifdef ARCH_NRF52 nrf52Loop(); +#endif +#ifdef ARCH_NRF54L15 + nrf54l15Loop(); #endif power->powerCommandsCheck(); diff --git a/src/main.h b/src/main.h index 56f048134..058a2aebc 100644 --- a/src/main.h +++ b/src/main.h @@ -19,6 +19,10 @@ extern NimbleBluetooth *nimbleBluetooth; #include "NRF52Bluetooth.h" extern NRF52Bluetooth *nrf52Bluetooth; #endif +#ifdef ARCH_NRF54L15 +#include "NRF54L15Bluetooth.h" +extern NRF54L15Bluetooth *nrf54l15Bluetooth; +#endif #if !MESHTASTIC_EXCLUDE_I2C #include "detect/ScanI2CTwoWire.h" #endif diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 1583567fe..be75e3d42 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -93,6 +93,23 @@ void Channels::initDefaultLoraConfig() #ifdef USERPREFS_LORACONFIG_CHANNEL_NUM loraConfig.channel_num = USERPREFS_LORACONFIG_CHANNEL_NUM; #endif + + // Apply any hardcoded USERPREFS overrides for custom modem config (e.g. region-locked boards) +#ifdef USERPREFS_LORACONFIG_USE_PRESET + loraConfig.use_preset = USERPREFS_LORACONFIG_USE_PRESET; +#endif +#ifdef USERPREFS_LORACONFIG_BANDWIDTH + loraConfig.bandwidth = USERPREFS_LORACONFIG_BANDWIDTH; +#endif +#ifdef USERPREFS_LORACONFIG_SPREAD_FACTOR + loraConfig.spread_factor = USERPREFS_LORACONFIG_SPREAD_FACTOR; +#endif +#ifdef USERPREFS_LORACONFIG_CODING_RATE + loraConfig.coding_rate = USERPREFS_LORACONFIG_CODING_RATE; +#endif +#ifdef USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY + loraConfig.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY; +#endif } bool Channels::ensureLicensedOperation() diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 151831b96..f219e60d2 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -334,7 +334,9 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p) if (toPhoneQueue.enqueue(p, 0) == false) { LOG_CRIT("Failed to queue a packet into toPhoneQueue!"); - abort(); + releaseToPool(p); + fromNum++; // notify observers so phone can resync + return; } fromNum++; } @@ -351,7 +353,8 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage if (toPhoneMqttProxyQueue.enqueue(m, 0) == false) { LOG_CRIT("Failed to queue a packet into toPhoneMqttProxyQueue!"); - abort(); + releaseMqttClientProxyMessageToPool(m); + return; } fromNum++; } @@ -383,7 +386,8 @@ void MeshService::sendClientNotification(meshtastic_ClientNotification *n) if (toPhoneClientNotificationQueue.enqueue(n, 0) == false) { LOG_CRIT("Failed to queue a notification into toPhoneClientNotificationQueue!"); - abort(); + releaseClientNotificationToPool(n); + return; } fromNum++; } diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8660aefae..5b6a6e980 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -393,6 +393,13 @@ NodeDB::NodeDB() } else { LOG_WARN("Failed to read unique id from efuse"); } +#elif defined(ARCH_NRF54L15) + // nRF54L15: DEVICEID is under FICR->INFO sub-struct (not top-level as on nRF52) + uint64_t device_id_start = ((uint64_t)NRF_FICR->INFO.DEVICEID[1] << 32) | NRF_FICR->INFO.DEVICEID[0]; + uint64_t device_id_end = ((uint64_t)NRF_FICR->DEVICEADDR[1] << 32) | NRF_FICR->DEVICEADDR[0]; + memcpy(myNodeInfo.device_id.bytes, &device_id_start, sizeof(device_id_start)); + memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end)); + myNodeInfo.device_id.size = 16; #elif defined(ARCH_NRF52) // Nordic applies a FIPS compliant Random ID to each chip at the factory // We concatenate the device address to the Random ID to create a unique ID for now @@ -826,6 +833,23 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.lora.modem_preset = USERPREFS_LORACONFIG_MODEM_PRESET; #else config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; +#endif +#ifdef USERPREFS_LORACONFIG_USE_PRESET + config.lora.use_preset = USERPREFS_LORACONFIG_USE_PRESET; +#else + config.lora.use_preset = true; +#endif +#ifdef USERPREFS_LORACONFIG_BANDWIDTH + config.lora.bandwidth = USERPREFS_LORACONFIG_BANDWIDTH; +#endif +#ifdef USERPREFS_LORACONFIG_SPREAD_FACTOR + config.lora.spread_factor = USERPREFS_LORACONFIG_SPREAD_FACTOR; +#endif +#ifdef USERPREFS_LORACONFIG_CODING_RATE + config.lora.coding_rate = USERPREFS_LORACONFIG_CODING_RATE; +#endif +#ifdef USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY + config.lora.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY; #endif config.lora.hop_limit = HOP_RELIABLE; #ifdef USERPREFS_CONFIG_LORA_IGNORE_MQTT @@ -1217,7 +1241,14 @@ void NodeDB::initModuleConfigIntervals() #else moduleConfig.telemetry.device_update_interval = MAX_INTERVAL; #endif +#ifdef USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL + moduleConfig.telemetry.environment_update_interval = USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL; +#else moduleConfig.telemetry.environment_update_interval = 0; +#endif +#ifdef USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED + moduleConfig.telemetry.environment_measurement_enabled = USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED; +#endif moduleConfig.telemetry.air_quality_interval = 0; moduleConfig.telemetry.power_update_interval = 0; moduleConfig.telemetry.health_update_interval = 0; @@ -1726,6 +1757,28 @@ void NodeDB::loadFromDisk() config.lora.tx_enabled = false; #endif + // Always-apply LoRa overrides: applied after loading saved config so they + // take effect even when NVS already has a valid config (e.g. region-locked + // dev boards with no BLE/serial to set the region at runtime). +#ifdef USERPREFS_CONFIG_LORA_REGION + config.lora.region = USERPREFS_CONFIG_LORA_REGION; +#endif +#ifdef USERPREFS_LORACONFIG_USE_PRESET + config.lora.use_preset = USERPREFS_LORACONFIG_USE_PRESET; +#endif +#ifdef USERPREFS_LORACONFIG_BANDWIDTH + config.lora.bandwidth = USERPREFS_LORACONFIG_BANDWIDTH; +#endif +#ifdef USERPREFS_LORACONFIG_SPREAD_FACTOR + config.lora.spread_factor = USERPREFS_LORACONFIG_SPREAD_FACTOR; +#endif +#ifdef USERPREFS_LORACONFIG_CODING_RATE + config.lora.coding_rate = USERPREFS_LORACONFIG_CODING_RATE; +#endif +#ifdef USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY + config.lora.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY; +#endif + if (backupSecurity.private_key.size > 0) { LOG_DEBUG("Restoring backup of security config"); config.security = backupSecurity; diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index ecf6ff809..349069de2 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -71,7 +71,15 @@ void PhoneAPI::handleStartConfig() } pauseBluetoothLogging = true; spiLock->lock(); +#if defined(MESHTASTIC_EXCLUDE_FILES_MANIFEST) + // Skip the recursive FS walk. Used by platforms whose Zephyr LittleFS + // backend can't safely traverse a deep tree (e.g. nRF54L15) and platforms + // that don't support OTA browsing — the manifest is only consumed by + // companion apps for those flows. + filesManifest.clear(); +#else filesManifest = getFiles("/", 10); +#endif spiLock->unlock(); LOG_DEBUG("Got %d files in manifest", filesManifest.size()); diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index de468cf97..02cf2281d 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -46,16 +46,6 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c #endif } -RadioLibInterface::~RadioLibInterface() -{ - // If the static `instance` pointer still references us, clear it. - // A later successful init() may have replaced `instance` with a newer - // interface — don't clobber that case. - if (instance == this) { - instance = nullptr; - } -} - #ifdef ARCH_ESP32 // ESP32 doesn't use that flag #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR() @@ -555,6 +545,9 @@ void RadioLibInterface::pollMissedIrqs() if (isReceiving) { checkRxDoneIrqFlag(); } + if (sendingPacket) { + checkTxDoneIrqFlag(); + } } void RadioLibInterface::resetAGC() @@ -570,6 +563,14 @@ void RadioLibInterface::checkRxDoneIrqFlag() } } +void RadioLibInterface::checkTxDoneIrqFlag() +{ + if (iface->checkIrq(RADIOLIB_IRQ_TX_DONE)) { + LOG_WARN("caught missed TX_DONE"); + notify(ISR_TX, true); + } +} + void RadioLibInterface::configHardwareForSend() { powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn); diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 0740561f9..5b850d675 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -104,6 +104,13 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ static RadioLibInterface *instance; + /** Clear instance on destruction so stale pointer checks in loop() are safe */ + virtual ~RadioLibInterface() + { + if (instance == this) + instance = nullptr; + } + /** * Glue functions called from ISR land */ @@ -136,13 +143,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, PhysicalLayer *iface = NULL); - /** - * Clear the static `instance` pointer if it still points at us, so callers - * that check `RadioLibInterface::instance != nullptr` don't dereference a - * freed object after a failed init() + unique_ptr reset. - */ - virtual ~RadioLibInterface(); - virtual ErrorCode send(meshtastic_MeshPacket *p) override; /** @@ -293,4 +293,5 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override; void checkRxDoneIrqFlag(); + void checkTxDoneIrqFlag(); }; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index ba1cfdb97..e7acdf89e 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -666,7 +666,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers) bool requiresReboot = true; switch (c.which_payload_variant) { - case meshtastic_Config_device_tag: + case meshtastic_Config_device_tag: { LOG_INFO("Set config: Device"); config.has_device = true; #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && \ @@ -720,6 +720,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers) } #endif break; + } // case meshtastic_Config_device_tag case meshtastic_Config_position_tag: LOG_INFO("Set config: Position"); config.has_position = true; @@ -1345,6 +1346,10 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r if (config.bluetooth.enabled && nrf52Bluetooth) { conn.bluetooth.is_connected = nrf52Bluetooth->isConnected(); } +#elif defined(ARCH_NRF54L15) + if (config.bluetooth.enabled && nrf54l15Bluetooth) { + conn.bluetooth.is_connected = nrf54l15Bluetooth->isConnected(); + } #endif #endif conn.has_serial = true; // No serial-less devices @@ -1605,6 +1610,9 @@ void disableBluetooth() #elif defined(ARCH_NRF52) if (nrf52Bluetooth) nrf52Bluetooth->shutdown(); +#elif defined(ARCH_NRF54L15) + if (nrf54l15Bluetooth) + nrf54l15Bluetooth->shutdown(); #endif #endif } diff --git a/src/platform/nrf54l15/Arduino.h b/src/platform/nrf54l15/Arduino.h new file mode 100644 index 000000000..d94d9fd4e --- /dev/null +++ b/src/platform/nrf54l15/Arduino.h @@ -0,0 +1,824 @@ +/** + * Arduino.h — Zephyr compatibility shim for nRF54L15 + * + * Provides the Arduino API surface expected by Meshtastic, backed by + * Zephyr primitives. Only the subset actually used by Meshtastic is + * implemented; the rest compiles as no-ops / stubs for now. + * + * Phase 2: compile only. Real GPIO / SPI / Wire implementations follow + * in Phase 3 once the build is clean. + */ + +#pragma once +#ifndef Arduino_h +#define Arduino_h + +// ── C standard headers ─────────────────────────────────────────────────────── +#include +#include +#include +#include +#include +#include +#include +#include /* strcasecmp, strncasecmp */ + +// ── Zephyr kernel ──────────────────────────────────────────────────────────── +#include +#include + +// ── Basic Arduino types ────────────────────────────────────────────────────── +typedef bool boolean; +typedef uint8_t byte; +typedef uint16_t word; + +// ── Pin / digital constants ────────────────────────────────────────────────── +#define INPUT 0u +#define OUTPUT 1u +#define INPUT_PULLUP 2u +#define INPUT_PULLDOWN 3u +#define OUTPUT_OPENDRAIN 4u + +#define HIGH 1u +#define LOW 0u + +#define CHANGE 1 +#define FALLING 2 +#define RISING 3 + +#ifndef LED_BUILTIN +#define LED_BUILTIN -1 +#endif + +// ── Math / trig constants ──────────────────────────────────────────────────── +#ifndef PI +#define PI 3.14159265358979323846 +#endif +#define HALF_PI 1.57079632679489661923 +#define TWO_PI 6.28318530717958647693 +#define DEG_TO_RAD 0.01745329251994329576 +#define RAD_TO_DEG 57.2957795130823208767 +#define EULER 2.71828182845904523536 + +// ── Bit utilities ──────────────────────────────────────────────────────────── +#define bitRead(v, b) (((v) >> (b)) & 1) +#define bitSet(v, b) ((v) |= (1UL << (b))) +#define bitClear(v, b) ((v) &= ~(1UL << (b))) +#define bitToggle(v, b) ((v) ^= (1UL << (b))) +#define bitWrite(v, b, x) ((x) ? bitSet(v, b) : bitClear(v, b)) +#define bit(b) (1UL << (b)) +#define lowByte(w) ((uint8_t)((w)&0xff)) +#define highByte(w) ((uint8_t)((w) >> 8)) +// word(h,l) — only define if not already defined (conflicts with typedef above) +#undef word +#define word(h, l) ((uint16_t)(((h) << 8) | (l))) + +// ── UART config constants ───────────────────────────────────────────────────── +#define SERIAL_8N1 0x800001cu +#define SERIAL_8N2 0x8000001eu +#define SERIAL_8E1 0x8000001eu +#define SERIAL_7E1 0x8000001cu + +// ── Integer order ──────────────────────────────────────────────────────────── +// Adafruit BusIO's SPIDevice.h has `typedef BitOrder BusIOBitOrder;` which +// requires BitOrder to be a *type*, not a macro. Mirror the ArduinoCore-API +// enum definition rather than #defines. +enum BitOrder : uint8_t { + LSBFIRST = 0, + MSBFIRST = 1, +}; + +// ── pgmspace compatibility (no-ops on Cortex-M) ────────────────────────────── +#define PROGMEM +#define PSTR(s) (s) +#define F(s) (s) +#define pgm_read_byte(addr) (*((const uint8_t *)(addr))) +#define pgm_read_word(addr) (*((const uint16_t *)(addr))) +#define pgm_read_dword(addr) (*((const uint32_t *)(addr))) +#define pgm_read_float(addr) (*((const float *)(addr))) +#define pgm_read_ptr(addr) (*((const void **)(addr))) +#define strlen_P(s) strlen(s) +#define strcpy_P(d, s) strcpy(d, s) +#define strncpy_P(d, s, n) strncpy(d, s, n) +#define strcmp_P(a, b) strcmp(a, b) +#define memcpy_P(d, s, n) memcpy(d, s, n) +#define sprintf_P sprintf +typedef const char *PGM_P; +typedef const char *PGM_VOID_P; + +// ── Arduino numeric base constants (used by Print, RadioLib, etc.) ─────────── +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 + +// ── ulong / uint typedef (used by RadioLibInterface, etc.) ─────────────────── +typedef unsigned long ulong; +typedef unsigned int uint; + +// ── Interrupt stubs ────────────────────────────────────────────────────────── +static inline void interrupts() {} +static inline void noInterrupts() {} +#define digitalPinToInterrupt(p) (p) + +// ── portMAX_DELAY — freertosinc.h also defines this; let it win ────────────── +// We intentionally do NOT define portMAX_DELAY here. freertosinc.h defines +// it for the FreeRTOS / Meshtastic threading layer and must not be overridden. + +// ── Timing & system functions — declared with C linkage ────────────────────── +// buzz.cpp and others forward-declare delay() as extern "C"; keep linkage +// consistent by wrapping in extern "C" here. +#ifdef __cplusplus +extern "C" { +#endif +void NVIC_SystemReset(void); +uint32_t millis(void); +uint32_t micros(void); +void delay(uint32_t ms); +void delayMicroseconds(uint32_t us); +void yield(void); +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus + +#include +#include + +// ── C++ STL — include BEFORE defining any min/max helpers ─────────────────── +// Include algorithm first so its min/max templates are in scope. +// We must NOT define min/max as function-like macros: the C++ STL uses +// 3-argument versions (min(a,b,comp)) that the preprocessor would treat as +// calling a 2-arg macro with 3 args. +#include +// Bring 2-arg std::min / std::max into the global namespace as unqualified +// names so that Arduino code calling min(a,b) continues to compile. +// (Arduino convention; kept minimal to avoid surprises.) +#undef min +#undef max +using std::max; +using std::min; + +// ── Arduino math helpers (macros safe for mixed-type / C calls) ────────────── +#ifndef abs +#define abs(x) ((x) >= 0 ? (x) : -(x)) +#endif +#define constrain(x, l, h) ((x) < (l) ? (l) : ((x) > (h) ? (h) : (x))) +#define round(x) ((x) >= 0 ? (long)((x) + 0.5) : (long)((x)-0.5)) +#define radians(d) ((d)*DEG_TO_RAD) +#define degrees(r) ((r)*RAD_TO_DEG) +#define sq(x) ((x) * (x)) + +// ── Random ─────────────────────────────────────────────────────────────────── +static inline void randomSeed(unsigned long seed) +{ + srand((unsigned int)seed); +} +static inline long random(void) +{ + return (long)rand(); +} +static inline long random(long bound) +{ + return bound > 0 ? (rand() % bound) : 0; +} +static inline long random(long lo, long hi) +{ + return hi > lo ? lo + rand() % (hi - lo) : lo; +} + +// ── GPIO — real Zephyr implementation (Phase 3) ────────────────────────────── +// Implemented in nrf54l15_arduino.cpp using Zephyr GPIO/SPI APIs. +// Pin numbering: P0.n = n, P1.n = 16+n, P2.n = 32+n +void pinMode(uint32_t pin, uint32_t mode); +void digitalWrite(uint32_t pin, uint32_t value); +int digitalRead(uint32_t pin); +static inline void digitalToggle(uint32_t pin) +{ + digitalWrite(pin, !digitalRead(pin)); +} +static inline uint32_t analogRead(uint32_t) +{ + return 0; +} +static inline void analogWrite(uint32_t, uint32_t) {} +static inline void analogReadResolution(int) {} +static inline void analogWriteResolution(int) {} + +// ── __WFI — provided by CMSIS core_cm33.h; do NOT redefine here ───────────── + +// ── __FlashStringHelper — Arduino PROGMEM string class (no-op on Cortex-M) ── +class __FlashStringHelper; + +// ── attachInterrupt / detachInterrupt — real Zephyr GPIO interrupt impl ────── +typedef void (*voidFuncPtr)(void); +void attachInterrupt(uint32_t pin, voidFuncPtr cb, int mode); +void detachInterrupt(uint32_t pin); + +// ── Forward declaration of String (needed by Print / Stream) ───────────────── +class String; + +// ── Print base class ───────────────────────────────────────────────────────── +class Print +{ + public: + virtual size_t write(uint8_t c) = 0; + virtual size_t write(const uint8_t *buf, size_t n) + { + size_t written = 0; + while (n--) + written += write(*buf++); + return written; + } + size_t write(const char *s) { return s ? write((const uint8_t *)s, strlen(s)) : 0; } + size_t write(const char *s, size_t n) { return write((const uint8_t *)s, n); } + + size_t print(const char *s) { return s ? write((const uint8_t *)s, strlen(s)) : 0; } + int printf(const char *fmt, ...) __attribute__((format(printf, 2, 3))); + + size_t print(char c) { return write((uint8_t)c); } + size_t print(const String &s); + size_t print(unsigned char n, int base = 10); + size_t print(int n, int base = 10); + size_t print(long n, int base = 10); + size_t print(unsigned int n, int base = 10); + size_t print(unsigned long n, int base = 10); + size_t print(float n, int digits = 2); + size_t print(double n, int digits = 2); + size_t print(bool b) { return print(b ? "true" : "false"); } + + size_t println() { return write((uint8_t)'\n'); } + size_t println(const char *s) + { + size_t r = print(s); + return r + println(); + } + size_t println(char c) + { + size_t r = print(c); + return r + println(); + } + size_t println(const String &s); + size_t println(int n, int base = 10) + { + size_t r = print(n, base); + return r + println(); + } + size_t println(long n, int base = 10) + { + size_t r = print(n, base); + return r + println(); + } + size_t println(unsigned long n, int base = 10) + { + size_t r = print(n, base); + return r + println(); + } + size_t println(unsigned int n, int base = 10) + { + size_t r = print(n, base); + return r + println(); + } + size_t println(float n, int d = 2) + { + size_t r = print(n, d); + return r + println(); + } + size_t println(double n, int d = 2) + { + size_t r = print(n, d); + return r + println(); + } + size_t println(bool b) + { + size_t r = print(b); + return r + println(); + } + + virtual void flush() {} +}; + +// ── Stream base class ──────────────────────────────────────────────────────── +class Stream : public Print +{ + public: + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + virtual void setTimeout(unsigned long) {} + virtual bool find(const char *) { return false; } + String readString(); + String readStringUntil(char terminator); +}; + +// ── Minimal Arduino String class (backed by a char buffer) ─────────────────── +class String +{ + public: + String() : _buf(nullptr), _len(0), _cap(0) {} + // Implicit conversion is part of the Arduino String contract, used pervasively across the codebase. + // cppcheck-suppress noExplicitConstructor + String(const char *cstr) : _buf(nullptr), _len(0), _cap(0) + { + if (cstr) + assign(cstr, strlen(cstr)); + } + // cppcheck-suppress noExplicitConstructor + String(const String &s) : _buf(nullptr), _len(0), _cap(0) { assign(s._buf ? s._buf : "", s._len); } + // cppcheck-suppress noExplicitConstructor + String(char c) : _buf(nullptr), _len(0), _cap(0) + { + const char tmp[2] = {c, 0}; + assign(tmp, 1); + } + // cppcheck-suppress noExplicitConstructor + String(int n) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[16]; + snprintf(tmp, 16, "%d", n); + assign(tmp, strlen(tmp)); + } + // cppcheck-suppress noExplicitConstructor + String(unsigned int n) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[16]; + snprintf(tmp, 16, "%u", n); + assign(tmp, strlen(tmp)); + } + // cppcheck-suppress noExplicitConstructor + String(long n) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[24]; + snprintf(tmp, 24, "%ld", n); + assign(tmp, strlen(tmp)); + } + // cppcheck-suppress noExplicitConstructor + String(unsigned long n) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[24]; + snprintf(tmp, 24, "%lu", n); + assign(tmp, strlen(tmp)); + } + // cppcheck-suppress noExplicitConstructor + String(float n, int d = 2) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[32]; + snprintf(tmp, 32, "%.*f", d, n); + assign(tmp, strlen(tmp)); + } + // cppcheck-suppress noExplicitConstructor + String(double n, int d = 2) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[32]; + snprintf(tmp, 32, "%.*f", d, (double)n); + assign(tmp, strlen(tmp)); + } + ~String() { free(_buf); } + + String &operator=(const String &s) + { + assign(s._buf ? s._buf : "", s._len); + return *this; + } + String &operator=(const char *s) + { + assign(s ? s : "", s ? strlen(s) : 0); + return *this; + } + String &operator=(char c) + { + const char tmp[2] = {c, 0}; + assign(tmp, 1); + return *this; + } + + String &operator+=(const String &s) + { + concat(s._buf ? s._buf : "", s._len); + return *this; + } + String &operator+=(const char *s) + { + if (s) + concat(s, strlen(s)); + return *this; + } + String &operator+=(char c) + { + concat(&c, 1); + return *this; + } + String &operator+=(int n) { return *this += String(n); } + String &operator+=(unsigned long n) { return *this += String(n); } + + String operator+(const String &rhs) const + { + String r(*this); + r += rhs; + return r; + } + String operator+(const char *rhs) const + { + String r(*this); + r += rhs; + return r; + } + String operator+(char rhs) const + { + String r(*this); + r += rhs; + return r; + } + + bool operator==(const String &s) const { return _len == s._len && (_len == 0 || strcmp(_buf, s._buf) == 0); } + bool operator==(const char *s) const { return s && strcmp(c_str(), s) == 0; } + bool operator!=(const String &s) const { return !(*this == s); } + bool operator!=(const char *s) const { return !(*this == s); } + bool operator<(const String &s) const { return strcmp(c_str(), s.c_str()) < 0; } + bool operator>(const String &s) const { return strcmp(c_str(), s.c_str()) > 0; } + + char operator[](unsigned int i) const { return (_buf && i < _len) ? _buf[i] : 0; } + char &operator[](unsigned int i) + { + static char dummy = 0; + return (_buf && i < _len) ? _buf[i] : dummy; + } + + const char *c_str() const { return _buf ? _buf : ""; } + unsigned int length() const { return _len; } + bool isEmpty() const { return _len == 0; } + bool equals(const String &s) const { return *this == s; } + bool equals(const char *s) const { return *this == s; } + bool equalsIgnoreCase(const String &s) const + { + if (_len != s._len) + return false; + for (unsigned i = 0; i < _len; i++) + if (std::tolower(_buf[i]) != std::tolower(s._buf[i])) + return false; + return true; + } + bool startsWith(const String &pfx) const + { + if (pfx._len > _len) + return false; + return strncmp(c_str(), pfx.c_str(), pfx._len) == 0; + } + bool startsWith(const char *pfx) const + { + if (!pfx) + return false; + size_t pl = strlen(pfx); + return pl <= _len && strncmp(c_str(), pfx, pl) == 0; + } + bool endsWith(const String &sfx) const + { + if (sfx._len > _len) + return false; + return strcmp(c_str() + _len - sfx._len, sfx.c_str()) == 0; + } + int indexOf(char c, unsigned from = 0) const + { + if (!_buf) + return -1; + const char *p = strchr(_buf + from, c); + return p ? (int)(p - _buf) : -1; + } + int indexOf(const String &s, unsigned from = 0) const + { + if (!_buf) + return -1; + const char *p = strstr(_buf + from, s.c_str()); + return p ? (int)(p - _buf) : -1; + } + int lastIndexOf(char c) const + { + if (!_buf) + return -1; + const char *p = strrchr(_buf, c); + return p ? (int)(p - _buf) : -1; + } + String substring(unsigned beginIndex) const + { + if (!_buf || beginIndex >= _len) + return String(); + return String(_buf + beginIndex); + } + String substring(unsigned beginIndex, unsigned endIndex) const + { + if (!_buf || beginIndex >= _len) + return String(); + if (endIndex > _len) + endIndex = _len; + if (endIndex <= beginIndex) + return String(); + String r; + r.assign(_buf + beginIndex, endIndex - beginIndex); + return r; + } + void toUpperCase() + { + if (_buf) + for (unsigned i = 0; i < _len; i++) + _buf[i] = (char)std::toupper(_buf[i]); + } + void toLowerCase() + { + if (_buf) + for (unsigned i = 0; i < _len; i++) + _buf[i] = (char)std::tolower(_buf[i]); + } + void trim() + { + if (!_buf || _len == 0) + return; + unsigned s = 0; + while (s < _len && std::isspace(_buf[s])) + s++; + unsigned e = _len; + while (e > s && std::isspace(_buf[e - 1])) + e--; + if (s > 0 || e < _len) { + memmove(_buf, _buf + s, e - s); + _len = e - s; + _buf[_len] = 0; + } + } + void replace(char from, char to) + { + if (_buf) + for (unsigned i = 0; i < _len; i++) + if (_buf[i] == from) + _buf[i] = to; + } + void replace(const String &from, const String &to); + bool remove(unsigned index, unsigned count = 1) + { + if (!_buf || index >= _len) + return false; + if (index + count > _len) + count = _len - index; + memmove(_buf + index, _buf + index + count, _len - index - count + 1); + _len -= count; + return true; + } + void clear() + { + _len = 0; + if (_buf) + _buf[0] = 0; + } + char charAt(unsigned i) const { return (*this)[i]; } + void setCharAt(unsigned i, char c) + { + if (_buf && i < _len) + _buf[i] = c; + } + void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const + { + if (!buf || bufsize == 0) + return; + unsigned int avail = (_buf && _len > index) ? (_len - index) : 0; + unsigned int copy = avail < bufsize - 1 ? avail : bufsize - 1; + if (copy > 0) + memcpy(buf, _buf + index, copy); + buf[copy] = '\0'; + } + void concat(const String &s) { *this += s; } + void concat(const char *s) { *this += s; } + long toInt() const { return _buf ? atol(_buf) : 0; } + float toFloat() const { return _buf ? (float)atof(_buf) : 0.0f; } + double toDouble() const { return _buf ? atof(_buf) : 0.0; } + + private: + char *_buf; + unsigned int _len; + unsigned int _cap; + + void assign(const char *s, unsigned int n) + { + if (n >= _cap) + reserve(n + 1); + if (_buf) { + memcpy(_buf, s, n); + _buf[n] = 0; + _len = n; + } + } + void concat(const char *s, unsigned int n) + { + if (!s || n == 0) + return; + unsigned newlen = _len + n; + if (newlen >= _cap) + reserve(newlen + 1); + if (_buf) { + memcpy(_buf + _len, s, n); + _len = newlen; + _buf[_len] = 0; + } + } + void reserve(unsigned int n) + { + char *b = (char *)realloc(_buf, n); + if (b) { + _buf = b; + _cap = n; + } + } +}; + +inline String operator+(const char *lhs, const String &rhs) +{ + return String(lhs) + rhs; +} +inline String operator+(char lhs, const String &rhs) +{ + return String(lhs) + rhs; +} + +// ── Print inline definitions that need String ──────────────────────────────── +inline size_t Print::print(const String &s) +{ + return write((const uint8_t *)s.c_str(), s.length()); +} +inline size_t Print::println(const String &s) +{ + size_t r = print(s); + return r + println(); +} + +// ── Stream inline definitions that need String ─────────────────────────────── +inline String Stream::readString() +{ + return String(); +} +inline String Stream::readStringUntil(char) +{ + return String(); +} + +// ── HardwareSerial ─────────────────────────────────────────────────────────── +class HardwareSerial : public Stream +{ + public: + void begin(unsigned long) {} + void begin(unsigned long, uint16_t) {} + void end() {} + void setPins(int rx, int tx) {} + void setPinout(int tx, int rx) {} + void setFIFOSize(size_t) {} + void setRxBufferSize(size_t) {} + void begin(unsigned long baud, uint32_t config, int8_t rx = -1, int8_t tx = -1, bool invert = false) {} + int available() override { return 0; } + int read() override { return -1; } + int peek() override { return -1; } + size_t write(uint8_t c) override; + size_t write(const uint8_t *buf, size_t n) override; + using Print::write; // un-hide base class write(const char*) + size_t readBytes(uint8_t *buf, size_t len) { return 0; } + size_t readBytes(char *buf, size_t len) { return 0; } + operator bool() const { return true; } + void flush() override {} + String readString() { return String(); } + String readStringUntil(char) { return String(); } +}; + +// Uart — nRF52 BSP alias for HardwareSerial (used by GPS.h when ARCH_NRF52) +typedef HardwareSerial Uart; + +extern HardwareSerial Serial; +extern HardwareSerial Serial1; +extern HardwareSerial Serial2; + +// ── map() utility ──────────────────────────────────────────────────────────── +static inline long map(long x, long in_min, long in_max, long out_min, long out_max) +{ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +// ── shiftIn / shiftOut stubs ───────────────────────────────────────────────── +static inline uint8_t shiftIn(uint8_t, uint8_t, uint8_t) +{ + return 0; +} +static inline void shiftOut(uint8_t, uint8_t, uint8_t, uint8_t) {} + +// ── tone / noTone stubs ────────────────────────────────────────────────────── +static inline void tone(uint8_t, unsigned int, unsigned long = 0) {} +static inline void noTone(uint8_t) {} + +// ── pulseIn stub ───────────────────────────────────────────────────────────── +static inline unsigned long pulseIn(uint8_t, uint8_t, unsigned long = 1000000UL) +{ + return 0; +} + +// ── strdup / stpcpy — POSIX extensions not in Zephyr newlib ───────────────── +#ifndef strdup +static inline char *strdup(const char *s) +{ + size_t n = strlen(s) + 1; + char *d = (char *)malloc(n); + if (d) + memcpy(d, s, n); + return d; +} +#endif +#ifndef stpcpy +static inline char *stpcpy(char *dst, const char *src) +{ + while ((*dst++ = *src++) != '\0') { + } + return dst - 1; +} +#endif + +// ── strnstr — BSD extension not in Zephyr libc; defined in meshUtils.cpp ───── +// Declare here so callers (GPS.cpp etc.) don't need ARCH_PORTDUINO. +#ifndef STRNSTR +#define STRNSTR +char *strnstr(const char *s, const char *find, size_t slen); +#endif + +// ── strlcpy — BSD extension; implementation in nrf54l15_arduino.cpp ────────── +#ifndef HAVE_STRLCPY +#define HAVE_STRLCPY +#ifdef __cplusplus +extern "C" { +#endif +size_t strlcpy(char *dst, const char *src, size_t size); +#ifdef __cplusplus +} +#endif +#endif + +// ── setenv / getenv / tzset — Zephyr stubs for timezone support ────────────── +#include +static inline int setenv(const char *, const char *, int) +{ + return 0; +} +static inline void tzset(void) {} + +// ── dbgHeapFree / dbgHeapTotal — nRF52 BSP heap diagnostics ───────────────── +// Used by memGet.cpp when ARCH_NRF52 is defined. Return 0 for Phase 2. +static inline uint32_t dbgHeapFree(void) +{ + return 0; +} +static inline uint32_t dbgHeapTotal(void) +{ + return 0; +} + +// ── WCharacter helpers ─────────────────────────────────────────────────────── +static inline bool isAlpha(char c) +{ + return std::isalpha((unsigned char)c) != 0; +} +static inline bool isAlphaNumeric(char c) +{ + return std::isalnum((unsigned char)c) != 0; +} +static inline bool isDigit(char c) +{ + return std::isdigit((unsigned char)c) != 0; +} +static inline bool isSpace(char c) +{ + return std::isspace((unsigned char)c) != 0; +} +static inline bool isUpperCase(char c) +{ + return std::isupper((unsigned char)c) != 0; +} +static inline bool isLowerCase(char c) +{ + return std::islower((unsigned char)c) != 0; +} +static inline char toUpperCase(char c) +{ + return (char)std::toupper((unsigned char)c); +} +static inline char toLowerCase(char c) +{ + return (char)std::tolower((unsigned char)c); +} + +#else /* C only */ +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef abs +#define abs(x) ((x) >= 0 ? (x) : -(x)) +#endif +#define constrain(x, l, h) ((x) < (l) ? (l) : ((x) > (h) ? (h) : (x))) +#define round(x) ((x) >= 0 ? (long)((x) + 0.5) : (long)((x)-0.5)) +#endif /* __cplusplus */ + +#endif /* Arduino_h */ diff --git a/src/platform/nrf54l15/IPAddress.h b/src/platform/nrf54l15/IPAddress.h new file mode 100644 index 000000000..bb68580b9 --- /dev/null +++ b/src/platform/nrf54l15/IPAddress.h @@ -0,0 +1,34 @@ +// IPAddress.h — stub for nRF54L15/Zephyr +// MQTT.cpp includes this for IPv4 address representation. +// Phase 2: compile-only stub. +#pragma once +#include + +class IPAddress +{ + public: + IPAddress() : _addr(0) {} + explicit IPAddress(uint32_t addr) : _addr(addr) {} + IPAddress(uint8_t a, uint8_t b, uint8_t c, uint8_t d) + : _addr(((uint32_t)a) | ((uint32_t)b << 8) | ((uint32_t)c << 16) | ((uint32_t)d << 24)) + { + } + + uint8_t operator[](int i) const { return reinterpret_cast(&_addr)[i]; } + operator uint32_t() const { return _addr; } + bool operator==(const IPAddress &o) const { return _addr == o._addr; } + bool operator!=(const IPAddress &o) const { return _addr != o._addr; } + + bool fromString(const char *addr) + { + unsigned a, b, c, d; + if (sscanf(addr, "%u.%u.%u.%u", &a, &b, &c, &d) == 4 && a <= 255 && b <= 255 && c <= 255 && d <= 255) { + _addr = a | (b << 8) | (c << 16) | (d << 24); + return true; + } + return false; + } + + private: + uint32_t _addr; +}; diff --git a/src/platform/nrf54l15/InternalFileSystem.cpp b/src/platform/nrf54l15/InternalFileSystem.cpp new file mode 100644 index 000000000..1aed726f2 --- /dev/null +++ b/src/platform/nrf54l15/InternalFileSystem.cpp @@ -0,0 +1,274 @@ +// InternalFileSystem.cpp — Zephyr LittleFS backend for nRF54L15 +// +// Implements Adafruit_LittleFS_Namespace used by FSCommon.h/cpp. +// Storage: 36 KB storage_partition in nRF54L15 internal RRAM (defined in +// zephyr/dts/nordic/nrf54l15_partition.dtsi, included by the board DTS). + +#include "InternalFileSystem.h" +#include "configuration.h" + +#include +#include +#include + +using namespace Adafruit_LittleFS_Namespace; + +// ── LittleFS mount ──────────────────────────────────────────────────────── + +FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(nrf54l15_lfs_data); + +static struct fs_mount_t _lfs_mnt = { + .type = FS_LITTLEFS, + .mnt_point = NRF54L15_FS_MOUNT, + .fs_data = &nrf54l15_lfs_data, + .storage_dev = (void *)(uintptr_t)FIXED_PARTITION_ID(storage_partition), + .flags = 0, +}; + +// ── Global singleton ────────────────────────────────────────────────────── + +Adafruit_LittleFS_Namespace::InternalFileSystem InternalFS; + +// ── Path helpers ────────────────────────────────────────────────────────── + +void InternalFileSystem::toabs(const char *rel, char *abs, size_t abssz) +{ + // Root "/" maps to the mount point itself (no trailing slash) + if (rel[0] == '/' && rel[1] == '\0') { + strncpy(abs, NRF54L15_FS_MOUNT, abssz - 1); + abs[abssz - 1] = '\0'; + } else if (rel[0] == '/') { + snprintf(abs, abssz, "%s%s", NRF54L15_FS_MOUNT, rel); + } else { + snprintf(abs, abssz, "%s/%s", NRF54L15_FS_MOUNT, rel); + } +} + +// Strip mount-point prefix to get the FS-root-relative path ("/prefs/..."). +static void torel(const char *abs, char *rel, size_t relsz) +{ + const char *mp = NRF54L15_FS_MOUNT; + size_t mplen = strlen(mp); + if (strncmp(abs, mp, mplen) == 0) { + const char *suffix = abs + mplen; + if (suffix[0] == '\0') { + strncpy(rel, "/", relsz); + } else { + strncpy(rel, suffix, relsz - 1); + rel[relsz - 1] = '\0'; + } + } else { + strncpy(rel, abs, relsz - 1); + rel[relsz - 1] = '\0'; + } +} + +// ── InternalFileSystem methods ──────────────────────────────────────────── + +bool InternalFileSystem::begin() +{ + if (_mounted) + return true; + + int rc = fs_mount(&_lfs_mnt); + if (rc == 0) { + _mounted = true; + return true; + } + + // Mount failed: attempt to format (creates a fresh LittleFS) + LOG_WARN("LittleFS mount failed (%d), formatting storage partition...", rc); + int fmt_rc = fs_mkfs(FS_LITTLEFS, (uintptr_t)FIXED_PARTITION_ID(storage_partition), NULL, 0); + if (fmt_rc != 0) { + LOG_ERROR("LittleFS format failed (%d)", fmt_rc); + return false; + } + + rc = fs_mount(&_lfs_mnt); + if (rc == 0) { + _mounted = true; + return true; + } + + LOG_ERROR("LittleFS mount failed after format (%d)", rc); + return false; +} + +File InternalFileSystem::open(const char *path, const char *mode) +{ + if (!_mounted) + return File(); + + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + + auto s = std::make_shared(); + if (!s) + return File(); + + strncpy(s->fullpath, abs, sizeof(s->fullpath) - 1); + torel(abs, s->relpath, sizeof(s->relpath)); + + // Check whether the path is a directory + struct fs_dirent entry; + int stat_rc = fs_stat(abs, &entry); + if (stat_rc == 0 && entry.type == FS_DIR_ENTRY_DIR) { + s->is_dir = true; + if (fs_opendir(&s->dir, abs) == 0) { + s->valid = true; + return File(s); + } + return File(); + } + + // Open as a regular file + fs_mode_t flags; + if (strcmp(mode, FILE_O_WRITE) == 0) { + // Truncate on write — unlink first to ensure a clean start + fs_unlink(abs); + flags = FS_O_WRITE | FS_O_CREATE; + } else { + flags = FS_O_READ; + } + + if (fs_open(&s->file, abs, flags) == 0) { + s->is_dir = false; + s->valid = true; + return File(s); + } + + return File(); +} + +bool InternalFileSystem::exists(const char *path) +{ + if (!_mounted) + return false; + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + struct fs_dirent entry; + return fs_stat(abs, &entry) == 0; +} + +bool InternalFileSystem::remove(const char *path) +{ + if (!_mounted) + return false; + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + return fs_unlink(abs) == 0; +} + +bool InternalFileSystem::rename(const char *from, const char *to) +{ + if (!_mounted) + return false; + char absfrom[NRF54L15_FS_PATHLEN], absto[NRF54L15_FS_PATHLEN]; + toabs(from, absfrom, sizeof(absfrom)); + toabs(to, absto, sizeof(absto)); + return fs_rename(absfrom, absto) == 0; +} + +bool InternalFileSystem::mkdir(const char *path) +{ + if (!_mounted) + return false; + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + int rc = fs_mkdir(abs); + return rc == 0 || rc == -EEXIST; +} + +bool InternalFileSystem::rmdir(const char *path) +{ + if (!_mounted) + return false; + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + return fs_unlink(abs) == 0; +} + +bool InternalFileSystem::rmdir_r(const char *path) +{ + if (!_mounted) + return false; + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + + struct fs_dir_t dir; + fs_dir_t_init(&dir); + if (fs_opendir(&dir, abs) != 0) { + // Not a directory — try to delete as file + return fs_unlink(abs) == 0; + } + + struct fs_dirent entry; + char child[NRF54L15_FS_PATHLEN]; + while (fs_readdir(&dir, &entry) == 0 && entry.name[0] != '\0') { + snprintf(child, sizeof(child), "%s/%s", abs, entry.name); + if (entry.type == FS_DIR_ENTRY_DIR) { + // Recurse: pass the absolute path stripped of mount prefix + char childrel[NRF54L15_FS_PATHLEN]; + torel(child, childrel, sizeof(childrel)); + rmdir_r(childrel); + } else { + fs_unlink(child); + } + } + fs_closedir(&dir); + return fs_unlink(abs) == 0; +} + +bool InternalFileSystem::format() +{ + if (_mounted) { + fs_unmount(&_lfs_mnt); + _mounted = false; + } + int rc = fs_mkfs(FS_LITTLEFS, (uintptr_t)FIXED_PARTITION_ID(storage_partition), NULL, 0); + if (rc != 0) { + LOG_ERROR("LittleFS format failed (%d)", rc); + return false; + } + return begin(); +} + +// ── File::openNextFile ──────────────────────────────────────────────────── +// Defined here because it accesses Zephyr fs_readdir/fs_open APIs. + +File File::openNextFile() +{ + if (!_s || !_s->valid || !_s->is_dir) + return File(); + + struct fs_dirent entry; + if (fs_readdir(&_s->dir, &entry) != 0) + return File(); + if (entry.name[0] == '\0') + return File(); // end of directory + + char childabs[NRF54L15_FS_PATHLEN]; + snprintf(childabs, sizeof(childabs), "%s/%s", _s->fullpath, entry.name); + + auto s = std::make_shared(); + if (!s) + return File(); + + strncpy(s->fullpath, childabs, sizeof(s->fullpath) - 1); + torel(childabs, s->relpath, sizeof(s->relpath)); + + if (entry.type == FS_DIR_ENTRY_DIR) { + s->is_dir = true; + if (fs_opendir(&s->dir, childabs) == 0) { + s->valid = true; + return File(s); + } + } else { + s->is_dir = false; + if (fs_open(&s->file, childabs, FS_O_READ) == 0) { + s->valid = true; + return File(s); + } + } + return File(); +} diff --git a/src/platform/nrf54l15/InternalFileSystem.h b/src/platform/nrf54l15/InternalFileSystem.h new file mode 100644 index 000000000..d6b1a9d00 --- /dev/null +++ b/src/platform/nrf54l15/InternalFileSystem.h @@ -0,0 +1,212 @@ +// InternalFileSystem.h — Zephyr LittleFS backend for nRF54L15 +// +// Implements the Adafruit InternalFileSystem API subset used by Meshtastic, +// backed by Zephyr's fs/littlefs on the storage_partition of the nRF54L15's +// internal RRAM. Partition size is taken from the DTS at compile time via +// FIXED_PARTITION_SIZE(storage_partition) — the DK overlay currently maps +// ~700 KB into slot1 (see zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay). +// +// Mount point: /lfs +// All paths passed to open/exists/mkdir etc. are relative to the FS root +// (e.g. "/prefs/config.proto") and are prepended with "/lfs" internally. +// +// File objects are copyable via std::shared_ptr. +// The underlying Zephyr handle is closed when the last copy is destroyed. + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#ifndef FILE_O_READ +#define FILE_O_READ "r" +#define FILE_O_WRITE "w" +#endif + +#define NRF54L15_FS_MOUNT "/lfs" +#define NRF54L15_FS_PATHLEN 256 + +namespace Adafruit_LittleFS_Namespace +{ + +class InternalFileSystem; // forward + +// ── Internal file/dir state ─────────────────────────────────────────────── + +struct NRF54L15FileState { + bool valid = false; + bool is_dir = false; + + // Absolute Zephyr path, e.g. "/lfs/prefs/config.proto" + char fullpath[NRF54L15_FS_PATHLEN] = {0}; + // Path from FS root, e.g. "/prefs/config.proto" (returned by name()) + char relpath[NRF54L15_FS_PATHLEN] = {0}; + + struct fs_file_t file; + struct fs_dir_t dir; + + NRF54L15FileState() + { + fs_file_t_init(&file); + fs_dir_t_init(&dir); + } + + ~NRF54L15FileState() + { + if (valid) { + if (is_dir) + fs_closedir(&dir); + else + fs_close(&file); + valid = false; + } + } +}; + +// ── File ───────────────────────────────────────────────────────────────── + +class File +{ + public: + File() = default; + explicit File(InternalFileSystem &) {} // nRF52 compat constructor + + explicit operator bool() const { return _s && _s->valid; } + + int read(void *buf, uint16_t nbyte) + { + if (!_s || !_s->valid || _s->is_dir) + return -1; + ssize_t n = fs_read(&_s->file, buf, nbyte); + return n < 0 ? -1 : (int)n; + } + + int read() + { + uint8_t b; + return read(&b, 1) == 1 ? (int)b : -1; + } + + size_t write(const uint8_t *buf, size_t len) + { + if (!_s || !_s->valid || _s->is_dir) + return 0; + ssize_t n = fs_write(&_s->file, buf, len); + return n < 0 ? 0 : (size_t)n; + } + + size_t write(uint8_t b) { return write(&b, 1); } + + void flush() + { + if (_s && _s->valid && !_s->is_dir) + fs_sync(&_s->file); + } + + void close() { _s.reset(); } + + size_t size() + { + if (!_s || !_s->valid || _s->is_dir) + return 0; + struct fs_dirent entry; + if (fs_stat(_s->fullpath, &entry) == 0) + return (size_t)entry.size; + return 0; + } + + bool isDirectory() { return _s && _s->valid && _s->is_dir; } + + // Returns path from FS root, e.g. "/prefs/config.proto" + const char *name() { return _s ? _s->relpath : ""; } + + // Returns the next entry in a directory. Modifies the dir stream in _s. + File openNextFile(); + + void rewindDirectory() + { + if (!_s || !_s->valid || !_s->is_dir) + return; + // Zephyr has no rewinddir(); close + reopen the same handle. Skipping + // the close would leak the LittleFS dir state and the next openNextFile + // could return stale entries on some Zephyr versions. + fs_closedir(&_s->dir); + fs_dir_t_init(&_s->dir); + if (fs_opendir(&_s->dir, _s->fullpath) != 0) { + _s->valid = false; + } + } + + bool seek(uint32_t pos) + { + if (!_s || !_s->valid || _s->is_dir) + return false; + return fs_seek(&_s->file, (off_t)pos, FS_SEEK_SET) == 0; + } + + int available() + { + if (!_s || !_s->valid || _s->is_dir) + return 0; + off_t pos = fs_tell(&_s->file); + if (pos < 0) + return 0; + struct fs_dirent entry; + if (fs_stat(_s->fullpath, &entry) != 0) + return 0; + long rem = (long)entry.size - (long)pos; + return rem > 0 ? (int)rem : 0; + } + + int peek() { return -1; } + + // Internal: constructed by InternalFileSystem and openNextFile() + explicit File(std::shared_ptr s) : _s(std::move(s)) {} + + private: + std::shared_ptr _s; +}; + +// ── InternalFileSystem ──────────────────────────────────────────────────── + +class InternalFileSystem +{ + public: + bool begin(); + File open(const char *path, const char *mode); + bool exists(const char *path); + bool remove(const char *path); + bool rename(const char *from, const char *to); + bool mkdir(const char *path); + bool rmdir(const char *path); + bool rmdir_r(const char *path); // recursive delete (used by FSCommon rmDir) + uint32_t usedBytes() + { + struct fs_statvfs st = {}; + if (fs_statvfs(NRF54L15_FS_MOUNT, &st) != 0) + return 0; + // Zephyr returns block counts; convert to bytes. f_frsize is the + // fundamental fragment size (LittleFS reports it equal to the block + // size). used = (total - free) * frag_size. + if (st.f_blocks <= st.f_bfree) + return 0; + return (uint32_t)((st.f_blocks - st.f_bfree) * st.f_frsize); + } + uint32_t totalBytes() { return (uint32_t)FIXED_PARTITION_SIZE(storage_partition); } + bool format(); + + // Convert a FS-root-relative path to an absolute Zephyr path. + static void toabs(const char *rel, char *abs, size_t abssz); + + private: + bool _mounted = false; +}; + +} // namespace Adafruit_LittleFS_Namespace + +extern Adafruit_LittleFS_Namespace::InternalFileSystem InternalFS; diff --git a/src/platform/nrf54l15/NRF52Bluetooth.h b/src/platform/nrf54l15/NRF52Bluetooth.h new file mode 100644 index 000000000..13cc73485 --- /dev/null +++ b/src/platform/nrf54l15/NRF52Bluetooth.h @@ -0,0 +1,18 @@ +// NRF52Bluetooth.h — stub for nRF54L15/Zephyr +// main.h includes this when ARCH_NRF52 is defined. +// Bluetooth is excluded (MESHTASTIC_EXCLUDE_BLUETOOTH=1); this satisfies the +// include chain without pulling in the nRF52 Bluefruit SDK. +#pragma once + +class NRF52Bluetooth +{ + public: + void setup() {} + void shutdown() {} + void startDisabled() {} + void resumeAdvertising() {} + void clearBonds() {} + bool isConnected() { return false; } + int getRssi() { return 0; } + void sendLog(const uint8_t *, size_t) {} +}; diff --git a/src/platform/nrf54l15/NRF54L15Bluetooth.cpp b/src/platform/nrf54l15/NRF54L15Bluetooth.cpp new file mode 100644 index 000000000..1e9281b7b --- /dev/null +++ b/src/platform/nrf54l15/NRF54L15Bluetooth.cpp @@ -0,0 +1,805 @@ +// NRF54L15Bluetooth.cpp — Zephyr BLE GATT peripheral for Meshtastic nRF54L15 +// +// GATT profile (identical UUIDs to the nRF52 / NimBLE implementations): +// Service: 6ba1b218-15a8-461f-9fa8-5dcae273eafd +// fromNum: ed9da18c-a800-4f66-a670-aa7547e34453 READ | NOTIFY +// fromRadio: 2c55e69e-4993-11ed-b878-0242ac120002 READ +// toRadio: f75c76d2-129e-4dad-a1dd-7866124401e7 WRITE +// logRadio: 5a3d6e49-06e6-4423-9944-e9de8cdf9547 READ | NOTIFY | INDICATE +// +// Threading model: +// - BT RX thread: connected_cb / disconnected_cb / GATT read_/write_ +// callbacks +// - Meshtastic OSThread scheduler (cooperative, main thread): +// BleDeferredThread +// polls pendingToRadio and runs the zombie-connection watchdog every 100 ms +// - PhoneAPI::onNowHasData: sends fromNum notify synchronously from whichever +// thread pushed the packet (bt_gatt_notify is thread-safe in Zephyr) +// - active_conn protected by ble_mutex where needed + +#include "NRF54L15Bluetooth.h" +#include "BluetoothCommon.h" +#include "BluetoothStatus.h" +#include "PowerFSM.h" +#include "concurrency/OSThread.h" +#include "configuration.h" +#include "main.h" +#include "mesh/PhoneAPI.h" +#include "mesh/mesh-pb-constants.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +// ── UUID definitions (little-endian per Bluetooth spec) +// ─────────────────────── Syntax: replace hyphens with commas, prefix 0x — +// matches BT_UUID_128_ENCODE doc. + +#define MESH_SVC_UUID_VAL BT_UUID_128_ENCODE(0x6ba1b218, 0x15a8, 0x461f, 0x9fa8, 0x5dcae273eafd) +#define FROMNUM_UUID_VAL BT_UUID_128_ENCODE(0xed9da18c, 0xa800, 0x4f66, 0xa670, 0xaa7547e34453) +#define FROMRADIO_UUID_VAL BT_UUID_128_ENCODE(0x2c55e69e, 0x4993, 0x11ed, 0xb878, 0x0242ac120002) +#define TORADIO_UUID_VAL BT_UUID_128_ENCODE(0xf75c76d2, 0x129e, 0x4dad, 0xa1dd, 0x7866124401e7) +#define LOGRADIO_UUID_VAL BT_UUID_128_ENCODE(0x5a3d6e49, 0x06e6, 0x4423, 0x9944, 0xe9de8cdf9547) + +static const struct bt_uuid_128 mesh_svc_uuid = BT_UUID_INIT_128(MESH_SVC_UUID_VAL); +static const struct bt_uuid_128 fromnum_uuid = BT_UUID_INIT_128(FROMNUM_UUID_VAL); +static const struct bt_uuid_128 fromradio_uuid = BT_UUID_INIT_128(FROMRADIO_UUID_VAL); +static const struct bt_uuid_128 toradio_uuid = BT_UUID_INIT_128(TORADIO_UUID_VAL); +static const struct bt_uuid_128 logradio_uuid = BT_UUID_INIT_128(LOGRADIO_UUID_VAL); + +// ── Module state ───────────────────────────────────────────────────────────── + +static struct bt_conn *active_conn = nullptr; +static K_MUTEX_DEFINE(ble_mutex); + +// Take a reference to active_conn under ble_mutex. Returns nullptr if there is +// no active connection. Caller MUST bt_conn_unref() when done. +// +// Reading `active_conn` outside this lock races with disconnected_cb which can +// unref + null it on the BT RX thread — touching the freed pointer (even just +// to bt_conn_ref it) is a use-after-free. +static struct bt_conn *acquire_active_conn() +{ + struct bt_conn *conn = nullptr; + k_mutex_lock(&ble_mutex, K_FOREVER); + if (active_conn) { + conn = bt_conn_ref(active_conn); + } + k_mutex_unlock(&ble_mutex); + return conn; +} + +static bool bt_initialized = false; // bt_enable() called at most once +static bool ble_enabled = false; // set by setup(), cleared by shutdown() + +// Forward declarations — BT_GATT_SERVICE_DEFINE(mesh_svc, ...) is below, but +// read_fromradio() (defined earlier) needs to reference the service to notify +// on fromNum after each non-empty read. +#define FROMNUM_ATTR_IDX 2 +#define LOGRADIO_ATTR_IDX 9 +extern const struct bt_gatt_service_static mesh_svc; + +static void start_advertising(); // forward declaration (defined in advertising + // section below) + +// Work item for advertising restart after disconnect. +// +// disconnected_cb runs on the BT RX thread (the same thread that processes +// HCI Command Complete events). Calling bt_le_adv_start() → +// bt_hci_cmd_send_sync() directly from that thread deadlocks: the thread blocks +// on k_sem_take waiting for Command Complete, but it is the very thread that +// would process it. After 10 s the host panics with "Controller unresponsive, +// opcode 0x2006 timeout". +// +// Fix: submit a k_work item. The system workqueue runs bt_adv_restart_work_fn +// on its own thread → no deadlock. +static struct k_work adv_restart_work; + +static void adv_restart_work_fn(struct k_work *work) +{ + if (ble_enabled) { + start_advertising(); + } +} + +// CCC state: 0=off, BT_GATT_CCC_NOTIFY=notify, BT_GATT_CCC_INDICATE=indicate +static uint16_t fromnum_ccc_val = 0; +static uint16_t logradio_ccc_val = 0; + +// Scratch buffers — only one BLE operation at a time +static uint8_t fromRadioBytes[meshtastic_FromRadio_size]; +static size_t fromRadioLen = 0; +static uint8_t toRadioBytes[meshtastic_ToRadio_size]; +static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; +static uint32_t fromNumValue = 0; + +// Deferred ToRadio processing +// +// write_toradio() runs on the BT RX workqueue thread (6 KB stack). Calling +// phoneAPI->handleToRadio() directly triggers handleStartConfig → +// getFiles("/", 10) → nanopb encode, which overflows the stack on the exact +// "Client wants config" write. Instead we copy the payload into a pending +// buffer under a mutex and let BleDeferredThread (running on the Meshtastic +// OSThread scheduler, 24 KB stack) do the actual call outside the lock. +// +// The mutex makes the producer/consumer handoff race-free — producer may +// overwrite a pending buffer the consumer hasn't read yet (dropped packet), +// but partial reads / torn writes are impossible. +K_MUTEX_DEFINE(pendingToRadioMutex); +static uint8_t pendingToRadioBuf[MAX_TO_FROM_RADIO_SIZE]; +static size_t pendingToRadioLen = 0; +static bool pendingToRadio = false; + +// Zombie-connection watchdog state. +// +// The nRF54L15 Zephyr 4.2.1 SW-LL occasionally fails to forward an +// LE Disconnection Complete event to the host: when iOS tears down the link +// (either explicitly by the user or via supervision timeout), the LL layer +// drops the connection but disconnected_cb never fires, active_conn stays +// non-null and advertising never restarts — the device vanishes from scans +// until power cycle. Track the connected timestamp and the last time we +// observed ATT traffic; a long ATT idle on an "active" connection means we +// are zombied. A cold reboot is the only path that reliably recovers (any +// bt_hci_cmd_send_sync after this state, e.g. bt_le_adv_start or +// bt_conn_disconnect, hangs in k_sem_take and later panics with "Controller +// unresponsive, opcode 0x2006 timeout"). +static uint32_t connect_time_ms = 0; +static uint32_t last_att_time_ms = 0; + +// ── BluetoothPhoneAPI +// ───────────────────────────────────────────────────────── + +class BluetoothPhoneAPI : public PhoneAPI +{ + virtual void onNowHasData(uint32_t fromRadioNum) override; + virtual bool checkIsConnected() override; + + public: + BluetoothPhoneAPI() { api_type = TYPE_BLE; } +}; + +static BluetoothPhoneAPI *phoneAPI = nullptr; + +// ── CCC change callbacks +// ────────────────────────────────────────────────────── + +static void fromnum_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + fromnum_ccc_val = value; + LOG_INFO("BLE fromNum CCC: %u", value); +} + +static void logradio_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + logradio_ccc_val = value; + LOG_INFO("BLE logRadio CCC: %u", value); +} + +// ── GATT attribute callbacks +// ────────────────────────────────────────────────── + +static ssize_t read_fromnum(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) +{ + LOG_INFO("GATT read_fromnum: fromNum=%u offset=%u", fromNumValue, offset); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &fromNumValue, sizeof(fromNumValue)); +} + +static ssize_t read_fromradio(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) +{ + if (offset == 0) { + // First chunk: pull the next packet from the queue. + // Subsequent chunks (offset > 0) are ATT_READ_BLOB continuations of the + // same value and must reuse fromRadioBytes untouched. + fromRadioLen = phoneAPI ? phoneAPI->getFromRadio(fromRadioBytes) : 0; + LOG_DEBUG("GATT read_fromradio len=%u", (unsigned)fromRadioLen); + } + last_att_time_ms = k_uptime_get_32(); + return bt_gatt_attr_read(conn, attr, buf, len, offset, fromRadioBytes, fromRadioLen); +} + +static ssize_t read_logradio(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) +{ + // logRadio is write-only from the device side (notify/indicate). + // Return an empty read so GATT discovery doesn't fail with NOT_PERMITTED. + return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); +} + +static ssize_t write_toradio(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, + uint16_t offset, uint8_t flags) +{ + // Writes >MTU-3 arrive here with offset=0 and + // flags=BT_GATT_WRITE_FLAG_EXECUTE after Zephyr reassembles the ATT Prepare + // Write fragments (CONFIG_BT_ATT_PREPARE_COUNT>0). Single writes arrive with + // flags=0. + LOG_DEBUG("GATT write_toradio len=%u flags=0x%x", len, flags); + if (offset != 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + // Reject any write that won't fit in the dedup buffer (lastToRadio) or the + // pending handoff buffer (pendingToRadioBuf), both sized + // MAX_TO_FROM_RADIO_SIZE. Returning success while silently dropping a + // payload would let the phone believe a config write was applied. + if (len > sizeof(toRadioBytes) || len > MAX_TO_FROM_RADIO_SIZE) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + // Deduplicate — drop packet if identical to the last one we processed + if (memcmp(lastToRadio, buf, len) != 0) { + memcpy(lastToRadio, buf, len); + if (len < MAX_TO_FROM_RADIO_SIZE) { + memset(lastToRadio + len, 0, MAX_TO_FROM_RADIO_SIZE - len); + } + // Defer handleToRadio() to BleDeferredThread (24 KB stack). + // Running it here on bt_workq (6 KB) overflows during handleStartConfig. + // Always overwrite pending — we already dedup'd above via lastToRadio, + // so any new write here is genuinely new data that must be delivered. + k_mutex_lock(&pendingToRadioMutex, K_FOREVER); + memcpy(pendingToRadioBuf, buf, len); + pendingToRadioLen = len; + pendingToRadio = true; + k_mutex_unlock(&pendingToRadioMutex); + } + last_att_time_ms = k_uptime_get_32(); + return (ssize_t)len; +} + +// ── GATT service definition (static, linked at compile time) +// ────────────────── +// +// Attribute indices (0-based): +// [0] Primary Service declaration +// [1] fromNum characteristic declaration +// [2] fromNum value ← notify target (FROMNUM_ATTR_IDX) +// [3] fromNum CCC descriptor +// [4] fromRadio characteristic declaration +// [5] fromRadio value +// [6] toRadio characteristic declaration +// [7] toRadio value +// [8] logRadio characteristic declaration +// [9] logRadio value ← notify target (LOGRADIO_ATTR_IDX) +// [10] logRadio CCC descriptor + +// All user characteristics require authenticated encryption (MITM passkey) +// before the client can read/write. This mirrors the nrf52 +// SECMODE_ENC_WITH_MITM service permission. The stack returns "Insufficient +// Authentication" on the first access attempt, prompting the client to pair +// with the configured PIN. +#define MESH_PERM_READ (BT_GATT_PERM_READ | BT_GATT_PERM_READ_AUTHEN) +#define MESH_PERM_WRITE (BT_GATT_PERM_WRITE | BT_GATT_PERM_WRITE_AUTHEN) + +BT_GATT_SERVICE_DEFINE(mesh_svc, BT_GATT_PRIMARY_SERVICE(&mesh_svc_uuid.uuid), + + // fromNum: READ | NOTIFY — packet-counter triggers phone to read fromRadio + BT_GATT_CHARACTERISTIC(&fromnum_uuid.uuid, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, MESH_PERM_READ, + read_fromnum, NULL, &fromNumValue), + BT_GATT_CCC(fromnum_ccc_changed, MESH_PERM_READ | MESH_PERM_WRITE), + + // fromRadio: READ — phone polls this after receiving a fromNum notification + BT_GATT_CHARACTERISTIC(&fromradio_uuid.uuid, BT_GATT_CHRC_READ, MESH_PERM_READ, read_fromradio, NULL, + NULL), + + // toRadio: WRITE — phone sends protobuf packets to the device + BT_GATT_CHARACTERISTIC(&toradio_uuid.uuid, BT_GATT_CHRC_WRITE, MESH_PERM_WRITE, NULL, write_toradio, NULL), + + // logRadio: READ | NOTIFY | INDICATE — log stream to phone when connected + BT_GATT_CHARACTERISTIC(&logradio_uuid.uuid, + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE, MESH_PERM_READ, + read_logradio, NULL, NULL), + BT_GATT_CCC(logradio_ccc_changed, MESH_PERM_READ | MESH_PERM_WRITE), ); + +// ── Advertising +// ─────────────────────────────────────────────────────────────── +// +// Use legacy advertising (bt_le_adv_start / HCI 0x2006 path). +// +// History: we previously used bt_le_ext_adv_create (true extended advertising) +// because bt_le_adv_start() with CONFIG_BT_EXT_ADV=y was translated internally +// to the extended HCI path with LEGACY-bit (0x2036), which produced +// non-connectable PDUs on the nRF54L15 SW-LL. The true extended path +// (0x203x, AUX_ADV_IND) was connectable but caused two problems: +// 1. iOS CoreBluetooth does not reliably complete GATT after connecting via +// extended advertising (zero ATT PDUs observed in all test sessions). +// 2. After each connection the controller auto-stops the advertising set, and +// the subsequent bt_le_ext_adv_delete() sends LE Remove Advertising Set +// (0x203c) which times out → kernel oops at hci_core.c:506. +// +// With CONFIG_BT_EXT_ADV=n the host uses pure legacy HCI commands — the same +// path Nordic NCS uses in all nRF54L15 examples (peripheral_uart, +// peripheral_lbs) and which is universally iOS-compatible. The legacy data +// payload is 31 bytes: +// FLAGS (3B) + UUID128 (18B) = 21B in adv; NAME in scan-response (17B). + +static void start_advertising() +{ + // IMPORTANT: BT_DATA_BYTES() uses C99 compound literals that GCC C++ treats + // as temporaries; with -Os the compiler may elide writes, leaving stack + // uninitialized. Use static const arrays for stable data (flags, UUID) + // and a runtime pointer for the dynamic device name. + static const uint8_t adv_flags_val[] = {BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR}; + static const uint8_t adv_uuid128_val[] = {MESH_SVC_UUID_VAL}; + + const char *name = bt_get_name(); + size_t full_name_len = strlen(name); + + // Legacy scan-response payload is 31 bytes total. Each AD entry costs 2 + // bytes (length + type), leaving 29 bytes for the name. With + // CONFIG_BT_DEVICE_NAME_MAX=32 the name can exceed that — truncate and + // mark as SHORTENED so bt_le_adv_start() doesn't reject the payload. + constexpr size_t LEGACY_SCAN_RSP_NAME_MAX = 31 - 2; + bool name_shortened = full_name_len > LEGACY_SCAN_RSP_NAME_MAX; + uint8_t name_len = (uint8_t)(name_shortened ? LEGACY_SCAN_RSP_NAME_MAX : full_name_len); + + // Primary advertising data: FLAGS + Meshtastic service UUID128 (21 bytes + // total) + struct bt_data ad[] = { + {BT_DATA_FLAGS, sizeof(adv_flags_val), adv_flags_val}, + {BT_DATA_UUID128_ALL, sizeof(adv_uuid128_val), adv_uuid128_val}, + }; + // Scan response: device name (discovered after scan request) + struct bt_data sd[] = { + {(uint8_t)(name_shortened ? BT_DATA_NAME_SHORTENED : BT_DATA_NAME_COMPLETE), name_len, (const uint8_t *)name}, + }; + + // BT_LE_ADV_OPT_CONN = connectable legacy ADV_IND + stops after first + // connection (replaces deprecated + // CONNECTABLE|ONE_TIME in Zephyr 4.2.1; + // BT_LE_ADV_OPT_CONN = BIT(0)|BIT(1)) + // BT_LE_ADV_OPT_USE_IDENTITY = use static random identity address (stable + // across reboots) Advertising restart after disconnect is via + // adv_restart_work (system workqueue) so calling bt_le_adv_start() from the + // BT RX thread context is avoided. + int err = bt_le_adv_start(BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_USE_IDENTITY, BT_GAP_ADV_FAST_INT_MIN_2, + BT_GAP_ADV_FAST_INT_MAX_2, NULL), + ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); + + if (err == -EALREADY) { + return; + } + if (err) { + LOG_WARN("BLE adv start failed: %d", err); + } else { + LOG_INFO("BLE advertising as '%s'", bt_get_name()); + } +} + +static void stop_advertising() +{ + bt_le_adv_stop(); +} + +// ── Connection callbacks +// ────────────────────────────────────────────────────── + +static void connected_cb(struct bt_conn *conn, uint8_t err) +{ + if (err) { + LOG_WARN("BLE connection failed, err=0x%02x", err); + return; + } + + k_mutex_lock(&ble_mutex, K_FOREVER); + active_conn = bt_conn_ref(conn); + k_mutex_unlock(&ble_mutex); + + memset(lastToRadio, 0, sizeof(lastToRadio)); + connect_time_ms = k_uptime_get_32(); + last_att_time_ms = connect_time_ms; + + char addr[BT_ADDR_LE_STR_LEN]; + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + LOG_INFO("BLE connected: %s", addr); + + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); + bluetoothStatus->updateStatus(&newStatus); + + // nRF54L15-DK has no screen — cannot display a PIN to the user. + // Requesting BT_SECURITY_L2 causes the OS to show a pairing dialog that + // the user dismisses, triggering disconnect + advertising restart failure. + // Skip security negotiation; the Meshtastic app works over plain GATT. + // (Security can be re-enabled once a display or NFC OOB path is available.) +} + +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + LOG_INFO("BLE disconnected, reason=0x%02x", reason); + + k_mutex_lock(&ble_mutex, K_FOREVER); + if (active_conn) { + bt_conn_unref(active_conn); + active_conn = nullptr; + } + k_mutex_unlock(&ble_mutex); + + fromnum_ccc_val = 0; + logradio_ccc_val = 0; + connect_time_ms = 0; + last_att_time_ms = 0; + + if (phoneAPI) { + phoneAPI->close(); + } + memset(lastToRadio, 0, sizeof(lastToRadio)); + + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); + bluetoothStatus->updateStatus(&newStatus); + + // Schedule advertising restart via work queue — NOT from this callback + // directly. disconnected_cb runs on the BT RX thread; calling + // bt_le_adv_start() here would deadlock (see adv_restart_work comment above). + if (ble_enabled) { + k_work_submit(&adv_restart_work); + } +} + +#if defined(CONFIG_BT_SMP) +static void security_changed_cb(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) +{ + if (err == BT_SECURITY_ERR_PIN_OR_KEY_MISSING) { + // Phone has a stale bond (device was wiped/reflashed). Unpair the stale + // entry so the phone re-pairs cleanly on the next connection attempt. + LOG_WARN("BLE stale bond detected (key missing) — unpairing"); + bt_unpair(BT_ID_DEFAULT, bt_conn_get_dst(conn)); + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); + } else if (err) { + LOG_WARN("BLE security change failed: level=%d err=%d", (int)level, (int)err); + } else { + LOG_INFO("BLE security level %d established", (int)level); + } +} +#endif /* CONFIG_BT_SMP */ + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected_cb, + .disconnected = disconnected_cb, +#if defined(CONFIG_BT_SMP) + .security_changed = security_changed_cb, +#endif +}; + +// ── Pairing / auth callbacks +// ────────────────────────────────────────────────── + +#if defined(CONFIG_BT_SMP) +static uint32_t configuredPasskey; + +static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey) +{ + char passkey_str[7]; + snprintf(passkey_str, sizeof(passkey_str), "%06u", passkey); + configuredPasskey = passkey; + LOG_INFO("BLE pairing PIN: %s", passkey_str); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); + + std::string textkey(passkey_str); + meshtastic::BluetoothStatus pairingStatus(textkey); + bluetoothStatus->updateStatus(&pairingStatus); +} + +static void auth_cancel(struct bt_conn *conn) +{ + LOG_WARN("BLE pairing cancelled"); +} + +static struct bt_conn_auth_cb auth_cb = { + .passkey_display = auth_passkey_display, + .passkey_entry = NULL, + .cancel = auth_cancel, +}; + +static void pairing_complete_cb(struct bt_conn *conn, bool bonded) +{ + LOG_INFO("BLE pairing complete, bonded=%d", (int)bonded); + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); + bluetoothStatus->updateStatus(&newStatus); +} + +static void pairing_failed_cb(struct bt_conn *conn, enum bt_security_err reason) +{ + LOG_WARN("BLE pairing failed, reason=%d", (int)reason); + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); + bluetoothStatus->updateStatus(&newStatus); +} + +static struct bt_conn_auth_info_cb auth_info_cb = { + .pairing_complete = pairing_complete_cb, + .pairing_failed = pairing_failed_cb, +}; +#endif /* CONFIG_BT_SMP */ + +// ── BluetoothPhoneAPI methods +// ───────────────────────────────────────────────── + +void BluetoothPhoneAPI::onNowHasData(uint32_t fromRadioNum) +{ + PhoneAPI::onNowHasData(fromRadioNum); + fromNumValue = fromRadioNum; + + if (!(fromnum_ccc_val & BT_GATT_CCC_NOTIFY)) + return; + + // active_conn may be torn down on another thread while we're dispatching + // this notify — acquire under ble_mutex so disconnected_cb can't free the + // conn between the null check and bt_conn_ref. + struct bt_conn *conn = acquire_active_conn(); + if (!conn) + return; + bt_gatt_notify(conn, &mesh_svc.attrs[FROMNUM_ATTR_IDX], &fromNumValue, sizeof(fromNumValue)); + bt_conn_unref(conn); +} + +bool BluetoothPhoneAPI::checkIsConnected() +{ + return active_conn != nullptr; +} + +// ── Deferred ToRadio processor + zombie-connection watchdog ────────────────── +// +// write_toradio() runs on the BT RX workqueue thread (CONFIG_BT_RX_STACK_SIZE) +// and cannot execute phoneAPI->handleToRadio() directly: handleStartConfig +// recurses through nanopb encode + state machine init and overflows the RX +// stack. This thread runs on the Meshtastic OSThread scheduler (24 KB stack), +// picks up the pending ToRadio buffer flagged by write_toradio(), and calls +// handleToRadio() with plenty of headroom. +// +// Real-time fromNum notifications are sent synchronously from +// BluetoothPhoneAPI::onNowHasData() (called by PhoneAPI when new data is +// queued). +// +// Zombie-connection detection has two tiers: +// +// (1) Liveness probe. After IDLE_BEFORE_PROBE_MS without ATT traffic, send +// a bt_gatt_notify to fromNum every PROBE_INTERVAL_MS. If the +// controller replies -ENOTCONN the LL link is definitely dead but the +// host didn't forward LE Disconnection Complete → reboot. We avoid +// probing during normal activity so iOS isn't woken up unnecessarily +// (each probe wakes iOS → triggers a zero-byte FromRadio drain). +// +// (2) Hard watchdog. Absolute HARD_WATCHDOG_MS ceiling on ATT idle as a +// fallback if probes somehow don't detect the zombie. +class BleDeferredThread : public concurrency::OSThread +{ + static constexpr uint32_t IDLE_BEFORE_PROBE_MS = 30000; // 30 s: start probing + static constexpr uint32_t PROBE_INTERVAL_MS = 5000; // 5 s: between probes + static constexpr uint32_t HARD_WATCHDOG_MS = 60000; // 1 min: last resort + + uint32_t last_probe_ms = 0; + + public: + BleDeferredThread() : concurrency::OSThread("BleDeferred") {} + + protected: + int32_t runOnce() override + { + // Snapshot the pending ToRadio buffer under the mutex, then release + // the lock before calling into handleToRadio (which can be slow and + // must not block the BT RX thread producer). + uint8_t buf[MAX_TO_FROM_RADIO_SIZE]; + size_t n = 0; + bool have_pending = false; + k_mutex_lock(&pendingToRadioMutex, K_FOREVER); + if (pendingToRadio) { + memcpy(buf, pendingToRadioBuf, pendingToRadioLen); + n = pendingToRadioLen; + pendingToRadio = false; + have_pending = true; + } + k_mutex_unlock(&pendingToRadioMutex); + if (have_pending && phoneAPI) { + phoneAPI->handleToRadio(buf, n); + } + + // Take a reference to active_conn so it can't be freed underneath us + // if disconnected_cb fires on another thread while we're dispatching. + struct bt_conn *conn = acquire_active_conn(); + if (!conn || connect_time_ms == 0) { + if (conn) + bt_conn_unref(conn); + last_probe_ms = 0; + return 100; + } + + uint32_t now = k_uptime_get_32(); + uint32_t att_idle = now - last_att_time_ms; + + // Liveness probe — only when ATT has been quiet for a while. + if (att_idle > IDLE_BEFORE_PROBE_MS && (now - last_probe_ms) >= PROBE_INTERVAL_MS && + (fromnum_ccc_val & BT_GATT_CCC_NOTIFY)) { + last_probe_ms = now; + int err = bt_gatt_notify(conn, &mesh_svc.attrs[FROMNUM_ATTR_IDX], &fromNumValue, sizeof(fromNumValue)); + if (err == -ENOTCONN) { + LOG_WARN("BLE zombie (probe ENOTCONN); rebooting"); + bt_conn_unref(conn); + k_sleep(K_MSEC(50)); // flush log + sys_reboot(SYS_REBOOT_COLD); + } + } + bt_conn_unref(conn); + + // Hard ceiling — last-resort reboot if probes miss the zombie. + if (att_idle > HARD_WATCHDOG_MS && (now - connect_time_ms) > HARD_WATCHDOG_MS) { + LOG_WARN("BLE zombie (hard watchdog %us); rebooting", HARD_WATCHDOG_MS / 1000); + k_sleep(K_MSEC(50)); + sys_reboot(SYS_REBOOT_COLD); + } + return 100; + } +}; + +static BleDeferredThread *bleDeferredThread = nullptr; + +// ── BT stack pre-initializer (call from main thread before OSThreads start) ── +// +// bt_enable() requires substantially more stack than a Meshtastic OSThread +// (PowerFSMThread) provides — calling it there causes a stack overflow. +// Call this from nrf54l15Setup() (main Zephyr thread, CONFIG_MAIN_STACK_SIZE) +// so that by the time NRF54L15Bluetooth::setup() runs from PowerFSMThread, +// bt_initialized is already true and bt_enable() is skipped. + +void nrf54l15_bt_preinit() +{ + if (!bt_initialized) { + int err = bt_enable(NULL); + if (err) { + LOG_ERROR("BLE pre-init failed: %d", err); + return; + } + bt_initialized = true; + LOG_INFO("BLE stack pre-initialized on main thread"); + + // Phase 7: load bonding keys from LittleFS (/lfs/bt_settings). + // LittleFS is already mounted by fsInit() before nrf54l15Setup() runs. + // On first boot the file doesn't exist — settings_load() returns 0 (OK). + // On subsequent boots, previously bonded peers are restored so the + // phone can reconnect without re-pairing. + err = settings_load(); + if (err) { + LOG_WARN("settings_load failed: %d (OK on first boot)", err); + } else { + LOG_INFO("BT settings loaded from /lfs/bt_settings"); + } + } +} + +// ── NRF54L15Bluetooth public methods ───────────────────────────────────────── + +// Shared init: idempotent setup of work item, OSThread, auth callbacks, +// bt_enable, and device name. Leaves advertising control to the caller. +static bool nrf54l15_bt_init_common() +{ + k_work_init(&adv_restart_work, adv_restart_work_fn); + + if (!bleDeferredThread) { + bleDeferredThread = new BleDeferredThread(); + } + + if (!phoneAPI) { + phoneAPI = new BluetoothPhoneAPI(); + } + +#if defined(CONFIG_BT_SMP) + // NO_PIN is unsupported on this platform: the mesh GATT permissions are + // declared with BT_GATT_PERM_*_AUTHEN, prj.conf sets + // CONFIG_BT_SMP_ENFORCE_MITM=y, and the build pulls in the SMP/passkey path. + // If a user requested NO_PIN we'd register no auth callbacks → no display + // path for the passkey → every GATT access returns BT_ATT_ERR_AUTHENTICATION + // and the link is unusable. Fall back to RANDOM_PIN behavior with a warning + // instead of leaving BLE silently broken. + if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { + LOG_WARN("BLE: NO_PIN not supported on nRF54L15-DK (MITM-only build); " + "treating as RANDOM_PIN"); + } + + bt_conn_auth_cb_register(&auth_cb); + bt_conn_auth_info_cb_register(&auth_info_cb); + + // FIXED_PIN — register the configured passkey so the mobile app prompts + // the user for that specific number instead of a random display-only PIN. + // RANDOM_PIN (and clamped NO_PIN) keeps the default behavior: Zephyr + // generates a fresh passkey on each pairing attempt and fires + // auth_passkey_display with it. + if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN) { + configuredPasskey = config.bluetooth.fixed_pin; + int rc = bt_passkey_set(configuredPasskey); + if (rc) { + LOG_WARN("bt_passkey_set(%u) failed: %d", configuredPasskey, rc); + } else { + LOG_INFO("BLE fixed PIN: %06u", configuredPasskey); + } + } else { + bt_passkey_set(BT_PASSKEY_INVALID); // random per-pair + } +#endif /* CONFIG_BT_SMP */ + + if (!bt_initialized) { + int err = bt_enable(NULL); + if (err) { + LOG_ERROR("BLE enable failed: %d", err); + return false; + } + bt_initialized = true; + LOG_INFO("BLE stack enabled"); + } + + bt_set_name(getDeviceName()); + return true; +} + +void NRF54L15Bluetooth::setup() +{ + LOG_INFO("NRF54L15Bluetooth::setup()"); + if (!nrf54l15_bt_init_common()) { + return; + } + ble_enabled = true; + start_advertising(); +} + +void NRF54L15Bluetooth::shutdown() +{ + LOG_INFO("NRF54L15Bluetooth::shutdown()"); + ble_enabled = false; + stop_advertising(); + + struct bt_conn *conn = acquire_active_conn(); + if (conn) { + bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + bt_conn_unref(conn); + } +} + +void NRF54L15Bluetooth::startDisabled() +{ + // Initialize BT stack but leave advertising off until resumeAdvertising(). + if (!nrf54l15_bt_init_common()) { + return; + } + ble_enabled = false; + LOG_INFO("BLE initialized, advertising stopped (startDisabled)"); +} + +void NRF54L15Bluetooth::resumeAdvertising() +{ + ble_enabled = true; + start_advertising(); +} + +void NRF54L15Bluetooth::clearBonds() +{ + LOG_INFO("BLE clear bonds"); + bt_unpair(BT_ID_DEFAULT, BT_ADDR_LE_ANY); +} + +bool NRF54L15Bluetooth::isConnected() +{ + return active_conn != nullptr; +} + +int NRF54L15Bluetooth::getRssi() +{ + return 0; // TODO: Zephyr has no direct bt_conn_get_rssi; use HCI RSSI read + // command +} + +void NRF54L15Bluetooth::sendLog(const uint8_t *logMessage, size_t length) +{ + if (length > 512 || logradio_ccc_val == 0) { + return; + } + // Acquire a reference under ble_mutex so disconnected_cb can't free the + // connection between the null check and bt_gatt_notify. + struct bt_conn *conn = acquire_active_conn(); + if (!conn) { + return; + } + // Send as notify regardless of whether client subscribed to NOTIFY or + // INDICATE — bt_gatt_indicate() requires a params struct with a callback; + // notify is simpler and the app accepts both. Change to indicate if + // compatibility issues arise. + bt_gatt_notify(conn, &mesh_svc.attrs[LOGRADIO_ATTR_IDX], logMessage, (uint16_t)length); + bt_conn_unref(conn); +} diff --git a/src/platform/nrf54l15/NRF54L15Bluetooth.h b/src/platform/nrf54l15/NRF54L15Bluetooth.h new file mode 100644 index 000000000..499e91f98 --- /dev/null +++ b/src/platform/nrf54l15/NRF54L15Bluetooth.h @@ -0,0 +1,29 @@ +// NRF54L15Bluetooth.h — Zephyr BLE backend for nRF54L15 +// +// Implements the same interface as NRF52Bluetooth (same method names and +// signatures) so main.cpp and AdminModule can use nrf52Bluetooth pointer +// without knowing the underlying implementation. +// +// GATT profile is identical to the nRF52 implementation: +// Service: MESH_SERVICE_UUID +// toRadio: TORADIO_UUID (WRITE) +// fromRadio: FROMRADIO_UUID (READ) +// fromNum: FROMNUM_UUID (READ | NOTIFY) +// logRadio: LOGRADIO_UUID (READ | NOTIFY | INDICATE) + +#pragma once + +#include "BluetoothCommon.h" + +class NRF54L15Bluetooth : public BluetoothApi +{ + public: + void setup(); + void shutdown(); + void startDisabled(); + void resumeAdvertising(); + void clearBonds(); + bool isConnected(); + int getRssi(); + void sendLog(const uint8_t *logMessage, size_t length); +}; diff --git a/src/platform/nrf54l15/Nrf52SaadcLock.h b/src/platform/nrf54l15/Nrf52SaadcLock.h new file mode 100644 index 000000000..deb43a7a6 --- /dev/null +++ b/src/platform/nrf54l15/Nrf52SaadcLock.h @@ -0,0 +1,17 @@ +// Nrf52SaadcLock.h — stub for nRF54L15/Zephyr +// Power.cpp includes this when ARCH_NRF52 is defined. +// Phase 2: compile-only stub. +#pragma once + +#ifdef ARCH_NRF52 + +#include "concurrency/Lock.h" + +namespace concurrency +{ +/** Shared mutex for SAADC configuration and reads (VDD + battery analog path). + * On nRF54L15 ADC is handled differently; this is a compile-only stub. */ +extern Lock *nrf52SaadcLock; +} // namespace concurrency + +#endif diff --git a/src/platform/nrf54l15/Print.h b/src/platform/nrf54l15/Print.h new file mode 100644 index 000000000..5ff8ed016 --- /dev/null +++ b/src/platform/nrf54l15/Print.h @@ -0,0 +1,4 @@ +// Print.h — shim for nRF54L15/Zephyr +// Meshtastic includes separately; redirect to our Arduino.h shim. +#pragma once +#include "Arduino.h" diff --git a/src/platform/nrf54l15/SPI.h b/src/platform/nrf54l15/SPI.h new file mode 100644 index 000000000..b95890b0e --- /dev/null +++ b/src/platform/nrf54l15/SPI.h @@ -0,0 +1,62 @@ +/** + * SPI.h — Arduino SPI shim for Zephyr/nRF54L15 + * + * Provides the Arduino SPIClass interface backed by Zephyr's SPI API. The + * backing controller is SPIM00 (HP domain, 3.0 V); the implementation in + * nrf54l15_arduino.cpp binds to DEVICE_DT_GET(DT_NODELABEL(spi00)) and the + * bus is configured in zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay. + * RadioLib uses ArduinoHal which calls transfer() byte-by-byte. + * + * CS pin is handled by RadioLib via digitalWrite() — hardware CS is not used. + */ + +#pragma once + +#include "Arduino.h" +#include + +#define SPI_MODE0 0 +#define SPI_MODE1 1 +#define SPI_MODE2 2 +#define SPI_MODE3 3 + +struct SPISettings { + uint32_t clock; + uint8_t bitOrder; + uint8_t dataMode; + + // Arduino API allows `SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0))` — implicit form is intentional. + // cppcheck-suppress noExplicitConstructor + SPISettings(uint32_t clock = 4000000, uint8_t bitOrder = MSBFIRST, uint8_t dataMode = SPI_MODE0) + : clock(clock), bitOrder(bitOrder), dataMode(dataMode) + { + } +}; + +class SPIClass +{ + public: + void begin() {} + void begin(uint8_t sck, uint8_t miso, uint8_t mosi, uint8_t ss = 0xFF) {} + void end() {} + void beginTransaction(SPISettings) {} + void endTransaction() {} + void setBitOrder(uint8_t order) {} + void setDataMode(uint8_t mode) {} + void setClockDivider(uint8_t div) {} + void setFrequency(uint32_t freq) {} + + // Real Zephyr SPI implementation — defined in nrf54l15_arduino.cpp + uint8_t transfer(uint8_t data); + uint16_t transfer16(uint16_t data); + void transfer(void *buf, size_t count); + void transferBytes(const uint8_t *tx, uint8_t *rx, uint32_t count); + uint8_t transfer(uint8_t tx, uint8_t *rx, uint32_t count) + { + transferBytes(&tx, rx, count); + return rx ? rx[0] : 0; + } +}; + +extern SPIClass SPI; +extern SPIClass SPI1; diff --git a/src/platform/nrf54l15/Stream.h b/src/platform/nrf54l15/Stream.h new file mode 100644 index 000000000..a20f241da --- /dev/null +++ b/src/platform/nrf54l15/Stream.h @@ -0,0 +1,5 @@ +// Stream.h — shim for nRF54L15/Zephyr +// StreamAPI.h and other Meshtastic headers include . +// Redirect to our Arduino.h shim which defines the Stream base class. +#pragma once +#include "Arduino.h" diff --git a/src/platform/nrf54l15/Tone.h b/src/platform/nrf54l15/Tone.h new file mode 100644 index 000000000..68dc98b7b --- /dev/null +++ b/src/platform/nrf54l15/Tone.h @@ -0,0 +1,4 @@ +// Tone.h — shim for nRF54L15/Zephyr +// Tone functions are stubbed in Arduino.h; this header satisfies direct includes. +#pragma once +#include "Arduino.h" diff --git a/src/platform/nrf54l15/WProgram.h b/src/platform/nrf54l15/WProgram.h new file mode 100644 index 000000000..e602f1ee6 --- /dev/null +++ b/src/platform/nrf54l15/WProgram.h @@ -0,0 +1,5 @@ +// WProgram.h — shim for nRF54L15/Zephyr +// ArduinoThread (and other legacy Arduino libs) include . +// Redirect to our Arduino.h shim. +#pragma once +#include "Arduino.h" diff --git a/src/platform/nrf54l15/Wire.cpp b/src/platform/nrf54l15/Wire.cpp new file mode 100644 index 000000000..2fc0acab7 --- /dev/null +++ b/src/platform/nrf54l15/Wire.cpp @@ -0,0 +1,219 @@ +// Wire.cpp — Arduino TwoWire backed by Zephyr i2c30 (TWIM30 hardware). +// +// The pinctrl + clock-frequency are configured in +// zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay. Runtime begin()/setClock() +// are best-effort: setClock() goes through i2c_configure() to actually change +// the bus speed; begin() just verifies the device is ready. + +#include "Wire.h" +#include "configuration.h" + +#include +#include +#include +#include + +// Resolve the i2c30 node at compile time. If the overlay has not enabled +// i2c30, this evaluates to a NULL device pointer and every call short-circuits +// to a NACK return code — matching the prior compile-only stub behavior. +#define I2C_NODE DT_NODELABEL(i2c30) + +static const struct device *getI2CDevice() +{ +#if DT_NODE_HAS_STATUS(I2C_NODE, okay) + static const struct device *const dev = DEVICE_DT_GET(I2C_NODE); + return dev; +#else + return nullptr; +#endif +} + +// Wire/Wire1 instances are defined in nrf54l15_arduino.cpp alongside the +// other Arduino singletons (Serial, SPI, …). + +TwoWire::TwoWire() : txAddr(0), txLen(0), txBuf{}, rxLen(0), rxPos(0), rxBuf{} {} + +void TwoWire::begin() +{ + const struct device *dev = getI2CDevice(); + if (dev == nullptr) { + LOG_WARN("Wire.begin(): i2c30 not enabled in DT overlay"); + return; + } + if (!device_is_ready(dev)) { + LOG_WARN("Wire.begin(): i2c30 device not ready"); + return; + } + LOG_INFO("Wire.begin(): i2c30 ready"); +} + +void TwoWire::begin(uint8_t /*sda*/, uint8_t /*scl*/) +{ + // SDA/SCL fixed by overlay pinctrl — pin args ignored. + begin(); +} + +void TwoWire::begin(int /*sda*/, int /*scl*/, uint32_t freq) +{ + begin(); + if (freq) { + setClock(freq); + } +} + +void TwoWire::end() +{ + // No-op: Zephyr i2c devices stay initialized for the lifetime of the + // application. Runtime PM (zephyr,pm-device-runtime-auto in the DT) + // handles low-power transitions when idle. +} + +void TwoWire::setClock(uint32_t freq) +{ + const struct device *dev = getI2CDevice(); + if (dev == nullptr) { + return; + } + uint32_t speed; + if (freq >= 1000000U) { + speed = I2C_SPEED_FAST_PLUS; // 1 MHz + } else if (freq >= 400000U) { + speed = I2C_SPEED_FAST; // 400 kHz + } else { + speed = I2C_SPEED_STANDARD; // 100 kHz + } + uint32_t cfg = I2C_MODE_CONTROLLER | I2C_SPEED_SET(speed); + int rc = i2c_configure(dev, cfg); + if (rc) { + LOG_WARN("Wire.setClock(%u) failed: %d", (unsigned)freq, rc); + } +} + +void TwoWire::beginTransmission(uint8_t addr) +{ + txAddr = addr; + txLen = 0; +} + +size_t TwoWire::write(uint8_t data) +{ + if (txLen >= WIRE_BUFFER_LENGTH) { + return 0; // overflow — endTransmission() will return 1 + } + txBuf[txLen++] = data; + return 1; +} + +size_t TwoWire::write(const uint8_t *data, size_t n) +{ + size_t written = 0; + for (size_t i = 0; i < n; i++) { + if (write(data[i]) == 0) { + break; + } + written++; + } + return written; +} + +uint8_t TwoWire::endTransmission(bool /*stop*/) +{ + // Arduino return codes: + // 0 = success + // 1 = data-too-long (overflow caught in write()) + // 2 = NACK on address + // 3 = NACK on data + // 4 = other error + // 5 = timeout + if (txLen > WIRE_BUFFER_LENGTH) { + return 1; + } + const struct device *dev = getI2CDevice(); + if (dev == nullptr || !device_is_ready(dev)) { + return 4; + } + int rc = i2c_write(dev, txBuf, txLen, txAddr); + txLen = 0; + if (rc == 0) { + return 0; + } + if (rc == -EIO) { + return 2; // address NACK is the most common -EIO source on nrf-twim + } + if (rc == -ETIMEDOUT) { + return 5; + } + return 4; +} + +uint8_t TwoWire::requestFrom(uint8_t addr, uint8_t quantity, bool /*stop*/) +{ + rxLen = 0; + rxPos = 0; + if (quantity == 0) { + return 0; + } + if (quantity > WIRE_BUFFER_LENGTH) { + quantity = WIRE_BUFFER_LENGTH; + } + const struct device *dev = getI2CDevice(); + if (dev == nullptr || !device_is_ready(dev)) { + return 0; + } + + // If there is a pending TX (driver wrote register address via write() + // without an explicit endTransmission()), use i2c_write_read so the + // repeated-start path matches the typical "set register pointer then + // read N bytes" sensor protocol. + int rc; + if (txLen > 0) { + rc = i2c_write_read(dev, addr, txBuf, txLen, rxBuf, quantity); + txLen = 0; + } else { + rc = i2c_read(dev, rxBuf, quantity, addr); + } + if (rc) { + return 0; + } + rxLen = quantity; + return quantity; +} + +int TwoWire::available() +{ + return rxLen - rxPos; +} + +int TwoWire::read() +{ + if (rxPos >= rxLen) { + return -1; + } + return rxBuf[rxPos++]; +} + +int TwoWire::peek() +{ + if (rxPos >= rxLen) { + return -1; + } + return rxBuf[rxPos]; +} + +size_t TwoWire::readBytes(uint8_t *buf, size_t len) +{ + size_t n = 0; + while (n < len) { + int b = read(); + if (b < 0) { + break; + } + buf[n++] = (uint8_t)b; + } + return n; +} + +TwoWire::operator bool() const +{ + return getI2CDevice() != nullptr; +} diff --git a/src/platform/nrf54l15/Wire.h b/src/platform/nrf54l15/Wire.h new file mode 100644 index 000000000..2c8f66dff --- /dev/null +++ b/src/platform/nrf54l15/Wire.h @@ -0,0 +1,83 @@ +/** + * Wire.h — Arduino TwoWire (I2C) shim for Zephyr / nRF54L15. + * + * Bus binding: the Zephyr device tree alias `i2c30` (TWIM30 hardware + * peripheral, HP domain, 3.0 V) is resolved at compile time via + * DEVICE_DT_GET in Wire.cpp. SDA/SCL pins are configured in the board + * overlay via pinctrl — `begin(sda, scl)` overloads are accepted for + * Arduino API compatibility but the pin arguments are ignored. + * + * Buffer sizes are sized for the worst-case I2C consumer we plan to use + * (NXP SE050 secure element, ~256-byte T=1 frames). BMP280 / INA228 / + * SHT40 / INA3221 read in single-digit bytes and fit trivially. + */ + +#pragma once + +#include "Arduino.h" +#include +#include + +#ifndef WIRE_BUFFER_LENGTH +#define WIRE_BUFFER_LENGTH 256 +#endif + +class TwoWire +{ + public: + TwoWire(); + + // ── Bus lifecycle ───────────────────────────────────────────────── + // begin() variants — pin arguments are accepted for API compatibility + // but ignored: SDA/SCL are fixed by the Zephyr overlay pinctrl. freq + // is also fixed by overlay clock-frequency (use setClock() at runtime). + void begin(); + void begin(uint8_t sda, uint8_t scl); + void begin(int sda, int scl, uint32_t freq); + void end(); + + void setClock(uint32_t freq); + void setClockStretchLimit(uint32_t) {} // no-op on TWIM hardware + + // ── Master write ───────────────────────────────────────────────── + void beginTransmission(uint8_t addr); + void beginTransmission(int addr) { beginTransmission((uint8_t)addr); } + // Return codes (Arduino convention): + // 0 = success, 1 = data-too-long, 2 = NACK on addr, 3 = NACK on data, + // 4 = other error, 5 = timeout. + uint8_t endTransmission(bool stop = true); + uint8_t endTransmission(uint8_t stop) { return endTransmission(stop != 0); } + + size_t write(uint8_t data); + size_t write(const uint8_t *data, size_t n); + + // ── Master read ────────────────────────────────────────────────── + uint8_t requestFrom(uint8_t addr, uint8_t quantity, bool stop = true); + uint8_t requestFrom(uint8_t addr, uint8_t quantity, uint8_t stop) { return requestFrom(addr, quantity, stop != 0); } + uint8_t requestFrom(int addr, int quantity, int stop = 1) { return requestFrom((uint8_t)addr, (uint8_t)quantity, stop != 0); } + + int available(); + int read(); + int peek(); + size_t readBytes(uint8_t *buf, size_t len); + size_t readBytes(char *buf, size_t len) { return readBytes((uint8_t *)buf, len); } + void flush() {} + + // Slave callbacks unsupported — peripheral-only stub. + void onReceive(void (*)(int)) {} + void onRequest(void (*)(void)) {} + + operator bool() const; + + private: + uint8_t txAddr; + uint16_t txLen; + uint8_t txBuf[WIRE_BUFFER_LENGTH]; + + uint16_t rxLen; + uint16_t rxPos; + uint8_t rxBuf[WIRE_BUFFER_LENGTH]; +}; + +extern TwoWire Wire; +extern TwoWire Wire1; // alias to Wire — only one I2C bus on this board diff --git a/src/platform/nrf54l15/architecture.h b/src/platform/nrf54l15/architecture.h new file mode 100644 index 000000000..3f2a4c4e0 --- /dev/null +++ b/src/platform/nrf54l15/architecture.h @@ -0,0 +1,79 @@ +#pragma once + +#define ARCH_NRF54L15 + +// +// Feature flags for nRF54L15. +// +// The HAS_* macros below are Meshtastic's compile-time feature gate: every +// optional subsystem (BLE, screen, I2C, GPS, buttons, telemetry, sensors, +// radio, CPU shutdown, ...) is wrapped in `#if HAS_FOO` so a given board +// only pays for the features it actually ships. On memory-tight MCUs this +// is not cosmetic — it's the difference between a binary that fits in +// flash and one that doesn't, and between a build that links and one that +// drags in drivers for hardware the board doesn't have. Defaulting to 0 +// here (rather than inheriting nRF52 defaults) is deliberate: the +// nRF54L15-DK is a bare dev kit with no screen, no I2C sensors, no GPS, +// no user buttons — so every HAS_* flag starts off and gets flipped on +// explicitly by variants that add that hardware. +// +// Feature flags are also the cleanest way to absorb platform divergence +// without sprinkling `#ifdef ARCH_NRF54L15` across shared code. Anywhere +// a subsystem can be conditionally compiled via HAS_*, prefer that over +// per-arch guards: it keeps the core code arch-agnostic, makes it trivial +// to bring up the next board (flip the flags, don't patch call sites), +// and keeps the "does this platform support X?" question answerable by +// reading one file instead of grepping the tree. BLE in particular is +// deferred to Phase 2 on this port — the nRF54L15 uses MPSL/Zephyr BLE +// APIs rather than the Adafruit SoftDevice stack used by nRF52840 — so +// while HAS_BLUETOOTH defaults to 1, the actual implementation lives in +// NRF54L15Bluetooth.cpp behind its own Zephyr Kconfig gates. +// + +#ifndef HAS_BLUETOOTH +#define HAS_BLUETOOTH 1 +#endif +#ifndef HAS_SCREEN +#define HAS_SCREEN 0 +#endif +#ifndef HAS_WIRE +#define HAS_WIRE 0 +#endif +#ifndef HAS_GPS +#define HAS_GPS 0 +#endif +#ifndef HAS_BUTTON +#define HAS_BUTTON 0 +#endif +#ifndef HAS_TELEMETRY +#define HAS_TELEMETRY 0 +#endif +#ifndef HAS_SENSOR +#define HAS_SENSOR 0 +#endif +#ifndef HAS_RADIO +#define HAS_RADIO 1 +#endif +#ifndef HAS_CPU_SHUTDOWN +#define HAS_CPU_SHUTDOWN 0 +#endif + +// ADC reference — nRF54L15 SAADC uses VDD/4 internal ref by default +#ifndef AREF_VOLTAGE +#define AREF_VOLTAGE 3.6 +#endif +#ifndef BATTERY_SENSE_RESOLUTION_BITS +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#endif + +// +// HW_VENDOR — maps build-time define to HardwareModel enum. +// PRIVATE_HW (255): the protobuf HardwareModel enum reserves DK / DIY boards +// without an SKU under this value; the nRF54L15-DK doesn't get a dedicated +// enum number. Variant manifest matches via custom_meshtastic_hw_model = 255. +// +#ifdef NRF54L15_DK +#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW +#else +#define HW_VENDOR meshtastic_HardwareModel_UNSET +#endif diff --git a/src/platform/nrf54l15/bluefruit.h b/src/platform/nrf54l15/bluefruit.h new file mode 100644 index 000000000..a9c0afe71 --- /dev/null +++ b/src/platform/nrf54l15/bluefruit.h @@ -0,0 +1,19 @@ +// bluefruit.h — stub for nRF54L15/Zephyr +// NodeDB.cpp includes this when ARCH_NRF52 is defined. +// Bluetooth is excluded (MESHTASTIC_EXCLUDE_BLUETOOTH=1); this satisfies +// the include chain without pulling in the Adafruit Bluefruit SDK. +#pragma once + +struct BLEPeripheral { + void clearBonds() {} +}; +struct BLECentral { + void clearBonds() {} +}; + +struct BlueFruitClass { + BLEPeripheral Periph; + BLECentral Central; +}; + +extern BlueFruitClass Bluefruit; diff --git a/src/platform/nrf54l15/main-nrf54l15.cpp b/src/platform/nrf54l15/main-nrf54l15.cpp new file mode 100644 index 000000000..5b7729b4d --- /dev/null +++ b/src/platform/nrf54l15/main-nrf54l15.cpp @@ -0,0 +1,210 @@ +/* + * main-nrf54l15.cpp — Platform entry points for Nordic nRF54L15 + * + * Adapted from src/platform/nrf52/main-nrf52.cpp. + * SoftDevice, Adafruit BLE, and nRFCrypto are NOT available on nRF54L15. + * Phase 2 will add proper BLE via Zephyr MPSL APIs. + * + * TODO items are marked with "TODO(nrf54l15):" + */ + +#include "configuration.h" +#include +#include +#include +#include + +#include "NodeDB.h" +#include "PowerMon.h" +#include "Router.h" +#include "error.h" +#include "main.h" +#include "mesh/MeshService.h" +#include "meshUtils.h" +#include "power.h" +#include + +// ── Watchdog ────────────────────────────────────────────────────────────── +// TODO(nrf54l15): nRF54L15 has a WDT peripheral but nrfx_wdt driver support +// may differ depending on the Zephyr SDK version. Enable once confirmed. +#define APP_WATCHDOG_SECS 90 +static bool watchdog_running = false; + +static inline void watchdog_feed() {} // TODO(nrf54l15): replace with real WDT feed + +// ── Weak variant hooks ──────────────────────────────────────────────────── +void variant_shutdown() __attribute__((weak)); +void variant_shutdown() {} + +void variant_nrf54l15LoopHook(void) __attribute__((weak)); +void variant_nrf54l15LoopHook(void) {} + +// ── PowerHAL ───────────────────────────────────────────────────────────── +bool powerHAL_isVBUSConnected() +{ + // TODO(nrf54l15): nRF54L15 has a USB POWER peripheral — read USBREGSTATUS + return false; +} + +bool powerHAL_isPowerLevelSafe() +{ + // TODO(nrf54l15): implement SAADC VDD measurement similar to nRF52 + return true; +} + +void powerHAL_platformInit() +{ + // TODO(nrf54l15): configure POF comparator and analog reference if needed +} + +// ── Utilities ───────────────────────────────────────────────────────────── +bool loopCanSleep() +{ + return !Serial; +} + +void updateBatteryLevel(uint8_t level) +{ + (void)level; +} + +void __attribute__((noreturn)) __assert_func(const char *file, int line, const char *func, const char *failedexpr) +{ + LOG_ERROR("assert failed %s: %d, %s, test=%s", file, line, func, failedexpr); + NVIC_SystemReset(); +} + +void getMacAddr(uint8_t *dmac) +{ + // TODO(nrf54l15): verify FICR register layout for nRF54L15. + // nRF52840 uses NRF_FICR->DEVICEADDR[0/1]; nRF54L15 Zephyr HAL may differ. +#if defined(NRF_FICR) + const uint8_t *src = (const uint8_t *)NRF_FICR->DEVICEADDR; + dmac[5] = src[0]; + dmac[4] = src[1]; + dmac[3] = src[2]; + dmac[2] = src[3]; + dmac[1] = src[4]; + dmac[0] = src[5] | 0xc0; +#else + // Fallback: fixed placeholder until Zephyr FICR path is confirmed + dmac[0] = 0xC2; + dmac[1] = 0xA7; + dmac[2] = 0x54; + dmac[3] = 0x15; + dmac[4] = 0x00; + dmac[5] = 0x01; +#endif +} + +// ── Bluetooth ───────────────────────────────────────────────────────────────── + +void setBluetoothEnable(bool enable) +{ + if (enable) { + static bool initialized = false; + if (!initialized) { + nrf54l15Bluetooth = new NRF54L15Bluetooth(); + nrf54l15Bluetooth->startDisabled(); + initialized = true; + } + if (nrf54l15Bluetooth) { + nrf54l15Bluetooth->resumeAdvertising(); + } + } else { + if (nrf54l15Bluetooth) { + nrf54l15Bluetooth->shutdown(); + } + } +} + +void clearBonds() +{ + if (!nrf54l15Bluetooth) { + nrf54l15Bluetooth = new NRF54L15Bluetooth(); + nrf54l15Bluetooth->setup(); + } + nrf54l15Bluetooth->clearBonds(); +} + +void enterDfuMode() +{ + // TODO(nrf54l15): nRF54L15 uses nRF Connect DFU (MCUboot/SUIT). + // Trigger via Zephyr boot_request_upgrade() or similar. + NVIC_SystemReset(); +} + +// ── printf via RTT ──────────────────────────────────────────────────────── +// TODO(nrf54l15): SEGGER_RTT may not be available with Zephyr; use printk() +// or a USB CDC console instead. Remove this override if it conflicts. +#ifdef SEGGER_RTT_PRINTF +int printf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + auto res = SEGGER_RTT_vprintf(0, fmt, &args); + va_end(args); + return res; +} +#endif + +// ── Deep sleep ──────────────────────────────────────────────────────────── +void cpuDeepSleep(uint32_t msecToWake) +{ +#if HAS_WIRE + Wire.end(); +#endif + SPI.end(); + if (Serial) + Serial.end(); + + variant_shutdown(); + + // TODO(nrf54l15): use Zephyr pm_system_suspend() or WFI for proper low-power + if (msecToWake != portMAX_DELAY) { + delay(msecToWake); + NVIC_SystemReset(); + } else { + // System off equivalent — halt + while (1) { + __WFI(); + } + } +} + +// ── Setup / Loop ────────────────────────────────────────────────────────── +// Forward declaration — defined in NRF54L15Bluetooth.cpp +void nrf54l15_bt_preinit(); + +void nrf54l15Setup() +{ + // nRF54L15 power peripheral layout differs from nRF52; RESETREAS not present here. + // TODO(Phase 3): use zephyr/drivers/hwinfo.h hwinfo_get_reset_cause() + LOG_DEBUG("Reset reason: (nRF54L15 power peripheral differs from nRF52, skipped)"); + + // TODO(nrf54l15): init SAADC, watchdog, and random seed via nrfx or Zephyr + // For now seed with a fixed value; replace with hardware entropy source. +#if defined(NRF_FICR) + randomSeed(analogRead(0) ^ (uint32_t)NRF_FICR->DEVICEADDR[0]); +#else + randomSeed(analogRead(0)); +#endif + + // Pre-initialize BT stack here on the main thread (CONFIG_MAIN_STACK_SIZE=8192). + // bt_enable() overflows the smaller PowerFSMThread stack when called later. + // NRF54L15Bluetooth::setup() checks bt_initialized and skips bt_enable() if true. + nrf54l15_bt_preinit(); +} + +void nrf54l15Loop() +{ + // First-call gate for the future WDT init — body will hold real init code, not just the bookkeeping flag. + // cppcheck-suppress duplicateConditionalAssign + if (!watchdog_running) { + // TODO(nrf54l15): enable WDT here + watchdog_running = true; + } + watchdog_feed(); + + variant_nrf54l15LoopHook(); +} diff --git a/src/platform/nrf54l15/nrf54l15_arduino.cpp b/src/platform/nrf54l15/nrf54l15_arduino.cpp new file mode 100644 index 000000000..43a3c11f8 --- /dev/null +++ b/src/platform/nrf54l15/nrf54l15_arduino.cpp @@ -0,0 +1,557 @@ +/** + * nrf54l15_arduino.cpp — Arduino shim implementations for Zephyr/nRF54L15 + * + * Provides concrete implementations for Print, HardwareSerial, GPIO, SPI, + * and String methods declared in Arduino.h / SPI.h. + * + * Phase 3: real GPIO via Zephyr GPIO API and real SPI via Zephyr SPI API. + * Pin numbering convention: P0.n = n, P1.n = 16+n, P2.n = 32+n. + */ + +#include "Arduino.h" +#include "SPI.h" +#include "Wire.h" +#include +#include +#include +#include +#include +#include +#include +// ── Bluefruit singleton stub (satisfies NodeDB.cpp ARCH_NRF52 path) ────────── +#include "bluefruit.h" +BlueFruitClass Bluefruit; + +// ── _fini stub — ARM newlib's __libc_fini_array references _fini, but ──────── +// Zephyr startup doesn't provide it. Provide a weak no-op so the linker +// is satisfied when C++ global dtors or atexit() pull in __libc_fini_array. +extern "C" void __attribute__((weak)) _fini(void) {} + +// ── SPI / Wire singletons ───────────────────────────────────────────────────── +SPIClass SPI; +SPIClass SPI1; +TwoWire Wire; +TwoWire Wire1; + +// ── HardwareSerial singletons ──────────────────────────────────────────────── +HardwareSerial Serial; +HardwareSerial Serial1; +HardwareSerial Serial2; + +// ── Timing functions — C linkage to match extern "C" declarations ──────────── +extern "C" uint32_t millis(void) +{ + return (uint32_t)k_uptime_get_32(); +} +extern "C" uint32_t micros(void) +{ + return (uint32_t)(k_uptime_get() * 1000ULL); +} +extern "C" void delay(uint32_t ms) +{ + k_sleep(K_MSEC(ms)); +} +extern "C" void delayMicroseconds(uint32_t us) +{ + k_sleep(K_USEC(us)); +} +extern "C" void yield(void) +{ + k_yield(); +} + +// ── NVIC_SystemReset — wraps __NVIC_SystemReset from CMSIS core_cm33.h ─────── +// core_cm33.h has #define NVIC_SystemReset __NVIC_SystemReset, so undef it +// before defining our own implementation to prevent macro expansion collision. +#pragma push_macro("NVIC_SystemReset") +#undef NVIC_SystemReset +extern "C" void NVIC_SystemReset(void) +{ + sys_reboot(SYS_REBOOT_COLD); +} +#pragma pop_macro("NVIC_SystemReset") + +// ── HardwareSerial::write ───────────────────────────────────────────────────── +size_t HardwareSerial::write(uint8_t c) +{ + // TODO(nrf54l15 Phase 3): route through Zephyr UART / USB-CDC console + // For now use printk so we at least get something over RTT/UART0 + printk("%c", (char)c); + return 1; +} + +size_t HardwareSerial::write(const uint8_t *buf, size_t n) +{ + for (size_t i = 0; i < n; i++) + printk("%c", (char)buf[i]); + return n; +} + +// ── Print::printf ───────────────────────────────────────────────────────────── +int Print::printf(const char *fmt, ...) +{ + char buf[256]; + va_list args; + va_start(args, fmt); + int n = vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + if (n > 0) + write((const uint8_t *)buf, (size_t)(n < (int)sizeof(buf) ? n : (int)sizeof(buf) - 1)); + return n; +} + +// ── strlcpy — BSD extension not in Zephyr newlib ──────────────────────────── +extern "C" size_t strlcpy(char *dst, const char *src, size_t size) +{ + size_t len = strlen(src); + if (size > 0) { + size_t copy = len < size - 1 ? len : size - 1; + memcpy(dst, src, copy); + dst[copy] = '\0'; + } + return len; +} + +// ── Print numeric helpers ───────────────────────────────────────────────────── +static size_t printNumber(Print &p, unsigned long n, uint8_t base) +{ + if (base == 0) + return p.write((uint8_t)n); + + char buf[8 * sizeof(long) + 1]; + char *end = buf + sizeof(buf) - 1; + *end = '\0'; + if (n == 0) { + *--end = '0'; + } else { + while (n > 0) { + unsigned long remainder = n % base; + *--end = (char)(remainder < 10 ? '0' + remainder : 'A' + remainder - 10); + n /= base; + } + } + return p.write((const uint8_t *)end, strlen(end)); +} + +static size_t printFloat(Print &p, double number, uint8_t digits) +{ + if (isnan(number)) + return p.print("nan"); + if (isinf(number)) + return p.print("inf"); + if (number > 4294967040.0 || number < -4294967040.0) + return p.print("ovf"); + + size_t n = 0; + if (number < 0.0) { + n += p.write('-'); + number = -number; + } + + // Round + double rounding = 0.5; + for (uint8_t i = 0; i < digits; i++) + rounding /= 10.0; + number += rounding; + + unsigned long int_part = (unsigned long)number; + double remainder = number - (double)int_part; + n += printNumber(p, int_part, 10); + if (digits > 0) { + n += p.write('.'); + for (uint8_t i = 0; i < digits; i++) { + remainder *= 10.0; + unsigned int d = (unsigned int)remainder; + n += p.write('0' + d); + remainder -= d; + } + } + return n; +} + +size_t Print::print(unsigned char n, int base) +{ + return printNumber(*this, n, base); +} +size_t Print::print(int n, int base) +{ + if (base == 10 && n < 0) { + size_t r = write('-'); + return r + printNumber(*this, (unsigned long)(-n), base); + } + return printNumber(*this, (unsigned long)n, base); +} +size_t Print::print(long n, int base) +{ + if (base == 10 && n < 0) { + size_t r = write('-'); + return r + printNumber(*this, (unsigned long)(-n), base); + } + return printNumber(*this, (unsigned long)n, base); +} +size_t Print::print(unsigned int n, int base) +{ + return printNumber(*this, n, base); +} +size_t Print::print(unsigned long n, int base) +{ + return printNumber(*this, n, base); +} +size_t Print::print(float n, int d) +{ + return printFloat(*this, n, d); +} +size_t Print::print(double n, int d) +{ + return printFloat(*this, n, d); +} + +// ── String::replace(String, String) ───────────────────────────────────────── +void String::replace(const String &from, const String &to) +{ + if (from.isEmpty() || !_buf) + return; + // Simple O(n²) replace — fine for typical Meshtastic string lengths + String result; + const char *p = _buf; + while (*p) { + if (strncmp(p, from.c_str(), from.length()) == 0) { + result += to; + p += from.length(); + } else { + result += *p++; + } + } + *this = result; +} + +// ═════════════════════════════════════════════════════════════════════════════ +// GPIO — Real Zephyr implementation (Phase 3) +// Pin mapping: P0.n = n (0-15), P1.n = 16+n (16-31), P2.n = 32+n (32-47) +// ═════════════════════════════════════════════════════════════════════════════ + +static const struct device *_gpio_dev_for_pin(uint32_t pin, gpio_pin_t *zpin) +{ + if (pin < 16) { + *zpin = (gpio_pin_t)pin; + return DEVICE_DT_GET(DT_NODELABEL(gpio0)); + } else if (pin < 32) { + *zpin = (gpio_pin_t)(pin - 16); + return DEVICE_DT_GET(DT_NODELABEL(gpio1)); + } else { + *zpin = (gpio_pin_t)(pin - 32); + return DEVICE_DT_GET(DT_NODELABEL(gpio2)); + } +} + +void pinMode(uint32_t pin, uint32_t mode) +{ + gpio_pin_t zpin; + const struct device *dev = _gpio_dev_for_pin(pin, &zpin); + if (!device_is_ready(dev)) + return; + + gpio_flags_t flags; + switch (mode) { + case OUTPUT: + flags = GPIO_OUTPUT_INACTIVE; + break; + case INPUT_PULLUP: + flags = GPIO_INPUT | GPIO_PULL_UP; + break; + case INPUT_PULLDOWN: + flags = GPIO_INPUT | GPIO_PULL_DOWN; + break; + default: + flags = GPIO_INPUT; + break; + } + gpio_pin_configure(dev, zpin, flags); +} + +// Bring-up diagnostics for the SX1262 wiring path. Off by default — enable by +// adding `-DNRF54L15_GPIO_DEBUG` to platformio.ini build_flags. Useful when +// validating CS/NRESET toggles after a wiring change, diagnosing a "stuck HIGH" +// BUSY before the first NRESET pulse, or tracing BUSY transitions during early +// boot. In normal operation these traces are noise (they bypass LOG level +// controls and print on every GPIO touch), so they are gated at compile time. +#ifdef NRF54L15_GPIO_DEBUG +#define GPIO_LOG_MAX 20 +static uint32_t _gpio_log_count = 0; +#endif + +void digitalWrite(uint32_t pin, uint32_t value) +{ +#ifdef NRF54L15_GPIO_DEBUG + // Before the very first NRESET pulse, snapshot BUSY state. + // If BUSY is already HIGH here, the chip never completed power-on calibration. + if (pin == 32 && value == 0) { + static bool _first_nreset = true; + if (_first_nreset) { + _first_nreset = false; + const struct device *bdev = DEVICE_DT_GET(DT_NODELABEL(gpio2)); + if (device_is_ready(bdev)) { + gpio_pin_configure(bdev, 3, GPIO_INPUT); // P2.03 = BUSY + int busy_before = gpio_pin_get(bdev, 3); + printk("[nrf54l15] BUSY before first NRESET = %d%s\n", busy_before, + busy_before ? " ← STUCK HIGH (chip damaged?)" : " ← LOW (chip OK)"); + } + } + } +#endif + + gpio_pin_t zpin; + const struct device *dev = _gpio_dev_for_pin(pin, &zpin); + if (!device_is_ready(dev)) { + // Genuine hardware/DTS misconfiguration — keep this regardless of the + // GPIO_DEBUG gate so it surfaces in production builds too. + printk("[GPIO] pin%u dev NOT READY\n", (unsigned)pin); + return; + } + gpio_pin_set(dev, zpin, (int)value); +#ifdef NRF54L15_GPIO_DEBUG + if ((pin == 37 || pin == 32) && _gpio_log_count < GPIO_LOG_MAX) { + // Read back the pin state to confirm it actually changed + int actual = gpio_pin_get(dev, zpin); + printk("[GPIO] pin%u → %u (read-back=%d)\n", (unsigned)pin, (unsigned)value, actual); + _gpio_log_count++; + } +#endif +} + +int digitalRead(uint32_t pin) +{ + gpio_pin_t zpin; + const struct device *dev = _gpio_dev_for_pin(pin, &zpin); + if (!device_is_ready(dev)) + return 0; + int v = gpio_pin_get(dev, zpin); +#ifdef NRF54L15_GPIO_DEBUG + // Log BUSY pin (35=P2.03) state changes + periodic updates for 10 seconds + if (pin == 35) { + static uint32_t busy_log_count = 0; + static int last_busy = -1; + static uint32_t first_read_ms = 0; + if (first_read_ms == 0) + first_read_ms = k_uptime_get_32(); + uint32_t elapsed_ms = k_uptime_get_32() - first_read_ms; + + // Always log state changes + if (v != last_busy) { + printk("[BUSY] %ums: state changed %d → %d\n", (unsigned)elapsed_ms, last_busy, v); + last_busy = v; + } + // Also log every 500ms for first 10 seconds so we can see timeline + if (elapsed_ms < 10000 && busy_log_count < 20 && (elapsed_ms / 500) > (busy_log_count)) { + printk("[BUSY] %ums: pin=%d (periodic)\n", (unsigned)elapsed_ms, v); + busy_log_count = (elapsed_ms / 500) + 1; + } + } +#endif + return v; +} + +// ─── attachInterrupt — supports up to NRF54L15_MAX_IRQS pins ──────────────── +#define NRF54L15_MAX_IRQS 8 + +struct _PinIrq { + struct gpio_callback cb; + voidFuncPtr user_cb; + const struct device *dev; + gpio_pin_t zpin; + bool used; +}; + +static _PinIrq _irq_table[NRF54L15_MAX_IRQS]; + +static void _gpio_irq_dispatch(const struct device *dev, struct gpio_callback *cb, uint32_t pins) +{ + _PinIrq *irq = CONTAINER_OF(cb, _PinIrq, cb); + if (irq->user_cb) + irq->user_cb(); +} + +void attachInterrupt(uint32_t pin, voidFuncPtr cb, int mode) +{ + gpio_pin_t zpin; + const struct device *dev = _gpio_dev_for_pin(pin, &zpin); + if (!device_is_ready(dev)) + return; + + // Find a free slot (or reuse existing registration for same pin) + _PinIrq *slot = nullptr; + for (int i = 0; i < NRF54L15_MAX_IRQS; i++) { + if (_irq_table[i].used && _irq_table[i].dev == dev && _irq_table[i].zpin == zpin) { + // Re-register: remove old callback first + gpio_remove_callback(dev, &_irq_table[i].cb); + slot = &_irq_table[i]; + break; + } + if (!slot && !_irq_table[i].used) + slot = &_irq_table[i]; + } + if (!slot) + return; // table full + + gpio_flags_t irq_flags; + switch (mode) { + case RISING: + irq_flags = GPIO_INT_EDGE_RISING; + break; + case FALLING: + irq_flags = GPIO_INT_EDGE_FALLING; + break; + default: + irq_flags = GPIO_INT_EDGE_BOTH; + break; + } + + slot->user_cb = cb; + slot->dev = dev; + slot->zpin = zpin; + slot->used = true; + + gpio_pin_configure(dev, zpin, GPIO_INPUT); + gpio_init_callback(&slot->cb, _gpio_irq_dispatch, BIT(zpin)); + gpio_add_callback(dev, &slot->cb); + gpio_pin_interrupt_configure(dev, zpin, irq_flags); +} + +void detachInterrupt(uint32_t pin) +{ + gpio_pin_t zpin; + const struct device *dev = _gpio_dev_for_pin(pin, &zpin); + for (int i = 0; i < NRF54L15_MAX_IRQS; i++) { + if (_irq_table[i].used && _irq_table[i].dev == dev && _irq_table[i].zpin == zpin) { + gpio_pin_interrupt_configure(dev, zpin, GPIO_INT_DISABLE); + gpio_remove_callback(dev, &_irq_table[i].cb); + _irq_table[i].used = false; + break; + } + } +} + +// ═════════════════════════════════════════════════════════════════════════════ +// SPI — Real Zephyr implementation using SPIM00 (HP domain, 3.0V) +// CS is handled by RadioLib via digitalWrite() — hardware CS not used. +// Mode 0 (CPOL=0, CPHA=0), MSB first. +// ═════════════════════════════════════════════════════════════════════════════ + +// Use SPIM00 (HP domain, 3.0V) — SPIM20 is 1.8V LP domain, incompatible with SX1262. +// Lazy-init: DEVICE_DT_GET in global scope fails when the extern symbol is +// not visible in this translation unit. Use a function-local static instead. +static const struct device *_spi00(void) +{ + static const struct device *dev = nullptr; + if (!dev) { + dev = DEVICE_DT_GET(DT_NODELABEL(spi00)); + if (!device_is_ready(dev)) { + printk("[nrf54l15] spi00 NOT READY\n"); + dev = nullptr; + } else { + printk("[nrf54l15] spi00 ready\n"); + } + } + return dev; +} + +// SPI config: Mode 0, MSB first, no hardware CS (RadioLib does it manually) +// +// SPIM00 base clock = 128 MHz (nRF54L15 default when NRF_CONFIG_CPU_FREQ_MHZ +// is not set; SystemInit() applies 128 MHz). Hardware prescaler must be EVEN +// and in [4, 126] (SPIM00_PRESCALER_DIVISOR_RANGE_MIN/MAX from MDK). +// 1 MHz → prescaler = 128 > 126 → NRFX_ERROR_INVALID_PARAM → -EIO on every +// transfer. Minimum valid frequency is 2 MHz (prescaler = 64). +static const struct spi_config _spi00_cfg = { + .frequency = 2000000U, // 2 MHz — minimum valid for SPIM00 at 128 MHz base + .operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_MSB, + .slave = 0, + .cs = {}, // CS = NULL → RadioLib handles CS via GPIO +}; + +// Static DMA buffers — stack-allocated bufs on nRF54L15 may not be reachable +// by SPIM20 EasyDMA. Static placement in .bss/.data is always in Global SRAM. +// rx_byte is pre-filled with 0xAA before every transfer so we can distinguish: +// 0xAA → DMA never wrote (EasyDMA can't reach the buffer) +// 0x00 → MISO actively driven LOW (chip in reset / bus fight) +// 0xFF → MISO floating HIGH +// other → real chip response +static uint8_t _spi_tx_byte __attribute__((aligned(4))); +static uint8_t _spi_rx_byte __attribute__((aligned(4))); + +// Dump the first SPI_DUMP_N byte exchanges so we can see what MISO returns. +#define SPI_DUMP_N 30 +static uint32_t _spi_dump_count = 0; + +uint8_t SPIClass::transfer(uint8_t data) +{ + const struct device *dev = _spi00(); + if (!dev) + return 0xFF; + + _spi_tx_byte = data; + _spi_rx_byte = 0xAA; // sentinel: if DMA doesn't write, we return 0xAA + + struct spi_buf tx_buf = {.buf = &_spi_tx_byte, .len = 1}; + struct spi_buf rx_buf = {.buf = &_spi_rx_byte, .len = 1}; + struct spi_buf_set tx_set = {.buffers = &tx_buf, .count = 1}; + struct spi_buf_set rx_set = {.buffers = &rx_buf, .count = 1}; + + static uint32_t spi_err_count = 0; + int ret = spi_transceive(dev, &_spi00_cfg, &tx_set, &rx_set); + if (ret != 0 && spi_err_count++ < 3) + printk("[SPI] err=%d tx=0x%02x\n", ret, data); + + if (_spi_dump_count < SPI_DUMP_N) { + printk("[SPI] #%u tx=0x%02x rx=0x%02x\n", (unsigned)_spi_dump_count, data, _spi_rx_byte); + _spi_dump_count++; + } + + return _spi_rx_byte; +} + +// Static DMA-safe buffers for transfer16 — same EasyDMA reachability concern +// applies as for the byte path: stack buffers from a caller thread may sit in +// per-thread RAM regions that EasyDMA cannot reach. +static uint8_t _spi_tx16[2] __attribute__((aligned(4))); +static uint8_t _spi_rx16[2] __attribute__((aligned(4))); + +uint16_t SPIClass::transfer16(uint16_t data) +{ + const struct device *dev = _spi00(); + if (!dev) + return 0xFFFF; + + _spi_tx16[0] = (uint8_t)(data >> 8); + _spi_tx16[1] = (uint8_t)(data & 0xFF); + _spi_rx16[0] = 0xAA; + _spi_rx16[1] = 0xAA; + struct spi_buf tx_buf = {.buf = _spi_tx16, .len = 2}; + struct spi_buf rx_buf = {.buf = _spi_rx16, .len = 2}; + struct spi_buf_set tx_set = {.buffers = &tx_buf, .count = 1}; + struct spi_buf_set rx_set = {.buffers = &rx_buf, .count = 1}; + spi_transceive(dev, &_spi00_cfg, &tx_set, &rx_set); + return ((uint16_t)_spi_rx16[0] << 8) | _spi_rx16[1]; +} + +void SPIClass::transferBytes(const uint8_t *tx, uint8_t *rx, uint32_t count) +{ + if (!count) + return; + const struct device *dev = _spi00(); + if (!dev) + return; + // Zephyr requires non-const buf pointer; cast is safe for tx-only direction + struct spi_buf tx_buf = {.buf = const_cast(tx), .len = count}; + struct spi_buf rx_buf = {.buf = rx, .len = count}; + struct spi_buf_set tx_set = {.buffers = &tx_buf, .count = 1}; + struct spi_buf_set rx_set = {.buffers = rx_buf.buf ? &rx_buf : nullptr, .count = rx_buf.buf ? 1U : 0U}; + spi_transceive(dev, &_spi00_cfg, &tx_set, rx ? &rx_set : nullptr); +} + +void SPIClass::transfer(void *buf, size_t count) +{ + if (!count || !buf) + return; + transferBytes(reinterpret_cast(buf), reinterpret_cast(buf), (uint32_t)count); +} diff --git a/src/platform/nrf54l15/nrf54l15_main.cpp b/src/platform/nrf54l15/nrf54l15_main.cpp new file mode 100644 index 000000000..4b633f0df --- /dev/null +++ b/src/platform/nrf54l15/nrf54l15_main.cpp @@ -0,0 +1,121 @@ +/* + * nrf54l15_main.cpp — Zephyr entry point for Meshtastic nRF54L15 port + * + * Zephyr calls main() instead of Arduino's setup()/loop(). + * This file provides the main() that bootstraps the Arduino-style + * Meshtastic application loop. + */ + +#include +#include +#include +#include + +// Forward declarations from src/main.cpp +void setup(); +void loop(); + +// ── Crash info saved to noinit RAM (survives soft reset) ───────────────────── +// Zephyr's arch_esf does not expose the faulting SP directly; we capture PSP +// at entry to the fatal handler (the exception-basic frame lives there) and +// store xPSR alongside PC/LR for context. +struct crash_info { + uint32_t magic; + uint32_t reason; + uint32_t pc; + uint32_t psp; // stack pointer captured at fault entry + uint32_t xpsr; // saved program status (flags + exception number) + uint32_t lr; + uint32_t cfsr; // Configurable Fault Status Register +}; +static struct crash_info saved_crash __attribute__((section(".noinit"))); +#define CRASH_MAGIC 0xDEADBEEF + +// Override Zephyr's weak fatal handler to save crash info, then cold-reboot so +// main() can report the saved record on the next boot. We don't rely on +// CONFIG_RESET_ON_FATAL_ERROR (default off → k_fatal_halt would spin forever) +// — we issue sys_reboot() ourselves after flushing logs. +extern "C" void k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf *esf) +{ + saved_crash.magic = CRASH_MAGIC; + saved_crash.reason = reason; + // Capture the faulting thread's stack pointer before we start using the + // handler's own stack for logging. + uint32_t psp_at_entry; + __asm__ volatile("mrs %0, psp" : "=r"(psp_at_entry)); + saved_crash.psp = psp_at_entry; + if (esf) { + saved_crash.pc = esf->basic.pc; + saved_crash.xpsr = esf->basic.xpsr; + saved_crash.lr = esf->basic.lr; + } + // Read Cortex-M33 SCB CFSR + saved_crash.cfsr = *((volatile uint32_t *)0xE000ED28U); + printk("[nrf54l15] FATAL reason=%u pc=0x%08x lr=0x%08x cfsr=0x%08x\n", reason, saved_crash.pc, saved_crash.lr, + saved_crash.cfsr); + + // Walk the failing thread's stack and print any word that looks like a + // Thumb code address (0x1000 — flash end, with the Thumb-mode low bit set). + // The Cortex-M exception frame at PSP holds r0,r1,r2,r3,r12,lr,pc,xpsr + // (8 words); deeper words are the caller's saved frame, which gives a + // crude but useful poor-man's backtrace when CONFIG_DEBUG_COREDUMP is off. + // Found the BLE-init bad_alloc → abort() chain (heap exhaustion under + // CONFIG_BT_BUF_ACL_RX_SIZE=251) when the fault dump alone showed only + // abort itself. Cheap (~150 B of code) and silent until a fault. + uint32_t psp; + __asm__ volatile("mrs %0, psp" : "=r"(psp)); + printk("[nrf54l15] PSP=0x%08x — stack walk:\n", psp); + // Validate PSP before dereferencing. Real faults frequently leave PSP + // pointing at corrupted/unmapped memory, and walking it blindly triggers a + // second fault inside this handler. Restrict to nRF54L15 SRAM (256 KB at + // 0x20000000) with 4-byte alignment, and clamp the walk so we never read + // past the end of RAM. + const uintptr_t SRAM_START = 0x20000000UL; + const uintptr_t SRAM_END = 0x20040000UL; + if (psp < SRAM_START || psp >= SRAM_END || (psp & 0x3U) != 0) { + printk("[nrf54l15] PSP out of SRAM range or unaligned, skipping walk\n"); + } else { + const uint32_t *sp = (const uint32_t *)psp; + int max_words = (int)((SRAM_END - psp) / sizeof(uint32_t)); + if (max_words > 96) + max_words = 96; + for (int i = 0; i < max_words; i++) { + uint32_t v = sp[i]; + if (v >= 0x00001000 && v < 0x00080000 && (v & 1)) { + printk("[nrf54l15] sp[%d]=0x%08x (code)\n", i, v); + } + } + } + + // Give the RTT/printk backend a chance to drain before we reset, otherwise + // the crash log line above is lost and the next boot's "Prev crash" line is + // the only forensic evidence we get. + k_busy_wait(50000); // 50 ms + sys_reboot(SYS_REBOOT_COLD); + // Unreachable; k_fatal_halt as a defensive backstop in case sys_reboot + // returns (it shouldn't). + k_fatal_halt(reason); +} + +int main(void) +{ + uint32_t reset_cause = 0; + hwinfo_get_reset_cause(&reset_cause); + hwinfo_clear_reset_cause(); + printk("[nrf54l15] Reset cause: 0x%08x\n", reset_cause); + + if (saved_crash.magic == CRASH_MAGIC) { + printk("[nrf54l15] Prev crash: reason=%u pc=0x%08x lr=0x%08x psp=0x%08x xpsr=0x%08x cfsr=0x%08x\n", saved_crash.reason, + saved_crash.pc, saved_crash.lr, saved_crash.psp, saved_crash.xpsr, saved_crash.cfsr); + saved_crash.magic = 0; + } + + printk("[nrf54l15] A: main() entry\n"); + printk("[nrf54l15] B: calling setup()\n"); + setup(); + printk("[nrf54l15] C: setup() returned\n"); + while (true) { + loop(); + } + return 0; +} diff --git a/src/platform/nrf54l15/utility/bonding.h b/src/platform/nrf54l15/utility/bonding.h new file mode 100644 index 000000000..c55625bb6 --- /dev/null +++ b/src/platform/nrf54l15/utility/bonding.h @@ -0,0 +1,11 @@ +// utility/bonding.h — stub for nRF54L15/Zephyr +// NodeDB.cpp includes this when ARCH_NRF52 is defined. +// Bluetooth is excluded; this stub satisfies the include chain. +#pragma once + +// BLE role constants (from Bluefruit SDK) +#define BLE_GAP_ROLE_PERIPH 0x01 +#define BLE_GAP_ROLE_CENTRAL 0x02 + +// Stub for bond_print_list() +static inline void bond_print_list(uint8_t) {} diff --git a/userPrefs.jsonc b/userPrefs.jsonc index a8201bab3..57ede8bbf 100644 --- a/userPrefs.jsonc +++ b/userPrefs.jsonc @@ -34,6 +34,8 @@ // "USERPREFS_CONFIG_GPS_UPDATE_INTERVAL": "600", // "USERPREFS_CONFIG_POSITION_BROADCAST_INTERVAL": "1800", // "USERPREFS_CONFIG_DEVICE_TELEM_UPDATE_INTERVAL": "900", // Device telemetry update interval in seconds + // "USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL": "900", // Environment telemetry update interval in seconds + // "USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED": "1", // Force BMP280/sensor reads + LoRa broadcast on first boot // "USERPREFS_LORACONFIG_CHANNEL_NUM": "31", // "USERPREFS_LORACONFIG_MODEM_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST", // "USERPREFS_USE_ADMIN_KEY_0": "{ 0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a, 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c }", diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini index cd5a8d593..56e6e8340 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -16,7 +16,7 @@ extra_scripts = extra_scripts/esp32_extra.py build_src_filter = - ${arduino_base.build_src_filter} - - - - - + ${arduino_base.build_src_filter} - - - - - - upload_speed = 921600 debug_init_break = tbreak setup diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index 9ab45d1ab..972a9f3bf 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -9,8 +9,9 @@ build_src_filter = ${env.build_src_filter} - - - - - - + - + - + - - - - diff --git a/variants/nrf52840/nrf52.ini b/variants/nrf52840/nrf52.ini index d11f4fc56..c115da605 100644 --- a/variants/nrf52840/nrf52.ini +++ b/variants/nrf52840/nrf52.ini @@ -40,7 +40,7 @@ build_unflags = -std=gnu++11 build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - - - lib_deps= ${arduino_base.lib_deps} diff --git a/variants/nrf54l15/nrf54l15.ini b/variants/nrf54l15/nrf54l15.ini new file mode 100644 index 000000000..4e6fa3f01 --- /dev/null +++ b/variants/nrf54l15/nrf54l15.ini @@ -0,0 +1,74 @@ +[nrf54l15_base] +platform = https://github.com/Seeed-Studio/platform-seeedboards.git +framework = zephyr +extends = arduino_base + +build_type = release +build_flags = + ${arduino_base.build_flags} + -Isrc/platform/nrf54l15 + -DMESHTASTIC_EXCLUDE_AUDIO=1 + -DMESHTASTIC_EXCLUDE_GPS=1 + -DMESHTASTIC_EXCLUDE_MQTT=1 + -DHAS_WIRE=1 + -DHAS_SENSOR=1 + -DHAS_BUTTON=0 + -DHAS_TELEMETRY=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -DARDUINO=100 + -DMESHTASTIC_EXCLUDE_ACCELEROMETER=1 + -DMAX_NUM_NODES=40 + -fpermissive + # Libraries that Zephyr LDF misses; add include paths explicitly + -I.pio/libdeps/${PIOENV}/Crypto + -I.pio/libdeps/${PIOENV}/ArduinoThread + -I".pio/libdeps/${PIOENV}/ESP8266 and ESP32 OLED driver for SSD1306 displays/src" + -I.pio/libdeps/${PIOENV}/OneButton/src + -I.pio/libdeps/${PIOENV}/arduino-fsm + -I.pio/libdeps/${PIOENV}/TinyGPSPlus/src + -I.pio/libdeps/${PIOENV}/ErriezCRC32/src + -I.pio/libdeps/${PIOENV}/NonBlockingRTTTL/src + -I.pio/libdeps/${PIOENV}/RadioLib/src + +build_src_filter = + ${arduino_base.build_src_filter} + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + +lib_compat_mode = off + +lib_deps = + ${arduino_base.lib_deps} + ${radiolib_base.lib_deps} + rweather/Crypto@0.4.0 + ; Cherry-picked sensor libs from environmental_base. The full + ; environmental_base pulls Adafruit_SSD1306 / GFX which need Arduino + ; pin macros (digitalPinToPort / portOutputRegister) that the Zephyr + ; Arduino shim does not implement. + https://github.com/adafruit/Adafruit_BusIO/archive/refs/tags/1.17.4.zip + https://github.com/adafruit/Adafruit_Sensor/archive/refs/tags/1.1.15.zip + https://github.com/adafruit/Adafruit_BMP280_Library/archive/refs/tags/3.0.0.zip + https://github.com/adafruit/Adafruit_BME280_Library/archive/refs/tags/2.3.0.zip + https://github.com/adafruit/Adafruit_INA260/archive/refs/tags/1.5.3.zip + https://github.com/adafruit/Adafruit_INA219/archive/refs/tags/1.2.3.zip + https://github.com/RobTillaart/INA3221_RT/archive/refs/tags/0.4.2.zip + https://github.com/RobTillaart/INA226/archive/refs/tags/0.6.6.zip + https://github.com/adafruit/Adafruit_SHT4X/archive/refs/tags/1.0.5.zip + +lib_ignore = + BluetoothOTA + lvgl + Adafruit_nRFCrypto diff --git a/variants/nrf54l15/nrf54l15dk/README.md b/variants/nrf54l15/nrf54l15dk/README.md new file mode 100644 index 000000000..bede3a04a --- /dev/null +++ b/variants/nrf54l15/nrf54l15dk/README.md @@ -0,0 +1,108 @@ +# nRF54L15-DK — EBYTE E22-900M30S Wiring Guide + +Board: **Nordic nRF54L15-DK (PCA10156)** +Radio: **EBYTE E22-900M30S** (SX1262, 30 dBm, 868/915 MHz) + +--- + +## Why P2 (HP domain) and not P1 + +The nRF54L15 splits its GPIOs across three supply domains: + +- **P0** — Main domain, **3.0 V** — usable +- **P1** — LP domain, **1.8 V** — **not compatible** with the SX1262 +- **P2** — HP domain, **3.0 V** — usable + +The SX1262 requires VIH ≥ 0.7 × VDD (≈ 2.31 V at VDD = 3.3 V). P1's 1.8 V output +leaves the chip stuck in reset with `BUSY` never going LOW. All E22 signals +therefore live on **P2** and are driven by **SPIM00**. + +> `P2.09` is normally wired to LED0 on the DK; we ignore the LED and use +> SPIM00's default MISO pin. The on-board MX25R64 NOR flash also sat on SPIM00 +> — it is deleted in the device-tree overlay to free the bus. + +--- + +## Connections — J2 header, P2 bank + +| E22-900M30S | GPIO | DK pin | Function | +| ----------- | ----- | ------ | ---------------------------------------------- | +| MISO | P2.04 | 36 | SPIM00 data in | +| NSS / CS | P2.05 | 37 | SPI chip-select (driven by RadioLib as a GPIO) | +| DIO1 | P2.06 | 38 | IRQ — modem interrupt (routed via gpiote30) | +| BUSY | P2.03 | 35 | Module busy (GPIO input) | +| NRESET | P2.00 | 32 | Module reset (GPIO output, active LOW) | +| RXEN | P2.07 | 39 | LNA enable — held HIGH via `SX126X_ANT_SW` | +| MOSI | P2.02 | 34 | SPIM00 data out | +| SCK | P2.01 | 33 | SPIM00 clock | +| GND | — | GND | Common ground | +| VCC | — | VDD | 3.3 V | + +> **Numbering convention**: `P0.n = n`, `P1.n = 16+n`, `P2.n = 32+n`. +> Example: `P2.04` → 32 + 4 = **36**. + +--- + +## DIO2 → TXEN bridge (required) + +The E22-900M30S does **not** connect DIO2 to TXEN internally. A physical bridge on the module is required: + +1. Locate the `DIO2` and `TXEN` pads on the underside of the E22 module. +2. Solder a wire bridge or a 0 Ω resistor between the two pads. +3. With this bridge, the SX1262 drives the PA automatically via `SX126X_DIO2_AS_RF_SWITCH`. + +Without this bridge the module **will not transmit** (PA is never enabled). + +--- + +## RXEN — LNA always on + +`RXEN` (P2.07) is held HIGH permanently via `SX126X_ANT_SW 39` in `variant.h`. +**Do not use** `SX126X_RXEN` — RadioLib would drive it LOW in IDLE state and +the LNA would stay disabled (radio deaf in RX). + +--- + +## Reserved DK pins — do not reuse + +| Pins | Reserved function | +| ----------- | -------------------------------------------------------- | +| P0.00–P0.03 | IMCU debug UART (uart30, J-Link VCOM — used by RTT host) | +| P0.04 | BTN3 | +| P1.00–P1.01 | 32 kHz crystal | +| P1.02–P1.03 | NFC antenna | +| P1.10 | LED1 (status LED — kept) | +| P1.13 | BTN0 (only remaining user button) | +| P1.14 | LED3 | +| P2.01–P2.05 | SPIM00 / E22 (see connection table above) | +| P2.08–P2.10 | Trace ETM / LED2 (avoid) | + +--- + +## Build and flash + +```bash +# Build +pio run -e nrf54l15dk + +# Flash (requires a J-Link connected via the DK's on-board IMCU) +pio run -e nrf54l15dk -t upload + +# Monitor RTT (channel 1 = Meshtastic logs) +JLinkRTTLogger -device nRF54L15_xxAA -if SWD -speed 4000 -RTTChannel 1 boot.log +``` + +Expected boot log: + +```text +*** Booting Zephyr OS build zephyr-v40201 *** +[nrf54l15] Reset cause: ... +[nrf54l15] B: calling setup() +INFO | ... SX1262 +INFO | ... lora.begin() = 0 ← RADIOLIB_ERR_NONE +[nrf54l15] C: setup() returned +``` + +If you see `Record critical error 3` (`NO_RADIO`), check: DIO2→TXEN bridge, +supply voltages (the E22 must see 3.0–3.3 V on P2, not 1.8 V), and SPI wiring +continuity. diff --git a/variants/nrf54l15/nrf54l15dk/platformio.ini b/variants/nrf54l15/nrf54l15dk/platformio.ini new file mode 100644 index 000000000..71bbe252b --- /dev/null +++ b/variants/nrf54l15/nrf54l15dk/platformio.ini @@ -0,0 +1,24 @@ +[env:nrf54l15dk] +# PRIVATE_HW (255) — the protobuf HardwareModel enum reserves DK / DIY boards +# without an SKU under PRIVATE_HW; no dedicated enum value will be assigned. +custom_meshtastic_hw_model = 255 +custom_meshtastic_hw_model_slug = NRF54L15_DK +custom_meshtastic_architecture = nrf54l15 +custom_meshtastic_actively_supported = false +custom_meshtastic_support_level = 3 +custom_meshtastic_display_name = Nordic nRF54L15-DK + +extends = nrf54l15_base +board = nrf54l15dk +board_level = extra +debug_tool = jlink +upload_protocol = jlink +board_runner_args_jlink = --device nRF54L15_xxAA --speed 4000 + +build_flags = ${nrf54l15_base.build_flags} + -Ivariants/nrf54l15/nrf54l15dk + -DNRF54L15_DK + -DMESHTASTIC_EXCLUDE_FILES_MANIFEST=1 + +build_src_filter = ${nrf54l15_base.build_src_filter} + +<../variants/nrf54l15/nrf54l15dk> diff --git a/variants/nrf54l15/nrf54l15dk/variant.cpp b/variants/nrf54l15/nrf54l15dk/variant.cpp new file mode 100644 index 000000000..b2982e7c5 --- /dev/null +++ b/variants/nrf54l15/nrf54l15dk/variant.cpp @@ -0,0 +1,9 @@ +#include "variant.h" + +void initVariant() +{ + // Minimal board init for nRF54L15-DK. + // GPIO/SPI peripheral setup is handled by the Zephyr device tree overlay + // (zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay). + // Add any board-level power sequencing here if needed. +} diff --git a/variants/nrf54l15/nrf54l15dk/variant.h b/variants/nrf54l15/nrf54l15dk/variant.h new file mode 100644 index 000000000..b837ed7cf --- /dev/null +++ b/variants/nrf54l15/nrf54l15dk/variant.h @@ -0,0 +1,86 @@ +#pragma once + +/* + * Nordic nRF54L15-DK (PCA10156) — Meshtastic variant + * + * ── GPIO voltage domains ───────────────────────────────────────────────────── + * P0 (gpio0 @ 0x10A000) Main domain 3.0 V ← usable + * P1 (gpio1 @ 0xd8200 ) LP domain 1.8 V ← NOT compatible with E22 + * P2 (gpio2 @ 0x50400 ) HP domain 3.0 V ← usable + * + * The SX1262 needs VIH ≥ 0.7 × VDD = 2.31 V (VDD = 3.3 V). + * P1 outputs only 1.8 V → chip stays in reset, BUSY never goes LOW. + * All E22 signals are therefore on P2 (3.0 V), driven by SPIM00. + * + * EBYTE E22-900M30S (SX1262) wiring — J2 header, all P2: + * + * E22 pin GPIO pin# Notes + * ───────────────────────────────────────────────────────────────────── + * MISO → P2.04 36 SPIM00 data in + * NSS/CS → P2.05 37 SPI chip-select (RadioLib GPIO) + * DIO1 → P2.06 38 IRQ — interrupt via gpiote30 + * BUSY → P2.03 35 GPIO input + * NRESET → P2.00 32 GPIO output + * RXEN → P2.07 39 Held HIGH via ANT_SW (LNA always active) + * MOSI → P2.02 34 SPIM00 data out + * SCK → P2.01 33 SPIM00 clock + * + * DIO2 → TXEN bridge required on E22 module (solder bridge / wire). + * DIO3 drives TCXO reference (1.8 V). + * + * Pin numbering convention: P0.n = n, P1.n = 16+n, P2.n = 32+n. + * + * Reserved / do-not-use DK pins: + * P0.00-P0.02 IMCU VCOM TX/RX/RTS pads (uart30 disabled; pads idle) + * P0.03 I2C SDA (TWIM30) — sensor bus + * P0.04 I2C SCL (TWIM30) — sensor bus; SW3 button on this pad, + * DO NOT press SW3 while I2C is active + * P1.00-P1.01 32 kHz crystal + * P1.02-P1.03 NFC antenna + * P1.10 LED1 (status LED — keep) + * P1.13 BTN0 — main user button + * P1.14 LED3 + * P2.01-P2.05 SPIM00 / E22 (see above) + * P2.08-P2.10 Trace pins (avoid) + */ + +#ifndef NRF54L15_DK +#define NRF54L15_DK +#endif + +// ── SX1262 / E22-900M30S — all P2, HP domain (3.0 V) ──────────────────────── +#define USE_SX1262 +#define SX126X_CS 37 // P2.05 — chip-select +#define SX126X_DIO1 38 // P2.06 — IRQ (gpiote30 capable) +#define SX126X_BUSY 35 // P2.03 — BUSY +#define SX126X_RESET 32 // P2.00 — NRESET + +// RXEN (P2.07) held HIGH permanently — LNA always active. +// RadioLib must NOT toggle it; ANT_SW drives it HIGH before lora.begin(). +#define SX126X_ANT_SW 39 // P2.07 — RXEN driven HIGH at init + +// DIO2 controls TXEN via bridge on E22 module. +// DIO3 provides 1.8 V TCXO reference. +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8f + +// ── LEDs (active HIGH) ─────────────────────────────────────────────────────── +#define PIN_LED1 26 // P1.10 — LED1 (status LED, LP domain — output only, OK) +#define PIN_LED2 41 // P2.09 — LED0 on DK (remapped; P2.07 now used for RXEN) +#define LED_STATE_ON 1 + +// ── Buttons (active LOW, internal pull-up) ─────────────────────────────────── +// BTN1 (P1.09), BTN2 (P1.08) and BTN3 (P0.04) deleted from DTS — only BTN0 +// remains. BTN3's pad (P0.04) is now I2C SCL. +#define PIN_BUTTON1 29 // P1.13 — BTN0 +#define BUTTON_NEED_PULLUP + +// ── I2C bus (TWIM30, HP domain, 3.0 V) ────────────────────────────────────── +// SDA=P0.03, SCL=P0.04. Pinctrl + clock-frequency live in the board overlay. +// External 4.7 kΩ pull-ups required on both lines. Meshtastic's Arduino +// TwoWire layer (src/platform/nrf54l15/Wire.cpp) resolves the device at +// compile time via DT_NODELABEL(i2c30); these PIN_WIRE_* defines are kept +// for parity with the Arduino convention used by other variants. +#define PIN_WIRE_SDA 3 // P0.03 +#define PIN_WIRE_SCL 4 // P0.04 +#define WIRE_INTERFACES_COUNT 1 diff --git a/variants/rp2040/rp2040.ini b/variants/rp2040/rp2040.ini index 090f5d19a..fb29153ea 100644 --- a/variants/rp2040/rp2040.ini +++ b/variants/rp2040/rp2040.ini @@ -20,7 +20,7 @@ build_flags = -D__FREERTOS=1 # -D _POSIX_THREADS build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - lib_ignore = BluetoothOTA diff --git a/variants/rp2350/rp2350.ini b/variants/rp2350/rp2350.ini index 934875c6a..964545311 100644 --- a/variants/rp2350/rp2350.ini +++ b/variants/rp2350/rp2350.ini @@ -16,8 +16,8 @@ build_flags = -Isrc/platform/rp2xx0 -D__PLAT_RP2350__ -D__FREERTOS=1 -build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - +build_src_filter = + ${arduino_base.build_src_filter} - - - - - - - - - - - - lib_ignore = BluetoothOTA diff --git a/variants/stm32/stm32.ini b/variants/stm32/stm32.ini index 1efe18e3d..051a25524 100644 --- a/variants/stm32/stm32.ini +++ b/variants/stm32/stm32.ini @@ -44,7 +44,7 @@ build_flags = -Wl,--wrap=_tzset_unlocked_r build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - - - - - - board_upload.offset_address = 0x08000000 upload_protocol = stlink diff --git a/zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay new file mode 100644 index 000000000..4c861312c --- /dev/null +++ b/zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -0,0 +1,117 @@ +#include + +/* + * Zephyr device tree overlay — Nordic nRF54L15-DK + EBYTE E22-900M30S (SX1262) + * + * P1 GPIO bank runs at 1.8V VDDIO (LP domain) — NOT compatible with SX1262 + * which needs VIH ≥ 2.31V (0.7 × 3.3V). All E22 signals therefore use P2, + * which is in the HP domain (3.0V VDDIO), via SPIM00. + * + * SPIM00 (HP domain, 3.0V) replaces SPIM20 (LP domain, 1.8V). + * Default pinctrl from cpuapp_common.dtsi already maps SPIM00 to P2 pins: + * MISO = P2.04, MOSI = P2.02, SCK = P2.01 + * + * The on-board MX25R64 NOR flash was attached to SPIM00; it is not used by + * Meshtastic (LittleFS lives in internal RRAM), so we delete its DTS node to + * free the bus and P2.05 (its CS pin) for our use. + * + * Physical wiring (all P2, HP domain): + * E22 MISO → P2.04 (SPIM00 MISO) + * E22 NSS → P2.05 (freed from MX25R64 CS, RadioLib GPIO) + * E22 DIO1 → P2.06 (IRQ — interrupt capable via gpiote30) + * E22 BUSY → P2.03 (GPIO input) + * E22 RST → P2.00 (GPIO output) + * E22 RXEN → P2.07 (held HIGH via ANT_SW; replaces LED2 on DK) + * E22 MOSI → P2.02 (SPIM00 MOSI) + * E22 SCK → P2.01 (SPIM00 SCK) + * + * Pin numbering convention: P0.n = n, P1.n = 16+n, P2.n = 32+n. + */ + +/* Disable uart20 — no longer needed for LoRa (P1 domain abandoned). */ +&uart20 { + status = "disabled"; +}; + +/* Disable uart30 to free P0.00–P0.03 from the uart30_default pinctrl (which + * also reserves P0.03 as UART30 CTS). The peripheral instance 30 is shared + * with i2c30/spi30 — only one of {UARTE30, TWIM30, SPIM30} can be enabled at + * a time. We pick TWIM30 (i2c30) for sensors (SHT40 / INA3221 / SE050 on the + * custom PCB; BMP280 / INA228 on the DK for bring-up). Console stays on RTT + * via CONFIG_RTT_CONSOLE=y. */ +&uart30 { + status = "disabled"; +}; + +/* I2C bus via TWIM30 (HP domain, 3.0 V), SDA=P0.03 / SCL=P0.04. + * P0.03 was the UART30 CTS; freed by disabling uart30 above. + * P0.04 was button SW3 on the DK; deleted below. The pad still routes to + * the SW3 button on the board — DO NOT press SW3 during I2C use, it will + * short SCL to GND mid-transaction. + * 400 kHz is the highest standard rate supported by all three target sensors + * (BMP280, INA228, SE050). External 4.7 kΩ pull-ups required on both lines. */ +&pinctrl { + i2c30_default: i2c30_default { + group1 { + psels = , + ; + bias-pull-up; + }; + }; + + i2c30_sleep: i2c30_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; +}; + +&i2c30 { + status = "okay"; + clock-frequency = ; /* 400 kHz */ + pinctrl-0 = <&i2c30_default>; + pinctrl-1 = <&i2c30_sleep>; + pinctrl-names = "default", "sleep"; +}; + +/ { + buttons { + /* button1 (P1.09) and button2 (P1.08) no longer repurposed — + * E22 now uses P2. button3 (P0.04) is now I2C SCL — its node + * must be removed before the i2c30 pinctrl can claim the pad. */ + /delete-node/ button_1; + /delete-node/ button_2; + /delete-node/ button_3; + }; + + aliases { + /delete-property/ sw1; + /delete-property/ sw2; + /delete-property/ sw3; + }; +}; + +/* + * Override SPIM00 to remove the MX25R64 NOR flash child. + * The SPIM00 peripheral itself stays enabled (status = "okay" from + * cpuapp_common.dtsi) with its existing pinctrl (P2.01/P2.02/P2.04). + * RadioLib drives CS (P2.05) as a plain GPIO — no hardware CS needed. + */ +&spi00 { + /delete-node/ mx25r6435f@0; +}; + +/* + * Grow storage_partition from 36 KB to 700 KB by reclaiming slot1_partition. + * slot1 (image-1) is the MCUboot secondary slot for dual-bank OTA, which we + * don't use (flashing is direct via J-Link). With only 9 blocks (36 KB / 4 KB) + * LittleFS ran out of space during COW writes of config.proto. + * New layout: storage_partition spans 0xb6000..0x165000 (700 KB, ~175 blocks). + */ +/delete-node/ &slot1_partition; + +&storage_partition { + reg = <0xb6000 0xaf000>; +}; diff --git a/zephyr/prj.conf b/zephyr/prj.conf new file mode 100644 index 000000000..0723d0cd6 --- /dev/null +++ b/zephyr/prj.conf @@ -0,0 +1,299 @@ +# Zephyr project configuration for nRF54L15 Meshtastic port +# +# NOTE: this prj.conf is shared by ALL Zephyr PlatformIO environments +# in this project. Keep it compatible with any future Zephyr targets. + +# ── C++ support (required by Meshtastic) ────────────────────────────────────── +CONFIG_CPP=y +CONFIG_STD_CPP17=y +# Full libstdc++ — provides , , , , etc. +# Works with either newlib or picolibc (Zephyr auto-selects based on board). +CONFIG_REQUIRES_FULL_LIBCPP=y +# Disable C++ exceptions — not needed by Meshtastic and saves RAM/ROM +CONFIG_CPP_EXCEPTIONS=n + +# ── Peripheral subsystems ───────────────────────────────────────────────────── +CONFIG_SPI=y +CONFIG_I2C=y +CONFIG_GPIO=y + +# sys_reboot() used by BLE zombie-connection watchdog (BleDeferredThread). +# nRF54L15 SW-LL occasionally drops the BLE link without forwarding +# LE Disconnection Complete to the host; cold reboot is the only reliable +# recovery path. +CONFIG_REBOOT=y + +# ── ATT Prepare/Execute Write (LONG WRITE) ─────────────────────────────────── +# iOS CoreBluetooth automatically fragments writes >MTU-3 via ATT Prepare Write +# (opcode 0x16). Default CONFIG_BT_ATT_PREPARE_COUNT=0 makes Zephyr reject with +# "Request Not Supported" (0x06), which iOS surfaces as a write error → +# disconnect. With MTU=65 any ToRadio write >62 bytes triggers this path. +# Enabling this allocates N prep_pool buffers (each BT_ATT_BUF_SIZE = 65 bytes) +# and reassembles fragments into a single write_toradio() call on execute. +# 4 × 65 = 260 B max assembled value — enough for typical iOS NodeInfo/admin +# writes after config stream completes. +CONFIG_BT_ATT_PREPARE_COUNT=4 + +# ── Filesystem — LittleFS on storage_partition (RRAM) ─────────────────────── +# Size is set by the board overlay (the nRF54L15-DK overlay reclaims slot1 to +# expand storage_partition to ~700 KB). Capacity is reported at runtime via +# FIXED_PARTITION_SIZE(storage_partition) in InternalFileSystem::totalBytes(). +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y +CONFIG_FILE_SYSTEM_MKFS=y +# Disable SPI NOR flash driver — MX25R64 node deleted from DTS, SPIM00 used +# exclusively by RadioLib (SX1262). Without this, the spi_nor driver claims +# SPIM00 at boot and tries to read MX25R64 ID (gets garbage since the chip is +# not wired), producing "Device id a8 a8 a8 does not match config c2 28 17". +CONFIG_SPI_NOR=n +# Disable runtime PM — keeps SPI initialization path simple; avoids any +# interaction between PM auto-suspend/resume cycles and the SPIM00 clock +# request mechanism (CONFIG_CLOCK_CONTROL_NRF_HSFLL_GLOBAL). +CONFIG_PM_DEVICE_RUNTIME=n +# Suppress Zephyr FS subsystem's internal error/warning logs (ENOENT on +# missing files and EEXIST on duplicate mkdir are expected and handled). +CONFIG_FS_LOG_LEVEL_OFF=y + +# ── Console / logging ───────────────────────────────────────────────────────── +# Use SEGGER RTT for console — does not require COM3 (CDC UART), reads via SWD +CONFIG_UART_CONSOLE=n +CONFIG_USE_SEGGER_RTT=y +CONFIG_RTT_CONSOLE=y +CONFIG_LOG=y +CONFIG_LOG_BACKEND_RTT=y +CONFIG_LOG_DEFAULT_LEVEL=2 +# Immediate mode: log writes go directly to RTT backend without a separate thread. +# Deferred mode requires the log thread to run (lowest priority — never gets CPU +# in heavy setup()/loop() workloads), leaving the RTT buffer empty indefinitely. +CONFIG_LOG_MODE_IMMEDIATE=y +# Force RTT control block re-init on every boot — prevents stale/corrupted CB after crash +CONFIG_SEGGER_RTT_INIT_MODE_ALWAYS=y +# Use RTT channel 1 for the LOG backend, channel 0 (Terminal) for direct printk. +# Sharing channel 0 forces LOG_PRINTK=y (deferred) to avoid corruption. +CONFIG_LOG_BACKEND_RTT_BUFFER=1 +# Buffer sizes shrunk from 24576 → 4096 to free ~40 KB of BSS for newlib heap. +# At 24576 the BSS pushed _end up so far that newlib heap was only ~25 KB, +# and BUF_ACL_RX_SIZE=152 + BLE/PhoneAPI lazy init ran out of malloc space. +# 4 KB still gives several seconds of log retention before host attaches. +CONFIG_LOG_BACKEND_RTT_BUFFER_SIZE=4096 +# Overwrite oldest data if buffer fills — never stalls +CONFIG_LOG_BACKEND_RTT_MODE_BLOCK=n +CONFIG_LOG_BACKEND_RTT_MODE_OVERWRITE=y +CONFIG_LOG_BACKEND_RTT_OUTPUT_BUFFER_SIZE=256 + +# ── LFXO clock source — use RC oscillator to avoid ~2s crystal stabilization +# disrupting the GRTC timer and hanging k_sleep +CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y + +# ── Stack sizes — Meshtastic setup() is heavy (RadioLib, NodeDB, printf) ────── +# bt_enable() called from nrf54l15Setup() needs >8KB. +# Phase 7: CONFIG_BT_SETTINGS=y causes bt_set_name() → settings_save_one() → +# settings_file_save() → LittleFS I/O. The I/O chain needs ~3 KB of stack +# headroom beyond what the BT init alone requires. Increase main stack to 24KB +# and system workqueue to 8KB to cover both the cooperative-OSThread call path +# (which runs on the main thread) and any async flash work items. +CONFIG_MAIN_STACK_SIZE=24576 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=8192 +# Log processing thread stack — default 768 overflows when processing RTT fault dump +CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=2048 + +# ── Fault/exception diagnostics — identify ~2000ms crash ───────────────────── +CONFIG_FAULT_DUMP=2 +CONFIG_EXCEPTION_DEBUG=y +CONFIG_STACK_SENTINEL=y +# Thread names + extra exception info — fault dumps then identify the failing +# thread (otherwise "Current thread: 0x... (unknown)") and include r4-r11 + psp +# so the custom k_sys_fatal_error_handler can walk the stack to show the caller +# chain. Cheap (~32 B/thread for names, no perf hit) and very useful when a +# crash recurs in the field. +CONFIG_THREAD_NAME=y +CONFIG_EXTRA_EXCEPTION_INFO=y +# SEGGER RTT buffer — keep modest (4 KB) to leave RAM for newlib heap. +CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=4096 +# Report reset reason from previous crash +CONFIG_HWINFO=y + +# ── Bluetooth ───────────────────────────────────────────────────────────────── +# Zephyr BT host + LL SW controller (MPSL) — peripheral role only +CONFIG_BT=y +CONFIG_BT_PERIPHERAL=y +# SMP / LE Encryption enabled so the Meshtastic app pairs with a PIN before +# accessing the GATT service. +CONFIG_BT_SMP=y +# Enforce MITM so clients must complete passkey exchange — without this Just +# Works pairings complete silently without prompting the user for a PIN. +CONFIG_BT_SMP_ENFORCE_MITM=y +# Fixed-passkey path so the device (no display) can advertise a known PIN via +# bt_passkey_set() when config.bluetooth.mode == FIXED_PIN. +CONFIG_BT_FIXED_PASSKEY=y +# Allow legacy pairing as fallback. SC_PAIR_ONLY=y has been observed to cause +# some clients to abort pairing with reason 0x01 within 150 ms of the pairing +# request, before any PIN dialog appears. Accepting legacy lets the same +# clients fall through to Passkey Entry successfully. +CONFIG_BT_SMP_SC_PAIR_ONLY=n +# BT_LL_SW_SPLIT is auto-selected from DT (zephyr,bt-hci-ll-sw-split node in nRF54L15 DTS) +# Do NOT set CONFIG_BT_CTLR=y (deprecated — radio silently non-functional) +# Dynamic device name so bt_set_name() can embed the node short ID at runtime +CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_DEVICE_NAME_MAX=32 +# Only need one simultaneous central connection +CONFIG_BT_MAX_CONN=1 +# BT subsystem logging — INF for connection/service diagnostics +CONFIG_BT_LOG_LEVEL_INF=y +# BT thread stacks — defaults are too small for nRF54L15 SW-LL init. +# prio_recv_thread overflows at 2048; bump all BT stacks to safe sizes. +# BT RX thread runs ALL our GATT write callbacks (rx_work_handler → hci_acl → +# bt_conn_recv → bt_l2cap_recv → bt_att_recv → write_toradio_cb → +# PhoneAPI::handleStartConfig → getFiles("/", 10) recursion + nanopb encode). +# 4096 overflows on "Client wants config" → abort() / kernel panic (reason 4). +CONFIG_BT_RX_STACK_SIZE=4096 +CONFIG_BT_HCI_TX_STACK_SIZE=1024 +# bt_long_wq runs bt_pub_key_gen (ECC P256 keygen) on this thread. +# Defaults (prio=10, stack=1400) leave it starved by Meshtastic app threads +# at boot: pub_key gen never completes, so smp_public_key() defers +# indefinitely waiting for sc_public_key, and every SC pairing attempt +# stalls right after exchanging public keys (no PIN prompt on iOS, every +# AUTHEN-gated char rejects with ATT error 0x05). +# Prio 0 = highest preemptible, ties with main; stack 4096 clears P256M +# driver frames with margin. +CONFIG_BT_LONG_WQ_PRIO=0 +CONFIG_BT_LONG_WQ_STACK_SIZE=4096 +# Use legacy advertising (bt_le_adv_start / HCI 0x2006 path). +# With CONFIG_BT_EXT_ADV=y, bt_le_adv_start() is internally translated to the +# extended HCI path with LEGACY-bit (0x2036), which produces non-connectable PDUs +# on the nRF54L15 SW-LL. With CONFIG_BT_EXT_ADV=n the host uses pure legacy HCI +# commands (0x2006/0x2008/0x200a) — the same path Nordic uses in all nRF54L15 +# NCS examples (peripheral_uart, peripheral_lbs), which is iOS-compatible and +# avoids the LE Remove Advertising Set (0x203c) controller timeout crash. +CONFIG_BT_EXT_ADV=n + +# ── Phase 7: BT bond persistence ────────────────────────────────────────────── +# CONFIG_BT_SETTINGS enables the BT host settings integration: the stack +# automatically calls settings_save_subtree("bt/keys") after pairing, and +# settings_load() on boot restores previously bonded peers. +# +# Backend: SETTINGS_FILE stores all key-value pairs in a single flat file in +# LittleFS. No new partition needed — the existing storage_partition (mounted +# at /lfs, size set by the board overlay) is used. File path: /lfs/bt_settings. +# +# Ordering guarantee: LittleFS is mounted by fsInit() BEFORE nrf54l15Setup() +# calls nrf54l15_bt_preinit(), so the file backend is always available when +# settings_load() is called after bt_enable(). +CONFIG_BT_SETTINGS=y +CONFIG_SETTINGS=y +CONFIG_SETTINGS_FILE=y +CONFIG_SETTINGS_FILE_PATH="/lfs/bt_settings" +# BT_MAX_PAIRED default is 1 — first bond (e.g. iOS) blocks every subsequent +# peer's SMP pairing request with "Unable to get keys" because there is no free +# bt_keys slot to allocate. Raise to 4 so the device can simultaneously hold +# iOS, Windows, Linux, and one spare bond. Add OVERWRITE_OLDEST so that when +# the table fills, the LRU peer is evicted instead of rejecting the new pair. +CONFIG_BT_MAX_PAIRED=4 +CONFIG_BT_KEYS_OVERWRITE_OLDEST=y +# Disable GATT database caching and Service Changed characteristic. +# CONFIG_BT_GATT_CACHING (default y with BT_SETTINGS) marks every new client as +# "not change-aware" and returns ATT_ERR_DB_OUT_OF_SYNC (0x12) on every GATT +# request until the client reads the DB-hash characteristic. The Meshtastic app +# does not implement GATT caching and silently aborts service discovery on 0x12, +# causing the connection to stall with zero GATT activity. +# CONFIG_BT_GATT_SERVICE_CHANGED (default y) adds the Generic Attribute Profile +# service; disabling it is required before BT_GATT_CACHING can be disabled. +CONFIG_BT_GATT_SERVICE_CHANGED=n +CONFIG_BT_GATT_CACHING=n +# Disable automatic PHY update (1M→2M) after connection. +# The nRF54L15 SW-LL fails the LL_PHY_REQ/RSP exchange and disconnects +# exactly 1.786s after connection — before any ATT/GATT operations. +CONFIG_BT_AUTO_PHY_UPDATE=n +# ATT/GATT/L2CAP debug logging — see exactly what happens after connection +CONFIG_BT_ATT_LOG_LEVEL_DBG=y +CONFIG_BT_GATT_LOG_LEVEL_DBG=y +CONFIG_BT_SMP_LOG_LEVEL_DBG=y +# L2CAP DBG: shows recv on fixed ATT channel — confirms whether iOS sends any data +CONFIG_BT_L2CAP_LOG_LEVEL_DBG=y +# Keep bt_conn at INF — DBG floods RTT buffer every ~150µs (tx_processor loop), +# overwriting all ATT/GATT messages before they can be read. +# Connection events (connected/disconnected) are logged at INF level. +CONFIG_BT_CONN_LOG_LEVEL_INF=y +# Keep HCI logs at INF to save RAM (log thread processing buffers, etc.). +# (Earlier DBG was used to diagnose the hci_acl → L2CAP stall — fix applied.) +CONFIG_BT_HCI_CORE_LOG_LEVEL_INF=y +CONFIG_BT_HCI_DRIVER_LOG_LEVEL_INF=y +# Fix: ACL packets reach hci_acl() but never reach bt_l2cap_recv(). +# Root cause: bt_conn_recv() calls bt_conn_tx_notify(conn, true) which submits +# tx_complete_work to k_sys_work_q and blocks on k_work_flush(). The BT rx +# workqueue (bt_workq) is stuck in k_work_flush waiting for the system +# workqueue, which is busy with LittleFS I/O / other work → dead stall until +# iOS supervision timeout fires (5s) and disconnects with reason 0x13. +# Solution: dedicate a separate workqueue for TX notify processing so it is +# independent from the system workqueue. +CONFIG_BT_CONN_TX_NOTIFY_WQ=y +# Dedicated workqueue only runs tx_notify_process() (iterates tx_complete list, +# calls short callbacks). Default 8192 is overkill and eats malloc heap needed +# by PowerFSM init → realloc() returns NULL → bus fault during FSM::add_transition. +CONFIG_BT_CONN_TX_NOTIFY_WQ_STACK_SIZE=2048 + +# ── ATT/L2CAP MTU — larger payloads for Meshtastic packets ─────────────────── +# TX side: controller sends up to L2CAP_TX_MTU bytes per ATT operation. +# RX side: server ATT MTU is min(BT_L2CAP_TX_MTU, BT_BUF_ACL_RX_SIZE - 4). +# Both set to 247 / 251 → ATT MTU = 247 in each direction, matching Zephyr's +# samples/bluetooth/mtu_update reference. This means typical iOS ToRadio +# writes (NodeInfo, channel settings, common admin packets) fit in a single +# ATT_WRITE_REQ and avoid the ATT Prepare/Execute Write path entirely. +# CONFIG_BT_ATT_PREPARE_COUNT=4 (above) still backstops oversized writes. +# +# Heap dependency: bumping BUF_ACL_RX_SIZE > default (~69) grows the BT host +# net_buf pools in BSS, which proportionally shrinks the newlib heap arena +# (MAX_HEAP_SIZE = SRAM_SIZE - (_end - SRAM_BASE), so any BSS growth steals +# from the heap directly). Empirically the lazy BLE init path +# (setBluetoothEnable → startDisabled → bt_set_name → settings_save → +# LittleFS) needs ~12 KB of newlib heap to run without bad_alloc. At +# BUF_ACL_RX_SIZE=251 with the previous 24 KB RTT buffers (LOG_BACKEND_RTT + +# SEGGER_RTT_BUFFER_SIZE_UP), the heap collapsed to ~4 KB free at +# transition time → `new char[]` in RedirectablePrint::log returned NULL → +# libstdc++ called abort() from main thread. Shrinking both RTT buffers to +# 4 KB (above) frees ~40 KB of BSS for the heap and resolves it. +# +# DLE stays off (BT_DATA_LEN_UPDATE=n below): the LLCP remote table at +# ull_llcp_remote.c:878 is guarded by #ifdef CONFIG_BT_CTLR_DATA_LENGTH, so +# the controller answers iOS's LL_LENGTH_REQ with LL_UNKNOWN_RSP and falls +# back to 27-byte LL PDUs. The host reassembles LL PDUs into L2CAP frames +# up to BT_BUF_ACL_RX_SIZE before dispatching to ATT. +CONFIG_BT_L2CAP_TX_MTU=247 +# Server ATT MTU = BUF_ACL_RX_SIZE - 4 = 247 (matches L2CAP_TX_MTU) +CONFIG_BT_BUF_ACL_RX_SIZE=251 + +# ── Fix: LL Feature Exchange collision (ROOT CAUSE of iOS GATT hang) ───────── +# On connection, Zephyr host calls bt_hci_le_read_remote_features() because +# BT_CTLR_PER_INIT_FEAT_XCHG=y makes can_initiate_feature_exchange() return +# true for peripheral role. This makes the controller send LL_PER_INIT_FEAT_XCHG +# to iOS right after connecting. +# iOS (as central) simultaneously sends LL_FEATURE_REQ to the peripheral. +# The nRF54L15 SW-LL mishandles this COLLISION: iOS waits for LL_FEATURE_RSP +# to its LL_FEATURE_REQ, never gets it, and stalls — sending zero L2CAP bytes. +# BT_CTLR_PER_INIT_FEAT_XCHG=n: host does NOT send HCI_LE_Read_Remote_Features +# as peripheral → no LL_PER_INIT_FEAT_XCHG sent → no collision → iOS feature +# exchange completes → iOS proceeds to L2CAP/ATT. +CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG=n +# Fix: LL Connection Parameter Request handling. +# BT_CTLR_CONN_PARAM_REQ=n was ineffective: the LLCP remote decode table in +# ull_llcp_remote.c hardcodes PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ → PROC_CONN_PARAM_REQ +# regardless of Kconfig. With =n the handler is compiled out → controller asserts / +# enters broken state when iOS sends LL_CONN_PARAM_REQ (which is optional from Central). +# Fix: =y so the procedure is actually handled. To avoid host/peripheral vs Central +# collision at 5 s (deferred_work → send_conn_le_param_update), disable auto-update +# below so the host never initiates HCI_LE_Connection_Update. +CONFIG_BT_CTLR_CONN_PARAM_REQ=y +# Prevent Zephyr host from initiating connection parameter update 5 s after connect. +# With CONN_PARAM_REQ=y, if iOS (Central) already issued LL_CONN_PARAM_REQ and the +# SW-LL is mid-procedure, a simultaneous host-initiated HCI_LE_Connection_Update +# creates an LL collision. Disabling the auto-update avoids the collision entirely. +CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n +# Disable optional LL procedures (belt-and-suspenders while debugging): +# BT_PHY_UPDATE=n + BT_CTLR_PHY_2M=n: no PHY update procedure → iOS doesn't attempt LL_PHY_REQ +# BT_DATA_LEN_UPDATE=n: no DLE → controller sends LL_UNKNOWN_RSP to LL_LENGTH_REQ +CONFIG_BT_CTLR_PHY_2M=n +CONFIG_BT_PHY_UPDATE=n +CONFIG_BT_DATA_LEN_UPDATE=n From fc5556b8e639306448c6c5c44583ffb6f27d0a2f Mon Sep 17 00:00:00 2001 From: Jord <650645+Jord-JD@users.noreply.github.com> Date: Fri, 15 May 2026 02:51:44 +0100 Subject: [PATCH 06/21] Clamp direct position packets to channel precision (fixes #8640) (#10383) * Fix position precision for direct sends * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Clarify zero position precision logging * Use const channel reference for position precision * Use C linkage for position precision test entrypoints --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/mesh/PositionPrecision.cpp | 75 ++++++++++++++++ src/mesh/PositionPrecision.h | 9 ++ src/mesh/Router.cpp | 6 ++ src/modules/PositionModule.cpp | 48 +++------- test/test_position_precision/test_main.cpp | 100 +++++++++++++++++++++ 5 files changed, 204 insertions(+), 34 deletions(-) create mode 100644 src/mesh/PositionPrecision.cpp create mode 100644 src/mesh/PositionPrecision.h create mode 100644 test/test_position_precision/test_main.cpp diff --git a/src/mesh/PositionPrecision.cpp b/src/mesh/PositionPrecision.cpp new file mode 100644 index 000000000..04db01c79 --- /dev/null +++ b/src/mesh/PositionPrecision.cpp @@ -0,0 +1,75 @@ +#include "PositionPrecision.h" +#include "Channels.h" +#include "mesh-pb-constants.h" + +#include + +uint32_t getPositionPrecisionForChannel(uint8_t channelIndex) +{ + const meshtastic_Channel &channel = channels.getByIndex(channelIndex); + + if (channel.settings.has_module_settings) { + return channel.settings.module_settings.position_precision; + } else if (channel.role == meshtastic_Channel_Role_PRIMARY) { + return 32; + } else { + return 0; + } +} + +static int32_t truncateCoordinate(int32_t coordinate, uint32_t precision) +{ + uint32_t coordinateBits = static_cast(coordinate); + uint32_t truncated = coordinateBits & (UINT32_MAX << (32 - precision)); + + // Use the middle of the possible location, not the low edge of the bucket. + truncated += (1UL << (31 - precision)); + + return static_cast(truncated); +} + +void applyPositionPrecision(meshtastic_Position &position, uint32_t precision) +{ + if (precision == 0) { + uint32_t time = position.time; + position = meshtastic_Position_init_default; + position.time = time; + return; + } + + uint32_t effectivePrecision = precision > 32 ? 32 : precision; + position.precision_bits = effectivePrecision; + + if (effectivePrecision < 32) { + position.latitude_i = truncateCoordinate(position.latitude_i, effectivePrecision); + position.longitude_i = truncateCoordinate(position.longitude_i, effectivePrecision); + } +} + +bool applyPositionPrecision(meshtastic_MeshPacket &packet, uint32_t precision) +{ + if (packet.which_payload_variant != meshtastic_MeshPacket_decoded_tag || + packet.decoded.portnum != meshtastic_PortNum_POSITION_APP) { + return true; + } + + meshtastic_Position position = meshtastic_Position_init_default; + if (!pb_decode_from_bytes(packet.decoded.payload.bytes, packet.decoded.payload.size, &meshtastic_Position_msg, &position)) { + return false; + } + + applyPositionPrecision(position, precision); + packet.decoded.payload.size = pb_encode_to_bytes(packet.decoded.payload.bytes, sizeof(packet.decoded.payload.bytes), + &meshtastic_Position_msg, &position); + return true; +} + +bool applyPositionPrecisionForChannel(meshtastic_MeshPacket &packet, uint8_t channelIndex) +{ + if (packet.which_payload_variant != meshtastic_MeshPacket_decoded_tag || + packet.decoded.portnum != meshtastic_PortNum_POSITION_APP) { + return true; + } + + return applyPositionPrecision(packet, getPositionPrecisionForChannel(channelIndex)); +} diff --git a/src/mesh/PositionPrecision.h b/src/mesh/PositionPrecision.h new file mode 100644 index 000000000..6fdbd2f64 --- /dev/null +++ b/src/mesh/PositionPrecision.h @@ -0,0 +1,9 @@ +#pragma once + +#include "meshtastic/mesh.pb.h" +#include + +uint32_t getPositionPrecisionForChannel(uint8_t channelIndex); +void applyPositionPrecision(meshtastic_Position &position, uint32_t precision); +bool applyPositionPrecision(meshtastic_MeshPacket &packet, uint32_t precision); +bool applyPositionPrecisionForChannel(meshtastic_MeshPacket &packet, uint8_t channelIndex); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index eb5fd41ff..bb24b365e 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -4,6 +4,7 @@ #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" +#include "PositionPrecision.h" #include "RTC.h" #include "configuration.h" @@ -350,6 +351,11 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) } fixPriority(p); // Before encryption, fix the priority if it's unset + if (!applyPositionPrecisionForChannel(*p, p->channel)) { + LOG_ERROR("Dropping malformed position packet before send"); + packetPool.release(p); + return meshtastic_Routing_Error_BAD_REQUEST; + } // If the packet is not yet encrypted, do so now if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 0378d01e7..c4ffc9a92 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -4,6 +4,7 @@ #include "GPS.h" #include "MeshService.h" #include "NodeDB.h" +#include "PositionPrecision.h" #include "RTC.h" #include "Router.h" #include "TransmitHistory.h" @@ -107,13 +108,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes } nodeDB->updatePosition(getFrom(&mp), p); - if (channels.getByIndex(mp.channel).settings.has_module_settings) { - precision = channels.getByIndex(mp.channel).settings.module_settings.position_precision; - } else if (channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { - precision = 32; - } else { - precision = 0; - } + precision = getPositionPrecisionForChannel(mp.channel); return false; // Let others look at this message also if they want } @@ -121,15 +116,12 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes void PositionModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_Position *p) { // Phone position packets need to be truncated to the channel precision - if (isFromUs(&mp) && (precision < 32 && precision > 0)) { - LOG_DEBUG("Truncate phone position to channel precision %i", precision); - p->latitude_i = p->latitude_i & (UINT32_MAX << (32 - precision)); - p->longitude_i = p->longitude_i & (UINT32_MAX << (32 - precision)); - - // We want the imprecise position to be the middle of the possible location, not - p->latitude_i += (1 << (31 - precision)); - p->longitude_i += (1 << (31 - precision)); - + if (isFromUs(&mp)) { + if (precision == 0) + LOG_DEBUG("Strip phone position due to channel precision 0"); + else if (precision < 32) + LOG_DEBUG("Truncate phone position to channel precision %i", precision); + applyPositionPrecision(*p, precision); mp.decoded.payload.size = pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_Position_msg, p); } @@ -205,20 +197,11 @@ meshtastic_MeshPacket *PositionModule::allocPositionPacket() // lat/lon are unconditionally included - IF AVAILABLE! LOG_DEBUG("Send location with precision %i", precision); - if (precision < 32 && precision > 0) { - p.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - precision)); - p.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - precision)); - - // We want the imprecise position to be the middle of the possible location, not - p.latitude_i += (1 << (31 - precision)); - p.longitude_i += (1 << (31 - precision)); - } else { - p.latitude_i = localPosition.latitude_i; - p.longitude_i = localPosition.longitude_i; - } - p.precision_bits = precision; + p.latitude_i = localPosition.latitude_i; + p.longitude_i = localPosition.longitude_i; p.has_latitude_i = true; p.has_longitude_i = true; + applyPositionPrecision(p, precision); // Always use NTP / GPS time if available if (getValidTime(RTCQualityNTP) > 0) { p.time = getValidTime(RTCQualityNTP); @@ -349,8 +332,7 @@ void PositionModule::sendOurPosition() // If we changed channels, ask everyone else for their latest info LOG_INFO("Send pos@%x:6 to mesh (wantReplies=%d)", localPosition.timestamp, requestReplies); for (uint8_t channelNum = 0; channelNum < 8; channelNum++) { - if (channels.getByIndex(channelNum).settings.has_module_settings && - channels.getByIndex(channelNum).settings.module_settings.position_precision != 0) { + if (getPositionPrecisionForChannel(channelNum) != 0) { sendOurPosition(NODENUM_BROADCAST, requestReplies, channelNum); return; } @@ -368,10 +350,8 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal) service->cancelSending(prevPacketId); - // Set's the class precision value for this particular packet - if (channels.getByIndex(channel).settings.has_module_settings) { - precision = channels.getByIndex(channel).settings.module_settings.position_precision; - } + // Set the class precision value for this particular packet. + precision = getPositionPrecisionForChannel(channel); meshtastic_MeshPacket *p = allocPositionPacket(); if (p == nullptr) { diff --git a/test/test_position_precision/test_main.cpp b/test/test_position_precision/test_main.cpp new file mode 100644 index 000000000..4f5aecfda --- /dev/null +++ b/test/test_position_precision/test_main.cpp @@ -0,0 +1,100 @@ +#include "PositionPrecision.h" +#include "TestUtil.h" +#include "mesh-pb-constants.h" +#include + +static meshtastic_Position makePosition() +{ + meshtastic_Position position = meshtastic_Position_init_default; + position.has_latitude_i = true; + position.latitude_i = static_cast(0x12345678); + position.has_longitude_i = true; + position.longitude_i = static_cast(0x22345678); + position.has_altitude = true; + position.altitude = 123; + position.time = 42; + position.location_source = meshtastic_Position_LocSource_LOC_EXTERNAL; + position.timestamp = 43; + position.sats_in_view = 10; + return position; +} + +static void test_applyPositionPrecision_clampsLatLonAndSetsPrecisionBits() +{ + meshtastic_Position position = makePosition(); + + applyPositionPrecision(position, 16); + + TEST_ASSERT_EQUAL_INT32(static_cast(0x12348000), position.latitude_i); + TEST_ASSERT_EQUAL_INT32(static_cast(0x22348000), position.longitude_i); + TEST_ASSERT_EQUAL_UINT32(16, position.precision_bits); + TEST_ASSERT_TRUE(position.has_latitude_i); + TEST_ASSERT_TRUE(position.has_longitude_i); +} + +static void test_applyPositionPrecision_fullPrecisionKeepsLatLon() +{ + meshtastic_Position position = makePosition(); + + applyPositionPrecision(position, 32); + + TEST_ASSERT_EQUAL_INT32(static_cast(0x12345678), position.latitude_i); + TEST_ASSERT_EQUAL_INT32(static_cast(0x22345678), position.longitude_i); + TEST_ASSERT_EQUAL_UINT32(32, position.precision_bits); +} + +static void test_applyPositionPrecision_zeroScrubsLocationButKeepsTime() +{ + meshtastic_Position position = makePosition(); + + applyPositionPrecision(position, 0); + + TEST_ASSERT_FALSE(position.has_latitude_i); + TEST_ASSERT_EQUAL_INT32(0, position.latitude_i); + TEST_ASSERT_FALSE(position.has_longitude_i); + TEST_ASSERT_EQUAL_INT32(0, position.longitude_i); + TEST_ASSERT_FALSE(position.has_altitude); + TEST_ASSERT_EQUAL_INT32(0, position.altitude); + TEST_ASSERT_EQUAL_UINT32(42, position.time); + TEST_ASSERT_EQUAL_UINT32(0, position.timestamp); + TEST_ASSERT_EQUAL_UINT32(0, position.sats_in_view); + TEST_ASSERT_EQUAL_UINT32(0, position.precision_bits); +} + +static void test_applyPositionPrecision_reencodesPositionPacket() +{ + meshtastic_Position position = makePosition(); + meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_default; + packet.which_payload_variant = meshtastic_MeshPacket_decoded_tag; + packet.decoded.portnum = meshtastic_PortNum_POSITION_APP; + packet.decoded.payload.size = pb_encode_to_bytes(packet.decoded.payload.bytes, sizeof(packet.decoded.payload.bytes), + &meshtastic_Position_msg, &position); + + TEST_ASSERT_TRUE(applyPositionPrecision(packet, 16)); + + meshtastic_Position decoded = meshtastic_Position_init_default; + TEST_ASSERT_TRUE( + pb_decode_from_bytes(packet.decoded.payload.bytes, packet.decoded.payload.size, &meshtastic_Position_msg, &decoded)); + TEST_ASSERT_EQUAL_INT32(static_cast(0x12348000), decoded.latitude_i); + TEST_ASSERT_EQUAL_INT32(static_cast(0x22348000), decoded.longitude_i); + TEST_ASSERT_EQUAL_UINT32(16, decoded.precision_bits); +} + +void setUp(void) {} + +void tearDown(void) {} + +extern "C" { +void setup() +{ + initializeTestEnvironment(); + UNITY_BEGIN(); + RUN_TEST(test_applyPositionPrecision_clampsLatLonAndSetsPrecisionBits); + RUN_TEST(test_applyPositionPrecision_fullPrecisionKeepsLatLon); + RUN_TEST(test_applyPositionPrecision_zeroScrubsLocationButKeepsTime); + RUN_TEST(test_applyPositionPrecision_reencodesPositionPacket); + exit(UNITY_END()); +} + +void loop() {} +} From 767a7481880d7cfc5d555dc8e9a2f0b7c4317d0f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 16 May 2026 22:11:49 -0500 Subject: [PATCH 07/21] add optional LED_LORA to indicate LoRa TX (#10465) --- src/mesh/RadioLibInterface.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 02cf2281d..ef9a17f8d 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -424,6 +424,9 @@ void RadioLibInterface::completeSending() // that can take a long time auto p = sendingPacket; sendingPacket = NULL; +#ifdef LED_LORA + digitalWrite(LED_LORA, LED_STATE_OFF); +#endif if (p) { // Packet has been sent, count it toward our TX airtime utilization. @@ -612,6 +615,9 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp) enableInterrupt(isrTxLevel0); lastTxStart = millis(); printPacket("Started Tx", txp); +#ifdef LED_LORA + digitalWrite(LED_LORA, LED_STATE_ON); +#endif } return res == RADIOLIB_ERR_NONE; From a5419574804709db446273c8ce5d946a3a62807c Mon Sep 17 00:00:00 2001 From: Austin Date: Sun, 17 May 2026 19:53:32 -0400 Subject: [PATCH 08/21] ESP32: Migrate to Arduino 3.x (pioarduino) (#9122) * Migrate esp32 families to pioarduino platform * ESP32c6 align text.handler_execute same as C3 * Use pioarduino `develop` The latest fixes and the latest bugs! * preliminary esp32p4.ini * pioarduino: Update LovyanGFX Includes Manuel's recent commit * pioarduino 3.3.6 * pioarduino 3.3.6 *release* chasing the release * pioarduino: Fix OG ESP32 duplicate libs * pioarduino: T-Beam 1W CDC mode * pioarduino: disable network provisioning (wifiprov) * pioarduino: use legacy esptoolpy naming (forward-compatible) * Update lovyangfx from `develop` commit to 1.2.19 * fix esp32p4.ini * check for esp32 w/ wifi * esp32-p4 specific adaptations * Switch to meshtastic/esp32_https_server fork (idf5 branch) * don't ignore esp_lcd * config for MUI * fix/workaround SDMMC * revert a6f6175, update to 3.3.8 * enable esp_hosted for esp32-p4 (experimental) * Pioarduino 55.03.38-1 * NimBLE-Arduino -> Arduino "BLE" (3.3.x) migration (#10164) * NimBLE-Arduino -> Arduino "BLE" (3.3.x) migration * More NimBLE * Fix Device Name in ATT Read Request (0x2A00). Device Name is exposed in two places: - Advertisement data: this is set properly in startAdvertising. - GATT attribute Device Name (0x2A00). This one is handled internally in NimBLE and comes from ble_svc_gap_device_name_set. This is set initially, but then BLEDevice::createServer calls ble_svc_gap_init which resets the device name. This causes the device to apparently "change name after pairing": < ACL Data TX:... flags 0x00 dlen 7 #113 [hci0] 14.241149 ATT: Read Request (0x0a) len 2 Handle: 0x0003 Type: Device Name (0x2a00) > ACL Data RX: Handle 2048 flags 0x02 dlen 11 #115 [hci0] 14.269050 ATT: Read Response (0x0b) len 6 Value[6]: 6e696d626c65 # "nimble" Workaround this by setting the device name once again after BLEDevice::createServer. * Temporarily lower CORE_DEBUG_LEVEL to INFO to avoid triggering an apparent ESP-IDF Bluetooth bug when re-connecting to Pixel 8 Android devices. Initial pairing works, but after ESP32 is rebooted, phone fails to reconnect. Meshtastic app shows it as disconnecting immediately. LightBlue shows a more detailed error "Peripheral Connection - Warning: onConnectionStatusChange: status 61" (0x3D - MIC Failure). Bug report to Espresssif: https://github.com/espressif/esp-idf/issues/18126#issuecomment-4286197744 * Temporarily disable ble_gap_set_data_len, causes crash with Pixel 8 Android reconnect. Crash looks like this: [ 11966][E][BLEAdvertising.cpp:341] setScanResponseData(): ble_gap_adv_rsp_set_data: 22 [ 11975][E][BLEAdvertising.cpp:1554] start(): Host reset, wait for sync. ERROR | ??:??:?? 11 BLE failed to start advertising Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled. Core 0 register dump: PC : 0x420e6190 PS : 0x00060730 A0 : 0x820e158b A1 : 0x3fce50c0 A2 : 0x00000000 A3 : 0x3fcb8600 A4 : 0x3fcb85cc A5 : 0x00000000 A6 : 0x00000000 A7 : 0x00000c03 A8 : 0x00000000 A9 : 0x3fce50b0 A10 : 0x0000000e A11 : 0x00000000 A12 : 0x00000010 A13 : 0x3fce50e0 A14 : 0x00000c03 A15 : 0x00000001 SAR : 0x0000001e EXCCAUSE: 0x0000001c EXCVADDR: 0x00000000 LBEG : 0x400570e8 LEND : 0x400570f3 LCOUNT : 0x00000000 Backtrace: 0x420e618d:0x3fce50c0 0x420e1588:0x3fce5110 0x420dfe87:0x3fce5200 0x420dfefb:0x3fce5220 0x420dff3f:0x3fce5240 0x4219602b:0x3fce5260 0x4037b0e5:0x3fce5280 0x4201edf3:0x3fce52a0 Connection seems fast enough even without this. We'll investigate the reason for the crash and re-enable once it's safe. --------- Co-authored-by: Catalin Patulea * Add extension from pioarduino nag "Jason2866.esp-decoder" * Cleanup after merge * ESP32: Disable classic bluetooth * Cleanup: Fix ADC channels on new variants * InkHUD: Fix type casting for message size in saveToFlash method inkhud compiles again! * update p4 esp_hosted for BT * I thought I fixed this * fix linker error using response file (p4 only) * fix infinite loop * Fix Power.cpp check warning Local variable 'config' shadows outer variable [shadowVariable] * Build ESP32 original with NimBLE ('custom_sdkconfig' approach). (#10235) * Re-enable littlefs json manifest This works locally again :) Not sure what changed * Re-add tool-mklittlefs * sensecap indicator fixes after upgrade arduino-esp & lovyanGFX libs * hackaday fix * robot tbeam cache error fix Co-authored-by: Copilot * trunk fmt * ignore trunk * BLEDevice::deinit() added Co-authored-by: Copilot * platformio-custom: Modify mtjson target dependency to prevent fake-success. (#10291) Co-authored-by: Copilot * Fix ESP32-C6 linker errors. Align .text.handler_execute section to 4 bytes and update watchdog timer core mask configuration Co-authored-by: Copilot * tlora-c6: Disable Screen MESHTASTIC_EXCLUDE_SCREEN=1 on tlora-c6. It doesn't have a screen, and this gets it compiling again (saving flash). * Use mverch's iram_memset hack for all OG-ESP32 * Refactor watchdog timer initialization and handling * use adc_channel_t in variant.h * Fix variant headers * More idiomatic default ethernet that doesn't break the build * Elecrows: Delete problematic variant.cpp Not needed after USE_ETHERNET_DEFAULT --------- Co-authored-by: mverch67 Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com> Co-authored-by: Catalin Patulea Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> Co-authored-by: Copilot Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> Co-authored-by: Ben Meadors --- bin/build-esp32.sh | 2 +- bin/platformio-custom.py | 12 +- boards/CDEBYTE_EoRa-S3.json | 2 +- boards/bpi_picow_esp32_s3.json | 2 +- boards/hackaday-communicator.json | 2 +- boards/heltec_vision_master_e213.json | 2 +- boards/heltec_vision_master_e290.json | 2 +- boards/heltec_vision_master_t190.json | 2 +- boards/heltec_wireless_tracker.json | 2 +- boards/heltec_wireless_tracker_v2.json | 2 +- boards/seeed-xiao-s3.json | 2 +- boards/t-beam-1w.json | 2 +- boards/t-deck.json | 2 +- boards/tbeam-s3-core.json | 2 +- boards/tlora-t3s3-v1.json | 2 +- boards/unphone.json | 2 +- default_16MB.csv | 7 + default_8MB.csv | 7 + extra_scripts/esp32_extra.py | 17 +- extra_scripts/ld_response_file.py | 23 + platformio.ini | 2 +- src/DebugConfiguration.h | 5 +- src/FSCommon.cpp | 2 +- src/Power.cpp | 209 +++++---- src/configuration.h | 4 + src/graphics/niche/InkHUD/MessageStore.cpp | 9 +- src/input/ExpressLRSFiveWay.h | 2 + src/input/TLoraPagerKeyboard.cpp | 4 +- src/mesh/NodeDB.cpp | 4 + src/mesh/api/WiFiServerAPI.h | 5 +- src/mesh/http/WebServer.cpp | 5 +- src/mesh/udp/UdpMulticastHandler.h | 5 +- src/mesh/wifi/WiFiAPClient.cpp | 5 +- src/mesh/wifi/WiFiAPClient.h | 5 +- src/modules/AdminModule.cpp | 2 +- src/modules/esp32/PaxcounterModule.cpp | 4 +- src/mqtt/MQTT.cpp | 8 +- src/nimble/NimbleBluetooth.cpp | 437 ++++++++---------- src/nimble/NimbleBluetooth.h | 5 - src/platform/esp32/IramMemcpy.c | 42 ++ src/platform/esp32/IramMemset.c | 42 ++ .../esp32/align-text.handler_execute-4.ld | 11 + src/platform/esp32/main-esp32.cpp | 27 +- src/power.h | 11 +- src/sleep.cpp | 42 +- variants/esp32/chatter2/variant.h | 2 +- .../diy/9m2ibr_aprs_lora_tracker/variant.h | 2 +- variants/esp32/diy/dr-dev/variant.h | 2 +- variants/esp32/diy/hydra/variant.h | 2 +- variants/esp32/diy/v1/variant.h | 2 +- variants/esp32/esp32-common.ini | 272 ++++++++++- variants/esp32/esp32.ini | 31 +- variants/esp32/heltec_wsl_v2.1/variant.h | 2 +- variants/esp32/m5stack_coreink/variant.h | 2 +- variants/esp32/nano-g1-explorer/variant.h | 2 +- .../radiomaster_900_bandit/platformio.ini | 2 +- variants/esp32/rak11200/variant.h | 2 +- variants/esp32/station-g1/variant.h | 2 +- variants/esp32/tbeam/platformio.ini | 1 + variants/esp32/tbeam_v07/variant.h | 2 +- variants/esp32/tlora_v1_3/variant.h | 2 +- variants/esp32/tlora_v2/variant.h | 2 +- variants/esp32/tlora_v2_1_16/variant.h | 2 +- variants/esp32/tlora_v2_1_18/variant.h | 2 +- variants/esp32/trackerd/variant.h | 2 +- variants/esp32c3/esp32c3.ini | 13 + variants/esp32c6/esp32c6.ini | 60 +-- .../esp32c6/m5stack_unitc6l/platformio.ini | 16 - variants/esp32c6/tlora_c6/platformio.ini | 8 + variants/esp32p4/esp32p4.ini | 110 +++++ variants/esp32s2/esp32s2.ini | 13 +- variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h | 2 +- variants/esp32s3/CDEBYTE_EoRa-S3/variant.h | 2 +- .../esp32s3/ELECROW-ThinkNode-G3/variant.cpp | 6 - .../esp32s3/ELECROW-ThinkNode-G3/variant.h | 2 + .../esp32s3/ELECROW-ThinkNode-M2/variant.h | 4 +- .../esp32s3/ELECROW-ThinkNode-M5/variant.h | 2 +- .../esp32s3/ELECROW-ThinkNode-M7/variant.cpp | 6 - .../esp32s3/ELECROW-ThinkNode-M7/variant.h | 2 + .../crowpanel-esp32s3-5-epaper/variant.h | 2 +- .../diy/my_esp32s3_diy_eink/platformio.ini | 1 + .../diy/my_esp32s3_diy_oled/platformio.ini | 4 - .../diy/t-energy-s3_e22/platformio.ini | 5 +- .../esp32s3/diy/t-energy-s3_e22/variant.h | 2 +- variants/esp32s3/elecrow_panel/pins_arduino.h | 6 +- variants/esp32s3/elecrow_panel/platformio.ini | 7 +- variants/esp32s3/esp32-s3-pico/variant.h | 4 +- variants/esp32s3/esp32s3.ini | 10 + .../hackaday-communicator/platformio.ini | 4 +- .../heltec_capsule_sensor_v3/variant.h | 2 +- variants/esp32s3/heltec_sensor_hub/variant.h | 2 +- variants/esp32s3/heltec_v3/variant.h | 2 +- variants/esp32s3/heltec_v4/variant.h | 2 +- variants/esp32s3/heltec_v4_r8/variant.h | 2 +- .../heltec_vision_master_e213/platformio.ini | 2 + .../heltec_vision_master_e213/variant.h | 2 +- .../heltec_vision_master_e290/variant.h | 2 +- .../heltec_vision_master_t190/variant.h | 2 +- .../esp32s3/heltec_wireless_paper/variant.h | 2 +- .../heltec_wireless_paper_v1/variant.h | 2 +- .../heltec_wireless_tracker/platformio.ini | 4 +- .../esp32s3/heltec_wireless_tracker/variant.h | 2 +- .../heltec_wireless_tracker_V1_0/variant.h | 2 +- .../heltec_wireless_tracker_v2/variant.h | 2 +- variants/esp32s3/heltec_wsl_v3/variant.h | 2 +- variants/esp32s3/icarus/platformio.ini | 4 +- .../esp32s3/m5stack_cardputer_adv/variant.h | 2 +- variants/esp32s3/mesh-tab/variant.h | 2 +- variants/esp32s3/mini-epaper-s3/variant.h | 2 +- variants/esp32s3/picomputer-s3/variant.h | 2 +- variants/esp32s3/rak3312/platformio.ini | 2 +- variants/esp32s3/rak3312/variant.h | 4 +- variants/esp32s3/rak_wismesh_tap_v2/variant.h | 2 +- .../seeed-sensecap-indicator/platformio.ini | 53 ++- .../seeed-sensecap-indicator/variant.h | 4 +- variants/esp32s3/seeed_xiao_s3/platformio.ini | 4 - variants/esp32s3/seeed_xiao_s3/variant.h | 2 +- variants/esp32s3/station-g2/variant.h | 2 +- variants/esp32s3/t-beam-1w/variant.h | 2 +- variants/esp32s3/t-deck/variant.h | 2 +- variants/esp32s3/t-eth-elite/platformio.ini | 17 +- variants/esp32s3/t5s3_epaper/variant.h | 1 + variants/esp32s3/tlora_t3s3_epaper/variant.h | 2 +- variants/esp32s3/tlora_t3s3_v1/variant.h | 2 +- .../esp32s3/tracksenger/internal/variant.h | 2 +- variants/esp32s3/tracksenger/lcd/variant.h | 2 +- variants/esp32s3/tracksenger/oled/variant.h | 2 +- variants/esp32s3/unphone/platformio.ini | 1 - 128 files changed, 1159 insertions(+), 615 deletions(-) create mode 100644 default_16MB.csv create mode 100644 default_8MB.csv create mode 100644 extra_scripts/ld_response_file.py create mode 100644 src/platform/esp32/IramMemcpy.c create mode 100644 src/platform/esp32/IramMemset.c create mode 100644 src/platform/esp32/align-text.handler_execute-4.ld create mode 100644 variants/esp32p4/esp32p4.ini delete mode 100644 variants/esp32s3/ELECROW-ThinkNode-G3/variant.cpp delete mode 100644 variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp diff --git a/bin/build-esp32.sh b/bin/build-esp32.sh index d07a09a16..4e799b30a 100755 --- a/bin/build-esp32.sh +++ b/bin/build-esp32.sh @@ -38,4 +38,4 @@ cp bin/device-install.* $OUTDIR/ cp bin/device-update.* $OUTDIR/ echo "Copying manifest" -cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true +cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index b75c66624..f1946770c 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -293,9 +293,12 @@ if ("HAS_TFT", 1) in env.get("CPPDEFINES", []): board_arch = infer_architecture(env.BoardConfig()) should_skip_manifest = board_arch is None -# For host/native envs, avoid depending on 'buildprog' (some targets don't define it) -mtjson_deps = [] if should_skip_manifest else ["buildprog"] -if not should_skip_manifest and platform.name == "espressif32": +# Most platforms can generate the manifest as part of the default 'buildprog' target. +# Typically this passes success/failure properly. +mtjson_deps = ["buildprog"] +if platform.name == "espressif32": + # On ESP32, we need to explicitly depend upon the binary to prevent fake-success upon failure. + mtjson_deps = ["$BUILD_DIR/${PROGNAME}.bin"] # Build littlefs image as part of mtjson target # Equivalent to `pio run -t buildfs` target_lfs = env.DataToBin( @@ -309,7 +312,8 @@ if should_skip_manifest: env.AddCustomTarget( name="mtjson", - dependencies=mtjson_deps, + # For host/native envs, avoid depending on 'buildprog' (some targets don't define it) + dependencies=[], actions=[skip_manifest], title="Meshtastic Manifest (skipped)", description="mtjson generation is skipped for native environments", diff --git a/boards/CDEBYTE_EoRa-S3.json b/boards/CDEBYTE_EoRa-S3.json index afaabc5a7..2355cecd3 100644 --- a/boards/CDEBYTE_EoRa-S3.json +++ b/boards/CDEBYTE_EoRa-S3.json @@ -7,7 +7,7 @@ "extra_flags": [ "-D CDEBYTE_EORA_S3", "-D ARDUINO_USB_CDC_ON_BOOT=1", - "-D ARDUINO_USB_MODE=0", + "-D ARDUINO_USB_MODE=1", "-D ARDUINO_RUNNING_CORE=1", "-D ARDUINO_EVENT_RUNNING_CORE=1", "-D BOARD_HAS_PSRAM" diff --git a/boards/bpi_picow_esp32_s3.json b/boards/bpi_picow_esp32_s3.json index 75983d845..62ad666f1 100644 --- a/boards/bpi_picow_esp32_s3.json +++ b/boards/bpi_picow_esp32_s3.json @@ -6,7 +6,7 @@ "core": "esp32", "extra_flags": [ "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1", "-DBOARD_HAS_PSRAM" diff --git a/boards/hackaday-communicator.json b/boards/hackaday-communicator.json index 6e6c1ad2d..5aedf5d19 100644 --- a/boards/hackaday-communicator.json +++ b/boards/hackaday-communicator.json @@ -8,7 +8,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/heltec_vision_master_e213.json b/boards/heltec_vision_master_e213.json index 152515cf3..d9d5f8582 100644 --- a/boards/heltec_vision_master_e213.json +++ b/boards/heltec_vision_master_e213.json @@ -9,7 +9,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/heltec_vision_master_e290.json b/boards/heltec_vision_master_e290.json index b7cbac878..171125338 100644 --- a/boards/heltec_vision_master_e290.json +++ b/boards/heltec_vision_master_e290.json @@ -9,7 +9,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/heltec_vision_master_t190.json b/boards/heltec_vision_master_t190.json index 440f76ad0..fbdf1f09d 100644 --- a/boards/heltec_vision_master_t190.json +++ b/boards/heltec_vision_master_t190.json @@ -9,7 +9,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/heltec_wireless_tracker.json b/boards/heltec_wireless_tracker.json index 04c6e5553..59d0daa15 100644 --- a/boards/heltec_wireless_tracker.json +++ b/boards/heltec_wireless_tracker.json @@ -8,7 +8,7 @@ "extra_flags": [ "-DHELTEC_WIRELESS_TRACKER", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/heltec_wireless_tracker_v2.json b/boards/heltec_wireless_tracker_v2.json index 502954e69..3d20f7edb 100644 --- a/boards/heltec_wireless_tracker_v2.json +++ b/boards/heltec_wireless_tracker_v2.json @@ -7,7 +7,7 @@ "core": "esp32", "extra_flags": [ "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/seeed-xiao-s3.json b/boards/seeed-xiao-s3.json index 6981085dd..d8e5d6b94 100644 --- a/boards/seeed-xiao-s3.json +++ b/boards/seeed-xiao-s3.json @@ -8,7 +8,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=0" ], diff --git a/boards/t-beam-1w.json b/boards/t-beam-1w.json index 40f16195d..80776ee05 100644 --- a/boards/t-beam-1w.json +++ b/boards/t-beam-1w.json @@ -9,7 +9,7 @@ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_1W", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/t-deck.json b/boards/t-deck.json index b112921b9..33a34b60d 100644 --- a/boards/t-deck.json +++ b/boards/t-deck.json @@ -8,7 +8,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json index 7bda2e5a0..8d2c3eed6 100644 --- a/boards/tbeam-s3-core.json +++ b/boards/tbeam-s3-core.json @@ -8,7 +8,7 @@ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_S3_CORE", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/tlora-t3s3-v1.json b/boards/tlora-t3s3-v1.json index 0bfd17afc..652b4178e 100644 --- a/boards/tlora-t3s3-v1.json +++ b/boards/tlora-t3s3-v1.json @@ -7,7 +7,7 @@ "extra_flags": [ "-DLILYGO_T3S3_V1", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1", "-DBOARD_HAS_PSRAM" diff --git a/boards/unphone.json b/boards/unphone.json index 4d37f7bb5..72075f5ae 100644 --- a/boards/unphone.json +++ b/boards/unphone.json @@ -10,7 +10,7 @@ "-DBOARD_HAS_PSRAM", "-DUNPHONE_SPIN=9", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/default_16MB.csv b/default_16MB.csv new file mode 100644 index 000000000..67d773728 --- /dev/null +++ b/default_16MB.csv @@ -0,0 +1,7 @@ +# 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/default_8MB.csv b/default_8MB.csv new file mode 100644 index 000000000..4e92afa69 --- /dev/null +++ b/default_8MB.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x330000, +app1, app, ota_1, 0x340000,0x330000, +spiffs, data, spiffs, 0x670000,0x180000, +coredump, data, coredump,0x7F0000,0x10000, diff --git a/extra_scripts/esp32_extra.py b/extra_scripts/esp32_extra.py index f7698561a..975ec0f30 100755 --- a/extra_scripts/esp32_extra.py +++ b/extra_scripts/esp32_extra.py @@ -70,17 +70,6 @@ def esp32_create_combined_bin(source, target, env): env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) -esp32_kind = env.GetProjectOption("custom_esp32_kind") -if esp32_kind == "esp32": - # Free up some IRAM by removing auxiliary SPI flash chip drivers. - # Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c. - env.Append( - LINKFLAGS=[ - "-Wl,--wrap=esp_flash_chip_gd", - "-Wl,--wrap=esp_flash_chip_issi", - "-Wl,--wrap=esp_flash_chip_winbond", - ] - ) -else: - # For newer ESP32 targets, using newlib nano works better. - env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) +# Enable Newlib Nano formatting to save space +# ...but allow printf float support (compromise) +env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) diff --git a/extra_scripts/ld_response_file.py b/extra_scripts/ld_response_file.py new file mode 100644 index 000000000..e79475f19 --- /dev/null +++ b/extra_scripts/ld_response_file.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# trunk-ignore-all(ruff/F821) +# trunk-ignore-all(flake8/F821): For SConstruct imports + +# force linker response file instead of command line arguments + +Import("env") + + +def wrap_with_tempfile(command_key): + command = env.get(command_key) + if not command or not isinstance(command, str): + return + if "TEMPFILE(" in command: + return + env.Replace(**{command_key: "${TEMPFILE('%s')}" % command}) + + +# Force SCons to spill long commands into response files on this target. +env.Replace(MAXLINELENGTH=8192) + +for key in ("LINKCOM", "CXXLINKCOM", "SHLINKCOM", "SHCXXLINKCOM"): + wrap_with_tempfile(key) diff --git a/platformio.ini b/platformio.ini index c306eed7b..fd25bfa59 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam +default_envs = heltec-v3 extra_configs = variants/*/*.ini diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index a78dde78e..f8ab28a5e 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -158,9 +158,8 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...); #include #endif // HAS_ETHERNET -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #if HAS_WIFI diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 8fafc6c52..a229dcbfc 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -277,7 +277,7 @@ void fsInit() */ void setupSDCard() { -#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) +#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) && !defined(HAS_SD_MMC) concurrency::LockGuard g(spiLock); SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI); if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) { diff --git a/src/Power.cpp b/src/Power.cpp index 0318dd9f3..43e9f1059 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -24,6 +24,13 @@ #include "meshUtils.h" #include "power/PowerHAL.h" #include "sleep.h" +#ifdef ARCH_ESP32 +// #include +#include +#include +#include +#include +#endif #if defined(ARCH_PORTDUINO) #include "api/WiFiServerAPI.h" @@ -63,9 +70,8 @@ #include #endif -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #endif @@ -77,21 +83,86 @@ #if defined(BATTERY_PIN) && defined(ARCH_ESP32) #ifndef BAT_MEASURE_ADC_UNIT // ADC1 is default -static const adc1_channel_t adc_channel = ADC_CHANNEL; +static const adc_channel_t adc_channel = ADC_CHANNEL; static const adc_unit_t unit = ADC_UNIT_1; -#else // ADC2 -static const adc2_channel_t adc_channel = ADC_CHANNEL; +#else // ADC2 +static const adc_channel_t adc_channel = ADC_CHANNEL; static const adc_unit_t unit = ADC_UNIT_2; -RTC_NOINIT_ATTR uint64_t RTC_reg_b; - #endif // BAT_MEASURE_ADC_UNIT -esp_adc_cal_characteristics_t *adc_characs = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t)); +static adc_oneshot_unit_handle_t adc_handle = nullptr; +static adc_cali_handle_t adc_cali_handle = nullptr; +static bool adc_calibrated = false; #ifndef ADC_ATTENUATION static const adc_atten_t atten = ADC_ATTEN_DB_12; #else static const adc_atten_t atten = ADC_ATTENUATION; #endif +#ifdef ADC_BITWIDTH +static const adc_bitwidth_t adc_width = ADC_BITWIDTH; +#else +static const adc_bitwidth_t adc_width = ADC_BITWIDTH_DEFAULT; +#endif + +static int adcBitWidthToBits(adc_bitwidth_t width) +{ + switch (width) { + case ADC_BITWIDTH_9: + return 9; + case ADC_BITWIDTH_10: + return 10; + case ADC_BITWIDTH_11: + return 11; + case ADC_BITWIDTH_12: + return 12; +#ifdef ADC_BITWIDTH_13 + case ADC_BITWIDTH_13: + return 13; +#endif + default: + return 12; + } +} + +static bool initAdcCalibration() +{ +#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = unit, + .atten = atten, + .bitwidth = adc_width, + }; + esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle); + if (ret == ESP_OK) { + LOG_INFO("ADC calibration: curve fitting enabled"); + return true; + } + if (ret != ESP_ERR_NOT_SUPPORTED) { + LOG_WARN("ADC calibration: curve fitting failed: %s", esp_err_to_name(ret)); + } +#endif + +#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + adc_cali_line_fitting_config_t cali_config = { + .unit_id = unit, + .atten = atten, + .bitwidth = adc_width, + .default_vref = DEFAULT_VREF, + }; + esp_err_t ret = adc_cali_create_scheme_line_fitting(&cali_config, &adc_cali_handle); + if (ret == ESP_OK) { + LOG_INFO("ADC calibration: line fitting enabled"); + return true; + } + if (ret != ESP_ERR_NOT_SUPPORTED) { + LOG_WARN("ADC calibration: line fitting failed: %s", esp_err_to_name(ret)); + } +#endif + + LOG_INFO("ADC calibration not supported; using approximate scaling"); + return false; +} + #endif // BATTERY_PIN && ARCH_ESP32 #ifdef EXT_PWR_DETECT @@ -367,8 +438,20 @@ class AnalogBatteryLevel : public HasBatteryLevel scaled *= operativeAdcMultiplier; #elif defined(ARCH_ESP32) // ADC block for espressif platforms raw = espAdcRead(); - scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs); - scaled *= operativeAdcMultiplier; + int voltage_mv = 0; + if (adc_calibrated && adc_cali_handle) { + if (adc_cali_raw_to_voltage(adc_cali_handle, raw, &voltage_mv) != ESP_OK) { + LOG_WARN("ADC calibration read failed; using raw value"); + voltage_mv = 0; + } + } + if (voltage_mv == 0) { + // Fallback approximate conversion without calibration + const int bits = adcBitWidthToBits(adc_width); + const float max_code = powf(2.0f, bits) - 1.0f; + voltage_mv = (int)((raw / max_code) * DEFAULT_VREF); + } + scaled = voltage_mv * operativeAdcMultiplier; #else // block for all other platforms #ifdef ARCH_NRF52 concurrency::LockGuard saadcGuard(concurrency::nrf52SaadcLock); @@ -410,51 +493,22 @@ class AnalogBatteryLevel : public HasBatteryLevel uint32_t raw = 0; uint8_t raw_c = 0; // raw reading counter -#ifndef BAT_MEASURE_ADC_UNIT // ADC1 + if (!adc_handle) { + LOG_ERROR("ADC oneshot handle not initialized"); + return 0; + } + for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) { - int val_ = adc1_get_raw(adc_channel); - if (val_ >= 0) { // save only valid readings - raw += val_; + int val = 0; + esp_err_t err = adc_oneshot_read(adc_handle, adc_channel, &val); + if (err == ESP_OK) { + raw += val; raw_c++; - } - // delayMicroseconds(100); - } -#else // ADC2 -#ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3 - // ADC2 wifi bug workaround not required, breaks compile - // On ESP32S3, ADC2 can take turns with Wifi (?) - - int32_t adc_buf; - esp_err_t read_result; - - // Multiple samples - for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) { - adc_buf = 0; - read_result = -1; - - read_result = adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf); - if (read_result == ESP_OK) { - raw += adc_buf; - raw_c++; // Count valid samples } else { - LOG_DEBUG("An attempt to sample ADC2 failed"); + LOG_DEBUG("ADC read failed: %s", esp_err_to_name(err)); } } -#else // Other ESP32 - int32_t adc_buf = 0; - for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) { - // ADC2 wifi bug workaround, see - // https://github.com/espressif/arduino-esp32/issues/102 - WRITE_PERI_REG(SENS_SAR_READ_CTRL2_REG, RTC_reg_b); - SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV); - adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf); - raw += adc_buf; - raw_c++; - } -#endif // BAT_MEASURE_ADC_UNIT - -#endif // End BAT_MEASURE_ADC_UNIT return (raw / (raw_c < 1 ? 1 : raw_c)); } #endif @@ -666,42 +720,31 @@ bool Power::analogInit() #ifdef ARCH_STM32WL analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); #elif defined(ARCH_ESP32) // ESP32 needs special analog stuff + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = unit, + }; -#ifndef ADC_WIDTH // max resolution by default - static const adc_bits_width_t width = ADC_WIDTH_BIT_12; -#else - static const adc_bits_width_t width = ADC_WIDTH; -#endif -#ifndef BAT_MEASURE_ADC_UNIT // ADC1 - adc1_config_width(width); - adc1_config_channel_atten(adc_channel, atten); -#else // ADC2 - adc2_config_channel_atten(adc_channel, atten); -#ifndef CONFIG_IDF_TARGET_ESP32S3 - // ADC2 wifi bug workaround - // Not required with ESP32S3, breaks compile - RTC_reg_b = READ_PERI_REG(SENS_SAR_READ_CTRL2_REG); -#endif -#endif - // calibrate ADC - esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_characs); - // show ADC characterization base - if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { - LOG_INFO("ADC config based on Two Point values stored in eFuse"); - } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { - LOG_INFO("ADC config based on reference voltage stored in eFuse"); + if (!adc_handle) { + esp_err_t err = adc_oneshot_new_unit(&init_config, &adc_handle); + if (err != ESP_OK) { + LOG_ERROR("ADC oneshot init failed: %s", esp_err_to_name(err)); + return false; + } } -#ifdef CONFIG_IDF_TARGET_ESP32S3 - // ESP32S3 - else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) { - LOG_INFO("ADC config based on Two Point values and fitting curve " - "coefficients stored in eFuse"); + + adc_oneshot_chan_cfg_t chan_cfg = { + .atten = atten, + .bitwidth = adc_width, + }; + + esp_err_t err = adc_oneshot_config_channel(adc_handle, adc_channel, &chan_cfg); + if (err != ESP_OK) { + LOG_ERROR("ADC channel config failed: %s", esp_err_to_name(err)); + return false; } -#endif - else { - LOG_INFO("ADC config based on default reference voltage"); - } -#endif // ARCH_ESP32 + + adc_calibrated = initAdcCalibration(); +#endif // ARCH_ESP32 // NRF52 ADC init moved to powerHAL_init in nrf52 platform diff --git a/src/configuration.h b/src/configuration.h index d263d9ae1..a31198346 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -561,5 +561,9 @@ along with this program. If not, see . #define HAS_SCREEN 0 #endif +#ifndef USE_ETHERNET_DEFAULT +#define USE_ETHERNET_DEFAULT 0 +#endif + #include "DebugConfiguration.h" #include "RF95Configuration.h" diff --git a/src/graphics/niche/InkHUD/MessageStore.cpp b/src/graphics/niche/InkHUD/MessageStore.cpp index 44a1ef633..671f38057 100644 --- a/src/graphics/niche/InkHUD/MessageStore.cpp +++ b/src/graphics/niche/InkHUD/MessageStore.cpp @@ -53,10 +53,11 @@ void InkHUD::MessageStore::saveToFlash() f.write(reinterpret_cast(&m.timestamp), sizeof(m.timestamp)); // Write timestamp. 4 bytes f.write(reinterpret_cast(&m.sender), sizeof(m.sender)); // Write sender NodeId. 4 Bytes f.write(reinterpret_cast(&m.channelIndex), sizeof(m.channelIndex)); // Write channel index. 1 Byte - f.write(reinterpret_cast(m.text.c_str()), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text - f.write('\0'); // Append null term - LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast(i), min(MAX_MESSAGE_SIZE, m.text.size()), - m.text.c_str()); + f.write(reinterpret_cast(m.text.c_str()), + min((size_t)MAX_MESSAGE_SIZE, m.text.size())); // Write message text + f.write('\0'); // Append null term + LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast(i), + min((size_t)MAX_MESSAGE_SIZE, m.text.size()), m.text.c_str()); } // Release firmware's SPI lock, because SafeFile::close needs it diff --git a/src/input/ExpressLRSFiveWay.h b/src/input/ExpressLRSFiveWay.h index 7c7f210f8..e98031710 100644 --- a/src/input/ExpressLRSFiveWay.h +++ b/src/input/ExpressLRSFiveWay.h @@ -13,6 +13,8 @@ #ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE +// REVISIT esp_adc_cal.h +// "legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h" #include #include diff --git a/src/input/TLoraPagerKeyboard.cpp b/src/input/TLoraPagerKeyboard.cpp index 4efa5d6e2..3b7e45d18 100644 --- a/src/input/TLoraPagerKeyboard.cpp +++ b/src/input/TLoraPagerKeyboard.cpp @@ -68,7 +68,7 @@ TLoraPagerKeyboard::TLoraPagerKeyboard() : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX), next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0) { -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); #else ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); @@ -108,7 +108,7 @@ void TLoraPagerKeyboard::setBacklight(bool on) uint32_t _brightness = 0; if (on) _brightness = brightness; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) ledcWrite(KB_BL_PIN, _brightness); #else ledcWrite(LEDC_BACKLIGHT_CHANNEL, _brightness); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5b6a6e980..9a8593bc4 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -985,6 +985,10 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.network.wifi_enabled = USERPREFS_NETWORK_WIFI_ENABLED; #endif +#if USE_ETHERNET_DEFAULT + config.network.eth_enabled = true; +#endif + #ifdef USERPREFS_NETWORK_WIFI_SSID strncpy(config.network.wifi_ssid, USERPREFS_NETWORK_WIFI_SSID, sizeof(config.network.wifi_ssid)); #endif diff --git a/src/mesh/api/WiFiServerAPI.h b/src/mesh/api/WiFiServerAPI.h index 5f2019983..4d754e7ed 100644 --- a/src/mesh/api/WiFiServerAPI.h +++ b/src/mesh/api/WiFiServerAPI.h @@ -3,9 +3,8 @@ #include "ServerAPI.h" #include -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET /** diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 90fd8b084..fddc118a7 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -13,9 +13,8 @@ #include #include -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #ifdef ARCH_ESP32 diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h index 493cc5353..eb4d90d40 100644 --- a/src/mesh/udp/UdpMulticastHandler.h +++ b/src/mesh/udp/UdpMulticastHandler.h @@ -12,9 +12,8 @@ #include -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #define UDP_MULTICAST_DEFAUL_PORT 4403 // Default port for UDP multicast is same as TCP api server diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 9930d0a55..82201ab82 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -10,9 +10,8 @@ #include "target_specific.h" #include -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #if HAS_ETHERNET && defined(USE_CH390D) diff --git a/src/mesh/wifi/WiFiAPClient.h b/src/mesh/wifi/WiFiAPClient.h index 1de897d7a..710868361 100644 --- a/src/mesh/wifi/WiFiAPClient.h +++ b/src/mesh/wifi/WiFiAPClient.h @@ -9,9 +9,8 @@ #include #endif -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET extern bool needReconnect; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index e7acdf89e..1d05caaa2 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -262,7 +262,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta break; } case meshtastic_AdminMessage_ota_request_tag: { -#if defined(ARCH_ESP32) +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI LOG_INFO("OTA Requested"); if (r->ota_request.ota_hash.size != 32) { diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 9c25177bc..4bee4fb7e 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -90,8 +90,8 @@ int32_t PaxcounterModule::runOnce() configuration.blecounter = 1; configuration.blescantime = 0; // infinite configuration.wificounter = 1; - configuration.wifi_channel_map = WIFI_CHANNEL_ALL; - configuration.wifi_channel_switch_interval = 50; + // configuration.wifi_channel_map = WIFI_CHANNEL_ALL; + // configuration.wifi_channel_switch_interval = 50; configuration.wifi_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.wifi_threshold, -80); configuration.ble_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.ble_threshold, -80); libpax_update_config(&configuration); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index a6ae79614..46ad952f4 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -19,12 +19,8 @@ #include "mesh/wifi/WiFiAPClient.h" #include #endif -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 -#elif HAS_ETHERNET && defined(USE_CH390D) -#include "ESP32_CH390.h" -#define ETH CH390 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #include "Default.h" #if !defined(ARCH_NRF52) || NRF52_USE_JSON diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index ac52a99bc..6857f9b5d 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -10,23 +10,18 @@ #include "mesh/PhoneAPI.h" #include "mesh/mesh-pb-constants.h" #include "sleep.h" -#include +#include +#include +#include +#include +#include #include #include -#ifdef NIMBLE_TWO -#include "NimBLEAdvertising.h" -#include "NimBLEExtAdvertising.h" #include "PowerStatus.h" -#endif -#if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_gap.h" -#else -#include "nimble/nimble/host/include/host/ble_gap.h" -#endif - -#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) +#include "host/ble_store.h" namespace { @@ -34,7 +29,6 @@ constexpr uint16_t kPreferredBleMtu = 517; constexpr uint16_t kPreferredBleTxOctets = 251; constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8; } // namespace -#endif // Debugging options: careful, they slow things down quite a bit! // #define DEBUG_NIMBLE_ON_READ_TIMING // uncomment to time onRead duration @@ -44,10 +38,10 @@ constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8; #define NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE 3 #define NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE 3 -NimBLECharacteristic *fromNumCharacteristic; -NimBLECharacteristic *BatteryCharacteristic; -NimBLECharacteristic *logRadioCharacteristic; -NimBLEServer *bleServer; +BLECharacteristic *fromNumCharacteristic; +BLECharacteristic *BatteryCharacteristic; +BLECharacteristic *logRadioCharacteristic; +BLEServer *bleServer; static bool passkeyShowing; static std::atomic nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection" @@ -118,7 +112,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread - Yes, we have to do some copy operations on pop because of this, but it's worth it to avoid cross-task memory management. NOTIFY IS BROKEN: - - Adding NIMBLE_PROPERTY::NOTIFY to FromRadioCharacteristic appears to break things. It is NOT backwards compatible. + - Adding BLECharacteristic::PROPERTY_NOTIFY to FromRadioCharacteristic appears to break things. It is NOT backwards + compatible. ZERO-SIZE READS: - Returning a zero-size read from onRead breaks some clients during the config phase. So we have to block onRead until we @@ -139,7 +134,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread std::mutex fromPhoneMutex; std::atomic fromPhoneQueueSize{0}; // We use array here (and pay the cost of memcpy) to avoid dynamic memory allocations and frees across FreeRTOS tasks. - std::array fromPhoneQueue{}; + std::array fromPhoneQueue{}; /* Packets to phone (BLE onRead callback) */ std::mutex toPhoneMutex; @@ -301,7 +296,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread LOG_DEBUG("NimbleBluetooth: handling ToRadio packet, fromPhoneQueueSize=%u", fromPhoneQueueSize.load()); // Pop the front of fromPhoneQueue, holding the mutex only briefly while we pop. - NimBLEAttValue val; + BLEValue val; { // scope for fromPhoneMutex mutex std::lock_guard guard(fromPhoneMutex); val = fromPhoneQueue[0]; @@ -316,7 +311,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread fromPhoneQueueSize--; } - handleToRadio(val.data(), val.length()); + handleToRadio(val.getData(), val.getLength()); } } @@ -328,9 +323,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread PhoneAPI::onNowHasData(fromRadioNum); #ifdef DEBUG_NIMBLE_NOTIFY - int currentNotifyCount = notifyCount.fetch_add(1); - uint8_t cc = bleServer->getConnectedCount(); // This logging slows things down when there are lots of packets going to the phone, like initial connection: LOG_DEBUG("BLE notify(%d) fromNum: %d connections: %d", currentNotifyCount, fromRadioNum, cc); @@ -340,13 +333,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread put_le32(val, fromRadioNum); fromNumCharacteristic->setValue(val, sizeof(val)); -#ifdef NIMBLE_TWO - // NOTE: I don't have any NIMBLE_TWO devices, but this line makes me suspicious, and I suspect it needs to just be - // notify(). - fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE); -#else fromNumCharacteristic->notify(); -#endif } /// Check the current underlying physical link to see if the client is currently connected @@ -409,14 +396,9 @@ static BluetoothPhoneAPI *bluetoothPhoneAPI; // Last ToRadio value received from the phone static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; -class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks +class NimbleBluetoothToRadioCallback : public BLECharacteristicCallbacks { -#ifdef NIMBLE_TWO - virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override -#else - virtual void onWrite(NimBLECharacteristic *pCharacteristic) override - -#endif + void onWrite(BLECharacteristic *pCharacteristic) override { // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce. // Assumption: onWrite is serialized by NimBLE, so we don't need to lock here against multiple concurrent onWrite calls. @@ -428,15 +410,17 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks LOG_DEBUG("BLE onWrite(%d): start millis=%d", currentWriteCount, startMillis); #endif - auto val = pCharacteristic->getValue(); + // Create a BLEValue and populate it with the received data + BLEValue val; + val.setValue(pCharacteristic->getData(), pCharacteristic->getLength()); - if (memcmp(lastToRadio, val.data(), val.length()) != 0) { + if (memcmp(lastToRadio, val.getData(), val.getLength()) != 0) { if (bluetoothPhoneAPI->fromPhoneQueueSize < NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE) { // Note: the comparison above is safe without a mutex because we are the only method that *increases* // fromPhoneQueueSize. (It's okay if fromPhoneQueueSize *decreases* in the main task meanwhile.) - memcpy(lastToRadio, val.data(), val.length()); + memcpy(lastToRadio, val.getData(), val.getLength()); - { // scope for fromPhoneMutex mutex + { // scope for fromPhoneMutex mutexv, pCharacteristic->getLen // Append to fromPhoneQueue, protected by fromPhoneMutex. Hold the mutex as briefly as possible. std::lock_guard guard(bluetoothPhoneAPI->fromPhoneMutex); bluetoothPhoneAPI->fromPhoneQueue.at(bluetoothPhoneAPI->fromPhoneQueueSize) = val; @@ -450,24 +434,21 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks #ifdef DEBUG_NIMBLE_ON_WRITE_TIMING int finishMillis = millis(); LOG_DEBUG("BLE onWrite(%d): append to fromPhoneQueue took %u ms. numBytes=%d", currentWriteCount, - finishMillis - startMillis, val.length()); + finishMillis - startMillis, val.getLength()); #endif } else { - LOG_WARN("BLE onWrite(%d): Drop ToRadio packet, fromPhoneQueue full (%u bytes)", currentWriteCount, val.length()); + LOG_WARN("BLE onWrite(%d): Drop ToRadio packet, fromPhoneQueue full (%u bytes)", currentWriteCount, + val.getLength()); } } else { - LOG_DEBUG("BLE onWrite(%d): Drop duplicate ToRadio packet (%u bytes)", currentWriteCount, val.length()); + LOG_DEBUG("BLE onWrite(%d): Drop duplicate ToRadio packet (%u bytes)", currentWriteCount, val.getLength()); } } }; -class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks +class NimbleBluetoothFromRadioCallback : public BLECharacteristicCallbacks { -#ifdef NIMBLE_TWO - virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override -#else - virtual void onRead(NimBLECharacteristic *pCharacteristic) override -#endif + void onRead(BLECharacteristic *pCharacteristic) override { // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce. @@ -573,51 +554,46 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks } }; -class NimbleBluetoothServerCallback : public NimBLEServerCallbacks +class NimbleBluetoothSecurityCallback : public BLESecurityCallbacks { -#ifdef NIMBLE_TWO - public: - NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; } - - private: - NimbleBluetooth *ble; - - virtual uint32_t onPassKeyDisplay() override -#else - virtual uint32_t onPassKeyRequest() override -#endif + void onPassKeyNotify(uint32_t passkey) override { - uint32_t configuredPasskey = config.bluetooth.fixed_pin; - - if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN) { - LOG_INFO("Use random passkey"); - // This is the passkey to be entered on peer - we pick a number >100,000 to ensure 6 digits - configuredPasskey = random(100000, 999999); - } - LOG_INFO("*** Enter passkey %d on the peer side ***", configuredPasskey); - + LOG_INFO("*** Enter passkey %06u on the peer side ***", passkey); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - std::string passkey = std::to_string(configuredPasskey); - meshtastic::BluetoothStatus newStatus(passkey); + meshtastic::BluetoothStatus newStatus(std::to_string(passkey)); bluetoothStatus->updateStatus(&newStatus); - -#if HAS_SCREEN // Todo: migrate this display code back into Screen class, and observe bluetoothStatus +#if HAS_SCREEN if (screen) { + screen->startAlert([passkey](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + char btPIN[16] = "888888"; + snprintf(btPIN, sizeof(btPIN), "%06u", passkey); + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 12; + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, y_offset + y, "Bluetooth"); +#if !defined(M5STACK_UNITC6L) + display->setFont(FONT_SMALL); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5; + display->drawString(x_offset + x, y_offset + y, "Enter this code"); +#endif + display->setFont(FONT_LARGE); + char pin[8]; + snprintf(pin, sizeof(pin), "%.3s %.3s", btPIN, btPIN + 3); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5; + display->drawString(x_offset + x, y_offset + y, pin); - std::string ble_message = "Bluetooth\nPIN\n[M]" + passkey.substr(0, 3) + " " + passkey.substr(3, 6); - screen->showSimpleBanner(ble_message.c_str(), 30000); + display->setFont(FONT_SMALL); + char deviceName[64]; + snprintf(deviceName, sizeof(deviceName), "Name: %s", getDeviceName()); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; + display->drawString(x_offset + x, y_offset + y, deviceName); + }); } #endif passkeyShowing = true; - - return configuredPasskey; } - -#ifdef NIMBLE_TWO - virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo) override -#else - virtual void onAuthenticationComplete(ble_gap_conn_desc *desc) override -#endif + void onAuthenticationComplete(ble_gap_conn_desc *desc) override { LOG_INFO("BLE authentication complete"); @@ -625,58 +601,47 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks bluetoothStatus->updateStatus(&newStatus); clearPairingDisplay(); - // Store the connection handle for future use -#ifdef NIMBLE_TWO - nimbleBluetoothConnHandle = connInfo.getConnHandle(); -#else nimbleBluetoothConnHandle = desc->conn_handle; -#endif } +}; -#ifdef NIMBLE_TWO - virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override +class NimbleBluetoothServerCallback : public BLEServerCallbacks +{ + public: + explicit NimbleBluetoothServerCallback(NimbleBluetooth *ble) : ble(ble) {} + + private: + NimbleBluetooth *ble; + + void onConnect(BLEServer *pServer, struct ble_gap_conn_desc *desc) { - LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str()); + BLEAddress peer_addr(desc->peer_id_addr); + LOG_INFO("BLE incoming connection %s", peer_addr.toString().c_str()); - const uint16_t connHandle = connInfo.getConnHandle(); -#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)) - int phyResult = - ble_gap_set_prefered_le_phy(connHandle, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_CODED_ANY); - if (phyResult == 0) { - LOG_INFO("BLE conn %u requested 2M PHY", connHandle); - } else { - LOG_WARN("Failed to prefer 2M PHY for conn %u, rc=%d", connHandle, phyResult); - } -#endif + const uint16_t connHandle = desc->conn_handle; + // With Google Pixel 8 Android devices, this causes ESP32 device crash + // when phone reconnects. Disable this to make progress on the + // Arduino v3 migration while we investigate the Android compatibility + // issue. +#if 0 int dataLenResult = ble_gap_set_data_len(connHandle, kPreferredBleTxOctets, kPreferredBleTxTimeUs); if (dataLenResult == 0) { LOG_INFO("BLE conn %u requested data length %u bytes", connHandle, kPreferredBleTxOctets); } else { LOG_WARN("Failed to raise data length for conn %u, rc=%d", connHandle, dataLenResult); } +#endif - LOG_INFO("BLE conn %u initial MTU %u (target %u)", connHandle, connInfo.getMTU(), kPreferredBleMtu); + LOG_INFO("BLE conn %u peer MTU %u (target %u)", connHandle, pServer->getPeerMTU(connHandle), kPreferredBleMtu); pServer->updateConnParams(connHandle, 6, 12, 0, 200); } -#endif -#ifdef NIMBLE_TWO - virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override + void onDisconnect(BLEServer *pServer, struct ble_gap_conn_desc *desc) { - LOG_INFO("BLE disconnect reason: %d", reason); -#else - virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc) override - { - LOG_INFO("BLE disconnect"); -#endif -#ifdef NIMBLE_TWO + LOG_INFO("BLE disconnected"); if (ble->isDeInit) return; -#else - if (nimbleBluetooth && nimbleBluetooth->isDeInit) - return; -#endif meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); bluetoothStatus->updateStatus(&newStatus); @@ -701,43 +666,51 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks bluetoothPhoneAPI->writeCount = 0; } - // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection memset(lastToRadio, 0, sizeof(lastToRadio)); - nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; // BLE_HS_CONN_HANDLE_NONE means "no connection" + nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; -#ifdef NIMBLE_TWO - // Restart Advertising ble->startAdvertising(); -#else - NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); - if (!pAdvertising->start(0)) { - if (pAdvertising->isAdvertising()) { - LOG_DEBUG("BLE advertising already running"); - } else { - LOG_ERROR("BLE failed to restart advertising"); - } - } -#endif } }; static NimbleBluetoothToRadioCallback *toRadioCallbacks; static NimbleBluetoothFromRadioCallback *fromRadioCallbacks; +void NimbleBluetooth::startAdvertising() +{ + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->stop(); + pAdvertising->reset(); + + pAdvertising->addServiceUUID(MESH_SERVICE_UUID); + // if (powerStatus->getHasBattery() == 1) { + // pAdvertising->addServiceUUID(BLEUUID((uint16_t)0x180f)); + // } + + BLEAdvertisementData scan = BLEAdvertisementData(); + scan.setName(getDeviceName()); + pAdvertising->setScanResponseData(scan); + pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue + pAdvertising->setMaxPreferred(0x12); + + if (!pAdvertising->start(0)) { + LOG_ERROR("BLE failed to start advertising"); + } else { + LOG_DEBUG("BLE Advertising started"); + } +} + void NimbleBluetooth::shutdown() { - // No measurable power saving for ESP32 during light-sleep(?) #ifndef ARCH_ESP32 - // Shutdown bluetooth for minimum power draw LOG_INFO("Disable bluetooth"); - NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->reset(); pAdvertising->stop(); #endif } -// Proper shutdown for ESP32. Needs reboot to reverse. void NimbleBluetooth::deinit() { #ifdef ARCH_ESP32 @@ -747,21 +720,19 @@ void NimbleBluetooth::deinit() #ifdef BLE_LED digitalWrite(BLE_LED, LED_STATE_OFF); #endif -#ifndef NIMBLE_TWO - NimBLEDevice::deinit(); -#endif + + BLEDevice::deinit(true); #endif } -// Has initial setup been completed bool NimbleBluetooth::isActive() { - return bleServer; + return bleServer != nullptr; } bool NimbleBluetooth::isConnected() { - return bleServer->getConnectedCount() > 0; + return bleServer && bleServer->getConnectedCount() > 0; } int NimbleBluetooth::getRssi() @@ -774,9 +745,9 @@ int NimbleBluetooth::getRssi() uint16_t connHandle = nimbleBluetoothConnHandle.load(); if (connHandle == BLE_HS_CONN_HANDLE_NONE) { - const auto peers = bleServer->getPeerDevices(); + const auto peers = bleServer->getPeerDevices(true); if (!peers.empty()) { - connHandle = peers.front(); + connHandle = peers.begin()->first; nimbleBluetoothConnHandle = connHandle; } } @@ -804,74 +775,84 @@ void NimbleBluetooth::setup() LOG_INFO("Init the NimBLE bluetooth module"); - NimBLEDevice::init(getDeviceName()); - NimBLEDevice::setPower(ESP_PWR_LVL_P9); + BLEDevice::init(getDeviceName()); + BLEDevice::setPower(ESP_PWR_LVL_P9); -#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)) - int mtuResult = NimBLEDevice::setMTU(kPreferredBleMtu); + int mtuResult = BLEDevice::setMTU(kPreferredBleMtu); if (mtuResult == 0) { LOG_INFO("BLE MTU request set to %u", kPreferredBleMtu); } else { LOG_WARN("Unable to request MTU %u, rc=%d", kPreferredBleMtu, mtuResult); } - int phyResult = ble_gap_set_prefered_default_le_phy(BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK); - if (phyResult == 0) { - LOG_INFO("BLE default PHY preference set to 2M"); - } else { - LOG_WARN("Failed to prefer 2M PHY by default, rc=%d", phyResult); - } - - int dataLenResult = ble_gap_write_sugg_def_data_len(kPreferredBleTxOctets, kPreferredBleTxTimeUs); - if (dataLenResult == 0) { - LOG_INFO("BLE suggested data length set to %u bytes", kPreferredBleTxOctets); - } else { - LOG_WARN("Failed to raise suggested data length (%u/%u), rc=%d", kPreferredBleTxOctets, kPreferredBleTxTimeUs, - dataLenResult); - } -#endif - + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { - NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC); - NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); - NimBLEDevice::setSecurityRespKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); - NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); + // Set IO capability to DisplayOnly for MITM authentication + pSecurity->setCapability(ESP_IO_CAP_OUT); + // Set the passkey + if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN) { + LOG_INFO("Use random passkey"); + pSecurity->setPassKey(false); // generate a random passkey + } else { + LOG_INFO("Use fixed passkey"); + pSecurity->setPassKey(true, config.bluetooth.fixed_pin); + } + // Enable authorization requirements: + // - bonding: true (for persistent storage of the keys) + // - MITM: true (enables Man-In-The-Middle protection for password prompts) + // - secure connection: true (enables secure connection for encryption) + pSecurity->setAuthenticationMode(true, true, true); + } else { + // No IO capability for no PIN mode + pSecurity->setCapability(ESP_IO_CAP_NONE); + // No PIN mode: no MITM protection + pSecurity->setAuthenticationMode(true, false, false); } - bleServer = NimBLEDevice::createServer(); -#ifdef NIMBLE_TWO - NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this); -#else - NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(); -#endif - bleServer->setCallbacks(serverCallbacks, true); + // Set the security callbacks + BLEDevice::setSecurityCallbacks(new NimbleBluetoothSecurityCallback()); + bleServer = BLEDevice::createServer(); + + // BLEDevice::createServer calls ble_svc_gap_init, which resets the device + // name to default, so set it again. + int nameRc = ble_svc_gap_device_name_set(BLEDevice::getDeviceName().c_str()); + if (nameRc != 0) { + LOG_ERROR("ble_svc_gap_device_name_set: rc=%d %s", nameRc, BLEUtils::returnCodeToString(nameRc)); + } + + bleServer->setCallbacks(new NimbleBluetoothServerCallback(this)); setupService(); startAdvertising(); } void NimbleBluetooth::setupService() { - NimBLEService *bleService = bleServer->createService(MESH_SERVICE_UUID); - NimBLECharacteristic *ToRadioCharacteristic; - NimBLECharacteristic *FromRadioCharacteristic; + BLEService *bleService = bleServer->createService(MESH_SERVICE_UUID); + BLECharacteristic *ToRadioCharacteristic; + BLECharacteristic *FromRadioCharacteristic; // Define the characteristics that the app is looking for if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { - ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE); + ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, BLECharacteristic::PROPERTY_WRITE); // Allow notifications so phones can stream FromRadio without polling. - FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ); - fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); - logRadioCharacteristic = - bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ, 512U); - } else { - ToRadioCharacteristic = bleService->createCharacteristic( - TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC); - FromRadioCharacteristic = bleService->createCharacteristic( - FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); + FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, BLECharacteristic::PROPERTY_READ); fromNumCharacteristic = - bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); + bleService->createCharacteristic(FROMNUM_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ); + logRadioCharacteristic = bleService->createCharacteristic(LOGRADIO_UUID, BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_READ); + } else { + ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_WRITE_AUTHEN | + BLECharacteristic::PROPERTY_WRITE_ENC); + FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_READ_AUTHEN | + BLECharacteristic::PROPERTY_READ_ENC); + fromNumCharacteristic = bleService->createCharacteristic( + FROMNUM_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_ENC); logRadioCharacteristic = bleService->createCharacteristic( - LOGRADIO_UUID, - NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC, 512U); + LOGRADIO_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_ENC); } bluetoothPhoneAPI = new BluetoothPhoneAPI(); @@ -884,76 +865,31 @@ void NimbleBluetooth::setupService() bleService->start(); // Setup the battery service - NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service - BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic) - (uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1); -#ifdef NIMBLE_TWO - NimBLE2904 *batteryLevelDescriptor = BatteryCharacteristic->create2904(); -#else - NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904); -#endif - batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8); + BLEService *batteryService = bleServer->createService(BLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service + BLE2904 *batteryLevelDescriptor = new BLE2904(); + batteryLevelDescriptor->setFormat(BLE2904::FORMAT_UINT8); batteryLevelDescriptor->setNamespace(1); batteryLevelDescriptor->setUnit(0x27ad); - + BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic) + (uint16_t)0x2a19, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BatteryCharacteristic->addDescriptor(batteryLevelDescriptor); batteryService->start(); } -void NimbleBluetooth::startAdvertising() -{ -#ifdef NIMBLE_TWO - NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); - NimBLEExtAdvertisement legacyAdvertising; - - legacyAdvertising.setLegacyAdvertising(true); - legacyAdvertising.setScannable(true); - legacyAdvertising.setConnectable(true); - legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN); - if (powerStatus->getHasBattery() == 1) { - legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f)); - } - legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID)); - legacyAdvertising.setMinInterval(500); - legacyAdvertising.setMaxInterval(1000); - - NimBLEExtAdvertisement legacyScanResponse; - legacyScanResponse.setLegacyAdvertising(true); - legacyScanResponse.setConnectable(true); - legacyScanResponse.setName(getDeviceName()); - - if (!pAdvertising->setInstanceData(0, legacyAdvertising)) { - LOG_ERROR("BLE failed to set legacyAdvertising"); - } else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) { - LOG_ERROR("BLE failed to set legacyScanResponse"); - } else if (!pAdvertising->start(0, 0, 0)) { - LOG_ERROR("BLE failed to start legacyAdvertising"); - } -#else - NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); - pAdvertising->reset(); - pAdvertising->addServiceUUID(MESH_SERVICE_UUID); - pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service - pAdvertising->start(0); -#endif -} - /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level) { if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) { BatteryCharacteristic->setValue(&level, 1); -#ifdef NIMBLE_TWO - BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE); -#else BatteryCharacteristic->notify(); -#endif } } void NimbleBluetooth::clearBonds() { LOG_INFO("Clearing bluetooth bonds!"); - NimBLEDevice::deleteAllBonds(); + ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_PEER_SEC, nullptr); + ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_CCCD, nullptr); } void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length) @@ -961,11 +897,16 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length) if (!bleServer || !isConnected() || length > 512) { return; } -#ifdef NIMBLE_TWO - logRadioCharacteristic->notify(logMessage, length, BLE_HS_CONN_HANDLE_NONE); -#else - logRadioCharacteristic->notify(logMessage, length, true); -#endif + logRadioCharacteristic->setValue(logMessage, length); + logRadioCharacteristic->notify(); } +void clearNVS() +{ + ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_PEER_SEC, nullptr); + ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_CCCD, nullptr); +#ifdef ARCH_ESP32 + ESP.restart(); +#endif +} #endif diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index a7b14ff73..dad0a8c98 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -12,16 +12,11 @@ class NimbleBluetooth : BluetoothApi bool isConnected(); int getRssi(); void sendLog(const uint8_t *logMessage, size_t length); -#if defined(NIMBLE_TWO) void startAdvertising(); -#endif bool isDeInit = false; private: void setupService(); -#if !defined(NIMBLE_TWO) - void startAdvertising(); -#endif }; void setBluetoothEnable(bool enable); \ No newline at end of file diff --git a/src/platform/esp32/IramMemcpy.c b/src/platform/esp32/IramMemcpy.c new file mode 100644 index 000000000..898f1bdc6 --- /dev/null +++ b/src/platform/esp32/IramMemcpy.c @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "esp_attr.h" + +#ifdef ESP32_FORCE_IRAM_MEMSET + +/* + * T-Beam/classic ESP32 boot workaround + * ------------------------------------ + * During early flash operations the ESP32 disables cache, but some IRAM flash + * code paths still reach libc memcpy/memset. If those resolve to flash-resident + * implementations, startup can panic with cache-disabled access errors. + * + * We wrap memcpy/memset for the T-Beam environment. Fast path uses the + * normal libc routines when cache is enabled; slow path uses IRAM-safe byte + * loops when cache is disabled. + */ + +extern void *__real_memcpy(void *dst, const void *src, size_t n); + +static inline bool IRAM_ATTR cache_is_enabled(void) +{ + return (*(volatile uint32_t *)0x3FF00040u) & (1u << 3); +} + +extern void *IRAM_ATTR __wrap_memcpy(void *dst, const void *src, size_t n) +{ + if (cache_is_enabled()) { + return __real_memcpy(dst, src, n); + } + + uint8_t *d = (uint8_t *)dst; + const uint8_t *s = (const uint8_t *)src; + while (n--) { + *d++ = *s++; + } + return dst; +} + +#endif diff --git a/src/platform/esp32/IramMemset.c b/src/platform/esp32/IramMemset.c new file mode 100644 index 000000000..66d74d8bc --- /dev/null +++ b/src/platform/esp32/IramMemset.c @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "esp_attr.h" + +#ifdef ESP32_FORCE_IRAM_MEMSET + +/* + * T-Beam/classic ESP32 boot workaround + * ------------------------------------ + * During early flash operations the ESP32 disables cache, but some IRAM flash + * code paths still reach libc memcpy/memset. If those resolve to flash-resident + * implementations, startup can panic with cache-disabled access errors. + * + * We wrap memcpy/memset for the T-Beam environment. Fast path uses the + * normal libc routines when cache is enabled; slow path uses IRAM-safe byte + * loops when cache is disabled. + */ + +extern void *__real_memset(void *dst, int c, size_t n); + +static inline bool IRAM_ATTR cache_is_enabled(void) +{ + return (*(volatile uint32_t *)0x3FF00040u) & (1u << 3); +} + +extern void *IRAM_ATTR __wrap_memset(void *dst, int c, size_t n) +{ + if (cache_is_enabled()) { + return __real_memset(dst, c, n); + } + + uint8_t *ptr = (uint8_t *)dst; + const uint8_t fill = (uint8_t)c; + while (n--) { + *ptr++ = fill; + } + return dst; +} + +#endif \ No newline at end of file diff --git a/src/platform/esp32/align-text.handler_execute-4.ld b/src/platform/esp32/align-text.handler_execute-4.ld new file mode 100644 index 000000000..fca4f27a2 --- /dev/null +++ b/src/platform/esp32/align-text.handler_execute-4.ld @@ -0,0 +1,11 @@ +/* Arduino fix: catch esp_event's orphaned .text.handler_execute section and align to 4 bytes */ +SECTIONS +{ + .text.handler_execute ALIGN(4) : + { + KEEP(*(.text.handler_execute)) + KEEP(*(.text.handler_execute.*)) + . = ALIGN(4); + } +} +INSERT AFTER .flash.text; \ No newline at end of file diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index dbc573c95..69b69ff68 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -165,17 +165,30 @@ void esp32Setup() // #define APP_WATCHDOG_SECS 45 #define APP_WATCHDOG_SECS 90 -#ifdef CONFIG_IDF_TARGET_ESP32C6 - esp_task_wdt_config_t *wdt_config = (esp_task_wdt_config_t *)malloc(sizeof(esp_task_wdt_config_t)); - wdt_config->timeout_ms = APP_WATCHDOG_SECS * 1000; - wdt_config->trigger_panic = true; - res = esp_task_wdt_init(wdt_config); +#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) + const esp_task_wdt_config_t wdt_config = { + .timeout_ms = APP_WATCHDOG_SECS * 1000, + .idle_core_mask = (1U << CONFIG_FREERTOS_NUMBER_OF_CORES) - 1U, + .trigger_panic = true, + }; + res = esp_task_wdt_init(&wdt_config); + if (res == ESP_ERR_INVALID_STATE) { + LOG_WARN("Task watchdog already initialized, reconfiguring existing instance"); + res = esp_task_wdt_reconfigure(&wdt_config); + } assert(res == ESP_OK); #else res = esp_task_wdt_init(APP_WATCHDOG_SECS, true); + if (res == ESP_ERR_INVALID_STATE) { + LOG_WARN("Task watchdog already initialized, reusing existing instance"); + res = ESP_OK; + } assert(res == ESP_OK); #endif - res = esp_task_wdt_add(NULL); + res = esp_task_wdt_status(NULL); + if (res == ESP_ERR_NOT_FOUND) { + res = esp_task_wdt_add(NULL); + } assert(res == ESP_OK); #if HAS_32768HZ @@ -258,8 +271,10 @@ void cpuDeepSleep(uint32_t msecToWake) #endif variant_shutdown(); +#if SOC_PM_SUPPORT_RTC_PERIPH_PD // We want RTC peripherals to stay on esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); +#endif esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs esp_deep_sleep_start(); // TBD mA sleep current (battery) diff --git a/src/power.h b/src/power.h index 4b5ef609d..993163c39 100644 --- a/src/power.h +++ b/src/power.h @@ -4,8 +4,10 @@ #include "configuration.h" #ifdef ARCH_ESP32 -// "legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h -#include +// #include +#include +#include +#include #include #endif @@ -28,11 +30,6 @@ #define NUM_CELLS 1 #endif -#ifdef BAT_MEASURE_ADC_UNIT -extern RTC_NOINIT_ATTR uint64_t RTC_reg_b; -#include "soc/sens_reg.h" // needed for adc pin reset -#endif - #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "modules/Telemetry/Sensor/nullSensor.h" #if __has_include() diff --git a/src/sleep.cpp b/src/sleep.cpp index 792781f6d..626fc4e11 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -18,8 +18,6 @@ #include "target_specific.h" #ifdef ARCH_ESP32 -// "esp_pm_config_esp32_t is deprecated, please include esp_pm.h and use esp_pm_config_t instead" -#include "esp32/pm.h" #include "esp_pm.h" #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" @@ -146,15 +144,31 @@ void initDeepSleep() // If we booted because our timer ran out or the user pressed reset, send those as fake events RESET_REASON hwReason = rtc_get_reset_reason(0); +#ifdef CONFIG_IDF_TARGET_ESP32P4 + if (hwReason == BROWN_OUT_RESET) + reason = "brownout"; + else if (hwReason == HP_CORE_HP_WDT_RESET) + reason = "taskWatchdog"; + else if (hwReason == HP_CORE_LP_WDT_RESET) + reason = "intWatchdog"; + else if (hwReason == CHIP_LP_WDT_RESET) + reason = "chipWatchdog"; + else if (hwReason == SUPER_WDT_RESET) + reason = "superWatchdog"; + else if (hwReason == HP_SYS_HP_WDT_RESET) + reason = "systemWatchdog"; + else if (hwReason == HP_SYS_LP_WDT_RESET) + reason = "systemLowPowerWatchdog"; +#else if (hwReason == RTCWDT_BROWN_OUT_RESET) reason = "brownout"; - - if (hwReason == TG0WDT_SYS_RESET) + else if (hwReason == RTCWDT_RTC_RESET) + reason = "rtcWatchdog"; + else if (hwReason == TG0WDT_SYS_RESET) reason = "taskWatchdog"; - - if (hwReason == TG1WDT_SYS_RESET) + else if (hwReason == TG1WDT_SYS_RESET) reason = "intWatchdog"; - +#endif LOG_INFO("Booted, wake cause %d (boot count %d), reset_reason=%s", wakeCause, bootCount, reason); #endif @@ -397,8 +411,10 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r // NOTE! ESP docs say we must disable bluetooth and wifi before light sleep +#if SOC_PM_SUPPORT_RTC_PERIPH_PD // We want RTC peripherals to stay on esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); +#endif #if defined(BUTTON_PIN) && defined(BUTTON_NEED_PULLUP) gpio_pullup_en((gpio_num_t)BUTTON_PIN); @@ -532,11 +548,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r */ void enableModemSleep() { -#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) static esp_pm_config_t esp32_config; // filled with zeros because bss -#else - static esp_pm_config_esp32_t esp32_config; // filled with zeros because bss -#endif #if CONFIG_IDF_TARGET_ESP32S3 esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32S2 @@ -545,6 +557,12 @@ void enableModemSleep() esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32C3 esp32_config.max_freq_mhz = CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ; +#elif CONFIG_IDF_TARGET_ESP32P4 +#if CONFIG_ESP32P4_REV_MIN_FULL < 300 + esp32_config.max_freq_mhz = 360; +#else + esp32_config.max_freq_mhz = 400; +#endif #else esp32_config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ; #endif @@ -562,8 +580,8 @@ bool shouldLoraWake(uint32_t msecToWake) void enableLoraInterrupt() { - esp_err_t res; #if SOC_PM_SUPPORT_EXT_WAKEUP && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + esp_err_t res; res = gpio_pulldown_en((gpio_num_t)LORA_DIO1); if (res != ESP_OK) { LOG_ERROR("gpio_pulldown_en(LORA_DIO1) result %d", res); diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index e91e4dcef..9241ed6f2 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -70,7 +70,7 @@ // Battery #define BATTERY_PIN 34 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO34_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_ATTENUATION \ ADC_ATTEN_DB_2_5 // 2_5-> 100mv-1250mv, 11-> 150mv-3100mv for ESP32 // ESP32-S2/C3/S3 are different diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h index 1f84fffa1..6435e29b2 100644 --- a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h @@ -30,7 +30,7 @@ // Battery sense #define BATTERY_PIN 35 #define ADC_MULTIPLIER 2.01 // 100k + 100k, and add 1% tolerance -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION // SPI diff --git a/variants/esp32/diy/dr-dev/variant.h b/variants/esp32/diy/dr-dev/variant.h index 35b18ee74..e47b4c41c 100644 --- a/variants/esp32/diy/dr-dev/variant.h +++ b/variants/esp32/diy/dr-dev/variant.h @@ -3,7 +3,7 @@ #define I2C_SDA 4 #define I2C_SCL 5 #define BATTERY_PIN 34 -#define ADC_CHANNEL ADC1_GPIO34_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 // GPS #undef GPS_RX_PIN diff --git a/variants/esp32/diy/hydra/variant.h b/variants/esp32/diy/hydra/variant.h index 68194f869..917962b5f 100644 --- a/variants/esp32/diy/hydra/variant.h +++ b/variants/esp32/diy/hydra/variant.h @@ -11,7 +11,7 @@ // Note: On the ESP32 base version, gpio34-39 are input-only, and do not have internal pull-ups. // If 39 is not being used for a button, it is suggested to remove the #define. #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k) #define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards #define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). diff --git a/variants/esp32/diy/v1/variant.h b/variants/esp32/diy/v1/variant.h index 862969af0..98a9eb9c3 100644 --- a/variants/esp32/diy/v1/variant.h +++ b/variants/esp32/diy/v1/variant.h @@ -11,7 +11,7 @@ #define BUTTON_PIN 39 // The middle button GPIO on the T-Beam #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k) #define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards #define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini index 56e6e8340..f2fbb4c61 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -4,8 +4,9 @@ extends = arduino_base custom_esp32_kind = custom_mtjson_part = platform = - # renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32 - platformio/espressif32@6.13.0 + # TODO renovate + https://github.com/pioarduino/platform-espressif32/releases/download/55.03.38-1/platform-espressif32.zip + ; https://github.com/pioarduino/platform-espressif32.git#develop platform_packages = # renovate: datasource=custom.pio depName=platformio/tool-mklittlefs packageName=platformio/tool/tool-mklittlefs platformio/tool-mklittlefs@1.203.210628 @@ -35,20 +36,18 @@ build_unflags = -std=gnu++11 build_flags = ${arduino_base.build_flags} - -flto -Wall -Wextra -Isrc/platform/esp32 -std=gnu++17 -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG - -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + # DO NOT INCREASE THIS TO DEBUG. It appears to trigger a bug in ESP-IDF + # Bluetooth stack with Pixel 8 Android devices: + # https://github.com/espressif/esp-idf/issues/18126#issuecomment-4286197744 + # Once the bug is resolved, we can remove this warning. + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL -DAXP_DEBUG_PORT=Serial - -DCONFIG_BT_NIMBLE_ENABLED - -DCONFIG_BT_NIMBLE_MAX_BONDS=6 # default is 3 - -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 - -DCONFIG_BT_NIMBLE_MAX_CCCDS=20 - -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192 -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING -DSERIAL_BUFFER_SIZE=4096 -DSERIAL_HAS_ON_RECEIVE @@ -62,16 +61,13 @@ build_flags = lib_deps = ${arduino_base.lib_deps} ${networking_base.lib_deps} - ${networking_extra.lib_deps} ${environmental_base.lib_deps} ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master https://github.com/meshtastic/esp32_https_server/archive/0c71f380390ad483ff134ad938e07f6cf1226c5b.zip - # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino - h2zero/NimBLE-Arduino@1.4.3 - # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master - https://github.com/dbinfrago/libpax/archive/df424747f9acb86ab07c5a206ded1e8e3650726a.zip + # TODO renovate + https://github.com/mverch67/libpax/archive/6f52ee989301cdabaeef00bcbf93bff55708ce2f.zip # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib lewisxhe/XPowersLib@0.3.3 # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto @@ -79,7 +75,30 @@ lib_deps = lib_ignore = segger_rtt - ESP32 BLE Arduino + ; ESP32 BLE Arduino + ; BLE + ; Ignore select Bluetooth libs + NimBLE-Arduino + BluetoothSerial + SimpleBLE + WiFiProv + ArduinoOTA + ; Ignore pioarduino esp32 libs we don't use + ESP_I2S + ESP_NOW + ESP_SR + Insights + Matter + OpenThread + RainMaker + ;SPIFFS + USB + ;NetworkClientSecure + Zigbee + Micro-RTSP + ; testing below + mqtt + esp-mqtt ; leave this commented out to avoid breaking Windows ;upload_port = /dev/ttyUSB0 @@ -92,3 +111,226 @@ lib_ignore = ; customize the partition table ; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables board_build.partitions = partition-table.csv + +custom_component_remove = + espressif/esp_hosted + espressif/esp_wifi_remote + espressif/esp_modem + espressif/esp-dsp + espressif/esp32-camera + espressif/libsodium + espressif/esp-modbus + espressif/qrcode + espressif/esp_insights + espressif/esp_diag_data_store + espressif/esp_diagnostics + espressif/esp_rainmaker + espressif/rmaker_common + espressif/network_provisioning + chmorgan/esp-libhelix-mp3 + espressif/esp-tflite-micro + espressif/esp-sr + espressif/esp_matter + espressif/esp-zboss-lib + espressif/esp-zigbee-lib + espressif/mqtt + +custom_sdkconfig = + ; CONFIG_LOG_DEFAULT_LEVEL=4 + ; CONFIG_LOG_MAXIMUM_LEVEL=4 + '# CONFIG_BT_NIMBLE_LOG_LEVEL_INFO is not set' + CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR=y + CONFIG_LOG_COLORS=y + CONFIG_ARDUHAL_LOG_COLORS=y + CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y + CONFIG_IDF_EXPERIMENTAL_FEATURES=y + ; + ; Compiler options + ; + CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=n + CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y + CONFIG_COMPILER_CXX_EXCEPTIONS=n + CONFIG_COMPILER_STACK_CHECK_MODE_NORM=n + CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y + CONFIG_COMPILER_DISABLE_GCC12_WARNINGS=y + CONFIG_COMPILER_DISABLE_GCC13_WARNINGS=y + CONFIG_COMPILER_DISABLE_GCC14_WARNINGS=y + CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=n + CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE=y + CONFIG_ESP_GDBSTUB_ENABLED=n + CONFIG_ESP_TASK_WDT_INIT=n + CONFIG_NEWLIB_NANO_FORMAT=y + ; LIBC_NEWLIB_NANO_FORMAT is enabled via 'esp32_extra.py' to allow float support + CONFIG_ESP_COREDUMP_ENABLE=n + CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n + CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y + CONFIG_ESP32_ENABLE_COREDUMP=n + CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=n + CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=n + CONFIG_FREERTOS_USE_TRACE_FACILITY=n + CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y + CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y + CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y + CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM=y + CONFIG_ESP_WIFI_IRAM_OPT=n + CONFIG_ESP32_WIFI_RX_IRAM_OPT=n + CONFIG_SPIRAM_CACHE_LIBCHAR_IN_IRAM=n + CONFIG_SPIRAM_CACHE_LIBSTR_IN_IRAM=n + CONFIG_SPIRAM_CACHE_LIBMISC_IN_IRAM=n + CONFIG_SPIRAM_CACHE_LIBTIME_IN_IRAM=n + CONFIG_UNITY_ENABLE_FLOAT=n + CONFIG_UNITY_ENABLE_DOUBLE=n + CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n + CONFIG_SUPPORT_TERMIOS=n + CONFIG_VFS_SUPPORT_TERMIOS=n + CONFIG_VFS_SUPPORT_SELECT=n + CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=n + CONFIG_WS_TRANSPORT=n + CONFIG_PPP_SUPPORT=n + CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI=n + ; CONFIG_LWIP_DHCPS=n + CONFIG_LWIP_PPP_SUPPORT=n + CONFIG_LWIP_IP_FORWARD=n + CONFIG_LWIP_IPV4_NAPT=n + ; + ; MBEDTLS + ; + CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=8192 + CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n + CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y + ; Switch to custom CA bundle (for Meshtastic MQTT/etc) in the future + ; https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/kconfig-reference.html#certificate-bundle-configuration + ; CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y + ; CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="" + CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=1 + ; #shame + CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION=y + CONFIG_MBEDTLS_SSL_RENEGOTIATION=n + CONFIG_MBEDTLS_SSL_PROTO_DTLS=n + CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=n + CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=n + CONFIG_MBEDTLS_PKCS7_C=n + CONFIG_MBEDTLS_CAMELLIA_C=n + CONFIG_MBEDTLS_CCM_C=n + CONFIG_MBEDTLS_CMAC_C=n + CONFIG_MBEDTLS_SHA512_C=n + CONFIG_MBEDTLS_X509_CRL_PARSE_C=n + CONFIG_MBEDTLS_X509_CSR_PARSE_C=n + CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_PSK=n + CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=n + CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=n + CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=n + CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=n + CONFIG_MDNS_ENABLE_CONSOLE_CLI=n + CONFIG_MDNS_PREDEF_NETIF_AP=n + ; CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n + CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n + CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n + CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=n + CONFIG_ESP_WIFI_ENABLE_SAE_H2E=n + CONFIG_OPENTHREAD_ENABLED=n + CONFIG_ZB_ENABLED=n + CONFIG_IEEE802154_ENABLED=n + CONFIG_ESP_INSIGHTS_ENABLED=n + CONFIG_HTTPD_WS_SUPPORT=n + CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=n + CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=n + ; using esp-idf5-https_server library instead, for now? + CONFIG_ESP_HTTPS_SERVER_ENABLE=n + ; + ; Builtin ESP-MQTT, we don't use this (yet?) + ; + CONFIG_MQTT_TRANSPORT_SSL=n + CONFIG_MQTT_TRANSPORT_WEBSOCKET=n + CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=n + CONFIG_MQTT_PROTOCOL_311=n + ; + ; Ethernet + ; Disabled in esp32_common, re-enable on variants that need it + ; + CONFIG_ETH_ENABLED=n + CONFIG_ARDUINO_SELECTIVE_Ethernet=n + ; + ; Bluetooth + ; + CONFIG_BLE_MESH=n + CONFIG_BT_ENABLED=y + CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y + CONFIG_BT_BLUEDROID_ENABLED=n + CONFIG_BT_NIMBLE_ENABLED=y + CONFIG_BT_NIMBLE_NVS_PERSIST=y + CONFIG_BT_NIMBLE_ROLE_CENTRAL=n + CONFIG_BT_NIMBLE_ROLE_OBSERVER=n + CONFIG_BT_CONTROLLER_ENABLED=y + CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192 + CONFIG_BT_NIMBLE_MAX_CCCDS=20 + CONFIG_BT_NIMBLE_MAX_BONDS=6 + CONFIG_BT_NIMBLE_ENABLE_PERIODIC_SYNC=n + CONFIG_BT_NIMBLE_ENABLE_PERIODIC_ADV=n + CONFIG_BT_NIMBLE_EXT_SCAN=n + CONFIG_BT_NIMBLE_EXT_ADV=n + CONFIG_BT_NIMBLE_PERIODIC_ADV_SYNC_TRANSFER=n + CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_2M_PHY=n + CONFIG_BT_NIMBLE_BLUFI_ENABLE=n + CONFIG_BT_NIMBLE_PROX_SERVICE=n + CONFIG_BT_NIMBLE_ANS_SERVICE=n + CONFIG_BT_NIMBLE_CTS_SERVICE=n + CONFIG_BT_NIMBLE_HTP_SERVICE=n + CONFIG_BT_NIMBLE_IPSS_SERVICE=n + CONFIG_BT_NIMBLE_TPS_SERVICE=n + CONFIG_BT_NIMBLE_IAS_SERVICE=n + CONFIG_BT_NIMBLE_LLS_SERVICE=n + CONFIG_BT_NIMBLE_SPS_SERVICE=y + CONFIG_BT_NIMBLE_HR_SERVICE=n + CONFIG_BT_NIMBLE_HID_SERVICE=n + CONFIG_BT_NIMBLE_BAS_SERVICE=n + CONFIG_BT_NIMBLE_DIS_SERVICE=n + ; + ; Arduino selective compilation + ; Disable unused Arduino libraries to save space + ; + CONFIG_ARDUINO_SELECTIVE_COMPILATION=y + CONFIG_ARDUINO_SELECTIVE_SPI=y + CONFIG_ARDUINO_SELECTIVE_Wire=y + CONFIG_ARDUINO_SELECTIVE_ESP_SR=n + CONFIG_ARDUINO_SELECTIVE_EEPROM=y + CONFIG_ARDUINO_SELECTIVE_Preferences=y + CONFIG_ARDUINO_SELECTIVE_Ticker=y + CONFIG_ARDUINO_SELECTIVE_Update=y + CONFIG_ARDUINO_SELECTIVE_Zigbee=n + CONFIG_ARDUINO_SELECTIVE_FS=y + CONFIG_ARDUINO_SELECTIVE_SD=y + CONFIG_ARDUINO_SELECTIVE_SD_MMC=y + CONFIG_ARDUINO_SELECTIVE_SPIFFS=y + CONFIG_ARDUINO_SELECTIVE_FFat=n + CONFIG_ARDUINO_SELECTIVE_LittleFS=y + CONFIG_ARDUINO_SELECTIVE_Network=y + ; CONFIG_ARDUINO_SELECTIVE_Ethernet=n + CONFIG_ARDUINO_SELECTIVE_PPP=n + CONFIG_ARDUINO_SELECTIVE_Hash=y + CONFIG_ARDUINO_SELECTIVE_ArduinoOTA=n + CONFIG_ARDUINO_SELECTIVE_AsyncUDP=y + CONFIG_ARDUINO_SELECTIVE_DNSServer=n + CONFIG_ARDUINO_SELECTIVE_ESPmDNS=y + CONFIG_ARDUINO_SELECTIVE_HTTPClient=n + CONFIG_ARDUINO_SELECTIVE_Matter=n + CONFIG_ARDUINO_SELECTIVE_NetBIOS=n + CONFIG_ARDUINO_SELECTIVE_WebServer=n + CONFIG_ARDUINO_SELECTIVE_WiFi=y + CONFIG_ARDUINO_SELECTIVE_NetworkClientSecure=y + CONFIG_ARDUINO_SELECTIVE_WiFiProv=n + CONFIG_ARDUINO_SELECTIVE_BLE=y + CONFIG_ARDUINO_SELECTIVE_BluetoothSerial=n + CONFIG_ARDUINO_SELECTIVE_SimpleBLE=n + CONFIG_ARDUINO_SELECTIVE_RainMaker=n + CONFIG_ARDUINO_SELECTIVE_OpenThread=n + CONFIG_ARDUINO_SELECTIVE_Insights=n diff --git a/variants/esp32/esp32.ini b/variants/esp32/esp32.ini index f40f1d064..50b475a6c 100644 --- a/variants/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -11,12 +11,39 @@ build_src_filter = build_flags = ${esp32_common.build_flags} + -mtext-section-literals + -D ESP32_FORCE_IRAM_MEMSET + -Wl,--wrap=memset + -Wl,--wrap=memcpy -DMESHTASTIC_EXCLUDE_AUDIO=1 -DMESHTASTIC_EXCLUDE_ACCELEROMETER=1 -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DMESHTASTIC_EXCLUDE_WEBSERVER=1 -DMESHTASTIC_EXCLUDE_RANGETEST=1 + ; HACK HACK HACK this is just a proof of concept ESP32+NimBLE but these + ; includes need to be done properly somehow + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/host/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/porting/nimble/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/port/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/porting/npl/freertos/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/transport/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/host/services/gap/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/esp-hci/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/host/services/gatt/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/host/util/include +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} + CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y + '# CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY is not set' + '# CONFIG_BTDM_CTRL_MODE_BTDM is not set' + '# CONFIG_BT_BLUEDROID_ENABLED is not set' + CONFIG_BT_NIMBLE_ENABLED=y + CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y + +; Override lib_deps to use environmental_extra_no_bsec instead of environmental_extra +; BSEC library uses ~3.5KB DRAM which causes overflow on original ESP32 targets lib_deps = ${arduino_base.lib_deps} ${networking_base.lib_deps} @@ -24,8 +51,8 @@ lib_deps = ${radiolib_base.lib_deps} ${environmental_base.lib_deps} ${environmental_extra_no_bsec.lib_deps} - # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino - h2zero/NimBLE-Arduino@1.4.3 + # TODO renovate + https://github.com/mverch67/libpax/archive/6f52ee989301cdabaeef00bcbf93bff55708ce2f.zip # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib lewisxhe/XPowersLib@0.3.3 # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto diff --git a/variants/esp32/heltec_wsl_v2.1/variant.h b/variants/esp32/heltec_wsl_v2.1/variant.h index db374afb6..c0afeac82 100644 --- a/variants/esp32/heltec_wsl_v2.1/variant.h +++ b/variants/esp32/heltec_wsl_v2.1/variant.h @@ -12,7 +12,7 @@ #define ADC_CTRL 21 #define ADC_CTRL_ENABLED LOW #define BATTERY_PIN 37 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_CHANNEL_1 +#define ADC_CHANNEL ADC_CHANNEL_1 // ratio of voltage divider = 3.20 (R1=100k, R2=220k) #define ADC_MULTIPLIER 3.2 diff --git a/variants/esp32/m5stack_coreink/variant.h b/variants/esp32/m5stack_coreink/variant.h index 84a1e1966..ede008bab 100644 --- a/variants/esp32/m5stack_coreink/variant.h +++ b/variants/esp32/m5stack_coreink/variant.h @@ -92,7 +92,7 @@ #define PIN_EINK_MOSI 23 // EPD_MOSI #define BATTERY_PIN 35 -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 // https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/schematic/Core/m5paper/M5_PAPER_SCH.pdf // https://github.com/m5stack/M5Core-Ink/blob/master/examples/Basics/FactoryTest/FactoryTest.ino#L58 // VBAT diff --git a/variants/esp32/nano-g1-explorer/variant.h b/variants/esp32/nano-g1-explorer/variant.h index f3640241a..48d064710 100644 --- a/variants/esp32/nano-g1-explorer/variant.h +++ b/variants/esp32/nano-g1-explorer/variant.h @@ -33,7 +33,7 @@ #endif #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. #define ADC_MULTIPLIER 2 diff --git a/variants/esp32/radiomaster_900_bandit/platformio.ini b/variants/esp32/radiomaster_900_bandit/platformio.ini index 6729235ed..fdc483fe3 100644 --- a/variants/esp32/radiomaster_900_bandit/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit/platformio.ini @@ -7,7 +7,7 @@ build_flags = -DVTABLES_IN_FLASH=1 -DCONFIG_DISABLE_HAL_LOCKS=1 -DHAS_STK8XXX=1 - -O2 + ; -O2 -I variants/esp32/radiomaster_900_bandit board_build.f_cpu = 240000000L upload_protocol = esptool diff --git a/variants/esp32/rak11200/variant.h b/variants/esp32/rak11200/variant.h index a38ac83b7..feb38a8e1 100644 --- a/variants/esp32/rak11200/variant.h +++ b/variants/esp32/rak11200/variant.h @@ -47,7 +47,7 @@ static const uint8_t SCK = 33; #define PIN_VBAT WB_A0 #define BATTERY_PIN PIN_VBAT -#define ADC_CHANNEL ADC1_GPIO36_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 // https://docs.rakwireless.com/Product-Categories/WisBlock/RAK13300/ diff --git a/variants/esp32/station-g1/variant.h b/variants/esp32/station-g1/variant.h index 6c3a39261..29f3cc1d8 100644 --- a/variants/esp32/station-g1/variant.h +++ b/variants/esp32/station-g1/variant.h @@ -32,7 +32,7 @@ #endif #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define BATTERY_SENSE_SAMPLES 30 // Set the number of samples, It has an effect of increasing sensitivity. #define ADC_MULTIPLIER 6.45 #define CELL_TYPE_LION // same curve for liion/lipo diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini index 96e9879ce..b1367df87 100644 --- a/variants/esp32/tbeam/platformio.ini +++ b/variants/esp32/tbeam/platformio.ini @@ -16,6 +16,7 @@ board_check = true build_flags = ${esp32_base.build_flags} -D TBEAM_V10 -I variants/esp32/tbeam + upload_speed = 921600 [env:tbeam-displayshield] diff --git a/variants/esp32/tbeam_v07/variant.h b/variants/esp32/tbeam_v07/variant.h index 898705ce2..1fdb295bf 100644 --- a/variants/esp32/tbeam_v07/variant.h +++ b/variants/esp32/tbeam_v07/variant.h @@ -6,7 +6,7 @@ #define BUTTON_PIN 39 #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define USE_RF95 #define LORA_DIO0 26 // a No connect on the SX1262 module diff --git a/variants/esp32/tlora_v1_3/variant.h b/variants/esp32/tlora_v1_3/variant.h index 2b0395d8a..ab783e57d 100644 --- a/variants/esp32/tlora_v1_3/variant.h +++ b/variants/esp32/tlora_v1_3/variant.h @@ -1,5 +1,5 @@ #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 diff --git a/variants/esp32/tlora_v2/variant.h b/variants/esp32/tlora_v2/variant.h index 099fdc2ee..cd714b8fe 100644 --- a/variants/esp32/tlora_v2/variant.h +++ b/variants/esp32/tlora_v2/variant.h @@ -1,5 +1,5 @@ #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 diff --git a/variants/esp32/tlora_v2_1_16/variant.h b/variants/esp32/tlora_v2_1_16/variant.h index 5488fddf4..27d2fb5ab 100644 --- a/variants/esp32/tlora_v2_1_16/variant.h +++ b/variants/esp32/tlora_v2_1_16/variant.h @@ -1,5 +1,5 @@ #define BATTERY_PIN 35 -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define BATTERY_SENSE_SAMPLES 30 // ratio of voltage divider = 2.0 (R42=100k, R43=100k) diff --git a/variants/esp32/tlora_v2_1_18/variant.h b/variants/esp32/tlora_v2_1_18/variant.h index 1ab08c364..75578e1ca 100644 --- a/variants/esp32/tlora_v2_1_18/variant.h +++ b/variants/esp32/tlora_v2_1_18/variant.h @@ -1,7 +1,7 @@ #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider = 2.0 (R42=100k, R43=100k) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 diff --git a/variants/esp32/trackerd/variant.h b/variants/esp32/trackerd/variant.h index 8071ba99d..8ba32c776 100644 --- a/variants/esp32/trackerd/variant.h +++ b/variants/esp32/trackerd/variant.h @@ -22,7 +22,7 @@ #undef BAT_MEASURE_ADC_UNIT #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_MULTIPLIER 1.34 // tracked resistance divider is 100k+470k, so it can not fillfull well on esp32 adc -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define ADC_ATTENUATION ADC_ATTEN_DB_12 // lower dB for high resistance voltage divider #undef GPS_RX_PIN diff --git a/variants/esp32c3/esp32c3.ini b/variants/esp32c3/esp32c3.ini index e5f117ad7..02042047f 100644 --- a/variants/esp32c3/esp32c3.ini +++ b/variants/esp32c3/esp32c3.ini @@ -5,6 +5,19 @@ custom_esp32_kind = esp32c3 monitor_speed = 115200 monitor_filters = esp32_c3_exception_decoder +build_flags = + ${esp32_common.build_flags} + ; Linker script to align text.handler_execute section to 4 bytes + -Wl,-Tsrc/platform/esp32/align-text.handler_execute-4.ld + +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} + ; ESP32c3 doesn't support SD_MMC + CONFIG_ARDUINO_SELECTIVE_SD_MMC=n + ; CONFIG_BT_NIMBLE_EXT_ADV=y + ; CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=257 + ; CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 + lib_deps = ${esp32_common.lib_deps} # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master diff --git a/variants/esp32c6/esp32c6.ini b/variants/esp32c6/esp32c6.ini index cdd9f9868..ae91e6e81 100644 --- a/variants/esp32c6/esp32c6.ini +++ b/variants/esp32c6/esp32c6.ini @@ -1,50 +1,34 @@ [esp32c6_base] extends = esp32_common -platform = - # Do not renovate until we have switched to pioarduino tagged builds - https://github.com/Jason2866/platform-espressif32/archive/22faa566df8c789000f8136cd8d0aca49617af55.zip -platform_packages = - # HACK: This release was automatically removed upstream - framework-arduinoespressif32 @ https://github.com/vidplace7/platform-espressif32/releases/download/meshtastic-esp32c6/framework-arduinoespressif32-all-release_v5.1-124d64e.zip -build_flags = - ${arduino_base.build_flags} - -Wall - -Wextra - -Isrc/platform/esp32 - -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING - -DSERIAL_BUFFER_SIZE=4096 - -DLIBPAX_ARDUINO - -DLIBPAX_WIFI - -DLIBPAX_BLE - -DMESHTASTIC_EXCLUDE_WEBSERVER - ;-DDEBUG_HEAP - ; TEMP - -DHAS_BLUETOOTH=0 - -DMESHTASTIC_EXCLUDE_PAXCOUNTER - -DMESHTASTIC_EXCLUDE_BLUETOOTH +custom_esp32_kind = esp32c6 -lib_deps = - ${arduino_base.lib_deps} - ${networking_base.lib_deps} - ${environmental_base.lib_deps} - ${environmental_extra.lib_deps} - ${radiolib_base.lib_deps} - # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib - lewisxhe/XPowersLib@0.3.3 - # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master - https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip - # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto - rweather/Crypto@0.4.0 +build_flags = + ${esp32_common.build_flags} + ; Linker script to align text.handler_execute section to 4 bytes + -Wl,-Tsrc/platform/esp32/align-text.handler_execute-4.ld + ; Exclude Paxcounter, it uses 'esp_vhci_host_send_packet' whch is not available on ESP32-C6 + ; https://github.com/espressif/arduino-esp32/issues/11716 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER build_src_filter = - ${esp32_common.build_src_filter} - + ${esp32_common.build_src_filter} monitor_speed = 460800 monitor_filters = esp32_c3_exception_decoder +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} + ; ESP32c6 doesn't support SD_MMC + CONFIG_ARDUINO_SELECTIVE_SD_MMC=n + ; CONFIG_BT_NIMBLE_EXT_ADV=y + ; CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=257 + ; CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 + lib_ignore = ${esp32_common.lib_ignore} - NonBlockingRTTTL - NimBLE-Arduino libpax - + +lib_deps = + ${esp32_common.lib_deps} + # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master + https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index bf605ca61..59eca377b 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -17,28 +17,12 @@ board_build.partitions = default_16MB.csv ;Normal method upload_protocol = esptool ;upload_port = /dev/ttyACM2 -build_unflags = - -D HAS_BLUETOOTH - -D MESHTASTIC_EXCLUDE_BLUETOOTH - -D HAS_WIFI -lib_deps = - ${esp32c6_base.lib_deps} - # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino - h2zero/NimBLE-Arduino@2.3.7 build_flags = ${esp32c6_base.build_flags} -D M5STACK_UNITC6L -I variants/esp32c6/m5stack_unitc6l - -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 - -D HAS_BLUETOOTH=1 - -DCONFIG_BT_NIMBLE_EXT_ADV=1 - -DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 - -D NIMBLE_TWO monitor_speed=115200 -lib_ignore = - NonBlockingRTTTL - libpax build_src_filter = ${esp32c6_base.build_src_filter} +<../variants/esp32c6/m5stack_unitc6l> diff --git a/variants/esp32c6/tlora_c6/platformio.ini b/variants/esp32c6/tlora_c6/platformio.ini index 6b402d7c5..07585e79f 100644 --- a/variants/esp32c6/tlora_c6/platformio.ini +++ b/variants/esp32c6/tlora_c6/platformio.ini @@ -8,3 +8,11 @@ build_flags = -I variants/esp32c6/tlora_c6 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 + ; Disable screen + audio. + ; ESP32-C6 with 4MB flash is TIGHT on space. + -DMESHTASTIC_EXCLUDE_SCREEN=1 + -DMESHTASTIC_EXCLUDE_AUDIO=1 + +lib_ignore = + ${esp32c6_base.lib_ignore} + ESP32 Codec2 diff --git a/variants/esp32p4/esp32p4.ini b/variants/esp32p4/esp32p4.ini new file mode 100644 index 000000000..44094ee04 --- /dev/null +++ b/variants/esp32p4/esp32p4.ini @@ -0,0 +1,110 @@ +[esp32p4_base] +extends = esp32_common +custom_esp32_kind = esp32p4 +board_build.mcu = esp32p4 + +build_unflags= + -DHAS_UDP_MULTICAST=1 + +build_flags = + ${esp32_common.build_flags} + -DMESHTASTIC_EXCLUDE_WIFI=1 ; TODO + -DMESHTASTIC_EXCLUDE_MQTT=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + +build_src_filter = + ${esp32_common.build_src_filter} - - - - - - - + +extra_scripts = + ${esp32_common.extra_scripts} + extra_scripts/ld_response_file.py + +; Override esp32_common component pruning: keep esp_hosted + esp_wifi_remote for P4 hosted BT +custom_component_remove = + espressif/esp_modem + espressif/esp-dsp + espressif/esp32-camera + espressif/libsodium + espressif/esp-modbus + espressif/qrcode + espressif/esp_insights + espressif/esp_diag_data_store + espressif/esp_diagnostics + espressif/esp_rainmaker + espressif/rmaker_common + espressif/network_provisioning + chmorgan/esp-libhelix-mp3 + espressif/esp-tflite-micro + espressif/esp-sr + espressif/esp_matter + espressif/esp-zboss-lib + espressif/esp-zigbee-lib + espressif/mqtt + +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} + CONFIG_ARDUINO_SELECTIVE_SD_MMC=y + CONFIG_BT_CONTROLLER_DISABLED=y + CONFIG_ESP_WIFI_REMOTE_ENABLED=y + # esp_hosted core + CONFIG_ESP_HOSTED_ENABLED=y + # Board: custom (not Espressif EV board) + CONFIG_ESP_HOSTED_P4_DEV_BOARD_NONE=y + # Delay after C6 reset to allow boot (e.g. old v2.3.0 may be slow) + CONFIG_ESP_HOSTED_SDIO_RESET_DELAY_MS=1500 + CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_STREAMING_MODE=y + CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y + CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET="esp32c6" + CONFIG_ESP_HOSTED_CP_TARGET_ESP32C6=y + CONFIG_ESP_HOSTED_CP_TARGET_ESP32H2=n + CONFIG_ESP_HOSTED_PRIV_SDIO_OPTION=y + CONFIG_ESP_HOSTED_SPI_HOST_INTERFACE=n + CONFIG_ESP_HOSTED_TRANSPORT_SDIO=y + CONFIG_ESP_HOSTED_ENABLE_NIMBLE=y + CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y + CONFIG_ESP_HOSTED_NIMBLE_HCI_VHCI=y + CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER=y + CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS=3 + # OTA method: LittleFS + CONFIG_OTA_METHOD_LITTLEFS=y + + # Skip version check — we force OTA regardless + # CONFIG_OTA_VERSION_CHECK_HOST_SLAVE is not set + # CONFIG_OTA_VERSION_CHECK_SLAVEFW_SLAVE is not set + + # RX streaming mode + CONFIG_ESP_HOSTED_SLAVE_RESET_ON_EVERY_HOST_BOOTUP=y + ;CONFIG_ESP_HOSTED_SLAVE_RESET_ONLY_IF_NECESSARY=y + # SOC_LCD (MUI / lovyanGFX) + CONFIG_SOC_LCD_I80_SUPPORTED=y + CONFIG_SOC_LCD_RGB_SUPPORTED=y + CONFIG_SOC_MIPI_DSI_SUPPORTED=y + # stack dump + CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y ; remove for production version + ;CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y; for production version + ;CONFIG_ESP_SYSTEM_PANIC_GDBSTUB=y ; for target debugging + # Logger: verbose for experiment + CONFIG_LOG_DEFAULT_LEVEL_INFO=y + CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y + + +lib_ignore = + ${esp32_common.lib_ignore} + libpax + esp8266-oled-ssd1306 + bsec2 + esp32_idf5_https_server + esp_driver_cam + esp_http_server + +; Override lib_deps to exclude environmental_extras +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${networking_extra.lib_deps} + ${environmental_base.lib_deps} + ${radiolib_base.lib_deps} + # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto + rweather/Crypto@0.4.0 + # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master + https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip diff --git a/variants/esp32s2/esp32s2.ini b/variants/esp32s2/esp32s2.ini index 836e31d8d..189f51898 100644 --- a/variants/esp32s2/esp32s2.ini +++ b/variants/esp32s2/esp32s2.ini @@ -12,13 +12,18 @@ build_flags = -DHAS_BLUETOOTH=0 -DMESHTASTIC_EXCLUDE_PAXCOUNTER -DMESHTASTIC_EXCLUDE_BLUETOOTH + -mtext-section-literals -lib_deps = - ${esp32_common.lib_deps} - # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master - https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} lib_ignore = ${esp32_common.lib_ignore} NimBLE-Arduino libpax + +lib_deps = + ${esp32_common.lib_deps} + # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master + https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip + \ No newline at end of file diff --git a/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h b/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h index 5decc7eb2..628ebcf8b 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h +++ b/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h @@ -7,7 +7,7 @@ #define BUTTON_PIN 0 // BOOT button #define BATTERY_PIN 1 -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_MULTIPLIER 103.0 // Calibrated value #define ADC_ATTENUATION ADC_ATTEN_DB_0 #define ADC_CTRL 37 diff --git a/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h b/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h index 85321cbe0..0991afd53 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h +++ b/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h @@ -18,7 +18,7 @@ // Battery voltage monitoring - TODO: test, currently untested, copied from T3S3 variant #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_MULTIPLIER \ 2.11 // ratio of voltage divider = 2.0 (R10=1M, R13=1M), plus some undervoltage correction - TODO: this was carried over from // the T3S3, test to see if the undervoltage correction is needed. diff --git a/variants/esp32s3/ELECROW-ThinkNode-G3/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-G3/variant.cpp deleted file mode 100644 index c6ff6b8d8..000000000 --- a/variants/esp32s3/ELECROW-ThinkNode-G3/variant.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "mesh/NodeDB.h" - -void variantDefaultConfig() -{ - config.network.eth_enabled = true; -} diff --git a/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h b/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h index c5afd574a..316763499 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h @@ -34,3 +34,5 @@ #define ETH_INT_PIN 10 #define ETH_RST_PIN 9 // #define ETH_ADDR 1 + +#define USE_ETHERNET_DEFAULT 1 \ No newline at end of file diff --git a/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h index c8e56426f..668208586 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h @@ -40,8 +40,8 @@ // Battery // #define BATTERY_PIN 2 #define BATTERY_PIN 17 -// #define ADC_CHANNEL ADC1_GPIO2_CHANNEL -#define ADC_CHANNEL ADC2_GPIO17_CHANNEL +// #define ADC_CHANNEL ADC_CHANNEL_1 +#define ADC_CHANNEL ADC_CHANNEL_6 #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 #undef AREF_VOLTAGE diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h index 2d02c7f27..55bcd0dc7 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -16,7 +16,7 @@ // USB_CHECK #define EXT_PWR_DETECT 12 #define BATTERY_PIN 8 -#define ADC_CHANNEL ADC1_GPIO8_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define ADC_MULTIPLIER 2.0 // 2.0 + 10% for correction of display undervoltage. diff --git a/variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp deleted file mode 100644 index c6ff6b8d8..000000000 --- a/variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "mesh/NodeDB.h" - -void variantDefaultConfig() -{ - config.network.eth_enabled = true; -} diff --git a/variants/esp32s3/ELECROW-ThinkNode-M7/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M7/variant.h index 9724b20fa..da73bcd1e 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M7/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M7/variant.h @@ -41,3 +41,5 @@ #define ETH_CS_PIN 21 #define ETH_INT_PIN 45 // #define ETH_ADDR 1 + +#define USE_ETHERNET_DEFAULT 1 \ No newline at end of file diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h b/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h index c9200b96b..f8a0e28c8 100644 --- a/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h +++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h @@ -17,7 +17,7 @@ // #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to // measure battery voltage ratio of voltage divider = 2.0 (assumption) // #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -// #define ADC_CHANNEL ADC1_GPIO1_CHANNEL +// #define ADC_CHANNEL ADC_CHANNEL_0 #define I2C_SDA SDA // 21 #define I2C_SCL SCL // 15 diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini index 71116279c..f43f104d7 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini @@ -26,3 +26,4 @@ build_flags = -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DARDUINO_USB_MODE=0 + -DARDUINO_USB_CDC_ON_BOOT=1 diff --git a/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini index 60b030d42..b90325f51 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini @@ -8,13 +8,9 @@ board_build.f_cpu = 240000000L upload_protocol = esptool ;upload_port = /dev/ttyACM0 upload_speed = 921600 -build_unflags = - ${esp32s3_base.build_unflags} - -DARDUINO_USB_MODE=1 build_flags = ${esp32s3_base.build_flags} -D PRIVATE_HW -I variants/esp32s3/diy/my_esp32s3_diy_oled -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue - -DARDUINO_USB_MODE=0 diff --git a/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini b/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini index 681ee6c45..9d977e45d 100644 --- a/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini +++ b/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini @@ -6,13 +6,10 @@ board_build.partitions = default_16MB.csv board_level = extra board_upload.flash_size = 16MB ;Specify the FLASH capacity as 16MB board_build.arduino.memory_type = qio_opi ;Enable internal PSRAM -build_unflags = - ${esp32s3_base.build_unflags} - -D ARDUINO_USB_MODE=1 build_flags = ${esp32s3_base.build_flags} -D EBYTE_ESP32_S3 -D BOARD_HAS_PSRAM - -D ARDUINO_USB_MODE=0 + -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1 -I variants/esp32s3/diy/t-energy-s3_e22 diff --git a/variants/esp32s3/diy/t-energy-s3_e22/variant.h b/variants/esp32s3/diy/t-energy-s3_e22/variant.h index 6933d7715..10a0fc20d 100644 --- a/variants/esp32s3/diy/t-energy-s3_e22/variant.h +++ b/variants/esp32s3/diy/t-energy-s3_e22/variant.h @@ -4,7 +4,7 @@ // Battery #define BATTERY_PIN 3 #define ADC_MULTIPLIER 2.0 -#define ADC_CHANNEL ADC1_GPIO3_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_2 // Button on NanoVHF PCB #define BUTTON_PIN 39 diff --git a/variants/esp32s3/elecrow_panel/pins_arduino.h b/variants/esp32s3/elecrow_panel/pins_arduino.h index 81c9e0a2c..75b5d561a 100644 --- a/variants/esp32s3/elecrow_panel/pins_arduino.h +++ b/variants/esp32s3/elecrow_panel/pins_arduino.h @@ -15,9 +15,9 @@ static const uint8_t MOSI = 48; static const uint8_t MISO = 47; static const uint8_t SCK = 41; -static const uint8_t SPI_MOSI = 6; -static const uint8_t SPI_SCK = 5; -static const uint8_t SPI_MISO = 4; +// static const uint8_t SPI_MOSI = 6; +// static const uint8_t SPI_SCK = 5; +// static const uint8_t SPI_MISO = 4; static const uint8_t A0 = 1; static const uint8_t A1 = 2; diff --git a/variants/esp32s3/elecrow_panel/platformio.ini b/variants/esp32s3/elecrow_panel/platformio.ini index 1b91a02bb..a707a832e 100644 --- a/variants/esp32s3/elecrow_panel/platformio.ini +++ b/variants/esp32s3/elecrow_panel/platformio.ini @@ -38,6 +38,9 @@ build_flags = ${esp32s3_base.build_flags} -Os -D USE_PACKET_API -D HAS_SDCARD -D SD_SPI_FREQUENCY=75000000 + -D SPI_MISO=4 + -D SPI_MOSI=6 + -D SPI_SCK=5 lib_deps = ${esp32s3_base.lib_deps} ${device-ui_base.lib_deps} @@ -47,7 +50,9 @@ lib_deps = ${esp32s3_base.lib_deps} earlephilhower/ESP8266SAM@1.1.0 # renovate: datasource=custom.pio depName=TCA9534 packageName=hideakitai/library/TCA9534 hideakitai/TCA9534@0.1.1 - lovyan03/LovyanGFX@1.2.0 ; note: v1.2.7 breaks the elecrow 7" display functionality + # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX + lovyan03/LovyanGFX@1.2.19 + ; REVISIT note: v1.2.7 breaks the elecrow 7" display functionality [crowpanel_small_esp32s3_base] ; 2.4, 2.8, 3.5 inch extends = crowpanel_base diff --git a/variants/esp32s3/esp32-s3-pico/variant.h b/variants/esp32s3/esp32-s3-pico/variant.h index 65732171a..1629ebebe 100644 --- a/variants/esp32s3/esp32-s3-pico/variant.h +++ b/variants/esp32s3/esp32-s3-pico/variant.h @@ -21,8 +21,8 @@ // https://www.waveshare.com/img/devkit/ESP32-S3-Pico/ESP32-S3-Pico-details-inter-1.jpg // digram is incorrect labeled as battery pin is getting readings on GPIO7_CH1? #define BATTERY_PIN 7 -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL -// #define ADC_CHANNEL ADC1_GPIO6_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 +// #define ADC_CHANNEL ADC_CHANNEL_5 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) #define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic diff --git a/variants/esp32s3/esp32s3.ini b/variants/esp32s3/esp32s3.ini index 299415442..17b451d42 100644 --- a/variants/esp32s3/esp32s3.ini +++ b/variants/esp32s3/esp32s3.ini @@ -4,6 +4,16 @@ custom_esp32_kind = esp32s3 monitor_speed = 115200 +build_flags = + ${esp32_common.build_flags} + -mtext-section-literals + +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} + ; CONFIG_BT_NIMBLE_EXT_ADV=y + ; CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=257 + ; CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 + lib_deps = ${esp32_common.lib_deps} # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master diff --git a/variants/esp32s3/hackaday-communicator/platformio.ini b/variants/esp32s3/hackaday-communicator/platformio.ini index 8fd275c0e..cee393e48 100644 --- a/variants/esp32s3/hackaday-communicator/platformio.ini +++ b/variants/esp32s3/hackaday-communicator/platformio.ini @@ -16,5 +16,5 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/hackaday-communicator lib_deps = ${esp32s3_base.lib_deps} - # renovate: datasource=git-refs depName=meshtastic-Arduino_GFX packageName=https://github.com/meshtastic/Arduino_GFX gitBranch=master - https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip \ No newline at end of file + # renovate: datasource=git-refs depName=moononournation-Arduino_GFX packageName=https://github.com/moononournation/Arduino_GFX gitBranch=master + https://github.com/moononournation/Arduino_GFX/archive/refs/tags/v1.6.5.zip \ No newline at end of file diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/variant.h b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h index f689b20a8..71c5f0743 100644 --- a/variants/esp32s3/heltec_capsule_sensor_v3/variant.h +++ b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h @@ -8,7 +8,7 @@ #define BUTTON_ACTIVE_PULLUP false #define BATTERY_PIN 7 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER (4.9 * 1.045) #define ADC_CTRL 36 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/heltec_sensor_hub/variant.h b/variants/esp32s3/heltec_sensor_hub/variant.h index 64255c038..64450e0ea 100644 --- a/variants/esp32s3/heltec_sensor_hub/variant.h +++ b/variants/esp32s3/heltec_sensor_hub/variant.h @@ -6,7 +6,7 @@ #define BUTTON_ACTIVE_PULLUP false #define BATTERY_PIN 7 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER (4.9 * 1.045) #define ADC_CTRL 34 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/heltec_v3/variant.h b/variants/esp32s3/heltec_v3/variant.h index d2d904d9c..e4a2004e5 100644 --- a/variants/esp32s3/heltec_v3/variant.h +++ b/variants/esp32s3/heltec_v3/variant.h @@ -16,7 +16,7 @@ #define ADC_CTRL 37 #define ADC_CTRL_ENABLED LOW #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h index 72f55d09f..5a806653e 100644 --- a/variants/esp32s3/heltec_v4/variant.h +++ b/variants/esp32s3/heltec_v4/variant.h @@ -4,7 +4,7 @@ #define ADC_CTRL 37 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 diff --git a/variants/esp32s3/heltec_v4_r8/variant.h b/variants/esp32s3/heltec_v4_r8/variant.h index 1f638f24c..d59f0ae2c 100644 --- a/variants/esp32s3/heltec_v4_r8/variant.h +++ b/variants/esp32s3/heltec_v4_r8/variant.h @@ -3,7 +3,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.035 diff --git a/variants/esp32s3/heltec_vision_master_e213/platformio.ini b/variants/esp32s3/heltec_vision_master_e213/platformio.ini index bd1b73d2b..fb97fd983 100644 --- a/variants/esp32s3/heltec_vision_master_e213/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_e213/platformio.ini @@ -16,6 +16,7 @@ board = heltec_vision_master_e213 board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} + -DBOARD_HAS_PSRAM -Ivariants/esp32s3/heltec_vision_master_e213 -DHELTEC_VISION_MASTER_E213 -DUSE_EINK @@ -44,6 +45,7 @@ build_src_filter = build_flags = ${esp32s3_base.build_flags} ${inkhud.build_flags} + -D BOARD_HAS_PSRAM -I variants/esp32s3/heltec_vision_master_e213 -D HELTEC_VISION_MASTER_E213 lib_deps = diff --git a/variants/esp32s3/heltec_vision_master_e213/variant.h b/variants/esp32s3/heltec_vision_master_e213/variant.h index c9aaa2ee8..931a2d7bb 100644 --- a/variants/esp32s3/heltec_vision_master_e213/variant.h +++ b/variants/esp32s3/heltec_vision_master_e213/variant.h @@ -28,7 +28,7 @@ #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 7 -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_MULTIPLIER 4.9 * 1.03 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 diff --git a/variants/esp32s3/heltec_vision_master_e290/variant.h b/variants/esp32s3/heltec_vision_master_e290/variant.h index b32715e39..daeac0a51 100644 --- a/variants/esp32s3/heltec_vision_master_e290/variant.h +++ b/variants/esp32s3/heltec_vision_master_e290/variant.h @@ -27,7 +27,7 @@ #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 7 -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_MULTIPLIER 4.9 * 1.03 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 diff --git a/variants/esp32s3/heltec_vision_master_t190/variant.h b/variants/esp32s3/heltec_vision_master_t190/variant.h index a6a809207..037460c2f 100644 --- a/variants/esp32s3/heltec_vision_master_t190/variant.h +++ b/variants/esp32s3/heltec_vision_master_t190/variant.h @@ -45,7 +45,7 @@ #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 6 -#define ADC_CHANNEL ADC1_GPIO6_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_5 #define ADC_MULTIPLIER 4.9 * 1.03 // Voltage divider is roughly 1:1 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high diff --git a/variants/esp32s3/heltec_wireless_paper/variant.h b/variants/esp32s3/heltec_wireless_paper/variant.h index 7f57bb67f..c18fa40c1 100644 --- a/variants/esp32s3/heltec_wireless_paper/variant.h +++ b/variants/esp32s3/heltec_wireless_paper/variant.h @@ -24,7 +24,7 @@ #define VEXT_ON_VALUE LOW #define ADC_CTRL 19 #define BATTERY_PIN 20 -#define ADC_CHANNEL ADC2_GPIO20_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_9 #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 #define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high diff --git a/variants/esp32s3/heltec_wireless_paper_v1/variant.h b/variants/esp32s3/heltec_wireless_paper_v1/variant.h index 59dd485f6..62615fa46 100644 --- a/variants/esp32s3/heltec_wireless_paper_v1/variant.h +++ b/variants/esp32s3/heltec_wireless_paper_v1/variant.h @@ -25,7 +25,7 @@ #define VEXT_ON_VALUE LOW #define ADC_CTRL 19 #define BATTERY_PIN 20 -#define ADC_CHANNEL ADC2_GPIO20_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_9 #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 #define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high diff --git a/variants/esp32s3/heltec_wireless_tracker/platformio.ini b/variants/esp32s3/heltec_wireless_tracker/platformio.ini index 1450bb45c..3f11bf7e2 100644 --- a/variants/esp32s3/heltec_wireless_tracker/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker/platformio.ini @@ -15,10 +15,12 @@ board = heltec_wireless_tracker board_build.partitions = default_8MB.csv upload_protocol = esptool -build_flags = +build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_wireless_tracker -D HELTEC_TRACKER_V1_1 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output lib_deps = diff --git a/variants/esp32s3/heltec_wireless_tracker/variant.h b/variants/esp32s3/heltec_wireless_tracker/variant.h index b40e40011..e2049bcf6 100644 --- a/variants/esp32s3/heltec_wireless_tracker/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker/variant.h @@ -39,7 +39,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h index e7d3f93c1..16afbee81 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h @@ -35,7 +35,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h index 7c797a503..a3d52fd3c 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h @@ -34,7 +34,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. diff --git a/variants/esp32s3/heltec_wsl_v3/variant.h b/variants/esp32s3/heltec_wsl_v3/variant.h index c81f45d3b..26276d227 100644 --- a/variants/esp32s3/heltec_wsl_v3/variant.h +++ b/variants/esp32s3/heltec_wsl_v3/variant.h @@ -10,7 +10,7 @@ #define ADC_CTRL 37 #define ADC_CTRL_ENABLED LOW #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 diff --git a/variants/esp32s3/icarus/platformio.ini b/variants/esp32s3/icarus/platformio.ini index 0aaff52f5..de29cc2cf 100644 --- a/variants/esp32s3/icarus/platformio.ini +++ b/variants/esp32s3/icarus/platformio.ini @@ -7,12 +7,10 @@ board_build.mcu = esp32s3 board_build.partitions = default_8MB.csv upload_protocol = esptool upload_speed = 921600 -build_unflags = - ${esp32s3_base.build_unflags} - -DARDUINO_USB_MODE=1 build_flags = ${esp32s3_base.build_flags} -D PRIVATE_HW -I variants/esp32s3/icarus -DBOARD_HAS_PSRAM + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=0 diff --git a/variants/esp32s3/m5stack_cardputer_adv/variant.h b/variants/esp32s3/m5stack_cardputer_adv/variant.h index 48437cd13..7b25a0193 100644 --- a/variants/esp32s3/m5stack_cardputer_adv/variant.h +++ b/variants/esp32s3/m5stack_cardputer_adv/variant.h @@ -84,7 +84,7 @@ #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) #define BATTERY_PIN 10 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO10_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_9 #define ADC_MULTIPLIER 2 * 1.02 // 100k + 100k, and add 2% to kick the voltage over the max voltage to show charging. // BMI270 6-axis IMU on internal I2C bus diff --git a/variants/esp32s3/mesh-tab/variant.h b/variants/esp32s3/mesh-tab/variant.h index 30042b90f..5b450aa6c 100644 --- a/variants/esp32s3/mesh-tab/variant.h +++ b/variants/esp32s3/mesh-tab/variant.h @@ -10,7 +10,7 @@ #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider (100k, 220k) #define ADC_MULTIPLIER 1.6 // 1.45 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_3 // LED #define LED_POWER 21 diff --git a/variants/esp32s3/mini-epaper-s3/variant.h b/variants/esp32s3/mini-epaper-s3/variant.h index 0b640f9cf..1ac0a1e74 100644 --- a/variants/esp32s3/mini-epaper-s3/variant.h +++ b/variants/esp32s3/mini-epaper-s3/variant.h @@ -18,7 +18,7 @@ #define BATTERY_PIN 2 // A battery voltage measurement pin, voltage divider connected here to // measure battery voltage ratio of voltage divider = 2.0 (assumption) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO2_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_1 // Display (E-Ink) #define PIN_EINK_EN 42 diff --git a/variants/esp32s3/picomputer-s3/variant.h b/variants/esp32s3/picomputer-s3/variant.h index 60afac002..10adff3b6 100644 --- a/variants/esp32s3/picomputer-s3/variant.h +++ b/variants/esp32s3/picomputer-s3/variant.h @@ -11,7 +11,7 @@ // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider = 3.0 (R11=200k, R7=100k) #define ADC_MULTIPLIER 3.1 // 3.0 with correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO2_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_1 #define USE_RF95 // RFM95/SX127x diff --git a/variants/esp32s3/rak3312/platformio.ini b/variants/esp32s3/rak3312/platformio.ini index 87e5b63ff..dcdb2974c 100644 --- a/variants/esp32s3/rak3312/platformio.ini +++ b/variants/esp32s3/rak3312/platformio.ini @@ -29,7 +29,7 @@ board_level = extra upload_protocol = esptool build_flags = - ${esp32_base.build_flags} + ${esp32s3_base.build_flags} -D RAK3312 -D _VARIANT_RAK3112_ -I variants/esp32s3/rak3312 diff --git a/variants/esp32s3/rak3312/variant.h b/variants/esp32s3/rak3312/variant.h index ee0fff524..381506866 100644 --- a/variants/esp32s3/rak3312/variant.h +++ b/variants/esp32s3/rak3312/variant.h @@ -30,7 +30,7 @@ #define LED_STATE_ON 1 // State when LED is litted #define BATTERY_PIN 1 -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #ifdef _VARIANT_RAK3112_ // Modular variant (stamp) #define ADC_MULTIPLIER 2.11 @@ -55,4 +55,4 @@ #define GPS_TX_PIN 43 #define GPS_RX_PIN 44 -#endif \ No newline at end of file +#endif diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h index 90cb12053..f8672edac 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -51,7 +51,7 @@ #define USE_VIRTUAL_KEYBOARD 1 #define BATTERY_PIN 1 -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_MULTIPLIER 1.667 #define PIN_BUZZER 38 diff --git a/variants/esp32s3/seeed-sensecap-indicator/platformio.ini b/variants/esp32s3/seeed-sensecap-indicator/platformio.ini index bb52f801b..9b01033df 100644 --- a/variants/esp32s3/seeed-sensecap-indicator/platformio.ini +++ b/variants/esp32s3/seeed-sensecap-indicator/platformio.ini @@ -13,7 +13,8 @@ custom_meshtastic_has_mui = true extends = esp32s3_base platform_packages = - platformio/framework-arduinoespressif32 @ https://github.com/mverch67/arduino-esp32/archive/aef7fef6de3329ed6f75512d46d63bba12b09bb5.zip ; add_tca9535 (based on 2.0.16) + ; Version needs to match the pioarduino version used in esp32_common.ini + platformio/framework-arduinoespressif32 @ https://github.com/mverch67/arduino-esp32#82aee17619ea2940de03b0db4d082d5def657771 board = seeed-sensecap-indicator board_check = true @@ -23,7 +24,7 @@ upload_protocol = esptool build_flags = ${esp32s3_base.build_flags} -Ivariants/esp32s3/seeed-sensecap-indicator -DSENSECAP_INDICATOR - -DCONFIG_ARDUHAL_LOG_COLORS + -DUSE_ARDUINO_HAL_GPIO ; patch for lovyanGFX to use digitalWrite instead of direct register access for GPIO control, which is necessary for the IO expander to work correctly -DRADIOLIB_DEBUG_SPI=0 -DRADIOLIB_DEBUG_PROTOCOL=0 -DRADIOLIB_DEBUG_BASIC=0 @@ -32,19 +33,48 @@ build_flags = ${esp32s3_base.build_flags} -DIO_EXPANDER=0x40 -DIO_EXPANDER_IRQ=42 ;-DIO_EXPANDER_DEBUG - -DUSE_ARDUINO_HAL_GPIO lib_deps = ${esp32s3_base.lib_deps} - ; TODO switch back to official LovyanGFX - https://github.com/mverch67/LovyanGFX/archive/4c76238c1344162a234ae917b27651af146d6fb2.zip + https://github.com/mverch67/LovyanGFX/archive/9abe502add013f392a1898d7dc48d65ddc112754.zip # renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM earlephilhower/ESP8266SAM@1.1.0 +custom_sdkconfig = + ${esp32s3_base.custom_sdkconfig} + CONFIG_AUTOSTART_ARDUINO=y + CONFIG_LOG_DEFAULT_LEVEL_INFO=y + CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y + CONFIG_SPIRAM_MODE_OCT=y +; CONFIG_SPIRAM_SPEED_120M=y +; CONFIG_SPIRAM_SPEED=120 + CONFIG_SPIRAM_SPEED_80M=y + CONFIG_SPIRAM_SPEED=80 + CONFIG_SPIRAM_XIP_FROM_PSRAM=y + CONFIG_LCD_RGB_ISR_IRAM_SAFE=y + CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y + CONFIG_I2S_ISR_IRAM_SAFE=y + CONFIG_GDMA_ISR_IRAM_SAFE=y + CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y + CONFIG_ESP32S3_DATA_CACHE_64KB=y + CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK=y + CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=0 + CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y + CONFIG_ESPTOOLPY_FLASHSIZE="8MB" +; CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +; CONFIG_ESPTOOLPY_FLASHFREQ="120m" + '# CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set' + CONFIG_ESPTOOLPY_FLASHFREQ_80M=y + CONFIG_ESPTOOLPY_FLASHFREQ="80m" + CONFIG_COMPILER_OPTIMIZATION_PERF=y + CONFIG_BOOTLOADER_LOG_LEVEL_NONE=n + CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y + [env:seeed-sensecap-indicator-tft] extends = env:seeed-sensecap-indicator -board_level = pr +board_check = true upload_speed = 460800 build_flags = @@ -54,10 +84,10 @@ build_flags = -D HAS_SCREEN=1 -D HAS_TFT=1 -D DISPLAY_SET_RESOLUTION - -D RAM_SIZE=4096 - -D LV_LVGL_H_INCLUDE_SIMPLE - -D LV_CONF_INCLUDE_SIMPLE - -D LV_COMP_CONF_INCLUDE_SIMPLE + -D RAM_SIZE=3072 + -D LV_LVGL_H_INCLUDE_SIMPLE + -D LV_CONF_INCLUDE_SIMPLE + -D LV_COMP_CONF_INCLUDE_SIMPLE -D LV_USE_SYSMON=0 -D LV_USE_PROFILER=0 -D LV_USE_PERF_MONITOR=0 @@ -77,5 +107,6 @@ build_flags = lib_deps = ${env:seeed-sensecap-indicator.lib_deps} ${device-ui_base.lib_deps} - ; TODO switch back to official bb_captouch + ;# renovate: datasource=github-tags depName=bb_captouch packageName=bitbank2/bb_captouch + ;https://github.com/bitbank2/bb_captouch/archive/refs/tags/1.3.1.zip ; not working https://github.com/mverch67/bb_captouch/archive/8626412fe650d808a267791c0eae6e5860c85a5d.zip ; alternative touch library supporting FT6x36 diff --git a/variants/esp32s3/seeed-sensecap-indicator/variant.h b/variants/esp32s3/seeed-sensecap-indicator/variant.h index 8fa9e2393..db0054348 100644 --- a/variants/esp32s3/seeed-sensecap-indicator/variant.h +++ b/variants/esp32s3/seeed-sensecap-indicator/variant.h @@ -4,7 +4,7 @@ // This board has a serial coprocessor for sensor readings #define SENSOR_RP2040_TXD 19 #define SENSOR_RP2040_RXD 20 -#define SENSOR_PORT_NUM 2 +#define SENSOR_PORT_NUM UART_NUM_2 #define SENSOR_BAUD_RATE 115200 #define BUTTON_PIN 38 @@ -14,7 +14,7 @@ // #define BUTTON_NEED_PULLUP // #define BATTERY_PIN 27 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -// #define ADC_CHANNEL ADC1_GPIO27_CHANNEL +// #define ADC_CHANNEL ADC2_GPIO27_CHANNEL // #define ADC_MULTIPLIER 2 // ST7701 TFT LCD diff --git a/variants/esp32s3/seeed_xiao_s3/platformio.ini b/variants/esp32s3/seeed_xiao_s3/platformio.ini index b0e66241b..ee457eaf0 100644 --- a/variants/esp32s3/seeed_xiao_s3/platformio.ini +++ b/variants/esp32s3/seeed_xiao_s3/platformio.ini @@ -17,12 +17,8 @@ board_check = true board_build.partitions = default_8MB.csv upload_protocol = esptool upload_speed = 921600 -build_unflags = - ${esp32s3_base.build_unflags} - -DARDUINO_USB_MODE=1 build_flags = ${esp32s3_base.build_flags} -D SEEED_XIAO_S3 -I variants/esp32s3/seeed_xiao_s3 -DBOARD_HAS_PSRAM - -DARDUINO_USB_MODE=0 diff --git a/variants/esp32s3/seeed_xiao_s3/variant.h b/variants/esp32s3/seeed_xiao_s3/variant.h index cbdbf8eb8..7eee372fa 100644 --- a/variants/esp32s3/seeed_xiao_s3/variant.h +++ b/variants/esp32s3/seeed_xiao_s3/variant.h @@ -37,7 +37,7 @@ L76K GPS Module Information : https://www.seeedstudio.com/L76K-GNSS-Module-for-S #define BUTTON_NEED_PULLUP #define BATTERY_PIN -1 -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define BATTERY_SENSE_RESOLUTION_BITS 12 /*Warning: diff --git a/variants/esp32s3/station-g2/variant.h b/variants/esp32s3/station-g2/variant.h index a343823c7..9e844588f 100755 --- a/variants/esp32s3/station-g2/variant.h +++ b/variants/esp32s3/station-g2/variant.h @@ -20,7 +20,7 @@ Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2 /* #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_3 #define ADC_MULTIPLIER 4 #define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. #define BAT_FULLVOLT 8400 diff --git a/variants/esp32s3/t-beam-1w/variant.h b/variants/esp32s3/t-beam-1w/variant.h index 52e99320e..673b7c4c0 100644 --- a/variants/esp32s3/t-beam-1w/variant.h +++ b/variants/esp32s3/t-beam-1w/variant.h @@ -72,7 +72,7 @@ // Battery ADC #define BATTERY_PIN 4 -#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_3 #define BATTERY_SENSE_SAMPLES 30 #define ADC_MULTIPLIER 2.9333 diff --git a/variants/esp32s3/t-deck/variant.h b/variants/esp32s3/t-deck/variant.h index eb1bbdfef..1e6d7b97d 100644 --- a/variants/esp32s3/t-deck/variant.h +++ b/variants/esp32s3/t-deck/variant.h @@ -53,7 +53,7 @@ #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider = 2.0 (RD2=100k, RD3=100k) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_3 // keyboard #define I2C_SDA 18 // I2C pins for this board diff --git a/variants/esp32s3/t-eth-elite/platformio.ini b/variants/esp32s3/t-eth-elite/platformio.ini index 5ed67e977..5b1f01db7 100644 --- a/variants/esp32s3/t-eth-elite/platformio.ini +++ b/variants/esp32s3/t-eth-elite/platformio.ini @@ -10,10 +10,15 @@ build_flags = -D HAS_UDP_MULTICAST=1 -I variants/esp32s3/t-eth-elite -lib_ignore = - Ethernet +custom_sdkconfig = + ${esp32s3_base.custom_sdkconfig} + CONFIG_ETH_ENABLED=y + CONFIG_ARDUINO_SELECTIVE_Ethernet=y -lib_deps = - ${esp32s3_base.lib_deps} - # renovate: datasource=github-tags depName=ETHClass2 packageName=meshtastic/ETHClass2 - https://github.com/meshtastic/ETHClass2/archive/v1.0.0.zip +; lib_ignore = +; Ethernet + +; lib_deps = +; ${esp32s3_base.lib_deps} +; # renovate: datasource=github-tags depName=ETHClass2 packageName=meshtastic/ETHClass2 +; https://github.com/meshtastic/ETHClass2/archive/v1.0.0.zip diff --git a/variants/esp32s3/t5s3_epaper/variant.h b/variants/esp32s3/t5s3_epaper/variant.h index 49579a39c..fdf8589da 100644 --- a/variants/esp32s3/t5s3_epaper/variant.h +++ b/variants/esp32s3/t5s3_epaper/variant.h @@ -1,3 +1,4 @@ +#include "pins_arduino.h" // Display (E-Ink) ED047TC1 - 8bit parallel #define EPD_WIDTH 960 diff --git a/variants/esp32s3/tlora_t3s3_epaper/variant.h b/variants/esp32s3/tlora_t3s3_epaper/variant.h index 0f4875fc4..77dccb616 100644 --- a/variants/esp32s3/tlora_t3s3_epaper/variant.h +++ b/variants/esp32s3/tlora_t3s3_epaper/variant.h @@ -12,7 +12,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to // measure battery voltage ratio of voltage divider = 2.0 (assumption) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define I2C_SDA SDA #define I2C_SCL SCL diff --git a/variants/esp32s3/tlora_t3s3_v1/variant.h b/variants/esp32s3/tlora_t3s3_v1/variant.h index 02e2a0e42..7c6df9be9 100644 --- a/variants/esp32s3/tlora_t3s3_v1/variant.h +++ b/variants/esp32s3/tlora_t3s3_v1/variant.h @@ -6,7 +6,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider = 2.0 (R42=100k, R43=100k) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define I2C_SDA 18 // I2C pins for this board #define I2C_SCL 17 diff --git a/variants/esp32s3/tracksenger/internal/variant.h b/variants/esp32s3/tracksenger/internal/variant.h index b2822c24b..c015be280 100644 --- a/variants/esp32s3/tracksenger/internal/variant.h +++ b/variants/esp32s3/tracksenger/internal/variant.h @@ -35,7 +35,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/tracksenger/lcd/variant.h b/variants/esp32s3/tracksenger/lcd/variant.h index 6c32ff279..38df4d3ab 100644 --- a/variants/esp32s3/tracksenger/lcd/variant.h +++ b/variants/esp32s3/tracksenger/lcd/variant.h @@ -59,7 +59,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/tracksenger/oled/variant.h b/variants/esp32s3/tracksenger/oled/variant.h index 72762c7af..f85e845f3 100644 --- a/variants/esp32s3/tracksenger/oled/variant.h +++ b/variants/esp32s3/tracksenger/oled/variant.h @@ -36,7 +36,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index 56838b1fc..8c551cf41 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -21,7 +21,6 @@ build_flags = ${esp32s3_base.build_flags} -D UNPHONE -I variants/esp32s3/unphone - -D ARDUINO_USB_MODE=0 -D UNPHONE_ACCEL=0 -D UNPHONE_TOUCHS=0 -D UNPHONE_SDCARD=0 From 3261c04afbdcc7ccfde40591b514d8f438837cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 18 May 2026 11:18:40 +0200 Subject: [PATCH 09/21] Fix Antenna Switch on Cardputer (#10491) --- .../m5stack_cardputer_adv/variant.cpp | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp index 7ec9dca80..0c252159f 100644 --- a/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp +++ b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp @@ -3,14 +3,63 @@ #ifdef M5STACK_CARDPUTER_ADV #include "AudioBoard.h" +#include DriverPins PinsAudioBoardES8311; AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311); +// PI4IOE5V6408 on the optional Cap LoRa-1262 (and Cap LoRa868). +#define PI4IO_ADDR 0x43 +#define PI4IO_REG_IO_DIR 0x03 +#define PI4IO_REG_OUT_SET 0x05 +#define PI4IO_REG_OUT_H_IM 0x07 + +static TwoWire *findLoraCapBus() +{ + TwoWire *candidates[] = {&Wire1, &Wire}; + for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) { + candidates[i]->beginTransmission(PI4IO_ADDR); + if (candidates[i]->endTransmission() == 0) { + return candidates[i]; + } + } + return nullptr; +} + +static bool pi4ioWrite(TwoWire *bus, uint8_t reg, uint8_t val) +{ + bus->beginTransmission(PI4IO_ADDR); + bus->write(reg); + bus->write(val); + uint8_t status = bus->endTransmission(); + if (status != 0) { + LOG_DEBUG("PI4IO write reg=0x%02x val=0x%02x failed, I2C status=%u", reg, val, status); + return false; + } + return true; +} + +static void initLoraCap() +{ + TwoWire *bus = findLoraCapBus(); + if (!bus) { + LOG_ERROR("Cap LoRa-1262 not found"); + return; + } + bool ok = pi4ioWrite(bus, PI4IO_REG_IO_DIR, 0b00000001); + ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_H_IM, 0b00000001); + ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_SET, 0b00000001); + if (!ok) { + LOG_ERROR("Antenna switch init failed"); + } +} + // M5stack Cardputer ADV specific init void lateInitVariant() { + initLoraCap(); + // AudioDriverLogger.begin(Serial, AudioDriverLogLevel::Debug); // I2C: function, scl, sda PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire); From 6199faacf19193694c090a5d904f838269a22397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 18 May 2026 23:24:32 +0200 Subject: [PATCH 10/21] cherry pick backport fix for cardputer --- .../m5stack_cardputer_adv/variant.cpp | 56 ++++++++++++++++++- .../m5stack_cardputer_adv/platformio.ini | 3 - .../esp32s3/m5stack_cardputer_adv/variant.h | 1 + 3 files changed, 56 insertions(+), 4 deletions(-) rename {variants/esp32s3 => src/platform/extra_variants}/m5stack_cardputer_adv/variant.cpp (50%) diff --git a/variants/esp32s3/m5stack_cardputer_adv/variant.cpp b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp similarity index 50% rename from variants/esp32s3/m5stack_cardputer_adv/variant.cpp rename to src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp index 2bbe8e2e3..0c252159f 100644 --- a/variants/esp32s3/m5stack_cardputer_adv/variant.cpp +++ b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp @@ -1,13 +1,65 @@ -#include "AudioBoard.h" #include "configuration.h" +#ifdef M5STACK_CARDPUTER_ADV + +#include "AudioBoard.h" +#include + DriverPins PinsAudioBoardES8311; AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311); +// PI4IOE5V6408 on the optional Cap LoRa-1262 (and Cap LoRa868). +#define PI4IO_ADDR 0x43 +#define PI4IO_REG_IO_DIR 0x03 +#define PI4IO_REG_OUT_SET 0x05 +#define PI4IO_REG_OUT_H_IM 0x07 + +static TwoWire *findLoraCapBus() +{ + TwoWire *candidates[] = {&Wire1, &Wire}; + for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) { + candidates[i]->beginTransmission(PI4IO_ADDR); + if (candidates[i]->endTransmission() == 0) { + return candidates[i]; + } + } + return nullptr; +} + +static bool pi4ioWrite(TwoWire *bus, uint8_t reg, uint8_t val) +{ + bus->beginTransmission(PI4IO_ADDR); + bus->write(reg); + bus->write(val); + uint8_t status = bus->endTransmission(); + if (status != 0) { + LOG_DEBUG("PI4IO write reg=0x%02x val=0x%02x failed, I2C status=%u", reg, val, status); + return false; + } + return true; +} + +static void initLoraCap() +{ + TwoWire *bus = findLoraCapBus(); + if (!bus) { + LOG_ERROR("Cap LoRa-1262 not found"); + return; + } + bool ok = pi4ioWrite(bus, PI4IO_REG_IO_DIR, 0b00000001); + ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_H_IM, 0b00000001); + ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_SET, 0b00000001); + if (!ok) { + LOG_ERROR("Antenna switch init failed"); + } +} + // M5stack Cardputer ADV specific init void lateInitVariant() { + initLoraCap(); + // AudioDriverLogger.begin(Serial, AudioDriverLogLevel::Debug); // I2C: function, scl, sda PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire); @@ -38,3 +90,5 @@ void lateInitVariant() es8311_write_reg(0x32, 0xBF); // DAC volume (0dB) es8311_write_reg(0x37, 0x08); // EQ bypass } + +#endif diff --git a/variants/esp32s3/m5stack_cardputer_adv/platformio.ini b/variants/esp32s3/m5stack_cardputer_adv/platformio.ini index 3b378ed94..69c4f52a5 100644 --- a/variants/esp32s3/m5stack_cardputer_adv/platformio.ini +++ b/variants/esp32s3/m5stack_cardputer_adv/platformio.ini @@ -10,9 +10,6 @@ build_flags = -D M5STACK_CARDPUTER_ADV -D ARDUINO_USB_CDC_ON_BOOT=1 -I variants/esp32s3/m5stack_cardputer_adv -build_src_filter = - ${esp32s3_base.build_src_filter} - +<../variants/esp32s3/m5stack_cardputer_adv> lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main diff --git a/variants/esp32s3/m5stack_cardputer_adv/variant.h b/variants/esp32s3/m5stack_cardputer_adv/variant.h index 5fdb1436e..48437cd13 100644 --- a/variants/esp32s3/m5stack_cardputer_adv/variant.h +++ b/variants/esp32s3/m5stack_cardputer_adv/variant.h @@ -9,6 +9,7 @@ #define ST7789_BUSY -1 // #define VTFT_CTRL 38 #define VTFT_LEDA 38 +#define TFT_BACKLIGHT_ON HIGH // #define ST7789_BL (32+6) #define ST7789_SPI_HOST SPI2_HOST // #define TFT_BL (32+6) From 0148a89ddb562d4497b345f95a5ef0c80f5b1f9e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 20:49:28 -0500 Subject: [PATCH 11/21] Upgrade trunk (#10493) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index e055a6d50..77006cf99 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -26,7 +26,7 @@ lint: - hadolint@2.14.0 - shfmt@3.6.0 - shellcheck@0.11.0 - - black@26.3.1 + - black@26.5.0 - git-diff-check - gitleaks@8.30.1 - clang-format@16.0.3 From af3739fd6356e57971152171e1bbb1d7919a8f55 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 07:22:15 +0200 Subject: [PATCH 12/21] Update protobufs (#10499) Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.cpp | 3 + src/mesh/generated/meshtastic/admin.pb.h | 67 +++++++++++++- src/mesh/generated/meshtastic/config.pb.h | 8 +- src/mesh/generated/meshtastic/mesh.pb.cpp | 5 + src/mesh/generated/meshtastic/mesh.pb.h | 91 ++++++++++++++++++- .../generated/meshtastic/module_config.pb.h | 2 +- 7 files changed, 171 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index b302d9233..59cb394dc 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit b302d923327402fbe49efcf15ff1b6ef2361b22b +Subproject commit 59cb394dcfc4432cb216358ca26e861c7d13f462 diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp index 3dcc241d9..945840c0f 100644 --- a/src/mesh/generated/meshtastic/admin.pb.cpp +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -15,6 +15,9 @@ PB_BIND(meshtastic_AdminMessage_InputEvent, meshtastic_AdminMessage_InputEvent, PB_BIND(meshtastic_AdminMessage_OTAEvent, meshtastic_AdminMessage_OTAEvent, AUTO) +PB_BIND(meshtastic_LockdownAuth, meshtastic_LockdownAuth, AUTO) + + PB_BIND(meshtastic_HamParameters, meshtastic_HamParameters, AUTO) diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 58e0356ca..e6f5110ad 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -130,6 +130,41 @@ typedef struct _meshtastic_AdminMessage_OTAEvent { meshtastic_AdminMessage_OTAEvent_ota_hash_t ota_hash; } meshtastic_AdminMessage_OTAEvent; +typedef PB_BYTES_ARRAY_T(32) meshtastic_LockdownAuth_passphrase_t; +/* Lockdown passphrase delivery payload. + + One message handles three operations distinguished by content: + - Provision (first-time): passphrase set, lock_now=false. Firmware + generates DEK, wraps with passphrase-derived KEK, persists. + - Unlock: passphrase set, lock_now=false. Firmware verifies + passphrase against stored DEK, unlocks storage, authorizes the + connection that delivered this packet. + - Lock now: lock_now=true, passphrase ignored. Firmware revokes + all client auth and reboots into the locked state. + + Firmware decides between provision and unlock based on its own state + (whether a DEK file already exists). Clients do not need to track + which case applies. */ +typedef struct _meshtastic_LockdownAuth { + /* Passphrase bytes (1-32). Empty when lock_now is true. + Capped to 32 to match the proto cap on related security fields. */ + meshtastic_LockdownAuth_passphrase_t passphrase; + /* Optional override of the boot-count token TTL granted on success. + 0 = use firmware default (TOKEN_DEFAULT_BOOTS). + On reboot the firmware decrements this; when it reaches 0 the + device boots fully locked and requires a fresh passphrase. */ + uint32_t boots_remaining; + /* Optional wall-clock expiry for the unlock token, as absolute + Unix-epoch seconds. 0 = no time limit (only the boot-count TTL + applies). On boot, if the device RTC is set and now > this value, + the token is treated as expired. */ + uint32_t valid_until_epoch; + /* If true, ignore passphrase fields, immediately revoke all + connection-level admin authorization, and reboot the device into + the locked state. Always honoured regardless of current lock state. */ + bool lock_now; +} meshtastic_LockdownAuth; + /* Parameters for setting up Meshtastic for ameteur radio usage */ typedef struct _meshtastic_HamParameters { /* Amateur radio call sign, eg. KD2ABC */ @@ -384,6 +419,15 @@ typedef struct _meshtastic_AdminMessage { meshtastic_AdminMessage_OTAEvent ota_request; /* Parameters and sensor configuration */ meshtastic_SensorConfig sensor_config; + /* Lockdown passphrase delivery / unlock / lock-now command for hardened + firmware builds (see MESHTASTIC_LOCKDOWN). Used to provision the + passphrase on first boot, unlock encrypted storage on subsequent + reboots, re-verify on already-unlocked devices to authorize a new + client connection, or immediately re-lock the device. + + Replaces the earlier scheme that repurposed SecurityConfig.private_key + to carry passphrase bytes; that hack is retired. */ + meshtastic_LockdownAuth lockdown_auth; }; /* The node generates this key and sends it with any get_x_response packets. The client MUST include the same key with any set_x commands. Key expires after 300 seconds. @@ -429,6 +473,7 @@ extern "C" { + #define meshtastic_KeyVerificationAdmin_message_type_ENUMTYPE meshtastic_KeyVerificationAdmin_MessageType @@ -441,6 +486,7 @@ extern "C" { #define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_default {_meshtastic_OTAMode_MIN, {0, {0}}} +#define meshtastic_LockdownAuth_init_default {{0, {0}}, 0, 0, 0} #define meshtastic_HamParameters_init_default {"", 0, 0, ""} #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} #define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0} @@ -453,6 +499,7 @@ extern "C" { #define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}} +#define meshtastic_LockdownAuth_init_zero {{0, {0}}, 0, 0, 0} #define meshtastic_HamParameters_init_zero {"", 0, 0, ""} #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} #define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0} @@ -470,6 +517,10 @@ extern "C" { #define meshtastic_AdminMessage_InputEvent_touch_y_tag 4 #define meshtastic_AdminMessage_OTAEvent_reboot_ota_mode_tag 1 #define meshtastic_AdminMessage_OTAEvent_ota_hash_tag 2 +#define meshtastic_LockdownAuth_passphrase_tag 1 +#define meshtastic_LockdownAuth_boots_remaining_tag 2 +#define meshtastic_LockdownAuth_valid_until_epoch_tag 3 +#define meshtastic_LockdownAuth_lock_now_tag 4 #define meshtastic_HamParameters_call_sign_tag 1 #define meshtastic_HamParameters_tx_power_tag 2 #define meshtastic_HamParameters_frequency_tag 3 @@ -560,6 +611,7 @@ extern "C" { #define meshtastic_AdminMessage_nodedb_reset_tag 100 #define meshtastic_AdminMessage_ota_request_tag 102 #define meshtastic_AdminMessage_sensor_config_tag 103 +#define meshtastic_AdminMessage_lockdown_auth_tag 104 #define meshtastic_AdminMessage_session_passkey_tag 101 /* Struct field encoding specification for nanopb */ @@ -621,7 +673,8 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \ X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config), 103) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config), 103) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,lockdown_auth,lockdown_auth), 104) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL #define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel @@ -644,6 +697,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config) #define meshtastic_AdminMessage_payload_variant_key_verification_MSGTYPE meshtastic_KeyVerificationAdmin #define meshtastic_AdminMessage_payload_variant_ota_request_MSGTYPE meshtastic_AdminMessage_OTAEvent #define meshtastic_AdminMessage_payload_variant_sensor_config_MSGTYPE meshtastic_SensorConfig +#define meshtastic_AdminMessage_payload_variant_lockdown_auth_MSGTYPE meshtastic_LockdownAuth #define meshtastic_AdminMessage_InputEvent_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, event_code, 1) \ @@ -659,6 +713,14 @@ X(a, STATIC, SINGULAR, BYTES, ota_hash, 2) #define meshtastic_AdminMessage_OTAEvent_CALLBACK NULL #define meshtastic_AdminMessage_OTAEvent_DEFAULT NULL +#define meshtastic_LockdownAuth_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BYTES, passphrase, 1) \ +X(a, STATIC, SINGULAR, UINT32, boots_remaining, 2) \ +X(a, STATIC, SINGULAR, UINT32, valid_until_epoch, 3) \ +X(a, STATIC, SINGULAR, BOOL, lock_now, 4) +#define meshtastic_LockdownAuth_CALLBACK NULL +#define meshtastic_LockdownAuth_DEFAULT NULL + #define meshtastic_HamParameters_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, call_sign, 1) \ X(a, STATIC, SINGULAR, INT32, tx_power, 2) \ @@ -737,6 +799,7 @@ X(a, STATIC, OPTIONAL, UINT32, set_accuracy, 1) extern const pb_msgdesc_t meshtastic_AdminMessage_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg; +extern const pb_msgdesc_t meshtastic_LockdownAuth_msg; extern const pb_msgdesc_t meshtastic_HamParameters_msg; extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg; extern const pb_msgdesc_t meshtastic_SharedContact_msg; @@ -751,6 +814,7 @@ extern const pb_msgdesc_t meshtastic_SHTXX_config_msg; #define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg #define meshtastic_AdminMessage_InputEvent_fields &meshtastic_AdminMessage_InputEvent_msg #define meshtastic_AdminMessage_OTAEvent_fields &meshtastic_AdminMessage_OTAEvent_msg +#define meshtastic_LockdownAuth_fields &meshtastic_LockdownAuth_msg #define meshtastic_HamParameters_fields &meshtastic_HamParameters_msg #define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg #define meshtastic_SharedContact_fields &meshtastic_SharedContact_msg @@ -768,6 +832,7 @@ extern const pb_msgdesc_t meshtastic_SHTXX_config_msg; #define meshtastic_AdminMessage_size 511 #define meshtastic_HamParameters_size 31 #define meshtastic_KeyVerificationAdmin_size 25 +#define meshtastic_LockdownAuth_size 48 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 #define meshtastic_SCD30_config_size 27 #define meshtastic_SCD4X_config_size 29 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index d614a6438..820bb2764 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -198,7 +198,9 @@ typedef enum _meshtastic_Config_DisplayConfig_OledType { /* Can not be auto detected but set by proto. Used for 128x64 screens */ meshtastic_Config_DisplayConfig_OledType_OLED_SH1107 = 3, /* Can not be auto detected but set by proto. Used for 128x128 screens */ - meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4 + meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4, + /* Can not be auto detected but set by proto. Used for 64x128 rotated screens */ + meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED = 5 } meshtastic_Config_DisplayConfig_OledType; typedef enum _meshtastic_Config_DisplayConfig_DisplayMode { @@ -720,8 +722,8 @@ extern "C" { #define _meshtastic_Config_DisplayConfig_DisplayUnits_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayUnits)(meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL+1)) #define _meshtastic_Config_DisplayConfig_OledType_MIN meshtastic_Config_DisplayConfig_OledType_OLED_AUTO -#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 -#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128+1)) +#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED +#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED+1)) #define _meshtastic_Config_DisplayConfig_DisplayMode_MIN meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT #define _meshtastic_Config_DisplayConfig_DisplayMode_MAX meshtastic_Config_DisplayConfig_DisplayMode_COLOR diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 3648d8850..a68ffabac 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -57,6 +57,9 @@ PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO) PB_BIND(meshtastic_FromRadio, meshtastic_FromRadio, 2) +PB_BIND(meshtastic_LockdownStatus, meshtastic_LockdownStatus, AUTO) + + PB_BIND(meshtastic_ClientNotification, meshtastic_ClientNotification, 2) @@ -134,6 +137,8 @@ PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AU + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 41ef2798c..cb5f19df5 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -321,6 +321,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_HELTEC_MESH_NODE_T1 = 133, /* B&Q Consulting Station G3: TBD */ meshtastic_HardwareModel_STATION_G3 = 134, + /* Lilygo T-Impulse-Plus */ + meshtastic_HardwareModel_T_IMPULSE_PLUS = 135, + /* Lilygo T-Echo Card */ + meshtastic_HardwareModel_T_ECHO_CARD = 136, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -638,6 +642,25 @@ typedef enum _meshtastic_LogRecord_Level { meshtastic_LogRecord_Level_TRACE = 5 } meshtastic_LogRecord_Level; +typedef enum _meshtastic_LockdownStatus_State { + /* Default; should not be sent. */ + meshtastic_LockdownStatus_State_STATE_UNSPECIFIED = 0, + /* No passphrase has ever been provisioned on this device. + Client should prompt the operator to set one. */ + meshtastic_LockdownStatus_State_NEEDS_PROVISION = 1, + /* Storage is locked or this client has not authenticated yet. + lock_reason carries a machine-readable detail string. + Client should present (or auto-replay) a passphrase via + AdminMessage.lockdown_auth. */ + meshtastic_LockdownStatus_State_LOCKED = 2, + /* Passphrase accepted; client is now authorized for this connection. + boots_remaining and valid_until_epoch describe the active session + token's TTL. */ + meshtastic_LockdownStatus_State_UNLOCKED = 3, + /* Passphrase rejected. backoff_seconds is non-zero when rate-limited. */ + meshtastic_LockdownStatus_State_UNLOCK_FAILED = 4 +} meshtastic_LockdownStatus_State; + /* Struct definitions */ /* A GPS Position */ typedef struct _meshtastic_Position { @@ -1148,6 +1171,38 @@ typedef struct _meshtastic_QueueStatus { uint32_t mesh_packet_id; } meshtastic_QueueStatus; +/* Lockdown state report from firmware to client (for hardened builds + with MESHTASTIC_LOCKDOWN). Sent immediately after config_complete_id + to inform a freshly-connected unauthorized client what it must do, + and again in response to each LockdownAuth admin command. */ +typedef struct _meshtastic_LockdownStatus { + /* Current lockdown state being reported. */ + meshtastic_LockdownStatus_State state; + /* For LOCKED: machine-readable reason. Known values: + "needs_auth" — storage already unlocked, client must auth + "token_missing" — no boot token on flash + "token_expired" — boot token wall-clock TTL elapsed + "token_boots_zero" — boot token boot-count TTL exhausted + "token_hmac_fail" — token tampered or wrong device + "token_dek_fail" — token DEK decrypt failed + "token_wrong_size" — token file corrupted + "token_bad_magic" — token file corrupted + "not_provisioned" — should generally use NEEDS_PROVISION state instead + Other values may be added; clients should treat unknown values as + "locked, ask for passphrase". */ + char lock_reason[32]; + /* For UNLOCKED: remaining boots on the issued session token. + Decrements by 1 on each subsequent boot. */ + uint32_t boots_remaining; + /* For UNLOCKED: wall-clock expiry of the issued session token, + absolute Unix-epoch seconds. 0 = no time limit. */ + uint32_t valid_until_epoch; + /* For UNLOCK_FAILED: seconds the client must wait before another + passphrase attempt will be accepted. 0 = wrong passphrase, no + backoff (immediate retry allowed but advisable to prompt user). */ + uint32_t backoff_seconds; +} meshtastic_LockdownStatus; + typedef struct _meshtastic_KeyVerificationNumberInform { uint64_t nonce; char remote_longname[40]; @@ -1321,6 +1376,12 @@ typedef struct _meshtastic_FromRadio { meshtastic_ClientNotification clientNotification; /* Persistent data for device-ui */ meshtastic_DeviceUIConfig deviceuiConfig; + /* Lockdown state notification for hardened firmware builds. + Sent post-config (so unauthorized clients learn they must + provision/unlock) and after each LockdownAuth admin command + to report success or failure. Replaces the earlier scheme of + encoding state as magic-string prefixes inside ClientNotification. */ + meshtastic_LockdownStatus lockdown_status; }; } meshtastic_FromRadio; @@ -1462,6 +1523,10 @@ extern "C" { #define _meshtastic_LogRecord_Level_MAX meshtastic_LogRecord_Level_CRITICAL #define _meshtastic_LogRecord_Level_ARRAYSIZE ((meshtastic_LogRecord_Level)(meshtastic_LogRecord_Level_CRITICAL+1)) +#define _meshtastic_LockdownStatus_State_MIN meshtastic_LockdownStatus_State_STATE_UNSPECIFIED +#define _meshtastic_LockdownStatus_State_MAX meshtastic_LockdownStatus_State_UNLOCK_FAILED +#define _meshtastic_LockdownStatus_State_ARRAYSIZE ((meshtastic_LockdownStatus_State)(meshtastic_LockdownStatus_State_UNLOCK_FAILED+1)) + #define meshtastic_Position_location_source_ENUMTYPE meshtastic_Position_LocSource #define meshtastic_Position_altitude_source_ENUMTYPE meshtastic_Position_AltSource @@ -1492,6 +1557,8 @@ extern "C" { +#define meshtastic_LockdownStatus_state_ENUMTYPE meshtastic_LockdownStatus_State + #define meshtastic_ClientNotification_level_ENUMTYPE meshtastic_LogRecord_Level @@ -1532,6 +1599,7 @@ extern "C" { #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} #define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}} +#define meshtastic_LockdownStatus_init_default {_meshtastic_LockdownStatus_State_MIN, "", 0, 0, 0} #define meshtastic_ClientNotification_init_default {false, 0, 0, _meshtastic_LogRecord_Level_MIN, "", 0, {meshtastic_KeyVerificationNumberInform_init_default}} #define meshtastic_KeyVerificationNumberInform_init_default {0, "", 0} #define meshtastic_KeyVerificationNumberRequest_init_default {0, ""} @@ -1566,6 +1634,7 @@ extern "C" { #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} #define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}} +#define meshtastic_LockdownStatus_init_zero {_meshtastic_LockdownStatus_State_MIN, "", 0, 0, 0} #define meshtastic_ClientNotification_init_zero {false, 0, 0, _meshtastic_LogRecord_Level_MIN, "", 0, {meshtastic_KeyVerificationNumberInform_init_zero}} #define meshtastic_KeyVerificationNumberInform_init_zero {0, "", 0} #define meshtastic_KeyVerificationNumberRequest_init_zero {0, ""} @@ -1718,6 +1787,11 @@ extern "C" { #define meshtastic_QueueStatus_free_tag 2 #define meshtastic_QueueStatus_maxlen_tag 3 #define meshtastic_QueueStatus_mesh_packet_id_tag 4 +#define meshtastic_LockdownStatus_state_tag 1 +#define meshtastic_LockdownStatus_lock_reason_tag 2 +#define meshtastic_LockdownStatus_boots_remaining_tag 3 +#define meshtastic_LockdownStatus_valid_until_epoch_tag 4 +#define meshtastic_LockdownStatus_backoff_seconds_tag 5 #define meshtastic_KeyVerificationNumberInform_nonce_tag 1 #define meshtastic_KeyVerificationNumberInform_remote_longname_tag 2 #define meshtastic_KeyVerificationNumberInform_security_number_tag 3 @@ -1777,6 +1851,7 @@ extern "C" { #define meshtastic_FromRadio_fileInfo_tag 15 #define meshtastic_FromRadio_clientNotification_tag 16 #define meshtastic_FromRadio_deviceuiConfig_tag 17 +#define meshtastic_FromRadio_lockdown_status_tag 18 #define meshtastic_Heartbeat_nonce_tag 1 #define meshtastic_ToRadio_packet_tag 1 #define meshtastic_ToRadio_want_config_id_tag 3 @@ -2017,7 +2092,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,metadata,metadata), 13) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,clientNotification,clientNotification), 16) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfig), 17) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfig), 17) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,lockdown_status,lockdown_status), 18) #define meshtastic_FromRadio_CALLBACK NULL #define meshtastic_FromRadio_DEFAULT NULL #define meshtastic_FromRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket @@ -2034,6 +2110,16 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfi #define meshtastic_FromRadio_payload_variant_fileInfo_MSGTYPE meshtastic_FileInfo #define meshtastic_FromRadio_payload_variant_clientNotification_MSGTYPE meshtastic_ClientNotification #define meshtastic_FromRadio_payload_variant_deviceuiConfig_MSGTYPE meshtastic_DeviceUIConfig +#define meshtastic_FromRadio_payload_variant_lockdown_status_MSGTYPE meshtastic_LockdownStatus + +#define meshtastic_LockdownStatus_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, state, 1) \ +X(a, STATIC, SINGULAR, STRING, lock_reason, 2) \ +X(a, STATIC, SINGULAR, UINT32, boots_remaining, 3) \ +X(a, STATIC, SINGULAR, UINT32, valid_until_epoch, 4) \ +X(a, STATIC, SINGULAR, UINT32, backoff_seconds, 5) +#define meshtastic_LockdownStatus_CALLBACK NULL +#define meshtastic_LockdownStatus_DEFAULT NULL #define meshtastic_ClientNotification_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, UINT32, reply_id, 1) \ @@ -2194,6 +2280,7 @@ extern const pb_msgdesc_t meshtastic_MyNodeInfo_msg; extern const pb_msgdesc_t meshtastic_LogRecord_msg; extern const pb_msgdesc_t meshtastic_QueueStatus_msg; extern const pb_msgdesc_t meshtastic_FromRadio_msg; +extern const pb_msgdesc_t meshtastic_LockdownStatus_msg; extern const pb_msgdesc_t meshtastic_ClientNotification_msg; extern const pb_msgdesc_t meshtastic_KeyVerificationNumberInform_msg; extern const pb_msgdesc_t meshtastic_KeyVerificationNumberRequest_msg; @@ -2230,6 +2317,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LogRecord_fields &meshtastic_LogRecord_msg #define meshtastic_QueueStatus_fields &meshtastic_QueueStatus_msg #define meshtastic_FromRadio_fields &meshtastic_FromRadio_msg +#define meshtastic_LockdownStatus_fields &meshtastic_LockdownStatus_msg #define meshtastic_ClientNotification_fields &meshtastic_ClientNotification_msg #define meshtastic_KeyVerificationNumberInform_fields &meshtastic_KeyVerificationNumberInform_msg #define meshtastic_KeyVerificationNumberRequest_fields &meshtastic_KeyVerificationNumberRequest_msg @@ -2265,6 +2353,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_KeyVerificationNumberInform_size 58 #define meshtastic_KeyVerificationNumberRequest_size 52 #define meshtastic_KeyVerification_size 79 +#define meshtastic_LockdownStatus_size 53 #define meshtastic_LogRecord_size 426 #define meshtastic_LowEntropyKey_size 0 #define meshtastic_MeshPacket_size 381 diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index b8cf60bf0..25937e972 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -144,7 +144,7 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig { (the default official mqtt.meshtastic.org server can handle encrypted packets) Decrypted packets may be useful for external systems that want to consume meshtastic packets */ bool encryption_enabled; - /* Whether to send / consume json packets on MQTT */ + /* Deprecated: JSON packet support on MQTT was removed, and this field is ignored. */ bool json_enabled; /* If true, we attempt to establish a secure connection using TLS */ bool tls_enabled; From 23ead5f2f13f1ad9855aba2c887adab654083e8a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 07:47:06 +0200 Subject: [PATCH 13/21] Update protobufs (#10500) Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 45 +---- src/mesh/generated/meshtastic/config.pb.h | 8 +- .../generated/meshtastic/deviceonly.pb.cpp | 12 -- src/mesh/generated/meshtastic/deviceonly.pb.h | 185 +++++------------- src/mesh/generated/meshtastic/mesh.pb.h | 43 ++-- .../generated/meshtastic/module_config.pb.h | 2 +- 7 files changed, 75 insertions(+), 222 deletions(-) diff --git a/protobufs b/protobufs index 7ffb4bb60..59cb394dc 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 7ffb4bb60ded743a1ce23fe2edd5ead32be52bbb +Subproject commit 59cb394dcfc4432cb216358ca26e861c7d13f462 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 82644bc2a..e6f5110ad 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -163,41 +163,6 @@ typedef struct _meshtastic_LockdownAuth { connection-level admin authorization, and reboot the device into the locked state. Always honoured regardless of current lock state. */ bool lock_now; - /* Optional per-boot uptime cap on the unlocked session, in seconds. - 0 = unlimited (token-only enforcement, suitable for unattended - tower / infrastructure nodes). - - When non-zero, the firmware arms an uptime timer at unlock. On - each expiry, while there is still boot-count budget, the firmware - decrements the on-flash boot count in place, revokes per- - connection admin auth (clients must re-authenticate to see - content), re-engages the screen lock, and re-arms the timer - without rebooting. Mesh routing keeps running across session - boundaries; only when the boot-count budget reaches zero does - the device hard-lock and reboot. - - Total exposure ceiling = ((resolved boot count) + 1) * max_session_seconds. - The +1 accounts for the initial passphrase-unlocked session - itself, since boots_remaining is the number of subsequent - session rolls (each consuming one boot from the rollback ledger). - The resolved boot count is the value the firmware writes into the - token at unlock time: the client-supplied boots_remaining when - non-zero, otherwise the firmware default (TOKEN_DEFAULT_BOOTS). - Note that boots_remaining == 0 in this message means "use firmware - default", NOT "zero boots" — a client computing the ceiling for - display should mirror that resolution rather than multiplying the - raw request value. - - The cap is persisted in the token, so it survives token-based - auto-unlock across reboots. Explicit operator Lock Now still - deletes the token and forces passphrase re-entry. - - Uses millis() (CPU uptime), not wall-clock time, so the cap is - immune to GPS spoofing, RTC backup-battery removal, and Faraday - cage isolation — none of those move the uptime counter. The only - way to reset the session clock is a reboot, which costs a boot - from the on-flash, HMAC-bound counter. */ - uint32_t max_session_seconds; } meshtastic_LockdownAuth; /* Parameters for setting up Meshtastic for ameteur radio usage */ @@ -521,7 +486,7 @@ extern "C" { #define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_default {_meshtastic_OTAMode_MIN, {0, {0}}} -#define meshtastic_LockdownAuth_init_default {{0, {0}}, 0, 0, 0, 0} +#define meshtastic_LockdownAuth_init_default {{0, {0}}, 0, 0, 0} #define meshtastic_HamParameters_init_default {"", 0, 0, ""} #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} #define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0} @@ -534,7 +499,7 @@ extern "C" { #define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}} -#define meshtastic_LockdownAuth_init_zero {{0, {0}}, 0, 0, 0, 0} +#define meshtastic_LockdownAuth_init_zero {{0, {0}}, 0, 0, 0} #define meshtastic_HamParameters_init_zero {"", 0, 0, ""} #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} #define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0} @@ -556,7 +521,6 @@ extern "C" { #define meshtastic_LockdownAuth_boots_remaining_tag 2 #define meshtastic_LockdownAuth_valid_until_epoch_tag 3 #define meshtastic_LockdownAuth_lock_now_tag 4 -#define meshtastic_LockdownAuth_max_session_seconds_tag 5 #define meshtastic_HamParameters_call_sign_tag 1 #define meshtastic_HamParameters_tx_power_tag 2 #define meshtastic_HamParameters_frequency_tag 3 @@ -753,8 +717,7 @@ X(a, STATIC, SINGULAR, BYTES, ota_hash, 2) X(a, STATIC, SINGULAR, BYTES, passphrase, 1) \ X(a, STATIC, SINGULAR, UINT32, boots_remaining, 2) \ X(a, STATIC, SINGULAR, UINT32, valid_until_epoch, 3) \ -X(a, STATIC, SINGULAR, BOOL, lock_now, 4) \ -X(a, STATIC, SINGULAR, UINT32, max_session_seconds, 5) +X(a, STATIC, SINGULAR, BOOL, lock_now, 4) #define meshtastic_LockdownAuth_CALLBACK NULL #define meshtastic_LockdownAuth_DEFAULT NULL @@ -869,7 +832,7 @@ extern const pb_msgdesc_t meshtastic_SHTXX_config_msg; #define meshtastic_AdminMessage_size 511 #define meshtastic_HamParameters_size 31 #define meshtastic_KeyVerificationAdmin_size 25 -#define meshtastic_LockdownAuth_size 54 +#define meshtastic_LockdownAuth_size 48 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 #define meshtastic_SCD30_config_size 27 #define meshtastic_SCD4X_config_size 29 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index d614a6438..820bb2764 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -198,7 +198,9 @@ typedef enum _meshtastic_Config_DisplayConfig_OledType { /* Can not be auto detected but set by proto. Used for 128x64 screens */ meshtastic_Config_DisplayConfig_OledType_OLED_SH1107 = 3, /* Can not be auto detected but set by proto. Used for 128x128 screens */ - meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4 + meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4, + /* Can not be auto detected but set by proto. Used for 64x128 rotated screens */ + meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED = 5 } meshtastic_Config_DisplayConfig_OledType; typedef enum _meshtastic_Config_DisplayConfig_DisplayMode { @@ -720,8 +722,8 @@ extern "C" { #define _meshtastic_Config_DisplayConfig_DisplayUnits_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayUnits)(meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL+1)) #define _meshtastic_Config_DisplayConfig_OledType_MIN meshtastic_Config_DisplayConfig_OledType_OLED_AUTO -#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 -#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128+1)) +#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED +#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED+1)) #define _meshtastic_Config_DisplayConfig_DisplayMode_MIN meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT #define _meshtastic_Config_DisplayConfig_DisplayMode_MAX meshtastic_Config_DisplayConfig_DisplayMode_COLOR diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.cpp b/src/mesh/generated/meshtastic/deviceonly.pb.cpp index 558086637..5a9695702 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.cpp +++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp @@ -18,18 +18,6 @@ PB_BIND(meshtastic_NodeInfoLite, meshtastic_NodeInfoLite, AUTO) PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 2) -PB_BIND(meshtastic_NodePositionEntry, meshtastic_NodePositionEntry, AUTO) - - -PB_BIND(meshtastic_NodeTelemetryEntry, meshtastic_NodeTelemetryEntry, AUTO) - - -PB_BIND(meshtastic_NodeEnvironmentEntry, meshtastic_NodeEnvironmentEntry, AUTO) - - -PB_BIND(meshtastic_NodeStatusEntry, meshtastic_NodeStatusEntry, AUTO) - - PB_BIND(meshtastic_NodeDatabase, meshtastic_NodeDatabase, AUTO) diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 7c14c3e0f..6d03dc643 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -33,8 +33,6 @@ typedef struct _meshtastic_PositionLite { uint32_t time; /* TODO: REPLACE */ meshtastic_Position_LocSource location_source; - /* Indicates the bits of precision set by the sending node */ - uint32_t precision_bits; } meshtastic_PositionLite; typedef PB_BYTES_ARRAY_T(32) meshtastic_UserLite_public_key_t; @@ -65,35 +63,43 @@ typedef struct _meshtastic_UserLite { bool is_unmessagable; } meshtastic_UserLite; -typedef PB_BYTES_ARRAY_T(32) meshtastic_NodeInfoLite_public_key_t; typedef struct _meshtastic_NodeInfoLite { /* The node number */ uint32_t num; + /* The user info for this node */ + bool has_user; + meshtastic_UserLite user; + /* This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. + Position.time now indicates the last time we received a POSITION from that node. */ + bool has_position; + meshtastic_PositionLite position; /* Returns the Signal-to-noise ratio (SNR) of the last received message, as measured by the receiver. Return SNR of the last received message in dB */ float snr; /* Set to indicate the last time we received a packet from this node */ uint32_t last_heard; + /* The latest device metrics for the node. */ + bool has_device_metrics; + meshtastic_DeviceMetrics device_metrics; /* local channel index we heard that node on. Only populated if its not the default channel. */ uint8_t channel; + /* True if we witnessed the node over MQTT instead of LoRA transport */ + bool via_mqtt; /* Number of hops away from us this node is (0 if direct neighbor) */ bool has_hops_away; uint8_t hops_away; + /* True if node is in our favorites list + Persists between NodeDB internal clean ups */ + bool is_favorite; + /* True if node is in our ignored list + Persists between NodeDB internal clean ups */ + bool is_ignored; /* Last byte of the node number of the node that should be used as the next hop to reach this node. */ uint8_t next_hop; - /* Bitfield for storing booleans. See NODEINFO_BITFIELD_* in src/mesh/NodeDB.h. */ + /* Bitfield for storing booleans. + LSB 0 is_key_manually_verified + LSB 1 is_muted */ uint32_t bitfield; - /* A full name for this user, i.e. "Kevin Hester". */ - char long_name[25]; - /* A VERY short name, ideally two characters or an emoji. - Suitable for a tiny OLED screen. */ - char short_name[5]; - /* Hardware model the user's device is running. */ - meshtastic_HardwareModel hw_model; - /* The user's role in the mesh. */ - meshtastic_Config_DeviceConfig_Role role; - /* The public key of the user's device, for PKI-based encrypted DMs. */ - meshtastic_NodeInfoLite_public_key_t public_key; } meshtastic_NodeInfoLite; /* This message is never sent over the wire, but it is used for serializing DB @@ -137,30 +143,6 @@ typedef struct _meshtastic_DeviceState { meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12]; } meshtastic_DeviceState; -typedef struct _meshtastic_NodePositionEntry { - uint32_t num; - bool has_position; - meshtastic_PositionLite position; -} meshtastic_NodePositionEntry; - -typedef struct _meshtastic_NodeTelemetryEntry { - uint32_t num; - bool has_device_metrics; - meshtastic_DeviceMetrics device_metrics; -} meshtastic_NodeTelemetryEntry; - -typedef struct _meshtastic_NodeEnvironmentEntry { - uint32_t num; - bool has_environment_metrics; - meshtastic_EnvironmentMetrics environment_metrics; -} meshtastic_NodeEnvironmentEntry; - -typedef struct _meshtastic_NodeStatusEntry { - uint32_t num; - bool has_status; - meshtastic_StatusMessage status; -} meshtastic_NodeStatusEntry; - typedef struct _meshtastic_NodeDatabase { /* A version integer used to invalidate old save files when we make incompatible changes This integer is set at build time and is private to @@ -168,12 +150,6 @@ typedef struct _meshtastic_NodeDatabase { uint32_t version; /* New lite version of NodeDB to decrease memory footprint */ std::vector nodes; - /* Per-NodeNum satellite arrays. Constrained platforms (e.g. STM32WL) omit - these via MESHTASTIC_EXCLUDE_*DB build flags. */ - std::vector positions; - std::vector telemetry; - std::vector status; - std::vector environment; } meshtastic_NodeDatabase; /* The on-disk saved channels */ @@ -213,26 +189,18 @@ extern "C" { #endif /* Initializer values for message structs */ -#define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, 0} +#define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_UserLite_init_default {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0} -#define meshtastic_NodeInfoLite_init_default {0, 0, 0, 0, false, 0, 0, 0, "", "", _meshtastic_HardwareModel_MIN, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} +#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0} #define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} -#define meshtastic_NodePositionEntry_init_default {0, false, meshtastic_PositionLite_init_default} -#define meshtastic_NodeTelemetryEntry_init_default {0, false, meshtastic_DeviceMetrics_init_default} -#define meshtastic_NodeEnvironmentEntry_init_default {0, false, meshtastic_EnvironmentMetrics_init_default} -#define meshtastic_NodeStatusEntry_init_default {0, false, meshtastic_StatusMessage_init_default} -#define meshtastic_NodeDatabase_init_default {0, {0}, {0}, {0}, {0}, {0}} +#define meshtastic_NodeDatabase_init_default {0, {0}} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default} -#define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, 0} +#define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} #define meshtastic_UserLite_init_zero {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0} -#define meshtastic_NodeInfoLite_init_zero {0, 0, 0, 0, false, 0, 0, 0, "", "", _meshtastic_HardwareModel_MIN, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} +#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0} #define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} -#define meshtastic_NodePositionEntry_init_zero {0, false, meshtastic_PositionLite_init_zero} -#define meshtastic_NodeTelemetryEntry_init_zero {0, false, meshtastic_DeviceMetrics_init_zero} -#define meshtastic_NodeEnvironmentEntry_init_zero {0, false, meshtastic_EnvironmentMetrics_init_zero} -#define meshtastic_NodeStatusEntry_init_zero {0, false, meshtastic_StatusMessage_init_zero} -#define meshtastic_NodeDatabase_init_zero {0, {0}, {0}, {0}, {0}, {0}} +#define meshtastic_NodeDatabase_init_zero {0, {0}} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_BackupPreferences_init_zero {0, 0, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero, false, meshtastic_ChannelFile_init_zero, false, meshtastic_User_init_zero} @@ -242,7 +210,6 @@ extern "C" { #define meshtastic_PositionLite_altitude_tag 3 #define meshtastic_PositionLite_time_tag 4 #define meshtastic_PositionLite_location_source_tag 5 -#define meshtastic_PositionLite_precision_bits_tag 6 #define meshtastic_UserLite_macaddr_tag 1 #define meshtastic_UserLite_long_name_tag 2 #define meshtastic_UserLite_short_name_tag 3 @@ -252,17 +219,18 @@ extern "C" { #define meshtastic_UserLite_public_key_tag 7 #define meshtastic_UserLite_is_unmessagable_tag 9 #define meshtastic_NodeInfoLite_num_tag 1 +#define meshtastic_NodeInfoLite_user_tag 2 +#define meshtastic_NodeInfoLite_position_tag 3 #define meshtastic_NodeInfoLite_snr_tag 4 #define meshtastic_NodeInfoLite_last_heard_tag 5 +#define meshtastic_NodeInfoLite_device_metrics_tag 6 #define meshtastic_NodeInfoLite_channel_tag 7 +#define meshtastic_NodeInfoLite_via_mqtt_tag 8 #define meshtastic_NodeInfoLite_hops_away_tag 9 +#define meshtastic_NodeInfoLite_is_favorite_tag 10 +#define meshtastic_NodeInfoLite_is_ignored_tag 11 #define meshtastic_NodeInfoLite_next_hop_tag 12 #define meshtastic_NodeInfoLite_bitfield_tag 13 -#define meshtastic_NodeInfoLite_long_name_tag 14 -#define meshtastic_NodeInfoLite_short_name_tag 15 -#define meshtastic_NodeInfoLite_hw_model_tag 16 -#define meshtastic_NodeInfoLite_role_tag 17 -#define meshtastic_NodeInfoLite_public_key_tag 18 #define meshtastic_DeviceState_my_node_tag 2 #define meshtastic_DeviceState_owner_tag 3 #define meshtastic_DeviceState_receive_queue_tag 5 @@ -272,20 +240,8 @@ extern "C" { #define meshtastic_DeviceState_did_gps_reset_tag 11 #define meshtastic_DeviceState_rx_waypoint_tag 12 #define meshtastic_DeviceState_node_remote_hardware_pins_tag 13 -#define meshtastic_NodePositionEntry_num_tag 1 -#define meshtastic_NodePositionEntry_position_tag 2 -#define meshtastic_NodeTelemetryEntry_num_tag 1 -#define meshtastic_NodeTelemetryEntry_device_metrics_tag 2 -#define meshtastic_NodeEnvironmentEntry_num_tag 1 -#define meshtastic_NodeEnvironmentEntry_environment_metrics_tag 2 -#define meshtastic_NodeStatusEntry_num_tag 1 -#define meshtastic_NodeStatusEntry_status_tag 2 #define meshtastic_NodeDatabase_version_tag 1 #define meshtastic_NodeDatabase_nodes_tag 2 -#define meshtastic_NodeDatabase_positions_tag 3 -#define meshtastic_NodeDatabase_telemetry_tag 4 -#define meshtastic_NodeDatabase_status_tag 5 -#define meshtastic_NodeDatabase_environment_tag 6 #define meshtastic_ChannelFile_channels_tag 1 #define meshtastic_ChannelFile_version_tag 2 #define meshtastic_BackupPreferences_version_tag 1 @@ -301,8 +257,7 @@ X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \ X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \ X(a, STATIC, SINGULAR, INT32, altitude, 3) \ X(a, STATIC, SINGULAR, FIXED32, time, 4) \ -X(a, STATIC, SINGULAR, UENUM, location_source, 5) \ -X(a, STATIC, SINGULAR, UINT32, precision_bits, 6) +X(a, STATIC, SINGULAR, UENUM, location_source, 5) #define meshtastic_PositionLite_CALLBACK NULL #define meshtastic_PositionLite_DEFAULT NULL @@ -320,19 +275,23 @@ X(a, STATIC, OPTIONAL, BOOL, is_unmessagable, 9) #define meshtastic_NodeInfoLite_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, num, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ +X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ +X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ X(a, STATIC, SINGULAR, UINT32, channel, 7) \ +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ X(a, STATIC, OPTIONAL, UINT32, hops_away, 9) \ +X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) \ +X(a, STATIC, SINGULAR, BOOL, is_ignored, 11) \ X(a, STATIC, SINGULAR, UINT32, next_hop, 12) \ -X(a, STATIC, SINGULAR, UINT32, bitfield, 13) \ -X(a, STATIC, SINGULAR, STRING, long_name, 14) \ -X(a, STATIC, SINGULAR, STRING, short_name, 15) \ -X(a, STATIC, SINGULAR, UENUM, hw_model, 16) \ -X(a, STATIC, SINGULAR, UENUM, role, 17) \ -X(a, STATIC, SINGULAR, BYTES, public_key, 18) +X(a, STATIC, SINGULAR, UINT32, bitfield, 13) #define meshtastic_NodeInfoLite_CALLBACK NULL #define meshtastic_NodeInfoLite_DEFAULT NULL +#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite +#define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite +#define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics #define meshtastic_DeviceState_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \ @@ -353,49 +312,13 @@ X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 13) #define meshtastic_DeviceState_rx_waypoint_MSGTYPE meshtastic_MeshPacket #define meshtastic_DeviceState_node_remote_hardware_pins_MSGTYPE meshtastic_NodeRemoteHardwarePin -#define meshtastic_NodePositionEntry_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, num, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, position, 2) -#define meshtastic_NodePositionEntry_CALLBACK NULL -#define meshtastic_NodePositionEntry_DEFAULT NULL -#define meshtastic_NodePositionEntry_position_MSGTYPE meshtastic_PositionLite - -#define meshtastic_NodeTelemetryEntry_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, num, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 2) -#define meshtastic_NodeTelemetryEntry_CALLBACK NULL -#define meshtastic_NodeTelemetryEntry_DEFAULT NULL -#define meshtastic_NodeTelemetryEntry_device_metrics_MSGTYPE meshtastic_DeviceMetrics - -#define meshtastic_NodeEnvironmentEntry_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, num, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, environment_metrics, 2) -#define meshtastic_NodeEnvironmentEntry_CALLBACK NULL -#define meshtastic_NodeEnvironmentEntry_DEFAULT NULL -#define meshtastic_NodeEnvironmentEntry_environment_metrics_MSGTYPE meshtastic_EnvironmentMetrics - -#define meshtastic_NodeStatusEntry_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, num, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, status, 2) -#define meshtastic_NodeStatusEntry_CALLBACK NULL -#define meshtastic_NodeStatusEntry_DEFAULT NULL -#define meshtastic_NodeStatusEntry_status_MSGTYPE meshtastic_StatusMessage - #define meshtastic_NodeDatabase_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, version, 1) \ -X(a, CALLBACK, REPEATED, MESSAGE, nodes, 2) \ -X(a, CALLBACK, REPEATED, MESSAGE, positions, 3) \ -X(a, CALLBACK, REPEATED, MESSAGE, telemetry, 4) \ -X(a, CALLBACK, REPEATED, MESSAGE, status, 5) \ -X(a, CALLBACK, REPEATED, MESSAGE, environment, 6) +X(a, CALLBACK, REPEATED, MESSAGE, nodes, 2) extern bool meshtastic_NodeDatabase_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field); #define meshtastic_NodeDatabase_CALLBACK meshtastic_NodeDatabase_callback #define meshtastic_NodeDatabase_DEFAULT NULL #define meshtastic_NodeDatabase_nodes_MSGTYPE meshtastic_NodeInfoLite -#define meshtastic_NodeDatabase_positions_MSGTYPE meshtastic_NodePositionEntry -#define meshtastic_NodeDatabase_telemetry_MSGTYPE meshtastic_NodeTelemetryEntry -#define meshtastic_NodeDatabase_status_MSGTYPE meshtastic_NodeStatusEntry -#define meshtastic_NodeDatabase_environment_MSGTYPE meshtastic_NodeEnvironmentEntry #define meshtastic_ChannelFile_FIELDLIST(X, a) \ X(a, STATIC, REPEATED, MESSAGE, channels, 1) \ @@ -422,10 +345,6 @@ extern const pb_msgdesc_t meshtastic_PositionLite_msg; extern const pb_msgdesc_t meshtastic_UserLite_msg; extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg; extern const pb_msgdesc_t meshtastic_DeviceState_msg; -extern const pb_msgdesc_t meshtastic_NodePositionEntry_msg; -extern const pb_msgdesc_t meshtastic_NodeTelemetryEntry_msg; -extern const pb_msgdesc_t meshtastic_NodeEnvironmentEntry_msg; -extern const pb_msgdesc_t meshtastic_NodeStatusEntry_msg; extern const pb_msgdesc_t meshtastic_NodeDatabase_msg; extern const pb_msgdesc_t meshtastic_ChannelFile_msg; extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; @@ -435,10 +354,6 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; #define meshtastic_UserLite_fields &meshtastic_UserLite_msg #define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg #define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg -#define meshtastic_NodePositionEntry_fields &meshtastic_NodePositionEntry_msg -#define meshtastic_NodeTelemetryEntry_fields &meshtastic_NodeTelemetryEntry_msg -#define meshtastic_NodeEnvironmentEntry_fields &meshtastic_NodeEnvironmentEntry_msg -#define meshtastic_NodeStatusEntry_fields &meshtastic_NodeStatusEntry_msg #define meshtastic_NodeDatabase_fields &meshtastic_NodeDatabase_msg #define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg #define meshtastic_BackupPreferences_fields &meshtastic_BackupPreferences_msg @@ -448,13 +363,9 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size #define meshtastic_BackupPreferences_size 2432 #define meshtastic_ChannelFile_size 718 -#define meshtastic_DeviceState_size 1944 -#define meshtastic_NodeEnvironmentEntry_size 170 -#define meshtastic_NodeInfoLite_size 105 -#define meshtastic_NodePositionEntry_size 42 -#define meshtastic_NodeStatusEntry_size 89 -#define meshtastic_NodeTelemetryEntry_size 35 -#define meshtastic_PositionLite_size 34 +#define meshtastic_DeviceState_size 1737 +#define meshtastic_NodeInfoLite_size 196 +#define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 98 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index f0fea08d7..cb5f19df5 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -321,6 +321,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_HELTEC_MESH_NODE_T1 = 133, /* B&Q Consulting Station G3: TBD */ meshtastic_HardwareModel_STATION_G3 = 134, + /* Lilygo T-Impulse-Plus */ + meshtastic_HardwareModel_T_IMPULSE_PLUS = 135, + /* Lilygo T-Echo Card */ + meshtastic_HardwareModel_T_ECHO_CARD = 136, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -820,7 +824,6 @@ typedef struct _meshtastic_Routing { } meshtastic_Routing; typedef PB_BYTES_ARRAY_T(233) meshtastic_Data_payload_t; -typedef PB_BYTES_ARRAY_T(64) meshtastic_Data_xeddsa_signature_t; /* (Formerly called SubPacket) The payload portion fo a packet, this is the actual bytes that are sent inside a radio packet (because from/to are broken out by the comms library) */ @@ -854,8 +857,6 @@ typedef struct _meshtastic_Data { /* Bitfield for extra flags. First use is to indicate that user approves the packet being uploaded to MQTT. */ bool has_bitfield; uint8_t bitfield; - /* XEdDSA signature for the payload */ - meshtastic_Data_xeddsa_signature_t xeddsa_signature; } meshtastic_Data; typedef PB_BYTES_ARRAY_T(32) meshtastic_KeyVerification_hash1_t; @@ -1060,8 +1061,6 @@ typedef struct _meshtastic_MeshPacket { uint32_t tx_after; /* Indicates which transport mechanism this packet arrived over */ meshtastic_MeshPacket_TransportMechanism transport_mechanism; - /* Indicates whether the packet has a valid signature */ - bool xeddsa_signed; } meshtastic_MeshPacket; /* The bluetooth to device link: @@ -1118,10 +1117,6 @@ typedef struct _meshtastic_NodeInfo { /* True if node has been muted Persistes between NodeDB internal clean ups */ bool is_muted; - /* True if node is signing its packets via XEdDSA - Persists between NodeDB internal clean ups - LSB 1 of the bitfield */ - bool has_xeddsa_signed; } meshtastic_NodeInfo; typedef PB_BYTES_ARRAY_T(16) meshtastic_MyNodeInfo_device_id_t; @@ -1591,15 +1586,15 @@ extern "C" { #define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0} #define meshtastic_RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}} -#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0, {0, {0}}} +#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_RemoteShell_init_default {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_default {""} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN, 0} -#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0} +#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} +#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} @@ -1626,15 +1621,15 @@ extern "C" { #define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0} #define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}} -#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0, {0, {0}}} +#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_RemoteShell_init_zero {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_zero {""} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN, 0} -#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0} +#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} +#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} @@ -1707,7 +1702,6 @@ extern "C" { #define meshtastic_Data_reply_id_tag 7 #define meshtastic_Data_emoji_tag 8 #define meshtastic_Data_bitfield_tag 9 -#define meshtastic_Data_xeddsa_signature_tag 10 #define meshtastic_KeyVerification_nonce_tag 1 #define meshtastic_KeyVerification_hash1_tag 2 #define meshtastic_KeyVerification_hash2_tag 3 @@ -1765,7 +1759,6 @@ extern "C" { #define meshtastic_MeshPacket_relay_node_tag 19 #define meshtastic_MeshPacket_tx_after_tag 20 #define meshtastic_MeshPacket_transport_mechanism_tag 21 -#define meshtastic_MeshPacket_xeddsa_signed_tag 22 #define meshtastic_NodeInfo_num_tag 1 #define meshtastic_NodeInfo_user_tag 2 #define meshtastic_NodeInfo_position_tag 3 @@ -1779,7 +1772,6 @@ extern "C" { #define meshtastic_NodeInfo_is_ignored_tag 11 #define meshtastic_NodeInfo_is_key_manually_verified_tag 12 #define meshtastic_NodeInfo_is_muted_tag 13 -#define meshtastic_NodeInfo_has_xeddsa_signed_tag 14 #define meshtastic_MyNodeInfo_my_node_num_tag 1 #define meshtastic_MyNodeInfo_reboot_count_tag 8 #define meshtastic_MyNodeInfo_min_app_version_tag 11 @@ -1946,8 +1938,7 @@ X(a, STATIC, SINGULAR, FIXED32, source, 5) \ X(a, STATIC, SINGULAR, FIXED32, request_id, 6) \ X(a, STATIC, SINGULAR, FIXED32, reply_id, 7) \ X(a, STATIC, SINGULAR, FIXED32, emoji, 8) \ -X(a, STATIC, OPTIONAL, UINT32, bitfield, 9) \ -X(a, STATIC, SINGULAR, BYTES, xeddsa_signature, 10) +X(a, STATIC, OPTIONAL, UINT32, bitfield, 9) #define meshtastic_Data_CALLBACK NULL #define meshtastic_Data_DEFAULT NULL @@ -2032,8 +2023,7 @@ X(a, STATIC, SINGULAR, BOOL, pki_encrypted, 17) \ X(a, STATIC, SINGULAR, UINT32, next_hop, 18) \ X(a, STATIC, SINGULAR, UINT32, relay_node, 19) \ X(a, STATIC, SINGULAR, UINT32, tx_after, 20) \ -X(a, STATIC, SINGULAR, UENUM, transport_mechanism, 21) \ -X(a, STATIC, SINGULAR, BOOL, xeddsa_signed, 22) +X(a, STATIC, SINGULAR, UENUM, transport_mechanism, 21) #define meshtastic_MeshPacket_CALLBACK NULL #define meshtastic_MeshPacket_DEFAULT NULL #define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data @@ -2051,8 +2041,7 @@ X(a, STATIC, OPTIONAL, UINT32, hops_away, 9) \ X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) \ X(a, STATIC, SINGULAR, BOOL, is_ignored, 11) \ X(a, STATIC, SINGULAR, BOOL, is_key_manually_verified, 12) \ -X(a, STATIC, SINGULAR, BOOL, is_muted, 13) \ -X(a, STATIC, SINGULAR, BOOL, has_xeddsa_signed, 14) +X(a, STATIC, SINGULAR, BOOL, is_muted, 13) #define meshtastic_NodeInfo_CALLBACK NULL #define meshtastic_NodeInfo_DEFAULT NULL #define meshtastic_NodeInfo_user_MSGTYPE meshtastic_User @@ -2354,7 +2343,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_ChunkedPayload_size 245 #define meshtastic_ClientNotification_size 482 #define meshtastic_Compressed_size 239 -#define meshtastic_Data_size 335 +#define meshtastic_Data_size 269 #define meshtastic_DeviceMetadata_size 54 #define meshtastic_DuplicatedPublicKey_size 0 #define meshtastic_FileInfo_size 236 @@ -2367,12 +2356,12 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LockdownStatus_size 53 #define meshtastic_LogRecord_size 426 #define meshtastic_LowEntropyKey_size 0 -#define meshtastic_MeshPacket_size 450 +#define meshtastic_MeshPacket_size 381 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 83 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 -#define meshtastic_NodeInfo_size 327 +#define meshtastic_NodeInfo_size 325 #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index b8cf60bf0..25937e972 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -144,7 +144,7 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig { (the default official mqtt.meshtastic.org server can handle encrypted packets) Decrypted packets may be useful for external systems that want to consume meshtastic packets */ bool encryption_enabled; - /* Whether to send / consume json packets on MQTT */ + /* Deprecated: JSON packet support on MQTT was removed, and this field is ignored. */ bool json_enabled; /* If true, we attempt to establish a secure connection using TLS */ bool tls_enabled; From 98e0604edf878cb2fe22c94e1c02cd715bfa27a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 19 May 2026 07:58:24 +0200 Subject: [PATCH 14/21] Fix antenna switch initialization logic once more --- src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp index 0c252159f..6bd86ab4b 100644 --- a/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp +++ b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp @@ -47,7 +47,7 @@ static void initLoraCap() return; } bool ok = pi4ioWrite(bus, PI4IO_REG_IO_DIR, 0b00000001); - ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_H_IM, 0b00000001); + ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_H_IM, 0b00000000); ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_SET, 0b00000001); if (!ok) { LOG_ERROR("Antenna switch init failed"); From 0832330327867f666c2196dd0deab37ed9ffe129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 19 May 2026 08:00:02 +0200 Subject: [PATCH 15/21] Fix antenna switch initialization logic --- src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp index 0c252159f..6bd86ab4b 100644 --- a/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp +++ b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp @@ -47,7 +47,7 @@ static void initLoraCap() return; } bool ok = pi4ioWrite(bus, PI4IO_REG_IO_DIR, 0b00000001); - ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_H_IM, 0b00000001); + ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_H_IM, 0b00000000); ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_SET, 0b00000001); if (!ok) { LOG_ERROR("Antenna switch init failed"); From 622aa046f1a99e099ae6f8606a8fa894b4ec87cc Mon Sep 17 00:00:00 2001 From: Riker Date: Tue, 19 May 2026 14:31:42 +0800 Subject: [PATCH 16/21] Enabled SX_LNA_EN by default (#10469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enabled SX_LNA_EN by default * Update I2C configuration for IO direction and pull settings --------- Co-authored-by: Thomas Göttgens --- variants/esp32c6/m5stack_unitc6l/variant.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/variants/esp32c6/m5stack_unitc6l/variant.cpp b/variants/esp32c6/m5stack_unitc6l/variant.cpp index 8e26b4ab7..7dc5785f6 100644 --- a/variants/esp32c6/m5stack_unitc6l/variant.cpp +++ b/variants/esp32c6/m5stack_unitc6l/variant.cpp @@ -52,23 +52,25 @@ void c6l_init() vTaskDelay(10 / portTICK_PERIOD_MS); i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, &in_data); vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11000000); // 0: input 1: output + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11100000); // P5,P6,P7 as outputs vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00111100); // 使用到的引脚关闭High-Impedance + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00011100); // High-Impedance vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11000011); // pull up/down select, 0 down, 1 up + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11100011); // pull up/down select, 0 down, 1 up vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11000011); // pull up/down enable, 0 disable, 1 enable + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11100011); // pull up/down enable, 0 disable, 1 enable vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 默认高电平, 按键按下触发中断 + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 default to high level; button press triggers the interrupt vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 中断使能 0 enable, 1 disable + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 interrupts enabled (0 = enable, 1 = disable) vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // 默认输出为0 + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // default output is 0 + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // read IRQ_STA to clear the flag vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // 读取IRQ_STA清除标志 i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, &in_data); - setbit(in_data, 6); // HIGH + setbit(in_data, 6); // enable SX_ANT_SW + setbit(in_data, 5); // enable SX_LNA_EN i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, in_data); } From 1747e2d8e5c542f01fd9ed526d143adf853ce177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 19 May 2026 09:31:04 +0200 Subject: [PATCH 17/21] T-Echo-Card support (#10267) --- .trunk/trunk.yaml | 8 +- src/graphics/Screen.cpp | 11 +- src/graphics/ScreenFonts.h | 2 +- src/graphics/SharedUIDisplay.cpp | 8 +- src/graphics/draw/DebugRenderer.cpp | 4 +- src/graphics/draw/MenuHandler.cpp | 2 +- src/graphics/draw/NodeListRenderer.cpp | 8 +- src/graphics/draw/NotificationRenderer.cpp | 2 +- src/graphics/draw/UIRenderer.cpp | 12 +- src/graphics/images.h | 2 +- src/main.cpp | 5 + src/mesh/NodeDB.cpp | 5 +- src/modules/ExternalNotificationModule.cpp | 10 + src/modules/ExternalNotificationModule.h | 17 ++ src/modules/StatusLEDModule.cpp | 33 +++ src/modules/StatusLEDModule.h | 26 +++ src/nimble/NimbleBluetooth.cpp | 2 +- variants/esp32c6/m5stack_unitc6l/variant.h | 3 + variants/native/portduino.ini | 2 +- variants/nrf52840/t-echo-card/platformio.ini | 12 ++ variants/nrf52840/t-echo-card/variant.cpp | 66 ++++++ variants/nrf52840/t-echo-card/variant.h | 202 +++++++++++++++++++ 22 files changed, 414 insertions(+), 28 deletions(-) create mode 100644 variants/nrf52840/t-echo-card/platformio.ini create mode 100644 variants/nrf52840/t-echo-card/variant.cpp create mode 100644 variants/nrf52840/t-echo-card/variant.h diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index a36974327..26e4c6892 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,11 +4,11 @@ cli: plugins: sources: - id: trunk - ref: v1.9.0 + ref: v1.10.0 uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.528 + - checkov@3.2.529 - renovate@43.150.0 - prettier@3.8.3 - trufflehog@3.95.3 @@ -16,7 +16,7 @@ lint: - bandit@1.9.4 - trivy@0.70.0 - taplo@0.10.0 - - ruff@0.15.12 + - ruff@0.15.13 - isort@8.0.1 - markdownlint@0.48.0 - oxipng@10.1.1 @@ -26,7 +26,7 @@ lint: - hadolint@2.14.0 - shfmt@3.6.0 - shellcheck@0.11.0 - - black@26.3.1 + - black@26.5.0 - git-diff-check - gitleaks@8.30.1 - clang-format@16.0.3 diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 044db3637..4de23bce7 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -425,6 +425,11 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#if defined(OLED_Y_OFFSET_PAGES) + // Panels whose active window does not start at GDDRAM row 0 (e.g. 72x40 + // modules on pages 3..7) need a fixed vertical page shift on every write. + static_cast(dispdev)->setYOffset(OLED_Y_OFFSET_PAGES); +#endif #elif defined(USE_SPISSD1306) dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48); if (!dispdev->init()) { @@ -910,7 +915,7 @@ int32_t Screen::runOnce() #ifndef DISABLE_WELCOME_UNSET if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) menuHandler::LoraRegionPicker(); #else menuHandler::OnboardMessage(); @@ -1147,7 +1152,7 @@ void Screen::setFrames(FrameFocus focus) #if defined(DISPLAY_CLOCK_FRAME) if (!hiddenFrames.clock) { fsi.positions.clock = numframes; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame; #else normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame @@ -1606,7 +1611,7 @@ void Screen::showFrame(FrameDirection direction) void Screen::setFastFramerate() { -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) dispdev->clear(); #if GRAPHICS_TFT_COLORING_ENABLED prepareFrameColorRegions(); diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 82ceb5406..624465f33 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -106,7 +106,7 @@ #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 #define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28 #define FONT_LARGE FONT_LARGE_LOCAL // Height: 28 -#elif defined(M5STACK_UNITC6L) +#elif defined(OLED_TINY) #define FONT_SMALL FONT_SMALL_LOCAL // Height: 13 #define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13 #define FONT_LARGE FONT_SMALL_LOCAL // Height: 13 diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index becd3e75d..691a6fc58 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -31,6 +31,12 @@ ScreenResolution determineScreenResolution(int16_t screenheight, int16_t screenw return ScreenResolution::UltraLow; } +#ifdef DISPLAY_FORCE_SMALL_FONTS + if (screenwidth <= 160 && screenheight <= 80) { + return ScreenResolution::Low; + } +#endif + // Standard OLED screens if (screenwidth > 128 && screenheight <= 64) { return ScreenResolution::Low; @@ -240,7 +246,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti int batteryX = 1; int batteryY = HEADER_OFFSET_Y + 1; -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === Battery Icons === if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging batteryX += 1; diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 67136437a..0f8198254 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -460,7 +460,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], frequencyslot); -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === Fifth Row: Channel Utilization === const char *chUtil = "ChUtil:"; char chUtilPercentage[10]; @@ -592,7 +592,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x // Label display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawString(labelX, getTextPositions(display)[line], label); -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // Bar int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2; display->setColor(WHITE); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 386a4c077..9fc7fa410 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -555,7 +555,7 @@ void menuHandler::TZPicker() void menuHandler::clockMenu() { -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) static const char *optionsArray[] = {"Back", "Time Format", "Timezone"}; #else static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"}; diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index d0b027c13..dfe6671c8 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -26,7 +26,7 @@ extern bool haveGlyphs(const char *str); // Global screen instance extern graphics::Screen *screen; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) static uint32_t lastSwitchTime = 0; #endif namespace graphics @@ -788,7 +788,7 @@ void drawDynamicListScreen_Nodes(OLEDDisplay *display, OLEDDisplayUiState *state unsigned long now = millis(); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->clear(); if (now - lastSwitchTime >= 3000) { display->display(); @@ -824,7 +824,7 @@ void drawDynamicListScreen_Location(OLEDDisplay *display, OLEDDisplayUiState *st unsigned long now = millis(); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->clear(); if (now - lastSwitchTime >= 3000) { display->display(); @@ -894,7 +894,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, double lat = DegD(ourSelfPos.latitude_i); double lon = DegD(ourSelfPos.longitude_i); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->clear(); uint32_t now = millis(); if (now - lastSwitchTime >= 2000) { diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index d7eba0be4..02bfec31f 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -728,7 +728,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay } int16_t boxTop = (display->height() / 2) - (boxHeight / 2); boxHeight += (currentResolution == ScreenResolution::High) ? 2 : 1; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) if (visibleTotalLines == 1) { boxTop += 25; } diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index cfde10124..e7b00e5d5 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -25,7 +25,7 @@ // External variables extern graphics::Screen *screen; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) static uint32_t lastSwitchTime = 0; #endif namespace graphics @@ -753,7 +753,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat if (!node || node->num == nodeDB->getNodeNum() || !nodeInfoLiteIsFavorite(node)) return; display->clear(); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) uint32_t now = millis(); if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒 { @@ -959,7 +959,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat if (seenStr[0]) { display->drawString(x, getTextPositions(display)[line++], seenStr); } -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === 4. Uptime (only show if metric is present) === char uptimeStr[32] = ""; meshtastic_DeviceMetrics nodeMetrics; @@ -1172,7 +1172,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta } #endif -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) line += 1; // === Node Identity === @@ -1456,7 +1456,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED // needs to be drawn relative to x and y // draw centered icon left to right and centered above the one line of app text -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits); if (gBootSplashBoldPass) { display->drawXbm(x + (SCREEN_WIDTH - 50) / 2 + 1, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits); @@ -1666,7 +1666,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU } display->drawString(x, textPos[line++], altitudeLine); } -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === Draw Compass === if (validHeading || statusLine1) { // --- Compass Rendering: landscape (wide) screens use original side-aligned logic --- diff --git a/src/graphics/images.h b/src/graphics/images.h index 66fcbc79c..f11ad5686 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -318,7 +318,7 @@ const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f}; #define connection_icon_height 5 const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36}; -#ifdef M5STACK_UNITC6L +#ifdef OLED_TINY #include "img/icon_small.xbm" #else #include "img/icon.xbm" diff --git a/src/main.cpp b/src/main.cpp index 304712e6f..979517f0a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -753,6 +753,11 @@ void setup() } } #endif +#ifdef OLED_GEOMETRY_OVERRIDE + // Per-variant geometry (e.g. 72x40 micro-OLEDs). Takes precedence over the + // default GEOMETRY_128_64 set at the top of setup(). + screen_geometry = OLED_GEOMETRY_OVERRIDE; +#endif #endif #if !MESHTASTIC_EXCLUDE_I2C diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 9a8593bc4..9c1b85dad 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1071,7 +1071,8 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_store_forward = true; moduleConfig.has_telemetry = true; moduleConfig.has_external_notification = true; -#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION) +#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION) || \ + defined(NEOPIXEL_STATUS_NOTIFICATION_PIN) moduleConfig.external_notification.enabled = true; #endif #if defined(PIN_BUZZER) @@ -1092,7 +1093,7 @@ void NodeDB::installDefaultModuleConfig() #endif #if defined(PIN_VIBRATION) moduleConfig.external_notification.nag_timeout = 2; -#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) +#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) || defined(NEOPIXEL_STATUS_NOTIFICATION_PIN) moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; #endif diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 1a0b69930..8a98e86df 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -206,6 +206,10 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on) #ifdef PCA_LED_NOTIFICATION io.digitalWrite(PCA_LED_NOTIFICATION, on); +#endif +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN + notificationPixel.setPixelColor(0, on ? NEOPIXEL_STATUS_NOTIFICATION_COLOR : 0); + notificationPixel.show(); #endif break; } @@ -324,6 +328,12 @@ ExternalNotificationModule::ExternalNotificationModule() LOG_INFO("Use Pin %i in digital mode", output); pinMode(output, OUTPUT); } +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN + LOG_INFO("Use WS2812 on GPIO %d as notification LED", NEOPIXEL_STATUS_NOTIFICATION_PIN); + notificationPixel.begin(); + notificationPixel.clear(); + notificationPixel.show(); +#endif setExternalState(0, false); externalTurnedOn[0] = 0; if (moduleConfig.external_notification.output_vibra) { diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 94b021360..8781c1ca8 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -10,6 +10,19 @@ extern AmbientLightingThread *ambientLightingThread; #endif +// Drive a single WS2812 as the notification LED (M1/M2-style LED_NOTIFICATION +// but addressable). A variant defines NEOPIXEL_STATUS_NOTIFICATION_PIN to +// enable. Colour defaults to green but can be overridden. +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN +#include +#ifndef NEOPIXEL_STATUS_TYPE +#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800) +#endif +#ifndef NEOPIXEL_STATUS_NOTIFICATION_COLOR +#define NEOPIXEL_STATUS_NOTIFICATION_COLOR 0x00FF00 // green +#endif +#endif + #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) #include #else @@ -38,6 +51,10 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency: CallbackObserver(this, &ExternalNotificationModule::handleInputEvent); uint32_t output = 0; +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN + Adafruit_NeoPixel notificationPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_NOTIFICATION_PIN, NEOPIXEL_STATUS_TYPE); +#endif + public: ExternalNotificationModule(); diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 4ea34fb52..f3a0e7a03 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -17,8 +17,29 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule") if (inputBroker) inputObserver.observe(inputBroker); #endif +#ifdef NEOPIXEL_STATUS_POWER_PIN + powerPixel.begin(); + powerPixel.clear(); + powerPixel.show(); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + pairingPixel.begin(); + pairingPixel.clear(); + pairingPixel.show(); +#endif } +// Helper: write a 1-pixel NeoPixel strand to `color` when stateOn, else clear. +// Kept as a static inline here (rather than a member) so it compiles out +// completely when no NeoPixel status pins are defined. +#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN) +static inline void writeStatusPixel(Adafruit_NeoPixel &pixel, uint32_t color, bool stateOn) +{ + pixel.setPixelColor(0, stateOn ? color : 0); + pixel.show(); +} +#endif + int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) { switch (arg->getStatusType()) { @@ -176,6 +197,12 @@ int32_t StatusLEDModule::runOnce() #ifdef LED_PAIRING digitalWrite(LED_PAIRING, PAIRING_LED_state); #endif +#ifdef NEOPIXEL_STATUS_POWER_PIN + writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, CHARGE_LED_state == LED_STATE_ON); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, PAIRING_LED_state == LED_STATE_ON); +#endif #ifdef RGB_LED_POWER if (!config.device.led_heartbeat_disabled) { @@ -225,6 +252,12 @@ void StatusLEDModule::setPowerLED(bool LEDon) #ifdef LED_PAIRING digitalWrite(LED_PAIRING, ledState); #endif +#ifdef NEOPIXEL_STATUS_POWER_PIN + writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, LEDon); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, LEDon); +#endif #ifdef Battery_LED_1 digitalWrite(Battery_LED_1, ledState); diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h index f66a536f6..f20198e39 100644 --- a/src/modules/StatusLEDModule.h +++ b/src/modules/StatusLEDModule.h @@ -13,6 +13,25 @@ #include "input/InputBroker.h" #endif +// WS2812/NeoPixel status-LED support. A variant may define +// NEOPIXEL_STATUS_POWER_PIN (required to enable the power/charge pixel) +// NEOPIXEL_STATUS_POWER_COLOR (optional, default red 0xFF0000) +// NEOPIXEL_STATUS_PAIRING_PIN / _COLOR (default blue 0x0000FF) +// Each pixel is a standalone 1-LED strand on its own GPIO — this mirrors how +// boards like the LilyGo T-Echo-Card expose three independent WS2812s. +#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN) +#include +#ifndef NEOPIXEL_STATUS_TYPE +#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800) +#endif +#ifndef NEOPIXEL_STATUS_POWER_COLOR +#define NEOPIXEL_STATUS_POWER_COLOR 0xFF0000 // red +#endif +#ifndef NEOPIXEL_STATUS_PAIRING_COLOR +#define NEOPIXEL_STATUS_PAIRING_COLOR 0x0000FF // blue +#endif +#endif + class StatusLEDModule : private concurrency::OSThread { bool slowTrack = false; @@ -27,6 +46,13 @@ class StatusLEDModule : private concurrency::OSThread void setPowerLED(bool); +#ifdef NEOPIXEL_STATUS_POWER_PIN + Adafruit_NeoPixel powerPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_POWER_PIN, NEOPIXEL_STATUS_TYPE); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + Adafruit_NeoPixel pairingPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_PAIRING_PIN, NEOPIXEL_STATUS_TYPE); +#endif + protected: unsigned int my_interval = 1000; // interval in millisconds virtual int32_t runOnce() override; diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 6857f9b5d..e66e738e3 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -572,7 +572,7 @@ class NimbleBluetoothSecurityCallback : public BLESecurityCallbacks display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); display->drawString(x_offset + x, y_offset + y, "Bluetooth"); -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) display->setFont(FONT_SMALL); y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5; display->drawString(x_offset + x, y_offset + y, "Enter this code"); diff --git a/variants/esp32c6/m5stack_unitc6l/variant.h b/variants/esp32c6/m5stack_unitc6l/variant.h index 1654ee590..576d4e114 100644 --- a/variants/esp32c6/m5stack_unitc6l/variant.h +++ b/variants/esp32c6/m5stack_unitc6l/variant.h @@ -48,6 +48,9 @@ void c6l_init(); #define SSD1306_RESET 15 // #define OLED_DG 1 #endif +// Tiny OLED panel — opts into compile-time layout/font/feature substitutions +// gated on OLED_TINY across the graphics stack. +#define OLED_TINY #define SCREEN_TRANSITION_FRAMERATE 10 #define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index 972a9f3bf..bc21a9e9d 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -30,7 +30,7 @@ lib_deps = # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.21 ; # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main - https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip + https://github.com/pine64/libch341-spi-userspace/archive/2e5ff751d0c39667993df672cb683740ed5c9394.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main diff --git a/variants/nrf52840/t-echo-card/platformio.ini b/variants/nrf52840/t-echo-card/platformio.ini new file mode 100644 index 000000000..bc012d6e1 --- /dev/null +++ b/variants/nrf52840/t-echo-card/platformio.ini @@ -0,0 +1,12 @@ +[env:t-echo-card] +extends = nrf52840_base +board = t-echo +board_level = extra +debug_tool = jlink + +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/t-echo-card + -D PRIVATE_HW + -D T_ECHO_CARD + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo-card> diff --git a/variants/nrf52840/t-echo-card/variant.cpp b/variants/nrf52840/t-echo-card/variant.cpp new file mode 100644 index 000000000..e82a63f8e --- /dev/null +++ b/variants/nrf52840/t-echo-card/variant.cpp @@ -0,0 +1,66 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "Arduino.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // No plain GPIO LEDs on this board (only WS2812 addressable LEDs, not driven here). +} + +// Reproduces the vendor firmware's boot sequence from +// examples/original_test/original_test.ino. Runs before Meshtastic touches +// PIN_POWER_EN, so the RT9080 LDO gets a clean reset pulse and peripherals +// whose EN pins must be LOW at boot (GPS_EN, GPS_RF_EN, BUZZER) aren't left +// floating while the 3V3 rail is ramping. +void earlyInitVariant() +{ + // 3.3V rail: toggle RT9080_EN HIGH → LOW → HIGH with 100 ms dwell so the + // LDO enters enable from a known state. The single-shot HIGH in main.cpp + // is not enough on this hardware — if the chip was in a half-enabled + // state from a previous reset, the rail brown-outs once LoRa TX fires. + pinMode(PIN_POWER_EN, OUTPUT); + digitalWrite(PIN_POWER_EN, HIGH); + delay(100); + digitalWrite(PIN_POWER_EN, LOW); + delay(100); + digitalWrite(PIN_POWER_EN, HIGH); + delay(100); + + // Park peripherals with active-high enables LOW so they don't sink + // current while the rest of setup() runs. + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, LOW); + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, LOW); + pinMode(PIN_BUZZER, OUTPUT); + digitalWrite(PIN_BUZZER, LOW); +} diff --git a/variants/nrf52840/t-echo-card/variant.h b/variants/nrf52840/t-echo-card/variant.h new file mode 100644 index 000000000..37fa28d89 --- /dev/null +++ b/variants/nrf52840/t-echo-card/variant.h @@ -0,0 +1,202 @@ +// Variant definition for LilyGo T-Echo-Card (nRF52840) + +#ifndef _VARIANT_T_ECHO_CARD_ +#define _VARIANT_T_ECHO_CARD_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32kHz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs - board only exposes 3x WS2812 addressable LEDs. No plain GPIO LEDs. +// Intentionally do not define PIN_LED1 on this variant, so nRF52 platform +// code does not auto-enable a nonexistent GPIO power/status LED. +#define LED_STATE_ON 1 + +// Three independent WS2812 data lines (one LED per line, not a daisy chain). +// Each is driven as a 1-pixel NeoPixel by StatusLEDModule / ExternalNotification, +// assigns LED_POWER (red) and LED_NOTIFICATION (green). +#define WS2812_DATA_1 (32 + 7) // P1.7 - charge/heartbeat (red) +#define WS2812_DATA_2 (32 + 12) // P1.12 - external notification (green) +#define WS2812_DATA_3 (0 + 28) // P0.28 - BLE pairing (blue) + +// Wire each WS2812 to a status role. Colour defaults are scaled to 25% +// brightness (0x40) — the bare-die WS2812s on this board are very bright at +// full intensity in a close-range enclosure. +#define NEOPIXEL_STATUS_POWER_PIN WS2812_DATA_1 +#define NEOPIXEL_STATUS_NOTIFICATION_PIN WS2812_DATA_2 +#define NEOPIXEL_STATUS_PAIRING_PIN WS2812_DATA_3 +#define NEOPIXEL_STATUS_POWER_COLOR 0x400000 // red @ 25% +#define NEOPIXEL_STATUS_NOTIFICATION_COLOR 0x004000 // green @ 25% +#define NEOPIXEL_STATUS_PAIRING_COLOR 0x000040 // blue @ 25% + +// The charger IC does not blink on its own; let StatusLEDModule do the +// software blink while charging +// If left defined: hardware would be expected to handle the charging pulse. +// #define POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING + +// Buttons +#define PIN_BUTTON1 (32 + 10) // KEY_1: P1.10 + +#define BUTTON_CLICK_MS 400 + +// Analog pins +#define PIN_A0 (0 + 2) // Battery ADC (BATTERY_ADC_DATA) + +#define BATTERY_PIN PIN_A0 + +static const uint8_t A0 = PIN_A0; + +#define ADC_RESOLUTION 14 + +// BATTERY_MEASUREMENT_CONTROL - enable divider for battery reading +#define ADC_CTRL (0 + 31) +#define ADC_CTRL_ENABLED HIGH + +// NFC placeholders, not used +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +// Wire Interfaces (IIC_1 on the vendor header) +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (32 + 4) // IIC_1_SDA: P1.4 +#define PIN_WIRE_SCL (32 + 2) // IIC_1_SCL: P1.2 + +// External serial flash ZD25WQ32CEIGR +// QSPI Pins +#define PIN_QSPI_SCK (0 + 4) +#define PIN_QSPI_CS (0 + 12) +#define PIN_QSPI_IO0 (0 + 6) // MOSI if using two bit interface +#define PIN_QSPI_IO1 (0 + 8) // MISO if using two bit interface +#define PIN_QSPI_IO2 (32 + 9) // WP +#define PIN_QSPI_IO3 (0 + 26) // HOLD + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES ZD25WQ32CEIGR +#define EXTERNAL_FLASH_USE_QSPI + +// Lora S62F (SX1262) +#define USE_SX1262 +#define SX126X_CS (0 + 11) +#define SX126X_DIO1 (32 + 8) +#define SX126X_DIO2 (0 + 5) +#define SX126X_BUSY (0 + 14) +#define SX126X_RESET (0 + 7) +#define SX126X_RXEN (32 + 1) // SX1262_RF_VC2 +#define SX126X_TXEN (0 + 27) // SX1262_RF_VC1 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// ─────────────────────────────────────────────────────────────────────────── +// OLED display: SSD1315 on I2C @ 0x3C (IIC_1). SSD1315 is register-compatible +// with SSD1306, so USE_SSD1306 initializes the controller correctly. +// +// Viewport: the physical panel is 72×40, mapped into the SSD1315's 128×64 +// GDDRAM at columns 28..99, pages 3..7 (rows 24..63). The firmware handles +// this by: +// * asking the library for GEOMETRY_72_40, which sets the framebuffer to +// 72×40 and emits the right SETMULTIPLEX (39) / SETCOMPINS at init; +// * relying on SSD1306Wire's built-in horizontal auto-centering +// ((128 - width) / 2 = 28), so no horizontal shim is needed; +// * calling SSD1306Wire::setYOffset(3) in Screen.cpp when +// OLED_Y_OFFSET_PAGES is defined — this shifts every PAGEADDR write by +// three pages (24 rows) so data lands on the visible rows. +// ─────────────────────────────────────────────────────────────────────────── +#define HAS_SCREEN 1 +#define USE_SSD1306 +#define OLED_GEOMETRY_OVERRIDE GEOMETRY_72_40 +#define OLED_Y_OFFSET_PAGES 3 +#define OLED_TINY + +// Controls power 3V3 for all peripherals (GPS + LoRa + Sensor) +#define PIN_POWER_EN (0 + 30) // RT9080_EN + +// SPI1 is unused (no external SPI display). Keep declarations for the core. +#define PIN_SPI1_MISO (-1) +#define PIN_SPI1_MOSI (-1) +#define PIN_SPI1_SCK (-1) + +// GPS pins +#define GPS_L76K +#define GPS_BAUDRATE 9600 +#define HAS_GPS 1 + +#define PIN_GPS_EN (32 + 15) // GPS_EN: P1.15 - GPS power enable +#define GPS_EN_ACTIVE 1 +#define PIN_GPS_STANDBY (0 + 25) // GPS_WAKE_UP: P0.25 - wakeup pin +#define PIN_GPS_PPS (0 + 23) // GPS_1PPS: P0.23 +#define GPS_RX_PIN (0 + 19) // MCU RX ← GPS's TX (vendor GPS_UART_TX / P0.19) +#define GPS_TX_PIN (0 + 21) // MCU TX → GPS's RX (vendor GPS_UART_RX / P0.21) +#define PIN_GPS_RESET (0 + 29) // GPS_RF_EN: GPS RF enable / reset + +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN + +// SPI Interfaces (LoRa on SPI0) +#define SPI_INTERFACES_COUNT 2 + +// For LORA, SPI 0 +#define PIN_SPI_MISO (0 + 17) +#define PIN_SPI_MOSI (0 + 15) +#define PIN_SPI_SCK (0 + 13) + +// Battery +// The battery sense is hooked to PIN_A0 (P0.2) via a divider controlled by ADC_CTRL. +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER (2.0F) + +// Buzzer (PWM output, passive piezo) +#define PIN_BUZZER (32 + 6) // BUZZER_DATA: P1.6 + +// ─────────────────────────────────────────────────────────────────────────── +// I²S speaker (MAX98357 Class-D amp). Stereo I²S data path. +// Not supported on nrf52. These defines exist for out-of-tree code only. +// ─────────────────────────────────────────────────────────────────────────── +#define SPEAKER_EN (32 + 11) // P1.11 - amp main enable +#define SPEAKER_EN_2 (0 + 3) // P0.3 - secondary enable (vendor firmware toggles both) +#define SPEAKER_BCLK (0 + 16) // P0.16 - I2S bit clock +#define SPEAKER_DATA (0 + 20) // P0.20 - I2S data (SDOUT) +#define SPEAKER_WS_LRCK (0 + 22) // P0.22 - I2S word select / LRCK + +// ─────────────────────────────────────────────────────────────────────────── +// PDM microphone (ST MP34DT05). +// TODO to enable a mic path: +// Use Adafruit nRF52 core's built-in PDM.h wrapper (Arduino-compatible +// API exists on nRF52840). Clock on MIC_SCLK, data on MIC_DATA. +// ─────────────────────────────────────────────────────────────────────────── +#define MIC_SCLK (32 + 3) // P1.3 - PDM clock (MIC_SCLK on vendor header) +#define MIC_DATA (32 + 5) // P1.5 - PDM data (MIC_DATA on vendor header) + +#define SERIAL_PRINT_PORT 0 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From e2aa44ec5440f2b8b455acdaf748561371f10f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 19 May 2026 09:31:04 +0200 Subject: [PATCH 18/21] T-Echo-Card support (#10267) # Conflicts: # src/graphics/draw/UIRenderer.cpp --- src/graphics/Screen.cpp | 11 +- src/graphics/ScreenFonts.h | 2 +- src/graphics/SharedUIDisplay.cpp | 2 +- src/graphics/draw/DebugRenderer.cpp | 4 +- src/graphics/draw/MenuHandler.cpp | 2 +- src/graphics/draw/NodeListRenderer.cpp | 8 +- src/graphics/draw/NotificationRenderer.cpp | 2 +- src/graphics/draw/UIRenderer.cpp | 12 +- src/graphics/images.h | 2 +- src/main.cpp | 5 + src/mesh/NodeDB.cpp | 5 +- src/modules/ExternalNotificationModule.cpp | 10 + src/modules/ExternalNotificationModule.h | 17 ++ src/modules/StatusLEDModule.cpp | 33 +++ src/modules/StatusLEDModule.h | 26 +++ src/nimble/NimbleBluetooth.cpp | 2 +- variants/esp32c6/m5stack_unitc6l/variant.h | 3 + variants/nrf52840/t-echo-card/platformio.ini | 12 ++ variants/nrf52840/t-echo-card/variant.cpp | 66 ++++++ variants/nrf52840/t-echo-card/variant.h | 202 +++++++++++++++++++ 20 files changed, 403 insertions(+), 23 deletions(-) create mode 100644 variants/nrf52840/t-echo-card/platformio.ini create mode 100644 variants/nrf52840/t-echo-card/variant.cpp create mode 100644 variants/nrf52840/t-echo-card/variant.h diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 60e1c43a6..f51a6ee9e 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -353,6 +353,11 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#if defined(OLED_Y_OFFSET_PAGES) + // Panels whose active window does not start at GDDRAM row 0 (e.g. 72x40 + // modules on pages 3..7) need a fixed vertical page shift on every write. + static_cast(dispdev)->setYOffset(OLED_Y_OFFSET_PAGES); +#endif #elif defined(USE_SPISSD1306) dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48); if (!dispdev->init()) { @@ -834,7 +839,7 @@ int32_t Screen::runOnce() #ifndef DISABLE_WELCOME_UNSET if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) menuHandler::LoraRegionPicker(); #else menuHandler::OnboardMessage(); @@ -1058,7 +1063,7 @@ void Screen::setFrames(FrameFocus focus) #if defined(DISPLAY_CLOCK_FRAME) if (!hiddenFrames.clock) { fsi.positions.clock = numframes; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame; #else normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame @@ -1511,7 +1516,7 @@ void Screen::showFrame(FrameDirection direction) void Screen::setFastFramerate() { -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) dispdev->clear(); dispdev->display(); #endif diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 26276edb2..c6689d0d1 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -96,7 +96,7 @@ #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 #define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28 #define FONT_LARGE FONT_LARGE_LOCAL // Height: 28 -#elif defined(M5STACK_UNITC6L) +#elif defined(OLED_TINY) #define FONT_SMALL FONT_SMALL_LOCAL // Height: 13 #define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13 #define FONT_LARGE FONT_SMALL_LOCAL // Height: 13 diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index 032b14dfa..7ad0b93bb 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -161,7 +161,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti int batteryX = 1; int batteryY = HEADER_OFFSET_Y + 1; -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === Battery Icons === if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging batteryX += 1; diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 6b26abe7f..6472f3e5e 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -449,7 +449,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], frequencyslot); -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === Fifth Row: Channel Utilization === const char *chUtil = "ChUtil:"; char chUtilPercentage[10]; @@ -569,7 +569,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x // Label display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawString(labelX, getTextPositions(display)[line], label); -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // Bar int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2; display->setColor(WHITE); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index a1d49946f..24302c1db 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -491,7 +491,7 @@ void menuHandler::TZPicker() void menuHandler::clockMenu() { -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) static const char *optionsArray[] = {"Back", "Time Format", "Timezone"}; #else static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"}; diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 98644ee3b..d7f0a1483 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -21,7 +21,7 @@ extern bool haveGlyphs(const char *str); // Global screen instance extern graphics::Screen *screen; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) static uint32_t lastSwitchTime = 0; #endif namespace graphics @@ -670,7 +670,7 @@ void drawDynamicListScreen_Nodes(OLEDDisplay *display, OLEDDisplayUiState *state unsigned long now = millis(); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->clear(); if (now - lastSwitchTime >= 3000) { display->display(); @@ -706,7 +706,7 @@ void drawDynamicListScreen_Location(OLEDDisplay *display, OLEDDisplayUiState *st unsigned long now = millis(); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->clear(); if (now - lastSwitchTime >= 3000) { display->display(); @@ -771,7 +771,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, double lat = DegD(ourNode->position.latitude_i); double lon = DegD(ourNode->position.longitude_i); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->clear(); uint32_t now = millis(); if (now - lastSwitchTime >= 2000) { diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index 31eb2c3c8..3704dcf79 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -580,7 +580,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay } int16_t boxTop = (display->height() / 2) - (boxHeight / 2); boxHeight += (currentResolution == ScreenResolution::High) ? 2 : 1; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) if (visibleTotalLines == 1) { boxTop += 25; } diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index e3a4d13a2..92cc59a9a 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -20,7 +20,7 @@ // External variables extern graphics::Screen *screen; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) static uint32_t lastSwitchTime = 0; #endif namespace graphics @@ -304,7 +304,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite) return; display->clear(); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) uint32_t now = millis(); if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒 { @@ -518,7 +518,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i if (seenStr[0]) { display->drawString(x, getTextPositions(display)[line++], seenStr); } -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === 4. Uptime (only show if metric is present) === char uptimeStr[32] = ""; if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) { @@ -795,7 +795,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta } #endif -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) line += 1; // === Node Identity === @@ -1092,7 +1092,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED // needs to be drawn relative to x and y // draw centered icon left to right and centered above the one line of app text -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits); display->setFont(FONT_MEDIUM); display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -1243,7 +1243,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU } display->drawString(x, getTextPositions(display)[line++], altitudeLine); } -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === Draw Compass if heading is valid === if (validHeading) { // --- Compass Rendering: landscape (wide) screens use original side-aligned logic --- diff --git a/src/graphics/images.h b/src/graphics/images.h index 66fcbc79c..f11ad5686 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -318,7 +318,7 @@ const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f}; #define connection_icon_height 5 const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36}; -#ifdef M5STACK_UNITC6L +#ifdef OLED_TINY #include "img/icon_small.xbm" #else #include "img/icon.xbm" diff --git a/src/main.cpp b/src/main.cpp index 2f4b12437..dab965c4c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -741,6 +741,11 @@ void setup() } } #endif +#ifdef OLED_GEOMETRY_OVERRIDE + // Per-variant geometry (e.g. 72x40 micro-OLEDs). Takes precedence over the + // default GEOMETRY_128_64 set at the top of setup(). + screen_geometry = OLED_GEOMETRY_OVERRIDE; +#endif #endif #if !MESHTASTIC_EXCLUDE_I2C diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d35e0a38a..3ce78513c 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -836,7 +836,8 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_store_forward = true; moduleConfig.has_telemetry = true; moduleConfig.has_external_notification = true; -#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION) +#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION) || \ + defined(NEOPIXEL_STATUS_NOTIFICATION_PIN) moduleConfig.external_notification.enabled = true; #endif #if defined(PIN_BUZZER) @@ -857,7 +858,7 @@ void NodeDB::installDefaultModuleConfig() #endif #if defined(PIN_VIBRATION) moduleConfig.external_notification.nag_timeout = 2; -#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) +#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) || defined(NEOPIXEL_STATUS_NOTIFICATION_PIN) moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; #endif diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 16ccdd744..0a1c4a6dd 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -206,6 +206,10 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on) #ifdef PCA_LED_NOTIFICATION io.digitalWrite(PCA_LED_NOTIFICATION, on); +#endif +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN + notificationPixel.setPixelColor(0, on ? NEOPIXEL_STATUS_NOTIFICATION_COLOR : 0); + notificationPixel.show(); #endif break; } @@ -324,6 +328,12 @@ ExternalNotificationModule::ExternalNotificationModule() LOG_INFO("Use Pin %i in digital mode", output); pinMode(output, OUTPUT); } +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN + LOG_INFO("Use WS2812 on GPIO %d as notification LED", NEOPIXEL_STATUS_NOTIFICATION_PIN); + notificationPixel.begin(); + notificationPixel.clear(); + notificationPixel.show(); +#endif setExternalState(0, false); externalTurnedOn[0] = 0; if (moduleConfig.external_notification.output_vibra) { diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 94b021360..8781c1ca8 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -10,6 +10,19 @@ extern AmbientLightingThread *ambientLightingThread; #endif +// Drive a single WS2812 as the notification LED (M1/M2-style LED_NOTIFICATION +// but addressable). A variant defines NEOPIXEL_STATUS_NOTIFICATION_PIN to +// enable. Colour defaults to green but can be overridden. +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN +#include +#ifndef NEOPIXEL_STATUS_TYPE +#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800) +#endif +#ifndef NEOPIXEL_STATUS_NOTIFICATION_COLOR +#define NEOPIXEL_STATUS_NOTIFICATION_COLOR 0x00FF00 // green +#endif +#endif + #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) #include #else @@ -38,6 +51,10 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency: CallbackObserver(this, &ExternalNotificationModule::handleInputEvent); uint32_t output = 0; +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN + Adafruit_NeoPixel notificationPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_NOTIFICATION_PIN, NEOPIXEL_STATUS_TYPE); +#endif + public: ExternalNotificationModule(); diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 4ea34fb52..f3a0e7a03 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -17,8 +17,29 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule") if (inputBroker) inputObserver.observe(inputBroker); #endif +#ifdef NEOPIXEL_STATUS_POWER_PIN + powerPixel.begin(); + powerPixel.clear(); + powerPixel.show(); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + pairingPixel.begin(); + pairingPixel.clear(); + pairingPixel.show(); +#endif } +// Helper: write a 1-pixel NeoPixel strand to `color` when stateOn, else clear. +// Kept as a static inline here (rather than a member) so it compiles out +// completely when no NeoPixel status pins are defined. +#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN) +static inline void writeStatusPixel(Adafruit_NeoPixel &pixel, uint32_t color, bool stateOn) +{ + pixel.setPixelColor(0, stateOn ? color : 0); + pixel.show(); +} +#endif + int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) { switch (arg->getStatusType()) { @@ -176,6 +197,12 @@ int32_t StatusLEDModule::runOnce() #ifdef LED_PAIRING digitalWrite(LED_PAIRING, PAIRING_LED_state); #endif +#ifdef NEOPIXEL_STATUS_POWER_PIN + writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, CHARGE_LED_state == LED_STATE_ON); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, PAIRING_LED_state == LED_STATE_ON); +#endif #ifdef RGB_LED_POWER if (!config.device.led_heartbeat_disabled) { @@ -225,6 +252,12 @@ void StatusLEDModule::setPowerLED(bool LEDon) #ifdef LED_PAIRING digitalWrite(LED_PAIRING, ledState); #endif +#ifdef NEOPIXEL_STATUS_POWER_PIN + writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, LEDon); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, LEDon); +#endif #ifdef Battery_LED_1 digitalWrite(Battery_LED_1, ledState); diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h index f66a536f6..f20198e39 100644 --- a/src/modules/StatusLEDModule.h +++ b/src/modules/StatusLEDModule.h @@ -13,6 +13,25 @@ #include "input/InputBroker.h" #endif +// WS2812/NeoPixel status-LED support. A variant may define +// NEOPIXEL_STATUS_POWER_PIN (required to enable the power/charge pixel) +// NEOPIXEL_STATUS_POWER_COLOR (optional, default red 0xFF0000) +// NEOPIXEL_STATUS_PAIRING_PIN / _COLOR (default blue 0x0000FF) +// Each pixel is a standalone 1-LED strand on its own GPIO — this mirrors how +// boards like the LilyGo T-Echo-Card expose three independent WS2812s. +#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN) +#include +#ifndef NEOPIXEL_STATUS_TYPE +#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800) +#endif +#ifndef NEOPIXEL_STATUS_POWER_COLOR +#define NEOPIXEL_STATUS_POWER_COLOR 0xFF0000 // red +#endif +#ifndef NEOPIXEL_STATUS_PAIRING_COLOR +#define NEOPIXEL_STATUS_PAIRING_COLOR 0x0000FF // blue +#endif +#endif + class StatusLEDModule : private concurrency::OSThread { bool slowTrack = false; @@ -27,6 +46,13 @@ class StatusLEDModule : private concurrency::OSThread void setPowerLED(bool); +#ifdef NEOPIXEL_STATUS_POWER_PIN + Adafruit_NeoPixel powerPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_POWER_PIN, NEOPIXEL_STATUS_TYPE); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + Adafruit_NeoPixel pairingPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_PAIRING_PIN, NEOPIXEL_STATUS_TYPE); +#endif + protected: unsigned int my_interval = 1000; // interval in millisconds virtual int32_t runOnce() override; diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 3bb4ce817..d4cb1d9ef 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -610,7 +610,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); display->drawString(x_offset + x, y_offset + y, "Bluetooth"); -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) display->setFont(FONT_SMALL); y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5; display->drawString(x_offset + x, y_offset + y, "Enter this code"); diff --git a/variants/esp32c6/m5stack_unitc6l/variant.h b/variants/esp32c6/m5stack_unitc6l/variant.h index 1654ee590..576d4e114 100644 --- a/variants/esp32c6/m5stack_unitc6l/variant.h +++ b/variants/esp32c6/m5stack_unitc6l/variant.h @@ -48,6 +48,9 @@ void c6l_init(); #define SSD1306_RESET 15 // #define OLED_DG 1 #endif +// Tiny OLED panel — opts into compile-time layout/font/feature substitutions +// gated on OLED_TINY across the graphics stack. +#define OLED_TINY #define SCREEN_TRANSITION_FRAMERATE 10 #define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness diff --git a/variants/nrf52840/t-echo-card/platformio.ini b/variants/nrf52840/t-echo-card/platformio.ini new file mode 100644 index 000000000..bc012d6e1 --- /dev/null +++ b/variants/nrf52840/t-echo-card/platformio.ini @@ -0,0 +1,12 @@ +[env:t-echo-card] +extends = nrf52840_base +board = t-echo +board_level = extra +debug_tool = jlink + +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/t-echo-card + -D PRIVATE_HW + -D T_ECHO_CARD + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo-card> diff --git a/variants/nrf52840/t-echo-card/variant.cpp b/variants/nrf52840/t-echo-card/variant.cpp new file mode 100644 index 000000000..e82a63f8e --- /dev/null +++ b/variants/nrf52840/t-echo-card/variant.cpp @@ -0,0 +1,66 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "Arduino.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // No plain GPIO LEDs on this board (only WS2812 addressable LEDs, not driven here). +} + +// Reproduces the vendor firmware's boot sequence from +// examples/original_test/original_test.ino. Runs before Meshtastic touches +// PIN_POWER_EN, so the RT9080 LDO gets a clean reset pulse and peripherals +// whose EN pins must be LOW at boot (GPS_EN, GPS_RF_EN, BUZZER) aren't left +// floating while the 3V3 rail is ramping. +void earlyInitVariant() +{ + // 3.3V rail: toggle RT9080_EN HIGH → LOW → HIGH with 100 ms dwell so the + // LDO enters enable from a known state. The single-shot HIGH in main.cpp + // is not enough on this hardware — if the chip was in a half-enabled + // state from a previous reset, the rail brown-outs once LoRa TX fires. + pinMode(PIN_POWER_EN, OUTPUT); + digitalWrite(PIN_POWER_EN, HIGH); + delay(100); + digitalWrite(PIN_POWER_EN, LOW); + delay(100); + digitalWrite(PIN_POWER_EN, HIGH); + delay(100); + + // Park peripherals with active-high enables LOW so they don't sink + // current while the rest of setup() runs. + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, LOW); + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, LOW); + pinMode(PIN_BUZZER, OUTPUT); + digitalWrite(PIN_BUZZER, LOW); +} diff --git a/variants/nrf52840/t-echo-card/variant.h b/variants/nrf52840/t-echo-card/variant.h new file mode 100644 index 000000000..37fa28d89 --- /dev/null +++ b/variants/nrf52840/t-echo-card/variant.h @@ -0,0 +1,202 @@ +// Variant definition for LilyGo T-Echo-Card (nRF52840) + +#ifndef _VARIANT_T_ECHO_CARD_ +#define _VARIANT_T_ECHO_CARD_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32kHz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs - board only exposes 3x WS2812 addressable LEDs. No plain GPIO LEDs. +// Intentionally do not define PIN_LED1 on this variant, so nRF52 platform +// code does not auto-enable a nonexistent GPIO power/status LED. +#define LED_STATE_ON 1 + +// Three independent WS2812 data lines (one LED per line, not a daisy chain). +// Each is driven as a 1-pixel NeoPixel by StatusLEDModule / ExternalNotification, +// assigns LED_POWER (red) and LED_NOTIFICATION (green). +#define WS2812_DATA_1 (32 + 7) // P1.7 - charge/heartbeat (red) +#define WS2812_DATA_2 (32 + 12) // P1.12 - external notification (green) +#define WS2812_DATA_3 (0 + 28) // P0.28 - BLE pairing (blue) + +// Wire each WS2812 to a status role. Colour defaults are scaled to 25% +// brightness (0x40) — the bare-die WS2812s on this board are very bright at +// full intensity in a close-range enclosure. +#define NEOPIXEL_STATUS_POWER_PIN WS2812_DATA_1 +#define NEOPIXEL_STATUS_NOTIFICATION_PIN WS2812_DATA_2 +#define NEOPIXEL_STATUS_PAIRING_PIN WS2812_DATA_3 +#define NEOPIXEL_STATUS_POWER_COLOR 0x400000 // red @ 25% +#define NEOPIXEL_STATUS_NOTIFICATION_COLOR 0x004000 // green @ 25% +#define NEOPIXEL_STATUS_PAIRING_COLOR 0x000040 // blue @ 25% + +// The charger IC does not blink on its own; let StatusLEDModule do the +// software blink while charging +// If left defined: hardware would be expected to handle the charging pulse. +// #define POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING + +// Buttons +#define PIN_BUTTON1 (32 + 10) // KEY_1: P1.10 + +#define BUTTON_CLICK_MS 400 + +// Analog pins +#define PIN_A0 (0 + 2) // Battery ADC (BATTERY_ADC_DATA) + +#define BATTERY_PIN PIN_A0 + +static const uint8_t A0 = PIN_A0; + +#define ADC_RESOLUTION 14 + +// BATTERY_MEASUREMENT_CONTROL - enable divider for battery reading +#define ADC_CTRL (0 + 31) +#define ADC_CTRL_ENABLED HIGH + +// NFC placeholders, not used +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +// Wire Interfaces (IIC_1 on the vendor header) +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (32 + 4) // IIC_1_SDA: P1.4 +#define PIN_WIRE_SCL (32 + 2) // IIC_1_SCL: P1.2 + +// External serial flash ZD25WQ32CEIGR +// QSPI Pins +#define PIN_QSPI_SCK (0 + 4) +#define PIN_QSPI_CS (0 + 12) +#define PIN_QSPI_IO0 (0 + 6) // MOSI if using two bit interface +#define PIN_QSPI_IO1 (0 + 8) // MISO if using two bit interface +#define PIN_QSPI_IO2 (32 + 9) // WP +#define PIN_QSPI_IO3 (0 + 26) // HOLD + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES ZD25WQ32CEIGR +#define EXTERNAL_FLASH_USE_QSPI + +// Lora S62F (SX1262) +#define USE_SX1262 +#define SX126X_CS (0 + 11) +#define SX126X_DIO1 (32 + 8) +#define SX126X_DIO2 (0 + 5) +#define SX126X_BUSY (0 + 14) +#define SX126X_RESET (0 + 7) +#define SX126X_RXEN (32 + 1) // SX1262_RF_VC2 +#define SX126X_TXEN (0 + 27) // SX1262_RF_VC1 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// ─────────────────────────────────────────────────────────────────────────── +// OLED display: SSD1315 on I2C @ 0x3C (IIC_1). SSD1315 is register-compatible +// with SSD1306, so USE_SSD1306 initializes the controller correctly. +// +// Viewport: the physical panel is 72×40, mapped into the SSD1315's 128×64 +// GDDRAM at columns 28..99, pages 3..7 (rows 24..63). The firmware handles +// this by: +// * asking the library for GEOMETRY_72_40, which sets the framebuffer to +// 72×40 and emits the right SETMULTIPLEX (39) / SETCOMPINS at init; +// * relying on SSD1306Wire's built-in horizontal auto-centering +// ((128 - width) / 2 = 28), so no horizontal shim is needed; +// * calling SSD1306Wire::setYOffset(3) in Screen.cpp when +// OLED_Y_OFFSET_PAGES is defined — this shifts every PAGEADDR write by +// three pages (24 rows) so data lands on the visible rows. +// ─────────────────────────────────────────────────────────────────────────── +#define HAS_SCREEN 1 +#define USE_SSD1306 +#define OLED_GEOMETRY_OVERRIDE GEOMETRY_72_40 +#define OLED_Y_OFFSET_PAGES 3 +#define OLED_TINY + +// Controls power 3V3 for all peripherals (GPS + LoRa + Sensor) +#define PIN_POWER_EN (0 + 30) // RT9080_EN + +// SPI1 is unused (no external SPI display). Keep declarations for the core. +#define PIN_SPI1_MISO (-1) +#define PIN_SPI1_MOSI (-1) +#define PIN_SPI1_SCK (-1) + +// GPS pins +#define GPS_L76K +#define GPS_BAUDRATE 9600 +#define HAS_GPS 1 + +#define PIN_GPS_EN (32 + 15) // GPS_EN: P1.15 - GPS power enable +#define GPS_EN_ACTIVE 1 +#define PIN_GPS_STANDBY (0 + 25) // GPS_WAKE_UP: P0.25 - wakeup pin +#define PIN_GPS_PPS (0 + 23) // GPS_1PPS: P0.23 +#define GPS_RX_PIN (0 + 19) // MCU RX ← GPS's TX (vendor GPS_UART_TX / P0.19) +#define GPS_TX_PIN (0 + 21) // MCU TX → GPS's RX (vendor GPS_UART_RX / P0.21) +#define PIN_GPS_RESET (0 + 29) // GPS_RF_EN: GPS RF enable / reset + +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN + +// SPI Interfaces (LoRa on SPI0) +#define SPI_INTERFACES_COUNT 2 + +// For LORA, SPI 0 +#define PIN_SPI_MISO (0 + 17) +#define PIN_SPI_MOSI (0 + 15) +#define PIN_SPI_SCK (0 + 13) + +// Battery +// The battery sense is hooked to PIN_A0 (P0.2) via a divider controlled by ADC_CTRL. +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER (2.0F) + +// Buzzer (PWM output, passive piezo) +#define PIN_BUZZER (32 + 6) // BUZZER_DATA: P1.6 + +// ─────────────────────────────────────────────────────────────────────────── +// I²S speaker (MAX98357 Class-D amp). Stereo I²S data path. +// Not supported on nrf52. These defines exist for out-of-tree code only. +// ─────────────────────────────────────────────────────────────────────────── +#define SPEAKER_EN (32 + 11) // P1.11 - amp main enable +#define SPEAKER_EN_2 (0 + 3) // P0.3 - secondary enable (vendor firmware toggles both) +#define SPEAKER_BCLK (0 + 16) // P0.16 - I2S bit clock +#define SPEAKER_DATA (0 + 20) // P0.20 - I2S data (SDOUT) +#define SPEAKER_WS_LRCK (0 + 22) // P0.22 - I2S word select / LRCK + +// ─────────────────────────────────────────────────────────────────────────── +// PDM microphone (ST MP34DT05). +// TODO to enable a mic path: +// Use Adafruit nRF52 core's built-in PDM.h wrapper (Arduino-compatible +// API exists on nRF52840). Clock on MIC_SCLK, data on MIC_DATA. +// ─────────────────────────────────────────────────────────────────────────── +#define MIC_SCLK (32 + 3) // P1.3 - PDM clock (MIC_SCLK on vendor header) +#define MIC_DATA (32 + 5) // P1.5 - PDM data (MIC_DATA on vendor header) + +#define SERIAL_PRINT_PORT 0 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From 0f9eb86830c82c22b525d5e6f3130097345beef6 Mon Sep 17 00:00:00 2001 From: Riker Date: Tue, 19 May 2026 14:31:42 +0800 Subject: [PATCH 19/21] Enabled SX_LNA_EN by default (#10469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enabled SX_LNA_EN by default * Update I2C configuration for IO direction and pull settings --------- Co-authored-by: Thomas Göttgens --- variants/esp32c6/m5stack_unitc6l/variant.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/variants/esp32c6/m5stack_unitc6l/variant.cpp b/variants/esp32c6/m5stack_unitc6l/variant.cpp index 8e26b4ab7..7dc5785f6 100644 --- a/variants/esp32c6/m5stack_unitc6l/variant.cpp +++ b/variants/esp32c6/m5stack_unitc6l/variant.cpp @@ -52,23 +52,25 @@ void c6l_init() vTaskDelay(10 / portTICK_PERIOD_MS); i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, &in_data); vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11000000); // 0: input 1: output + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11100000); // P5,P6,P7 as outputs vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00111100); // 使用到的引脚关闭High-Impedance + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00011100); // High-Impedance vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11000011); // pull up/down select, 0 down, 1 up + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11100011); // pull up/down select, 0 down, 1 up vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11000011); // pull up/down enable, 0 disable, 1 enable + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11100011); // pull up/down enable, 0 disable, 1 enable vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 默认高电平, 按键按下触发中断 + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 default to high level; button press triggers the interrupt vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 中断使能 0 enable, 1 disable + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 interrupts enabled (0 = enable, 1 = disable) vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // 默认输出为0 + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // default output is 0 + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // read IRQ_STA to clear the flag vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // 读取IRQ_STA清除标志 i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, &in_data); - setbit(in_data, 6); // HIGH + setbit(in_data, 6); // enable SX_ANT_SW + setbit(in_data, 5); // enable SX_LNA_EN i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, in_data); } From 82aefd1af16648e73ba58e2a27d353855a9f3db6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 05:23:01 -0500 Subject: [PATCH 20/21] Upgrade trunk (#10503) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 77006cf99..0bbfbcf08 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -26,7 +26,7 @@ lint: - hadolint@2.14.0 - shfmt@3.6.0 - shellcheck@0.11.0 - - black@26.5.0 + - black@26.5.1 - git-diff-check - gitleaks@8.30.1 - clang-format@16.0.3 From 00ec69201d96d22baf54b649cc030b76c83b5014 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 19 May 2026 06:57:05 -0500 Subject: [PATCH 21/21] Develop protos should be on develop --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 45 ++++- .../generated/meshtastic/deviceonly.pb.cpp | 12 ++ src/mesh/generated/meshtastic/deviceonly.pb.h | 185 +++++++++++++----- src/mesh/generated/meshtastic/mesh.pb.h | 39 ++-- 5 files changed, 218 insertions(+), 65 deletions(-) diff --git a/protobufs b/protobufs index 59cb394dc..e978a1850 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 59cb394dcfc4432cb216358ca26e861c7d13f462 +Subproject commit e978a1850b905e05913c6ef6c73c1d3b79486d4a diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index e6f5110ad..82644bc2a 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -163,6 +163,41 @@ typedef struct _meshtastic_LockdownAuth { connection-level admin authorization, and reboot the device into the locked state. Always honoured regardless of current lock state. */ bool lock_now; + /* Optional per-boot uptime cap on the unlocked session, in seconds. + 0 = unlimited (token-only enforcement, suitable for unattended + tower / infrastructure nodes). + + When non-zero, the firmware arms an uptime timer at unlock. On + each expiry, while there is still boot-count budget, the firmware + decrements the on-flash boot count in place, revokes per- + connection admin auth (clients must re-authenticate to see + content), re-engages the screen lock, and re-arms the timer + without rebooting. Mesh routing keeps running across session + boundaries; only when the boot-count budget reaches zero does + the device hard-lock and reboot. + + Total exposure ceiling = ((resolved boot count) + 1) * max_session_seconds. + The +1 accounts for the initial passphrase-unlocked session + itself, since boots_remaining is the number of subsequent + session rolls (each consuming one boot from the rollback ledger). + The resolved boot count is the value the firmware writes into the + token at unlock time: the client-supplied boots_remaining when + non-zero, otherwise the firmware default (TOKEN_DEFAULT_BOOTS). + Note that boots_remaining == 0 in this message means "use firmware + default", NOT "zero boots" — a client computing the ceiling for + display should mirror that resolution rather than multiplying the + raw request value. + + The cap is persisted in the token, so it survives token-based + auto-unlock across reboots. Explicit operator Lock Now still + deletes the token and forces passphrase re-entry. + + Uses millis() (CPU uptime), not wall-clock time, so the cap is + immune to GPS spoofing, RTC backup-battery removal, and Faraday + cage isolation — none of those move the uptime counter. The only + way to reset the session clock is a reboot, which costs a boot + from the on-flash, HMAC-bound counter. */ + uint32_t max_session_seconds; } meshtastic_LockdownAuth; /* Parameters for setting up Meshtastic for ameteur radio usage */ @@ -486,7 +521,7 @@ extern "C" { #define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_default {_meshtastic_OTAMode_MIN, {0, {0}}} -#define meshtastic_LockdownAuth_init_default {{0, {0}}, 0, 0, 0} +#define meshtastic_LockdownAuth_init_default {{0, {0}}, 0, 0, 0, 0} #define meshtastic_HamParameters_init_default {"", 0, 0, ""} #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} #define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0} @@ -499,7 +534,7 @@ extern "C" { #define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}} -#define meshtastic_LockdownAuth_init_zero {{0, {0}}, 0, 0, 0} +#define meshtastic_LockdownAuth_init_zero {{0, {0}}, 0, 0, 0, 0} #define meshtastic_HamParameters_init_zero {"", 0, 0, ""} #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} #define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0} @@ -521,6 +556,7 @@ extern "C" { #define meshtastic_LockdownAuth_boots_remaining_tag 2 #define meshtastic_LockdownAuth_valid_until_epoch_tag 3 #define meshtastic_LockdownAuth_lock_now_tag 4 +#define meshtastic_LockdownAuth_max_session_seconds_tag 5 #define meshtastic_HamParameters_call_sign_tag 1 #define meshtastic_HamParameters_tx_power_tag 2 #define meshtastic_HamParameters_frequency_tag 3 @@ -717,7 +753,8 @@ X(a, STATIC, SINGULAR, BYTES, ota_hash, 2) X(a, STATIC, SINGULAR, BYTES, passphrase, 1) \ X(a, STATIC, SINGULAR, UINT32, boots_remaining, 2) \ X(a, STATIC, SINGULAR, UINT32, valid_until_epoch, 3) \ -X(a, STATIC, SINGULAR, BOOL, lock_now, 4) +X(a, STATIC, SINGULAR, BOOL, lock_now, 4) \ +X(a, STATIC, SINGULAR, UINT32, max_session_seconds, 5) #define meshtastic_LockdownAuth_CALLBACK NULL #define meshtastic_LockdownAuth_DEFAULT NULL @@ -832,7 +869,7 @@ extern const pb_msgdesc_t meshtastic_SHTXX_config_msg; #define meshtastic_AdminMessage_size 511 #define meshtastic_HamParameters_size 31 #define meshtastic_KeyVerificationAdmin_size 25 -#define meshtastic_LockdownAuth_size 48 +#define meshtastic_LockdownAuth_size 54 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 #define meshtastic_SCD30_config_size 27 #define meshtastic_SCD4X_config_size 29 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.cpp b/src/mesh/generated/meshtastic/deviceonly.pb.cpp index 5a9695702..558086637 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.cpp +++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp @@ -18,6 +18,18 @@ PB_BIND(meshtastic_NodeInfoLite, meshtastic_NodeInfoLite, AUTO) PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 2) +PB_BIND(meshtastic_NodePositionEntry, meshtastic_NodePositionEntry, AUTO) + + +PB_BIND(meshtastic_NodeTelemetryEntry, meshtastic_NodeTelemetryEntry, AUTO) + + +PB_BIND(meshtastic_NodeEnvironmentEntry, meshtastic_NodeEnvironmentEntry, AUTO) + + +PB_BIND(meshtastic_NodeStatusEntry, meshtastic_NodeStatusEntry, AUTO) + + PB_BIND(meshtastic_NodeDatabase, meshtastic_NodeDatabase, AUTO) diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 6d03dc643..7c14c3e0f 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -33,6 +33,8 @@ typedef struct _meshtastic_PositionLite { uint32_t time; /* TODO: REPLACE */ meshtastic_Position_LocSource location_source; + /* Indicates the bits of precision set by the sending node */ + uint32_t precision_bits; } meshtastic_PositionLite; typedef PB_BYTES_ARRAY_T(32) meshtastic_UserLite_public_key_t; @@ -63,43 +65,35 @@ typedef struct _meshtastic_UserLite { bool is_unmessagable; } meshtastic_UserLite; +typedef PB_BYTES_ARRAY_T(32) meshtastic_NodeInfoLite_public_key_t; typedef struct _meshtastic_NodeInfoLite { /* The node number */ uint32_t num; - /* The user info for this node */ - bool has_user; - meshtastic_UserLite user; - /* This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. - Position.time now indicates the last time we received a POSITION from that node. */ - bool has_position; - meshtastic_PositionLite position; /* Returns the Signal-to-noise ratio (SNR) of the last received message, as measured by the receiver. Return SNR of the last received message in dB */ float snr; /* Set to indicate the last time we received a packet from this node */ uint32_t last_heard; - /* The latest device metrics for the node. */ - bool has_device_metrics; - meshtastic_DeviceMetrics device_metrics; /* local channel index we heard that node on. Only populated if its not the default channel. */ uint8_t channel; - /* True if we witnessed the node over MQTT instead of LoRA transport */ - bool via_mqtt; /* Number of hops away from us this node is (0 if direct neighbor) */ bool has_hops_away; uint8_t hops_away; - /* True if node is in our favorites list - Persists between NodeDB internal clean ups */ - bool is_favorite; - /* True if node is in our ignored list - Persists between NodeDB internal clean ups */ - bool is_ignored; /* Last byte of the node number of the node that should be used as the next hop to reach this node. */ uint8_t next_hop; - /* Bitfield for storing booleans. - LSB 0 is_key_manually_verified - LSB 1 is_muted */ + /* Bitfield for storing booleans. See NODEINFO_BITFIELD_* in src/mesh/NodeDB.h. */ uint32_t bitfield; + /* A full name for this user, i.e. "Kevin Hester". */ + char long_name[25]; + /* A VERY short name, ideally two characters or an emoji. + Suitable for a tiny OLED screen. */ + char short_name[5]; + /* Hardware model the user's device is running. */ + meshtastic_HardwareModel hw_model; + /* The user's role in the mesh. */ + meshtastic_Config_DeviceConfig_Role role; + /* The public key of the user's device, for PKI-based encrypted DMs. */ + meshtastic_NodeInfoLite_public_key_t public_key; } meshtastic_NodeInfoLite; /* This message is never sent over the wire, but it is used for serializing DB @@ -143,6 +137,30 @@ typedef struct _meshtastic_DeviceState { meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12]; } meshtastic_DeviceState; +typedef struct _meshtastic_NodePositionEntry { + uint32_t num; + bool has_position; + meshtastic_PositionLite position; +} meshtastic_NodePositionEntry; + +typedef struct _meshtastic_NodeTelemetryEntry { + uint32_t num; + bool has_device_metrics; + meshtastic_DeviceMetrics device_metrics; +} meshtastic_NodeTelemetryEntry; + +typedef struct _meshtastic_NodeEnvironmentEntry { + uint32_t num; + bool has_environment_metrics; + meshtastic_EnvironmentMetrics environment_metrics; +} meshtastic_NodeEnvironmentEntry; + +typedef struct _meshtastic_NodeStatusEntry { + uint32_t num; + bool has_status; + meshtastic_StatusMessage status; +} meshtastic_NodeStatusEntry; + typedef struct _meshtastic_NodeDatabase { /* A version integer used to invalidate old save files when we make incompatible changes This integer is set at build time and is private to @@ -150,6 +168,12 @@ typedef struct _meshtastic_NodeDatabase { uint32_t version; /* New lite version of NodeDB to decrease memory footprint */ std::vector nodes; + /* Per-NodeNum satellite arrays. Constrained platforms (e.g. STM32WL) omit + these via MESHTASTIC_EXCLUDE_*DB build flags. */ + std::vector positions; + std::vector telemetry; + std::vector status; + std::vector environment; } meshtastic_NodeDatabase; /* The on-disk saved channels */ @@ -189,18 +213,26 @@ extern "C" { #endif /* Initializer values for message structs */ -#define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} +#define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, 0} #define meshtastic_UserLite_init_default {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0} -#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0} +#define meshtastic_NodeInfoLite_init_default {0, 0, 0, 0, false, 0, 0, 0, "", "", _meshtastic_HardwareModel_MIN, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} #define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} -#define meshtastic_NodeDatabase_init_default {0, {0}} +#define meshtastic_NodePositionEntry_init_default {0, false, meshtastic_PositionLite_init_default} +#define meshtastic_NodeTelemetryEntry_init_default {0, false, meshtastic_DeviceMetrics_init_default} +#define meshtastic_NodeEnvironmentEntry_init_default {0, false, meshtastic_EnvironmentMetrics_init_default} +#define meshtastic_NodeStatusEntry_init_default {0, false, meshtastic_StatusMessage_init_default} +#define meshtastic_NodeDatabase_init_default {0, {0}, {0}, {0}, {0}, {0}} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default} -#define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} +#define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, 0} #define meshtastic_UserLite_init_zero {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0} -#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0} +#define meshtastic_NodeInfoLite_init_zero {0, 0, 0, 0, false, 0, 0, 0, "", "", _meshtastic_HardwareModel_MIN, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}} #define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} -#define meshtastic_NodeDatabase_init_zero {0, {0}} +#define meshtastic_NodePositionEntry_init_zero {0, false, meshtastic_PositionLite_init_zero} +#define meshtastic_NodeTelemetryEntry_init_zero {0, false, meshtastic_DeviceMetrics_init_zero} +#define meshtastic_NodeEnvironmentEntry_init_zero {0, false, meshtastic_EnvironmentMetrics_init_zero} +#define meshtastic_NodeStatusEntry_init_zero {0, false, meshtastic_StatusMessage_init_zero} +#define meshtastic_NodeDatabase_init_zero {0, {0}, {0}, {0}, {0}, {0}} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_BackupPreferences_init_zero {0, 0, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero, false, meshtastic_ChannelFile_init_zero, false, meshtastic_User_init_zero} @@ -210,6 +242,7 @@ extern "C" { #define meshtastic_PositionLite_altitude_tag 3 #define meshtastic_PositionLite_time_tag 4 #define meshtastic_PositionLite_location_source_tag 5 +#define meshtastic_PositionLite_precision_bits_tag 6 #define meshtastic_UserLite_macaddr_tag 1 #define meshtastic_UserLite_long_name_tag 2 #define meshtastic_UserLite_short_name_tag 3 @@ -219,18 +252,17 @@ extern "C" { #define meshtastic_UserLite_public_key_tag 7 #define meshtastic_UserLite_is_unmessagable_tag 9 #define meshtastic_NodeInfoLite_num_tag 1 -#define meshtastic_NodeInfoLite_user_tag 2 -#define meshtastic_NodeInfoLite_position_tag 3 #define meshtastic_NodeInfoLite_snr_tag 4 #define meshtastic_NodeInfoLite_last_heard_tag 5 -#define meshtastic_NodeInfoLite_device_metrics_tag 6 #define meshtastic_NodeInfoLite_channel_tag 7 -#define meshtastic_NodeInfoLite_via_mqtt_tag 8 #define meshtastic_NodeInfoLite_hops_away_tag 9 -#define meshtastic_NodeInfoLite_is_favorite_tag 10 -#define meshtastic_NodeInfoLite_is_ignored_tag 11 #define meshtastic_NodeInfoLite_next_hop_tag 12 #define meshtastic_NodeInfoLite_bitfield_tag 13 +#define meshtastic_NodeInfoLite_long_name_tag 14 +#define meshtastic_NodeInfoLite_short_name_tag 15 +#define meshtastic_NodeInfoLite_hw_model_tag 16 +#define meshtastic_NodeInfoLite_role_tag 17 +#define meshtastic_NodeInfoLite_public_key_tag 18 #define meshtastic_DeviceState_my_node_tag 2 #define meshtastic_DeviceState_owner_tag 3 #define meshtastic_DeviceState_receive_queue_tag 5 @@ -240,8 +272,20 @@ extern "C" { #define meshtastic_DeviceState_did_gps_reset_tag 11 #define meshtastic_DeviceState_rx_waypoint_tag 12 #define meshtastic_DeviceState_node_remote_hardware_pins_tag 13 +#define meshtastic_NodePositionEntry_num_tag 1 +#define meshtastic_NodePositionEntry_position_tag 2 +#define meshtastic_NodeTelemetryEntry_num_tag 1 +#define meshtastic_NodeTelemetryEntry_device_metrics_tag 2 +#define meshtastic_NodeEnvironmentEntry_num_tag 1 +#define meshtastic_NodeEnvironmentEntry_environment_metrics_tag 2 +#define meshtastic_NodeStatusEntry_num_tag 1 +#define meshtastic_NodeStatusEntry_status_tag 2 #define meshtastic_NodeDatabase_version_tag 1 #define meshtastic_NodeDatabase_nodes_tag 2 +#define meshtastic_NodeDatabase_positions_tag 3 +#define meshtastic_NodeDatabase_telemetry_tag 4 +#define meshtastic_NodeDatabase_status_tag 5 +#define meshtastic_NodeDatabase_environment_tag 6 #define meshtastic_ChannelFile_channels_tag 1 #define meshtastic_ChannelFile_version_tag 2 #define meshtastic_BackupPreferences_version_tag 1 @@ -257,7 +301,8 @@ X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \ X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \ X(a, STATIC, SINGULAR, INT32, altitude, 3) \ X(a, STATIC, SINGULAR, FIXED32, time, 4) \ -X(a, STATIC, SINGULAR, UENUM, location_source, 5) +X(a, STATIC, SINGULAR, UENUM, location_source, 5) \ +X(a, STATIC, SINGULAR, UINT32, precision_bits, 6) #define meshtastic_PositionLite_CALLBACK NULL #define meshtastic_PositionLite_DEFAULT NULL @@ -275,23 +320,19 @@ X(a, STATIC, OPTIONAL, BOOL, is_unmessagable, 9) #define meshtastic_NodeInfoLite_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, num, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ -X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ -X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ X(a, STATIC, SINGULAR, UINT32, channel, 7) \ -X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ X(a, STATIC, OPTIONAL, UINT32, hops_away, 9) \ -X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) \ -X(a, STATIC, SINGULAR, BOOL, is_ignored, 11) \ X(a, STATIC, SINGULAR, UINT32, next_hop, 12) \ -X(a, STATIC, SINGULAR, UINT32, bitfield, 13) +X(a, STATIC, SINGULAR, UINT32, bitfield, 13) \ +X(a, STATIC, SINGULAR, STRING, long_name, 14) \ +X(a, STATIC, SINGULAR, STRING, short_name, 15) \ +X(a, STATIC, SINGULAR, UENUM, hw_model, 16) \ +X(a, STATIC, SINGULAR, UENUM, role, 17) \ +X(a, STATIC, SINGULAR, BYTES, public_key, 18) #define meshtastic_NodeInfoLite_CALLBACK NULL #define meshtastic_NodeInfoLite_DEFAULT NULL -#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite -#define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite -#define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics #define meshtastic_DeviceState_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \ @@ -312,13 +353,49 @@ X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 13) #define meshtastic_DeviceState_rx_waypoint_MSGTYPE meshtastic_MeshPacket #define meshtastic_DeviceState_node_remote_hardware_pins_MSGTYPE meshtastic_NodeRemoteHardwarePin +#define meshtastic_NodePositionEntry_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, num, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, position, 2) +#define meshtastic_NodePositionEntry_CALLBACK NULL +#define meshtastic_NodePositionEntry_DEFAULT NULL +#define meshtastic_NodePositionEntry_position_MSGTYPE meshtastic_PositionLite + +#define meshtastic_NodeTelemetryEntry_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, num, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 2) +#define meshtastic_NodeTelemetryEntry_CALLBACK NULL +#define meshtastic_NodeTelemetryEntry_DEFAULT NULL +#define meshtastic_NodeTelemetryEntry_device_metrics_MSGTYPE meshtastic_DeviceMetrics + +#define meshtastic_NodeEnvironmentEntry_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, num, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, environment_metrics, 2) +#define meshtastic_NodeEnvironmentEntry_CALLBACK NULL +#define meshtastic_NodeEnvironmentEntry_DEFAULT NULL +#define meshtastic_NodeEnvironmentEntry_environment_metrics_MSGTYPE meshtastic_EnvironmentMetrics + +#define meshtastic_NodeStatusEntry_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, num, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, status, 2) +#define meshtastic_NodeStatusEntry_CALLBACK NULL +#define meshtastic_NodeStatusEntry_DEFAULT NULL +#define meshtastic_NodeStatusEntry_status_MSGTYPE meshtastic_StatusMessage + #define meshtastic_NodeDatabase_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, version, 1) \ -X(a, CALLBACK, REPEATED, MESSAGE, nodes, 2) +X(a, CALLBACK, REPEATED, MESSAGE, nodes, 2) \ +X(a, CALLBACK, REPEATED, MESSAGE, positions, 3) \ +X(a, CALLBACK, REPEATED, MESSAGE, telemetry, 4) \ +X(a, CALLBACK, REPEATED, MESSAGE, status, 5) \ +X(a, CALLBACK, REPEATED, MESSAGE, environment, 6) extern bool meshtastic_NodeDatabase_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field); #define meshtastic_NodeDatabase_CALLBACK meshtastic_NodeDatabase_callback #define meshtastic_NodeDatabase_DEFAULT NULL #define meshtastic_NodeDatabase_nodes_MSGTYPE meshtastic_NodeInfoLite +#define meshtastic_NodeDatabase_positions_MSGTYPE meshtastic_NodePositionEntry +#define meshtastic_NodeDatabase_telemetry_MSGTYPE meshtastic_NodeTelemetryEntry +#define meshtastic_NodeDatabase_status_MSGTYPE meshtastic_NodeStatusEntry +#define meshtastic_NodeDatabase_environment_MSGTYPE meshtastic_NodeEnvironmentEntry #define meshtastic_ChannelFile_FIELDLIST(X, a) \ X(a, STATIC, REPEATED, MESSAGE, channels, 1) \ @@ -345,6 +422,10 @@ extern const pb_msgdesc_t meshtastic_PositionLite_msg; extern const pb_msgdesc_t meshtastic_UserLite_msg; extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg; extern const pb_msgdesc_t meshtastic_DeviceState_msg; +extern const pb_msgdesc_t meshtastic_NodePositionEntry_msg; +extern const pb_msgdesc_t meshtastic_NodeTelemetryEntry_msg; +extern const pb_msgdesc_t meshtastic_NodeEnvironmentEntry_msg; +extern const pb_msgdesc_t meshtastic_NodeStatusEntry_msg; extern const pb_msgdesc_t meshtastic_NodeDatabase_msg; extern const pb_msgdesc_t meshtastic_ChannelFile_msg; extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; @@ -354,6 +435,10 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; #define meshtastic_UserLite_fields &meshtastic_UserLite_msg #define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg #define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg +#define meshtastic_NodePositionEntry_fields &meshtastic_NodePositionEntry_msg +#define meshtastic_NodeTelemetryEntry_fields &meshtastic_NodeTelemetryEntry_msg +#define meshtastic_NodeEnvironmentEntry_fields &meshtastic_NodeEnvironmentEntry_msg +#define meshtastic_NodeStatusEntry_fields &meshtastic_NodeStatusEntry_msg #define meshtastic_NodeDatabase_fields &meshtastic_NodeDatabase_msg #define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg #define meshtastic_BackupPreferences_fields &meshtastic_BackupPreferences_msg @@ -363,9 +448,13 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size #define meshtastic_BackupPreferences_size 2432 #define meshtastic_ChannelFile_size 718 -#define meshtastic_DeviceState_size 1737 -#define meshtastic_NodeInfoLite_size 196 -#define meshtastic_PositionLite_size 28 +#define meshtastic_DeviceState_size 1944 +#define meshtastic_NodeEnvironmentEntry_size 170 +#define meshtastic_NodeInfoLite_size 105 +#define meshtastic_NodePositionEntry_size 42 +#define meshtastic_NodeStatusEntry_size 89 +#define meshtastic_NodeTelemetryEntry_size 35 +#define meshtastic_PositionLite_size 34 #define meshtastic_UserLite_size 98 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index cb5f19df5..192aeeffe 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -824,6 +824,7 @@ typedef struct _meshtastic_Routing { } meshtastic_Routing; typedef PB_BYTES_ARRAY_T(233) meshtastic_Data_payload_t; +typedef PB_BYTES_ARRAY_T(64) meshtastic_Data_xeddsa_signature_t; /* (Formerly called SubPacket) The payload portion fo a packet, this is the actual bytes that are sent inside a radio packet (because from/to are broken out by the comms library) */ @@ -857,6 +858,8 @@ typedef struct _meshtastic_Data { /* Bitfield for extra flags. First use is to indicate that user approves the packet being uploaded to MQTT. */ bool has_bitfield; uint8_t bitfield; + /* XEdDSA signature for the payload */ + meshtastic_Data_xeddsa_signature_t xeddsa_signature; } meshtastic_Data; typedef PB_BYTES_ARRAY_T(32) meshtastic_KeyVerification_hash1_t; @@ -1061,6 +1064,8 @@ typedef struct _meshtastic_MeshPacket { uint32_t tx_after; /* Indicates which transport mechanism this packet arrived over */ meshtastic_MeshPacket_TransportMechanism transport_mechanism; + /* Indicates whether the packet has a valid signature */ + bool xeddsa_signed; } meshtastic_MeshPacket; /* The bluetooth to device link: @@ -1117,6 +1122,10 @@ typedef struct _meshtastic_NodeInfo { /* True if node has been muted Persistes between NodeDB internal clean ups */ bool is_muted; + /* True if node is signing its packets via XEdDSA + Persists between NodeDB internal clean ups + LSB 1 of the bitfield */ + bool has_xeddsa_signed; } meshtastic_NodeInfo; typedef PB_BYTES_ARRAY_T(16) meshtastic_MyNodeInfo_device_id_t; @@ -1586,15 +1595,15 @@ extern "C" { #define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0} #define meshtastic_RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}} -#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} +#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0, {0, {0}}} #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_RemoteShell_init_default {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_default {""} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} -#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0} +#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN, 0} +#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} @@ -1621,15 +1630,15 @@ extern "C" { #define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0} #define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}} -#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} +#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0, {0, {0}}} #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_RemoteShell_init_zero {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_zero {""} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} -#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0} +#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN, 0} +#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} @@ -1702,6 +1711,7 @@ extern "C" { #define meshtastic_Data_reply_id_tag 7 #define meshtastic_Data_emoji_tag 8 #define meshtastic_Data_bitfield_tag 9 +#define meshtastic_Data_xeddsa_signature_tag 10 #define meshtastic_KeyVerification_nonce_tag 1 #define meshtastic_KeyVerification_hash1_tag 2 #define meshtastic_KeyVerification_hash2_tag 3 @@ -1759,6 +1769,7 @@ extern "C" { #define meshtastic_MeshPacket_relay_node_tag 19 #define meshtastic_MeshPacket_tx_after_tag 20 #define meshtastic_MeshPacket_transport_mechanism_tag 21 +#define meshtastic_MeshPacket_xeddsa_signed_tag 22 #define meshtastic_NodeInfo_num_tag 1 #define meshtastic_NodeInfo_user_tag 2 #define meshtastic_NodeInfo_position_tag 3 @@ -1772,6 +1783,7 @@ extern "C" { #define meshtastic_NodeInfo_is_ignored_tag 11 #define meshtastic_NodeInfo_is_key_manually_verified_tag 12 #define meshtastic_NodeInfo_is_muted_tag 13 +#define meshtastic_NodeInfo_has_xeddsa_signed_tag 14 #define meshtastic_MyNodeInfo_my_node_num_tag 1 #define meshtastic_MyNodeInfo_reboot_count_tag 8 #define meshtastic_MyNodeInfo_min_app_version_tag 11 @@ -1938,7 +1950,8 @@ X(a, STATIC, SINGULAR, FIXED32, source, 5) \ X(a, STATIC, SINGULAR, FIXED32, request_id, 6) \ X(a, STATIC, SINGULAR, FIXED32, reply_id, 7) \ X(a, STATIC, SINGULAR, FIXED32, emoji, 8) \ -X(a, STATIC, OPTIONAL, UINT32, bitfield, 9) +X(a, STATIC, OPTIONAL, UINT32, bitfield, 9) \ +X(a, STATIC, SINGULAR, BYTES, xeddsa_signature, 10) #define meshtastic_Data_CALLBACK NULL #define meshtastic_Data_DEFAULT NULL @@ -2023,7 +2036,8 @@ X(a, STATIC, SINGULAR, BOOL, pki_encrypted, 17) \ X(a, STATIC, SINGULAR, UINT32, next_hop, 18) \ X(a, STATIC, SINGULAR, UINT32, relay_node, 19) \ X(a, STATIC, SINGULAR, UINT32, tx_after, 20) \ -X(a, STATIC, SINGULAR, UENUM, transport_mechanism, 21) +X(a, STATIC, SINGULAR, UENUM, transport_mechanism, 21) \ +X(a, STATIC, SINGULAR, BOOL, xeddsa_signed, 22) #define meshtastic_MeshPacket_CALLBACK NULL #define meshtastic_MeshPacket_DEFAULT NULL #define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data @@ -2041,7 +2055,8 @@ X(a, STATIC, OPTIONAL, UINT32, hops_away, 9) \ X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) \ X(a, STATIC, SINGULAR, BOOL, is_ignored, 11) \ X(a, STATIC, SINGULAR, BOOL, is_key_manually_verified, 12) \ -X(a, STATIC, SINGULAR, BOOL, is_muted, 13) +X(a, STATIC, SINGULAR, BOOL, is_muted, 13) \ +X(a, STATIC, SINGULAR, BOOL, has_xeddsa_signed, 14) #define meshtastic_NodeInfo_CALLBACK NULL #define meshtastic_NodeInfo_DEFAULT NULL #define meshtastic_NodeInfo_user_MSGTYPE meshtastic_User @@ -2343,7 +2358,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_ChunkedPayload_size 245 #define meshtastic_ClientNotification_size 482 #define meshtastic_Compressed_size 239 -#define meshtastic_Data_size 269 +#define meshtastic_Data_size 335 #define meshtastic_DeviceMetadata_size 54 #define meshtastic_DuplicatedPublicKey_size 0 #define meshtastic_FileInfo_size 236 @@ -2356,12 +2371,12 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LockdownStatus_size 53 #define meshtastic_LogRecord_size 426 #define meshtastic_LowEntropyKey_size 0 -#define meshtastic_MeshPacket_size 381 +#define meshtastic_MeshPacket_size 450 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 83 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 -#define meshtastic_NodeInfo_size 325 +#define meshtastic_NodeInfo_size 327 #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23