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