⚡ Adds event bus
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <template/stateful_service.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
#include <event_bus/event_bus.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>
|
||||
|
||||
class APService : public StatefulService<APSettings> {
|
||||
class APService {
|
||||
public:
|
||||
APService();
|
||||
~APService();
|
||||
@@ -23,10 +22,16 @@ class APService : public StatefulService<APSettings> {
|
||||
void statusProto(api_APStatus &proto);
|
||||
APNetworkStatus getAPNetworkStatus();
|
||||
|
||||
StatefulProtoEndpoint<APSettings, api_APSettings> protoEndpoint;
|
||||
esp_err_t getSettings(httpd_req_t *request);
|
||||
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
|
||||
|
||||
private:
|
||||
FSPersistencePB<APSettings> _persistence;
|
||||
static constexpr const char *TAG = "APService";
|
||||
|
||||
void onSettingsChanged(const api_APSettings &newSettings);
|
||||
|
||||
APSettings _settings = APSettings_defaults();
|
||||
EventBus::Handle<api_APSettings> _settingsHandle;
|
||||
DNSServer *_dnsServer;
|
||||
|
||||
volatile unsigned long _lastManaged;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#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
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
#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);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
#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);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
#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;
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
#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)
|
||||
|
||||
REGISTER_EVENT_TYPE(socket_message_IMUData, EventType::IMU_DATA)
|
||||
REGISTER_EVENT_TYPE(socket_message_ControllerData, EventType::MOTION_COMMAND)
|
||||
REGISTER_EVENT_TYPE(socket_message_ModeData, EventType::MOTION_MODE)
|
||||
@@ -0,0 +1,24 @@
|
||||
#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,
|
||||
SERVO_STATE = 141,
|
||||
|
||||
SYSTEM_BOOT = 300,
|
||||
STORAGE_HYDRATION_COMPLETE = 301,
|
||||
};
|
||||
|
||||
const char* eventTypeName(EventType type);
|
||||
bool isSettingsEvent(EventType type);
|
||||
@@ -0,0 +1,55 @@
|
||||
#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>;
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
enum class SystemEventType : uint8_t {
|
||||
STORAGE_HYDRATION_COMPLETE,
|
||||
SYSTEM_BOOT_COMPLETE,
|
||||
};
|
||||
|
||||
struct SystemEvent {
|
||||
SystemEventType type;
|
||||
};
|
||||
@@ -0,0 +1,287 @@
|
||||
#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>
|
||||
|
||||
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;
|
||||
std::array<Msg, BatchSize> buf;
|
||||
size_t cnt;
|
||||
std::atomic<bool> enabled;
|
||||
std::atomic<uint32_t> running;
|
||||
};
|
||||
inline static StaticQueue_t qbuf;
|
||||
inline static Item qStorage[QueueDepth];
|
||||
inline static QueueHandle_t queue =
|
||||
xQueueCreateStatic(QueueDepth, sizeof(Item), reinterpret_cast<uint8_t*>(qStorage), &qbuf);
|
||||
inline static std::array<std::optional<Sub>, MaxSubs> subs {};
|
||||
inline static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
inline static Msg latest {};
|
||||
inline static std::atomic<bool> hasLatest {false};
|
||||
inline static std::atomic<size_t> subCount {0};
|
||||
inline static std::atomic<bool> taskStarted {false};
|
||||
|
||||
static void storeISR(const Msg& m) {
|
||||
UBaseType_t s = portSET_INTERRUPT_MASK_FROM_ISR();
|
||||
latest = m;
|
||||
hasLatest.store(true, std::memory_order_release);
|
||||
portCLEAR_INTERRUPT_MASK_FROM_ISR(s);
|
||||
}
|
||||
|
||||
static void dispatch(const Msg& m, size_t ex) {
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
Sub* ready[MaxSubs];
|
||||
size_t readyCnt = 0;
|
||||
|
||||
portENTER_CRITICAL(&mux);
|
||||
for (size_t i = 0; i < MaxSubs; ++i) {
|
||||
auto& opt = subs[i];
|
||||
if (!opt || i == ex) continue;
|
||||
Sub& s = *opt;
|
||||
if (!s.enabled.load(std::memory_order_acquire)) continue;
|
||||
|
||||
TickType_t dt = now - s.last;
|
||||
|
||||
if (s.interval && dt < s.interval) {
|
||||
if (s.mode == EmitMode::Batch) {
|
||||
if (s.cnt < BatchSize)
|
||||
s.buf[s.cnt++] = m;
|
||||
else
|
||||
s.buf[BatchSize - 1] = m;
|
||||
} else {
|
||||
s.buf[0] = m;
|
||||
s.cnt = 1;
|
||||
}
|
||||
} else {
|
||||
if (s.cnt < BatchSize)
|
||||
s.buf[s.cnt++] = m;
|
||||
else
|
||||
s.buf[BatchSize - 1] = m;
|
||||
s.last = now;
|
||||
s.running.fetch_add(1, std::memory_order_acq_rel);
|
||||
ready[readyCnt++] = &s;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&mux);
|
||||
|
||||
for (size_t i = 0; i < readyCnt; ++i) {
|
||||
Sub* s = ready[i];
|
||||
s->cb(s->buf.data(), s->cnt);
|
||||
s->cnt = 0;
|
||||
s->running.fetch_sub(1, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
|
||||
static void worker(void*) {
|
||||
Item it;
|
||||
while (xQueueReceive(queue, &it, portMAX_DELAY) == pdTRUE) dispatch(it.payload, it.exclude);
|
||||
}
|
||||
|
||||
static void ensureTask() {
|
||||
if (!taskStarted.load(std::memory_order_acquire)) {
|
||||
bool expected = false;
|
||||
if (taskStarted.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) {
|
||||
xTaskCreatePinnedToCore(worker, "evtbus", 4096, nullptr, 6, nullptr, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool push(const Msg& m, size_t ex = NO_EX, TickType_t to = 0) {
|
||||
ensureTask();
|
||||
Item it {m, ex};
|
||||
return xQueueSend(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) {
|
||||
Sub* s = nullptr;
|
||||
portENTER_CRITICAL(&mux);
|
||||
if (subs[idx]) {
|
||||
s = &*subs[idx];
|
||||
s->enabled.store(false, std::memory_order_release);
|
||||
}
|
||||
portEXIT_CRITICAL(&mux);
|
||||
if (s) {
|
||||
while (s->running.load(std::memory_order_acquire) != 0) taskYIELD();
|
||||
portENTER_CRITICAL(&mux);
|
||||
subs[idx].reset();
|
||||
portEXIT_CRITICAL(&mux);
|
||||
subCount.fetch_sub(1, std::memory_order_acq_rel);
|
||||
}
|
||||
idx = NO_EX;
|
||||
}
|
||||
}
|
||||
bool valid() const { return idx < MaxSubs; }
|
||||
};
|
||||
|
||||
static void store(const Msg& m) {
|
||||
portENTER_CRITICAL(&mux);
|
||||
latest = m;
|
||||
hasLatest.store(true, std::memory_order_release);
|
||||
portEXIT_CRITICAL(&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();
|
||||
portENTER_CRITICAL(&mux);
|
||||
for (size_t i = 0; i < MaxSubs; ++i)
|
||||
if (!subs[i]) {
|
||||
subs[i].emplace();
|
||||
Sub& s = *subs[i];
|
||||
s.cb.set(std::move(fn));
|
||||
s.interval = pdMS_TO_TICKS(ms);
|
||||
s.last = xTaskGetTickCount() - s.interval;
|
||||
s.mode = mode;
|
||||
s.cnt = 0;
|
||||
s.enabled.store(true, std::memory_order_release);
|
||||
s.running.store(0, std::memory_order_release);
|
||||
subCount.fetch_add(1, std::memory_order_acq_rel);
|
||||
portEXIT_CRITICAL(&mux);
|
||||
return Handle(i);
|
||||
}
|
||||
portEXIT_CRITICAL(&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) {
|
||||
storeISR(m);
|
||||
Item it {m, NO_EX};
|
||||
xQueueSendFromISR(queue, &it, hpw);
|
||||
}
|
||||
|
||||
static bool peek(Msg& out) {
|
||||
if (!hasLatest.load(std::memory_order_acquire)) return false;
|
||||
portENTER_CRITICAL(&mux);
|
||||
out = latest;
|
||||
portEXIT_CRITICAL(&mux);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool take(Msg& out) {
|
||||
if (!hasLatest.load(std::memory_order_acquire)) return false;
|
||||
portENTER_CRITICAL(&mux);
|
||||
out = latest;
|
||||
hasLatest.store(false, std::memory_order_release);
|
||||
portEXIT_CRITICAL(&mux);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool hasSubscribers() { return subCount.load(std::memory_order_acquire) > 0; }
|
||||
};
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <mdns.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
#include <event_bus/event_bus.h>
|
||||
#include <settings/mdns_settings.h>
|
||||
#include <utils/timing.h>
|
||||
|
||||
class MDNSService : public StatefulService<MDNSSettings> {
|
||||
class MDNSService {
|
||||
public:
|
||||
MDNSService();
|
||||
~MDNSService();
|
||||
@@ -18,10 +16,16 @@ class MDNSService : public StatefulService<MDNSSettings> {
|
||||
esp_err_t getStatus(httpd_req_t *request);
|
||||
esp_err_t queryServices(httpd_req_t *request, api_Request *protoReq);
|
||||
|
||||
StatefulProtoEndpoint<MDNSSettings, api_MDNSSettings> protoEndpoint;
|
||||
esp_err_t getSettings(httpd_req_t *request);
|
||||
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
|
||||
|
||||
private:
|
||||
FSPersistencePB<MDNSSettings> _persistence;
|
||||
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();
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
#include <esp_http_server.h>
|
||||
|
||||
#include <features.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
#include <event_bus/event_bus.h>
|
||||
|
||||
#include <settings/camera_settings.h>
|
||||
|
||||
@@ -23,7 +21,7 @@ camera_fb_t *safe_camera_fb_get();
|
||||
sensor_t *safe_sensor_get();
|
||||
void safe_sensor_return();
|
||||
|
||||
class CameraService : public StatefulService<CameraSettings> {
|
||||
class CameraService {
|
||||
public:
|
||||
CameraService();
|
||||
|
||||
@@ -32,10 +30,16 @@ class CameraService : public StatefulService<CameraSettings> {
|
||||
esp_err_t cameraStill(httpd_req_t *request);
|
||||
esp_err_t cameraStream(httpd_req_t *request);
|
||||
|
||||
StatefulProtoEndpoint<CameraSettings, api_CameraSettings> protoEndpoint;
|
||||
esp_err_t getSettings(httpd_req_t *request);
|
||||
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
|
||||
|
||||
private:
|
||||
FSPersistencePB<CameraSettings> _persistence;
|
||||
static constexpr const char *TAG = "CameraService";
|
||||
|
||||
void onSettingsChanged(const api_CameraSettings &newSettings);
|
||||
void updateCamera();
|
||||
|
||||
CameraSettings _settings = CameraSettings_defaults();
|
||||
EventBus::Handle<api_CameraSettings> _settingsHandle;
|
||||
};
|
||||
} // namespace Camera
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <event_bus/event_bus.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>
|
||||
|
||||
@@ -26,7 +25,7 @@
|
||||
*/
|
||||
#define MAX_DISTANCE 200
|
||||
|
||||
class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
||||
class Peripherals {
|
||||
public:
|
||||
Peripherals();
|
||||
|
||||
@@ -42,7 +41,6 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
||||
void getIMUProto(socket_message_IMUData &data);
|
||||
void getSettingsProto(socket_message_PeripheralSettingsData &data);
|
||||
|
||||
/* IMU FUNCTIONS */
|
||||
bool readImu();
|
||||
|
||||
bool readMag();
|
||||
@@ -66,10 +64,16 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
||||
|
||||
bool calibrateIMU();
|
||||
|
||||
StatefulProtoEndpoint<PeripheralsConfiguration, api_PeripheralSettings> protoEndpoint;
|
||||
esp_err_t getSettings(httpd_req_t *request);
|
||||
esp_err_t updateSettings(httpd_req_t *request, api_Request *protoReq);
|
||||
|
||||
private:
|
||||
FSPersistencePB<PeripheralsConfiguration> _persistence;
|
||||
static constexpr const char *TAG = "Peripherals";
|
||||
|
||||
void onSettingsChanged(const api_PeripheralSettings &newSettings);
|
||||
|
||||
PeripheralsConfiguration _settings = PeripheralsConfiguration_defaults();
|
||||
EventBus::Handle<api_PeripheralSettings> _settingsHandle;
|
||||
|
||||
SemaphoreHandle_t _accessMutex;
|
||||
inline void beginTransaction() { xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); }
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
#define ServoController_h
|
||||
|
||||
#include <peripherals/drivers/pca9685.h>
|
||||
#include <template/stateful_persistence_pb.h>
|
||||
#include <template/stateful_proto_endpoint.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <event_bus/event_bus.h>
|
||||
#include <utils/math_utils.h>
|
||||
#include <platform_shared/api.pb.h>
|
||||
#include <esp_http_server.h>
|
||||
|
||||
#ifndef FACTORY_SERVO_PWM_FREQUENCY
|
||||
#define FACTORY_SERVO_PWM_FREQUENCY 50
|
||||
@@ -24,43 +23,34 @@ 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;
|
||||
}
|
||||
|
||||
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> {
|
||||
class ServoController {
|
||||
public:
|
||||
ServoController()
|
||||
: protoEndpoint(ServoSettings_read, ServoSettings_update, this,
|
||||
API_REQUEST_EXTRACTOR(servo_settings, ServoSettings),
|
||||
API_RESPONSE_ASSIGNER(servo_settings, ServoSettings)),
|
||||
_persistence(ServoSettings_read, ServoSettings_update, this,
|
||||
SERVO_SETTINGS_FILE, api_ServoSettings_fields, api_ServoSettings_size,
|
||||
ServoSettings_defaults()) {}
|
||||
ServoController() {}
|
||||
|
||||
void begin() {
|
||||
_persistence.readFromFS();
|
||||
_settingsHandle = EventBus::subscribe<api_ServoSettings>(
|
||||
[this](const api_ServoSettings &settings) { onSettingsChanged(settings); });
|
||||
|
||||
api_ServoSettings initialSettings;
|
||||
if (EventBus::peek(initialSettings)) {
|
||||
onSettingsChanged(initialSettings);
|
||||
}
|
||||
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);
|
||||
@@ -109,7 +99,7 @@ class ServoController : public StatefulService<ServoSettings> {
|
||||
uint16_t pwms[12];
|
||||
for (int i = 0; i < 12; i++) {
|
||||
angles[i] = lerp(angles[i], target_angles[i], 0.1);
|
||||
auto &servo = state().servos[i];
|
||||
auto &servo = _settings.servos[i];
|
||||
float angle = servo.direction * angles[i] + servo.center_angle;
|
||||
uint16_t pwm = angle * servo.conversion + servo.center_pwm;
|
||||
pwms[i] = pwm = std::clamp<uint16_t>(pwm, 125, 600);
|
||||
@@ -121,16 +111,20 @@ class ServoController : public StatefulService<ServoSettings> {
|
||||
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();
|
||||
}
|
||||
FSPersistencePB<ServoSettings> _persistence;
|
||||
|
||||
api_ServoSettings _settings = ServoSettings_defaults();
|
||||
EventBus::Handle<api_ServoSettings> _settingsHandle;
|
||||
|
||||
PCA9685Driver _pca;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <esp_http_server.h>
|
||||
#include <template/stateful_service.h>
|
||||
#include <communication/webserver.h>
|
||||
@@ -27,7 +28,8 @@ class StatefulProtoEndpoint {
|
||||
// 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
|
||||
// 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&)>;
|
||||
@@ -51,8 +53,8 @@ class StatefulProtoEndpoint {
|
||||
* @param responseAssigner Assigns specific type to Response oneof
|
||||
*/
|
||||
StatefulProtoEndpoint(ProtoStateReader stateReader, ProtoStateUpdater stateUpdater,
|
||||
StatefulService<T>* statefulService,
|
||||
RequestExtractor requestExtractor, ResponseAssigner responseAssigner)
|
||||
StatefulService<T>* statefulService, RequestExtractor requestExtractor,
|
||||
ResponseAssigner responseAssigner)
|
||||
: _stateReader(stateReader),
|
||||
_stateUpdater(stateUpdater),
|
||||
_statefulService(statefulService),
|
||||
@@ -81,9 +83,7 @@ class StatefulProtoEndpoint {
|
||||
/**
|
||||
* Handles GET requests: reads current state and returns it as Response
|
||||
*/
|
||||
esp_err_t getState(httpd_req_t* request) {
|
||||
return sendStateResponse(request, 200);
|
||||
}
|
||||
esp_err_t getState(httpd_req_t* request) { return sendStateResponse(request, 200); }
|
||||
|
||||
private:
|
||||
/** Sends current state wrapped in Response */
|
||||
@@ -115,21 +115,21 @@ class StatefulProtoEndpoint {
|
||||
* 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; \
|
||||
#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; \
|
||||
#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; \
|
||||
}
|
||||
|
||||
@@ -5,18 +5,15 @@
|
||||
#include <mdns.h>
|
||||
#include <string>
|
||||
|
||||
#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 <event_bus/event_bus.h>
|
||||
#include <settings/wifi_settings.h>
|
||||
#include <utils/timing.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> {
|
||||
class WiFiService {
|
||||
public:
|
||||
WiFiService();
|
||||
~WiFiService();
|
||||
@@ -27,27 +24,39 @@ class WiFiService : public StatefulService<WiFiSettings> {
|
||||
void setupMDNS(const char *hostname);
|
||||
void selectNetwork(uint32_t index);
|
||||
|
||||
const char *getHostname() { return state().hostname; }
|
||||
const char *getHostname() {
|
||||
static api_WifiSettings cached_settings;
|
||||
EventBus::peek(cached_settings);
|
||||
return cached_settings.hostname;
|
||||
}
|
||||
|
||||
static esp_err_t handleScan(httpd_req_t *request);
|
||||
static esp_err_t getNetworks(httpd_req_t *request);
|
||||
static esp_err_t getNetworkStatus(httpd_req_t *request);
|
||||
|
||||
StatefulProtoEndpoint<WiFiSettings, api_WifiSettings> protoEndpoint;
|
||||
|
||||
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);
|
||||
|
||||
FSPersistencePB<WiFiSettings> _persistence;
|
||||
void onSettingsChanged(const api_WifiSettings &newSettings);
|
||||
|
||||
void reconfigureWiFiConnection();
|
||||
void manageSTA();
|
||||
void configureNetwork(WiFiNetwork &network);
|
||||
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";
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user