/* * Copyright (C) EdgeTX * * Based on code named * opentx - https://github.com/opentx/opentx * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "keys.h" #include "edgetx_helpers.h" #include "definitions.h" #include "timers_driver.h" #include "hal/watchdog_driver.h" #include "hal/rotary_encoder.h" #include "dataconstants.h" #if !defined(BOOT) #include "edgetx.h" #endif // long key press minimum duration (x10ms), // must be less than KEY_REPEAT_DELAY #define KEY_LONG_DELAY 32 // press longer than this enables repeat (but does not fire it yet) #define KEY_REPEAT_DELAY 40 // repeat trigger, used in combination with m_state // to produce decreasing times between repeat events #define KEY_REPEAT_TRIGGER 48 #define KEY_REPEAT_PAUSE_DELAY 64 // defines how many bits are used for debounce #if defined(SIMU) #define FILTERBITS 1 #else #define FILTERBITS 4 #endif #define FILTER_MASK ((1 << FILTERBITS) - 1) #define KSTATE_OFF 0 #define KSTATE_RPTDELAY 95 #define KSTATE_START 97 #define KSTATE_PAUSE 98 #define KFLAG_KILLED 1 #define KFLAG_LONG_PRESS 2 // global last event event_t s_evt; event_t s_trim_evt; InactivityData inactivity = {0}; class Key { private: uint8_t m_vals = 0; uint8_t m_cnt = 0; uint8_t m_state = 0; uint8_t m_flags = 0; public: event_t input(bool val); bool pressed() const { return (m_vals & FILTER_MASK) == FILTER_MASK; } void pauseEvents(); void killEvents(); }; event_t Key::input(bool val) { event_t evt = 0; // store new value in the bits that hold the key state history (used for debounce) uint8_t t_vals = m_vals ; t_vals <<= 1 ; if (val) t_vals |= 1; m_vals = t_vals ; m_cnt++; if ((m_state || m_flags) && m_vals == 0) { // key is released #if defined(COLORLCD) if ((m_flags & (KFLAG_KILLED)) == 0) { evt = (m_flags & KFLAG_LONG_PRESS) ? _MSK_KEY_LONG_BRK : _MSK_KEY_BREAK; } #else if ((m_flags & (KFLAG_KILLED)) == 0) { evt = _MSK_KEY_BREAK; } #endif m_state = KSTATE_OFF; m_cnt = 0; m_flags = 0; return evt; } if (m_flags & KFLAG_KILLED) return evt; switch (m_state) { case KSTATE_OFF: if (m_vals == FILTER_MASK) { m_state = KSTATE_START; m_cnt = 0; } break; case KSTATE_START: evt = _MSK_KEY_FIRST; inactivity.counter = 0; m_state = KSTATE_RPTDELAY; m_cnt = 0; break; case KSTATE_RPTDELAY: // gruvin: delay state before first key repeat if (m_cnt == KEY_LONG_DELAY) { // generate long key press evt = _MSK_KEY_LONG; m_flags |= KFLAG_LONG_PRESS; } if (m_cnt == KEY_REPEAT_DELAY) { m_state = 16; m_cnt = 0; } break; case 16: case 8: case 4: case 2: if (m_cnt >= KEY_REPEAT_TRIGGER) { //3 6 12 24 48 pulses in every 480ms m_state >>= 1; m_cnt = 0; } // no break case 1: if ((m_cnt & (m_state - 1)) == 0) { // this produces repeat events that at first repeat slowly and then increase in speed evt = _MSK_KEY_REPT; } break; case KSTATE_PAUSE: //pause repeat events if (m_cnt >= KEY_REPEAT_PAUSE_DELAY) { m_state = 8; m_cnt = 0; } break; } return evt; } void Key::pauseEvents() { m_state = KSTATE_PAUSE; m_cnt = 0; } void Key::killEvents() { if (m_state) m_flags |= KFLAG_KILLED; } static Key keys[MAX_KEYS]; static Key trim_keys[MAX_TRIMS * 2]; #if !defined(BOOT) && defined(KEYS_LOCK_KEY1) && defined(KEYS_LOCK_KEY2) // Per-target key combo (from hal.h KEYS_LOCK_KEY1/KEY2) long-pressed together // toggles a software key lock. static bool s_keys_locked = false; static bool s_keys_lock_notify = false; static uint8_t s_keys_lock_combo_cnt = 0; bool areKeysLocked() { return s_keys_locked; } bool consumeKeysLockToggleEvent() { if (!s_keys_lock_notify) return false; s_keys_lock_notify = false; return true; } void setKeyLockedState(bool state) { if (g_eeGeneral.keyLockEnabled && s_keys_locked != state) { s_keys_locked = state; s_keys_lock_notify = true; } } #else bool areKeysLocked() { return false; } bool consumeKeysLockToggleEvent() { return false; } void setKeyLockedState(bool state) {} #endif /** * @brief returns true if there is an event waiting. * */ bool isEvent() { return s_evt != 0; } void pushEvent(event_t evt) { s_evt = evt; } event_t getEvent() { auto event = s_evt; s_evt = 0; return event; } void pushTrimEvent(event_t evt) { s_trim_evt = evt; } event_t getTrimEvent() { auto event = s_trim_evt; s_trim_evt = 0; return event; } // Introduce a slight delay in the key repeat sequence void pauseEvents(event_t event) { event = EVT_KEY_MASK(event); if (event < (int)DIM(keys)) keys[event].pauseEvents(); } void pauseTrimEvents(event_t event) { event = EVT_KEY_MASK(event); if (event < (int)DIM(trim_keys)) trim_keys[event].pauseEvents(); } // Disables any further event generation (BREAK and REPEAT) for this key, // until the key is released void killEvents(event_t event) { event = EVT_KEY_MASK(event); if (event < (int)DIM(keys)) { keys[event].killEvents(); } } void killTrimEvents(event_t event) { event = EVT_KEY_MASK(event); if (event < (int)DIM(trim_keys)) { trim_keys[event].killEvents(); } } void killAllEvents() { for (uint8_t key = 0; key < DIM(keys); key++) { keys[key].killEvents(); } } bool waitKeysReleased() { // loop until all keys are up #if !defined(BOOT) tmr10ms_t start = get_tmr10ms(); #endif while (keyDown()) { WDG_RESET(); #if !defined(BOOT) if ((get_tmr10ms() - start) >= 300) { // wait no more than 3 seconds //timeout expired, at least one key stuck return false; } #endif } memclear(keys, sizeof(keys)); pushEvent(0); return true; } #if defined(PCBXLITE) && !defined(BOOT) uint32_t _readTrims() { uint32_t trims = readTrims(); uint8_t lr = trims & 0x3; uint8_t ud = trims & 0xc; bool shift = readKeys() & (1 << KEY_SHIFT); // Mode 1 or 2 - AIL on right stick bool ailRight = g_eeGeneral.stickMode < 2; // Mode 2 or 4 - ELE on right stick bool eleRight = (g_eeGeneral.stickMode & 1) == 1; // Ensure non-shifted trims are AIL and ELE if (ailRight == !shift) lr <<= 6; if (eleRight == !shift) ud <<= 2; return lr | ud; } #define READ_TRIMS() _readTrims() #else #define READ_TRIMS() readTrims() #endif bool keyDown() { return readKeys() || readTrims(); } bool trimDown(uint8_t idx) { return READ_TRIMS() & (1 << idx); } bool keysGetState(uint8_t key) { if (key >= MAX_KEYS) return 0; return keys[key].pressed(); } uint8_t keysGetTrimState(uint8_t trim) { if (trim >= keysGetMaxTrims() * 2) return 0; return trim_keys[trim].pressed(); } #if defined(USE_HATS_AS_KEYS) #define ROTARY_EMU_KEY_REPEAT_RATE 12 // times 10 [ms] #if defined(BOOT) bool getHatsAsKeys() { return true; } bool getTransposeHatsForLUA() { return false; } #else static bool _trims_as_buttons = false; static bool _trims_as_buttons_LUA = false; void setHatsAsKeys(bool val) { _trims_as_buttons = val; } bool getHatsAsKeys() { return _trims_as_buttons; } void setTransposeHatsForLUA(bool val) { _trims_as_buttons_LUA = val; } bool getTransposeHatsForLUA() { return _trims_as_buttons_LUA; } int16_t getEmuRotaryData() { static bool rotaryTrimPressed = false; static tmr10ms_t timePressed = 0; if (getHatsAsKeys() || getTransposeHatsForLUA()) { tmr10ms_t now = get_tmr10ms(); if (rotaryTrimPressed) { if (now < (timePressed + ROTARY_EMU_KEY_REPEAT_RATE)) return 0; rotaryTrimPressed = false; } auto trims = readTrims(); if (trims & (1 << 4)) { rotaryTrimPressed = true; timePressed = now; return 1; } if (trims & (1 << 5)) { rotaryTrimPressed = true; timePressed = now; return -1; } } return 0; } #endif static void transpose_trims(uint32_t *keys) { #if defined(BOOT) auto trims = readTrims(); if (trims & (1 << 4)) *keys |= 1 << KEY_DOWN; // right hat, down 0x10 if (trims & (1 << 5)) *keys |= 1 << KEY_UP; // right hat, up 0x20 #else static uint8_t state = 0; bool allowModeSwitch = ((g_model.hatsMode == HATSMODE_GLOBAL && g_eeGeneral.hatsMode == HATSMODE_SWITCHABLE) || (g_model.hatsMode == HATSMODE_SWITCHABLE)) && !getTransposeHatsForLUA(); if (allowModeSwitch) { static bool lastExitState = false; static bool lastEnterState = false; bool exitState = *keys & (1 << KEY_EXIT); // edge detection for EXIT and ENTER keys bool enterState = *keys & (1 << KEY_ENTER); bool exitPressed = !lastExitState && exitState; bool exitReleased = lastExitState && !exitState; bool enterPressed = !lastEnterState && enterState; lastExitState = exitState; lastEnterState = enterState; switch (state) { case 0: // idle state waiting for EXIT or ENTER key if (exitPressed) { state = 1; } break; case 1: // state EXIT received if (exitReleased) { // if exit released go back to idle state state = 0; break; } if (enterPressed) { // ENTER received with EXIT still pressed setHatsAsKeys(!getHatsAsKeys()); // change mode and don't forward // EXIT and ENTER keys killEvents(KEY_EXIT); killEvents(KEY_ENTER); state = 0; // go to for EXIT to be released break; } break; } } else state = 0; // state machine in idle if not in mode "BOTH" if (getHatsAsKeys() || // map hats to keys in button mode or LUA active getTransposeHatsForLUA()) { auto trims = readTrims(); // spare key in buttons mode: left hat left // if (trims & (1 << 0)) *keys |= 1 << tbd; // left hat, left 0x01 if (trims & (1 << 1)) *keys |= 1 << KEY_MODEL; // left hat, right 0x02 if (trims & (1 << 2)) *keys |= 1 << KEY_TELE; // left hat, down 0x04 if (trims & (1 << 3)) *keys |= 1 << KEY_SYS; // left hat, up 0x08 if (trims & (1 << 6)) *keys |= 1 << KEY_PAGEUP; // rht, left 0x40 if (trims & (1 << 7)) *keys |= 1 << KEY_PAGEDN; // rht, right 0x80 } #endif } #endif // Mapping for: // - quick menu shortcut key on radios with limited keys // - radios with single PAGE key // - radios with a SHIFT key uint16_t keyMapping(uint16_t event) { // TODO: this needs to be done in radio specific code! #if defined(RADIO_PA01) if (event == EVT_KEY_BREAK(KEY_MODEL)) return EVT_KEY_BREAK(KEY_SYS); #endif // TODO: this needs to be done in radio specific code! #if defined(RADIO_ST16) if (event == EVT_KEY_LONG(KEY_PAGEDN)) return EVT_KEY_BREAK(KEY_SYS); #endif // Radio with single PAGEDN key if (!keyIsSupported(KEY_PAGEUP)) { if (event == EVT_KEY_LONG(KEY_PAGEDN)) { // Convert long press PAGEDN to short press PAGEUP killEvents(KEY_PAGEDN); return EVT_KEY_BREAK(KEY_PAGEUP); } } // SHIFT key should not trigger REPT events if (event == EVT_KEY_REPT(KEY_SHIFT)) return 0; return event; } uint8_t keysPollingCycle() { uint32_t trims_input; pollKeys(); uint32_t keys_input = readKeys(); #if defined(USE_HATS_AS_KEYS) transpose_trims(&keys_input); if (getHatsAsKeys() || getTransposeHatsForLUA()) { trims_input = 0; } else { trims_input = readTrims(); } #else trims_input = READ_TRIMS(); #endif #if !defined(BOOT) && defined(KEYS_LOCK_KEY1) && defined(KEYS_LOCK_KEY2) // Combo keys (from hal.h) long-pressed together toggle the key lock; killing // the per-key events while both are held prevents their individual actions // from firing. if (g_eeGeneral.keyLockEnabled && keyIsSupported(KEYS_LOCK_KEY1) && keyIsSupported(KEYS_LOCK_KEY2)) { bool both = (keys_input & (1u << KEYS_LOCK_KEY1)) && (keys_input & (1u << KEYS_LOCK_KEY2)); if (both) { keys[KEYS_LOCK_KEY1].killEvents(); keys[KEYS_LOCK_KEY2].killEvents(); if (s_keys_lock_combo_cnt < 0xFF) s_keys_lock_combo_cnt++; if (s_keys_lock_combo_cnt == KEY_LONG_DELAY) { s_keys_locked = !s_keys_locked; s_keys_lock_notify = true; } } else { s_keys_lock_combo_cnt = 0; } } else { // Feature disabled: never leave keys stuck locked. s_keys_locked = false; s_keys_lock_combo_cnt = 0; } #endif for (int i = 0; i < MAX_KEYS; i++) { event_t evt = keys[i].input(keys_input & (1 << i)); if (evt) { evt = keyMapping(evt | i); #if !defined(BOOT) && defined(KEYS_LOCK_KEY1) && defined(KEYS_LOCK_KEY2) if (evt) { if (!s_keys_locked) { pushEvent(evt); } else if ((evt & _MSK_KEY_FLAGS) == _MSK_KEY_FIRST) { // Re-show the "Keys locked" toast on each new key press while locked. s_keys_lock_notify = true; } } #else if (evt) pushEvent(evt); #endif } } auto trim_switches = keysGetMaxTrims() * 2; for (int i = 0; i < trim_switches; i++) { event_t evt = trim_keys[i].input(trims_input & (1 << i)); if (evt) pushTrimEvent(evt | i); } // Report trims separately so they count as controls, not keys, for the // backlight. Nav hats (USE_HATS_AS_KEYS) are folded into keys_input. uint8_t activity = 0; if (keys_input) activity |= KEY_ACTIVITY_KEYS; if (trims_input) activity |= KEY_ACTIVITY_TRIMS; return activity; } #if !defined(COLORLCD) #define ROTENC_DELAY_MIDSPEED 32 #define ROTENC_DELAY_HIGHSPEED 16 int8_t rotencSpeed = ROTENC_LOWSPEED; int8_t rotaryEncoderGetAccel() { return rotencSpeed; } void rotaryEncoderResetAccel() { rotencSpeed = ROTENC_LOWSPEED; } bool rotaryEncoderPollingCycle() { static rotenc_t rePreviousValue; static bool cw = false; rotenc_t reNewValue = rotaryEncoderGetValue(); rotenc_t scrollRE = reNewValue - rePreviousValue; if (scrollRE) { static uint32_t lastEvent; rePreviousValue = reNewValue; bool new_cw = (scrollRE < 0) ? false : true; if ((g_tmr10ms - lastEvent >= 10) || (cw == new_cw)) { // 100ms pushEvent(new_cw ? EVT_ROTARY_RIGHT : EVT_ROTARY_LEFT); // rotary encoder navigation speed (acceleration) detection/calculation static uint32_t delay = 2 * ROTENC_DELAY_MIDSPEED; if (new_cw == cw) { // Modified moving average filter used for smoother change of speed delay = (((g_tmr10ms - lastEvent) << 3) + delay) >> 1; } else { delay = 2 * ROTENC_DELAY_MIDSPEED; } if (delay < ROTENC_DELAY_HIGHSPEED) rotencSpeed = ROTENC_HIGHSPEED; else if (delay < ROTENC_DELAY_MIDSPEED) rotencSpeed = ROTENC_MIDSPEED; else rotencSpeed = ROTENC_LOWSPEED; cw = new_cw; lastEvent = g_tmr10ms; } return true; } return false; } #endif