diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 12e6696c0..9d563a39a 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,14 +9,14 @@ plugins: lint: enabled: - checkov@3.2.497 - - renovate@42.81.8 + - renovate@42.84.2 - prettier@3.8.0 - - trufflehog@3.92.4 + - trufflehog@3.92.5 - yamllint@1.38.0 - - bandit@1.9.2 + - bandit@1.9.3 - trivy@0.68.2 - taplo@0.10.0 - - ruff@0.14.11 + - ruff@0.14.13 - isort@7.0.0 - markdownlint@0.47.0 - oxipng@10.0.0 @@ -26,7 +26,7 @@ lint: - hadolint@2.14.0 - shfmt@3.6.0 - shellcheck@0.11.0 - - black@25.12.0 + - black@26.1.0 - git-diff-check - gitleaks@8.30.0 - clang-format@16.0.3 diff --git a/bin/device-install.sh b/bin/device-install.sh index 1778a952d..49427524e 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -32,6 +32,19 @@ if ! command -v jq >/dev/null 2>&1; then exit 1 fi +# esptool v5 supports commands with dashes and deprecates commands with +# underscores. Prior versions only support commands with underscores +if ${ESPTOOL_CMD} | grep --quiet write-flash +then + ESPTOOL_WRITE_FLASH=write-flash + ESPTOOL_ERASE_FLASH=erase-flash + ESPTOOL_READ_FLASH_STATUS=read-flash-status +else + ESPTOOL_WRITE_FLASH=write_flash + ESPTOOL_ERASE_FLASH=erase_flash + ESPTOOL_READ_FLASH_STATUS=read_flash_status +fi + set -e # Usage info @@ -83,8 +96,8 @@ while [ $# -gt 0 ]; do done if [[ $BPS_RESET == true ]]; then - $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status - exit 0 + $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS} + exit 0 fi [ -z "$FILENAME" ] && [ -n "$1" ] && { @@ -144,12 +157,12 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then fi echo "Trying to flash ${FILENAME}, but first erasing and writing system information" - $ESPTOOL_CMD erase-flash - $ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}" + $ESPTOOL_CMD ${ESPTOOL_ERASE_FLASH} + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $FIRMWARE_OFFSET "${FILENAME}" echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}" - $ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}" + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OTA_OFFSET "${OTAFILE}" echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}" - $ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}" + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OFFSET "${SPIFFSFILE}" else show_help diff --git a/bin/device-update.sh b/bin/device-update.sh index 1c3d6be70..10eb5eedd 100755 --- a/bin/device-update.sh +++ b/bin/device-update.sh @@ -20,6 +20,17 @@ else exit 1 fi +# esptool v5 supports commands with dashes and deprecates commands with +# underscores. Prior versions only support commands with underscores +if ${ESPTOOL_CMD} | grep --quiet write-flash +then + ESPTOOL_WRITE_FLASH=write-flash + ESPTOOL_READ_FLASH_STATUS=read-flash-status +else + ESPTOOL_WRITE_FLASH=write_flash + ESPTOOL_READ_FLASH_STATUS=read_flash_status +fi + # Usage info show_help() { cat << EOF @@ -69,7 +80,7 @@ done shift "$((OPTIND-1))" if [ "$CHANGE_MODE" = true ]; then - $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status + $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS} exit 0 fi @@ -80,7 +91,7 @@ fi if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then echo "Trying to flash update ${FILENAME}" - $ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}" + $ESPTOOL_CMD --baud $FLASH_BAUD ${ESPTOOL_WRITE_FLASH} $UPDATE_OFFSET "${FILENAME}" else show_help echo "Invalid file: ${FILENAME}" diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index cb8985ee6..6ad8962d1 100644 --- a/bin/org.meshtastic.meshtasticd.metainfo.xml +++ b/bin/org.meshtastic.meshtasticd.metainfo.xml @@ -87,6 +87,9 @@ + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18 diff --git a/boards/minimesh_lite.json b/boards/minimesh_lite.json new file mode 100644 index 000000000..0b8f0b909 --- /dev/null +++ b/boards/minimesh_lite.json @@ -0,0 +1,50 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DMINIMESH_LITE -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"] + ], + "usb_product": "Minimesh Lite", + "mcu": "nrf52840", + "variant": "dls_Minimesh_Lite", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Minimesh Lite", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://deeplabstudio.com", + "vendor": "Deeplab Studio" +} diff --git a/debian/changelog b/debian/changelog index 5f25d53ad..38489b074 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.19.0) unstable; urgency=medium + + * Version 2.7.19 + + -- GitHub Actions Thu, 22 Jan 2026 22:17:40 +0000 + meshtasticd (2.7.18.0) unstable; urgency=medium * Version 2.7.18 diff --git a/monitor/filter_c3_exception_decoder.py b/monitor/filter_c3_exception_decoder.py index 5e74dc2b9..fbc372bcf 100644 --- a/monitor/filter_c3_exception_decoder.py +++ b/monitor/filter_c3_exception_decoder.py @@ -43,13 +43,11 @@ class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase): self.enabled = self.setup_paths() if self.config.get("env:" + self.environment, "build_type") != "debug": - print( - """ + print(""" Please build project in debug configuration to get more details about an exception. See https://docs.platformio.org/page/projectconf/build_configurations.html -""" - ) +""") return self diff --git a/platformio.ini b/platformio.ini index fbf65f359..13f4ec273 100644 --- a/platformio.ini +++ b/platformio.ini @@ -119,7 +119,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/5a870c623a4e9ab7a7abe3d02950536f107d1a31.zip + https://github.com/meshtastic/device-ui/archive/613c0953313bbd236f4ddc5ede447e9edf8e890a.zip ; Common libs for environmental measurements in telemetry module [environmental_base] @@ -212,3 +212,30 @@ lib_deps = sensirion/Sensirion Core@0.7.2 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 +; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets) +[environmental_extra_no_bsec] +lib_deps = + # renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library + adafruit/Adafruit BMP3XX Library@2.1.6 + # renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X + adafruit/Adafruit MAX1704X@1.0.3 + # renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library + adafruit/Adafruit SHTC3 Library@1.0.2 + # renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X + adafruit/Adafruit LPS2X@2.0.6 + # renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library + adafruit/Adafruit SHT31 Library@2.2.2 + # renovate: datasource=custom.pio depName=Adafruit VEML7700 packageName=adafruit/library/Adafruit VEML7700 Library + adafruit/Adafruit VEML7700 Library@2.1.6 + # renovate: datasource=custom.pio depName=Adafruit SHT4x packageName=adafruit/library/Adafruit SHT4x Library + adafruit/Adafruit SHT4x Library@1.0.5 + # renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library + sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6 + # renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001 + closedcube/ClosedCube OPT3001@1.1.2 + # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master + https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip + # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core + sensirion/Sensirion Core@0.7.2 + # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x + sensirion/Sensirion I2C SCD4x@1.1.0 \ No newline at end of file diff --git a/protobufs b/protobufs index 4b9f104a1..77c8329a5 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 4b9f104a18ea43b1b2091ee2b48899fe43ad8a0b +Subproject commit 77c8329a59a9c96a61c447b5d5f1a52ca583e4f2 diff --git a/src/Power.cpp b/src/Power.cpp index e320f0557..b2a4ddaaf 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1,11 +1,14 @@ /** * @file Power.cpp - * @brief This file contains the implementation of the Power class, which is responsible for managing power-related functionality - * of the device. It includes battery level sensing, power management unit (PMU) control, and power state machine management. The - * Power class is used by the main device class to manage power-related functionality. + * @brief This file contains the implementation of the Power class, which is + * responsible for managing power-related functionality of the device. It + * includes battery level sensing, power management unit (PMU) control, and + * power state machine management. The Power class is used by the main device + * class to manage power-related functionality. * - * The file also includes implementations of various battery level sensors, such as the AnalogBatteryLevel class, which assumes - * the battery voltage is attached via a voltage-divider to an analog input. + * The file also includes implementations of various battery level sensors, such + * as the AnalogBatteryLevel class, which assumes the battery voltage is + * attached via a voltage-divider to an analog input. * * This file is part of the Meshtastic project. * For more information, see: https://meshtastic.org/ @@ -19,6 +22,7 @@ #include "configuration.h" #include "main.h" #include "meshUtils.h" +#include "power/PowerHAL.h" #include "sleep.h" #if defined(ARCH_PORTDUINO) @@ -171,22 +175,12 @@ Power *power; using namespace meshtastic; -#ifndef AREF_VOLTAGE -#if defined(ARCH_NRF52) -/* - * Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4, - * 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels. - * - * External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning - * VDD/4, VDD/2 or VDD for the ADC levels. - * - * Default settings are internal reference with 1/6 gain (GND..3.6V ADC range) - */ -#define AREF_VOLTAGE 3.6 -#else +// NRF52 has AREF_VOLTAGE defined in architecture.h but +// make sure it's included. If something is wrong with NRF52 +// definition - compilation will fail on missing definition +#if !defined(AREF_VOLTAGE) && !defined(ARCH_NRF52) #define AREF_VOLTAGE 3.3 #endif -#endif /** * If this board has a battery level sensor, set this to a valid implementation @@ -233,7 +227,8 @@ static void battery_adcDisable() #endif /** - * A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input + * A simple battery level sensor that assumes the battery voltage is attached + * via a voltage-divider to an analog input */ class AnalogBatteryLevel : public HasBatteryLevel { @@ -311,7 +306,8 @@ class AnalogBatteryLevel : public HasBatteryLevel #ifndef BATTERY_SENSE_SAMPLES #define BATTERY_SENSE_SAMPLES \ - 15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment. + 15 // Set the number of samples, it has an effect of increasing sensitivity in + // complex electromagnetic environment. #endif #ifdef BATTERY_PIN @@ -341,7 +337,8 @@ class AnalogBatteryLevel : public HasBatteryLevel battery_adcDisable(); if (!initial_read_done) { - // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct + // Flush the smoothing filter with an ADC reading, if the reading is + // plausibly correct if (scaled > last_read_value) last_read_value = scaled; initial_read_done = true; @@ -350,8 +347,8 @@ class AnalogBatteryLevel : public HasBatteryLevel last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF } - // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) - // (last_read_value)); + // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", + // BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) (last_read_value)); } return last_read_value; #endif // BATTERY_PIN @@ -420,7 +417,8 @@ class AnalogBatteryLevel : public HasBatteryLevel /** * return true if there is a battery installed in this unit */ - // if we have a integrated device with a battery, we can assume that the battery is always connected + // if we have a integrated device with a battery, we can assume that the + // battery is always connected #ifdef BATTERY_IMMUTABLE virtual bool isBatteryConnect() override { return true; } #elif defined(ADC_V) @@ -441,10 +439,10 @@ class AnalogBatteryLevel : public HasBatteryLevel virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; } #endif - /// If we see a battery voltage higher than physics allows - assume charger is pumping - /// in power - /// On some boards we don't have the power management chip (like AXPxxxx) - /// so we use EXT_PWR_DETECT GPIO pin to detect external power source + /// If we see a battery voltage higher than physics allows - assume charger is + /// pumping in power On some boards we don't have the power management chip + /// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power + /// source virtual bool isVbusIn() override { #ifdef EXT_PWR_DETECT @@ -461,8 +459,12 @@ class AnalogBatteryLevel : public HasBatteryLevel } // if it's not HIGH - check the battery #endif -#elif defined(MUZI_BASE) - return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk; + +// technically speaking this should work for all(?) NRF52 boards +// but needs testing across multiple devices. NRF52 USB would not even work if +// VBUS was not properly connected and detected by the CPU +#elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO) + return powerHAL_isVBUSConnected(); #endif return getBattVoltage() > chargingVolt; } @@ -485,8 +487,9 @@ class AnalogBatteryLevel : public HasBatteryLevel #else #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION) if (hasINA()) { - // get current flow from INA sensor - negative value means power flowing into the battery - // default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD + // get current flow from INA sensor - negative value means power flowing + // into the battery default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT + // RESISTOR <--> INA_VIN- <--> LOAD LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address); #if defined(INA_CHARGING_DETECTION_INVERT) return getINACurrent() > 0; @@ -502,8 +505,8 @@ class AnalogBatteryLevel : public HasBatteryLevel } private: - /// If we see a battery voltage higher than physics allows - assume charger is pumping - /// in power + /// If we see a battery voltage higher than physics allows - assume charger is + /// pumping in power /// For heltecs with no battery connected, the measured voltage is 2204, so // need to be higher than that, in this case is 2500mV (3000-500) @@ -512,7 +515,8 @@ class AnalogBatteryLevel : public HasBatteryLevel const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS; // Start value from minimum voltage for the filter to not start from 0 // that could trigger some events. - // This value is over-written by the first ADC reading, it the voltage seems reasonable. + // This value is over-written by the first ADC reading, it the voltage seems + // reasonable. bool initial_read_done = false; float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS); uint32_t last_read_time_ms = 0; @@ -654,7 +658,8 @@ bool Power::analogInit() #ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3 else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) { - LOG_INFO("ADC config based on Two Point values and fitting curve coefficients stored in eFuse"); + LOG_INFO("ADC config based on Two Point values and fitting curve " + "coefficients stored in eFuse"); } #endif else { @@ -662,13 +667,7 @@ bool Power::analogInit() } #endif // ARCH_ESP32 -#ifdef ARCH_NRF52 -#ifdef VBAT_AR_INTERNAL - analogReference(VBAT_AR_INTERNAL); -#else - analogReference(AR_INTERNAL); // 3.6V -#endif -#endif // ARCH_NRF52 + // NRF52 ADC init moved to powerHAL_init in nrf52 platform #ifndef ARCH_ESP32 analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); @@ -723,6 +722,16 @@ bool Power::setup() runASAP = true; }, CHANGE); +#endif +#ifdef EXT_CHRG_DETECT + attachInterrupt( + EXT_CHRG_DETECT, + []() { + power->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + }, + CHANGE); #endif enabled = found; low_voltage_counter = 0; @@ -769,7 +778,8 @@ void Power::reboot() HAL_NVIC_SystemReset(); #else rebootAtMsec = -1; - LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied"); + LOG_WARN("FIXME implement reboot for this platform. Note that some settings " + "require a restart to be applied"); #endif } @@ -779,9 +789,12 @@ void Power::shutdown() #if HAS_SCREEN if (screen) { #ifdef T_DECK_PRO - screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button + screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", + 0); // T-Deck Pro has no power button #elif defined(USE_EINK) - screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen + screen->showSimpleBanner("Shutting Down...", + 2250); // dismiss after 3 seconds to avoid the + // banner on the sleep screen #else screen->showSimpleBanner("Shutting Down...", 0); // stays on screen #endif @@ -820,7 +833,8 @@ void Power::readPowerStatus() int32_t batteryVoltageMv = -1; // Assume unknown int8_t batteryChargePercent = -1; OptionalBool usbPowered = OptUnknown; - OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time + OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM + // code doesn't run every time OptionalBool isChargingNow = OptUnknown; if (batteryLevel) { @@ -833,9 +847,10 @@ void Power::readPowerStatus() if (batteryLevel->getBatteryPercent() >= 0) { batteryChargePercent = batteryLevel->getBatteryPercent(); } else { - // If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error - // In that case, we compute an estimate of the charge percent based on open circuit voltage table defined - // in power.h + // If the AXP192 returns a percentage less than 0, the feature is either + // not supported or there is an error In that case, we compute an + // estimate of the charge percent based on open circuit voltage table + // defined in power.h batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) / ((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))), 0, 100); @@ -843,12 +858,12 @@ void Power::readPowerStatus() } } -// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass -// (which shares a superclass with the BatteryLevel stuff) -// that just provides a few methods. But in the interest of fixing this bug I'm going to follow current -// practice. -#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect - // changes. +// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way +// better instead to make a Nrf52IsUsbPowered subclass (which shares a +// superclass with the BatteryLevel stuff) that just provides a few methods. But +// in the interest of fixing this bug I'm going to follow current practice. +#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates + // the power states. Takes 20 seconds or so to detect changes. nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get(); // LOG_DEBUG("NRF Power %d", nrf_usb_state); @@ -922,8 +937,9 @@ void Power::readPowerStatus() #endif - // If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in - // a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough. + // If we have a battery at all and it is less than 0%, force deep sleep if we + // have more than 10 low readings in a row. NOTE: min LiIon/LiPo voltage + // is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough. // if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) { @@ -945,8 +961,8 @@ int32_t Power::runOnce() readPowerStatus(); #ifdef HAS_PMU - // WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll - // the IRQ status by reading the registers over I2C + // WE no longer use the IRQ line to wake the CPU (due to false wakes from + // sleep), but we do poll the IRQ status by reading the registers over I2C if (PMU) { PMU->getIrqStatus(); @@ -988,7 +1004,8 @@ int32_t Power::runOnce() PMU->clearIrqStatus(); } #endif - // Only read once every 20 seconds once the power status for the app has been initialized + // Only read once every 20 seconds once the power status for the app has been + // initialized return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME; } @@ -996,10 +1013,12 @@ int32_t Power::runOnce() * Init the power manager chip * * axp192 power - DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the - axp192 share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this - on!) LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of - days), can not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS + DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose + comms to the axp192 because the OLED and the axp192 share the same i2c bus, + instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> + ESP32 (keep this on!) LDO1 30mA -> charges GPS backup battery // charges the + tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can + not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS * */ bool Power::axpChipInit() @@ -1044,9 +1063,10 @@ bool Power::axpChipInit() if (!PMU) { /* - * In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will be called at the same time. - * In order not to affect other devices, if the initialization of the PMU fails, Wire needs to be re-initialized once, - * if there are multiple devices sharing the bus. + * In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will + * be called at the same time. In order not to affect other devices, if the + * initialization of the PMU fails, Wire needs to be re-initialized once, if + * there are multiple devices sharing the bus. * * */ #ifndef PMU_USE_WIRE1 w->begin(I2C_SDA, I2C_SCL); @@ -1063,8 +1083,8 @@ bool Power::axpChipInit() PMU->enablePowerOutput(XPOWERS_LDO2); // oled module power channel, - // disable it will cause abnormal communication between boot and AXP power supply, - // do not turn it off + // disable it will cause abnormal communication between boot and AXP power + // supply, do not turn it off PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); // enable oled power PMU->enablePowerOutput(XPOWERS_DCDC1); @@ -1091,7 +1111,8 @@ bool Power::axpChipInit() PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); } else if (PMU->getChipModel() == XPOWERS_AXP2101) { - /*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it uses an AXP2101 power chip*/ + /*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it + * uses an AXP2101 power chip*/ if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { // Unuse power channel PMU->disablePowerOutput(XPOWERS_DCDC2); @@ -1126,8 +1147,8 @@ bool Power::axpChipInit() // t-beam s3 core /** * gnss module power channel - * The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during - * initialization + * The default ALDO4 is off, you need to turn on the GNSS power first, + * otherwise it will be invalid during initialization */ PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); PMU->enablePowerOutput(XPOWERS_ALDO4); @@ -1177,7 +1198,8 @@ bool Power::axpChipInit() // disable all axp chip interrupt PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ); - // Set the constant current charging current of AXP2101, temporarily use 500mA by default + // Set the constant current charging current of AXP2101, temporarily use + // 500mA by default PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); // Set up the charging voltage @@ -1243,11 +1265,12 @@ bool Power::axpChipInit() PMU->getPowerChannelVoltage(XPOWERS_BLDO2)); } -// We can safely ignore this approach for most (or all) boards because MCU turned off -// earlier than battery discharged to 2.6V. +// We can safely ignore this approach for most (or all) boards because MCU +// turned off earlier than battery discharged to 2.6V. // -// Unfortanly for now we can't use this killswitch for RAK4630-based boards because they have a bug with -// battery voltage measurement. Probably it sometimes drops to low values. +// Unfortunately for now we can't use this killswitch for RAK4630-based boards +// because they have a bug with battery voltage measurement. Probably it +// sometimes drops to low values. #ifndef RAK4630 // Set PMU shutdown voltage at 2.6V to maximize battery utilization PMU->setSysPowerDownVoltage(2600); @@ -1266,10 +1289,12 @@ bool Power::axpChipInit() attachInterrupt( PMU_IRQ, [] { pmu_irq = true; }, FALLING); - // we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ because it occurs repeatedly while there is - // no battery also it could cause inadvertent waking from light sleep just because the battery filled - // we don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed - // we don't look at AXPXXX_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus + // we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ + // because it occurs repeatedly while there is no battery also it could cause + // inadvertent waking from light sleep just because the battery filled we + // don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while + // no battery installed we don't look at AXPXXX_VBUS_REMOVED_IRQ because we + // don't have anything hooked to vbus PMU->enableIRQ(pmuIrqMask); PMU->clearIrqStatus(); @@ -1385,8 +1410,8 @@ class LipoCharger : public HasBatteryLevel bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR); if (result) { LOG_INFO("PPM BQ25896 init succeeded"); - // Set the minimum operating voltage. Below this voltage, the PPM will protect - // PPM->setSysPowerDownVoltage(3100); + // Set the minimum operating voltage. Below this voltage, the PPM will + // protect PPM->setSysPowerDownVoltage(3100); // Set input current limit, default is 500mA // PPM->setInputCurrentLimit(800); @@ -1409,7 +1434,8 @@ class LipoCharger : public HasBatteryLevel PPM->enableMeasure(); // Turn on charging function - // If there is no battery connected, do not turn on the charging function + // If there is no battery connected, do not turn on the charging + // function PPM->enableCharge(); } else { LOG_WARN("PPM BQ25896 init failed"); @@ -1444,7 +1470,8 @@ class LipoCharger : public HasBatteryLevel virtual int getBatteryPercent() override { return -1; - // return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated + // return bq->getChargePercent(); // don't use BQ27220 for battery percent, + // it is not calibrated } /** @@ -1566,7 +1593,8 @@ bool Power::meshSolarInit() #else /** - * The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel + * The meshSolar battery level sensor is unavailable - default to + * AnalogBatteryLevel */ bool Power::meshSolarInit() { diff --git a/src/configuration.h b/src/configuration.h index eb258651c..f7b438272 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -155,6 +155,10 @@ along with this program. If not, see . #endif // Default system gain to 0 if not defined +#ifndef NUM_PA_POINTS +#define NUM_PA_POINTS 1 +#endif + #ifndef TX_GAIN_LORA #define TX_GAIN_LORA 0 #endif @@ -445,18 +449,6 @@ along with this program. If not, see . #endif #endif -// BME680 BSEC2 support detection -#if !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) -#if defined(RAK_4631) || defined(TBEAM_V10) - -#define MESHTASTIC_BME680_BSEC2_SUPPORTED 1 -#define MESHTASTIC_BME680_HEADER -#else -#define MESHTASTIC_BME680_BSEC2_SUPPORTED 0 -#define MESHTASTIC_BME680_HEADER -#endif // defined(RAK_4631) -#endif // !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) - // ----------------------------------------------------------------------------- // Global switches to turn off features for a minimized build // ----------------------------------------------------------------------------- diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 75b65c65f..2dca38d66 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -438,7 +438,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, if (currentResolution == ScreenResolution::UltraLow) { snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num); } else { - snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num); + snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz (%d)", freqStr, config.lora.channel_num); } } size_t len = strlen(frequencyslot); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 5c459d984..c5a4106e7 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -65,12 +65,12 @@ uint8_t test_count = 0; void menuHandler::loraMenu() { - static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "LoRa Region"}; - enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, lora_picker = 3 }; + static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region"}; + enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, frequency_slot = 3, lora_picker = 4 }; BannerOverlayOptions bannerOptions; bannerOptions.message = "LoRa Actions"; bannerOptions.optionsArrayPtr = optionsArray; - bannerOptions.optionsCount = 4; + bannerOptions.optionsCount = 5; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Back) { // No action @@ -78,6 +78,8 @@ void menuHandler::loraMenu() menuHandler::menuQueue = menuHandler::device_role_picker; } else if (selected == radio_preset_picker) { menuHandler::menuQueue = menuHandler::radio_preset_picker; + } else if (selected == frequency_slot) { + menuHandler::menuQueue = menuHandler::frequency_slot; } else if (selected == lora_picker) { menuHandler::menuQueue = menuHandler::lora_picker; } @@ -248,6 +250,113 @@ void menuHandler::DeviceRolePicker() screen->showOverlayBanner(bannerOptions); } +void menuHandler::FrequencySlotPicker() +{ + + enum ReplyOptions : int { Back = -1 }; + constexpr int MAX_CHANNEL_OPTIONS = 202; + static const char *optionsArray[MAX_CHANNEL_OPTIONS]; + static int optionsEnumArray[MAX_CHANNEL_OPTIONS]; + static char channelText[MAX_CHANNEL_OPTIONS - 1][12]; + int options = 0; + optionsArray[options] = "Back"; + optionsEnumArray[options++] = Back; + optionsArray[options] = "Slot 0 (Auto)"; + optionsEnumArray[options++] = 0; + + // Calculate number of channels (copied from RadioInterface::applyModemConfig()) + meshtastic_Config_LoRaConfig &loraConfig = config.lora; + double bw = loraConfig.bandwidth; + if (loraConfig.use_preset) { + switch (loraConfig.modem_preset) { + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: + bw = (myRegion->wideLora) ? 1625.0 : 500; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: + bw = (myRegion->wideLora) ? 812.5 : 250; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: + bw = (myRegion->wideLora) ? 812.5 : 250; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: + bw = (myRegion->wideLora) ? 812.5 : 250; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: + bw = (myRegion->wideLora) ? 812.5 : 250; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO: + bw = (myRegion->wideLora) ? 1625.0 : 500; + break; + default: + bw = (myRegion->wideLora) ? 812.5 : 250; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: + bw = (myRegion->wideLora) ? 406.25 : 125; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: + bw = (myRegion->wideLora) ? 406.25 : 125; + break; + } + } else { + bw = loraConfig.bandwidth; + if (bw == 31) // This parameter is not an integer + bw = 31.25; + if (bw == 62) // Fix for 62.5Khz bandwidth + bw = 62.5; + if (bw == 200) + bw = 203.125; + if (bw == 400) + bw = 406.25; + if (bw == 800) + bw = 812.5; + if (bw == 1600) + bw = 1625.0; + } + + uint32_t numChannels = 0; + if (myRegion) { + numChannels = (uint32_t)floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000.0))); + } else { + LOG_WARN("Region not set, cannot calculate number of channels"); + return; + } + + if (numChannels > (uint32_t)(MAX_CHANNEL_OPTIONS - 2)) + numChannels = (uint32_t)(MAX_CHANNEL_OPTIONS - 2); + + for (uint32_t ch = 1; ch <= numChannels; ch++) { + snprintf(channelText[ch - 1], sizeof(channelText[ch - 1]), "Slot %lu", (unsigned long)ch); + optionsArray[options] = channelText[ch - 1]; + optionsEnumArray[options++] = (int)ch; + } + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Frequency Slot"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.optionsCount = options; + + // Start highlight on current channel if possible, otherwise on "1" + int initial = (int)config.lora.channel_num + 1; + if (initial < 2 || initial > (int)numChannels + 1) + initial = 1; + bannerOptions.InitialSelected = initial; + + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Back) { + menuHandler::menuQueue = menuHandler::lora_Menu; + screen->runNow(); + return; + } + + config.lora.channel_num = selected; + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + }; + + screen->showOverlayBanner(bannerOptions); +} + void menuHandler::RadioPresetPicker() { static const RadioPresetOption presetOptions[] = { @@ -278,6 +387,8 @@ void menuHandler::RadioPresetPicker() } config.lora.modem_preset = option.value; + config.lora.channel_num = 0; // Reset to default channel for the preset + config.lora.override_frequency = 0; // Clear any custom frequency service->reloadConfig(SEGMENT_CONFIG); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); }); @@ -2551,6 +2662,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display) case radio_preset_picker: RadioPresetPicker(); break; + case frequency_slot: + FrequencySlotPicker(); + break; case no_timeout_lora_picker: LoraRegionPicker(0); break; diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index 121b6dfc9..45fd0bf5f 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -13,6 +13,7 @@ class menuHandler lora_picker, device_role_picker, radio_preset_picker, + frequency_slot, no_timeout_lora_picker, TZ_picker, twelve_hour_picker, @@ -63,6 +64,7 @@ class menuHandler static void loraMenu(); static void DeviceRolePicker(); static void RadioPresetPicker(); + static void FrequencySlotPicker(); static void handleMenuSwitch(OLEDDisplay *display); static void showConfirmationBanner(const char *message, std::function onConfirm); static void clockMenu(); diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 09b798e06..72746e415 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -6,7 +6,6 @@ #include "MessageStore.h" #include "NodeDB.h" #include "UIRenderer.h" -#include "configuration.h" #include "gps/RTC.h" #include "graphics/Screen.h" #include "graphics/ScreenFonts.h" @@ -20,7 +19,6 @@ // External declarations extern bool hasUnreadMessage; -extern meshtastic_DeviceState devicestate; extern graphics::Screen *screen; using graphics::Emote; @@ -49,7 +47,7 @@ static inline size_t utf8CharLen(uint8_t c) } // Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels -std::string normalizeEmoji(const std::string &s) +static std::string normalizeEmoji(const std::string &s) { std::string out; for (size_t i = 0; i < s.size();) { @@ -82,6 +80,7 @@ uint32_t pauseStart = 0; bool waitingToReset = false; bool scrollStarted = false; static bool didReset = false; +static constexpr int MESSAGE_BLOCK_GAP = 6; void scrollUp() { @@ -111,22 +110,6 @@ void scrollDown() void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount) { - std::string renderLine; - for (size_t i = 0; i < line.size();) { - uint8_t c = (uint8_t)line[i]; - size_t len = utf8CharLen(c); - if (c == 0xEF && i + 2 < line.size() && (uint8_t)line[i + 1] == 0xB8 && (uint8_t)line[i + 2] == 0x8F) { - i += 3; - continue; - } - if (c == 0xF0 && i + 3 < line.size() && (uint8_t)line[i + 1] == 0x9F && (uint8_t)line[i + 2] == 0x8F && - ((uint8_t)line[i + 3] >= 0xBB && (uint8_t)line[i + 3] <= 0xBF)) { - i += 4; - continue; - } - renderLine.append(line, i, len); - i += len; - } int cursorX = x; const int fontHeight = FONT_HEIGHT_SMALL; @@ -203,8 +186,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string // Render the emote (if found) if (matchedEmote && i == nextEmotePos) { - // Vertically center emote relative to font baseline (not just midline) - int iconY = fontY + (fontHeight - matchedEmote->height) / 2; + int iconY = y + (lineHeight - matchedEmote->height) / 2; display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap); cursorX += matchedEmote->width + 1; i += emojiLen; @@ -423,6 +405,102 @@ static inline int getRenderedLineWidth(OLEDDisplay *display, const std::string & return totalWidth; } +struct MessageBlock { + size_t start; + size_t end; + bool mine; +}; + +static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool isHeaderLine) +{ + if (isHeaderLine) { + return lineTopY + (FONT_HEIGHT_SMALL - 1); + } + + int tallest = FONT_HEIGHT_SMALL; + for (int e = 0; e < numEmotes; ++e) { + if (line.find(emotes[e].label) != std::string::npos) { + if (emotes[e].height > tallest) + tallest = emotes[e].height; + } + } + + const int lineHeight = std::max(FONT_HEIGHT_SMALL, tallest); + const int iconTop = lineTopY + (lineHeight - tallest) / 2; + + return iconTop + tallest - 1; +} + +static void drawRoundedRectOutline(OLEDDisplay *display, int x, int y, int w, int h, int r) +{ + if (w <= 1 || h <= 1) + return; + + if (r < 0) + r = 0; + + int maxR = (std::min(w, h) / 2) - 1; + if (r > maxR) + r = maxR; + + if (r == 0) { + display->drawRect(x, y, w, h); + return; + } + + const int x0 = x; + const int y0 = y; + const int x1 = x + w - 1; + const int y1 = y + h - 1; + + // sides + if (x0 + r <= x1 - r) { + display->drawLine(x0 + r, y0, x1 - r, y0); // top + display->drawLine(x0 + r, y1, x1 - r, y1); // bottom + } + if (y0 + r <= y1 - r) { + display->drawLine(x0, y0 + r, x0, y1 - r); // left + display->drawLine(x1, y0 + r, x1, y1 - r); // right + } + + // corner arcs + display->drawCircleQuads(x0 + r, y0 + r, r, 2); // top left + display->drawCircleQuads(x1 - r, y0 + r, r, 1); // top right + display->drawCircleQuads(x1 - r, y1 - r, r, 8); // bottom right + display->drawCircleQuads(x0 + r, y1 - r, r, 4); // bottom left +} + +static std::vector buildMessageBlocks(const std::vector &isHeaderVec, const std::vector &isMineVec) +{ + std::vector blocks; + if (isHeaderVec.empty()) + return blocks; + + size_t start = 0; + bool mine = isMineVec[0]; + + for (size_t i = 1; i < isHeaderVec.size(); ++i) { + if (isHeaderVec[i]) { + MessageBlock b; + b.start = start; + b.end = i - 1; + b.mine = mine; + blocks.push_back(b); + + start = i; + mine = isMineVec[i]; + } + } + + MessageBlock last; + last.start = start; + last.end = isHeaderVec.size() - 1; + last.mine = mine; + blocks.push_back(last); + + return blocks; +} + static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY) { if (totalHeight <= visibleHeight) @@ -482,9 +560,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 constexpr int LEFT_MARGIN = 2; constexpr int RIGHT_MARGIN = 2; constexpr int SCROLLBAR_WIDTH = 3; + constexpr int BUBBLE_PAD_X = 3; + constexpr int BUBBLE_PAD_Y = 4; + constexpr int BUBBLE_RADIUS = 4; + constexpr int BUBBLE_MIN_W = 24; + constexpr int BUBBLE_TEXT_INDENT = 2; - const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN; - + // Derived widths + const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (BUBBLE_PAD_X * 2); const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH; // Title string depending on mode @@ -547,7 +630,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 char chanType[32] = ""; if (currentMode == ThreadMode::ALL) { if (m.dest == NODENUM_BROADCAST) { - snprintf(chanType, sizeof(chanType), "#%s", channels.getName(m.channelIndex)); + const char *name = channels.getName(m.channelIndex); + if (currentResolution == ScreenResolution::Low || currentResolution == ScreenResolution::UltraLow) { + if (strcmp(name, "ShortTurbo") == 0) + name = "ShortT"; + else if (strcmp(name, "ShortSlow") == 0) + name = "ShortS"; + else if (strcmp(name, "ShortFast") == 0) + name = "ShortF"; + else if (strcmp(name, "MediumSlow") == 0) + name = "MedS"; + else if (strcmp(name, "MediumFast") == 0) + name = "MedF"; + else if (strcmp(name, "LongSlow") == 0) + name = "LongS"; + else if (strcmp(name, "LongFast") == 0) + name = "LongF"; + else if (strcmp(name, "LongTurbo") == 0) + name = "LongT"; + else if (strcmp(name, "LongMod") == 0) + name = "LongM"; + } + snprintf(chanType, sizeof(chanType), "#%s", name); } else { snprintf(chanType, sizeof(chanType), "(DM)"); } @@ -614,8 +718,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 } // Shrink Sender name if needed - int availWidth = SCREEN_WIDTH - display->getStringWidth(timeBuf) - display->getStringWidth(chanType) - - display->getStringWidth(" @...") - 10; + int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) - + display->getStringWidth(chanType) - display->getStringWidth(" @..."); if (availWidth < 0) availWidth = 0; @@ -667,6 +771,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 cachedLines = allLines; cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader); + std::vector blocks = buildMessageBlocks(isHeader, isMine); + // Scrolling logic (unchanged) int totalHeight = 0; for (size_t i = 0; i < cachedHeights.size(); ++i) @@ -714,12 +820,123 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 int finalScroll = (int)scrollY; int yOffset = -finalScroll + getTextPositions(display)[1]; + const int contentTop = getTextPositions(display)[1]; + const int contentBottom = scrollBottom; // already excludes nav line + const int rightEdge = SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN; + const int bubbleGapY = std::max(1, MESSAGE_BLOCK_GAP / 2); + + std::vector lineTop; + lineTop.resize(cachedLines.size()); + { + int acc = 0; + for (size_t i = 0; i < cachedLines.size(); ++i) { + lineTop[i] = yOffset + acc; + acc += cachedHeights[i]; + } + } + + // Draw bubbles + for (size_t bi = 0; bi < blocks.size(); ++bi) { + const auto &b = blocks[bi]; + if (b.start >= cachedLines.size() || b.end >= cachedLines.size() || b.start > b.end) + continue; + + int visualTop = lineTop[b.start]; + + int topY; + if (isHeader[b.start]) { + // Header start + constexpr int BUBBLE_PAD_TOP_HEADER = 1; // try 1 or 2 + topY = visualTop - BUBBLE_PAD_TOP_HEADER; + } else { + // Body start + bool thisLineHasEmote = false; + for (int e = 0; e < numEmotes; ++e) { + if (cachedLines[b.start].find(emotes[e].label) != std::string::npos) { + thisLineHasEmote = true; + break; + } + } + if (thisLineHasEmote) { + constexpr int EMOTE_PADDING_ABOVE = 4; + visualTop -= EMOTE_PADDING_ABOVE; + } + topY = visualTop - BUBBLE_PAD_Y; + } + int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]); + int bottomY = visualBottom + BUBBLE_PAD_Y; + + if (bi + 1 < blocks.size()) { + int nextHeaderIndex = (int)blocks[bi + 1].start; + int nextTop = lineTop[nextHeaderIndex]; + int maxBottom = nextTop - 1 - bubbleGapY; + if (bottomY > maxBottom) + bottomY = maxBottom; + } + + if (bottomY <= topY + 2) + continue; + + if (bottomY < contentTop || topY > contentBottom - 1) + continue; + + int maxLineW = 0; + + for (size_t i = b.start; i <= b.end; ++i) { + int w = 0; + if (isHeader[i]) { + w = display->getStringWidth(cachedLines[i].c_str()); + if (b.mine) + w += 12; // room for ACK/NACK/relay mark + } else { + w = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); + } + if (w > maxLineW) + maxLineW = w; + } + + int bubbleW = std::max(BUBBLE_MIN_W, maxLineW + (BUBBLE_PAD_X * 2)); + int bubbleH = (bottomY - topY) + 1; + int bubbleX = 0; + if (b.mine) { + bubbleX = rightEdge - bubbleW; + } else { + bubbleX = x; + } + if (bubbleX < x) + bubbleX = x; + if (bubbleX + bubbleW > rightEdge) + bubbleW = std::max(1, rightEdge - bubbleX); + + if (bubbleW > 1 && bubbleH > 1) { + int r = BUBBLE_RADIUS; + int maxR = (std::min(bubbleW, bubbleH) / 2) - 1; + if (maxR < 0) + maxR = 0; + if (r > maxR) + r = maxR; + + drawRoundedRectOutline(display, bubbleX, topY, bubbleW, bubbleH, r); + const int extra = 3; + const int rr = r + extra; + int x1 = bubbleX + bubbleW - 1; + int y1 = topY + bubbleH - 1; + + if (!b.mine) { + // top-left corner square + display->drawLine(bubbleX, topY, bubbleX + rr, topY); + display->drawLine(bubbleX, topY, bubbleX, topY + rr); + } else { + // bottom-right corner square + display->drawLine(x1 - rr, y1, x1, y1); + display->drawLine(x1, y1 - rr, x1, y1); + } + } + } // Render visible lines + int lineY = yOffset; for (size_t i = 0; i < cachedLines.size(); ++i) { - int lineY = yOffset; - for (size_t j = 0; j < i; ++j) - lineY += cachedHeights[j]; if (lineY > -cachedHeights[i] && lineY < scrollBottom) { if (isHeader[i]) { @@ -728,14 +945,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 int headerX; if (isMine[i]) { // push header left to avoid overlap with scrollbar - headerX = SCREEN_WIDTH - w - SCROLLBAR_WIDTH - RIGHT_MARGIN; + headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - BUBBLE_TEXT_INDENT; if (headerX < LEFT_MARGIN) headerX = LEFT_MARGIN; } else { - headerX = x; + headerX = x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT; } display->drawString(headerX, lineY, cachedLines[i].c_str()); + // Draw underline just under header text + int underlineY = lineY + FONT_HEIGHT_SMALL; + + int underlineW = w; + int maxW = rightEdge - headerX; + if (maxW < 0) + maxW = 0; + if (underlineW > maxW) + underlineW = maxW; + + for (int px = 0; px < underlineW; ++px) { + display->setPixel(headerX + px, underlineY); + } + // Draw ACK/NACK mark for our own messages if (isMine[i]) { int markX = headerX - 10; @@ -753,32 +984,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // AckStatus::NONE → show nothing } - // Draw underline just under header text - int underlineY = lineY + FONT_HEIGHT_SMALL; - for (int px = 0; px < w; ++px) { - display->setPixel(headerX + px, underlineY); - } } else { // Render message line if (isMine[i]) { // Calculate actual rendered width including emotes int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); - int rightX = SCREEN_WIDTH - renderedWidth - SCROLLBAR_WIDTH - RIGHT_MARGIN; + int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - BUBBLE_TEXT_INDENT; if (rightX < LEFT_MARGIN) rightX = LEFT_MARGIN; drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes); } else { - drawStringWithEmotes(display, x, lineY, cachedLines[i], emotes, numEmotes); + drawStringWithEmotes(display, x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT, lineY, cachedLines[i], emotes, + numEmotes); } } } + + lineY += cachedHeights[i]; } - int totalContentHeight = totalHeight; - int visibleHeight = usableHeight; // Draw scrollbar - drawMessageScrollbar(display, visibleHeight, totalContentHeight, finalScroll, getTextPositions(display)[1]); + drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, getTextPositions(display)[1]); graphics::drawCommonHeader(display, x, y, titleStr); graphics::drawCommonFooter(display, x, y); } @@ -841,7 +1068,6 @@ std::vector calculateLineHeights(const std::vector &lines, con constexpr int HEADER_UNDERLINE_GAP = 0; // space between underline and first body line constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn) constexpr int BODY_LINE_LEADING = -4; // default vertical leading for normal body lines - constexpr int MESSAGE_BLOCK_GAP = 4; // gap after a message block before a new header constexpr int EMOTE_PADDING_ABOVE = 4; // space above emote line (added to line above) constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line) @@ -851,6 +1077,7 @@ std::vector calculateLineHeights(const std::vector &lines, con for (size_t idx = 0; idx < lines.size(); ++idx) { const auto &line = lines[idx]; const int baseHeight = FONT_HEIGHT_SMALL; + int lineHeight = baseHeight; // Detect if THIS line or NEXT line contains an emote bool hasEmote = false; @@ -872,8 +1099,6 @@ std::vector calculateLineHeights(const std::vector &lines, con } } - int lineHeight = baseHeight; - if (isHeaderVec[idx]) { // Header line spacing lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP; @@ -922,7 +1147,7 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht // Banner logic const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from); - char longName[48] = "???"; + char longName[48] = "?"; if (node && node->user.long_name) { strncpy(longName, node->user.long_name, sizeof(longName) - 1); longName[sizeof(longName) - 1] = '\0'; diff --git a/src/main.cpp b/src/main.cpp index 88282c837..ea114ea34 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" +#include "power/PowerHAL.h" #include "FSCommon.h" #include "Led.h" @@ -105,6 +106,43 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include #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 + #if HAS_BUTTON || defined(ARCH_PORTDUINO) #include "input/ButtonThread.h" @@ -295,6 +333,43 @@ __attribute__((weak, noinline)) bool loopCanSleep() void lateInitVariant() __attribute__((weak)); void lateInitVariant() {} +// 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() +{ + +#ifdef LED_PIN + pinMode(LED_PIN, OUTPUT); +#endif + + while (powerHAL_isPowerLevelSafe() == false) { + +#ifdef LED_PIN + + // 3x: blink for 300 ms, pause for 300 ms + + for (int i = 0; i < 3; i++) { + digitalWrite(LED_PIN, LED_STATE_ON); + delay(300); + digitalWrite(LED_PIN, LED_STATE_OFF); + delay(300); + } +#endif + + // sleep for 2s + delay(2000); + } +} + /** * Print info as a structured log message (for automated log processing) */ @@ -305,6 +380,14 @@ void printInfo() #ifndef PIO_UNIT_TESTING void setup() { + + // initialize power HAL layer as early as possible + powerHAL_init(); + + // prevent booting if device is in power failure mode + // boot sequence will follow when battery level raises to safe mode + waitUntilPowerLevelSafe(); + #if defined(R1_NEO) pinMode(DCDC_EN_HOLD, OUTPUT); digitalWrite(DCDC_EN_HOLD, HIGH); @@ -648,7 +731,11 @@ void setup() 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) { diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 9ca16878d..0f4d64113 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -61,11 +61,6 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey) return true; } #endif -void CryptoEngine::clearKeys() -{ - memset(public_key, 0, sizeof(public_key)); - memset(private_key, 0, sizeof(private_key)); -} /** * Encrypt a packet's payload using a key generated with Curve25519 and SHA256 diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 6bbcb3b8a..7689006ab 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -37,7 +37,6 @@ class CryptoEngine virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey); #endif - void clearKeys(); void setDHPrivateKey(uint8_t *_private_key); virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 375bc76e3..2a3294086 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #ifdef ARCH_ESP32 @@ -1418,6 +1419,14 @@ void NodeDB::loadFromDisk() bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, bool fullAtomic) { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveProto() on unsafe device power level."); + return false; + } + bool okay = false; #ifdef FSCom auto f = SafeFile(filename, fullAtomic); @@ -1444,6 +1453,14 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_ bool NodeDB::saveChannelsToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveChannelsToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1454,6 +1471,14 @@ bool NodeDB::saveChannelsToDisk() bool NodeDB::saveDeviceStateToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveDeviceStateToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1466,6 +1491,14 @@ bool NodeDB::saveDeviceStateToDisk() bool NodeDB::saveNodeDatabaseToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveNodeDatabaseToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1478,6 +1511,14 @@ bool NodeDB::saveNodeDatabaseToDisk() bool NodeDB::saveToDiskNoRetry(int saveWhat) { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveToDiskNoRetry() on unsafe device power level."); + return false; + } + bool success = true; #ifdef FSCom spiLock->lock(); @@ -1533,6 +1574,14 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) bool NodeDB::saveToDisk(int saveWhat) { LOG_DEBUG("Save to disk %d", saveWhat); + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveToDisk() on unsafe device power level."); + return false; + } + bool success = saveToDiskNoRetry(saveWhat); if (!success) { diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index cb2bc9753..8e3229f47 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -666,18 +666,24 @@ void RadioInterface::limitPower(int8_t loraMaxPower) power = maxPower; } -#ifndef NUM_PA_POINTS - if (TX_GAIN_LORA > 0 && !devicestate.owner.is_licensed) { - LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA); - power -= TX_GAIN_LORA; - } +#ifdef ARCH_PORTDUINO + size_t num_pa_points = portduino_config.num_pa_points; + const uint16_t *tx_gain = portduino_config.tx_gain_lora; #else - if (!devicestate.owner.is_licensed) { + size_t num_pa_points = NUM_PA_POINTS; + const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; +#endif + + if (num_pa_points == 1) { + if (tx_gain[0] > 0 && !devicestate.owner.is_licensed) { + LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[0]); + power -= tx_gain[0]; + } + } else if (!devicestate.owner.is_licensed) { // we have an array of PA gain values. Find the highest power setting that works. - const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; - for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) { + for (int radio_dbm = 0; radio_dbm < num_pa_points; radio_dbm++) { if (((radio_dbm + tx_gain[radio_dbm]) > power) || - ((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { + ((radio_dbm == (num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { // we've exceeded the power limit, or hit the max we can do LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]); power -= tx_gain[radio_dbm]; @@ -685,7 +691,7 @@ void RadioInterface::limitPower(int8_t loraMaxPower) } } } -#endif + if (power > loraMaxPower) // Clamp power to maximum defined level power = loraMaxPower; diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 26b4343e9..efdead91b 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -77,7 +77,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG = 11, /* TODO: REPLACE */ - meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12 + meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12, + /* TODO: REPLACE */ + meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13 } meshtastic_AdminMessage_ModuleConfigType; typedef enum _meshtastic_AdminMessage_BackupLocation { @@ -323,8 +325,8 @@ extern "C" { #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG+1)) +#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG +#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1)) #define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH #define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 409805d24..57e7df8fc 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2279 +#define meshtastic_BackupPreferences_size 2362 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 2b44d0c9a..f11b13419 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -87,6 +87,9 @@ typedef struct _meshtastic_LocalModuleConfig { /* Paxcounter Config */ bool has_paxcounter; meshtastic_ModuleConfig_PaxcounterConfig paxcounter; + /* StatusMessage Config */ + bool has_statusmessage; + meshtastic_ModuleConfig_StatusMessageConfig statusmessage; } meshtastic_LocalModuleConfig; @@ -96,9 +99,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} -#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default} +#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default} #define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} -#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero} +#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_LocalConfig_device_tag 1 @@ -124,6 +127,7 @@ extern "C" { #define meshtastic_LocalModuleConfig_ambient_lighting_tag 12 #define meshtastic_LocalModuleConfig_detection_sensor_tag 13 #define meshtastic_LocalModuleConfig_paxcounter_tag 14 +#define meshtastic_LocalModuleConfig_statusmessage_tag 15 /* Struct field encoding specification for nanopb */ #define meshtastic_LocalConfig_FIELDLIST(X, a) \ @@ -161,7 +165,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, remote_hardware, 10) \ X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \ X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \ -X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) +X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \ +X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) #define meshtastic_LocalModuleConfig_CALLBACK NULL #define meshtastic_LocalModuleConfig_DEFAULT NULL #define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -177,6 +182,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) #define meshtastic_LocalModuleConfig_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig +#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig extern const pb_msgdesc_t meshtastic_LocalConfig_msg; extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; @@ -186,9 +192,9 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; #define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size +#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 749 -#define meshtastic_LocalModuleConfig_size 675 +#define meshtastic_LocalModuleConfig_size 758 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index d8eee1203..7f1a738c6 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -30,6 +30,9 @@ PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2) PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO) +PB_BIND(meshtastic_StatusMessage, meshtastic_StatusMessage, AUTO) + + PB_BIND(meshtastic_MqttClientProxyMessage, meshtastic_MqttClientProxyMessage, 2) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 68552ede5..aeae4bd84 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -858,6 +858,11 @@ typedef struct _meshtastic_Waypoint { uint32_t icon; } meshtastic_Waypoint; +/* Message for node status */ +typedef struct _meshtastic_StatusMessage { + char status[80]; +} meshtastic_StatusMessage; + typedef PB_BYTES_ARRAY_T(435) meshtastic_MqttClientProxyMessage_data_t; /* This message will be proxied over the PhoneAPI for the client to deliver to the MQTT server */ typedef struct _meshtastic_MqttClientProxyMessage { @@ -1402,6 +1407,7 @@ extern "C" { + #define meshtastic_MeshPacket_priority_ENUMTYPE meshtastic_MeshPacket_Priority #define meshtastic_MeshPacket_delayed_ENUMTYPE meshtastic_MeshPacket_Delayed #define meshtastic_MeshPacket_transport_mechanism_ENUMTYPE meshtastic_MeshPacket_TransportMechanism @@ -1444,6 +1450,7 @@ extern "C" { #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} +#define meshtastic_StatusMessage_init_default {""} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} #define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0} @@ -1476,6 +1483,7 @@ extern "C" { #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} +#define meshtastic_StatusMessage_init_zero {""} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} #define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0} @@ -1571,6 +1579,7 @@ extern "C" { #define meshtastic_Waypoint_name_tag 6 #define meshtastic_Waypoint_description_tag 7 #define meshtastic_Waypoint_icon_tag 8 +#define meshtastic_StatusMessage_status_tag 1 #define meshtastic_MqttClientProxyMessage_topic_tag 1 #define meshtastic_MqttClientProxyMessage_data_tag 2 #define meshtastic_MqttClientProxyMessage_text_tag 3 @@ -1806,6 +1815,11 @@ X(a, STATIC, SINGULAR, FIXED32, icon, 8) #define meshtastic_Waypoint_CALLBACK NULL #define meshtastic_Waypoint_DEFAULT NULL +#define meshtastic_StatusMessage_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, status, 1) +#define meshtastic_StatusMessage_CALLBACK NULL +#define meshtastic_StatusMessage_DEFAULT NULL + #define meshtastic_MqttClientProxyMessage_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, topic, 1) \ X(a, STATIC, ONEOF, BYTES, (payload_variant,data,payload_variant.data), 2) \ @@ -2072,6 +2086,7 @@ extern const pb_msgdesc_t meshtastic_Data_msg; extern const pb_msgdesc_t meshtastic_KeyVerification_msg; extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg; extern const pb_msgdesc_t meshtastic_Waypoint_msg; +extern const pb_msgdesc_t meshtastic_StatusMessage_msg; extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg; extern const pb_msgdesc_t meshtastic_MeshPacket_msg; extern const pb_msgdesc_t meshtastic_NodeInfo_msg; @@ -2106,6 +2121,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg #define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg #define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg +#define meshtastic_StatusMessage_fields &meshtastic_StatusMessage_msg #define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg #define meshtastic_MeshPacket_fields &meshtastic_MeshPacket_msg #define meshtastic_NodeInfo_fields &meshtastic_NodeInfo_msg @@ -2161,6 +2177,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 256 #define meshtastic_Routing_size 259 +#define meshtastic_StatusMessage_size 81 #define meshtastic_StoreForwardPlusPlus_size 377 #define meshtastic_ToRadio_size 504 #define meshtastic_User_size 115 diff --git a/src/mesh/generated/meshtastic/module_config.pb.cpp b/src/mesh/generated/meshtastic/module_config.pb.cpp index f262df6a3..bb57c3f2d 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.cpp +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -51,6 +51,9 @@ PB_BIND(meshtastic_ModuleConfig_CannedMessageConfig, meshtastic_ModuleConfig_Can PB_BIND(meshtastic_ModuleConfig_AmbientLightingConfig, meshtastic_ModuleConfig_AmbientLightingConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_StatusMessageConfig, meshtastic_ModuleConfig_StatusMessageConfig, AUTO) + + PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index dd0151e3f..46a7164d2 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -409,6 +409,12 @@ typedef struct _meshtastic_ModuleConfig_AmbientLightingConfig { uint8_t blue; } meshtastic_ModuleConfig_AmbientLightingConfig; +/* StatusMessage config - Allows setting a status message for a node to periodically rebroadcast */ +typedef struct _meshtastic_ModuleConfig_StatusMessageConfig { + /* The actual status string */ + char node_status[80]; +} meshtastic_ModuleConfig_StatusMessageConfig; + /* A GPIO pin definition for remote hardware module */ typedef struct _meshtastic_RemoteHardwarePin { /* GPIO Pin number (must match Arduino) */ @@ -460,6 +466,8 @@ typedef struct _meshtastic_ModuleConfig { meshtastic_ModuleConfig_DetectionSensorConfig detection_sensor; /* TODO: REPLACE */ meshtastic_ModuleConfig_PaxcounterConfig paxcounter; + /* TODO: REPLACE */ + meshtastic_ModuleConfig_StatusMessageConfig statusmessage; } payload_variant; } meshtastic_ModuleConfig; @@ -515,6 +523,7 @@ extern "C" { #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_event_press_ENUMTYPE meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar + #define meshtastic_RemoteHardwarePin_type_ENUMTYPE meshtastic_RemoteHardwarePinType @@ -534,6 +543,7 @@ extern "C" { #define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_StatusMessageConfig_init_default {""} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} #define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}} #define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero} @@ -550,6 +560,7 @@ extern "C" { #define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_StatusMessageConfig_init_zero {""} #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} /* Field tags (for use in manual encoding/decoding) */ @@ -653,6 +664,7 @@ extern "C" { #define meshtastic_ModuleConfig_AmbientLightingConfig_red_tag 3 #define meshtastic_ModuleConfig_AmbientLightingConfig_green_tag 4 #define meshtastic_ModuleConfig_AmbientLightingConfig_blue_tag 5 +#define meshtastic_ModuleConfig_StatusMessageConfig_node_status_tag 1 #define meshtastic_RemoteHardwarePin_gpio_pin_tag 1 #define meshtastic_RemoteHardwarePin_name_tag 2 #define meshtastic_RemoteHardwarePin_type_tag 3 @@ -672,6 +684,7 @@ extern "C" { #define meshtastic_ModuleConfig_ambient_lighting_tag 11 #define meshtastic_ModuleConfig_detection_sensor_tag 12 #define meshtastic_ModuleConfig_paxcounter_tag 13 +#define meshtastic_ModuleConfig_statusmessage_tag 14 /* Struct field encoding specification for nanopb */ #define meshtastic_ModuleConfig_FIELDLIST(X, a) \ @@ -687,7 +700,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,remote_hardware,payload_vari X(a, STATIC, ONEOF, MESSAGE, (payload_variant,neighbor_info,payload_variant.neighbor_info), 10) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_variant.ambient_lighting), 11) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14) #define meshtastic_ModuleConfig_CALLBACK NULL #define meshtastic_ModuleConfig_DEFAULT NULL #define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -703,6 +717,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.p #define meshtastic_ModuleConfig_payload_variant_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_ModuleConfig_payload_variant_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig +#define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig #define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ @@ -865,6 +880,11 @@ X(a, STATIC, SINGULAR, UINT32, blue, 5) #define meshtastic_ModuleConfig_AmbientLightingConfig_CALLBACK NULL #define meshtastic_ModuleConfig_AmbientLightingConfig_DEFAULT NULL +#define meshtastic_ModuleConfig_StatusMessageConfig_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, node_status, 1) +#define meshtastic_ModuleConfig_StatusMessageConfig_CALLBACK NULL +#define meshtastic_ModuleConfig_StatusMessageConfig_DEFAULT NULL + #define meshtastic_RemoteHardwarePin_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, gpio_pin, 1) \ X(a, STATIC, SINGULAR, STRING, name, 2) \ @@ -887,6 +907,7 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_RangeTestConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_TelemetryConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_CannedMessageConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_AmbientLightingConfig_msg; +extern const pb_msgdesc_t meshtastic_ModuleConfig_StatusMessageConfig_msg; extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ @@ -905,6 +926,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_TelemetryConfig_fields &meshtastic_ModuleConfig_TelemetryConfig_msg #define meshtastic_ModuleConfig_CannedMessageConfig_fields &meshtastic_ModuleConfig_CannedMessageConfig_msg #define meshtastic_ModuleConfig_AmbientLightingConfig_fields &meshtastic_ModuleConfig_AmbientLightingConfig_msg +#define meshtastic_ModuleConfig_StatusMessageConfig_fields &meshtastic_ModuleConfig_StatusMessageConfig_msg #define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ @@ -921,6 +943,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_RangeTestConfig_size 12 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 +#define meshtastic_ModuleConfig_StatusMessageConfig_size 81 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24 #define meshtastic_ModuleConfig_TelemetryConfig_size 50 #define meshtastic_ModuleConfig_size 227 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 6b89c6a37..d31daa4b2 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -91,6 +91,11 @@ typedef enum _meshtastic_PortNum { This module is specifically for Native Linux nodes, and provides a Git-style chain of messages. */ meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP = 35, + /* Node Status module + ENCODING: protobuf + This module allows setting an extra string of status for a node. + Broadcasts on change and on a timer, possibly once a day. */ + meshtastic_PortNum_NODE_STATUS_APP = 36, /* Provides a hardware serial interface to send and receive from the Meshtastic network. Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network. diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index dec89ba15..131dd9949 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -361,6 +361,8 @@ typedef struct _meshtastic_LocalStats { uint32_t heap_free_bytes; /* Number of packets that were dropped because the transmit queue was full. */ uint16_t num_tx_dropped; + /* Noise floor value measured in dBm */ + int32_t noise_floor; } meshtastic_LocalStats; /* Health telemetry metrics */ @@ -458,7 +460,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0} #define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} @@ -467,7 +469,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0} #define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -556,6 +558,7 @@ extern "C" { #define meshtastic_LocalStats_heap_total_bytes_tag 12 #define meshtastic_LocalStats_heap_free_bytes_tag 13 #define meshtastic_LocalStats_num_tx_dropped_tag 14 +#define meshtastic_LocalStats_noise_floor_tag 15 #define meshtastic_HealthMetrics_heart_bpm_tag 1 #define meshtastic_HealthMetrics_spO2_tag 2 #define meshtastic_HealthMetrics_temperature_tag 3 @@ -678,7 +681,8 @@ X(a, STATIC, SINGULAR, UINT32, num_tx_relay, 10) \ X(a, STATIC, SINGULAR, UINT32, num_tx_relay_canceled, 11) \ X(a, STATIC, SINGULAR, UINT32, heap_total_bytes, 12) \ X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13) \ -X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14) +X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14) \ +X(a, STATIC, SINGULAR, INT32, noise_floor, 15) #define meshtastic_LocalStats_CALLBACK NULL #define meshtastic_LocalStats_DEFAULT NULL @@ -755,7 +759,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_EnvironmentMetrics_size 113 #define meshtastic_HealthMetrics_size 11 #define meshtastic_HostMetrics_size 264 -#define meshtastic_LocalStats_size 76 +#define meshtastic_LocalStats_size 87 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 81 #define meshtastic_Telemetry_size 272 diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 7b7ebb595..ea8d6af8e 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -173,7 +173,7 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) if (req->getMethod() == "OPTIONS") { res->setStatusCode(204); // Success with no content - // res->print(""); @todo remove + res->print(""); return; } @@ -223,7 +223,7 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res) if (req->getMethod() == "OPTIONS") { res->setStatusCode(204); // Success with no content - // res->print(""); @todo remove + res->print(""); return; } diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 990ca0f46..1fda9bf13 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -235,22 +235,46 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_ota_request_tag: { #if defined(ARCH_ESP32) + LOG_INFO("OTA Requested"); + if (r->ota_request.ota_hash.size != 32) { suppressRebootBanner = true; - LOG_INFO("OTA Failed: Invalid `ota_hash` provided"); + sendWarningAndLog("Cannot start OTA: Invalid `ota_hash` provided."); break; } meshtastic_OTAMode mode = r->ota_request.reboot_ota_mode; + const char *mode_name = (mode == METHOD_OTA_BLE ? "BLE" : "WiFi"); + + // Check that we have an OTA partition + const esp_partition_t *part = MeshtasticOTA::getAppPartition(); + if (part == NULL) { + suppressRebootBanner = true; + sendWarningAndLog("Cannot start OTA: Cannot find OTA Loader partition."); + break; + } + + static esp_app_desc_t app_desc; + if (!MeshtasticOTA::getAppDesc(part, &app_desc)) { + suppressRebootBanner = true; + sendWarningAndLog("Cannot start OTA: Device does have a valid OTA Loader."); + break; + } + + if (!MeshtasticOTA::checkOTACapability(&app_desc, mode)) { + suppressRebootBanner = true; + sendWarningAndLog("OTA Loader does not support %s", mode_name); + break; + } + if (MeshtasticOTA::trySwitchToOTA()) { - LOG_INFO("OTA Requested"); suppressRebootBanner = true; if (screen) screen->startFirmwareUpdateScreen(); MeshtasticOTA::saveConfig(&config.network, mode, r->ota_request.ota_hash.bytes); - LOG_INFO("Rebooting to WiFi OTA"); + sendWarningAndLog("Rebooting to %s OTA", mode_name); } else { - LOG_INFO("WIFI OTA Failed"); + sendWarningAndLog("Unable to switch to the OTA partition."); } #endif int s = 1; // Reboot in 1 second, hard coded @@ -1472,15 +1496,43 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent #endif } -void AdminModule::sendWarning(const char *message) +void AdminModule::sendWarning(const char *format, ...) { meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + if (!cn) + return; + cn->level = meshtastic_LogRecord_Level_WARNING; cn->time = getValidTime(RTCQualityFromNet); - strncpy(cn->message, message, sizeof(cn->message)); + + va_list args; + va_start(args, format); + // Format the arguments directly into the notification object + vsnprintf(cn->message, sizeof(cn->message), format, args); + va_end(args); + service->sendClientNotification(cn); } +void AdminModule::sendWarningAndLog(const char *format, ...) +{ + // We need a temporary buffer to hold the formatted text so we can log it + // Using 250 bytes as a safe upper limit for typical text notifications + char buf[250]; + + va_list args; + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + LOG_WARN(buf); + // 2. Call sendWarning + // SECURITY NOTE: We pass "%s", buf instead of just 'buf'. + // If 'buf' contained a % symbol (e.g. "Battery 50%"), passing it directly + // would crash sendWarning. "%s" treats it purely as text. + sendWarning("%s", buf); +} + void disableBluetooth() { #if HAS_BLUETOOTH diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 867751f49..c446887b3 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,7 +1,9 @@ -#include - #pragma once +#ifdef ESP_PLATFORM +#include +#endif #include "ProtobufModule.h" +#include #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif @@ -71,7 +73,8 @@ class AdminModule : public ProtobufModule, public Obser bool messageIsResponse(const meshtastic_AdminMessage *r); bool messageIsRequest(const meshtastic_AdminMessage *r); - void sendWarning(const char *message); + void sendWarning(const char *format, ...) __attribute__((format(printf, 2, 3))); + void sendWarningAndLog(const char *format, ...) __attribute__((format(printf, 2, 3))); }; static constexpr const char *licensedModeMessage = diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 04fcd8e73..8b7ce700a 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -460,12 +460,15 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); - bool mutedNode = false; - if (sender) { - mutedNode = (sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK); - } meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex()); + // If we receive a broadcast message, apply channel mute setting + // If we receive a direct message and the receipent is us, apply DM mute setting + // Else we just handle it as not muted. + const bool directToUs = !isBroadcast(mp.to) && isToUs(&mp); + bool is_muted = directToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0)) + : (ch.settings.has_module_settings && ch.settings.module_settings.is_muted); + if (moduleConfig.external_notification.alert_bell) { if (containsBell) { LOG_INFO("externalNotificationModule - Notification Bell"); @@ -516,8 +519,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - if (moduleConfig.external_notification.alert_message && !mutedNode && - (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { + if (moduleConfig.external_notification.alert_message && !is_muted) { LOG_INFO("externalNotificationModule - Notification Module"); isNagging = true; setExternalState(0, true); @@ -528,8 +530,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - if (moduleConfig.external_notification.alert_message_vibra && !mutedNode && - (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { + if (moduleConfig.external_notification.alert_message_vibra && !is_muted) { LOG_INFO("externalNotificationModule - Notification Module (Vibra)"); isNagging = true; setExternalState(1, true); @@ -540,8 +541,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - if (moduleConfig.external_notification.alert_message_buzzer && !mutedNode && - (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { + if (moduleConfig.external_notification.alert_message_buzzer && !is_muted) { LOG_INFO("externalNotificationModule - Notification Module (Buzzer)"); if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || (!isBroadcast(mp.to) && isToUs(&mp))) { diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 7db8b66cc..a568505ae 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -2,6 +2,7 @@ #include "Default.h" #include "MeshService.h" #include "NodeDB.h" +#include "NodeStatus.h" #include "RTC.h" #include "Router.h" #include "configuration.h" @@ -129,14 +130,17 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() LOG_DEBUG("Skip send NodeInfo > 40%% ch. util"); return NULL; } - // If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway. - if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 5 * 60 * 1000)) { - LOG_DEBUG("Skip send NodeInfo since we sent it <5min ago"); + + // Use graduated scaling based on active mesh size (10 minute base, scales with congestion coefficient) + uint32_t timeoutMs = Default::getConfiguredOrDefaultMsScaled(0, 10 * 60, nodeStatus->getNumOnline()); + if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, timeoutMs)) { + LOG_DEBUG("Skip send NodeInfo since we sent it <%us ago", timeoutMs / 1000); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; } else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) { + // For interactive/urgent requests (e.g., user-triggered or implicit requests), use a shorter 60s timeout LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago"); - ignoreRequest = true; // Mark it as ignored for MeshModule + ignoreRequest = true; return NULL; } else { ignoreRequest = false; // Don't ignore requests anymore diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 33aa58127..fed035513 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -50,9 +50,10 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) break; } case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: { - ble_state = connected; - PAIRING_LED_starttime = millis(); - break; + if (ble_state != connected) { + ble_state = connected; + PAIRING_LED_starttime = millis(); + } } } diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index ec6fe4799..86a8606c2 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -53,7 +53,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #include "Sensor/LTR390UVSensor.h" #endif -#if __has_include(MESHTASTIC_BME680_HEADER) +#if __has_include() || __has_include() #include "Sensor/BME680Sensor.h" #endif @@ -187,7 +187,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::LTR390UV); #endif -#if __has_include(MESHTASTIC_BME680_HEADER) +#if __has_include() || __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::BME_680); #endif #if __has_include() diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 22330ca75..3a1eb9532 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -1,6 +1,6 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER) +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include() || __has_include()) #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "BME680Sensor.h" @@ -10,7 +10,7 @@ BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {} -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() int32_t BME680Sensor::runOnce() { if (!bme680.run()) { @@ -18,13 +18,13 @@ int32_t BME680Sensor::runOnce() } return 35; } -#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) +#endif bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { status = 0; -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() if (!bme680.begin(dev->address.address, *bus)) checkStatus("begin"); @@ -56,7 +56,7 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) status = 1; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif initI2CSensor(); return status; @@ -64,7 +64,7 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) { -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0) return false; @@ -98,11 +98,11 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F; measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif return true; } -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() void BME680Sensor::loadState() { #ifdef FSCom @@ -179,6 +179,6 @@ void BME680Sensor::checkStatus(const char *functionName) else if (bme680.sensor.status > BME68X_OK) LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status); } -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif #endif diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index 9bef56e1e..eaeceb848 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -1,29 +1,29 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER) +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include() || __has_include()) #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() #include #include #else #include #include -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif #define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis() -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() const uint8_t bsec_config[] = { #include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt" }; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif class BME680Sensor : public TelemetrySensor { private: -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() Bsec2 bme680; #else using BME680Ptr = std::unique_ptr; @@ -31,10 +31,10 @@ class BME680Sensor : public TelemetrySensor static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique(bus); } BME680Ptr bme680; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif protected: -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() const char *bsecConfigFileName = "/prefs/bsec.dat"; uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0}; uint8_t accuracy = 0; @@ -51,13 +51,13 @@ class BME680Sensor : public TelemetrySensor void loadState(); void updateState(); void checkStatus(const char *functionName); -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif public: BME680Sensor(); -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() virtual int32_t runOnce() override; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp index ff0628cc3..626cc0e87 100644 --- a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp @@ -4,6 +4,15 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "RAK12035Sensor.h" +// The RAK12035 library's sensor_sleep() sets WB_IO2 (GPIO 34) LOW, which controls +// the 3.3V switched power rail (PIN_3V3_EN). This turns off power to ALL peripherals +// including GPS. We need to restore power after the library turns it off. +#ifdef PIN_3V3_EN +#define RESTORE_3V3_POWER() digitalWrite(PIN_3V3_EN, HIGH) +#else +#define RESTORE_3V3_POWER() +#endif + RAK12035Sensor::RAK12035Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RAK12035, "RAK12035") {} bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) @@ -13,7 +22,6 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) delay(100); sensor.begin(dev->address.address); - // Get sensor firmware version uint8_t data = 0; sensor.get_sensor_version(&data); if (data != 0) { @@ -21,8 +29,8 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) LOG_INFO("RAK12035Sensor Init Succeed \nSensor1 Firmware version: %i, Sensor Name: %s", data, sensorName); status = true; sensor.sensor_sleep(); + RESTORE_3V3_POWER(); } else { - // If we reach here, it means the sensor did not initialize correctly. LOG_INFO("Init sensor: %s", sensorName); LOG_ERROR("RAK12035Sensor Init Failed"); status = false; @@ -38,8 +46,6 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) void RAK12035Sensor::setup() { - // Set the calibration values - // Reading the saved calibration values from the sensor. // TODO:: Check for and run calibration check for up to 2 additional sensors if present. uint16_t zero_val = 0; uint16_t hundred_val = 0; @@ -71,6 +77,7 @@ void RAK12035Sensor::setup() LOG_INFO("Wet calibration reset complete. New value is %d", hundred_val); } sensor.sensor_sleep(); + RESTORE_3V3_POWER(); delay(200); LOG_INFO("Dry calibration value is %d", zero_val); LOG_INFO("Wet calibration value is %d", hundred_val); @@ -79,10 +86,6 @@ void RAK12035Sensor::setup() bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement) { // TODO:: read and send metrics for up to 2 additional soil monitors if present. - // -- how to do this.. this could get a little complex.. - // ie - 1> we combine them into an average and send that, 2> we send them as separate metrics - // ^-- these scenarios would require different handling of the metrics in the receiving end and maybe a setting in the - // device ui and an additional proto for that? measurement->variant.environment_metrics.has_soil_temperature = true; measurement->variant.environment_metrics.has_soil_moisture = true; @@ -97,6 +100,7 @@ bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement) success &= sensor.get_sensor_temperature(&temp); delay(200); sensor.sensor_sleep(); + RESTORE_3V3_POWER(); if (success == false) { LOG_ERROR("Failed to read sensor data"); diff --git a/src/platform/esp32/MeshtasticOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp index b8cb052ef..4ca074723 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -1,13 +1,17 @@ #include "MeshtasticOTA.h" #include "configuration.h" +#ifdef ESP_PLATFORM #include #include +#endif namespace MeshtasticOTA { static const char *nvsNamespace = "MeshtasticOTA"; -static const char *appProjectName = "MeshtasticOTA"; +static const char *combinedAppProjectName = "MeshtasticOTA"; +static const char *bleOnlyAppProjectName = "MeshtasticOTA-BLE"; +static const char *wifiOnlyAppProjectName = "MeshtasticOTA-WiFi"; static bool updated = false; @@ -68,21 +72,44 @@ bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc) LOG_INFO("esp_ota_get_partition_description failed"); return false; } - if (strcmp(app_desc->project_name, appProjectName) != 0) { - LOG_INFO("app_desc->project_name == 0"); - return false; - } return true; } +bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method) +{ + // Combined loader supports all (both) transports, BLE and WiFi + if (strcmp(app_desc->project_name, combinedAppProjectName) == 0) { + LOG_INFO("OTA partition contains combined BLE/WiFi OTA Loader"); + return true; + } + if (method == METHOD_OTA_BLE && strcmp(app_desc->project_name, bleOnlyAppProjectName) == 0) { + LOG_INFO("OTA partition contains BLE-only OTA Loader"); + return true; + } + if (method == METHOD_OTA_WIFI && strcmp(app_desc->project_name, wifiOnlyAppProjectName) == 0) { + LOG_INFO("OTA partition contains WiFi-only OTA Loader"); + return true; + } + LOG_INFO("OTA partition does not contain a known OTA loader"); + return false; +} + bool trySwitchToOTA() { const esp_partition_t *part = getAppPartition(); - esp_app_desc_t app_desc; - if (!getAppDesc(part, &app_desc)) + + if (part == NULL) { + LOG_WARN("Unable to get app partition in preparation of OTA reboot"); return false; - if (esp_ota_set_boot_partition(part) != ESP_OK) + } + + uint8_t result = esp_ota_set_boot_partition(part); + // Partition and app checks should now be done in the AdminModule before this is called + if (result != ESP_OK) { + LOG_WARN("Unable to switch to OTA partiton. (Reason %d)", result); return false; + } + return true; } diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h index 001eba039..7c158775f 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -3,12 +3,20 @@ #include "mesh-pb-constants.h" #include +#ifdef ESP_PLATFORM +#include +#endif + +#define METHOD_OTA_BLE 1 +#define METHOD_OTA_WIFI 2 namespace MeshtasticOTA { void initialize(); bool isUpdated(); - +const esp_partition_t *getAppPartition(); +bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc); +bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method); void recoverConfig(meshtastic_Config_NetworkConfig *network); void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash); bool trySwitchToOTA(); diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 4f7fb4776..6a552f236 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -119,7 +119,7 @@ void startAdv(void) Bluefruit.Advertising.addService(meshBleService); /* Start Advertising * - Enable auto advertising if disconnected - * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Interval: fast mode = 20 ms, slow mode = 417,5 ms * - Timeout for fast mode is 30 seconds * - Start(timeout) with timeout = 0 will advertise forever (until connected) * @@ -127,7 +127,7 @@ void startAdv(void) * https://developer.apple.com/library/content/qa/qa1931/_index.html */ Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X } @@ -240,6 +240,12 @@ int NRF52Bluetooth::getRssi() { return 0; // FIXME figure out where to source this } + +// Valid BLE TX power levels as per nRF52840 Product Specification are: "-20 to +8 dBm TX power, configurable in 4 dB steps". +// See https://docs.nordicsemi.com/bundle/ps_nrf52840/page/keyfeatures_html5.html +#define VALID_BLE_TX_POWER(x) \ + ((x) == -20 || (x) == -16 || (x) == -12 || (x) == -8 || (x) == -4 || (x) == 0 || (x) == 4 || (x) == 8) + void NRF52Bluetooth::setup() { // Initialise the Bluefruit module @@ -251,6 +257,9 @@ void NRF52Bluetooth::setup() Bluefruit.Advertising.stop(); Bluefruit.Advertising.clearData(); Bluefruit.ScanResponse.clearData(); +#if defined(NRF52_BLE_TX_POWER) && VALID_BLE_TX_POWER(NRF52_BLE_TX_POWER) + Bluefruit.setTxPower(NRF52_BLE_TX_POWER); +#endif if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN ? config.bluetooth.fixed_pin @@ -272,6 +281,29 @@ void NRF52Bluetooth::setup() // Set the connect/disconnect callback handlers Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); + + // Do not change Slave Latency to value other than 0 !!! + // There is probably a bug in SoftDevice + certain Apple iOS versions being + // brain damaged causing connectivity problems. + + // On one side it seems SoftDevice is using SlaveLatency value even + // if connection parameter negotation failed and phone sees it as connectivity errors. + + // On the other hand Apple can randomly refuse any parameter negotiation and shutdown connection + // even if you meet Apple Developer Guidelines for BLE devices. Because f* you, that's why. + + // While this API call sets preferred connection parameters (PPCP) - many phones ignore it (yeah) and it seems SoftDevice + // will try to renegotiate connection parameters based on those values after phone connection. + // So those are relatively safe values so Apple braindead firmware won't get angry and at least we may try + // to negotiate some longer connection interval to save battery. + + // See https://github.com/meshtastic/firmware/pull/8858 for measurements. We are dealing with microamp savings anyway so not + // worth dying on a hill here. + + Bluefruit.Periph.setConnSlaveLatency(0); + // 1.25 ms units - so min, max is 15, 100 ms range. + Bluefruit.Periph.setConnInterval(12, 80); + #ifndef BLE_DFU_SECURE bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bledfu.begin(); // Install the DFU helper @@ -300,7 +332,7 @@ void NRF52Bluetooth::setup() void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); } diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 7734c0020..d1965f03e 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -5,6 +5,25 @@ // // defaults for NRF52 architecture // + +/* + * Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4, + * 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels. + * + * External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning + * VDD/4, VDD/2 or VDD for the ADC levels. + * + * Default settings are internal reference with 1/6 gain (GND..3.6V ADC range) + * Some variants overwrite it. + */ +#ifndef AREF_VOLTAGE +#define AREF_VOLTAGE 3.6 +#endif + +#ifndef BATTERY_SENSE_RESOLUTION_BITS +#define BATTERY_SENSE_RESOLUTION_BITS 10 +#endif + #ifndef HAS_BLUETOOTH #define HAS_BLUETOOTH 1 #endif diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 472107229..2068fe2a7 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -9,12 +9,12 @@ #define NRFX_WDT_ENABLED 1 #define NRFX_WDT0_ENABLED 1 #define NRFX_WDT_CONFIG_NO_IRQ 1 -#include -#include - +#include "nrfx_power.h" #include #include #include +#include +#include #include // #include #include "NodeDB.h" @@ -23,6 +23,7 @@ #include "main.h" #include "meshUtils.h" #include "power.h" +#include #include @@ -30,6 +31,21 @@ #include "BQ25713.h" #endif +// WARNING! THRESHOLD + HYSTERESIS should be less than regulated VDD voltage - which depends on board +// and is 3.0 or 3.3V. Also VDD likes to read values like 2.9999 so make sure you account for that +// otherwise board will not boot at all. Before you modify this part - please triple read NRF52840 power design +// section in datasheet and you understand how REG0 and REG1 regulators work together. +#ifndef SAFE_VDD_VOLTAGE_THRESHOLD +#define SAFE_VDD_VOLTAGE_THRESHOLD 2.7 +#endif + +// hysteresis value +#ifndef SAFE_VDD_VOLTAGE_THRESHOLD_HYST +#define SAFE_VDD_VOLTAGE_THRESHOLD_HYST 0.2 +#endif + +uint16_t getVDDVoltage(); + // Weak empty variant initialization function. // May be redefined by variant files. void variant_shutdown() __attribute__((weak)); @@ -38,12 +54,95 @@ void variant_shutdown() {} static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0); static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main; +// This is a public global so that the debugger can set it to false automatically from our gdbinit +// @phaseloop comment: most part of codebase, including filesystem flash driver depend on softdevice +// methods so disabling it may actually crash thing. Proceed with caution. + +bool useSoftDevice = true; // Set to false for easier debugging + static inline void debugger_break(void) { __asm volatile("bkpt #0x01\n\t" "mov pc, lr\n\t"); } +// PowerHAL NRF52 specific function implementations +bool powerHAL_isVBUSConnected() +{ + return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk; +} + +bool powerHAL_isPowerLevelSafe() +{ + + static bool powerLevelSafe = true; + + uint16_t threshold = SAFE_VDD_VOLTAGE_THRESHOLD * 1000; // convert V to mV + uint16_t hysteresis = SAFE_VDD_VOLTAGE_THRESHOLD_HYST * 1000; + + if (powerLevelSafe) { + if (getVDDVoltage() < threshold) { + powerLevelSafe = false; + } + } else { + // power level is only safe again when it raises above threshold + hysteresis + if (getVDDVoltage() >= (threshold + hysteresis)) { + powerLevelSafe = true; + } + } + + return powerLevelSafe; +} + +void powerHAL_platformInit() +{ + + // Enable POF power failure comparator. It will prevent writing to NVMC flash when supply voltage is too low. + // Set to some low value as last resort - powerHAL_isPowerLevelSafe uses different method and should manage proper node + // behaviour on its own. + + // POFWARN is pretty useless for node power management because it triggers only once and clearing this event will not + // re-trigger it again until voltage rises to safe level and drops again. So we will use SAADC routed to VDD to read safely + // voltage. + + // @phaseloop: I disable POFCON for now because it seems to be unreliable or buggy. Even when set at 2.0V it + // triggers below 2.8V and corrupts data when pairing bluetooth - because it prevents filesystem writes and + // adafruit BLE library triggers lfs_assert which reboots node and formats filesystem. + // I did experiments with bench power supply and no matter what is set to POFCON, it always triggers right below + // 2.8V. I compared raw registry values with datasheet. + + NRF_POWER->POFCON = + ((POWER_POFCON_THRESHOLD_V22 << POWER_POFCON_THRESHOLD_Pos) | (POWER_POFCON_POF_Enabled << POWER_POFCON_POF_Pos)); + + // remember to always match VBAT_AR_INTERNAL with AREF_VALUE in variant definition file +#ifdef VBAT_AR_INTERNAL + analogReference(VBAT_AR_INTERNAL); +#else + analogReference(AR_INTERNAL); // 3.6V +#endif +} + +// get VDD voltage (in millivolts) +uint16_t getVDDVoltage() +{ + // we use the same values as regular battery read so there is no conflict on SAADC + analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); + + // VDD range on NRF52840 is 1.8-3.3V so we need to remap analog reference to 3.6V + // let's hope battery reading runs in same task and we don't have race condition + analogReference(AR_INTERNAL); + + uint16_t vddADCRead = analogReadVDD(); + float voltage = ((1000 * 3.6) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * vddADCRead; + +// restore default battery reading reference +#ifdef VBAT_AR_INTERNAL + analogReference(VBAT_AR_INTERNAL); +#endif + + return voltage; +} + bool loopCanSleep() { // turn off sleep only while connected via USB @@ -72,22 +171,6 @@ void getMacAddr(uint8_t *dmac) dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack } -static void initBrownout() -{ - auto vccthresh = POWER_POFCON_THRESHOLD_V24; - - auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled); - assert(err_code == NRF_SUCCESS); - - err_code = sd_power_pof_threshold_set(vccthresh); - assert(err_code == NRF_SUCCESS); - - // We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice -} - -// This is a public global so that the debugger can set it to false automatically from our gdbinit -bool useSoftDevice = true; // Set to false for easier debugging - #if !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { @@ -106,7 +189,6 @@ void setBluetoothEnable(bool enable) if (!initialized) { nrf52Bluetooth = new NRF52Bluetooth(); nrf52Bluetooth->startDisabled(); - initBrownout(); initialized = true; } return; @@ -120,9 +202,6 @@ void setBluetoothEnable(bool enable) LOG_DEBUG("Init NRF52 Bluetooth"); nrf52Bluetooth = new NRF52Bluetooth(); nrf52Bluetooth->setup(); - - // We delay brownout init until after BLE because BLE starts soft device - initBrownout(); } // Already setup, apparently else @@ -192,9 +271,24 @@ extern "C" void lfs_assert(const char *reason) delay(500); // Give the serial port a bit of time to output that last message. // Try setting GPREGRET with the SoftDevice first. If that fails (perhaps because the SD hasn't been initialize yet) then set // NRF_POWER->GPREGRET directly. - if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) { - NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT; + + // TODO: this will/can crash CPU if bluetooth stack is not compiled in or bluetooth is not initialized + // (regardless if enabled or disabled) - as there is no live SoftDevice stack + // implement "safe" functions detecting softdevice stack state and using proper method to set registers + + // do not set GPREGRET if POFWARN is triggered because it means lfs_assert reports flash undervoltage protection + // and not data corruption. Reboot is fine as boot procedure will wait until power level is safe again + + if (!NRF_POWER->EVENTS_POFWARN) { + if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && + sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) { + NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT; + } } + + // TODO: this should not be done when SoftDevice is enabled as device will not boot back on soft reset + // as some data is retained in RAM which will prevent re-enabling bluetooth stack + // Google what Nordic has to say about NVIC_* + SoftDevice NVIC_SystemReset(); } diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index de4f54c4a..0a48e626d 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -55,15 +55,18 @@ void cpuDeepSleep(uint32_t msecs) void updateBatteryLevel(uint8_t level) NOT_IMPLEMENTED("updateBatteryLevel"); int TCPPort = SERVER_API_DEFAULT_PORT; +bool checkConfigPort = true; static error_t parse_opt(int key, char *arg, struct argp_state *state) { switch (key) { case 'p': - if (sscanf(arg, "%d", &TCPPort) < 1) + if (sscanf(arg, "%d", &TCPPort) < 1) { return ARGP_ERR_UNKNOWN; - else + } else { + checkConfigPort = false; printf("Using config file %d\n", TCPPort); + } break; case 'c': configPath = arg; @@ -648,6 +651,19 @@ bool loadConfig(const char *configPath) if (yamlConfig["Lora"]["RF95_MAX_POWER"]) portduino_config.rf95_max_power = yamlConfig["Lora"]["RF95_MAX_POWER"].as(20); + if (yamlConfig["Lora"]["TX_GAIN_LORA"]) { + YAML::Node tx_gain_node = yamlConfig["Lora"]["TX_GAIN_LORA"]; + if (tx_gain_node.IsSequence() && tx_gain_node.size() != 0) { + portduino_config.num_pa_points = min(tx_gain_node.size(), std::size(portduino_config.tx_gain_lora)); + for (int i = 0; i < portduino_config.num_pa_points; i++) { + portduino_config.tx_gain_lora[i] = tx_gain_node[i].as(); + } + } else { + portduino_config.num_pa_points = 1; + portduino_config.tx_gain_lora[0] = tx_gain_node.as(0); + } + } + if (portduino_config.lora_module != use_autoconf && portduino_config.lora_module != use_simradio && !portduino_config.force_simradio) { portduino_config.dio2_as_rf_switch = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as(false); @@ -893,6 +909,12 @@ bool loadConfig(const char *configPath) std::cout << "Cannot set both MACAddress and MACAddressSource!" << std::endl; exit(EXIT_FAILURE); } + if (checkConfigPort) { + portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as(-1); + if (portduino_config.api_port != -1 && portduino_config.api_port > 1023 && portduino_config.api_port < 65536) { + TCPPort = (portduino_config.api_port); + } + } portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as(""); if (portduino_config.mac_address != "") { portduino_config.mac_address_explicit = true; diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index b5b22a1f6..efda0eb8a 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -91,6 +91,8 @@ extern struct portduino_config_struct { int lora_usb_pid = 0x5512; int lora_usb_vid = 0x1A86; int spiSpeed = 2000000; + int num_pa_points = 1; // default to 1 point, with 0 gain + uint16_t tx_gain_lora[22] = {0}; pinMapping lora_cs_pin = {"Lora", "CS"}; pinMapping lora_irq_pin = {"Lora", "IRQ"}; pinMapping lora_busy_pin = {"Lora", "Busy"}; @@ -195,6 +197,7 @@ extern struct portduino_config_struct { std::string mac_address = ""; bool mac_address_explicit = false; std::string mac_address_source = ""; + int api_port = -1; std::string config_directory = ""; std::string available_directory = "/etc/meshtasticd/available.d/"; int maxtophone = 100; @@ -250,6 +253,17 @@ extern struct portduino_config_struct { out << YAML::Key << "LR1120_MAX_POWER" << YAML::Value << lr1120_max_power; if (rf95_max_power != 20) out << YAML::Key << "RF95_MAX_POWER" << YAML::Value << rf95_max_power; + + if (num_pa_points > 1) { + out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << YAML::Flow << YAML::BeginSeq; + for (int i = 0; i < num_pa_points; i++) { + out << YAML::Value << tx_gain_lora[i]; + } + out << YAML::EndSeq; + } else if (tx_gain_lora[0] != 0) { + out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << tx_gain_lora[0]; + } + out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch; if (dio3_tcxo_voltage != 0) out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << YAML::Precision(3) << (float)dio3_tcxo_voltage / 1000; @@ -551,6 +565,8 @@ extern struct portduino_config_struct { out << YAML::Key << "General" << YAML::Value << YAML::BeginMap; if (config_directory != "") out << YAML::Key << "ConfigDirectory" << YAML::Value << config_directory; + if (api_port != -1) + out << YAML::Key << "TCPPort" << YAML::Value << api_port; if (mac_address_explicit) out << YAML::Key << "MACAddress" << YAML::Value << mac_address; if (mac_address_source != "") @@ -562,4 +578,4 @@ extern struct portduino_config_struct { out << YAML::EndMap; // General return out.c_str(); } -} portduino_config; \ No newline at end of file +} portduino_config; diff --git a/src/power/PowerHAL.cpp b/src/power/PowerHAL.cpp new file mode 100644 index 000000000..0a8d5f10b --- /dev/null +++ b/src/power/PowerHAL.cpp @@ -0,0 +1,19 @@ + +#include "PowerHAL.h" + +void powerHAL_init() +{ + return powerHAL_platformInit(); +} + +__attribute__((weak, noinline)) void powerHAL_platformInit() {} + +__attribute__((weak, noinline)) bool powerHAL_isPowerLevelSafe() +{ + return true; +} + +__attribute__((weak, noinline)) bool powerHAL_isVBUSConnected() +{ + return false; +} diff --git a/src/power/PowerHAL.h b/src/power/PowerHAL.h new file mode 100644 index 000000000..318b06810 --- /dev/null +++ b/src/power/PowerHAL.h @@ -0,0 +1,26 @@ + +/* + +Power Hardware Abstraction Layer. Set of API calls to offload power management, measurements, reboots, etc +to the platform and variant code to avoid #ifdef spaghetti hell and limitless device-based edge cases +in the main firmware code + +Functions declared here (with exception of powerHAL_init) should be defined in platform specific codebase. +Default function body does usually nothing. + +*/ + +// Initialize HAL layer. Call it as early as possible during device boot +// do not overwrite it as it's not declared with "weak" attribute. +void powerHAL_init(); + +// platform specific init code if needed to be run early on boot +void powerHAL_platformInit(); + +// Return true if current battery level is safe for device operation (for example flash writes). +// This should be reported by power failure comparator (NRF52) or similar circuits on other platforms. +// Do not use battery ADC as improper ADC configuration may prevent device from booting. +bool powerHAL_isPowerLevelSafe(); + +// return if USB voltage is connected +bool powerHAL_isVBUSConnected(); diff --git a/variants/esp32/chatter2/platformio.ini b/variants/esp32/chatter2/platformio.ini index 94a846bc9..4218e8503 100644 --- a/variants/esp32/chatter2/platformio.ini +++ b/variants/esp32/chatter2/platformio.ini @@ -6,6 +6,8 @@ build_flags = ${esp32_base.build_flags} -D CHATTER_2 -I variants/esp32/chatter2 + -DMESHTASTIC_EXCLUDE_WEBSERVER=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 lib_deps = ${esp32_base.lib_deps} diff --git a/variants/esp32/esp32.ini b/variants/esp32/esp32.ini index 5999bc098..502d937b0 100644 --- a/variants/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -7,3 +7,22 @@ custom_esp32_kind = esp32 build_flags = ${esp32_common.build_flags} -DMESHTASTIC_EXCLUDE_AUDIO=1 +; Override lib_deps to use environmental_extra_no_bsec instead of environmental_extra +; BSEC library uses ~3.5KB DRAM which causes overflow on original ESP32 targets +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${networking_extra.lib_deps} + ${environmental_base.lib_deps} + ${environmental_extra_no_bsec.lib_deps} + ${radiolib_base.lib_deps} + # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master + https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip + # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino + h2zero/NimBLE-Arduino@^1.4.3 + # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master + https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip + # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib + https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip + # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto + rweather/Crypto@0.4.0 \ No newline at end of file diff --git a/variants/esp32/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini index d6c5c5b4c..4544d73b5 100644 --- a/variants/esp32/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -16,6 +16,8 @@ build_flags = ${esp32_base.build_flags} -I variants/esp32/m5stack_core -DM5STACK + -DMESHTASTIC_EXCLUDE_WEBSERVER=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DUSER_SETUP_LOADED -DTFT_SDA_READ -DTFT_DRIVER=0x9341 diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index 3054d342a..ed26598d2 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -10,6 +10,8 @@ custom_meshtastic_tags = M5Stack extends = esp32c6_base board = esp32-c6-devkitc-1 +board_upload.flash_size = 16MB +board_build.partitions = default_16MB.csv ;OpenOCD flash method ;upload_protocol = esp-builtin ;Normal method diff --git a/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h b/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h index 1448b1d74..9bb3af45a 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h +++ b/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h @@ -8,7 +8,8 @@ // DIO6 -> RFSW1_V2 // DIO7 -> not connected on E80 module - note that GNSS and Wifi scanning are not possible. -static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, RADIOLIB_NC}; +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, + RADIOLIB_NC}; static const Module::RfSwitchMode_t rfswitch_table[] = { // mode DIO5 DIO6 DIO7 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp index 9c7b521ef..4ce8ecdf0 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp @@ -67,4 +67,8 @@ void variant_shutdown() nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense1); + + nrf_gpio_cfg_input(EXT_CHRG_DETECT, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input + nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(EXT_CHRG_DETECT, sense2); } diff --git a/variants/nrf52840/dls_Minimesh_Lite/platformio.ini b/variants/nrf52840/dls_Minimesh_Lite/platformio.ini new file mode 100644 index 000000000..763ff5477 --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/platformio.ini @@ -0,0 +1,9 @@ +[env:minimesh_lite] +extends = nrf52840_base +board = minimesh_lite +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/dls_Minimesh_Lite + -DPRIVATE_HW +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/dls_Minimesh_Lite> +debug_tool = jlink diff --git a/variants/nrf52840/dls_Minimesh_Lite/variant.cpp b/variants/nrf52840/dls_Minimesh_Lite/variant.cpp new file mode 100644 index 000000000..5869ed1d4 --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/variant.cpp @@ -0,0 +1,38 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/nrf52840/dls_Minimesh_Lite/variant.h b/variants/nrf52840/dls_Minimesh_Lite/variant.h new file mode 100644 index 000000000..f5163619b --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/variant.h @@ -0,0 +1,104 @@ +#ifndef _VARIANT_MINIMESH_LITE_ +#define _VARIANT_MINIMESH_LITE_ + +#define VARIANT_MCK (64000000ul) +#define USE_LFRC + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MINIMESH_LITE + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +#define PIN_3V3_EN (0 + 13) // P0.13 + +// Analog pins +#define BATTERY_PIN (0 + 31) // P0.31 Battery ADC +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#define VBAT_MV_PER_LSB (0.73242188F) +#define VBAT_DIVIDER (0.6F) +#define VBAT_DIVIDER_COMP (1.73) +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER VBAT_DIVIDER_COMP +#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) + +// WIRE IC AND IIC PINS +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (32 + 4) +#define PIN_WIRE_SCL (0 + 11) + +// LED +#define PIN_LED1 (0 + 15) +#define LED_BUILTIN PIN_LED1 +// Actually red +#define LED_BLUE PIN_LED1 +#define LED_STATE_ON 1 + +// Button +#define BUTTON_PIN (32 + 0) + +// GPS +#define GPS_TX_PIN (0 + 20) +#define GPS_RX_PIN (0 + 22) + +#define PIN_GPS_EN (0 + 24) +#define GPS_UBLOX +// define GPS_DEBUG + +// UART interfaces +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN + +#define PIN_SERIAL2_RX (0 + 6) +#define PIN_SERIAL2_TX (0 + 8) + +// Serial interfaces +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (0 + 2) +#define PIN_SPI_MOSI (32 + 15) +#define PIN_SPI_SCK (32 + 11) + +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_SCK PIN_SPI_SCK +#define LORA_CS (32 + 13) + +// LORA MODULES +#define USE_LLCC68 +#define USE_SX1262 +#define USE_SX1268 + +// SX126X CONFIG +#define SX126X_CS (32 + 13) +#define SX126X_DIO1 (0 + 10) +#define SX126X_DIO2_AS_RF_SWITCH + +#define SX126X_BUSY (0 + 29) +#define SX126X_RESET (0 + 9) +#define SX126X_RXEN (0 + 17) +#define SX126X_TXEN RADIOLIB_NC + +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL + +#ifdef __cplusplus +} +#endif + +#endif // _VARIANT_MINIMESH_LITE_ diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index bad488b35..fb7f61ac7 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -150,6 +150,14 @@ No longer populated on PCB #define PIN_SPI1_MOSI ST7789_SDA #define PIN_SPI1_SCK ST7789_SCK +/* + * Bluetooth + */ + +// The bluetooth transmit power on the nRF52840 is adjustable from -20dB to +8dB in steps of 4dB +// so NRF52_BLE_TX_POWER can be set to -20, -16, -12, -8, -4, 0 (default), 4, and 8. +//#define NRF52_BLE_TX_POWER 8 + /* * GPS pins */ diff --git a/variants/stm32/russell/platformio.ini b/variants/stm32/russell/platformio.ini new file mode 100644 index 000000000..0dd57a2c7 --- /dev/null +++ b/variants/stm32/russell/platformio.ini @@ -0,0 +1,21 @@ +; Russell is a board designed to mount on an ER34615/IFR32700 cell and go Up! on a balloon +; Hardware repository: https://github.com/Meshtastic-Malaysia/russell +; - RAK3172 STM32WLE5CCU6 MCU + integrated SX1262 LoRa +; - CDtop CD-PA1010D GPS +; - Bosch Sensortec BME280 sensor +; - Consonance CN3158 LiFePO4 solar charger +[env:russell] +extends = stm32_base +board = wiscore_rak3172 +board_level = extra +board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem +build_flags = + ${stm32_base.build_flags} + -Ivariants/stm32/russell + -DPRIVATE_HW +lib_deps = + ${stm32_base.lib_deps} + # renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library + adafruit/Adafruit BME280 Library@2.3.0 + +upload_port = stlink diff --git a/variants/stm32/russell/rfswitch.h b/variants/stm32/russell/rfswitch.h new file mode 100644 index 000000000..ec4829de6 --- /dev/null +++ b/variants/stm32/russell/rfswitch.h @@ -0,0 +1,7 @@ +// Pins from https://forum.rakwireless.com/t/rak3172-internal-schematic/4557/2 +// PB8, PC13 + +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PB8, PC13, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[4] = { + {STM32WLx::MODE_IDLE, {LOW, LOW}}, {STM32WLx::MODE_RX, {HIGH, LOW}}, {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, END_OF_MODE_TABLE}; diff --git a/variants/stm32/russell/variant.h b/variants/stm32/russell/variant.h new file mode 100644 index 000000000..796302d34 --- /dev/null +++ b/variants/stm32/russell/variant.h @@ -0,0 +1,41 @@ +#ifndef _VARIANT_RUSSELL_ +#define _VARIANT_RUSSELL_ + +#define USE_STM32WLx + +// I/O +#define LED_PIN PA0 // Red LED +#define LED_STATE_ON 1 +#define BUTTON_PIN PH3 // Shared with BOOT0 +#define BUTTON_NEED_PULLUP +// Charger IC charge/standby pins are open-drain with no hardware pull-up: +// Internal pull-up is needed on STM32 (TODO) +// #define EXT_CHRG_DETECT PA5 +// #define EXT_PWR_DETECT PA4 + +// Bosch Sensortec BME280 +#define HAS_SENSOR 1 + +// CDtop CD-PA1010D +#define ENABLE_HWSERIAL1 +#define PIN_SERIAL1_RX PB7 +#define PIN_SERIAL1_TX PB6 +#define HAS_GPS 1 +#define PIN_GPS_STANDBY PA15 +#define GPS_RX_PIN PB7 +#define GPS_TX_PIN PB6 + +// LoRa +/* + * RAK3172 (-20–85°C) -> No TCXO + * RAK3172-T (-40–85°C) -> 3.0V TCXO + * https://github.com/RAKWireless/RAK-STM32-RUI/blob/e5a28be8fab1a492bd9223dd425ca33a8a297d90/variants/WisDuo_RAK3172-T_Board/radio_conf.h#L91 + */ +#define TCXO_OPTIONAL +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 + +// Required to avoid Serial1 conflicts due to board definition here: +// https://github.com/stm32duino/Arduino_Core_STM32/blob/main/variants/STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U/variant_RAK3172_MODULE.h +#define RAK3172 + +#endif diff --git a/version.properties b/version.properties index 0a028eff0..62145da14 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 18 +build = 19