From 4705e414761ebcdfd458090c5eb90c042d5023ec Mon Sep 17 00:00:00 2001 From: Lorow Date: Thu, 15 Aug 2024 20:13:01 +0200 Subject: [PATCH 1/8] Initial changes, cleanup and improvements --- ESP/lib/src/data/utilities/helpers.cpp | 166 +++++++++--------- ESP/lib/src/data/utilities/helpers.hpp | 62 ++++--- ESP/lib/src/io/LEDManager/LEDManager.cpp | 23 ++- ESP/lib/src/io/LEDManager/LEDManager.hpp | 50 +++--- ESP/lib/src/network/api/baseAPI/Hash.h | 17 -- ESP/lib/src/network/api/baseAPI/baseAPI.cpp | 11 -- ESP/lib/src/network/api/baseAPI/baseAPI.hpp | 37 ++-- ESP/lib/src/network/mDNS/MDNSManager.cpp | 3 +- .../src/network/wifihandler/wifihandler.cpp | 39 ++-- .../src/network/wifihandler/wifihandler.hpp | 5 +- ESP/src/main.cpp | 63 ++----- 11 files changed, 220 insertions(+), 256 deletions(-) delete mode 100644 ESP/lib/src/network/api/baseAPI/Hash.h diff --git a/ESP/lib/src/data/utilities/helpers.cpp b/ESP/lib/src/data/utilities/helpers.cpp index 52af448..bf62d48 100644 --- a/ESP/lib/src/data/utilities/helpers.cpp +++ b/ESP/lib/src/data/utilities/helpers.cpp @@ -1,99 +1,93 @@ #include "helpers.hpp" -char *Helpers::itoa(int value, char *result, int base) -{ - // check that the base if valid - if (base < 2 || base > 36) - { - *result = '\0'; - return result; - } - - char *ptr = result, *ptr1 = result, tmp_char; - int tmp_value; - - do - { - tmp_value = value; - value /= base; - *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)]; - } while (value); - - // Apply negative sign - if (tmp_value < 0) - *ptr++ = '-'; - *ptr-- = '\0'; - while (ptr1 < ptr) - { - tmp_char = *ptr; - *ptr-- = *ptr1; - *ptr1++ = tmp_char; - } +char* Helpers::itoa(int value, char* result, int base) { + // check that the base if valid + if (base < 2 || base > 36) { + *result = '\0'; return result; + } + + char *ptr = result, *ptr1 = result, tmp_char; + int tmp_value; + + do { + tmp_value = value; + value /= base; + *ptr++ = + "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxy" + "z"[35 + (tmp_value - value * base)]; + } while (value); + + // Apply negative sign + if (tmp_value < 0) + *ptr++ = '-'; + *ptr-- = '\0'; + while (ptr1 < ptr) { + tmp_char = *ptr; + *ptr-- = *ptr1; + *ptr1++ = tmp_char; + } + return result; } -void split(const std::string &str, const std::string &splitBy, std::vector &tokens) -{ - /* Store the original string in the array, so we can loop the rest - * of the algorithm. */ - tokens.emplace_back(str); +void split(const std::string& str, + const std::string& splitBy, + std::vector& tokens) { + /* Store the original string in the array, so we can loop the rest + * of the algorithm. */ + tokens.emplace_back(str); - // Store the split index in a 'size_t' (unsigned integer) type. - size_t splitAt; - // Store the size of what we're splicing out. - size_t splitLen = splitBy.size(); - // Create a string for temporarily storing the fragment we're processing. - std::string frag; - // Loop infinitely - break is internal. - while (true) - { - /* Store the last string in the vector, which is the only logical - * candidate for processing. */ - frag = tokens.back(); - /* The index where the split is. */ - splitAt = frag.find(splitBy); - // If we didn't find a new split point... - if (splitAt == std::string::npos) - { - // Break the loop and (implicitly) return. - break; - } - /* Put everything from the left side of the split where the string - * being processed used to be. */ - tokens.back() = frag.substr(0, splitAt); - /* Push everything from the right side of the split to the next empty - * index in the vector. */ - tokens.emplace_back(frag.substr(splitAt + splitLen, frag.size() - (splitAt + splitLen))); + // Store the split index in a 'size_t' (unsigned integer) type. + size_t splitAt; + // Store the size of what we're splicing out. + size_t splitLen = splitBy.size(); + // Create a string for temporarily storing the fragment we're processing. + std::string frag; + // Loop infinitely - break is internal. + while (true) { + /* Store the last string in the vector, which is the only logical + * candidate for processing. */ + frag = tokens.back(); + /* The index where the split is. */ + splitAt = frag.find(splitBy); + // If we didn't find a new split point... + if (splitAt == std::string::npos) { + // Break the loop and (implicitly) return. + break; } + /* Put everything from the left side of the split where the string + * being processed used to be. */ + tokens.back() = frag.substr(0, splitAt); + /* Push everything from the right side of the split to the next empty + * index in the vector. */ + tokens.emplace_back( + frag.substr(splitAt + splitLen, frag.size() - (splitAt + splitLen))); + } } -std::vector Helpers::split(const std::string &s, char delimiter) -{ - std::vector parts; - std::string part; - std::istringstream tokenStream(s); - while (std::getline(tokenStream, part, delimiter)) - { - parts.push_back(part); - } - return parts; +std::vector Helpers::split(const std::string& s, char delimiter) { + std::vector parts; + std::string part; + std::istringstream tokenStream(s); + while (std::getline(tokenStream, part, delimiter)) { + parts.push_back(part); + } + return parts; } -void Helpers::update_progress_bar(int progress, int total) -{ - int barWidth = 70; +void Helpers::update_progress_bar(int progress, int total) { + int barWidth = 70; - std::cout << "\r["; - int pos = barWidth * progress / total; - for (int i = 0; i < barWidth; ++i) - { - if (i < pos) - std::cout << "="; - else if (i == pos) - std::cout << ">"; - else - std::cout << " "; - } - std::cout << "] " << int(progress * 100.0 / total) << " %\r"; - std::cout.flush(); + std::cout << "\r["; + int pos = barWidth * progress / total; + for (int i = 0; i < barWidth; ++i) { + if (i < pos) + std::cout << "="; + else if (i == pos) + std::cout << ">"; + else + std::cout << " "; + } + std::cout << "] " << int(progress * 100.0 / total) << " %\r"; + std::cout.flush(); } \ No newline at end of file diff --git a/ESP/lib/src/data/utilities/helpers.hpp b/ESP/lib/src/data/utilities/helpers.hpp index 0f1d0a4..35cab0f 100644 --- a/ESP/lib/src/data/utilities/helpers.hpp +++ b/ESP/lib/src/data/utilities/helpers.hpp @@ -1,37 +1,41 @@ #ifndef HELPERS_HPP #define HELPERS_HPP -#include -#include -#include #include #include +#include +#include +#include -namespace Helpers -{ - char *itoa(int value, char *result, int base); - void split(std::string str, std::string splitBy, std::vector &tokens); - std::vector split(const std::string &s, char delimiter); - void update_progress_bar(int progress, int total); +const unsigned char ETVR_HEADER[] = {0xFF, 0xA0, 0xFF, 0xA1, 0x01}; +const char* const ETVR_HEADER_BYTES = "\xff\xa0\xff\xa1"; - /// @brief - /// @tparam ...Args - /// @param format - /// @param ...args - /// @return - template - std::string format_string(const std::string &format, Args... args) - { - int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' - if (size_s <= 0) - { - std::cout << "Error during formatting."; - return ""; - } - auto size = static_cast(size_s); - std::unique_ptr buf(new char[size]); - std::snprintf(buf.get(), size, format.c_str(), args...); - return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +namespace Helpers { + char* itoa(int value, char* result, int base); + void split(std::string str, + std::string splitBy, + std::vector& tokens); + std::vector split(const std::string& s, char delimiter); + void update_progress_bar(int progress, int total); + + /// @brief + /// @tparam ...Args + /// @param format + /// @param ...args + /// @return + template + std::string format_string(const std::string& format, Args... args) { + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + + 1; // Extra space for '\0' + if (size_s <= 0) { + std::cout << "Error during formatting."; + return ""; } -} + auto size = static_cast(size_s); + std::unique_ptr buf(new char[size]); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), + buf.get() + size - 1); // We don't want the '\0' inside + } +} // namespace Helpers -#endif // HELPERS_HPP +#endif // HELPERS_HPP diff --git a/ESP/lib/src/io/LEDManager/LEDManager.cpp b/ESP/lib/src/io/LEDManager/LEDManager.cpp index 2ff76cf..20e3dc5 100644 --- a/ESP/lib/src/io/LEDManager/LEDManager.cpp +++ b/ESP/lib/src/io/LEDManager/LEDManager.cpp @@ -9,6 +9,7 @@ *between blinks. */ +// TODO rethink this LEDManager::ledStateMap_t LEDManager::ledStateMap = { {LEDStates_e::_LedStateNone, {{0, 500}}}, {LEDStates_e::_Improv_Error, @@ -58,7 +59,7 @@ LEDManager::~LEDManager() {} void LEDManager::begin() { pinMode(_ledPin, OUTPUT); - // the defualt state is _LedStateNone so we're fine + // the default state is _LedStateNone so we're fine this->currentState = ledStateManager.getCurrentState(); this->currentPatternIndex = 0; BlinkPatterns_t pattern = @@ -66,7 +67,12 @@ void LEDManager::begin() { this->toggleLED(pattern.state); this->nextStateChangeMillis = pattern.delayTime; - log_d("begin %d", this->currentPatternIndex); + log_d("Led manager began with: %d", this->currentPatternIndex); + +#ifdef CONFIG_CAMERA_MODULE_SWROOM_BABBLE_S3 + log_d("Setting up LED for the Babble board"); + this->setupBabbeLed(); +#endif } /** @@ -120,6 +126,19 @@ void LEDManager::handleLED() { log_d("updated stage %d", this->currentPatternIndex); } +#ifdef CONFIG_CAMERA_MODULE_SWROOM_BABBLE_S3 +void LEDManager::setupBabbeLed() { + // Set IR emitter strength to 100%. + const int ledPin = 1; // Replace this with a command endpoint eventually. + const int freq = 5000; + const int ledChannel = 0; + const int resolution = 8; + const int dutyCycle = 255; + ledcSetup(ledChannel, freq, resolution); + ledcAttachPin(ledPin, ledChannel); + ledcWrite(ledChannel, dutyCycle); +} +#endif /** * @brief Turn the LED on or off * diff --git a/ESP/lib/src/io/LEDManager/LEDManager.hpp b/ESP/lib/src/io/LEDManager/LEDManager.hpp index f8772a3..b1f5533 100644 --- a/ESP/lib/src/io/LEDManager/LEDManager.hpp +++ b/ESP/lib/src/io/LEDManager/LEDManager.hpp @@ -1,36 +1,38 @@ #ifndef LEDMANAGER_HPP #define LEDMANAGER_HPP -#include #include #include #include +#include -class LEDManager -{ -public: - LEDManager(byte pin); - virtual ~LEDManager(); +class LEDManager { + public: + LEDManager(byte pin); + virtual ~LEDManager(); - void begin(); - void handleLED(); - void toggleLED(bool state) const; + void begin(); + void handleLED(); + void toggleLED(bool state) const; +#ifdef CONFIG_CAMERA_MODULE_SWROOM_BABBLE_S3 + void setupBabbeLed(); +#endif -private: - byte _ledPin; - unsigned long nextStateChangeMillis = 0; - bool _ledState; + private: + byte _ledPin; + unsigned long nextStateChangeMillis = 0; + bool _ledState; - struct BlinkPatterns_t - { - int state; - int delayTime; - }; + struct BlinkPatterns_t { + int state; + int delayTime; + }; - typedef std::unordered_map> ledStateMap_t; - static ledStateMap_t ledStateMap; - static std::vector keepAliveStates; - LEDStates_e currentState; - unsigned int currentPatternIndex = 0; + typedef std::unordered_map> + ledStateMap_t; + static ledStateMap_t ledStateMap; + static std::vector keepAliveStates; + LEDStates_e currentState; + unsigned int currentPatternIndex = 0; }; -#endif // LEDMANAGER_HPP +#endif // LEDMANAGER_HPP diff --git a/ESP/lib/src/network/api/baseAPI/Hash.h b/ESP/lib/src/network/api/baseAPI/Hash.h deleted file mode 100644 index d98e562..0000000 --- a/ESP/lib/src/network/api/baseAPI/Hash.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef HASH_H_ -#define HASH_H_ - -#include -#include - -// #define DEBUG_SHA1 - -void sha1(const uint8_t* data, uint32_t size, uint8_t hash[20]); -void sha1(const char* data, uint32_t size, uint8_t hash[20]); -void sha1(const std::string& data, uint8_t hash[20]); - -std::string sha1(const uint8_t* data, uint32_t size); -std::string sha1(const char* data, uint32_t size); -std::string sha1(const std::string& data); - -#endif /* HASH_H_ */ diff --git a/ESP/lib/src/network/api/baseAPI/baseAPI.cpp b/ESP/lib/src/network/api/baseAPI/baseAPI.cpp index d3ce808..ec74227 100644 --- a/ESP/lib/src/network/api/baseAPI/baseAPI.cpp +++ b/ESP/lib/src/network/api/baseAPI/baseAPI.cpp @@ -102,17 +102,6 @@ void BaseAPI::setWiFi(AsyncWebServerRequest* request) { projectConfig.setWifiConfig(networkName, ssid, password, channel, power, adhoc, true); - /* if (WiFiStateManager->getCurrentState() == - WiFiState_e::WiFiState_ADHOC) - { - projectConfig.setAPWifiConfig(ssid, password, &channel, adhoc, - true); - } - else - { - - } */ - request->send(200, MIMETYPE_JSON, "{\"msg\":\"Done. Wifi Creds have been set.\"}"); break; diff --git a/ESP/lib/src/network/api/baseAPI/baseAPI.hpp b/ESP/lib/src/network/api/baseAPI/baseAPI.hpp index 4b6a544..35d5fbc 100644 --- a/ESP/lib/src/network/api/baseAPI/baseAPI.hpp +++ b/ESP/lib/src/network/api/baseAPI/baseAPI.hpp @@ -3,7 +3,6 @@ //! Warning do not format this file with clang-format or it will break the code -#include #include #include @@ -31,7 +30,6 @@ constexpr int HTTP_ANY = 0b01111111; #include #include -#include "Hash.h" #include "data/utilities/network_utilities.hpp" #include "tasks/tasks.hpp" @@ -100,33 +98,34 @@ class BaseAPI { typedef std::unordered_map networkMethodsMap_t; - ProjectConfig &projectConfig; - /// @brief Local instance of the AsyncWebServer - so that we dont need to use new and delete - AsyncWebServer server; + ProjectConfig& projectConfig; + /// @brief Local instance of the AsyncWebServer - so that we dont need to use + /// new and delete + AsyncWebServer server; #ifndef SIM_ENABLED - CameraHandler &camera; + CameraHandler& camera; #endif // SIM_ENABLED -public : - BaseAPI(ProjectConfig& projectConfig, + public: + BaseAPI(ProjectConfig& projectConfig, #ifndef SIM_ENABLED - CameraHandler& camera, + CameraHandler& camera, #endif // SIM_ENABLED - const std::string& api_url, + const std::string& api_url, #ifndef SIM_ENABLED - int port = 81 + int port = 81 #else - int port = 80 + int port = 80 #endif ); - virtual ~BaseAPI(); - virtual void begin(); - void checkAuthentication(AsyncWebServerRequest* request, - const char* login, - const char* password); - void beginOTA(); - void notFound(AsyncWebServerRequest* request) const; + virtual ~BaseAPI(); + virtual void begin(); + void checkAuthentication(AsyncWebServerRequest* request, + const char* login, + const char* password); + void beginOTA(); + void notFound(AsyncWebServerRequest* request) const; }; #endif // BASEAPI_HPP diff --git a/ESP/lib/src/network/mDNS/MDNSManager.cpp b/ESP/lib/src/network/mDNS/MDNSManager.cpp index e0e4d29..cff4632 100644 --- a/ESP/lib/src/network/mDNS/MDNSManager.cpp +++ b/ESP/lib/src/network/mDNS/MDNSManager.cpp @@ -6,7 +6,8 @@ MDNSHandler::MDNSHandler(ProjectConfig& configManager) bool MDNSHandler::startMDNS() { const std::string service = "_openiristracker"; auto mdnsConfig = configManager.getMDNSConfig(); - if (!MDNS.begin(mdnsConfig.hostname.c_str())) // lowercase only - as this will be the url + if (!MDNS.begin(mdnsConfig.hostname + .c_str())) // lowercase only - as this will be the url { mdnsStateManager.setState(MDNSState_e::MDNSState_Error); log_e("Error initializing MDNS"); diff --git a/ESP/lib/src/network/wifihandler/wifihandler.cpp b/ESP/lib/src/network/wifihandler/wifihandler.cpp index 8693eb2..f8d3626 100644 --- a/ESP/lib/src/network/wifihandler/wifihandler.cpp +++ b/ESP/lib/src/network/wifihandler/wifihandler.cpp @@ -1,7 +1,4 @@ #include "wifihandler.hpp" -#include -#include "data/StateManager/StateManager.hpp" -#include "data/utilities/helpers.hpp" WiFiHandler::WiFiHandler(ProjectConfig& configManager, const std::string& ssid, @@ -18,10 +15,11 @@ WiFiHandler::WiFiHandler(ProjectConfig& configManager, WiFiHandler::~WiFiHandler() {} void WiFiHandler::begin() { - - // just to be sure, we reeset everything before we do anything, some boards were having problems otherwise + // just to be sure, we reeset everything before we do anything, some boards + // were having problems otherwise WiFi.disconnect(); - // we purposefully set the lowest min required security level, some boards have problems connecting otherwise + // we purposefully set the lowest min required security level, some boards + // have problems connecting otherwise // https://github.com/espressif/arduino-esp32/issues/8770 WiFi.setMinSecurity(WIFI_AUTH_WEP); @@ -33,7 +31,9 @@ void WiFiHandler::begin() { return; } - log_d("ADHOC is disabled, setting up STA network and checking transmission power \n\r"); + log_d( + "ADHOC is disabled, setting up STA network and checking transmission " + "power \n\r"); auto txpower = configManager.getWiFiTxPowerConfig(); log_d("Setting Wifi Power to: %d", txpower.power); log_d("Setting WiFi sleep mode to NONE \n\r"); @@ -46,14 +46,9 @@ void WiFiHandler::begin() { if (networks.empty()) { log_i("No networks found in config, trying the default one \n\r"); - - if (this->iniSTA( - this->ssid, - this->password, - this->channel, - (wifi_power_t)txpower.power - ) - ) { + + if (this->iniSTA(this->ssid, this->password, this->channel, + (wifi_power_t)txpower.power)) { return; } @@ -139,12 +134,11 @@ bool WiFiHandler::iniSTA(const std::string& ssid, const std::string& password, uint8_t channel, wifi_power_t power) { - - // since networks may not have a password, we only need to check if we have an ssid - // bail if we don't - if (ssid == ""){ + // since networks may not have a password, we only need to check if we have an + // ssid bail if we don't + if (ssid == "") { log_d("ssid missing, bailing"); - return false; + return false; } unsigned long currentMillis = millis(); @@ -159,8 +153,9 @@ bool WiFiHandler::iniSTA(const std::string& ssid, INADDR_NONE); // need to call before setting hostname log_d("Setting hostname %s \n\r"); WiFi.setHostname(mdnsConfig.hostname.c_str()); - log_i("Setting TX power to: %d \n\r", (uint8_t)power); - WiFi.setTxPower(power); // https://github.com/espressif/arduino-esp32/issues/5698 + log_i("Setting TX power to: %d \n\r", (uint8_t)power); + WiFi.setTxPower( + power); // https://github.com/espressif/arduino-esp32/issues/5698 WiFi.begin(ssid.c_str(), password.c_str(), channel); log_d("Waiting for WiFi to connect... \n\r"); diff --git a/ESP/lib/src/network/wifihandler/wifihandler.hpp b/ESP/lib/src/network/wifihandler/wifihandler.hpp index fd187d7..acae52e 100644 --- a/ESP/lib/src/network/wifihandler/wifihandler.hpp +++ b/ESP/lib/src/network/wifihandler/wifihandler.hpp @@ -1,8 +1,12 @@ #pragma once #ifndef WIFIHANDLER_HPP #define WIFIHANDLER_HPP +#include #include +#include "data/StateManager/StateManager.hpp" #include "data/config/project_config.hpp" +#include "data/utilities/helpers.hpp" + #include "data/utilities/Observer.hpp" class WiFiHandler : public IObserver { @@ -29,7 +33,6 @@ class WiFiHandler : public IObserver { ProjectConfig& configManager; - bool _enable_adhoc; std::string ssid; std::string password; diff --git a/ESP/src/main.cpp b/ESP/src/main.cpp index db05ca3..3badeec 100644 --- a/ESP/src/main.cpp +++ b/ESP/src/main.cpp @@ -6,18 +6,16 @@ * @param mdnsName The mDNS hostname to use */ ProjectConfig deviceConfig("openiris", MDNS_HOSTNAME); -CommandManager commandManager(&deviceConfig); +CommandManager commandManager(deviceConfig); SerialManager serialManager(&commandManager); #ifdef CONFIG_CAMERA_MODULE_ESP32S3_XIAO_SENSE LEDManager ledManager(LED_BUILTIN); - #elif CONFIG_CAMERA_MODULE_SWROOM_BABBLE_S3 LEDManager ledManager(38); - #else LEDManager ledManager(33); -#endif // ESP32S3_XIAO_SENSE +#endif #ifndef SIM_ENABLED CameraHandler cameraHandler(deviceConfig); @@ -30,10 +28,10 @@ WiFiHandler wifiHandler(deviceConfig, WIFI_CHANNEL, ENABLE_ADHOC); MDNSHandler mdnsHandler(deviceConfig); -#ifdef SIM_ENABLED -APIServer apiServer(deviceConfig, wifiStateManager, "/control"); -#else + APIServer apiServer(deviceConfig, cameraHandler, "/control"); + +#ifndef SIM_ENABLED StreamServer streamServer; #endif // SIM_ENABLED @@ -45,37 +43,22 @@ void etvr_eye_tracker_web_init() { log_d("[SETUP]: Starting MDNS Handler"); mdnsHandler.startMDNS(); - switch (wifiStateManager.getCurrentState()) { - case WiFiState_e::WiFiState_Disconnected: { - //! TODO: Implement - break; - } - case WiFiState_e::WiFiState_ADHOC: { + auto wifiState = wifiStateManager.getCurrentState(); #ifndef SIM_ENABLED + if (wifiState == WiFiState_e::WiFiState_Connected || + wifiState == WiFiState_e::WiFiState_ADHOC) { + { log_d("[SETUP]: Starting Stream Server"); - streamServer.startStreamServer(); -#endif // SIM_ENABLED + auto result = streamServer.startTCPStreamServer(); + // streamServer.startStreamServer(); + // auto result = streamServer.startUDPStreamServer(); + + log_d("[SETUP]: Stream Server state: %s", + result ? "Connected" : "Failed to connect"); log_d("[SETUP]: Starting API Server"); apiServer.setup(); - break; } - case WiFiState_e::WiFiState_Connected: { -#ifndef SIM_ENABLED - log_d("[SETUP]: Starting Stream Server"); - streamServer.startStreamServer(); #endif // SIM_ENABLED - log_d("[SETUP]: Starting API Server"); - apiServer.setup(); - break; - } - case WiFiState_e::WiFiState_Connecting: { - //! TODO: Implement - break; - } - case WiFiState_e::WiFiState_Error: { - //! TODO: Implement - break; - } } } #endif // ETVR_EYE_TRACKER_WEB_API @@ -86,22 +69,10 @@ void setup() { Logo::printASCII(); ledManager.begin(); - #ifdef CONFIG_CAMERA_MODULE_SWROOM_BABBLE_S3 // Set IR emitter strength to 100%. - const int ledPin = 1; // Replace this with a command endpoint eventually. - const int freq = 5000; - const int ledChannel = 0; - const int resolution = 8; - const int dutyCycle = 255; - ledcSetup(ledChannel, freq, resolution); - ledcAttachPin(1, ledChannel); - ledcWrite(ledChannel, dutyCycle); - #endif - #ifndef SIM_ENABLED deviceConfig.attach(cameraHandler); #endif // SIM_ENABLED deviceConfig.load(); - serialManager.init(); #ifndef ETVR_EYE_TRACKER_USB_API @@ -112,6 +83,10 @@ void setup() { } void loop() { +#ifndef ETVR_EYE_TRACKER_USB_API + streamServer.sendTCPFrame(); +#endif + // streamServer.sendUDPFrame(); ledManager.handleLED(); serialManager.run(); } From 27177e411ccae78ee1a12d2b4a5e99891d6b2298 Mon Sep 17 00:00:00 2001 From: Lorow Date: Thu, 15 Aug 2024 20:15:09 +0200 Subject: [PATCH 2/8] Rewrite commands into a more modern form in anticipation for API rewrite --- ESP/lib/src/data/CommandManager/Command.cpp | 41 ++++++ ESP/lib/src/data/CommandManager/Command.hpp | 85 +++++++++++ .../data/CommandManager/CommandManager.cpp | 138 +++++++++++------- .../data/CommandManager/CommandManager.hpp | 58 ++++++-- ESP/lib/src/io/Serial/SerialManager.cpp | 2 +- ESP/lib/src/io/Serial/SerialManager.hpp | 4 +- 6 files changed, 259 insertions(+), 69 deletions(-) create mode 100644 ESP/lib/src/data/CommandManager/Command.cpp create mode 100644 ESP/lib/src/data/CommandManager/Command.hpp diff --git a/ESP/lib/src/data/CommandManager/Command.cpp b/ESP/lib/src/data/CommandManager/Command.cpp new file mode 100644 index 0000000..e216656 --- /dev/null +++ b/ESP/lib/src/data/CommandManager/Command.cpp @@ -0,0 +1,41 @@ +#include "Command.hpp" + +CommandResult PingCommand::execute() { + return CommandResult::getSuccessResult("pong"); +} + +CommandResult SetWiFiCommand::validate() { + if (!data.containsKey("ssid")) + return CommandResult::getErrorResult("{\"error\": \"Missing ssid\"}"); + if (!data.containsKey("password")) + return CommandResult::getErrorResult("{\"error\": \"Missing password\"}"); + return CommandResult::getSuccessResult(""); +} + +CommandResult SetWiFiCommand::execute() { + std::string network_name = "main"; + if (data.containsKey("network_name")) + network_name = data["network_name"].as(); + + projectConfig.setWifiConfig(network_name, data["ssid"], data["password"], 0, + 0, false, false); + + return CommandResult::getSuccessResult("WIFI SET"); +} + +CommandResult SetMDNSCommand::validate() { + if (!data.containsKey("hostname") || !strlen(data["hostname"])) + return CommandResult::getErrorResult("{\"error\": \"Missing hostname\"}"); + + return CommandResult::getSuccessResult(""); +} + +CommandResult SetMDNSCommand::execute() { + projectConfig.setMDNSConfig(data["hostname"], "openiristracker", false); + return CommandResult::getSuccessResult("MDNS SET"); +} + +CommandResult SaveConfigCommand::execute() { + projectConfig.save(); + return CommandResult::getSuccessResult("CONFIG SAVED"); +} \ No newline at end of file diff --git a/ESP/lib/src/data/CommandManager/Command.hpp b/ESP/lib/src/data/CommandManager/Command.hpp new file mode 100644 index 0000000..067cdae --- /dev/null +++ b/ESP/lib/src/data/CommandManager/Command.hpp @@ -0,0 +1,85 @@ +#ifndef COMMAND_HPP +#define COMMAND_HPP +#include +#include +#include +#include +#include "data/config/project_config.hpp" + +class CommandResult { + private: + // or maybe std::optional? + std::optional successMessage; + std::optional errorMessage; + + public: + CommandResult(std::optional successMessage, + std::optional errorMessage) + : successMessage(successMessage), errorMessage(errorMessage) {} + + bool isSuccess() const { return successMessage.has_value(); } + + static CommandResult getSuccessResult(std::string message) { + return CommandResult(message, std::nullopt); + } + + static CommandResult getErrorResult(std::string message) { + return CommandResult(std::nullopt, message); + } + + std::string getSuccessMessage() const { return successMessage.value(); }; + std::string getErrorMessage() const { return errorMessage.value(); } +}; + +class ICommand { + public: + virtual CommandResult validate() = 0; + virtual CommandResult execute() = 0; + virtual ~ICommand() = default; +}; + +class PingCommand : public ICommand { + public: + CommandResult validate() override { + return CommandResult::getSuccessResult(""); + }; + CommandResult execute() override; +}; + +class SetWiFiCommand : public ICommand { + ProjectConfig& projectConfig; + JsonVariant data; + + public: + SetWiFiCommand(ProjectConfig& projectConfig, JsonVariant data) + : projectConfig(projectConfig), data(data) {} + CommandResult validate() override; + CommandResult execute() override; +}; + +class SetMDNSCommand : public ICommand { + ProjectConfig& projectConfig; + JsonVariant data; + + public: + SetMDNSCommand(ProjectConfig& projectConfig, JsonVariant data) + : projectConfig(projectConfig), data(data) {} + CommandResult validate() override; + CommandResult execute() override; +}; + +class SaveConfigCommand : public ICommand { + ProjectConfig& projectConfig; + + public: + SaveConfigCommand(ProjectConfig& projectConfig) + : projectConfig(projectConfig) {} + + CommandResult validate() override { + return CommandResult::getSuccessResult(""); + }; + + CommandResult execute() override; +}; + +#endif \ No newline at end of file diff --git a/ESP/lib/src/data/CommandManager/CommandManager.cpp b/ESP/lib/src/data/CommandManager/CommandManager.cpp index 02aaeba..1a5677d 100644 --- a/ESP/lib/src/data/CommandManager/CommandManager.cpp +++ b/ESP/lib/src/data/CommandManager/CommandManager.cpp @@ -1,9 +1,20 @@ #include "CommandManager.hpp" -CommandManager::CommandManager(ProjectConfig* deviceConfig) - : deviceConfig(deviceConfig) {} +std::unique_ptr CommandManager::createCommand(CommandType commandType, + JsonVariant& data) { + switch (commandType) { + case CommandType::PING: + return std::make_unique(); + case CommandType::SET_WIFI: + return std::make_unique(this->projectConfig, data); + case CommandType::SET_MDNS: + return std::make_unique(this->projectConfig, data); + case CommandType::SAVE_CONFIG: + return std::make_unique(this->projectConfig); + } +} -const CommandType CommandManager::getCommandType(JsonVariant& command) { +CommandType CommandManager::getCommandType(JsonVariant& command) { if (!command.containsKey("command")) return CommandType::None; @@ -18,64 +29,87 @@ bool CommandManager::hasDataField(JsonVariant& command) { return command.containsKey("data"); } -void CommandManager::handleCommands(CommandsPayload commandsPayload) { +std::variant, CommandResult> +CommandManager::handleBatchCommands(CommandsPayload commandsPayload) { + std::vector results = {}; + std::vector errors = {}; + std::vector> commands; + if (!commandsPayload.data.containsKey("commands")) { - log_e("Json data sent not supported, lacks commands field"); - return; + std::string error = "Json data sent not supported, lacks commands field"; + log_e("%s", error.c_str()); + return CommandResult::getErrorResult( + Helpers::format_string("\"error\":\"%s\"", error)); } for (JsonVariant commandData : commandsPayload.data["commands"].as()) { - this->handleCommand(commandData); + auto command_or_result = this->createCommandFromJsonVariant(commandData); + + if (auto command_ptr = + std::get_if>(&command_or_result)) { + auto validation_result = (*command_ptr)->validate(); + if (validation_result.isSuccess()) + commands.emplace_back(std::move((*command_ptr))); + else + errors.push_back(validation_result.getErrorMessage()); + } else { + errors.push_back( + std::get(command_or_result).getErrorMessage()); + continue; + } } - this->deviceConfig->save(); + // if we have any errors, consolidate them into a single message and return + if (errors.size() > 0) { + return CommandResult::getErrorResult(Helpers::format_string( + "\"error\":\"[%s]\"", this->join_strings(errors, ","))); + } + + for (auto& valid_command : commands) { + results.push_back(valid_command->execute()); + } + + return results; } -void CommandManager::handleCommand(JsonVariant command) { - auto command_type = this->getCommandType(command); +CommandResult CommandManager::handleSingleCommand( + CommandsPayload commandsPayload) { + if (!commandsPayload.data.containsKey("command")) { + std::string error = "Json data sent not supported, lacks commands field"; + log_e("%s", error.c_str()); - switch (command_type) { - case CommandType::SET_WIFI: { - if (!this->hasDataField(command)) - // malformed command, lacked data field - break; - - if (!command["data"].containsKey("ssid") || - !command["data"].containsKey("password")) - break; - - std::string customNetworkName = "main"; - if (command["data"].containsKey("network_name")) - customNetworkName = command["data"]["network_name"].as(); - - this->deviceConfig->setWifiConfig(customNetworkName, - command["data"]["ssid"], - command["data"]["password"], - 0, // channel, should this be zero? - 0, // power, should this be zero? - false, false); - - break; - } - case CommandType::SET_MDNS: { - if (!this->hasDataField(command)) - break; - - if (!command["data"].containsKey("hostname") || - !strlen(command["data"]["hostname"])) - break; - - this->deviceConfig->setMDNSConfig(command["data"]["hostname"], - "openiristracker", false); - - break; - } - case CommandType::PING: { - Serial.println("PONG \n\r"); - break; - } - default: - break; + CommandResult::getErrorResult( + Helpers::format_string("\"error\":\"%s\"", error)); } + + JsonVariant commandData = commandsPayload.data["command"]; + auto command_or_result = this->createCommandFromJsonVariant(commandData); + + if (std::holds_alternative(command_or_result)) { + return std::get(command_or_result); + } + + auto command = + std::move(std::get>(command_or_result)); + + auto validation_result = command->validate(); + if (!validation_result.isSuccess()) { + return validation_result; + }; + + return command->execute(); +} + +std::variant, CommandResult> +CommandManager::createCommandFromJsonVariant(JsonVariant& command) { + auto command_type = this->getCommandType(command); + if (command_type == CommandType::None) { + std::string error = + Helpers::format_string("Command not supported: %s", command["command"]); + log_e("%s", error.c_str()); + throw CommandResult::getErrorResult( + Helpers::format_string("\"error\":\"%s\"", error)); + } + return this->createCommand(command_type, command); } \ No newline at end of file diff --git a/ESP/lib/src/data/CommandManager/CommandManager.hpp b/ESP/lib/src/data/CommandManager/CommandManager.hpp index 55440f4..c122042 100644 --- a/ESP/lib/src/data/CommandManager/CommandManager.hpp +++ b/ESP/lib/src/data/CommandManager/CommandManager.hpp @@ -2,37 +2,69 @@ #ifndef TASK_MANAGER_HPP #define TASK_MANAGER_HPP #include +#include +#include #include +#include + +#include +#include +#include +#include +#include + +#include "data/CommandManager/Command.hpp" #include "data/config/project_config.hpp" +struct CommandsPayload { + JsonVariant data; +}; + enum CommandType { None, PING, SET_WIFI, SET_MDNS, + SAVE_CONFIG, }; -struct CommandsPayload { - JsonDocument data; +const std::unordered_map commandMap = { + {"ping", CommandType::PING}, + {"set_wifi", CommandType::SET_WIFI}, + {"set_mdns", CommandType::SET_MDNS}, }; class CommandManager { private: - const std::unordered_map commandMap = { - {"ping", CommandType::PING}, - {"set_wifi", CommandType::SET_WIFI}, - {"set_mdns", CommandType::SET_MDNS}, - }; + ProjectConfig& projectConfig; - ProjectConfig* deviceConfig; + std::string join_strings(std::vector const& strings, + std::string delim) { + std::stringstream ss; + std::copy(strings.begin(), strings.end(), + std::ostream_iterator(ss, delim.c_str())); + return ss.str(); + } bool hasDataField(JsonVariant& command); - void handleCommand(JsonVariant command); - const CommandType getCommandType(JsonVariant& command); + std::unique_ptr createCommand(CommandType commandType, + JsonVariant& data); + + std::variant, CommandResult> + createCommandFromJsonVariant(JsonVariant& command); + + CommandType getCommandType(JsonVariant& command); + + // // TODO rewrite the API + // // TODO add FPS/ Freq / cropping to the API + // // TODO rewrite camera handler to be simpler and easier to change public: - CommandManager(ProjectConfig* deviceConfig); - void handleCommands(CommandsPayload commandsPayload); -}; + CommandManager(ProjectConfig& projectConfig) + : projectConfig(projectConfig) {}; + CommandResult handleSingleCommand(CommandsPayload commandsPayload); + std::variant, CommandResult> handleBatchCommands( + CommandsPayload commandsPayload); +}; #endif \ No newline at end of file diff --git a/ESP/lib/src/io/Serial/SerialManager.cpp b/ESP/lib/src/io/Serial/SerialManager.cpp index b065fd3..f5a1cc5 100644 --- a/ESP/lib/src/io/Serial/SerialManager.cpp +++ b/ESP/lib/src/io/Serial/SerialManager.cpp @@ -72,7 +72,7 @@ void SerialManager::run() { } CommandsPayload commands = {doc}; - this->commandManager->handleCommands(commands); + this->commandManager->handleBatchCommands(commands); } #ifdef ETVR_EYE_TRACKER_USB_API else { diff --git a/ESP/lib/src/io/Serial/SerialManager.hpp b/ESP/lib/src/io/Serial/SerialManager.hpp index 9e1eaf8..6733d36 100644 --- a/ESP/lib/src/io/Serial/SerialManager.hpp +++ b/ESP/lib/src/io/Serial/SerialManager.hpp @@ -8,9 +8,7 @@ #include #include "data/CommandManager/CommandManager.hpp" #include "data/config/project_config.hpp" - -const char* const ETVR_HEADER = "\xff\xa0"; -const char* const ETVR_HEADER_FRAME = "\xff\xa1"; +#include "data/utilities/helpers.hpp" enum QueryAction { READY_TO_RECEIVE, From 48e1e8de179e8f080c9bfcfb3798fbbe74edefef Mon Sep 17 00:00:00 2001 From: Lorow Date: Thu, 15 Aug 2024 20:17:11 +0200 Subject: [PATCH 3/8] initial experimentation with streaming over UDP/TCP --- ESP/lib/src/io/camera/cameraHandler.cpp | 3 +- ESP/lib/src/network/api/webserverHandler.cpp | 7 +- ESP/lib/src/network/stream/streamServer.cpp | 338 +++++++++++++------ ESP/lib/src/network/stream/streamServer.hpp | 49 ++- 4 files changed, 279 insertions(+), 118 deletions(-) diff --git a/ESP/lib/src/io/camera/cameraHandler.cpp b/ESP/lib/src/io/camera/cameraHandler.cpp index 7b51bdb..e4213e6 100644 --- a/ESP/lib/src/io/camera/cameraHandler.cpp +++ b/ESP/lib/src/io/camera/cameraHandler.cpp @@ -16,7 +16,8 @@ void CameraHandler::setupCameraPinout() { // 16500000 optimal freq on ESP32-CAM (default) // 20000000 max freq on ESP32-CAM // 24000000 optimal freq on ESP32-S3 - int xclk_freq_hz = DEFAULT_XCLK_FREQ_HZ; + // int xclk_freq_hz = DEFAULT_XCLK_FREQ_HZ; + int xclk_freq_hz = USB_DEFAULT_XCLK_FREQ_HZ; #if CONFIG_CAMERA_MODULE_ESP_EYE /* IO13, IO14 is designed for JTAG by default, diff --git a/ESP/lib/src/network/api/webserverHandler.cpp b/ESP/lib/src/network/api/webserverHandler.cpp index 8193083..5a437f5 100644 --- a/ESP/lib/src/network/api/webserverHandler.cpp +++ b/ESP/lib/src/network/api/webserverHandler.cpp @@ -28,11 +28,10 @@ void APIServer::setup() { "^\\%s\\/([a-zA-Z0-9]+)\\/command\\/([a-zA-Z0-9]+)$", this->api_url.c_str()); log_d("API URL: %s", buffer); - server.on(buffer, 0b01111111, [&](AsyncWebServerRequest* request) { - handleRequest(request); - }); + server.on(buffer, 0b01111111, + [&](AsyncWebServerRequest* request) { handleRequest(request); }); #ifndef SIM_ENABLED - //this->_authRequired = true; + // this->_authRequired = true; #endif // SIM_ENABLED beginOTA(); server.begin(); diff --git a/ESP/lib/src/network/stream/streamServer.cpp b/ESP/lib/src/network/stream/streamServer.cpp index 909ecb5..f9f9cef 100644 --- a/ESP/lib/src/network/stream/streamServer.cpp +++ b/ESP/lib/src/network/stream/streamServer.cpp @@ -1,114 +1,250 @@ #include "streamServer.hpp" -constexpr static const char *STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; -constexpr static const char *STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; -constexpr static const char *STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n"; +constexpr static const char* STREAM_CONTENT_TYPE = + "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; +constexpr static const char* STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +constexpr static const char* STREAM_PART = + "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: " + "%d.%06d\r\n\r\n"; -esp_err_t StreamHelpers::stream(httpd_req_t *req) -{ - long last_request_time = 0; - camera_fb_t *fb = NULL; - struct timeval _timestamp; +esp_err_t StreamHelpers::stream(httpd_req_t* req) { + long last_request_time = 0; + camera_fb_t* fb = NULL; + struct timeval _timestamp; - esp_err_t res = ESP_OK; + esp_err_t res = ESP_OK; - size_t _jpg_buf_len = 0; - uint8_t *_jpg_buf = NULL; + size_t _jpg_buf_len = 0; + uint8_t* _jpg_buf = NULL; - char *part_buf[256]; + char* part_buf[256]; - static int64_t last_frame = 0; - if (!last_frame) - last_frame = esp_timer_get_time(); + static int64_t last_frame = 0; + if (!last_frame) + last_frame = esp_timer_get_time(); - res = httpd_resp_set_type(req, STREAM_CONTENT_TYPE); - if (res != ESP_OK) - return res; - - httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - httpd_resp_set_hdr(req, "X-Framerate", "60"); - - while (true) - { - fb = esp_camera_fb_get(); - if (!fb) - { - log_e("Camera capture failed with response: %s", esp_err_to_name(res)); - res = ESP_FAIL; - } - else - { - _timestamp.tv_sec = fb->timestamp.tv_sec; - _timestamp.tv_usec = fb->timestamp.tv_usec; - _jpg_buf_len = fb->len; - _jpg_buf = fb->buf; - } - if (res == ESP_OK) - res = httpd_resp_send_chunk(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY)); - if (res == ESP_OK) - { - size_t hlen = snprintf((char *)part_buf, 128, STREAM_PART, _jpg_buf_len, _timestamp.tv_sec, _timestamp.tv_usec); - res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); - } - if (res == ESP_OK) - res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); - if (fb) - { - esp_camera_fb_return(fb); - fb = NULL; - _jpg_buf = NULL; - } - else if (_jpg_buf) - { - free(_jpg_buf); - _jpg_buf = NULL; - } - if (res != ESP_OK) - break; - long request_end = millis(); - long latency = (request_end - last_request_time); - last_request_time = request_end; - log_d("Size: %uKB, Time: %ums (%ifps)\n", _jpg_buf_len / 1024, latency, 1000 / latency); - } - last_frame = 0; + res = httpd_resp_set_type(req, STREAM_CONTENT_TYPE); + if (res != ESP_OK) return res; -} -StreamServer::StreamServer(const int STREAM_PORT) : STREAM_SERVER_PORT(STREAM_PORT) {} + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + httpd_resp_set_hdr(req, "X-Framerate", "60"); -int StreamServer::startStreamServer() -{ - // WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //! Turn-off the 'brownout detector' - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - config.stack_size = 20480; - config.max_uri_handlers = 1; - config.server_port = this->STREAM_SERVER_PORT; - config.ctrl_port = this->STREAM_SERVER_PORT; - config.stack_size = 20480; - - httpd_uri_t stream_page = { - .uri = "/", - .method = HTTP_GET, - .handler = &StreamHelpers::stream, - .user_ctx = nullptr}; - - int status = httpd_start(&camera_stream, &config); - - if (status != ESP_OK) - return -1; - else - { - httpd_register_uri_handler(camera_stream, &stream_page); - Serial.println("Stream server initialized"); - switch (wifiStateManager.getCurrentState()) - { - case WiFiState_e::WiFiState_ADHOC: - Serial.printf("\n\rThe stream is under: http://%s:%i\n\r", WiFi.softAPIP().toString().c_str(), this->STREAM_SERVER_PORT); - break; - default: - Serial.printf("\n\rThe stream is under: http://%s:%i\n\r", WiFi.localIP().toString().c_str(), this->STREAM_SERVER_PORT); - break; - } - return 0; + while (true) { + fb = esp_camera_fb_get(); + if (!fb) { + log_e("Camera capture failed with response: %s", esp_err_to_name(res)); + res = ESP_FAIL; + } else { + _timestamp.tv_sec = fb->timestamp.tv_sec; + _timestamp.tv_usec = fb->timestamp.tv_usec; + _jpg_buf_len = fb->len; + _jpg_buf = fb->buf; } + if (res == ESP_OK) + res = + httpd_resp_send_chunk(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY)); + if (res == ESP_OK) { + size_t hlen = snprintf((char*)part_buf, 128, STREAM_PART, _jpg_buf_len, + _timestamp.tv_sec, _timestamp.tv_usec); + res = httpd_resp_send_chunk(req, (const char*)part_buf, hlen); + } + if (res == ESP_OK) + res = httpd_resp_send_chunk(req, (const char*)_jpg_buf, _jpg_buf_len); + if (fb) { + esp_camera_fb_return(fb); + fb = NULL; + _jpg_buf = NULL; + } else if (_jpg_buf) { + free(_jpg_buf); + _jpg_buf = NULL; + } + if (res != ESP_OK) + break; + long request_end = millis(); + long latency = (request_end - last_request_time); + last_request_time = request_end; + log_d("Size: %uKB, Time: %ums (%ifps)\n", _jpg_buf_len / 1024, latency, + 1000 / latency); + } + last_frame = 0; + return res; } + +StreamServer::StreamServer(const int STREAM_PORT) + : STREAM_SERVER_PORT(STREAM_PORT) { + memcpy(initial_packet_buffer, ETVR_HEADER, sizeof(ETVR_HEADER)); +} + +int StreamServer::startStreamServer() { + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.stack_size = 20480; + config.max_uri_handlers = 1; + config.server_port = this->STREAM_SERVER_PORT; + config.ctrl_port = this->STREAM_SERVER_PORT; + config.stack_size = 20480; + + httpd_uri_t stream_page = {.uri = "/", + .method = HTTP_GET, + .handler = &StreamHelpers::stream, + .user_ctx = nullptr}; + + int status = httpd_start(&camera_stream, &config); + + if (status != ESP_OK) + return -1; + else { + httpd_register_uri_handler(camera_stream, &stream_page); + Serial.println("Stream server initialized"); + switch (wifiStateManager.getCurrentState()) { + case WiFiState_e::WiFiState_ADHOC: + Serial.printf("\n\rThe stream is under: http://%s:%i\n\r", + WiFi.softAPIP().toString().c_str(), + this->STREAM_SERVER_PORT); + break; + default: + Serial.printf("\n\rThe stream is under: http://%s:%i\n\r", + WiFi.localIP().toString().c_str(), + this->STREAM_SERVER_PORT); + break; + } + return 0; + } +} + +bool StreamServer::startUDPStreamServer() { + socket = AsyncUDP(); + return socket.listen(this->STREAM_SERVER_PORT + 1); +} + +void StreamServer::sendUDPFrame() { + /////////////////////////////////////////////////////// + /////////////////////////////////////////////////////// + // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO + // + // ADD PROTOCOL VERSION + // + // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO + /////////////////////////////////////////////////////// + /////////////////////////////////////////////////////// + + if (!last_frame) + last_frame = esp_timer_get_time(); + + size_t len = 0; + uint8_t* buf = NULL; + + auto fb = esp_camera_fb_get(); + if (fb) { + len = fb->len; + buf = fb->buf; + } else { + log_e("Camera capture failed"); + return; + } + + // we're sending the initial header with the total number of chunks first + // we can then later detect new frame with the header packets + uint8_t totalChunks = (len + CHUNK_SIZE - 1) / CHUNK_SIZE; + initial_packet_buffer[sizeof(ETVR_HEADER)] = totalChunks; + socket.broadcastTo(initial_packet_buffer, sizeof(initial_packet_buffer), + this->STREAM_SERVER_PORT); + + for (uint8_t i = 0; i < totalChunks; i++) { + auto offset = i * CHUNK_SIZE; + // we need to make sure we don't overread + auto chunkSize = (offset + CHUNK_SIZE <= len) ? CHUNK_SIZE : len - offset; + packet_buffer[0] = static_cast(i); + // since this is a pointer, we can just add an offset to it, with a + // chunksize to read and we're done + memcpy(packet_buffer + 1, buf + offset, chunkSize); + socket.broadcastTo(packet_buffer, chunkSize + 1, this->STREAM_SERVER_PORT); + } + + if (fb) { + esp_camera_fb_return(fb); + fb = NULL; + buf = NULL; + } else if (buf) { + free(buf); + buf = NULL; + } + + long request_end = millis(); + long latency = request_end - last_request_time; + last_request_time = request_end; + + log_d("Size: %uKB, Time: %ums (%ifps) chunks: %u \n", len / 1024, latency, + 1000 / latency, totalChunks); +} + +bool StreamServer::startTCPStreamServer() { + tcp_server = new AsyncServer(this->STREAM_SERVER_PORT); + tcp_server->onClient( + [this](void* arg, AsyncClient* client) { + this->handleNewTCPClient(arg, client); + }, + tcp_server); + + tcp_server->begin(); + return true; +} + +void StreamServer::handleNewTCPClient(void* arg, AsyncClient* client) { + Serial.printf("Client connecting with ip: %s \n\r", + client->remoteIP().toString().c_str()); + + if (this->tcp_connected_client == nullptr) { + this->tcp_connected_client = client; + + this->tcp_connected_client->onError( + [this](void* arg, AsyncClient* client, int8_t error) { + Serial.printf("\n connection error %s from client %s \n", + client->errorToString(error), + client->remoteIP().toString().c_str()); + + this->tcp_connected_client = nullptr; + }); + + this->tcp_connected_client->onDisconnect( + [this](void* arg, AsyncClient* client) { + this->tcp_connected_client = nullptr; + }); + + Serial.println("Client connected!"); + } else { + client->close(); + Serial.println("Rejected client, only one connection allowed!"); + } +} + +void StreamServer::sendTCPFrame() { + if (this->tcp_connected_client == nullptr || + !this->tcp_connected_client->connected()) { + return; + } + + if (!last_frame) + last_frame = esp_timer_get_time(); + + auto fb = esp_camera_fb_get(); + if (!fb) { + log_e("Camera capture failed"); + return; + } + + size_t len = fb->len; + + this->tcp_connected_client->write(ETVR_HEADER_BYTES, 4); + this->tcp_connected_client->write((const char*)fb->buf, fb->len); + + if (fb) { + esp_camera_fb_return(fb); + } + + long request_end = millis(); + long latency = request_end - last_request_time; + last_request_time = request_end; + log_d("Size: %uKB, Time: %ums (%ifps)\n", len / 1024, latency, + 1000 / latency); +} \ No newline at end of file diff --git a/ESP/lib/src/network/stream/streamServer.hpp b/ESP/lib/src/network/stream/streamServer.hpp index 1157313..a6d1a1f 100644 --- a/ESP/lib/src/network/stream/streamServer.hpp +++ b/ESP/lib/src/network/stream/streamServer.hpp @@ -3,8 +3,15 @@ #define STREAM_SERVER_HPP #define PART_BOUNDARY "123456789000000000000987654321" #include +#include +#include #include + +#include +#include + #include "data/StateManager/StateManager.hpp" +#include "data/utilities/helpers.hpp" // Camera includes #include "esp_camera.h" @@ -13,20 +20,38 @@ #include "fb_gfx.h" #include "img_converters.h" -namespace StreamHelpers -{ - esp_err_t stream(httpd_req_t *req); +constexpr int CHUNK_SIZE = 1323; // old: 1024; + +namespace StreamHelpers { + esp_err_t stream(httpd_req_t* req); } -class StreamServer -{ +// namespace StreamHelpers +class StreamServer { + private: + AsyncUDP socket; + AsyncServer* tcp_server; + AsyncClient* tcp_connected_client; + httpd_handle_t camera_stream = nullptr; + uint8_t initial_packet_buffer[6]; + uint8_t packet_buffer[CHUNK_SIZE]; -private: - httpd_handle_t camera_stream = nullptr; - int STREAM_SERVER_PORT; + int64_t last_frame = 0; + long last_request_time = 0; -public: - StreamServer(const int STREAM_PORT = 80); - int startStreamServer(); + int STREAM_SERVER_PORT; + + public: + StreamServer(const int STREAM_PORT = 80); + int startStreamServer(); + bool startUDPStreamServer(); + bool startTCPStreamServer(); + + // rewrite this to an RTOS task pinned to the second core, for testing this is + // fine https://randomnerdtutorials.com/esp32-dual-core-arduino-ide/ + void sendUDPFrame(); + + void sendTCPFrame(); + void handleNewTCPClient(void* arg, AsyncClient* client); }; -#endif // STREAM_SERVER_HPP +#endif // STREAM_SERVER_HPP From 7bf33d8c45895b52898fb6c85276a843dd70b6a4 Mon Sep 17 00:00:00 2001 From: Lorow Date: Sat, 17 Aug 2024 00:05:59 +0200 Subject: [PATCH 4/8] simplify command result creation, add saving config in serial manager, fix bug in handleSingleCommand --- ESP/lib/src/data/CommandManager/Command.cpp | 13 +++++----- ESP/lib/src/data/CommandManager/Command.hpp | 17 +++++++++--- .../data/CommandManager/CommandManager.cpp | 26 ++++++++++++------- .../data/CommandManager/CommandManager.hpp | 2 +- ESP/lib/src/io/Serial/SerialManager.cpp | 22 ++++++++++++++-- 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/ESP/lib/src/data/CommandManager/Command.cpp b/ESP/lib/src/data/CommandManager/Command.cpp index e216656..e5c41a8 100644 --- a/ESP/lib/src/data/CommandManager/Command.cpp +++ b/ESP/lib/src/data/CommandManager/Command.cpp @@ -6,9 +6,9 @@ CommandResult PingCommand::execute() { CommandResult SetWiFiCommand::validate() { if (!data.containsKey("ssid")) - return CommandResult::getErrorResult("{\"error\": \"Missing ssid\"}"); + return CommandResult::getErrorResult("Missing ssid"); if (!data.containsKey("password")) - return CommandResult::getErrorResult("{\"error\": \"Missing password\"}"); + return CommandResult::getErrorResult("Missing password"); return CommandResult::getSuccessResult(""); } @@ -19,20 +19,21 @@ CommandResult SetWiFiCommand::execute() { projectConfig.setWifiConfig(network_name, data["ssid"], data["password"], 0, 0, false, false); - - return CommandResult::getSuccessResult("WIFI SET"); + return CommandResult::getSuccessResult("WIFI Set to: " + + data["ssid"].as()); } CommandResult SetMDNSCommand::validate() { if (!data.containsKey("hostname") || !strlen(data["hostname"])) - return CommandResult::getErrorResult("{\"error\": \"Missing hostname\"}"); + return CommandResult::getErrorResult("Missing hostname"); return CommandResult::getSuccessResult(""); } CommandResult SetMDNSCommand::execute() { projectConfig.setMDNSConfig(data["hostname"], "openiristracker", false); - return CommandResult::getSuccessResult("MDNS SET"); + return CommandResult::getSuccessResult("MDNS set to:" + + data["hostname"].as()); } CommandResult SaveConfigCommand::execute() { diff --git a/ESP/lib/src/data/CommandManager/Command.hpp b/ESP/lib/src/data/CommandManager/Command.hpp index 067cdae..895a2a1 100644 --- a/ESP/lib/src/data/CommandManager/Command.hpp +++ b/ESP/lib/src/data/CommandManager/Command.hpp @@ -8,14 +8,23 @@ class CommandResult { private: - // or maybe std::optional? std::optional successMessage; std::optional errorMessage; public: - CommandResult(std::optional successMessage, - std::optional errorMessage) - : successMessage(successMessage), errorMessage(errorMessage) {} + CommandResult(std::optional success_message, + std::optional error_message) { + if (success_message.has_value()) { + this->successMessage = + "{\"message\":\"" + success_message.value() + "\"}"; + } else + this->successMessage = std::nullopt; + + if (error_message.has_value()) + this->errorMessage = "{\"error\":\"" + error_message.value() + "\"}"; + else + this->errorMessage = std::nullopt; + } bool isSuccess() const { return successMessage.has_value(); } diff --git a/ESP/lib/src/data/CommandManager/CommandManager.cpp b/ESP/lib/src/data/CommandManager/CommandManager.cpp index 1a5677d..ac0f1a5 100644 --- a/ESP/lib/src/data/CommandManager/CommandManager.cpp +++ b/ESP/lib/src/data/CommandManager/CommandManager.cpp @@ -38,8 +38,7 @@ CommandManager::handleBatchCommands(CommandsPayload commandsPayload) { if (!commandsPayload.data.containsKey("commands")) { std::string error = "Json data sent not supported, lacks commands field"; log_e("%s", error.c_str()); - return CommandResult::getErrorResult( - Helpers::format_string("\"error\":\"%s\"", error)); + return CommandResult::getErrorResult(error); } for (JsonVariant commandData : @@ -62,8 +61,8 @@ CommandManager::handleBatchCommands(CommandsPayload commandsPayload) { // if we have any errors, consolidate them into a single message and return if (errors.size() > 0) { - return CommandResult::getErrorResult(Helpers::format_string( - "\"error\":\"[%s]\"", this->join_strings(errors, ","))); + return CommandResult::getErrorResult( + Helpers::format_string("\"[%s]\"", this->join_strings(errors, ", "))); } for (auto& valid_command : commands) { @@ -79,11 +78,10 @@ CommandResult CommandManager::handleSingleCommand( std::string error = "Json data sent not supported, lacks commands field"; log_e("%s", error.c_str()); - CommandResult::getErrorResult( - Helpers::format_string("\"error\":\"%s\"", error)); + CommandResult::getErrorResult(error); } - JsonVariant commandData = commandsPayload.data["command"]; + JsonVariant commandData = commandsPayload.data; auto command_or_result = this->createCommandFromJsonVariant(commandData); if (std::holds_alternative(command_or_result)) { @@ -108,8 +106,16 @@ CommandManager::createCommandFromJsonVariant(JsonVariant& command) { std::string error = Helpers::format_string("Command not supported: %s", command["command"]); log_e("%s", error.c_str()); - throw CommandResult::getErrorResult( - Helpers::format_string("\"error\":\"%s\"", error)); + return CommandResult::getErrorResult(error); } - return this->createCommand(command_type, command); + + if (!this->hasDataField(command)) { + std::string error = Helpers::format_string( + "Command is missing data field: %s", command["command"]); + log_e("%s", error.c_str()); + return CommandResult::getErrorResult(error); + } + + auto command_data = command["data"].as(); + return this->createCommand(command_type, command_data); } \ No newline at end of file diff --git a/ESP/lib/src/data/CommandManager/CommandManager.hpp b/ESP/lib/src/data/CommandManager/CommandManager.hpp index c122042..560b5d2 100644 --- a/ESP/lib/src/data/CommandManager/CommandManager.hpp +++ b/ESP/lib/src/data/CommandManager/CommandManager.hpp @@ -32,7 +32,7 @@ const std::unordered_map commandMap = { {"ping", CommandType::PING}, {"set_wifi", CommandType::SET_WIFI}, {"set_mdns", CommandType::SET_MDNS}, -}; + {"save_config", CommandType::SAVE_CONFIG}}; class CommandManager { private: diff --git a/ESP/lib/src/io/Serial/SerialManager.cpp b/ESP/lib/src/io/Serial/SerialManager.cpp index f5a1cc5..be363c6 100644 --- a/ESP/lib/src/io/Serial/SerialManager.cpp +++ b/ESP/lib/src/io/Serial/SerialManager.cpp @@ -67,12 +67,30 @@ void SerialManager::run() { if (deserializationError) { log_e("Command deserialization failed: %s", deserializationError.c_str()); - return; } CommandsPayload commands = {doc}; - this->commandManager->handleBatchCommands(commands); + auto results = this->commandManager->handleBatchCommands(commands); + + if (std::holds_alternative(results)) { + auto error = std::get(results); + Serial.printf("%s \n\r", error.getErrorMessage().c_str()); + } else { + for (auto& result : std::get>(results)) { + Serial.printf("%s \n\r", result.getSuccessMessage().c_str()); + } + // we should only call save on the config when the commands where + // successful, no point otherwise + + // also, I'm not really vibing with havin to create + // an entire JsonDocument for this, though it's light + JsonDocument saveCommanddDoc; + saveCommanddDoc["command"] = "save_config"; + saveCommanddDoc["data"].to(); + CommandsPayload saveCommandPayload = {saveCommanddDoc}; + this->commandManager->handleSingleCommand(saveCommandPayload); + } } #ifdef ETVR_EYE_TRACKER_USB_API else { From 6241cf9addf3cf061506f6b6d8000c1e61640577 Mon Sep 17 00:00:00 2001 From: Lorow Date: Sat, 17 Aug 2024 22:57:29 +0200 Subject: [PATCH 5/8] simplify and document handleBatchCommands --- .../data/CommandManager/CommandManager.cpp | 31 +++++++++++++------ .../data/CommandManager/CommandManager.hpp | 3 +- ESP/lib/src/io/Serial/SerialManager.cpp | 22 ++++++------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/ESP/lib/src/data/CommandManager/CommandManager.cpp b/ESP/lib/src/data/CommandManager/CommandManager.cpp index ac0f1a5..e4ffc87 100644 --- a/ESP/lib/src/data/CommandManager/CommandManager.cpp +++ b/ESP/lib/src/data/CommandManager/CommandManager.cpp @@ -29,11 +29,11 @@ bool CommandManager::hasDataField(JsonVariant& command) { return command.containsKey("data"); } -std::variant, CommandResult> -CommandManager::handleBatchCommands(CommandsPayload commandsPayload) { - std::vector results = {}; - std::vector errors = {}; +CommandResult CommandManager::handleBatchCommands( + CommandsPayload commandsPayload) { std::vector> commands; + std::vector results = {}; + std::vector errors = {}; if (!commandsPayload.data.containsKey("commands")) { std::string error = "Json data sent not supported, lacks commands field"; @@ -41,10 +41,15 @@ CommandManager::handleBatchCommands(CommandsPayload commandsPayload) { return CommandResult::getErrorResult(error); } + // we first try to create a command based on the payload + // if it's not supported, we register that as an error + // then, we try to validate the command, if it's succeful + // we add it to the list of commands to execute + // otherwise - you guessed it, error + // we only execute them if no errors were registered for (JsonVariant commandData : commandsPayload.data["commands"].as()) { auto command_or_result = this->createCommandFromJsonVariant(commandData); - if (auto command_ptr = std::get_if>(&command_or_result)) { auto validation_result = (*command_ptr)->validate(); @@ -60,16 +65,24 @@ CommandManager::handleBatchCommands(CommandsPayload commandsPayload) { } // if we have any errors, consolidate them into a single message and return - if (errors.size() > 0) { + if (errors.size() > 0) return CommandResult::getErrorResult( Helpers::format_string("\"[%s]\"", this->join_strings(errors, ", "))); - } for (auto& valid_command : commands) { - results.push_back(valid_command->execute()); + auto result = valid_command->execute(); + if (result.isSuccess()) { + results.push_back(result.getSuccessMessage()); + } else { + // since we're executing them already, and we've encountered an error + // we should add it to regular results + results.push_back(result.getErrorMessage()); + } } - return results; + return CommandResult::getErrorResult( + Helpers::format_string("\"[%s]\"", this->join_strings(results, ", "))); + ; } CommandResult CommandManager::handleSingleCommand( diff --git a/ESP/lib/src/data/CommandManager/CommandManager.hpp b/ESP/lib/src/data/CommandManager/CommandManager.hpp index 560b5d2..c94ab25 100644 --- a/ESP/lib/src/data/CommandManager/CommandManager.hpp +++ b/ESP/lib/src/data/CommandManager/CommandManager.hpp @@ -64,7 +64,6 @@ class CommandManager { : projectConfig(projectConfig) {}; CommandResult handleSingleCommand(CommandsPayload commandsPayload); - std::variant, CommandResult> handleBatchCommands( - CommandsPayload commandsPayload); + CommandResult handleBatchCommands(CommandsPayload commandsPayload); }; #endif \ No newline at end of file diff --git a/ESP/lib/src/io/Serial/SerialManager.cpp b/ESP/lib/src/io/Serial/SerialManager.cpp index be363c6..172bb44 100644 --- a/ESP/lib/src/io/Serial/SerialManager.cpp +++ b/ESP/lib/src/io/Serial/SerialManager.cpp @@ -71,25 +71,25 @@ void SerialManager::run() { } CommandsPayload commands = {doc}; - auto results = this->commandManager->handleBatchCommands(commands); + CommandResult result = CommandResult::getSuccessResult(""); - if (std::holds_alternative(results)) { - auto error = std::get(results); - Serial.printf("%s \n\r", error.getErrorMessage().c_str()); + if (doc.containsKey("command")) { + result = this->commandManager->handleSingleCommand(commands); } else { - for (auto& result : std::get>(results)) { - Serial.printf("%s \n\r", result.getSuccessMessage().c_str()); - } - // we should only call save on the config when the commands where - // successful, no point otherwise + result = this->commandManager->handleBatchCommands(commands); + } - // also, I'm not really vibing with havin to create - // an entire JsonDocument for this, though it's light + if (result.isSuccess()) { + Serial.printf("%s \n\r", result.getSuccessMessage().c_str()); + + // we also save the config if the commands were successful JsonDocument saveCommanddDoc; saveCommanddDoc["command"] = "save_config"; saveCommanddDoc["data"].to(); CommandsPayload saveCommandPayload = {saveCommanddDoc}; this->commandManager->handleSingleCommand(saveCommandPayload); + } else { + Serial.printf("%s \n\r", result.getErrorMessage().c_str()); } } #ifdef ETVR_EYE_TRACKER_USB_API From 04af0160c4c5e4bb63837d08f6864b346052cf19 Mon Sep 17 00:00:00 2001 From: Lorow Date: Sun, 18 Aug 2024 21:27:21 +0200 Subject: [PATCH 6/8] add SetFPSCommand and ToggleStreamCommand, cleanup streaming implementation, add FPS limiting in TCP stream, improve logging a bit --- ESP/lib/src/data/CommandManager/Command.cpp | 29 +++++ ESP/lib/src/data/CommandManager/Command.hpp | 24 ++++ .../data/CommandManager/CommandManager.cpp | 17 ++- .../data/CommandManager/CommandManager.hpp | 10 +- ESP/lib/src/network/stream/streamServer.cpp | 119 ++++++------------ ESP/lib/src/network/stream/streamServer.hpp | 35 ++++-- ESP/src/main.cpp | 25 ++-- 7 files changed, 151 insertions(+), 108 deletions(-) diff --git a/ESP/lib/src/data/CommandManager/Command.cpp b/ESP/lib/src/data/CommandManager/Command.cpp index e5c41a8..f87a57a 100644 --- a/ESP/lib/src/data/CommandManager/Command.cpp +++ b/ESP/lib/src/data/CommandManager/Command.cpp @@ -39,4 +39,33 @@ CommandResult SetMDNSCommand::execute() { CommandResult SaveConfigCommand::execute() { projectConfig.save(); return CommandResult::getSuccessResult("CONFIG SAVED"); +} + +CommandResult SetFPSCommand::validate() { + if (!data.containsKey("fps") || !data["hostname"]) + return CommandResult::getErrorResult("Missing fps or FPS were negative"); + + return CommandResult::getSuccessResult(""); +} + +CommandResult SetFPSCommand::execute() { + // handle FPS here, poc of the interface: + // auto defaultCameraSettings = projectConfig.getDefaultCameraSettings(); + // projectConfig.setCamera(..defaultCameraSettings, data["fps"]); + return CommandResult::getSuccessResult("FPS set to:" + + data["fps"].as()); +} + +CommandResult ToggleStreamCommand::validate() { + if (!data.containsKey("state")) + return CommandResult::getErrorResult("Missing state field"); + + return CommandResult::getSuccessResult(""); +} + +CommandResult ToggleStreamCommand::execute() { + this->streamServer.toggleTCPStream(data["state"].as()); + + return CommandResult::getSuccessResult("TCP Stream state set to:" + + data["state"].as()); } \ No newline at end of file diff --git a/ESP/lib/src/data/CommandManager/Command.hpp b/ESP/lib/src/data/CommandManager/Command.hpp index 895a2a1..e098b54 100644 --- a/ESP/lib/src/data/CommandManager/Command.hpp +++ b/ESP/lib/src/data/CommandManager/Command.hpp @@ -5,6 +5,7 @@ #include #include #include "data/config/project_config.hpp" +#include "network/stream/streamServer.hpp" class CommandResult { private: @@ -91,4 +92,27 @@ class SaveConfigCommand : public ICommand { CommandResult execute() override; }; +class SetFPSCommand : public ICommand { + ProjectConfig& projectConfig; + JsonVariant data; + + public: + SetFPSCommand(ProjectConfig& projectConfig, JsonVariant data) + : projectConfig(projectConfig), data(data) {} + + CommandResult validate() override; + CommandResult execute() override; +}; + +class ToggleStreamCommand : public ICommand { + StreamServer& streamServer; + JsonVariant data; + + public: + ToggleStreamCommand(StreamServer& streamServer, JsonVariant data) + : streamServer(streamServer), data(data) {} + + CommandResult validate() override; + CommandResult execute() override; +}; #endif \ No newline at end of file diff --git a/ESP/lib/src/data/CommandManager/CommandManager.cpp b/ESP/lib/src/data/CommandManager/CommandManager.cpp index e4ffc87..ba953c9 100644 --- a/ESP/lib/src/data/CommandManager/CommandManager.cpp +++ b/ESP/lib/src/data/CommandManager/CommandManager.cpp @@ -11,6 +11,12 @@ std::unique_ptr CommandManager::createCommand(CommandType commandType, return std::make_unique(this->projectConfig, data); case CommandType::SAVE_CONFIG: return std::make_unique(this->projectConfig); + case CommandType::SET_FPS: + return std::make_unique(this->projectConfig, data); + case CommandType::TOGGLE_STREAM: + return std::make_unique(this->streamServer, data); + default: + return nullptr; } } @@ -130,5 +136,14 @@ CommandManager::createCommandFromJsonVariant(JsonVariant& command) { } auto command_data = command["data"].as(); - return this->createCommand(command_type, command_data); + auto command_ptr = this->createCommand(command_type, command_data); + + if (!command_ptr) { + std::string error = Helpers::format_string("Command is not supported: %s", + command["command"]); + log_e("%s", error.c_str()); + return CommandResult::getErrorResult(error); + } + + return command_ptr; } \ No newline at end of file diff --git a/ESP/lib/src/data/CommandManager/CommandManager.hpp b/ESP/lib/src/data/CommandManager/CommandManager.hpp index c94ab25..9a6632d 100644 --- a/ESP/lib/src/data/CommandManager/CommandManager.hpp +++ b/ESP/lib/src/data/CommandManager/CommandManager.hpp @@ -15,6 +15,7 @@ #include "data/CommandManager/Command.hpp" #include "data/config/project_config.hpp" +#include "network/stream/streamServer.hpp" struct CommandsPayload { JsonVariant data; @@ -25,6 +26,8 @@ enum CommandType { PING, SET_WIFI, SET_MDNS, + SET_FPS, + TOGGLE_STREAM, SAVE_CONFIG, }; @@ -32,11 +35,14 @@ const std::unordered_map commandMap = { {"ping", CommandType::PING}, {"set_wifi", CommandType::SET_WIFI}, {"set_mdns", CommandType::SET_MDNS}, + {"set_fps", CommandType::SET_FPS}, + {"toggle_stream", CommandType::TOGGLE_STREAM}, {"save_config", CommandType::SAVE_CONFIG}}; class CommandManager { private: ProjectConfig& projectConfig; + StreamServer& streamServer; std::string join_strings(std::vector const& strings, std::string delim) { @@ -60,8 +66,8 @@ class CommandManager { // // TODO rewrite camera handler to be simpler and easier to change public: - CommandManager(ProjectConfig& projectConfig) - : projectConfig(projectConfig) {}; + CommandManager(ProjectConfig& projectConfig, StreamServer& streamServer) + : projectConfig(projectConfig), streamServer(streamServer) {}; CommandResult handleSingleCommand(CommandsPayload commandsPayload); CommandResult handleBatchCommands(CommandsPayload commandsPayload); diff --git a/ESP/lib/src/network/stream/streamServer.cpp b/ESP/lib/src/network/stream/streamServer.cpp index f9f9cef..59cda95 100644 --- a/ESP/lib/src/network/stream/streamServer.cpp +++ b/ESP/lib/src/network/stream/streamServer.cpp @@ -71,18 +71,12 @@ esp_err_t StreamHelpers::stream(httpd_req_t* req) { return res; } -StreamServer::StreamServer(const int STREAM_PORT) - : STREAM_SERVER_PORT(STREAM_PORT) { - memcpy(initial_packet_buffer, ETVR_HEADER, sizeof(ETVR_HEADER)); -} - int StreamServer::startStreamServer() { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.stack_size = 20480; config.max_uri_handlers = 1; config.server_port = this->STREAM_SERVER_PORT; config.ctrl_port = this->STREAM_SERVER_PORT; - config.stack_size = 20480; httpd_uri_t stream_page = {.uri = "/", .method = HTTP_GET, @@ -96,90 +90,28 @@ int StreamServer::startStreamServer() { else { httpd_register_uri_handler(camera_stream, &stream_page); Serial.println("Stream server initialized"); + String serverStatusMessage = "The stream is under: http://"; + switch (wifiStateManager.getCurrentState()) { case WiFiState_e::WiFiState_ADHOC: - Serial.printf("\n\rThe stream is under: http://%s:%i\n\r", - WiFi.softAPIP().toString().c_str(), - this->STREAM_SERVER_PORT); + // this should be Serial.printf but for some odd reason + // Serial.printf shows up only on debug, not in release + Serial.println((serverStatusMessage + WiFi.softAPIP().toString() + ":" + + this->STREAM_SERVER_PORT + "\n\r") + .c_str()); break; default: - Serial.printf("\n\rThe stream is under: http://%s:%i\n\r", - WiFi.localIP().toString().c_str(), - this->STREAM_SERVER_PORT); + Serial.println((serverStatusMessage + WiFi.localIP().toString() + ":" + + this->STREAM_SERVER_PORT + "\n\r") + .c_str()); break; } return 0; } } -bool StreamServer::startUDPStreamServer() { - socket = AsyncUDP(); - return socket.listen(this->STREAM_SERVER_PORT + 1); -} - -void StreamServer::sendUDPFrame() { - /////////////////////////////////////////////////////// - /////////////////////////////////////////////////////// - // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO - // - // ADD PROTOCOL VERSION - // - // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO - /////////////////////////////////////////////////////// - /////////////////////////////////////////////////////// - - if (!last_frame) - last_frame = esp_timer_get_time(); - - size_t len = 0; - uint8_t* buf = NULL; - - auto fb = esp_camera_fb_get(); - if (fb) { - len = fb->len; - buf = fb->buf; - } else { - log_e("Camera capture failed"); - return; - } - - // we're sending the initial header with the total number of chunks first - // we can then later detect new frame with the header packets - uint8_t totalChunks = (len + CHUNK_SIZE - 1) / CHUNK_SIZE; - initial_packet_buffer[sizeof(ETVR_HEADER)] = totalChunks; - socket.broadcastTo(initial_packet_buffer, sizeof(initial_packet_buffer), - this->STREAM_SERVER_PORT); - - for (uint8_t i = 0; i < totalChunks; i++) { - auto offset = i * CHUNK_SIZE; - // we need to make sure we don't overread - auto chunkSize = (offset + CHUNK_SIZE <= len) ? CHUNK_SIZE : len - offset; - packet_buffer[0] = static_cast(i); - // since this is a pointer, we can just add an offset to it, with a - // chunksize to read and we're done - memcpy(packet_buffer + 1, buf + offset, chunkSize); - socket.broadcastTo(packet_buffer, chunkSize + 1, this->STREAM_SERVER_PORT); - } - - if (fb) { - esp_camera_fb_return(fb); - fb = NULL; - buf = NULL; - } else if (buf) { - free(buf); - buf = NULL; - } - - long request_end = millis(); - long latency = request_end - last_request_time; - last_request_time = request_end; - - log_d("Size: %uKB, Time: %ums (%ifps) chunks: %u \n", len / 1024, latency, - 1000 / latency, totalChunks); -} - bool StreamServer::startTCPStreamServer() { - tcp_server = new AsyncServer(this->STREAM_SERVER_PORT); + tcp_server = new AsyncServer(this->TCP_STREAM_SERVER_PORT); tcp_server->onClient( [this](void* arg, AsyncClient* client) { this->handleNewTCPClient(arg, client); @@ -220,12 +152,16 @@ void StreamServer::handleNewTCPClient(void* arg, AsyncClient* client) { void StreamServer::sendTCPFrame() { if (this->tcp_connected_client == nullptr || - !this->tcp_connected_client->connected()) { + !this->tcp_connected_client->connected() || this->pauseTCPStream) { return; } - if (!last_frame) - last_frame = esp_timer_get_time(); + // todo test this + if (last_time_frame_sent && + last_time_frame_sent - millis() < target_fps_time) { + return; + } + last_time_frame_sent = millis(); auto fb = esp_camera_fb_get(); if (!fb) { @@ -234,7 +170,6 @@ void StreamServer::sendTCPFrame() { } size_t len = fb->len; - this->tcp_connected_client->write(ETVR_HEADER_BYTES, 4); this->tcp_connected_client->write((const char*)fb->buf, fb->len); @@ -247,4 +182,22 @@ void StreamServer::sendTCPFrame() { last_request_time = request_end; log_d("Size: %uKB, Time: %ums (%ifps)\n", len / 1024, latency, 1000 / latency); +} + +std::string StreamServer::getName() { + return "StreamServer"; +} + +void StreamServer::toggleTCPStream(bool state) { + pauseTCPStream = state; +} + +void StreamServer::update(ConfigState_e event) { + switch (event) { + case ConfigState_e::cameraConfigUpdated: + // add FPS update here + break; + default: + break; + } } \ No newline at end of file diff --git a/ESP/lib/src/network/stream/streamServer.hpp b/ESP/lib/src/network/stream/streamServer.hpp index a6d1a1f..fde311c 100644 --- a/ESP/lib/src/network/stream/streamServer.hpp +++ b/ESP/lib/src/network/stream/streamServer.hpp @@ -11,6 +11,8 @@ #include #include "data/StateManager/StateManager.hpp" +#include "data/config/project_config.hpp" +#include "data/utilities/Observer.hpp" #include "data/utilities/helpers.hpp" // Camera includes @@ -26,32 +28,41 @@ namespace StreamHelpers { esp_err_t stream(httpd_req_t* req); } // namespace StreamHelpers -class StreamServer { +class StreamServer : public IObserver { private: - AsyncUDP socket; + ProjectConfig& configManager; + int64_t last_frame = 0; + AsyncServer* tcp_server; AsyncClient* tcp_connected_client; httpd_handle_t camera_stream = nullptr; - uint8_t initial_packet_buffer[6]; - uint8_t packet_buffer[CHUNK_SIZE]; - int64_t last_frame = 0; long last_request_time = 0; + int STREAM_SERVER_PORT = 80; + int TCP_STREAM_SERVER_PORT = 82; - int STREAM_SERVER_PORT; + int last_time_frame_sent = 0; + float target_fps_time = 1000 / 30; + + bool pauseTCPStream = true; public: - StreamServer(const int STREAM_PORT = 80); + StreamServer(ProjectConfig& configManager, + const int STREAM_PORT, + const int TPC_SERVER_PORT) + : configManager(configManager), + STREAM_SERVER_PORT(STREAM_PORT), + TCP_STREAM_SERVER_PORT(TPC_SERVER_PORT) {}; + int startStreamServer(); - bool startUDPStreamServer(); bool startTCPStreamServer(); - // rewrite this to an RTOS task pinned to the second core, for testing this is - // fine https://randomnerdtutorials.com/esp32-dual-core-arduino-ide/ - void sendUDPFrame(); - + void toggleTCPStream(bool state); void sendTCPFrame(); void handleNewTCPClient(void* arg, AsyncClient* client); + + void update(ConfigState_e event) override; + std::string getName() override; }; #endif // STREAM_SERVER_HPP diff --git a/ESP/src/main.cpp b/ESP/src/main.cpp index 3badeec..843a67d 100644 --- a/ESP/src/main.cpp +++ b/ESP/src/main.cpp @@ -6,8 +6,6 @@ * @param mdnsName The mDNS hostname to use */ ProjectConfig deviceConfig("openiris", MDNS_HOSTNAME); -CommandManager commandManager(deviceConfig); -SerialManager serialManager(&commandManager); #ifdef CONFIG_CAMERA_MODULE_ESP32S3_XIAO_SENSE LEDManager ledManager(LED_BUILTIN); @@ -32,9 +30,12 @@ MDNSHandler mdnsHandler(deviceConfig); APIServer apiServer(deviceConfig, cameraHandler, "/control"); #ifndef SIM_ENABLED -StreamServer streamServer; +StreamServer streamServer(deviceConfig, 80, 82); #endif // SIM_ENABLED +CommandManager commandManager(deviceConfig, streamServer); +SerialManager serialManager(&commandManager); + void etvr_eye_tracker_web_init() { log_d("[SETUP]: Starting Network Handler"); deviceConfig.attach(mdnsHandler); @@ -48,13 +49,18 @@ void etvr_eye_tracker_web_init() { if (wifiState == WiFiState_e::WiFiState_Connected || wifiState == WiFiState_e::WiFiState_ADHOC) { { - log_d("[SETUP]: Starting Stream Server"); - auto result = streamServer.startTCPStreamServer(); - // streamServer.startStreamServer(); - // auto result = streamServer.startUDPStreamServer(); + log_d("[SETUP]: Starting HTTP Stream Server"); + auto httpd_result = streamServer.startStreamServer(); + + log_d("[SETUP]: Starting TPC Stream Server"); + auto tpc_result = streamServer.startTCPStreamServer(); + + log_d("[SETUP]: Stream Server states: HTTP: %s, TCP: %s", + httpd_result + ? "Failed to connect" + : "Connected", // we return 0 in case of successful connection + tpc_result ? "Connected" : "Failed to connect"); - log_d("[SETUP]: Stream Server state: %s", - result ? "Connected" : "Failed to connect"); log_d("[SETUP]: Starting API Server"); apiServer.setup(); } @@ -86,7 +92,6 @@ void loop() { #ifndef ETVR_EYE_TRACKER_USB_API streamServer.sendTCPFrame(); #endif - // streamServer.sendUDPFrame(); ledManager.handleLED(); serialManager.run(); } From a085b6d885e5894e081de1a93b4ac1df658aef18 Mon Sep 17 00:00:00 2001 From: Lorow Date: Sun, 25 Aug 2024 01:16:43 +0200 Subject: [PATCH 7/8] Begin commands cleanup --- .../src/data/CommandManager/BaseCommand.cpp | 19 ++++++ .../{Command.hpp => BaseCommand.hpp} | 50 +-------------- .../data/CommandManager/CommandManager.hpp | 26 ++++---- .../{Command.cpp => ConfigCommands.cpp} | 35 ++++------- .../data/CommandManager/ConfigCommands.hpp | 61 +++++++++++++++++++ 5 files changed, 107 insertions(+), 84 deletions(-) create mode 100644 ESP/lib/src/data/CommandManager/BaseCommand.cpp rename ESP/lib/src/data/CommandManager/{Command.hpp => BaseCommand.hpp} (61%) rename ESP/lib/src/data/CommandManager/{Command.cpp => ConfigCommands.cpp} (68%) create mode 100644 ESP/lib/src/data/CommandManager/ConfigCommands.hpp diff --git a/ESP/lib/src/data/CommandManager/BaseCommand.cpp b/ESP/lib/src/data/CommandManager/BaseCommand.cpp new file mode 100644 index 0000000..22a7a96 --- /dev/null +++ b/ESP/lib/src/data/CommandManager/BaseCommand.cpp @@ -0,0 +1,19 @@ +#include "BaseCommand.hpp" + +CommandResult PingCommand::execute() { + return CommandResult::getSuccessResult("pong"); +} + +CommandResult ToggleStreamCommand::validate() { + if (!data.containsKey("state")) + return CommandResult::getErrorResult("Missing state field"); + + return CommandResult::getSuccessResult(""); +} + +CommandResult ToggleStreamCommand::execute() { + this->streamServer.toggleTCPStream(data["state"].as()); + + return CommandResult::getSuccessResult("TCP Stream state set to:" + + data["state"].as()); +} \ No newline at end of file diff --git a/ESP/lib/src/data/CommandManager/Command.hpp b/ESP/lib/src/data/CommandManager/BaseCommand.hpp similarity index 61% rename from ESP/lib/src/data/CommandManager/Command.hpp rename to ESP/lib/src/data/CommandManager/BaseCommand.hpp index e098b54..396ccc8 100644 --- a/ESP/lib/src/data/CommandManager/Command.hpp +++ b/ESP/lib/src/data/CommandManager/BaseCommand.hpp @@ -1,9 +1,11 @@ #ifndef COMMAND_HPP #define COMMAND_HPP #include + #include #include #include + #include "data/config/project_config.hpp" #include "network/stream/streamServer.hpp" @@ -56,54 +58,6 @@ class PingCommand : public ICommand { CommandResult execute() override; }; -class SetWiFiCommand : public ICommand { - ProjectConfig& projectConfig; - JsonVariant data; - - public: - SetWiFiCommand(ProjectConfig& projectConfig, JsonVariant data) - : projectConfig(projectConfig), data(data) {} - CommandResult validate() override; - CommandResult execute() override; -}; - -class SetMDNSCommand : public ICommand { - ProjectConfig& projectConfig; - JsonVariant data; - - public: - SetMDNSCommand(ProjectConfig& projectConfig, JsonVariant data) - : projectConfig(projectConfig), data(data) {} - CommandResult validate() override; - CommandResult execute() override; -}; - -class SaveConfigCommand : public ICommand { - ProjectConfig& projectConfig; - - public: - SaveConfigCommand(ProjectConfig& projectConfig) - : projectConfig(projectConfig) {} - - CommandResult validate() override { - return CommandResult::getSuccessResult(""); - }; - - CommandResult execute() override; -}; - -class SetFPSCommand : public ICommand { - ProjectConfig& projectConfig; - JsonVariant data; - - public: - SetFPSCommand(ProjectConfig& projectConfig, JsonVariant data) - : projectConfig(projectConfig), data(data) {} - - CommandResult validate() override; - CommandResult execute() override; -}; - class ToggleStreamCommand : public ICommand { StreamServer& streamServer; JsonVariant data; diff --git a/ESP/lib/src/data/CommandManager/CommandManager.hpp b/ESP/lib/src/data/CommandManager/CommandManager.hpp index 9a6632d..e571c90 100644 --- a/ESP/lib/src/data/CommandManager/CommandManager.hpp +++ b/ESP/lib/src/data/CommandManager/CommandManager.hpp @@ -2,18 +2,18 @@ #ifndef TASK_MANAGER_HPP #define TASK_MANAGER_HPP #include -#include -#include -#include -#include #include #include #include +#include #include +#include +#include +#include #include -#include "data/CommandManager/Command.hpp" +#include "data/CommandManager/BaseCommand.hpp" #include "data/config/project_config.hpp" #include "network/stream/streamServer.hpp" @@ -41,10 +41,10 @@ const std::unordered_map commandMap = { class CommandManager { private: - ProjectConfig& projectConfig; - StreamServer& streamServer; + ProjectConfig &projectConfig; + StreamServer &streamServer; - std::string join_strings(std::vector const& strings, + std::string join_strings(std::vector const &strings, std::string delim) { std::stringstream ss; std::copy(strings.begin(), strings.end(), @@ -52,21 +52,21 @@ class CommandManager { return ss.str(); } - bool hasDataField(JsonVariant& command); + bool hasDataField(JsonVariant &command); std::unique_ptr createCommand(CommandType commandType, - JsonVariant& data); + JsonVariant &data); std::variant, CommandResult> - createCommandFromJsonVariant(JsonVariant& command); + createCommandFromJsonVariant(JsonVariant &command); - CommandType getCommandType(JsonVariant& command); + CommandType getCommandType(JsonVariant &command); // // TODO rewrite the API // // TODO add FPS/ Freq / cropping to the API // // TODO rewrite camera handler to be simpler and easier to change public: - CommandManager(ProjectConfig& projectConfig, StreamServer& streamServer) + CommandManager(ProjectConfig &projectConfig, StreamServer &streamServer) : projectConfig(projectConfig), streamServer(streamServer) {}; CommandResult handleSingleCommand(CommandsPayload commandsPayload); diff --git a/ESP/lib/src/data/CommandManager/Command.cpp b/ESP/lib/src/data/CommandManager/ConfigCommands.cpp similarity index 68% rename from ESP/lib/src/data/CommandManager/Command.cpp rename to ESP/lib/src/data/CommandManager/ConfigCommands.cpp index f87a57a..ace5d11 100644 --- a/ESP/lib/src/data/CommandManager/Command.cpp +++ b/ESP/lib/src/data/CommandManager/ConfigCommands.cpp @@ -1,8 +1,4 @@ -#include "Command.hpp" - -CommandResult PingCommand::execute() { - return CommandResult::getSuccessResult("pong"); -} +#include "ConfigCommands.hpp" CommandResult SetWiFiCommand::validate() { if (!data.containsKey("ssid")) @@ -31,11 +27,20 @@ CommandResult SetMDNSCommand::validate() { } CommandResult SetMDNSCommand::execute() { - projectConfig.setMDNSConfig(data["hostname"], "openiristracker", false); + projectConfig.setMDNSConfig(data, false); return CommandResult::getSuccessResult("MDNS set to:" + data["hostname"].as()); } +CommandResult SetDeviceConfigCommand::validate() { + return CommandResult::getSuccessResult(""); +} + +CommandResult SetDeviceConfigCommand::execute() { + projectConfig.setDeviceConfig(data, false); + return CommandResult::getSuccessResult("Device config updated."); +} + CommandResult SaveConfigCommand::execute() { projectConfig.save(); return CommandResult::getSuccessResult("CONFIG SAVED"); @@ -49,23 +54,7 @@ CommandResult SetFPSCommand::validate() { } CommandResult SetFPSCommand::execute() { - // handle FPS here, poc of the interface: - // auto defaultCameraSettings = projectConfig.getDefaultCameraSettings(); - // projectConfig.setCamera(..defaultCameraSettings, data["fps"]); + projectConfig.setCameraConfig("fps", data["fps"]); return CommandResult::getSuccessResult("FPS set to:" + data["fps"].as()); -} - -CommandResult ToggleStreamCommand::validate() { - if (!data.containsKey("state")) - return CommandResult::getErrorResult("Missing state field"); - - return CommandResult::getSuccessResult(""); -} - -CommandResult ToggleStreamCommand::execute() { - this->streamServer.toggleTCPStream(data["state"].as()); - - return CommandResult::getSuccessResult("TCP Stream state set to:" + - data["state"].as()); } \ No newline at end of file diff --git a/ESP/lib/src/data/CommandManager/ConfigCommands.hpp b/ESP/lib/src/data/CommandManager/ConfigCommands.hpp new file mode 100644 index 0000000..ac8d19e --- /dev/null +++ b/ESP/lib/src/data/CommandManager/ConfigCommands.hpp @@ -0,0 +1,61 @@ +#include "data/CommandManager/BaseCommand.hpp" + +// todo: make use of the update +class SetWiFiCommand : public ICommand { + ProjectConfig& projectConfig; + JsonVariant data; + + public: + SetWiFiCommand(ProjectConfig& projectConfig, JsonVariant data) + : projectConfig(projectConfig), data(data) {} + CommandResult validate() override; + CommandResult execute() override; +}; + +class SetMDNSCommand : public ICommand { + ProjectConfig& projectConfig; + JsonVariant data; + + public: + SetMDNSCommand(ProjectConfig& projectConfig, JsonVariant data) + : projectConfig(projectConfig), data(data) {} + CommandResult validate() override; + CommandResult execute() override; +}; + +class SetDeviceConfigCommand : public ICommand { + ProjectConfig& projectConfig; + JsonVariant data; + + public: + SetDeviceConfigCommand(ProjectConfig& projectConfig, JsonVariant data) + : projectConfig(projectConfig), data(data) {} + CommandResult validate() override; + CommandResult execute() override; +}; + +class SaveConfigCommand : public ICommand { + ProjectConfig& projectConfig; + + public: + SaveConfigCommand(ProjectConfig& projectConfig) + : projectConfig(projectConfig) {} + + CommandResult validate() override { + return CommandResult::getSuccessResult(""); + }; + + CommandResult execute() override; +}; + +class SetFPSCommand : public ICommand { + ProjectConfig& projectConfig; + JsonVariant data; + + public: + SetFPSCommand(ProjectConfig& projectConfig, JsonVariant data) + : projectConfig(projectConfig), data(data) {} + + CommandResult validate() override; + CommandResult execute() override; +}; From 18cf39f291739a46826a0c7811071393edc57512 Mon Sep 17 00:00:00 2001 From: Lorow Date: Sun, 25 Aug 2024 01:26:53 +0200 Subject: [PATCH 8/8] Make project config use update methods --- ESP/lib/src/data/config/project_config.cpp | 118 +++++++++------------ ESP/lib/src/data/config/project_config.hpp | 103 +++++++++++++----- 2 files changed, 127 insertions(+), 94 deletions(-) diff --git a/ESP/lib/src/data/config/project_config.cpp b/ESP/lib/src/data/config/project_config.cpp index 9154b1e..0407b45 100644 --- a/ESP/lib/src/data/config/project_config.cpp +++ b/ESP/lib/src/data/config/project_config.cpp @@ -213,59 +213,44 @@ void ProjectConfig::load() { //! DeviceConfig //* //********************************************************************************************************************** -void ProjectConfig::setDeviceConfig(const std::string& OTALogin, - const std::string& OTAPassword, - int OTAPort, - bool shouldNotify) { +void ProjectConfig::setDeviceConfig(JsonVariant& data, bool shouldNotify) { log_d("Updating device config"); - this->config.device.OTALogin.assign(OTALogin); - this->config.device.OTAPassword.assign(OTAPassword); - this->config.device.OTAPort = OTAPort; + for (JsonPair kv : data.as()) { + config.device.update(kv.key().c_str(), kv.value()); + } if (shouldNotify) this->notifyAll(ConfigState_e::deviceConfigUpdated); } -void ProjectConfig::setMDNSConfig(const std::string& hostname, - const std::string& service, - bool shouldNotify) { +void ProjectConfig::setMDNSConfig(JsonVariant& data, bool shouldNotify) { log_d("Updating MDNS config"); - this->config.mdns.hostname.assign(hostname); - this->config.mdns.service.assign(service); + + for (JsonPair kv : data.as()) { + this->config.mdns.update(kv.key().c_str(), kv.value()); + } if (shouldNotify) this->notifyAll(ConfigState_e::mdnsConfigUpdated); } -void ProjectConfig::setCameraConfig(uint8_t vflip, - uint8_t framesize, - uint8_t href, - uint8_t quality, - uint8_t brightness, - bool shouldNotify) { +void ProjectConfig::setCameraConfig(JsonVariant& data, bool shouldNotify) { log_d("Updating camera config"); - this->config.camera.vflip = vflip; - this->config.camera.href = href; - this->config.camera.framesize = framesize; - this->config.camera.quality = quality; - this->config.camera.brightness = brightness; - log_d("Updating Camera config"); + for (JsonPair kv : data.as()) { + this->config.camera.update(kv.key().c_str(), kv.value()); + } + if (shouldNotify) this->notifyAll(ConfigState_e::cameraConfigUpdated); } -void ProjectConfig::setWifiConfig(const std::string& networkName, - const std::string& ssid, - const std::string& password, - uint8_t channel, - uint8_t power, - bool adhoc, - bool shouldNotify) { +void ProjectConfig::setWifiConfig(JsonVariant& data, bool shouldNotify) { // we store the ADHOC flag as false because the networks we store in the // config are the ones we want the esp to connect to, rather than host as AP, // and here we're just updating them size_t size = this->config.networks.size(); + auto networkName = data["name"].as(); for (auto it = this->config.networks.begin(); it != this->config.networks.end();) { @@ -273,12 +258,10 @@ void ProjectConfig::setWifiConfig(const std::string& networkName, log_i("[Project Config]: Found network %s, updating it ...", it->name.c_str()); - it->name = networkName; - it->ssid = ssid; - it->password = password; - it->channel = channel; - it->power = power; - it->adhoc = false; + // we found the network, so we can just update it with the new values + for (JsonPair kv : data.as()) { + it->update(kv.key().c_str(), kv.value()); + } if (shouldNotify) { wifiStateManager.setState(WiFiState_e::WiFiState_Disconnected); @@ -293,20 +276,19 @@ void ProjectConfig::setWifiConfig(const std::string& networkName, } } - if (size < 3 && size > 0) { - Serial.println("We're adding a new network"); + if (size < 3) { + if (size == 0) + Serial.println("No networks, We're adding a new network"); + else + Serial.println("We're adding a new network"); // we don't have that network yet, we can add it as we still have some // space we're using emplace_back as push_back will create a copy of it, // we want to avoid that - this->config.networks.emplace_back(networkName, ssid, password, channel, - power, false); - } - - // we're allowing to store up to three additional networks - if (size == 0) { - Serial.println("No networks, We're adding a new network"); - this->config.networks.emplace_back(networkName, ssid, password, channel, - power, false); + this->config.networks.emplace_back( + networkName, data["ssid"].as(), + // todo add validation for this, maybe deconstruct it before emplacing + data["password"].as(), data["channel"].as(), + data["power"].as(), false); } if (shouldNotify) { @@ -324,17 +306,19 @@ void ProjectConfig::deleteWifiConfig(const std::string& networkName, Serial.println("No networks, nothing to delete"); } - for (auto it = this->config.networks.begin(); - it != this->config.networks.end();) { - if (it->name == networkName) { - log_i("[Project Config]: Found network %s", it->name.c_str()); - it = this->config.networks.erase(it); - log_i("[Project Config]: Deleted network %s", networkName.c_str()); + auto networkPredicate = [networkName](WiFiConfig_t network) { + return network.name == networkName; + }; - } else { - ++it; - } - } + if (auto networkToBeDeleted = + std::find_if(this->config.networks.begin(), + this->config.networks.end(), networkPredicate); + networkToBeDeleted != this->config.networks.end()) { + log_i("[Project Config]: Found network %s", + networkToBeDeleted->name.c_str()); + this->config.networks.erase(networkToBeDeleted); + log_i("[Project Config]: Deleted network %s", networkName.c_str()); + }; if (shouldNotify) { this->wifiConfigSave(); @@ -343,24 +327,20 @@ void ProjectConfig::deleteWifiConfig(const std::string& networkName, } void ProjectConfig::setWiFiTxPower(uint8_t power, bool shouldNotify) { - this->config.txpower.power = power; log_d("Updating wifi tx power"); + + this->config.txpower.power = power; if (shouldNotify) this->notifyAll(ConfigState_e::wifiTxPowerUpdated); } -void ProjectConfig::setAPWifiConfig(const std::string& ssid, - const std::string& password, - uint8_t channel, - bool adhoc, - bool shouldNotify) { - this->config.ap_network.ssid.assign(ssid); - this->config.ap_network.password.assign(password); - this->config.ap_network.channel = channel; - this->config.ap_network.adhoc = adhoc; - +void ProjectConfig::setAPWifiConfig(JsonVariant& data, bool shouldNotify) { log_d("Updating access point config"); + for (JsonPair kv : data.as()) { + config.device.update(kv.key().c_str(), kv.value()); + } + if (shouldNotify) { wifiStateManager.setState(WiFiState_e::WiFiState_None); WiFi.disconnect(); diff --git a/ESP/lib/src/data/config/project_config.hpp b/ESP/lib/src/data/config/project_config.hpp index a2a5c99..ad3c043 100644 --- a/ESP/lib/src/data/config/project_config.hpp +++ b/ESP/lib/src/data/config/project_config.hpp @@ -2,9 +2,12 @@ #ifndef PROJECT_CONFIG_HPP #define PROJECT_CONFIG_HPP #include +#include #include + #include #include +#include #include #include "data/StateManager/StateManager.hpp" @@ -33,22 +36,62 @@ class ProjectConfig : public Preferences, public ISubject { std::string OTAPassword; int OTAPort; std::string toRepresentation(); + + void update(std::string field, JsonVariant value) { + // this technically could be done with a hashmap that takes + // and std::function and some lambda captures + // but it seemed too unvieldy for the time being + + if (field == "OTALogin") { + this->OTALogin = value.as(); + } else if (field == "OTAPassword") { + this->OTAPassword = value.as(); + } else if (field == "OTAPort") { + this->OTAPort = value.as(); + } + } }; struct MDNSConfig_t { std::string hostname; std::string service; std::string toRepresentation(); + + void update(std::string field, JsonVariant value) { + if (field == "hostname") { + this->hostname = std::move(value.as()); + } else if (field == "service") { + this->service = std::move(value.as()); + } + } }; struct CameraConfig_t { + public: uint8_t vflip; uint8_t href; uint8_t framesize; uint8_t quality; uint8_t brightness; + uint8_t fps; std::string toRepresentation(); + + void update(std::string field, JsonVariant value) { + if (field == "vflip") { + this->vflip = value.as(); + } else if (field == "href") { + this->href = value.as(); + } else if (field == "framesize") { + this->framesize = value.as(); + } else if (field == "quality") { + this->quality = value.as(); + } else if (field == "brightness") { + this->brightness = value.as(); + } else if (field == "fps") { + this->fps = value.as(); + } + } }; struct WiFiConfig_t { @@ -73,6 +116,22 @@ class ProjectConfig : public Preferences, public ISubject { bool adhoc; std::string toRepresentation(); + + void update(std::string field, JsonVariant value) { + if (field == "name") { + this->name = std::move(value.as()); + } else if (field == "ssid") { + this->ssid = std::move(value.as()); + } else if (field == "password") { + this->password = std::move(value.as()); + } else if (field == "channel") { + this->channel = value.as(); + } else if (field == "power") { + this->power = value.as(); + } else if (field == "adhoc") { + this->adhoc = value.as(); + } + } }; struct AP_WiFiConfig_t { @@ -81,11 +140,23 @@ class ProjectConfig : public Preferences, public ISubject { uint8_t channel; bool adhoc; std::string toRepresentation(); + void update(std::string field, JsonVariant value) { + if (field == "ssid") { + this->ssid = std::move(value.as()); + } else if (field == "password") { + this->password = std::move(value.as()); + } else if (field == "channel") { + this->channel = value.as(); + } else if (field == "adhoc") { + this->adhoc = value.as(); + } + } }; struct WiFiTxPower_t { uint8_t power; std::string toRepresentation(); + void update(uint8_t power) { this->power = power; } }; struct TrackerConfig_t { @@ -104,31 +175,13 @@ class ProjectConfig : public Preferences, public ISubject { MDNSConfig_t& getMDNSConfig(); WiFiTxPower_t& getWiFiTxPowerConfig(); - void setDeviceConfig(const std::string& OTALogin, - const std::string& OTAPassword, - int OTAPort, - bool shouldNotify); - void setMDNSConfig(const std::string& hostname, - const std::string& service, - bool shouldNotify); - void setCameraConfig(uint8_t vflip, - uint8_t framesize, - uint8_t href, - uint8_t quality, - uint8_t brightness, - bool shouldNotify); - void setWifiConfig(const std::string& networkName, - const std::string& ssid, - const std::string& password, - uint8_t channel, - uint8_t power, - bool adhoc, - bool shouldNotify); - void setAPWifiConfig(const std::string& ssid, - const std::string& password, - uint8_t channel, - bool adhoc, - bool shouldNotify); + void setDeviceConfig(JsonVariant& data, bool shouldNotify); + + void setMDNSConfig(JsonVariant& data, bool shouldNotify); + void setCameraConfig(JsonVariant& data, bool shouldNotify); + void setWifiConfig(JsonVariant& data, bool shouldNotify); + void setAPWifiConfig(JsonVariant& data, bool shouldNotify); + void setWiFiTxPower(uint8_t power, bool shouldNotify); void deleteWifiConfig(const std::string& networkName, bool shouldNotify);