mirror of
https://github.com/meshtastic/firmware.git
synced 2025-04-24 09:26:52 +00:00
207 lines
6.7 KiB
C++
207 lines
6.7 KiB
C++
#include "configuration.h"
|
|
|
|
// Normally these input methods are protected by guarding in setupModules
|
|
// In order to have the user button dismiss the canned message frame, this class lightly interacts with the Screen class
|
|
#if HAS_SCREEN
|
|
|
|
#include "ScanAndSelect.h"
|
|
#include "modules/CannedMessageModule.h"
|
|
#include <Throttle.h>
|
|
|
|
// Config
|
|
static const char name[] = "scanAndSelect"; // should match "allow input source" string
|
|
static constexpr uint32_t durationShortMs = 50;
|
|
static constexpr uint32_t durationLongMs = 1500;
|
|
static constexpr uint32_t durationAlertMs = 2000;
|
|
|
|
// Constructor: init base class
|
|
ScanAndSelectInput::ScanAndSelectInput() : concurrency::OSThread(name) {}
|
|
|
|
// Attempt to setup class; true if success.
|
|
// Called by setupModules method. Instance deleted if setup fails.
|
|
bool ScanAndSelectInput::init()
|
|
{
|
|
// Short circuit: Canned messages enabled?
|
|
if (!moduleConfig.canned_message.enabled)
|
|
return false;
|
|
|
|
// Short circuit: Using correct "input source"?
|
|
// Todo: protobuf enum instead of string?
|
|
if (strcasecmp(moduleConfig.canned_message.allow_input_source, name) != 0)
|
|
return false;
|
|
|
|
// Use any available inputbroker pin as the button
|
|
if (moduleConfig.canned_message.inputbroker_pin_press)
|
|
pin = moduleConfig.canned_message.inputbroker_pin_press;
|
|
else if (moduleConfig.canned_message.inputbroker_pin_a)
|
|
pin = moduleConfig.canned_message.inputbroker_pin_a;
|
|
else if (moduleConfig.canned_message.inputbroker_pin_b)
|
|
pin = moduleConfig.canned_message.inputbroker_pin_b;
|
|
else
|
|
return false; // Short circuit: no button found
|
|
|
|
// Set-up the button
|
|
pinMode(pin, INPUT_PULLUP);
|
|
attachInterrupt(pin, handleChangeInterrupt, CHANGE);
|
|
|
|
// Connect our class to the canned message module
|
|
inputBroker->registerSource(this);
|
|
|
|
LOG_INFO("Initialized 'Scan and Select' input for Canned Messages, using pin %d", pin);
|
|
return true; // Init succeded
|
|
}
|
|
|
|
// Runs periodically, unless sleeping between presses
|
|
int32_t ScanAndSelectInput::runOnce()
|
|
{
|
|
uint32_t now = millis();
|
|
|
|
// If: "no messages added" alert screen currently shown
|
|
if (alertingNoMessage) {
|
|
// Dismiss the alert screen several seconds after it appears
|
|
if (!Throttle::isWithinTimespanMs(alertingSinceMs, durationAlertMs)) {
|
|
alertingNoMessage = false;
|
|
screen->endAlert();
|
|
}
|
|
}
|
|
|
|
// If: Button is pressed
|
|
if (digitalRead(pin) == LOW) {
|
|
// New press
|
|
if (!held) {
|
|
downSinceMs = now;
|
|
}
|
|
|
|
// Existing press
|
|
else {
|
|
// Longer than shortpress window
|
|
// Long press not yet fired (prevent repeat firing while held)
|
|
if (!longPressFired && !Throttle::isWithinTimespanMs(downSinceMs, durationLongMs)) {
|
|
longPressFired = true;
|
|
longPress();
|
|
}
|
|
}
|
|
|
|
// Record the change of state: button is down
|
|
held = true;
|
|
}
|
|
|
|
// If: Button is not pressed
|
|
else {
|
|
// Button newly released
|
|
// Long press event didn't already fire
|
|
if (held && !longPressFired) {
|
|
// Duration within shortpress window
|
|
// - longer than durationShortPress (debounce)
|
|
// - shorter than durationLongPress
|
|
if (!Throttle::isWithinTimespanMs(downSinceMs, durationShortMs)) {
|
|
shortPress();
|
|
}
|
|
}
|
|
|
|
// Record the change of state: button is up
|
|
held = false;
|
|
longPressFired = false; // Re-Arm: allow another long press
|
|
}
|
|
|
|
// If thread's job is done, let it sleep
|
|
if (!held && !alertingNoMessage) {
|
|
Thread::canSleep = true;
|
|
return OSThread::disable();
|
|
}
|
|
|
|
// Run this method again is a few ms
|
|
return durationShortMs;
|
|
}
|
|
|
|
void ScanAndSelectInput::longPress()
|
|
{
|
|
// (If canned messages set)
|
|
if (cannedMessageModule->hasMessages()) {
|
|
// If module frame displayed already, send the current message
|
|
if (cannedMessageModule->shouldDraw())
|
|
raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT);
|
|
|
|
// Otherwise, initial long press opens the module frame
|
|
else
|
|
raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN);
|
|
}
|
|
|
|
// (If canned messages not set) tell the user
|
|
else
|
|
alertNoMessage();
|
|
}
|
|
|
|
void ScanAndSelectInput::shortPress()
|
|
{
|
|
// (If canned messages set) scroll to next message
|
|
if (cannedMessageModule->hasMessages())
|
|
raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN);
|
|
|
|
// (If canned messages not yet set) tell the user
|
|
else
|
|
alertNoMessage();
|
|
}
|
|
|
|
// Begin running runOnce at regular intervals
|
|
// Called from pin change interrupt
|
|
void ScanAndSelectInput::enableThread()
|
|
{
|
|
Thread::canSleep = false;
|
|
OSThread::enabled = true;
|
|
OSThread::setIntervalFromNow(0);
|
|
}
|
|
|
|
// Inform user (screen) that no canned messages have been added
|
|
// Automatically dismissed after several seconds
|
|
void ScanAndSelectInput::alertNoMessage()
|
|
{
|
|
alertingNoMessage = true;
|
|
alertingSinceMs = millis();
|
|
|
|
// Graphics code: the alert frame to show on screen
|
|
screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
|
display->setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
|
|
display->setFont(FONT_SMALL);
|
|
int16_t textX = display->getWidth() / 2;
|
|
int16_t textY = display->getHeight() / 2;
|
|
display->drawString(textX + x, textY + y, "No Canned Messages");
|
|
});
|
|
}
|
|
|
|
// Remove the canned message frame from screen
|
|
// Used to dismiss the module frame when user button pressed
|
|
// Returns true if the frame was previously displayed, and has now been closed
|
|
// Return value consumed by Screen class when determining how to handle user button
|
|
bool ScanAndSelectInput::dismissCannedMessageFrame()
|
|
{
|
|
if (cannedMessageModule->shouldDraw()) {
|
|
raiseEvent(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Feed input to the canned messages module
|
|
void ScanAndSelectInput::raiseEvent(_meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar key)
|
|
{
|
|
InputEvent e;
|
|
e.source = name;
|
|
e.inputEvent = key;
|
|
notifyObservers(&e);
|
|
}
|
|
|
|
// Pin change interrupt
|
|
void ScanAndSelectInput::handleChangeInterrupt()
|
|
{
|
|
// Because we need to detect both press and release (rising and falling edge), the interrupt itself can't determine the
|
|
// action. Instead, we start up the thread and get it to read the button for us
|
|
|
|
// The instance we're referring to here is created in setupModules()
|
|
scanAndSelectInput->enableThread();
|
|
}
|
|
|
|
ScanAndSelectInput *scanAndSelectInput = nullptr; // Instantiated in setupModules method. Deleted if unused, or init() fails
|
|
|
|
#endif |