mirror of
https://github.com/MrUnknownDE/OpenIris-ESPIDF.git
synced 2026-04-08 17:33:48 +02:00
Merge pull request #17 from lorow/feature/replace-json-libs
Removed heartbeats in favor of sending a ping command from time to time on the PC side Reworked the response structure - both in code and in JSON - we're now getting a proper JSON resposne that's easier to parse, handling them in code now also becomes a bit easier. Removed unused reset camera command Reworked how scanned networks get returned Reworked how the response gets returned - no more raw printf, instead we're getting chunked serial response with proper content
This commit is contained in:
43
README.md
43
README.md
@@ -8,10 +8,11 @@ Firmware and tools for OpenIris — Wi‑Fi, UVC streaming, and a Python setup C
|
||||
---
|
||||
|
||||
## What’s inside
|
||||
|
||||
- ESP‑IDF firmware (C/C++) with modules for Camera, Wi‑Fi, UVC, REST/Serial commands, and more
|
||||
- Python tools for setup over USB serial:
|
||||
- `tools/switchBoardType.py` — choose a board profile (builds the right sdkconfig)
|
||||
- `tools/openiris_setup.py` — interactive CLI for Wi‑Fi, MDNS/Name, Mode, LED PWM, Logs, and a Settings Summary
|
||||
- `tools/setup_openiris.py` — interactive CLI for Wi‑Fi, MDNS/Name, Mode, LED PWM, Logs, and a Settings Summary
|
||||
- Composite USB (UVC + CDC) when UVC mode is enabled (`GENERAL_INCLUDE_UVC_MODE`) for simultaneous video streaming and command channel
|
||||
- LED current monitoring (if enabled via `MONITORING_LED_CURRENT`) with filtered mA readings
|
||||
- Configurable debug LED + external IR LED control with optional error mirroring (`LED_DEBUG_ENABLE`, `LED_EXTERNAL_AS_DEBUG`)
|
||||
@@ -22,29 +23,36 @@ Firmware and tools for OpenIris — Wi‑Fi, UVC streaming, and a Python setup C
|
||||
---
|
||||
|
||||
## First-time setup on Windows (VS Code + ESP‑IDF extension)
|
||||
|
||||
If you’re starting fresh on Windows, this workflow is smooth and reliable:
|
||||
|
||||
1) Install tooling
|
||||
1. Install tooling
|
||||
|
||||
- Git: https://git-scm.com/downloads/win
|
||||
- Visual Studio Code: https://code.visualstudio.com/
|
||||
|
||||
2) Get the source code
|
||||
2. Get the source code
|
||||
|
||||
- Create a folder where you want the repo (e.g., `D:\OpenIris-ESPIDF\`). In File Explorer, right‑click the folder and choose “Open in Terminal”.
|
||||
- Clone and open in VS Code:
|
||||
|
||||
```cmd
|
||||
git clone https://github.com/lorow/OpenIris-ESPIDF.git
|
||||
cd OpenIris-ESPIDF
|
||||
code .
|
||||
```
|
||||
|
||||
3) Install the ESP‑IDF VS Code extension
|
||||
3. Install the ESP‑IDF VS Code extension
|
||||
|
||||
- In VS Code, open the Extensions tab and install: https://marketplace.visualstudio.com/items?itemName=espressif.esp-idf-extension
|
||||
|
||||
4) Set the default terminal profile to Command Prompt
|
||||
4. Set the default terminal profile to Command Prompt
|
||||
|
||||
- Press Ctrl+Shift+P → search “Terminal: Select Default Profile” → choose “Command Prompt”.
|
||||
- Restart VS Code from its normal shortcut (not from Git Bash). This avoids running ESP‑IDF in the wrong shell.
|
||||
|
||||
5) Configure ESP‑IDF in the extension
|
||||
5. Configure ESP‑IDF in the extension
|
||||
|
||||
- On first launch, the extension may prompt to install ESP‑IDF and tools — follow the steps. It can take a while.
|
||||
- If you see the extension’s home page instead, click “Configure extension”, pick “EXPRESS”, choose “GitHub” as the server and version “v5.4.2”.
|
||||
- Then open the ESP‑IDF Explorer tab and click “Open ESP‑IDF Terminal”. We’ll use that for builds.
|
||||
@@ -59,11 +67,14 @@ After this, you’re ready for the Quick start below.
|
||||
Boards are auto‑discovered from the `boards/` directory. First list them, then pick one:
|
||||
|
||||
Windows (cmd):
|
||||
|
||||
```cmd
|
||||
python .\tools\switchBoardType.py --list
|
||||
python .\tools\switchBoardType.py --board seed_studio_xiao_esp32s3 --diff
|
||||
```
|
||||
|
||||
macOS/Linux (bash):
|
||||
|
||||
```bash
|
||||
python3 ./tools/switchBoardType.py --list
|
||||
python3 ./tools/switchBoardType.py --board seed_studio_xiao_esp32s3 --diff
|
||||
@@ -75,28 +86,35 @@ Notes:
|
||||
- You can also pass partial or path‑like inputs (e.g. `facefocusvr/eye_L`), the tool normalizes them.
|
||||
|
||||
### 2) Build & flash
|
||||
|
||||
- Set the target (e.g., ESP32‑S3).
|
||||
- Build, flash, and open the serial monitor.
|
||||
- (Optional) For UVC mode ensure `GENERAL_INCLUDE_UVC_MODE=y`. If you want device to boot directly into UVC: also set `START_IN_UVC_MODE=y`.
|
||||
- Disable Wi‑Fi services for pure wired builds: `GENERAL_ENABLE_WIRELESS=n`.
|
||||
|
||||
### 3) Use the Python setup CLI (recommended)
|
||||
|
||||
Configure the device over USB serial.
|
||||
|
||||
Before you run it:
|
||||
|
||||
- If you still have the serial monitor open, close it (the port must be free).
|
||||
- In VS Code, open the sidebar “ESP‑IDF: Explorer” and click “Open ESP‑IDF Terminal”. We’ll run the CLI there so Python and packages are in the right environment.
|
||||
|
||||
Then run:
|
||||
|
||||
```cmd
|
||||
python .\tools\openiris_setup.py --port COMxx
|
||||
python .\tools\setup_openiris.py --port COMxx
|
||||
```
|
||||
|
||||
Examples:
|
||||
- Windows: `python .\tools\openiris_setup.py --port COM69`, …
|
||||
|
||||
- Windows: `python .\tools\setup_openiris.py --port COM69`, …
|
||||
- macOS: idk
|
||||
- Linux: idk
|
||||
|
||||
What the CLI can do:
|
||||
|
||||
- Wi‑Fi menu: automatic (scan → pick → password → connect → wait for IP) or manual (scan, show, configure, connect, status)
|
||||
- Set MDNS/Device name (also used for the UVC device name)
|
||||
- Switch mode (Wi‑Fi / UVC / Setup)
|
||||
@@ -107,6 +125,7 @@ What the CLI can do:
|
||||
---
|
||||
|
||||
## Serial number & MAC
|
||||
|
||||
- Internally, the serial number is derived from the Wi‑Fi MAC address.
|
||||
- The CLI displays the MAC by default (clearer); it’s the value used as the serial number.
|
||||
- The UVC device name is based on the MDNS hostname.
|
||||
@@ -121,6 +140,7 @@ Runtime override: If the setup CLI (or a JSON command) provides a new device nam
|
||||
---
|
||||
|
||||
## Common workflows
|
||||
|
||||
- Fast Wi‑Fi setup: in the CLI, go to “Wi‑Fi settings” → “Automatic setup”, then check “status”.
|
||||
- Change name/MDNS: set the device name in the CLI, then replug USB — UVC will show the new name.
|
||||
- Adjust brightness/LED: set LED PWM in the CLI.
|
||||
@@ -132,6 +152,7 @@ Runtime override: If the setup CLI (or a JSON command) provides a new device nam
|
||||
---
|
||||
|
||||
## Project layout (short)
|
||||
|
||||
- `main/` — entry point
|
||||
- `components/` — modules (Camera, WiFi, UVC, CommandManager, …)
|
||||
- `tools/` — Python helper tools (board switch, setup CLI, scanner)
|
||||
@@ -140,8 +161,10 @@ If you want to dig deeper: commands are mapped via the `CommandManager` under `c
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### USB Composite (UVC + CDC)
|
||||
When UVC support is compiled in the device enumerates as a composite USB device:
|
||||
When UVC support is compiled in, the device enumerates as a composite USB device:
|
||||
- UVC interface: video streaming (JPEG frames)
|
||||
- CDC (virtual COM): command channel accepting newline‑terminated JSON objects
|
||||
|
||||
@@ -209,4 +232,4 @@ The firmware uses a small set of LED patterns to indicate status and blocking er
|
||||
|
||||
---
|
||||
|
||||
Feedback, issues, and PRs are welcome.
|
||||
Feedback, issues, and PRs are welcome.
|
||||
|
||||
@@ -255,28 +255,4 @@ int CameraManager::setVieWindow(int offsetX,
|
||||
|
||||
// todo safariMonkey made a PoC, implement it here
|
||||
return 0;
|
||||
}
|
||||
|
||||
//! either hardware(1) or software(0)
|
||||
void CameraManager::resetCamera(bool type)
|
||||
{
|
||||
// TODO add camera reset
|
||||
|
||||
// if (type)
|
||||
// {
|
||||
// // power cycle the camera module (handy if camera stops responding)
|
||||
// digitalWrite(PWDN_GPIO_NUM, HIGH); // turn power off to camera module
|
||||
// Network_Utilities::my_delay(0.3); // a for loop with a delay of 300ms
|
||||
// digitalWrite(PWDN_GPIO_NUM, LOW);
|
||||
// Network_Utilities::my_delay(0.3);
|
||||
// setupCamera();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // reset via software (handy if you wish to change resolution or image type
|
||||
// // etc. - see test procedure)
|
||||
// esp_camera_deinit();
|
||||
// Network_Utilities::my_delay(0.05);
|
||||
// setupCamera();
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ public:
|
||||
int setVFlip(int direction);
|
||||
int setHFlip(int direction);
|
||||
int setVieWindow(int offsetX, int offsetY, int outputX, int outputY);
|
||||
void resetCamera(bool type);
|
||||
|
||||
private:
|
||||
void loadConfigData();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"CommandManager/CommandManager.cpp"
|
||||
"CommandManager/CommandResult.cpp"
|
||||
"CommandManager/CommandSchema.cpp"
|
||||
"CommandManager/commands/simple_commands.cpp"
|
||||
"CommandManager/commands/camera_commands.cpp"
|
||||
"CommandManager/commands/wifi_commands.cpp"
|
||||
@@ -11,5 +13,5 @@ idf_component_register(
|
||||
INCLUDE_DIRS
|
||||
"CommandManager"
|
||||
"CommandManager/commands"
|
||||
REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers LEDManager Monitoring
|
||||
REQUIRES ProjectConfig nlohmann-json CameraManager OpenIrisTasks wifiManager Helpers LEDManager Monitoring
|
||||
)
|
||||
@@ -12,7 +12,6 @@ std::unordered_map<std::string, CommandType> commandTypeMap = {
|
||||
{"set_mdns", CommandType::SET_MDNS},
|
||||
{"get_mdns_name", CommandType::GET_MDNS_NAME},
|
||||
{"update_camera", CommandType::UPDATE_CAMERA},
|
||||
{"restart_camera", CommandType::RESTART_CAMERA},
|
||||
{"save_config", CommandType::SAVE_CONFIG},
|
||||
{"get_config", CommandType::GET_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},
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -39,8 +38,6 @@ std::function<CommandResult()> CommandManager::createCommand(const CommandType t
|
||||
case CommandType::PAUSE:
|
||||
return [json]
|
||||
{ return PauseCommand(json); };
|
||||
return [json]
|
||||
{ return PauseCommand(json); };
|
||||
case CommandType::UPDATE_OTA_CREDENTIALS:
|
||||
return [this, json]
|
||||
{ return updateOTACredentialsCommand(this->registry, json); };
|
||||
@@ -65,9 +62,6 @@ std::function<CommandResult()> CommandManager::createCommand(const CommandType t
|
||||
case CommandType::UPDATE_CAMERA:
|
||||
return [this, json]
|
||||
{ return updateCameraCommand(this->registry, json); };
|
||||
case CommandType::RESTART_CAMERA:
|
||||
return [this, json]
|
||||
{ return restartCameraCommand(this->registry, json); };
|
||||
case CommandType::GET_CONFIG:
|
||||
return [this]
|
||||
{ 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 (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)
|
||||
if (!nlohmann::json::accept(json))
|
||||
{
|
||||
cJSON_Delete(parsedJson);
|
||||
return CommandResult::getErrorResult("Commands missing");
|
||||
return CommandManagerResponse(nlohmann::json{{"error", "Initial JSON Parse - Invalid JSON"}});
|
||||
}
|
||||
|
||||
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");
|
||||
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);
|
||||
return CommandManagerResponse(CommandResult::getErrorResult("Commands missing"));
|
||||
}
|
||||
|
||||
char *jsonString = cJSON_Print(responseDocument);
|
||||
cJSON_Delete(responseDocument);
|
||||
cJSON_Delete(parsedJson);
|
||||
nlohmann::json results = nlohmann::json::array();
|
||||
|
||||
// Return the JSON response directly without wrapping it
|
||||
// The responseDocument already contains the proper format: {"results": [...]}
|
||||
CommandResult result = CommandResult::getRawJsonResult(jsonString);
|
||||
free(jsonString);
|
||||
return result;
|
||||
for (auto &commandObject : parsedJson["commands"].items())
|
||||
{
|
||||
auto commandData = commandObject.value();
|
||||
if (!commandData.contains("command"))
|
||||
{
|
||||
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);
|
||||
|
||||
if (command == nullptr)
|
||||
{
|
||||
return CommandResult::getErrorResult("Unknown command");
|
||||
return CommandManagerResponse({{"command", type}, {"error", "Unknown command"}});
|
||||
}
|
||||
|
||||
return command();
|
||||
return CommandManagerResponse({"result", command()});
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
#include "commands/wifi_commands.hpp"
|
||||
#include "commands/device_commands.hpp"
|
||||
#include "commands/scan_commands.hpp"
|
||||
#include <cJSON.h>
|
||||
#include <nlohmann-json.hpp>
|
||||
|
||||
enum class CommandType
|
||||
{
|
||||
@@ -33,7 +33,6 @@ enum class CommandType
|
||||
SET_MDNS,
|
||||
GET_MDNS_NAME,
|
||||
UPDATE_CAMERA,
|
||||
RESTART_CAMERA,
|
||||
SAVE_CONFIG,
|
||||
GET_CONFIG,
|
||||
RESET_CONFIG,
|
||||
@@ -57,10 +56,10 @@ class CommandManager
|
||||
|
||||
public:
|
||||
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;
|
||||
CommandResult executeFromType(CommandType type, std::string_view json) const;
|
||||
CommandManagerResponse executeFromJson(std::string_view json) const;
|
||||
CommandManagerResponse executeFromType(CommandType type, std::string_view json) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
24
components/CommandManager/CommandManager/CommandResult.cpp
Normal file
24
components/CommandManager/CommandManager/CommandResult.cpp
Normal 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"));
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
#pragma once
|
||||
#ifndef COMMAND_RESULT
|
||||
#define COMMAND_RESULT
|
||||
|
||||
#include <format>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <nlohmann-json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
class CommandResult
|
||||
{
|
||||
@@ -15,60 +19,41 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
nlohmann::json data;
|
||||
Status status;
|
||||
std::string message;
|
||||
|
||||
public:
|
||||
CommandResult(std::string message, const 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);
|
||||
}
|
||||
}
|
||||
CommandResult(nlohmann::json data, const Status status) : data(data), status(status) {}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static CommandResult getErrorResult(const std::string &message)
|
||||
static CommandResult getErrorResult(nlohmann::json message)
|
||||
{
|
||||
return CommandResult(message, Status::FAILURE);
|
||||
}
|
||||
|
||||
// Create a result that returns raw JSON without wrapper
|
||||
static CommandResult getRawJsonResult(const std::string &jsonMessage)
|
||||
{
|
||||
CommandResult result("", Status::SUCCESS);
|
||||
result.message = jsonMessage;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string getResult() const { return this->message; }
|
||||
nlohmann::json getData() const { return this->data; }
|
||||
};
|
||||
|
||||
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
|
||||
81
components/CommandManager/CommandManager/CommandSchema.cpp
Normal file
81
components/CommandManager/CommandManager/CommandSchema.cpp
Normal 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>();
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,40 @@
|
||||
#ifndef COMMAND_SCHEMA_HPP
|
||||
#define COMMAND_SCHEMA_HPP
|
||||
#include <nlohmann-json.hpp>
|
||||
|
||||
struct BasePayload {};
|
||||
struct BasePayload
|
||||
{
|
||||
};
|
||||
|
||||
struct WifiPayload : BasePayload
|
||||
{
|
||||
std::string networkName;
|
||||
std::string name;
|
||||
std::string ssid;
|
||||
std::string password;
|
||||
uint8_t channel;
|
||||
uint8_t power;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(WifiPayload, name, ssid, password, channel, power)
|
||||
|
||||
struct UpdateWifiPayload : BasePayload
|
||||
{
|
||||
std::string networkName;
|
||||
std::string name;
|
||||
std::optional<std::string> ssid;
|
||||
std::optional<std::string> password;
|
||||
std::optional<uint8_t> channel;
|
||||
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
|
||||
{
|
||||
std::string networkName;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(deleteNetworkPayload, name)
|
||||
|
||||
struct UpdateAPWiFiPayload : BasePayload
|
||||
{
|
||||
std::optional<std::string> ssid;
|
||||
@@ -33,11 +42,15 @@ struct UpdateAPWiFiPayload : BasePayload
|
||||
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
|
||||
{
|
||||
std::string hostname;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(MDNSPayload, hostname)
|
||||
|
||||
struct UpdateCameraConfigPayload : BasePayload
|
||||
{
|
||||
std::optional<uint8_t> vflip;
|
||||
@@ -48,18 +61,6 @@ struct UpdateCameraConfigPayload : BasePayload
|
||||
// TODO add more options here
|
||||
};
|
||||
|
||||
struct ResetConfigPayload : BasePayload
|
||||
{
|
||||
std::string section;
|
||||
};
|
||||
|
||||
struct RestartCameraPayload : BasePayload
|
||||
{
|
||||
bool mode;
|
||||
};
|
||||
|
||||
struct PausePayload : BasePayload
|
||||
{
|
||||
bool pause;
|
||||
};
|
||||
void to_json(nlohmann::json &j, const UpdateCameraConfigPayload &payload);
|
||||
void from_json(const nlohmann::json &j, UpdateCameraConfigPayload &payload);
|
||||
#endif
|
||||
@@ -1,86 +1,17 @@
|
||||
#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;
|
||||
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();
|
||||
auto payload = json.get<UpdateCameraConfigPayload>();
|
||||
|
||||
std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
||||
auto oldConfig = projectConfig->getCameraConfig();
|
||||
projectConfig->setCameraConfig(
|
||||
updatedConfig.vflip.has_value() ? updatedConfig.vflip.value() : oldConfig.vflip,
|
||||
updatedConfig.framesize.has_value() ? updatedConfig.framesize.value() : oldConfig.framesize,
|
||||
updatedConfig.href.has_value() ? updatedConfig.href.value() : oldConfig.href,
|
||||
updatedConfig.quality.has_value() ? updatedConfig.quality.value() : oldConfig.quality,
|
||||
updatedConfig.brightness.has_value() ? updatedConfig.brightness.value() : oldConfig.brightness);
|
||||
payload.vflip.has_value() ? payload.vflip.value() : oldConfig.vflip,
|
||||
payload.framesize.has_value() ? payload.framesize.value() : oldConfig.framesize,
|
||||
payload.href.has_value() ? payload.href.value() : oldConfig.href,
|
||||
payload.quality.has_value() ? payload.quality.value() : oldConfig.quality,
|
||||
payload.brightness.has_value() ? payload.brightness.value() : oldConfig.brightness);
|
||||
|
||||
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");
|
||||
}
|
||||
@@ -4,17 +4,14 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cJSON.h>
|
||||
#include "CommandResult.hpp"
|
||||
#include "CommandSchema.hpp"
|
||||
#include "DependencyRegistry.hpp"
|
||||
#include <CameraManager.hpp>
|
||||
#include <nlohmann-json.hpp>
|
||||
|
||||
std::optional<UpdateCameraConfigPayload> parseUpdateCameraPayload(std::string_view jsonPayload);
|
||||
CommandResult updateCameraCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
||||
CommandResult updateCameraCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
|
||||
|
||||
std::optional<RestartCameraPayload> parseRestartCameraPayload(std::string_view jsonPayload);
|
||||
CommandResult restartCameraCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
||||
#endif
|
||||
|
||||
// add cropping command
|
||||
@@ -1,27 +1,5 @@
|
||||
#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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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 = {
|
||||
"all",
|
||||
};
|
||||
|
||||
auto payload = parseResetConfigCommandPayload(jsonPayload);
|
||||
|
||||
if (!payload.has_value())
|
||||
if (!json.contains("section"))
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
// 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
|
||||
if (sectionPayload.section == "all")
|
||||
// todo, add more granular control for other sections, like only reset camera, or only reset wifi
|
||||
if (section == "all")
|
||||
{
|
||||
std::shared_ptr<ProjectConfig> projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
||||
projectConfig->reset();
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cJSON.h>
|
||||
#include "CommandResult.hpp"
|
||||
#include "CommandSchema.hpp"
|
||||
#include "DependencyRegistry.hpp"
|
||||
#include <nlohmann-json.hpp>
|
||||
|
||||
CommandResult saveConfigCommand(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, std::string_view jsonPayload);
|
||||
CommandResult resetConfigCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
|
||||
@@ -4,23 +4,14 @@
|
||||
#include "esp_mac.h"
|
||||
#include <cstdio>
|
||||
|
||||
// Implementation inspired by SummerSigh work, initial PR opened in openiris repo, adapted to this rewrite
|
||||
CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
|
||||
CommandResult setDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
|
||||
{
|
||||
const auto parsedJson = cJSON_Parse(jsonPayload.data());
|
||||
if (parsedJson == nullptr)
|
||||
if (!json.contains("mode") || !json["mode"].is_number_integer())
|
||||
{
|
||||
return CommandResult::getErrorResult("Invalid payload");
|
||||
return CommandResult::getErrorResult("Invalid payload - missing or unsupported mode");
|
||||
}
|
||||
|
||||
const auto modeObject = cJSON_GetObjectItem(parsedJson, "mode");
|
||||
if (modeObject == nullptr)
|
||||
{
|
||||
return CommandResult::getErrorResult("Invalid payload - missing mode");
|
||||
}
|
||||
|
||||
const auto mode = modeObject->valueint;
|
||||
|
||||
const auto mode = json["mode"].get<int>();
|
||||
if (mode < 0 || mode > 2)
|
||||
{
|
||||
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);
|
||||
projectConfig->setDeviceMode(static_cast<StreamingMode>(mode));
|
||||
|
||||
cJSON_Delete(parsedJson);
|
||||
|
||||
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 oldDeviceConfig = projectConfig->getDeviceConfig();
|
||||
@@ -49,48 +32,39 @@ CommandResult updateOTACredentialsCommand(std::shared_ptr<DependencyRegistry> re
|
||||
auto OTAPassword = oldDeviceConfig.OTAPassword;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(parsedJson);
|
||||
|
||||
projectConfig->setOTAConfig(OTALogin, OTAPassword, OTAPort);
|
||||
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 (parsedJson == nullptr)
|
||||
{
|
||||
return CommandResult::getErrorResult("Invalid payload");
|
||||
}
|
||||
|
||||
const auto dutyCycleObject = cJSON_GetObjectItem(parsedJson, "dutyCycle");
|
||||
if (dutyCycleObject == nullptr)
|
||||
if (!json.contains("dutyCycle") || !json["dutyCycle"].is_number_integer())
|
||||
{
|
||||
return CommandResult::getErrorResult("Invalid payload - missing dutyCycle");
|
||||
}
|
||||
|
||||
const auto dutyCycle = dutyCycleObject->valueint;
|
||||
const auto dutyCycle = json["dutyCycle"].get<int>();
|
||||
|
||||
if (dutyCycle < 0 || dutyCycle > 100)
|
||||
{
|
||||
@@ -107,8 +81,6 @@ CommandResult updateLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> regi
|
||||
ledMgr->setExternalLEDDutyCycle(static_cast<uint8_t>(dutyCycle));
|
||||
}
|
||||
|
||||
cJSON_Delete(parsedJson);
|
||||
|
||||
return CommandResult::getSuccessResult("LED duty cycle set");
|
||||
}
|
||||
|
||||
@@ -123,8 +95,8 @@ CommandResult getLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registr
|
||||
const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
||||
const auto deviceCfg = projectConfig->getDeviceConfig();
|
||||
int duty = deviceCfg.led_external_pwm_duty_cycle;
|
||||
auto result = std::format("{{ \"led_external_pwm_duty_cycle\": {} }}", duty);
|
||||
return CommandResult::getSuccessResult(result);
|
||||
const auto json = nlohmann::json{{"led_external_pwm_duty_cycle", duty}};
|
||||
return CommandResult::getSuccessResult(json);
|
||||
}
|
||||
|
||||
CommandResult startStreamingCommand()
|
||||
@@ -145,34 +117,28 @@ CommandResult startStreamingCommand()
|
||||
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 (modeObject == nullptr)
|
||||
if (!json.contains("mode") || !json["mode"].is_string())
|
||||
{
|
||||
return CommandResult::getErrorResult("Invalid payload - missing mode");
|
||||
}
|
||||
|
||||
const char *modeStr = modeObject->valuestring;
|
||||
auto modeStr = json["mode"].get<std::string>();
|
||||
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;
|
||||
}
|
||||
else if (strcmp(modeStr, "wifi") == 0)
|
||||
else if (modeStr == "wifi")
|
||||
{
|
||||
newMode = StreamingMode::WIFI;
|
||||
}
|
||||
else if (strcmp(modeStr, "setup") == 0 || strcmp(modeStr, "auto") == 0)
|
||||
else if (modeStr == "setup" || modeStr == "auto")
|
||||
{
|
||||
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);
|
||||
projectConfig->setDeviceMode(newMode);
|
||||
|
||||
cJSON_Delete(parsedJson);
|
||||
|
||||
return CommandResult::getSuccessResult("Device mode switched, restart to apply");
|
||||
}
|
||||
|
||||
@@ -209,8 +173,11 @@ CommandResult getDeviceModeCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||
break;
|
||||
}
|
||||
|
||||
auto result = std::format("{{ \"mode\": \"{}\", \"value\": {} }}", modeStr, static_cast<int>(currentMode));
|
||||
return CommandResult::getSuccessResult(result);
|
||||
const auto json = nlohmann::json{
|
||||
{"mode", modeStr},
|
||||
{"value", static_cast<int>(currentMode)},
|
||||
};
|
||||
return CommandResult::getSuccessResult(json);
|
||||
}
|
||||
|
||||
CommandResult getSerialNumberCommand(std::shared_ptr<DependencyRegistry> /*registry*/)
|
||||
@@ -229,8 +196,11 @@ CommandResult getSerialNumberCommand(std::shared_ptr<DependencyRegistry> /*regis
|
||||
std::snprintf(mac_colon, sizeof(mac_colon), "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
|
||||
auto result = std::format("{{ \"serial\": \"{}\", \"mac\": \"{}\" }}", serial_no_sep, mac_colon);
|
||||
return CommandResult::getSuccessResult(result);
|
||||
const auto json = nlohmann::json{
|
||||
{"serial", serial_no_sep},
|
||||
{"mac", mac_colon},
|
||||
};
|
||||
return CommandResult::getSuccessResult(json);
|
||||
}
|
||||
|
||||
CommandResult getLEDCurrentCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||
@@ -242,8 +212,8 @@ CommandResult getLEDCurrentCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||
return CommandResult::getErrorResult("MonitoringManager unavailable");
|
||||
}
|
||||
float ma = mon->getCurrentMilliAmps();
|
||||
auto result = std::format("{{ \"led_current_ma\": {:.3f} }}", static_cast<double>(ma));
|
||||
return CommandResult::getSuccessResult(result);
|
||||
const auto json = nlohmann::json{{"led_current_ma", std::format("{:.3f}", static_cast<double>(ma))}};
|
||||
return CommandResult::getSuccessResult(json);
|
||||
#else
|
||||
return CommandResult::getErrorResult("Monitoring disabled");
|
||||
#endif
|
||||
@@ -251,11 +221,17 @@ CommandResult getLEDCurrentCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||
|
||||
CommandResult getInfoCommand(std::shared_ptr<DependencyRegistry> /*registry*/)
|
||||
{
|
||||
const char* who = CONFIG_GENERAL_BOARD;
|
||||
const char* ver = CONFIG_GENERAL_VERSION;
|
||||
const char *who = CONFIG_GENERAL_BOARD;
|
||||
const char *ver = CONFIG_GENERAL_VERSION;
|
||||
// Ensure non-null strings
|
||||
if (!who) who = "";
|
||||
if (!ver) ver = "";
|
||||
auto result = std::format("{{ \"who_am_i\": \"{}\", \"version\": \"{}\" }}", who, ver);
|
||||
return CommandResult::getSuccessResult(result);
|
||||
if (!who)
|
||||
who = "";
|
||||
if (!ver)
|
||||
ver = "";
|
||||
|
||||
const auto json = nlohmann::json{
|
||||
{"who_am_i", who},
|
||||
{"version", ver},
|
||||
};
|
||||
return CommandResult::getSuccessResult(json);
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
#include "OpenIrisTasks.hpp"
|
||||
#include "DependencyRegistry.hpp"
|
||||
#include "esp_timer.h"
|
||||
#include "cJSON.h"
|
||||
#include "main_globals.hpp"
|
||||
|
||||
#include <format>
|
||||
#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 restartDeviceCommand();
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,33 +1,13 @@
|
||||
#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;
|
||||
cJSON *parsedJson = cJSON_Parse(jsonPayload.data());
|
||||
if (parsedJson == nullptr)
|
||||
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");
|
||||
const auto payload = json.get<MDNSPayload>();
|
||||
if (payload.hostname.empty())
|
||||
return CommandResult::getErrorResult("Invalid payload - empty hostname");
|
||||
|
||||
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");
|
||||
}
|
||||
@@ -37,6 +17,6 @@ CommandResult getMDNSNameCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||
const auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
||||
auto mdnsConfig = projectConfig->getMDNSConfig();
|
||||
|
||||
auto result = std::format("{{ \"hostname\": \"{}\" }}", mdnsConfig.hostname);
|
||||
return CommandResult::getSuccessResult(result);
|
||||
const auto json = nlohmann::json{{"hostname", mdnsConfig.hostname}};
|
||||
return CommandResult::getSuccessResult(json);
|
||||
}
|
||||
@@ -2,11 +2,10 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cJSON.h>
|
||||
#include "CommandResult.hpp"
|
||||
#include "CommandSchema.hpp"
|
||||
#include "DependencyRegistry.hpp"
|
||||
#include <nlohmann-json.hpp>
|
||||
|
||||
std::optional<MDNSPayload> parseMDNSCommandPayload(std::string_view jsonPayload);
|
||||
CommandResult setMDNSCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
||||
CommandResult setMDNSCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
|
||||
CommandResult getMDNSNameCommand(std::shared_ptr<DependencyRegistry> registry);
|
||||
@@ -14,29 +14,24 @@ CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||
|
||||
auto networks = wifiManager->ScanNetworks();
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON *networksArray = cJSON_CreateArray();
|
||||
cJSON_AddItemToObject(root, "networks", networksArray);
|
||||
nlohmann::json result;
|
||||
std::vector<nlohmann::json> networksJson;
|
||||
|
||||
for (const auto &network : networks)
|
||||
{
|
||||
cJSON *networkObject = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(networkObject, "ssid", network.ssid.c_str());
|
||||
cJSON_AddNumberToObject(networkObject, "channel", network.channel);
|
||||
cJSON_AddNumberToObject(networkObject, "rssi", network.rssi);
|
||||
nlohmann::json networkItem;
|
||||
networkItem["ssid"] = network.ssid;
|
||||
networkItem["channel"] = network.channel;
|
||||
networkItem["rssi"] = network.rssi;
|
||||
char mac_str[18];
|
||||
sprintf(mac_str, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
network.mac[0], network.mac[1], network.mac[2],
|
||||
network.mac[3], network.mac[4], network.mac[5]);
|
||||
cJSON_AddStringToObject(networkObject, "mac_address", mac_str);
|
||||
cJSON_AddNumberToObject(networkObject, "auth_mode", network.auth_mode);
|
||||
cJSON_AddItemToArray(networksArray, networkObject);
|
||||
networkItem["mac_address"] = mac_str;
|
||||
networkItem["auth_mode"] = network.auth_mode;
|
||||
networksJson.push_back(networkItem);
|
||||
}
|
||||
|
||||
char *json_string = cJSON_PrintUnformatted(root);
|
||||
printf("%s\n", json_string);
|
||||
cJSON_Delete(root);
|
||||
free(json_string);
|
||||
|
||||
return CommandResult::getSuccessResult("Networks scanned");
|
||||
result["networks"] = networksJson;
|
||||
return CommandResult::getSuccessResult(result);
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
#include "CommandResult.hpp"
|
||||
#include "DependencyRegistry.hpp"
|
||||
#include "esp_log.h"
|
||||
#include <cJSON.h>
|
||||
#include <wifiManager.hpp>
|
||||
#include <string>
|
||||
#include <nlohmann-json.hpp>
|
||||
|
||||
CommandResult scanNetworksCommand(std::shared_ptr<DependencyRegistry> registry);
|
||||
|
||||
|
||||
@@ -7,27 +7,19 @@ CommandResult PingCommand()
|
||||
return CommandResult::getSuccessResult("pong");
|
||||
};
|
||||
|
||||
CommandResult PauseCommand(std::string_view jsonPayload)
|
||||
CommandResult PauseCommand(const nlohmann::json &json)
|
||||
{
|
||||
PausePayload payload;
|
||||
// pause by default if this command gets executed, even if the payload was invalid
|
||||
payload.pause = true;
|
||||
auto pause = true;
|
||||
|
||||
cJSON *root = cJSON_Parse(std::string(jsonPayload).c_str());
|
||||
if (root)
|
||||
if (json.contains("pause") && json["pause"].is_boolean())
|
||||
{
|
||||
cJSON *pauseItem = cJSON_GetObjectItem(root, "pause");
|
||||
if (pauseItem && cJSON_IsBool(pauseItem))
|
||||
{
|
||||
payload.pause = cJSON_IsTrue(pauseItem);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
pause = json["pause"].get<bool>();
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Pause command received: %s", payload.pause ? "true" : "false");
|
||||
ESP_LOGI(TAG, "Pause command received: %s", pause ? "true" : "false");
|
||||
|
||||
setStartupPaused(payload.pause);
|
||||
if (payload.pause)
|
||||
setStartupPaused(pause);
|
||||
if (pause)
|
||||
{
|
||||
ESP_LOGI(TAG, "Startup paused - device will remain in configuration mode");
|
||||
return CommandResult::getSuccessResult("Startup paused");
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
|
||||
#include <string>
|
||||
#include "CommandResult.hpp"
|
||||
#include "CommandSchema.hpp"
|
||||
#include "main_globals.hpp"
|
||||
#include "esp_log.h"
|
||||
#include <cJSON.h>
|
||||
#include <nlohmann-json.hpp>
|
||||
|
||||
CommandResult PingCommand();
|
||||
CommandResult PauseCommand(std::string_view jsonPayload);
|
||||
CommandResult PauseCommand(const nlohmann::json &json);
|
||||
|
||||
#endif
|
||||
@@ -2,212 +2,76 @@
|
||||
#include "esp_netif.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
std::optional<WifiPayload> parseSetWiFiCommandPayload(std::string_view jsonPayload)
|
||||
{
|
||||
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)
|
||||
CommandResult setWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json)
|
||||
{
|
||||
#if !CONFIG_GENERAL_ENABLE_WIRELESS
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
#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);
|
||||
projectConfig->setWifiConfig(
|
||||
wifiConfig.networkName,
|
||||
wifiConfig.ssid,
|
||||
wifiConfig.password,
|
||||
wifiConfig.channel,
|
||||
wifiConfig.power);
|
||||
payload.name,
|
||||
payload.ssid,
|
||||
payload.password,
|
||||
payload.channel,
|
||||
payload.power);
|
||||
|
||||
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
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
#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");
|
||||
|
||||
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
||||
|
||||
projectConfig->deleteWifiConfig(payload.value().networkName);
|
||||
projectConfig->deleteWifiConfig(payload.name);
|
||||
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
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
#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 storedNetworks = projectConfig->getWifiConfigs();
|
||||
if (const auto networkToUpdate = std::ranges::find_if(
|
||||
storedNetworks,
|
||||
[&](auto &network){ return network.name == updatedConfig.networkName; }
|
||||
);
|
||||
storedNetworks,
|
||||
[&](auto &network)
|
||||
{ return network.name == payload.name; });
|
||||
networkToUpdate != storedNetworks.end())
|
||||
{
|
||||
projectConfig->setWifiConfig(
|
||||
updatedConfig.networkName,
|
||||
updatedConfig.ssid.has_value() ? updatedConfig.ssid.value() : networkToUpdate->ssid,
|
||||
updatedConfig.password.has_value() ? updatedConfig.password.value() : networkToUpdate->password,
|
||||
updatedConfig.channel.has_value() ? updatedConfig.channel.value() : networkToUpdate->channel,
|
||||
updatedConfig.power.has_value() ? updatedConfig.power.value() : networkToUpdate->power);
|
||||
payload.name,
|
||||
payload.ssid.has_value() ? payload.ssid.value() : networkToUpdate->ssid,
|
||||
payload.password.has_value() ? payload.password.value() : networkToUpdate->password,
|
||||
payload.channel.has_value() ? payload.channel.value() : networkToUpdate->channel,
|
||||
payload.power.has_value() ? payload.power.value() : networkToUpdate->power);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
#endif
|
||||
const auto payload = parseUpdateAPWiFiCommandPayload(jsonPayload);
|
||||
|
||||
if (!payload.has_value())
|
||||
return CommandResult::getErrorResult("Invalid payload");
|
||||
|
||||
auto updatedConfig = payload.value();
|
||||
const auto payload = json.get<UpdateAPWiFiPayload>();
|
||||
|
||||
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
||||
const auto previousAPConfig = projectConfig->getAPWifiConfig();
|
||||
|
||||
projectConfig->setAPWifiConfig(
|
||||
updatedConfig.ssid.has_value() ? updatedConfig.ssid.value() : previousAPConfig.ssid,
|
||||
updatedConfig.password.has_value() ? updatedConfig.password.value() : previousAPConfig.password,
|
||||
updatedConfig.channel.has_value() ? updatedConfig.channel.value() : previousAPConfig.channel);
|
||||
payload.ssid.has_value() ? payload.ssid.value() : previousAPConfig.ssid,
|
||||
payload.password.has_value() ? payload.password.value() : previousAPConfig.password,
|
||||
payload.channel.has_value() ? payload.channel.value() : previousAPConfig.channel);
|
||||
|
||||
return CommandResult::getSuccessResult("Config updated");
|
||||
}
|
||||
|
||||
CommandResult getWiFiStatusCommand(std::shared_ptr<DependencyRegistry> registry) {
|
||||
CommandResult getWiFiStatusCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||
{
|
||||
#if !CONFIG_GENERAL_ENABLE_WIRELESS
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
#endif
|
||||
|
||||
auto wifiManager = registry->resolve<WiFiManager>(DependencyType::wifi_manager);
|
||||
if (!wifiManager) {
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
||||
|
||||
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
|
||||
auto wifiState = wifiManager->GetCurrentWiFiState();
|
||||
auto networks = projectConfig->getWifiConfigs();
|
||||
|
||||
cJSON* statusJson = cJSON_CreateObject();
|
||||
|
||||
// Add WiFi state
|
||||
const char* stateStr = "unknown";
|
||||
switch(wifiState) {
|
||||
case WiFiState_e::WiFiState_NotInitialized:
|
||||
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;
|
||||
|
||||
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));
|
||||
result["ip_address"] = ip_str;
|
||||
}
|
||||
cJSON_AddStringToObject(statusJson, "status", stateStr);
|
||||
cJSON_AddNumberToObject(statusJson, "networks_configured", networks.size());
|
||||
|
||||
// Add IP info if connected
|
||||
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);
|
||||
}
|
||||
|
||||
return CommandResult::getSuccessResult(result);
|
||||
}
|
||||
|
||||
CommandResult connectWiFiCommand(std::shared_ptr<DependencyRegistry> registry) {
|
||||
CommandResult connectWiFiCommand(std::shared_ptr<DependencyRegistry> registry)
|
||||
{
|
||||
#if !CONFIG_GENERAL_ENABLE_WIRELESS
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
#endif
|
||||
|
||||
auto wifiManager = registry->resolve<WiFiManager>(DependencyType::wifi_manager);
|
||||
if (!wifiManager) {
|
||||
return CommandResult::getErrorResult("Not supported by current firmware");
|
||||
auto projectConfig = registry->resolve<ProjectConfig>(DependencyType::project_config);
|
||||
|
||||
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();
|
||||
if (networks.empty()) {
|
||||
return CommandResult::getErrorResult("No WiFi networks configured");
|
||||
}
|
||||
|
||||
// Trigger WiFi connection attempt
|
||||
wifiManager->TryConnectToStoredNetworks();
|
||||
|
||||
return CommandResult::getSuccessResult("WiFi connection attempt started");
|
||||
|
||||
// Trigger WiFi connection attempt
|
||||
wifiManager->TryConnectToStoredNetworks();
|
||||
|
||||
return CommandResult::getSuccessResult("WiFi connection attempt started");
|
||||
}
|
||||
|
||||
@@ -4,22 +4,18 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cJSON.h>
|
||||
#include "CommandResult.hpp"
|
||||
#include "CommandSchema.hpp"
|
||||
#include "DependencyRegistry.hpp"
|
||||
#include <nlohmann-json.hpp>
|
||||
|
||||
std::optional<WifiPayload> parseSetWiFiCommandPayload(std::string_view jsonPayload);
|
||||
CommandResult setWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
||||
CommandResult setWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
|
||||
|
||||
std::optional<deleteNetworkPayload> parseDeleteWifiCommandPayload(std::string_view jsonPayload);
|
||||
CommandResult deleteWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
||||
CommandResult deleteWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
|
||||
|
||||
std::optional<UpdateWifiPayload> parseUpdateWifiCommandPayload(std::string_view jsonPayload);
|
||||
CommandResult updateWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
||||
CommandResult updateWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
|
||||
|
||||
std::optional<UpdateAPWiFiPayload> parseUpdateAPWiFiCommandPayload(std::string_view jsonPayload);
|
||||
CommandResult updateAPWiFiCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload);
|
||||
CommandResult updateAPWiFiCommand(std::shared_ptr<DependencyRegistry> registry, const nlohmann::json &json);
|
||||
|
||||
CommandResult getWiFiStatusCommand(std::shared_ptr<DependencyRegistry> registry);
|
||||
CommandResult connectWiFiCommand(std::shared_ptr<DependencyRegistry> registry);
|
||||
|
||||
@@ -4,6 +4,19 @@
|
||||
|
||||
#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)
|
||||
{
|
||||
this->url = std::move(url);
|
||||
@@ -84,66 +97,73 @@ void HandleRestAPIPollTask(void *pvParameter)
|
||||
|
||||
// COMMANDS
|
||||
// updates
|
||||
void RestAPI::handle_update_wifi(RequestContext *context) {
|
||||
void RestAPI::handle_update_wifi(RequestContext *context)
|
||||
{
|
||||
if (context->method != POST_METHOD)
|
||||
{
|
||||
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
auto const result = command_manager->executeFromType(CommandType::UPDATE_WIFI, context->body);
|
||||
auto const code = result.isSuccess() ? 200 : 500;
|
||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.getResult().c_str());
|
||||
const nlohmann::json result = command_manager->executeFromType(CommandType::UPDATE_WIFI, context->body);
|
||||
const auto code = getIsSuccess(result) ? 200 : 400;
|
||||
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)
|
||||
{
|
||||
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
auto const result = command_manager->executeFromType(CommandType::UPDATE_OTA_CREDENTIALS, context->body);
|
||||
auto const code = result.isSuccess() ? 200 : 500;
|
||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.getResult().c_str());
|
||||
const nlohmann::json result = command_manager->executeFromType(CommandType::UPDATE_OTA_CREDENTIALS, context->body);
|
||||
const auto code = getIsSuccess(result) ? 200 : 500;
|
||||
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)
|
||||
{
|
||||
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
auto const result = command_manager->executeFromType(CommandType::UPDATE_CAMERA, context->body);
|
||||
auto const code = result.isSuccess() ? 200 : 500;
|
||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.getResult().c_str());
|
||||
const nlohmann::json result = command_manager->executeFromType(CommandType::UPDATE_CAMERA, context->body);
|
||||
const auto code = getIsSuccess(result) ? 200 : 500;
|
||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
|
||||
}
|
||||
|
||||
// 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, "");
|
||||
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
|
||||
|
||||
void RestAPI::handle_reset_config(RequestContext *context) {
|
||||
void RestAPI::handle_reset_config(RequestContext *context)
|
||||
{
|
||||
if (context->method != POST_METHOD)
|
||||
{
|
||||
mg_http_reply(context->connection, 401, JSON_RESPONSE, "{%m:%m}", MG_ESC("error"), "Method not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
auto const result = this->command_manager->executeFromType(CommandType::RESET_CONFIG, "{\"section\": \"all\"}");
|
||||
auto const code = result.isSuccess() ? 200 : 500;
|
||||
mg_http_reply(context->connection, code, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), result.getResult());
|
||||
const nlohmann::json result = this->command_manager->executeFromType(CommandType::RESET_CONFIG, "{\"section\": \"all\"}");
|
||||
const auto code = getIsSuccess(result) ? 200 : 500;
|
||||
mg_http_reply(context->connection, code, JSON_RESPONSE, "{%m:%m}", MG_ESC("result"), result.dump().c_str());
|
||||
}
|
||||
|
||||
// reboots
|
||||
void RestAPI::handle_reboot(RequestContext *context) {
|
||||
auto const result = this->command_manager->executeFromType(CommandType::RESTART_DEVICE, "");
|
||||
void RestAPI::handle_reboot(RequestContext *context)
|
||||
{
|
||||
const auto result = this->command_manager->executeFromType(CommandType::RESTART_DEVICE, "");
|
||||
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
|
||||
|
||||
void RestAPI::pong(RequestContext *context) {
|
||||
auto const result = this->command_manager->executeFromType(CommandType::PING, "");
|
||||
auto const code = result.isSuccess() ? 200 : 500;
|
||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.getResult().c_str());
|
||||
void RestAPI::pong(RequestContext *context)
|
||||
{
|
||||
const nlohmann::json result = this->command_manager->executeFromType(CommandType::PING, "");
|
||||
const auto code = getIsSuccess(result) ? 200 : 500;
|
||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
|
||||
}
|
||||
|
||||
// special
|
||||
|
||||
void RestAPI::handle_save(RequestContext *context) {
|
||||
auto const result = this->command_manager->executeFromType(CommandType::SAVE_CONFIG, "");
|
||||
auto const code = result.isSuccess() ? 200 : 500;
|
||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.getResult().c_str());
|
||||
void RestAPI::handle_save(RequestContext *context)
|
||||
{
|
||||
const nlohmann::json result = this->command_manager->executeFromType(CommandType::SAVE_CONFIG, "");
|
||||
const auto code = getIsSuccess(result) ? 200 : 500;
|
||||
mg_http_reply(context->connection, code, JSON_RESPONSE, result.dump().c_str());
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
#define BUF_SIZE (1024)
|
||||
|
||||
SerialManager::SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle, std::shared_ptr<ProjectConfig> deviceConfig)
|
||||
: commandManager(commandManager), timerHandle(timerHandle), deviceConfig(deviceConfig)
|
||||
SerialManager::SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle)
|
||||
: commandManager(commandManager), timerHandle(timerHandle)
|
||||
{
|
||||
this->data = static_cast<uint8_t *>(malloc(BUF_SIZE));
|
||||
this->temp_data = static_cast<uint8_t *>(malloc(256));
|
||||
@@ -50,14 +50,24 @@ void SerialManager::try_receive()
|
||||
data[current_position] = '\0';
|
||||
current_position = 0;
|
||||
|
||||
const auto result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data)));
|
||||
const auto resultMessage = result.getResult();
|
||||
int written = usb_serial_jtag_write_bytes(resultMessage.c_str(), resultMessage.length(), 1000 / 20);
|
||||
(void)written; // ignore errors if driver already uninstalled
|
||||
const nlohmann::json result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(this->data)));
|
||||
const auto resultMessage = result.dump();
|
||||
usb_serial_jtag_write_bytes_chunked(resultMessage.c_str(), resultMessage.length(), 1000 / 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
void SerialManager::notify_startup_command_received()
|
||||
{
|
||||
@@ -73,46 +83,6 @@ void SerialManager::notify_startup_command_received()
|
||||
}
|
||||
}
|
||||
|
||||
void SerialManager::send_heartbeat()
|
||||
{
|
||||
// Get the MAC address as unique identifier
|
||||
uint8_t mac[6];
|
||||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||
|
||||
// Format as serial number string
|
||||
char serial_number[18];
|
||||
sprintf(serial_number, "%02X%02X%02X%02X%02X%02X",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
|
||||
// Create heartbeat JSON with serial number
|
||||
char heartbeat[128];
|
||||
sprintf(heartbeat, "{\"heartbeat\":\"openiris_setup_mode\",\"serial\":\"%s\"}\n", serial_number);
|
||||
|
||||
usb_serial_jtag_write_bytes(heartbeat, strlen(heartbeat), 1000 / 20);
|
||||
// Ignore return value; if the driver was uninstalled, this is a no-op
|
||||
}
|
||||
|
||||
bool SerialManager::should_send_heartbeat()
|
||||
{
|
||||
// Always send heartbeat during startup delay or if no WiFi configured
|
||||
|
||||
// If startup timer is still running, always send heartbeat
|
||||
if (timerHandle != nullptr && *timerHandle != nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If in heartbeat mode after startup, continue sending
|
||||
if (getStartupCommandReceived())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, only send if no WiFi credentials configured
|
||||
const auto wifiConfigs = deviceConfig->getWifiConfigs();
|
||||
return wifiConfigs.empty();
|
||||
}
|
||||
|
||||
void SerialManager::shutdown()
|
||||
{
|
||||
// Stop heartbeats; timer will be deleted by main if needed.
|
||||
@@ -132,23 +102,9 @@ void SerialManager::shutdown()
|
||||
void HandleSerialManagerTask(void *pvParameters)
|
||||
{
|
||||
auto const serialManager = static_cast<SerialManager *>(pvParameters);
|
||||
TickType_t lastHeartbeat = xTaskGetTickCount();
|
||||
const TickType_t heartbeatInterval = pdMS_TO_TICKS(2000); // 2 second heartbeat
|
||||
|
||||
while (true)
|
||||
{
|
||||
serialManager->try_receive();
|
||||
|
||||
// Send heartbeat every 2 seconds, but only if no WiFi credentials are set
|
||||
TickType_t currentTime = xTaskGetTickCount();
|
||||
if ((currentTime - lastHeartbeat) >= heartbeatInterval)
|
||||
{
|
||||
if (serialManager->should_send_heartbeat())
|
||||
{
|
||||
serialManager->send_heartbeat();
|
||||
}
|
||||
lastHeartbeat = currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +127,8 @@ void HandleCDCSerialManagerTask(void *pvParameters)
|
||||
if (idx >= BUF_SIZE || buffer[idx - 1] == '\n' || buffer[idx - 1] == '\r')
|
||||
{
|
||||
buffer[idx - 1] = '\0';
|
||||
const auto result = commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(buffer)));
|
||||
const auto resultMessage = result.getResult();
|
||||
const nlohmann::json result = commandManager->executeFromJson(std::string_view(reinterpret_cast<const char *>(buffer)));
|
||||
const auto resultMessage = result.dump();
|
||||
tud_cdc_write(resultMessage.c_str(), resultMessage.length());
|
||||
tud_cdc_write_flush();
|
||||
idx = 0;
|
||||
|
||||
@@ -32,18 +32,17 @@ struct cdc_command_packet_t
|
||||
class SerialManager
|
||||
{
|
||||
public:
|
||||
explicit SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle, std::shared_ptr<ProjectConfig> deviceConfig);
|
||||
explicit SerialManager(std::shared_ptr<CommandManager> commandManager, esp_timer_handle_t *timerHandle);
|
||||
void setup();
|
||||
void try_receive();
|
||||
void send_heartbeat();
|
||||
bool should_send_heartbeat();
|
||||
void notify_startup_command_received();
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
void usb_serial_jtag_write_bytes_chunked(const char *data, size_t len, size_t timeout);
|
||||
|
||||
std::shared_ptr<CommandManager> commandManager;
|
||||
esp_timer_handle_t *timerHandle;
|
||||
std::shared_ptr<ProjectConfig> deviceConfig;
|
||||
uint8_t *data;
|
||||
uint8_t *temp_data;
|
||||
};
|
||||
|
||||
@@ -65,7 +65,6 @@ static esp_err_t UVCStreamHelpers::camera_start_cb(uvc_format_t format, int widt
|
||||
}
|
||||
|
||||
cameraHandler->setCameraResolution(frame_size);
|
||||
cameraHandler->resetCamera(false);
|
||||
|
||||
constexpr SystemEvent event = {EventSource::STREAM, StreamState_e::Stream_ON};
|
||||
xQueueSend(eventQueue, &event, 10);
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
idf_component_register(SRCS "cJSON/cJSON.c"
|
||||
INCLUDE_DIRS "cJSON"
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,300 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef cJSON__h
|
||||
#define cJSON__h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
|
||||
#define __WINDOWS__
|
||||
#endif
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
|
||||
|
||||
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
|
||||
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
|
||||
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
|
||||
|
||||
For *nix builds that support visibility attribute, you can define similar behavior by
|
||||
|
||||
setting default visibility to hidden by adding
|
||||
-fvisibility=hidden (for gcc)
|
||||
or
|
||||
-xldscope=hidden (for sun cc)
|
||||
to CFLAGS
|
||||
|
||||
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
|
||||
|
||||
*/
|
||||
|
||||
#define CJSON_CDECL __cdecl
|
||||
#define CJSON_STDCALL __stdcall
|
||||
|
||||
/* export symbols by default, this is necessary for copy pasting the C and header file */
|
||||
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
|
||||
#define CJSON_EXPORT_SYMBOLS
|
||||
#endif
|
||||
|
||||
#if defined(CJSON_HIDE_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) type CJSON_STDCALL
|
||||
#elif defined(CJSON_EXPORT_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
|
||||
#elif defined(CJSON_IMPORT_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
|
||||
#endif
|
||||
#else /* !__WINDOWS__ */
|
||||
#define CJSON_CDECL
|
||||
#define CJSON_STDCALL
|
||||
|
||||
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
|
||||
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
|
||||
#else
|
||||
#define CJSON_PUBLIC(type) type
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* project version */
|
||||
#define CJSON_VERSION_MAJOR 1
|
||||
#define CJSON_VERSION_MINOR 7
|
||||
#define CJSON_VERSION_PATCH 18
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* cJSON Types: */
|
||||
#define cJSON_Invalid (0)
|
||||
#define cJSON_False (1 << 0)
|
||||
#define cJSON_True (1 << 1)
|
||||
#define cJSON_NULL (1 << 2)
|
||||
#define cJSON_Number (1 << 3)
|
||||
#define cJSON_String (1 << 4)
|
||||
#define cJSON_Array (1 << 5)
|
||||
#define cJSON_Object (1 << 6)
|
||||
#define cJSON_Raw (1 << 7) /* raw json */
|
||||
|
||||
#define cJSON_IsReference 256
|
||||
#define cJSON_StringIsConst 512
|
||||
|
||||
/* The cJSON structure: */
|
||||
typedef struct cJSON
|
||||
{
|
||||
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
|
||||
struct cJSON *next;
|
||||
struct cJSON *prev;
|
||||
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
|
||||
struct cJSON *child;
|
||||
|
||||
/* The type of the item, as above. */
|
||||
int type;
|
||||
|
||||
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
|
||||
char *valuestring;
|
||||
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
|
||||
int valueint;
|
||||
/* The item's number, if type==cJSON_Number */
|
||||
double valuedouble;
|
||||
|
||||
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
|
||||
char *string;
|
||||
} cJSON;
|
||||
|
||||
typedef struct cJSON_Hooks
|
||||
{
|
||||
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
|
||||
void *(CJSON_CDECL *malloc_fn)(size_t sz);
|
||||
void (CJSON_CDECL *free_fn)(void *ptr);
|
||||
} cJSON_Hooks;
|
||||
|
||||
typedef int cJSON_bool;
|
||||
|
||||
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
|
||||
* This is to prevent stack overflows. */
|
||||
#ifndef CJSON_NESTING_LIMIT
|
||||
#define CJSON_NESTING_LIMIT 1000
|
||||
#endif
|
||||
|
||||
/* returns the version of cJSON as a string */
|
||||
CJSON_PUBLIC(const char*) cJSON_Version(void);
|
||||
|
||||
/* Supply malloc, realloc and free functions to cJSON */
|
||||
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
|
||||
|
||||
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
|
||||
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
|
||||
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
|
||||
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||
|
||||
/* Render a cJSON entity to text for transfer/storage. */
|
||||
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
|
||||
/* Render a cJSON entity to text for transfer/storage without any formatting. */
|
||||
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
|
||||
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
|
||||
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
|
||||
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
|
||||
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
|
||||
/* Delete a cJSON entity and all subentities. */
|
||||
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
|
||||
|
||||
/* Returns the number of items in an array (or object). */
|
||||
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
|
||||
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
|
||||
/* Get item "string" from object. Case insensitive. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
|
||||
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
|
||||
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
|
||||
|
||||
/* Check item type and return its value */
|
||||
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
|
||||
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
|
||||
|
||||
/* These functions check the type of an item */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
|
||||
|
||||
/* These calls create a cJSON item of the appropriate type. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
|
||||
/* raw json */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
|
||||
|
||||
/* Create a string where valuestring references a string so
|
||||
* it will not be freed by cJSON_Delete */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
|
||||
/* Create an object/array that only references it's elements so
|
||||
* they will not be freed by cJSON_Delete */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
|
||||
|
||||
/* These utilities create an Array of count items.
|
||||
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
|
||||
|
||||
/* Append item to the specified array/object. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
|
||||
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
|
||||
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
|
||||
* writing to `item->string` */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
|
||||
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
|
||||
|
||||
/* Remove/Detach items from Arrays/Objects. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
|
||||
/* Update array items. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
|
||||
|
||||
/* Duplicate a cJSON item */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
|
||||
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
|
||||
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
|
||||
* The item->next and ->prev pointers are always zero on return from Duplicate. */
|
||||
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
|
||||
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
|
||||
|
||||
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
|
||||
* The input pointer json cannot point to a read-only address area, such as a string constant,
|
||||
* but should point to a readable and writable address area. */
|
||||
CJSON_PUBLIC(void) cJSON_Minify(char *json);
|
||||
|
||||
/* Helper functions for creating and adding items to an object at the same time.
|
||||
* They return the added item or NULL on failure. */
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
|
||||
|
||||
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
|
||||
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
|
||||
/* helper for the cJSON_SetNumberValue macro */
|
||||
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
|
||||
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
|
||||
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
|
||||
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
|
||||
|
||||
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
|
||||
#define cJSON_SetBoolValue(object, boolValue) ( \
|
||||
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
|
||||
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
|
||||
cJSON_Invalid\
|
||||
)
|
||||
|
||||
/* Macro for iterating over an array or object */
|
||||
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
|
||||
|
||||
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
|
||||
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
|
||||
CJSON_PUBLIC(void) cJSON_free(void *object);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
3
components/nlohmann-json/CMakeLists.txt
Normal file
3
components/nlohmann-json/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(
|
||||
INCLUDE_DIRS "nlohmann-json"
|
||||
)
|
||||
25526
components/nlohmann-json/nlohmann-json/nlohmann-json.hpp
Normal file
25526
components/nlohmann-json/nlohmann-json/nlohmann-json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -28,11 +28,15 @@
|
||||
#include <UVCStream.hpp>
|
||||
#endif
|
||||
|
||||
// defines to configure nlohmann-json for esp32
|
||||
#define JSON_NO_IO 1
|
||||
#define JSON_NOEXCEPTION 1
|
||||
|
||||
#ifdef CONFIG_LED_DEBUG_ENABLE
|
||||
#define BLINK_GPIO (gpio_num_t) CONFIG_LED_DEBUG_GPIO
|
||||
#else
|
||||
// Use an invalid / unused GPIO when debug LED disabled to avoid accidental toggles
|
||||
#define BLINK_GPIO (gpio_num_t) -1
|
||||
#define BLINK_GPIO (gpio_num_t) - 1
|
||||
#endif
|
||||
#define CONFIG_LED_C_PIN_GPIO (gpio_num_t) CONFIG_LED_EXTERNAL_GPIO
|
||||
|
||||
@@ -64,8 +68,8 @@ UVCStreamManager uvcStream;
|
||||
#endif
|
||||
|
||||
auto ledManager = std::make_shared<LEDManager>(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue, deviceConfig);
|
||||
auto *serialManager = new SerialManager(commandManager, &timerHandle, deviceConfig);
|
||||
std::shared_ptr<MonitoringManager> monitoringManager = std::make_shared<MonitoringManager>();
|
||||
auto *serialManager = new SerialManager(commandManager, &timerHandle);
|
||||
|
||||
void startWiFiMode();
|
||||
void startWiredMode(bool shouldCloseSerialManager);
|
||||
@@ -108,8 +112,8 @@ void launch_streaming()
|
||||
}
|
||||
else if (deviceMode == StreamingMode::SETUP)
|
||||
{
|
||||
// we're still in setup, the user didn't select anything yet, let's give a bit of time for them to make a choice
|
||||
ESP_LOGI("[MAIN]", "No mode was selected, staying in SETUP mode. WiFi streaming will be enabled still. \nPlease select another mode if you'd like.");
|
||||
// we're still in setup, the user didn't select anything yet, let's give a bit of time for them to make a choice
|
||||
ESP_LOGI("[MAIN]", "No mode was selected, staying in SETUP mode. WiFi streaming will be enabled still. \nPlease select another mode if you'd like.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -260,11 +264,8 @@ extern "C" void app_main(void)
|
||||
dependencyRegistry->registerService<MonitoringManager>(DependencyType::monitoring_manager, monitoringManager);
|
||||
|
||||
// add endpoint to check firmware version
|
||||
// add firmware version somewhere
|
||||
// setup CI and building for other boards
|
||||
// finish todos, overhaul stuff a bit
|
||||
|
||||
// todo - do we need logs over CDC? Or just commands and their results?
|
||||
// esp_log_set_vprintf(&websocket_logger);
|
||||
Logo::printASCII();
|
||||
initNVSStorage();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
pyserial>=3.5
|
||||
@@ -1,7 +0,0 @@
|
||||
@echo off
|
||||
echo Installing OpenIris Setup Tool dependencies...
|
||||
pip install -r requirements.txt
|
||||
echo.
|
||||
echo Setup complete! Run the tool with:
|
||||
echo python openiris_setup.py
|
||||
pause
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "Installing OpenIris Setup Tool dependencies..."
|
||||
pip3 install -r requirements.txt
|
||||
echo ""
|
||||
echo "Setup complete! Run the tool with:"
|
||||
echo "python3 openiris_setup.py"
|
||||
717
tools/setup_openiris.py
Normal file
717
tools/setup_openiris.py
Normal file
@@ -0,0 +1,717 @@
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "pyserial>=3.5",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
|
||||
import json
|
||||
import time
|
||||
import argparse
|
||||
import sys
|
||||
import serial
|
||||
import string
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
def is_back(choice: str):
|
||||
return choice.lower() in ["back", "b", "exit"]
|
||||
|
||||
|
||||
class SubMenu:
|
||||
def __init__(self, title, context: dict, parent_menu=None):
|
||||
self.title = title
|
||||
self.context = context
|
||||
self.items = []
|
||||
|
||||
if parent_menu:
|
||||
parent_menu.add_submenu(self)
|
||||
|
||||
def render(self, idx: int):
|
||||
print(f"{str(idx + 1):>2} {self.title}")
|
||||
|
||||
def add_submenu(self, submenu):
|
||||
self.items.append(submenu)
|
||||
|
||||
def add_action(self, title, action):
|
||||
self.items.append((title, action))
|
||||
|
||||
def validate_choice(self, choice: str):
|
||||
try:
|
||||
parsed_choice = int(choice)
|
||||
if parsed_choice >= 0 and parsed_choice <= len(self.items):
|
||||
return parsed_choice
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# we'll print the error regardless if it was an exception or wrong choice
|
||||
print("❌ Invalid choice")
|
||||
print("-" * 50)
|
||||
|
||||
def show(self):
|
||||
while True:
|
||||
for idx, item in enumerate(self.items):
|
||||
if isinstance(item, SubMenu):
|
||||
item.render(idx)
|
||||
else:
|
||||
print(f"{str(idx + 1):>2} {item[0]}")
|
||||
print("[Back] To go back")
|
||||
|
||||
choice = input(">> ")
|
||||
if is_back(choice):
|
||||
break
|
||||
|
||||
choice = self.validate_choice(choice)
|
||||
if not choice:
|
||||
continue
|
||||
|
||||
selected_element = self.items[int(choice) - 1]
|
||||
if isinstance(selected_element, SubMenu):
|
||||
selected_element.show()
|
||||
else:
|
||||
print("-" * 50)
|
||||
selected_element[1](**self.context)
|
||||
print("-" * 50)
|
||||
|
||||
|
||||
class Menu(SubMenu):
|
||||
def __init__(self, title, context=None, parent_menu=None):
|
||||
super().__init__(title, context, parent_menu)
|
||||
|
||||
|
||||
class OpenIrisDevice:
|
||||
def __init__(self, port: str, debug: bool, debug_commands: bool):
|
||||
self.port = port
|
||||
self.debug = debug
|
||||
self.debug_commands = debug_commands
|
||||
self.connection: serial.Serial | None = None
|
||||
self.connected = False
|
||||
|
||||
def __enter__(self):
|
||||
self.connected = self.__connect()
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.__disconnect()
|
||||
|
||||
def __connect(self) -> bool:
|
||||
print(f"📡 Connecting directly to {self.port}...")
|
||||
|
||||
try:
|
||||
self.connection = serial.Serial(
|
||||
port=self.port, baudrate=115200, timeout=1, write_timeout=1
|
||||
)
|
||||
print(f"✅ Connected to the device on {self.port}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to connect to {self.port}: {e}")
|
||||
return False
|
||||
|
||||
def __disconnect(self):
|
||||
if self.connection and self.connection.is_open:
|
||||
self.connection.close()
|
||||
print(f"🔌 Disconnected from {self.port}")
|
||||
|
||||
def __check_if_response_is_complete(self, response) -> dict | None:
|
||||
try:
|
||||
return json.loads(response)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def __read_response(self, timeout: int | None = None) -> dict | None:
|
||||
# we can try and retrieve the response now.
|
||||
# it should be more or less immediate, but some commands may take longer
|
||||
# so we gotta timeout
|
||||
timeout = timeout if timeout is not None else 15
|
||||
start_time = time.time()
|
||||
response_buffer = ""
|
||||
while time.time() - start_time < timeout:
|
||||
if self.connection.in_waiting:
|
||||
packet = self.connection.read_all().decode("utf-8", errors="ignore")
|
||||
if self.debug and packet.strip():
|
||||
print(f"Received: {packet}")
|
||||
print("-" * 10)
|
||||
print(f"Current buffer: {response_buffer}")
|
||||
print("-" * 10)
|
||||
|
||||
# we can't rely on new lines to detect if we're done
|
||||
# nor can we assume that we're always gonna get valid json response
|
||||
# but we can assume that if we're to get a valid response, it's gonna be json
|
||||
# so we can start actually building the buffer only when
|
||||
# some part of the packet starts with "{", and start building from there
|
||||
# we can assume that no further data will be returned, so we can validate
|
||||
# right after receiving the last packet
|
||||
if (not response_buffer and "{" in packet) or response_buffer:
|
||||
# assume we just started building the buffer and we've received the first packet
|
||||
|
||||
# alternative approach in case this doesn't work - we're always sending a valid json
|
||||
# so we can start building the buffer from the first packet and keep trying to find the
|
||||
# starting and ending brackets, extract that part and validate, if the message is complete, return
|
||||
if not response_buffer:
|
||||
starting_idx = packet.find("{")
|
||||
response_buffer = packet[starting_idx:]
|
||||
else:
|
||||
response_buffer += packet
|
||||
|
||||
# and once we get something, we can validate if it's a valid json
|
||||
if parsed_response := self.__check_if_response_is_complete(
|
||||
response_buffer
|
||||
):
|
||||
return parsed_response
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
return None
|
||||
|
||||
def is_connected(self) -> bool:
|
||||
return self.connected
|
||||
|
||||
def send_command(
|
||||
self, command: str, params: dict | None = None, timeout: int | None = None
|
||||
) -> dict:
|
||||
if not self.connection or not self.connection.is_open:
|
||||
return {"error": "Device Not Connected"}
|
||||
|
||||
cmd_obj = {"commands": [{"command": command}]}
|
||||
if params:
|
||||
cmd_obj["commands"][0]["data"] = params
|
||||
|
||||
# we're expecting the json string to end with a new line
|
||||
# to signify we've finished sending the command
|
||||
cmd_str = json.dumps(cmd_obj) + "\n"
|
||||
try:
|
||||
# clean it out first, just to be sure we're starting fresh
|
||||
self.connection.reset_input_buffer()
|
||||
if self.debug or self.debug_commands:
|
||||
print(f"Sending command: {cmd_str}")
|
||||
self.connection.write(cmd_str.encode())
|
||||
response = self.__read_response(timeout)
|
||||
|
||||
if self.debug:
|
||||
print(f"Received response: {response}")
|
||||
|
||||
return response or {"error": "Command timeout"}
|
||||
|
||||
except Exception as e:
|
||||
return {"error": f"Communication error: {e}"}
|
||||
|
||||
|
||||
@dataclass
|
||||
class WiFiNetwork:
|
||||
ssid: str
|
||||
channel: int
|
||||
rssi: int
|
||||
mac_address: str
|
||||
auth_mode: int
|
||||
|
||||
@property
|
||||
def security_type(self) -> str:
|
||||
"""Convert auth_mode to human readable string"""
|
||||
auth_modes = {
|
||||
0: "Open",
|
||||
1: "WEP",
|
||||
2: "WPA PSK",
|
||||
3: "WPA2 PSK",
|
||||
4: "WPA WPA2 PSK",
|
||||
5: "WPA2 Enterprise",
|
||||
6: "WPA3 PSK",
|
||||
7: "WPA2 WPA3 PSK",
|
||||
}
|
||||
return auth_modes.get(self.auth_mode, f"Unknown ({self.auth_mode})")
|
||||
|
||||
|
||||
class WiFiScanner:
|
||||
def __init__(self, device: OpenIrisDevice):
|
||||
self.device = device
|
||||
self.networks = []
|
||||
|
||||
def scan_networks(self, timeout: int = 30):
|
||||
print(
|
||||
f"🔍 Scanning for WiFi networks (this may take up to {timeout} seconds)..."
|
||||
)
|
||||
response = self.device.send_command("scan_networks", timeout=timeout)
|
||||
if has_command_failed(response):
|
||||
print(f"❌ Scan failed: {response['error']}")
|
||||
return
|
||||
|
||||
channels_found = set()
|
||||
networks = response["results"][0]["result"]["data"]["networks"]
|
||||
|
||||
# after each scan, clear the network list to avoid duplication
|
||||
self.networks = []
|
||||
for net in networks:
|
||||
network = WiFiNetwork(
|
||||
ssid=net["ssid"],
|
||||
channel=net["channel"],
|
||||
rssi=net["rssi"],
|
||||
mac_address=net["mac_address"],
|
||||
auth_mode=net["auth_mode"],
|
||||
)
|
||||
self.networks.append(network)
|
||||
channels_found.add(net["channel"])
|
||||
|
||||
# Sort networks by RSSI (strongest first)
|
||||
self.networks.sort(key=lambda x: x.rssi, reverse=True)
|
||||
print(
|
||||
f"✅ Found {len(self.networks)} networks on channels: {sorted(channels_found)}"
|
||||
)
|
||||
|
||||
def get_networks(self) -> list:
|
||||
return self.networks
|
||||
|
||||
|
||||
def has_command_failed(result) -> bool:
|
||||
return "error" in result or result["results"][0]["result"]["status"] != "success"
|
||||
|
||||
|
||||
def get_device_mode(device: OpenIrisDevice) -> dict:
|
||||
command_result = device.send_command("get_device_mode")
|
||||
if has_command_failed(command_result):
|
||||
return {"mode": "unknown"}
|
||||
|
||||
return {"mode": command_result["results"][0]["result"]["data"]["mode"].lower()}
|
||||
|
||||
|
||||
def get_led_duty_cycle(device: OpenIrisDevice) -> dict:
|
||||
command_result = device.send_command("get_led_duty_cycle")
|
||||
if has_command_failed(command_result):
|
||||
print(f"❌ Failed to get LED duty cycle: {command_result['error']}")
|
||||
return {"duty_cycle": "unknown"}
|
||||
try:
|
||||
return {
|
||||
"duty_cycle": int(
|
||||
command_result["results"][0]["result"]["data"][
|
||||
"led_external_pwm_duty_cycle"
|
||||
]
|
||||
)
|
||||
}
|
||||
except ValueError as e:
|
||||
print(f"❌ Failed to parse LED duty cycle: {e}")
|
||||
return {"duty_cycle": "unknown"}
|
||||
|
||||
|
||||
def get_mdns_name(device: OpenIrisDevice) -> dict:
|
||||
response = device.send_command("get_mdns_name")
|
||||
if "error" in response:
|
||||
print(f"❌ Failed to get device name: {response['error']}")
|
||||
return {"name": "unknown"}
|
||||
|
||||
return {"name": response["results"][0]["result"]["data"]["hostname"]}
|
||||
|
||||
|
||||
def get_serial_info(device: OpenIrisDevice) -> dict:
|
||||
response = device.send_command("get_serial")
|
||||
if has_command_failed(response):
|
||||
print(f"❌ Failed to get serial/MAC: {response['error']}")
|
||||
return {"serial": None, "mac": None}
|
||||
|
||||
return {
|
||||
"serial": response["results"][0]["result"]["data"]["serial"],
|
||||
"mac": response["results"][0]["result"]["data"]["mac"],
|
||||
}
|
||||
|
||||
def get_device_info(device: OpenIrisDevice) -> dict:
|
||||
response = device.send_command("get_who_am_i")
|
||||
|
||||
if has_command_failed(response):
|
||||
print(f"❌ Failed to get device info: {response['error']}")
|
||||
return {"who_am_i": None, "version": None}
|
||||
|
||||
return {"who_am_i": response["results"][0]["result"]["data"]["who_am_i"], "version": response["results"][0]["result"]["data"]["version"]}
|
||||
|
||||
|
||||
def get_wifi_status(device: OpenIrisDevice) -> dict:
|
||||
response = device.send_command("get_wifi_status")
|
||||
if has_command_failed(response):
|
||||
print(f"❌ Failed to get wifi status: {response['error']}")
|
||||
return {"wifi_status": {"status": "unknown"}}
|
||||
|
||||
return {"wifi_status": response["results"][0]["result"]["data"]}
|
||||
|
||||
|
||||
def get_led_current(device: OpenIrisDevice) -> dict:
|
||||
response = device.send_command("get_led_current")
|
||||
if has_command_failed(response):
|
||||
print(f"❌ Failed to get LED current: {response}")
|
||||
return {"led_current_ma": "unknown"}
|
||||
|
||||
return {"led_current_ma": response["results"][0]["result"]["data"]["led_current_ma"]}
|
||||
|
||||
|
||||
def configure_device_name(device: OpenIrisDevice, *args, **kwargs):
|
||||
current_name = get_mdns_name(device)
|
||||
print(f"\n📍 Current device name: {current_name['name']} \n")
|
||||
print(
|
||||
"💡 Please enter your preferred device name, your board will be accessible under http://<name>.local/"
|
||||
)
|
||||
print("💡 Please avoid spaces and special characters")
|
||||
print(" To back out, enter `back`")
|
||||
print("\n Note, this will also modify the name of the UVC device")
|
||||
|
||||
while True:
|
||||
name_choice = input("\nDevice name: ").strip()
|
||||
if any(space in name_choice for space in string.whitespace):
|
||||
print("❌ Please avoid spaces and special characters")
|
||||
else:
|
||||
break
|
||||
|
||||
if is_back(name_choice):
|
||||
return
|
||||
|
||||
response = device.send_command("set_mdns", {"hostname": name_choice})
|
||||
if "error" in response:
|
||||
print(f"❌ MDNS name setup failed: {response['error']}")
|
||||
return
|
||||
|
||||
print("✅ MDNS name set successfully")
|
||||
|
||||
|
||||
def start_streaming(device: OpenIrisDevice, *args, **kwargs):
|
||||
print("🚀 Starting streaming mode...")
|
||||
response = device.send_command("start_streaming")
|
||||
|
||||
if "error" in response:
|
||||
print(f"❌ Failed to start streaming: {response['error']}")
|
||||
return
|
||||
|
||||
print("✅ Streaming mode started")
|
||||
|
||||
|
||||
def switch_device_mode_command(device: OpenIrisDevice, *args, **kwargs):
|
||||
modes = ["wifi", "uvc", "auto"]
|
||||
current_mode = get_device_mode(device)["mode"]
|
||||
print(f"\n📍 Current device mode: {current_mode}")
|
||||
print("\n🔄 Select new device mode:")
|
||||
print("1. WiFi - Stream over WiFi connection")
|
||||
print("2. UVC - Stream as USB webcam")
|
||||
print("3. Auto - Automatic mode selection")
|
||||
print("Back - Return to main menu")
|
||||
|
||||
mode_choice = input("\nSelect mode (1-3): ").strip()
|
||||
if is_back(mode_choice):
|
||||
return
|
||||
|
||||
try:
|
||||
mode = modes[int(mode_choice) - 1]
|
||||
except ValueError:
|
||||
print("❌ Invalid mode selection")
|
||||
return
|
||||
|
||||
command_result = device.send_command("switch_mode", {"mode": mode})
|
||||
if "error" in command_result:
|
||||
print(f"❌ Failed to switch mode: {command_result['error']}")
|
||||
return
|
||||
|
||||
print(f"✅ Device mode switched to '{mode}' successfully!")
|
||||
print("🔄 Please restart the device for changes to take effect")
|
||||
|
||||
|
||||
def set_led_duty_cycle(device: OpenIrisDevice, *args, **kwargs):
|
||||
current_duty_cycle = get_led_duty_cycle(device)["duty_cycle"]
|
||||
print(f"💡 Current LED duty cycle: {current_duty_cycle}%")
|
||||
|
||||
while True:
|
||||
desired_pwd = input(
|
||||
"Enter LED external PWM duty cycle (0-100) or `back` to exit: \n>> "
|
||||
)
|
||||
if is_back(desired_pwd):
|
||||
break
|
||||
|
||||
try:
|
||||
duty_cycle = int(desired_pwd)
|
||||
except ValueError:
|
||||
print("❌ Invalid input. Please enter a number between 0 and 100.")
|
||||
|
||||
if duty_cycle < 0 or duty_cycle > 100:
|
||||
print("❌ Duty cycle must be between 0 and 100.")
|
||||
else:
|
||||
response = device.send_command(
|
||||
"set_led_duty_cycle", {"dutyCycle": duty_cycle}
|
||||
)
|
||||
if has_command_failed(response):
|
||||
print(f"❌ Failed to set LED duty cycle: {response['error']}")
|
||||
return False
|
||||
|
||||
print("✅ LED duty cycle set successfully")
|
||||
updated = get_led_duty_cycle(device)["duty_cycle"]
|
||||
print(f"💡 Current LED duty cycle: {updated}%")
|
||||
|
||||
|
||||
def get_settings_summary(device: OpenIrisDevice, *args, **kwargs):
|
||||
print("🧩 Collecting device settings...\n")
|
||||
|
||||
probes = [
|
||||
("Identity", get_serial_info),
|
||||
("AdvertisedName", get_mdns_name),
|
||||
("Info", get_device_info),
|
||||
("LED", get_led_duty_cycle),
|
||||
("Current", get_led_current),
|
||||
("Mode", get_device_mode),
|
||||
("WiFi", get_wifi_status),
|
||||
]
|
||||
|
||||
summary: dict[str, dict] = {}
|
||||
|
||||
for label, probe in probes:
|
||||
summary[label] = probe(device)
|
||||
|
||||
print(f"🔑 Serial: {summary['Identity']}")
|
||||
print(f"💡 LED PWM Duty: {summary['LED']['duty_cycle']}%")
|
||||
print(f"🎚️ Mode: {summary['Mode']['mode']}")
|
||||
|
||||
current_section = summary.get("Current", {})
|
||||
led_current_ma = current_section.get("led_current_ma")
|
||||
print(f"🔌 LED Current: {led_current_ma} mA")
|
||||
|
||||
advertised_name_data = summary.get("AdvertisedName", {})
|
||||
advertised_name = advertised_name_data.get("name")
|
||||
print(f"📛 Name: {advertised_name}")
|
||||
|
||||
info = summary.get("Info", {})
|
||||
who = info.get("who_am_i")
|
||||
ver = info.get("version")
|
||||
if who:
|
||||
print(f"🏷️ Device: {who}")
|
||||
if ver:
|
||||
print(f"🧭 Version: {ver}")
|
||||
|
||||
|
||||
wifi = summary.get("WiFi", {}).get("wifi_status", {})
|
||||
if wifi:
|
||||
status = wifi.get("status", "unknown")
|
||||
ip = wifi.get("ip_address") or "-"
|
||||
configured = wifi.get("networks_configured", 0)
|
||||
print(f"📶 WiFi: {status} | IP: {ip} | Networks configured: {configured}")
|
||||
|
||||
|
||||
def scan_networks(wifi_scanner: WiFiScanner, *args, **kwargs):
|
||||
use_custom_timeout = (
|
||||
input("Should we use a custom scan timeout? (y/n)\n>> ").strip().lower() == "y"
|
||||
)
|
||||
if use_custom_timeout:
|
||||
timeout = input("Enter timeout in seconds (5-120) or back to go back\n>> ")
|
||||
if is_back(timeout):
|
||||
return
|
||||
|
||||
try:
|
||||
timeout = int(timeout)
|
||||
if 5 <= timeout <= 120:
|
||||
print(
|
||||
f"🔍 Scanning for WiFi networks (this may take up to {timeout} seconds)..."
|
||||
)
|
||||
wifi_scanner.scan_networks(timeout)
|
||||
else:
|
||||
print("❌ Timeout must be between 5 and 120 seconds, using default")
|
||||
wifi_scanner.scan_networks()
|
||||
except ValueError:
|
||||
print("❌ Invalid timeout")
|
||||
else:
|
||||
wifi_scanner.scan_networks()
|
||||
|
||||
|
||||
def display_networks(wifi_scanner: WiFiScanner, *args, **kwargs):
|
||||
networks = wifi_scanner.get_networks()
|
||||
if not networks:
|
||||
print("❌ No networks found, please scan first")
|
||||
return
|
||||
|
||||
print("\n📡 Available WiFi Networks:")
|
||||
print("-" * 85)
|
||||
print(f"{'#':<3} {'SSID':<32} {'Channel':<8} {'Signal':<20} {'Security':<15}")
|
||||
print("-" * 85)
|
||||
|
||||
channels = {}
|
||||
for idx, network in enumerate(networks, 1):
|
||||
channels[network.channel] = channels.get(network.channel, 0) + 1
|
||||
|
||||
signal_bars = "▓" * min(5, max(0, (network.rssi + 100) // 10))
|
||||
signal_str = f"{signal_bars:<5} ({network.rssi} dBm)"
|
||||
ssid_display = network.ssid if network.ssid else "<hidden>"
|
||||
|
||||
print(
|
||||
f"{idx:<3} {ssid_display:<32} {network.channel:<8} {signal_str:<20} {network.security_type:<15}"
|
||||
)
|
||||
|
||||
print("-" * 85)
|
||||
|
||||
print("\n📊 Channel distribution: ", end="")
|
||||
for channel in sorted(channels.keys()):
|
||||
print(f"Ch{channel}: {channels[channel]} networks ", end="")
|
||||
|
||||
|
||||
def configure_wifi(device: OpenIrisDevice, wifi_scanner: WiFiScanner, *args, **kwargs):
|
||||
networks = wifi_scanner.get_networks()
|
||||
if not networks:
|
||||
print("❌ No networks available. Please scan first.")
|
||||
return
|
||||
|
||||
display_networks(wifi_scanner, *args, **kwargs)
|
||||
|
||||
while True:
|
||||
net_choice = input("\nEnter network number (or 'back'): ").strip()
|
||||
if is_back(net_choice):
|
||||
break
|
||||
|
||||
try:
|
||||
net_idx = int(net_choice) - 1
|
||||
except ValueError:
|
||||
print("❌ Please enter a number or 'back'")
|
||||
continue
|
||||
|
||||
sorted_networks = wifi_scanner.get_networks()
|
||||
if 0 <= net_idx < len(sorted_networks):
|
||||
selected_network = sorted_networks[net_idx]
|
||||
ssid = selected_network.ssid
|
||||
|
||||
print(f"\n🔐 Selected: {ssid}")
|
||||
print(f"Security: {selected_network.security_type}")
|
||||
|
||||
if selected_network.auth_mode == 0: # Open network
|
||||
password = ""
|
||||
print("🔓 Open network - no password required")
|
||||
else:
|
||||
password = input("Enter WiFi password (or 'back'): ")
|
||||
if is_back(password):
|
||||
break
|
||||
|
||||
print(f"🔧 Setting WiFi credentials for '{ssid}'...")
|
||||
|
||||
params = {
|
||||
"name": "main",
|
||||
"ssid": ssid,
|
||||
"password": password,
|
||||
"channel": 0,
|
||||
"power": 0,
|
||||
}
|
||||
|
||||
response = device.send_command("set_wifi", params)
|
||||
if has_command_failed(response):
|
||||
print(f"❌ WiFi setup failed: {response['error']}")
|
||||
break
|
||||
|
||||
print("✅ WiFi configured successfully!")
|
||||
print("💡 Next steps:")
|
||||
print(" • Open WiFi menu to connect to WiFi (if needed)")
|
||||
print(" • Open WiFi menu to check WiFi status")
|
||||
print(" • Start streaming from the main menu when connected")
|
||||
break
|
||||
else:
|
||||
print("❌ Invalid network number")
|
||||
|
||||
|
||||
def automatic_wifi_configuration(
|
||||
device: OpenIrisDevice, wifi_scanner: WiFiScanner, *args, **kwargs
|
||||
):
|
||||
print("\n⚙️ Automatic WiFi setup starting...")
|
||||
scan_networks(wifi_scanner, *args, **kwargs)
|
||||
configure_wifi(device, wifi_scanner, *args, **kwargs)
|
||||
attempt_wifi_connection(device)
|
||||
|
||||
print("⏳ Connecting to WiFi, waiting for IP...")
|
||||
start = time.time()
|
||||
timeout_s = 30
|
||||
ip = None
|
||||
last_status = None
|
||||
while time.time() - start < timeout_s:
|
||||
status = get_wifi_status(device).get("wifi_status", {})
|
||||
last_status = status
|
||||
ip = status.get("ip_address")
|
||||
if ip and ip not in ("0.0.0.0", "", None):
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
if ip and ip not in ("0.0.0.0", "", None):
|
||||
print(f"✅ Connected! IP Address: {ip}")
|
||||
else:
|
||||
print("⚠️ Connection not confirmed within timeout")
|
||||
if last_status:
|
||||
print(
|
||||
f" Status: {last_status.get('status', 'unknown')} | IP: {last_status.get('ip_address', '-')}"
|
||||
)
|
||||
|
||||
|
||||
def attempt_wifi_connection(device: OpenIrisDevice, *args, **kwargs):
|
||||
print("🔗 Attempting WiFi connection...")
|
||||
response = device.send_command("connect_wifi")
|
||||
if has_command_failed(response):
|
||||
print(f"❌ WiFi connection failed: {response['error']}")
|
||||
return
|
||||
|
||||
print("✅ WiFi connection attempt started")
|
||||
|
||||
|
||||
def check_wifi_status(device: OpenIrisDevice, *args, **kwargs):
|
||||
status = get_wifi_status(device).get("wifi_status")
|
||||
print(f"📶 WiFi Status: {status.get('status', 'Unknown')}")
|
||||
if ip_address := status.get("ip_address"):
|
||||
print(f"🌐 IP Address: {ip_address}")
|
||||
|
||||
|
||||
def handle_menu(menu_context: dict | None = None) -> str:
|
||||
menu = Menu("OpenIris Setup", menu_context)
|
||||
wifi_settings = SubMenu("📶 WiFi settings", menu_context, menu)
|
||||
wifi_settings.add_action("⚙️ Automatic WiFi setup", automatic_wifi_configuration)
|
||||
manual_wifi_actions = SubMenu(
|
||||
"📁 WiFi Manual Actions:",
|
||||
menu_context,
|
||||
wifi_settings,
|
||||
)
|
||||
|
||||
manual_wifi_actions.add_action("🔍 Scan for WiFi networks", scan_networks)
|
||||
manual_wifi_actions.add_action("📡 Show available networks", display_networks)
|
||||
manual_wifi_actions.add_action("🔐 Configure WiFi", configure_wifi)
|
||||
manual_wifi_actions.add_action("🔗 Connect to WiFi", attempt_wifi_connection)
|
||||
manual_wifi_actions.add_action("🛰️ Check WiFi status", check_wifi_status)
|
||||
|
||||
menu.add_action("🌐 Configure MDNS", configure_device_name)
|
||||
menu.add_action("💻 Configure UVC Name", configure_device_name)
|
||||
menu.add_action("🚀 Start streaming mode", start_streaming)
|
||||
menu.add_action("🔄 Switch device mode (WiFi/UVC/Auto)", switch_device_mode_command)
|
||||
menu.add_action("💡 Update PWM Duty Cycle", set_led_duty_cycle)
|
||||
menu.add_action("🧩 Get settings summary", get_settings_summary)
|
||||
menu.show()
|
||||
|
||||
|
||||
def valid_port(port: str):
|
||||
if not port.startswith("COM"):
|
||||
raise argparse.ArgumentTypeError("Invalid port name. We only support COM ports")
|
||||
return port
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="OpenIris CLI Setup Tool")
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
type=valid_port,
|
||||
help="Serial port to connect to [COM4, COM3, etc]",
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
action="store_true",
|
||||
help="Show debug output including raw serial data",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show-commands",
|
||||
action="store_true",
|
||||
help="Debug mode, but will show only sent commands",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
print("🔧 OpenIris Setup Tool")
|
||||
print("=" * 50)
|
||||
|
||||
with OpenIrisDevice(args.port, args.debug, args.show_commands) as device:
|
||||
if not device.is_connected():
|
||||
return 1
|
||||
|
||||
wifi_scanner = WiFiScanner(device)
|
||||
try:
|
||||
handle_menu({"device": device, "args": args, "wifi_scanner": wifi_scanner})
|
||||
except KeyboardInterrupt:
|
||||
print("\n🛑 Setup interrupted, disconnecting")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,177 +0,0 @@
|
||||
import serial
|
||||
import time
|
||||
import json
|
||||
from typing import List, Dict
|
||||
|
||||
class ESPWiFiScanner:
|
||||
def __init__(self, port: str, baudrate: int = 115200):
|
||||
self.port = port
|
||||
self.baudrate = baudrate
|
||||
self.serial = None
|
||||
|
||||
def connect(self) -> bool:
|
||||
try:
|
||||
self.serial = serial.Serial(
|
||||
port=self.port,
|
||||
baudrate=self.baudrate,
|
||||
timeout=1
|
||||
)
|
||||
return True
|
||||
except serial.SerialException as e:
|
||||
print(f"Error connecting to ESP32: {e}")
|
||||
return False
|
||||
|
||||
def scan_networks(self, timeout_seconds: int = 30) -> List[Dict]:
|
||||
if not self.serial:
|
||||
print("Not connected to ESP32")
|
||||
return []
|
||||
|
||||
self.serial.reset_input_buffer()
|
||||
|
||||
command = '{"commands":[{"command":"scan_networks"}]}\n'
|
||||
self.serial.write(command.encode())
|
||||
|
||||
timeout_start = time.time()
|
||||
response_buffer = ""
|
||||
|
||||
while time.time() - timeout_start < timeout_seconds:
|
||||
if self.serial.in_waiting:
|
||||
data = self.serial.read(self.serial.in_waiting).decode('utf-8', errors='ignore')
|
||||
response_buffer += data
|
||||
|
||||
# Look for WiFi networks JSON directly (new format)
|
||||
# The scan command now outputs JSON directly followed by command result
|
||||
if '{"networks":[' in response_buffer:
|
||||
import re
|
||||
|
||||
# Look for the networks JSON pattern that appears first
|
||||
networks_pattern = r'\{"networks":\[.*?\]\}'
|
||||
matches = re.findall(networks_pattern, response_buffer, re.DOTALL)
|
||||
|
||||
for match in matches:
|
||||
try:
|
||||
# Parse the networks JSON directly
|
||||
networks_data = json.loads(match)
|
||||
if "networks" in networks_data:
|
||||
return networks_data["networks"]
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# Also check if we have the command result indicating completion
|
||||
if '{"results":' in response_buffer and '"Networks scanned"' in response_buffer:
|
||||
# We've received the completion message, parse any networks found
|
||||
import re
|
||||
networks_pattern = r'\{"networks":\[.*?\]\}'
|
||||
matches = re.findall(networks_pattern, response_buffer, re.DOTALL)
|
||||
|
||||
for match in matches:
|
||||
try:
|
||||
networks_data = json.loads(match)
|
||||
if "networks" in networks_data:
|
||||
return networks_data["networks"]
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# If we get here, scan completed but no networks found
|
||||
return []
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
|
||||
print("Failed to receive clean JSON response. Raw data:")
|
||||
print("=" * 50)
|
||||
print(response_buffer)
|
||||
print("=" * 50)
|
||||
return []
|
||||
|
||||
def close(self):
|
||||
if self.serial:
|
||||
self.serial.close()
|
||||
|
||||
def main():
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='ESP32 WiFi Scanner')
|
||||
parser.add_argument('port', nargs='?', help='Serial port - COM1, /dev/ttyUSB0, etc.')
|
||||
parser.add_argument('-t', '--timeout', type=int, default=30,
|
||||
help='Scan timeout in seconds (default: 30)')
|
||||
args = parser.parse_args()
|
||||
|
||||
scanner = ESPWiFiScanner(args.port)
|
||||
|
||||
if scanner.connect():
|
||||
print(f"Connected to ESP32 on {args.port}")
|
||||
print(f"Scanning for WiFi networks (timeout: {args.timeout} seconds)...")
|
||||
start_time = time.time()
|
||||
networks = scanner.scan_networks(args.timeout)
|
||||
scan_time = time.time() - start_time
|
||||
|
||||
if networks:
|
||||
# Sort by RSSI (strongest first)
|
||||
networks.sort(key=lambda x: x.get('rssi', -100), reverse=True)
|
||||
|
||||
print(f"\n✅ Found {len(networks)} WiFi Networks in {scan_time:.1f} seconds:")
|
||||
print("{:<32} | {:<7} | {:<15} | {:<17} | {:<9}".format(
|
||||
"SSID", "Channel", "Signal", "MAC Address", "Security"
|
||||
))
|
||||
print("-" * 85)
|
||||
|
||||
# Track channels found
|
||||
channels_found = set()
|
||||
auth_modes = {
|
||||
0: "Open",
|
||||
1: "WEP",
|
||||
2: "WPA-PSK",
|
||||
3: "WPA2-PSK",
|
||||
4: "WPA/WPA2",
|
||||
5: "WPA2-Enterprise",
|
||||
6: "WPA3-PSK",
|
||||
7: "WPA2/WPA3",
|
||||
8: "WAPI-PSK"
|
||||
}
|
||||
|
||||
for network in networks:
|
||||
ssid = network.get('ssid', '')
|
||||
if not ssid:
|
||||
ssid = "<hidden>"
|
||||
|
||||
channel = network.get('channel', 0)
|
||||
channels_found.add(channel)
|
||||
|
||||
# Create signal strength visualization
|
||||
rssi = network.get('rssi', -100)
|
||||
signal_bars = "▓" * min(5, max(0, (rssi + 100) // 10))
|
||||
signal_str = f"{signal_bars:<5} ({rssi} dBm)"
|
||||
|
||||
auth_mode = network.get('auth_mode', 0)
|
||||
security = auth_modes.get(auth_mode, f"Type {auth_mode}")
|
||||
|
||||
print("{:<32} | {:<7} | {:<15} | {:<17} | {:<9}".format(
|
||||
ssid[:32], # Truncate long SSIDs
|
||||
channel,
|
||||
signal_str,
|
||||
network.get('mac_address', '?'),
|
||||
security
|
||||
))
|
||||
|
||||
# Show channel distribution
|
||||
print(f"\n📊 Channels detected: {sorted(channels_found)}")
|
||||
channel_counts = {}
|
||||
for net in networks:
|
||||
ch = net.get('channel', 0)
|
||||
channel_counts[ch] = channel_counts.get(ch, 0) + 1
|
||||
|
||||
print("📊 Channel distribution: ", end="")
|
||||
for ch in sorted(channel_counts.keys()):
|
||||
print(f"Ch{ch}: {channel_counts[ch]} networks ", end="")
|
||||
print()
|
||||
|
||||
else:
|
||||
print("❌ No networks found or scan failed")
|
||||
|
||||
scanner.close()
|
||||
else:
|
||||
print(f"Failed to connect to ESP32 on {args.port}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user