diff --git a/src/configuration.h b/src/configuration.h index fb96430bc..e116854c2 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -101,6 +101,7 @@ along with this program. If not, see . // I2C Keyboards (M5Stack, RAK14004, T-Deck) #define CARDKB_ADDR 0x5F #define TDECK_KB_ADDR 0x55 +#define BBQ10_KB_ADDR 0x1F // ----------------------------------------------------------------------------- // SENSOR diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 996bdf62a..bf206c190 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -30,8 +30,8 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { - ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, RAK14004}; - return firstOfOrNONE(3, types); + ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004}; + return firstOfOrNONE(4, types); } ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 418a6bf5e..15d9b1342 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -17,6 +17,7 @@ class ScanI2C RTC_PCF8563, CARDKB, TDECKKB, + BBQ10KB, RAK14004, PMU_AXP192_AXP2101, BME_680, diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 30f9e7b7c..ced1e34dd 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -213,6 +213,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) break; SCAN_SIMPLE_CASE(TDECK_KB_ADDR, TDECKKB, "T-Deck keyboard found\n"); + SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10 keyboard found\n"); SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "st7567 display found\n"); #ifdef HAS_NCP5623 SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623 RGB LED found\n"); diff --git a/src/input/BBQ10Keyboard.cpp b/src/input/BBQ10Keyboard.cpp new file mode 100644 index 000000000..f0459d8f7 --- /dev/null +++ b/src/input/BBQ10Keyboard.cpp @@ -0,0 +1,177 @@ +// Based on arturo182 arduino_bbq10kbd library https://github.com/arturo182/arduino_bbq10kbd + +#include + +#include "BBQ10Keyboard.h" + +#define _REG_VER 1 +#define _REG_CFG 2 +#define _REG_INT 3 +#define _REG_KEY 4 +#define _REG_BKL 5 +#define _REG_DEB 6 +#define _REG_FRQ 7 +#define _REG_RST 8 +#define _REG_FIF 9 + +#define _WRITE_MASK (1 << 7) + +#define CFG_OVERFLOW_ON (1 << 0) +#define CFG_OVERFLOW_INT (1 << 1) +#define CFG_CAPSLOCK_INT (1 << 2) +#define CFG_NUMLOCK_INT (1 << 3) +#define CFG_KEY_INT (1 << 4) +#define CFG_PANIC_INT (1 << 5) + +#define INT_OVERFLOW (1 << 0) +#define INT_CAPSLOCK (1 << 1) +#define INT_NUMLOCK (1 << 2) +#define INT_KEY (1 << 3) +#define INT_PANIC (1 << 4) + +#define KEY_CAPSLOCK (1 << 5) +#define KEY_NUMLOCK (1 << 6) +#define KEY_COUNT_MASK (0x1F) + +BBQ10Keyboard::BBQ10Keyboard() : m_wire(nullptr), m_addr(NULL), writeCallback(nullptr), readCallback(nullptr) {} + +void BBQ10Keyboard::begin(uint8_t addr, TwoWire *wire) +{ + m_addr = addr; + m_wire = wire; + + m_wire->begin(); + + reset(); +} + +void BBQ10Keyboard::begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr) +{ + m_addr = addr; + m_wire = nullptr; + writeCallback = w; + readCallback = r; + reset(); +} + +void BBQ10Keyboard::reset() +{ + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(_REG_RST); + m_wire->endTransmission(); + } + if (writeCallback) { + uint8_t data = 0; + writeCallback(m_addr, _REG_RST, &data, 0); + } + delay(100); +} + +void BBQ10Keyboard::attachInterrupt(uint8_t pin, void (*func)(void)) const +{ + pinMode(pin, INPUT_PULLUP); + ::attachInterrupt(digitalPinToInterrupt(pin), func, RISING); +} + +void BBQ10Keyboard::detachInterrupt(uint8_t pin) const +{ + ::detachInterrupt(pin); +} + +void BBQ10Keyboard::clearInterruptStatus() +{ + writeRegister(_REG_INT, 0x00); +} + +uint8_t BBQ10Keyboard::status() const +{ + return readRegister8(_REG_KEY); +} + +uint8_t BBQ10Keyboard::keyCount() const +{ + return status() & KEY_COUNT_MASK; +} + +BBQ10Keyboard::KeyEvent BBQ10Keyboard::keyEvent() const +{ + KeyEvent event = {.key = '\0', .state = StateIdle}; + + if (keyCount() == 0) + return event; + + const uint16_t buf = readRegister16(_REG_FIF); + event.key = buf >> 8; + event.state = KeyState(buf & 0xFF); + + return event; +} + +float BBQ10Keyboard::backlight() const +{ + return readRegister8(_REG_BKL) / 255.0f; +} + +void BBQ10Keyboard::setBacklight(float value) +{ + writeRegister(_REG_BKL, value * 255); +} + +uint8_t BBQ10Keyboard::readRegister8(uint8_t reg) const +{ + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(reg); + m_wire->endTransmission(); + + m_wire->requestFrom(m_addr, (uint8_t)1); + if (m_wire->available() < 1) + return 0; + + return m_wire->read(); + } + if (readCallback) { + uint8_t data; + readCallback(m_addr, reg, &data, 1); + return data; + } + return 0; +} + +uint16_t BBQ10Keyboard::readRegister16(uint8_t reg) const +{ + uint8_t data[2] = {0}; + // uint8_t low = 0, high = 0; + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(reg); + m_wire->endTransmission(); + + m_wire->requestFrom(m_addr, (uint8_t)2); + if (m_wire->available() < 2) + return 0; + data[0] = m_wire->read(); + data[1] = m_wire->read(); + } + if (readCallback) { + readCallback(m_addr, reg, data, 2); + } + return (data[1] << 8) | data[0]; +} + +void BBQ10Keyboard::writeRegister(uint8_t reg, uint8_t value) +{ + uint8_t data[2]; + data[0] = reg | _WRITE_MASK; + data[1] = value; + + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(data, sizeof(uint8_t) * 2); + m_wire->endTransmission(); + } + if (writeCallback) { + writeCallback(m_addr, data[0], &(data[1]), 1); + } +} diff --git a/src/input/BBQ10Keyboard.h b/src/input/BBQ10Keyboard.h new file mode 100644 index 000000000..da6de71d7 --- /dev/null +++ b/src/input/BBQ10Keyboard.h @@ -0,0 +1,46 @@ +// Based on arturo182 arduino_bbq10kbd library https://github.com/arturo182/arduino_bbq10kbd + +#include "configuration.h" +#include + +class BBQ10Keyboard +{ + public: + typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len); + + enum KeyState { StateIdle = 0, StatePress, StateLongPress, StateRelease }; + + struct KeyEvent { + char key; + KeyState state; + }; + + BBQ10Keyboard(); + + void begin(uint8_t addr = BBQ10_KB_ADDR, TwoWire *wire = &Wire); + + void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = BBQ10_KB_ADDR); + + void reset(void); + + void attachInterrupt(uint8_t pin, void (*func)(void)) const; + void detachInterrupt(uint8_t pin) const; + void clearInterruptStatus(void); + + uint8_t status(void) const; + uint8_t keyCount(void) const; + KeyEvent keyEvent(void) const; + + float backlight() const; + void setBacklight(float value); + + uint8_t readRegister8(uint8_t reg) const; + uint16_t readRegister16(uint8_t reg) const; + void writeRegister(uint8_t reg, uint8_t value); + + private: + TwoWire *m_wire; + uint8_t m_addr; + i2c_com_fptr_t readCallback; + i2c_com_fptr_t writeCallback; +}; diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index 44db1d952..e000f36eb 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -7,7 +7,7 @@ CardKbI2cImpl::CardKbI2cImpl() : KbI2cBase("cardKB") {} void CardKbI2cImpl::init() { - if (cardkb_found.address != CARDKB_ADDR && cardkb_found.address != TDECK_KB_ADDR) { + if (cardkb_found.address == 0x00) { disable(); return; } diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index f7fd62645..3289a665c 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -30,7 +30,7 @@ uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t len int32_t KbI2cBase::runOnce() { - if (cardkb_found.address != CARDKB_ADDR && cardkb_found.address != TDECK_KB_ADDR) { + if (cardkb_found.address == 0x00) { // Input device is not detected. return INT32_MAX; } @@ -41,11 +41,19 @@ int32_t KbI2cBase::runOnce() #ifdef I2C_SDA1 LOG_DEBUG("Using I2C Bus 1 (the second one)\n"); i2cBus = &Wire1; + if (cardkb_found.address == BBQ10_KB_ADDR) { + Q10keyboard.begin(BBQ10_KB_ADDR, &Wire1); + Q10keyboard.setBacklight(0); + } break; #endif case ScanI2C::WIRE: LOG_DEBUG("Using I2C Bus 0 (the first one)\n"); i2cBus = &Wire; + if (cardkb_found.address == BBQ10_KB_ADDR) { + Q10keyboard.begin(BBQ10_KB_ADDR, &Wire); + Q10keyboard.setBacklight(0); + } break; case ScanI2C::NO_I2C: default: @@ -53,7 +61,60 @@ int32_t KbI2cBase::runOnce() } } - if (kb_model == 0x02) { + switch (kb_model) { + case 0x11: { // BB Q10 + int keyCount = Q10keyboard.keyCount(); + while (keyCount--) { + const BBQ10Keyboard::KeyEvent key = Q10keyboard.keyEvent(); + if ((key.key != 0x00) && (key.state == BBQ10Keyboard::StateRelease)) { + InputEvent e; + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + e.source = this->_originName; + switch (key.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.key; + break; + case 0x12: // sym shift+2 + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; + e.kbchar = 0xb5; + break; + case 0x18: // sym shift+8 + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN; + e.kbchar = 0xb6; + break; + case 0x14: // Left (sym shift+4) + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT; + e.kbchar = 0x00; // tweak for destSelect + break; + case 0x16: // Right (sym shift+6) + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; + e.kbchar = 0x00; // tweak for destSelect + break; + case 0x0d: // Enter + case 0x0a: // apparently Enter on Q10 is a line feed instead of carriage return + 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.key; + break; + } + + if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) { + this->notifyObservers(&e); + } + } + } + break; + } + case 0x02: { // RAK14004 uint8_t rDataBuf[8] = {0}; uint8_t PrintDataBuf = 0; @@ -74,9 +135,12 @@ int32_t KbI2cBase::runOnce() e.kbchar = PrintDataBuf; this->notifyObservers(&e); } - } else if (kb_model == 0x00 || kb_model == 0x10) { - // m5 cardkb and T-Deck - i2cBus->requestFrom(kb_model == 0x00 ? CARDKB_ADDR : TDECK_KB_ADDR, 1); + break; + } + case 0x00: // CARDKB + case 0x10: { // T-DECK + + i2cBus->requestFrom((int)cardkb_found.address, 1); while (i2cBus->available()) { char c = i2cBus->read(); @@ -93,17 +157,19 @@ int32_t KbI2cBase::runOnce() break; case 0xb5: // Up e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; + e.kbchar = 0xb5; break; case 0xb6: // Down e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN; + e.kbchar = 0xb6; break; case 0xb4: // Left e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT; - e.kbchar = c; + e.kbchar = 0xb4; break; case 0xb7: // Right e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; - e.kbchar = c; + e.kbchar = 0xb7; break; case 0x0d: // Enter e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; @@ -121,7 +187,9 @@ int32_t KbI2cBase::runOnce() this->notifyObservers(&e); } } - } else { + break; + } + default: LOG_WARN("Unknown kb_model 0x%02x\n", kb_model); } return 300; diff --git a/src/input/kbI2cBase.h b/src/input/kbI2cBase.h index a0a4dd608..59c26e16d 100644 --- a/src/input/kbI2cBase.h +++ b/src/input/kbI2cBase.h @@ -1,5 +1,6 @@ #pragma once +#include "BBQ10Keyboard.h" #include "InputBroker.h" #include "Wire.h" #include "concurrency/OSThread.h" @@ -16,4 +17,6 @@ class KbI2cBase : public Observable, public concurrency::OST const char *_originName; TwoWire *i2cBus = 0; + + BBQ10Keyboard Q10keyboard; }; diff --git a/src/main.cpp b/src/main.cpp index 5f0eaca30..09fa62896 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -387,6 +387,10 @@ void setup() // assign an arbitrary value to distinguish from other models kb_model = 0x10; break; + case ScanI2C::DeviceType::BBQ10KB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x11; + break; default: // use this as default since it's also just zero LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00\n", kb_info.type); diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 1e605656f..ad71a7112 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -65,8 +65,8 @@ CannedMessageModule::CannedMessageModule() { if (moduleConfig.canned_message.enabled || CANNED_MESSAGE_MODULE_ENABLE) { this->loadProtoForModule(); - if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address != CARDKB_ADDR) && - (cardkb_found.address != TDECK_KB_ADDR) && !INPUTBROKER_MATRIX_TYPE && !CANNED_MESSAGE_MODULE_ENABLE) { + if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE && + !CANNED_MESSAGE_MODULE_ENABLE) { LOG_INFO("CannedMessageModule: No messages are configured. Module is disabled\n"); this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED; disable();