Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dd7a5dce9 | |||
| c7e6376afe | |||
| bc8145aca9 | |||
| 90f561a62b | |||
| 32966a3430 | |||
| ee571ed0ff | |||
| 135ab88e25 | |||
| cefeb11e10 | |||
| e5e8a94acd | |||
| 6f3e254341 | |||
| 7221418378 | |||
| b8038e402b | |||
| 2037b654a6 | |||
| 941a2b10f7 | |||
| fcf058921a | |||
| 0047810098 | |||
| 326decdae3 | |||
| ebbe1f9ca4 | |||
| 42cce24c11 | |||
| 48829dafaa | |||
| 2536410ee3 | |||
| 41da5163bc |
@@ -6,11 +6,6 @@ __pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.pio
|
||||
managed_components/
|
||||
dependencies.lock
|
||||
sdkconfig
|
||||
sdkconfig.*
|
||||
!sdkconfig.defaults
|
||||
esp32/src/platform_shared/*
|
||||
!esp32/src/platform_shared/.gitkeep
|
||||
app/src/lib/platform_shared/*
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(Spot_Micro_Leika)
|
||||
@@ -245,6 +245,10 @@
|
||||
angleChart.update('none')
|
||||
}
|
||||
|
||||
if ($features.mag) {
|
||||
updateChartData(magnetometerChart, $imu.heading)
|
||||
}
|
||||
|
||||
if ($features.bmp && tempChart && altitudeChart) {
|
||||
updateChartData(
|
||||
tempChart,
|
||||
|
||||
@@ -13,6 +13,8 @@ build_flags =
|
||||
-D USE_HMC5883=0
|
||||
-D USE_BMP180=0
|
||||
-D USE_MPU6050=0
|
||||
-D USE_ICM20948=1
|
||||
-D USE_ICM20948_SPIMODE=0
|
||||
-D USE_WS2812=1
|
||||
-D USE_BNO055=0
|
||||
-D USE_USS=0
|
||||
|
||||
+10
-17
@@ -1,15 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <event_bus/event_bus.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
#include <settings/ap_settings.h>
|
||||
#include <utils/timing.h>
|
||||
#include <wifi/wifi_idf.h>
|
||||
#include <wifi/dns_server.h>
|
||||
#include <esp_timer.h>
|
||||
#include <esp_http_server.h>
|
||||
#include <string>
|
||||
#include <WiFi.h>
|
||||
#include "esp_timer.h"
|
||||
|
||||
class APService {
|
||||
class APService : public StatefulService<APSettings> {
|
||||
public:
|
||||
APService();
|
||||
~APService();
|
||||
@@ -22,21 +21,15 @@ class APService {
|
||||
void statusProto(api_APStatus &proto);
|
||||
APNetworkStatus getAPNetworkStatus();
|
||||
|
||||
esp_err_t getSettings(httpd_req_t *request);
|
||||
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
|
||||
StatefulProtoEndpoint<APSettings, api_APSettings> protoEndpoint;
|
||||
|
||||
private:
|
||||
static constexpr const char *TAG = "APService";
|
||||
|
||||
void onSettingsChanged(const api_APSettings &newSettings);
|
||||
|
||||
APSettings _settings = APSettings_defaults();
|
||||
EventBus::Handle<api_APSettings> _settingsHandle;
|
||||
FSPersistencePB<APSettings> _persistence;
|
||||
DNSServer *_dnsServer;
|
||||
|
||||
volatile unsigned long _lastManaged;
|
||||
volatile bool _reconfigureAp;
|
||||
volatile bool _recoveryMode = false;
|
||||
volatile boolean _reconfigureAp;
|
||||
volatile boolean _recoveryMode = false;
|
||||
|
||||
void reconfigureAP();
|
||||
void manageAP();
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <communication/proto_helpers.h>
|
||||
#include <event_bus/event_bus.h>
|
||||
|
||||
class CommAdapterBase {
|
||||
public:
|
||||
@@ -38,63 +32,38 @@ class CommAdapterBase {
|
||||
decoder_.on<T>(handler);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void onPublish(std::function<void(const T&, int)> handler = nullptr) {
|
||||
decoder_.on<T>([this, handler](const T& data, int clientId) {
|
||||
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>
|
||||
void emit(const T& data, int clientId = -1) {
|
||||
constexpr pb_size_t tag = MessageTraits<T>::tag;
|
||||
|
||||
if (clientId < 0 && !hasSubscribers(tag)) return;
|
||||
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
|
||||
msg_.which_message = tag;
|
||||
MessageTraits<T>::assign(msg_, data);
|
||||
|
||||
size_t out_size;
|
||||
pb_get_encoded_size(&out_size, socket_message_Message_fields, &msg_);
|
||||
uint8_t* buffer = pb_heap_enc_buf;
|
||||
if (out_size > sizeof(pb_heap_enc_buf)) {
|
||||
if (out_size > sizeof(pb_heap_enc_buf)) { // If the encoded size exceeds our buffer size, we needs to malloc a
|
||||
// buffer of a proper size
|
||||
buffer = (uint8_t*)malloc(out_size);
|
||||
}
|
||||
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(buffer, out_size);
|
||||
if (!pb_encode(&stream, socket_message_Message_fields, &msg_)) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (clientId >= 0) {
|
||||
send(buffer, stream.bytes_written, clientId);
|
||||
} else {
|
||||
sendToSubscribersLocked(tag, buffer, stream.bytes_written);
|
||||
sendToSubscribers(tag, buffer, stream.bytes_written);
|
||||
}
|
||||
|
||||
if (pb_heap_enc_buf != buffer) {
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
xSemaphoreGive(mutex_);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -144,22 +113,12 @@ class CommAdapterBase {
|
||||
socket_message_Message msg_ = socket_message_Message_init_zero;
|
||||
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:
|
||||
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]) {
|
||||
send(data, len, cid);
|
||||
}
|
||||
xSemaphoreGive(mutex_);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef CONFIG_HTTPD_WS_SUPPORT
|
||||
#define CONFIG_HTTPD_WS_SUPPORT 1
|
||||
#endif
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <Arduino.h>
|
||||
#include <communication/webserver.h>
|
||||
#include <communication/comm_base.hpp>
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef PROGMEM
|
||||
#define PROGMEM
|
||||
#endif
|
||||
|
||||
#ifndef PGM_P
|
||||
#define PGM_P const char *
|
||||
#endif
|
||||
|
||||
#ifndef pgm_read_byte
|
||||
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
|
||||
#endif
|
||||
|
||||
#ifndef pgm_read_word
|
||||
#define pgm_read_word(addr) (*(const unsigned short *)(addr))
|
||||
#endif
|
||||
|
||||
#ifndef pgm_read_dword
|
||||
#define pgm_read_dword(addr) (*(const unsigned long *)(addr))
|
||||
#endif
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
@@ -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>;
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
enum class SystemEventType : uint8_t {
|
||||
STORAGE_HYDRATION_COMPLETE,
|
||||
SYSTEM_BOOT_COMPLETE,
|
||||
};
|
||||
|
||||
struct SystemEvent {
|
||||
SystemEventType type;
|
||||
};
|
||||
@@ -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; }
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <wifi/wifi_idf.h>
|
||||
#include <WiFi.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <esp_http_server.h>
|
||||
#include "platform_shared/message.pb.h"
|
||||
|
||||
@@ -18,6 +19,15 @@
|
||||
#define USE_BNO055 1
|
||||
#endif
|
||||
|
||||
// ESP32 IMU off by default
|
||||
#ifndef USE_ICM20948
|
||||
#define USE_ICM20948 0
|
||||
#endif
|
||||
#ifndef USE_ICM20948_SPIMODE // I2C on by default
|
||||
#define USE_ICM20948_SPIMODE 0
|
||||
#endif
|
||||
|
||||
// ESP32 magnetometer on by default
|
||||
#ifndef USE_HMC5883
|
||||
#define USE_HMC5883 0
|
||||
#endif
|
||||
|
||||
+12
-22
@@ -1,39 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <esp_littlefs.h>
|
||||
#include <esp_vfs.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <LittleFS.h>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <platform_shared/api.pb.h>
|
||||
|
||||
#define MOUNT_POINT "/littlefs"
|
||||
#define ESP_FS LittleFS
|
||||
|
||||
#define FS_CONFIG_DIRECTORY "/config"
|
||||
#define DEVICE_CONFIG_FILE "/config/peripheral.json"
|
||||
#define CAMERA_SETTINGS_FILE "/config/cameraSettings.pb"
|
||||
#define AP_SETTINGS_FILE "/config/apSettings.pb"
|
||||
#define MDNS_SETTINGS_FILE "/config/mdnsSettings.pb"
|
||||
#define WIFI_SETTINGS_FILE "/config/wifiSettings.pb"
|
||||
#define PERIPHERAL_SETTINGS_FILE "/config/peripheralSettings.pb"
|
||||
#define SERVO_SETTINGS_FILE "/config/servoSettings.pb"
|
||||
|
||||
#define FS_CONFIG_DIRECTORY MOUNT_POINT "/config"
|
||||
#define DEVICE_CONFIG_FILE MOUNT_POINT "/config/peripheral.pb"
|
||||
#define CAMERA_SETTINGS_FILE MOUNT_POINT "/config/cameraSettings.pb"
|
||||
#define AP_SETTINGS_FILE MOUNT_POINT "/config/apSettings.pb"
|
||||
#define MDNS_SETTINGS_FILE MOUNT_POINT "/config/mdnsSettings.pb"
|
||||
#define WIFI_SETTINGS_FILE MOUNT_POINT "/config/wifiSettings.pb"
|
||||
#define PERIPHERAL_SETTINGS_FILE MOUNT_POINT "/config/peripheralSettings.pb"
|
||||
#define SERVO_SETTINGS_FILE MOUNT_POINT "/config/servoSettings.pb"
|
||||
|
||||
namespace FileSystem {
|
||||
|
||||
bool init();
|
||||
|
||||
void listFilesProto(const std::string &directory, api_FileEntry *entry);
|
||||
std::string listFiles(const std::string &directory, bool isRoot = true);
|
||||
bool deleteFile(const char *filename);
|
||||
bool editFile(const char *filename, const uint8_t *content, size_t size);
|
||||
bool editFile(const char *filename, const char *content);
|
||||
bool fileExists(const char *filename);
|
||||
std::string readFile(const char *filename);
|
||||
bool writeFile(const char *filename, const char *content);
|
||||
bool writeFile(const char *filename, const uint8_t *content, size_t size);
|
||||
bool mkdirRecursive(const char *path);
|
||||
|
||||
esp_err_t getFilesProto(httpd_req_t *request);
|
||||
esp_err_t getFiles(httpd_req_t *request);
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <LittleFS.h>
|
||||
#include <platform_shared/message.pb.h>
|
||||
#include <filesystem.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <cstdio>
|
||||
|
||||
#define FS_MAX_CHUNK_SIZE 16384
|
||||
#define FS_TRANSFER_TIMEOUT_MS 30000
|
||||
// Make sure that this aligns with socket_message.FSDownloadData.data max_size (and for upload)
|
||||
#define FS_MAX_CHUNK_SIZE 16384 // ~= 16 kb
|
||||
#define FS_TRANSFER_TIMEOUT_MS 30000 // 30 seconds
|
||||
|
||||
namespace FileSystemWS {
|
||||
|
||||
struct DownloadState {
|
||||
std::string path;
|
||||
FILE* file;
|
||||
File file;
|
||||
uint32_t fileSize;
|
||||
uint32_t chunkSize;
|
||||
uint32_t totalChunks;
|
||||
@@ -25,7 +25,7 @@ struct DownloadState {
|
||||
|
||||
struct UploadState {
|
||||
std::string path;
|
||||
FILE* file;
|
||||
File file;
|
||||
uint32_t fileSize;
|
||||
uint32_t totalChunks;
|
||||
uint32_t chunksReceived;
|
||||
@@ -36,6 +36,7 @@ struct UploadState {
|
||||
std::string errorMessage;
|
||||
};
|
||||
|
||||
// Callback type for sending messages to clients
|
||||
using SendMetadataCallback = std::function<void(const socket_message_FSDownloadMetadata&, int clientId)>;
|
||||
using SendCallback = std::function<void(const socket_message_FSDownloadData&, int clientId)>;
|
||||
using SendCompleteCallback = std::function<void(const socket_message_FSDownloadComplete&, int clientId)>;
|
||||
@@ -45,17 +46,35 @@ class FileSystemHandler {
|
||||
public:
|
||||
FileSystemHandler();
|
||||
|
||||
// Set callbacks for sending streaming data
|
||||
void setSendCallbacks(SendMetadataCallback sendMetadata, SendCallback sendData, SendCompleteCallback sendComplete,
|
||||
SendUploadCompleteCallback sendUploadComplete);
|
||||
|
||||
// Delete file/directory
|
||||
socket_message_FSDeleteResponse handleDelete(const socket_message_FSDeleteRequest& req);
|
||||
|
||||
// Create directory
|
||||
socket_message_FSMkdirResponse handleMkdir(const socket_message_FSMkdirRequest& req);
|
||||
|
||||
// List directory
|
||||
socket_message_FSListResponse handleList(const socket_message_FSListRequest& req);
|
||||
|
||||
// Streaming download - starts the download and streams all chunks
|
||||
void handleDownloadRequest(const socket_message_FSDownloadRequest& req, int clientId);
|
||||
|
||||
// Streaming upload - start upload session
|
||||
socket_message_FSUploadStartResponse handleUploadStart(const socket_message_FSUploadStart& req, int clientId);
|
||||
|
||||
// Streaming upload - receive chunk data (fire-and-forget from client)
|
||||
void handleUploadData(const socket_message_FSUploadData& req);
|
||||
|
||||
// Cancel transfer
|
||||
socket_message_FSCancelTransferResponse handleCancelTransfer(const socket_message_FSCancelTransfer& req);
|
||||
|
||||
// Cleanup expired transfers
|
||||
void cleanupExpiredTransfers();
|
||||
|
||||
// Process pending downloads (call from main loop)
|
||||
void processPendingDownloads();
|
||||
|
||||
private:
|
||||
@@ -72,7 +91,9 @@ class FileSystemHandler {
|
||||
|
||||
void listDirectory(const std::string& path, socket_message_FSListResponse& response);
|
||||
bool deleteRecursive(const std::string& path);
|
||||
|
||||
bool sendNextDownloadChunk(uint32_t transferId);
|
||||
|
||||
void finalizeUpload(uint32_t transferId, bool success, const std::string& error = "");
|
||||
};
|
||||
|
||||
|
||||
+18
-7
@@ -1,15 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <sdkconfig.h>
|
||||
#include <esp_system.h>
|
||||
#include <esp32-hal.h>
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
|
||||
#include "esp32/rom/rtc.h"
|
||||
#ifndef ESP_PLATFORM_NAME
|
||||
#define ESP_PLATFORM_NAME "ESP32"
|
||||
#endif
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#include "esp32s2/rom/rtc.h"
|
||||
#include "esp32/rom/rtc.h"
|
||||
#ifndef ESP_PLATFORM_NAME
|
||||
#define ESP_PLATFORM_NAME "ESP32-S2"
|
||||
#endif
|
||||
@@ -27,12 +26,24 @@
|
||||
#error Target CONFIG_IDF_TARGET is not supported
|
||||
#endif
|
||||
|
||||
#ifndef ARDUINO_VERSION
|
||||
#ifndef STRINGIFY
|
||||
#define STRINGIFY(s) #s
|
||||
#endif
|
||||
#define ARDUINO_VERSION_STR(major, minor, patch) "v" STRINGIFY(major) "." STRINGIFY(minor) "." STRINGIFY(patch)
|
||||
#define ARDUINO_VERSION \
|
||||
ARDUINO_VERSION_STR(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* I2C software connection
|
||||
*/
|
||||
#ifndef SDA_PIN
|
||||
#define SDA_PIN 21
|
||||
#define SDA_PIN SDA
|
||||
#endif
|
||||
#ifndef SCL_PIN
|
||||
#define SCL_PIN 22
|
||||
#define SCL_PIN SCL
|
||||
#endif
|
||||
#ifndef I2C_FREQUENCY
|
||||
#define I2C_FREQUENCY 100000UL
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,12 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <mdns.h>
|
||||
#include <event_bus/event_bus.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
#include <settings/mdns_settings.h>
|
||||
#include <utils/timing.h>
|
||||
|
||||
class MDNSService {
|
||||
class MDNSService : public StatefulService<MDNSSettings> {
|
||||
private:
|
||||
FSPersistencePB<MDNSSettings> _persistence;
|
||||
bool _started {false};
|
||||
|
||||
void reconfigureMDNS();
|
||||
void startMDNS();
|
||||
void stopMDNS();
|
||||
void addServices();
|
||||
|
||||
public:
|
||||
MDNSService();
|
||||
~MDNSService();
|
||||
@@ -16,20 +27,5 @@ class MDNSService {
|
||||
esp_err_t getStatus(httpd_req_t *request);
|
||||
esp_err_t queryServices(httpd_req_t *request, api_Request *protoReq);
|
||||
|
||||
esp_err_t getSettings(httpd_req_t *request);
|
||||
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
|
||||
|
||||
private:
|
||||
static constexpr const char *TAG = "MDNSService";
|
||||
|
||||
void onSettingsChanged(const api_MDNSSettings &newSettings);
|
||||
|
||||
MDNSSettings _settings = MDNSSettings_defaults();
|
||||
EventBus::Handle<api_MDNSSettings> _settingsHandle;
|
||||
bool _started {false};
|
||||
|
||||
void reconfigureMDNS();
|
||||
void startMDNS();
|
||||
void stopMDNS();
|
||||
void addServices();
|
||||
StatefulProtoEndpoint<MDNSSettings, api_MDNSSettings> protoEndpoint;
|
||||
};
|
||||
|
||||
@@ -14,18 +14,13 @@
|
||||
#include <motion_states/stand_state.h>
|
||||
#include <motion_states/rest_state.h>
|
||||
#include <message_types.h>
|
||||
#include <event_bus/event_bus.h>
|
||||
|
||||
enum class MOTION_STATE { DEACTIVATED, IDLE, CALIBRATION, REST, STAND, WALK };
|
||||
|
||||
class MotionService {
|
||||
public:
|
||||
using ModeChangeCallback = std::function<void(bool active)>;
|
||||
|
||||
void begin();
|
||||
|
||||
void setModeChangeCallback(ModeChangeCallback callback) { modeChangeCallback_ = callback; }
|
||||
|
||||
void handleAngles(const socket_message_AnglesData& data);
|
||||
|
||||
void handleInput(const socket_message_ControllerData& data);
|
||||
@@ -47,8 +42,6 @@ class MotionService {
|
||||
inline bool isActive() { return state != nullptr; }
|
||||
|
||||
private:
|
||||
void subscribeToEvents();
|
||||
|
||||
Kinematics kinematics;
|
||||
|
||||
CommandMsg command = {0, 0, 0, 0, 0, 0, 0};
|
||||
@@ -69,13 +62,6 @@ class MotionService {
|
||||
float dir[12] = {1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1};
|
||||
|
||||
int64_t lastUpdate = esp_timer_get_time();
|
||||
|
||||
ModeChangeCallback modeChangeCallback_;
|
||||
|
||||
EventBus::Handle<socket_message_ControllerData> controllerHandle_;
|
||||
EventBus::Handle<socket_message_ModeData> modeHandle_;
|
||||
EventBus::Handle<socket_message_AnglesData> anglesHandle_;
|
||||
EventBus::Handle<socket_message_WalkGaitData> walkGaitHandle_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include <kinematics.h>
|
||||
#include <message_types.h>
|
||||
#include <utils/math_utils.h>
|
||||
#include <cstring>
|
||||
|
||||
class MotionState {
|
||||
protected:
|
||||
@@ -28,15 +26,15 @@ class MotionState {
|
||||
}
|
||||
|
||||
void updateFeet(body_state_t& body_state, const float smoothing_factor = default_smoothing_factor) {
|
||||
if (std::memcmp(target_body_state.feet, body_state.feet, sizeof(body_state.feet)) != 0) {
|
||||
if (target_body_state.feet != body_state.feet) {
|
||||
body_state.updateFeet(target_body_state.feet);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void updateImuOffsets(const float new_omega, const float new_psi) {
|
||||
omega_offset = RAD_TO_DEG_F(new_omega);
|
||||
psi_offset = RAD_TO_DEG_F(new_psi);
|
||||
omega_offset = new_omega * RAD_TO_DEG;
|
||||
psi_offset = new_psi * RAD_TO_DEG;
|
||||
}
|
||||
virtual ~MotionState() {}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ struct BarometerMsg {
|
||||
|
||||
class Barometer : public SensorBase<BarometerMsg> {
|
||||
public:
|
||||
bool initialize() override {
|
||||
bool initialize(void* _) {
|
||||
_msg.success = _bmp.begin();
|
||||
if (_msg.success) {
|
||||
ESP_LOGI("BMP", "BMP180 initialized successfully");
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include <features.h>
|
||||
#include <event_bus/event_bus.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
|
||||
#include <settings/camera_settings.h>
|
||||
|
||||
@@ -21,7 +24,7 @@ camera_fb_t *safe_camera_fb_get();
|
||||
sensor_t *safe_sensor_get();
|
||||
void safe_sensor_return();
|
||||
|
||||
class CameraService {
|
||||
class CameraService : public StatefulService<CameraSettings> {
|
||||
public:
|
||||
CameraService();
|
||||
|
||||
@@ -30,16 +33,10 @@ class CameraService {
|
||||
esp_err_t cameraStill(httpd_req_t *request);
|
||||
esp_err_t cameraStream(httpd_req_t *request);
|
||||
|
||||
esp_err_t getSettings(httpd_req_t *request);
|
||||
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
|
||||
StatefulProtoEndpoint<CameraSettings, api_CameraSettings> protoEndpoint;
|
||||
|
||||
private:
|
||||
static constexpr const char *TAG = "CameraService";
|
||||
|
||||
void onSettingsChanged(const api_CameraSettings &newSettings);
|
||||
FSPersistencePB<CameraSettings> _persistence;
|
||||
void updateCamera();
|
||||
|
||||
CameraSettings _settings = CameraSettings_defaults();
|
||||
EventBus::Handle<api_CameraSettings> _settingsHandle;
|
||||
};
|
||||
} // namespace Camera
|
||||
|
||||
@@ -316,7 +316,8 @@ class MPU6050Driver {
|
||||
i += chunkSize;
|
||||
address += chunkSize;
|
||||
|
||||
if (address == 0) {
|
||||
if (address == 0 || address >= 256) {
|
||||
address = 0;
|
||||
bank++;
|
||||
setMemoryBank(bank);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
#include <features.h>
|
||||
#include <peripherals/sensor.hpp>
|
||||
|
||||
#if FT_ENABLED(USE_ICM20948)
|
||||
#include "ICM_20948.h"
|
||||
#endif
|
||||
|
||||
#if FT_ENABLED(USE_MPU6050)
|
||||
#include <peripherals/drivers/mpu6050.h>
|
||||
#endif
|
||||
@@ -20,7 +24,7 @@ struct IMUAnglesMsg {
|
||||
|
||||
class IMU : public SensorBase<IMUAnglesMsg> {
|
||||
public:
|
||||
bool initialize() override {
|
||||
bool initialize(void* _arg = nullptr) override {
|
||||
#if FT_ENABLED(USE_MPU6050)
|
||||
_msg.success = _imu.begin();
|
||||
if (!_msg.success) {
|
||||
@@ -35,13 +39,43 @@ class IMU : public SensorBase<IMUAnglesMsg> {
|
||||
ESP_LOGE("IMU", "BNO055 initialization failed");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGI("IMU", "BNO055 initialized successfully");
|
||||
#endif
|
||||
#if FT_ENABLED(USE_ICM20948)
|
||||
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||
#define ICM_20948_USE_DMP // TODO: Move to features.ini
|
||||
SPI_PORT.begin(SPI_SCK, SPI_MISO, SPI_MOSI, -1); // TODO: Move to global spi start
|
||||
_imu = (ICM_20948_SPI*)_arg;
|
||||
#ifndef ICM20948_ALIVE
|
||||
#define ICM20948_ALIVE
|
||||
_imu->begin(ICM20948_SPI_CS, SPI_PORT); ESP_LOGI("IMU", "Beginning ICM20948 in SPI mode");
|
||||
_imu->initializeDMP();
|
||||
#endif
|
||||
#else
|
||||
_imu = (ICM_20948_I2C*)_arg;
|
||||
#ifndef ICM20948_ALIVE
|
||||
#define ICM20948_ALIVE
|
||||
_imu->begin(Wire, 1, 0xFF); ESP_LOGI("IMU", "Beginning ICM20948 in I2C mode");
|
||||
#endif
|
||||
|
||||
#endif
|
||||
if (_imu->status != ICM_20948_Stat_Ok){ ESP_LOGW("IMU", "Failed to start ICM20948: begin failed"); return false; }
|
||||
|
||||
_imu->setSampleMode((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), ICM_20948_Sample_Mode_Continuous);
|
||||
if (_imu->status != ICM_20948_Stat_Ok){ ESP_LOGW("IMU", "Failed to start ICM20948: set sample failed"); return false; }
|
||||
|
||||
ICM_20948_fss_t myFSS;
|
||||
myFSS.a = gpm2;
|
||||
myFSS.g = dps250;
|
||||
_imu->setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), myFSS);
|
||||
if (_imu->status != ICM_20948_Stat_Ok){ ESP_LOGW("IMU", "Failed to start ICM20948: set full scale failed"); return false; }
|
||||
// TODO: Setup low pass filter config
|
||||
_msg.success = true;
|
||||
#endif
|
||||
return _msg.success;
|
||||
}
|
||||
|
||||
bool update() override {
|
||||
if (!_msg.success) return false;
|
||||
//if (!_msg.success) return false;
|
||||
#if FT_ENABLED(USE_MPU6050)
|
||||
if (!_imu.update()) return false;
|
||||
_msg.rpy[0] = _imu.getYaw();
|
||||
@@ -49,11 +83,22 @@ class IMU : public SensorBase<IMUAnglesMsg> {
|
||||
_msg.rpy[2] = _imu.getRoll();
|
||||
_msg.temperature = _imu.getTemperature();
|
||||
#endif
|
||||
#if FT_ENABLED(USE_ICM20948)
|
||||
|
||||
#ifndef ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
||||
#define ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
||||
if (_imu->dataReady())
|
||||
{
|
||||
_imu->getAGMT();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
_msg.rpy[0] = _imu->accX();
|
||||
_msg.rpy[1] = _imu->accY();
|
||||
_msg.rpy[2] = _imu->accZ();
|
||||
#endif
|
||||
#if FT_ENABLED(USE_BNO055)
|
||||
if (!_imu.update()) return false;
|
||||
_msg.rpy[0] = _imu.getHeading();
|
||||
_msg.rpy[1] = _imu.getPitch();
|
||||
_msg.rpy[2] = _imu.getRoll();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
@@ -88,4 +133,12 @@ class IMU : public SensorBase<IMUAnglesMsg> {
|
||||
#if FT_ENABLED(USE_BNO055)
|
||||
BNO055Driver _imu;
|
||||
#endif
|
||||
#if FT_ENABLED(USE_ICM20948)
|
||||
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||
ICM_20948_SPI* _imu;
|
||||
#else
|
||||
//#define WIRE_PORT Wire
|
||||
ICM_20948_I2C* _imu;
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1,105 +1,56 @@
|
||||
#ifndef LEDService_h
|
||||
#define LEDService_h
|
||||
|
||||
#include <driver/rmt_tx.h>
|
||||
#include <led_strip.h>
|
||||
#include <led_strip_rmt.h>
|
||||
#include <wifi/wifi_idf.h>
|
||||
#include <utils/timing.h>
|
||||
#include <esp_log.h>
|
||||
#include <FastLED.h>
|
||||
|
||||
#ifndef WS2812_PIN
|
||||
#define WS2812_PIN 12
|
||||
#endif
|
||||
|
||||
#ifndef WS2812_NUM_LEDS
|
||||
#define WS2812_NUM_LEDS 13
|
||||
#define WS2812_NUM_LEDS 1 + 12
|
||||
#endif
|
||||
|
||||
#define COLOR_ORDER GRB
|
||||
#define CHIPSET WS2811
|
||||
|
||||
class LEDService {
|
||||
private:
|
||||
led_strip_handle_t led_strip = nullptr;
|
||||
CRGB leds[WS2812_NUM_LEDS];
|
||||
CRGBPalette16 currentPalette;
|
||||
TBlendType currentBlending;
|
||||
|
||||
int _brightness = 255;
|
||||
int direction = 1;
|
||||
bool initialized = false;
|
||||
|
||||
struct RGB {
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
RGB oceanColor = {0, 119, 190};
|
||||
RGB forestColor = {34, 139, 34};
|
||||
|
||||
public:
|
||||
LEDService() {}
|
||||
|
||||
~LEDService() {
|
||||
if (initialized && led_strip) {
|
||||
led_strip_del(led_strip);
|
||||
}
|
||||
}
|
||||
|
||||
void begin() {
|
||||
if (initialized) return;
|
||||
|
||||
led_strip_config_t strip_config = {
|
||||
.strip_gpio_num = WS2812_PIN,
|
||||
.max_leds = WS2812_NUM_LEDS,
|
||||
.led_pixel_format = LED_PIXEL_FORMAT_GRB,
|
||||
.led_model = LED_MODEL_WS2812,
|
||||
.flags = {.invert_out = false},
|
||||
};
|
||||
|
||||
led_strip_rmt_config_t rmt_config = {
|
||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 10 * 1000 * 1000,
|
||||
.mem_block_symbols = 64,
|
||||
.flags = {.with_dma = false},
|
||||
};
|
||||
|
||||
esp_err_t err = led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip);
|
||||
if (err == ESP_OK) {
|
||||
initialized = true;
|
||||
led_strip_clear(led_strip);
|
||||
ESP_LOGI("LEDService", "LED strip initialized on GPIO %d", WS2812_PIN);
|
||||
} else {
|
||||
ESP_LOGE("LEDService", "Failed to initialize LED strip: %s", esp_err_to_name(err));
|
||||
}
|
||||
LEDService() {
|
||||
FastLED.addLeds<CHIPSET, WS2812_PIN, COLOR_ORDER>(leds, WS2812_NUM_LEDS).setCorrection(TypicalLEDStrip);
|
||||
currentPalette = OceanColors_p;
|
||||
currentBlending = LINEARBLEND;
|
||||
}
|
||||
~LEDService() {}
|
||||
|
||||
void loop() {
|
||||
if (!initialized) return;
|
||||
|
||||
EXECUTE_EVERY_N_MS(1000 / 60, {
|
||||
if (_brightness >= 200) direction = -5;
|
||||
if (_brightness <= 50) direction = 5;
|
||||
_brightness += direction;
|
||||
|
||||
RGB color = WiFi.isConnected() ? oceanColor : forestColor;
|
||||
|
||||
uint8_t r = (color.r * _brightness) / 255;
|
||||
uint8_t g = (color.g * _brightness) / 255;
|
||||
uint8_t b = (color.b * _brightness) / 255;
|
||||
|
||||
for (int i = 0; i < WS2812_NUM_LEDS; ++i) {
|
||||
led_strip_set_pixel(led_strip, i, r, g, b);
|
||||
if (WiFi.isConnected()) {
|
||||
fillFromPallette(OceanColors_p, 0);
|
||||
} else {
|
||||
fillFromPallette(ForestColors_p, 128);
|
||||
}
|
||||
led_strip_refresh(led_strip);
|
||||
FastLED.show();
|
||||
});
|
||||
}
|
||||
|
||||
void setColor(uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (!initialized) return;
|
||||
void fillFromPallette(CRGBPalette16 colorPalette, uint8_t colorIndex) {
|
||||
CRGB color = ColorFromPalette(colorPalette, colorIndex, _brightness, currentBlending);
|
||||
for (int i = 0; i < WS2812_NUM_LEDS; ++i) {
|
||||
led_strip_set_pixel(led_strip, i, r, g, b);
|
||||
leds[i] = color;
|
||||
}
|
||||
led_strip_refresh(led_strip);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if (!initialized) return;
|
||||
led_strip_clear(led_strip);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -12,23 +12,63 @@ struct MagnetometerMsg {
|
||||
|
||||
class Magnetometer : public SensorBase<MagnetometerMsg> {
|
||||
public:
|
||||
bool initialize() override {
|
||||
_msg.success = _mag.begin();
|
||||
if (_msg.success) {
|
||||
ESP_LOGI("MAG", "HMC5883L initialized successfully");
|
||||
} else {
|
||||
ESP_LOGE("MAG", "HMC5883L initialization failed");
|
||||
}
|
||||
bool initialize(void* _arg) override {
|
||||
#if FT_ENABLED(USE_ICM20948)
|
||||
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||
SPI_PORT.begin(SPI_SCK, SPI_MISO, SPI_MOSI, -1);
|
||||
_mag = (ICM_20948_SPI*)_arg;
|
||||
#ifndef ICM20948_ALIVE
|
||||
#define ICM20948_ALIVE
|
||||
_imu->begin(ICM20948_SPI_CS, SPI_PORT); ESP_LOGI("Magnetometer", "Beginning ICM20948 in SPI mode");
|
||||
#endif
|
||||
#else
|
||||
_mag = (ICM_20948_I2C*)_arg;
|
||||
#ifndef ICM20948_ALIVE
|
||||
#define ICM20948_ALIVE
|
||||
if (!_mag->isConnected()) { _mag->begin(Wire, 1, 0xFF); ESP_LOGI("Magnetometer", "Beginning ICM20948 in I2C mode"); }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
if (_mag->status != ICM_20948_Stat_Ok){ return false; }
|
||||
|
||||
_mag->startupMagnetometer();
|
||||
if (_mag->status != ICM_20948_Stat_Ok){ return false; }
|
||||
_msg.success = true;
|
||||
#elif FT_ENABLED(USE_HMC5883)
|
||||
_msg.success = _mag.begin();
|
||||
#endif
|
||||
return _msg.success;
|
||||
}
|
||||
|
||||
bool update() override {
|
||||
if (!_msg.success) return false;
|
||||
if (!_mag.update()) return false;
|
||||
_msg.rpy[0] = _mag.getMagX();
|
||||
_msg.rpy[1] = _mag.getMagY();
|
||||
_msg.rpy[2] = _mag.getMagZ();
|
||||
_msg.heading = _mag.getHeading();
|
||||
#if FT_ENABLED(USE_ICM20948)
|
||||
#ifndef ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
||||
#define ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
||||
if (_imu->dataReady())
|
||||
{
|
||||
_imu->getAGMT();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
_msg.rpy[0] = _mag->magX();
|
||||
_msg.rpy[1] = _mag->magY();
|
||||
_msg.rpy[2] = _mag->magZ();
|
||||
|
||||
#elif FT_ENABLED(USE_HMC5883)
|
||||
sensors_event_t event;
|
||||
bool updated = _mag.getEvent(&event);
|
||||
if (!updated) return false;
|
||||
_msg.rpy[0] = event.magnetic.x;
|
||||
_msg.rpy[1] = event.magnetic.y;
|
||||
_msg.rpy[2] = event.magnetic.z;
|
||||
#endif
|
||||
_msg.heading = atan2(_msg.rpy[1], _msg.rpy[0]); // atan2(y, x)
|
||||
_msg.heading += declinationAngle;
|
||||
if (_msg.heading < 0) _msg.heading += 2 * PI;
|
||||
if (_msg.heading > 2 * PI) _msg.heading -= 2 * PI;
|
||||
_msg.heading *= 180 / M_PI;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,5 +78,18 @@ class Magnetometer : public SensorBase<MagnetometerMsg> {
|
||||
float getHeading() { return _msg.heading; }
|
||||
|
||||
private:
|
||||
HMC5883LDriver _mag;
|
||||
|
||||
#if FT_ENABLED(USE_ICM20948)
|
||||
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||
#define SPI_PORT SPI // TODO in periphearals_seetings.h
|
||||
#define CS_PIN 2
|
||||
ICM_20948_SPI* _mag;
|
||||
#else
|
||||
//#define WIRE_PORT Wire
|
||||
ICM_20948_I2C* _mag;
|
||||
#endif
|
||||
#elif FT_ENABLED(USE_HMC5883)
|
||||
Adafruit_HMC5883_Unified _mag {12345};
|
||||
#endif
|
||||
const float declinationAngle = 0.22;
|
||||
};
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <event_bus/event_bus.h>
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <utils/math_utils.h>
|
||||
#include <utils/timing.h>
|
||||
#include <filesystem.h>
|
||||
#include <features.h>
|
||||
#include <settings/peripherals_settings.h>
|
||||
#include <platform_shared/message.pb.h>
|
||||
#include <esp_http_server.h>
|
||||
|
||||
#include <list>
|
||||
|
||||
#if FT_ENABLED(USE_USS)
|
||||
#include <NewPing.h>
|
||||
#endif
|
||||
#include <peripherals/i2c_bus.h>
|
||||
#include <peripherals/imu.h>
|
||||
#include <peripherals/magnetometer.h>
|
||||
@@ -25,7 +24,7 @@
|
||||
*/
|
||||
#define MAX_DISTANCE 200
|
||||
|
||||
class Peripherals {
|
||||
class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
||||
public:
|
||||
Peripherals();
|
||||
|
||||
@@ -41,6 +40,7 @@ class Peripherals {
|
||||
void getIMUProto(socket_message_IMUData &data);
|
||||
void getSettingsProto(socket_message_PeripheralSettingsData &data);
|
||||
|
||||
/* IMU FUNCTIONS */
|
||||
bool readImu();
|
||||
|
||||
bool readMag();
|
||||
@@ -64,26 +64,20 @@ class Peripherals {
|
||||
|
||||
bool calibrateIMU();
|
||||
|
||||
esp_err_t getSettings(httpd_req_t *request);
|
||||
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
|
||||
StatefulProtoEndpoint<PeripheralsConfiguration, api_PeripheralSettings> protoEndpoint;
|
||||
|
||||
private:
|
||||
static constexpr const char *TAG = "Peripherals";
|
||||
|
||||
void onSettingsChanged(const api_PeripheralSettings &newSettings);
|
||||
|
||||
PeripheralsConfiguration _settings = PeripheralsConfiguration_defaults();
|
||||
EventBus::Handle<api_PeripheralSettings> _settingsHandle;
|
||||
FSPersistencePB<PeripheralsConfiguration> _persistence;
|
||||
|
||||
SemaphoreHandle_t _accessMutex;
|
||||
inline void beginTransaction() { xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); }
|
||||
|
||||
inline void endTransaction() { xSemaphoreGiveRecursive(_accessMutex); }
|
||||
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||
IMU _imu;
|
||||
#endif
|
||||
#if FT_ENABLED(USE_HMC5883)
|
||||
#if FT_ENABLED(USE_HMC5883) || FT_ENABLED(USE_ICM20948)
|
||||
Magnetometer _mag;
|
||||
#endif
|
||||
#if FT_ENABLED(USE_BMP180)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
template <class T>
|
||||
class SensorBase {
|
||||
public:
|
||||
virtual bool initialize() = 0;
|
||||
virtual bool initialize(void* _arg) = 0;
|
||||
virtual bool update() = 0;
|
||||
virtual bool isActive() { return _msg.success; }
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
#define ServoController_h
|
||||
|
||||
#include <peripherals/drivers/pca9685.h>
|
||||
#include <event_bus/event_bus.h>
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <utils/math_utils.h>
|
||||
#include <platform_shared/api.pb.h>
|
||||
#include <platform_shared/message.pb.h>
|
||||
#include <esp_http_server.h>
|
||||
|
||||
#ifndef FACTORY_SERVO_PWM_FREQUENCY
|
||||
#define FACTORY_SERVO_PWM_FREQUENCY 50
|
||||
@@ -24,40 +24,43 @@ inline ServoSettings ServoSettings_defaults() {
|
||||
ServoSettings settings = {};
|
||||
settings.servos_count = 12;
|
||||
const api_Servo defaults[12] = {
|
||||
{306, -1, 0, 2.0f, "Servo1"}, {306, 1, -45, 2.0f, "Servo2"}, {306, 1, 90, 2.0f, "Servo3"},
|
||||
{306, -1, 0, 2.0f, "Servo4"}, {306, -1, 45, 2.0f, "Servo5"}, {306, -1, -90, 2.0f, "Servo6"},
|
||||
{306, 1, 0, 2.0f, "Servo7"}, {306, 1, -45, 2.0f, "Servo8"}, {306, 1, 90, 2.0f, "Servo9"},
|
||||
{306, 1, 0, 2.0f, "Servo10"}, {306, -1, 45, 2.0f, "Servo11"}, {306, -1, -90, 2.0f, "Servo12"}};
|
||||
{306, -1, 0, 2.0f, "Servo1"}, {306, 1, -45, 2.0f, "Servo2"},
|
||||
{306, 1, 90, 2.0f, "Servo3"}, {306, -1, 0, 2.0f, "Servo4"},
|
||||
{306, -1, 45, 2.0f, "Servo5"}, {306, -1, -90, 2.0f, "Servo6"},
|
||||
{306, 1, 0, 2.0f, "Servo7"}, {306, 1, -45, 2.0f, "Servo8"},
|
||||
{306, 1, 90, 2.0f, "Servo9"}, {306, 1, 0, 2.0f, "Servo10"},
|
||||
{306, -1, 45, 2.0f, "Servo11"}, {306, -1, -90, 2.0f, "Servo12"}
|
||||
};
|
||||
for (int i = 0; i < 12; i++) {
|
||||
settings.servos[i] = defaults[i];
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
class ServoController {
|
||||
inline void ServoSettings_read(const ServoSettings &settings, ServoSettings &proto) {
|
||||
proto = settings;
|
||||
}
|
||||
|
||||
inline StateUpdateResult ServoSettings_update(const ServoSettings &proto, ServoSettings &settings) {
|
||||
settings = proto;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
class ServoController : public StatefulService<ServoSettings> {
|
||||
public:
|
||||
ServoController() {}
|
||||
ServoController()
|
||||
: protoEndpoint(ServoSettings_read, ServoSettings_update, this,
|
||||
API_REQUEST_EXTRACTOR(servo_settings, ServoSettings),
|
||||
API_RESPONSE_ASSIGNER(servo_settings, ServoSettings)),
|
||||
_persistence(ServoSettings_read, ServoSettings_update, this,
|
||||
SERVO_SETTINGS_FILE, api_ServoSettings_fields, api_ServoSettings_size,
|
||||
ServoSettings_defaults()) {}
|
||||
|
||||
void begin() {
|
||||
_settingsHandle = EventBus::subscribe<api_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);
|
||||
}
|
||||
_persistence.readFromFS();
|
||||
initializePCA();
|
||||
}
|
||||
|
||||
esp_err_t getSettings(httpd_req_t *request);
|
||||
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
|
||||
|
||||
void pcaWrite(int index, int value) {
|
||||
if (value < 0 || value > 4096) {
|
||||
ESP_LOGE("Peripherals", "Invalid PWM value %d for %d :: Valid range 0-4096", value, index);
|
||||
@@ -106,7 +109,7 @@ class ServoController {
|
||||
uint16_t pwms[12];
|
||||
for (int i = 0; i < 12; i++) {
|
||||
angles[i] = lerp(angles[i], target_angles[i], 0.1);
|
||||
auto &servo = _settings.servos[i];
|
||||
auto &servo = state().servos[i];
|
||||
float angle = servo.direction * angles[i] + servo.center_angle;
|
||||
uint16_t pwm = angle * servo.conversion + servo.center_pwm;
|
||||
pwms[i] = pwm = std::clamp<uint16_t>(pwm, 125, 600);
|
||||
@@ -118,22 +121,16 @@ class ServoController {
|
||||
if (control_state == SERVO_CONTROL_STATE::ANGLE) calculatePWM();
|
||||
}
|
||||
|
||||
StatefulProtoEndpoint<ServoSettings, ServoSettings> protoEndpoint;
|
||||
|
||||
private:
|
||||
static constexpr const char *TAG = "ServoController";
|
||||
|
||||
void onSettingsChanged(const api_ServoSettings &newSettings) { _settings = newSettings; }
|
||||
|
||||
void initializePCA() {
|
||||
_pca.begin();
|
||||
_pca.setOscillatorFrequency(FACTORY_SERVO_OSCILLATOR_FREQUENCY);
|
||||
_pca.setPWMFreq(FACTORY_SERVO_PWM_FREQUENCY);
|
||||
_pca.sleep();
|
||||
}
|
||||
|
||||
api_ServoSettings _settings = ServoSettings_defaults();
|
||||
EventBus::Handle<api_ServoSettings> _settingsHandle;
|
||||
EventBus::Handle<socket_message_ServoPWMData> _pwmHandle;
|
||||
EventBus::Handle<socket_message_ServoStateData> _stateHandle;
|
||||
FSPersistencePB<ServoSettings> _persistence;
|
||||
|
||||
PCA9685Driver _pca;
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <wifi/wifi_idf.h>
|
||||
#include <wifi/dns_server.h>
|
||||
#include <WiFi.h>
|
||||
#include <template/state_result.h>
|
||||
#include <platform_shared/api.pb.h>
|
||||
#include <cstring>
|
||||
|
||||
#include <DNSServer.h>
|
||||
|
||||
#ifndef FACTORY_AP_PROVISION_MODE
|
||||
#define FACTORY_AP_PROVISION_MODE api_APProvisionMode_AP_MODE_DISCONNECTED
|
||||
#endif
|
||||
@@ -74,3 +76,12 @@ inline APSettings APSettings_defaults() {
|
||||
settings.subnet_mask = parseIPv4(FACTORY_AP_SUBNET_MASK);
|
||||
return settings;
|
||||
}
|
||||
|
||||
inline void APSettings_read(const APSettings &settings, APSettings &proto) {
|
||||
proto = settings;
|
||||
}
|
||||
|
||||
inline StateUpdateResult APSettings_update(const APSettings &proto, APSettings &settings) {
|
||||
settings = proto;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <template/state_result.h>
|
||||
#include <platform_shared/api.pb.h>
|
||||
#include <esp_camera.h>
|
||||
|
||||
@@ -44,4 +45,14 @@ inline CameraSettings CameraSettings_defaults() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
// Proto read/update are identity functions since type is the same
|
||||
inline void CameraSettings_read(const CameraSettings& settings, CameraSettings& proto) {
|
||||
proto = settings;
|
||||
}
|
||||
|
||||
inline StateUpdateResult CameraSettings_update(const CameraSettings& proto, CameraSettings& settings) {
|
||||
settings = proto;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
} // namespace Camera
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <template/state_result.h>
|
||||
#include <platform_shared/api.pb.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
@@ -44,3 +45,13 @@ inline MDNSSettings MDNSSettings_defaults() {
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
// Proto read/update are identity functions since type is the same
|
||||
inline void MDNSSettings_read(const MDNSSettings& settings, MDNSSettings& proto) {
|
||||
proto = settings;
|
||||
}
|
||||
|
||||
inline StateUpdateResult MDNSSettings_update(const MDNSSettings& proto, MDNSSettings& settings) {
|
||||
settings = proto;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <Arduino.h>
|
||||
#include <template/state_result.h>
|
||||
#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 StateUpdateResult 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;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
};
|
||||
@@ -1,18 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <template/state_result.h>
|
||||
#include <platform_shared/api.pb.h>
|
||||
|
||||
/*
|
||||
* I2C software connection
|
||||
*/
|
||||
#ifndef SDA_PIN
|
||||
#define SDA_PIN 21
|
||||
#define SDA_PIN SDA
|
||||
#endif
|
||||
#ifndef SCL_PIN
|
||||
#define SCL_PIN 22
|
||||
#define SCL_PIN SCL
|
||||
#endif
|
||||
#ifndef I2C_FREQUENCY
|
||||
#define I2C_FREQUENCY 1000000UL
|
||||
#define I2C_FREQUENCY 400000UL
|
||||
#endif
|
||||
|
||||
// Use proto types directly
|
||||
@@ -28,3 +29,13 @@ inline PeripheralsConfiguration PeripheralsConfiguration_defaults() {
|
||||
settings.pins_count = 0;
|
||||
return settings;
|
||||
}
|
||||
|
||||
// Proto read/update are identity functions since type is the same
|
||||
inline void PeripheralsConfiguration_read(const PeripheralsConfiguration& settings, PeripheralsConfiguration& proto) {
|
||||
proto = settings;
|
||||
}
|
||||
|
||||
inline StateUpdateResult PeripheralsConfiguration_update(const PeripheralsConfiguration& proto, PeripheralsConfiguration& settings) {
|
||||
settings = proto;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <wifi/wifi_idf.h>
|
||||
#include <WiFi.h>
|
||||
#include <template/state_result.h>
|
||||
#include <platform_shared/api.pb.h>
|
||||
#include <cstring>
|
||||
|
||||
@@ -41,10 +42,18 @@ inline WiFiSettings WiFiSettings_defaults() {
|
||||
strncpy(settings.hostname, FACTORY_WIFI_HOSTNAME, sizeof(settings.hostname) - 1);
|
||||
settings.priority_rssi = true;
|
||||
settings.wifi_networks_count = 0;
|
||||
settings.selected_network = 0;
|
||||
if (strlen(FACTORY_WIFI_SSID) > 0) {
|
||||
settings.wifi_networks[0] = WiFiNetwork_defaults();
|
||||
settings.wifi_networks_count = 1;
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
inline void WiFiSettings_read(const WiFiSettings &settings, WiFiSettings &proto) {
|
||||
proto = settings;
|
||||
}
|
||||
|
||||
inline StateUpdateResult WiFiSettings_update(const WiFiSettings &proto, WiFiSettings &settings) {
|
||||
settings = proto;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <mdns.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <esp_http_server.h>
|
||||
#include <wifi/wifi_idf.h>
|
||||
#include <WiFi.h>
|
||||
#include <filesystem.h>
|
||||
#include <global.h>
|
||||
#include <esp_timer.h>
|
||||
@@ -20,6 +20,7 @@ esp_err_t handleSleep(httpd_req_t *request);
|
||||
void reset();
|
||||
void restart();
|
||||
void sleep();
|
||||
void status(JsonObject &root);
|
||||
void getAnalytics(socket_message_AnalyticsData &analytics);
|
||||
void getStaticSystemInformation(socket_message_StaticSystemInformation &info);
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
enum class StateUpdateResult {
|
||||
CHANGED = 0, // The update changed the state and propagation should take place if required
|
||||
UNCHANGED, // The state was unchanged, propagation should not take place
|
||||
ERROR // There was a problem updating the state, propagation should not take place
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <communication/webserver.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#define HTTP_ENDPOINT_ORIGIN_ID "http"
|
||||
#define HTTPS_ENDPOINT_ORIGIN_ID "https"
|
||||
|
||||
template <class T>
|
||||
class StatefulHttpEndpoint {
|
||||
protected:
|
||||
JsonStateReader<T> _stateReader;
|
||||
JsonStateUpdater<T> _stateUpdater;
|
||||
StatefulService<T> *_statefulService;
|
||||
|
||||
public:
|
||||
StatefulHttpEndpoint(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater,
|
||||
StatefulService<T> *statefulService)
|
||||
: _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService) {}
|
||||
|
||||
esp_err_t handleStateUpdate(httpd_req_t *request, JsonVariant &json) {
|
||||
JsonVariant jsonObject = json.as<JsonVariant>();
|
||||
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
|
||||
|
||||
if (outcome == StateUpdateResult::ERROR) {
|
||||
return WebServer::sendError(request, 400, "Invalid state");
|
||||
} else if ((outcome == StateUpdateResult::CHANGED)) {
|
||||
_statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID);
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
JsonVariant root = doc.to<JsonVariant>();
|
||||
_statefulService->read(root, _stateReader);
|
||||
return WebServer::sendJson(request, 200, doc);
|
||||
}
|
||||
|
||||
esp_err_t getState(httpd_req_t *request) {
|
||||
JsonDocument doc;
|
||||
JsonVariant root = doc.to<JsonVariant>();
|
||||
_statefulService->read(root, _stateReader);
|
||||
return WebServer::sendJson(request, 200, doc);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
#ifndef FSPersistence_h
|
||||
#define FSPersistence_h
|
||||
|
||||
/**
|
||||
* ESP32 SvelteKit
|
||||
*
|
||||
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
|
||||
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
|
||||
* https://github.com/theelims/ESP32-sveltekit
|
||||
*
|
||||
* Copyright (C) 2018 - 2023 rjwats
|
||||
* Copyright (C) 2023 theelims
|
||||
* Copyright (C) 2025 runeharlyk
|
||||
*
|
||||
* All Rights Reserved. This software may be modified and distributed under
|
||||
* the terms of the LGPL v3 license. See the LICENSE file for details.
|
||||
**/
|
||||
|
||||
#include <FS.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <filesystem.h>
|
||||
|
||||
template <class T>
|
||||
class FSPersistence {
|
||||
public:
|
||||
FSPersistence(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> *statefulService,
|
||||
const char *filePath)
|
||||
: _stateReader(stateReader),
|
||||
_stateUpdater(stateUpdater),
|
||||
_statefulService(statefulService),
|
||||
_filePath(filePath),
|
||||
_updateHandlerId(0) {
|
||||
enableUpdateHandler();
|
||||
}
|
||||
|
||||
void readFromFS() {
|
||||
File settingsFile = _fs->open(_filePath, "r");
|
||||
|
||||
if (settingsFile) {
|
||||
JsonDocument jsonDocument;
|
||||
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
|
||||
if (error == DeserializationError::Ok) {
|
||||
JsonVariant jsonObject = jsonDocument.as<JsonVariant>();
|
||||
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
|
||||
settingsFile.close();
|
||||
return;
|
||||
}
|
||||
settingsFile.close();
|
||||
}
|
||||
|
||||
// If we reach here we have not been successful in loading the config
|
||||
// and hard-coded defaults are now applied. The settings are then
|
||||
// written back to the file system so the defaults persist between
|
||||
// resets. This last step is required as in some cases defaults contain
|
||||
// randomly generated values which would otherwise be modified on reset.
|
||||
applyDefaults();
|
||||
writeToFS();
|
||||
}
|
||||
|
||||
bool writeToFS() {
|
||||
JsonDocument jsonDocument;
|
||||
JsonVariant jsonObject = jsonDocument.to<JsonVariant>();
|
||||
_statefulService->read(jsonObject, _stateReader);
|
||||
|
||||
mkdirs();
|
||||
|
||||
File file = _fs->open(_filePath, "w");
|
||||
|
||||
if (!file) return false;
|
||||
|
||||
serializeJson(jsonDocument, file);
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void disableUpdateHandler() {
|
||||
if (_updateHandlerId) {
|
||||
_statefulService->removeUpdateHandler(_updateHandlerId);
|
||||
_updateHandlerId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void enableUpdateHandler() {
|
||||
if (!_updateHandlerId) {
|
||||
_updateHandlerId = _statefulService->addUpdateHandler([&](const std::string &originId) { writeToFS(); });
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
JsonStateReader<T> _stateReader;
|
||||
JsonStateUpdater<T> _stateUpdater;
|
||||
StatefulService<T> *_statefulService;
|
||||
FS *_fs {&ESP_FS};
|
||||
const char *_filePath;
|
||||
size_t _bufferSize;
|
||||
HandlerId _updateHandlerId;
|
||||
|
||||
// We assume we have a _filePath with format
|
||||
// "/directory1/directory2/filename" We create a directory for each missing
|
||||
// parent
|
||||
void mkdirs() {
|
||||
std::string path(_filePath);
|
||||
size_t index = 0;
|
||||
while ((index = path.find('/', index + 1)) != std::string::npos) {
|
||||
std::string segment = path.substr(0, index);
|
||||
if (!_fs->exists(segment.c_str())) _fs->mkdir(segment.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// We assume the updater supplies sensible defaults if an empty object
|
||||
// is supplied, this virtual function allows that to be changed.
|
||||
virtual void applyDefaults() {
|
||||
JsonDocument jsonDocument;
|
||||
JsonVariant jsonObject = jsonDocument.as<JsonVariant>();
|
||||
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // end FSPersistence
|
||||
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#include <FS.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <template/state_result.h>
|
||||
#include <filesystem.h>
|
||||
#include <pb_encode.h>
|
||||
#include <pb_decode.h>
|
||||
|
||||
/**
|
||||
* Protobuf-based filesystem persistence for StatefulService.
|
||||
*
|
||||
* @tparam T The state type (should be a nanopb-generated struct like api_APSettings)
|
||||
*/
|
||||
template <class T>
|
||||
class FSPersistencePB {
|
||||
public:
|
||||
// Formats are passed as referenced const (local variable) we want to read from, and a reference (proto) we write to
|
||||
using ProtoStateReader = std::function<void(const T&, T&)>;
|
||||
// Formats are passed as referenced const (new object) we read from, and a reference to the local variable we write to
|
||||
using ProtoStateUpdater = std::function<StateUpdateResult(const T&, T&)>;
|
||||
|
||||
FSPersistencePB(ProtoStateReader stateReader, ProtoStateUpdater stateUpdater,
|
||||
StatefulService<T> *statefulService, const char *filePath,
|
||||
const pb_msgdesc_t *msgDescriptor, size_t maxSize,
|
||||
const T &defaultState)
|
||||
: _stateReader(stateReader),
|
||||
_stateUpdater(stateUpdater),
|
||||
_statefulService(statefulService),
|
||||
_filePath(filePath),
|
||||
_msgDescriptor(msgDescriptor),
|
||||
_maxSize(maxSize),
|
||||
_defaultState(defaultState),
|
||||
_updateHandlerId(0) {
|
||||
enableUpdateHandler();
|
||||
}
|
||||
|
||||
void readFromFS() {
|
||||
File file = _fs->open(_filePath, "r");
|
||||
|
||||
if (file) {
|
||||
size_t fileSize = file.size();
|
||||
if (fileSize > 0 && fileSize <= _maxSize) {
|
||||
uint8_t *buffer = new uint8_t[fileSize];
|
||||
size_t bytesRead = file.read(buffer, fileSize);
|
||||
file.close();
|
||||
|
||||
if (bytesRead == fileSize) {
|
||||
// Allocate on heap to avoid stack overflow with large proto messages
|
||||
T *protoMsg = new T();
|
||||
*protoMsg = {};
|
||||
pb_istream_t stream = pb_istream_from_buffer(buffer, bytesRead);
|
||||
|
||||
if (pb_decode(&stream, _msgDescriptor, protoMsg)) {
|
||||
_statefulService->updateWithoutPropagation(
|
||||
[this, protoMsg](T &state) { return _stateUpdater(*protoMsg, state); });
|
||||
delete protoMsg;
|
||||
delete[] buffer;
|
||||
return;
|
||||
}
|
||||
delete protoMsg;
|
||||
}
|
||||
delete[] buffer;
|
||||
} else {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
applyDefaults();
|
||||
writeToFS();
|
||||
}
|
||||
|
||||
bool writeToFS() {
|
||||
uint8_t *buffer = new uint8_t[_maxSize];
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(buffer, _maxSize);
|
||||
|
||||
// Allocate on heap to avoid stack overflow with large proto messages
|
||||
T *protoMsg = new T();
|
||||
*protoMsg = {};
|
||||
_statefulService->read([this, protoMsg](const T &state) { _stateReader(state, *protoMsg); });
|
||||
|
||||
bool encodeSuccess = pb_encode(&stream, _msgDescriptor, protoMsg);
|
||||
delete protoMsg;
|
||||
|
||||
if (!encodeSuccess) {
|
||||
delete[] buffer;
|
||||
return false;
|
||||
}
|
||||
|
||||
mkdirs();
|
||||
|
||||
File file = _fs->open(_filePath, "w");
|
||||
if (!file) {
|
||||
delete[] buffer;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t written = file.write(buffer, stream.bytes_written);
|
||||
file.close();
|
||||
delete[] buffer;
|
||||
|
||||
return written == stream.bytes_written;
|
||||
}
|
||||
|
||||
void disableUpdateHandler() {
|
||||
if (_updateHandlerId) {
|
||||
_statefulService->removeUpdateHandler(_updateHandlerId);
|
||||
_updateHandlerId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void enableUpdateHandler() {
|
||||
if (!_updateHandlerId) {
|
||||
_updateHandlerId = _statefulService->addUpdateHandler(
|
||||
[&](const std::string &originId) { writeToFS(); });
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ProtoStateReader _stateReader;
|
||||
ProtoStateUpdater _stateUpdater;
|
||||
StatefulService<T> *_statefulService;
|
||||
FS *_fs{&ESP_FS};
|
||||
const char *_filePath;
|
||||
const pb_msgdesc_t *_msgDescriptor;
|
||||
size_t _maxSize;
|
||||
T _defaultState;
|
||||
HandlerId _updateHandlerId;
|
||||
|
||||
void mkdirs() {
|
||||
std::string path(_filePath);
|
||||
size_t index = 0;
|
||||
while ((index = path.find('/', index + 1)) != std::string::npos) {
|
||||
std::string segment = path.substr(0, index);
|
||||
if (!_fs->exists(segment.c_str())) _fs->mkdir(segment.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void applyDefaults() {
|
||||
_statefulService->updateWithoutPropagation(
|
||||
[this](T &state) { return _stateUpdater(_defaultState, state); });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <communication/webserver.h>
|
||||
#include <platform_shared/api.pb.h>
|
||||
#include <pb_encode.h>
|
||||
#include <pb_decode.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#define PROTO_ENDPOINT_ORIGIN_ID "proto"
|
||||
|
||||
/**
|
||||
* A stateful HTTP endpoint that uses protobuf encoding with api::Request/Response wrappers.
|
||||
*
|
||||
* @tparam T The internal state type (e.g., APSettings C++ class)
|
||||
* @tparam ProtoT The protobuf message type within the oneof (e.g., api_APSettings)
|
||||
*
|
||||
* The endpoint receives api::Request, extracts the specific payload from the oneof,
|
||||
* and returns api::Response with the updated state.
|
||||
*/
|
||||
template <class T, class ProtoT>
|
||||
class StatefulProtoEndpoint {
|
||||
public:
|
||||
/** Converts internal state to protobuf message for responses */
|
||||
// Formats are passed as referenced const (local variable) we want to read from, and a reference (proto) we write to
|
||||
using ProtoStateReader = std::function<void(const T&, ProtoT&)>;
|
||||
/** Converts incoming protobuf message to internal state */
|
||||
// Formats are passed as referenced const (new object) we read from, and a reference to the local variable we write to
|
||||
using ProtoStateUpdater = std::function<StateUpdateResult(const ProtoT&, T&)>;
|
||||
/** Extracts the specific proto type from Request oneof */
|
||||
using RequestExtractor = std::function<bool(const api_Request&, ProtoT&)>;
|
||||
/** Assigns the specific proto type to Response oneof */
|
||||
using ResponseAssigner = std::function<void(api_Response&, const ProtoT&)>;
|
||||
|
||||
protected:
|
||||
ProtoStateReader _stateReader;
|
||||
ProtoStateUpdater _stateUpdater;
|
||||
StatefulService<T>* _statefulService;
|
||||
RequestExtractor _requestExtractor;
|
||||
ResponseAssigner _responseAssigner;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for wrapped proto endpoint
|
||||
* @param stateReader Converts internal state to proto
|
||||
* @param stateUpdater Converts proto to internal state
|
||||
* @param statefulService The stateful service to manage
|
||||
* @param requestExtractor Extracts specific type from Request oneof
|
||||
* @param responseAssigner Assigns specific type to Response oneof
|
||||
*/
|
||||
StatefulProtoEndpoint(ProtoStateReader stateReader, ProtoStateUpdater stateUpdater,
|
||||
StatefulService<T>* statefulService,
|
||||
RequestExtractor requestExtractor, ResponseAssigner responseAssigner)
|
||||
: _stateReader(stateReader),
|
||||
_stateUpdater(stateUpdater),
|
||||
_statefulService(statefulService),
|
||||
_requestExtractor(requestExtractor),
|
||||
_responseAssigner(responseAssigner) {}
|
||||
|
||||
/**
|
||||
* Handles POST requests: extracts payload from pre-decoded Request, updates state, returns Response
|
||||
*/
|
||||
esp_err_t handleStateUpdate(httpd_req_t* httpReq, api_Request* protoReq) {
|
||||
ProtoT protoMsg = {};
|
||||
if (!_requestExtractor(*protoReq, protoMsg)) {
|
||||
return sendErrorResponse(httpReq, 400, "Invalid request type");
|
||||
}
|
||||
|
||||
StateUpdateResult outcome = _statefulService->update(
|
||||
[this, &protoMsg](T& settings) { return _stateUpdater(protoMsg, settings); }, PROTO_ENDPOINT_ORIGIN_ID);
|
||||
|
||||
if (outcome == StateUpdateResult::ERROR) {
|
||||
return sendErrorResponse(httpReq, 400, "Invalid state");
|
||||
}
|
||||
|
||||
return sendStateResponse(httpReq, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles GET requests: reads current state and returns it as Response
|
||||
*/
|
||||
esp_err_t getState(httpd_req_t* request) {
|
||||
return sendStateResponse(request, 200);
|
||||
}
|
||||
|
||||
private:
|
||||
/** Sends current state wrapped in Response */
|
||||
esp_err_t sendStateResponse(httpd_req_t* request, uint32_t statusCode) {
|
||||
api_Response res = api_Response_init_zero;
|
||||
res.status_code = statusCode;
|
||||
|
||||
ProtoT protoState = {};
|
||||
_statefulService->read([this, &protoState](const T& settings) { _stateReader(settings, protoState); });
|
||||
_responseAssigner(res, protoState);
|
||||
|
||||
return WebServer::send(request, 200, res, api_Response_fields);
|
||||
}
|
||||
|
||||
/** Sends error wrapped in Response */
|
||||
esp_err_t sendErrorResponse(httpd_req_t* request, uint32_t statusCode, const char* message) {
|
||||
api_Response res = api_Response_init_zero;
|
||||
res.status_code = statusCode;
|
||||
res.error_message = (char*)message;
|
||||
return WebServer::send(request, statusCode == 200 ? 200 : 400, res, api_Response_fields);
|
||||
}
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Helper macros for defining request extractors and response assigners
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Creates a request extractor lambda for a specific payload type
|
||||
* Usage: API_REQUEST_EXTRACTOR(ap_settings, api_APSettings)
|
||||
*/
|
||||
#define API_REQUEST_EXTRACTOR(field_name, proto_type) \
|
||||
[](const api_Request& req, proto_type& out) -> bool { \
|
||||
if (req.which_payload == api_Request_##field_name##_tag) { \
|
||||
out = req.payload.field_name; \
|
||||
return true; \
|
||||
} \
|
||||
return false; \
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a response assigner lambda for a specific payload type
|
||||
* Usage: API_RESPONSE_ASSIGNER(ap_settings, api_APSettings)
|
||||
*/
|
||||
#define API_RESPONSE_ASSIGNER(field_name, proto_type) \
|
||||
[](api_Response& res, const proto_type& data) { \
|
||||
res.which_payload = api_Response_##field_name##_tag; \
|
||||
res.payload.field_name = data; \
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <list>
|
||||
#include <functional>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <string>
|
||||
|
||||
#include <template/state_result.h>
|
||||
|
||||
template <typename T>
|
||||
using JsonStateUpdater = std::function<StateUpdateResult(JsonVariant &root, T &settings)>;
|
||||
|
||||
template <typename T>
|
||||
using JsonStateReader = std::function<void(T &settings, JsonVariant &root)>;
|
||||
|
||||
using HandlerId = size_t;
|
||||
using StateUpdateCallback = std::function<void(const std::string &originId)>;
|
||||
using StateHookCallback = std::function<void(const std::string &originId, StateUpdateResult &result)>;
|
||||
|
||||
class HandlerBase {
|
||||
protected:
|
||||
static inline HandlerId nextId_ = 1; // Start from 1, 0 is invalid
|
||||
HandlerId id_;
|
||||
bool allowRemove_;
|
||||
|
||||
HandlerBase(bool allowRemove) : id_(nextId_++), allowRemove_(allowRemove) {}
|
||||
|
||||
public:
|
||||
HandlerId getId() const { return id_; }
|
||||
bool isRemovable() const { return allowRemove_; }
|
||||
};
|
||||
|
||||
class UpdateHandler : public HandlerBase {
|
||||
StateUpdateCallback callback_;
|
||||
|
||||
public:
|
||||
UpdateHandler(StateUpdateCallback callback, bool allowRemove)
|
||||
: HandlerBase(allowRemove), callback_(std::move(callback)) {}
|
||||
|
||||
void invoke(const std::string &originId) const { callback_(originId); }
|
||||
};
|
||||
|
||||
class HookHandler : public HandlerBase {
|
||||
StateHookCallback callback_;
|
||||
|
||||
public:
|
||||
HookHandler(StateHookCallback callback, bool allowRemove)
|
||||
: HandlerBase(allowRemove), callback_(std::move(callback)) {}
|
||||
|
||||
void invoke(const std::string &originId, StateUpdateResult &result) const { callback_(originId, result); }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class StatefulService {
|
||||
public:
|
||||
template <typename... Args>
|
||||
StatefulService(Args &&...args) : state_(std::forward<Args>(args)...), mutex_(xSemaphoreCreateRecursiveMutex()) {}
|
||||
|
||||
HandlerId addUpdateHandler(StateUpdateCallback callback, bool allowRemove = true) {
|
||||
if (!callback) return 0;
|
||||
|
||||
updateHandlers_.emplace_back(std::move(callback), allowRemove);
|
||||
return updateHandlers_.back().getId();
|
||||
}
|
||||
|
||||
void removeUpdateHandler(HandlerId id) {
|
||||
updateHandlers_.remove_if(
|
||||
[id](const UpdateHandler &handler) { return handler.isRemovable() && handler.getId() == id; });
|
||||
}
|
||||
|
||||
HandlerId addHookHandler(StateHookCallback callback, bool allowRemove = true) {
|
||||
if (!callback) return 0;
|
||||
|
||||
hookHandlers_.emplace_back(std::move(callback), allowRemove);
|
||||
return hookHandlers_.back().getId();
|
||||
}
|
||||
|
||||
void removeHookHandler(HandlerId id) {
|
||||
hookHandlers_.remove_if(
|
||||
[id](const HookHandler &handler) { return handler.isRemovable() && handler.getId() == id; });
|
||||
}
|
||||
|
||||
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater, const std::string &originId) {
|
||||
lock();
|
||||
StateUpdateResult result = stateUpdater(state_);
|
||||
unlock();
|
||||
notifyStateChange(originId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T &)> stateUpdater) {
|
||||
lock();
|
||||
StateUpdateResult result = stateUpdater(state_);
|
||||
unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
StateUpdateResult update(JsonVariant &jsonObject, JsonStateUpdater<T> stateUpdater, const std::string &originId) {
|
||||
lock();
|
||||
StateUpdateResult result = stateUpdater(jsonObject, state_);
|
||||
unlock();
|
||||
notifyStateChange(originId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
StateUpdateResult updateWithoutPropagation(JsonVariant &jsonObject, JsonStateUpdater<T> stateUpdater) {
|
||||
lock();
|
||||
StateUpdateResult result = stateUpdater(jsonObject, state_);
|
||||
unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void read(std::function<void(T &)> stateReader) {
|
||||
lock();
|
||||
stateReader(state_);
|
||||
unlock();
|
||||
}
|
||||
|
||||
void read(JsonVariant &jsonObject, JsonStateReader<T> stateReader) {
|
||||
lock();
|
||||
stateReader(state_, jsonObject);
|
||||
unlock();
|
||||
}
|
||||
|
||||
void callUpdateHandlers(const std::string &originId) {
|
||||
for (const UpdateHandler &updateHandler : updateHandlers_) {
|
||||
updateHandler.invoke(originId);
|
||||
}
|
||||
}
|
||||
|
||||
void callHookHandlers(const std::string &originId, StateUpdateResult &result) {
|
||||
for (const HookHandler &hookHandler : hookHandlers_) {
|
||||
hookHandler.invoke(originId, result);
|
||||
}
|
||||
}
|
||||
|
||||
T &state() { return state_; }
|
||||
|
||||
private:
|
||||
T state_;
|
||||
|
||||
inline void lock() { xSemaphoreTakeRecursive(mutex_, portMAX_DELAY); }
|
||||
inline void unlock() { xSemaphoreGiveRecursive(mutex_); }
|
||||
|
||||
void notifyStateChange(const std::string &originId, StateUpdateResult &result) {
|
||||
callHookHandlers(originId, result);
|
||||
if (result == StateUpdateResult::CHANGED) {
|
||||
callUpdateHandlers(originId);
|
||||
}
|
||||
}
|
||||
|
||||
SemaphoreHandle_t mutex_;
|
||||
std::list<UpdateHandler> updateHandlers_;
|
||||
std::list<HookHandler> hookHandlers_;
|
||||
};
|
||||
@@ -1,76 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <esp_netif.h>
|
||||
|
||||
class IPAddress {
|
||||
public:
|
||||
IPAddress() : _addr {0, 0, 0, 0} {}
|
||||
|
||||
IPAddress(uint8_t a, uint8_t b, uint8_t c, uint8_t d) : _addr {a, b, c, d} {}
|
||||
|
||||
IPAddress(uint32_t addr) {
|
||||
_addr[0] = addr & 0xFF;
|
||||
_addr[1] = (addr >> 8) & 0xFF;
|
||||
_addr[2] = (addr >> 16) & 0xFF;
|
||||
_addr[3] = (addr >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
IPAddress(const esp_ip4_addr_t& ip4) {
|
||||
_addr[0] = ip4.addr & 0xFF;
|
||||
_addr[1] = (ip4.addr >> 8) & 0xFF;
|
||||
_addr[2] = (ip4.addr >> 16) & 0xFF;
|
||||
_addr[3] = (ip4.addr >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
operator uint32_t() const {
|
||||
return static_cast<uint32_t>(_addr[0]) | (static_cast<uint32_t>(_addr[1]) << 8) |
|
||||
(static_cast<uint32_t>(_addr[2]) << 16) | (static_cast<uint32_t>(_addr[3]) << 24);
|
||||
}
|
||||
|
||||
operator esp_ip4_addr_t() const {
|
||||
esp_ip4_addr_t ip4;
|
||||
ip4.addr = static_cast<uint32_t>(*this);
|
||||
return ip4;
|
||||
}
|
||||
|
||||
bool operator==(const IPAddress& other) const {
|
||||
return _addr[0] == other._addr[0] && _addr[1] == other._addr[1] && _addr[2] == other._addr[2] &&
|
||||
_addr[3] == other._addr[3];
|
||||
}
|
||||
|
||||
bool operator!=(const IPAddress& other) const { return !(*this == other); }
|
||||
|
||||
uint8_t operator[](int index) const { return _addr[index]; }
|
||||
|
||||
uint8_t& operator[](int index) { return _addr[index]; }
|
||||
|
||||
bool fromString(const char* str) {
|
||||
int parts[4];
|
||||
if (sscanf(str, "%d.%d.%d.%d", &parts[0], &parts[1], &parts[2], &parts[3]) != 4) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (parts[i] < 0 || parts[i] > 255) return false;
|
||||
_addr[i] = static_cast<uint8_t>(parts[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string toString() const {
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%d.%d.%d.%d", _addr[0], _addr[1], _addr[2], _addr[3]);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
const char* c_str() const {
|
||||
static char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%d.%d.%d.%d", _addr[0], _addr[1], _addr[2], _addr[3]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t _addr[4];
|
||||
};
|
||||
@@ -40,6 +40,51 @@
|
||||
last_time = esp_timer_get_time() / 1000; \
|
||||
}
|
||||
|
||||
#define CALLS_PER_SECOND_TIMED_START_TICK(name, function) \
|
||||
static uint64_t name##_##function##_start = 0; \
|
||||
static uint64_t name##_##function##_total_time = 0; \
|
||||
static uint64_t name##_##function##_call_count = 0; \
|
||||
name##_##function##_start = esp_timer_get_time();
|
||||
|
||||
#define CALLS_PER_SECOND_TIMED_END_TICK(name, function) \
|
||||
name##_##function##_total_time += (esp_timer_get_time() - name##_##function##_start); \
|
||||
name##_##function##_call_count++;
|
||||
|
||||
#define CALLS_PER_SECOND_TIMED_CALL(name, function, call) \
|
||||
static uint64_t name##_##function##_total_time = 0; \
|
||||
static uint64_t name##_##function##_call_count = 0; \
|
||||
do { \
|
||||
uint64_t name##_##function##_start = esp_timer_get_time(); \
|
||||
call; \
|
||||
name##_##function##_total_time += (esp_timer_get_time() - name##_##function##_start); \
|
||||
name##_##function##_call_count++; \
|
||||
} while (0)
|
||||
|
||||
#define CALLS_PER_SECOND_TIMED_FUNC_PRINT(name, function) \
|
||||
if (name##_##function##_call_count > 0) { \
|
||||
uint64_t avg = name##_##function##_total_time / name##_##function##_call_count; \
|
||||
if (avg < 1000) { \
|
||||
ESP_LOGI("Timing", " %s: %llu us (avg over %llu calls)", \
|
||||
#function, avg, name##_##function##_call_count); \
|
||||
} else { \
|
||||
ESP_LOGI("Timing", " %s: %llu ms (avg over %llu calls)", \
|
||||
#function, avg / 1000, name##_##function##_call_count); \
|
||||
} \
|
||||
name##_##function##_total_time = 0; \
|
||||
name##_##function##_call_count = 0; \
|
||||
}
|
||||
|
||||
#define CALLS_PER_SECOND_TIMED(name, ...) \
|
||||
do { \
|
||||
static uint64_t name##_last_print = 0; \
|
||||
uint64_t name##_current_time = esp_timer_get_time() / 1000; \
|
||||
if (name##_current_time - name##_last_print >= 1000) { \
|
||||
ESP_LOGI("Timing", "=== %s Average Timings ===", #name); \
|
||||
__VA_ARGS__ \
|
||||
name##_last_print = name##_current_time; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define WARN_IF_SLOW(name, period_ms) \
|
||||
static uint64_t name##_slow_count = 0; \
|
||||
static uint64_t name##_slow_last_time = 0; \
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lwip/sockets.h>
|
||||
#include <lwip/netdb.h>
|
||||
#include <esp_log.h>
|
||||
#include <utils/ip_address.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
|
||||
#define DNS_PORT 53
|
||||
#define DNS_MAX_PACKET_SIZE 512
|
||||
|
||||
class DNSServer {
|
||||
public:
|
||||
DNSServer() : _socket(-1), _running(false), _task(nullptr) {}
|
||||
~DNSServer() { stop(); }
|
||||
|
||||
bool start(uint16_t port, const char* domainName, const IPAddress& resolvedIP) {
|
||||
if (_running) return true;
|
||||
|
||||
_port = port;
|
||||
_resolvedIP = resolvedIP;
|
||||
_domainName = domainName ? domainName : "*";
|
||||
|
||||
_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (_socket < 0) {
|
||||
ESP_LOGE("DNSServer", "Failed to create socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
int opt = 1;
|
||||
setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
struct sockaddr_in serverAddr = {};
|
||||
serverAddr.sin_family = AF_INET;
|
||||
serverAddr.sin_addr.s_addr = INADDR_ANY;
|
||||
serverAddr.sin_port = htons(_port);
|
||||
|
||||
if (bind(_socket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
|
||||
ESP_LOGE("DNSServer", "Failed to bind socket");
|
||||
close(_socket);
|
||||
_socket = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
_running = true;
|
||||
xTaskCreate(dnsTask, "dns_server", 4096, this, 3, &_task);
|
||||
|
||||
ESP_LOGI("DNSServer", "Started on port %d, resolving to %s", _port, _resolvedIP.toString().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
_running = false;
|
||||
if (_task) {
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
_task = nullptr;
|
||||
}
|
||||
if (_socket >= 0) {
|
||||
close(_socket);
|
||||
_socket = -1;
|
||||
}
|
||||
ESP_LOGI("DNSServer", "Stopped");
|
||||
}
|
||||
|
||||
void processNextRequest() {}
|
||||
|
||||
private:
|
||||
static void dnsTask(void* param) {
|
||||
DNSServer* self = static_cast<DNSServer*>(param);
|
||||
self->run();
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
void run() {
|
||||
uint8_t buffer[DNS_MAX_PACKET_SIZE];
|
||||
struct sockaddr_in clientAddr;
|
||||
socklen_t clientAddrLen = sizeof(clientAddr);
|
||||
|
||||
while (_running) {
|
||||
int len = recvfrom(_socket, buffer, DNS_MAX_PACKET_SIZE, 0, (struct sockaddr*)&clientAddr, &clientAddrLen);
|
||||
if (len > 0) {
|
||||
processRequest(buffer, len, &clientAddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processRequest(uint8_t* buffer, int len, struct sockaddr_in* clientAddr) {
|
||||
if (len < 12) return;
|
||||
|
||||
uint16_t flags = (buffer[2] << 8) | buffer[3];
|
||||
if ((flags & 0x8000) != 0) return;
|
||||
|
||||
uint8_t response[DNS_MAX_PACKET_SIZE];
|
||||
memcpy(response, buffer, len);
|
||||
|
||||
response[2] = 0x81;
|
||||
response[3] = 0x80;
|
||||
|
||||
response[6] = 0x00;
|
||||
response[7] = 0x01;
|
||||
|
||||
int responseLen = len;
|
||||
|
||||
response[responseLen++] = 0xC0;
|
||||
response[responseLen++] = 0x0C;
|
||||
|
||||
response[responseLen++] = 0x00;
|
||||
response[responseLen++] = 0x01;
|
||||
|
||||
response[responseLen++] = 0x00;
|
||||
response[responseLen++] = 0x01;
|
||||
|
||||
response[responseLen++] = 0x00;
|
||||
response[responseLen++] = 0x00;
|
||||
response[responseLen++] = 0x00;
|
||||
response[responseLen++] = 0x3C;
|
||||
|
||||
response[responseLen++] = 0x00;
|
||||
response[responseLen++] = 0x04;
|
||||
|
||||
uint32_t ip = static_cast<uint32_t>(_resolvedIP);
|
||||
response[responseLen++] = ip & 0xFF;
|
||||
response[responseLen++] = (ip >> 8) & 0xFF;
|
||||
response[responseLen++] = (ip >> 16) & 0xFF;
|
||||
response[responseLen++] = (ip >> 24) & 0xFF;
|
||||
|
||||
sendto(_socket, response, responseLen, 0, (struct sockaddr*)clientAddr, sizeof(*clientAddr));
|
||||
}
|
||||
|
||||
int _socket;
|
||||
uint16_t _port;
|
||||
IPAddress _resolvedIP;
|
||||
std::string _domainName;
|
||||
std::atomic<bool> _running;
|
||||
TaskHandle_t _task;
|
||||
};
|
||||
@@ -1,127 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_event.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_log.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <utils/ip_address.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
typedef enum {
|
||||
WL_NO_SHIELD = 255,
|
||||
WL_IDLE_STATUS = 0,
|
||||
WL_NO_SSID_AVAIL = 1,
|
||||
WL_SCAN_COMPLETED = 2,
|
||||
WL_CONNECTED = 3,
|
||||
WL_CONNECT_FAILED = 4,
|
||||
WL_CONNECTION_LOST = 5,
|
||||
WL_DISCONNECTED = 6
|
||||
} wl_status_t;
|
||||
|
||||
typedef enum {
|
||||
WIFI_AUTH_OPEN_IDF = 0,
|
||||
WIFI_AUTH_WEP_IDF,
|
||||
WIFI_AUTH_WPA_PSK_IDF,
|
||||
WIFI_AUTH_WPA2_PSK_IDF
|
||||
} wifi_enc_type_t;
|
||||
|
||||
using WiFiEventCb = std::function<void(int32_t event, void* event_data)>;
|
||||
|
||||
struct WiFiEventHandler {
|
||||
int32_t event_id;
|
||||
WiFiEventCb callback;
|
||||
};
|
||||
|
||||
class WiFiClass {
|
||||
public:
|
||||
WiFiClass();
|
||||
~WiFiClass();
|
||||
|
||||
bool init();
|
||||
|
||||
bool mode(wifi_mode_t mode);
|
||||
wifi_mode_t getMode();
|
||||
|
||||
bool begin(const char* ssid, const char* password = nullptr, int32_t channel = 0, const uint8_t* bssid = nullptr);
|
||||
bool disconnect(bool wifiOff = false);
|
||||
bool reconnect();
|
||||
|
||||
bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = IPAddress(),
|
||||
IPAddress dns2 = IPAddress());
|
||||
bool setHostname(const char* hostname);
|
||||
const char* getHostname();
|
||||
|
||||
wl_status_t status();
|
||||
bool isConnected();
|
||||
|
||||
IPAddress localIP();
|
||||
IPAddress subnetMask();
|
||||
IPAddress gatewayIP();
|
||||
IPAddress dnsIP(uint8_t dns_no = 0);
|
||||
|
||||
std::string macAddress();
|
||||
std::string SSID();
|
||||
std::string BSSIDstr();
|
||||
int32_t RSSI();
|
||||
uint8_t channel();
|
||||
|
||||
int16_t scanNetworks(bool async = false);
|
||||
int16_t scanComplete();
|
||||
void scanDelete();
|
||||
|
||||
std::string SSID(uint8_t i);
|
||||
int32_t RSSI(uint8_t i);
|
||||
wifi_enc_type_t encryptionType(uint8_t i);
|
||||
std::string BSSIDstr(uint8_t i);
|
||||
int32_t channel(uint8_t i);
|
||||
void getNetworkInfo(uint8_t i, std::string& ssid, uint8_t& encType, int32_t& rssi, uint8_t*& bssid, int32_t& ch);
|
||||
|
||||
bool softAP(const char* ssid, const char* password = nullptr, int channel = 1, bool ssid_hidden = false,
|
||||
int max_connection = 4);
|
||||
bool softAPConfig(IPAddress local_ip, IPAddress gateway, IPAddress subnet);
|
||||
bool softAPdisconnect(bool wifiOff = false);
|
||||
IPAddress softAPIP();
|
||||
std::string softAPmacAddress();
|
||||
uint8_t softAPgetStationNum();
|
||||
|
||||
bool setAutoReconnect(bool autoReconnect);
|
||||
bool persistent(bool persistent);
|
||||
bool setTxPower(int8_t power);
|
||||
|
||||
void onEvent(WiFiEventCb callback, int32_t event_id);
|
||||
|
||||
private:
|
||||
static void eventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||
void dispatchEvent(int32_t event_id, void* event_data);
|
||||
|
||||
esp_netif_t* _sta_netif;
|
||||
esp_netif_t* _ap_netif;
|
||||
bool _initialized;
|
||||
bool _autoReconnect;
|
||||
bool _persistent;
|
||||
wl_status_t _status;
|
||||
wifi_mode_t _mode;
|
||||
|
||||
std::string _hostname;
|
||||
wifi_ap_record_t* _scanResult;
|
||||
uint16_t _scanCount;
|
||||
int16_t _scanStatus;
|
||||
|
||||
std::vector<WiFiEventHandler> _eventHandlers;
|
||||
|
||||
IPAddress _sta_static_ip;
|
||||
IPAddress _sta_static_gw;
|
||||
IPAddress _sta_static_sn;
|
||||
IPAddress _sta_static_dns1;
|
||||
IPAddress _sta_static_dns2;
|
||||
bool _useStaticIp;
|
||||
};
|
||||
|
||||
extern WiFiClass WiFi;
|
||||
@@ -1,19 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <wifi/wifi_idf.h>
|
||||
#include <mdns.h>
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <string>
|
||||
|
||||
#include <event_bus/event_bus.h>
|
||||
#include <settings/wifi_settings.h>
|
||||
#include <filesystem.h>
|
||||
#include <utils/timing.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <settings/wifi_settings.h>
|
||||
|
||||
#define WIFI_EVENT_STA_DISCONNECTED_IDF WIFI_EVENT_STA_DISCONNECTED
|
||||
#define WIFI_EVENT_STA_STOP_IDF WIFI_EVENT_STA_STOP
|
||||
#define IP_EVENT_STA_GOT_IP_IDF 1000
|
||||
class WiFiService : public StatefulService<WiFiSettings> {
|
||||
private:
|
||||
void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
void onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
static void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
|
||||
FSPersistencePB<WiFiSettings> _persistence;
|
||||
|
||||
void reconfigureWiFiConnection();
|
||||
void manageSTA();
|
||||
void connectToWiFi();
|
||||
void configureNetwork(WiFiNetwork &network);
|
||||
|
||||
unsigned long _lastConnectionAttempt;
|
||||
bool _stopping;
|
||||
|
||||
constexpr static uint16_t reconnectDelay {10000};
|
||||
|
||||
class WiFiService {
|
||||
public:
|
||||
WiFiService();
|
||||
~WiFiService();
|
||||
@@ -22,41 +38,12 @@ class WiFiService {
|
||||
void loop();
|
||||
|
||||
void setupMDNS(const char *hostname);
|
||||
void selectNetwork(uint32_t index);
|
||||
|
||||
const char *getHostname() {
|
||||
static api_WifiSettings cached_settings;
|
||||
EventBus::peek(cached_settings);
|
||||
return cached_settings.hostname;
|
||||
}
|
||||
const char *getHostname() { return state().hostname; }
|
||||
|
||||
static esp_err_t handleScan(httpd_req_t *request);
|
||||
static esp_err_t getNetworks(httpd_req_t *request);
|
||||
static esp_err_t getNetworkStatus(httpd_req_t *request);
|
||||
|
||||
private:
|
||||
void onStationModeDisconnected(int32_t event, void *event_data);
|
||||
void onStationModeStop(int32_t event, void *event_data);
|
||||
static void onStationModeGotIP(int32_t event, void *event_data);
|
||||
|
||||
void onSettingsChanged(const api_WifiSettings &newSettings);
|
||||
|
||||
void reconfigureWiFiConnection();
|
||||
void manageSTA();
|
||||
void configureNetwork(const WiFiNetwork &network);
|
||||
|
||||
api_WifiSettings getSettings() const {
|
||||
api_WifiSettings settings;
|
||||
EventBus::peek(settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
EventBus::Handle<api_WifiSettings> _settingsHandle;
|
||||
|
||||
bool _initialized;
|
||||
unsigned long _lastConnectionAttempt;
|
||||
bool _stopping;
|
||||
|
||||
constexpr static uint16_t reconnectDelay {10000};
|
||||
static constexpr const char *TAG = "WiFiService";
|
||||
StatefulProtoEndpoint<WiFiSettings, api_WifiSettings> protoEndpoint;
|
||||
};
|
||||
|
||||
@@ -3,4 +3,3 @@
|
||||
#include "WWWData.h"
|
||||
|
||||
void mountStaticAssets(WebServer& s);
|
||||
void mountSpaFallback(WebServer& s);
|
||||
|
||||
@@ -114,7 +114,7 @@ def write_header():
|
||||
|
||||
with open(output_file, "w", newline="\n") as f:
|
||||
f.write("#pragma once\n")
|
||||
f.write("#include <compat/pgmspace.h>\n\n")
|
||||
f.write("#include <Arduino.h>\n\n")
|
||||
f.write(
|
||||
"struct WebAsset { const char* uri; const char* mime; const uint8_t* data; uint32_t len; uint32_t etag; uint8_t gz; };\n")
|
||||
f.write(
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS
|
||||
"."
|
||||
"communication"
|
||||
"peripherals"
|
||||
"wifi"
|
||||
"platform_shared"
|
||||
"event_bus"
|
||||
"../../submodules/nanopb"
|
||||
INCLUDE_DIRS
|
||||
"../include"
|
||||
"../../submodules/nanopb"
|
||||
REQUIRES
|
||||
driver
|
||||
esp_http_server
|
||||
nvs_flash
|
||||
esp_wifi
|
||||
esp_event
|
||||
esp_netif
|
||||
mdns
|
||||
esp_timer
|
||||
esp_psram
|
||||
spi_flash
|
||||
littlefs
|
||||
esp32-camera
|
||||
esp-dsp
|
||||
)
|
||||
+28
-61
@@ -1,38 +1,22 @@
|
||||
#include <ap_service.h>
|
||||
#include <communication/webserver.h>
|
||||
|
||||
APService::APService() : _dnsServer(nullptr), _lastManaged(0), _reconfigureAp(false), _recoveryMode(false) {}
|
||||
static const char *TAG = "APService";
|
||||
|
||||
APService::~APService() {
|
||||
if (_dnsServer) {
|
||||
delete _dnsServer;
|
||||
_dnsServer = nullptr;
|
||||
}
|
||||
APService::APService()
|
||||
: protoEndpoint(APSettings_read, APSettings_update, this,
|
||||
API_REQUEST_EXTRACTOR(ap_settings, api_APSettings),
|
||||
API_RESPONSE_ASSIGNER(ap_settings, api_APSettings)),
|
||||
_persistence(APSettings_read, APSettings_update, this,
|
||||
AP_SETTINGS_FILE, api_APSettings_fields, api_APSettings_size,
|
||||
APSettings_defaults()) {
|
||||
addUpdateHandler([&](const std::string &originId) { reconfigureAP(); }, false);
|
||||
}
|
||||
|
||||
APService::~APService() {}
|
||||
|
||||
void APService::begin() {
|
||||
_settingsHandle =
|
||||
EventBus::subscribe<api_APSettings>([this](const api_APSettings &settings) { onSettingsChanged(settings); });
|
||||
|
||||
api_APSettings initialSettings;
|
||||
if (EventBus::peek(initialSettings)) {
|
||||
onSettingsChanged(initialSettings);
|
||||
}
|
||||
}
|
||||
|
||||
void APService::onSettingsChanged(const api_APSettings &newSettings) {
|
||||
strncpy(_settings.ssid, newSettings.ssid, sizeof(_settings.ssid) - 1);
|
||||
_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();
|
||||
_persistence.readFromFS();
|
||||
}
|
||||
|
||||
esp_err_t APService::getStatusProto(httpd_req_t *request) {
|
||||
@@ -46,16 +30,16 @@ esp_err_t APService::getStatusProto(httpd_req_t *request) {
|
||||
void APService::statusProto(api_APStatus &proto) {
|
||||
proto.status = getAPNetworkStatus();
|
||||
proto.ip_address = static_cast<uint32_t>(WiFi.softAPIP());
|
||||
std::string mac = WiFi.softAPmacAddress();
|
||||
String mac = WiFi.softAPmacAddress();
|
||||
strncpy(proto.mac_address, mac.c_str(), sizeof(proto.mac_address) - 1);
|
||||
proto.mac_address[sizeof(proto.mac_address) - 1] = '\0';
|
||||
proto.station_num = WiFi.softAPgetStationNum();
|
||||
}
|
||||
|
||||
APNetworkStatus APService::getAPNetworkStatus() {
|
||||
wifi_mode_t currentWiFiMode = WiFi.getMode();
|
||||
bool apActive = currentWiFiMode == WIFI_MODE_AP || currentWiFiMode == WIFI_MODE_APSTA;
|
||||
if (apActive && _settings.provision_mode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
|
||||
WiFiMode_t currentWiFiMode = WiFi.getMode();
|
||||
bool apActive = currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA;
|
||||
if (apActive && state().provision_mode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
|
||||
return LINGERING;
|
||||
}
|
||||
return apActive ? ACTIVE : INACTIVE;
|
||||
@@ -80,13 +64,13 @@ void APService::loop() {
|
||||
}
|
||||
|
||||
void APService::manageAP() {
|
||||
wifi_mode_t currentWiFiMode = WiFi.getMode();
|
||||
if (_settings.provision_mode == AP_MODE_ALWAYS ||
|
||||
(_settings.provision_mode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED) || _recoveryMode) {
|
||||
if (_reconfigureAp || currentWiFiMode == WIFI_MODE_NULL || currentWiFiMode == WIFI_MODE_STA) {
|
||||
WiFiMode_t currentWiFiMode = WiFi.getMode();
|
||||
if (state().provision_mode == AP_MODE_ALWAYS ||
|
||||
(state().provision_mode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED) || _recoveryMode) {
|
||||
if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
|
||||
startAP();
|
||||
}
|
||||
} else if ((currentWiFiMode == WIFI_MODE_AP || currentWiFiMode == WIFI_MODE_APSTA) &&
|
||||
} else if ((currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA) &&
|
||||
(_reconfigureAp || !WiFi.softAPgetStationNum())) {
|
||||
stopAP();
|
||||
}
|
||||
@@ -94,11 +78,11 @@ void APService::manageAP() {
|
||||
}
|
||||
|
||||
void APService::startAP() {
|
||||
ESP_LOGI(TAG, "Starting software access point: %s", _settings.ssid);
|
||||
WiFi.softAPConfig(IPAddress(_settings.local_ip), IPAddress(_settings.gateway_ip), IPAddress(_settings.subnet_mask));
|
||||
WiFi.softAP(_settings.ssid, _settings.password, _settings.channel, _settings.ssid_hidden, _settings.max_clients);
|
||||
ESP_LOGI(TAG, "Starting software access point: %s", state().ssid);
|
||||
WiFi.softAPConfig(IPAddress(state().local_ip), IPAddress(state().gateway_ip), IPAddress(state().subnet_mask));
|
||||
WiFi.softAP(state().ssid, state().password, state().channel, state().ssid_hidden, state().max_clients);
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
WiFi.setTxPower(8);
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm);
|
||||
#endif
|
||||
if (!_dnsServer) {
|
||||
IPAddress apIp = WiFi.softAPIP();
|
||||
@@ -119,25 +103,8 @@ void APService::stopAP() {
|
||||
WiFi.softAPdisconnect(true);
|
||||
}
|
||||
|
||||
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;
|
||||
void APService::handleDNS() {
|
||||
if (_dnsServer) {
|
||||
_dnsServer->processNextRequest();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ esp_err_t WebServer::wsSendAll(const uint8_t* data, size_t len) {
|
||||
}
|
||||
|
||||
esp_err_t WebServer::sendError(httpd_req_t* req, int status, const char* message) {
|
||||
return send(req, status, (uint8_t*)message, strlen(message));
|
||||
return send(req, status, (uint8_t*) message, strlen(message));
|
||||
}
|
||||
|
||||
esp_err_t WebServer::sendOk(httpd_req_t* req) { return send(req, 200, nullptr, 0); }
|
||||
|
||||
@@ -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*);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -10,11 +10,14 @@ void printFeatureConfiguration() {
|
||||
ESP_LOGI("Features", "USE_CAMERA: %s", USE_CAMERA ? "enabled" : "disabled");
|
||||
ESP_LOGI("Features", "USE_MOTION: %s", USE_MOTION ? "enabled" : "disabled");
|
||||
|
||||
ESP_LOGI("Features", "USE_ICM20948: %s", USE_ICM20948 ? "enabled" : "disabled");
|
||||
ESP_LOGI("Features", "USE_BNO055: %s", USE_BNO055 ? "enabled" : "disabled");
|
||||
ESP_LOGI("Features", "USE_MPU6050: %s", USE_MPU6050 ? "enabled" : "disabled");
|
||||
ESP_LOGI("Features", "USE_HMC5883: %s", USE_HMC5883 ? "enabled" : "disabled");
|
||||
ESP_LOGI("Features", "USE_BMP180: %s", USE_BMP180 ? "enabled" : "disabled");
|
||||
ESP_LOGI("Features", "USE_USS: %s", USE_USS ? "enabled" : "disabled");
|
||||
ESP_LOGI("Features", "USE_PAJ7620U2: %s", USE_PAJ7620U2 ? "enabled" : "disabled");
|
||||
|
||||
|
||||
ESP_LOGI("Features", "USE_PCA9685: %s", USE_PCA9685 ? "enabled" : "disabled");
|
||||
ESP_LOGI("Features", "USE_WS2812: %s", USE_WS2812 ? "enabled" : "disabled");
|
||||
@@ -27,8 +30,8 @@ void printFeatureConfiguration() {
|
||||
|
||||
void features_request(const socket_message_FeaturesDataRequest& fd_req, socket_message_FeaturesDataResponse& fd_res) {
|
||||
fd_res.camera = USE_CAMERA ? true : false;
|
||||
fd_res.imu = (USE_MPU6050 || USE_BNO055) ? true : false;
|
||||
fd_res.mag = (USE_HMC5883 || USE_BNO055) ? true : false;
|
||||
fd_res.imu = (USE_MPU6050 || USE_BNO055 || USE_ICM20948) ? true : false;
|
||||
fd_res.mag = (USE_HMC5883 || USE_BNO055 || USE_ICM20948) ? true : false;
|
||||
fd_res.bmp = USE_BMP180 ? true : false;
|
||||
fd_res.sonar = USE_USS ? true : false;
|
||||
fd_res.servo = USE_PCA9685 ? true : false;
|
||||
|
||||
+64
-205
@@ -3,15 +3,13 @@
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include "utils/string_utils.hpp"
|
||||
#include <esp_log.h>
|
||||
#include <pb_encode.h>
|
||||
#include <pb_decode.h>
|
||||
|
||||
static const char *TAG = "FileSystem";
|
||||
static const char *TAG = "FileService";
|
||||
|
||||
namespace FileSystem {
|
||||
|
||||
static std::vector<api_FileEntry *> allocatedEntries;
|
||||
// Storage for dynamically allocated FileEntry arrays
|
||||
static std::vector<api_FileEntry*> allocatedEntries;
|
||||
|
||||
static void freeAllocatedEntries() {
|
||||
for (auto ptr : allocatedEntries) {
|
||||
@@ -20,81 +18,67 @@ static void freeAllocatedEntries() {
|
||||
allocatedEntries.clear();
|
||||
}
|
||||
|
||||
static void listFilesProtoRecursive(const std::string &directory, api_FileEntry *entry) {
|
||||
DIR *dir = opendir(directory.c_str());
|
||||
if (!dir) {
|
||||
void listFilesProto(const std::string &directory, api_FileEntry *entry) {
|
||||
File root = ESP_FS.open(directory.find("/") == 0 ? directory.c_str() : ("/" + directory).c_str());
|
||||
if (!root.isDirectory()) {
|
||||
entry->children_count = 0;
|
||||
entry->children = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> names;
|
||||
std::vector<bool> isDirs;
|
||||
std::vector<size_t> sizes;
|
||||
|
||||
struct dirent *d;
|
||||
while ((d = readdir(dir)) != nullptr) {
|
||||
if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) continue;
|
||||
std::string fullPath = directory + "/" + d->d_name;
|
||||
struct stat st;
|
||||
if (stat(fullPath.c_str(), &st) == 0) {
|
||||
names.push_back(d->d_name);
|
||||
isDirs.push_back(S_ISDIR(st.st_mode));
|
||||
sizes.push_back(st.st_size);
|
||||
}
|
||||
// First pass: count children
|
||||
std::vector<File> files;
|
||||
File file = root.openNextFile();
|
||||
while (file) {
|
||||
files.push_back(file);
|
||||
file = root.openNextFile();
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
if (names.empty()) {
|
||||
if (files.empty()) {
|
||||
entry->children_count = 0;
|
||||
entry->children = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
entry->children_count = names.size();
|
||||
entry->children = new api_FileEntry[names.size()];
|
||||
// Allocate children array
|
||||
entry->children_count = files.size();
|
||||
entry->children = new api_FileEntry[files.size()];
|
||||
allocatedEntries.push_back(entry->children);
|
||||
|
||||
for (size_t i = 0; i < names.size(); i++) {
|
||||
// Fill children
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
api_FileEntry &child = entry->children[i];
|
||||
memset(&child, 0, sizeof(child));
|
||||
|
||||
strncpy(child.name, names[i].c_str(), sizeof(child.name) - 1);
|
||||
std::string name = std::string(files[i].name());
|
||||
strncpy(child.name, name.c_str(), sizeof(child.name) - 1);
|
||||
child.name[sizeof(child.name) - 1] = '\0';
|
||||
|
||||
child.is_directory = isDirs[i];
|
||||
child.is_directory = files[i].isDirectory();
|
||||
if (child.is_directory) {
|
||||
std::string childPath = directory + "/" + names[i];
|
||||
listFilesProtoRecursive(childPath, &child);
|
||||
listFilesProto(name, &child);
|
||||
} else {
|
||||
child.size = sizes[i];
|
||||
child.size = files[i].size();
|
||||
child.children_count = 0;
|
||||
child.children = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void listFilesProto(const std::string &directory, api_FileEntry *entry) {
|
||||
std::string path = directory;
|
||||
if (path.empty() || path[0] != '/') {
|
||||
path = "/" + directory;
|
||||
}
|
||||
std::string fullPath = std::string(MOUNT_POINT) + path;
|
||||
listFilesProtoRecursive(fullPath, entry);
|
||||
}
|
||||
|
||||
esp_err_t getFilesProto(httpd_req_t *request) {
|
||||
freeAllocatedEntries();
|
||||
freeAllocatedEntries(); // Clean up any previous allocations
|
||||
|
||||
api_Response res = api_Response_init_zero;
|
||||
res.status_code = 200;
|
||||
res.which_payload = api_Response_file_list_tag;
|
||||
|
||||
// Create root entry
|
||||
api_FileEntry rootEntry = api_FileEntry_init_zero;
|
||||
strncpy(rootEntry.name, "root", sizeof(rootEntry.name) - 1);
|
||||
rootEntry.is_directory = true;
|
||||
listFilesProtoRecursive(MOUNT_POINT, &rootEntry);
|
||||
listFilesProto("/", &rootEntry);
|
||||
|
||||
// Allocate entries array for FileList
|
||||
res.payload.file_list.entries_count = 1;
|
||||
res.payload.file_list.entries = new api_FileEntry[1];
|
||||
allocatedEntries.push_back(res.payload.file_list.entries);
|
||||
@@ -102,146 +86,28 @@ esp_err_t getFilesProto(httpd_req_t *request) {
|
||||
|
||||
esp_err_t result = WebServer::send(request, 200, res, api_Response_fields);
|
||||
|
||||
freeAllocatedEntries();
|
||||
freeAllocatedEntries(); // Clean up after sending
|
||||
return result;
|
||||
}
|
||||
|
||||
bool init() {
|
||||
esp_vfs_littlefs_conf_t conf = {
|
||||
.base_path = MOUNT_POINT,
|
||||
.partition_label = "spiffs",
|
||||
.format_if_mount_failed = true,
|
||||
.dont_mount = false,
|
||||
};
|
||||
|
||||
esp_err_t ret = esp_vfs_littlefs_register(&conf);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount or format filesystem");
|
||||
} else if (ret == ESP_ERR_NOT_FOUND) {
|
||||
ESP_LOGE(TAG, "Failed to find LittleFS partition");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize LittleFS (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t total = 0, used = 0;
|
||||
ret = esp_littlefs_info("spiffs", &total, &used);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
|
||||
}
|
||||
|
||||
mkdirRecursive(FS_CONFIG_DIRECTORY);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fileExists(const char *filename) {
|
||||
struct stat st;
|
||||
return stat(filename, &st) == 0;
|
||||
}
|
||||
|
||||
std::string readFile(const char *filename) {
|
||||
FILE *f = fopen(filename, "r");
|
||||
if (!f) {
|
||||
return "";
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
long size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
std::string content;
|
||||
content.resize(size);
|
||||
fread(&content[0], 1, size, f);
|
||||
fclose(f);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
bool writeFile(const char *filename, const char *content) {
|
||||
FILE *f = fopen(filename, "w");
|
||||
if (!f) {
|
||||
ESP_LOGE(TAG, "Failed to open file for writing: %s", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len = strlen(content);
|
||||
size_t written = fwrite(content, 1, len, f);
|
||||
fclose(f);
|
||||
|
||||
return written == len;
|
||||
}
|
||||
|
||||
bool writeFile(const char *filename, const uint8_t *content, size_t size) {
|
||||
FILE *f = fopen(filename, "wb");
|
||||
if (!f) {
|
||||
ESP_LOGE(TAG, "Failed to open file for writing: %s", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t written = fwrite(content, 1, size, f);
|
||||
fclose(f);
|
||||
|
||||
return written == size;
|
||||
}
|
||||
|
||||
bool mkdirRecursive(const char *path) {
|
||||
char tmp[256];
|
||||
char *p = nullptr;
|
||||
size_t len;
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "%s", path);
|
||||
len = strlen(tmp);
|
||||
if (tmp[len - 1] == '/') {
|
||||
tmp[len - 1] = 0;
|
||||
}
|
||||
|
||||
for (p = tmp + 1; *p; p++) {
|
||||
if (*p == '/') {
|
||||
*p = 0;
|
||||
struct stat st;
|
||||
if (stat(tmp, &st) != 0) {
|
||||
if (::mkdir(tmp, 0755) != 0) {
|
||||
ESP_LOGE(TAG, "Failed to create directory: %s", tmp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (stat(tmp, &st) != 0) {
|
||||
if (::mkdir(tmp, 0755) != 0) {
|
||||
ESP_LOGE(TAG, "Failed to create directory: %s", tmp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t getFiles(httpd_req_t *request) {
|
||||
std::string files = listFiles(MOUNT_POINT);
|
||||
std::string files = listFiles("/");
|
||||
httpd_resp_set_type(request, "application/json");
|
||||
return httpd_resp_send(request, files.c_str(), files.length());
|
||||
}
|
||||
|
||||
esp_err_t getConfigFile(httpd_req_t *request) {
|
||||
const char *uri = request->uri;
|
||||
std::string path = std::string(MOUNT_POINT) + "/config" + std::string(uri).substr(11);
|
||||
|
||||
if (!fileExists(path.c_str())) {
|
||||
std::string path = "/config" + std::string(uri).substr(11);
|
||||
if (!ESP_FS.exists(path.c_str())) {
|
||||
return WebServer::sendError(request, 404, "File not found");
|
||||
}
|
||||
|
||||
std::string content = readFile(path.c_str());
|
||||
if (content.empty()) {
|
||||
return WebServer::sendError(request, 500, "Failed to read file");
|
||||
File file = ESP_FS.open(path.c_str(), "r");
|
||||
if (!file) {
|
||||
return WebServer::sendError(request, 500, "Failed to open file");
|
||||
}
|
||||
|
||||
String content = file.readString();
|
||||
file.close();
|
||||
if (ends_with(path, ".pb")) {
|
||||
httpd_resp_set_type(request, "application/x-protobuf");
|
||||
} else if (ends_with(path, ".json")) {
|
||||
@@ -253,11 +119,10 @@ esp_err_t getConfigFile(httpd_req_t *request) {
|
||||
}
|
||||
|
||||
esp_err_t handleDelete(httpd_req_t *request, const api_FileDeleteRequest &req) {
|
||||
std::string fullPath = std::string(MOUNT_POINT) + req.path;
|
||||
ESP_LOGI(TAG, "Deleting file: %s", fullPath.c_str());
|
||||
ESP_LOGI(TAG, "Deleting file: %s", req.path);
|
||||
|
||||
api_Response res = api_Response_init_zero;
|
||||
if (deleteFile(fullPath.c_str())) {
|
||||
if (deleteFile(req.path)) {
|
||||
res.status_code = 200;
|
||||
res.which_payload = api_Response_empty_message_tag;
|
||||
return WebServer::send(request, 200, res, api_Response_fields);
|
||||
@@ -267,11 +132,10 @@ esp_err_t handleDelete(httpd_req_t *request, const api_FileDeleteRequest &req) {
|
||||
}
|
||||
|
||||
esp_err_t handleEdit(httpd_req_t *request, const api_FileEditRequest &req) {
|
||||
std::string fullPath = std::string(MOUNT_POINT) + req.path;
|
||||
ESP_LOGI(TAG, "Editing file: %s", fullPath.c_str());
|
||||
ESP_LOGI(TAG, "Editing file: %s", req.path);
|
||||
|
||||
api_Response res = api_Response_init_zero;
|
||||
if (editFile(fullPath.c_str(), req.content->bytes, req.content->size)) {
|
||||
if (editFile(req.path, req.content->bytes, req.content->size)) {
|
||||
res.status_code = 200;
|
||||
res.which_payload = api_Response_empty_message_tag;
|
||||
return WebServer::send(request, 200, res, api_Response_fields);
|
||||
@@ -280,57 +144,52 @@ esp_err_t handleEdit(httpd_req_t *request, const api_FileEditRequest &req) {
|
||||
}
|
||||
}
|
||||
|
||||
bool deleteFile(const char *filename) { return remove(filename) == 0; }
|
||||
bool deleteFile(const char *filename) { return ESP_FS.remove(filename); }
|
||||
|
||||
std::string listFiles(const std::string &directory, bool isRoot) {
|
||||
DIR *dir = opendir(directory.c_str());
|
||||
if (!dir) {
|
||||
File root = ESP_FS.open(directory.find("/") == 0 ? directory.c_str() : ("/" + directory).c_str());
|
||||
if (!root.isDirectory()) return "{}";
|
||||
|
||||
File file = root.openNextFile();
|
||||
if (!file) {
|
||||
return isRoot ? "{ \"root\": {} }" : "{}";
|
||||
}
|
||||
|
||||
std::string output = isRoot ? "{ \"root\": {" : "{";
|
||||
bool first = true;
|
||||
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
while (file) {
|
||||
std::string name = std::string(file.name());
|
||||
if (file.isDirectory()) {
|
||||
output += "\"" + name + "\": " + listFiles(name, false);
|
||||
} else {
|
||||
output += "\"" + name + "\": " + std::to_string(file.size());
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
output += ", ";
|
||||
}
|
||||
first = false;
|
||||
|
||||
std::string fullPath = directory + "/" + entry->d_name;
|
||||
struct stat st;
|
||||
if (stat(fullPath.c_str(), &st) == 0) {
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
output += "\"" + std::string(entry->d_name) + "\": " + listFiles(fullPath, false);
|
||||
} else {
|
||||
output += "\"" + std::string(entry->d_name) + "\": " + std::to_string(st.st_size);
|
||||
}
|
||||
}
|
||||
File next = root.openNextFile();
|
||||
if (next) output += ", ";
|
||||
file = next;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
output += "}";
|
||||
if (isRoot) output += "}";
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
bool editFile(const char *filename, const uint8_t *content, size_t size) { return writeFile(filename, content, size); }
|
||||
bool editFile(const char *filename, const uint8_t *content, size_t size) {
|
||||
File file = ESP_FS.open(filename, FILE_WRITE);
|
||||
if (!file) return false;
|
||||
|
||||
bool editFile(const char *filename, const char *content) { return writeFile(filename, content); }
|
||||
file.write(content, size);
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t mkdir(httpd_req_t *request, const api_FileMkdirRequest &req) {
|
||||
std::string fullPath = std::string(MOUNT_POINT) + req.path;
|
||||
ESP_LOGI(TAG, "Creating directory: %s", fullPath.c_str());
|
||||
ESP_LOGI(TAG, "Creating directory: %s", req.path);
|
||||
|
||||
api_Response res = api_Response_init_zero;
|
||||
if (mkdirRecursive(fullPath.c_str())) {
|
||||
if (ESP_FS.mkdir(req.path)) {
|
||||
res.status_code = 200;
|
||||
res.which_payload = api_Response_empty_message_tag;
|
||||
return WebServer::send(request, 200, res, api_Response_fields);
|
||||
|
||||
+78
-77
@@ -1,12 +1,9 @@
|
||||
#include <filesystem_ws.h>
|
||||
#include <filesystem.h>
|
||||
#include <pb_encode.h>
|
||||
#include <pb_decode.h>
|
||||
#include <esp_littlefs.h>
|
||||
#include <esp_timer.h>
|
||||
#include <esp_log.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <cstring>
|
||||
|
||||
static const char* TAG = "FileSystemWS";
|
||||
|
||||
@@ -32,7 +29,7 @@ void FileSystemHandler::cleanupExpiredTransfers() {
|
||||
while (dlIt != downloads_.end()) {
|
||||
if (now - dlIt->second.lastActivityTime > FS_TRANSFER_TIMEOUT_MS) {
|
||||
if (dlIt->second.file) {
|
||||
fclose(dlIt->second.file);
|
||||
dlIt->second.file.close();
|
||||
}
|
||||
ESP_LOGW(TAG, "Download %u timed out", dlIt->first);
|
||||
|
||||
@@ -56,9 +53,9 @@ void FileSystemHandler::cleanupExpiredTransfers() {
|
||||
while (ulIt != uploads_.end()) {
|
||||
if (now - ulIt->second.lastActivityTime > FS_TRANSFER_TIMEOUT_MS) {
|
||||
if (ulIt->second.file) {
|
||||
fclose(ulIt->second.file);
|
||||
ulIt->second.file.close();
|
||||
}
|
||||
remove(ulIt->second.path.c_str());
|
||||
ESP_FS.remove(ulIt->second.path.c_str());
|
||||
ESP_LOGW(TAG, "Upload %u timed out, deleted partial file", ulIt->first);
|
||||
|
||||
if (sendUploadCompleteCallback_) {
|
||||
@@ -80,11 +77,10 @@ void FileSystemHandler::cleanupExpiredTransfers() {
|
||||
socket_message_FSDeleteResponse FileSystemHandler::handleDelete(const socket_message_FSDeleteRequest& req) {
|
||||
socket_message_FSDeleteResponse response = socket_message_FSDeleteResponse_init_zero;
|
||||
|
||||
std::string path = std::string(MOUNT_POINT) + req.path;
|
||||
std::string path(req.path);
|
||||
ESP_LOGI(TAG, "Delete request: %s", path.c_str());
|
||||
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) != 0) {
|
||||
if (!ESP_FS.exists(path.c_str())) {
|
||||
response.success = false;
|
||||
strncpy(response.error, "File or directory not found", sizeof(response.error) - 1);
|
||||
return response;
|
||||
@@ -101,45 +97,41 @@ socket_message_FSDeleteResponse FileSystemHandler::handleDelete(const socket_mes
|
||||
}
|
||||
|
||||
bool FileSystemHandler::deleteRecursive(const std::string& path) {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) != 0) return false;
|
||||
File file = ESP_FS.open(path.c_str());
|
||||
if (!file) return false;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
DIR* dir = opendir(path.c_str());
|
||||
if (!dir) return false;
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
std::string childPath = path + "/" + entry->d_name;
|
||||
if (file.isDirectory()) {
|
||||
File child = file.openNextFile();
|
||||
while (child) {
|
||||
std::string childPath = std::string(child.name());
|
||||
child.close();
|
||||
if (!deleteRecursive(childPath)) {
|
||||
closedir(dir);
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
child = file.openNextFile();
|
||||
}
|
||||
closedir(dir);
|
||||
return rmdir(path.c_str()) == 0;
|
||||
file.close();
|
||||
return ESP_FS.rmdir(path.c_str());
|
||||
} else {
|
||||
return remove(path.c_str()) == 0;
|
||||
file.close();
|
||||
return ESP_FS.remove(path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
socket_message_FSMkdirResponse FileSystemHandler::handleMkdir(const socket_message_FSMkdirRequest& req) {
|
||||
socket_message_FSMkdirResponse response = socket_message_FSMkdirResponse_init_zero;
|
||||
|
||||
std::string path = std::string(MOUNT_POINT) + req.path;
|
||||
std::string path(req.path);
|
||||
ESP_LOGI(TAG, "Mkdir request: %s", path.c_str());
|
||||
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) == 0) {
|
||||
if (ESP_FS.exists(path.c_str())) {
|
||||
response.success = false;
|
||||
strncpy(response.error, "Path already exists", sizeof(response.error) - 1);
|
||||
return response;
|
||||
}
|
||||
|
||||
if (FileSystem::mkdirRecursive(path.c_str())) {
|
||||
if (ESP_FS.mkdir(path.c_str())) {
|
||||
response.success = true;
|
||||
} else {
|
||||
response.success = false;
|
||||
@@ -150,40 +142,32 @@ socket_message_FSMkdirResponse FileSystemHandler::handleMkdir(const socket_messa
|
||||
}
|
||||
|
||||
void FileSystemHandler::listDirectory(const std::string& path, socket_message_FSListResponse& response) {
|
||||
DIR* dir = opendir(path.c_str());
|
||||
if (!dir) {
|
||||
File dir = ESP_FS.open(path.c_str());
|
||||
if (!dir || !dir.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct dirent* entry;
|
||||
File file = dir.openNextFile();
|
||||
int fileCount = 0;
|
||||
int dirCount = 0;
|
||||
|
||||
while ((entry = readdir(dir)) != nullptr && fileCount < 20 && dirCount < 20) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string fullPath = path + "/" + entry->d_name;
|
||||
struct stat st;
|
||||
if (stat(fullPath.c_str(), &st) != 0) continue;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
while (file && fileCount < 20 && dirCount < 20) { // Limit to match protobuf max_count
|
||||
if (file.isDirectory()) {
|
||||
if (dirCount < 20) {
|
||||
strncpy(response.directories[dirCount].name, entry->d_name,
|
||||
strncpy(response.directories[dirCount].name, file.name(),
|
||||
sizeof(response.directories[dirCount].name) - 1);
|
||||
dirCount++;
|
||||
}
|
||||
} else {
|
||||
if (fileCount < 20) {
|
||||
strncpy(response.files[fileCount].name, entry->d_name, sizeof(response.files[fileCount].name) - 1);
|
||||
response.files[fileCount].size = st.st_size;
|
||||
strncpy(response.files[fileCount].name, file.name(), sizeof(response.files[fileCount].name) - 1);
|
||||
response.files[fileCount].size = file.size();
|
||||
fileCount++;
|
||||
}
|
||||
}
|
||||
file = dir.openNextFile();
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
response.files_count = fileCount;
|
||||
response.directories_count = dirCount;
|
||||
}
|
||||
@@ -191,15 +175,12 @@ void FileSystemHandler::listDirectory(const std::string& path, socket_message_FS
|
||||
socket_message_FSListResponse FileSystemHandler::handleList(const socket_message_FSListRequest& req) {
|
||||
socket_message_FSListResponse response = socket_message_FSListResponse_init_zero;
|
||||
|
||||
std::string path = std::string(MOUNT_POINT);
|
||||
if (strlen(req.path) > 0 && req.path[0] != '\0') {
|
||||
path += req.path;
|
||||
}
|
||||
std::string path(req.path);
|
||||
if (path.empty()) path = "/";
|
||||
|
||||
ESP_LOGI(TAG, "List request: %s", path.c_str());
|
||||
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) != 0) {
|
||||
if (!ESP_FS.exists(path.c_str())) {
|
||||
response.success = false;
|
||||
strncpy(response.error, "Path not found", sizeof(response.error) - 1);
|
||||
return response;
|
||||
@@ -211,12 +192,14 @@ socket_message_FSListResponse FileSystemHandler::handleList(const socket_message
|
||||
return response;
|
||||
}
|
||||
|
||||
// ===== STREAMING DOWNLOAD =====
|
||||
|
||||
void FileSystemHandler::handleDownloadRequest(const socket_message_FSDownloadRequest& req, int clientId) {
|
||||
std::string path = std::string(MOUNT_POINT) + req.path;
|
||||
std::string path(req.path);
|
||||
ESP_LOGI(TAG, "Download request: %s", path.c_str());
|
||||
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) != 0 || S_ISDIR(st.st_mode)) {
|
||||
// Validate file exists
|
||||
if (!ESP_FS.exists(path.c_str())) {
|
||||
if (sendMetadataCallback_) {
|
||||
socket_message_FSDownloadMetadata metadata = socket_message_FSDownloadMetadata_init_zero;
|
||||
metadata.success = false;
|
||||
@@ -226,8 +209,8 @@ void FileSystemHandler::handleDownloadRequest(const socket_message_FSDownloadReq
|
||||
return;
|
||||
}
|
||||
|
||||
FILE* file = fopen(path.c_str(), "rb");
|
||||
if (!file) {
|
||||
File file = ESP_FS.open(path.c_str(), "r");
|
||||
if (!file || file.isDirectory()) {
|
||||
if (sendMetadataCallback_) {
|
||||
socket_message_FSDownloadMetadata metadata = socket_message_FSDownloadMetadata_init_zero;
|
||||
metadata.success = false;
|
||||
@@ -237,7 +220,11 @@ void FileSystemHandler::handleDownloadRequest(const socket_message_FSDownloadReq
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t fileSize = st.st_size;
|
||||
// Set file buffer size large, so we use ram to read from
|
||||
// Set buffer size so 1 mb (static) TODO: Check that there is enough ram to do this
|
||||
file.setBufferSize(1000000);
|
||||
|
||||
uint32_t fileSize = file.size();
|
||||
uint32_t chunkSize = FS_MAX_CHUNK_SIZE;
|
||||
uint32_t totalChunks = (fileSize + chunkSize - 1) / chunkSize;
|
||||
if (totalChunks == 0) totalChunks = 1;
|
||||
@@ -267,8 +254,10 @@ void FileSystemHandler::handleDownloadRequest(const socket_message_FSDownloadReq
|
||||
|
||||
ESP_LOGI(TAG, "Download started: %s, size=%u, chunks=%u, id=%u", path.c_str(), fileSize, totalChunks, transferId);
|
||||
|
||||
// Start streaming chunks immediately
|
||||
while (sendNextDownloadChunk(transferId)) {
|
||||
taskYIELD();
|
||||
// Keep sending until done or error
|
||||
taskYIELD(); // Allow other tasks to run
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,7 +280,7 @@ bool FileSystemHandler::sendNextDownloadChunk(uint32_t transferId) {
|
||||
sendCompleteCallback_(complete, state.clientId);
|
||||
}
|
||||
|
||||
fclose(state.file);
|
||||
state.file.close();
|
||||
downloads_.erase(it);
|
||||
ESP_LOGI(TAG, "Download completed: %u", transferId);
|
||||
return false;
|
||||
@@ -308,7 +297,7 @@ bool FileSystemHandler::sendNextDownloadChunk(uint32_t transferId) {
|
||||
bytesToRead = state.fileSize - position;
|
||||
}
|
||||
|
||||
size_t bytesRead = fread(data->data.bytes, 1, bytesToRead, state.file);
|
||||
size_t bytesRead = state.file.read(data->data.bytes, bytesToRead);
|
||||
if (bytesRead == 0 && bytesToRead > 0) {
|
||||
delete data;
|
||||
if (sendCompleteCallback_) {
|
||||
@@ -321,7 +310,7 @@ bool FileSystemHandler::sendNextDownloadChunk(uint32_t transferId) {
|
||||
sendCompleteCallback_(complete, state.clientId);
|
||||
}
|
||||
|
||||
fclose(state.file);
|
||||
state.file.close();
|
||||
downloads_.erase(it);
|
||||
ESP_LOGE(TAG, "Download failed - read error: %u", transferId);
|
||||
return false;
|
||||
@@ -339,40 +328,45 @@ bool FileSystemHandler::sendNextDownloadChunk(uint32_t transferId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== STREAMING UPLOAD =====
|
||||
|
||||
socket_message_FSUploadStartResponse FileSystemHandler::handleUploadStart(const socket_message_FSUploadStart& req,
|
||||
int clientId) {
|
||||
socket_message_FSUploadStartResponse response = socket_message_FSUploadStartResponse_init_zero;
|
||||
|
||||
std::string path = std::string(MOUNT_POINT) + req.path;
|
||||
std::string path(req.path);
|
||||
ESP_LOGI(TAG, "Upload start request: %s, size=%u, chunks=%u", path.c_str(), req.file_size, req.total_chunks);
|
||||
|
||||
// Check available space
|
||||
size_t fs_total = 0, fs_used = 0;
|
||||
esp_littlefs_info("spiffs", &fs_total, &fs_used);
|
||||
size_t freeSpace = fs_total - fs_used;
|
||||
if (freeSpace < req.file_size + 4096) {
|
||||
if (freeSpace < req.file_size + 4096) { // 4KB safety margin
|
||||
response.success = false;
|
||||
strncpy(response.error, "Insufficient storage space", sizeof(response.error) - 1);
|
||||
return response;
|
||||
}
|
||||
|
||||
// Ensure parent directory exists
|
||||
size_t lastSlash = path.find_last_of('/');
|
||||
if (lastSlash != std::string::npos && lastSlash > 0) {
|
||||
std::string parentDir = path.substr(0, lastSlash);
|
||||
struct stat st;
|
||||
if (stat(parentDir.c_str(), &st) != 0) {
|
||||
if (!ESP_FS.exists(parentDir.c_str())) {
|
||||
response.success = false;
|
||||
strncpy(response.error, "Parent directory does not exist", sizeof(response.error) - 1);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
FILE* file = fopen(path.c_str(), "wb");
|
||||
File file = ESP_FS.open(path.c_str(), FILE_WRITE);
|
||||
if (!file) {
|
||||
response.success = false;
|
||||
strncpy(response.error, "Cannot open file for writing", sizeof(response.error) - 1);
|
||||
return response;
|
||||
}
|
||||
|
||||
file.setBufferSize(1000000);
|
||||
|
||||
uint32_t transferId = generateTransferId();
|
||||
|
||||
UploadState state;
|
||||
@@ -408,15 +402,20 @@ void FileSystemHandler::handleUploadData(const socket_message_FSUploadData& req)
|
||||
UploadState& state = it->second;
|
||||
state.lastActivityTime = esp_timer_get_time() / 1000;
|
||||
|
||||
// Skip if we already have an error
|
||||
if (state.hasError) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate chunk index (allow out-of-order but warn)
|
||||
if (req.chunk_index != state.chunksReceived) {
|
||||
ESP_LOGW(TAG, "Upload chunk out of order: expected %u, got %u", state.chunksReceived, req.chunk_index);
|
||||
// For now, we'll accept it anyway and write sequentially
|
||||
// A more robust implementation would buffer out-of-order chunks
|
||||
}
|
||||
|
||||
size_t bytesWritten = fwrite(req.data.bytes, 1, req.data.size, state.file);
|
||||
// Write chunk data
|
||||
size_t bytesWritten = state.file.write(req.data.bytes, req.data.size);
|
||||
if (bytesWritten != req.data.size) {
|
||||
state.hasError = true;
|
||||
state.errorMessage = "Failed to write chunk";
|
||||
@@ -427,13 +426,15 @@ void FileSystemHandler::handleUploadData(const socket_message_FSUploadData& req)
|
||||
state.chunksReceived++;
|
||||
state.bytesReceived += bytesWritten;
|
||||
|
||||
// Flush periodically to prevent LittleFS buffer issues
|
||||
if (state.chunksReceived > 0 && state.chunksReceived % 64 == 0) {
|
||||
ESP_LOGD(TAG, "Flushing file at chunk %u", state.chunksReceived);
|
||||
fflush(state.file);
|
||||
state.file.flush();
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Upload chunk %u/%u: %u bytes", state.chunksReceived, state.totalChunks, bytesWritten);
|
||||
|
||||
// Check if upload is complete
|
||||
if (state.chunksReceived >= state.totalChunks) {
|
||||
finalizeUpload(transferId, true);
|
||||
}
|
||||
@@ -448,11 +449,11 @@ void FileSystemHandler::finalizeUpload(uint32_t transferId, bool success, const
|
||||
UploadState& state = it->second;
|
||||
|
||||
if (state.file) {
|
||||
fclose(state.file);
|
||||
state.file.close();
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
remove(state.path.c_str());
|
||||
ESP_FS.remove(state.path.c_str());
|
||||
ESP_LOGW(TAG, "Upload failed, deleted partial file: %s", state.path.c_str());
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Upload completed: %s (%u bytes)", state.path.c_str(), state.bytesReceived);
|
||||
@@ -472,6 +473,8 @@ void FileSystemHandler::finalizeUpload(uint32_t transferId, bool success, const
|
||||
uploads_.erase(it);
|
||||
}
|
||||
|
||||
// ===== TRANSFER CONTROL =====
|
||||
|
||||
socket_message_FSCancelTransferResponse FileSystemHandler::handleCancelTransfer(
|
||||
const socket_message_FSCancelTransfer& req) {
|
||||
socket_message_FSCancelTransferResponse response = socket_message_FSCancelTransferResponse_init_zero;
|
||||
@@ -481,7 +484,7 @@ socket_message_FSCancelTransferResponse FileSystemHandler::handleCancelTransfer(
|
||||
auto dlIt = downloads_.find(transferId);
|
||||
if (dlIt != downloads_.end()) {
|
||||
if (dlIt->second.file) {
|
||||
fclose(dlIt->second.file);
|
||||
dlIt->second.file.close();
|
||||
}
|
||||
downloads_.erase(dlIt);
|
||||
response.success = true;
|
||||
@@ -492,9 +495,9 @@ socket_message_FSCancelTransferResponse FileSystemHandler::handleCancelTransfer(
|
||||
auto ulIt = uploads_.find(transferId);
|
||||
if (ulIt != uploads_.end()) {
|
||||
if (ulIt->second.file) {
|
||||
fclose(ulIt->second.file);
|
||||
ulIt->second.file.close();
|
||||
}
|
||||
remove(ulIt->second.path.c_str());
|
||||
ESP_FS.remove(ulIt->second.path.c_str());
|
||||
uploads_.erase(ulIt);
|
||||
response.success = true;
|
||||
ESP_LOGI(TAG, "Upload cancelled: %u", transferId);
|
||||
@@ -505,6 +508,4 @@ socket_message_FSCancelTransferResponse FileSystemHandler::handleCancelTransfer(
|
||||
return response;
|
||||
}
|
||||
|
||||
void FileSystemHandler::processPendingDownloads() {}
|
||||
|
||||
} // namespace FileSystemWS
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0.0"
|
||||
|
||||
espressif/mdns:
|
||||
version: "^1.3.0"
|
||||
|
||||
joltwallet/littlefs:
|
||||
version: "^1.14.0"
|
||||
|
||||
espressif/esp-dsp:
|
||||
version: "^1.4.0"
|
||||
|
||||
espressif/led_strip:
|
||||
version: "^2.5.0"
|
||||
|
||||
espressif/esp32-camera:
|
||||
version: "^2.0.0"
|
||||
+93
-91
@@ -1,9 +1,6 @@
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_log.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <wifi/wifi_idf.h>
|
||||
#include <mdns.h>
|
||||
#include <Arduino.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <WiFi.h>
|
||||
#include <map>
|
||||
|
||||
#include <filesystem.h>
|
||||
@@ -20,12 +17,10 @@
|
||||
#include <ap_service.h>
|
||||
#include <mdns_service.h>
|
||||
#include <system_service.h>
|
||||
#include <consumers/event_storage_manager.h>
|
||||
#include <event_bus/rest_endpoints.h>
|
||||
|
||||
#include <www_mount.hpp>
|
||||
|
||||
Websocket wsSocket {server, "/api/ws"};
|
||||
Websocket socket {server, "/api/ws"};
|
||||
|
||||
Peripherals peripherals;
|
||||
ServoController servoController;
|
||||
@@ -44,7 +39,7 @@ WiFiService wifiService;
|
||||
APService apService;
|
||||
|
||||
void setupServer() {
|
||||
server.config(50 + WWW_ASSETS_COUNT, 12288);
|
||||
server.config(50 + WWW_ASSETS_COUNT, 32768);
|
||||
server.listen(80);
|
||||
|
||||
server.on("/api/system/reset", HTTP_POST,
|
||||
@@ -58,61 +53,62 @@ void setupServer() {
|
||||
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);
|
||||
[&](httpd_req_t *request) { return cameraService.protoEndpoint.getState(request); });
|
||||
server.on("/api/camera/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
||||
return cameraService.protoEndpoint.handleStateUpdate(request, protoReq);
|
||||
});
|
||||
#endif
|
||||
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);
|
||||
[&](httpd_req_t *request) { return servoController.protoEndpoint.getState(request); });
|
||||
server.on("/api/servo/config", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
||||
return servoController.protoEndpoint.handleStateUpdate(request, protoReq);
|
||||
});
|
||||
|
||||
server.on("/api/wifi/sta/settings", HTTP_GET,
|
||||
[](httpd_req_t *request) { return WiFiSettingsEndpoint::getSettings(request); });
|
||||
server.on("/api/wifi/sta/settings", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
|
||||
return WiFiSettingsEndpoint::updateSettings(request, protoReq);
|
||||
[&](httpd_req_t *request) { return wifiService.protoEndpoint.getState(request); });
|
||||
server.on("/api/wifi/sta/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
|
||||
return wifiService.protoEndpoint.handleStateUpdate(request, protoReq);
|
||||
});
|
||||
server.on("/api/wifi/scan", HTTP_GET, [&](httpd_req_t *request) { return wifiService.handleScan(request); });
|
||||
server.on("/api/wifi/networks", HTTP_GET, [&](httpd_req_t *request) { return wifiService.getNetworks(request); });
|
||||
server.on("/api/wifi/sta/status", HTTP_GET,
|
||||
[&](httpd_req_t *request) { return wifiService.getNetworkStatus(request); });
|
||||
|
||||
|
||||
server.on("/api/ap/status", HTTP_GET, [&](httpd_req_t *request) { return apService.getStatusProto(request); });
|
||||
server.on("/api/ap/settings", HTTP_GET,
|
||||
[](httpd_req_t *request) { return APSettingsEndpoint::getSettings(request); });
|
||||
server.on("/api/ap/settings", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
|
||||
return APSettingsEndpoint::updateSettings(request, protoReq);
|
||||
});
|
||||
|
||||
[&](httpd_req_t *request) { return apService.protoEndpoint.getState(request); });
|
||||
server.on("/api/ap/settings", HTTP_POST,
|
||||
[&](httpd_req_t *request, api_Request *protoReq) {
|
||||
return apService.protoEndpoint.handleStateUpdate(request, protoReq);
|
||||
});
|
||||
|
||||
server.on("/api/peripherals/settings", HTTP_GET,
|
||||
[](httpd_req_t *request) { return PeripheralSettingsEndpoint::getSettings(request); });
|
||||
server.on("/api/peripherals/settings", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
|
||||
return PeripheralSettingsEndpoint::updateSettings(request, protoReq);
|
||||
});
|
||||
[&](httpd_req_t *request) { return peripherals.protoEndpoint.getState(request); });
|
||||
server.on("/api/peripherals/settings", HTTP_POST,
|
||||
[&](httpd_req_t *request, api_Request *protoReq) {
|
||||
return peripherals.protoEndpoint.handleStateUpdate(request, protoReq);
|
||||
});
|
||||
|
||||
#if FT_ENABLED(USE_MDNS)
|
||||
server.on("/api/mdns/settings", HTTP_GET,
|
||||
[](httpd_req_t *request) { return MDNSSettingsEndpoint::getSettings(request); });
|
||||
server.on("/api/mdns/settings", HTTP_POST, [](httpd_req_t *request, api_Request *protoReq) {
|
||||
return MDNSSettingsEndpoint::updateSettings(request, protoReq);
|
||||
});
|
||||
server.on("/api/mdns/settings", HTTP_GET, [&](httpd_req_t *request) { return mdnsService.protoEndpoint.getState(request); });
|
||||
server.on("/api/mdns/settings", HTTP_POST,
|
||||
[&](httpd_req_t *request, api_Request *protoReq) {
|
||||
return mdnsService.protoEndpoint.handleStateUpdate(request, protoReq);
|
||||
});
|
||||
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);
|
||||
});
|
||||
server.on("/api/mdns/query", HTTP_POST,
|
||||
[&](httpd_req_t *request, api_Request *protoReq) {
|
||||
return mdnsService.queryServices(request, protoReq);
|
||||
});
|
||||
#endif
|
||||
|
||||
|
||||
server.on("/api/config/*", HTTP_GET, [](httpd_req_t *request) { return FileSystem::getConfigFile(request); });
|
||||
server.on("/api/files", HTTP_GET, [&](httpd_req_t *request) { return FileSystem::getFilesProto(request); });
|
||||
STAITC_PROTO_POST_ENDPOINT(server, "/api/files/delete", file_delete_request, FileSystem::handleDelete);
|
||||
STAITC_PROTO_POST_ENDPOINT(server, "/api/files/edit", file_edit_request, FileSystem::handleEdit);
|
||||
STAITC_PROTO_POST_ENDPOINT(server, "/api/files/mkdir", file_mkdir_request, FileSystem::mkdir);
|
||||
wsSocket.begin();
|
||||
#if EMBED_WEBAPP
|
||||
mountStaticAssets(server);
|
||||
mountSpaFallback(server);
|
||||
#endif
|
||||
server.on("/*", HTTP_OPTIONS, [](httpd_req_t *request) {
|
||||
httpd_resp_set_status(request, "200 OK");
|
||||
@@ -127,23 +123,37 @@ void setupServer() {
|
||||
|
||||
void setupEventSocket() {
|
||||
FileSystemWS::fsHandler.setSendCallbacks(
|
||||
[](const socket_message_FSDownloadMetadata &metadata, int clientId) { wsSocket.emit(metadata, clientId); },
|
||||
[](const socket_message_FSDownloadData &data, int clientId) { wsSocket.emit(data, 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_FSDownloadMetadata &metadata, int clientId) { socket.emit(metadata, clientId); },
|
||||
[](const socket_message_FSDownloadData &data, int clientId) { socket.emit(data, clientId); },
|
||||
[](const socket_message_FSDownloadComplete &complete, int clientId) { socket.emit(complete, clientId); },
|
||||
[](const socket_message_FSUploadComplete &complete, int clientId) { socket.emit(complete, clientId); });
|
||||
|
||||
wsSocket.onPublish<socket_message_ControllerData>();
|
||||
wsSocket.onPublish<socket_message_ModeData>();
|
||||
wsSocket.onPublish<socket_message_WalkGaitData>();
|
||||
wsSocket.onPublish<socket_message_AnglesData>();
|
||||
wsSocket.onPublish<socket_message_ServoPWMData>();
|
||||
wsSocket.onPublish<socket_message_ServoStateData>();
|
||||
socket.on<socket_message_ControllerData>(
|
||||
[&](const socket_message_ControllerData &data, int clientId) { motionService.handleInput(data); });
|
||||
|
||||
wsSocket.on<socket_message_FSUploadData>(
|
||||
socket.on<socket_message_ModeData>([&](const socket_message_ModeData &data, int clientId) {
|
||||
servoController.setMode(SERVO_CONTROL_STATE::ANGLE);
|
||||
motionService.handleMode(data);
|
||||
motionService.isActive() ? servoController.activate() : servoController.deactivate();
|
||||
});
|
||||
|
||||
socket.on<socket_message_WalkGaitData>(
|
||||
[&](const socket_message_WalkGaitData &data, int clientId) { motionService.handleWalkGait(data); });
|
||||
|
||||
socket.on<socket_message_AnglesData>(
|
||||
[&](const socket_message_AnglesData &data, int clientId) { motionService.handleAngles(data); });
|
||||
|
||||
socket.on<socket_message_ServoPWMData>([&](const socket_message_ServoPWMData &data, int clientId) {
|
||||
servoController.setServoPWM(data.servo_id, data.servo_pwm);
|
||||
});
|
||||
|
||||
socket.on<socket_message_ServoStateData>([&](const socket_message_ServoStateData &data, int clientId) {
|
||||
data.active ? servoController.activate() : servoController.deactivate();
|
||||
});
|
||||
|
||||
socket.on<socket_message_FSUploadData>(
|
||||
[&](const socket_message_FSUploadData &data, int clientId) { FileSystemWS::fsHandler.handleUploadData(data); });
|
||||
|
||||
wsSocket.bridgeFromEventBus<socket_message_IMUData>(100);
|
||||
|
||||
using CorrelationHandler =
|
||||
std::function<void(const socket_message_CorrelationRequest &, socket_message_CorrelationResponse &, int)>;
|
||||
static std::map<pb_size_t, CorrelationHandler> correlationHandlers = {
|
||||
@@ -215,7 +225,7 @@ void setupEventSocket() {
|
||||
}},
|
||||
};
|
||||
|
||||
wsSocket.on<socket_message_CorrelationRequest>([&](const socket_message_CorrelationRequest &data, int clientId) {
|
||||
socket.on<socket_message_CorrelationRequest>([&](const socket_message_CorrelationRequest &data, int clientId) {
|
||||
auto res = new socket_message_CorrelationResponse();
|
||||
*res = socket_message_CorrelationResponse_init_default;
|
||||
res->correlation_id = data.correlation_id;
|
||||
@@ -225,7 +235,7 @@ void setupEventSocket() {
|
||||
if (it != correlationHandlers.end()) {
|
||||
it->second(data, *res, clientId);
|
||||
if (res->status_code != 0) {
|
||||
wsSocket.emit(*res, clientId);
|
||||
socket.emit(*res, clientId);
|
||||
}
|
||||
} else {
|
||||
printf("WARNING: no handler for correlation request: %d\n", data.which_request);
|
||||
@@ -238,22 +248,15 @@ void setupEventSocket() {
|
||||
void IRAM_ATTR SpotControlLoopEntry(void *) {
|
||||
ESP_LOGI("main", "Control task starting");
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(10);
|
||||
const TickType_t xFrequency = 5 / portTICK_PERIOD_MS;
|
||||
|
||||
peripherals.begin();
|
||||
servoController.begin();
|
||||
motionService.begin();
|
||||
motionService.setModeChangeCallback([](bool active) {
|
||||
servoController.setMode(SERVO_CONTROL_STATE::ANGLE);
|
||||
active ? servoController.activate() : servoController.deactivate();
|
||||
});
|
||||
#if FT_ENABLED(USE_WS2812)
|
||||
ledService.begin();
|
||||
#endif
|
||||
peripherals.calibrateIMU();
|
||||
|
||||
for (;;) {
|
||||
WARN_IF_SLOW(SpotControlLoopEntry, 10);
|
||||
WARN_IF_SLOW(SpotControlLoopEntry, 5);
|
||||
peripherals.update();
|
||||
motionService.update(&peripherals);
|
||||
servoController.setAngles(motionService.getAngles());
|
||||
@@ -261,22 +264,22 @@ void IRAM_ATTR SpotControlLoopEntry(void *) {
|
||||
#if FT_ENABLED(USE_WS2812)
|
||||
ledService.loop();
|
||||
#endif
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
// CALLS_PER_SECOND_TIMED(SpotControlLoopEntry,
|
||||
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, peripherals_update)
|
||||
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, motionService_update)
|
||||
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, servoController_setAngles)
|
||||
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, servoController_update)
|
||||
// );
|
||||
// vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR serviceLoopEntry(void *) {
|
||||
ESP_LOGI("main", "Service task starting");
|
||||
|
||||
static EventStorageManager storageManager;
|
||||
storageManager.initialize();
|
||||
ESP_LOGI("main", "Event storage initialized, settings loaded and published");
|
||||
|
||||
WiFi.init();
|
||||
wifiService.begin();
|
||||
mdns_init();
|
||||
mdns_hostname_set(APP_NAME);
|
||||
mdns_instance_name_set(APP_NAME);
|
||||
MDNS.begin(APP_NAME);
|
||||
MDNS.setInstanceName(APP_NAME);
|
||||
apService.begin();
|
||||
|
||||
#if FT_ENABLED(USE_CAMERA)
|
||||
@@ -284,6 +287,8 @@ void IRAM_ATTR serviceLoopEntry(void *) {
|
||||
#endif
|
||||
|
||||
setupServer();
|
||||
|
||||
socket.begin();
|
||||
setupEventSocket();
|
||||
|
||||
ESP_LOGI("main", "Service task started");
|
||||
@@ -292,21 +297,23 @@ void IRAM_ATTR serviceLoopEntry(void *) {
|
||||
apService.loop();
|
||||
|
||||
EXECUTE_EVERY_N_MS(2000, {
|
||||
if (wsSocket.hasSubscribers(socket_message_Message_analytics_tag)) {
|
||||
if (socket.hasSubscribers(socket_message_Message_analytics_tag)) {
|
||||
socket_message_AnalyticsData analytics = socket_message_AnalyticsData_init_zero;
|
||||
system_service::getAnalytics(analytics);
|
||||
wsSocket.emit(analytics);
|
||||
socket.emit(analytics);
|
||||
}
|
||||
});
|
||||
|
||||
EXECUTE_EVERY_N_MS(100, {
|
||||
socket_message_IMUData imu = socket_message_IMUData_init_zero;
|
||||
peripherals.getIMUProto(imu);
|
||||
EventBus::publish(imu);
|
||||
if (socket.hasSubscribers(socket_message_Message_imu_tag)) {
|
||||
socket_message_IMUData imu = socket_message_IMUData_init_zero;
|
||||
peripherals.getIMUProto(imu);
|
||||
socket.emit(imu);
|
||||
}
|
||||
|
||||
if (wsSocket.hasSubscribers(socket_message_Message_rssi_tag)) {
|
||||
if (socket.hasSubscribers(socket_message_Message_rssi_tag)) {
|
||||
socket_message_RSSIData rssi = {.rssi = WiFi.RSSI()};
|
||||
wsSocket.emit(rssi);
|
||||
socket.emit(rssi);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -316,23 +323,18 @@ void IRAM_ATTR serviceLoopEntry(void *) {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void app_main(void) {
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
FileSystem::init();
|
||||
void setup() {
|
||||
ESP_FS.begin();
|
||||
|
||||
ESP_LOGI("main", "Booting robot");
|
||||
|
||||
feature_service::printFeatureConfiguration();
|
||||
|
||||
xTaskCreate(serviceLoopEntry, "Service task", 8192, nullptr, 2, nullptr);
|
||||
xTaskCreate(serviceLoopEntry, "Service task", 4096, nullptr, 2, nullptr);
|
||||
|
||||
xTaskCreatePinnedToCore(SpotControlLoopEntry, "Control task", 8192, nullptr, 5, nullptr, 1);
|
||||
xTaskCreatePinnedToCore(SpotControlLoopEntry, "Control task", 4096, nullptr, 5, nullptr, 1);
|
||||
|
||||
ESP_LOGI("main", "Finished booting");
|
||||
}
|
||||
|
||||
void loop() { vTaskDelete(nullptr); }
|
||||
|
||||
+49
-124
@@ -1,8 +1,17 @@
|
||||
#include <mdns_service.h>
|
||||
#include <communication/webserver.h>
|
||||
#include <esp_netif.h>
|
||||
|
||||
MDNSService::MDNSService() {}
|
||||
static const char *TAG = "MDNSService";
|
||||
|
||||
MDNSService::MDNSService()
|
||||
: protoEndpoint(MDNSSettings_read, MDNSSettings_update, this,
|
||||
API_REQUEST_EXTRACTOR(mdns_settings, api_MDNSSettings),
|
||||
API_RESPONSE_ASSIGNER(mdns_settings, api_MDNSSettings)),
|
||||
_persistence(MDNSSettings_read, MDNSSettings_update, this,
|
||||
MDNS_SETTINGS_FILE, api_MDNSSettings_fields, api_MDNSSettings_size,
|
||||
MDNSSettings_defaults()) {
|
||||
addUpdateHandler([&](const std::string &originId) { reconfigureMDNS(); }, false);
|
||||
}
|
||||
|
||||
MDNSService::~MDNSService() {
|
||||
if (_started) {
|
||||
@@ -11,32 +20,10 @@ MDNSService::~MDNSService() {
|
||||
}
|
||||
|
||||
void MDNSService::begin() {
|
||||
_settingsHandle = EventBus::subscribe<api_MDNSSettings>(
|
||||
[this](const api_MDNSSettings &settings) { onSettingsChanged(settings); });
|
||||
|
||||
api_MDNSSettings initialSettings;
|
||||
if (EventBus::peek(initialSettings)) {
|
||||
onSettingsChanged(initialSettings);
|
||||
}
|
||||
_persistence.readFromFS();
|
||||
startMDNS();
|
||||
}
|
||||
|
||||
void MDNSService::onSettingsChanged(const api_MDNSSettings &newSettings) {
|
||||
strncpy(_settings.hostname, newSettings.hostname, sizeof(_settings.hostname) - 1);
|
||||
_settings.hostname[sizeof(_settings.hostname) - 1] = '\0';
|
||||
strncpy(_settings.instance, newSettings.instance, sizeof(_settings.instance) - 1);
|
||||
_settings.instance[sizeof(_settings.instance) - 1] = '\0';
|
||||
_settings.services_count = newSettings.services_count;
|
||||
for (size_t i = 0; i < newSettings.services_count; i++) {
|
||||
_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() {
|
||||
if (_started) {
|
||||
stopMDNS();
|
||||
@@ -45,60 +32,43 @@ void MDNSService::reconfigureMDNS() {
|
||||
}
|
||||
|
||||
void MDNSService::startMDNS() {
|
||||
ESP_LOGV(TAG, "Starting MDNS with hostname: %s", _settings.hostname);
|
||||
ESP_LOGV(TAG, "Starting MDNS with hostname: %s", state().hostname);
|
||||
|
||||
esp_err_t err = mdns_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize MDNS: %s", esp_err_to_name(err));
|
||||
if (MDNS.begin(state().hostname)) {
|
||||
_started = true;
|
||||
MDNS.setInstanceName(state().instance);
|
||||
|
||||
addServices();
|
||||
|
||||
ESP_LOGI(TAG, "MDNS started successfully with hostname: %s", state().hostname);
|
||||
} else {
|
||||
_started = false;
|
||||
return;
|
||||
ESP_LOGE(TAG, "Failed to start MDNS");
|
||||
}
|
||||
|
||||
err = mdns_hostname_set(_settings.hostname);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set MDNS hostname: %s", esp_err_to_name(err));
|
||||
mdns_free();
|
||||
_started = false;
|
||||
return;
|
||||
}
|
||||
|
||||
err = mdns_instance_name_set(_settings.instance);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to set MDNS instance name: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
_started = true;
|
||||
addServices();
|
||||
|
||||
ESP_LOGI(TAG, "MDNS started successfully with hostname: %s", _settings.hostname);
|
||||
}
|
||||
|
||||
void MDNSService::stopMDNS() {
|
||||
ESP_LOGV(TAG, "Stopping MDNS");
|
||||
mdns_free();
|
||||
MDNS.end();
|
||||
_started = false;
|
||||
}
|
||||
|
||||
void MDNSService::addServices() {
|
||||
for (size_t i = 0; i < _settings.services_count; i++) {
|
||||
const auto &service = _settings.services[i];
|
||||
esp_err_t err = mdns_service_add(nullptr, service.service, service.protocol, service.port, nullptr, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to add service %s: %s", service.service, esp_err_to_name(err));
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < state().services_count; i++) {
|
||||
const auto &service = state().services[i];
|
||||
MDNS.addService(service.service, service.protocol, service.port);
|
||||
|
||||
for (size_t j = 0; j < service.txt_records_count; j++) {
|
||||
const auto &txt = service.txt_records[j];
|
||||
mdns_service_txt_item_set(service.service, service.protocol, txt.key, txt.value);
|
||||
MDNS.addServiceTxt(service.service, service.protocol, txt.key, txt.value);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < _settings.global_txt_records_count; i++) {
|
||||
const auto &txt = _settings.global_txt_records[i];
|
||||
for (size_t j = 0; j < _settings.services_count; j++) {
|
||||
const auto &service = _settings.services[j];
|
||||
mdns_service_txt_item_set(service.service, service.protocol, txt.key, txt.value);
|
||||
for (size_t i = 0; i < state().global_txt_records_count; i++) {
|
||||
const auto &txt = state().global_txt_records[i];
|
||||
for (size_t j = 0; j < state().services_count; j++) {
|
||||
const auto &service = state().services[j];
|
||||
MDNS.addServiceTxt(service.service, service.protocol, txt.key, txt.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,17 +79,17 @@ esp_err_t MDNSService::getStatus(httpd_req_t *request) {
|
||||
|
||||
MDNSStatus &status = response.payload.mdns_status;
|
||||
status.started = _started;
|
||||
strncpy(status.hostname, _settings.hostname, sizeof(status.hostname) - 1);
|
||||
strncpy(status.instance, _settings.instance, sizeof(status.instance) - 1);
|
||||
strncpy(status.hostname, state().hostname, sizeof(status.hostname) - 1);
|
||||
strncpy(status.instance, state().instance, sizeof(status.instance) - 1);
|
||||
|
||||
status.services_count = _settings.services_count;
|
||||
for (size_t i = 0; i < _settings.services_count; i++) {
|
||||
status.services[i] = _settings.services[i];
|
||||
status.services_count = state().services_count;
|
||||
for (size_t i = 0; i < state().services_count; i++) {
|
||||
status.services[i] = state().services[i];
|
||||
}
|
||||
|
||||
status.global_txt_records_count = _settings.global_txt_records_count;
|
||||
for (size_t i = 0; i < _settings.global_txt_records_count; i++) {
|
||||
status.global_txt_records[i] = _settings.global_txt_records[i];
|
||||
status.global_txt_records_count = state().global_txt_records_count;
|
||||
for (size_t i = 0; i < state().global_txt_records_count; i++) {
|
||||
status.global_txt_records[i] = state().global_txt_records[i];
|
||||
}
|
||||
|
||||
return WebServer::send(request, 200, response, api_Response_fields);
|
||||
@@ -133,67 +103,22 @@ esp_err_t MDNSService::queryServices(httpd_req_t *request, api_Request *protoReq
|
||||
const api_MDNSQueryRequest &queryReq = protoReq->payload.mdns_query_request;
|
||||
ESP_LOGI(TAG, "Querying for service: %s, protocol: %s", queryReq.service, queryReq.protocol);
|
||||
|
||||
mdns_result_t *results = nullptr;
|
||||
esp_err_t err = mdns_query_ptr(queryReq.service, queryReq.protocol, 3000, 20, &results);
|
||||
int n = MDNS.queryService(queryReq.service, queryReq.protocol);
|
||||
ESP_LOGI(TAG, "Found %d services", n);
|
||||
|
||||
api_Response response = api_Response_init_zero;
|
||||
response.which_payload = api_Response_mdns_query_response_tag;
|
||||
api_MDNSQueryResponse &queryResp = response.payload.mdns_query_response;
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "MDNS query failed: %s", esp_err_to_name(err));
|
||||
queryResp.services_count = 0;
|
||||
return WebServer::send(request, 200, response, api_Response_fields);
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
mdns_result_t *r = results;
|
||||
while (r && count < 16) {
|
||||
count++;
|
||||
r = r->next;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Found %d services", count);
|
||||
|
||||
// Limit to max_count from options file (16)
|
||||
size_t count = (n > 16) ? 16 : static_cast<size_t>(n);
|
||||
queryResp.services_count = count;
|
||||
r = results;
|
||||
size_t i = 0;
|
||||
while (r && i < 16) {
|
||||
if (r->hostname) {
|
||||
strncpy(queryResp.services[i].name, r->hostname, sizeof(queryResp.services[i].name) - 1);
|
||||
}
|
||||
if (r->addr) {
|
||||
char ip_str[16];
|
||||
esp_ip4addr_ntoa(&r->addr->addr.u_addr.ip4, ip_str, sizeof(ip_str));
|
||||
strncpy(queryResp.services[i].ip, ip_str, sizeof(queryResp.services[i].ip) - 1);
|
||||
}
|
||||
queryResp.services[i].port = r->port;
|
||||
r = r->next;
|
||||
i++;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
strncpy(queryResp.services[i].name, MDNS.hostname(i).c_str(), sizeof(queryResp.services[i].name) - 1);
|
||||
strncpy(queryResp.services[i].ip, MDNS.IP(i).toString().c_str(), sizeof(queryResp.services[i].ip) - 1);
|
||||
queryResp.services[i].port = MDNS.port(i);
|
||||
}
|
||||
|
||||
mdns_query_results_free(results);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
+1
-19
@@ -1,23 +1,6 @@
|
||||
#include <motion.h>
|
||||
|
||||
void MotionService::begin() {
|
||||
body_state.updateFeet(KinConfig::default_feet_positions);
|
||||
subscribeToEvents();
|
||||
}
|
||||
|
||||
void MotionService::subscribeToEvents() {
|
||||
controllerHandle_ = EventBus::subscribe<socket_message_ControllerData>(
|
||||
[this](const socket_message_ControllerData& data) { handleInput(data); });
|
||||
|
||||
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); });
|
||||
}
|
||||
void MotionService::begin() { body_state.updateFeet(KinConfig::default_feet_positions); }
|
||||
|
||||
void MotionService::handleAngles(const socket_message_AnglesData& data) {
|
||||
for (int i = 0; i < 12 && i < data.angles_count; i++) {
|
||||
@@ -58,7 +41,6 @@ void MotionService::handleMode(const socket_message_ModeData& data) {
|
||||
case MOTION_STATE::DEACTIVATED: setState(nullptr); break;
|
||||
default: setState(nullptr); break;
|
||||
}
|
||||
if (modeChangeCallback_) modeChangeCallback_(isActive());
|
||||
}
|
||||
|
||||
void MotionService::handleGestures(const gesture_t ges) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#include <peripherals/camera_service.h>
|
||||
#include <communication/webserver.h>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
namespace Camera {
|
||||
|
||||
static const char *const TAG = "CameraService";
|
||||
|
||||
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_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
|
||||
@@ -29,16 +30,18 @@ sensor_t *safe_sensor_get() {
|
||||
|
||||
void safe_sensor_return() { xSemaphoreGiveRecursive(cameraMutex); }
|
||||
|
||||
CameraService::CameraService() {}
|
||||
CameraService::CameraService()
|
||||
: protoEndpoint(CameraSettings_read, CameraSettings_update, this,
|
||||
API_REQUEST_EXTRACTOR(camera_settings, api_CameraSettings),
|
||||
API_RESPONSE_ASSIGNER(camera_settings, api_CameraSettings)),
|
||||
_persistence(CameraSettings_read, CameraSettings_update, this,
|
||||
CAMERA_SETTINGS_FILE, api_CameraSettings_fields, api_CameraSettings_size,
|
||||
CameraSettings_defaults()) {
|
||||
addUpdateHandler([&](const std::string &originId) { updateCamera(); }, false);
|
||||
}
|
||||
|
||||
esp_err_t CameraService::begin() {
|
||||
_settingsHandle = EventBus::subscribe<api_CameraSettings>(
|
||||
[this](const api_CameraSettings &settings) { onSettingsChanged(settings); });
|
||||
|
||||
api_CameraSettings initialSettings;
|
||||
if (EventBus::peek(initialSettings)) {
|
||||
onSettingsChanged(initialSettings);
|
||||
}
|
||||
_persistence.readFromFS();
|
||||
camera_config_t camera_config;
|
||||
camera_config.ledc_channel = LEDC_CHANNEL_0;
|
||||
camera_config.ledc_timer = LEDC_TIMER_0;
|
||||
@@ -63,7 +66,7 @@ esp_err_t CameraService::begin() {
|
||||
camera_config.xclk_freq_hz = 20000000;
|
||||
camera_config.pixel_format = PIXFORMAT_JPEG;
|
||||
|
||||
if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) > 0) {
|
||||
if (psramFound()) {
|
||||
camera_config.frame_size = FRAMESIZE_SVGA;
|
||||
camera_config.jpeg_quality = 10;
|
||||
camera_config.fb_location = CAMERA_FB_IN_PSRAM;
|
||||
@@ -75,7 +78,7 @@ esp_err_t CameraService::begin() {
|
||||
camera_config.fb_count = 1;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing camera");
|
||||
log_i("Initializing camera");
|
||||
esp_err_t err = esp_camera_init(&camera_config);
|
||||
if (err == ESP_OK)
|
||||
ESP_LOGI(TAG, "Camera probe successful");
|
||||
@@ -142,65 +145,39 @@ esp_err_t CameraService::cameraStream(httpd_req_t *request) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void CameraService::onSettingsChanged(const api_CameraSettings &newSettings) {
|
||||
_settings = newSettings;
|
||||
updateCamera();
|
||||
}
|
||||
|
||||
void CameraService::updateCamera() {
|
||||
ESP_LOGI(TAG, "Updating camera settings");
|
||||
ESP_LOGI("CameraSettings", "Updating camera settings");
|
||||
sensor_t *s = safe_sensor_get();
|
||||
if (!s) {
|
||||
ESP_LOGE(TAG, "Failed to update camera settings");
|
||||
ESP_LOGE("CameraSettings", "Failed to update camera settings");
|
||||
safe_sensor_return();
|
||||
return;
|
||||
}
|
||||
s->set_pixformat(s, static_cast<pixformat_t>(_settings.pixformat));
|
||||
s->set_framesize(s, static_cast<framesize_t>(_settings.framesize));
|
||||
s->set_brightness(s, _settings.brightness);
|
||||
s->set_contrast(s, _settings.contrast);
|
||||
s->set_saturation(s, _settings.saturation);
|
||||
s->set_sharpness(s, _settings.sharpness);
|
||||
s->set_denoise(s, _settings.denoise);
|
||||
s->set_gainceiling(s, static_cast<gainceiling_t>(_settings.gainceiling));
|
||||
s->set_quality(s, _settings.quality);
|
||||
s->set_colorbar(s, _settings.colorbar);
|
||||
s->set_awb_gain(s, _settings.awb_gain);
|
||||
s->set_wb_mode(s, _settings.wb_mode);
|
||||
s->set_aec2(s, _settings.aec2);
|
||||
s->set_ae_level(s, _settings.ae_level);
|
||||
s->set_aec_value(s, _settings.aec_value);
|
||||
s->set_agc_gain(s, _settings.agc_gain);
|
||||
s->set_bpc(s, _settings.bpc);
|
||||
s->set_wpc(s, _settings.wpc);
|
||||
s->set_special_effect(s, _settings.special_effect);
|
||||
s->set_raw_gma(s, _settings.raw_gma);
|
||||
s->set_lenc(s, _settings.lenc);
|
||||
s->set_hmirror(s, _settings.hmirror);
|
||||
s->set_vflip(s, _settings.vflip);
|
||||
s->set_dcw(s, _settings.dcw);
|
||||
s->set_pixformat(s, static_cast<pixformat_t>(state().pixformat));
|
||||
s->set_framesize(s, static_cast<framesize_t>(state().framesize));
|
||||
s->set_brightness(s, state().brightness);
|
||||
s->set_contrast(s, state().contrast);
|
||||
s->set_saturation(s, state().saturation);
|
||||
s->set_sharpness(s, state().sharpness);
|
||||
s->set_denoise(s, state().denoise);
|
||||
s->set_gainceiling(s, static_cast<gainceiling_t>(state().gainceiling));
|
||||
s->set_quality(s, state().quality);
|
||||
s->set_colorbar(s, state().colorbar);
|
||||
s->set_awb_gain(s, state().awb_gain);
|
||||
s->set_wb_mode(s, state().wb_mode);
|
||||
s->set_aec2(s, state().aec2);
|
||||
s->set_ae_level(s, state().ae_level);
|
||||
s->set_aec_value(s, state().aec_value);
|
||||
s->set_agc_gain(s, state().agc_gain);
|
||||
s->set_bpc(s, state().bpc);
|
||||
s->set_wpc(s, state().wpc);
|
||||
s->set_special_effect(s, state().special_effect);
|
||||
s->set_raw_gma(s, state().raw_gma);
|
||||
s->set_lenc(s, state().lenc);
|
||||
s->set_hmirror(s, state().hmirror);
|
||||
s->set_vflip(s, state().vflip);
|
||||
s->set_dcw(s, state().dcw);
|
||||
safe_sensor_return();
|
||||
}
|
||||
|
||||
esp_err_t CameraService::getSettings(httpd_req_t *request) {
|
||||
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) {
|
||||
if (protoReq->which_payload != api_Request_camera_settings_tag) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
EventBus::publish(protoReq->payload.camera_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);
|
||||
}
|
||||
|
||||
} // namespace Camera
|
||||
|
||||
@@ -1,31 +1,55 @@
|
||||
#include <peripherals/peripherals.h>
|
||||
#include <communication/webserver.h>
|
||||
|
||||
Peripherals::Peripherals() { _accessMutex = xSemaphoreCreateMutex(); }
|
||||
Peripherals::Peripherals()
|
||||
: protoEndpoint(PeripheralsConfiguration_read, PeripheralsConfiguration_update, this,
|
||||
API_REQUEST_EXTRACTOR(peripheral_settings, api_PeripheralSettings),
|
||||
API_RESPONSE_ASSIGNER(peripheral_settings, api_PeripheralSettings)),
|
||||
_persistence(PeripheralsConfiguration_read, PeripheralsConfiguration_update, this,
|
||||
PERIPHERAL_SETTINGS_FILE, api_PeripheralSettings_fields, api_PeripheralSettings_size,
|
||||
PeripheralsConfiguration_defaults()) {
|
||||
_accessMutex = xSemaphoreCreateMutex();
|
||||
addUpdateHandler([&](const std::string &originId) { updatePins(); }, false);
|
||||
}
|
||||
|
||||
void Peripherals::begin() {
|
||||
_settingsHandle = EventBus::subscribe<api_PeripheralSettings>(
|
||||
[this](const api_PeripheralSettings &settings) { onSettingsChanged(settings); });
|
||||
|
||||
api_PeripheralSettings initialSettings;
|
||||
if (EventBus::peek(initialSettings)) {
|
||||
onSettingsChanged(initialSettings);
|
||||
}
|
||||
_persistence.readFromFS();
|
||||
|
||||
updatePins();
|
||||
|
||||
#if FT_ENABLED(USE_ICM20948)
|
||||
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||
ICM_20948_SPI* icm20948 = new ICM_20948_SPI;
|
||||
#else
|
||||
ICM_20948_I2C* icm20948 = new ICM_20948_I2C;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// --- IMU ---
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||
if (!_imu.initialize()) ESP_LOGE("IMUService", "IMU initialize failed");
|
||||
if (!_imu.initialize(nullptr)) ESP_LOGE("Peripherals", "IMU initialize failed");
|
||||
#elif FT_ENABLED(USE_ICM20948)
|
||||
if (!_imu.initialize(icm20948)) ESP_LOGE("Peripherals", "IMU initialize failed (ICM20948)");
|
||||
#endif
|
||||
|
||||
// --- MAGNETOMETER ---
|
||||
#if FT_ENABLED(USE_HMC5883)
|
||||
if (!_mag.initialize()) ESP_LOGE("IMUService", "MAG initialize failed");
|
||||
if (!_mag.initialize(nullptr)) ESP_LOGE("Peripherals", "MAG initialize failed");
|
||||
#elif FT_ENABLED(USE_ICM20948)
|
||||
if (!_mag.initialize(icm20948)) ESP_LOGE("Peripherals", "MAG initialize failed (ICM20948)");
|
||||
#endif
|
||||
|
||||
// --- BMP ---
|
||||
#if FT_ENABLED(USE_BMP180)
|
||||
if (!_bmp.initialize()) ESP_LOGE("IMUService", "BMP initialize failed");
|
||||
if (!_bmp.initialize(nullptr)) ESP_LOGE("Peripherals", "BMP initialize failed");
|
||||
#endif
|
||||
|
||||
|
||||
// --- GESTURE ---
|
||||
#if FT_ENABLED(USE_PAJ7620U2)
|
||||
if (!_gesture.initialize()) ESP_LOGE("IMUService", "Gesture initialize failed");
|
||||
if (!_gesture.initialize(nullptr)) ESP_LOGE("Peripherals", "Gesture initialize failed");
|
||||
#endif
|
||||
|
||||
// --- SONAR ---
|
||||
#if FT_ENABLED(USE_USS)
|
||||
_left_sonar = std::make_unique<NewPing>(USS_LEFT_PIN, USS_LEFT_PIN, MAX_DISTANCE);
|
||||
_right_sonar = std::make_unique<NewPing>(USS_RIGHT_PIN, USS_RIGHT_PIN, MAX_DISTANCE);
|
||||
@@ -40,21 +64,14 @@ void Peripherals::update() {
|
||||
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() {
|
||||
if (i2c_active) {
|
||||
I2CBus::instance().end();
|
||||
}
|
||||
|
||||
if (_settings.sda != -1 && _settings.scl != -1) {
|
||||
esp_err_t err = I2CBus::instance().begin(static_cast<gpio_num_t>(_settings.sda),
|
||||
static_cast<gpio_num_t>(_settings.scl), _settings.frequency);
|
||||
if (state().sda != -1 && state().scl != -1) {
|
||||
esp_err_t err = I2CBus::instance().begin(static_cast<gpio_num_t>(state().sda),
|
||||
static_cast<gpio_num_t>(state().scl), state().frequency);
|
||||
i2c_active = (err == ESP_OK);
|
||||
}
|
||||
}
|
||||
@@ -79,7 +96,7 @@ void Peripherals::scanI2C(uint8_t lower, uint8_t higher) {
|
||||
}
|
||||
|
||||
void Peripherals::getIMUProto(socket_message_IMUData &data) {
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||
data.x = _imu.getAngleX();
|
||||
data.y = _imu.getAngleY();
|
||||
data.z = _imu.getAngleZ();
|
||||
@@ -97,37 +114,16 @@ void Peripherals::getIMUProto(socket_message_IMUData &data) {
|
||||
}
|
||||
|
||||
void Peripherals::getSettingsProto(socket_message_PeripheralSettingsData &data) {
|
||||
data.sda = _settings.sda;
|
||||
data.scl = _settings.scl;
|
||||
data.frequency = _settings.frequency;
|
||||
data.sda = state().sda;
|
||||
data.scl = state().scl;
|
||||
data.frequency = state().frequency;
|
||||
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 updated = false;
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||
beginTransaction();
|
||||
updated = _imu.update();
|
||||
endTransaction();
|
||||
@@ -137,7 +133,7 @@ bool Peripherals::readImu() {
|
||||
|
||||
bool Peripherals::readMag() {
|
||||
bool updated = false;
|
||||
#if FT_ENABLED(USE_HMC5883)
|
||||
#if FT_ENABLED(USE_HMC5883 || USE_ICM20948)
|
||||
beginTransaction();
|
||||
updated = _mag.update();
|
||||
endTransaction();
|
||||
@@ -175,7 +171,7 @@ void Peripherals::readSonar() {
|
||||
|
||||
float Peripherals::angleX() {
|
||||
return
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||
_imu.getAngleX();
|
||||
#else
|
||||
0;
|
||||
@@ -184,7 +180,7 @@ float Peripherals::angleX() {
|
||||
|
||||
float Peripherals::angleY() {
|
||||
return
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||
_imu.getAngleY();
|
||||
#else
|
||||
0;
|
||||
@@ -193,7 +189,7 @@ float Peripherals::angleY() {
|
||||
|
||||
float Peripherals::angleZ() {
|
||||
return
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||
_imu.getAngleZ();
|
||||
#else
|
||||
0;
|
||||
@@ -213,7 +209,7 @@ float Peripherals::leftDistance() { return _left_distance; }
|
||||
float Peripherals::rightDistance() { return _right_distance; }
|
||||
|
||||
bool Peripherals::calibrateIMU() {
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||
beginTransaction();
|
||||
bool result = _imu.calibrate();
|
||||
endTransaction();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+19
-135
@@ -1,43 +1,5 @@
|
||||
#include "system_service.h"
|
||||
#include <communication/webserver.h>
|
||||
#include <dirent.h>
|
||||
#include <esp_chip_info.h>
|
||||
#include <esp_flash.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_system.h>
|
||||
#include <esp_sleep.h>
|
||||
#include <soc/soc.h>
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6
|
||||
#include <driver/temperature_sensor.h>
|
||||
|
||||
static float temperatureRead() {
|
||||
static temperature_sensor_handle_t temp_sensor = nullptr;
|
||||
static bool initialized = false;
|
||||
|
||||
if (!initialized) {
|
||||
temperature_sensor_config_t temp_sensor_config = {
|
||||
.range_min = -10,
|
||||
.range_max = 80,
|
||||
.clk_src = TEMPERATURE_SENSOR_CLK_SRC_DEFAULT,
|
||||
};
|
||||
if (temperature_sensor_install(&temp_sensor_config, &temp_sensor) == ESP_OK) {
|
||||
temperature_sensor_enable(temp_sensor);
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (initialized && temp_sensor) {
|
||||
float temp = 0;
|
||||
if (temperature_sensor_get_celsius(temp_sensor, &temp) == ESP_OK) {
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
#else
|
||||
static inline float temperatureRead() { return 0.0f; }
|
||||
#endif
|
||||
|
||||
namespace system_service {
|
||||
|
||||
@@ -60,15 +22,12 @@ esp_err_t handleSleep(httpd_req_t *request) {
|
||||
|
||||
void reset() {
|
||||
ESP_LOGI(TAG, "Resetting device");
|
||||
DIR *dir = opendir(FS_CONFIG_DIRECTORY);
|
||||
if (dir) {
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
||||
std::string path = std::string(FS_CONFIG_DIRECTORY) + "/" + entry->d_name;
|
||||
remove(path.c_str());
|
||||
}
|
||||
closedir(dir);
|
||||
File root = ESP_FS.open(FS_CONFIG_DIRECTORY);
|
||||
File file;
|
||||
while (file = root.openNextFile()) {
|
||||
std::string path = file.path();
|
||||
file.close();
|
||||
ESP_FS.remove(path.c_str());
|
||||
}
|
||||
restart();
|
||||
}
|
||||
@@ -78,11 +37,11 @@ void restart() {
|
||||
[](void *pvParameters) {
|
||||
for (;;) {
|
||||
vTaskDelay(250 / portTICK_PERIOD_MS);
|
||||
mdns_free();
|
||||
MDNS.end();
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
WiFi.disconnect(true);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
esp_restart();
|
||||
ESP.restart();
|
||||
}
|
||||
},
|
||||
"Restart task", 4096, nullptr, 10, nullptr);
|
||||
@@ -93,7 +52,7 @@ void sleep() {
|
||||
[](void *pvParameters) {
|
||||
for (;;) {
|
||||
vTaskDelay(250 / portTICK_PERIOD_MS);
|
||||
mdns_free();
|
||||
MDNS.end();
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
WiFi.disconnect(true);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
@@ -113,97 +72,22 @@ void sleep() {
|
||||
ESP_LOGI(TAG, "Setting device to sleep");
|
||||
}
|
||||
|
||||
static const char *getChipModel() {
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
switch (chip_info.model) {
|
||||
case CHIP_ESP32: return "ESP32";
|
||||
case CHIP_ESP32S2: return "ESP32-S2";
|
||||
case CHIP_ESP32S3: return "ESP32-S3";
|
||||
case CHIP_ESP32C3: return "ESP32-C3";
|
||||
case CHIP_ESP32C2: return "ESP32-C2";
|
||||
case CHIP_ESP32C6: return "ESP32-C6";
|
||||
case CHIP_ESP32H2: return "ESP32-H2";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t getCpuFreqMHz() { return CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; }
|
||||
|
||||
static uint8_t getChipCores() {
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
return chip_info.cores;
|
||||
}
|
||||
|
||||
static uint8_t getChipRevision() {
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
return chip_info.revision;
|
||||
}
|
||||
|
||||
static uint32_t getFlashChipSize() {
|
||||
uint32_t size = 0;
|
||||
esp_flash_get_size(NULL, &size);
|
||||
return size;
|
||||
}
|
||||
|
||||
static uint32_t getFlashChipSpeed() {
|
||||
#if defined(CONFIG_ESPTOOLPY_FLASHFREQ_120M)
|
||||
return 120000000;
|
||||
#elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_80M)
|
||||
return 80000000;
|
||||
#elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_64M)
|
||||
return 64000000;
|
||||
#elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_60M)
|
||||
return 60000000;
|
||||
#elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_48M)
|
||||
return 48000000;
|
||||
#elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_40M)
|
||||
return 40000000;
|
||||
#elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_30M)
|
||||
return 30000000;
|
||||
#elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_26M)
|
||||
return 26000000;
|
||||
#elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_20M)
|
||||
return 20000000;
|
||||
#else
|
||||
return 80000000;
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t getSketchSize() {
|
||||
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||
if (running) {
|
||||
return running->size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t getFreeSketchSpace() {
|
||||
const esp_partition_t *next = esp_ota_get_next_update_partition(NULL);
|
||||
if (next) {
|
||||
return next->size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void getStaticSystemInformation(socket_message_StaticSystemInformation &info) {
|
||||
size_t fs_total = 0, fs_used = 0;
|
||||
esp_littlefs_info("spiffs", &fs_total, &fs_used);
|
||||
|
||||
info.esp_platform = (char *)ESP_PLATFORM_NAME;
|
||||
info.firmware_version = APP_VERSION;
|
||||
info.cpu_freq_mhz = getCpuFreqMHz();
|
||||
info.cpu_type = (char *)getChipModel();
|
||||
info.cpu_rev = getChipRevision();
|
||||
info.cpu_cores = getChipCores();
|
||||
info.sketch_size = getSketchSize();
|
||||
info.free_sketch_space = getFreeSketchSpace();
|
||||
info.sdk_version = (char *)esp_get_idf_version();
|
||||
info.arduino_version = "";
|
||||
info.flash_chip_size = getFlashChipSize();
|
||||
info.flash_chip_speed = getFlashChipSpeed();
|
||||
info.cpu_freq_mhz = ESP.getCpuFreqMHz();
|
||||
info.cpu_type = (char *)ESP.getChipModel();
|
||||
info.cpu_rev = ESP.getChipRevision();
|
||||
info.cpu_cores = ESP.getChipCores();
|
||||
info.sketch_size = ESP.getSketchSize();
|
||||
info.free_sketch_space = ESP.getFreeSketchSpace();
|
||||
info.sdk_version = (char *)ESP.getSdkVersion();
|
||||
info.arduino_version = ARDUINO_VERSION;
|
||||
info.flash_chip_size = ESP.getFlashChipSize();
|
||||
info.flash_chip_speed = ESP.getFlashChipSpeed();
|
||||
info.cpu_reset_reason = (char *)resetReason(esp_reset_reason());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,571 +0,0 @@
|
||||
#include <wifi/wifi_idf.h>
|
||||
|
||||
static const char* TAG = "WiFiIDF";
|
||||
|
||||
WiFiClass WiFi;
|
||||
|
||||
WiFiClass::WiFiClass()
|
||||
: _sta_netif(nullptr),
|
||||
_ap_netif(nullptr),
|
||||
_initialized(false),
|
||||
_autoReconnect(false),
|
||||
_persistent(false),
|
||||
_status(WL_DISCONNECTED),
|
||||
_mode(WIFI_MODE_NULL),
|
||||
_scanResult(nullptr),
|
||||
_scanCount(0),
|
||||
_scanStatus(-2),
|
||||
_useStaticIp(false) {}
|
||||
|
||||
WiFiClass::~WiFiClass() {
|
||||
if (_scanResult) {
|
||||
free(_scanResult);
|
||||
_scanResult = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool WiFiClass::init() {
|
||||
if (_initialized) return true;
|
||||
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
nvs_flash_erase();
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
|
||||
ret = esp_netif_init();
|
||||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
ret = esp_event_loop_create_default();
|
||||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "Event loop create failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
_sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
if (!_sta_netif) {
|
||||
_sta_netif = esp_netif_create_default_wifi_sta();
|
||||
}
|
||||
|
||||
_ap_netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
|
||||
if (!_ap_netif) {
|
||||
_ap_netif = esp_netif_create_default_wifi_ap();
|
||||
}
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_wifi_init(&cfg);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "esp_wifi_init failed: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WiFiClass::eventHandler, this, nullptr);
|
||||
esp_event_handler_instance_register(IP_EVENT, ESP_EVENT_ANY_ID, &WiFiClass::eventHandler, this, nullptr);
|
||||
|
||||
esp_wifi_set_storage(_persistent ? WIFI_STORAGE_FLASH : WIFI_STORAGE_RAM);
|
||||
|
||||
_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WiFiClass::eventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
|
||||
WiFiClass* self = static_cast<WiFiClass*>(arg);
|
||||
|
||||
if (event_base == WIFI_EVENT) {
|
||||
switch (event_id) {
|
||||
case WIFI_EVENT_STA_START: ESP_LOGI(TAG, "STA Started"); break;
|
||||
case WIFI_EVENT_STA_STOP:
|
||||
ESP_LOGI(TAG, "STA Stopped");
|
||||
self->_status = WL_DISCONNECTED;
|
||||
break;
|
||||
case WIFI_EVENT_STA_CONNECTED: ESP_LOGI(TAG, "STA Connected"); break;
|
||||
case WIFI_EVENT_STA_DISCONNECTED: {
|
||||
wifi_event_sta_disconnected_t* event = (wifi_event_sta_disconnected_t*)event_data;
|
||||
ESP_LOGI(TAG, "STA Disconnected, reason: %d", event->reason);
|
||||
self->_status = WL_DISCONNECTED;
|
||||
if (self->_autoReconnect) {
|
||||
esp_wifi_connect();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WIFI_EVENT_AP_START: ESP_LOGI(TAG, "AP Started"); break;
|
||||
case WIFI_EVENT_AP_STOP: ESP_LOGI(TAG, "AP Stopped"); break;
|
||||
case WIFI_EVENT_AP_STACONNECTED: {
|
||||
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*)event_data;
|
||||
ESP_LOGI(TAG, "Station connected, AID=%d", event->aid);
|
||||
break;
|
||||
}
|
||||
case WIFI_EVENT_AP_STADISCONNECTED: {
|
||||
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*)event_data;
|
||||
ESP_LOGI(TAG, "Station disconnected, AID=%d", event->aid);
|
||||
break;
|
||||
}
|
||||
case WIFI_EVENT_SCAN_DONE: {
|
||||
wifi_event_sta_scan_done_t* event = (wifi_event_sta_scan_done_t*)event_data;
|
||||
if (event->status == 0) {
|
||||
self->_scanCount = event->number;
|
||||
if (self->_scanResult) {
|
||||
free(self->_scanResult);
|
||||
}
|
||||
self->_scanResult = (wifi_ap_record_t*)malloc(sizeof(wifi_ap_record_t) * self->_scanCount);
|
||||
if (self->_scanResult) {
|
||||
esp_wifi_scan_get_ap_records(&self->_scanCount, self->_scanResult);
|
||||
}
|
||||
self->_scanStatus = self->_scanCount;
|
||||
} else {
|
||||
self->_scanStatus = -2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
self->dispatchEvent(event_id, event_data);
|
||||
} else if (event_base == IP_EVENT) {
|
||||
if (event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
|
||||
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
|
||||
self->_status = WL_CONNECTED;
|
||||
self->dispatchEvent(event_id + 1000, event_data);
|
||||
} else if (event_id == IP_EVENT_STA_LOST_IP) {
|
||||
ESP_LOGI(TAG, "Lost IP");
|
||||
self->_status = WL_DISCONNECTED;
|
||||
self->dispatchEvent(event_id + 1000, event_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiClass::dispatchEvent(int32_t event_id, void* event_data) {
|
||||
for (auto& handler : _eventHandlers) {
|
||||
if (handler.event_id == event_id || handler.event_id == -1) {
|
||||
handler.callback(event_id, event_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiClass::onEvent(WiFiEventCb callback, int32_t event_id) { _eventHandlers.push_back({event_id, callback}); }
|
||||
|
||||
bool WiFiClass::mode(wifi_mode_t m) {
|
||||
if (!_initialized) init();
|
||||
|
||||
if (_mode == m) return true;
|
||||
|
||||
esp_err_t err;
|
||||
if (_mode == WIFI_MODE_NULL) {
|
||||
err = esp_wifi_set_mode(m);
|
||||
if (err == ESP_OK) {
|
||||
err = esp_wifi_start();
|
||||
}
|
||||
} else if (m == WIFI_MODE_NULL) {
|
||||
err = esp_wifi_stop();
|
||||
} else {
|
||||
err = esp_wifi_set_mode(m);
|
||||
}
|
||||
|
||||
if (err == ESP_OK) {
|
||||
_mode = m;
|
||||
return true;
|
||||
}
|
||||
ESP_LOGE(TAG, "Failed to set mode: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
wifi_mode_t WiFiClass::getMode() {
|
||||
if (!_initialized) return WIFI_MODE_NULL;
|
||||
wifi_mode_t m;
|
||||
if (esp_wifi_get_mode(&m) == ESP_OK) {
|
||||
return m;
|
||||
}
|
||||
return WIFI_MODE_NULL;
|
||||
}
|
||||
|
||||
bool WiFiClass::begin(const char* ssid, const char* password, int32_t channel, const uint8_t* bssid) {
|
||||
if (!_initialized) init();
|
||||
|
||||
wifi_mode_t currentMode = getMode();
|
||||
if (currentMode == WIFI_MODE_NULL || currentMode == WIFI_MODE_AP) {
|
||||
mode(currentMode == WIFI_MODE_AP ? WIFI_MODE_APSTA : WIFI_MODE_STA);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
wifi_config_t wifi_config = {};
|
||||
strncpy((char*)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
|
||||
if (password) {
|
||||
strncpy((char*)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1);
|
||||
}
|
||||
if (channel > 0) {
|
||||
wifi_config.sta.channel = channel;
|
||||
}
|
||||
if (bssid) {
|
||||
memcpy(wifi_config.sta.bssid, bssid, 6);
|
||||
wifi_config.sta.bssid_set = true;
|
||||
}
|
||||
|
||||
wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
|
||||
wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
|
||||
wifi_config.sta.threshold.authmode = password && strlen(password) > 0 ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN;
|
||||
|
||||
esp_err_t err = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set STA config: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_useStaticIp) {
|
||||
esp_netif_dhcpc_stop(_sta_netif);
|
||||
esp_netif_ip_info_t ip_info = {};
|
||||
ip_info.ip = _sta_static_ip;
|
||||
ip_info.gw = _sta_static_gw;
|
||||
ip_info.netmask = _sta_static_sn;
|
||||
esp_netif_set_ip_info(_sta_netif, &ip_info);
|
||||
|
||||
if (static_cast<uint32_t>(_sta_static_dns1) != 0) {
|
||||
esp_netif_dns_info_t dns;
|
||||
dns.ip.u_addr.ip4 = _sta_static_dns1;
|
||||
dns.ip.type = ESP_IPADDR_TYPE_V4;
|
||||
esp_netif_set_dns_info(_sta_netif, ESP_NETIF_DNS_MAIN, &dns);
|
||||
}
|
||||
if (static_cast<uint32_t>(_sta_static_dns2) != 0) {
|
||||
esp_netif_dns_info_t dns;
|
||||
dns.ip.u_addr.ip4 = _sta_static_dns2;
|
||||
dns.ip.type = ESP_IPADDR_TYPE_V4;
|
||||
esp_netif_set_dns_info(_sta_netif, ESP_NETIF_DNS_BACKUP, &dns);
|
||||
}
|
||||
} else {
|
||||
esp_netif_dhcpc_start(_sta_netif);
|
||||
}
|
||||
|
||||
err = esp_wifi_connect();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to connect: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiFiClass::disconnect(bool wifiOff) {
|
||||
esp_err_t err = esp_wifi_disconnect();
|
||||
if (wifiOff) {
|
||||
wifi_mode_t m = getMode();
|
||||
if (m == WIFI_MODE_APSTA) {
|
||||
mode(WIFI_MODE_AP);
|
||||
} else if (m == WIFI_MODE_STA) {
|
||||
mode(WIFI_MODE_NULL);
|
||||
}
|
||||
}
|
||||
_status = WL_DISCONNECTED;
|
||||
return err == ESP_OK;
|
||||
}
|
||||
|
||||
bool WiFiClass::reconnect() { return esp_wifi_connect() == ESP_OK; }
|
||||
|
||||
bool WiFiClass::config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1, IPAddress dns2) {
|
||||
_sta_static_ip = local_ip;
|
||||
_sta_static_gw = gateway;
|
||||
_sta_static_sn = subnet;
|
||||
_sta_static_dns1 = dns1;
|
||||
_sta_static_dns2 = dns2;
|
||||
_useStaticIp = (static_cast<uint32_t>(local_ip) != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiFiClass::setHostname(const char* hostname) {
|
||||
_hostname = hostname;
|
||||
if (_sta_netif) {
|
||||
return esp_netif_set_hostname(_sta_netif, hostname) == ESP_OK;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* WiFiClass::getHostname() {
|
||||
const char* hostname = nullptr;
|
||||
if (_sta_netif) {
|
||||
esp_netif_get_hostname(_sta_netif, &hostname);
|
||||
}
|
||||
return hostname ? hostname : _hostname.c_str();
|
||||
}
|
||||
|
||||
wl_status_t WiFiClass::status() { return _status; }
|
||||
|
||||
bool WiFiClass::isConnected() { return _status == WL_CONNECTED; }
|
||||
|
||||
IPAddress WiFiClass::localIP() {
|
||||
esp_netif_ip_info_t ip_info;
|
||||
if (_sta_netif && esp_netif_get_ip_info(_sta_netif, &ip_info) == ESP_OK) {
|
||||
return IPAddress(ip_info.ip);
|
||||
}
|
||||
return IPAddress();
|
||||
}
|
||||
|
||||
IPAddress WiFiClass::subnetMask() {
|
||||
esp_netif_ip_info_t ip_info;
|
||||
if (_sta_netif && esp_netif_get_ip_info(_sta_netif, &ip_info) == ESP_OK) {
|
||||
return IPAddress(ip_info.netmask);
|
||||
}
|
||||
return IPAddress();
|
||||
}
|
||||
|
||||
IPAddress WiFiClass::gatewayIP() {
|
||||
esp_netif_ip_info_t ip_info;
|
||||
if (_sta_netif && esp_netif_get_ip_info(_sta_netif, &ip_info) == ESP_OK) {
|
||||
return IPAddress(ip_info.gw);
|
||||
}
|
||||
return IPAddress();
|
||||
}
|
||||
|
||||
IPAddress WiFiClass::dnsIP(uint8_t dns_no) {
|
||||
esp_netif_dns_info_t dns;
|
||||
esp_netif_dns_type_t type = dns_no == 0 ? ESP_NETIF_DNS_MAIN : ESP_NETIF_DNS_BACKUP;
|
||||
if (_sta_netif && esp_netif_get_dns_info(_sta_netif, type, &dns) == ESP_OK) {
|
||||
return IPAddress(dns.ip.u_addr.ip4);
|
||||
}
|
||||
return IPAddress();
|
||||
}
|
||||
|
||||
std::string WiFiClass::macAddress() {
|
||||
uint8_t mac[6];
|
||||
char buf[18];
|
||||
esp_wifi_get_mac(WIFI_IF_STA, mac);
|
||||
snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string WiFiClass::SSID() {
|
||||
wifi_ap_record_t info;
|
||||
if (esp_wifi_sta_get_ap_info(&info) == ESP_OK) {
|
||||
return std::string((char*)info.ssid);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string WiFiClass::BSSIDstr() {
|
||||
wifi_ap_record_t info;
|
||||
if (esp_wifi_sta_get_ap_info(&info) == ESP_OK) {
|
||||
char buf[18];
|
||||
snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X", info.bssid[0], info.bssid[1], info.bssid[2],
|
||||
info.bssid[3], info.bssid[4], info.bssid[5]);
|
||||
return std::string(buf);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int32_t WiFiClass::RSSI() {
|
||||
wifi_ap_record_t info;
|
||||
if (esp_wifi_sta_get_ap_info(&info) == ESP_OK) {
|
||||
return info.rssi;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t WiFiClass::channel() {
|
||||
wifi_ap_record_t info;
|
||||
if (esp_wifi_sta_get_ap_info(&info) == ESP_OK) {
|
||||
return info.primary;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int16_t WiFiClass::scanNetworks(bool async) {
|
||||
if (!_initialized) init();
|
||||
|
||||
wifi_mode_t currentMode = getMode();
|
||||
if (currentMode == WIFI_MODE_NULL || currentMode == WIFI_MODE_AP) {
|
||||
mode(currentMode == WIFI_MODE_AP ? WIFI_MODE_APSTA : WIFI_MODE_STA);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
_scanStatus = -1;
|
||||
|
||||
wifi_scan_config_t scan_config = {};
|
||||
scan_config.show_hidden = true;
|
||||
|
||||
esp_err_t err = esp_wifi_scan_start(&scan_config, !async);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Scan start failed: %s", esp_err_to_name(err));
|
||||
_scanStatus = -2;
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (!async) {
|
||||
if (_scanResult) {
|
||||
free(_scanResult);
|
||||
_scanResult = nullptr;
|
||||
}
|
||||
esp_wifi_scan_get_ap_num(&_scanCount);
|
||||
_scanResult = (wifi_ap_record_t*)malloc(sizeof(wifi_ap_record_t) * _scanCount);
|
||||
if (_scanResult) {
|
||||
esp_wifi_scan_get_ap_records(&_scanCount, _scanResult);
|
||||
}
|
||||
_scanStatus = _scanCount;
|
||||
return _scanCount;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t WiFiClass::scanComplete() { return _scanStatus; }
|
||||
|
||||
void WiFiClass::scanDelete() {
|
||||
if (_scanResult) {
|
||||
free(_scanResult);
|
||||
_scanResult = nullptr;
|
||||
}
|
||||
_scanCount = 0;
|
||||
_scanStatus = -2;
|
||||
}
|
||||
|
||||
std::string WiFiClass::SSID(uint8_t i) {
|
||||
if (i < _scanCount && _scanResult) {
|
||||
return std::string((char*)_scanResult[i].ssid);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int32_t WiFiClass::RSSI(uint8_t i) {
|
||||
if (i < _scanCount && _scanResult) {
|
||||
return _scanResult[i].rssi;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
wifi_enc_type_t WiFiClass::encryptionType(uint8_t i) {
|
||||
if (i < _scanCount && _scanResult) {
|
||||
switch (_scanResult[i].authmode) {
|
||||
case WIFI_AUTH_OPEN: return WIFI_AUTH_OPEN_IDF;
|
||||
case WIFI_AUTH_WEP: return WIFI_AUTH_WEP_IDF;
|
||||
case WIFI_AUTH_WPA_PSK: return WIFI_AUTH_WPA_PSK_IDF;
|
||||
default: return WIFI_AUTH_WPA2_PSK_IDF;
|
||||
}
|
||||
}
|
||||
return WIFI_AUTH_OPEN_IDF;
|
||||
}
|
||||
|
||||
std::string WiFiClass::BSSIDstr(uint8_t i) {
|
||||
if (i < _scanCount && _scanResult) {
|
||||
char buf[18];
|
||||
snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X", _scanResult[i].bssid[0], _scanResult[i].bssid[1],
|
||||
_scanResult[i].bssid[2], _scanResult[i].bssid[3], _scanResult[i].bssid[4], _scanResult[i].bssid[5]);
|
||||
return std::string(buf);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int32_t WiFiClass::channel(uint8_t i) {
|
||||
if (i < _scanCount && _scanResult) {
|
||||
return _scanResult[i].primary;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WiFiClass::getNetworkInfo(uint8_t i, std::string& ssid, uint8_t& encType, int32_t& rssi, uint8_t*& bssid,
|
||||
int32_t& ch) {
|
||||
if (i < _scanCount && _scanResult) {
|
||||
ssid = std::string((char*)_scanResult[i].ssid);
|
||||
encType = static_cast<uint8_t>(encryptionType(i));
|
||||
rssi = _scanResult[i].rssi;
|
||||
bssid = _scanResult[i].bssid;
|
||||
ch = _scanResult[i].primary;
|
||||
}
|
||||
}
|
||||
|
||||
bool WiFiClass::softAP(const char* ssid, const char* password, int channel, bool ssid_hidden, int max_connection) {
|
||||
if (!_initialized) init();
|
||||
|
||||
wifi_mode_t currentMode = getMode();
|
||||
if (currentMode == WIFI_MODE_NULL || currentMode == WIFI_MODE_STA) {
|
||||
mode(currentMode == WIFI_MODE_STA ? WIFI_MODE_APSTA : WIFI_MODE_AP);
|
||||
}
|
||||
|
||||
wifi_config_t wifi_config = {};
|
||||
strncpy((char*)wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid) - 1);
|
||||
wifi_config.ap.ssid_len = strlen(ssid);
|
||||
wifi_config.ap.channel = channel;
|
||||
wifi_config.ap.ssid_hidden = ssid_hidden ? 1 : 0;
|
||||
wifi_config.ap.max_connection = max_connection;
|
||||
|
||||
if (password && strlen(password) > 0) {
|
||||
strncpy((char*)wifi_config.ap.password, password, sizeof(wifi_config.ap.password) - 1);
|
||||
wifi_config.ap.authmode = WIFI_AUTH_WPA2_PSK;
|
||||
} else {
|
||||
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_wifi_set_config(WIFI_IF_AP, &wifi_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set AP config: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiFiClass::softAPConfig(IPAddress local_ip, IPAddress gateway, IPAddress subnet) {
|
||||
if (!_ap_netif) return false;
|
||||
|
||||
esp_netif_dhcps_stop(_ap_netif);
|
||||
|
||||
esp_netif_ip_info_t ip_info = {};
|
||||
ip_info.ip = local_ip;
|
||||
ip_info.gw = gateway;
|
||||
ip_info.netmask = subnet;
|
||||
|
||||
esp_err_t err = esp_netif_set_ip_info(_ap_netif, &ip_info);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set AP IP info: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_netif_dhcps_start(_ap_netif);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiFiClass::softAPdisconnect(bool wifiOff) {
|
||||
wifi_mode_t m = getMode();
|
||||
if (wifiOff) {
|
||||
if (m == WIFI_MODE_APSTA) {
|
||||
return mode(WIFI_MODE_STA);
|
||||
} else if (m == WIFI_MODE_AP) {
|
||||
return mode(WIFI_MODE_NULL);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
IPAddress WiFiClass::softAPIP() {
|
||||
esp_netif_ip_info_t ip_info;
|
||||
if (_ap_netif && esp_netif_get_ip_info(_ap_netif, &ip_info) == ESP_OK) {
|
||||
return IPAddress(ip_info.ip);
|
||||
}
|
||||
return IPAddress();
|
||||
}
|
||||
|
||||
std::string WiFiClass::softAPmacAddress() {
|
||||
uint8_t mac[6];
|
||||
char buf[18];
|
||||
esp_wifi_get_mac(WIFI_IF_AP, mac);
|
||||
snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
uint8_t WiFiClass::softAPgetStationNum() {
|
||||
wifi_sta_list_t sta_list;
|
||||
if (esp_wifi_ap_get_sta_list(&sta_list) == ESP_OK) {
|
||||
return sta_list.num;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool WiFiClass::setAutoReconnect(bool autoReconnect) {
|
||||
_autoReconnect = autoReconnect;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiFiClass::persistent(bool persistent) {
|
||||
_persistent = persistent;
|
||||
if (_initialized) {
|
||||
return esp_wifi_set_storage(persistent ? WIFI_STORAGE_FLASH : WIFI_STORAGE_RAM) == ESP_OK;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiFiClass::setTxPower(int8_t power) { return esp_wifi_set_max_tx_power(power * 4) == ESP_OK; }
|
||||
+100
-87
@@ -1,72 +1,45 @@
|
||||
#include <wifi_service.h>
|
||||
#include <communication/webserver.h>
|
||||
|
||||
WiFiService::WiFiService() : _initialized(false), _lastConnectionAttempt(0), _stopping(false) {}
|
||||
WiFiService::WiFiService()
|
||||
: _persistence(WiFiSettings_read, WiFiSettings_update, this, WIFI_SETTINGS_FILE,
|
||||
api_WifiSettings_fields, api_WifiSettings_size, WiFiSettings_defaults()),
|
||||
protoEndpoint(WiFiSettings_read, WiFiSettings_update, this,
|
||||
API_REQUEST_EXTRACTOR(wifi_settings, api_WifiSettings),
|
||||
API_RESPONSE_ASSIGNER(wifi_settings, api_WifiSettings)) {
|
||||
addUpdateHandler([&](const std::string &originId) { reconfigureWiFiConnection(); }, false);
|
||||
}
|
||||
|
||||
WiFiService::~WiFiService() {}
|
||||
|
||||
void WiFiService::begin() {
|
||||
WiFi.mode(WIFI_MODE_STA);
|
||||
|
||||
WiFi.persistent(false);
|
||||
WiFi.setAutoReconnect(false);
|
||||
|
||||
WiFi.onEvent([this](int32_t event, void *data) { this->onStationModeDisconnected(event, data); },
|
||||
WIFI_EVENT_STA_DISCONNECTED);
|
||||
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(std::bind(&WiFiService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
|
||||
WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
|
||||
WiFi.onEvent(std::bind(&WiFiService::onStationModeStop, this, std::placeholders::_1, std::placeholders::_2),
|
||||
WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_STOP);
|
||||
|
||||
_settingsHandle = EventBus::subscribe<api_WifiSettings>(
|
||||
[this](const api_WifiSettings &settings) { onSettingsChanged(settings); });
|
||||
WiFi.onEvent(onStationModeGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
_persistence.readFromFS();
|
||||
reconfigureWiFiConnection();
|
||||
|
||||
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();
|
||||
if (state().wifi_networks_count == 1) {
|
||||
configureNetwork(state().wifi_networks[0]);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiService::reconfigureWiFiConnection() {
|
||||
_lastConnectionAttempt = 0;
|
||||
|
||||
if (WiFi.disconnect(true)) _stopping = true;
|
||||
}
|
||||
|
||||
void WiFiService::selectNetwork(uint32_t index) {
|
||||
api_WifiSettings settings = getSettings();
|
||||
if (index >= settings.wifi_networks_count) return;
|
||||
|
||||
settings.selected_network = index;
|
||||
EventBus::publish(settings, "WiFiService");
|
||||
|
||||
reconfigureWiFiConnection();
|
||||
}
|
||||
|
||||
void WiFiService::loop() { EXECUTE_EVERY_N_MS(reconnectDelay, manageSTA()); }
|
||||
|
||||
esp_err_t WiFiService::handleScan(httpd_req_t *request) {
|
||||
@@ -74,6 +47,7 @@ esp_err_t WiFiService::handleScan(httpd_req_t *request) {
|
||||
WiFi.scanDelete();
|
||||
WiFi.scanNetworks(true);
|
||||
}
|
||||
// Return 202 with empty_message payload (no pointer fields to encode)
|
||||
api_Response response = api_Response_init_zero;
|
||||
response.status_code = 202;
|
||||
response.which_payload = api_Response_empty_message_tag;
|
||||
@@ -83,6 +57,7 @@ esp_err_t WiFiService::handleScan(httpd_req_t *request) {
|
||||
esp_err_t WiFiService::getNetworks(httpd_req_t *request) {
|
||||
int numNetworks = WiFi.scanComplete();
|
||||
if (numNetworks == -1) {
|
||||
// Scan in progress - return 202 with empty_message payload
|
||||
api_Response response = api_Response_init_zero;
|
||||
response.status_code = 202;
|
||||
response.which_payload = api_Response_empty_message_tag;
|
||||
@@ -91,8 +66,10 @@ esp_err_t WiFiService::getNetworks(httpd_req_t *request) {
|
||||
return handleScan(request);
|
||||
}
|
||||
|
||||
// Limit to 20 networks max
|
||||
size_t count = (numNetworks > 20) ? 20 : static_cast<size_t>(numNetworks);
|
||||
|
||||
// Allocate networks array on stack (pointer type in proto)
|
||||
api_WifiNetworkScan networks[20] = {};
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
@@ -113,14 +90,11 @@ esp_err_t WiFiService::getNetworks(httpd_req_t *request) {
|
||||
}
|
||||
|
||||
void WiFiService::setupMDNS(const char *hostname) {
|
||||
mdns_init();
|
||||
api_WifiSettings settings = getSettings();
|
||||
mdns_hostname_set(settings.hostname);
|
||||
mdns_instance_name_set(hostname);
|
||||
mdns_service_add(nullptr, "_http", "_tcp", 80, nullptr, 0);
|
||||
mdns_service_add(nullptr, "_ws", "_tcp", 80, nullptr, 0);
|
||||
mdns_txt_item_t txtData = {"Firmware Version", APP_VERSION};
|
||||
mdns_service_txt_set("_http", "_tcp", &txtData, 1);
|
||||
MDNS.begin(state().hostname);
|
||||
MDNS.setInstanceName(hostname);
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
MDNS.addService("ws", "tcp", 80);
|
||||
MDNS.addServiceTxt("http", "tcp", "Firmware Version", APP_VERSION);
|
||||
}
|
||||
|
||||
esp_err_t WiFiService::getNetworkStatus(httpd_req_t *request) {
|
||||
@@ -140,6 +114,7 @@ esp_err_t WiFiService::getNetworkStatus(httpd_req_t *request) {
|
||||
wifiStatus.channel = WiFi.channel();
|
||||
wifiStatus.subnet_mask = static_cast<uint32_t>(WiFi.subnetMask());
|
||||
wifiStatus.gateway_ip = static_cast<uint32_t>(WiFi.gatewayIP());
|
||||
|
||||
IPAddress dnsIP1 = WiFi.dnsIP(0);
|
||||
IPAddress dnsIP2 = WiFi.dnsIP(1);
|
||||
if (dnsIP1 != IPAddress(0, 0, 0, 0)) {
|
||||
@@ -154,61 +129,99 @@ esp_err_t WiFiService::getNetworkStatus(httpd_req_t *request) {
|
||||
}
|
||||
|
||||
void WiFiService::manageSTA() {
|
||||
api_WifiSettings settings = getSettings();
|
||||
if (WiFi.isConnected() || settings.wifi_networks_count == 0) return;
|
||||
wifi_mode_t mode = WiFi.getMode();
|
||||
if (mode == WIFI_MODE_NULL || mode == WIFI_MODE_AP) return;
|
||||
if (WiFi.isConnected() || state().wifi_networks_count == 0) return;
|
||||
if ((WiFi.getMode() & WIFI_STA) == 0) connectToWiFi();
|
||||
}
|
||||
|
||||
static uint32_t startTime = 0;
|
||||
static bool attempted = false;
|
||||
void WiFiService::connectToWiFi() {
|
||||
int scanResult = WiFi.scanNetworks();
|
||||
if (scanResult == WIFI_SCAN_FAILED) {
|
||||
ESP_LOGE("WiFiSettingsService", "WiFi scan failed.");
|
||||
} else if (scanResult == 0) {
|
||||
ESP_LOGI("WiFiSettingsService", "No networks found.");
|
||||
} else {
|
||||
ESP_LOGI("WiFiSettingsService", "%d networks found.", scanResult);
|
||||
|
||||
if (startTime == 0) {
|
||||
startTime = esp_timer_get_time() / 1000;
|
||||
return;
|
||||
}
|
||||
WiFiNetwork *bestNetwork = nullptr;
|
||||
int32_t bestNetworkDb = FACTORY_WIFI_RSSI_THRESHOLD;
|
||||
|
||||
uint32_t now = esp_timer_get_time() / 1000;
|
||||
if (now - startTime < 3000) return;
|
||||
for (int i = 0; i < scanResult; ++i) {
|
||||
String ssid_scan;
|
||||
int32_t rssi_scan;
|
||||
uint8_t sec_scan;
|
||||
uint8_t *BSSID_scan;
|
||||
int32_t chan_scan;
|
||||
|
||||
if (!attempted && settings.wifi_networks_count > 0) {
|
||||
attempted = true;
|
||||
uint32_t idx = settings.selected_network;
|
||||
if (idx >= settings.wifi_networks_count) idx = 0;
|
||||
ESP_LOGI(TAG, "Connecting to: %s", settings.wifi_networks[idx].ssid);
|
||||
configureNetwork(settings.wifi_networks[idx]);
|
||||
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan);
|
||||
|
||||
for (pb_size_t j = 0; j < state().wifi_networks_count; j++) {
|
||||
WiFiNetwork &network = state().wifi_networks[j];
|
||||
if (ssid_scan == network.ssid) {
|
||||
if (rssi_scan >= FACTORY_WIFI_RSSI_THRESHOLD) {
|
||||
// Network is available
|
||||
}
|
||||
if (rssi_scan > bestNetworkDb) {
|
||||
bestNetworkDb = rssi_scan;
|
||||
bestNetwork = &network;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!state().priority_rssi) {
|
||||
for (pb_size_t j = 0; j < state().wifi_networks_count; j++) {
|
||||
WiFiNetwork &network = state().wifi_networks[j];
|
||||
// Check if this network was found in scan
|
||||
for (int i = 0; i < scanResult; ++i) {
|
||||
if (WiFi.SSID(i) == network.ssid) {
|
||||
ESP_LOGI("WiFiSettingsService", "Connecting to first available network: %s", network.ssid);
|
||||
configureNetwork(network);
|
||||
WiFi.scanDelete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (bestNetwork) {
|
||||
ESP_LOGI("WiFiSettingsService", "Connecting to strongest network: %s", bestNetwork->ssid);
|
||||
configureNetwork(*bestNetwork);
|
||||
} else {
|
||||
ESP_LOGI("WiFiSettingsService", "No known networks found.");
|
||||
}
|
||||
|
||||
WiFi.scanDelete();
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiService::configureNetwork(const WiFiNetwork &network) {
|
||||
void WiFiService::configureNetwork(WiFiNetwork &network) {
|
||||
if (network.static_ip_config) {
|
||||
WiFi.config(IPAddress(network.local_ip), IPAddress(network.gateway_ip), IPAddress(network.subnet_mask),
|
||||
IPAddress(network.dns_ip_1), IPAddress(network.dns_ip_2));
|
||||
WiFi.config(IPAddress(network.local_ip), IPAddress(network.gateway_ip),
|
||||
IPAddress(network.subnet_mask), IPAddress(network.dns_ip_1), IPAddress(network.dns_ip_2));
|
||||
} else {
|
||||
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(state().hostname);
|
||||
|
||||
WiFi.begin(network.ssid, network.password);
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
WiFi.setTxPower(8);
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm);
|
||||
#endif
|
||||
}
|
||||
|
||||
void WiFiService::onStationModeDisconnected(int32_t event, void *event_data) {
|
||||
void WiFiService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
WiFi.disconnect(true);
|
||||
wifi_event_sta_disconnected_t *info = static_cast<wifi_event_sta_disconnected_t *>(event_data);
|
||||
ESP_LOGI(TAG, "WiFi Disconnected. Reason code=%d", info ? info->reason : 0);
|
||||
ESP_LOGI("WiFiStatus", "WiFi Disconnected. Reason code=%d", info.wifi_sta_disconnected.reason);
|
||||
}
|
||||
|
||||
void WiFiService::onStationModeStop(int32_t event, void *event_data) {
|
||||
void WiFiService::onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
if (_stopping) {
|
||||
_lastConnectionAttempt = 0;
|
||||
_stopping = false;
|
||||
}
|
||||
ESP_LOGI(TAG, "WiFi STA stopped.");
|
||||
ESP_LOGI("WiFiStatus", "WiFi Connected.");
|
||||
}
|
||||
|
||||
void WiFiService::onStationModeGotIP(int32_t event, void *event_data) {
|
||||
ESP_LOGI(TAG, "WiFi Got IP. localIP=%s, hostName=%s", WiFi.localIP().toString().c_str(), WiFi.getHostname());
|
||||
void WiFiService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
ESP_LOGI("WiFiStatus", "WiFi Got IP. localIP=%s, hostName=%s", WiFi.localIP().toString().c_str(),
|
||||
WiFi.getHostname());
|
||||
}
|
||||
|
||||
+2
-25
@@ -1,14 +1,4 @@
|
||||
#include "www_mount.hpp"
|
||||
#include <cstring>
|
||||
|
||||
static const WebAsset* findAsset(const char* uri) {
|
||||
for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) {
|
||||
if (strcmp(WWW_ASSETS[i].uri, uri) == 0) {
|
||||
return &WWW_ASSETS[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static esp_err_t web_send(httpd_req_t* req, const WebAsset& asset) {
|
||||
httpd_resp_set_status(req, "200 OK");
|
||||
@@ -17,11 +7,11 @@ static esp_err_t web_send(httpd_req_t* req, const WebAsset& asset) {
|
||||
if (WWW_OPT.add_vary) httpd_resp_set_hdr(req, "Vary", "Accept-Encoding");
|
||||
|
||||
char cc[64];
|
||||
snprintf(cc, sizeof(cc), "public, immutable, max-age=%lu", (unsigned long)WWW_OPT.max_age);
|
||||
snprintf(cc, sizeof(cc), "public, immutable, max-age=%u", WWW_OPT.max_age);
|
||||
httpd_resp_set_hdr(req, "Cache-Control", cc);
|
||||
|
||||
char et[34];
|
||||
snprintf(et, sizeof(et), "\"%08lx\"", (unsigned long)asset.etag);
|
||||
snprintf(et, sizeof(et), "\"%08x\"", asset.etag);
|
||||
httpd_resp_set_hdr(req, "ETag", et);
|
||||
|
||||
return httpd_resp_send(req, (const char*)asset.data, asset.len);
|
||||
@@ -33,16 +23,3 @@ void mountStaticAssets(WebServer& server) {
|
||||
server.on(a->uri, HTTP_GET, [a](httpd_req_t* req) { return web_send(req, *a); });
|
||||
}
|
||||
}
|
||||
|
||||
void mountSpaFallback(WebServer& server) {
|
||||
const WebAsset* indexAsset = findAsset(WWW_OPT.default_uri);
|
||||
if (indexAsset) {
|
||||
server.on("/*", HTTP_GET, [indexAsset](httpd_req_t* req) {
|
||||
if (strncmp(req->uri, "/api/", 5) == 0) {
|
||||
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Not found");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return web_send(req, *indexAsset);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ message WifiSettings {
|
||||
string hostname = 1;
|
||||
bool priority_rssi = 2;
|
||||
repeated WifiNetwork wifi_networks = 3; // max 5 networks
|
||||
uint32 selected_network = 4;
|
||||
}
|
||||
|
||||
message WifiSettingsRequest {}
|
||||
|
||||
+22
-14
@@ -40,13 +40,14 @@ build_flags=
|
||||
|
||||
[env:esp32-wroom-camera]
|
||||
board = esp32-s3-devkitc-1
|
||||
board_build.flash_mode = qio
|
||||
board_build.arduino.memory_type = qio_opi
|
||||
board_upload.flash_size = 8MB
|
||||
upload_speed = 921600
|
||||
board_build.partitions = esp32/partition_table/default_8MB.csv
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-DBOARD_HAS_PSRAM
|
||||
-mfix-esp32-psram-cache-issue
|
||||
-D USE_CAMERA=1
|
||||
-D CAMERA_MODEL_ESP32S3_EYE=1
|
||||
-D WS2812_PIN=48
|
||||
@@ -54,6 +55,10 @@ build_flags =
|
||||
-D USS_RIGHT_PIN=14
|
||||
-D SDA_PIN=47
|
||||
-D SCL_PIN=21
|
||||
-D SPI_SCK=41
|
||||
-D SPI_MISO=19
|
||||
-D SPI_MOSI=20
|
||||
-D ICM20948_SPI_CS=2 # Only needed if ICM20948 is in SPI mode
|
||||
|
||||
[env:seeed-xiao-esp32s3]
|
||||
platform = espressif32
|
||||
@@ -79,15 +84,17 @@ build_flags =
|
||||
|
||||
[env]
|
||||
platform = espressif32 @ 6.8.1
|
||||
framework = espidf
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
monitor_filters =
|
||||
direct
|
||||
esp32_exception_decoder
|
||||
monitor_filters =
|
||||
esp32_exception_decoder
|
||||
default
|
||||
colorize
|
||||
build_flags =
|
||||
${factory_settings.build_flags}
|
||||
${features.build_flags}
|
||||
${build_settings.build_flags}
|
||||
-D SPI_PORT=SPI # Define which spi port to use for external components
|
||||
-D CORE_DEBUG_LEVEL=4
|
||||
-D register=
|
||||
-std=gnu++2a
|
||||
@@ -96,25 +103,26 @@ build_flags =
|
||||
-fdata-sections
|
||||
-Wl,--gc-sections
|
||||
-I submodules/nanopb
|
||||
-Wno-missing-braces
|
||||
-Wno-format
|
||||
-D CONFIG_HTTPD_WS_SUPPORT=1
|
||||
-Wno-missing-braces ; Added to suppress protobufs extra nested braces causing warning
|
||||
build_unflags = -std=gnu++11
|
||||
build_src_filter =
|
||||
+<*>
|
||||
+<../../submodules/nanopb/pb_*.c>
|
||||
build_src_flags =
|
||||
-Wformat=2
|
||||
-Wformat-truncation
|
||||
-Wstack-usage=4096
|
||||
-Wno-format
|
||||
test_ignore = test_embedded
|
||||
board_build.filesystem = littlefs
|
||||
board_build.embed_txtfiles =
|
||||
board_build.sdkconfig_defaults = esp32/sdkconfig.defaults
|
||||
lib_deps =
|
||||
lib_ldf_mode = deep
|
||||
lib_compat_mode = strict
|
||||
ArduinoJson@>=7.0.0
|
||||
teckel12/NewPing@^1.9.7
|
||||
FastLED@3.5.0
|
||||
sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@^1.3.2
|
||||
extra_scripts =
|
||||
pre:esp32/scripts/pre_build.py
|
||||
pre:esp32/scripts/build_app.py
|
||||
lib_compat_mode = strict
|
||||
; debug_tool = esp-builtin
|
||||
; debug_init_break =
|
||||
; upload_port = COM[13] # Only use this when upload port is not correctly detected due to multiple COM ports attached.
|
||||
; upload_port = COM[13] # Only use this when upload port is not correctly detected due to multiple COM ports attached.
|
||||
@@ -1,40 +0,0 @@
|
||||
# HTTP Server WebSocket support
|
||||
CONFIG_HTTPD_WS_SUPPORT=y
|
||||
CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048
|
||||
CONFIG_HTTPD_MAX_URI_LEN=1024
|
||||
|
||||
# mDNS
|
||||
CONFIG_MDNS_MAX_SERVICES=10
|
||||
|
||||
# LittleFS
|
||||
CONFIG_LITTLEFS_MAX_PARTITIONS=3
|
||||
|
||||
#
|
||||
# ESP System Settings
|
||||
#
|
||||
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set
|
||||
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160 is not set
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
|
||||
|
||||
# Main task stack
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=4096
|
||||
|
||||
# PSRAM for S3
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_OCT=y
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
|
||||
# DSP library
|
||||
CONFIG_DSP_MAX_FFT_SIZE_4096=y
|
||||
|
||||
# Compiler
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
|
||||
# Partition table
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
|
||||
# CONFIG_FATFS_LFN_NONE is not set
|
||||
# CONFIG_FATFS_LFN_HEAP is not set
|
||||
CONFIG_FATFS_LFN_STACK=y
|
||||
Reference in New Issue
Block a user