Merge branch 'master' into store-and-forward

This commit is contained in:
Thomas Göttgens
2025-06-25 20:16:49 +02:00
committed by GitHub
313 changed files with 15066 additions and 5317 deletions

View File

@@ -99,7 +99,24 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
#endif
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
#include "ButtonThread.h"
#include "input/ButtonThread.h"
#if defined(BUTTON_PIN_TOUCH)
ButtonThread *TouchButtonThread = nullptr;
#endif
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
ButtonThread *UserButtonThread = nullptr;
#endif
#if defined(ALT_BUTTON_PIN)
ButtonThread *BackButtonThread = nullptr;
#endif
#if defined(CANCEL_BUTTON_PIN)
ButtonThread *CancelButtonThread = nullptr;
#endif
#endif
#include "AmbientLightingThread.h"
@@ -169,6 +186,8 @@ ScanI2C::DeviceAddress screen_found = ScanI2C::ADDRESS_NONE;
ScanI2C::DeviceAddress cardkb_found = ScanI2C::ADDRESS_NONE;
// 0x02 for RAK14004, 0x00 for cardkb, 0x10 for T-Deck
uint8_t kb_model;
// global bool to record that a kb is present
bool kb_found = false;
// The I2C address of the RTC Module (if found)
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
@@ -220,64 +239,6 @@ const char *getDeviceName()
return name;
}
#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
static int32_t ledBlinkCount = 0;
static int32_t elecrowLedBlinker()
{
// are we in alert buzzer mode?
#if HAS_BUTTON
if (buttonThread->isBuzzing()) {
// blink LED three times for 3 seconds, then 3 times for a second, with one second pause
if (ledBlinkCount % 2) { // odd means LED OFF
ledBlink.set(false);
ledBlinkCount++;
if (ledBlinkCount >= 12)
ledBlinkCount = 0;
noTone(PIN_BUZZER);
return 1000;
} else {
if (ledBlinkCount < 6) {
ledBlink.set(true);
tone(PIN_BUZZER, 4000, 3000);
ledBlinkCount++;
return 3000;
} else {
ledBlink.set(true);
tone(PIN_BUZZER, 4000, 1000);
ledBlinkCount++;
return 1000;
}
}
} else {
#endif
ledBlinkCount = 0;
if (config.device.led_heartbeat_disabled)
return 1000;
static bool ledOn;
// remain on when fully charged or discharging above 10%
if ((powerStatus->getIsCharging() && powerStatus->getBatteryChargePercent() >= 100) ||
(!powerStatus->getIsCharging() && powerStatus->getBatteryChargePercent() >= 10)) {
ledOn = true;
} else {
ledOn ^= 1;
}
ledBlink.set(ledOn);
// when charging, blink 0.5Hz square wave rate to indicate that
if (powerStatus->getIsCharging()) {
return 500;
}
// Blink rapidly when almost empty or if battery is not connected
if ((!powerStatus->getIsCharging() && powerStatus->getBatteryChargePercent() < 10) || !powerStatus->getHasBattery()) {
return 250;
}
#if HAS_BUTTON
}
#endif
return 1000;
}
#else
static int32_t ledBlinker()
{
// Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if
@@ -293,7 +254,6 @@ static int32_t ledBlinker()
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000);
}
#endif
uint32_t timeLastPowered = 0;
@@ -337,12 +297,22 @@ void setup()
#ifdef LED_POWER
pinMode(LED_POWER, OUTPUT);
digitalWrite(LED_POWER, HIGH);
digitalWrite(LED_POWER, LED_STATE_ON);
#endif
#ifdef USER_LED
pinMode(USER_LED, OUTPUT);
digitalWrite(USER_LED, LOW);
digitalWrite(USER_LED, HIGH ^ LED_STATE_ON);
#endif
#ifdef WIFI_LED
pinMode(WIFI_LED, OUTPUT);
digitalWrite(WIFI_LED, LOW);
#endif
#ifdef BLE_LED
pinMode(BLE_LED, OUTPUT);
digitalWrite(BLE_LED, LOW);
#endif
#if defined(T_DECK)
@@ -372,11 +342,9 @@ void setup()
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
#endif
#if !HAS_TFT
meshtastic_Config_DisplayConfig_OledType screen_model =
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64;
#endif
#ifdef USE_SEGGER
auto mode = false ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM;
@@ -465,6 +433,10 @@ void setup()
gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN));
delay(10);
#endif
#ifdef BUTTON_NEED_PULLUP2
gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2);
delay(10);
#endif
#endif
#endif
#endif
@@ -475,26 +447,13 @@ void setup()
#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
// The ThinkNodes have their own blink logic
ledPeriodic = new Periodic("Blink", elecrowLedBlinker);
// ledPeriodic = new Periodic("Blink", elecrowLedBlinker);
#else
ledPeriodic = new Periodic("Blink", ledBlinker);
#endif
fsInit();
#if defined(_SEEED_XIAO_NRF52840_SENSE_H_)
pinMode(CHARGE_LED, INPUT); // sets to detect if charge LED is on or off to see if USB is plugged in
pinMode(HICHG, OUTPUT);
digitalWrite(HICHG, LOW); // 100 mA charging current if set to LOW and 50mA (actually about 20mA) if set to HIGH
pinMode(BAT_READ, OUTPUT);
digitalWrite(BAT_READ, LOW); // This is pin P0_14 = 14 and by pullling low to GND it provices path to read on pin 32 (P0,31)
// PIN_VBAT the voltage from divider on XIAO board
#endif
#if !MESHTASTIC_EXCLUDE_I2C
#if defined(I2C_SDA1) && defined(ARCH_RP2040)
Wire1.setSDA(I2C_SDA1);
@@ -539,10 +498,6 @@ void setup()
digitalWrite(AQ_SET_PIN, HIGH);
#endif
#if HAS_TFT
tftSetup();
#endif
// Currently only the tbeam has a PMU
// PMU initialization needs to be placed before i2c scanning
power = new Power();
@@ -605,7 +560,6 @@ void setup()
}
#endif
#if !HAS_TFT
auto screenInfo = i2cScanner->firstScreen();
screen_found = screenInfo.type != ScanI2C::DeviceType::NONE ? screenInfo.address : ScanI2C::ADDRESS_NONE;
@@ -623,16 +577,18 @@ void setup()
screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
}
}
#endif
#define UPDATE_FROM_SCANNER(FIND_FN)
#if defined(USE_VIRTUAL_KEYBOARD)
kb_found = true;
#endif
auto rtc_info = i2cScanner->firstRTC();
rtc_found = rtc_info.type != ScanI2C::DeviceType::NONE ? rtc_info.address : rtc_found;
auto kb_info = i2cScanner->firstKeyboard();
if (kb_info.type != ScanI2C::DeviceType::NONE) {
kb_found = true;
cardkb_found = kb_info.address;
switch (kb_info.type) {
case ScanI2C::DeviceType::RAK14004:
@@ -732,6 +688,8 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN, meshtastic_TelemetrySensorType_DFROBOT_RAIN);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LTR390UV, meshtastic_TelemetrySensorType_LTR390UV);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DPS310, meshtastic_TelemetrySensorType_DPS310);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075);
i2cScanner.reset();
#endif
@@ -771,6 +729,12 @@ void setup()
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
nodeDB = new NodeDB;
#if HAS_TFT
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
tftSetup();
}
#endif
// If we're taking on the repeater role, use NextHopRouter and turn off 3V3_S rail because peripherals are not needed
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
router = new NextHopRouter();
@@ -780,11 +744,6 @@ void setup()
} else
router = new ReliableRouter();
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
// Buttons. Moved here cause we need NodeDB to be initialized
buttonThread = new ButtonThread();
#endif
// only play start melody when role is not tracker or sensor
if (config.power.is_power_saving == true &&
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TRACKER,
@@ -793,11 +752,9 @@ void setup()
else
playStartMelody();
#if !HAS_TFT
// fixed screen override?
if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO)
screen_model = config.display.oled;
#endif
#if defined(USE_SH1107)
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128
@@ -866,8 +823,23 @@ void setup()
// Initialize the screen first so we can show the logo while we start up everything else.
#if HAS_SCREEN
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
}
#else
if (screen_found.port != ScanI2C::I2CPort::NO_I2C)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#endif
}
#endif // HAS_SCREEN
// setup TZ prior to time actions.
#if !MESHTASTIC_EXCLUDE_TZ
LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string
@@ -931,9 +903,125 @@ void setup()
service = new MeshService();
service->init();
if (nodeDB->keyIsLowEntropy) {
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
}
// Now that the mesh service is created, create any modules
setupModules();
// buttons are now inputBroker, so have to come after setupModules
#if HAS_BUTTON
int pullup_sense = 0;
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
#ifdef BUTTON_SENSE_TYPE
pullup_sense = BUTTON_SENSE_TYPE;
#else
pullup_sense = INPUT_PULLUP_SENSE;
#endif
#endif
#if defined(ARCH_PORTDUINO)
if (settingsMap.count(userButtonPin) != 0 && settingsMap[userButtonPin] != RADIOLIB_NC) {
LOG_DEBUG("Use GPIO%02d for button", settingsMap[userButtonPin]);
UserButtonThread = new ButtonThread("UserButton");
if (screen)
UserButtonThread->initButton(
settingsMap[userButtonPin], true, true, INPUT_PULLUP, // pull up bias
[]() {
UserButtonThread->userButton.tick();
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
},
INPUT_BROKER_USER_PRESS, INPUT_BROKER_SELECT);
}
#endif
#ifdef BUTTON_PIN_TOUCH
TouchButtonThread = new ButtonThread("BackButton");
TouchButtonThread->initButton(
BUTTON_PIN_TOUCH, true, true, pullup_sense,
[]() {
TouchButtonThread->userButton.tick();
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
},
INPUT_BROKER_NONE, INPUT_BROKER_BACK);
#endif
#if defined(CANCEL_BUTTON_PIN)
// Buttons. Moved here cause we need NodeDB to be initialized
CancelButtonThread = new ButtonThread("CancelButton");
CancelButtonThread->initButton(
CANCEL_BUTTON_PIN, CANCEL_BUTTON_ACTIVE_LOW, CANCEL_BUTTON_ACTIVE_PULLUP, pullup_sense,
[]() {
CancelButtonThread->userButton.tick();
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
},
INPUT_BROKER_CANCEL, INPUT_BROKER_SHUTDOWN, 4000);
#endif
#if defined(ALT_BUTTON_PIN)
// Buttons. Moved here cause we need NodeDB to be initialized
BackButtonThread = new ButtonThread("BackButton");
BackButtonThread->initButton(
ALT_BUTTON_PIN, ALT_BUTTON_ACTIVE_LOW, ALT_BUTTON_ACTIVE_PULLUP, pullup_sense,
[]() {
BackButtonThread->userButton.tick();
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
},
INPUT_BROKER_ALT_PRESS, INPUT_BROKER_ALT_LONG, 500);
#endif
#if defined(BUTTON_PIN)
#if defined(USERPREFS_BUTTON_PIN)
int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN;
#else
int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
#endif
#ifndef BUTTON_ACTIVE_LOW
#define BUTTON_ACTIVE_LOW true
#endif
#ifndef BUTTON_ACTIVE_PULLUP
#define BUTTON_ACTIVE_PULLUP true
#endif
// Buttons. Moved here cause we need NodeDB to be initialized
// If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP
UserButtonThread = new ButtonThread("UserButton");
if (screen)
UserButtonThread->initButton(
_pinNum, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP, pullup_sense,
[]() {
UserButtonThread->userButton.tick();
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
},
INPUT_BROKER_USER_PRESS, INPUT_BROKER_SELECT, 500, INPUT_BROKER_NONE, INPUT_BROKER_SHUTDOWN);
else
UserButtonThread->initButton(
_pinNum, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP, pullup_sense,
[]() {
UserButtonThread->userButton.tick();
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
},
INPUT_BROKER_USER_PRESS, INPUT_BROKER_SHUTDOWN, 5000, INPUT_BROKER_SEND_PING, INPUT_BROKER_GPS_TOGGLE);
#endif
#endif
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
// After modules are setup, so we can observe modules
setupNicheGraphics();
@@ -956,19 +1044,19 @@ void setup()
// the current region name)
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS)
screen->setup();
if (screen)
screen->setup();
#elif defined(ARCH_PORTDUINO)
if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) {
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
screen->setup();
}
#else
if (screen_found.port != ScanI2C::I2CPort::NO_I2C)
if (screen_found.port != ScanI2C::I2CPort::NO_I2C && screen)
screen->setup();
#endif
#endif
screen->print("Started...\n");
#ifdef PIN_PWR_DELAY_MS
// This may be required to give the peripherals time to power up.
delay(PIN_PWR_DELAY_MS);
@@ -1227,9 +1315,12 @@ void setup()
LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset");
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
nodeDB->saveToDisk(SEGMENT_CONFIG);
if (!rIf->reconfigure()) {
LOG_WARN("Reconfigure failed, rebooting");
screen->startAlert("Rebooting...");
if (screen) {
screen->showOverlayBanner("Rebooting...");
}
rebootAtMsec = millis() + 5000;
}
}
@@ -1332,7 +1423,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_AUDIO_CONFIG;
#endif
// Option to explicitly include canned messages for edge cases, e.g. niche graphics
#if (!HAS_SCREEN || NO_EXT_GPIO) || MESHTASTIC_EXCLUDE_CANNEDMESSAGES
#if ((!HAS_SCREEN || NO_EXT_GPIO) || MESHTASTIC_EXCLUDE_CANNEDMESSAGES) && !defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_CANNEDMSG_CONFIG;
#endif
#if NO_EXT_GPIO