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/12] 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/12] 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/12] 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/12] 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 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 05/12] 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 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 06/12] 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 07/12] 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 08/12] 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 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 09/12] 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 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 10/12] 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 11/12] 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 12/12] 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