♻️ Handle merging

This commit is contained in:
Rune Harlyk
2026-01-31 16:11:20 +01:00
committed by Rune Harlyk
parent aca8ee6de5
commit bd984309f1
19 changed files with 224 additions and 391 deletions
-1
View File
@@ -1,7 +1,6 @@
#pragma once #pragma once
#include <wifi/wifi_idf.h> #include <wifi/wifi_idf.h>
#include <ArduinoJson.h>
#include <esp_http_server.h> #include <esp_http_server.h>
#include "platform_shared/message.pb.h" #include "platform_shared/message.pb.h"
+10 -23
View File
@@ -1,7 +1,6 @@
#pragma once #pragma once
#include <esp_http_server.h> #include <esp_http_server.h>
#include <ArduinoJson.h>
#include <esp_littlefs.h> #include <esp_littlefs.h>
#include <esp_vfs.h> #include <esp_vfs.h>
#include <dirent.h> #include <dirent.h>
@@ -12,40 +11,28 @@
#define MOUNT_POINT "/littlefs" #define MOUNT_POINT "/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"
namespace FileSystem {
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);
#define AP_SETTINGS_FILE MOUNT_POINT "/config/apSettings.json"
#define CAMERA_SETTINGS_FILE MOUNT_POINT "/config/cameraSettings.json"
#define FS_CONFIG_DIRECTORY MOUNT_POINT "/config" #define FS_CONFIG_DIRECTORY MOUNT_POINT "/config"
#define DEVICE_CONFIG_FILE MOUNT_POINT "/config/peripheral.json" #define DEVICE_CONFIG_FILE MOUNT_POINT "/config/peripheral.pb"
#define WIFI_SETTINGS_FILE MOUNT_POINT "/config/wifiSettings.json" #define CAMERA_SETTINGS_FILE MOUNT_POINT "/config/cameraSettings.pb"
#define SERVO_SETTINGS_FILE MOUNT_POINT "/config/servoSettings.json" #define AP_SETTINGS_FILE MOUNT_POINT "/config/apSettings.pb"
#define MDNS_SETTINGS_FILE MOUNT_POINT "/config/mdnsSettings.json" #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 { namespace FileSystem {
bool init(); bool init();
void listFilesProto(const std::string &directory, api_FileEntry *entry);
std::string listFiles(const std::string &directory, bool isRoot = true); std::string listFiles(const std::string &directory, bool isRoot = true);
bool deleteFile(const char *filename); 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 editFile(const char *filename, const char *content);
bool fileExists(const char *filename); bool fileExists(const char *filename);
std::string readFile(const char *filename); std::string readFile(const char *filename);
bool writeFile(const char *filename, const char *content); 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); bool mkdirRecursive(const char *path);
esp_err_t getFilesProto(httpd_req_t *request); esp_err_t getFilesProto(httpd_req_t *request);
+9 -10
View File
@@ -1,7 +1,6 @@
#pragma once #pragma once
#include <esp_http_server.h> #include <esp_http_server.h>
#include <ArduinoJson.h>
#include <mdns.h> #include <mdns.h>
#include <template/stateful_service.h> #include <template/stateful_service.h>
#include <template/stateful_proto_endpoint.h> #include <template/stateful_proto_endpoint.h>
@@ -10,15 +9,6 @@
#include <utils/timing.h> #include <utils/timing.h>
class MDNSService : public StatefulService<MDNSSettings> { class MDNSService : public StatefulService<MDNSSettings> {
private:
FSPersistencePB<MDNSSettings> _persistence;
bool _started {false};
void reconfigureMDNS();
void startMDNS();
void stopMDNS();
void addServices();
public: public:
MDNSService(); MDNSService();
~MDNSService(); ~MDNSService();
@@ -29,4 +19,13 @@ class MDNSService : public StatefulService<MDNSSettings> {
esp_err_t queryServices(httpd_req_t *request, api_Request *protoReq); esp_err_t queryServices(httpd_req_t *request, api_Request *protoReq);
StatefulProtoEndpoint<MDNSSettings, api_MDNSSettings> protoEndpoint; StatefulProtoEndpoint<MDNSSettings, api_MDNSSettings> protoEndpoint;
private:
FSPersistencePB<MDNSSettings> _persistence;
bool _started {false};
void reconfigureMDNS();
void startMDNS();
void stopMDNS();
void addServices();
}; };
+1 -4
View File
@@ -2,7 +2,6 @@
#include <wifi/wifi_idf.h> #include <wifi/wifi_idf.h>
#include <wifi/dns_server.h> #include <wifi/dns_server.h>
#include <ArduinoJson.h>
#include <template/state_result.h> #include <template/state_result.h>
#include <platform_shared/api.pb.h> #include <platform_shared/api.pb.h>
#include <cstring> #include <cstring>
@@ -77,9 +76,7 @@ inline APSettings APSettings_defaults() {
return settings; return settings;
} }
inline void APSettings_read(const APSettings &settings, APSettings &proto) { inline void APSettings_read(const APSettings &settings, APSettings &proto) { proto = settings; }
proto = settings;
}
inline StateUpdateResult APSettings_update(const APSettings &proto, APSettings &settings) { inline StateUpdateResult APSettings_update(const APSettings &proto, APSettings &settings) {
settings = proto; settings = proto;
@@ -7,10 +7,10 @@
* I2C software connection * I2C software connection
*/ */
#ifndef SDA_PIN #ifndef SDA_PIN
#define SDA_PIN SDA #define SDA_PIN 21
#endif #endif
#ifndef SCL_PIN #ifndef SCL_PIN
#define SCL_PIN SCL #define SCL_PIN 22
#endif #endif
#ifndef I2C_FREQUENCY #ifndef I2C_FREQUENCY
#define I2C_FREQUENCY 1000000UL #define I2C_FREQUENCY 1000000UL
@@ -35,7 +35,8 @@ inline void PeripheralsConfiguration_read(const PeripheralsConfiguration& settin
proto = settings; proto = settings;
} }
inline StateUpdateResult PeripheralsConfiguration_update(const PeripheralsConfiguration& proto, PeripheralsConfiguration& settings) { inline StateUpdateResult PeripheralsConfiguration_update(const PeripheralsConfiguration& proto,
PeripheralsConfiguration& settings) {
settings = proto; settings = proto;
return StateUpdateResult::CHANGED; return StateUpdateResult::CHANGED;
} }
+1 -4
View File
@@ -1,7 +1,6 @@
#pragma once #pragma once
#include <wifi/wifi_idf.h> #include <wifi/wifi_idf.h>
#include <ArduinoJson.h>
#include <template/state_result.h> #include <template/state_result.h>
#include <platform_shared/api.pb.h> #include <platform_shared/api.pb.h>
#include <cstring> #include <cstring>
@@ -50,9 +49,7 @@ inline WiFiSettings WiFiSettings_defaults() {
return settings; return settings;
} }
inline void WiFiSettings_read(const WiFiSettings &settings, WiFiSettings &proto) { inline void WiFiSettings_read(const WiFiSettings &settings, WiFiSettings &proto) { proto = settings; }
proto = settings;
}
inline StateUpdateResult WiFiSettings_update(const WiFiSettings &proto, WiFiSettings &settings) { inline StateUpdateResult WiFiSettings_update(const WiFiSettings &proto, WiFiSettings &settings) {
settings = proto; settings = proto;
-1
View File
@@ -20,7 +20,6 @@ esp_err_t handleSleep(httpd_req_t *request);
void reset(); void reset();
void restart(); void restart();
void sleep(); void sleep();
void status(JsonObject &root);
void getAnalytics(socket_message_AnalyticsData &analytics); void getAnalytics(socket_message_AnalyticsData &analytics);
void getStaticSystemInformation(socket_message_StaticSystemInformation &info); void getStaticSystemInformation(socket_message_StaticSystemInformation &info);
@@ -1,29 +1,24 @@
#pragma once #pragma once
#include <FS.h>
#include <template/stateful_service.h> #include <template/stateful_service.h>
#include <template/state_result.h> #include <template/state_result.h>
#include <filesystem.h> #include <filesystem.h>
#include <pb_encode.h> #include <pb_encode.h>
#include <pb_decode.h> #include <pb_decode.h>
#include <cstdio>
#include <sys/stat.h>
#include <esp_log.h>
static const char *TAG_PERSISTENCE = "FSPersistencePB";
/**
* Protobuf-based filesystem persistence for StatefulService.
*
* @tparam T The state type (should be a nanopb-generated struct like api_APSettings)
*/
template <class T> template <class T>
class FSPersistencePB { class FSPersistencePB {
public: 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 &)>;
using ProtoStateReader = std::function<void(const T&, T&)>; using ProtoStateUpdater = std::function<StateUpdateResult(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, FSPersistencePB(ProtoStateReader stateReader, ProtoStateUpdater stateUpdater, StatefulService<T> *statefulService,
StatefulService<T> *statefulService, const char *filePath, const char *filePath, const pb_msgdesc_t *msgDescriptor, size_t maxSize, const T &defaultState)
const pb_msgdesc_t *msgDescriptor, size_t maxSize,
const T &defaultState)
: _stateReader(stateReader), : _stateReader(stateReader),
_stateUpdater(stateUpdater), _stateUpdater(stateUpdater),
_statefulService(statefulService), _statefulService(statefulService),
@@ -36,17 +31,19 @@ class FSPersistencePB {
} }
void readFromFS() { void readFromFS() {
File file = _fs->open(_filePath, "r"); FILE *file = fopen(_filePath, "rb");
if (file) { if (file) {
size_t fileSize = file.size(); fseek(file, 0, SEEK_END);
size_t fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
if (fileSize > 0 && fileSize <= _maxSize) { if (fileSize > 0 && fileSize <= _maxSize) {
uint8_t *buffer = new uint8_t[fileSize]; uint8_t *buffer = new uint8_t[fileSize];
size_t bytesRead = file.read(buffer, fileSize); size_t bytesRead = fread(buffer, 1, fileSize, file);
file.close(); fclose(file);
if (bytesRead == fileSize) { if (bytesRead == fileSize) {
// Allocate on heap to avoid stack overflow with large proto messages
T *protoMsg = new T(); T *protoMsg = new T();
*protoMsg = {}; *protoMsg = {};
pb_istream_t stream = pb_istream_from_buffer(buffer, bytesRead); pb_istream_t stream = pb_istream_from_buffer(buffer, bytesRead);
@@ -62,7 +59,7 @@ class FSPersistencePB {
} }
delete[] buffer; delete[] buffer;
} else { } else {
file.close(); fclose(file);
} }
} }
@@ -74,7 +71,6 @@ class FSPersistencePB {
uint8_t *buffer = new uint8_t[_maxSize]; uint8_t *buffer = new uint8_t[_maxSize];
pb_ostream_t stream = pb_ostream_from_buffer(buffer, _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(); T *protoMsg = new T();
*protoMsg = {}; *protoMsg = {};
_statefulService->read([this, protoMsg](const T &state) { _stateReader(state, *protoMsg); }); _statefulService->read([this, protoMsg](const T &state) { _stateReader(state, *protoMsg); });
@@ -89,14 +85,15 @@ class FSPersistencePB {
mkdirs(); mkdirs();
File file = _fs->open(_filePath, "w"); FILE *file = fopen(_filePath, "wb");
if (!file) { if (!file) {
ESP_LOGE(TAG_PERSISTENCE, "Failed to open file for writing: %s", _filePath);
delete[] buffer; delete[] buffer;
return false; return false;
} }
size_t written = file.write(buffer, stream.bytes_written); size_t written = fwrite(buffer, 1, stream.bytes_written, file);
file.close(); fclose(file);
delete[] buffer; delete[] buffer;
return written == stream.bytes_written; return written == stream.bytes_written;
@@ -111,8 +108,7 @@ class FSPersistencePB {
void enableUpdateHandler() { void enableUpdateHandler() {
if (!_updateHandlerId) { if (!_updateHandlerId) {
_updateHandlerId = _statefulService->addUpdateHandler( _updateHandlerId = _statefulService->addUpdateHandler([&](const std::string &originId) { writeToFS(); });
[&](const std::string &originId) { writeToFS(); });
} }
} }
@@ -120,7 +116,6 @@ class FSPersistencePB {
ProtoStateReader _stateReader; ProtoStateReader _stateReader;
ProtoStateUpdater _stateUpdater; ProtoStateUpdater _stateUpdater;
StatefulService<T> *_statefulService; StatefulService<T> *_statefulService;
FS *_fs{&ESP_FS};
const char *_filePath; const char *_filePath;
const pb_msgdesc_t *_msgDescriptor; const pb_msgdesc_t *_msgDescriptor;
size_t _maxSize; size_t _maxSize;
@@ -132,13 +127,15 @@ class FSPersistencePB {
size_t index = 0; size_t index = 0;
while ((index = path.find('/', index + 1)) != std::string::npos) { while ((index = path.find('/', index + 1)) != std::string::npos) {
std::string segment = path.substr(0, index); std::string segment = path.substr(0, index);
if (!_fs->exists(segment.c_str())) _fs->mkdir(segment.c_str()); struct stat st;
if (stat(segment.c_str(), &st) != 0) {
FileSystem::mkdirRecursive(segment.c_str());
}
} }
} }
protected: protected:
void applyDefaults() { void applyDefaults() {
_statefulService->updateWithoutPropagation( _statefulService->updateWithoutPropagation([this](T &state) { return _stateUpdater(_defaultState, state); });
[this](T &state) { return _stateUpdater(_defaultState, state); });
} }
}; };
+5 -28
View File
@@ -1,7 +1,5 @@
#pragma once #pragma once
#include <ArduinoJson.h>
#include <list> #include <list>
#include <functional> #include <functional>
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
@@ -10,19 +8,13 @@
#include <template/state_result.h> #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 HandlerId = size_t;
using StateUpdateCallback = std::function<void(const std::string &originId)>; using StateUpdateCallback = std::function<void(const std::string &originId)>;
using StateHookCallback = std::function<void(const std::string &originId, StateUpdateResult &result)>; using StateHookCallback = std::function<void(const std::string &originId, StateUpdateResult &result)>;
class HandlerBase { class HandlerBase {
protected: protected:
static inline HandlerId nextId_ = 1; // Start from 1, 0 is invalid static inline HandlerId nextId_ = 1;
HandlerId id_; HandlerId id_;
bool allowRemove_; bool allowRemove_;
@@ -98,31 +90,16 @@ class StatefulService {
return result; 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) { void read(std::function<void(T &)> stateReader) {
lock(); lock();
stateReader(state_); stateReader(state_);
unlock(); unlock();
} }
void read(JsonVariant &jsonObject, JsonStateReader<T> stateReader) { void read(std::function<void(const T &)> stateReader) const {
lock(); const_cast<StatefulService *>(this)->lock();
stateReader(state_, jsonObject); stateReader(state_);
unlock(); const_cast<StatefulService *>(this)->unlock();
} }
void callUpdateHandlers(const std::string &originId) { void callUpdateHandlers(const std::string &originId) {
+17 -19
View File
@@ -17,25 +17,6 @@
#define IP_EVENT_STA_GOT_IP_IDF 1000 #define IP_EVENT_STA_GOT_IP_IDF 1000
class WiFiService : public StatefulService<WiFiSettings> { class WiFiService : public StatefulService<WiFiSettings> {
private:
static void getNetworks(JsonObject &root);
static void getNetworkStatus(JsonObject &root);
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 reconfigureWiFiConnection();
void manageSTA();
void connectToWiFi();
void configureNetwork(WiFiNetwork &network);
unsigned long _lastConnectionAttempt;
bool _stopping;
constexpr static uint16_t reconnectDelay {10000};
public: public:
WiFiService(); WiFiService();
~WiFiService(); ~WiFiService();
@@ -52,4 +33,21 @@ class WiFiService : public StatefulService<WiFiSettings> {
static esp_err_t getNetworkStatus(httpd_req_t *request); static esp_err_t getNetworkStatus(httpd_req_t *request);
StatefulProtoEndpoint<WiFiSettings, api_WifiSettings> protoEndpoint; 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 reconfigureWiFiConnection();
void manageSTA();
void connectToWiFi();
void configureNetwork(WiFiNetwork &network);
unsigned long _lastConnectionAttempt;
bool _stopping;
constexpr static uint16_t reconnectDelay {10000};
}; };
+2 -2
View File
@@ -1,7 +1,7 @@
# HTTP Server WebSocket support # HTTP Server WebSocket support
CONFIG_HTTPD_WS_SUPPORT=y CONFIG_HTTPD_WS_SUPPORT=y
CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048
CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_MAX_URI_LEN=1024
# mDNS # mDNS
CONFIG_MDNS_MAX_SERVICES=10 CONFIG_MDNS_MAX_SERVICES=10
+9 -27
View File
@@ -4,14 +4,10 @@
static const char *TAG = "APService"; static const char *TAG = "APService";
APService::APService() APService::APService()
: protoEndpoint(APSettings_read, APSettings_update, this, : protoEndpoint(APSettings_read, APSettings_update, this, API_REQUEST_EXTRACTOR(ap_settings, api_APSettings),
API_REQUEST_EXTRACTOR(ap_settings, api_APSettings),
API_RESPONSE_ASSIGNER(ap_settings, api_APSettings)), API_RESPONSE_ASSIGNER(ap_settings, api_APSettings)),
_persistence(APSettings_read, APSettings_update, this, _persistence(APSettings_read, APSettings_update, this, AP_SETTINGS_FILE, api_APSettings_fields,
AP_SETTINGS_FILE, api_APSettings_fields, api_APSettings_size, api_APSettings_size, APSettings_defaults()),
APSettings_defaults()) {
: endpoint(APSettings::read, APSettings::update, this),
_persistence(APSettings::read, APSettings::update, this, AP_SETTINGS_FILE),
_dnsServer(nullptr), _dnsServer(nullptr),
_lastManaged(0), _lastManaged(0),
_reconfigureAp(false), _reconfigureAp(false),
@@ -26,22 +22,8 @@ APService::~APService() {
} }
} }
void APService::begin() { void APService::begin() { _persistence.readFromFS(); }
_persistence.readFromFS();
}
void APService::status(JsonObject &root) {
root["status"] = getAPNetworkStatus();
root["ip_address"] = (uint32_t)(WiFi.softAPIP());
root["mac_address"] = WiFi.softAPmacAddress().c_str();
root["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 && state().provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
return APNetworkStatus::LINGERING;
esp_err_t APService::getStatusProto(httpd_req_t *request) { esp_err_t APService::getStatusProto(httpd_req_t *request) {
api_Response res = api_Response_init_zero; api_Response res = api_Response_init_zero;
res.status_code = 200; res.status_code = 200;
@@ -53,15 +35,15 @@ esp_err_t APService::getStatusProto(httpd_req_t *request) {
void APService::statusProto(api_APStatus &proto) { void APService::statusProto(api_APStatus &proto) {
proto.status = getAPNetworkStatus(); proto.status = getAPNetworkStatus();
proto.ip_address = static_cast<uint32_t>(WiFi.softAPIP()); proto.ip_address = static_cast<uint32_t>(WiFi.softAPIP());
String mac = WiFi.softAPmacAddress(); std::string mac = WiFi.softAPmacAddress();
strncpy(proto.mac_address, mac.c_str(), sizeof(proto.mac_address) - 1); strncpy(proto.mac_address, mac.c_str(), sizeof(proto.mac_address) - 1);
proto.mac_address[sizeof(proto.mac_address) - 1] = '\0'; proto.mac_address[sizeof(proto.mac_address) - 1] = '\0';
proto.station_num = WiFi.softAPgetStationNum(); proto.station_num = WiFi.softAPgetStationNum();
} }
APNetworkStatus APService::getAPNetworkStatus() { APNetworkStatus APService::getAPNetworkStatus() {
WiFiMode_t currentWiFiMode = WiFi.getMode(); wifi_mode_t currentWiFiMode = WiFi.getMode();
bool apActive = currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA; bool apActive = currentWiFiMode == WIFI_MODE_AP || currentWiFiMode == WIFI_MODE_APSTA;
if (apActive && state().provision_mode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) { if (apActive && state().provision_mode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
return LINGERING; return LINGERING;
} }
@@ -88,8 +70,8 @@ void APService::loop() {
void APService::manageAP() { void APService::manageAP() {
wifi_mode_t currentWiFiMode = WiFi.getMode(); wifi_mode_t currentWiFiMode = WiFi.getMode();
if (state().provisionMode == AP_MODE_ALWAYS || if (state().provision_mode == AP_MODE_ALWAYS ||
(state().provisionMode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED) || _recoveryMode) { (state().provision_mode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED) || _recoveryMode) {
if (_reconfigureAp || currentWiFiMode == WIFI_MODE_NULL || currentWiFiMode == WIFI_MODE_STA) { if (_reconfigureAp || currentWiFiMode == WIFI_MODE_NULL || currentWiFiMode == WIFI_MODE_STA) {
startAP(); startAP();
} }
+1 -1
View File
@@ -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) { 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); } esp_err_t WebServer::sendOk(httpd_req_t* req) { return send(req, 200, nullptr, 0); }
+70 -71
View File
@@ -4,13 +4,14 @@
#include <cstring> #include <cstring>
#include "utils/string_utils.hpp" #include "utils/string_utils.hpp"
#include <esp_log.h> #include <esp_log.h>
#include <pb_encode.h>
#include <pb_decode.h>
static const char *TAG = "FileSystem"; static const char *TAG = "FileSystem";
namespace FileSystem { namespace FileSystem {
// Storage for dynamically allocated FileEntry arrays static std::vector<api_FileEntry *> allocatedEntries;
static std::vector<api_FileEntry*> allocatedEntries;
static void freeAllocatedEntries() { static void freeAllocatedEntries() {
for (auto ptr : allocatedEntries) { for (auto ptr : allocatedEntries) {
@@ -19,67 +20,81 @@ static void freeAllocatedEntries() {
allocatedEntries.clear(); allocatedEntries.clear();
} }
void listFilesProto(const std::string &directory, api_FileEntry *entry) { static void listFilesProtoRecursive(const std::string &directory, api_FileEntry *entry) {
File root = ESP_FS.open(directory.find("/") == 0 ? directory.c_str() : ("/" + directory).c_str()); DIR *dir = opendir(directory.c_str());
if (!root.isDirectory()) { if (!dir) {
entry->children_count = 0; entry->children_count = 0;
entry->children = nullptr; entry->children = nullptr;
return; return;
} }
// First pass: count children std::vector<std::string> names;
std::vector<File> files; std::vector<bool> isDirs;
File file = root.openNextFile(); std::vector<size_t> sizes;
while (file) {
files.push_back(file);
file = root.openNextFile();
}
if (files.empty()) { 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);
}
}
closedir(dir);
if (names.empty()) {
entry->children_count = 0; entry->children_count = 0;
entry->children = nullptr; entry->children = nullptr;
return; return;
} }
// Allocate children array entry->children_count = names.size();
entry->children_count = files.size(); entry->children = new api_FileEntry[names.size()];
entry->children = new api_FileEntry[files.size()];
allocatedEntries.push_back(entry->children); allocatedEntries.push_back(entry->children);
// Fill children for (size_t i = 0; i < names.size(); i++) {
for (size_t i = 0; i < files.size(); i++) {
api_FileEntry &child = entry->children[i]; api_FileEntry &child = entry->children[i];
memset(&child, 0, sizeof(child)); memset(&child, 0, sizeof(child));
std::string name = std::string(files[i].name()); strncpy(child.name, names[i].c_str(), sizeof(child.name) - 1);
strncpy(child.name, name.c_str(), sizeof(child.name) - 1);
child.name[sizeof(child.name) - 1] = '\0'; child.name[sizeof(child.name) - 1] = '\0';
child.is_directory = files[i].isDirectory(); child.is_directory = isDirs[i];
if (child.is_directory) { if (child.is_directory) {
listFilesProto(name, &child); std::string childPath = directory + "/" + names[i];
listFilesProtoRecursive(childPath, &child);
} else { } else {
child.size = files[i].size(); child.size = sizes[i];
child.children_count = 0; child.children_count = 0;
child.children = nullptr; 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) { esp_err_t getFilesProto(httpd_req_t *request) {
freeAllocatedEntries(); // Clean up any previous allocations freeAllocatedEntries();
api_Response res = api_Response_init_zero; api_Response res = api_Response_init_zero;
res.status_code = 200; res.status_code = 200;
res.which_payload = api_Response_file_list_tag; res.which_payload = api_Response_file_list_tag;
// Create root entry
api_FileEntry rootEntry = api_FileEntry_init_zero; api_FileEntry rootEntry = api_FileEntry_init_zero;
strncpy(rootEntry.name, "root", sizeof(rootEntry.name) - 1); strncpy(rootEntry.name, "root", sizeof(rootEntry.name) - 1);
rootEntry.is_directory = true; rootEntry.is_directory = true;
listFilesProto("/", &rootEntry); listFilesProtoRecursive(MOUNT_POINT, &rootEntry);
// Allocate entries array for FileList
res.payload.file_list.entries_count = 1; res.payload.file_list.entries_count = 1;
res.payload.file_list.entries = new api_FileEntry[1]; res.payload.file_list.entries = new api_FileEntry[1];
allocatedEntries.push_back(res.payload.file_list.entries); allocatedEntries.push_back(res.payload.file_list.entries);
@@ -87,8 +102,10 @@ esp_err_t getFilesProto(httpd_req_t *request) {
esp_err_t result = WebServer::send(request, 200, res, api_Response_fields); esp_err_t result = WebServer::send(request, 200, res, api_Response_fields);
freeAllocatedEntries(); // Clean up after sending freeAllocatedEntries();
return result; return result;
}
bool init() { bool init() {
esp_vfs_littlefs_conf_t conf = { esp_vfs_littlefs_conf_t conf = {
.base_path = MOUNT_POINT, .base_path = MOUNT_POINT,
@@ -157,6 +174,19 @@ bool writeFile(const char *filename, const char *content) {
return written == len; 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) { bool mkdirRecursive(const char *path) {
char tmp[256]; char tmp[256];
char *p = nullptr; char *p = nullptr;
@@ -211,8 +241,7 @@ esp_err_t getConfigFile(httpd_req_t *request) {
if (content.empty()) { if (content.empty()) {
return WebServer::sendError(request, 500, "Failed to read file"); return WebServer::sendError(request, 500, "Failed to read file");
} }
String content = file.readString();
file.close();
if (ends_with(path, ".pb")) { if (ends_with(path, ".pb")) {
httpd_resp_set_type(request, "application/x-protobuf"); httpd_resp_set_type(request, "application/x-protobuf");
} else if (ends_with(path, ".json")) { } else if (ends_with(path, ".json")) {
@@ -224,48 +253,30 @@ esp_err_t getConfigFile(httpd_req_t *request) {
} }
esp_err_t handleDelete(httpd_req_t *request, const api_FileDeleteRequest &req) { esp_err_t handleDelete(httpd_req_t *request, const api_FileDeleteRequest &req) {
ESP_LOGI(TAG, "Deleting file: %s", req.path); std::string fullPath = std::string(MOUNT_POINT) + req.path;
ESP_LOGI(TAG, "Deleting file: %s", fullPath.c_str());
api_Response res = api_Response_init_zero; api_Response res = api_Response_init_zero;
if (deleteFile(req.path)) { if (deleteFile(fullPath.c_str())) {
res.status_code = 200; res.status_code = 200;
res.which_payload = api_Response_empty_message_tag; res.which_payload = api_Response_empty_message_tag;
return WebServer::send(request, 200, res, api_Response_fields); return WebServer::send(request, 200, res, api_Response_fields);
} else { } else {
return WebServer::sendError(request, 500, "Delete failed"); return WebServer::sendError(request, 500, "Delete failed");
httpd_resp_set_type(request, "application/json");
return httpd_resp_send(request, content.c_str(), content.length());
}
esp_err_t handleDelete(httpd_req_t *request, JsonVariant &json) {
if (json.is<JsonObject>()) {
const char *filename = json["file"].as<const char *>();
std::string fullPath = std::string(MOUNT_POINT) + filename;
ESP_LOGI(TAG, "Deleting file: %s", fullPath.c_str());
return deleteFile(fullPath.c_str()) ? WebServer::sendOk(request)
: WebServer::sendError(request, 500, "Delete failed");
} }
} }
esp_err_t handleEdit(httpd_req_t *request, const api_FileEditRequest &req) { esp_err_t handleEdit(httpd_req_t *request, const api_FileEditRequest &req) {
ESP_LOGI(TAG, "Editing file: %s", req.path); std::string fullPath = std::string(MOUNT_POINT) + req.path;
ESP_LOGI(TAG, "Editing file: %s", fullPath.c_str());
api_Response res = api_Response_init_zero; api_Response res = api_Response_init_zero;
if (editFile(req.path, req.content->bytes, req.content->size)) { if (editFile(fullPath.c_str(), req.content->bytes, req.content->size)) {
res.status_code = 200; res.status_code = 200;
res.which_payload = api_Response_empty_message_tag; res.which_payload = api_Response_empty_message_tag;
return WebServer::send(request, 200, res, api_Response_fields); return WebServer::send(request, 200, res, api_Response_fields);
} else { } else {
return WebServer::sendError(request, 500, "Edit failed"); return WebServer::sendError(request, 500, "Edit failed");
esp_err_t handleEdit(httpd_req_t *request, JsonVariant &json) {
if (json.is<JsonObject>()) {
const char *filename = json["file"].as<const char *>();
const char *content = json["content"].as<const char *>();
std::string fullPath = std::string(MOUNT_POINT) + filename;
ESP_LOGI(TAG, "Editing file: %s", fullPath.c_str());
return editFile(fullPath.c_str(), content) ? WebServer::sendOk(request)
: WebServer::sendError(request, 500, "Edit failed");
} }
} }
@@ -310,34 +321,22 @@ std::string listFiles(const std::string &directory, bool isRoot) {
return output; return output;
} }
bool editFile(const char *filename, const uint8_t *content, size_t size) { bool editFile(const char *filename, const uint8_t *content, size_t size) { return writeFile(filename, content, size); }
File file = ESP_FS.open(filename, FILE_WRITE);
if (!file) return false;
file.write(content, size); bool editFile(const char *filename, const char *content) { return writeFile(filename, content); }
file.close();
return true;
}
esp_err_t mkdir(httpd_req_t *request, const api_FileMkdirRequest &req) { esp_err_t mkdir(httpd_req_t *request, const api_FileMkdirRequest &req) {
ESP_LOGI(TAG, "Creating directory: %s", req.path); std::string fullPath = std::string(MOUNT_POINT) + req.path;
ESP_LOGI(TAG, "Creating directory: %s", fullPath.c_str());
api_Response res = api_Response_init_zero; api_Response res = api_Response_init_zero;
if (ESP_FS.mkdir(req.path)) { if (mkdirRecursive(fullPath.c_str())) {
res.status_code = 200; res.status_code = 200;
res.which_payload = api_Response_empty_message_tag; res.which_payload = api_Response_empty_message_tag;
return WebServer::send(request, 200, res, api_Response_fields); return WebServer::send(request, 200, res, api_Response_fields);
} else { } else {
return WebServer::sendError(request, 500, "mkdir failed"); return WebServer::sendError(request, 500, "mkdir failed");
} }
bool editFile(const char *filename, const char *content) { return writeFile(filename, content); }
esp_err_t mkdir(httpd_req_t *request, JsonVariant &json) {
const char *path = json["path"].as<const char *>();
std::string fullPath = std::string(MOUNT_POINT) + path;
ESP_LOGI(TAG, "Creating directory: %s", fullPath.c_str());
return mkdirRecursive(fullPath.c_str()) ? WebServer::sendOk(request)
: WebServer::sendError(request, 500, "mkdir failed");
} }
} // namespace FileSystem } // namespace FileSystem
+7 -9
View File
@@ -80,27 +80,24 @@ void setupServer() {
server.on("/api/ap/status", HTTP_GET, [&](httpd_req_t *request) { return apService.getStatusProto(request); }); server.on("/api/ap/status", HTTP_GET, [&](httpd_req_t *request) { return apService.getStatusProto(request); });
server.on("/api/ap/settings", HTTP_GET, server.on("/api/ap/settings", HTTP_GET,
[&](httpd_req_t *request) { return apService.protoEndpoint.getState(request); }); [&](httpd_req_t *request) { return apService.protoEndpoint.getState(request); });
server.on("/api/ap/settings", HTTP_POST, server.on("/api/ap/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
[&](httpd_req_t *request, api_Request *protoReq) {
return apService.protoEndpoint.handleStateUpdate(request, protoReq); return apService.protoEndpoint.handleStateUpdate(request, protoReq);
}); });
server.on("/api/peripherals/settings", HTTP_GET, server.on("/api/peripherals/settings", HTTP_GET,
[&](httpd_req_t *request) { return peripherals.protoEndpoint.getState(request); }); [&](httpd_req_t *request) { return peripherals.protoEndpoint.getState(request); });
server.on("/api/peripherals/settings", HTTP_POST, server.on("/api/peripherals/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
[&](httpd_req_t *request, api_Request *protoReq) {
return peripherals.protoEndpoint.handleStateUpdate(request, protoReq); return peripherals.protoEndpoint.handleStateUpdate(request, protoReq);
}); });
#if FT_ENABLED(USE_MDNS) #if FT_ENABLED(USE_MDNS)
server.on("/api/mdns/settings", HTTP_GET, [&](httpd_req_t *request) { return mdnsService.protoEndpoint.getState(request); }); server.on("/api/mdns/settings", HTTP_GET,
server.on("/api/mdns/settings", HTTP_POST, [&](httpd_req_t *request) { return mdnsService.protoEndpoint.getState(request); });
[&](httpd_req_t *request, api_Request *protoReq) { server.on("/api/mdns/settings", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
return mdnsService.protoEndpoint.handleStateUpdate(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/status", HTTP_GET, [&](httpd_req_t *request) { return mdnsService.getStatus(request); });
server.on("/api/mdns/query", HTTP_POST, server.on("/api/mdns/query", HTTP_POST, [&](httpd_req_t *request, api_Request *protoReq) {
[&](httpd_req_t *request, api_Request *protoReq) {
return mdnsService.queryServices(request, protoReq); return mdnsService.queryServices(request, protoReq);
}); });
#endif #endif
@@ -277,6 +274,7 @@ void IRAM_ATTR SpotControlLoopEntry(void *) {
void IRAM_ATTR serviceLoopEntry(void *) { void IRAM_ATTR serviceLoopEntry(void *) {
ESP_LOGI("main", "Service task starting"); ESP_LOGI("main", "Service task starting");
WiFi.init();
wifiService.begin(); wifiService.begin();
mdns_init(); mdns_init();
mdns_hostname_set(APP_NAME); mdns_hostname_set(APP_NAME);
+25 -70
View File
@@ -8,9 +8,8 @@ MDNSService::MDNSService()
: protoEndpoint(MDNSSettings_read, MDNSSettings_update, this, : protoEndpoint(MDNSSettings_read, MDNSSettings_update, this,
API_REQUEST_EXTRACTOR(mdns_settings, api_MDNSSettings), API_REQUEST_EXTRACTOR(mdns_settings, api_MDNSSettings),
API_RESPONSE_ASSIGNER(mdns_settings, api_MDNSSettings)), API_RESPONSE_ASSIGNER(mdns_settings, api_MDNSSettings)),
_persistence(MDNSSettings_read, MDNSSettings_update, this, _persistence(MDNSSettings_read, MDNSSettings_update, this, MDNS_SETTINGS_FILE, api_MDNSSettings_fields,
MDNS_SETTINGS_FILE, api_MDNSSettings_fields, api_MDNSSettings_size, api_MDNSSettings_size, MDNSSettings_defaults()) {
MDNSSettings_defaults()) {
addUpdateHandler([&](const std::string &originId) { reconfigureMDNS(); }, false); addUpdateHandler([&](const std::string &originId) { reconfigureMDNS(); }, false);
} }
@@ -35,14 +34,6 @@ void MDNSService::reconfigureMDNS() {
void MDNSService::startMDNS() { void MDNSService::startMDNS() {
ESP_LOGV(TAG, "Starting MDNS with hostname: %s", state().hostname); ESP_LOGV(TAG, "Starting MDNS with hostname: %s", state().hostname);
if (MDNS.begin(state().hostname)) {
_started = true;
MDNS.setInstanceName(state().instance);
addServices();
ESP_LOGI(TAG, "MDNS started successfully with hostname: %s", state().hostname);
} else {
esp_err_t err = mdns_init(); esp_err_t err = mdns_init();
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize MDNS: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Failed to initialize MDNS: %s", esp_err_to_name(err));
@@ -50,7 +41,7 @@ void MDNSService::startMDNS() {
return; return;
} }
err = mdns_hostname_set(state().hostname.c_str()); err = mdns_hostname_set(state().hostname);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set MDNS hostname: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Failed to set MDNS hostname: %s", esp_err_to_name(err));
mdns_free(); mdns_free();
@@ -58,7 +49,7 @@ void MDNSService::startMDNS() {
return; return;
} }
err = mdns_instance_name_set(state().instance.c_str()); err = mdns_instance_name_set(state().instance);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to set MDNS instance name: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Failed to set MDNS instance name: %s", esp_err_to_name(err));
} }
@@ -66,7 +57,7 @@ void MDNSService::startMDNS() {
_started = true; _started = true;
addServices(); addServices();
ESP_LOGI(TAG, "MDNS started successfully with hostname: %s", state().hostname.c_str()); ESP_LOGI(TAG, "MDNS started successfully with hostname: %s", state().hostname);
} }
void MDNSService::stopMDNS() { void MDNSService::stopMDNS() {
@@ -78,11 +69,15 @@ void MDNSService::stopMDNS() {
void MDNSService::addServices() { void MDNSService::addServices() {
for (size_t i = 0; i < state().services_count; i++) { for (size_t i = 0; i < state().services_count; i++) {
const auto &service = state().services[i]; const auto &service = state().services[i];
MDNS.addService(service.service, service.protocol, service.port); 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 j = 0; j < service.txt_records_count; j++) { for (size_t j = 0; j < service.txt_records_count; j++) {
const auto &txt = service.txt_records[j]; const auto &txt = service.txt_records[j];
MDNS.addServiceTxt(service.service, service.protocol, txt.key, txt.value); mdns_service_txt_item_set(service.service, service.protocol, txt.key, txt.value);
} }
} }
@@ -90,25 +85,7 @@ void MDNSService::addServices() {
const auto &txt = state().global_txt_records[i]; const auto &txt = state().global_txt_records[i];
for (size_t j = 0; j < state().services_count; j++) { for (size_t j = 0; j < state().services_count; j++) {
const auto &service = state().services[j]; const auto &service = state().services[j];
MDNS.addServiceTxt(service.service, service.protocol, txt.key, txt.value); mdns_service_txt_item_set(service.service, service.protocol, txt.key, txt.value);
for (const auto &service : state().services) {
esp_err_t err =
mdns_service_add(nullptr, service.service.c_str(), service.protocol.c_str(), service.port, nullptr, 0);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to add service %s: %s", service.service.c_str(), esp_err_to_name(err));
continue;
}
for (const auto &txt : service.txtRecords) {
mdns_service_txt_item_set(service.service.c_str(), service.protocol.c_str(), txt.key.c_str(),
txt.value.c_str());
}
}
for (const auto &txt : state().globalTxtRecords) {
for (const auto &service : state().services) {
mdns_service_txt_item_set(service.service.c_str(), service.protocol.c_str(), txt.key.c_str(),
txt.value.c_str());
} }
} }
} }
@@ -143,68 +120,46 @@ esp_err_t MDNSService::queryServices(httpd_req_t *request, api_Request *protoReq
const api_MDNSQueryRequest &queryReq = protoReq->payload.mdns_query_request; const api_MDNSQueryRequest &queryReq = protoReq->payload.mdns_query_request;
ESP_LOGI(TAG, "Querying for service: %s, protocol: %s", queryReq.service, queryReq.protocol); ESP_LOGI(TAG, "Querying for service: %s, protocol: %s", queryReq.service, queryReq.protocol);
int n = MDNS.queryService(queryReq.service, queryReq.protocol); mdns_result_t *results = nullptr;
ESP_LOGI(TAG, "Found %d services", n); esp_err_t err = mdns_query_ptr(queryReq.service, queryReq.protocol, 3000, 20, &results);
api_Response response = api_Response_init_zero; api_Response response = api_Response_init_zero;
response.which_payload = api_Response_mdns_query_response_tag; response.which_payload = api_Response_mdns_query_response_tag;
api_MDNSQueryResponse &queryResp = response.payload.mdns_query_response; api_MDNSQueryResponse &queryResp = response.payload.mdns_query_response;
// Limit to max_count from options file (16)
size_t count = (n > 16) ? 16 : static_cast<size_t>(n);
queryResp.services_count = count;
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);
}
return WebServer::send(request, 200, response, api_Response_fields);
esp_err_t MDNSService::queryServices(httpd_req_t *request, JsonVariant &json) {
std::string service = json["service"].as<std::string>();
std::string proto = json["protocol"].as<std::string>();
JsonDocument doc;
JsonVariant root = doc.to<JsonVariant>();
ESP_LOGI(TAG, "Querying for service: %s, protocol: %s", service.c_str(), proto.c_str());
mdns_result_t *results = nullptr;
esp_err_t err = mdns_query_ptr(service.c_str(), proto.c_str(), 3000, 20, &results);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "MDNS query failed: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "MDNS query failed: %s", esp_err_to_name(err));
root["services"] = JsonArray(); queryResp.services_count = 0;
return WebServer::sendJson(request, 200, doc); return WebServer::send(request, 200, response, api_Response_fields);
} }
int count = 0; int count = 0;
mdns_result_t *r = results; mdns_result_t *r = results;
while (r) { while (r && count < 16) {
count++; count++;
r = r->next; r = r->next;
} }
ESP_LOGI(TAG, "Found %d services", count); ESP_LOGI(TAG, "Found %d services", count);
JsonArray servicesArray = root["services"].to<JsonArray>(); queryResp.services_count = count;
r = results; r = results;
while (r) { size_t i = 0;
JsonVariant serviceObj = servicesArray.add<JsonVariant>(); while (r && i < 16) {
if (r->hostname) { if (r->hostname) {
serviceObj["name"] = r->hostname; strncpy(queryResp.services[i].name, r->hostname, sizeof(queryResp.services[i].name) - 1);
} }
if (r->addr) { if (r->addr) {
char ip_str[16]; char ip_str[16];
esp_ip4addr_ntoa(&r->addr->addr.u_addr.ip4, ip_str, sizeof(ip_str)); esp_ip4addr_ntoa(&r->addr->addr.u_addr.ip4, ip_str, sizeof(ip_str));
serviceObj["ip"] = ip_str; strncpy(queryResp.services[i].ip, ip_str, sizeof(queryResp.services[i].ip) - 1);
} }
serviceObj["port"] = r->port; queryResp.services[i].port = r->port;
r = r->next; r = r->next;
i++;
} }
mdns_query_results_free(results); mdns_query_results_free(results);
return WebServer::sendJson(request, 200, doc); return WebServer::send(request, 200, response, api_Response_fields);
} }
+6 -2
View File
@@ -8,7 +8,7 @@
#include <esp_sleep.h> #include <esp_sleep.h>
#include <soc/soc.h> #include <soc/soc.h>
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6
#include <driver/temperature_sensor.h> #include <driver/temperature_sensor.h>
static float temperatureRead() { static float temperatureRead() {
@@ -16,7 +16,11 @@ static float temperatureRead() {
static bool initialized = false; static bool initialized = false;
if (!initialized) { if (!initialized) {
temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80); 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) { if (temperature_sensor_install(&temp_sensor_config, &temp_sensor) == ESP_OK) {
temperature_sensor_enable(temp_sensor); temperature_sensor_enable(temp_sensor);
initialized = true; initialized = true;
+16 -70
View File
@@ -4,15 +4,13 @@
static const char *TAG = "WiFiService"; static const char *TAG = "WiFiService";
WiFiService::WiFiService() WiFiService::WiFiService()
: _persistence(WiFiSettings_read, WiFiSettings_update, this, WIFI_SETTINGS_FILE, : protoEndpoint(WiFiSettings_read, WiFiSettings_update, this,
api_WifiSettings_fields, api_WifiSettings_size, WiFiSettings_defaults()),
protoEndpoint(WiFiSettings_read, WiFiSettings_update, this,
API_REQUEST_EXTRACTOR(wifi_settings, api_WifiSettings), API_REQUEST_EXTRACTOR(wifi_settings, api_WifiSettings),
API_RESPONSE_ASSIGNER(wifi_settings, api_WifiSettings)) { API_RESPONSE_ASSIGNER(wifi_settings, api_WifiSettings)),
: _persistence(WiFiSettings::read, WiFiSettings::update, this, WIFI_SETTINGS_FILE), _persistence(WiFiSettings_read, WiFiSettings_update, this, WIFI_SETTINGS_FILE, api_WifiSettings_fields,
api_WifiSettings_size, WiFiSettings_defaults()),
_lastConnectionAttempt(0), _lastConnectionAttempt(0),
_stopping(false), _stopping(false) {
endpoint(WiFiSettings::read, WiFiSettings::update, this) {
addUpdateHandler([&](const std::string &originId) { reconfigureWiFiConnection(); }, false); addUpdateHandler([&](const std::string &originId) { reconfigureWiFiConnection(); }, false);
} }
@@ -30,13 +28,10 @@ void WiFiService::begin() {
_persistence.readFromFS(); _persistence.readFromFS();
_lastConnectionAttempt = 0; _lastConnectionAttempt = 0;
if (state().wifi_networks_count == 1) { if (state().wifi_networks_count >= 1) {
configureNetwork(state().wifi_networks[0]);
vTaskDelay(500 / portTICK_PERIOD_MS);
if (!state().wifiSettings.empty()) {
WiFi.mode(WIFI_MODE_STA); WiFi.mode(WIFI_MODE_STA);
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
configureNetwork(state().wifiSettings[0]); configureNetwork(state().wifi_networks[0]);
} }
} }
@@ -52,7 +47,6 @@ esp_err_t WiFiService::handleScan(httpd_req_t *request) {
WiFi.scanDelete(); WiFi.scanDelete();
WiFi.scanNetworks(true); WiFi.scanNetworks(true);
} }
// Return 202 with empty_message payload (no pointer fields to encode)
api_Response response = api_Response_init_zero; api_Response response = api_Response_init_zero;
response.status_code = 202; response.status_code = 202;
response.which_payload = api_Response_empty_message_tag; response.which_payload = api_Response_empty_message_tag;
@@ -62,7 +56,6 @@ esp_err_t WiFiService::handleScan(httpd_req_t *request) {
esp_err_t WiFiService::getNetworks(httpd_req_t *request) { esp_err_t WiFiService::getNetworks(httpd_req_t *request) {
int numNetworks = WiFi.scanComplete(); int numNetworks = WiFi.scanComplete();
if (numNetworks == -1) { if (numNetworks == -1) {
// Scan in progress - return 202 with empty_message payload
api_Response response = api_Response_init_zero; api_Response response = api_Response_init_zero;
response.status_code = 202; response.status_code = 202;
response.which_payload = api_Response_empty_message_tag; response.which_payload = api_Response_empty_message_tag;
@@ -71,10 +64,8 @@ esp_err_t WiFiService::getNetworks(httpd_req_t *request) {
return handleScan(request); return handleScan(request);
} }
// Limit to 20 networks max
size_t count = (numNetworks > 20) ? 20 : static_cast<size_t>(numNetworks); size_t count = (numNetworks > 20) ? 20 : static_cast<size_t>(numNetworks);
// Allocate networks array on stack (pointer type in proto)
api_WifiNetworkScan networks[20] = {}; api_WifiNetworkScan networks[20] = {};
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
@@ -95,15 +86,8 @@ esp_err_t WiFiService::getNetworks(httpd_req_t *request) {
} }
void WiFiService::setupMDNS(const char *hostname) { void WiFiService::setupMDNS(const char *hostname) {
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);
}
mdns_init(); mdns_init();
mdns_hostname_set(state().hostname.c_str()); mdns_hostname_set(state().hostname);
mdns_instance_name_set(hostname); mdns_instance_name_set(hostname);
mdns_service_add(nullptr, "_http", "_tcp", 80, nullptr, 0); mdns_service_add(nullptr, "_http", "_tcp", 80, nullptr, 0);
mdns_service_add(nullptr, "_ws", "_tcp", 80, nullptr, 0); mdns_service_add(nullptr, "_ws", "_tcp", 80, nullptr, 0);
@@ -111,19 +95,6 @@ void WiFiService::setupMDNS(const char *hostname) {
mdns_service_txt_set("_http", "_tcp", &txtData, 1); mdns_service_txt_set("_http", "_tcp", &txtData, 1);
} }
void WiFiService::getNetworks(JsonObject &root) {
JsonArray networks = root["networks"].to<JsonArray>();
int numNetworks = WiFi.scanComplete();
for (int i = 0; i < numNetworks; i++) {
JsonObject network = networks.add<JsonObject>();
network["rssi"] = WiFi.RSSI(i);
network["ssid"] = WiFi.SSID(i).c_str();
network["bssid"] = WiFi.BSSIDstr(i).c_str();
network["channel"] = WiFi.channel(i);
network["encryption_type"] = (uint8_t)WiFi.encryptionType(i);
}
}
esp_err_t WiFiService::getNetworkStatus(httpd_req_t *request) { esp_err_t WiFiService::getNetworkStatus(httpd_req_t *request) {
api_Response response = api_Response_init_zero; api_Response response = api_Response_init_zero;
response.which_payload = api_Response_wifi_status_tag; response.which_payload = api_Response_wifi_status_tag;
@@ -141,15 +112,6 @@ esp_err_t WiFiService::getNetworkStatus(httpd_req_t *request) {
wifiStatus.channel = WiFi.channel(); wifiStatus.channel = WiFi.channel();
wifiStatus.subnet_mask = static_cast<uint32_t>(WiFi.subnetMask()); wifiStatus.subnet_mask = static_cast<uint32_t>(WiFi.subnetMask());
wifiStatus.gateway_ip = static_cast<uint32_t>(WiFi.gatewayIP()); wifiStatus.gateway_ip = static_cast<uint32_t>(WiFi.gatewayIP());
root["local_ip"] = (uint32_t)(WiFi.localIP());
root["mac_address"] = WiFi.macAddress().c_str();
root["rssi"] = WiFi.RSSI();
root["ssid"] = WiFi.SSID().c_str();
root["bssid"] = WiFi.BSSIDstr().c_str();
root["channel"] = WiFi.channel();
root["subnet_mask"] = (uint32_t)(WiFi.subnetMask());
root["gateway_ip"] = (uint32_t)(WiFi.gatewayIP());
IPAddress dnsIP1 = WiFi.dnsIP(0); IPAddress dnsIP1 = WiFi.dnsIP(0);
IPAddress dnsIP2 = WiFi.dnsIP(1); IPAddress dnsIP2 = WiFi.dnsIP(1);
if (dnsIP1 != IPAddress(0, 0, 0, 0)) { if (dnsIP1 != IPAddress(0, 0, 0, 0)) {
@@ -165,8 +127,10 @@ esp_err_t WiFiService::getNetworkStatus(httpd_req_t *request) {
void WiFiService::manageSTA() { void WiFiService::manageSTA() {
if (WiFi.isConnected() || state().wifi_networks_count == 0) return; if (WiFi.isConnected() || state().wifi_networks_count == 0) return;
if ((WiFi.getMode() & WIFI_STA) == 0) connectToWiFi(); if ((WiFi.getMode() & WIFI_MODE_STA) == 0) {
if (WiFi.isConnected() || state().wifiSettings.empty()) return; WiFi.mode(WIFI_MODE_STA);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
connectToWiFi(); connectToWiFi();
} }
@@ -193,11 +157,7 @@ void WiFiService::connectToWiFi() {
for (pb_size_t j = 0; j < state().wifi_networks_count; j++) { for (pb_size_t j = 0; j < state().wifi_networks_count; j++) {
WiFiNetwork &network = state().wifi_networks[j]; WiFiNetwork &network = state().wifi_networks[j];
for (auto &network : state().wifiSettings) {
if (ssid_scan == network.ssid) { if (ssid_scan == network.ssid) {
if (rssi_scan >= FACTORY_WIFI_RSSI_THRESHOLD) {
// Network is available
}
if (rssi_scan > bestNetworkDb) { if (rssi_scan > bestNetworkDb) {
bestNetworkDb = rssi_scan; bestNetworkDb = rssi_scan;
bestNetwork = &network; bestNetwork = &network;
@@ -209,10 +169,9 @@ void WiFiService::connectToWiFi() {
if (!state().priority_rssi) { if (!state().priority_rssi) {
for (pb_size_t j = 0; j < state().wifi_networks_count; j++) { for (pb_size_t j = 0; j < state().wifi_networks_count; j++) {
WiFiNetwork &network = state().wifi_networks[j]; WiFiNetwork &network = state().wifi_networks[j];
// Check if this network was found in scan
for (int i = 0; i < scanResult; ++i) { for (int i = 0; i < scanResult; ++i) {
if (WiFi.SSID(i) == network.ssid) { if (WiFi.SSID(i) == network.ssid) {
ESP_LOGI("WiFiSettingsService", "Connecting to first available network: %s", network.ssid); ESP_LOGI(TAG, "Connecting to first available network: %s", network.ssid);
configureNetwork(network); configureNetwork(network);
WiFi.scanDelete(); WiFi.scanDelete();
return; return;
@@ -220,17 +179,7 @@ void WiFiService::connectToWiFi() {
} }
} }
} else if (bestNetwork) { } else if (bestNetwork) {
ESP_LOGI("WiFiSettingsService", "Connecting to strongest network: %s", bestNetwork->ssid); ESP_LOGI(TAG, "Connecting to strongest network: %s", bestNetwork->ssid);
if (!state().priorityBySignalStrength) {
for (auto &network : state().wifiSettings) {
if (network.available == true) {
ESP_LOGI(TAG, "Connecting to first available network: %s", network.ssid.c_str());
configureNetwork(network);
break;
}
}
} else if (state().priorityBySignalStrength && bestNetwork) {
ESP_LOGI(TAG, "Connecting to strongest network: %s", bestNetwork->ssid.c_str());
configureNetwork(*bestNetwork); configureNetwork(*bestNetwork);
} else { } else {
ESP_LOGI(TAG, "No known networks found."); ESP_LOGI(TAG, "No known networks found.");
@@ -242,16 +191,13 @@ void WiFiService::connectToWiFi() {
void WiFiService::configureNetwork(WiFiNetwork &network) { void WiFiService::configureNetwork(WiFiNetwork &network) {
if (network.static_ip_config) { if (network.static_ip_config) {
WiFi.config(IPAddress(network.local_ip), IPAddress(network.gateway_ip), WiFi.config(IPAddress(network.local_ip), IPAddress(network.gateway_ip), IPAddress(network.subnet_mask),
IPAddress(network.subnet_mask), IPAddress(network.dns_ip_1), IPAddress(network.dns_ip_2)); IPAddress(network.dns_ip_1), IPAddress(network.dns_ip_2));
} else { } else {
WiFi.config(IPAddress(0, 0, 0, 0), IPAddress(0, 0, 0, 0), IPAddress(0, 0, 0, 0)); WiFi.config(IPAddress(0, 0, 0, 0), IPAddress(0, 0, 0, 0), IPAddress(0, 0, 0, 0));
} }
WiFi.setHostname(state().hostname); WiFi.setHostname(state().hostname);
WiFi.begin(network.ssid, network.password); WiFi.begin(network.ssid, network.password);
WiFi.setHostname(state().hostname.c_str());
WiFi.begin(network.ssid.c_str(), network.password.c_str());
#if CONFIG_IDF_TARGET_ESP32C3 #if CONFIG_IDF_TARGET_ESP32C3
WiFi.setTxPower(8); WiFi.setTxPower(8);
+1 -3
View File
@@ -111,13 +111,11 @@ board_build.filesystem = littlefs
board_build.embed_txtfiles = board_build.embed_txtfiles =
board_build.sdkconfig_defaults = esp32/sdkconfig.defaults board_build.sdkconfig_defaults = esp32/sdkconfig.defaults
lib_deps = lib_deps =
bblanchon/ArduinoJson@^7.0.0
lib_ldf_mode = deep lib_ldf_mode = deep
lib_compat_mode = off lib_compat_mode = strict
extra_scripts = extra_scripts =
pre:esp32/scripts/pre_build.py pre:esp32/scripts/pre_build.py
pre:esp32/scripts/build_app.py pre:esp32/scripts/build_app.py
lib_compat_mode = strict
; debug_tool = esp-builtin ; debug_tool = esp-builtin
; debug_init_break = ; 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.