🚀 Builds with new ESP32-Sveltekit template

This commit is contained in:
Rune Harlyk
2024-03-28 18:16:06 +01:00
committed by Rune Harlyk
parent 6b47100f3f
commit 3eb8190cda
59 changed files with 2343 additions and 2451 deletions
-128
View File
@@ -1,128 +0,0 @@
#include <AsyncJpegStream.h>
static const char* STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* STREAM_PART = "Content-Type: %s\r\nContent-Length: %u\r\n\r\n";
static const char * JPG_CONTENT_TYPE = "image/jpeg";
AsyncJpegStreamResponse::AsyncJpegStreamResponse(){
_callback = nullptr;
_code = 200;
_contentLength = 0;
_contentType = STREAM_CONTENT_TYPE;
_sendContentLength = false;
_chunked = true;
_index = 0;
_jpg_buf_len = 0;
_jpg_buf = NULL;
lastAsyncRequest = 0;
memset(&_frame, 0, sizeof(camera_frame_t));
}
AsyncJpegStreamResponse::~AsyncJpegStreamResponse(){
if(_frame.fb){
if(_frame.fb->format != PIXFORMAT_JPEG){
free(_jpg_buf);
}
esp_camera_fb_return(_frame.fb);
}
}
/*bool AsyncJpegStreamResponse::_sourceValid() {
return true;
}*/
size_t AsyncJpegStreamResponse::_fillBuffer(uint8_t *buf, size_t maxLen) {
size_t ret = _content(buf, maxLen, _index);
if(ret != RESPONSE_TRY_AGAIN){
_index += ret;
}
return ret;
}
size_t AsyncJpegStreamResponse::_content(uint8_t *buffer, size_t maxLen, size_t index){
if(!_frame.fb || _frame.index == _jpg_buf_len) {
if(index && _frame.fb){
uint64_t end = (uint64_t)micros();
int fp = (end - lastAsyncRequest) / 1000;
log_printf("Size: %uKB, Time: %ums (%.1ffps)\n", _jpg_buf_len/1024, fp);
lastAsyncRequest = end;
if(_frame.fb->format != PIXFORMAT_JPEG){
free(_jpg_buf);
}
esp_camera_fb_return(_frame.fb);
_frame.fb = NULL;
_jpg_buf_len = 0;
_jpg_buf = NULL;
}
if(maxLen < (strlen(STREAM_BOUNDARY) + strlen(STREAM_PART) + strlen(JPG_CONTENT_TYPE) + 8)){
//log_w("Not enough space for headers");
return RESPONSE_TRY_AGAIN;
}
//get frame
_frame.index = 0;
_frame.fb = esp_camera_fb_get();
if (_frame.fb == NULL) {
log_e("Camera frame failed");
return 0;
}
if(_frame.fb->format != PIXFORMAT_JPEG){
unsigned long st = millis();
bool jpeg_converted = frame2jpg(_frame.fb, 80, &_jpg_buf, &_jpg_buf_len);
if(!jpeg_converted){
log_e("JPEG compression failed");
esp_camera_fb_return(_frame.fb);
_frame.fb = NULL;
_jpg_buf_len = 0;
_jpg_buf = NULL;
return 0;
}
log_i("JPEG: %lums, %uB", millis() - st, _jpg_buf_len);
} else {
_jpg_buf_len = _frame.fb->len;
_jpg_buf = _frame.fb->buf;
}
//send boundary
size_t blen = 0;
if(index){
blen = strlen(STREAM_BOUNDARY);
memcpy(buffer, STREAM_BOUNDARY, blen);
buffer += blen;
}
//send header
size_t hlen = sprintf((char *)buffer, STREAM_PART, JPG_CONTENT_TYPE, _jpg_buf_len);
buffer += hlen;
//send frame
hlen = maxLen - hlen - blen;
if(hlen > _jpg_buf_len){
maxLen -= hlen - _jpg_buf_len;
hlen = _jpg_buf_len;
}
memcpy(buffer, _jpg_buf, hlen);
_frame.index += hlen;
return maxLen;
}
size_t available = _jpg_buf_len - _frame.index;
if(maxLen > available){
maxLen = available;
}
memcpy(buffer, _jpg_buf+_frame.index, maxLen);
_frame.index += maxLen;
return maxLen;
}
void streamJpg(AsyncWebServerRequest *request){
AsyncJpegStreamResponse *response = new AsyncJpegStreamResponse();
if(!response){
request->send(501);
return;
}
request->send(response);
}
-43
View File
@@ -1,43 +0,0 @@
#include <servo.h>
#include <camera_pins.h>
#include <esp_camera.h>
esp_err_t InitializeCamera(){
camera_config_t camera_config;
camera_config.ledc_channel = LEDC_CHANNEL_0;
camera_config.ledc_timer = LEDC_TIMER_0;
camera_config.pin_d0 = Y2_GPIO_NUM;
camera_config.pin_d1 = Y3_GPIO_NUM;
camera_config.pin_d2 = Y4_GPIO_NUM;
camera_config.pin_d3 = Y5_GPIO_NUM;
camera_config.pin_d4 = Y6_GPIO_NUM;
camera_config.pin_d5 = Y7_GPIO_NUM;
camera_config.pin_d6 = Y8_GPIO_NUM;
camera_config.pin_d7 = Y9_GPIO_NUM;
camera_config.pin_xclk = XCLK_GPIO_NUM;
camera_config.pin_pclk = PCLK_GPIO_NUM;
camera_config.pin_vsync = VSYNC_GPIO_NUM;
camera_config.pin_href = HREF_GPIO_NUM;
camera_config.pin_sscb_sda = SIOD_GPIO_NUM;
camera_config.pin_sscb_scl = SIOC_GPIO_NUM;
camera_config.pin_pwdn = PWDN_GPIO_NUM;
camera_config.pin_reset = RESET_GPIO_NUM;
camera_config.xclk_freq_hz = 20000000;
camera_config.pixel_format = PIXFORMAT_JPEG;
if(psramFound()){
camera_config.frame_size = FRAMESIZE_SVGA;
camera_config.jpeg_quality = 10;
camera_config.fb_count = 2;
} else {
camera_config.frame_size = FRAMESIZE_SVGA;
camera_config.jpeg_quality = 12;
camera_config.fb_count = 1;
}
log_i("Initializing camera");
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) log_e("Camera probe failed with error 0x%x", err);
return err;
}
Binary file not shown.
-55
View File
@@ -1,55 +0,0 @@
#include <HTTPClient.h>
#include <UrlEncode.h>
#include <globals.h>
#include <deviceconfig.h>
#include <secrets.h>
DRAM_ATTR std::unique_ptr<DeviceConfig> g_ptrDeviceConfig;
DRAM_ATTR size_t g_DeviceConfigJSONBufferSize = 0;
void DeviceConfig::SaveToJSON()
{
g_ptrJSONWriter->FlagWriter(writerIndex);
}
DeviceConfig::DeviceConfig()
{
// Add SettingSpec for additional settings to this list
settingSpecs.emplace_back(
NAME_OF(ntpServer),
"NTP server address",
"The hostname or IP address of the NTP server to be used for time synchronization.",
SettingSpec::SettingType::String
);
settingSpecs.emplace_back(
NAME_OF(useMetric),
"Use metric system",
"A boolean that indicates if unit should be shown in metric ('true'/1) or imperial ('false'/0) format.",
SettingSpec::SettingType::Boolean
);
log_i("about to write");
writerIndex = g_ptrJSONWriter->RegisterWriter(
[this]() { SaveToJSONFile(DEVICE_CONFIG_FILE, g_DeviceConfigJSONBufferSize, *this); }
);
std::unique_ptr<AllocatedJsonDocument> pJsonDoc(nullptr);
if (LoadJSONFile(DEVICE_CONFIG_FILE, g_DeviceConfigJSONBufferSize, pJsonDoc))
{
log_i("Loading DeviceConfig from JSON");
DeserializeFromJSON(pJsonDoc->as<JsonObjectConst>(), true);
}
else
{
log_w("DeviceConfig could not be loaded from JSON, using defaults");
// Set default for additional settings in this code
ntpServer = DEFAULT_NTP_SERVER;
SaveToJSON();
}
}
-177
View File
@@ -1,177 +0,0 @@
#include <globals.h>
#include <SPIFFS.h>
#include <jsonserializer.h>
#include <taskmanager.h>
DRAM_ATTR std::unique_ptr<JSONWriter> g_ptrJSONWriter = nullptr;
bool LoadJSONFile(const char *fileName, size_t& bufferSize, std::unique_ptr<AllocatedJsonDocument>& pJsonDoc)
{
bool jsonReadSuccessful = false;
File file = SPIFFS.open(fileName);
if (file)
{
if (file.size() > 0)
{
log_i("Attempting to read JSON file %s", fileName);
if (bufferSize == 0)
bufferSize = std::max((size_t)JSON_BUFFER_BASE_SIZE, file.size());
// Loop is here to deal with out of memory conditions
while(true)
{
pJsonDoc.reset(new AllocatedJsonDocument(bufferSize));
DeserializationError error = deserializeJson(*pJsonDoc, file);
if (error == DeserializationError::NoMemory)
{
pJsonDoc.reset(nullptr);
file.seek(0);
bufferSize += JSON_BUFFER_INCREMENT;
log_w("Out of memory reading JSON from file %s - increasing buffer to %zu bytes", fileName, bufferSize);
}
else if (error == DeserializationError::Ok)
{
jsonReadSuccessful = true;
break;
}
else
{
log_w("Error with code %d occurred while deserializing JSON from file %s", to_value(error.code()), fileName);
break;
}
}
}
file.close();
}
return jsonReadSuccessful;
}
void SerializeWithBufferSize(std::unique_ptr<AllocatedJsonDocument>& pJsonDoc, size_t& bufferSize, std::function<bool(JsonObject&)> serializationFunction)
{
// Loop is here to deal with out of memory conditions
while(true)
{
pJsonDoc.reset(new AllocatedJsonDocument(bufferSize));
JsonObject jsonObject = pJsonDoc->to<JsonObject>();
if (serializationFunction(jsonObject))
break;
pJsonDoc.reset(nullptr);
bufferSize += JSON_BUFFER_INCREMENT;
log_w("Out of memory serializing object - increasing buffer to %zu bytes", bufferSize);
}
}
bool SaveToJSONFile(const char *fileName, size_t& bufferSize, IJSONSerializable& object)
{
if (bufferSize == 0)
bufferSize = JSON_BUFFER_BASE_SIZE;
std::unique_ptr<AllocatedJsonDocument> pJsonDoc(nullptr);
SerializeWithBufferSize(pJsonDoc, bufferSize, [&object](JsonObject& jsonObject) { return object.SerializeToJSON(jsonObject); });
SPIFFS.remove(fileName);
File file = SPIFFS.open(fileName, FILE_WRITE);
if (!file)
{
log_e("Unable to open file %s to write JSON!", fileName);
return false;
}
size_t bytesWritten = serializeJson(*pJsonDoc, file);
log_i("Number of bytes written to JSON file %s: %d", fileName, bytesWritten);
file.flush();
file.close();
if (bytesWritten == 0)
{
log_e("Unable to write JSON to file %s!", fileName);
SPIFFS.remove(fileName);
return false;
}
/*
file = SPIFFS.open(fileName);
if (file)
{
while (file.available())
Serial.write(file.read());
file.close();
}
*/
return true;
}
bool RemoveJSONFile(const char *fileName)
{
return SPIFFS.remove(fileName);
}
size_t JSONWriter::RegisterWriter(std::function<void()> writer)
{
// Add the writer with its flag unset
writers.emplace_back(writer);
return writers.size() - 1;
}
void JSONWriter::FlagWriter(size_t index)
{
// Check if we received a valid writer index
if (index >= writers.size())
return;
writers[index].flag = true;
latestFlagMs = millis();
g_TaskManager.NotifyJSONWriterThread();
}
// JSONWriterTaskEntry
//
// Invoke functions that write serialized JSON objects to SPIFFS at request, with some delay
void IRAM_ATTR JSONWriterTaskEntry(void *)
{
for(;;)
{
TickType_t notifyWait = portMAX_DELAY;
for (;;)
{
// Wait until we're woken up by a writer being flagged, or until we've reached the hold point
ulTaskNotifyTake(pdTRUE, notifyWait);
if (!g_ptrJSONWriter)
continue;
unsigned long holdUntil = g_ptrJSONWriter->latestFlagMs + JSON_WRITER_DELAY;
unsigned long now = millis();
if (now >= holdUntil)
break;
notifyWait = pdMS_TO_TICKS(holdUntil - now);
}
for (auto &entry : g_ptrJSONWriter->writers)
{
// Unset flag before we do the actual write. This makes that we don't miss another flag raise if it happens while writing
if (entry.flag.exchange(false))
entry.writer();
}
}
}
+9 -23
View File
@@ -1,32 +1,18 @@
#include <globals.h>
#include <deviceconfig.h>
#include <ESP32SvelteKit.h>
#include <PsychicHttpServer.h>
#define SERIAL_BAUD_RATE 115200
ESPTaskManager g_TaskManager;
#if USE_WIFI && USE_WEBSERVER
DRAM_ATTR CWebServer g_WebServer;
#endif
PsychicHttpServer server;
ESP32SvelteKit esp32sveltekit(&server, 120);
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
Serial.begin(BAUDRATE);
log_i("Booting");
SPIFFS.begin();
Wire.begin(SDA, SCL);
InitializeCamera();
g_TaskManager.begin();
g_TaskManager.StartThreads();
Serial.begin(SERIAL_BAUD_RATE);
g_ptrJSONWriter = std::make_unique<JSONWriter>();
g_ptrDeviceConfig = std::make_unique<DeviceConfig>();
g_ptrServo = std::make_unique<Servo>();
g_ptrServo->begin();
g_ptrServo->setOscillatorFrequency(SERVO_OSCILLATOR_FREQUENCY);
g_ptrServo->setPWMFreq(SERVO_FREQ);
g_ptrServo->updateServos();
esp32sveltekit.begin();
}
void loop() {
delay(200);
vTaskDelete(NULL);
}
-79
View File
@@ -1,79 +0,0 @@
#include <featureflags.h>
#if USE_MPU
#include <MPU6050_light.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_HMC5883_U.h>
#endif
#if USE_POWER_BUTTON
#include <Bounce2.h>
#endif
#if USE_POWER_BUTTON
Bounce2::Button PowerButton;
#endif
#if USE_MPU
DRAM_ATTR MPU6050 g_imu(Wire);
DRAM_ATTR Adafruit_HMC5883_Unified g_mag = Adafruit_HMC5883_Unified(12345);
#endif
void beginMag() {
if(!g_mag.begin()) log_w("Failed to initialize HMC5883L");
}
void beginImu() {
byte status = g_imu.begin();
if(status != 0) log_w("MPU initialize failed");
delay(100);
g_imu.calcOffsets(true,true);
}
float getHeading() {
sensors_event_t event;
g_mag.getEvent(&event);
float heading = atan2(event.magnetic.y, event.magnetic.x);
float declinationAngle = 0.22;
heading += declinationAngle;
if(heading < 0) heading += 2*PI;
if(heading > 2*PI) heading -= 2*PI;
return heading * 180/M_PI;
}
float getTemp() {
return g_imu.getTemp();
}
float getAngleX() {
return g_imu.getAngleX();
}
float getAngleY() {
return g_imu.getAngleX();
}
float getAngleZ() {
return g_imu.getAngleZ();
}
void IRAM_ATTR MovementHandlingLoopEntry(void *) {
TickType_t notifyWait = 0;
beginMag();
beginImu();
for (;;) {
g_imu.update();
ulTaskNotifyTake(pdTRUE, notifyWait);
// #if USE_POWER_BUTTON
// PowerButton.update();
// if (PowerButton.pressed()) {
// log_i("Power Button Pressed");
// g_ptrServo->toggleState();
// }
// #endif
notifyWait = pdMS_TO_TICKS(100);
}
}
-96
View File
@@ -1,96 +0,0 @@
#include <globals.h>
#include <deviceconfig.h>
#if USE_WIFI && USE_WEBSERVER
extern DRAM_ATTR CWebServer g_WebServer;
#endif
#if USE_WIFI
bool ConnectToWiFi(uint cRetries) {
static bool bPreviousConnection = false;
if (WiFi.isConnected()) return true;
log_i("Connection to wifi");
WiFi.disconnect();
WiFi.mode(WIFI_STA);
WiFi.setHostname(HOSTNAME);
for (uint iPass = 0; iPass < cRetries; iPass++) {
log_i("Pass %u of %u: Connecting to Wifi SSID: \"%s\" - ESP32 Free Memory: %u, PSRAM:%u, PSRAM Free: %u\n",
iPass + 1, cRetries, SSID, ESP.getFreeHeap(), ESP.getPsramSize(), ESP.getFreePsram());
WiFi.begin(SSID, PASS);
delay(4000 + iPass * 1000);
if (WiFi.isConnected()) {
log_i("Connected to AP with BSSID: %s\n", WiFi.BSSIDstr().c_str());
break;
}
}
// Additional Services onwwards reliant on network so close if not up.
if (false == WiFi.isConnected()) {
log_i("Giving up on WiFi\n");
return false;
}
log_i("Received IP: %s", WiFi.localIP().toString().c_str());
// If we were connected before, network-dependent services will have been started already
if (bPreviousConnection)
return true;
#if USE_OTA
//debugI("Publishing OTA...");
SetupOTA(String(cszHostname));
#endif
#if USE_NTP
//debugI("Setting Clock...");
//NTPTimeClient::UpdateClockFromWeb(&g_Udp);
#endif
#if USE_WEBSERVER
g_WebServer.begin();
#endif
#if USE_MDNS
if(MDNS.begin(HOSTNAME)){
MDNS.addService("http", "tcp", HTTP_PORT);
}
#endif
bPreviousConnection = true;
return true;
}
void IRAM_ATTR NetworkHandlingLoopEntry(void *) {
unsigned long lastWifiCheck = 0;
unsigned long checkWiFiEveryMs = 1000;
TickType_t notifyWait = 0;
for (;;) {
ulTaskNotifyTake(pdTRUE, notifyWait);
if ( millis() - lastWifiCheck > checkWiFiEveryMs) {
if (WiFi.isConnected() == false && ConnectToWiFi(5) == false) {
log_e("Cannot Connect to Wifi!");
#if WAIT_FOR_WIFI
log_e("Rebooting in 5 seconds due to no Wifi available.");
delay(5000);
throw new std::runtime_error("Rebooting due to no Wifi available.");
#endif
}
}
#if USE_WEBSERVER
g_WebServer.loop();
#endif
notifyWait = pdMS_TO_TICKS(1000);
}
}
#endif // ENABLE_WIFI
-3
View File
@@ -1,3 +0,0 @@
#include <servo.h>
DRAM_ATTR std::unique_ptr<Servo> g_ptrServo;
-346
View File
@@ -1,346 +0,0 @@
#include "globals.h"
#include "webserver.h"
// Maps settings for which a validator is available to the invocation thereof
const std::map<String, CWebServer::ValueValidator> CWebServer::settingValidators
{};
std::vector<SettingSpec> CWebServer::deviceSettingSpecs{};
// Member function template specialzations
// Push param that represents a bool. Values considered true are text "true" and any whole number not equal to 0
template<>
bool CWebServer::PushPostParamIfPresent<bool>(AsyncWebServerRequest * pRequest, const String &paramName, ValueSetter<bool> setter)
{
return PushPostParamIfPresent<bool>(pRequest, paramName, setter, [](AsyncWebParameter * param) constexpr
{
const String& value = param->value();
return value == "true" || strtol(value.c_str(), NULL, 10);
});
}
// Push param that represents a size_t
template<>
bool CWebServer::PushPostParamIfPresent<size_t>(AsyncWebServerRequest * pRequest, const String &paramName, ValueSetter<size_t> setter)
{
return PushPostParamIfPresent<size_t>(pRequest, paramName, setter, [](AsyncWebParameter * param) constexpr
{
return strtoul(param->value().c_str(), NULL, 10);
});
}
// Add CORS header to and send JSON response
template<>
void CWebServer::AddCORSHeaderAndSendResponse<AsyncJsonResponse>(AsyncWebServerRequest * pRequest, AsyncJsonResponse * pResponse)
{
pResponse->setLength();
AddCORSHeaderAndSendResponse<AsyncWebServerResponse>(pRequest, pResponse);
}
// Member function implementations
bool CWebServer::IsPostParamTrue(AsyncWebServerRequest * pRequest, const String & paramName)
{
bool returnValue = false;
PushPostParamIfPresent<bool>(pRequest, paramName, [&returnValue](auto value) { returnValue = value; return true; });
return returnValue;
}
void CWebServer::GetStatistics(AsyncWebServerRequest * pRequest)
{
log_v("GetStatistics");
auto response = new AsyncJsonResponse(false, JSON_BUFFER_BASE_SIZE);
auto j = response->getRoot();
j["HEAP_SIZE"] = _staticStats.HeapSize;
j["HEAP_FREE"] = ESP.getFreeHeap();
j["HEAP_MIN"] = ESP.getMinFreeHeap();
j["DMA_SIZE"] = _staticStats.DmaHeapSize;
j["DMA_FREE"] = heap_caps_get_free_size(MALLOC_CAP_DMA);
j["DMA_MIN"] = heap_caps_get_largest_free_block(MALLOC_CAP_DMA);
j["PSRAM_SIZE"] = _staticStats.PsramSize;
j["PSRAM_FREE"] = ESP.getFreePsram();
j["PSRAM_MIN"] = ESP.getMinFreePsram();
j["CHIP_MODEL"] = _staticStats.ChipModel;
j["CHIP_CORES"] = _staticStats.ChipCores;
j["CHIP_SPEED"] = _staticStats.CpuFreqMHz;
j["PROG_SIZE"] = _staticStats.SketchSize;
j["CODE_SIZE"] = _staticStats.SketchSize;
j["CODE_FREE"] = _staticStats.FreeSketchSpace;
j["FLASH_SIZE"] = _staticStats.FlashChipSize;
j["CPU_USED"] = g_TaskManager.GetCPUUsagePercent();
j["CPU_USED_CORE0"] = g_TaskManager.GetCPUUsagePercent(0);
j["CPU_USED_CORE1"] = g_TaskManager.GetCPUUsagePercent(1);
AddCORSHeaderAndSendResponse(pRequest, response);
}
void CWebServer::SendSettingSpecsResponse(AsyncWebServerRequest * pRequest, const std::vector<SettingSpec> & settingSpecs)
{
static size_t jsonBufferSize = JSON_BUFFER_BASE_SIZE;
bool bufferOverflow;
do
{
bufferOverflow = false;
auto response = std::make_unique<AsyncJsonResponse>(false, jsonBufferSize);
auto jsonArray = response->getRoot().to<JsonArray>();
for (auto& spec : settingSpecs)
{
auto specObject = jsonArray.createNestedObject();
StaticJsonDocument<384> jsonDoc;
jsonDoc["name"] = spec.Name;
jsonDoc["friendlyName"] = spec.FriendlyName;
jsonDoc["description"] = spec.Description;
jsonDoc["type"] = to_value(spec.Type);
jsonDoc["typeName"] = spec.ToName(spec.Type);
if (!specObject.set(jsonDoc.as<JsonObjectConst>()))
{
bufferOverflow = true;
jsonBufferSize += JSON_BUFFER_INCREMENT;
log_v("JSON response buffer overflow! Increased buffer to %zu bytes", jsonBufferSize);
break;
}
}
if (!bufferOverflow)
AddCORSHeaderAndSendResponse(pRequest, response.release());
} while (bufferOverflow);
}
const std::vector<SettingSpec> & CWebServer::LoadDeviceSettingSpecs()
{
if (deviceSettingSpecs.size() == 0)
{
auto deviceConfigSpecs = g_ptrDeviceConfig->GetSettingSpecs();
deviceSettingSpecs.insert(deviceSettingSpecs.end(), deviceConfigSpecs.begin(), deviceConfigSpecs.end());
}
return deviceSettingSpecs;
}
void CWebServer::GetSettingSpecs(AsyncWebServerRequest * pRequest)
{
SendSettingSpecsResponse(pRequest, LoadDeviceSettingSpecs());
}
// Responds with current config, excluding any sensitive values
void CWebServer::GetSettings(AsyncWebServerRequest * pRequest)
{
log_v("GetSettings");
auto response = new AsyncJsonResponse(false, JSON_BUFFER_BASE_SIZE);
response->addHeader("Server","NightDriverStrip");
auto root = response->getRoot();
JsonObject jsonObject = root.to<JsonObject>();
// We get the serialized JSON for the device config, without any sensitive values
g_ptrDeviceConfig->SerializeToJSON(jsonObject, false);
AddCORSHeaderAndSendResponse(pRequest, response);
}
// Support function that silently sets whatever settings are included in the request passed.
// Composing a response is left to the invoker!
void CWebServer::SetSettingsIfPresent(AsyncWebServerRequest * pRequest)
{
PushPostParamIfPresent<String>(pRequest, DeviceConfig::NTPServerTag, SET_VALUE(g_ptrDeviceConfig->SetNTPServer(value)));
}
// Set settings and return resulting config
void CWebServer::SetSettings(AsyncWebServerRequest * pRequest)
{
log_v("SetSettings");
SetSettingsIfPresent(pRequest);
// We return the current config in response
GetSettings(pRequest);
}
// Save the posted file to SPIFFS
void CWebServer::SaveFile(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final)
{
log_v("SaveFile");
}
#if USE_OAT
// Update the current firmware
void CWebServer::UpdateFileSystemImage(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
{
log_v("UpdateFileSystemImage");
HandleUpdate(request, filename, index, data, len, final, (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000, U_FLASH);
}
// Update the current firmware
void CWebServer::UpdateFirmware(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
{
log_v("UpdateFirmware");
HandleUpdate(request, filename, index, data, len, final, UPDATE_SIZE_UNKNOWN, U_SPIFFS);
}
// Handles updating the filesystem image or firmware
void CWebServer::HandleUpdate(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final, size_t upload_size, uint8_t upload_index) {
if (!index) {
log_i("Update Start: %s\n", filename.c_str());
log_i("Uploading: %d", upload_index);
if (!Update.begin(upload_size, upload_index)) {
Update.printError(Serial);
}
}
if (!Update.hasError()) {
if (Update.write(data, len) != len) {
Update.printError(Serial);
}
}
if (final) {
if (Update.end(true)) {
log_i("Update Success: %uB\n", index + len);
}
else {
Update.printError(Serial);
}
}
}
// Ensures the request gets send to the client
void CWebServer::UpdateRequestHandler(AsyncWebServerRequest* request){
bool success = !Update.hasError();
AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", success ? "OK" : "FAIL");
response->addHeader("Connection", "close");
request->send(response);
if (success) {
delay(250);
ESP.restart();
}
}
#endif
// Reset effect config, device config and/or the board itself
void CWebServer::Reset(AsyncWebServerRequest * pRequest)
{
if (IsPostParamTrue(pRequest, "deviceConfig"))
{
log_i("Removing DeviceConfig");
g_ptrDeviceConfig->RemovePersisted();
}
bool boardResetRequested = IsPostParamTrue(pRequest, "board");
AddCORSHeaderAndSendOKResponse(pRequest);
if (boardResetRequested)
{
log_w("Resetting device at API request!");
delay(1000); // Give the response a second to be sent
throw new std::runtime_error("Resetting device at API request");
}
}
String dir(void) {
String x = "<table>";
File root = SPIFFS.open("/");
if(!root) {
return "Failed to open directory";
}
if(!root.isDirectory()) {
return "Not a directory";
}
File file = root.openNextFile();
while(file) {
x += "<tr><td>"+String(file.name());
if(file.isDirectory()){
x += "<td>DIR";
} else {
x += "<td style='text-align:right'>"+String(file.size());
}
file = root.openNextFile();
}
x += "<tr><td>Occupied space<td style='text-align:right'>"+String(SPIFFS.usedBytes());
x += "<tr><td>Total space<td style='text-align:right'>"+String(SPIFFS.totalBytes());
return x+"</table>";
}
// Send Gzip SPA
void CWebServer::GzipSpa(AsyncWebServerRequest * pRequest)
{
if(!SPIFFS.exists("/index.html.gz")){
log_e("Gzipped SPA not found");
pRequest->send(404, "text/html", dir());
return;
}
AsyncWebServerResponse *response = pRequest->beginResponse(SPIFFS, "/index.html.gz", "text/html", false);
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control"," max-age=86400");
AddCORSHeaderAndSendResponse(pRequest, response);
}
enum Action {
SERVO_UPDATE,
};
void handleWebSocketJson(uint8_t* data) {
const uint8_t size = JSON_OBJECT_SIZE(1);
StaticJsonDocument<100> json;
DeserializationError err = deserializeJson(json, data);
if (err) {
Serial.print(F("deserializeJson() failed with code "));
Serial.println(err.c_str());
return;
}
const uint8_t action = json["action"];
if(action == 0) {
const uint8_t servoIndex = json["servo"];
const int16_t pwm = json["pwm"];
log_i("Moving servo:%u to pwm%u", servoIndex, pwm);
g_ptrServo->setPWM(servoIndex, 0, pwm);
}
}
void handleWebSocketBuffer(uint8_t* data) {
if(data[0] == 0) g_ptrServo->setBody(data[1], data[2], data[3], data[4], data[5], data[6]);
else if(data[0] == 1) {
log_i("About to update all servos");
int16_t* angles = (int16_t*)data+1;
g_ptrServo->SetAngles(angles);
}
}
void handleWebSocketMessage(void* arg, uint8_t* data, size_t len, AsyncWebSocket* server, AsyncWebSocketClient* client) {
AwsFrameInfo* info = (AwsFrameInfo*)arg;
if(info->final && info->index == 0 && info->len == len){
if(info->opcode == WS_TEXT) handleWebSocketJson(data);
else handleWebSocketBuffer(data);
}
}
// Reset effect config, device config and/or the board itself
void CWebServer::HandleWsMessage(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
{
//if (type == WS_EVT_CONNECT) handleNewConnection(server, client);
/*else*/ if (type == WS_EVT_DISCONNECT) log_i("ws[%s][%u] disconnect\n", server->url(), client->id());
else if (type == WS_EVT_ERROR) log_i("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
else if (type == WS_EVT_PONG) log_i("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len) ? (char*)data : "");
else if (type == WS_EVT_DATA) handleWebSocketMessage(arg, data, len, server, client);
}