Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7376ecf270 | |||
| 5481a598d9 | |||
| 0d379a8013 | |||
| 868ff0446a | |||
| 081c1e7046 | |||
| 042548412d | |||
| 5c4dc51093 | |||
| 94a50302cc | |||
| e17382c505 | |||
| 106c20418c | |||
| 413097db1c | |||
| f9c28ed42a | |||
| 69dbea3fae | |||
| a24ab44b17 | |||
| 9e02f8b8ee |
+1
-1
@@ -14,7 +14,7 @@ build_flags =
|
|||||||
-D USE_BMP180=0
|
-D USE_BMP180=0
|
||||||
-D USE_MPU6050=0
|
-D USE_MPU6050=0
|
||||||
-D USE_ICM20948=1
|
-D USE_ICM20948=1
|
||||||
-D USE_ICM20948_SPIMODE=1
|
-D USE_ICM20948_SPIMODE=0
|
||||||
-D USE_WS2812=1
|
-D USE_WS2812=1
|
||||||
-D USE_BNO055=0
|
-D USE_BNO055=0
|
||||||
-D USE_USS=0
|
-D USE_USS=0
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
#include <template/stateful_persistence.h>
|
#include <template/stateful_persistence.h>
|
||||||
#include <settings/ap_settings.h>
|
#include <settings/ap_settings.h>
|
||||||
#include <utils/timing.h>
|
#include <utils/timing.h>
|
||||||
#include <WiFi.h>
|
#include <utils/http_utils.h>
|
||||||
#include "esp_timer.h"
|
#include <esp_http_server.h>
|
||||||
|
#include <esp_timer.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class APService : public StatefulService<APSettings> {
|
class APService : public StatefulService<APSettings> {
|
||||||
@@ -16,14 +17,13 @@ class APService : public StatefulService<APSettings> {
|
|||||||
void loop();
|
void loop();
|
||||||
void recoveryMode();
|
void recoveryMode();
|
||||||
|
|
||||||
esp_err_t getStatus(PsychicRequest *request);
|
esp_err_t getStatus(httpd_req_t *req);
|
||||||
void status(JsonObject &root);
|
void status(JsonObject &root);
|
||||||
APNetworkStatus getAPNetworkStatus();
|
APNetworkStatus getAPNetworkStatus();
|
||||||
|
|
||||||
StatefulHttpEndpoint<APSettings> endpoint;
|
StatefulHttpEndpoint<APSettings> endpoint;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PsychicHttpServer *_server;
|
|
||||||
FSPersistence<APSettings> _persistence;
|
FSPersistence<APSettings> _persistence;
|
||||||
|
|
||||||
DNSServer *_dnsServer;
|
DNSServer *_dnsServer;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#ifndef Socket_h
|
#ifndef Socket_h
|
||||||
#define Socket_h
|
#define Socket_h
|
||||||
|
|
||||||
#include <PsychicHttp.h>
|
#include <esp_http_server.h>
|
||||||
#include <template/stateful_service.h>
|
#include <template/stateful_service.h>
|
||||||
|
#include <utils/websocket_server.h>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
|
|
||||||
class Websocket : public CommAdapterBase {
|
class Websocket : public CommAdapterBase {
|
||||||
public:
|
public:
|
||||||
Websocket(PsychicHttpServer &server, const char *route = "/api/ws");
|
Websocket(httpd_handle_t *server, const char *route = "/api/ws");
|
||||||
|
|
||||||
void begin() override;
|
void begin() override;
|
||||||
|
|
||||||
@@ -20,17 +21,21 @@ class Websocket : public CommAdapterBase {
|
|||||||
|
|
||||||
void emit(const char *event, JsonVariant &payload, const char *originId = "", bool onlyToSameOrigin = false);
|
void emit(const char *event, JsonVariant &payload, const char *originId = "", bool onlyToSameOrigin = false);
|
||||||
|
|
||||||
private:
|
httpd_uri_t *getUriHandler() { return &_ws_uri; }
|
||||||
PsychicWebSocketHandler _socket;
|
|
||||||
PsychicHttpServer &_server;
|
|
||||||
const char *_route;
|
|
||||||
|
|
||||||
void onWSOpen(PsychicWebSocketClient *client);
|
private:
|
||||||
void onWSClose(PsychicWebSocketClient *client);
|
websocket::WebSocketServer _socket;
|
||||||
esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame);
|
httpd_handle_t *_server;
|
||||||
|
const char *_route;
|
||||||
|
httpd_uri_t _ws_uri;
|
||||||
|
|
||||||
|
void onWSOpen(int fd);
|
||||||
|
void onWSClose(int fd);
|
||||||
|
esp_err_t onFrame(httpd_req_t *req, httpd_ws_frame_t *frame);
|
||||||
|
|
||||||
void send(const uint8_t *data, size_t len, int cid = -1) override;
|
void send(const uint8_t *data, size_t len, int cid = -1) override;
|
||||||
|
|
||||||
|
static esp_err_t ws_handler_wrapper(httpd_req_t *req);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
#ifndef Features_h
|
#ifndef Features_h
|
||||||
#define Features_h
|
#define Features_h
|
||||||
|
|
||||||
#include <WiFi.h>
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <PsychicHttp.h>
|
#include <esp_http_server.h>
|
||||||
|
|
||||||
#define FT_ENABLED(feature) feature
|
#define FT_ENABLED(feature) feature
|
||||||
|
|
||||||
@@ -91,7 +90,7 @@ void printFeatureConfiguration();
|
|||||||
|
|
||||||
void features(JsonObject &root);
|
void features(JsonObject &root);
|
||||||
|
|
||||||
esp_err_t getFeatures(PsychicRequest *request);
|
esp_err_t getFeatures(httpd_req_t *req);
|
||||||
|
|
||||||
} // namespace feature_service
|
} // namespace feature_service
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <PsychicHttp.h>
|
#include <mdns.h>
|
||||||
#include <ESPmDNS.h>
|
#include <esp_http_server.h>
|
||||||
#include <template/stateful_service.h>
|
#include <utils/http_utils.h>
|
||||||
#include <template/stateful_endpoint.h>
|
|
||||||
#include <template/stateful_persistence.h>
|
|
||||||
#include <settings/mdns_settings.h>
|
|
||||||
#include <utils/timing.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class MDNSService : public StatefulService<MDNSSettings> {
|
namespace mdns_service {
|
||||||
private:
|
|
||||||
FSPersistence<MDNSSettings> _persistence;
|
|
||||||
bool _started {false};
|
|
||||||
|
|
||||||
void reconfigureMDNS();
|
void begin(const char *hostname);
|
||||||
void startMDNS();
|
void end();
|
||||||
void stopMDNS();
|
void addService(const char *service, const char *proto, uint16_t port);
|
||||||
void addServices();
|
void addServiceTxt(const char *service, const char *proto, const char *key, const char *value);
|
||||||
|
|
||||||
public:
|
} // namespace mdns_service
|
||||||
MDNSService();
|
|
||||||
~MDNSService();
|
|
||||||
|
|
||||||
void begin();
|
|
||||||
|
|
||||||
esp_err_t getStatus(PsychicRequest *request);
|
|
||||||
void getStatus(JsonVariant &root);
|
|
||||||
|
|
||||||
static esp_err_t queryServices(PsychicRequest *request, JsonVariant &json);
|
|
||||||
|
|
||||||
StatefulHttpEndpoint<MDNSSettings> endpoint;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
#define CameraService_h
|
#define CameraService_h
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <PsychicHttp.h>
|
#include <esp_http_server.h>
|
||||||
#include <WiFi.h>
|
#include <utils/http_utils.h>
|
||||||
#include <async_worker.h>
|
#include <async_worker.h>
|
||||||
|
|
||||||
#include <features.h>
|
#include <features.h>
|
||||||
@@ -35,8 +35,8 @@ class CameraService : public StatefulService<CameraSettings> {
|
|||||||
|
|
||||||
esp_err_t begin();
|
esp_err_t begin();
|
||||||
|
|
||||||
esp_err_t cameraStill(PsychicRequest *request);
|
esp_err_t cameraStill(httpd_req_t *req);
|
||||||
esp_err_t cameraStream(PsychicRequest *request);
|
esp_err_t cameraStream(httpd_req_t *req);
|
||||||
|
|
||||||
StatefulHttpEndpoint<CameraSettings> endpoint;
|
StatefulHttpEndpoint<CameraSettings> endpoint;
|
||||||
|
|
||||||
|
|||||||
@@ -87,40 +87,32 @@ class IMU : public SensorBase<IMUAnglesMsg> {
|
|||||||
_imu.setExtCrystalUse(true);
|
_imu.setExtCrystalUse(true);
|
||||||
#endif
|
#endif
|
||||||
#if FT_ENABLED(USE_ICM20948)
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
#if USE_ICM20948_SPIMODE > 0
|
||||||
#define ICM_20948_USE_DMP // TODO: Move to features.ini
|
|
||||||
SPI_PORT.begin(SPI_SCK, SPI_MISO, SPI_MOSI, -1); // TODO: Move to global spi start
|
|
||||||
_imu = (ICM_20948_SPI*)_arg;
|
_imu = (ICM_20948_SPI*)_arg;
|
||||||
#ifndef ICM20948_ALIVE
|
if (true || !_imu->isConnected()) { _imu->begin(CS_PIN, SPI_PORT); ESP_LOGI("IMU", "Beginning ICM20948 in SPI mode"); }
|
||||||
#define ICM20948_ALIVE
|
|
||||||
_imu->begin(ICM20948_SPI_CS, SPI_PORT); ESP_LOGI("IMU", "Beginning ICM20948 in SPI mode");
|
|
||||||
_imu->initializeDMP();
|
|
||||||
#endif
|
|
||||||
#else
|
#else
|
||||||
_imu = (ICM_20948_I2C*)_arg;
|
_imu = (ICM_20948_I2C*)_arg;
|
||||||
#ifndef ICM20948_ALIVE
|
if (true || !_imu->isConnected()) { _imu->begin(Wire, 1, 0xFF); ESP_LOGI("IMU", "Beginning ICM20948 in I2C mode"); }
|
||||||
#define ICM20948_ALIVE
|
|
||||||
_imu->begin(Wire, 1, 0xFF); ESP_LOGI("IMU", "Beginning ICM20948 in I2C mode");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
if (_imu->status != ICM_20948_Stat_Ok){ ESP_LOGW("IMU", "Failed to start ICM20948: begin failed"); return false; }
|
if (_imu->status != ICM_20948_Stat_Ok){ return false; }
|
||||||
|
|
||||||
_imu->setSampleMode((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), ICM_20948_Sample_Mode_Continuous);
|
_imu->setSampleMode((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), ICM_20948_Sample_Mode_Continuous);
|
||||||
if (_imu->status != ICM_20948_Stat_Ok){ ESP_LOGW("IMU", "Failed to start ICM20948: set sample failed"); return false; }
|
if (_imu->status != ICM_20948_Stat_Ok){ return false; }
|
||||||
|
|
||||||
ICM_20948_fss_t myFSS;
|
ICM_20948_fss_t myFSS;
|
||||||
myFSS.a = gpm2;
|
myFSS.a = gpm2;
|
||||||
myFSS.g = dps250;
|
myFSS.g = dps250;
|
||||||
_imu->setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), myFSS);
|
_imu->setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), myFSS);
|
||||||
if (_imu->status != ICM_20948_Stat_Ok){ ESP_LOGW("IMU", "Failed to start ICM20948: set full scale failed"); return false; }
|
if (_imu->status != ICM_20948_Stat_Ok){ return false; }
|
||||||
// TODO: Setup low pass filter config
|
// TODO: Setup low pass filter config
|
||||||
_msg.success = true;
|
_msg.success = true;
|
||||||
#endif
|
#endif
|
||||||
return _msg.success;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool update() override {
|
bool update() override {
|
||||||
|
//if (!_msg.success) return false;
|
||||||
#if FT_ENABLED(USE_MPU6050)
|
#if FT_ENABLED(USE_MPU6050)
|
||||||
uint16_t fifoCount = _imu.getFIFOCount();
|
uint16_t fifoCount = _imu.getFIFOCount();
|
||||||
uint8_t intStatus = _imu.getIntStatus();
|
uint8_t intStatus = _imu.getIntStatus();
|
||||||
@@ -140,26 +132,30 @@ class IMU : public SensorBase<IMUAnglesMsg> {
|
|||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
#if FT_ENABLED(USE_ICM20948)
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
|
|
||||||
#ifndef ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
|
||||||
#define ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
|
||||||
if (_imu->dataReady())
|
if (_imu->dataReady())
|
||||||
{
|
{
|
||||||
_imu->getAGMT();
|
_imu->getAGMT();
|
||||||
} else {
|
_msg.rpy[0] = _imu->gyrX();
|
||||||
return false;
|
_msg.rpy[1] = _imu->gyrY();
|
||||||
|
_msg.rpy[2] = _imu->gyrZ();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
_msg.rpy[0] = _imu->accX();
|
|
||||||
_msg.rpy[1] = _imu->accY();
|
|
||||||
_msg.rpy[2] = _imu->accZ();
|
|
||||||
#endif
|
|
||||||
#if FT_ENABLED(USE_BNO055)
|
#if FT_ENABLED(USE_BNO055)
|
||||||
sensors_event_t event;
|
sensors_event_t event;
|
||||||
_imu.getEvent(&event);
|
_imu.getEvent(&event);
|
||||||
_msg.rpy[0] = event.orientation.x;
|
_msg.rpy[0] = event.orientation.x;
|
||||||
_msg.rpy[1] = event.orientation.y;
|
_msg.rpy[1] = event.orientation.y;
|
||||||
_msg.rpy[2] = event.orientation.z;
|
_msg.rpy[2] = event.orientation.z;
|
||||||
|
#endif
|
||||||
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
|
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||||
|
#define SPI_PORT SPI // TODO in periphearals_seetings.h
|
||||||
|
#define CS_PIN 2
|
||||||
|
ICM_20948_SPI _imu;
|
||||||
|
#else
|
||||||
|
//#define WIRE_PORT Wire
|
||||||
|
ICM_20948_I2C _imu;
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -185,6 +181,8 @@ class IMU : public SensorBase<IMUAnglesMsg> {
|
|||||||
#endif
|
#endif
|
||||||
#if FT_ENABLED(USE_ICM20948)
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||||
|
#define SPI_PORT SPI // TODO in periphearals_seetings.h
|
||||||
|
#define CS_PIN 2
|
||||||
ICM_20948_SPI* _imu;
|
ICM_20948_SPI* _imu;
|
||||||
#else
|
#else
|
||||||
//#define WIRE_PORT Wire
|
//#define WIRE_PORT Wire
|
||||||
|
|||||||
@@ -41,19 +41,12 @@ class Magnetometer : public SensorBase<MagnetometerMsg> {
|
|||||||
public:
|
public:
|
||||||
bool initialize(void* _arg) override {
|
bool initialize(void* _arg) override {
|
||||||
#if FT_ENABLED(USE_ICM20948)
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
#if USE_ICM20948_SPIMODE > 0
|
||||||
SPI_PORT.begin(SPI_SCK, SPI_MISO, SPI_MOSI, -1);
|
|
||||||
_mag = (ICM_20948_SPI*)_arg;
|
_mag = (ICM_20948_SPI*)_arg;
|
||||||
#ifndef ICM20948_ALIVE
|
if (true || !_mag->isConnected()) { _mag->begin(CS_PIN, SPI_PORT); ESP_LOGI("Magnetometer", "Beginning ICM20948 in SPI mode"); }
|
||||||
#define ICM20948_ALIVE
|
|
||||||
_imu->begin(ICM20948_SPI_CS, SPI_PORT); ESP_LOGI("Magnetometer", "Beginning ICM20948 in SPI mode");
|
|
||||||
#endif
|
|
||||||
#else
|
#else
|
||||||
_mag = (ICM_20948_I2C*)_arg;
|
_mag = (ICM_20948_I2C*)_arg;
|
||||||
#ifndef ICM20948_ALIVE
|
if (true || !_mag->isConnected()) { _mag->begin(Wire, 1, 0xFF); ESP_LOGI("Magnetometer", "Beginning ICM20948 in I2C mode"); }
|
||||||
#define ICM20948_ALIVE
|
|
||||||
if (!_mag->isConnected()) { _mag->begin(Wire, 1, 0xFF); ESP_LOGI("Magnetometer", "Beginning ICM20948 in I2C mode"); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
if (_mag->status != ICM_20948_Stat_Ok){ return false; }
|
if (_mag->status != ICM_20948_Stat_Ok){ return false; }
|
||||||
@@ -70,15 +63,8 @@ class Magnetometer : public SensorBase<MagnetometerMsg> {
|
|||||||
bool update() override {
|
bool update() override {
|
||||||
if (!_msg.success) return false;
|
if (!_msg.success) return false;
|
||||||
#if FT_ENABLED(USE_ICM20948)
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
#ifndef ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
_mag->getAGMT();
|
||||||
#define ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
if (_mag->status != ICM_20948_Stat_Ok){ return false; }
|
||||||
if (_imu->dataReady())
|
|
||||||
{
|
|
||||||
_imu->getAGMT();
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
_msg.rpy[0] = _mag->magX();
|
_msg.rpy[0] = _mag->magX();
|
||||||
_msg.rpy[1] = _mag->magY();
|
_msg.rpy[1] = _mag->magY();
|
||||||
_msg.rpy[2] = _mag->magZ();
|
_msg.rpy[2] = _mag->magZ();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
#define SCL_PIN SCL
|
#define SCL_PIN SCL
|
||||||
#endif
|
#endif
|
||||||
#ifndef I2C_FREQUENCY
|
#ifndef I2C_FREQUENCY
|
||||||
#define I2C_FREQUENCY 400000UL
|
#define I2C_FREQUENCY 1000000UL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class PinConfig {
|
class PinConfig {
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <PsychicHttp.h>
|
#include <esp_http_server.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <communication/websocket_adapter.h>
|
#include <communication/websocket_adapter.h>
|
||||||
#include <filesystem.h>
|
#include <filesystem.h>
|
||||||
#include <global.h>
|
#include <global.h>
|
||||||
#include "esp_timer.h"
|
#include <esp_timer.h>
|
||||||
|
#include <utils/http_utils.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#define MAX_ESP_ANALYTICS_SIZE 2024
|
#define MAX_ESP_ANALYTICS_SIZE 2024
|
||||||
#define EVENT_ANALYTICS "analytics"
|
#define EVENT_ANALYTICS "analytics"
|
||||||
|
|
||||||
namespace system_service {
|
namespace system_service {
|
||||||
esp_err_t handleReset(PsychicRequest *request);
|
esp_err_t handleReset(httpd_req_t *req);
|
||||||
esp_err_t handleRestart(PsychicRequest *request);
|
esp_err_t handleRestart(httpd_req_t *req);
|
||||||
esp_err_t handleSleep(PsychicRequest *request);
|
esp_err_t handleSleep(httpd_req_t *req);
|
||||||
esp_err_t getStatus(PsychicRequest *request);
|
esp_err_t getStatus(httpd_req_t *req);
|
||||||
esp_err_t getMetrics(PsychicRequest *request);
|
esp_err_t getMetrics(httpd_req_t *req);
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
void restart();
|
void restart();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <PsychicHttp.h>
|
#include <esp_http_server.h>
|
||||||
#include <template/stateful_service.h>
|
#include <template/stateful_service.h>
|
||||||
|
#include <utils/http_utils.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
@@ -20,29 +21,27 @@ class StatefulHttpEndpoint {
|
|||||||
StatefulService<T> *statefulService)
|
StatefulService<T> *statefulService)
|
||||||
: _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService) {}
|
: _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService) {}
|
||||||
|
|
||||||
esp_err_t handleStateUpdate(PsychicRequest *request, JsonVariant &json) {
|
esp_err_t handleStateUpdate(httpd_req_t *req, JsonVariant &json) {
|
||||||
JsonVariant jsonObject = json.as<JsonVariant>();
|
JsonVariant jsonObject = json.as<JsonVariant>();
|
||||||
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
|
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
|
||||||
|
|
||||||
if (outcome == StateUpdateResult::ERROR)
|
if (outcome == StateUpdateResult::ERROR) {
|
||||||
return request->reply(400);
|
return http_utils::send_error(req, 400);
|
||||||
else if ((outcome == StateUpdateResult::CHANGED)) {
|
} else if ((outcome == StateUpdateResult::CHANGED)) {
|
||||||
// persist the changes to the FS
|
|
||||||
_statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID);
|
_statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request, false);
|
JsonDocument doc;
|
||||||
jsonObject = response.getRoot();
|
jsonObject = doc.to<JsonVariant>();
|
||||||
|
|
||||||
_statefulService->read(jsonObject, _stateReader);
|
_statefulService->read(jsonObject, _stateReader);
|
||||||
|
|
||||||
return response.send();
|
return http_utils::send_json_response(req, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t getState(PsychicRequest *request) {
|
esp_err_t getState(httpd_req_t *req) {
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request, false);
|
JsonDocument doc;
|
||||||
JsonVariant jsonObject = response.getRoot();
|
JsonVariant jsonObject = doc.to<JsonVariant>();
|
||||||
_statefulService->read(jsonObject, _stateReader);
|
_statefulService->read(jsonObject, _stateReader);
|
||||||
return response.send();
|
return http_utils::send_json_response(req, doc);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
namespace http_utils {
|
||||||
|
|
||||||
|
esp_err_t send_json_response(httpd_req_t *req, JsonDocument &doc, int status_code = 200);
|
||||||
|
|
||||||
|
esp_err_t send_error(httpd_req_t *req, int status_code, const char *message = nullptr);
|
||||||
|
|
||||||
|
esp_err_t send_empty_response(httpd_req_t *req, int status_code = 200);
|
||||||
|
|
||||||
|
esp_err_t add_cors_headers(httpd_req_t *req);
|
||||||
|
|
||||||
|
esp_err_t add_standard_headers(httpd_req_t *req);
|
||||||
|
|
||||||
|
esp_err_t parse_json_body(httpd_req_t *req, JsonDocument &doc);
|
||||||
|
|
||||||
|
esp_err_t handle_options_cors(httpd_req_t *req);
|
||||||
|
|
||||||
|
const char *get_client_ip(httpd_req_t *req);
|
||||||
|
|
||||||
|
} // namespace http_utils
|
||||||
@@ -39,49 +39,3 @@
|
|||||||
name##_count = 0; \
|
name##_count = 0; \
|
||||||
last_time = esp_timer_get_time() / 1000; \
|
last_time = esp_timer_get_time() / 1000; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CALLS_PER_SECOND_TIMED_START_TICK(name, function) \
|
|
||||||
static uint64_t name##_##function##_start = 0; \
|
|
||||||
static uint64_t name##_##function##_total_time = 0; \
|
|
||||||
static uint64_t name##_##function##_call_count = 0; \
|
|
||||||
name##_##function##_start = esp_timer_get_time();
|
|
||||||
|
|
||||||
#define CALLS_PER_SECOND_TIMED_END_TICK(name, function) \
|
|
||||||
name##_##function##_total_time += (esp_timer_get_time() - name##_##function##_start); \
|
|
||||||
name##_##function##_call_count++;
|
|
||||||
|
|
||||||
#define CALLS_PER_SECOND_TIMED_CALL(name, function, call) \
|
|
||||||
static uint64_t name##_##function##_total_time = 0; \
|
|
||||||
static uint64_t name##_##function##_call_count = 0; \
|
|
||||||
do { \
|
|
||||||
uint64_t name##_##function##_start = esp_timer_get_time(); \
|
|
||||||
call; \
|
|
||||||
name##_##function##_total_time += (esp_timer_get_time() - name##_##function##_start); \
|
|
||||||
name##_##function##_call_count++; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define CALLS_PER_SECOND_TIMED_FUNC_PRINT(name, function) \
|
|
||||||
if (name##_##function##_call_count > 0) { \
|
|
||||||
uint64_t avg = name##_##function##_total_time / name##_##function##_call_count; \
|
|
||||||
if (avg < 1000) { \
|
|
||||||
ESP_LOGI("Timing", " %s: %llu us (avg over %llu calls)", \
|
|
||||||
#function, avg, name##_##function##_call_count); \
|
|
||||||
} else { \
|
|
||||||
ESP_LOGI("Timing", " %s: %llu ms (avg over %llu calls)", \
|
|
||||||
#function, avg / 1000, name##_##function##_call_count); \
|
|
||||||
} \
|
|
||||||
name##_##function##_total_time = 0; \
|
|
||||||
name##_##function##_call_count = 0; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CALLS_PER_SECOND_TIMED(name, ...) \
|
|
||||||
do { \
|
|
||||||
static uint64_t name##_last_print = 0; \
|
|
||||||
uint64_t name##_current_time = esp_timer_get_time() / 1000; \
|
|
||||||
if (name##_current_time - name##_last_print >= 1000) { \
|
|
||||||
ESP_LOGI("Timing", "=== %s Average Timings ===", #name); \
|
|
||||||
__VA_ARGS__ \
|
|
||||||
name##_last_print = name##_current_time; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
#include <map>
|
||||||
|
#include <list>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace websocket {
|
||||||
|
|
||||||
|
struct WebSocketClient {
|
||||||
|
int fd;
|
||||||
|
uint64_t last_seen;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::function<void(int fd)> ClientCallback;
|
||||||
|
typedef std::function<esp_err_t(httpd_req_t *req, httpd_ws_frame_t *frame)> FrameCallback;
|
||||||
|
|
||||||
|
class WebSocketServer {
|
||||||
|
public:
|
||||||
|
WebSocketServer();
|
||||||
|
~WebSocketServer();
|
||||||
|
|
||||||
|
void setOpenCallback(ClientCallback callback);
|
||||||
|
void setCloseCallback(ClientCallback callback);
|
||||||
|
void setFrameCallback(FrameCallback callback);
|
||||||
|
|
||||||
|
esp_err_t handleWebSocket(httpd_req_t *req);
|
||||||
|
|
||||||
|
void addClient(int fd);
|
||||||
|
void removeClient(int fd);
|
||||||
|
WebSocketClient *getClient(int fd);
|
||||||
|
|
||||||
|
esp_err_t sendText(int fd, const char *data, size_t len);
|
||||||
|
esp_err_t sendBinary(int fd, const uint8_t *data, size_t len);
|
||||||
|
esp_err_t sendToAll(httpd_ws_type_t type, const uint8_t *data, size_t len);
|
||||||
|
|
||||||
|
bool hasClients() const { return !_clients.empty(); }
|
||||||
|
size_t clientCount() const { return _clients.size(); }
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setServer(httpd_handle_t server) { _server = server; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<int, WebSocketClient> _clients;
|
||||||
|
ClientCallback _onOpen;
|
||||||
|
ClientCallback _onClose;
|
||||||
|
FrameCallback _onFrame;
|
||||||
|
SemaphoreHandle_t _mutex;
|
||||||
|
httpd_handle_t _server;
|
||||||
|
|
||||||
|
friend esp_err_t websocket_handler(httpd_req_t *req);
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t websocket_handler(httpd_req_t *req);
|
||||||
|
|
||||||
|
} // namespace websocket
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <PsychicHttp.h>
|
#include <esp_http_server.h>
|
||||||
#include <WiFi.h>
|
#include <esp_wifi.h>
|
||||||
#include <ESPmDNS.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <filesystem.h>
|
#include <filesystem.h>
|
||||||
#include <utils/timing.h>
|
#include <utils/timing.h>
|
||||||
|
#include <utils/http_utils.h>
|
||||||
#include <template/stateful_service.h>
|
#include <template/stateful_service.h>
|
||||||
#include <template/stateful_persistence.h>
|
#include <template/stateful_persistence.h>
|
||||||
#include <template/stateful_endpoint.h>
|
#include <template/stateful_endpoint.h>
|
||||||
@@ -43,9 +43,9 @@ class WiFiService : public StatefulService<WiFiSettings> {
|
|||||||
|
|
||||||
const char *getHostname() { return state().hostname.c_str(); }
|
const char *getHostname() { return state().hostname.c_str(); }
|
||||||
|
|
||||||
static esp_err_t handleScan(PsychicRequest *request);
|
static esp_err_t handleScan(httpd_req_t *req);
|
||||||
static esp_err_t getNetworks(PsychicRequest *request);
|
static esp_err_t getNetworks(httpd_req_t *req);
|
||||||
static esp_err_t getNetworkStatus(PsychicRequest *request);
|
static esp_err_t getNetworkStatus(httpd_req_t *req);
|
||||||
|
|
||||||
StatefulHttpEndpoint<WiFiSettings> endpoint;
|
StatefulHttpEndpoint<WiFiSettings> endpoint;
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <PsychicHttp.h>
|
#include <esp_http_server.h>
|
||||||
#include "WWWData.h"
|
#include "WWWData.h"
|
||||||
|
|
||||||
void mountStaticAssets(PsychicHttpServer& s);
|
void mountStaticAssets(httpd_handle_t server);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <ap_service.h>
|
#include <ap_service.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
|
||||||
static const char *TAG = "APService";
|
static const char *TAG = "APService";
|
||||||
|
|
||||||
@@ -12,11 +13,11 @@ APService::~APService() {}
|
|||||||
|
|
||||||
void APService::begin() { _persistence.readFromFS(); }
|
void APService::begin() { _persistence.readFromFS(); }
|
||||||
|
|
||||||
esp_err_t APService::getStatus(PsychicRequest *request) {
|
esp_err_t APService::getStatus(httpd_req_t *req) {
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request, false);
|
JsonDocument doc;
|
||||||
JsonObject root = response.getRoot();
|
JsonObject root = doc.to<JsonObject>();
|
||||||
status(root);
|
status(root);
|
||||||
return response.send();
|
return http_utils::send_json_response(req, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void APService::status(JsonObject &root) {
|
void APService::status(JsonObject &root) {
|
||||||
|
|||||||
@@ -3,13 +3,25 @@
|
|||||||
|
|
||||||
static const char *TAG = "Websocket";
|
static const char *TAG = "Websocket";
|
||||||
|
|
||||||
Websocket::Websocket(PsychicHttpServer &server, const char *route) : _server(server), _route(route) {
|
Websocket::Websocket(httpd_handle_t *server, const char *route) : _server(server), _route(route) {
|
||||||
_socket.onOpen((std::bind(&Websocket::onWSOpen, this, std::placeholders::_1)));
|
_socket.setOpenCallback([this](int fd) { this->onWSOpen(fd); });
|
||||||
_socket.onClose(std::bind(&Websocket::onWSClose, this, std::placeholders::_1));
|
_socket.setCloseCallback([this](int fd) { this->onWSClose(fd); });
|
||||||
_socket.onFrame(std::bind(&Websocket::onFrame, this, std::placeholders::_1, std::placeholders::_2));
|
_socket.setFrameCallback([this](httpd_req_t *req, httpd_ws_frame_t *frame) { return this->onFrame(req, frame); });
|
||||||
|
|
||||||
|
_ws_uri.uri = _route;
|
||||||
|
_ws_uri.method = HTTP_GET;
|
||||||
|
_ws_uri.handler = ws_handler_wrapper;
|
||||||
|
_ws_uri.user_ctx = this;
|
||||||
|
_ws_uri.is_websocket = true;
|
||||||
|
_ws_uri.handle_ws_control_frames = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Websocket::begin() { _server.on(_route, &_socket); }
|
void Websocket::begin() {
|
||||||
|
if (_server && *_server) {
|
||||||
|
_socket.setServer(*_server);
|
||||||
|
httpd_register_uri_handler(*_server, &_ws_uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Websocket::onEvent(std::string event, EventCallback callback) {
|
void Websocket::onEvent(std::string event, EventCallback callback) {
|
||||||
CommAdapterBase::onEvent(std::move(event), std::move(callback));
|
CommAdapterBase::onEvent(std::move(event), std::move(callback));
|
||||||
@@ -19,38 +31,44 @@ void Websocket::emit(const char *event, JsonVariant &payload, const char *origin
|
|||||||
CommAdapterBase::emit(event, payload, originId, onlyToSameOrigin);
|
CommAdapterBase::emit(event, payload, originId, onlyToSameOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Websocket::onWSOpen(PsychicWebSocketClient *client) {
|
void Websocket::onWSOpen(int fd) {
|
||||||
ESP_LOGI("EventSocket", "ws[%s][%u] connect", client->remoteIP().toString().c_str(), client->socket());
|
ESP_LOGI(TAG, "ws[%d] connect", fd);
|
||||||
ping(client->socket());
|
ping(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Websocket::onWSClose(PsychicWebSocketClient *client) {
|
void Websocket::onWSClose(int fd) {
|
||||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||||
for (auto &event_subscriptions : client_subscriptions) {
|
for (auto &event_subscriptions : client_subscriptions) {
|
||||||
event_subscriptions.second.remove(client->socket());
|
event_subscriptions.second.remove(fd);
|
||||||
}
|
}
|
||||||
xSemaphoreGive(mutex_);
|
xSemaphoreGive(mutex_);
|
||||||
ESP_LOGI("EventSocket", "ws[%s][%u] disconnect", client->remoteIP().toString().c_str(), client->socket());
|
ESP_LOGI(TAG, "ws[%d] disconnect", fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t Websocket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
esp_err_t Websocket::onFrame(httpd_req_t *req, httpd_ws_frame_t *frame) {
|
||||||
ESP_LOGV(TAG, "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString().c_str(),
|
int fd = httpd_req_to_sockfd(req);
|
||||||
request->client()->socket(), frame->type);
|
ESP_LOGV(TAG, "ws[%d] opcode[%d]", fd, frame->type);
|
||||||
|
|
||||||
if (frame->type != HTTPD_WS_TYPE_TEXT && frame->type != HTTPD_WS_TYPE_BINARY) {
|
if (frame->type != HTTPD_WS_TYPE_TEXT && frame->type != HTTPD_WS_TYPE_BINARY) {
|
||||||
ESP_LOGE(TAG, "Unsupported frame type: %d", frame->type);
|
ESP_LOGE(TAG, "Unsupported frame type: %d", frame->type);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if USE_MSGPACK
|
#if USE_PROTOBUF
|
||||||
if (frame->type == HTTPD_WS_TYPE_BINARY) {
|
if (frame->type == HTTPD_WS_TYPE_BINARY) {
|
||||||
handleIncoming(frame->payload, frame->len, request->client()->socket());
|
handleIncoming(frame->payload, frame->len, fd);
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Expected binary, got text");
|
||||||
|
}
|
||||||
|
#elif USE_MSGPACK
|
||||||
|
if (frame->type == HTTPD_WS_TYPE_BINARY) {
|
||||||
|
handleIncoming(frame->payload, frame->len, fd);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "Expected binary, got text");
|
ESP_LOGE(TAG, "Expected binary, got text");
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (frame->type == HTTPD_WS_TYPE_TEXT) {
|
if (frame->type == HTTPD_WS_TYPE_TEXT) {
|
||||||
handleIncoming(frame->payload, frame->len, request->client()->socket());
|
handleIncoming(frame->payload, frame->len, fd);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "Expected text, got binary");
|
ESP_LOGE(TAG, "Expected text, got binary");
|
||||||
}
|
}
|
||||||
@@ -60,22 +78,35 @@ esp_err_t Websocket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *f
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Websocket::send(const uint8_t *data, size_t len, int cid) {
|
void Websocket::send(const uint8_t *data, size_t len, int cid) {
|
||||||
|
if (!_server || !*_server) {
|
||||||
|
ESP_LOGW(TAG, "Server not initialized, cannot send");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_ws_type_t type;
|
||||||
|
#if USE_PROTOBUF || USE_MSGPACK
|
||||||
|
type = HTTPD_WS_TYPE_BINARY;
|
||||||
|
#else
|
||||||
|
type = HTTPD_WS_TYPE_TEXT;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (cid != -1) {
|
if (cid != -1) {
|
||||||
auto *client = _socket.getClient(cid);
|
ESP_LOGV(TAG, "Sending to client %d: %.*s", cid, (int)len, data);
|
||||||
if (client) {
|
|
||||||
ESP_LOGV(TAG, "Sending to client %s: %s", client->remoteIP().toString().c_str(), data);
|
httpd_ws_frame_t ws_pkt;
|
||||||
#if USE_MSGPACK
|
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||||
client->sendMessage(HTTPD_WS_TYPE_BINARY, data, len);
|
ws_pkt.payload = (uint8_t *)data;
|
||||||
#else
|
ws_pkt.len = len;
|
||||||
client->sendMessage(HTTPD_WS_TYPE_TEXT, data, len);
|
ws_pkt.type = type;
|
||||||
#endif
|
|
||||||
}
|
httpd_ws_send_frame_async(*_server, cid, &ws_pkt);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Sending to all clients: %s", data);
|
ESP_LOGV(TAG, "Sending to all clients: %.*s", (int)len, data);
|
||||||
#if USE_MSGPACK
|
_socket.sendToAll(type, data, len);
|
||||||
_socket.sendAll(HTTPD_WS_TYPE_BINARY, data, len);
|
|
||||||
#else
|
|
||||||
_socket.sendAll(HTTPD_WS_TYPE_TEXT, data, len);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t Websocket::ws_handler_wrapper(httpd_req_t *req) {
|
||||||
|
Websocket *socket = (Websocket *)req->user_ctx;
|
||||||
|
return socket->_socket.handleWebSocket(req);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <features.h>
|
#include <features.h>
|
||||||
|
#include <utils/http_utils.h>
|
||||||
|
|
||||||
namespace feature_service {
|
namespace feature_service {
|
||||||
|
|
||||||
@@ -48,11 +49,11 @@ void features(JsonObject &root) {
|
|||||||
root["variant"] = KINEMATICS_VARIANT_STR;
|
root["variant"] = KINEMATICS_VARIANT_STR;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t getFeatures(PsychicRequest *request) {
|
esp_err_t getFeatures(httpd_req_t *req) {
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request, false);
|
JsonDocument doc;
|
||||||
JsonObject root = response.getRoot();
|
JsonObject root = doc.to<JsonObject>();
|
||||||
features(root);
|
features(root);
|
||||||
return response.send();
|
return http_utils::send_json_response(req, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace feature_service
|
} // namespace feature_service
|
||||||
+116
-70
@@ -1,8 +1,6 @@
|
|||||||
#include <Arduino.h>
|
#include <esp_http_server.h>
|
||||||
#include <PsychicHttp.h>
|
#include <esp_log.h>
|
||||||
#include <ESPmDNS.h>
|
#include <driver/uart.h>
|
||||||
#include <WiFi.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
|
|
||||||
#include <filesystem.h>
|
#include <filesystem.h>
|
||||||
#include <peripherals/peripherals.h>
|
#include <peripherals/peripherals.h>
|
||||||
@@ -16,14 +14,13 @@
|
|||||||
#include <ap_service.h>
|
#include <ap_service.h>
|
||||||
#include <mdns_service.h>
|
#include <mdns_service.h>
|
||||||
#include <system_service.h>
|
#include <system_service.h>
|
||||||
|
#include <utils/http_utils.h>
|
||||||
|
|
||||||
#include <www_mount.hpp>
|
#include <www_mount.hpp>
|
||||||
|
|
||||||
// Communication
|
httpd_handle_t server = NULL;
|
||||||
PsychicHttpServer server;
|
Websocket socket(&server, "/api/ws");
|
||||||
Websocket socket {server, "/api/ws"};
|
|
||||||
|
|
||||||
// Core
|
|
||||||
Peripherals peripherals;
|
Peripherals peripherals;
|
||||||
ServoController servoController;
|
ServoController servoController;
|
||||||
MotionService motionService;
|
MotionService motionService;
|
||||||
@@ -34,47 +31,9 @@ LEDService ledService;
|
|||||||
Camera::CameraService cameraService;
|
Camera::CameraService cameraService;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Service
|
|
||||||
WiFiService wifiService;
|
WiFiService wifiService;
|
||||||
APService apService;
|
APService apService;
|
||||||
|
|
||||||
void setupServer() {
|
|
||||||
server.config.max_uri_handlers = 10 + 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
|
|
||||||
server.on("/api/servo/config", HTTP_GET,
|
|
||||||
[&](PsychicRequest *request) { return servoController.endpoint.getState(request); });
|
|
||||||
server.on("/api/servo/config", HTTP_POST, [&](PsychicRequest *request, JsonVariant &json) {
|
|
||||||
return servoController.endpoint.handleStateUpdate(request, json);
|
|
||||||
});
|
|
||||||
#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 ANGLES_EVENT "angles"
|
||||||
#define INPUT_EVENT "input"
|
#define INPUT_EVENT "input"
|
||||||
#define MODE_EVENT "mode"
|
#define MODE_EVENT "mode"
|
||||||
@@ -83,8 +42,103 @@ void setupServer() {
|
|||||||
#define EVENT_SERVO_CONFIGURATION_SETTINGS "servoPWM"
|
#define EVENT_SERVO_CONFIGURATION_SETTINGS "servoPWM"
|
||||||
#define EVENT_SERVO_STATE "servoState"
|
#define EVENT_SERVO_STATE "servoState"
|
||||||
|
|
||||||
|
esp_err_t cors_options_handler(httpd_req_t *req) { return http_utils::handle_options_cors(req); }
|
||||||
|
|
||||||
|
esp_err_t servo_config_get_handler(httpd_req_t *req) { return servoController.endpoint.getState(req); }
|
||||||
|
|
||||||
|
esp_err_t servo_config_post_handler(httpd_req_t *req) {
|
||||||
|
JsonDocument doc;
|
||||||
|
if (http_utils::parse_json_body(req, doc) != ESP_OK) {
|
||||||
|
return http_utils::send_error(req, 400, "Invalid JSON");
|
||||||
|
}
|
||||||
|
JsonVariant json = doc.as<JsonVariant>();
|
||||||
|
return servoController.endpoint.handleStateUpdate(req, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if USE_CAMERA
|
||||||
|
esp_err_t camera_still_handler(httpd_req_t *req) { return cameraService.cameraStill(req); }
|
||||||
|
|
||||||
|
esp_err_t camera_stream_handler(httpd_req_t *req) { return cameraService.cameraStream(req); }
|
||||||
|
|
||||||
|
esp_err_t camera_settings_get_handler(httpd_req_t *req) { return cameraService.endpoint.getState(req); }
|
||||||
|
|
||||||
|
esp_err_t camera_settings_post_handler(httpd_req_t *req) {
|
||||||
|
JsonDocument doc;
|
||||||
|
if (http_utils::parse_json_body(req, doc) != ESP_OK) {
|
||||||
|
return http_utils::send_error(req, 400, "Invalid JSON");
|
||||||
|
}
|
||||||
|
JsonVariant json = doc.as<JsonVariant>();
|
||||||
|
return cameraService.endpoint.handleStateUpdate(req, json);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setupServer() {
|
||||||
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
|
config.max_uri_handlers = 20 + WWW_ASSETS_COUNT;
|
||||||
|
config.stack_size = 8192;
|
||||||
|
config.max_open_sockets = 7;
|
||||||
|
config.lru_purge_enable = true;
|
||||||
|
|
||||||
|
if (httpd_start(&server, &config) == ESP_OK) {
|
||||||
|
ESP_LOGI("main", "HTTP server started");
|
||||||
|
|
||||||
|
httpd_uri_t config_static = {.uri = "/api/config/*",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = [](httpd_req_t *req) -> esp_err_t {
|
||||||
|
return http_utils::send_error(req, 501,
|
||||||
|
"Static file serving not yet implemented");
|
||||||
|
},
|
||||||
|
.user_ctx = nullptr};
|
||||||
|
httpd_register_uri_handler(server, &config_static);
|
||||||
|
|
||||||
|
httpd_uri_t features_uri = {
|
||||||
|
.uri = "/api/features", .method = HTTP_GET, .handler = feature_service::getFeatures, .user_ctx = nullptr};
|
||||||
|
httpd_register_uri_handler(server, &features_uri);
|
||||||
|
|
||||||
|
#if USE_CAMERA
|
||||||
|
httpd_uri_t camera_still_uri = {
|
||||||
|
.uri = "/api/camera/still", .method = HTTP_GET, .handler = camera_still_handler, .user_ctx = nullptr};
|
||||||
|
httpd_register_uri_handler(server, &camera_still_uri);
|
||||||
|
|
||||||
|
httpd_uri_t camera_stream_uri = {
|
||||||
|
.uri = "/api/camera/stream", .method = HTTP_GET, .handler = camera_stream_handler, .user_ctx = nullptr};
|
||||||
|
httpd_register_uri_handler(server, &camera_stream_uri);
|
||||||
|
|
||||||
|
httpd_uri_t camera_settings_get_uri = {.uri = "/api/camera/settings",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = camera_settings_get_handler,
|
||||||
|
.user_ctx = nullptr};
|
||||||
|
httpd_register_uri_handler(server, &camera_settings_get_uri);
|
||||||
|
|
||||||
|
httpd_uri_t camera_settings_post_uri = {.uri = "/api/camera/settings",
|
||||||
|
.method = HTTP_POST,
|
||||||
|
.handler = camera_settings_post_handler,
|
||||||
|
.user_ctx = nullptr};
|
||||||
|
httpd_register_uri_handler(server, &camera_settings_post_uri);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
httpd_uri_t servo_config_get_uri = {
|
||||||
|
.uri = "/api/servo/config", .method = HTTP_GET, .handler = servo_config_get_handler, .user_ctx = nullptr};
|
||||||
|
httpd_register_uri_handler(server, &servo_config_get_uri);
|
||||||
|
|
||||||
|
httpd_uri_t servo_config_post_uri = {
|
||||||
|
.uri = "/api/servo/config", .method = HTTP_POST, .handler = servo_config_post_handler, .user_ctx = nullptr};
|
||||||
|
httpd_register_uri_handler(server, &servo_config_post_uri);
|
||||||
|
|
||||||
|
#if EMBED_WEBAPP
|
||||||
|
mountStaticAssets(server);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
httpd_uri_t options_uri = {
|
||||||
|
.uri = "/*", .method = HTTP_OPTIONS, .handler = cors_options_handler, .user_ctx = nullptr};
|
||||||
|
httpd_register_uri_handler(server, &options_uri);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ESP_LOGE("main", "Failed to start HTTP server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setupEventSocket() {
|
void setupEventSocket() {
|
||||||
// Motion events
|
|
||||||
socket.onEvent(INPUT_EVENT, [&](JsonVariant &root, int originId) { motionService.handleInput(root, originId); });
|
socket.onEvent(INPUT_EVENT, [&](JsonVariant &root, int originId) { motionService.handleInput(root, originId); });
|
||||||
|
|
||||||
socket.onEvent(MODE_EVENT, [&](JsonVariant &root, int originId) {
|
socket.onEvent(MODE_EVENT, [&](JsonVariant &root, int originId) {
|
||||||
@@ -98,7 +152,6 @@ void setupEventSocket() {
|
|||||||
|
|
||||||
socket.onEvent(ANGLES_EVENT, [&](JsonVariant &root, int originId) { motionService.anglesEvent(root, originId); });
|
socket.onEvent(ANGLES_EVENT, [&](JsonVariant &root, int originId) { motionService.anglesEvent(root, originId); });
|
||||||
|
|
||||||
// Peripherals events
|
|
||||||
socket.onEvent(EVENT_I2C_SCAN, [&](JsonVariant &root, int originId) {
|
socket.onEvent(EVENT_I2C_SCAN, [&](JsonVariant &root, int originId) {
|
||||||
peripherals.scanI2C();
|
peripherals.scanI2C();
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
@@ -107,15 +160,15 @@ void setupEventSocket() {
|
|||||||
socket.emit(EVENT_I2C_SCAN, results);
|
socket.emit(EVENT_I2C_SCAN, results);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Servo controller events
|
|
||||||
socket.onEvent(EVENT_SERVO_CONFIGURATION_SETTINGS,
|
socket.onEvent(EVENT_SERVO_CONFIGURATION_SETTINGS,
|
||||||
[&](JsonVariant &root, int originId) { servoController.servoEvent(root, originId); });
|
[&](JsonVariant &root, int originId) { servoController.servoEvent(root, originId); });
|
||||||
|
|
||||||
socket.onEvent(EVENT_SERVO_STATE,
|
socket.onEvent(EVENT_SERVO_STATE,
|
||||||
[&](JsonVariant &root, int originId) { servoController.stateUpdate(root, originId); });
|
[&](JsonVariant &root, int originId) { servoController.stateUpdate(root, originId); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void IRAM_ATTR SpotControlLoopEntry(void *) {
|
void IRAM_ATTR SpotControlLoopEntry(void *) {
|
||||||
ESP_LOGI("main", "Setup complete now runing tsk");
|
ESP_LOGI("main", "Setup complete now running control task");
|
||||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||||
const TickType_t xFrequency = 5 / portTICK_PERIOD_MS;
|
const TickType_t xFrequency = 5 / portTICK_PERIOD_MS;
|
||||||
|
|
||||||
@@ -124,20 +177,15 @@ void IRAM_ATTR SpotControlLoopEntry(void *) {
|
|||||||
motionService.begin();
|
motionService.begin();
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
CALLS_PER_SECOND_TIMED_CALL(SpotControlLoopEntry, peripherals_update, peripherals.update());
|
CALLS_PER_SECOND(SpotControlLoopEntry);
|
||||||
CALLS_PER_SECOND_TIMED_CALL(SpotControlLoopEntry, motionService_update, motionService.update(&peripherals));
|
peripherals.update();
|
||||||
CALLS_PER_SECOND_TIMED_CALL(SpotControlLoopEntry, servoController_setAngles, servoController.setAngles(motionService.getAngles()));
|
motionService.update(&peripherals);
|
||||||
CALLS_PER_SECOND_TIMED_CALL(SpotControlLoopEntry, servoController_update, servoController.update());
|
servoController.setAngles(motionService.getAngles());
|
||||||
|
servoController.update();
|
||||||
#if FT_ENABLED(USE_WS2812)
|
#if FT_ENABLED(USE_WS2812)
|
||||||
ledService.loop();
|
ledService.loop();
|
||||||
#endif
|
#endif
|
||||||
// CALLS_PER_SECOND_TIMED(SpotControlLoopEntry,
|
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||||
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, peripherals_update)
|
|
||||||
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, motionService_update)
|
|
||||||
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, servoController_setAngles)
|
|
||||||
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, servoController_update)
|
|
||||||
// );
|
|
||||||
// vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,8 +193,7 @@ void IRAM_ATTR serviceLoopEntry(void *) {
|
|||||||
ESP_LOGI("main", "Service control task starting");
|
ESP_LOGI("main", "Service control task starting");
|
||||||
|
|
||||||
wifiService.begin();
|
wifiService.begin();
|
||||||
MDNS.begin(APP_NAME);
|
mdns_service::begin(APP_NAME);
|
||||||
MDNS.setInstanceName(APP_NAME);
|
|
||||||
apService.begin();
|
apService.begin();
|
||||||
|
|
||||||
#if FT_ENABLED(USE_CAMERA)
|
#if FT_ENABLED(USE_CAMERA)
|
||||||
@@ -174,8 +221,9 @@ void IRAM_ATTR serviceLoopEntry(void *) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
extern "C" void app_main() {
|
||||||
Serial.begin(115200);
|
uart_driver_install(UART_NUM_0, 256, 0, 0, NULL, 0);
|
||||||
|
esp_log_level_set("*", ESP_LOG_INFO);
|
||||||
|
|
||||||
ESP_FS.begin();
|
ESP_FS.begin();
|
||||||
|
|
||||||
@@ -183,11 +231,9 @@ void setup() {
|
|||||||
|
|
||||||
feature_service::printFeatureConfiguration();
|
feature_service::printFeatureConfiguration();
|
||||||
|
|
||||||
xTaskCreate(serviceLoopEntry, "Service task", 4096, nullptr, 2, nullptr);
|
xTaskCreate(serviceLoopEntry, "Service task", 8192, nullptr, 2, nullptr);
|
||||||
|
|
||||||
xTaskCreatePinnedToCore(SpotControlLoopEntry, "Control task", 4096, nullptr, 5, nullptr, 1);
|
xTaskCreatePinnedToCore(SpotControlLoopEntry, "Control task", 8192, nullptr, 5, nullptr, 1);
|
||||||
|
|
||||||
ESP_LOGI("main", "Finished booting");
|
ESP_LOGI("main", "Finished booting");
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() { vTaskDelete(nullptr); }
|
|
||||||
+34
-79
@@ -2,99 +2,54 @@
|
|||||||
|
|
||||||
static const char *TAG = "MDNSService";
|
static const char *TAG = "MDNSService";
|
||||||
|
|
||||||
MDNSService::MDNSService()
|
namespace mdns_service {
|
||||||
: _persistence(MDNSSettings::read, MDNSSettings::update, this, MDNS_SETTINGS_FILE),
|
|
||||||
endpoint(MDNSSettings::read, MDNSSettings::update, this) {
|
void begin(const char *hostname) {
|
||||||
addUpdateHandler([&](const std::string &originId) { reconfigureMDNS(); }, false);
|
ESP_LOGI(TAG, "Starting mDNS with hostname: %s", hostname);
|
||||||
|
|
||||||
|
esp_err_t err = mdns_init();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "mDNS init failed: %d", err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MDNSService::~MDNSService() {
|
err = mdns_hostname_set(hostname);
|
||||||
if (_started) {
|
if (err != ESP_OK) {
|
||||||
stopMDNS();
|
ESP_LOGE(TAG, "Failed to set hostname: %d", err);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MDNSService::begin() {
|
err = mdns_instance_name_set(hostname);
|
||||||
_persistence.readFromFS();
|
if (err != ESP_OK) {
|
||||||
startMDNS();
|
ESP_LOGE(TAG, "Failed to set instance name: %d", err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MDNSService::reconfigureMDNS() {
|
addService("http", "_tcp", 80);
|
||||||
if (_started) {
|
addService("ws", "_tcp", 80);
|
||||||
stopMDNS();
|
addServiceTxt("http", "_tcp", "version", APP_VERSION);
|
||||||
}
|
|
||||||
startMDNS();
|
ESP_LOGI(TAG, "mDNS started successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MDNSService::startMDNS() {
|
void end() { mdns_free(); }
|
||||||
ESP_LOGV(TAG, "Starting MDNS with hostname: %s", state().hostname.c_str());
|
|
||||||
|
|
||||||
if (MDNS.begin(state().hostname.c_str())) {
|
void addService(const char *service, const char *proto, uint16_t port) {
|
||||||
_started = true;
|
esp_err_t err = mdns_service_add(NULL, service, proto, port, NULL, 0);
|
||||||
MDNS.setInstanceName(state().instance.c_str());
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to add service %s.%s: %d", service, proto, err);
|
||||||
addServices();
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "MDNS started successfully with hostname: %s", state().hostname.c_str());
|
|
||||||
} else {
|
} else {
|
||||||
_started = false;
|
ESP_LOGI(TAG, "Added mDNS service: %s.%s on port %d", service, proto, port);
|
||||||
ESP_LOGE(TAG, "Failed to start MDNS");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MDNSService::stopMDNS() {
|
void addServiceTxt(const char *service, const char *proto, const char *key, const char *value) {
|
||||||
ESP_LOGV(TAG, "Stopping MDNS");
|
mdns_txt_item_t txt_data[1] = {{(char *)key, (char *)value}};
|
||||||
MDNS.end();
|
|
||||||
_started = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MDNSService::addServices() {
|
esp_err_t err = mdns_service_txt_set(service, proto, txt_data, 1);
|
||||||
for (const auto &service : state().services) {
|
if (err != ESP_OK) {
|
||||||
MDNS.addService(service.service.c_str(), service.protocol.c_str(), service.port);
|
ESP_LOGE(TAG, "Failed to set TXT record for %s.%s: %d", service, proto, err);
|
||||||
|
|
||||||
for (const auto &txt : service.txtRecords) {
|
|
||||||
MDNS.addServiceTxt(service.service.c_str(), service.protocol.c_str(), txt.key.c_str(), txt.value.c_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &txt : state().globalTxtRecords) {
|
} // namespace mdns_service
|
||||||
for (const auto &service : state().services) {
|
|
||||||
MDNS.addServiceTxt(service.service.c_str(), service.protocol.c_str(), txt.key.c_str(), txt.value.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t MDNSService::getStatus(PsychicRequest *request) {
|
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request, false);
|
|
||||||
JsonVariant root = response.getRoot();
|
|
||||||
getStatus(root);
|
|
||||||
return response.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MDNSService::getStatus(JsonVariant &root) {
|
|
||||||
state().read(state(), root);
|
|
||||||
root["started"] = _started;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t MDNSService::queryServices(PsychicRequest *request, JsonVariant &json) {
|
|
||||||
std::string service = json["service"].as<std::string>();
|
|
||||||
std::string proto = json["protocol"].as<std::string>();
|
|
||||||
|
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request, false);
|
|
||||||
JsonVariant root = response.getRoot();
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Querying for service: %s, protocol: %s", service.c_str(), proto.c_str());
|
|
||||||
|
|
||||||
int n = MDNS.queryService(service.c_str(), proto.c_str());
|
|
||||||
ESP_LOGI(TAG, "Found %d services", n);
|
|
||||||
|
|
||||||
JsonArray servicesArray = root["services"].to<JsonArray>();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
JsonVariant serviceObj = servicesArray.add<JsonVariant>();
|
|
||||||
serviceObj["name"] = MDNS.hostname(i);
|
|
||||||
serviceObj["ip"] = MDNS.IP(i);
|
|
||||||
serviceObj["port"] = MDNS.port(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.send();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -85,44 +85,40 @@ esp_err_t CameraService::begin() {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t CameraService::cameraStill(PsychicRequest *request) {
|
esp_err_t CameraService::cameraStill(httpd_req_t *req) {
|
||||||
camera_fb_t *fb = safe_camera_fb_get();
|
camera_fb_t *fb = safe_camera_fb_get();
|
||||||
if (!fb) {
|
if (!fb) {
|
||||||
ESP_LOGE(TAG, "Camera capture failed");
|
ESP_LOGE(TAG, "Camera capture failed");
|
||||||
request->reply(500, "text/plain", "Camera capture failed");
|
return http_utils::send_error(req, 500, "Camera capture failed");
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
}
|
||||||
PsychicStreamResponse response = PsychicStreamResponse(request, "image/jpeg", "capture.jpg");
|
|
||||||
response.beginSend();
|
httpd_resp_set_type(req, "image/jpeg");
|
||||||
response.write(fb->buf, fb->len);
|
httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
|
||||||
|
|
||||||
|
esp_err_t res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
|
||||||
esp_camera_fb_return(fb);
|
esp_camera_fb_return(fb);
|
||||||
return response.endSend();
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void streamTask(void *pv) {
|
void streamTask(void *pv) {
|
||||||
esp_err_t res = ESP_OK;
|
esp_err_t res = ESP_OK;
|
||||||
|
|
||||||
PsychicRequest *request = static_cast<PsychicRequest *>(pv);
|
httpd_req_t *req = static_cast<httpd_req_t *>(pv);
|
||||||
|
|
||||||
httpd_req_t *copy = nullptr;
|
httpd_req_t *copy = nullptr;
|
||||||
res = httpd_req_async_handler_begin(request->request(), ©);
|
res = httpd_req_async_handler_begin(req, ©);
|
||||||
if (res != ESP_OK) {
|
if (res != ESP_OK) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PsychicHttpServer *server = request->server();
|
|
||||||
PsychicRequest new_request = PsychicRequest(server, copy);
|
|
||||||
request = &new_request;
|
|
||||||
|
|
||||||
PsychicStreamResponse response = PsychicStreamResponse(request, _STREAM_CONTENT_TYPE);
|
httpd_resp_set_type(copy, _STREAM_CONTENT_TYPE);
|
||||||
camera_fb_t *fb = NULL;
|
camera_fb_t *fb = NULL;
|
||||||
|
|
||||||
char *part_buf[64];
|
char part_buf[64];
|
||||||
size_t buf_len = 0;
|
size_t buf_len = 0;
|
||||||
uint8_t *buf = NULL;
|
uint8_t *buf = NULL;
|
||||||
int64_t fr_start = esp_timer_get_time();
|
int64_t fr_start = esp_timer_get_time();
|
||||||
|
|
||||||
response.beginSend();
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
fb = safe_camera_fb_get();
|
fb = safe_camera_fb_get();
|
||||||
if (!fb) {
|
if (!fb) {
|
||||||
@@ -136,26 +132,28 @@ void streamTask(void *pv) {
|
|||||||
buf = fb->buf;
|
buf = fb->buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, buf_len);
|
size_t hlen = snprintf(part_buf, 64, _STREAM_PART, buf_len);
|
||||||
size_t w = response.write((const char *)part_buf, hlen);
|
if (httpd_resp_send_chunk(copy, part_buf, hlen) != ESP_OK) break;
|
||||||
w += response.write((const char *)buf, buf_len);
|
if (httpd_resp_send_chunk(copy, (const char *)buf, buf_len) != ESP_OK) break;
|
||||||
w += response.write((char *)_STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
|
if (httpd_resp_send_chunk(copy, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)) != ESP_OK) break;
|
||||||
if (w == 62) break;
|
|
||||||
esp_camera_fb_return(fb);
|
esp_camera_fb_return(fb);
|
||||||
safe_sensor_return();
|
safe_sensor_return();
|
||||||
buf = NULL;
|
buf = NULL;
|
||||||
taskYIELD();
|
taskYIELD();
|
||||||
int64_t delay = 30000ll - esp_timer_get_time() - fr_start;
|
int64_t delay = 30000ll - (esp_timer_get_time() - fr_start);
|
||||||
if (delay > 0) vTaskDelay(pdMS_TO_TICKS(delay));
|
if (delay > 0) vTaskDelay(pdMS_TO_TICKS(delay / 1000));
|
||||||
|
fr_start = esp_timer_get_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI("Stream", "Stream ended");
|
ESP_LOGI("Stream", "Stream ended");
|
||||||
response.endSend();
|
httpd_resp_send_chunk(copy, nullptr, 0);
|
||||||
httpd_req_async_handler_complete(copy);
|
httpd_req_async_handler_complete(copy);
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t CameraService::cameraStream(PsychicRequest *request) {
|
esp_err_t CameraService::cameraStream(httpd_req_t *req) {
|
||||||
xTaskCreate(streamTask, "Stream client task", 4096, request, 4, nullptr);
|
xTaskCreate(streamTask, "Stream client task", 4096, req, 4, nullptr);
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ void Peripherals::begin() {
|
|||||||
updatePins();
|
updatePins();
|
||||||
|
|
||||||
#if FT_ENABLED(USE_ICM20948)
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
#if USE_ICM20948_SPIMODE > 0
|
||||||
ICM_20948_SPI* icm20948 = new ICM_20948_SPI;
|
ICM_20948_SPI* icm20948 = new ICM_20948_SPI;
|
||||||
#else
|
#else
|
||||||
ICM_20948_I2C* icm20948 = new ICM_20948_I2C;
|
ICM_20948_I2C* icm20948 = new ICM_20948_I2C;
|
||||||
@@ -56,25 +56,11 @@ void Peripherals::begin() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void Peripherals::update() {
|
void Peripherals::update() {
|
||||||
bool res = true;
|
readImu();
|
||||||
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_imu, res = readImu());
|
readMag();
|
||||||
#ifdef FT_ENABLED(USE_ICM20948)
|
EXECUTE_EVERY_N_MS(100, { readGesture(); });
|
||||||
// IF ICM_20948 fails to get IMU, it means that mag also does not have new data
|
EXECUTE_EVERY_N_MS(500, { readBMP(); });
|
||||||
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_mag, if (res) { res = readMag(); } );
|
EXECUTE_EVERY_N_MS(500, { readSonar(); });
|
||||||
#else
|
|
||||||
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_mag, res = readMag());
|
|
||||||
#endif
|
|
||||||
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_gesture, EXECUTE_EVERY_N_MS(100, { readGesture(); }) );
|
|
||||||
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_bmp, EXECUTE_EVERY_N_MS(500, { readBMP(); }) );
|
|
||||||
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_sonar, EXECUTE_EVERY_N_MS(500, { readSonar(); }) );
|
|
||||||
|
|
||||||
CALLS_PER_SECOND_TIMED(Peripherals_update,
|
|
||||||
CALLS_PER_SECOND_TIMED_FUNC_PRINT(Peripherals_update, read_imu)
|
|
||||||
CALLS_PER_SECOND_TIMED_FUNC_PRINT(Peripherals_update, read_mag)
|
|
||||||
CALLS_PER_SECOND_TIMED_FUNC_PRINT(Peripherals_update, read_gesture)
|
|
||||||
CALLS_PER_SECOND_TIMED_FUNC_PRINT(Peripherals_update, read_bmp)
|
|
||||||
CALLS_PER_SECOND_TIMED_FUNC_PRINT(Peripherals_update, read_sonar)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Peripherals::updatePins() {
|
void Peripherals::updatePins() {
|
||||||
@@ -84,7 +70,6 @@ void Peripherals::updatePins() {
|
|||||||
|
|
||||||
if (state().sda != -1 && state().scl != -1) {
|
if (state().sda != -1 && state().scl != -1) {
|
||||||
Wire.begin(state().sda, state().scl, state().frequency);
|
Wire.begin(state().sda, state().scl, state().frequency);
|
||||||
ESP_LOGI("Peripherals", "Starting Wire with SDA=%d, SCL=%d, FREQ=%d", state().sda, state().scl, state().frequency);
|
|
||||||
i2c_active = true;
|
i2c_active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,33 +4,33 @@ namespace system_service {
|
|||||||
|
|
||||||
static const char *TAG = "SystemService";
|
static const char *TAG = "SystemService";
|
||||||
|
|
||||||
esp_err_t handleReset(PsychicRequest *request) {
|
esp_err_t handleReset(httpd_req_t *req) {
|
||||||
reset();
|
reset();
|
||||||
return request->reply(200);
|
return http_utils::send_empty_response(req, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t handleRestart(PsychicRequest *request) {
|
esp_err_t handleRestart(httpd_req_t *req) {
|
||||||
restart();
|
restart();
|
||||||
return request->reply(200);
|
return http_utils::send_empty_response(req, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t handleSleep(PsychicRequest *request) {
|
esp_err_t handleSleep(httpd_req_t *req) {
|
||||||
sleep();
|
sleep();
|
||||||
return request->reply(200);
|
return http_utils::send_empty_response(req, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t getStatus(PsychicRequest *request) {
|
esp_err_t getStatus(httpd_req_t *req) {
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request, false);
|
JsonDocument doc;
|
||||||
JsonObject root = response.getRoot();
|
JsonObject root = doc.to<JsonObject>();
|
||||||
status(root);
|
status(root);
|
||||||
return response.send();
|
return http_utils::send_json_response(req, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t getMetrics(PsychicRequest *request) {
|
esp_err_t getMetrics(httpd_req_t *req) {
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request, false);
|
JsonDocument doc;
|
||||||
JsonObject root = response.getRoot();
|
JsonObject root = doc.to<JsonObject>();
|
||||||
metrics(root);
|
metrics(root);
|
||||||
return response.send();
|
return http_utils::send_json_response(req, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
@@ -49,12 +49,8 @@ void restart() {
|
|||||||
xTaskCreate(
|
xTaskCreate(
|
||||||
[](void *pvParameters) {
|
[](void *pvParameters) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
vTaskDelay(250 / portTICK_PERIOD_MS);
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
MDNS.end();
|
esp_restart();
|
||||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
|
||||||
WiFi.disconnect(true);
|
|
||||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
|
||||||
ESP.restart();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Restart task", 4096, nullptr, 10, nullptr);
|
"Restart task", 4096, nullptr, 10, nullptr);
|
||||||
@@ -64,11 +60,7 @@ void sleep() {
|
|||||||
xTaskCreate(
|
xTaskCreate(
|
||||||
[](void *pvParameters) {
|
[](void *pvParameters) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
vTaskDelay(250 / portTICK_PERIOD_MS);
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
MDNS.end();
|
|
||||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
|
||||||
WiFi.disconnect(true);
|
|
||||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
|
||||||
|
|
||||||
uint64_t bitmask = (uint64_t)1 << (WAKEUP_PIN_NUMBER);
|
uint64_t bitmask = (uint64_t)1 << (WAKEUP_PIN_NUMBER);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
#include <utils/http_utils.h>
|
||||||
|
#include <global.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
namespace http_utils {
|
||||||
|
|
||||||
|
static const char *TAG = "HttpUtils";
|
||||||
|
|
||||||
|
esp_err_t send_json_response(httpd_req_t *req, JsonDocument &doc, int status_code) {
|
||||||
|
add_standard_headers(req);
|
||||||
|
add_cors_headers(req);
|
||||||
|
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
|
||||||
|
if (status_code != 200) {
|
||||||
|
char status_str[4];
|
||||||
|
snprintf(status_str, sizeof(status_str), "%d", status_code);
|
||||||
|
httpd_resp_set_status(req, status_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
String json_str;
|
||||||
|
serializeJson(doc, json_str);
|
||||||
|
|
||||||
|
return httpd_resp_send(req, json_str.c_str(), json_str.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t send_error(httpd_req_t *req, int status_code, const char *message) {
|
||||||
|
add_standard_headers(req);
|
||||||
|
add_cors_headers(req);
|
||||||
|
|
||||||
|
char status_str[4];
|
||||||
|
snprintf(status_str, sizeof(status_str), "%d", status_code);
|
||||||
|
httpd_resp_set_status(req, status_str);
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
httpd_resp_set_type(req, "text/plain");
|
||||||
|
return httpd_resp_send(req, message, strlen(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpd_resp_send(req, nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t send_empty_response(httpd_req_t *req, int status_code) {
|
||||||
|
add_standard_headers(req);
|
||||||
|
add_cors_headers(req);
|
||||||
|
|
||||||
|
if (status_code != 200) {
|
||||||
|
char status_str[4];
|
||||||
|
snprintf(status_str, sizeof(status_str), "%d", status_code);
|
||||||
|
httpd_resp_set_status(req, status_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpd_resp_send(req, nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t add_cors_headers(httpd_req_t *req) {
|
||||||
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
|
||||||
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
||||||
|
httpd_resp_set_hdr(req, "Access-Control-Max-Age", "86400");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t add_standard_headers(httpd_req_t *req) {
|
||||||
|
httpd_resp_set_hdr(req, "Server", APP_NAME);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t parse_json_body(httpd_req_t *req, JsonDocument &doc) {
|
||||||
|
size_t content_len = req->content_len;
|
||||||
|
|
||||||
|
if (content_len == 0) {
|
||||||
|
ESP_LOGW(TAG, "Empty request body");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content_len > 16384) {
|
||||||
|
ESP_LOGE(TAG, "Request body too large: %d bytes", content_len);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *buf = (char *)malloc(content_len + 1);
|
||||||
|
if (!buf) {
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate memory for request body");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = httpd_req_recv(req, buf, content_len);
|
||||||
|
if (ret <= 0) {
|
||||||
|
free(buf);
|
||||||
|
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||||
|
ESP_LOGE(TAG, "Socket timeout");
|
||||||
|
return ESP_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[ret] = '\0';
|
||||||
|
|
||||||
|
DeserializationError error = deserializeJson(doc, buf, ret);
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
ESP_LOGE(TAG, "JSON parse error: %s", error.c_str());
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t handle_options_cors(httpd_req_t *req) { return send_empty_response(req, 200); }
|
||||||
|
|
||||||
|
const char *get_client_ip(httpd_req_t *req) {
|
||||||
|
int sockfd = httpd_req_to_sockfd(req);
|
||||||
|
struct sockaddr_in6 addr;
|
||||||
|
socklen_t addr_size = sizeof(addr);
|
||||||
|
|
||||||
|
if (getpeername(sockfd, (struct sockaddr *)&addr, &addr_size) < 0) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
static char ip_str[INET6_ADDRSTRLEN];
|
||||||
|
if (addr.sin6_family == AF_INET) {
|
||||||
|
struct sockaddr_in *addr_in = (struct sockaddr_in *)&addr;
|
||||||
|
snprintf(ip_str, sizeof(ip_str), "%d.%d.%d.%d", (addr_in->sin_addr.s_addr >> 0) & 0xFF,
|
||||||
|
(addr_in->sin_addr.s_addr >> 8) & 0xFF, (addr_in->sin_addr.s_addr >> 16) & 0xFF,
|
||||||
|
(addr_in->sin_addr.s_addr >> 24) & 0xFF);
|
||||||
|
} else {
|
||||||
|
inet_ntop(AF_INET6, &addr.sin6_addr, ip_str, sizeof(ip_str));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace http_utils
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
#include <utils/websocket_server.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace websocket {
|
||||||
|
|
||||||
|
static const char *TAG = "WebSocketServer";
|
||||||
|
|
||||||
|
WebSocketServer::WebSocketServer() : _server(nullptr) { _mutex = xSemaphoreCreateMutex(); }
|
||||||
|
|
||||||
|
WebSocketServer::~WebSocketServer() {
|
||||||
|
if (_mutex) {
|
||||||
|
vSemaphoreDelete(_mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketServer::setOpenCallback(ClientCallback callback) { _onOpen = callback; }
|
||||||
|
|
||||||
|
void WebSocketServer::setCloseCallback(ClientCallback callback) { _onClose = callback; }
|
||||||
|
|
||||||
|
void WebSocketServer::setFrameCallback(FrameCallback callback) { _onFrame = callback; }
|
||||||
|
|
||||||
|
esp_err_t WebSocketServer::handleWebSocket(httpd_req_t *req) {
|
||||||
|
if (req->method == HTTP_GET) {
|
||||||
|
int fd = httpd_req_to_sockfd(req);
|
||||||
|
addClient(fd);
|
||||||
|
ESP_LOGI(TAG, "WebSocket client connected: fd=%d", fd);
|
||||||
|
|
||||||
|
if (_onOpen) {
|
||||||
|
_onOpen(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_ws_frame_t ws_pkt;
|
||||||
|
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||||
|
|
||||||
|
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ws_pkt.len) {
|
||||||
|
ws_pkt.payload = (uint8_t *)malloc(ws_pkt.len + 1);
|
||||||
|
if (!ws_pkt.payload) {
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate memory for WebSocket payload");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
|
||||||
|
free(ws_pkt.payload);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
((uint8_t *)ws_pkt.payload)[ws_pkt.len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ws_pkt.type == HTTPD_WS_TYPE_CLOSE) {
|
||||||
|
int fd = httpd_req_to_sockfd(req);
|
||||||
|
ESP_LOGI(TAG, "WebSocket client disconnected: fd=%d", fd);
|
||||||
|
removeClient(fd);
|
||||||
|
|
||||||
|
if (_onClose) {
|
||||||
|
_onClose(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ws_pkt.payload) {
|
||||||
|
free(ws_pkt.payload);
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_onFrame) {
|
||||||
|
ret = _onFrame(req, &ws_pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ws_pkt.payload) {
|
||||||
|
free(ws_pkt.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketServer::addClient(int fd) {
|
||||||
|
xSemaphoreTake(_mutex, portMAX_DELAY);
|
||||||
|
WebSocketClient client;
|
||||||
|
client.fd = fd;
|
||||||
|
client.last_seen = esp_timer_get_time();
|
||||||
|
_clients[fd] = client;
|
||||||
|
xSemaphoreGive(_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketServer::removeClient(int fd) {
|
||||||
|
xSemaphoreTake(_mutex, portMAX_DELAY);
|
||||||
|
_clients.erase(fd);
|
||||||
|
xSemaphoreGive(_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketClient *WebSocketServer::getClient(int fd) {
|
||||||
|
xSemaphoreTake(_mutex, portMAX_DELAY);
|
||||||
|
auto it = _clients.find(fd);
|
||||||
|
WebSocketClient *client = (it != _clients.end()) ? &it->second : nullptr;
|
||||||
|
xSemaphoreGive(_mutex);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t WebSocketServer::sendText(int fd, const char *data, size_t len) {
|
||||||
|
return sendBinary(fd, (const uint8_t *)data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t WebSocketServer::sendBinary(int fd, const uint8_t *data, size_t len) {
|
||||||
|
if (!_server) {
|
||||||
|
ESP_LOGE(TAG, "Server handle not set");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_ws_frame_t ws_pkt;
|
||||||
|
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||||
|
ws_pkt.payload = (uint8_t *)data;
|
||||||
|
ws_pkt.len = len;
|
||||||
|
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||||
|
|
||||||
|
return httpd_ws_send_frame_async(_server, fd, &ws_pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t WebSocketServer::sendToAll(httpd_ws_type_t type, const uint8_t *data, size_t len) {
|
||||||
|
if (!_server) {
|
||||||
|
ESP_LOGE(TAG, "Server handle not set");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_ws_frame_t ws_pkt;
|
||||||
|
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||||
|
ws_pkt.payload = (uint8_t *)data;
|
||||||
|
ws_pkt.len = len;
|
||||||
|
ws_pkt.type = type;
|
||||||
|
|
||||||
|
xSemaphoreTake(_mutex, portMAX_DELAY);
|
||||||
|
for (auto &client_pair : _clients) {
|
||||||
|
int fd = client_pair.first;
|
||||||
|
httpd_ws_send_frame_async(_server, fd, &ws_pkt);
|
||||||
|
}
|
||||||
|
xSemaphoreGive(_mutex);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace websocket
|
||||||
+14
-12
@@ -1,4 +1,6 @@
|
|||||||
#include <wifi_service.h>
|
#include <wifi_service.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
|
||||||
WiFiService::WiFiService()
|
WiFiService::WiFiService()
|
||||||
: _persistence(WiFiSettings::read, WiFiSettings::update, this, WIFI_SETTINGS_FILE),
|
: _persistence(WiFiSettings::read, WiFiSettings::update, this, WIFI_SETTINGS_FILE),
|
||||||
@@ -38,25 +40,25 @@ void WiFiService::reconfigureWiFiConnection() {
|
|||||||
|
|
||||||
void WiFiService::loop() { EXECUTE_EVERY_N_MS(reconnectDelay, manageSTA()); }
|
void WiFiService::loop() { EXECUTE_EVERY_N_MS(reconnectDelay, manageSTA()); }
|
||||||
|
|
||||||
esp_err_t WiFiService::handleScan(PsychicRequest *request) {
|
esp_err_t WiFiService::handleScan(httpd_req_t *req) {
|
||||||
if (WiFi.scanComplete() != -1) {
|
if (WiFi.scanComplete() != -1) {
|
||||||
WiFi.scanDelete();
|
WiFi.scanDelete();
|
||||||
WiFi.scanNetworks(true);
|
WiFi.scanNetworks(true);
|
||||||
}
|
}
|
||||||
return request->reply(202);
|
return http_utils::send_empty_response(req, 202);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t WiFiService::getNetworks(PsychicRequest *request) {
|
esp_err_t WiFiService::getNetworks(httpd_req_t *req) {
|
||||||
int numNetworks = WiFi.scanComplete();
|
int numNetworks = WiFi.scanComplete();
|
||||||
if (numNetworks == -1)
|
if (numNetworks == -1)
|
||||||
return request->reply(202);
|
return http_utils::send_empty_response(req, 202);
|
||||||
else if (numNetworks < -1)
|
else if (numNetworks < -1)
|
||||||
return handleScan(request);
|
return handleScan(req);
|
||||||
|
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request, false);
|
JsonDocument doc;
|
||||||
JsonObject root = response.getRoot();
|
JsonObject root = doc.to<JsonObject>();
|
||||||
getNetworks(root);
|
getNetworks(root);
|
||||||
return response.send();
|
return http_utils::send_json_response(req, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiService::setupMDNS(const char *hostname) {
|
void WiFiService::setupMDNS(const char *hostname) {
|
||||||
@@ -80,11 +82,11 @@ void WiFiService::getNetworks(JsonObject &root) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t WiFiService::getNetworkStatus(PsychicRequest *request) {
|
esp_err_t WiFiService::getNetworkStatus(httpd_req_t *req) {
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request, false);
|
JsonDocument doc;
|
||||||
JsonObject root = response.getRoot();
|
JsonObject root = doc.to<JsonObject>();
|
||||||
getNetworkStatus(root);
|
getNetworkStatus(root);
|
||||||
return response.send();
|
return http_utils::send_json_response(req, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiService::getNetworkStatus(JsonObject &root) {
|
void WiFiService::getNetworkStatus(JsonObject &root) {
|
||||||
|
|||||||
+56
-20
@@ -1,34 +1,70 @@
|
|||||||
#include "www_mount.hpp"
|
#include "www_mount.hpp"
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const char *TAG = "WWWMount";
|
||||||
|
|
||||||
|
struct asset_handler_ctx {
|
||||||
|
const WebAsset *asset;
|
||||||
|
};
|
||||||
|
|
||||||
|
static esp_err_t web_send(httpd_req_t *req, const WebAsset &asset) {
|
||||||
|
httpd_resp_set_status(req, "200 OK");
|
||||||
|
httpd_resp_set_type(req, asset.mime);
|
||||||
|
|
||||||
|
if (asset.gz) {
|
||||||
|
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WWW_OPT.add_vary) {
|
||||||
|
httpd_resp_set_hdr(req, "Vary", "Accept-Encoding");
|
||||||
|
}
|
||||||
|
|
||||||
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];
|
char cc[64];
|
||||||
snprintf(cc, sizeof(cc), "public, immutable, max-age=%u", WWW_OPT.max_age);
|
snprintf(cc, sizeof(cc), "public, immutable, max-age=%u", WWW_OPT.max_age);
|
||||||
resp.addHeader("Cache-Control", cc);
|
httpd_resp_set_hdr(req, "Cache-Control", cc);
|
||||||
|
|
||||||
char et[34];
|
char et[34];
|
||||||
snprintf(et, sizeof(et), "\"%08x\"", asset.etag);
|
snprintf(et, sizeof(et), "\"%08x\"", asset.etag);
|
||||||
resp.addHeader("ETag", et);
|
httpd_resp_set_hdr(req, "ETag", et);
|
||||||
resp.setContent(asset.data, asset.len);
|
|
||||||
return resp.send();
|
return httpd_resp_send(req, (const char *)asset.data, asset.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mountStaticAssets(PsychicHttpServer& server) {
|
static esp_err_t asset_handler(httpd_req_t *req) {
|
||||||
static uint8_t buf[sizeof(PsychicWebHandler) * WWW_ASSETS_COUNT];
|
asset_handler_ctx *ctx = (asset_handler_ctx *)req->user_ctx;
|
||||||
for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) {
|
return web_send(req, *ctx->asset);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static esp_err_t default_handler(httpd_req_t *req) {
|
||||||
for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) {
|
for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) {
|
||||||
if (strcmp(WWW_ASSETS[i].uri, WWW_OPT.default_uri) == 0) {
|
if (strcmp(WWW_ASSETS[i].uri, WWW_OPT.default_uri) == 0) {
|
||||||
server.defaultEndpoint->setHandler(
|
return web_send(req, WWW_ASSETS[i]);
|
||||||
reinterpret_cast<PsychicWebHandler*>(&buf[i * sizeof(PsychicWebHandler)]));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
httpd_resp_send_404(req);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mountStaticAssets(httpd_handle_t server) {
|
||||||
|
static asset_handler_ctx contexts[WWW_ASSETS_COUNT];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) {
|
||||||
|
contexts[i].asset = &WWW_ASSETS[i];
|
||||||
|
|
||||||
|
httpd_uri_t uri_handler = {
|
||||||
|
.uri = WWW_ASSETS[i].uri, .method = HTTP_GET, .handler = asset_handler, .user_ctx = &contexts[i]};
|
||||||
|
|
||||||
|
esp_err_t err = httpd_register_uri_handler(server, &uri_handler);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to register handler for %s: %d", WWW_ASSETS[i].uri, err);
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "Registered handler for %s", WWW_ASSETS[i].uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_uri_t default_uri_handler = {.uri = "/", .method = HTTP_GET, .handler = default_handler, .user_ctx = nullptr};
|
||||||
|
httpd_register_uri_handler(server, &default_uri_handler);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Mounted %d static assets", WWW_ASSETS_COUNT);
|
||||||
}
|
}
|
||||||
@@ -55,10 +55,6 @@ build_flags =
|
|||||||
-D USS_RIGHT_PIN=14
|
-D USS_RIGHT_PIN=14
|
||||||
-D SDA_PIN=47
|
-D SDA_PIN=47
|
||||||
-D SCL_PIN=21
|
-D SCL_PIN=21
|
||||||
-D SPI_SCK=41
|
|
||||||
-D SPI_MISO=19
|
|
||||||
-D SPI_MOSI=20
|
|
||||||
-D ICM20948_SPI_CS=2 # Only needed if ICM20948 is in SPI mode
|
|
||||||
|
|
||||||
[env:seeed-xiao-esp32s3]
|
[env:seeed-xiao-esp32s3]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
@@ -94,7 +90,6 @@ build_flags =
|
|||||||
${factory_settings.build_flags}
|
${factory_settings.build_flags}
|
||||||
${features.build_flags}
|
${features.build_flags}
|
||||||
${build_settings.build_flags}
|
${build_settings.build_flags}
|
||||||
-D SPI_PORT=SPI # Define which spi port to use for external components
|
|
||||||
-D CORE_DEBUG_LEVEL=4
|
-D CORE_DEBUG_LEVEL=4
|
||||||
-D register=
|
-D register=
|
||||||
-std=gnu++2a
|
-std=gnu++2a
|
||||||
@@ -110,7 +105,6 @@ build_src_flags =
|
|||||||
test_ignore = test_embedded
|
test_ignore = test_embedded
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
lib_deps =
|
lib_deps =
|
||||||
hoeken/PsychicHttp@^1.2.1
|
|
||||||
ArduinoJson@>=7.0.0
|
ArduinoJson@>=7.0.0
|
||||||
teckel12/NewPing@^1.9.7
|
teckel12/NewPing@^1.9.7
|
||||||
jrowberg/I2Cdevlib-MPU6050@^1.0.0
|
jrowberg/I2Cdevlib-MPU6050@^1.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user