From 54e4e0abd92b6d3233f81bbbc3d74e1370239b4f Mon Sep 17 00:00:00 2001 From: Zdzislaw Goik Date: Tue, 13 Feb 2024 13:14:20 +0100 Subject: [PATCH 1/2] feat: Add a PoC implementation of command manager responding to commands send over serial. (#59) * Add a PoC implementation of command manager responding to commands send over serial. TODO: - refactor web API to make use of the command handler - implement the rest of the commands - remove handwritten json in favour of ArduinoJSON * Move ssid check to iniSTA method --- ESP/ini/dev_config.ini | 4 + .../data/CommandManager/CommandManager.cpp | 55 ++++++++++++ .../data/CommandManager/CommandManager.hpp | 35 ++++++++ ESP/lib/src/io/Serial/SerialManager.cpp | 83 +++++++++++++++++++ ESP/lib/src/io/Serial/SerialManager.hpp | 33 ++++++++ ESP/lib/src/network/mDNS/MDNSManager.cpp | 3 +- .../src/network/wifihandler/wifihandler.cpp | 30 +++++-- .../src/network/wifihandler/wifihandler.hpp | 7 +- ESP/lib/src/openiris.hpp | 2 + ESP/src/main.cpp | 16 ++-- 10 files changed, 244 insertions(+), 24 deletions(-) create mode 100644 ESP/lib/src/data/CommandManager/CommandManager.cpp create mode 100644 ESP/lib/src/data/CommandManager/CommandManager.hpp create mode 100644 ESP/lib/src/io/Serial/SerialManager.cpp create mode 100644 ESP/lib/src/io/Serial/SerialManager.hpp diff --git a/ESP/ini/dev_config.ini b/ESP/ini/dev_config.ini index 18fd225..a421311 100644 --- a/ESP/ini/dev_config.ini +++ b/ESP/ini/dev_config.ini @@ -9,6 +9,9 @@ custom_firmware_version = 2.2.11 monitor_rts = 0 monitor_dtr = 0 monitor_filters = + --echo + --eol + send_on_enter log2file time default @@ -23,6 +26,7 @@ lib_deps = esp32-camera https://github.com/me-no-dev/ESPAsyncWebServer.git https://github.com/me-no-dev/AsyncTCP.git + https://github.com/bblanchon/ArduinoJson.git extra_scripts = pre:tools/customname.py post:tools/createzip.py diff --git a/ESP/lib/src/data/CommandManager/CommandManager.cpp b/ESP/lib/src/data/CommandManager/CommandManager.cpp new file mode 100644 index 0000000..7b6f0b0 --- /dev/null +++ b/ESP/lib/src/data/CommandManager/CommandManager.cpp @@ -0,0 +1,55 @@ +#include "CommandManager.hpp" + +CommandManager::CommandManager(ProjectConfig *deviceConfig) : deviceConfig(deviceConfig) {} + + +const CommandType CommandManager::getCommandType(Command &command){ + if (!command.data.containsKey("command")) + return CommandType::None; + + if (auto search = commandMap.find(command.data["command"]); search != commandMap.end()) + return search->second; + + return CommandType::None; +} + + +void CommandManager::handleCommand(Command command) { + auto command_type = this->getCommandType(command); + + if (!command.data.containsKey("data")) + // malformed command, lacked data field + return; + + switch(command_type) + { + case CommandType::SET_WIFI: { + if(!command.data["data"].containsKey("ssid") || !command.data["data"].containsKey("password")) + break; + + std::string customNetworkName = "main"; + if (command.data["data"].containsKey("network_name")) + customNetworkName = command.data["data"]["network_name"].as(); + + this->deviceConfig->setWifiConfig( + customNetworkName, + command.data["data"]["ssid"], + command.data["data"]["password"], + 0, // channel, should this be zero? + 0, // power, should this be zero? + false, + false + ); + + // we purposefully save here + this->deviceConfig->save(); + break; + } + case CommandType::PING: { + Serial.println("PONG \n\r"); + break; + } + default: + break; + } +} \ No newline at end of file diff --git a/ESP/lib/src/data/CommandManager/CommandManager.hpp b/ESP/lib/src/data/CommandManager/CommandManager.hpp new file mode 100644 index 0000000..b50d28a --- /dev/null +++ b/ESP/lib/src/data/CommandManager/CommandManager.hpp @@ -0,0 +1,35 @@ +#pragma once +#ifndef TASK_MANAGER_HPP +#define TASK_MANAGER_HPP +#include +#include +#include "data/config/project_config.hpp" + +enum CommandType { + None, + PING, + SET_WIFI, +}; + + +struct Command { + JsonDocument data; +}; + +class CommandManager{ + +private: +const std::unordered_map commandMap = { + {"ping", CommandType::PING}, + {"set_wifi", CommandType::SET_WIFI}, +}; + +ProjectConfig* deviceConfig; + +public: + CommandManager(ProjectConfig *deviceConfig); + void handleCommand(Command command); + const CommandType getCommandType(Command &command); +}; + +#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 new file mode 100644 index 0000000..9a2ed43 --- /dev/null +++ b/ESP/lib/src/io/Serial/SerialManager.cpp @@ -0,0 +1,83 @@ +#include "SerialManager.hpp" + +SerialManager::SerialManager(CommandManager* commandManager) + : commandManager(commandManager) {} + +#ifdef ETVR_EYE_TRACKER_USB_API +void SerialManager::send_frame() { + // if we failed to capture the frame, we bail, but we still want to listen to commands + if (err != ESP_OK) + return; + + if (!last_frame) + last_frame = esp_timer_get_time(); + + uint8_t len_bytes[2]; + + 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 with response: %s", esp_err_to_name(err)); + err = ESP_FAIL; + } + + if (err == ESP_OK) + Serial.write(ETVR_HEADER, 2); + + Serial.write(ETVR_HEADER_FRAME, 2); + len_bytes[0] = len & 0xFF; + len_bytes[1] = (len >> CHAR_BIT) & 0xFF; + Serial.write(len_bytes, 2); + Serial.write((const char*)buf, len); + + 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)\n", len / 1024, latency, + 1000 / latency); +} +#endif + +void SerialManager::init() { + Serial.begin(3000000); + Serial.flush(); +} + +void SerialManager::run() { + while (true) { + // we're getting a command, read it whole and process. + // otherwise, send a frame if we're supposed to + if (Serial.available()) { + JsonDocument doc; + DeserializationError deserializationError = deserializeJson(doc, Serial); + + if (deserializationError) { + log_e("Command deserialization failed: %s", + deserializationError.c_str()); + continue; + } + + Command command = {doc}; + this->commandManager->handleCommand(command); + } +#ifdef ETVR_EYE_TRACKER_USB_API + else { + this->send_frame(); + } +#endif + } +} \ No newline at end of file diff --git a/ESP/lib/src/io/Serial/SerialManager.hpp b/ESP/lib/src/io/Serial/SerialManager.hpp new file mode 100644 index 0000000..f21d31a --- /dev/null +++ b/ESP/lib/src/io/Serial/SerialManager.hpp @@ -0,0 +1,33 @@ +#pragma once +#ifndef SERIAL_MANAGER +#define SERIAL_MANAGER + +#include +#include +#include +#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"; + +class SerialManager { + private: + esp_err_t err = ESP_OK; + CommandManager* commandManager; + +#ifdef ETVR_EYE_TRACKER_USB_API + int64_t last_frame = 0; + long last_request_time = 0; + + void send_frame(); +#endif + + public: + SerialManager(CommandManager* commandManager); + void init(); + void run(); +}; + +#endif \ No newline at end of file diff --git a/ESP/lib/src/network/mDNS/MDNSManager.cpp b/ESP/lib/src/network/mDNS/MDNSManager.cpp index 45c75c8..5e4580c 100644 --- a/ESP/lib/src/network/mDNS/MDNSManager.cpp +++ b/ESP/lib/src/network/mDNS/MDNSManager.cpp @@ -6,8 +6,7 @@ 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 cc2e89d..0c85ed5 100644 --- a/ESP/lib/src/network/wifihandler/wifihandler.cpp +++ b/ESP/lib/src/network/wifihandler/wifihandler.cpp @@ -6,13 +6,14 @@ WiFiHandler::WiFiHandler(ProjectConfig& configManager, const std::string& ssid, const std::string& password, - uint8_t channel) + uint8_t channel, + bool enable_adhoc) : configManager(configManager), ssid(std::move(ssid)), password(std::move(password)), channel(channel), power(0), - _enable_adhoc(false) {} + _enable_adhoc(enable_adhoc) {} WiFiHandler::~WiFiHandler() {} @@ -25,9 +26,7 @@ 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"); @@ -40,10 +39,19 @@ 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)) { + + // since networks may not have a password, we only need to check if we have an ssid + // bail if we don't + if (this->iniSTA( + this->ssid, + this->password, + this->channel, + (wifi_power_t)txpower.power + ) + ) { return; } + log_i( "Could not connect to the hardcoded network, setting up ADHOC " "network \n\r"); @@ -126,6 +134,12 @@ bool WiFiHandler::iniSTA(const std::string& ssid, const std::string& password, uint8_t channel, wifi_power_t power) { + + if (ssid == ""){ + log_d("ssid missing, bailing"); + return false; + } + unsigned long currentMillis = millis(); unsigned long startingMillis = currentMillis; int connectionTimeout = 45000; // 30 seconds @@ -147,8 +161,6 @@ bool WiFiHandler::iniSTA(const std::string& ssid, currentMillis = millis(); log_i("."); log_d("Progress: %d \n\r", progress); - /* Helpers::update_progress_bar(progress, 100); - delay(301); */ if ((currentMillis - startingMillis) >= connectionTimeout) { wifiStateManager.setState(WiFiState_e::WiFiState_Error); log_e("Connection to: %s TIMEOUT \n\r", ssid.c_str()); diff --git a/ESP/lib/src/network/wifihandler/wifihandler.hpp b/ESP/lib/src/network/wifihandler/wifihandler.hpp index c83ef46..fd187d7 100644 --- a/ESP/lib/src/network/wifihandler/wifihandler.hpp +++ b/ESP/lib/src/network/wifihandler/wifihandler.hpp @@ -10,14 +10,13 @@ class WiFiHandler : public IObserver { WiFiHandler(ProjectConfig& configManager, const std::string& ssid, const std::string& password, - uint8_t channel); + uint8_t channel, + bool enable_adhoc); virtual ~WiFiHandler(); void begin(); void update(ConfigState_e event) override; std::string getName() override; - bool _enable_adhoc; - private: void setUpADHOC(); void adhoc(const std::string& ssid, @@ -30,6 +29,8 @@ class WiFiHandler : public IObserver { ProjectConfig& configManager; + + bool _enable_adhoc; std::string ssid; std::string password; uint8_t channel; diff --git a/ESP/lib/src/openiris.hpp b/ESP/lib/src/openiris.hpp index 7316fdd..2cd61af 100644 --- a/ESP/lib/src/openiris.hpp +++ b/ESP/lib/src/openiris.hpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #ifndef ETVR_EYE_TRACKER_USB_API #include diff --git a/ESP/src/main.cpp b/ESP/src/main.cpp index 05c3c04..d37ded7 100644 --- a/ESP/src/main.cpp +++ b/ESP/src/main.cpp @@ -1,5 +1,4 @@ #include - /** * @brief ProjectConfig object * @brief This is the main configuration object for the project @@ -7,6 +6,8 @@ * @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); @@ -19,7 +20,7 @@ CameraHandler cameraHandler(deviceConfig); #endif // SIM_ENABLED #ifndef ETVR_EYE_TRACKER_USB_API -WiFiHandler wifiHandler(deviceConfig, WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL); +WiFiHandler wifiHandler(deviceConfig, WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL, ENABLE_ADHOC); MDNSHandler mdnsHandler(deviceConfig); #ifdef SIM_ENABLED APIServer apiServer(deviceConfig, wifiStateManager, "/control"); @@ -30,9 +31,6 @@ StreamServer streamServer; void etvr_eye_tracker_web_init() { log_d("[SETUP]: Starting Network Handler"); - // deviceConfig.attach(wifiHandler); - log_d("[SETUP]: Checking ADHOC Settings"); - wifiHandler._enable_adhoc = ENABLE_ADHOC; deviceConfig.attach(mdnsHandler); log_d("[SETUP]: Starting WiFi Handler"); wifiHandler.begin(); @@ -78,7 +76,6 @@ void setup() { setCpuFrequencyMhz(240); Serial.begin(115200); Logo::printASCII(); - // Serial.flush(); ledManager.begin(); #ifndef SIM_ENABLED @@ -86,17 +83,16 @@ void setup() { #endif // SIM_ENABLED deviceConfig.load(); +serialManager.init(); + #ifndef ETVR_EYE_TRACKER_USB_API etvr_eye_tracker_web_init(); #else // ETVR_EYE_TRACKER_WEB_API WiFi.disconnect(true); - etvr_eye_tracker_usb_init(); #endif // ETVR_EYE_TRACKER_WEB_API } void loop() { ledManager.handleLED(); -#ifdef ETVR_EYE_TRACKER_USB_API - etvr_eye_tracker_usb_loop(); -#endif // ETVR_EYE_TRACKER_USB_API + serialManager.run(); } From b04f1f986aa03f7703548184f6685beaffa775cf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 18 Feb 2024 17:44:06 +0000 Subject: [PATCH 2/2] chore(minor): 2.3.0 [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [2.3.0](https://github.com/EyeTrackVR/OpenIris/compare/v2.2.11...v2.3.0) (2024-02-18) ### 🍕 Features * Add a PoC implementation of command manager responding to commands send over serial. ([#59](https://github.com/EyeTrackVR/OpenIris/issues/59)) ([54e4e0a](https://github.com/EyeTrackVR/OpenIris/commit/54e4e0abd92b6d3233f81bbbc3d74e1370239b4f)) --- CHANGELOG.md | 7 +++++++ ESP/ini/dev_config.ini | 2 +- ESP/lib/library.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a331a9c..d1f961d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ [![semantic versioning](https://img.shields.io/badge/semantic%20versioning-2.0.0-green.svg)](https://semver.org) > All notable changes to this project will be documented in this file +## [2.3.0](https://github.com/EyeTrackVR/OpenIris/compare/v2.2.11...v2.3.0) (2024-02-18) + + +### 🍕 Features + +* Add a PoC implementation of command manager responding to commands send over serial. ([#59](https://github.com/EyeTrackVR/OpenIris/issues/59)) ([54e4e0a](https://github.com/EyeTrackVR/OpenIris/commit/54e4e0abd92b6d3233f81bbbc3d74e1370239b4f)) + ## [2.2.11](https://github.com/EyeTrackVR/OpenIris/compare/v2.2.10...v2.2.11) (2023-11-18) diff --git a/ESP/ini/dev_config.ini b/ESP/ini/dev_config.ini index a421311..7f95c99 100644 --- a/ESP/ini/dev_config.ini +++ b/ESP/ini/dev_config.ini @@ -5,7 +5,7 @@ platform = https://github.com/platformio/platform-espressif32.git framework = arduino monitor_speed = 115200 -custom_firmware_version = 2.2.11 +custom_firmware_version = 2.3.0 monitor_rts = 0 monitor_dtr = 0 monitor_filters = diff --git a/ESP/lib/library.json b/ESP/lib/library.json index 0058fdf..6e451da 100644 --- a/ESP/lib/library.json +++ b/ESP/lib/library.json @@ -20,7 +20,7 @@ "include": "/lib/src" }, "dependencies": {}, - "version": "2.2.11", + "version": "2.3.0", "frameworks": "arduino", "platforms": "espressif32" }