Skip to content
This repository was archived by the owner on Dec 14, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PythonExamples/__pycache__/mode_switch_test.cpython-311.pyc
81 changes: 74 additions & 7 deletions ESP/lib/src/data/CommandManager/CommandManager.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#include "CommandManager.hpp"
#include "data/DeviceMode/DeviceMode.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*>())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering, why the change? Is .is<T> is faster / more memory efficient than .containsKey()?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

command["command"].is<const char*>() it's the recommended approach in ArduinoJson because containsKey() is deprecated lol and i didn't like the warnings

return CommandType::None;

if (auto search = commandMap.find(command["command"]);
Expand All @@ -15,11 +18,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;
}
Expand All @@ -41,12 +44,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,
Expand All @@ -55,14 +58,24 @@ void CommandManager::handleCommand(JsonVariant command) {
0, // channel, should this be zero?
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a bad idea, we're sending two commands in one payload in the flasher, first one to setup wifi and second to setup mDNS, so the second one might fail or won't get executed at all since they're executed in the order they were received.

I think it'd be better to introduce one more command - RESTART_DEVICE and send it as the very last command so that we're sure we've got everything set properly before

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! That makes sense; I'll add it then! i wasnt really sure how the flasher worked, but that makes sense

}

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;

Expand All @@ -75,6 +88,60 @@ 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;

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

break;
}
case CommandType::WIPE_WIFI_CREDS: {

auto networks = this->deviceConfig->getWifiConfigs();
for (auto& network : networks) {
this->deviceConfig->deleteWifiConfig(network.name, false);
}

DeviceModeManager* deviceModeManager = DeviceModeManager::getInstance();
if (deviceModeManager) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from what I gather, getInstance() will return the already existing instance or create a fresh one, do we still have to check for a nullpointer then?

deviceModeManager->setHasWiFiCredentials(false);

deviceModeManager->setMode(DeviceMode::USB_MODE);
log_i("[CommandManager] Switching to USB mode after wiping credentials");

OpenIrisTasks::ScheduleRestart(2000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something happened here with formatting or github crewed up?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! i wrote some code that didn't work, deleted it and then forgot to fix the restart fomat imao

}

break;
}
default:
break;
}
Expand Down
4 changes: 4 additions & 0 deletions ESP/lib/src/data/CommandManager/CommandManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ enum CommandType {
PING,
SET_WIFI,
SET_MDNS,
SWITCH_MODE,
WIPE_WIFI_CREDS,
};

struct CommandsPayload {
Expand All @@ -22,6 +24,8 @@ 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},
};

ProjectConfig* deviceConfig;
Expand Down
62 changes: 62 additions & 0 deletions ESP/lib/src/data/DeviceMode/DeviceMode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I'm sold on this. We've got a config manager that takes care of manipulating everything related to preferences and configuration so we could move most of the responsibility into it, what do ya think?

Granted, the idea of DeviceModeManager being a singleton would have to go and with it a bit of the implementation, but might be a bit cleaner?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perchance yeah, I needed some lightweight code to do what i wanted at the time, so ill change it to use config manager lol


// 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();
}
}
47 changes: 47 additions & 0 deletions ESP/lib/src/data/DeviceMode/DeviceMode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#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
47 changes: 36 additions & 11 deletions ESP/lib/src/io/Serial/SerialManager.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
#include "SerialManager.hpp"
#include "data/DeviceMode/DeviceMode.hpp"

SerialManager::SerialManager(CommandManager* commandManager)
: commandManager(commandManager) {}
: commandManager(commandManager) {}

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() {
DeviceMode currentMode = DeviceModeManager::getInstance()->getMode();
if (currentMode == DeviceMode::USB_MODE) {
log_i("[SerialManager] USB mode active - auto-streaming enabled");

}
}

#ifdef ETVR_EYE_TRACKER_USB_API
void SerialManager::send_frame() {
if (!last_frame)
last_frame = esp_timer_get_time();
Expand Down Expand Up @@ -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
Expand All @@ -58,25 +80,28 @@ 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);
Comment on lines +96 to +98
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the change?

}

CommandsPayload commands = {doc};
this->commandManager->handleCommands(commands);
}
#ifdef ETVR_EYE_TRACKER_USB_API
else {

// Check if we're in USB mode and automatically send frames
DeviceMode currentMode = DeviceModeManager::getInstance()->getMode();
if (currentMode == DeviceMode::USB_MODE) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about:

if (DeviceModeManager::getInstance()->getMode() == DeviceMode::USB_MODE)

I'm pretty sure the compiler will optimize it away anyway, but we can make it slightly cleaner

this->send_frame();
}
#endif
}
3 changes: 1 addition & 2 deletions ESP/lib/src/io/Serial/SerialManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -49,6 +47,7 @@ class SerialManager {
std::string additional_info);
void init();
void run();
void checkUSBMode();
};

#endif
Loading
Loading