Merge branch 'meshtastic:master' into master

This commit is contained in:
Mictronics 2024-02-13 18:26:04 +01:00 committed by GitHub
commit 59c4321cc3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 319 additions and 237 deletions

219
src/ButtonThread.cpp Normal file
View File

@ -0,0 +1,219 @@
#include "ButtonThread.h"
#include "GPS.h"
#include "MeshService.h"
#include "PowerFSM.h"
#include "RadioLibInterface.h"
#include "buzz.h"
#include "graphics/Screen.h"
#include "main.h"
#include "modules/ExternalNotificationModule.h"
#include "power.h"
#ifdef ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
#endif
#define DEBUG_BUTTONS 0
#if DEBUG_BUTTONS
#define LOG_BUTTON(...) LOG_DEBUG(__VA_ARGS__)
#else
#define LOG_BUTTON(...)
#endif
using namespace concurrency;
volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BUTTON_EVENT_NONE;
ButtonThread::ButtonThread() : OSThread("Button")
{
#if defined(ARCH_PORTDUINO) || defined(BUTTON_PIN)
#if defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
userButton = OneButton(settingsMap[user], true, true);
LOG_DEBUG("Using GPIO%02d for button\n", settingsMap[user]);
}
#elif defined(BUTTON_PIN)
int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
this->userButton = OneButton(pin, true, true);
LOG_DEBUG("Using GPIO%02d for button\n", pin);
#endif
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
pinMode(pin, INPUT_PULLUP_SENSE);
#endif
userButton.attachClick(userButtonPressed);
userButton.setClickMs(250);
userButton.setPressMs(c_longPressTime);
userButton.setDebounceMs(1);
userButton.attachDoubleClick(userButtonDoublePressed);
userButton.attachMultiClick(userButtonMultiPressed);
#ifndef T_DECK // T-Deck immediately wakes up after shutdown, so disable this function
userButton.attachLongPressStart(userButtonPressedLongStart);
userButton.attachLongPressStop(userButtonPressedLongStop);
#endif
#if defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
wakeOnIrq(settingsMap[user], FALLING);
#else
static OneButton *pBtn = &userButton; // only one instance of ButtonThread is created, so static is safe
attachInterrupt(
pin,
[]() {
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
pBtn->tick();
},
CHANGE);
#endif
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
#endif
userButtonAlt.attachClick(userButtonPressed);
userButtonAlt.setClickMs(250);
userButtonAlt.setPressMs(c_longPressTime);
userButtonAlt.setDebounceMs(1);
userButtonAlt.attachDoubleClick(userButtonDoublePressed);
userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
userButtonAlt.attachLongPressStop(userButtonPressedLongStop);
wakeOnIrq(BUTTON_PIN_ALT, FALLING);
#endif
#ifdef BUTTON_PIN_TOUCH
userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
userButtonTouch.attachClick(touchPressed);
wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
#endif
}
int32_t ButtonThread::runOnce()
{
// If the button is pressed we suppress CPU sleep until release
canSleep = true; // Assume we should not keep the board awake
#if defined(BUTTON_PIN)
userButton.tick();
canSleep &= userButton.isIdle();
#elif defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
userButton.tick();
canSleep &= userButton.isIdle();
}
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt.tick();
canSleep &= userButtonAlt.isIdle();
#endif
#ifdef BUTTON_PIN_TOUCH
userButtonTouch.tick();
canSleep &= userButtonTouch.isIdle();
#endif
if (btnEvent != BUTTON_EVENT_NONE) {
switch (btnEvent) {
case BUTTON_EVENT_PRESSED: {
LOG_BUTTON("press!\n");
#ifdef BUTTON_PIN
if (((config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN) !=
moduleConfig.canned_message.inputbroker_pin_press) ||
!(moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.rotary1_enabled) ||
!moduleConfig.canned_message.enabled) {
powerFSM.trigger(EVENT_PRESS);
}
#endif
#if defined(ARCH_PORTDUINO)
if ((settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) &&
(settingsMap[user] != moduleConfig.canned_message.inputbroker_pin_press) ||
!moduleConfig.canned_message.enabled) {
powerFSM.trigger(EVENT_PRESS);
}
#endif
break;
}
case BUTTON_EVENT_DOUBLE_PRESSED: {
LOG_BUTTON("Double press!\n");
#if defined(USE_EINK) && defined(PIN_EINK_EN)
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
#endif
service.refreshLocalMeshNode();
service.sendNetworkPing(NODENUM_BROADCAST, true);
if (screen)
screen->print("Sent ad-hoc ping\n");
break;
}
case BUTTON_EVENT_MULTI_PRESSED: {
LOG_BUTTON("Multi press!\n");
if (!config.device.disable_triple_click && (gps != nullptr)) {
gps->toggleGpsMode();
if (screen)
screen->forceDisplay();
}
break;
}
case BUTTON_EVENT_LONG_PRESSED: {
LOG_BUTTON("Long press!\n");
powerFSM.trigger(EVENT_PRESS);
if (screen)
screen->startShutdownScreen();
playBeep();
break;
}
// Do actual shutdown when button released, otherwise the button release
// may wake the board immediatedly.
case BUTTON_EVENT_LONG_RELEASED: {
LOG_INFO("Shutdown from long press\n");
playShutdownMelody();
delay(3000);
power->shutdown();
break;
}
case BUTTON_EVENT_TOUCH_PRESSED: {
LOG_BUTTON("Touch press!\n");
if (screen)
screen->forceDisplay();
break;
}
default:
break;
}
btnEvent = BUTTON_EVENT_NONE;
}
return 50;
}
/**
* Watch a GPIO and if we get an IRQ, wake the main thread.
* Use to add wake on button press
*/
void ButtonThread::wakeOnIrq(int irq, int mode)
{
attachInterrupt(
irq,
[] {
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
},
FALLING);
}
void ButtonThread::userButtonPressedLongStart()
{
if (millis() > c_holdOffTime) {
btnEvent = BUTTON_EVENT_LONG_PRESSED;
}
}
void ButtonThread::userButtonPressedLongStop()
{
if (millis() > c_holdOffTime) {
btnEvent = BUTTON_EVENT_LONG_RELEASED;
}
}

View File

@ -1,34 +1,29 @@
#include "PowerFSM.h"
#include "RadioLibInterface.h"
#include "buzz.h"
#pragma once
#include "OneButton.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#include "graphics/Screen.h"
#include "main.h"
#include "modules/ExternalNotificationModule.h"
#include "power.h"
#include <OneButton.h>
namespace concurrency
{
/**
* Watch a GPIO and if we get an IRQ, wake the main thread.
* Use to add wake on button press
*/
void wakeOnIrq(int irq, int mode)
{
attachInterrupt(
irq,
[] {
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
},
FALLING);
}
class ButtonThread : public concurrency::OSThread
{
// Prepare for button presses
public:
static const uint32_t c_longPressTime = 5000; // shutdown after 5s
static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
enum ButtonEventType {
BUTTON_EVENT_NONE,
BUTTON_EVENT_PRESSED,
BUTTON_EVENT_DOUBLE_PRESSED,
BUTTON_EVENT_MULTI_PRESSED,
BUTTON_EVENT_LONG_PRESSED,
BUTTON_EVENT_LONG_RELEASED,
BUTTON_EVENT_TOUCH_PRESSED
};
ButtonThread();
int32_t runOnce() override;
private:
#ifdef BUTTON_PIN
OneButton userButton;
#endif
@ -41,200 +36,17 @@ class ButtonThread : public concurrency::OSThread
#if defined(ARCH_PORTDUINO)
OneButton userButton;
#endif
static bool shutdown_on_long_stop;
public:
static uint32_t longPressTime;
// set during IRQ
static volatile ButtonEventType btnEvent;
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
ButtonThread() : OSThread("Button")
{
#if defined(ARCH_PORTDUINO) || defined(BUTTON_PIN)
#if defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
userButton = OneButton(settingsMap[user], true, true);
#elif defined(BUTTON_PIN)
int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
userButton = OneButton(pin, true, true);
#endif
static void wakeOnIrq(int irq, int mode);
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
pinMode(pin, INPUT_PULLUP_SENSE);
#endif
userButton.attachClick(userButtonPressed);
userButton.setClickMs(400);
userButton.setPressMs(1000);
userButton.setDebounceMs(10);
userButton.attachDuringLongPress(userButtonPressedLong);
userButton.attachDoubleClick(userButtonDoublePressed);
userButton.attachMultiClick(userButtonMultiPressed);
userButton.attachLongPressStart(userButtonPressedLongStart);
userButton.attachLongPressStop(userButtonPressedLongStop);
#if defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
wakeOnIrq(settingsMap[user], FALLING);
#else
static OneButton *pBtn = &userButton; // only one instance of ButtonThread is created, so static is safe
attachInterrupt(
pin,
[]() {
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
pBtn->tick();
},
CHANGE);
#endif
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
#endif
userButtonAlt.attachClick(userButtonPressed);
userButtonAlt.attachDuringLongPress(userButtonPressedLong);
userButtonAlt.attachDoubleClick(userButtonDoublePressed);
userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
userButtonAlt.attachLongPressStop(userButtonPressedLongStop);
wakeOnIrq(BUTTON_PIN_ALT, FALLING);
#endif
#ifdef BUTTON_PIN_TOUCH
userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
userButtonTouch.attachClick(touchPressed);
wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
#endif
}
protected:
/// If the button is pressed we suppress CPU sleep until release
int32_t runOnce() override
{
canSleep = true; // Assume we should not keep the board awake
#if defined(BUTTON_PIN)
userButton.tick();
canSleep &= userButton.isIdle();
#elif defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
userButton.tick();
canSleep &= userButton.isIdle();
}
#endif
#ifdef BUTTON_PIN_ALT
userButtonAlt.tick();
canSleep &= userButtonAlt.isIdle();
#endif
#ifdef BUTTON_PIN_TOUCH
userButtonTouch.tick();
canSleep &= userButtonTouch.isIdle();
#endif
// if (!canSleep) LOG_DEBUG("Suppressing sleep!\n");
// else LOG_DEBUG("sleep ok\n");
return 50;
}
private:
static void touchPressed()
{
screen->forceDisplay();
LOG_DEBUG("touch press!\n");
}
static void userButtonPressed()
{
// LOG_DEBUG("press!\n");
#ifdef BUTTON_PIN
if (((config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN) !=
moduleConfig.canned_message.inputbroker_pin_press) ||
!(moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.rotary1_enabled) ||
!moduleConfig.canned_message.enabled) {
powerFSM.trigger(EVENT_PRESS);
}
#endif
#if defined(ARCH_PORTDUINO)
if ((settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) &&
(settingsMap[user] != moduleConfig.canned_message.inputbroker_pin_press) ||
!moduleConfig.canned_message.enabled) {
powerFSM.trigger(EVENT_PRESS);
}
#endif
}
static void userButtonPressedLong()
{
// LOG_DEBUG("Long press!\n");
// If user button is held down for 5 seconds, shutdown the device.
if ((millis() - longPressTime > 5000) && (longPressTime > 0)) {
#if defined(ARCH_NRF52) || defined(ARCH_ESP32)
// Do actual shutdown when button released, otherwise the button release
// may wake the board immediatedly.
if ((!shutdown_on_long_stop) && (millis() > 30 * 1000)) {
screen->startShutdownScreen();
LOG_INFO("Shutdown from long press");
playBeep();
#ifdef PIN_LED1
ledOff(PIN_LED1);
#endif
#ifdef PIN_LED2
ledOff(PIN_LED2);
#endif
#ifdef PIN_LED3
ledOff(PIN_LED3);
#endif
shutdown_on_long_stop = true;
}
#endif
} else {
// LOG_DEBUG("Long press %u\n", (millis() - longPressTime));
}
}
static void userButtonDoublePressed()
{
#if defined(USE_EINK) && defined(PIN_EINK_EN)
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
#endif
screen->print("Sent ad-hoc ping\n");
service.refreshLocalMeshNode();
service.sendNetworkPing(NODENUM_BROADCAST, true);
}
static void userButtonMultiPressed()
{
if (!config.device.disable_triple_click && (gps != nullptr)) {
gps->toggleGpsMode();
screen->forceDisplay();
}
}
static void userButtonPressedLongStart()
{
#ifdef T_DECK
// False positive long-press triggered on T-Deck with i2s audio, so short circuit
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
return;
}
#endif
if (millis() > 30 * 1000) {
LOG_DEBUG("Long press start!\n");
longPressTime = millis();
}
}
static void userButtonPressedLongStop()
{
if (millis() > 30 * 1000) {
LOG_DEBUG("Long press stop!\n");
longPressTime = 0;
if (shutdown_on_long_stop) {
playShutdownMelody();
delay(3000);
power->shutdown();
}
}
}
// IRQ callbacks
static void touchPressed() { btnEvent = BUTTON_EVENT_TOUCH_PRESSED; }
static void userButtonPressed() { btnEvent = BUTTON_EVENT_PRESSED; }
static void userButtonDoublePressed() { btnEvent = BUTTON_EVENT_DOUBLE_PRESSED; }
static void userButtonMultiPressed() { btnEvent = BUTTON_EVENT_MULTI_PRESSED; }
static void userButtonPressedLongStart();
static void userButtonPressedLongStop();
};
} // namespace concurrency

View File

@ -209,6 +209,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
{
uint32_t raw = 0;
uint8_t raw_c = 0;
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
#ifdef ADC_CTRL
@ -226,7 +227,37 @@ class AnalogBatteryLevel : public HasBatteryLevel
digitalWrite(ADC_CTRL, LOW);
}
#endif
#else // ADC2
#else // ADC2
#ifdef ADC_CTRL
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
pinMode(ADC_CTRL, OUTPUT);
digitalWrite(ADC_CTRL, LOW); // ACTIVE LOW
delay(10);
#endif
#endif // End ADC_CTRL
#ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3
// ADC2 wifi bug workaround not required, breaks compile
// On ESP32S3, ADC2 can take turns with Wifi (?)
int32_t adc_buf;
esp_err_t read_result;
// Multiple samples
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
adc_buf = 0;
read_result = -1;
read_result = adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
if (read_result == ESP_OK) {
raw += adc_buf;
raw_c++; // Count valid samples
} else {
LOG_DEBUG("An attempt to sample ADC2 failed\n");
}
}
#else // Other ESP32
int32_t adc_buf = 0;
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
// ADC2 wifi bug workaround, see
@ -235,10 +266,18 @@ class AnalogBatteryLevel : public HasBatteryLevel
SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV);
adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
raw += adc_buf;
raw_c++;
}
#endif // BAT_MEASURE_ADC_UNIT
raw = raw / BATTERY_SENSE_SAMPLES;
return raw;
#endif
#ifdef ADC_CTRL
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
digitalWrite(ADC_CTRL, HIGH);
#endif
#endif // End ADC_CTRL
#endif // End BAT_MEASURE_ADC_UNIT
return (raw / (raw_c < 1 ? 1 : raw_c));
}
#endif
@ -364,8 +403,11 @@ bool Power::analogInit()
adc1_config_channel_atten(adc_channel, atten);
#else // ADC2
adc2_config_channel_atten(adc_channel, atten);
#ifndef CONFIG_IDF_TARGET_ESP32S3
// ADC2 wifi bug workaround
// Not required with ESP32S3, breaks compile
RTC_reg_b = READ_PERI_REG(SENS_SAR_READ_CTRL2_REG);
#endif
#endif
// calibrate ADC
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_characs);
@ -374,7 +416,14 @@ bool Power::analogInit()
LOG_INFO("ADCmod: ADC characterization based on Two Point values stored in eFuse\n");
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
LOG_INFO("ADCmod: ADC characterization based on reference voltage stored in eFuse\n");
} else {
}
#ifdef CONFIG_IDF_TARGET_ESP32S3
// ESP32S3
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
LOG_INFO("ADCmod: ADC Characterization based on Two Point values and fitting curve coefficients stored in eFuse\n");
}
#endif
else {
LOG_INFO("ADCmod: ADC characterization based on default reference voltage\n");
}
#if defined(HELTEC_V3) || defined(HELTEC_WSL_V3)
@ -390,7 +439,10 @@ bool Power::analogInit()
analogReference(AR_INTERNAL); // 3.6V
#endif
#endif // ARCH_NRF52
#ifndef ARCH_ESP32
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
#endif
batteryLevel = &analogLevel;
return true;

View File

@ -191,15 +191,10 @@ static int32_t ledBlinker()
uint32_t timeLastPowered = 0;
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
bool ButtonThread::shutdown_on_long_stop = false;
#endif
static Periodic *ledPeriodic;
static OSThread *powerFSMthread;
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
static OSThread *buttonThread;
uint32_t ButtonThread::longPressTime = 0;
#endif
static OSThread *accelerometerThread;
static OSThread *ambientLightingThread;

View File

@ -27,10 +27,12 @@
#define VEXT_ENABLE 45 // active low, powers the oled display and the lora antenna boost
#define BUTTON_PIN 0
#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider
#define ADC_MULTIPLIER 4.9
#define ADC_CTRL 19
#define BATTERY_PIN 20
#define ADC_CHANNEL ADC2_GPIO20_CHANNEL
#define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1
#define BAT_MEASURE_ADC_UNIT 2 // Use ADC2
#define ADC_ATTENUATION ADC_ATTEN_DB_11 // Voltage divider output is quite high
#define USE_SX1262

View File

@ -35,10 +35,12 @@
#define VEXT_ENABLE 45 // active low, powers the oled display and the lora antenna boost
#define BUTTON_PIN 0
#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider
#define ADC_MULTIPLIER 4.9
#define ADC_CTRL 19
#define BATTERY_PIN 20
#define ADC_CHANNEL ADC2_GPIO20_CHANNEL
#define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1
#define BAT_MEASURE_ADC_UNIT 2 // Use ADC2
#define ADC_ATTENUATION ADC_ATTEN_DB_11 // Voltage divider output is quite high
#define USE_SX1262