mirror of
https://github.com/meshtastic/firmware.git
synced 2026-02-15 10:01:29 -05:00
* Move PMSA003I to separate class and update AQ telemetry
* AirQualityTelemetry module not depend on PM sensor presence
* Remove commented line
* Fixes on PMS class
* Add missing warmup period to wakeUp function
* Fixes on compilation for different variants
* Add functions to check for I2C bus speed and set it
* Initial implementation for SFA30Sensor
* Move PMSA003I to separate class and update AQ telemetry
* AirQualityTelemetry module not depend on PM sensor presence
* Remove commented line
* Fixes on PMS class
* Add missing warmup period to wakeUp function
* Fixes on compilation for different variants
* Add functions to check for I2C bus speed and set it
* Add ScreenFonts.h
Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>
* PMSA003I 1st round test
* Fix I2C scan speed
* Fix minor issues and bring back I2C SPEED def
* Remove PMSA003I library as its no longer needed
* Add functional SCD4X
* Fix screen frame for CO2
* Add admin commands to SCD4X class
* Add further admin commands and fixes.
* Remove unused I2C speed functions and cleanup
* Cleanup of SEN5X specific code added from switching branches
* Remove SCAN_I2C_CLOCK_SPEED block as its not needed
* Remove associated functions for setting I2C speed
* Unify build epoch to add flag in platformio-custom.py (#7917)
* Unify build_epoch replacement logic in platformio-custom
* Missed one
* Fix build error in rak_wismesh_tap_v2 (#7905)
In the logs was:
"No screen resolution defined in build_flags. Please define DISPLAY_SIZE."
set according to similar devices.
* Put guards in place around debug heap operations (#7955)
* Put guards in place around debug heap operations
* Add macros to clean up code
* Add pointer as well
* Cleanup
* Fix memory leak in NextHopRouter: always free packet copy when removing from pending
* Formatting
* Only queue 2 client notification
* Merge pull request #7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak
Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap
* Merge pull request #7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak
Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap
* Update protobufs (#7973)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
* T-Lora Pager: Support LR1121 and SX1280 models (#7956)
* T-Lora Pager: Support LR1121 and SX1280 models
* Remove ifdefs
* Trunk
* Trunk
* Static memory pool allocation (#7966)
* Static memory pool
* Initializer
* T-Lora Pager: Support LR1121 and SX1280 models (#7956)
* T-Lora Pager: Support LR1121 and SX1280 models
* Remove ifdefs
---------
Co-authored-by: WillyJL <me@willyjl.dev>
* Portduino dynamic alloc
* Missed
* Drop the limit
* Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (#7977)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* Fix json report crashes on esp32 (#7978)
* Tweak maximums
* Fix DRAM overflow on old esp32 targets
* Guard bad time warning logs using GPS_DEBUG (#7897)
In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy.
In combination, these result in a spamming of the logs when a bad time is found
When the GPS is active, we're calling the GPS thread every 0.2secs.
So this log could be printed 4,500 times in a no-lock scenario :)
Reserve this experience for developers using GPS_DEBUG.
Fixes https://github.com/meshtastic/firmware/issues/7896
* Scale probe buffer size based on current baud rate (#7975)
* Scale probe buffer size based on current baud rate
* Throttle bad time validation logging and fix time comparison logic
* Remove comment
* Missed the other instances
* Copy pasta
* Fix GPS gm_mktime memory leak (#7981)
* Fix overflow of time value (#7984)
* Fix overflow of time value
* Revert "Fix overflow of time value"
This reverts commit 0847969201.
* That got boogered up
* Remove PMSA003 include from modules
* Add flag to exclude air quality module
* Rework PMSA003I to align with new I2C scanner
* Reworks AQ telemetry to match new dynamic allocation method
* Adds VBLE_I2C_CLOCK_SPEED build flag for sensors with different I2C speed requirements
* Reworks PMSA003I
* Move add sensor template to separate file
* Split telemetry on screen options
* Add variable I2C clock compile flag
* Added to Seeed Xiao S3 as demo
* Fix drawFrame in AQ module
* Module settings override to i2cScan module function
* Move to CAN_RECLOCK_I2C per architecture
* Add reclock function in TelemetrySensor.cpp
* Add flag in ESP32 common
* Minor fix
* Move I2C reclock function to src/detect
* Fix uninitMemberVar errors and compile issue
* Make sleep, wakeUp functions generic
* Fix STM32 builds
* Add exclude AQ sensor to builds that have environmental sensor excludes
* Add includes to AddI2CSensorTemplate.h
* SEN5X first pass
* WIP Sen5X functions
* Further (non-working) progress in SEN5X
* WIP Sen5X functions
* Changes on SEN5X library - removing pm_env as well
* Small cleanup of SEN5X sensors
* Minor change for SEN5X detection
* Remove dup code
* Enable PM sensor before sending telemetry.
This enables the PM sensor for a predefined period to allow for warmup.
Once telemetry is sent, the sensor shuts down again.
* Small cleanups in SEN5X sensor
* Add dynamic measurement interval for SEN5X
* Only disable SEN5X if enough time after reading.
* Idle for SEN5X on communication error
* Cleanup of logs and remove unnecessary delays
* Small TODO
* Settle on uint16_t for SEN5X PM data
* Make AQTelemetry sensors non-exclusive
* Implementation of cleaning in FS prefs and cleanup
* Remove unnecessary LOGS
* Add cleaning date storage in FS
* Report non-cumulative PN
* Bring back detection code for SEN5X after branch rebase
* Add placeholder for admin message
* Add VOC measurements and persistence (WIP)
* Adds VOC measurements and state
* Still not working on VOC Index persistence
* Should it stay in continuous mode?
* Add one-shot mode config flag to SEN5X
* Add nan checks on sensor data from SEN5X
* Working implementation on VOCState
* Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h)
* Adds conditions for stability and sensor state
* Fixes on VOC state and mode swtiching
* Adds a new RHT/Gas only mode, with 3600s stabilization time
* Fixes the VOCState buffer mismatch
* Fixes SEN50/54/55 model mistake
* Adapt SEN5X to new sensor list structure. Improve reclock.
* Improve reClockI2C conditions for different variants
* Add sleep, wakeUp, pendingForReady, hasSleep functions to PM sensors to save battery
* Add SEN5X
* Fix merge errors
* Small reordering in PMS class for consistency
* If one sensor fails, AQ telemetry still reports data
* Small formatting fix
* Add SEN5X to AQI in ScanI2C
* SCD4X now part of AQ module with template list
* Fixes difference between idle and sleep
* In LowPower, sleep is disabled
* Requires testing for I2C clock comms for commands
* Remove unnecessary import
* Add co2 to serialized AQ metrics
* Add SFA30 with new sensor template in AQ module
* Update library dependencies in platformio.ini
* Fix unitialized variables in SEN5X constructor
* Fix missing import
* Fix uninitMemberVars
* Fix import error for SCD4X
* Fix I2CClock logic
* Fix not reclocking back to 700000Hz
* Fix multiple sensors being read simultaneously
* The logic in AQ module is different to the one in EnvironmentTelemetryModule. In Env module, if any sensor fails to getMetrics, no valid flag for the module. This prevents other sensors to report data.
* Fix pending clock change in PMSA003
* Cleanup of SEN5X class
* Exclude AQ sensor from wio-e5 due to flash limitations
* Fix I2C clock change logic
* Make sure clock is always set to needed value
* Fix returns
* Fix trunk
* Fix on condition in reclock
* Fix trunk
* Final SFA30 class implementation
* Add HCHO to screen and improve logs
* Add metrics to mesh packet serializer
* Minor fixes in logs
* OCD tidy up of logs
* Fix sleep function
* Remove old I2C_CLOCK_SPEED code
---------
Co-authored-by: nikl <nikl174@mailbox.org>
Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>
Co-authored-by: Nashui-Yan <yannashui10@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Mike Robbins <mrobbins@alum.mit.edu>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: WillyJL <me@willyjl.dev>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
1206 lines
39 KiB
C++
1206 lines
39 KiB
C++
#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 "RadioLibInterface.h"
|
|
#include "ReliableRouter.h"
|
|
#include "airtime.h"
|
|
#include "buzz.h"
|
|
#include "power/PowerHAL.h"
|
|
|
|
#include "FSCommon.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/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>
|
|
#if HAS_SCREEN
|
|
#include "MessageStore.h"
|
|
#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
|
|
|
|
#ifdef ARCH_PORTDUINO
|
|
#include "linux/LinuxHardwareI2C.h"
|
|
#include "mesh/raspihttp/PiWebServer.h"
|
|
#include "platform/portduino/PortduinoGlue.h"
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <string>
|
|
#endif
|
|
|
|
#ifdef ARCH_ESP32
|
|
#ifdef DEBUG_PARTITION_TABLE
|
|
#include "esp_partition.h"
|
|
|
|
void printPartitionTable()
|
|
{
|
|
printf("\n--- Partition Table ---\n");
|
|
// Print Column Headers
|
|
printf("| %-16s | %-4s | %-7s | %-10s | %-10s |\n", "Label", "Type", "Subtype", "Offset", "Size");
|
|
printf("|------------------|------|---------|------------|------------|\n");
|
|
|
|
// Create an iterator to find ALL partitions (Type ANY, Subtype ANY)
|
|
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
|
|
|
|
// Loop through the iterator
|
|
if (it != NULL) {
|
|
do {
|
|
const esp_partition_t *part = esp_partition_get(it);
|
|
|
|
// Print details: Label, Type (Hex), Subtype (Hex), Offset (Hex), Size (Hex)
|
|
printf("| %-16s | 0x%02x | 0x%02x | 0x%08x | 0x%08x |\n", part->label, part->type, part->subtype, part->address,
|
|
part->size);
|
|
|
|
// Move to next partition
|
|
it = esp_partition_next(it);
|
|
} while (it != NULL);
|
|
|
|
// Release the iterator memory
|
|
esp_partition_iterator_release(it);
|
|
} else {
|
|
printf("No partitions found.\n");
|
|
}
|
|
printf("-----------------------\n");
|
|
}
|
|
#endif // DEBUG_PARTITION_TABLE
|
|
#endif // ARCH_ESP32
|
|
|
|
#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
|
|
|
|
#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;
|
|
|
|
unsigned long last_listen = 0;
|
|
|
|
// 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;
|
|
|
|
#ifdef HAS_DRV2605
|
|
Adafruit_DRV2605 drv;
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
uint32_t timeLastPowered = 0;
|
|
|
|
static OSThread *powerFSMthread;
|
|
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() {}
|
|
|
|
void earlyInitVariant() __attribute__((weak));
|
|
void earlyInitVariant() {}
|
|
|
|
// NRF52 (and probably other platforms) can report when system is in power failure mode
|
|
// (eg. too low battery voltage) and operating it is unsafe (data corruption, bootloops, etc).
|
|
// For example NRF52 will prevent any flash writes in that case automatically
|
|
// (but it causes issues we need to handle).
|
|
// This detection is independent from whatever ADC or dividers used in Meshtastic
|
|
// boards and is internal to chip.
|
|
|
|
// we use powerHAL layer to get this info and delay booting until power level is safe
|
|
|
|
// wait until power level is safe to continue booting (to avoid bootloops)
|
|
// blink user led in 3 flashes sequence to indicate what is happening
|
|
void waitUntilPowerLevelSafe()
|
|
{
|
|
while (powerHAL_isPowerLevelSafe() == false) {
|
|
|
|
#ifdef LED_POWER
|
|
|
|
// 3x: blink for 300 ms, pause for 300 ms
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
digitalWrite(LED_POWER, LED_STATE_ON);
|
|
delay(300);
|
|
digitalWrite(LED_POWER, LED_STATE_OFF);
|
|
delay(300);
|
|
}
|
|
#endif
|
|
|
|
// sleep for 2s
|
|
delay(2000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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()
|
|
{
|
|
|
|
// initialize power HAL layer as early as possible
|
|
powerHAL_init();
|
|
|
|
#ifdef LED_POWER
|
|
pinMode(LED_POWER, OUTPUT);
|
|
digitalWrite(LED_POWER, LED_STATE_ON);
|
|
#endif
|
|
|
|
// prevent booting if device is in power failure mode
|
|
// boot sequence will follow when battery level raises to safe mode
|
|
waitUntilPowerLevelSafe();
|
|
|
|
// Defined in variant.cpp for early init code
|
|
earlyInitVariant();
|
|
|
|
#if defined(PIN_POWER_EN)
|
|
pinMode(PIN_POWER_EN, OUTPUT);
|
|
digitalWrite(PIN_POWER_EN, HIGH);
|
|
#endif
|
|
|
|
#ifdef LED_NOTIFICATION
|
|
pinMode(LED_NOTIFICATION, OUTPUT);
|
|
digitalWrite(LED_NOTIFICATION, 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, LED_STATE_OFF);
|
|
#endif
|
|
|
|
concurrency::hasBeenSetup = true;
|
|
#if HAS_SCREEN
|
|
meshtastic_Config_DisplayConfig_OledType screen_model =
|
|
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
|
|
#endif
|
|
OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64;
|
|
|
|
#ifdef USE_SEGGER
|
|
auto mode = false ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM;
|
|
#ifdef NRF52840_XXAA
|
|
auto buflen = 4096; // this board has a fair amount of ram
|
|
#else
|
|
auto buflen = 256; // this board has a fair amount of ram
|
|
#endif
|
|
SEGGER_RTT_ConfigUpBuffer(SEGGER_STDOUT_CH, NULL, NULL, buflen, mode);
|
|
#endif
|
|
|
|
#ifdef DEBUG_PORT
|
|
consoleInit(); // Set serial baud rate and init our mesh console
|
|
#endif
|
|
|
|
#ifdef UNPHONE
|
|
unphone.printStore();
|
|
#endif
|
|
|
|
#if ARCH_PORTDUINO
|
|
RTCQuality ourQuality = RTCQualityDevice;
|
|
|
|
std::string timeCommandResult = exec("timedatectl status | grep synchronized | grep yes -c");
|
|
if (timeCommandResult[0] == '1') {
|
|
ourQuality = RTCQualityNTP;
|
|
}
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = time(NULL);
|
|
tv.tv_usec = 0;
|
|
perhapsSetRTC(ourQuality, &tv);
|
|
#endif
|
|
|
|
powerMonInit();
|
|
serialSinceMsec = millis();
|
|
|
|
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
|
|
|
|
#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
|
|
#ifndef SENSECAP_INDICATOR
|
|
// use PSRAM for malloc calls > 256 bytes
|
|
heap_caps_malloc_extmem_enable(256);
|
|
#endif
|
|
#endif
|
|
|
|
#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);
|
|
delay(2);
|
|
digitalWrite(RESET_OLED, 0);
|
|
delay(10);
|
|
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
|
|
initSPI();
|
|
|
|
OSThread::setup();
|
|
|
|
fsInit();
|
|
|
|
#if !MESHTASTIC_EXCLUDE_I2C
|
|
#if defined(I2C_SDA1) && defined(ARCH_RP2040)
|
|
Wire1.setSDA(I2C_SDA1);
|
|
Wire1.setSCL(I2C_SCL1);
|
|
Wire1.begin();
|
|
#elif defined(I2C_SDA1) && !defined(ARCH_RP2040)
|
|
Wire1.begin(I2C_SDA1, I2C_SCL1);
|
|
#elif WIRE_INTERFACES_COUNT == 2
|
|
Wire1.begin();
|
|
#endif
|
|
|
|
#if defined(I2C_SDA) && defined(ARCH_RP2040)
|
|
Wire.setSDA(I2C_SDA);
|
|
Wire.setSCL(I2C_SCL);
|
|
Wire.begin();
|
|
#elif defined(I2C_SDA) && !defined(ARCH_RP2040)
|
|
LOG_INFO("Starting Bus with (SDA) %d and (SCL) %d: ", I2C_SDA, I2C_SCL);
|
|
Wire.begin(I2C_SDA, I2C_SCL);
|
|
#elif defined(ARCH_PORTDUINO)
|
|
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.begin();
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(M5STACK_UNITC6L)
|
|
pinMode(LORA_CS, OUTPUT);
|
|
digitalWrite(LORA_CS, 1);
|
|
c6l_init();
|
|
#endif
|
|
|
|
#ifdef PIN_LCD_RESET
|
|
// FIXME - move this someplace better, LCD is at address 0x3F
|
|
pinMode(PIN_LCD_RESET, OUTPUT);
|
|
digitalWrite(PIN_LCD_RESET, 0);
|
|
delay(1);
|
|
digitalWrite(PIN_LCD_RESET, 1);
|
|
delay(1);
|
|
#endif
|
|
|
|
#ifdef AQ_SET_PIN
|
|
// RAK-12039 set pin for Air quality sensor. Detectable on I2C after ~3 seconds, so we need to rescan later
|
|
pinMode(AQ_SET_PIN, OUTPUT);
|
|
digitalWrite(AQ_SET_PIN, HIGH);
|
|
#endif
|
|
|
|
// Currently only the tbeam has a PMU
|
|
// PMU initialization needs to be placed before i2c scanning
|
|
power = new Power();
|
|
power->setStatusHandler(powerStatus);
|
|
powerStatus->observe(&power->newStatus);
|
|
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
|
|
#ifdef DEBUG_PARTITION_TABLE
|
|
printPartitionTable();
|
|
#endif
|
|
#endif // ARCH_ESP32
|
|
#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
|
|
|
|
#if HAS_SCREEN
|
|
auto screenInfo = i2cScanner->firstScreen();
|
|
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:
|
|
screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_SH1106;
|
|
break;
|
|
case ScanI2C::DeviceType::SCREEN_SSD1306:
|
|
screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_SSD1306;
|
|
break;
|
|
case ScanI2C::DeviceType::SCREEN_ST7567:
|
|
case ScanI2C::DeviceType::SCREEN_UNKNOWN:
|
|
default:
|
|
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:
|
|
kb_model = 0x02;
|
|
break;
|
|
case ScanI2C::DeviceType::CARDKB:
|
|
kb_model = 0x00;
|
|
break;
|
|
case ScanI2C::DeviceType::TDECKKB:
|
|
// assign an arbitrary value to distinguish from other models
|
|
kb_model = 0x10;
|
|
break;
|
|
case ScanI2C::DeviceType::BBQ10KB:
|
|
// assign an arbitrary value to distinguish from other models
|
|
kb_model = 0x11;
|
|
break;
|
|
case ScanI2C::DeviceType::MPR121KB:
|
|
// assign an arbitrary value to distinguish from other models
|
|
kb_model = 0x37;
|
|
break;
|
|
case ScanI2C::DeviceType::TCA8418KB:
|
|
// assign an arbitrary value to distinguish from other models
|
|
kb_model = 0x84;
|
|
break;
|
|
default:
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
pmu_found = i2cScanner->exists(ScanI2C::DeviceType::PMU_AXP192_AXP2101);
|
|
|
|
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".
|
|
*/
|
|
|
|
// Two supported RGB LED currently
|
|
#ifdef HAS_RGB_LED
|
|
rgb_found = i2cScanner->firstRGBLED();
|
|
#endif
|
|
|
|
#ifdef HAS_TPS65233
|
|
// TPS65233 is a power management IC for satellite modems, used in the Dreamcatcher
|
|
// We are switching it off here since we don't use an LNB.
|
|
if (i2cScanner->exists(ScanI2C::DeviceType::TPS65233)) {
|
|
Wire.beginTransmission(TPS65233_ADDR);
|
|
Wire.write(0); // Register 0
|
|
Wire.write(128); // Turn off the LNB power, keep I2C Control enabled
|
|
Wire.endTransmission();
|
|
Wire.beginTransmission(TPS65233_ADDR);
|
|
Wire.write(1); // Register 1
|
|
Wire.write(0); // Turn off Tone Generator 22kHz
|
|
Wire.endTransmission();
|
|
}
|
|
#endif
|
|
|
|
#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
|
|
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310U, meshtastic_TelemetrySensorType_QMC6310);
|
|
// TODO: Types need to be added meshtastic_TelemetrySensorType_QMC6310N
|
|
// scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310N, meshtastic_TelemetrySensorType_QMC6310N);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
|
|
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
|
|
#endif
|
|
|
|
#ifdef HAS_SDCARD
|
|
setupSDCard();
|
|
#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();
|
|
|
|
#if HAS_SCREEN
|
|
// 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
|
|
#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
|
|
|
|
#ifdef HAS_DRV2605
|
|
#if defined(PIN_DRV_EN)
|
|
pinMode(PIN_DRV_EN, OUTPUT);
|
|
digitalWrite(PIN_DRV_EN, HIGH);
|
|
delay(10);
|
|
#endif
|
|
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) || defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR)
|
|
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
|
|
|
|
// TODO Remove magic string
|
|
// 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);
|
|
} 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();
|
|
|
|
// Set osk_found for trackball/encoder devices BEFORE setupModules so CannedMessageModule can detect it
|
|
#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2)
|
|
#ifndef HAS_PHYSICAL_KEYBOARD
|
|
osk_found = true;
|
|
#endif
|
|
#endif
|
|
|
|
// Now that the mesh service is created, create any modules
|
|
setupModules();
|
|
|
|
#if !MESHTASTIC_EXCLUDE_I2C
|
|
// Inform modules about I2C devices
|
|
ScanI2CCompleted(i2cScanner.get());
|
|
i2cScanner.reset();
|
|
#endif
|
|
|
|
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
|
// 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;
|
|
}
|
|
#endif
|
|
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
|
|
if (inputBroker)
|
|
inputBroker->Init();
|
|
#endif
|
|
|
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
|
// After modules are setup, so we can observe modules
|
|
setupNicheGraphics();
|
|
#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_ST7796) || defined(USE_SPISSD1306) || defined(HACKADAY_COMMUNICATOR)
|
|
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
|
|
|
|
initLoRa();
|
|
|
|
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(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)
|
|
bool suppressRebootBanner; // If true, suppress "Rebooting..." overlay (used for OTA handoff)
|
|
|
|
// 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;
|
|
|
|
// TODO find better home than main.cpp
|
|
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();
|
|
|
|
if (RadioLibInterface::instance != nullptr && !Throttle::isWithinTimespanMs(last_listen, 1000 * 60) &&
|
|
!(RadioLibInterface::instance->isSending() || RadioLibInterface::instance->isActivelyReceiving())) {
|
|
RadioLibInterface::instance->startReceive();
|
|
LOG_DEBUG("attempting AGC reset");
|
|
}
|
|
|
|
#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
|
|
if (portduino_config.lora_spi_dev == "ch341" && ch341Hal != nullptr) {
|
|
ch341Hal->checkError();
|
|
}
|
|
if (portduino_status.LoRa_in_error && rebootAtMsec == 0) {
|
|
LOG_ERROR("LoRa in error detected, attempting to recover");
|
|
if (rIf != nullptr) {
|
|
delete rIf;
|
|
rIf = nullptr;
|
|
}
|
|
if (portduino_config.lora_spi_dev == "ch341") {
|
|
if (ch341Hal != nullptr) {
|
|
delete ch341Hal;
|
|
ch341Hal = nullptr;
|
|
sleep(3);
|
|
}
|
|
try {
|
|
ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid,
|
|
portduino_config.lora_usb_pid);
|
|
} catch (std::exception &e) {
|
|
std::cerr << e.what() << std::endl;
|
|
std::cerr << "Could not initialize CH341 device!" << std::endl;
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
if (initLoRa()) {
|
|
router->addInterface(rIf);
|
|
portduino_status.LoRa_in_error = false;
|
|
} else {
|
|
LOG_WARN("Reconfigure failed, rebooting");
|
|
if (screen) {
|
|
screen->showSimpleBanner("Rebooting...");
|
|
}
|
|
rebootAtMsec = millis() + 25;
|
|
}
|
|
}
|
|
#if 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
|
|
#endif
|
|
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
|
|
messageStoreAutosaveTick();
|
|
#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
|