Rewrite commands into a more modern form in anticipation for API rewrite

This commit is contained in:
Lorow 2024-08-15 20:15:09 +02:00
parent 4705e41476
commit 27177e411c
6 changed files with 259 additions and 69 deletions

View File

@ -0,0 +1,41 @@
#include "Command.hpp"
CommandResult PingCommand::execute() {
return CommandResult::getSuccessResult("pong");
}
CommandResult SetWiFiCommand::validate() {
if (!data.containsKey("ssid"))
return CommandResult::getErrorResult("{\"error\": \"Missing ssid\"}");
if (!data.containsKey("password"))
return CommandResult::getErrorResult("{\"error\": \"Missing password\"}");
return CommandResult::getSuccessResult("");
}
CommandResult SetWiFiCommand::execute() {
std::string network_name = "main";
if (data.containsKey("network_name"))
network_name = data["network_name"].as<std::string>();
projectConfig.setWifiConfig(network_name, data["ssid"], data["password"], 0,
0, false, false);
return CommandResult::getSuccessResult("WIFI SET");
}
CommandResult SetMDNSCommand::validate() {
if (!data.containsKey("hostname") || !strlen(data["hostname"]))
return CommandResult::getErrorResult("{\"error\": \"Missing hostname\"}");
return CommandResult::getSuccessResult("");
}
CommandResult SetMDNSCommand::execute() {
projectConfig.setMDNSConfig(data["hostname"], "openiristracker", false);
return CommandResult::getSuccessResult("MDNS SET");
}
CommandResult SaveConfigCommand::execute() {
projectConfig.save();
return CommandResult::getSuccessResult("CONFIG SAVED");
}

View File

@ -0,0 +1,85 @@
#ifndef COMMAND_HPP
#define COMMAND_HPP
#include <ArduinoJson.h>
#include <optional>
#include <string>
#include <variant>
#include "data/config/project_config.hpp"
class CommandResult {
private:
// or maybe std::optional?
std::optional<std::string> successMessage;
std::optional<std::string> errorMessage;
public:
CommandResult(std::optional<std::string> successMessage,
std::optional<std::string> errorMessage)
: successMessage(successMessage), errorMessage(errorMessage) {}
bool isSuccess() const { return successMessage.has_value(); }
static CommandResult getSuccessResult(std::string message) {
return CommandResult(message, std::nullopt);
}
static CommandResult getErrorResult(std::string message) {
return CommandResult(std::nullopt, message);
}
std::string getSuccessMessage() const { return successMessage.value(); };
std::string getErrorMessage() const { return errorMessage.value(); }
};
class ICommand {
public:
virtual CommandResult validate() = 0;
virtual CommandResult execute() = 0;
virtual ~ICommand() = default;
};
class PingCommand : public ICommand {
public:
CommandResult validate() override {
return CommandResult::getSuccessResult("");
};
CommandResult execute() override;
};
class SetWiFiCommand : public ICommand {
ProjectConfig& projectConfig;
JsonVariant data;
public:
SetWiFiCommand(ProjectConfig& projectConfig, JsonVariant data)
: projectConfig(projectConfig), data(data) {}
CommandResult validate() override;
CommandResult execute() override;
};
class SetMDNSCommand : public ICommand {
ProjectConfig& projectConfig;
JsonVariant data;
public:
SetMDNSCommand(ProjectConfig& projectConfig, JsonVariant data)
: projectConfig(projectConfig), data(data) {}
CommandResult validate() override;
CommandResult execute() override;
};
class SaveConfigCommand : public ICommand {
ProjectConfig& projectConfig;
public:
SaveConfigCommand(ProjectConfig& projectConfig)
: projectConfig(projectConfig) {}
CommandResult validate() override {
return CommandResult::getSuccessResult("");
};
CommandResult execute() override;
};
#endif

View File

@ -1,9 +1,20 @@
#include "CommandManager.hpp"
CommandManager::CommandManager(ProjectConfig* deviceConfig)
: deviceConfig(deviceConfig) {}
std::unique_ptr<ICommand> CommandManager::createCommand(CommandType commandType,
JsonVariant& data) {
switch (commandType) {
case CommandType::PING:
return std::make_unique<PingCommand>();
case CommandType::SET_WIFI:
return std::make_unique<SetWiFiCommand>(this->projectConfig, data);
case CommandType::SET_MDNS:
return std::make_unique<SetMDNSCommand>(this->projectConfig, data);
case CommandType::SAVE_CONFIG:
return std::make_unique<SaveConfigCommand>(this->projectConfig);
}
}
const CommandType CommandManager::getCommandType(JsonVariant& command) {
CommandType CommandManager::getCommandType(JsonVariant& command) {
if (!command.containsKey("command"))
return CommandType::None;
@ -18,64 +29,87 @@ bool CommandManager::hasDataField(JsonVariant& command) {
return command.containsKey("data");
}
void CommandManager::handleCommands(CommandsPayload commandsPayload) {
std::variant<std::vector<CommandResult>, CommandResult>
CommandManager::handleBatchCommands(CommandsPayload commandsPayload) {
std::vector<CommandResult> results = {};
std::vector<std::string> errors = {};
std::vector<std::unique_ptr<ICommand>> commands;
if (!commandsPayload.data.containsKey("commands")) {
log_e("Json data sent not supported, lacks commands field");
return;
std::string error = "Json data sent not supported, lacks commands field";
log_e("%s", error.c_str());
return CommandResult::getErrorResult(
Helpers::format_string("\"error\":\"%s\"", error));
}
for (JsonVariant commandData :
commandsPayload.data["commands"].as<JsonArray>()) {
this->handleCommand(commandData);
auto command_or_result = this->createCommandFromJsonVariant(commandData);
if (auto command_ptr =
std::get_if<std::unique_ptr<ICommand>>(&command_or_result)) {
auto validation_result = (*command_ptr)->validate();
if (validation_result.isSuccess())
commands.emplace_back(std::move((*command_ptr)));
else
errors.push_back(validation_result.getErrorMessage());
} else {
errors.push_back(
std::get<CommandResult>(command_or_result).getErrorMessage());
continue;
}
}
this->deviceConfig->save();
// if we have any errors, consolidate them into a single message and return
if (errors.size() > 0) {
return CommandResult::getErrorResult(Helpers::format_string(
"\"error\":\"[%s]\"", this->join_strings(errors, ",")));
}
for (auto& valid_command : commands) {
results.push_back(valid_command->execute());
}
return results;
}
void CommandManager::handleCommand(JsonVariant command) {
auto command_type = this->getCommandType(command);
CommandResult CommandManager::handleSingleCommand(
CommandsPayload commandsPayload) {
if (!commandsPayload.data.containsKey("command")) {
std::string error = "Json data sent not supported, lacks commands field";
log_e("%s", error.c_str());
switch (command_type) {
case CommandType::SET_WIFI: {
if (!this->hasDataField(command))
// malformed command, lacked data field
break;
if (!command["data"].containsKey("ssid") ||
!command["data"].containsKey("password"))
break;
std::string customNetworkName = "main";
if (command["data"].containsKey("network_name"))
customNetworkName = command["data"]["network_name"].as<std::string>();
this->deviceConfig->setWifiConfig(customNetworkName,
command["data"]["ssid"],
command["data"]["password"],
0, // channel, should this be zero?
0, // power, should this be zero?
false, false);
break;
}
case CommandType::SET_MDNS: {
if (!this->hasDataField(command))
break;
if (!command["data"].containsKey("hostname") ||
!strlen(command["data"]["hostname"]))
break;
this->deviceConfig->setMDNSConfig(command["data"]["hostname"],
"openiristracker", false);
break;
}
case CommandType::PING: {
Serial.println("PONG \n\r");
break;
}
default:
break;
CommandResult::getErrorResult(
Helpers::format_string("\"error\":\"%s\"", error));
}
JsonVariant commandData = commandsPayload.data["command"];
auto command_or_result = this->createCommandFromJsonVariant(commandData);
if (std::holds_alternative<CommandResult>(command_or_result)) {
return std::get<CommandResult>(command_or_result);
}
auto command =
std::move(std::get<std::unique_ptr<ICommand>>(command_or_result));
auto validation_result = command->validate();
if (!validation_result.isSuccess()) {
return validation_result;
};
return command->execute();
}
std::variant<std::unique_ptr<ICommand>, CommandResult>
CommandManager::createCommandFromJsonVariant(JsonVariant& command) {
auto command_type = this->getCommandType(command);
if (command_type == CommandType::None) {
std::string error =
Helpers::format_string("Command not supported: %s", command["command"]);
log_e("%s", error.c_str());
throw CommandResult::getErrorResult(
Helpers::format_string("\"error\":\"%s\"", error));
}
return this->createCommand(command_type, command);
}

View File

@ -2,37 +2,69 @@
#ifndef TASK_MANAGER_HPP
#define TASK_MANAGER_HPP
#include <ArduinoJson.h>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>
#include <iostream>
#include <iterator>
#include <memory>
#include <sstream>
#include <vector>
#include "data/CommandManager/Command.hpp"
#include "data/config/project_config.hpp"
struct CommandsPayload {
JsonVariant data;
};
enum CommandType {
None,
PING,
SET_WIFI,
SET_MDNS,
SAVE_CONFIG,
};
struct CommandsPayload {
JsonDocument data;
const std::unordered_map<std::string, CommandType> commandMap = {
{"ping", CommandType::PING},
{"set_wifi", CommandType::SET_WIFI},
{"set_mdns", CommandType::SET_MDNS},
};
class CommandManager {
private:
const std::unordered_map<std::string, CommandType> commandMap = {
{"ping", CommandType::PING},
{"set_wifi", CommandType::SET_WIFI},
{"set_mdns", CommandType::SET_MDNS},
};
ProjectConfig& projectConfig;
ProjectConfig* deviceConfig;
std::string join_strings(std::vector<std::string> const& strings,
std::string delim) {
std::stringstream ss;
std::copy(strings.begin(), strings.end(),
std::ostream_iterator<std::string>(ss, delim.c_str()));
return ss.str();
}
bool hasDataField(JsonVariant& command);
void handleCommand(JsonVariant command);
const CommandType getCommandType(JsonVariant& command);
std::unique_ptr<ICommand> createCommand(CommandType commandType,
JsonVariant& data);
std::variant<std::unique_ptr<ICommand>, CommandResult>
createCommandFromJsonVariant(JsonVariant& command);
CommandType getCommandType(JsonVariant& command);
// // TODO rewrite the API
// // TODO add FPS/ Freq / cropping to the API
// // TODO rewrite camera handler to be simpler and easier to change
public:
CommandManager(ProjectConfig* deviceConfig);
void handleCommands(CommandsPayload commandsPayload);
};
CommandManager(ProjectConfig& projectConfig)
: projectConfig(projectConfig) {};
CommandResult handleSingleCommand(CommandsPayload commandsPayload);
std::variant<std::vector<CommandResult>, CommandResult> handleBatchCommands(
CommandsPayload commandsPayload);
};
#endif

View File

@ -72,7 +72,7 @@ void SerialManager::run() {
}
CommandsPayload commands = {doc};
this->commandManager->handleCommands(commands);
this->commandManager->handleBatchCommands(commands);
}
#ifdef ETVR_EYE_TRACKER_USB_API
else {

View File

@ -8,9 +8,7 @@
#include <esp_camera.h>
#include "data/CommandManager/CommandManager.hpp"
#include "data/config/project_config.hpp"
const char* const ETVR_HEADER = "\xff\xa0";
const char* const ETVR_HEADER_FRAME = "\xff\xa1";
#include "data/utilities/helpers.hpp"
enum QueryAction {
READY_TO_RECEIVE,