large update

- Fully reworked the API code, wifi handler, and serial manager
- Added proper APIServer
This commit is contained in:
ZanzyTHEbar 2022-08-20 14:02:10 +01:00
parent 36bfcf3a3a
commit 687be8afb7
29 changed files with 1322 additions and 725 deletions

View File

@ -0,0 +1,22 @@
#pragma once
#ifndef WIFIHANDLER_HPP
#define WIFIHANDLER_HPP
#include <memory>
#include <WiFi.h>
#include "data/StateManager/StateManager.hpp"
#include "data/config/project_config.hpp"
class WiFiHandler
{
public:
WiFiHandler(ProjectConfig *configManager, StateManager<WiFiState_e> *stateManager);
virtual ~WiFiHandler();
void setupWifi();
ProjectConfig *configManager;
StateManager<WiFiState_e> *stateManager;
private:
void setUpADHOC();
void adhoc(const char *ssid, const char *password, uint8_t channel);
void iniSTA();
};
#endif // WIFIHANDLER_HPP

View File

@ -0,0 +1,151 @@
#include "WifiHandler.hpp"
#include <vector>
WiFiHandler::WiFiHandler(ProjectConfig *configManager, StateManager<WiFiState_e> *stateManager) : configManager(configManager),
stateManager(stateManager) {}
WiFiHandler::~WiFiHandler() {}
void WiFiHandler::setupWifi()
{
if (ENABLE_ADHOC || stateManager->getCurrentState() == WiFiState_e::WiFiState_ADHOC)
{
this->setUpADHOC();
return;
}
log_i("Initializing connection to wifi");
stateManager->setState(WiFiState_e::WiFiState_Connecting);
std::vector<ProjectConfig::WiFiConfig_t> *networks = configManager->getWifiConfigs();
int connection_timeout = 30000; // 30 seconds
int count = 0;
unsigned long currentMillis = millis();
unsigned long _previousMillis = currentMillis;
for (auto networkIterator = networks->begin(); networkIterator != networks->end(); ++networkIterator)
{
log_i("Trying to connect to the %s network", networkIterator->ssid);
WiFi.begin(networkIterator->ssid.c_str(), networkIterator->password.c_str());
count++;
if (!WiFi.isConnected())
log_i("\n\rCould not connect to %s, trying another network\n\r", networkIterator->ssid);
else
{
log_i("\n\rSuccessfully connected to %s\n\r", networkIterator->ssid);
stateManager->setState(WiFiState_e::WiFiState_Connected);
return;
}
while (WiFi.status() != WL_CONNECTED)
{
stateManager->setState(ProgramStates::DeviceStates::WiFiState_e::WiFiState_Connecting);
currentMillis = millis();
Serial.print(".");
delay(300);
if (((currentMillis - _previousMillis) >= connection_timeout) && count >= networks->size())
{
log_i("[INFO]: WiFi connection timed out.\n");
// we've tried all saved networks, none worked, let's error out
log_e("Could not connect to any of the saved networks, check your Wifi credentials");
stateManager->setState(WiFiState_e::WiFiState_Error);
this->iniSTA();
log_i("[INFO]: Attempting to connect to hardcoded network from ini file");
return;
}
}
}
}
void WiFiHandler::adhoc(const char *ssid, const char *password, uint8_t channel)
{
log_i("[INFO]: Setting Access Point...\n");
log_i("[INFO]: Configuring access point...\n");
WiFi.mode(WIFI_AP);
Serial.printf("\r\nStarting AP. \r\nAP IP address: ");
IPAddress IP = WiFi.softAPIP();
Serial.printf("[INFO]: AP IP address: %s.\r\n", IP.toString().c_str());
// You can remove the password parameter if you want the AP to be open.
WiFi.softAP(ssid, password, channel); // AP mode with password
WiFi.setTxPower(WIFI_POWER_11dBm);
stateManager->setState(WiFiState_e::WiFiState_ADHOC);
}
/*
* *
*/
void WiFiHandler::setUpADHOC()
{
log_i("[INFO]: Setting Access Point...\n");
size_t ssidLen = strlen(configManager->getAPWifiConfig()->ssid.c_str());
size_t passwordLen = strlen(configManager->getAPWifiConfig()->password.c_str());
char ssid[ssidLen + 1];
char password[passwordLen + 1];
uint8_t channel = configManager->getAPWifiConfig()->channel;
if (ssidLen > 0 || passwordLen > 0)
{
strcpy(ssid, configManager->getAPWifiConfig()->ssid.c_str());
strcpy(password, configManager->getAPWifiConfig()->password.c_str());
channel = configManager->getAPWifiConfig()->channel;
}
else
{
strcpy(ssid, WIFI_AP_SSID);
strcpy(password, WIFI_AP_PASSWORD);
channel = ADHOC_CHANNEL;
}
this->adhoc(ssid, password, channel);
log_i("[INFO]: Configuring access point...\n");
log_d("[DEBUG]: ssid: %s\n", ssid);
log_d("[DEBUG]: password: %s\n", password);
log_d("[DEBUG]: channel: %d\n", channel);
}
void WiFiHandler::iniSTA()
{
log_i("[INFO]: Setting up station...\n");
int connection_timeout = 30000; // 30 seconds
unsigned long currentMillis = millis();
unsigned long _previousMillis = currentMillis;
log_i("Trying to connect to the %s network", WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL);
if (!WiFi.isConnected())
log_i("\n\rCould not connect to %s, please try another network\n\r", WIFI_SSID);
else
{
log_i("\n\rSuccessfully connected to %s\n\r", WIFI_SSID);
stateManager->setState(WiFiState_e::WiFiState_Connected);
return;
}
while (WiFi.status() != WL_CONNECTED)
{
stateManager->setState(ProgramStates::DeviceStates::WiFiState_e::WiFiState_Connecting);
currentMillis = millis();
Serial.print(".");
delay(300);
if ((currentMillis - _previousMillis) >= connection_timeout)
{
log_i("[INFO]: WiFi connection timed out.\n");
// we've tried all saved networks, none worked, let's error out
log_e("Could not connect to any of the save networks, check your Wifi credentials");
stateManager->setState(WiFiState_e::WiFiState_Error);
this->setUpADHOC();
log_w("Setting up adhoc mode");
log_w("Please use adhoc mode and the app to set your WiFi credentials and reboot the device");
stateManager->setState(WiFiState_e::WiFiState_ADHOC);
return;
}
}
}

View File

@ -166,9 +166,9 @@ void APIServer::triggerWifiConfigWrite()
pass_write = false;
channel_write = false;
if (network->stateManager->getCurrentState() == WiFiState_e::WiFiState_ADHOC)
network->configManager->setWifiConfig(wifiConfig.local_WifiConfig[0].ssid.c_str(), wifiConfig.local_WifiConfig[0].ssid.c_str(), wifiConfig.local_WifiConfig[0].pass.c_str(), &wifiConfig.local_WifiConfig[0].channel, true);
network->configManager->setWifiConfig(wifiConfig.local_WifiConfig[0].ssid.c_str(), wifiConfig.local_WifiConfig[0].ssid.c_str(), wifiConfig.local_WifiConfig[0].pass.c_str(), &wifiConfig.local_WifiConfig[0].channel, wifiConfig.local_WifiConfig[0].adhoc, true);
else
network->configManager->setWifiConfig(wifiConfig.local_WifiConfig[1].ssid.c_str(), wifiConfig.local_WifiConfig[1].ssid.c_str(), wifiConfig.local_WifiConfig[1].pass.c_str(), &wifiConfig.local_WifiConfig[1].channel, true);
network->configManager->setWifiConfig(wifiConfig.local_WifiConfig[1].ssid.c_str(), wifiConfig.local_WifiConfig[1].ssid.c_str(), wifiConfig.local_WifiConfig[1].pass.c_str(), &wifiConfig.local_WifiConfig[1].channel, wifiConfig.local_WifiConfig[1].adhoc, true);
network->configManager->save();
}
}

View File

@ -70,6 +70,7 @@ private:
std::string ssid;
std::string pass;
uint8_t channel;
bool adhoc;
};
struct WifiConfig

View File

@ -22,8 +22,7 @@ void ProjectConfig::initConfig()
false,
"",
"",
""
};
""};
this->config.camera = {
0,
@ -38,6 +37,7 @@ void ProjectConfig::initConfig()
"",
"",
0,
false,
},
};
@ -45,6 +45,7 @@ void ProjectConfig::initConfig()
"",
"",
0,
false,
};
}
@ -82,8 +83,9 @@ void ProjectConfig::load()
bool networks_password_success = this->read(buff, this->config.networks[i].password);
snprintf(buff, sizeof(buff), "%d_channel", i);
bool networks_channel_success = this->read(buff, this->config.networks[i].channel);
bool networks_adhoc_success = this->read(buff, this->config.networks[i].adhoc);
network_info_success = networks_name_success && networks_ssid_success && networks_password_success && networks_channel_success;
network_info_success = networks_name_success && networks_ssid_success && networks_password_success && networks_channel_success && networks_adhoc_success;
}
if (!device_success || !camera_success || !network_info_success)
@ -123,7 +125,13 @@ void ProjectConfig::save()
this->write(buff, this->config.networks[i].password);
snprintf(buff, sizeof(buff), "%d_channel", i);
this->write(buff, this->config.networks[i].channel);
this->write(buff, this->config.networks[i].adhoc);
}
log_i("Project config saved and system is rebooting");
delay(20000);
ESP.restart();
}
void ProjectConfig::reset()
@ -167,7 +175,7 @@ void ProjectConfig::setCameraConfig(uint8_t *vflip, uint8_t *framesize, uint8_t
}
}
void ProjectConfig::setWifiConfig(const char *networkName, const char *ssid, const char *password, uint8_t *channel, bool shouldNotify)
void ProjectConfig::setWifiConfig(const char *networkName, const char *ssid, const char *password, uint8_t *channel, bool adhoc, bool shouldNotify)
{
WiFiConfig_t *networkToUpdate = nullptr;
@ -185,6 +193,7 @@ void ProjectConfig::setWifiConfig(const char *networkName, const char *ssid, con
(char *)ssid,
(char *)password,
*channel,
adhoc,
},
};
if (shouldNotify)
@ -193,12 +202,13 @@ void ProjectConfig::setWifiConfig(const char *networkName, const char *ssid, con
log_d("Updating wifi config");
}
void ProjectConfig::setAPWifiConfig(const char *ssid, const char *password, uint8_t *channel, bool shouldNotify)
void ProjectConfig::setAPWifiConfig(const char *ssid, const char *password, uint8_t *channel, bool adhoc, bool shouldNotify)
{
this->config.ap_network = {
(char *)ssid,
(char *)password,
*channel,
adhoc,
};
log_d("Updating access point config");

View File

@ -45,6 +45,7 @@ public:
std::string ssid;
std::string password;
uint8_t channel;
bool adhoc;
};
struct AP_WiFiConfig_t
@ -52,6 +53,7 @@ public:
std::string ssid;
std::string password;
uint8_t channel;
bool adhoc;
};
struct TrackerConfig_t
@ -69,8 +71,8 @@ public:
void setDeviceConfig(const char *name, const char *OTAPassword, int *OTAPort, bool shouldNotify);
void setCameraConfig(uint8_t *vflip, uint8_t *framesize, uint8_t *href, uint8_t *quality, bool shouldNotify);
void setWifiConfig(const char *networkName, const char *ssid, const char *password, uint8_t *channel, bool shouldNotify);
void setAPWifiConfig(const char *ssid, const char *password, uint8_t *channel, bool shouldNotify);
void setWifiConfig(const char *networkName, const char *ssid, const char *password, uint8_t *channel, bool adhoc, bool shouldNotify);
void setAPWifiConfig(const char *ssid, const char *password, uint8_t *channel, bool adhoc, bool shouldNotify);
private:
const char *configFileName;

View File

@ -1,4 +1,6 @@
#pragma once
#ifndef OBSERVER_HPP
#define OBSERVER_HPP
#include <set>
namespace ObserverEvent
@ -45,3 +47,5 @@ public:
}
}
};
#endif // !OBSERVER_HPP

View File

@ -0,0 +1,80 @@
#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;
}
return result;
}
void split(std::string str, 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.push_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.push_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;
}

View File

@ -0,0 +1,10 @@
#include <string>
#include <sstream>
#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);
}

View File

@ -1,12 +1,10 @@
#pragma once
#ifndef MAKE_UNIQUE_HPP
#define MAKE_UNIQUE_HPP
#include <memory>
#include <cstddef>
#include <type_traits>
#include <utility>
namespace Utilities
{
}
/**
* @brief override the STD namespace to add make_unique function
@ -54,3 +52,5 @@ namespace std
typename _Unique_if<T>::_Known_bound
make_unique(Args &&...) = delete;
}
#endif // !MAKE_UNIQUE_HPP

View File

@ -0,0 +1,54 @@
#include "network_utilities.hpp"
void Network_Utilities::SetupWifiScan()
{
// Set WiFi to station mode and disconnect from an AP if it was previously connected
WiFi.mode(WIFI_STA);
WiFi.disconnect(); // Disconnect from the access point if connected before
delay(100);
Serial.println("Setup done");
}
bool Network_Utilities::LoopWifiScan()
{
// WiFi.scanNetworks will return the number of networks found
log_i("[INFO]: Beginning WiFi Scanner");
int networks = WiFi.scanNetworks();
log_i("[INFO]: scan done");
log_i("%d networks found", networks);
for (int i = networks; i--;)
{
// Print SSID and RSSI for each network found
//! Add method here to interface with the API and forward the scanned networks to the API
log_i("%d: %s (%d) %s\n", i - 1, WiFi.SSID(i), WiFi.RSSI(i), (WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
my_delay(0.02L); // delay 20ms
}
// Wait a bit before scanning again
delay(5000);
return (networks > 0);
}
// Take measurements of the Wi-Fi strength and return the average result.
int Network_Utilities::getStrength(int points) // TODO: add to JSON doc
{
int32_t rssi = 0, averageRSSI = 0;
for (int i = 0; i < points; i++)
{
rssi += WiFi.RSSI();
delay(20);
}
averageRSSI = rssi / points;
return averageRSSI;
}
void Network_Utilities::my_delay(volatile long delay_time)
{
delay_time = delay_time * 1e6L;
for (volatile long count = delay_time; count > 0L; count--)
;
}

View File

@ -0,0 +1,16 @@
#pragma once
#ifndef UTILITIES_hpp
#define UTILITIES_hpp
#include <Arduino.h>
#include "network/wifihandler/WifiHandler.hpp"
#include <unordered_map>
namespace Network_Utilities
{
bool LoopWifiScan();
void SetupWifiScan();
void my_delay(volatile long delay_time);
int CheckWifiState();
int getStrength(int points);
String generateDeviceID();
}
#endif // !UTILITIES_hpp

View File

@ -1,340 +0,0 @@
#include "serialmanager.hpp"
#if SERIAL_CMD_DBG_EN
static void printHex(Stream &port, uint8_t *data, uint8_t length);
static void printHex(Stream &port, uint16_t *data, uint8_t length);
void printHex(Stream &port, uint8_t *data, uint8_t length) // prints 8-bit data in hex with leading zeroes
{
for (int i = 0; i < length; i++)
{
// port.print("0x");
if (data[i] < 0x10)
{
port.print("0");
}
port.print(data[i], HEX);
port.print(" ");
}
}
void printHex(Stream &port, uint16_t *data, uint8_t length) // prints 16-bit data in hex with leading zeroes
{
for (int i = 0; i < length; i++)
{
// port.print("0x");
uint8_t MSB = byte(data[i] >> 8);
uint8_t LSB = byte(data[i]);
if (MSB < 0x10)
{
port.print("0");
}
port.print(MSB, HEX);
if (LSB < 0x10)
{
port.print("0");
}
port.print(LSB, HEX);
port.print(" ");
}
}
#endif
SerialManager2::SerialManager2() : userErrorHandler(NULL), _serial(NULL), ManagerCount(0), _serialManager2Active(false), newData(false)
{
clear();
}
void SerialManager2::begin(Stream &serialPort)
{
/* Save Serial Port configurations */
_serial = &serialPort;
}
// This checks the Serial stream for characters, and assembles them into a buffer.
// When the terminator character (defined by EOL constant) is seen, it starts parsing the
// buffer for a prefix Manager, and calls handlers setup by addManager() method
void SerialManager2::loop(unsigned long timeout)
{
log_d("Listening to serial");
_serialManager2Active = true;
Serial.setTimeout(timeout);
static bool recvInProgress = false;
char startDelimiter = '<'; //! we need to decide on a delimiter for the start of a message
char endDelimiter = '>'; //! we need to decide on a delimiter for the end of a message
char c;
while ((available() > 0) && !newData)
{
c = read();
if (recvInProgress)
{
if (c != endDelimiter)
{
bufferHandler(c);
}
else
{
recvInProgress = false;
newData = true;
}
}
else
{
if (c == startDelimiter)
{
recvInProgress = true;
}
}
}
delay(timeout);
_serialManager2Active = false;
}
/* Clear buffer */
void SerialManager2::clear(void)
{
memset(buffer, 0, SERIAL_CMD_BUFF_LEN);
pBuff = buffer;
}
/*
* Send error response
* NOTE: Will execute user defined callback (defined using addDefault method),
* if no user defined callback it will send the ERROR message (sendERROR method).
*/
void SerialManager2::error(void)
{
if (NULL != userErrorHandler)
{
(*userErrorHandler)();
}
clear(); /* Clear buffer */
}
// Retrieve the next token ("word" or "argument") from the Manager buffer.
// returns a NULL if no more tokens exist.
char *SerialManager2::next(void)
{
return strtok_r(NULL, delimiters, &last);
}
void SerialManager2::bufferHandler(char c)
{
int len;
char *lastChars = NULL;
if ((pBuff - buffer) > (SERIAL_CMD_BUFF_LEN - 2)) /* Check buffer overflow */
{
error(); /* Send ERROR, Buffer overflow */
}
*pBuff++ = c; /* Put character into buffer */
*pBuff = '\0'; /* Always null terminate strings */
if ((pBuff - buffer) > 2) /* Check buffer length */
{
/* Get EOL */
len = strlen(buffer);
lastChars = buffer + len - 2;
/* Compare last chars to EOL */
if (0 == strcmp(lastChars, EOL))
{
// *lastChars = '\0'; /* Replace EOL with NULL terminator */
#if (SERIAL_CMD_DBG_EN == 1)
log_d("Received: %s", buffer);
#endif
if (ManagerHandler())
{
clear();
}
else
{
error();
}
}
}
}
/* Return true if match was found */
bool SerialManager2::ManagerHandler(void)
{
int i;
bool ret = false;
char *token = NULL;
char *offset = NULL;
char userInput[SERIAL_CMD_BUFF_LEN];
memcpy(userInput, buffer, SERIAL_CMD_BUFF_LEN);
/* Search for Manager at start of buffer */
token = strtok_r(buffer, delimiters, &last);
#if SERIAL_CMD_DBG_EN
print("User input: (");
printHex(Serial, (uint8_t *)userInput, SERIAL_CMD_BUFF_LEN);
println(")");
#endif
if (NULL != token)
{
#if SERIAL_CMD_DBG_EN
log_d("Token: %s", token);
#endif
for (i = 0; (i < ManagerCount); i++)
{
#if SERIAL_CMD_DBG_EN
print("Case: \"");
print(ManagerList[i].Manager);
print("\" ");
#endif
/* Compare the token against the list of known Managers */
if (0 == strncmp(token, ManagerList[i].Manager, SERIAL_CMD_BUFF_LEN))
{
#if SERIAL_CMD_DBG_EN
log_d("- Match Found!");
#endif
offset = (char *)(userInput + strlen(token));
/* Check for query Manager */
if (0 == strncmp(offset, "=?", 2))
{
#if SERIAL_CMD_DBG_EN
log_d("Run test callback");
#endif
if (NULL != *ManagerList[i].test)
{
/* Run test callback */
(*ManagerList[i].test)();
}
}
else if (('?' == *offset) && (NULL != *ManagerList[i].read))
{
#if SERIAL_CMD_DBG_EN
log_d("Run read callback");
#endif
/* Run read callback */
(*ManagerList[i].read)();
}
else if (('=' == *offset) && (NULL != *ManagerList[i].write))
{
#if (SERIAL_CMD_DBG_EN == 1)
log_d("Run write callback");
#endif
/* Run write callback */
(*ManagerList[i].write)();
}
else if (NULL != *ManagerList[i].execute)
{
#if SERIAL_CMD_DBG_EN
log_d("Run execute callback");
#endif
/* Run execute callback */
(*ManagerList[i].execute)();
}
else
{
log_e("INVALID");
ret = false;
break;
}
ret = true;
break;
}
#if SERIAL_CMD_DBG_EN
else
{
log_e("- Not a match!");
}
#endif
}
}
return ret;
}
// Adds a "Manager" and a handler function to the list of available Managers.
// This is used for matching a found token in the buffer, and gives the pointer
// to the handler function to deal with it.
void SerialManager2::addManager(char *cmd, void (*test)(), void (*read)(), void (*write)(), void (*execute)())
{
#if SERIAL_CMD_DBG_EN
print("[");
print(ManagerCount);
print("] New Manager: ");
println(cmd);
#endif
ManagerList = (serialManager2Callback *)realloc(ManagerList, (ManagerCount + 1) * sizeof(serialManager2Callback));
strncpy(ManagerList[ManagerCount].Manager, cmd, SERIAL_CMD_BUFF_LEN);
ManagerList[ManagerCount].test = test;
ManagerList[ManagerCount].read = read;
ManagerList[ManagerCount].write = write;
ManagerList[ManagerCount].execute = execute;
ManagerCount++;
}
/* Optional user-defined function to call when an error occurs, default is NULL */
void SerialManager2::addError(void (*callback)())
{
userErrorHandler = callback;
}
int SerialManager2::available()
{
int bytes = 0;
if (NULL != _serial)
{
bytes = _serial->available();
}
return bytes;
}
int SerialManager2::read()
{
int bytes = 0;
if (NULL != _serial)
{
bytes = _serial->read();
}
return bytes;
}
int SerialManager2::peek()
{
int bytes = 0;
if (NULL != _serial)
{
bytes = _serial->peek();
}
return bytes;
}
void SerialManager2::flush()
{
if (NULL != _serial)
{
_serial->flush();
}
}
size_t SerialManager2::write(uint8_t x)
{
(void)x;
return 0;
}
SerialManager2 serialManager2;

View File

@ -1,158 +0,0 @@
#ifndef SERIALMANAGER2_HPP
#define SERIALMANAGER2_HPP
#include <Arduino.h>
#include <string.h>
#define SERIAL_CMD_DBG_EN 0
#define SERIAL_CMD_BUFF_LEN 100 /* Max length for each serial Manager */
/* Data structure to hold Manager/Handler function key-value pairs */
typedef struct
{
char Manager[SERIAL_CMD_BUFF_LEN];
void (*test)();
void (*read)();
void (*write)();
void (*execute)();
} serialManager2Callback;
/*
* Token delimiters (setup '=', query '?', separator ',')
*/
const char delimiters[] = "=,?\r\n";
/*
* End Of Line: <CR><LF>
* <CR> = <Carriage Return, 0x0D, 13, '\r'>
* <LF> = <Line Feed, 0x0A, 10, '\n'>
*/
const char EOL[] = "\r\n";
class SerialManager2 : public Stream
{
public:
SerialManager2();
virtual ~SerialManager2();
/**
* Start connection to serial port
*
* @param serialPort - Serial port to listen for Managers
* @param baud - Baud rate
*/
void begin(Stream &serialPort);
/**
* Execute this function inside Arduino's loop function.
*/
void loop(unsigned long timeout);
/**
* Add a new Manager
*
* @param cmd - Manager to listen
* @param test - Test Manager callback
* @param read - Read Manager callback
* @param write - Write Manager callback
* @param execute - Execute Manager callback
*/
void addManager(char *cmd, void (*test)(), void (*read)(), void (*write)(), void (*execute)());
/**
* Add a read-only Manager
*
* @param cmd - Manager to listen
* @param callback - Read Manager callback
*/
void addTestManager(char *cmd, void (*callback)())
{
addManager(cmd, callback, NULL, NULL, NULL);
}
/**
* Add a read-only Manager
*
* @param cmd - Manager to listen
* @param callback - Read Manager callback
*/
void addReadManager(char *cmd, void (*callback)())
{
addManager(cmd, NULL, callback, NULL, NULL);
}
/**
* Add a write-only Manager
*
* @param cmd - Manager to listen
* @param callback - Write Manager callback
*/
void addWriteManager(char *cmd, void (*callback)())
{
addManager(cmd, NULL, NULL, callback, NULL);
}
/**
* Add a execute-only Manager
*
* @param cmd - Manager to listen
* @param callback - Execute Manager callback
*/
void addExecuteManager(char *cmd, void (*callback)())
{
addManager(cmd, NULL, NULL, NULL, callback);
}
/**
* Default function to execute when no match is found
*
* @param callback - Function to execute when Manager is received
*/
void addError(void (*callback)());
/* Return next argument found in Manager buffer */
char *next(void);
/* variable to track state of newdata in the buffer */
bool newData;
/*
* Virtual methods to match Stream class
*/
size_t write(uint8_t);
int available();
int read();
int peek();
void flush();
private:
/* Setup serial port */
void setup(unsigned long baud);
/* Sets the Manager buffer to all '\0' (nulls) */
void clear(void);
/* Send error message and clear buffer */
void error();
/* Process buffer */
void bufferHandler(char c);
/* Check for Manager instances and handle callbacks and queries */
bool ManagerHandler(void);
/* User defined error handler */
void (*userErrorHandler)();
/* Serial Port handler */
Stream *_serial;
/* Actual definition for Manager/handler array */
serialManager2Callback *ManagerList;
/* Buffer of stored characters while waiting for terminator character */
char buffer[SERIAL_CMD_BUFF_LEN];
/* Pointer to buffer, used to store data in the buffer */
char *pBuff;
/* State variable used by strtok_r during processing */
char *last;
/* Number of available Managers registered by new() */
uint8_t ManagerCount;
bool _serialManager2Active;
};
extern SerialManager2 serialManager2;
#endif // SerialManager2_h

View File

@ -1,126 +1,83 @@
#include "serialmanager.hpp"
std::unordered_map<std::string, SerialManager::Serial_Commands> SerialManager::command_map = {
{"", NO_INPUT},
{"device_config", DEVICE_CONFIG},
{"camera_config", CAMERA_CONFIG},
{"wifi_config", WIFI_CONFIG}};
void readStr(const char *inStr);
SerialManager::SerialManager(ProjectConfig *projectConfig) : projectConfig(projectConfig),
serialManagerActive(false),
newData(false),
tempBuffer{0},
serialBuffer{0},
device_config_name{0},
device_config_OTAPassword{0},
device_config_OTAPort(0),
camera_config_vflip{0},
camera_config_href{0},
camera_config_framesize{0},
camera_config_quality{0},
wifi_config_name{0},
wifi_config_ssid{0},
wifi_config_password{0},
wifi_config_channel(0) {}
serReader(std::make_unique<serialStr>())
{
}
SerialManager::~SerialManager() {}
void SerialManager::listenToSerial(unsigned long timeout)
void SerialManager::begin()
{
log_d("Listening to serial");
serialManagerActive = true;
Serial.setTimeout(timeout);
static bool recvInProgress = false;
static uint8_t index = 0; // index
char startDelimiter = '<'; //! we need to decide on a delimiter for the start of a message
char endDelimiter = '>'; //! we need to decide on a delimiter for the end of a message
char receivedChar; // to test for received data on the line
while ((Serial.available() > 0) && !newData)
{
serialManagerActive = true;
receivedChar = Serial.read();
if (recvInProgress)
{
if (receivedChar != endDelimiter)
{
serialBuffer[index] = receivedChar;
index++;
if (index >= sizeof(serialBuffer))
{
log_e("Serial buffer overflow");
index = 0;
recvInProgress = false;
}
}
else
{
recvInProgress = false;
serialBuffer[index] = '\0';
index = 0;
newData = true;
}
}
else
{
if (receivedChar == startDelimiter)
{
recvInProgress = true;
}
}
}
serialManagerActive = false;
serReader->setCallback(readStr);
}
void SerialManager::parseData()
void readStr(const char *inStr)
{
log_d("Parsing data");
char *strtokIndx; // this is used by strtok() as an index
Serial.print("command : ");
Serial.println(inStr);
std::string raw = inStr;
std::vector<std::string> command;
Helpers::split(raw, ":", command); //! gives us the command and the value - "command:value"
std::vector<std::string> command_value;
Helpers::split(command[1], ",", command_value); //! gives us the command and the value - "command:value"
//! Parse the data
//* Device Config *//
strtokIndx = strtok(tempBuffer, ","); // get the first part
strcpy(device_config_name, strtokIndx); // copy it to buffer
//! The following line uses strdup to return a char* to lwrCase
char *lwr_case = strdup(command[0].c_str());
lwrCase(lwr_case); //! converts the command to lowercase
strtokIndx = strtok(NULL, ","); // get the second part
strcpy(device_config_OTAPassword, strtokIndx);
strtokIndx = strtok(NULL, ",");
device_config_OTAPort = atoi(strtokIndx);
//* Camera Config *//
strtokIndx = strtok(NULL, ",");
camera_config_vflip = atoi(strtokIndx);
strtokIndx = strtok(NULL, ",");
camera_config_framesize = atoi(strtokIndx);
strtokIndx = strtok(NULL, ",");
camera_config_href = atoi(strtokIndx);
strtokIndx = strtok(NULL, ",");
camera_config_quality = atoi(strtokIndx);
//* Wifi Config *//
strtokIndx = strtok(tempBuffer, ",");
strcpy(wifi_config_name, strtokIndx);
strtokIndx = strtok(NULL, ",");
strcpy(wifi_config_ssid, strtokIndx);
strtokIndx = strtok(NULL, ",");
strcpy(wifi_config_password, strtokIndx);
strtokIndx = strtok(NULL, ",");
wifi_config_channel = atoi(strtokIndx);
switch (SerialManager::command_map[lwr_case])
{
case SerialManager::NO_INPUT:
break;
case SerialManager::DEVICE_CONFIG:
break;
case SerialManager::CAMERA_CONFIG:
break;
case SerialManager::WIFI_CONFIG:
break;
}
}
void SerialManager::handleSerial()
{
listenToSerial(30000L); // test for serial input every 30 seconds
if (newData) // input received
if (Serial.available() > 0)
{
strcpy(tempBuffer, serialBuffer); // this temporary copy is necessary to protect the original data because strtok() used in parseData() replaces the commas with \0
parseData(); // split the data into tokens and store them in the data structure
projectConfig->setDeviceConfig(device_config_name, device_config_OTAPassword, &device_config_OTAPort, true); // set the values in the project config
projectConfig->setCameraConfig(&camera_config_vflip, &camera_config_framesize, &camera_config_href, &camera_config_quality, true); // set the values in the project config
projectConfig->setWifiConfig(wifi_config_name, wifi_config_ssid, wifi_config_password, &wifi_config_channel, true); // set the values in the project config
projectConfig->save(); // save the config to the EEPROM
newData = false; // reset new data
delay(10);
std::string raw = Serial.readStringUntil('#').c_str();
// String s = "{\"a\":\"b\"}";
while (Serial.available() > 0)
{
Serial.read();
}
log_d("Received Serial Data: %s", raw.c_str());
DeserializationError error = deserializeJson(jsonDoc, raw);
if (error)
{
log_e("deserializeJson() failed: %s", error.c_str());
return;
}
const char *device_config_name = jsonDoc["device_config_name"];
const char *device_config_OTAPassword = jsonDoc["device_config_OTAPassword"];
const char *device_config_OTAPort = jsonDoc["device_config_OTAPort"];
const char *camera_config_vflip = jsonDoc["camera_config_vflip"];
const char *camera_config_href = jsonDoc["camera_config_href"];
const char *camera_config_framesize = jsonDoc["camera_config_framesize"];
const char *camera_config_quality = jsonDoc["camera_config_quality"];
const char *wifi_config_name = jsonDoc["wifi_config_name"];
const char *wifi_config_ssid = jsonDoc["wifi_config_ssid"];
const char *wifi_config_password = jsonDoc["wifi_config_password"];
const char *wifi_config_channel = jsonDoc["wifi_config_channel"];
}
}

View File

@ -2,8 +2,16 @@
#ifndef SERIAL_MANAGER_HPP
#define SERIAL_MANAGER_HPP
#include <Arduino.h>
#include <unordered_map>
#include <string>
#include <memory>
#include <serialStr.h>
#include <strTools.h>
#include <ArduinoJSON.h>
#include "data/config/project_config.hpp"
#include "data/utilities/makeunique.hpp"
#include "data/utilities/helpers.hpp"
class SerialManager
{
@ -11,36 +19,25 @@ public:
SerialManager(ProjectConfig *projectConfig);
virtual ~SerialManager();
void begin();
void handleSerial();
bool serialManagerActive;
friend void readStr(const char *inStr);
/* Device Config Variables */
char device_config_name[32];
char device_config_OTAPassword[100];
int device_config_OTAPort;
/* Camera Config Variables */
uint8_t camera_config_vflip;
uint8_t camera_config_framesize;
uint8_t camera_config_href;
uint8_t camera_config_quality;
/* Wifi Config Variables */
char wifi_config_name[32];
char wifi_config_ssid[100];
char wifi_config_password[100];
uint8_t wifi_config_channel;
private:
void listenToSerial(unsigned long timeout);
void parseData();
char serialBuffer[1000]; //! Need to find the appropriate size for this - count the maximum possible size of a message
char tempBuffer[sizeof(serialBuffer) / sizeof(serialBuffer[0])];
bool newData;
protected:
ProjectConfig *projectConfig;
std::unique_ptr<serialStr> serReader;
enum Serial_Commands
{
NO_INPUT,
DEVICE_CONFIG,
CAMERA_CONFIG,
WIFI_CONFIG
};
static std::unordered_map<std::string, Serial_Commands> command_map;
StaticJsonDocument<1024> jsonDoc;
};
#endif // SERIAL_MANAGER_HPP

View File

@ -2,6 +2,7 @@
#ifndef WIFIHANDLER_HPP
#define WIFIHANDLER_HPP
#include <memory>
#include <string>
#include <WiFi.h>
#include "data/StateManager/StateManager.hpp"
#include "data/config/project_config.hpp"
@ -9,14 +10,25 @@
class WiFiHandler
{
public:
WiFiHandler(ProjectConfig *configManager, StateManager<WiFiState_e> *stateManager);
WiFiHandler(ProjectConfig *configManager, StateManager<WiFiState_e> *stateManager,
std::string ssid,
std::string password,
uint8_t channel);
virtual ~WiFiHandler();
void setupWifi();
ProjectConfig *configManager;
StateManager<WiFiState_e> *stateManager;
bool _enable_adhoc;
private:
void setUpADHOC();
void adhoc(const char *ssid, const char *password, uint8_t channel);
void iniSTA();
std::string ssid;
std::string password;
uint8_t channel;
};
#endif // WIFIHANDLER_HPP

View File

@ -1,22 +1,41 @@
#include "WifiHandler.hpp"
#include <vector>
WiFiHandler::WiFiHandler(ProjectConfig *configManager, StateManager<WiFiState_e> *stateManager) : configManager(configManager),
stateManager(stateManager) {}
WiFiHandler::WiFiHandler(ProjectConfig *configManager,
StateManager<WiFiState_e> *stateManager,
std::string ssid,
std::string password,
uint8_t channel) : configManager(configManager),
stateManager(stateManager),
ssid(ssid),
password(password),
channel(channel),
_enable_adhoc(false) {}
WiFiHandler::~WiFiHandler() {}
void WiFiHandler::setupWifi()
{
if (ENABLE_ADHOC || stateManager->getCurrentState() == WiFiState_e::WiFiState_ADHOC)
if (this->_enable_adhoc || stateManager->getCurrentState() == WiFiState_e::WiFiState_ADHOC)
{
this->setUpADHOC();
return;
}
log_i("Initializing connection to wifi");
stateManager->setState(WiFiState_e::WiFiState_Connecting);
std::vector<ProjectConfig::WiFiConfig_t> *networks = configManager->getWifiConfigs();
// check size of networks
if (networks->size() == 0)
{
log_e("No networks found in config");
this->iniSTA();
stateManager->setState(WiFiState_e::WiFiState_Error);
return;
}
int connection_timeout = 30000; // 30 seconds
int count = 0;
@ -26,19 +45,9 @@ void WiFiHandler::setupWifi()
for (auto networkIterator = networks->begin(); networkIterator != networks->end(); ++networkIterator)
{
log_i("Trying to connect to the %s network", networkIterator->ssid);
WiFi.begin(networkIterator->ssid.c_str(), networkIterator->password.c_str());
count++;
if (!WiFi.isConnected())
log_i("\n\rCould not connect to %s, trying another network\n\r", networkIterator->ssid);
else
{
log_i("\n\rSuccessfully connected to %s\n\r", networkIterator->ssid);
stateManager->setState(WiFiState_e::WiFiState_Connected);
return;
}
while (WiFi.status() != WL_CONNECTED)
{
stateManager->setState(ProgramStates::DeviceStates::WiFiState_e::WiFiState_Connecting);
@ -50,12 +59,20 @@ void WiFiHandler::setupWifi()
log_i("[INFO]: WiFi connection timed out.\n");
// we've tried all saved networks, none worked, let's error out
log_e("Could not connect to any of the saved networks, check your Wifi credentials");
stateManager->setState(WiFiState_e::WiFiState_Error);
stateManager->setState(WiFiState_e::WiFiState_Disconnected);
log_i("[INFO]: Attempting to connect to hardcoded network");
this->iniSTA();
log_i("[INFO]: Attempting to connect to hardcoded network from ini file");
return;
}
}
if (!WiFi.isConnected())
log_i("\n\rCould not connect to %s, trying another network\n\r", networkIterator->ssid);
else
{
log_i("\n\rSuccessfully connected to %s\n\r", networkIterator->ssid);
stateManager->setState(WiFiState_e::WiFiState_Connected);
return;
}
}
}
@ -72,8 +89,8 @@ void WiFiHandler::adhoc(const char *ssid, const char *password, uint8_t channel)
// You can remove the password parameter if you want the AP to be open.
WiFi.softAP(ssid, password, channel); // AP mode with password
WiFi.setTxPower(WIFI_POWER_11dBm);
stateManager->setState(WiFiState_e::WiFiState_ADHOC);
}
@ -96,9 +113,9 @@ void WiFiHandler::setUpADHOC()
}
else
{
strcpy(ssid, WIFI_AP_SSID);
strcpy(password, WIFI_AP_PASSWORD);
channel = ADHOC_CHANNEL;
strcpy(ssid, "OpenIris");
strcpy(password, "12345678");
channel = 1;
}
this->adhoc(ssid, password, channel);
@ -116,18 +133,17 @@ void WiFiHandler::iniSTA()
unsigned long currentMillis = millis();
unsigned long _previousMillis = currentMillis;
log_i("Trying to connect to the %s network", WIFI_SSID);
log_i("Trying to connect to the %s network", this->ssid.c_str());
WiFi.begin(WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL);
if (!WiFi.isConnected())
log_i("\n\rCould not connect to %s, please try another network\n\r", WIFI_SSID);
else
// check size of networks
if (this->ssid.size() == 0)
{
log_i("\n\rSuccessfully connected to %s\n\r", WIFI_SSID);
stateManager->setState(WiFiState_e::WiFiState_Connected);
log_e("No networks passed into the constructor");
this->setUpADHOC();
stateManager->setState(WiFiState_e::WiFiState_Error);
return;
}
WiFi.begin(this->ssid.c_str(), this->password.c_str(), this->channel);
while (WiFi.status() != WL_CONNECTED)
{
@ -148,4 +164,13 @@ void WiFiHandler::iniSTA()
return;
}
}
if (!WiFi.isConnected())
log_i("\n\rCould not connect to %s, please try another network\n\r", this->ssid.c_str());
else
{
log_i("\n\rSuccessfully connected to %s\n\r", this->ssid.c_str());
stateManager->setState(WiFiState_e::WiFiState_Connected);
return;
}
}

View File

@ -0,0 +1,297 @@
#include "baseAPI.hpp"
BaseAPI::BaseAPI(int CONTROL_PORT,
WiFiHandler *network,
CameraHandler *camera,
StateManager<WiFiState_e> *stateManager,
std::string api_url) : API_Utilities(CONTROL_PORT,
network,
camera,
stateManager,
api_url) {}
BaseAPI::~BaseAPI() {}
void BaseAPI::begin()
{
this->setupServer();
//! i have changed this to use lambdas instead of std::bind to avoid the overhead. Lambdas are always more preferable.
server->on("/", 0b00000001, [&](AsyncWebServerRequest *request)
{ request->send(200); });
// preflight cors check
server->on("/", 0b01000000, [&](AsyncWebServerRequest *request)
{
AsyncWebServerResponse* response = request->beginResponse(204);
response->addHeader("Access-Control-Allow-Methods", "PUT,POST,GET,OPTIONS");
response->addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
response->addHeader("Access-Control-Allow-Credentials", "true");
request->send(response); });
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
// std::bind(&BaseAPI::API_Utilities::notFound, &api_utilities, std::placeholders::_1);
server->onNotFound([&](AsyncWebServerRequest *request)
{ notFound(request); });
}
void BaseAPI::setupServer()
{
localWifiConfig = {
.ssid = "",
.pass = "",
.channel = 0,
.adhoc = false,
};
localAPWifiConfig = {
.ssid = "",
.pass = "",
.channel = 0,
.adhoc = false,
};
}
//*********************************************************************************************
//! Command Functions
//*********************************************************************************************
void BaseAPI::setWiFi(AsyncWebServerRequest *request)
{
switch (_networkMethodsMap_enum[request->method()])
{
case POST:
{
int params = request->params();
for (int i = 0; i < params; i++)
{
AsyncWebParameter *param = request->getParam(i);
if (network->stateManager->getCurrentState() == WiFiState_e::WiFiState_ADHOC)
{
localAPWifiConfig.ssid = param->value().c_str();
localAPWifiConfig.pass = param->value().c_str();
localAPWifiConfig.channel = atoi(param->value().c_str());
localAPWifiConfig.adhoc = atoi(param->value().c_str());
}
else
{
localWifiConfig.ssid = param->value().c_str();
localWifiConfig.pass = param->value().c_str();
localWifiConfig.channel = atoi(param->value().c_str());
localWifiConfig.adhoc = atoi(param->value().c_str());
}
}
ssid_write = true;
pass_write = true;
channel_write = true;
request->send(200, MIMETYPE_JSON, "{\"msg\":\"Done. Wifi Creds have been set.\"}");
break;
}
default:
{
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Request\"}");
request->redirect("/");
break;
}
}
}
/**
* * Trigger in main loop to save config to flash
* ? Should we force the users to update all config params before triggering a config write?
*/
void BaseAPI::triggerWifiConfigWrite()
{
if (ssid_write && pass_write && channel_write)
{
ssid_write = false;
pass_write = false;
channel_write = false;
if (network->stateManager->getCurrentState() == WiFiState_e::WiFiState_ADHOC)
network->configManager->setAPWifiConfig(localAPWifiConfig.ssid.c_str(), localAPWifiConfig.pass.c_str(), &localAPWifiConfig.channel, localAPWifiConfig.adhoc, true);
else
network->configManager->setWifiConfig(localWifiConfig.ssid.c_str(), localWifiConfig.ssid.c_str(), localWifiConfig.pass.c_str(), &localWifiConfig.channel, localAPWifiConfig.adhoc, true);
network->configManager->save();
}
}
void BaseAPI::handleJson(AsyncWebServerRequest *request)
{
std::string type = request->pathArg(0).c_str();
switch (_networkMethodsMap_enum[request->method()])
{
case POST:
{
switch (json_TypesMap.at(type))
{
case DATA:
{
break;
}
case SETTINGS:
{
break;
}
case CONFIG:
{
break;
}
default:
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Request\"}");
break;
}
break;
}
case GET:
{
switch (json_TypesMap.at(type))
{
case DATA:
{
network->configManager->getDeviceConfig()->data_json = true;
Network_Utilities::my_delay(1L);
String temp = network->configManager->getDeviceConfig()->data_json_string;
request->send(200, MIMETYPE_JSON, temp);
temp = "";
break;
}
case SETTINGS:
{
network->configManager->getDeviceConfig()->config_json = true;
Network_Utilities::my_delay(1L);
String temp = network->configManager->getDeviceConfig()->config_json_string;
request->send(200, MIMETYPE_JSON, temp);
temp = "";
break;
}
case CONFIG:
{
network->configManager->getDeviceConfig()->settings_json = true;
Network_Utilities::my_delay(1L);
String temp = network->configManager->getDeviceConfig()->settings_json_string;
request->send(200, MIMETYPE_JSON, temp);
temp = "";
break;
}
default:
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Request\"}");
break;
}
break;
}
default:
{
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Request\"}");
break;
}
}
}
void BaseAPI::rebootDevice(AsyncWebServerRequest *request)
{
switch (_networkMethodsMap_enum[request->method()])
{
case GET:
{
delay(20000);
request->send(200, MIMETYPE_JSON, "{\"msg\":\"Rebooting Device\"}");
ESP.restart();
}
default:
{
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Request\"}");
break;
}
}
}
void BaseAPI::factoryReset(AsyncWebServerRequest *request)
{
switch (_networkMethodsMap_enum[request->method()])
{
case GET:
{
log_d("Factory Reset");
network->configManager->reset();
request->send(200, MIMETYPE_JSON, "{\"msg\":\"Factory Reset\"}");
}
default:
{
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Request\"}");
break;
}
}
}
/**
* @brief Remove a command handler from the API
*
* @param request
* @return \c void
*/
void BaseAPI::deleteRoute(AsyncWebServerRequest *request)
{
log_i("Request: %s", request->url().c_str());
int params = request->params();
auto it_map = route_map.find(request->pathArg(0).c_str());
log_i("Request: %s", request->pathArg(0).c_str());
if (it_map != route_map.end())
{
auto it = it_map->second.find(request->pathArg(1).c_str());
if (it != it_map->second.end())
{
switch (_networkMethodsMap_enum[request->method()])
{
case DELETE:
{
route_map.erase(it_map->first);
request->send(200, MIMETYPE_JSON, "{\"msg\":\"OK - Command handler removed\"}");
break;
}
default:
{
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Request\"}");
break;
}
}
}
else
{
request->send(404);
}
}
else
{
request->send(404);
}
}
//*********************************************************************************************
//! Camera Command Functions
//*********************************************************************************************
void BaseAPI::setCamera(AsyncWebServerRequest *request)
{
switch (_networkMethodsMap_enum[request->method()])
{
case GET:
{
int params = request->params();
for (int i = 0; i < params; i++)
{
AsyncWebParameter *param = request->getParam(i);
camera->setCameraResolution((framesize_t)atoi(param->value().c_str()));
camera->setVFlip(atoi(param->value().c_str()));
camera->setHFlip(atoi(param->value().c_str()));
}
request->send(200, MIMETYPE_JSON, "{\"msg\":\"Done. Camera Settings have been set.\"}");
break;
}
default:
{
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Request\"}");
request->redirect("/");
break;
}
}
}

View File

@ -0,0 +1,84 @@
#ifndef BASEAPI_HPP
#define BASEAPI_HPP
#include "network/wifihandler/wifiHandler.hpp"
#include "network/api/utilities/apiUtilities.hpp"
class BaseAPI : public API_Utilities
{
protected:
struct LocalWifiConfig
{
std::string ssid;
std::string pass;
uint8_t channel;
bool adhoc;
};
LocalWifiConfig localWifiConfig;
struct LocalAPWifiConfig
{
std::string ssid;
std::string pass;
uint8_t channel;
};
LocalWifiConfig localAPWifiConfig;
enum JSON_TYPES
{
CONFIG,
SETTINGS,
DATA,
STATUS,
COMMANDS,
WIFI,
WIFIAP,
};
std::unordered_map<std::string, JSON_TYPES> json_TypesMap = {
{"config", CONFIG},
{"settings", SETTINGS},
{"data", DATA},
{"status", STATUS},
{"commands", COMMANDS},
{"wifi", WIFI},
{"wifiap", WIFIAP},
};
protected:
/* Commands */
void setWiFi(AsyncWebServerRequest *request);
void handleJson(AsyncWebServerRequest *request);
void factoryReset(AsyncWebServerRequest *request);
void rebootDevice(AsyncWebServerRequest *request);
void deleteRoute(AsyncWebServerRequest *request);
/* Camera Handler */
void setCamera(AsyncWebServerRequest *request);
using call_back_function_t = void (BaseAPI::*)(AsyncWebServerRequest *);
typedef call_back_function_t (*call_back_function_ptr)(AsyncWebServerRequest *);
/* Route Command types */
using route_method = void (BaseAPI::*)(AsyncWebServerRequest *);
// typedef void (*callback)(AsyncWebServerRequest *);
typedef std::unordered_map<std::string, route_method> route_t;
typedef std::unordered_map<std::string, route_t> route_map_t;
route_t routes;
route_map_t route_map;
public:
BaseAPI(int CONTROL_PORT,
WiFiHandler *network,
CameraHandler *camera,
StateManager<WiFiState_e> *stateManager,
std::string api_url);
virtual ~BaseAPI();
virtual void begin();
virtual void setupServer();
void triggerWifiConfigWrite();
};
#endif // BASEAPI_HPP

View File

@ -0,0 +1,116 @@
#include "apiUtilities.hpp"
//! These have to be called before the constructor of the class because they are static
//! C++ 11 does not have inline variables, sadly. So we have to do this.
const char *API_Utilities::MIMETYPE_HTML{"text/html"};
// const char *BaseAPI::MIMETYPE_CSS{"text/css"};
// const char *BaseAPI::MIMETYPE_JS{"application/javascript"};
// const char *BaseAPI::MIMETYPE_PNG{"image/png"};
// const char *BaseAPI::MIMETYPE_JPG{"image/jpeg"};
// const char *BaseAPI::MIMETYPE_ICO{"image/x-icon"};
const char *API_Utilities::MIMETYPE_JSON{"application/json"};
bool API_Utilities::ssid_write = false;
bool API_Utilities::pass_write = false;
bool API_Utilities::channel_write = false;
//*********************************************************************************************
//! API Utilities
//*********************************************************************************************
API_Utilities::API_Utilities(int CONTROL_PORT,
WiFiHandler *network,
CameraHandler *camera,
StateManager<WiFiState_e> *stateManager,
std::string api_url) : server(new AsyncWebServer(CONTROL_PORT)),
stateManager(stateManager),
network(network),
camera(camera),
api_url(api_url) {}
API_Utilities::~API_Utilities() {}
std::string API_Utilities::shaEncoder(std::string data)
{
const char *data_c = data.c_str();
int size = 64;
uint8_t hash[size];
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA512;
const size_t len = strlen(data_c);
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
mbedtls_md_starts(&ctx);
mbedtls_md_update(&ctx, (const unsigned char *)data_c, len);
mbedtls_md_finish(&ctx, hash);
mbedtls_md_free(&ctx);
std::string hash_string = "";
for (uint16_t i = 0; i < size; i++)
{
std::string hex = String(hash[i], HEX).c_str();
if (hex.length() < 2)
{
hex = "0" + hex;
}
hash_string += hex;
}
return hash_string;
}
void API_Utilities::notFound(AsyncWebServerRequest *request) const
{
if (_networkMethodsMap.find(request->method()) != _networkMethodsMap.end())
{
log_i("%s: http://%s%s/\n", _networkMethodsMap.at(request->method()).c_str(), request->host().c_str(), request->url().c_str());
char buffer[100];
snprintf(buffer, sizeof(buffer), "Request %s Not found: %s", _networkMethodsMap.at(request->method()).c_str(), request->url().c_str());
request->send(404, "text/plain", buffer);
}
else
{
request->send(404, "text/plain", "Request Not found using unknown method");
}
}
// Read File from SPIFFS
/* String API_Utilities::readFile(fs::FS &fs, std::string path)
{
log_i("Reading file: %s\r\n", path.c_str());
File file = fs.open(path.c_str());
if (!file || file.isDirectory())
{
log_e("[INFO]: Failed to open file for reading");
return String();
}
String fileContent;
while (file.available())
{
fileContent = file.readStringUntil('\n');
break;
}
return fileContent;
}
// Write file to SPIFFS
void API_Utilities::writeFile(fs::FS &fs, std::string path, std::string message)
{
log_i("[Writing File]: Writing file: %s\r\n", path);
Network_Utilities::my_delay(0.1L);
File file = fs.open(path.c_str(), FILE_WRITE);
if (!file)
{
log_i("[Writing File]: failed to open file for writing");
return;
}
if (file.print(message.c_str()))
{
log_i("[Writing File]: file written");
}
else
{
log_i("[Writing File]: file write failed");
}
} */

View File

@ -0,0 +1,98 @@
#ifndef APIUTILITIES_HPP
#define APIUTILITIES_HPP
#include <unordered_map>
#include <string>
#define WEBSERVER_H
/* #define XHTTP_GET 0b00000001;
#define XHTTP_POST 0b00000010;
#define XHTTP_DELETE 0b00000100;
#define XHTTP_PUT 0b00001000;
#define XHTTP_PATCH 0b00010000;
#define XHTTP_HEAD 0b00100000;
#define XHTTP_OPTIONS 0b01000000;
#define XHTTP_ANY 0b01111111; */
#define HTTP_ANY 0b01111111
#define HTTP_GET 0b00000001
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include "mbedtls/md.h"
#include "data/utilities/network_utilities.hpp"
#include "data/StateManager/StateManager.hpp"
#include "io/camera/cameraHandler.hpp"
class API_Utilities
{
public:
API_Utilities(int CONTROL_PORT,
WiFiHandler *network,
CameraHandler *camera,
StateManager<WiFiState_e> *stateManager,
std::string api_url);
virtual ~API_Utilities();
protected:
void notFound(AsyncWebServerRequest *request) const;
/* String readFile(fs::FS &fs, std::string path);
void writeFile(fs::FS &fs, std::string path, std::string message); */
std::string shaEncoder(std::string data);
std::unordered_map<int, std::string> _networkMethodsMap = {
{0, "NULL"},
{0b00000001, "GET"},
{0b00000010, "POST"},
{0b00001000, "PUT"},
{0b00000100, "DELETE"},
{0b00010000, "PATCH"},
{0b01000000, "OPTIONS"},
};
enum RequestMethods
{
NULL_METHOD,
GET,
POST,
PUT,
DELETE,
PATCH,
OPTIONS,
};
std::unordered_map<int, RequestMethods> _networkMethodsMap_enum = {
{0, NULL_METHOD},
{0b00000001, GET},
{0b00000010, POST},
{0b00001000, PUT},
{0b00000100, DELETE},
{0b00010000, PATCH},
{0b01000000, OPTIONS},
};
protected:
AsyncWebServer *server;
WiFiHandler *network;
CameraHandler *camera;
StateManager<WiFiState_e> *stateManager;
typedef std::unordered_map<std::string, WebRequestMethodComposite> networkMethodsMap_t;
protected:
std::string api_url;
static bool ssid_write;
static bool pass_write;
static bool channel_write;
static const char *MIMETYPE_HTML;
/* static const char *MIMETYPE_CSS; */
/* static const char *MIMETYPE_JS; */
/* static const char *MIMETYPE_PNG; */
/* static const char *MIMETYPE_JPG; */
/* static const char *MIMETYPE_ICO; */
static const char *MIMETYPE_JSON;
};
#endif // APIUTILITIES_HPP

View File

@ -0,0 +1,115 @@
#include "webserverHandler.hpp"
//*********************************************************************************************
//! API Server
//*********************************************************************************************
APIServer::APIServer(int CONTROL_PORT,
WiFiHandler *network,
CameraHandler *camera,
StateManager<WiFiState_e> *stateManager,
std::string api_url) : BaseAPI(CONTROL_PORT,
network,
camera,
stateManager,
api_url) {}
APIServer::~APIServer() {}
void APIServer::begin()
{
log_d("Initializing REST API");
this->setupServer();
BaseAPI::begin();
char buffer[1000];
snprintf(buffer, sizeof(buffer), "^\\%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->begin();
}
void APIServer::setupServer()
{
// Set case NULL_METHOD routes
routes.emplace("wifi", &APIServer::setWiFi);
routes.emplace("reset_config", &APIServer::factoryReset);
routes.emplace("reboot_device", &APIServer::rebootDevice);
routes.emplace("set_json", &APIServer::handleJson);
routes.emplace("set_camera", &APIServer::setCamera);
routes.emplace("delete_route", &APIServer::deleteRoute);
routeHandler("builtin", routes); // add new map to the route map
}
void APIServer::findParam(AsyncWebServerRequest *request, const char *param, String &value)
{
if (request->hasParam(param))
{
value = request->getParam(param)->value();
}
}
/**
* @brief Add a command handler to the API
*
* @param index
* @param funct
* @return \c vector<string> a list of the indexes of the command handlers
*/
std::vector<std::string> APIServer::routeHandler(std::string index, route_t route)
{
route_map.emplace(index, route);
std::vector<std::string> indexes;
indexes.reserve(route.size());
for (const auto &key : route)
{
indexes.push_back(key.first);
}
return indexes;
}
void APIServer::handleRequest(AsyncWebServerRequest *request)
{
// Get the route
log_i("Request: %s", request->url().c_str());
int params = request->params();
auto it_map = route_map.find(request->pathArg(0).c_str());
log_i("Request: %s", request->pathArg(0).c_str());
auto it_method = it_map->second.find(request->pathArg(1).c_str());
log_i("Request: %s", request->pathArg(1).c_str());
for (int i = 0; i < params; i++)
{
AsyncWebParameter *param = request->getParam(i);
{
{
if (it_map != route_map.end())
{
if (it_method != it_map->second.end())
{
(*this.*(it_method->second))(request);
}
else
{
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Command\"}");
request->redirect("/");
return;
}
}
else
{
request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Map Index\"}");
request->redirect("/");
return;
}
}
log_i("%s[%s]: %s\n", _networkMethodsMap[request->method()].c_str(), param->name().c_str(), param->value().c_str());
}
}
request->send(200, MIMETYPE_JSON, "{\"msg\":\"Command executed\"}");
}

View File

@ -0,0 +1,26 @@
#pragma once
#ifndef XWEBSERVERHANDLER_HPP
#define XWEBSERVERHANDLER_HPP
#include "network/api/baseAPI/baseAPI.hpp"
class APIServer : public BaseAPI
{
public:
APIServer(int CONTROL_PORT,
WiFiHandler *network,
CameraHandler* camera,
StateManager<WiFiState_e> *stateManager,
std::string api_url);
virtual ~APIServer();
void begin();
void setupServer();
void findParam(AsyncWebServerRequest *request, const char *param, String &value);
void updateCommandHandlers();
std::vector<std::string> routeHandler(std::string index, route_t route);
void handleRequest(AsyncWebServerRequest *request);
};
#endif // WEBSERVERHANDLER_HPP

View File

@ -8,7 +8,9 @@ void MDNSHandler::startMDNS()
{
stateManager->setState(MDNSState_e::MDNSState_Starting);
MDNS.addService("openIrisTracker", "tcp", 80);
MDNS.addServiceTxt("openIrisTracker", "tcp", "stream_port", String(80));
char port[20];
//!Add service needs leading _ on ESP32 implementation for some reason (according to the docs)
MDNS.addServiceTxt("_openIrisTracker", "_tcp", "_stream_port", (const char*)Helpers::itoa(80, port, 10)); //! convert int to const char* using a very efficient implemenation of itoa
log_i("MDNS initialized!");
stateManager->setState(MDNSState_e::MDNSState_Started);
}

View File

@ -2,6 +2,7 @@
#include <ESPmDNS.h>
#include "data/StateManager/StateManager.hpp"
#include "data/utilities/Observer.hpp"
#include "data/utilities/helpers.hpp"
#include "data/config/project_config.hpp"
class MDNSHandler : public IObserver

View File

@ -1,4 +1,6 @@
#pragma once
#ifndef STREAM_SERVER_HPP
#define STREAM_SERVER_HPP
#define PART_BOUNDARY "123456789000000000000987654321"
#include <Arduino.h>
#include "esp_camera.h"
@ -19,3 +21,5 @@ public:
StreamServer(int STREAM_PORT) : STREAM_SERVER_PORT(STREAM_PORT) {}
int startStreamServer();
};
#endif // STREAM_SERVER_HPP

View File

@ -14,14 +14,14 @@ default_envs = esp32Cam ; do not change this value
; The below options are available for all environments
; The ssid and password are requried for the trackers to connect to your network!!!
[wifi]
ssid="" ; your wifi network name goes here
password="" ; your wifi network password goes here
ssid="LoveHouse2G" ; your wifi network name goes here
password="vxwby2Gwtswp" ; your wifi network password goes here
channel=1 ; wifi channel
ap_ssid="EyeTrackVR" ; your AP wifi network name goes here
ap_password="test" ; Place your AP Wifi password here
OTAPassword="" ; if empty, no password will be required
OTAServerPort=3232
enableADHOC=0 ; 0 = disable, 1 = enable
enableADHOC=1 ; 0 = disable, 1 = enable
adhocChannel=1 ; channel to use for adhoc network
; DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING
@ -104,6 +104,8 @@ build_flags =
-DBOARD_HAS_PSRAM
-DASYNCWEBSERVER_REGEX ; add regex support to AsyncWebServer
-mfix-esp32-psram-cache-issue
;-I include
@ -118,9 +120,11 @@ upload_speed = 921600
release_version = 0.0.1 ; increase this value every release build
lib_deps =
esp32-camera
leftcoast/LC_baseTools@^1.5
https://github.com/ZanzyTHEbar/EasyPreferencesLibrary.git
https://github.com/me-no-dev/ESPAsyncWebServer.git
https://github.com/me-no-dev/AsyncTCP.git
https://github.com/bblanchon/ArduinoJson.git
build_type = debug
@ -132,8 +136,7 @@ monitor_speed = ${common.monitor_speed}
monitor_rts = ${common.monitor_rts}
monitor_dtr = ${common.monitor_dtr}
monitor_filters = ${common.monitor_filters}
build_flags =
${common.build_flags}
build_flags = ${common.build_flags}
; CAMERA PINOUT DEFINITIONS
-DPWDN_GPIO_NUM=${pinoutsESPCAM.PWDN_GPIO_NUM} ; Set the PWDN pin

View File

@ -5,41 +5,49 @@
#include <io/camera/cameraHandler.hpp>
#include <io/LEDManager/LEDManager.hpp>
#include <network/stream/streamServer.hpp>
#include <network/webserver/webserverHandler.hpp>
#include <network/api/webserverHandler.hpp>
#include <data/config/project_config.hpp>
#include <io/SerialManager/serialmanager.hpp> // Basic Serial Manager
//#include <io/SerialManager/SerialManager2/serialmanager.hpp> // Advanced Serial MAnager //! Finish this to update the serial manager
//#include <io/SerialManager/serialmanager.hpp> // Basic Serial Manager
//#include <io/SerialManager/SerialManager2/serialmanager.hpp // Advanced Serial MAnager //! Finish this to update the serial manager
#include <network/OTA/OTA.hpp>
uint8_t STREAM_SERVER_PORT = 80;
uint8_t CONTROL_SERVER_PORT = 81;
int STREAM_SERVER_PORT = 80;
int CONTROL_SERVER_PORT = 81;
// Create smart pointers to the various classes that will be used in the program to make sure that they are deleted when the program ends
// This is done to make sure that the memory is freed when the program ends and we are not left with dangling pointers to memory that is no longer in use
// Make unique is a templated function that takes a class and returns a unique pointer to that class -
// it is used to create a unique pointer to a class and ensure exception safety
std::unique_ptr<ProjectConfig> deviceConfig = std::make_unique<ProjectConfig>();
OTA ota(&*deviceConfig);
std::unique_ptr<SerialManager> serialManager = std::make_unique<SerialManager>(&*deviceConfig);
std::unique_ptr<WiFiHandler> wifiHandler = std::make_unique<WiFiHandler>(&*deviceConfig, &wifiStateManager);
std::unique_ptr<LEDManager> ledManager = std::make_unique<LEDManager>(33);
std::shared_ptr<CameraHandler> cameraHandler = std::make_shared<CameraHandler>(&*deviceConfig); //! Create a shared pointer to the camera handler
std::unique_ptr<APIServer> apiServer = std::make_unique<APIServer>(CONTROL_SERVER_PORT, &*cameraHandler, &*wifiHandler); //! Dereference the shared pointer to get the address of the camera handler
std::unique_ptr<MDNSHandler> mdnsHandler = std::make_unique<MDNSHandler>(&mdnsStateManager, &*deviceConfig);
std::unique_ptr<StreamServer> streamServer = std::make_unique<StreamServer>(STREAM_SERVER_PORT);
ProjectConfig deviceConfig;
OTA ota(&deviceConfig);
LEDManager ledManager(33);
CameraHandler cameraHandler(&deviceConfig);
//SerialManager serialManager(&deviceConfig);
WiFiHandler wifiHandler(&deviceConfig, &wifiStateManager, WIFI_SSID, WIFI_PASSWORD, 1);
//APIServer apiServer(CONTROL_SERVER_PORT, &wifiHandler, &cameraHandler, &wifiStateManager, "/control");
MDNSHandler mdnsHandler(&mdnsStateManager, &deviceConfig);
StreamServer streamServer(STREAM_SERVER_PORT);
void setup()
{
Serial.begin(115200);
Serial.setDebugOutput(true);
ledManager->begin();
deviceConfig->initConfig();
deviceConfig->load();
cameraHandler->setupCamera();
ledManager.begin();
deviceConfig.initConfig();
deviceConfig.load();
cameraHandler.setupCamera();
wifiHandler->setupWifi();
mdnsHandler->startMDNS();
/* auto localConfig = deviceConfig.getAPWifiConfig();
if (localConfig->adhoc == true)
{
} */
wifiHandler._enable_adhoc = ENABLE_ADHOC;
wifiHandler.setupWifi();
mdnsHandler.startMDNS();
switch (wifiStateManager.getCurrentState())
{
@ -56,8 +64,8 @@ void setup()
}
case WiFiState_e::WiFiState_Connected:
{
apiServer->startAPIServer();
streamServer->startStreamServer();
//apiServer.begin();
streamServer.startStreamServer();
log_d("[SETUP]: Starting Stream Server");
break;
}
@ -76,7 +84,7 @@ void setup()
void loop()
{
ota.HandleOTAUpdate();
ledManager->displayStatus();
apiServer->triggerWifiConfigWrite();
// serialManager->handleSerial();
ledManager.displayStatus();
//apiServer.triggerWifiConfigWrite();
// serialManager.handleSerial();
}