mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-17 23:37:44 +00:00
add a .clang-format file (#9154)
This commit is contained in:
@@ -37,145 +37,119 @@
|
||||
|
||||
BBQ10Keyboard::BBQ10Keyboard() : m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr) {}
|
||||
|
||||
void BBQ10Keyboard::begin(uint8_t addr, TwoWire *wire)
|
||||
{
|
||||
m_addr = addr;
|
||||
m_wire = wire;
|
||||
void BBQ10Keyboard::begin(uint8_t addr, TwoWire *wire) {
|
||||
m_addr = addr;
|
||||
m_wire = wire;
|
||||
|
||||
m_wire->begin();
|
||||
m_wire->begin();
|
||||
|
||||
reset();
|
||||
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::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);
|
||||
writeRegister(_REG_CFG, readRegister8(_REG_CFG) | CFG_REPORT_MODS);
|
||||
delay(100);
|
||||
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);
|
||||
writeRegister(_REG_CFG, readRegister8(_REG_CFG) | CFG_REPORT_MODS);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
void BBQ10Keyboard::attachInterrupt(uint8_t pin, void (*func)(void)) const
|
||||
{
|
||||
pinMode(pin, INPUT_PULLUP);
|
||||
::attachInterrupt(digitalPinToInterrupt(pin), func, RISING);
|
||||
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::detachInterrupt(uint8_t pin) const { ::detachInterrupt(pin); }
|
||||
|
||||
void BBQ10Keyboard::clearInterruptStatus()
|
||||
{
|
||||
writeRegister(_REG_INT, 0x00);
|
||||
}
|
||||
void BBQ10Keyboard::clearInterruptStatus() { writeRegister(_REG_INT, 0x00); }
|
||||
|
||||
uint8_t BBQ10Keyboard::status() const
|
||||
{
|
||||
return readRegister8(_REG_KEY);
|
||||
}
|
||||
uint8_t BBQ10Keyboard::status() const { return readRegister8(_REG_KEY); }
|
||||
|
||||
uint8_t BBQ10Keyboard::keyCount() const
|
||||
{
|
||||
return status() & KEY_COUNT_MASK;
|
||||
}
|
||||
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);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
void BBQ10Keyboard::setBacklight(float value)
|
||||
{
|
||||
writeRegister(_REG_BKL, value * 255);
|
||||
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];
|
||||
}
|
||||
|
||||
uint8_t BBQ10Keyboard::readRegister8(uint8_t reg) const
|
||||
{
|
||||
if (m_wire) {
|
||||
m_wire->beginTransmission(m_addr);
|
||||
m_wire->write(reg);
|
||||
m_wire->endTransmission();
|
||||
void BBQ10Keyboard::writeRegister(uint8_t reg, uint8_t value) {
|
||||
uint8_t data[2];
|
||||
data[0] = reg | _WRITE_MASK;
|
||||
data[1] = value;
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,44 +8,43 @@
|
||||
#define KEY_MOD_SHR (0x1C)
|
||||
#define KEY_MOD_SYM (0x1D)
|
||||
|
||||
class BBQ10Keyboard
|
||||
{
|
||||
public:
|
||||
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
|
||||
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 };
|
||||
enum KeyState { StateIdle = 0, StatePress, StateLongPress, StateRelease };
|
||||
|
||||
struct KeyEvent {
|
||||
char key;
|
||||
KeyState state;
|
||||
};
|
||||
struct KeyEvent {
|
||||
char key;
|
||||
KeyState state;
|
||||
};
|
||||
|
||||
BBQ10Keyboard();
|
||||
BBQ10Keyboard();
|
||||
|
||||
void begin(uint8_t addr = BBQ10_KB_ADDR, TwoWire *wire = &Wire);
|
||||
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 begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = BBQ10_KB_ADDR);
|
||||
|
||||
void reset(void);
|
||||
void reset(void);
|
||||
|
||||
void attachInterrupt(uint8_t pin, void (*func)(void)) const;
|
||||
void detachInterrupt(uint8_t pin) const;
|
||||
void clearInterruptStatus(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;
|
||||
uint8_t status(void) const;
|
||||
uint8_t keyCount(void) const;
|
||||
KeyEvent keyEvent(void) const;
|
||||
|
||||
float backlight() const;
|
||||
void setBacklight(float value);
|
||||
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);
|
||||
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;
|
||||
private:
|
||||
TwoWire *m_wire;
|
||||
uint8_t m_addr;
|
||||
i2c_com_fptr_t readCallback;
|
||||
i2c_com_fptr_t writeCallback;
|
||||
};
|
||||
|
||||
@@ -22,307 +22,291 @@ using namespace concurrency;
|
||||
|
||||
#if HAS_BUTTON
|
||||
#endif
|
||||
ButtonThread::ButtonThread(const char *name) : OSThread(name)
|
||||
{
|
||||
_originName = name;
|
||||
}
|
||||
ButtonThread::ButtonThread(const char *name) : OSThread(name) { _originName = name; }
|
||||
|
||||
bool ButtonThread::initButton(const ButtonConfig &config)
|
||||
{
|
||||
if (inputBroker)
|
||||
inputBroker->registerSource(this);
|
||||
_longPressTime = config.longPressTime;
|
||||
_longLongPressTime = config.longLongPressTime;
|
||||
_pinNum = config.pinNumber;
|
||||
_activeLow = config.activeLow;
|
||||
_touchQuirk = config.touchQuirk;
|
||||
_intRoutine = config.intRoutine;
|
||||
_longLongPress = config.longLongPress;
|
||||
bool ButtonThread::initButton(const ButtonConfig &config) {
|
||||
if (inputBroker)
|
||||
inputBroker->registerSource(this);
|
||||
_longPressTime = config.longPressTime;
|
||||
_longLongPressTime = config.longLongPressTime;
|
||||
_pinNum = config.pinNumber;
|
||||
_activeLow = config.activeLow;
|
||||
_touchQuirk = config.touchQuirk;
|
||||
_intRoutine = config.intRoutine;
|
||||
_longLongPress = config.longLongPress;
|
||||
|
||||
userButton = OneButton(config.pinNumber, config.activeLow, config.activePullup);
|
||||
userButton = OneButton(config.pinNumber, config.activeLow, config.activePullup);
|
||||
|
||||
if (config.pullupSense != 0) {
|
||||
pinMode(config.pinNumber, config.pullupSense);
|
||||
}
|
||||
if (config.pullupSense != 0) {
|
||||
pinMode(config.pinNumber, config.pullupSense);
|
||||
}
|
||||
|
||||
_singlePress = config.singlePress;
|
||||
userButton.attachClick(
|
||||
_singlePress = config.singlePress;
|
||||
userButton.attachClick(
|
||||
[](void *callerThread) -> void {
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
thread->btnEvent = BUTTON_EVENT_PRESSED;
|
||||
},
|
||||
this);
|
||||
|
||||
_longPress = config.longPress;
|
||||
userButton.attachLongPressStart(
|
||||
[](void *callerThread) -> void {
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
// if (millis() > 30000) // hold off 30s after boot
|
||||
thread->btnEvent = BUTTON_EVENT_LONG_PRESSED;
|
||||
},
|
||||
this);
|
||||
userButton.attachLongPressStop(
|
||||
[](void *callerThread) -> void {
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
// if (millis() > 30000) // hold off 30s after boot
|
||||
thread->btnEvent = BUTTON_EVENT_LONG_RELEASED;
|
||||
},
|
||||
this);
|
||||
|
||||
if (config.doublePress != INPUT_BROKER_NONE) {
|
||||
_doublePress = config.doublePress;
|
||||
userButton.attachDoubleClick(
|
||||
[](void *callerThread) -> void {
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
thread->btnEvent = BUTTON_EVENT_PRESSED;
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
thread->btnEvent = BUTTON_EVENT_DOUBLE_PRESSED;
|
||||
},
|
||||
this);
|
||||
}
|
||||
|
||||
_longPress = config.longPress;
|
||||
userButton.attachLongPressStart(
|
||||
if (config.triplePress != INPUT_BROKER_NONE) {
|
||||
_triplePress = config.triplePress;
|
||||
userButton.attachMultiClick(
|
||||
[](void *callerThread) -> void {
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
// if (millis() > 30000) // hold off 30s after boot
|
||||
thread->btnEvent = BUTTON_EVENT_LONG_PRESSED;
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
thread->storeClickCount();
|
||||
thread->btnEvent = BUTTON_EVENT_MULTI_PRESSED;
|
||||
},
|
||||
this);
|
||||
userButton.attachLongPressStop(
|
||||
[](void *callerThread) -> void {
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
// if (millis() > 30000) // hold off 30s after boot
|
||||
thread->btnEvent = BUTTON_EVENT_LONG_RELEASED;
|
||||
},
|
||||
this);
|
||||
|
||||
if (config.doublePress != INPUT_BROKER_NONE) {
|
||||
_doublePress = config.doublePress;
|
||||
userButton.attachDoubleClick(
|
||||
[](void *callerThread) -> void {
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
thread->btnEvent = BUTTON_EVENT_DOUBLE_PRESSED;
|
||||
},
|
||||
this);
|
||||
}
|
||||
|
||||
if (config.triplePress != INPUT_BROKER_NONE) {
|
||||
_triplePress = config.triplePress;
|
||||
userButton.attachMultiClick(
|
||||
[](void *callerThread) -> void {
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
thread->storeClickCount();
|
||||
thread->btnEvent = BUTTON_EVENT_MULTI_PRESSED;
|
||||
},
|
||||
this);
|
||||
}
|
||||
if (config.shortLong != INPUT_BROKER_NONE) {
|
||||
_shortLong = config.shortLong;
|
||||
}
|
||||
}
|
||||
if (config.shortLong != INPUT_BROKER_NONE) {
|
||||
_shortLong = config.shortLong;
|
||||
}
|
||||
#ifdef USE_EINK
|
||||
userButton.setDebounceMs(0);
|
||||
userButton.setDebounceMs(0);
|
||||
#else
|
||||
userButton.setDebounceMs(1);
|
||||
userButton.setDebounceMs(1);
|
||||
#endif
|
||||
userButton.setPressMs(_longPressTime);
|
||||
userButton.setPressMs(_longPressTime);
|
||||
|
||||
if (screen) {
|
||||
userButton.setClickMs(20);
|
||||
} else {
|
||||
userButton.setClickMs(BUTTON_CLICK_MS);
|
||||
}
|
||||
attachButtonInterrupts();
|
||||
if (screen) {
|
||||
userButton.setClickMs(20);
|
||||
} else {
|
||||
userButton.setClickMs(BUTTON_CLICK_MS);
|
||||
}
|
||||
attachButtonInterrupts();
|
||||
#ifdef ARCH_ESP32
|
||||
// Register callbacks for before and after lightsleep
|
||||
// Used to detach and reattach interrupts
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
// Register callbacks for before and after lightsleep
|
||||
// Used to detach and reattach interrupts
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
#endif
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t ButtonThread::runOnce()
|
||||
{
|
||||
// If the button is pressed we suppress CPU sleep until release
|
||||
canSleep = true; // Assume we should not keep the board awake
|
||||
int32_t ButtonThread::runOnce() {
|
||||
// If the button is pressed we suppress CPU sleep until release
|
||||
canSleep = true; // Assume we should not keep the board awake
|
||||
|
||||
// Check for combination timeout
|
||||
if (waitingForLongPress && (millis() - shortPressTime) > BUTTON_COMBO_TIMEOUT_MS) {
|
||||
waitingForLongPress = false;
|
||||
// Check for combination timeout
|
||||
if (waitingForLongPress && (millis() - shortPressTime) > BUTTON_COMBO_TIMEOUT_MS) {
|
||||
waitingForLongPress = false;
|
||||
}
|
||||
|
||||
userButton.tick();
|
||||
canSleep &= userButton.isIdle();
|
||||
|
||||
// Check if we should play lead-up sound during long press
|
||||
// Play lead-up when button has been held for BUTTON_LEADUP_MS but before long press triggers
|
||||
bool buttonCurrentlyPressed = isButtonPressed(_pinNum);
|
||||
|
||||
// Detect start of button press
|
||||
if (buttonCurrentlyPressed && !buttonWasPressed) {
|
||||
buttonPressStartTime = millis();
|
||||
leadUpPlayed = false;
|
||||
leadUpSequenceActive = false;
|
||||
resetLeadUpSequence();
|
||||
}
|
||||
|
||||
// Progressive lead-up sound system
|
||||
if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
|
||||
|
||||
// Start the progressive sequence if not already active
|
||||
if (!leadUpSequenceActive) {
|
||||
leadUpSequenceActive = true;
|
||||
lastLeadUpNoteTime = millis();
|
||||
playNextLeadUpNote(); // Play the first note immediately
|
||||
}
|
||||
// Continue playing notes at intervals
|
||||
else if ((millis() - lastLeadUpNoteTime) >= 400) { // 400ms interval between notes
|
||||
if (playNextLeadUpNote()) {
|
||||
lastLeadUpNoteTime = millis();
|
||||
} else {
|
||||
leadUpPlayed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset when button is released
|
||||
if (!buttonCurrentlyPressed && buttonWasPressed) {
|
||||
leadUpSequenceActive = false;
|
||||
resetLeadUpSequence();
|
||||
}
|
||||
|
||||
buttonWasPressed = buttonCurrentlyPressed;
|
||||
|
||||
// new behavior
|
||||
if (btnEvent != BUTTON_EVENT_NONE) {
|
||||
InputEvent evt;
|
||||
evt.source = _originName;
|
||||
evt.kbchar = 0;
|
||||
evt.touchX = 0;
|
||||
evt.touchY = 0;
|
||||
switch (btnEvent) {
|
||||
case BUTTON_EVENT_PRESSED: {
|
||||
// Forward single press to InputBroker (but NOT as DOWN/SELECT, just forward a "button press" event)
|
||||
evt.inputEvent = _singlePress;
|
||||
// evt.kbchar = _singlePress; // todo: fix this. Some events are kb characters rather than event types
|
||||
this->notifyObservers(&evt);
|
||||
|
||||
// Start tracking for potential combination
|
||||
waitingForLongPress = true;
|
||||
shortPressTime = millis();
|
||||
|
||||
break;
|
||||
}
|
||||
case BUTTON_EVENT_LONG_PRESSED: {
|
||||
// Ignore if: TX in progress
|
||||
// Uncommon T-Echo hardware bug, LoRa TX triggers touch button
|
||||
if (_touchQuirk && RadioLibInterface::instance && RadioLibInterface::instance->isSending())
|
||||
break;
|
||||
|
||||
// Check if this is part of a short-press + long-press combination
|
||||
if (_shortLong != INPUT_BROKER_NONE && waitingForLongPress && (millis() - shortPressTime) <= BUTTON_COMBO_TIMEOUT_MS) {
|
||||
evt.inputEvent = _shortLong;
|
||||
// evt.kbchar = _shortLong;
|
||||
this->notifyObservers(&evt);
|
||||
// Play the combination tune
|
||||
playComboTune();
|
||||
|
||||
break;
|
||||
}
|
||||
if (_longPress != INPUT_BROKER_NONE) {
|
||||
// Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event)
|
||||
evt.inputEvent = _longPress;
|
||||
this->notifyObservers(&evt);
|
||||
}
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
userButton.tick();
|
||||
canSleep &= userButton.isIdle();
|
||||
case BUTTON_EVENT_DOUBLE_PRESSED: { // not wired in if screen detected
|
||||
LOG_INFO("Double press!");
|
||||
|
||||
// Check if we should play lead-up sound during long press
|
||||
// Play lead-up when button has been held for BUTTON_LEADUP_MS but before long press triggers
|
||||
bool buttonCurrentlyPressed = isButtonPressed(_pinNum);
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
// Detect start of button press
|
||||
if (buttonCurrentlyPressed && !buttonWasPressed) {
|
||||
buttonPressStartTime = millis();
|
||||
leadUpPlayed = false;
|
||||
leadUpSequenceActive = false;
|
||||
resetLeadUpSequence();
|
||||
evt.inputEvent = _doublePress;
|
||||
// evt.kbchar = _doublePress;
|
||||
this->notifyObservers(&evt);
|
||||
playComboTune();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Progressive lead-up sound system
|
||||
if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
|
||||
case BUTTON_EVENT_MULTI_PRESSED: { // not wired in when screen is present
|
||||
LOG_INFO("Mulitipress! %hux", multipressClickCount);
|
||||
|
||||
// Start the progressive sequence if not already active
|
||||
if (!leadUpSequenceActive) {
|
||||
leadUpSequenceActive = true;
|
||||
lastLeadUpNoteTime = millis();
|
||||
playNextLeadUpNote(); // Play the first note immediately
|
||||
}
|
||||
// Continue playing notes at intervals
|
||||
else if ((millis() - lastLeadUpNoteTime) >= 400) { // 400ms interval between notes
|
||||
if (playNextLeadUpNote()) {
|
||||
lastLeadUpNoteTime = millis();
|
||||
} else {
|
||||
leadUpPlayed = true;
|
||||
}
|
||||
}
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
switch (multipressClickCount) {
|
||||
case 3:
|
||||
evt.inputEvent = _triplePress;
|
||||
// evt.kbchar = _triplePress;
|
||||
this->notifyObservers(&evt);
|
||||
playComboTune();
|
||||
break;
|
||||
|
||||
// No valid multipress action
|
||||
default:
|
||||
break;
|
||||
} // end switch: click count
|
||||
|
||||
break;
|
||||
} // end multipress event
|
||||
|
||||
// Do actual shutdown when button released, otherwise the button release
|
||||
// may wake the board immediatedly.
|
||||
case BUTTON_EVENT_LONG_RELEASED: {
|
||||
|
||||
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
|
||||
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE && (millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) {
|
||||
evt.inputEvent = _longLongPress;
|
||||
this->notifyObservers(&evt);
|
||||
}
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
leadUpPlayed = false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset when button is released
|
||||
if (!buttonCurrentlyPressed && buttonWasPressed) {
|
||||
leadUpSequenceActive = false;
|
||||
resetLeadUpSequence();
|
||||
// doesn't handle BUTTON_EVENT_PRESSED_SCREEN BUTTON_EVENT_TOUCH_LONG_PRESSED BUTTON_EVENT_COMBO_SHORT_LONG
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
|
||||
buttonWasPressed = buttonCurrentlyPressed;
|
||||
|
||||
// new behavior
|
||||
if (btnEvent != BUTTON_EVENT_NONE) {
|
||||
InputEvent evt;
|
||||
evt.source = _originName;
|
||||
evt.kbchar = 0;
|
||||
evt.touchX = 0;
|
||||
evt.touchY = 0;
|
||||
switch (btnEvent) {
|
||||
case BUTTON_EVENT_PRESSED: {
|
||||
// Forward single press to InputBroker (but NOT as DOWN/SELECT, just forward a "button press" event)
|
||||
evt.inputEvent = _singlePress;
|
||||
// evt.kbchar = _singlePress; // todo: fix this. Some events are kb characters rather than event types
|
||||
this->notifyObservers(&evt);
|
||||
|
||||
// Start tracking for potential combination
|
||||
waitingForLongPress = true;
|
||||
shortPressTime = millis();
|
||||
|
||||
break;
|
||||
}
|
||||
case BUTTON_EVENT_LONG_PRESSED: {
|
||||
// Ignore if: TX in progress
|
||||
// Uncommon T-Echo hardware bug, LoRa TX triggers touch button
|
||||
if (_touchQuirk && RadioLibInterface::instance && RadioLibInterface::instance->isSending())
|
||||
break;
|
||||
|
||||
// Check if this is part of a short-press + long-press combination
|
||||
if (_shortLong != INPUT_BROKER_NONE && waitingForLongPress &&
|
||||
(millis() - shortPressTime) <= BUTTON_COMBO_TIMEOUT_MS) {
|
||||
evt.inputEvent = _shortLong;
|
||||
// evt.kbchar = _shortLong;
|
||||
this->notifyObservers(&evt);
|
||||
// Play the combination tune
|
||||
playComboTune();
|
||||
|
||||
break;
|
||||
}
|
||||
if (_longPress != INPUT_BROKER_NONE) {
|
||||
// Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event)
|
||||
evt.inputEvent = _longPress;
|
||||
this->notifyObservers(&evt);
|
||||
}
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case BUTTON_EVENT_DOUBLE_PRESSED: { // not wired in if screen detected
|
||||
LOG_INFO("Double press!");
|
||||
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
evt.inputEvent = _doublePress;
|
||||
// evt.kbchar = _doublePress;
|
||||
this->notifyObservers(&evt);
|
||||
playComboTune();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case BUTTON_EVENT_MULTI_PRESSED: { // not wired in when screen is present
|
||||
LOG_INFO("Mulitipress! %hux", multipressClickCount);
|
||||
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
|
||||
switch (multipressClickCount) {
|
||||
case 3:
|
||||
evt.inputEvent = _triplePress;
|
||||
// evt.kbchar = _triplePress;
|
||||
this->notifyObservers(&evt);
|
||||
playComboTune();
|
||||
break;
|
||||
|
||||
// No valid multipress action
|
||||
default:
|
||||
break;
|
||||
} // end switch: click count
|
||||
|
||||
break;
|
||||
} // end multipress event
|
||||
|
||||
// Do actual shutdown when button released, otherwise the button release
|
||||
// may wake the board immediatedly.
|
||||
case BUTTON_EVENT_LONG_RELEASED: {
|
||||
|
||||
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
|
||||
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
|
||||
(millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) {
|
||||
evt.inputEvent = _longLongPress;
|
||||
this->notifyObservers(&evt);
|
||||
}
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
leadUpPlayed = false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// doesn't handle BUTTON_EVENT_PRESSED_SCREEN BUTTON_EVENT_TOUCH_LONG_PRESSED BUTTON_EVENT_COMBO_SHORT_LONG
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
btnEvent = BUTTON_EVENT_NONE;
|
||||
}
|
||||
btnEvent = BUTTON_EVENT_NONE;
|
||||
|
||||
// only pull when the button is pressed, we get notified via IRQ on a new press
|
||||
if (!userButton.isIdle() || waitingForLongPress) {
|
||||
return 50;
|
||||
}
|
||||
return 100; // FIXME: Why can't we rely on interrupts and use INT32_MAX here?
|
||||
// only pull when the button is pressed, we get notified via IRQ on a new press
|
||||
if (!userButton.isIdle() || waitingForLongPress) {
|
||||
return 50;
|
||||
}
|
||||
return 100; // FIXME: Why can't we rely on interrupts and use INT32_MAX here?
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach (or re-attach) hardware interrupts for buttons
|
||||
* Public method. Used outside class when waking from MCU sleep
|
||||
*/
|
||||
void ButtonThread::attachButtonInterrupts()
|
||||
{
|
||||
// Interrupt for user button, during normal use. Improves responsiveness.
|
||||
attachInterrupt(_pinNum, _intRoutine, CHANGE);
|
||||
void ButtonThread::attachButtonInterrupts() {
|
||||
// Interrupt for user button, during normal use. Improves responsiveness.
|
||||
attachInterrupt(_pinNum, _intRoutine, CHANGE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Detach the "normal" button interrupts.
|
||||
* Public method. Used before attaching a "wake-on-button" interrupt for MCU sleep
|
||||
*/
|
||||
void ButtonThread::detachButtonInterrupts()
|
||||
{
|
||||
detachInterrupt(_pinNum);
|
||||
}
|
||||
void ButtonThread::detachButtonInterrupts() { detachInterrupt(_pinNum); }
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
|
||||
// Detach our class' interrupts before lightsleep
|
||||
// Allows sleep.cpp to configure its own interrupts, which wake the device on user-button press
|
||||
int ButtonThread::beforeLightSleep(void *unused)
|
||||
{
|
||||
detachButtonInterrupts();
|
||||
return 0; // Indicates success
|
||||
int ButtonThread::beforeLightSleep(void *unused) {
|
||||
detachButtonInterrupts();
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
// Reconfigure our interrupts
|
||||
// Our class' interrupts were disconnected during sleep, to allow the user button to wake the device from sleep
|
||||
int ButtonThread::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
||||
{
|
||||
attachButtonInterrupts();
|
||||
return 0; // Indicates success
|
||||
int ButtonThread::afterLightSleep(esp_sleep_wakeup_cause_t cause) {
|
||||
attachButtonInterrupts();
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Non-static method, runs during callback. Grabs info while still valid
|
||||
void ButtonThread::storeClickCount()
|
||||
{
|
||||
multipressClickCount = userButton.getNumberClicks();
|
||||
}
|
||||
void ButtonThread::storeClickCount() { multipressClickCount = userButton.getNumberClicks(); }
|
||||
@@ -8,23 +8,23 @@
|
||||
typedef void (*voidFuncPtr)(void);
|
||||
|
||||
struct ButtonConfig {
|
||||
uint8_t pinNumber;
|
||||
bool activeLow = true;
|
||||
bool activePullup = true;
|
||||
uint32_t pullupSense = 0;
|
||||
voidFuncPtr intRoutine = nullptr;
|
||||
input_broker_event singlePress = INPUT_BROKER_NONE;
|
||||
input_broker_event longPress = INPUT_BROKER_NONE;
|
||||
uint16_t longPressTime = 500;
|
||||
input_broker_event doublePress = INPUT_BROKER_NONE;
|
||||
input_broker_event longLongPress = INPUT_BROKER_NONE;
|
||||
uint16_t longLongPressTime = 3900;
|
||||
input_broker_event triplePress = INPUT_BROKER_NONE;
|
||||
input_broker_event shortLong = INPUT_BROKER_NONE;
|
||||
bool touchQuirk = false;
|
||||
uint8_t pinNumber;
|
||||
bool activeLow = true;
|
||||
bool activePullup = true;
|
||||
uint32_t pullupSense = 0;
|
||||
voidFuncPtr intRoutine = nullptr;
|
||||
input_broker_event singlePress = INPUT_BROKER_NONE;
|
||||
input_broker_event longPress = INPUT_BROKER_NONE;
|
||||
uint16_t longPressTime = 500;
|
||||
input_broker_event doublePress = INPUT_BROKER_NONE;
|
||||
input_broker_event longLongPress = INPUT_BROKER_NONE;
|
||||
uint16_t longLongPressTime = 3900;
|
||||
input_broker_event triplePress = INPUT_BROKER_NONE;
|
||||
input_broker_event shortLong = INPUT_BROKER_NONE;
|
||||
bool touchQuirk = false;
|
||||
|
||||
// Constructor to set required parameter
|
||||
explicit ButtonConfig(uint8_t pin = 0) : pinNumber(pin) {}
|
||||
// Constructor to set required parameter
|
||||
explicit ButtonConfig(uint8_t pin = 0) : pinNumber(pin) {}
|
||||
};
|
||||
|
||||
#ifndef BUTTON_CLICK_MS
|
||||
@@ -43,89 +43,86 @@ struct ButtonConfig {
|
||||
#define BUTTON_LEADUP_MS 2200 // Play lead-up sound after 2.5 seconds of holding
|
||||
#endif
|
||||
|
||||
class ButtonThread : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
const char *_originName;
|
||||
static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
|
||||
bool initButton(const ButtonConfig &config);
|
||||
class ButtonThread : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
const char *_originName;
|
||||
static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
|
||||
bool initButton(const ButtonConfig &config);
|
||||
|
||||
enum ButtonEventType {
|
||||
BUTTON_EVENT_NONE,
|
||||
BUTTON_EVENT_PRESSED,
|
||||
BUTTON_EVENT_PRESSED_SCREEN,
|
||||
BUTTON_EVENT_DOUBLE_PRESSED,
|
||||
BUTTON_EVENT_MULTI_PRESSED,
|
||||
BUTTON_EVENT_LONG_PRESSED,
|
||||
BUTTON_EVENT_LONG_RELEASED,
|
||||
BUTTON_EVENT_TOUCH_LONG_PRESSED,
|
||||
BUTTON_EVENT_COMBO_SHORT_LONG,
|
||||
};
|
||||
enum ButtonEventType {
|
||||
BUTTON_EVENT_NONE,
|
||||
BUTTON_EVENT_PRESSED,
|
||||
BUTTON_EVENT_PRESSED_SCREEN,
|
||||
BUTTON_EVENT_DOUBLE_PRESSED,
|
||||
BUTTON_EVENT_MULTI_PRESSED,
|
||||
BUTTON_EVENT_LONG_PRESSED,
|
||||
BUTTON_EVENT_LONG_RELEASED,
|
||||
BUTTON_EVENT_TOUCH_LONG_PRESSED,
|
||||
BUTTON_EVENT_COMBO_SHORT_LONG,
|
||||
};
|
||||
|
||||
explicit ButtonThread(const char *name);
|
||||
int32_t runOnce() override;
|
||||
OneButton userButton;
|
||||
void attachButtonInterrupts();
|
||||
void detachButtonInterrupts();
|
||||
void storeClickCount();
|
||||
bool isButtonPressed(int buttonPin)
|
||||
{
|
||||
if (_activeLow)
|
||||
return !digitalRead(buttonPin); // Active low: pressed = LOW
|
||||
else
|
||||
return digitalRead(buttonPin); // Most buttons are active low by default
|
||||
}
|
||||
explicit ButtonThread(const char *name);
|
||||
int32_t runOnce() override;
|
||||
OneButton userButton;
|
||||
void attachButtonInterrupts();
|
||||
void detachButtonInterrupts();
|
||||
void storeClickCount();
|
||||
bool isButtonPressed(int buttonPin) {
|
||||
if (_activeLow)
|
||||
return !digitalRead(buttonPin); // Active low: pressed = LOW
|
||||
else
|
||||
return digitalRead(buttonPin); // Most buttons are active low by default
|
||||
}
|
||||
|
||||
// Returns true while this thread's button is physically held down
|
||||
bool isHeld() { return isButtonPressed(_pinNum); }
|
||||
// Returns true while this thread's button is physically held down
|
||||
bool isHeld() { return isButtonPressed(_pinNum); }
|
||||
|
||||
// Disconnect and reconnect interrupts for light sleep
|
||||
// Disconnect and reconnect interrupts for light sleep
|
||||
#ifdef ARCH_ESP32
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
#endif
|
||||
private:
|
||||
input_broker_event _singlePress = INPUT_BROKER_NONE;
|
||||
input_broker_event _longPress = INPUT_BROKER_NONE;
|
||||
input_broker_event _longLongPress = INPUT_BROKER_NONE;
|
||||
private:
|
||||
input_broker_event _singlePress = INPUT_BROKER_NONE;
|
||||
input_broker_event _longPress = INPUT_BROKER_NONE;
|
||||
input_broker_event _longLongPress = INPUT_BROKER_NONE;
|
||||
|
||||
input_broker_event _doublePress = INPUT_BROKER_NONE;
|
||||
input_broker_event _triplePress = INPUT_BROKER_NONE;
|
||||
input_broker_event _shortLong = INPUT_BROKER_NONE;
|
||||
input_broker_event _doublePress = INPUT_BROKER_NONE;
|
||||
input_broker_event _triplePress = INPUT_BROKER_NONE;
|
||||
input_broker_event _shortLong = INPUT_BROKER_NONE;
|
||||
|
||||
voidFuncPtr _intRoutine = nullptr;
|
||||
uint16_t _longPressTime = 500;
|
||||
uint16_t _longLongPressTime = 3900;
|
||||
int _pinNum = 0;
|
||||
bool _activeLow = true;
|
||||
bool _touchQuirk = false;
|
||||
voidFuncPtr _intRoutine = nullptr;
|
||||
uint16_t _longPressTime = 500;
|
||||
uint16_t _longLongPressTime = 3900;
|
||||
int _pinNum = 0;
|
||||
bool _activeLow = true;
|
||||
bool _touchQuirk = false;
|
||||
|
||||
uint32_t buttonPressStartTime = 0;
|
||||
bool buttonWasPressed = false;
|
||||
uint32_t buttonPressStartTime = 0;
|
||||
bool buttonWasPressed = false;
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Get notified when lightsleep begins and ends
|
||||
CallbackObserver<ButtonThread, void *> lsObserver =
|
||||
CallbackObserver<ButtonThread, void *>(this, &ButtonThread::beforeLightSleep);
|
||||
CallbackObserver<ButtonThread, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<ButtonThread, esp_sleep_wakeup_cause_t>(this, &ButtonThread::afterLightSleep);
|
||||
// Get notified when lightsleep begins and ends
|
||||
CallbackObserver<ButtonThread, void *> lsObserver = CallbackObserver<ButtonThread, void *>(this, &ButtonThread::beforeLightSleep);
|
||||
CallbackObserver<ButtonThread, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<ButtonThread, esp_sleep_wakeup_cause_t>(this, &ButtonThread::afterLightSleep);
|
||||
#endif
|
||||
|
||||
volatile ButtonEventType btnEvent = BUTTON_EVENT_NONE;
|
||||
volatile ButtonEventType btnEvent = BUTTON_EVENT_NONE;
|
||||
|
||||
// Store click count during callback, for later use
|
||||
volatile int multipressClickCount = 0;
|
||||
// Store click count during callback, for later use
|
||||
volatile int multipressClickCount = 0;
|
||||
|
||||
// Combination tracking state
|
||||
bool waitingForLongPress = false;
|
||||
uint32_t shortPressTime = 0;
|
||||
// Combination tracking state
|
||||
bool waitingForLongPress = false;
|
||||
uint32_t shortPressTime = 0;
|
||||
|
||||
// Long press lead-up tracking
|
||||
bool leadUpPlayed = false;
|
||||
uint32_t lastLeadUpNoteTime = 0;
|
||||
bool leadUpSequenceActive = false;
|
||||
// Long press lead-up tracking
|
||||
bool leadUpPlayed = false;
|
||||
uint32_t lastLeadUpNoteTime = 0;
|
||||
bool leadUpSequenceActive = false;
|
||||
|
||||
static void wakeOnIrq(int irq, int mode);
|
||||
static void wakeOnIrq(int irq, int mode);
|
||||
};
|
||||
|
||||
extern ButtonThread *buttonThread;
|
||||
|
||||
@@ -21,225 +21,210 @@ static const char inputSourceName[] = "ExpressLRS5Way"; // should match "allow i
|
||||
* Example Fuzz values: {20, 20, 100, 100, 125, 300} now the fuzz for the 1600
|
||||
* position is 300 instead of 20
|
||||
*/
|
||||
void ExpressLRSFiveWay::calcFuzzValues()
|
||||
{
|
||||
for (unsigned int i = 0; i < N_JOY_ADC_VALUES; i++) {
|
||||
uint16_t closestDist = 0xffff;
|
||||
uint16_t ival = joyAdcValues[i];
|
||||
// Find the closest value to ival
|
||||
for (unsigned int j = 0; j < N_JOY_ADC_VALUES; j++) {
|
||||
// Don't compare value with itself
|
||||
if (j == i)
|
||||
continue;
|
||||
uint16_t jval = joyAdcValues[j];
|
||||
if (jval < ival && (ival - jval < closestDist))
|
||||
closestDist = ival - jval;
|
||||
if (jval > ival && (jval - ival < closestDist))
|
||||
closestDist = jval - ival;
|
||||
} // for j
|
||||
void ExpressLRSFiveWay::calcFuzzValues() {
|
||||
for (unsigned int i = 0; i < N_JOY_ADC_VALUES; i++) {
|
||||
uint16_t closestDist = 0xffff;
|
||||
uint16_t ival = joyAdcValues[i];
|
||||
// Find the closest value to ival
|
||||
for (unsigned int j = 0; j < N_JOY_ADC_VALUES; j++) {
|
||||
// Don't compare value with itself
|
||||
if (j == i)
|
||||
continue;
|
||||
uint16_t jval = joyAdcValues[j];
|
||||
if (jval < ival && (ival - jval < closestDist))
|
||||
closestDist = ival - jval;
|
||||
if (jval > ival && (jval - ival < closestDist))
|
||||
closestDist = jval - ival;
|
||||
} // for j
|
||||
|
||||
// And the fuzz is half the distance to the closest value
|
||||
fuzzValues[i] = closestDist / 2;
|
||||
// DBG("joy%u=%u f=%u, ", i, ival, fuzzValues[i]);
|
||||
} // for i
|
||||
// And the fuzz is half the distance to the closest value
|
||||
fuzzValues[i] = closestDist / 2;
|
||||
// DBG("joy%u=%u f=%u, ", i, ival, fuzzValues[i]);
|
||||
} // for i
|
||||
}
|
||||
|
||||
int ExpressLRSFiveWay::readKey()
|
||||
{
|
||||
uint16_t value = analogRead(PIN_JOYSTICK);
|
||||
int ExpressLRSFiveWay::readKey() {
|
||||
uint16_t value = analogRead(PIN_JOYSTICK);
|
||||
|
||||
constexpr uint8_t IDX_TO_INPUT[N_JOY_ADC_VALUES - 1] = {UP, DOWN, LEFT, RIGHT, OK};
|
||||
for (unsigned int i = 0; i < N_JOY_ADC_VALUES - 1; ++i) {
|
||||
if (value < (joyAdcValues[i] + fuzzValues[i]) && value > (joyAdcValues[i] - fuzzValues[i]))
|
||||
return IDX_TO_INPUT[i];
|
||||
}
|
||||
return NO_PRESS;
|
||||
constexpr uint8_t IDX_TO_INPUT[N_JOY_ADC_VALUES - 1] = {UP, DOWN, LEFT, RIGHT, OK};
|
||||
for (unsigned int i = 0; i < N_JOY_ADC_VALUES - 1; ++i) {
|
||||
if (value < (joyAdcValues[i] + fuzzValues[i]) && value > (joyAdcValues[i] - fuzzValues[i]))
|
||||
return IDX_TO_INPUT[i];
|
||||
}
|
||||
return NO_PRESS;
|
||||
}
|
||||
|
||||
ExpressLRSFiveWay::ExpressLRSFiveWay() : concurrency::OSThread(inputSourceName)
|
||||
{
|
||||
// ExpressLRS: init values
|
||||
isLongPressed = false;
|
||||
keyInProcess = NO_PRESS;
|
||||
keyDownStart = 0;
|
||||
ExpressLRSFiveWay::ExpressLRSFiveWay() : concurrency::OSThread(inputSourceName) {
|
||||
// ExpressLRS: init values
|
||||
isLongPressed = false;
|
||||
keyInProcess = NO_PRESS;
|
||||
keyDownStart = 0;
|
||||
|
||||
// Express LRS: calculate the threshold for interpreting ADC values as various buttons
|
||||
calcFuzzValues();
|
||||
// Express LRS: calculate the threshold for interpreting ADC values as various buttons
|
||||
calcFuzzValues();
|
||||
|
||||
// Meshtastic: register with canned messages
|
||||
inputBroker->registerSource(this);
|
||||
// Meshtastic: register with canned messages
|
||||
inputBroker->registerSource(this);
|
||||
}
|
||||
|
||||
// ExpressLRS: interpret reading as key events
|
||||
void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed)
|
||||
{
|
||||
*keyValue = NO_PRESS;
|
||||
void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed) {
|
||||
*keyValue = NO_PRESS;
|
||||
|
||||
int newKey = readKey();
|
||||
if (keyInProcess == NO_PRESS) {
|
||||
// New key down
|
||||
if (newKey != NO_PRESS) {
|
||||
keyDownStart = millis();
|
||||
// DBGLN("down=%u", newKey);
|
||||
int newKey = readKey();
|
||||
if (keyInProcess == NO_PRESS) {
|
||||
// New key down
|
||||
if (newKey != NO_PRESS) {
|
||||
keyDownStart = millis();
|
||||
// DBGLN("down=%u", newKey);
|
||||
}
|
||||
} else {
|
||||
// if key released
|
||||
if (newKey == NO_PRESS) {
|
||||
// DBGLN("up=%u", keyInProcess);
|
||||
if (!isLongPressed) {
|
||||
if (!Throttle::isWithinTimespanMs(keyDownStart, KEY_DEBOUNCE_MS)) {
|
||||
*keyValue = keyInProcess;
|
||||
*keyLongPressed = false;
|
||||
}
|
||||
} else {
|
||||
// if key released
|
||||
if (newKey == NO_PRESS) {
|
||||
// DBGLN("up=%u", keyInProcess);
|
||||
if (!isLongPressed) {
|
||||
if (!Throttle::isWithinTimespanMs(keyDownStart, KEY_DEBOUNCE_MS)) {
|
||||
*keyValue = keyInProcess;
|
||||
*keyLongPressed = false;
|
||||
}
|
||||
}
|
||||
isLongPressed = false;
|
||||
}
|
||||
// else if the key has changed while down, reset state for next go-around
|
||||
else if (newKey != keyInProcess) {
|
||||
newKey = NO_PRESS;
|
||||
}
|
||||
// else still pressing, waiting for long if not already signaled
|
||||
else if (!isLongPressed) {
|
||||
if (!Throttle::isWithinTimespanMs(keyDownStart, KEY_LONG_PRESS_MS)) {
|
||||
*keyValue = keyInProcess;
|
||||
*keyLongPressed = true;
|
||||
isLongPressed = true;
|
||||
}
|
||||
}
|
||||
} // if keyInProcess != NO_PRESS
|
||||
}
|
||||
isLongPressed = false;
|
||||
}
|
||||
// else if the key has changed while down, reset state for next go-around
|
||||
else if (newKey != keyInProcess) {
|
||||
newKey = NO_PRESS;
|
||||
}
|
||||
// else still pressing, waiting for long if not already signaled
|
||||
else if (!isLongPressed) {
|
||||
if (!Throttle::isWithinTimespanMs(keyDownStart, KEY_LONG_PRESS_MS)) {
|
||||
*keyValue = keyInProcess;
|
||||
*keyLongPressed = true;
|
||||
isLongPressed = true;
|
||||
}
|
||||
}
|
||||
} // if keyInProcess != NO_PRESS
|
||||
|
||||
keyInProcess = newKey;
|
||||
keyInProcess = newKey;
|
||||
}
|
||||
|
||||
// Meshtastic: runs at regular intervals
|
||||
int32_t ExpressLRSFiveWay::runOnce()
|
||||
{
|
||||
uint32_t now = millis();
|
||||
int32_t ExpressLRSFiveWay::runOnce() {
|
||||
uint32_t now = millis();
|
||||
|
||||
// Dismiss any alert frames after 2 seconds
|
||||
// Feedback for GPS toggle / adhoc ping
|
||||
if (alerting && now > alertingSinceMs + 2000) {
|
||||
alerting = false;
|
||||
screen->endAlert();
|
||||
}
|
||||
// Dismiss any alert frames after 2 seconds
|
||||
// Feedback for GPS toggle / adhoc ping
|
||||
if (alerting && now > alertingSinceMs + 2000) {
|
||||
alerting = false;
|
||||
screen->endAlert();
|
||||
}
|
||||
|
||||
// Get key events from ExpressLRS code
|
||||
int keyValue;
|
||||
bool longPressed;
|
||||
update(&keyValue, &longPressed);
|
||||
// Get key events from ExpressLRS code
|
||||
int keyValue;
|
||||
bool longPressed;
|
||||
update(&keyValue, &longPressed);
|
||||
|
||||
// Do something about this key press
|
||||
determineAction((KeyType)keyValue, longPressed ? LONG : SHORT);
|
||||
// Do something about this key press
|
||||
determineAction((KeyType)keyValue, longPressed ? LONG : SHORT);
|
||||
|
||||
// If there has been recent key activity, poll the joystick slightly more frequently
|
||||
if (now < keyDownStart + (20 * 1000UL)) // Within last 20 seconds
|
||||
return 100;
|
||||
// If there has been recent key activity, poll the joystick slightly more frequently
|
||||
if (now < keyDownStart + (20 * 1000UL)) // Within last 20 seconds
|
||||
return 100;
|
||||
|
||||
// Otherwise, poll slightly less often
|
||||
// Too many missed pressed if much slower than 250ms
|
||||
return 250;
|
||||
// Otherwise, poll slightly less often
|
||||
// Too many missed pressed if much slower than 250ms
|
||||
return 250;
|
||||
}
|
||||
|
||||
// Determine what action to take when a button press is detected
|
||||
// Written verbose for easier remapping by user
|
||||
void ExpressLRSFiveWay::determineAction(KeyType key, PressLength length)
|
||||
{
|
||||
switch (key) {
|
||||
case LEFT:
|
||||
if (inCannedMessageMenu()) // If in canned message menu
|
||||
sendKey(INPUT_BROKER_CANCEL); // exit the menu (press imaginary cancel key)
|
||||
else
|
||||
sendKey(INPUT_BROKER_LEFT);
|
||||
break;
|
||||
void ExpressLRSFiveWay::determineAction(KeyType key, PressLength length) {
|
||||
switch (key) {
|
||||
case LEFT:
|
||||
if (inCannedMessageMenu()) // If in canned message menu
|
||||
sendKey(INPUT_BROKER_CANCEL); // exit the menu (press imaginary cancel key)
|
||||
else
|
||||
sendKey(INPUT_BROKER_LEFT);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
if (inCannedMessageMenu()) // If in canned message menu:
|
||||
sendKey(INPUT_BROKER_CANCEL); // exit the menu (press imaginary cancel key)
|
||||
else
|
||||
sendKey(INPUT_BROKER_RIGHT);
|
||||
break;
|
||||
case RIGHT:
|
||||
if (inCannedMessageMenu()) // If in canned message menu:
|
||||
sendKey(INPUT_BROKER_CANCEL); // exit the menu (press imaginary cancel key)
|
||||
else
|
||||
sendKey(INPUT_BROKER_RIGHT);
|
||||
break;
|
||||
|
||||
case UP:
|
||||
if (length == LONG)
|
||||
toggleGPS();
|
||||
else
|
||||
sendKey(INPUT_BROKER_UP);
|
||||
break;
|
||||
case UP:
|
||||
if (length == LONG)
|
||||
toggleGPS();
|
||||
else
|
||||
sendKey(INPUT_BROKER_UP);
|
||||
break;
|
||||
|
||||
case DOWN:
|
||||
if (length == LONG)
|
||||
sendAdhocPing();
|
||||
else
|
||||
sendKey(INPUT_BROKER_DOWN);
|
||||
break;
|
||||
case DOWN:
|
||||
if (length == LONG)
|
||||
sendAdhocPing();
|
||||
else
|
||||
sendKey(INPUT_BROKER_DOWN);
|
||||
break;
|
||||
|
||||
case OK:
|
||||
if (length == LONG)
|
||||
shutdown();
|
||||
else
|
||||
click(); // Use instead of sendKey(OK). Works better when canned message module disabled
|
||||
break;
|
||||
case OK:
|
||||
if (length == LONG)
|
||||
shutdown();
|
||||
else
|
||||
click(); // Use instead of sendKey(OK). Works better when canned message module disabled
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Feed input to the canned messages module
|
||||
void ExpressLRSFiveWay::sendKey(input_broker_event key)
|
||||
{
|
||||
InputEvent e = {};
|
||||
e.source = inputSourceName;
|
||||
e.inputEvent = key;
|
||||
notifyObservers(&e);
|
||||
void ExpressLRSFiveWay::sendKey(input_broker_event key) {
|
||||
InputEvent e = {};
|
||||
e.source = inputSourceName;
|
||||
e.inputEvent = key;
|
||||
notifyObservers(&e);
|
||||
}
|
||||
|
||||
// Enable or Disable a connected GPS
|
||||
// Contained as one method for easier remapping of buttons by user
|
||||
void ExpressLRSFiveWay::toggleGPS()
|
||||
{
|
||||
void ExpressLRSFiveWay::toggleGPS() {
|
||||
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
|
||||
if (gps != nullptr) {
|
||||
gps->toggleGpsMode();
|
||||
screen->startAlert("GPS Toggled");
|
||||
alerting = true;
|
||||
alertingSinceMs = millis();
|
||||
}
|
||||
if (gps != nullptr) {
|
||||
gps->toggleGpsMode();
|
||||
screen->startAlert("GPS Toggled");
|
||||
alerting = true;
|
||||
alertingSinceMs = millis();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Send either node-info or position, on demand
|
||||
// Contained as one method for easier remapping of buttons by user
|
||||
void ExpressLRSFiveWay::sendAdhocPing()
|
||||
{
|
||||
service->refreshLocalMeshNode();
|
||||
bool sentPosition = service->trySendPosition(NODENUM_BROADCAST, true);
|
||||
void ExpressLRSFiveWay::sendAdhocPing() {
|
||||
service->refreshLocalMeshNode();
|
||||
bool sentPosition = service->trySendPosition(NODENUM_BROADCAST, true);
|
||||
|
||||
// Show custom alert frame, with multi-line centering
|
||||
screen->startAlert([sentPosition](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||
uint16_t x_offset = display->width() / 2;
|
||||
uint16_t y_offset = 26; // Same constant as the default startAlert frame
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->drawString(x_offset + x, y_offset + y, "Sent ad-hoc");
|
||||
display->drawString(x_offset + x, y_offset + FONT_HEIGHT_MEDIUM + y, sentPosition ? "position" : "nodeinfo");
|
||||
});
|
||||
// Show custom alert frame, with multi-line centering
|
||||
screen->startAlert([sentPosition](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||
uint16_t x_offset = display->width() / 2;
|
||||
uint16_t y_offset = 26; // Same constant as the default startAlert frame
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->drawString(x_offset + x, y_offset + y, "Sent ad-hoc");
|
||||
display->drawString(x_offset + x, y_offset + FONT_HEIGHT_MEDIUM + y, sentPosition ? "position" : "nodeinfo");
|
||||
});
|
||||
|
||||
alerting = true;
|
||||
alertingSinceMs = millis();
|
||||
alerting = true;
|
||||
alertingSinceMs = millis();
|
||||
}
|
||||
|
||||
// Shutdown the node (enter deep-sleep)
|
||||
// Contained as one method for easier remapping of buttons by user
|
||||
void ExpressLRSFiveWay::shutdown()
|
||||
{
|
||||
sendKey(INPUT_BROKER_SHUTDOWN);
|
||||
}
|
||||
void ExpressLRSFiveWay::shutdown() { sendKey(INPUT_BROKER_SHUTDOWN); }
|
||||
|
||||
void ExpressLRSFiveWay::click()
|
||||
{
|
||||
sendKey(INPUT_BROKER_SELECT);
|
||||
}
|
||||
void ExpressLRSFiveWay::click() { sendKey(INPUT_BROKER_SELECT); }
|
||||
|
||||
ExpressLRSFiveWay *expressLRSFiveWayInput = nullptr;
|
||||
|
||||
|
||||
@@ -28,56 +28,55 @@
|
||||
#include "GPS.h" // For toggle GPS action
|
||||
#endif
|
||||
|
||||
class ExpressLRSFiveWay : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
private:
|
||||
// Number of values in JOY_ADC_VALUES, if defined
|
||||
// These must be ADC readings for {UP, DOWN, LEFT, RIGHT, ENTER, IDLE}
|
||||
static constexpr size_t N_JOY_ADC_VALUES = 6;
|
||||
static constexpr uint32_t KEY_DEBOUNCE_MS = 25;
|
||||
static constexpr uint32_t KEY_LONG_PRESS_MS = 3000; // How many milliseconds to hold key for a long press
|
||||
class ExpressLRSFiveWay : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
private:
|
||||
// Number of values in JOY_ADC_VALUES, if defined
|
||||
// These must be ADC readings for {UP, DOWN, LEFT, RIGHT, ENTER, IDLE}
|
||||
static constexpr size_t N_JOY_ADC_VALUES = 6;
|
||||
static constexpr uint32_t KEY_DEBOUNCE_MS = 25;
|
||||
static constexpr uint32_t KEY_LONG_PRESS_MS = 3000; // How many milliseconds to hold key for a long press
|
||||
|
||||
// This merged an enum used by the ExpressLRS code, with meshtastic canned message values
|
||||
// Key names are kept simple, to allow user customizaton
|
||||
typedef enum {
|
||||
UP = INPUT_BROKER_UP,
|
||||
DOWN = INPUT_BROKER_DOWN,
|
||||
LEFT = INPUT_BROKER_LEFT,
|
||||
RIGHT = INPUT_BROKER_RIGHT,
|
||||
OK = INPUT_BROKER_SELECT,
|
||||
CANCEL = INPUT_BROKER_CANCEL,
|
||||
NO_PRESS = INPUT_BROKER_NONE
|
||||
} KeyType;
|
||||
// This merged an enum used by the ExpressLRS code, with meshtastic canned message values
|
||||
// Key names are kept simple, to allow user customizaton
|
||||
typedef enum {
|
||||
UP = INPUT_BROKER_UP,
|
||||
DOWN = INPUT_BROKER_DOWN,
|
||||
LEFT = INPUT_BROKER_LEFT,
|
||||
RIGHT = INPUT_BROKER_RIGHT,
|
||||
OK = INPUT_BROKER_SELECT,
|
||||
CANCEL = INPUT_BROKER_CANCEL,
|
||||
NO_PRESS = INPUT_BROKER_NONE
|
||||
} KeyType;
|
||||
|
||||
typedef enum { SHORT, LONG } PressLength;
|
||||
typedef enum { SHORT, LONG } PressLength;
|
||||
|
||||
// From ExpressLRS
|
||||
int keyInProcess;
|
||||
uint32_t keyDownStart;
|
||||
bool isLongPressed;
|
||||
const uint16_t joyAdcValues[N_JOY_ADC_VALUES] = {JOYSTICK_ADC_VALS};
|
||||
uint16_t fuzzValues[N_JOY_ADC_VALUES];
|
||||
void calcFuzzValues();
|
||||
int readKey();
|
||||
void update(int *keyValue, bool *keyLongPressed);
|
||||
// From ExpressLRS
|
||||
int keyInProcess;
|
||||
uint32_t keyDownStart;
|
||||
bool isLongPressed;
|
||||
const uint16_t joyAdcValues[N_JOY_ADC_VALUES] = {JOYSTICK_ADC_VALS};
|
||||
uint16_t fuzzValues[N_JOY_ADC_VALUES];
|
||||
void calcFuzzValues();
|
||||
int readKey();
|
||||
void update(int *keyValue, bool *keyLongPressed);
|
||||
|
||||
// Meshtastic code
|
||||
void determineAction(KeyType key, PressLength length);
|
||||
void sendKey(input_broker_event key);
|
||||
inline bool inCannedMessageMenu() { return cannedMessageModule->shouldDraw(); }
|
||||
int32_t runOnce() override;
|
||||
// Meshtastic code
|
||||
void determineAction(KeyType key, PressLength length);
|
||||
void sendKey(input_broker_event key);
|
||||
inline bool inCannedMessageMenu() { return cannedMessageModule->shouldDraw(); }
|
||||
int32_t runOnce() override;
|
||||
|
||||
// Simplified Meshtastic actions, for easier remapping by user
|
||||
void toggleGPS();
|
||||
void sendAdhocPing();
|
||||
void shutdown();
|
||||
void click();
|
||||
// Simplified Meshtastic actions, for easier remapping by user
|
||||
void toggleGPS();
|
||||
void sendAdhocPing();
|
||||
void shutdown();
|
||||
void click();
|
||||
|
||||
bool alerting = false; // Is the screen showing an alert frame? Feedback for GPS toggle / adhoc ping actions
|
||||
uint32_t alertingSinceMs = 0; // When did screen begin showing an alert frame? Used to auto-dismiss
|
||||
bool alerting = false; // Is the screen showing an alert frame? Feedback for GPS toggle / adhoc ping actions
|
||||
uint32_t alertingSinceMs = 0; // When did screen begin showing an alert frame? Used to auto-dismiss
|
||||
|
||||
public:
|
||||
ExpressLRSFiveWay();
|
||||
public:
|
||||
ExpressLRSFiveWay();
|
||||
};
|
||||
|
||||
extern ExpressLRSFiveWay *expressLRSFiveWayInput;
|
||||
|
||||
@@ -106,112 +106,103 @@ static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{},
|
||||
{}};
|
||||
|
||||
HackadayCommunicatorKeyboard::HackadayCommunicatorKeyboard()
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
|
||||
last_tap(0L), char_idx(0), tap_interval(0)
|
||||
{
|
||||
reset();
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), last_tap(0L),
|
||||
char_idx(0), tap_interval(0) {
|
||||
reset();
|
||||
}
|
||||
|
||||
void HackadayCommunicatorKeyboard::reset(void)
|
||||
{
|
||||
TCA8418KeyboardBase::reset();
|
||||
enableInterrupts();
|
||||
void HackadayCommunicatorKeyboard::reset(void) {
|
||||
TCA8418KeyboardBase::reset();
|
||||
enableInterrupts();
|
||||
}
|
||||
|
||||
// handle multi-key presses (shift and alt)
|
||||
void HackadayCommunicatorKeyboard::trigger()
|
||||
{
|
||||
uint8_t count = keyCount();
|
||||
if (count == 0)
|
||||
return;
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
|
||||
uint8_t key = k & 0x7F;
|
||||
if (k & 0x80) {
|
||||
pressed(key);
|
||||
} else {
|
||||
released();
|
||||
state = Idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HackadayCommunicatorKeyboard::pressed(uint8_t key)
|
||||
{
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
return; // Invalid key
|
||||
}
|
||||
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
state = Held;
|
||||
|
||||
uint32_t now = millis();
|
||||
tap_interval = now - last_tap;
|
||||
|
||||
updateModifierFlag(next_key);
|
||||
if (isModifierKey(next_key)) {
|
||||
last_modifier_time = now;
|
||||
}
|
||||
|
||||
if (tap_interval < 0) {
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0;
|
||||
void HackadayCommunicatorKeyboard::trigger() {
|
||||
uint8_t count = keyCount();
|
||||
if (count == 0)
|
||||
return;
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
|
||||
uint8_t key = k & 0x7F;
|
||||
if (k & 0x80) {
|
||||
pressed(key);
|
||||
} else {
|
||||
char_idx += 1;
|
||||
released();
|
||||
state = Idle;
|
||||
}
|
||||
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
}
|
||||
}
|
||||
|
||||
void HackadayCommunicatorKeyboard::released()
|
||||
{
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
void HackadayCommunicatorKeyboard::pressed(uint8_t key) {
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
last_tap = now;
|
||||
if (HackadayCommunicatorTapMod[last_key])
|
||||
queueEvent(HackadayCommunicatorTapMap[last_key][modifierFlag % HackadayCommunicatorTapMod[last_key]]);
|
||||
if (isModifierKey(last_key) == false)
|
||||
modifierFlag = 0;
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
return; // Invalid key
|
||||
}
|
||||
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
state = Held;
|
||||
|
||||
uint32_t now = millis();
|
||||
tap_interval = now - last_tap;
|
||||
|
||||
updateModifierFlag(next_key);
|
||||
if (isModifierKey(next_key)) {
|
||||
last_modifier_time = now;
|
||||
}
|
||||
|
||||
if (tap_interval < 0) {
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0;
|
||||
} else {
|
||||
char_idx += 1;
|
||||
}
|
||||
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
}
|
||||
|
||||
void HackadayCommunicatorKeyboard::updateModifierFlag(uint8_t key)
|
||||
{
|
||||
if (key == modifierRightShiftKey) {
|
||||
modifierFlag ^= modifierRightShift;
|
||||
} else if (key == modifierLeftShiftKey) {
|
||||
modifierFlag ^= modifierLeftShift;
|
||||
}
|
||||
void HackadayCommunicatorKeyboard::released() {
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
last_tap = now;
|
||||
if (HackadayCommunicatorTapMod[last_key])
|
||||
queueEvent(HackadayCommunicatorTapMap[last_key][modifierFlag % HackadayCommunicatorTapMod[last_key]]);
|
||||
if (isModifierKey(last_key) == false)
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
bool HackadayCommunicatorKeyboard::isModifierKey(uint8_t key)
|
||||
{
|
||||
return (key == modifierRightShiftKey || key == modifierLeftShiftKey);
|
||||
void HackadayCommunicatorKeyboard::updateModifierFlag(uint8_t key) {
|
||||
if (key == modifierRightShiftKey) {
|
||||
modifierFlag ^= modifierRightShift;
|
||||
} else if (key == modifierLeftShiftKey) {
|
||||
modifierFlag ^= modifierLeftShift;
|
||||
}
|
||||
}
|
||||
|
||||
bool HackadayCommunicatorKeyboard::isModifierKey(uint8_t key) { return (key == modifierRightShiftKey || key == modifierLeftShiftKey); }
|
||||
|
||||
#endif
|
||||
@@ -1,26 +1,25 @@
|
||||
#include "TCA8418KeyboardBase.h"
|
||||
|
||||
class HackadayCommunicatorKeyboard : public TCA8418KeyboardBase
|
||||
{
|
||||
public:
|
||||
HackadayCommunicatorKeyboard();
|
||||
void reset(void);
|
||||
void trigger(void) override;
|
||||
virtual ~HackadayCommunicatorKeyboard() {}
|
||||
class HackadayCommunicatorKeyboard : public TCA8418KeyboardBase {
|
||||
public:
|
||||
HackadayCommunicatorKeyboard();
|
||||
void reset(void);
|
||||
void trigger(void) override;
|
||||
virtual ~HackadayCommunicatorKeyboard() {}
|
||||
|
||||
protected:
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
protected:
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
|
||||
void updateModifierFlag(uint8_t key);
|
||||
bool isModifierKey(uint8_t key);
|
||||
void updateModifierFlag(uint8_t key);
|
||||
bool isModifierKey(uint8_t key);
|
||||
|
||||
private:
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
private:
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
};
|
||||
|
||||
@@ -5,72 +5,63 @@
|
||||
|
||||
InputBroker *inputBroker = nullptr;
|
||||
|
||||
InputBroker::InputBroker()
|
||||
{
|
||||
InputBroker::InputBroker() {
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
inputEventQueue = xQueueCreate(5, sizeof(InputEvent));
|
||||
pollSoonQueue = xQueueCreate(5, sizeof(InputPollable *));
|
||||
xTaskCreate(pollSoonWorker, "input-pollSoon", 2 * 1024, this, 10, &pollSoonTask);
|
||||
inputEventQueue = xQueueCreate(5, sizeof(InputEvent));
|
||||
pollSoonQueue = xQueueCreate(5, sizeof(InputPollable *));
|
||||
xTaskCreate(pollSoonWorker, "input-pollSoon", 2 * 1024, this, 10, &pollSoonTask);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InputBroker::registerSource(Observable<const InputEvent *> *source)
|
||||
{
|
||||
this->inputEventObserver.observe(source);
|
||||
}
|
||||
void InputBroker::registerSource(Observable<const InputEvent *> *source) { this->inputEventObserver.observe(source); }
|
||||
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
void InputBroker::requestPollSoon(InputPollable *pollable)
|
||||
{
|
||||
if (xPortInIsrContext() == pdTRUE) {
|
||||
xQueueSendFromISR(pollSoonQueue, &pollable, NULL);
|
||||
} else {
|
||||
xQueueSend(pollSoonQueue, &pollable, 0);
|
||||
}
|
||||
void InputBroker::requestPollSoon(InputPollable *pollable) {
|
||||
if (xPortInIsrContext() == pdTRUE) {
|
||||
xQueueSendFromISR(pollSoonQueue, &pollable, NULL);
|
||||
} else {
|
||||
xQueueSend(pollSoonQueue, &pollable, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void InputBroker::queueInputEvent(const InputEvent *event)
|
||||
{
|
||||
if (xPortInIsrContext() == pdTRUE) {
|
||||
xQueueSendFromISR(inputEventQueue, event, NULL);
|
||||
} else {
|
||||
xQueueSend(inputEventQueue, event, portMAX_DELAY);
|
||||
}
|
||||
void InputBroker::queueInputEvent(const InputEvent *event) {
|
||||
if (xPortInIsrContext() == pdTRUE) {
|
||||
xQueueSendFromISR(inputEventQueue, event, NULL);
|
||||
} else {
|
||||
xQueueSend(inputEventQueue, event, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void InputBroker::processInputEventQueue()
|
||||
{
|
||||
InputEvent event;
|
||||
while (xQueueReceive(inputEventQueue, &event, 0)) {
|
||||
handleInputEvent(&event);
|
||||
}
|
||||
void InputBroker::processInputEventQueue() {
|
||||
InputEvent event;
|
||||
while (xQueueReceive(inputEventQueue, &event, 0)) {
|
||||
handleInputEvent(&event);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int InputBroker::handleInputEvent(const InputEvent *event)
|
||||
{
|
||||
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
|
||||
int InputBroker::handleInputEvent(const InputEvent *event) {
|
||||
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
|
||||
|
||||
if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule &&
|
||||
moduleConfig.external_notification.enabled && externalNotificationModule->nagging()) {
|
||||
externalNotificationModule->stopNow();
|
||||
}
|
||||
if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule && moduleConfig.external_notification.enabled &&
|
||||
externalNotificationModule->nagging()) {
|
||||
externalNotificationModule->stopNow();
|
||||
}
|
||||
|
||||
this->notifyObservers(event);
|
||||
return 0;
|
||||
this->notifyObservers(event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
void InputBroker::pollSoonWorker(void *p)
|
||||
{
|
||||
InputBroker *instance = (InputBroker *)p;
|
||||
while (true) {
|
||||
InputPollable *pollable = NULL;
|
||||
xQueueReceive(instance->pollSoonQueue, &pollable, portMAX_DELAY);
|
||||
if (pollable) {
|
||||
pollable->pollOnce();
|
||||
}
|
||||
void InputBroker::pollSoonWorker(void *p) {
|
||||
InputBroker *instance = (InputBroker *)p;
|
||||
while (true) {
|
||||
InputPollable *pollable = NULL;
|
||||
xQueueReceive(instance->pollSoonQueue, &pollable, portMAX_DELAY);
|
||||
if (pollable) {
|
||||
pollable->pollOnce();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -10,25 +10,25 @@
|
||||
#endif
|
||||
|
||||
enum input_broker_event {
|
||||
INPUT_BROKER_NONE = 0,
|
||||
INPUT_BROKER_SELECT = 10,
|
||||
INPUT_BROKER_SELECT_LONG = 11,
|
||||
INPUT_BROKER_UP_LONG = 12,
|
||||
INPUT_BROKER_DOWN_LONG = 13,
|
||||
INPUT_BROKER_UP = 17,
|
||||
INPUT_BROKER_DOWN = 18,
|
||||
INPUT_BROKER_LEFT = 19,
|
||||
INPUT_BROKER_RIGHT = 20,
|
||||
INPUT_BROKER_CANCEL = 24,
|
||||
INPUT_BROKER_BACK = 27,
|
||||
INPUT_BROKER_USER_PRESS,
|
||||
INPUT_BROKER_ALT_PRESS,
|
||||
INPUT_BROKER_ALT_LONG,
|
||||
INPUT_BROKER_SHUTDOWN = 0x9b,
|
||||
INPUT_BROKER_GPS_TOGGLE = 0x9e,
|
||||
INPUT_BROKER_SEND_PING = 0xaf,
|
||||
INPUT_BROKER_MATRIXKEY = 0xFE,
|
||||
INPUT_BROKER_ANYKEY = 0xff
|
||||
INPUT_BROKER_NONE = 0,
|
||||
INPUT_BROKER_SELECT = 10,
|
||||
INPUT_BROKER_SELECT_LONG = 11,
|
||||
INPUT_BROKER_UP_LONG = 12,
|
||||
INPUT_BROKER_DOWN_LONG = 13,
|
||||
INPUT_BROKER_UP = 17,
|
||||
INPUT_BROKER_DOWN = 18,
|
||||
INPUT_BROKER_LEFT = 19,
|
||||
INPUT_BROKER_RIGHT = 20,
|
||||
INPUT_BROKER_CANCEL = 24,
|
||||
INPUT_BROKER_BACK = 27,
|
||||
INPUT_BROKER_USER_PRESS,
|
||||
INPUT_BROKER_ALT_PRESS,
|
||||
INPUT_BROKER_ALT_LONG,
|
||||
INPUT_BROKER_SHUTDOWN = 0x9b,
|
||||
INPUT_BROKER_GPS_TOGGLE = 0x9e,
|
||||
INPUT_BROKER_SEND_PING = 0xaf,
|
||||
INPUT_BROKER_MATRIXKEY = 0xFE,
|
||||
INPUT_BROKER_ANYKEY = 0xff
|
||||
|
||||
};
|
||||
|
||||
@@ -43,44 +43,42 @@ enum input_broker_event {
|
||||
#define INPUT_BROKER_MSG_EMOTE_LIST 0x8F
|
||||
|
||||
typedef struct _InputEvent {
|
||||
const char *source;
|
||||
input_broker_event inputEvent;
|
||||
unsigned char kbchar;
|
||||
uint16_t touchX;
|
||||
uint16_t touchY;
|
||||
const char *source;
|
||||
input_broker_event inputEvent;
|
||||
unsigned char kbchar;
|
||||
uint16_t touchX;
|
||||
uint16_t touchY;
|
||||
} InputEvent;
|
||||
|
||||
class InputPollable
|
||||
{
|
||||
public:
|
||||
virtual ~InputPollable() = default;
|
||||
virtual void pollOnce() = 0;
|
||||
class InputPollable {
|
||||
public:
|
||||
virtual ~InputPollable() = default;
|
||||
virtual void pollOnce() = 0;
|
||||
};
|
||||
|
||||
class InputBroker : public Observable<const InputEvent *>
|
||||
{
|
||||
CallbackObserver<InputBroker, const InputEvent *> inputEventObserver =
|
||||
CallbackObserver<InputBroker, const InputEvent *>(this, &InputBroker::handleInputEvent);
|
||||
class InputBroker : public Observable<const InputEvent *> {
|
||||
CallbackObserver<InputBroker, const InputEvent *> inputEventObserver =
|
||||
CallbackObserver<InputBroker, const InputEvent *>(this, &InputBroker::handleInputEvent);
|
||||
|
||||
public:
|
||||
InputBroker();
|
||||
void registerSource(Observable<const InputEvent *> *source);
|
||||
void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
|
||||
public:
|
||||
InputBroker();
|
||||
void registerSource(Observable<const InputEvent *> *source);
|
||||
void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
void requestPollSoon(InputPollable *pollable);
|
||||
void queueInputEvent(const InputEvent *event);
|
||||
void processInputEventQueue();
|
||||
void requestPollSoon(InputPollable *pollable);
|
||||
void queueInputEvent(const InputEvent *event);
|
||||
void processInputEventQueue();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
int handleInputEvent(const InputEvent *event);
|
||||
protected:
|
||||
int handleInputEvent(const InputEvent *event);
|
||||
|
||||
private:
|
||||
private:
|
||||
#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
|
||||
QueueHandle_t inputEventQueue;
|
||||
QueueHandle_t pollSoonQueue;
|
||||
TaskHandle_t pollSoonTask;
|
||||
static void pollSoonWorker(void *p);
|
||||
QueueHandle_t inputEventQueue;
|
||||
QueueHandle_t pollSoonQueue;
|
||||
TaskHandle_t pollSoonTask;
|
||||
static void pollSoonWorker(void *p);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -18,172 +18,167 @@
|
||||
|
||||
// Inspired by https://github.com/librerpi/rpi-tools/blob/master/keyboard-proxy/main.c which is GPL-v2
|
||||
|
||||
LinuxInput::LinuxInput(const char *name) : concurrency::OSThread(name)
|
||||
{
|
||||
this->_originName = name;
|
||||
LinuxInput::LinuxInput(const char *name) : concurrency::OSThread(name) { this->_originName = name; }
|
||||
|
||||
void LinuxInput::deInit() {
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void LinuxInput::deInit()
|
||||
{
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
int32_t LinuxInput::runOnce() {
|
||||
|
||||
int32_t LinuxInput::runOnce()
|
||||
{
|
||||
if (firstTime) {
|
||||
if (portduino_config.keyboardDevice == "")
|
||||
return disable();
|
||||
fd = open(portduino_config.keyboardDevice.c_str(), O_RDWR);
|
||||
if (fd < 0)
|
||||
return disable();
|
||||
ret = ioctl(fd, EVIOCGRAB, (void *)1);
|
||||
if (ret != 0)
|
||||
return disable();
|
||||
|
||||
if (firstTime) {
|
||||
if (portduino_config.keyboardDevice == "")
|
||||
return disable();
|
||||
fd = open(portduino_config.keyboardDevice.c_str(), O_RDWR);
|
||||
if (fd < 0)
|
||||
return disable();
|
||||
ret = ioctl(fd, EVIOCGRAB, (void *)1);
|
||||
if (ret != 0)
|
||||
return disable();
|
||||
epollfd = epoll_create1(0);
|
||||
assert(epollfd >= 0);
|
||||
|
||||
epollfd = epoll_create1(0);
|
||||
assert(epollfd >= 0);
|
||||
ev.events = EPOLLIN;
|
||||
ev.data.fd = fd;
|
||||
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev)) {
|
||||
perror("unable to epoll add");
|
||||
return disable();
|
||||
}
|
||||
kb_found = true;
|
||||
// This is the first time the OSThread library has called this function, so do port setup
|
||||
firstTime = 0;
|
||||
}
|
||||
|
||||
ev.events = EPOLLIN;
|
||||
ev.data.fd = fd;
|
||||
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev)) {
|
||||
perror("unable to epoll add");
|
||||
return disable();
|
||||
int nfds = epoll_wait(epollfd, events, MAX_EVENTS, 1);
|
||||
if (nfds < 0) {
|
||||
printf("%d ", nfds);
|
||||
perror("epoll_wait failed");
|
||||
return disable();
|
||||
} else if (nfds == 0) {
|
||||
return 50;
|
||||
}
|
||||
|
||||
int keys = 0;
|
||||
memset(report, 0, 8);
|
||||
for (int i = 0; i < nfds; i++) {
|
||||
|
||||
struct input_event ev[64];
|
||||
int rd = read(events[i].data.fd, ev, sizeof(ev));
|
||||
assert(rd > ((signed int)sizeof(struct input_event)));
|
||||
for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) {
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
e.kbchar = 0;
|
||||
unsigned int type, code;
|
||||
type = ev[j].type;
|
||||
code = ev[j].code;
|
||||
int value = ev[j].value;
|
||||
// printf("Event: time %ld.%06ld, ", ev[j].time.tv_sec, ev[j].time.tv_usec);
|
||||
|
||||
if (type == EV_KEY) {
|
||||
uint8_t mod = 0;
|
||||
|
||||
switch (code) {
|
||||
case KEY_LEFTCTRL:
|
||||
mod = 0x01;
|
||||
break;
|
||||
case KEY_RIGHTCTRL:
|
||||
mod = 0x10;
|
||||
break;
|
||||
case KEY_LEFTSHIFT:
|
||||
mod = 0x02;
|
||||
break;
|
||||
case KEY_RIGHTSHIFT:
|
||||
mod = 0x20;
|
||||
break;
|
||||
case KEY_LEFTALT:
|
||||
mod = 0x04;
|
||||
break;
|
||||
case KEY_RIGHTALT:
|
||||
mod = 0x40;
|
||||
break;
|
||||
case KEY_LEFTMETA:
|
||||
mod = 0x08;
|
||||
break;
|
||||
}
|
||||
kb_found = true;
|
||||
// This is the first time the OSThread library has called this function, so do port setup
|
||||
firstTime = 0;
|
||||
}
|
||||
if (value == 1) {
|
||||
switch (code) {
|
||||
case KEY_LEFTCTRL:
|
||||
mod = 0x01;
|
||||
break;
|
||||
case KEY_RIGHTCTRL:
|
||||
mod = 0x10;
|
||||
break;
|
||||
case KEY_LEFTSHIFT:
|
||||
mod = 0x02;
|
||||
break;
|
||||
case KEY_RIGHTSHIFT:
|
||||
mod = 0x20;
|
||||
break;
|
||||
case KEY_LEFTALT:
|
||||
mod = 0x04;
|
||||
break;
|
||||
case KEY_RIGHTALT:
|
||||
mod = 0x40;
|
||||
break;
|
||||
case KEY_LEFTMETA:
|
||||
mod = 0x08;
|
||||
break;
|
||||
case KEY_ESC: // ESC
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
break;
|
||||
case KEY_BACK: // Back
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
// e.kbchar = key;
|
||||
break;
|
||||
|
||||
int nfds = epoll_wait(epollfd, events, MAX_EVENTS, 1);
|
||||
if (nfds < 0) {
|
||||
printf("%d ", nfds);
|
||||
perror("epoll_wait failed");
|
||||
return disable();
|
||||
} else if (nfds == 0) {
|
||||
return 50;
|
||||
}
|
||||
|
||||
int keys = 0;
|
||||
memset(report, 0, 8);
|
||||
for (int i = 0; i < nfds; i++) {
|
||||
|
||||
struct input_event ev[64];
|
||||
int rd = read(events[i].data.fd, ev, sizeof(ev));
|
||||
assert(rd > ((signed int)sizeof(struct input_event)));
|
||||
for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) {
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
case KEY_UP: // Up
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
break;
|
||||
case KEY_DOWN: // Down
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
break;
|
||||
case KEY_LEFT: // Left
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
break;
|
||||
e.kbchar = INPUT_BROKER_LEFT;
|
||||
case KEY_RIGHT: // Right
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
break;
|
||||
e.kbchar = 0;
|
||||
unsigned int type, code;
|
||||
type = ev[j].type;
|
||||
code = ev[j].code;
|
||||
int value = ev[j].value;
|
||||
// printf("Event: time %ld.%06ld, ", ev[j].time.tv_sec, ev[j].time.tv_usec);
|
||||
|
||||
if (type == EV_KEY) {
|
||||
uint8_t mod = 0;
|
||||
|
||||
switch (code) {
|
||||
case KEY_LEFTCTRL:
|
||||
mod = 0x01;
|
||||
break;
|
||||
case KEY_RIGHTCTRL:
|
||||
mod = 0x10;
|
||||
break;
|
||||
case KEY_LEFTSHIFT:
|
||||
mod = 0x02;
|
||||
break;
|
||||
case KEY_RIGHTSHIFT:
|
||||
mod = 0x20;
|
||||
break;
|
||||
case KEY_LEFTALT:
|
||||
mod = 0x04;
|
||||
break;
|
||||
case KEY_RIGHTALT:
|
||||
mod = 0x40;
|
||||
break;
|
||||
case KEY_LEFTMETA:
|
||||
mod = 0x08;
|
||||
break;
|
||||
}
|
||||
if (value == 1) {
|
||||
switch (code) {
|
||||
case KEY_LEFTCTRL:
|
||||
mod = 0x01;
|
||||
break;
|
||||
case KEY_RIGHTCTRL:
|
||||
mod = 0x10;
|
||||
break;
|
||||
case KEY_LEFTSHIFT:
|
||||
mod = 0x02;
|
||||
break;
|
||||
case KEY_RIGHTSHIFT:
|
||||
mod = 0x20;
|
||||
break;
|
||||
case KEY_LEFTALT:
|
||||
mod = 0x04;
|
||||
break;
|
||||
case KEY_RIGHTALT:
|
||||
mod = 0x40;
|
||||
break;
|
||||
case KEY_LEFTMETA:
|
||||
mod = 0x08;
|
||||
break;
|
||||
case KEY_ESC: // ESC
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
break;
|
||||
case KEY_BACK: // Back
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
// e.kbchar = key;
|
||||
break;
|
||||
|
||||
case KEY_UP: // Up
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
break;
|
||||
case KEY_DOWN: // Down
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
break;
|
||||
case KEY_LEFT: // Left
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
break;
|
||||
e.kbchar = INPUT_BROKER_LEFT;
|
||||
case KEY_RIGHT: // Right
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
break;
|
||||
e.kbchar = 0;
|
||||
case KEY_ENTER: // Enter
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
break;
|
||||
case KEY_POWER:
|
||||
system("poweroff");
|
||||
break;
|
||||
default: // all other keys
|
||||
if (keymap[code]) {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = keymap[code];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ev[j].value) {
|
||||
modifiers |= mod;
|
||||
} else {
|
||||
modifiers &= ~mod;
|
||||
}
|
||||
report[0] = modifiers;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
if (e.inputEvent == INPUT_BROKER_ANYKEY && (modifiers && 0x22))
|
||||
e.kbchar = uppers[e.kbchar]; // doesn't get punctuation. Meh.
|
||||
this->notifyObservers(&e);
|
||||
case KEY_ENTER: // Enter
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
break;
|
||||
case KEY_POWER:
|
||||
system("poweroff");
|
||||
break;
|
||||
default: // all other keys
|
||||
if (keymap[code]) {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = keymap[code];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ev[j].value) {
|
||||
modifiers |= mod;
|
||||
} else {
|
||||
modifiers &= ~mod;
|
||||
}
|
||||
report[0] = modifiers;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
if (e.inputEvent == INPUT_BROKER_ANYKEY && (modifiers && 0x22))
|
||||
e.kbchar = uppers[e.kbchar]; // doesn't get punctuation. Meh.
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 50; // Keyscan every 50msec to avoid key bounce
|
||||
return 50; // Keyscan every 50msec to avoid key bounce
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -17,49 +17,47 @@
|
||||
|
||||
#define MAX_EVENTS 10
|
||||
|
||||
class LinuxInput : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit LinuxInput(const char *name);
|
||||
void deInit(); // Strictly for cleanly "rebooting" the binary on native
|
||||
class LinuxInput : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
explicit LinuxInput(const char *name);
|
||||
void deInit(); // Strictly for cleanly "rebooting" the binary on native
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
const char *_originName;
|
||||
bool firstTime = 1;
|
||||
int shift = 0;
|
||||
char key = 0;
|
||||
char prevkey = 0;
|
||||
private:
|
||||
const char *_originName;
|
||||
bool firstTime = 1;
|
||||
int shift = 0;
|
||||
char key = 0;
|
||||
char prevkey = 0;
|
||||
|
||||
InputEvent eventqueue[50]; // The Linux API will return multiple keypresses at a time. Queue them to not miss any.
|
||||
int queue_length = 0;
|
||||
int queue_progress = 0;
|
||||
InputEvent eventqueue[50]; // The Linux API will return multiple keypresses at a time. Queue them to not miss any.
|
||||
int queue_length = 0;
|
||||
int queue_progress = 0;
|
||||
|
||||
struct epoll_event events[MAX_EVENTS];
|
||||
int fd = -1;
|
||||
int ret;
|
||||
uint8_t report[8];
|
||||
int epollfd;
|
||||
struct epoll_event ev;
|
||||
uint8_t modifiers = 0;
|
||||
std::map<int, char> keymap{
|
||||
{KEY_A, 'a'}, {KEY_B, 'b'}, {KEY_C, 'c'}, {KEY_D, 'd'}, {KEY_E, 'e'},
|
||||
{KEY_F, 'f'}, {KEY_G, 'g'}, {KEY_H, 'h'}, {KEY_I, 'i'}, {KEY_J, 'j'},
|
||||
{KEY_K, 'k'}, {KEY_L, 'l'}, {KEY_M, 'm'}, {KEY_N, 'n'}, {KEY_O, 'o'},
|
||||
{KEY_P, 'p'}, {KEY_Q, 'q'}, {KEY_R, 'r'}, {KEY_S, 's'}, {KEY_T, 't'},
|
||||
{KEY_U, 'u'}, {KEY_V, 'v'}, {KEY_W, 'w'}, {KEY_X, 'x'}, {KEY_Y, 'y'},
|
||||
{KEY_Z, 'z'}, {KEY_BACKSPACE, 0x08}, {KEY_SPACE, ' '}, {KEY_1, '1'}, {KEY_2, '2'},
|
||||
{KEY_3, '3'}, {KEY_4, '4'}, {KEY_5, '5'}, {KEY_6, '6'}, {KEY_7, '7'},
|
||||
{KEY_8, '8'}, {KEY_9, '9'}, {KEY_0, '0'}, {KEY_DOT, '.'}, {KEY_COMMA, ','},
|
||||
{KEY_MINUS, '-'}, {KEY_EQUAL, '='}, {KEY_LEFTBRACE, '['}, {KEY_RIGHTBRACE, ']'}, {KEY_BACKSLASH, '\\'},
|
||||
{KEY_SEMICOLON, ';'}, {KEY_APOSTROPHE, '\''}, {KEY_SLASH, '/'}, {KEY_TAB, 0x09}};
|
||||
std::map<char, char> uppers{{'a', 'A'}, {'b', 'B'}, {'c', 'C'}, {'d', 'D'}, {'e', 'E'}, {'f', 'F'}, {'g', 'G'}, {'h', 'H'},
|
||||
{'i', 'I'}, {'j', 'J'}, {'k', 'K'}, {'l', 'L'}, {'m', 'M'}, {'n', 'N'}, {'o', 'O'}, {'p', 'P'},
|
||||
{'q', 'Q'}, {'r', 'R'}, {'s', 'S'}, {'t', 'T'}, {'u', 'U'}, {'v', 'V'}, {'w', 'W'}, {'x', 'X'},
|
||||
{'y', 'Y'}, {'z', 'Z'}, {'1', '!'}, {'2', '@'}, {'3', '#'}, {'4', '$'}, {'5', '%'}, {'6', '^'},
|
||||
{'7', '&'}, {'8', '*'}, {'9', '('}, {'0', ')'}, {'.', '>'}, {',', '<'}, {'-', '_'}, {'=', '+'},
|
||||
{'[', '{'}, {']', '}'}, {'\\', '|'}, {';', ':'}, {'\'', '"'}, {'/', '?'}};
|
||||
struct epoll_event events[MAX_EVENTS];
|
||||
int fd = -1;
|
||||
int ret;
|
||||
uint8_t report[8];
|
||||
int epollfd;
|
||||
struct epoll_event ev;
|
||||
uint8_t modifiers = 0;
|
||||
std::map<int, char> keymap{{KEY_A, 'a'}, {KEY_B, 'b'}, {KEY_C, 'c'}, {KEY_D, 'd'}, {KEY_E, 'e'},
|
||||
{KEY_F, 'f'}, {KEY_G, 'g'}, {KEY_H, 'h'}, {KEY_I, 'i'}, {KEY_J, 'j'},
|
||||
{KEY_K, 'k'}, {KEY_L, 'l'}, {KEY_M, 'm'}, {KEY_N, 'n'}, {KEY_O, 'o'},
|
||||
{KEY_P, 'p'}, {KEY_Q, 'q'}, {KEY_R, 'r'}, {KEY_S, 's'}, {KEY_T, 't'},
|
||||
{KEY_U, 'u'}, {KEY_V, 'v'}, {KEY_W, 'w'}, {KEY_X, 'x'}, {KEY_Y, 'y'},
|
||||
{KEY_Z, 'z'}, {KEY_BACKSPACE, 0x08}, {KEY_SPACE, ' '}, {KEY_1, '1'}, {KEY_2, '2'},
|
||||
{KEY_3, '3'}, {KEY_4, '4'}, {KEY_5, '5'}, {KEY_6, '6'}, {KEY_7, '7'},
|
||||
{KEY_8, '8'}, {KEY_9, '9'}, {KEY_0, '0'}, {KEY_DOT, '.'}, {KEY_COMMA, ','},
|
||||
{KEY_MINUS, '-'}, {KEY_EQUAL, '='}, {KEY_LEFTBRACE, '['}, {KEY_RIGHTBRACE, ']'}, {KEY_BACKSLASH, '\\'},
|
||||
{KEY_SEMICOLON, ';'}, {KEY_APOSTROPHE, '\''}, {KEY_SLASH, '/'}, {KEY_TAB, 0x09}};
|
||||
std::map<char, char> uppers{{'a', 'A'}, {'b', 'B'}, {'c', 'C'}, {'d', 'D'}, {'e', 'E'}, {'f', 'F'}, {'g', 'G'}, {'h', 'H'},
|
||||
{'i', 'I'}, {'j', 'J'}, {'k', 'K'}, {'l', 'L'}, {'m', 'M'}, {'n', 'N'}, {'o', 'O'}, {'p', 'P'},
|
||||
{'q', 'Q'}, {'r', 'R'}, {'s', 'S'}, {'t', 'T'}, {'u', 'U'}, {'v', 'V'}, {'w', 'W'}, {'x', 'X'},
|
||||
{'y', 'Y'}, {'z', 'Z'}, {'1', '!'}, {'2', '@'}, {'3', '#'}, {'4', '$'}, {'5', '%'}, {'6', '^'},
|
||||
{'7', '&'}, {'8', '*'}, {'9', '('}, {'0', ')'}, {'.', '>'}, {',', '<'}, {'-', '_'}, {'=', '+'},
|
||||
{'[', '{'}, {']', '}'}, {'\\', '|'}, {';', ':'}, {'\'', '"'}, {'/', '?'}};
|
||||
};
|
||||
#endif
|
||||
@@ -7,9 +7,6 @@ LinuxInputImpl *aLinuxInputImpl;
|
||||
|
||||
LinuxInputImpl::LinuxInputImpl() : LinuxInput("LinuxInput") {}
|
||||
|
||||
void LinuxInputImpl::init()
|
||||
{
|
||||
inputBroker->registerSource(this);
|
||||
}
|
||||
void LinuxInputImpl::init() { inputBroker->registerSource(this); }
|
||||
|
||||
#endif
|
||||
@@ -11,11 +11,10 @@
|
||||
* handlers, thus you need to have a RotaryEncoderInterrupt implementation.
|
||||
*/
|
||||
|
||||
class LinuxInputImpl : public LinuxInput
|
||||
{
|
||||
public:
|
||||
LinuxInputImpl();
|
||||
void init();
|
||||
class LinuxInputImpl : public LinuxInput {
|
||||
public:
|
||||
LinuxInputImpl();
|
||||
void init();
|
||||
};
|
||||
extern LinuxInputImpl *aLinuxInputImpl;
|
||||
#endif
|
||||
@@ -87,347 +87,320 @@ unsigned char MPR121_LongPressMap[12] = {MPR121_ESC, ' ', MPR121_NONE,
|
||||
// Rotated Layout
|
||||
uint8_t MPR121_KeyMap[12] = {2, 5, 8, 11, 1, 4, 7, 10, 0, 3, 6, 9};
|
||||
|
||||
MPR121Keyboard::MPR121Keyboard() : m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr)
|
||||
{
|
||||
// LOG_DEBUG("MPR121 @ %02x", m_addr);
|
||||
state = Init;
|
||||
last_key = -1;
|
||||
last_tap = 0L;
|
||||
char_idx = 0;
|
||||
queue = "";
|
||||
MPR121Keyboard::MPR121Keyboard() : m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr) {
|
||||
// LOG_DEBUG("MPR121 @ %02x", m_addr);
|
||||
state = Init;
|
||||
last_key = -1;
|
||||
last_tap = 0L;
|
||||
char_idx = 0;
|
||||
queue = "";
|
||||
}
|
||||
|
||||
void MPR121Keyboard::begin(uint8_t addr, TwoWire *wire)
|
||||
{
|
||||
m_addr = addr;
|
||||
m_wire = wire;
|
||||
void MPR121Keyboard::begin(uint8_t addr, TwoWire *wire) {
|
||||
m_addr = addr;
|
||||
m_wire = wire;
|
||||
|
||||
m_wire->begin();
|
||||
m_wire->begin();
|
||||
|
||||
reset();
|
||||
reset();
|
||||
}
|
||||
|
||||
void MPR121Keyboard::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 MPR121Keyboard::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 MPR121Keyboard::reset()
|
||||
{
|
||||
LOG_DEBUG("MPR121 Reset");
|
||||
// Trigger a MPR121 Soft Reset
|
||||
if (m_wire) {
|
||||
m_wire->beginTransmission(m_addr);
|
||||
m_wire->write(_MPR121_REG_SOFT_RESET);
|
||||
m_wire->endTransmission();
|
||||
void MPR121Keyboard::reset() {
|
||||
LOG_DEBUG("MPR121 Reset");
|
||||
// Trigger a MPR121 Soft Reset
|
||||
if (m_wire) {
|
||||
m_wire->beginTransmission(m_addr);
|
||||
m_wire->write(_MPR121_REG_SOFT_RESET);
|
||||
m_wire->endTransmission();
|
||||
}
|
||||
if (writeCallback) {
|
||||
uint8_t data = 0;
|
||||
writeCallback(m_addr, _MPR121_REG_SOFT_RESET, &data, 0);
|
||||
}
|
||||
delay(100);
|
||||
// Reset Electrode Configuration to 0x00, Stop Mode
|
||||
writeRegister(_MPR121_REG_ELECTRODE_CONFIG, 0x00);
|
||||
delay(100);
|
||||
|
||||
LOG_DEBUG("MPR121 Configuring");
|
||||
// Set touch release thresholds
|
||||
for (uint8_t i = 0; i < 12; i++) {
|
||||
// Set touch threshold
|
||||
writeRegister(_MPR121_REG_TOUCH_THRESHOLD + (i * 2), 10);
|
||||
delay(20);
|
||||
// Set release threshold
|
||||
writeRegister(_MPR121_REG_RELEASE_THRESHOLD + (i * 2), 5);
|
||||
delay(20);
|
||||
}
|
||||
// Configure filtering and baseline registers
|
||||
writeRegister(_MPR121_REG_MAX_HALF_DELTA_RISING, 0x05);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_MAX_HALF_DELTA_FALLING, 0x01);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_HALF_DELTA_RISING, 0x01);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_HALF_DELTA_FALLING, 0x05);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_HALF_DELTA_TOUCHED, 0x00);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_RISING, 0x05);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_FALLING, 0x01);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_TOUCHED, 0x00);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_RISING, 0x00);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_FALLING, 0x00);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_TOUCHED, 0x00);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_AUTOCONF_CTRL0, 0x04); // Auto-config enable
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_AUTOCONF_CTRL1, 0x00); // Ensure no auto-config interrupt
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_DEBOUNCE, 0x02);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_CONFIG1, 0x20);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_CONFIG2, 0x21);
|
||||
delay(20);
|
||||
// Enter run mode by Seting partial filter calibration tracking, disable proximity detection, enable 12 channels
|
||||
writeRegister(_MPR121_REG_ELECTRODE_CONFIG, ECR_CALIBRATION_TRACK_FROM_FULL_FILTER | ECR_PROXIMITY_DETECTION_OFF | ECR_TOUCH_DETECTION_12CH);
|
||||
delay(100);
|
||||
LOG_DEBUG("MPR121 Run");
|
||||
state = Idle;
|
||||
}
|
||||
|
||||
void MPR121Keyboard::attachInterrupt(uint8_t pin, void (*func)(void)) const {
|
||||
pinMode(pin, INPUT_PULLUP);
|
||||
::attachInterrupt(digitalPinToInterrupt(pin), func, RISING);
|
||||
}
|
||||
|
||||
void MPR121Keyboard::detachInterrupt(uint8_t pin) const { ::detachInterrupt(pin); }
|
||||
|
||||
uint8_t MPR121Keyboard::status() const { return readRegister16(_MPR121_REG_KEY); }
|
||||
|
||||
uint8_t MPR121Keyboard::keyCount() const {
|
||||
// Read the key register
|
||||
uint16_t keyRegister = readRegister16(_MPR121_REG_KEY);
|
||||
return keyCount(keyRegister);
|
||||
}
|
||||
|
||||
uint8_t MPR121Keyboard::keyCount(uint16_t value) const {
|
||||
// Mask the first 12 bits
|
||||
uint16_t buttonState = value & _KEY_MASK;
|
||||
|
||||
// Count how many bits are set to 1 (i.e., how many buttons are pressed)
|
||||
uint8_t numButtonsPressed = 0;
|
||||
for (uint8_t i = 0; i < 12; ++i) {
|
||||
if (buttonState & (1 << i)) {
|
||||
numButtonsPressed++;
|
||||
}
|
||||
if (writeCallback) {
|
||||
uint8_t data = 0;
|
||||
writeCallback(m_addr, _MPR121_REG_SOFT_RESET, &data, 0);
|
||||
}
|
||||
delay(100);
|
||||
// Reset Electrode Configuration to 0x00, Stop Mode
|
||||
writeRegister(_MPR121_REG_ELECTRODE_CONFIG, 0x00);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
LOG_DEBUG("MPR121 Configuring");
|
||||
// Set touch release thresholds
|
||||
for (uint8_t i = 0; i < 12; i++) {
|
||||
// Set touch threshold
|
||||
writeRegister(_MPR121_REG_TOUCH_THRESHOLD + (i * 2), 10);
|
||||
delay(20);
|
||||
// Set release threshold
|
||||
writeRegister(_MPR121_REG_RELEASE_THRESHOLD + (i * 2), 5);
|
||||
delay(20);
|
||||
}
|
||||
// Configure filtering and baseline registers
|
||||
writeRegister(_MPR121_REG_MAX_HALF_DELTA_RISING, 0x05);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_MAX_HALF_DELTA_FALLING, 0x01);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_HALF_DELTA_RISING, 0x01);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_HALF_DELTA_FALLING, 0x05);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_HALF_DELTA_TOUCHED, 0x00);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_RISING, 0x05);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_FALLING, 0x01);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_TOUCHED, 0x00);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_RISING, 0x00);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_FALLING, 0x00);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_TOUCHED, 0x00);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_AUTOCONF_CTRL0, 0x04); // Auto-config enable
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_AUTOCONF_CTRL1, 0x00); // Ensure no auto-config interrupt
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_DEBOUNCE, 0x02);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_CONFIG1, 0x20);
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_CONFIG2, 0x21);
|
||||
delay(20);
|
||||
// Enter run mode by Seting partial filter calibration tracking, disable proximity detection, enable 12 channels
|
||||
writeRegister(_MPR121_REG_ELECTRODE_CONFIG,
|
||||
ECR_CALIBRATION_TRACK_FROM_FULL_FILTER | ECR_PROXIMITY_DETECTION_OFF | ECR_TOUCH_DETECTION_12CH);
|
||||
delay(100);
|
||||
LOG_DEBUG("MPR121 Run");
|
||||
state = Idle;
|
||||
return numButtonsPressed;
|
||||
}
|
||||
|
||||
void MPR121Keyboard::attachInterrupt(uint8_t pin, void (*func)(void)) const
|
||||
{
|
||||
pinMode(pin, INPUT_PULLUP);
|
||||
::attachInterrupt(digitalPinToInterrupt(pin), func, RISING);
|
||||
bool MPR121Keyboard::hasEvent() { return queue.length() > 0; }
|
||||
|
||||
void MPR121Keyboard::queueEvent(char next) {
|
||||
if (next == MPR121_NONE) {
|
||||
return;
|
||||
}
|
||||
queue.concat(next);
|
||||
}
|
||||
|
||||
void MPR121Keyboard::detachInterrupt(uint8_t pin) const
|
||||
{
|
||||
::detachInterrupt(pin);
|
||||
char MPR121Keyboard::dequeueEvent() {
|
||||
if (queue.length() < 1) {
|
||||
return MPR121_NONE;
|
||||
}
|
||||
char next = queue.charAt(0);
|
||||
queue.remove(0, 1);
|
||||
return next;
|
||||
}
|
||||
|
||||
uint8_t MPR121Keyboard::status() const
|
||||
{
|
||||
return readRegister16(_MPR121_REG_KEY);
|
||||
}
|
||||
|
||||
uint8_t MPR121Keyboard::keyCount() const
|
||||
{
|
||||
void MPR121Keyboard::trigger() {
|
||||
// Intended to fire in response to an interrupt from the MPR121 or a longpress callback
|
||||
// Only functional if not in Init state
|
||||
if (state != Init) {
|
||||
// Read the key register
|
||||
uint16_t keyRegister = readRegister16(_MPR121_REG_KEY);
|
||||
return keyCount(keyRegister);
|
||||
uint8_t keysPressed = keyCount(keyRegister);
|
||||
if (keysPressed == 0) {
|
||||
// No buttons pressed
|
||||
if (state == Held)
|
||||
released();
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
if (keysPressed == 1) {
|
||||
// No buttons pressed
|
||||
if (state == Held || state == HeldLong)
|
||||
held(keyRegister);
|
||||
if (state == Idle)
|
||||
pressed(keyRegister);
|
||||
return;
|
||||
}
|
||||
if (keysPressed > 1) {
|
||||
// Multipress
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t MPR121Keyboard::keyCount(uint16_t value) const
|
||||
{
|
||||
// Mask the first 12 bits
|
||||
uint16_t buttonState = value & _KEY_MASK;
|
||||
|
||||
// Count how many bits are set to 1 (i.e., how many buttons are pressed)
|
||||
uint8_t numButtonsPressed = 0;
|
||||
for (uint8_t i = 0; i < 12; ++i) {
|
||||
if (buttonState & (1 << i)) {
|
||||
numButtonsPressed++;
|
||||
}
|
||||
void MPR121Keyboard::pressed(uint16_t keyRegister) {
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
if (keyCount(keyRegister) != 1) {
|
||||
LOG_DEBUG("Multipress");
|
||||
return;
|
||||
} else {
|
||||
LOG_DEBUG("Pressed");
|
||||
}
|
||||
uint16_t buttonState = keyRegister & _KEY_MASK;
|
||||
uint8_t next_pin = 0;
|
||||
for (uint8_t i = 0; i < 12; ++i) {
|
||||
if (buttonState & (1 << i)) {
|
||||
next_pin = i;
|
||||
}
|
||||
|
||||
return numButtonsPressed;
|
||||
}
|
||||
uint8_t next_key = MPR121_KeyMap[next_pin];
|
||||
LOG_DEBUG("MPR121 Pin: %i Key: %i", next_pin, next_key);
|
||||
uint32_t now = millis();
|
||||
int32_t tap_interval = now - last_tap;
|
||||
if (tap_interval < 0) {
|
||||
// long running, millis has overflowed.
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
if (next_key != last_key || tap_interval > MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0;
|
||||
} else {
|
||||
char_idx += 1;
|
||||
}
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
state = Held;
|
||||
return;
|
||||
}
|
||||
|
||||
bool MPR121Keyboard::hasEvent()
|
||||
{
|
||||
return queue.length() > 0;
|
||||
}
|
||||
|
||||
void MPR121Keyboard::queueEvent(char next)
|
||||
{
|
||||
if (next == MPR121_NONE) {
|
||||
return;
|
||||
void MPR121Keyboard::held(uint16_t keyRegister) {
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
if (keyCount(keyRegister) != 1) {
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Held");
|
||||
uint16_t buttonState = keyRegister & _KEY_MASK;
|
||||
uint8_t next_key = 0;
|
||||
for (uint8_t i = 0; i < 12; ++i) {
|
||||
if (buttonState & (1 << i)) {
|
||||
next_key = MPR121_KeyMap[i];
|
||||
}
|
||||
queue.concat(next);
|
||||
}
|
||||
|
||||
char MPR121Keyboard::dequeueEvent()
|
||||
{
|
||||
if (queue.length() < 1) {
|
||||
return MPR121_NONE;
|
||||
}
|
||||
char next = queue.charAt(0);
|
||||
queue.remove(0, 1);
|
||||
return next;
|
||||
}
|
||||
|
||||
void MPR121Keyboard::trigger()
|
||||
{
|
||||
// Intended to fire in response to an interrupt from the MPR121 or a longpress callback
|
||||
// Only functional if not in Init state
|
||||
if (state != Init) {
|
||||
// Read the key register
|
||||
uint16_t keyRegister = readRegister16(_MPR121_REG_KEY);
|
||||
uint8_t keysPressed = keyCount(keyRegister);
|
||||
if (keysPressed == 0) {
|
||||
// No buttons pressed
|
||||
if (state == Held)
|
||||
released();
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
if (keysPressed == 1) {
|
||||
// No buttons pressed
|
||||
if (state == Held || state == HeldLong)
|
||||
held(keyRegister);
|
||||
if (state == Idle)
|
||||
pressed(keyRegister);
|
||||
return;
|
||||
}
|
||||
if (keysPressed > 1) {
|
||||
// Multipress
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
void MPR121Keyboard::pressed(uint16_t keyRegister)
|
||||
{
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
if (keyCount(keyRegister) != 1) {
|
||||
LOG_DEBUG("Multipress");
|
||||
return;
|
||||
} else {
|
||||
LOG_DEBUG("Pressed");
|
||||
}
|
||||
uint16_t buttonState = keyRegister & _KEY_MASK;
|
||||
uint8_t next_pin = 0;
|
||||
for (uint8_t i = 0; i < 12; ++i) {
|
||||
if (buttonState & (1 << i)) {
|
||||
next_pin = i;
|
||||
}
|
||||
}
|
||||
uint8_t next_key = MPR121_KeyMap[next_pin];
|
||||
LOG_DEBUG("MPR121 Pin: %i Key: %i", next_pin, next_key);
|
||||
uint32_t now = millis();
|
||||
int32_t tap_interval = now - last_tap;
|
||||
if (tap_interval < 0) {
|
||||
// long running, millis has overflowed.
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
if (next_key != last_key || tap_interval > MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0;
|
||||
} else {
|
||||
char_idx += 1;
|
||||
}
|
||||
last_key = next_key;
|
||||
}
|
||||
uint32_t now = millis();
|
||||
int32_t held_interval = now - last_tap;
|
||||
if (held_interval < 0 || next_key != last_key) {
|
||||
// long running, millis has overflowed, or a key has been switched quickly...
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
if (held_interval > LONG_PRESS_THRESHOLD) {
|
||||
// Set state to heldlong, send a longpress, and reset the timer...
|
||||
state = HeldLong; // heldlong will allow this function to still fire, but prevent a "release"
|
||||
queueEvent(MPR121_LongPressMap[last_key]);
|
||||
last_tap = now;
|
||||
state = Held;
|
||||
LOG_DEBUG("Long Press Key: %i Map: %i", last_key, MPR121_LongPressMap[last_key]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void MPR121Keyboard::released() {
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
|
||||
void MPR121Keyboard::held(uint16_t keyRegister)
|
||||
{
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
if (keyCount(keyRegister) != 1) {
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Held");
|
||||
uint16_t buttonState = keyRegister & _KEY_MASK;
|
||||
uint8_t next_key = 0;
|
||||
for (uint8_t i = 0; i < 12; ++i) {
|
||||
if (buttonState & (1 << i)) {
|
||||
next_key = MPR121_KeyMap[i];
|
||||
}
|
||||
}
|
||||
uint32_t now = millis();
|
||||
int32_t held_interval = now - last_tap;
|
||||
if (held_interval < 0 || next_key != last_key) {
|
||||
// long running, millis has overflowed, or a key has been switched quickly...
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
if (held_interval > LONG_PRESS_THRESHOLD) {
|
||||
// Set state to heldlong, send a longpress, and reset the timer...
|
||||
state = HeldLong; // heldlong will allow this function to still fire, but prevent a "release"
|
||||
queueEvent(MPR121_LongPressMap[last_key]);
|
||||
last_tap = now;
|
||||
LOG_DEBUG("Long Press Key: %i Map: %i", last_key, MPR121_LongPressMap[last_key]);
|
||||
}
|
||||
}
|
||||
// would clear longpress callback... later.
|
||||
if (last_key < 0 || last_key > _NUM_KEYS) { // reset to idle if last_key out of bounds
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Released");
|
||||
if (char_idx > 0 && TapMod[last_key] > 1) {
|
||||
queueEvent(MPR121_BSP);
|
||||
LOG_DEBUG("Multi Press, Backspace");
|
||||
}
|
||||
queueEvent(MPR121_TapMap[last_key][(char_idx % TapMod[last_key])]);
|
||||
LOG_DEBUG("Key Press: %i Index:%i if %i Map: %i", last_key, char_idx, TapMod[last_key], MPR121_TapMap[last_key][(char_idx % TapMod[last_key])]);
|
||||
}
|
||||
|
||||
void MPR121Keyboard::released()
|
||||
{
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
// would clear longpress callback... later.
|
||||
if (last_key < 0 || last_key > _NUM_KEYS) { // reset to idle if last_key out of bounds
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Released");
|
||||
if (char_idx > 0 && TapMod[last_key] > 1) {
|
||||
queueEvent(MPR121_BSP);
|
||||
LOG_DEBUG("Multi Press, Backspace");
|
||||
}
|
||||
queueEvent(MPR121_TapMap[last_key][(char_idx % TapMod[last_key])]);
|
||||
LOG_DEBUG("Key Press: %i Index:%i if %i Map: %i", last_key, char_idx, TapMod[last_key],
|
||||
MPR121_TapMap[last_key][(char_idx % TapMod[last_key])]);
|
||||
uint8_t MPR121Keyboard::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;
|
||||
}
|
||||
|
||||
uint8_t MPR121Keyboard::readRegister8(uint8_t reg) const
|
||||
{
|
||||
if (m_wire) {
|
||||
m_wire->beginTransmission(m_addr);
|
||||
m_wire->write(reg);
|
||||
m_wire->endTransmission();
|
||||
uint16_t MPR121Keyboard::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)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;
|
||||
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];
|
||||
}
|
||||
|
||||
uint16_t MPR121Keyboard::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();
|
||||
void MPR121Keyboard::writeRegister(uint8_t reg, uint8_t value) {
|
||||
uint8_t data[2];
|
||||
data[0] = reg;
|
||||
data[1] = value;
|
||||
|
||||
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 MPR121Keyboard::writeRegister(uint8_t reg, uint8_t value)
|
||||
{
|
||||
uint8_t data[2];
|
||||
data[0] = reg;
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -5,52 +5,51 @@
|
||||
#include <Wire.h>
|
||||
#include <main.h>
|
||||
|
||||
class MPR121Keyboard
|
||||
{
|
||||
public:
|
||||
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
|
||||
class MPR121Keyboard {
|
||||
public:
|
||||
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
|
||||
|
||||
enum MPR121States { Init = 0, Idle, Held, HeldLong, Busy };
|
||||
enum MPR121States { Init = 0, Idle, Held, HeldLong, Busy };
|
||||
|
||||
MPR121States state;
|
||||
MPR121States state;
|
||||
|
||||
int8_t last_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int8_t last_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
|
||||
String queue;
|
||||
String queue;
|
||||
|
||||
MPR121Keyboard();
|
||||
MPR121Keyboard();
|
||||
|
||||
void begin(uint8_t addr = MPR121_KB_ADDR, TwoWire *wire = &Wire);
|
||||
void begin(uint8_t addr = MPR121_KB_ADDR, TwoWire *wire = &Wire);
|
||||
|
||||
void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = MPR121_KB_ADDR);
|
||||
void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = MPR121_KB_ADDR);
|
||||
|
||||
void reset(void);
|
||||
void reset(void);
|
||||
|
||||
void attachInterrupt(uint8_t pin, void (*func)(void)) const;
|
||||
void detachInterrupt(uint8_t pin) const;
|
||||
void attachInterrupt(uint8_t pin, void (*func)(void)) const;
|
||||
void detachInterrupt(uint8_t pin) const;
|
||||
|
||||
void trigger(void);
|
||||
void pressed(uint16_t value);
|
||||
void held(uint16_t value);
|
||||
void released(void);
|
||||
void trigger(void);
|
||||
void pressed(uint16_t value);
|
||||
void held(uint16_t value);
|
||||
void released(void);
|
||||
|
||||
uint8_t status(void) const;
|
||||
uint8_t keyCount(void) const;
|
||||
uint8_t keyCount(uint16_t value) const;
|
||||
uint8_t status(void) const;
|
||||
uint8_t keyCount(void) const;
|
||||
uint8_t keyCount(uint16_t value) const;
|
||||
|
||||
bool hasEvent(void);
|
||||
char dequeueEvent(void);
|
||||
void queueEvent(char);
|
||||
bool hasEvent(void);
|
||||
char dequeueEvent(void);
|
||||
void queueEvent(char);
|
||||
|
||||
uint8_t readRegister8(uint8_t reg) const;
|
||||
uint16_t readRegister16(uint8_t reg) const;
|
||||
void writeRegister(uint8_t reg, uint8_t 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;
|
||||
private:
|
||||
TwoWire *m_wire;
|
||||
uint8_t m_addr;
|
||||
i2c_com_fptr_t readCallback;
|
||||
i2c_com_fptr_t writeCallback;
|
||||
};
|
||||
@@ -11,131 +11,122 @@
|
||||
|
||||
RotaryEncoderImpl *rotaryEncoderImpl;
|
||||
|
||||
RotaryEncoderImpl::RotaryEncoderImpl()
|
||||
{
|
||||
RotaryEncoderImpl::RotaryEncoderImpl() {
|
||||
rotary = nullptr;
|
||||
#ifdef ARCH_ESP32
|
||||
isFirstInit = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
RotaryEncoderImpl::~RotaryEncoderImpl() {
|
||||
LOG_DEBUG("RotaryEncoderImpl destructor");
|
||||
detachRotaryEncoderInterrupts();
|
||||
|
||||
if (rotary != nullptr) {
|
||||
delete rotary;
|
||||
rotary = nullptr;
|
||||
#ifdef ARCH_ESP32
|
||||
isFirstInit = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
RotaryEncoderImpl::~RotaryEncoderImpl()
|
||||
{
|
||||
LOG_DEBUG("RotaryEncoderImpl destructor");
|
||||
detachRotaryEncoderInterrupts();
|
||||
bool RotaryEncoderImpl::init() {
|
||||
if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
|
||||
moduleConfig.canned_message.inputbroker_pin_b == 0) {
|
||||
// Input device is disabled.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rotary != nullptr) {
|
||||
delete rotary;
|
||||
rotary = nullptr;
|
||||
}
|
||||
}
|
||||
eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
|
||||
eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
|
||||
eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
|
||||
|
||||
bool RotaryEncoderImpl::init()
|
||||
{
|
||||
if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
|
||||
moduleConfig.canned_message.inputbroker_pin_b == 0) {
|
||||
// Input device is disabled.
|
||||
return false;
|
||||
}
|
||||
if (rotary == nullptr) {
|
||||
rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b,
|
||||
moduleConfig.canned_message.inputbroker_pin_press);
|
||||
}
|
||||
|
||||
eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
|
||||
eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
|
||||
eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
|
||||
|
||||
if (rotary == nullptr) {
|
||||
rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b,
|
||||
moduleConfig.canned_message.inputbroker_pin_press);
|
||||
}
|
||||
|
||||
attachRotaryEncoderInterrupts();
|
||||
attachRotaryEncoderInterrupts();
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Register callbacks for before and after lightsleep
|
||||
// Used to detach and reattach interrupts
|
||||
if (isFirstInit) {
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
isFirstInit = false;
|
||||
}
|
||||
// Register callbacks for before and after lightsleep
|
||||
// Used to detach and reattach interrupts
|
||||
if (isFirstInit) {
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
isFirstInit = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
|
||||
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
|
||||
eventPressed);
|
||||
return true;
|
||||
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
|
||||
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw, eventPressed);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RotaryEncoderImpl::pollOnce()
|
||||
{
|
||||
InputEvent e{ORIGIN_NAME, INPUT_BROKER_NONE, 0, 0, 0};
|
||||
void RotaryEncoderImpl::pollOnce() {
|
||||
InputEvent e{ORIGIN_NAME, INPUT_BROKER_NONE, 0, 0, 0};
|
||||
|
||||
static uint32_t lastPressed = millis();
|
||||
if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
|
||||
if (lastPressed + 200 < millis()) {
|
||||
LOG_DEBUG("Rotary event Press");
|
||||
lastPressed = millis();
|
||||
e.inputEvent = this->eventPressed;
|
||||
inputBroker->queueInputEvent(&e);
|
||||
}
|
||||
static uint32_t lastPressed = millis();
|
||||
if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
|
||||
if (lastPressed + 200 < millis()) {
|
||||
LOG_DEBUG("Rotary event Press");
|
||||
lastPressed = millis();
|
||||
e.inputEvent = this->eventPressed;
|
||||
inputBroker->queueInputEvent(&e);
|
||||
}
|
||||
}
|
||||
|
||||
switch (rotary->process()) {
|
||||
case RotaryEncoder::DIRECTION_CW:
|
||||
LOG_DEBUG("Rotary event CW");
|
||||
e.inputEvent = this->eventCw;
|
||||
inputBroker->queueInputEvent(&e);
|
||||
break;
|
||||
case RotaryEncoder::DIRECTION_CCW:
|
||||
LOG_DEBUG("Rotary event CCW");
|
||||
e.inputEvent = this->eventCcw;
|
||||
inputBroker->queueInputEvent(&e);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (rotary->process()) {
|
||||
case RotaryEncoder::DIRECTION_CW:
|
||||
LOG_DEBUG("Rotary event CW");
|
||||
e.inputEvent = this->eventCw;
|
||||
inputBroker->queueInputEvent(&e);
|
||||
break;
|
||||
case RotaryEncoder::DIRECTION_CCW:
|
||||
LOG_DEBUG("Rotary event CCW");
|
||||
e.inputEvent = this->eventCcw;
|
||||
inputBroker->queueInputEvent(&e);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RotaryEncoderImpl::detachRotaryEncoderInterrupts()
|
||||
{
|
||||
LOG_DEBUG("RotaryEncoderImpl detach button interrupts");
|
||||
if (interruptInstance == this) {
|
||||
detachInterrupt(moduleConfig.canned_message.inputbroker_pin_a);
|
||||
detachInterrupt(moduleConfig.canned_message.inputbroker_pin_b);
|
||||
detachInterrupt(moduleConfig.canned_message.inputbroker_pin_press);
|
||||
interruptInstance = nullptr;
|
||||
} else {
|
||||
LOG_WARN("RotaryEncoderImpl: interrupts already detached");
|
||||
}
|
||||
void RotaryEncoderImpl::detachRotaryEncoderInterrupts() {
|
||||
LOG_DEBUG("RotaryEncoderImpl detach button interrupts");
|
||||
if (interruptInstance == this) {
|
||||
detachInterrupt(moduleConfig.canned_message.inputbroker_pin_a);
|
||||
detachInterrupt(moduleConfig.canned_message.inputbroker_pin_b);
|
||||
detachInterrupt(moduleConfig.canned_message.inputbroker_pin_press);
|
||||
interruptInstance = nullptr;
|
||||
} else {
|
||||
LOG_WARN("RotaryEncoderImpl: interrupts already detached");
|
||||
}
|
||||
}
|
||||
|
||||
void RotaryEncoderImpl::attachRotaryEncoderInterrupts()
|
||||
{
|
||||
LOG_DEBUG("RotaryEncoderImpl attach button interrupts");
|
||||
if (rotary != nullptr && interruptInstance == nullptr) {
|
||||
rotary->resetButton();
|
||||
void RotaryEncoderImpl::attachRotaryEncoderInterrupts() {
|
||||
LOG_DEBUG("RotaryEncoderImpl attach button interrupts");
|
||||
if (rotary != nullptr && interruptInstance == nullptr) {
|
||||
rotary->resetButton();
|
||||
|
||||
interruptInstance = this;
|
||||
auto interruptHandler = []() { inputBroker->requestPollSoon(interruptInstance); };
|
||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
|
||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
|
||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
|
||||
} else {
|
||||
LOG_WARN("RotaryEncoderImpl: interrupts already attached");
|
||||
}
|
||||
interruptInstance = this;
|
||||
auto interruptHandler = []() { inputBroker->requestPollSoon(interruptInstance); };
|
||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
|
||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
|
||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
|
||||
} else {
|
||||
LOG_WARN("RotaryEncoderImpl: interrupts already attached");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
|
||||
int RotaryEncoderImpl::beforeLightSleep(void *unused)
|
||||
{
|
||||
detachRotaryEncoderInterrupts();
|
||||
return 0; // Indicates success;
|
||||
int RotaryEncoderImpl::beforeLightSleep(void *unused) {
|
||||
detachRotaryEncoderInterrupts();
|
||||
return 0; // Indicates success;
|
||||
}
|
||||
|
||||
int RotaryEncoderImpl::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
||||
{
|
||||
attachRotaryEncoderInterrupts();
|
||||
return 0; // Indicates success;
|
||||
int RotaryEncoderImpl::afterLightSleep(esp_sleep_wakeup_cause_t cause) {
|
||||
attachRotaryEncoderInterrupts();
|
||||
return 0; // Indicates success;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -8,41 +8,39 @@
|
||||
|
||||
class RotaryEncoder;
|
||||
|
||||
class RotaryEncoderImpl final : public InputPollable
|
||||
{
|
||||
public:
|
||||
RotaryEncoderImpl();
|
||||
~RotaryEncoderImpl() override;
|
||||
bool init();
|
||||
virtual void pollOnce() override;
|
||||
// Disconnect and reconnect interrupts for light sleep
|
||||
class RotaryEncoderImpl final : public InputPollable {
|
||||
public:
|
||||
RotaryEncoderImpl();
|
||||
~RotaryEncoderImpl() override;
|
||||
bool init();
|
||||
virtual void pollOnce() override;
|
||||
// Disconnect and reconnect interrupts for light sleep
|
||||
#ifdef ARCH_ESP32
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
static RotaryEncoderImpl *interruptInstance;
|
||||
protected:
|
||||
static RotaryEncoderImpl *interruptInstance;
|
||||
|
||||
input_broker_event eventCw = INPUT_BROKER_NONE;
|
||||
input_broker_event eventCcw = INPUT_BROKER_NONE;
|
||||
input_broker_event eventPressed = INPUT_BROKER_NONE;
|
||||
input_broker_event eventCw = INPUT_BROKER_NONE;
|
||||
input_broker_event eventCcw = INPUT_BROKER_NONE;
|
||||
input_broker_event eventPressed = INPUT_BROKER_NONE;
|
||||
|
||||
RotaryEncoder *rotary;
|
||||
RotaryEncoder *rotary;
|
||||
|
||||
private:
|
||||
private:
|
||||
#ifdef ARCH_ESP32
|
||||
bool isFirstInit;
|
||||
bool isFirstInit;
|
||||
#endif
|
||||
void detachRotaryEncoderInterrupts();
|
||||
void attachRotaryEncoderInterrupts();
|
||||
void detachRotaryEncoderInterrupts();
|
||||
void attachRotaryEncoderInterrupts();
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Get notified when lightsleep begins and ends
|
||||
CallbackObserver<RotaryEncoderImpl, void *> lsObserver =
|
||||
CallbackObserver<RotaryEncoderImpl, void *>(this, &RotaryEncoderImpl::beforeLightSleep);
|
||||
CallbackObserver<RotaryEncoderImpl, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<RotaryEncoderImpl, esp_sleep_wakeup_cause_t>(this, &RotaryEncoderImpl::afterLightSleep);
|
||||
// Get notified when lightsleep begins and ends
|
||||
CallbackObserver<RotaryEncoderImpl, void *> lsObserver = CallbackObserver<RotaryEncoderImpl, void *>(this, &RotaryEncoderImpl::beforeLightSleep);
|
||||
CallbackObserver<RotaryEncoderImpl, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<RotaryEncoderImpl, esp_sleep_wakeup_cause_t>(this, &RotaryEncoderImpl::afterLightSleep);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -1,129 +1,120 @@
|
||||
#include "RotaryEncoderInterruptBase.h"
|
||||
#include "configuration.h"
|
||||
|
||||
RotaryEncoderInterruptBase::RotaryEncoderInterruptBase(const char *name) : concurrency::OSThread(name)
|
||||
{
|
||||
this->_originName = name;
|
||||
}
|
||||
RotaryEncoderInterruptBase::RotaryEncoderInterruptBase(const char *name) : concurrency::OSThread(name) { this->_originName = name; }
|
||||
|
||||
void RotaryEncoderInterruptBase::init(
|
||||
uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw,
|
||||
input_broker_event eventPressed, input_broker_event eventPressedLong,
|
||||
uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw, input_broker_event eventPressed,
|
||||
input_broker_event eventPressedLong,
|
||||
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress) :
|
||||
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)())
|
||||
{
|
||||
this->_pinA = pinA;
|
||||
this->_pinB = pinB;
|
||||
this->_pinPress = pinPress;
|
||||
this->_eventCw = eventCw;
|
||||
this->_eventCcw = eventCcw;
|
||||
this->_eventPressed = eventPressed;
|
||||
this->_eventPressedLong = eventPressedLong;
|
||||
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)()) {
|
||||
this->_pinA = pinA;
|
||||
this->_pinB = pinB;
|
||||
this->_pinPress = pinPress;
|
||||
this->_eventCw = eventCw;
|
||||
this->_eventCcw = eventCcw;
|
||||
this->_eventPressed = eventPressed;
|
||||
this->_eventPressedLong = eventPressedLong;
|
||||
|
||||
bool isRAK = false;
|
||||
bool isRAK = false;
|
||||
#ifdef RAK_4631
|
||||
isRAK = true;
|
||||
isRAK = true;
|
||||
#endif
|
||||
|
||||
if (!isRAK || pinPress != 0) {
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
attachInterrupt(pinPress, onIntPress, CHANGE);
|
||||
}
|
||||
if (!isRAK || this->_pinA != 0) {
|
||||
pinMode(this->_pinA, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinA, onIntA, CHANGE);
|
||||
}
|
||||
if (!isRAK || this->_pinA != 0) {
|
||||
pinMode(this->_pinB, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinB, onIntB, CHANGE);
|
||||
}
|
||||
if (!isRAK || pinPress != 0) {
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
attachInterrupt(pinPress, onIntPress, CHANGE);
|
||||
}
|
||||
if (!isRAK || this->_pinA != 0) {
|
||||
pinMode(this->_pinA, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinA, onIntA, CHANGE);
|
||||
}
|
||||
if (!isRAK || this->_pinA != 0) {
|
||||
pinMode(this->_pinB, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinB, onIntB, CHANGE);
|
||||
}
|
||||
|
||||
this->rotaryLevelA = digitalRead(this->_pinA);
|
||||
this->rotaryLevelB = digitalRead(this->_pinB);
|
||||
LOG_INFO("Rotary initialized (%d, %d, %d)", this->_pinA, this->_pinB, pinPress);
|
||||
this->rotaryLevelA = digitalRead(this->_pinA);
|
||||
this->rotaryLevelB = digitalRead(this->_pinB);
|
||||
LOG_INFO("Rotary initialized (%d, %d, %d)", this->_pinA, this->_pinB, pinPress);
|
||||
}
|
||||
|
||||
int32_t RotaryEncoderInterruptBase::runOnce()
|
||||
{
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
unsigned long now = millis();
|
||||
int32_t RotaryEncoderInterruptBase::runOnce() {
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
unsigned long now = millis();
|
||||
|
||||
// Handle press long/short detection
|
||||
if (this->action == ROTARY_ACTION_PRESSED) {
|
||||
bool buttonPressed = !digitalRead(_pinPress);
|
||||
if (!pressDetected && buttonPressed) {
|
||||
pressDetected = true;
|
||||
pressStartTime = now;
|
||||
}
|
||||
|
||||
if (pressDetected) {
|
||||
uint32_t duration = now - pressStartTime;
|
||||
if (!buttonPressed) {
|
||||
// released -> if short press, send short, else already sent long
|
||||
if (duration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) {
|
||||
lastPressKeyTime = now;
|
||||
LOG_DEBUG("Rotary event Press short");
|
||||
e.inputEvent = this->_eventPressed;
|
||||
}
|
||||
pressDetected = false;
|
||||
pressStartTime = 0;
|
||||
lastPressLongEventTime = 0;
|
||||
this->action = ROTARY_ACTION_NONE;
|
||||
} else if (duration >= LONG_PRESS_DURATION && this->_eventPressedLong != INPUT_BROKER_NONE &&
|
||||
lastPressLongEventTime == 0) {
|
||||
// fire single-shot long press
|
||||
lastPressLongEventTime = now;
|
||||
LOG_DEBUG("Rotary event Press long");
|
||||
e.inputEvent = this->_eventPressedLong;
|
||||
}
|
||||
}
|
||||
} else if (this->action == ROTARY_ACTION_CW) {
|
||||
LOG_DEBUG("Rotary event CW");
|
||||
e.inputEvent = this->_eventCw;
|
||||
} else if (this->action == ROTARY_ACTION_CCW) {
|
||||
LOG_DEBUG("Rotary event CCW");
|
||||
e.inputEvent = this->_eventCcw;
|
||||
// Handle press long/short detection
|
||||
if (this->action == ROTARY_ACTION_PRESSED) {
|
||||
bool buttonPressed = !digitalRead(_pinPress);
|
||||
if (!pressDetected && buttonPressed) {
|
||||
pressDetected = true;
|
||||
pressStartTime = now;
|
||||
}
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
if (!pressDetected) {
|
||||
if (pressDetected) {
|
||||
uint32_t duration = now - pressStartTime;
|
||||
if (!buttonPressed) {
|
||||
// released -> if short press, send short, else already sent long
|
||||
if (duration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) {
|
||||
lastPressKeyTime = now;
|
||||
LOG_DEBUG("Rotary event Press short");
|
||||
e.inputEvent = this->_eventPressed;
|
||||
}
|
||||
pressDetected = false;
|
||||
pressStartTime = 0;
|
||||
lastPressLongEventTime = 0;
|
||||
this->action = ROTARY_ACTION_NONE;
|
||||
} else if (duration >= LONG_PRESS_DURATION && this->_eventPressedLong != INPUT_BROKER_NONE && lastPressLongEventTime == 0) {
|
||||
// fire single-shot long press
|
||||
lastPressLongEventTime = now;
|
||||
LOG_DEBUG("Rotary event Press long");
|
||||
e.inputEvent = this->_eventPressedLong;
|
||||
}
|
||||
}
|
||||
} else if (this->action == ROTARY_ACTION_CW) {
|
||||
LOG_DEBUG("Rotary event CW");
|
||||
e.inputEvent = this->_eventCw;
|
||||
} else if (this->action == ROTARY_ACTION_CCW) {
|
||||
LOG_DEBUG("Rotary event CCW");
|
||||
e.inputEvent = this->_eventCcw;
|
||||
}
|
||||
|
||||
return INT32_MAX;
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
if (!pressDetected) {
|
||||
this->action = ROTARY_ACTION_NONE;
|
||||
}
|
||||
|
||||
return INT32_MAX;
|
||||
}
|
||||
|
||||
void RotaryEncoderInterruptBase::intPressHandler()
|
||||
{
|
||||
this->action = ROTARY_ACTION_PRESSED;
|
||||
setIntervalFromNow(20); // start checking for long/short
|
||||
void RotaryEncoderInterruptBase::intPressHandler() {
|
||||
this->action = ROTARY_ACTION_PRESSED;
|
||||
setIntervalFromNow(20); // start checking for long/short
|
||||
}
|
||||
|
||||
void RotaryEncoderInterruptBase::intAHandler()
|
||||
{
|
||||
// CW rotation (at least on most common rotary encoders)
|
||||
int currentLevelA = digitalRead(this->_pinA);
|
||||
if (this->rotaryLevelA == currentLevelA) {
|
||||
return;
|
||||
}
|
||||
this->rotaryLevelA = currentLevelA;
|
||||
this->rotaryStateCCW = intHandler(currentLevelA == HIGH, this->rotaryLevelB, ROTARY_ACTION_CCW, this->rotaryStateCCW);
|
||||
void RotaryEncoderInterruptBase::intAHandler() {
|
||||
// CW rotation (at least on most common rotary encoders)
|
||||
int currentLevelA = digitalRead(this->_pinA);
|
||||
if (this->rotaryLevelA == currentLevelA) {
|
||||
return;
|
||||
}
|
||||
this->rotaryLevelA = currentLevelA;
|
||||
this->rotaryStateCCW = intHandler(currentLevelA == HIGH, this->rotaryLevelB, ROTARY_ACTION_CCW, this->rotaryStateCCW);
|
||||
}
|
||||
|
||||
void RotaryEncoderInterruptBase::intBHandler()
|
||||
{
|
||||
// CW rotation (at least on most common rotary encoders)
|
||||
int currentLevelB = digitalRead(this->_pinB);
|
||||
if (this->rotaryLevelB == currentLevelB) {
|
||||
return;
|
||||
}
|
||||
this->rotaryLevelB = currentLevelB;
|
||||
this->rotaryStateCW = intHandler(currentLevelB == HIGH, this->rotaryLevelA, ROTARY_ACTION_CW, this->rotaryStateCW);
|
||||
void RotaryEncoderInterruptBase::intBHandler() {
|
||||
// CW rotation (at least on most common rotary encoders)
|
||||
int currentLevelB = digitalRead(this->_pinB);
|
||||
if (this->rotaryLevelB == currentLevelB) {
|
||||
return;
|
||||
}
|
||||
this->rotaryLevelB = currentLevelB;
|
||||
this->rotaryStateCW = intHandler(currentLevelB == HIGH, this->rotaryLevelA, ROTARY_ACTION_CW, this->rotaryStateCW);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,21 +128,20 @@ void RotaryEncoderInterruptBase::intBHandler()
|
||||
*/
|
||||
RotaryEncoderInterruptBaseStateType RotaryEncoderInterruptBase::intHandler(bool actualPinRaising, int otherPinLevel,
|
||||
RotaryEncoderInterruptBaseActionType action,
|
||||
RotaryEncoderInterruptBaseStateType state)
|
||||
{
|
||||
RotaryEncoderInterruptBaseStateType newState = state;
|
||||
if (actualPinRaising && (otherPinLevel == LOW)) {
|
||||
if (state == ROTARY_EVENT_CLEARED) {
|
||||
newState = ROTARY_EVENT_OCCURRED;
|
||||
if ((this->action != ROTARY_ACTION_PRESSED) && (this->action != action)) {
|
||||
this->action = action;
|
||||
}
|
||||
}
|
||||
} else if (!actualPinRaising && (otherPinLevel == HIGH)) {
|
||||
// Logic to prevent bouncing.
|
||||
newState = ROTARY_EVENT_CLEARED;
|
||||
RotaryEncoderInterruptBaseStateType state) {
|
||||
RotaryEncoderInterruptBaseStateType newState = state;
|
||||
if (actualPinRaising && (otherPinLevel == LOW)) {
|
||||
if (state == ROTARY_EVENT_CLEARED) {
|
||||
newState = ROTARY_EVENT_OCCURRED;
|
||||
if ((this->action != ROTARY_ACTION_PRESSED) && (this->action != action)) {
|
||||
this->action = action;
|
||||
}
|
||||
}
|
||||
setIntervalFromNow(ROTARY_DELAY); // TODO: this modifies a non-volatile variable!
|
||||
} else if (!actualPinRaising && (otherPinLevel == HIGH)) {
|
||||
// Logic to prevent bouncing.
|
||||
newState = ROTARY_EVENT_CLEARED;
|
||||
}
|
||||
setIntervalFromNow(ROTARY_DELAY); // TODO: this modifies a non-volatile variable!
|
||||
|
||||
return newState;
|
||||
return newState;
|
||||
}
|
||||
|
||||
@@ -8,47 +8,46 @@ enum RotaryEncoderInterruptBaseStateType { ROTARY_EVENT_OCCURRED, ROTARY_EVENT_C
|
||||
|
||||
enum RotaryEncoderInterruptBaseActionType { ROTARY_ACTION_NONE, ROTARY_ACTION_PRESSED, ROTARY_ACTION_CW, ROTARY_ACTION_CCW };
|
||||
|
||||
class RotaryEncoderInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit RotaryEncoderInterruptBase(const char *name);
|
||||
void init(uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw,
|
||||
input_broker_event eventPressed, input_broker_event eventPressedLong,
|
||||
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress);
|
||||
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)());
|
||||
void intPressHandler();
|
||||
void intAHandler();
|
||||
void intBHandler();
|
||||
class RotaryEncoderInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
explicit RotaryEncoderInterruptBase(const char *name);
|
||||
void init(uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw, input_broker_event eventPressed,
|
||||
input_broker_event eventPressedLong,
|
||||
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)>
|
||||
// onIntPress);
|
||||
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)());
|
||||
void intPressHandler();
|
||||
void intAHandler();
|
||||
void intBHandler();
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
RotaryEncoderInterruptBaseStateType intHandler(bool actualPinRaising, int otherPinLevel,
|
||||
RotaryEncoderInterruptBaseActionType action,
|
||||
RotaryEncoderInterruptBaseStateType state);
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
RotaryEncoderInterruptBaseStateType intHandler(bool actualPinRaising, int otherPinLevel, RotaryEncoderInterruptBaseActionType action,
|
||||
RotaryEncoderInterruptBaseStateType state);
|
||||
|
||||
volatile RotaryEncoderInterruptBaseStateType rotaryStateCW = ROTARY_EVENT_CLEARED;
|
||||
volatile RotaryEncoderInterruptBaseStateType rotaryStateCCW = ROTARY_EVENT_CLEARED;
|
||||
volatile int rotaryLevelA = LOW;
|
||||
volatile int rotaryLevelB = LOW;
|
||||
volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE;
|
||||
volatile RotaryEncoderInterruptBaseStateType rotaryStateCW = ROTARY_EVENT_CLEARED;
|
||||
volatile RotaryEncoderInterruptBaseStateType rotaryStateCCW = ROTARY_EVENT_CLEARED;
|
||||
volatile int rotaryLevelA = LOW;
|
||||
volatile int rotaryLevelB = LOW;
|
||||
volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE;
|
||||
|
||||
private:
|
||||
// pins and events
|
||||
uint8_t _pinA = 0;
|
||||
uint8_t _pinB = 0;
|
||||
uint8_t _pinPress = 0;
|
||||
input_broker_event _eventCw = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventCcw = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressed = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
||||
const char *_originName;
|
||||
private:
|
||||
// pins and events
|
||||
uint8_t _pinA = 0;
|
||||
uint8_t _pinB = 0;
|
||||
uint8_t _pinPress = 0;
|
||||
input_broker_event _eventCw = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventCcw = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressed = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
||||
const char *_originName;
|
||||
|
||||
// Long press detection variables
|
||||
uint32_t pressStartTime = 0;
|
||||
bool pressDetected = false;
|
||||
uint32_t lastPressLongEventTime = 0;
|
||||
unsigned long lastPressKeyTime = 0;
|
||||
static const uint32_t LONG_PRESS_DURATION = 300; // ms
|
||||
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 0; // 0 = single-shot for rotary select
|
||||
const unsigned long pressDebounceMs = 200; // ms
|
||||
// Long press detection variables
|
||||
uint32_t pressStartTime = 0;
|
||||
bool pressDetected = false;
|
||||
uint32_t lastPressLongEventTime = 0;
|
||||
unsigned long lastPressKeyTime = 0;
|
||||
static const uint32_t LONG_PRESS_DURATION = 300; // ms
|
||||
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 0; // 0 = single-shot for rotary select
|
||||
const unsigned long pressDebounceMs = 200; // ms
|
||||
};
|
||||
|
||||
@@ -6,42 +6,31 @@ RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
|
||||
|
||||
RotaryEncoderInterruptImpl1::RotaryEncoderInterruptImpl1() : RotaryEncoderInterruptBase("rotEnc1") {}
|
||||
|
||||
bool RotaryEncoderInterruptImpl1::init()
|
||||
{
|
||||
if (!moduleConfig.canned_message.rotary1_enabled) {
|
||||
// Input device is disabled.
|
||||
disable();
|
||||
return false;
|
||||
}
|
||||
bool RotaryEncoderInterruptImpl1::init() {
|
||||
if (!moduleConfig.canned_message.rotary1_enabled) {
|
||||
// Input device is disabled.
|
||||
disable();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t pinA = moduleConfig.canned_message.inputbroker_pin_a;
|
||||
uint8_t pinB = moduleConfig.canned_message.inputbroker_pin_b;
|
||||
uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press;
|
||||
input_broker_event eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
|
||||
input_broker_event eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
|
||||
input_broker_event eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
|
||||
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
|
||||
uint8_t pinA = moduleConfig.canned_message.inputbroker_pin_a;
|
||||
uint8_t pinB = moduleConfig.canned_message.inputbroker_pin_b;
|
||||
uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press;
|
||||
input_broker_event eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
|
||||
input_broker_event eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
|
||||
input_broker_event eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
|
||||
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
|
||||
|
||||
// moduleConfig.canned_message.ext_notification_module_output
|
||||
RotaryEncoderInterruptBase::init(pinA, pinB, pinPress, eventCw, eventCcw, eventPressed, eventPressedLong,
|
||||
RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB,
|
||||
RotaryEncoderInterruptImpl1::handleIntPressed);
|
||||
inputBroker->registerSource(this);
|
||||
// moduleConfig.canned_message.ext_notification_module_output
|
||||
RotaryEncoderInterruptBase::init(pinA, pinB, pinPress, eventCw, eventCcw, eventPressed, eventPressedLong, RotaryEncoderInterruptImpl1::handleIntA,
|
||||
RotaryEncoderInterruptImpl1::handleIntB, RotaryEncoderInterruptImpl1::handleIntPressed);
|
||||
inputBroker->registerSource(this);
|
||||
#ifndef HAS_PHYSICAL_KEYBOARD
|
||||
osk_found = true;
|
||||
osk_found = true;
|
||||
#endif
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RotaryEncoderInterruptImpl1::handleIntA()
|
||||
{
|
||||
rotaryEncoderInterruptImpl1->intAHandler();
|
||||
}
|
||||
void RotaryEncoderInterruptImpl1::handleIntB()
|
||||
{
|
||||
rotaryEncoderInterruptImpl1->intBHandler();
|
||||
}
|
||||
void RotaryEncoderInterruptImpl1::handleIntPressed()
|
||||
{
|
||||
rotaryEncoderInterruptImpl1->intPressHandler();
|
||||
}
|
||||
void RotaryEncoderInterruptImpl1::handleIntA() { rotaryEncoderInterruptImpl1->intAHandler(); }
|
||||
void RotaryEncoderInterruptImpl1::handleIntB() { rotaryEncoderInterruptImpl1->intBHandler(); }
|
||||
void RotaryEncoderInterruptImpl1::handleIntPressed() { rotaryEncoderInterruptImpl1->intPressHandler(); }
|
||||
@@ -8,14 +8,13 @@
|
||||
* to your device as you wish, but you always need to have separate event
|
||||
* handlers, thus you need to have a RotaryEncoderInterrupt implementation.
|
||||
*/
|
||||
class RotaryEncoderInterruptImpl1 : public RotaryEncoderInterruptBase
|
||||
{
|
||||
public:
|
||||
RotaryEncoderInterruptImpl1();
|
||||
bool init();
|
||||
static void handleIntA();
|
||||
static void handleIntB();
|
||||
static void handleIntPressed();
|
||||
class RotaryEncoderInterruptImpl1 : public RotaryEncoderInterruptBase {
|
||||
public:
|
||||
RotaryEncoderInterruptImpl1();
|
||||
bool init();
|
||||
static void handleIntA();
|
||||
static void handleIntB();
|
||||
static void handleIntPressed();
|
||||
};
|
||||
|
||||
extern RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
|
||||
@@ -6,78 +6,73 @@ using namespace concurrency;
|
||||
|
||||
SeesawRotary *seesawRotary;
|
||||
|
||||
SeesawRotary::SeesawRotary(const char *name) : OSThread(name)
|
||||
{
|
||||
_originName = name;
|
||||
SeesawRotary::SeesawRotary(const char *name) : OSThread(name) { _originName = name; }
|
||||
|
||||
bool SeesawRotary::init() {
|
||||
if (inputBroker)
|
||||
inputBroker->registerSource(this);
|
||||
|
||||
if (!ss.begin(SEESAW_ADDR)) {
|
||||
return false;
|
||||
}
|
||||
// attachButtonInterrupts();
|
||||
|
||||
uint32_t version = ((ss.getVersion() >> 16) & 0xFFFF);
|
||||
if (version != 4991) {
|
||||
LOG_WARN("Wrong firmware loaded? %u", version);
|
||||
} else {
|
||||
LOG_INFO("Found Product 4991");
|
||||
}
|
||||
/*
|
||||
#ifdef ARCH_ESP32
|
||||
// Register callbacks for before and after lightsleep
|
||||
// Used to detach and reattach interrupts
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
#endif
|
||||
*/
|
||||
ss.pinMode(SS_SWITCH, INPUT_PULLUP);
|
||||
|
||||
// get starting position
|
||||
encoder_position = ss.getEncoderPosition();
|
||||
|
||||
ss.setGPIOInterrupts((uint32_t)1 << SS_SWITCH, 1);
|
||||
ss.enableEncoderInterrupt();
|
||||
canSleep = true; // Assume we should not keep the board awake
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SeesawRotary::init()
|
||||
{
|
||||
if (inputBroker)
|
||||
inputBroker->registerSource(this);
|
||||
int32_t SeesawRotary::runOnce() {
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
bool currentlyPressed = !ss.digitalRead(SS_SWITCH);
|
||||
|
||||
if (!ss.begin(SEESAW_ADDR)) {
|
||||
return false;
|
||||
}
|
||||
// attachButtonInterrupts();
|
||||
if (currentlyPressed && !wasPressed) {
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
}
|
||||
wasPressed = currentlyPressed;
|
||||
|
||||
uint32_t version = ((ss.getVersion() >> 16) & 0xFFFF);
|
||||
if (version != 4991) {
|
||||
LOG_WARN("Wrong firmware loaded? %u", version);
|
||||
int32_t new_position = ss.getEncoderPosition();
|
||||
// did we move arounde?
|
||||
if (encoder_position != new_position) {
|
||||
if (encoder_position == 0 && new_position != 1) {
|
||||
e.inputEvent = INPUT_BROKER_ALT_PRESS;
|
||||
} else if (new_position == 0 && encoder_position != 1) {
|
||||
e.inputEvent = INPUT_BROKER_USER_PRESS;
|
||||
} else if (new_position > encoder_position) {
|
||||
e.inputEvent = INPUT_BROKER_USER_PRESS;
|
||||
} else {
|
||||
LOG_INFO("Found Product 4991");
|
||||
e.inputEvent = INPUT_BROKER_ALT_PRESS;
|
||||
}
|
||||
/*
|
||||
#ifdef ARCH_ESP32
|
||||
// Register callbacks for before and after lightsleep
|
||||
// Used to detach and reattach interrupts
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
#endif
|
||||
*/
|
||||
ss.pinMode(SS_SWITCH, INPUT_PULLUP);
|
||||
encoder_position = new_position;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
e.source = this->_originName;
|
||||
e.kbchar = 0x00;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
// get starting position
|
||||
encoder_position = ss.getEncoderPosition();
|
||||
|
||||
ss.setGPIOInterrupts((uint32_t)1 << SS_SWITCH, 1);
|
||||
ss.enableEncoderInterrupt();
|
||||
canSleep = true; // Assume we should not keep the board awake
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t SeesawRotary::runOnce()
|
||||
{
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
bool currentlyPressed = !ss.digitalRead(SS_SWITCH);
|
||||
|
||||
if (currentlyPressed && !wasPressed) {
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
}
|
||||
wasPressed = currentlyPressed;
|
||||
|
||||
int32_t new_position = ss.getEncoderPosition();
|
||||
// did we move arounde?
|
||||
if (encoder_position != new_position) {
|
||||
if (encoder_position == 0 && new_position != 1) {
|
||||
e.inputEvent = INPUT_BROKER_ALT_PRESS;
|
||||
} else if (new_position == 0 && encoder_position != 1) {
|
||||
e.inputEvent = INPUT_BROKER_USER_PRESS;
|
||||
} else if (new_position > encoder_position) {
|
||||
e.inputEvent = INPUT_BROKER_USER_PRESS;
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ALT_PRESS;
|
||||
}
|
||||
encoder_position = new_position;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
e.source = this->_originName;
|
||||
e.kbchar = 0x00;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
return 50;
|
||||
return 50;
|
||||
}
|
||||
#endif
|
||||
@@ -11,18 +11,17 @@
|
||||
|
||||
#define SEESAW_ADDR 0x36
|
||||
|
||||
class SeesawRotary : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
const char *_originName;
|
||||
bool init();
|
||||
SeesawRotary(const char *name);
|
||||
int32_t runOnce() override;
|
||||
class SeesawRotary : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
const char *_originName;
|
||||
bool init();
|
||||
SeesawRotary(const char *name);
|
||||
int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
Adafruit_seesaw ss;
|
||||
int32_t encoder_position;
|
||||
bool wasPressed = false;
|
||||
private:
|
||||
Adafruit_seesaw ss;
|
||||
int32_t encoder_position;
|
||||
bool wasPressed = false;
|
||||
};
|
||||
|
||||
extern SeesawRotary *seesawRotary;
|
||||
|
||||
@@ -24,164 +24,161 @@ unsigned char KeyMap[3][4][10] = {{{'.', 'a', 'd', 'g', 'j', 'm', 'p', 't', 'w',
|
||||
|
||||
#endif
|
||||
|
||||
SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name)
|
||||
{
|
||||
this->_originName = name;
|
||||
SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name) {
|
||||
this->_originName = name;
|
||||
|
||||
globalSerialKeyboard = this;
|
||||
globalSerialKeyboard = this;
|
||||
}
|
||||
|
||||
void SerialKeyboard::erase()
|
||||
{
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0x08;
|
||||
e.source = this->_originName;
|
||||
this->notifyObservers(&e);
|
||||
void SerialKeyboard::erase() {
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0x08;
|
||||
e.source = this->_originName;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
int32_t SerialKeyboard::runOnce()
|
||||
{
|
||||
if (!INPUTBROKER_SERIAL_TYPE) {
|
||||
// Input device is not requested.
|
||||
return disable();
|
||||
int32_t SerialKeyboard::runOnce() {
|
||||
if (!INPUTBROKER_SERIAL_TYPE) {
|
||||
// Input device is not requested.
|
||||
return disable();
|
||||
}
|
||||
|
||||
if (firstTime) {
|
||||
// This is the first time the OSThread library has called this function, so do port setup
|
||||
firstTime = 0;
|
||||
pinMode(KB_LOAD, OUTPUT);
|
||||
pinMode(KB_CLK, OUTPUT);
|
||||
pinMode(KB_DATA, INPUT);
|
||||
digitalWrite(KB_LOAD, HIGH);
|
||||
digitalWrite(KB_CLK, LOW);
|
||||
prevKeys = 0b1111111111111111;
|
||||
LOG_DEBUG("Serial Keyboard setup");
|
||||
}
|
||||
|
||||
if (INPUTBROKER_SERIAL_TYPE == 1) { // Chatter V1.0 & V2.0 keypads
|
||||
// scan for keypresses
|
||||
// Write pulse to load pin
|
||||
digitalWrite(KB_LOAD, LOW);
|
||||
delayMicroseconds(5);
|
||||
digitalWrite(KB_LOAD, HIGH);
|
||||
delayMicroseconds(5);
|
||||
|
||||
// Get data from 74HC165
|
||||
byte shiftRegister1 = shiftIn(KB_DATA, KB_CLK, LSBFIRST);
|
||||
byte shiftRegister2 = shiftIn(KB_DATA, KB_CLK, LSBFIRST);
|
||||
|
||||
keys = (shiftRegister1 << 8) + shiftRegister2;
|
||||
|
||||
// Print to serial monitor
|
||||
// Serial.print (shiftRegister1, BIN);
|
||||
// Serial.print ("X");
|
||||
// Serial.println (shiftRegister2, BIN);
|
||||
|
||||
if (!Throttle::isWithinTimespanMs(lastPressTime, 500)) {
|
||||
quickPress = 0;
|
||||
}
|
||||
|
||||
if (firstTime) {
|
||||
// This is the first time the OSThread library has called this function, so do port setup
|
||||
firstTime = 0;
|
||||
pinMode(KB_LOAD, OUTPUT);
|
||||
pinMode(KB_CLK, OUTPUT);
|
||||
pinMode(KB_DATA, INPUT);
|
||||
digitalWrite(KB_LOAD, HIGH);
|
||||
digitalWrite(KB_CLK, LOW);
|
||||
prevKeys = 0b1111111111111111;
|
||||
LOG_DEBUG("Serial Keyboard setup");
|
||||
}
|
||||
if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once
|
||||
// but shouldn't be a limitation
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
// SELECT OR SEND OR CANCEL EVENT
|
||||
if (!(shiftRegister2 & (1 << 3))) {
|
||||
if (shift > 0) {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED
|
||||
e.kbchar = 0x09; // TAB
|
||||
shift = 0; // reset shift after TAB
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
}
|
||||
} else if (!(shiftRegister2 & (1 << 2))) {
|
||||
if (shift > 0) {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED
|
||||
e.kbchar = 0x09; // TAB
|
||||
shift = 0; // reset shift after TAB
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
}
|
||||
e.kbchar = 0;
|
||||
} else if (!(shiftRegister2 & (1 << 1))) {
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
} else if (!(shiftRegister2 & (1 << 0))) {
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
}
|
||||
|
||||
if (INPUTBROKER_SERIAL_TYPE == 1) { // Chatter V1.0 & V2.0 keypads
|
||||
// scan for keypresses
|
||||
// Write pulse to load pin
|
||||
digitalWrite(KB_LOAD, LOW);
|
||||
delayMicroseconds(5);
|
||||
digitalWrite(KB_LOAD, HIGH);
|
||||
delayMicroseconds(5);
|
||||
// TEXT INPUT EVENT
|
||||
else if (!(shiftRegister1 & (1 << 4))) {
|
||||
keyPressed = 0;
|
||||
} else if (!(shiftRegister1 & (1 << 3))) {
|
||||
keyPressed = 1;
|
||||
} else if (!(shiftRegister2 & (1 << 4))) {
|
||||
keyPressed = 2;
|
||||
} else if (!(shiftRegister1 & (1 << 5))) {
|
||||
keyPressed = 3;
|
||||
} else if (!(shiftRegister1 & (1 << 2))) {
|
||||
keyPressed = 4;
|
||||
} else if (!(shiftRegister2 & (1 << 5))) {
|
||||
keyPressed = 5;
|
||||
} else if (!(shiftRegister1 & (1 << 6))) {
|
||||
keyPressed = 6;
|
||||
} else if (!(shiftRegister1 & (1 << 1))) {
|
||||
keyPressed = 7;
|
||||
} else if (!(shiftRegister2 & (1 << 6))) {
|
||||
keyPressed = 8;
|
||||
} else if (!(shiftRegister1 & (1 << 0))) {
|
||||
keyPressed = 9;
|
||||
}
|
||||
// BACKSPACE or TAB
|
||||
else if (!(shiftRegister1 & (1 << 7))) {
|
||||
if (shift == 0 || shift == 2) { // BACKSPACE
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0x08;
|
||||
} else { // shift = 1 => TAB
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x09;
|
||||
}
|
||||
}
|
||||
// SHIFT
|
||||
else if (!(shiftRegister2 & (1 << 7))) {
|
||||
keyPressed = 10;
|
||||
}
|
||||
|
||||
// Get data from 74HC165
|
||||
byte shiftRegister1 = shiftIn(KB_DATA, KB_CLK, LSBFIRST);
|
||||
byte shiftRegister2 = shiftIn(KB_DATA, KB_CLK, LSBFIRST);
|
||||
|
||||
keys = (shiftRegister1 << 8) + shiftRegister2;
|
||||
|
||||
// Print to serial monitor
|
||||
// Serial.print (shiftRegister1, BIN);
|
||||
// Serial.print ("X");
|
||||
// Serial.println (shiftRegister2, BIN);
|
||||
|
||||
if (!Throttle::isWithinTimespanMs(lastPressTime, 500)) {
|
||||
if (keyPressed < 11) {
|
||||
if (keyPressed == lastKeyPressed && millis() - lastPressTime < 500) {
|
||||
quickPress += 1;
|
||||
if (quickPress > 3) {
|
||||
quickPress = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but
|
||||
// shouldn't be a limitation
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
// SELECT OR SEND OR CANCEL EVENT
|
||||
if (!(shiftRegister2 & (1 << 3))) {
|
||||
if (shift > 0) {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED
|
||||
e.kbchar = 0x09; // TAB
|
||||
shift = 0; // reset shift after TAB
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
}
|
||||
} else if (!(shiftRegister2 & (1 << 2))) {
|
||||
if (shift > 0) {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED
|
||||
e.kbchar = 0x09; // TAB
|
||||
shift = 0; // reset shift after TAB
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
}
|
||||
e.kbchar = 0;
|
||||
} else if (!(shiftRegister2 & (1 << 1))) {
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
} else if (!(shiftRegister2 & (1 << 0))) {
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
}
|
||||
|
||||
// TEXT INPUT EVENT
|
||||
else if (!(shiftRegister1 & (1 << 4))) {
|
||||
keyPressed = 0;
|
||||
} else if (!(shiftRegister1 & (1 << 3))) {
|
||||
keyPressed = 1;
|
||||
} else if (!(shiftRegister2 & (1 << 4))) {
|
||||
keyPressed = 2;
|
||||
} else if (!(shiftRegister1 & (1 << 5))) {
|
||||
keyPressed = 3;
|
||||
} else if (!(shiftRegister1 & (1 << 2))) {
|
||||
keyPressed = 4;
|
||||
} else if (!(shiftRegister2 & (1 << 5))) {
|
||||
keyPressed = 5;
|
||||
} else if (!(shiftRegister1 & (1 << 6))) {
|
||||
keyPressed = 6;
|
||||
} else if (!(shiftRegister1 & (1 << 1))) {
|
||||
keyPressed = 7;
|
||||
} else if (!(shiftRegister2 & (1 << 6))) {
|
||||
keyPressed = 8;
|
||||
} else if (!(shiftRegister1 & (1 << 0))) {
|
||||
keyPressed = 9;
|
||||
}
|
||||
// BACKSPACE or TAB
|
||||
else if (!(shiftRegister1 & (1 << 7))) {
|
||||
if (shift == 0 || shift == 2) { // BACKSPACE
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0x08;
|
||||
} else { // shift = 1 => TAB
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x09;
|
||||
}
|
||||
}
|
||||
// SHIFT
|
||||
else if (!(shiftRegister2 & (1 << 7))) {
|
||||
keyPressed = 10;
|
||||
}
|
||||
|
||||
if (keyPressed < 11) {
|
||||
if (keyPressed == lastKeyPressed && millis() - lastPressTime < 500) {
|
||||
quickPress += 1;
|
||||
if (quickPress > 3) {
|
||||
quickPress = 0;
|
||||
}
|
||||
}
|
||||
if (keyPressed != lastKeyPressed) {
|
||||
quickPress = 0;
|
||||
}
|
||||
if (keyPressed < 10) { // if it's a letter
|
||||
if (keyPressed == lastKeyPressed && millis() - lastPressTime < 500) {
|
||||
erase();
|
||||
}
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = char(KeyMap[shift][quickPress][keyPressed]);
|
||||
} else { // then it's shift
|
||||
shift += 1;
|
||||
if (shift > 2) {
|
||||
shift = 0;
|
||||
}
|
||||
}
|
||||
lastPressTime = millis();
|
||||
lastKeyPressed = keyPressed;
|
||||
keyPressed = 13;
|
||||
}
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
if (keyPressed != lastKeyPressed) {
|
||||
quickPress = 0;
|
||||
}
|
||||
prevKeys = keys;
|
||||
if (keyPressed < 10) { // if it's a letter
|
||||
if (keyPressed == lastKeyPressed && millis() - lastPressTime < 500) {
|
||||
erase();
|
||||
}
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = char(KeyMap[shift][quickPress][keyPressed]);
|
||||
} else { // then it's shift
|
||||
shift += 1;
|
||||
if (shift > 2) {
|
||||
shift = 0;
|
||||
}
|
||||
}
|
||||
lastPressTime = millis();
|
||||
lastKeyPressed = keyPressed;
|
||||
keyPressed = 13;
|
||||
}
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
return 50;
|
||||
prevKeys = keys;
|
||||
}
|
||||
return 50;
|
||||
}
|
||||
|
||||
#endif // INPUTBROKER_SERIAL_TYPE
|
||||
@@ -3,27 +3,26 @@
|
||||
#include "InputBroker.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
class SerialKeyboard : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit SerialKeyboard(const char *name);
|
||||
class SerialKeyboard : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
explicit SerialKeyboard(const char *name);
|
||||
|
||||
uint8_t getShift() const { return shift; }
|
||||
uint8_t getShift() const { return shift; }
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
void erase();
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
void erase();
|
||||
|
||||
private:
|
||||
const char *_originName;
|
||||
bool firstTime = 1;
|
||||
int prevKeys = 0;
|
||||
int keys = 0;
|
||||
int shift = 0;
|
||||
int keyPressed = 13;
|
||||
int lastKeyPressed = 13;
|
||||
int quickPress = 0;
|
||||
unsigned long lastPressTime = 0;
|
||||
private:
|
||||
const char *_originName;
|
||||
bool firstTime = 1;
|
||||
int prevKeys = 0;
|
||||
int keys = 0;
|
||||
int shift = 0;
|
||||
int keyPressed = 13;
|
||||
int lastKeyPressed = 13;
|
||||
int quickPress = 0;
|
||||
unsigned long lastPressTime = 0;
|
||||
};
|
||||
|
||||
extern SerialKeyboard *globalSerialKeyboard;
|
||||
@@ -8,14 +8,13 @@ SerialKeyboardImpl *aSerialKeyboardImpl;
|
||||
|
||||
SerialKeyboardImpl::SerialKeyboardImpl() : SerialKeyboard("serialKB") {}
|
||||
|
||||
void SerialKeyboardImpl::init()
|
||||
{
|
||||
if (!INPUTBROKER_SERIAL_TYPE) {
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
void SerialKeyboardImpl::init() {
|
||||
if (!INPUTBROKER_SERIAL_TYPE) {
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
|
||||
inputBroker->registerSource(this);
|
||||
inputBroker->registerSource(this);
|
||||
}
|
||||
|
||||
#endif // INPUTBROKER_SERIAL_TYPE
|
||||
@@ -9,11 +9,10 @@
|
||||
* to your device as you wish, but you always need to have separate event
|
||||
* handlers, thus you need to have a RotaryEncoderInterrupt implementation.
|
||||
*/
|
||||
class SerialKeyboardImpl : public SerialKeyboard
|
||||
{
|
||||
public:
|
||||
SerialKeyboardImpl();
|
||||
void init();
|
||||
class SerialKeyboardImpl : public SerialKeyboard {
|
||||
public:
|
||||
SerialKeyboardImpl();
|
||||
void init();
|
||||
};
|
||||
|
||||
extern SerialKeyboardImpl *aSerialKeyboardImpl;
|
||||
@@ -44,94 +44,88 @@ static unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = {
|
||||
|
||||
TCA8418Keyboard::TCA8418Keyboard()
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(-1), next_key(-1), last_tap(0L), char_idx(0), tap_interval(0),
|
||||
should_backspace(false)
|
||||
{
|
||||
should_backspace(false) {}
|
||||
|
||||
void TCA8418Keyboard::reset() {
|
||||
TCA8418KeyboardBase::reset();
|
||||
|
||||
// Set COL9 as GPIO output
|
||||
writeRegister(TCA8418_REG_GPIO_DIR_3, 0x02);
|
||||
// Switch off keyboard backlight (COL9 = LOW)
|
||||
writeRegister(TCA8418_REG_GPIO_DAT_OUT_3, 0x00);
|
||||
}
|
||||
|
||||
void TCA8418Keyboard::reset()
|
||||
{
|
||||
TCA8418KeyboardBase::reset();
|
||||
void TCA8418Keyboard::pressed(uint8_t key) {
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
|
||||
// Set COL9 as GPIO output
|
||||
writeRegister(TCA8418_REG_GPIO_DIR_3, 0x02);
|
||||
// Switch off keyboard backlight (COL9 = LOW)
|
||||
writeRegister(TCA8418_REG_GPIO_DAT_OUT_3, 0x00);
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
return; // Invalid key
|
||||
}
|
||||
|
||||
// Compute key index based on dynamic row/column
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
|
||||
// LOG_DEBUG("TCA8418: Key %u -> Next Key %u", key, next_key);
|
||||
|
||||
state = Held;
|
||||
uint32_t now = millis();
|
||||
tap_interval = now - last_tap;
|
||||
if (tap_interval < 0) {
|
||||
// Long running, millis has overflowed.
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the key is the same as the last one or if the time interval has passed
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0; // Reset char index if new key or long press
|
||||
should_backspace = false; // dont backspace on new key
|
||||
} else {
|
||||
char_idx += 1; // Cycle through characters if same key pressed
|
||||
should_backspace = true; // allow backspace on same key
|
||||
}
|
||||
|
||||
// Store the current key as the last key
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
}
|
||||
|
||||
void TCA8418Keyboard::pressed(uint8_t key)
|
||||
{
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
void TCA8418Keyboard::released() {
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
return; // Invalid key
|
||||
}
|
||||
|
||||
// Compute key index based on dynamic row/column
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
|
||||
// LOG_DEBUG("TCA8418: Key %u -> Next Key %u", key, next_key);
|
||||
|
||||
state = Held;
|
||||
uint32_t now = millis();
|
||||
tap_interval = now - last_tap;
|
||||
if (tap_interval < 0) {
|
||||
// Long running, millis has overflowed.
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the key is the same as the last one or if the time interval has passed
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0; // Reset char index if new key or long press
|
||||
should_backspace = false; // dont backspace on new key
|
||||
} else {
|
||||
char_idx += 1; // Cycle through characters if same key pressed
|
||||
should_backspace = true; // allow backspace on same key
|
||||
}
|
||||
|
||||
// Store the current key as the last key
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
if (last_key < 0 || last_key > _TCA8418_NUM_KEYS) { // reset to idle if last_key out of bounds
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
uint32_t now = millis();
|
||||
int32_t held_interval = now - last_tap;
|
||||
last_tap = now;
|
||||
if (tap_interval < _TCA8418_MULTI_TAP_THRESHOLD && should_backspace) {
|
||||
queueEvent(BSP);
|
||||
}
|
||||
if (held_interval > _TCA8418_LONG_PRESS_THRESHOLD) {
|
||||
queueEvent(TCA8418LongPressMap[last_key]);
|
||||
// LOG_DEBUG("Long Press Key: %i Map: %i", last_key, TCA8418LongPressMap[last_key]);
|
||||
} else {
|
||||
queueEvent(TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])]);
|
||||
// LOG_DEBUG("Key Press: %i Index:%i if %i Map: %c", last_key, char_idx, TCA8418TapMod[last_key],
|
||||
// TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])]);
|
||||
}
|
||||
}
|
||||
|
||||
void TCA8418Keyboard::released()
|
||||
{
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key < 0 || last_key > _TCA8418_NUM_KEYS) { // reset to idle if last_key out of bounds
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
uint32_t now = millis();
|
||||
int32_t held_interval = now - last_tap;
|
||||
last_tap = now;
|
||||
if (tap_interval < _TCA8418_MULTI_TAP_THRESHOLD && should_backspace) {
|
||||
queueEvent(BSP);
|
||||
}
|
||||
if (held_interval > _TCA8418_LONG_PRESS_THRESHOLD) {
|
||||
queueEvent(TCA8418LongPressMap[last_key]);
|
||||
// LOG_DEBUG("Long Press Key: %i Map: %i", last_key, TCA8418LongPressMap[last_key]);
|
||||
} else {
|
||||
queueEvent(TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])]);
|
||||
// LOG_DEBUG("Key Press: %i Index:%i if %i Map: %c", last_key, char_idx, TCA8418TapMod[last_key],
|
||||
// TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])]);
|
||||
}
|
||||
}
|
||||
|
||||
void TCA8418Keyboard::setBacklight(bool on)
|
||||
{
|
||||
if (on) {
|
||||
digitalWrite(TCA8418_COL9, HIGH);
|
||||
} else {
|
||||
digitalWrite(TCA8418_COL9, LOW);
|
||||
}
|
||||
void TCA8418Keyboard::setBacklight(bool on) {
|
||||
if (on) {
|
||||
digitalWrite(TCA8418_COL9, HIGH);
|
||||
} else {
|
||||
digitalWrite(TCA8418_COL9, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,20 @@
|
||||
/**
|
||||
* @brief 3x4 keypad with 3 columns and 4 rows
|
||||
*/
|
||||
class TCA8418Keyboard : public TCA8418KeyboardBase
|
||||
{
|
||||
public:
|
||||
TCA8418Keyboard();
|
||||
void reset(void) override;
|
||||
void setBacklight(bool on) override;
|
||||
class TCA8418Keyboard : public TCA8418KeyboardBase {
|
||||
public:
|
||||
TCA8418Keyboard();
|
||||
void reset(void) override;
|
||||
void setBacklight(bool on) override;
|
||||
|
||||
protected:
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
protected:
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
bool should_backspace;
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
bool should_backspace;
|
||||
};
|
||||
|
||||
@@ -32,346 +32,314 @@
|
||||
#define _TCA8418_REG_LCK_EC_KLEC_0 0x01 // Key event count bit 0
|
||||
|
||||
TCA8418KeyboardBase::TCA8418KeyboardBase(uint8_t rows, uint8_t columns)
|
||||
: rows(rows), columns(columns), state(Init), queue(""), m_wire(nullptr), m_addr(0), readCallback(nullptr),
|
||||
writeCallback(nullptr)
|
||||
{
|
||||
: rows(rows), columns(columns), state(Init), queue(""), m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr) {}
|
||||
|
||||
void TCA8418KeyboardBase::begin(uint8_t addr, TwoWire *wire) {
|
||||
m_addr = addr;
|
||||
m_wire = wire;
|
||||
m_wire->begin();
|
||||
reset();
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::begin(uint8_t addr, TwoWire *wire)
|
||||
{
|
||||
m_addr = addr;
|
||||
m_wire = wire;
|
||||
m_wire->begin();
|
||||
reset();
|
||||
void TCA8418KeyboardBase::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 TCA8418KeyboardBase::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 TCA8418KeyboardBase::reset() {
|
||||
LOG_DEBUG("TCA8418 Reset");
|
||||
// GPIO
|
||||
// set default all GIO pins to INPUT
|
||||
writeRegister(TCA8418_REG_GPIO_DIR_1, 0x00);
|
||||
writeRegister(TCA8418_REG_GPIO_DIR_2, 0x00);
|
||||
writeRegister(TCA8418_REG_GPIO_DIR_3, 0x00);
|
||||
|
||||
// add all pins to key events
|
||||
writeRegister(TCA8418_REG_GPI_EM_1, 0xFF);
|
||||
writeRegister(TCA8418_REG_GPI_EM_2, 0xFF);
|
||||
writeRegister(TCA8418_REG_GPI_EM_3, 0xFF);
|
||||
|
||||
// set all pins to FALLING interrupts
|
||||
writeRegister(TCA8418_REG_GPIO_INT_LVL_1, 0x00);
|
||||
writeRegister(TCA8418_REG_GPIO_INT_LVL_2, 0x00);
|
||||
writeRegister(TCA8418_REG_GPIO_INT_LVL_3, 0x00);
|
||||
|
||||
// add all pins to interrupts
|
||||
writeRegister(TCA8418_REG_GPIO_INT_EN_1, 0xFF);
|
||||
writeRegister(TCA8418_REG_GPIO_INT_EN_2, 0xFF);
|
||||
writeRegister(TCA8418_REG_GPIO_INT_EN_3, 0xFF);
|
||||
|
||||
// Set keyboard matrix size
|
||||
matrix(rows, columns);
|
||||
enableDebounce();
|
||||
flush();
|
||||
state = Idle;
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::reset()
|
||||
{
|
||||
LOG_DEBUG("TCA8418 Reset");
|
||||
// GPIO
|
||||
// set default all GIO pins to INPUT
|
||||
writeRegister(TCA8418_REG_GPIO_DIR_1, 0x00);
|
||||
writeRegister(TCA8418_REG_GPIO_DIR_2, 0x00);
|
||||
writeRegister(TCA8418_REG_GPIO_DIR_3, 0x00);
|
||||
bool TCA8418KeyboardBase::matrix(uint8_t rows, uint8_t columns) {
|
||||
if (rows < 1 || rows > 8 || columns < 1 || columns > 10)
|
||||
return false;
|
||||
|
||||
// add all pins to key events
|
||||
writeRegister(TCA8418_REG_GPI_EM_1, 0xFF);
|
||||
writeRegister(TCA8418_REG_GPI_EM_2, 0xFF);
|
||||
writeRegister(TCA8418_REG_GPI_EM_3, 0xFF);
|
||||
// Setup the keypad matrix.
|
||||
uint8_t mask = 0x00;
|
||||
for (int r = 0; r < rows; r++) {
|
||||
mask <<= 1;
|
||||
mask |= 1;
|
||||
}
|
||||
writeRegister(TCA8418_REG_KP_GPIO_1, mask);
|
||||
|
||||
// set all pins to FALLING interrupts
|
||||
writeRegister(TCA8418_REG_GPIO_INT_LVL_1, 0x00);
|
||||
writeRegister(TCA8418_REG_GPIO_INT_LVL_2, 0x00);
|
||||
writeRegister(TCA8418_REG_GPIO_INT_LVL_3, 0x00);
|
||||
mask = 0x00;
|
||||
for (int c = 0; c < columns && c < 8; c++) {
|
||||
mask <<= 1;
|
||||
mask |= 1;
|
||||
}
|
||||
writeRegister(TCA8418_REG_KP_GPIO_2, mask);
|
||||
|
||||
// add all pins to interrupts
|
||||
writeRegister(TCA8418_REG_GPIO_INT_EN_1, 0xFF);
|
||||
writeRegister(TCA8418_REG_GPIO_INT_EN_2, 0xFF);
|
||||
writeRegister(TCA8418_REG_GPIO_INT_EN_3, 0xFF);
|
||||
if (columns > 8) {
|
||||
if (columns == 9)
|
||||
mask = 0x01;
|
||||
else
|
||||
mask = 0x03;
|
||||
writeRegister(TCA8418_REG_KP_GPIO_3, mask);
|
||||
}
|
||||
|
||||
// Set keyboard matrix size
|
||||
matrix(rows, columns);
|
||||
enableDebounce();
|
||||
flush();
|
||||
state = Idle;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TCA8418KeyboardBase::matrix(uint8_t rows, uint8_t columns)
|
||||
{
|
||||
if (rows < 1 || rows > 8 || columns < 1 || columns > 10)
|
||||
return false;
|
||||
|
||||
// Setup the keypad matrix.
|
||||
uint8_t mask = 0x00;
|
||||
for (int r = 0; r < rows; r++) {
|
||||
mask <<= 1;
|
||||
mask |= 1;
|
||||
}
|
||||
writeRegister(TCA8418_REG_KP_GPIO_1, mask);
|
||||
|
||||
mask = 0x00;
|
||||
for (int c = 0; c < columns && c < 8; c++) {
|
||||
mask <<= 1;
|
||||
mask |= 1;
|
||||
}
|
||||
writeRegister(TCA8418_REG_KP_GPIO_2, mask);
|
||||
|
||||
if (columns > 8) {
|
||||
if (columns == 9)
|
||||
mask = 0x01;
|
||||
else
|
||||
mask = 0x03;
|
||||
writeRegister(TCA8418_REG_KP_GPIO_3, mask);
|
||||
}
|
||||
|
||||
return true;
|
||||
uint8_t TCA8418KeyboardBase::keyCount() const {
|
||||
uint8_t eventCount = readRegister(TCA8418_REG_KEY_LCK_EC);
|
||||
eventCount &= 0x0F; // lower 4 bits only
|
||||
return eventCount;
|
||||
}
|
||||
|
||||
uint8_t TCA8418KeyboardBase::keyCount() const
|
||||
{
|
||||
uint8_t eventCount = readRegister(TCA8418_REG_KEY_LCK_EC);
|
||||
eventCount &= 0x0F; // lower 4 bits only
|
||||
return eventCount;
|
||||
bool TCA8418KeyboardBase::hasEvent() const { return queue.length() > 0; }
|
||||
|
||||
void TCA8418KeyboardBase::queueEvent(char next) {
|
||||
if (next == NONE) {
|
||||
return;
|
||||
}
|
||||
queue.concat(next);
|
||||
}
|
||||
|
||||
bool TCA8418KeyboardBase::hasEvent() const
|
||||
{
|
||||
return queue.length() > 0;
|
||||
char TCA8418KeyboardBase::dequeueEvent() {
|
||||
if (queue.length() < 1) {
|
||||
return NONE;
|
||||
}
|
||||
char next = queue.charAt(0);
|
||||
queue.remove(0, 1);
|
||||
return next;
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::queueEvent(char next)
|
||||
{
|
||||
if (next == NONE) {
|
||||
return;
|
||||
}
|
||||
queue.concat(next);
|
||||
}
|
||||
|
||||
char TCA8418KeyboardBase::dequeueEvent()
|
||||
{
|
||||
if (queue.length() < 1) {
|
||||
return NONE;
|
||||
}
|
||||
char next = queue.charAt(0);
|
||||
queue.remove(0, 1);
|
||||
return next;
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::trigger()
|
||||
{
|
||||
if (keyCount() == 0) {
|
||||
return;
|
||||
}
|
||||
if (state != Init) {
|
||||
// Read the key register
|
||||
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A);
|
||||
uint8_t key = k & 0x7F;
|
||||
if (k & 0x80) {
|
||||
if (state == Idle)
|
||||
pressed(key);
|
||||
return;
|
||||
} else {
|
||||
if (state == Held) {
|
||||
released();
|
||||
}
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
void TCA8418KeyboardBase::trigger() {
|
||||
if (keyCount() == 0) {
|
||||
return;
|
||||
}
|
||||
if (state != Init) {
|
||||
// Read the key register
|
||||
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A);
|
||||
uint8_t key = k & 0x7F;
|
||||
if (k & 0x80) {
|
||||
if (state == Idle)
|
||||
pressed(key);
|
||||
return;
|
||||
} else {
|
||||
reset();
|
||||
if (state == Held) {
|
||||
released();
|
||||
}
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::pressed(uint8_t key)
|
||||
{
|
||||
// must be defined in derived class
|
||||
LOG_ERROR("pressed() not implemented in derived class");
|
||||
void TCA8418KeyboardBase::pressed(uint8_t key) {
|
||||
// must be defined in derived class
|
||||
LOG_ERROR("pressed() not implemented in derived class");
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::released()
|
||||
{
|
||||
// must be defined in derived class
|
||||
LOG_ERROR("released() not implemented in derived class");
|
||||
void TCA8418KeyboardBase::released() {
|
||||
// must be defined in derived class
|
||||
LOG_ERROR("released() not implemented in derived class");
|
||||
}
|
||||
|
||||
uint8_t TCA8418KeyboardBase::flush()
|
||||
{
|
||||
// Flush key events
|
||||
uint8_t count = 0;
|
||||
while (readRegister(TCA8418_REG_KEY_EVENT_A) != 0)
|
||||
count++;
|
||||
uint8_t TCA8418KeyboardBase::flush() {
|
||||
// Flush key events
|
||||
uint8_t count = 0;
|
||||
while (readRegister(TCA8418_REG_KEY_EVENT_A) != 0)
|
||||
count++;
|
||||
|
||||
// Flush gpio events
|
||||
readRegister(TCA8418_REG_GPIO_INT_STAT_1);
|
||||
readRegister(TCA8418_REG_GPIO_INT_STAT_2);
|
||||
readRegister(TCA8418_REG_GPIO_INT_STAT_3);
|
||||
// Flush gpio events
|
||||
readRegister(TCA8418_REG_GPIO_INT_STAT_1);
|
||||
readRegister(TCA8418_REG_GPIO_INT_STAT_2);
|
||||
readRegister(TCA8418_REG_GPIO_INT_STAT_3);
|
||||
|
||||
// Clear INT_STAT register
|
||||
writeRegister(TCA8418_REG_INT_STAT, 3);
|
||||
return count;
|
||||
// Clear INT_STAT register
|
||||
writeRegister(TCA8418_REG_INT_STAT, 3);
|
||||
return count;
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::clearInt()
|
||||
{
|
||||
writeRegister(TCA8418_REG_INT_STAT, 3);
|
||||
void TCA8418KeyboardBase::clearInt() { writeRegister(TCA8418_REG_INT_STAT, 3); }
|
||||
|
||||
uint8_t TCA8418KeyboardBase::digitalRead(uint8_t pinnum) const {
|
||||
if (pinnum > TCA8418_COL9)
|
||||
return 0xFF;
|
||||
|
||||
uint8_t reg = TCA8418_REG_GPIO_DAT_STAT_1 + pinnum / 8;
|
||||
uint8_t mask = (1 << (pinnum % 8));
|
||||
|
||||
// Level 0 = low other = high
|
||||
uint8_t value = readRegister(reg);
|
||||
if (value & mask)
|
||||
return HIGH;
|
||||
return LOW;
|
||||
}
|
||||
|
||||
uint8_t TCA8418KeyboardBase::digitalRead(uint8_t pinnum) const
|
||||
{
|
||||
if (pinnum > TCA8418_COL9)
|
||||
return 0xFF;
|
||||
bool TCA8418KeyboardBase::digitalWrite(uint8_t pinnum, uint8_t level) {
|
||||
if (pinnum > TCA8418_COL9)
|
||||
return false;
|
||||
|
||||
uint8_t reg = TCA8418_REG_GPIO_DAT_STAT_1 + pinnum / 8;
|
||||
uint8_t mask = (1 << (pinnum % 8));
|
||||
uint8_t reg = TCA8418_REG_GPIO_DAT_OUT_1 + pinnum / 8;
|
||||
uint8_t mask = (1 << (pinnum % 8));
|
||||
|
||||
// Level 0 = low other = high
|
||||
uint8_t value = readRegister(reg);
|
||||
if (value & mask)
|
||||
return HIGH;
|
||||
return LOW;
|
||||
}
|
||||
|
||||
bool TCA8418KeyboardBase::digitalWrite(uint8_t pinnum, uint8_t level)
|
||||
{
|
||||
if (pinnum > TCA8418_COL9)
|
||||
return false;
|
||||
|
||||
uint8_t reg = TCA8418_REG_GPIO_DAT_OUT_1 + pinnum / 8;
|
||||
uint8_t mask = (1 << (pinnum % 8));
|
||||
|
||||
// Level 0 = low other = high
|
||||
uint8_t value = readRegister(reg);
|
||||
if (level == LOW)
|
||||
value &= ~mask;
|
||||
else
|
||||
value |= mask;
|
||||
writeRegister(reg, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TCA8418KeyboardBase::pinMode(uint8_t pinnum, uint8_t mode)
|
||||
{
|
||||
if (pinnum > TCA8418_COL9)
|
||||
return false;
|
||||
|
||||
uint8_t idx = pinnum / 8;
|
||||
uint8_t reg = TCA8418_REG_GPIO_DIR_1 + idx;
|
||||
uint8_t mask = (1 << (pinnum % 8));
|
||||
|
||||
// Mode 0 = input 1 = output
|
||||
uint8_t value = readRegister(reg);
|
||||
if (mode == OUTPUT)
|
||||
value |= mask;
|
||||
else
|
||||
value &= ~mask;
|
||||
writeRegister(reg, value);
|
||||
|
||||
// Pullup 0 = enabled 1 = disabled
|
||||
reg = TCA8418_REG_GPIO_PULL_1 + idx;
|
||||
value = readRegister(reg);
|
||||
if (mode == INPUT_PULLUP)
|
||||
value &= ~mask;
|
||||
else
|
||||
value |= mask;
|
||||
writeRegister(reg, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TCA8418KeyboardBase::pinIRQMode(uint8_t pinnum, uint8_t mode)
|
||||
{
|
||||
if (pinnum > TCA8418_COL9)
|
||||
return false;
|
||||
if ((mode != RISING) && (mode != FALLING))
|
||||
return false;
|
||||
|
||||
// Mode 0 = falling 1 = rising
|
||||
uint8_t idx = pinnum / 8;
|
||||
uint8_t reg = TCA8418_REG_GPIO_INT_LVL_1 + idx;
|
||||
uint8_t mask = (1 << (pinnum % 8));
|
||||
|
||||
uint8_t value = readRegister(reg);
|
||||
if (mode == RISING)
|
||||
value |= mask;
|
||||
else
|
||||
value &= ~mask;
|
||||
writeRegister(reg, value);
|
||||
|
||||
// Enable interrupt
|
||||
reg = TCA8418_REG_GPIO_INT_EN_1 + idx;
|
||||
value = readRegister(reg);
|
||||
// Level 0 = low other = high
|
||||
uint8_t value = readRegister(reg);
|
||||
if (level == LOW)
|
||||
value &= ~mask;
|
||||
else
|
||||
value |= mask;
|
||||
writeRegister(reg, value);
|
||||
|
||||
return true;
|
||||
writeRegister(reg, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::enableInterrupts()
|
||||
{
|
||||
uint8_t value = readRegister(TCA8418_REG_CFG);
|
||||
value |= (_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN);
|
||||
writeRegister(TCA8418_REG_CFG, value);
|
||||
};
|
||||
bool TCA8418KeyboardBase::pinMode(uint8_t pinnum, uint8_t mode) {
|
||||
if (pinnum > TCA8418_COL9)
|
||||
return false;
|
||||
|
||||
void TCA8418KeyboardBase::disableInterrupts()
|
||||
{
|
||||
uint8_t value = readRegister(TCA8418_REG_CFG);
|
||||
value &= ~(_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN);
|
||||
writeRegister(TCA8418_REG_CFG, value);
|
||||
};
|
||||
uint8_t idx = pinnum / 8;
|
||||
uint8_t reg = TCA8418_REG_GPIO_DIR_1 + idx;
|
||||
uint8_t mask = (1 << (pinnum % 8));
|
||||
|
||||
void TCA8418KeyboardBase::enableMatrixOverflow()
|
||||
{
|
||||
uint8_t value = readRegister(TCA8418_REG_CFG);
|
||||
value |= _TCA8418_REG_CFG_OVR_FLOW_M;
|
||||
writeRegister(TCA8418_REG_CFG, value);
|
||||
};
|
||||
// Mode 0 = input 1 = output
|
||||
uint8_t value = readRegister(reg);
|
||||
if (mode == OUTPUT)
|
||||
value |= mask;
|
||||
else
|
||||
value &= ~mask;
|
||||
writeRegister(reg, value);
|
||||
|
||||
void TCA8418KeyboardBase::disableMatrixOverflow()
|
||||
{
|
||||
uint8_t value = readRegister(TCA8418_REG_CFG);
|
||||
value &= ~_TCA8418_REG_CFG_OVR_FLOW_M;
|
||||
writeRegister(TCA8418_REG_CFG, value);
|
||||
};
|
||||
// Pullup 0 = enabled 1 = disabled
|
||||
reg = TCA8418_REG_GPIO_PULL_1 + idx;
|
||||
value = readRegister(reg);
|
||||
if (mode == INPUT_PULLUP)
|
||||
value &= ~mask;
|
||||
else
|
||||
value |= mask;
|
||||
writeRegister(reg, value);
|
||||
|
||||
void TCA8418KeyboardBase::enableDebounce()
|
||||
{
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_1, 0x00);
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_2, 0x00);
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_3, 0x00);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::disableDebounce()
|
||||
{
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_1, 0xFF);
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_2, 0xFF);
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_3, 0xFF);
|
||||
bool TCA8418KeyboardBase::pinIRQMode(uint8_t pinnum, uint8_t mode) {
|
||||
if (pinnum > TCA8418_COL9)
|
||||
return false;
|
||||
if ((mode != RISING) && (mode != FALLING))
|
||||
return false;
|
||||
|
||||
// Mode 0 = falling 1 = rising
|
||||
uint8_t idx = pinnum / 8;
|
||||
uint8_t reg = TCA8418_REG_GPIO_INT_LVL_1 + idx;
|
||||
uint8_t mask = (1 << (pinnum % 8));
|
||||
|
||||
uint8_t value = readRegister(reg);
|
||||
if (mode == RISING)
|
||||
value |= mask;
|
||||
else
|
||||
value &= ~mask;
|
||||
writeRegister(reg, value);
|
||||
|
||||
// Enable interrupt
|
||||
reg = TCA8418_REG_GPIO_INT_EN_1 + idx;
|
||||
value = readRegister(reg);
|
||||
value |= mask;
|
||||
writeRegister(reg, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::enableInterrupts() {
|
||||
uint8_t value = readRegister(TCA8418_REG_CFG);
|
||||
value |= (_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN);
|
||||
writeRegister(TCA8418_REG_CFG, value);
|
||||
};
|
||||
|
||||
void TCA8418KeyboardBase::disableInterrupts() {
|
||||
uint8_t value = readRegister(TCA8418_REG_CFG);
|
||||
value &= ~(_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN);
|
||||
writeRegister(TCA8418_REG_CFG, value);
|
||||
};
|
||||
|
||||
void TCA8418KeyboardBase::enableMatrixOverflow() {
|
||||
uint8_t value = readRegister(TCA8418_REG_CFG);
|
||||
value |= _TCA8418_REG_CFG_OVR_FLOW_M;
|
||||
writeRegister(TCA8418_REG_CFG, value);
|
||||
};
|
||||
|
||||
void TCA8418KeyboardBase::disableMatrixOverflow() {
|
||||
uint8_t value = readRegister(TCA8418_REG_CFG);
|
||||
value &= ~_TCA8418_REG_CFG_OVR_FLOW_M;
|
||||
writeRegister(TCA8418_REG_CFG, value);
|
||||
};
|
||||
|
||||
void TCA8418KeyboardBase::enableDebounce() {
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_1, 0x00);
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_2, 0x00);
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_3, 0x00);
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::disableDebounce() {
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_1, 0xFF);
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_2, 0xFF);
|
||||
writeRegister(TCA8418_REG_DEBOUNCE_DIS_3, 0xFF);
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::setBacklight(bool on) {}
|
||||
|
||||
uint8_t TCA8418KeyboardBase::readRegister(uint8_t reg) const
|
||||
{
|
||||
if (m_wire) {
|
||||
m_wire->beginTransmission(m_addr);
|
||||
m_wire->write(reg);
|
||||
m_wire->endTransmission();
|
||||
uint8_t TCA8418KeyboardBase::readRegister(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;
|
||||
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;
|
||||
return m_wire->read();
|
||||
}
|
||||
if (readCallback) {
|
||||
uint8_t data;
|
||||
readCallback(m_addr, reg, &data, 1);
|
||||
return data;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TCA8418KeyboardBase::writeRegister(uint8_t reg, uint8_t value)
|
||||
{
|
||||
uint8_t data[2];
|
||||
data[0] = reg;
|
||||
data[1] = value;
|
||||
void TCA8418KeyboardBase::writeRegister(uint8_t reg, uint8_t value) {
|
||||
uint8_t data[2];
|
||||
data[0] = reg;
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -8,165 +8,164 @@
|
||||
* and handling key states. It is designed to be extended for specific keyboard implementations.
|
||||
* It supports both I2C communication and function pointers for custom I2C operations.
|
||||
*/
|
||||
class TCA8418KeyboardBase
|
||||
{
|
||||
public:
|
||||
enum TCA8418Key : uint8_t {
|
||||
NONE = 0x00,
|
||||
BSP = 0x08,
|
||||
TAB = 0x09,
|
||||
SELECT = 0x0d,
|
||||
ESC = 0x1b,
|
||||
REBOOT = 0x90,
|
||||
LEFT = 0xb4,
|
||||
UP = 0xb5,
|
||||
DOWN = 0xb6,
|
||||
RIGHT = 0xb7,
|
||||
BT_TOGGLE = 0xAA,
|
||||
GPS_TOGGLE = 0x9E,
|
||||
MUTE_TOGGLE = 0xAC,
|
||||
SEND_PING = 0xAF,
|
||||
BL_TOGGLE = 0xAB
|
||||
};
|
||||
class TCA8418KeyboardBase {
|
||||
public:
|
||||
enum TCA8418Key : uint8_t {
|
||||
NONE = 0x00,
|
||||
BSP = 0x08,
|
||||
TAB = 0x09,
|
||||
SELECT = 0x0d,
|
||||
ESC = 0x1b,
|
||||
REBOOT = 0x90,
|
||||
LEFT = 0xb4,
|
||||
UP = 0xb5,
|
||||
DOWN = 0xb6,
|
||||
RIGHT = 0xb7,
|
||||
BT_TOGGLE = 0xAA,
|
||||
GPS_TOGGLE = 0x9E,
|
||||
MUTE_TOGGLE = 0xAC,
|
||||
SEND_PING = 0xAF,
|
||||
BL_TOGGLE = 0xAB
|
||||
};
|
||||
|
||||
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
|
||||
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
|
||||
|
||||
TCA8418KeyboardBase(uint8_t rows, uint8_t columns);
|
||||
TCA8418KeyboardBase(uint8_t rows, uint8_t columns);
|
||||
|
||||
virtual void begin(uint8_t addr = TCA8418_KB_ADDR, TwoWire *wire = &Wire);
|
||||
virtual void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = TCA8418_KB_ADDR);
|
||||
virtual void begin(uint8_t addr = TCA8418_KB_ADDR, TwoWire *wire = &Wire);
|
||||
virtual void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = TCA8418_KB_ADDR);
|
||||
|
||||
virtual void reset(void);
|
||||
void clearInt(void);
|
||||
virtual void reset(void);
|
||||
void clearInt(void);
|
||||
|
||||
virtual void trigger(void);
|
||||
virtual void trigger(void);
|
||||
|
||||
virtual void setBacklight(bool on);
|
||||
virtual void setBacklight(bool on);
|
||||
|
||||
// Key events available
|
||||
virtual bool hasEvent(void) const;
|
||||
virtual char dequeueEvent(void);
|
||||
// Key events available
|
||||
virtual bool hasEvent(void) const;
|
||||
virtual char dequeueEvent(void);
|
||||
|
||||
protected:
|
||||
enum KeyState { Init, Idle, Held, Busy };
|
||||
protected:
|
||||
enum KeyState { Init, Idle, Held, Busy };
|
||||
|
||||
enum TCA8418Register : uint8_t {
|
||||
TCA8418_REG_RESERVED = 0x00,
|
||||
TCA8418_REG_CFG = 0x01,
|
||||
TCA8418_REG_INT_STAT = 0x02,
|
||||
TCA8418_REG_KEY_LCK_EC = 0x03,
|
||||
TCA8418_REG_KEY_EVENT_A = 0x04,
|
||||
TCA8418_REG_KEY_EVENT_B = 0x05,
|
||||
TCA8418_REG_KEY_EVENT_C = 0x06,
|
||||
TCA8418_REG_KEY_EVENT_D = 0x07,
|
||||
TCA8418_REG_KEY_EVENT_E = 0x08,
|
||||
TCA8418_REG_KEY_EVENT_F = 0x09,
|
||||
TCA8418_REG_KEY_EVENT_G = 0x0A,
|
||||
TCA8418_REG_KEY_EVENT_H = 0x0B,
|
||||
TCA8418_REG_KEY_EVENT_I = 0x0C,
|
||||
TCA8418_REG_KEY_EVENT_J = 0x0D,
|
||||
TCA8418_REG_KP_LCK_TIMER = 0x0E,
|
||||
TCA8418_REG_UNLOCK_1 = 0x0F,
|
||||
TCA8418_REG_UNLOCK_2 = 0x10,
|
||||
TCA8418_REG_GPIO_INT_STAT_1 = 0x11,
|
||||
TCA8418_REG_GPIO_INT_STAT_2 = 0x12,
|
||||
TCA8418_REG_GPIO_INT_STAT_3 = 0x13,
|
||||
TCA8418_REG_GPIO_DAT_STAT_1 = 0x14,
|
||||
TCA8418_REG_GPIO_DAT_STAT_2 = 0x15,
|
||||
TCA8418_REG_GPIO_DAT_STAT_3 = 0x16,
|
||||
TCA8418_REG_GPIO_DAT_OUT_1 = 0x17,
|
||||
TCA8418_REG_GPIO_DAT_OUT_2 = 0x18,
|
||||
TCA8418_REG_GPIO_DAT_OUT_3 = 0x19,
|
||||
TCA8418_REG_GPIO_INT_EN_1 = 0x1A,
|
||||
TCA8418_REG_GPIO_INT_EN_2 = 0x1B,
|
||||
TCA8418_REG_GPIO_INT_EN_3 = 0x1C,
|
||||
TCA8418_REG_KP_GPIO_1 = 0x1D,
|
||||
TCA8418_REG_KP_GPIO_2 = 0x1E,
|
||||
TCA8418_REG_KP_GPIO_3 = 0x1F,
|
||||
TCA8418_REG_GPI_EM_1 = 0x20,
|
||||
TCA8418_REG_GPI_EM_2 = 0x21,
|
||||
TCA8418_REG_GPI_EM_3 = 0x22,
|
||||
TCA8418_REG_GPIO_DIR_1 = 0x23,
|
||||
TCA8418_REG_GPIO_DIR_2 = 0x24,
|
||||
TCA8418_REG_GPIO_DIR_3 = 0x25,
|
||||
TCA8418_REG_GPIO_INT_LVL_1 = 0x26,
|
||||
TCA8418_REG_GPIO_INT_LVL_2 = 0x27,
|
||||
TCA8418_REG_GPIO_INT_LVL_3 = 0x28,
|
||||
TCA8418_REG_DEBOUNCE_DIS_1 = 0x29,
|
||||
TCA8418_REG_DEBOUNCE_DIS_2 = 0x2A,
|
||||
TCA8418_REG_DEBOUNCE_DIS_3 = 0x2B,
|
||||
TCA8418_REG_GPIO_PULL_1 = 0x2C,
|
||||
TCA8418_REG_GPIO_PULL_2 = 0x2D,
|
||||
TCA8418_REG_GPIO_PULL_3 = 0x2E
|
||||
};
|
||||
enum TCA8418Register : uint8_t {
|
||||
TCA8418_REG_RESERVED = 0x00,
|
||||
TCA8418_REG_CFG = 0x01,
|
||||
TCA8418_REG_INT_STAT = 0x02,
|
||||
TCA8418_REG_KEY_LCK_EC = 0x03,
|
||||
TCA8418_REG_KEY_EVENT_A = 0x04,
|
||||
TCA8418_REG_KEY_EVENT_B = 0x05,
|
||||
TCA8418_REG_KEY_EVENT_C = 0x06,
|
||||
TCA8418_REG_KEY_EVENT_D = 0x07,
|
||||
TCA8418_REG_KEY_EVENT_E = 0x08,
|
||||
TCA8418_REG_KEY_EVENT_F = 0x09,
|
||||
TCA8418_REG_KEY_EVENT_G = 0x0A,
|
||||
TCA8418_REG_KEY_EVENT_H = 0x0B,
|
||||
TCA8418_REG_KEY_EVENT_I = 0x0C,
|
||||
TCA8418_REG_KEY_EVENT_J = 0x0D,
|
||||
TCA8418_REG_KP_LCK_TIMER = 0x0E,
|
||||
TCA8418_REG_UNLOCK_1 = 0x0F,
|
||||
TCA8418_REG_UNLOCK_2 = 0x10,
|
||||
TCA8418_REG_GPIO_INT_STAT_1 = 0x11,
|
||||
TCA8418_REG_GPIO_INT_STAT_2 = 0x12,
|
||||
TCA8418_REG_GPIO_INT_STAT_3 = 0x13,
|
||||
TCA8418_REG_GPIO_DAT_STAT_1 = 0x14,
|
||||
TCA8418_REG_GPIO_DAT_STAT_2 = 0x15,
|
||||
TCA8418_REG_GPIO_DAT_STAT_3 = 0x16,
|
||||
TCA8418_REG_GPIO_DAT_OUT_1 = 0x17,
|
||||
TCA8418_REG_GPIO_DAT_OUT_2 = 0x18,
|
||||
TCA8418_REG_GPIO_DAT_OUT_3 = 0x19,
|
||||
TCA8418_REG_GPIO_INT_EN_1 = 0x1A,
|
||||
TCA8418_REG_GPIO_INT_EN_2 = 0x1B,
|
||||
TCA8418_REG_GPIO_INT_EN_3 = 0x1C,
|
||||
TCA8418_REG_KP_GPIO_1 = 0x1D,
|
||||
TCA8418_REG_KP_GPIO_2 = 0x1E,
|
||||
TCA8418_REG_KP_GPIO_3 = 0x1F,
|
||||
TCA8418_REG_GPI_EM_1 = 0x20,
|
||||
TCA8418_REG_GPI_EM_2 = 0x21,
|
||||
TCA8418_REG_GPI_EM_3 = 0x22,
|
||||
TCA8418_REG_GPIO_DIR_1 = 0x23,
|
||||
TCA8418_REG_GPIO_DIR_2 = 0x24,
|
||||
TCA8418_REG_GPIO_DIR_3 = 0x25,
|
||||
TCA8418_REG_GPIO_INT_LVL_1 = 0x26,
|
||||
TCA8418_REG_GPIO_INT_LVL_2 = 0x27,
|
||||
TCA8418_REG_GPIO_INT_LVL_3 = 0x28,
|
||||
TCA8418_REG_DEBOUNCE_DIS_1 = 0x29,
|
||||
TCA8418_REG_DEBOUNCE_DIS_2 = 0x2A,
|
||||
TCA8418_REG_DEBOUNCE_DIS_3 = 0x2B,
|
||||
TCA8418_REG_GPIO_PULL_1 = 0x2C,
|
||||
TCA8418_REG_GPIO_PULL_2 = 0x2D,
|
||||
TCA8418_REG_GPIO_PULL_3 = 0x2E
|
||||
};
|
||||
|
||||
// Pin IDs for matrix rows/columns
|
||||
enum TCA8418PinId : uint8_t {
|
||||
TCA8418_ROW0, // Pin ID for row 0
|
||||
TCA8418_ROW1, // Pin ID for row 1
|
||||
TCA8418_ROW2, // Pin ID for row 2
|
||||
TCA8418_ROW3, // Pin ID for row 3
|
||||
TCA8418_ROW4, // Pin ID for row 4
|
||||
TCA8418_ROW5, // Pin ID for row 5
|
||||
TCA8418_ROW6, // Pin ID for row 6
|
||||
TCA8418_ROW7, // Pin ID for row 7
|
||||
TCA8418_COL0, // Pin ID for column 0
|
||||
TCA8418_COL1, // Pin ID for column 1
|
||||
TCA8418_COL2, // Pin ID for column 2
|
||||
TCA8418_COL3, // Pin ID for column 3
|
||||
TCA8418_COL4, // Pin ID for column 4
|
||||
TCA8418_COL5, // Pin ID for column 5
|
||||
TCA8418_COL6, // Pin ID for column 6
|
||||
TCA8418_COL7, // Pin ID for column 7
|
||||
TCA8418_COL8, // Pin ID for column 8
|
||||
TCA8418_COL9 // Pin ID for column 9
|
||||
};
|
||||
// Pin IDs for matrix rows/columns
|
||||
enum TCA8418PinId : uint8_t {
|
||||
TCA8418_ROW0, // Pin ID for row 0
|
||||
TCA8418_ROW1, // Pin ID for row 1
|
||||
TCA8418_ROW2, // Pin ID for row 2
|
||||
TCA8418_ROW3, // Pin ID for row 3
|
||||
TCA8418_ROW4, // Pin ID for row 4
|
||||
TCA8418_ROW5, // Pin ID for row 5
|
||||
TCA8418_ROW6, // Pin ID for row 6
|
||||
TCA8418_ROW7, // Pin ID for row 7
|
||||
TCA8418_COL0, // Pin ID for column 0
|
||||
TCA8418_COL1, // Pin ID for column 1
|
||||
TCA8418_COL2, // Pin ID for column 2
|
||||
TCA8418_COL3, // Pin ID for column 3
|
||||
TCA8418_COL4, // Pin ID for column 4
|
||||
TCA8418_COL5, // Pin ID for column 5
|
||||
TCA8418_COL6, // Pin ID for column 6
|
||||
TCA8418_COL7, // Pin ID for column 7
|
||||
TCA8418_COL8, // Pin ID for column 8
|
||||
TCA8418_COL9 // Pin ID for column 9
|
||||
};
|
||||
|
||||
virtual void pressed(uint8_t key);
|
||||
virtual void released(void);
|
||||
virtual void pressed(uint8_t key);
|
||||
virtual void released(void);
|
||||
|
||||
virtual void queueEvent(char);
|
||||
virtual void queueEvent(char);
|
||||
|
||||
virtual ~TCA8418KeyboardBase() {}
|
||||
virtual ~TCA8418KeyboardBase() {}
|
||||
|
||||
protected:
|
||||
// Set the size of the keypad matrix
|
||||
// All other rows and columns are set as inputs.
|
||||
bool matrix(uint8_t rows, uint8_t columns);
|
||||
protected:
|
||||
// Set the size of the keypad matrix
|
||||
// All other rows and columns are set as inputs.
|
||||
bool matrix(uint8_t rows, uint8_t columns);
|
||||
|
||||
uint8_t keyCount(void) const;
|
||||
uint8_t keyCount(void) const;
|
||||
|
||||
// Flush all events in the FIFO buffer + GPIO events.
|
||||
uint8_t flush(void);
|
||||
// Flush all events in the FIFO buffer + GPIO events.
|
||||
uint8_t flush(void);
|
||||
|
||||
// debounce keys.
|
||||
void enableDebounce();
|
||||
void disableDebounce();
|
||||
// debounce keys.
|
||||
void enableDebounce();
|
||||
void disableDebounce();
|
||||
|
||||
// enable / disable interrupts for matrix and GPI pins
|
||||
void enableInterrupts();
|
||||
void disableInterrupts();
|
||||
// enable / disable interrupts for matrix and GPI pins
|
||||
void enableInterrupts();
|
||||
void disableInterrupts();
|
||||
|
||||
// ignore key events when FIFO buffer is full or not.
|
||||
void enableMatrixOverflow();
|
||||
void disableMatrixOverflow();
|
||||
// ignore key events when FIFO buffer is full or not.
|
||||
void enableMatrixOverflow();
|
||||
void disableMatrixOverflow();
|
||||
|
||||
uint8_t digitalRead(uint8_t pinnum) const;
|
||||
bool digitalWrite(uint8_t pinnum, uint8_t level);
|
||||
bool pinMode(uint8_t pinnum, uint8_t mode);
|
||||
bool pinIRQMode(uint8_t pinnum, uint8_t mode); // MODE FALLING or RISING
|
||||
uint8_t readRegister(uint8_t reg) const;
|
||||
void writeRegister(uint8_t reg, uint8_t value);
|
||||
uint8_t digitalRead(uint8_t pinnum) const;
|
||||
bool digitalWrite(uint8_t pinnum, uint8_t level);
|
||||
bool pinMode(uint8_t pinnum, uint8_t mode);
|
||||
bool pinIRQMode(uint8_t pinnum, uint8_t mode); // MODE FALLING or RISING
|
||||
uint8_t readRegister(uint8_t reg) const;
|
||||
void writeRegister(uint8_t reg, uint8_t value);
|
||||
|
||||
protected:
|
||||
uint8_t rows;
|
||||
uint8_t columns;
|
||||
KeyState state;
|
||||
String queue;
|
||||
protected:
|
||||
uint8_t rows;
|
||||
uint8_t columns;
|
||||
KeyState state;
|
||||
String queue;
|
||||
|
||||
private:
|
||||
TwoWire *m_wire;
|
||||
uint8_t m_addr;
|
||||
i2c_com_fptr_t readCallback;
|
||||
i2c_com_fptr_t writeCallback;
|
||||
private:
|
||||
TwoWire *m_wire;
|
||||
uint8_t m_addr;
|
||||
i2c_com_fptr_t readCallback;
|
||||
i2c_com_fptr_t writeCallback;
|
||||
};
|
||||
|
||||
@@ -62,135 +62,123 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = {
|
||||
};
|
||||
|
||||
TDeckProKeyboard::TDeckProKeyboard()
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
|
||||
last_tap(0L), char_idx(0), tap_interval(0)
|
||||
{
|
||||
}
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), last_tap(0L),
|
||||
char_idx(0), tap_interval(0) {}
|
||||
|
||||
void TDeckProKeyboard::reset()
|
||||
{
|
||||
TCA8418KeyboardBase::reset();
|
||||
pinMode(KB_BL_PIN, OUTPUT);
|
||||
setBacklight(false);
|
||||
void TDeckProKeyboard::reset() {
|
||||
TCA8418KeyboardBase::reset();
|
||||
pinMode(KB_BL_PIN, OUTPUT);
|
||||
setBacklight(false);
|
||||
}
|
||||
|
||||
// handle multi-key presses (shift and alt)
|
||||
void TDeckProKeyboard::trigger()
|
||||
{
|
||||
uint8_t count = keyCount();
|
||||
if (count == 0)
|
||||
return;
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
|
||||
uint8_t key = k & 0x7F;
|
||||
if (k & 0x80) {
|
||||
pressed(key);
|
||||
} else {
|
||||
released();
|
||||
state = Idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TDeckProKeyboard::pressed(uint8_t key)
|
||||
{
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
return; // Invalid key
|
||||
}
|
||||
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
state = Held;
|
||||
|
||||
uint32_t now = millis();
|
||||
tap_interval = now - last_tap;
|
||||
|
||||
updateModifierFlag(next_key);
|
||||
if (isModifierKey(next_key)) {
|
||||
last_modifier_time = now;
|
||||
}
|
||||
|
||||
if (tap_interval < 0) {
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0;
|
||||
void TDeckProKeyboard::trigger() {
|
||||
uint8_t count = keyCount();
|
||||
if (count == 0)
|
||||
return;
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
|
||||
uint8_t key = k & 0x7F;
|
||||
if (k & 0x80) {
|
||||
pressed(key);
|
||||
} else {
|
||||
char_idx += 1;
|
||||
released();
|
||||
state = Idle;
|
||||
}
|
||||
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
}
|
||||
}
|
||||
|
||||
void TDeckProKeyboard::released()
|
||||
{
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
void TDeckProKeyboard::pressed(uint8_t key) {
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
|
||||
uint32_t now = millis();
|
||||
last_tap = now;
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
return; // Invalid key
|
||||
}
|
||||
|
||||
if (TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]] == Key::BL_TOGGLE) {
|
||||
toggleBacklight();
|
||||
return;
|
||||
}
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
state = Held;
|
||||
|
||||
queueEvent(TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]]);
|
||||
if (isModifierKey(last_key) == false)
|
||||
modifierFlag = 0;
|
||||
uint32_t now = millis();
|
||||
tap_interval = now - last_tap;
|
||||
|
||||
updateModifierFlag(next_key);
|
||||
if (isModifierKey(next_key)) {
|
||||
last_modifier_time = now;
|
||||
}
|
||||
|
||||
if (tap_interval < 0) {
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0;
|
||||
} else {
|
||||
char_idx += 1;
|
||||
}
|
||||
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
}
|
||||
|
||||
void TDeckProKeyboard::setBacklight(bool on)
|
||||
{
|
||||
if (on) {
|
||||
digitalWrite(KB_BL_PIN, HIGH);
|
||||
} else {
|
||||
digitalWrite(KB_BL_PIN, LOW);
|
||||
}
|
||||
void TDeckProKeyboard::released() {
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
last_tap = now;
|
||||
|
||||
if (TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]] == Key::BL_TOGGLE) {
|
||||
toggleBacklight();
|
||||
return;
|
||||
}
|
||||
|
||||
queueEvent(TDeckProTapMap[last_key][modifierFlag % TDeckProTapMod[last_key]]);
|
||||
if (isModifierKey(last_key) == false)
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
void TDeckProKeyboard::toggleBacklight(void)
|
||||
{
|
||||
digitalWrite(KB_BL_PIN, !digitalRead(KB_BL_PIN));
|
||||
void TDeckProKeyboard::setBacklight(bool on) {
|
||||
if (on) {
|
||||
digitalWrite(KB_BL_PIN, HIGH);
|
||||
} else {
|
||||
digitalWrite(KB_BL_PIN, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
void TDeckProKeyboard::updateModifierFlag(uint8_t key)
|
||||
{
|
||||
if (key == modifierRightShiftKey) {
|
||||
modifierFlag ^= modifierRightShift;
|
||||
} else if (key == modifierLeftShiftKey) {
|
||||
modifierFlag ^= modifierLeftShift;
|
||||
} else if (key == modifierSymKey) {
|
||||
modifierFlag ^= modifierSym;
|
||||
} else if (key == modifierAltKey) {
|
||||
modifierFlag ^= modifierAlt;
|
||||
}
|
||||
void TDeckProKeyboard::toggleBacklight(void) { digitalWrite(KB_BL_PIN, !digitalRead(KB_BL_PIN)); }
|
||||
|
||||
void TDeckProKeyboard::updateModifierFlag(uint8_t key) {
|
||||
if (key == modifierRightShiftKey) {
|
||||
modifierFlag ^= modifierRightShift;
|
||||
} else if (key == modifierLeftShiftKey) {
|
||||
modifierFlag ^= modifierLeftShift;
|
||||
} else if (key == modifierSymKey) {
|
||||
modifierFlag ^= modifierSym;
|
||||
} else if (key == modifierAltKey) {
|
||||
modifierFlag ^= modifierAlt;
|
||||
}
|
||||
}
|
||||
|
||||
bool TDeckProKeyboard::isModifierKey(uint8_t key)
|
||||
{
|
||||
return (key == modifierRightShiftKey || key == modifierLeftShiftKey || key == modifierAltKey || key == modifierSymKey);
|
||||
bool TDeckProKeyboard::isModifierKey(uint8_t key) {
|
||||
return (key == modifierRightShiftKey || key == modifierLeftShiftKey || key == modifierAltKey || key == modifierSymKey);
|
||||
}
|
||||
|
||||
#endif // T_DECK_PRO
|
||||
@@ -1,27 +1,26 @@
|
||||
#include "TCA8418KeyboardBase.h"
|
||||
|
||||
class TDeckProKeyboard : public TCA8418KeyboardBase
|
||||
{
|
||||
public:
|
||||
TDeckProKeyboard();
|
||||
void reset(void) override;
|
||||
void trigger(void) override;
|
||||
void setBacklight(bool on) override;
|
||||
class TDeckProKeyboard : public TCA8418KeyboardBase {
|
||||
public:
|
||||
TDeckProKeyboard();
|
||||
void reset(void) override;
|
||||
void trigger(void) override;
|
||||
void setBacklight(bool on) override;
|
||||
|
||||
protected:
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
protected:
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
|
||||
void updateModifierFlag(uint8_t key);
|
||||
bool isModifierKey(uint8_t key);
|
||||
void toggleBacklight(void);
|
||||
void updateModifierFlag(uint8_t key);
|
||||
bool isModifierKey(uint8_t key);
|
||||
void toggleBacklight(void);
|
||||
|
||||
private:
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
private:
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
};
|
||||
|
||||
@@ -29,8 +29,7 @@ constexpr uint8_t modifierSymKey = 21 - 1;
|
||||
constexpr uint8_t modifierSym = 0b0010;
|
||||
|
||||
// Num chars per key, Modulus for rotating through characters
|
||||
static uint8_t TLoraPagerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
|
||||
static uint8_t TLoraPagerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
|
||||
|
||||
static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'},
|
||||
{'w', 'W', '2'},
|
||||
@@ -65,168 +64,156 @@ static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'},
|
||||
{' ', 0x00, Key::BL_TOGGLE}};
|
||||
|
||||
TLoraPagerKeyboard::TLoraPagerKeyboard()
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
|
||||
last_tap(0L), char_idx(0), tap_interval(0)
|
||||
{
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), last_tap(0L),
|
||||
char_idx(0), tap_interval(0) {
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
|
||||
ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
|
||||
#else
|
||||
ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
|
||||
ledcAttachPin(KB_BL_PIN, LEDC_BACKLIGHT_CHANNEL);
|
||||
ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
|
||||
ledcAttachPin(KB_BL_PIN, LEDC_BACKLIGHT_CHANNEL);
|
||||
#endif
|
||||
reset();
|
||||
reset();
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::reset(void)
|
||||
{
|
||||
TCA8418KeyboardBase::reset();
|
||||
pinMode(KB_BL_PIN, OUTPUT);
|
||||
digitalWrite(KB_BL_PIN, LOW);
|
||||
setBacklight(false);
|
||||
void TLoraPagerKeyboard::reset(void) {
|
||||
TCA8418KeyboardBase::reset();
|
||||
pinMode(KB_BL_PIN, OUTPUT);
|
||||
digitalWrite(KB_BL_PIN, LOW);
|
||||
setBacklight(false);
|
||||
}
|
||||
|
||||
// handle multi-key presses (shift and alt)
|
||||
void TLoraPagerKeyboard::trigger()
|
||||
{
|
||||
uint8_t count = keyCount();
|
||||
if (count == 0)
|
||||
return;
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
|
||||
uint8_t key = k & 0x7F;
|
||||
if (k & 0x80) {
|
||||
pressed(key);
|
||||
} else {
|
||||
released();
|
||||
state = Idle;
|
||||
}
|
||||
void TLoraPagerKeyboard::trigger() {
|
||||
uint8_t count = keyCount();
|
||||
if (count == 0)
|
||||
return;
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
|
||||
uint8_t key = k & 0x7F;
|
||||
if (k & 0x80) {
|
||||
pressed(key);
|
||||
} else {
|
||||
released();
|
||||
state = Idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::setBacklight(bool on)
|
||||
{
|
||||
uint32_t _brightness = 0;
|
||||
if (on)
|
||||
_brightness = brightness;
|
||||
void TLoraPagerKeyboard::setBacklight(bool on) {
|
||||
uint32_t _brightness = 0;
|
||||
if (on)
|
||||
_brightness = brightness;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
ledcWrite(KB_BL_PIN, _brightness);
|
||||
ledcWrite(KB_BL_PIN, _brightness);
|
||||
#else
|
||||
ledcWrite(LEDC_BACKLIGHT_CHANNEL, _brightness);
|
||||
ledcWrite(LEDC_BACKLIGHT_CHANNEL, _brightness);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::pressed(uint8_t key)
|
||||
{
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED ||
|
||||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY) {
|
||||
hapticFeedback();
|
||||
}
|
||||
void TLoraPagerKeyboard::pressed(uint8_t key) {
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED ||
|
||||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY) {
|
||||
hapticFeedback();
|
||||
}
|
||||
|
||||
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||
modifierFlag = 0;
|
||||
}
|
||||
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
return; // Invalid key
|
||||
}
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
return; // Invalid key
|
||||
}
|
||||
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
state = Held;
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
state = Held;
|
||||
|
||||
uint32_t now = millis();
|
||||
tap_interval = now - last_tap;
|
||||
uint32_t now = millis();
|
||||
tap_interval = now - last_tap;
|
||||
|
||||
updateModifierFlag(next_key);
|
||||
if (isModifierKey(next_key)) {
|
||||
last_modifier_time = now;
|
||||
}
|
||||
updateModifierFlag(next_key);
|
||||
if (isModifierKey(next_key)) {
|
||||
last_modifier_time = now;
|
||||
}
|
||||
|
||||
if (tap_interval < 0) {
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
if (tap_interval < 0) {
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0;
|
||||
} else {
|
||||
char_idx += 1;
|
||||
}
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0;
|
||||
} else {
|
||||
char_idx += 1;
|
||||
}
|
||||
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::released()
|
||||
{
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
void TLoraPagerKeyboard::released() {
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
last_tap = now;
|
||||
uint32_t now = millis();
|
||||
last_tap = now;
|
||||
|
||||
if (TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]] == Key::BL_TOGGLE) {
|
||||
toggleBacklight();
|
||||
return;
|
||||
}
|
||||
if (TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]] == Key::BL_TOGGLE) {
|
||||
toggleBacklight();
|
||||
return;
|
||||
}
|
||||
|
||||
queueEvent(TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]]);
|
||||
if (isModifierKey(last_key) == false)
|
||||
modifierFlag = 0;
|
||||
queueEvent(TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]]);
|
||||
if (isModifierKey(last_key) == false)
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::hapticFeedback()
|
||||
{
|
||||
drv.setWaveform(0, 14); // strong buzz 100%
|
||||
drv.setWaveform(1, 0); // end waveform
|
||||
drv.go();
|
||||
void TLoraPagerKeyboard::hapticFeedback() {
|
||||
drv.setWaveform(0, 14); // strong buzz 100%
|
||||
drv.setWaveform(1, 0); // end waveform
|
||||
drv.go();
|
||||
}
|
||||
|
||||
// toggle brightness of the backlight in three steps
|
||||
void TLoraPagerKeyboard::toggleBacklight(bool off)
|
||||
{
|
||||
if (off) {
|
||||
brightness = 0;
|
||||
} else {
|
||||
if (brightness == 0) {
|
||||
brightness = 40;
|
||||
} else if (brightness == 40) {
|
||||
brightness = 127;
|
||||
} else if (brightness >= 127) {
|
||||
brightness = 0;
|
||||
}
|
||||
void TLoraPagerKeyboard::toggleBacklight(bool off) {
|
||||
if (off) {
|
||||
brightness = 0;
|
||||
} else {
|
||||
if (brightness == 0) {
|
||||
brightness = 40;
|
||||
} else if (brightness == 40) {
|
||||
brightness = 127;
|
||||
} else if (brightness >= 127) {
|
||||
brightness = 0;
|
||||
}
|
||||
LOG_DEBUG("Toggle backlight: %d", brightness);
|
||||
}
|
||||
LOG_DEBUG("Toggle backlight: %d", brightness);
|
||||
|
||||
setBacklight(true);
|
||||
setBacklight(true);
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::updateModifierFlag(uint8_t key)
|
||||
{
|
||||
if (key == modifierRightShiftKey) {
|
||||
modifierFlag ^= modifierRightShift;
|
||||
} else if (key == modifierSymKey) {
|
||||
modifierFlag ^= modifierSym;
|
||||
}
|
||||
void TLoraPagerKeyboard::updateModifierFlag(uint8_t key) {
|
||||
if (key == modifierRightShiftKey) {
|
||||
modifierFlag ^= modifierRightShift;
|
||||
} else if (key == modifierSymKey) {
|
||||
modifierFlag ^= modifierSym;
|
||||
}
|
||||
}
|
||||
|
||||
bool TLoraPagerKeyboard::isModifierKey(uint8_t key)
|
||||
{
|
||||
return (key == modifierRightShiftKey || key == modifierSymKey);
|
||||
}
|
||||
bool TLoraPagerKeyboard::isModifierKey(uint8_t key) { return (key == modifierRightShiftKey || key == modifierSymKey); }
|
||||
|
||||
#endif
|
||||
@@ -1,30 +1,29 @@
|
||||
#include "TCA8418KeyboardBase.h"
|
||||
|
||||
class TLoraPagerKeyboard : public TCA8418KeyboardBase
|
||||
{
|
||||
public:
|
||||
TLoraPagerKeyboard();
|
||||
void reset(void);
|
||||
void trigger(void) override;
|
||||
void setBacklight(bool on) override;
|
||||
virtual ~TLoraPagerKeyboard() {}
|
||||
class TLoraPagerKeyboard : public TCA8418KeyboardBase {
|
||||
public:
|
||||
TLoraPagerKeyboard();
|
||||
void reset(void);
|
||||
void trigger(void) override;
|
||||
void setBacklight(bool on) override;
|
||||
virtual ~TLoraPagerKeyboard() {}
|
||||
|
||||
protected:
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
void hapticFeedback(void);
|
||||
protected:
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
void hapticFeedback(void);
|
||||
|
||||
void updateModifierFlag(uint8_t key);
|
||||
bool isModifierKey(uint8_t key);
|
||||
void toggleBacklight(bool off = false);
|
||||
void updateModifierFlag(uint8_t key);
|
||||
bool isModifierKey(uint8_t key);
|
||||
void toggleBacklight(bool off = false);
|
||||
|
||||
private:
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
uint32_t brightness = 0;
|
||||
private:
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
uint32_t brightness = 0;
|
||||
};
|
||||
|
||||
@@ -19,141 +19,136 @@
|
||||
#endif
|
||||
|
||||
TouchScreenBase::TouchScreenBase(const char *name, uint16_t width, uint16_t height)
|
||||
: concurrency::OSThread(name), _display_width(width), _display_height(height), _first_x(0), _last_x(0), _first_y(0),
|
||||
_last_y(0), _start(0), _tapped(false), _originName(name)
|
||||
{
|
||||
: concurrency::OSThread(name), _display_width(width), _display_height(height), _first_x(0), _last_x(0), _first_y(0), _last_y(0), _start(0),
|
||||
_tapped(false), _originName(name) {}
|
||||
|
||||
void TouchScreenBase::init(bool hasTouch) {
|
||||
if (hasTouch) {
|
||||
LOG_INFO("TouchScreen initialized %d %d", TOUCH_THRESHOLD_X, TOUCH_THRESHOLD_Y);
|
||||
this->setInterval(100);
|
||||
} else {
|
||||
disable();
|
||||
this->setInterval(UINT_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
void TouchScreenBase::init(bool hasTouch)
|
||||
{
|
||||
if (hasTouch) {
|
||||
LOG_INFO("TouchScreen initialized %d %d", TOUCH_THRESHOLD_X, TOUCH_THRESHOLD_Y);
|
||||
this->setInterval(100);
|
||||
} else {
|
||||
disable();
|
||||
this->setInterval(UINT_MAX);
|
||||
}
|
||||
}
|
||||
int32_t TouchScreenBase::runOnce() {
|
||||
TouchEvent e;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_NONE);
|
||||
|
||||
int32_t TouchScreenBase::runOnce()
|
||||
{
|
||||
TouchEvent e;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_NONE);
|
||||
|
||||
// process touch events
|
||||
int16_t x, y;
|
||||
bool touched = getTouch(x, y);
|
||||
if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turing off the screen
|
||||
touched = false;
|
||||
// process touch events
|
||||
int16_t x, y;
|
||||
bool touched = getTouch(x, y);
|
||||
if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turing off the screen
|
||||
touched = false;
|
||||
if (touched) {
|
||||
this->setInterval(20);
|
||||
_last_x = x;
|
||||
_last_y = y;
|
||||
}
|
||||
if (touched != _touchedOld) {
|
||||
if (touched) {
|
||||
this->setInterval(20);
|
||||
_last_x = x;
|
||||
_last_y = y;
|
||||
}
|
||||
if (touched != _touchedOld) {
|
||||
if (touched) {
|
||||
hapticFeedback();
|
||||
_state = TOUCH_EVENT_OCCURRED;
|
||||
_start = millis();
|
||||
_first_x = x;
|
||||
_first_y = y;
|
||||
} else {
|
||||
_state = TOUCH_EVENT_CLEARED;
|
||||
time_t duration = millis() - _start;
|
||||
x = _last_x;
|
||||
y = _last_y;
|
||||
this->setInterval(50);
|
||||
hapticFeedback();
|
||||
_state = TOUCH_EVENT_OCCURRED;
|
||||
_start = millis();
|
||||
_first_x = x;
|
||||
_first_y = y;
|
||||
} else {
|
||||
_state = TOUCH_EVENT_CLEARED;
|
||||
time_t duration = millis() - _start;
|
||||
x = _last_x;
|
||||
y = _last_y;
|
||||
this->setInterval(50);
|
||||
|
||||
// compute distance
|
||||
int16_t dx = x - _first_x;
|
||||
int16_t dy = y - _first_y;
|
||||
uint16_t adx = abs(dx);
|
||||
uint16_t ady = abs(dy);
|
||||
// compute distance
|
||||
int16_t dx = x - _first_x;
|
||||
int16_t dy = y - _first_y;
|
||||
uint16_t adx = abs(dx);
|
||||
uint16_t ady = abs(dy);
|
||||
|
||||
// swipe horizontal
|
||||
if (adx > ady && adx > TOUCH_THRESHOLD_X) {
|
||||
if (0 > dx) { // swipe right to left
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_LEFT);
|
||||
LOG_DEBUG("action SWIPE: right to left");
|
||||
} else { // swipe left to right
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_RIGHT);
|
||||
LOG_DEBUG("action SWIPE: left to right");
|
||||
}
|
||||
}
|
||||
// swipe vertical
|
||||
else if (ady > adx && ady > TOUCH_THRESHOLD_Y) {
|
||||
if (0 > dy) { // swipe bottom to top
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_UP);
|
||||
LOG_DEBUG("action SWIPE: bottom to top");
|
||||
} else { // swipe top to bottom
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_DOWN);
|
||||
LOG_DEBUG("action SWIPE: top to bottom");
|
||||
}
|
||||
}
|
||||
// tap
|
||||
else {
|
||||
if (duration > 0 && duration < TIME_LONG_PRESS) {
|
||||
if (_tapped) {
|
||||
_tapped = false;
|
||||
} else {
|
||||
_tapped = true;
|
||||
}
|
||||
} else {
|
||||
_tapped = false;
|
||||
}
|
||||
}
|
||||
// swipe horizontal
|
||||
if (adx > ady && adx > TOUCH_THRESHOLD_X) {
|
||||
if (0 > dx) { // swipe right to left
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_LEFT);
|
||||
LOG_DEBUG("action SWIPE: right to left");
|
||||
} else { // swipe left to right
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_RIGHT);
|
||||
LOG_DEBUG("action SWIPE: left to right");
|
||||
}
|
||||
}
|
||||
// swipe vertical
|
||||
else if (ady > adx && ady > TOUCH_THRESHOLD_Y) {
|
||||
if (0 > dy) { // swipe bottom to top
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_UP);
|
||||
LOG_DEBUG("action SWIPE: bottom to top");
|
||||
} else { // swipe top to bottom
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_DOWN);
|
||||
LOG_DEBUG("action SWIPE: top to bottom");
|
||||
}
|
||||
}
|
||||
// tap
|
||||
else {
|
||||
if (duration > 0 && duration < TIME_LONG_PRESS) {
|
||||
if (_tapped) {
|
||||
_tapped = false;
|
||||
} else {
|
||||
_tapped = true;
|
||||
}
|
||||
} else {
|
||||
_tapped = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
_touchedOld = touched;
|
||||
}
|
||||
_touchedOld = touched;
|
||||
|
||||
#if defined RAK14014
|
||||
// Speed up the processing speed of the keyboard in virtual keyboard mode
|
||||
auto state = cannedMessageModule->getRunState();
|
||||
if (state == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
|
||||
if (_tapped) {
|
||||
_tapped = false;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_TAP);
|
||||
LOG_DEBUG("action TAP(%d/%d)", _last_x, _last_y);
|
||||
}
|
||||
} else {
|
||||
if (_tapped && (time_t(millis()) - _start) > TIME_LONG_PRESS - 50) {
|
||||
_tapped = false;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_TAP);
|
||||
LOG_DEBUG("action TAP(%d/%d)", _last_x, _last_y);
|
||||
}
|
||||
}
|
||||
#else
|
||||
// fire TAP event when no 2nd tap occured within time
|
||||
// Speed up the processing speed of the keyboard in virtual keyboard mode
|
||||
auto state = cannedMessageModule->getRunState();
|
||||
if (state == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
|
||||
if (_tapped) {
|
||||
_tapped = false;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_TAP);
|
||||
LOG_DEBUG("action TAP(%d/%d)", _last_x, _last_y);
|
||||
_tapped = false;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_TAP);
|
||||
LOG_DEBUG("action TAP(%d/%d)", _last_x, _last_y);
|
||||
}
|
||||
} else {
|
||||
if (_tapped && (time_t(millis()) - _start) > TIME_LONG_PRESS - 50) {
|
||||
_tapped = false;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_TAP);
|
||||
LOG_DEBUG("action TAP(%d/%d)", _last_x, _last_y);
|
||||
}
|
||||
}
|
||||
#else
|
||||
// fire TAP event when no 2nd tap occured within time
|
||||
if (_tapped) {
|
||||
_tapped = false;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_TAP);
|
||||
LOG_DEBUG("action TAP(%d/%d)", _last_x, _last_y);
|
||||
}
|
||||
#endif
|
||||
|
||||
// fire LONG_PRESS event without the need for release
|
||||
if (touched && (time_t(millis()) - _start) > TIME_LONG_PRESS) {
|
||||
// tricky: prevent reoccurring events and another touch event when releasing
|
||||
_start = millis() + 30000;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_LONG_PRESS);
|
||||
LOG_DEBUG("action LONG PRESS(%d/%d)", _last_x, _last_y);
|
||||
}
|
||||
// fire LONG_PRESS event without the need for release
|
||||
if (touched && (time_t(millis()) - _start) > TIME_LONG_PRESS) {
|
||||
// tricky: prevent reoccurring events and another touch event when releasing
|
||||
_start = millis() + 30000;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_LONG_PRESS);
|
||||
LOG_DEBUG("action LONG PRESS(%d/%d)", _last_x, _last_y);
|
||||
}
|
||||
|
||||
if (e.touchEvent != TOUCH_ACTION_NONE) {
|
||||
e.source = this->_originName;
|
||||
e.x = _last_x;
|
||||
e.y = _last_y;
|
||||
onEvent(e);
|
||||
}
|
||||
if (e.touchEvent != TOUCH_ACTION_NONE) {
|
||||
e.source = this->_originName;
|
||||
e.x = _last_x;
|
||||
e.y = _last_y;
|
||||
onEvent(e);
|
||||
}
|
||||
|
||||
return interval;
|
||||
return interval;
|
||||
}
|
||||
|
||||
void TouchScreenBase::hapticFeedback()
|
||||
{
|
||||
void TouchScreenBase::hapticFeedback() {
|
||||
#ifdef T_WATCH_S3
|
||||
drv.setWaveform(0, 75);
|
||||
drv.setWaveform(1, 0); // end waveform
|
||||
drv.go();
|
||||
drv.setWaveform(0, 75);
|
||||
drv.setWaveform(1, 0); // end waveform
|
||||
drv.go();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -6,50 +6,49 @@
|
||||
#include "time.h"
|
||||
|
||||
typedef struct _TouchEvent {
|
||||
const char *source;
|
||||
char touchEvent;
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
const char *source;
|
||||
char touchEvent;
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
} TouchEvent;
|
||||
|
||||
class TouchScreenBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit TouchScreenBase(const char *name, uint16_t width, uint16_t height);
|
||||
void init(bool hasTouch);
|
||||
class TouchScreenBase : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
explicit TouchScreenBase(const char *name, uint16_t width, uint16_t height);
|
||||
void init(bool hasTouch);
|
||||
|
||||
protected:
|
||||
enum TouchScreenBaseStateType { TOUCH_EVENT_OCCURRED, TOUCH_EVENT_CLEARED };
|
||||
protected:
|
||||
enum TouchScreenBaseStateType { TOUCH_EVENT_OCCURRED, TOUCH_EVENT_CLEARED };
|
||||
|
||||
enum TouchScreenBaseEventType {
|
||||
TOUCH_ACTION_NONE,
|
||||
TOUCH_ACTION_UP,
|
||||
TOUCH_ACTION_DOWN,
|
||||
TOUCH_ACTION_LEFT,
|
||||
TOUCH_ACTION_RIGHT,
|
||||
TOUCH_ACTION_TAP,
|
||||
TOUCH_ACTION_LONG_PRESS
|
||||
};
|
||||
enum TouchScreenBaseEventType {
|
||||
TOUCH_ACTION_NONE,
|
||||
TOUCH_ACTION_UP,
|
||||
TOUCH_ACTION_DOWN,
|
||||
TOUCH_ACTION_LEFT,
|
||||
TOUCH_ACTION_RIGHT,
|
||||
TOUCH_ACTION_TAP,
|
||||
TOUCH_ACTION_LONG_PRESS
|
||||
};
|
||||
|
||||
virtual int32_t runOnce() override;
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
virtual bool getTouch(int16_t &x, int16_t &y) = 0;
|
||||
virtual void onEvent(const TouchEvent &event) = 0;
|
||||
virtual bool getTouch(int16_t &x, int16_t &y) = 0;
|
||||
virtual void onEvent(const TouchEvent &event) = 0;
|
||||
|
||||
volatile TouchScreenBaseStateType _state = TOUCH_EVENT_CLEARED;
|
||||
volatile TouchScreenBaseEventType _action = TOUCH_ACTION_NONE;
|
||||
void hapticFeedback();
|
||||
volatile TouchScreenBaseStateType _state = TOUCH_EVENT_CLEARED;
|
||||
volatile TouchScreenBaseEventType _action = TOUCH_ACTION_NONE;
|
||||
void hapticFeedback();
|
||||
|
||||
protected:
|
||||
uint16_t _display_width;
|
||||
uint16_t _display_height;
|
||||
protected:
|
||||
uint16_t _display_width;
|
||||
uint16_t _display_height;
|
||||
|
||||
private:
|
||||
bool _touchedOld = false; // previous touch state
|
||||
int16_t _first_x, _last_x; // horizontal swipe direction
|
||||
int16_t _first_y, _last_y; // vertical swipe direction
|
||||
time_t _start; // for LONG_PRESS
|
||||
bool _tapped; // for DOUBLE_TAP
|
||||
private:
|
||||
bool _touchedOld = false; // previous touch state
|
||||
int16_t _first_x, _last_x; // horizontal swipe direction
|
||||
int16_t _first_y, _last_y; // vertical swipe direction
|
||||
time_t _start; // for LONG_PRESS
|
||||
bool _tapped; // for DOUBLE_TAP
|
||||
|
||||
const char *_originName;
|
||||
const char *_originName;
|
||||
};
|
||||
|
||||
@@ -11,32 +11,26 @@
|
||||
TouchScreenImpl1 *touchScreenImpl1;
|
||||
|
||||
TouchScreenImpl1::TouchScreenImpl1(uint16_t width, uint16_t height, bool (*getTouch)(int16_t *, int16_t *))
|
||||
: TouchScreenBase("touchscreen1", width, height), _getTouch(getTouch)
|
||||
{
|
||||
}
|
||||
: TouchScreenBase("touchscreen1", width, height), _getTouch(getTouch) {}
|
||||
|
||||
void TouchScreenImpl1::init()
|
||||
{
|
||||
void TouchScreenImpl1::init() {
|
||||
#if ARCH_PORTDUINO
|
||||
if (portduino_config.touchscreenModule) {
|
||||
TouchScreenBase::init(true);
|
||||
inputBroker->registerSource(this);
|
||||
} else {
|
||||
TouchScreenBase::init(false);
|
||||
}
|
||||
#elif !HAS_TOUCHSCREEN
|
||||
TouchScreenBase::init(false);
|
||||
return;
|
||||
#else
|
||||
if (portduino_config.touchscreenModule) {
|
||||
TouchScreenBase::init(true);
|
||||
inputBroker->registerSource(this);
|
||||
} else {
|
||||
TouchScreenBase::init(false);
|
||||
}
|
||||
#elif !HAS_TOUCHSCREEN
|
||||
TouchScreenBase::init(false);
|
||||
return;
|
||||
#else
|
||||
TouchScreenBase::init(true);
|
||||
inputBroker->registerSource(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool TouchScreenImpl1::getTouch(int16_t &x, int16_t &y)
|
||||
{
|
||||
return _getTouch(&x, &y);
|
||||
}
|
||||
bool TouchScreenImpl1::getTouch(int16_t &x, int16_t &y) { return _getTouch(&x, &y); }
|
||||
|
||||
/**
|
||||
* @brief forward touchscreen event
|
||||
@@ -45,41 +39,40 @@ bool TouchScreenImpl1::getTouch(int16_t &x, int16_t &y)
|
||||
*
|
||||
* The touchscreen events are translated to input events and reversed
|
||||
*/
|
||||
void TouchScreenImpl1::onEvent(const TouchEvent &event)
|
||||
{
|
||||
InputEvent e = {};
|
||||
e.source = event.source;
|
||||
e.kbchar = 0;
|
||||
e.touchX = event.x;
|
||||
e.touchY = event.y;
|
||||
void TouchScreenImpl1::onEvent(const TouchEvent &event) {
|
||||
InputEvent e = {};
|
||||
e.source = event.source;
|
||||
e.kbchar = 0;
|
||||
e.touchX = event.x;
|
||||
e.touchY = event.y;
|
||||
|
||||
switch (event.touchEvent) {
|
||||
case TOUCH_ACTION_LEFT: {
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
break;
|
||||
}
|
||||
case TOUCH_ACTION_RIGHT: {
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
break;
|
||||
}
|
||||
case TOUCH_ACTION_UP: {
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
break;
|
||||
}
|
||||
case TOUCH_ACTION_DOWN: {
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
break;
|
||||
}
|
||||
case TOUCH_ACTION_LONG_PRESS: {
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
break;
|
||||
}
|
||||
case TOUCH_ACTION_TAP: {
|
||||
e.inputEvent = INPUT_BROKER_USER_PRESS;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
this->notifyObservers(&e);
|
||||
switch (event.touchEvent) {
|
||||
case TOUCH_ACTION_LEFT: {
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
break;
|
||||
}
|
||||
case TOUCH_ACTION_RIGHT: {
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
break;
|
||||
}
|
||||
case TOUCH_ACTION_UP: {
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
break;
|
||||
}
|
||||
case TOUCH_ACTION_DOWN: {
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
break;
|
||||
}
|
||||
case TOUCH_ACTION_LONG_PRESS: {
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
break;
|
||||
}
|
||||
case TOUCH_ACTION_TAP: {
|
||||
e.inputEvent = INPUT_BROKER_USER_PRESS;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
#pragma once
|
||||
#include "TouchScreenBase.h"
|
||||
|
||||
class TouchScreenImpl1 : public TouchScreenBase
|
||||
{
|
||||
public:
|
||||
TouchScreenImpl1(uint16_t width, uint16_t height, bool (*getTouch)(int16_t *, int16_t *));
|
||||
void init(void);
|
||||
class TouchScreenImpl1 : public TouchScreenBase {
|
||||
public:
|
||||
TouchScreenImpl1(uint16_t width, uint16_t height, bool (*getTouch)(int16_t *, int16_t *));
|
||||
void init(void);
|
||||
|
||||
protected:
|
||||
virtual bool getTouch(int16_t &x, int16_t &y);
|
||||
virtual void onEvent(const TouchEvent &event);
|
||||
protected:
|
||||
virtual bool getTouch(int16_t &x, int16_t &y);
|
||||
virtual void onEvent(const TouchEvent &event);
|
||||
|
||||
bool (*_getTouch)(int16_t *, int16_t *);
|
||||
bool (*_getTouch)(int16_t *, int16_t *);
|
||||
};
|
||||
|
||||
extern TouchScreenImpl1 *touchScreenImpl1;
|
||||
|
||||
@@ -4,219 +4,201 @@ extern bool osk_found;
|
||||
|
||||
TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {}
|
||||
|
||||
void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress,
|
||||
input_broker_event eventDown, input_broker_event eventUp, input_broker_event eventLeft,
|
||||
input_broker_event eventRight, input_broker_event eventPressed,
|
||||
input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(),
|
||||
void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)())
|
||||
{
|
||||
this->_pinDown = pinDown;
|
||||
this->_pinUp = pinUp;
|
||||
this->_pinLeft = pinLeft;
|
||||
this->_pinRight = pinRight;
|
||||
this->_pinPress = pinPress;
|
||||
this->_eventDown = eventDown;
|
||||
this->_eventUp = eventUp;
|
||||
this->_eventLeft = eventLeft;
|
||||
this->_eventRight = eventRight;
|
||||
this->_eventPressed = eventPressed;
|
||||
this->_eventPressedLong = eventPressedLong;
|
||||
void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress, input_broker_event eventDown,
|
||||
input_broker_event eventUp, input_broker_event eventLeft, input_broker_event eventRight,
|
||||
input_broker_event eventPressed, input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(),
|
||||
void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)()) {
|
||||
this->_pinDown = pinDown;
|
||||
this->_pinUp = pinUp;
|
||||
this->_pinLeft = pinLeft;
|
||||
this->_pinRight = pinRight;
|
||||
this->_pinPress = pinPress;
|
||||
this->_eventDown = eventDown;
|
||||
this->_eventUp = eventUp;
|
||||
this->_eventLeft = eventLeft;
|
||||
this->_eventRight = eventRight;
|
||||
this->_eventPressed = eventPressed;
|
||||
this->_eventPressedLong = eventPressedLong;
|
||||
|
||||
if (pinPress != 255) {
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
attachInterrupt(pinPress, onIntPress, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinDown != 255) {
|
||||
pinMode(this->_pinDown, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinDown, onIntDown, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinUp != 255) {
|
||||
pinMode(this->_pinUp, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinUp, onIntUp, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinLeft != 255) {
|
||||
pinMode(this->_pinLeft, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinLeft, onIntLeft, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinRight != 255) {
|
||||
pinMode(this->_pinRight, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinRight, onIntRight, TB_DIRECTION);
|
||||
}
|
||||
if (pinPress != 255) {
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
attachInterrupt(pinPress, onIntPress, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinDown != 255) {
|
||||
pinMode(this->_pinDown, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinDown, onIntDown, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinUp != 255) {
|
||||
pinMode(this->_pinUp, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinUp, onIntUp, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinLeft != 255) {
|
||||
pinMode(this->_pinLeft, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinLeft, onIntLeft, TB_DIRECTION);
|
||||
}
|
||||
if (this->_pinRight != 255) {
|
||||
pinMode(this->_pinRight, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinRight, onIntRight, TB_DIRECTION);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Trackball GPIO initialized - UP:%d DOWN:%d LEFT:%d RIGHT:%d PRESS:%d", this->_pinUp, this->_pinDown,
|
||||
this->_pinLeft, this->_pinRight, pinPress);
|
||||
LOG_DEBUG("Trackball GPIO initialized - UP:%d DOWN:%d LEFT:%d RIGHT:%d PRESS:%d", this->_pinUp, this->_pinDown, this->_pinLeft, this->_pinRight,
|
||||
pinPress);
|
||||
#ifndef HAS_PHYSICAL_KEYBOARD
|
||||
osk_found = true;
|
||||
osk_found = true;
|
||||
#endif
|
||||
this->setInterval(100);
|
||||
this->setInterval(100);
|
||||
}
|
||||
|
||||
int32_t TrackballInterruptBase::runOnce()
|
||||
{
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
int32_t TrackballInterruptBase::runOnce() {
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
|
||||
// Handle long press detection for press button
|
||||
if (pressDetected && pressStartTime > 0) {
|
||||
uint32_t pressDuration = millis() - pressStartTime;
|
||||
bool buttonStillPressed = false;
|
||||
// Handle long press detection for press button
|
||||
if (pressDetected && pressStartTime > 0) {
|
||||
uint32_t pressDuration = millis() - pressStartTime;
|
||||
bool buttonStillPressed = false;
|
||||
|
||||
#if defined(T_DECK)
|
||||
buttonStillPressed = (this->action == TB_ACTION_PRESSED);
|
||||
buttonStillPressed = (this->action == TB_ACTION_PRESSED);
|
||||
#else
|
||||
buttonStillPressed = !digitalRead(_pinPress);
|
||||
buttonStillPressed = !digitalRead(_pinPress);
|
||||
#endif
|
||||
|
||||
if (!buttonStillPressed) {
|
||||
// Button released
|
||||
if (pressDuration < LONG_PRESS_DURATION) {
|
||||
// Short press
|
||||
e.inputEvent = this->_eventPressed;
|
||||
}
|
||||
// Reset state
|
||||
pressDetected = false;
|
||||
pressStartTime = 0;
|
||||
lastLongPressEventTime = 0;
|
||||
this->action = TB_ACTION_NONE;
|
||||
} else if (pressDuration >= LONG_PRESS_DURATION) {
|
||||
// Long press detected
|
||||
uint32_t currentTime = millis();
|
||||
// Only trigger long press event if enough time has passed since the last one
|
||||
if (lastLongPressEventTime == 0 || (currentTime - lastLongPressEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
|
||||
e.inputEvent = this->_eventPressedLong;
|
||||
lastLongPressEventTime = currentTime;
|
||||
}
|
||||
this->action = TB_ACTION_PRESSED_LONG;
|
||||
}
|
||||
if (!buttonStillPressed) {
|
||||
// Button released
|
||||
if (pressDuration < LONG_PRESS_DURATION) {
|
||||
// Short press
|
||||
e.inputEvent = this->_eventPressed;
|
||||
}
|
||||
// Reset state
|
||||
pressDetected = false;
|
||||
pressStartTime = 0;
|
||||
lastLongPressEventTime = 0;
|
||||
this->action = TB_ACTION_NONE;
|
||||
} else if (pressDuration >= LONG_PRESS_DURATION) {
|
||||
// Long press detected
|
||||
uint32_t currentTime = millis();
|
||||
// Only trigger long press event if enough time has passed since the last one
|
||||
if (lastLongPressEventTime == 0 || (currentTime - lastLongPressEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
|
||||
e.inputEvent = this->_eventPressedLong;
|
||||
lastLongPressEventTime = currentTime;
|
||||
}
|
||||
this->action = TB_ACTION_PRESSED_LONG;
|
||||
}
|
||||
}
|
||||
|
||||
if (directionDetected && directionStartTime > 0) {
|
||||
uint32_t directionDuration = millis() - directionStartTime;
|
||||
uint8_t directionPressedNow = 0;
|
||||
directionInterval++;
|
||||
|
||||
if (!digitalRead(_pinUp)) {
|
||||
directionPressedNow = TB_ACTION_UP;
|
||||
} else if (!digitalRead(_pinDown)) {
|
||||
directionPressedNow = TB_ACTION_DOWN;
|
||||
} else if (!digitalRead(_pinLeft)) {
|
||||
directionPressedNow = TB_ACTION_LEFT;
|
||||
} else if (!digitalRead(_pinRight)) {
|
||||
directionPressedNow = TB_ACTION_RIGHT;
|
||||
}
|
||||
|
||||
if (directionDetected && directionStartTime > 0) {
|
||||
uint32_t directionDuration = millis() - directionStartTime;
|
||||
uint8_t directionPressedNow = 0;
|
||||
directionInterval++;
|
||||
const uint8_t DIRECTION_REPEAT_THRESHOLD = 3;
|
||||
|
||||
if (!digitalRead(_pinUp)) {
|
||||
directionPressedNow = TB_ACTION_UP;
|
||||
} else if (!digitalRead(_pinDown)) {
|
||||
directionPressedNow = TB_ACTION_DOWN;
|
||||
} else if (!digitalRead(_pinLeft)) {
|
||||
directionPressedNow = TB_ACTION_LEFT;
|
||||
} else if (!digitalRead(_pinRight)) {
|
||||
directionPressedNow = TB_ACTION_RIGHT;
|
||||
}
|
||||
if (directionPressedNow == TB_ACTION_NONE) {
|
||||
// Reset state
|
||||
directionDetected = false;
|
||||
directionStartTime = 0;
|
||||
directionInterval = 0;
|
||||
this->action = TB_ACTION_NONE;
|
||||
} else if (directionDuration >= LONG_PRESS_DURATION && directionInterval >= DIRECTION_REPEAT_THRESHOLD) {
|
||||
// repeat event when long press these direction.
|
||||
switch (directionPressedNow) {
|
||||
case TB_ACTION_UP:
|
||||
e.inputEvent = this->_eventUp;
|
||||
break;
|
||||
case TB_ACTION_DOWN:
|
||||
e.inputEvent = this->_eventDown;
|
||||
break;
|
||||
case TB_ACTION_LEFT:
|
||||
e.inputEvent = this->_eventLeft;
|
||||
break;
|
||||
case TB_ACTION_RIGHT:
|
||||
e.inputEvent = this->_eventRight;
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t DIRECTION_REPEAT_THRESHOLD = 3;
|
||||
|
||||
if (directionPressedNow == TB_ACTION_NONE) {
|
||||
// Reset state
|
||||
directionDetected = false;
|
||||
directionStartTime = 0;
|
||||
directionInterval = 0;
|
||||
this->action = TB_ACTION_NONE;
|
||||
} else if (directionDuration >= LONG_PRESS_DURATION && directionInterval >= DIRECTION_REPEAT_THRESHOLD) {
|
||||
// repeat event when long press these direction.
|
||||
switch (directionPressedNow) {
|
||||
case TB_ACTION_UP:
|
||||
e.inputEvent = this->_eventUp;
|
||||
break;
|
||||
case TB_ACTION_DOWN:
|
||||
e.inputEvent = this->_eventDown;
|
||||
break;
|
||||
case TB_ACTION_LEFT:
|
||||
e.inputEvent = this->_eventLeft;
|
||||
break;
|
||||
case TB_ACTION_RIGHT:
|
||||
e.inputEvent = this->_eventRight;
|
||||
break;
|
||||
}
|
||||
|
||||
directionInterval = 0;
|
||||
}
|
||||
directionInterval = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball
|
||||
if (this->action == TB_ACTION_PRESSED && !pressDetected) {
|
||||
// Start long press detection
|
||||
pressDetected = true;
|
||||
pressStartTime = millis();
|
||||
// Don't send event yet, wait to see if it's a long press
|
||||
} else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) {
|
||||
// LOG_DEBUG("Trackball event UP");
|
||||
e.inputEvent = this->_eventUp;
|
||||
} else if (this->action == TB_ACTION_DOWN && lastEvent == TB_ACTION_DOWN) {
|
||||
// LOG_DEBUG("Trackball event DOWN");
|
||||
e.inputEvent = this->_eventDown;
|
||||
} else if (this->action == TB_ACTION_LEFT && lastEvent == TB_ACTION_LEFT) {
|
||||
// LOG_DEBUG("Trackball event LEFT");
|
||||
e.inputEvent = this->_eventLeft;
|
||||
} else if (this->action == TB_ACTION_RIGHT && lastEvent == TB_ACTION_RIGHT) {
|
||||
// LOG_DEBUG("Trackball event RIGHT");
|
||||
e.inputEvent = this->_eventRight;
|
||||
}
|
||||
if (this->action == TB_ACTION_PRESSED && !pressDetected) {
|
||||
// Start long press detection
|
||||
pressDetected = true;
|
||||
pressStartTime = millis();
|
||||
// Don't send event yet, wait to see if it's a long press
|
||||
} else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) {
|
||||
// LOG_DEBUG("Trackball event UP");
|
||||
e.inputEvent = this->_eventUp;
|
||||
} else if (this->action == TB_ACTION_DOWN && lastEvent == TB_ACTION_DOWN) {
|
||||
// LOG_DEBUG("Trackball event DOWN");
|
||||
e.inputEvent = this->_eventDown;
|
||||
} else if (this->action == TB_ACTION_LEFT && lastEvent == TB_ACTION_LEFT) {
|
||||
// LOG_DEBUG("Trackball event LEFT");
|
||||
e.inputEvent = this->_eventLeft;
|
||||
} else if (this->action == TB_ACTION_RIGHT && lastEvent == TB_ACTION_RIGHT) {
|
||||
// LOG_DEBUG("Trackball event RIGHT");
|
||||
e.inputEvent = this->_eventRight;
|
||||
}
|
||||
#else
|
||||
if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress) && !pressDetected) {
|
||||
// Start long press detection
|
||||
pressDetected = true;
|
||||
pressStartTime = millis();
|
||||
// Don't send event yet, wait to see if it's a long press
|
||||
} else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp) && !directionDetected) {
|
||||
directionDetected = true;
|
||||
directionStartTime = millis();
|
||||
e.inputEvent = this->_eventUp;
|
||||
// send event first,will automatically trigger every 50ms * 3 after 500ms
|
||||
} else if (this->action == TB_ACTION_DOWN && !digitalRead(_pinDown) && !directionDetected) {
|
||||
directionDetected = true;
|
||||
directionStartTime = millis();
|
||||
e.inputEvent = this->_eventDown;
|
||||
} else if (this->action == TB_ACTION_LEFT && !digitalRead(_pinLeft) && !directionDetected) {
|
||||
directionDetected = true;
|
||||
directionStartTime = millis();
|
||||
e.inputEvent = this->_eventLeft;
|
||||
} else if (this->action == TB_ACTION_RIGHT && !digitalRead(_pinRight) && !directionDetected) {
|
||||
directionDetected = true;
|
||||
directionStartTime = millis();
|
||||
e.inputEvent = this->_eventRight;
|
||||
}
|
||||
if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress) && !pressDetected) {
|
||||
// Start long press detection
|
||||
pressDetected = true;
|
||||
pressStartTime = millis();
|
||||
// Don't send event yet, wait to see if it's a long press
|
||||
} else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp) && !directionDetected) {
|
||||
directionDetected = true;
|
||||
directionStartTime = millis();
|
||||
e.inputEvent = this->_eventUp;
|
||||
// send event first,will automatically trigger every 50ms * 3 after 500ms
|
||||
} else if (this->action == TB_ACTION_DOWN && !digitalRead(_pinDown) && !directionDetected) {
|
||||
directionDetected = true;
|
||||
directionStartTime = millis();
|
||||
e.inputEvent = this->_eventDown;
|
||||
} else if (this->action == TB_ACTION_LEFT && !digitalRead(_pinLeft) && !directionDetected) {
|
||||
directionDetected = true;
|
||||
directionStartTime = millis();
|
||||
e.inputEvent = this->_eventLeft;
|
||||
} else if (this->action == TB_ACTION_RIGHT && !digitalRead(_pinRight) && !directionDetected) {
|
||||
directionDetected = true;
|
||||
directionStartTime = millis();
|
||||
e.inputEvent = this->_eventRight;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
e.source = this->_originName;
|
||||
e.kbchar = 0x00;
|
||||
this->notifyObservers(&e);
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
e.source = this->_originName;
|
||||
e.kbchar = 0x00;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
// Only update lastEvent for non-press actions or completed press actions
|
||||
if (this->action != TB_ACTION_PRESSED || !pressDetected) {
|
||||
lastEvent = action;
|
||||
if (!pressDetected) {
|
||||
this->action = TB_ACTION_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
// Only update lastEvent for non-press actions or completed press actions
|
||||
if (this->action != TB_ACTION_PRESSED || !pressDetected) {
|
||||
lastEvent = action;
|
||||
if (!pressDetected) {
|
||||
this->action = TB_ACTION_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
return 50; // Check more frequently for better long press detection
|
||||
return 50; // Check more frequently for better long press detection
|
||||
}
|
||||
|
||||
void TrackballInterruptBase::intPressHandler()
|
||||
{
|
||||
this->action = TB_ACTION_PRESSED;
|
||||
}
|
||||
void TrackballInterruptBase::intPressHandler() { this->action = TB_ACTION_PRESSED; }
|
||||
|
||||
void TrackballInterruptBase::intDownHandler()
|
||||
{
|
||||
this->action = TB_ACTION_DOWN;
|
||||
}
|
||||
void TrackballInterruptBase::intDownHandler() { this->action = TB_ACTION_DOWN; }
|
||||
|
||||
void TrackballInterruptBase::intUpHandler()
|
||||
{
|
||||
this->action = TB_ACTION_UP;
|
||||
}
|
||||
void TrackballInterruptBase::intUpHandler() { this->action = TB_ACTION_UP; }
|
||||
|
||||
void TrackballInterruptBase::intLeftHandler()
|
||||
{
|
||||
this->action = TB_ACTION_LEFT;
|
||||
}
|
||||
void TrackballInterruptBase::intLeftHandler() { this->action = TB_ACTION_LEFT; }
|
||||
|
||||
void TrackballInterruptBase::intRightHandler()
|
||||
{
|
||||
this->action = TB_ACTION_RIGHT;
|
||||
}
|
||||
void TrackballInterruptBase::intRightHandler() { this->action = TB_ACTION_RIGHT; }
|
||||
|
||||
@@ -12,59 +12,58 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class TrackballInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit TrackballInterruptBase(const char *name);
|
||||
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress, input_broker_event eventDown,
|
||||
input_broker_event eventUp, input_broker_event eventLeft, input_broker_event eventRight,
|
||||
input_broker_event eventPressed, input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(),
|
||||
void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)());
|
||||
void intPressHandler();
|
||||
void intDownHandler();
|
||||
void intUpHandler();
|
||||
void intLeftHandler();
|
||||
void intRightHandler();
|
||||
uint32_t lastTime = 0;
|
||||
class TrackballInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
explicit TrackballInterruptBase(const char *name);
|
||||
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress, input_broker_event eventDown,
|
||||
input_broker_event eventUp, input_broker_event eventLeft, input_broker_event eventRight, input_broker_event eventPressed,
|
||||
input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(), void (*onIntLeft)(), void (*onIntRight)(),
|
||||
void (*onIntPress)());
|
||||
void intPressHandler();
|
||||
void intDownHandler();
|
||||
void intUpHandler();
|
||||
void intLeftHandler();
|
||||
void intRightHandler();
|
||||
uint32_t lastTime = 0;
|
||||
|
||||
virtual int32_t runOnce() override;
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
protected:
|
||||
enum TrackballInterruptBaseActionType {
|
||||
TB_ACTION_NONE,
|
||||
TB_ACTION_PRESSED,
|
||||
TB_ACTION_PRESSED_LONG,
|
||||
TB_ACTION_UP,
|
||||
TB_ACTION_DOWN,
|
||||
TB_ACTION_LEFT,
|
||||
TB_ACTION_RIGHT
|
||||
};
|
||||
uint8_t _pinDown = 0;
|
||||
uint8_t _pinUp = 0;
|
||||
uint8_t _pinLeft = 0;
|
||||
uint8_t _pinRight = 0;
|
||||
uint8_t _pinPress = 0;
|
||||
protected:
|
||||
enum TrackballInterruptBaseActionType {
|
||||
TB_ACTION_NONE,
|
||||
TB_ACTION_PRESSED,
|
||||
TB_ACTION_PRESSED_LONG,
|
||||
TB_ACTION_UP,
|
||||
TB_ACTION_DOWN,
|
||||
TB_ACTION_LEFT,
|
||||
TB_ACTION_RIGHT
|
||||
};
|
||||
uint8_t _pinDown = 0;
|
||||
uint8_t _pinUp = 0;
|
||||
uint8_t _pinLeft = 0;
|
||||
uint8_t _pinRight = 0;
|
||||
uint8_t _pinPress = 0;
|
||||
|
||||
volatile TrackballInterruptBaseActionType action = TB_ACTION_NONE;
|
||||
volatile TrackballInterruptBaseActionType action = TB_ACTION_NONE;
|
||||
|
||||
// Long press detection for press button
|
||||
uint32_t pressStartTime = 0;
|
||||
uint32_t directionStartTime = 0;
|
||||
uint8_t directionInterval = 0;
|
||||
bool pressDetected = false;
|
||||
bool directionDetected = false;
|
||||
uint32_t lastLongPressEventTime = 0;
|
||||
uint32_t lastDirectionPressEventTime = 0;
|
||||
static const uint32_t LONG_PRESS_DURATION = 500; // ms
|
||||
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300; // ms - interval between repeated long press events
|
||||
// Long press detection for press button
|
||||
uint32_t pressStartTime = 0;
|
||||
uint32_t directionStartTime = 0;
|
||||
uint8_t directionInterval = 0;
|
||||
bool pressDetected = false;
|
||||
bool directionDetected = false;
|
||||
uint32_t lastLongPressEventTime = 0;
|
||||
uint32_t lastDirectionPressEventTime = 0;
|
||||
static const uint32_t LONG_PRESS_DURATION = 500; // ms
|
||||
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300; // ms - interval between repeated long press events
|
||||
|
||||
private:
|
||||
input_broker_event _eventDown = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventUp = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventLeft = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventRight = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressed = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
||||
const char *_originName;
|
||||
TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE;
|
||||
private:
|
||||
input_broker_event _eventDown = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventUp = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventLeft = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventRight = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressed = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
||||
const char *_originName;
|
||||
TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE;
|
||||
};
|
||||
|
||||
@@ -6,59 +6,52 @@ TrackballInterruptImpl1 *trackballInterruptImpl1;
|
||||
|
||||
TrackballInterruptImpl1::TrackballInterruptImpl1() : TrackballInterruptBase("trackball1") {}
|
||||
|
||||
void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress)
|
||||
{
|
||||
input_broker_event eventDown = INPUT_BROKER_DOWN;
|
||||
input_broker_event eventUp = INPUT_BROKER_UP;
|
||||
input_broker_event eventLeft = INPUT_BROKER_LEFT;
|
||||
input_broker_event eventRight = INPUT_BROKER_RIGHT;
|
||||
input_broker_event eventPressed = INPUT_BROKER_SELECT;
|
||||
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
|
||||
void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress) {
|
||||
input_broker_event eventDown = INPUT_BROKER_DOWN;
|
||||
input_broker_event eventUp = INPUT_BROKER_UP;
|
||||
input_broker_event eventLeft = INPUT_BROKER_LEFT;
|
||||
input_broker_event eventRight = INPUT_BROKER_RIGHT;
|
||||
input_broker_event eventPressed = INPUT_BROKER_SELECT;
|
||||
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
|
||||
|
||||
TrackballInterruptBase::init(pinDown, pinUp, pinLeft, pinRight, pinPress, eventDown, eventUp, eventLeft, eventRight,
|
||||
eventPressed, eventPressedLong, TrackballInterruptImpl1::handleIntDown,
|
||||
TrackballInterruptImpl1::handleIntUp, TrackballInterruptImpl1::handleIntLeft,
|
||||
TrackballInterruptImpl1::handleIntRight, TrackballInterruptImpl1::handleIntPressed);
|
||||
inputBroker->registerSource(this);
|
||||
TrackballInterruptBase::init(pinDown, pinUp, pinLeft, pinRight, pinPress, eventDown, eventUp, eventLeft, eventRight, eventPressed, eventPressedLong,
|
||||
TrackballInterruptImpl1::handleIntDown, TrackballInterruptImpl1::handleIntUp, TrackballInterruptImpl1::handleIntLeft,
|
||||
TrackballInterruptImpl1::handleIntRight, TrackballInterruptImpl1::handleIntPressed);
|
||||
inputBroker->registerSource(this);
|
||||
}
|
||||
|
||||
void TrackballInterruptImpl1::handleIntDown()
|
||||
{
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intDownHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntDown() {
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intDownHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntUp()
|
||||
{
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intUpHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntUp() {
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intUpHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntLeft()
|
||||
{
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intLeftHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntLeft() {
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intLeftHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntRight()
|
||||
{
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intRightHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntRight() {
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intRightHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntPressed()
|
||||
{
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intPressHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntPressed() {
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intPressHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#pragma once
|
||||
#include "TrackballInterruptBase.h"
|
||||
|
||||
class TrackballInterruptImpl1 : public TrackballInterruptBase
|
||||
{
|
||||
public:
|
||||
TrackballInterruptImpl1();
|
||||
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress);
|
||||
static void handleIntDown();
|
||||
static void handleIntUp();
|
||||
static void handleIntLeft();
|
||||
static void handleIntRight();
|
||||
static void handleIntPressed();
|
||||
class TrackballInterruptImpl1 : public TrackballInterruptBase {
|
||||
public:
|
||||
TrackballInterruptImpl1();
|
||||
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress);
|
||||
static void handleIntDown();
|
||||
static void handleIntUp();
|
||||
static void handleIntLeft();
|
||||
static void handleIntRight();
|
||||
static void handleIntPressed();
|
||||
};
|
||||
|
||||
extern TrackballInterruptImpl1 *trackballInterruptImpl1;
|
||||
|
||||
@@ -1,165 +1,151 @@
|
||||
#include "UpDownInterruptBase.h"
|
||||
#include "configuration.h"
|
||||
|
||||
UpDownInterruptBase::UpDownInterruptBase(const char *name) : concurrency::OSThread(name)
|
||||
{
|
||||
this->_originName = name;
|
||||
}
|
||||
UpDownInterruptBase::UpDownInterruptBase(const char *name) : concurrency::OSThread(name) { this->_originName = name; }
|
||||
|
||||
void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown,
|
||||
input_broker_event eventUp, input_broker_event eventPressed, input_broker_event eventPressedLong,
|
||||
input_broker_event eventUpLong, input_broker_event eventDownLong, void (*onIntDown)(),
|
||||
void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs)
|
||||
{
|
||||
this->_pinDown = pinDown;
|
||||
this->_pinUp = pinUp;
|
||||
this->_pinPress = pinPress;
|
||||
this->_eventDown = eventDown;
|
||||
this->_eventUp = eventUp;
|
||||
this->_eventPressed = eventPressed;
|
||||
this->_eventPressedLong = eventPressedLong;
|
||||
this->_eventUpLong = eventUpLong;
|
||||
this->_eventDownLong = eventDownLong;
|
||||
void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp,
|
||||
input_broker_event eventPressed, input_broker_event eventPressedLong, input_broker_event eventUpLong,
|
||||
input_broker_event eventDownLong, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(),
|
||||
unsigned long updownDebounceMs) {
|
||||
this->_pinDown = pinDown;
|
||||
this->_pinUp = pinUp;
|
||||
this->_pinPress = pinPress;
|
||||
this->_eventDown = eventDown;
|
||||
this->_eventUp = eventUp;
|
||||
this->_eventPressed = eventPressed;
|
||||
this->_eventPressedLong = eventPressedLong;
|
||||
this->_eventUpLong = eventUpLong;
|
||||
this->_eventDownLong = eventDownLong;
|
||||
|
||||
// Store debounce configuration passed by caller
|
||||
this->updownDebounceMs = updownDebounceMs;
|
||||
bool isRAK = false;
|
||||
// Store debounce configuration passed by caller
|
||||
this->updownDebounceMs = updownDebounceMs;
|
||||
bool isRAK = false;
|
||||
#ifdef RAK_4631
|
||||
isRAK = true;
|
||||
isRAK = true;
|
||||
#endif
|
||||
|
||||
if (!isRAK || pinPress != 0) {
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
attachInterrupt(pinPress, onIntPress, FALLING);
|
||||
}
|
||||
if (!isRAK || this->_pinDown != 0) {
|
||||
pinMode(this->_pinDown, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinDown, onIntDown, FALLING);
|
||||
}
|
||||
if (!isRAK || this->_pinUp != 0) {
|
||||
pinMode(this->_pinUp, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinUp, onIntUp, FALLING);
|
||||
}
|
||||
if (!isRAK || pinPress != 0) {
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
attachInterrupt(pinPress, onIntPress, FALLING);
|
||||
}
|
||||
if (!isRAK || this->_pinDown != 0) {
|
||||
pinMode(this->_pinDown, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinDown, onIntDown, FALLING);
|
||||
}
|
||||
if (!isRAK || this->_pinUp != 0) {
|
||||
pinMode(this->_pinUp, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinUp, onIntUp, FALLING);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress);
|
||||
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress);
|
||||
|
||||
this->setInterval(20);
|
||||
this->setInterval(20);
|
||||
}
|
||||
|
||||
int32_t UpDownInterruptBase::runOnce()
|
||||
{
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
unsigned long now = millis();
|
||||
int32_t UpDownInterruptBase::runOnce() {
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
unsigned long now = millis();
|
||||
|
||||
// Read all button states once at the beginning
|
||||
bool pressButtonPressed = !digitalRead(_pinPress);
|
||||
bool upButtonPressed = !digitalRead(_pinUp);
|
||||
bool downButtonPressed = !digitalRead(_pinDown);
|
||||
// Read all button states once at the beginning
|
||||
bool pressButtonPressed = !digitalRead(_pinPress);
|
||||
bool upButtonPressed = !digitalRead(_pinUp);
|
||||
bool downButtonPressed = !digitalRead(_pinDown);
|
||||
|
||||
// Handle initial button press detection - only if not already detected
|
||||
if (this->action == UPDOWN_ACTION_PRESSED && pressButtonPressed && !pressDetected) {
|
||||
pressDetected = true;
|
||||
pressStartTime = now;
|
||||
} else if (this->action == UPDOWN_ACTION_UP && upButtonPressed && !upDetected) {
|
||||
upDetected = true;
|
||||
upStartTime = now;
|
||||
} else if (this->action == UPDOWN_ACTION_DOWN && downButtonPressed && !downDetected) {
|
||||
downDetected = true;
|
||||
downStartTime = now;
|
||||
// Handle initial button press detection - only if not already detected
|
||||
if (this->action == UPDOWN_ACTION_PRESSED && pressButtonPressed && !pressDetected) {
|
||||
pressDetected = true;
|
||||
pressStartTime = now;
|
||||
} else if (this->action == UPDOWN_ACTION_UP && upButtonPressed && !upDetected) {
|
||||
upDetected = true;
|
||||
upStartTime = now;
|
||||
} else if (this->action == UPDOWN_ACTION_DOWN && downButtonPressed && !downDetected) {
|
||||
downDetected = true;
|
||||
downStartTime = now;
|
||||
}
|
||||
|
||||
// Handle long press detection for press button
|
||||
if (pressDetected && pressStartTime > 0) {
|
||||
uint32_t pressDuration = now - pressStartTime;
|
||||
|
||||
if (!pressButtonPressed) {
|
||||
// Button released
|
||||
if (pressDuration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) {
|
||||
lastPressKeyTime = now;
|
||||
e.inputEvent = this->_eventPressed;
|
||||
}
|
||||
// Reset state
|
||||
pressDetected = false;
|
||||
pressStartTime = 0;
|
||||
lastPressLongEventTime = 0;
|
||||
} else if (pressDuration >= LONG_PRESS_DURATION && lastPressLongEventTime == 0) {
|
||||
// First long press event only - avoid repeated events causing lag
|
||||
e.inputEvent = this->_eventPressedLong;
|
||||
lastPressLongEventTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle long press detection for press button
|
||||
if (pressDetected && pressStartTime > 0) {
|
||||
uint32_t pressDuration = now - pressStartTime;
|
||||
// Handle long press detection for up button
|
||||
if (upDetected && upStartTime > 0) {
|
||||
uint32_t upDuration = now - upStartTime;
|
||||
|
||||
if (!pressButtonPressed) {
|
||||
// Button released
|
||||
if (pressDuration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) {
|
||||
lastPressKeyTime = now;
|
||||
e.inputEvent = this->_eventPressed;
|
||||
}
|
||||
// Reset state
|
||||
pressDetected = false;
|
||||
pressStartTime = 0;
|
||||
lastPressLongEventTime = 0;
|
||||
} else if (pressDuration >= LONG_PRESS_DURATION && lastPressLongEventTime == 0) {
|
||||
// First long press event only - avoid repeated events causing lag
|
||||
e.inputEvent = this->_eventPressedLong;
|
||||
lastPressLongEventTime = now;
|
||||
}
|
||||
if (!upButtonPressed) {
|
||||
// Button released
|
||||
if (upDuration < LONG_PRESS_DURATION && now - lastUpKeyTime >= updownDebounceMs) {
|
||||
lastUpKeyTime = now;
|
||||
e.inputEvent = this->_eventUp;
|
||||
}
|
||||
// Reset state
|
||||
upDetected = false;
|
||||
upStartTime = 0;
|
||||
lastUpLongEventTime = 0;
|
||||
} else if (upDuration >= LONG_PRESS_DURATION) {
|
||||
// Auto-repeat long press events
|
||||
if (lastUpLongEventTime == 0 || (now - lastUpLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
|
||||
e.inputEvent = this->_eventUpLong;
|
||||
lastUpLongEventTime = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle long press detection for up button
|
||||
if (upDetected && upStartTime > 0) {
|
||||
uint32_t upDuration = now - upStartTime;
|
||||
// Handle long press detection for down button
|
||||
if (downDetected && downStartTime > 0) {
|
||||
uint32_t downDuration = now - downStartTime;
|
||||
|
||||
if (!upButtonPressed) {
|
||||
// Button released
|
||||
if (upDuration < LONG_PRESS_DURATION && now - lastUpKeyTime >= updownDebounceMs) {
|
||||
lastUpKeyTime = now;
|
||||
e.inputEvent = this->_eventUp;
|
||||
}
|
||||
// Reset state
|
||||
upDetected = false;
|
||||
upStartTime = 0;
|
||||
lastUpLongEventTime = 0;
|
||||
} else if (upDuration >= LONG_PRESS_DURATION) {
|
||||
// Auto-repeat long press events
|
||||
if (lastUpLongEventTime == 0 || (now - lastUpLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
|
||||
e.inputEvent = this->_eventUpLong;
|
||||
lastUpLongEventTime = now;
|
||||
}
|
||||
}
|
||||
if (!downButtonPressed) {
|
||||
// Button released
|
||||
if (downDuration < LONG_PRESS_DURATION && now - lastDownKeyTime >= updownDebounceMs) {
|
||||
lastDownKeyTime = now;
|
||||
e.inputEvent = this->_eventDown;
|
||||
}
|
||||
// Reset state
|
||||
downDetected = false;
|
||||
downStartTime = 0;
|
||||
lastDownLongEventTime = 0;
|
||||
} else if (downDuration >= LONG_PRESS_DURATION) {
|
||||
// Auto-repeat long press events
|
||||
if (lastDownLongEventTime == 0 || (now - lastDownLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
|
||||
e.inputEvent = this->_eventDownLong;
|
||||
lastDownLongEventTime = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle long press detection for down button
|
||||
if (downDetected && downStartTime > 0) {
|
||||
uint32_t downDuration = now - downStartTime;
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
e.source = this->_originName;
|
||||
e.kbchar = INPUT_BROKER_NONE;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
if (!downButtonPressed) {
|
||||
// Button released
|
||||
if (downDuration < LONG_PRESS_DURATION && now - lastDownKeyTime >= updownDebounceMs) {
|
||||
lastDownKeyTime = now;
|
||||
e.inputEvent = this->_eventDown;
|
||||
}
|
||||
// Reset state
|
||||
downDetected = false;
|
||||
downStartTime = 0;
|
||||
lastDownLongEventTime = 0;
|
||||
} else if (downDuration >= LONG_PRESS_DURATION) {
|
||||
// Auto-repeat long press events
|
||||
if (lastDownLongEventTime == 0 || (now - lastDownLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
|
||||
e.inputEvent = this->_eventDownLong;
|
||||
lastDownLongEventTime = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!pressDetected && !upDetected && !downDetected) {
|
||||
this->action = UPDOWN_ACTION_NONE;
|
||||
}
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
e.source = this->_originName;
|
||||
e.kbchar = INPUT_BROKER_NONE;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
if (!pressDetected && !upDetected && !downDetected) {
|
||||
this->action = UPDOWN_ACTION_NONE;
|
||||
}
|
||||
|
||||
return 20; // This will control how the input frequency
|
||||
return 20; // This will control how the input frequency
|
||||
}
|
||||
|
||||
void UpDownInterruptBase::intPressHandler()
|
||||
{
|
||||
this->action = UPDOWN_ACTION_PRESSED;
|
||||
}
|
||||
void UpDownInterruptBase::intPressHandler() { this->action = UPDOWN_ACTION_PRESSED; }
|
||||
|
||||
void UpDownInterruptBase::intDownHandler()
|
||||
{
|
||||
this->action = UPDOWN_ACTION_DOWN;
|
||||
}
|
||||
void UpDownInterruptBase::intDownHandler() { this->action = UPDOWN_ACTION_DOWN; }
|
||||
|
||||
void UpDownInterruptBase::intUpHandler()
|
||||
{
|
||||
this->action = UPDOWN_ACTION_UP;
|
||||
}
|
||||
void UpDownInterruptBase::intUpHandler() { this->action = UPDOWN_ACTION_UP; }
|
||||
|
||||
@@ -11,61 +11,59 @@
|
||||
#define UPDOWN_LONG_PRESS_REPEAT_INTERVAL 300
|
||||
#endif
|
||||
|
||||
class UpDownInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit UpDownInterruptBase(const char *name);
|
||||
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp,
|
||||
input_broker_event eventPressed, input_broker_event eventPressedLong, input_broker_event eventUpLong,
|
||||
input_broker_event eventDownLong, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(),
|
||||
unsigned long updownDebounceMs = 50);
|
||||
void intPressHandler();
|
||||
void intDownHandler();
|
||||
void intUpHandler();
|
||||
class UpDownInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
explicit UpDownInterruptBase(const char *name);
|
||||
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp,
|
||||
input_broker_event eventPressed, input_broker_event eventPressedLong, input_broker_event eventUpLong, input_broker_event eventDownLong,
|
||||
void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs = 50);
|
||||
void intPressHandler();
|
||||
void intDownHandler();
|
||||
void intUpHandler();
|
||||
|
||||
int32_t runOnce() override;
|
||||
int32_t runOnce() override;
|
||||
|
||||
protected:
|
||||
enum UpDownInterruptBaseActionType {
|
||||
UPDOWN_ACTION_NONE,
|
||||
UPDOWN_ACTION_PRESSED,
|
||||
UPDOWN_ACTION_PRESSED_LONG,
|
||||
UPDOWN_ACTION_UP,
|
||||
UPDOWN_ACTION_UP_LONG,
|
||||
UPDOWN_ACTION_DOWN,
|
||||
UPDOWN_ACTION_DOWN_LONG
|
||||
};
|
||||
protected:
|
||||
enum UpDownInterruptBaseActionType {
|
||||
UPDOWN_ACTION_NONE,
|
||||
UPDOWN_ACTION_PRESSED,
|
||||
UPDOWN_ACTION_PRESSED_LONG,
|
||||
UPDOWN_ACTION_UP,
|
||||
UPDOWN_ACTION_UP_LONG,
|
||||
UPDOWN_ACTION_DOWN,
|
||||
UPDOWN_ACTION_DOWN_LONG
|
||||
};
|
||||
|
||||
volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE;
|
||||
volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE;
|
||||
|
||||
// Long press detection variables
|
||||
uint32_t pressStartTime = 0;
|
||||
uint32_t upStartTime = 0;
|
||||
uint32_t downStartTime = 0;
|
||||
bool pressDetected = false;
|
||||
bool upDetected = false;
|
||||
bool downDetected = false;
|
||||
uint32_t lastPressLongEventTime = 0;
|
||||
uint32_t lastUpLongEventTime = 0;
|
||||
uint32_t lastDownLongEventTime = 0;
|
||||
static const uint32_t LONG_PRESS_DURATION = UPDOWN_LONG_PRESS_DURATION;
|
||||
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = UPDOWN_LONG_PRESS_REPEAT_INTERVAL;
|
||||
// Long press detection variables
|
||||
uint32_t pressStartTime = 0;
|
||||
uint32_t upStartTime = 0;
|
||||
uint32_t downStartTime = 0;
|
||||
bool pressDetected = false;
|
||||
bool upDetected = false;
|
||||
bool downDetected = false;
|
||||
uint32_t lastPressLongEventTime = 0;
|
||||
uint32_t lastUpLongEventTime = 0;
|
||||
uint32_t lastDownLongEventTime = 0;
|
||||
static const uint32_t LONG_PRESS_DURATION = UPDOWN_LONG_PRESS_DURATION;
|
||||
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = UPDOWN_LONG_PRESS_REPEAT_INTERVAL;
|
||||
|
||||
private:
|
||||
uint8_t _pinDown = 0;
|
||||
uint8_t _pinUp = 0;
|
||||
uint8_t _pinPress = 0;
|
||||
input_broker_event _eventDown = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventUp = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressed = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventUpLong = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventDownLong = INPUT_BROKER_NONE;
|
||||
const char *_originName;
|
||||
private:
|
||||
uint8_t _pinDown = 0;
|
||||
uint8_t _pinUp = 0;
|
||||
uint8_t _pinPress = 0;
|
||||
input_broker_event _eventDown = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventUp = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressed = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventUpLong = INPUT_BROKER_NONE;
|
||||
input_broker_event _eventDownLong = INPUT_BROKER_NONE;
|
||||
const char *_originName;
|
||||
|
||||
unsigned long lastUpKeyTime = 0;
|
||||
unsigned long lastDownKeyTime = 0;
|
||||
unsigned long lastPressKeyTime = 0;
|
||||
unsigned long updownDebounceMs = 50;
|
||||
const unsigned long pressDebounceMs = 200;
|
||||
unsigned long lastUpKeyTime = 0;
|
||||
unsigned long lastDownKeyTime = 0;
|
||||
unsigned long lastPressKeyTime = 0;
|
||||
unsigned long updownDebounceMs = 50;
|
||||
const unsigned long pressDebounceMs = 200;
|
||||
};
|
||||
|
||||
@@ -6,44 +6,33 @@ UpDownInterruptImpl1 *upDownInterruptImpl1;
|
||||
|
||||
UpDownInterruptImpl1::UpDownInterruptImpl1() : UpDownInterruptBase("upDown1") {}
|
||||
|
||||
bool UpDownInterruptImpl1::init()
|
||||
{
|
||||
bool UpDownInterruptImpl1::init() {
|
||||
|
||||
if (!moduleConfig.canned_message.updown1_enabled) {
|
||||
// Input device is disabled.
|
||||
return false;
|
||||
}
|
||||
if (!moduleConfig.canned_message.updown1_enabled) {
|
||||
// Input device is disabled.
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t pinUp = moduleConfig.canned_message.inputbroker_pin_a;
|
||||
uint8_t pinDown = moduleConfig.canned_message.inputbroker_pin_b;
|
||||
uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press;
|
||||
uint8_t pinUp = moduleConfig.canned_message.inputbroker_pin_a;
|
||||
uint8_t pinDown = moduleConfig.canned_message.inputbroker_pin_b;
|
||||
uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press;
|
||||
|
||||
input_broker_event eventDown = INPUT_BROKER_USER_PRESS; // acts like RIGHT/DOWN
|
||||
input_broker_event eventUp = INPUT_BROKER_ALT_PRESS; // acts like LEFT/UP
|
||||
input_broker_event eventPressed = INPUT_BROKER_SELECT;
|
||||
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
|
||||
input_broker_event eventUpLong = INPUT_BROKER_UP_LONG;
|
||||
input_broker_event eventDownLong = INPUT_BROKER_DOWN_LONG;
|
||||
input_broker_event eventDown = INPUT_BROKER_USER_PRESS; // acts like RIGHT/DOWN
|
||||
input_broker_event eventUp = INPUT_BROKER_ALT_PRESS; // acts like LEFT/UP
|
||||
input_broker_event eventPressed = INPUT_BROKER_SELECT;
|
||||
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
|
||||
input_broker_event eventUpLong = INPUT_BROKER_UP_LONG;
|
||||
input_broker_event eventDownLong = INPUT_BROKER_DOWN_LONG;
|
||||
|
||||
UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, eventPressedLong, eventUpLong,
|
||||
eventDownLong, UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp,
|
||||
UpDownInterruptImpl1::handleIntPressed);
|
||||
inputBroker->registerSource(this);
|
||||
UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, eventPressedLong, eventUpLong, eventDownLong,
|
||||
UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp, UpDownInterruptImpl1::handleIntPressed);
|
||||
inputBroker->registerSource(this);
|
||||
#ifndef HAS_PHYSICAL_KEYBOARD
|
||||
osk_found = true;
|
||||
osk_found = true;
|
||||
#endif
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpDownInterruptImpl1::handleIntDown()
|
||||
{
|
||||
upDownInterruptImpl1->intDownHandler();
|
||||
}
|
||||
void UpDownInterruptImpl1::handleIntUp()
|
||||
{
|
||||
upDownInterruptImpl1->intUpHandler();
|
||||
}
|
||||
void UpDownInterruptImpl1::handleIntPressed()
|
||||
{
|
||||
upDownInterruptImpl1->intPressHandler();
|
||||
}
|
||||
void UpDownInterruptImpl1::handleIntDown() { upDownInterruptImpl1->intDownHandler(); }
|
||||
void UpDownInterruptImpl1::handleIntUp() { upDownInterruptImpl1->intUpHandler(); }
|
||||
void UpDownInterruptImpl1::handleIntPressed() { upDownInterruptImpl1->intPressHandler(); }
|
||||
@@ -1,14 +1,13 @@
|
||||
#pragma once
|
||||
#include "UpDownInterruptBase.h"
|
||||
|
||||
class UpDownInterruptImpl1 : public UpDownInterruptBase
|
||||
{
|
||||
public:
|
||||
UpDownInterruptImpl1();
|
||||
bool init();
|
||||
static void handleIntDown();
|
||||
static void handleIntUp();
|
||||
static void handleIntPressed();
|
||||
class UpDownInterruptImpl1 : public UpDownInterruptBase {
|
||||
public:
|
||||
UpDownInterruptImpl1();
|
||||
bool init();
|
||||
static void handleIntDown();
|
||||
static void handleIntUp();
|
||||
static void handleIntPressed();
|
||||
};
|
||||
|
||||
extern UpDownInterruptImpl1 *upDownInterruptImpl1;
|
||||
@@ -7,69 +7,68 @@ CardKbI2cImpl *cardKbI2cImpl;
|
||||
|
||||
CardKbI2cImpl::CardKbI2cImpl() : KbI2cBase("cardKB") {}
|
||||
|
||||
void CardKbI2cImpl::init()
|
||||
{
|
||||
void CardKbI2cImpl::init() {
|
||||
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN)
|
||||
if (cardkb_found.address == 0x00) {
|
||||
LOG_DEBUG("Rescan for I2C keyboard");
|
||||
uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, TCA8418_KB_ADDR};
|
||||
if (cardkb_found.address == 0x00) {
|
||||
LOG_DEBUG("Rescan for I2C keyboard");
|
||||
uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, TCA8418_KB_ADDR};
|
||||
#if defined(T_LORA_PAGER)
|
||||
uint8_t i2caddr_asize = sizeof(i2caddr_scan) / sizeof(i2caddr_scan[0]);
|
||||
uint8_t i2caddr_asize = sizeof(i2caddr_scan) / sizeof(i2caddr_scan[0]);
|
||||
#else
|
||||
uint8_t i2caddr_asize = 5;
|
||||
uint8_t i2caddr_asize = 5;
|
||||
#endif
|
||||
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
|
||||
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
|
||||
|
||||
#if WIRE_INTERFACES_COUNT == 2
|
||||
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize);
|
||||
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize);
|
||||
#endif
|
||||
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize);
|
||||
auto kb_info = i2cScanner->firstKeyboard();
|
||||
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize);
|
||||
auto kb_info = i2cScanner->firstKeyboard();
|
||||
|
||||
if (kb_info.type != ScanI2C::DeviceType::NONE) {
|
||||
cardkb_found = kb_info.address;
|
||||
switch (kb_info.type) {
|
||||
case ScanI2C::DeviceType::RAK14004:
|
||||
kb_model = 0x02;
|
||||
break;
|
||||
case ScanI2C::DeviceType::CARDKB:
|
||||
kb_model = 0x00;
|
||||
break;
|
||||
case ScanI2C::DeviceType::TDECKKB:
|
||||
// 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;
|
||||
case ScanI2C::DeviceType::MPR121KB:
|
||||
// assign an arbitrary value to distinguish from other models
|
||||
kb_model = 0x37;
|
||||
break;
|
||||
case ScanI2C::DeviceType::TCA8418KB:
|
||||
// assign an arbitrary value to distinguish from other models
|
||||
kb_model = 0x84;
|
||||
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", kb_info.type);
|
||||
kb_model = 0x00;
|
||||
}
|
||||
}
|
||||
if (cardkb_found.address == 0x00) {
|
||||
disable();
|
||||
return;
|
||||
} else {
|
||||
LOG_DEBUG("Keyboard Type: 0x%02x Model: 0x%02x Address: 0x%02x", kb_info.type, kb_model, cardkb_found.address);
|
||||
}
|
||||
if (kb_info.type != ScanI2C::DeviceType::NONE) {
|
||||
cardkb_found = kb_info.address;
|
||||
switch (kb_info.type) {
|
||||
case ScanI2C::DeviceType::RAK14004:
|
||||
kb_model = 0x02;
|
||||
break;
|
||||
case ScanI2C::DeviceType::CARDKB:
|
||||
kb_model = 0x00;
|
||||
break;
|
||||
case ScanI2C::DeviceType::TDECKKB:
|
||||
// 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;
|
||||
case ScanI2C::DeviceType::MPR121KB:
|
||||
// assign an arbitrary value to distinguish from other models
|
||||
kb_model = 0x37;
|
||||
break;
|
||||
case ScanI2C::DeviceType::TCA8418KB:
|
||||
// assign an arbitrary value to distinguish from other models
|
||||
kb_model = 0x84;
|
||||
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", kb_info.type);
|
||||
kb_model = 0x00;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (cardkb_found.address == 0x00) {
|
||||
disable();
|
||||
return;
|
||||
disable();
|
||||
return;
|
||||
} else {
|
||||
LOG_DEBUG("Keyboard Type: 0x%02x Model: 0x%02x Address: 0x%02x", kb_info.type, kb_model, cardkb_found.address);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (cardkb_found.address == 0x00) {
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
inputBroker->registerSource(this);
|
||||
kb_found = true;
|
||||
inputBroker->registerSource(this);
|
||||
kb_found = true;
|
||||
}
|
||||
@@ -8,11 +8,10 @@
|
||||
* to your device as you wish, but you always need to have separate event
|
||||
* handlers, thus you need to have a RotaryEncoderInterrupt implementation.
|
||||
*/
|
||||
class CardKbI2cImpl : public KbI2cBase
|
||||
{
|
||||
public:
|
||||
CardKbI2cImpl();
|
||||
void init();
|
||||
class CardKbI2cImpl : public KbI2cBase {
|
||||
public:
|
||||
CardKbI2cImpl();
|
||||
void init();
|
||||
};
|
||||
|
||||
extern CardKbI2cImpl *cardKbI2cImpl;
|
||||
@@ -31,65 +31,63 @@ extern void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value);
|
||||
#define PI4IO_REG_IN_STA 0x0F
|
||||
#define PI4IO_REG_CHIP_RESET 0x01
|
||||
|
||||
i2cButtonThread::i2cButtonThread(const char *name) : OSThread(name)
|
||||
{
|
||||
_originName = name;
|
||||
if (inputBroker)
|
||||
inputBroker->registerSource(this);
|
||||
i2cButtonThread::i2cButtonThread(const char *name) : OSThread(name) {
|
||||
_originName = name;
|
||||
if (inputBroker)
|
||||
inputBroker->registerSource(this);
|
||||
}
|
||||
|
||||
int32_t i2cButtonThread::runOnce()
|
||||
{
|
||||
static bool btn1_pressed = false;
|
||||
static uint32_t press_start_time = 0;
|
||||
const uint32_t LONG_PRESS_TIME = 1000;
|
||||
static bool long_press_triggered = false;
|
||||
int32_t i2cButtonThread::runOnce() {
|
||||
static bool btn1_pressed = false;
|
||||
static uint32_t press_start_time = 0;
|
||||
const uint32_t LONG_PRESS_TIME = 1000;
|
||||
static bool long_press_triggered = false;
|
||||
|
||||
uint8_t in_data;
|
||||
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data);
|
||||
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, in_data);
|
||||
if (getbit(in_data, 0)) {
|
||||
uint8_t input_state;
|
||||
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IN_STA, &input_state);
|
||||
uint8_t in_data;
|
||||
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data);
|
||||
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, in_data);
|
||||
if (getbit(in_data, 0)) {
|
||||
uint8_t input_state;
|
||||
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IN_STA, &input_state);
|
||||
|
||||
if (!getbit(input_state, 0)) {
|
||||
if (!btn1_pressed) {
|
||||
btn1_pressed = true;
|
||||
press_start_time = millis();
|
||||
long_press_triggered = false;
|
||||
}
|
||||
} else {
|
||||
if (btn1_pressed) {
|
||||
btn1_pressed = false;
|
||||
uint32_t press_duration = millis() - press_start_time;
|
||||
if (long_press_triggered) {
|
||||
long_press_triggered = false;
|
||||
return 50;
|
||||
}
|
||||
|
||||
if (press_duration < LONG_PRESS_TIME) {
|
||||
InputEvent evt;
|
||||
evt.source = "UserButton";
|
||||
evt.inputEvent = INPUT_BROKER_USER_PRESS;
|
||||
evt.kbchar = 0;
|
||||
evt.touchX = 0;
|
||||
evt.touchY = 0;
|
||||
this->notifyObservers(&evt);
|
||||
}
|
||||
}
|
||||
if (!getbit(input_state, 0)) {
|
||||
if (!btn1_pressed) {
|
||||
btn1_pressed = true;
|
||||
press_start_time = millis();
|
||||
long_press_triggered = false;
|
||||
}
|
||||
} else {
|
||||
if (btn1_pressed) {
|
||||
btn1_pressed = false;
|
||||
uint32_t press_duration = millis() - press_start_time;
|
||||
if (long_press_triggered) {
|
||||
long_press_triggered = false;
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
|
||||
if (btn1_pressed && !long_press_triggered && (millis() - press_start_time >= LONG_PRESS_TIME)) {
|
||||
long_press_triggered = true;
|
||||
InputEvent evt;
|
||||
evt.source = "UserButton";
|
||||
evt.inputEvent = INPUT_BROKER_SELECT;
|
||||
evt.kbchar = 0;
|
||||
evt.touchX = 0;
|
||||
evt.touchY = 0;
|
||||
this->notifyObservers(&evt);
|
||||
if (press_duration < LONG_PRESS_TIME) {
|
||||
InputEvent evt;
|
||||
evt.source = "UserButton";
|
||||
evt.inputEvent = INPUT_BROKER_USER_PRESS;
|
||||
evt.kbchar = 0;
|
||||
evt.touchX = 0;
|
||||
evt.touchY = 0;
|
||||
this->notifyObservers(&evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 50;
|
||||
}
|
||||
|
||||
if (btn1_pressed && !long_press_triggered && (millis() - press_start_time >= LONG_PRESS_TIME)) {
|
||||
long_press_triggered = true;
|
||||
InputEvent evt;
|
||||
evt.source = "UserButton";
|
||||
evt.inputEvent = INPUT_BROKER_SELECT;
|
||||
evt.kbchar = 0;
|
||||
evt.touchX = 0;
|
||||
evt.touchY = 0;
|
||||
this->notifyObservers(&evt);
|
||||
}
|
||||
return 50;
|
||||
}
|
||||
#endif
|
||||
@@ -6,12 +6,11 @@
|
||||
#include "configuration.h"
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
|
||||
class i2cButtonThread : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
const char *_originName;
|
||||
explicit i2cButtonThread(const char *name);
|
||||
int32_t runOnce() override;
|
||||
class i2cButtonThread : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
const char *_originName;
|
||||
explicit i2cButtonThread(const char *name);
|
||||
int32_t runOnce() override;
|
||||
};
|
||||
|
||||
extern i2cButtonThread *i2cButton;
|
||||
|
||||
@@ -28,505 +28,502 @@ KbI2cBase::KbI2cBase(const char *name)
|
||||
TCAKeyboard(*(new TCA8418Keyboard()))
|
||||
#endif
|
||||
{
|
||||
this->_originName = name;
|
||||
this->_originName = name;
|
||||
}
|
||||
|
||||
uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t length)
|
||||
{
|
||||
uint8_t readflag = 0;
|
||||
i2cBus->beginTransmission(CARDKB_ADDR);
|
||||
i2cBus->write(reg);
|
||||
i2cBus->endTransmission(); // stop transmitting
|
||||
delay(20);
|
||||
i2cBus->requestFrom(CARDKB_ADDR, (int)length);
|
||||
int i = 0;
|
||||
while (i2cBus->available()) // slave may send less than requested
|
||||
{
|
||||
data[i++] = i2cBus->read(); // receive a byte as a proper uint8_t
|
||||
readflag = 1;
|
||||
}
|
||||
return readflag;
|
||||
uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t length) {
|
||||
uint8_t readflag = 0;
|
||||
i2cBus->beginTransmission(CARDKB_ADDR);
|
||||
i2cBus->write(reg);
|
||||
i2cBus->endTransmission(); // stop transmitting
|
||||
delay(20);
|
||||
i2cBus->requestFrom(CARDKB_ADDR, (int)length);
|
||||
int i = 0;
|
||||
while (i2cBus->available()) // slave may send less than requested
|
||||
{
|
||||
data[i++] = i2cBus->read(); // receive a byte as a proper uint8_t
|
||||
readflag = 1;
|
||||
}
|
||||
return readflag;
|
||||
}
|
||||
|
||||
int32_t KbI2cBase::runOnce()
|
||||
{
|
||||
if (!i2cBus) {
|
||||
switch (cardkb_found.port) {
|
||||
case ScanI2C::WIRE1:
|
||||
int32_t KbI2cBase::runOnce() {
|
||||
if (!i2cBus) {
|
||||
switch (cardkb_found.port) {
|
||||
case ScanI2C::WIRE1:
|
||||
#if WIRE_INTERFACES_COUNT == 2
|
||||
LOG_DEBUG("Use I2C Bus 1 (the second one)");
|
||||
i2cBus = &Wire1;
|
||||
if (cardkb_found.address == BBQ10_KB_ADDR) {
|
||||
Q10keyboard.begin(BBQ10_KB_ADDR, &Wire1);
|
||||
Q10keyboard.setBacklight(0);
|
||||
}
|
||||
if (cardkb_found.address == MPR121_KB_ADDR) {
|
||||
MPRkeyboard.begin(MPR121_KB_ADDR, &Wire1);
|
||||
}
|
||||
if (cardkb_found.address == TCA8418_KB_ADDR) {
|
||||
TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire1);
|
||||
}
|
||||
break;
|
||||
LOG_DEBUG("Use I2C Bus 1 (the second one)");
|
||||
i2cBus = &Wire1;
|
||||
if (cardkb_found.address == BBQ10_KB_ADDR) {
|
||||
Q10keyboard.begin(BBQ10_KB_ADDR, &Wire1);
|
||||
Q10keyboard.setBacklight(0);
|
||||
}
|
||||
if (cardkb_found.address == MPR121_KB_ADDR) {
|
||||
MPRkeyboard.begin(MPR121_KB_ADDR, &Wire1);
|
||||
}
|
||||
if (cardkb_found.address == TCA8418_KB_ADDR) {
|
||||
TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire1);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case ScanI2C::WIRE:
|
||||
LOG_DEBUG("Use I2C Bus 0 (the first one)");
|
||||
i2cBus = &Wire;
|
||||
if (cardkb_found.address == BBQ10_KB_ADDR) {
|
||||
Q10keyboard.begin(BBQ10_KB_ADDR, &Wire);
|
||||
Q10keyboard.setBacklight(0);
|
||||
}
|
||||
if (cardkb_found.address == MPR121_KB_ADDR) {
|
||||
MPRkeyboard.begin(MPR121_KB_ADDR, &Wire);
|
||||
}
|
||||
if (cardkb_found.address == TCA8418_KB_ADDR) {
|
||||
TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire);
|
||||
}
|
||||
break;
|
||||
case ScanI2C::NO_I2C:
|
||||
default:
|
||||
i2cBus = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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 = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
switch (key.key) {
|
||||
case 'p': // TAB
|
||||
case 't': // TAB as well
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x09; // TAB Scancode
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 'q': // ESC
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
e.kbchar = 0;
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 0x08: // Back
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = key.key;
|
||||
break;
|
||||
case 'e': // sym e
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
e.kbchar = INPUT_BROKER_UP;
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 'x': // sym x
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
e.kbchar = 0;
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 's': // sym s
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0x00; // tweak for destSelect
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 'f': // sym f
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0x00; // tweak for destSelect
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 0x13: // Code scanner says the SYM key is 0x13
|
||||
is_sym = !is_sym;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that
|
||||
: INPUT_BROKER_MSG_FN_SYMBOL_OFF; // the modifier key is active
|
||||
break;
|
||||
case 0x0a: // apparently Enter on Q10 is a line feed instead of carriage return
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
break;
|
||||
case 0x00: // nopress
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
break;
|
||||
default: // all other keys
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x37: { // MPR121
|
||||
MPRkeyboard.trigger();
|
||||
InputEvent e = {};
|
||||
|
||||
while (MPRkeyboard.hasEvent()) {
|
||||
char nextEvent = MPRkeyboard.dequeueEvent();
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x00;
|
||||
e.source = this->_originName;
|
||||
switch (nextEvent) {
|
||||
case 0x00: // MPR121_NONE
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case 0x90: // MPR121_REBOOT
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_REBOOT;
|
||||
break;
|
||||
case 0xb4: // MPR121_LEFT
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case 0xb5: // MPR121_UP
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case 0xb6: // MPR121_DOWN
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case 0xb7: // MPR121_RIGHT
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case 0x1b: // MPR121_ESC
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0x08: // MPR121_BSP
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0x08;
|
||||
break;
|
||||
case 0x0d: // MPR121_SELECT
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
default:
|
||||
if (nextEvent > 127) {
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
}
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = nextEvent;
|
||||
break;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
LOG_DEBUG("MP121 Notifying: %i Char: %i", e.inputEvent, e.kbchar);
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x84: { // Adafruit TCA8418
|
||||
TCAKeyboard.trigger();
|
||||
InputEvent e = {};
|
||||
while (TCAKeyboard.hasEvent()) {
|
||||
char nextEvent = TCAKeyboard.dequeueEvent();
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x00;
|
||||
e.source = this->_originName;
|
||||
switch (nextEvent) {
|
||||
case TCA8418KeyboardBase::NONE:
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::REBOOT:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_REBOOT;
|
||||
break;
|
||||
case TCA8418KeyboardBase::LEFT:
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::UP:
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::DOWN:
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::RIGHT:
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::BSP:
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0x08;
|
||||
break;
|
||||
case TCA8418KeyboardBase::SELECT:
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::ESC:
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::GPS_TOGGLE:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_GPS_TOGGLE;
|
||||
break;
|
||||
case TCA8418KeyboardBase::SEND_PING:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_SEND_PING;
|
||||
break;
|
||||
case TCA8418KeyboardBase::MUTE_TOGGLE:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE;
|
||||
break;
|
||||
case TCA8418KeyboardBase::BT_TOGGLE:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE;
|
||||
break;
|
||||
case TCA8418KeyboardBase::BL_TOGGLE:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE;
|
||||
break;
|
||||
case TCA8418KeyboardBase::TAB:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_TAB;
|
||||
break;
|
||||
default:
|
||||
if (nextEvent > 127) {
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
}
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = nextEvent;
|
||||
break;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
// LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar);
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
TCAKeyboard.trigger();
|
||||
}
|
||||
TCAKeyboard.clearInt();
|
||||
break;
|
||||
}
|
||||
case 0x02: {
|
||||
// RAK14004
|
||||
uint8_t rDataBuf[8] = {0};
|
||||
uint8_t PrintDataBuf = 0;
|
||||
if (read_from_14004(i2cBus, 0x01, rDataBuf, 0x04) == 1) {
|
||||
for (uint8_t aCount = 0; aCount < 0x04; aCount++) {
|
||||
for (uint8_t bCount = 0; bCount < 0x04; bCount++) {
|
||||
if (((rDataBuf[aCount] >> bCount) & 0x01) == 0x01) {
|
||||
PrintDataBuf = aCount * 0x04 + bCount + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PrintDataBuf != 0) {
|
||||
LOG_DEBUG("RAK14004 key 0x%x pressed", PrintDataBuf);
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_MATRIXKEY;
|
||||
e.source = this->_originName;
|
||||
e.kbchar = PrintDataBuf;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x00: // CARDKB
|
||||
case 0x10: { // T-DECK
|
||||
|
||||
i2cBus->requestFrom((int)cardkb_found.address, 1);
|
||||
|
||||
if (i2cBus->available()) {
|
||||
char c = i2cBus->read();
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
switch (c) {
|
||||
case 0x71: // This is the button q. If modifier and q pressed, it cancels the input
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x74: // letter t. if modifier and t pressed call 'tab'
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x09; // TAB Scancode
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x6d: // letter m. Modifier makes it mute notifications
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE; // mute notifications
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x6f: // letter o(+). Modifier makes screen increase in brightness
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_BRIGHTNESS_UP; // Increase Brightness code
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x69: // letter i(-). Modifier makes screen decrease in brightness
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_BRIGHTNESS_DOWN; // Decrease Brightness code
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x20: // Space. Send network ping like double press does
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_SEND_PING; // (fn + space)
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x67: // letter g. toggle gps
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_GPS_TOGGLE;
|
||||
e.kbchar = INPUT_BROKER_GPS_TOGGLE;
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x1b: // ESC
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
break;
|
||||
case 0x08: // Back
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb5: // Up
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb6: // Down
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb4: // Left
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb7: // Right
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker))
|
||||
// toggle moddifiers button.
|
||||
is_sym = !is_sym;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that the
|
||||
: INPUT_BROKER_MSG_FN_SYMBOL_OFF; // modifier key is active
|
||||
break;
|
||||
case 0x9e: // fn+g INPUT_BROKER_GPS_TOGGLE
|
||||
e.inputEvent = INPUT_BROKER_GPS_TOGGLE;
|
||||
e.kbchar = c;
|
||||
break;
|
||||
case 0xaf: // fn+space INPUT_BROKER_SEND_PING
|
||||
e.inputEvent = INPUT_BROKER_SEND_PING;
|
||||
e.kbchar = c;
|
||||
break;
|
||||
case 0x9b: // fn+s INPUT_BROKER_MSG_SHUTDOWN
|
||||
e.inputEvent = INPUT_BROKER_SHUTDOWN;
|
||||
e.kbchar = c;
|
||||
break;
|
||||
|
||||
case 0x90: // fn+r INPUT_BROKER_MSG_REBOOT
|
||||
case 0x91: // fn+t
|
||||
case 0xac: // fn+m INPUT_BROKER_MSG_MUTE_TOGGLE
|
||||
case 0xAA: // fn+b INPUT_BROKER_MSG_BLUETOOTH_TOGGLE
|
||||
case 0x8F: // fn+e INPUT_BROKER_MSG_EMOTE_LIST
|
||||
// just pass those unmodified
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
break;
|
||||
case 0x0d: // Enter
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
break;
|
||||
case 0x00: // nopress
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
break;
|
||||
default: // all other keys
|
||||
if (c > 127) { // bogus key value
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
break;
|
||||
}
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
is_sym = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ScanI2C::WIRE:
|
||||
LOG_DEBUG("Use I2C Bus 0 (the first one)");
|
||||
i2cBus = &Wire;
|
||||
if (cardkb_found.address == BBQ10_KB_ADDR) {
|
||||
Q10keyboard.begin(BBQ10_KB_ADDR, &Wire);
|
||||
Q10keyboard.setBacklight(0);
|
||||
}
|
||||
if (cardkb_found.address == MPR121_KB_ADDR) {
|
||||
MPRkeyboard.begin(MPR121_KB_ADDR, &Wire);
|
||||
}
|
||||
if (cardkb_found.address == TCA8418_KB_ADDR) {
|
||||
TCAKeyboard.begin(TCA8418_KB_ADDR, &Wire);
|
||||
}
|
||||
break;
|
||||
case ScanI2C::NO_I2C:
|
||||
default:
|
||||
LOG_WARN("Unknown kb_model 0x%02x", kb_model);
|
||||
i2cBus = 0;
|
||||
}
|
||||
return 300;
|
||||
}
|
||||
|
||||
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 = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
switch (key.key) {
|
||||
case 'p': // TAB
|
||||
case 't': // TAB as well
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x09; // TAB Scancode
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 'q': // ESC
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
e.kbchar = 0;
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 0x08: // Back
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = key.key;
|
||||
break;
|
||||
case 'e': // sym e
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
e.kbchar = INPUT_BROKER_UP;
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 'x': // sym x
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
e.kbchar = 0;
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 's': // sym s
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0x00; // tweak for destSelect
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 'f': // sym f
|
||||
if (is_sym) {
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0x00; // tweak for destSelect
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
}
|
||||
break;
|
||||
case 0x13: // Code scanner says the SYM key is 0x13
|
||||
is_sym = !is_sym;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that
|
||||
: INPUT_BROKER_MSG_FN_SYMBOL_OFF; // the modifier key is active
|
||||
break;
|
||||
case 0x0a: // apparently Enter on Q10 is a line feed instead of carriage return
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
break;
|
||||
case 0x00: // nopress
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
break;
|
||||
default: // all other keys
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key.key;
|
||||
is_sym = false; // reset sym state after second keypress
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x37: { // MPR121
|
||||
MPRkeyboard.trigger();
|
||||
InputEvent e = {};
|
||||
|
||||
while (MPRkeyboard.hasEvent()) {
|
||||
char nextEvent = MPRkeyboard.dequeueEvent();
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x00;
|
||||
e.source = this->_originName;
|
||||
switch (nextEvent) {
|
||||
case 0x00: // MPR121_NONE
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case 0x90: // MPR121_REBOOT
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_REBOOT;
|
||||
break;
|
||||
case 0xb4: // MPR121_LEFT
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case 0xb5: // MPR121_UP
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case 0xb6: // MPR121_DOWN
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case 0xb7: // MPR121_RIGHT
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case 0x1b: // MPR121_ESC
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0x08: // MPR121_BSP
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0x08;
|
||||
break;
|
||||
case 0x0d: // MPR121_SELECT
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
default:
|
||||
if (nextEvent > 127) {
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
}
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = nextEvent;
|
||||
break;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
LOG_DEBUG("MP121 Notifying: %i Char: %i", e.inputEvent, e.kbchar);
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x84: { // Adafruit TCA8418
|
||||
TCAKeyboard.trigger();
|
||||
InputEvent e = {};
|
||||
while (TCAKeyboard.hasEvent()) {
|
||||
char nextEvent = TCAKeyboard.dequeueEvent();
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x00;
|
||||
e.source = this->_originName;
|
||||
switch (nextEvent) {
|
||||
case TCA8418KeyboardBase::NONE:
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::REBOOT:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_REBOOT;
|
||||
break;
|
||||
case TCA8418KeyboardBase::LEFT:
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::UP:
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::DOWN:
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::RIGHT:
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::BSP:
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0x08;
|
||||
break;
|
||||
case TCA8418KeyboardBase::SELECT:
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::ESC:
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::GPS_TOGGLE:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_GPS_TOGGLE;
|
||||
break;
|
||||
case TCA8418KeyboardBase::SEND_PING:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_SEND_PING;
|
||||
break;
|
||||
case TCA8418KeyboardBase::MUTE_TOGGLE:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE;
|
||||
break;
|
||||
case TCA8418KeyboardBase::BT_TOGGLE:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE;
|
||||
break;
|
||||
case TCA8418KeyboardBase::BL_TOGGLE:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_BLUETOOTH_TOGGLE;
|
||||
break;
|
||||
case TCA8418KeyboardBase::TAB:
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_TAB;
|
||||
break;
|
||||
default:
|
||||
if (nextEvent > 127) {
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
}
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = nextEvent;
|
||||
break;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
// LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar);
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
TCAKeyboard.trigger();
|
||||
}
|
||||
TCAKeyboard.clearInt();
|
||||
break;
|
||||
}
|
||||
case 0x02: {
|
||||
// RAK14004
|
||||
uint8_t rDataBuf[8] = {0};
|
||||
uint8_t PrintDataBuf = 0;
|
||||
if (read_from_14004(i2cBus, 0x01, rDataBuf, 0x04) == 1) {
|
||||
for (uint8_t aCount = 0; aCount < 0x04; aCount++) {
|
||||
for (uint8_t bCount = 0; bCount < 0x04; bCount++) {
|
||||
if (((rDataBuf[aCount] >> bCount) & 0x01) == 0x01) {
|
||||
PrintDataBuf = aCount * 0x04 + bCount + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PrintDataBuf != 0) {
|
||||
LOG_DEBUG("RAK14004 key 0x%x pressed", PrintDataBuf);
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_MATRIXKEY;
|
||||
e.source = this->_originName;
|
||||
e.kbchar = PrintDataBuf;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x00: // CARDKB
|
||||
case 0x10: { // T-DECK
|
||||
|
||||
i2cBus->requestFrom((int)cardkb_found.address, 1);
|
||||
|
||||
if (i2cBus->available()) {
|
||||
char c = i2cBus->read();
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
switch (c) {
|
||||
case 0x71: // This is the button q. If modifier and q pressed, it cancels the input
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x74: // letter t. if modifier and t pressed call 'tab'
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x09; // TAB Scancode
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x6d: // letter m. Modifier makes it mute notifications
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE; // mute notifications
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x6f: // letter o(+). Modifier makes screen increase in brightness
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_BRIGHTNESS_UP; // Increase Brightness code
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x69: // letter i(-). Modifier makes screen decrease in brightness
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_BRIGHTNESS_DOWN; // Decrease Brightness code
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x20: // Space. Send network ping like double press does
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_SEND_PING; // (fn + space)
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x67: // letter g. toggle gps
|
||||
if (is_sym) {
|
||||
is_sym = false;
|
||||
e.inputEvent = INPUT_BROKER_GPS_TOGGLE;
|
||||
e.kbchar = INPUT_BROKER_GPS_TOGGLE;
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
}
|
||||
break;
|
||||
case 0x1b: // ESC
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
break;
|
||||
case 0x08: // Back
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb5: // Up
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb6: // Down
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb4: // Left
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb7: // Right
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker))
|
||||
// toggle moddifiers button.
|
||||
is_sym = !is_sym;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that the
|
||||
: INPUT_BROKER_MSG_FN_SYMBOL_OFF; // modifier key is active
|
||||
break;
|
||||
case 0x9e: // fn+g INPUT_BROKER_GPS_TOGGLE
|
||||
e.inputEvent = INPUT_BROKER_GPS_TOGGLE;
|
||||
e.kbchar = c;
|
||||
break;
|
||||
case 0xaf: // fn+space INPUT_BROKER_SEND_PING
|
||||
e.inputEvent = INPUT_BROKER_SEND_PING;
|
||||
e.kbchar = c;
|
||||
break;
|
||||
case 0x9b: // fn+s INPUT_BROKER_MSG_SHUTDOWN
|
||||
e.inputEvent = INPUT_BROKER_SHUTDOWN;
|
||||
e.kbchar = c;
|
||||
break;
|
||||
|
||||
case 0x90: // fn+r INPUT_BROKER_MSG_REBOOT
|
||||
case 0x91: // fn+t
|
||||
case 0xac: // fn+m INPUT_BROKER_MSG_MUTE_TOGGLE
|
||||
case 0xAA: // fn+b INPUT_BROKER_MSG_BLUETOOTH_TOGGLE
|
||||
case 0x8F: // fn+e INPUT_BROKER_MSG_EMOTE_LIST
|
||||
// just pass those unmodified
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
break;
|
||||
case 0x0d: // Enter
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
break;
|
||||
case 0x00: // nopress
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
break;
|
||||
default: // all other keys
|
||||
if (c > 127) { // bogus key value
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
break;
|
||||
}
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
is_sym = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_WARN("Unknown kb_model 0x%02x", kb_model);
|
||||
}
|
||||
return 300;
|
||||
}
|
||||
|
||||
void KbI2cBase::toggleBacklight(bool on)
|
||||
{
|
||||
void KbI2cBase::toggleBacklight(bool on) {
|
||||
#if defined(T_LORA_PAGER)
|
||||
TCAKeyboard.setBacklight(on);
|
||||
TCAKeyboard.setBacklight(on);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -8,22 +8,21 @@
|
||||
|
||||
class TCA8418KeyboardBase;
|
||||
|
||||
class KbI2cBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit KbI2cBase(const char *name);
|
||||
void toggleBacklight(bool on);
|
||||
class KbI2cBase : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
explicit KbI2cBase(const char *name);
|
||||
void toggleBacklight(bool on);
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
const char *_originName;
|
||||
private:
|
||||
const char *_originName;
|
||||
|
||||
TwoWire *i2cBus = 0;
|
||||
TwoWire *i2cBus = 0;
|
||||
|
||||
BBQ10Keyboard Q10keyboard;
|
||||
MPR121Keyboard MPRkeyboard;
|
||||
TCA8418KeyboardBase &TCAKeyboard;
|
||||
bool is_sym = false;
|
||||
BBQ10Keyboard Q10keyboard;
|
||||
MPR121Keyboard MPRkeyboard;
|
||||
TCA8418KeyboardBase &TCAKeyboard;
|
||||
bool is_sym = false;
|
||||
};
|
||||
@@ -30,102 +30,98 @@ unsigned char KeyMap[3][sizeof(keys_rows)][sizeof(keys_cols)] = {{{' ', '.', 'm'
|
||||
{'1', '2', '3', '4', '5', 0x1a}}};
|
||||
#endif
|
||||
|
||||
KbMatrixBase::KbMatrixBase(const char *name) : concurrency::OSThread(name)
|
||||
{
|
||||
this->_originName = name;
|
||||
}
|
||||
KbMatrixBase::KbMatrixBase(const char *name) : concurrency::OSThread(name) { this->_originName = name; }
|
||||
|
||||
int32_t KbMatrixBase::runOnce()
|
||||
{
|
||||
if (!INPUTBROKER_MATRIX_TYPE) {
|
||||
// Input device is not requested.
|
||||
return disable();
|
||||
int32_t KbMatrixBase::runOnce() {
|
||||
if (!INPUTBROKER_MATRIX_TYPE) {
|
||||
// Input device is not requested.
|
||||
return disable();
|
||||
}
|
||||
|
||||
if (firstTime) {
|
||||
// This is the first time the OSThread library has called this function, so do port setup
|
||||
firstTime = 0;
|
||||
for (byte i = 0; i < sizeof(keys_rows); i++) {
|
||||
pinMode(keys_rows[i], OUTPUT);
|
||||
digitalWrite(keys_rows[i], HIGH);
|
||||
}
|
||||
for (byte i = 0; i < sizeof(keys_cols); i++) {
|
||||
pinMode(keys_cols[i], INPUT_PULLUP);
|
||||
}
|
||||
}
|
||||
|
||||
key = 0;
|
||||
|
||||
if (INPUTBROKER_MATRIX_TYPE == 1) {
|
||||
// scan for keypresses
|
||||
for (byte i = 0; i < sizeof(keys_rows); i++) {
|
||||
digitalWrite(keys_rows[i], LOW);
|
||||
for (byte j = 0; j < sizeof(keys_cols); j++) {
|
||||
if (digitalRead(keys_cols[j]) == LOW) {
|
||||
key = KeyMap[shift][i][j];
|
||||
}
|
||||
}
|
||||
digitalWrite(keys_rows[i], HIGH);
|
||||
}
|
||||
// debounce
|
||||
if (key != prevkey) {
|
||||
if (key != 0) {
|
||||
LOG_DEBUG("Key 0x%x pressed", key);
|
||||
// reset shift now that we have a keypress
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
switch (key) {
|
||||
case 0x1b: // ESC
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
break;
|
||||
case 0x08: // Back
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb5: // Up
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
break;
|
||||
case 0xb6: // Down
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
break;
|
||||
case 0xb4: // Left
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb7: // Right
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0x0d: // Enter
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
break;
|
||||
case 0x00: // nopress
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
break;
|
||||
case 0x1a: // Shift
|
||||
shift++;
|
||||
if (shift > 2) {
|
||||
shift = 0;
|
||||
}
|
||||
break;
|
||||
default: // all other keys
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key;
|
||||
break;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
prevkey = key;
|
||||
}
|
||||
|
||||
if (firstTime) {
|
||||
// This is the first time the OSThread library has called this function, so do port setup
|
||||
firstTime = 0;
|
||||
for (byte i = 0; i < sizeof(keys_rows); i++) {
|
||||
pinMode(keys_rows[i], OUTPUT);
|
||||
digitalWrite(keys_rows[i], HIGH);
|
||||
}
|
||||
for (byte i = 0; i < sizeof(keys_cols); i++) {
|
||||
pinMode(keys_cols[i], INPUT_PULLUP);
|
||||
}
|
||||
}
|
||||
|
||||
key = 0;
|
||||
|
||||
if (INPUTBROKER_MATRIX_TYPE == 1) {
|
||||
// scan for keypresses
|
||||
for (byte i = 0; i < sizeof(keys_rows); i++) {
|
||||
digitalWrite(keys_rows[i], LOW);
|
||||
for (byte j = 0; j < sizeof(keys_cols); j++) {
|
||||
if (digitalRead(keys_cols[j]) == LOW) {
|
||||
key = KeyMap[shift][i][j];
|
||||
}
|
||||
}
|
||||
digitalWrite(keys_rows[i], HIGH);
|
||||
}
|
||||
// debounce
|
||||
if (key != prevkey) {
|
||||
if (key != 0) {
|
||||
LOG_DEBUG("Key 0x%x pressed", key);
|
||||
// reset shift now that we have a keypress
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
switch (key) {
|
||||
case 0x1b: // ESC
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
break;
|
||||
case 0x08: // Back
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb5: // Up
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
break;
|
||||
case 0xb6: // Down
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
break;
|
||||
case 0xb4: // Left
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb7: // Right
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0x0d: // Enter
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
break;
|
||||
case 0x00: // nopress
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
break;
|
||||
case 0x1a: // Shift
|
||||
shift++;
|
||||
if (shift > 2) {
|
||||
shift = 0;
|
||||
}
|
||||
break;
|
||||
default: // all other keys
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = key;
|
||||
break;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
prevkey = key;
|
||||
}
|
||||
|
||||
} else {
|
||||
LOG_WARN("Unknown kb_model 0x%02x", INPUTBROKER_MATRIX_TYPE);
|
||||
return disable();
|
||||
}
|
||||
return 50; // Keyscan every 50msec to avoid key bounce
|
||||
} else {
|
||||
LOG_WARN("Unknown kb_model 0x%02x", INPUTBROKER_MATRIX_TYPE);
|
||||
return disable();
|
||||
}
|
||||
return 50; // Keyscan every 50msec to avoid key bounce
|
||||
}
|
||||
|
||||
#endif // INPUTBROKER_MATRIX_TYPE
|
||||
@@ -3,18 +3,17 @@
|
||||
#include "InputBroker.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
class KbMatrixBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit KbMatrixBase(const char *name);
|
||||
class KbMatrixBase : public Observable<const InputEvent *>, public concurrency::OSThread {
|
||||
public:
|
||||
explicit KbMatrixBase(const char *name);
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
const char *_originName;
|
||||
bool firstTime = 1;
|
||||
int shift = 0;
|
||||
char key = 0;
|
||||
char prevkey = 0;
|
||||
private:
|
||||
const char *_originName;
|
||||
bool firstTime = 1;
|
||||
int shift = 0;
|
||||
char key = 0;
|
||||
char prevkey = 0;
|
||||
};
|
||||
@@ -7,14 +7,13 @@ KbMatrixImpl *kbMatrixImpl;
|
||||
|
||||
KbMatrixImpl::KbMatrixImpl() : KbMatrixBase("matrixKB") {}
|
||||
|
||||
void KbMatrixImpl::init()
|
||||
{
|
||||
if (!INPUTBROKER_MATRIX_TYPE) {
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
void KbMatrixImpl::init() {
|
||||
if (!INPUTBROKER_MATRIX_TYPE) {
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
|
||||
inputBroker->registerSource(this);
|
||||
inputBroker->registerSource(this);
|
||||
}
|
||||
|
||||
#endif // INPUTBROKER_MATRIX_TYPE
|
||||
@@ -9,11 +9,10 @@
|
||||
* to your device as you wish, but you always need to have separate event
|
||||
* handlers, thus you need to have a RotaryEncoderInterrupt implementation.
|
||||
*/
|
||||
class KbMatrixImpl : public KbMatrixBase
|
||||
{
|
||||
public:
|
||||
KbMatrixImpl();
|
||||
void init();
|
||||
class KbMatrixImpl : public KbMatrixBase {
|
||||
public:
|
||||
KbMatrixImpl();
|
||||
void init();
|
||||
};
|
||||
|
||||
extern KbMatrixImpl *kbMatrixImpl;
|
||||
Reference in New Issue
Block a user