mirror of
https://github.com/MrUnknownDE/OpenIris-ESPIDF.git
synced 2026-05-03 12:46:06 +02:00
Merge pull request #9 from PhosphorosVR/main
LED Control, USB Serial handover before starting UVC, FPS Limiting, default UVC mode for facefocus CLI Enhancements and bugfixes
This commit is contained in:
@@ -1,69 +1,123 @@
|
|||||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
| Supported Targets | ESP32-S3 |
|
||||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
|
| ----------------- | -------- |
|
||||||
|
|
||||||
# Blink Example
|
## OpenIris-ESPIDF
|
||||||
|
|
||||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
Firmware and tools for OpenIris — Wi‑Fi, UVC streaming, and a Python setup CLI.
|
||||||
|
|
||||||
This example demonstrates how to blink a LED by using the GPIO driver or using the [led_strip](https://components.espressif.com/component/espressif/led_strip) library if the LED is addressable e.g. [WS2812](https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf). The `led_strip` library is installed via [component manager](main/idf_component.yml).
|
---
|
||||||
|
|
||||||
## How to Use Example
|
## What’s inside
|
||||||
|
- ESP‑IDF firmware (C/C++) with modules for Camera, Wi‑Fi, UVC, REST/Serial commands, and more
|
||||||
|
- Python tools for setup over USB serial:
|
||||||
|
- `tools/switchBoardType.py` — choose a board profile (builds the right sdkconfig)
|
||||||
|
- `tools/openiris_setup.py` — interactive CLI for Wi‑Fi, MDNS/Name, Mode, LED PWM, Logs, and a Settings Summary
|
||||||
|
|
||||||
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
|
---
|
||||||
|
|
||||||
### Hardware Required
|
## First-time setup on Windows (VS Code + ESP‑IDF extension)
|
||||||
|
If you’re starting fresh on Windows, this workflow is smooth and reliable:
|
||||||
|
|
||||||
* A development board with normal LED or addressable LED on-board (e.g., ESP32-S3-DevKitC, ESP32-C6-DevKitC etc.)
|
1) Install tooling
|
||||||
* A USB cable for Power supply and programming
|
- Git: https://git-scm.com/downloads/win
|
||||||
|
- Visual Studio Code: https://code.visualstudio.com/
|
||||||
|
|
||||||
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
|
2) Get the source code
|
||||||
|
- Create a folder where you want the repo (e.g., `D:\OpenIris-ESPIDF\`). In File Explorer, right‑click the folder and choose “Open in Terminal”.
|
||||||
### Configure the Project
|
- Clone and open in VS Code:
|
||||||
|
```cmd
|
||||||
Open the project configuration menu (`idf.py menuconfig`).
|
git clone https://github.com/lorow/OpenIris-ESPIDF.git
|
||||||
|
cd OpenIris-ESPIDF
|
||||||
In the `Example Configuration` menu:
|
code .
|
||||||
|
|
||||||
* Select the LED type in the `Blink LED type` option.
|
|
||||||
* Use `GPIO` for regular LED
|
|
||||||
* Use `LED strip` for addressable LED
|
|
||||||
* If the LED type is `LED strip`, select the backend peripheral
|
|
||||||
* `RMT` is only available for ESP targets with RMT peripheral supported
|
|
||||||
* `SPI` is available for all ESP targets
|
|
||||||
* Set the GPIO number used for the signal in the `Blink GPIO number` option.
|
|
||||||
* Set the blinking period in the `Blink period in ms` option.
|
|
||||||
|
|
||||||
### Build and Flash
|
|
||||||
|
|
||||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
|
||||||
|
|
||||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
|
||||||
|
|
||||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
|
||||||
|
|
||||||
## Example Output
|
|
||||||
|
|
||||||
As you run the example, you will see the LED blinking, according to the previously defined period. For the addressable LED, you can also change the LED color by setting the `led_strip_set_pixel(led_strip, 0, 16, 16, 16);` (LED Strip, Pixel Number, Red, Green, Blue) with values from 0 to 255 in the [source file](main/blink_example_main.c).
|
|
||||||
|
|
||||||
```text
|
|
||||||
I (315) example: Example configured to blink addressable LED!
|
|
||||||
I (325) example: Turning the LED OFF!
|
|
||||||
I (1325) example: Turning the LED ON!
|
|
||||||
I (2325) example: Turning the LED OFF!
|
|
||||||
I (3325) example: Turning the LED ON!
|
|
||||||
I (4325) example: Turning the LED OFF!
|
|
||||||
I (5325) example: Turning the LED ON!
|
|
||||||
I (6325) example: Turning the LED OFF!
|
|
||||||
I (7325) example: Turning the LED ON!
|
|
||||||
I (8325) example: Turning the LED OFF!
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: The color order could be different according to the LED model.
|
3) Install the ESP‑IDF VS Code extension
|
||||||
|
- In VS Code, open the Extensions tab and install: https://marketplace.visualstudio.com/items?itemName=espressif.esp-idf-extension
|
||||||
|
|
||||||
The pixel number indicates the pixel position in the LED strip. For a single LED, use 0.
|
4) Set the default terminal profile to Command Prompt
|
||||||
|
- Press Ctrl+Shift+P → search “Terminal: Select Default Profile” → choose “Command Prompt”.
|
||||||
|
- Restart VS Code from its normal shortcut (not from Git Bash). This avoids running ESP‑IDF in the wrong shell.
|
||||||
|
|
||||||
|
5) Configure ESP‑IDF in the extension
|
||||||
|
- On first launch, the extension may prompt to install ESP‑IDF and tools — follow the steps. It can take a while.
|
||||||
|
- If you see the extension’s home page instead, click “Configure extension”, pick “EXPRESS”, choose “GitHub” as the server and version “v5.4.2”.
|
||||||
|
- Then open the ESP‑IDF Explorer tab and click “Open ESP‑IDF Terminal”. We’ll use that for builds.
|
||||||
|
|
||||||
|
After this, you’re ready for the Quick start below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
### 1) Pick your board (loads the default configuration)
|
||||||
|
Windows (cmd):
|
||||||
|
```cmd
|
||||||
|
python .\tools\switchBoardType.py --board xiao-esp32s3 --diff
|
||||||
|
```
|
||||||
|
macOS/Linux (bash):
|
||||||
|
```bash
|
||||||
|
python3 ./tools/switchBoardType.py --board xiao-esp32s3 --diff
|
||||||
|
```
|
||||||
|
- Set `--board` to your target board
|
||||||
|
- `--diff` shows what changed in the config
|
||||||
|
|
||||||
|
### 2) Build & flash
|
||||||
|
- Set the target (e.g., ESP32‑S3).
|
||||||
|
- Build, flash, and open the serial monitor.
|
||||||
|
|
||||||
|
### 3) Use the Python setup CLI (recommended)
|
||||||
|
Configure the device over USB serial.
|
||||||
|
|
||||||
|
Before you run it:
|
||||||
|
- If you still have the serial monitor open, close it (the port must be free).
|
||||||
|
- In VS Code, open the sidebar “ESP‑IDF: Explorer” and click “Open ESP‑IDF Terminal”. We’ll run the CLI there so Python and packages are in the right environment.
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
```cmd
|
||||||
|
python .\tools\openiris_setup.py --port COMxx
|
||||||
|
```
|
||||||
|
Examples:
|
||||||
|
- Windows: `python .\tools\openiris_setup.py --port COM69`, …
|
||||||
|
- macOS: idk
|
||||||
|
- Linux: idk
|
||||||
|
|
||||||
|
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)
|
||||||
|
- Adjust LED PWM
|
||||||
|
- Show a Settings Summary (MAC, Wi‑Fi status, mode, PWM, …)
|
||||||
|
- View logs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Serial number & MAC
|
||||||
|
- Internally, the serial number is derived from the Wi‑Fi MAC address.
|
||||||
|
- The CLI displays the MAC by default (clearer); it’s the value used as the serial number.
|
||||||
|
- The UVC device name is based on the MDNS hostname.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common workflows
|
||||||
|
- Fast Wi‑Fi setup: in the CLI, go to “Wi‑Fi settings” → “Automatic setup”, then check “status”.
|
||||||
|
- Change name/MDNS: set the device name in the CLI, then replug USB — UVC will show the new name.
|
||||||
|
- Adjust brightness/LED: set LED PWM in the CLI.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project layout (short)
|
||||||
|
- `main/` — entry point
|
||||||
|
- `components/` — modules (Camera, WiFi, UVC, CommandManager, …)
|
||||||
|
- `tools/` — Python helper tools (board switch, setup CLI, scanner)
|
||||||
|
|
||||||
|
If you want to dig deeper: commands are mapped via the `CommandManager` under `components/CommandManager/...`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
- UVC doesn’t appear on the host?
|
||||||
|
- Switch mode to UVC via CLI tool, replug USB and wait 20s.
|
||||||
|
|
||||||
* If the LED isn't blinking, check the GPIO or the LED type selection in the `Example Configuration` menu.
|
---
|
||||||
|
|
||||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
Feedback, issues, and PRs are welcome.
|
||||||
@@ -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
|
// 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_WIRED_MODE
|
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "soc/rtc_cntl_struct.h"
|
#include "soc/rtc_cntl_struct.h"
|
||||||
#include "soc/usb_serial_jtag_reg.h"
|
#include "soc/usb_serial_jtag_reg.h"
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ void CameraManager::setupCameraPinout()
|
|||||||
|
|
||||||
ESP_LOGI(CAMERA_MANAGER_TAG, "CAM_BOARD");
|
ESP_LOGI(CAMERA_MANAGER_TAG, "CAM_BOARD");
|
||||||
#endif
|
#endif
|
||||||
#if CONFIG_GENERAL_WIRED_MODE
|
#if CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||||
xclk_freq_hz = CONFIG_CAMERA_USB_XCLK_FREQ;
|
xclk_freq_hz = CONFIG_CAMERA_USB_XCLK_FREQ;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ void CameraManager::setupCameraPinout()
|
|||||||
.jpeg_quality = 7, // 0-63, for OV series camera sensors, lower number means higher quality // Below 6 stability problems
|
.jpeg_quality = 7, // 0-63, for OV series camera sensors, lower number means higher quality // Below 6 stability problems
|
||||||
.fb_count = 2, // When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode.
|
.fb_count = 2, // When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode.
|
||||||
.fb_location = CAMERA_FB_IN_DRAM,
|
.fb_location = CAMERA_FB_IN_DRAM,
|
||||||
.grab_mode = CAMERA_GRAB_WHEN_EMPTY,
|
.grab_mode = CAMERA_GRAB_LATEST, //CAMERA_GRAB_WHEN_EMPTY
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ void CameraManager::setupBasicResolution()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGE(CAMERA_MANAGER_TAG, "PSRAM size: %u", esp_psram_get_size());
|
ESP_LOGI(CAMERA_MANAGER_TAG, "PSRAM size: %u", esp_psram_get_size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CameraManager::setupCameraSensor()
|
void CameraManager::setupCameraSensor()
|
||||||
@@ -196,7 +196,7 @@ bool CameraManager::setupCamera()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CONFIG_GENERAL_WIRED_MODE
|
#if CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||||
const auto temp_sensor = esp_camera_sensor_get();
|
const auto temp_sensor = esp_camera_sensor_get();
|
||||||
|
|
||||||
// Thanks to lick_it, we discovered that OV5640 likes to overheat when
|
// Thanks to lick_it, we discovered that OV5640 likes to overheat when
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ idf_component_register(
|
|||||||
INCLUDE_DIRS
|
INCLUDE_DIRS
|
||||||
"CommandManager"
|
"CommandManager"
|
||||||
"CommandManager/commands"
|
"CommandManager/commands"
|
||||||
REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers
|
REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers LEDManager
|
||||||
)
|
)
|
||||||
@@ -23,53 +23,84 @@ std::unordered_map<std::string, CommandType> commandTypeMap = {
|
|||||||
{"connect_wifi", CommandType::CONNECT_WIFI},
|
{"connect_wifi", CommandType::CONNECT_WIFI},
|
||||||
{"switch_mode", CommandType::SWITCH_MODE},
|
{"switch_mode", CommandType::SWITCH_MODE},
|
||||||
{"get_device_mode", CommandType::GET_DEVICE_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},
|
||||||
|
{"get_serial", CommandType::GET_SERIAL},
|
||||||
};
|
};
|
||||||
|
|
||||||
std::function<CommandResult()> CommandManager::createCommand(const CommandType type, std::string_view json) const {
|
std::function<CommandResult()> CommandManager::createCommand(const CommandType type, std::string_view json) const
|
||||||
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case CommandType::PING:
|
case CommandType::PING:
|
||||||
return {PingCommand};
|
return {PingCommand};
|
||||||
case CommandType::PAUSE:
|
case CommandType::PAUSE:
|
||||||
return [json] { return PauseCommand(json); };
|
return [json]
|
||||||
|
{ return PauseCommand(json); };
|
||||||
case CommandType::SET_STREAMING_MODE:
|
case CommandType::SET_STREAMING_MODE:
|
||||||
return [this, json] {return setDeviceModeCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return setDeviceModeCommand(this->registry, json); };
|
||||||
case CommandType::UPDATE_OTA_CREDENTIALS:
|
case CommandType::UPDATE_OTA_CREDENTIALS:
|
||||||
return [this, json] { return updateOTACredentialsCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return updateOTACredentialsCommand(this->registry, json); };
|
||||||
case CommandType::SET_WIFI:
|
case CommandType::SET_WIFI:
|
||||||
return [this, json] { return setWiFiCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return setWiFiCommand(this->registry, json); };
|
||||||
case CommandType::UPDATE_WIFI:
|
case CommandType::UPDATE_WIFI:
|
||||||
return [this, json] { return updateWiFiCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return updateWiFiCommand(this->registry, json); };
|
||||||
case CommandType::UPDATE_AP_WIFI:
|
case CommandType::UPDATE_AP_WIFI:
|
||||||
return [this, json] { return updateAPWiFiCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return updateAPWiFiCommand(this->registry, json); };
|
||||||
case CommandType::DELETE_NETWORK:
|
case CommandType::DELETE_NETWORK:
|
||||||
return [this, json] { return deleteWiFiCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return deleteWiFiCommand(this->registry, json); };
|
||||||
case CommandType::SET_MDNS:
|
case CommandType::SET_MDNS:
|
||||||
return [this, json] { return setMDNSCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return setMDNSCommand(this->registry, json); };
|
||||||
case CommandType::UPDATE_CAMERA:
|
case CommandType::UPDATE_CAMERA:
|
||||||
return [this, json] { return updateCameraCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return updateCameraCommand(this->registry, json); };
|
||||||
case CommandType::RESTART_CAMERA:
|
case CommandType::RESTART_CAMERA:
|
||||||
return [this, json] { return restartCameraCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return restartCameraCommand(this->registry, json); };
|
||||||
case CommandType::GET_CONFIG:
|
case CommandType::GET_CONFIG:
|
||||||
return [this] { return getConfigCommand(this->registry); };
|
return [this]
|
||||||
|
{ return getConfigCommand(this->registry); };
|
||||||
case CommandType::SAVE_CONFIG:
|
case CommandType::SAVE_CONFIG:
|
||||||
return [this] { return saveConfigCommand(this->registry); };
|
return [this]
|
||||||
|
{ return saveConfigCommand(this->registry); };
|
||||||
case CommandType::RESET_CONFIG:
|
case CommandType::RESET_CONFIG:
|
||||||
return [this, json] { return resetConfigCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return resetConfigCommand(this->registry, json); };
|
||||||
case CommandType::RESTART_DEVICE:
|
case CommandType::RESTART_DEVICE:
|
||||||
return restartDeviceCommand;
|
return restartDeviceCommand;
|
||||||
case CommandType::SCAN_NETWORKS:
|
case CommandType::SCAN_NETWORKS:
|
||||||
return [this] { return scanNetworksCommand(this->registry); };
|
return [this]
|
||||||
|
{ return scanNetworksCommand(this->registry); };
|
||||||
case CommandType::START_STREAMING:
|
case CommandType::START_STREAMING:
|
||||||
return startStreamingCommand;
|
return startStreamingCommand;
|
||||||
case CommandType::GET_WIFI_STATUS:
|
case CommandType::GET_WIFI_STATUS:
|
||||||
return [this] { return getWiFiStatusCommand(this->registry); };
|
return [this]
|
||||||
|
{ return getWiFiStatusCommand(this->registry); };
|
||||||
case CommandType::CONNECT_WIFI:
|
case CommandType::CONNECT_WIFI:
|
||||||
return [this] { return connectWiFiCommand(this->registry); };
|
return [this]
|
||||||
|
{ return connectWiFiCommand(this->registry); };
|
||||||
case CommandType::SWITCH_MODE:
|
case CommandType::SWITCH_MODE:
|
||||||
return [this, json] { return switchModeCommand(this->registry, json); };
|
return [this, json]
|
||||||
|
{ return switchModeCommand(this->registry, json); };
|
||||||
case CommandType::GET_DEVICE_MODE:
|
case CommandType::GET_DEVICE_MODE:
|
||||||
return [this] { return getDeviceModeCommand(this->registry); };
|
return [this]
|
||||||
|
{ return getDeviceModeCommand(this->registry); };
|
||||||
|
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); };
|
||||||
|
case CommandType::GET_SERIAL:
|
||||||
|
return [this]
|
||||||
|
{ return getSerialNumberCommand(this->registry); };
|
||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ enum class CommandType
|
|||||||
CONNECT_WIFI,
|
CONNECT_WIFI,
|
||||||
SWITCH_MODE,
|
SWITCH_MODE,
|
||||||
GET_DEVICE_MODE,
|
GET_DEVICE_MODE,
|
||||||
|
SET_LED_DUTY_CYCLE,
|
||||||
|
GET_LED_DUTY_CYCLE,
|
||||||
|
GET_SERIAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
class CommandManager
|
class CommandManager
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ enum class DependencyType
|
|||||||
{
|
{
|
||||||
project_config,
|
project_config,
|
||||||
camera_manager,
|
camera_manager,
|
||||||
wifi_manager
|
wifi_manager,
|
||||||
|
led_manager
|
||||||
};
|
};
|
||||||
|
|
||||||
class DependencyRegistry
|
class DependencyRegistry
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#include "device_commands.hpp"
|
#include "device_commands.hpp"
|
||||||
|
#include "LEDManager.hpp"
|
||||||
|
#include "esp_mac.h"
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
// Implementation inspired by SummerSigh work, initial PR opened in openiris repo, adapted to this rewrite
|
// Implementation inspired by SummerSigh work, initial PR opened in openiris repo, adapted to this rewrite
|
||||||
CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
|
CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
|
||||||
@@ -25,6 +28,8 @@ CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry,
|
|||||||
const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
||||||
projectConfig->setDeviceMode(static_cast<StreamingMode>(mode));
|
projectConfig->setDeviceMode(static_cast<StreamingMode>(mode));
|
||||||
|
|
||||||
|
cJSON_Delete(parsedJson);
|
||||||
|
|
||||||
return CommandResult::getSuccessResult("Device mode set");
|
return CommandResult::getSuccessResult("Device mode set");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,16 +69,63 @@ CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
projectConfig->setDeviceConfig(OTALogin, OTAPassword, OTAPort);
|
cJSON_Delete(parsedJson);
|
||||||
|
|
||||||
|
projectConfig->setOTAConfig(OTALogin, OTAPassword, OTAPort);
|
||||||
return CommandResult::getSuccessResult("OTA Config set");
|
return CommandResult::getSuccessResult("OTA Config set");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandResult updateLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
|
||||||
|
{
|
||||||
|
const auto parsedJson = cJSON_Parse(jsonPayload.data());
|
||||||
|
if (parsedJson == nullptr)
|
||||||
|
{
|
||||||
|
return CommandResult::getErrorResult("Invalid payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto dutyCycleObject = cJSON_GetObjectItem(parsedJson, "dutyCycle");
|
||||||
|
if (dutyCycleObject == nullptr)
|
||||||
|
{
|
||||||
|
return CommandResult::getErrorResult("Invalid payload - missing dutyCycle");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto dutyCycle = dutyCycleObject->valueint;
|
||||||
|
|
||||||
|
if (dutyCycle < 0 || dutyCycle > 100)
|
||||||
|
{
|
||||||
|
return CommandResult::getErrorResult("Invalid payload - unsupported dutyCycle");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
||||||
|
projectConfig->setLEDDUtyCycleConfig(dutyCycle);
|
||||||
|
|
||||||
|
// Try to apply the change live via LEDManager if available
|
||||||
|
auto ledMgr = registry->resolve<LEDManager>(DependencyType::led_manager);
|
||||||
|
if (ledMgr)
|
||||||
|
{
|
||||||
|
ledMgr->setExternalLEDDutyCycle(static_cast<uint8_t>(dutyCycle));
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(parsedJson);
|
||||||
|
|
||||||
|
return CommandResult::getSuccessResult("LED duty cycle set");
|
||||||
|
}
|
||||||
|
|
||||||
CommandResult restartDeviceCommand()
|
CommandResult restartDeviceCommand()
|
||||||
{
|
{
|
||||||
OpenIrisTasks::ScheduleRestart(2000);
|
OpenIrisTasks::ScheduleRestart(2000);
|
||||||
return CommandResult::getSuccessResult("Device restarted");
|
return CommandResult::getSuccessResult("Device restarted");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandResult getLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||||
|
{
|
||||||
|
const auto projectConfig = registry->resolve<ProjectConfig>(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()
|
CommandResult startStreamingCommand()
|
||||||
{
|
{
|
||||||
activateStreaming(false); // Don't disable setup interfaces by default
|
activateStreaming(false); // Don't disable setup interfaces by default
|
||||||
@@ -147,3 +199,23 @@ CommandResult getDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry)
|
|||||||
auto result = std::format("{{ \"mode\": \"{}\", \"value\": {} }}", modeStr, static_cast<int>(currentMode));
|
auto result = std::format("{{ \"mode\": \"{}\", \"value\": {} }}", modeStr, static_cast<int>(currentMode));
|
||||||
return CommandResult::getSuccessResult(result);
|
return CommandResult::getSuccessResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandResult getSerialNumberCommand(std::shared_ptr<DependencyRegistry> /*registry*/)
|
||||||
|
{
|
||||||
|
// Read MAC for STA interface
|
||||||
|
uint8_t mac[6] = {0};
|
||||||
|
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||||
|
|
||||||
|
char serial_no_sep[13];
|
||||||
|
// Serial without separators (12 hex chars)
|
||||||
|
std::snprintf(serial_no_sep, sizeof(serial_no_sep), "%02X%02X%02X%02X%02X%02X",
|
||||||
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
|
||||||
|
char mac_colon[18];
|
||||||
|
// MAC with colons
|
||||||
|
std::snprintf(mac_colon, sizeof(mac_colon), "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||||
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
|
||||||
|
auto result = std::format("{{ \"serial\": \"{}\", \"mac\": \"{}\" }}", serial_no_sep, mac_colon);
|
||||||
|
return CommandResult::getSuccessResult(result);
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry,
|
|||||||
|
|
||||||
CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
||||||
|
|
||||||
|
CommandResult updateLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
||||||
|
CommandResult getLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registry);
|
||||||
|
|
||||||
CommandResult restartDeviceCommand();
|
CommandResult restartDeviceCommand();
|
||||||
|
|
||||||
CommandResult startStreamingCommand();
|
CommandResult startStreamingCommand();
|
||||||
@@ -20,3 +23,5 @@ CommandResult startStreamingCommand();
|
|||||||
CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
||||||
|
|
||||||
CommandResult getDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry);
|
CommandResult getDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry);
|
||||||
|
|
||||||
|
CommandResult getSerialNumberCommand(std::shared_ptr<DependencyRegistry> registry);
|
||||||
@@ -4,15 +4,15 @@
|
|||||||
// Forward declarations
|
// Forward declarations
|
||||||
extern void start_video_streaming(void *arg);
|
extern void start_video_streaming(void *arg);
|
||||||
|
|
||||||
bool startupCommandReceived = false;
|
static bool s_startupCommandReceived = false;
|
||||||
bool getStartupCommandReceived()
|
bool getStartupCommandReceived()
|
||||||
{
|
{
|
||||||
return startupCommandReceived;
|
return s_startupCommandReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStartupCommandReceived(bool startupCommandReceived)
|
void setStartupCommandReceived(bool startupCommandReceived)
|
||||||
{
|
{
|
||||||
startupCommandReceived = startupCommandReceived;
|
s_startupCommandReceived = startupCommandReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
static TaskHandle_t *g_serial_manager_handle = nullptr;
|
static TaskHandle_t *g_serial_manager_handle = nullptr;
|
||||||
@@ -27,15 +27,15 @@ void setSerialManagerHandle(TaskHandle_t *serialManagerHandle)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Global pause state
|
// Global pause state
|
||||||
bool startupPaused = false;
|
static bool s_startupPaused = false;
|
||||||
bool getStartupPaused()
|
bool getStartupPaused()
|
||||||
{
|
{
|
||||||
return startupPaused;
|
return s_startupPaused;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStartupPaused(bool startupPaused)
|
void setStartupPaused(bool startupPaused)
|
||||||
{
|
{
|
||||||
startupPaused = startupPaused;
|
s_startupPaused = startupPaused;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to manually activate streaming
|
// Function to manually activate streaming
|
||||||
@@ -48,3 +48,8 @@ void activateStreaming(bool disableSetup)
|
|||||||
|
|
||||||
start_video_streaming(serialTaskHandle);
|
start_video_streaming(serialTaskHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// USB handover state
|
||||||
|
static bool s_usbHandoverDone = false;
|
||||||
|
bool getUsbHandoverDone() { return s_usbHandoverDone; }
|
||||||
|
void setUsbHandoverDone(bool done) { s_usbHandoverDone = done; }
|
||||||
@@ -21,4 +21,8 @@ void setStartupCommandReceived(bool startupCommandReceived);
|
|||||||
bool getStartupPaused();
|
bool getStartupPaused();
|
||||||
void setStartupPaused(bool startupPaused);
|
void setStartupPaused(bool startupPaused);
|
||||||
|
|
||||||
|
// Tracks whether USB handover from usb_serial_jtag to TinyUSB was performed
|
||||||
|
bool getUsbHandoverDone();
|
||||||
|
void setUsbHandoverDone(bool done);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
idf_component_register(SRCS "LEDManager/LEDManager.cpp"
|
idf_component_register(SRCS "LEDManager/LEDManager.cpp"
|
||||||
INCLUDE_DIRS "LEDManager"
|
INCLUDE_DIRS "LEDManager"
|
||||||
REQUIRES StateManager driver esp_driver_ledc Helpers
|
REQUIRES StateManager driver esp_driver_ledc Helpers ProjectConfig
|
||||||
)
|
)
|
||||||
@@ -48,10 +48,7 @@ ledStateMap_t LEDManager::ledStateMap = {
|
|||||||
{
|
{
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
{
|
{{LED_ON, 200}, {LED_OFF, 200}, {LED_ON, 200}, {LED_OFF, 200}, {LED_ON, 200}, {LED_OFF, 200}, {LED_ON, 200}, {LED_OFF, 200}, {LED_ON, 200}, {LED_OFF, 200}},
|
||||||
{LED_ON, 200}, {LED_OFF, 200}, {LED_ON, 200}, {LED_OFF, 200}, {LED_ON, 200}, {LED_OFF, 200},
|
|
||||||
{LED_ON, 200}, {LED_OFF, 200}, {LED_ON, 200}, {LED_OFF, 200}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -65,32 +62,38 @@ ledStateMap_t LEDManager::ledStateMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
LEDManager::LEDManager(gpio_num_t pin, gpio_num_t illumninator_led_pin,
|
LEDManager::LEDManager(gpio_num_t pin, gpio_num_t illumninator_led_pin,
|
||||||
QueueHandle_t ledStateQueue) : blink_led_pin(pin),
|
QueueHandle_t ledStateQueue, std::shared_ptr<ProjectConfig> deviceConfig) : blink_led_pin(pin),
|
||||||
illumninator_led_pin(illumninator_led_pin),
|
illumninator_led_pin(illumninator_led_pin),
|
||||||
ledStateQueue(ledStateQueue),
|
ledStateQueue(ledStateQueue),
|
||||||
currentState(LEDStates_e::LedStateNone) {
|
currentState(LEDStates_e::LedStateNone),
|
||||||
|
deviceConfig(deviceConfig)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void LEDManager::setup() {
|
void LEDManager::setup()
|
||||||
ESP_LOGD(LED_MANAGER_TAG, "Setting up status led.");
|
{
|
||||||
|
ESP_LOGI(LED_MANAGER_TAG, "Setting up status led.");
|
||||||
gpio_reset_pin(blink_led_pin);
|
gpio_reset_pin(blink_led_pin);
|
||||||
/* Set the GPIO as a push/pull output */
|
/* Set the GPIO as a push/pull output */
|
||||||
gpio_set_direction(blink_led_pin, GPIO_MODE_OUTPUT);
|
gpio_set_direction(blink_led_pin, GPIO_MODE_OUTPUT);
|
||||||
this->toggleLED(LED_OFF);
|
this->toggleLED(LED_OFF);
|
||||||
|
|
||||||
#ifdef CONFIG_LED_EXTERNAL_CONTROL
|
#ifdef CONFIG_LED_EXTERNAL_CONTROL
|
||||||
ESP_LOGD(LED_MANAGER_TAG, "Setting up illuminator led.");
|
ESP_LOGI(LED_MANAGER_TAG, "Setting up illuminator led.");
|
||||||
const int freq = CONFIG_LED_EXTERNAL_PWM_FREQ;
|
const int freq = CONFIG_LED_EXTERNAL_PWM_FREQ;
|
||||||
const auto resolution = LEDC_TIMER_8_BIT;
|
const auto resolution = LEDC_TIMER_8_BIT;
|
||||||
const int dutyCycle = (CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE * 255) / 100;
|
const auto deviceConfig = this->deviceConfig->getDeviceConfig();
|
||||||
|
|
||||||
|
const uint32_t dutyCycle = (deviceConfig.led_external_pwm_duty_cycle * 255) / 100;
|
||||||
|
|
||||||
|
ESP_LOGI(LED_MANAGER_TAG, "Setting dutyCycle to: %lu ", dutyCycle);
|
||||||
|
|
||||||
ledc_timer_config_t ledc_timer = {
|
ledc_timer_config_t ledc_timer = {
|
||||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||||
.duty_resolution = resolution,
|
.duty_resolution = resolution,
|
||||||
.timer_num = LEDC_TIMER_0,
|
.timer_num = LEDC_TIMER_0,
|
||||||
.freq_hz = freq,
|
.freq_hz = freq,
|
||||||
.clk_cfg = LEDC_AUTO_CLK
|
.clk_cfg = LEDC_AUTO_CLK};
|
||||||
};
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
|
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
|
||||||
|
|
||||||
@@ -101,8 +104,7 @@ void LEDManager::setup() {
|
|||||||
.intr_type = LEDC_INTR_DISABLE,
|
.intr_type = LEDC_INTR_DISABLE,
|
||||||
.timer_sel = LEDC_TIMER_0,
|
.timer_sel = LEDC_TIMER_0,
|
||||||
.duty = dutyCycle,
|
.duty = dutyCycle,
|
||||||
.hpoint = 0
|
.hpoint = 0};
|
||||||
};
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
|
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
|
||||||
#endif
|
#endif
|
||||||
@@ -110,65 +112,98 @@ void LEDManager::setup() {
|
|||||||
ESP_LOGD(LED_MANAGER_TAG, "Done.");
|
ESP_LOGD(LED_MANAGER_TAG, "Done.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void LEDManager::handleLED() {
|
void LEDManager::handleLED()
|
||||||
if (!this->finishedPattern) {
|
{
|
||||||
|
if (!this->finishedPattern)
|
||||||
|
{
|
||||||
displayCurrentPattern();
|
displayCurrentPattern();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xQueueReceive(this->ledStateQueue, &buffer, 10)) {
|
if (xQueueReceive(this->ledStateQueue, &buffer, 10))
|
||||||
|
{
|
||||||
this->updateState(buffer);
|
this->updateState(buffer);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// we've finished displaying the pattern, so let's check if it's repeatable and if so - reset it
|
// we've finished displaying the pattern, so let's check if it's repeatable and if so - reset it
|
||||||
if (ledStateMap[this->currentState].isRepeatable || ledStateMap[this->currentState].isError) {
|
if (ledStateMap[this->currentState].isRepeatable || ledStateMap[this->currentState].isError)
|
||||||
|
{
|
||||||
this->currentPatternIndex = 0;
|
this->currentPatternIndex = 0;
|
||||||
this->finishedPattern = false;
|
this->finishedPattern = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LEDManager::displayCurrentPattern() {
|
void LEDManager::displayCurrentPattern()
|
||||||
|
{
|
||||||
auto [state, delayTime] = ledStateMap[this->currentState].patterns[this->currentPatternIndex];
|
auto [state, delayTime] = ledStateMap[this->currentState].patterns[this->currentPatternIndex];
|
||||||
this->toggleLED(state);
|
this->toggleLED(state);
|
||||||
this->timeToDelayFor = delayTime;
|
this->timeToDelayFor = delayTime;
|
||||||
|
|
||||||
if (this->currentPatternIndex < ledStateMap[this->currentState].patterns.size() - 1)
|
if (this->currentPatternIndex < ledStateMap[this->currentState].patterns.size() - 1)
|
||||||
this->currentPatternIndex++;
|
this->currentPatternIndex++;
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
this->finishedPattern = true;
|
this->finishedPattern = true;
|
||||||
this->toggleLED(LED_OFF);
|
this->toggleLED(LED_OFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LEDManager::updateState(const LEDStates_e newState) {
|
void LEDManager::updateState(const LEDStates_e newState)
|
||||||
// we should change the displayed state
|
{
|
||||||
// only if we finished displaying the current one - which is handled by the task
|
// If we've got an error state - that's it, keep repeating it indefinitely
|
||||||
// if the new state is not the same as the current one
|
|
||||||
// and if can actually display the state
|
|
||||||
|
|
||||||
// if we've got an error state - that's it, we'll just keep repeating it indefinitely
|
|
||||||
if (ledStateMap[this->currentState].isError)
|
if (ledStateMap[this->currentState].isError)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Alternative (recoverable error states):
|
||||||
|
// Allow recovery from error states by only blocking transitions when both, current and new states are error. Uncomment to enable recovery.
|
||||||
|
// if (ledStateMap[this->currentState].isError && ledStateMap[newState].isError)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// Only update when new state differs and is known.
|
||||||
|
if (!ledStateMap.contains(newState))
|
||||||
|
return;
|
||||||
|
|
||||||
if (newState == this->currentState)
|
if (newState == this->currentState)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ledStateMap.contains(newState)) {
|
|
||||||
this->currentState = newState;
|
this->currentState = newState;
|
||||||
this->currentPatternIndex = 0;
|
this->currentPatternIndex = 0;
|
||||||
this->finishedPattern = false;
|
this->finishedPattern = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void LEDManager::toggleLED(const bool state) const {
|
void LEDManager::toggleLED(const bool state) const
|
||||||
|
{
|
||||||
gpio_set_level(blink_led_pin, state);
|
gpio_set_level(blink_led_pin, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleLEDDisplayTask(void *pvParameter) {
|
void LEDManager::setExternalLEDDutyCycle(uint8_t dutyPercent)
|
||||||
auto *ledManager = static_cast<LEDManager *>(pvParameter);
|
{
|
||||||
|
#ifdef CONFIG_LED_EXTERNAL_CONTROL
|
||||||
|
const uint32_t dutyCycle = (static_cast<uint32_t>(dutyPercent) * 255) / 100;
|
||||||
|
ESP_LOGI(LED_MANAGER_TAG, "Updating external LED duty to %u%% (raw %lu)", dutyPercent, dutyCycle);
|
||||||
|
|
||||||
while (true) {
|
// 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<LEDManager *>(pvParameter);
|
||||||
|
TickType_t lastWakeTime = xTaskGetTickCount();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
ledManager->handleLED();
|
ledManager->handleLED();
|
||||||
vTaskDelay(ledManager->getTimeToDelayFor());
|
const TickType_t delayTicks = pdMS_TO_TICKS(ledManager->getTimeToDelayFor());
|
||||||
|
// Ensure at least 1 tick delay to yield CPU
|
||||||
|
vTaskDelayUntil(&lastWakeTime, delayTicks > 0 ? delayTicks : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <StateManager.hpp>
|
#include <StateManager.hpp>
|
||||||
|
#include <ProjectConfig.hpp>
|
||||||
#include <helpers.hpp>
|
#include <helpers.hpp>
|
||||||
|
|
||||||
// it kinda looks like different boards have these states swapped
|
// it kinda looks like different boards have these states swapped
|
||||||
@@ -41,12 +42,16 @@ typedef std::unordered_map<LEDStates_e, LEDStage>
|
|||||||
class LEDManager
|
class LEDManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LEDManager(gpio_num_t blink_led_pin, gpio_num_t illumninator_led_pin, QueueHandle_t ledStateQueue);
|
LEDManager(gpio_num_t blink_led_pin, gpio_num_t illumninator_led_pin, QueueHandle_t ledStateQueue, std::shared_ptr<ProjectConfig> deviceConfig);
|
||||||
|
|
||||||
void setup();
|
void setup();
|
||||||
void handleLED();
|
void handleLED();
|
||||||
size_t getTimeToDelayFor() const { return timeToDelayFor; }
|
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:
|
private:
|
||||||
void toggleLED(bool state) const;
|
void toggleLED(bool state) const;
|
||||||
void displayCurrentPattern();
|
void displayCurrentPattern();
|
||||||
@@ -60,6 +65,7 @@ private:
|
|||||||
|
|
||||||
LEDStates_e buffer;
|
LEDStates_e buffer;
|
||||||
LEDStates_e currentState;
|
LEDStates_e currentState;
|
||||||
|
std::shared_ptr<ProjectConfig> deviceConfig;
|
||||||
|
|
||||||
size_t currentPatternIndex = 0;
|
size_t currentPatternIndex = 0;
|
||||||
size_t timeToDelayFor = 100;
|
size_t timeToDelayFor = 100;
|
||||||
|
|||||||
@@ -21,35 +21,49 @@ struct BaseConfigModel
|
|||||||
Preferences *pref;
|
Preferences *pref;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class StreamingMode {
|
enum class StreamingMode
|
||||||
|
{
|
||||||
AUTO,
|
AUTO,
|
||||||
UVC,
|
UVC,
|
||||||
WIFI,
|
WIFI,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DeviceMode_t : BaseConfigModel {
|
struct DeviceMode_t : BaseConfigModel
|
||||||
|
{
|
||||||
StreamingMode mode;
|
StreamingMode mode;
|
||||||
explicit DeviceMode_t(Preferences *pref) : BaseConfigModel(pref), mode(StreamingMode::AUTO) {}
|
explicit DeviceMode_t(Preferences *pref) : BaseConfigModel(pref), mode(StreamingMode::AUTO) {}
|
||||||
|
|
||||||
void load() {
|
void load()
|
||||||
int stored_mode = this->pref->getInt("mode", 0);
|
{
|
||||||
|
// Default mode can be controlled via sdkconfig:
|
||||||
|
// - If CONFIG_GENERAL_DEFAULT_WIRED_MODE is enabled, default to UVC
|
||||||
|
// - Otherwise default to AUTO
|
||||||
|
int default_mode =
|
||||||
|
#if CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||||
|
static_cast<int>(StreamingMode::UVC);
|
||||||
|
#else
|
||||||
|
static_cast<int>(StreamingMode::AUTO);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int stored_mode = this->pref->getInt("mode", default_mode);
|
||||||
this->mode = static_cast<StreamingMode>(stored_mode);
|
this->mode = static_cast<StreamingMode>(stored_mode);
|
||||||
ESP_LOGI("DeviceMode", "Loaded device mode: %d", stored_mode);
|
ESP_LOGI("DeviceMode", "Loaded device mode: %d", stored_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void save() const {
|
void save() const
|
||||||
|
{
|
||||||
this->pref->putInt("mode", static_cast<int>(this->mode));
|
this->pref->putInt("mode", static_cast<int>(this->mode));
|
||||||
ESP_LOGI("DeviceMode", "Saved device mode: %d", static_cast<int>(this->mode));
|
ESP_LOGI("DeviceMode", "Saved device mode: %d", static_cast<int>(this->mode));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct DeviceConfig_t : BaseConfigModel
|
struct DeviceConfig_t : BaseConfigModel
|
||||||
{
|
{
|
||||||
DeviceConfig_t(Preferences *pref) : BaseConfigModel(pref) {}
|
DeviceConfig_t(Preferences *pref) : BaseConfigModel(pref) {}
|
||||||
|
|
||||||
std::string OTALogin;
|
std::string OTALogin;
|
||||||
std::string OTAPassword;
|
std::string OTAPassword;
|
||||||
|
int led_external_pwm_duty_cycle;
|
||||||
int OTAPort;
|
int OTAPort;
|
||||||
|
|
||||||
void load()
|
void load()
|
||||||
@@ -57,20 +71,23 @@ struct DeviceConfig_t : BaseConfigModel
|
|||||||
this->OTALogin = this->pref->getString("OTALogin", "openiris");
|
this->OTALogin = this->pref->getString("OTALogin", "openiris");
|
||||||
this->OTAPassword = this->pref->getString("OTAPassword", "openiris");
|
this->OTAPassword = this->pref->getString("OTAPassword", "openiris");
|
||||||
this->OTAPort = this->pref->getInt("OTAPort", 3232);
|
this->OTAPort = this->pref->getInt("OTAPort", 3232);
|
||||||
|
this->led_external_pwm_duty_cycle = this->pref->getInt("led_ext_pwm", CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE);
|
||||||
};
|
};
|
||||||
|
|
||||||
void save() const {
|
void save() const
|
||||||
|
{
|
||||||
this->pref->putString("OTALogin", this->OTALogin.c_str());
|
this->pref->putString("OTALogin", this->OTALogin.c_str());
|
||||||
this->pref->putString("OTAPassword", this->OTAPassword.c_str());
|
this->pref->putString("OTAPassword", this->OTAPassword.c_str());
|
||||||
this->pref->putInt("OTAPort", this->OTAPort);
|
this->pref->putInt("OTAPort", this->OTAPort);
|
||||||
|
this->pref->putInt("led_ext_pwm", this->led_external_pwm_duty_cycle);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string toRepresentation() const
|
std::string toRepresentation() const
|
||||||
{
|
{
|
||||||
return Helpers::format_string(
|
return Helpers::format_string(
|
||||||
"\"device_config\": {\"OTALogin\": \"%s\", \"OTAPassword\": \"%s\", "
|
"\"device_config\": {\"OTALogin\": \"%s\", \"OTAPassword\": \"%s\", "
|
||||||
"\"OTAPort\": %u}",
|
"\"OTAPort\": %u, \"led_external_pwm_duty_cycle\": %u}",
|
||||||
this->OTALogin.c_str(), this->OTAPassword.c_str(), this->OTAPort);
|
this->OTALogin.c_str(), this->OTAPassword.c_str(), this->OTAPort, this->led_external_pwm_duty_cycle);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -94,7 +111,8 @@ struct MDNSConfig_t : BaseConfigModel
|
|||||||
this->hostname = this->pref->getString("hostname", default_hostname);
|
this->hostname = this->pref->getString("hostname", default_hostname);
|
||||||
};
|
};
|
||||||
|
|
||||||
void save() const {
|
void save() const
|
||||||
|
{
|
||||||
this->pref->putString("hostname", this->hostname.c_str());
|
this->pref->putString("hostname", this->hostname.c_str());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,7 +143,8 @@ struct CameraConfig_t : BaseConfigModel
|
|||||||
this->brightness = this->pref->getInt("brightness", 2);
|
this->brightness = this->pref->getInt("brightness", 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
void save() const {
|
void save() const
|
||||||
|
{
|
||||||
this->pref->putInt("vflip", this->vflip);
|
this->pref->putInt("vflip", this->vflip);
|
||||||
this->pref->putInt("href", this->href);
|
this->pref->putInt("href", this->href);
|
||||||
this->pref->putInt("framesize", this->framesize);
|
this->pref->putInt("framesize", this->framesize);
|
||||||
@@ -191,7 +210,8 @@ struct WiFiConfig_t : BaseConfigModel
|
|||||||
index, this->name.c_str(), this->ssid.c_str(), this->channel);
|
index, this->name.c_str(), this->ssid.c_str(), this->channel);
|
||||||
};
|
};
|
||||||
|
|
||||||
void save() const {
|
void save() const
|
||||||
|
{
|
||||||
char buffer[2];
|
char buffer[2];
|
||||||
auto const iter_str = std::string(Helpers::itoa(this->index, buffer, 10));
|
auto const iter_str = std::string(Helpers::itoa(this->index, buffer, 10));
|
||||||
|
|
||||||
@@ -228,7 +248,8 @@ struct AP_WiFiConfig_t : BaseConfigModel
|
|||||||
this->password = this->pref->getString("apPassword", CONFIG_WIFI_AP_PASSWORD);
|
this->password = this->pref->getString("apPassword", CONFIG_WIFI_AP_PASSWORD);
|
||||||
};
|
};
|
||||||
|
|
||||||
void save() const {
|
void save() const
|
||||||
|
{
|
||||||
this->pref->putString("apSSID", this->ssid.c_str());
|
this->pref->putString("apSSID", this->ssid.c_str());
|
||||||
this->pref->putString("apPass", this->password.c_str());
|
this->pref->putString("apPass", this->password.c_str());
|
||||||
this->pref->putUInt("apChannel", this->channel);
|
this->pref->putUInt("apChannel", this->channel);
|
||||||
@@ -254,7 +275,8 @@ struct WiFiTxPower_t : BaseConfigModel
|
|||||||
this->power = this->pref->getUInt("txpower", 52);
|
this->power = this->pref->getUInt("txpower", 52);
|
||||||
};
|
};
|
||||||
|
|
||||||
void save() const {
|
void save() const
|
||||||
|
{
|
||||||
this->pref->putUInt("txpower", this->power);
|
this->pref->putUInt("txpower", this->power);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ ProjectConfig::ProjectConfig(Preferences *pref) : pref(pref),
|
|||||||
|
|
||||||
ProjectConfig::~ProjectConfig() = default;
|
ProjectConfig::~ProjectConfig() = default;
|
||||||
|
|
||||||
void ProjectConfig::save() const {
|
void ProjectConfig::save() const
|
||||||
|
{
|
||||||
ESP_LOGD(CONFIGURATION_TAG, "Saving project config");
|
ESP_LOGD(CONFIGURATION_TAG, "Saving project config");
|
||||||
this->config.device.save();
|
this->config.device.save();
|
||||||
this->config.device_mode.save();
|
this->config.device_mode.save();
|
||||||
@@ -92,7 +93,7 @@ bool ProjectConfig::reset()
|
|||||||
//! DeviceConfig
|
//! DeviceConfig
|
||||||
//*
|
//*
|
||||||
//**********************************************************************************************************************
|
//**********************************************************************************************************************
|
||||||
void ProjectConfig::setDeviceConfig(const std::string &OTALogin,
|
void ProjectConfig::setOTAConfig(const std::string &OTALogin,
|
||||||
const std::string &OTAPassword,
|
const std::string &OTAPassword,
|
||||||
const int OTAPort)
|
const int OTAPort)
|
||||||
{
|
{
|
||||||
@@ -100,6 +101,14 @@ void ProjectConfig::setDeviceConfig(const std::string &OTALogin,
|
|||||||
this->config.device.OTALogin.assign(OTALogin);
|
this->config.device.OTALogin.assign(OTALogin);
|
||||||
this->config.device.OTAPassword.assign(OTAPassword);
|
this->config.device.OTAPassword.assign(OTAPassword);
|
||||||
this->config.device.OTAPort = OTAPort;
|
this->config.device.OTAPort = OTAPort;
|
||||||
|
this->config.device.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::setLEDDUtyCycleConfig(int led_external_pwm_duty_cycle)
|
||||||
|
{
|
||||||
|
this->config.device.led_external_pwm_duty_cycle = led_external_pwm_duty_cycle;
|
||||||
|
ESP_LOGI(CONFIGURATION_TAG, "Setting duty cycle to %d", led_external_pwm_duty_cycle);
|
||||||
|
this->config.device.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectConfig::setMDNSConfig(const std::string &hostname)
|
void ProjectConfig::setMDNSConfig(const std::string &hostname)
|
||||||
@@ -120,6 +129,7 @@ void ProjectConfig::setCameraConfig(const uint8_t vflip,
|
|||||||
this->config.camera.framesize = framesize;
|
this->config.camera.framesize = framesize;
|
||||||
this->config.camera.quality = quality;
|
this->config.camera.quality = quality;
|
||||||
this->config.camera.brightness = brightness;
|
this->config.camera.brightness = brightness;
|
||||||
|
this->config.camera.save();
|
||||||
|
|
||||||
ESP_LOGD(CONFIGURATION_TAG, "Updating Camera config");
|
ESP_LOGD(CONFIGURATION_TAG, "Updating Camera config");
|
||||||
}
|
}
|
||||||
@@ -205,6 +215,7 @@ void ProjectConfig::deleteWifiConfig(const std::string &networkName)
|
|||||||
void ProjectConfig::setWiFiTxPower(uint8_t power)
|
void ProjectConfig::setWiFiTxPower(uint8_t power)
|
||||||
{
|
{
|
||||||
this->config.txpower.power = power;
|
this->config.txpower.power = power;
|
||||||
|
this->config.txpower.save();
|
||||||
ESP_LOGD(CONFIGURATION_TAG, "Updating wifi tx power");
|
ESP_LOGD(CONFIGURATION_TAG, "Updating wifi tx power");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,10 +226,12 @@ void ProjectConfig::setAPWifiConfig(const std::string &ssid,
|
|||||||
this->config.ap_network.ssid.assign(ssid);
|
this->config.ap_network.ssid.assign(ssid);
|
||||||
this->config.ap_network.password.assign(password);
|
this->config.ap_network.password.assign(password);
|
||||||
this->config.ap_network.channel = channel;
|
this->config.ap_network.channel = channel;
|
||||||
|
this->config.ap_network.save();
|
||||||
ESP_LOGD(CONFIGURATION_TAG, "Updating access point config");
|
ESP_LOGD(CONFIGURATION_TAG, "Updating access point config");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectConfig::setDeviceMode(const StreamingMode deviceMode) {
|
void ProjectConfig::setDeviceMode(const StreamingMode deviceMode)
|
||||||
|
{
|
||||||
this->config.device_mode.mode = deviceMode;
|
this->config.device_mode.mode = deviceMode;
|
||||||
this->config.device_mode.save(); // Save immediately
|
this->config.device_mode.save(); // Save immediately
|
||||||
}
|
}
|
||||||
@@ -258,10 +271,12 @@ TrackerConfig_t &ProjectConfig::getTrackerConfig()
|
|||||||
return this->config;
|
return this->config;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceMode_t &ProjectConfig::getDeviceModeConfig() {
|
DeviceMode_t &ProjectConfig::getDeviceModeConfig()
|
||||||
|
{
|
||||||
return this->config.device_mode;
|
return this->config.device_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamingMode ProjectConfig::getDeviceMode() {
|
StreamingMode ProjectConfig::getDeviceMode()
|
||||||
|
{
|
||||||
return this->config.device_mode.mode;
|
return this->config.device_mode.mode;
|
||||||
}
|
}
|
||||||
@@ -22,11 +22,6 @@ public:
|
|||||||
void load();
|
void load();
|
||||||
void save() const;
|
void save() const;
|
||||||
|
|
||||||
void wifiConfigSave();
|
|
||||||
void cameraConfigSave();
|
|
||||||
void deviceConfigSave();
|
|
||||||
void mdnsConfigSave();
|
|
||||||
void wifiTxPowerConfigSave();
|
|
||||||
bool reset();
|
bool reset();
|
||||||
|
|
||||||
DeviceConfig_t &getDeviceConfig();
|
DeviceConfig_t &getDeviceConfig();
|
||||||
@@ -38,9 +33,10 @@ public:
|
|||||||
WiFiTxPower_t &getWiFiTxPowerConfig();
|
WiFiTxPower_t &getWiFiTxPowerConfig();
|
||||||
TrackerConfig_t &getTrackerConfig();
|
TrackerConfig_t &getTrackerConfig();
|
||||||
|
|
||||||
void setDeviceConfig(const std::string &OTALogin,
|
void setOTAConfig(const std::string &OTALogin,
|
||||||
const std::string &OTAPassword,
|
const std::string &OTAPassword,
|
||||||
int OTAPort);
|
int OTAPort);
|
||||||
|
void setLEDDUtyCycleConfig(int led_external_pwm_duty_cycle);
|
||||||
void setMDNSConfig(const std::string &hostname);
|
void setMDNSConfig(const std::string &hostname);
|
||||||
void setCameraConfig(uint8_t vflip,
|
void setCameraConfig(uint8_t vflip,
|
||||||
uint8_t framesize,
|
uint8_t framesize,
|
||||||
|
|||||||
@@ -24,13 +24,36 @@ void SerialManager::try_receive()
|
|||||||
int current_position = 0;
|
int current_position = 0;
|
||||||
int len = usb_serial_jtag_read_bytes(this->temp_data, 256, 1000 / 20);
|
int len = usb_serial_jtag_read_bytes(this->temp_data, 256, 1000 / 20);
|
||||||
|
|
||||||
|
// If driver is uninstalled or an error occurs, abort read gracefully
|
||||||
|
if (len < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// since we've got something on the serial port
|
// since we've got something on the serial port
|
||||||
// we gotta keep reading until we've got the whole message
|
// we gotta keep reading until we've got the whole message
|
||||||
while (len)
|
while (len > 0)
|
||||||
{
|
{
|
||||||
memcpy(this->data + current_position, this->temp_data, len);
|
// Prevent buffer overflow
|
||||||
|
if (current_position + len >= BUF_SIZE)
|
||||||
|
{
|
||||||
|
int copy_len = BUF_SIZE - 1 - current_position;
|
||||||
|
if (copy_len > 0)
|
||||||
|
{
|
||||||
|
memcpy(this->data + current_position, this->temp_data, copy_len);
|
||||||
|
current_position += copy_len;
|
||||||
|
}
|
||||||
|
// Drop the rest of the input to avoid overflow
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
memcpy(this->data + current_position, this->temp_data, (size_t)len);
|
||||||
current_position += len;
|
current_position += len;
|
||||||
len = usb_serial_jtag_read_bytes(this->temp_data, 256, 1000 / 20);
|
len = usb_serial_jtag_read_bytes(this->temp_data, 256, 1000 / 20);
|
||||||
|
if (len < 0)
|
||||||
|
{
|
||||||
|
// Driver likely uninstalled during handover; stop processing this cycle
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_position)
|
if (current_position)
|
||||||
@@ -44,7 +67,8 @@ void SerialManager::try_receive()
|
|||||||
|
|
||||||
const auto result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data)));
|
const auto result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data)));
|
||||||
const auto resultMessage = result.getResult();
|
const auto resultMessage = result.getResult();
|
||||||
usb_serial_jtag_write_bytes(resultMessage.c_str(), resultMessage.length(), 1000 / 20);
|
int written = usb_serial_jtag_write_bytes(resultMessage.c_str(), resultMessage.length(), 1000 / 20);
|
||||||
|
(void)written; // ignore errors if driver already uninstalled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +103,7 @@ void SerialManager::send_heartbeat()
|
|||||||
sprintf(heartbeat, "{\"heartbeat\":\"openiris_setup_mode\",\"serial\":\"%s\"}\n", serial_number);
|
sprintf(heartbeat, "{\"heartbeat\":\"openiris_setup_mode\",\"serial\":\"%s\"}\n", serial_number);
|
||||||
|
|
||||||
usb_serial_jtag_write_bytes(heartbeat, strlen(heartbeat), 1000 / 20);
|
usb_serial_jtag_write_bytes(heartbeat, strlen(heartbeat), 1000 / 20);
|
||||||
|
// Ignore return value; if the driver was uninstalled, this is a no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SerialManager::should_send_heartbeat()
|
bool SerialManager::should_send_heartbeat()
|
||||||
@@ -124,3 +149,18 @@ void HandleSerialManagerTask(void *pvParameters)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SerialManager::shutdown()
|
||||||
|
{
|
||||||
|
// Stop heartbeats; timer will be deleted by main if needed.
|
||||||
|
// Uninstall the USB Serial JTAG driver to free the internal USB for TinyUSB.
|
||||||
|
esp_err_t err = usb_serial_jtag_driver_uninstall();
|
||||||
|
if (err == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGI("[SERIAL]", "usb_serial_jtag driver uninstalled");
|
||||||
|
}
|
||||||
|
else if (err != ESP_ERR_INVALID_STATE)
|
||||||
|
{
|
||||||
|
ESP_LOGW("[SERIAL]", "usb_serial_jtag_driver_uninstall returned %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ public:
|
|||||||
void send_heartbeat();
|
void send_heartbeat();
|
||||||
bool should_send_heartbeat();
|
bool should_send_heartbeat();
|
||||||
void notify_startup_command_received();
|
void notify_startup_command_received();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<CommandManager> commandManager;
|
std::shared_ptr<CommandManager> commandManager;
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ esp_err_t StreamHelpers::stream(httpd_req_t *req)
|
|||||||
size_t _jpg_buf_len = 0;
|
size_t _jpg_buf_len = 0;
|
||||||
uint8_t *_jpg_buf = nullptr;
|
uint8_t *_jpg_buf = nullptr;
|
||||||
|
|
||||||
char *part_buf[256];
|
// Buffer for multipart header; was mistakenly declared as array of pointers
|
||||||
|
char part_buf[256];
|
||||||
static int64_t last_frame = 0;
|
static int64_t last_frame = 0;
|
||||||
if (!last_frame)
|
if (!last_frame)
|
||||||
last_frame = esp_timer_get_time();
|
last_frame = esp_timer_get_time();
|
||||||
@@ -55,7 +56,7 @@ esp_err_t StreamHelpers::stream(httpd_req_t *req)
|
|||||||
response = httpd_resp_send_chunk(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
|
response = httpd_resp_send_chunk(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
|
||||||
if (response == ESP_OK)
|
if (response == ESP_OK)
|
||||||
{
|
{
|
||||||
size_t hlen = snprintf((char *)part_buf, 128, STREAM_PART, _jpg_buf_len, _timestamp.tv_sec, _timestamp.tv_usec);
|
size_t hlen = snprintf((char *)part_buf, sizeof(part_buf), STREAM_PART, _jpg_buf_len, _timestamp.tv_sec, _timestamp.tv_usec);
|
||||||
response = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
|
response = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
|
||||||
}
|
}
|
||||||
if (response == ESP_OK)
|
if (response == ESP_OK)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
idf_component_register(SRCS "UVCStream/UVCStream.cpp"
|
idf_component_register(SRCS "UVCStream/UVCStream.cpp"
|
||||||
INCLUDE_DIRS "UVCStream"
|
INCLUDE_DIRS "UVCStream"
|
||||||
REQUIRES esp_timer esp32-camera StateManager usb_device_uvc CameraManager
|
REQUIRES esp_timer esp32-camera StateManager usb_device_uvc CameraManager Helpers
|
||||||
)
|
)
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
#include "UVCStream.hpp"
|
#include "UVCStream.hpp"
|
||||||
constexpr int UVC_MAX_FRAMESIZE_SIZE(75 * 1024);
|
#include <cstdio> // for snprintf
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
// no deps on main globals here; handover is performed in main before calling setup when needed
|
||||||
|
|
||||||
static const char *UVC_STREAM_TAG = "[UVC DEVICE]";
|
static const char *UVC_STREAM_TAG = "[UVC DEVICE]";
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
static char serial_number_str[18];
|
static char serial_number_str[13];
|
||||||
|
|
||||||
const char *get_uvc_device_name()
|
const char *get_uvc_device_name()
|
||||||
{
|
{
|
||||||
@@ -23,17 +26,19 @@ extern "C" {
|
|||||||
return CONFIG_TUSB_SERIAL_NUM;
|
return CONFIG_TUSB_SERIAL_NUM;
|
||||||
}
|
}
|
||||||
|
|
||||||
sniprintf(serial_number_str, sizeof(serial_number_str), "%02X:%02X:%02X:%02X:%02X:%02X",
|
// 12 hex chars without separators
|
||||||
mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5]
|
snprintf(serial_number_str, sizeof(serial_number_str), "%02X%02X%02X%02X%02X%02X",
|
||||||
);
|
mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5]);
|
||||||
}
|
}
|
||||||
return serial_number_str;
|
return serial_number_str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// single definition of shared framebuffer storage
|
||||||
|
UVCStreamHelpers::fb_t UVCStreamHelpers::s_fb = {};
|
||||||
|
|
||||||
static esp_err_t UVCStreamHelpers::camera_start_cb(uvc_format_t format, int width, int height, int rate, void *cb_ctx)
|
static esp_err_t UVCStreamHelpers::camera_start_cb(uvc_format_t format, int width, int height, int rate, void *cb_ctx)
|
||||||
{
|
{
|
||||||
(void)cb_ctx;
|
|
||||||
ESP_LOGI(UVC_STREAM_TAG, "Camera Start");
|
ESP_LOGI(UVC_STREAM_TAG, "Camera Start");
|
||||||
ESP_LOGI(UVC_STREAM_TAG, "Format: %d, width: %d, height: %d, rate: %d", format, width, height, rate);
|
ESP_LOGI(UVC_STREAM_TAG, "Format: %d, width: %d, height: %d, rate: %d", format, width, height, rate);
|
||||||
framesize_t frame_size = FRAMESIZE_QVGA;
|
framesize_t frame_size = FRAMESIZE_QVGA;
|
||||||
@@ -78,7 +83,7 @@ static void UVCStreamHelpers::camera_stop_cb(void *cb_ctx)
|
|||||||
|
|
||||||
static uvc_fb_t *UVCStreamHelpers::camera_fb_get_cb(void *cb_ctx)
|
static uvc_fb_t *UVCStreamHelpers::camera_fb_get_cb(void *cb_ctx)
|
||||||
{
|
{
|
||||||
(void)cb_ctx;
|
auto *mgr = static_cast<UVCStreamManager *>(cb_ctx);
|
||||||
s_fb.cam_fb_p = esp_camera_fb_get();
|
s_fb.cam_fb_p = esp_camera_fb_get();
|
||||||
|
|
||||||
if (!s_fb.cam_fb_p)
|
if (!s_fb.cam_fb_p)
|
||||||
@@ -86,6 +91,31 @@ static uvc_fb_t *UVCStreamHelpers::camera_fb_get_cb(void *cb_ctx)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------------------
|
||||||
|
// Pace frames to exactly 60 fps (drop extras). Uses fixed-point accumulator
|
||||||
|
// to achieve an exact average of 60.000 fps without drifting.
|
||||||
|
static int64_t next_deadline_us = 0;
|
||||||
|
static int rem_acc = 0; // remainder accumulator for 1e6 % fps distribution
|
||||||
|
constexpr int target_fps = 60;
|
||||||
|
constexpr int64_t us_per_sec = 1000000LL;
|
||||||
|
constexpr int base_interval_us = us_per_sec / target_fps; // 16666
|
||||||
|
constexpr int rem_us = us_per_sec % target_fps; // 40
|
||||||
|
|
||||||
|
const int64_t now_us = esp_timer_get_time();
|
||||||
|
if (next_deadline_us == 0)
|
||||||
|
{
|
||||||
|
// First frame: allow immediately and schedule next slot from now
|
||||||
|
next_deadline_us = now_us;
|
||||||
|
}
|
||||||
|
if (now_us < next_deadline_us)
|
||||||
|
{
|
||||||
|
// Too early for next frame: drop this camera buffer
|
||||||
|
esp_camera_fb_return(s_fb.cam_fb_p);
|
||||||
|
s_fb.cam_fb_p = nullptr;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
s_fb.uvc_fb.buf = s_fb.cam_fb_p->buf;
|
s_fb.uvc_fb.buf = s_fb.cam_fb_p->buf;
|
||||||
s_fb.uvc_fb.len = s_fb.cam_fb_p->len;
|
s_fb.uvc_fb.len = s_fb.cam_fb_p->len;
|
||||||
s_fb.uvc_fb.width = s_fb.cam_fb_p->width;
|
s_fb.uvc_fb.width = s_fb.cam_fb_p->width;
|
||||||
@@ -93,13 +123,28 @@ static uvc_fb_t *UVCStreamHelpers::camera_fb_get_cb(void *cb_ctx)
|
|||||||
s_fb.uvc_fb.format = UVC_FORMAT_JPEG; // we gotta make sure we're ALWAYS using JPEG
|
s_fb.uvc_fb.format = UVC_FORMAT_JPEG; // we gotta make sure we're ALWAYS using JPEG
|
||||||
s_fb.uvc_fb.timestamp = s_fb.cam_fb_p->timestamp;
|
s_fb.uvc_fb.timestamp = s_fb.cam_fb_p->timestamp;
|
||||||
|
|
||||||
if (s_fb.uvc_fb.len > UVC_MAX_FRAMESIZE_SIZE)
|
// Ensure frame fits into configured UVC transfer buffer
|
||||||
|
if (mgr && s_fb.uvc_fb.len > mgr->getUvcBufferSize())
|
||||||
{
|
{
|
||||||
ESP_LOGE(UVC_STREAM_TAG, "Frame size %d is larger than max frame size %d", s_fb.uvc_fb.len, UVC_MAX_FRAMESIZE_SIZE);
|
ESP_LOGE(UVC_STREAM_TAG, "Frame size %d exceeds UVC buffer size %u", (int)s_fb.uvc_fb.len, (unsigned)mgr->getUvcBufferSize());
|
||||||
esp_camera_fb_return(s_fb.cam_fb_p);
|
esp_camera_fb_return(s_fb.cam_fb_p);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------------------
|
||||||
|
// Schedule the next allowed frame time: base interval plus distributed remainder
|
||||||
|
rem_acc += rem_us;
|
||||||
|
int extra_us = 0;
|
||||||
|
if (rem_acc >= target_fps)
|
||||||
|
{
|
||||||
|
rem_acc -= target_fps;
|
||||||
|
extra_us = 1;
|
||||||
|
}
|
||||||
|
// Accumulate from the previous deadline to avoid drift; if we are badly late, catch up from now
|
||||||
|
const int64_t base_next = next_deadline_us + base_interval_us + extra_us;
|
||||||
|
next_deadline_us = (base_next < now_us) ? now_us : base_next;
|
||||||
|
//--------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
return &s_fb.uvc_fb;
|
return &s_fb.uvc_fb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,14 +158,15 @@ static void UVCStreamHelpers::camera_fb_return_cb(uvc_fb_t *fb, void *cb_ctx)
|
|||||||
esp_err_t UVCStreamManager::setup()
|
esp_err_t UVCStreamManager::setup()
|
||||||
{
|
{
|
||||||
|
|
||||||
#ifndef CONFIG_GENERAL_WIRED_MODE
|
#ifndef CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||||
ESP_LOGE(UVC_STREAM_TAG, "The board does not support UVC, please, setup WiFi connection.");
|
ESP_LOGE(UVC_STREAM_TAG, "The board does not support UVC, please, setup WiFi connection.");
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ESP_LOGI(UVC_STREAM_TAG, "Setting up UVC Stream");
|
ESP_LOGI(UVC_STREAM_TAG, "Setting up UVC Stream");
|
||||||
|
// Allocate a fixed-size transfer buffer (compile-time constant)
|
||||||
uvc_buffer = static_cast<uint8_t *>(malloc(UVC_MAX_FRAMESIZE_SIZE));
|
uvc_buffer_size = UVCStreamManager::UVC_MAX_FRAMESIZE_SIZE;
|
||||||
|
uvc_buffer = static_cast<uint8_t *>(malloc(uvc_buffer_size));
|
||||||
if (uvc_buffer == nullptr)
|
if (uvc_buffer == nullptr)
|
||||||
{
|
{
|
||||||
ESP_LOGE(UVC_STREAM_TAG, "Allocating buffer for UVC Device failed");
|
ESP_LOGE(UVC_STREAM_TAG, "Allocating buffer for UVC Device failed");
|
||||||
@@ -129,11 +175,12 @@ esp_err_t UVCStreamManager::setup()
|
|||||||
|
|
||||||
uvc_device_config_t config = {
|
uvc_device_config_t config = {
|
||||||
.uvc_buffer = uvc_buffer,
|
.uvc_buffer = uvc_buffer,
|
||||||
.uvc_buffer_size = UVC_MAX_FRAMESIZE_SIZE,
|
.uvc_buffer_size = UVCStreamManager::UVC_MAX_FRAMESIZE_SIZE,
|
||||||
.start_cb = UVCStreamHelpers::camera_start_cb,
|
.start_cb = UVCStreamHelpers::camera_start_cb,
|
||||||
.fb_get_cb = UVCStreamHelpers::camera_fb_get_cb,
|
.fb_get_cb = UVCStreamHelpers::camera_fb_get_cb,
|
||||||
.fb_return_cb = UVCStreamHelpers::camera_fb_return_cb,
|
.fb_return_cb = UVCStreamHelpers::camera_fb_return_cb,
|
||||||
.stop_cb = UVCStreamHelpers::camera_stop_cb,
|
.stop_cb = UVCStreamHelpers::camera_stop_cb,
|
||||||
|
.cb_ctx = this,
|
||||||
};
|
};
|
||||||
|
|
||||||
esp_err_t ret = uvc_device_config(0, &config);
|
esp_err_t ret = uvc_device_config(0, &config);
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ namespace UVCStreamHelpers
|
|||||||
uvc_fb_t uvc_fb;
|
uvc_fb_t uvc_fb;
|
||||||
} fb_t;
|
} fb_t;
|
||||||
|
|
||||||
static fb_t s_fb;
|
// single storage is defined in UVCStream.cpp
|
||||||
|
extern fb_t s_fb;
|
||||||
|
|
||||||
static esp_err_t camera_start_cb(uvc_format_t format, int width, int height, int rate, void *cb_ctx);
|
static esp_err_t camera_start_cb(uvc_format_t format, int width, int height, int rate, void *cb_ctx);
|
||||||
static void camera_stop_cb(void *cb_ctx);
|
static void camera_stop_cb(void *cb_ctx);
|
||||||
@@ -51,10 +52,14 @@ namespace UVCStreamHelpers
|
|||||||
class UVCStreamManager
|
class UVCStreamManager
|
||||||
{
|
{
|
||||||
uint8_t *uvc_buffer = nullptr;
|
uint8_t *uvc_buffer = nullptr;
|
||||||
|
uint32_t uvc_buffer_size = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// Compile-time buffer size; keep conservative headroom for MJPEG QVGA
|
||||||
|
static constexpr uint32_t UVC_MAX_FRAMESIZE_SIZE = 75 * 1024;
|
||||||
esp_err_t setup();
|
esp_err_t setup();
|
||||||
esp_err_t start();
|
esp_err_t start();
|
||||||
|
uint32_t getUvcBufferSize() const { return uvc_buffer_size; }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // UVCSTREAM_HPP
|
#endif // UVCSTREAM_HPP
|
||||||
|
|||||||
@@ -21,15 +21,16 @@
|
|||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "tusb.h"
|
#include "tusb.h"
|
||||||
#include "usb_descriptors.h"
|
#include "usb_descriptors.h"
|
||||||
|
#include <string.h> // memcpy, strlen
|
||||||
|
|
||||||
//--------------------------------------------------------------------+
|
//--------------------------------------------------------------------+
|
||||||
// Device Descriptors
|
// Device Descriptors
|
||||||
//--------------------------------------------------------------------+
|
//--------------------------------------------------------------------+
|
||||||
|
// Device descriptor: identifies this as a composite device using IAD for UVC
|
||||||
tusb_desc_device_t const desc_device = {
|
tusb_desc_device_t const desc_device = {
|
||||||
.bLength = sizeof(tusb_desc_device_t),
|
.bLength = sizeof(tusb_desc_device_t),
|
||||||
.bDescriptorType = TUSB_DESC_DEVICE,
|
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||||
@@ -63,6 +64,16 @@ uint8_t const *tud_descriptor_device_cb(void)
|
|||||||
//--------------------------------------------------------------------+
|
//--------------------------------------------------------------------+
|
||||||
// Configuration Descriptor
|
// Configuration Descriptor
|
||||||
//--------------------------------------------------------------------+
|
//--------------------------------------------------------------------+
|
||||||
|
// String descriptor indices used in interface descriptors
|
||||||
|
#define STRID_LANGID 0
|
||||||
|
#define STRID_MANUFACTURER 1
|
||||||
|
#define STRID_PRODUCT 2
|
||||||
|
#define STRID_SERIAL 3
|
||||||
|
#define STRID_UVC_CAM1 4
|
||||||
|
|
||||||
|
// Endpoint numbers for UVC video IN endpoints (device -> host)
|
||||||
|
#define EPNUM_CAM1_VIDEO_IN 0x81
|
||||||
|
|
||||||
#if CFG_TUD_CAM1_VIDEO_STREAMING_BULK
|
#if CFG_TUD_CAM1_VIDEO_STREAMING_BULK
|
||||||
|
|
||||||
#if CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
#if CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
||||||
@@ -101,76 +112,39 @@ uint8_t const *tud_descriptor_device_cb(void)
|
|||||||
|
|
||||||
#endif // CFG_TUD_CAM1_VIDEO_STREAMING_BULK
|
#endif // CFG_TUD_CAM1_VIDEO_STREAMING_BULK
|
||||||
|
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
// Total length of this configuration
|
||||||
#if CFG_TUD_CAM2_VIDEO_STREAMING_BULK
|
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CAM1_VIDEO_CAPTURE_DESC_LEN)
|
||||||
|
|
||||||
#if CONFIG_UVC_CAM2_MULTI_FRAMESIZE
|
// Full-speed configuration descriptor
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM2
|
static uint8_t const desc_fs_configuration[] = {
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_MULTI_MJPEG_BULK_LEN(4))
|
// TUD_CONFIG_DESCRIPTOR(config_number, interface_count, string_index,
|
||||||
#elif CONFIG_FORMAT_H264_CAM2
|
// total_length, attributes, power_mA)
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_MULTI_FRAME_BASED_BULK_LEN(4))
|
// attributes: 0 = bus-powered (default). Add TUSB_DESC_CONFIG_ATT_SELF_POWERED or _REMOTE_WAKEUP if needed.
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM2
|
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_MJPEG_BULK_LEN)
|
|
||||||
#elif CONFIG_FORMAT_H264_CAM2
|
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_FRAME_BASED_BULK_LEN)
|
|
||||||
#else
|
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_UNCOMPR_BULK_LEN)
|
|
||||||
#endif
|
|
||||||
#endif // CONFIG_UVC_CAM2_MULTI_FRAMESIZE
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#if CONFIG_UVC_CAM2_MULTI_FRAMESIZE
|
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM2
|
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_MULTI_MJPEG_LEN(4))
|
|
||||||
#elif CONFIG_FORMAT_H264_CAM2
|
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_MULTI_FRAME_BASED_LEN(4))
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM2
|
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_MJPEG_LEN)
|
|
||||||
#elif CONFIG_FORMAT_H264_CAM2
|
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_FRAME_BASED_LEN)
|
|
||||||
#else
|
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_UNCOMPR_LEN)
|
|
||||||
#endif
|
|
||||||
#endif // CONFIG_UVC_CAM2_MULTI_FRAMESIZE
|
|
||||||
|
|
||||||
#endif // CFG_TUD_CAM2_VIDEO_STREAMING_BULK
|
|
||||||
#else
|
|
||||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CAM1_VIDEO_CAPTURE_DESC_LEN + TUD_CAM2_VIDEO_CAPTURE_DESC_LEN)
|
|
||||||
#define EPNUM_CAM1_VIDEO_IN 0x81
|
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
||||||
#define EPNUM_CAM2_VIDEO_IN 0x82
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint8_t const desc_fs_configuration[] = {
|
|
||||||
// Config number, interface count, string index, total length, attribute, power in mA
|
|
||||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0, 500),
|
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0, 500),
|
||||||
// IAD for Video Control
|
// IAD for Video Control
|
||||||
#if CFG_TUD_CAM1_VIDEO_STREAMING_BULK
|
#if CFG_TUD_CAM1_VIDEO_STREAMING_BULK
|
||||||
#if CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
#if CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM1
|
#if CONFIG_FORMAT_MJPEG_CAM1
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG_BULK(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN, CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
// Camera 1, multi-size MJPEG over BULK
|
||||||
|
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG_BULK(STRID_UVC_CAM1, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN, CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||||
#elif CONFIG_FORMAT_H264_CAM1
|
#elif CONFIG_FORMAT_H264_CAM1
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264_BULK(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN, CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
// Camera 1, multi-size H.264 over BULK
|
||||||
|
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264_BULK(STRID_UVC_CAM1, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN, CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM1
|
#if CONFIG_FORMAT_MJPEG_CAM1
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG_BULK(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
// Camera 1, single-size MJPEG over BULK
|
||||||
|
TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG_BULK(STRID_UVC_CAM1, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
||||||
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
||||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||||
#elif CONFIG_FORMAT_H264_CAM1
|
#elif CONFIG_FORMAT_H264_CAM1
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_H264_BULK(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
// Camera 1, single-size H.264 over BULK
|
||||||
|
TUD_VIDEO_CAPTURE_DESCRIPTOR_H264_BULK(STRID_UVC_CAM1, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
||||||
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
||||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||||
#else
|
#else
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
// Camera 1, single-size Uncompressed (YUY2/etc) over BULK
|
||||||
|
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(STRID_UVC_CAM1, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
||||||
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
||||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||||
#endif
|
#endif
|
||||||
@@ -178,75 +152,32 @@ uint8_t const desc_fs_configuration[] = {
|
|||||||
#else
|
#else
|
||||||
#if CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
#if CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM1
|
#if CONFIG_FORMAT_MJPEG_CAM1
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN, CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
// Camera 1, multi-size MJPEG over ISO
|
||||||
|
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG(STRID_UVC_CAM1, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN, CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||||
#elif CONFIG_FORMAT_H264_CAM1
|
#elif CONFIG_FORMAT_H264_CAM1
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN, CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
// Camera 1, multi-size H.264 over ISO
|
||||||
|
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264(STRID_UVC_CAM1, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN, CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM1
|
#if CONFIG_FORMAT_MJPEG_CAM1
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
// Camera 1, single-size MJPEG over ISO
|
||||||
|
TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG(STRID_UVC_CAM1, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
||||||
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
||||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||||
#elif CONFIG_FORMAT_H264_CAM1
|
#elif CONFIG_FORMAT_H264_CAM1
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_H264(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
// Camera 1, single-size H.264 over ISO
|
||||||
|
TUD_VIDEO_CAPTURE_DESCRIPTOR_H264(STRID_UVC_CAM1, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
||||||
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
||||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||||
#else
|
#else
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
// Camera 1, single-size Uncompressed over ISO
|
||||||
|
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(STRID_UVC_CAM1, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM1_VIDEO_IN,
|
||||||
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE,
|
||||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||||
#endif
|
#endif
|
||||||
#endif // CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
#endif // CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
||||||
#endif // CFG_TUD_CAM1_VIDEO_STREAMING_BULK
|
#endif // CFG_TUD_CAM1_VIDEO_STREAMING_BULK
|
||||||
|
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
||||||
#if CFG_TUD_CAM2_VIDEO_STREAMING_BULK
|
|
||||||
#if CONFIG_UVC_CAM2_MULTI_FRAMESIZE
|
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM2
|
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG_BULK(4, ITF_NUM_VIDEO_CONTROL_2, EPNUM_CAM2_VIDEO_IN, CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE),
|
|
||||||
#elif CONFIG_FORMAT_H264_CAM2
|
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264_BULK(4, ITF_NUM_VIDEO_CONTROL_2, EPNUM_CAM2_VIDEO_IN, CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE),
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM2
|
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG_BULK(4, ITF_NUM_VIDEO_CONTROL_2, EPNUM_CAM2_VIDEO_IN,
|
|
||||||
UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE,
|
|
||||||
CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE),
|
|
||||||
#elif CONFIG_FORMAT_H264_CAM2
|
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_H264_BULK(4, ITF_NUM_VIDEO_CONTROL_2, EPNUM_CAM2_VIDEO_IN,
|
|
||||||
UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE,
|
|
||||||
CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE),
|
|
||||||
#else
|
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(4, ITF_NUM_VIDEO_CONTROL_2, EPNUM_CAM2_VIDEO_IN,
|
|
||||||
UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE,
|
|
||||||
CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE),
|
|
||||||
#endif
|
|
||||||
#endif // CONFIG_UVC_CAM2_MULTI_FRAMESIZE
|
|
||||||
#else
|
|
||||||
#if CONFIG_UVC_CAM2_MULTI_FRAMESIZE
|
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM2
|
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG(4, ITF_NUM_VIDEO_CONTROL_2, EPNUM_CAM2_VIDEO_IN, CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE),
|
|
||||||
#elif CONFIG_FORMAT_H264_CAM2
|
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264(4, ITF_NUM_VIDEO_CONTROL_2, EPNUM_CAM2_VIDEO_IN, CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE),
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#if CONFIG_FORMAT_MJPEG_CAM2
|
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG(4, ITF_NUM_VIDEO_CONTROL_2, EPNUM_CAM2_VIDEO_IN,
|
|
||||||
UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE,
|
|
||||||
CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE),
|
|
||||||
#elif CONFIG_FORMAT_H264_CAM2
|
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_H264(4, ITF_NUM_VIDEO_CONTROL_2, EPNUM_CAM2_VIDEO_IN,
|
|
||||||
UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE,
|
|
||||||
CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE),
|
|
||||||
#else
|
|
||||||
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(4, ITF_NUM_VIDEO_CONTROL, EPNUM_CAM2_VIDEO_IN,
|
|
||||||
UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE,
|
|
||||||
CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE),
|
|
||||||
#endif
|
|
||||||
#endif // CONFIG_UVC_CAM2_MULTI_FRAMESIZE
|
|
||||||
#endif // CFG_TUD_CAM2_VIDEO_STREAMING_BULK
|
|
||||||
#endif
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Invoked when received GET CONFIGURATION DESCRIPTOR
|
// Invoked when received GET CONFIGURATION DESCRIPTOR
|
||||||
@@ -263,30 +194,26 @@ uint8_t const *tud_descriptor_configuration_cb(uint8_t index)
|
|||||||
// String Descriptors
|
// String Descriptors
|
||||||
//--------------------------------------------------------------------+
|
//--------------------------------------------------------------------+
|
||||||
|
|
||||||
// array of pointer to string descriptors
|
// Array of pointers to string literals. Indices must match STRID_* above.
|
||||||
char const *string_desc_arr[] = {
|
char const *string_desc_arr[] = {
|
||||||
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
|
(const char[]){0x09, 0x04}, // 0: Supported language: English (0x0409)
|
||||||
CONFIG_TUSB_MANUFACTURER, // 1: Manufacturer
|
CONFIG_TUSB_MANUFACTURER, // 1: Manufacturer
|
||||||
CONFIG_TUSB_PRODUCT, // 2: Product
|
CONFIG_TUSB_PRODUCT, // 2: Product
|
||||||
CONFIG_TUSB_SERIAL_NUM, // 3: Serials, should use chip ID, overridden with get_serial_number()
|
CONFIG_TUSB_SERIAL_NUM, // 3: Serial (overridden by get_serial_number())
|
||||||
"UVC CAM1", // 4: UVC Interface, default, because we're overriding it get_uvc_device_name(), but we still have to keep the structure
|
"UVC CAM1", // 4: UVC Interface name for Cam1 (overridden by get_uvc_device_name())
|
||||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
||||||
"UVC CAM2", // 5: UVC Interface
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint16_t _desc_str[32];
|
static uint16_t _desc_str[32];
|
||||||
|
|
||||||
__attribute__((weak)) const char *get_uvc_device_name(void)
|
__attribute__((weak)) const char *get_uvc_device_name(void)
|
||||||
{
|
{
|
||||||
// ETVR Override, by default we're reporting ourselves as this, users can override it
|
// Default UVC device name, can be overridden by application
|
||||||
return "UVC OpenIris Camera";
|
return "UVC OpenIris Camera";
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((weak)) const char *get_serial_number(void)
|
__attribute__((weak)) const char *get_serial_number(void)
|
||||||
{
|
{
|
||||||
// ETVR Override, by default we're reporting ourselves as the predefined serial number
|
// Default serial number, can be overridden by application (e.g., chip ID)
|
||||||
// this should get overwritten with a better implementation
|
|
||||||
return CONFIG_TUSB_SERIAL_NUM;
|
return CONFIG_TUSB_SERIAL_NUM;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +221,6 @@ __attribute__((weak)) const char *get_serial_number(void)
|
|||||||
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
|
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
|
||||||
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
|
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
|
||||||
{
|
{
|
||||||
printf("am I being asked for this?");
|
|
||||||
(void)langid;
|
(void)langid;
|
||||||
|
|
||||||
uint8_t chr_count;
|
uint8_t chr_count;
|
||||||
@@ -315,9 +241,10 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const char *str = string_desc_arr[index];
|
const char *str = string_desc_arr[index];
|
||||||
if (index == 3)
|
// Allow dynamic overrides for specific indices
|
||||||
|
if (index == STRID_SERIAL)
|
||||||
str = get_serial_number();
|
str = get_serial_number();
|
||||||
if (index == 4)
|
if (index == STRID_UVC_CAM1)
|
||||||
str = get_uvc_device_name();
|
str = get_uvc_device_name();
|
||||||
if (str == NULL)
|
if (str == NULL)
|
||||||
str = string_desc_arr[index];
|
str = string_desc_arr[index];
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ endmenu
|
|||||||
|
|
||||||
menu "OpenIris: General Configuration"
|
menu "OpenIris: General Configuration"
|
||||||
|
|
||||||
config GENERAL_WIRED_MODE
|
config GENERAL_DEFAULT_WIRED_MODE
|
||||||
bool "Wired mode"
|
bool "Wired mode"
|
||||||
default false
|
default false
|
||||||
|
help
|
||||||
|
Enables UVC (wired) support in the firmware. 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_UVC_DELAY
|
config GENERAL_UVC_DELAY
|
||||||
int "UVC delay (s)"
|
int "UVC delay (s)"
|
||||||
|
|||||||
+26
-13
@@ -22,7 +22,7 @@
|
|||||||
#include <RestAPI.hpp>
|
#include <RestAPI.hpp>
|
||||||
#include <main_globals.hpp>
|
#include <main_globals.hpp>
|
||||||
|
|
||||||
#ifdef CONFIG_GENERAL_WIRED_MODE
|
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||||
#include <UVCStream.hpp>
|
#include <UVCStream.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -49,11 +49,11 @@ StreamServer streamServer(80, stateManager);
|
|||||||
|
|
||||||
auto *restAPI = new RestAPI("http://0.0.0.0:81", commandManager);
|
auto *restAPI = new RestAPI("http://0.0.0.0:81", commandManager);
|
||||||
|
|
||||||
#ifdef CONFIG_GENERAL_WIRED_MODE
|
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||||
UVCStreamManager uvcStream;
|
UVCStreamManager uvcStream;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto *ledManager = new LEDManager(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue);
|
auto ledManager = std::make_shared<LEDManager>(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue, deviceConfig);
|
||||||
auto *serialManager = new SerialManager(commandManager, &timerHandle, deviceConfig);
|
auto *serialManager = new SerialManager(commandManager, &timerHandle, deviceConfig);
|
||||||
|
|
||||||
static void initNVSStorage()
|
static void initNVSStorage()
|
||||||
@@ -95,9 +95,21 @@ void start_video_streaming(void *arg)
|
|||||||
|
|
||||||
if (deviceMode == StreamingMode::UVC)
|
if (deviceMode == StreamingMode::UVC)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_GENERAL_WIRED_MODE
|
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||||
ESP_LOGI("[MAIN]", "Starting UVC streaming mode.");
|
ESP_LOGI("[MAIN]", "Starting UVC streaming mode.");
|
||||||
ESP_LOGI("[MAIN]", "Initializing UVC hardware...");
|
ESP_LOGI("[MAIN]", "Initializing UVC hardware...");
|
||||||
|
// If we were given the Serial task handle, stop the task and uninstall the driver
|
||||||
|
if (arg != nullptr)
|
||||||
|
{
|
||||||
|
const auto serialTaskHandle = static_cast<TaskHandle_t>(arg);
|
||||||
|
vTaskDelete(serialTaskHandle);
|
||||||
|
ESP_LOGI("[MAIN]", "Serial task deleted before UVC init");
|
||||||
|
serialManager->shutdown();
|
||||||
|
ESP_LOGI("[MAIN]", "Serial driver uninstalled");
|
||||||
|
// Leave a small gap for the host to see COM disappear
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
setUsbHandoverDone(true);
|
||||||
|
}
|
||||||
esp_err_t ret = uvcStream.setup();
|
esp_err_t ret = uvcStream.setup();
|
||||||
if (ret != ESP_OK)
|
if (ret != ESP_OK)
|
||||||
{
|
{
|
||||||
@@ -105,6 +117,8 @@ void start_video_streaming(void *arg)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uvcStream.start();
|
uvcStream.start();
|
||||||
|
ESP_LOGI("[MAIN]", "UVC streaming started");
|
||||||
|
return; // UVC path complete, do not fall through to WiFi
|
||||||
#else
|
#else
|
||||||
ESP_LOGE("[MAIN]", "UVC mode selected but the board likely does not support it.");
|
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");
|
ESP_LOGI("[MAIN]", "Falling back to WiFi mode if credentials available");
|
||||||
@@ -193,12 +207,12 @@ void startup_timer_callback(void *arg)
|
|||||||
}
|
}
|
||||||
else if (deviceMode == StreamingMode::UVC)
|
else if (deviceMode == StreamingMode::UVC)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_GENERAL_WIRED_MODE
|
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||||
ESP_LOGI("[MAIN]", "Starting UVC streaming automatically");
|
ESP_LOGI("[MAIN]", "Starting UVC streaming automatically");
|
||||||
activate_streaming(serialTaskHandle);
|
activate_streaming(serialTaskHandle);
|
||||||
#else
|
#else
|
||||||
ESP_LOGE("[MAIN]", "UVC mode selected but CONFIG_GENERAL_WIRED_MODE not enabled in build!");
|
ESP_LOGE("[MAIN]", "UVC mode selected but CONFIG_GENERAL_DEFAULT_WIRED_MODE not enabled in build!");
|
||||||
ESP_LOGI("[MAIN]", "Device will stay in setup mode. Enable CONFIG_GENERAL_WIRED_MODE and rebuild.");
|
ESP_LOGI("[MAIN]", "Device will stay in setup mode. Enable CONFIG_GENERAL_DEFAULT_WIRED_MODE and rebuild.");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -228,6 +242,7 @@ extern "C" void app_main(void)
|
|||||||
dependencyRegistry->registerService<ProjectConfig>(DependencyType::project_config, deviceConfig);
|
dependencyRegistry->registerService<ProjectConfig>(DependencyType::project_config, deviceConfig);
|
||||||
dependencyRegistry->registerService<CameraManager>(DependencyType::camera_manager, cameraHandler);
|
dependencyRegistry->registerService<CameraManager>(DependencyType::camera_manager, cameraHandler);
|
||||||
dependencyRegistry->registerService<WiFiManager>(DependencyType::wifi_manager, wifiManager);
|
dependencyRegistry->registerService<WiFiManager>(DependencyType::wifi_manager, wifiManager);
|
||||||
|
dependencyRegistry->registerService<LEDManager>(DependencyType::led_manager, ledManager);
|
||||||
// uvc plan
|
// uvc plan
|
||||||
// cleanup the logs - done
|
// cleanup the logs - done
|
||||||
// prepare the camera to be initialized with UVC - done?
|
// prepare the camera to be initialized with UVC - done?
|
||||||
@@ -274,10 +289,10 @@ extern "C" void app_main(void)
|
|||||||
// setup CI and building for other boards
|
// setup CI and building for other boards
|
||||||
// finish todos, overhaul stuff a bit
|
// finish todos, overhaul stuff a bit
|
||||||
|
|
||||||
|
// esp_log_set_vprintf(&websocket_logger);
|
||||||
Logo::printASCII();
|
Logo::printASCII();
|
||||||
initNVSStorage();
|
initNVSStorage();
|
||||||
|
deviceConfig->load();
|
||||||
// esp_log_set_vprintf(&websocket_logger);
|
|
||||||
ledManager->setup();
|
ledManager->setup();
|
||||||
|
|
||||||
xTaskCreate(
|
xTaskCreate(
|
||||||
@@ -293,11 +308,10 @@ extern "C" void app_main(void)
|
|||||||
HandleLEDDisplayTask,
|
HandleLEDDisplayTask,
|
||||||
"HandleLEDDisplayTask",
|
"HandleLEDDisplayTask",
|
||||||
1024 * 2,
|
1024 * 2,
|
||||||
ledManager,
|
ledManager.get(),
|
||||||
3,
|
3,
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
deviceConfig->load();
|
|
||||||
serialManager->setup();
|
serialManager->setup();
|
||||||
|
|
||||||
static TaskHandle_t serialManagerHandle = nullptr;
|
static TaskHandle_t serialManagerHandle = nullptr;
|
||||||
@@ -308,8 +322,7 @@ extern "C" void app_main(void)
|
|||||||
1024 * 6,
|
1024 * 6,
|
||||||
serialManager,
|
serialManager,
|
||||||
1, // we only rely on the serial manager during provisioning, we can run it slower
|
1, // we only rely on the serial manager during provisioning, we can run it slower
|
||||||
&serialManagerHandle
|
&serialManagerHandle);
|
||||||
);
|
|
||||||
|
|
||||||
wifiManager->Begin();
|
wifiManager->Begin();
|
||||||
mdnsManager.start();
|
mdnsManager.start();
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ CONFIG_IDF_TOOLCHAIN_GCC=y
|
|||||||
CONFIG_IDF_TARGET_ARCH_XTENSA=y
|
CONFIG_IDF_TARGET_ARCH_XTENSA=y
|
||||||
CONFIG_IDF_TARGET_ARCH="xtensa"
|
CONFIG_IDF_TARGET_ARCH="xtensa"
|
||||||
CONFIG_IDF_TARGET="esp32s3"
|
CONFIG_IDF_TARGET="esp32s3"
|
||||||
CONFIG_IDF_INIT_VERSION="$IDF_INIT_VERSION"
|
CONFIG_IDF_INIT_VERSION="5.4.2"
|
||||||
CONFIG_IDF_TARGET_ESP32S3=y
|
CONFIG_IDF_TARGET_ESP32S3=y
|
||||||
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009
|
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009
|
||||||
|
|
||||||
@@ -570,7 +570,7 @@ CONFIG_ENV_GPIO_OUT_RANGE_MAX=48
|
|||||||
#
|
#
|
||||||
# OpenIris: General Configuration
|
# OpenIris: General Configuration
|
||||||
#
|
#
|
||||||
CONFIG_GENERAL_WIRED_MODE=y
|
# CONFIG_GENERAL_DEFAULT_WIRED_MODE is not set
|
||||||
CONFIG_GENERAL_UVC_DELAY=30
|
CONFIG_GENERAL_UVC_DELAY=30
|
||||||
# end of OpenIris: General Configuration
|
# end of OpenIris: General Configuration
|
||||||
|
|
||||||
@@ -2242,7 +2242,7 @@ CONFIG_FRAMESIZE_QVGA=y
|
|||||||
# CONFIG_FRAMESIZE_SVGA is not set
|
# CONFIG_FRAMESIZE_SVGA is not set
|
||||||
# CONFIG_FRAMESIZE_HD is not set
|
# CONFIG_FRAMESIZE_HD is not set
|
||||||
# CONFIG_FRAMESIZE_FHD is not set
|
# CONFIG_FRAMESIZE_FHD is not set
|
||||||
CONFIG_UVC_CAM1_FRAMERATE=90
|
CONFIG_UVC_CAM1_FRAMERATE=60
|
||||||
CONFIG_UVC_CAM1_FRAMESIZE_WIDTH=240
|
CONFIG_UVC_CAM1_FRAMESIZE_WIDTH=240
|
||||||
CONFIG_UVC_CAM1_FRAMESIZE_HEIGT=240
|
CONFIG_UVC_CAM1_FRAMESIZE_HEIGT=240
|
||||||
CONFIG_UVC_CAM1_MULTI_FRAMESIZE=y
|
CONFIG_UVC_CAM1_MULTI_FRAMESIZE=y
|
||||||
@@ -2257,7 +2257,7 @@ CONFIG_UVC_CAM1_MULTI_FRAMESIZE=y
|
|||||||
#
|
#
|
||||||
CONFIG_UVC_MULTI_FRAME_WIDTH_1=240
|
CONFIG_UVC_MULTI_FRAME_WIDTH_1=240
|
||||||
CONFIG_UVC_MULTI_FRAME_HEIGHT_1=240
|
CONFIG_UVC_MULTI_FRAME_HEIGHT_1=240
|
||||||
CONFIG_UVC_MULTI_FRAME_FPS_1=90
|
CONFIG_UVC_MULTI_FRAME_FPS_1=60
|
||||||
# end of FRAME_SIZE_1
|
# end of FRAME_SIZE_1
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -2265,7 +2265,7 @@ CONFIG_UVC_MULTI_FRAME_FPS_1=90
|
|||||||
#
|
#
|
||||||
CONFIG_UVC_MULTI_FRAME_WIDTH_2=240
|
CONFIG_UVC_MULTI_FRAME_WIDTH_2=240
|
||||||
CONFIG_UVC_MULTI_FRAME_HEIGHT_2=240
|
CONFIG_UVC_MULTI_FRAME_HEIGHT_2=240
|
||||||
CONFIG_UVC_MULTI_FRAME_FPS_2=90
|
CONFIG_UVC_MULTI_FRAME_FPS_2=60
|
||||||
# end of FRAME_SIZE_2
|
# end of FRAME_SIZE_2
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -2273,7 +2273,7 @@ CONFIG_UVC_MULTI_FRAME_FPS_2=90
|
|||||||
#
|
#
|
||||||
CONFIG_UVC_MULTI_FRAME_WIDTH_3=240
|
CONFIG_UVC_MULTI_FRAME_WIDTH_3=240
|
||||||
CONFIG_UVC_MULTI_FRAME_HEIGHT_3=240
|
CONFIG_UVC_MULTI_FRAME_HEIGHT_3=240
|
||||||
CONFIG_UVC_MULTI_FRAME_FPS_3=90
|
CONFIG_UVC_MULTI_FRAME_FPS_3=60
|
||||||
# end of FRAME_SIZE_3
|
# end of FRAME_SIZE_3
|
||||||
# end of UVC_MULTI_FRAME_CONFIG
|
# end of UVC_MULTI_FRAME_CONFIG
|
||||||
|
|
||||||
|
|||||||
@@ -570,7 +570,7 @@ CONFIG_ENV_GPIO_OUT_RANGE_MAX=48
|
|||||||
#
|
#
|
||||||
# OpenIris: General Configuration
|
# OpenIris: General Configuration
|
||||||
#
|
#
|
||||||
# CONFIG_GENERAL_WIRED_MODE is not set
|
# CONFIG_GENERAL_DEFAULT_WIRED_MODE is not set
|
||||||
# CONFIG_GENERAL_UVC_DELAY is not set
|
# CONFIG_GENERAL_UVC_DELAY is not set
|
||||||
# end of OpenIris: General Configuration
|
# end of OpenIris: General Configuration
|
||||||
|
|
||||||
@@ -2242,7 +2242,7 @@ CONFIG_FRAMESIZE_QVGA=y
|
|||||||
# CONFIG_FRAMESIZE_SVGA is not set
|
# CONFIG_FRAMESIZE_SVGA is not set
|
||||||
# CONFIG_FRAMESIZE_HD is not set
|
# CONFIG_FRAMESIZE_HD is not set
|
||||||
# CONFIG_FRAMESIZE_FHD is not set
|
# CONFIG_FRAMESIZE_FHD is not set
|
||||||
CONFIG_UVC_CAM1_FRAMERATE=90
|
CONFIG_UVC_CAM1_FRAMERATE=60
|
||||||
CONFIG_UVC_CAM1_FRAMESIZE_WIDTH=240
|
CONFIG_UVC_CAM1_FRAMESIZE_WIDTH=240
|
||||||
CONFIG_UVC_CAM1_FRAMESIZE_HEIGT=240
|
CONFIG_UVC_CAM1_FRAMESIZE_HEIGT=240
|
||||||
CONFIG_UVC_CAM1_MULTI_FRAMESIZE=y
|
CONFIG_UVC_CAM1_MULTI_FRAMESIZE=y
|
||||||
@@ -2257,7 +2257,7 @@ CONFIG_UVC_CAM1_MULTI_FRAMESIZE=y
|
|||||||
#
|
#
|
||||||
CONFIG_UVC_MULTI_FRAME_WIDTH_1=240
|
CONFIG_UVC_MULTI_FRAME_WIDTH_1=240
|
||||||
CONFIG_UVC_MULTI_FRAME_HEIGHT_1=240
|
CONFIG_UVC_MULTI_FRAME_HEIGHT_1=240
|
||||||
CONFIG_UVC_MULTI_FRAME_FPS_1=90
|
CONFIG_UVC_MULTI_FRAME_FPS_1=60
|
||||||
# end of FRAME_SIZE_1
|
# end of FRAME_SIZE_1
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -2265,7 +2265,7 @@ CONFIG_UVC_MULTI_FRAME_FPS_1=90
|
|||||||
#
|
#
|
||||||
CONFIG_UVC_MULTI_FRAME_WIDTH_2=240
|
CONFIG_UVC_MULTI_FRAME_WIDTH_2=240
|
||||||
CONFIG_UVC_MULTI_FRAME_HEIGHT_2=240
|
CONFIG_UVC_MULTI_FRAME_HEIGHT_2=240
|
||||||
CONFIG_UVC_MULTI_FRAME_FPS_2=90
|
CONFIG_UVC_MULTI_FRAME_FPS_2=60
|
||||||
# end of FRAME_SIZE_2
|
# end of FRAME_SIZE_2
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -2273,7 +2273,7 @@ CONFIG_UVC_MULTI_FRAME_FPS_2=90
|
|||||||
#
|
#
|
||||||
CONFIG_UVC_MULTI_FRAME_WIDTH_3=240
|
CONFIG_UVC_MULTI_FRAME_WIDTH_3=240
|
||||||
CONFIG_UVC_MULTI_FRAME_HEIGHT_3=240
|
CONFIG_UVC_MULTI_FRAME_HEIGHT_3=240
|
||||||
CONFIG_UVC_MULTI_FRAME_FPS_3=90
|
CONFIG_UVC_MULTI_FRAME_FPS_3=60
|
||||||
# end of FRAME_SIZE_3
|
# end of FRAME_SIZE_3
|
||||||
# end of UVC_MULTI_FRAME_CONFIG
|
# end of UVC_MULTI_FRAME_CONFIG
|
||||||
|
|
||||||
|
|||||||
@@ -59,4 +59,4 @@ CONFIG_LED_EXTERNAL_GPIO=9
|
|||||||
CONFIG_LED_EXTERNAL_PWM_FREQ=20000
|
CONFIG_LED_EXTERNAL_PWM_FREQ=20000
|
||||||
CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE=50
|
CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE=50
|
||||||
CONFIG_CAMERA_USB_XCLK_FREQ=23000000
|
CONFIG_CAMERA_USB_XCLK_FREQ=23000000
|
||||||
CONFIG_GENERAL_WIRED_MODE=y
|
CONFIG_GENERAL_DEFAULT_WIRED_MODE=y
|
||||||
@@ -59,4 +59,4 @@ CONFIG_LED_EXTERNAL_GPIO=9
|
|||||||
CONFIG_LED_EXTERNAL_PWM_FREQ=20000
|
CONFIG_LED_EXTERNAL_PWM_FREQ=20000
|
||||||
CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE=100
|
CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE=100
|
||||||
CONFIG_CAMERA_USB_XCLK_FREQ=23000000
|
CONFIG_CAMERA_USB_XCLK_FREQ=23000000
|
||||||
CONFIG_GENERAL_WIRED_MODE=y
|
CONFIG_GENERAL_DEFAULT_WIRED_MODE=y
|
||||||
@@ -50,5 +50,5 @@ CONFIG_LED_EXTERNAL_CONTROL=y
|
|||||||
CONFIG_LED_EXTERNAL_PWM_FREQ=5000
|
CONFIG_LED_EXTERNAL_PWM_FREQ=5000
|
||||||
CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE=100
|
CONFIG_LED_EXTERNAL_PWM_DUTY_CYCLE=100
|
||||||
CONFIG_LED_EXTERNAL_GPIO=1
|
CONFIG_LED_EXTERNAL_GPIO=1
|
||||||
CONFIG_CAMERA_USB_XCLK_FREQ=23000000 # NOT TESTED
|
CONFIG_CAMERA_USB_XCLK_FREQ=23000000
|
||||||
CONFIG_GENERAL_WIRED_MODE=y
|
# CONFIG_GENERAL_DEFAULT_WIRED_MODE is not set
|
||||||
@@ -56,4 +56,4 @@ CONFIG_SPIRAM_SPEED=80
|
|||||||
CONFIG_SPIRAM_SPEED_80M=y
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
# CONFIG_LED_EXTERNAL_CONTROL is not set
|
# CONFIG_LED_EXTERNAL_CONTROL is not set
|
||||||
CONFIG_CAMERA_USB_XCLK_FREQ=23000000
|
CONFIG_CAMERA_USB_XCLK_FREQ=23000000
|
||||||
CONFIG_GENERAL_WIRED_MODE=y
|
# CONFIG_GENERAL_DEFAULT_WIRED_MODE is not set
|
||||||
+311
-27
@@ -410,6 +410,55 @@ class OpenIrisDevice:
|
|||||||
print(f"❌ Failed to parse mode response: {e}")
|
print(f"❌ Failed to parse mode response: {e}")
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
|
def set_led_duty_cycle(self, duty_cycle):
|
||||||
|
"""Sets the PWN duty cycle of the LED"""
|
||||||
|
print(f"🌟 Setting LED duty cycle to {duty_cycle}%...")
|
||||||
|
response = self.send_command("set_led_duty_cycle", {"dutyCycle": duty_cycle})
|
||||||
|
if "error" in response:
|
||||||
|
print(f"❌ Failed to set LED duty cycle: {response['error']}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
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 get_serial_info(self) -> Optional[Tuple[str, str]]:
|
||||||
|
"""Get device serial number and MAC address"""
|
||||||
|
response = self.send_command("get_serial")
|
||||||
|
if "error" in response:
|
||||||
|
print(f"❌ Failed to get serial/MAC: {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)
|
||||||
|
serial = payload.get("serial")
|
||||||
|
mac = payload.get("mac")
|
||||||
|
return serial, mac
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Failed to parse serial/MAC: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def monitor_logs(self):
|
def monitor_logs(self):
|
||||||
"""Monitor device logs until interrupted"""
|
"""Monitor device logs until interrupted"""
|
||||||
print("📋 Monitoring device logs (Press Ctrl+C to exit)...")
|
print("📋 Monitoring device logs (Press Ctrl+C to exit)...")
|
||||||
@@ -631,9 +680,9 @@ def configure_wifi(device: OpenIrisDevice, args = None):
|
|||||||
if device.set_wifi(selected_network.ssid, password):
|
if device.set_wifi(selected_network.ssid, password):
|
||||||
print("✅ WiFi configured successfully!")
|
print("✅ WiFi configured successfully!")
|
||||||
print("💡 Next steps:")
|
print("💡 Next steps:")
|
||||||
print(" 4. Check WiFi status")
|
print(" • Open WiFi menu to connect to WiFi (if needed)")
|
||||||
print(" 5. Connect to WiFi (if needed)")
|
print(" • Open WiFi menu to check WiFi status")
|
||||||
print(" 6. Start streaming when connected")
|
print(" • Start streaming from the main menu when connected")
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
print("❌ Invalid network number")
|
print("❌ Invalid network number")
|
||||||
@@ -708,12 +757,127 @@ def check_wifi_status(device: OpenIrisDevice, args = None):
|
|||||||
|
|
||||||
def attempt_wifi_connection(device: OpenIrisDevice, args = None):
|
def attempt_wifi_connection(device: OpenIrisDevice, args = None):
|
||||||
device.connect_wifi()
|
device.connect_wifi()
|
||||||
print("🕰️ Wait a few seconds then check status (option 4)")
|
print("🕰️ Wait a few seconds then check status in the WiFi menu")
|
||||||
|
|
||||||
|
|
||||||
def start_streaming(device: OpenIrisDevice, args = None):
|
def start_streaming(device: OpenIrisDevice, args = None):
|
||||||
device.start_streaming()
|
device.start_streaming()
|
||||||
print("🚀 Streaming started! Use option 8 to monitor logs.")
|
print("🚀 Streaming started! Use 'Monitor logs' from the main menu.")
|
||||||
|
|
||||||
|
|
||||||
|
# ----- WiFi submenu -----
|
||||||
|
def wifi_auto_setup(device: OpenIrisDevice, args=None):
|
||||||
|
print("\n⚙️ Automatic WiFi setup starting...")
|
||||||
|
scan_timeout = getattr(args, "scan_timeout", 30) if args else 30
|
||||||
|
|
||||||
|
# 1) Scan
|
||||||
|
if not device.scan_networks(timeout=scan_timeout):
|
||||||
|
print("❌ Auto-setup aborted: no networks found or scan failed")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2) Show networks (sorted strongest-first already)
|
||||||
|
display_networks(device)
|
||||||
|
|
||||||
|
# 3) Select a network (default strongest)
|
||||||
|
choice = input("Select network number [default: 1] or 'back': ").strip()
|
||||||
|
if choice.lower() == "back":
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
idx = int(choice) - 1 if choice else 0
|
||||||
|
except ValueError:
|
||||||
|
idx = 0
|
||||||
|
|
||||||
|
sorted_networks = sorted(device.networks, key=lambda x: x.rssi, reverse=True)
|
||||||
|
if not (0 <= idx < len(sorted_networks)):
|
||||||
|
print("⚠️ Invalid selection, using strongest network")
|
||||||
|
idx = 0
|
||||||
|
|
||||||
|
selected = sorted_networks[idx]
|
||||||
|
print(f"\n🔐 Selected: {selected.ssid if selected.ssid else '<hidden>'}")
|
||||||
|
if selected.auth_mode == 0:
|
||||||
|
password = ""
|
||||||
|
print("🔓 Open network - no password required")
|
||||||
|
else:
|
||||||
|
password = input("Enter WiFi password: ")
|
||||||
|
|
||||||
|
# 4) Configure WiFi
|
||||||
|
if not device.set_wifi(selected.ssid, password):
|
||||||
|
print("❌ Auto-setup aborted: failed to configure WiFi")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 5) Connect
|
||||||
|
if not device.connect_wifi():
|
||||||
|
print("❌ Auto-setup aborted: failed to start WiFi connection")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 6) Wait for IP / connected status
|
||||||
|
print("⏳ Connecting to WiFi, waiting for IP...")
|
||||||
|
start = time.time()
|
||||||
|
timeout_s = 30
|
||||||
|
ip = None
|
||||||
|
last_status = None
|
||||||
|
while time.time() - start < timeout_s:
|
||||||
|
status = device.get_wifi_status()
|
||||||
|
last_status = status
|
||||||
|
ip = (status or {}).get("ip_address")
|
||||||
|
if ip and ip not in ("0.0.0.0", "", None):
|
||||||
|
break
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
if ip and ip not in ("0.0.0.0", "", None):
|
||||||
|
print(f"✅ Connected! IP Address: {ip}")
|
||||||
|
else:
|
||||||
|
print("⚠️ Connection not confirmed within timeout")
|
||||||
|
if last_status:
|
||||||
|
print(f" Status: {last_status.get('status', 'unknown')} | IP: {last_status.get('ip_address', '-')}")
|
||||||
|
|
||||||
|
|
||||||
|
def wifi_menu(device: OpenIrisDevice, args=None):
|
||||||
|
while True:
|
||||||
|
print("\n📶 WiFi Settings:")
|
||||||
|
print(f"{str(1):>2} ⚙️ Automatic WiFi setup")
|
||||||
|
print(f"{str(2):>2} 📁 Manual WiFi actions")
|
||||||
|
print("back Back")
|
||||||
|
|
||||||
|
choice = input("\nSelect option (1-2 or 'back'): ").strip()
|
||||||
|
if choice.lower() == "back":
|
||||||
|
break
|
||||||
|
|
||||||
|
if choice == "1":
|
||||||
|
wifi_auto_setup(device, args)
|
||||||
|
elif choice == "2":
|
||||||
|
wifi_manual_menu(device, args)
|
||||||
|
else:
|
||||||
|
print("❌ Invalid option")
|
||||||
|
|
||||||
|
|
||||||
|
def wifi_manual_menu(device: OpenIrisDevice, args=None):
|
||||||
|
while True:
|
||||||
|
print("\n📁 WiFi Manual Actions:")
|
||||||
|
print(f"{str(1):>2} 🔍 Scan for WiFi networks")
|
||||||
|
print(f"{str(2):>2} 📡 Show available networks")
|
||||||
|
print(f"{str(3):>2} 🔐 Configure WiFi")
|
||||||
|
print(f"{str(4):>2} 🔗 Connect to WiFi")
|
||||||
|
print(f"{str(5):>2} 🛰️ Check WiFi status")
|
||||||
|
print("back Back")
|
||||||
|
|
||||||
|
choice = input("\nSelect option (1-5 or 'back'): ").strip()
|
||||||
|
if choice.lower() == "back":
|
||||||
|
break
|
||||||
|
|
||||||
|
sub_map = {
|
||||||
|
"1": scan_networks,
|
||||||
|
"2": display_networks,
|
||||||
|
"3": configure_wifi,
|
||||||
|
"4": attempt_wifi_connection,
|
||||||
|
"5": check_wifi_status,
|
||||||
|
}
|
||||||
|
|
||||||
|
handler = sub_map.get(choice)
|
||||||
|
if not handler:
|
||||||
|
print("❌ Invalid option")
|
||||||
|
continue
|
||||||
|
handler(device, args)
|
||||||
|
|
||||||
|
|
||||||
def switch_device_mode(device: OpenIrisDevice, args = None):
|
def switch_device_mode(device: OpenIrisDevice, args = None):
|
||||||
@@ -736,21 +900,143 @@ def switch_device_mode(device: OpenIrisDevice, args = None):
|
|||||||
print("❌ Invalid mode selection")
|
print("❌ Invalid mode selection")
|
||||||
|
|
||||||
|
|
||||||
|
def set_led_duty_cycle(device: OpenIrisDevice, args=None):
|
||||||
|
# Show current duty cycle on entry
|
||||||
|
current = device.get_led_duty_cycle()
|
||||||
|
if current is not None:
|
||||||
|
print(f"💡 Current LED duty cycle: {current}%")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
input_data = input("Enter LED external PWM duty cycle (0-100) or `back` to exit: \n")
|
||||||
|
if input_data.lower() == "back":
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
duty_cycle = int(input_data)
|
||||||
|
except ValueError:
|
||||||
|
print("❌ Invalid input. Please enter a number between 0 and 100.")
|
||||||
|
|
||||||
|
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
|
||||||
|
if device.set_led_duty_cycle(duty_cycle):
|
||||||
|
# Read back and display current value using existing getter
|
||||||
|
updated = device.get_led_duty_cycle()
|
||||||
|
if updated is not None:
|
||||||
|
print(f"💡 Current LED duty cycle: {updated}%")
|
||||||
|
else:
|
||||||
|
print("ℹ️ Duty cycle updated, but current value could not be read back.")
|
||||||
|
|
||||||
|
|
||||||
def monitor_logs(device: OpenIrisDevice, args = None):
|
def monitor_logs(device: OpenIrisDevice, args = None):
|
||||||
device.monitor_logs()
|
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}%")
|
||||||
|
|
||||||
|
|
||||||
|
def get_serial(device: OpenIrisDevice, args=None):
|
||||||
|
info = device.get_serial_info()
|
||||||
|
if info is not None:
|
||||||
|
serial, mac = info
|
||||||
|
# print(f"🔑 Serial: {serial}")
|
||||||
|
print(f"🔗 MAC: {mac}")
|
||||||
|
|
||||||
|
|
||||||
|
# ----- Aggregated GET: settings summary -----
|
||||||
|
def _probe_serial(device: OpenIrisDevice) -> Dict:
|
||||||
|
info = device.get_serial_info()
|
||||||
|
if info is None:
|
||||||
|
return {"serial": None, "mac": None}
|
||||||
|
serial, mac = info
|
||||||
|
return {"serial": serial, "mac": mac}
|
||||||
|
|
||||||
|
|
||||||
|
def _probe_led_pwm(device: OpenIrisDevice) -> Dict:
|
||||||
|
duty = device.get_led_duty_cycle()
|
||||||
|
return {"led_external_pwm_duty_cycle": duty}
|
||||||
|
|
||||||
|
|
||||||
|
def _probe_mode(device: OpenIrisDevice) -> Dict:
|
||||||
|
mode = device.get_device_mode()
|
||||||
|
return {"mode": mode}
|
||||||
|
|
||||||
|
|
||||||
|
def _probe_wifi_status(device: OpenIrisDevice) -> Dict:
|
||||||
|
# Returns dict as provided by device; pass through
|
||||||
|
status = device.get_wifi_status() or {}
|
||||||
|
return {"wifi_status": status}
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings(device: OpenIrisDevice, args=None):
|
||||||
|
print("\n🧩 Collecting device settings...\n")
|
||||||
|
|
||||||
|
probes = [
|
||||||
|
("Identity", _probe_serial),
|
||||||
|
("LED", _probe_led_pwm),
|
||||||
|
("Mode", _probe_mode),
|
||||||
|
("WiFi", _probe_wifi_status),
|
||||||
|
]
|
||||||
|
|
||||||
|
summary: Dict[str, Dict] = {}
|
||||||
|
|
||||||
|
for label, probe in probes:
|
||||||
|
try:
|
||||||
|
data = probe(device)
|
||||||
|
summary[label] = data
|
||||||
|
except Exception as e:
|
||||||
|
summary[label] = {"error": str(e)}
|
||||||
|
|
||||||
|
# Pretty print summary
|
||||||
|
# Identity
|
||||||
|
ident = summary.get("Identity", {})
|
||||||
|
serial = ident.get("serial")
|
||||||
|
mac = ident.get("mac")
|
||||||
|
if serial:
|
||||||
|
print(f"🔑 Serial: {serial}")
|
||||||
|
# if mac:
|
||||||
|
# print(f"🔗 MAC: {mac}")
|
||||||
|
if not serial and not mac:
|
||||||
|
print("🔑 Serial/MAC: unavailable")
|
||||||
|
|
||||||
|
# LED
|
||||||
|
led = summary.get("LED", {})
|
||||||
|
duty = led.get("led_external_pwm_duty_cycle")
|
||||||
|
if duty is not None:
|
||||||
|
print(f"💡 LED PWM Duty: {duty}%")
|
||||||
|
else:
|
||||||
|
print("💡 LED PWM Duty: unknown")
|
||||||
|
|
||||||
|
# Mode
|
||||||
|
mode = summary.get("Mode", {}).get("mode")
|
||||||
|
print(f"🎚️ Mode: {mode if mode else 'unknown'}")
|
||||||
|
|
||||||
|
# WiFi
|
||||||
|
wifi = summary.get("WiFi", {}).get("wifi_status", {})
|
||||||
|
if wifi:
|
||||||
|
status = wifi.get("status", "unknown")
|
||||||
|
ip = wifi.get("ip_address") or "-"
|
||||||
|
configured = wifi.get("networks_configured", 0)
|
||||||
|
print(f"📶 WiFi: {status} | IP: {ip} | Networks configured: {configured}")
|
||||||
|
else:
|
||||||
|
print("📶 WiFi: status unavailable")
|
||||||
|
|
||||||
|
print("")
|
||||||
|
|
||||||
|
|
||||||
COMMANDS_MAP = {
|
COMMANDS_MAP = {
|
||||||
"1": scan_networks,
|
"1": wifi_menu,
|
||||||
"2": display_networks,
|
"2": configure_mdns,
|
||||||
"3": configure_wifi,
|
"3": configure_mdns,
|
||||||
"4": configure_mdns,
|
"4": start_streaming,
|
||||||
"5": configure_mdns,
|
"5": switch_device_mode,
|
||||||
"6": check_wifi_status,
|
"6": set_led_duty_cycle,
|
||||||
"7": attempt_wifi_connection,
|
"7": monitor_logs,
|
||||||
"8": start_streaming,
|
"8": get_settings,
|
||||||
"9": switch_device_mode,
|
|
||||||
"10": monitor_logs,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -839,18 +1125,16 @@ def main():
|
|||||||
# Main interaction loop
|
# Main interaction loop
|
||||||
while True:
|
while True:
|
||||||
print("\n🔧 Setup Options:")
|
print("\n🔧 Setup Options:")
|
||||||
print("1. 🔍 Scan for WiFi networks")
|
print(f"{str(1):>2} 📶 WiFi settings")
|
||||||
print("2. 📡 Show available networks")
|
print(f"{str(2):>2} 🌐 Configure MDNS")
|
||||||
print("3. 🔐 Configure WiFi")
|
print(f"{str(3):>2} 💻 Configure UVC Name")
|
||||||
print("4. 🌐 Configure MDNS")
|
print(f"{str(4):>2} 🚀 Start streaming mode")
|
||||||
print("5. 💻 Configure UVC Name")
|
print(f"{str(5):>2} 🔄 Switch device mode (WiFi/UVC/Auto)")
|
||||||
print("6. 📶 Check WiFi status")
|
print(f"{str(6):>2} 💡 Update PWM Duty Cycle")
|
||||||
print("7. 🔗 Connect to WiFi")
|
print(f"{str(7):>2} 📖 Monitor logs")
|
||||||
print("8. 🚀 Start streaming mode")
|
print(f"{str(8):>2} 🧩 Get settings summary")
|
||||||
print("9. 🔄 Switch device mode (WiFi/UVC/Auto)")
|
print("exit 🚪 Exit")
|
||||||
print("10. 📋 Monitor logs")
|
choice = input("\nSelect option (1-8): ").strip()
|
||||||
print("exit. 🚪 Exit")
|
|
||||||
choice = input("\nSelect option (1-10): ").strip()
|
|
||||||
|
|
||||||
if choice == "exit":
|
if choice == "exit":
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user