mirror of
https://github.com/meshtastic/firmware.git
synced 2025-05-01 19:46:42 +00:00

* Radio Master Bandit 5-Way Joystick: first draft Untested on genuine hardware * "Okay" moves to next frame, even when canned message disabled * Refactor to allow easier customization * Implement feedback from testing * guard toggleGPS() * show "Shutting down.." screen * split adhoc ping alert onto two lines * Don't block while waiting for shutdown Was preventing the alert from showing
260 lines
7.9 KiB
C++
260 lines
7.9 KiB
C++
|
|
#include "ExpressLRSFiveWay.h"
|
|
|
|
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
|
|
|
|
static const char inputSourceName[] = "ExpressLRS5Way"; // should match "allow input source" string
|
|
|
|
/**
|
|
* @brief Calculate fuzz: half the distance to the next nearest neighbor for each joystick position.
|
|
*
|
|
* The goal is to avoid collisions between joystick positions while still maintaining
|
|
* the widest tolerance for the analog value.
|
|
*
|
|
* Example: {10,50,800,1000,300,1600}
|
|
* If we just choose the minimum difference for this array the value would
|
|
* be 40/2 = 20.
|
|
*
|
|
* 20 does not leave enough room for the joystick position using 1600 which
|
|
* could have a +-100 offset.
|
|
*
|
|
* 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
|
|
|
|
// 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);
|
|
|
|
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;
|
|
|
|
// Express LRS: calculate the threshold for interpreting ADC values as various buttons
|
|
calcFuzzValues();
|
|
|
|
// Meshtastic: register with canned messages
|
|
inputBroker->registerSource(this);
|
|
}
|
|
|
|
// ExpressLRS: interpret reading as key events
|
|
void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed)
|
|
{
|
|
*keyValue = NO_PRESS;
|
|
|
|
int newKey = readKey();
|
|
uint32_t now = millis();
|
|
if (keyInProcess == NO_PRESS) {
|
|
// New key down
|
|
if (newKey != NO_PRESS) {
|
|
keyDownStart = now;
|
|
// DBGLN("down=%u", newKey);
|
|
}
|
|
} else {
|
|
// if key released
|
|
if (newKey == NO_PRESS) {
|
|
// DBGLN("up=%u", keyInProcess);
|
|
if (!isLongPressed) {
|
|
if ((now - 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 ((now - keyDownStart) > KEY_LONG_PRESS_MS) {
|
|
*keyValue = keyInProcess;
|
|
*keyLongPressed = true;
|
|
isLongPressed = true;
|
|
}
|
|
}
|
|
} // if keyInProcess != NO_PRESS
|
|
|
|
keyInProcess = newKey;
|
|
}
|
|
|
|
// Meshtastic: runs at regular intervals
|
|
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();
|
|
}
|
|
|
|
// 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);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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(CANCEL); // exit the menu (press imaginary cancel key)
|
|
else
|
|
sendKey(LEFT);
|
|
break;
|
|
|
|
case RIGHT:
|
|
if (inCannedMessageMenu()) // If in canned message menu:
|
|
sendKey(CANCEL); // exit the menu (press imaginary cancel key)
|
|
else
|
|
sendKey(RIGHT);
|
|
break;
|
|
|
|
case UP:
|
|
if (length == LONG)
|
|
toggleGPS();
|
|
else
|
|
sendKey(UP);
|
|
break;
|
|
|
|
case DOWN:
|
|
if (length == LONG)
|
|
sendAdhocPing();
|
|
else
|
|
sendKey(DOWN);
|
|
break;
|
|
|
|
case OK:
|
|
if (length == LONG)
|
|
shutdown();
|
|
else
|
|
click(); // Use instead of sendKey(OK). Works better when canned message module disabled
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Feed input to the canned messages module
|
|
void ExpressLRSFiveWay::sendKey(KeyType 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()
|
|
{
|
|
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
|
|
if (!config.device.disable_triple_click && (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);
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Shutdown the node (enter deep-sleep)
|
|
// Contained as one method for easier remapping of buttons by user
|
|
void ExpressLRSFiveWay::shutdown()
|
|
{
|
|
LOG_INFO("Shutdown from long press\n");
|
|
powerFSM.trigger(EVENT_PRESS);
|
|
screen->startAlert("Shutting down...");
|
|
// Don't set alerting = true. We don't want to auto-dismiss this alert.
|
|
|
|
playShutdownMelody(); // In case user adds a buzzer
|
|
|
|
shutdownAtMsec = millis() + 3000;
|
|
}
|
|
|
|
// Emulate user button, or canned message SELECT
|
|
// This is necessary as canned message module doesn't translate SELECT to user button presses if the module is disabled
|
|
// Contained as one method for easier remapping of buttons by user
|
|
void ExpressLRSFiveWay::click()
|
|
{
|
|
if (!moduleConfig.canned_message.enabled)
|
|
powerFSM.trigger(EVENT_PRESS);
|
|
else
|
|
sendKey(OK);
|
|
}
|
|
|
|
ExpressLRSFiveWay *expressLRSFiveWayInput = nullptr;
|
|
|
|
#endif |