diff --git a/README.md b/README.md index ac8bbb3..b286a96 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Examples: What the CLI can do: - Wi‑Fi menu: automatic (scan → pick → password → connect → wait for IP) or manual (scan, show, configure, connect, status) - Set MDNS/Device name (also used for the UVC device name) -- Switch mode (Wi‑Fi / UVC / Auto) +- Switch mode (Wi‑Fi / UVC / Setup) - Adjust LED PWM - Show a Settings Summary (MAC, Wi‑Fi status, mode, PWM, …) - View logs diff --git a/bootloader_components/boot_hooks.c b/bootloader_components/boot_hooks.c index c0d7cc2..8f00ddb 100644 --- a/bootloader_components/boot_hooks.c +++ b/bootloader_components/boot_hooks.c @@ -1,6 +1,6 @@ // source: https://github.com/espressif/esp-iot-solution/blob/4730d91db70df7e6e0a3191d725ab1c5f98ff9ce/examples/usb/device/usb_webcam/bootloader_components/boot_hooks/boot_hooks.c -#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE +#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE #include "esp_log.h" #include "soc/rtc_cntl_struct.h" #include "soc/usb_serial_jtag_reg.h" diff --git a/components/CameraManager/CameraManager/CameraManager.cpp b/components/CameraManager/CameraManager/CameraManager.cpp index 26bd8b5..6efdb3e 100644 --- a/components/CameraManager/CameraManager/CameraManager.cpp +++ b/components/CameraManager/CameraManager/CameraManager.cpp @@ -48,7 +48,7 @@ void CameraManager::setupCameraPinout() ESP_LOGI(CAMERA_MANAGER_TAG, "CAM_BOARD"); #endif -#if CONFIG_GENERAL_DEFAULT_WIRED_MODE +#if CONFIG_GENERAL_INCLUDE_UVC_MODE xclk_freq_hz = CONFIG_CAMERA_USB_XCLK_FREQ; #endif @@ -196,7 +196,7 @@ bool CameraManager::setupCamera() return false; } -#if CONFIG_GENERAL_DEFAULT_WIRED_MODE +#if CONFIG_GENERAL_INCLUDE_UVC_MODE const auto temp_sensor = esp_camera_sensor_get(); // Thanks to lick_it, we discovered that OV5640 likes to overheat when diff --git a/components/CommandManager/CMakeLists.txt b/components/CommandManager/CMakeLists.txt index e4b61ca..dc8ed4e 100644 --- a/components/CommandManager/CMakeLists.txt +++ b/components/CommandManager/CMakeLists.txt @@ -11,5 +11,5 @@ idf_component_register( INCLUDE_DIRS "CommandManager" "CommandManager/commands" - REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers LEDManager + REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers LEDManager Monitoring ) \ No newline at end of file diff --git a/components/CommandManager/CommandManager/CommandManager.cpp b/components/CommandManager/CommandManager/CommandManager.cpp index 9787d3d..4ffed9d 100644 --- a/components/CommandManager/CommandManager/CommandManager.cpp +++ b/components/CommandManager/CommandManager/CommandManager.cpp @@ -26,6 +26,7 @@ std::unordered_map commandTypeMap = { {"set_led_duty_cycle", CommandType::SET_LED_DUTY_CYCLE}, {"get_led_duty_cycle", CommandType::GET_LED_DUTY_CYCLE}, {"get_serial", CommandType::GET_SERIAL}, + {"get_led_current", CommandType::GET_LED_CURRENT}, }; std::function CommandManager::createCommand(const CommandType type, std::string_view json) const @@ -103,6 +104,9 @@ std::function CommandManager::createCommand(const CommandType t case CommandType::GET_SERIAL: return [this] { return getSerialNumberCommand(this->registry); }; + case CommandType::GET_LED_CURRENT: + return [this] + { return getLEDCurrentCommand(this->registry); }; default: return nullptr; } diff --git a/components/CommandManager/CommandManager/CommandManager.hpp b/components/CommandManager/CommandManager/CommandManager.hpp index a4fde07..9a5fd7d 100644 --- a/components/CommandManager/CommandManager/CommandManager.hpp +++ b/components/CommandManager/CommandManager/CommandManager.hpp @@ -47,6 +47,7 @@ enum class CommandType SET_LED_DUTY_CYCLE, GET_LED_DUTY_CYCLE, GET_SERIAL, + GET_LED_CURRENT, }; class CommandManager diff --git a/components/CommandManager/CommandManager/DependencyRegistry.hpp b/components/CommandManager/CommandManager/DependencyRegistry.hpp index b0758dd..28d3df4 100644 --- a/components/CommandManager/CommandManager/DependencyRegistry.hpp +++ b/components/CommandManager/CommandManager/DependencyRegistry.hpp @@ -9,7 +9,8 @@ enum class DependencyType project_config, camera_manager, wifi_manager, - led_manager + led_manager, + monitoring_manager }; class DependencyRegistry diff --git a/components/CommandManager/CommandManager/commands/device_commands.cpp b/components/CommandManager/CommandManager/commands/device_commands.cpp index ac9e294..751af1d 100644 --- a/components/CommandManager/CommandManager/commands/device_commands.cpp +++ b/components/CommandManager/CommandManager/commands/device_commands.cpp @@ -1,5 +1,6 @@ #include "device_commands.hpp" #include "LEDManager.hpp" +#include "MonitoringManager.hpp" #include "esp_mac.h" #include @@ -171,9 +172,9 @@ CommandResult switchModeCommand(std::shared_ptr registry, st { newMode = StreamingMode::WIFI; } - else if (strcmp(modeStr, "auto") == 0) + else if (strcmp(modeStr, "setup") == 0 || strcmp(modeStr, "auto") == 0) { - newMode = StreamingMode::AUTO; + newMode = StreamingMode::SETUP; } else { @@ -203,8 +204,8 @@ CommandResult getDeviceModeCommand(std::shared_ptr registry) case StreamingMode::WIFI: modeStr = "WiFi"; break; - case StreamingMode::AUTO: - modeStr = "Auto"; + case StreamingMode::SETUP: + modeStr = "Setup"; break; } @@ -231,3 +232,19 @@ CommandResult getSerialNumberCommand(std::shared_ptr /*regis auto result = std::format("{{ \"serial\": \"{}\", \"mac\": \"{}\" }}", serial_no_sep, mac_colon); return CommandResult::getSuccessResult(result); } + +CommandResult getLEDCurrentCommand(std::shared_ptr registry) +{ +#if CONFIG_MONITORING_LED_CURRENT + auto mon = registry->resolve(DependencyType::monitoring_manager); + if (!mon) + { + return CommandResult::getErrorResult("MonitoringManager unavailable"); + } + float ma = mon->getCurrentMilliAmps(); + auto result = std::format("{{ \"led_current_ma\": {:.3f} }}", static_cast(ma)); + return CommandResult::getSuccessResult(result); +#else + return CommandResult::getErrorResult("Monitoring disabled"); +#endif +} diff --git a/components/CommandManager/CommandManager/commands/device_commands.hpp b/components/CommandManager/CommandManager/commands/device_commands.hpp index 06e7751..a01798d 100644 --- a/components/CommandManager/CommandManager/commands/device_commands.hpp +++ b/components/CommandManager/CommandManager/commands/device_commands.hpp @@ -22,4 +22,7 @@ CommandResult switchModeCommand(std::shared_ptr registry, st CommandResult getDeviceModeCommand(std::shared_ptr registry); -CommandResult getSerialNumberCommand(std::shared_ptr registry); \ No newline at end of file +CommandResult getSerialNumberCommand(std::shared_ptr registry); + +// Monitoring +CommandResult getLEDCurrentCommand(std::shared_ptr registry); \ No newline at end of file diff --git a/components/Monitoring/CMakeLists.txt b/components/Monitoring/CMakeLists.txt new file mode 100644 index 0000000..fd6f78f --- /dev/null +++ b/components/Monitoring/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register(SRCS + "Monitoring/CurrentMonitor.cpp" + "Monitoring/MonitoringManager.cpp" + INCLUDE_DIRS "Monitoring" + REQUIRES driver esp_adc Helpers +) diff --git a/components/Monitoring/Monitoring/CurrentMonitor.cpp b/components/Monitoring/Monitoring/CurrentMonitor.cpp new file mode 100644 index 0000000..024d767 --- /dev/null +++ b/components/Monitoring/Monitoring/CurrentMonitor.cpp @@ -0,0 +1,179 @@ +#include "CurrentMonitor.hpp" +#include +#include + +#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(filtered_mv_)) / static_cast(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(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(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 diff --git a/components/Monitoring/Monitoring/CurrentMonitor.hpp b/components/Monitoring/Monitoring/CurrentMonitor.hpp new file mode 100644 index 0000000..3285f4e --- /dev/null +++ b/components/Monitoring/Monitoring/CurrentMonitor.hpp @@ -0,0 +1,45 @@ +#pragma once +#include +#include +#include +#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 samples_; + size_t sample_idx_ = 0; + size_t sample_count_ = 0; +}; diff --git a/components/Monitoring/Monitoring/MonitoringManager.cpp b/components/Monitoring/Monitoring/MonitoringManager.cpp new file mode 100644 index 0000000..1a371b5 --- /dev/null +++ b/components/Monitoring/Monitoring/MonitoringManager.cpp @@ -0,0 +1,58 @@ +#include "MonitoringManager.hpp" +#include +#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(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 +} diff --git a/components/Monitoring/Monitoring/MonitoringManager.hpp b/components/Monitoring/Monitoring/MonitoringManager.hpp new file mode 100644 index 0000000..6b96688 --- /dev/null +++ b/components/Monitoring/Monitoring/MonitoringManager.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include +#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 last_current_ma_{0.0f}; + CurrentMonitor cm_; +}; diff --git a/components/ProjectConfig/ProjectConfig/Models.hpp b/components/ProjectConfig/ProjectConfig/Models.hpp index e400f14..9261ade 100644 --- a/components/ProjectConfig/ProjectConfig/Models.hpp +++ b/components/ProjectConfig/ProjectConfig/Models.hpp @@ -23,7 +23,7 @@ struct BaseConfigModel enum class StreamingMode { - AUTO, + SETUP, UVC, WIFI, }; @@ -31,18 +31,18 @@ enum class StreamingMode struct DeviceMode_t : BaseConfigModel { StreamingMode mode; - explicit DeviceMode_t(Preferences *pref) : BaseConfigModel(pref), mode(StreamingMode::AUTO) {} + explicit DeviceMode_t(Preferences *pref) : BaseConfigModel(pref), mode(StreamingMode::SETUP) {} void load() { - // Default mode can be controlled via sdkconfig: - // - If CONFIG_START_IN_UVC_MODE is enabled, default to UVC - // - Otherwise default to AUTO + // Default mode can be controlled via sdkconfig: + // - If CONFIG_START_IN_UVC_MODE is enabled, default to UVC + // - Otherwise default to SETUP int default_mode = #if CONFIG_START_IN_UVC_MODE static_cast(StreamingMode::UVC); #else - static_cast(StreamingMode::AUTO); + static_cast(StreamingMode::SETUP); #endif int stored_mode = this->pref->getInt("mode", default_mode); diff --git a/components/UVCStream/UVCStream/UVCStream.cpp b/components/UVCStream/UVCStream/UVCStream.cpp index 473fc1e..4fcc853 100644 --- a/components/UVCStream/UVCStream/UVCStream.cpp +++ b/components/UVCStream/UVCStream/UVCStream.cpp @@ -159,7 +159,7 @@ static void UVCStreamHelpers::camera_fb_return_cb(uvc_fb_t *fb, void *cb_ctx) esp_err_t UVCStreamManager::setup() { -#ifndef CONFIG_GENERAL_DEFAULT_WIRED_MODE +#ifndef CONFIG_GENERAL_INCLUDE_UVC_MODE ESP_LOGE(UVC_STREAM_TAG, "The board does not support UVC, please, setup WiFi connection."); return ESP_FAIL; #endif diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 0fcf058..1528c3f 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -15,7 +15,7 @@ menu "OpenIris: General Configuration" When enabled, the default device streaming mode will be UVC unless overridden by a saved preference. When disabled, the default mode is AUTO. - config GENERAL_DEFAULT_WIRED_MODE + config GENERAL_INCLUDE_UVC_MODE bool "Wired mode" default false help @@ -114,4 +114,54 @@ menu "OpenIris: LED Configuration" Duty cycle of the PWM signal for external IR LEDs, in percent. 0 means always off, 100 means always on. +endmenu + +menu "OpenIris: Monitoring" + + config MONITORING_LED_CURRENT + bool "Enable LED current monitoring" + default y + help + Enable sampling LED current via ADC and report it over commands. + + config MONITORING_LED_ADC_GPIO + int "ADC GPIO for LED current sense" + depends on MONITORING_LED_CURRENT + range 0 48 + default 3 + help + GPIO connected to the current sense input (ADC1 on ESP32-S3: 1..10 supported). + + config MONITORING_LED_GAIN + int "Analog front-end gain/divider" + depends on MONITORING_LED_CURRENT + range 1 1024 + default 11 + help + Divider or amplifier gain between shunt and ADC. The measured mV are divided by this value. + + config MONITORING_LED_SHUNT_MILLIOHM + int "Shunt resistance (milli-ohms)" + depends on MONITORING_LED_CURRENT + range 1 1000000 + default 22000 + help + Shunt resistor value in milli-ohms. Current[mA] = 1000 * Vshunt[mV] / R[mΩ]. + + config MONITORING_LED_SAMPLES + int "Filter window size (samples)" + depends on MONITORING_LED_CURRENT + range 1 200 + default 10 + help + Moving-average window length for voltage filtering. + + config MONITORING_LED_INTERVAL_MS + int "Sampling interval (ms)" + depends on MONITORING_LED_CURRENT + range 10 60000 + default 500 + help + Period between samples when background monitoring is active. + endmenu \ No newline at end of file diff --git a/main/openiris_main.cpp b/main/openiris_main.cpp index fdc49f5..5daefa4 100644 --- a/main/openiris_main.cpp +++ b/main/openiris_main.cpp @@ -21,8 +21,9 @@ #include #include #include +#include -#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE +#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE #include #endif @@ -52,12 +53,13 @@ StreamServer streamServer(80, stateManager); auto *restAPI = new RestAPI("http://0.0.0.0:81", commandManager); -#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE +#ifdef CONFIG_GENERAL_INCLUDE_UVC_MODE UVCStreamManager uvcStream; #endif auto ledManager = std::make_shared(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue, deviceConfig); auto *serialManager = new SerialManager(commandManager, &timerHandle, deviceConfig); +MonitoringManager monitoringManager; void startWiFiMode(bool shouldCloseSerialManager); void startWiredMode(bool shouldCloseSerialManager); @@ -81,14 +83,14 @@ int websocket_logger(const char *format, va_list args) void launch_streaming() { - // Note, when switching and later right away activating UVC mode when we were previously in WiFi or Auto mode, the WiFi + // Note, when switching and later right away activating UVC mode when we were previously in WiFi or Setup mode, the WiFi // utilities will still be running since we've launched them with startAutoMode() -> startWiFiMode() // we could add detection of this case, but it's probably not worth it since the next start of the device literally won't launch them // and we're telling folks to just reboot the device anyway // same case goes for when switching from UVC to WiFi StreamingMode deviceMode = deviceConfig->getDeviceMode(); - // if we've changed the mode from auto to something else, we can clean up serial manager + // if we've changed the mode from setup to something else, we can clean up serial manager // either the API endpoints or CDC will take care of further configuration if (deviceMode == StreamingMode::WIFI) { @@ -98,10 +100,10 @@ void launch_streaming() { startWiredMode(true); } - else if (deviceMode == StreamingMode::AUTO) + else if (deviceMode == StreamingMode::SETUP) { - // we're still in auto, the user didn't select anything yet, let's give a bit of time for them to make a choice - ESP_LOGI("[MAIN]", "No mode was selected, staying in AUTO mode. WiFi streaming will be enabled still. \nPlease select another mode if you'd like."); + // we're still in setup, the user didn't select anything yet, let's give a bit of time for them to make a choice + ESP_LOGI("[MAIN]", "No mode was selected, staying in SETUP mode. WiFi streaming will be enabled still. \nPlease select another mode if you'd like."); } else { @@ -150,7 +152,7 @@ void force_activate_streaming() void startWiredMode(bool shouldCloseSerialManager) { -#ifndef CONFIG_GENERAL_DEFAULT_WIRED_MODE +#ifndef CONFIG_GENERAL_INCLUDE_UVC_MODE ESP_LOGE("[MAIN]", "UVC mode selected but the board likely does not support it."); ESP_LOGI("[MAIN]", "Falling back to WiFi mode if credentials available"); deviceMode = StreamingMode::WIFI; @@ -220,7 +222,7 @@ void startWiFiMode(bool shouldCloseSerialManager) void startSetupMode() { - // If we're in an auto mode - Device starts with a 20-second delay before deciding on what to do + // If we're in SETUP mode - Device starts with a 20-second delay before deciding on what to do // during this time we await any commands ESP_LOGI("[MAIN]", "====================================="); ESP_LOGI("[MAIN]", "STARTUP: 20-SECOND DELAY MODE ACTIVE"); @@ -247,6 +249,7 @@ extern "C" void app_main(void) dependencyRegistry->registerService(DependencyType::camera_manager, cameraHandler); dependencyRegistry->registerService(DependencyType::wifi_manager, wifiManager); dependencyRegistry->registerService(DependencyType::led_manager, ledManager); + dependencyRegistry->registerService(DependencyType::monitoring_manager, std::shared_ptr(&monitoringManager, [](MonitoringManager*){})); // add endpoint to check firmware version // add firmware version somewhere @@ -259,6 +262,8 @@ extern "C" void app_main(void) initNVSStorage(); deviceConfig->load(); ledManager->setup(); + monitoringManager.setup(); + monitoringManager.start(); xTaskCreate( HandleStateManagerTask, diff --git a/sdkconfig b/sdkconfig index acaf9ba..53c6bce 100644 --- a/sdkconfig +++ b/sdkconfig @@ -514,9 +514,9 @@ CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y # CONFIG_ESPTOOLPY_NO_STUB is not set # CONFIG_ESPTOOLPY_OCT_FLASH is not set CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=y -CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set # CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set -# CONFIG_ESPTOOLPY_FLASHMODE_DIO is not set +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y # CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y CONFIG_ESPTOOLPY_FLASHMODE="dio" @@ -527,13 +527,13 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESPTOOLPY_FLASHFREQ="80m" # CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y -# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y # CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE="4MB" +CONFIG_ESPTOOLPY_FLASHSIZE="8MB" # CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set CONFIG_ESPTOOLPY_BEFORE_RESET=y # CONFIG_ESPTOOLPY_BEFORE_NORESET is not set @@ -571,7 +571,7 @@ CONFIG_ENV_GPIO_OUT_RANGE_MAX=48 # OpenIris: General Configuration # # CONFIG_START_IN_UVC_MODE is not set -CONFIG_GENERAL_DEFAULT_WIRED_MODE=y +CONFIG_GENERAL_INCLUDE_UVC_MODE=y CONFIG_GENERAL_UVC_DELAY=30 # end of OpenIris: General Configuration @@ -596,32 +596,43 @@ CONFIG_WIFI_AP_PASSWORD="12345678" # OpenIris: LED Configuration # CONFIG_LED_BLINK_GPIO=8 -CONFIG_LED_EXTERNAL_GPIO=1 +CONFIG_LED_EXTERNAL_GPIO=9 CONFIG_LED_EXTERNAL_CONTROL=y -CONFIG_LED_EXTERNAL_PWM_FREQ=5000 +CONFIG_LED_EXTERNAL_PWM_FREQ=20000 CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE=100 # end of OpenIris: LED Configuration +# +# OpenIris: Monitoring +# +CONFIG_MONITORING_LED_CURRENT=y +CONFIG_MONITORING_LED_ADC_GPIO=3 +CONFIG_MONITORING_LED_GAIN=11 +CONFIG_MONITORING_LED_SHUNT_MILLIOHM=22000 +CONFIG_MONITORING_LED_SAMPLES=10 +CONFIG_MONITORING_LED_INTERVAL_MS=500 +# end of OpenIris: Monitoring + # # Camera sensor pinout configuration # -CONFIG_CAMERA_MODULE_NAME="SWROOM_BABBLE_S3" +CONFIG_CAMERA_MODULE_NAME="FaceFocusVR_Face" CONFIG_PWDN_GPIO_NUM=-1 CONFIG_RESET_GPIO_NUM=-1 -CONFIG_XCLK_GPIO_NUM=4 -CONFIG_SIOD_GPIO_NUM=48 -CONFIG_SIOC_GPIO_NUM=47 -CONFIG_Y9_GPIO_NUM=13 -CONFIG_Y8_GPIO_NUM=5 -CONFIG_Y7_GPIO_NUM=6 -CONFIG_Y6_GPIO_NUM=15 -CONFIG_Y5_GPIO_NUM=17 -CONFIG_Y4_GPIO_NUM=8 -CONFIG_Y3_GPIO_NUM=18 -CONFIG_Y2_GPIO_NUM=16 -CONFIG_VSYNC_GPIO_NUM=21 -CONFIG_HREF_GPIO_NUM=14 -CONFIG_PCLK_GPIO_NUM=7 +CONFIG_XCLK_GPIO_NUM=10 +CONFIG_SIOD_GPIO_NUM=40 +CONFIG_SIOC_GPIO_NUM=39 +CONFIG_Y9_GPIO_NUM=48 +CONFIG_Y8_GPIO_NUM=11 +CONFIG_Y7_GPIO_NUM=12 +CONFIG_Y6_GPIO_NUM=14 +CONFIG_Y5_GPIO_NUM=16 +CONFIG_Y4_GPIO_NUM=18 +CONFIG_Y3_GPIO_NUM=17 +CONFIG_Y2_GPIO_NUM=15 +CONFIG_VSYNC_GPIO_NUM=38 +CONFIG_HREF_GPIO_NUM=47 +CONFIG_PCLK_GPIO_NUM=13 # end of Camera sensor pinout configuration # @@ -1156,21 +1167,19 @@ CONFIG_SPIRAM=y # # SPI RAM config # -CONFIG_SPIRAM_MODE_QUAD=y -# CONFIG_SPIRAM_MODE_OCT is not set +# CONFIG_SPIRAM_MODE_QUAD is not set +CONFIG_SPIRAM_MODE_OCT=y CONFIG_SPIRAM_TYPE_AUTO=y -# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set -# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set # CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set CONFIG_SPIRAM_CLK_IO=30 CONFIG_SPIRAM_CS_IO=26 # CONFIG_SPIRAM_XIP_FROM_PSRAM is not set # CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set # CONFIG_SPIRAM_RODATA is not set -# CONFIG_SPIRAM_SPEED_120M is not set CONFIG_SPIRAM_SPEED_80M=y # CONFIG_SPIRAM_SPEED_40M is not set CONFIG_SPIRAM_SPEED=80 +# CONFIG_SPIRAM_ECC_ENABLE is not set CONFIG_SPIRAM_BOOT_INIT=y CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y # CONFIG_SPIRAM_IGNORE_NOTFOUND is not set @@ -2387,9 +2396,9 @@ CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y CONFIG_LOG_BOOTLOADER_LEVEL=3 # CONFIG_APP_ROLLBACK_ENABLE is not set # CONFIG_FLASH_ENCRYPTION_ENABLED is not set -CONFIG_FLASHMODE_QIO=y +# CONFIG_FLASHMODE_QIO is not set # CONFIG_FLASHMODE_QOUT is not set -# CONFIG_FLASHMODE_DIO is not set +CONFIG_FLASHMODE_DIO=y # CONFIG_FLASHMODE_DOUT is not set CONFIG_MONITOR_BAUD=115200 # CONFIG_OPTIMIZATION_LEVEL_DEBUG is not set diff --git a/sdkconfig.base_defaults b/sdkconfig.base_defaults index 11c4cab..b84c892 100644 --- a/sdkconfig.base_defaults +++ b/sdkconfig.base_defaults @@ -570,7 +570,7 @@ CONFIG_ENV_GPIO_OUT_RANGE_MAX=48 # # OpenIris: General Configuration # -# CONFIG_GENERAL_DEFAULT_WIRED_MODE is not set +# CONFIG_GENERAL_INCLUDE_UVC_MODE is not set # CONFIG_START_IN_UVC_MODE is not set # CONFIG_GENERAL_UVC_DELAY is not set # end of OpenIris: General Configuration diff --git a/sdkconfig.board.facefocusvr_eye b/sdkconfig.board.facefocusvr_eye index e4a0abd..5cca0bf 100644 --- a/sdkconfig.board.facefocusvr_eye +++ b/sdkconfig.board.facefocusvr_eye @@ -59,5 +59,5 @@ CONFIG_LED_EXTERNAL_GPIO=9 CONFIG_LED_EXTERNAL_PWM_FREQ=20000 CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE=50 CONFIG_CAMERA_USB_XCLK_FREQ=23000000 -CONFIG_GENERAL_DEFAULT_WIRED_MODE=y +CONFIG_GENERAL_INCLUDE_UVC_MODE=y CONFIG_START_IN_UVC_MODE=y \ No newline at end of file diff --git a/sdkconfig.board.facefocusvr_face b/sdkconfig.board.facefocusvr_face index 2769e22..891ff96 100644 --- a/sdkconfig.board.facefocusvr_face +++ b/sdkconfig.board.facefocusvr_face @@ -59,5 +59,5 @@ CONFIG_LED_EXTERNAL_GPIO=9 CONFIG_LED_EXTERNAL_PWM_FREQ=20000 CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE=100 CONFIG_CAMERA_USB_XCLK_FREQ=23000000 -CONFIG_GENERAL_DEFAULT_WIRED_MODE=y +CONFIG_GENERAL_INCLUDE_UVC_MODE=y CONFIG_START_IN_UVC_MODE=y \ No newline at end of file diff --git a/sdkconfig.board.project_babble b/sdkconfig.board.project_babble index 7be1b7d..3eade99 100644 --- a/sdkconfig.board.project_babble +++ b/sdkconfig.board.project_babble @@ -51,5 +51,5 @@ CONFIG_LED_EXTERNAL_PWM_FREQ=5000 CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE=100 CONFIG_LED_EXTERNAL_GPIO=1 CONFIG_CAMERA_USB_XCLK_FREQ=23000000 -CONFIG_GENERAL_DEFAULT_WIRED_MODE=y +CONFIG_GENERAL_INCLUDE_UVC_MODE=y # CONFIG_START_IN_UVC_MODE is not set \ No newline at end of file diff --git a/sdkconfig.board.xiao-esp32s3 b/sdkconfig.board.xiao-esp32s3 index 703f771..b3f7fb1 100644 --- a/sdkconfig.board.xiao-esp32s3 +++ b/sdkconfig.board.xiao-esp32s3 @@ -56,5 +56,5 @@ CONFIG_SPIRAM_SPEED=80 CONFIG_SPIRAM_SPEED_80M=y # CONFIG_LED_EXTERNAL_CONTROL is not set CONFIG_CAMERA_USB_XCLK_FREQ=23000000 -CONFIG_GENERAL_DEFAULT_WIRED_MODE=y +CONFIG_GENERAL_INCLUDE_UVC_MODE=y # CONFIG_START_IN_UVC_MODE is not set \ No newline at end of file diff --git a/tools/openiris_setup.py b/tools/openiris_setup.py index 30b6ff8..c6a0380 100644 --- a/tools/openiris_setup.py +++ b/tools/openiris_setup.py @@ -378,7 +378,7 @@ class OpenIrisDevice: return True def switch_mode(self, mode: str) -> bool: - """Switch device mode between WiFi, UVC, and Auto""" + """Switch device mode between WiFi, UVC, and Setup""" print(f"🔄 Switching device mode to '{mode}'...") params = {"mode": mode} @@ -906,7 +906,7 @@ def switch_device_mode(device: OpenIrisDevice, args = None): print("\n🔄 Select new device mode:") print("1. WiFi - Stream over WiFi connection") print("2. UVC - Stream as USB webcam") - print("3. Auto - Automatic mode selection") + print("3. Setup - Configuration mode") mode_choice = input("\nSelect mode (1-3): ").strip() @@ -915,7 +915,7 @@ def switch_device_mode(device: OpenIrisDevice, args = None): elif mode_choice == "2": device.switch_mode("uvc") elif mode_choice == "3": - device.switch_mode("auto") + device.switch_mode("setup") else: print("❌ Invalid mode selection") @@ -980,6 +980,23 @@ def _probe_led_pwm(device: OpenIrisDevice) -> Dict: duty = device.get_led_duty_cycle() return {"led_external_pwm_duty_cycle": duty} +def _probe_led_current(device: OpenIrisDevice) -> Dict: + # Query device for current in mA via new command + resp = device.send_command("get_led_current") + if "error" in resp: + return {"led_current_ma": None, "error": resp["error"]} + try: + results = resp.get("results", []) + if results: + result_data = json.loads(results[0]) + payload = result_data["result"] + if isinstance(payload, str): + payload = json.loads(payload) + return {"led_current_ma": float(payload.get("led_current_ma"))} + except Exception as e: + return {"led_current_ma": None, "error": str(e)} + return {"led_current_ma": None} + def _probe_mode(device: OpenIrisDevice) -> Dict: mode = device.get_device_mode() @@ -998,6 +1015,7 @@ def get_settings(device: OpenIrisDevice, args=None): probes = [ ("Identity", _probe_serial), ("LED", _probe_led_pwm), + ("Current", _probe_led_current), ("Mode", _probe_mode), ("WiFi", _probe_wifi_status), ] @@ -1035,6 +1053,17 @@ def get_settings(device: OpenIrisDevice, args=None): mode = summary.get("Mode", {}).get("mode") print(f"🎚️ Mode: {mode if mode else 'unknown'}") + # Current + current = summary.get("Current", {}).get("led_current_ma") + if current is not None: + print(f"🔌 LED Current: {current:.3f} mA") + else: + err = summary.get("Current", {}).get("error") + if err: + print(f"🔌 LED Current: unavailable ({err})") + else: + print("🔌 LED Current: unavailable") + # WiFi wifi = summary.get("WiFi", {}).get("wifi_status", {}) if wifi: