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 01/10] 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 02/10] 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 03/10] 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 da34334bb23521bc5e2a866b0f3f82e2e781c5d2 Mon Sep 17 00:00:00 2001 From: vidplace7 Date: Mon, 18 May 2026 12:54:09 -0400 Subject: [PATCH 04/10] Add Lilygo T-Impulse-Plus --- boards/t-impulse-plus.json | 62 +++++++ src/platform/nrf52/architecture.h | 3 + .../nrf52840/t-impulse-plus/platformio.ini | 9 + variants/nrf52840/t-impulse-plus/variant.cpp | 91 +++++++++ variants/nrf52840/t-impulse-plus/variant.h | 173 ++++++++++++++++++ 5 files changed, 338 insertions(+) create mode 100644 boards/t-impulse-plus.json create mode 100644 variants/nrf52840/t-impulse-plus/platformio.ini create mode 100644 variants/nrf52840/t-impulse-plus/variant.cpp create mode 100644 variants/nrf52840/t-impulse-plus/variant.h diff --git a/boards/t-impulse-plus.json b/boards/t-impulse-plus.json new file mode 100644 index 000000000..b43d85d6b --- /dev/null +++ b/boards/t-impulse-plus.json @@ -0,0 +1,62 @@ +{ + "build": { + "arduino":{ + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_T_IMPULSE_PLUS -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x8029" + ] + ], + "usb_product": "T-Impulse-Plus-nRF52840", + "mcu": "nrf52840", + "variant": "t_impulse_plus_nrf52840", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": [ + "jlink" + ], + "svd_path": "nrf52840.svd" + }, + "frameworks": [ + "arduino" + ], + "name": "Lilygo T-Impulse-Plus-nRF52840", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "require_upload_port": true, + "speed": 115200, + "protocol": "jlink", + "protocols": [ + "jlink", + "nrfjprog", + "stlink", + "cmsis-dap", + "blackmagic" + ] + }, + "url": "https://www.lilygo.cc/", + "vendor": "Lilygo" +} diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index eafd799fc..9f565fd39 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -87,6 +87,9 @@ #define HW_VENDOR meshtastic_HardwareModel_T_ECHO_LITE #elif defined(TTGO_T_ECHO_PLUS) #define HW_VENDOR meshtastic_HardwareModel_T_ECHO_PLUS +#elif defined(T_IMPULSE_PLUS) +// #define HW_VENDOR meshtastic_HardwareModel_T_IMPULSE_PLUS +#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #elif defined(ELECROW_ThinkNode_M1) #define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M1 #elif defined(ELECROW_ThinkNode_M3) diff --git a/variants/nrf52840/t-impulse-plus/platformio.ini b/variants/nrf52840/t-impulse-plus/platformio.ini new file mode 100644 index 000000000..fd0b66c8d --- /dev/null +++ b/variants/nrf52840/t-impulse-plus/platformio.ini @@ -0,0 +1,9 @@ +[env:t-impulse-plus] +extends = nrf52840_base +board = t-impulse-plus + +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/t-impulse-plus + -D T_IMPULSE_PLUS + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-impulse-plus> diff --git a/variants/nrf52840/t-impulse-plus/variant.cpp b/variants/nrf52840/t-impulse-plus/variant.cpp new file mode 100644 index 000000000..c50a2527d --- /dev/null +++ b/variants/nrf52840/t-impulse-plus/variant.cpp @@ -0,0 +1,91 @@ +/* + * variant.cpp - Digital pin mapping for LilyGo T-Impulse Plus + * + * Board: T-Impulse Plus V1.0 (nRF52840) + * Hardware: + * - SSD1315 OLED + * - SX1262 (S62F) + * - MIA-M10Q GPS + * - ICM20948 IMU + * - ZD25WQ32C Flash + * - TTP223 Touch Button + * - Vibration Motor + */ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +extern "C" { +const uint32_t g_ADigitalPinMap[] = { + // D0-D6: LoRa SX1262 (S62F module) SPI + control + 2, // D0 P0.02 SX1262_RST + 29, // D1 P0.29 SX1262_DIO1 + 31, // D2 P0.31 SX1262_BUSY + 46, // D3 P1.14 SX1262_CS + 3, // D4 P0.03 SPI_SCK + 30, // D5 P0.30 SPI_MISO + 28, // D6 P0.28 SPI_MOSI + + // D7-D8: RF switch control + 45, // D7 P1.13 SX1262_RF_VC1 (TXEN) + 39, // D8 P1.07 SX1262_RF_VC2 (RXEN) + + // D9-D11: GPS (u-blox MIA-M10Q) + 44, // D9 P1.12 GPS_TX (MCU TX -> GPS RX) + 43, // D10 P1.11 GPS_RX (MCU RX <- GPS TX) + 42, // D11 P1.10 GPS_EN + + // D12-D13: Display I2C (SSD1315) + 20, // D12 P0.20 SCREEN_SDA + 15, // D13 P0.15 SCREEN_SCL + + // D14-D15: Sensor I2C (ICM20948, SGM41562) + 40, // D14 P1.08 IMU_SDA + 11, // D15 P0.11 IMU_SCL + + // D16-D17: Battery management + 5, // D16 P0.05 BATTERY_ADC + 25, // D17 P0.25 BATTERY_MEASUREMENT_CONTROL + + // D18: Touch button (TTP223) + 36, // D18 P1.04 TTP223_KEY + + // D19: Vibration motor + 22, // D19 P0.22 VIBRATION_MOTOR + + // D20: LDO enable + 14, // D20 P0.14 RT9080_EN + + // D21-D26: Flash QSPI (ZD25WQ32C) + 12, // D21 P0.12 FLASH_CS + 4, // D22 P0.04 FLASH_SCLK + 6, // D23 P0.06 FLASH_IO0 + 41, // D24 P1.09 FLASH_IO1 + 8, // D25 P0.08 FLASH_IO2 + 26, // D26 P0.26 FLASH_IO3 + + // D27-D28: Interrupt lines + 7, // D27 P0.07 ICM20948_INT + 16, // D28 P0.16 SGM41562_INT + + // D29: Boot button + 24, // D29 P0.24 BOOT +}; +} + +void initVariant() +{ + // Flash CS high (deselect) + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); + + // Enable battery voltage measurement + pinMode(BAT_READ, OUTPUT); + digitalWrite(BAT_READ, HIGH); + + // Enable RT9080 LDO + pinMode(D20, OUTPUT); + digitalWrite(D20, HIGH); +} \ No newline at end of file diff --git a/variants/nrf52840/t-impulse-plus/variant.h b/variants/nrf52840/t-impulse-plus/variant.h new file mode 100644 index 000000000..db6e4d221 --- /dev/null +++ b/variants/nrf52840/t-impulse-plus/variant.h @@ -0,0 +1,173 @@ +#ifndef _T_IMPULSE_PLUS_H_ +#define _T_IMPULSE_PLUS_H_ +#include "WVariant.h" + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Clock Configuration +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define VARIANT_MCK (64000000ul) +#define USE_LFXO + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Pin Capacity Definitions +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define PINS_COUNT (30u) +#define NUM_DIGITAL_PINS (30u) +#define NUM_ANALOG_INPUTS (1u) +#define NUM_ANALOG_OUTPUTS (0u) + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Digital Pin Mapping +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define D0 0 // P0.02 SX1262_RST +#define D1 1 // P0.29 SX1262_DIO1 +#define D2 2 // P0.31 SX1262_BUSY +#define D3 3 // P1.14 SX1262_CS +#define D4 4 // P0.03 SPI_SCK +#define D5 5 // P0.30 SPI_MISO +#define D6 6 // P0.28 SPI_MOSI +#define D7 7 // P1.13 RF_VC1 (TXEN) +#define D8 8 // P1.07 RF_VC2 (RXEN) +#define D9 9 // P1.12 GPS_TX +#define D10 10 // P1.11 GPS_RX +#define D11 11 // P1.10 GPS_EN +#define D12 12 // P0.20 SCREEN_SDA +#define D13 13 // P0.15 SCREEN_SCL +#define D14 14 // P1.08 IMU_SDA +#define D15 15 // P0.11 IMU_SCL +#define D16 16 // P0.05 BATTERY_ADC +#define D17 17 // P0.25 BATTERY_CTL +#define D18 18 // P1.04 TTP223_KEY +#define D19 19 // P0.22 VIBRATION_MOTOR +#define D20 20 // P0.14 RT9080_EN +#define D21 21 // P0.12 FLASH_CS +#define D22 22 // P0.04 FLASH_SCLK +#define D23 23 // P0.06 FLASH_IO0 +#define D24 24 // P1.09 FLASH_IO1 +#define D25 25 // P0.08 FLASH_IO2 +#define D26 26 // P0.26 FLASH_IO3 +#define D27 27 // P0.07 ICM20948_INT +#define D28 28 // P0.16 SGM41562_INT +#define D29 29 // P0.24 BOOT + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// LED Configuration (no physical LED) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define LED_STATE_ON 1 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Button Configuration (TTP223 capacitive touch) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define PIN_BUTTON_TOUCH D18 +#define BUTTON_TOUCH_ACTIVE_LOW true +#define BUTTON_TOUCH_ACTIVE_PULLUP false + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Analog Pin Definitions +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define PIN_VBAT D16 // P0.05 Battery voltage sense + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// I2C Configuration +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Primary I2C: Display (SSD1315) +#define PIN_WIRE_SDA D12 // P0.20 +#define PIN_WIRE_SCL D13 // P0.15 + +// Secondary I2C: IMU (ICM20948) + PMU (SGM41562) +#define WIRE_INTERFACES_COUNT 2 +#define PIN_WIRE1_SDA D14 // P1.08 +#define PIN_WIRE1_SCL D15 // P0.11 +#define I2C_NO_RESCAN + +static const uint8_t SDA = PIN_WIRE_SDA; +static const uint8_t SCL = PIN_WIRE_SCL; + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Display (SSD1315, compatible with SSD1306 driver) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define HAS_SCREEN 1 +#define USE_SSD1306 1 +#define DISPLAY_FORCE_SMALL_FONTS + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// SPI Configuration (SX1262 LoRa) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define SPI_INTERFACES_COUNT 1 +#define PIN_SPI_SCK D4 // P0.03 +#define PIN_SPI_MISO D5 // P0.30 +#define PIN_SPI_MOSI D6 // P0.28 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// SX1262 LoRa (S62F module) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define USE_SX1262 +#define SX126X_CS D3 +#define SX126X_DIO1 D1 +#define SX126X_BUSY D2 +#define SX126X_RESET D0 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_TXEN D7 // RF_VC1 +#define SX126X_RXEN D8 // RF_VC2 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Power Management +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define BAT_READ D17 // P0.25 Battery measurement control (HIGH = enable) +#define BATTERY_PIN PIN_VBAT +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define ADC_MULTIPLIER 2.0 +#define AREF_VOLTAGE 3.6 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// GPS (u-blox MIA-M10Q) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define GPS_UBLOX +#define HAS_GPS 1 +#define GPS_TX_PIN D9 // P1.12 MCU TX -> GPS RX +#define GPS_RX_PIN D10 // P1.11 MCU RX <- GPS TX +#define PIN_GPS_EN D11 // P1.10 GPS enable (active HIGH) +#define GPS_EN_ACTIVE HIGH +#define GPS_BAUDRATE 9600 +#define GPS_THREAD_INTERVAL 50 +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// On-board QSPI Flash (ZD25WQ32CEIGR) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define PIN_QSPI_SCK D22 // P0.04 +#define PIN_QSPI_CS D21 // P0.12 +#define PIN_QSPI_IO0 D23 // P0.06 +#define PIN_QSPI_IO1 D24 // P1.09 +#define PIN_QSPI_IO2 D25 // P0.08 +#define PIN_QSPI_IO3 D26 // P0.26 + +#define EXTERNAL_FLASH_DEVICES W25Q32JV_IQ +#define EXTERNAL_FLASH_USE_QSPI + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Vibration Motor (active-low, used as notification output) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define LED_NOTIFICATION D19 // P0.22 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// IMU (ICM20948 on Wire1) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define HAS_ICM20948 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Compatibility Definitions +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#ifdef __cplusplus +extern "C" { +#endif + +#define PIN_SERIAL2_RX (-1) +#define PIN_SERIAL2_TX (-1) + +#ifdef __cplusplus +} +#endif + +#endif // _T_IMPULSE_PLUS_H_ \ No newline at end of file From eda3a79a97a133768c0d19db994087f11d3c66f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 18 May 2026 22:16:10 +0200 Subject: [PATCH 05/10] Enable small screen layout 64x32 --- variants/nrf52840/t-impulse-plus/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/nrf52840/t-impulse-plus/variant.h b/variants/nrf52840/t-impulse-plus/variant.h index db6e4d221..7aee32c46 100644 --- a/variants/nrf52840/t-impulse-plus/variant.h +++ b/variants/nrf52840/t-impulse-plus/variant.h @@ -88,7 +88,7 @@ static const uint8_t SCL = PIN_WIRE_SCL; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ #define HAS_SCREEN 1 #define USE_SSD1306 1 -#define DISPLAY_FORCE_SMALL_FONTS +#define OLED_TINY // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // SPI Configuration (SX1262 LoRa) From 0c1457b5dc7c77897af3440f186b7163791892db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 18 May 2026 22:21:33 +0200 Subject: [PATCH 06/10] trunk'd --- boards/t-impulse-plus.json | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/boards/t-impulse-plus.json b/boards/t-impulse-plus.json index b43d85d6b..1711b684c 100644 --- a/boards/t-impulse-plus.json +++ b/boards/t-impulse-plus.json @@ -1,18 +1,13 @@ { "build": { - "arduino":{ + "arduino": { "ldscript": "nrf52840_s140_v6.ld" }, "core": "nRF5", "cpu": "cortex-m4", "extra_flags": "-DARDUINO_NRF52840_T_IMPULSE_PLUS -DNRF52840_XXAA", "f_cpu": "64000000L", - "hwids": [ - [ - "0x239A", - "0x8029" - ] - ], + "hwids": [["0x239A", "0x8029"]], "usb_product": "T-Impulse-Plus-nRF52840", "mcu": "nrf52840", "variant": "t_impulse_plus_nrf52840", @@ -29,19 +24,13 @@ "settings_addr": "0xFF000" } }, - "connectivity": [ - "bluetooth" - ], + "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "onboard_tools": [ - "jlink" - ], + "onboard_tools": ["jlink"], "svd_path": "nrf52840.svd" }, - "frameworks": [ - "arduino" - ], + "frameworks": ["arduino"], "name": "Lilygo T-Impulse-Plus-nRF52840", "upload": { "maximum_ram_size": 248832, @@ -49,13 +38,7 @@ "require_upload_port": true, "speed": 115200, "protocol": "jlink", - "protocols": [ - "jlink", - "nrfjprog", - "stlink", - "cmsis-dap", - "blackmagic" - ] + "protocols": ["jlink", "nrfjprog", "stlink", "cmsis-dap", "blackmagic"] }, "url": "https://www.lilygo.cc/", "vendor": "Lilygo" From 8a8e4b841a7ab0e838d28ead7a3101820bdc65fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 18 May 2026 22:52:37 +0200 Subject: [PATCH 07/10] Haptic Feedback (short and long press) --- src/input/HapticFeedback.cpp | 86 ++++++++++++++++++++++ src/input/HapticFeedback.h | 47 ++++++++++++ src/input/InputBroker.cpp | 13 ++++ variants/nrf52840/t-impulse-plus/variant.h | 3 +- 4 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 src/input/HapticFeedback.cpp create mode 100644 src/input/HapticFeedback.h diff --git a/src/input/HapticFeedback.cpp b/src/input/HapticFeedback.cpp new file mode 100644 index 000000000..ef277779c --- /dev/null +++ b/src/input/HapticFeedback.cpp @@ -0,0 +1,86 @@ +#include "HapticFeedback.h" + +#ifdef HAPTIC_FEEDBACK_PIN + +#include + +#ifdef HAPTIC_FEEDBACK_ACTIVE_LOW +#define HAPTIC_FEEDBACK_ON_STATE LOW +#define HAPTIC_FEEDBACK_OFF_STATE HIGH +#else +#define HAPTIC_FEEDBACK_ON_STATE HIGH +#define HAPTIC_FEEDBACK_OFF_STATE LOW +#endif + +HapticFeedback *hapticFeedback = nullptr; + +void initHapticFeedback() +{ + if (!hapticFeedback) + hapticFeedback = new HapticFeedback(); +} + +HapticFeedback::HapticFeedback() : concurrency::OSThread("Haptic") +{ + pinMode(HAPTIC_FEEDBACK_PIN, OUTPUT); + digitalWrite(HAPTIC_FEEDBACK_PIN, HAPTIC_FEEDBACK_OFF_STATE); +} + +void HapticFeedback::motorWrite(bool on) +{ + digitalWrite(HAPTIC_FEEDBACK_PIN, on ? HAPTIC_FEEDBACK_ON_STATE : HAPTIC_FEEDBACK_OFF_STATE); +} + +void HapticFeedback::pulse(uint16_t durationMs) +{ + motorWrite(true); + pulseOffAt = millis() + durationMs; + if (pulseOffAt == 0) // disambiguate from "no pulse active" sentinel on millis() wrap + pulseOffAt = 1; + setIntervalFromNow(durationMs); +} + +void HapticFeedback::armDelayedPulse(uint16_t delayMs, uint16_t durationMs) +{ + delayedPulseAt = millis() + delayMs; + if (delayedPulseAt == 0) + delayedPulseAt = 1; + delayedPulseDuration = durationMs; + setIntervalFromNow(delayMs); +} + +void HapticFeedback::cancelDelayedPulse() +{ + delayedPulseAt = 0; +} + +int32_t HapticFeedback::runOnce() +{ + uint32_t now = millis(); + + // End an in-flight pulse if its time has come. + if (pulseOffAt != 0 && (int32_t)(now - pulseOffAt) >= 0) { + motorWrite(false); + pulseOffAt = 0; + } + + // Fire an armed delayed pulse if its time has come. + if (delayedPulseAt != 0 && (int32_t)(now - delayedPulseAt) >= 0) { + uint16_t dur = delayedPulseDuration; + delayedPulseAt = 0; + pulse(dur); + } + + // Sleep until the next scheduled event, or idle long if nothing pending. + uint32_t next = 0; + if (pulseOffAt != 0) + next = pulseOffAt; + if (delayedPulseAt != 0 && (next == 0 || (int32_t)(delayedPulseAt - next) < 0)) + next = delayedPulseAt; + if (next == 0) + return 60 * 1000; // nothing pending — idle for a minute + int32_t delay = (int32_t)(next - now); + return delay > 0 ? delay : 0; +} + +#endif // HAPTIC_FEEDBACK_PIN diff --git a/src/input/HapticFeedback.h b/src/input/HapticFeedback.h new file mode 100644 index 000000000..cb0f94873 --- /dev/null +++ b/src/input/HapticFeedback.h @@ -0,0 +1,47 @@ +#pragma once + +#include "configuration.h" + +#ifdef HAPTIC_FEEDBACK_PIN + +#include "concurrency/OSThread.h" +#include + +// Drives short, non-blocking pulses on a GPIO-controlled vibration motor. +// A variant opts in by defining HAPTIC_FEEDBACK_PIN; HAPTIC_FEEDBACK_ACTIVE_LOW +// inverts the drive polarity (default: active-high — pin HIGH = motor on). +// +// Used by the touch button to produce button-like haptic feedback. Coexists +// with ExternalNotificationModule if both target the same pin — pulses are +// fire-and-forget, no synchronization, last writer wins. +class HapticFeedback : public concurrency::OSThread +{ + public: + HapticFeedback(); + + // Turn motor on now, schedule off after durationMs. + void pulse(uint16_t durationMs = 30); + + // Schedule a one-shot pulse to fire delayMs from now. + void armDelayedPulse(uint16_t delayMs, uint16_t durationMs = 30); + + // Cancel a previously-armed delayed pulse (no effect if none pending). + void cancelDelayedPulse(); + + protected: + int32_t runOnce() override; + + private: + uint32_t pulseOffAt = 0; // millis() when current pulse should end (0 = no pulse active) + uint32_t delayedPulseAt = 0; // millis() when armed pulse should fire (0 = nothing armed) + uint16_t delayedPulseDuration = 0; + + void motorWrite(bool on); +}; + +extern HapticFeedback *hapticFeedback; + +// Lazy-instantiate the global on first call. Safe to call repeatedly. +void initHapticFeedback(); + +#endif // HAPTIC_FEEDBACK_PIN diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index 42ab7f70d..1b4b64cad 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -2,6 +2,7 @@ #include "PowerFSM.h" // needed for event trigger #include "configuration.h" #include "graphics/Screen.h" +#include "input/HapticFeedback.h" #include "modules/ExternalNotificationModule.h" #if ARCH_PORTDUINO @@ -237,6 +238,18 @@ void InputBroker::Init() } touchBacklightActive = false; }; +#endif +#if defined(HAPTIC_FEEDBACK_PIN) + // Haptic feedback: short pulse on touch contact, and a second short + // pulse when the long-press fires (BACK). The delayed pulse delay + // matches touchConfig.longPressTime's default (500 ms). + touchConfig.suppressLeadUpSound = true; + initHapticFeedback(); + touchConfig.onPress = []() { + hapticFeedback->pulse(30); + hapticFeedback->armDelayedPulse(500, 30); + }; + touchConfig.onRelease = []() { hapticFeedback->cancelDelayedPulse(); }; #endif TouchButtonThread->initButton(touchConfig); #endif diff --git a/variants/nrf52840/t-impulse-plus/variant.h b/variants/nrf52840/t-impulse-plus/variant.h index 7aee32c46..4c789d7ab 100644 --- a/variants/nrf52840/t-impulse-plus/variant.h +++ b/variants/nrf52840/t-impulse-plus/variant.h @@ -147,9 +147,10 @@ static const uint8_t SCL = PIN_WIRE_SCL; #define EXTERNAL_FLASH_USE_QSPI // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Vibration Motor (active-low, used as notification output) +// Vibration Motor (GPIO active-high) // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ #define LED_NOTIFICATION D19 // P0.22 +#define HAPTIC_FEEDBACK_PIN LED_NOTIFICATION // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // IMU (ICM20948 on Wire1) From 8fa7ae40a767fb2762a5dec5c220409fcf09eca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 18 May 2026 23:41:41 +0200 Subject: [PATCH 08/10] enable Charging Indicator --- src/Power.cpp | 15 +++ src/power/SGM41562.cpp | 109 +++++++++++++++++++++ src/power/SGM41562.h | 105 ++++++++++++++++++++ variants/nrf52840/t-impulse-plus/variant.h | 6 ++ 4 files changed, 235 insertions(+) create mode 100644 src/power/SGM41562.cpp create mode 100644 src/power/SGM41562.h diff --git a/src/Power.cpp b/src/Power.cpp index f752e9461..ef1143d02 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -23,6 +23,7 @@ #include "main.h" #include "meshUtils.h" #include "power/PowerHAL.h" +#include "power/SGM41562.h" #include "sleep.h" #if defined(ARCH_PORTDUINO) @@ -453,6 +454,10 @@ class AnalogBatteryLevel : public HasBatteryLevel /// source virtual bool isVbusIn() override { +#ifdef HAS_SGM41562 + if (sgm41562 && sgm41562->refresh()) + return sgm41562->isInputPowerGood(); +#endif #ifdef EXT_PWR_DETECT #if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB) // if external powered that pin will be pulled down @@ -483,6 +488,10 @@ class AnalogBatteryLevel : public HasBatteryLevel /// we can't be smart enough to say 'full'? virtual bool isCharging() override { +#ifdef HAS_SGM41562 + if (sgm41562 && sgm41562->refresh()) + return sgm41562->isCharging(); +#endif #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT) && !defined(HAS_PMU) if (hasRAK()) { return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse; @@ -697,6 +706,12 @@ bool Power::analogInit() */ bool Power::setup() { +#ifdef HAS_SGM41562 + // Initialize the charger early so AnalogBatteryLevel can read charging + // state from it. The charger does not provide battery voltage / percent — + // those still come from the platform ADC via analogInit() below. + initSGM41562(SGM41562_WIRE); +#endif bool found = false; if (axpChipInit()) { found = true; diff --git a/src/power/SGM41562.cpp b/src/power/SGM41562.cpp new file mode 100644 index 000000000..4f6acf406 --- /dev/null +++ b/src/power/SGM41562.cpp @@ -0,0 +1,109 @@ +#include "SGM41562.h" + +#ifdef HAS_SGM41562 + +#include + +SGM41562 *sgm41562 = nullptr; + +bool initSGM41562(TwoWire &wire) +{ + if (sgm41562) + return true; + sgm41562 = new SGM41562(); + if (!sgm41562->begin(wire)) { + delete sgm41562; + sgm41562 = nullptr; + return false; + } + return true; +} + +bool SGM41562::readReg(uint8_t reg, uint8_t &value) +{ + wire_->beginTransmission(address_); + wire_->write(reg); + if (wire_->endTransmission(false) != 0) + return false; + if (wire_->requestFrom((int)address_, 1) != 1) + return false; + value = wire_->read(); + return true; +} + +bool SGM41562::writeReg(uint8_t reg, uint8_t value) +{ + wire_->beginTransmission(address_); + wire_->write(reg); + wire_->write(value); + return wire_->endTransmission() == 0; +} + +bool SGM41562::updateReg(uint8_t reg, uint8_t mask, uint8_t value) +{ + uint8_t cur; + if (!readReg(reg, cur)) + return false; + cur = (cur & ~mask) | (value & mask); + return writeReg(reg, cur); +} + +bool SGM41562::begin(TwoWire &wire, uint8_t address) +{ + wire_ = &wire; + address_ = address; + + uint8_t id; + if (!readReg(REG_DEVICE_ID, id)) { + LOG_WARN("SGM41562: I2C read failed at 0x%02X", address_); + return false; + } + if (id != DEVICE_ID_EXPECTED) { + LOG_WARN("SGM41562: unexpected device ID 0x%02X (expected 0x%02X)", id, DEVICE_ID_EXPECTED); + return false; + } + LOG_INFO("SGM41562: detected at 0x%02X (id 0x%02X)", address_, id); + + // Mirror the vendor reference init sequence: PCB OTP off, NTC off, + // watchdog off, charger enabled. These match LilyGo's stock firmware + // for the T-Impulse Plus. + delay(120); + writeReg(REG_SYS_VOLTAGE_REG, 0xB7); + writeReg(REG_MISC_OP_CONTROL, 0x40); + writeReg(REG_CHARGE_TERM_TIMER, 0x1A); + writeReg(REG_POWER_ON_CFG, 0xA4); + + return refresh(); +} + +bool SGM41562::refresh() +{ + uint32_t now = millis(); + if (lastRefreshMs_ != 0 && (now - lastRefreshMs_) < 250) + return true; // cached + lastRefreshMs_ = now == 0 ? 1 : now; + + uint8_t status, fault; + if (!readReg(REG_SYSTEM_STATUS, status)) + return false; + if (!readReg(REG_FAULT, fault)) + return false; + + chargeStatus_ = static_cast((status >> SYS_STATUS_CHRG_SHIFT) & SYS_STATUS_CHRG_MASK); + inputPowerGood_ = (status & SYS_STATUS_PG) != 0; + thermalReg_ = (status & SYS_STATUS_THERM_REG) != 0; + faultMask_ = fault & 0x3F; // bits [7:6] are enter_ship_time config, not faults + return true; +} + +bool SGM41562::setChargeEnable(bool enable) +{ + return updateReg(REG_POWER_ON_CFG, POWER_ON_CFG_CHG_DISABLE, enable ? 0x00 : POWER_ON_CFG_CHG_DISABLE); +} + +bool SGM41562::setShippingModeEnable(bool enable) +{ + return updateReg(REG_MISC_OP_CONTROL, MISC_OP_SHIPPING_MODE, enable ? MISC_OP_SHIPPING_MODE : 0x00); +} + +#endif // HAS_SGM41562 diff --git a/src/power/SGM41562.h b/src/power/SGM41562.h new file mode 100644 index 000000000..a75f781df --- /dev/null +++ b/src/power/SGM41562.h @@ -0,0 +1,105 @@ +#pragma once + +#include "configuration.h" + +#ifdef HAS_SGM41562 + +#include +#include + +// SG Micro SGM41562 — single-cell Li-ion buck charger, I²C-controlled, no +// fuel gauge. This driver exposes status (charging / input good / fault), +// charge enable, and shipping-mode control. Battery voltage/percent still +// come from the platform ADC path; the charger is plumbed in as a +// side-channel for isCharging()/isVbusIn() in AnalogBatteryLevel. +// +// Reference: SGM41562 datasheet (Cmd map + bit fields cross-verified against +// LilyGo's `Cpp_Bus_Driver::Sgm41562xx` driver, which is what their vendor +// example for this board uses). + +#ifndef SGM41562_ADDR +#define SGM41562_ADDR 0x03 // Per datasheet — unusual but correct +#endif + +#ifndef SGM41562_WIRE +#define SGM41562_WIRE Wire1 // Most boards put the PMU on the secondary bus +#endif + +class SGM41562 +{ + public: + enum class ChargeStatus : uint8_t { + NotCharging = 0b00, + Precharge = 0b01, + FastCharge = 0b10, + ChargeDone = 0b11, + }; + + bool begin(TwoWire &wire, uint8_t address = SGM41562_ADDR); + + // Re-read the system status + fault registers. Throttled internally to + // at most one I²C transaction per 250 ms — call as often as you like. + bool refresh(); + + // Status — cached from the most recent refresh(). + ChargeStatus chargeStatus() const { return chargeStatus_; } + bool isCharging() const + { + return chargeStatus_ == ChargeStatus::Precharge || chargeStatus_ == ChargeStatus::FastCharge; + } + bool isChargeDone() const { return chargeStatus_ == ChargeStatus::ChargeDone; } + bool isInputPowerGood() const { return inputPowerGood_; } + bool isThermalRegulation() const { return thermalReg_; } + uint8_t faultMask() const { return faultMask_; } + + // Control. + bool setChargeEnable(bool enable); + bool setShippingModeEnable(bool enable); + + private: + TwoWire *wire_ = nullptr; + uint8_t address_ = SGM41562_ADDR; + uint32_t lastRefreshMs_ = 0; + + ChargeStatus chargeStatus_ = ChargeStatus::NotCharging; + bool inputPowerGood_ = false; + bool thermalReg_ = false; + uint8_t faultMask_ = 0; + + bool readReg(uint8_t reg, uint8_t &value); + bool writeReg(uint8_t reg, uint8_t value); + bool updateReg(uint8_t reg, uint8_t mask, uint8_t value); + + // SGM41562 register addresses + static constexpr uint8_t REG_INPUT_SOURCE = 0x00; + static constexpr uint8_t REG_POWER_ON_CFG = 0x01; + static constexpr uint8_t REG_CHARGE_CURRENT = 0x02; + static constexpr uint8_t REG_DISCHARGE_TERM_CURRENT = 0x03; + static constexpr uint8_t REG_CHARGE_VOLTAGE = 0x04; + static constexpr uint8_t REG_CHARGE_TERM_TIMER = 0x05; + static constexpr uint8_t REG_MISC_OP_CONTROL = 0x06; + static constexpr uint8_t REG_SYS_VOLTAGE_REG = 0x07; + static constexpr uint8_t REG_SYSTEM_STATUS = 0x08; + static constexpr uint8_t REG_FAULT = 0x09; + static constexpr uint8_t REG_I2C_ADDR_MISC = 0x0A; + static constexpr uint8_t REG_DEVICE_ID = 0x0B; + + // Bit positions in REG_POWER_ON_CFG. + static constexpr uint8_t POWER_ON_CFG_CHG_DISABLE = 0x08; // bit 3: 1 = charging disabled + // Bit positions in REG_MISC_OP_CONTROL. + static constexpr uint8_t MISC_OP_SHIPPING_MODE = 0x20; // bit 5: 1 = enter shipping mode + // Bit positions in REG_SYSTEM_STATUS. + static constexpr uint8_t SYS_STATUS_CHRG_SHIFT = 3; + static constexpr uint8_t SYS_STATUS_CHRG_MASK = 0x03; + static constexpr uint8_t SYS_STATUS_PG = 0x02; // bit 1: input power good + static constexpr uint8_t SYS_STATUS_THERM_REG = 0x01; // bit 0: thermal regulation + + static constexpr uint8_t DEVICE_ID_EXPECTED = 0x04; +}; + +extern SGM41562 *sgm41562; + +// Lazy-instantiate the global on the supplied wire. Returns true on success. +bool initSGM41562(TwoWire &wire); + +#endif // HAS_SGM41562 diff --git a/variants/nrf52840/t-impulse-plus/variant.h b/variants/nrf52840/t-impulse-plus/variant.h index 4c789d7ab..0b5d2e22d 100644 --- a/variants/nrf52840/t-impulse-plus/variant.h +++ b/variants/nrf52840/t-impulse-plus/variant.h @@ -157,6 +157,12 @@ static const uint8_t SCL = PIN_WIRE_SCL; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ #define HAS_ICM20948 +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Charger (SGM41562 on Wire1 @ 0x03) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define HAS_SGM41562 +#define SGM41562_WIRE Wire1 + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Compatibility Definitions // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ From 6fde0f949b8fc19dc3d9e2c7afffb6f8bbfe28f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 18 May 2026 23:42:11 +0200 Subject: [PATCH 09/10] enable nrfutil uploads --- boards/t-impulse-plus.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boards/t-impulse-plus.json b/boards/t-impulse-plus.json index 1711b684c..233ba7756 100644 --- a/boards/t-impulse-plus.json +++ b/boards/t-impulse-plus.json @@ -37,8 +37,8 @@ "maximum_size": 815104, "require_upload_port": true, "speed": 115200, - "protocol": "jlink", - "protocols": ["jlink", "nrfjprog", "stlink", "cmsis-dap", "blackmagic"] + "protocol": "nrfutil", + "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink", "cmsis-dap", "blackmagic"] }, "url": "https://www.lilygo.cc/", "vendor": "Lilygo" From 2e343a156d5ed7765a7d10fcf15eb41a142e9f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 19 May 2026 07:18:12 +0200 Subject: [PATCH 10/10] trunk fmt --- boards/t-impulse-plus.json | 9 ++++++++- src/power/SGM41562.h | 7 ++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/boards/t-impulse-plus.json b/boards/t-impulse-plus.json index 233ba7756..f5586e1d8 100644 --- a/boards/t-impulse-plus.json +++ b/boards/t-impulse-plus.json @@ -38,7 +38,14 @@ "require_upload_port": true, "speed": 115200, "protocol": "nrfutil", - "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink", "cmsis-dap", "blackmagic"] + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink", + "cmsis-dap", + "blackmagic" + ] }, "url": "https://www.lilygo.cc/", "vendor": "Lilygo" diff --git a/src/power/SGM41562.h b/src/power/SGM41562.h index a75f781df..30836ff09 100644 --- a/src/power/SGM41562.h +++ b/src/power/SGM41562.h @@ -43,10 +43,7 @@ class SGM41562 // Status — cached from the most recent refresh(). ChargeStatus chargeStatus() const { return chargeStatus_; } - bool isCharging() const - { - return chargeStatus_ == ChargeStatus::Precharge || chargeStatus_ == ChargeStatus::FastCharge; - } + bool isCharging() const { return chargeStatus_ == ChargeStatus::Precharge || chargeStatus_ == ChargeStatus::FastCharge; } bool isChargeDone() const { return chargeStatus_ == ChargeStatus::ChargeDone; } bool isInputPowerGood() const { return inputPowerGood_; } bool isThermalRegulation() const { return thermalReg_; } @@ -91,7 +88,7 @@ class SGM41562 // Bit positions in REG_SYSTEM_STATUS. static constexpr uint8_t SYS_STATUS_CHRG_SHIFT = 3; static constexpr uint8_t SYS_STATUS_CHRG_MASK = 0x03; - static constexpr uint8_t SYS_STATUS_PG = 0x02; // bit 1: input power good + static constexpr uint8_t SYS_STATUS_PG = 0x02; // bit 1: input power good static constexpr uint8_t SYS_STATUS_THERM_REG = 0x01; // bit 0: thermal regulation static constexpr uint8_t DEVICE_ID_EXPECTED = 0x04;