mirror of
https://github.com/MrUnknownDE/OpenIris-ESPIDF.git
synced 2026-04-16 04:53:45 +02:00
Merge pull request #28 from m-RNA/feature/add-battery-monitor
Add AdcSampler & BatteryMonitor
This commit is contained in:
21
README.md
21
README.md
@@ -15,6 +15,7 @@ Firmware and tools for OpenIris — Wi‑Fi, UVC streaming, and a Python setup C
|
||||
- `tools/setup_openiris.py` — interactive CLI for Wi‑Fi, MDNS/Name, Mode, LED PWM, Logs, and a Settings Summary
|
||||
- Composite USB (UVC + CDC) when UVC mode is enabled (`GENERAL_INCLUDE_UVC_MODE`) for simultaneous video streaming and command channel
|
||||
- LED current monitoring (if enabled via `MONITORING_LED_CURRENT`) with filtered mA readings
|
||||
- Battery voltage monitoring (if enabled via `MONITORING_BATTERY_ENABLE`) with Li-ion SOC percentage calculation
|
||||
- Configurable debug LED + external IR LED control with optional error mirroring (`LED_DEBUG_ENABLE`, `LED_EXTERNAL_AS_DEBUG`)
|
||||
- Auto‑discovered per‑board configuration overlays under `boards/`
|
||||
- Command framework (JSON over serial / CDC / REST) for mode switching, Wi‑Fi config, OTA credentials, LED brightness, info & monitoring
|
||||
@@ -158,6 +159,8 @@ Runtime override: If the setup CLI (or a JSON command) provides a new device nam
|
||||
`{"commands":[{"command":"switch_mode","data":{"mode":"uvc"}}]}` then reboot.
|
||||
- Read filtered LED current (if enabled):
|
||||
`{"commands":[{"command":"get_led_current"}]}`
|
||||
- Read battery status (if enabled):
|
||||
`{"commands":[{"command":"get_battery_status"}]}`
|
||||
|
||||
---
|
||||
|
||||
@@ -241,10 +244,26 @@ Responses are JSON blobs flushed immediately.
|
||||
|
||||
---
|
||||
|
||||
### Monitoring (LED Current)
|
||||
### Monitoring (LED Current & Battery)
|
||||
|
||||
**LED Current Monitoring**
|
||||
|
||||
Enabled with `MONITORING_LED_CURRENT=y` plus shunt/gain settings. The task samples every `CONFIG_MONITORING_LED_INTERVAL_MS` ms and maintains a filtered moving average over `CONFIG_MONITORING_LED_SAMPLES` samples. Use `get_led_current` command to query.
|
||||
|
||||
**Battery Monitoring**
|
||||
|
||||
Enabled with `MONITORING_BATTERY_ENABLE=y`. Supports voltage divider configuration for measuring Li-ion/Li-Po battery voltage:
|
||||
|
||||
| Kconfig | Description |
|
||||
|---------|-------------|
|
||||
| MONITORING_BATTERY_ADC_GPIO | GPIO pin connected to voltage divider output |
|
||||
| MONITORING_BATTERY_DIVIDER_R_TOP_OHM | Top resistor value (battery side) |
|
||||
| MONITORING_BATTERY_DIVIDER_R_BOTTOM_OHM | Bottom resistor value (GND side) |
|
||||
| MONITORING_BATTERY_INTERVAL_MS | Sampling interval in milliseconds |
|
||||
| MONITORING_BATTERY_SAMPLES | Moving average window size |
|
||||
|
||||
The firmware includes a Li-ion discharge curve lookup table for SOC (State of Charge) percentage calculation with linear interpolation. Use `get_battery_status` command to query voltage (mV) and percentage (%).
|
||||
|
||||
### Debug & External LED Configuration
|
||||
|
||||
| Kconfig | Effect |
|
||||
|
||||
@@ -26,6 +26,7 @@ std::unordered_map<std::string, CommandType> commandTypeMap = {
|
||||
{"get_led_duty_cycle", CommandType::GET_LED_DUTY_CYCLE},
|
||||
{"get_serial", CommandType::GET_SERIAL},
|
||||
{"get_led_current", CommandType::GET_LED_CURRENT},
|
||||
{"get_battery_status", CommandType::GET_BATTERY_STATUS},
|
||||
{"get_who_am_i", CommandType::GET_WHO_AM_I},
|
||||
};
|
||||
|
||||
@@ -102,6 +103,9 @@ std::function<CommandResult()> CommandManager::createCommand(const CommandType t
|
||||
case CommandType::GET_LED_CURRENT:
|
||||
return [this]
|
||||
{ return getLEDCurrentCommand(this->registry); };
|
||||
case CommandType::GET_BATTERY_STATUS:
|
||||
return [this]
|
||||
{ return getBatteryStatusCommand(this->registry); };
|
||||
case CommandType::GET_WHO_AM_I:
|
||||
return [this]
|
||||
{ return getInfoCommand(this->registry); };
|
||||
|
||||
@@ -47,6 +47,7 @@ enum class CommandType
|
||||
GET_LED_DUTY_CYCLE,
|
||||
GET_SERIAL,
|
||||
GET_LED_CURRENT,
|
||||
GET_BATTERY_STATUS,
|
||||
GET_WHO_AM_I,
|
||||
};
|
||||
|
||||
|
||||
@@ -219,6 +219,31 @@ CommandResult getLEDCurrentCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||
#endif
|
||||
}
|
||||
|
||||
CommandResult getBatteryStatusCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||
{
|
||||
#if CONFIG_MONITORING_BATTERY_ENABLE
|
||||
auto mon = registry->resolve<MonitoringManager>(DependencyType::monitoring_manager);
|
||||
if (!mon)
|
||||
{
|
||||
return CommandResult::getErrorResult("MonitoringManager unavailable");
|
||||
}
|
||||
|
||||
const auto status = mon->getBatteryStatus();
|
||||
if (!status.valid)
|
||||
{
|
||||
return CommandResult::getErrorResult("Battery voltage unavailable");
|
||||
}
|
||||
|
||||
const auto json = nlohmann::json{
|
||||
{"voltage_mv", std::format("{:.2f}", static_cast<double>(status.voltage_mv))},
|
||||
{"percentage", std::format("{:.1f}", static_cast<double>(status.percentage))},
|
||||
};
|
||||
return CommandResult::getSuccessResult(json);
|
||||
#else
|
||||
return CommandResult::getErrorResult("Battery monitor disabled");
|
||||
#endif
|
||||
}
|
||||
|
||||
CommandResult getInfoCommand(std::shared_ptr<DependencyRegistry> /*registry*/)
|
||||
{
|
||||
const char *who = CONFIG_GENERAL_BOARD;
|
||||
|
||||
@@ -26,6 +26,7 @@ CommandResult getSerialNumberCommand(std::shared_ptr<DependencyRegistry> registr
|
||||
|
||||
// Monitoring
|
||||
CommandResult getLEDCurrentCommand(std::shared_ptr<DependencyRegistry> registry);
|
||||
CommandResult getBatteryStatusCommand(std::shared_ptr<DependencyRegistry> registry);
|
||||
|
||||
// General info
|
||||
CommandResult getInfoCommand(std::shared_ptr<DependencyRegistry> registry);
|
||||
@@ -1,30 +1,53 @@
|
||||
# Architecture:
|
||||
# +-----------------------+
|
||||
# | MonitoringManager | ← High-level coordinator
|
||||
# +-----------------------+
|
||||
# | BatteryMonitor | ← Battery logic (platform-independent)
|
||||
# | CurrentMonitor | ← Current logic (platform-independent)
|
||||
# +-----------------------+
|
||||
# | AdcSampler | ← BSP: Unified ADC sampling interface
|
||||
# +-----------------------+
|
||||
# | ESP-IDF ADC HAL | ← Espressif official driver
|
||||
# +-----------------------+
|
||||
|
||||
set(
|
||||
requires
|
||||
Helpers
|
||||
)
|
||||
|
||||
if ("$ENV{IDF_TARGET}" STREQUAL "esp32s3")
|
||||
# Platform-specific dependencies
|
||||
if ("$ENV{IDF_TARGET}" STREQUAL "esp32s3" OR "$ENV{IDF_TARGET}" STREQUAL "esp32")
|
||||
list(APPEND requires
|
||||
driver
|
||||
esp_adc
|
||||
)
|
||||
endif()
|
||||
|
||||
# Common source files (platform-independent business logic)
|
||||
set(
|
||||
source_files
|
||||
""
|
||||
"Monitoring/MonitoringManager.cpp"
|
||||
"Monitoring/BatteryMonitor.cpp"
|
||||
"Monitoring/CurrentMonitor.cpp"
|
||||
)
|
||||
|
||||
# BSP Layer: ADC sampler implementation
|
||||
if ("$ENV{IDF_TARGET}" STREQUAL "esp32s3" OR "$ENV{IDF_TARGET}" STREQUAL "esp32")
|
||||
# Common ADC implementation
|
||||
list(APPEND source_files
|
||||
"Monitoring/AdcSampler.cpp"
|
||||
)
|
||||
|
||||
# Platform-specific GPIO-to-channel mapping
|
||||
if ("$ENV{IDF_TARGET}" STREQUAL "esp32s3")
|
||||
list(APPEND source_files
|
||||
"Monitoring/CurrentMonitor_esp32s3.cpp"
|
||||
"Monitoring/MonitoringManager_esp32s3.cpp"
|
||||
)
|
||||
else()
|
||||
list(APPEND source_files
|
||||
"Monitoring/CurrentMonitor_esp32.cpp"
|
||||
"Monitoring/MonitoringManager_esp32.cpp"
|
||||
)
|
||||
list(APPEND source_files
|
||||
"Monitoring/AdcSampler_esp32s3.cpp"
|
||||
)
|
||||
elseif ("$ENV{IDF_TARGET}" STREQUAL "esp32")
|
||||
list(APPEND source_files
|
||||
"Monitoring/AdcSampler_esp32.cpp"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
197
components/Monitoring/Monitoring/AdcSampler.cpp
Normal file
197
components/Monitoring/Monitoring/AdcSampler.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* @file AdcSampler.cpp
|
||||
* @brief BSP Layer - Common ADC sampling implementation
|
||||
*
|
||||
* This file contains platform-independent ADC sampling logic.
|
||||
* Platform-specific GPIO-to-channel mapping is in separate files:
|
||||
* - AdcSampler_esp32.cpp
|
||||
* - AdcSampler_esp32s3.cpp
|
||||
*/
|
||||
|
||||
#include "AdcSampler.hpp"
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32)
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG = "[AdcSampler]";
|
||||
|
||||
// Static member initialization
|
||||
adc_oneshot_unit_handle_t AdcSampler::shared_unit_ = nullptr;
|
||||
|
||||
AdcSampler::~AdcSampler()
|
||||
{
|
||||
if (cali_handle_)
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
adc_cali_delete_scheme_curve_fitting(cali_handle_);
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||
adc_cali_delete_scheme_line_fitting(cali_handle_);
|
||||
#endif
|
||||
cali_handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool AdcSampler::init(int gpio, adc_atten_t atten, adc_bitwidth_t bitwidth, size_t window_size)
|
||||
{
|
||||
// Initialize moving average filter
|
||||
if (window_size == 0)
|
||||
{
|
||||
window_size = 1;
|
||||
}
|
||||
samples_.assign(window_size, 0);
|
||||
sample_sum_ = 0;
|
||||
sample_idx_ = 0;
|
||||
sample_count_ = 0;
|
||||
|
||||
atten_ = atten;
|
||||
bitwidth_ = bitwidth;
|
||||
|
||||
// Map GPIO to ADC channel (platform-specific)
|
||||
if (!map_gpio_to_channel(gpio, unit_, channel_))
|
||||
{
|
||||
ESP_LOGW(TAG, "GPIO %d is not a valid ADC1 pin on this chip", gpio);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize shared ADC unit
|
||||
if (!ensure_unit())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure the ADC channel
|
||||
if (!configure_channel(gpio, atten, bitwidth))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try calibration (requires eFuse data)
|
||||
// ESP32-S3 uses curve-fitting, ESP32 uses line-fitting
|
||||
esp_err_t cal_err = ESP_FAIL;
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// ESP32-S3 curve fitting calibration
|
||||
adc_cali_curve_fitting_config_t cal_cfg = {
|
||||
.unit_id = unit_,
|
||||
.chan = channel_,
|
||||
.atten = atten_,
|
||||
.bitwidth = bitwidth_,
|
||||
};
|
||||
cal_err = adc_cali_create_scheme_curve_fitting(&cal_cfg, &cali_handle_);
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||
// ESP32 line-fitting calibration is per-unit, not per-channel
|
||||
adc_cali_line_fitting_config_t cal_cfg = {
|
||||
.unit_id = unit_,
|
||||
.atten = atten_,
|
||||
.bitwidth = bitwidth_,
|
||||
};
|
||||
cal_err = adc_cali_create_scheme_line_fitting(&cal_cfg, &cali_handle_);
|
||||
#endif
|
||||
|
||||
if (cal_err == ESP_OK)
|
||||
{
|
||||
cali_inited_ = true;
|
||||
ESP_LOGI(TAG, "ADC calibration initialized");
|
||||
}
|
||||
else
|
||||
{
|
||||
cali_inited_ = false;
|
||||
ESP_LOGW(TAG, "ADC calibration not available; using raw-to-mV approximation");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AdcSampler::sampleOnce()
|
||||
{
|
||||
if (!shared_unit_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int raw = 0;
|
||||
esp_err_t err = adc_oneshot_read(shared_unit_, channel_, &raw);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "adc_oneshot_read failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
int mv = 0;
|
||||
if (cali_inited_)
|
||||
{
|
||||
if (adc_cali_raw_to_voltage(cali_handle_, raw, &mv) != ESP_OK)
|
||||
{
|
||||
mv = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Approximate conversion for 12dB attenuation (~0–3600 mV range)
|
||||
// Full-scale raw = (1 << bitwidth_) - 1
|
||||
// For 12-bit: max raw = 4095 → ~3600 mV
|
||||
int full_scale_mv = 3600;
|
||||
int max_raw = (1 << bitwidth_) - 1;
|
||||
if (max_raw > 0)
|
||||
{
|
||||
mv = (raw * full_scale_mv) / max_raw;
|
||||
}
|
||||
else
|
||||
{
|
||||
mv = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Update moving average filter
|
||||
sample_sum_ -= samples_[sample_idx_];
|
||||
samples_[sample_idx_] = mv;
|
||||
sample_sum_ += mv;
|
||||
sample_idx_ = (sample_idx_ + 1) % samples_.size();
|
||||
if (sample_count_ < samples_.size())
|
||||
{
|
||||
sample_count_++;
|
||||
}
|
||||
filtered_mv_ = sample_sum_ / static_cast<int>(sample_count_ > 0 ? sample_count_ : 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AdcSampler::ensure_unit()
|
||||
{
|
||||
if (shared_unit_)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
adc_oneshot_unit_init_cfg_t unit_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.clk_src = ADC_RTC_CLK_SRC_DEFAULT,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
esp_err_t err = adc_oneshot_new_unit(&unit_cfg, &shared_unit_);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "adc_oneshot_new_unit failed: %s", esp_err_to_name(err));
|
||||
shared_unit_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AdcSampler::configure_channel(int gpio, adc_atten_t atten, adc_bitwidth_t bitwidth)
|
||||
{
|
||||
adc_oneshot_chan_cfg_t chan_cfg = {
|
||||
.atten = atten,
|
||||
.bitwidth = bitwidth,
|
||||
};
|
||||
esp_err_t err = adc_oneshot_config_channel(shared_unit_, channel_, &chan_cfg);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "adc_oneshot_config_channel failed (GPIO %d, CH %d): %s",
|
||||
gpio, static_cast<int>(channel_), esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32
|
||||
119
components/Monitoring/Monitoring/AdcSampler.hpp
Normal file
119
components/Monitoring/Monitoring/AdcSampler.hpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#pragma once
|
||||
/**
|
||||
* @file AdcSampler.hpp
|
||||
* @brief BSP Layer - Unified ADC sampling interface (Hardware Abstraction)
|
||||
*
|
||||
* Architecture:
|
||||
* +-----------------------+
|
||||
* | MonitoringManager | ← High-level coordinator
|
||||
* +-----------------------+
|
||||
* | BatteryMonitor | ← Battery logic: voltage, capacity, health
|
||||
* | CurrentMonitor | ← Current logic: power, instantaneous current
|
||||
* +-----------------------+
|
||||
* | AdcSampler | ← BSP: Unified ADC sampling interface (this file)
|
||||
* +-----------------------+
|
||||
* | ESP-IDF ADC HAL | ← Espressif official driver
|
||||
* +-----------------------+
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32)
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_adc/adc_cali.h"
|
||||
#include "esp_adc/adc_cali_scheme.h"
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @class AdcSampler
|
||||
* @brief Hardware abstraction layer for ADC sampling with moving average filter
|
||||
*
|
||||
* This class provides a unified interface for ADC sampling across different
|
||||
* ESP32 variants. Platform-specific GPIO-to-channel mapping is handled internally.
|
||||
*/
|
||||
class AdcSampler
|
||||
{
|
||||
public:
|
||||
AdcSampler() = default;
|
||||
~AdcSampler();
|
||||
|
||||
// Non-copyable, non-movable (owns hardware resources)
|
||||
AdcSampler(const AdcSampler &) = delete;
|
||||
AdcSampler &operator=(const AdcSampler &) = delete;
|
||||
AdcSampler(AdcSampler &&) = delete;
|
||||
AdcSampler &operator=(AdcSampler &&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Initialize the ADC channel on the shared ADC1 oneshot unit
|
||||
* @param gpio GPIO pin number for ADC input
|
||||
* @param atten ADC attenuation setting (default: 12dB for ~0-3.3V range)
|
||||
* @param bitwidth ADC resolution (default: 12-bit)
|
||||
* @param window_size Moving average window size (>=1)
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
bool init(int gpio,
|
||||
adc_atten_t atten = ADC_ATTEN_DB_12,
|
||||
adc_bitwidth_t bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
size_t window_size = 1);
|
||||
|
||||
/**
|
||||
* @brief Perform one ADC conversion and update filtered value
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
bool sampleOnce();
|
||||
|
||||
/**
|
||||
* @brief Get the filtered ADC reading in millivolts
|
||||
* @return Filtered voltage in mV
|
||||
*/
|
||||
int getFilteredMilliVolts() const { return filtered_mv_; }
|
||||
|
||||
/**
|
||||
* @brief Check if ADC sampling is supported on current platform
|
||||
* @return true if supported
|
||||
*/
|
||||
static constexpr bool isSupported() { return true; }
|
||||
|
||||
private:
|
||||
// Hardware initialization helpers
|
||||
bool ensure_unit();
|
||||
bool configure_channel(int gpio, adc_atten_t atten, adc_bitwidth_t bitwidth);
|
||||
|
||||
/**
|
||||
* @brief Platform-specific GPIO to ADC channel mapping
|
||||
* @note Implemented separately in AdcSampler_esp32.cpp and AdcSampler_esp32s3.cpp
|
||||
*/
|
||||
static bool map_gpio_to_channel(int gpio, adc_unit_t &unit, adc_channel_t &channel);
|
||||
|
||||
// Shared ADC1 oneshot handle (single instance for all AdcSampler objects)
|
||||
static adc_oneshot_unit_handle_t shared_unit_;
|
||||
|
||||
// Per-instance state
|
||||
adc_cali_handle_t cali_handle_{nullptr};
|
||||
bool cali_inited_{false};
|
||||
adc_channel_t channel_{ADC_CHANNEL_0};
|
||||
adc_unit_t unit_{ADC_UNIT_1};
|
||||
adc_atten_t atten_{ADC_ATTEN_DB_12};
|
||||
adc_bitwidth_t bitwidth_{ADC_BITWIDTH_DEFAULT};
|
||||
|
||||
// Moving average filter state
|
||||
std::vector<int> samples_{};
|
||||
int sample_sum_{0};
|
||||
size_t sample_idx_{0};
|
||||
size_t sample_count_{0};
|
||||
int filtered_mv_{0};
|
||||
};
|
||||
|
||||
#else
|
||||
// Stub for unsupported targets to keep interfaces consistent
|
||||
class AdcSampler
|
||||
{
|
||||
public:
|
||||
bool init(int /*gpio*/, int /*atten*/ = 0, int /*bitwidth*/ = 0, size_t /*window_size*/ = 1) { return false; }
|
||||
bool sampleOnce() { return false; }
|
||||
int getFilteredMilliVolts() const { return 0; }
|
||||
static constexpr bool isSupported() { return false; }
|
||||
};
|
||||
#endif
|
||||
59
components/Monitoring/Monitoring/AdcSampler_esp32.cpp
Normal file
59
components/Monitoring/Monitoring/AdcSampler_esp32.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @file AdcSampler_esp32.cpp
|
||||
* @brief BSP Layer - ESP32 specific GPIO to ADC channel mapping
|
||||
*
|
||||
* ESP32 ADC1 GPIO mapping:
|
||||
* - GPIO32 → ADC1_CH4
|
||||
* - GPIO33 → ADC1_CH5
|
||||
* - GPIO34 → ADC1_CH6
|
||||
* - GPIO35 → ADC1_CH7
|
||||
* - GPIO36 → ADC1_CH0
|
||||
* - GPIO37 → ADC1_CH1
|
||||
* - GPIO38 → ADC1_CH2
|
||||
* - GPIO39 → ADC1_CH3
|
||||
*
|
||||
* Note: ADC2 is not used to avoid conflicts with Wi-Fi.
|
||||
*/
|
||||
|
||||
#include "AdcSampler.hpp"
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
|
||||
bool AdcSampler::map_gpio_to_channel(int gpio, adc_unit_t &unit, adc_channel_t &channel)
|
||||
{
|
||||
unit = ADC_UNIT_1; // Only use ADC1 to avoid Wi-Fi conflict
|
||||
|
||||
// ESP32: ADC1 GPIO mapping (GPIO32-39)
|
||||
switch (gpio)
|
||||
{
|
||||
case 36:
|
||||
channel = ADC_CHANNEL_0;
|
||||
return true;
|
||||
case 37:
|
||||
channel = ADC_CHANNEL_1;
|
||||
return true;
|
||||
case 38:
|
||||
channel = ADC_CHANNEL_2;
|
||||
return true;
|
||||
case 39:
|
||||
channel = ADC_CHANNEL_3;
|
||||
return true;
|
||||
case 32:
|
||||
channel = ADC_CHANNEL_4;
|
||||
return true;
|
||||
case 33:
|
||||
channel = ADC_CHANNEL_5;
|
||||
return true;
|
||||
case 34:
|
||||
channel = ADC_CHANNEL_6;
|
||||
return true;
|
||||
case 35:
|
||||
channel = ADC_CHANNEL_7;
|
||||
return true;
|
||||
default:
|
||||
channel = ADC_CHANNEL_0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CONFIG_IDF_TARGET_ESP32
|
||||
39
components/Monitoring/Monitoring/AdcSampler_esp32s3.cpp
Normal file
39
components/Monitoring/Monitoring/AdcSampler_esp32s3.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file AdcSampler_esp32s3.cpp
|
||||
* @brief BSP Layer - ESP32-S3 specific GPIO to ADC channel mapping
|
||||
*
|
||||
* ESP32-S3 ADC1 GPIO mapping:
|
||||
* - GPIO1 → ADC1_CH0
|
||||
* - GPIO2 → ADC1_CH1
|
||||
* - GPIO3 → ADC1_CH2
|
||||
* - GPIO4 → ADC1_CH3
|
||||
* - GPIO5 → ADC1_CH4
|
||||
* - GPIO6 → ADC1_CH5
|
||||
* - GPIO7 → ADC1_CH6
|
||||
* - GPIO8 → ADC1_CH7
|
||||
* - GPIO9 → ADC1_CH8
|
||||
* - GPIO10 → ADC1_CH9
|
||||
*
|
||||
* Note: ADC2 is not used to avoid conflicts with Wi-Fi.
|
||||
*/
|
||||
|
||||
#include "AdcSampler.hpp"
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
|
||||
bool AdcSampler::map_gpio_to_channel(int gpio, adc_unit_t &unit, adc_channel_t &channel)
|
||||
{
|
||||
unit = ADC_UNIT_1; // Only use ADC1 to avoid Wi-Fi conflict
|
||||
|
||||
// ESP32-S3: ADC1 on GPIO1–10 → CH0–CH9
|
||||
if (gpio >= 1 && gpio <= 10)
|
||||
{
|
||||
channel = static_cast<adc_channel_t>(gpio - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
channel = ADC_CHANNEL_0;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // CONFIG_IDF_TARGET_ESP32S3
|
||||
122
components/Monitoring/Monitoring/BatteryMonitor.cpp
Normal file
122
components/Monitoring/Monitoring/BatteryMonitor.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* @file BatteryMonitor.cpp
|
||||
* @brief Business Logic Layer - Battery monitoring implementation
|
||||
*
|
||||
* Platform-independent battery monitoring logic.
|
||||
* Uses AdcSampler (BSP layer) for hardware abstraction.
|
||||
*/
|
||||
|
||||
#include "BatteryMonitor.hpp"
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG = "[BatteryMonitor]";
|
||||
|
||||
bool BatteryMonitor::setup()
|
||||
{
|
||||
#if CONFIG_MONITORING_BATTERY_ENABLE
|
||||
if (!AdcSampler::isSupported())
|
||||
{
|
||||
ESP_LOGI(TAG, "Battery monitoring not supported on this target");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate divider resistor configuration
|
||||
if (CONFIG_MONITORING_BATTERY_DIVIDER_R_BOTTOM_OHM == 0)
|
||||
{
|
||||
ESP_LOGE(TAG, "Invalid divider bottom resistor: %d", CONFIG_MONITORING_BATTERY_DIVIDER_R_BOTTOM_OHM);
|
||||
return false;
|
||||
}
|
||||
if (CONFIG_MONITORING_BATTERY_DIVIDER_R_TOP_OHM <= 0 || CONFIG_MONITORING_BATTERY_DIVIDER_R_BOTTOM_OHM < 0)
|
||||
{
|
||||
scale_ = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calculate voltage divider scaling factor
|
||||
// Vbat = Vadc * (R_TOP + R_BOTTOM) / R_BOTTOM
|
||||
scale_ = 1.0f + static_cast<float>(CONFIG_MONITORING_BATTERY_DIVIDER_R_TOP_OHM) / static_cast<float>(CONFIG_MONITORING_BATTERY_DIVIDER_R_BOTTOM_OHM);
|
||||
}
|
||||
|
||||
// Initialize ADC sampler (BSP layer)
|
||||
if (!adc_.init(CONFIG_MONITORING_BATTERY_ADC_GPIO, ADC_ATTEN_DB_12, ADC_BITWIDTH_DEFAULT, CONFIG_MONITORING_BATTERY_SAMPLES))
|
||||
{
|
||||
ESP_LOGE(TAG, "Battery ADC init failed");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGI(TAG, "Battery monitor enabled (GPIO=%d, scale=%.3f)", CONFIG_MONITORING_BATTERY_ADC_GPIO, scale_);
|
||||
return true;
|
||||
#else
|
||||
ESP_LOGI(TAG, "Battery monitoring disabled by Kconfig");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
int BatteryMonitor::getBatteryMilliVolts() const
|
||||
{
|
||||
#if CONFIG_MONITORING_BATTERY_ENABLE
|
||||
if (!AdcSampler::isSupported())
|
||||
return 0;
|
||||
|
||||
if (!adc_.sampleOnce())
|
||||
return 0;
|
||||
|
||||
const int mv_at_adc = adc_.getFilteredMilliVolts();
|
||||
if (mv_at_adc <= 0)
|
||||
return 0;
|
||||
|
||||
// Apply voltage divider scaling
|
||||
const float battery_mv = static_cast<float>(mv_at_adc) * scale_;
|
||||
return static_cast<int>(std::lround(battery_mv));
|
||||
#else
|
||||
return 0;
|
||||
#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;
|
||||
}
|
||||
121
components/Monitoring/Monitoring/BatteryMonitor.hpp
Normal file
121
components/Monitoring/Monitoring/BatteryMonitor.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
/**
|
||||
* @file BatteryMonitor.hpp
|
||||
* @brief Business Logic Layer - Battery monitoring (voltage, capacity, health)
|
||||
*
|
||||
* Architecture:
|
||||
* +-----------------------+
|
||||
* | MonitoringManager | ← High-level coordinator
|
||||
* +-----------------------+
|
||||
* | BatteryMonitor | ← Battery logic (this file)
|
||||
* | CurrentMonitor | ← Current logic
|
||||
* +-----------------------+
|
||||
* | AdcSampler | ← BSP: Unified ADC sampling interface
|
||||
* +-----------------------+
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include "AdcSampler.hpp"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
|
||||
/**
|
||||
* @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 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
|
||||
* - CONFIG_MONITORING_BATTERY_DIVIDER_R_TOP_OHM
|
||||
* - CONFIG_MONITORING_BATTERY_DIVIDER_R_BOTTOM_OHM
|
||||
* - CONFIG_MONITORING_BATTERY_SAMPLES
|
||||
*/
|
||||
class BatteryMonitor
|
||||
{
|
||||
public:
|
||||
BatteryMonitor() = default;
|
||||
~BatteryMonitor() = default;
|
||||
|
||||
// Initialize battery monitoring hardware
|
||||
bool setup();
|
||||
|
||||
/**
|
||||
* @brief Read battery voltage (with divider compensation)
|
||||
* @return Battery voltage in millivolts, 0 on failure
|
||||
*/
|
||||
int getBatteryMilliVolts() const;
|
||||
|
||||
/**
|
||||
* @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
|
||||
return AdcSampler::isSupported();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
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
|
||||
mutable AdcSampler adc_; // ADC sampler instance (BSP layer)
|
||||
};
|
||||
62
components/Monitoring/Monitoring/CurrentMonitor.cpp
Normal file
62
components/Monitoring/Monitoring/CurrentMonitor.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @file CurrentMonitor.cpp
|
||||
* @brief Business Logic Layer - Current monitoring implementation
|
||||
*
|
||||
* Platform-independent current monitoring logic.
|
||||
* Uses AdcSampler (BSP layer) for hardware abstraction.
|
||||
*/
|
||||
|
||||
#include "CurrentMonitor.hpp"
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG = "[CurrentMonitor]";
|
||||
|
||||
void CurrentMonitor::setup()
|
||||
{
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
if (!AdcSampler::isSupported())
|
||||
{
|
||||
ESP_LOGI(TAG, "LED current monitoring not supported on this target");
|
||||
return;
|
||||
}
|
||||
|
||||
const bool ok = adc_.init(CONFIG_MONITORING_LED_ADC_GPIO, ADC_ATTEN_DB_12, ADC_BITWIDTH_DEFAULT, CONFIG_MONITORING_LED_SAMPLES);
|
||||
if (!ok)
|
||||
{
|
||||
ESP_LOGE(TAG, "ADC init failed for LED current monitor");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "LED current monitor enabled (GPIO=%d, Shunt=%dmΩ, Gain=%d)", CONFIG_MONITORING_LED_ADC_GPIO, CONFIG_MONITORING_LED_SHUNT_MILLIOHM,
|
||||
CONFIG_MONITORING_LED_GAIN);
|
||||
#else
|
||||
ESP_LOGI(TAG, "LED current monitoring disabled by Kconfig");
|
||||
#endif
|
||||
}
|
||||
|
||||
float CurrentMonitor::getCurrentMilliAmps() const
|
||||
{
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
if (!AdcSampler::isSupported())
|
||||
return 0.0f;
|
||||
|
||||
const int shunt_milliohm = CONFIG_MONITORING_LED_SHUNT_MILLIOHM; // mΩ
|
||||
if (shunt_milliohm <= 0)
|
||||
return 0.0f;
|
||||
|
||||
if (!adc_.sampleOnce())
|
||||
return 0.0f;
|
||||
|
||||
int filtered_mv = adc_.getFilteredMilliVolts();
|
||||
|
||||
// Apply gain compensation if using current sense amplifier
|
||||
if (CONFIG_MONITORING_LED_GAIN > 0)
|
||||
filtered_mv = filtered_mv / CONFIG_MONITORING_LED_GAIN; // convert back to shunt voltage
|
||||
|
||||
// Physically correct scaling:
|
||||
// I[mA] = 1000 * Vshunt[mV] / R[mΩ]
|
||||
return (1000.0f * static_cast<float>(filtered_mv)) / static_cast<float>(shunt_milliohm);
|
||||
#else
|
||||
return 0.0f;
|
||||
#endif
|
||||
}
|
||||
@@ -1,50 +1,61 @@
|
||||
#ifndef CURRENT_MONITOR_HPP
|
||||
#define CURRENT_MONITOR_HPP
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "sdkconfig.h"
|
||||
/**
|
||||
* @file CurrentMonitor.hpp
|
||||
* @brief Business Logic Layer - Current monitoring (power, instantaneous current)
|
||||
*
|
||||
* Architecture:
|
||||
* +-----------------------+
|
||||
* | MonitoringManager | ← High-level coordinator
|
||||
* +-----------------------+
|
||||
* | BatteryMonitor | ← Battery logic
|
||||
* | CurrentMonitor | ← Current logic (this file)
|
||||
* +-----------------------+
|
||||
* | AdcSampler | ← BSP: Unified ADC sampling interface
|
||||
* +-----------------------+
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include "sdkconfig.h"
|
||||
#include "AdcSampler.hpp"
|
||||
|
||||
/**
|
||||
* @class CurrentMonitor
|
||||
* @brief Monitors LED current through a shunt resistor
|
||||
*
|
||||
* Uses AdcSampler (BSP layer) for hardware abstraction.
|
||||
* Configuration is done via Kconfig options:
|
||||
* - CONFIG_MONITORING_LED_CURRENT
|
||||
* - CONFIG_MONITORING_LED_ADC_GPIO
|
||||
* - CONFIG_MONITORING_LED_SHUNT_MILLIOHM
|
||||
* - CONFIG_MONITORING_LED_GAIN
|
||||
* - CONFIG_MONITORING_LED_SAMPLES
|
||||
*/
|
||||
class CurrentMonitor
|
||||
{
|
||||
public:
|
||||
CurrentMonitor();
|
||||
CurrentMonitor() = default;
|
||||
~CurrentMonitor() = default;
|
||||
|
||||
// Initialize current monitoring hardware
|
||||
void setup();
|
||||
void sampleOnce();
|
||||
|
||||
// Returns filtered voltage in millivolts at shunt (after dividing by gain)
|
||||
int getFilteredMillivolts() const { return filtered_mv_; }
|
||||
// Returns current in milliamps computed as Vshunt[mV] / R[mΩ]
|
||||
float getCurrentMilliAmps() const;
|
||||
|
||||
// convenience: combined sampling and compute; returns mA
|
||||
float pollAndGetMilliAmps();
|
||||
float getCurrentMilliAmps() const;
|
||||
|
||||
// Whether monitoring is enabled by Kconfig
|
||||
// Whether monitoring is enabled by Kconfig and supported by BSP
|
||||
static constexpr bool isEnabled()
|
||||
{
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
return true;
|
||||
#if CONFIG_MONITORING_LED_CURRENT
|
||||
return AdcSampler::isSupported();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
void init_adc();
|
||||
int read_mv_once();
|
||||
int gpio_to_adc_channel(int gpio);
|
||||
#endif
|
||||
|
||||
int filtered_mv_ = 0;
|
||||
int sample_sum_ = 0;
|
||||
std::vector<int> samples_;
|
||||
size_t sample_idx_ = 0;
|
||||
size_t sample_count_ = 0;
|
||||
mutable AdcSampler adc_; // ADC sampler instance (BSP layer)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "CurrentMonitor.hpp"
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG_CM = "[CurrentMonitor]";
|
||||
|
||||
CurrentMonitor::CurrentMonitor()
|
||||
{
|
||||
// empty as esp32 doesn't support this
|
||||
// but without a separate implementation, the linker will complain :c
|
||||
}
|
||||
|
||||
void CurrentMonitor::setup()
|
||||
{
|
||||
ESP_LOGI(TAG_CM, "LED current monitoring disabled");
|
||||
}
|
||||
|
||||
float CurrentMonitor::getCurrentMilliAmps() const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float CurrentMonitor::pollAndGetMilliAmps()
|
||||
{
|
||||
sampleOnce();
|
||||
return getCurrentMilliAmps();
|
||||
}
|
||||
|
||||
void CurrentMonitor::sampleOnce()
|
||||
{
|
||||
(void)0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
void CurrentMonitor::init_adc()
|
||||
{
|
||||
}
|
||||
|
||||
int CurrentMonitor::read_mv_once()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -1,179 +0,0 @@
|
||||
#include "CurrentMonitor.hpp"
|
||||
#include <esp_log.h>
|
||||
#include <cmath>
|
||||
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_adc/adc_cali.h"
|
||||
#include "esp_adc/adc_cali_scheme.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG_CM = "[CurrentMonitor]";
|
||||
|
||||
CurrentMonitor::CurrentMonitor()
|
||||
{
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
samples_.assign(CONFIG_MONITORING_LED_SAMPLES, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CurrentMonitor::setup()
|
||||
{
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
init_adc();
|
||||
#else
|
||||
ESP_LOGI(TAG_CM, "LED current monitoring disabled");
|
||||
#endif
|
||||
}
|
||||
|
||||
float CurrentMonitor::getCurrentMilliAmps() const
|
||||
{
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
const int shunt_milliohm = CONFIG_MONITORING_LED_SHUNT_MILLIOHM; // mΩ
|
||||
if (shunt_milliohm <= 0)
|
||||
return 0.0f;
|
||||
// Physically correct scaling:
|
||||
// I[mA] = 1000 * Vshunt[mV] / R[mΩ]
|
||||
return (1000.0f * static_cast<float>(filtered_mv_)) / static_cast<float>(shunt_milliohm);
|
||||
#else
|
||||
return 0.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
float CurrentMonitor::pollAndGetMilliAmps()
|
||||
{
|
||||
sampleOnce();
|
||||
return getCurrentMilliAmps();
|
||||
}
|
||||
|
||||
void CurrentMonitor::sampleOnce()
|
||||
{
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
int mv = read_mv_once();
|
||||
// Divide by analog gain/divider factor to get shunt voltage
|
||||
if (CONFIG_MONITORING_LED_GAIN > 0)
|
||||
mv = mv / CONFIG_MONITORING_LED_GAIN;
|
||||
|
||||
// Moving average over N samples
|
||||
if (samples_.empty())
|
||||
{
|
||||
samples_.assign(CONFIG_MONITORING_LED_SAMPLES, 0);
|
||||
sample_sum_ = 0;
|
||||
sample_idx_ = 0;
|
||||
sample_count_ = 0;
|
||||
}
|
||||
|
||||
sample_sum_ -= samples_[sample_idx_];
|
||||
samples_[sample_idx_] = mv;
|
||||
sample_sum_ += mv;
|
||||
sample_idx_ = (sample_idx_ + 1) % samples_.size();
|
||||
if (sample_count_ < samples_.size())
|
||||
sample_count_++;
|
||||
|
||||
filtered_mv_ = sample_sum_ / static_cast<int>(sample_count_ > 0 ? sample_count_ : 1);
|
||||
#else
|
||||
(void)0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
|
||||
static adc_oneshot_unit_handle_t s_adc_handle = nullptr;
|
||||
static adc_cali_handle_t s_cali_handle = nullptr;
|
||||
static bool s_cali_inited = false;
|
||||
static adc_channel_t s_channel;
|
||||
static adc_unit_t s_unit;
|
||||
|
||||
void CurrentMonitor::init_adc()
|
||||
{
|
||||
// Derive ADC unit/channel from GPIO
|
||||
int gpio = CONFIG_MONITORING_LED_ADC_GPIO;
|
||||
|
||||
esp_err_t err;
|
||||
adc_oneshot_unit_init_cfg_t unit_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
};
|
||||
err = adc_oneshot_new_unit(&unit_cfg, &s_adc_handle);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG_CM, "adc_oneshot_new_unit failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to map GPIO to ADC channel automatically if helper exists; otherwise guess for ESP32-S3 ADC1
|
||||
#ifdef ADC1_GPIO1_CHANNEL
|
||||
(void)0; // placeholder for potential future macros
|
||||
#endif
|
||||
|
||||
// Use IO-to-channel helper where available
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||
// ESP32-S3: ADC1 channels on GPIO1..GPIO10 map to CH0..CH9
|
||||
if (gpio >= 1 && gpio <= 10)
|
||||
{
|
||||
s_unit = ADC_UNIT_1;
|
||||
s_channel = static_cast<adc_channel_t>(gpio - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG_CM, "Configured GPIO %d may not be ADC-capable on ESP32-S3", gpio);
|
||||
s_unit = ADC_UNIT_1;
|
||||
s_channel = ADC_CHANNEL_0;
|
||||
}
|
||||
#else
|
||||
// Fallback: assume ADC1 CH0
|
||||
s_unit = ADC_UNIT_1;
|
||||
s_channel = ADC_CHANNEL_0;
|
||||
#endif
|
||||
|
||||
adc_oneshot_chan_cfg_t chan_cfg = {
|
||||
.atten = ADC_ATTEN_DB_11,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
err = adc_oneshot_config_channel(s_adc_handle, s_channel, &chan_cfg);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG_CM, "adc_oneshot_config_channel failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Calibration using curve fitting if available
|
||||
adc_cali_curve_fitting_config_t cal_cfg = {
|
||||
.unit_id = s_unit,
|
||||
.atten = chan_cfg.atten,
|
||||
.bitwidth = chan_cfg.bitwidth,
|
||||
};
|
||||
if (adc_cali_create_scheme_curve_fitting(&cal_cfg, &s_cali_handle) == ESP_OK)
|
||||
{
|
||||
s_cali_inited = true;
|
||||
ESP_LOGI(TAG_CM, "ADC calibration initialized (curve fitting)");
|
||||
}
|
||||
else
|
||||
{
|
||||
s_cali_inited = false;
|
||||
ESP_LOGW(TAG_CM, "ADC calibration not available; using raw-to-mV approximation");
|
||||
}
|
||||
}
|
||||
|
||||
int CurrentMonitor::read_mv_once()
|
||||
{
|
||||
if (!s_adc_handle)
|
||||
return 0;
|
||||
int raw = 0;
|
||||
if (adc_oneshot_read(s_adc_handle, s_channel, &raw) != ESP_OK)
|
||||
return 0;
|
||||
|
||||
int mv = 0;
|
||||
if (s_cali_inited)
|
||||
{
|
||||
if (adc_cali_raw_to_voltage(s_cali_handle, raw, &mv) != ESP_OK)
|
||||
mv = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Very rough fallback for 11dB attenuation
|
||||
// Typical full-scale ~2450mV at raw max 4095 (12-bit). IDF defaults may vary.
|
||||
mv = (raw * 2450) / 4095;
|
||||
}
|
||||
return mv;
|
||||
}
|
||||
|
||||
#endif // CONFIG_MONITORING_LED_CURRENT
|
||||
175
components/Monitoring/Monitoring/MonitoringManager.cpp
Normal file
175
components/Monitoring/Monitoring/MonitoringManager.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* @file MonitoringManager.cpp
|
||||
* @brief High-level Coordinator - Monitoring manager implementation
|
||||
*
|
||||
* Platform-independent monitoring coordination logic.
|
||||
* Manages BatteryMonitor and CurrentMonitor subsystems.
|
||||
*/
|
||||
|
||||
#include "MonitoringManager.hpp"
|
||||
#include <esp_log.h>
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static const char *TAG = "[MonitoringManager]";
|
||||
|
||||
void MonitoringManager::setup()
|
||||
{
|
||||
#if CONFIG_MONITORING_LED_CURRENT
|
||||
if (CurrentMonitor::isEnabled())
|
||||
{
|
||||
cm_.setup();
|
||||
ESP_LOGI(TAG, "LED current monitoring enabled. Interval=%dms, Samples=%d, Gain=%d, R=%dmΩ",
|
||||
CONFIG_MONITORING_LED_INTERVAL_MS,
|
||||
CONFIG_MONITORING_LED_SAMPLES,
|
||||
CONFIG_MONITORING_LED_GAIN,
|
||||
CONFIG_MONITORING_LED_SHUNT_MILLIOHM);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGI(TAG, "LED current monitoring not supported on this target");
|
||||
}
|
||||
#else
|
||||
ESP_LOGI(TAG, "LED current monitoring disabled by Kconfig");
|
||||
#endif
|
||||
|
||||
#if CONFIG_MONITORING_BATTERY_ENABLE
|
||||
if (BatteryMonitor::isEnabled())
|
||||
{
|
||||
bm_.setup();
|
||||
ESP_LOGI(TAG, "Battery monitoring enabled. Interval=%dms, Samples=%d, R-Top=%dΩ, R-Bottom=%dΩ",
|
||||
CONFIG_MONITORING_BATTERY_INTERVAL_MS,
|
||||
CONFIG_MONITORING_BATTERY_SAMPLES,
|
||||
CONFIG_MONITORING_BATTERY_DIVIDER_R_TOP_OHM,
|
||||
CONFIG_MONITORING_BATTERY_DIVIDER_R_BOTTOM_OHM);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGI(TAG, "Battery monitoring not supported on this target");
|
||||
}
|
||||
#else
|
||||
ESP_LOGI(TAG, "Battery monitoring disabled by Kconfig");
|
||||
#endif
|
||||
}
|
||||
|
||||
void MonitoringManager::start()
|
||||
{
|
||||
if (!isEnabled())
|
||||
{
|
||||
ESP_LOGI(TAG, "No monitoring features enabled, task not started");
|
||||
return;
|
||||
}
|
||||
|
||||
if (task_ == nullptr)
|
||||
{
|
||||
xTaskCreate(&MonitoringManager::taskEntry, "MonitoringTask", 2048, this, 1, &task_);
|
||||
ESP_LOGI(TAG, "Monitoring task started");
|
||||
}
|
||||
}
|
||||
|
||||
void MonitoringManager::stop()
|
||||
{
|
||||
if (task_)
|
||||
{
|
||||
TaskHandle_t toDelete = task_;
|
||||
task_ = nullptr;
|
||||
vTaskDelete(toDelete);
|
||||
ESP_LOGI(TAG, "Monitoring task stopped");
|
||||
}
|
||||
}
|
||||
|
||||
void MonitoringManager::taskEntry(void *arg)
|
||||
{
|
||||
static_cast<MonitoringManager *>(arg)->run();
|
||||
}
|
||||
|
||||
void MonitoringManager::run()
|
||||
{
|
||||
if (!isEnabled())
|
||||
{
|
||||
vTaskDelete(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
TickType_t now_tick = xTaskGetTickCount();
|
||||
|
||||
#if CONFIG_MONITORING_LED_CURRENT
|
||||
TickType_t next_tick_led = now_tick;
|
||||
const TickType_t led_period = pdMS_TO_TICKS(CONFIG_MONITORING_LED_INTERVAL_MS);
|
||||
#endif
|
||||
|
||||
#if CONFIG_MONITORING_BATTERY_ENABLE
|
||||
TickType_t next_tick_bat = now_tick;
|
||||
const TickType_t batt_period = pdMS_TO_TICKS(CONFIG_MONITORING_BATTERY_INTERVAL_MS);
|
||||
#endif
|
||||
|
||||
while (true)
|
||||
{
|
||||
now_tick = xTaskGetTickCount();
|
||||
TickType_t wait_ticks = pdMS_TO_TICKS(50); // Default wait time
|
||||
|
||||
#if CONFIG_MONITORING_LED_CURRENT
|
||||
if (CurrentMonitor::isEnabled() && now_tick >= next_tick_led)
|
||||
{
|
||||
float ma = cm_.getCurrentMilliAmps();
|
||||
last_current_ma_.store(ma);
|
||||
next_tick_led = now_tick + led_period;
|
||||
}
|
||||
if (CurrentMonitor::isEnabled())
|
||||
{
|
||||
TickType_t to_led = (next_tick_led > now_tick) ? (next_tick_led - now_tick) : 1;
|
||||
if (to_led < wait_ticks)
|
||||
{
|
||||
wait_ticks = to_led;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_MONITORING_BATTERY_ENABLE
|
||||
if (BatteryMonitor::isEnabled() && now_tick >= next_tick_bat)
|
||||
{
|
||||
const auto status = bm_.getBatteryStatus();
|
||||
if (status.valid)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(battery_mutex_);
|
||||
last_battery_status_ = status;
|
||||
}
|
||||
next_tick_bat = now_tick + batt_period;
|
||||
}
|
||||
if (BatteryMonitor::isEnabled())
|
||||
{
|
||||
TickType_t to_batt = (next_tick_bat > now_tick) ? (next_tick_bat - now_tick) : 1;
|
||||
if (to_batt < wait_ticks)
|
||||
{
|
||||
wait_ticks = to_batt;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (wait_ticks == 0)
|
||||
{
|
||||
wait_ticks = 1;
|
||||
}
|
||||
vTaskDelay(wait_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
float MonitoringManager::getCurrentMilliAmps() const
|
||||
{
|
||||
#if CONFIG_MONITORING_LED_CURRENT
|
||||
if (CurrentMonitor::isEnabled())
|
||||
return last_current_ma_.load();
|
||||
#endif
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
BatteryStatus MonitoringManager::getBatteryStatus() const
|
||||
{
|
||||
#if CONFIG_MONITORING_BATTERY_ENABLE
|
||||
if (BatteryMonitor::isEnabled())
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(battery_mutex_);
|
||||
return last_battery_status_;
|
||||
}
|
||||
#endif
|
||||
return {0, 0.0f, false};
|
||||
}
|
||||
@@ -1,18 +1,61 @@
|
||||
#pragma once
|
||||
/**
|
||||
* @file MonitoringManager.hpp
|
||||
* @brief High-level Coordinator - Combines Battery and Current monitoring
|
||||
*
|
||||
* Architecture:
|
||||
* +-----------------------+
|
||||
* | MonitoringManager | ← High-level coordinator (this file)
|
||||
* +-----------------------+
|
||||
* | BatteryMonitor | ← Battery logic: voltage, capacity, health
|
||||
* | CurrentMonitor | ← Current logic: power, instantaneous current
|
||||
* +-----------------------+
|
||||
* | AdcSampler | ← BSP: Unified ADC sampling interface
|
||||
* +-----------------------+
|
||||
* | ESP-IDF ADC HAL | ← Espressif official driver
|
||||
* +-----------------------+
|
||||
*/
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include "BatteryMonitor.hpp"
|
||||
#include "CurrentMonitor.hpp"
|
||||
|
||||
/**
|
||||
* @class MonitoringManager
|
||||
* @brief Coordinates battery and current monitoring subsystems
|
||||
*
|
||||
* This class manages the lifecycle and periodic sampling of both
|
||||
* BatteryMonitor and CurrentMonitor. It runs a background FreeRTOS task
|
||||
* to perform periodic measurements based on Kconfig intervals.
|
||||
*
|
||||
* Thread-safety: Uses atomic variables for cross-thread data access.
|
||||
*/
|
||||
class MonitoringManager
|
||||
{
|
||||
public:
|
||||
MonitoringManager() = default;
|
||||
~MonitoringManager() = default;
|
||||
|
||||
// Initialize monitoring subsystems based on Kconfig settings
|
||||
void setup();
|
||||
// Start the background monitoring task
|
||||
void start();
|
||||
// Stop the background monitoring task
|
||||
void stop();
|
||||
|
||||
// Latest filtered current in mA
|
||||
float getCurrentMilliAmps() const { return last_current_ma_.load(); }
|
||||
float getCurrentMilliAmps() const;
|
||||
// Get complete battery status (voltage + percentage + validity)
|
||||
BatteryStatus getBatteryStatus() const;
|
||||
|
||||
// Check if any monitoring feature is enabled
|
||||
static constexpr bool isEnabled()
|
||||
{
|
||||
return CurrentMonitor::isEnabled() || BatteryMonitor::isEnabled();
|
||||
}
|
||||
|
||||
private:
|
||||
static void taskEntry(void *arg);
|
||||
@@ -20,5 +63,9 @@ private:
|
||||
|
||||
TaskHandle_t task_{nullptr};
|
||||
std::atomic<float> last_current_ma_{0.0f};
|
||||
BatteryStatus last_battery_status_{0, 0.0f, false};
|
||||
mutable std::mutex battery_mutex_; // Protect non-atomic BatteryStatus
|
||||
|
||||
CurrentMonitor cm_;
|
||||
BatteryMonitor bm_;
|
||||
};
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#include "MonitoringManager.hpp"
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG_MM = "[MonitoringManager]";
|
||||
|
||||
void MonitoringManager::setup()
|
||||
{
|
||||
ESP_LOGI(TAG_MM, "Monitoring disabled by Kconfig");
|
||||
}
|
||||
|
||||
void MonitoringManager::start()
|
||||
{
|
||||
}
|
||||
|
||||
void MonitoringManager::stop()
|
||||
{
|
||||
}
|
||||
|
||||
void MonitoringManager::taskEntry(void *arg)
|
||||
{
|
||||
}
|
||||
|
||||
void MonitoringManager::run()
|
||||
{
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#include "MonitoringManager.hpp"
|
||||
#include <esp_log.h>
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static const char *TAG_MM = "[MonitoringManager]";
|
||||
|
||||
void MonitoringManager::setup()
|
||||
{
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
cm_.setup();
|
||||
ESP_LOGI(TAG_MM, "Monitoring enabled. Interval=%dms, Samples=%d, Gain=%d, R=%dmΩ",
|
||||
CONFIG_MONITORING_LED_INTERVAL_MS,
|
||||
CONFIG_MONITORING_LED_SAMPLES,
|
||||
CONFIG_MONITORING_LED_GAIN,
|
||||
CONFIG_MONITORING_LED_SHUNT_MILLIOHM);
|
||||
#else
|
||||
ESP_LOGI(TAG_MM, "Monitoring disabled by Kconfig");
|
||||
#endif
|
||||
}
|
||||
|
||||
void MonitoringManager::start()
|
||||
{
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
if (task_ == nullptr)
|
||||
{
|
||||
xTaskCreate(&MonitoringManager::taskEntry, "MonitoringTask", 2048, this, 1, &task_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MonitoringManager::stop()
|
||||
{
|
||||
if (task_)
|
||||
{
|
||||
TaskHandle_t toDelete = task_;
|
||||
task_ = nullptr;
|
||||
vTaskDelete(toDelete);
|
||||
}
|
||||
}
|
||||
|
||||
void MonitoringManager::taskEntry(void *arg)
|
||||
{
|
||||
static_cast<MonitoringManager *>(arg)->run();
|
||||
}
|
||||
|
||||
void MonitoringManager::run()
|
||||
{
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
while (true)
|
||||
{
|
||||
float ma = cm_.pollAndGetMilliAmps();
|
||||
last_current_ma_.store(ma);
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_MONITORING_LED_INTERVAL_MS));
|
||||
}
|
||||
#else
|
||||
vTaskDelete(nullptr);
|
||||
#endif
|
||||
}
|
||||
@@ -204,7 +204,9 @@ menu "OpenIris: Monitoring"
|
||||
range 0 48
|
||||
default 3
|
||||
help
|
||||
GPIO connected to the current sense input (ADC1 on ESP32-S3: 1..10 supported).
|
||||
GPIO connected to the current sense input. On ESP32-S3 ADC1 channels
|
||||
0..9 map to GPIO1..10; On ESP32 ADC1 channels 0..3 map to GPIO36..39,
|
||||
channels 4..7 map to GPIO32..35; adjust if your board uses a different pin.
|
||||
|
||||
config MONITORING_LED_GAIN
|
||||
int "Analog front-end gain/divider"
|
||||
@@ -238,4 +240,56 @@ menu "OpenIris: Monitoring"
|
||||
help
|
||||
Period between samples when background monitoring is active.
|
||||
|
||||
config MONITORING_BATTERY_ENABLE
|
||||
bool "Enable battery voltage monitoring"
|
||||
default n
|
||||
help
|
||||
Enables an ADC-based readout of the lithium battery voltage so the software
|
||||
can report remaining charge in commands or REST endpoints.
|
||||
|
||||
config MONITORING_BATTERY_ADC_GPIO
|
||||
int "ADC GPIO for battery voltage"
|
||||
depends on MONITORING_BATTERY_ENABLE
|
||||
range 0 48
|
||||
default 1
|
||||
help
|
||||
GPIO that is wired to the battery sense divider. On ESP32-S3 ADC1 channels
|
||||
0..9 map to GPIO1..10; On ESP32 ADC1 channels 0..3 map to GPIO36..39;
|
||||
channels 4..7 map to GPIO32..35; adjust if your board uses a different pin.
|
||||
|
||||
config MONITORING_BATTERY_DIVIDER_R_TOP_OHM
|
||||
int "Battery divider top resistor (ohms)"
|
||||
depends on MONITORING_BATTERY_ENABLE
|
||||
range 1 10000000
|
||||
default 10000
|
||||
help
|
||||
The resistor from battery positive to ADC input in the voltage divider.
|
||||
Set together with MONITORING_BATTERY_DIVIDER_R_BOTTOM_OHM to match your
|
||||
board's sense network. Effective scale = 1 + R_top / R_bottom.
|
||||
|
||||
config MONITORING_BATTERY_DIVIDER_R_BOTTOM_OHM
|
||||
int "Battery divider bottom resistor (ohms)"
|
||||
depends on MONITORING_BATTERY_ENABLE
|
||||
range 1 10000000
|
||||
default 10000
|
||||
help
|
||||
The resistor from ADC input to ground in the voltage divider. For a 1:1
|
||||
divider with 10k/10k, leave the defaults (10 kΩ each).
|
||||
|
||||
config MONITORING_BATTERY_SAMPLES
|
||||
int "Battery filter window size (samples)"
|
||||
depends on MONITORING_BATTERY_ENABLE
|
||||
range 1 200
|
||||
default 10
|
||||
help
|
||||
Moving-average window length for battery voltage filtering.
|
||||
|
||||
config MONITORING_BATTERY_INTERVAL_MS
|
||||
int "Battery sampling interval (ms)"
|
||||
depends on MONITORING_BATTERY_ENABLE
|
||||
range 10 60000
|
||||
default 1000
|
||||
help
|
||||
Period between background battery voltage samples.
|
||||
|
||||
endmenu
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <RestAPI.hpp>
|
||||
#include <main_globals.hpp>
|
||||
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
#if CONFIG_MONITORING_LED_CURRENT || CONFIG_MONITORING_BATTERY_ENABLE
|
||||
#include <MonitoringManager.hpp>
|
||||
#endif
|
||||
|
||||
@@ -72,7 +72,7 @@ UVCStreamManager uvcStream;
|
||||
|
||||
auto ledManager = std::make_shared<LEDManager>(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue, deviceConfig);
|
||||
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
#if CONFIG_MONITORING_LED_CURRENT || CONFIG_MONITORING_BATTERY_ENABLE
|
||||
std::shared_ptr<MonitoringManager> monitoringManager = std::make_shared<MonitoringManager>();
|
||||
#endif
|
||||
|
||||
@@ -273,7 +273,7 @@ extern "C" void app_main(void)
|
||||
#endif
|
||||
dependencyRegistry->registerService<LEDManager>(DependencyType::led_manager, ledManager);
|
||||
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
#if CONFIG_MONITORING_LED_CURRENT || CONFIG_MONITORING_BATTERY_ENABLE
|
||||
dependencyRegistry->registerService<MonitoringManager>(DependencyType::monitoring_manager, monitoringManager);
|
||||
#endif
|
||||
|
||||
@@ -285,7 +285,7 @@ extern "C" void app_main(void)
|
||||
deviceConfig->load();
|
||||
ledManager->setup();
|
||||
|
||||
#ifdef CONFIG_MONITORING_LED_CURRENT
|
||||
#if CONFIG_MONITORING_LED_CURRENT || CONFIG_MONITORING_BATTERY_ENABLE
|
||||
monitoringManager->setup();
|
||||
monitoringManager->start();
|
||||
#endif
|
||||
|
||||
@@ -232,6 +232,19 @@ def get_led_current(device: OpenIrisDevice) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def get_battery_status(device: OpenIrisDevice) -> dict:
|
||||
response = device.send_command("get_battery_status")
|
||||
if has_command_failed(response):
|
||||
print(f"❌ Failed to get battery status: {response}")
|
||||
return {"voltage_mv": "unknown", "percentage": "unknown"}
|
||||
|
||||
data = response["results"][0]["result"]["data"]
|
||||
return {
|
||||
"voltage_mv": data.get("voltage_mv", "unknown"),
|
||||
"percentage": data.get("percentage", "unknown"),
|
||||
}
|
||||
|
||||
|
||||
def configure_device_name(device: OpenIrisDevice, *args, **kwargs):
|
||||
current_name = get_mdns_name(device)
|
||||
print(f"\n📍 Current device name: {current_name['name']} \n")
|
||||
@@ -340,6 +353,7 @@ def get_settings_summary(device: OpenIrisDevice, *args, **kwargs):
|
||||
("Info", get_device_info),
|
||||
("LED", get_led_duty_cycle),
|
||||
("Current", get_led_current),
|
||||
("Battery", get_battery_status),
|
||||
("Mode", get_device_mode),
|
||||
("WiFi", get_wifi_status),
|
||||
]
|
||||
@@ -357,6 +371,11 @@ def get_settings_summary(device: OpenIrisDevice, *args, **kwargs):
|
||||
led_current_ma = current_section.get("led_current_ma")
|
||||
print(f"🔌 LED Current: {led_current_ma} mA")
|
||||
|
||||
battery = summary.get("Battery", {})
|
||||
voltage_mv = battery.get("voltage_mv")
|
||||
percentage = battery.get("percentage")
|
||||
print(f"🔋 Battery: {voltage_mv} mV | {percentage} %")
|
||||
|
||||
advertised_name_data = summary.get("AdvertisedName", {})
|
||||
advertised_name = advertised_name_data.get("name")
|
||||
print(f"📛 Name: {advertised_name}")
|
||||
|
||||
Reference in New Issue
Block a user