Rewrite commands to nlohmann-json

This commit is contained in:
Lorow
2025-10-09 23:12:03 +02:00
parent cf9eecc822
commit 44b5fe157a
24 changed files with 441 additions and 657 deletions

View File

@@ -1,6 +1,8 @@
idf_component_register( idf_component_register(
SRCS SRCS
"CommandManager/CommandManager.cpp" "CommandManager/CommandManager.cpp"
"CommandManager/CommandResult.cpp"
"CommandManager/CommandSchema.cpp"
"CommandManager/commands/simple_commands.cpp" "CommandManager/commands/simple_commands.cpp"
"CommandManager/commands/camera_commands.cpp" "CommandManager/commands/camera_commands.cpp"
"CommandManager/commands/wifi_commands.cpp" "CommandManager/commands/wifi_commands.cpp"
@@ -11,5 +13,5 @@ idf_component_register(
INCLUDE_DIRS INCLUDE_DIRS
"CommandManager" "CommandManager"
"CommandManager/commands" "CommandManager/commands"
REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers LEDManager Monitoring REQUIRES ProjectConfig nlohmann-json CameraManager OpenIrisTasks wifiManager Helpers LEDManager Monitoring
) )

View File

@@ -12,7 +12,6 @@ std::unordered_map<std::string, CommandType> commandTypeMap = {
{"set_mdns", CommandType::SET_MDNS}, {"set_mdns", CommandType::SET_MDNS},
{"get_mdns_name", CommandType::GET_MDNS_NAME}, {"get_mdns_name", CommandType::GET_MDNS_NAME},
{"update_camera", CommandType::UPDATE_CAMERA}, {"update_camera", CommandType::UPDATE_CAMERA},
{"restart_camera", CommandType::RESTART_CAMERA},
{"save_config", CommandType::SAVE_CONFIG}, {"save_config", CommandType::SAVE_CONFIG},
{"get_config", CommandType::GET_CONFIG}, {"get_config", CommandType::GET_CONFIG},
{"reset_config", CommandType::RESET_CONFIG}, {"reset_config", CommandType::RESET_CONFIG},
@@ -30,7 +29,7 @@ std::unordered_map<std::string, CommandType> commandTypeMap = {
{"get_who_am_i", CommandType::GET_WHO_AM_I}, {"get_who_am_i", CommandType::GET_WHO_AM_I},
}; };
std::function<CommandResult()> CommandManager::createCommand(const CommandType type, std::string_view json) const std::function<CommandResult()> CommandManager::createCommand(const CommandType type, const nlohmann::json &json) const
{ {
switch (type) switch (type)
{ {
@@ -39,8 +38,6 @@ std::function<CommandResult()> CommandManager::createCommand(const CommandType t
case CommandType::PAUSE: case CommandType::PAUSE:
return [json] return [json]
{ return PauseCommand(json); }; { return PauseCommand(json); };
return [json]
{ return PauseCommand(json); };
case CommandType::UPDATE_OTA_CREDENTIALS: case CommandType::UPDATE_OTA_CREDENTIALS:
return [this, json] return [this, json]
{ return updateOTACredentialsCommand(this->registry, json); }; { return updateOTACredentialsCommand(this->registry, json); };
@@ -65,9 +62,6 @@ std::function<CommandResult()> CommandManager::createCommand(const CommandType t
case CommandType::UPDATE_CAMERA: case CommandType::UPDATE_CAMERA:
return [this, json] return [this, json]
{ return updateCameraCommand(this->registry, json); }; { return updateCameraCommand(this->registry, json); };
case CommandType::RESTART_CAMERA:
return [this, json]
{ return restartCameraCommand(this->registry, json); };
case CommandType::GET_CONFIG: case CommandType::GET_CONFIG:
return [this] return [this]
{ return getConfigCommand(this->registry); }; { return getConfigCommand(this->registry); };
@@ -116,70 +110,56 @@ std::function<CommandResult()> CommandManager::createCommand(const CommandType t
} }
} }
CommandResult CommandManager::executeFromJson(const std::string_view json) const CommandManagerResponse CommandManager::executeFromJson(const std::string_view json) const
{ {
cJSON *parsedJson = cJSON_Parse(json.data()); if (!nlohmann::json::accept(json))
if (parsedJson == nullptr)
return CommandResult::getErrorResult("Initial JSON Parse: Invalid JSON");
const cJSON *commandData = nullptr;
const cJSON *commands = cJSON_GetObjectItem(parsedJson, "commands");
cJSON *responseDocument = cJSON_CreateObject();
cJSON *responses = cJSON_CreateArray();
cJSON *response = nullptr;
cJSON_AddItemToObject(responseDocument, "results", responses);
if (cJSON_GetArraySize(commands) == 0)
{ {
cJSON_Delete(parsedJson); return CommandManagerResponse(nlohmann::json{{"error", "Initial JSON Parse - Invalid JSON"}});
return CommandResult::getErrorResult("Commands missing");
} }
cJSON_ArrayForEach(commandData, commands) nlohmann::json parsedJson = nlohmann::json::parse(json);
if (!parsedJson.contains("commands") || !parsedJson["commands"].is_array() || parsedJson["commands"].empty())
{ {
const cJSON *commandTypeString = cJSON_GetObjectItem(commandData, "command"); return CommandManagerResponse(CommandResult::getErrorResult("Commands missing"));
if (commandTypeString == nullptr)
{
return CommandResult::getErrorResult("Unknown command - missing command type");
}
const cJSON *commandPayload = cJSON_GetObjectItem(commandData, "data");
const auto commandType = commandTypeMap.at(std::string(commandTypeString->valuestring));
std::string commandPayloadString;
if (commandPayload != nullptr)
commandPayloadString = std::string(cJSON_Print(commandPayload));
auto command = createCommand(commandType, commandPayloadString);
if (command == nullptr)
{
cJSON_Delete(parsedJson);
return CommandResult::getErrorResult("Unknown command");
}
response = cJSON_CreateString(command().getResult().c_str());
cJSON_AddItemToArray(responses, response);
} }
char *jsonString = cJSON_Print(responseDocument); nlohmann::json results = nlohmann::json::array();
cJSON_Delete(responseDocument);
cJSON_Delete(parsedJson);
// Return the JSON response directly without wrapping it for (auto &commandObject : parsedJson["commands"].items())
// The responseDocument already contains the proper format: {"results": [...]} {
CommandResult result = CommandResult::getRawJsonResult(jsonString); auto commandData = commandObject.value();
free(jsonString); if (!commandData.contains("command"))
return result; {
return CommandManagerResponse({{"command", "Unknown command"}, {"error", "Missing command type"}});
}
const auto commandName = commandData["command"].get<std::string>();
if (!commandTypeMap.contains(commandName))
{
return CommandManagerResponse({{"command", commandName}, {"error", "Unknown command"}});
}
const auto commandType = commandTypeMap.at(commandName);
const auto commandPayload = commandData.contains("data") ? commandData["data"] : nlohmann::json::object();
auto command = createCommand(commandType, commandPayload);
results.push_back({
{"command", commandName},
{"result", command()},
});
}
auto response = nlohmann::json{{"results", results}};
return CommandManagerResponse(response);
} }
CommandResult CommandManager::executeFromType(const CommandType type, const std::string_view json) const CommandManagerResponse CommandManager::executeFromType(const CommandType type, const std::string_view json) const
{ {
const auto command = createCommand(type, json); const auto command = createCommand(type, json);
if (command == nullptr) if (command == nullptr)
{ {
return CommandResult::getErrorResult("Unknown command"); return CommandManagerResponse({{"command", type}, {"error", "Unknown command"}});
} }
return command(); return CommandManagerResponse({"result", command()});
} }

View File

@@ -18,7 +18,7 @@
#include "commands/wifi_commands.hpp" #include "commands/wifi_commands.hpp"
#include "commands/device_commands.hpp" #include "commands/device_commands.hpp"
#include "commands/scan_commands.hpp" #include "commands/scan_commands.hpp"
#include <cJSON.h> #include <nlohmann-json.hpp>
enum class CommandType enum class CommandType
{ {
@@ -33,7 +33,6 @@ enum class CommandType
SET_MDNS, SET_MDNS,
GET_MDNS_NAME, GET_MDNS_NAME,
UPDATE_CAMERA, UPDATE_CAMERA,
RESTART_CAMERA,
SAVE_CONFIG, SAVE_CONFIG,
GET_CONFIG, GET_CONFIG,
RESET_CONFIG, RESET_CONFIG,
@@ -57,10 +56,10 @@ class CommandManager
public: public:
explicit CommandManager(const std::shared_ptr<DependencyRegistry> &DependencyRegistry) : registry(DependencyRegistry) {}; explicit CommandManager(const std::shared_ptr<DependencyRegistry> &DependencyRegistry) : registry(DependencyRegistry) {};
std::function<CommandResult()> createCommand(CommandType type, std::string_view json) const; std::function<CommandResult()> createCommand(const CommandType type, const nlohmann::json &json) const;
CommandResult executeFromJson(std::string_view json) const; CommandManagerResponse executeFromJson(std::string_view json) const;
CommandResult executeFromType(CommandType type, std::string_view json) const; CommandManagerResponse executeFromType(CommandType type, std::string_view json) const;
}; };
#endif #endif

View File

@@ -0,0 +1,24 @@
#include "CommandResult.hpp"
void to_json(nlohmann::json &j, const CommandResult &result)
{
j = nlohmann::json{{"status", result.isSuccess() ? "success" : "error"}, {"data", result.getData()}};
}
// defined only for interface compatibility, should not be used directly
void from_json(const nlohmann::json &j, CommandResult &result)
{
auto message = j.at("message");
j.at("status") == "success" ? result = CommandResult::getSuccessResult(message) : result = CommandResult::getErrorResult(message);
}
void to_json(nlohmann::json &j, const CommandManagerResponse &result)
{
j = result.getData();
}
// defined only for interface compatibility, should not be used directly
void from_json(const nlohmann::json &j, CommandManagerResponse &result)
{
result = CommandManagerResponse(j.at("result"));
}

View File

@@ -1,9 +1,13 @@
#pragma once
#ifndef COMMAND_RESULT #ifndef COMMAND_RESULT
#define COMMAND_RESULT #define COMMAND_RESULT
#include <format> #include <format>
#include <string> #include <string>
#include <algorithm> #include <algorithm>
#include <nlohmann-json.hpp>
using json = nlohmann::json;
class CommandResult class CommandResult
{ {
@@ -15,60 +19,41 @@ public:
}; };
private: private:
nlohmann::json data;
Status status; Status status;
std::string message;
public: public:
CommandResult(std::string message, const Status status) CommandResult(nlohmann::json data, const Status status) : data(data), status(status) {}
{
this->status = status;
// Escape quotes and backslashes in the message for JSON
std::string escapedMessage = message;
size_t pos = 0;
// First escape backslashes
while ((pos = escapedMessage.find('\\', pos)) != std::string::npos) {
escapedMessage.replace(pos, 1, "\\\\");
pos += 2;
}
// Then escape quotes
pos = 0;
while ((pos = escapedMessage.find('"', pos)) != std::string::npos) {
escapedMessage.replace(pos, 1, "\\\"");
pos += 2;
}
if (status == Status::SUCCESS)
{
this->message = std::format("{{\"result\":\"{}\"}}", escapedMessage);
}
else
{
this->message = std::format("{{\"error\":\"{}\"}}", escapedMessage);
}
}
bool isSuccess() const { return status == Status::SUCCESS; } bool isSuccess() const { return status == Status::SUCCESS; }
static CommandResult getSuccessResult(const std::string &message) static CommandResult getSuccessResult(nlohmann::json message)
{ {
return CommandResult(message, Status::SUCCESS); return CommandResult(message, Status::SUCCESS);
} }
static CommandResult getErrorResult(const std::string &message) static CommandResult getErrorResult(nlohmann::json message)
{ {
return CommandResult(message, Status::FAILURE); return CommandResult(message, Status::FAILURE);
} }
// Create a result that returns raw JSON without wrapper nlohmann::json getData() const { return this->data; }
static CommandResult getRawJsonResult(const std::string &jsonMessage)
{
CommandResult result("", Status::SUCCESS);
result.message = jsonMessage;
return result;
}
std::string getResult() const { return this->message; }
}; };
void to_json(nlohmann::json &j, const CommandResult &result);
void from_json(const nlohmann::json &j, CommandResult &result);
class CommandManagerResponse
{
private:
nlohmann::json data;
public:
CommandManagerResponse(nlohmann::json data) : data(data) {}
nlohmann::json getData() const { return this->data; }
};
void to_json(nlohmann::json &j, const CommandManagerResponse &result);
void from_json(const nlohmann::json &j, CommandManagerResponse &result);
#endif #endif

View File

@@ -0,0 +1,81 @@
#include "CommandSchema.hpp"
void to_json(nlohmann::json &j, const UpdateWifiPayload &payload)
{
j = nlohmann::json{{"name", payload.name}, {"ssid", payload.ssid}, {"password", payload.password}, {"channel", payload.channel}, {"power", payload.power}};
}
void from_json(const nlohmann::json &j, UpdateWifiPayload &payload)
{
payload.name = j.at("name").get<std::string>();
if (j.contains("ssid"))
{
payload.ssid = j.at("ssid").get<std::string>();
}
if (j.contains("password"))
{
payload.password = j.at("password").get<std::string>();
}
if (j.contains("channel"))
{
payload.channel = j.at("channel").get<uint8_t>();
}
if (j.contains("power"))
{
payload.power = j.at("power").get<uint8_t>();
}
}
void to_json(nlohmann::json &j, const UpdateAPWiFiPayload &payload)
{
j = nlohmann::json{{"ssid", payload.ssid}, {"password", payload.password}, {"channel", payload.channel}};
}
void from_json(const nlohmann::json &j, UpdateAPWiFiPayload &payload)
{
if (j.contains("ssid"))
{
payload.ssid = j.at("ssid").get<std::string>();
}
if (j.contains("password"))
{
payload.password = j.at("password").get<std::string>();
}
if (j.contains("channel"))
{
payload.channel = j.at("channel").get<uint8_t>();
}
}
void to_json(nlohmann::json &j, const UpdateCameraConfigPayload &payload)
{
j = nlohmann::json{{"vflip", payload.vflip}, {"href", payload.href}, {"framesize", payload.framesize}, {"quality", payload.quality}, {"brightness", payload.brightness}};
}
void from_json(const nlohmann::json &j, UpdateCameraConfigPayload &payload)
{
if (j.contains("vflip"))
{
payload.vflip = j.at("vflip").get<uint8_t>();
}
if (j.contains("href"))
{
payload.href = j.at("href").get<uint8_t>();
}
if (j.contains("framesize"))
{
payload.framesize = j.at("framesize").get<uint8_t>();
}
if (j.contains("quality"))
{
payload.quality = j.at("quality").get<uint8_t>();
}
if (j.contains("brightness"))
{
payload.brightness = j.at("brightness").get<uint8_t>();
}
}

View File

@@ -1,31 +1,40 @@
#ifndef COMMAND_SCHEMA_HPP #ifndef COMMAND_SCHEMA_HPP
#define COMMAND_SCHEMA_HPP #define COMMAND_SCHEMA_HPP
#include <nlohmann-json.hpp>
struct BasePayload {}; struct BasePayload
{
};
struct WifiPayload : BasePayload struct WifiPayload : BasePayload
{ {
std::string networkName; std::string name;
std::string ssid; std::string ssid;
std::string password; std::string password;
uint8_t channel; uint8_t channel;
uint8_t power; uint8_t power;
}; };
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(WifiPayload, name, ssid, password, channel, power)
struct UpdateWifiPayload : BasePayload struct UpdateWifiPayload : BasePayload
{ {
std::string networkName; std::string name;
std::optional<std::string> ssid; std::optional<std::string> ssid;
std::optional<std::string> password; std::optional<std::string> password;
std::optional<uint8_t> channel; std::optional<uint8_t> channel;
std::optional<uint8_t> power; std::optional<uint8_t> power;
}; };
void to_json(nlohmann::json &j, const UpdateWifiPayload &payload);
void from_json(const nlohmann::json &j, UpdateWifiPayload &payload);
struct deleteNetworkPayload : BasePayload struct deleteNetworkPayload : BasePayload
{ {
std::string networkName; std::string name;
}; };
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(deleteNetworkPayload, name)
struct UpdateAPWiFiPayload : BasePayload struct UpdateAPWiFiPayload : BasePayload
{ {
std::optional<std::string> ssid; std::optional<std::string> ssid;
@@ -33,11 +42,15 @@ struct UpdateAPWiFiPayload : BasePayload
std::optional<uint8_t> channel; std::optional<uint8_t> channel;
}; };
void to_json(nlohmann::json &j, const UpdateAPWiFiPayload &payload);
void from_json(const nlohmann::json &j, UpdateAPWiFiPayload &payload);
struct MDNSPayload : BasePayload struct MDNSPayload : BasePayload
{ {
std::string hostname; std::string hostname;
}; };
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(MDNSPayload, hostname)
struct UpdateCameraConfigPayload : BasePayload struct UpdateCameraConfigPayload : BasePayload
{ {
std::optional<uint8_t> vflip; std::optional<uint8_t> vflip;
@@ -48,18 +61,6 @@ struct UpdateCameraConfigPayload : BasePayload
// TODO add more options here // TODO add more options here
}; };
struct ResetConfigPayload : BasePayload void to_json(nlohmann::json &j, const UpdateCameraConfigPayload &payload);
{ void from_json(const nlohmann::json &j, UpdateCameraConfigPayload &payload);
std::string section;
};
struct RestartCameraPayload : BasePayload
{
bool mode;
};
struct PausePayload : BasePayload
{
bool pause;
};
#endif #endif

View File

@@ -1,86 +1,17 @@
#include "camera_commands.hpp" #include "camera_commands.hpp"
std::optional<UpdateCameraConfigPayload> parseUpdateCameraPayload(std::string_view jsonPayload) CommandResult updateCameraCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
{ {
UpdateCameraConfigPayload payload; auto payload = json.get<UpdateCameraConfigPayload>();
cJSON *parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr)
return std::nullopt;
cJSON *vflipObject = cJSON_GetObjectItem(parsedJson, "vflip");
cJSON *hrefObject = cJSON_GetObjectItem(parsedJson, "href");
cJSON *framesize = cJSON_GetObjectItem(parsedJson, "framesize");
cJSON *quality = cJSON_GetObjectItem(parsedJson, "quality");
cJSON *brightness = cJSON_GetObjectItem(parsedJson, "brightness");
if (vflipObject != nullptr)
payload.vflip = vflipObject->valueint;
if (hrefObject != nullptr)
payload.href = hrefObject->valueint;
if (framesize != nullptr)
payload.framesize = framesize->valueint;
if (quality != nullptr)
payload.quality = quality->valueint;
if (brightness != nullptr)
payload.brightness = brightness->valueint;
cJSON_Delete(parsedJson);
return payload;
}
std::optional<RestartCameraPayload> parseRestartCameraPayload(std::string_view jsonPayload)
{
RestartCameraPayload payload;
cJSON *parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr)
return std::nullopt;
cJSON *mode = cJSON_GetObjectItem(parsedJson, "mode");
if (mode == nullptr)
{
cJSON_Delete(parsedJson);
}
payload.mode = (bool)mode->valueint;
cJSON_Delete(parsedJson);
return payload;
}
CommandResult updateCameraCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
{
auto payload = parseUpdateCameraPayload(jsonPayload);
if (!payload.has_value())
{
return CommandResult::getErrorResult("Invalid payload");
}
auto updatedConfig = payload.value();
std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config); std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
auto oldConfig = projectConfig->getCameraConfig(); auto oldConfig = projectConfig->getCameraConfig();
projectConfig->setCameraConfig( projectConfig->setCameraConfig(
updatedConfig.vflip.has_value() ? updatedConfig.vflip.value() : oldConfig.vflip, payload.vflip.has_value() ? payload.vflip.value() : oldConfig.vflip,
updatedConfig.framesize.has_value() ? updatedConfig.framesize.value() : oldConfig.framesize, payload.framesize.has_value() ? payload.framesize.value() : oldConfig.framesize,
updatedConfig.href.has_value() ? updatedConfig.href.value() : oldConfig.href, payload.href.has_value() ? payload.href.value() : oldConfig.href,
updatedConfig.quality.has_value() ? updatedConfig.quality.value() : oldConfig.quality, payload.quality.has_value() ? payload.quality.value() : oldConfig.quality,
updatedConfig.brightness.has_value() ? updatedConfig.brightness.value() : oldConfig.brightness); payload.brightness.has_value() ? payload.brightness.value() : oldConfig.brightness);
return CommandResult::getSuccessResult("Config updated"); return CommandResult::getSuccessResult("Config updated");
} }
CommandResult restartCameraCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
{
auto payload = parseRestartCameraPayload(jsonPayload);
if (!payload.has_value())
{
return CommandResult::getErrorResult("Invalid payload");
}
std::shared_ptr<CameraManager> cameraManager = registry->resolve<CameraManager>(DependencyType::camera_manager);
cameraManager->resetCamera(payload.value().mode);
return CommandResult::getSuccessResult("Camera restarted");
}

View File

@@ -4,17 +4,14 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <optional> #include <optional>
#include <cJSON.h>
#include "CommandResult.hpp" #include "CommandResult.hpp"
#include "CommandSchema.hpp" #include "CommandSchema.hpp"
#include "DependencyRegistry.hpp" #include "DependencyRegistry.hpp"
#include <CameraManager.hpp> #include <CameraManager.hpp>
#include <nlohmann-json.hpp>
std::optional<UpdateCameraConfigPayload> parseUpdateCameraPayload(std::string_view jsonPayload); CommandResult updateCameraCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
CommandResult updateCameraCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
std::optional<RestartCameraPayload> parseRestartCameraPayload(std::string_view jsonPayload);
CommandResult restartCameraCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
#endif #endif
// add cropping command // add cropping command

View File

@@ -1,27 +1,5 @@
#include "config_commands.hpp" #include "config_commands.hpp"
std::optional<ResetConfigPayload> parseResetConfigCommandPayload(std::string_view jsonPayload)
{
ResetConfigPayload payload;
cJSON *parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr)
return std::nullopt;
cJSON *section = cJSON_GetObjectItem(parsedJson, "section");
if (section == nullptr)
{
cJSON_Delete(parsedJson);
return std::nullopt;
}
payload.section = std::string(section->valuestring);
cJSON_Delete(parsedJson);
return payload;
}
CommandResult saveConfigCommand(std::shared_ptr<DependencyRegistry> registry) CommandResult saveConfigCommand(std::shared_ptr<DependencyRegistry> registry)
{ {
std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config); std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
@@ -38,29 +16,27 @@ CommandResult getConfigCommand(std::shared_ptr<DependencyRegistry> registry)
return CommandResult::getSuccessResult(configRepresentation); return CommandResult::getSuccessResult(configRepresentation);
} }
CommandResult resetConfigCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) CommandResult resetConfigCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
{ {
std::array<std::string, 4> supported_sections = { std::array<std::string, 4> supported_sections = {
"all", "all",
}; };
auto payload = parseResetConfigCommandPayload(jsonPayload); if (!json.contains("section"))
if (!payload.has_value())
{ {
return CommandResult::getErrorResult("Invalid payload or missing section"); return CommandResult::getErrorResult("Invalid payload - missing section");
} }
auto sectionPayload = payload.value(); auto section = json["section"].get<std::string>();
if (std::find(supported_sections.begin(), supported_sections.end(), sectionPayload.section) == supported_sections.end()) if (std::find(supported_sections.begin(), supported_sections.end(), section) == supported_sections.end())
{ {
return CommandResult::getErrorResult("Selected section is unsupported"); return CommandResult::getErrorResult("Selected section is unsupported");
} }
// we cannot match on string, and making a map would be overkill right now, sooo // we cannot match on string, and making a map would be overkill right now, sooo
// todo, add more granual control for other sections, like only reset camera, or only reset wifi // todo, add more granular control for other sections, like only reset camera, or only reset wifi
if (sectionPayload.section == "all") if (section == "all")
{ {
std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config); std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
projectConfig->reset(); projectConfig->reset();

View File

@@ -2,13 +2,12 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <optional> #include <optional>
#include <cJSON.h>
#include "CommandResult.hpp" #include "CommandResult.hpp"
#include "CommandSchema.hpp" #include "CommandSchema.hpp"
#include "DependencyRegistry.hpp" #include "DependencyRegistry.hpp"
#include <nlohmann-json.hpp>
CommandResult saveConfigCommand(std::shared_ptr<DependencyRegistry> registry); CommandResult saveConfigCommand(std::shared_ptr<DependencyRegistry> registry);
CommandResult getConfigCommand(std::shared_ptr<DependencyRegistry> registry); CommandResult getConfigCommand(std::shared_ptr<DependencyRegistry> registry);
std::optional<ResetConfigPayload> parseresetConfigCommandPayload(std::string_view jsonPayload); CommandResult resetConfigCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
CommandResult resetConfigCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);

View File

@@ -4,23 +4,14 @@
#include "esp_mac.h" #include "esp_mac.h"
#include <cstdio> #include <cstdio>
// Implementation inspired by SummerSigh work, initial PR opened in openiris repo, adapted to this rewrite CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
{ {
const auto parsedJson = cJSON_Parse(jsonPayload.data()); if (!json.contains("mode") || !json["mode"].is_number_integer())
if (parsedJson == nullptr)
{ {
return CommandResult::getErrorResult("Invalid payload"); return CommandResult::getErrorResult("Invalid payload - missing or unsupported mode");
} }
const auto modeObject = cJSON_GetObjectItem(parsedJson, "mode"); const auto mode = json["mode"].get<int>();
if (modeObject == nullptr)
{
return CommandResult::getErrorResult("Invalid payload - missing mode");
}
const auto mode = modeObject->valueint;
if (mode < 0 || mode > 2) if (mode < 0 || mode > 2)
{ {
return CommandResult::getErrorResult("Invalid payload - unsupported mode"); return CommandResult::getErrorResult("Invalid payload - unsupported mode");
@@ -29,19 +20,11 @@ CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry,
const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config); const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
projectConfig->setDeviceMode(static_cast<StreamingMode>(mode)); projectConfig->setDeviceMode(static_cast<StreamingMode>(mode));
cJSON_Delete(parsedJson);
return CommandResult::getSuccessResult("Device mode set"); return CommandResult::getSuccessResult("Device mode set");
} }
CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
{ {
const auto parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr)
{
return CommandResult::getErrorResult("Invalid payload");
}
const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config); const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
const auto oldDeviceConfig = projectConfig->getDeviceConfig(); const auto oldDeviceConfig = projectConfig->getDeviceConfig();
@@ -49,48 +32,39 @@ CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> re
auto OTAPassword = oldDeviceConfig.OTAPassword; auto OTAPassword = oldDeviceConfig.OTAPassword;
auto OTAPort = oldDeviceConfig.OTAPort; auto OTAPort = oldDeviceConfig.OTAPort;
if (const auto OTALoginObject = cJSON_GetObjectItem(parsedJson, "login"); OTALoginObject != nullptr) if (json.contains("login") && json["login"].is_string())
{ {
if (const auto newLogin = OTALoginObject->valuestring; strcmp(newLogin, "") != 0) if (const auto newLogin = json["login"].get<std::string>(); strcmp(newLogin.c_str(), "") != 0)
{ {
OTALogin = newLogin; OTALogin = newLogin;
} }
} }
if (const auto OTAPasswordObject = cJSON_GetObjectItem(parsedJson, "password"); OTAPasswordObject != nullptr) if (json.contains("password") && json["password"].is_string())
{ {
OTAPassword = OTAPasswordObject->valuestring; OTAPassword = json["password"].get<std::string>();
} }
if (const auto OTAPortObject = cJSON_GetObjectItem(parsedJson, "port"); OTAPortObject != nullptr) if (json.contains("port") && json["port"].is_number_integer())
{ {
if (const auto newPort = OTAPortObject->valueint; newPort >= 82) if (const auto newPort = json["port"].get<int>(); newPort >= 82)
{ {
OTAPort = newPort; OTAPort = newPort;
} }
} }
cJSON_Delete(parsedJson);
projectConfig->setOTAConfig(OTALogin, OTAPassword, OTAPort); projectConfig->setOTAConfig(OTALogin, OTAPassword, OTAPort);
return CommandResult::getSuccessResult("OTA Config set"); return CommandResult::getSuccessResult("OTA Config set");
} }
CommandResult updateLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) CommandResult updateLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
{ {
const auto parsedJson = cJSON_Parse(jsonPayload.data()); if (!json.contains("dutyCycle") || !json["dutyCycle"].is_number_integer())
if (parsedJson == nullptr)
{
return CommandResult::getErrorResult("Invalid payload");
}
const auto dutyCycleObject = cJSON_GetObjectItem(parsedJson, "dutyCycle");
if (dutyCycleObject == nullptr)
{ {
return CommandResult::getErrorResult("Invalid payload - missing dutyCycle"); return CommandResult::getErrorResult("Invalid payload - missing dutyCycle");
} }
const auto dutyCycle = dutyCycleObject->valueint; const auto dutyCycle = json["dutyCycle"].get<int>();
if (dutyCycle < 0 || dutyCycle > 100) if (dutyCycle < 0 || dutyCycle > 100)
{ {
@@ -107,8 +81,6 @@ CommandResult updateLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> regi
ledMgr->setExternalLEDDutyCycle(static_cast<uint8_t>(dutyCycle)); ledMgr->setExternalLEDDutyCycle(static_cast<uint8_t>(dutyCycle));
} }
cJSON_Delete(parsedJson);
return CommandResult::getSuccessResult("LED duty cycle set"); return CommandResult::getSuccessResult("LED duty cycle set");
} }
@@ -145,34 +117,28 @@ CommandResult startStreamingCommand()
return CommandResult::getSuccessResult("Streaming starting"); return CommandResult::getSuccessResult("Streaming starting");
} }
CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
{ {
const auto parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr)
{
return CommandResult::getErrorResult("Invalid payload");
}
const auto modeObject = cJSON_GetObjectItem(parsedJson, "mode"); if (!json.contains("mode") || !json["mode"].is_string())
if (modeObject == nullptr)
{ {
return CommandResult::getErrorResult("Invalid payload - missing mode"); return CommandResult::getErrorResult("Invalid payload - missing mode");
} }
const char *modeStr = modeObject->valuestring; auto modeStr = json["mode"].get<std::string>();
StreamingMode newMode; StreamingMode newMode;
ESP_LOGI("[DEVICE_COMMANDS]", "Switch mode command received with mode: %s", modeStr); ESP_LOGI("[DEVICE_COMMANDS]", "Switch mode command received with mode: %s", modeStr.c_str());
if (strcmp(modeStr, "uvc") == 0) if (modeStr == "uvc")
{ {
newMode = StreamingMode::UVC; newMode = StreamingMode::UVC;
} }
else if (strcmp(modeStr, "wifi") == 0) else if (modeStr == "wifi")
{ {
newMode = StreamingMode::WIFI; newMode = StreamingMode::WIFI;
} }
else if (strcmp(modeStr, "setup") == 0 || strcmp(modeStr, "auto") == 0) else if (modeStr == "setup" || modeStr == "auto"))
{ {
newMode = StreamingMode::SETUP; newMode = StreamingMode::SETUP;
} }
@@ -185,8 +151,6 @@ CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, st
ESP_LOGI("[DEVICE_COMMANDS]", "Setting device mode to: %d", (int)newMode); ESP_LOGI("[DEVICE_COMMANDS]", "Setting device mode to: %d", (int)newMode);
projectConfig->setDeviceMode(newMode); projectConfig->setDeviceMode(newMode);
cJSON_Delete(parsedJson);
return CommandResult::getSuccessResult("Device mode switched, restart to apply"); return CommandResult::getSuccessResult("Device mode switched, restart to apply");
} }

View File

@@ -3,22 +3,22 @@
#include "OpenIrisTasks.hpp" #include "OpenIrisTasks.hpp"
#include "DependencyRegistry.hpp" #include "DependencyRegistry.hpp"
#include "esp_timer.h" #include "esp_timer.h"
#include "cJSON.h"
#include "main_globals.hpp" #include "main_globals.hpp"
#include <format> #include <format>
#include <string> #include <string>
#include <nlohmann-json.hpp>
CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload); CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
CommandResult updateLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload); CommandResult updateLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
CommandResult getLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registry); CommandResult getLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registry);
CommandResult restartDeviceCommand(); CommandResult restartDeviceCommand();
CommandResult startStreamingCommand(); CommandResult startStreamingCommand();
CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload); CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
CommandResult getDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry); CommandResult getDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry);

View File

@@ -1,33 +1,13 @@
#include "mdns_commands.hpp" #include "mdns_commands.hpp"
std::optional<MDNSPayload> parseMDNSCommandPayload(std::string_view jsonPayload) CommandResult setMDNSCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
{ {
MDNSPayload payload; const auto payload = json.get<MDNSPayload>();
cJSON *parsedJson = cJSON_Parse(jsonPayload.data()); if (payload.hostname.empty())
if (parsedJson == nullptr) return CommandResult::getErrorResult("Invalid payload - empty hostname");
return std::nullopt;
cJSON *hostnameObject = cJSON_GetObjectItem(parsedJson, "hostname");
if (hostnameObject == nullptr)
{
cJSON_Delete(parsedJson);
return std::nullopt;
}
payload.hostname = std::string(hostnameObject->valuestring);
cJSON_Delete(parsedJson);
return payload;
}
CommandResult setMDNSCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
{
auto payload = parseMDNSCommandPayload(jsonPayload);
if (!payload.has_value())
return CommandResult::getErrorResult("Invalid payload");
std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config); std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
projectConfig->setMDNSConfig(payload.value().hostname); projectConfig->setMDNSConfig(payload.hostname);
return CommandResult::getSuccessResult("Config updated"); return CommandResult::getSuccessResult("Config updated");
} }

View File

@@ -2,11 +2,10 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <optional> #include <optional>
#include <cJSON.h>
#include "CommandResult.hpp" #include "CommandResult.hpp"
#include "CommandSchema.hpp" #include "CommandSchema.hpp"
#include "DependencyRegistry.hpp" #include "DependencyRegistry.hpp"
#include <nlohmann-json.hpp>
std::optional<MDNSPayload> parseMDNSCommandPayload(std::string_view jsonPayload); CommandResult setMDNSCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
CommandResult setMDNSCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
CommandResult getMDNSNameCommand(std::shared_ptr<DependencyRegistry> registry); CommandResult getMDNSNameCommand(std::shared_ptr<DependencyRegistry> registry);

View File

@@ -14,29 +14,24 @@ CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry)
auto networks = wifiManager->ScanNetworks(); auto networks = wifiManager->ScanNetworks();
cJSON *root = cJSON_CreateObject(); nlohmann::json result;
cJSON *networksArray = cJSON_CreateArray(); std::vector<nlohmann::json> networksJson;
cJSON_AddItemToObject(root, "networks", networksArray);
for (const auto &network : networks) for (const auto &network : networks)
{ {
cJSON *networkObject = cJSON_CreateObject(); nlohmann::json networkItem;
cJSON_AddStringToObject(networkObject, "ssid", network.ssid.c_str()); networkItem["ssid"] = network.ssid;
cJSON_AddNumberToObject(networkObject, "channel", network.channel); networkItem["channel"] = network.channel;
cJSON_AddNumberToObject(networkObject, "rssi", network.rssi); networkItem["rssi"] = network.rssi;
char mac_str[18]; char mac_str[18];
sprintf(mac_str, "%02x:%02x:%02x:%02x:%02x:%02x", sprintf(mac_str, "%02x:%02x:%02x:%02x:%02x:%02x",
network.mac[0], network.mac[1], network.mac[2], network.mac[0], network.mac[1], network.mac[2],
network.mac[3], network.mac[4], network.mac[5]); network.mac[3], network.mac[4], network.mac[5]);
cJSON_AddStringToObject(networkObject, "mac_address", mac_str); networkItem["mac_address"] = mac_str;
cJSON_AddNumberToObject(networkObject, "auth_mode", network.auth_mode); networkItem["auth_mode"] = network.auth_mode;
cJSON_AddItemToArray(networksArray, networkObject); networksJson.push_back(networkItem);
} }
char *json_string = cJSON_PrintUnformatted(root); result["networks"] = networksJson;
printf("%s\n", json_string); return CommandResult::getSuccessResult(result);
cJSON_Delete(root);
free(json_string);
return CommandResult::getSuccessResult("Networks scanned");
} }

View File

@@ -4,9 +4,9 @@
#include "CommandResult.hpp" #include "CommandResult.hpp"
#include "DependencyRegistry.hpp" #include "DependencyRegistry.hpp"
#include "esp_log.h" #include "esp_log.h"
#include <cJSON.h>
#include <wifiManager.hpp> #include <wifiManager.hpp>
#include <string> #include <string>
#include <nlohmann-json.hpp>
CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry); CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry);

View File

@@ -7,27 +7,19 @@ CommandResult PingCommand()
return CommandResult::getSuccessResult("pong"); return CommandResult::getSuccessResult("pong");
}; };
CommandResult PauseCommand(std::string_view jsonPayload) CommandResult PauseCommand(const nlohmann::json &json)
{ {
PausePayload payload; auto pause = true;
// pause by default if this command gets executed, even if the payload was invalid
payload.pause = true;
cJSON *root = cJSON_Parse(std::string(jsonPayload).c_str()); if (json.contains("pause") && json["pause"].is_boolean())
if (root)
{ {
cJSON *pauseItem = cJSON_GetObjectItem(root, "pause"); pause = json["pause"].get<bool>();
if (pauseItem && cJSON_IsBool(pauseItem))
{
payload.pause = cJSON_IsTrue(pauseItem);
}
cJSON_Delete(root);
} }
ESP_LOGI(TAG, "Pause command received: %s", payload.pause ? "true" : "false"); ESP_LOGI(TAG, "Pause command received: %s", pause ? "true" : "false");
setStartupPaused(payload.pause); setStartupPaused(pause);
if (payload.pause) if (pause)
{ {
ESP_LOGI(TAG, "Startup paused - device will remain in configuration mode"); ESP_LOGI(TAG, "Startup paused - device will remain in configuration mode");
return CommandResult::getSuccessResult("Startup paused"); return CommandResult::getSuccessResult("Startup paused");

View File

@@ -3,12 +3,11 @@
#include <string> #include <string>
#include "CommandResult.hpp" #include "CommandResult.hpp"
#include "CommandSchema.hpp"
#include "main_globals.hpp" #include "main_globals.hpp"
#include "esp_log.h" #include "esp_log.h"
#include <cJSON.h> #include <nlohmann-json.hpp>
CommandResult PingCommand(); CommandResult PingCommand();
CommandResult PauseCommand(std::string_view jsonPayload); CommandResult PauseCommand(const nlohmann::json &json);
#endif #endif

View File

@@ -2,212 +2,76 @@
#include "esp_netif.h" #include "esp_netif.h"
#include "sdkconfig.h" #include "sdkconfig.h"
std::optional<WifiPayload> parseSetWiFiCommandPayload(std::string_view jsonPayload) CommandResult setWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
{
WifiPayload payload;
cJSON *parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr)
return std::nullopt;
const cJSON *networkName = cJSON_GetObjectItem(parsedJson, "name");
const cJSON *ssidPtr = cJSON_GetObjectItem(parsedJson, "ssid");
const cJSON *passwordPtr = cJSON_GetObjectItem(parsedJson, "password");
const cJSON *channelPtr = cJSON_GetObjectItem(parsedJson, "channel");
const cJSON *powerPtr = cJSON_GetObjectItem(parsedJson, "power");
if (ssidPtr == nullptr)
{
// without an SSID we can't do anything
cJSON_Delete(parsedJson);
return std::nullopt;
}
if (networkName == nullptr)
payload.networkName = "main";
else
payload.networkName = std::string(networkName->valuestring);
payload.ssid = std::string(ssidPtr->valuestring);
if (passwordPtr == nullptr)
payload.password = "";
else
payload.password = std::string(passwordPtr->valuestring);
if (channelPtr == nullptr)
payload.channel = 0;
else
payload.channel = channelPtr->valueint;
if (powerPtr == nullptr)
payload.power = 0;
else
payload.power = powerPtr->valueint;
cJSON_Delete(parsedJson);
return payload;
}
std::optional<deleteNetworkPayload> parseDeleteWifiCommandPayload(std::string_view jsonPayload)
{
deleteNetworkPayload payload;
auto *parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr)
return std::nullopt;
const auto *networkName = cJSON_GetObjectItem(parsedJson, "name");
if (networkName == nullptr)
{
cJSON_Delete(parsedJson);
return std::nullopt;
}
payload.networkName = std::string(networkName->valuestring);
cJSON_Delete(parsedJson);
return payload;
}
std::optional<UpdateWifiPayload> parseUpdateWifiCommandPayload(std::string_view jsonPayload)
{
UpdateWifiPayload payload;
cJSON *parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr)
return std::nullopt;
const cJSON *networkName = cJSON_GetObjectItem(parsedJson, "name");
if (networkName == nullptr)
{
cJSON_Delete(parsedJson);
return std::nullopt;
}
payload.networkName = std::string(networkName->valuestring);
const auto *ssidObject = cJSON_GetObjectItem(parsedJson, "ssid");
const auto *passwordObject = cJSON_GetObjectItem(parsedJson, "password");
const auto *channelObject = cJSON_GetObjectItem(parsedJson, "channel");
const auto *powerObject = cJSON_GetObjectItem(parsedJson, "power");
if (ssidObject != nullptr && (strcmp(ssidObject->valuestring, "") == 0))
{
// we need ssid to actually connect
cJSON_Delete(parsedJson);
return std::nullopt;
}
if (ssidObject != nullptr)
payload.ssid = std::string(ssidObject->valuestring);
if (passwordObject != nullptr)
payload.password = std::string(passwordObject->valuestring);
if (channelObject != nullptr)
payload.channel = channelObject->valueint;
if (powerObject != nullptr)
payload.power = powerObject->valueint;
cJSON_Delete(parsedJson);
return payload;
}
std::optional<UpdateAPWiFiPayload> parseUpdateAPWiFiCommandPayload(const std::string_view jsonPayload)
{
UpdateAPWiFiPayload payload;
cJSON *parsedJson = cJSON_Parse(jsonPayload.data());
if (parsedJson == nullptr)
{
return std::nullopt;
}
const cJSON *ssidObject = cJSON_GetObjectItem(parsedJson, "ssid");
const cJSON *passwordObject = cJSON_GetObjectItem(parsedJson, "password");
const cJSON *channelObject = cJSON_GetObjectItem(parsedJson, "channel");
if (ssidObject != nullptr)
payload.ssid = std::string(ssidObject->valuestring);
if (passwordObject != nullptr)
payload.password = std::string(passwordObject->valuestring);
if (channelObject != nullptr)
payload.channel = channelObject->valueint;
cJSON_Delete(parsedJson);
return payload;
}
CommandResult setWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
{ {
#if !CONFIG_GENERAL_ENABLE_WIRELESS #if !CONFIG_GENERAL_ENABLE_WIRELESS
return CommandResult::getErrorResult("Not supported by current firmware"); return CommandResult::getErrorResult("Not supported by current firmware");
#endif #endif
const auto payload = parseSetWiFiCommandPayload(jsonPayload);
if (!payload.has_value()) auto payload = json.get<WifiPayload>();
if (payload.name.empty())
{ {
return CommandResult::getErrorResult("Invalid payload"); payload.name = std::string("main");
}
if (payload.ssid.empty())
{
return CommandResult::getErrorResult("Invalid payload: missing SSID");
} }
const auto wifiConfig = payload.value();
std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config); std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
projectConfig->setWifiConfig( projectConfig->setWifiConfig(
wifiConfig.networkName, payload.name,
wifiConfig.ssid, payload.ssid,
wifiConfig.password, payload.password,
wifiConfig.channel, payload.channel,
wifiConfig.power); payload.power);
return CommandResult::getSuccessResult("Config updated"); return CommandResult::getSuccessResult("Config updated");
} }
CommandResult deleteWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) CommandResult deleteWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
{ {
#if !CONFIG_GENERAL_ENABLE_WIRELESS #if !CONFIG_GENERAL_ENABLE_WIRELESS
return CommandResult::getErrorResult("Not supported by current firmware"); return CommandResult::getErrorResult("Not supported by current firmware");
#endif #endif
const auto payload = parseDeleteWifiCommandPayload(jsonPayload);
if (!payload.has_value()) const auto payload = json.get<deleteNetworkPayload>();
if (payload.name.empty())
return CommandResult::getErrorResult("Invalid payload"); return CommandResult::getErrorResult("Invalid payload");
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config); auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
projectConfig->deleteWifiConfig(payload.value().networkName); projectConfig->deleteWifiConfig(payload.name);
return CommandResult::getSuccessResult("Config updated"); return CommandResult::getSuccessResult("Config updated");
} }
CommandResult updateWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) CommandResult updateWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
{ {
#if !CONFIG_GENERAL_ENABLE_WIRELESS #if !CONFIG_GENERAL_ENABLE_WIRELESS
return CommandResult::getErrorResult("Not supported by current firmware"); return CommandResult::getErrorResult("Not supported by current firmware");
#endif #endif
const auto payload = parseUpdateWifiCommandPayload(jsonPayload);
if (!payload.has_value())
{
return CommandResult::getErrorResult("Invalid payload");
}
auto updatedConfig = payload.value(); auto payload = json.get<UpdateWifiPayload>();
if (payload.name.empty())
{
return CommandResult::getErrorResult("Invalid payload - missing network name");
}
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config); auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
auto storedNetworks = projectConfig->getWifiConfigs(); auto storedNetworks = projectConfig->getWifiConfigs();
if (const auto networkToUpdate = std::ranges::find_if( if (const auto networkToUpdate = std::ranges::find_if(
storedNetworks, storedNetworks,
[&](auto &network){ return network.name == updatedConfig.networkName; } [&](auto &network)
); { return network.name == payload.name; });
networkToUpdate != storedNetworks.end()) networkToUpdate != storedNetworks.end())
{ {
projectConfig->setWifiConfig( projectConfig->setWifiConfig(
updatedConfig.networkName, payload.name,
updatedConfig.ssid.has_value() ? updatedConfig.ssid.value() : networkToUpdate->ssid, payload.ssid.has_value() ? payload.ssid.value() : networkToUpdate->ssid,
updatedConfig.password.has_value() ? updatedConfig.password.value() : networkToUpdate->password, payload.password.has_value() ? payload.password.value() : networkToUpdate->password,
updatedConfig.channel.has_value() ? updatedConfig.channel.value() : networkToUpdate->channel, payload.channel.has_value() ? payload.channel.value() : networkToUpdate->channel,
updatedConfig.power.has_value() ? updatedConfig.power.value() : networkToUpdate->power); payload.power.has_value() ? payload.power.value() : networkToUpdate->power);
return CommandResult::getSuccessResult("Config updated"); return CommandResult::getSuccessResult("Config updated");
} }
@@ -215,113 +79,99 @@ CommandResult updateWiFiCommand(std::shared_ptr<DependencyRegistry> registry, st
return CommandResult::getErrorResult("Requested network does not exist"); return CommandResult::getErrorResult("Requested network does not exist");
} }
CommandResult updateAPWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) CommandResult updateAPWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
{ {
#if !CONFIG_GENERAL_ENABLE_WIRELESS #if !CONFIG_GENERAL_ENABLE_WIRELESS
return CommandResult::getErrorResult("Not supported by current firmware"); return CommandResult::getErrorResult("Not supported by current firmware");
#endif #endif
const auto payload = parseUpdateAPWiFiCommandPayload(jsonPayload);
if (!payload.has_value()) const auto payload = json.get<UpdateAPWiFiPayload>();
return CommandResult::getErrorResult("Invalid payload");
auto updatedConfig = payload.value();
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config); auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
const auto previousAPConfig = projectConfig->getAPWifiConfig(); const auto previousAPConfig = projectConfig->getAPWifiConfig();
projectConfig->setAPWifiConfig( projectConfig->setAPWifiConfig(
updatedConfig.ssid.has_value() ? updatedConfig.ssid.value() : previousAPConfig.ssid, payload.ssid.has_value() ? payload.ssid.value() : previousAPConfig.ssid,
updatedConfig.password.has_value() ? updatedConfig.password.value() : previousAPConfig.password, payload.password.has_value() ? payload.password.value() : previousAPConfig.password,
updatedConfig.channel.has_value() ? updatedConfig.channel.value() : previousAPConfig.channel); payload.channel.has_value() ? payload.channel.value() : previousAPConfig.channel);
return CommandResult::getSuccessResult("Config updated"); return CommandResult::getSuccessResult("Config updated");
} }
CommandResult getWiFiStatusCommand(std::shared_ptr<DependencyRegistry> registry) { CommandResult getWiFiStatusCommand(std::shared_ptr<DependencyRegistry> registry)
{
#if !CONFIG_GENERAL_ENABLE_WIRELESS #if !CONFIG_GENERAL_ENABLE_WIRELESS
return CommandResult::getErrorResult("Not supported by current firmware"); return CommandResult::getErrorResult("Not supported by current firmware");
#endif #endif
auto wifiManager = registry->resolve<WiFiManager>(DependencyType::wifi_manager); auto wifiManager = registry->resolve<WiFiManager>(DependencyType::wifi_manager);
if (!wifiManager) { auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
return CommandResult::getErrorResult("Not supported by current firmware");
auto wifiState = wifiManager->GetCurrentWiFiState();
auto networks = projectConfig->getWifiConfigs();
nlohmann::json result;
switch (wifiState)
{
case WiFiState_e::WiFiState_NotInitialized:
result["status"] = "not_initialized";
break;
case WiFiState_e::WiFiState_Initialized:
result["status"] = "initialized";
break;
case WiFiState_e::WiFiState_ReadyToConnect:
result["status"] = "ready";
break;
case WiFiState_e::WiFiState_Connecting:
result["status"] = "connecting";
break;
case WiFiState_e::WiFiState_WaitingForIp:
result["status"] = "waiting_for_ip";
break;
case WiFiState_e::WiFiState_Connected:
result["status"] = "connected";
break;
case WiFiState_e::WiFiState_Disconnected:
result["status"] = "disconnected";
break;
case WiFiState_e::WiFiState_Error:
result["status"] = "error";
break;
} }
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
// Get current WiFi state if (wifiState == WiFiState_e::WiFiState_Connected)
auto wifiState = wifiManager->GetCurrentWiFiState(); {
auto networks = projectConfig->getWifiConfigs(); // Get IP address from ESP32
esp_netif_ip_info_t ip_info;
cJSON* statusJson = cJSON_CreateObject(); esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (netif && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK)
// Add WiFi state {
const char* stateStr = "unknown"; char ip_str[16];
switch(wifiState) { sprintf(ip_str, IPSTR, IP2STR(&ip_info.ip));
case WiFiState_e::WiFiState_NotInitialized: result["ip_address"] = ip_str;
stateStr = "not_initialized";
break;
case WiFiState_e::WiFiState_Initialized:
stateStr = "initialized";
break;
case WiFiState_e::WiFiState_ReadyToConnect:
stateStr = "ready";
break;
case WiFiState_e::WiFiState_Connecting:
stateStr = "connecting";
break;
case WiFiState_e::WiFiState_WaitingForIp:
stateStr = "waiting_for_ip";
break;
case WiFiState_e::WiFiState_Connected:
stateStr = "connected";
break;
case WiFiState_e::WiFiState_Disconnected:
stateStr = "disconnected";
break;
case WiFiState_e::WiFiState_Error:
stateStr = "error";
break;
} }
cJSON_AddStringToObject(statusJson, "status", stateStr); }
cJSON_AddNumberToObject(statusJson, "networks_configured", networks.size());
// Add IP info if connected return CommandResult::getSuccessResult(result);
if (wifiState == WiFiState_e::WiFiState_Connected) {
// Get IP address from ESP32
esp_netif_ip_info_t ip_info;
esp_netif_t* netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (netif && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) {
char ip_str[16];
sprintf(ip_str, IPSTR, IP2STR(&ip_info.ip));
cJSON_AddStringToObject(statusJson, "ip_address", ip_str);
}
}
char* statusString = cJSON_PrintUnformatted(statusJson);
std::string result(statusString);
free(statusString);
cJSON_Delete(statusJson);
return CommandResult::getSuccessResult(result);
} }
CommandResult connectWiFiCommand(std::shared_ptr<DependencyRegistry> registry) { CommandResult connectWiFiCommand(std::shared_ptr<DependencyRegistry> registry)
{
#if !CONFIG_GENERAL_ENABLE_WIRELESS #if !CONFIG_GENERAL_ENABLE_WIRELESS
return CommandResult::getErrorResult("Not supported by current firmware"); return CommandResult::getErrorResult("Not supported by current firmware");
#endif #endif
auto wifiManager = registry->resolve<WiFiManager>(DependencyType::wifi_manager); auto wifiManager = registry->resolve<WiFiManager>(DependencyType::wifi_manager);
if (!wifiManager) { auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
return CommandResult::getErrorResult("Not supported by current firmware");
auto networks = projectConfig->getWifiConfigs();
if (networks.empty())
{
return CommandResult::getErrorResult("No WiFi networks configured");
} }
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
auto networks = projectConfig->getWifiConfigs(); // Trigger WiFi connection attempt
if (networks.empty()) { wifiManager->TryConnectToStoredNetworks();
return CommandResult::getErrorResult("No WiFi networks configured");
}
// Trigger WiFi connection attempt return CommandResult::getSuccessResult("WiFi connection attempt started");
wifiManager->TryConnectToStoredNetworks();
return CommandResult::getSuccessResult("WiFi connection attempt started");
} }

View File

@@ -4,22 +4,18 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <optional> #include <optional>
#include <cJSON.h>
#include "CommandResult.hpp" #include "CommandResult.hpp"
#include "CommandSchema.hpp" #include "CommandSchema.hpp"
#include "DependencyRegistry.hpp" #include "DependencyRegistry.hpp"
#include <nlohmann-json.hpp>
std::optional<WifiPayload> parseSetWiFiCommandPayload(std::string_view jsonPayload); CommandResult setWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
CommandResult setWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
std::optional<deleteNetworkPayload> parseDeleteWifiCommandPayload(std::string_view jsonPayload); CommandResult deleteWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
CommandResult deleteWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
std::optional<UpdateWifiPayload> parseUpdateWifiCommandPayload(std::string_view jsonPayload); CommandResult updateWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
CommandResult updateWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
std::optional<UpdateAPWiFiPayload> parseUpdateAPWiFiCommandPayload(std::string_view jsonPayload); CommandResult updateAPWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
CommandResult updateAPWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
CommandResult getWiFiStatusCommand(std::shared_ptr<DependencyRegistry> registry); CommandResult getWiFiStatusCommand(std::shared_ptr<DependencyRegistry> registry);
CommandResult connectWiFiCommand(std::shared_ptr<DependencyRegistry> registry); CommandResult connectWiFiCommand(std::shared_ptr<DependencyRegistry> registry);

View File

@@ -4,6 +4,19 @@
#define POST_METHOD "POST" #define POST_METHOD "POST"
bool getIsSuccess(const nlohmann::json &response)
{
// since the commandManager will be returning CommandManagerResponse to simplify parsing on the clients end
// we can slightly its json representation, and extract the status from there
// note: This will only work for commands executed with CommandManager::executeFromType().
if (!response.contains("result"))
{
return false;
}
return response.at("result").at("status").get<std::string>() == "success";
}
RestAPI::RestAPI(std::string url, std::shared_ptr<CommandManager> commandManager) : command_manager(commandManager) RestAPI::RestAPI(std::string url, std::shared_ptr<CommandManager> commandManager) : command_manager(commandManager)
{ {
this->url = std::move(url); this->url = std::move(url);
@@ -84,66 +97,73 @@ void HandleRestAPIPollTask(void *pvParameter)
// COMMANDS // COMMANDS
// updates // updates
void RestAPI::handle_update_wifi(RequestContext *context) { void RestAPI::handle_update_wifi(RequestContext *context)
{
if (context->method != POST_METHOD) if (context->method != POST_METHOD)
{ {
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed"); mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
return; return;
} }
auto const result = command_manager->executeFromType(CommandType::UPDATE_WIFI, context->body); const nlohmann::json result = command_manager->executeFromType(CommandType::UPDATE_WIFI, context->body);
auto const code = result.isSuccess() ? 200 : 500; const auto code = getIsSuccess(result) ? 200 : 400;
mg_http_reply(context->connection, code, JSON_RESPONSE, result.getResult().c_str()); mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
} }
void RestAPI::handle_update_device(RequestContext *context) { void RestAPI::handle_update_device(RequestContext *context)
{
if (context->method != POST_METHOD) if (context->method != POST_METHOD)
{ {
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed"); mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
return; return;
} }
auto const result = command_manager->executeFromType(CommandType::UPDATE_OTA_CREDENTIALS, context->body); const nlohmann::json result = command_manager->executeFromType(CommandType::UPDATE_OTA_CREDENTIALS, context->body);
auto const code = result.isSuccess() ? 200 : 500; const auto code = getIsSuccess(result) ? 200 : 500;
mg_http_reply(context->connection, code, JSON_RESPONSE, result.getResult().c_str()); mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
} }
void RestAPI::handle_update_camera(RequestContext *context) { void RestAPI::handle_update_camera(RequestContext *context)
{
if (context->method != POST_METHOD) if (context->method != POST_METHOD)
{ {
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed"); mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
return; return;
} }
auto const result = command_manager->executeFromType(CommandType::UPDATE_CAMERA, context->body); const nlohmann::json result = command_manager->executeFromType(CommandType::UPDATE_CAMERA, context->body);
auto const code = result.isSuccess() ? 200 : 500; const auto code = getIsSuccess(result) ? 200 : 500;
mg_http_reply(context->connection, code, JSON_RESPONSE, result.getResult().c_str()); mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
} }
// gets // gets
void RestAPI::handle_get_config(RequestContext *context) { void RestAPI::handle_get_config(RequestContext *context)
{
auto const result = this->command_manager->executeFromType(CommandType::GET_CONFIG, ""); auto const result = this->command_manager->executeFromType(CommandType::GET_CONFIG, "");
mg_http_reply(context->connection, 200, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), result.getResult()); const nlohmann::json jsonResult = result;
mg_http_reply(context->connection, 200, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), jsonResult.dump().c_str());
} }
// resets // resets
void RestAPI::handle_reset_config(RequestContext *context) { void RestAPI::handle_reset_config(RequestContext *context)
{
if (context->method != POST_METHOD) if (context->method != POST_METHOD)
{ {
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed"); mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
return; return;
} }
auto const result = this->command_manager->executeFromType(CommandType::RESET_CONFIG, "{\"section\": \"all\"}"); const nlohmann::json result = this->command_manager->executeFromType(CommandType::RESET_CONFIG, "{\"section\": \"all\"}");
auto const code = result.isSuccess() ? 200 : 500; const auto code = getIsSuccess(result) ? 200 : 500;
mg_http_reply(context->connection, code, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), result.getResult()); mg_http_reply(context->connection, code, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), result.dump().c_str());
} }
// reboots // reboots
void RestAPI::handle_reboot(RequestContext *context) { void RestAPI::handle_reboot(RequestContext *context)
auto const result = this->command_manager->executeFromType(CommandType::RESTART_DEVICE, ""); {
const auto result = this->command_manager->executeFromType(CommandType::RESTART_DEVICE, "");
mg_http_reply(context->connection, 200, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), "Ok"); mg_http_reply(context->connection, 200, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), "Ok");
} }
@@ -154,16 +174,18 @@ void RestAPI::handle_camera_reboot(RequestContext *context)
// heartbeat // heartbeat
void RestAPI::pong(RequestContext *context) { void RestAPI::pong(RequestContext *context)
auto const result = this->command_manager->executeFromType(CommandType::PING, ""); {
auto const code = result.isSuccess() ? 200 : 500; const nlohmann::json result = this->command_manager->executeFromType(CommandType::PING, "");
mg_http_reply(context->connection, code, JSON_RESPONSE, result.getResult().c_str()); const auto code = getIsSuccess(result) ? 200 : 500;
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
} }
// special // special
void RestAPI::handle_save(RequestContext *context) { void RestAPI::handle_save(RequestContext *context)
auto const result = this->command_manager->executeFromType(CommandType::SAVE_CONFIG, ""); {
auto const code = result.isSuccess() ? 200 : 500; const nlohmann::json result = this->command_manager->executeFromType(CommandType::SAVE_CONFIG, "");
mg_http_reply(context->connection, code, JSON_RESPONSE, result.getResult().c_str()); const auto code = getIsSuccess(result) ? 200 : 500;
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
} }

View File

@@ -50,14 +50,24 @@ void SerialManager::try_receive()
data[current_position] = '\0'; data[current_position] = '\0';
current_position = 0; current_position = 0;
const auto result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data))); const nlohmann::json result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data)));
const auto resultMessage = result.getResult(); const auto resultMessage = result.dump();
int written = usb_serial_jtag_write_bytes(resultMessage.c_str(), resultMessage.length(), 1000 / 20); usb_serial_jtag_write_bytes_chunked(resultMessage.c_str(), resultMessage.length(), 1000 / 20);
(void)written; // ignore errors if driver already uninstalled
} }
} }
} }
void SerialManager::usb_serial_jtag_write_bytes_chunked(const char *data, size_t len, size_t timeout)
{
while (len > 0)
{
auto to_write = len > BUF_SIZE ? BUF_SIZE : len;
auto written = usb_serial_jtag_write_bytes(data, to_write, timeout);
data += written;
len -= written;
}
}
// Function to notify that a command was received during startup // Function to notify that a command was received during startup
void SerialManager::notify_startup_command_received() void SerialManager::notify_startup_command_received()
{ {
@@ -171,8 +181,8 @@ void HandleCDCSerialManagerTask(void *pvParameters)
if (idx >= BUF_SIZE || buffer[idx - 1] == '\n' || buffer[idx - 1] == '\r') if (idx >= BUF_SIZE || buffer[idx - 1] == '\n' || buffer[idx - 1] == '\r')
{ {
buffer[idx - 1] = '\0'; buffer[idx - 1] = '\0';
const auto result = commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(buffer))); const nlohmann::json result = commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(buffer)));
const auto resultMessage = result.getResult(); const auto resultMessage = result.dump();
tud_cdc_write(resultMessage.c_str(), resultMessage.length()); tud_cdc_write(resultMessage.c_str(), resultMessage.length());
tud_cdc_write_flush(); tud_cdc_write_flush();
idx = 0; idx = 0;

View File

@@ -41,6 +41,8 @@ public:
void shutdown(); void shutdown();
private: private:
void usb_serial_jtag_write_bytes_chunked(const char *data, size_t len, size_t timeout);
std::shared_ptr<CommandManager> commandManager; std::shared_ptr<CommandManager> commandManager;
esp_timer_handle_t *timerHandle; esp_timer_handle_t *timerHandle;
std::shared_ptr<ProjectConfig> deviceConfig; std::shared_ptr<ProjectConfig> deviceConfig;