6 Commits

Author SHA1 Message Date
Rune Harlyk 5863598fbc Adds callback based event bus 2026-02-09 22:45:43 +01:00
Rune Harlyk eba00f98cd Clean up macros 2026-02-09 16:39:46 +01:00
Rune Harlyk 43e7f13888 Adds working camera stream for p4 with correct colors and good perf 2026-02-09 16:39:46 +01:00
Rune Harlyk d81b1b0851 🙏 Working camera stream with p4 2026-02-09 16:39:46 +01:00
Rune Harlyk bf2fd957af Adds support for esp32 P4 2026-02-09 16:39:46 +01:00
Rune Harlyk d6075deb6c 🔥 Removes old stateful persistence 2026-02-01 00:45:55 +01:00
53 changed files with 1367 additions and 1387 deletions
+1 -1
View File
@@ -13,7 +13,7 @@
}, },
"editor.tabSize": 4, "editor.tabSize": 4,
"editor.detectIndentation": false, "editor.detectIndentation": false,
"cmake.sourceDirectory": "C:/data/repos/Hardware/Spot Micro - Leika/.pio/libdeps/esp32cam/esp32-camera", "cmake.sourceDirectory": "C:/data/repos/Hardware/Spot_Micro_Leika",
"cSpell.words": [ "cSpell.words": [
"Adafruit", "Adafruit",
"IRAM", "IRAM",
+33
View File
@@ -0,0 +1,33 @@
{
"build": {
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM"
],
"f_cpu": "360000000L",
"f_flash": "80000000L",
"f_psram": "200000000L",
"flash_mode": "qio",
"mcu": "esp32p4",
"variant": "esp32p4"
},
"connectivity": [
"wifi"
],
"debug": {
"openocd_target": "esp32p4.cfg"
},
"frameworks": [
"espidf"
],
"name": "ESP32-P4 Dev Board (32MB PSRAM + 32MB Flash, C6 coprocessor)",
"upload": {
"flash_size": "32MB",
"maximum_ram_size": 786432,
"maximum_size": 33554432,
"require_upload_port": true,
"speed": 1500000
},
"url": "https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/",
"vendor": "Espressif"
}
+7 -10
View File
@@ -1,14 +1,16 @@
#pragma once #pragma once
#include <event_bus/event_bus.h> #include <esp_http_server.h>
#include <eventbus.hpp>
#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>
#include <wifi/dns_server.h> #include <wifi/dns_server.h>
#include <esp_timer.h> #include <esp_timer.h>
#include <esp_http_server.h>
#include <string> #include <string>
class WebServer;
class APService { class APService {
public: public:
APService(); APService();
@@ -22,16 +24,11 @@ class APService {
void statusProto(api_APStatus &proto); void statusProto(api_APStatus &proto);
APNetworkStatus getAPNetworkStatus(); APNetworkStatus getAPNetworkStatus();
esp_err_t getSettings(httpd_req_t *request); void registerRoutes(WebServer &server);
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
private: private:
static constexpr const char *TAG = "APService"; APSettings _settings {};
SubscriptionHandle _settingsHandle;
void onSettingsChanged(const api_APSettings &newSettings);
APSettings _settings = APSettings_defaults();
EventBus::Handle<api_APSettings> _settingsHandle;
DNSServer *_dnsServer; DNSServer *_dnsServer;
volatile unsigned long _lastManaged; volatile unsigned long _lastManaged;
+7 -40
View File
@@ -6,11 +6,9 @@
#include <functional> #include <functional>
#include <list> #include <list>
#include <map> #include <map>
#include <memory>
#include <vector>
#include <type_traits> #include <type_traits>
#include <communication/proto_helpers.h> #include <communication/proto_helpers.h>
#include <event_bus/event_bus.h> #include <eventbus.hpp>
class CommAdapterBase { class CommAdapterBase {
public: public:
@@ -39,23 +37,8 @@ class CommAdapterBase {
} }
template <typename T> template <typename T>
void onPublish(std::function<void(const T&, int)> handler = nullptr) { void forward() {
decoder_.on<T>([this, handler](const T& data, int clientId) { decoder_.on<T>([](const T& data, int) { EventBus::instance().publish(data); });
EventBus::publish(data);
if (handler) handler(data, clientId);
});
}
template <typename T>
void bridgeFromEventBus() {
eventBusHandles_.push_back(
std::make_unique<EventBusHandleStorage<T>>(EventBus::subscribe<T>([this](const T& data) { emit(data); })));
}
template <typename T>
void bridgeFromEventBus(uint32_t intervalMs) {
eventBusHandles_.push_back(std::make_unique<EventBusHandleStorage<T>>(
EventBus::subscribe<T>(intervalMs, [this](const T& data) { emit(data); })));
} }
template <typename T> template <typename T>
@@ -64,8 +47,6 @@ class CommAdapterBase {
if (clientId < 0 && !hasSubscribers(tag)) return; if (clientId < 0 && !hasSubscribers(tag)) return;
xSemaphoreTake(mutex_, portMAX_DELAY);
msg_.which_message = tag; msg_.which_message = tag;
MessageTraits<T>::assign(msg_, data); MessageTraits<T>::assign(msg_, data);
@@ -79,22 +60,18 @@ class CommAdapterBase {
pb_ostream_t stream = pb_ostream_from_buffer(buffer, out_size); pb_ostream_t stream = pb_ostream_from_buffer(buffer, out_size);
if (!pb_encode(&stream, socket_message_Message_fields, &msg_)) { if (!pb_encode(&stream, socket_message_Message_fields, &msg_)) {
ESP_LOGE("ProtoComm", "Failed to encode message (tag %d), buffer too small?", (int)tag); ESP_LOGE("ProtoComm", "Failed to encode message (tag %d), buffer too small?", (int)tag);
xSemaphoreGive(mutex_);
if (pb_heap_enc_buf != buffer) free(buffer);
return; return;
} }
if (clientId >= 0) { if (clientId >= 0) {
send(buffer, stream.bytes_written, clientId); send(buffer, stream.bytes_written, clientId);
} else { } else {
sendToSubscribersLocked(tag, buffer, stream.bytes_written); sendToSubscribers(tag, buffer, stream.bytes_written);
} }
if (pb_heap_enc_buf != buffer) { if (pb_heap_enc_buf != buffer) {
free(buffer); free(buffer);
} }
xSemaphoreGive(mutex_);
} }
protected: protected:
@@ -144,22 +121,12 @@ class CommAdapterBase {
socket_message_Message msg_ = socket_message_Message_init_zero; socket_message_Message msg_ = socket_message_Message_init_zero;
uint8_t pb_heap_enc_buf[PROTO_BUFFER_SIZE]; uint8_t pb_heap_enc_buf[PROTO_BUFFER_SIZE];
struct EventBusHandleBase {
virtual ~EventBusHandleBase() = default;
};
template <typename T>
struct EventBusHandleStorage : EventBusHandleBase {
EventBus::Handle<T> handle;
EventBusHandleStorage(EventBus::Handle<T>&& h) : handle(std::move(h)) {}
};
std::vector<std::unique_ptr<EventBusHandleBase>> eventBusHandles_;
private: private:
void sendToSubscribersLocked(int32_t tag, const uint8_t* data, size_t len) { void sendToSubscribers(int32_t tag, const uint8_t* data, size_t len) {
xSemaphoreTake(mutex_, portMAX_DELAY);
for (int cid : client_subscriptions_[tag]) { for (int cid : client_subscriptions_[tag]) {
send(data, len, cid); send(data, len, cid);
} }
xSemaphoreGive(mutex_);
} }
}; };
+46 -11
View File
@@ -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,16 +23,21 @@ 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 {
std::string uri; std::string uri;
@@ -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);
@@ -1,51 +0,0 @@
#pragma once
#include <consumers/proto_event_storage.h>
#include <platform_shared/api.pb.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 <esp_log.h>
class EventStorageManager {
public:
void initialize() {
ESP_LOGI(TAG, "Loading settings from storage");
_wifiStorage.begin();
_apStorage.begin();
_mdnsStorage.begin();
_peripheralStorage.begin();
#if FT_ENABLED(USE_CAMERA)
_cameraStorage.begin();
#endif
ESP_LOGI(TAG, "Settings loaded and published");
}
private:
static constexpr const char* TAG = "StorageManager";
ProtoEventStorage<api_WifiSettings, WiFiSettings_defaults> _wifiStorage =
ProtoEventStorage<api_WifiSettings, WiFiSettings_defaults>("/config/wifiSettings.pb", api_WifiSettings_fields,
api_WifiSettings_size, 1000);
ProtoEventStorage<api_APSettings, APSettings_defaults> _apStorage =
ProtoEventStorage<api_APSettings, APSettings_defaults>("/config/apSettings.pb", api_APSettings_fields,
api_APSettings_size, 1000);
ProtoEventStorage<api_MDNSSettings, MDNSSettings_defaults> _mdnsStorage =
ProtoEventStorage<api_MDNSSettings, MDNSSettings_defaults>("/config/mdnsSettings.pb", api_MDNSSettings_fields,
api_MDNSSettings_size, 1000);
ProtoEventStorage<api_PeripheralSettings, PeripheralsConfiguration_defaults> _peripheralStorage =
ProtoEventStorage<api_PeripheralSettings, PeripheralsConfiguration_defaults>(
"/config/peripheralSettings.pb", api_PeripheralSettings_fields, api_PeripheralSettings_size, 500);
#if FT_ENABLED(USE_CAMERA)
ProtoEventStorage<api_CameraSettings, Camera::CameraSettings_defaults> _cameraStorage =
ProtoEventStorage<api_CameraSettings, Camera::CameraSettings_defaults>(
"/config/cameraSettings.pb", api_CameraSettings_fields, api_CameraSettings_size, 1000);
#endif
};
@@ -1,72 +0,0 @@
#pragma once
#include <event_bus/event_bus.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include <esp_log.h>
#include <memory>
#include <cstdio>
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() {
auto loaded = std::unique_ptr<ProtoMsg>(new ProtoMsg {});
loadOrDefault(*loaded);
EventBus::publish(*loaded, "EventStorage");
ESP_LOGI(TAG, "Loaded %s", _filename);
_handle = EventBus::subscribe<ProtoMsg>(_debounceMs, [this](const ProtoMsg& msg) { save(msg); });
}
private:
static constexpr const char* TAG = "ProtoStorage";
const char* _filename;
const pb_msgdesc_t* _descriptor;
size_t _maxSize;
uint32_t _debounceMs;
typename EventBus::Handle<ProtoMsg> _handle;
void loadOrDefault(ProtoMsg& outMsg) {
FILE* file = fopen(_filename, "rb");
if (!file) {
outMsg = DefaultsFn();
return;
}
fseek(file, 0, SEEK_END);
size_t size = ftell(file);
fseek(file, 0, SEEK_SET);
if (size == 0 || size > _maxSize) {
fclose(file);
outMsg = DefaultsFn();
return;
}
auto buffer = std::make_unique<uint8_t[]>(size);
fread(buffer.get(), 1, size, file);
fclose(file);
pb_istream_t stream = pb_istream_from_buffer(buffer.get(), size);
if (!pb_decode(&stream, _descriptor, &outMsg)) {
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)) return;
FILE* file = fopen(_filename, "wb");
if (!file) return;
fwrite(buffer.get(), 1, stream.bytes_written, file);
fclose(file);
ESP_LOGD(TAG, "Saved %s", _filename);
}
};
-65
View File
@@ -1,65 +0,0 @@
#pragma once
#include <event_bus/typed_event_bus.h>
#include <event_bus/event_registry.h>
#include <event_bus/event_metadata.h>
#include <esp_timer.h>
#include <atomic>
class EventBus {
public:
template <typename Msg>
using Bus = TypedEventBus<Msg, EventBusConfig<Msg>::QueueDepth, EventBusConfig<Msg>::MaxSubs,
EventBusConfig<Msg>::BatchSize>;
template <typename Msg>
using Handle = typename Bus<Msg>::Handle;
template <typename Msg>
static bool publish(const Msg& msg, const char* source = nullptr) {
if (_hasGlobalListeners.load(std::memory_order_acquire)) {
notifyGlobalListeners(msg, source);
}
return Bus<Msg>::publish(msg);
}
template <typename Msg, typename Callback>
static auto subscribe(Callback&& callback) {
return Bus<Msg>::subscribe(std::forward<Callback>(callback));
}
template <typename Msg, typename Callback>
static auto subscribe(uint32_t intervalMs, Callback&& callback) {
return Bus<Msg>::subscribe(intervalMs, std::forward<Callback>(callback));
}
template <typename Msg>
static void publishISR(const Msg& msg, BaseType_t* higherPriorityTaskWoken = nullptr) {
Bus<Msg>::publishISR(msg, higherPriorityTaskWoken);
}
template <typename Msg>
static bool peek(Msg& out) {
return Bus<Msg>::peek(out);
}
template <typename Msg>
static bool take(Msg& out) {
return Bus<Msg>::take(out);
}
template <typename Msg>
static bool hasSubscribers() {
return Bus<Msg>::hasSubscribers();
}
using GlobalHandler = FixedFn<void(EventType, const void*, size_t, uint64_t), 64>;
static size_t subscribeGlobal(GlobalHandler&& handler);
static void unsubscribeGlobal(size_t id);
private:
static std::atomic<bool> _hasGlobalListeners;
template <typename Msg>
static void notifyGlobalListeners(const Msg& msg, const char* source);
};
-16
View File
@@ -1,16 +0,0 @@
#pragma once
#include <event_bus/event_types.h>
#include <cstdint>
struct EventMetadata {
EventType type;
uint64_t timestamp;
uint32_t sequence;
const char* source;
};
template <typename Msg>
struct EventEnvelope {
EventMetadata metadata;
Msg payload;
};
-63
View File
@@ -1,63 +0,0 @@
#pragma once
#include <event_bus/event_types.h>
#include <platform_shared/api.pb.h>
#include <platform_shared/message.pb.h>
template <typename T>
struct EventTypeTraits;
template <typename T>
struct EventBusConfig {
static constexpr size_t QueueDepth = 64;
static constexpr size_t MaxSubs = 8;
static constexpr size_t BatchSize = 16;
};
#define REGISTER_EVENT_TYPE(MsgType, EventTypeValue) \
template <> \
struct EventTypeTraits<MsgType> { \
static constexpr EventType type = EventTypeValue; \
static constexpr const char* name = #MsgType; \
};
#define REGISTER_SETTINGS_TYPE(MsgType, EventTypeValue) \
REGISTER_EVENT_TYPE(MsgType, EventTypeValue) \
template <> \
struct EventBusConfig<MsgType> { \
static constexpr size_t QueueDepth = 1; \
static constexpr size_t MaxSubs = 6; \
static constexpr size_t BatchSize = 1; \
};
REGISTER_SETTINGS_TYPE(api_WifiSettings, EventType::WIFI_SETTINGS)
REGISTER_SETTINGS_TYPE(api_APSettings, EventType::AP_SETTINGS)
REGISTER_SETTINGS_TYPE(api_MDNSSettings, EventType::MDNS_SETTINGS)
REGISTER_SETTINGS_TYPE(api_PeripheralSettings, EventType::PERIPHERAL_SETTINGS)
REGISTER_SETTINGS_TYPE(api_ServoSettings, EventType::SERVO_SETTINGS)
REGISTER_SETTINGS_TYPE(api_CameraSettings, EventType::CAMERA_SETTINGS)
#define REGISTER_COMMAND_TYPE(MsgType, EventTypeValue) \
REGISTER_EVENT_TYPE(MsgType, EventTypeValue) \
template <> \
struct EventBusConfig<MsgType> { \
static constexpr size_t QueueDepth = 1; \
static constexpr size_t MaxSubs = 3; \
static constexpr size_t BatchSize = 1; \
};
#define REGISTER_STREAM_TYPE(MsgType, EventTypeValue) \
REGISTER_EVENT_TYPE(MsgType, EventTypeValue) \
template <> \
struct EventBusConfig<MsgType> { \
static constexpr size_t QueueDepth = 4; \
static constexpr size_t MaxSubs = 3; \
static constexpr size_t BatchSize = 4; \
};
REGISTER_STREAM_TYPE(socket_message_IMUData, EventType::IMU_DATA)
REGISTER_COMMAND_TYPE(socket_message_ControllerData, EventType::MOTION_COMMAND)
REGISTER_COMMAND_TYPE(socket_message_ModeData, EventType::MOTION_MODE)
REGISTER_COMMAND_TYPE(socket_message_AnglesData, EventType::MOTION_ANGLES)
REGISTER_COMMAND_TYPE(socket_message_WalkGaitData, EventType::MOTION_WALK_GAIT)
REGISTER_COMMAND_TYPE(socket_message_ServoStateData, EventType::SERVO_STATE)
REGISTER_COMMAND_TYPE(socket_message_ServoPWMData, EventType::SERVO_PWM)
-27
View File
@@ -1,27 +0,0 @@
#pragma once
#include <cstdint>
enum class EventType : uint16_t {
WIFI_SETTINGS = 100,
AP_SETTINGS = 110,
MDNS_SETTINGS = 120,
PERIPHERAL_SETTINGS = 130,
SERVO_SETTINGS = 140,
CAMERA_SETTINGS = 150,
WIFI_STATUS = 101,
AP_STATUS = 111,
IMU_DATA = 131,
MOTION_COMMAND = 200,
MOTION_MODE = 201,
MOTION_ANGLES = 202,
MOTION_WALK_GAIT = 203,
SERVO_STATE = 141,
SERVO_PWM = 142,
SYSTEM_BOOT = 300,
STORAGE_HYDRATION_COMPLETE = 301,
};
const char* eventTypeName(EventType type);
bool isSettingsEvent(EventType type);
-55
View File
@@ -1,55 +0,0 @@
#pragma once
#include <event_bus/event_bus.h>
#include <event_bus/event_registry.h>
#include <communication/webserver.h>
#include <esp_http_server.h>
template <typename TMsg, pb_size_t RequestTag, pb_size_t ResponseTag>
class RestSettingsEndpoint {
public:
static esp_err_t getSettings(httpd_req_t *request) {
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = ResponseTag;
TMsg settings;
if (!EventBus::peek(settings)) {
return WebServer::sendError(request, 404, "Settings not found");
}
*reinterpret_cast<TMsg *>(&response.payload) = settings;
return WebServer::send(request, 200, response, api_Response_fields);
}
static esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq) {
if (protoReq->which_payload != RequestTag) {
return WebServer::sendError(request, 400, "Invalid payload type");
}
const TMsg &settings = *reinterpret_cast<const TMsg *>(&protoReq->payload);
EventBus::publish(settings, "HTTPEndpoint");
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = api_Response_empty_message_tag;
return WebServer::send(request, 200, response, api_Response_fields);
}
};
using WiFiSettingsEndpoint =
RestSettingsEndpoint<api_WifiSettings, api_Request_wifi_settings_tag, api_Response_wifi_settings_tag>;
using ServoSettingsEndpoint =
RestSettingsEndpoint<api_ServoSettings, api_Request_servo_settings_tag, api_Response_servo_settings_tag>;
using PeripheralSettingsEndpoint = RestSettingsEndpoint<api_PeripheralSettings, api_Request_peripheral_settings_tag,
api_Response_peripheral_settings_tag>;
using APSettingsEndpoint =
RestSettingsEndpoint<api_APSettings, api_Request_ap_settings_tag, api_Response_ap_settings_tag>;
using MDNSSettingsEndpoint =
RestSettingsEndpoint<api_MDNSSettings, api_Request_mdns_settings_tag, api_Response_mdns_settings_tag>;
using CameraSettingsEndpoint =
RestSettingsEndpoint<api_CameraSettings, api_Request_camera_settings_tag, api_Response_camera_settings_tag>;
-11
View File
@@ -1,11 +0,0 @@
#pragma once
#include <cstdint>
enum class SystemEventType : uint8_t {
STORAGE_HYDRATION_COMPLETE,
SYSTEM_BOOT_COMPLETE,
};
struct SystemEvent {
SystemEventType type;
};
-337
View File
@@ -1,337 +0,0 @@
#pragma once
#include <vector>
#include <functional>
#include <type_traits>
#include <cstddef>
#include <cstring>
#include <array>
#include <optional>
#include <atomic>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <esp_heap_caps.h>
template <typename Sig, size_t MaxSize>
class FixedFn;
template <typename R, typename... A, size_t MaxSize>
class FixedFn<R(A...), MaxSize> {
alignas(void*) std::byte buf[MaxSize];
R (*call)(void*, A&&...) {};
void (*moveFn)(void*, void*) {};
void (*destroy)(void*) {};
public:
template <typename Fun>
void set(Fun&& f) {
static_assert(sizeof(Fun) <= MaxSize);
new (buf) Fun(std::forward<Fun>(f));
call = [](void* p, A&&... as) -> R { return (*reinterpret_cast<Fun*>(p))(std::forward<A>(as)...); };
moveFn = [](void* d, void* s) { new (d) Fun(std::move(*reinterpret_cast<Fun*>(s))); };
destroy = [](void* p) { reinterpret_cast<Fun*>(p)->~Fun(); };
}
R operator()(A... as) const {
return call(const_cast<void*>(static_cast<const void*>(buf)), std::forward<A>(as)...);
}
FixedFn() = default;
FixedFn(FixedFn&& o) noexcept {
if (o.moveFn) o.moveFn(buf, o.buf);
call = o.call;
moveFn = o.moveFn;
destroy = o.destroy;
o.call = nullptr;
o.moveFn = nullptr;
o.destroy = nullptr;
}
FixedFn(const FixedFn&) = delete;
FixedFn& operator=(const FixedFn&) = delete;
FixedFn& operator=(FixedFn&&) = delete;
~FixedFn() {
if (destroy) destroy(buf);
}
};
enum class EmitMode { Latest, Batch };
template <typename Msg, size_t QueueDepth = 64, size_t MaxSubs = 8, size_t BatchSize = 16>
class TypedEventBus {
struct Item {
Msg payload;
size_t exclude;
};
static constexpr size_t NO_EX = MaxSubs;
struct Sub {
FixedFn<void(const Msg*, size_t), 48> cb;
TickType_t interval;
TickType_t last;
EmitMode mode;
Msg* buf;
size_t cnt;
std::atomic<bool> enabled;
std::atomic<uint32_t> running;
Sub() : buf(nullptr), cnt(0), enabled(false), running(0) {
buf = static_cast<Msg*>(heap_caps_malloc(BatchSize * sizeof(Msg), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
if (!buf) buf = static_cast<Msg*>(malloc(BatchSize * sizeof(Msg)));
}
~Sub() {
if (buf) free(buf);
}
Sub(const Sub&) = delete;
Sub& operator=(const Sub&) = delete;
};
struct BusState {
QueueHandle_t queue;
Sub* subs[MaxSubs];
portMUX_TYPE mux;
Msg latest;
std::atomic<bool> hasLatest;
std::atomic<size_t> subCount;
std::atomic<bool> taskStarted;
BusState()
: queue(nullptr),
mux(portMUX_INITIALIZER_UNLOCKED),
latest {},
hasLatest(false),
subCount(0),
taskStarted(false) {
for (size_t i = 0; i < MaxSubs; ++i) subs[i] = nullptr;
}
};
static BusState& state() {
static BusState s;
return s;
}
static void ensureQueue() {
auto& s = state();
if (s.queue) return;
portENTER_CRITICAL(&s.mux);
if (!s.queue) {
s.queue = xQueueCreate(QueueDepth, sizeof(Item));
}
portEXIT_CRITICAL(&s.mux);
}
static void storeISR(const Msg& m) {
auto& s = state();
UBaseType_t saved = portSET_INTERRUPT_MASK_FROM_ISR();
s.latest = m;
s.hasLatest.store(true, std::memory_order_release);
portCLEAR_INTERRUPT_MASK_FROM_ISR(saved);
}
static void dispatch(const Msg& m, size_t ex) {
auto& s = state();
TickType_t now = xTaskGetTickCount();
Sub* ready[MaxSubs];
size_t readyCnt = 0;
portENTER_CRITICAL(&s.mux);
for (size_t i = 0; i < MaxSubs; ++i) {
Sub* sub = s.subs[i];
if (!sub || i == ex) continue;
if (!sub->enabled.load(std::memory_order_acquire)) continue;
TickType_t dt = now - sub->last;
if (sub->interval && dt < sub->interval) {
if (sub->mode == EmitMode::Batch) {
if (sub->cnt < BatchSize)
sub->buf[sub->cnt++] = m;
else
sub->buf[BatchSize - 1] = m;
} else {
sub->buf[0] = m;
sub->cnt = 1;
}
} else {
if (sub->cnt < BatchSize)
sub->buf[sub->cnt++] = m;
else
sub->buf[BatchSize - 1] = m;
sub->last = now;
sub->running.fetch_add(1, std::memory_order_acq_rel);
ready[readyCnt++] = sub;
}
}
portEXIT_CRITICAL(&s.mux);
for (size_t i = 0; i < readyCnt; ++i) {
Sub* sub = ready[i];
sub->cb(sub->buf, sub->cnt);
sub->cnt = 0;
sub->running.fetch_sub(1, std::memory_order_acq_rel);
}
}
static void worker(void*) {
auto& s = state();
Item it;
while (xQueueReceive(s.queue, &it, portMAX_DELAY) == pdTRUE) dispatch(it.payload, it.exclude);
}
static void ensureTask() {
auto& s = state();
if (!s.taskStarted.load(std::memory_order_acquire)) {
bool expected = false;
if (s.taskStarted.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) {
ensureQueue();
xTaskCreatePinnedToCore(worker, "evtbus", 4096, nullptr, 6, nullptr, 1);
}
}
}
static bool push(const Msg& m, size_t ex = NO_EX, TickType_t to = 0) {
ensureTask();
auto& s = state();
Item it {m, ex};
return xQueueSend(s.queue, &it, to) == pdTRUE;
}
public:
class Handle {
size_t idx {NO_EX};
friend class TypedEventBus;
explicit Handle(size_t i) : idx(i) {}
public:
Handle() = default;
Handle(const Handle&) = delete;
Handle& operator=(const Handle&) = delete;
Handle(Handle&& o) noexcept : idx(o.idx) { o.idx = NO_EX; }
Handle& operator=(Handle&& o) noexcept {
if (this != &o) {
unsubscribe();
idx = o.idx;
o.idx = NO_EX;
}
return *this;
}
~Handle() { unsubscribe(); }
void unsubscribe() {
if (idx < MaxSubs) {
auto& s = state();
Sub* sub = nullptr;
portENTER_CRITICAL(&s.mux);
sub = s.subs[idx];
if (sub) {
sub->enabled.store(false, std::memory_order_release);
}
portEXIT_CRITICAL(&s.mux);
if (sub) {
while (sub->running.load(std::memory_order_acquire) != 0) taskYIELD();
portENTER_CRITICAL(&s.mux);
s.subs[idx] = nullptr;
portEXIT_CRITICAL(&s.mux);
delete sub;
s.subCount.fetch_sub(1, std::memory_order_acq_rel);
}
idx = NO_EX;
}
}
bool valid() const { return idx < MaxSubs; }
};
static void store(const Msg& m) {
auto& s = state();
portENTER_CRITICAL(&s.mux);
s.latest = m;
s.hasLatest.store(true, std::memory_order_release);
portEXIT_CRITICAL(&s.mux);
}
template <typename C>
static void consume(C fn) {
static Handle h = subscribe(std::forward<C>(fn));
(void)h;
}
template <typename C>
static Handle subscribe(uint32_t ms, EmitMode mode, C fn) {
ensureTask();
auto& s = state();
portENTER_CRITICAL(&s.mux);
for (size_t i = 0; i < MaxSubs; ++i) {
if (!s.subs[i]) {
Sub* sub = new Sub();
sub->cb.set(std::move(fn));
sub->interval = pdMS_TO_TICKS(ms);
sub->last = xTaskGetTickCount() - sub->interval;
sub->mode = mode;
sub->cnt = 0;
sub->enabled.store(true, std::memory_order_release);
sub->running.store(0, std::memory_order_release);
s.subs[i] = sub;
s.subCount.fetch_add(1, std::memory_order_acq_rel);
portEXIT_CRITICAL(&s.mux);
return Handle(i);
}
}
portEXIT_CRITICAL(&s.mux);
return Handle(NO_EX);
}
template <typename C>
static Handle subscribe(C fn) {
if constexpr (std::is_invocable_v<C, const Msg*, size_t>)
return subscribe(0, EmitMode::Latest, std::move(fn));
else
return subscribe(0, EmitMode::Latest, [fn = std::move(fn)](const Msg* p, size_t n) {
for (size_t i = 0; i < n; ++i) fn(p[i]);
});
}
template <typename C>
static Handle subscribe(uint32_t ms, C fn) {
if constexpr (std::is_invocable_v<C, const Msg*, size_t>)
return subscribe(ms, EmitMode::Batch, std::move(fn));
else
return subscribe(ms, EmitMode::Batch, [fn = std::move(fn)](const Msg* p, size_t n) {
for (size_t i = 0; i < n; ++i) fn(p[i]);
});
}
static bool publish(const Msg& m) {
store(m);
return push(m);
}
static bool publish(const Msg& m, const Handle& h) {
store(m);
return push(m, h.valid() ? h.idx : NO_EX);
}
static void publishISR(const Msg& m, BaseType_t* hpw = nullptr) {
auto& s = state();
storeISR(m);
ensureQueue();
Item it {m, NO_EX};
xQueueSendFromISR(s.queue, &it, hpw);
}
static bool peek(Msg& out) {
auto& s = state();
if (!s.hasLatest.load(std::memory_order_acquire)) return false;
portENTER_CRITICAL(&s.mux);
out = s.latest;
portEXIT_CRITICAL(&s.mux);
return true;
}
static bool take(Msg& out) {
auto& s = state();
if (!s.hasLatest.load(std::memory_order_acquire)) return false;
portENTER_CRITICAL(&s.mux);
out = s.latest;
s.hasLatest.store(false, std::memory_order_release);
portEXIT_CRITICAL(&s.mux);
return true;
}
static bool hasSubscribers() { return state().subCount.load(std::memory_order_acquire) > 0; }
};
+50
View File
@@ -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
};
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <cstdint>
struct SystemReadyEvent {};
+166
View File
@@ -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;
}
}
+1
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <sdkconfig.h>
#include <wifi/wifi_idf.h> #include <wifi/wifi_idf.h>
#include <esp_http_server.h> #include <esp_http_server.h>
#include "platform_shared/message.pb.h" #include "platform_shared/message.pb.h"
+4
View File
@@ -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);
+4
View File
@@ -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;
+25 -2
View File
@@ -23,16 +23,39 @@
#ifndef ESP_PLATFORM_NAME #ifndef ESP_PLATFORM_NAME
#define ESP_PLATFORM_NAME "ESP32-S3" #define ESP_PLATFORM_NAME "ESP32-S3"
#endif #endif
#elif CONFIG_IDF_TARGET_ESP32C6
#include "esp32c6/rom/rtc.h"
#ifndef ESP_PLATFORM_NAME
#define ESP_PLATFORM_NAME "ESP32-C6"
#endif
#elif CONFIG_IDF_TARGET_ESP32P4
#include "esp32p4/rom/rtc.h"
#ifndef ESP_PLATFORM_NAME
#define ESP_PLATFORM_NAME "ESP32-P4"
#endif
#define ESP32P4_USES_C6_COPROCESSOR 1
#else #else
#error Target CONFIG_IDF_TARGET is not supported #error Target CONFIG_IDF_TARGET is not supported
#endif #endif
/*
* I2C software connection
*/
#if CONFIG_IDF_TARGET_ESP32P4
#ifndef SDA_PIN
#define SDA_PIN 7
#endif
#ifndef SCL_PIN
#define SCL_PIN 8
#endif
#else
#ifndef SDA_PIN #ifndef SDA_PIN
#define SDA_PIN 21 #define SDA_PIN 21
#endif #endif
#ifndef SCL_PIN #ifndef SCL_PIN
#define SCL_PIN 22 #define SCL_PIN 22
#endif #endif
#ifndef I2C_FREQUENCY #endif
#define I2C_FREQUENCY 100000UL #ifndef I2C_FREQUENCY
#define I2C_FREQUENCY 1000000UL
#endif #endif
+6 -9
View File
@@ -2,10 +2,12 @@
#include <esp_http_server.h> #include <esp_http_server.h>
#include <mdns.h> #include <mdns.h>
#include <event_bus/event_bus.h> #include <eventbus.hpp>
#include <settings/mdns_settings.h> #include <settings/mdns_settings.h>
#include <utils/timing.h> #include <utils/timing.h>
class WebServer;
class MDNSService { class MDNSService {
public: public:
MDNSService(); MDNSService();
@@ -16,16 +18,11 @@ class MDNSService {
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);
esp_err_t getSettings(httpd_req_t *request); void registerRoutes(WebServer &server);
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
private: private:
static constexpr const char *TAG = "MDNSService"; MDNSSettings _settings {};
SubscriptionHandle _settingsHandle;
void onSettingsChanged(const api_MDNSSettings &newSettings);
MDNSSettings _settings = MDNSSettings_defaults();
EventBus::Handle<api_MDNSSettings> _settingsHandle;
bool _started {false}; bool _started {false};
void reconfigureMDNS(); void reconfigureMDNS();
+4 -13
View File
@@ -14,18 +14,14 @@
#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 <event_bus/event_bus.h> #include <eventbus.hpp>
enum class MOTION_STATE { DEACTIVATED, IDLE, CALIBRATION, REST, STAND, WALK }; enum class MOTION_STATE { DEACTIVATED, IDLE, CALIBRATION, REST, STAND, WALK };
class MotionService { class MotionService {
public: public:
using ModeChangeCallback = std::function<void(bool active)>;
void begin(); void begin();
void setModeChangeCallback(ModeChangeCallback callback) { modeChangeCallback_ = callback; }
void handleAngles(const socket_message_AnglesData& data); void handleAngles(const socket_message_AnglesData& data);
void handleInput(const socket_message_ControllerData& data); void handleInput(const socket_message_ControllerData& data);
@@ -47,8 +43,6 @@ class MotionService {
inline bool isActive() { return state != nullptr; } inline bool isActive() { return state != nullptr; }
private: private:
void subscribeToEvents();
Kinematics kinematics; Kinematics kinematics;
CommandMsg command = {0, 0, 0, 0, 0, 0, 0}; CommandMsg command = {0, 0, 0, 0, 0, 0, 0};
@@ -70,12 +64,9 @@ class MotionService {
int64_t lastUpdate = esp_timer_get_time(); int64_t lastUpdate = esp_timer_get_time();
ModeChangeCallback modeChangeCallback_; SubscriptionHandle controllerHandle_;
SubscriptionHandle walkGaitHandle_;
EventBus::Handle<socket_message_ControllerData> controllerHandle_; SubscriptionHandle anglesHandle_;
EventBus::Handle<socket_message_ModeData> modeHandle_;
EventBus::Handle<socket_message_AnglesData> anglesHandle_;
EventBus::Handle<socket_message_WalkGaitData> walkGaitHandle_;
}; };
#endif #endif
+1
View File
@@ -2,6 +2,7 @@
#include <motion_states/state.h> #include <motion_states/state.h>
#include <utils/math_utils.h> #include <utils/math_utils.h>
#include <algorithm>
#include <array> #include <array>
#include <functional> #include <functional>
+15 -14
View File
@@ -3,23 +3,27 @@
#include <esp_http_server.h> #include <esp_http_server.h>
#include <features.h> #include <features.h>
#include <event_bus/event_bus.h> #include <eventbus.hpp>
#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_CSI_CAMERA (USE_CAMERA && CONFIG_IDF_TARGET_ESP32P4)
#if USE_DVP_CAMERA
#include <esp_camera.h> #include <esp_camera.h>
#if USE_CAMERA
#include <peripherals/camera_pins.h> #include <peripherals/camera_pins.h>
#endif
#define PART_BOUNDARY "frame"
camera_fb_t *safe_camera_fb_get(); camera_fb_t *safe_camera_fb_get();
sensor_t *safe_sensor_get(); sensor_t *safe_sensor_get();
void safe_sensor_return(); void safe_sensor_return();
#endif
#define PART_BOUNDARY "frame"
class CameraService { class CameraService {
public: public:
@@ -30,16 +34,13 @@ 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);
esp_err_t getSettings(httpd_req_t *request); void registerRoutes(WebServer &server);
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
#if USE_DVP_CAMERA
private: private:
static constexpr const char *TAG = "CameraService"; CameraSettings _settings {};
SubscriptionHandle _settingsHandle;
void onSettingsChanged(const api_CameraSettings &newSettings);
void updateCamera(); void updateCamera();
#endif
CameraSettings _settings = CameraSettings_defaults();
EventBus::Handle<api_CameraSettings> _settingsHandle;
}; };
} // namespace Camera } // namespace Camera
+52 -67
View File
@@ -1,11 +1,12 @@
#pragma once #pragma once
#include <driver/i2c.h> #include <driver/i2c_master.h>
#include <esp_log.h> #include <esp_log.h>
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/semphr.h> #include <freertos/semphr.h>
#include <functional> #include <functional>
#include <vector> #include <vector>
#include <cstring>
class I2CBus { class I2CBus {
public: public:
@@ -24,30 +25,17 @@ class I2CBus {
_scl = scl; _scl = scl;
_freq = freq; _freq = freq;
i2c_config_t conf = {}; i2c_master_bus_config_t bus_cfg = {};
conf.mode = I2C_MODE_MASTER; bus_cfg.i2c_port = port;
conf.sda_io_num = sda; bus_cfg.sda_io_num = sda;
conf.scl_io_num = scl; bus_cfg.scl_io_num = scl;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE; bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE; bus_cfg.glitch_ignore_cnt = 7;
conf.master.clk_speed = freq; bus_cfg.flags.enable_internal_pullup = true;
esp_err_t err = i2c_param_config(_port, &conf); esp_err_t err = i2c_new_master_bus(&bus_cfg, &_bus);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "i2c_param_config failed: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "i2c_new_master_bus failed: %s", esp_err_to_name(err));
return err;
}
err = i2c_driver_install(_port, I2C_MODE_MASTER, 0, 0, 0);
if (err == ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "I2C driver already installed for port %d, reconfiguring", _port);
i2c_driver_delete(_port);
err = i2c_param_config(_port, &conf);
if (err != ESP_OK) return err;
err = i2c_driver_install(_port, I2C_MODE_MASTER, 0, 0, 0);
}
if (err != ESP_OK) {
ESP_LOGE(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err));
return err; return err;
} }
@@ -57,73 +45,51 @@ class I2CBus {
void end() { void end() {
if (_initialized) { if (_initialized) {
i2c_driver_delete(_port); if (_dev) {
i2c_master_bus_rm_device(_dev);
_dev = NULL;
_dev_addr = 0xFF;
}
i2c_del_master_bus(_bus);
_bus = NULL;
_initialized = false; _initialized = false;
} }
} }
bool isInitialized() const { return _initialized; } bool isInitialized() const { return _initialized; }
i2c_master_bus_handle_t busHandle() const { return _bus; }
esp_err_t writeBytes(uint8_t addr, const uint8_t* data, size_t len) { esp_err_t writeBytes(uint8_t addr, const 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);
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); if (err != ESP_OK) return err;
i2c_master_start(cmd); return i2c_master_transmit(_dev, data, len, pdMS_TO_TICKS(100));
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
if (len > 0 && data != nullptr) {
i2c_master_write(cmd, data, len, true);
}
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);
return ret;
} }
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) {
if (!_initialized) return ESP_ERR_INVALID_STATE; if (!_initialized) return ESP_ERR_INVALID_STATE;
esp_err_t err = ensureDevice(addr);
if (err != ESP_OK) return err;
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); uint8_t buf[len + 1];
i2c_master_start(cmd); buf[0] = reg;
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg, true);
if (len > 0 && data != nullptr) { if (len > 0 && data != nullptr) {
i2c_master_write(cmd, data, len, true); memcpy(buf + 1, data, len);
} }
i2c_master_stop(cmd); return i2c_master_transmit(_dev, buf, len + 1, pdMS_TO_TICKS(100));
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);
return ret;
} }
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);
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); if (err != ESP_OK) return err;
i2c_master_start(cmd); return i2c_master_transmit_receive(_dev, &reg, 1, data, len, pdMS_TO_TICKS(100));
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg, true);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, true);
if (len > 1) {
i2c_master_read(cmd, data, len - 1, I2C_MASTER_ACK);
}
i2c_master_read_byte(cmd, data + len - 1, I2C_MASTER_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, pdMS_TO_TICKS(100));
i2c_cmd_link_delete(cmd);
return ret;
} }
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(50)) == ESP_OK;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, pdMS_TO_TICKS(50));
i2c_cmd_link_delete(cmd);
return ret == 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) {
@@ -157,4 +123,23 @@ class I2CBus {
gpio_num_t _scl = GPIO_NUM_NC; gpio_num_t _scl = GPIO_NUM_NC;
uint32_t _freq = 100000; uint32_t _freq = 100000;
bool _initialized = false; bool _initialized = false;
i2c_master_bus_handle_t _bus = NULL;
i2c_master_dev_handle_t _dev = NULL;
uint8_t _dev_addr = 0xFF;
esp_err_t ensureDevice(uint8_t addr) {
if (_dev && _dev_addr == addr) return ESP_OK;
if (_dev) {
i2c_master_bus_rm_device(_dev);
_dev = NULL;
}
i2c_device_config_t dev_cfg = {};
dev_cfg.dev_addr_length = I2C_ADDR_BIT_LEN_7;
dev_cfg.device_address = addr;
dev_cfg.scl_speed_hz = _freq;
esp_err_t err = i2c_master_bus_add_device(_bus, &dev_cfg, &_dev);
if (err == ESP_OK) _dev_addr = addr;
return err;
}
}; };
+5
View File
@@ -1,6 +1,7 @@
#ifndef LEDService_h #ifndef LEDService_h
#define LEDService_h #define LEDService_h
#include <sdkconfig.h>
#include <driver/rmt_tx.h> #include <driver/rmt_tx.h>
#include <led_strip.h> #include <led_strip.h>
#include <led_strip_rmt.h> #include <led_strip_rmt.h>
@@ -9,8 +10,12 @@
#include <esp_log.h> #include <esp_log.h>
#ifndef WS2812_PIN #ifndef WS2812_PIN
#if CONFIG_IDF_TARGET_ESP32P4
#define WS2812_PIN 27
#else
#define WS2812_PIN 12 #define WS2812_PIN 12
#endif #endif
#endif
#ifndef WS2812_NUM_LEDS #ifndef WS2812_NUM_LEDS
#define WS2812_NUM_LEDS 13 #define WS2812_NUM_LEDS 13
+3 -21
View File
@@ -1,13 +1,12 @@
#pragma once #pragma once
#include <event_bus/event_bus.h> #include <eventbus.hpp>
#include <utils/math_utils.h> #include <utils/math_utils.h>
#include <utils/timing.h> #include <utils/timing.h>
#include <filesystem.h> #include <filesystem.h>
#include <features.h> #include <features.h>
#include <settings/peripherals_settings.h> #include <settings/peripherals_settings.h>
#include <platform_shared/message.pb.h> #include <platform_shared/message.pb.h>
#include <esp_http_server.h>
#include <list> #include <list>
@@ -20,9 +19,6 @@
#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 { class Peripherals {
@@ -42,19 +38,13 @@ class Peripherals {
void getSettingsProto(socket_message_PeripheralSettingsData &data); void getSettingsProto(socket_message_PeripheralSettingsData &data);
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();
@@ -64,20 +54,12 @@ class Peripherals {
bool calibrateIMU(); bool calibrateIMU();
esp_err_t getSettings(httpd_req_t *request);
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
private: private:
static constexpr const char *TAG = "Peripherals"; PeripheralsConfiguration _settings {};
SubscriptionHandle _settingsHandle;
void onSettingsChanged(const api_PeripheralSettings &newSettings);
PeripheralsConfiguration _settings = PeripheralsConfiguration_defaults();
EventBus::Handle<api_PeripheralSettings> _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)
+15 -30
View File
@@ -2,11 +2,10 @@
#define ServoController_h #define ServoController_h
#include <peripherals/drivers/pca9685.h> #include <peripherals/drivers/pca9685.h>
#include <event_bus/event_bus.h> #include <eventbus.hpp>
#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> #include <platform_shared/message.pb.h>
#include <esp_http_server.h>
#ifndef FACTORY_SERVO_PWM_FREQUENCY #ifndef FACTORY_SERVO_PWM_FREQUENCY
#define FACTORY_SERVO_PWM_FREQUENCY 50 #define FACTORY_SERVO_PWM_FREQUENCY 50
@@ -36,27 +35,17 @@ inline ServoSettings ServoSettings_defaults() {
class ServoController { class ServoController {
public: public:
ServoController() {}
void begin() { void begin() {
_settingsHandle = EventBus::subscribe<api_ServoSettings>( _settings = EventBus::instance().peek<ServoSettings>();
[this](const api_ServoSettings &settings) { onSettingsChanged(settings); });
_pwmHandle = EventBus::subscribe<socket_message_ServoPWMData>(
[this](const socket_message_ServoPWMData &data) { setServoPWM(data.servo_id, data.servo_pwm); });
_stateHandle = EventBus::subscribe<socket_message_ServoStateData>(
[this](const socket_message_ServoStateData &data) { data.active ? activate() : deactivate(); });
api_ServoSettings initialSettings;
if (EventBus::peek(initialSettings)) {
onSettingsChanged(initialSettings);
}
initializePCA(); initializePCA();
}
esp_err_t getSettings(httpd_req_t *request); _settingsHandle =
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq); 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) {
if (value < 0 || value > 4096) { if (value < 0 || value > 4096) {
@@ -106,7 +95,7 @@ class ServoController {
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 = _settings.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);
@@ -119,10 +108,6 @@ class ServoController {
} }
private: private:
static constexpr const char *TAG = "ServoController";
void onSettingsChanged(const api_ServoSettings &newSettings) { _settings = newSettings; }
void initializePCA() { void initializePCA() {
_pca.begin(); _pca.begin();
_pca.setOscillatorFrequency(FACTORY_SERVO_OSCILLATOR_FREQUENCY); _pca.setOscillatorFrequency(FACTORY_SERVO_OSCILLATOR_FREQUENCY);
@@ -130,11 +115,7 @@ class ServoController {
_pca.sleep(); _pca.sleep();
} }
api_ServoSettings _settings = ServoSettings_defaults(); ServoSettings _settings {};
EventBus::Handle<api_ServoSettings> _settingsHandle;
EventBus::Handle<socket_message_ServoPWMData> _pwmHandle;
EventBus::Handle<socket_message_ServoStateData> _stateHandle;
PCA9685Driver _pca; PCA9685Driver _pca;
SERVO_CONTROL_STATE control_state = SERVO_CONTROL_STATE::DEACTIVATED; SERVO_CONTROL_STATE control_state = SERVO_CONTROL_STATE::DEACTIVATED;
@@ -142,6 +123,10 @@ class ServoController {
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
+113
View File
@@ -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);
}
}
};
+8 -2
View File
@@ -1,14 +1,20 @@
#pragma once #pragma once
#include <sdkconfig.h>
#include <platform_shared/api.pb.h> #include <platform_shared/api.pb.h>
#if !CONFIG_IDF_TARGET_ESP32P4
#include <esp_camera.h> #include <esp_camera.h>
#else
#define PIXFORMAT_JPEG 0
#define FRAMESIZE_VGA 0
#define GAINCEILING_2X 0
#endif
namespace Camera { namespace Camera {
// Use proto type directly as settings type
using CameraSettings = api_CameraSettings; using CameraSettings = api_CameraSettings;
// Default factory settings
inline CameraSettings CameraSettings_defaults() { inline CameraSettings CameraSettings_defaults() {
CameraSettings settings = api_CameraSettings_init_zero; CameraSettings settings = api_CameraSettings_init_zero;
settings.pixformat = PIXFORMAT_JPEG; settings.pixformat = PIXFORMAT_JPEG;
-5
View File
@@ -12,32 +12,27 @@
#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);
+40
View File
@@ -0,0 +1,40 @@
#include <ArduinoJson.h>
#include <string>
#ifndef FACTORY_NTP_ENABLED
#define FACTORY_NTP_ENABLED true
#endif
#ifndef FACTORY_NTP_TIME_ZONE_LABEL
#define FACTORY_NTP_TIME_ZONE_LABEL "Europe/London"
#endif
#ifndef FACTORY_NTP_TIME_ZONE_FORMAT
#define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0"
#endif
#ifndef FACTORY_NTP_SERVER
#define FACTORY_NTP_SERVER "time.google.com"
#endif
class NTPSettings {
public:
bool enabled;
std::string tzLabel;
std::string tzFormat;
std::string server;
static void read(NTPSettings &settings, JsonVariant &root) {
root["enabled"] = settings.enabled;
root["server"] = settings.server.c_str();
root["tz_label"] = settings.tzLabel.c_str();
root["tz_format"] = settings.tzFormat.c_str();
}
static void update(JsonVariant &root, NTPSettings &settings) {
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
settings.server = root["server"] | FACTORY_NTP_SERVER;
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
}
};
+2 -15
View File
@@ -1,25 +1,12 @@
#pragma once #pragma once
#include <sdkconfig.h>
#include <platform_shared/api.pb.h> #include <platform_shared/api.pb.h>
#include <global.h>
/*
* I2C software connection
*/
#ifndef SDA_PIN
#define SDA_PIN 21
#endif
#ifndef SCL_PIN
#define SCL_PIN 22
#endif
#ifndef I2C_FREQUENCY
#define I2C_FREQUENCY 1000000UL
#endif
// 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;
+4
View File
@@ -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();
+11 -19
View File
@@ -5,9 +5,12 @@
#include <mdns.h> #include <mdns.h>
#include <string> #include <string>
#include <event_bus/event_bus.h> #include <filesystem.h>
#include <settings/wifi_settings.h>
#include <utils/timing.h> #include <utils/timing.h>
#include <eventbus.hpp>
#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
@@ -24,39 +27,28 @@ class WiFiService {
void setupMDNS(const char *hostname); void setupMDNS(const char *hostname);
void selectNetwork(uint32_t index); void selectNetwork(uint32_t index);
const char *getHostname() { const char *getHostname() { return _settings.hostname; }
static api_WifiSettings cached_settings;
EventBus::peek(cached_settings);
return cached_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);
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);
void onSettingsChanged(const api_WifiSettings &newSettings); WiFiSettings _settings {};
SubscriptionHandle _settingsHandle;
void reconfigureWiFiConnection(); void reconfigureWiFiConnection();
void manageSTA(); void manageSTA();
void configureNetwork(const WiFiNetwork &network); void configureNetwork(WiFiNetwork &network);
api_WifiSettings getSettings() const {
api_WifiSettings settings;
EventBus::peek(settings);
return settings;
}
EventBus::Handle<api_WifiSettings> _settingsHandle;
bool _initialized;
unsigned long _lastConnectionAttempt; unsigned long _lastConnectionAttempt;
bool _stopping; bool _stopping;
constexpr static uint16_t reconnectDelay {10000}; constexpr static uint16_t reconnectDelay {10000};
static constexpr const char *TAG = "WiFiService";
}; };
+8
View File
@@ -0,0 +1,8 @@
# Name, Type, SubType, Offset, Size, Flags
# 32MB Flash partition table for ESP32-P4
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0xC80000,
app1, app, ota_1, 0xC90000, 0xC80000,
spiffs, data, spiffs, 0x1910000,0x6E0000,
coredump, data, coredump,0x1FF0000,0x10000,
1 # Name, Type, SubType, Offset, Size, Flags
2 # 32MB Flash partition table for ESP32-P4
3 nvs, data, nvs, 0x9000, 0x5000,
4 otadata, data, ota, 0xe000, 0x2000,
5 app0, app, ota_0, 0x10000, 0xC80000,
6 app1, app, ota_1, 0xC90000, 0xC80000,
7 spiffs, data, spiffs, 0x1910000,0x6E0000,
8 coredump, data, coredump,0x1FF0000,0x10000,
+23 -14
View File
@@ -1,3 +1,25 @@
set(COMPONENT_REQUIRES
driver
esp_driver_i2c
esp_http_server
nvs_flash
esp_wifi
esp_event
esp_netif
mdns
esp_timer
esp_psram
spi_flash
littlefs
esp-dsp
)
if(IDF_TARGET STREQUAL "esp32p4")
list(APPEND COMPONENT_REQUIRES esp_wifi_remote esp_hosted esp_driver_cam esp_driver_isp esp_driver_jpeg espressif__esp_sccb_intf espressif__esp_cam_sensor)
else()
list(APPEND COMPONENT_REQUIRES esp32-camera)
endif()
idf_component_register( idf_component_register(
SRC_DIRS SRC_DIRS
"." "."
@@ -5,23 +27,10 @@ idf_component_register(
"peripherals" "peripherals"
"wifi" "wifi"
"platform_shared" "platform_shared"
"event_bus"
"../../submodules/nanopb" "../../submodules/nanopb"
INCLUDE_DIRS INCLUDE_DIRS
"../include" "../include"
"../../submodules/nanopb" "../../submodules/nanopb"
REQUIRES REQUIRES
driver ${COMPONENT_REQUIRES}
esp_http_server
nvs_flash
esp_wifi
esp_event
esp_netif
mdns
esp_timer
esp_psram
spi_flash
littlefs
esp32-camera
esp-dsp
) )
+9 -41
View File
@@ -1,6 +1,8 @@
#include <ap_service.h> #include <ap_service.h>
#include <communication/webserver.h> #include <communication/webserver.h>
static const char *TAG = "APService";
APService::APService() : _dnsServer(nullptr), _lastManaged(0), _reconfigureAp(false), _recoveryMode(false) {} APService::APService() : _dnsServer(nullptr), _lastManaged(0), _reconfigureAp(false), _recoveryMode(false) {}
APService::~APService() { APService::~APService() {
@@ -11,28 +13,15 @@ APService::~APService() {
} }
void APService::begin() { void APService::begin() {
_settingsHandle = _settings = EventBus::instance().peek<APSettings>();
EventBus::subscribe<api_APSettings>([this](const api_APSettings &settings) { onSettingsChanged(settings); }); _settingsHandle = EventBus::instance().subscribe<APSettings>([this](const APSettings &s) {
_settings = s;
api_APSettings initialSettings; reconfigureAP();
if (EventBus::peek(initialSettings)) { });
onSettingsChanged(initialSettings);
}
} }
void APService::onSettingsChanged(const api_APSettings &newSettings) { void APService::registerRoutes(WebServer &s) {
strncpy(_settings.ssid, newSettings.ssid, sizeof(_settings.ssid) - 1); s.on("/api/ap/status", HTTP_GET, [this](httpd_req_t *request) { return getStatusProto(request); });
_settings.ssid[sizeof(_settings.ssid) - 1] = '\0';
strncpy(_settings.password, newSettings.password, sizeof(_settings.password) - 1);
_settings.password[sizeof(_settings.password) - 1] = '\0';
_settings.local_ip = newSettings.local_ip;
_settings.gateway_ip = newSettings.gateway_ip;
_settings.subnet_mask = newSettings.subnet_mask;
_settings.channel = newSettings.channel;
_settings.ssid_hidden = newSettings.ssid_hidden;
_settings.max_clients = newSettings.max_clients;
_settings.provision_mode = newSettings.provision_mode;
reconfigureAP();
} }
esp_err_t APService::getStatusProto(httpd_req_t *request) { esp_err_t APService::getStatusProto(httpd_req_t *request) {
@@ -120,24 +109,3 @@ void APService::stopAP() {
} }
void APService::handleDNS() {} void APService::handleDNS() {}
esp_err_t APService::getSettings(httpd_req_t *request) {
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = api_Response_ap_settings_tag;
response.payload.ap_settings = _settings;
return WebServer::send(request, 200, response, api_Response_fields);
}
esp_err_t APService::updateSettings(httpd_req_t *request, api_Request *protoReq) {
if (protoReq->which_payload != api_Request_ap_settings_tag) {
return ESP_FAIL;
}
EventBus::publish(protoReq->payload.ap_settings, "HTTPEndpoint");
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = api_Response_empty_message_tag;
return WebServer::send(request, 200, response, api_Response_fields);
}
-61
View File
@@ -1,61 +0,0 @@
#include <event_bus/event_bus.h>
#include <vector>
#include <memory>
#include <mutex>
#include <algorithm>
std::atomic<bool> EventBus::_hasGlobalListeners {false};
struct GlobalSubscription {
size_t id;
std::unique_ptr<EventBus::GlobalHandler> handler;
};
static std::vector<GlobalSubscription> globalSubs;
static std::mutex globalMutex;
static size_t nextGlobalId = 1;
size_t EventBus::subscribeGlobal(GlobalHandler&& handler) {
std::lock_guard<std::mutex> lock(globalMutex);
size_t id = nextGlobalId++;
auto h = std::make_unique<GlobalHandler>(std::move(handler));
globalSubs.push_back({id, std::move(h)});
_hasGlobalListeners.store(true, std::memory_order_release);
return id;
}
void EventBus::unsubscribeGlobal(size_t id) {
std::lock_guard<std::mutex> lock(globalMutex);
globalSubs.erase(std::remove_if(globalSubs.begin(), globalSubs.end(), [id](const auto& s) { return s.id == id; }),
globalSubs.end());
_hasGlobalListeners.store(!globalSubs.empty(), std::memory_order_release);
}
template <typename Msg>
void EventBus::notifyGlobalListeners(const Msg& msg, const char* source) {
uint64_t timestamp = esp_timer_get_time();
EventType type = EventTypeTraits<Msg>::type;
std::lock_guard<std::mutex> lock(globalMutex);
for (auto& sub : globalSubs) {
(*sub.handler)(type, &msg, sizeof(Msg), timestamp);
}
}
template void EventBus::notifyGlobalListeners<api_WifiSettings>(const api_WifiSettings&, const char*);
template void EventBus::notifyGlobalListeners<api_APSettings>(const api_APSettings&, const char*);
template void EventBus::notifyGlobalListeners<api_MDNSSettings>(const api_MDNSSettings&, const char*);
template void EventBus::notifyGlobalListeners<api_PeripheralSettings>(const api_PeripheralSettings&, const char*);
template void EventBus::notifyGlobalListeners<api_ServoSettings>(const api_ServoSettings&, const char*);
template void EventBus::notifyGlobalListeners<api_CameraSettings>(const api_CameraSettings&, const char*);
template void EventBus::notifyGlobalListeners<socket_message_IMUData>(const socket_message_IMUData&, const char*);
template void EventBus::notifyGlobalListeners<socket_message_ControllerData>(const socket_message_ControllerData&,
const char*);
template void EventBus::notifyGlobalListeners<socket_message_ModeData>(const socket_message_ModeData&, const char*);
template void EventBus::notifyGlobalListeners<socket_message_AnglesData>(const socket_message_AnglesData&, const char*);
template void EventBus::notifyGlobalListeners<socket_message_WalkGaitData>(const socket_message_WalkGaitData&,
const char*);
template void EventBus::notifyGlobalListeners<socket_message_ServoStateData>(const socket_message_ServoStateData&,
const char*);
template void EventBus::notifyGlobalListeners<socket_message_ServoPWMData>(const socket_message_ServoPWMData&,
const char*);
-29
View File
@@ -1,29 +0,0 @@
#include <event_bus/event_types.h>
const char* eventTypeName(EventType type) {
switch (type) {
case EventType::WIFI_SETTINGS: return "WIFI_SETTINGS";
case EventType::AP_SETTINGS: return "AP_SETTINGS";
case EventType::MDNS_SETTINGS: return "MDNS_SETTINGS";
case EventType::PERIPHERAL_SETTINGS: return "PERIPHERAL_SETTINGS";
case EventType::SERVO_SETTINGS: return "SERVO_SETTINGS";
case EventType::CAMERA_SETTINGS: return "CAMERA_SETTINGS";
case EventType::WIFI_STATUS: return "WIFI_STATUS";
case EventType::AP_STATUS: return "AP_STATUS";
case EventType::IMU_DATA: return "IMU_DATA";
case EventType::MOTION_COMMAND: return "MOTION_COMMAND";
case EventType::MOTION_MODE: return "MOTION_MODE";
case EventType::MOTION_ANGLES: return "MOTION_ANGLES";
case EventType::MOTION_WALK_GAIT: return "MOTION_WALK_GAIT";
case EventType::SERVO_STATE: return "SERVO_STATE";
case EventType::SERVO_PWM: return "SERVO_PWM";
case EventType::SYSTEM_BOOT: return "SYSTEM_BOOT";
case EventType::STORAGE_HYDRATION_COMPLETE: return "STORAGE_HYDRATION_COMPLETE";
default: return "UNKNOWN";
}
}
bool isSettingsEvent(EventType type) {
return static_cast<uint16_t>(type) >= 100 && static_cast<uint16_t>(type) < 200 &&
static_cast<uint16_t>(type) % 10 == 0;
}
+23
View File
@@ -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() {
+5
View File
@@ -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) {
+18
View File
@@ -16,3 +16,21 @@ dependencies:
espressif/esp32-camera: espressif/esp32-camera:
version: "^2.0.0" version: "^2.0.0"
rules:
- if: "idf_version >=5.0.0"
- if: "target not in [esp32p4]"
espressif/esp_wifi_remote:
version: ">=0.3.0"
rules:
- if: "target in [esp32p4]"
espressif/esp_hosted:
version: ">=0.0.6"
rules:
- if: "target in [esp32p4]"
espressif/esp_cam_sensor:
version: ">=0.5.0"
rules:
- if: "target in [esp32p4]"
+68 -82
View File
@@ -20,8 +20,14 @@
#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 <consumers/event_storage_manager.h> #include <eventbus.hpp>
#include <event_bus/rest_endpoints.h> #include <event_storage_manager.hpp>
#include <event_types.h>
#include <settings/camera_settings.h>
#if CONFIG_IDF_TARGET_ESP32P4
#include <esp_hosted.h>
#endif
#include <www_mount.hpp> #include <www_mount.hpp>
@@ -42,73 +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, 12288); 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); });
server.on("/api/camera/settings", HTTP_GET,
[](httpd_req_t *request) { return CameraSettingsEndpoint::getSettings(request); });
server.on("/api/camera/settings", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
return CameraSettingsEndpoint::updateSettings(request, protoReq);
});
#endif
server.on("/api/servo/config", HTTP_GET,
[](httpd_req_t *request) { return ServoSettingsEndpoint::getSettings(request); });
server.on("/api/servo/config", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
return ServoSettingsEndpoint::updateSettings(request, protoReq);
});
server.on("/api/wifi/sta/settings", HTTP_GET,
[](httpd_req_t *request) { return WiFiSettingsEndpoint::getSettings(request); });
server.on("/api/wifi/sta/settings", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
return WiFiSettingsEndpoint::updateSettings(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 APSettingsEndpoint::getSettings(request); });
server.on("/api/ap/settings", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
return APSettingsEndpoint::updateSettings(request, protoReq);
});
server.on("/api/peripherals/settings", HTTP_GET,
[](httpd_req_t *request) { return PeripheralSettingsEndpoint::getSettings(request); });
server.on("/api/peripherals/settings", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
return PeripheralSettingsEndpoint::updateSettings(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 MDNSSettingsEndpoint::getSettings(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 MDNSSettingsEndpoint::updateSettings(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);
@@ -132,17 +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.onPublish<socket_message_ControllerData>(); wsSocket.forward<socket_message_ControllerData>();
wsSocket.onPublish<socket_message_ModeData>(); wsSocket.forward<socket_message_ModeData>();
wsSocket.onPublish<socket_message_WalkGaitData>(); wsSocket.forward<socket_message_WalkGaitData>();
wsSocket.onPublish<socket_message_AnglesData>(); wsSocket.forward<socket_message_AnglesData>();
wsSocket.onPublish<socket_message_ServoPWMData>(); wsSocket.forward<socket_message_ServoPWMData>();
wsSocket.onPublish<socket_message_ServoStateData>(); wsSocket.forward<socket_message_ServoStateData>();
wsSocket.forward<socket_message_FSUploadData>();
wsSocket.on<socket_message_FSUploadData>( modeHandle = EventBus::instance().subscribe<socket_message_ModeData>([&](const socket_message_ModeData &data) {
[&](const socket_message_FSUploadData &data, int clientId) { FileSystemWS::fsHandler.handleUploadData(data); }); servoController.setMode(SERVO_CONTROL_STATE::ANGLE);
motionService.handleMode(data);
motionService.isActive() ? servoController.activate() : servoController.deactivate();
});
wsSocket.bridgeFromEventBus<socket_message_IMUData>(100); FileSystemWS::fsHandler.begin();
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)>;
@@ -243,10 +216,6 @@ void IRAM_ATTR SpotControlLoopEntry(void *) {
peripherals.begin(); peripherals.begin();
servoController.begin(); servoController.begin();
motionService.begin(); motionService.begin();
motionService.setModeChangeCallback([](bool active) {
servoController.setMode(SERVO_CONTROL_STATE::ANGLE);
active ? servoController.activate() : servoController.deactivate();
});
#if FT_ENABLED(USE_WS2812) #if FT_ENABLED(USE_WS2812)
ledService.begin(); ledService.begin();
#endif #endif
@@ -267,10 +236,21 @@ void IRAM_ATTR SpotControlLoopEntry(void *) {
void IRAM_ATTR serviceLoopEntry(void *) { void IRAM_ATTR serviceLoopEntry(void *) {
ESP_LOGI("main", "Service task starting"); ESP_LOGI("main", "Service task starting");
#if CONFIG_IDF_TARGET_ESP32P4
static EventStorageManager storageManager; ESP_LOGI("main", "Initializing ESP-Hosted for C6 coprocessor WiFi...");
storageManager.initialize(); int ret = esp_hosted_init();
ESP_LOGI("main", "Event storage initialized, settings loaded and published"); if (ret != 0) {
ESP_LOGE("main", "ESP-Hosted init failed: %d", ret);
} else {
ESP_LOGI("main", "ESP-Hosted initialized, connecting to C6...");
ret = esp_hosted_connect_to_slave();
if (ret != 0) {
ESP_LOGW("main", "ESP-Hosted connect failed: %d - WiFi may not work", ret);
} else {
ESP_LOGI("main", "ESP-Hosted link established with C6");
}
}
#endif
WiFi.init(); WiFi.init();
wifiService.begin(); wifiService.begin();
@@ -287,6 +267,7 @@ void IRAM_ATTR serviceLoopEntry(void *) {
setupEventSocket(); setupEventSocket();
ESP_LOGI("main", "Service task started"); ESP_LOGI("main", "Service task started");
for (;;) { for (;;) {
wifiService.loop(); wifiService.loop();
apService.loop(); apService.loop();
@@ -300,9 +281,11 @@ void IRAM_ATTR serviceLoopEntry(void *) {
}); });
EXECUTE_EVERY_N_MS(100, { EXECUTE_EVERY_N_MS(100, {
socket_message_IMUData imu = socket_message_IMUData_init_zero; if (wsSocket.hasSubscribers(socket_message_Message_imu_tag)) {
peripherals.getIMUProto(imu); socket_message_IMUData imu = socket_message_IMUData_init_zero;
EventBus::publish(imu); peripherals.getIMUProto(imu);
wsSocket.emit(imu);
}
if (wsSocket.hasSubscribers(socket_message_Message_rssi_tag)) { if (wsSocket.hasSubscribers(socket_message_Message_rssi_tag)) {
socket_message_RSSIData rssi = {.rssi = WiFi.RSSI()}; socket_message_RSSIData rssi = {.rssi = WiFi.RSSI()};
@@ -325,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");
@@ -334,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");
} }
+13 -44
View File
@@ -2,6 +2,8 @@
#include <communication/webserver.h> #include <communication/webserver.h>
#include <esp_netif.h> #include <esp_netif.h>
static const char *TAG = "MDNSService";
MDNSService::MDNSService() {} MDNSService::MDNSService() {}
MDNSService::~MDNSService() { MDNSService::~MDNSService() {
@@ -10,31 +12,19 @@ MDNSService::~MDNSService() {
} }
} }
void MDNSService::begin() { void MDNSService::registerRoutes(WebServer &s) {
_settingsHandle = EventBus::subscribe<api_MDNSSettings>( s.on("/api/mdns/status", HTTP_GET, [this](httpd_req_t *request) { return getStatus(request); });
[this](const api_MDNSSettings &settings) { onSettingsChanged(settings); }); s.on("/api/mdns/query", HTTP_POST,
[this](httpd_req_t *request, api_Request *protoReq) { return queryServices(request, protoReq); });
api_MDNSSettings initialSettings;
if (EventBus::peek(initialSettings)) {
onSettingsChanged(initialSettings);
}
startMDNS();
} }
void MDNSService::onSettingsChanged(const api_MDNSSettings &newSettings) { void MDNSService::begin() {
strncpy(_settings.hostname, newSettings.hostname, sizeof(_settings.hostname) - 1); _settings = EventBus::instance().peek<MDNSSettings>();
_settings.hostname[sizeof(_settings.hostname) - 1] = '\0'; _settingsHandle = EventBus::instance().subscribe<MDNSSettings>([this](const MDNSSettings &s) {
strncpy(_settings.instance, newSettings.instance, sizeof(_settings.instance) - 1); _settings = s;
_settings.instance[sizeof(_settings.instance) - 1] = '\0'; reconfigureMDNS();
_settings.services_count = newSettings.services_count; });
for (size_t i = 0; i < newSettings.services_count; i++) { startMDNS();
_settings.services[i] = newSettings.services[i];
}
_settings.global_txt_records_count = newSettings.global_txt_records_count;
for (size_t i = 0; i < newSettings.global_txt_records_count; i++) {
_settings.global_txt_records[i] = newSettings.global_txt_records[i];
}
reconfigureMDNS();
} }
void MDNSService::reconfigureMDNS() { void MDNSService::reconfigureMDNS() {
@@ -176,24 +166,3 @@ esp_err_t MDNSService::queryServices(httpd_req_t *request, api_Request *protoReq
return WebServer::send(request, 200, response, api_Response_fields); return WebServer::send(request, 200, response, api_Response_fields);
} }
esp_err_t MDNSService::getSettings(httpd_req_t *request) {
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = api_Response_mdns_settings_tag;
response.payload.mdns_settings = _settings;
return WebServer::send(request, 200, response, api_Response_fields);
}
esp_err_t MDNSService::updateSettings(httpd_req_t *request, api_Request *protoReq) {
if (protoReq->which_payload != api_Request_mdns_settings_tag) {
return ESP_FAIL;
}
EventBus::publish(protoReq->payload.mdns_settings, "HTTPEndpoint");
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = api_Response_empty_message_tag;
return WebServer::send(request, 200, response, api_Response_fields);
}
+4 -13
View File
@@ -2,21 +2,13 @@
void MotionService::begin() { void MotionService::begin() {
body_state.updateFeet(KinConfig::default_feet_positions); body_state.updateFeet(KinConfig::default_feet_positions);
subscribeToEvents();
}
void MotionService::subscribeToEvents() { controllerHandle_ = EventBus::instance().subscribe<socket_message_ControllerData>(
controllerHandle_ = EventBus::subscribe<socket_message_ControllerData>(
[this](const socket_message_ControllerData& data) { handleInput(data); }); [this](const socket_message_ControllerData& data) { handleInput(data); });
walkGaitHandle_ = EventBus::instance().subscribe<socket_message_WalkGaitData>(
modeHandle_ =
EventBus::subscribe<socket_message_ModeData>([this](const socket_message_ModeData& data) { handleMode(data); });
anglesHandle_ = EventBus::subscribe<socket_message_AnglesData>(
[this](const socket_message_AnglesData& data) { handleAngles(data); });
walkGaitHandle_ = EventBus::subscribe<socket_message_WalkGaitData>(
[this](const socket_message_WalkGaitData& data) { handleWalkGait(data); }); [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) {
@@ -58,7 +50,6 @@ void MotionService::handleMode(const socket_message_ModeData& data) {
case MOTION_STATE::DEACTIVATED: setState(nullptr); break; case MOTION_STATE::DEACTIVATED: setState(nullptr); break;
default: setState(nullptr); break; default: setState(nullptr); break;
} }
if (modeChangeCallback_) modeChangeCallback_(isActive());
} }
void MotionService::handleGestures(const gesture_t ges) { void MotionService::handleGestures(const gesture_t ges) {
+474 -29
View File
@@ -4,9 +4,15 @@
namespace Camera { namespace Camera {
static const char *const TAG = "CameraService";
#if USE_DVP_CAMERA || USE_CSI_CAMERA
static constexpr const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; static constexpr const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static constexpr const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; static constexpr const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static constexpr const char *_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; static constexpr const char *_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
#endif
#if USE_DVP_CAMERA
SemaphoreHandle_t cameraMutex = xSemaphoreCreateMutex(); SemaphoreHandle_t cameraMutex = xSemaphoreCreateMutex();
@@ -32,17 +38,14 @@ void safe_sensor_return() { xSemaphoreGiveRecursive(cameraMutex); }
CameraService::CameraService() {} CameraService::CameraService() {}
esp_err_t CameraService::begin() { esp_err_t CameraService::begin() {
_settingsHandle = EventBus::subscribe<api_CameraSettings>( _settings = EventBus::instance().peek<CameraSettings>();
[this](const api_CameraSettings &settings) { onSettingsChanged(settings); }); _settingsHandle = EventBus::instance().subscribe<CameraSettings>([this](const CameraSettings &s) {
_settings = s;
api_CameraSettings initialSettings; updateCamera();
if (EventBus::peek(initialSettings)) { });
onSettingsChanged(initialSettings);
}
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;
#if FT_ENABLED(USE_CAMERA)
camera_config.pin_d0 = Y2_GPIO_NUM; camera_config.pin_d0 = Y2_GPIO_NUM;
camera_config.pin_d1 = Y3_GPIO_NUM; camera_config.pin_d1 = Y3_GPIO_NUM;
camera_config.pin_d2 = Y4_GPIO_NUM; camera_config.pin_d2 = Y4_GPIO_NUM;
@@ -59,7 +62,6 @@ esp_err_t CameraService::begin() {
camera_config.pin_sccb_scl = SIOC_GPIO_NUM; camera_config.pin_sccb_scl = SIOC_GPIO_NUM;
camera_config.pin_pwdn = PWDN_GPIO_NUM; camera_config.pin_pwdn = PWDN_GPIO_NUM;
camera_config.pin_reset = RESET_GPIO_NUM; camera_config.pin_reset = RESET_GPIO_NUM;
#endif
camera_config.xclk_freq_hz = 20000000; camera_config.xclk_freq_hz = 20000000;
camera_config.pixel_format = PIXFORMAT_JPEG; camera_config.pixel_format = PIXFORMAT_JPEG;
@@ -142,16 +144,11 @@ esp_err_t CameraService::cameraStream(httpd_req_t *request) {
return ESP_OK; return ESP_OK;
} }
void CameraService::onSettingsChanged(const api_CameraSettings &newSettings) {
_settings = newSettings;
updateCamera();
}
void CameraService::updateCamera() { void CameraService::updateCamera() {
ESP_LOGI(TAG, "Updating camera settings"); ESP_LOGI("CameraSettings", "Updating camera settings");
sensor_t *s = safe_sensor_get(); sensor_t *s = safe_sensor_get();
if (!s) { if (!s) {
ESP_LOGE(TAG, "Failed to update camera settings"); ESP_LOGE("CameraSettings", "Failed to update camera settings");
safe_sensor_return(); safe_sensor_return();
return; return;
} }
@@ -182,25 +179,473 @@ void CameraService::updateCamera() {
safe_sensor_return(); safe_sensor_return();
} }
esp_err_t CameraService::getSettings(httpd_req_t *request) { #elif USE_CSI_CAMERA
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = api_Response_camera_settings_tag;
response.payload.camera_settings = _settings;
return WebServer::send(request, 200, response, api_Response_fields);
} }
esp_err_t CameraService::updateSettings(httpd_req_t *request, api_Request *protoReq) { extern "C" {
if (protoReq->which_payload != api_Request_camera_settings_tag) { #include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_csi.h"
#include "esp_cam_ctlr_types.h"
#include "driver/isp.h"
#include "driver/jpeg_encode.h"
#include "esp_sccb_intf.h"
#include "esp_sccb_i2c.h"
#include "esp_cam_sensor.h"
#include "ov5647.h"
#include "esp_ldo_regulator.h"
#include "driver/isp_demosaic.h"
#include "driver/isp_bf.h"
#include "driver/isp_sharpen.h"
}
#include <peripherals/i2c_bus.h>
namespace Camera {
#ifndef MIPI_CSI_HRES
#define MIPI_CSI_HRES 640
#endif
#ifndef MIPI_CSI_VRES
#define MIPI_CSI_VRES 480
#endif
#ifndef MIPI_CSI_LANE_BITRATE_MBPS
#define MIPI_CSI_LANE_BITRATE_MBPS 200
#endif
#ifndef MIPI_CSI_DATA_LANES
#define MIPI_CSI_DATA_LANES 2
#endif
#ifndef CAM_SCCB_FREQ_HZ
#define CAM_SCCB_FREQ_HZ 100000
#endif
#ifndef CAM_SENSOR_ADDR
#define CAM_SENSOR_ADDR 0x36
#endif
#ifndef CAM_XCLK_PIN
#define CAM_XCLK_PIN -1
#endif
#ifndef CAM_XCLK_FREQ_HZ
#define CAM_XCLK_FREQ_HZ 25000000
#endif
#ifndef CAM_RESET_PIN
#define CAM_RESET_PIN -1
#endif
#ifndef CAM_PWDN_PIN
#define CAM_PWDN_PIN -1
#endif
#ifndef CSI_JPEG_QUALITY
#define CSI_JPEG_QUALITY 80
#endif
#define NUM_FRAME_BUFS 2
static constexpr size_t CACHE_LINE_SIZE = 64;
#define ALIGN_UP(n, a) (((n) + ((a) - 1)) & ~((a) - 1))
static esp_cam_ctlr_handle_t s_cam_handle = NULL;
static isp_proc_handle_t s_isp_proc = NULL;
static jpeg_encoder_handle_t s_jpeg_enc = NULL;
static uint8_t *s_frame_bufs[NUM_FRAME_BUFS] = {};
static size_t s_frame_buf_size = 0;
static uint8_t *s_jpeg_bufs[NUM_FRAME_BUFS] = {};
static size_t s_jpeg_buf_alloc = 0;
static bool s_cam_initialized = false;
static uint16_t s_frame_hres = MIPI_CSI_HRES;
static uint16_t s_frame_vres = MIPI_CSI_VRES;
static SemaphoreHandle_t s_frame_done = NULL;
static SemaphoreHandle_t s_jpeg_lock = NULL;
static SemaphoreHandle_t s_jpeg_ready = NULL;
static TaskHandle_t s_capture_task = NULL;
static volatile bool s_capture_running = false;
static int s_write_idx = 0;
static int s_ready_idx = -1;
static size_t s_ready_jpeg_len = 0;
static uint8_t *s_send_buf = NULL;
static size_t s_send_buf_size = 0;
static bool on_trans_finished(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data) {
BaseType_t woken = pdFALSE;
xSemaphoreGiveFromISR(s_frame_done, &woken);
return (woken == pdTRUE);
}
static void capture_task_fn(void *arg) {
while (s_capture_running) {
int idx = s_write_idx;
esp_cam_ctlr_trans_t trans = {};
trans.buffer = s_frame_bufs[idx];
trans.buflen = s_frame_buf_size;
if (esp_cam_ctlr_receive(s_cam_handle, &trans, 2000) != ESP_OK) {
vTaskDelay(pdMS_TO_TICKS(5));
continue;
}
if (xSemaphoreTake(s_frame_done, pdMS_TO_TICKS(2000)) != pdTRUE) {
continue;
}
jpeg_encode_cfg_t enc_cfg = {};
enc_cfg.src_type = JPEG_ENCODE_IN_FORMAT_RGB565;
enc_cfg.sub_sample = JPEG_DOWN_SAMPLING_YUV420;
enc_cfg.image_quality = CSI_JPEG_QUALITY;
enc_cfg.width = s_frame_hres;
enc_cfg.height = s_frame_vres;
uint32_t out_size = 0;
esp_err_t err = jpeg_encoder_process(s_jpeg_enc, &enc_cfg, s_frame_bufs[idx], trans.received_size,
s_jpeg_bufs[idx], s_jpeg_buf_alloc, &out_size);
if (err != ESP_OK) {
continue;
}
xSemaphoreTake(s_jpeg_lock, portMAX_DELAY);
s_ready_idx = idx;
s_ready_jpeg_len = out_size;
xSemaphoreGive(s_jpeg_lock);
s_write_idx = (idx + 1) % NUM_FRAME_BUFS;
xSemaphoreGive(s_jpeg_ready);
}
vTaskDelete(NULL);
}
CameraService::CameraService() {
s_frame_done = xSemaphoreCreateBinary();
s_jpeg_lock = xSemaphoreCreateMutex();
s_jpeg_ready = xSemaphoreCreateBinary();
}
esp_err_t CameraService::begin() {
ESP_LOGI(TAG, "Initializing MIPI-CSI camera for ESP32-P4");
esp_ldo_channel_handle_t ldo_mipi_phy = NULL;
esp_ldo_channel_config_t ldo_cfg = {};
ldo_cfg.chan_id = 3;
ldo_cfg.voltage_mv = 2500;
esp_err_t ldo_err = esp_ldo_acquire_channel(&ldo_cfg, &ldo_mipi_phy);
if (ldo_err != ESP_OK) {
ESP_LOGE(TAG, "Failed to acquire MIPI PHY LDO: %s", esp_err_to_name(ldo_err));
return ldo_err;
}
i2c_master_bus_handle_t i2c_bus = I2CBus::instance().busHandle();
if (!i2c_bus) {
ESP_LOGE(TAG, "I2C bus not initialized, cannot init camera SCCB");
return ESP_ERR_INVALID_STATE;
}
esp_sccb_io_handle_t sccb_io = NULL;
sccb_i2c_config_t sccb_cfg = {};
sccb_cfg.scl_speed_hz = CAM_SCCB_FREQ_HZ;
sccb_cfg.device_address = CAM_SENSOR_ADDR;
sccb_cfg.dev_addr_length = I2C_ADDR_BIT_LEN_7;
esp_err_t err = sccb_new_i2c_io(i2c_bus, &sccb_cfg, &sccb_io);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to create SCCB I/O handle: %s", esp_err_to_name(err));
return err;
}
esp_cam_sensor_config_t cam_sensor_cfg = {};
cam_sensor_cfg.sccb_handle = sccb_io;
cam_sensor_cfg.reset_pin = static_cast<gpio_num_t>(CAM_RESET_PIN);
cam_sensor_cfg.pwdn_pin = static_cast<gpio_num_t>(CAM_PWDN_PIN);
cam_sensor_cfg.xclk_pin = static_cast<gpio_num_t>(CAM_XCLK_PIN);
cam_sensor_cfg.xclk_freq_hz = CAM_XCLK_FREQ_HZ;
cam_sensor_cfg.sensor_port = ESP_CAM_SENSOR_MIPI_CSI;
esp_cam_sensor_device_t *cam_sensor = ov5647_detect(&cam_sensor_cfg);
if (!cam_sensor) {
ESP_LOGE(TAG, "OV5647 detection failed");
return ESP_FAIL;
}
ESP_LOGI(TAG, "OV5647 camera sensor detected");
esp_cam_sensor_format_array_t fmt_array = {};
err = esp_cam_sensor_query_format(cam_sensor, &fmt_array);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to query sensor formats: %s", esp_err_to_name(err));
return err;
}
const esp_cam_sensor_format_t *selected_format = NULL;
uint32_t best_area = 0;
for (uint32_t i = 0; i < fmt_array.count; i++) {
const auto &f = fmt_array.format_array[i];
ESP_LOGI(TAG, "Sensor format[%u]: %dx%d mipi_clk=%uHz lanes=%d", (unsigned)i, f.width, f.height,
(unsigned)f.mipi_info.mipi_clk, f.mipi_info.lane_num);
}
for (uint32_t i = 0; i < fmt_array.count; i++) {
const uint16_t w = fmt_array.format_array[i].width;
const uint16_t h = fmt_array.format_array[i].height;
if (w <= MIPI_CSI_HRES && h <= MIPI_CSI_VRES) {
const uint32_t area = (uint32_t)w * (uint32_t)h;
if (!selected_format || area > best_area) {
selected_format = &fmt_array.format_array[i];
best_area = area;
}
}
}
if (!selected_format && fmt_array.count > 0) {
uint32_t min_area = UINT32_MAX;
for (uint32_t i = 0; i < fmt_array.count; i++) {
const uint32_t area =
(uint32_t)fmt_array.format_array[i].width * (uint32_t)fmt_array.format_array[i].height;
if (area < min_area) {
selected_format = &fmt_array.format_array[i];
min_area = area;
}
}
}
if (!selected_format) {
ESP_LOGE(TAG, "No sensor formats available");
return ESP_FAIL; return ESP_FAIL;
} }
EventBus::publish(protoReq->payload.camera_settings, "HTTPEndpoint"); err = esp_cam_sensor_set_format(cam_sensor, selected_format);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set sensor format: %s", esp_err_to_name(err));
return err;
}
api_Response response = api_Response_init_zero; s_frame_hres = selected_format->width;
response.status_code = 200; s_frame_vres = selected_format->height;
response.which_payload = api_Response_empty_message_tag; ESP_LOGI(TAG, "Sensor format set: %dx%d", s_frame_hres, s_frame_vres);
return WebServer::send(request, 200, response, api_Response_fields);
esp_cam_sensor_format_t cur_fmt = {};
if (esp_cam_sensor_get_format(cam_sensor, &cur_fmt) == ESP_OK) {
ESP_LOGI(TAG, "Active format: %dx%d, mipi_clk=%uHz, lanes=%d", cur_fmt.width, cur_fmt.height,
(unsigned)cur_fmt.mipi_info.mipi_clk, cur_fmt.mipi_info.lane_num);
}
int stream_on = 1;
err = esp_cam_sensor_ioctl(cam_sensor, ESP_CAM_SENSOR_IOC_S_STREAM, &stream_on);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start sensor stream: %s", esp_err_to_name(err));
return err;
}
esp_isp_processor_cfg_t isp_cfg = {};
isp_cfg.clk_src = static_cast<isp_clk_src_t>(0);
isp_cfg.clk_hz = 80 * 1000 * 1000;
isp_cfg.input_data_source = ISP_INPUT_DATA_SOURCE_CSI;
isp_cfg.input_data_color_type = ISP_COLOR_RAW8;
isp_cfg.output_data_color_type = ISP_COLOR_RGB565;
isp_cfg.has_line_start_packet = false;
isp_cfg.has_line_end_packet = false;
isp_cfg.h_res = s_frame_hres;
isp_cfg.v_res = s_frame_vres;
isp_cfg.bayer_order = COLOR_RAW_ELEMENT_ORDER_GBRG;
err = esp_isp_new_processor(&isp_cfg, &s_isp_proc);
if (err != ESP_OK) {
ESP_LOGE(TAG, "ISP processor init failed: %s", esp_err_to_name(err));
return err;
}
err = esp_isp_enable(s_isp_proc);
if (err != ESP_OK) {
ESP_LOGE(TAG, "ISP enable failed: %s", esp_err_to_name(err));
return err;
}
esp_isp_demosaic_config_t demosaic_cfg = {};
demosaic_cfg.grad_ratio.val = 16;
demosaic_cfg.padding_mode = ISP_DEMOSAIC_EDGE_PADDING_MODE_SRND_DATA;
esp_isp_demosaic_configure(s_isp_proc, &demosaic_cfg);
esp_isp_demosaic_enable(s_isp_proc);
esp_isp_bf_config_t bf_cfg = {};
bf_cfg.denoising_level = 10;
bf_cfg.padding_mode = ISP_BF_EDGE_PADDING_MODE_SRND_DATA;
uint8_t bf_tpl[ISP_BF_TEMPLATE_X_NUMS][ISP_BF_TEMPLATE_Y_NUMS] = {{1, 2, 1}, {2, 4, 2}, {1, 2, 1}};
memcpy(bf_cfg.bf_template, bf_tpl, sizeof(bf_tpl));
esp_isp_bf_configure(s_isp_proc, &bf_cfg);
esp_isp_bf_enable(s_isp_proc);
esp_isp_sharpen_config_t sharp_cfg = {};
sharp_cfg.h_thresh = 255;
sharp_cfg.l_thresh = 20;
sharp_cfg.padding_mode = ISP_SHARPEN_EDGE_PADDING_MODE_SRND_DATA;
uint8_t sharp_m[ISP_SHARPEN_TEMPLATE_X_NUMS][ISP_SHARPEN_TEMPLATE_Y_NUMS] = {{1, 2, 1}, {2, 4, 2}, {1, 2, 1}};
memcpy(sharp_cfg.sharpen_template, sharp_m, sizeof(sharp_m));
sharp_cfg.h_freq_coeff.integer = 1;
sharp_cfg.h_freq_coeff.decimal = 0;
sharp_cfg.m_freq_coeff.integer = 1;
sharp_cfg.m_freq_coeff.decimal = 0;
esp_isp_sharpen_configure(s_isp_proc, &sharp_cfg);
esp_isp_sharpen_enable(s_isp_proc);
esp_cam_ctlr_csi_config_t csi_cfg = {};
csi_cfg.ctlr_id = 0;
csi_cfg.h_res = s_frame_hres;
csi_cfg.v_res = s_frame_vres;
csi_cfg.lane_bit_rate_mbps = MIPI_CSI_LANE_BITRATE_MBPS;
csi_cfg.input_data_color_type = CAM_CTLR_COLOR_RAW8;
csi_cfg.output_data_color_type = CAM_CTLR_COLOR_RGB565;
csi_cfg.data_lane_num = MIPI_CSI_DATA_LANES;
csi_cfg.byte_swap_en = false;
csi_cfg.queue_items = NUM_FRAME_BUFS;
err = esp_cam_new_csi_ctlr(&csi_cfg, &s_cam_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "CSI controller init failed: %s", esp_err_to_name(err));
return err;
}
esp_cam_ctlr_evt_cbs_t cbs = {};
cbs.on_trans_finished = on_trans_finished;
err = esp_cam_ctlr_register_event_callbacks(s_cam_handle, &cbs, NULL);
if (err != ESP_OK) {
ESP_LOGE(TAG, "CSI register callbacks failed: %s", esp_err_to_name(err));
return err;
}
s_frame_buf_size = ALIGN_UP((size_t)s_frame_hres * s_frame_vres * 2, CACHE_LINE_SIZE);
for (int i = 0; i < NUM_FRAME_BUFS; i++) {
s_frame_bufs[i] = (uint8_t *)heap_caps_aligned_alloc(CACHE_LINE_SIZE, s_frame_buf_size, MALLOC_CAP_SPIRAM);
if (!s_frame_bufs[i]) {
ESP_LOGE(TAG, "Failed to allocate frame buffer %d (%d bytes)", i, (int)s_frame_buf_size);
return ESP_ERR_NO_MEM;
}
}
jpeg_encode_memory_alloc_cfg_t jpeg_mem_cfg = {};
jpeg_mem_cfg.buffer_direction = JPEG_ENC_ALLOC_OUTPUT_BUFFER;
for (int i = 0; i < NUM_FRAME_BUFS; i++) {
size_t alloc_sz = 0;
s_jpeg_bufs[i] = (uint8_t *)jpeg_alloc_encoder_mem(s_frame_hres * s_frame_vres, &jpeg_mem_cfg, &alloc_sz);
if (!s_jpeg_bufs[i]) {
ESP_LOGE(TAG, "Failed to allocate JPEG buffer %d", i);
return ESP_ERR_NO_MEM;
}
if (i == 0) s_jpeg_buf_alloc = alloc_sz;
}
s_send_buf_size = s_jpeg_buf_alloc;
s_send_buf = (uint8_t *)heap_caps_aligned_alloc(CACHE_LINE_SIZE, s_send_buf_size, MALLOC_CAP_SPIRAM);
if (!s_send_buf) {
ESP_LOGE(TAG, "Failed to allocate send buffer");
return ESP_ERR_NO_MEM;
}
jpeg_encode_engine_cfg_t enc_eng_cfg = {};
enc_eng_cfg.timeout_ms = 500;
err = jpeg_new_encoder_engine(&enc_eng_cfg, &s_jpeg_enc);
if (err != ESP_OK) {
ESP_LOGE(TAG, "JPEG encoder init failed: %s", esp_err_to_name(err));
return err;
}
err = esp_cam_ctlr_enable(s_cam_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "CSI controller enable failed: %s", esp_err_to_name(err));
return err;
}
err = esp_cam_ctlr_start(s_cam_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "CSI controller start failed: %s", esp_err_to_name(err));
return err;
}
s_cam_initialized = true;
s_capture_running = true;
xTaskCreatePinnedToCore(capture_task_fn, "csi_cap", 4096, NULL, 6, &s_capture_task, 1);
ESP_LOGI(TAG, "MIPI-CSI camera initialized (%dx%d, %d-lane, %d Mbps)", s_frame_hres, s_frame_vres,
MIPI_CSI_DATA_LANES, MIPI_CSI_LANE_BITRATE_MBPS);
return ESP_OK;
} }
esp_err_t CameraService::cameraStill(httpd_req_t *request) {
if (!s_cam_initialized) {
return WebServer::sendError(request, 503, "Camera not initialized");
}
if (xSemaphoreTake(s_jpeg_ready, pdMS_TO_TICKS(3000)) != pdTRUE) {
return WebServer::sendError(request, 500, "Camera capture timed out");
}
xSemaphoreTake(s_jpeg_lock, portMAX_DELAY);
size_t len = s_ready_jpeg_len;
if (s_ready_idx >= 0 && len > 0) {
memcpy(s_send_buf, s_jpeg_bufs[s_ready_idx], len);
}
xSemaphoreGive(s_jpeg_lock);
if (len == 0) {
return WebServer::sendError(request, 500, "No frame available");
}
httpd_resp_set_type(request, "image/jpeg");
httpd_resp_set_hdr(request, "Content-Disposition", "inline; filename=capture.jpg");
return httpd_resp_send(request, (const char *)s_send_buf, len);
}
esp_err_t CameraService::cameraStream(httpd_req_t *request) {
if (!s_cam_initialized) {
return WebServer::sendError(request, 503, "Camera not initialized");
}
httpd_resp_set_type(request, _STREAM_CONTENT_TYPE);
char part_buf[64];
esp_err_t res = ESP_OK;
while (res == ESP_OK) {
if (xSemaphoreTake(s_jpeg_ready, pdMS_TO_TICKS(3000)) != pdTRUE) {
break;
}
xSemaphoreTake(s_jpeg_lock, portMAX_DELAY);
size_t jpeg_len = s_ready_jpeg_len;
if (s_ready_idx >= 0 && jpeg_len > 0) {
memcpy(s_send_buf, s_jpeg_bufs[s_ready_idx], jpeg_len);
}
xSemaphoreGive(s_jpeg_lock);
if (jpeg_len == 0) continue;
size_t hlen = snprintf(part_buf, 64, _STREAM_PART, (unsigned int)jpeg_len);
res = httpd_resp_send_chunk(request, part_buf, hlen);
if (res == ESP_OK) res = httpd_resp_send_chunk(request, (const char *)s_send_buf, jpeg_len);
if (res == ESP_OK) res = httpd_resp_send_chunk(request, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
ESP_LOGI(TAG, "Stream ended");
httpd_resp_send_chunk(request, NULL, 0);
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
CameraService::CameraService() {}
esp_err_t CameraService::begin() { return ESP_ERR_NOT_SUPPORTED; }
esp_err_t CameraService::cameraStill(httpd_req_t *request) {
return WebServer::sendError(request, 501, "Camera not supported on this platform");
}
esp_err_t CameraService::cameraStream(httpd_req_t *request) {
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
} // namespace Camera } // namespace Camera
+6 -37
View File
@@ -1,16 +1,14 @@
#include <peripherals/peripherals.h> #include <peripherals/peripherals.h>
#include <communication/webserver.h>
Peripherals::Peripherals() { _accessMutex = xSemaphoreCreateMutex(); } Peripherals::Peripherals() { _accessMutex = xSemaphoreCreateMutex(); }
void Peripherals::begin() { void Peripherals::begin() {
_settingsHandle = EventBus::subscribe<api_PeripheralSettings>( _settings = EventBus::instance().peek<PeripheralsConfiguration>();
[this](const api_PeripheralSettings &settings) { onSettingsChanged(settings); }); _settingsHandle =
EventBus::instance().subscribe<PeripheralsConfiguration>([this](const PeripheralsConfiguration &s) {
api_PeripheralSettings initialSettings; _settings = s;
if (EventBus::peek(initialSettings)) { updatePins();
onSettingsChanged(initialSettings); });
}
updatePins(); updatePins();
@@ -40,13 +38,6 @@ void Peripherals::update() {
EXECUTE_EVERY_N_MS(500, { readSonar(); }); EXECUTE_EVERY_N_MS(500, { readSonar(); });
} }
void Peripherals::onSettingsChanged(const api_PeripheralSettings &newSettings) {
_settings.sda = newSettings.sda;
_settings.scl = newSettings.scl;
_settings.frequency = newSettings.frequency;
updatePins();
}
void Peripherals::updatePins() { void Peripherals::updatePins() {
if (i2c_active) { if (i2c_active) {
I2CBus::instance().end(); I2CBus::instance().end();
@@ -103,28 +94,6 @@ void Peripherals::getSettingsProto(socket_message_PeripheralSettingsData &data)
data.pins_count = 0; data.pins_count = 0;
} }
esp_err_t Peripherals::getSettings(httpd_req_t *request) {
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = api_Response_peripheral_settings_tag;
response.payload.peripheral_settings = _settings;
return WebServer::send(request, 200, response, api_Response_fields);
}
esp_err_t Peripherals::updateSettings(httpd_req_t *request, api_Request *protoReq) {
if (protoReq->which_payload != api_Request_peripheral_settings_tag) {
return ESP_FAIL;
}
EventBus::publish(protoReq->payload.peripheral_settings, "HTTPEndpoint");
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = api_Response_empty_message_tag;
return WebServer::send(request, 200, response, api_Response_fields);
}
/* 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)
@@ -1,23 +0,0 @@
#include <peripherals/servo_controller.h>
#include <communication/webserver.h>
esp_err_t ServoController::getSettings(httpd_req_t *request) {
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = api_Response_servo_settings_tag;
response.payload.servo_settings = _settings;
return WebServer::send(request, 200, response, api_Response_fields);
}
esp_err_t ServoController::updateSettings(httpd_req_t *request, api_Request *protoReq) {
if (protoReq->which_payload != api_Request_servo_settings_tag) {
return ESP_FAIL;
}
EventBus::publish(protoReq->payload.servo_settings, "HTTPEndpoint");
api_Response response = api_Response_init_zero;
response.status_code = 200;
response.which_payload = api_Response_empty_message_tag;
return WebServer::send(request, 200, response, api_Response_fields);
}
+13 -2
View File
@@ -8,7 +8,8 @@
#include <esp_sleep.h> #include <esp_sleep.h>
#include <soc/soc.h> #include <soc/soc.h>
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || \
CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4
#include <driver/temperature_sensor.h> #include <driver/temperature_sensor.h>
static float temperatureRead() { static float temperatureRead() {
@@ -58,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);
@@ -100,7 +110,7 @@ void sleep() {
uint64_t bitmask = (uint64_t)1 << (WAKEUP_PIN_NUMBER); uint64_t bitmask = (uint64_t)1 << (WAKEUP_PIN_NUMBER);
#ifdef CONFIG_IDF_TARGET_ESP32C3 #if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4
esp_deep_sleep_enable_gpio_wakeup(bitmask, (esp_deepsleep_gpio_wake_up_mode_t)WAKEUP_SIGNAL); esp_deep_sleep_enable_gpio_wakeup(bitmask, (esp_deepsleep_gpio_wake_up_mode_t)WAKEUP_SIGNAL);
#else #else
esp_sleep_enable_ext1_wakeup(bitmask, (esp_sleep_ext1_wakeup_mode_t)WAKEUP_SIGNAL); esp_sleep_enable_ext1_wakeup(bitmask, (esp_sleep_ext1_wakeup_mode_t)WAKEUP_SIGNAL);
@@ -124,6 +134,7 @@ static const char *getChipModel() {
case CHIP_ESP32C2: return "ESP32-C2"; case CHIP_ESP32C2: return "ESP32-C2";
case CHIP_ESP32C6: return "ESP32-C6"; case CHIP_ESP32C6: return "ESP32-C6";
case CHIP_ESP32H2: return "ESP32-H2"; case CHIP_ESP32H2: return "ESP32-H2";
case CHIP_ESP32P4: return "ESP32-P4";
default: return "Unknown"; default: return "Unknown";
} }
} }
+33 -54
View File
@@ -1,7 +1,15 @@
#include <wifi_service.h> #include <wifi_service.h>
#include <communication/webserver.h> #include <communication/webserver.h>
WiFiService::WiFiService() : _initialized(false), _lastConnectionAttempt(0), _stopping(false) {} static const char *TAG = "WiFiService";
void WiFiService::registerRoutes(WebServer &s) {
s.on("/api/wifi/scan", HTTP_GET, [](httpd_req_t *request) { return handleScan(request); });
s.on("/api/wifi/networks", HTTP_GET, [](httpd_req_t *request) { return getNetworks(request); });
s.on("/api/wifi/sta/status", HTTP_GET, [](httpd_req_t *request) { return getNetworkStatus(request); });
}
WiFiService::WiFiService() : _lastConnectionAttempt(0), _stopping(false) {}
WiFiService::~WiFiService() {} WiFiService::~WiFiService() {}
@@ -14,41 +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);
_settingsHandle = EventBus::subscribe<api_WifiSettings>( _settings = EventBus::instance().peek<WiFiSettings>();
[this](const api_WifiSettings &settings) { onSettingsChanged(settings); }); _settingsHandle = EventBus::instance().subscribe<WiFiSettings>([this](const WiFiSettings &s) {
_settings = s;
api_WifiSettings initialSettings;
if (EventBus::peek(initialSettings)) {
ESP_LOGI(TAG, "Applying initial WiFi settings from storage");
onSettingsChanged(initialSettings);
} else {
ESP_LOGW(TAG, "No WiFi settings found, using defaults");
}
}
void WiFiService::onSettingsChanged(const api_WifiSettings &newSettings) {
api_WifiSettings oldSettings = getSettings();
bool needsReconnect = _initialized && (strcmp(oldSettings.hostname, newSettings.hostname) != 0 ||
oldSettings.selected_network != newSettings.selected_network ||
oldSettings.wifi_networks_count != newSettings.wifi_networks_count);
if (!_initialized) {
_initialized = true;
_lastConnectionAttempt = 0;
ESP_LOGI(TAG, "Initializing WiFi with loaded settings");
if (newSettings.wifi_networks_count >= 1) {
WiFi.mode(WIFI_MODE_STA);
vTaskDelay(100 / portTICK_PERIOD_MS);
uint32_t idx = newSettings.selected_network;
if (idx >= newSettings.wifi_networks_count) idx = 0;
configureNetwork(newSettings.wifi_networks[idx]);
}
} else if (needsReconnect) {
ESP_LOGI(TAG, "Settings changed, reconnecting");
reconfigureWiFiConnection(); reconfigureWiFiConnection();
});
_lastConnectionAttempt = 0;
if (_settings.wifi_networks_count >= 1) {
WiFi.mode(WIFI_MODE_STA);
vTaskDelay(100 / portTICK_PERIOD_MS);
uint32_t idx = _settings.selected_network;
if (idx >= _settings.wifi_networks_count) idx = 0;
configureNetwork(_settings.wifi_networks[idx]);
} }
} }
@@ -58,13 +44,9 @@ void WiFiService::reconfigureWiFiConnection() {
} }
void WiFiService::selectNetwork(uint32_t index) { void WiFiService::selectNetwork(uint32_t index) {
api_WifiSettings settings = getSettings(); if (index >= _settings.wifi_networks_count) return;
if (index >= settings.wifi_networks_count) return; _settings.selected_network = index;
EventBus::instance().publish(_settings);
settings.selected_network = index;
EventBus::publish(settings, "WiFiService");
reconfigureWiFiConnection();
} }
void WiFiService::loop() { EXECUTE_EVERY_N_MS(reconnectDelay, manageSTA()); } void WiFiService::loop() { EXECUTE_EVERY_N_MS(reconnectDelay, manageSTA()); }
@@ -114,8 +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();
api_WifiSettings settings = getSettings(); mdns_hostname_set(_settings.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);
@@ -154,8 +135,7 @@ esp_err_t WiFiService::getNetworkStatus(httpd_req_t *request) {
} }
void WiFiService::manageSTA() { void WiFiService::manageSTA() {
api_WifiSettings settings = getSettings(); if (WiFi.isConnected() || _settings.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;
@@ -170,24 +150,23 @@ 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 && settings.wifi_networks_count > 0) { if (!attempted && _settings.wifi_networks_count > 0) {
attempted = true; attempted = true;
uint32_t idx = settings.selected_network; uint32_t idx = _settings.selected_network;
if (idx >= settings.wifi_networks_count) idx = 0; if (idx >= _settings.wifi_networks_count) idx = 0;
ESP_LOGI(TAG, "Connecting to: %s", settings.wifi_networks[idx].ssid); ESP_LOGI(TAG, "Connecting to: %s", _settings.wifi_networks[idx].ssid);
configureNetwork(settings.wifi_networks[idx]); configureNetwork(_settings.wifi_networks[idx]);
} }
} }
void WiFiService::configureNetwork(const WiFiNetwork &network) { void WiFiService::configureNetwork(WiFiNetwork &network) {
if (network.static_ip_config) { if (network.static_ip_config) {
WiFi.config(IPAddress(network.local_ip), IPAddress(network.gateway_ip), IPAddress(network.subnet_mask), WiFi.config(IPAddress(network.local_ip), IPAddress(network.gateway_ip), IPAddress(network.subnet_mask),
IPAddress(network.dns_ip_1), IPAddress(network.dns_ip_2)); IPAddress(network.dns_ip_1), IPAddress(network.dns_ip_2));
} 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));
} }
api_WifiSettings settings = getSettings(); WiFi.setHostname(_settings.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
+40
View File
@@ -15,6 +15,7 @@ src_dir = esp32/src
include_dir = esp32/include include_dir = esp32/include
lib_dir = esp32/lib lib_dir = esp32/lib
test_dir = esp32/test test_dir = esp32/test
boards_dir = boards
extra_configs = extra_configs =
esp32/factory_settings.ini esp32/factory_settings.ini
esp32/features.ini esp32/features.ini
@@ -73,6 +74,45 @@ board_build.partitions = esp32/partition_table/min_spiffs.csv
build_flags = build_flags =
${env.build_flags} ${env.build_flags}
[env:esp32-p4]
platform = https://github.com/pioarduino/platform-espressif32.git
framework = espidf
board = esp32p4_dev
board_build.mcu = esp32p4
board_build.f_cpu = 360000000L
board_build.f_flash = 80000000L
board_build.f_psram = 200000000L
board_build.flash_mode = qio
board_upload.flash_size = 32MB
board_build.partitions = esp32/partition_table/default_32MB.csv
board_build.filesystem = littlefs
board_build.sdkconfig_defaults =
esp32/sdkconfig.defaults
esp32/sdkconfig.defaults.esp32p4
upload_speed = 921600
monitor_speed = 115200
monitor_filters =
direct
esp32_exception_decoder
extra_scripts =
pre:esp32/scripts/pre_build.py
pre:esp32/scripts/build_app.py
build_flags =
${env.build_flags}
-D USE_CAMERA=1
-D CAM_XCLK_PIN=-1
-D CAM_RESET_PIN=-1
-D CAM_PWDN_PIN=-1
-D MIPI_CSI_HRES=800
-D MIPI_CSI_VRES=640
-D MIPI_CSI_LANE_BITRATE_MBPS=400
-D MIPI_CSI_DATA_LANES=2
-D CSI_JPEG_QUALITY=65
-D BOARD_HAS_PSRAM
-D SDA_PIN=7
-D SCL_PIN=8
-D WS2812_PIN=27
; ================================================================ ; ================================================================
; General environment section ; General environment section