Move the battery power calculation part to the BatteryMonitor class

This commit is contained in:
m-RNA
2026-01-01 17:14:53 +08:00
parent 017474746f
commit 97b910a38c
5 changed files with 136 additions and 58 deletions
@@ -1,6 +1,4 @@
#include "device_commands.hpp" #include "device_commands.hpp"
#include <algorithm>
#include <array>
#include "LEDManager.hpp" #include "LEDManager.hpp"
#include "MonitoringManager.hpp" #include "MonitoringManager.hpp"
#include "esp_mac.h" #include "esp_mac.h"
@@ -230,53 +228,15 @@ CommandResult getBatteryStatusCommand(std::shared_ptr<DependencyRegistry> regist
return CommandResult::getErrorResult("MonitoringManager unavailable"); return CommandResult::getErrorResult("MonitoringManager unavailable");
} }
const float volts = mon->getBatteryVoltageMilliVolts(); const auto status = mon->getBatteryStatus();
if (volts <= 0.0f) if (!status.valid)
{ {
return CommandResult::getErrorResult("Battery voltage unavailable"); return CommandResult::getErrorResult("Battery voltage unavailable");
} }
struct VoltageSOC
{
float voltage_mv;
float soc;
};
constexpr std::array<VoltageSOC, 12> 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{ const auto json = nlohmann::json{
{"voltage_mv", std::format("{:.2f}", static_cast<double>(volts))}, {"voltage_mv", std::format("{:.2f}", static_cast<double>(status.voltage_mv))},
{"percentage", std::format("{:.1f}", static_cast<double>(percent))}, {"percentage", std::format("{:.1f}", static_cast<double>(status.percentage))},
}; };
return CommandResult::getSuccessResult(json); return CommandResult::getSuccessResult(json);
#else #else
@@ -71,3 +71,52 @@ int BatteryMonitor::getBatteryMilliVolts() const
return 0; return 0;
#endif #endif
} }
float BatteryMonitor::voltageToPercentage(int voltage_mv)
{
const float volts = static_cast<float>(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;
}
@@ -14,15 +14,31 @@
* +-----------------------+ * +-----------------------+
*/ */
#include <algorithm>
#include <array>
#include <cmath>
#include "AdcSampler.hpp" #include "AdcSampler.hpp"
#include "sdkconfig.h" #include "sdkconfig.h"
#include <cmath>
/**
* @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 * @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. * 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: * Configuration is done via Kconfig options:
* - CONFIG_MONITORING_BATTERY_ENABLE * - CONFIG_MONITORING_BATTERY_ENABLE
* - CONFIG_MONITORING_BATTERY_ADC_GPIO * - CONFIG_MONITORING_BATTERY_ADC_GPIO
@@ -39,10 +55,29 @@ public:
// Initialize battery monitoring hardware // Initialize battery monitoring hardware
bool setup(); 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; 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() static constexpr bool isEnabled()
{ {
#ifdef CONFIG_MONITORING_BATTERY_ENABLE #ifdef CONFIG_MONITORING_BATTERY_ENABLE
@@ -53,6 +88,34 @@ public:
} }
private: private:
/**
* @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<VoltageSOC, 12> 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 float scale_{1.0f}; // Voltage divider scaling factor
mutable AdcSampler adc_; // ADC sampler instance (BSP layer) mutable AdcSampler adc_; // ADC sampler instance (BSP layer)
}; };
@@ -127,10 +127,11 @@ void MonitoringManager::run()
#if CONFIG_MONITORING_BATTERY_ENABLE #if CONFIG_MONITORING_BATTERY_ENABLE
if (BatteryMonitor::isEnabled() && now_tick >= next_tick_bat) if (BatteryMonitor::isEnabled() && now_tick >= next_tick_bat)
{ {
const int mv = bm_.getBatteryMilliVolts(); const auto status = bm_.getBatteryStatus();
if (mv > 0) if (status.valid)
{ {
last_battery_mv_.store(mv); std::lock_guard<std::mutex> lock(battery_mutex_);
last_battery_status_ = status;
} }
next_tick_bat = now_tick + batt_period; next_tick_bat = now_tick + batt_period;
} }
@@ -161,11 +162,14 @@ float MonitoringManager::getCurrentMilliAmps() const
return 0.0f; return 0.0f;
} }
float MonitoringManager::getBatteryVoltageMilliVolts() const BatteryStatus MonitoringManager::getBatteryStatus() const
{ {
#if CONFIG_MONITORING_BATTERY_ENABLE #if CONFIG_MONITORING_BATTERY_ENABLE
if (BatteryMonitor::isEnabled()) if (BatteryMonitor::isEnabled())
return static_cast<float>(last_battery_mv_.load()); {
std::lock_guard<std::mutex> lock(battery_mutex_);
return last_battery_status_;
}
#endif #endif
return 0.0f; return {0, 0.0f, false};
} }
@@ -19,6 +19,7 @@
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <atomic> #include <atomic>
#include <mutex>
#include "BatteryMonitor.hpp" #include "BatteryMonitor.hpp"
#include "CurrentMonitor.hpp" #include "CurrentMonitor.hpp"
@@ -47,8 +48,8 @@ public:
// Latest filtered current in mA // Latest filtered current in mA
float getCurrentMilliAmps() const; float getCurrentMilliAmps() const;
// Latest battery voltage in mV // Get complete battery status (voltage + percentage + validity)
float getBatteryVoltageMilliVolts() const; BatteryStatus getBatteryStatus() const;
// Check if any monitoring feature is enabled // Check if any monitoring feature is enabled
static constexpr bool isEnabled() static constexpr bool isEnabled()
@@ -62,7 +63,8 @@ private:
TaskHandle_t task_{nullptr}; TaskHandle_t task_{nullptr};
std::atomic<float> last_current_ma_{0.0f}; std::atomic<float> last_current_ma_{0.0f};
std::atomic<int> last_battery_mv_{0}; BatteryStatus last_battery_status_{0, 0.0f, false};
mutable std::mutex battery_mutex_; // Protect non-atomic BatteryStatus
CurrentMonitor cm_; CurrentMonitor cm_;
BatteryMonitor bm_; BatteryMonitor bm_;