mirror of
https://github.com/EyeTrackVR/OpenIris.git
synced 2025-11-04 15:39:42 +08:00
Merge fe09dd0c62 into 5da262c8da
This commit is contained in:
commit
176a85a403
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
PythonExamples/__pycache__/mode_switch_test.cpython-311.pyc
|
||||
@ -1,10 +1,12 @@
|
||||
#include "CommandManager.hpp"
|
||||
#include "tasks/tasks.hpp"
|
||||
|
||||
|
||||
CommandManager::CommandManager(ProjectConfig* deviceConfig)
|
||||
: deviceConfig(deviceConfig) {}
|
||||
|
||||
const CommandType CommandManager::getCommandType(JsonVariant& command) {
|
||||
if (!command.containsKey("command"))
|
||||
if (!command["command"].is<const char*>())
|
||||
return CommandType::None;
|
||||
|
||||
if (auto search = commandMap.find(command["command"]);
|
||||
@ -15,11 +17,11 @@ const CommandType CommandManager::getCommandType(JsonVariant& command) {
|
||||
}
|
||||
|
||||
bool CommandManager::hasDataField(JsonVariant& command) {
|
||||
return command.containsKey("data");
|
||||
return command["data"].is<JsonObject>();
|
||||
}
|
||||
|
||||
void CommandManager::handleCommands(CommandsPayload commandsPayload) {
|
||||
if (!commandsPayload.data.containsKey("commands")) {
|
||||
if (!commandsPayload.data["commands"].is<JsonArray>()) {
|
||||
log_e("Json data sent not supported, lacks commands field");
|
||||
return;
|
||||
}
|
||||
@ -41,12 +43,12 @@ void CommandManager::handleCommand(JsonVariant command) {
|
||||
// malformed command, lacked data field
|
||||
break;
|
||||
|
||||
if (!command["data"].containsKey("ssid") ||
|
||||
!command["data"].containsKey("password"))
|
||||
if (!command["data"]["ssid"].is<const char*>() ||
|
||||
!command["data"]["password"].is<const char*>())
|
||||
break;
|
||||
|
||||
std::string customNetworkName = "main";
|
||||
if (command["data"].containsKey("network_name"))
|
||||
if (command["data"]["network_name"].is<const char*>())
|
||||
customNetworkName = command["data"]["network_name"].as<std::string>();
|
||||
|
||||
this->deviceConfig->setWifiConfig(customNetworkName,
|
||||
@ -56,13 +58,17 @@ void CommandManager::handleCommand(JsonVariant command) {
|
||||
0, // power, should this be zero?
|
||||
false, false);
|
||||
|
||||
this->deviceConfig->setHasWiFiCredentials(true, false);
|
||||
this->deviceConfig->setDeviceMode(DeviceMode::WIFI_MODE, true);
|
||||
log_i("[CommandManager] Switching to WiFi mode after receiving credentials");
|
||||
|
||||
break;
|
||||
}
|
||||
case CommandType::SET_MDNS: {
|
||||
if (!this->hasDataField(command))
|
||||
break;
|
||||
|
||||
if (!command["data"].containsKey("hostname") ||
|
||||
if (!command["data"]["hostname"].is<const char*>() ||
|
||||
!strlen(command["data"]["hostname"]))
|
||||
break;
|
||||
|
||||
@ -75,6 +81,55 @@ void CommandManager::handleCommand(JsonVariant command) {
|
||||
Serial.println("PONG \n\r");
|
||||
break;
|
||||
}
|
||||
case CommandType::SWITCH_MODE: {
|
||||
if (!this->hasDataField(command))
|
||||
break;
|
||||
|
||||
if (!command["data"]["mode"].is<int>())
|
||||
break;
|
||||
|
||||
int modeValue = command["data"]["mode"];
|
||||
DeviceMode newMode = static_cast<DeviceMode>(modeValue);
|
||||
DeviceMode currentMode = this->deviceConfig->getDeviceModeConfig().mode;
|
||||
|
||||
// If switching to USB mode from WiFi or AP mode, disconnect WiFi immediately
|
||||
if (newMode == DeviceMode::USB_MODE &&
|
||||
(currentMode == DeviceMode::WIFI_MODE || currentMode == DeviceMode::AP_MODE)) {
|
||||
log_i("[CommandManager] Immediately switching to USB mode");
|
||||
WiFi.disconnect(true);
|
||||
}
|
||||
|
||||
this->deviceConfig->setDeviceMode(newMode, true);
|
||||
log_i("[CommandManager] Switching to mode: %d", modeValue);
|
||||
|
||||
// Removed automatic restart to allow explicit control via RESTART_DEVICE command
|
||||
// if (!(newMode == DeviceMode::USB_MODE &&
|
||||
// (currentMode == DeviceMode::WIFI_MODE || currentMode == DeviceMode::AP_MODE) &&
|
||||
// wifiStateManager.getCurrentState() == WiFiState_e::WiFiState_Connecting)) {
|
||||
// OpenIrisTasks::ScheduleRestart(2000);
|
||||
// }
|
||||
|
||||
break;
|
||||
}
|
||||
case CommandType::WIPE_WIFI_CREDS: {
|
||||
|
||||
auto networks = this->deviceConfig->getWifiConfigs();
|
||||
for (auto& network : networks) {
|
||||
this->deviceConfig->deleteWifiConfig(network.name, false);
|
||||
}
|
||||
|
||||
this->deviceConfig->setHasWiFiCredentials(false, false);
|
||||
this->deviceConfig->setDeviceMode(DeviceMode::USB_MODE, true);
|
||||
log_i("[CommandManager] Switching to USB mode after wiping credentials");
|
||||
// Removed automatic restart to allow processing of all commands in payload
|
||||
|
||||
break;
|
||||
}
|
||||
case CommandType::RESTART_DEVICE: {
|
||||
log_i("[CommandManager] Explicit restart requested");
|
||||
OpenIrisTasks::ScheduleRestart(2000);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@ -10,6 +10,9 @@ enum CommandType {
|
||||
PING,
|
||||
SET_WIFI,
|
||||
SET_MDNS,
|
||||
SWITCH_MODE,
|
||||
WIPE_WIFI_CREDS,
|
||||
RESTART_DEVICE,
|
||||
};
|
||||
|
||||
struct CommandsPayload {
|
||||
@ -22,6 +25,9 @@ class CommandManager {
|
||||
{"ping", CommandType::PING},
|
||||
{"set_wifi", CommandType::SET_WIFI},
|
||||
{"set_mdns", CommandType::SET_MDNS},
|
||||
{"switch_mode", CommandType::SWITCH_MODE},
|
||||
{"wipe_wifi_creds", CommandType::WIPE_WIFI_CREDS},
|
||||
{"restart_device", CommandType::RESTART_DEVICE},
|
||||
};
|
||||
|
||||
ProjectConfig* deviceConfig;
|
||||
@ -33,6 +39,7 @@ class CommandManager {
|
||||
public:
|
||||
CommandManager(ProjectConfig* deviceConfig);
|
||||
void handleCommands(CommandsPayload commandsPayload);
|
||||
ProjectConfig* getDeviceConfig() { return deviceConfig; }
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -28,7 +28,8 @@ struct DeviceStates {
|
||||
networksConfigUpdated,
|
||||
apConfigUpdated,
|
||||
wifiTxPowerUpdated,
|
||||
cameraConfigUpdated
|
||||
cameraConfigUpdated,
|
||||
deviceModeUpdated
|
||||
};
|
||||
|
||||
enum WiFiState_e {
|
||||
|
||||
@ -58,6 +58,12 @@ void ProjectConfig::initConfig() {
|
||||
.quality = 7,
|
||||
.brightness = 2,
|
||||
};
|
||||
|
||||
// Initialize device mode with default values
|
||||
this->config.deviceMode = {
|
||||
.mode = DeviceMode::AUTO_MODE,
|
||||
.hasWiFiCredentials = false,
|
||||
};
|
||||
}
|
||||
|
||||
void ProjectConfig::save() {
|
||||
@ -67,11 +73,13 @@ void ProjectConfig::save() {
|
||||
cameraConfigSave();
|
||||
wifiConfigSave();
|
||||
wifiTxPowerConfigSave();
|
||||
end(); // we call end() here to close the connection to the NVS partition, we
|
||||
// only do this because we call ESP.restart() next.
|
||||
OpenIrisTasks::ScheduleRestart(2000);
|
||||
deviceModeConfigSave();
|
||||
end(); // we call end() here to close the connection to the NVS partition
|
||||
// Removed automatic restart to allow explicit control via RESTART_DEVICE command
|
||||
// OpenIrisTasks::ScheduleRestart(2000);
|
||||
}
|
||||
|
||||
|
||||
void ProjectConfig::wifiConfigSave() {
|
||||
log_d("Saving wifi config");
|
||||
|
||||
@ -126,6 +134,15 @@ void ProjectConfig::wifiTxPowerConfigSave() {
|
||||
putInt("txpower", this->config.txpower.power);
|
||||
}
|
||||
|
||||
void ProjectConfig::deviceModeConfigSave() {
|
||||
/* Device Mode Config */
|
||||
putInt(MODE_KEY, static_cast<int>(this->config.deviceMode.mode));
|
||||
putBool(HAS_WIFI_CREDS_KEY, this->config.deviceMode.hasWiFiCredentials);
|
||||
log_i("[ProjectConfig] Device mode config saved: mode=%d, hasWiFiCredentials=%d",
|
||||
static_cast<int>(this->config.deviceMode.mode),
|
||||
this->config.deviceMode.hasWiFiCredentials);
|
||||
}
|
||||
|
||||
void ProjectConfig::cameraConfigSave() {
|
||||
/* Camera Config */
|
||||
putInt("vflip", this->config.camera.vflip);
|
||||
@ -205,6 +222,18 @@ void ProjectConfig::load() {
|
||||
this->config.camera.quality = getInt("quality", 7);
|
||||
this->config.camera.brightness = getInt("brightness", 2);
|
||||
|
||||
int savedMode = getInt(MODE_KEY, static_cast<int>(DeviceMode::AUTO_MODE));
|
||||
this->config.deviceMode.mode = static_cast<DeviceMode>(savedMode);
|
||||
this->config.deviceMode.hasWiFiCredentials = getBool(HAS_WIFI_CREDS_KEY, false);
|
||||
|
||||
if (this->config.deviceMode.mode == DeviceMode::AUTO_MODE) {
|
||||
this->config.deviceMode.mode = determineMode();
|
||||
}
|
||||
|
||||
log_i("[ProjectConfig] Loaded device mode: %d, hasWiFiCredentials: %d",
|
||||
static_cast<int>(this->config.deviceMode.mode),
|
||||
this->config.deviceMode.hasWiFiCredentials);
|
||||
|
||||
this->_already_loaded = true;
|
||||
this->notifyAll(ConfigState_e::configLoaded);
|
||||
}
|
||||
@ -419,6 +448,14 @@ std::string ProjectConfig::WiFiTxPower_t::toRepresentation() {
|
||||
return json;
|
||||
}
|
||||
|
||||
std::string ProjectConfig::DeviceModeConfig_t::toRepresentation() {
|
||||
std::string json = Helpers::format_string(
|
||||
"\"device_mode\": {\"mode\": %d, \"hasWiFiCredentials\": %s}",
|
||||
static_cast<int>(this->mode),
|
||||
this->hasWiFiCredentials ? "true" : "false");
|
||||
return json;
|
||||
}
|
||||
|
||||
//**********************************************************************************************************************
|
||||
//*
|
||||
//! Get Methods
|
||||
@ -443,3 +480,32 @@ ProjectConfig::MDNSConfig_t& ProjectConfig::getMDNSConfig() {
|
||||
ProjectConfig::WiFiTxPower_t& ProjectConfig::getWiFiTxPowerConfig() {
|
||||
return this->config.txpower;
|
||||
}
|
||||
|
||||
ProjectConfig::DeviceModeConfig_t& ProjectConfig::getDeviceModeConfig() {
|
||||
return this->config.deviceMode;
|
||||
}
|
||||
|
||||
void ProjectConfig::setDeviceMode(DeviceMode mode, bool shouldNotify) {
|
||||
this->config.deviceMode.mode = mode;
|
||||
putInt(MODE_KEY, static_cast<int>(mode));
|
||||
log_i("[ProjectConfig] Mode set to: %d", static_cast<int>(mode));
|
||||
|
||||
if (shouldNotify) {
|
||||
this->notifyAll(ConfigState_e::deviceModeUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectConfig::setHasWiFiCredentials(bool hasCredentials, bool shouldNotify) {
|
||||
this->config.deviceMode.hasWiFiCredentials = hasCredentials;
|
||||
putBool(HAS_WIFI_CREDS_KEY, hasCredentials);
|
||||
log_i("[ProjectConfig] WiFi credentials status set to: %d", hasCredentials);
|
||||
|
||||
if (shouldNotify) {
|
||||
this->notifyAll(ConfigState_e::deviceModeUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
DeviceMode ProjectConfig::determineMode() {
|
||||
// If WiFi credentials are saved, use WiFi mode, otherwise use AP mode
|
||||
return this->config.deviceMode.hasWiFiCredentials ? DeviceMode::WIFI_MODE : DeviceMode::AP_MODE;
|
||||
}
|
||||
|
||||
@ -13,6 +13,14 @@
|
||||
#include "data/utilities/network_utilities.hpp"
|
||||
#include "tasks/tasks.hpp"
|
||||
|
||||
// Enum to represent the device operating mode
|
||||
enum class DeviceMode {
|
||||
USB_MODE, // Device operates in USB mode only
|
||||
WIFI_MODE, // Device operates in WiFi mode only
|
||||
AP_MODE, // Device operates in AP mode with serial commands enabled
|
||||
AUTO_MODE // Device automatically selects mode based on saved credentials
|
||||
};
|
||||
|
||||
class ProjectConfig : public Preferences, public ISubject<ConfigState_e> {
|
||||
public:
|
||||
ProjectConfig(const std::string& name = std::string(),
|
||||
@ -88,6 +96,12 @@ class ProjectConfig : public Preferences, public ISubject<ConfigState_e> {
|
||||
std::string toRepresentation();
|
||||
};
|
||||
|
||||
struct DeviceModeConfig_t {
|
||||
DeviceMode mode;
|
||||
bool hasWiFiCredentials;
|
||||
std::string toRepresentation();
|
||||
};
|
||||
|
||||
struct TrackerConfig_t {
|
||||
DeviceConfig_t device;
|
||||
CameraConfig_t camera;
|
||||
@ -95,6 +109,7 @@ class ProjectConfig : public Preferences, public ISubject<ConfigState_e> {
|
||||
AP_WiFiConfig_t ap_network;
|
||||
MDNSConfig_t mdns;
|
||||
WiFiTxPower_t txpower;
|
||||
DeviceModeConfig_t deviceMode;
|
||||
};
|
||||
|
||||
DeviceConfig_t& getDeviceConfig();
|
||||
@ -103,6 +118,7 @@ class ProjectConfig : public Preferences, public ISubject<ConfigState_e> {
|
||||
AP_WiFiConfig_t& getAPWifiConfig();
|
||||
MDNSConfig_t& getMDNSConfig();
|
||||
WiFiTxPower_t& getWiFiTxPowerConfig();
|
||||
DeviceModeConfig_t& getDeviceModeConfig();
|
||||
|
||||
void setDeviceConfig(const std::string& OTALogin,
|
||||
const std::string& OTAPassword,
|
||||
@ -133,11 +149,23 @@ class ProjectConfig : public Preferences, public ISubject<ConfigState_e> {
|
||||
|
||||
void deleteWifiConfig(const std::string& networkName, bool shouldNotify);
|
||||
|
||||
void setDeviceMode(DeviceMode mode, bool shouldNotify);
|
||||
|
||||
void setHasWiFiCredentials(bool hasCredentials, bool shouldNotify);
|
||||
|
||||
DeviceMode determineMode();
|
||||
|
||||
void deviceModeConfigSave();
|
||||
|
||||
private:
|
||||
TrackerConfig_t config;
|
||||
std::string _name;
|
||||
std::string _mdnsName;
|
||||
bool _already_loaded;
|
||||
|
||||
// Device mode related constants
|
||||
const char* MODE_KEY = "mode";
|
||||
const char* HAS_WIFI_CREDS_KEY = "has_wifi_creds";
|
||||
};
|
||||
|
||||
#endif // PROJECT_CONFIG_HPP
|
||||
|
||||
@ -3,7 +3,30 @@
|
||||
SerialManager::SerialManager(CommandManager* commandManager)
|
||||
: commandManager(commandManager) {}
|
||||
|
||||
#ifdef ETVR_EYE_TRACKER_USB_API
|
||||
void SerialManager::sendQuery(QueryAction action,
|
||||
QueryStatus status,
|
||||
std::string additional_info) {
|
||||
JsonDocument doc;
|
||||
doc["action"] = queryActionMap.at(action);
|
||||
doc["status"] = static_cast<int>(status);
|
||||
if (!additional_info.empty()) {
|
||||
doc["info"] = additional_info;
|
||||
}
|
||||
|
||||
String output;
|
||||
serializeJson(doc, output);
|
||||
Serial.println(output);
|
||||
}
|
||||
|
||||
void SerialManager::checkUSBMode() {
|
||||
// Get device mode from ProjectConfig via CommandManager
|
||||
DeviceMode currentMode = this->commandManager->getDeviceConfig()->getDeviceModeConfig().mode;
|
||||
if (currentMode == DeviceMode::USB_MODE) {
|
||||
log_i("[SerialManager] USB mode active - auto-streaming enabled");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void SerialManager::send_frame() {
|
||||
if (!last_frame)
|
||||
last_frame = esp_timer_get_time();
|
||||
@ -49,7 +72,6 @@ void SerialManager::send_frame() {
|
||||
log_d("Size: %uKB, Time: %ums (%ifps)\n", len / 1024, latency,
|
||||
1000 / latency);
|
||||
}
|
||||
#endif
|
||||
|
||||
void SerialManager::init() {
|
||||
#ifdef SERIAL_MANAGER_USE_HIGHER_FREQUENCY
|
||||
@ -58,25 +80,27 @@ void SerialManager::init() {
|
||||
if (SERIAL_FLUSH_ENABLED) {
|
||||
Serial.flush();
|
||||
}
|
||||
|
||||
// Check if we're in USB mode and set up accordingly
|
||||
checkUSBMode();
|
||||
}
|
||||
|
||||
void SerialManager::run() {
|
||||
// Process any available commands first to ensure mode changes are detected immediately
|
||||
if (Serial.available()) {
|
||||
JsonDocument doc;
|
||||
DeserializationError deserializationError = deserializeJson(doc, Serial);
|
||||
|
||||
if (deserializationError) {
|
||||
log_e("Command deserialization failed: %s", deserializationError.c_str());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
CommandsPayload commands = {doc};
|
||||
this->commandManager->handleCommands(commands);
|
||||
}
|
||||
#ifdef ETVR_EYE_TRACKER_USB_API
|
||||
else {
|
||||
}
|
||||
|
||||
DeviceMode currentMode = this->commandManager->getDeviceConfig()->getDeviceModeConfig().mode;
|
||||
if (currentMode == DeviceMode::USB_MODE) {
|
||||
this->send_frame();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -35,12 +35,10 @@ class SerialManager {
|
||||
esp_err_t err = ESP_OK;
|
||||
CommandManager* commandManager;
|
||||
|
||||
#ifdef ETVR_EYE_TRACKER_USB_API
|
||||
int64_t last_frame = 0;
|
||||
long last_request_time = 0;
|
||||
|
||||
void send_frame();
|
||||
#endif
|
||||
|
||||
public:
|
||||
SerialManager(CommandManager* commandManager);
|
||||
@ -49,6 +47,7 @@ class SerialManager {
|
||||
std::string additional_info);
|
||||
void init();
|
||||
void run();
|
||||
void checkUSBMode();
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -36,6 +36,8 @@ void BaseAPI::begin() {
|
||||
|
||||
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
|
||||
|
||||
// The restart_device endpoint has been removed in favor of using rebootDevice through POST
|
||||
|
||||
// std::bind(&BaseAPI::notFound, &std::placeholders::_1);
|
||||
server.onNotFound([&](AsyncWebServerRequest* request) { notFound(request); });
|
||||
}
|
||||
@ -214,7 +216,15 @@ void BaseAPI::rebootDevice(AsyncWebServerRequest* request) {
|
||||
switch (_networkMethodsMap_enum[request->method()]) {
|
||||
case GET: {
|
||||
request->send(200, MIMETYPE_JSON, "{\"msg\":\"Rebooting Device\"}");
|
||||
|
||||
OpenIrisTasks::ScheduleRestart(2000);
|
||||
break;
|
||||
}
|
||||
case POST: {
|
||||
request->send(200, MIMETYPE_JSON, "{\"msg\":\"Rebooting Device\"}");
|
||||
|
||||
OpenIrisTasks::ScheduleRestart(2000);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Request\"}");
|
||||
@ -381,7 +391,7 @@ void BaseAPI::beginOTA() {
|
||||
esp_camera_deinit(); // deinitialize the camera driver
|
||||
digitalWrite(PWDN_GPIO_NUM, HIGH); // turn power off to camera module
|
||||
|
||||
AsyncWebServerResponse* response = request->beginResponse_P(
|
||||
AsyncWebServerResponse* response = request->beginResponse(
|
||||
200, "text/html", ELEGANT_HTML, ELEGANT_HTML_SIZE);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
request->send(response);
|
||||
|
||||
@ -169,6 +169,19 @@ bool WiFiHandler::iniSTA(const std::string& ssid,
|
||||
currentMillis = millis();
|
||||
log_i(".");
|
||||
log_d("Progress: %d \n\r", progress);
|
||||
|
||||
// Check if mode has been changed to USB mode during connection attempt
|
||||
if (configManager.getDeviceModeConfig().mode == DeviceMode::USB_MODE) {
|
||||
log_i("[WiFiHandler] Mode changed to USB during connection, aborting WiFi setup");
|
||||
WiFi.disconnect(true);
|
||||
wifiStateManager.setState(WiFiState_e::WiFiState_Disconnected);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Serial.available()) {
|
||||
yield(); // Allow other processes to run
|
||||
}
|
||||
|
||||
if ((currentMillis - startingMillis) >= connectionTimeout) {
|
||||
wifiStateManager.setState(WiFiState_e::WiFiState_Error);
|
||||
log_e("Connection to: %s TIMEOUT \n\r", ssid.c_str());
|
||||
|
||||
@ -38,10 +38,33 @@ StreamServer streamServer;
|
||||
#endif // SIM_ENABLED
|
||||
|
||||
void etvr_eye_tracker_web_init() {
|
||||
// Check if mode has been changed to USB mode before starting network initialization
|
||||
if (deviceConfig.getDeviceModeConfig().mode == DeviceMode::USB_MODE) {
|
||||
log_i("[SETUP]: Mode changed to USB before network initialization, aborting");
|
||||
WiFi.disconnect(true);
|
||||
return;
|
||||
}
|
||||
|
||||
log_d("[SETUP]: Starting Network Handler");
|
||||
deviceConfig.attach(mdnsHandler);
|
||||
|
||||
// Check mode again before starting WiFi
|
||||
if (deviceConfig.getDeviceModeConfig().mode == DeviceMode::USB_MODE) {
|
||||
log_i("[SETUP]: Mode changed to USB before WiFi initialization, aborting");
|
||||
WiFi.disconnect(true);
|
||||
return;
|
||||
}
|
||||
|
||||
log_d("[SETUP]: Starting WiFi Handler");
|
||||
wifiHandler.begin();
|
||||
|
||||
// Check mode again before starting MDNS
|
||||
if (deviceConfig.getDeviceModeConfig().mode == DeviceMode::USB_MODE) {
|
||||
log_i("[SETUP]: Mode changed to USB before MDNS initialization, aborting");
|
||||
WiFi.disconnect(true);
|
||||
return;
|
||||
}
|
||||
|
||||
log_d("[SETUP]: Starting MDNS Handler");
|
||||
mdnsHandler.startMDNS();
|
||||
|
||||
@ -86,6 +109,8 @@ void setup() {
|
||||
Logo::printASCII();
|
||||
ledManager.begin();
|
||||
|
||||
// Device mode is now managed by ProjectConfig
|
||||
|
||||
#ifdef CONFIG_CAMERA_MODULE_SWROOM_BABBLE_S3 // Set IR emitter strength to 100%.
|
||||
const int ledPin = 1; // Replace this with a command endpoint eventually.
|
||||
const int freq = 5000;
|
||||
@ -104,11 +129,20 @@ void setup() {
|
||||
|
||||
serialManager.init();
|
||||
|
||||
#ifndef ETVR_EYE_TRACKER_USB_API
|
||||
DeviceMode currentMode = deviceConfig.getDeviceModeConfig().mode;
|
||||
|
||||
if (currentMode == DeviceMode::WIFI_MODE) {
|
||||
// Initialize WiFi mode
|
||||
etvr_eye_tracker_web_init();
|
||||
#else // ETVR_EYE_TRACKER_WEB_API
|
||||
log_i("[SETUP]: Initialized in WiFi mode");
|
||||
} else if (currentMode == DeviceMode::AP_MODE) {
|
||||
// Initialize AP mode with serial commands enabled
|
||||
etvr_eye_tracker_web_init();
|
||||
log_i("[SETUP]: Initialized in AP mode with serial commands enabled");
|
||||
} else {
|
||||
WiFi.disconnect(true);
|
||||
#endif // ETVR_EYE_TRACKER_WEB_API
|
||||
log_i("[SETUP]: Initialized in USB mode");
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
368
PythonExamples/mode_switch_test.py
Normal file
368
PythonExamples/mode_switch_test.py
Normal file
@ -0,0 +1,368 @@
|
||||
#!/usr/bin/env python3
|
||||
import serial
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import argparse
|
||||
import socket
|
||||
|
||||
class OpenIrisModeTester:
|
||||
def __init__(self, port, baudrate=115200, timeout=5):
|
||||
self.port = port
|
||||
self.baudrate = baudrate
|
||||
self.timeout = timeout
|
||||
self.serial_conn = None
|
||||
self.device_ip = None
|
||||
self.device_port = None
|
||||
self.last_boot_time = None
|
||||
|
||||
def connect_serial(self):
|
||||
try:
|
||||
self.serial_conn = serial.Serial(self.port, self.baudrate, timeout=self.timeout)
|
||||
self.flush_serial_logs()
|
||||
print(f"Connected to {self.port} at {self.baudrate} baud")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error connecting to serial port: {e}")
|
||||
return False
|
||||
|
||||
def flush_serial_logs(self):
|
||||
if self.serial_conn and self.serial_conn.is_open:
|
||||
self.serial_conn.reset_input_buffer()
|
||||
self.serial_conn.reset_output_buffer()
|
||||
print("Serial logs flushed.")
|
||||
|
||||
def send_command(self, command_obj):
|
||||
if not self.serial_conn:
|
||||
print("Serial connection not established")
|
||||
return False
|
||||
|
||||
try:
|
||||
command_json = json.dumps(command_obj)
|
||||
self.serial_conn.write(command_json.encode() + b'\n')
|
||||
time.sleep(0.5)
|
||||
|
||||
response = ""
|
||||
start_time = time.time()
|
||||
while (time.time() - start_time) < self.timeout:
|
||||
if self.serial_conn.in_waiting:
|
||||
line = self.serial_conn.readline().decode('utf-8', errors='replace').strip()
|
||||
if line:
|
||||
response += line
|
||||
try:
|
||||
json.loads(response)
|
||||
return response
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
|
||||
return response if response else None
|
||||
except Exception as e:
|
||||
print(f"Error sending command: {e}")
|
||||
return None
|
||||
|
||||
def set_wifi_credentials(self, ssid, password):
|
||||
command = {"commands": [{"command": "set_wifi", "data": {"ssid": ssid, "password": password, "network_name": "main"}}]}
|
||||
print("Sending WiFi credentials...")
|
||||
response = self.send_command(command)
|
||||
print(f"Response: {response}")
|
||||
return response
|
||||
|
||||
def wipe_wifi_credentials(self):
|
||||
command = {"commands": [{"command": "wipe_wifi_creds"}]}
|
||||
print("Wiping WiFi credentials...")
|
||||
response = self.send_command(command)
|
||||
print(f"Response: {response}")
|
||||
return response
|
||||
|
||||
def discover_device(self, hostname="openiristracker.local"):
|
||||
try:
|
||||
self.device_ip = socket.gethostbyname(hostname)
|
||||
self.device_port = 80
|
||||
print(f"Device found at {self.device_ip}:{self.device_port}")
|
||||
return True
|
||||
except socket.error as e:
|
||||
print(f"Hostname resolution failed: {e}")
|
||||
return False
|
||||
|
||||
def test_wifi_api(self):
|
||||
if not self.device_ip:
|
||||
print("Device IP not available. Discover device first.")
|
||||
return False
|
||||
|
||||
try:
|
||||
url = f"http://{self.device_ip}:81/control/builtin/command/ping"
|
||||
response = requests.get(url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("WiFi API test successful!")
|
||||
return True
|
||||
else:
|
||||
print(f"WiFi API test failed with status code: {response.status_code}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error testing WiFi API: {e}")
|
||||
return False
|
||||
|
||||
def restart_device_serial(self):
|
||||
"""Send reboot command via serial connection"""
|
||||
command = {"commands": [{"command": "restart_device"}]}
|
||||
print("Sending reboot command via serial...")
|
||||
response = self.send_command(command)
|
||||
print(f"Response: {response}")
|
||||
self.last_boot_time = time.time()
|
||||
|
||||
# Close the serial connection after sending reboot command
|
||||
# This helps prevent permission issues when reconnecting
|
||||
if self.serial_conn and self.serial_conn.is_open:
|
||||
try:
|
||||
self.serial_conn.close()
|
||||
print("Serial connection closed after reboot command")
|
||||
except Exception as e:
|
||||
print(f"Error closing serial connection: {e}")
|
||||
|
||||
return response
|
||||
|
||||
def restart_device_wifi(self):
|
||||
"""Send reboot command via WiFi API"""
|
||||
if not self.device_ip:
|
||||
print("Device IP not available. Cannot restart via WiFi.")
|
||||
return False
|
||||
|
||||
try:
|
||||
# First verify the device is responsive via WiFi
|
||||
ping_url = f"http://{self.device_ip}:81/control/builtin/command/ping"
|
||||
print(f"Testing device responsiveness with: {ping_url}")
|
||||
ping_response = requests.get(ping_url, timeout=5)
|
||||
if ping_response.status_code != 200:
|
||||
print(f"Device not responsive via WiFi before restart attempt: {ping_response.status_code}")
|
||||
return False
|
||||
|
||||
# Use rebootDevice endpoint with POST method
|
||||
url = f"http://{self.device_ip}:81/update/rebootDevice"
|
||||
print(f"Sending reboot command to: {url}")
|
||||
|
||||
# Use POST method as recommended
|
||||
try:
|
||||
response = requests.post(url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("Reboot command via WiFi (POST) sent successfully!")
|
||||
self.last_boot_time = time.time()
|
||||
return True
|
||||
else:
|
||||
print(f"Reboot command via WiFi (POST) failed with status code: {response.status_code}")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("Connection error during POST: Device may have already started restarting")
|
||||
self.last_boot_time = time.time()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error sending POST reboot command: {e}")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("Connection error: Device may have already started restarting")
|
||||
self.last_boot_time = time.time()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error sending reboot command via WiFi: {e}")
|
||||
return False
|
||||
|
||||
def verify_restart(self, wait_time=60, retry_count=3):
|
||||
"""Verify that the device has rebooted by checking uptime"""
|
||||
if not self.last_boot_time:
|
||||
print("No reboot command was previously sent")
|
||||
return False
|
||||
|
||||
# Calculate elapsed time since reboot command
|
||||
elapsed = time.time() - self.last_boot_time
|
||||
if elapsed < wait_time:
|
||||
# Wait remaining time if needed
|
||||
remaining = wait_time - elapsed
|
||||
print(f"Waiting {remaining:.1f} more seconds for device to reboot...")
|
||||
time.sleep(remaining)
|
||||
else:
|
||||
print(f"Already waited {elapsed:.1f} seconds since reboot command")
|
||||
|
||||
# Close the serial connection before attempting to reconnect
|
||||
if self.serial_conn and self.serial_conn.is_open:
|
||||
try:
|
||||
self.serial_conn.close()
|
||||
time.sleep(1) # Give the OS time to release the port
|
||||
except Exception as e:
|
||||
print(f"Error closing serial connection: {e}")
|
||||
|
||||
# Try to reconnect serial with retries
|
||||
for attempt in range(retry_count):
|
||||
try:
|
||||
if self.connect_serial():
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Error during reconnection attempt {attempt+1}: {e}")
|
||||
|
||||
if attempt < retry_count - 1:
|
||||
print(f"Reconnection attempt {attempt+1} failed, retrying in 2 seconds...")
|
||||
time.sleep(2)
|
||||
else:
|
||||
print(f"Failed to reconnect to device after {retry_count} attempts")
|
||||
return False
|
||||
|
||||
# Send ping command to check if device is responsive
|
||||
for attempt in range(retry_count):
|
||||
ping_command = {"commands": [{"command": "ping"}]}
|
||||
response = self.send_command(ping_command)
|
||||
|
||||
if response and "pong" in response.lower():
|
||||
print(f"Device responded after restart (attempt {attempt+1})")
|
||||
return True
|
||||
elif attempt < retry_count - 1:
|
||||
print(f"Ping attempt {attempt+1} failed, retrying in 2 seconds...")
|
||||
time.sleep(2)
|
||||
|
||||
print(f"Device did not respond properly after {retry_count} ping attempts")
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
if self.serial_conn and self.serial_conn.is_open:
|
||||
self.serial_conn.close()
|
||||
|
||||
|
||||
def run_test(port, ssid, password, wait_time=15, retry_count=3, skip_wifi_test=False):
|
||||
tester = OpenIrisModeTester(port)
|
||||
test_results = {
|
||||
"serial_connection": False,
|
||||
"wifi_credentials": False,
|
||||
"device_discovery": False,
|
||||
"wifi_api": False,
|
||||
"serial_restart": False,
|
||||
"wifi_restart": False,
|
||||
"usb_mode": False
|
||||
}
|
||||
|
||||
try:
|
||||
print("\n=== Step 1: Testing Serial Connection ===")
|
||||
if not tester.connect_serial():
|
||||
print("❌ Serial connection failed. Aborting test.")
|
||||
return test_results
|
||||
test_results["serial_connection"] = True
|
||||
print("✅ Serial connection successful")
|
||||
|
||||
print("\n=== Step 2: Setting WiFi credentials ===")
|
||||
response = tester.set_wifi_credentials(ssid, password)
|
||||
if response and "error" not in response.lower():
|
||||
test_results["wifi_credentials"] = True
|
||||
print("✅ WiFi credentials set successfully")
|
||||
|
||||
# Explicitly send reboot command after setting WiFi credentials
|
||||
print("Sending reboot command after setting WiFi credentials...")
|
||||
tester.restart_device_serial()
|
||||
|
||||
# Wait specifically 30 seconds for the device to restart
|
||||
print("Waiting 30 seconds for device to restart...")
|
||||
time.sleep(30)
|
||||
else:
|
||||
print("❌ Failed to set WiFi credentials")
|
||||
|
||||
# Verify device reboots after setting WiFi credentials
|
||||
if not tester.verify_restart(wait_time=wait_time, retry_count=retry_count):
|
||||
print("❌ Device did not reboot properly after setting WiFi credentials")
|
||||
else:
|
||||
print("✅ Device rebooted successfully after setting WiFi credentials")
|
||||
|
||||
print("\n=== Step 3: Discovering device on network ===")
|
||||
if tester.discover_device():
|
||||
test_results["device_discovery"] = True
|
||||
print("✅ Device discovered on network")
|
||||
|
||||
print("\n=== Step 4: Testing WiFi API ===")
|
||||
if tester.test_wifi_api():
|
||||
test_results["wifi_api"] = True
|
||||
print("✅ WiFi API test successful")
|
||||
|
||||
print("\n=== Step 5: Testing reboot via WiFi ===")
|
||||
if tester.restart_device_wifi():
|
||||
# Wait for device to reboot
|
||||
if tester.verify_restart(wait_time=wait_time, retry_count=retry_count):
|
||||
test_results["wifi_restart"] = True
|
||||
print("✅ Device rebooted successfully via WiFi")
|
||||
else:
|
||||
print("❌ Device did not reboot properly via WiFi")
|
||||
else:
|
||||
print("❌ Failed to send reboot command via WiFi")
|
||||
else:
|
||||
print("❌ WiFi API test failed")
|
||||
else:
|
||||
print("❌ Failed to discover device on network")
|
||||
|
||||
# Reconnect serial if needed
|
||||
if not tester.serial_conn or not tester.serial_conn.is_open:
|
||||
tester.connect_serial()
|
||||
tester.flush_serial_logs()
|
||||
|
||||
print("\n=== Step 6: Testing reboot via Serial ===")
|
||||
tester.restart_device_serial()
|
||||
if tester.verify_restart(wait_time=wait_time, retry_count=retry_count):
|
||||
test_results["serial_restart"] = True
|
||||
print("✅ Device rebooted successfully via Serial")
|
||||
else:
|
||||
print("❌ Device did not reboot properly via Serial")
|
||||
|
||||
print("\n=== Step 7: Wiping WiFi credentials ===")
|
||||
tester.wipe_wifi_credentials()
|
||||
if not tester.verify_restart(wait_time=wait_time, retry_count=retry_count):
|
||||
print("❌ Device did not reboot properly after wiping WiFi credentials")
|
||||
else:
|
||||
print("✅ Device rebooted successfully after wiping WiFi credentials")
|
||||
|
||||
print("\n=== Step 8: Verifying USB mode ===")
|
||||
ping_command = {"commands": [{"command": "ping"}]}
|
||||
response = tester.send_command(ping_command)
|
||||
if response and "pong" in response.lower():
|
||||
test_results["usb_mode"] = True
|
||||
print("✅ Device is in USB mode")
|
||||
else:
|
||||
print("❌ Failed to verify USB mode")
|
||||
|
||||
tester.flush_serial_logs()
|
||||
|
||||
# Print summary
|
||||
print("\n=== Test Summary ===")
|
||||
for test, result in test_results.items():
|
||||
status = "✅ PASS" if result else "❌ FAIL"
|
||||
print(f"{test.replace('_', ' ').title()}: {status}")
|
||||
|
||||
return test_results
|
||||
|
||||
finally:
|
||||
tester.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Test OpenIris mode switching and reboot functionality")
|
||||
parser.add_argument("--port", required=True, help="Serial port (e.g., COM3 or /dev/ttyUSB0)")
|
||||
parser.add_argument("--ssid", required=True, help="WiFi SSID")
|
||||
parser.add_argument("--password", required=True, help="WiFi password")
|
||||
parser.add_argument("--wait-time", type=int, default=60, help="Wait time in seconds for device reboot (default: 15)")
|
||||
parser.add_argument("--retry-count", type=int, default=3, help="Number of retry attempts for reconnection (default: 3)")
|
||||
parser.add_argument("--skip-wifi-test", action="store_true", help="Skip WiFi-related tests")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Configure test parameters
|
||||
test_config = {
|
||||
"port": args.port,
|
||||
"ssid": args.ssid,
|
||||
"password": args.password,
|
||||
"wait_time": args.wait_time,
|
||||
"retry_count": args.retry_count,
|
||||
"skip_wifi_test": args.skip_wifi_test
|
||||
}
|
||||
|
||||
# Run the test and get results
|
||||
results = run_test(**test_config)
|
||||
|
||||
# Exit with appropriate status code
|
||||
if all(results.values()):
|
||||
print("\n✅ All tests passed successfully!")
|
||||
exit(0)
|
||||
else:
|
||||
print("\n❌ Some tests failed. See details above.")
|
||||
exit(1)
|
||||
Loading…
Reference in New Issue
Block a user