-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Expand file tree
/
Copy pathExpressLRSFiveWay.cpp
More file actions
247 lines (215 loc) · 7.72 KB
/
Copy pathExpressLRSFiveWay.cpp
File metadata and controls
247 lines (215 loc) · 7.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#include "ExpressLRSFiveWay.h"
#include "Throttle.h"
#include "UptimeClock.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();
if (keyInProcess == NO_PRESS) {
// New key down
if (newKey != NO_PRESS) {
keyDownStart = Time::getMillis();
// DBGLN("down=%u", newKey);
}
} else {
// if key released
if (newKey == NO_PRESS) {
// DBGLN("up=%u", keyInProcess);
if (!isLongPressed) {
if (!Throttle::isWithinTimespanMs(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 (!Throttle::isWithinTimespanMs(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()
{
// Dismiss any alert frames after 2 seconds
// Feedback for GPS toggle / adhoc ping
// `alerting` is the armed flag, so alertingSinceMs never reaches the comparison unarmed.
if (alerting && Throttle::hasElapsed(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. keyDownStart
// is 0 until the first press of a boot, which is no activity rather than activity at time zero.
if (keyDownStart != 0 && Throttle::isWithinTimespanMs(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(INPUT_BROKER_CANCEL); // exit the menu (press imaginary cancel key)
else
sendKey(INPUT_BROKER_LEFT);
break;
case RIGHT:
if (inCannedMessageMenu()) // If in canned message menu:
sendKey(INPUT_BROKER_CANCEL); // exit the menu (press imaginary cancel key)
else
sendKey(INPUT_BROKER_RIGHT);
break;
case UP:
if (length == LONG)
toggleGPS();
else
sendKey(INPUT_BROKER_UP);
break;
case DOWN:
if (length == LONG)
sendAdhocPing();
else
sendKey(INPUT_BROKER_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(input_broker_event 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 (gps != nullptr) {
gps->toggleGpsMode();
screen->startAlert("GPS Toggled");
alerting = true;
alertingSinceMs = Time::getMillis();
}
#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 = Time::getMillis();
}
// Shutdown the node (enter deep-sleep)
// Contained as one method for easier remapping of buttons by user
void ExpressLRSFiveWay::shutdown()
{
sendKey(INPUT_BROKER_SHUTDOWN);
}
void ExpressLRSFiveWay::click()
{
sendKey(INPUT_BROKER_SELECT);
}
ExpressLRSFiveWay *expressLRSFiveWayInput = nullptr;
#endif