mirror of
https://github.com/MrUnknownDE/OpenIris-ESPIDF.git
synced 2026-04-18 05:53:44 +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:
162
README.md
162
README.md
@@ -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.)
|
||||
* A USB cable for Power supply and programming
|
||||
1) Install tooling
|
||||
- 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.
|
||||
|
||||
### Configure the Project
|
||||
|
||||
Open the project configuration menu (`idf.py menuconfig`).
|
||||
|
||||
In the `Example Configuration` menu:
|
||||
|
||||
* 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!
|
||||
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”.
|
||||
- Clone and open in VS Code:
|
||||
```cmd
|
||||
git clone https://github.com/lorow/OpenIris-ESPIDF.git
|
||||
cd OpenIris-ESPIDF
|
||||
code .
|
||||
```
|
||||
|
||||
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
|
||||
- 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
|
||||
|
||||
#ifdef CONFIG_GENERAL_WIRED_MODE
|
||||
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||
#include "esp_log.h"
|
||||
#include "soc/rtc_cntl_struct.h"
|
||||
#include "soc/usb_serial_jtag_reg.h"
|
||||
|
||||
@@ -48,7 +48,7 @@ void CameraManager::setupCameraPinout()
|
||||
|
||||
ESP_LOGI(CAMERA_MANAGER_TAG, "CAM_BOARD");
|
||||
#endif
|
||||
#if CONFIG_GENERAL_WIRED_MODE
|
||||
#if CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||
xclk_freq_hz = CONFIG_CAMERA_USB_XCLK_FREQ;
|
||||
#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
|
||||
.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,
|
||||
.grab_mode = CAMERA_GRAB_WHEN_EMPTY,
|
||||
.grab_mode = CAMERA_GRAB_LATEST, //CAMERA_GRAB_WHEN_EMPTY
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ void CameraManager::setupBasicResolution()
|
||||
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()
|
||||
@@ -196,7 +196,7 @@ bool CameraManager::setupCamera()
|
||||
return false;
|
||||
}
|
||||
|
||||
#if CONFIG_GENERAL_WIRED_MODE
|
||||
#if CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||
const auto temp_sensor = esp_camera_sensor_get();
|
||||
|
||||
// Thanks to lick_it, we discovered that OV5640 likes to overheat when
|
||||
|
||||
@@ -11,5 +11,5 @@ idf_component_register(
|
||||
INCLUDE_DIRS
|
||||
"CommandManager"
|
||||
"CommandManager/commands"
|
||||
REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers
|
||||
REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers LEDManager
|
||||
)
|
||||
@@ -23,53 +23,84 @@ std::unordered_map<std::string, CommandType> commandTypeMap = {
|
||||
{"connect_wifi", CommandType::CONNECT_WIFI},
|
||||
{"switch_mode", CommandType::SWITCH_MODE},
|
||||
{"get_device_mode", CommandType::GET_DEVICE_MODE},
|
||||
{"set_led_duty_cycle", CommandType::SET_LED_DUTY_CYCLE},
|
||||
{"get_led_duty_cycle", CommandType::GET_LED_DUTY_CYCLE},
|
||||
{"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)
|
||||
{
|
||||
case CommandType::PING:
|
||||
return { PingCommand };
|
||||
return {PingCommand};
|
||||
case CommandType::PAUSE:
|
||||
return [json] { return PauseCommand(json); };
|
||||
return [json]
|
||||
{ return PauseCommand(json); };
|
||||
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:
|
||||
return [this, json] { return updateOTACredentialsCommand(this->registry, json); };
|
||||
return [this, json]
|
||||
{ return updateOTACredentialsCommand(this->registry, json); };
|
||||
case CommandType::SET_WIFI:
|
||||
return [this, json] { return setWiFiCommand(this->registry, json); };
|
||||
return [this, json]
|
||||
{ return setWiFiCommand(this->registry, json); };
|
||||
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:
|
||||
return [this, json] { return updateAPWiFiCommand(this->registry, json); };
|
||||
return [this, json]
|
||||
{ return updateAPWiFiCommand(this->registry, json); };
|
||||
case CommandType::DELETE_NETWORK:
|
||||
return [this, json] { return deleteWiFiCommand(this->registry, json); };
|
||||
return [this, json]
|
||||
{ return deleteWiFiCommand(this->registry, json); };
|
||||
case CommandType::SET_MDNS:
|
||||
return [this, json] { return setMDNSCommand(this->registry, json); };
|
||||
return [this, json]
|
||||
{ return setMDNSCommand(this->registry, json); };
|
||||
case CommandType::UPDATE_CAMERA:
|
||||
return [this, json] { return updateCameraCommand(this->registry, json); };
|
||||
return [this, json]
|
||||
{ return updateCameraCommand(this->registry, json); };
|
||||
case CommandType::RESTART_CAMERA:
|
||||
return [this, json] { return restartCameraCommand(this->registry, json); };
|
||||
return [this, json]
|
||||
{ return restartCameraCommand(this->registry, json); };
|
||||
case CommandType::GET_CONFIG:
|
||||
return [this] { return getConfigCommand(this->registry); };
|
||||
return [this]
|
||||
{ return getConfigCommand(this->registry); };
|
||||
case CommandType::SAVE_CONFIG:
|
||||
return [this] { return saveConfigCommand(this->registry); };
|
||||
return [this]
|
||||
{ return saveConfigCommand(this->registry); };
|
||||
case CommandType::RESET_CONFIG:
|
||||
return [this, json] { return resetConfigCommand(this->registry, json); };
|
||||
return [this, json]
|
||||
{ return resetConfigCommand(this->registry, json); };
|
||||
case CommandType::RESTART_DEVICE:
|
||||
return restartDeviceCommand;
|
||||
case CommandType::SCAN_NETWORKS:
|
||||
return [this] { return scanNetworksCommand(this->registry); };
|
||||
return [this]
|
||||
{ return scanNetworksCommand(this->registry); };
|
||||
case CommandType::START_STREAMING:
|
||||
return startStreamingCommand;
|
||||
case CommandType::GET_WIFI_STATUS:
|
||||
return [this] { return getWiFiStatusCommand(this->registry); };
|
||||
return [this]
|
||||
{ return getWiFiStatusCommand(this->registry); };
|
||||
case CommandType::CONNECT_WIFI:
|
||||
return [this] { return connectWiFiCommand(this->registry); };
|
||||
return [this]
|
||||
{ return connectWiFiCommand(this->registry); };
|
||||
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:
|
||||
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:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ enum class CommandType
|
||||
CONNECT_WIFI,
|
||||
SWITCH_MODE,
|
||||
GET_DEVICE_MODE,
|
||||
SET_LED_DUTY_CYCLE,
|
||||
GET_LED_DUTY_CYCLE,
|
||||
GET_SERIAL,
|
||||
};
|
||||
|
||||
class CommandManager
|
||||
|
||||
@@ -8,7 +8,8 @@ enum class DependencyType
|
||||
{
|
||||
project_config,
|
||||
camera_manager,
|
||||
wifi_manager
|
||||
wifi_manager,
|
||||
led_manager
|
||||
};
|
||||
|
||||
class DependencyRegistry
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#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
|
||||
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);
|
||||
projectConfig->setDeviceMode(static_cast<StreamingMode>(mode));
|
||||
|
||||
cJSON_Delete(parsedJson);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
OpenIrisTasks::ScheduleRestart(2000);
|
||||
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()
|
||||
{
|
||||
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));
|
||||
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,10 +13,15 @@ CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry,
|
||||
|
||||
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 startStreamingCommand();
|
||||
|
||||
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
|
||||
extern void start_video_streaming(void *arg);
|
||||
|
||||
bool startupCommandReceived = false;
|
||||
static bool s_startupCommandReceived = false;
|
||||
bool getStartupCommandReceived()
|
||||
{
|
||||
return startupCommandReceived;
|
||||
return s_startupCommandReceived;
|
||||
}
|
||||
|
||||
void setStartupCommandReceived(bool startupCommandReceived)
|
||||
{
|
||||
startupCommandReceived = startupCommandReceived;
|
||||
s_startupCommandReceived = startupCommandReceived;
|
||||
}
|
||||
|
||||
static TaskHandle_t *g_serial_manager_handle = nullptr;
|
||||
@@ -27,15 +27,15 @@ void setSerialManagerHandle(TaskHandle_t *serialManagerHandle)
|
||||
}
|
||||
|
||||
// Global pause state
|
||||
bool startupPaused = false;
|
||||
static bool s_startupPaused = false;
|
||||
bool getStartupPaused()
|
||||
{
|
||||
return startupPaused;
|
||||
return s_startupPaused;
|
||||
}
|
||||
|
||||
void setStartupPaused(bool startupPaused)
|
||||
{
|
||||
startupPaused = startupPaused;
|
||||
s_startupPaused = startupPaused;
|
||||
}
|
||||
|
||||
// Function to manually activate streaming
|
||||
@@ -47,4 +47,9 @@ void activateStreaming(bool disableSetup)
|
||||
void *serialTaskHandle = (serialHandle && *serialHandle) ? *serialHandle : nullptr;
|
||||
|
||||
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();
|
||||
void setStartupPaused(bool startupPaused);
|
||||
|
||||
// Tracks whether USB handover from usb_serial_jtag to TinyUSB was performed
|
||||
bool getUsbHandoverDone();
|
||||
void setUsbHandoverDone(bool done);
|
||||
|
||||
#endif
|
||||
@@ -1,4 +1,4 @@
|
||||
idf_component_register(SRCS "LEDManager/LEDManager.cpp"
|
||||
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,
|
||||
{
|
||||
{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,
|
||||
QueueHandle_t ledStateQueue) : blink_led_pin(pin),
|
||||
illumninator_led_pin(illumninator_led_pin),
|
||||
ledStateQueue(ledStateQueue),
|
||||
currentState(LEDStates_e::LedStateNone) {
|
||||
QueueHandle_t ledStateQueue, std::shared_ptr<ProjectConfig> deviceConfig) : blink_led_pin(pin),
|
||||
illumninator_led_pin(illumninator_led_pin),
|
||||
ledStateQueue(ledStateQueue),
|
||||
currentState(LEDStates_e::LedStateNone),
|
||||
deviceConfig(deviceConfig)
|
||||
{
|
||||
}
|
||||
|
||||
void LEDManager::setup() {
|
||||
ESP_LOGD(LED_MANAGER_TAG, "Setting up status led.");
|
||||
void LEDManager::setup()
|
||||
{
|
||||
ESP_LOGI(LED_MANAGER_TAG, "Setting up status led.");
|
||||
gpio_reset_pin(blink_led_pin);
|
||||
/* Set the GPIO as a push/pull output */
|
||||
gpio_set_direction(blink_led_pin, GPIO_MODE_OUTPUT);
|
||||
this->toggleLED(LED_OFF);
|
||||
|
||||
#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 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 = {
|
||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||
.duty_resolution = resolution,
|
||||
.timer_num = LEDC_TIMER_0,
|
||||
.freq_hz = freq,
|
||||
.clk_cfg = LEDC_AUTO_CLK
|
||||
};
|
||||
.clk_cfg = LEDC_AUTO_CLK};
|
||||
|
||||
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
|
||||
|
||||
@@ -101,8 +104,7 @@ void LEDManager::setup() {
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.timer_sel = LEDC_TIMER_0,
|
||||
.duty = dutyCycle,
|
||||
.hpoint = 0
|
||||
};
|
||||
.hpoint = 0};
|
||||
|
||||
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
|
||||
#endif
|
||||
@@ -110,65 +112,98 @@ void LEDManager::setup() {
|
||||
ESP_LOGD(LED_MANAGER_TAG, "Done.");
|
||||
}
|
||||
|
||||
void LEDManager::handleLED() {
|
||||
if (!this->finishedPattern) {
|
||||
void LEDManager::handleLED()
|
||||
{
|
||||
if (!this->finishedPattern)
|
||||
{
|
||||
displayCurrentPattern();
|
||||
return;
|
||||
}
|
||||
|
||||
if (xQueueReceive(this->ledStateQueue, &buffer, 10)) {
|
||||
if (xQueueReceive(this->ledStateQueue, &buffer, 10))
|
||||
{
|
||||
this->updateState(buffer);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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->finishedPattern = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LEDManager::displayCurrentPattern() {
|
||||
void LEDManager::displayCurrentPattern()
|
||||
{
|
||||
auto [state, delayTime] = ledStateMap[this->currentState].patterns[this->currentPatternIndex];
|
||||
this->toggleLED(state);
|
||||
this->timeToDelayFor = delayTime;
|
||||
|
||||
if (this->currentPatternIndex < ledStateMap[this->currentState].patterns.size() - 1)
|
||||
this->currentPatternIndex++;
|
||||
else {
|
||||
else
|
||||
{
|
||||
this->finishedPattern = true;
|
||||
this->toggleLED(LED_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
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 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
|
||||
void LEDManager::updateState(const LEDStates_e newState)
|
||||
{
|
||||
// If we've got an error state - that's it, keep repeating it indefinitely
|
||||
if (ledStateMap[this->currentState].isError)
|
||||
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)
|
||||
return;
|
||||
|
||||
if (ledStateMap.contains(newState)) {
|
||||
this->currentState = newState;
|
||||
this->currentPatternIndex = 0;
|
||||
this->finishedPattern = false;
|
||||
}
|
||||
this->currentState = newState;
|
||||
this->currentPatternIndex = 0;
|
||||
this->finishedPattern = false;
|
||||
}
|
||||
|
||||
void LEDManager::toggleLED(const bool state) const {
|
||||
void LEDManager::toggleLED(const bool state) const
|
||||
{
|
||||
gpio_set_level(blink_led_pin, state);
|
||||
}
|
||||
|
||||
void HandleLEDDisplayTask(void *pvParameter) {
|
||||
auto *ledManager = static_cast<LEDManager *>(pvParameter);
|
||||
void LEDManager::setExternalLEDDutyCycle(uint8_t dutyPercent)
|
||||
{
|
||||
#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();
|
||||
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 <vector>
|
||||
#include <StateManager.hpp>
|
||||
#include <ProjectConfig.hpp>
|
||||
#include <helpers.hpp>
|
||||
|
||||
// it kinda looks like different boards have these states swapped
|
||||
@@ -41,12 +42,16 @@ typedef std::unordered_map<LEDStates_e, LEDStage>
|
||||
class LEDManager
|
||||
{
|
||||
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 handleLED();
|
||||
size_t getTimeToDelayFor() const { return timeToDelayFor; }
|
||||
|
||||
// Apply new external LED PWM duty cycle immediately (0-100)
|
||||
void setExternalLEDDutyCycle(uint8_t dutyPercent);
|
||||
uint8_t getExternalLEDDutyCycle() const { return deviceConfig ? deviceConfig->getDeviceConfig().led_external_pwm_duty_cycle : 0; }
|
||||
|
||||
private:
|
||||
void toggleLED(bool state) const;
|
||||
void displayCurrentPattern();
|
||||
@@ -60,6 +65,7 @@ private:
|
||||
|
||||
LEDStates_e buffer;
|
||||
LEDStates_e currentState;
|
||||
std::shared_ptr<ProjectConfig> deviceConfig;
|
||||
|
||||
size_t currentPatternIndex = 0;
|
||||
size_t timeToDelayFor = 100;
|
||||
|
||||
@@ -21,35 +21,49 @@ struct BaseConfigModel
|
||||
Preferences *pref;
|
||||
};
|
||||
|
||||
enum class StreamingMode {
|
||||
enum class StreamingMode
|
||||
{
|
||||
AUTO,
|
||||
UVC,
|
||||
WIFI,
|
||||
};
|
||||
|
||||
struct DeviceMode_t : BaseConfigModel {
|
||||
struct DeviceMode_t : BaseConfigModel
|
||||
{
|
||||
StreamingMode mode;
|
||||
explicit DeviceMode_t( Preferences *pref) : BaseConfigModel(pref), mode(StreamingMode::AUTO){}
|
||||
explicit DeviceMode_t(Preferences *pref) : BaseConfigModel(pref), mode(StreamingMode::AUTO) {}
|
||||
|
||||
void load() {
|
||||
int stored_mode = this->pref->getInt("mode", 0);
|
||||
void load()
|
||||
{
|
||||
// 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);
|
||||
ESP_LOGI("DeviceMode", "Loaded device mode: %d", stored_mode);
|
||||
}
|
||||
|
||||
void save() const {
|
||||
void save() const
|
||||
{
|
||||
this->pref->putInt("mode", static_cast<int>(this->mode));
|
||||
ESP_LOGI("DeviceMode", "Saved device mode: %d", static_cast<int>(this->mode));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct DeviceConfig_t : BaseConfigModel
|
||||
{
|
||||
DeviceConfig_t(Preferences *pref) : BaseConfigModel(pref) {}
|
||||
|
||||
std::string OTALogin;
|
||||
std::string OTAPassword;
|
||||
int led_external_pwm_duty_cycle;
|
||||
int OTAPort;
|
||||
|
||||
void load()
|
||||
@@ -57,20 +71,23 @@ struct DeviceConfig_t : BaseConfigModel
|
||||
this->OTALogin = this->pref->getString("OTALogin", "openiris");
|
||||
this->OTAPassword = this->pref->getString("OTAPassword", "openiris");
|
||||
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("OTAPassword", this->OTAPassword.c_str());
|
||||
this->pref->putInt("OTAPort", this->OTAPort);
|
||||
this->pref->putInt("led_ext_pwm", this->led_external_pwm_duty_cycle);
|
||||
};
|
||||
|
||||
std::string toRepresentation() const
|
||||
{
|
||||
return Helpers::format_string(
|
||||
"\"device_config\": {\"OTALogin\": \"%s\", \"OTAPassword\": \"%s\", "
|
||||
"\"OTAPort\": %u}",
|
||||
this->OTALogin.c_str(), this->OTAPassword.c_str(), this->OTAPort);
|
||||
"\"OTAPort\": %u, \"led_external_pwm_duty_cycle\": %u}",
|
||||
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);
|
||||
};
|
||||
|
||||
void save() const {
|
||||
void save() const
|
||||
{
|
||||
this->pref->putString("hostname", this->hostname.c_str());
|
||||
};
|
||||
|
||||
@@ -125,7 +143,8 @@ struct CameraConfig_t : BaseConfigModel
|
||||
this->brightness = this->pref->getInt("brightness", 2);
|
||||
};
|
||||
|
||||
void save() const {
|
||||
void save() const
|
||||
{
|
||||
this->pref->putInt("vflip", this->vflip);
|
||||
this->pref->putInt("href", this->href);
|
||||
this->pref->putInt("framesize", this->framesize);
|
||||
@@ -186,12 +205,13 @@ struct WiFiConfig_t : BaseConfigModel
|
||||
this->password = this->pref->getString(("password" + iter_str).c_str(), "");
|
||||
this->channel = this->pref->getUInt(("channel" + iter_str).c_str());
|
||||
this->power = this->pref->getUInt(("power" + iter_str).c_str());
|
||||
|
||||
ESP_LOGI("WiFiConfig", "Loaded network %d: name=%s, ssid=%s, channel=%d",
|
||||
|
||||
ESP_LOGI("WiFiConfig", "Loaded network %d: name=%s, ssid=%s, channel=%d",
|
||||
index, this->name.c_str(), this->ssid.c_str(), this->channel);
|
||||
};
|
||||
|
||||
void save() const {
|
||||
void save() const
|
||||
{
|
||||
char buffer[2];
|
||||
auto const iter_str = std::string(Helpers::itoa(this->index, buffer, 10));
|
||||
|
||||
@@ -200,8 +220,8 @@ struct WiFiConfig_t : BaseConfigModel
|
||||
this->pref->putString(("password" + iter_str).c_str(), this->password.c_str());
|
||||
this->pref->putUInt(("channel" + iter_str).c_str(), this->channel);
|
||||
this->pref->putUInt(("power" + iter_str).c_str(), this->power);
|
||||
|
||||
ESP_LOGI("WiFiConfig", "Saved network %d: name=%s, ssid=%s, channel=%d",
|
||||
|
||||
ESP_LOGI("WiFiConfig", "Saved network %d: name=%s, ssid=%s, channel=%d",
|
||||
this->index, this->name.c_str(), this->ssid.c_str(), this->channel);
|
||||
};
|
||||
|
||||
@@ -228,7 +248,8 @@ struct AP_WiFiConfig_t : BaseConfigModel
|
||||
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("apPass", this->password.c_str());
|
||||
this->pref->putUInt("apChannel", this->channel);
|
||||
@@ -254,7 +275,8 @@ struct WiFiTxPower_t : BaseConfigModel
|
||||
this->power = this->pref->getUInt("txpower", 52);
|
||||
};
|
||||
|
||||
void save() const {
|
||||
void save() const
|
||||
{
|
||||
this->pref->putUInt("txpower", this->power);
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ ProjectConfig::ProjectConfig(Preferences *pref) : pref(pref),
|
||||
|
||||
ProjectConfig::~ProjectConfig() = default;
|
||||
|
||||
void ProjectConfig::save() const {
|
||||
void ProjectConfig::save() const
|
||||
{
|
||||
ESP_LOGD(CONFIGURATION_TAG, "Saving project config");
|
||||
this->config.device.save();
|
||||
this->config.device_mode.save();
|
||||
@@ -92,14 +93,22 @@ bool ProjectConfig::reset()
|
||||
//! DeviceConfig
|
||||
//*
|
||||
//**********************************************************************************************************************
|
||||
void ProjectConfig::setDeviceConfig(const std::string &OTALogin,
|
||||
const std::string &OTAPassword,
|
||||
const int OTAPort)
|
||||
void ProjectConfig::setOTAConfig(const std::string &OTALogin,
|
||||
const std::string &OTAPassword,
|
||||
const int OTAPort)
|
||||
{
|
||||
ESP_LOGD(CONFIGURATION_TAG, "Updating device config");
|
||||
this->config.device.OTALogin.assign(OTALogin);
|
||||
this->config.device.OTAPassword.assign(OTAPassword);
|
||||
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)
|
||||
@@ -120,6 +129,7 @@ void ProjectConfig::setCameraConfig(const uint8_t vflip,
|
||||
this->config.camera.framesize = framesize;
|
||||
this->config.camera.quality = quality;
|
||||
this->config.camera.brightness = brightness;
|
||||
this->config.camera.save();
|
||||
|
||||
ESP_LOGD(CONFIGURATION_TAG, "Updating Camera config");
|
||||
}
|
||||
@@ -133,8 +143,8 @@ void ProjectConfig::setWifiConfig(const std::string &networkName,
|
||||
const auto size = this->config.networks.size();
|
||||
|
||||
const auto it = std::ranges::find_if(this->config.networks,
|
||||
[&](const WiFiConfig_t &network)
|
||||
{ return network.name == networkName; });
|
||||
[&](const WiFiConfig_t &network)
|
||||
{ return network.name == networkName; });
|
||||
|
||||
if (it != this->config.networks.end())
|
||||
{
|
||||
@@ -191,8 +201,8 @@ void ProjectConfig::deleteWifiConfig(const std::string &networkName)
|
||||
}
|
||||
|
||||
const auto it = std::ranges::find_if(this->config.networks,
|
||||
[&](const WiFiConfig_t &network)
|
||||
{ return network.name == networkName; });
|
||||
[&](const WiFiConfig_t &network)
|
||||
{ return network.name == networkName; });
|
||||
|
||||
if (it != this->config.networks.end())
|
||||
{
|
||||
@@ -205,6 +215,7 @@ void ProjectConfig::deleteWifiConfig(const std::string &networkName)
|
||||
void ProjectConfig::setWiFiTxPower(uint8_t power)
|
||||
{
|
||||
this->config.txpower.power = power;
|
||||
this->config.txpower.save();
|
||||
ESP_LOGD(CONFIGURATION_TAG, "Updating wifi tx power");
|
||||
}
|
||||
|
||||
@@ -215,12 +226,14 @@ void ProjectConfig::setAPWifiConfig(const std::string &ssid,
|
||||
this->config.ap_network.ssid.assign(ssid);
|
||||
this->config.ap_network.password.assign(password);
|
||||
this->config.ap_network.channel = channel;
|
||||
this->config.ap_network.save();
|
||||
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.save(); // Save immediately
|
||||
this->config.device_mode.save(); // Save immediately
|
||||
}
|
||||
|
||||
//**********************************************************************************************************************
|
||||
@@ -258,10 +271,12 @@ TrackerConfig_t &ProjectConfig::getTrackerConfig()
|
||||
return this->config;
|
||||
}
|
||||
|
||||
DeviceMode_t &ProjectConfig::getDeviceModeConfig() {
|
||||
DeviceMode_t &ProjectConfig::getDeviceModeConfig()
|
||||
{
|
||||
return this->config.device_mode;
|
||||
}
|
||||
|
||||
StreamingMode ProjectConfig::getDeviceMode() {
|
||||
StreamingMode ProjectConfig::getDeviceMode()
|
||||
{
|
||||
return this->config.device_mode.mode;
|
||||
}
|
||||
@@ -22,11 +22,6 @@ public:
|
||||
void load();
|
||||
void save() const;
|
||||
|
||||
void wifiConfigSave();
|
||||
void cameraConfigSave();
|
||||
void deviceConfigSave();
|
||||
void mdnsConfigSave();
|
||||
void wifiTxPowerConfigSave();
|
||||
bool reset();
|
||||
|
||||
DeviceConfig_t &getDeviceConfig();
|
||||
@@ -38,9 +33,10 @@ public:
|
||||
WiFiTxPower_t &getWiFiTxPowerConfig();
|
||||
TrackerConfig_t &getTrackerConfig();
|
||||
|
||||
void setDeviceConfig(const std::string &OTALogin,
|
||||
const std::string &OTAPassword,
|
||||
int OTAPort);
|
||||
void setOTAConfig(const std::string &OTALogin,
|
||||
const std::string &OTAPassword,
|
||||
int OTAPort);
|
||||
void setLEDDUtyCycleConfig(int led_external_pwm_duty_cycle);
|
||||
void setMDNSConfig(const std::string &hostname);
|
||||
void setCameraConfig(uint8_t vflip,
|
||||
uint8_t framesize,
|
||||
|
||||
@@ -24,13 +24,36 @@ void SerialManager::try_receive()
|
||||
int current_position = 0;
|
||||
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
|
||||
// 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;
|
||||
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)
|
||||
@@ -42,9 +65,10 @@ void SerialManager::try_receive()
|
||||
// Notify main that a command was received during startup
|
||||
notify_startup_command_received();
|
||||
|
||||
const auto result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data)));
|
||||
const auto resultMessage = result.getResult();
|
||||
usb_serial_jtag_write_bytes(resultMessage.c_str(), resultMessage.length(), 1000 / 20);
|
||||
const auto result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data)));
|
||||
const auto resultMessage = result.getResult();
|
||||
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);
|
||||
|
||||
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()
|
||||
@@ -123,4 +148,19 @@ void HandleSerialManagerTask(void *pvParameters)
|
||||
lastHeartbeat = currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
bool should_send_heartbeat();
|
||||
void notify_startup_command_received();
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
std::shared_ptr<CommandManager> commandManager;
|
||||
|
||||
@@ -20,7 +20,8 @@ esp_err_t StreamHelpers::stream(httpd_req_t *req)
|
||||
size_t _jpg_buf_len = 0;
|
||||
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;
|
||||
if (!last_frame)
|
||||
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));
|
||||
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);
|
||||
}
|
||||
if (response == ESP_OK)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
idf_component_register(SRCS "UVCStream/UVCStream.cpp"
|
||||
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"
|
||||
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]";
|
||||
|
||||
extern "C" {
|
||||
static char serial_number_str[18];
|
||||
static char serial_number_str[13];
|
||||
|
||||
const char *get_uvc_device_name()
|
||||
{
|
||||
@@ -23,17 +26,19 @@ extern "C" {
|
||||
return CONFIG_TUSB_SERIAL_NUM;
|
||||
}
|
||||
|
||||
sniprintf(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]
|
||||
);
|
||||
// 12 hex chars without separators
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
(void)cb_ctx;
|
||||
ESP_LOGI(UVC_STREAM_TAG, "Camera Start");
|
||||
ESP_LOGI(UVC_STREAM_TAG, "Format: %d, width: %d, height: %d, rate: %d", format, width, height, rate);
|
||||
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)
|
||||
{
|
||||
(void)cb_ctx;
|
||||
auto *mgr = static_cast<UVCStreamManager *>(cb_ctx);
|
||||
s_fb.cam_fb_p = esp_camera_fb_get();
|
||||
|
||||
if (!s_fb.cam_fb_p)
|
||||
@@ -86,6 +91,31 @@ static uvc_fb_t *UVCStreamHelpers::camera_fb_get_cb(void *cb_ctx)
|
||||
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.len = s_fb.cam_fb_p->len;
|
||||
s_fb.uvc_fb.width = s_fb.cam_fb_p->width;
|
||||
@@ -93,12 +123,27 @@ 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.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);
|
||||
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;
|
||||
}
|
||||
@@ -113,14 +158,15 @@ static void UVCStreamHelpers::camera_fb_return_cb(uvc_fb_t *fb, void *cb_ctx)
|
||||
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.");
|
||||
return ESP_FAIL;
|
||||
#endif
|
||||
|
||||
ESP_LOGI(UVC_STREAM_TAG, "Setting up UVC Stream");
|
||||
|
||||
uvc_buffer = static_cast<uint8_t *>(malloc(UVC_MAX_FRAMESIZE_SIZE));
|
||||
// Allocate a fixed-size transfer buffer (compile-time constant)
|
||||
uvc_buffer_size = UVCStreamManager::UVC_MAX_FRAMESIZE_SIZE;
|
||||
uvc_buffer = static_cast<uint8_t *>(malloc(uvc_buffer_size));
|
||||
if (uvc_buffer == nullptr)
|
||||
{
|
||||
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_buffer = uvc_buffer,
|
||||
.uvc_buffer_size = UVC_MAX_FRAMESIZE_SIZE,
|
||||
.uvc_buffer_size = UVCStreamManager::UVC_MAX_FRAMESIZE_SIZE,
|
||||
.start_cb = UVCStreamHelpers::camera_start_cb,
|
||||
.fb_get_cb = UVCStreamHelpers::camera_fb_get_cb,
|
||||
.fb_return_cb = UVCStreamHelpers::camera_fb_return_cb,
|
||||
.stop_cb = UVCStreamHelpers::camera_stop_cb,
|
||||
.cb_ctx = this,
|
||||
};
|
||||
|
||||
esp_err_t ret = uvc_device_config(0, &config);
|
||||
|
||||
@@ -40,7 +40,8 @@ namespace UVCStreamHelpers
|
||||
uvc_fb_t uvc_fb;
|
||||
} 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 void camera_stop_cb(void *cb_ctx);
|
||||
@@ -51,10 +52,14 @@ namespace UVCStreamHelpers
|
||||
class UVCStreamManager
|
||||
{
|
||||
uint8_t *uvc_buffer = nullptr;
|
||||
uint32_t uvc_buffer_size = 0;
|
||||
|
||||
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 start();
|
||||
uint32_t getUvcBufferSize() const { return uvc_buffer_size; }
|
||||
};
|
||||
|
||||
#endif // UVCSTREAM_HPP
|
||||
|
||||
@@ -21,15 +21,16 @@
|
||||
* 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
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "tusb.h"
|
||||
#include "usb_descriptors.h"
|
||||
#include <string.h> // memcpy, strlen
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Device Descriptors
|
||||
//--------------------------------------------------------------------+
|
||||
// Device descriptor: identifies this as a composite device using IAD for UVC
|
||||
tusb_desc_device_t const desc_device = {
|
||||
.bLength = sizeof(tusb_desc_device_t),
|
||||
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||
@@ -63,6 +64,16 @@ uint8_t const *tud_descriptor_device_cb(void)
|
||||
//--------------------------------------------------------------------+
|
||||
// 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 CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
||||
@@ -101,76 +112,39 @@ uint8_t const *tud_descriptor_device_cb(void)
|
||||
|
||||
#endif // CFG_TUD_CAM1_VIDEO_STREAMING_BULK
|
||||
|
||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
||||
#if CFG_TUD_CAM2_VIDEO_STREAMING_BULK
|
||||
// Total length of this configuration
|
||||
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CAM1_VIDEO_CAPTURE_DESC_LEN)
|
||||
|
||||
#if CONFIG_UVC_CAM2_MULTI_FRAMESIZE
|
||||
#if CONFIG_FORMAT_MJPEG_CAM2
|
||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_MULTI_MJPEG_BULK_LEN(4))
|
||||
#elif CONFIG_FORMAT_H264_CAM2
|
||||
#define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN (TUD_VIDEO_CAPTURE_DESC_MULTI_FRAME_BASED_BULK_LEN(4))
|
||||
#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
|
||||
// Full-speed configuration descriptor
|
||||
static uint8_t const desc_fs_configuration[] = {
|
||||
// TUD_CONFIG_DESCRIPTOR(config_number, interface_count, string_index,
|
||||
// total_length, attributes, power_mA)
|
||||
// attributes: 0 = bus-powered (default). Add TUSB_DESC_CONFIG_ATT_SELF_POWERED or _REMOTE_WAKEUP if needed.
|
||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0, 500),
|
||||
// IAD for Video Control
|
||||
#if CFG_TUD_CAM1_VIDEO_STREAMING_BULK
|
||||
#if CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
||||
#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
|
||||
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
|
||||
#else
|
||||
#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,
|
||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||
#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,
|
||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||
#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,
|
||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||
#endif
|
||||
@@ -178,75 +152,32 @@ uint8_t const desc_fs_configuration[] = {
|
||||
#else
|
||||
#if CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
||||
#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
|
||||
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
|
||||
#else
|
||||
#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,
|
||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||
#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,
|
||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||
#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,
|
||||
CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE),
|
||||
#endif
|
||||
#endif // CONFIG_UVC_CAM1_MULTI_FRAMESIZE
|
||||
#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
|
||||
@@ -263,30 +194,26 @@ uint8_t const *tud_descriptor_configuration_cb(uint8_t index)
|
||||
// String Descriptors
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// array of pointer to string descriptors
|
||||
// Array of pointers to string literals. Indices must match STRID_* above.
|
||||
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_PRODUCT, // 2: Product
|
||||
CONFIG_TUSB_SERIAL_NUM, // 3: Serials, should use chip ID, overridden with 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
|
||||
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
||||
"UVC CAM2", // 5: UVC Interface
|
||||
#endif
|
||||
CONFIG_TUSB_SERIAL_NUM, // 3: Serial (overridden by get_serial_number())
|
||||
"UVC CAM1", // 4: UVC Interface name for Cam1 (overridden by get_uvc_device_name())
|
||||
};
|
||||
|
||||
static uint16_t _desc_str[32];
|
||||
|
||||
__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";
|
||||
}
|
||||
|
||||
__attribute__((weak)) const char *get_serial_number(void)
|
||||
{
|
||||
// ETVR Override, by default we're reporting ourselves as the predefined serial number
|
||||
// this should get overwritten with a better implementation
|
||||
// Default serial number, can be overridden by application (e.g., chip ID)
|
||||
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
|
||||
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
|
||||
{
|
||||
printf("am I being asked for this?");
|
||||
(void)langid;
|
||||
|
||||
uint8_t chr_count;
|
||||
@@ -309,15 +235,16 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
|
||||
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
|
||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
|
||||
|
||||
if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])))
|
||||
if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *str = string_desc_arr[index];
|
||||
if (index == 3)
|
||||
// Allow dynamic overrides for specific indices
|
||||
if (index == STRID_SERIAL)
|
||||
str = get_serial_number();
|
||||
if (index == 4)
|
||||
if (index == STRID_UVC_CAM1)
|
||||
str = get_uvc_device_name();
|
||||
if (str == NULL)
|
||||
str = string_desc_arr[index];
|
||||
@@ -340,4 +267,4 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
|
||||
_desc_str[0] = (uint16_t)((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
|
||||
|
||||
return _desc_str;
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,13 @@ endmenu
|
||||
|
||||
menu "OpenIris: General Configuration"
|
||||
|
||||
config GENERAL_WIRED_MODE
|
||||
config GENERAL_DEFAULT_WIRED_MODE
|
||||
bool "Wired mode"
|
||||
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
|
||||
int "UVC delay (s)"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <RestAPI.hpp>
|
||||
#include <main_globals.hpp>
|
||||
|
||||
#ifdef CONFIG_GENERAL_WIRED_MODE
|
||||
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||
#include <UVCStream.hpp>
|
||||
#endif
|
||||
|
||||
@@ -49,11 +49,11 @@ StreamServer streamServer(80, stateManager);
|
||||
|
||||
auto *restAPI = new RestAPI("http://0.0.0.0:81", commandManager);
|
||||
|
||||
#ifdef CONFIG_GENERAL_WIRED_MODE
|
||||
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||
UVCStreamManager uvcStream;
|
||||
#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);
|
||||
|
||||
static void initNVSStorage()
|
||||
@@ -95,9 +95,21 @@ void start_video_streaming(void *arg)
|
||||
|
||||
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]", "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();
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
@@ -105,8 +117,10 @@ void start_video_streaming(void *arg)
|
||||
return;
|
||||
}
|
||||
uvcStream.start();
|
||||
ESP_LOGI("[MAIN]", "UVC streaming started");
|
||||
return; // UVC path complete, do not fall through to WiFi
|
||||
#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");
|
||||
deviceMode = StreamingMode::WIFI;
|
||||
#endif
|
||||
@@ -193,12 +207,12 @@ void startup_timer_callback(void *arg)
|
||||
}
|
||||
else if (deviceMode == StreamingMode::UVC)
|
||||
{
|
||||
#ifdef CONFIG_GENERAL_WIRED_MODE
|
||||
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE
|
||||
ESP_LOGI("[MAIN]", "Starting UVC streaming automatically");
|
||||
activate_streaming(serialTaskHandle);
|
||||
#else
|
||||
ESP_LOGE("[MAIN]", "UVC mode selected but CONFIG_GENERAL_WIRED_MODE not enabled in build!");
|
||||
ESP_LOGI("[MAIN]", "Device will stay in setup mode. Enable CONFIG_GENERAL_WIRED_MODE and rebuild.");
|
||||
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_DEFAULT_WIRED_MODE and rebuild.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
@@ -228,6 +242,7 @@ extern "C" void app_main(void)
|
||||
dependencyRegistry->registerService<ProjectConfig>(DependencyType::project_config, deviceConfig);
|
||||
dependencyRegistry->registerService<CameraManager>(DependencyType::camera_manager, cameraHandler);
|
||||
dependencyRegistry->registerService<WiFiManager>(DependencyType::wifi_manager, wifiManager);
|
||||
dependencyRegistry->registerService<LEDManager>(DependencyType::led_manager, ledManager);
|
||||
// uvc plan
|
||||
// cleanup the logs - 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
|
||||
// finish todos, overhaul stuff a bit
|
||||
|
||||
// esp_log_set_vprintf(&websocket_logger);
|
||||
Logo::printASCII();
|
||||
initNVSStorage();
|
||||
|
||||
// esp_log_set_vprintf(&websocket_logger);
|
||||
deviceConfig->load();
|
||||
ledManager->setup();
|
||||
|
||||
xTaskCreate(
|
||||
@@ -293,11 +308,10 @@ extern "C" void app_main(void)
|
||||
HandleLEDDisplayTask,
|
||||
"HandleLEDDisplayTask",
|
||||
1024 * 2,
|
||||
ledManager,
|
||||
ledManager.get(),
|
||||
3,
|
||||
nullptr);
|
||||
|
||||
deviceConfig->load();
|
||||
serialManager->setup();
|
||||
|
||||
static TaskHandle_t serialManagerHandle = nullptr;
|
||||
@@ -308,8 +322,7 @@ extern "C" void app_main(void)
|
||||
1024 * 6,
|
||||
serialManager,
|
||||
1, // we only rely on the serial manager during provisioning, we can run it slower
|
||||
&serialManagerHandle
|
||||
);
|
||||
&serialManagerHandle);
|
||||
|
||||
wifiManager->Begin();
|
||||
mdnsManager.start();
|
||||
|
||||
12
sdkconfig
12
sdkconfig
@@ -375,7 +375,7 @@ CONFIG_IDF_TOOLCHAIN_GCC=y
|
||||
CONFIG_IDF_TARGET_ARCH_XTENSA=y
|
||||
CONFIG_IDF_TARGET_ARCH="xtensa"
|
||||
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_FIRMWARE_CHIP_ID=0x0009
|
||||
|
||||
@@ -570,7 +570,7 @@ CONFIG_ENV_GPIO_OUT_RANGE_MAX=48
|
||||
#
|
||||
# OpenIris: General Configuration
|
||||
#
|
||||
CONFIG_GENERAL_WIRED_MODE=y
|
||||
# CONFIG_GENERAL_DEFAULT_WIRED_MODE is not set
|
||||
CONFIG_GENERAL_UVC_DELAY=30
|
||||
# end of OpenIris: General Configuration
|
||||
|
||||
@@ -2242,7 +2242,7 @@ CONFIG_FRAMESIZE_QVGA=y
|
||||
# CONFIG_FRAMESIZE_SVGA is not set
|
||||
# CONFIG_FRAMESIZE_HD 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_HEIGT=240
|
||||
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_HEIGHT_1=240
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_1=90
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_1=60
|
||||
# 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_HEIGHT_2=240
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_2=90
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_2=60
|
||||
# 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_HEIGHT_3=240
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_3=90
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_3=60
|
||||
# end of FRAME_SIZE_3
|
||||
# end of UVC_MULTI_FRAME_CONFIG
|
||||
|
||||
|
||||
@@ -570,7 +570,7 @@ CONFIG_ENV_GPIO_OUT_RANGE_MAX=48
|
||||
#
|
||||
# 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
|
||||
# end of OpenIris: General Configuration
|
||||
|
||||
@@ -2242,7 +2242,7 @@ CONFIG_FRAMESIZE_QVGA=y
|
||||
# CONFIG_FRAMESIZE_SVGA is not set
|
||||
# CONFIG_FRAMESIZE_HD 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_HEIGT=240
|
||||
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_HEIGHT_1=240
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_1=90
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_1=60
|
||||
# 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_HEIGHT_2=240
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_2=90
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_2=60
|
||||
# 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_HEIGHT_3=240
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_3=90
|
||||
CONFIG_UVC_MULTI_FRAME_FPS_3=60
|
||||
# end of FRAME_SIZE_3
|
||||
# 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_DUTY_CYCLE=50
|
||||
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_DUTY_CYCLE=100
|
||||
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_DUTY_CYCLE=100
|
||||
CONFIG_LED_EXTERNAL_GPIO=1
|
||||
CONFIG_CAMERA_USB_XCLK_FREQ=23000000 # NOT TESTED
|
||||
CONFIG_GENERAL_WIRED_MODE=y
|
||||
CONFIG_CAMERA_USB_XCLK_FREQ=23000000
|
||||
# CONFIG_GENERAL_DEFAULT_WIRED_MODE is not set
|
||||
@@ -56,4 +56,4 @@ CONFIG_SPIRAM_SPEED=80
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
# CONFIG_LED_EXTERNAL_CONTROL is not set
|
||||
CONFIG_CAMERA_USB_XCLK_FREQ=23000000
|
||||
CONFIG_GENERAL_WIRED_MODE=y
|
||||
# CONFIG_GENERAL_DEFAULT_WIRED_MODE is not set
|
||||
@@ -410,6 +410,55 @@ class OpenIrisDevice:
|
||||
print(f"❌ Failed to parse mode response: {e}")
|
||||
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):
|
||||
"""Monitor device logs until interrupted"""
|
||||
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):
|
||||
print("✅ WiFi configured successfully!")
|
||||
print("💡 Next steps:")
|
||||
print(" 4. Check WiFi status")
|
||||
print(" 5. Connect to WiFi (if needed)")
|
||||
print(" 6. Start streaming when connected")
|
||||
print(" • Open WiFi menu to connect to WiFi (if needed)")
|
||||
print(" • Open WiFi menu to check WiFi status")
|
||||
print(" • Start streaming from the main menu when connected")
|
||||
break
|
||||
else:
|
||||
print("❌ Invalid network number")
|
||||
@@ -708,12 +757,127 @@ def check_wifi_status(device: OpenIrisDevice, args = None):
|
||||
|
||||
def attempt_wifi_connection(device: OpenIrisDevice, args = None):
|
||||
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):
|
||||
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):
|
||||
@@ -736,21 +900,143 @@ def switch_device_mode(device: OpenIrisDevice, args = None):
|
||||
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):
|
||||
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 = {
|
||||
"1": scan_networks,
|
||||
"2": display_networks,
|
||||
"3": configure_wifi,
|
||||
"4": configure_mdns,
|
||||
"5": configure_mdns,
|
||||
"6": check_wifi_status,
|
||||
"7": attempt_wifi_connection,
|
||||
"8": start_streaming,
|
||||
"9": switch_device_mode,
|
||||
"10": monitor_logs,
|
||||
"1": wifi_menu,
|
||||
"2": configure_mdns,
|
||||
"3": configure_mdns,
|
||||
"4": start_streaming,
|
||||
"5": switch_device_mode,
|
||||
"6": set_led_duty_cycle,
|
||||
"7": monitor_logs,
|
||||
"8": get_settings,
|
||||
}
|
||||
|
||||
|
||||
@@ -839,18 +1125,16 @@ def main():
|
||||
# Main interaction loop
|
||||
while True:
|
||||
print("\n🔧 Setup Options:")
|
||||
print("1. 🔍 Scan for WiFi networks")
|
||||
print("2. 📡 Show available networks")
|
||||
print("3. 🔐 Configure WiFi")
|
||||
print("4. 🌐 Configure MDNS")
|
||||
print("5. 💻 Configure UVC Name")
|
||||
print("6. 📶 Check WiFi status")
|
||||
print("7. 🔗 Connect to WiFi")
|
||||
print("8. 🚀 Start streaming mode")
|
||||
print("9. 🔄 Switch device mode (WiFi/UVC/Auto)")
|
||||
print("10. 📋 Monitor logs")
|
||||
print("exit. 🚪 Exit")
|
||||
choice = input("\nSelect option (1-10): ").strip()
|
||||
print(f"{str(1):>2} 📶 WiFi settings")
|
||||
print(f"{str(2):>2} 🌐 Configure MDNS")
|
||||
print(f"{str(3):>2} 💻 Configure UVC Name")
|
||||
print(f"{str(4):>2} 🚀 Start streaming mode")
|
||||
print(f"{str(5):>2} 🔄 Switch device mode (WiFi/UVC/Auto)")
|
||||
print(f"{str(6):>2} 💡 Update PWM Duty Cycle")
|
||||
print(f"{str(7):>2} 📖 Monitor logs")
|
||||
print(f"{str(8):>2} 🧩 Get settings summary")
|
||||
print("exit 🚪 Exit")
|
||||
choice = input("\nSelect option (1-8): ").strip()
|
||||
|
||||
if choice == "exit":
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user