Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5863598fbc |
@@ -262,9 +262,8 @@
|
|||||||
const rotatedXm = settings.xm * cosTotal - settings.zm * sinTotal
|
const rotatedXm = settings.xm * cosTotal - settings.zm * sinTotal
|
||||||
const rotatedZm = settings.xm * sinTotal + settings.zm * cosTotal
|
const rotatedZm = settings.xm * sinTotal + settings.zm * cosTotal
|
||||||
|
|
||||||
const mpuHeadingRad = degToRad($mpu.heading)
|
const cosHead = Math.cos(headingYaw)
|
||||||
const cosHead = Math.cos(mpuHeadingRad)
|
const sinHead = Math.sin(headingYaw)
|
||||||
const sinHead = Math.sin(mpuHeadingRad)
|
|
||||||
const rotatedCumX = body_state.cumulative_x * cosHead - body_state.cumulative_z * sinHead
|
const rotatedCumX = body_state.cumulative_x * cosHead - body_state.cumulative_z * sinHead
|
||||||
const rotatedCumZ = body_state.cumulative_x * sinHead + body_state.cumulative_z * cosHead
|
const rotatedCumZ = body_state.cumulative_x * sinHead + body_state.cumulative_z * cosHead
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <template/stateful_service.h>
|
#include <esp_http_server.h>
|
||||||
#include <template/stateful_proto_endpoint.h>
|
#include <eventbus.hpp>
|
||||||
#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/wifi_idf.h>
|
#include <wifi/wifi_idf.h>
|
||||||
@@ -10,7 +9,9 @@
|
|||||||
#include <esp_timer.h>
|
#include <esp_timer.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class APService : public StatefulService<APSettings> {
|
class WebServer;
|
||||||
|
|
||||||
|
class APService {
|
||||||
public:
|
public:
|
||||||
APService();
|
APService();
|
||||||
~APService();
|
~APService();
|
||||||
@@ -23,10 +24,11 @@ class APService : public StatefulService<APSettings> {
|
|||||||
void statusProto(api_APStatus &proto);
|
void statusProto(api_APStatus &proto);
|
||||||
APNetworkStatus getAPNetworkStatus();
|
APNetworkStatus getAPNetworkStatus();
|
||||||
|
|
||||||
StatefulProtoEndpoint<APSettings, api_APSettings> protoEndpoint;
|
void registerRoutes(WebServer &server);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FSPersistencePB<APSettings> _persistence;
|
APSettings _settings {};
|
||||||
|
SubscriptionHandle _settingsHandle;
|
||||||
DNSServer *_dnsServer;
|
DNSServer *_dnsServer;
|
||||||
|
|
||||||
volatile unsigned long _lastManaged;
|
volatile unsigned long _lastManaged;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <communication/proto_helpers.h>
|
#include <communication/proto_helpers.h>
|
||||||
|
#include <eventbus.hpp>
|
||||||
|
|
||||||
class CommAdapterBase {
|
class CommAdapterBase {
|
||||||
public:
|
public:
|
||||||
@@ -35,6 +36,11 @@ class CommAdapterBase {
|
|||||||
decoder_.on<T>(handler);
|
decoder_.on<T>(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void forward() {
|
||||||
|
decoder_.on<T>([](const T& data, int) { EventBus::instance().publish(data); });
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void emit(const T& data, int clientId = -1) {
|
void emit(const T& data, int clientId = -1) {
|
||||||
constexpr pb_size_t tag = MessageTraits<T>::tag;
|
constexpr pb_size_t tag = MessageTraits<T>::tag;
|
||||||
@@ -47,8 +53,7 @@ class CommAdapterBase {
|
|||||||
size_t out_size;
|
size_t out_size;
|
||||||
pb_get_encoded_size(&out_size, socket_message_Message_fields, &msg_);
|
pb_get_encoded_size(&out_size, socket_message_Message_fields, &msg_);
|
||||||
uint8_t* buffer = pb_heap_enc_buf;
|
uint8_t* buffer = pb_heap_enc_buf;
|
||||||
if (out_size > sizeof(pb_heap_enc_buf)) { // If the encoded size exceeds our buffer size, we needs to malloc a
|
if (out_size > sizeof(pb_heap_enc_buf)) {
|
||||||
// buffer of a proper size
|
|
||||||
buffer = (uint8_t*)malloc(out_size);
|
buffer = (uint8_t*)malloc(out_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include <platform_shared/api.pb.h>
|
#include <platform_shared/api.pb.h>
|
||||||
#include <freertos/semphr.h>
|
#include <eventbus.hpp>
|
||||||
|
|
||||||
using HttpGetHandler = std::function<esp_err_t(httpd_req_t*)>;
|
using HttpGetHandler = std::function<esp_err_t(httpd_req_t*)>;
|
||||||
using HttpPostHandler = std::function<esp_err_t(httpd_req_t*, api_Request*)>;
|
using HttpPostHandler = std::function<esp_err_t(httpd_req_t*, api_Request*)>;
|
||||||
@@ -23,15 +23,20 @@ using WsFrameHandler = std::function<esp_err_t(httpd_req_t*, httpd_ws_frame_t*)>
|
|||||||
using WsOpenHandler = std::function<void(httpd_req_t*)>;
|
using WsOpenHandler = std::function<void(httpd_req_t*)>;
|
||||||
using WsCloseHandler = std::function<void(int)>;
|
using WsCloseHandler = std::function<void(int)>;
|
||||||
|
|
||||||
// Macro to register a proto endpoint that extracts a specific payload type
|
#define PROTO_ROUTE(server_ref, uri, field_name, proto_type) \
|
||||||
// Usage: STAITC_PROTO_POST_ENDPOINT(server, "/api/files/delete", file_delete_request, FileSystem::handleDelete)
|
(server_ref) \
|
||||||
// Handler signature: esp_err_t handleDelete(httpd_req_t* req, const api_FileDeleteRequest& payload)
|
.protoRoute<proto_type>( \
|
||||||
#define STAITC_PROTO_POST_ENDPOINT(server_ref, uri, payload_type, handler) \
|
uri, \
|
||||||
(server_ref).on(uri, HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) { \
|
[](const api_Request& req, proto_type& out) -> bool { \
|
||||||
if (protoReq->which_payload != api_Request_##payload_type##_tag) { \
|
if (req.which_payload == api_Request_##field_name##_tag) { \
|
||||||
return WebServer::sendError(request, 400, "Invalid request payload"); \
|
out = req.payload.field_name; \
|
||||||
|
return true; \
|
||||||
} \
|
} \
|
||||||
return handler(request, protoReq->payload.payload_type); \
|
return false; \
|
||||||
|
}, \
|
||||||
|
[](api_Response& res, const proto_type& data) { \
|
||||||
|
res.which_payload = api_Response_##field_name##_tag; \
|
||||||
|
res.payload.field_name = data; \
|
||||||
})
|
})
|
||||||
|
|
||||||
struct HttpRoute {
|
struct HttpRoute {
|
||||||
@@ -54,6 +59,36 @@ class WebServer {
|
|||||||
void on(const char* uri, httpd_method_t method, HttpGetHandler handler);
|
void on(const char* uri, httpd_method_t method, HttpGetHandler handler);
|
||||||
void on(const char* uri, httpd_method_t method, HttpPostHandler handler);
|
void on(const char* uri, httpd_method_t method, HttpPostHandler handler);
|
||||||
|
|
||||||
|
template <typename ProtoT>
|
||||||
|
void protoRoute(const char* uri, std::function<bool(const api_Request&, ProtoT&)> extractor,
|
||||||
|
std::function<void(api_Response&, const ProtoT&)> assigner) {
|
||||||
|
on(uri, HTTP_GET, [assigner](httpd_req_t* req) {
|
||||||
|
auto* res = new api_Response();
|
||||||
|
*res = api_Response_init_zero;
|
||||||
|
res->status_code = 200;
|
||||||
|
ProtoT current = EventBus::instance().peek<ProtoT>();
|
||||||
|
assigner(*res, current);
|
||||||
|
esp_err_t ret = WebServer::send(req, 200, *res, api_Response_fields);
|
||||||
|
delete res;
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
on(uri, HTTP_POST, [extractor, assigner](httpd_req_t* req, api_Request* protoReq) {
|
||||||
|
ProtoT msg = {};
|
||||||
|
if (!extractor(*protoReq, msg)) {
|
||||||
|
return sendError(req, 400, "Invalid request type");
|
||||||
|
}
|
||||||
|
EventBus::instance().publish(msg);
|
||||||
|
auto* res = new api_Response();
|
||||||
|
*res = api_Response_init_zero;
|
||||||
|
res->status_code = 200;
|
||||||
|
ProtoT current = EventBus::instance().peek<ProtoT>();
|
||||||
|
assigner(*res, current);
|
||||||
|
esp_err_t ret = WebServer::send(req, 200, *res, api_Response_fields);
|
||||||
|
delete res;
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void onWsFrame(WsFrameHandler handler);
|
void onWsFrame(WsFrameHandler handler);
|
||||||
void onWsOpen(WsOpenHandler handler);
|
void onWsOpen(WsOpenHandler handler);
|
||||||
void onWsClose(WsCloseHandler handler);
|
void onWsClose(WsCloseHandler handler);
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <proto_event_storage.hpp>
|
||||||
|
#include <platform_shared/api.pb.h>
|
||||||
|
#include <peripherals/servo_controller.h>
|
||||||
|
#include <settings/wifi_settings.h>
|
||||||
|
#include <settings/ap_settings.h>
|
||||||
|
#include <settings/mdns_settings.h>
|
||||||
|
#include <settings/peripherals_settings.h>
|
||||||
|
#include <settings/camera_settings.h>
|
||||||
|
#include <features.h>
|
||||||
|
|
||||||
|
class EventStorageManager {
|
||||||
|
public:
|
||||||
|
void initialize() {
|
||||||
|
_servoStorage.begin();
|
||||||
|
_wifiStorage.begin();
|
||||||
|
_apStorage.begin();
|
||||||
|
_peripheralStorage.begin();
|
||||||
|
#if FT_ENABLED(USE_MDNS)
|
||||||
|
_mdnsStorage.begin();
|
||||||
|
#endif
|
||||||
|
#if FT_ENABLED(USE_CAMERA) && USE_DVP_CAMERA
|
||||||
|
_cameraStorage.begin();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ProtoEventStorage<api_ServoSettings, ServoSettings_defaults> _servoStorage {
|
||||||
|
SERVO_SETTINGS_FILE, api_ServoSettings_fields, api_ServoSettings_size, 1000};
|
||||||
|
|
||||||
|
ProtoEventStorage<api_WifiSettings, WiFiSettings_defaults> _wifiStorage {
|
||||||
|
WIFI_SETTINGS_FILE, api_WifiSettings_fields, api_WifiSettings_size, 1000};
|
||||||
|
|
||||||
|
ProtoEventStorage<api_APSettings, APSettings_defaults> _apStorage {AP_SETTINGS_FILE, api_APSettings_fields,
|
||||||
|
api_APSettings_size, 1000};
|
||||||
|
|
||||||
|
ProtoEventStorage<api_PeripheralSettings, PeripheralsConfiguration_defaults> _peripheralStorage {
|
||||||
|
PERIPHERAL_SETTINGS_FILE, api_PeripheralSettings_fields, api_PeripheralSettings_size, 500};
|
||||||
|
|
||||||
|
#if FT_ENABLED(USE_MDNS)
|
||||||
|
ProtoEventStorage<api_MDNSSettings, MDNSSettings_defaults> _mdnsStorage {
|
||||||
|
MDNS_SETTINGS_FILE, api_MDNSSettings_fields, api_MDNSSettings_size, 1000};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if FT_ENABLED(USE_CAMERA) && USE_DVP_CAMERA
|
||||||
|
ProtoEventStorage<api_CameraSettings, CameraSettings_defaults> _cameraStorage {
|
||||||
|
CAMERA_SETTINGS_FILE, api_CameraSettings_fields, api_CameraSettings_size, 1000};
|
||||||
|
#endif
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct SystemReadyEvent {};
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class EventBus;
|
||||||
|
|
||||||
|
class SubscriptionHandle {
|
||||||
|
public:
|
||||||
|
SubscriptionHandle() = default;
|
||||||
|
|
||||||
|
~SubscriptionHandle() { unsubscribe(); }
|
||||||
|
|
||||||
|
SubscriptionHandle(SubscriptionHandle&& other) noexcept
|
||||||
|
: typeId_(other.typeId_), handlerId_(other.handlerId_), bus_(other.bus_) {
|
||||||
|
other.bus_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubscriptionHandle& operator=(SubscriptionHandle&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
unsubscribe();
|
||||||
|
typeId_ = other.typeId_;
|
||||||
|
handlerId_ = other.handlerId_;
|
||||||
|
bus_ = other.bus_;
|
||||||
|
other.bus_ = nullptr;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubscriptionHandle(const SubscriptionHandle&) = delete;
|
||||||
|
SubscriptionHandle& operator=(const SubscriptionHandle&) = delete;
|
||||||
|
|
||||||
|
void unsubscribe();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class EventBus;
|
||||||
|
using TypeId = const void*;
|
||||||
|
|
||||||
|
SubscriptionHandle(TypeId typeId, uint32_t handlerId, EventBus* bus)
|
||||||
|
: typeId_(typeId), handlerId_(handlerId), bus_(bus) {}
|
||||||
|
|
||||||
|
TypeId typeId_ = nullptr;
|
||||||
|
uint32_t handlerId_ = 0;
|
||||||
|
EventBus* bus_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EventBus {
|
||||||
|
public:
|
||||||
|
using TypeId = const void*;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static TypeId typeId() {
|
||||||
|
static const char id = 0;
|
||||||
|
return &id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EventBus& instance() {
|
||||||
|
static EventBus bus;
|
||||||
|
return bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
SubscriptionHandle subscribe(std::function<void(const T&)> callback) {
|
||||||
|
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||||
|
auto id = typeId<T>();
|
||||||
|
uint32_t hid = nextHandlerId_++;
|
||||||
|
handlers_[id].push_back(
|
||||||
|
{hid, [cb = std::move(callback)](const void* data) { cb(*static_cast<const T*>(data)); }});
|
||||||
|
xSemaphoreGive(mutex_);
|
||||||
|
return SubscriptionHandle(id, hid, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void publish(const T& event) {
|
||||||
|
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||||
|
|
||||||
|
auto id = typeId<T>();
|
||||||
|
auto& entry = cache_[id];
|
||||||
|
if (entry.data) {
|
||||||
|
*static_cast<T*>(entry.data) = event;
|
||||||
|
} else {
|
||||||
|
entry.data = new T(event);
|
||||||
|
entry.deleter = [](void* p) { delete static_cast<T*>(p); };
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = handlers_.find(id);
|
||||||
|
if (it == handlers_.end()) {
|
||||||
|
xSemaphoreGive(mutex_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto snapshot = it->second;
|
||||||
|
xSemaphoreGive(mutex_);
|
||||||
|
|
||||||
|
for (auto& handler : snapshot) {
|
||||||
|
handler.callback(&event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool has() {
|
||||||
|
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||||
|
auto it = cache_.find(typeId<T>());
|
||||||
|
bool result = it != cache_.end() && it->second.data != nullptr;
|
||||||
|
xSemaphoreGive(mutex_);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T peek() {
|
||||||
|
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||||
|
auto it = cache_.find(typeId<T>());
|
||||||
|
T result = (it != cache_.end() && it->second.data) ? *static_cast<const T*>(it->second.data) : T {};
|
||||||
|
xSemaphoreGive(mutex_);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class SubscriptionHandle;
|
||||||
|
|
||||||
|
EventBus() { mutex_ = xSemaphoreCreateMutex(); }
|
||||||
|
|
||||||
|
~EventBus() {
|
||||||
|
for (auto& [id, entry] : cache_) {
|
||||||
|
if (entry.deleter) entry.deleter(entry.data);
|
||||||
|
}
|
||||||
|
vSemaphoreDelete(mutex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventBus(const EventBus&) = delete;
|
||||||
|
EventBus& operator=(const EventBus&) = delete;
|
||||||
|
|
||||||
|
void removeHandler(TypeId typeId, uint32_t handlerId) {
|
||||||
|
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||||
|
auto it = handlers_.find(typeId);
|
||||||
|
if (it != handlers_.end()) {
|
||||||
|
it->second.remove_if([handlerId](const Handler& h) { return h.id == handlerId; });
|
||||||
|
}
|
||||||
|
xSemaphoreGive(mutex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Handler {
|
||||||
|
uint32_t id;
|
||||||
|
std::function<void(const void*)> callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CacheEntry {
|
||||||
|
void* data = nullptr;
|
||||||
|
void (*deleter)(void*) = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
SemaphoreHandle_t mutex_;
|
||||||
|
uint32_t nextHandlerId_ = 1;
|
||||||
|
std::map<TypeId, std::list<Handler>> handlers_;
|
||||||
|
std::map<TypeId, CacheEntry> cache_;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void SubscriptionHandle::unsubscribe() {
|
||||||
|
if (bus_) {
|
||||||
|
bus_->removeHandler(typeId_, handlerId_);
|
||||||
|
bus_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <platform_shared/api.pb.h>
|
#include <platform_shared/api.pb.h>
|
||||||
|
|
||||||
|
class WebServer;
|
||||||
|
|
||||||
#define MOUNT_POINT "/littlefs"
|
#define MOUNT_POINT "/littlefs"
|
||||||
|
|
||||||
#define FS_CONFIG_DIRECTORY MOUNT_POINT "/config"
|
#define FS_CONFIG_DIRECTORY MOUNT_POINT "/config"
|
||||||
@@ -35,6 +37,8 @@ bool writeFile(const char *filename, const char *content);
|
|||||||
bool writeFile(const char *filename, const uint8_t *content, size_t size);
|
bool writeFile(const char *filename, const uint8_t *content, size_t size);
|
||||||
bool mkdirRecursive(const char *path);
|
bool mkdirRecursive(const char *path);
|
||||||
|
|
||||||
|
void registerRoutes(WebServer &server);
|
||||||
|
|
||||||
esp_err_t getFilesProto(httpd_req_t *request);
|
esp_err_t getFilesProto(httpd_req_t *request);
|
||||||
esp_err_t getFiles(httpd_req_t *request);
|
esp_err_t getFiles(httpd_req_t *request);
|
||||||
esp_err_t getConfigFile(httpd_req_t *request);
|
esp_err_t getConfigFile(httpd_req_t *request);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <platform_shared/message.pb.h>
|
#include <platform_shared/message.pb.h>
|
||||||
|
#include <eventbus.hpp>
|
||||||
#include <filesystem.h>
|
#include <filesystem.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -45,6 +46,7 @@ class FileSystemHandler {
|
|||||||
public:
|
public:
|
||||||
FileSystemHandler();
|
FileSystemHandler();
|
||||||
|
|
||||||
|
void begin();
|
||||||
void setSendCallbacks(SendMetadataCallback sendMetadata, SendCallback sendData, SendCompleteCallback sendComplete,
|
void setSendCallbacks(SendMetadataCallback sendMetadata, SendCallback sendData, SendCompleteCallback sendComplete,
|
||||||
SendUploadCompleteCallback sendUploadComplete);
|
SendUploadCompleteCallback sendUploadComplete);
|
||||||
|
|
||||||
@@ -74,6 +76,8 @@ class FileSystemHandler {
|
|||||||
bool deleteRecursive(const std::string& path);
|
bool deleteRecursive(const std::string& path);
|
||||||
bool sendNextDownloadChunk(uint32_t transferId);
|
bool sendNextDownloadChunk(uint32_t transferId);
|
||||||
void finalizeUpload(uint32_t transferId, bool success, const std::string& error = "");
|
void finalizeUpload(uint32_t transferId, bool success, const std::string& error = "");
|
||||||
|
|
||||||
|
SubscriptionHandle uploadHandle_;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern FileSystemHandler fsHandler;
|
extern FileSystemHandler fsHandler;
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
#include <esp_http_server.h>
|
#include <esp_http_server.h>
|
||||||
#include <mdns.h>
|
#include <mdns.h>
|
||||||
#include <template/stateful_service.h>
|
#include <eventbus.hpp>
|
||||||
#include <template/stateful_proto_endpoint.h>
|
|
||||||
#include <template/stateful_persistence.h>
|
|
||||||
#include <settings/mdns_settings.h>
|
#include <settings/mdns_settings.h>
|
||||||
#include <utils/timing.h>
|
#include <utils/timing.h>
|
||||||
|
|
||||||
class MDNSService : public StatefulService<MDNSSettings> {
|
class WebServer;
|
||||||
|
|
||||||
|
class MDNSService {
|
||||||
public:
|
public:
|
||||||
MDNSService();
|
MDNSService();
|
||||||
~MDNSService();
|
~MDNSService();
|
||||||
@@ -18,10 +18,11 @@ class MDNSService : public StatefulService<MDNSSettings> {
|
|||||||
esp_err_t getStatus(httpd_req_t *request);
|
esp_err_t getStatus(httpd_req_t *request);
|
||||||
esp_err_t queryServices(httpd_req_t *request, api_Request *protoReq);
|
esp_err_t queryServices(httpd_req_t *request, api_Request *protoReq);
|
||||||
|
|
||||||
StatefulProtoEndpoint<MDNSSettings, api_MDNSSettings> protoEndpoint;
|
void registerRoutes(WebServer &server);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FSPersistencePB<MDNSSettings> _persistence;
|
MDNSSettings _settings {};
|
||||||
|
SubscriptionHandle _settingsHandle;
|
||||||
bool _started {false};
|
bool _started {false};
|
||||||
|
|
||||||
void reconfigureMDNS();
|
void reconfigureMDNS();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <motion_states/stand_state.h>
|
#include <motion_states/stand_state.h>
|
||||||
#include <motion_states/rest_state.h>
|
#include <motion_states/rest_state.h>
|
||||||
#include <message_types.h>
|
#include <message_types.h>
|
||||||
|
#include <eventbus.hpp>
|
||||||
|
|
||||||
enum class MOTION_STATE { DEACTIVATED, IDLE, CALIBRATION, REST, STAND, WALK };
|
enum class MOTION_STATE { DEACTIVATED, IDLE, CALIBRATION, REST, STAND, WALK };
|
||||||
|
|
||||||
@@ -62,6 +63,10 @@ class MotionService {
|
|||||||
float dir[12] = {1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1};
|
float dir[12] = {1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1};
|
||||||
|
|
||||||
int64_t lastUpdate = esp_timer_get_time();
|
int64_t lastUpdate = esp_timer_get_time();
|
||||||
|
|
||||||
|
SubscriptionHandle controllerHandle_;
|
||||||
|
SubscriptionHandle walkGaitHandle_;
|
||||||
|
SubscriptionHandle anglesHandle_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
#include <esp_http_server.h>
|
#include <esp_http_server.h>
|
||||||
|
|
||||||
#include <features.h>
|
#include <features.h>
|
||||||
#include <template/stateful_service.h>
|
#include <eventbus.hpp>
|
||||||
#include <template/stateful_proto_endpoint.h>
|
|
||||||
#include <template/stateful_persistence.h>
|
|
||||||
|
|
||||||
#include <settings/camera_settings.h>
|
#include <settings/camera_settings.h>
|
||||||
|
|
||||||
|
class WebServer;
|
||||||
|
|
||||||
namespace Camera {
|
namespace Camera {
|
||||||
|
|
||||||
#define USE_DVP_CAMERA (USE_CAMERA && !CONFIG_IDF_TARGET_ESP32P4)
|
#define USE_DVP_CAMERA (USE_CAMERA && !CONFIG_IDF_TARGET_ESP32P4)
|
||||||
@@ -25,11 +25,7 @@ void safe_sensor_return();
|
|||||||
|
|
||||||
#define PART_BOUNDARY "frame"
|
#define PART_BOUNDARY "frame"
|
||||||
|
|
||||||
class CameraService
|
class CameraService {
|
||||||
#if USE_DVP_CAMERA
|
|
||||||
: public StatefulService<CameraSettings>
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
CameraService();
|
CameraService();
|
||||||
|
|
||||||
@@ -38,11 +34,12 @@ class CameraService
|
|||||||
esp_err_t cameraStill(httpd_req_t *request);
|
esp_err_t cameraStill(httpd_req_t *request);
|
||||||
esp_err_t cameraStream(httpd_req_t *request);
|
esp_err_t cameraStream(httpd_req_t *request);
|
||||||
|
|
||||||
#if USE_DVP_CAMERA
|
void registerRoutes(WebServer &server);
|
||||||
StatefulProtoEndpoint<CameraSettings, api_CameraSettings> protoEndpoint;
|
|
||||||
|
|
||||||
|
#if USE_DVP_CAMERA
|
||||||
private:
|
private:
|
||||||
FSPersistencePB<CameraSettings> _persistence;
|
CameraSettings _settings {};
|
||||||
|
SubscriptionHandle _settingsHandle;
|
||||||
void updateCamera();
|
void updateCamera();
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,11 +31,7 @@ class I2CBus {
|
|||||||
bus_cfg.scl_io_num = scl;
|
bus_cfg.scl_io_num = scl;
|
||||||
bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
|
bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
|
||||||
bus_cfg.glitch_ignore_cnt = 7;
|
bus_cfg.glitch_ignore_cnt = 7;
|
||||||
#if CONFIG_IDF_TARGET_ESP32P4
|
|
||||||
bus_cfg.flags.enable_internal_pullup = false;
|
|
||||||
#else
|
|
||||||
bus_cfg.flags.enable_internal_pullup = true;
|
bus_cfg.flags.enable_internal_pullup = true;
|
||||||
#endif
|
|
||||||
|
|
||||||
esp_err_t err = i2c_new_master_bus(&bus_cfg, &_bus);
|
esp_err_t err = i2c_new_master_bus(&bus_cfg, &_bus);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
@@ -68,7 +64,7 @@ class I2CBus {
|
|||||||
if (!_initialized) return ESP_ERR_INVALID_STATE;
|
if (!_initialized) return ESP_ERR_INVALID_STATE;
|
||||||
esp_err_t err = ensureDevice(addr);
|
esp_err_t err = ensureDevice(addr);
|
||||||
if (err != ESP_OK) return err;
|
if (err != ESP_OK) return err;
|
||||||
return i2c_master_transmit(_dev, data, len, pdMS_TO_TICKS(200));
|
return i2c_master_transmit(_dev, data, len, pdMS_TO_TICKS(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t writeReg(uint8_t addr, uint8_t reg, const uint8_t* data, size_t len) {
|
esp_err_t writeReg(uint8_t addr, uint8_t reg, const uint8_t* data, size_t len) {
|
||||||
@@ -81,19 +77,19 @@ class I2CBus {
|
|||||||
if (len > 0 && data != nullptr) {
|
if (len > 0 && data != nullptr) {
|
||||||
memcpy(buf + 1, data, len);
|
memcpy(buf + 1, data, len);
|
||||||
}
|
}
|
||||||
return i2c_master_transmit(_dev, buf, len + 1, pdMS_TO_TICKS(200));
|
return i2c_master_transmit(_dev, buf, len + 1, pdMS_TO_TICKS(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t readReg(uint8_t addr, uint8_t reg, uint8_t* data, size_t len) {
|
esp_err_t readReg(uint8_t addr, uint8_t reg, uint8_t* data, size_t len) {
|
||||||
if (!_initialized) return ESP_ERR_INVALID_STATE;
|
if (!_initialized) return ESP_ERR_INVALID_STATE;
|
||||||
esp_err_t err = ensureDevice(addr);
|
esp_err_t err = ensureDevice(addr);
|
||||||
if (err != ESP_OK) return err;
|
if (err != ESP_OK) return err;
|
||||||
return i2c_master_transmit_receive(_dev, ®, 1, data, len, pdMS_TO_TICKS(200));
|
return i2c_master_transmit_receive(_dev, ®, 1, data, len, pdMS_TO_TICKS(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool probe(uint8_t addr) {
|
bool probe(uint8_t addr) {
|
||||||
if (!_initialized) return false;
|
if (!_initialized) return false;
|
||||||
return i2c_master_probe(_bus, addr, pdMS_TO_TICKS(200)) == ESP_OK;
|
return i2c_master_probe(_bus, addr, pdMS_TO_TICKS(50)) == ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> scan(uint8_t lower = 1, uint8_t upper = 127) {
|
std::vector<uint8_t> scan(uint8_t lower = 1, uint8_t upper = 127) {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <template/stateful_persistence.h>
|
#include <eventbus.hpp>
|
||||||
#include <template/stateful_service.h>
|
|
||||||
#include <template/stateful_proto_endpoint.h>
|
|
||||||
#include <utils/math_utils.h>
|
#include <utils/math_utils.h>
|
||||||
#include <utils/timing.h>
|
#include <utils/timing.h>
|
||||||
#include <filesystem.h>
|
#include <filesystem.h>
|
||||||
@@ -21,12 +19,9 @@
|
|||||||
#include <peripherals/barometer.h>
|
#include <peripherals/barometer.h>
|
||||||
#include <peripherals/gesture.h>
|
#include <peripherals/gesture.h>
|
||||||
|
|
||||||
/*
|
|
||||||
* Ultrasonic Sensor Settings
|
|
||||||
*/
|
|
||||||
#define MAX_DISTANCE 200
|
#define MAX_DISTANCE 200
|
||||||
|
|
||||||
class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
class Peripherals {
|
||||||
public:
|
public:
|
||||||
Peripherals();
|
Peripherals();
|
||||||
|
|
||||||
@@ -42,21 +37,14 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
|||||||
void getIMUProto(socket_message_IMUData &data);
|
void getIMUProto(socket_message_IMUData &data);
|
||||||
void getSettingsProto(socket_message_PeripheralSettingsData &data);
|
void getSettingsProto(socket_message_PeripheralSettingsData &data);
|
||||||
|
|
||||||
/* IMU FUNCTIONS */
|
|
||||||
bool readImu();
|
bool readImu();
|
||||||
|
|
||||||
bool readMag();
|
bool readMag();
|
||||||
|
|
||||||
bool readBMP();
|
bool readBMP();
|
||||||
|
|
||||||
bool readGesture();
|
bool readGesture();
|
||||||
|
|
||||||
void readSonar();
|
void readSonar();
|
||||||
|
|
||||||
float angleX();
|
float angleX();
|
||||||
|
|
||||||
float angleY();
|
float angleY();
|
||||||
|
|
||||||
float angleZ();
|
float angleZ();
|
||||||
|
|
||||||
gesture_t takeGesture();
|
gesture_t takeGesture();
|
||||||
@@ -66,14 +54,12 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
|||||||
|
|
||||||
bool calibrateIMU();
|
bool calibrateIMU();
|
||||||
|
|
||||||
StatefulProtoEndpoint<PeripheralsConfiguration, api_PeripheralSettings> protoEndpoint;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FSPersistencePB<PeripheralsConfiguration> _persistence;
|
PeripheralsConfiguration _settings {};
|
||||||
|
SubscriptionHandle _settingsHandle;
|
||||||
|
|
||||||
SemaphoreHandle_t _accessMutex;
|
SemaphoreHandle_t _accessMutex;
|
||||||
inline void beginTransaction() { xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); }
|
inline void beginTransaction() { xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); }
|
||||||
|
|
||||||
inline void endTransaction() { xSemaphoreGiveRecursive(_accessMutex); }
|
inline void endTransaction() { xSemaphoreGiveRecursive(_accessMutex); }
|
||||||
|
|
||||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
#define ServoController_h
|
#define ServoController_h
|
||||||
|
|
||||||
#include <peripherals/drivers/pca9685.h>
|
#include <peripherals/drivers/pca9685.h>
|
||||||
#include <template/stateful_persistence.h>
|
#include <eventbus.hpp>
|
||||||
#include <template/stateful_proto_endpoint.h>
|
|
||||||
#include <template/stateful_service.h>
|
|
||||||
#include <utils/math_utils.h>
|
#include <utils/math_utils.h>
|
||||||
#include <platform_shared/api.pb.h>
|
#include <platform_shared/api.pb.h>
|
||||||
|
#include <platform_shared/message.pb.h>
|
||||||
|
|
||||||
#ifndef FACTORY_SERVO_PWM_FREQUENCY
|
#ifndef FACTORY_SERVO_PWM_FREQUENCY
|
||||||
#define FACTORY_SERVO_PWM_FREQUENCY 50
|
#define FACTORY_SERVO_PWM_FREQUENCY 50
|
||||||
@@ -34,25 +33,18 @@ inline ServoSettings ServoSettings_defaults() {
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void ServoSettings_read(const ServoSettings &settings, ServoSettings &proto) { proto = settings; }
|
class ServoController {
|
||||||
|
|
||||||
inline StateUpdateResult ServoSettings_update(const ServoSettings &proto, ServoSettings &settings) {
|
|
||||||
settings = proto;
|
|
||||||
return StateUpdateResult::CHANGED;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ServoController : public StatefulService<ServoSettings> {
|
|
||||||
public:
|
public:
|
||||||
ServoController()
|
|
||||||
: protoEndpoint(ServoSettings_read, ServoSettings_update, this,
|
|
||||||
API_REQUEST_EXTRACTOR(servo_settings, ServoSettings),
|
|
||||||
API_RESPONSE_ASSIGNER(servo_settings, ServoSettings)),
|
|
||||||
_persistence(ServoSettings_read, ServoSettings_update, this, SERVO_SETTINGS_FILE, api_ServoSettings_fields,
|
|
||||||
api_ServoSettings_size, ServoSettings_defaults()) {}
|
|
||||||
|
|
||||||
void begin() {
|
void begin() {
|
||||||
_persistence.readFromFS();
|
_settings = EventBus::instance().peek<ServoSettings>();
|
||||||
initializePCA();
|
initializePCA();
|
||||||
|
|
||||||
|
_settingsHandle =
|
||||||
|
EventBus::instance().subscribe<ServoSettings>([this](const ServoSettings& s) { _settings = s; });
|
||||||
|
servoPwmHandle_ = EventBus::instance().subscribe<socket_message_ServoPWMData>(
|
||||||
|
[this](const socket_message_ServoPWMData& data) { setServoPWM(data.servo_id, data.servo_pwm); });
|
||||||
|
servoStateHandle_ = EventBus::instance().subscribe<socket_message_ServoStateData>(
|
||||||
|
[this](const socket_message_ServoStateData& data) { data.active ? activate() : deactivate(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void pcaWrite(int index, int value) {
|
void pcaWrite(int index, int value) {
|
||||||
@@ -103,7 +95,7 @@ class ServoController : public StatefulService<ServoSettings> {
|
|||||||
uint16_t pwms[12];
|
uint16_t pwms[12];
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
angles[i] = lerp(angles[i], target_angles[i], 0.1);
|
angles[i] = lerp(angles[i], target_angles[i], 0.1);
|
||||||
auto &servo = state().servos[i];
|
auto& servo = _settings.servos[i];
|
||||||
float angle = servo.direction * angles[i] + servo.center_angle;
|
float angle = servo.direction * angles[i] + servo.center_angle;
|
||||||
uint16_t pwm = angle * servo.conversion + servo.center_pwm;
|
uint16_t pwm = angle * servo.conversion + servo.center_pwm;
|
||||||
pwms[i] = pwm = std::clamp<uint16_t>(pwm, 125, 600);
|
pwms[i] = pwm = std::clamp<uint16_t>(pwm, 125, 600);
|
||||||
@@ -115,8 +107,6 @@ class ServoController : public StatefulService<ServoSettings> {
|
|||||||
if (control_state == SERVO_CONTROL_STATE::ANGLE) calculatePWM();
|
if (control_state == SERVO_CONTROL_STATE::ANGLE) calculatePWM();
|
||||||
}
|
}
|
||||||
|
|
||||||
StatefulProtoEndpoint<ServoSettings, ServoSettings> protoEndpoint;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initializePCA() {
|
void initializePCA() {
|
||||||
_pca.begin();
|
_pca.begin();
|
||||||
@@ -124,8 +114,8 @@ class ServoController : public StatefulService<ServoSettings> {
|
|||||||
_pca.setPWMFreq(FACTORY_SERVO_PWM_FREQUENCY);
|
_pca.setPWMFreq(FACTORY_SERVO_PWM_FREQUENCY);
|
||||||
_pca.sleep();
|
_pca.sleep();
|
||||||
}
|
}
|
||||||
FSPersistencePB<ServoSettings> _persistence;
|
|
||||||
|
|
||||||
|
ServoSettings _settings {};
|
||||||
PCA9685Driver _pca;
|
PCA9685Driver _pca;
|
||||||
|
|
||||||
SERVO_CONTROL_STATE control_state = SERVO_CONTROL_STATE::DEACTIVATED;
|
SERVO_CONTROL_STATE control_state = SERVO_CONTROL_STATE::DEACTIVATED;
|
||||||
@@ -133,6 +123,10 @@ class ServoController : public StatefulService<ServoSettings> {
|
|||||||
bool is_active {false};
|
bool is_active {false};
|
||||||
float angles[12] = {0, 90, -145, 0, 90, -145, 0, 90, -145, 0, 90, -145};
|
float angles[12] = {0, 90, -145, 0, 90, -145, 0, 90, -145, 0, 90, -145};
|
||||||
float target_angles[12] = {0, 90, -145, 0, 90, -145, 0, 90, -145, 0, 90, -145};
|
float target_angles[12] = {0, 90, -145, 0, 90, -145, 0, 90, -145, 0, 90, -145};
|
||||||
|
|
||||||
|
SubscriptionHandle _settingsHandle;
|
||||||
|
SubscriptionHandle servoPwmHandle_;
|
||||||
|
SubscriptionHandle servoStateHandle_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eventbus.hpp>
|
||||||
|
#include <pb_encode.h>
|
||||||
|
#include <pb_decode.h>
|
||||||
|
#include <esp_timer.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
template <typename ProtoMsg, ProtoMsg (*DefaultsFn)()>
|
||||||
|
class ProtoEventStorage {
|
||||||
|
public:
|
||||||
|
ProtoEventStorage(const char* filename, const pb_msgdesc_t* descriptor, size_t maxSize, uint32_t debounceMs = 1000)
|
||||||
|
: _filename(filename), _descriptor(descriptor), _maxSize(maxSize), _debounceMs(debounceMs) {}
|
||||||
|
|
||||||
|
void begin() {
|
||||||
|
ProtoMsg loaded {};
|
||||||
|
loadOrDefault(loaded);
|
||||||
|
EventBus::instance().publish(loaded);
|
||||||
|
|
||||||
|
esp_timer_create_args_t timerArgs {};
|
||||||
|
timerArgs.callback = timerCallback;
|
||||||
|
timerArgs.arg = this;
|
||||||
|
timerArgs.dispatch_method = ESP_TIMER_TASK;
|
||||||
|
timerArgs.name = _filename;
|
||||||
|
esp_timer_create(&timerArgs, &_timer);
|
||||||
|
|
||||||
|
_handle = EventBus::instance().subscribe<ProtoMsg>([this](const ProtoMsg& msg) {
|
||||||
|
_pending = msg;
|
||||||
|
esp_timer_stop(_timer);
|
||||||
|
esp_timer_start_once(_timer, static_cast<uint64_t>(_debounceMs) * 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* _filename;
|
||||||
|
const pb_msgdesc_t* _descriptor;
|
||||||
|
size_t _maxSize;
|
||||||
|
uint32_t _debounceMs;
|
||||||
|
SubscriptionHandle _handle;
|
||||||
|
ProtoMsg _pending {};
|
||||||
|
esp_timer_handle_t _timer = nullptr;
|
||||||
|
|
||||||
|
static void timerCallback(void* arg) {
|
||||||
|
auto* self = static_cast<ProtoEventStorage*>(arg);
|
||||||
|
self->save(self->_pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadOrDefault(ProtoMsg& outMsg) {
|
||||||
|
FILE* file = fopen(_filename, "rb");
|
||||||
|
if (!file) {
|
||||||
|
ESP_LOGI("ProtoStorage", "No file %s, using defaults", _filename);
|
||||||
|
outMsg = DefaultsFn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
long tellResult = ftell(file);
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
if (tellResult < 0) {
|
||||||
|
ESP_LOGE("ProtoStorage", "ftell failed for %s", _filename);
|
||||||
|
fclose(file);
|
||||||
|
outMsg = DefaultsFn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t size = static_cast<size_t>(tellResult);
|
||||||
|
|
||||||
|
if (size == 0 || size > _maxSize) {
|
||||||
|
ESP_LOGW("ProtoStorage", "Invalid size %zu for %s (max %zu)", size, _filename, _maxSize);
|
||||||
|
fclose(file);
|
||||||
|
outMsg = DefaultsFn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buffer = std::make_unique<uint8_t[]>(size);
|
||||||
|
size_t read = fread(buffer.get(), 1, size, file);
|
||||||
|
fclose(file);
|
||||||
|
if (read != size) {
|
||||||
|
ESP_LOGE("ProtoStorage", "Read %zu/%zu from %s", read, size, _filename);
|
||||||
|
outMsg = DefaultsFn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pb_istream_t stream = pb_istream_from_buffer(buffer.get(), size);
|
||||||
|
if (!pb_decode(&stream, _descriptor, &outMsg)) {
|
||||||
|
ESP_LOGE("ProtoStorage", "Decode failed for %s", _filename);
|
||||||
|
outMsg = DefaultsFn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(const ProtoMsg& msg) {
|
||||||
|
auto buffer = std::make_unique<uint8_t[]>(_maxSize);
|
||||||
|
pb_ostream_t stream = pb_ostream_from_buffer(buffer.get(), _maxSize);
|
||||||
|
|
||||||
|
if (!pb_encode(&stream, _descriptor, &msg)) {
|
||||||
|
ESP_LOGE("ProtoStorage", "Encode failed for %s", _filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* file = fopen(_filename, "wb");
|
||||||
|
if (!file) {
|
||||||
|
ESP_LOGE("ProtoStorage", "Open write failed for %s", _filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t written = fwrite(buffer.get(), 1, stream.bytes_written, file);
|
||||||
|
fclose(file);
|
||||||
|
if (written != stream.bytes_written) {
|
||||||
|
ESP_LOGE("ProtoStorage", "Write failed for %s (%zu/%zu)", _filename, written, stream.bytes_written);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <wifi/wifi_idf.h>
|
#include <wifi/wifi_idf.h>
|
||||||
#include <wifi/dns_server.h>
|
#include <wifi/dns_server.h>
|
||||||
#include <template/state_result.h>
|
|
||||||
#include <platform_shared/api.pb.h>
|
#include <platform_shared/api.pb.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@@ -75,10 +74,3 @@ inline APSettings APSettings_defaults() {
|
|||||||
settings.subnet_mask = parseIPv4(FACTORY_AP_SUBNET_MASK);
|
settings.subnet_mask = parseIPv4(FACTORY_AP_SUBNET_MASK);
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void APSettings_read(const APSettings &settings, APSettings &proto) { proto = settings; }
|
|
||||||
|
|
||||||
inline StateUpdateResult APSettings_update(const APSettings &proto, APSettings &settings) {
|
|
||||||
settings = proto;
|
|
||||||
return StateUpdateResult::CHANGED;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -50,14 +50,4 @@ inline CameraSettings CameraSettings_defaults() {
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proto read/update are identity functions since type is the same
|
|
||||||
inline void CameraSettings_read(const CameraSettings& settings, CameraSettings& proto) {
|
|
||||||
proto = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline StateUpdateResult CameraSettings_update(const CameraSettings& proto, CameraSettings& settings) {
|
|
||||||
settings = proto;
|
|
||||||
return StateUpdateResult::CHANGED;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Camera
|
} // namespace Camera
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <template/state_result.h>
|
|
||||||
#include <platform_shared/api.pb.h>
|
#include <platform_shared/api.pb.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -13,45 +12,30 @@
|
|||||||
#define FACTORY_MDNS_INSTANCE "ESP32 Device"
|
#define FACTORY_MDNS_INSTANCE "ESP32 Device"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Use proto types directly
|
|
||||||
using MDNSTxtRecord = api_MDNSTxtRecord;
|
using MDNSTxtRecord = api_MDNSTxtRecord;
|
||||||
using MDNSServiceDef = api_MDNSServiceDef;
|
using MDNSServiceDef = api_MDNSServiceDef;
|
||||||
using MDNSSettings = api_MDNSSettings;
|
using MDNSSettings = api_MDNSSettings;
|
||||||
using MDNSStatus = api_MDNSStatus;
|
using MDNSStatus = api_MDNSStatus;
|
||||||
|
|
||||||
// Default factory settings
|
|
||||||
inline MDNSSettings MDNSSettings_defaults() {
|
inline MDNSSettings MDNSSettings_defaults() {
|
||||||
MDNSSettings settings = api_MDNSSettings_init_zero;
|
MDNSSettings settings = api_MDNSSettings_init_zero;
|
||||||
strncpy(settings.hostname, FACTORY_MDNS_HOSTNAME, sizeof(settings.hostname) - 1);
|
strncpy(settings.hostname, FACTORY_MDNS_HOSTNAME, sizeof(settings.hostname) - 1);
|
||||||
strncpy(settings.instance, FACTORY_MDNS_INSTANCE, sizeof(settings.instance) - 1);
|
strncpy(settings.instance, FACTORY_MDNS_INSTANCE, sizeof(settings.instance) - 1);
|
||||||
|
|
||||||
// Default HTTP service
|
|
||||||
settings.services_count = 2;
|
settings.services_count = 2;
|
||||||
strncpy(settings.services[0].service, "http", sizeof(settings.services[0].service) - 1);
|
strncpy(settings.services[0].service, "http", sizeof(settings.services[0].service) - 1);
|
||||||
strncpy(settings.services[0].protocol, "tcp", sizeof(settings.services[0].protocol) - 1);
|
strncpy(settings.services[0].protocol, "tcp", sizeof(settings.services[0].protocol) - 1);
|
||||||
settings.services[0].port = 80;
|
settings.services[0].port = 80;
|
||||||
settings.services[0].txt_records_count = 0;
|
settings.services[0].txt_records_count = 0;
|
||||||
|
|
||||||
// Default WS service
|
|
||||||
strncpy(settings.services[1].service, "ws", sizeof(settings.services[1].service) - 1);
|
strncpy(settings.services[1].service, "ws", sizeof(settings.services[1].service) - 1);
|
||||||
strncpy(settings.services[1].protocol, "tcp", sizeof(settings.services[1].protocol) - 1);
|
strncpy(settings.services[1].protocol, "tcp", sizeof(settings.services[1].protocol) - 1);
|
||||||
settings.services[1].port = 80;
|
settings.services[1].port = 80;
|
||||||
settings.services[1].txt_records_count = 0;
|
settings.services[1].txt_records_count = 0;
|
||||||
|
|
||||||
// Default global txt record
|
|
||||||
settings.global_txt_records_count = 1;
|
settings.global_txt_records_count = 1;
|
||||||
strncpy(settings.global_txt_records[0].key, "Firmware Version", sizeof(settings.global_txt_records[0].key) - 1);
|
strncpy(settings.global_txt_records[0].key, "Firmware Version", sizeof(settings.global_txt_records[0].key) - 1);
|
||||||
strncpy(settings.global_txt_records[0].value, APP_VERSION, sizeof(settings.global_txt_records[0].value) - 1);
|
strncpy(settings.global_txt_records[0].value, APP_VERSION, sizeof(settings.global_txt_records[0].value) - 1);
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proto read/update are identity functions since type is the same
|
|
||||||
inline void MDNSSettings_read(const MDNSSettings& settings, MDNSSettings& proto) {
|
|
||||||
proto = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline StateUpdateResult MDNSSettings_update(const MDNSSettings& proto, MDNSSettings& settings) {
|
|
||||||
settings = proto;
|
|
||||||
return StateUpdateResult::CHANGED;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include <template/state_result.h>
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -32,11 +31,10 @@ class NTPSettings {
|
|||||||
root["tz_format"] = settings.tzFormat.c_str();
|
root["tz_format"] = settings.tzFormat.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
static StateUpdateResult update(JsonVariant &root, NTPSettings &settings) {
|
static void update(JsonVariant &root, NTPSettings &settings) {
|
||||||
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
|
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
|
||||||
settings.server = root["server"] | FACTORY_NTP_SERVER;
|
settings.server = root["server"] | FACTORY_NTP_SERVER;
|
||||||
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
||||||
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
|
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||||
return StateUpdateResult::CHANGED;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <template/state_result.h>
|
|
||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#include <platform_shared/api.pb.h>
|
#include <platform_shared/api.pb.h>
|
||||||
#include <global.h>
|
#include <global.h>
|
||||||
|
|
||||||
// Use proto types directly
|
|
||||||
using PinConfig = api_PinConfig;
|
using PinConfig = api_PinConfig;
|
||||||
using PeripheralsConfiguration = api_PeripheralSettings;
|
using PeripheralsConfiguration = api_PeripheralSettings;
|
||||||
|
|
||||||
// Default factory settings
|
|
||||||
inline PeripheralsConfiguration PeripheralsConfiguration_defaults() {
|
inline PeripheralsConfiguration PeripheralsConfiguration_defaults() {
|
||||||
PeripheralsConfiguration settings = api_PeripheralSettings_init_zero;
|
PeripheralsConfiguration settings = api_PeripheralSettings_init_zero;
|
||||||
settings.sda = SDA_PIN;
|
settings.sda = SDA_PIN;
|
||||||
@@ -18,14 +15,3 @@ inline PeripheralsConfiguration PeripheralsConfiguration_defaults() {
|
|||||||
settings.pins_count = 0;
|
settings.pins_count = 0;
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proto read/update are identity functions since type is the same
|
|
||||||
inline void PeripheralsConfiguration_read(const PeripheralsConfiguration& settings, PeripheralsConfiguration& proto) {
|
|
||||||
proto = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline StateUpdateResult PeripheralsConfiguration_update(const PeripheralsConfiguration& proto,
|
|
||||||
PeripheralsConfiguration& settings) {
|
|
||||||
settings = proto;
|
|
||||||
return StateUpdateResult::CHANGED;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <wifi/wifi_idf.h>
|
#include <wifi/wifi_idf.h>
|
||||||
#include <template/state_result.h>
|
|
||||||
#include <platform_shared/api.pb.h>
|
#include <platform_shared/api.pb.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@@ -49,10 +48,3 @@ inline WiFiSettings WiFiSettings_defaults() {
|
|||||||
}
|
}
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void WiFiSettings_read(const WiFiSettings &settings, WiFiSettings &proto) { proto = settings; }
|
|
||||||
|
|
||||||
inline StateUpdateResult WiFiSettings_update(const WiFiSettings &proto, WiFiSettings &settings) {
|
|
||||||
settings = proto;
|
|
||||||
return StateUpdateResult::CHANGED;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,11 +12,15 @@
|
|||||||
|
|
||||||
#include "platform_shared/message.pb.h"
|
#include "platform_shared/message.pb.h"
|
||||||
|
|
||||||
|
class WebServer;
|
||||||
|
|
||||||
namespace system_service {
|
namespace system_service {
|
||||||
esp_err_t handleReset(httpd_req_t *request);
|
esp_err_t handleReset(httpd_req_t *request);
|
||||||
esp_err_t handleRestart(httpd_req_t *request);
|
esp_err_t handleRestart(httpd_req_t *request);
|
||||||
esp_err_t handleSleep(httpd_req_t *request);
|
esp_err_t handleSleep(httpd_req_t *request);
|
||||||
|
|
||||||
|
void registerRoutes(WebServer &server);
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
void restart();
|
void restart();
|
||||||
void sleep();
|
void sleep();
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
enum class StateUpdateResult {
|
|
||||||
CHANGED = 0, // The update changed the state and propagation should take place if required
|
|
||||||
UNCHANGED, // The state was unchanged, propagation should not take place
|
|
||||||
ERROR // There was a problem updating the state, propagation should not take place
|
|
||||||
};
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <esp_http_server.h>
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <template/stateful_service.h>
|
|
||||||
#include <communication/webserver.h>
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#define HTTP_ENDPOINT_ORIGIN_ID "http"
|
|
||||||
#define HTTPS_ENDPOINT_ORIGIN_ID "https"
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
class StatefulHttpEndpoint {
|
|
||||||
protected:
|
|
||||||
JsonStateReader<T> _stateReader;
|
|
||||||
JsonStateUpdater<T> _stateUpdater;
|
|
||||||
StatefulService<T> *_statefulService;
|
|
||||||
|
|
||||||
public:
|
|
||||||
StatefulHttpEndpoint(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater,
|
|
||||||
StatefulService<T> *statefulService)
|
|
||||||
: _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService) {}
|
|
||||||
|
|
||||||
esp_err_t handleStateUpdate(httpd_req_t *request, JsonVariant &json) {
|
|
||||||
JsonVariant jsonObject = json.as<JsonVariant>();
|
|
||||||
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
|
|
||||||
|
|
||||||
if (outcome == StateUpdateResult::ERROR) {
|
|
||||||
return WebServer::sendError(request, 400, "Invalid state");
|
|
||||||
} else if ((outcome == StateUpdateResult::CHANGED)) {
|
|
||||||
_statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonDocument doc;
|
|
||||||
JsonVariant root = doc.to<JsonVariant>();
|
|
||||||
_statefulService->read(root, _stateReader);
|
|
||||||
return WebServer::sendJson(request, 200, doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t getState(httpd_req_t *request) {
|
|
||||||
JsonDocument doc;
|
|
||||||
JsonVariant root = doc.to<JsonVariant>();
|
|
||||||
_statefulService->read(root, _stateReader);
|
|
||||||
return WebServer::sendJson(request, 200, doc);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <template/stateful_service.h>
|
|
||||||
#include <template/state_result.h>
|
|
||||||
#include <filesystem.h>
|
|
||||||
#include <pb_encode.h>
|
|
||||||
#include <pb_decode.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <esp_log.h>
|
|
||||||
|
|
||||||
static const char *TAG_PERSISTENCE = "FSPersistencePB";
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
class FSPersistencePB {
|
|
||||||
public:
|
|
||||||
using ProtoStateReader = std::function<void(const T &, T &)>;
|
|
||||||
using ProtoStateUpdater = std::function<StateUpdateResult(const T &, T &)>;
|
|
||||||
|
|
||||||
FSPersistencePB(ProtoStateReader stateReader, ProtoStateUpdater stateUpdater, StatefulService<T> *statefulService,
|
|
||||||
const char *filePath, const pb_msgdesc_t *msgDescriptor, size_t maxSize, const T &defaultState)
|
|
||||||
: _stateReader(stateReader),
|
|
||||||
_stateUpdater(stateUpdater),
|
|
||||||
_statefulService(statefulService),
|
|
||||||
_filePath(filePath),
|
|
||||||
_msgDescriptor(msgDescriptor),
|
|
||||||
_maxSize(maxSize),
|
|
||||||
_defaultState(defaultState),
|
|
||||||
_updateHandlerId(0) {
|
|
||||||
enableUpdateHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
void readFromFS() {
|
|
||||||
FILE *file = fopen(_filePath, "rb");
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
fseek(file, 0, SEEK_END);
|
|
||||||
size_t fileSize = ftell(file);
|
|
||||||
fseek(file, 0, SEEK_SET);
|
|
||||||
|
|
||||||
if (fileSize > 0 && fileSize <= _maxSize) {
|
|
||||||
uint8_t *buffer = new uint8_t[fileSize];
|
|
||||||
size_t bytesRead = fread(buffer, 1, fileSize, file);
|
|
||||||
fclose(file);
|
|
||||||
|
|
||||||
if (bytesRead == fileSize) {
|
|
||||||
T *protoMsg = new T();
|
|
||||||
*protoMsg = {};
|
|
||||||
pb_istream_t stream = pb_istream_from_buffer(buffer, bytesRead);
|
|
||||||
|
|
||||||
if (pb_decode(&stream, _msgDescriptor, protoMsg)) {
|
|
||||||
_statefulService->updateWithoutPropagation(
|
|
||||||
[this, protoMsg](T &state) { return _stateUpdater(*protoMsg, state); });
|
|
||||||
delete protoMsg;
|
|
||||||
delete[] buffer;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
delete protoMsg;
|
|
||||||
}
|
|
||||||
delete[] buffer;
|
|
||||||
} else {
|
|
||||||
fclose(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyDefaults();
|
|
||||||
writeToFS();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool writeToFS() {
|
|
||||||
uint8_t *buffer = new uint8_t[_maxSize];
|
|
||||||
pb_ostream_t stream = pb_ostream_from_buffer(buffer, _maxSize);
|
|
||||||
|
|
||||||
T *protoMsg = new T();
|
|
||||||
*protoMsg = {};
|
|
||||||
_statefulService->read([this, protoMsg](const T &state) { _stateReader(state, *protoMsg); });
|
|
||||||
|
|
||||||
bool encodeSuccess = pb_encode(&stream, _msgDescriptor, protoMsg);
|
|
||||||
delete protoMsg;
|
|
||||||
|
|
||||||
if (!encodeSuccess) {
|
|
||||||
delete[] buffer;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdirs();
|
|
||||||
|
|
||||||
FILE *file = fopen(_filePath, "wb");
|
|
||||||
if (!file) {
|
|
||||||
ESP_LOGE(TAG_PERSISTENCE, "Failed to open file for writing: %s", _filePath);
|
|
||||||
delete[] buffer;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t written = fwrite(buffer, 1, stream.bytes_written, file);
|
|
||||||
fclose(file);
|
|
||||||
delete[] buffer;
|
|
||||||
|
|
||||||
return written == stream.bytes_written;
|
|
||||||
}
|
|
||||||
|
|
||||||
void disableUpdateHandler() {
|
|
||||||
if (_updateHandlerId) {
|
|
||||||
_statefulService->removeUpdateHandler(_updateHandlerId);
|
|
||||||
_updateHandlerId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void enableUpdateHandler() {
|
|
||||||
if (!_updateHandlerId) {
|
|
||||||
_updateHandlerId = _statefulService->addUpdateHandler([&](const std::string &originId) { writeToFS(); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ProtoStateReader _stateReader;
|
|
||||||
ProtoStateUpdater _stateUpdater;
|
|
||||||
StatefulService<T> *_statefulService;
|
|
||||||
const char *_filePath;
|
|
||||||
const pb_msgdesc_t *_msgDescriptor;
|
|
||||||
size_t _maxSize;
|
|
||||||
T _defaultState;
|
|
||||||
HandlerId _updateHandlerId;
|
|
||||||
|
|
||||||
void mkdirs() {
|
|
||||||
std::string path(_filePath);
|
|
||||||
size_t index = 0;
|
|
||||||
while ((index = path.find('/', index + 1)) != std::string::npos) {
|
|
||||||
std::string segment = path.substr(0, index);
|
|
||||||
struct stat st;
|
|
||||||
if (stat(segment.c_str(), &st) != 0) {
|
|
||||||
FileSystem::mkdirRecursive(segment.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void applyDefaults() {
|
|
||||||
_statefulService->updateWithoutPropagation([this](T &state) { return _stateUpdater(_defaultState, state); });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <esp_http_server.h>
|
|
||||||
#include <template/stateful_service.h>
|
|
||||||
#include <communication/webserver.h>
|
|
||||||
#include <platform_shared/api.pb.h>
|
|
||||||
#include <pb_encode.h>
|
|
||||||
#include <pb_decode.h>
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#define PROTO_ENDPOINT_ORIGIN_ID "proto"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A stateful HTTP endpoint that uses protobuf encoding with api::Request/Response wrappers.
|
|
||||||
*
|
|
||||||
* @tparam T The internal state type (e.g., APSettings C++ class)
|
|
||||||
* @tparam ProtoT The protobuf message type within the oneof (e.g., api_APSettings)
|
|
||||||
*
|
|
||||||
* The endpoint receives api::Request, extracts the specific payload from the oneof,
|
|
||||||
* and returns api::Response with the updated state.
|
|
||||||
*/
|
|
||||||
template <class T, class ProtoT>
|
|
||||||
class StatefulProtoEndpoint {
|
|
||||||
public:
|
|
||||||
/** Converts internal state to protobuf message for responses */
|
|
||||||
// Formats are passed as referenced const (local variable) we want to read from, and a reference (proto) we write to
|
|
||||||
using ProtoStateReader = std::function<void(const T&, ProtoT&)>;
|
|
||||||
/** Converts incoming protobuf message to internal state */
|
|
||||||
// Formats are passed as referenced const (new object) we read from, and a reference to the local variable we write to
|
|
||||||
using ProtoStateUpdater = std::function<StateUpdateResult(const ProtoT&, T&)>;
|
|
||||||
/** Extracts the specific proto type from Request oneof */
|
|
||||||
using RequestExtractor = std::function<bool(const api_Request&, ProtoT&)>;
|
|
||||||
/** Assigns the specific proto type to Response oneof */
|
|
||||||
using ResponseAssigner = std::function<void(api_Response&, const ProtoT&)>;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
ProtoStateReader _stateReader;
|
|
||||||
ProtoStateUpdater _stateUpdater;
|
|
||||||
StatefulService<T>* _statefulService;
|
|
||||||
RequestExtractor _requestExtractor;
|
|
||||||
ResponseAssigner _responseAssigner;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Constructor for wrapped proto endpoint
|
|
||||||
* @param stateReader Converts internal state to proto
|
|
||||||
* @param stateUpdater Converts proto to internal state
|
|
||||||
* @param statefulService The stateful service to manage
|
|
||||||
* @param requestExtractor Extracts specific type from Request oneof
|
|
||||||
* @param responseAssigner Assigns specific type to Response oneof
|
|
||||||
*/
|
|
||||||
StatefulProtoEndpoint(ProtoStateReader stateReader, ProtoStateUpdater stateUpdater,
|
|
||||||
StatefulService<T>* statefulService,
|
|
||||||
RequestExtractor requestExtractor, ResponseAssigner responseAssigner)
|
|
||||||
: _stateReader(stateReader),
|
|
||||||
_stateUpdater(stateUpdater),
|
|
||||||
_statefulService(statefulService),
|
|
||||||
_requestExtractor(requestExtractor),
|
|
||||||
_responseAssigner(responseAssigner) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles POST requests: extracts payload from pre-decoded Request, updates state, returns Response
|
|
||||||
*/
|
|
||||||
esp_err_t handleStateUpdate(httpd_req_t* httpReq, api_Request* protoReq) {
|
|
||||||
ProtoT protoMsg = {};
|
|
||||||
if (!_requestExtractor(*protoReq, protoMsg)) {
|
|
||||||
return sendErrorResponse(httpReq, 400, "Invalid request type");
|
|
||||||
}
|
|
||||||
|
|
||||||
StateUpdateResult outcome = _statefulService->update(
|
|
||||||
[this, &protoMsg](T& settings) { return _stateUpdater(protoMsg, settings); }, PROTO_ENDPOINT_ORIGIN_ID);
|
|
||||||
|
|
||||||
if (outcome == StateUpdateResult::ERROR) {
|
|
||||||
return sendErrorResponse(httpReq, 400, "Invalid state");
|
|
||||||
}
|
|
||||||
|
|
||||||
return sendStateResponse(httpReq, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles GET requests: reads current state and returns it as Response
|
|
||||||
*/
|
|
||||||
esp_err_t getState(httpd_req_t* request) {
|
|
||||||
return sendStateResponse(request, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/** Sends current state wrapped in Response */
|
|
||||||
esp_err_t sendStateResponse(httpd_req_t* request, uint32_t statusCode) {
|
|
||||||
api_Response res = api_Response_init_zero;
|
|
||||||
res.status_code = statusCode;
|
|
||||||
|
|
||||||
ProtoT protoState = {};
|
|
||||||
_statefulService->read([this, &protoState](const T& settings) { _stateReader(settings, protoState); });
|
|
||||||
_responseAssigner(res, protoState);
|
|
||||||
|
|
||||||
return WebServer::send(request, 200, res, api_Response_fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sends error wrapped in Response */
|
|
||||||
esp_err_t sendErrorResponse(httpd_req_t* request, uint32_t statusCode, const char* message) {
|
|
||||||
api_Response res = api_Response_init_zero;
|
|
||||||
res.status_code = statusCode;
|
|
||||||
res.error_message = (char*)message;
|
|
||||||
return WebServer::send(request, statusCode == 200 ? 200 : 400, res, api_Response_fields);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Helper macros for defining request extractors and response assigners
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a request extractor lambda for a specific payload type
|
|
||||||
* Usage: API_REQUEST_EXTRACTOR(ap_settings, api_APSettings)
|
|
||||||
*/
|
|
||||||
#define API_REQUEST_EXTRACTOR(field_name, proto_type) \
|
|
||||||
[](const api_Request& req, proto_type& out) -> bool { \
|
|
||||||
if (req.which_payload == api_Request_##field_name##_tag) { \
|
|
||||||
out = req.payload.field_name; \
|
|
||||||
return true; \
|
|
||||||
} \
|
|
||||||
return false; \
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a response assigner lambda for a specific payload type
|
|
||||||
* Usage: API_RESPONSE_ASSIGNER(ap_settings, api_APSettings)
|
|
||||||
*/
|
|
||||||
#define API_RESPONSE_ASSIGNER(field_name, proto_type) \
|
|
||||||
[](api_Response& res, const proto_type& data) { \
|
|
||||||
res.which_payload = api_Response_##field_name##_tag; \
|
|
||||||
res.payload.field_name = data; \
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <functional>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/semphr.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <template/state_result.h>
|
|
||||||
|
|
||||||
using HandlerId = size_t;
|
|
||||||
using StateUpdateCallback = std::function<void(const std::string &originId)>;
|
|
||||||
using StateHookCallback = std::function<void(const std::string &originId, StateUpdateResult &result)>;
|
|
||||||
|
|
||||||
class HandlerBase {
|
|
||||||
protected:
|
|
||||||
static inline HandlerId nextId_ = 1;
|
|
||||||
HandlerId id_;
|
|
||||||
bool allowRemove_;
|
|
||||||
|
|
||||||
HandlerBase(bool allowRemove) : id_(nextId_++), allowRemove_(allowRemove) {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
HandlerId getId() const { return id_; }
|
|
||||||
bool isRemovable() const { return allowRemove_; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class UpdateHandler : public HandlerBase {
|
|
||||||
StateUpdateCallback callback_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
UpdateHandler(StateUpdateCallback callback, bool allowRemove)
|
|
||||||
: HandlerBase(allowRemove), callback_(std::move(callback)) {}
|
|
||||||
|
|
||||||
void invoke(const std::string &originId) const { callback_(originId); }
|
|
||||||
};
|
|
||||||
|
|
||||||
class HookHandler : public HandlerBase {
|
|
||||||
StateHookCallback callback_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
HookHandler(StateHookCallback callback, bool allowRemove)
|
|
||||||
: HandlerBase(allowRemove), callback_(std::move(callback)) {}
|
|
||||||
|
|
||||||
void invoke(const std::string &originId, StateUpdateResult &result) const { callback_(originId, result); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
class StatefulService {
|
|
||||||
public:
|
|
||||||
template <typename... Args>
|
|
||||||
StatefulService(Args &&...args) : state_(std::forward<Args>(args)...), mutex_(xSemaphoreCreateRecursiveMutex()) {}
|
|
||||||
|
|
||||||
HandlerId addUpdateHandler(StateUpdateCallback callback, bool allowRemove = true) {
|
|
||||||
if (!callback) return 0;
|
|
||||||
|
|
||||||
updateHandlers_.emplace_back(std::move(callback), allowRemove);
|
|
||||||
return updateHandlers_.back().getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeUpdateHandler(HandlerId id) {
|
|
||||||
updateHandlers_.remove_if(
|
|
||||||
[id](const UpdateHandler &handler) { return handler.isRemovable() && handler.getId() == id; });
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerId addHookHandler(StateHookCallback callback, bool allowRemove = true) {
|
|
||||||
if (!callback) return 0;
|
|
||||||
|
|
||||||
hookHandlers_.emplace_back(std::move(callback), allowRemove);
|
|
||||||
return hookHandlers_.back().getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeHookHandler(HandlerId id) {
|
|
||||||
hookHandlers_.remove_if(
|
|
||||||
[id](const HookHandler &handler) { return handler.isRemovable() && handler.getId() == id; });
|
|
||||||
}
|
|
||||||
|
|
||||||
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater, const std::string &originId) {
|
|
||||||
lock();
|
|
||||||
StateUpdateResult result = stateUpdater(state_);
|
|
||||||
unlock();
|
|
||||||
notifyStateChange(originId, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T &)> stateUpdater) {
|
|
||||||
lock();
|
|
||||||
StateUpdateResult result = stateUpdater(state_);
|
|
||||||
unlock();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void read(std::function<void(T &)> stateReader) {
|
|
||||||
lock();
|
|
||||||
stateReader(state_);
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void read(std::function<void(const T &)> stateReader) const {
|
|
||||||
const_cast<StatefulService *>(this)->lock();
|
|
||||||
stateReader(state_);
|
|
||||||
const_cast<StatefulService *>(this)->unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void callUpdateHandlers(const std::string &originId) {
|
|
||||||
for (const UpdateHandler &updateHandler : updateHandlers_) {
|
|
||||||
updateHandler.invoke(originId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void callHookHandlers(const std::string &originId, StateUpdateResult &result) {
|
|
||||||
for (const HookHandler &hookHandler : hookHandlers_) {
|
|
||||||
hookHandler.invoke(originId, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
T &state() { return state_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
T state_;
|
|
||||||
|
|
||||||
inline void lock() { xSemaphoreTakeRecursive(mutex_, portMAX_DELAY); }
|
|
||||||
inline void unlock() { xSemaphoreGiveRecursive(mutex_); }
|
|
||||||
|
|
||||||
void notifyStateChange(const std::string &originId, StateUpdateResult &result) {
|
|
||||||
callHookHandlers(originId, result);
|
|
||||||
if (result == StateUpdateResult::CHANGED) {
|
|
||||||
callUpdateHandlers(originId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SemaphoreHandle_t mutex_;
|
|
||||||
std::list<UpdateHandler> updateHandlers_;
|
|
||||||
std::list<HookHandler> hookHandlers_;
|
|
||||||
};
|
|
||||||
@@ -7,16 +7,16 @@
|
|||||||
|
|
||||||
#include <filesystem.h>
|
#include <filesystem.h>
|
||||||
#include <utils/timing.h>
|
#include <utils/timing.h>
|
||||||
#include <template/stateful_service.h>
|
#include <eventbus.hpp>
|
||||||
#include <template/stateful_persistence.h>
|
|
||||||
#include <template/stateful_proto_endpoint.h>
|
|
||||||
#include <settings/wifi_settings.h>
|
#include <settings/wifi_settings.h>
|
||||||
|
|
||||||
|
class WebServer;
|
||||||
|
|
||||||
#define WIFI_EVENT_STA_DISCONNECTED_IDF WIFI_EVENT_STA_DISCONNECTED
|
#define WIFI_EVENT_STA_DISCONNECTED_IDF WIFI_EVENT_STA_DISCONNECTED
|
||||||
#define WIFI_EVENT_STA_STOP_IDF WIFI_EVENT_STA_STOP
|
#define WIFI_EVENT_STA_STOP_IDF WIFI_EVENT_STA_STOP
|
||||||
#define IP_EVENT_STA_GOT_IP_IDF 1000
|
#define IP_EVENT_STA_GOT_IP_IDF 1000
|
||||||
|
|
||||||
class WiFiService : public StatefulService<WiFiSettings> {
|
class WiFiService {
|
||||||
public:
|
public:
|
||||||
WiFiService();
|
WiFiService();
|
||||||
~WiFiService();
|
~WiFiService();
|
||||||
@@ -27,20 +27,21 @@ class WiFiService : public StatefulService<WiFiSettings> {
|
|||||||
void setupMDNS(const char *hostname);
|
void setupMDNS(const char *hostname);
|
||||||
void selectNetwork(uint32_t index);
|
void selectNetwork(uint32_t index);
|
||||||
|
|
||||||
const char *getHostname() { return state().hostname; }
|
const char *getHostname() { return _settings.hostname; }
|
||||||
|
|
||||||
static esp_err_t handleScan(httpd_req_t *request);
|
static esp_err_t handleScan(httpd_req_t *request);
|
||||||
static esp_err_t getNetworks(httpd_req_t *request);
|
static esp_err_t getNetworks(httpd_req_t *request);
|
||||||
static esp_err_t getNetworkStatus(httpd_req_t *request);
|
static esp_err_t getNetworkStatus(httpd_req_t *request);
|
||||||
|
|
||||||
StatefulProtoEndpoint<WiFiSettings, api_WifiSettings> protoEndpoint;
|
void registerRoutes(WebServer &server);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onStationModeDisconnected(int32_t event, void *event_data);
|
void onStationModeDisconnected(int32_t event, void *event_data);
|
||||||
void onStationModeStop(int32_t event, void *event_data);
|
void onStationModeStop(int32_t event, void *event_data);
|
||||||
static void onStationModeGotIP(int32_t event, void *event_data);
|
static void onStationModeGotIP(int32_t event, void *event_data);
|
||||||
|
|
||||||
FSPersistencePB<WiFiSettings> _persistence;
|
WiFiSettings _settings {};
|
||||||
|
SubscriptionHandle _settingsHandle;
|
||||||
|
|
||||||
void reconfigureWiFiConnection();
|
void reconfigureWiFiConnection();
|
||||||
void manageSTA();
|
void manageSTA();
|
||||||
|
|||||||
+18
-18
@@ -3,17 +3,7 @@
|
|||||||
|
|
||||||
static const char *TAG = "APService";
|
static const char *TAG = "APService";
|
||||||
|
|
||||||
APService::APService()
|
APService::APService() : _dnsServer(nullptr), _lastManaged(0), _reconfigureAp(false), _recoveryMode(false) {}
|
||||||
: protoEndpoint(APSettings_read, APSettings_update, this, API_REQUEST_EXTRACTOR(ap_settings, api_APSettings),
|
|
||||||
API_RESPONSE_ASSIGNER(ap_settings, api_APSettings)),
|
|
||||||
_persistence(APSettings_read, APSettings_update, this, AP_SETTINGS_FILE, api_APSettings_fields,
|
|
||||||
api_APSettings_size, APSettings_defaults()),
|
|
||||||
_dnsServer(nullptr),
|
|
||||||
_lastManaged(0),
|
|
||||||
_reconfigureAp(false),
|
|
||||||
_recoveryMode(false) {
|
|
||||||
addUpdateHandler([&](const std::string &originId) { reconfigureAP(); }, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
APService::~APService() {
|
APService::~APService() {
|
||||||
if (_dnsServer) {
|
if (_dnsServer) {
|
||||||
@@ -22,7 +12,17 @@ APService::~APService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APService::begin() { _persistence.readFromFS(); }
|
void APService::begin() {
|
||||||
|
_settings = EventBus::instance().peek<APSettings>();
|
||||||
|
_settingsHandle = EventBus::instance().subscribe<APSettings>([this](const APSettings &s) {
|
||||||
|
_settings = s;
|
||||||
|
reconfigureAP();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void APService::registerRoutes(WebServer &s) {
|
||||||
|
s.on("/api/ap/status", HTTP_GET, [this](httpd_req_t *request) { return getStatusProto(request); });
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t APService::getStatusProto(httpd_req_t *request) {
|
esp_err_t APService::getStatusProto(httpd_req_t *request) {
|
||||||
api_Response res = api_Response_init_zero;
|
api_Response res = api_Response_init_zero;
|
||||||
@@ -44,7 +44,7 @@ void APService::statusProto(api_APStatus &proto) {
|
|||||||
APNetworkStatus APService::getAPNetworkStatus() {
|
APNetworkStatus APService::getAPNetworkStatus() {
|
||||||
wifi_mode_t currentWiFiMode = WiFi.getMode();
|
wifi_mode_t currentWiFiMode = WiFi.getMode();
|
||||||
bool apActive = currentWiFiMode == WIFI_MODE_AP || currentWiFiMode == WIFI_MODE_APSTA;
|
bool apActive = currentWiFiMode == WIFI_MODE_AP || currentWiFiMode == WIFI_MODE_APSTA;
|
||||||
if (apActive && state().provision_mode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
|
if (apActive && _settings.provision_mode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
|
||||||
return LINGERING;
|
return LINGERING;
|
||||||
}
|
}
|
||||||
return apActive ? ACTIVE : INACTIVE;
|
return apActive ? ACTIVE : INACTIVE;
|
||||||
@@ -70,8 +70,8 @@ void APService::loop() {
|
|||||||
|
|
||||||
void APService::manageAP() {
|
void APService::manageAP() {
|
||||||
wifi_mode_t currentWiFiMode = WiFi.getMode();
|
wifi_mode_t currentWiFiMode = WiFi.getMode();
|
||||||
if (state().provision_mode == AP_MODE_ALWAYS ||
|
if (_settings.provision_mode == AP_MODE_ALWAYS ||
|
||||||
(state().provision_mode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED) || _recoveryMode) {
|
(_settings.provision_mode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED) || _recoveryMode) {
|
||||||
if (_reconfigureAp || currentWiFiMode == WIFI_MODE_NULL || currentWiFiMode == WIFI_MODE_STA) {
|
if (_reconfigureAp || currentWiFiMode == WIFI_MODE_NULL || currentWiFiMode == WIFI_MODE_STA) {
|
||||||
startAP();
|
startAP();
|
||||||
}
|
}
|
||||||
@@ -83,9 +83,9 @@ void APService::manageAP() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APService::startAP() {
|
void APService::startAP() {
|
||||||
ESP_LOGI(TAG, "Starting software access point: %s", state().ssid);
|
ESP_LOGI(TAG, "Starting software access point: %s", _settings.ssid);
|
||||||
WiFi.softAPConfig(IPAddress(state().local_ip), IPAddress(state().gateway_ip), IPAddress(state().subnet_mask));
|
WiFi.softAPConfig(IPAddress(_settings.local_ip), IPAddress(_settings.gateway_ip), IPAddress(_settings.subnet_mask));
|
||||||
WiFi.softAP(state().ssid, state().password, state().channel, state().ssid_hidden, state().max_clients);
|
WiFi.softAP(_settings.ssid, _settings.password, _settings.channel, _settings.ssid_hidden, _settings.max_clients);
|
||||||
#if CONFIG_IDF_TARGET_ESP32C3
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
WiFi.setTxPower(8);
|
WiFi.setTxPower(8);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -11,6 +11,29 @@ static const char *TAG = "FileSystem";
|
|||||||
|
|
||||||
namespace FileSystem {
|
namespace FileSystem {
|
||||||
|
|
||||||
|
void registerRoutes(WebServer &s) {
|
||||||
|
s.on("/api/config/*", HTTP_GET, [](httpd_req_t *request) { return getConfigFile(request); });
|
||||||
|
s.on("/api/files", HTTP_GET, [](httpd_req_t *request) { return getFilesProto(request); });
|
||||||
|
s.on("/api/files/delete", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
|
||||||
|
if (protoReq->which_payload != api_Request_file_delete_request_tag) {
|
||||||
|
return WebServer::sendError(request, 400, "Invalid request payload");
|
||||||
|
}
|
||||||
|
return handleDelete(request, protoReq->payload.file_delete_request);
|
||||||
|
});
|
||||||
|
s.on("/api/files/edit", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
|
||||||
|
if (protoReq->which_payload != api_Request_file_edit_request_tag) {
|
||||||
|
return WebServer::sendError(request, 400, "Invalid request payload");
|
||||||
|
}
|
||||||
|
return handleEdit(request, protoReq->payload.file_edit_request);
|
||||||
|
});
|
||||||
|
s.on("/api/files/mkdir", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
|
||||||
|
if (protoReq->which_payload != api_Request_file_mkdir_request_tag) {
|
||||||
|
return WebServer::sendError(request, 400, "Invalid request payload");
|
||||||
|
}
|
||||||
|
return mkdir(request, protoReq->payload.file_mkdir_request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static std::vector<api_FileEntry *> allocatedEntries;
|
static std::vector<api_FileEntry *> allocatedEntries;
|
||||||
|
|
||||||
static void freeAllocatedEntries() {
|
static void freeAllocatedEntries() {
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ FileSystemHandler fsHandler;
|
|||||||
|
|
||||||
FileSystemHandler::FileSystemHandler() : transferIdCounter_(0) {}
|
FileSystemHandler::FileSystemHandler() : transferIdCounter_(0) {}
|
||||||
|
|
||||||
|
void FileSystemHandler::begin() {
|
||||||
|
uploadHandle_ = EventBus::instance().subscribe<socket_message_FSUploadData>(
|
||||||
|
[this](const socket_message_FSUploadData& data) { handleUploadData(data); });
|
||||||
|
}
|
||||||
|
|
||||||
void FileSystemHandler::setSendCallbacks(SendMetadataCallback sendMetadata, SendCallback sendData,
|
void FileSystemHandler::setSendCallbacks(SendMetadataCallback sendMetadata, SendCallback sendData,
|
||||||
SendCompleteCallback sendComplete,
|
SendCompleteCallback sendComplete,
|
||||||
SendUploadCompleteCallback sendUploadComplete) {
|
SendUploadCompleteCallback sendUploadComplete) {
|
||||||
|
|||||||
+38
-80
@@ -20,6 +20,10 @@
|
|||||||
#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 <eventbus.hpp>
|
||||||
|
#include <event_storage_manager.hpp>
|
||||||
|
#include <event_types.h>
|
||||||
|
#include <settings/camera_settings.h>
|
||||||
|
|
||||||
#if CONFIG_IDF_TARGET_ESP32P4
|
#if CONFIG_IDF_TARGET_ESP32P4
|
||||||
#include <esp_hosted.h>
|
#include <esp_hosted.h>
|
||||||
@@ -44,75 +48,36 @@ MDNSService mdnsService;
|
|||||||
|
|
||||||
WiFiService wifiService;
|
WiFiService wifiService;
|
||||||
APService apService;
|
APService apService;
|
||||||
|
EventStorageManager storageManager;
|
||||||
|
|
||||||
|
static SubscriptionHandle modeHandle;
|
||||||
|
|
||||||
void setupServer() {
|
void setupServer() {
|
||||||
server.config(50 + WWW_ASSETS_COUNT, 16384);
|
server.config(50 + WWW_ASSETS_COUNT, 16384);
|
||||||
server.listen(80);
|
server.listen(80);
|
||||||
|
|
||||||
server.on("/api/system/reset", HTTP_POST,
|
PROTO_ROUTE(server, "/api/servo/config", servo_settings, ServoSettings);
|
||||||
[&](httpd_req_t *request, api_Request *protoReq) { return system_service::handleReset(request); });
|
PROTO_ROUTE(server, "/api/wifi/sta/settings", wifi_settings, WiFiSettings);
|
||||||
server.on("/api/system/restart", HTTP_POST,
|
PROTO_ROUTE(server, "/api/ap/settings", ap_settings, APSettings);
|
||||||
[&](httpd_req_t *request, api_Request *protoReq) { return system_service::handleRestart(request); });
|
PROTO_ROUTE(server, "/api/peripherals/settings", peripheral_settings, PeripheralsConfiguration);
|
||||||
server.on("/api/system/sleep", HTTP_POST,
|
|
||||||
[&](httpd_req_t *request, api_Request *protoReq) { return system_service::handleSleep(request); });
|
|
||||||
#if USE_CAMERA
|
|
||||||
server.on("/api/camera/still", HTTP_GET, [&](httpd_req_t *request) { return cameraService.cameraStill(request); });
|
|
||||||
server.on("/api/camera/stream", HTTP_GET,
|
|
||||||
[&](httpd_req_t *request) { return cameraService.cameraStream(request); });
|
|
||||||
#if USE_DVP_CAMERA
|
|
||||||
server.on("/api/camera/settings", HTTP_GET,
|
|
||||||
[&](httpd_req_t *request) { return cameraService.protoEndpoint.getState(request); });
|
|
||||||
server.on("/api/camera/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
|
||||||
return cameraService.protoEndpoint.handleStateUpdate(request, protoReq);
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
server.on("/api/servo/config", HTTP_GET,
|
|
||||||
[&](httpd_req_t *request) { return servoController.protoEndpoint.getState(request); });
|
|
||||||
server.on("/api/servo/config", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
|
||||||
return servoController.protoEndpoint.handleStateUpdate(request, protoReq);
|
|
||||||
});
|
|
||||||
|
|
||||||
server.on("/api/wifi/sta/settings", HTTP_GET,
|
|
||||||
[&](httpd_req_t *request) { return wifiService.protoEndpoint.getState(request); });
|
|
||||||
server.on("/api/wifi/sta/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
|
||||||
return wifiService.protoEndpoint.handleStateUpdate(request, protoReq);
|
|
||||||
});
|
|
||||||
server.on("/api/wifi/scan", HTTP_GET, [&](httpd_req_t *request) { return wifiService.handleScan(request); });
|
|
||||||
server.on("/api/wifi/networks", HTTP_GET, [&](httpd_req_t *request) { return wifiService.getNetworks(request); });
|
|
||||||
server.on("/api/wifi/sta/status", HTTP_GET,
|
|
||||||
[&](httpd_req_t *request) { return wifiService.getNetworkStatus(request); });
|
|
||||||
|
|
||||||
server.on("/api/ap/status", HTTP_GET, [&](httpd_req_t *request) { return apService.getStatusProto(request); });
|
|
||||||
server.on("/api/ap/settings", HTTP_GET,
|
|
||||||
[&](httpd_req_t *request) { return apService.protoEndpoint.getState(request); });
|
|
||||||
server.on("/api/ap/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
|
||||||
return apService.protoEndpoint.handleStateUpdate(request, protoReq);
|
|
||||||
});
|
|
||||||
|
|
||||||
server.on("/api/peripherals/settings", HTTP_GET,
|
|
||||||
[&](httpd_req_t *request) { return peripherals.protoEndpoint.getState(request); });
|
|
||||||
server.on("/api/peripherals/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
|
||||||
return peripherals.protoEndpoint.handleStateUpdate(request, protoReq);
|
|
||||||
});
|
|
||||||
|
|
||||||
#if FT_ENABLED(USE_MDNS)
|
#if FT_ENABLED(USE_MDNS)
|
||||||
server.on("/api/mdns/settings", HTTP_GET,
|
PROTO_ROUTE(server, "/api/mdns/settings", mdns_settings, MDNSSettings);
|
||||||
[&](httpd_req_t *request) { return mdnsService.protoEndpoint.getState(request); });
|
#endif
|
||||||
server.on("/api/mdns/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
#if FT_ENABLED(USE_CAMERA) && USE_DVP_CAMERA
|
||||||
return mdnsService.protoEndpoint.handleStateUpdate(request, protoReq);
|
PROTO_ROUTE(server, "/api/camera/settings", camera_settings, Camera::CameraSettings);
|
||||||
});
|
|
||||||
server.on("/api/mdns/status", HTTP_GET, [&](httpd_req_t *request) { return mdnsService.getStatus(request); });
|
|
||||||
server.on("/api/mdns/query", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
|
||||||
return mdnsService.queryServices(request, protoReq);
|
|
||||||
});
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
server.on("/api/config/*", HTTP_GET, [](httpd_req_t *request) { return FileSystem::getConfigFile(request); });
|
system_service::registerRoutes(server);
|
||||||
server.on("/api/files", HTTP_GET, [&](httpd_req_t *request) { return FileSystem::getFilesProto(request); });
|
wifiService.registerRoutes(server);
|
||||||
STAITC_PROTO_POST_ENDPOINT(server, "/api/files/delete", file_delete_request, FileSystem::handleDelete);
|
apService.registerRoutes(server);
|
||||||
STAITC_PROTO_POST_ENDPOINT(server, "/api/files/edit", file_edit_request, FileSystem::handleEdit);
|
#if FT_ENABLED(USE_MDNS)
|
||||||
STAITC_PROTO_POST_ENDPOINT(server, "/api/files/mkdir", file_mkdir_request, FileSystem::mkdir);
|
mdnsService.registerRoutes(server);
|
||||||
|
#endif
|
||||||
|
#if FT_ENABLED(USE_CAMERA)
|
||||||
|
cameraService.registerRoutes(server);
|
||||||
|
#endif
|
||||||
|
FileSystem::registerRoutes(server);
|
||||||
|
|
||||||
wsSocket.begin();
|
wsSocket.begin();
|
||||||
#if EMBED_WEBAPP
|
#if EMBED_WEBAPP
|
||||||
mountStaticAssets(server);
|
mountStaticAssets(server);
|
||||||
@@ -136,31 +101,21 @@ void setupEventSocket() {
|
|||||||
[](const socket_message_FSDownloadComplete &complete, int clientId) { wsSocket.emit(complete, clientId); },
|
[](const socket_message_FSDownloadComplete &complete, int clientId) { wsSocket.emit(complete, clientId); },
|
||||||
[](const socket_message_FSUploadComplete &complete, int clientId) { wsSocket.emit(complete, clientId); });
|
[](const socket_message_FSUploadComplete &complete, int clientId) { wsSocket.emit(complete, clientId); });
|
||||||
|
|
||||||
wsSocket.on<socket_message_ControllerData>(
|
wsSocket.forward<socket_message_ControllerData>();
|
||||||
[&](const socket_message_ControllerData &data, int clientId) { motionService.handleInput(data); });
|
wsSocket.forward<socket_message_ModeData>();
|
||||||
|
wsSocket.forward<socket_message_WalkGaitData>();
|
||||||
|
wsSocket.forward<socket_message_AnglesData>();
|
||||||
|
wsSocket.forward<socket_message_ServoPWMData>();
|
||||||
|
wsSocket.forward<socket_message_ServoStateData>();
|
||||||
|
wsSocket.forward<socket_message_FSUploadData>();
|
||||||
|
|
||||||
wsSocket.on<socket_message_ModeData>([&](const socket_message_ModeData &data, int clientId) {
|
modeHandle = EventBus::instance().subscribe<socket_message_ModeData>([&](const socket_message_ModeData &data) {
|
||||||
servoController.setMode(SERVO_CONTROL_STATE::ANGLE);
|
servoController.setMode(SERVO_CONTROL_STATE::ANGLE);
|
||||||
motionService.handleMode(data);
|
motionService.handleMode(data);
|
||||||
motionService.isActive() ? servoController.activate() : servoController.deactivate();
|
motionService.isActive() ? servoController.activate() : servoController.deactivate();
|
||||||
});
|
});
|
||||||
|
|
||||||
wsSocket.on<socket_message_WalkGaitData>(
|
FileSystemWS::fsHandler.begin();
|
||||||
[&](const socket_message_WalkGaitData &data, int clientId) { motionService.handleWalkGait(data); });
|
|
||||||
|
|
||||||
wsSocket.on<socket_message_AnglesData>(
|
|
||||||
[&](const socket_message_AnglesData &data, int clientId) { motionService.handleAngles(data); });
|
|
||||||
|
|
||||||
wsSocket.on<socket_message_ServoPWMData>([&](const socket_message_ServoPWMData &data, int clientId) {
|
|
||||||
servoController.setServoPWM(data.servo_id, data.servo_pwm);
|
|
||||||
});
|
|
||||||
|
|
||||||
wsSocket.on<socket_message_ServoStateData>([&](const socket_message_ServoStateData &data, int clientId) {
|
|
||||||
data.active ? servoController.activate() : servoController.deactivate();
|
|
||||||
});
|
|
||||||
|
|
||||||
wsSocket.on<socket_message_FSUploadData>(
|
|
||||||
[&](const socket_message_FSUploadData &data, int clientId) { FileSystemWS::fsHandler.handleUploadData(data); });
|
|
||||||
|
|
||||||
using CorrelationHandler =
|
using CorrelationHandler =
|
||||||
std::function<void(const socket_message_CorrelationRequest &, socket_message_CorrelationResponse &, int)>;
|
std::function<void(const socket_message_CorrelationRequest &, socket_message_CorrelationResponse &, int)>;
|
||||||
@@ -353,6 +308,7 @@ extern "C" void app_main(void) {
|
|||||||
ESP_ERROR_CHECK(ret);
|
ESP_ERROR_CHECK(ret);
|
||||||
|
|
||||||
FileSystem::init();
|
FileSystem::init();
|
||||||
|
storageManager.initialize();
|
||||||
|
|
||||||
ESP_LOGI("main", "Booting robot");
|
ESP_LOGI("main", "Booting robot");
|
||||||
|
|
||||||
@@ -362,5 +318,7 @@ extern "C" void app_main(void) {
|
|||||||
|
|
||||||
xTaskCreatePinnedToCore(SpotControlLoopEntry, "Control task", 8192, nullptr, 5, nullptr, 1);
|
xTaskCreatePinnedToCore(SpotControlLoopEntry, "Control task", 8192, nullptr, 5, nullptr, 1);
|
||||||
|
|
||||||
|
EventBus::instance().publish(SystemReadyEvent {});
|
||||||
|
|
||||||
ESP_LOGI("main", "Finished booting");
|
ESP_LOGI("main", "Finished booting");
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-27
@@ -4,14 +4,7 @@
|
|||||||
|
|
||||||
static const char *TAG = "MDNSService";
|
static const char *TAG = "MDNSService";
|
||||||
|
|
||||||
MDNSService::MDNSService()
|
MDNSService::MDNSService() {}
|
||||||
: protoEndpoint(MDNSSettings_read, MDNSSettings_update, this,
|
|
||||||
API_REQUEST_EXTRACTOR(mdns_settings, api_MDNSSettings),
|
|
||||||
API_RESPONSE_ASSIGNER(mdns_settings, api_MDNSSettings)),
|
|
||||||
_persistence(MDNSSettings_read, MDNSSettings_update, this, MDNS_SETTINGS_FILE, api_MDNSSettings_fields,
|
|
||||||
api_MDNSSettings_size, MDNSSettings_defaults()) {
|
|
||||||
addUpdateHandler([&](const std::string &originId) { reconfigureMDNS(); }, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
MDNSService::~MDNSService() {
|
MDNSService::~MDNSService() {
|
||||||
if (_started) {
|
if (_started) {
|
||||||
@@ -19,8 +12,18 @@ MDNSService::~MDNSService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MDNSService::registerRoutes(WebServer &s) {
|
||||||
|
s.on("/api/mdns/status", HTTP_GET, [this](httpd_req_t *request) { return getStatus(request); });
|
||||||
|
s.on("/api/mdns/query", HTTP_POST,
|
||||||
|
[this](httpd_req_t *request, api_Request *protoReq) { return queryServices(request, protoReq); });
|
||||||
|
}
|
||||||
|
|
||||||
void MDNSService::begin() {
|
void MDNSService::begin() {
|
||||||
_persistence.readFromFS();
|
_settings = EventBus::instance().peek<MDNSSettings>();
|
||||||
|
_settingsHandle = EventBus::instance().subscribe<MDNSSettings>([this](const MDNSSettings &s) {
|
||||||
|
_settings = s;
|
||||||
|
reconfigureMDNS();
|
||||||
|
});
|
||||||
startMDNS();
|
startMDNS();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +35,7 @@ void MDNSService::reconfigureMDNS() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MDNSService::startMDNS() {
|
void MDNSService::startMDNS() {
|
||||||
ESP_LOGV(TAG, "Starting MDNS with hostname: %s", state().hostname);
|
ESP_LOGV(TAG, "Starting MDNS with hostname: %s", _settings.hostname);
|
||||||
|
|
||||||
esp_err_t err = mdns_init();
|
esp_err_t err = mdns_init();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
@@ -41,7 +44,7 @@ void MDNSService::startMDNS() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mdns_hostname_set(state().hostname);
|
err = mdns_hostname_set(_settings.hostname);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to set MDNS hostname: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "Failed to set MDNS hostname: %s", esp_err_to_name(err));
|
||||||
mdns_free();
|
mdns_free();
|
||||||
@@ -49,7 +52,7 @@ void MDNSService::startMDNS() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mdns_instance_name_set(state().instance);
|
err = mdns_instance_name_set(_settings.instance);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "Failed to set MDNS instance name: %s", esp_err_to_name(err));
|
ESP_LOGW(TAG, "Failed to set MDNS instance name: %s", esp_err_to_name(err));
|
||||||
}
|
}
|
||||||
@@ -57,7 +60,7 @@ void MDNSService::startMDNS() {
|
|||||||
_started = true;
|
_started = true;
|
||||||
addServices();
|
addServices();
|
||||||
|
|
||||||
ESP_LOGI(TAG, "MDNS started successfully with hostname: %s", state().hostname);
|
ESP_LOGI(TAG, "MDNS started successfully with hostname: %s", _settings.hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MDNSService::stopMDNS() {
|
void MDNSService::stopMDNS() {
|
||||||
@@ -67,8 +70,8 @@ void MDNSService::stopMDNS() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MDNSService::addServices() {
|
void MDNSService::addServices() {
|
||||||
for (size_t i = 0; i < state().services_count; i++) {
|
for (size_t i = 0; i < _settings.services_count; i++) {
|
||||||
const auto &service = state().services[i];
|
const auto &service = _settings.services[i];
|
||||||
esp_err_t err = mdns_service_add(nullptr, service.service, service.protocol, service.port, nullptr, 0);
|
esp_err_t err = mdns_service_add(nullptr, service.service, service.protocol, service.port, nullptr, 0);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "Failed to add service %s: %s", service.service, esp_err_to_name(err));
|
ESP_LOGW(TAG, "Failed to add service %s: %s", service.service, esp_err_to_name(err));
|
||||||
@@ -81,10 +84,10 @@ void MDNSService::addServices() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < state().global_txt_records_count; i++) {
|
for (size_t i = 0; i < _settings.global_txt_records_count; i++) {
|
||||||
const auto &txt = state().global_txt_records[i];
|
const auto &txt = _settings.global_txt_records[i];
|
||||||
for (size_t j = 0; j < state().services_count; j++) {
|
for (size_t j = 0; j < _settings.services_count; j++) {
|
||||||
const auto &service = state().services[j];
|
const auto &service = _settings.services[j];
|
||||||
mdns_service_txt_item_set(service.service, service.protocol, txt.key, txt.value);
|
mdns_service_txt_item_set(service.service, service.protocol, txt.key, txt.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,17 +99,17 @@ esp_err_t MDNSService::getStatus(httpd_req_t *request) {
|
|||||||
|
|
||||||
MDNSStatus &status = response.payload.mdns_status;
|
MDNSStatus &status = response.payload.mdns_status;
|
||||||
status.started = _started;
|
status.started = _started;
|
||||||
strncpy(status.hostname, state().hostname, sizeof(status.hostname) - 1);
|
strncpy(status.hostname, _settings.hostname, sizeof(status.hostname) - 1);
|
||||||
strncpy(status.instance, state().instance, sizeof(status.instance) - 1);
|
strncpy(status.instance, _settings.instance, sizeof(status.instance) - 1);
|
||||||
|
|
||||||
status.services_count = state().services_count;
|
status.services_count = _settings.services_count;
|
||||||
for (size_t i = 0; i < state().services_count; i++) {
|
for (size_t i = 0; i < _settings.services_count; i++) {
|
||||||
status.services[i] = state().services[i];
|
status.services[i] = _settings.services[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
status.global_txt_records_count = state().global_txt_records_count;
|
status.global_txt_records_count = _settings.global_txt_records_count;
|
||||||
for (size_t i = 0; i < state().global_txt_records_count; i++) {
|
for (size_t i = 0; i < _settings.global_txt_records_count; i++) {
|
||||||
status.global_txt_records[i] = state().global_txt_records[i];
|
status.global_txt_records[i] = _settings.global_txt_records[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebServer::send(request, 200, response, api_Response_fields);
|
return WebServer::send(request, 200, response, api_Response_fields);
|
||||||
|
|||||||
+10
-1
@@ -1,6 +1,15 @@
|
|||||||
#include <motion.h>
|
#include <motion.h>
|
||||||
|
|
||||||
void MotionService::begin() { body_state.updateFeet(KinConfig::default_feet_positions); }
|
void MotionService::begin() {
|
||||||
|
body_state.updateFeet(KinConfig::default_feet_positions);
|
||||||
|
|
||||||
|
controllerHandle_ = EventBus::instance().subscribe<socket_message_ControllerData>(
|
||||||
|
[this](const socket_message_ControllerData& data) { handleInput(data); });
|
||||||
|
walkGaitHandle_ = EventBus::instance().subscribe<socket_message_WalkGaitData>(
|
||||||
|
[this](const socket_message_WalkGaitData& data) { handleWalkGait(data); });
|
||||||
|
anglesHandle_ = EventBus::instance().subscribe<socket_message_AnglesData>(
|
||||||
|
[this](const socket_message_AnglesData& data) { handleAngles(data); });
|
||||||
|
}
|
||||||
|
|
||||||
void MotionService::handleAngles(const socket_message_AnglesData& data) {
|
void MotionService::handleAngles(const socket_message_AnglesData& data) {
|
||||||
for (int i = 0; i < 12 && i < data.angles_count; i++) {
|
for (int i = 0; i < 12 && i < data.angles_count; i++) {
|
||||||
|
|||||||
@@ -35,17 +35,14 @@ sensor_t *safe_sensor_get() {
|
|||||||
|
|
||||||
void safe_sensor_return() { xSemaphoreGiveRecursive(cameraMutex); }
|
void safe_sensor_return() { xSemaphoreGiveRecursive(cameraMutex); }
|
||||||
|
|
||||||
CameraService::CameraService()
|
CameraService::CameraService() {}
|
||||||
: protoEndpoint(CameraSettings_read, CameraSettings_update, this,
|
|
||||||
API_REQUEST_EXTRACTOR(camera_settings, api_CameraSettings),
|
|
||||||
API_RESPONSE_ASSIGNER(camera_settings, api_CameraSettings)),
|
|
||||||
_persistence(CameraSettings_read, CameraSettings_update, this, CAMERA_SETTINGS_FILE, api_CameraSettings_fields,
|
|
||||||
api_CameraSettings_size, CameraSettings_defaults()) {
|
|
||||||
addUpdateHandler([&](const std::string &originId) { updateCamera(); }, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t CameraService::begin() {
|
esp_err_t CameraService::begin() {
|
||||||
_persistence.readFromFS();
|
_settings = EventBus::instance().peek<CameraSettings>();
|
||||||
|
_settingsHandle = EventBus::instance().subscribe<CameraSettings>([this](const CameraSettings &s) {
|
||||||
|
_settings = s;
|
||||||
|
updateCamera();
|
||||||
|
});
|
||||||
camera_config_t camera_config;
|
camera_config_t camera_config;
|
||||||
camera_config.ledc_channel = LEDC_CHANNEL_0;
|
camera_config.ledc_channel = LEDC_CHANNEL_0;
|
||||||
camera_config.ledc_timer = LEDC_TIMER_0;
|
camera_config.ledc_timer = LEDC_TIMER_0;
|
||||||
@@ -155,30 +152,30 @@ void CameraService::updateCamera() {
|
|||||||
safe_sensor_return();
|
safe_sensor_return();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
s->set_pixformat(s, static_cast<pixformat_t>(state().pixformat));
|
s->set_pixformat(s, static_cast<pixformat_t>(_settings.pixformat));
|
||||||
s->set_framesize(s, static_cast<framesize_t>(state().framesize));
|
s->set_framesize(s, static_cast<framesize_t>(_settings.framesize));
|
||||||
s->set_brightness(s, state().brightness);
|
s->set_brightness(s, _settings.brightness);
|
||||||
s->set_contrast(s, state().contrast);
|
s->set_contrast(s, _settings.contrast);
|
||||||
s->set_saturation(s, state().saturation);
|
s->set_saturation(s, _settings.saturation);
|
||||||
s->set_sharpness(s, state().sharpness);
|
s->set_sharpness(s, _settings.sharpness);
|
||||||
s->set_denoise(s, state().denoise);
|
s->set_denoise(s, _settings.denoise);
|
||||||
s->set_gainceiling(s, static_cast<gainceiling_t>(state().gainceiling));
|
s->set_gainceiling(s, static_cast<gainceiling_t>(_settings.gainceiling));
|
||||||
s->set_quality(s, state().quality);
|
s->set_quality(s, _settings.quality);
|
||||||
s->set_colorbar(s, state().colorbar);
|
s->set_colorbar(s, _settings.colorbar);
|
||||||
s->set_awb_gain(s, state().awb_gain);
|
s->set_awb_gain(s, _settings.awb_gain);
|
||||||
s->set_wb_mode(s, state().wb_mode);
|
s->set_wb_mode(s, _settings.wb_mode);
|
||||||
s->set_aec2(s, state().aec2);
|
s->set_aec2(s, _settings.aec2);
|
||||||
s->set_ae_level(s, state().ae_level);
|
s->set_ae_level(s, _settings.ae_level);
|
||||||
s->set_aec_value(s, state().aec_value);
|
s->set_aec_value(s, _settings.aec_value);
|
||||||
s->set_agc_gain(s, state().agc_gain);
|
s->set_agc_gain(s, _settings.agc_gain);
|
||||||
s->set_bpc(s, state().bpc);
|
s->set_bpc(s, _settings.bpc);
|
||||||
s->set_wpc(s, state().wpc);
|
s->set_wpc(s, _settings.wpc);
|
||||||
s->set_special_effect(s, state().special_effect);
|
s->set_special_effect(s, _settings.special_effect);
|
||||||
s->set_raw_gma(s, state().raw_gma);
|
s->set_raw_gma(s, _settings.raw_gma);
|
||||||
s->set_lenc(s, state().lenc);
|
s->set_lenc(s, _settings.lenc);
|
||||||
s->set_hmirror(s, state().hmirror);
|
s->set_hmirror(s, _settings.hmirror);
|
||||||
s->set_vflip(s, state().vflip);
|
s->set_vflip(s, _settings.vflip);
|
||||||
s->set_dcw(s, state().dcw);
|
s->set_dcw(s, _settings.dcw);
|
||||||
safe_sensor_return();
|
safe_sensor_return();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,6 +625,11 @@ esp_err_t CameraService::cameraStream(httpd_req_t *request) {
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CameraService::registerRoutes(WebServer &s) {
|
||||||
|
s.on("/api/camera/still", HTTP_GET, [this](httpd_req_t *request) { return cameraStill(request); });
|
||||||
|
s.on("/api/camera/stream", HTTP_GET, [this](httpd_req_t *request) { return cameraStream(request); });
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
CameraService::CameraService() {}
|
CameraService::CameraService() {}
|
||||||
@@ -639,6 +641,11 @@ esp_err_t CameraService::cameraStream(httpd_req_t *request) {
|
|||||||
return WebServer::sendError(request, 501, "Camera not supported on this platform");
|
return WebServer::sendError(request, 501, "Camera not supported on this platform");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CameraService::registerRoutes(WebServer &s) {
|
||||||
|
s.on("/api/camera/still", HTTP_GET, [this](httpd_req_t *request) { return cameraStill(request); });
|
||||||
|
s.on("/api/camera/stream", HTTP_GET, [this](httpd_req_t *request) { return cameraStream(request); });
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace Camera
|
} // namespace Camera
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
#include <peripherals/peripherals.h>
|
#include <peripherals/peripherals.h>
|
||||||
|
|
||||||
Peripherals::Peripherals()
|
Peripherals::Peripherals() { _accessMutex = xSemaphoreCreateMutex(); }
|
||||||
: protoEndpoint(PeripheralsConfiguration_read, PeripheralsConfiguration_update, this,
|
|
||||||
API_REQUEST_EXTRACTOR(peripheral_settings, api_PeripheralSettings),
|
|
||||||
API_RESPONSE_ASSIGNER(peripheral_settings, api_PeripheralSettings)),
|
|
||||||
_persistence(PeripheralsConfiguration_read, PeripheralsConfiguration_update, this,
|
|
||||||
PERIPHERAL_SETTINGS_FILE, api_PeripheralSettings_fields, api_PeripheralSettings_size,
|
|
||||||
PeripheralsConfiguration_defaults()) {
|
|
||||||
_accessMutex = xSemaphoreCreateMutex();
|
|
||||||
addUpdateHandler([&](const std::string &originId) { updatePins(); }, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Peripherals::begin() {
|
void Peripherals::begin() {
|
||||||
_persistence.readFromFS();
|
_settings = EventBus::instance().peek<PeripheralsConfiguration>();
|
||||||
|
_settingsHandle =
|
||||||
|
EventBus::instance().subscribe<PeripheralsConfiguration>([this](const PeripheralsConfiguration &s) {
|
||||||
|
_settings = s;
|
||||||
|
updatePins();
|
||||||
|
});
|
||||||
|
|
||||||
updatePins();
|
updatePins();
|
||||||
|
|
||||||
@@ -47,9 +43,9 @@ void Peripherals::updatePins() {
|
|||||||
I2CBus::instance().end();
|
I2CBus::instance().end();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state().sda != -1 && state().scl != -1) {
|
if (_settings.sda != -1 && _settings.scl != -1) {
|
||||||
esp_err_t err = I2CBus::instance().begin(static_cast<gpio_num_t>(state().sda),
|
esp_err_t err = I2CBus::instance().begin(static_cast<gpio_num_t>(_settings.sda),
|
||||||
static_cast<gpio_num_t>(state().scl), state().frequency);
|
static_cast<gpio_num_t>(_settings.scl), _settings.frequency);
|
||||||
i2c_active = (err == ESP_OK);
|
i2c_active = (err == ESP_OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,13 +88,12 @@ void Peripherals::getIMUProto(socket_message_IMUData &data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Peripherals::getSettingsProto(socket_message_PeripheralSettingsData &data) {
|
void Peripherals::getSettingsProto(socket_message_PeripheralSettingsData &data) {
|
||||||
data.sda = state().sda;
|
data.sda = _settings.sda;
|
||||||
data.scl = state().scl;
|
data.scl = _settings.scl;
|
||||||
data.frequency = state().frequency;
|
data.frequency = _settings.frequency;
|
||||||
data.pins_count = 0;
|
data.pins_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IMU FUNCTIONS */
|
|
||||||
bool Peripherals::readImu() {
|
bool Peripherals::readImu() {
|
||||||
bool updated = false;
|
bool updated = false;
|
||||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||||
|
|||||||
@@ -59,6 +59,15 @@ esp_err_t handleSleep(httpd_req_t *request) {
|
|||||||
return WebServer::sendOk(request);
|
return WebServer::sendOk(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void registerRoutes(WebServer &s) {
|
||||||
|
s.on("/api/system/reset", HTTP_POST,
|
||||||
|
[](httpd_req_t *request, api_Request *protoReq) { return handleReset(request); });
|
||||||
|
s.on("/api/system/restart", HTTP_POST,
|
||||||
|
[](httpd_req_t *request, api_Request *protoReq) { return handleRestart(request); });
|
||||||
|
s.on("/api/system/sleep", HTTP_POST,
|
||||||
|
[](httpd_req_t *request, api_Request *protoReq) { return handleSleep(request); });
|
||||||
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
ESP_LOGI(TAG, "Resetting device");
|
ESP_LOGI(TAG, "Resetting device");
|
||||||
DIR *dir = opendir(FS_CONFIG_DIRECTORY);
|
DIR *dir = opendir(FS_CONFIG_DIRECTORY);
|
||||||
|
|||||||
+26
-29
@@ -3,17 +3,14 @@
|
|||||||
|
|
||||||
static const char *TAG = "WiFiService";
|
static const char *TAG = "WiFiService";
|
||||||
|
|
||||||
WiFiService::WiFiService()
|
void WiFiService::registerRoutes(WebServer &s) {
|
||||||
: protoEndpoint(WiFiSettings_read, WiFiSettings_update, this,
|
s.on("/api/wifi/scan", HTTP_GET, [](httpd_req_t *request) { return handleScan(request); });
|
||||||
API_REQUEST_EXTRACTOR(wifi_settings, api_WifiSettings),
|
s.on("/api/wifi/networks", HTTP_GET, [](httpd_req_t *request) { return getNetworks(request); });
|
||||||
API_RESPONSE_ASSIGNER(wifi_settings, api_WifiSettings)),
|
s.on("/api/wifi/sta/status", HTTP_GET, [](httpd_req_t *request) { return getNetworkStatus(request); });
|
||||||
_persistence(WiFiSettings_read, WiFiSettings_update, this, WIFI_SETTINGS_FILE, api_WifiSettings_fields,
|
|
||||||
api_WifiSettings_size, WiFiSettings_defaults()),
|
|
||||||
_lastConnectionAttempt(0),
|
|
||||||
_stopping(false) {
|
|
||||||
addUpdateHandler([&](const std::string &originId) { reconfigureWiFiConnection(); }, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WiFiService::WiFiService() : _lastConnectionAttempt(0), _stopping(false) {}
|
||||||
|
|
||||||
WiFiService::~WiFiService() {}
|
WiFiService::~WiFiService() {}
|
||||||
|
|
||||||
void WiFiService::begin() {
|
void WiFiService::begin() {
|
||||||
@@ -25,15 +22,19 @@ void WiFiService::begin() {
|
|||||||
WiFi.onEvent([this](int32_t event, void *data) { this->onStationModeStop(event, data); }, WIFI_EVENT_STA_STOP);
|
WiFi.onEvent([this](int32_t event, void *data) { this->onStationModeStop(event, data); }, WIFI_EVENT_STA_STOP);
|
||||||
WiFi.onEvent(onStationModeGotIP, IP_EVENT_STA_GOT_IP_IDF);
|
WiFi.onEvent(onStationModeGotIP, IP_EVENT_STA_GOT_IP_IDF);
|
||||||
|
|
||||||
_persistence.readFromFS();
|
_settings = EventBus::instance().peek<WiFiSettings>();
|
||||||
|
_settingsHandle = EventBus::instance().subscribe<WiFiSettings>([this](const WiFiSettings &s) {
|
||||||
|
_settings = s;
|
||||||
|
reconfigureWiFiConnection();
|
||||||
|
});
|
||||||
_lastConnectionAttempt = 0;
|
_lastConnectionAttempt = 0;
|
||||||
|
|
||||||
if (state().wifi_networks_count >= 1) {
|
if (_settings.wifi_networks_count >= 1) {
|
||||||
WiFi.mode(WIFI_MODE_STA);
|
WiFi.mode(WIFI_MODE_STA);
|
||||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
uint32_t idx = state().selected_network;
|
uint32_t idx = _settings.selected_network;
|
||||||
if (idx >= state().wifi_networks_count) idx = 0;
|
if (idx >= _settings.wifi_networks_count) idx = 0;
|
||||||
configureNetwork(state().wifi_networks[idx]);
|
configureNetwork(_settings.wifi_networks[idx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,13 +44,9 @@ void WiFiService::reconfigureWiFiConnection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WiFiService::selectNetwork(uint32_t index) {
|
void WiFiService::selectNetwork(uint32_t index) {
|
||||||
if (index >= state().wifi_networks_count) return;
|
if (index >= _settings.wifi_networks_count) return;
|
||||||
updateWithoutPropagation([&](WiFiSettings &settings) {
|
_settings.selected_network = index;
|
||||||
settings.selected_network = index;
|
EventBus::instance().publish(_settings);
|
||||||
return StateUpdateResult::CHANGED;
|
|
||||||
});
|
|
||||||
_persistence.writeToFS();
|
|
||||||
reconfigureWiFiConnection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiService::loop() { EXECUTE_EVERY_N_MS(reconnectDelay, manageSTA()); }
|
void WiFiService::loop() { EXECUTE_EVERY_N_MS(reconnectDelay, manageSTA()); }
|
||||||
@@ -99,7 +96,7 @@ esp_err_t WiFiService::getNetworks(httpd_req_t *request) {
|
|||||||
|
|
||||||
void WiFiService::setupMDNS(const char *hostname) {
|
void WiFiService::setupMDNS(const char *hostname) {
|
||||||
mdns_init();
|
mdns_init();
|
||||||
mdns_hostname_set(state().hostname);
|
mdns_hostname_set(_settings.hostname);
|
||||||
mdns_instance_name_set(hostname);
|
mdns_instance_name_set(hostname);
|
||||||
mdns_service_add(nullptr, "_http", "_tcp", 80, nullptr, 0);
|
mdns_service_add(nullptr, "_http", "_tcp", 80, nullptr, 0);
|
||||||
mdns_service_add(nullptr, "_ws", "_tcp", 80, nullptr, 0);
|
mdns_service_add(nullptr, "_ws", "_tcp", 80, nullptr, 0);
|
||||||
@@ -138,7 +135,7 @@ esp_err_t WiFiService::getNetworkStatus(httpd_req_t *request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WiFiService::manageSTA() {
|
void WiFiService::manageSTA() {
|
||||||
if (WiFi.isConnected() || state().wifi_networks_count == 0) return;
|
if (WiFi.isConnected() || _settings.wifi_networks_count == 0) return;
|
||||||
wifi_mode_t mode = WiFi.getMode();
|
wifi_mode_t mode = WiFi.getMode();
|
||||||
if (mode == WIFI_MODE_NULL || mode == WIFI_MODE_AP) return;
|
if (mode == WIFI_MODE_NULL || mode == WIFI_MODE_AP) return;
|
||||||
|
|
||||||
@@ -153,12 +150,12 @@ void WiFiService::manageSTA() {
|
|||||||
uint32_t now = esp_timer_get_time() / 1000;
|
uint32_t now = esp_timer_get_time() / 1000;
|
||||||
if (now - startTime < 3000) return;
|
if (now - startTime < 3000) return;
|
||||||
|
|
||||||
if (!attempted && state().wifi_networks_count > 0) {
|
if (!attempted && _settings.wifi_networks_count > 0) {
|
||||||
attempted = true;
|
attempted = true;
|
||||||
uint32_t idx = state().selected_network;
|
uint32_t idx = _settings.selected_network;
|
||||||
if (idx >= state().wifi_networks_count) idx = 0;
|
if (idx >= _settings.wifi_networks_count) idx = 0;
|
||||||
ESP_LOGI(TAG, "Connecting to: %s", state().wifi_networks[idx].ssid);
|
ESP_LOGI(TAG, "Connecting to: %s", _settings.wifi_networks[idx].ssid);
|
||||||
configureNetwork(state().wifi_networks[idx]);
|
configureNetwork(_settings.wifi_networks[idx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +166,7 @@ void WiFiService::configureNetwork(WiFiNetwork &network) {
|
|||||||
} else {
|
} else {
|
||||||
WiFi.config(IPAddress(0, 0, 0, 0), IPAddress(0, 0, 0, 0), IPAddress(0, 0, 0, 0));
|
WiFi.config(IPAddress(0, 0, 0, 0), IPAddress(0, 0, 0, 0), IPAddress(0, 0, 0, 0));
|
||||||
}
|
}
|
||||||
WiFi.setHostname(state().hostname);
|
WiFi.setHostname(_settings.hostname);
|
||||||
WiFi.begin(network.ssid, network.password);
|
WiFi.begin(network.ssid, network.password);
|
||||||
|
|
||||||
#if CONFIG_IDF_TARGET_ESP32C3
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
|||||||
@@ -143,8 +143,8 @@ A PyBullet-based physics simulation is available for algorithm development and r
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd simulation
|
cd simulation
|
||||||
uv sync
|
pip install -r requirements.txt
|
||||||
uv run play.py
|
python play.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
@@ -245,7 +245,7 @@ Complete build instructions are available in the documentation:
|
|||||||
**Build and flash:**
|
**Build and flash:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone --recurse-submodules https://github.com/runeharlyk/SpotMicroESP32-Leika
|
git clone https://github.com/runeharlyk/SpotMicroESP32-Leika
|
||||||
cd SpotMicroESP32-Leika
|
cd SpotMicroESP32-Leika
|
||||||
|
|
||||||
cd app
|
cd app
|
||||||
@@ -264,8 +264,8 @@ To experiment with the simulation environments without hardware:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd simulation
|
cd simulation
|
||||||
uv sync
|
pip install -r requirements.txt
|
||||||
uv run play.py
|
python play.py
|
||||||
```
|
```
|
||||||
|
|
||||||
For development workflows and contribution guidelines, see [docs/6_developing.md](docs/6_developing.md) and [docs/7_contributing.md](docs/7_contributing.md).
|
For development workflows and contribution guidelines, see [docs/6_developing.md](docs/6_developing.md) and [docs/7_contributing.md](docs/7_contributing.md).
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
3.13
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
[project]
|
|
||||||
name = "simulation-leika"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Add your description here"
|
|
||||||
readme = "README.md"
|
|
||||||
requires-python = ">=3.13"
|
|
||||||
dependencies = [
|
|
||||||
"gymnasium>=1.2.3",
|
|
||||||
"matplotlib>=3.10.9",
|
|
||||||
"msgpack>=1.1.2",
|
|
||||||
"numpy>=2.4.4",
|
|
||||||
"onnx>=1.21.0",
|
|
||||||
"onnxruntime>=1.26.0",
|
|
||||||
"pybullet>=3.2.7",
|
|
||||||
"stable-baselines3[extra]>=2.0.0",
|
|
||||||
"tensorboard>=2.20.0",
|
|
||||||
"tensorflow>=2.21.0",
|
|
||||||
"torch>=2.11.0",
|
|
||||||
"tqdm>=4.67.3",
|
|
||||||
"websockets>=16.0",
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
torch
|
||||||
|
onnx
|
||||||
|
onnxruntime
|
||||||
|
tensorflow
|
||||||
|
numpy
|
||||||
|
matplotlib
|
||||||
|
pybullet
|
||||||
|
websockets
|
||||||
|
msgpack
|
||||||
|
asyncio
|
||||||
|
gymnasium
|
||||||
|
stable-baselines3[extra]>=2.0.0
|
||||||
|
tensorboard
|
||||||
|
tqdm
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
from src.envs.quadruped_env import QuadrupedEnv
|
||||||
|
from training.model import SimpleNN
|
||||||
|
|
||||||
|
import resources as resources
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
env = QuadrupedEnv(resources.getDataPath() + "/spot.urdf")
|
||||||
|
env.reset()
|
||||||
|
|
||||||
|
input_size = env.robot.get_observation().shape[0]
|
||||||
|
output_size = env.robot.get_observation().shape[0]
|
||||||
|
agent = SimpleNN(input_size, output_size)
|
||||||
|
|
||||||
|
done = False
|
||||||
|
observation = []
|
||||||
|
|
||||||
|
while not done:
|
||||||
|
action = agent.select_action(observation)
|
||||||
|
observation, reward, done = env.step(action)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Generated
-1724
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user