From 5a201d875ee04d9bef2d4fc865ee99e5cece80ef Mon Sep 17 00:00:00 2001 From: PhosphorosVR Date: Fri, 22 Aug 2025 01:01:49 +0200 Subject: [PATCH] Added PWM live control and get_led_duty_cycle --- components/CommandManager/CMakeLists.txt | 2 +- .../CommandManager/CommandManager.cpp | 4 +++ .../CommandManager/CommandManager.hpp | 1 + .../CommandManager/DependencyRegistry.hpp | 3 +- .../commands/device_commands.cpp | 17 ++++++++++ .../commands/device_commands.hpp | 1 + .../LEDManager/LEDManager/LEDManager.cpp | 23 +++++++++++++ .../LEDManager/LEDManager/LEDManager.hpp | 4 +++ main/openiris_main.cpp | 1 + tools/openiris_setup.py | 34 ++++++++++++++++--- 10 files changed, 84 insertions(+), 6 deletions(-) diff --git a/components/CommandManager/CMakeLists.txt b/components/CommandManager/CMakeLists.txt index 16e8afc..e4b61ca 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 + REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers LEDManager ) \ No newline at end of file diff --git a/components/CommandManager/CommandManager/CommandManager.cpp b/components/CommandManager/CommandManager/CommandManager.cpp index c4f897f..151b08e 100644 --- a/components/CommandManager/CommandManager/CommandManager.cpp +++ b/components/CommandManager/CommandManager/CommandManager.cpp @@ -24,6 +24,7 @@ std::unordered_map commandTypeMap = { {"switch_mode", CommandType::SWITCH_MODE}, {"get_device_mode", CommandType::GET_DEVICE_MODE}, {"set_led_duty_cycle", CommandType::SET_LED_DUTY_CYCLE}, + {"get_led_duty_cycle", CommandType::GET_LED_DUTY_CYCLE}, }; std::function CommandManager::createCommand(const CommandType type, std::string_view json) const @@ -93,6 +94,9 @@ std::function CommandManager::createCommand(const CommandType t case CommandType::SET_LED_DUTY_CYCLE: return [this, json] { return updateLEDDutyCycleCommand(this->registry, json); }; + case CommandType::GET_LED_DUTY_CYCLE: + return [this] + { return getLEDDutyCycleCommand(this->registry); }; default: return nullptr; } diff --git a/components/CommandManager/CommandManager/CommandManager.hpp b/components/CommandManager/CommandManager/CommandManager.hpp index 5db67d0..ab9aa5b 100644 --- a/components/CommandManager/CommandManager/CommandManager.hpp +++ b/components/CommandManager/CommandManager/CommandManager.hpp @@ -45,6 +45,7 @@ enum class CommandType SWITCH_MODE, GET_DEVICE_MODE, SET_LED_DUTY_CYCLE, + GET_LED_DUTY_CYCLE, }; class CommandManager diff --git a/components/CommandManager/CommandManager/DependencyRegistry.hpp b/components/CommandManager/CommandManager/DependencyRegistry.hpp index 48b6ee8..b0758dd 100644 --- a/components/CommandManager/CommandManager/DependencyRegistry.hpp +++ b/components/CommandManager/CommandManager/DependencyRegistry.hpp @@ -8,7 +8,8 @@ enum class DependencyType { project_config, camera_manager, - wifi_manager + wifi_manager, + led_manager }; class DependencyRegistry diff --git a/components/CommandManager/CommandManager/commands/device_commands.cpp b/components/CommandManager/CommandManager/commands/device_commands.cpp index 0ccd9ca..8accbbd 100644 --- a/components/CommandManager/CommandManager/commands/device_commands.cpp +++ b/components/CommandManager/CommandManager/commands/device_commands.cpp @@ -1,4 +1,5 @@ #include "device_commands.hpp" +#include "LEDManager.hpp" // Implementation inspired by SummerSigh work, initial PR opened in openiris repo, adapted to this rewrite CommandResult setDeviceModeCommand(std::shared_ptr registry, std::string_view jsonPayload) @@ -96,6 +97,13 @@ CommandResult updateLEDDutyCycleCommand(std::shared_ptr regi const auto projectConfig = registry->resolve(DependencyType::project_config); projectConfig->setLEDDUtyCycleConfig(dutyCycle); + // Try to apply the change live via LEDManager if available + auto ledMgr = registry->resolve(DependencyType::led_manager); + if (ledMgr) + { + ledMgr->setExternalLEDDutyCycle(static_cast(dutyCycle)); + } + cJSON_Delete(parsedJson); return CommandResult::getSuccessResult("LED duty cycle set"); @@ -107,6 +115,15 @@ CommandResult restartDeviceCommand() return CommandResult::getSuccessResult("Device restarted"); } +CommandResult getLEDDutyCycleCommand(std::shared_ptr registry) +{ + const auto projectConfig = registry->resolve(DependencyType::project_config); + const auto deviceCfg = projectConfig->getDeviceConfig(); + int duty = deviceCfg.led_external_pwm_duty_cycle; + auto result = std::format("{{ \"led_external_pwm_duty_cycle\": {} }}", duty); + return CommandResult::getSuccessResult(result); +} + CommandResult startStreamingCommand() { activateStreaming(false); // Don't disable setup interfaces by default diff --git a/components/CommandManager/CommandManager/commands/device_commands.hpp b/components/CommandManager/CommandManager/commands/device_commands.hpp index 22cde8a..9e89db4 100644 --- a/components/CommandManager/CommandManager/commands/device_commands.hpp +++ b/components/CommandManager/CommandManager/commands/device_commands.hpp @@ -14,6 +14,7 @@ CommandResult setDeviceModeCommand(std::shared_ptr registry, CommandResult updateOTACredentialsCommand(std::shared_ptr registry, std::string_view jsonPayload); CommandResult updateLEDDutyCycleCommand(std::shared_ptr registry, std::string_view jsonPayload); +CommandResult getLEDDutyCycleCommand(std::shared_ptr registry); CommandResult restartDeviceCommand(); diff --git a/components/LEDManager/LEDManager/LEDManager.cpp b/components/LEDManager/LEDManager/LEDManager.cpp index 19c7da4..8ba2460 100644 --- a/components/LEDManager/LEDManager/LEDManager.cpp +++ b/components/LEDManager/LEDManager/LEDManager.cpp @@ -177,6 +177,29 @@ void LEDManager::toggleLED(const bool state) const gpio_set_level(blink_led_pin, state); } +void LEDManager::setExternalLEDDutyCycle(uint8_t dutyPercent) +{ +#ifdef CONFIG_LED_EXTERNAL_CONTROL + dutyPercent = std::min(100, dutyPercent); + const uint32_t dutyCycle = (static_cast(dutyPercent) * 255) / 100; + ESP_LOGI(LED_MANAGER_TAG, "Updating external LED duty to %u%% (raw %lu)", dutyPercent, dutyCycle); + + // Persist into config immediately so it survives reboot + if (this->deviceConfig) + { + this->deviceConfig->setLEDDUtyCycleConfig(dutyPercent); + } + + // Apply to LEDC hardware live + // We configured channel 0 in setup with LEDC_LOW_SPEED_MODE + ESP_ERROR_CHECK_WITHOUT_ABORT(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, dutyCycle)); + ESP_ERROR_CHECK_WITHOUT_ABORT(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0)); +#else + (void)dutyPercent; // unused + ESP_LOGW(LED_MANAGER_TAG, "CONFIG_LED_EXTERNAL_CONTROL not enabled; ignoring duty update"); +#endif +} + void HandleLEDDisplayTask(void *pvParameter) { auto *ledManager = static_cast(pvParameter); diff --git a/components/LEDManager/LEDManager/LEDManager.hpp b/components/LEDManager/LEDManager/LEDManager.hpp index 1f4fa67..8f0838f 100644 --- a/components/LEDManager/LEDManager/LEDManager.hpp +++ b/components/LEDManager/LEDManager/LEDManager.hpp @@ -48,6 +48,10 @@ public: void handleLED(); size_t getTimeToDelayFor() const { return timeToDelayFor; } + // Apply new external LED PWM duty cycle immediately (0-100) + void setExternalLEDDutyCycle(uint8_t dutyPercent); + uint8_t getExternalLEDDutyCycle() const { return deviceConfig ? deviceConfig->getDeviceConfig().led_external_pwm_duty_cycle : 0; } + private: void toggleLED(bool state) const; void displayCurrentPattern(); diff --git a/main/openiris_main.cpp b/main/openiris_main.cpp index 1e66792..33b171d 100644 --- a/main/openiris_main.cpp +++ b/main/openiris_main.cpp @@ -228,6 +228,7 @@ extern "C" void app_main(void) dependencyRegistry->registerService(DependencyType::project_config, deviceConfig); dependencyRegistry->registerService(DependencyType::camera_manager, cameraHandler); dependencyRegistry->registerService(DependencyType::wifi_manager, wifiManager); + dependencyRegistry->registerService(DependencyType::led_manager, std::shared_ptr(ledManager, [](LEDManager*){})); // uvc plan // cleanup the logs - done // prepare the camera to be initialized with UVC - done? diff --git a/tools/openiris_setup.py b/tools/openiris_setup.py index 3b8ff30..8aa97f2 100644 --- a/tools/openiris_setup.py +++ b/tools/openiris_setup.py @@ -421,6 +421,24 @@ class OpenIrisDevice: print("✅ LED duty cycle set successfully") return True + def get_led_duty_cycle(self) -> Optional[int]: + """Get the current LED PWM duty cycle from the device""" + response = self.send_command("get_led_duty_cycle") + if "error" in response: + print(f"❌ Failed to get LED duty cycle: {response['error']}") + return None + try: + results = response.get("results", []) + if results: + result_data = json.loads(results[0]) + payload = result_data["result"] + if isinstance(payload, str): + payload = json.loads(payload) + return int(payload.get("led_external_pwm_duty_cycle")) + except Exception as e: + print(f"❌ Failed to parse LED duty cycle: {e}") + return None + def monitor_logs(self): """Monitor device logs until interrupted""" print("📋 Monitoring device logs (Press Ctrl+C to exit)...") @@ -761,14 +779,20 @@ def set_led_duty_cycle(device: OpenIrisDevice, args=None): if duty_cycle < 0 or duty_cycle > 100: print("❌ Duty cycle must be between 0 and 100.") else: + # Apply immediately; stay in loop for further tweaks device.set_led_duty_cycle(duty_cycle) - break def monitor_logs(device: OpenIrisDevice, args = None): device.monitor_logs() +def get_led_duty_cycle(device: OpenIrisDevice, args=None): + duty = device.get_led_duty_cycle() + if duty is not None: + print(f"💡 Current LED duty cycle: {duty}%") + + COMMANDS_MAP = { "1": scan_networks, "2": display_networks, @@ -780,7 +804,8 @@ COMMANDS_MAP = { "8": start_streaming, "9": switch_device_mode, "10": set_led_duty_cycle, - "11": monitor_logs, + "11": get_led_duty_cycle, + "12": monitor_logs, } @@ -879,9 +904,10 @@ def main(): print("8. 🚀 Start streaming mode") print("9. 🔄 Switch device mode (WiFi/UVC/Auto)") print("10. 💡 Update PWM Duty Cycle") - print("11. 📋 Monitor logs") + print("11. 💡Get PWM Duty Cycle") + print("12. 📖 Monitor logs") print("exit. 🚪 Exit") - choice = input("\nSelect option (1-11): ").strip() + choice = input("\nSelect option (1-12): ").strip() if choice == "exit": break