Improvements and refactors after CR, add option to modify mdns name and simplify setup tool

This commit is contained in:
Lorow
2025-08-12 23:48:44 +02:00
parent 9326746e1d
commit 5a86ae042f
21 changed files with 668 additions and 574 deletions

View File

@@ -25,66 +25,70 @@ std::unordered_map<std::string, CommandType> commandTypeMap = {
{"get_device_mode", CommandType::GET_DEVICE_MODE},
};
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] {
PausePayload payload;
cJSON* root = cJSON_Parse(std::string(json).c_str());
if (root) {
cJSON* pauseItem = cJSON_GetObjectItem(root, "pause");
if (pauseItem && cJSON_IsBool(pauseItem)) {
payload.pause = cJSON_IsTrue(pauseItem);
} else {
payload.pause = true; // Default to pause if not specified
}
cJSON_Delete(root);
} else {
payload.pause = true; // Default to pause if parsing fails
}
return PauseCommand(payload);
};
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); };
default:
return nullptr;
}
@@ -135,7 +139,7 @@ CommandResult CommandManager::executeFromJson(const std::string_view json) const
cJSON_AddItemToArray(responses, response);
}
char* jsonString = cJSON_Print(responseDocument);
char *jsonString = cJSON_Print(responseDocument);
cJSON_Delete(responseDocument);
cJSON_Delete(parsedJson);

View File

@@ -22,30 +22,30 @@ public:
CommandResult(std::string message, const Status status)
{
this->status = status;
// Escape quotes and backslashes in the message for JSON
std::string escapedMessage = message;
size_t pos = 0;
// First escape backslashes
while ((pos = escapedMessage.find('\\', pos)) != std::string::npos) {
while ((pos = escapedMessage.find('\\', pos)) != std::string::npos)
{
escapedMessage.replace(pos, 1, "\\\\");
pos += 2;
}
// Then escape quotes
pos = 0;
while ((pos = escapedMessage.find('"', pos)) != std::string::npos) {
while ((pos = escapedMessage.find('"', pos)) != std::string::npos)
{
escapedMessage.replace(pos, 1, "\\\"");
pos += 2;
}
if (status == Status::SUCCESS)
{
// we gotta do it this way, because if we define it as { "result": " {} " } it crashes the compiler, lol
this->message = std::format("{}\"result\":\"{}\"{}", "{", escapedMessage, "}");
this->message = std::format("{{\"result\":\"{}\"}}", escapedMessage);
}
else
{
this->message = std::format("{}\"error\":\"{}\"{}", "{", escapedMessage, "}");
this->message = std::format("{{\"error\":\"{}\"}}", escapedMessage);
}
}
@@ -60,7 +60,7 @@ public:
{
return CommandResult(message, Status::FAILURE);
}
// Create a result that returns raw JSON without wrapper
static CommandResult getRawJsonResult(const std::string &jsonMessage)
{
@@ -70,7 +70,6 @@ public:
}
std::string getResult() const { return this->message; }
};
#endif

View File

@@ -1,25 +1,24 @@
#include "device_commands.hpp"
#include <cJSON.h>
#include <ProjectConfig.hpp>
#include "esp_timer.h"
#include <main_globals.hpp>
// Implementation inspired by SummerSigh work, initial PR opened in openiris repo, adapted to this rewrite
CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) {
CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
{
const auto parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr) {
if (parsedJson == nullptr)
{
return CommandResult::getErrorResult("Invalid payload");
}
const auto modeObject = cJSON_GetObjectItem(parsedJson, "mode");
if (modeObject == nullptr) {
if (modeObject == nullptr)
{
return CommandResult::getErrorResult("Invalid payload - missing mode");
}
const auto mode = modeObject->valueint;
if (mode < 0 || mode > 2) {
if (mode < 0 || mode > 2)
{
return CommandResult::getErrorResult("Invalid payload - unsupported mode");
}
@@ -29,10 +28,12 @@ CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry,
return CommandResult::getSuccessResult("Device mode set");
}
CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) {
CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
{
const auto parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr) {
if (parsedJson == nullptr)
{
return CommandResult::getErrorResult("Invalid payload");
}
@@ -42,18 +43,23 @@ CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> re
auto OTAPassword = oldDeviceConfig.OTAPassword;
auto OTAPort = oldDeviceConfig.OTAPort;
if (const auto OTALoginObject = cJSON_GetObjectItem(parsedJson, "login"); OTALoginObject != nullptr) {
if (const auto newLogin = OTALoginObject->valuestring; strcmp(newLogin, "") != 0) {
if (const auto OTALoginObject = cJSON_GetObjectItem(parsedJson, "login"); OTALoginObject != nullptr)
{
if (const auto newLogin = OTALoginObject->valuestring; strcmp(newLogin, "") != 0)
{
OTALogin = newLogin;
}
}
if (const auto OTAPasswordObject = cJSON_GetObjectItem(parsedJson, "password"); OTAPasswordObject != nullptr) {
if (const auto OTAPasswordObject = cJSON_GetObjectItem(parsedJson, "password"); OTAPasswordObject != nullptr)
{
OTAPassword = OTAPasswordObject->valuestring;
}
if (const auto OTAPortObject = cJSON_GetObjectItem(parsedJson, "port"); OTAPortObject != nullptr) {
if (const auto newPort = OTAPortObject->valueint; newPort >= 82) {
if (const auto OTAPortObject = cJSON_GetObjectItem(parsedJson, "port"); OTAPortObject != nullptr)
{
if (const auto newPort = OTAPortObject->valueint; newPort >= 82)
{
OTAPort = newPort;
}
}
@@ -62,70 +68,82 @@ CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> re
return CommandResult::getSuccessResult("OTA Config set");
}
CommandResult restartDeviceCommand() {
CommandResult restartDeviceCommand()
{
OpenIrisTasks::ScheduleRestart(2000);
return CommandResult::getSuccessResult("Device restarted");
}
CommandResult startStreamingCommand() {
CommandResult startStreamingCommand()
{
activateStreaming(false); // Don't disable setup interfaces by default
return CommandResult::getSuccessResult("Streaming started");
}
CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) {
CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
{
const auto parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr) {
if (parsedJson == nullptr)
{
return CommandResult::getErrorResult("Invalid payload");
}
const auto modeObject = cJSON_GetObjectItem(parsedJson, "mode");
if (modeObject == nullptr) {
if (modeObject == nullptr)
{
return CommandResult::getErrorResult("Invalid payload - missing mode");
}
const char* modeStr = modeObject->valuestring;
const char *modeStr = modeObject->valuestring;
StreamingMode newMode;
ESP_LOGI("[DEVICE_COMMANDS]", "Switch mode command received with mode: %s", modeStr);
if (strcmp(modeStr, "uvc") == 0 || strcmp(modeStr, "UVC") == 0) {
if (strcmp(modeStr, "uvc") == 0)
{
newMode = StreamingMode::UVC;
} else if (strcmp(modeStr, "wifi") == 0 || strcmp(modeStr, "WiFi") == 0 || strcmp(modeStr, "WIFI") == 0) {
}
else if (strcmp(modeStr, "wifi") == 0)
{
newMode = StreamingMode::WIFI;
} else if (strcmp(modeStr, "auto") == 0 || strcmp(modeStr, "AUTO") == 0) {
}
else if (strcmp(modeStr, "auto") == 0)
{
newMode = StreamingMode::AUTO;
} else {
}
else
{
return CommandResult::getErrorResult("Invalid mode - use 'uvc', 'wifi', or 'auto'");
}
const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
ESP_LOGI("[DEVICE_COMMANDS]", "Setting device mode to: %d", (int)newMode);
projectConfig->setDeviceMode(newMode);
cJSON_Delete(parsedJson);
return CommandResult::getSuccessResult("Device mode switched, restart to apply");
}
CommandResult getDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry) {
CommandResult getDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry)
{
const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
StreamingMode currentMode = projectConfig->getDeviceMode();
const char* modeStr = "unknown";
switch (currentMode) {
case StreamingMode::UVC:
modeStr = "UVC";
break;
case StreamingMode::WIFI:
modeStr = "WiFi";
break;
case StreamingMode::AUTO:
modeStr = "Auto";
break;
const char *modeStr = "unknown";
switch (currentMode)
{
case StreamingMode::UVC:
modeStr = "UVC";
break;
case StreamingMode::WIFI:
modeStr = "WiFi";
break;
case StreamingMode::AUTO:
modeStr = "Auto";
break;
}
char result[100];
sprintf(result, "{\"mode\":\"%s\",\"value\":%d}", modeStr, (int)currentMode);
auto result = std::format("{{ \"mode\": \"{}\", \"value\": {} }}", modeStr, static_cast<int>(currentMode));
return CommandResult::getSuccessResult(result);
}

View File

@@ -1,6 +1,13 @@
#include "CommandResult.hpp"
#include "ProjectConfig.hpp"
#include "OpenIrisTasks.hpp"
#include "DependencyRegistry.hpp"
#include "esp_timer.h"
#include "cJSON.h"
#include "main_globals.hpp"
#include <format>
#include <string>
CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);

View File

@@ -1,11 +1,10 @@
#include "scan_commands.hpp"
#include "cJSON.h"
#include "esp_log.h"
#include <string>
CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry) {
CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry)
{
auto wifiManager = registry->resolve<WiFiManager>(DependencyType::wifi_manager);
if (!wifiManager) {
if (!wifiManager)
{
return CommandResult::getErrorResult("WiFiManager not available");
}
@@ -15,7 +14,8 @@ CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry)
cJSON *networksArray = cJSON_CreateArray();
cJSON_AddItemToObject(root, "networks", networksArray);
for (const auto& network : networks) {
for (const auto &network : networks)
{
cJSON *networkObject = cJSON_CreateObject();
cJSON_AddStringToObject(networkObject, "ssid", network.ssid.c_str());
cJSON_AddNumberToObject(networkObject, "channel", network.channel);

View File

@@ -1,9 +1,12 @@
#ifndef SCAN_COMMANDS_HPP
#define SCAN_COMMANDS_HPP
#include "../CommandResult.hpp"
#include "../DependencyRegistry.hpp"
#include "CommandResult.hpp"
#include "DependencyRegistry.hpp"
#include "esp_log.h"
#include <cJSON.h>
#include <wifiManager.hpp>
#include <string>
CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry);

View File

@@ -1,24 +1,39 @@
#include "simple_commands.hpp"
#include "main_globals.hpp"
#include "esp_log.h"
static const char* TAG = "SimpleCommands";
static const char *TAG = "SimpleCommands";
CommandResult PingCommand()
{
return CommandResult::getSuccessResult("pong");
};
CommandResult PauseCommand(const PausePayload& payload)
CommandResult PauseCommand(std::string_view jsonPayload)
{
PausePayload payload;
// pause by default if this command gets executed, even if the payload was invalid
payload.pause = true;
cJSON *root = cJSON_Parse(std::string(jsonPayload).c_str());
if (root)
{
cJSON *pauseItem = cJSON_GetObjectItem(root, "pause");
if (pauseItem && cJSON_IsBool(pauseItem))
{
payload.pause = cJSON_IsTrue(pauseItem);
}
cJSON_Delete(root);
}
ESP_LOGI(TAG, "Pause command received: %s", payload.pause ? "true" : "false");
startupPaused = payload.pause;
if (payload.pause) {
setStartupPaused(payload.pause);
if (payload.pause)
{
ESP_LOGI(TAG, "Startup paused - device will remain in configuration mode");
return CommandResult::getSuccessResult("Startup paused");
} else {
}
else
{
ESP_LOGI(TAG, "Startup resumed");
return CommandResult::getSuccessResult("Startup resumed");
}

View File

@@ -4,8 +4,11 @@
#include <string>
#include "CommandResult.hpp"
#include "CommandSchema.hpp"
#include "main_globals.hpp"
#include "esp_log.h"
#include <cJSON.h>
CommandResult PingCommand();
CommandResult PauseCommand(const PausePayload& payload);
CommandResult PauseCommand(std::string_view jsonPayload);
#endif

View File

@@ -4,37 +4,47 @@
// Forward declarations
extern void start_video_streaming(void *arg);
// Global variables to be set by main
static esp_timer_handle_t* g_streaming_timer_handle = nullptr;
static TaskHandle_t* g_serial_manager_handle = nullptr;
// Functions for main to set the global handles
void setStreamingTimerHandle(esp_timer_handle_t* handle) {
g_streaming_timer_handle = handle;
bool startupCommandReceived = false;
bool getStartupCommandReceived()
{
return startupCommandReceived;
}
void setSerialManagerHandle(TaskHandle_t* handle) {
g_serial_manager_handle = handle;
void setStartupCommandReceived(bool startupCommandReceived)
{
startupCommandReceived = startupCommandReceived;
}
// Functions for components to access the handles
esp_timer_handle_t* getStreamingTimerHandle() {
return g_streaming_timer_handle;
}
TaskHandle_t* getSerialManagerHandle() {
static TaskHandle_t *g_serial_manager_handle = nullptr;
TaskHandle_t *getSerialManagerHandle()
{
return g_serial_manager_handle;
}
void setSerialManagerHandle(TaskHandle_t *serialManagerHandle)
{
g_serial_manager_handle = serialManagerHandle;
}
// Global pause state
bool startupPaused = false;
bool getStartupPaused()
{
return startupPaused;
}
void setStartupPaused(bool startupPaused)
{
startupPaused = startupPaused;
}
// Function to manually activate streaming
void activateStreaming(bool disableSetup) {
void activateStreaming(bool disableSetup)
{
ESP_LOGI("[MAIN_GLOBALS]", "Manually activating streaming, disableSetup=%s", disableSetup ? "true" : "false");
TaskHandle_t* serialHandle = disableSetup ? g_serial_manager_handle : nullptr;
void* serialTaskHandle = (serialHandle && *serialHandle) ? *serialHandle : nullptr;
TaskHandle_t *serialHandle = disableSetup ? g_serial_manager_handle : nullptr;
void *serialTaskHandle = (serialHandle && *serialHandle) ? *serialHandle : nullptr;
start_video_streaming(serialTaskHandle);
}

View File

@@ -7,22 +7,18 @@
#include "freertos/task.h"
// Functions for main to set global handles
void setStreamingTimerHandle(esp_timer_handle_t* handle);
void setSerialManagerHandle(TaskHandle_t* handle);
// Functions to access global handles from components
esp_timer_handle_t* getStreamingTimerHandle();
TaskHandle_t* getSerialManagerHandle();
TaskHandle_t *getSerialManagerHandle();
void setSerialManagerHandle(TaskHandle_t *serialManagerHandle);
// Function to manually activate streaming
void activateStreaming(bool disableSetup = false);
// Function to notify that a command was received during startup
extern void notify_startup_command_received();
bool getStartupCommandReceived();
void setStartupCommandReceived(bool startupCommandReceived);
// Global variables for startup state
extern bool startupCommandReceived;
extern esp_timer_handle_t startupTimerHandle;
extern bool startupPaused;
bool getStartupPaused();
void setStartupPaused(bool startupPaused);
#endif

View File

@@ -9,7 +9,6 @@
#include "Models.hpp"
#include <Preferences.hpp>
int getNetworkCount(Preferences *pref);
void saveNetworkCount(Preferences *pref, int count);
@@ -60,7 +59,7 @@ public:
const std::string &password,
uint8_t channel);
void setWiFiTxPower(uint8_t power);
void setDeviceMode(StreamingMode deviceMode);
void setDeviceMode(StreamingMode deviceMode);
StreamingMode getDeviceMode();
private:

View File

@@ -4,8 +4,9 @@
#define BUF_SIZE (1024)
SerialManager::SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle, std::shared_ptr<ProjectConfig> deviceConfig)
: commandManager(commandManager), timerHandle(timerHandle), deviceConfig(deviceConfig) {
SerialManager::SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle, std::shared_ptr<ProjectConfig> deviceConfig)
: commandManager(commandManager), timerHandle(timerHandle), deviceConfig(deviceConfig)
{
this->data = static_cast<uint8_t *>(malloc(BUF_SIZE));
this->temp_data = static_cast<uint8_t *>(malloc(256));
}
@@ -40,47 +41,62 @@ 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);
}
}
// Function to notify that a command was received during startup
void SerialManager::notify_startup_command_received()
{
setStartupCommandReceived(true);
// Cancel the startup timer if it's still running
if (timerHandle != nullptr)
{
esp_timer_stop(*timerHandle);
esp_timer_delete(*timerHandle);
timerHandle = nullptr;
ESP_LOGI("[MAIN]", "Startup timer cancelled, staying in heartbeat mode");
}
}
void SerialManager::send_heartbeat()
{
// Get the MAC address as unique identifier
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
// Format as serial number string
char serial_number[18];
sprintf(serial_number, "%02X%02X%02X%02X%02X%02X",
sprintf(serial_number, "%02X%02X%02X%02X%02X%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// Create heartbeat JSON with serial number
char heartbeat[128];
sprintf(heartbeat, "{\"heartbeat\":\"openiris_setup_mode\",\"serial\":\"%s\"}\n", serial_number);
usb_serial_jtag_write_bytes(heartbeat, strlen(heartbeat), 1000 / 20);
}
bool SerialManager::should_send_heartbeat()
{
// Always send heartbeat during startup delay or if no WiFi configured
extern bool startupCommandReceived;
extern esp_timer_handle_t startupTimerHandle;
// If startup timer is still running, always send heartbeat
if (startupTimerHandle != nullptr) {
if (timerHandle != nullptr)
{
return true;
}
// If in heartbeat mode after startup, continue sending
if (startupCommandReceived) {
if (getStartupCommandReceived())
{
return true;
}
// Otherwise, only send if no WiFi credentials configured
const auto wifiConfigs = deviceConfig->getWifiConfigs();
return wifiConfigs.empty();
@@ -95,16 +111,16 @@ void HandleSerialManagerTask(void *pvParameters)
while (true)
{
serialManager->try_receive();
// Send heartbeat every 2 seconds, but only if no WiFi credentials are set
TickType_t currentTime = xTaskGetTickCount();
if ((currentTime - lastHeartbeat) >= heartbeatInterval) {
if (serialManager->should_send_heartbeat()) {
if ((currentTime - lastHeartbeat) >= heartbeatInterval)
{
if (serialManager->should_send_heartbeat())
{
serialManager->send_heartbeat();
}
lastHeartbeat = currentTime;
}
vTaskDelay(pdMS_TO_TICKS(50)); // Small delay to prevent busy waiting
}
}

View File

@@ -26,6 +26,7 @@ public:
void try_receive();
void send_heartbeat();
bool should_send_heartbeat();
void notify_startup_command_received();
private:
std::shared_ptr<CommandManager> commandManager;

View File

@@ -3,25 +3,29 @@ constexpr int UVC_MAX_FRAMESIZE_SIZE(75 * 1024);
static const char *UVC_STREAM_TAG = "[UVC DEVICE]";
extern "C" {
extern "C"
{
static char serial_number_str[13];
const char *get_uvc_device_name() {
const char *get_uvc_device_name()
{
return deviceConfig->getMDNSConfig().hostname.c_str();
}
const char *get_serial_number(void) {
if (serial_number_str[0] == '\0') {
const char *get_serial_number(void)
{
if (serial_number_str[0] == '\0')
{
uint8_t mac_address[6];
esp_err_t result = esp_efuse_mac_get_default(mac_address);
if (result != ESP_OK) {
if (result != ESP_OK)
{
ESP_LOGE(UVC_STREAM_TAG, "Failed to get MAC address of the board, returning default serial number");
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]
);
mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5]);
}
return serial_number_str;
}

View File

@@ -1,32 +1,38 @@
#include "WiFiScanner.hpp"
#include <cstring>
static const char *TAG = "WiFiScanner";
WiFiScanner::WiFiScanner() {}
void WiFiScanner::scanResultCallback(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
auto* scanner = static_cast<WiFiScanner*>(arg);
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
void WiFiScanner::scanResultCallback(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
auto *scanner = static_cast<WiFiScanner *>(arg);
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE)
{
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
if (ap_count == 0) {
if (ap_count == 0)
{
ESP_LOGI(TAG, "No access points found");
return;
}
wifi_ap_record_t* ap_records = new wifi_ap_record_t[ap_count];
wifi_ap_record_t *ap_records = new wifi_ap_record_t[ap_count];
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_records));
scanner->networks.clear();
for (uint16_t i = 0; i < ap_count; i++) {
for (uint16_t i = 0; i < ap_count; i++)
{
WiFiNetwork network;
network.ssid = std::string(reinterpret_cast<char*>(ap_records[i].ssid));
network.ssid = std::string(reinterpret_cast<char *>(ap_records[i].ssid));
network.channel = ap_records[i].primary;
network.rssi = ap_records[i].rssi;
memcpy(network.mac, ap_records[i].bssid, 6);
network.auth_mode = ap_records[i].authmode;
scanner->networks.push_back(network);
}
@@ -35,81 +41,88 @@ void WiFiScanner::scanResultCallback(void* arg, esp_event_base_t event_base,
}
}
std::vector<WiFiNetwork> WiFiScanner::scanNetworks() {
// todo this is garbage
std::vector<WiFiNetwork> WiFiScanner::scanNetworks()
{
std::vector<WiFiNetwork> scan_results;
// Check if WiFi is initialized
wifi_mode_t mode;
esp_err_t err = esp_wifi_get_mode(&mode);
if (err == ESP_ERR_WIFI_NOT_INIT) {
if (err == ESP_ERR_WIFI_NOT_INIT)
{
ESP_LOGE(TAG, "WiFi not initialized");
return scan_results;
}
// Give WiFi more time to be ready
vTaskDelay(pdMS_TO_TICKS(500));
// Stop any ongoing scan
esp_wifi_scan_stop();
// Try sequential channel scanning as a workaround
bool try_sequential_scan = true; // Enable sequential scan
if (!try_sequential_scan) {
bool try_sequential_scan = true; // Enable sequential scan
if (!try_sequential_scan)
{
// Normal all-channel scan
wifi_scan_config_t scan_config = {
.ssid = nullptr,
.bssid = nullptr,
.channel = 0, // 0 means scan all channels
.channel = 0, // 0 means scan all channels
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE, // Active scan
.scan_type = WIFI_SCAN_TYPE_ACTIVE, // Active scan
.scan_time = {
.active = {
.min = 120, // Min per channel
.max = 300 // Max per channel
.min = 120, // Min per channel
.max = 300 // Max per channel
},
.passive = 360
},
.home_chan_dwell_time = 0, // 0 for default
.channel_bitmap = 0 // 0 for all channels
.passive = 360},
.home_chan_dwell_time = 0, // 0 for default
.channel_bitmap = 0 // 0 for all channels
};
err = esp_wifi_scan_start(&scan_config, false);
if (err != ESP_OK) {
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to start scan: %s", esp_err_to_name(err));
return scan_results;
}
} else {
}
else
{
// Sequential channel scan - scan each channel individually
std::vector<wifi_ap_record_t> all_records;
for (uint8_t ch = 1; ch <= 13; ch++) {
for (uint8_t ch = 1; ch <= 13; ch++)
{
wifi_scan_config_t scan_config = {
.ssid = nullptr,
.bssid = nullptr,
.channel = ch, // Specific channel
.channel = ch,
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time = {
.active = {
.min = 100,
.max = 200
},
.passive = 300
},
.max = 200},
.passive = 300},
.home_chan_dwell_time = 0,
.channel_bitmap = 0
};
.channel_bitmap = 0};
err = esp_wifi_scan_start(&scan_config, true); // Blocking scan
if (err == ESP_OK) {
if (err == ESP_OK)
{
uint16_t ch_count = 0;
esp_wifi_scan_get_ap_num(&ch_count);
if (ch_count > 0) {
wifi_ap_record_t* ch_records = new wifi_ap_record_t[ch_count];
if (esp_wifi_scan_get_ap_records(&ch_count, ch_records) == ESP_OK) {
for (uint16_t i = 0; i < ch_count; i++) {
if (ch_count > 0)
{
wifi_ap_record_t *ch_records = new wifi_ap_record_t[ch_count];
if (esp_wifi_scan_get_ap_records(&ch_count, ch_records) == ESP_OK)
{
for (uint16_t i = 0; i < ch_count; i++)
{
all_records.push_back(ch_records[i]);
}
}
@@ -118,59 +131,66 @@ std::vector<WiFiNetwork> WiFiScanner::scanNetworks() {
}
vTaskDelay(pdMS_TO_TICKS(50));
}
// Process all collected records
for (const auto& record : all_records) {
for (const auto &record : all_records)
{
WiFiNetwork network;
network.ssid = std::string(reinterpret_cast<const char*>(record.ssid));
network.ssid = std::string(reinterpret_cast<const char *>(record.ssid));
network.channel = record.primary;
network.rssi = record.rssi;
memcpy(network.mac, record.bssid, 6);
network.auth_mode = record.authmode;
scan_results.push_back(network);
}
// Skip the normal result processing
return scan_results;
}
// Wait for scan completion with timeout
int timeout_ms = 15000; // 15 second timeout
int elapsed_ms = 0;
while (elapsed_ms < timeout_ms) {
while (elapsed_ms < timeout_ms)
{
uint16_t temp_count = 0;
esp_err_t count_err = esp_wifi_scan_get_ap_num(&temp_count);
if (count_err == ESP_OK) {
if (count_err == ESP_OK)
{
// Wait a bit longer after finding networks to ensure scan is complete
if (temp_count > 0 && elapsed_ms > 5000) {
if (temp_count > 0 && elapsed_ms > 5000)
{
break;
}
}
vTaskDelay(pdMS_TO_TICKS(200));
elapsed_ms += 200;
}
if (elapsed_ms >= timeout_ms) {
if (elapsed_ms >= timeout_ms)
{
ESP_LOGE(TAG, "Scan timeout after %d ms", timeout_ms);
esp_wifi_scan_stop();
return scan_results;
}
// Get scan results
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
if (ap_count == 0) {
if (ap_count == 0)
{
ESP_LOGI(TAG, "No access points found");
return scan_results;
}
wifi_ap_record_t* ap_records = new wifi_ap_record_t[ap_count];
wifi_ap_record_t *ap_records = new wifi_ap_record_t[ap_count];
err = esp_wifi_scan_get_ap_records(&ap_count, ap_records);
if (err != ESP_OK) {
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to get scan records: %s", esp_err_to_name(err));
delete[] ap_records;
return scan_results;
@@ -178,28 +198,26 @@ std::vector<WiFiNetwork> WiFiScanner::scanNetworks() {
// Build the results vector and track channels found
bool channels_found[15] = {false}; // Track channels 0-14
for (uint16_t i = 0; i < ap_count; i++) {
for (uint16_t i = 0; i < ap_count; i++)
{
WiFiNetwork network;
network.ssid = std::string(reinterpret_cast<char*>(ap_records[i].ssid));
network.ssid = std::string(reinterpret_cast<char *>(ap_records[i].ssid));
network.channel = ap_records[i].primary;
network.rssi = ap_records[i].rssi;
memcpy(network.mac, ap_records[i].bssid, 6);
network.auth_mode = ap_records[i].authmode;
if (network.channel <= 14) {
if (network.channel <= 14)
{
channels_found[network.channel] = true;
}
scan_results.push_back(network);
}
delete[] ap_records;
ESP_LOGI(TAG, "Found %d access points", ap_count);
// Also update the member variable for compatibility
networks = scan_results;
return scan_results;
}

View File

@@ -7,7 +7,8 @@
#include "esp_wifi.h"
#include "esp_log.h"
struct WiFiNetwork {
struct WiFiNetwork
{
std::string ssid;
uint8_t channel;
int8_t rssi;
@@ -15,14 +16,14 @@ struct WiFiNetwork {
wifi_auth_mode_t auth_mode;
};
class WiFiScanner {
class WiFiScanner
{
public:
WiFiScanner();
std::vector<WiFiNetwork> scanNetworks();
static void scanResultCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
static void scanResultCallback(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
private:
static constexpr char const* TAG = "WiFiScanner";
std::vector<WiFiNetwork> networks;
};

View File

@@ -2,7 +2,6 @@
static auto WIFI_MANAGER_TAG = "[WIFI_MANAGER]";
// Define the global variables declared as extern in the header
int s_retry_num = 0;
EventGroupHandle_t s_wifi_event_group;
@@ -19,9 +18,9 @@ void WiFiManagerHelpers::event_handler(void *arg, esp_event_base_t event_base,
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
wifi_event_sta_disconnected_t* disconnected = (wifi_event_sta_disconnected_t*) event_data;
const auto *disconnected = static_cast<wifi_event_sta_disconnected_t *>(event_data);
ESP_LOGI(WIFI_MANAGER_TAG, "Disconnect reason: %d", disconnected->reason);
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY)
{
esp_wifi_connect();
@@ -51,55 +50,52 @@ void WiFiManager::SetCredentials(const char *ssid, const char *password)
{
// Clear the config first
memset(&_wifi_cfg, 0, sizeof(_wifi_cfg));
// Copy SSID with null termination
size_t ssid_len = std::min(strlen(ssid), sizeof(_wifi_cfg.sta.ssid) - 1);
memcpy(_wifi_cfg.sta.ssid, ssid, ssid_len);
_wifi_cfg.sta.ssid[ssid_len] = '\0';
// Copy password with null termination
size_t pass_len = std::min(strlen(password), sizeof(_wifi_cfg.sta.password) - 1);
memcpy(_wifi_cfg.sta.password, password, pass_len);
_wifi_cfg.sta.password[pass_len] = '\0';
// Set other required fields
// Use open auth if no password, otherwise allow any WPA variant
if (strlen(password) == 0) {
if (strlen(password) == 0)
{
_wifi_cfg.sta.threshold.authmode = WIFI_AUTH_OPEN;
} else {
}
else
{
// IMPORTANT: Set threshold to WEP to accept ANY security mode >= WEP
// This allows WPA, WPA2, WPA3, etc. The driver will negotiate the highest common mode
_wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WEP;
}
// CRITICAL: Disable PMF completely - this often causes handshake timeouts
_wifi_cfg.sta.pmf_cfg.capable = false;
_wifi_cfg.sta.pmf_cfg.required = false;
// IMPORTANT: Set scan method to ALL channels
_wifi_cfg.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
_wifi_cfg.sta.bssid_set = 0; // Don't use specific BSSID
_wifi_cfg.sta.channel = 0; // Scan all channels
_wifi_cfg.sta.bssid_set = 0; // Don't use specific BSSID
_wifi_cfg.sta.channel = 0; // Scan all channels
// Additional settings that might help with compatibility
_wifi_cfg.sta.listen_interval = 0; // Default listen interval
_wifi_cfg.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; // Connect to strongest signal
_wifi_cfg.sta.listen_interval = 0; // Default listen interval
_wifi_cfg.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; // Connect to strongest signal
// IMPORTANT: For WPA/WPA2 Personal networks
_wifi_cfg.sta.threshold.rssi = -127; // Accept any signal strength
_wifi_cfg.sta.sae_pwe_h2e = WPA3_SAE_PWE_UNSPECIFIED; // Let driver decide SAE mode
_wifi_cfg.sta.threshold.rssi = -127; // Accept any signal strength
_wifi_cfg.sta.sae_pwe_h2e = WPA3_SAE_PWE_UNSPECIFIED; // Let driver decide SAE mode
// Log what we're trying to connect to with detailed debugging
ESP_LOGI(WIFI_MANAGER_TAG, "Setting credentials for SSID: '%s' (length: %d)", ssid, (int)strlen(ssid));
ESP_LOGI(WIFI_MANAGER_TAG, "Password: '%s' (length: %d)", password, (int)strlen(password));
ESP_LOGI(WIFI_MANAGER_TAG, "Auth mode: %d, PMF capable: %d",
ESP_LOGI(WIFI_MANAGER_TAG, "Auth mode: %d, PMF capable: %d",
_wifi_cfg.sta.threshold.authmode, _wifi_cfg.sta.pmf_cfg.capable);
// Print hex dump of SSID to catch any hidden characters
ESP_LOG_BUFFER_HEX_LEVEL(WIFI_MANAGER_TAG, _wifi_cfg.sta.ssid, strlen((char*)_wifi_cfg.sta.ssid), ESP_LOG_INFO);
// Print hex dump of password to catch any hidden characters
ESP_LOG_BUFFER_HEX_LEVEL(WIFI_MANAGER_TAG, _wifi_cfg.sta.password, strlen((char*)_wifi_cfg.sta.password), ESP_LOG_INFO);
}
void WiFiManager::ConnectWithHardcodedCredentials()
@@ -164,11 +160,11 @@ void WiFiManager::ConnectWithStoredCredentials()
// Stop WiFi once before the loop
esp_wifi_stop();
vTaskDelay(pdMS_TO_TICKS(100));
// Ensure we're in STA mode
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
for (const auto& network : networks)
for (const auto &network : networks)
{
// Reset retry counter for each network attempt
s_retry_num = 0;
@@ -177,19 +173,14 @@ void WiFiManager::ConnectWithStoredCredentials()
// Update config without stopping WiFi again
ESP_LOGI(WIFI_MANAGER_TAG, "Attempting to connect to SSID: '%s'", network.ssid.c_str());
// Double-check the actual config being sent to WiFi driver
ESP_LOGI(WIFI_MANAGER_TAG, "Final SSID in config: '%s' (len: %d)",
(char*)_wifi_cfg.sta.ssid, (int)strlen((char*)_wifi_cfg.sta.ssid));
ESP_LOGI(WIFI_MANAGER_TAG, "Final password in config: '%s' (len: %d)",
(char*)_wifi_cfg.sta.password, (int)strlen((char*)_wifi_cfg.sta.password));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &_wifi_cfg));
xQueueSend(this->eventQueue, &event, 10);
// Start WiFi if not already started
esp_err_t start_err = esp_wifi_start();
if (start_err != ESP_OK && start_err != ESP_ERR_WIFI_STATE) {
if (start_err != ESP_OK && start_err != ESP_ERR_WIFI_STATE)
{
ESP_LOGE(WIFI_MANAGER_TAG, "Failed to start WiFi: %s", esp_err_to_name(start_err));
continue;
}
@@ -214,7 +205,7 @@ void WiFiManager::ConnectWithStoredCredentials()
}
ESP_LOGE(WIFI_MANAGER_TAG, "Failed to connect to SSID:%s, trying next stored network",
network.ssid.c_str());
// Disconnect before trying next network
esp_wifi_disconnect();
vTaskDelay(pdMS_TO_TICKS(100));
@@ -249,107 +240,117 @@ void WiFiManager::SetupAccessPoint()
ESP_LOGI(WIFI_MANAGER_TAG, "AP started.");
}
std::vector<WiFiNetwork> WiFiManager::ScanNetworks() {
wifi_mode_t current_mode;
esp_err_t err = esp_wifi_get_mode(&current_mode);
if (err == ESP_ERR_WIFI_NOT_INIT) {
ESP_LOGE(WIFI_MANAGER_TAG, "WiFi not initialized for scanning");
return std::vector<WiFiNetwork>();
std::vector<WiFiNetwork> WiFiManager::ScanNetworks()
{
wifi_mode_t current_mode;
esp_err_t err = esp_wifi_get_mode(&current_mode);
if (err == ESP_ERR_WIFI_NOT_INIT)
{
ESP_LOGE(WIFI_MANAGER_TAG, "WiFi not initialized for scanning");
return std::vector<WiFiNetwork>();
}
// If we're in AP-only mode, we need STA interface for scanning
if (current_mode == WIFI_MODE_AP)
{
ESP_LOGI(WIFI_MANAGER_TAG, "AP mode detected, checking for STA interface");
// Check if STA netif already exists
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
bool sta_netif_exists = (sta_netif != nullptr);
if (!sta_netif_exists)
{
ESP_LOGI(WIFI_MANAGER_TAG, "Creating STA interface for scanning");
sta_netif = esp_netif_create_default_wifi_sta();
}
// If we're in AP-only mode, we need STA interface for scanning
if (current_mode == WIFI_MODE_AP) {
ESP_LOGI(WIFI_MANAGER_TAG, "AP mode detected, checking for STA interface");
// Check if STA netif already exists
esp_netif_t* sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
bool sta_netif_exists = (sta_netif != nullptr);
if (!sta_netif_exists) {
ESP_LOGI(WIFI_MANAGER_TAG, "Creating STA interface for scanning");
sta_netif = esp_netif_create_default_wifi_sta();
}
ESP_LOGI(WIFI_MANAGER_TAG, "Switching to APSTA mode for scanning");
err = esp_wifi_set_mode(WIFI_MODE_APSTA);
if (err != ESP_OK) {
ESP_LOGE(WIFI_MANAGER_TAG, "Failed to set APSTA mode: %s", esp_err_to_name(err));
if (!sta_netif_exists && sta_netif) {
esp_netif_destroy(sta_netif);
}
return std::vector<WiFiNetwork>();
}
// Configure STA with empty config to prevent auto-connect
wifi_config_t empty_config = {};
esp_wifi_set_config(WIFI_IF_STA, &empty_config);
// Ensure STA is disconnected and not trying to connect
esp_wifi_disconnect();
vTaskDelay(pdMS_TO_TICKS(500));
// Longer delay for mode to stabilize and enable all channels
vTaskDelay(pdMS_TO_TICKS(1500));
// Perform scan
auto networks = wifiScanner->scanNetworks();
// Restore AP-only mode
ESP_LOGI(WIFI_MANAGER_TAG, "Restoring AP-only mode");
esp_wifi_set_mode(WIFI_MODE_AP);
// Clean up STA interface if we created it
if (!sta_netif_exists && sta_netif) {
esp_netif_destroy(sta_netif);
}
return networks;
ESP_LOGI(WIFI_MANAGER_TAG, "Switching to APSTA mode for scanning");
err = esp_wifi_set_mode(WIFI_MODE_APSTA);
if (err != ESP_OK)
{
ESP_LOGE(WIFI_MANAGER_TAG, "Failed to set APSTA mode: %s", esp_err_to_name(err));
if (!sta_netif_exists && sta_netif)
{
esp_netif_destroy(sta_netif);
}
return std::vector<WiFiNetwork>();
}
// If already in STA or APSTA mode, scan directly
return wifiScanner->scanNetworks();
// Configure STA with empty config to prevent auto-connect
wifi_config_t empty_config = {};
esp_wifi_set_config(WIFI_IF_STA, &empty_config);
// Ensure STA is disconnected and not trying to connect
esp_wifi_disconnect();
// Longer delay for mode to stabilize and enable all channels
vTaskDelay(pdMS_TO_TICKS(2000));
// Perform scan
auto networks = wifiScanner->scanNetworks();
// Restore AP-only mode
ESP_LOGI(WIFI_MANAGER_TAG, "Restoring AP-only mode");
esp_wifi_set_mode(WIFI_MODE_AP);
// Clean up STA interface if we created it
if (!sta_netif_exists && sta_netif)
{
esp_netif_destroy(sta_netif);
}
return networks;
}
// If already in STA or APSTA mode, scan directly
return wifiScanner->scanNetworks();
}
WiFiState_e WiFiManager::GetCurrentWiFiState() {
return this->stateManager->GetWifiState();
WiFiState_e WiFiManager::GetCurrentWiFiState()
{
return this->stateManager->GetWifiState();
}
void WiFiManager::TryConnectToStoredNetworks() {
ESP_LOGI(WIFI_MANAGER_TAG, "Manual WiFi connection attempt requested");
// Check current WiFi mode
wifi_mode_t current_mode;
esp_err_t err = esp_wifi_get_mode(&current_mode);
if (err != ESP_OK) {
ESP_LOGE(WIFI_MANAGER_TAG, "Failed to get WiFi mode: %s", esp_err_to_name(err));
return;
void WiFiManager::TryConnectToStoredNetworks()
{
ESP_LOGI(WIFI_MANAGER_TAG, "Manual WiFi connection attempt requested");
// Check current WiFi mode
wifi_mode_t current_mode;
esp_err_t err = esp_wifi_get_mode(&current_mode);
if (err != ESP_OK)
{
ESP_LOGE(WIFI_MANAGER_TAG, "Failed to get WiFi mode: %s", esp_err_to_name(err));
return;
}
// If in AP mode, we need to properly transition to STA mode
if (current_mode == WIFI_MODE_AP || current_mode == WIFI_MODE_APSTA)
{
ESP_LOGI(WIFI_MANAGER_TAG, "Currently in AP mode, transitioning to STA mode");
// Stop WiFi first
esp_wifi_stop();
vTaskDelay(pdMS_TO_TICKS(100));
// Check if STA interface exists, create if needed
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (sta_netif == nullptr)
{
ESP_LOGI(WIFI_MANAGER_TAG, "Creating STA interface");
sta_netif = esp_netif_create_default_wifi_sta();
}
// If in AP mode, we need to properly transition to STA mode
if (current_mode == WIFI_MODE_AP || current_mode == WIFI_MODE_APSTA) {
ESP_LOGI(WIFI_MANAGER_TAG, "Currently in AP mode, transitioning to STA mode");
// Stop WiFi first
esp_wifi_stop();
vTaskDelay(pdMS_TO_TICKS(100));
// Check if STA interface exists, create if needed
esp_netif_t* sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (sta_netif == nullptr) {
ESP_LOGI(WIFI_MANAGER_TAG, "Creating STA interface");
sta_netif = esp_netif_create_default_wifi_sta();
}
// Set to STA mode
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
vTaskDelay(pdMS_TO_TICKS(100));
}
// Reset retry counter and clear all event bits
s_retry_num = 0;
xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT | WIFI_CONNECTED_BIT);
this->ConnectWithStoredCredentials();
// Set to STA mode
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
vTaskDelay(pdMS_TO_TICKS(100));
}
// Reset retry counter and clear all event bits
s_retry_num = 0;
xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT | WIFI_CONNECTED_BIT);
this->ConnectWithStoredCredentials();
}
void WiFiManager::Begin()
@@ -363,18 +364,6 @@ void WiFiManager::Begin()
wifi_init_config_t esp_wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&esp_wifi_init_config));
// Set WiFi country to enable all channels (1-14)
wifi_country_t country_config = {
.cc = "JP", // Japan allows channels 1-14 (most permissive)
.schan = 1,
.nchan = 14,
.max_tx_power = 20,
.policy = WIFI_COUNTRY_POLICY_AUTO
};
ESP_ERROR_CHECK(esp_wifi_set_country(&country_config));
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&WiFiManagerHelpers::event_handler,
@@ -387,8 +376,8 @@ void WiFiManager::Begin()
&instance_got_ip));
_wifi_cfg = {};
_wifi_cfg.sta.threshold.authmode = WIFI_AUTH_OPEN; // Start with open, will be set properly by SetCredentials
_wifi_cfg.sta.pmf_cfg.capable = false; // Disable PMF by default
_wifi_cfg.sta.threshold.authmode = WIFI_AUTH_OPEN; // Start with open, will be set properly by SetCredentials
_wifi_cfg.sta.pmf_cfg.capable = false; // Disable PMF by default
_wifi_cfg.sta.pmf_cfg.required = false;
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));

View File

@@ -19,9 +19,6 @@
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
extern int s_retry_num;
extern EventGroupHandle_t s_wifi_event_group;
namespace WiFiManagerHelpers
{
void event_handler(void *arg, esp_event_base_t event_base,

View File

@@ -30,10 +30,8 @@
#define CONFIG_LED_C_PIN_GPIO (gpio_num_t) CONFIG_LED_C_PIN
esp_timer_handle_t timerHandle;
esp_timer_handle_t startupTimerHandle;
QueueHandle_t eventQueue = xQueueCreate(10, sizeof(SystemEvent));
QueueHandle_t ledStateQueue = xQueueCreate(10, sizeof(uint32_t));
bool startupCommandReceived = false;
auto *stateManager = new StateManager(eventQueue, ledStateQueue);
auto dependencyRegistry = std::make_shared<DependencyRegistry>();
@@ -99,22 +97,16 @@ void start_video_streaming(void *arg)
{
#ifdef CONFIG_WIRED_MODE
ESP_LOGI("[MAIN]", "Starting UVC streaming mode.");
// Initialize UVC if not already done
static bool uvcInitialized = false;
if (!uvcInitialized)
ESP_LOGI("[MAIN]", "Initializing UVC hardware...");
esp_err_t ret = uvcStream.setup();
if (ret != ESP_OK)
{
ESP_LOGI("[MAIN]", "Initializing UVC hardware...");
esp_err_t ret = uvcStream.setup();
if (ret != ESP_OK)
{
ESP_LOGE("[MAIN]", "Failed to initialize UVC: %s", esp_err_to_name(ret));
return;
}
uvcInitialized = true;
ESP_LOGE("[MAIN]", "Failed to initialize UVC: %s", esp_err_to_name(ret));
return;
}
uvcStream.start();
#else
ESP_LOGE("[MAIN]", "UVC mode selected but CONFIG_WIRED_MODE not enabled in build!");
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
@@ -159,10 +151,10 @@ void activate_streaming(TaskHandle_t serialTaskHandle = nullptr)
void startup_timer_callback(void *arg)
{
ESP_LOGI("[MAIN]", "Startup timer fired, startupCommandReceived=%s, startupPaused=%s",
startupCommandReceived ? "true" : "false",
startupPaused ? "true" : "false");
getStartupCommandReceived() ? "true" : "false",
getStartupPaused() ? "true" : "false");
if (!startupCommandReceived && !startupPaused)
if (!getStartupCommandReceived() && !getStartupPaused())
{
ESP_LOGI("[MAIN]", "No command received during startup delay, proceeding with automatic mode startup");
@@ -216,7 +208,7 @@ void startup_timer_callback(void *arg)
}
else
{
if (startupPaused)
if (getStartupPaused())
{
ESP_LOGI("[MAIN]", "Startup paused, staying in heartbeat mode");
}
@@ -227,23 +219,8 @@ void startup_timer_callback(void *arg)
}
// Delete the timer after it fires
esp_timer_delete(startupTimerHandle);
startupTimerHandle = nullptr;
}
// Function to notify that a command was received during startup
void notify_startup_command_received()
{
startupCommandReceived = true;
// Cancel the startup timer if it's still running
if (startupTimerHandle != nullptr)
{
esp_timer_stop(startupTimerHandle);
esp_timer_delete(startupTimerHandle);
startupTimerHandle = nullptr;
ESP_LOGI("[MAIN]", "Startup timer cancelled, staying in heartbeat mode");
}
esp_timer_delete(timerHandle);
timerHandle = nullptr;
}
extern "C" void app_main(void)
@@ -337,9 +314,6 @@ extern "C" void app_main(void)
restAPI->begin();
cameraHandler->setupCamera();
// Don't initialize UVC here - wait for the timer to decide
// UVC will be initialized when streaming actually starts
xTaskCreate(
HandleRestAPIPollTask,
"HandleRestAPIPollTask",
@@ -354,23 +328,6 @@ extern "C" void app_main(void)
ESP_LOGI("[MAIN]", "=====================================");
ESP_LOGI("[MAIN]", "Device will wait 20 seconds for commands...");
// Get the stored device mode
StreamingMode deviceMode = deviceConfig->getDeviceMode();
ESP_LOGI("[MAIN]", "Stored device mode: %s (value: %d)",
deviceMode == StreamingMode::UVC ? "UVC" : deviceMode == StreamingMode::WIFI ? "WiFi"
: "Auto",
(int)deviceMode);
// If WiFi credentials exist, attempt connection but stay in setup mode
if (!deviceConfig->getWifiConfigs().empty())
{
ESP_LOGI("[MAIN]", "WiFi credentials found, attempting connection in background");
// WiFi connection happens in wifiManager->Begin() above
}
// Reset startup state
startupCommandReceived = false;
// Create a one-shot timer for 20 seconds
const esp_timer_create_args_t startup_timer_args = {
.callback = &startup_timer_callback,
@@ -379,13 +336,10 @@ extern "C" void app_main(void)
.name = "startup_timer",
.skip_unhandled_events = false};
ESP_ERROR_CHECK(esp_timer_create(&startup_timer_args, &startupTimerHandle));
ESP_ERROR_CHECK(esp_timer_start_once(startupTimerHandle, 20000000)); // 20 seconds in microseconds
ESP_ERROR_CHECK(esp_timer_create(&startup_timer_args, &timerHandle));
ESP_ERROR_CHECK(esp_timer_start_once(timerHandle, 20000000)); // 20 seconds in microseconds
ESP_LOGI("[MAIN]", "Started 20-second startup timer");
ESP_LOGI("[MAIN]", "Send any command within 20 seconds to enter heartbeat mode");
// Set global handles for component access
setStreamingTimerHandle(&timerHandle);
setSerialManagerHandle(&serialManagerHandle);
}

View File

@@ -6,11 +6,13 @@ This tool automatically discovers OpenIris devices via heartbeat,
allows WiFi configuration, and monitors the device logs.
"""
import re
import json
import time
import threading
import argparse
import sys
import string
from typing import Dict, List, Optional, Tuple
import serial
import serial.tools.list_ports
@@ -98,7 +100,6 @@ class OpenIrisDevice:
cmd_obj["commands"][0]["data"] = params
cmd_json = json.dumps(cmd_obj) + '\n'
try:
# Clear any pending data
self.connection.reset_input_buffer()
@@ -130,9 +131,6 @@ class OpenIrisDevice:
print(f"📡 Raw: {repr(data)}")
print(f"📝 Buffer: {repr(response_buffer[-200:])}")
# Clean buffer and look for JSON
import re
# Remove ANSI escape sequences
clean_buffer = re.sub(r'\x1b\[[0-9;]*m', '', response_buffer)
clean_buffer = clean_buffer.replace('\r', '')
@@ -303,6 +301,26 @@ class OpenIrisDevice:
print("✅ WiFi credentials set successfully")
return True
def set_mdns_name(self, name: str) -> bool:
"""Configure the MDNS hostname"""
print(f"🔧 Setting MDNS name to '{name}'...")
print(" The device should be accessible under")
print(f"http://{name}.local/")
print("\n Note, this will also modify the name of the UVC device")
params = {
"hostname": name
}
response = self.send_command("set_mdns", params)
if "error" in response:
print(f"❌ MDNS name setup failed: {response['error']}")
return False
print("✅ MDNS name set successfully")
return True
def get_wifi_status(self) -> Dict:
"""Get current WiFi connection status"""
response = self.send_command("get_wifi_status")
@@ -561,8 +579,89 @@ class OpenIrisDiscovery:
pass
def display_networks(networks: List[WiFiNetwork]):
def scan_networks(device: OpenIrisDevice, args = None):
should_use_custom_timeout = input("Use custom scan timeout? (y/N): ").strip().lower()
if should_use_custom_timeout == 'y':
try:
timeout = int(input("Enter timeout in seconds (5-120): "))
if 5 <= timeout <= 120:
device.scan_networks(timeout)
else:
print("❌ Timeout must be between 5 and 120 seconds")
device.scan_networks(args.scan_timeout)
except ValueError:
print("❌ Invalid timeout, using default")
device.scan_networks(args.scan_timeout)
else:
device.scan_networks(args.scan_timeout)
def configure_wifi(device: OpenIrisDevice, args = None):
if not device.networks:
print("❌ No networks available. Please scan first.")
return
display_networks(device)
while True:
net_choice = input("\nEnter network number (or 'back'): ").strip()
if net_choice.lower() == 'back':
break
try:
net_idx = int(net_choice) - 1
except ValueError:
print("❌ Please enter a number or 'back'")
continue
sorted_networks = sorted(device.networks, key=lambda x: x.rssi, reverse=True)
if 0 <= net_idx < len(sorted_networks):
selected_network = sorted_networks[net_idx]
print(f"\n🔐 Selected: {selected_network.ssid}")
print(f"Security: {selected_network.security_type}")
if selected_network.auth_mode == 0: # Open network
password = ""
print("🔓 Open network - no password required")
else:
password = input("Enter WiFi password: ")
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")
break
else:
print("❌ Invalid network number")
def configure_mdns(device: OpenIrisDevice, args = None):
print("💡 Please enter your preferred device name, your board will be accessible under http://<name>.local/")
print("💡 Please avoid spaces and special characters")
print(" To back out, enter `back`")
print("\n Note, this will also modify the name of the UVC device")
while True:
name_choice = input("\nDevice name: ").strip()
if any(space in name_choice for space in string.whitespace):
print("❌ Please avoid spaces and special characters")
else:
break
if name_choice.lower() == "back":
return
if device.set_mdns_name(name_choice):
print("✅ MDNS configured successfully!")
def display_networks(device: OpenIrisDevice, args = None):
"""Display available WiFi networks in a formatted table"""
networks = device.networks
if not networks:
print("❌ No networks available")
return
@@ -596,6 +695,65 @@ def display_networks(networks: List[WiFiNetwork]):
print()
def check_wifi_status(device: OpenIrisDevice, args = None):
status = device.get_wifi_status()
if status:
print(f"📶 WiFi Status: {status.get('status', 'unknown')}")
print(f"📡 Networks configured: {status.get('networks_configured', 0)}")
if status.get('ip_address'):
print(f"🌐 IP Address: {status['ip_address']}")
else:
print("❌ Unable to get WiFi status")
def attempt_wifi_connection(device: OpenIrisDevice, args = None):
device.connect_wifi()
print("🕰️ Wait a few seconds then check status (option 4)")
def start_streaming(device: OpenIrisDevice, args = None):
device.start_streaming()
print("🚀 Streaming started! Use option 8 to monitor logs.")
def switch_device_mode(device: OpenIrisDevice, args = None):
current_mode = device.get_device_mode()
print(f"\n📍 Current device mode: {current_mode}")
print("\n🔄 Select new device mode:")
print("1. WiFi - Stream over WiFi connection")
print("2. UVC - Stream as USB webcam")
print("3. Auto - Automatic mode selection")
mode_choice = input("\nSelect mode (1-3): ").strip()
if mode_choice == "1":
device.switch_mode("wifi")
elif mode_choice == "2":
device.switch_mode("uvc")
elif mode_choice == "3":
device.switch_mode("auto")
else:
print("❌ Invalid mode selection")
def monitor_logs(device: OpenIrisDevice, args = None):
device.monitor_logs()
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,
}
def main():
parser = argparse.ArgumentParser(description="OpenIris Setup CLI Tool")
parser.add_argument("--timeout", type=int, default=3,
@@ -684,123 +842,25 @@ def main():
print("1. 🔍 Scan for WiFi networks")
print("2. 📡 Show available networks")
print("3. 🔐 Configure WiFi")
print("4. 📶 Check WiFi status")
print("5. 🔗 Connect to WiFi")
print("6. 🚀 Start streaming mode")
print("7. 🔄 Switch device mode (WiFi/UVC/Auto)")
print("8. 📋 Monitor logs")
print("9. 🚪 Exit")
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()
choice = input("\nSelect option (1-9): ").strip()
if choice == "1":
# Ask if user wants custom timeout
custom = input("Use custom scan timeout? (y/N): ").strip().lower()
if custom == 'y':
try:
timeout = int(input("Enter timeout in seconds (5-120): "))
if 5 <= timeout <= 120:
device.scan_networks(timeout)
else:
print("❌ Timeout must be between 5 and 120 seconds")
device.scan_networks(args.scan_timeout)
except ValueError:
print("❌ Invalid timeout, using default")
device.scan_networks(args.scan_timeout)
else:
device.scan_networks(args.scan_timeout)
elif choice == "2":
display_networks(device.networks)
elif choice == "3":
if not device.networks:
print("❌ No networks available. Please scan first.")
continue
display_networks(device.networks)
while True:
try:
net_choice = input("\nEnter network number (or 'back'): ").strip()
if net_choice.lower() == 'back':
break
net_idx = int(net_choice) - 1
sorted_networks = sorted(device.networks, key=lambda x: x.rssi, reverse=True)
if 0 <= net_idx < len(sorted_networks):
selected_network = sorted_networks[net_idx]
print(f"\n🔐 Selected: {selected_network.ssid}")
print(f"Security: {selected_network.security_type}")
if selected_network.auth_mode == 0: # Open network
password = ""
print("🔓 Open network - no password required")
else:
password = input("Enter WiFi password: ")
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")
break
else:
print("❌ Invalid network number")
except ValueError:
print("❌ Please enter a number or 'back'")
elif choice == "4":
# Check WiFi status
status = device.get_wifi_status()
if status:
print(f"📶 WiFi Status: {status.get('status', 'unknown')}")
print(f"📡 Networks configured: {status.get('networks_configured', 0)}")
if status.get('ip_address'):
print(f"🌐 IP Address: {status['ip_address']}")
else:
print("❌ Unable to get WiFi status")
elif choice == "5":
# Attempt WiFi connection
device.connect_wifi()
print("🕰️ Wait a few seconds then check status (option 4)")
elif choice == "6":
device.start_streaming()
print("🚀 Streaming started! Use option 8 to monitor logs.")
elif choice == "7":
# Switch device mode
current_mode = device.get_device_mode()
print(f"\n📍 Current device mode: {current_mode}")
print("\n🔄 Select new device mode:")
print("1. WiFi - Stream over WiFi connection")
print("2. UVC - Stream as USB webcam")
print("3. Auto - Automatic mode selection")
mode_choice = input("\nSelect mode (1-3): ").strip()
if mode_choice == "1":
device.switch_mode("wifi")
elif mode_choice == "2":
device.switch_mode("uvc")
elif mode_choice == "3":
device.switch_mode("auto")
else:
print("❌ Invalid mode selection")
elif choice == "8":
device.monitor_logs()
elif choice == "9":
if choice == "exit":
break
else:
command_to_run = COMMANDS_MAP.get(choice, None)
if not command_to_run:
print("❌ Invalid option")
continue
command_to_run(device, args)
except KeyboardInterrupt:
print("\n🛑 Setup interrupted")

View File

@@ -28,7 +28,7 @@ class ESPWiFiScanner:
self.serial.reset_input_buffer()
command = '{"commands":[{"command":"scan_networks","data":{}}]}\n'
command = '{"commands":[{"command":"scan_networks"}]}\n'
self.serial.write(command.encode())
timeout_start = time.time()
@@ -92,7 +92,7 @@ def main():
import argparse
parser = argparse.ArgumentParser(description='ESP32 WiFi Scanner')
parser.add_argument('port', nargs='?', default='COM15', help='Serial port (default: COM9)')
parser.add_argument('port', nargs='?', help='Serial port - COM1, /dev/ttyUSB0, etc.')
parser.add_argument('-t', '--timeout', type=int, default=30,
help='Scan timeout in seconds (default: 30)')
args = parser.parse_args()