Skip to content

Commit

Permalink
Merge branch 'master' into miltieIV2/add_support_for_adafruit_feather…
Browse files Browse the repository at this point in the history
…_rp2040_rfm95
  • Loading branch information
miltieIV2 authored Aug 19, 2024
2 parents fe7cee6 + 6de3ca4 commit dc41f53
Show file tree
Hide file tree
Showing 17 changed files with 399 additions and 39 deletions.
1 change: 0 additions & 1 deletion src/ButtonThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ int32_t ButtonThread::runOnce()
btnEvent = BUTTON_EVENT_NONE;
}

runASAP = false;
return 50;
}

Expand Down
260 changes: 260 additions & 0 deletions src/input/ExpressLRSFiveWay.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@

#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
85 changes: 85 additions & 0 deletions src/input/ExpressLRSFiveWay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
Input source for Radio Master Bandit Nano, and similar hardware.
Devices have a 5-button "resistor ladder" style joystick, read by ADC.
These devices do not use the ADC to monitor input voltage.
Much of this code taken directly from ExpressLRS FiveWayButton class:
https://github.com/ExpressLRS/ExpressLRS/tree/d9f56f8bd6f9f7144d5f01caaca766383e1e0950/src/lib/SCREEN/FiveWayButton
*/

#pragma once

#include "configuration.h"

#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE

#include <esp_adc_cal.h>
#include <soc/adc_channel.h>

#include "InputBroker.h"
#include "MeshService.h" // For adhoc ping action
#include "buzz.h"
#include "concurrency/OSThread.h"
#include "graphics/Screen.h" // Feedback for adhoc ping / toggle GPS
#include "main.h"
#include "modules/CannedMessageModule.h"

#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h" // For toggle GPS action
#endif

class ExpressLRSFiveWay : public Observable<const InputEvent *>, public concurrency::OSThread
{
private:
// Number of values in JOY_ADC_VALUES, if defined
// These must be ADC readings for {UP, DOWN, LEFT, RIGHT, ENTER, IDLE}
static constexpr size_t N_JOY_ADC_VALUES = 6;
static constexpr uint32_t KEY_DEBOUNCE_MS = 25;
static constexpr uint32_t KEY_LONG_PRESS_MS = 3000; // How many milliseconds to hold key for a long press

// This merged an enum used by the ExpressLRS code, with meshtastic canned message values
// Key names are kept simple, to allow user customizaton
typedef enum {
UP = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP,
DOWN = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN,
LEFT = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT,
RIGHT = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT,
OK = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT,
CANCEL = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL,
NO_PRESS = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE
} KeyType;

typedef enum { SHORT, LONG } PressLength;

// From ExpressLRS
int keyInProcess;
uint32_t keyDownStart;
bool isLongPressed;
const uint16_t joyAdcValues[N_JOY_ADC_VALUES] = {JOYSTICK_ADC_VALS};
uint16_t fuzzValues[N_JOY_ADC_VALUES];
void calcFuzzValues();
int readKey();
void update(int *keyValue, bool *keyLongPressed);

// Meshtastic code
void determineAction(KeyType key, PressLength length);
void sendKey(KeyType key);
inline bool inCannedMessageMenu() { return cannedMessageModule->shouldDraw(); }
int32_t runOnce() override;

// Simplified Meshtastic actions, for easier remapping by user
void toggleGPS();
void sendAdhocPing();
void shutdown();
void click();

bool alerting = false; // Is the screen showing an alert frame? Feedback for GPS toggle / adhoc ping actions
uint32_t alertingSinceMs = 0; // When did screen begin showing an alert frame? Used to auto-dismiss

public:
ExpressLRSFiveWay();
};

extern ExpressLRSFiveWay *expressLRSFiveWayInput;

#endif
4 changes: 2 additions & 2 deletions src/mesh/Channels.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ void Channels::initDefaultChannel(ChannelIndex chIndex)
channelSettings.psk.bytes[0] = defaultpskIndex;
channelSettings.psk.size = 1;
strncpy(channelSettings.name, "", sizeof(channelSettings.name));
channelSettings.module_settings.position_precision = 32; // default to sending location on the primary channel
channelSettings.module_settings.position_precision = 13; // default to sending location on the primary channel
channelSettings.has_module_settings = true;

ch.has_settings = true;
Expand Down Expand Up @@ -363,4 +363,4 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
int16_t Channels::setActiveByIndex(ChannelIndex channelIndex)
{
return setCrypto(channelIndex);
}
}
2 changes: 2 additions & 0 deletions src/mesh/Default.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#define default_min_wake_secs 10
#define default_screen_on_secs IF_ROUTER(1, 60 * 10)
#define default_node_info_broadcast_secs 3 * 60 * 60
#define default_neighbor_info_broadcast_secs 6 * 60 * 60
#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
#define min_neighbor_info_broadcast_secs 2 * 60 * 60

#define default_mqtt_address "mqtt.meshtastic.org"
#define default_mqtt_username "meshdev"
Expand Down
4 changes: 4 additions & 0 deletions src/modules/AdminModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,10 @@ void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
case meshtastic_ModuleConfig_neighbor_info_tag:
LOG_INFO("Setting module config: Neighbor Info\n");
moduleConfig.has_neighbor_info = true;
if (moduleConfig.neighbor_info.update_interval < min_neighbor_info_broadcast_secs) {
LOG_DEBUG("Tried to set update_interval too low, setting to %d\n", default_neighbor_info_broadcast_secs);
moduleConfig.neighbor_info.update_interval = default_neighbor_info_broadcast_secs;
}
moduleConfig.neighbor_info = c.payload_variant.neighbor_info;
break;
case meshtastic_ModuleConfig_detection_sensor_tag:
Expand Down
Loading

0 comments on commit dc41f53

Please sign in to comment.