- Updated README to reflect changes in device mode terminology from "Auto" to "Setup".

- Changed configuration macros from CONFIG_GENERAL_DEFAULT_WIRED_MODE to CONFIG_GENERAL_INCLUDE_UVC_MODE across multiple files.
- Introduced new command for retrieving LED current in CommandManager.
- Added MonitoringManager and CurrentMonitor classes to handle LED current monitoring.
- Updated Kconfig to include options for LED current monitoring.
- Modified main application logic to integrate MonitoringManager and handle new device modes.
- Adjusted CMakeLists and source files to include new monitoring components.
This commit is contained in:
PhosphorosVR
2025-09-05 01:08:11 +02:00
parent d73958530a
commit 83d7805e9e
25 changed files with 499 additions and 66 deletions

View File

@@ -0,0 +1,6 @@
idf_component_register(SRCS
"Monitoring/CurrentMonitor.cpp"
"Monitoring/MonitoringManager.cpp"
INCLUDE_DIRS "Monitoring"
REQUIRES driver esp_adc Helpers
)

View File

@@ -0,0 +1,179 @@
#include "CurrentMonitor.hpp"
#include <esp_log.h>
#include <cmath>
#if 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()
{
#if CONFIG_MONITORING_LED_CURRENT
samples_.assign(CONFIG_MONITORING_LED_SAMPLES, 0);
#endif
}
void CurrentMonitor::setup()
{
#if CONFIG_MONITORING_LED_CURRENT
init_adc();
#else
ESP_LOGI(TAG_CM, "LED current monitoring disabled");
#endif
}
float CurrentMonitor::getCurrentMilliAmps() const
{
#if 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()
{
#if 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
}
#if 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

View File

@@ -0,0 +1,45 @@
#pragma once
#include <cstdint>
#include <memory>
#include <vector>
#include "sdkconfig.h"
class CurrentMonitor {
public:
CurrentMonitor();
~CurrentMonitor() = default;
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();
// Whether monitoring is enabled by Kconfig
static constexpr bool isEnabled()
{
#ifdef CONFIG_MONITORING_LED_CURRENT
return true;
#else
return false;
#endif
}
private:
#if 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;
};

View File

@@ -0,0 +1,58 @@
#include "MonitoringManager.hpp"
#include <esp_log.h>
#include "sdkconfig.h"
static const char* TAG_MM = "[MonitoringManager]";
void MonitoringManager::setup()
{
#if 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()
{
#if 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()
{
#if 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
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <atomic>
#include "CurrentMonitor.hpp"
class MonitoringManager {
public:
MonitoringManager() = default;
~MonitoringManager() = default;
void setup();
void start();
void stop();
// Latest filtered current in mA
float getCurrentMilliAmps() const { return last_current_ma_.load(); }
private:
static void taskEntry(void* arg);
void run();
TaskHandle_t task_{nullptr};
std::atomic<float> last_current_ma_{0.0f};
CurrentMonitor cm_;
};