diff --git a/src/ButtonThread.h b/src/ButtonThread.h index fcbb73af0..255ab5162 100644 --- a/src/ButtonThread.h +++ b/src/ButtonThread.h @@ -101,23 +101,8 @@ class ButtonThread : public concurrency::OSThread #endif // if (!canSleep) LOG_DEBUG("Suppressing sleep!\n"); // else LOG_DEBUG("sleep ok\n"); -#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) - int x, y = 0; - screen->getTouch(&x, &y); - if (x > 0 && y > 0) { -#ifdef T_WATCH_S3 - drv.setWaveform(0, 75); - drv.setWaveform(1, 0); // end waveform - drv.go(); -#endif - LOG_DEBUG("touch %d %d\n", x, y); - powerFSM.trigger(EVENT_PRESS); - return 150; // Check for next touch every in 150ms - } -#endif - - return 5; + return 50; } private: diff --git a/src/commands.h b/src/commands.h index 7c7595143..03ede5982 100644 --- a/src/commands.h +++ b/src/commands.h @@ -15,4 +15,6 @@ enum class Cmd { PRINT, START_SHUTDOWN_SCREEN, START_REBOOT_SCREEN, + SHOW_PREV_FRAME, + SHOW_NEXT_FRAME }; \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 3e289ef54..aa9064251 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -98,8 +98,9 @@ along with this program. If not, see . // Define if screen should be mirrored left to right // #define SCREEN_MIRROR -// The m5stack I2C Keyboard (also RAK14004) +// I2C Keyboards (M5Stack, RAK14004, T-Deck) #define CARDKB_ADDR 0x5F +#define TDECK_KB_ADDR 0x55 // ----------------------------------------------------------------------------- // SENSOR @@ -173,6 +174,12 @@ along with this program. If not, see . #ifndef HAS_BUTTON #define HAS_BUTTON 0 #endif +#ifndef HAS_TRACKBALL +#define HAS_TRACKBALL 0 +#endif +#ifndef HAS_TOUCHSCREEN +#define HAS_TOUCHSCREEN 0 +#endif #ifndef HAS_TELEMETRY #define HAS_TELEMETRY 0 #endif diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 75b23f419..996bdf62a 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -30,8 +30,8 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { - ScanI2C::DeviceType types[] = {CARDKB, RAK14004}; - return firstOfOrNONE(2, types); + ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, RAK14004}; + return firstOfOrNONE(3, types); } ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 559cff7ec..418a6bf5e 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -16,6 +16,7 @@ class ScanI2C RTC_RV3028, RTC_PCF8563, CARDKB, + TDECKKB, RAK14004, PMU_AXP192_AXP2101, BME_680, diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 66e092951..30f9e7b7c 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -212,6 +212,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port) } break; + SCAN_SIMPLE_CASE(TDECK_KB_ADDR, TDECKKB, "T-Deck keyboard found\n"); SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "st7567 display found\n"); #ifdef HAS_NCP5623 SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623 RGB LED found\n"); diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index b8abec66e..fbd99d252 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -31,6 +31,7 @@ along with this program. If not, see . #include "gps/GeoCoord.h" #include "gps/RTC.h" #include "graphics/images.h" +#include "input/TouchScreenImpl1.h" #include "main.h" #include "mesh-pb-constants.h" #include "mesh/Channels.h" @@ -1044,12 +1045,18 @@ void Screen::setup() #endif serialSinceMsec = millis(); +#if HAS_TOUCHSCREEN + touchScreenImpl1 = new TouchScreenImpl1(dispdev.getWidth(), dispdev.getHeight(), dispdev.getTouch); + touchScreenImpl1->init(); +#endif + // Subscribe to status updates powerStatusObserver.observe(&powerStatus->onNewStatus); gpsStatusObserver.observe(&gpsStatus->onNewStatus); nodeStatusObserver.observe(&nodeStatus->onNewStatus); if (textMessageModule) textMessageObserver.observe(textMessageModule); + inputObserver.observe(inputBroker); // Modules can notify screen about refresh MeshModule::observeUIEvents(&uiFrameEventObserver); @@ -1127,6 +1134,12 @@ int32_t Screen::runOnce() handleOnPress(); } break; + case Cmd::SHOW_PREV_FRAME: + handleShowPrevFrame(); + break; + case Cmd::SHOW_NEXT_FRAME: + handleShowNextFrame(); + break; case Cmd::START_BLUETOOTH_PIN_SCREEN: handleStartBluetoothPinScreen(cmd.bluetooth_pin); break; @@ -1420,6 +1433,28 @@ void Screen::handleOnPress() } } +void Screen::handleShowPrevFrame() +{ + // If screen was off, just wake it, otherwise go back to previous frame + // If we are in a transition, the press must have bounced, drop it. + if (ui.getUiState()->frameState == FIXED) { + ui.previousFrame(); + lastScreenTransition = millis(); + setFastFramerate(); + } +} + +void Screen::handleShowNextFrame() +{ + // If screen was off, just wake it, otherwise advance to next frame + // If we are in a transition, the press must have bounced, drop it. + if (ui.getUiState()->frameState == FIXED) { + ui.nextFrame(); + lastScreenTransition = millis(); + setFastFramerate(); + } +} + #ifndef SCREEN_TRANSITION_FRAMERATE #define SCREEN_TRANSITION_FRAMERATE 30 // fps #endif @@ -1857,6 +1892,20 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event) return 0; } +int Screen::handleInputEvent(const InputEvent *event) +{ + if (showingNormalScreen && moduleFrames.size() == 0) { + LOG_DEBUG("Screen::handleInputEvent from %s\n", event->source); + if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { + showPrevFrame(); + } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { + showNextFrame(); + } + } + + return 0; +} + } // namespace graphics #else graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {} diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 9ebe1c75a..8812b7c70 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -53,6 +53,7 @@ class Screen #include "commands.h" #include "concurrency/LockGuard.h" #include "concurrency/OSThread.h" +#include "input/InputBroker.h" #include "mesh/MeshModule.h" #include "power.h" #include @@ -118,6 +119,8 @@ class Screen : public concurrency::OSThread CallbackObserver(this, &Screen::handleTextMessage); CallbackObserver uiFrameEventObserver = CallbackObserver(this, &Screen::handleUIFrameEvent); + CallbackObserver inputObserver = + CallbackObserver(this, &Screen::handleInputEvent); public: explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY); @@ -152,8 +155,10 @@ class Screen : public concurrency::OSThread void blink(); - /// Handles a button press. + /// Handle button press, trackball or swipe action) void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); } + void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); } + void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); } // Implementation to Adjust Brightness void adjustBrightness(); @@ -301,9 +306,11 @@ class Screen : public concurrency::OSThread // Use this handle to set things like battery status, user count, GPS status, etc. DebugInfo *debug_info() { return &debugInfo; } + // Handle observer events int handleStatusUpdate(const meshtastic::Status *arg); int handleTextMessage(const meshtastic_MeshPacket *arg); int handleUIFrameEvent(const UIFrameEvent *arg); + int handleInputEvent(const InputEvent *arg); /// Used to force (super slow) eink displays to draw critical frames void forceDisplay(); @@ -313,13 +320,6 @@ class Screen : public concurrency::OSThread void setWelcomeFrames(); - void getTouch(int *x, int *y) - { -#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) - dispdev.getTouch(x, y); -#endif - }; - protected: /// Updates the UI. // @@ -350,6 +350,8 @@ class Screen : public concurrency::OSThread // Implementations of various commands, called from doTask(). void handleSetOn(bool on); void handleOnPress(); + void handleShowNextFrame(); + void handleShowPrevFrame(); void handleStartBluetoothPinScreen(uint32_t pin); void handlePrint(const char *text); void handleStartFirmwareUpdateScreen(); diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 1f67f90c9..4c9196b2f 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -174,7 +174,7 @@ class LGFX : public lgfx::LGFX_Device auto cfg = _light_instance.config(); // Gets a structure for backlight settings. cfg.pin_bl = ST7789_BL; // Pin number to which the backlight is connected - cfg.invert = true; // true to invert the brightness of the backlight + cfg.invert = false; // true to invert the brightness of the backlight // cfg.pwm_channel = 0; _light_instance.config(cfg); @@ -196,7 +196,7 @@ class LGFX : public lgfx::LGFX_Device // cfg.freq = 2500000; // I2C - cfg.i2c_port = 1; + cfg.i2c_port = TOUCH_I2C_PORT; cfg.i2c_addr = TOUCH_SLAVE_ADDRESS; #ifdef SCREEN_TOUCH_USE_I2C1 cfg.pin_sda = I2C_SDA1; @@ -205,7 +205,7 @@ class LGFX : public lgfx::LGFX_Device cfg.pin_sda = I2C_SDA; cfg.pin_scl = I2C_SCL; #endif - cfg.freq = 400000; + // cfg.freq = 400000; _touch_instance.config(cfg); _panel_instance.setTouch(&_touch_instance); @@ -275,6 +275,9 @@ void TFTDisplay::sendCommand(uint8_t com) #endif #ifdef VTFT_CTRL digitalWrite(VTFT_CTRL, LOW); +#endif +#ifndef M5STACK + tft.setBrightness(128); #endif break; } @@ -284,6 +287,9 @@ void TFTDisplay::sendCommand(uint8_t com) #endif #ifdef VTFT_CTRL digitalWrite(VTFT_CTRL, HIGH); +#endif +#ifndef M5STACK + tft.setBrightness(0); #endif break; } @@ -294,6 +300,24 @@ void TFTDisplay::sendCommand(uint8_t com) // Drop all other commands to device (we just update the buffer) } +bool TFTDisplay::hasTouch(void) +{ +#ifndef M5STACK + return tft.touch() != nullptr; +#else + return false; +#endif +} + +bool TFTDisplay::getTouch(int16_t *x, int16_t *y) +{ +#ifndef M5STACK + return tft.getTouch(x, y); +#else + return false; +#endif +} + void TFTDisplay::setDetected(uint8_t detected) { (void)detected; @@ -322,12 +346,4 @@ bool TFTDisplay::connect() return true; } -// Get touch coords from the display -void TFTDisplay::getTouch(int *x, int *y) -{ -#ifndef M5STACK - tft.getTouch(x, y); -#endif -} - #endif \ No newline at end of file diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h index 03293d6f4..325765b1f 100644 --- a/src/graphics/TFTDisplay.h +++ b/src/graphics/TFTDisplay.h @@ -22,14 +22,16 @@ class TFTDisplay : public OLEDDisplay // Write the buffer to the display memory virtual void display(void) override; + // Touch screen (static handlers) + static bool hasTouch(void); + static bool getTouch(int16_t *x, int16_t *y); + /** * shim to make the abstraction happy * */ void setDetected(uint8_t detected); - void getTouch(int *x, int *y); - protected: // the header size of the buffer used, e.g. for the SPI command header virtual int getBufferOffset(void) override { return 0; } diff --git a/src/input/TouchScreenBase.cpp b/src/input/TouchScreenBase.cpp new file mode 100644 index 000000000..dad1bb56c --- /dev/null +++ b/src/input/TouchScreenBase.cpp @@ -0,0 +1,137 @@ +#include "TouchScreenBase.h" +#include "main.h" + +#ifndef TIME_LONG_PRESS +#define TIME_LONG_PRESS 400 +#endif + +// move a minimum distance over the screen to detect a "swipe" +#ifndef TOUCH_THRESHOLD_X +#define TOUCH_THRESHOLD_X 30 +#endif + +#ifndef TOUCH_THRESHOLD_Y +#define TOUCH_THRESHOLD_Y 20 +#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) +{ +} + +void TouchScreenBase::init(bool hasTouch) +{ + if (hasTouch) { + LOG_INFO("TouchScreen initialized %d %d\n", 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(TOUCH_ACTION_NONE); + + // process touch events + int16_t x, y; + bool touched = getTouch(x, y); + if (touched) { + hapticFeedback(); + this->setInterval(20); + _last_x = x; + _last_y = y; + } + if (touched != _touchedOld) { + if (touched) { + _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); + + // swipe horizontal + if (adx > ady && adx > TOUCH_THRESHOLD_X) { + if (0 > dx) { // swipe right to left + e.touchEvent = static_cast(TOUCH_ACTION_LEFT); + LOG_DEBUG("action SWIPE: right to left\n"); + } else { // swipe left to right + e.touchEvent = static_cast(TOUCH_ACTION_RIGHT); + LOG_DEBUG("action SWIPE: left to right\n"); + } + } + // swipe vertical + else if (ady > adx && ady > TOUCH_THRESHOLD_Y) { + if (0 > dy) { // swipe bottom to top + e.touchEvent = static_cast(TOUCH_ACTION_UP); + LOG_DEBUG("action SWIPE: bottom to top\n"); + } else { // swipe top to bottom + e.touchEvent = static_cast(TOUCH_ACTION_DOWN); + LOG_DEBUG("action SWIPE: top to bottom\n"); + } + } + // tap + else { + if (duration > 0 && duration < TIME_LONG_PRESS) { + if (_tapped) { + _tapped = false; + e.touchEvent = static_cast(TOUCH_ACTION_DOUBLE_TAP); + LOG_DEBUG("action DOUBLE TAP(%d/%d)\n", x, y); + } else { + _tapped = true; + } + } else { + _tapped = false; + } + } + } + } + _touchedOld = touched; + + // fire TAP event when no 2nd tap occured within time + if (_tapped && (time_t(millis()) - _start) > TIME_LONG_PRESS - 50) { + _tapped = false; + e.touchEvent = static_cast(TOUCH_ACTION_TAP); + LOG_DEBUG("action TAP(%d/%d)\n", _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(TOUCH_ACTION_LONG_PRESS); + LOG_DEBUG("action LONG PRESS(%d/%d)\n", _last_x, _last_y); + } + + if (e.touchEvent != TOUCH_ACTION_NONE) { + e.source = this->_originName; + e.x = _last_x; + e.y = _last_y; + onEvent(e); + } + + return interval; +} + +void TouchScreenBase::hapticFeedback() +{ +#ifdef T_WATCH_S3 + drv.setWaveform(0, 75); + drv.setWaveform(1, 0); // end waveform + drv.go(); +#endif +} \ No newline at end of file diff --git a/src/input/TouchScreenBase.h b/src/input/TouchScreenBase.h new file mode 100644 index 000000000..a68c23e99 --- /dev/null +++ b/src/input/TouchScreenBase.h @@ -0,0 +1,55 @@ +#pragma once + +#include "InputBroker.h" +#include "concurrency/OSThread.h" +#include "mesh/NodeDB.h" + +typedef struct _TouchEvent { + const char *source; + char touchEvent; + uint16_t x; + uint16_t y; +} TouchEvent; + +class TouchScreenBase : public Observable, 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 }; + + enum TouchScreenBaseEventType { + TOUCH_ACTION_NONE, + TOUCH_ACTION_UP, + TOUCH_ACTION_DOWN, + TOUCH_ACTION_LEFT, + TOUCH_ACTION_RIGHT, + TOUCH_ACTION_TAP, + TOUCH_ACTION_DOUBLE_TAP, + TOUCH_ACTION_LONG_PRESS + }; + + virtual int32_t runOnce() override; + + 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(); + + 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 + + const char *_originName; +}; diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp new file mode 100644 index 000000000..9a7ecd4a2 --- /dev/null +++ b/src/input/TouchScreenImpl1.cpp @@ -0,0 +1,68 @@ +#include "TouchScreenImpl1.h" +#include "InputBroker.h" +#include "configuration.h" + +TouchScreenImpl1 *touchScreenImpl1; + +TouchScreenImpl1::TouchScreenImpl1(uint16_t width, uint16_t height, bool (*getTouch)(int16_t *, int16_t *)) + : TouchScreenBase("touchscreen1", width, height), _getTouch(getTouch) +{ +} + +void TouchScreenImpl1::init() +{ +#if !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); +} + +/** + * @brief forward touchscreen event + * + * @param event + * + * The touchscreen events are translated to input events and reversed + */ +void TouchScreenImpl1::onEvent(const TouchEvent &event) +{ + InputEvent e; + e.source = event.source; + switch (event.touchEvent) { + case TOUCH_ACTION_LEFT: { + e.inputEvent = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT); + break; + } + case TOUCH_ACTION_RIGHT: { + e.inputEvent = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT); + break; + } + case TOUCH_ACTION_UP: { + e.inputEvent = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP); + break; + } + case TOUCH_ACTION_DOWN: { + e.inputEvent = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN); + break; + } + case TOUCH_ACTION_DOUBLE_TAP: { + e.inputEvent = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT); + break; + } + case TOUCH_ACTION_LONG_PRESS: { + e.inputEvent = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL); + break; + } + default: + return; + } + this->notifyObservers(&e); +} \ No newline at end of file diff --git a/src/input/TouchScreenImpl1.h b/src/input/TouchScreenImpl1.h new file mode 100644 index 000000000..0c5338459 --- /dev/null +++ b/src/input/TouchScreenImpl1.h @@ -0,0 +1,17 @@ +#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); + + protected: + virtual bool getTouch(int16_t &x, int16_t &y); + virtual void onEvent(const TouchEvent &event); + + bool (*_getTouch)(int16_t *, int16_t *); +}; + +extern TouchScreenImpl1 *touchScreenImpl1; diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp new file mode 100644 index 000000000..649e4b362 --- /dev/null +++ b/src/input/TrackballInterruptBase.cpp @@ -0,0 +1,78 @@ +#include "TrackballInterruptBase.h" +#include "configuration.h" + +TrackballInterruptBase::TrackballInterruptBase(const char *name) +{ + this->_originName = name; +} + +void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress, + char eventDown, char eventUp, char eventLeft, char eventRight, char eventPressed, + void (*onIntDown)(), void (*onIntUp)(), void (*onIntLeft)(), void (*onIntRight)(), + void (*onIntPress)()) +{ + this->_pinDown = pinDown; + this->_pinUp = pinUp; + this->_pinLeft = pinLeft; + this->_pinRight = pinRight; + this->_eventDown = eventDown; + this->_eventUp = eventUp; + this->_eventLeft = eventLeft; + this->_eventRight = eventRight; + this->_eventPressed = eventPressed; + + pinMode(pinPress, INPUT_PULLUP); + pinMode(this->_pinDown, INPUT_PULLUP); + pinMode(this->_pinUp, INPUT_PULLUP); + pinMode(this->_pinLeft, INPUT_PULLUP); + pinMode(this->_pinRight, INPUT_PULLUP); + + attachInterrupt(pinPress, onIntPress, RISING); + attachInterrupt(this->_pinDown, onIntDown, RISING); + attachInterrupt(this->_pinUp, onIntUp, RISING); + attachInterrupt(this->_pinLeft, onIntLeft, RISING); + attachInterrupt(this->_pinRight, onIntRight, RISING); + + LOG_DEBUG("Trackball GPIO initialized (%d, %d, %d, %d, %d)\n", this->_pinUp, this->_pinDown, this->_pinLeft, this->_pinRight, + pinPress); +} + +void TrackballInterruptBase::intPressHandler() +{ + InputEvent e; + e.source = this->_originName; + e.inputEvent = this->_eventPressed; + this->notifyObservers(&e); +} + +void TrackballInterruptBase::intDownHandler() +{ + InputEvent e; + e.source = this->_originName; + e.inputEvent = this->_eventDown; + this->notifyObservers(&e); +} + +void TrackballInterruptBase::intUpHandler() +{ + InputEvent e; + e.source = this->_originName; + e.inputEvent = this->_eventUp; + this->notifyObservers(&e); +} + +void TrackballInterruptBase::intLeftHandler() +{ + InputEvent e; + e.source = this->_originName; + e.inputEvent = this->_eventLeft; + this->notifyObservers(&e); +} + +void TrackballInterruptBase::intRightHandler() +{ + InputEvent e; + e.source = this->_originName; + e.inputEvent = this->_eventRight; + this->notifyObservers(&e); +} diff --git a/src/input/TrackballInterruptBase.h b/src/input/TrackballInterruptBase.h new file mode 100644 index 000000000..a82a20cb0 --- /dev/null +++ b/src/input/TrackballInterruptBase.h @@ -0,0 +1,30 @@ +#pragma once + +#include "InputBroker.h" +#include "mesh/NodeDB.h" + +class TrackballInterruptBase : public Observable +{ + public: + explicit TrackballInterruptBase(const char *name); + void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress, char eventDown, char eventUp, + char eventLeft, char eventRight, char eventPressed, void (*onIntDown)(), void (*onIntUp)(), void (*onIntLeft)(), + void (*onIntRight)(), void (*onIntPress)()); + void intPressHandler(); + void intDownHandler(); + void intUpHandler(); + void intLeftHandler(); + void intRightHandler(); + + private: + uint8_t _pinDown = 0; + uint8_t _pinUp = 0; + uint8_t _pinLeft = 0; + uint8_t _pinRight = 0; + char _eventDown = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + char _eventUp = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + char _eventLeft = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + char _eventRight = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + char _eventPressed = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + const char *_originName; +}; diff --git a/src/input/TrackballInterruptImpl1.cpp b/src/input/TrackballInterruptImpl1.cpp new file mode 100644 index 000000000..0a73b83b6 --- /dev/null +++ b/src/input/TrackballInterruptImpl1.cpp @@ -0,0 +1,54 @@ +#include "TrackballInterruptImpl1.h" +#include "InputBroker.h" +#include "configuration.h" + +TrackballInterruptImpl1 *trackballInterruptImpl1; + +TrackballInterruptImpl1::TrackballInterruptImpl1() : TrackballInterruptBase("trackball1") {} + +void TrackballInterruptImpl1::init() +{ +#if !HAS_TRACKBALL + // Input device is disabled. + return; +#else + uint8_t pinUp = TB_UP; + uint8_t pinDown = TB_DOWN; + uint8_t pinLeft = TB_LEFT; + uint8_t pinRight = TB_RIGHT; + uint8_t pinPress = TB_PRESS; + + char eventDown = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN); + char eventUp = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP); + char eventLeft = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT); + char eventRight = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT); + char eventPressed = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT); + + TrackballInterruptBase::init(pinDown, pinUp, pinLeft, pinRight, pinPress, eventDown, eventUp, eventLeft, eventRight, + eventPressed, TrackballInterruptImpl1::handleIntDown, TrackballInterruptImpl1::handleIntUp, + TrackballInterruptImpl1::handleIntLeft, TrackballInterruptImpl1::handleIntRight, + TrackballInterruptImpl1::handleIntPressed); + inputBroker->registerSource(this); +#endif +} + +void TrackballInterruptImpl1::handleIntDown() +{ + trackballInterruptImpl1->intDownHandler(); +} +void TrackballInterruptImpl1::handleIntUp() +{ + trackballInterruptImpl1->intUpHandler(); +} +void TrackballInterruptImpl1::handleIntLeft() +{ + trackballInterruptImpl1->intLeftHandler(); +} +void TrackballInterruptImpl1::handleIntRight() +{ + trackballInterruptImpl1->intRightHandler(); +} +void TrackballInterruptImpl1::handleIntPressed() +{ + trackballInterruptImpl1->intPressHandler(); +} diff --git a/src/input/TrackballInterruptImpl1.h b/src/input/TrackballInterruptImpl1.h new file mode 100644 index 000000000..36efac6a6 --- /dev/null +++ b/src/input/TrackballInterruptImpl1.h @@ -0,0 +1,16 @@ +#pragma once +#include "TrackballInterruptBase.h" + +class TrackballInterruptImpl1 : public TrackballInterruptBase +{ + public: + TrackballInterruptImpl1(); + void init(); + static void handleIntDown(); + static void handleIntUp(); + static void handleIntLeft(); + static void handleIntRight(); + static void handleIntPressed(); +}; + +extern TrackballInterruptImpl1 *trackballInterruptImpl1; diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp index 7c340bab0..ecc3b944a 100644 --- a/src/input/UpDownInterruptBase.cpp +++ b/src/input/UpDownInterruptBase.cpp @@ -23,7 +23,7 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, attachInterrupt(this->_pinDown, onIntDown, RISING); attachInterrupt(this->_pinUp, onIntUp, RISING); - LOG_DEBUG("GPIO initialized (%d, %d, %d)\n", this->_pinDown, this->_pinUp, pinPress); + LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)\n", this->_pinUp, this->_pinDown, pinPress); } void UpDownInterruptBase::intPressHandler() diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index 686f4b5a2..44db1d952 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -7,7 +7,7 @@ CardKbI2cImpl::CardKbI2cImpl() : KbI2cBase("cardKB") {} void CardKbI2cImpl::init() { - if (cardkb_found.address != CARDKB_ADDR) { + if (cardkb_found.address != CARDKB_ADDR && cardkb_found.address != TDECK_KB_ADDR) { disable(); return; } diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 6850eff51..cdffbaf7e 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -41,7 +41,7 @@ void write_to_14004(const TwoWire * i2cBus, uint8_t reg, uint8_t data) int32_t KbI2cBase::runOnce() { - if (cardkb_found.address != CARDKB_ADDR) { + if (cardkb_found.address != CARDKB_ADDR && cardkb_found.address != TDECK_KB_ADDR) { // Input device is not detected. return INT32_MAX; } @@ -85,9 +85,9 @@ int32_t KbI2cBase::runOnce() e.kbchar = PrintDataBuf; this->notifyObservers(&e); } - } else { - // m5 cardkb - i2cBus->requestFrom(CARDKB_ADDR, 1); + } else if (kb_model == 0x00 || kb_model == 0x10) { + // m5 cardkb and T-Deck + i2cBus->requestFrom(kb_model == 0x00 ? CARDKB_ADDR : TDECK_KB_ADDR, 1); while (i2cBus->available()) { char c = i2cBus->read(); @@ -132,6 +132,8 @@ int32_t KbI2cBase::runOnce() this->notifyObservers(&e); } } + } else { + LOG_WARN("Unknown kb_model 0x%02x\n", kb_model); } - return 500; + return 300; } diff --git a/src/main.cpp b/src/main.cpp index 8559c32f1..12ee157a5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,7 +96,7 @@ ScanI2C::DeviceAddress screen_found = ScanI2C::ADDRESS_NONE; // The I2C address of the cardkb or RAK14004 (if found) ScanI2C::DeviceAddress cardkb_found = ScanI2C::ADDRESS_NONE; -// 0x02 for RAK14004 and 0x00 for cardkb +// 0x02 for RAK14004, 0x00 for cardkb, 0x10 for T-Deck uint8_t kb_model; // The I2C address of the RTC Module (if found) @@ -300,6 +300,15 @@ void setup() #endif #endif +#ifdef T_DECK + // enable keyboard + pinMode(KB_POWERON, OUTPUT); + digitalWrite(KB_POWERON, HIGH); + // There needs to be a delay after power on, give LILYGO-KEYBOARD some startup time + // otherwise keyboard and touch screen will not work + delay(800); +#endif + // Currently only the tbeam has a PMU // PMU initialization needs to be placed before i2c scanning power = new Power(); @@ -372,8 +381,15 @@ void setup() 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; default: // use this as default since it's also just zero + LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00\n", kb_info.type); kb_model = 0x00; } } diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index d3a450371..b788d6a06 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -164,12 +164,21 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) || (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT))) { LOG_DEBUG("Canned message event (%x)\n", event->kbchar); - if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { + // tweak for left/right events generated via trackball/touch with empty kbchar + if (!event->kbchar) { + if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { + this->payload = 0xb4; + this->destSelect = true; + } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { + this->payload = 0xb7; + this->destSelect = true; + } + } else { // pass the pressed key this->payload = event->kbchar; - this->lastTouchMillis = millis(); - validEvent = true; } + this->lastTouchMillis = millis(); + validEvent = true; } if (event->inputEvent == static_cast(ANYKEY)) { LOG_DEBUG("Canned message event any key pressed\n"); @@ -225,7 +234,7 @@ int32_t CannedMessageModule::runOnce() (this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE)) { return INT32_MAX; } - LOG_DEBUG("Check status\n"); + // LOG_DEBUG("Check status\n"); UIFrameEvent e = {false, true}; if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { // TODO: might have some feedback of sendig state @@ -300,8 +309,7 @@ int32_t CannedMessageModule::runOnce() this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; LOG_DEBUG("MOVE DOWN (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); } - } else if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { - e.frameChanged = true; + } else if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT || this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) { switch (this->payload) { case 0xb4: // left if (this->destSelect) { @@ -347,38 +355,49 @@ int32_t CannedMessageModule::runOnce() } } break; - case 0x08: // backspace - if (this->freetext.length() > 0) { - if (this->cursor == this->freetext.length()) { - this->freetext = this->freetext.substring(0, this->freetext.length() - 1); - } else { - this->freetext = this->freetext.substring(0, this->cursor - 1) + - this->freetext.substring(this->cursor, this->freetext.length()); - } - this->cursor--; - } - break; - case 0x09: // tab - if (this->destSelect) { - this->destSelect = false; - } else { - this->destSelect = true; - } - break; default: - if (this->cursor == this->freetext.length()) { - this->freetext += this->payload; - } else { - this->freetext = - this->freetext.substring(0, this->cursor) + this->payload + this->freetext.substring(this->cursor); - } - this->cursor += 1; - if (this->freetext.length() > meshtastic_Constants_DATA_PAYLOAD_LEN) { - this->cursor = meshtastic_Constants_DATA_PAYLOAD_LEN; - this->freetext = this->freetext.substring(0, meshtastic_Constants_DATA_PAYLOAD_LEN); - } break; } + if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { + e.frameChanged = true; + switch (this->payload) { + case 0x08: // backspace + if (this->freetext.length() > 0) { + if (this->cursor == this->freetext.length()) { + this->freetext = this->freetext.substring(0, this->freetext.length() - 1); + } else { + this->freetext = this->freetext.substring(0, this->cursor - 1) + + this->freetext.substring(this->cursor, this->freetext.length()); + } + this->cursor--; + } + break; + case 0x09: // tab + if (this->destSelect) { + this->destSelect = false; + } else { + this->destSelect = true; + } + break; + case 0xb4: // left + case 0xb7: // right + // already handled above + break; + default: + if (this->cursor == this->freetext.length()) { + this->freetext += this->payload; + } else { + this->freetext = + this->freetext.substring(0, this->cursor) + this->payload + this->freetext.substring(this->cursor); + } + this->cursor += 1; + if (this->freetext.length() > meshtastic_Constants_DATA_PAYLOAD_LEN) { + this->cursor = meshtastic_Constants_DATA_PAYLOAD_LEN; + this->freetext = this->freetext.substring(0, meshtastic_Constants_DATA_PAYLOAD_LEN); + } + break; + } + } this->lastTouchMillis = millis(); this->notifyObservers(&e); @@ -406,6 +425,11 @@ const char *CannedMessageModule::getNextMessage() { return this->messages[this->getNextIndex()]; } +const char *CannedMessageModule::getMessageByIndex(int index) +{ + return (index >= 0 && index < this->messagesCount) ? this->messages[index] : ""; +} + const char *CannedMessageModule::getNodeName(NodeNum node) { if (node == NODENUM_BROADCAST) { @@ -482,12 +506,31 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); display->drawStringf(0 + x, 0 + y, buffer, "To: %s", cannedMessageModule->getNodeName(this->dest)); - display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL, cannedMessageModule->getPrevMessage()); - display->fillRect(0 + x, 0 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(), y + FONT_HEIGHT_SMALL); - display->setColor(BLACK); - display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL * 2, cannedMessageModule->getCurrentMessage()); - display->setColor(WHITE); - display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL * 3, cannedMessageModule->getNextMessage()); + int lines = (display->getHeight() / FONT_HEIGHT_SMALL) - 1; + if (lines == 3) { + // static (old) behavior for small displays + display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL, cannedMessageModule->getPrevMessage()); + display->fillRect(0 + x, 0 + y + FONT_HEIGHT_SMALL * 2, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL * 2, cannedMessageModule->getCurrentMessage()); + display->setColor(WHITE); + display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL * 3, cannedMessageModule->getNextMessage()); + } else { + // use entire display height for larger displays + int topMsg = (messagesCount > lines && currentMessageIndex >= lines - 1) ? currentMessageIndex - lines + 2 : 0; + for (int i = 0; i < std::min(messagesCount, lines); i++) { + if (i == currentMessageIndex - topMsg) { + display->fillRect(0 + x, 0 + y + FONT_HEIGHT_SMALL * (i + 1), x + display->getWidth(), + y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL * (i + 1), cannedMessageModule->getCurrentMessage()); + display->setColor(WHITE); + } else { + display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL * (i + 1), + cannedMessageModule->getMessageByIndex(topMsg + i)); + } + } + } } } } diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 5858a473c..4e9dadccf 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -30,6 +30,7 @@ class CannedMessageModule : public SinglePortModule, public Observableinit(); #endif +#if HAS_TRACKBALL + trackballInterruptImpl1 = new TrackballInterruptImpl1(); + trackballInterruptImpl1->init(); +#endif #if HAS_SCREEN cannedMessageModule = new CannedMessageModule(); #endif diff --git a/variants/t-deck/variant.h b/variants/t-deck/variant.h index e434cd35d..04f20fa74 100644 --- a/variants/t-deck/variant.h +++ b/variants/t-deck/variant.h @@ -16,8 +16,11 @@ #define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 #define SCREEN_ROTATE -#define SCREEN_TRANSITION_FRAMERATE 1 // fps +#define SCREEN_TRANSITION_FRAMERATE 5 + +#define HAS_TOUCHSCREEN 1 #define SCREEN_TOUCH_INT 16 +#define TOUCH_I2C_PORT 0 #define TOUCH_SLAVE_ADDRESS 0x5D // GT911 #define BUTTON_PIN 0 @@ -43,14 +46,25 @@ // keyboard #define I2C_SDA 18 // I2C pins for this board #define I2C_SCL 8 -#define BOARD_POWERON 10 // must be set to HIGH -#define KB_SLAVE_ADDRESS 0x55 -#define KB_BL_PIN 46 // INT, set to INPUT -#define KB_UP 2 -#define KB_DOWN 3 -#define KB_LEFT 1 -#define KB_RIGHT 15 +#define KB_POWERON 10 // must be set to HIGH +#define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55 +#define KB_BL_PIN 46 // not used for now +// trackball +#define HAS_TRACKBALL 1 +#define TB_UP 3 +#define TB_DOWN 15 +#define TB_LEFT 1 +#define TB_RIGHT 2 +#define TB_PRESS BUTTON_PIN + +// microphone +#define ES7210_SCK 47 +#define ES7210_DIN 14 +#define ES7210_LRCK 21 +#define ES7210_MCLK 48 + +// LoRa #define USE_SX1262 #define USE_SX1268 diff --git a/variants/t-watch-s3/variant.h b/variants/t-watch-s3/variant.h index 652696c3f..8c0fc9122 100644 --- a/variants/t-watch-s3/variant.h +++ b/variants/t-watch-s3/variant.h @@ -16,10 +16,13 @@ #define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 #define SCREEN_ROTATE -#define SCREEN_TRANSITION_FRAMERATE 1 // fps +#define SCREEN_TRANSITION_FRAMERATE 5 // fps + +#define HAS_TOUCHSCREEN 1 #define SCREEN_TOUCH_INT 16 -#define SCREEN_TOUCH_USE_I2C1 1 -#define TOUCH_SLAVE_ADDRESS 0x38 // GT911 +#define SCREEN_TOUCH_USE_I2C1 +#define TOUCH_I2C_PORT 1 +#define TOUCH_SLAVE_ADDRESS 0x38 #define I2C_SDA1 39 // Used for capacitive touch #define I2C_SCL1 40 // Used for capacitive touch