mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-21 04:22:22 +00:00

Some checks are pending
Daily Packaging / package-ppa (jammy) (push) Waiting to run
Daily Packaging / package-ppa (noble) (push) Waiting to run
Daily Packaging / package-ppa (oracular) (push) Waiting to run
Daily Packaging / package-ppa (plucky) (push) Waiting to run
Daily Packaging / package-obs (push) Waiting to run
Daily Packaging / hook-copr (push) Waiting to run
CI / setup (check) (push) Waiting to run
CI / setup (esp32) (push) Waiting to run
CI / setup (esp32c3) (push) Waiting to run
CI / setup (esp32c6) (push) Waiting to run
CI / setup (esp32s3) (push) Waiting to run
CI / setup (nrf52840) (push) Waiting to run
CI / setup (rp2040) (push) Waiting to run
CI / setup (stm32) (push) Waiting to run
CI / check (push) Blocked by required conditions
CI / build-esp32 (push) Blocked by required conditions
CI / build-esp32-s3 (push) Blocked by required conditions
CI / build-esp32-c3 (push) Blocked by required conditions
CI / build-esp32-c6 (push) Blocked by required conditions
CI / build-nrf52 (push) Blocked by required conditions
CI / build-rpi2040 (push) Blocked by required conditions
CI / build-stm32 (push) Blocked by required conditions
CI / package-raspbian (push) Waiting to run
CI / package-raspbian-armv7l (push) Waiting to run
CI / package-native (push) Waiting to run
CI / build-debian-src (push) Waiting to run
CI / test-native (push) Waiting to run
CI / build-docker (push) Waiting to run
CI / after-checks (push) Blocked by required conditions
CI / gather-artifacts (esp32) (push) Blocked by required conditions
CI / gather-artifacts (esp32c3) (push) Blocked by required conditions
CI / gather-artifacts (esp32c6) (push) Blocked by required conditions
CI / gather-artifacts (esp32s3) (push) Blocked by required conditions
CI / gather-artifacts (nrf52840) (push) Blocked by required conditions
CI / gather-artifacts (rp2040) (push) Blocked by required conditions
CI / gather-artifacts (stm32) (push) Blocked by required conditions
CI / release-artifacts (push) Blocked by required conditions
CI / release-firmware (esp32) (push) Blocked by required conditions
CI / release-firmware (esp32c3) (push) Blocked by required conditions
CI / release-firmware (esp32c6) (push) Blocked by required conditions
CI / release-firmware (esp32s3) (push) Blocked by required conditions
CI / release-firmware (nrf52840) (push) Blocked by required conditions
CI / release-firmware (rp2040) (push) Blocked by required conditions
CI / release-firmware (stm32) (push) Blocked by required conditions
Flawfinder Scan / Flawfinder (push) Waiting to run
* Allow GPIO0; check for conflict with user button * Guard for no BUTTON_PIN; handle portduino * Portduino settings: attempt two We don't really need to #include radio code here just to check if the pin is RADIOLIB_NC. We're only interested if scanAndSelect pin matches user button pin, but they won't match if user button is RADIOLIB_NC. * Portduino attempt 3: glue
230 lines
7.6 KiB
C++
230 lines
7.6 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>
|
|
#ifdef ARCH_PORTDUINO // Only to check for pin conflict with user button
|
|
#include "platform/portduino/PortduinoGlue.h"
|
|
#endif
|
|
|
|
// 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;
|
|
|
|
// Determine which pin to use for the single scan-and-select button
|
|
// User can specify this by setting any of the inputbroker pins
|
|
// If all values are zero, we'll assume the user *does* want GPIO0
|
|
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
|
|
pin = 0; // GPIO 0 then
|
|
|
|
// Short circuit: if selected pin conficts with the user button
|
|
#if defined(ARCH_PORTDUINO)
|
|
int pinUserButton = 0;
|
|
if (settingsMap.count(user) != 0) {
|
|
pinUserButton = settingsMap[user];
|
|
}
|
|
#elif defined(USERPREFS_BUTTON_PIN)
|
|
int pinUserButton = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN;
|
|
#elif defined(BUTTON_PIN)
|
|
int pinUserButton = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
|
|
#else
|
|
int pinUserButton = config.device.button_gpio;
|
|
#endif
|
|
if (pin == pinUserButton) {
|
|
LOG_ERROR("ScanAndSelect conflict with user button");
|
|
return false;
|
|
}
|
|
|
|
// 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 |