Files
lora_meshtastic_project/code/firmware-2.7.15.567b8ea/src/main.cpp
T
2026-05-03 01:32:56 +08:00

1841 lines
66 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
#endif
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "PowerMon.h"
#include "ReliableRouter.h"
#include "airtime.h"
#include "buzz.h"
#include "FSCommon.h"
#include "Led.h"
#include "RTC.h"
#include "SPILock.h"
#include "Throttle.h"
#include "concurrency/OSThread.h"
#include "concurrency/Periodic.h"
#include "detect/ScanI2C.h"
#include "error.h"
#include "power.h"
#if !MESHTASTIC_EXCLUDE_I2C
#include "detect/ScanI2CConsumer.h"
#include "detect/ScanI2CTwoWire.h"
#include <Wire.h>
#endif
#include "detect/einkScan.h"
#include "graphics/RAKled.h"
#include "graphics/Screen.h"
#include "main.h"
#include "mesh/generated/meshtastic/config.pb.h"
#include "meshUtils.h"
#include "modules/Modules.h"
#include "sleep.h"
#include "target_specific.h"
#include <memory>
#include <utility>
#ifdef ELECROW_ThinkNode_M5
PCA9557 io(0x18, &Wire);
#endif
#ifdef ARCH_ESP32
#include "freertosinc.h"
#if !MESHTASTIC_EXCLUDE_WEBSERVER
#include "mesh/http/WebServer.h"
#endif
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
#include "nimble/NimbleBluetooth.h"
NimbleBluetooth *nimbleBluetooth = nullptr;
#endif
#endif
#ifdef ARCH_NRF52
#include "NRF52Bluetooth.h"
NRF52Bluetooth *nrf52Bluetooth = nullptr;
#endif
#if HAS_WIFI || defined(USE_WS5500)
#include "mesh/api/WiFiServerAPI.h"
#include "mesh/wifi/WiFiAPClient.h"
#endif
#if HAS_ETHERNET && !defined(USE_WS5500)
#include "mesh/api/ethServerAPI.h"
#include "mesh/eth/ethClient.h"
#endif
#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#endif
#include "LLCC68Interface.h"
#include "LR1110Interface.h"
#include "LR1120Interface.h"
#include "LR1121Interface.h"
#include "RF95Interface.h"
#include "SX1262Interface.h"
#include "SX1268Interface.h"
#include "SX1280Interface.h"
#include "detect/LoRaRadioType.h"
#ifdef ARCH_STM32WL
#include "STM32WLE5JCInterface.h"
#endif
#if defined(ARCH_PORTDUINO)
#include "platform/portduino/SimRadio.h"
#endif
#ifdef ARCH_PORTDUINO
#include "linux/LinuxHardwareI2C.h"
#include "mesh/raspihttp/PiWebServer.h"
#include "platform/portduino/PortduinoGlue.h"
#include "platform/portduino/USBHal.h"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#endif
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
#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"
#include "PowerFSMThread.h"
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
#include "motion/AccelerometerThread.h"
AccelerometerThread *accelerometerThread = nullptr;
#endif
#ifdef HAS_I2S
#include "AudioThread.h"
AudioThread *audioThread = nullptr;
#endif
#ifdef USE_XL9555
#include "ExtensionIOXL9555.hpp"
ExtensionIOXL9555 io;
#endif
#ifdef HAS_TCA9535_BUTTON
#include "input/TCA9535ButtonThread.h"
TCA9535ButtonThread *tca9535ButtonThread = nullptr;
#endif
// ---------------------------------------------------------------------------
// 自定义 HAL:拦截 TCA9535 虚拟引脚的 GPIO 操作,转发到 I²C
// 用于 LoRa RST 通过 TCA9535 P1.4 控制
// ---------------------------------------------------------------------------
#ifdef TCA9535_LORA_RST_VIRTUAL_PIN
#include <RadioLib.h>
class TCA9535GpioHal : public LockingArduinoHal
{
public:
TCA9535GpioHal(SPIClass &spi, SPISettings spiSettings) : LockingArduinoHal(spi, spiSettings) {}
void pinMode(uint32_t pin, uint32_t mode) override
{
if (pin == TCA9535_LORA_RST_VIRTUAL_PIN) {
// P1.4 已由 TCA9535 驱动配置为输出,无需重复操作
return;
}
LockingArduinoHal::pinMode(pin, mode);
}
void digitalWrite(uint32_t pin, uint32_t value) override
{
if (pin == TCA9535_LORA_RST_VIRTUAL_PIN) {
tca9535LoraReset(value == GpioLevelHigh);
return;
}
LockingArduinoHal::digitalWrite(pin, value);
}
};
#endif
#if HAS_TFT
extern void tftSetup(void);
#endif
#ifdef HAS_UDP_MULTICAST
#include "mesh/udp/UdpMulticastHandler.h"
UdpMulticastHandler *udpHandler = nullptr;
#endif
#if defined(TCXO_OPTIONAL)
float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if TCXO is optional, put this here so it can be changed further down.
#endif
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
void setupNicheGraphics();
#include "nicheGraphics.h"
#endif
#if defined(HW_SPI1_DEVICE) && defined(ARCH_ESP32)
SPIClass SPI1(HSPI);
#endif
using namespace concurrency;
volatile static const char slipstreamTZString[] = {USERPREFS_TZ_STRING};
// We always create a screen object, but we only init it if we find the hardware
graphics::Screen *screen = nullptr;
// Global power status
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
// Global GPS status
meshtastic::GPSStatus *gpsStatus = new meshtastic::GPSStatus();
// Global Node status
meshtastic::NodeStatus *nodeStatus = new meshtastic::NodeStatus();
// Global Bluetooth status
meshtastic::BluetoothStatus *bluetoothStatus = new meshtastic::BluetoothStatus();
// Scan for I2C Devices
/// The I2C address of our display (if found)
ScanI2C::DeviceAddress screen_found = ScanI2C::ADDRESS_NONE;
// The I2C address of the cardkb or RAK14004 (if found)
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;
// global bool to record that on-screen keyboard (OSK) is present
bool osk_found = false;
// The I2C address of the RTC Module (if found)
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
// The I2C address of the Accelerometer (if found)
ScanI2C::DeviceAddress accelerometer_found = ScanI2C::ADDRESS_NONE;
// The I2C address of the RGB LED (if found)
ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ScanI2C::ADDRESS_NONE);
/// The I2C address of our Air Quality Indicator (if found)
ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
Adafruit_DRV2605 drv;
#endif
// Global LoRa radio type
LoRaRadioType radioType = NO_RADIO;
bool isVibrating = false;
bool eink_found = true;
uint32_t serialSinceMsec;
bool pauseBluetoothLogging = false;
bool pmu_found;
#if !MESHTASTIC_EXCLUDE_I2C
// Array map of sensor types with i2c address and wire as we'll find in the i2c scan
std::pair<uint8_t, TwoWire *> nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1] = {};
#endif
Router *router = NULL; // Users of router don't care what sort of subclass implements that API
const char *firmware_version = optstr(APP_VERSION_SHORT);
const char *getDeviceName()
{
uint8_t dmac[6];
getMacAddr(dmac);
// Meshtastic_ab3c or Shortname_abcd
static char name[20];
snprintf(name, sizeof(name), "%02x%02x", dmac[4], dmac[5]);
// if the shortname exists and is NOT the new default of ab3c, use it for BLE name.
if (strcmp(owner.short_name, name) != 0) {
snprintf(name, sizeof(name), "%s_%02x%02x", owner.short_name, dmac[4], dmac[5]);
} else {
snprintf(name, sizeof(name), "Meshtastic_%02x%02x", dmac[4], dmac[5]);
}
return name;
}
static int32_t ledBlinker()
{
// Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if
// config.device.led_heartbeat_disabled is changed
if (config.device.led_heartbeat_disabled)
return 1000;
static bool ledOn;
ledOn ^= 1;
ledBlink.set(ledOn);
// 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);
}
uint32_t timeLastPowered = 0;
static Periodic *ledPeriodic;
static OSThread *powerFSMthread;
static OSThread *ambientLightingThread;
RadioInterface *rIf = NULL;
#ifdef ARCH_PORTDUINO
RadioLibHal *RadioLibHAL = NULL;
#endif
/**
* Some platforms (nrf52) might provide an alterate version that suppresses calling delay from sleep.
*/
__attribute__((weak, noinline)) bool loopCanSleep()
{
return true;
}
// Weak empty variant initialization function.
// May be redefined by variant files.
void lateInitVariant() __attribute__((weak));
void lateInitVariant() {}
/**
* Print info as a structured log message (for automated log processing)
*/
void printInfo()
{
LOG_INFO("S:B:%d,%s,%s,%s", HW_VENDOR, optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
}
#ifndef PIO_UNIT_TESTING
void setup()
{
// 适用于 R1_NEO 硬件:初始化 DCDC 和 NRF 模块电源使能引脚
#if defined(R1_NEO)
// 将 DCDC 电源保持引脚设置为输出模式,用于后续控制 DCDC 电源使能
pinMode(DCDC_EN_HOLD, OUTPUT);
// 拉高 DCDC 电源保持引脚,使能 DCDC 电源,确保核心电路供电正常
digitalWrite(DCDC_EN_HOLD, HIGH);
// 将 NRF 模块电源使能引脚设置为输出模式
pinMode(NRF_ON, OUTPUT);
// 拉高 NRF 电源使能引脚,开启 NRF 模块电源
digitalWrite(NRF_ON, HIGH);
#endif
// 适用于带 PIN_POWER_EN 引脚的硬件:初始化通用电源使能引脚
#if defined(PIN_POWER_EN)
// 将通用电源使能引脚设置为输出模式
pinMode(PIN_POWER_EN, OUTPUT);
// 拉高电源使能引脚,开启外设电源
digitalWrite(PIN_POWER_EN, HIGH);
#endif
// 适用于 ELECROW ThinkNode M5 硬件:初始化 I2C 总线及 PCA9557 IO 扩展器电源引脚
#if defined(ELECROW_ThinkNode_M5)
// 初始化 I2C 总线,SDA=48SCL=47
Wire.begin(48, 47);
// 设置 PCA9557 的电子墨水屏使能引脚为输出模式
io.pinMode(PCA_PIN_EINK_EN, OUTPUT);
// 设置 PCA9557 的电源使能引脚为输出模式
io.pinMode(PCA_PIN_POWER_EN, OUTPUT);
// 拉高 PCA9557 电源使能引脚,开启外设电源
io.digitalWrite(PCA_PIN_POWER_EN, HIGH);
// io.pinMode(C2_PIN, OUTPUT);
#endif
// 适用于带 LED_POWER 引脚的硬件:初始化电源指示灯
#ifdef LED_POWER
// 将电源指示灯引脚设置为输出模式
pinMode(LED_POWER, OUTPUT);
// 设置电源指示灯为点亮状态(LED_STATE_ON 为点亮电平)
digitalWrite(LED_POWER, LED_STATE_ON);
#endif
// 适用于带 USER_LED 引脚的硬件:初始化用户指示灯
#ifdef USER_LED
// 将用户 LED 引脚设置为输出模式
pinMode(USER_LED, OUTPUT);
// 设置用户 LED 状态(HIGH 与 LED_STATE_ON 异或,适配 LED 电平逻辑)
digitalWrite(USER_LED, HIGH ^ LED_STATE_ON);
#endif
// 适用于带 WIFI_LED 引脚的硬件:初始化 WiFi 指示灯
#ifdef WIFI_LED
// 将 WiFi 指示灯引脚设置为输出模式
pinMode(WIFI_LED, OUTPUT);
// 初始设置 WiFi 指示灯为熄灭状态
digitalWrite(WIFI_LED, LOW);
#endif
// 适用于带 BLE_LED 引脚的硬件:初始化蓝牙指示灯
#ifdef BLE_LED
// 将蓝牙指示灯引脚设置为输出模式
pinMode(BLE_LED, OUTPUT);
#ifdef BLE_LED_INVERTED
// 若蓝牙 LED 为低电平点亮(反逻辑),则拉高引脚熄灭
digitalWrite(BLE_LED, HIGH);
#else
// 若蓝牙 LED 为高电平点亮,则拉低引脚熄灭
digitalWrite(BLE_LED, LOW);
#endif
#endif
// 适用于 T-Deck、T-Deck Pro、T-LoRa Pager 等硬件:初始化外设电源、SPI 片选引脚及 IO 扩展器
#if defined(T_DECK)
// GPIO10 控制所有外设电源,启动后立即开启避免低电压导致 ESP32 复位
// GPIO10 manages all peripheral power supplies
// Turn on peripheral power immediately after MUC starts.
// If some boards are turned on late, ESP32 will reset due to low voltage.
// 外设包括:ESP32-C3(键盘)、MAX98357A(音频功放)、TF卡、显示屏背光、轨迹球、解码器
// ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) ,
// TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder)
// 将键盘电源使能引脚设置为输出模式
pinMode(KB_POWERON, OUTPUT);
// 拉高键盘电源引脚,开启外设电源
digitalWrite(KB_POWERON, HIGH);
// T-Deck 的 TFT、SD、LoRa 共用同一 SPI 总线,需提前初始化所有片选引脚避免通信冲突
// T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus
// We need to initialize all CS pins in advance otherwise there will be SPI communication issues
// e.g. when detecting the SD card
// 初始化 LoRa 片选引脚为输出并拉高(默认不选中)
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
// 初始化 SD 卡片选引脚为输出并拉高(默认不选中)
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
// 初始化 TFT 屏幕片选引脚为输出并拉高(默认不选中)
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH);
// 等待外设电源稳定
delay(100);
#elif defined(T_DECK_PRO)
// T-Deck Pro 硬件:初始化 LoRa、SD 卡、电子墨水屏的片选引脚并拉高
pinMode(LORA_EN, OUTPUT);
digitalWrite(LORA_EN, HIGH);
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
pinMode(PIN_EINK_CS, OUTPUT);
digitalWrite(PIN_EINK_CS, HIGH);
#elif defined(T_LORA_PAGER)
// T-LoRa Pager 硬件:初始化 SPI 片选引脚、键盘中断引脚及 XL9555 IO 扩展器
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH);
// 设置键盘中断引脚为上拉输入模式
pinMode(KB_INT, INPUT_PULLUP);
// 初始化 XL9555 IO 扩展器,使用默认 Wire 总线,地址为 XL9555_SLAVE_ADDRESS0
// io expander
io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL);
// 设置驱动使能引脚为输出并拉高,开启驱动
io.pinMode(EXPANDS_DRV_EN, OUTPUT);
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
// 设置音频功放使能引脚为输出并拉低,关闭功放(默认不开启)
io.pinMode(EXPANDS_AMP_EN, OUTPUT);
io.digitalWrite(EXPANDS_AMP_EN, LOW);
// 设置 LoRa 使能引脚为输出并拉高,开启 LoRa 模块
io.pinMode(EXPANDS_LORA_EN, OUTPUT);
io.digitalWrite(EXPANDS_LORA_EN, HIGH);
// 设置 GPS 使能引脚为输出并拉高,开启 GPS 模块
io.pinMode(EXPANDS_GPS_EN, OUTPUT);
io.digitalWrite(EXPANDS_GPS_EN, HIGH);
// 设置键盘使能引脚为输出并拉高,开启键盘
io.pinMode(EXPANDS_KB_EN, OUTPUT);
io.digitalWrite(EXPANDS_KB_EN, HIGH);
// 设置 SD 卡使能引脚为输出并拉高,开启 SD 卡
io.pinMode(EXPANDS_SD_EN, OUTPUT);
io.digitalWrite(EXPANDS_SD_EN, HIGH);
// 设置 GPIO 扩展使能引脚为输出并拉高,开启扩展 GPIO
io.pinMode(EXPANDS_GPIO_EN, OUTPUT);
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
// 设置 SD 卡上拉使能引脚为输入模式
io.pinMode(EXPANDS_SD_PULLEN, INPUT);
#endif
// 标记并发框架已完成 setup 初始化
concurrency::hasBeenSetup = true;
// 根据平台配置 SPI 通信参数:时钟速度、位顺序(MSB 优先)、SPI 模式
#if ARCH_PORTDUINO
// Portduino 平台:使用用户配置的 SPI 速度
SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
#else
// 其他平台:默认 SPI 时钟 4MHz,MSB 优先,模式0
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
#endif
// 设置屏幕默认型号为 OLED_AUTO(自动检测),默认分辨率为 128x64
meshtastic_Config_DisplayConfig_OledType screen_model =
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64;
// 适用于启用 SEGGER RTT 调试输出的硬件:配置 RTT 上行缓冲区
#ifdef USE_SEGGER
// 设置 RTT 模式:非阻塞裁剪模式(FIFO 满时丢弃数据)
auto mode = false ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM;
#ifdef NRF52840_XXAA
// NRF52840 内存较大,设置 RTT 缓冲区大小为 4096 字节
auto buflen = 4096; // this board has a fair amount of ram
#else
// 其他板卡内存较小,设置 RTT 缓冲区大小为 256 字节
auto buflen = 256; // this board has a fair amount of ram
#endif
// 配置 SEGGER RTT 上行缓冲区(用于调试输出)
SEGGER_RTT_ConfigUpBuffer(SEGGER_STDOUT_CH, NULL, NULL, buflen, mode);
#endif
// 适用于启用调试串口(DEBUG_PORT)的硬件:初始化串口控制台
#ifdef DEBUG_PORT
// 初始化调试串口(设置波特率等)并启动 mesh 控制台
consoleInit(); // Set serial baud rate and init our mesh console
#endif
// 适用于 UNPHONE 硬件:打印存储信息
#ifdef UNPHONE
// 打印 UNPHONE 的存储状态信息
unphone.printStore();
#endif
// 适用于 Portduino 平台:初始化系统时间并设置 RTC
#if ARCH_PORTDUINO
// 定义时间结构体
struct timeval tv;
// 获取当前系统时间(秒级)并赋值
tv.tv_sec = time(NULL);
// 微秒级时间设为 0
tv.tv_usec = 0;
// 尝试设置 RTC 时间,质量标记为设备级
perhapsSetRTC(RTCQualityDevice, &tv);
#endif
powerMonInit();
serialSinceMsec = millis();
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
#if defined(DEBUG_MUTE) && defined(DEBUG_PORT)
DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n");
DEBUG_PORT.printf("Version %s for %s from %s\r\n", optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
DEBUG_PORT.printf("Debug mute is enabled, there will be no serial output.\r\n");
#endif
initDeepSleep();
#if defined(MODEM_POWER_EN)
pinMode(MODEM_POWER_EN, OUTPUT);
digitalWrite(MODEM_POWER_EN, LOW);
#endif
#if defined(MODEM_PWRKEY)
pinMode(MODEM_PWRKEY, OUTPUT);
digitalWrite(MODEM_PWRKEY, LOW);
#endif
#if defined(LORA_TCXO_GPIO)
pinMode(LORA_TCXO_GPIO, OUTPUT);
digitalWrite(LORA_TCXO_GPIO, HIGH);
#endif
#if defined(VEXT_ENABLE)
pinMode(VEXT_ENABLE, OUTPUT);
digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); // turn on the display power
#endif
#if defined(BIAS_T_ENABLE)
pinMode(BIAS_T_ENABLE, OUTPUT);
digitalWrite(BIAS_T_ENABLE, BIAS_T_VALUE); // turn on 5V for GPS Antenna
#endif
#if defined(VTFT_CTRL)
pinMode(VTFT_CTRL, OUTPUT);
digitalWrite(VTFT_CTRL, LOW);
#endif
#ifdef RESET_OLED
pinMode(RESET_OLED, OUTPUT);
digitalWrite(RESET_OLED, 1);
#endif
#ifdef SENSOR_POWER_CTRL_PIN
pinMode(SENSOR_POWER_CTRL_PIN, OUTPUT);
digitalWrite(SENSOR_POWER_CTRL_PIN, SENSOR_POWER_ON);
#endif
#ifdef SENSOR_GPS_CONFLICT
bool sensor_detected = false;
#endif
#ifdef PERIPHERAL_WARMUP_MS
// Some peripherals may require additional time to stabilize after power is connected
// e.g. I2C on Heltec Vision Master
LOG_INFO("Wait for peripherals to stabilize");
delay(PERIPHERAL_WARMUP_MS);
#endif
#ifdef BUTTON_PIN
#ifdef ARCH_ESP32
#if ESP_ARDUINO_VERSION_MAJOR >= 3
#ifdef BUTTON_NEED_PULLUP
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP);
#else
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
#endif
#else
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
#ifdef BUTTON_NEED_PULLUP
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
// 初始化 SPI 总线(配置 SPI 参数并启动)
initSPI();
// 初始化 OSThread 并发框架(设置线程调度基础)
OSThread::setup();
// 适用于 ELECROW ThinkNode M1/M2 硬件:使用自定义 LED 闪烁逻辑(当前注释未启用)
#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
// The ThinkNodes have their own blink logic
// ledPeriodic = new Periodic("Blink", elecrowLedBlinker);
#else
// 其他硬件:创建 LED 周期闪烁线程(默认 1Hz 心跳效果)
ledPeriodic = new Periodic("Blink", ledBlinker);
#endif
// 初始化文件系统(挂载 SPI Flash/SD 卡等存储设备)
fsInit();
// 阶段3:I2C 总线初始化与设备扫描(排除 I2C 功能时跳过)
#if !MESHTASTIC_EXCLUDE_I2C
// 初始化第二 I2C 总线(Wire1,适用于有 2 个 I2C 接口的平台)
#if defined(I2C_SDA1) && defined(ARCH_RP2040)
// RP2040 平台:设置 Wire1 的 SDA/SCL 引脚并启动总线
Wire1.setSDA(I2C_SDA1);
Wire1.setSCL(I2C_SCL1);
Wire1.begin();
#elif defined(I2C_SDA1) && !defined(ARCH_RP2040)
// 非 RP2040 平台:启动 Wire1 总线并指定 SDA/SCL 引脚
Wire1.begin(I2C_SDA1, I2C_SCL1);
#elif WIRE_INTERFACES_COUNT == 2
// 平台有 2 个 I2C 接口:启动默认 Wire1 总线
Wire1.begin();
#endif
// 初始化主 I2C 总线(Wire
#if defined(I2C_SDA) && defined(ARCH_RP2040)
// RP2040 平台:设置主 Wire 的 SDA/SCL 引脚并启动总线
Wire.setSDA(I2C_SDA);
Wire.setSCL(I2C_SCL);
Wire.begin();
#elif defined(I2C_SDA) && !defined(ARCH_RP2040)
// 非 RP2040 平台:启动主 Wire 总线并指定 SDA/SCL 引脚
Wire.begin(I2C_SDA, I2C_SCL);
#ifdef HAS_TCA9535_BUTTON
// TCA9535 电源使能必须在 I2C 初始化后立即拉高,防止用户松开按键后系统掉电
// TCA9535 POWER_EN 必须在 I²C 初始化完成后立即拉高,否则用户松开按键后
// MOS 断电,系统在 setup() 中途就会掉电。
tca9535PowerEn(true);
LOG_INFO("TCA9535: POWER_EN latched HIGH (early boot)");
#endif
#elif defined(ARCH_PORTDUINO)
// Portduino 平台:根据配置启动主 I2C 总线
if (portduino_config.i2cdev != "") {
LOG_INFO("Use %s as I2C device", portduino_config.i2cdev.c_str());
Wire.begin(portduino_config.i2cdev.c_str());
} else {
LOG_INFO("No I2C device configured, Skip");
}
#elif HAS_WIRE
// 其他有 Wire 总线的平台:启动默认主 I2C 总线
Wire.begin();
#endif
#endif
// 适用于 M5STACK_UNITC6L 硬件:初始化 LoRa 片选引脚并启动 C6L 模块
#if defined(M5STACK_UNITC6L)
// 设置 LoRa 片选引脚为输出模式
pinMode(LORA_CS, OUTPUT);
// 拉高片选引脚,默认不选中 LoRa
digitalWrite(LORA_CS, 1);
// 初始化 M5STACK_UNITC6L 模块
c6l_init();
#endif
// 适用于带 LCD 复位引脚的硬件:执行 LCD 复位时序
#ifdef PIN_LCD_RESET
// FIXME - 后续将该复位逻辑移至更合适的位置,LCD I2C 地址为 0x3F
// FIXME - move this someplace better, LCD is at address 0x3F
// 设置 LCD 复位引脚为输出模式
pinMode(PIN_LCD_RESET, OUTPUT);
// 拉低复位引脚,触发 LCD 复位
digitalWrite(PIN_LCD_RESET, 0);
// 延时 1ms 确保复位生效
delay(1);
// 拉高复位引脚,结束复位状态
digitalWrite(PIN_LCD_RESET, 1);
// 延时 1ms 等待 LCD 启动
delay(1);
#endif
// 适用于带空气质量传感器(AQ)设置引脚的硬件:初始化 AQ 传感器电源
#ifdef AQ_SET_PIN
// RAK-12039 空气质量传感器设置引脚,上电后约 3 秒可在 I2C 检测到,需后续重新扫描
// RAK-12039 set pin for Air quality sensor. Detectable on I2C after ~3 seconds, so we need to rescan later
// 设置 AQ 传感器电源引脚为输出模式
pinMode(AQ_SET_PIN, OUTPUT);
// 拉高引脚,开启 AQ 传感器电源
digitalWrite(AQ_SET_PIN, HIGH);
#endif
// 初始化电源管理单元(PMU,目前仅 T-Beam 等硬件有)
// 当前仅 T-Beam 等硬件有 PMU
// Currently only the tbeam has a PMU
// PMU 初始化必须放在 I2C 扫描之前,因为需要 PMU 为外设供电
// PMU initialization needs to be placed before i2c scanning
// 创建 Power 实例(电源管理核心对象)
power = new Power();
// 设置电源状态处理器为 powerStatus,用于接收电源状态更新
power->setStatusHandler(powerStatus);
// 让 powerStatus 观察 power 的新状态事件
powerStatus->observe(&power->newStatus);
// 执行 PMU 初始化(必须在状态处理器安装后,确保接收初始配置通知)
power->setup(); // Must be after status handler is installed, so that handler gets notified of the initial configuration
#if !MESHTASTIC_EXCLUDE_I2C
// We need to scan here to decide if we have a screen for nodeDB.init() and because power has been applied to
// accessories
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
#if HAS_WIRE
LOG_INFO("Scan for i2c devices");
#endif
#if defined(I2C_SDA1) || (defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2))
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1);
#endif
#if defined(I2C_SDA)
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
#elif defined(ARCH_PORTDUINO)
if (portduino_config.i2cdev != "") {
LOG_INFO("Scan for i2c devices");
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
}
#elif HAS_WIRE
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
#endif
auto i2cCount = i2cScanner->countDevices();
if (i2cCount == 0) {
LOG_INFO("No I2C devices found");
} else {
LOG_INFO("%i I2C devices found", i2cCount);
#ifdef SENSOR_GPS_CONFLICT
sensor_detected = true;
#endif
}
#ifdef ARCH_ESP32
// Don't init display if we don't have one or we are waking headless due to a timer event
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) {
LOG_DEBUG("suppress screen wake because this is a headless timer wakeup");
i2cScanner->setSuppressScreen();
}
#endif
// 检测 I2C 扫描到的第一个屏幕设备,更新屏幕型号
auto screenInfo = i2cScanner->firstScreen();
// 记录屏幕设备地址(未找到则为 ADDRESS_NONE
screen_found = screenInfo.type != ScanI2C::DeviceType::NONE ? screenInfo.address : ScanI2C::ADDRESS_NONE;
// 若找到屏幕设备,根据型号更新屏幕型号配置
if (screen_found.port != ScanI2C::I2CPort::NO_I2C) {
switch (screenInfo.type) {
case ScanI2C::DeviceType::SCREEN_SH1106:
// SH1106 屏幕型号(128x64 OLED
screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_SH1106;
break;
case ScanI2C::DeviceType::SCREEN_SSD1306:
// SSD1306 屏幕型号(128x64 OLED
screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_SSD1306;
break;
case ScanI2C::DeviceType::SCREEN_ST7567:
case ScanI2C::DeviceType::SCREEN_UNKNOWN:
default:
// 未知或 ST7567 屏幕,保持自动检测模式
screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
}
}
// 宏定义:从扫描结果更新传感器映射(当前未使用,预留扩展)
#define UPDATE_FROM_SCANNER(FIND_FN)
// 若定义了虚拟键盘,直接标记键盘已找到
#if defined(USE_VIRTUAL_KEYBOARD)
kb_found = true;
#endif
// 获取第一个 RTC 设备信息,更新 RTC 地址
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;
// 根据键盘型号设置 kb_model 值(用于区分不同键盘型号)
switch (kb_info.type) {
case ScanI2C::DeviceType::RAK14004:
// RAK14004 键盘型号标识(0x02
kb_model = 0x02;
break;
case ScanI2C::DeviceType::CARDKB:
// CARDKB 键盘型号标识(0x00
kb_model = 0x00;
break;
case ScanI2C::DeviceType::TDECKKB:
// T-Deck 键盘型号标识(0x10
// assign an arbitrary value to distinguish from other models
kb_model = 0x10;
break;
case ScanI2C::DeviceType::BBQ10KB:
// BBQ10 键盘型号标识(0x11
// assign an arbitrary value to distinguish from other models
kb_model = 0x11;
break;
case ScanI2C::DeviceType::MPR121KB:
// MPR121 键盘型号标识(0x37
// assign an arbitrary value to distinguish from other models
kb_model = 0x37;
break;
case ScanI2C::DeviceType::TCA8418KB:
// TCA8418 键盘型号标识(0x84
// assign an arbitrary value to distinguish from other models
kb_model = 0x84;
break;
default:
// 未知键盘型号,默认使用 0x00
// use this as default since it's also just zero
LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00", kb_info.type);
kb_model = 0x00;
}
}
// 检查 PMUAXP192/AXP2101)是否存在,更新 pmu_found 标志
pmu_found = i2cScanner->exists(ScanI2C::DeviceType::PMU_AXP192_AXP2101);
// 获取第一个空气质量传感器(AQI)信息,更新 aqi_found 地址
auto aqiInfo = i2cScanner->firstAQI();
aqi_found = aqiInfo.type != ScanI2C::DeviceType::NONE ? aqiInfo.address : ScanI2C::ADDRESS_NONE;
/*
* There are a bunch of sensors that have no further logic than to be found and stuffed into the
* nodeTelemetrySensorsMap singleton. This wraps that logic in a temporary scope to declare the temporary field
* "found".
*/
// 当前支持两种 RGB LED 检测
#ifdef HAS_RGB_LED
// 从扫描结果获取 RGB LED 设备信息
rgb_found = i2cScanner->firstRGBLED();
#endif
#ifdef HAS_TPS65233
// TPS65233 是卫星调制解调器的电源管理 IC,用于 Dreamcatcher 硬件
// TPS65233 is a power management IC for satellite modems, used in the Dreamcatcher
// 此处关闭 LNB 电源(我们使用卫星调制解调器时不使用 LNB)
// We are switching it off here since we don't use an LNB.
if (i2cScanner->exists(ScanI2C::DeviceType::TPS65233)) {
// 写入寄存器 0:关闭 LNB 电源,保持 I2C 控制使能
Wire.beginTransmission(TPS65233_ADDR);
Wire.write(0); // Register 0
Wire.write(128); // Turn off the LNB power, keep I2C Control enabled
Wire.endTransmission();
// 写入寄存器 1:关闭 22kHz 音调发生器
Wire.beginTransmission(TPS65233_ADDR);
Wire.write(1); // Register 1
Wire.write(0); // Turn off Tone Generator 22kHz
Wire.endTransmission();
}
#endif
// 非 STM32WL 平台:检测加速度计并获取设备地址
#if !defined(ARCH_STM32WL)
// 获取第一个加速度计设备信息
auto acc_info = i2cScanner->firstAccelerometer();
// 更新加速度计设备地址(未找到则保持原值)
accelerometer_found = acc_info.type != ScanI2C::DeviceType::NONE ? acc_info.address : accelerometer_found;
// 输出加速度计设备类型调试信息
LOG_DEBUG("acc_info = %i", acc_info.type);
#endif
// 将 INA260 传感器映射到遥测传感器类型
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
// 将 INA226 传感器映射到遥测传感器类型
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
// 将 INA219 传感器映射到遥测传感器类型
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
// 将 INA3221 传感器映射到遥测传感器类型
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
// 将 MAX17048 传感器映射到遥测传感器类型
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
// 将 QMC6310 传感器映射到遥测传感器类型
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310);
// 将 QMI8658 传感器映射到遥测传感器类型
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
// 将 QMC5883L 传感器映射到遥测传感器类型
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
#endif
#ifdef HAS_SDCARD
setupSDCard();
#endif
// LED init
#ifdef LED_PIN
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LED_STATE_ON); // turn on for now
#endif
// Hello
printInfo();
#ifdef BUILD_EPOCH
LOG_INFO("Build timestamp: %ld", BUILD_EPOCH);
#endif
#ifdef ARCH_ESP32
esp32Setup();
#endif
#ifdef ARCH_NRF52
nrf52Setup();
#endif
#ifdef ARCH_RP2040
rp2040Setup();
#endif
// We do this as early as possible because this loads preferences from flash
// 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
router = new ReliableRouter();
// 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,
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER, meshtastic_Config_DeviceConfig_Role_SENSOR))
LOG_DEBUG("Tracker/Sensor: Skip start melody");
else
playStartMelody();
// fixed screen override?
if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO)
screen_model = config.display.oled;
#if defined(USE_SH1107)
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128
screen_geometry = GEOMETRY_128_128;
#endif
#if defined(USE_SH1107_128_64)
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64
#endif
#if !MESHTASTIC_EXCLUDE_I2C
#if !defined(ARCH_STM32WL)
if (acc_info.type != ScanI2C::DeviceType::NONE) {
accelerometerThread = new AccelerometerThread(acc_info.type);
}
#endif
#if defined(HAS_NEOPIXEL) || defined(UNPHONE) || defined(RGBLED_RED)
ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE);
#elif !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
if (rgb_found.type != ScanI2C::DeviceType::NONE) {
ambientLightingThread = new AmbientLightingThread(rgb_found.type);
}
#endif
#endif
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
drv.begin();
drv.selectLibrary(1);
// I2C trigger by sending 'go' command
drv.setMode(DRV2605_MODE_INTTRIG);
#endif
// Init our SPI controller (must be before screen and lora)
#ifdef ARCH_RP2040
#ifdef HW_SPI1_DEVICE
SPI1.setSCK(LORA_SCK);
SPI1.setTX(LORA_MOSI);
SPI1.setRX(LORA_MISO);
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
SPI1.begin(false);
#else // HW_SPI1_DEVICE
SPI.setSCK(LORA_SCK);
SPI.setTX(LORA_MOSI);
SPI.setRX(LORA_MISO);
SPI.begin(false);
#endif // HW_SPI1_DEVICE
#elif ARCH_PORTDUINO
if (portduino_config.lora_spi_dev != "ch341") {
SPI.begin();
}
#elif !defined(ARCH_ESP32) // ARCH_RP2040
#if defined(RAK3401) || defined(RAK13302)
pinMode(WB_IO2, OUTPUT);
digitalWrite(WB_IO2, HIGH);
SPI1.setPins(LORA_MISO, LORA_SCK, LORA_MOSI);
SPI1.begin();
#else
SPI.begin();
#endif
#else
// ESP32
#if defined(HW_SPI1_DEVICE)
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
SPI1.setFrequency(4000000);
#else
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LOG_DEBUG("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
SPI.setFrequency(4000000);
#endif
#endif
// Initialize the screen first so we can show the logo while we start up everything else.
#if HAS_SCREEN
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) || defined(ST7796_CS) || \
defined(USE_SPISSD1306)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.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
if (*config.device.tzdef && config.device.tzdef[0] != 0) {
LOG_DEBUG("Saved TZ: %s ", config.device.tzdef);
setenv("TZ", config.device.tzdef, 1);
} else {
if (strncmp((const char *)slipstreamTZString, "tzpl", 4) == 0) {
setenv("TZ", "GMT0", 1);
} else {
setenv("TZ", (const char *)slipstreamTZString, 1);
strcpy(config.device.tzdef, (const char *)slipstreamTZString);
}
}
tzset();
LOG_DEBUG("Set Timezone to %s", getenv("TZ"));
#endif
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
#if !MESHTASTIC_EXCLUDE_GPS
// If we're taking on the repeater role, ignore GPS
#ifdef SENSOR_GPS_CONFLICT
if (sensor_detected == false) {
#endif
if (HAS_GPS) {
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
gps = GPS::createGps();
if (gps) {
gpsStatus->observe(&gps->newStatus);
#ifdef TCA9535_GPS_HAS_CTRL
// 替换 enablePin 为 TCA9535 P1.7GPS EN),I²C 控制
// createGps() 创建了空的 GpioVirtPin,替换为实际的 TCA9535 驱动
class GpioTca9535GpsEnPin : public GpioPin {
public:
void set(bool value) override { tca9535GpsEn(value); }
} *tca9535GpsEnPin = new GpioTca9535GpsEnPin();
new GpioUnaryTransformer(gps->enablePin, tca9535GpsEnPin);
// GPS 已在 TCA9535 init 中上电(P1.7=高),无需额外操作
#endif
} else {
LOG_DEBUG("Run without GPS");
}
}
}
#ifdef SENSOR_GPS_CONFLICT
}
#endif
#endif
nodeStatus->observe(&nodeDB->newStatus);
#ifdef HAS_I2S
LOG_DEBUG("Start audio thread");
audioThread = new AudioThread();
#endif
#ifdef HAS_UDP_MULTICAST
LOG_DEBUG("Start multicast thread");
udpHandler = new UdpMulticastHandler();
#ifdef ARCH_PORTDUINO
// FIXME: portduino does not ever call onNetworkConnected so call it here because I don't know what happen if I call
// onNetworkConnected there
if (config.network.enabled_protocols & meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST) {
udpHandler->start();
}
#endif
#endif
service = new MeshService();
service->init();
// Now that the mesh service is created, create any modules
setupModules();
#ifdef HAS_TCA9535_BUTTON
// TCA9535PWR I²C IO 扩展器按键驱动
// Wire 已在 I2C 初始化段完成 begin()inputBroker 已在 setupModules() 中创建
tca9535ButtonThread = new TCA9535ButtonThread("TCA9535Btn");
if (!tca9535ButtonThread->init()) {
LOG_WARN("TCA9535 init failed, disabling button thread");
delete tca9535ButtonThread;
tca9535ButtonThread = nullptr;
} else {
LOG_INFO("TCA9535 button thread started");
}
#endif
#if !MESHTASTIC_EXCLUDE_I2C
// Inform modules about I2C devices
ScanI2CCompleted(i2cScanner.get());
i2cScanner.reset();
#endif
// warn the user about a low entropy key
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
LOG_WARN(LOW_ENTROPY_WARNING);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->time = getValidTime(RTCQualityFromNet);
sprintf(cn->message, LOW_ENTROPY_WARNING);
service->sendClientNotification(cn);
nodeDB->hasWarned = true;
}
// 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 (portduino_config.userButtonPin.enabled) {
LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin);
UserButtonThread = new ButtonThread("UserButton");
if (screen) {
ButtonConfig config;
config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin;
config.activeLow = true;
config.activePullup = true;
config.pullupSense = INPUT_PULLUP;
config.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
config.singlePress = INPUT_BROKER_USER_PRESS;
config.longPress = INPUT_BROKER_SELECT;
UserButtonThread->initButton(config);
}
}
#endif
#ifdef BUTTON_PIN_TOUCH
TouchButtonThread = new ButtonThread("BackButton");
ButtonConfig touchConfig;
touchConfig.pinNumber = BUTTON_PIN_TOUCH;
touchConfig.activeLow = true;
touchConfig.activePullup = true;
touchConfig.pullupSense = pullup_sense;
touchConfig.intRoutine = []() {
TouchButtonThread->userButton.tick();
TouchButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
touchConfig.singlePress = INPUT_BROKER_NONE;
touchConfig.longPress = INPUT_BROKER_BACK;
TouchButtonThread->initButton(touchConfig);
#endif
#if defined(CANCEL_BUTTON_PIN)
// Buttons. Moved here cause we need NodeDB to be initialized
CancelButtonThread = new ButtonThread("CancelButton");
ButtonConfig cancelConfig;
cancelConfig.pinNumber = CANCEL_BUTTON_PIN;
cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW;
cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP;
cancelConfig.pullupSense = pullup_sense;
cancelConfig.intRoutine = []() {
CancelButtonThread->userButton.tick();
CancelButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
cancelConfig.singlePress = INPUT_BROKER_CANCEL;
cancelConfig.longPress = INPUT_BROKER_SHUTDOWN;
cancelConfig.longPressTime = 4000;
CancelButtonThread->initButton(cancelConfig);
#endif
#if defined(ALT_BUTTON_PIN)
// Buttons. Moved here cause we need NodeDB to be initialized
BackButtonThread = new ButtonThread("BackButton");
ButtonConfig backConfig;
backConfig.pinNumber = ALT_BUTTON_PIN;
backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW;
backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP;
backConfig.pullupSense = pullup_sense;
backConfig.intRoutine = []() {
BackButtonThread->userButton.tick();
BackButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
backConfig.singlePress = INPUT_BROKER_ALT_PRESS;
backConfig.longPress = INPUT_BROKER_ALT_LONG;
backConfig.longPressTime = 500;
BackButtonThread->initButton(backConfig);
#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) {
ButtonConfig userConfig;
userConfig.pinNumber = (uint8_t)_pinNum;
userConfig.activeLow = BUTTON_ACTIVE_LOW;
userConfig.activePullup = BUTTON_ACTIVE_PULLUP;
userConfig.pullupSense = pullup_sense;
userConfig.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
userConfig.singlePress = INPUT_BROKER_USER_PRESS;
userConfig.longPress = INPUT_BROKER_SELECT;
userConfig.longPressTime = 500;
userConfig.longLongPress = INPUT_BROKER_SHUTDOWN;
#ifdef BUTTON_SINGLE_PRESS_EVENT
userConfig.singlePress = BUTTON_SINGLE_PRESS_EVENT;
#endif
#ifdef BUTTON_DISABLE_LONG_PRESS
userConfig.longPress = INPUT_BROKER_NONE;
userConfig.longLongPress = INPUT_BROKER_NONE;
#endif
UserButtonThread->initButton(userConfig);
} else {
ButtonConfig userConfigNoScreen;
userConfigNoScreen.pinNumber = (uint8_t)_pinNum;
userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW;
userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP;
userConfigNoScreen.pullupSense = pullup_sense;
userConfigNoScreen.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS;
userConfigNoScreen.longPress = INPUT_BROKER_NONE;
userConfigNoScreen.longPressTime = 500;
userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN;
userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING;
userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE;
UserButtonThread->initButton(userConfigNoScreen);
}
#endif
#endif
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
// After modules are setup, so we can observe modules
setupNicheGraphics();
#endif
#ifdef LED_PIN
// Turn LED off after boot, if heartbeat by config
if (config.device.led_heartbeat_disabled)
digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON);
#endif
// Do this after service.init (because that clears error_code)
#ifdef HAS_PMU
if (!pmu_found)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_NO_AXP192); // Record a hardware fault for missing hardware
#endif
#if !MESHTASTIC_EXCLUDE_I2C
// Don't call screen setup until after nodedb is setup (because we need
// 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) || defined(ST7796_CS) || \
defined(USE_SPISSD1306)
if (screen)
screen->setup();
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
screen->setup();
}
#else
if (screen_found.port != ScanI2C::I2CPort::NO_I2C && screen)
screen->setup();
#endif
#endif
#ifdef PIN_PWR_DELAY_MS
// This may be required to give the peripherals time to power up.
delay(PIN_PWR_DELAY_MS);
#endif
#ifdef ARCH_PORTDUINO
// as one can't use a function pointer to the class constructor:
auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy) {
switch (portduino_config.lora_module) {
case use_rf95:
return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy);
case use_sx1262:
return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy);
case use_sx1268:
return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy);
case use_sx1280:
return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy);
case use_lr1110:
return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy);
case use_lr1120:
return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy);
case use_lr1121:
return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy);
case use_llcc68:
return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy);
case use_simradio:
return (RadioInterface *)new SimRadio;
default:
assert(0); // shouldn't happen
return (RadioInterface *)nullptr;
}
};
LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(),
portduino_config.lora_spi_dev.c_str());
if (portduino_config.lora_spi_dev == "ch341") {
RadioLibHAL = ch341Hal;
} else {
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
}
rIf =
loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin,
portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin);
if (!rIf->init()) {
LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str());
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str());
}
#elif defined(HW_SPI1_DEVICE)
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings);
#elif defined(TCA9535_LORA_RST_VIRTUAL_PIN)
TCA9535GpioHal *RadioLibHAL = new TCA9535GpioHal(SPI, spiSettings);
#else // HW_SPI1_DEVICE
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
#endif
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
#if defined(USE_STM32WLx)
if (!rIf) {
rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("No STM32WL radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("STM32WL init success");
radioType = STM32WLx_RADIO;
}
}
#endif
#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1);
if (!rIf->init()) {
LOG_WARN("No RF95 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("RF95 init success");
radioType = RF95_RADIO;
}
}
#endif
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
#ifdef SX126X_DIO3_TCXO_VOLTAGE
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
#endif
if (!sxIf->init()) {
LOG_WARN("No SX1262 radio");
delete sxIf;
rIf = NULL;
} else {
LOG_INFO("SX1262 init success");
rIf = sxIf;
radioType = SX1262_RADIO;
}
}
#endif
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
// try using the specified TCXO voltage
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
if (!sxIf->init()) {
LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
delete sxIf;
rIf = NULL;
} else {
LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
rIf = sxIf;
radioType = SX1262_RADIO;
}
}
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instead
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("SX1262 init success, XTAL, Vref 0.0V");
radioType = SX1262_RADIO;
}
}
#endif
#if defined(USE_SX1268)
#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL)
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
// try using the specified TCXO voltage
auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
if (!sxIf->init()) {
LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
delete sxIf;
rIf = NULL;
} else {
LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
rIf = sxIf;
radioType = SX1268_RADIO;
}
}
#endif
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("No SX1268 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("SX1268 init success");
radioType = SX1268_RADIO;
}
}
#endif
#if defined(USE_LLCC68)
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("No LLCC68 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("LLCC68 init success");
radioType = LLCC68_RADIO;
}
}
#endif
#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN);
if (!rIf->init()) {
LOG_WARN("No LR1110 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("LR1110 init success");
radioType = LR1110_RADIO;
}
}
#endif
#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1
if (!rIf) {
rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN);
if (!rIf->init()) {
LOG_WARN("No LR1120 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("LR1120 init success");
radioType = LR1120_RADIO;
}
}
#endif
#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1
if (!rIf) {
rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN);
if (!rIf->init()) {
LOG_WARN("No LR1121 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("LR1121 init success");
radioType = LR1121_RADIO;
}
}
#endif
#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1
if (!rIf) {
rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY);
if (!rIf->init()) {
LOG_WARN("No SX1280 radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("SX1280 init success");
radioType = SX1280_RADIO;
}
}
#endif
// check if the radio chip matches the selected region
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) {
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");
if (screen) {
screen->showSimpleBanner("Rebooting...");
}
rebootAtMsec = millis() + 5000;
}
}
lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation)
#if !MESHTASTIC_EXCLUDE_MQTT
mqttInit();
#endif
#ifdef RF95_FAN_EN
// Ability to disable FAN if PIN has been set with RF95_FAN_EN.
// Make sure LoRa has been started before disabling FAN.
if (config.lora.pa_fan_disabled)
digitalWrite(RF95_FAN_EN, LOW ^ 0);
#endif
#ifndef ARCH_PORTDUINO
// Initialize Wifi
#if HAS_WIFI
initWifi();
#endif
#if HAS_ETHERNET
// Initialize Ethernet
initEthernet();
#endif
#endif
#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2)
osk_found = true;
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
// Start web server thread.
webServerThread = new WebServerThread();
#endif
#ifdef ARCH_PORTDUINO
#if __has_include(<ulfius.h>)
if (portduino_config.webserverport != -1) {
piwebServerThread = new PiWebServerThread();
std::atexit([] { delete piwebServerThread; });
}
#endif
initApiServer(TCPPort);
#endif
// Start airtime logger thread.
airTime = new AirTime();
if (!rIf)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_NO_RADIO);
else {
router->addInterface(rIf);
// Log bit rate to debug output
LOG_DEBUG("LoRA bitrate = %f bytes / sec", (float(meshtastic_Constants_DATA_PAYLOAD_LEN) /
(float(rIf->getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN)))) *
1000);
}
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS
powerFSMthread = new PowerFSMThread();
#if !HAS_TFT
setCPUFast(false); // 80MHz is fine for our slow peripherals
#endif
#ifdef ARDUINO_ARCH_ESP32
LOG_DEBUG("Free heap : %7d bytes", ESP.getFreeHeap());
LOG_DEBUG("Free PSRAM : %7d bytes", ESP.getFreePsram());
#endif
// We manually run this to update the NodeStatus
nodeDB->notifyObservers(true);
}
#endif
uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client)
// If a thread does something that might need for it to be rescheduled ASAP it can set this flag
// This will suppress the current delay and instead try to run ASAP.
bool runASAP;
extern meshtastic_DeviceMetadata getDeviceMetadata()
{
meshtastic_DeviceMetadata deviceMetadata;
strncpy(deviceMetadata.firmware_version, optstr(APP_VERSION), sizeof(deviceMetadata.firmware_version));
deviceMetadata.device_state_version = DEVICESTATE_CUR_VER;
deviceMetadata.canShutdown = pmu_found || HAS_CPU_SHUTDOWN;
deviceMetadata.hasBluetooth = HAS_BLUETOOTH;
deviceMetadata.hasWifi = HAS_WIFI;
deviceMetadata.hasEthernet = HAS_ETHERNET;
deviceMetadata.role = config.device.role;
deviceMetadata.position_flags = config.position.position_flags;
deviceMetadata.hw_model = HW_VENDOR;
deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled;
deviceMetadata.excluded_modules = meshtastic_ExcludedModules_EXCLUDED_NONE;
#if MESHTASTIC_EXCLUDE_REMOTEHARDWARE
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_REMOTEHARDWARE_CONFIG;
#endif
#if MESHTASTIC_EXCLUDE_AUDIO
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) && !defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_CANNEDMSG_CONFIG;
#endif
#if NO_EXT_GPIO || MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_EXTNOTIF_CONFIG;
#endif
// Only edge case here is if we apply this a device with built in Accelerometer and want to detect interrupts
// We'll have to macro guard against those targets potentially
#if NO_EXT_GPIO || MESHTASTIC_EXCLUDE_DETECTIONSENSOR
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_DETECTIONSENSOR_CONFIG;
#endif
// If we don't have any GPIO and we don't have GPS OR we don't want too - no purpose in having serial config
#if NO_EXT_GPIO && NO_GPS || MESHTASTIC_EXCLUDE_SERIAL
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_SERIAL_CONFIG;
#endif
#ifndef ARCH_ESP32
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_PAXCOUNTER_CONFIG;
#endif
#if !defined(HAS_RGB_LED) && !RAK_4631
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_AMBIENTLIGHTING_CONFIG;
#endif
// No bluetooth on these targets (yet):
// Pico W / 2W may get it at some point
// Portduino and ESP32-C6 are excluded because we don't have a working bluetooth stacks integrated yet.
#if defined(ARCH_RP2040) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) || defined(CONFIG_IDF_TARGET_ESP32C6)
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_BLUETOOTH_CONFIG;
#endif
#if defined(ARCH_NRF52) && !HAS_ETHERNET // nrf52 doesn't have network unless it's a RAK ethernet gateway currently
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_NETWORK_CONFIG; // No network on nRF52
#elif defined(ARCH_RP2040) && !HAS_WIFI && !HAS_ETHERNET
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_NETWORK_CONFIG; // No network on RP2040
#endif
#if !(MESHTASTIC_EXCLUDE_PKI)
deviceMetadata.hasPKC = true;
#endif
return deviceMetadata;
}
#if !MESHTASTIC_EXCLUDE_I2C
void scannerToSensorsMap(const std::unique_ptr<ScanI2CTwoWire> &i2cScanner, ScanI2C::DeviceType deviceType,
meshtastic_TelemetrySensorType sensorType)
{
auto found = i2cScanner->find(deviceType);
if (found.type != ScanI2C::DeviceType::NONE) {
nodeTelemetrySensorsMap[sensorType].first = found.address.address;
nodeTelemetrySensorsMap[sensorType].second = i2cScanner->fetchI2CBus(found.address);
}
}
#endif
#ifndef PIO_UNIT_TESTING
void loop()
{
runASAP = false;
#ifdef ARCH_ESP32
esp32Loop();
#endif
#ifdef ARCH_NRF52
nrf52Loop();
#endif
power->powerCommandsCheck();
#ifdef DEBUG_STACK
static uint32_t lastPrint = 0;
if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) {
lastPrint = millis();
meshtastic::printThreadInfo("main");
}
#endif
service->loop();
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
if (inputBroker)
inputBroker->processInputEventQueue();
#endif
#if ARCH_PORTDUINO && HAS_TFT
if (screen && portduino_config.displayPanel == x11 &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
auto dispdev = screen->getDisplayDevice();
if (dispdev)
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
}
#endif
long delayMsec = mainController.runOrDelay();
// We want to sleep as long as possible here - because it saves power
if (!runASAP && loopCanSleep()) {
#ifdef DEBUG_LOOP_TIMING
LOG_DEBUG("main loop delay: %d", delayMsec);
#endif
mainDelay.delay(delayMsec);
}
}
#endif