upload mutimodal

This commit is contained in:
Summer
2025-06-11 04:55:38 -07:00
committed by Lorow
parent b30a00900f
commit d9ace4bc05
41 changed files with 2484 additions and 219 deletions

View File

@@ -80,10 +80,10 @@ void CameraManager::setupCameraPinout()
.pixel_format = PIXFORMAT_JPEG, // YUV422,GRAYSCALE,RGB565,JPEG
.frame_size = FRAMESIZE_240X240, // QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates.
.jpeg_quality = 7, // 0-63, for OV series camera sensors, lower number means higher quality
.fb_count = 2, // 3 // When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode.
.fb_location = CAMERA_FB_IN_PSRAM, // maybe it cannot put them fully in psram?
.grab_mode = CAMERA_GRAB_WHEN_EMPTY, // CAMERA_GRAB_LATEST
.jpeg_quality = 10, // 0-63, for OV series camera sensors, lower number means higher quality
.fb_count = 2, // Use 2 for streaming to balance memory and performance
.fb_location = CAMERA_FB_IN_PSRAM, // Use PSRAM for frame buffers
.grab_mode = CAMERA_GRAB_LATEST, // Always grab the latest frame for streaming
};
}

View File

@@ -7,8 +7,9 @@ idf_component_register(
"CommandManager/commands/config_commands.cpp"
"CommandManager/commands/mdns_commands.cpp"
"CommandManager/commands/device_commands.cpp"
"CommandManager/commands/scan_commands.cpp"
INCLUDE_DIRS
"CommandManager"
"CommandManager/commands"
REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks
REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers
)

View File

@@ -1,7 +1,9 @@
#include "CommandManager.hpp"
#include <cstdlib>
std::unordered_map<std::string, CommandType> commandTypeMap = {
{"ping", CommandType::PING},
{"pause", CommandType::PAUSE},
{"set_wifi", CommandType::SET_WIFI},
{"update_wifi", CommandType::UPDATE_WIFI},
{"set_streaming_mode", CommandType::SET_STREAMING_MODE},
@@ -15,6 +17,12 @@ std::unordered_map<std::string, CommandType> commandTypeMap = {
{"get_config", CommandType::GET_CONFIG},
{"reset_config", CommandType::RESET_CONFIG},
{"restart_device", CommandType::RESTART_DEVICE},
{"scan_networks", CommandType::SCAN_NETWORKS},
{"start_streaming", CommandType::START_STREAMING},
{"get_wifi_status", CommandType::GET_WIFI_STATUS},
{"connect_wifi", CommandType::CONNECT_WIFI},
{"switch_mode", CommandType::SWITCH_MODE},
{"get_device_mode", CommandType::GET_DEVICE_MODE},
};
std::function<CommandResult()> CommandManager::createCommand(const CommandType type, std::string_view json) const {
@@ -22,6 +30,23 @@ std::function<CommandResult()> CommandManager::createCommand(const CommandType t
{
case CommandType::PING:
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);
};
case CommandType::SET_STREAMING_MODE:
return [this, json] {return setDeviceModeCommand(this->registry, json); };
case CommandType::UPDATE_OTA_CREDENTIALS:
@@ -48,6 +73,18 @@ std::function<CommandResult()> CommandManager::createCommand(const CommandType t
return [this, json] { return resetConfigCommand(this->registry, json); };
case CommandType::RESTART_DEVICE:
return restartDeviceCommand;
case CommandType::SCAN_NETWORKS:
return [this] { return scanNetworksCommand(this->registry); };
case CommandType::START_STREAMING:
return startStreamingCommand;
case CommandType::GET_WIFI_STATUS:
return [this] { return getWiFiStatusCommand(this->registry); };
case CommandType::CONNECT_WIFI:
return [this] { return connectWiFiCommand(this->registry); };
case CommandType::SWITCH_MODE:
return [this, json] { return switchModeCommand(this->registry, json); };
case CommandType::GET_DEVICE_MODE:
return [this] { return getDeviceModeCommand(this->registry); };
default:
return nullptr;
}
@@ -98,11 +135,15 @@ CommandResult CommandManager::executeFromJson(const std::string_view json) const
cJSON_AddItemToArray(responses, response);
}
const auto jsonString = cJSON_Print(responseDocument);
char* jsonString = cJSON_Print(responseDocument);
cJSON_Delete(responseDocument);
cJSON_Delete(parsedJson);
return CommandResult::getSuccessResult(jsonString);
// Return the JSON response directly without wrapping it
// The responseDocument already contains the proper format: {"results": [...]}
CommandResult result = CommandResult::getRawJsonResult(jsonString);
free(jsonString);
return result;
}
CommandResult CommandManager::executeFromType(const CommandType type, const std::string_view json) const

View File

@@ -17,12 +17,14 @@
#include "commands/mdns_commands.hpp"
#include "commands/wifi_commands.hpp"
#include "commands/device_commands.hpp"
#include "commands/scan_commands.hpp"
#include <cJSON.h>
enum class CommandType
{
None,
PING,
PAUSE,
SET_WIFI,
UPDATE_OTA_CREDENTIALS,
SET_STREAMING_MODE,
@@ -36,6 +38,12 @@ enum class CommandType
GET_CONFIG,
RESET_CONFIG,
RESTART_DEVICE,
SCAN_NETWORKS,
START_STREAMING,
GET_WIFI_STATUS,
CONNECT_WIFI,
SWITCH_MODE,
GET_DEVICE_MODE,
};
class CommandManager

View File

@@ -3,34 +3,52 @@
#include <format>
#include <string>
#include <algorithm>
class CommandResult
{
private:
public:
enum class Status
{
SUCCESS,
FAILURE,
};
private:
Status status;
std::string message;
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) {
escapedMessage.replace(pos, 1, "\\\\");
pos += 2;
}
// Then escape quotes
pos = 0;
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\":\" {} \"{}", "{", message, "}");
this->message = std::format("{}\"result\":\"{}\"{}", "{", escapedMessage, "}");
}
else
{
this->message = std::format("{}\"error\":\" {} \"{}", "{", message, "}");
this->message = std::format("{}\"error\":\"{}\"{}", "{", escapedMessage, "}");
}
}
public:
bool isSuccess() const { return status == Status::SUCCESS; }
static CommandResult getSuccessResult(const std::string &message)
@@ -42,8 +60,17 @@ public:
{
return CommandResult(message, Status::FAILURE);
}
// Create a result that returns raw JSON without wrapper
static CommandResult getRawJsonResult(const std::string &jsonMessage)
{
CommandResult result("", Status::SUCCESS);
result.message = jsonMessage;
return result;
}
std::string getResult() const { return this->message; }
};
#endif

View File

@@ -57,4 +57,9 @@ struct RestartCameraPayload : BasePayload
{
bool mode;
};
struct PausePayload : BasePayload
{
bool pause;
};
#endif

View File

@@ -7,7 +7,8 @@
enum class DependencyType
{
project_config,
camera_manager
camera_manager,
wifi_manager
};
class DependencyRegistry

View File

@@ -2,6 +2,8 @@
#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) {
@@ -64,3 +66,66 @@ CommandResult restartDeviceCommand() {
OpenIrisTasks::ScheduleRestart(2000);
return CommandResult::getSuccessResult("Device restarted");
}
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) {
const auto parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr) {
return CommandResult::getErrorResult("Invalid payload");
}
const auto modeObject = cJSON_GetObjectItem(parsedJson, "mode");
if (modeObject == nullptr) {
return CommandResult::getErrorResult("Invalid payload - missing mode");
}
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) {
newMode = StreamingMode::UVC;
} else if (strcmp(modeStr, "wifi") == 0 || strcmp(modeStr, "WiFi") == 0 || strcmp(modeStr, "WIFI") == 0) {
newMode = StreamingMode::WIFI;
} else if (strcmp(modeStr, "auto") == 0 || strcmp(modeStr, "AUTO") == 0) {
newMode = StreamingMode::AUTO;
} 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) {
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;
}
char result[100];
sprintf(result, "{\"mode\":\"%s\",\"value\":%d}", modeStr, (int)currentMode);
return CommandResult::getSuccessResult(result);
}

View File

@@ -6,4 +6,10 @@ CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry,
CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
CommandResult restartDeviceCommand();
CommandResult restartDeviceCommand();
CommandResult startStreamingCommand();
CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
CommandResult getDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry);

View File

@@ -0,0 +1,38 @@
#include "scan_commands.hpp"
#include "cJSON.h"
#include "esp_log.h"
#include <string>
CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry) {
auto wifiManager = registry->resolve<WiFiManager>(DependencyType::wifi_manager);
if (!wifiManager) {
return CommandResult::getErrorResult("WiFiManager not available");
}
auto networks = wifiManager->ScanNetworks();
cJSON *root = cJSON_CreateObject();
cJSON *networksArray = cJSON_CreateArray();
cJSON_AddItemToObject(root, "networks", networksArray);
for (const auto& network : networks) {
cJSON *networkObject = cJSON_CreateObject();
cJSON_AddStringToObject(networkObject, "ssid", network.ssid.c_str());
cJSON_AddNumberToObject(networkObject, "channel", network.channel);
cJSON_AddNumberToObject(networkObject, "rssi", network.rssi);
char mac_str[18];
sprintf(mac_str, "%02x:%02x:%02x:%02x:%02x:%02x",
network.mac[0], network.mac[1], network.mac[2],
network.mac[3], network.mac[4], network.mac[5]);
cJSON_AddStringToObject(networkObject, "mac_address", mac_str);
cJSON_AddNumberToObject(networkObject, "auth_mode", network.auth_mode);
cJSON_AddItemToArray(networksArray, networkObject);
}
char *json_string = cJSON_PrintUnformatted(root);
printf("%s\n", json_string);
cJSON_Delete(root);
free(json_string);
return CommandResult::getSuccessResult("Networks scanned");
}

View File

@@ -0,0 +1,10 @@
#ifndef SCAN_COMMANDS_HPP
#define SCAN_COMMANDS_HPP
#include "../CommandResult.hpp"
#include "../DependencyRegistry.hpp"
#include <wifiManager.hpp>
CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry);
#endif

View File

@@ -1,6 +1,25 @@
#include "simple_commands.hpp"
#include "main_globals.hpp"
#include "esp_log.h"
static const char* TAG = "SimpleCommands";
CommandResult PingCommand()
{
return CommandResult::getSuccessResult("pong");
};
CommandResult PauseCommand(const PausePayload& payload)
{
ESP_LOGI(TAG, "Pause command received: %s", payload.pause ? "true" : "false");
startupPaused = payload.pause;
if (payload.pause) {
ESP_LOGI(TAG, "Startup paused - device will remain in configuration mode");
return CommandResult::getSuccessResult("Startup paused");
} else {
ESP_LOGI(TAG, "Startup resumed");
return CommandResult::getSuccessResult("Startup resumed");
}
};

View File

@@ -3,7 +3,9 @@
#include <string>
#include "CommandResult.hpp"
#include "CommandSchema.hpp"
CommandResult PingCommand();
CommandResult PauseCommand(const PausePayload& payload);
#endif

View File

@@ -1,4 +1,5 @@
#include "wifi_commands.hpp"
#include "esp_netif.h"
std::optional<WifiPayload> parseSetWiFiCommandPayload(std::string_view jsonPayload)
{
@@ -223,3 +224,79 @@ CommandResult updateAPWiFiCommand(std::shared_ptr<DependencyRegistry> registry,
return CommandResult::getSuccessResult("Config updated");
}
CommandResult getWiFiStatusCommand(std::shared_ptr<DependencyRegistry> registry) {
auto wifiManager = registry->resolve<WiFiManager>(DependencyType::wifi_manager);
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
// Get current WiFi state
auto wifiState = wifiManager->GetCurrentWiFiState();
auto networks = projectConfig->getWifiConfigs();
cJSON* statusJson = cJSON_CreateObject();
// Add WiFi state
const char* stateStr = "unknown";
switch(wifiState) {
case WiFiState_e::WiFiState_NotInitialized:
stateStr = "not_initialized";
break;
case WiFiState_e::WiFiState_Initialized:
stateStr = "initialized";
break;
case WiFiState_e::WiFiState_ReadyToConnect:
stateStr = "ready";
break;
case WiFiState_e::WiFiState_Connecting:
stateStr = "connecting";
break;
case WiFiState_e::WiFiState_WaitingForIp:
stateStr = "waiting_for_ip";
break;
case WiFiState_e::WiFiState_Connected:
stateStr = "connected";
break;
case WiFiState_e::WiFiState_Disconnected:
stateStr = "disconnected";
break;
case WiFiState_e::WiFiState_Error:
stateStr = "error";
break;
}
cJSON_AddStringToObject(statusJson, "status", stateStr);
cJSON_AddNumberToObject(statusJson, "networks_configured", networks.size());
// Add IP info if connected
if (wifiState == WiFiState_e::WiFiState_Connected) {
// Get IP address from ESP32
esp_netif_ip_info_t ip_info;
esp_netif_t* netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (netif && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) {
char ip_str[16];
sprintf(ip_str, IPSTR, IP2STR(&ip_info.ip));
cJSON_AddStringToObject(statusJson, "ip_address", ip_str);
}
}
char* statusString = cJSON_PrintUnformatted(statusJson);
std::string result(statusString);
free(statusString);
cJSON_Delete(statusJson);
return CommandResult::getSuccessResult(result);
}
CommandResult connectWiFiCommand(std::shared_ptr<DependencyRegistry> registry) {
auto wifiManager = registry->resolve<WiFiManager>(DependencyType::wifi_manager);
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
auto networks = projectConfig->getWifiConfigs();
if (networks.empty()) {
return CommandResult::getErrorResult("No WiFi networks configured");
}
// Trigger WiFi connection attempt
wifiManager->TryConnectToStoredNetworks();
return CommandResult::getSuccessResult("WiFi connection attempt started");
}

View File

@@ -1,4 +1,6 @@
#include <ProjectConfig.hpp>
#include <wifiManager.hpp>
#include <StateManager.hpp>
#include <memory>
#include <string>
#include <optional>
@@ -18,3 +20,6 @@ CommandResult updateWiFiCommand(std::shared_ptr<DependencyRegistry> registry, st
std::optional<UpdateAPWiFiPayload> parseUpdateAPWiFiCommandPayload(std::string_view jsonPayload);
CommandResult updateAPWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
CommandResult getWiFiStatusCommand(std::shared_ptr<DependencyRegistry> registry);
CommandResult connectWiFiCommand(std::shared_ptr<DependencyRegistry> registry);

View File

@@ -1,4 +1,4 @@
idf_component_register(SRCS "Helpers/helpers.cpp"
idf_component_register(SRCS "Helpers/helpers.cpp" "Helpers/main_globals.cpp"
INCLUDE_DIRS "Helpers"
REQUIRES esp_timer
)

View File

@@ -0,0 +1,40 @@
#include "main_globals.hpp"
#include "esp_log.h"
// 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;
}
void setSerialManagerHandle(TaskHandle_t* handle) {
g_serial_manager_handle = handle;
}
// Functions for components to access the handles
esp_timer_handle_t* getStreamingTimerHandle() {
return g_streaming_timer_handle;
}
TaskHandle_t* getSerialManagerHandle() {
return g_serial_manager_handle;
}
// Global pause state
bool startupPaused = false;
// Function to manually activate streaming
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;
start_video_streaming(serialTaskHandle);
}

View File

@@ -0,0 +1,28 @@
#pragma once
#ifndef MAIN_GLOBALS_HPP
#define MAIN_GLOBALS_HPP
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#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();
// 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();
// Global variables for startup state
extern bool startupCommandReceived;
extern esp_timer_handle_t startupTimerHandle;
extern bool startupPaused;
#endif

View File

@@ -8,6 +8,7 @@
#include <helpers.hpp>
#include "sdkconfig.h"
#include <Preferences.hpp>
#include "esp_log.h"
struct BaseConfigModel
{
@@ -31,11 +32,14 @@ struct DeviceMode_t : BaseConfigModel {
explicit DeviceMode_t( Preferences *pref) : BaseConfigModel(pref), mode(StreamingMode::AUTO){}
void load() {
this->mode = static_cast<StreamingMode>(this->pref->getInt("mode", 0));
int stored_mode = this->pref->getInt("mode", 0);
this->mode = static_cast<StreamingMode>(stored_mode);
ESP_LOGI("DeviceMode", "Loaded device mode: %d", stored_mode);
}
void save() const {
this->pref->putInt("mode", static_cast<int>(this->mode));
ESP_LOGI("DeviceMode", "Saved device mode: %d", static_cast<int>(this->mode));
}
};
@@ -182,6 +186,9 @@ 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",
index, this->name.c_str(), this->ssid.c_str(), this->channel);
};
void save() const {
@@ -193,6 +200,9 @@ 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",
this->index, this->name.c_str(), this->ssid.c_str(), this->channel);
};
std::string toRepresentation()

View File

@@ -147,6 +147,8 @@ void ProjectConfig::setWifiConfig(const std::string &networkName,
it->password = password;
it->channel = channel;
it->power = power;
// Save the updated network immediately
it->save();
return;
}
@@ -155,6 +157,9 @@ void ProjectConfig::setWifiConfig(const std::string &networkName,
ESP_LOGI(CONFIGURATION_TAG, "No networks, We're adding a new network");
this->config.networks.emplace_back(this->pref, static_cast<uint8_t>(0), networkName, ssid, password, channel,
power);
// Save the new network immediately
this->config.networks.back().save();
saveNetworkCount(this->pref, 1);
return;
}
@@ -168,6 +173,9 @@ void ProjectConfig::setWifiConfig(const std::string &networkName,
uint8_t last_index = getNetworkCount(this->pref);
this->config.networks.emplace_back(this->pref, last_index, networkName, ssid, password, channel,
power);
// Save the new network immediately
this->config.networks.back().save();
saveNetworkCount(this->pref, static_cast<int>(this->config.networks.size()));
}
else
{
@@ -212,6 +220,7 @@ void ProjectConfig::setAPWifiConfig(const std::string &ssid,
void ProjectConfig::setDeviceMode(const StreamingMode deviceMode) {
this->config.device_mode.mode = deviceMode;
this->config.device_mode.save(); // Save immediately
}
//**********************************************************************************************************************
@@ -249,6 +258,10 @@ TrackerConfig_t &ProjectConfig::getTrackerConfig()
return this->config;
}
DeviceMode_t &ProjectConfig::getDeviceMode() {
DeviceMode_t &ProjectConfig::getDeviceModeConfig() {
return this->config.device_mode;
}
StreamingMode ProjectConfig::getDeviceMode() {
return this->config.device_mode.mode;
}

View File

@@ -31,7 +31,7 @@ public:
bool reset();
DeviceConfig_t &getDeviceConfig();
DeviceMode_t &getDeviceMode();
DeviceMode_t &getDeviceModeConfig();
CameraConfig_t &getCameraConfig();
std::vector<WiFiConfig_t> &getWifiConfigs();
AP_WiFiConfig_t &getAPWifiConfig();
@@ -61,6 +61,7 @@ public:
uint8_t channel);
void setWiFiTxPower(uint8_t power);
void setDeviceMode(StreamingMode deviceMode);
StreamingMode getDeviceMode();
private:
Preferences *pref;

View File

@@ -1,4 +1,4 @@
idf_component_register(SRCS "SerialManager/SerialManager.cpp"
INCLUDE_DIRS "SerialManager"
REQUIRES esp_driver_uart CommandManager
REQUIRES esp_driver_uart CommandManager ProjectConfig
)

View File

@@ -1,8 +1,11 @@
#include "SerialManager.hpp"
#include "esp_log.h"
#include "main_globals.hpp"
#define BUF_SIZE (1024)
SerialManager::SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle) : commandManager(commandManager), timerHandle(timerHandle) {
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));
}
@@ -20,9 +23,6 @@ void SerialManager::try_receive()
int current_position = 0;
int len = usb_serial_jtag_read_bytes(this->temp_data, 256, 1000 / 20);
if (len) {
esp_timer_stop(*timerHandle);
}
// since we've got something on the serial port
// we gotta keep reading until we've got the whole message
while (len)
@@ -38,19 +38,73 @@ void SerialManager::try_receive()
data[current_position] = '\0';
current_position = 0;
// 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);
esp_timer_start_once(*timerHandle, 30000000); // 30s
}
}
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",
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) {
return true;
}
// If in heartbeat mode after startup, continue sending
if (startupCommandReceived) {
return true;
}
// Otherwise, only send if no WiFi credentials configured
const auto wifiConfigs = deviceConfig->getWifiConfigs();
return wifiConfigs.empty();
}
void HandleSerialManagerTask(void *pvParameters)
{
auto const serialManager = static_cast<SerialManager *>(pvParameters);
TickType_t lastHeartbeat = xTaskGetTickCount();
const TickType_t heartbeatInterval = pdMS_TO_TICKS(2000); // 2 second heartbeat
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()) {
serialManager->send_heartbeat();
}
lastHeartbeat = currentTime;
}
vTaskDelay(pdMS_TO_TICKS(50)); // Small delay to prevent busy waiting
}
}

View File

@@ -6,6 +6,7 @@
#include <string>
#include <memory>
#include <CommandManager.hpp>
#include <ProjectConfig.hpp>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
@@ -15,17 +16,21 @@
#include "driver/usb_serial_jtag.h"
#include "esp_vfs_usb_serial_jtag.h"
#include "esp_vfs_dev.h"
#include "esp_mac.h"
class SerialManager
{
public:
explicit SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle);
explicit SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle, std::shared_ptr<ProjectConfig> deviceConfig);
void setup();
void try_receive();
void send_heartbeat();
bool should_send_heartbeat();
private:
std::shared_ptr<CommandManager> commandManager;
esp_timer_handle_t *timerHandle;
std::shared_ptr<ProjectConfig> deviceConfig;
uint8_t *data;
uint8_t *temp_data;
};

View File

@@ -38,8 +38,11 @@ esp_err_t StreamHelpers::stream(httpd_req_t *req)
if (!fb)
{
ESP_LOGE(STREAM_SERVER_TAG, "Camera capture failed with resposne %s", esp_err_to_name(response));
ESP_LOGE(STREAM_SERVER_TAG, "Camera capture failed");
response = ESP_FAIL;
// Don't break immediately, try to recover
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
else
{
@@ -70,10 +73,15 @@ esp_err_t StreamHelpers::stream(httpd_req_t *req)
}
if (response != ESP_OK)
break;
long request_end = Helpers::getTimeInMillis();
long latency = (request_end - last_request_time);
last_request_time = request_end;
ESP_LOGI(STREAM_SERVER_TAG, "Size: %uKB, Time: %lims (%lifps)\n", _jpg_buf_len / 1024, latency, 1000 / latency);
// Only log every 100 frames to reduce overhead
static int frame_count = 0;
if (++frame_count % 100 == 0) {
long request_end = Helpers::getTimeInMillis();
long latency = (request_end - last_request_time);
last_request_time = request_end;
ESP_LOGI(STREAM_SERVER_TAG, "Size: %uKB, Time: %lims (%lifps)", _jpg_buf_len / 1024, latency, 1000 / latency);
}
}
last_frame = 0;
return response;
@@ -93,8 +101,9 @@ esp_err_t StreamServer::startStreamServer()
config.max_uri_handlers = 10;
config.server_port = STREAM_SERVER_PORT;
config.ctrl_port = STREAM_SERVER_PORT;
config.recv_wait_timeout = 100;
config.send_wait_timeout = 100;
config.recv_wait_timeout = 5; // 5 seconds for receiving
config.send_wait_timeout = 5; // 5 seconds for sending
config.lru_purge_enable = true; // Enable LRU purge for better connection handling
httpd_uri_t stream_page = {
.uri = "/",

View File

@@ -149,5 +149,12 @@ esp_err_t UVCStreamManager::setup()
}
ESP_LOGI(UVC_STREAM_TAG, "Initialized UVC Device");
return ESP_OK;
}
esp_err_t UVCStreamManager::start()
{
ESP_LOGI(UVC_STREAM_TAG, "Starting UVC streaming");
// UVC device is already initialized in setup(), just log that we're starting
return ESP_OK;
}

View File

@@ -54,6 +54,7 @@ class UVCStreamManager
public:
esp_err_t setup();
esp_err_t start();
};
#endif // UVCSTREAM_HPP

View File

@@ -1,4 +1,4 @@
idf_component_register(SRCS "wifiManager/wifiManager.cpp"
idf_component_register(SRCS "wifiManager/wifiManager.cpp" "wifiManager/WiFiScanner.cpp"
INCLUDE_DIRS "wifiManager"
REQUIRES esp_wifi nvs_flash esp_event esp_netif lwip StateManager ProjectConfig
)

View File

@@ -0,0 +1,205 @@
#include "WiFiScanner.hpp"
#include <cstring>
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) {
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
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];
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++) {
WiFiNetwork network;
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);
}
delete[] ap_records;
ESP_LOGI(TAG, "Found %d access points", ap_count);
}
}
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) {
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) {
// Normal all-channel scan
wifi_scan_config_t scan_config = {
.ssid = nullptr,
.bssid = nullptr,
.channel = 0, // 0 means scan all channels
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE, // Active scan
.scan_time = {
.active = {
.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
};
err = esp_wifi_scan_start(&scan_config, false);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start scan: %s", esp_err_to_name(err));
return scan_results;
}
} else {
// Sequential channel scan - scan each channel individually
std::vector<wifi_ap_record_t> all_records;
for (uint8_t ch = 1; ch <= 13; ch++) {
wifi_scan_config_t scan_config = {
.ssid = nullptr,
.bssid = nullptr,
.channel = ch, // Specific channel
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time = {
.active = {
.min = 100,
.max = 200
},
.passive = 300
},
.home_chan_dwell_time = 0,
.channel_bitmap = 0
};
err = esp_wifi_scan_start(&scan_config, true); // Blocking scan
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++) {
all_records.push_back(ch_records[i]);
}
}
delete[] ch_records;
}
}
vTaskDelay(pdMS_TO_TICKS(50));
}
// Process all collected records
for (const auto& record : all_records) {
WiFiNetwork network;
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) {
uint16_t temp_count = 0;
esp_err_t count_err = esp_wifi_scan_get_ap_num(&temp_count);
if (count_err == ESP_OK) {
// Wait a bit longer after finding networks to ensure scan is complete
if (temp_count > 0 && elapsed_ms > 5000) {
break;
}
}
vTaskDelay(pdMS_TO_TICKS(200));
elapsed_ms += 200;
}
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) {
ESP_LOGI(TAG, "No access points found");
return scan_results;
}
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) {
ESP_LOGE(TAG, "Failed to get scan records: %s", esp_err_to_name(err));
delete[] ap_records;
return scan_results;
}
// 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++) {
WiFiNetwork network;
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) {
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

@@ -0,0 +1,29 @@
#pragma once
#ifndef WIFI_SCANNER_HPP
#define WIFI_SCANNER_HPP
#include <vector>
#include <string>
#include "esp_wifi.h"
#include "esp_log.h"
struct WiFiNetwork {
std::string ssid;
uint8_t channel;
int8_t rssi;
uint8_t mac[6];
wifi_auth_mode_t auth_mode;
};
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);
private:
static constexpr char const* TAG = "WiFiScanner";
std::vector<WiFiNetwork> networks;
};
#endif

View File

@@ -2,6 +2,10 @@
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;
void WiFiManagerHelpers::event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
@@ -15,6 +19,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;
ESP_LOGI(WIFI_MANAGER_TAG, "Disconnect reason: %d", disconnected->reason);
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY)
{
esp_wifi_connect();
@@ -37,19 +44,70 @@ void WiFiManagerHelpers::event_handler(void *arg, esp_event_base_t event_base,
}
}
WiFiManager::WiFiManager(std::shared_ptr<ProjectConfig> deviceConfig, QueueHandle_t eventQueue, StateManager *stateManager) : deviceConfig(deviceConfig), eventQueue(eventQueue), stateManager(stateManager) {}
WiFiManager::WiFiManager(std::shared_ptr<ProjectConfig> deviceConfig, QueueHandle_t eventQueue, StateManager *stateManager)
: deviceConfig(deviceConfig), eventQueue(eventQueue), stateManager(stateManager), wifiScanner(std::make_unique<WiFiScanner>()) {}
void WiFiManager::SetCredentials(const char *ssid, const char *password)
{
memcpy(_wifi_cfg.sta.ssid, ssid, std::min(strlen(ssid), sizeof(_wifi_cfg.sta.ssid)));
memcpy(_wifi_cfg.sta.password, password, std::min(strlen(password), sizeof(_wifi_cfg.sta.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) {
_wifi_cfg.sta.threshold.authmode = WIFI_AUTH_OPEN;
} 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
// 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
// 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
// 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",
_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()
{
SystemEvent event = {EventSource::WIFI, WiFiState_e::WiFiState_ReadyToConnect};
this->SetCredentials(CONFIG_WIFI_SSID, CONFIG_WIFI_PASSWORD);
esp_wifi_stop();
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &_wifi_cfg));
xQueueSend(this->eventQueue, &event, 10);
@@ -103,16 +161,38 @@ void WiFiManager::ConnectWithStoredCredentials()
return;
}
// 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)
{
xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT);
// Reset retry counter for each network attempt
s_retry_num = 0;
xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT | WIFI_CONNECTED_BIT);
this->SetCredentials(network.ssid.c_str(), network.password.c_str());
// we need to update the config after every credential change
// 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);
esp_wifi_start();
// 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) {
ESP_LOGE(WIFI_MANAGER_TAG, "Failed to start WiFi: %s", esp_err_to_name(start_err));
continue;
}
event.value = WiFiState_e::WiFiState_Connecting;
xQueueSend(this->eventQueue, &event, 10);
@@ -121,19 +201,23 @@ void WiFiManager::ConnectWithStoredCredentials()
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
pdMS_TO_TICKS(30000)); // 30 second timeout instead of portMAX_DELAY
if (bits & WIFI_CONNECTED_BIT)
{
ESP_LOGI(WIFI_MANAGER_TAG, "connected to ap SSID:%p password:%p",
_wifi_cfg.sta.ssid, _wifi_cfg.sta.password);
ESP_LOGI(WIFI_MANAGER_TAG, "connected to ap SSID:%s",
network.ssid.c_str());
event.value = WiFiState_e::WiFiState_Connected;
xQueueSend(this->eventQueue, &event, 10);
return;
}
ESP_LOGE(WIFI_MANAGER_TAG, "Failed to connect to SSID:%p, password:%p, trying next stored network",
_wifi_cfg.sta.ssid, _wifi_cfg.sta.password);
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));
}
event.value = WiFiState_e::WiFiState_Error;
@@ -165,6 +249,109 @@ 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>();
}
// 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;
}
// If already in STA or APSTA mode, scan directly
return wifiScanner->scanNetworks();
}
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;
}
// 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();
}
void WiFiManager::Begin()
{
s_wifi_event_group = xEventGroupCreate();
@@ -176,6 +363,18 @@ 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,
@@ -188,8 +387,8 @@ void WiFiManager::Begin()
&instance_got_ip));
_wifi_cfg = {};
_wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WEP;
_wifi_cfg.sta.pmf_cfg.capable = true;
_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

@@ -7,17 +7,20 @@
#include <algorithm>
#include <StateManager.hpp>
#include <ProjectConfig.hpp>
#include "WiFiScanner.hpp"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define EXAMPLE_ESP_MAXIMUM_RETRY 3
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
static EventGroupHandle_t s_wifi_event_group;
extern int s_retry_num;
extern EventGroupHandle_t s_wifi_event_group;
namespace WiFiManagerHelpers
{
@@ -34,6 +37,7 @@ private:
StateManager *stateManager;
wifi_init_config_t _wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT();
wifi_config_t _wifi_cfg = {};
std::unique_ptr<WiFiScanner> wifiScanner;
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
@@ -48,6 +52,9 @@ private:
public:
WiFiManager(std::shared_ptr<ProjectConfig> deviceConfig, QueueHandle_t eventQueue, StateManager *stateManager);
void Begin();
std::vector<WiFiNetwork> ScanNetworks();
WiFiState_e GetCurrentWiFiState();
void TryConnectToStoredNetworks();
};
#endif