Compare commits

...

2 Commits

Author SHA1 Message Date
Summer
176a85a403
Merge fe09dd0c62 into 5da262c8da 2025-04-11 20:42:43 +00:00
Summer
fe09dd0c62 refactor(DeviceMode): migrate device mode management to ProjectConfig
Removed DeviceModeManager and integrated device mode handling into ProjectConfig for better maintainability. Added RESTART_DEVICE command for explicit device reboots and updated related tests to verify reboot functionality. Simplified mode checks across the codebase by using ProjectConfig directly.
2025-04-11 11:49:19 -07:00
12 changed files with 387 additions and 200 deletions

View File

@ -1,5 +1,4 @@
#include "CommandManager.hpp"
#include "data/DeviceMode/DeviceMode.hpp"
#include "tasks/tasks.hpp"
@ -59,15 +58,9 @@ void CommandManager::handleCommand(JsonVariant command) {
0, // power, should this be zero?
false, false);
DeviceModeManager* deviceModeManager = DeviceModeManager::getInstance();
if (deviceModeManager) {
deviceModeManager->setHasWiFiCredentials(true);
deviceModeManager->setMode(DeviceMode::WIFI_MODE);
log_i("[CommandManager] Switching to WiFi mode after receiving credentials");
OpenIrisTasks::ScheduleRestart(2000);
}
this->deviceConfig->setHasWiFiCredentials(true, false);
this->deviceConfig->setDeviceMode(DeviceMode::WIFI_MODE, true);
log_i("[CommandManager] Switching to WiFi mode after receiving credentials");
break;
}
@ -97,30 +90,25 @@ void CommandManager::handleCommand(JsonVariant command) {
int modeValue = command["data"]["mode"];
DeviceMode newMode = static_cast<DeviceMode>(modeValue);
DeviceMode currentMode;
DeviceMode currentMode = this->deviceConfig->getDeviceModeConfig().mode;
DeviceModeManager* deviceModeManager = DeviceModeManager::getInstance();
if (deviceModeManager) {
currentMode = deviceModeManager->getMode();
// 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);
}
deviceModeManager->setMode(newMode);
log_i("[CommandManager] Switching to mode: %d", modeValue);
// Only schedule a restart if not switching to USB mode during WiFi/AP initialization
if (!(newMode == DeviceMode::USB_MODE &&
(currentMode == DeviceMode::WIFI_MODE || currentMode == DeviceMode::AP_MODE) &&
wifiStateManager.getCurrentState() == WiFiState_e::WiFiState_Connecting)) {
OpenIrisTasks::ScheduleRestart(2000);
}
// 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: {
@ -130,18 +118,18 @@ void CommandManager::handleCommand(JsonVariant command) {
this->deviceConfig->deleteWifiConfig(network.name, false);
}
DeviceModeManager* deviceModeManager = DeviceModeManager::getInstance();
if (deviceModeManager) {
deviceModeManager->setHasWiFiCredentials(false);
deviceModeManager->setMode(DeviceMode::USB_MODE);
log_i("[CommandManager] Switching to USB mode after wiping credentials");
OpenIrisTasks::ScheduleRestart(2000);
}
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;
}

View File

@ -12,6 +12,7 @@ enum CommandType {
SET_MDNS,
SWITCH_MODE,
WIPE_WIFI_CREDS,
RESTART_DEVICE,
};
struct CommandsPayload {
@ -26,6 +27,7 @@ class CommandManager {
{"set_mdns", CommandType::SET_MDNS},
{"switch_mode", CommandType::SWITCH_MODE},
{"wipe_wifi_creds", CommandType::WIPE_WIFI_CREDS},
{"restart_device", CommandType::RESTART_DEVICE},
};
ProjectConfig* deviceConfig;
@ -37,6 +39,7 @@ class CommandManager {
public:
CommandManager(ProjectConfig* deviceConfig);
void handleCommands(CommandsPayload commandsPayload);
ProjectConfig* getDeviceConfig() { return deviceConfig; }
};
#endif

View File

@ -1,62 +0,0 @@
#include "DeviceMode.hpp"
DeviceModeManager* DeviceModeManager::instance = nullptr;
DeviceModeManager::DeviceModeManager() : currentMode(DeviceMode::USB_MODE) {}
DeviceModeManager::~DeviceModeManager() {
preferences.end();
}
void DeviceModeManager::init() {
preferences.begin(PREF_NAMESPACE, false);
// Load the saved mode or use default (USB_MODE)
int savedMode = preferences.getInt(MODE_KEY, static_cast<int>(DeviceMode::AUTO_MODE));
currentMode = static_cast<DeviceMode>(savedMode);
// If in AUTO_MODE, determine the appropriate mode based on saved credentials
if (currentMode == DeviceMode::AUTO_MODE) {
currentMode = determineMode();
}
log_i("[DeviceModeManager] Initialized with mode: %d", static_cast<int>(currentMode));
}
DeviceMode DeviceModeManager::getMode() {
return currentMode;
}
void DeviceModeManager::setMode(DeviceMode mode) {
currentMode = mode;
preferences.putInt(MODE_KEY, static_cast<int>(mode));
log_i("[DeviceModeManager] Mode set to: %d", static_cast<int>(mode));
}
bool DeviceModeManager::hasWiFiCredentials() {
return preferences.getBool(HAS_WIFI_CREDS_KEY, false);
}
void DeviceModeManager::setHasWiFiCredentials(bool hasCredentials) {
preferences.putBool(HAS_WIFI_CREDS_KEY, hasCredentials);
log_i("[DeviceModeManager] WiFi credentials status set to: %d", hasCredentials);
}
DeviceMode DeviceModeManager::determineMode() {
// If WiFi credentials are saved, use WiFi mode, otherwise use AP mode
return hasWiFiCredentials() ? DeviceMode::WIFI_MODE : DeviceMode::AP_MODE;
}
DeviceModeManager* DeviceModeManager::getInstance() {
if (instance == nullptr) {
createInstance();
}
return instance;
}
void DeviceModeManager::createInstance() {
if (instance == nullptr) {
instance = new DeviceModeManager();
instance->init();
}
}

View File

@ -1,47 +0,0 @@
#pragma once
#ifndef DEVICE_MODE_HPP
#define DEVICE_MODE_HPP
#include <Arduino.h>
#include <Preferences.h>
#include <string>
// 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 DeviceModeManager {
private:
static DeviceModeManager* instance;
Preferences preferences;
DeviceMode currentMode;
const char* PREF_NAMESPACE = "device_mode";
const char* MODE_KEY = "mode";
const char* HAS_WIFI_CREDS_KEY = "has_wifi_creds";
public:
DeviceModeManager();
~DeviceModeManager();
static DeviceModeManager* getInstance();
static void createInstance();
void init();
DeviceMode getMode();
void setMode(DeviceMode mode);
bool hasWiFiCredentials();
void setHasWiFiCredentials(bool hasCredentials);
DeviceMode determineMode();
};
#endif // DEVICE_MODE_HPP

View File

@ -28,7 +28,8 @@ struct DeviceStates {
networksConfigUpdated,
apConfigUpdated,
wifiTxPowerUpdated,
cameraConfigUpdated
cameraConfigUpdated,
deviceModeUpdated
};
enum WiFiState_e {

View File

@ -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);
@ -204,6 +221,18 @@ void ProjectConfig::load() {
this->config.camera.framesize = getInt("framesize", (uint8_t)CAM_RESOLUTION);
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;
}

View File

@ -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,
@ -132,12 +148,24 @@ class ProjectConfig : public Preferences, public ISubject<ConfigState_e> {
void setWiFiTxPower(uint8_t power, bool shouldNotify);
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

View File

@ -1,5 +1,4 @@
#include "SerialManager.hpp"
#include "data/DeviceMode/DeviceMode.hpp"
SerialManager::SerialManager(CommandManager* commandManager)
: commandManager(commandManager) {}
@ -20,7 +19,8 @@ void SerialManager::sendQuery(QueryAction action,
}
void SerialManager::checkUSBMode() {
DeviceMode currentMode = DeviceModeManager::getInstance()->getMode();
// 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");
@ -99,8 +99,7 @@ void SerialManager::run() {
}
}
// Check if we're in USB mode and automatically send frames
DeviceMode currentMode = DeviceModeManager::getInstance()->getMode();
DeviceMode currentMode = this->commandManager->getDeviceConfig()->getDeviceModeConfig().mode;
if (currentMode == DeviceMode::USB_MODE) {
this->send_frame();
}

View File

@ -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);

View File

@ -2,7 +2,6 @@
#include <WiFi.h>
#include "data/StateManager/StateManager.hpp"
#include "data/utilities/helpers.hpp"
#include "data/DeviceMode/DeviceMode.hpp"
WiFiHandler::WiFiHandler(ProjectConfig& configManager,
const std::string& ssid,
@ -93,7 +92,6 @@ void WiFiHandler::begin() {
void WiFiHandler::adhoc(const std::string& ssid,
uint8_t channel,
const std::string& password) {
wifiStateManager.setState(WiFiState_e::WiFiState_ADHOC);
log_i("\n[INFO]: Configuring access point...\n");
@ -173,8 +171,7 @@ bool WiFiHandler::iniSTA(const std::string& ssid,
log_d("Progress: %d \n\r", progress);
// Check if mode has been changed to USB mode during connection attempt
DeviceModeManager* deviceModeManager = DeviceModeManager::getInstance();
if (deviceModeManager && deviceModeManager->getMode() == DeviceMode::USB_MODE) {
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);
@ -182,7 +179,7 @@ bool WiFiHandler::iniSTA(const std::string& ssid,
}
if (Serial.available()) {
yield();
yield(); // Allow other processes to run
}
if ((currentMillis - startingMillis) >= connectionTimeout) {

View File

@ -1,5 +1,4 @@
#include <openiris.hpp>
#include "data/DeviceMode/DeviceMode.hpp"
/**
* @brief ProjectConfig object
* @brief This is the main configuration object for the project
@ -40,8 +39,7 @@ StreamServer streamServer;
void etvr_eye_tracker_web_init() {
// Check if mode has been changed to USB mode before starting network initialization
DeviceModeManager* deviceModeManager = DeviceModeManager::getInstance();
if (deviceModeManager && deviceModeManager->getMode() == DeviceMode::USB_MODE) {
if (deviceConfig.getDeviceModeConfig().mode == DeviceMode::USB_MODE) {
log_i("[SETUP]: Mode changed to USB before network initialization, aborting");
WiFi.disconnect(true);
return;
@ -51,7 +49,7 @@ void etvr_eye_tracker_web_init() {
deviceConfig.attach(mdnsHandler);
// Check mode again before starting WiFi
if (deviceModeManager && deviceModeManager->getMode() == DeviceMode::USB_MODE) {
if (deviceConfig.getDeviceModeConfig().mode == DeviceMode::USB_MODE) {
log_i("[SETUP]: Mode changed to USB before WiFi initialization, aborting");
WiFi.disconnect(true);
return;
@ -61,7 +59,7 @@ void etvr_eye_tracker_web_init() {
wifiHandler.begin();
// Check mode again before starting MDNS
if (deviceModeManager && deviceModeManager->getMode() == DeviceMode::USB_MODE) {
if (deviceConfig.getDeviceModeConfig().mode == DeviceMode::USB_MODE) {
log_i("[SETUP]: Mode changed to USB before MDNS initialization, aborting");
WiFi.disconnect(true);
return;
@ -111,8 +109,7 @@ void setup() {
Logo::printASCII();
ledManager.begin();
DeviceModeManager::createInstance();
DeviceModeManager* deviceModeManager = DeviceModeManager::getInstance();
// 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.
@ -132,7 +129,7 @@ void setup() {
serialManager.init();
DeviceMode currentMode = deviceModeManager->getMode();
DeviceMode currentMode = deviceConfig.getDeviceModeConfig().mode;
if (currentMode == DeviceMode::WIFI_MODE) {
// Initialize WiFi mode

View File

@ -14,6 +14,7 @@ class OpenIrisModeTester:
self.serial_conn = None
self.device_ip = None
self.device_port = None
self.last_boot_time = None
def connect_serial(self):
try:
@ -101,61 +102,267 @@ class OpenIrisModeTester:
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):
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():
return
print("❌ Serial connection failed. Aborting test.")
return test_results
test_results["serial_connection"] = True
print("✅ Serial connection successful")
print("\n=== Step 1: Setting WiFi credentials ===")
tester.set_wifi_credentials(ssid, password)
print("Device will restart. Waiting 10 seconds...")
time.sleep(10)
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")
tester.connect_serial()
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 2: Discovering device on network ===")
if tester.discover_device():
print("\n=== Step 3: Testing WiFi API ===")
tester.test_wifi_api()
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")
tester.flush_serial_logs()
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 4: Wiping WiFi credentials ===")
tester.wipe_wifi_credentials()
print("Device will restart in USB mode. Waiting 10 seconds...")
time.sleep(10)
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.connect_serial()
tester.flush_serial_logs()
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}")
print("\n=== Step 5: Verifying USB mode ===")
ping_command = {"commands": [{"command": "ping"}]}
print("Device is most likely booting, waiting 10 seconds...")
time.sleep(10)
response = tester.send_command(ping_command)
print(f"Response: {response}")
tester.flush_serial_logs()
return test_results
finally:
tester.close()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Test OpenIris mode switching functionality")
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()
run_test(args.port, args.ssid, args.password)
# 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)