diff --git a/src/configuration.h b/src/configuration.h index b42a76940..f7d42afcd 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -104,9 +104,6 @@ along with this program. If not, see . // The m5stack I2C Keyboard (also RAK14004) #define CARDKB_ADDR 0x5F -// The older M5 Faces I2C Keyboard -#define FACESKB_ADDR 0x88 - // ----------------------------------------------------------------------------- // SENSOR // ----------------------------------------------------------------------------- diff --git a/src/detect/i2cScan.h b/src/detect/i2cScan.h index f5f8effc9..3591d307b 100644 --- a/src/detect/i2cScan.h +++ b/src/detect/i2cScan.h @@ -107,10 +107,6 @@ void scanI2Cdevice(void) kb_model = 0x00; } } - if (addr == FACESKB_ADDR) { - faceskb_found = addr; - DEBUG_MSG("m5 Faces found\n"); - } if (addr == ST7567_ADDRESS) { screen_found = addr; DEBUG_MSG("st7567 display found\n"); diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 77f61a796..6e195f133 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -117,6 +117,7 @@ static uint16_t displayWidth, displayHeight; #define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) #define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) +#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 2fa702cec..e38f6de66 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -213,27 +213,29 @@ class Screen : public concurrency::OSThread LASTCHAR = ch; switch (last) { // conversion depending on first UTF8-character - case 0xC2: { - SKIPREST = false; - return (uint8_t)ch; - } - case 0xC3: { - SKIPREST = false; - return (uint8_t)(ch | 0xC0); - } - // map UTF-8 cyrillic chars to it Windows-1251 (CP-1251) ASCII codes - // note: in this case we must use compatible font - provided ArialMT_Plain_10/16/24 by 'ThingPulse/esp8266-oled-ssd1306' library - // have empty chars for non-latin ASCII symbols - case 0xD0: { - SKIPREST = false; - if (ch == 129) return (uint8_t)(168); // Ё - if (ch > 143 && ch < 192) return (uint8_t)(ch + 48); - } - case 0xD1: { - SKIPREST = false; - if (ch == 145) return (uint8_t)(184); // ё - if (ch > 127 && ch < 144) return (uint8_t)(ch + 112); - } + case 0xC2: { + SKIPREST = false; + return (uint8_t)ch; + } + case 0xC3: { + SKIPREST = false; + return (uint8_t)(ch | 0xC0); + } + // map UTF-8 cyrillic chars to it Windows-1251 (CP-1251) ASCII codes + // note: in this case we must use compatible font - provided ArialMT_Plain_10/16/24 by 'ThingPulse/esp8266-oled-ssd1306' library + // have empty chars for non-latin ASCII symbols + case 0xD0: { + SKIPREST = false; + if (ch == 129) return (uint8_t)(168); // Ё + if (ch > 143 && ch < 192) return (uint8_t)(ch + 48); + break; + } + case 0xD1: { + SKIPREST = false; + if (ch == 145) return (uint8_t)(184); // ё + if (ch > 127 && ch < 144) return (uint8_t)(ch + 112); + break; + } } // We want to strip out prefix chars for two-byte char formats diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index e75b0c407..5736a8a65 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -1,9 +1,12 @@ #pragma once #include "Observer.h" +#define ANYKEY 0xFF + typedef struct _InputEvent { const char* source; char inputEvent; + char kbchar; } InputEvent; class InputBroker : public Observable diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index 1a101aee7..de0fbbd38 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -16,6 +16,5 @@ void CardKbI2cImpl::init() return; } - DEBUG_MSG("registerSource\n"); inputBroker->registerSource(this); } diff --git a/src/input/facesKbI2cImpl.cpp b/src/input/facesKbI2cImpl.cpp deleted file mode 100644 index c91350763..000000000 --- a/src/input/facesKbI2cImpl.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "facesKbI2cImpl.h" -#include "InputBroker.h" - -FacesKbI2cImpl *facesKbI2cImpl; - -FacesKbI2cImpl::FacesKbI2cImpl() : - KbI2cBase("facesKB") -{ -} - -void FacesKbI2cImpl::init() -{ - if (faceskb_found != FACESKB_ADDR) - { - // Input device is not detected. - return; - } - - inputBroker->registerSource(this); -} diff --git a/src/input/facesKbI2cImpl.h b/src/input/facesKbI2cImpl.h deleted file mode 100644 index edf73bd05..000000000 --- a/src/input/facesKbI2cImpl.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include "kbI2cBase.h" -#include "main.h" - -/** - * @brief The idea behind this class to have static methods for the event handlers. - * Check attachInterrupt() at RotaryEncoderInteruptBase.cpp - * Technically you can have as many rotary encoders hardver attached - * to your device as you wish, but you always need to have separate event - * handlers, thus you need to have a RotaryEncoderInterrupt implementation. - */ -class FacesKbI2cImpl : - public KbI2cBase -{ - public: - FacesKbI2cImpl(); - void init(); -}; - -extern FacesKbI2cImpl *facesKbI2cImpl; \ No newline at end of file diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index e12b52e5b..14e17b7a2 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -3,7 +3,6 @@ #include extern uint8_t cardkb_found; -extern uint8_t faceskb_found; KbI2cBase::KbI2cBase(const char *name) : concurrency::OSThread(name) { @@ -12,24 +11,25 @@ KbI2cBase::KbI2cBase(const char *name) : concurrency::OSThread(name) int32_t KbI2cBase::runOnce() { - if ((cardkb_found != CARDKB_ADDR) && (faceskb_found != CARDKB_ADDR)){ + if (cardkb_found != CARDKB_ADDR){ // Input device is not detected. return INT32_MAX; } - InputEvent e; - e.inputEvent = ModuleConfig_CannedMessageConfig_InputEventChar_NONE; - e.source = this->_originName; Wire.requestFrom(CARDKB_ADDR, 1); while (Wire.available()) { char c = Wire.read(); + InputEvent e; + e.inputEvent = ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + e.source = this->_originName; switch (c) { case 0x1b: // ESC e.inputEvent = ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL; break; case 0x08: // Back e.inputEvent = ModuleConfig_CannedMessageConfig_InputEventChar_BACK; + e.kbchar = c; break; case 0xb5: // Up e.inputEvent = ModuleConfig_CannedMessageConfig_InputEventChar_UP; @@ -39,18 +39,27 @@ int32_t KbI2cBase::runOnce() break; case 0xb4: // Left e.inputEvent = ModuleConfig_CannedMessageConfig_InputEventChar_LEFT; + e.kbchar = c; break; case 0xb7: // Right e.inputEvent = ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; + e.kbchar = c; break; case 0x0d: // Enter e.inputEvent = ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; break; + case 0x00: //nopress + e.inputEvent = ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + break; + default: // all other keys + e.inputEvent = ANYKEY; + e.kbchar = c; + break; } - } - if (e.inputEvent != ModuleConfig_CannedMessageConfig_InputEventChar_NONE) { - this->notifyObservers(&e); + if (e.inputEvent != ModuleConfig_CannedMessageConfig_InputEventChar_NONE) { + this->notifyObservers(&e); + } } return 500; } diff --git a/src/main.cpp b/src/main.cpp index 260e9aec3..c5e6e8b56 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -74,9 +74,6 @@ uint8_t cardkb_found; // 0x02 for RAK14004 and 0x00 for cardkb uint8_t kb_model; -// The I2C address of the Faces Keyboard (if found) -uint8_t faceskb_found; - // The I2C address of the RTC Module (if found) uint8_t rtc_found; diff --git a/src/main.h b/src/main.h index ed48a83cd..e1142d36c 100644 --- a/src/main.h +++ b/src/main.h @@ -11,7 +11,6 @@ extern uint8_t screen_found; extern uint8_t screen_model; extern uint8_t cardkb_found; extern uint8_t kb_model; -extern uint8_t faceskb_found; extern uint8_t rtc_found; extern bool eink_found; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 54c02f67f..240c23086 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -6,14 +6,36 @@ #include "PowerFSM.h" // neede for button bypass #include "mesh/generated/cannedmessages.pb.h" -// TODO: reuse defined from Screen.cpp +#ifdef OLED_RU +#include "graphics/fonts/OLEDDisplayFontsRU.h" +#endif + +#if defined(USE_EINK) || defined(ILI9341_DRIVER) +// The screen is bigger so use bigger fonts +#define FONT_SMALL ArialMT_Plain_16 +#define FONT_MEDIUM ArialMT_Plain_24 +#define FONT_LARGE ArialMT_Plain_24 +#else +#ifdef OLED_RU +#define FONT_SMALL ArialMT_Plain_10_RU +#else #define FONT_SMALL ArialMT_Plain_10 +#endif #define FONT_MEDIUM ArialMT_Plain_16 #define FONT_LARGE ArialMT_Plain_24 +#endif + +#define fontHeight(font) ((font)[1] + 1) // height is position 1 + +#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) +#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) +#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) // Remove Canned message screen if no action is taken for some milliseconds #define INACTIVATE_AFTER_MS 20000 +extern uint8_t cardkb_found; + static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto"; CannedMessageModuleConfig cannedMessageModuleConfig; @@ -30,7 +52,7 @@ CannedMessageModule::CannedMessageModule() { if (moduleConfig.canned_message.enabled) { this->loadProtoForModule(); - if (this->splitConfiguredMessages() <= 0) { + if ((this->splitConfiguredMessages() <= 0) && (cardkb_found != CARDKB_ADDR)) { DEBUG_MSG("CannedMessageModule: No messages are configured. Module is disabled\n"); this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED; } else { @@ -116,10 +138,40 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if ((this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED)) { powerFSM.trigger(EVENT_PRESS); } else { + this->payload = this->runState; this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; validEvent = true; } } + if (event->inputEvent == static_cast(ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) { + DEBUG_MSG("Canned message event Cancel\n"); + // emulate a timeout. Same result + this->lastTouchMillis = 0; + validEvent = true; + } + if ((event->inputEvent == static_cast(ModuleConfig_CannedMessageConfig_InputEventChar_BACK)) || + (event->inputEvent == static_cast(ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) || + (event->inputEvent == static_cast(ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT))) { + DEBUG_MSG("Canned message event (%x)\n",event->kbchar); + if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { + // pass the pressed key + this->payload = event->kbchar; + this->lastTouchMillis = millis(); + validEvent = true; + } + } + if (event->inputEvent == static_cast(ANYKEY)) { + DEBUG_MSG("Canned message event any key pressed\n"); + // when inactive, this will switch to the freetext mode + if ((this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED)) { + this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; + } + // pass the pressed key + this->payload = event->kbchar; + this->lastTouchMillis = millis(); + validEvent = true; + } + if (validEvent) { // Let runOnce to be called immediately. @@ -160,33 +212,102 @@ int32_t CannedMessageModule::runOnce() this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; e.frameChanged = true; this->currentMessageIndex = -1; + this->freetext = ""; // clear freetext + this->cursor = 0; this->notifyObservers(&e); - } else if ((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) && (millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS) { + } else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) && (millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS) { // Reset module - DEBUG_MSG("Reset due the lack of activity.\n"); + DEBUG_MSG("Reset due to lack of activity.\n"); e.frameChanged = true; this->currentMessageIndex = -1; + this->freetext = ""; // clear freetext + this->cursor = 0; this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; this->notifyObservers(&e); - } else if (this->currentMessageIndex == -1) { + } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { + if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) { + if (this->freetext.length() > 0) { + sendText(NODENUM_BROADCAST, this->freetext.c_str(), true); + this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; + } else { + DEBUG_MSG("Reset message is empty.\n"); + this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + e.frameChanged = true; + } + } else { + if (strlen(this->messages[this->currentMessageIndex]) > 0) { + sendText(NODENUM_BROADCAST, this->messages[this->currentMessageIndex], true); + this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; + } else { + DEBUG_MSG("Reset message is empty.\n"); + this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + e.frameChanged = true; + } + } + this->currentMessageIndex = -1; + this->freetext = ""; // clear freetext + this->cursor = 0; + this->notifyObservers(&e); + return 2000; + } else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) { this->currentMessageIndex = 0; DEBUG_MSG("First touch (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); e.frameChanged = true; this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; - } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { - sendText(NODENUM_BROADCAST, this->messages[this->currentMessageIndex], true); - this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; - this->currentMessageIndex = -1; - this->notifyObservers(&e); - return 2000; } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_UP) { this->currentMessageIndex = getPrevIndex(); + this->freetext = ""; // clear freetext + this->cursor = 0; this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; DEBUG_MSG("MOVE UP (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_DOWN) { this->currentMessageIndex = this->getNextIndex(); + this->freetext = ""; // clear freetext + this->cursor = 0; this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; DEBUG_MSG("MOVE DOWN (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); + } else if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { + e.frameChanged = true; + switch (this->payload) { + case 0xb4: // left + if (this->cursor > 0) { + this->cursor--; + } + break; + case 0xb7: // right + if (this->cursor < this->freetext.length()) { + this->cursor++; + } + break; + case 8: // 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; + 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() > Constants_DATA_PAYLOAD_LEN) { + this->cursor = Constants_DATA_PAYLOAD_LEN; + this->freetext = this->freetext.substring(0, Constants_DATA_PAYLOAD_LEN); + } + break; + } + + this->lastTouchMillis = millis(); + this->notifyObservers(&e); + return INACTIVATE_AFTER_MS; } if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) { @@ -247,15 +368,24 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_DISABLED) { display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); - display->drawString(10 + x, 0 + y + 16, "Canned Message\nModule disabled."); + display->drawString(10 + x, 0 + y + FONT_HEIGHT_SMALL, "Canned Message\nModule disabled."); + }else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_MEDIUM); + display->drawString(0 + x, 0 + y, "To: Broadcast"); + // used chars right aligned + char buffer[9]; + sprintf(buffer, "%d left", Constants_DATA_PAYLOAD_LEN - this->freetext.length()); + display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer); + display->drawString(0 + x, 0 + y + FONT_HEIGHT_MEDIUM, cannedMessageModule->drawWithCursor(cannedMessageModule->freetext, cannedMessageModule->cursor)); } else { display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); display->drawString(0 + x, 0 + y, cannedMessageModule->getPrevMessage()); display->setFont(FONT_MEDIUM); - display->drawString(0 + x, 0 + y + 8, cannedMessageModule->getCurrentMessage()); + display->drawString(0 + x, 0 + y + FONT_HEIGHT_SMALL, cannedMessageModule->getCurrentMessage()); display->setFont(FONT_SMALL); - display->drawString(0 + x, 0 + y + 24, cannedMessageModule->getNextMessage()); + display->drawString(0 + x, 0 + y + FONT_HEIGHT_MEDIUM, cannedMessageModule->getNextMessage()); } } @@ -354,4 +484,12 @@ void CannedMessageModule::handleSetCannedMessageModuleMessages(const char *from_ } } +String CannedMessageModule::drawWithCursor(String text, int cursor) +{ + String result = text.substring(0, cursor) + + "_" + + text.substring(cursor); + return result; +} + #endif \ No newline at end of file diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 2bd2927e1..33f60dd61 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -8,6 +8,7 @@ enum cannedMessageModuleRunState CANNED_MESSAGE_RUN_STATE_DISABLED, CANNED_MESSAGE_RUN_STATE_INACTIVE, CANNED_MESSAGE_RUN_STATE_ACTIVE, + CANNED_MESSAGE_RUN_STATE_FREETEXT, CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE, CANNED_MESSAGE_RUN_STATE_ACTION_SELECT, CANNED_MESSAGE_RUN_STATE_ACTION_UP, @@ -42,6 +43,8 @@ class CannedMessageModule : void handleGetCannedMessageModuleMessages(const MeshPacket &req, AdminMessage *response); void handleSetCannedMessageModuleMessages(const char *from_msg); + String drawWithCursor(String text, int cursor); + protected: virtual int32_t runOnce() override; @@ -70,6 +73,9 @@ class CannedMessageModule : int currentMessageIndex = -1; cannedMessageModuleRunState runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + char payload; + int cursor = 0; + String freetext = ""; // Text Buffer for Freetext Editor char messageStore[CANNED_MESSAGE_MODULE_MESSAGES_SIZE+1]; char *messages[CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT]; diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 7ff90d9ca..91f3f2edd 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -3,7 +3,6 @@ #include "input/RotaryEncoderInterruptImpl1.h" #include "input/UpDownInterruptImpl1.h" #include "input/cardKbI2cImpl.h" -#include "input/facesKbI2cImpl.h" #include "modules/AdminModule.h" #include "modules/CannedMessageModule.h" #include "modules/NodeInfoModule.h" @@ -53,8 +52,6 @@ void setupModules() upDownInterruptImpl1->init(); cardKbI2cImpl = new CardKbI2cImpl(); cardKbI2cImpl->init(); - facesKbI2cImpl = new FacesKbI2cImpl(); - facesKbI2cImpl->init(); #endif #if HAS_SCREEN cannedMessageModule = new CannedMessageModule();