#include "TrackballInterruptBase.h" #include "Throttle.h" #include "UptimeClock.h" #include "configuration.h" extern bool osk_found; TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {} TrackballInterruptBase::PressResult TrackballInterruptBase::updatePress(bool irqLatched, uint32_t irqTimeMs, bool pinLow) { if (pressDetected) { if (!pinLow) { const bool wasShort = Throttle::isWithinTimespanMs(pressStartTime, LONG_PRESS_DURATION); pressDetected = false; pressStartTime = 0; lastLongPressEventTime = 0; longPressRepeatSent = false; return wasShort ? PressResult::Short : PressResult::None; } // Tracked by a flag, not by lastLongPressEventTime == 0, which is a valid instant at rollover. if (Throttle::hasElapsed(pressStartTime, LONG_PRESS_DURATION) && (!longPressRepeatSent || Throttle::hasElapsed(lastLongPressEventTime, LONG_PRESS_REPEAT_INTERVAL))) { lastLongPressEventTime = Time::getMillis(); longPressRepeatSent = true; return PressResult::LongRepeat; } return PressResult::None; } if (!irqLatched) return PressResult::None; // Already released by the time we polled: classify from the latched interrupt time, since a // delayed poll can hide a long hold behind the same latch. if (!pinLow) return Throttle::isWithinTimespanMs(irqTimeMs, LONG_PRESS_DURATION) ? PressResult::Short : PressResult::None; pressDetected = true; pressStartTime = irqTimeMs; lastLongPressEventTime = 0; longPressRepeatSent = false; return PressResult::None; } 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); } 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; #endif this->setInterval(100); } int32_t TrackballInterruptBase::runOnce() { InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; #if TB_THRESHOLD if (lastInterruptTime && !Throttle::isWithinTimespanMs(lastInterruptTime, 1000)) { left_counter = 0; right_counter = 0; up_counter = 0; down_counter = 0; lastInterruptTime = 0; } #ifdef INPUT_DEBUG if (left_counter > 0 || right_counter > 0 || up_counter > 0 || down_counter > 0) { LOG_DEBUG("L %u R %u U %u D %u, time %u", left_counter, right_counter, up_counter, down_counter, millis()); } #endif #endif bool pressLatched = false; if (_pinPress != 255) { const uint32_t irqSeq = pressIrqSeq; const uint32_t irqTimeMs = pressIrqTime; pressLatched = irqSeq != pressIrqSeen; pressIrqSeen = irqSeq; switch (updatePress(pressLatched, irqTimeMs, !digitalRead(_pinPress))) { case PressResult::Short: e.inputEvent = this->_eventPressed; break; case PressResult::LongRepeat: e.inputEvent = this->_eventPressedLong; break; case PressResult::None: break; } } 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; } 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 && e.inputEvent == INPUT_BROKER_NONE) { // repeat event when long press these direction, unless a press event already claimed this poll. 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; } } #if TB_THRESHOLD // A starting press suppresses direction events as it always has, and a press event already // emitted this poll (a release classified on a later poll) must not be overwritten either. if (!pressLatched && e.inputEvent == INPUT_BROKER_NONE) { if (up_counter >= TB_THRESHOLD) { #ifdef INPUT_DEBUG LOG_DEBUG("Trackball event UP %u", millis()); #endif e.inputEvent = this->_eventUp; } else if (down_counter >= TB_THRESHOLD) { #ifdef INPUT_DEBUG LOG_DEBUG("Trackball event DOWN %u", millis()); #endif e.inputEvent = this->_eventDown; } else if (left_counter >= TB_THRESHOLD) { #ifdef INPUT_DEBUG LOG_DEBUG("Trackball event LEFT %u", millis()); #endif e.inputEvent = this->_eventLeft; } else if (right_counter >= TB_THRESHOLD) { #ifdef INPUT_DEBUG LOG_DEBUG("Trackball event RIGHT %u", millis()); #endif e.inputEvent = this->_eventRight; } } #else // A press event already claimed this poll: a direction IRQ must not overwrite the tap. if (e.inputEvent == INPUT_BROKER_NONE) { 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 TB_THRESHOLD left_counter = 0; right_counter = 0; up_counter = 0; down_counter = 0; #endif } // 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 } void TrackballInterruptBase::intPressHandler() { // pressIrqSeq == 0 means nothing recorded yet, so a press at clock 0 is not read as a cooldown. if (pressIrqSeq != 0 && Throttle::isWithinTimespanMs(lastPressInterruptTime, 10)) return; lastPressInterruptTime = Time::getMillis(); pressIrqTime = lastPressInterruptTime; pressIrqSeq++; this->action = TB_ACTION_PRESSED; } void TrackballInterruptBase::intDownHandler() { if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) this->action = TB_ACTION_DOWN; lastInterruptTime = Time::skipZero(Time::getMillis()); #if TB_THRESHOLD down_counter++; #endif } void TrackballInterruptBase::intUpHandler() { if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) this->action = TB_ACTION_UP; lastInterruptTime = Time::skipZero(Time::getMillis()); #if TB_THRESHOLD up_counter++; #endif } void TrackballInterruptBase::intLeftHandler() { if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) this->action = TB_ACTION_LEFT; lastInterruptTime = Time::skipZero(Time::getMillis()); #if TB_THRESHOLD left_counter++; #endif } void TrackballInterruptBase::intRightHandler() { if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) this->action = TB_ACTION_RIGHT; lastInterruptTime = Time::skipZero(Time::getMillis()); #if TB_THRESHOLD right_counter++; #endif }