Compare commits

...

13 Commits

Author SHA1 Message Date
Lorow
5e73704e8c
Merge 18cf39f291 into ebecc61292 2025-01-23 00:51:16 +00:00
semantic-release-bot
ebecc61292 chore(minor): 2.8.0 [skip ci]
## [2.8.0](https://github.com/EyeTrackVR/OpenIris/compare/v2.7.0...v2.8.0) (2025-01-15)

### 🍕 Features

* Fix typo in SerialManager ([#84](https://github.com/EyeTrackVR/OpenIris/issues/84)) ([f460867](f460867497))
2025-01-15 08:45:51 +00:00
Lorow
f460867497
feat: Fix typo in SerialManager (#84) 2025-01-15 09:41:26 +01:00
semantic-release-bot
25f99ce449 chore(minor): 2.7.0 [skip ci]
## [2.7.0](https://github.com/EyeTrackVR/OpenIris/compare/v2.6.0...v2.7.0) (2025-01-12)

### 🍕 Features

* Release 2.7.0 ([#83](https://github.com/EyeTrackVR/OpenIris/issues/83)) ([c7aef84](c7aef8457e))
2025-01-12 19:33:48 +00:00
Lorow
c7aef8457e
feat: Release 2.7.0 (#83)
Add better support for Babble, make resolution easier to select via ini files configuration

---------

Co-authored-by: Rames The Generic <ramesthegeneric@gmail.com>
Co-authored-by: m-RNA <41933749+m-RNA@users.noreply.github.com>
2025-01-12 20:29:40 +01:00
Lorow
18cf39f291 Make project config use update methods 2024-08-25 01:26:53 +02:00
Lorow
a085b6d885 Begin commands cleanup 2024-08-25 01:16:43 +02:00
Lorow
04af0160c4 add SetFPSCommand and ToggleStreamCommand, cleanup streaming implementation, add FPS limiting in TCP stream, improve logging a bit 2024-08-18 21:27:21 +02:00
Lorow
6241cf9add simplify and document handleBatchCommands 2024-08-17 22:57:29 +02:00
Lorow
7bf33d8c45 simplify command result creation, add saving config in serial manager, fix bug in handleSingleCommand 2024-08-17 00:05:59 +02:00
Lorow
48e1e8de17 initial experimentation with streaming over UDP/TCP 2024-08-16 16:19:42 +02:00
Lorow
27177e411c Rewrite commands into a more modern form in anticipation for API rewrite 2024-08-15 20:15:09 +02:00
Lorow
4705e41476 Initial changes, cleanup and improvements 2024-08-15 20:13:01 +02:00
31 changed files with 1127 additions and 558 deletions

View File

@ -40,7 +40,7 @@ jobs:
matrix: ${{fromJson(needs.setup.outputs.matrix)}}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build OpenIris Firmware
@ -68,7 +68,7 @@ jobs:
run: |
echo "FIRMWARE_NAME=$(cat ESP/build/${{ matrix.target_name }}${{ matrix.target_build_type }}/firmware_name.txt)" >> $GITHUB_ENV
- name: Archive Firmware Binaries
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target_name }}${{ matrix.target_build_type }}-firmware
#path: "./ESP/.pio/build/${{ matrix.target_name }}${{ matrix.target_build_type }}/${{ env.FIRMWARE_NAME }}.bin"
@ -80,17 +80,17 @@ jobs:
needs: [build]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Create Directory
run: mkdir -p build
- name: Download Firmware Files
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
path: build/
- name: Setup node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18
- run: npm install -g conventional-changelog-conventionalcommits@6
@ -117,6 +117,6 @@ jobs:
timeout-minutes: 10
steps:
- name: "♻️ remove build artifacts"
uses: geekyeggo/delete-artifact@v1
uses: geekyeggo/delete-artifact@v5
with:
name: ${{ matrix.target_name }}${{ matrix.target_build_type }}-firmware

View File

@ -3,6 +3,43 @@
[![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.8.0](https://github.com/EyeTrackVR/OpenIris/compare/v2.7.0...v2.8.0) (2025-01-15)
### 🍕 Features
* Fix typo in SerialManager ([#84](https://github.com/EyeTrackVR/OpenIris/issues/84)) ([f460867](https://github.com/EyeTrackVR/OpenIris/commit/f4608674978edf5728e495824ed615d9d45f85fd))
## [2.7.0](https://github.com/EyeTrackVR/OpenIris/compare/v2.6.0...v2.7.0) (2025-01-12)
### 🍕 Features
* Release 2.7.0 ([#83](https://github.com/EyeTrackVR/OpenIris/issues/83)) ([c7aef84](https://github.com/EyeTrackVR/OpenIris/commit/c7aef8457eb68fef6713daaf81c6eb23491d9ec7))
## [2.7.0-beta.2](https://github.com/EyeTrackVR/OpenIris/compare/v2.7.0-beta.1...v2.7.0-beta.2) (2025-01-12)
### 🍕 Features
* Add camera resolution configuration support ([#79](https://github.com/EyeTrackVR/OpenIris/issues/79)) ([5c76e01](https://github.com/EyeTrackVR/OpenIris/commit/5c76e01c5e466c8cc7ef4cd43e78a763af00c06e))
## [2.7.0-beta.1](https://github.com/EyeTrackVR/OpenIris/compare/v2.6.0...v2.7.0-beta.1) (2025-01-11)
### 🍕 Features
* Add support for ov5640 cameras ([14d2cb0](https://github.com/EyeTrackVR/OpenIris/commit/14d2cb0858cd65452f2ce1cee9a3a1f68d8f7f51))
* Fix ov5640 support implementation after beta cleanup ([4894c22](https://github.com/EyeTrackVR/OpenIris/commit/4894c2205864d5a7d52e37c81b14ff7dc46517d4))
* fix usb streaming on wroom boards, add support for babble custom board ([049b160](https://github.com/EyeTrackVR/OpenIris/commit/049b16078ec5c07042652f2de3545ea6b69de6a9))
* Move logging in serial streaming so that we at least attempt to get new frame ([53a3d4b](https://github.com/EyeTrackVR/OpenIris/commit/53a3d4bcd603e2fd07546621628e69de88e4139a))
* Refactor commands and serial manager to support updated commands payload ([811a9f8](https://github.com/EyeTrackVR/OpenIris/commit/811a9f898a3f1bdb1e20ffd6f0d6e77a4533f52c))
### 🐛 Bug Fixes
* serial output for esp32cams and other boards not supporting higher frequency baud rate ([01e5e28](https://github.com/EyeTrackVR/OpenIris/commit/01e5e280ad26d2709231fe6aa2a96d1991cbf360))
## [2.6.0](https://github.com/EyeTrackVR/OpenIris/compare/v2.5.0...v2.6.0) (2024-07-14)

View File

@ -190,7 +190,10 @@ upload_flags = --no-stub
[env:Babble-wrooms-s3]
board = esp32-s3-devkitc-1
board_build.flash_mode = qio ; qio_qspi
board_upload.flashsize = "4MB"
board_upload.flash_size=4MB
board_upload.maximum_size = 4194304
board_build.flash_mode = qio
board_build.arduino.memory_type = qio_qspi
build_type = debug
build_flags = ${env.build_flags}
@ -200,9 +203,27 @@ build_flags = ${env.build_flags}
${pinoutSWROOMBABBLES3.build_flags}
upload_flags = --no-stub
[env:Babble-wrooms-s3_release]
board = esp32-s3-devkitc-1
board_upload.flashsize = "4MB"
board_upload.flash_size=4MB
board_upload.maximum_size = 4194304
board_build.flash_mode = qio
board_build.arduino.memory_type = qio_qspi
build_type = debug
build_flags = ${env.build_flags}
-DCORE_DEBUG_LEVEL=1
-DDEBUG_MODE=0
-DSERIAL_MANAGER_USE_HIGHER_FREQUENCY
${pinoutSWROOMBABBLES3.build_flags}
upload_flags = --no-stub
[env:Babble_USB-wrooms-s3]
board = esp32-s3-devkitc-1
board_build.flash_mode = qio ; qio_qspi
board_upload.flashsize = "4MB"
board_upload.flash_size=4MB
board_upload.maximum_size = 4194304
board_build.flash_mode = qio
board_build.arduino.memory_type = qio_qspi
build_type = debug
build_flags = ${env.build_flags}
@ -211,6 +232,25 @@ build_flags = ${env.build_flags}
-DETVR_EYE_TRACKER_USB_API
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_FLASH
-DSERIAL_MANAGER_USE_HIGHER_FREQUENCY
${pinoutSWROOMBABBLES3.build_flags}
upload_flags = --no-stub
[env:Babble_USB-wrooms-s3_release]
board = esp32-s3-devkitc-1
board_upload.flashsize = "4MB"
board_upload.flash_size=4MB
board_upload.maximum_size = 4194304
board_build.flash_mode = qio
board_build.arduino.memory_type = qio_qspi
build_type = debug
build_flags = ${env.build_flags}
-DCORE_DEBUG_LEVEL=1
-DDEBUG_MODE=0
-DETVR_EYE_TRACKER_USB_API
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
-DSERIAL_MANAGER_USE_HIGHER_FREQUENCY
${pinoutSWROOMBABBLES3.build_flags}
upload_flags = --no-stub

View File

@ -5,7 +5,7 @@
platform = https://github.com/platformio/platform-espressif32.git
framework = arduino
monitor_speed = 115200
custom_firmware_version = 2.6.0
custom_firmware_version = 2.8.0
monitor_rts = 0
monitor_dtr = 0
monitor_filters =
@ -46,6 +46,7 @@ build_flags =
'-DOTA_PASSWORD=${ota.otapassword}' ; Set the OTA password
'-DOTA_LOGIN=${ota.otalogin}'
'-DCAM_RESOLUTION=${cam.resolution}'
-O2 ; optimize for speed
-DASYNCWEBSERVER_REGEX ; enable regex in asyncwebserver

View File

@ -18,3 +18,6 @@ otapassword = "12345678"
[development]
serial_flush_enabled = 0
[cam]
resolution = FRAMESIZE_240X240

View File

@ -20,7 +20,7 @@
"include": "/lib/src"
},
"dependencies": {},
"version": "2.6.0",
"version": "2.8.0",
"frameworks": "arduino",
"platforms": "espressif32"
}

View File

@ -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<bool>());
return CommandResult::getSuccessResult("TCP Stream state set to:" +
data["state"].as<std::string>());
}

View File

@ -0,0 +1,72 @@
#ifndef COMMAND_HPP
#define COMMAND_HPP
#include <ArduinoJson.h>
#include <optional>
#include <string>
#include <variant>
#include "data/config/project_config.hpp"
#include "network/stream/streamServer.hpp"
class CommandResult {
private:
std::optional<std::string> successMessage;
std::optional<std::string> errorMessage;
public:
CommandResult(std::optional<std::string> success_message,
std::optional<std::string> 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(); }
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 ToggleStreamCommand : public ICommand {
StreamServer& streamServer;
JsonVariant data;
public:
ToggleStreamCommand(StreamServer& streamServer, JsonVariant data)
: streamServer(streamServer), data(data) {}
CommandResult validate() override;
CommandResult execute() override;
};
#endif

View File

@ -1,9 +1,26 @@
#include "CommandManager.hpp"
CommandManager::CommandManager(ProjectConfig* deviceConfig)
: deviceConfig(deviceConfig) {}
std::unique_ptr<ICommand> CommandManager::createCommand(CommandType commandType,
JsonVariant& data) {
switch (commandType) {
case CommandType::PING:
return std::make_unique<PingCommand>();
case CommandType::SET_WIFI:
return std::make_unique<SetWiFiCommand>(this->projectConfig, data);
case CommandType::SET_MDNS:
return std::make_unique<SetMDNSCommand>(this->projectConfig, data);
case CommandType::SAVE_CONFIG:
return std::make_unique<SaveConfigCommand>(this->projectConfig);
case CommandType::SET_FPS:
return std::make_unique<SetFPSCommand>(this->projectConfig, data);
case CommandType::TOGGLE_STREAM:
return std::make_unique<ToggleStreamCommand>(this->streamServer, data);
default:
return nullptr;
}
}
const CommandType CommandManager::getCommandType(JsonVariant& command) {
CommandType CommandManager::getCommandType(JsonVariant& command) {
if (!command.containsKey("command"))
return CommandType::None;
@ -18,64 +35,115 @@ bool CommandManager::hasDataField(JsonVariant& command) {
return command.containsKey("data");
}
void CommandManager::handleCommands(CommandsPayload commandsPayload) {
CommandResult CommandManager::handleBatchCommands(
CommandsPayload commandsPayload) {
std::vector<std::unique_ptr<ICommand>> commands;
std::vector<std::string> results = {};
std::vector<std::string> errors = {};
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(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<JsonArray>()) {
this->handleCommand(commandData);
auto command_or_result = this->createCommandFromJsonVariant(commandData);
if (auto command_ptr =
std::get_if<std::unique_ptr<ICommand>>(&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<CommandResult>(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("\"[%s]\"", this->join_strings(errors, ", ")));
for (auto& valid_command : commands) {
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 CommandResult::getErrorResult(
Helpers::format_string("\"[%s]\"", this->join_strings(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<std::string>();
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(error);
}
JsonVariant commandData = commandsPayload.data;
auto command_or_result = this->createCommandFromJsonVariant(commandData);
if (std::holds_alternative<CommandResult>(command_or_result)) {
return std::get<CommandResult>(command_or_result);
}
auto command =
std::move(std::get<std::unique_ptr<ICommand>>(command_or_result));
auto validation_result = command->validate();
if (!validation_result.isSuccess()) {
return validation_result;
};
return command->execute();
}
std::variant<std::unique_ptr<ICommand>, 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());
return CommandResult::getErrorResult(error);
}
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<JsonVariant>();
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;
}

View File

@ -2,37 +2,74 @@
#ifndef TASK_MANAGER_HPP
#define TASK_MANAGER_HPP
#include <ArduinoJson.h>
#include <iostream>
#include <iterator>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
#include "data/CommandManager/BaseCommand.hpp"
#include "data/config/project_config.hpp"
#include "network/stream/streamServer.hpp"
struct CommandsPayload {
JsonVariant data;
};
enum CommandType {
None,
PING,
SET_WIFI,
SET_MDNS,
SET_FPS,
TOGGLE_STREAM,
SAVE_CONFIG,
};
struct CommandsPayload {
JsonDocument data;
};
const std::unordered_map<std::string, CommandType> 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:
const std::unordered_map<std::string, CommandType> commandMap = {
{"ping", CommandType::PING},
{"set_wifi", CommandType::SET_WIFI},
{"set_mdns", CommandType::SET_MDNS},
};
ProjectConfig &projectConfig;
StreamServer &streamServer;
ProjectConfig* deviceConfig;
std::string join_strings(std::vector<std::string> const &strings,
std::string delim) {
std::stringstream ss;
std::copy(strings.begin(), strings.end(),
std::ostream_iterator<std::string>(ss, delim.c_str()));
return ss.str();
}
bool hasDataField(JsonVariant& command);
void handleCommand(JsonVariant command);
const CommandType getCommandType(JsonVariant& command);
bool hasDataField(JsonVariant &command);
std::unique_ptr<ICommand> createCommand(CommandType commandType,
JsonVariant &data);
std::variant<std::unique_ptr<ICommand>, 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, StreamServer &streamServer)
: projectConfig(projectConfig), streamServer(streamServer) {};
CommandResult handleSingleCommand(CommandsPayload commandsPayload);
CommandResult handleBatchCommands(CommandsPayload commandsPayload);
};
#endif

View File

@ -0,0 +1,60 @@
#include "ConfigCommands.hpp"
CommandResult SetWiFiCommand::validate() {
if (!data.containsKey("ssid"))
return CommandResult::getErrorResult("Missing ssid");
if (!data.containsKey("password"))
return CommandResult::getErrorResult("Missing password");
return CommandResult::getSuccessResult("");
}
CommandResult SetWiFiCommand::execute() {
std::string network_name = "main";
if (data.containsKey("network_name"))
network_name = data["network_name"].as<std::string>();
projectConfig.setWifiConfig(network_name, data["ssid"], data["password"], 0,
0, false, false);
return CommandResult::getSuccessResult("WIFI Set to: " +
data["ssid"].as<std::string>());
}
CommandResult SetMDNSCommand::validate() {
if (!data.containsKey("hostname") || !strlen(data["hostname"]))
return CommandResult::getErrorResult("Missing hostname");
return CommandResult::getSuccessResult("");
}
CommandResult SetMDNSCommand::execute() {
projectConfig.setMDNSConfig(data, false);
return CommandResult::getSuccessResult("MDNS set to:" +
data["hostname"].as<std::string>());
}
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");
}
CommandResult SetFPSCommand::validate() {
if (!data.containsKey("fps") || !data["hostname"])
return CommandResult::getErrorResult("Missing fps or FPS were negative");
return CommandResult::getSuccessResult("");
}
CommandResult SetFPSCommand::execute() {
projectConfig.setCameraConfig("fps", data["fps"]);
return CommandResult::getSuccessResult("FPS set to:" +
data["fps"].as<std::string>());
}

View File

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

View File

@ -1,4 +1,5 @@
#include "project_config.hpp"
#include "sensor.h"
ProjectConfig::ProjectConfig(const std::string& name,
const std::string& mdnsName)
@ -53,7 +54,7 @@ void ProjectConfig::initConfig() {
this->config.camera = {
.vflip = 0,
.href = 0,
.framesize = 4,
.framesize = (uint8_t)CAM_RESOLUTION,
.quality = 7,
.brightness = 2,
};
@ -200,7 +201,7 @@ void ProjectConfig::load() {
/* Camera Config */
this->config.camera.vflip = getInt("vflip", 0);
this->config.camera.href = getInt("href", 0);
this->config.camera.framesize = getInt("framesize", 4);
this->config.camera.framesize = getInt("framesize", (uint8_t)CAM_RESOLUTION);
this->config.camera.quality = getInt("quality", 7);
this->config.camera.brightness = getInt("brightness", 2);
@ -213,59 +214,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<JsonObject>()) {
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<JsonObject>()) {
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<JsonObject>()) {
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<std::string>();
for (auto it = this->config.networks.begin();
it != this->config.networks.end();) {
@ -273,12 +259,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<JsonObject>()) {
it->update(kv.key().c_str(), kv.value());
}
if (shouldNotify) {
wifiStateManager.setState(WiFiState_e::WiFiState_Disconnected);
@ -293,20 +277,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<std::string>(),
// todo add validation for this, maybe deconstruct it before emplacing
data["password"].as<std::string>(), data["channel"].as<std::string>(),
data["power"].as<std::string>(), false);
}
if (shouldNotify) {
@ -324,17 +307,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 +328,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<JsonObject>()) {
config.device.update(kv.key().c_str(), kv.value());
}
if (shouldNotify) {
wifiStateManager.setState(WiFiState_e::WiFiState_None);
WiFi.disconnect();

View File

@ -2,9 +2,12 @@
#ifndef PROJECT_CONFIG_HPP
#define PROJECT_CONFIG_HPP
#include <Arduino.h>
#include <ArduinoJson.h>
#include <Preferences.h>
#include <algorithm>
#include <string>
#include <variant>
#include <vector>
#include "data/StateManager/StateManager.hpp"
@ -33,22 +36,62 @@ class ProjectConfig : public Preferences, public ISubject<ConfigState_e> {
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<void(JsonVariant)> and some lambda captures
// but it seemed too unvieldy for the time being
if (field == "OTALogin") {
this->OTALogin = value.as<std::string>();
} else if (field == "OTAPassword") {
this->OTAPassword = value.as<std::string>();
} else if (field == "OTAPort") {
this->OTAPort = value.as<int>();
}
}
};
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<std::string>());
} else if (field == "service") {
this->service = std::move(value.as<std::string>());
}
}
};
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<uint8_t>();
} else if (field == "href") {
this->href = value.as<uint8_t>();
} else if (field == "framesize") {
this->framesize = value.as<uint8_t>();
} else if (field == "quality") {
this->quality = value.as<uint8_t>();
} else if (field == "brightness") {
this->brightness = value.as<uint8_t>();
} else if (field == "fps") {
this->fps = value.as<uint8_t>();
}
}
};
struct WiFiConfig_t {
@ -73,6 +116,22 @@ class ProjectConfig : public Preferences, public ISubject<ConfigState_e> {
bool adhoc;
std::string toRepresentation();
void update(std::string field, JsonVariant value) {
if (field == "name") {
this->name = std::move(value.as<std::string>());
} else if (field == "ssid") {
this->ssid = std::move(value.as<std::string>());
} else if (field == "password") {
this->password = std::move(value.as<std::string>());
} else if (field == "channel") {
this->channel = value.as<uint8_t>();
} else if (field == "power") {
this->power = value.as<uint8_t>();
} else if (field == "adhoc") {
this->adhoc = value.as<bool>();
}
}
};
struct AP_WiFiConfig_t {
@ -81,11 +140,23 @@ class ProjectConfig : public Preferences, public ISubject<ConfigState_e> {
uint8_t channel;
bool adhoc;
std::string toRepresentation();
void update(std::string field, JsonVariant value) {
if (field == "ssid") {
this->ssid = std::move(value.as<std::string>());
} else if (field == "password") {
this->password = std::move(value.as<std::string>());
} else if (field == "channel") {
this->channel = value.as<uint8_t>();
} else if (field == "adhoc") {
this->adhoc = value.as<bool>();
}
}
};
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<ConfigState_e> {
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);

View File

@ -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<std::string> &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<std::string>& 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<std::string> Helpers::split(const std::string &s, char delimiter)
{
std::vector<std::string> parts;
std::string part;
std::istringstream tokenStream(s);
while (std::getline(tokenStream, part, delimiter))
{
parts.push_back(part);
}
return parts;
std::vector<std::string> Helpers::split(const std::string& s, char delimiter) {
std::vector<std::string> 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();
}

View File

@ -1,37 +1,41 @@
#ifndef HELPERS_HPP
#define HELPERS_HPP
#include <string>
#include <sstream>
#include <vector>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
namespace Helpers
{
char *itoa(int value, char *result, int base);
void split(std::string str, std::string splitBy, std::vector<std::string> &tokens);
std::vector<std::string> 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 <typename... Args>
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_t>(size_s);
std::unique_ptr<char[]> 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<std::string>& tokens);
std::vector<std::string> split(const std::string& s, char delimiter);
void update_progress_bar(int progress, int total);
/// @brief
/// @tparam ...Args
/// @param format
/// @param ...args
/// @return
template <typename... Args>
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_t>(size_s);
std::unique_ptr<char[]> 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

View File

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

View File

@ -1,36 +1,38 @@
#ifndef LEDMANAGER_HPP
#define LEDMANAGER_HPP
#include <vector>
#include <Arduino.h>
#include <data/StateManager/StateManager.hpp>
#include <unordered_map>
#include <vector>
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<LEDStates_e, std::vector<BlinkPatterns_t>> ledStateMap_t;
static ledStateMap_t ledStateMap;
static std::vector<LEDStates_e> keepAliveStates;
LEDStates_e currentState;
unsigned int currentPatternIndex = 0;
typedef std::unordered_map<LEDStates_e, std::vector<BlinkPatterns_t>>
ledStateMap_t;
static ledStateMap_t ledStateMap;
static std::vector<LEDStates_e> keepAliveStates;
LEDStates_e currentState;
unsigned int currentPatternIndex = 0;
};
#endif // LEDMANAGER_HPP
#endif // LEDMANAGER_HPP

View File

@ -52,7 +52,7 @@ void SerialManager::send_frame() {
#endif
void SerialManager::init() {
#ifndef SERIAL_MANAGER_USE_HIGHER_FREQUENCY
#ifdef SERIAL_MANAGER_USE_HIGHER_FREQUENCY
Serial.begin(3000000);
#endif
if (SERIAL_FLUSH_ENABLED) {
@ -67,12 +67,30 @@ void SerialManager::run() {
if (deserializationError) {
log_e("Command deserialization failed: %s", deserializationError.c_str());
return;
}
CommandsPayload commands = {doc};
this->commandManager->handleCommands(commands);
CommandResult result = CommandResult::getSuccessResult("");
if (doc.containsKey("command")) {
result = this->commandManager->handleSingleCommand(commands);
} else {
result = this->commandManager->handleBatchCommands(commands);
}
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<JsonObject>();
CommandsPayload saveCommandPayload = {saveCommanddDoc};
this->commandManager->handleSingleCommand(saveCommandPayload);
} else {
Serial.printf("%s \n\r", result.getErrorMessage().c_str());
}
}
#ifdef ETVR_EYE_TRACKER_USB_API
else {

View File

@ -8,9 +8,7 @@
#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";
#include "data/utilities/helpers.hpp"
enum QueryAction {
READY_TO_RECEIVE,

View File

@ -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,
@ -63,7 +64,7 @@ void CameraHandler::setupCameraPinout() {
void CameraHandler::setupBasicResolution() {
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_240X240;
config.frame_size = CAM_RESOLUTION;
if (!psramFound()) {
log_e("[Camera]: Did not find psram, setting lower image quality");
@ -108,6 +109,10 @@ void CameraHandler::setupCameraSensor() {
camera_sensor->set_aec2(camera_sensor, 0); // 0 = disable , 1 = enable
camera_sensor->set_ae_level(camera_sensor, 0); // -2 to 2
camera_sensor->set_aec_value(camera_sensor, 300); // 0 to 1200
// Use a lower aec value for babble to better isolate the face with illuminators
#ifdef CONFIG_CAMERA_MODULE_SWROOM_BABBLE_S3
camera_sensor->set_aec_value(camera_sensor, 100); // 0 to 1200
#endif
// controls the gain
camera_sensor->set_gain_ctrl(camera_sensor, 0); // 0 = disable , 1 = enable
@ -195,7 +200,29 @@ void CameraHandler::loadConfigData() {
log_d("Loading camera config data done");
}
int CameraHandler::setCameraResolution(framesize_t frameSize) {
#ifdef CONFIG_CAMERA_MODULE_SWROOM_BABBLE_S3
int CameraHandler::setCameraResolution(framesize_t frameSize) { // For Babble, use a firmware crop as shown by Physdude
if (camera_sensor->pixformat == PIXFORMAT_JPEG) {
try {
int outputSize = 240;
int baseRes = 2; //CIF
int ROIsize = 240;
int startPointX = 80;
int startPointY = 28;
return camera_sensor->set_res_raw(camera_sensor, baseRes, 0, 0, 0, startPointX, startPointY, ROIsize, ROIsize, outputSize, outputSize, 0, 0);
} catch (...) {
// they sent us a malformed or unsupported frameSize - rather than crash -
// tell them about it
return -1;
}
}
return -1;
}
#else
int CameraHandler::setCameraResolution(framesize_t frameSize) { // By default, use the standard method.
if (camera_sensor->pixformat == PIXFORMAT_JPEG) {
try {
return camera_sensor->set_framesize(camera_sensor, frameSize);
@ -207,6 +234,7 @@ int CameraHandler::setCameraResolution(framesize_t frameSize) {
}
return -1;
}
#endif
int CameraHandler::setVFlip(int direction) {
return camera_sensor->set_vflip(camera_sensor, direction);

View File

@ -1,17 +0,0 @@
#ifndef HASH_H_
#define HASH_H_
#include <stdint.h>
#include <string>
// #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_ */

View File

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

View File

@ -3,7 +3,6 @@
//! Warning do not format this file with clang-format or it will break the code
#include <unordered_map>
#include <string>
#include <unordered_map>
@ -31,7 +30,6 @@ constexpr int HTTP_ANY = 0b01111111;
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include "Hash.h"
#include "data/utilities/network_utilities.hpp"
#include "tasks/tasks.hpp"
@ -100,33 +98,34 @@ class BaseAPI {
typedef std::unordered_map<std::string, WebRequestMethodComposite>
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

View File

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

View File

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

View File

@ -1,114 +1,203 @@
#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;
}
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;
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");
String serverStatusMessage = "The stream is under: http://";
switch (wifiStateManager.getCurrentState()) {
case WiFiState_e::WiFiState_ADHOC:
// 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.println((serverStatusMessage + WiFi.localIP().toString() + ":" +
this->STREAM_SERVER_PORT + "\n\r")
.c_str());
break;
}
return 0;
}
}
bool StreamServer::startTCPStreamServer() {
tcp_server = new AsyncServer(this->TCP_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() || this->pauseTCPStream) {
return;
}
// 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) {
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);
}
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;
}
}

View File

@ -3,8 +3,17 @@
#define STREAM_SERVER_HPP
#define PART_BOUNDARY "123456789000000000000987654321"
#include <Arduino.h>
#include <AsyncTCP.h>
#include <AsyncUDP.h>
#include <WiFi.h>
#include <iomanip>
#include <sstream>
#include "data/StateManager/StateManager.hpp"
#include "data/config/project_config.hpp"
#include "data/utilities/Observer.hpp"
#include "data/utilities/helpers.hpp"
// Camera includes
#include "esp_camera.h"
@ -13,20 +22,47 @@
#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 : public IObserver<ConfigState_e> {
private:
ProjectConfig& configManager;
int64_t last_frame = 0;
private:
httpd_handle_t camera_stream = nullptr;
int STREAM_SERVER_PORT;
AsyncServer* tcp_server;
AsyncClient* tcp_connected_client;
httpd_handle_t camera_stream = nullptr;
public:
StreamServer(const int STREAM_PORT = 80);
int startStreamServer();
long last_request_time = 0;
int STREAM_SERVER_PORT = 80;
int TCP_STREAM_SERVER_PORT = 82;
int last_time_frame_sent = 0;
float target_fps_time = 1000 / 30;
bool pauseTCPStream = true;
public:
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 startTCPStreamServer();
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
#endif // STREAM_SERVER_HPP

View File

@ -1,7 +1,4 @@
#include "wifihandler.hpp"
#include <WiFi.h>
#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");

View File

@ -1,8 +1,12 @@
#pragma once
#ifndef WIFIHANDLER_HPP
#define WIFIHANDLER_HPP
#include <WiFi.h>
#include <string>
#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<ConfigState_e> {
@ -29,7 +33,6 @@ class WiFiHandler : public IObserver<ConfigState_e> {
ProjectConfig& configManager;
bool _enable_adhoc;
std::string ssid;
std::string password;

View File

@ -6,18 +6,14 @@
* @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);
#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,13 +26,16 @@ WiFiHandler wifiHandler(deviceConfig,
WIFI_CHANNEL,
ENABLE_ADHOC);
MDNSHandler mdnsHandler(deviceConfig);
#ifdef SIM_ENABLED
APIServer apiServer(deviceConfig, wifiStateManager, "/control");
#else
APIServer apiServer(deviceConfig, cameraHandler, "/control");
StreamServer streamServer;
#ifndef SIM_ENABLED
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);
@ -45,37 +44,27 @@ 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
log_d("[SETUP]: Starting Stream Server");
streamServer.startStreamServer();
#endif // SIM_ENABLED
if (wifiState == WiFiState_e::WiFiState_Connected ||
wifiState == WiFiState_e::WiFiState_ADHOC) {
{
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]: 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 +75,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 +89,9 @@ void setup() {
}
void loop() {
#ifndef ETVR_EYE_TRACKER_USB_API
streamServer.sendTCPFrame();
#endif
ledManager.handleLED();
serialManager.run();
}