Skip to content
This repository was archived by the owner on Dec 14, 2025. It is now read-only.

Commit 27177e4

Browse files
committed
Rewrite commands into a more modern form in anticipation for API rewrite
1 parent 4705e41 commit 27177e4

6 files changed

Lines changed: 253 additions & 63 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include "Command.hpp"
2+
3+
CommandResult PingCommand::execute() {
4+
return CommandResult::getSuccessResult("pong");
5+
}
6+
7+
CommandResult SetWiFiCommand::validate() {
8+
if (!data.containsKey("ssid"))
9+
return CommandResult::getErrorResult("{\"error\": \"Missing ssid\"}");
10+
if (!data.containsKey("password"))
11+
return CommandResult::getErrorResult("{\"error\": \"Missing password\"}");
12+
return CommandResult::getSuccessResult("");
13+
}
14+
15+
CommandResult SetWiFiCommand::execute() {
16+
std::string network_name = "main";
17+
if (data.containsKey("network_name"))
18+
network_name = data["network_name"].as<std::string>();
19+
20+
projectConfig.setWifiConfig(network_name, data["ssid"], data["password"], 0,
21+
0, false, false);
22+
23+
return CommandResult::getSuccessResult("WIFI SET");
24+
}
25+
26+
CommandResult SetMDNSCommand::validate() {
27+
if (!data.containsKey("hostname") || !strlen(data["hostname"]))
28+
return CommandResult::getErrorResult("{\"error\": \"Missing hostname\"}");
29+
30+
return CommandResult::getSuccessResult("");
31+
}
32+
33+
CommandResult SetMDNSCommand::execute() {
34+
projectConfig.setMDNSConfig(data["hostname"], "openiristracker", false);
35+
return CommandResult::getSuccessResult("MDNS SET");
36+
}
37+
38+
CommandResult SaveConfigCommand::execute() {
39+
projectConfig.save();
40+
return CommandResult::getSuccessResult("CONFIG SAVED");
41+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#ifndef COMMAND_HPP
2+
#define COMMAND_HPP
3+
#include <ArduinoJson.h>
4+
#include <optional>
5+
#include <string>
6+
#include <variant>
7+
#include "data/config/project_config.hpp"
8+
9+
class CommandResult {
10+
private:
11+
// or maybe std::optional?
12+
std::optional<std::string> successMessage;
13+
std::optional<std::string> errorMessage;
14+
15+
public:
16+
CommandResult(std::optional<std::string> successMessage,
17+
std::optional<std::string> errorMessage)
18+
: successMessage(successMessage), errorMessage(errorMessage) {}
19+
20+
bool isSuccess() const { return successMessage.has_value(); }
21+
22+
static CommandResult getSuccessResult(std::string message) {
23+
return CommandResult(message, std::nullopt);
24+
}
25+
26+
static CommandResult getErrorResult(std::string message) {
27+
return CommandResult(std::nullopt, message);
28+
}
29+
30+
std::string getSuccessMessage() const { return successMessage.value(); };
31+
std::string getErrorMessage() const { return errorMessage.value(); }
32+
};
33+
34+
class ICommand {
35+
public:
36+
virtual CommandResult validate() = 0;
37+
virtual CommandResult execute() = 0;
38+
virtual ~ICommand() = default;
39+
};
40+
41+
class PingCommand : public ICommand {
42+
public:
43+
CommandResult validate() override {
44+
return CommandResult::getSuccessResult("");
45+
};
46+
CommandResult execute() override;
47+
};
48+
49+
class SetWiFiCommand : public ICommand {
50+
ProjectConfig& projectConfig;
51+
JsonVariant data;
52+
53+
public:
54+
SetWiFiCommand(ProjectConfig& projectConfig, JsonVariant data)
55+
: projectConfig(projectConfig), data(data) {}
56+
CommandResult validate() override;
57+
CommandResult execute() override;
58+
};
59+
60+
class SetMDNSCommand : public ICommand {
61+
ProjectConfig& projectConfig;
62+
JsonVariant data;
63+
64+
public:
65+
SetMDNSCommand(ProjectConfig& projectConfig, JsonVariant data)
66+
: projectConfig(projectConfig), data(data) {}
67+
CommandResult validate() override;
68+
CommandResult execute() override;
69+
};
70+
71+
class SaveConfigCommand : public ICommand {
72+
ProjectConfig& projectConfig;
73+
74+
public:
75+
SaveConfigCommand(ProjectConfig& projectConfig)
76+
: projectConfig(projectConfig) {}
77+
78+
CommandResult validate() override {
79+
return CommandResult::getSuccessResult("");
80+
};
81+
82+
CommandResult execute() override;
83+
};
84+
85+
#endif
Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
#include "CommandManager.hpp"
22

3-
CommandManager::CommandManager(ProjectConfig* deviceConfig)
4-
: deviceConfig(deviceConfig) {}
3+
std::unique_ptr<ICommand> CommandManager::createCommand(CommandType commandType,
4+
JsonVariant& data) {
5+
switch (commandType) {
6+
case CommandType::PING:
7+
return std::make_unique<PingCommand>();
8+
case CommandType::SET_WIFI:
9+
return std::make_unique<SetWiFiCommand>(this->projectConfig, data);
10+
case CommandType::SET_MDNS:
11+
return std::make_unique<SetMDNSCommand>(this->projectConfig, data);
12+
case CommandType::SAVE_CONFIG:
13+
return std::make_unique<SaveConfigCommand>(this->projectConfig);
14+
}
15+
}
516

6-
const CommandType CommandManager::getCommandType(JsonVariant& command) {
17+
CommandType CommandManager::getCommandType(JsonVariant& command) {
718
if (!command.containsKey("command"))
819
return CommandType::None;
920

@@ -18,64 +29,87 @@ bool CommandManager::hasDataField(JsonVariant& command) {
1829
return command.containsKey("data");
1930
}
2031

21-
void CommandManager::handleCommands(CommandsPayload commandsPayload) {
32+
std::variant<std::vector<CommandResult>, CommandResult>
33+
CommandManager::handleBatchCommands(CommandsPayload commandsPayload) {
34+
std::vector<CommandResult> results = {};
35+
std::vector<std::string> errors = {};
36+
std::vector<std::unique_ptr<ICommand>> commands;
37+
2238
if (!commandsPayload.data.containsKey("commands")) {
23-
log_e("Json data sent not supported, lacks commands field");
24-
return;
39+
std::string error = "Json data sent not supported, lacks commands field";
40+
log_e("%s", error.c_str());
41+
return CommandResult::getErrorResult(
42+
Helpers::format_string("\"error\":\"%s\"", error));
2543
}
2644

2745
for (JsonVariant commandData :
2846
commandsPayload.data["commands"].as<JsonArray>()) {
29-
this->handleCommand(commandData);
47+
auto command_or_result = this->createCommandFromJsonVariant(commandData);
48+
49+
if (auto command_ptr =
50+
std::get_if<std::unique_ptr<ICommand>>(&command_or_result)) {
51+
auto validation_result = (*command_ptr)->validate();
52+
if (validation_result.isSuccess())
53+
commands.emplace_back(std::move((*command_ptr)));
54+
else
55+
errors.push_back(validation_result.getErrorMessage());
56+
} else {
57+
errors.push_back(
58+
std::get<CommandResult>(command_or_result).getErrorMessage());
59+
continue;
60+
}
3061
}
3162

32-
this->deviceConfig->save();
33-
}
63+
// if we have any errors, consolidate them into a single message and return
64+
if (errors.size() > 0) {
65+
return CommandResult::getErrorResult(Helpers::format_string(
66+
"\"error\":\"[%s]\"", this->join_strings(errors, ",")));
67+
}
3468

35-
void CommandManager::handleCommand(JsonVariant command) {
36-
auto command_type = this->getCommandType(command);
69+
for (auto& valid_command : commands) {
70+
results.push_back(valid_command->execute());
71+
}
3772

38-
switch (command_type) {
39-
case CommandType::SET_WIFI: {
40-
if (!this->hasDataField(command))
41-
// malformed command, lacked data field
42-
break;
73+
return results;
74+
}
4375

44-
if (!command["data"].containsKey("ssid") ||
45-
!command["data"].containsKey("password"))
46-
break;
76+
CommandResult CommandManager::handleSingleCommand(
77+
CommandsPayload commandsPayload) {
78+
if (!commandsPayload.data.containsKey("command")) {
79+
std::string error = "Json data sent not supported, lacks commands field";
80+
log_e("%s", error.c_str());
4781

48-
std::string customNetworkName = "main";
49-
if (command["data"].containsKey("network_name"))
50-
customNetworkName = command["data"]["network_name"].as<std::string>();
82+
CommandResult::getErrorResult(
83+
Helpers::format_string("\"error\":\"%s\"", error));
84+
}
5185

52-
this->deviceConfig->setWifiConfig(customNetworkName,
53-
command["data"]["ssid"],
54-
command["data"]["password"],
55-
0, // channel, should this be zero?
56-
0, // power, should this be zero?
57-
false, false);
86+
JsonVariant commandData = commandsPayload.data["command"];
87+
auto command_or_result = this->createCommandFromJsonVariant(commandData);
5888

59-
break;
60-
}
61-
case CommandType::SET_MDNS: {
62-
if (!this->hasDataField(command))
63-
break;
89+
if (std::holds_alternative<CommandResult>(command_or_result)) {
90+
return std::get<CommandResult>(command_or_result);
91+
}
6492

65-
if (!command["data"].containsKey("hostname") ||
66-
!strlen(command["data"]["hostname"]))
67-
break;
93+
auto command =
94+
std::move(std::get<std::unique_ptr<ICommand>>(command_or_result));
6895

69-
this->deviceConfig->setMDNSConfig(command["data"]["hostname"],
70-
"openiristracker", false);
96+
auto validation_result = command->validate();
97+
if (!validation_result.isSuccess()) {
98+
return validation_result;
99+
};
71100

72-
break;
73-
}
74-
case CommandType::PING: {
75-
Serial.println("PONG \n\r");
76-
break;
77-
}
78-
default:
79-
break;
101+
return command->execute();
102+
}
103+
104+
std::variant<std::unique_ptr<ICommand>, CommandResult>
105+
CommandManager::createCommandFromJsonVariant(JsonVariant& command) {
106+
auto command_type = this->getCommandType(command);
107+
if (command_type == CommandType::None) {
108+
std::string error =
109+
Helpers::format_string("Command not supported: %s", command["command"]);
110+
log_e("%s", error.c_str());
111+
throw CommandResult::getErrorResult(
112+
Helpers::format_string("\"error\":\"%s\"", error));
80113
}
114+
return this->createCommand(command_type, command);
81115
}

ESP/lib/src/data/CommandManager/CommandManager.hpp

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,69 @@
22
#ifndef TASK_MANAGER_HPP
33
#define TASK_MANAGER_HPP
44
#include <ArduinoJson.h>
5+
#include <optional>
6+
#include <string>
57
#include <unordered_map>
8+
#include <variant>
9+
10+
#include <iostream>
11+
#include <iterator>
12+
#include <memory>
13+
#include <sstream>
14+
#include <vector>
15+
16+
#include "data/CommandManager/Command.hpp"
617
#include "data/config/project_config.hpp"
718

19+
struct CommandsPayload {
20+
JsonVariant data;
21+
};
22+
823
enum CommandType {
924
None,
1025
PING,
1126
SET_WIFI,
1227
SET_MDNS,
28+
SAVE_CONFIG,
1329
};
1430

15-
struct CommandsPayload {
16-
JsonDocument data;
31+
const std::unordered_map<std::string, CommandType> commandMap = {
32+
{"ping", CommandType::PING},
33+
{"set_wifi", CommandType::SET_WIFI},
34+
{"set_mdns", CommandType::SET_MDNS},
1735
};
1836

1937
class CommandManager {
2038
private:
21-
const std::unordered_map<std::string, CommandType> commandMap = {
22-
{"ping", CommandType::PING},
23-
{"set_wifi", CommandType::SET_WIFI},
24-
{"set_mdns", CommandType::SET_MDNS},
25-
};
39+
ProjectConfig& projectConfig;
2640

27-
ProjectConfig* deviceConfig;
41+
std::string join_strings(std::vector<std::string> const& strings,
42+
std::string delim) {
43+
std::stringstream ss;
44+
std::copy(strings.begin(), strings.end(),
45+
std::ostream_iterator<std::string>(ss, delim.c_str()));
46+
return ss.str();
47+
}
2848

2949
bool hasDataField(JsonVariant& command);
30-
void handleCommand(JsonVariant command);
31-
const CommandType getCommandType(JsonVariant& command);
50+
std::unique_ptr<ICommand> createCommand(CommandType commandType,
51+
JsonVariant& data);
52+
53+
std::variant<std::unique_ptr<ICommand>, CommandResult>
54+
createCommandFromJsonVariant(JsonVariant& command);
55+
56+
CommandType getCommandType(JsonVariant& command);
57+
58+
// // TODO rewrite the API
59+
// // TODO add FPS/ Freq / cropping to the API
60+
// // TODO rewrite camera handler to be simpler and easier to change
3261

3362
public:
34-
CommandManager(ProjectConfig* deviceConfig);
35-
void handleCommands(CommandsPayload commandsPayload);
36-
};
63+
CommandManager(ProjectConfig& projectConfig)
64+
: projectConfig(projectConfig) {};
3765

66+
CommandResult handleSingleCommand(CommandsPayload commandsPayload);
67+
std::variant<std::vector<CommandResult>, CommandResult> handleBatchCommands(
68+
CommandsPayload commandsPayload);
69+
};
3870
#endif

ESP/lib/src/io/Serial/SerialManager.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ void SerialManager::run() {
7272
}
7373

7474
CommandsPayload commands = {doc};
75-
this->commandManager->handleCommands(commands);
75+
this->commandManager->handleBatchCommands(commands);
7676
}
7777
#ifdef ETVR_EYE_TRACKER_USB_API
7878
else {

ESP/lib/src/io/Serial/SerialManager.hpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
#include <esp_camera.h>
99
#include "data/CommandManager/CommandManager.hpp"
1010
#include "data/config/project_config.hpp"
11-
12-
const char* const ETVR_HEADER = "\xff\xa0";
13-
const char* const ETVR_HEADER_FRAME = "\xff\xa1";
11+
#include "data/utilities/helpers.hpp"
1412

1513
enum QueryAction {
1614
READY_TO_RECEIVE,

0 commit comments

Comments
 (0)