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
This commit is contained in:
Zdzislaw Goik 2024-02-13 13:14:20 +01:00 committed by lorow
parent 7e459da882
commit 54e4e0abd9
10 changed files with 244 additions and 24 deletions

View File

@ -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

View File

@ -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<std::string>();
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;
}
}

View File

@ -0,0 +1,35 @@
#pragma once
#ifndef TASK_MANAGER_HPP
#define TASK_MANAGER_HPP
#include <ArduinoJson.h>
#include <unordered_map>
#include "data/config/project_config.hpp"
enum CommandType {
None,
PING,
SET_WIFI,
};
struct Command {
JsonDocument data;
};
class CommandManager{
private:
const std::unordered_map<std::string, CommandType> 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

View File

@ -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
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#ifndef SERIAL_MANAGER
#define SERIAL_MANAGER
#include <Arduino.h>
#include <ArduinoJson.h>
#include <USBCDC.h>
#include <esp_camera.h>
#include "data/CommandManager/CommandManager.hpp"
#include "data/config/project_config.hpp"
const char* const ETVR_HEADER = "\xff\xa0";
const char* const ETVR_HEADER_FRAME = "\xff\xa1";
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

View File

@ -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");

View File

@ -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());

View File

@ -10,14 +10,13 @@ class WiFiHandler : public IObserver<ConfigState_e> {
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<ConfigState_e> {
ProjectConfig& configManager;
bool _enable_adhoc;
std::string ssid;
std::string password;
uint8_t channel;

View File

@ -7,6 +7,8 @@
#include <io/LEDManager/LEDManager.hpp>
#include <io/camera/cameraHandler.hpp>
#include <logo/logo.hpp>
#include <io/Serial/SerialManager.hpp>
#include <data/CommandManager/CommandManager.hpp>
#ifndef ETVR_EYE_TRACKER_USB_API
#include <network/api/webserverHandler.hpp>

View File

@ -1,5 +1,4 @@
#include <openiris.hpp>
/**
* @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();
}