Skip to content
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
089ad58
thinknode-m9 variant
mverch67 Apr 21, 2026
7960144
move lora to SPI1 device
mverch67 Apr 27, 2026
c85f2a2
enable SDcard
mverch67 Apr 27, 2026
9009bbd
use HSPI
mverch67 Apr 27, 2026
86a0d94
BaseUI tft -> HSPI
mverch67 Apr 27, 2026
d71a380
buzzer, webdav lib
mverch67 May 20, 2026
7088470
Merge branch 'develop' into thinknode-m9
mverch67 May 20, 2026
9d885ad
fix build issues
mverch67 May 21, 2026
d4d4b2c
M9 default to MUI, no BT, short ringtone
mverch67 May 21, 2026
8e6df32
add keyboard long-press config
mverch67 Jun 10, 2026
12ca206
update variant
mverch67 Jun 10, 2026
affbf05
add ThingNode-M9 GPS string
mverch67 Jun 11, 2026
813c990
GPS 115200 baud
mverch67 Jun 11, 2026
3d0b2ca
Merge branch 'develop' into thinknode-m9
mverch67 Jun 30, 2026
d56d5db
Merge branch 'develop' into thinknode-m9
jp-bennett Jul 1, 2026
ae34cce
Basic BaseUI support
jp-bennett Jul 1, 2026
8e75a76
Fixup power detection
jp-bennett Jul 1, 2026
071d7e1
Merge remote-tracking branch 'refs/remotes/origin/thinknode-m9' into …
mverch67 Jul 1, 2026
280a701
Compass and KB fixes for M9
jp-bennett Jul 2, 2026
2c23a2a
Merge remote-tracking branch 'refs/remotes/origin/thinknode-m9' into …
mverch67 Jul 6, 2026
fa5ff1e
add timed Lock::lock()
mverch67 Jul 6, 2026
7886e55
add SD card
mverch67 Jul 6, 2026
25c055a
Merge branch 'develop' into thinknode-m9
mverch67 Jul 6, 2026
000120f
point device-ui to thinknode m9 draft branch
mverch67 Jul 6, 2026
402c6f0
Merge branch 'develop' into thinknode-m9
thebentern Jul 6, 2026
8f18dce
Merge branch 'develop' into thinknode-m9
mverch67 Jul 7, 2026
a0c9736
trunk fmt
mverch67 Jul 7, 2026
c4e50c8
fix FusionCompass
mverch67 Jul 7, 2026
36a6b27
Fix t-deck-tft linker arg list overflow in CI
Copilot Jul 7, 2026
0d0aa7d
Merge branch 'develop' into thinknode-m9
mverch67 Jul 7, 2026
79ab3eb
Merge branch 'develop' into thinknode-m9
mverch67 Jul 8, 2026
fefb268
SDcard/lora fix: SPI1 must not be declared twice in arduino 3.x -> re…
mverch67 Jul 10, 2026
bbbd963
Merge branch 'develop' into thinknode-m9
mverch67 Jul 10, 2026
9fe9c31
Merge branch 'develop' into thinknode-m9
mverch67 Jul 13, 2026
5352260
Merge branch 'develop' into thinknode-m9
mverch67 Jul 14, 2026
2fc1d87
update battery parameters
mverch67 Jul 15, 2026
79ffa5c
reinit SD card when updating; fix PSRAM size
mverch67 Jul 15, 2026
269f21b
Merge branch 'develop' into thinknode-m9
mverch67 Jul 16, 2026
7b62dd3
Merge branch 'develop' into thinknode-m9
mverch67 Jul 22, 2026
58b6f8e
update lib versions
mverch67 Jul 22, 2026
e384f28
fix wakeup on key press (KB_INT)
mverch67 Jul 22, 2026
fd9bb08
fix default nag_timeout for TFT/MUI devices with buzzer
mverch67 Jul 22, 2026
4e96f7d
increase PSRAM and SD freq
mverch67 Jul 23, 2026
a3b0d2d
Merge branch 'develop' into thinknode-m9
mverch67 Jul 23, 2026
37ad16b
trunk fmt
mverch67 Jul 23, 2026
ddc4fb5
update lovyanGFX 1.2.26
mverch67 Jul 23, 2026
f0ec12b
update device-ui commit reference
mverch67 Jul 23, 2026
6024804
Merge branch 'develop' into thinknode-m9
mverch67 Jul 24, 2026
4dbd3a6
Merge branch 'develop' into thinknode-m9
mverch67 Jul 24, 2026
363b642
Merge branch 'develop' into thinknode-m9
mverch67 Jul 24, 2026
e33cc4c
Merge branch 'develop' into thinknode-m9
mverch67 Jul 27, 2026
fc7cae7
fix screen definition
mverch67 Jul 27, 2026
c831343
Merge branch 'develop' into thinknode-m9
mverch67 Jul 28, 2026
8df1ab1
remove DONE; maybe a keyword or other used identifier
mverch67 Jul 28, 2026
336097e
fixed CI error nag_timeout
mverch67 Jul 29, 2026
3691565
Merge branch 'develop' into thinknode-m9
mverch67 Jul 29, 2026
9e39aea
Merge branch 'develop' into thinknode-m9
mverch67 Aug 3, 2026
6fbd32f
fix prepareSleep initialization
mverch67 Aug 4, 2026
c6341ea
Merge branch 'develop' into thinknode-m9
mverch67 Aug 4, 2026
f1d410e
trunk fmt
mverch67 Aug 4, 2026
e5287f3
Merge branch 'develop' into thinknode-m9
mverch67 Aug 4, 2026
76c457f
reduce SD SPI frequency
mverch67 Aug 4, 2026
5f9a69b
update device-ui
mverch67 Aug 8, 2026
61ec082
Merge branch 'develop' into thinknode-m9
mverch67 Aug 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/ef573c368767625ffbe8c32cf921ea7366f2dd53.zip
https://github.com/meshtastic/device-ui/archive/c868c2adecc210a74931cbd67e9e1ef5b0a874e7.zip

; Common libs for environmental measurements in telemetry module
[environmental_base]
Expand Down
24 changes: 13 additions & 11 deletions src/PowerFSM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,23 +173,25 @@ static void lsIdle()
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
break;

default:
// We woke for some other reason (button press, device IRQ interrupt)

#ifdef BUTTON_PIN
bool pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
#else
case ESP_SLEEP_WAKEUP_GPIO: {
bool pressed = false;
#if defined(BUTTON_PIN)
pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
#elif defined(KB_INT)
// keyboard press (probably) triggered GPIO interrupt
pressed = true;
#endif
if (pressed) { // If we woke because of press, instead generate a PRESS event.
if (pressed) {
powerFSM.trigger(EVENT_PRESS);
} else {
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
// we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code
powerFSM.trigger(EVENT_WAKE_TIMER);
}
break;
Comment on lines +176 to 187

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not classify every GPIO wake as a keyboard press.

With KB_INT but no BUTTON_PIN, this sets pressed = true for unrelated GPIO wakes; with both defined, #elif ignores a keyboard wake. Check each configured pin’s active level and retain EVENT_WAKE_TIMER for unrecognized GPIO sources.

Proposed fix
             case ESP_SLEEP_WAKEUP_GPIO: {
                 bool pressed = false;
 `#if` defined(BUTTON_PIN)
-                pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
-#elif defined(KB_INT)
-                // keyboard press (probably) triggered GPIO interrupt
-                pressed = true;
+                pressed |= !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
+#endif
+#if defined(KB_INT)
+#if KB_INT_WAKE_ON_HIGH
+                pressed |= digitalRead(KB_INT);
+#else
+                pressed |= !digitalRead(KB_INT);
+#endif
 `#endif`
                 if (pressed) {
                     powerFSM.trigger(EVENT_PRESS);
+                } else {
+                    powerFSM.trigger(EVENT_WAKE_TIMER);
                 }
                 break;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case ESP_SLEEP_WAKEUP_GPIO: {
bool pressed = false;
#if defined(BUTTON_PIN)
pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
#elif defined(KB_INT)
// keyboard press (probably) triggered GPIO interrupt
pressed = true;
#endif
if (pressed) { // If we woke because of press, instead generate a PRESS event.
if (pressed) {
powerFSM.trigger(EVENT_PRESS);
} else {
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
// we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code
powerFSM.trigger(EVENT_WAKE_TIMER);
}
break;
case ESP_SLEEP_WAKEUP_GPIO: {
bool pressed = false;
`#if` defined(BUTTON_PIN)
pressed |= !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
`#endif`
`#if` defined(KB_INT)
`#if` KB_INT_WAKE_ON_HIGH
pressed |= digitalRead(KB_INT);
`#else`
pressed |= !digitalRead(KB_INT);
`#endif`
`#endif`
if (pressed) {
powerFSM.trigger(EVENT_PRESS);
} else {
powerFSM.trigger(EVENT_WAKE_TIMER);
}
break;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/PowerFSM.cpp` around lines 176 - 187, Update the ESP_SLEEP_WAKEUP_GPIO
handling in PowerFSM.cpp so GPIO wake sources are matched against each
configured button or keyboard interrupt pin using its active level, rather than
defaulting pressed to true when KB_INT is defined. Trigger EVENT_PRESS only for
a recognized asserted input; otherwise preserve EVENT_WAKE_TIMER for unrelated
GPIO wakes, including configurations where both BUTTON_PIN and KB_INT are
defined.

}
default:
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
// we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code
powerFSM.trigger(EVENT_WAKE_TIMER);
break;
}
} else {
// Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so
delay(100);
Expand Down
5 changes: 5 additions & 0 deletions src/concurrency/Lock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ void Lock::lock()
}
}

bool Lock::lock(uint32_t timeout)
{
return xSemaphoreTake(handle, pdMS_TO_TICKS(timeout)) == pdTRUE;
}
Comment on lines +29 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Provide a non-FreeRTOS definition for the new overload.

Lock::lock(uint32_t) is declared unconditionally in src/concurrency/Lock.h, but this definition exists only under HAS_FREE_RTOS. Any non-FreeRTOS target that calls it will fail at link time.

Proposed fix
 `#else`
 Lock::Lock() {}
 
 Lock::~Lock() {}
 
 void Lock::lock() {}
+
+bool Lock::lock(uint32_t) { return true; }
 
 void Lock::unlock() {}
 `#endif`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bool Lock::lock(uint32_t timeout)
{
return xSemaphoreTake(handle, pdMS_TO_TICKS(timeout)) == pdTRUE;
}
`#else`
Lock::Lock() {}
Lock::~Lock() {}
void Lock::lock() {}
bool Lock::lock(uint32_t) { return true; }
void Lock::unlock() {}
`#endif`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/concurrency/Lock.cpp` around lines 29 - 32, Add a non-FreeRTOS
implementation of Lock::lock(uint32_t) alongside the existing platform-specific
Lock implementation, ensuring every target has a definition matching the
unconditional declaration in Lock.h. Preserve the FreeRTOS xSemaphoreTake
behavior and choose the existing non-FreeRTOS locking behavior for the overload.


void Lock::unlock()
{
if (xSemaphoreGive(handle) == false) {
Expand Down
5 changes: 5 additions & 0 deletions src/concurrency/Lock.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ class Lock
// Must not be called from an ISR.
void lock();

/// Locks the lock with timeout.
//
// Must not be called from an ISR.
bool lock(uint32_t timeout);

// Unlocks the lock.
//
// Must not be called from an ISR.
Expand Down
2 changes: 2 additions & 0 deletions src/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BBQ10_KB_ADDR 0x1F
#define MPR121_KB_ADDR 0x5A
#define TCA8418_KB_ADDR 0x34
#define TSTC8_KB_ADDR 0x6C // STC8H companion-MCU keypad on the ThinkNode-M9

// -----------------------------------------------------------------------------
// SENSOR
Expand All @@ -264,6 +265,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define QMC5883L_ADDR 0x0D
#define HMC5883L_ADDR 0x1E
#define MMC5983MA_ADDR 0x30
#define QMC6309_ADDR 0x7C
#define SHTC3_ADDR 0x70
#define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D
Expand Down
14 changes: 7 additions & 7 deletions src/detect/ScanI2C.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const

ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
{
ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004, MPR121KB, TCA8418KB};
return firstOfOrNONE(6, types);
ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004, MPR121KB, TCA8418KB, STC8HKB};
return firstOfOrNONE(7, types);
}

ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX,
ICM20948, QMA6100P, BMM150, BMI270, ICM42607P, ISM330DHCX};
return firstOfOrNONE(12, types);
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948,
QMA6100P, BMM150, BMI270, ICM42607P, ISM330DHCX, QMI8658};
return firstOfOrNONE(13, types);
}

ScanI2C::FoundDevice ScanI2C::firstMagnetometer() const
{
ScanI2C::DeviceType types[] = {MMC5983MA, IIS2MDCTR};
return firstOfOrNONE(2, types);
ScanI2C::DeviceType types[] = {MMC5983MA, IIS2MDCTR, QMC6309};
return firstOfOrNONE(3, types);
}

ScanI2C::FoundDevice ScanI2C::firstAQI() const
Expand Down
2 changes: 2 additions & 0 deletions src/detect/ScanI2C.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class ScanI2C
QMC5883L,
HMC5883L,
MMC5983MA,
QMC6309,
PMSA003I,
QMA6100P,
MPU6050,
Expand Down Expand Up @@ -102,6 +103,7 @@ class ScanI2C
IIS2MDCTR,
ISM330DHCX,
SPA06,
STC8HKB, // STC8H companion-MCU keypad (ThinkNode-M9)
} DeviceType;

// typedef uint8_t DeviceAddress;
Expand Down
15 changes: 15 additions & 0 deletions src/detect/ScanI2CTwoWire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(TSTC8_KB_ADDR, STC8HKB, "STC8H KB", (uint8_t)addr.address);

SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address);
#ifdef HAS_NCP5623
Expand Down Expand Up @@ -956,6 +957,20 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
foundDevices[addr] = type;
}
}

// The QMC6309 magnetometer sits at 0x7C, above the general scan ceiling (the loop above stops at 0x77 to
// avoid the reserved 0x78-0x7F block). Probe it explicitly. Gated on the SensorLib driver being present so
// only boards that can actually drive the chip poke this reserved address.
#if __has_include(<SensorQMC6309.hpp>)
addr.address = QMC6309_ADDR;
i2cBus->beginTransmission(addr.address);
if (i2cBus->endTransmission() == 0 &&
getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1) == 0x90 /* QMC6309 chip id */) {
deviceAddresses[QMC6309] = addr;
foundDevices[addr] = QMC6309;
logFoundDevice("QMC6309", (uint8_t)addr.address);
}
#endif
}

void ScanI2CTwoWire::scanPort(I2CPort port)
Expand Down
8 changes: 5 additions & 3 deletions src/gps/GPS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1645,7 +1645,7 @@ GnssModel_t GPS::probe(int serialSpeed)
{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352},
{"UC6580", "UC6580", GNSS_MODEL_UC6580},
{"UC6580", "UC6580", GNSS_MODEL_UC6580}
// as L76K is sort of a last ditch effort, we won't attempt to detect it by startup messages for now.
/*{"L76K", "SW=URANUS", GNSS_MODEL_MTK}*/};
GnssModel_t detectedDriver = getProbeResponse(500, passive_detect, serialSpeed);
Expand All @@ -1672,8 +1672,10 @@ GnssModel_t GPS::probe(int serialSpeed)
case 1: {

// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
std::vector<ChipInfo> unicore = {
{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
std::vector<ChipInfo> unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580},
{"UM600", "UM600", GNSS_MODEL_UC6580},
{"CM121", "CM121", GNSS_MODEL_CM121},
{"CC1167Q", "CC1167Q", GNSS_MODEL_CM121}};
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
currentDelay = 20;
currentStep = 2;
Expand Down
23 changes: 23 additions & 0 deletions src/graphics/draw/UIRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
#include "graphics/images.h"
#include "main.h"
#include "target_specific.h"
#ifdef COMPASS_SENSOR_DEBUG
#include "motion/MotionSensor.h"
#endif
#include <OLEDDisplay.h>
#include <cstring>
#include <gps/RTC.h>
Expand Down Expand Up @@ -1573,6 +1576,26 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
graphics::drawCommonHeader(display, x, y, titleStr);
const int *textPos = getTextPositions(display);

#ifdef COMPASS_SENSOR_DEBUG
// Optional raw IMU accel + magnetometer x/y/z readout for on-device axis/sign tuning.
{
char dbg[40];
float sx = 0, sy = 0, sz = 0;
uint32_t age = 0;
if (MotionSensor::getLatestCompassAccelSample(sx, sy, sz, age))
snprintf(dbg, sizeof(dbg), "A %.2f %.2f %.2f", sx, sy, sz);
else
snprintf(dbg, sizeof(dbg), "A ---");
display->drawString(x, textPos[line++], dbg);

if (MotionSensor::getLatestCompassMagSample(sx, sy, sz, age))
snprintf(dbg, sizeof(dbg), "M %.2f %.2f %.2f", sx, sy, sz);
else
snprintf(dbg, sizeof(dbg), "M ---");
display->drawString(x, textPos[line++], dbg);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
#endif

// === First Row: My Location ===
#if HAS_GPS
bool origBold = config.display.heading_bold;
Expand Down
155 changes: 155 additions & 0 deletions src/input/STC8HKeyboard.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#include "STC8HKeyboard.h"

#if defined(ELECROW_ThinkNode_M9)
#include "cardKbI2cImpl.h"

#include "configuration.h"

// ---------------------------------------------------------------------------
// STC8H companion-MCU keypad driver (ThinkNode-M9).
//
// The original STC8HKeyboard.cpp was lost from the reference source tree, so
// this was recovered from the linked reference firmware.elf (the .o was an LTO
// object with no machine code; the final ELF had the real inlined bodies).
//
// How the hardware works:
// - The STC8H raises KB_INT (rising edge, idle-low) when a key is pressed. The ISR
// latches key_event; is_key_event() just returns that flag.
// - The pressed key code is read over I2C from register 0x05.
// - is_key_state() polls KB_INT directly to keep the backlight lit while a
// key is held.
// - Battery voltage lives in registers 0x01..0x04, little-endian.
// - Sleep is requested by writing 0x01 to the STATE register (0x06).
// - The keypad backlight (KB_LED) and torch (PIN_LED) are plain host GPIOs,
// not I2C commands.
// ---------------------------------------------------------------------------

STC8HKeyboard Stc8HKeyBoard;

// ISR latched on each KB_INT rising edge (a key was pressed).
static void has_key_event()
{
Stc8HKeyBoard.key_event = true;
if (cardKbI2cImpl) {
cardKbI2cImpl->setIntervalFromNow(0);
// runASAP = true;
BaseType_t higherWake = 0;
concurrency::mainDelay.interruptFromISR(&higherWake);
}
}

void STC8HKeyboard::writeRegister(uint8_t reg, uint8_t val)
{
_pWire->beginTransmission(_I2C_addr);
_pWire->write(reg);
_pWire->write(val);
_pWire->endTransmission();
}

uint8_t STC8HKeyboard::readRegister(uint8_t reg)
{
_pWire->beginTransmission(_I2C_addr);
_pWire->write(reg);
if (_pWire->endTransmission(false) != 0)
return 0xFF;
if (_pWire->requestFrom(_I2C_addr, (uint8_t)1) != 1)
return 0xFF;
return _pWire->read();
}

void STC8HKeyboard::begin(uint8_t addr, TwoWire *wire)
{
LOG_DEBUG("STC8HKeyboard::begin() addr=0x%02x", addr);
_I2C_addr = addr;
_pWire = wire;
pinMode(KB_INT, INPUT);
#ifdef KB_LED
pinMode(KB_LED, OUTPUT);
#endif
#ifdef PIN_LED
pinMode(PIN_LED, OUTPUT);
#endif
attachInterrupt(KB_INT, has_key_event, RISING);
_pWire->begin();
Keyboard_state = true;
#ifdef ARCH_ESP32
// Detach/reattach the key interrupt around ESP32 light sleep
lsObserver.observe(&notifyLightSleep);
lsEndObserver.observe(&notifyLightSleepEnd);
#endif
}

bool STC8HKeyboard::is_Keyboard_begin()
{
return Keyboard_state;
}

// A key is currently active (KB_INT held); used to wake the keypad backlight.
bool STC8HKeyboard::is_key_state()
{
return digitalRead(KB_INT);
}

// A key-press interrupt has been latched since the flag was last cleared.
bool STC8HKeyboard::is_key_event()
{
return key_event;
}

uint8_t STC8HKeyboard::bsp_get_key_value()
{
return readRegister(0x01);
}
Comment on lines +99 to +102

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Read the matrix-key register, not the battery register.

0x01 is STC8_REG_ADDR_BATTERY; the declared matrix-key register is 0x05. Every keypress is currently decoded from battery data.

Proposed fix
 uint8_t STC8HKeyboard::bsp_get_key_value()
 {
-    return readRegister(0x01);
+    return readRegister(STC8_REG_ADDR_MATRIX_KEY);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uint8_t STC8HKeyboard::bsp_get_key_value()
{
return readRegister(0x01);
}
uint8_t STC8HKeyboard::bsp_get_key_value()
{
return readRegister(STC8_REG_ADDR_MATRIX_KEY);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/input/STC8HKeyboard.cpp` around lines 99 - 102, Update
STC8HKeyboard::bsp_get_key_value() to read the declared matrix-key register at
0x05 instead of the battery register address 0x01, preserving the existing
return flow.


// Battery millivolts: registers 0x01..0x04 read little-endian, low 16 bits.
uint16_t STC8HKeyboard::bsp_get_battery_voltage()
{
if (!Keyboard_state)
return 0;
uint32_t voltage = 0;
for (uint8_t i = 0; i < 4; i++)
voltage |= (uint32_t)readRegister(STC8_REG_ADDR_BATTERY + i) << (i * 8);
return voltage > 0xFFFF ? 0xFFFF : (uint16_t)voltage;
}

void STC8HKeyboard::set_keyboard_blight(bool state)
{
#ifdef KB_LED
digitalWrite(KB_LED, state);
#else
(void)state; // KB_LED pin not defined for this board
#endif
}

void STC8HKeyboard::switch_flashlight()
{
#ifdef PIN_LED
digitalWrite(PIN_LED, !digitalRead(PIN_LED));
#endif
// else: torch pin unresolved on this board (old board used PIN_LED 13,
// which the current variant assigns to BATTERY_PIN) -- see variant.h.
}

void STC8HKeyboard::set_sleep_status(void)
{
writeRegister(STC8_REG_ADDR_STATE, 0x01);
_pWire->end();
}

#ifdef ARCH_ESP32
// Detach the key interrupt before ESP32 light sleep, so it can't fire while asleep.
int STC8HKeyboard::beforeLightSleep(void *unused)
{
detachInterrupt(KB_INT);
return 0; // Indicates success
}

// Reattach the key interrupt after waking from light sleep.
int STC8HKeyboard::afterLightSleep(esp_sleep_wakeup_cause_t cause)
{
attachInterrupt(KB_INT, has_key_event, RISING);
return 0; // Indicates success
}
#endif

#endif // ELECROW_ThinkNode_M9
Loading
Loading