diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini
index dd190c9d4..60bc41d23 100644
--- a/arch/stm32/stm32.ini
+++ b/arch/stm32/stm32.ini
@@ -28,9 +28,8 @@ build_flags =
-fmerge-all-constants
-ffunction-sections
-fdata-sections
-
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - - - - - - - -<.pio/*/*/I2CKeyPad/*>
board_upload.offset_address = 0x08000000
upload_protocol = stlink
@@ -43,5 +42,6 @@ lib_deps =
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip
lib_ignore =
- mathertel/OneButton@2.6.1
Wire
+ I2CKeyPad
+ mathertel/OneButton@2.6.1
diff --git a/platformio.ini b/platformio.ini
index ecde59de2..4ed55ece0 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -71,6 +71,7 @@ lib_deps =
nanopb/Nanopb@0.4.91
# renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32
erriez/ErriezCRC32@1.0.1
+ robtillaart/I2CKeyPad@0.5.0
; Used for the code analysis in PIO Home / Inspect
check_tool = cppcheck
diff --git a/src/configuration.h b/src/configuration.h
index 32d99295e..ab2143a03 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -114,11 +114,12 @@ along with this program. If not, see .
// Define if screen should be mirrored left to right
// #define SCREEN_MIRROR
-// I2C Keyboards (M5Stack, RAK14004, T-Deck)
+// I2C Keyboards (M5Stack, RAK14004, T-Deck, PCF8574A passive)
#define CARDKB_ADDR 0x5F
#define TDECK_KB_ADDR 0x55
#define BBQ10_KB_ADDR 0x1F
#define MPR121_KB_ADDR 0x5A
+#define PCF8574A_ADDRESS 0x20
// -----------------------------------------------------------------------------
// SENSOR
diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp
index 5bd5c0d12..881b75405 100644
--- a/src/detect/ScanI2C.cpp
+++ b/src/detect/ScanI2C.cpp
@@ -31,8 +31,8 @@ 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, PCF8574A, MPR121KB, TCA8418KB};
+ return firstOfOrNONE(7, types);
}
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 72184db69..72b2bda81 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -18,6 +18,7 @@ class ScanI2C
TDECKKB,
BBQ10KB,
RAK14004,
+ PCF8574A,
PMU_AXP192_AXP2101, // has the same adress as the TCA8418KB
BME_680,
BME_280,
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index e2ba78a92..f8d9ae907 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -210,6 +210,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address);
+#ifndef HAS_TCA9535
+ SCAN_SIMPLE_CASE(PCF8574A_ADDRESS, PCF8574A, "PCF8574A", (uint8_t)addr.address);
+#endif
#ifdef HAS_NCP5623
SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623", (uint8_t)addr.address);
#endif
diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp
index 21ecf381a..e3d1ae8de 100644
--- a/src/input/cardKbI2cImpl.cpp
+++ b/src/input/cardKbI2cImpl.cpp
@@ -9,6 +9,10 @@ CardKbI2cImpl::CardKbI2cImpl() : KbI2cBase("cardKB") {}
void CardKbI2cImpl::init()
{
+ if (kb_model == 0x12) {
+ disable();
+ return;
+ }
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN)
if (cardkb_found.address == 0x00) {
LOG_DEBUG("Rescan for I2C keyboard");
diff --git a/src/input/peMatrixBase.cpp b/src/input/peMatrixBase.cpp
new file mode 100644
index 000000000..db96eae22
--- /dev/null
+++ b/src/input/peMatrixBase.cpp
@@ -0,0 +1,88 @@
+#include "peMatrixBase.h"
+
+#include "configuration.h"
+#include "detect/ScanI2C.h"
+
+extern ScanI2C::DeviceAddress cardkb_found;
+extern uint8_t kb_model;
+
+#if WIRE_INTERFACES_COUNT == 2 // defined in architecture.h
+I2CKeyPad keyPad(cardkb_found.address, cardkb_found.port == ScanI2C::WIRE1 ? &Wire1 : &Wire);
+#else
+I2CKeyPad keyPad(cardkb_found.address, &Wire);
+#endif
+
+PeMatrixBase::PeMatrixBase(const char *name) : concurrency::OSThread(name)
+{
+ this->_originName = name;
+}
+
+int32_t PeMatrixBase::runOnce()
+{
+ if (kb_model != 0x12) {
+ // Input device is not detected.
+ return disable();
+ }
+
+ if (firstTime) {
+ // This is the first time the OSThread library has called this function, so do port setup
+ firstTime = 0;
+ if (!keyPad.begin()) {
+ LOG_ERROR("Failed to initialize I2C keypad");
+ return disable();
+ }
+ keyPad.loadKeyMap(keymap);
+ } else {
+ if (keyPad.isPressed()) {
+ key = keyPad.getChar();
+ // debounce
+ if (key != prevkey) {
+ if (key != 0) {
+ LOG_DEBUG("Key 0x%x pressed\n", key);
+ // reset shift now that we have a keypress
+ InputEvent e;
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
+ e.source = this->_originName;
+ switch (key) {
+ case 0x1b: // ESC
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
+ break;
+ case 0x08: // Back
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK;
+ e.kbchar = key;
+ break;
+ case 0xb5: // Up
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP;
+ break;
+ case 0xb6: // Down
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN;
+ break;
+ case 0xb4: // Left
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT;
+ e.kbchar = key;
+ break;
+ case 0xb7: // Right
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
+ e.kbchar = key;
+ break;
+ case 0x0d: // Enter
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
+ break;
+ case 0x00: // nopress
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
+ break;
+ default: // all other keys
+ e.inputEvent = ANYKEY;
+ e.kbchar = key;
+ break;
+ }
+ if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) {
+ this->notifyObservers(&e);
+ }
+ }
+ prevkey = key;
+ }
+ }
+ }
+ return 100; // Keyscan every 100msec to avoid key bounce
+}
diff --git a/src/input/peMatrixBase.h b/src/input/peMatrixBase.h
new file mode 100644
index 000000000..299135601
--- /dev/null
+++ b/src/input/peMatrixBase.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "InputBroker.h"
+#include "concurrency/OSThread.h"
+#include
+
+class PeMatrixBase : public Observable, public concurrency::OSThread
+{
+ public:
+ explicit PeMatrixBase(const char *name);
+
+ protected:
+ virtual int32_t runOnce() override;
+
+ private:
+ const char *_originName;
+ bool firstTime = 1;
+ // char keymap[19] = "123A456B789C*0#DNF"; // N = NoKey, F = Fail
+ char keymap[19] = {0x1b, 0xb5, '3', 'A', 0xb4, 0x0d, 0xb7, 'B', '7', 0xb6, '9', 'C', 0x09, '0', 0x08, 'D', 'N', 'F'};
+ char key = 0;
+ char prevkey = 0;
+};
\ No newline at end of file
diff --git a/src/input/peMatrixImpl.cpp b/src/input/peMatrixImpl.cpp
new file mode 100644
index 000000000..57d1c03f6
--- /dev/null
+++ b/src/input/peMatrixImpl.cpp
@@ -0,0 +1,16 @@
+#include "peMatrixImpl.h"
+#include "InputBroker.h"
+
+PeMatrixImpl *peMatrixImpl;
+
+PeMatrixImpl::PeMatrixImpl() : PeMatrixBase("matrixPE") {}
+
+void PeMatrixImpl::init()
+{
+ if (kb_model != 0x12) {
+ disable();
+ return;
+ }
+
+ inputBroker->registerSource(this);
+}
diff --git a/src/input/peMatrixImpl.h b/src/input/peMatrixImpl.h
new file mode 100644
index 000000000..eba100166
--- /dev/null
+++ b/src/input/peMatrixImpl.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "main.h"
+#include "peMatrixBase.h"
+
+/**
+ * @brief The idea behind this class to have static methods for the event handlers.
+ * Check attachInterrupt() at RotaryEncoderInteruptBase.cpp
+ * Technically you can have as many rotary encoders hardver attached
+ * to your device as you wish, but you always need to have separate event
+ * handlers, thus you need to have a RotaryEncoderInterrupt implementation.
+ */
+class PeMatrixImpl : public PeMatrixBase
+{
+ public:
+ PeMatrixImpl();
+ void init();
+};
+
+extern PeMatrixImpl *peMatrixImpl;
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index c12707cdb..46fe1e5f1 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -636,6 +636,9 @@ void setup()
// assign an arbitrary value to distinguish from other models
kb_model = 0x11;
break;
+ case ScanI2C::DeviceType::PCF8574A:
+ kb_model = 0x12;
+ break;
case ScanI2C::DeviceType::MPR121KB:
// assign an arbitrary value to distinguish from other models
kb_model = 0x37;
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index fac2ca976..001db1969 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -11,6 +11,7 @@
#include "input/cardKbI2cImpl.h"
#endif
#include "input/kbMatrixImpl.h"
+#include "input/peMatrixImpl.h"
#endif
#if !MESHTASTIC_EXCLUDE_ADMIN
#include "modules/AdminModule.h"
@@ -178,10 +179,15 @@ void setupModules()
kbMatrixImpl = new KbMatrixImpl();
kbMatrixImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
+
+ peMatrixImpl = new PeMatrixImpl();
+ peMatrixImpl->init();
+
#ifdef INPUTBROKER_SERIAL_TYPE
aSerialKeyboardImpl = new SerialKeyboardImpl();
aSerialKeyboardImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
+
#endif // HAS_BUTTON
#if ARCH_PORTDUINO && !HAS_TFT
aLinuxInputImpl = new LinuxInputImpl();
diff --git a/variants/seeed-sensecap-indicator/variant.h b/variants/seeed-sensecap-indicator/variant.h
index 1010e04c8..1be93f642 100644
--- a/variants/seeed-sensecap-indicator/variant.h
+++ b/variants/seeed-sensecap-indicator/variant.h
@@ -16,6 +16,9 @@
// #define ADC_CHANNEL ADC1_GPIO27_CHANNEL
// #define ADC_MULTIPLIER 2
+// Portexpander
+#define HAS_TCA9535
+
// ST7701 TFT LCD
#define ST7701_CS (4 | IO_EXPANDER)
#define ST7701_RS -1 // DC