diff --git a/components/CommandManager/CommandManager/commands/device_commands.cpp b/components/CommandManager/CommandManager/commands/device_commands.cpp index ccc80f4..5dbee0d 100644 --- a/components/CommandManager/CommandManager/commands/device_commands.cpp +++ b/components/CommandManager/CommandManager/commands/device_commands.cpp @@ -1,6 +1,4 @@ #include "device_commands.hpp" -#include -#include #include "LEDManager.hpp" #include "MonitoringManager.hpp" #include "esp_mac.h" @@ -230,53 +228,15 @@ CommandResult getBatteryStatusCommand(std::shared_ptr regist return CommandResult::getErrorResult("MonitoringManager unavailable"); } - const float volts = mon->getBatteryVoltageMilliVolts(); - if (volts <= 0.0f) + const auto status = mon->getBatteryStatus(); + if (!status.valid) { return CommandResult::getErrorResult("Battery voltage unavailable"); } - struct VoltageSOC - { - float voltage_mv; - float soc; - }; - - constexpr std::array lookup = { - VoltageSOC{4200.0f, 100.0f}, VoltageSOC{4060.0f, 90.0f}, VoltageSOC{3980.0f, 80.0f}, VoltageSOC{3920.0f, 70.0f}, - VoltageSOC{3870.0f, 60.0f}, VoltageSOC{3820.0f, 50.0f}, VoltageSOC{3790.0f, 40.0f}, VoltageSOC{3770.0f, 30.0f}, - VoltageSOC{3740.0f, 20.0f}, VoltageSOC{3680.0f, 10.0f}, VoltageSOC{3450.0f, 5.0f}, VoltageSOC{3300.0f, 0.0f}, - }; - - float percent = 0.0f; - if (volts >= lookup.front().voltage_mv) - { - percent = lookup.front().soc; - } - else if (volts <= lookup.back().voltage_mv) - { - percent = lookup.back().soc; - } - else - { - for (size_t index = 0; index < lookup.size() - 1; ++index) - { - const auto high = lookup[index]; - const auto low = lookup[index + 1]; - if (volts <= high.voltage_mv && volts >= low.voltage_mv) - { - const float span = high.voltage_mv - low.voltage_mv; - const float ratio = (volts - low.voltage_mv) / (span > 0.0f ? span : 1.0f); - percent = low.soc + ratio * (high.soc - low.soc); - break; - } - } - } - percent = std::clamp(percent, 0.0f, 100.0f); - const auto json = nlohmann::json{ - {"voltage_mv", std::format("{:.2f}", static_cast(volts))}, - {"percentage", std::format("{:.1f}", static_cast(percent))}, + {"voltage_mv", std::format("{:.2f}", static_cast(status.voltage_mv))}, + {"percentage", std::format("{:.1f}", static_cast(status.percentage))}, }; return CommandResult::getSuccessResult(json); #else diff --git a/components/Monitoring/Monitoring/BatteryMonitor.cpp b/components/Monitoring/Monitoring/BatteryMonitor.cpp index 9b08031..97d2f95 100644 --- a/components/Monitoring/Monitoring/BatteryMonitor.cpp +++ b/components/Monitoring/Monitoring/BatteryMonitor.cpp @@ -71,3 +71,52 @@ int BatteryMonitor::getBatteryMilliVolts() const return 0; #endif } + +float BatteryMonitor::voltageToPercentage(int voltage_mv) +{ + const float volts = static_cast(voltage_mv); + + // Handle boundary conditions + if (volts >= soc_lookup_.front().voltage_mv) + return soc_lookup_.front().soc; + + if (volts <= soc_lookup_.back().voltage_mv) + return soc_lookup_.back().soc; + + // Linear interpolation between lookup table points + for (size_t i = 0; i < soc_lookup_.size() - 1; ++i) + { + const auto &high = soc_lookup_[i]; + const auto &low = soc_lookup_[i + 1]; + + if (volts <= high.voltage_mv && volts >= low.voltage_mv) + { + const float voltage_span = high.voltage_mv - low.voltage_mv; + if (voltage_span <= 0.0f) + { + return low.soc; + } + const float ratio = (volts - low.voltage_mv) / voltage_span; + return low.soc + ratio * (high.soc - low.soc); + } + } + + return 0.0f; +} + +BatteryStatus BatteryMonitor::getBatteryStatus() const +{ + BatteryStatus status = {0, 0.0f, false}; + +#if CONFIG_MONITORING_BATTERY_ENABLE + const int mv = getBatteryMilliVolts(); + if (mv <= 0) + return status; + + status.voltage_mv = mv; + status.percentage = std::clamp(voltageToPercentage(mv), 0.0f, 100.0f); + status.valid = true; +#endif + + return status; +} diff --git a/components/Monitoring/Monitoring/BatteryMonitor.hpp b/components/Monitoring/Monitoring/BatteryMonitor.hpp index 1f70c82..defdf63 100644 --- a/components/Monitoring/Monitoring/BatteryMonitor.hpp +++ b/components/Monitoring/Monitoring/BatteryMonitor.hpp @@ -14,15 +14,31 @@ * +-----------------------+ */ +#include +#include +#include #include "AdcSampler.hpp" #include "sdkconfig.h" -#include + + +/** + * @struct BatteryStatus + * @brief Battery status information + */ +struct BatteryStatus +{ + int voltage_mv; // Battery voltage in millivolts + float percentage; // State of charge percentage (0-100%) + bool valid; // Whether the reading is valid +}; /** * @class BatteryMonitor - * @brief Monitors battery voltage through a resistor divider + * @brief Monitors battery voltage and calculates state of charge for Li-ion batteries * * Uses AdcSampler (BSP layer) for hardware abstraction. + * Includes voltage-to-SOC lookup table for typical Li-ion/Li-Po batteries. + * * Configuration is done via Kconfig options: * - CONFIG_MONITORING_BATTERY_ENABLE * - CONFIG_MONITORING_BATTERY_ADC_GPIO @@ -39,10 +55,29 @@ public: // Initialize battery monitoring hardware bool setup(); - // Read once, update filter, and return battery voltage in mV (after divider compensation), 0 on failure + /** + * @brief Read battery voltage (with divider compensation) + * @return Battery voltage in millivolts, 0 on failure + */ int getBatteryMilliVolts() const; - // Whether monitoring is enabled by Kconfig and supported by BSP + /** + * @brief Calculate battery state of charge from voltage + * @param voltage_mv Battery voltage in millivolts + * @return State of charge percentage (0-100%) + */ + static float voltageToPercentage(int voltage_mv); + + /** + * @brief Get complete battery status (voltage + percentage) + * @return BatteryStatus struct with voltage, percentage, and validity + */ + BatteryStatus getBatteryStatus() const; + + /** + * @brief Check if battery monitoring is enabled and supported + * @return true if enabled and ADC is supported + */ static constexpr bool isEnabled() { #ifdef CONFIG_MONITORING_BATTERY_ENABLE @@ -53,6 +88,34 @@ public: } private: - float scale_{1.0f}; // Voltage divider scaling factor + /** + * @brief Li-ion/Li-Po voltage to SOC lookup table entry + */ + struct VoltageSOC + { + float voltage_mv; + float soc; + }; + + /** + * @brief Typical Li-ion single cell discharge curve lookup table + * Based on typical 3.7V nominal Li-ion/Li-Po cell characteristics + */ + static constexpr std::array soc_lookup_ = {{ + {4200.0f, 100.0f}, // Fully charged + {4060.0f, 90.0f}, + {3980.0f, 80.0f}, + {3920.0f, 70.0f}, + {3870.0f, 60.0f}, + {3820.0f, 50.0f}, + {3790.0f, 40.0f}, + {3770.0f, 30.0f}, + {3740.0f, 20.0f}, + {3680.0f, 10.0f}, + {3450.0f, 5.0f}, // Low battery warning + {3300.0f, 0.0f}, // Empty / cutoff voltage + }}; + + float scale_{1.0f}; // Voltage divider scaling factor mutable AdcSampler adc_; // ADC sampler instance (BSP layer) }; diff --git a/components/Monitoring/Monitoring/MonitoringManager.cpp b/components/Monitoring/Monitoring/MonitoringManager.cpp index d5d29d8..1f265a3 100644 --- a/components/Monitoring/Monitoring/MonitoringManager.cpp +++ b/components/Monitoring/Monitoring/MonitoringManager.cpp @@ -127,10 +127,11 @@ void MonitoringManager::run() #if CONFIG_MONITORING_BATTERY_ENABLE if (BatteryMonitor::isEnabled() && now_tick >= next_tick_bat) { - const int mv = bm_.getBatteryMilliVolts(); - if (mv > 0) + const auto status = bm_.getBatteryStatus(); + if (status.valid) { - last_battery_mv_.store(mv); + std::lock_guard lock(battery_mutex_); + last_battery_status_ = status; } next_tick_bat = now_tick + batt_period; } @@ -161,11 +162,14 @@ float MonitoringManager::getCurrentMilliAmps() const return 0.0f; } -float MonitoringManager::getBatteryVoltageMilliVolts() const +BatteryStatus MonitoringManager::getBatteryStatus() const { #if CONFIG_MONITORING_BATTERY_ENABLE if (BatteryMonitor::isEnabled()) - return static_cast(last_battery_mv_.load()); + { + std::lock_guard lock(battery_mutex_); + return last_battery_status_; + } #endif - return 0.0f; + return {0, 0.0f, false}; } diff --git a/components/Monitoring/Monitoring/MonitoringManager.hpp b/components/Monitoring/Monitoring/MonitoringManager.hpp index 2db44b8..36069e7 100644 --- a/components/Monitoring/Monitoring/MonitoringManager.hpp +++ b/components/Monitoring/Monitoring/MonitoringManager.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "BatteryMonitor.hpp" #include "CurrentMonitor.hpp" @@ -47,8 +48,8 @@ public: // Latest filtered current in mA float getCurrentMilliAmps() const; - // Latest battery voltage in mV - float getBatteryVoltageMilliVolts() const; + // Get complete battery status (voltage + percentage + validity) + BatteryStatus getBatteryStatus() const; // Check if any monitoring feature is enabled static constexpr bool isEnabled() @@ -62,7 +63,8 @@ private: TaskHandle_t task_{nullptr}; std::atomic last_current_ma_{0.0f}; - std::atomic last_battery_mv_{0}; + BatteryStatus last_battery_status_{0, 0.0f, false}; + mutable std::mutex battery_mutex_; // Protect non-atomic BatteryStatus CurrentMonitor cm_; BatteryMonitor bm_;