♻️ Major clean up of project structure

This commit is contained in:
Rune Harlyk
2025-09-11 17:14:31 +02:00
committed by Rune Harlyk
parent 26c36b8302
commit 7fd35f3f48
25 changed files with 370 additions and 890 deletions
+4 -4
View File
@@ -2,12 +2,14 @@
SemaphoreHandle_t clientSubscriptionsMutex = xSemaphoreCreateMutex();
EventSocket::EventSocket() {
EventSocket::EventSocket(PsychicHttpServer &server, const char *route) : _server(server), _route(route) {
_socket.onOpen((std::bind(&EventSocket::onWSOpen, this, std::placeholders::_1)));
_socket.onClose(std::bind(&EventSocket::onWSClose, this, std::placeholders::_1));
_socket.onFrame(std::bind(&EventSocket::onFrame, this, std::placeholders::_1, std::placeholders::_2));
}
void EventSocket::begin() { _server.on(_route, &_socket); }
void EventSocket::onWSOpen(PsychicWebSocketClient *client) {
ESP_LOGI("EventSocket", "ws[%s][%u] connect", client->remoteIP().toString().c_str(), client->socket());
}
@@ -171,6 +173,4 @@ void EventSocket::onEvent(String event, EventCallback callback) {
void EventSocket::onSubscribe(String event, SubscribeCallback callback) {
subscribe_callbacks[event].push_back(std::move(callback));
}
EventSocket socket;
}
+2 -4
View File
@@ -28,8 +28,7 @@ void printFeatureConfiguration() {
// Web services
ESP_LOGI("Features", "USE_MDNS: %s", USE_MDNS ? "enabled" : "disabled");
ESP_LOGI("Features", "EMBED_WWW: %s", EMBED_WWW ? "enabled" : "disabled");
ESP_LOGI("Features", "ENABLE_CORS: %s", ENABLE_CORS ? "enabled" : "disabled");
ESP_LOGI("Features", "EMBED_WEBAPP: %s", EMBED_WEBAPP ? "enabled" : "disabled");
ESP_LOGI("Features", "SERVE_CONFIG_FILES: %s", SERVE_CONFIG_FILES ? "enabled" : "disabled");
ESP_LOGI("Features", "KINEMATICS_VARIANT: %s", KINEMATICS_VARIANT_STR);
ESP_LOGI("Features", "==========================================================");
@@ -48,8 +47,7 @@ void features(JsonObject &root) {
root["servo"] = USE_PCA9685 ? true : false;
root["ws2812"] = USE_WS2812 ? true : false;
root["mdns"] = USE_MDNS ? true : false;
root["embed_www"] = EMBED_WWW ? true : false;
root["enable_cors"] = ENABLE_CORS ? true : false;
root["embed_www"] = EMBED_WEBAPP ? true : false;
root["serve_config_files"] = SERVE_CONFIG_FILES ? true : false;
root["firmware_version"] = APP_VERSION;
root["firmware_name"] = APP_NAME;
+4 -4
View File
@@ -40,10 +40,10 @@ esp_err_t handleEdit(PsychicRequest *request, JsonVariant &json) {
/* Helpers */
bool deleteFile(const char *filename) { return ESPFS.remove(filename); }
bool deleteFile(const char *filename) { return ESP_FS.remove(filename); }
String listFiles(const String &directory, bool isRoot) {
File root = ESPFS.open(directory.startsWith("/") ? directory : "/" + directory);
File root = ESP_FS.open(directory.startsWith("/") ? directory : "/" + directory);
if (!root.isDirectory()) return "{}";
File file = root.openNextFile();
@@ -95,7 +95,7 @@ esp_err_t uploadFile(PsychicRequest *request, const String &filename, uint64_t i
}
bool editFile(const char *filename, const char *content) {
File file = ESPFS.open(filename, FILE_WRITE);
File file = ESP_FS.open(filename, FILE_WRITE);
if (!file) return false;
file.print(content);
@@ -106,7 +106,7 @@ bool editFile(const char *filename, const char *content) {
esp_err_t mkdir(PsychicRequest *request, JsonVariant &json) {
const char *path = json["path"].as<const char *>();
ESP_LOGI(TAG, "Creating directory: %s", path);
return ESPFS.mkdir(path) ? request->reply(200) : request->reply(500);
return ESP_FS.mkdir(path) ? request->reply(200) : request->reply(500);
}
} // namespace FileSystem
-105
View File
@@ -1,105 +0,0 @@
/**
* ESP32 SvelteKit
*
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
* https://github.com/theelims/ESP32-sveltekit
*
* Copyright (C) 2023 theelims
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
**/
#include <firmware_download_service.h>
extern const uint8_t rootca_crt_bundle_start[] asm("_binary_src_certs_x509_crt_bundle_bin_start");
static int previousProgress = 0;
JsonVariant obj;
void update_started() {
obj["status"] = "preparing";
socket.emit(EVENT_DOWNLOAD_OTA, obj);
}
void update_progress(int currentBytes, int totalBytes) {
obj["status"] = "progress";
int progress = ((currentBytes * 100) / totalBytes);
if (progress > previousProgress) {
obj["progress"] = progress;
socket.emit(EVENT_DOWNLOAD_OTA, obj);
ESP_LOGV("Download OTA", "HTTP update process at %d of %d bytes... (%d %%)", currentBytes, totalBytes,
progress);
}
previousProgress = progress;
}
void update_finished() {
obj["status"] = "finished";
socket.emit(EVENT_DOWNLOAD_OTA, obj);
// delay to allow the event to be sent out
vTaskDelay(100 / portTICK_PERIOD_MS);
}
void updateTask(void *param) {
WiFiClientSecure client;
client.setCACertBundle(rootca_crt_bundle_start);
client.setTimeout(10);
httpUpdate.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
httpUpdate.rebootOnUpdate(true);
String url = *((String *)param);
// httpUpdate.onStart(update_started);
// httpUpdate.onProgress(update_progress);
// httpUpdate.onEnd(update_finished);
t_httpUpdate_return ret = httpUpdate.update(client, url.c_str());
switch (ret) {
case HTTP_UPDATE_FAILED:
obj["status"] = "error";
obj["error"] = httpUpdate.getLastErrorString().c_str();
socket.emit(EVENT_DOWNLOAD_OTA, obj);
ESP_LOGE("Download OTA", "HTTP Update failed with error (%d): %s", httpUpdate.getLastError(),
httpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
obj["status"] = "error";
obj["error"] = "Update failed, has same firmware version";
socket.emit(EVENT_DOWNLOAD_OTA, obj);
ESP_LOGE("Download OTA", "HTTP Update failed, has same firmware version");
break;
case HTTP_UPDATE_OK: ESP_LOGI("Download OTA", "HTTP Update successful - Restarting"); break;
}
vTaskDelete(NULL);
}
DownloadFirmwareService::DownloadFirmwareService() {}
esp_err_t DownloadFirmwareService::handleDownloadUpdate(PsychicRequest *request, JsonVariant &json) {
if (!json.is<JsonObject>()) {
return request->reply(400);
}
String downloadURL = json["download_url"];
ESP_LOGI("Download OTA", "Starting OTA from: %s", downloadURL.c_str());
obj["status"] = "preparing";
obj["progress"] = 0;
obj["error"] = "";
socket.emit(EVENT_DOWNLOAD_OTA, obj);
const BaseType_t taskResult = g_taskManager.createTask(&updateTask, "Firmware download", OTA_TASK_STACK_SIZE,
&downloadURL, (configMAX_PRIORITIES - 1), NULL, 1);
if (taskResult != pdPASS) {
ESP_LOGE("Download OTA", "Couldn't create download OTA task");
return request->reply(500);
}
return request->reply(200);
}
-161
View File
@@ -1,161 +0,0 @@
#include <firmware_upload_service.h>
#include <esp_app_format.h>
#include <esp_ota_ops.h>
static const char *TAG = "FirmwareUploadService";
using namespace std::placeholders;
static char md5[33] = "\0";
static size_t fsize = 0;
static FileType fileType = ft_none;
FirmwareUploadService::FirmwareUploadService() {}
void FirmwareUploadService::begin() {
uploadHandler.onUpload(std::bind(&FirmwareUploadService::handleUpload, this, _1, _2, _3, _4, _5, _6));
uploadHandler.onRequest(std::bind(&FirmwareUploadService::uploadComplete, this, _1));
uploadHandler.onClose(std::bind(&FirmwareUploadService::handleEarlyDisconnect, this));
}
esp_err_t FirmwareUploadService::handleUpload(PsychicRequest *request, const String &filename, uint64_t index,
uint8_t *data, size_t len, bool final) {
// at init
if (!index) {
// check details of the file, to see if its a valid bin or json file
std::string fname(filename.c_str());
auto position = fname.find_last_of(".");
std::string extension = fname.substr(position + 1);
fsize = request->contentLength();
fileType = ft_none;
if ((extension == "bin") && (fsize > 1000000)) {
fileType = ft_firmware;
} else if (extension == "md5") {
fileType = ft_md5;
if (len == 32) {
memcpy(md5, data, 32);
md5[32] = '\0';
}
return ESP_OK;
} else {
md5[0] = '\0';
return handleError(request,
406); // Not Acceptable - unsupported file type
}
if (fileType == ft_firmware) {
// Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip
// offset 12: esp32:0, S2:2, C3:5
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
return handleError(request, 503); // service unavailable
}
#elif CONFIG_IDF_TARGET_ESP32S2
if (len > 12 && (data[0] != 0xE9 || data[12] != 2)) {
return handleError(request, 503); // service unavailable
}
#elif CONFIG_IDF_TARGET_ESP32C3
if (len > 12 && (data[0] != 0xE9 || data[12] != 5)) {
return handleError(request, 503); // service unavailable
}
#elif CONFIG_IDF_TARGET_ESP32S3
if (len > 12 && (data[0] != 0xE9 || data[12] != 9)) {
return handleError(request, 503); // service unavailable
}
#endif
// it's firmware - initialize the ArduinoOTA updater
if (Update.begin(fsize - sizeof(esp_image_header_t))) {
ESP_LOGI(TAG, "Starting update");
if (strlen(md5) == 32) {
Update.setMD5(md5);
md5[0] = '\0';
ESP_LOGI(TAG, "Setting MD5 hash");
}
} else {
return handleError(request, 507); // failed to begin, send an error
// response Insufficient Storage
}
}
}
// if we haven't delt with an error, continue with the firmware update
if (!request->_tempObject) {
if (Update.write(data, len) != len) {
handleError(request, 500);
} else {
JsonVariant obj;
obj["status"] = "progress";
obj["progress"] = (float)Update.progress() / (float)fsize * 100.f;
socket.emit("otastatus", obj);
delay(20);
}
if (final) {
if (!Update.end(true)) {
handleError(request, 500);
} else {
JsonVariant obj;
obj["status"] = "finished", obj["progress"] = 100;
socket.emit("otastatus", obj);
ESP_LOGI(TAG, "Finish writing update");
}
}
}
return ESP_OK;
}
esp_err_t FirmwareUploadService::uploadComplete(PsychicRequest *request) {
// if we completed uploading a md5 file create a JSON response
if (fileType == ft_md5) {
if (strlen(md5) == 32) {
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject root = response.getRoot();
root["md5"] = md5;
return response.send();
}
return ESP_OK;
}
// if no error, send the success response
if (!request->_tempObject) {
ESP_LOGI(TAG, "Finish updating");
system_service::restart();
return ESP_OK;
}
// if updated has an error send 500 response and log on Serial
if (Update.hasError()) {
Update.printError(Serial);
Update.abort();
handleError(request, 500);
}
return ESP_OK;
}
esp_err_t FirmwareUploadService::handleError(PsychicRequest *request, int code) {
JsonVariant obj;
obj["status"] = "error", obj["error"] = Update.getError();
socket.emit("otastatus", obj);
// if we have had an error already, do nothing
if (request->_tempObject) {
return ESP_OK;
}
// send the error code to the client and record the error code in the temp
// object
request->_tempObject = new int(code);
return request->reply(code);
}
esp_err_t FirmwareUploadService::handleEarlyDisconnect() {
// if updated has not ended on connection close, abort it
if (!Update.end(true)) {
Update.printError(Serial);
Update.abort();
return ESP_OK;
}
return ESP_OK;
}
+138 -10
View File
@@ -1,26 +1,154 @@
#include <spot.h>
#include <Arduino.h>
#include <PsychicHttp.h>
#include <ESPmDNS.h>
#include <WiFi.h>
#include <Wire.h>
DRAM_ATTR Spot spot;
#include <filesystem.h>
#include <peripherals/peripherals.h>
#include <peripherals/servo_controller.h>
#include <peripherals/led_service.h>
#include <peripherals/camera_service.h>
#include <event_socket.h>
#include <features.h>
#include <motion.h>
#include <task_manager.h>
#include <wifi_service.h>
#include <ap_service.h>
#include <mdns_service.h>
#include <system_service.h>
void IRAM_ATTR SpotControlLoopEntry(void*) {
#include <www_mount.hpp>
// Communication
PsychicHttpServer server;
EventSocket socket {server, "/api/ws"};
// Core
Peripherals peripherals;
ServoController servoController;
MotionService motionService;
#if FT_ENABLED(USE_WS2812)
LEDService ledService;
#endif
#if FT_ENABLED(USE_CAMERA)
Camera::CameraService cameraService;
#endif
// Service
WiFiService wifiService;
APService apService;
void setupServer() {
server.config.max_uri_handlers = 5 + WWW_ASSETS_COUNT;
server.maxUploadSize = 1000000; // 1 MB;
server.listen(80);
server.serveStatic("/api/config/", ESP_FS, "/config/");
server.on("/api/features", feature_service::getFeatures);
#if USE_CAMERA
server.on("/api/camera/still", HTTP_GET,
[&](PsychicRequest *request) { return cameraService.cameraStill(request); });
server.on("/api/camera/stream", HTTP_GET,
[&](PsychicRequest *request) { return cameraService.cameraStream(request); });
server.on("/api/camera/settings", HTTP_GET,
[&](PsychicRequest *request) { return cameraService.endpoint.getState(request); });
server.on("/api/camera/settings", HTTP_POST, [&](PsychicRequest *request, JsonVariant &json) {
return cameraService.endpoint.handleStateUpdate(request, json);
});
#endif
#if EMBED_WEBAPP
mountStaticAssets(server);
#endif
server.on("/*", HTTP_OPTIONS, [](PsychicRequest *request) { // CORS handling
PsychicResponse response(request);
response.setCode(200);
return response.send();
});
DefaultHeaders::Instance().addHeader("Server", APP_NAME);
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
DefaultHeaders::Instance().addHeader("Access-Control-Max-Age", "86400");
}
#define ANGLES_EVENT "angles"
#define INPUT_EVENT "input"
#define MODE_EVENT "mode"
#define WALK_GAIT_EVENT "walk_gait"
void setupEventSocket() {
socket.onEvent(INPUT_EVENT, [&](JsonVariant &root, int originId) { motionService.handleInput(root, originId); });
socket.onEvent(MODE_EVENT, [&](JsonVariant &root, int originId) { motionService.handleMode(root, originId); });
socket.onEvent(WALK_GAIT_EVENT,
[&](JsonVariant &root, int originId) { motionService.handleWalkGait(root, originId); });
socket.onEvent(ANGLES_EVENT, [&](JsonVariant &root, int originId) { motionService.anglesEvent(root, originId); });
socket.onSubscribe(ANGLES_EVENT, std::bind(&MotionService::syncAngles, motionService, std::placeholders::_1,
std::placeholders::_2));
}
void IRAM_ATTR SpotControlLoopEntry(void *) {
ESP_LOGI("main", "Setup complete now runing tsk");
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = 5 / portTICK_PERIOD_MS;
peripherals.begin();
servoController.begin();
motionService.begin();
for (;;) {
spot.readSensors();
spot.planMotion();
spot.updateActuators();
spot.emitTelemetry();
CALLS_PER_SECOND(SpotControlLoopEntry);
peripherals.update();
motionService.update(&peripherals);
servoController.setAngles(motionService.getAngles());
motionService.isActive() ? servoController.activate() : servoController.deactivate();
servoController.update();
#if FT_ENABLED(USE_WS2812)
ledService.loop();
#endif
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
void IRAM_ATTR serviceLoopEntry(void *) {
ESP_LOGI("main", "Service control task starting");
wifiService.begin();
MDNS.begin(APP_NAME);
MDNS.setInstanceName(APP_NAME);
apService.begin();
setupServer();
socket.begin();
setupEventSocket();
ESP_LOGI("main", "Service control task started");
for (;;) {
wifiService.loop();
apService.loop();
EXECUTE_EVERY_N_MS(2000, system_service::emitMetrics());
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
spot.initialize();
ESP_FS.begin();
g_taskManager.createTask(SpotControlLoopEntry, "Spot control task", 4096, nullptr, 5);
ESP_LOGI("main", "Booting robot");
feature_service::printFeatureConfiguration();
xTaskCreate(serviceLoopEntry, "Service task", 4096, nullptr, 2, nullptr);
xTaskCreatePinnedToCore(SpotControlLoopEntry, "Control task", 4096, nullptr, 5, nullptr, 1);
ESP_LOGI("main", "Finished booting");
}
void loop() { vTaskDelete(NULL); }
void loop() { vTaskDelete(nullptr); }
-195
View File
@@ -1,195 +0,0 @@
#include <spot.h>
static const char *TAG = "Spot";
Spot::Spot()
:
#if FT_ENABLED(USE_MOTION)
_motionService(&_servoController, &_peripherals)
#endif
{
}
void Spot::initialize() {
ESP_LOGI(TAG, "Running Firmware Version: %s", APP_VERSION);
feature_service::printFeatureConfiguration();
ESPFS.begin(true);
g_taskManager.begin();
#if FT_ENABLED(USE_WS2812)
_ledService.loop();
#endif
_wifiService.begin();
setupServer();
startServices();
ESP_LOGV(TAG, "Starting misc loop task");
g_taskManager.createTask(this->_loopImpl, "Spot misc", 4096, this, 2, NULL, APPLICATION_CORE);
}
void Spot::setupServer() {
_server.config.max_uri_handlers = _numberEndpoints;
_server.maxUploadSize = _maxFileUpload;
_server.listen(_port);
// WIFI
_server.on("/api/wifi/scan", HTTP_GET, _wifiService.handleScan);
_server.on("/api/wifi/networks", HTTP_GET,
[this](PsychicRequest *request) { return _wifiService.getNetworks(request); });
_server.on("/api/wifi/sta/status", HTTP_GET,
[this](PsychicRequest *request) { return _wifiService.getNetworkStatus(request); });
_server.on("/api/wifi/sta/settings", HTTP_GET,
[this](PsychicRequest *request) { return _wifiService.endpoint.getState(request); });
_server.on("/api/wifi/sta/settings", HTTP_POST, [this](PsychicRequest *request, JsonVariant &json) {
return _wifiService.endpoint.handleStateUpdate(request, json);
});
// AP
_server.on("/api/wifi/ap/status", HTTP_GET,
[this](PsychicRequest *request) { return _apService.getStatus(request); });
_server.on("/api/wifi/ap/settings", HTTP_GET,
[this](PsychicRequest *request) { return _apService.endpoint.getState(request); });
_server.on("/api/wifi/ap/settings", HTTP_POST, [this](PsychicRequest *request, JsonVariant &json) {
return _apService.endpoint.handleStateUpdate(request, json);
});
// CAMERA
#if USE_CAMERA
_server.on("/api/camera/still", HTTP_GET,
[this](PsychicRequest *request) { return _cameraService.cameraStill(request); });
_server.on("/api/camera/stream", HTTP_GET,
[this](PsychicRequest *request) { return _cameraService.cameraStream(request); });
_server.on("/api/camera/settings", HTTP_GET,
[this](PsychicRequest *request) { return _cameraService.endpoint.getState(request); });
_server.on("/api/camera/settings", HTTP_POST, [this](PsychicRequest *request, JsonVariant &json) {
return _cameraService.endpoint.handleStateUpdate(request, json);
});
#endif
// SYSTEM
_server.on("/api/system/reset", HTTP_POST, system_service::handleReset);
_server.on("/api/system/restart", HTTP_POST, system_service::handleRestart);
_server.on("/api/system/sleep", HTTP_POST, system_service::handleSleep);
_server.on("/api/system/status", HTTP_GET, system_service::getStatus);
_server.on("/api/system/metrics", HTTP_GET, system_service::getMetrics);
// FILESYSTEM
_server.on("/api/files", HTTP_GET, FileSystem::getFiles);
_server.on("/api/files/delete", HTTP_POST, FileSystem::handleDelete);
_server.on("/api/files/upload/*", HTTP_POST, FileSystem::uploadHandler);
_server.on("/api/files/edit", HTTP_POST, FileSystem::handleEdit);
_server.on("/api/files/mkdir", HTTP_POST, FileSystem::mkdir);
// SERVO
_server.on("/api/servo/config", HTTP_GET,
[this](PsychicRequest *request) { return _servoController.endpoint.getState(request); });
_server.on("/api/servo/config", HTTP_POST, [this](PsychicRequest *request, JsonVariant &json) {
return _servoController.endpoint.handleStateUpdate(request, json);
});
// PERIPHERALS
_server.on("/api/peripheral/settings", HTTP_GET,
[this](PsychicRequest *request) { return _peripherals.endpoint.getState(request); });
_server.on("/api/peripheral/settings", HTTP_POST, [this](PsychicRequest *request, JsonVariant &json) {
return _peripherals.endpoint.handleStateUpdate(request, json);
});
// MISC
_server.on("/api/ws/events", socket.getHandler());
_server.on("/api/features", feature_service::getFeatures);
#if FT_ENABLED(USE_UPLOAD_FIRMWARE)
_server.on("/api/firmware", HTTP_POST, _uploadFirmwareService.getHandler());
#endif
#if FT_ENABLED(USE_DOWNLOAD_FIRMWARE)
_server.on("/api/firmware/download", HTTP_POST, [this](PsychicRequest *r, JsonVariant &json) {
return _downloadFirmwareService.handleDownloadUpdate(r, json);
});
#endif
// MDNS
_server.on("/api/mdns/status", HTTP_GET,
[this](PsychicRequest *request) { return _mdnsService.getStatus(request); });
_server.on("/api/mdns/settings", HTTP_GET,
[this](PsychicRequest *request) { return _mdnsService.endpoint.getState(request); });
_server.on("/api/mdns/settings", HTTP_POST, [this](PsychicRequest *request, JsonVariant &json) {
return _mdnsService.endpoint.handleStateUpdate(request, json);
});
_server.on("/api/mdns/query", HTTP_POST, MDNSService::queryServices);
#ifdef EMBED_WWW
ESP_LOGV(TAG, "Registering routes from PROGMEM static resources");
WWWData::registerRoutes([&](const String &uri, const String &contentType, const uint8_t *content, size_t len) {
PsychicHttpRequestCallback requestHandler = [contentType, content, len](PsychicRequest *request) {
PsychicResponse response(request);
response.setCode(200);
response.setContentType(contentType.c_str());
response.addHeader("Content-Encoding", "gzip");
response.addHeader("Cache-Control", "public, immutable, max-age=31536000");
response.setContent(content, len);
return response.send();
};
PsychicWebHandler *handler = new PsychicWebHandler();
handler->onRequest(requestHandler);
_server.on(uri.c_str(), HTTP_GET, handler);
// Set default end-point for all non matching requests
// this is easier than using webServer.onNotFound()
if (uri.equals("/index.html")) {
_server.defaultEndpoint->setHandler(handler);
}
});
#else
// Serve static resources from /www/
ESP_LOGV(TAG, "Registering routes from FS /www/ static resources");
_server.serveStatic("/_app/", ESPFS, "/www/_app/");
_server.serveStatic("/favicon.png", ESPFS, "/www/favicon.png");
// Serving all other get requests with "/www/index.htm"
_server.onNotFound([](PsychicRequest *request) {
if (request->method() == HTTP_GET) {
PsychicFileResponse response(request, ESPFS, "/www/index.html", "text/html");
return response.send();
// String url = "http://" + request->host() + "/index.html";
// request->redirect(url.c_str());
}
});
#endif
#ifdef SERVE_CONFIG_FILES
_server.serveStatic("/api/config/", ESPFS, "/config/");
#endif
#if defined(ENABLE_CORS)
ESP_LOGV(TAG, "Enabling CORS headers");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true");
#endif
DefaultHeaders::Instance().addHeader("Server", _appName);
}
void Spot::startServices() {
_apService.begin();
#if FT_ENABLED(USE_UPLOAD_FIRMWARE)
_uploadFirmwareService.begin();
#endif
_peripherals.begin();
_servoController.begin();
#if FT_ENABLED(USE_MOTION)
_motionService.begin();
#endif
#if FT_ENABLED(USE_CAMERA)
_cameraService.begin();
#endif
_mdnsService.begin();
}
void IRAM_ATTR Spot::loop() {
while (1) {
_wifiService.loop();
_apService.loop();
EXECUTE_EVERY_N_MS(2000, system_service::emitMetrics());
delay(20);
}
}
+12 -12
View File
@@ -38,12 +38,12 @@ esp_err_t getMetrics(PsychicRequest *request) {
void reset() {
ESP_LOGI(TAG, "Resetting device");
File root = ESPFS.open(FS_CONFIG_DIRECTORY);
File root = ESP_FS.open(FS_CONFIG_DIRECTORY);
File file;
while (file = root.openNextFile()) {
String path = file.path();
file.close();
ESPFS.remove(path);
ESP_FS.remove(path);
}
restart();
}
@@ -106,8 +106,8 @@ void status(JsonObject &root) {
root["arduino_version"] = ARDUINO_VERSION;
root["flash_chip_size"] = ESP.getFlashChipSize();
root["flash_chip_speed"] = ESP.getFlashChipSpeed();
root["fs_total"] = ESPFS.totalBytes();
root["fs_used"] = ESPFS.usedBytes();
root["fs_total"] = ESP_FS.totalBytes();
root["fs_used"] = ESP_FS.usedBytes();
root["core_temp"] = temperatureRead();
root["cpu_reset_reason"] = resetReason(esp_reset_reason());
root["uptime"] = millis() / 1000;
@@ -119,8 +119,8 @@ void metrics(JsonObject &root) {
root["total_heap"] = ESP.getHeapSize();
root["min_free_heap"] = ESP.getMinFreeHeap();
root["max_alloc_heap"] = ESP.getMaxAllocHeap();
root["fs_used"] = ESPFS.usedBytes();
root["fs_total"] = ESPFS.totalBytes();
root["fs_used"] = ESP_FS.usedBytes();
root["fs_total"] = ESP_FS.totalBytes();
root["core_temp"] = temperatureRead();
root["cpu0_usage"] = g_taskManager.getCpuUsage(0);
root["cpu1_usage"] = g_taskManager.getCpuUsage(1);
@@ -136,12 +136,12 @@ void metrics(JsonObject &root) {
}
void emitMetrics() {
if (!socket.hasSubscribers(EVENT_ANALYTICS)) return;
analyticsDoc.clear();
JsonObject root = analyticsDoc.to<JsonObject>();
system_service::metrics(root);
JsonVariant data = analyticsDoc.as<JsonVariant>();
socket.emit(EVENT_ANALYTICS, data);
// if (!socket.hasSubscribers(EVENT_ANALYTICS)) return;
// analyticsDoc.clear();
// JsonObject root = analyticsDoc.to<JsonObject>();
// system_service::metrics(root);
// JsonVariant data = analyticsDoc.as<JsonVariant>();
// socket.emit(EVENT_ANALYTICS, data);
}
const char *resetReason(esp_reset_reason_t reason) {
+35
View File
@@ -0,0 +1,35 @@
#include <Arduino.h>
#include "www_mount.hpp"
static esp_err_t web_send(PsychicRequest* req, const WebAsset& asset) {
PsychicResponse resp(req);
resp.setCode(200);
resp.setContentType(asset.mime);
if (asset.gz) resp.addHeader("Content-Encoding", "gzip");
if (WWW_OPT.add_vary) resp.addHeader("Vary", "Accept-Encoding");
char cc[64];
snprintf(cc, sizeof(cc), "public, immutable, max-age=%u", WWW_OPT.max_age);
resp.addHeader("Cache-Control", cc);
char et[34];
snprintf(et, sizeof(et), "\"%08x\"", asset.etag);
resp.addHeader("ETag", et);
resp.setContent(asset.data, asset.len);
return resp.send();
}
void mountStaticAssets(PsychicHttpServer& server) {
static uint8_t buf[sizeof(PsychicWebHandler) * WWW_ASSETS_COUNT];
for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) {
const WebAsset* a = &WWW_ASSETS[i];
auto* handle = new (&buf[i * sizeof(PsychicWebHandler)]) PsychicWebHandler();
handle->onRequest([a](PsychicRequest* req) { return web_send(req, *a); });
server.on(a->uri, HTTP_GET, handle);
}
for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) {
if (strcmp(WWW_ASSETS[i].uri, WWW_OPT.default_uri) == 0) {
server.defaultEndpoint->setHandler(
reinterpret_cast<PsychicWebHandler*>(&buf[i * sizeof(PsychicWebHandler)]));
break;
}
}
}