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 all commits
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
69 changes: 62 additions & 7 deletions ESP/lib/src/data/CommandManager/CommandManager.cpp
Original file line number Diff line number Diff line change
@@ -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*>())
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 +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;
}
Expand All @@ -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,
Expand All @@ -55,14 +57,18 @@ void CommandManager::handleCommand(JsonVariant command) {
0, // channel, should this be zero?
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;

Expand All @@ -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;
}
Expand Down
7 changes: 7 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,9 @@ enum CommandType {
PING,
SET_WIFI,
SET_MDNS,
SWITCH_MODE,
WIPE_WIFI_CREDS,
RESTART_DEVICE,
};

struct CommandsPayload {
Expand All @@ -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;
Expand All @@ -33,6 +39,7 @@ class CommandManager {
public:
CommandManager(ProjectConfig* deviceConfig);
void handleCommands(CommandsPayload commandsPayload);
ProjectConfig* getDeviceConfig() { return deviceConfig; }
};

#endif
3 changes: 2 additions & 1 deletion ESP/lib/src/data/StateManager/StateManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ struct DeviceStates {
networksConfigUpdated,
apConfigUpdated,
wifiTxPowerUpdated,
cameraConfigUpdated
cameraConfigUpdated,
deviceModeUpdated
};

enum WiFiState_e {
Expand Down
72 changes: 69 additions & 3 deletions ESP/lib/src/data/config/project_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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");

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
28 changes: 28 additions & 0 deletions ESP/lib/src/data/config/project_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -88,13 +96,20 @@ 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;
std::vector<WiFiConfig_t> networks;
AP_WiFiConfig_t ap_network;
MDNSConfig_t mdns;
WiFiTxPower_t txpower;
DeviceModeConfig_t deviceMode;
};

DeviceConfig_t& getDeviceConfig();
Expand All @@ -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,
Expand Down Expand Up @@ -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
Loading
Loading