From 318ae98534046713a66f9a336a40f70323533ab0 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 23 Dec 2025 14:46:55 -0600 Subject: [PATCH] Battery handling and LED for M4 --- src/Power.cpp | 134 ++++++++++++++++++ src/modules/StatusLEDModule.cpp | 42 +++++- src/modules/StatusLEDModule.h | 6 + src/power.h | 2 + .../nrf52840/ELECROW-ThinkNode-M4/variant.cpp | 11 ++ .../nrf52840/ELECROW-ThinkNode-M4/variant.h | 14 +- 6 files changed, 202 insertions(+), 7 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index a2c559d91..4ddf811b5 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -692,6 +692,8 @@ bool Power::setup() found = true; } else if (lipoChargerInit()) { found = true; + } else if (serialBatteryInit()) { + found = true; } else if (meshSolarInit()) { found = true; } else if (analogInit()) { @@ -1566,3 +1568,135 @@ bool Power::meshSolarInit() return false; } #endif + +#ifdef HAS_SERIAL_BATTERY_LEVEL +#include + +/** + * SerialBatteryLevel class for pulling battery information from a secondary MCU over serial. + */ +class SerialBatteryLevel : public HasBatteryLevel +{ + + public: + /** + * Init the I2C meshSolar battery level sensor + */ + bool runOnce() + { + BatterySerial.begin(4800); + + return true; + } + + /** + * Battery state of charge, from 0 to 100 or -1 for unknown + */ + virtual int getBatteryPercent() override { return v_percent; } + + /** + * The raw voltage of the battery in millivolts, or NAN if unknown + */ + virtual uint16_t getBattVoltage() override { return voltage * 1000; } + + /** + * return true if there is a battery installed in this unit + */ + virtual bool isBatteryConnect() override + { + // definitely need to gobble up more bytes at once + if (BatterySerial.available() > 5) { + LOG_WARN("SerialBatteryLevel: %u bytes available", BatterySerial.available()); + while (BatterySerial.available() > 11) { + BatterySerial.read(); // flush old data + } + LOG_WARN("SerialBatteryLevel: %u bytes now available", BatterySerial.available()); + int tries = 0; + while (BatterySerial.read() != 0xFE) { + tries++; // wait for start byte + if (tries > 10) { + LOG_WARN("SerialBatteryLevel: no start byte found"); + return 1; + } + } + + Data[1] = BatterySerial.read(); + Data[2] = BatterySerial.read(); + Data[3] = BatterySerial.read(); + Data[4] = BatterySerial.read(); + Data[5] = BatterySerial.read(); + if (Data[5] != 0xFD) { + LOG_WARN("SerialBatteryLevel: invalid end byte %02x", Data[5]); + return true; + } + v_percent = Data[1]; + voltage = Data[2] + (((float)Data[3]) / 100) + (((float)Data[4]) / 10000); + voltage *= 2; + LOG_WARN("SerialBatteryLevel: received data %u, %f, %02x", v_percent, voltage, Data[5]); + return true; + } + // This function runs first, so use it to grab the latest data from the secondary MCU + return true; + } + + /** + * return true if there is an external power source detected + */ + virtual bool isVbusIn() override + { +#if defined(EXT_CHRG_DETECT) + + return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value; + +#endif + return false; + } + + virtual bool isCharging() override + { +#ifdef EXT_CHRG_DETECT + return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value; + +#endif + // by default, we check the battery voltage only + return isVbusIn(); + } + + private: + SoftwareSerial BatterySerial = SoftwareSerial(SERIAL_BATTERY_RX, SERIAL_BATTERY_TX); + uint8_t Data[6] = {0}; + int v_percent = 0; + float voltage = 0.0; +}; + +SerialBatteryLevel serialBatteryLevel; + +/** + * Init the serial battery level sensor + */ +bool Power::serialBatteryInit() +{ +#ifdef EXT_PWR_DETECT + pinMode(EXT_PWR_DETECT, INPUT); +#endif +#ifdef EXT_CHRG_DETECT + pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode); +#endif + + bool result = serialBatteryLevel.runOnce(); + LOG_DEBUG("Power::serialBatteryInit serial battery sensor is %s", result ? "ready" : "not ready yet"); + if (!result) + return false; + batteryLevel = &serialBatteryLevel; + return true; +} + +#else +/** + * If this device has no serial battery level sensor, don't try to use it. + */ +bool Power::serialBatteryInit() +{ + return false; +} +#endif diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index fc9ed310e..59639161a 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -13,16 +13,18 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule") { bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus); powerStatusObserver.observe(&powerStatus->onNewStatus); + if (inputBroker) + inputObserver.observe(inputBroker); } int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) { switch (arg->getStatusType()) { case STATUS_TYPE_POWER: { - meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)arg; - if (powerStatus->getHasUSB()) { + meshtastic::PowerStatus *_powerStatus = (meshtastic::PowerStatus *)arg; + if (_powerStatus->getHasUSB()) { power_state = charging; - if (powerStatus->getBatteryChargePercent() >= 100) { + if (_powerStatus->getBatteryChargePercent() >= 100) { power_state = charged; } } else { @@ -56,6 +58,12 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) return 0; }; +int StatusLEDModule::handleInputEvent(const InputEvent *event) +{ + lastUserbuttonTime = millis(); + return 0; +} + int32_t StatusLEDModule::runOnce() { @@ -82,6 +90,21 @@ int32_t StatusLEDModule::runOnce() PAIRING_LED_state = LED_STATE_ON; } + bool chargeIndicatorLED1 = LED_STATE_OFF; + bool chargeIndicatorLED2 = LED_STATE_OFF; + bool chargeIndicatorLED3 = LED_STATE_OFF; + bool chargeIndicatorLED4 = LED_STATE_OFF; + if (lastUserbuttonTime + 10 * 1000 > millis()) { + // should this be off at very low percentages? + chargeIndicatorLED1 = LED_STATE_ON; + if (powerStatus && powerStatus->getBatteryChargePercent() >= 25) + chargeIndicatorLED2 = LED_STATE_ON; + if (powerStatus && powerStatus->getBatteryChargePercent() >= 50) + chargeIndicatorLED3 = LED_STATE_ON; + if (powerStatus && powerStatus->getBatteryChargePercent() >= 75) + chargeIndicatorLED4 = LED_STATE_ON; + } + #ifdef LED_CHARGE digitalWrite(LED_CHARGE, CHARGE_LED_state); #endif @@ -90,5 +113,18 @@ int32_t StatusLEDModule::runOnce() digitalWrite(LED_PAIRING, PAIRING_LED_state); #endif +#ifdef Battery_LED_1 + digitalWrite(Battery_LED_1, chargeIndicatorLED1); +#endif +#ifdef Battery_LED_2 + digitalWrite(Battery_LED_2, chargeIndicatorLED2); +#endif +#ifdef Battery_LED_3 + digitalWrite(Battery_LED_3, chargeIndicatorLED3); +#endif +#ifdef Battery_LED_4 + digitalWrite(Battery_LED_4, chargeIndicatorLED4); +#endif + return (my_interval); } diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h index d9e3a4f33..0b9774a4e 100644 --- a/src/modules/StatusLEDModule.h +++ b/src/modules/StatusLEDModule.h @@ -5,6 +5,7 @@ #include "PowerStatus.h" #include "concurrency/OSThread.h" #include "configuration.h" +#include "input/InputBroker.h" #include #include @@ -17,6 +18,8 @@ class StatusLEDModule : private concurrency::OSThread int handleStatusUpdate(const meshtastic::Status *); + int handleInputEvent(const InputEvent *arg); + protected: unsigned int my_interval = 1000; // interval in millisconds virtual int32_t runOnce() override; @@ -25,12 +28,15 @@ class StatusLEDModule : private concurrency::OSThread CallbackObserver(this, &StatusLEDModule::handleStatusUpdate); CallbackObserver powerStatusObserver = CallbackObserver(this, &StatusLEDModule::handleStatusUpdate); + CallbackObserver inputObserver = + CallbackObserver(this, &StatusLEDModule::handleInputEvent); private: bool CHARGE_LED_state = LED_STATE_OFF; bool PAIRING_LED_state = LED_STATE_OFF; uint32_t PAIRING_LED_starttime = 0; + uint32_t lastUserbuttonTime = 0; enum PowerState { discharging, charging, charged }; diff --git a/src/power.h b/src/power.h index 3f28dedb2..4e64a2914 100644 --- a/src/power.h +++ b/src/power.h @@ -132,6 +132,8 @@ class Power : private concurrency::OSThread bool lipoChargerInit(); /// Setup a meshSolar battery sensor bool meshSolarInit(); + /// Setup a serial battery sensor + bool serialBatteryInit(); private: void shutdown(); diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp index 871634eb8..db08b99a1 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp @@ -37,4 +37,15 @@ void initVariant() pinMode(LED_PAIRING, OUTPUT); ledOff(LED_PAIRING); + + pinMode(Battery_LED_1, OUTPUT); + ledOff(Battery_LED_1); + pinMode(Battery_LED_2, OUTPUT); + ledOff(Battery_LED_2); + + pinMode(Battery_LED_3, OUTPUT); + ledOff(Battery_LED_3); + + pinMode(Battery_LED_4, OUTPUT); + ledOff(Battery_LED_4); } diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h index 1823f4c88..b62f55488 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h @@ -45,10 +45,12 @@ extern "C" { #define LED_CHARGE (32 + 9) #define LED_PAIRING (13) -#define LED_STATE_ON 1 +#define Battery_LED_1 (15) +#define Battery_LED_2 (17) +#define Battery_LED_3 (32 + 2) +#define Battery_LED_4 (32 + 4) -// USB / power detection -#define EXT_PWR_DETECT (32 + 3) // P1.03 USB present sense +#define LED_STATE_ON 1 // Button #define PIN_BUTTON1 (4) @@ -65,6 +67,10 @@ extern "C" { #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define HAS_SERIAL_BATTERY_LEVEL 1 +#define SERIAL_BATTERY_RX 30 +#define SERIAL_BATTERY_TX 5 + static const uint8_t A0 = PIN_A0; #define PIN_NFC1 (9) @@ -80,7 +86,7 @@ static const uint8_t A0 = PIN_A0; // charger status #define EXT_CHRG_DETECT (32 + 6) -#define EXT_CHRG_DETECT_VALUE LOW +#define EXT_CHRG_DETECT_VALUE HIGH // SPI #define SPI_INTERFACES_COUNT 1