🚚 Moves firmware to src and include

This commit is contained in:
Rune Harlyk
2025-07-11 12:12:07 +02:00
committed by Rune Harlyk
parent 743aa073b7
commit a3be035f98
60 changed files with 2 additions and 3 deletions
+101
View File
@@ -0,0 +1,101 @@
#include <ap_service.h>
static const char *TAG = "APService";
APService::APService()
: endpoint(APSettings::read, APSettings::update, this),
_persistence(APSettings::read, APSettings::update, this, AP_SETTINGS_FILE) {
addUpdateHandler([&](const String &originId) { reconfigureAP(); }, false);
}
APService::~APService() {}
void APService::begin() { _persistence.readFromFS(); }
esp_err_t APService::getStatus(PsychicRequest *request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject root = response.getRoot();
status(root);
return response.send();
}
void APService::status(JsonObject &root) {
root["status"] = getAPNetworkStatus();
root["ip_address"] = WiFi.softAPIP().toString();
root["mac_address"] = WiFi.softAPmacAddress();
root["station_num"] = WiFi.softAPgetStationNum();
}
APNetworkStatus APService::getAPNetworkStatus() {
WiFiMode_t currentWiFiMode = WiFi.getMode();
bool apActive = currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA;
if (apActive && state().provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
return APNetworkStatus::LINGERING;
}
return apActive ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE;
}
void APService::reconfigureAP() {
_lastManaged = millis() - MANAGE_NETWORK_DELAY;
_reconfigureAp = true;
_recoveryMode = false;
}
void APService::recoveryMode() {
ESP_LOGI(TAG, "Recovery Mode needed");
_lastManaged = millis() - MANAGE_NETWORK_DELAY;
_recoveryMode = true;
_reconfigureAp = true;
}
void APService::loop() {
EXECUTE_EVERY_N_MS(MANAGE_NETWORK_DELAY, manageAP());
handleDNS();
}
void APService::manageAP() {
WiFiMode_t currentWiFiMode = WiFi.getMode();
if (state().provisionMode == AP_MODE_ALWAYS ||
(state().provisionMode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED) || _recoveryMode) {
if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
startAP();
}
} else if ((currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA) &&
(_reconfigureAp || !WiFi.softAPgetStationNum())) {
stopAP();
}
_reconfigureAp = false;
}
void APService::startAP() {
ESP_LOGI(TAG, "Starting software access point: %s", state().ssid.c_str());
WiFi.softAPConfig(state().localIP, state().gatewayIP, state().subnetMask);
WiFi.softAP(state().ssid.c_str(), state().password.c_str(), state().channel, state().ssidHidden,
state().maxClients);
#if CONFIG_IDF_TARGET_ESP32C3
WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
#endif
if (!_dnsServer) {
IPAddress apIp = WiFi.softAPIP();
ESP_LOGI(TAG, "Starting captive portal on %s", apIp.toString().c_str());
_dnsServer = new DNSServer;
_dnsServer->start(DNS_PORT, "*", apIp);
}
}
void APService::stopAP() {
if (_dnsServer) {
ESP_LOGI(TAG, "Stopping captive portal");
_dnsServer->stop();
delete _dnsServer;
_dnsServer = nullptr;
}
ESP_LOGI(TAG, "Stopping AP");
WiFi.softAPdisconnect(true);
}
void APService::handleDNS() {
if (_dnsServer) {
_dnsServer->processNextRequest();
}
}
+176
View File
@@ -0,0 +1,176 @@
#include <event_socket.h>
SemaphoreHandle_t clientSubscriptionsMutex = xSemaphoreCreateMutex();
EventSocket::EventSocket() {
_socket.onOpen((std::bind(&EventSocket::onWSOpen, this, std::placeholders::_1)));
_socket.onClose(std::bind(&EventSocket::onWSClose, this, std::placeholders::_1));
_socket.onFrame(std::bind(&EventSocket::onFrame, this, std::placeholders::_1, std::placeholders::_2));
}
void EventSocket::onWSOpen(PsychicWebSocketClient *client) {
ESP_LOGI("EventSocket", "ws[%s][%u] connect", client->remoteIP().toString().c_str(), client->socket());
}
void EventSocket::onWSClose(PsychicWebSocketClient *client) {
xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY);
for (auto &event_subscriptions : client_subscriptions) {
event_subscriptions.second.remove(client->socket());
}
xSemaphoreGive(clientSubscriptionsMutex);
ESP_LOGI("EventSocket", "ws[%s][%u] disconnect", client->remoteIP().toString().c_str(), client->socket());
}
esp_err_t EventSocket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
ESP_LOGV("EventSocket", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString().c_str(),
request->client()->socket(), frame->type);
JsonDocument doc;
#if USE_MSGPACK
if (frame->type != HTTPD_WS_TYPE_BINARY) {
ESP_LOGE("EventSocket", "Unsupported frame type: %d", frame->type);
return ESP_OK;
}
if (deserializeMsgPack(doc, frame->payload, frame->len)) {
ESP_LOGE("EventSocket", "Could not deserialize msgpack");
return ESP_OK;
};
#else
if (frame->type != HTTPD_WS_TYPE_TEXT) {
ESP_LOGE("EventSocket", "Unsupported frame type: %d", frame->type);
return ESP_OK;
}
if (deserializeJson(doc, frame->payload, frame->len)) {
ESP_LOGE("EventSocket", "Could not deserialize json");
return ESP_OK;
};
#endif
auto msg = doc.as<JsonArray>();
message_type_t message_type = static_cast<message_type_t>(msg[0].as<uint8_t>());
if (message_type == PONG) {
ESP_LOGV("EventSocket", "Pong");
return ESP_OK;
} else if (message_type == PING) {
ESP_LOGV("EventSocket", "Ping");
#if USE_MSGPACK
const uint8_t out[] = {0x91, 0x04};
send(request->client(), reinterpret_cast<const char *>(out), sizeof(out));
#else
const char *out = "[4]";
send(request->client(), out, strlen(out));
#endif
return ESP_OK;
}
const char *event = msg[1].as<const char *>();
if (!event) {
ESP_LOGE("EventSocket", "Invalid event name");
return ESP_OK;
}
if (message_type == CONNECT) {
ESP_LOGV("EventSocket", "Connect: %s", event);
client_subscriptions[event].push_back(request->client()->socket());
handleSubscribeCallbacks(event, String(request->client()->socket()));
} else if (message_type == DISCONNECT) {
ESP_LOGV("EventSocket", "Disconnect: %s", event);
client_subscriptions[event].remove(request->client()->socket());
} else if (message_type == EVENT) {
JsonVariant payload = msg[2].as<JsonVariant>();
handleEventCallbacks(event, payload, request->client()->socket());
return ESP_OK;
}
return ESP_OK;
}
bool EventSocket::hasSubscribers(const char *event) { return !client_subscriptions[event].empty(); }
void EventSocket::emit(const char *event, JsonVariant &payload, const char *originId, bool onlyToSameOrigin) {
int originSubscriptionId = originId[0] ? atoi(originId) : -1;
xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY);
auto &subscriptions = client_subscriptions[event];
if (subscriptions.empty()) {
xSemaphoreGive(clientSubscriptionsMutex);
return;
}
JsonDocument doc;
auto a = doc.to<JsonArray>();
a.add(static_cast<uint8_t>(message_type_t::EVENT));
a.add(event);
a.add(payload);
#if USE_MSGPACK
static char out[512];
size_t len = serializeMsgPack(doc, out, sizeof(out));
if (len == 0 || len >= sizeof(out)) {
xSemaphoreGive(clientSubscriptionsMutex);
ESP_LOGE("EventSocket", "Message payload bigger than buffer (%d <= %d)", sizeof(out), len);
return;
}
const char *data = out;
#else
static char out[1024];
size_t len = serializeJson(doc, out, sizeof(out));
if (len == 0 || len >= sizeof(out)) {
xSemaphoreGive(clientSubscriptionsMutex);
ESP_LOGE("EventSocket", "Message payload bigger than buffer (%d <= %d)", sizeof(out), len);
return;
}
const char *data = out;
#endif
auto sendTo = [&](int id) {
if (auto *c = _socket.getClient(id)) {
send(c, data, len);
} else {
subscriptions.remove(id);
}
};
if (onlyToSameOrigin && originSubscriptionId > 0) {
sendTo(originSubscriptionId);
} else {
for (int id : subscriptions) {
if (id != originSubscriptionId) sendTo(id);
}
}
xSemaphoreGive(clientSubscriptionsMutex);
}
void EventSocket::send(PsychicWebSocketClient *client, const char *data, size_t len) {
if (!client) return;
#if USE_MSGPACK
client->sendMessage(HTTPD_WS_TYPE_BINARY, data, len);
#else
client->sendMessage(HTTPD_WS_TYPE_TEXT, data, len);
#endif
}
void EventSocket::handleEventCallbacks(String event, JsonVariant &jsonObject, int originId) {
for (auto &callback : event_callbacks[event]) {
callback(jsonObject, originId);
}
}
void EventSocket::handleSubscribeCallbacks(String event, const String &originId) {
for (auto &callback : subscribe_callbacks[event]) {
callback(originId, true);
}
}
void EventSocket::onEvent(String event, EventCallback callback) {
event_callbacks[event].push_back(std::move(callback));
}
void EventSocket::onSubscribe(String event, SubscribeCallback callback) {
subscribe_callbacks[event].push_back(std::move(callback));
}
EventSocket socket;
+65
View File
@@ -0,0 +1,65 @@
#include <features.h>
namespace feature_service {
// New function to print all feature flags to log
void printFeatureConfiguration() {
ESP_LOGI("Features", "====================== FEATURE FLAGS ======================");
ESP_LOGI("Features", "Firmware version: %s, name: %s, target: %s", APP_VERSION, APP_NAME, BUILD_TARGET);
// Core features
ESP_LOGI("Features", "USE_UPLOAD_FIRMWARE: %s", USE_UPLOAD_FIRMWARE ? "enabled" : "disabled");
ESP_LOGI("Features", "USE_DOWNLOAD_FIRMWARE: %s", USE_DOWNLOAD_FIRMWARE ? "enabled" : "disabled");
ESP_LOGI("Features", "USE_SLEEP: %s", USE_SLEEP ? "enabled" : "disabled");
ESP_LOGI("Features", "USE_CAMERA: %s", USE_CAMERA ? "enabled" : "disabled");
ESP_LOGI("Features", "USE_MOTION: %s", USE_MOTION ? "enabled" : "disabled");
// Sensors
ESP_LOGI("Features", "USE_BNO055: %s", USE_BNO055 ? "enabled" : "disabled");
ESP_LOGI("Features", "USE_MPU6050: %s", USE_MPU6050 ? "enabled" : "disabled");
ESP_LOGI("Features", "USE_HMC5883: %s", USE_HMC5883 ? "enabled" : "disabled");
ESP_LOGI("Features", "USE_BMP180: %s", USE_BMP180 ? "enabled" : "disabled");
ESP_LOGI("Features", "USE_USS: %s", USE_USS ? "enabled" : "disabled");
ESP_LOGI("Features", "USE_GPS: %s", USE_GPS ? "enabled" : "disabled");
// Peripherals
ESP_LOGI("Features", "USE_PCA9685: %s", USE_PCA9685 ? "enabled" : "disabled");
ESP_LOGI("Features", "USE_WS2812: %s", USE_WS2812 ? "enabled" : "disabled");
// Web services
ESP_LOGI("Features", "USE_MDNS: %s", USE_MDNS ? "enabled" : "disabled");
ESP_LOGI("Features", "EMBED_WWW: %s", EMBED_WWW ? "enabled" : "disabled");
ESP_LOGI("Features", "ENABLE_CORS: %s", ENABLE_CORS ? "enabled" : "disabled");
ESP_LOGI("Features", "SERVE_CONFIG_FILES: %s", SERVE_CONFIG_FILES ? "enabled" : "disabled");
ESP_LOGI("Features", "==========================================================");
}
void features(JsonObject &root) {
root["upload_firmware"] = USE_UPLOAD_FIRMWARE;
root["download_firmware"] = USE_DOWNLOAD_FIRMWARE;
root["sleep"] = USE_SLEEP;
root["camera"] = USE_CAMERA;
root["imu"] = USE_MPU6050 || USE_BNO055;
root["mag"] = USE_HMC5883 || USE_BNO055;
root["bmp"] = USE_BMP180;
root["sonar"] = USE_USS;
root["motion"] = USE_MOTION;
root["servo"] = USE_PCA9685;
root["ws2812"] = USE_WS2812;
root["mdns"] = USE_MDNS;
root["embed_www"] = EMBED_WWW;
root["enable_cors"] = ENABLE_CORS;
root["serve_config_files"] = SERVE_CONFIG_FILES;
root["firmware_version"] = APP_VERSION;
root["firmware_name"] = APP_NAME;
root["firmware_built_target"] = BUILD_TARGET;
}
esp_err_t getFeatures(PsychicRequest *request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject root = response.getRoot();
features(root);
return response.send();
}
} // namespace feature_service
+112
View File
@@ -0,0 +1,112 @@
#include <filesystem.h>
static const char *TAG = "FileService";
namespace FileSystem {
PsychicUploadHandler *uploadHandler;
class Initializer {
public:
Initializer() {
uploadHandler = new PsychicUploadHandler();
uploadHandler->onUpload(uploadFile);
uploadHandler->onRequest([](PsychicRequest *request) { return request->reply(200); });
}
};
static Initializer initializer;
esp_err_t getFiles(PsychicRequest *request) { return request->reply(200, "application/json", listFiles("/").c_str()); }
esp_err_t handleDelete(PsychicRequest *request, JsonVariant &json) {
if (json.is<JsonObject>()) {
const char *filename = json["file"].as<const char *>();
ESP_LOGI(TAG, "Deleting file: %s", filename);
return deleteFile(filename) ? request->reply(200) : request->reply(500);
}
return request->reply(400);
}
esp_err_t handleEdit(PsychicRequest *request, JsonVariant &json) {
if (json.is<JsonObject>()) {
const char *filename = json["file"].as<const char *>();
const char *content = json["content"].as<const char *>();
ESP_LOGI(TAG, "Editing file: %s", filename);
return editFile(filename, content) ? request->reply(200) : request->reply(500);
}
return request->reply(400);
}
/* Helpers */
bool deleteFile(const char *filename) { return ESPFS.remove(filename); }
String listFiles(const String &directory, bool isRoot) {
File root = ESPFS.open(directory.startsWith("/") ? directory : "/" + directory);
if (!root.isDirectory()) return "{}";
File file = root.openNextFile();
if (!file) {
return isRoot ? "{ \"root\": {} }" : "{}";
}
String output = isRoot ? "{ \"root\": {" : "{";
while (file) {
String name = String(file.name());
if (file.isDirectory()) {
output += "\"" + name + "\": " + listFiles(name, false);
} else {
output += "\"" + name + "\": " + String(file.size());
}
File next = root.openNextFile();
if (next) output += ", ";
file = next;
}
output += "}";
if (isRoot) output += "}";
return output;
}
esp_err_t uploadFile(PsychicRequest *request, const String &filename, uint64_t index, uint8_t *data, size_t len,
bool last) {
File file;
String path = "/www/" + filename;
ESP_LOGI(TAG, "Writing %d/%d bytes to: %s\n", (int)index + (int)len, request->contentLength(), path.c_str());
if (last) ESP_LOGI(TAG, "%s is finished. Total bytes: %d\n", path.c_str(), (int)index + (int)len);
file = LittleFS.open(path, !index ? FILE_WRITE : FILE_APPEND);
if (!file) {
ESP_LOGE(TAG, "Failed to open file");
return ESP_FAIL;
}
if (!file.write(data, len)) {
ESP_LOGE(TAG, "Write failed");
return ESP_FAIL;
}
return ESP_OK;
}
bool editFile(const char *filename, const char *content) {
File file = ESPFS.open(filename, FILE_WRITE);
if (!file) return false;
file.print(content);
file.close();
return true;
}
esp_err_t mkdir(PsychicRequest *request, JsonVariant &json) {
const char *path = json["path"].as<const char *>();
ESP_LOGI(TAG, "Creating directory: %s", path);
return ESPFS.mkdir(path) ? request->reply(200) : request->reply(500);
}
} // namespace FileSystem
+105
View File
@@ -0,0 +1,105 @@
/**
* ESP32 SvelteKit
*
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
* https://github.com/theelims/ESP32-sveltekit
*
* Copyright (C) 2023 theelims
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
**/
#include <firmware_download_service.h>
extern const uint8_t rootca_crt_bundle_start[] asm("_binary_src_certs_x509_crt_bundle_bin_start");
static int previousProgress = 0;
JsonVariant obj;
void update_started() {
obj["status"] = "preparing";
socket.emit(EVENT_DOWNLOAD_OTA, obj);
}
void update_progress(int currentBytes, int totalBytes) {
obj["status"] = "progress";
int progress = ((currentBytes * 100) / totalBytes);
if (progress > previousProgress) {
obj["progress"] = progress;
socket.emit(EVENT_DOWNLOAD_OTA, obj);
ESP_LOGV("Download OTA", "HTTP update process at %d of %d bytes... (%d %%)", currentBytes, totalBytes,
progress);
}
previousProgress = progress;
}
void update_finished() {
obj["status"] = "finished";
socket.emit(EVENT_DOWNLOAD_OTA, obj);
// delay to allow the event to be sent out
vTaskDelay(100 / portTICK_PERIOD_MS);
}
void updateTask(void *param) {
WiFiClientSecure client;
client.setCACertBundle(rootca_crt_bundle_start);
client.setTimeout(10);
httpUpdate.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
httpUpdate.rebootOnUpdate(true);
String url = *((String *)param);
// httpUpdate.onStart(update_started);
// httpUpdate.onProgress(update_progress);
// httpUpdate.onEnd(update_finished);
t_httpUpdate_return ret = httpUpdate.update(client, url.c_str());
switch (ret) {
case HTTP_UPDATE_FAILED:
obj["status"] = "error";
obj["error"] = httpUpdate.getLastErrorString().c_str();
socket.emit(EVENT_DOWNLOAD_OTA, obj);
ESP_LOGE("Download OTA", "HTTP Update failed with error (%d): %s", httpUpdate.getLastError(),
httpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
obj["status"] = "error";
obj["error"] = "Update failed, has same firmware version";
socket.emit(EVENT_DOWNLOAD_OTA, obj);
ESP_LOGE("Download OTA", "HTTP Update failed, has same firmware version");
break;
case HTTP_UPDATE_OK: ESP_LOGI("Download OTA", "HTTP Update successful - Restarting"); break;
}
vTaskDelete(NULL);
}
DownloadFirmwareService::DownloadFirmwareService() {}
esp_err_t DownloadFirmwareService::handleDownloadUpdate(PsychicRequest *request, JsonVariant &json) {
if (!json.is<JsonObject>()) {
return request->reply(400);
}
String downloadURL = json["download_url"];
ESP_LOGI("Download OTA", "Starting OTA from: %s", downloadURL.c_str());
obj["status"] = "preparing";
obj["progress"] = 0;
obj["error"] = "";
socket.emit(EVENT_DOWNLOAD_OTA, obj);
const BaseType_t taskResult = g_taskManager.createTask(&updateTask, "Firmware download", OTA_TASK_STACK_SIZE,
&downloadURL, (configMAX_PRIORITIES - 1), NULL, 1);
if (taskResult != pdPASS) {
ESP_LOGE("Download OTA", "Couldn't create download OTA task");
return request->reply(500);
}
return request->reply(200);
}
+161
View File
@@ -0,0 +1,161 @@
#include <firmware_upload_service.h>
#include <esp_app_format.h>
#include <esp_ota_ops.h>
static const char *TAG = "FirmwareUploadService";
using namespace std::placeholders;
static char md5[33] = "\0";
static size_t fsize = 0;
static FileType fileType = ft_none;
FirmwareUploadService::FirmwareUploadService() {}
void FirmwareUploadService::begin() {
uploadHandler.onUpload(std::bind(&FirmwareUploadService::handleUpload, this, _1, _2, _3, _4, _5, _6));
uploadHandler.onRequest(std::bind(&FirmwareUploadService::uploadComplete, this, _1));
uploadHandler.onClose(std::bind(&FirmwareUploadService::handleEarlyDisconnect, this));
}
esp_err_t FirmwareUploadService::handleUpload(PsychicRequest *request, const String &filename, uint64_t index,
uint8_t *data, size_t len, bool final) {
// at init
if (!index) {
// check details of the file, to see if its a valid bin or json file
std::string fname(filename.c_str());
auto position = fname.find_last_of(".");
std::string extension = fname.substr(position + 1);
fsize = request->contentLength();
fileType = ft_none;
if ((extension == "bin") && (fsize > 1000000)) {
fileType = ft_firmware;
} else if (extension == "md5") {
fileType = ft_md5;
if (len == 32) {
memcpy(md5, data, 32);
md5[32] = '\0';
}
return ESP_OK;
} else {
md5[0] = '\0';
return handleError(request,
406); // Not Acceptable - unsupported file type
}
if (fileType == ft_firmware) {
// Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip
// offset 12: esp32:0, S2:2, C3:5
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
return handleError(request, 503); // service unavailable
}
#elif CONFIG_IDF_TARGET_ESP32S2
if (len > 12 && (data[0] != 0xE9 || data[12] != 2)) {
return handleError(request, 503); // service unavailable
}
#elif CONFIG_IDF_TARGET_ESP32C3
if (len > 12 && (data[0] != 0xE9 || data[12] != 5)) {
return handleError(request, 503); // service unavailable
}
#elif CONFIG_IDF_TARGET_ESP32S3
if (len > 12 && (data[0] != 0xE9 || data[12] != 9)) {
return handleError(request, 503); // service unavailable
}
#endif
// it's firmware - initialize the ArduinoOTA updater
if (Update.begin(fsize - sizeof(esp_image_header_t))) {
ESP_LOGI(TAG, "Starting update");
if (strlen(md5) == 32) {
Update.setMD5(md5);
md5[0] = '\0';
ESP_LOGI(TAG, "Setting MD5 hash");
}
} else {
return handleError(request, 507); // failed to begin, send an error
// response Insufficient Storage
}
}
}
// if we haven't delt with an error, continue with the firmware update
if (!request->_tempObject) {
if (Update.write(data, len) != len) {
handleError(request, 500);
} else {
JsonVariant obj;
obj["status"] = "progress";
obj["progress"] = (float)Update.progress() / (float)fsize * 100.f;
socket.emit("otastatus", obj);
delay(20);
}
if (final) {
if (!Update.end(true)) {
handleError(request, 500);
} else {
JsonVariant obj;
obj["status"] = "finished", obj["progress"] = 100;
socket.emit("otastatus", obj);
ESP_LOGI(TAG, "Finish writing update");
}
}
}
return ESP_OK;
}
esp_err_t FirmwareUploadService::uploadComplete(PsychicRequest *request) {
// if we completed uploading a md5 file create a JSON response
if (fileType == ft_md5) {
if (strlen(md5) == 32) {
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject root = response.getRoot();
root["md5"] = md5;
return response.send();
}
return ESP_OK;
}
// if no error, send the success response
if (!request->_tempObject) {
ESP_LOGI(TAG, "Finish updating");
system_service::restart();
return ESP_OK;
}
// if updated has an error send 500 response and log on Serial
if (Update.hasError()) {
Update.printError(Serial);
Update.abort();
handleError(request, 500);
}
return ESP_OK;
}
esp_err_t FirmwareUploadService::handleError(PsychicRequest *request, int code) {
JsonVariant obj;
obj["status"] = "error", obj["error"] = Update.getError();
socket.emit("otastatus", obj);
// if we have had an error already, do nothing
if (request->_tempObject) {
return ESP_OK;
}
// send the error code to the client and record the error code in the temp
// object
request->_tempObject = new int(code);
return request->reply(code);
}
esp_err_t FirmwareUploadService::handleEarlyDisconnect() {
// if updated has not ended on connection close, abort it
if (!Update.end(true)) {
Update.printError(Serial);
Update.abort();
return ESP_OK;
}
return ESP_OK;
}
+101
View File
@@ -0,0 +1,101 @@
#include <mdns_service.h>
static const char *TAG = "MDNSService";
MDNSService::MDNSService()
: endpoint(MDNSSettings::read, MDNSSettings::update, this),
_persistence(MDNSSettings::read, MDNSSettings::update, this, MDNS_SETTINGS_FILE),
_started(false) {
addUpdateHandler([&](const String &originId) { reconfigureMDNS(); }, false);
}
MDNSService::~MDNSService() {
if (_started) {
stopMDNS();
}
}
void MDNSService::begin() {
_persistence.readFromFS();
startMDNS();
}
void MDNSService::reconfigureMDNS() {
if (_started) {
stopMDNS();
}
startMDNS();
}
void MDNSService::startMDNS() {
ESP_LOGV(TAG, "Starting MDNS with hostname: %s", state().hostname.c_str());
if (MDNS.begin(state().hostname.c_str())) {
_started = true;
MDNS.setInstanceName(state().instance.c_str());
addServices();
ESP_LOGI(TAG, "MDNS started successfully with hostname: %s", state().hostname.c_str());
} else {
_started = false;
ESP_LOGE(TAG, "Failed to start MDNS");
}
}
void MDNSService::stopMDNS() {
ESP_LOGV(TAG, "Stopping MDNS");
MDNS.end();
_started = false;
}
void MDNSService::addServices() {
for (const auto &service : state().services) {
MDNS.addService(service.service.c_str(), service.protocol.c_str(), service.port);
for (const auto &txt : service.txtRecords) {
MDNS.addServiceTxt(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.addServiceTxt(service.service.c_str(), service.protocol.c_str(), txt.key.c_str(), txt.value.c_str());
}
}
}
esp_err_t MDNSService::getStatus(PsychicRequest *request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonVariant root = response.getRoot();
getStatus(root);
return response.send();
}
void MDNSService::getStatus(JsonVariant &root) {
state().read(state(), root);
root["started"] = _started;
}
esp_err_t MDNSService::queryServices(PsychicRequest *request, JsonVariant &json) {
String service = json["service"].as<String>();
String proto = json["protocol"].as<String>();
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonVariant root = response.getRoot();
ESP_LOGI(TAG, "Querying for service: %s, protocol: %s", service.c_str(), proto.c_str());
int n = MDNS.queryService(service.c_str(), proto.c_str());
ESP_LOGI(TAG, "Found %d services", n);
JsonArray servicesArray = root["services"].to<JsonArray>();
for (int i = 0; i < n; i++) {
JsonVariant serviceObj = servicesArray.add<JsonVariant>();
serviceObj["name"] = MDNS.hostname(i);
serviceObj["ip"] = MDNS.IP(i);
serviceObj["port"] = MDNS.port(i);
}
return response.send();
}
+198
View File
@@ -0,0 +1,198 @@
#include <peripherals/camera_service.h>
namespace Camera {
static const char *const TAG = "CameraService";
static const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char *_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
SemaphoreHandle_t cameraMutex = xSemaphoreCreateMutex();
camera_fb_t *safe_camera_fb_get() {
camera_fb_t *fb = NULL;
if (xSemaphoreTakeRecursive(cameraMutex, portMAX_DELAY) == pdTRUE) {
fb = esp_camera_fb_get();
xSemaphoreGiveRecursive(cameraMutex);
}
return fb;
}
sensor_t *safe_sensor_get() {
sensor_t *s = NULL;
if (xSemaphoreTakeRecursive(cameraMutex, portMAX_DELAY) == pdTRUE) {
s = esp_camera_sensor_get();
}
return s;
}
void safe_sensor_return() { xSemaphoreGiveRecursive(cameraMutex); }
CameraService::CameraService()
: endpoint(CameraSettings::read, CameraSettings::update, this),
_eventEndpoint(CameraSettings::read, CameraSettings::update, this, EVENT_CAMERA_SETTINGS),
_persistence(CameraSettings::read, CameraSettings::update, this, CAMERA_SETTINGS_FILE) {
addUpdateHandler([&](const String &originId) { updateCamera(); }, false);
}
esp_err_t CameraService::begin() {
_eventEndpoint.begin();
_persistence.readFromFS();
camera_config_t camera_config;
camera_config.ledc_channel = LEDC_CHANNEL_0;
camera_config.ledc_timer = LEDC_TIMER_0;
#if FT_ENABLED(USE_CAMERA)
camera_config.pin_d0 = Y2_GPIO_NUM;
camera_config.pin_d1 = Y3_GPIO_NUM;
camera_config.pin_d2 = Y4_GPIO_NUM;
camera_config.pin_d3 = Y5_GPIO_NUM;
camera_config.pin_d4 = Y6_GPIO_NUM;
camera_config.pin_d5 = Y7_GPIO_NUM;
camera_config.pin_d6 = Y8_GPIO_NUM;
camera_config.pin_d7 = Y9_GPIO_NUM;
camera_config.pin_xclk = XCLK_GPIO_NUM;
camera_config.pin_pclk = PCLK_GPIO_NUM;
camera_config.pin_vsync = VSYNC_GPIO_NUM;
camera_config.pin_href = HREF_GPIO_NUM;
camera_config.pin_sccb_sda = SIOD_GPIO_NUM;
camera_config.pin_sccb_scl = SIOC_GPIO_NUM;
camera_config.pin_pwdn = PWDN_GPIO_NUM;
camera_config.pin_reset = RESET_GPIO_NUM;
#endif
camera_config.xclk_freq_hz = 20000000;
camera_config.pixel_format = PIXFORMAT_JPEG;
if (psramFound()) {
camera_config.frame_size = FRAMESIZE_SVGA;
camera_config.jpeg_quality = 10;
camera_config.fb_location = CAMERA_FB_IN_PSRAM;
camera_config.fb_count = 2;
camera_config.grab_mode = CAMERA_GRAB_LATEST;
} else {
camera_config.frame_size = FRAMESIZE_SVGA;
camera_config.jpeg_quality = 12;
camera_config.fb_count = 1;
}
log_i("Initializing camera");
esp_err_t err = esp_camera_init(&camera_config);
if (err == ESP_OK)
ESP_LOGI(TAG, "Camera probe successful");
else
ESP_LOGE(TAG, "Camera probe failed with error 0x%x", err);
return err;
}
esp_err_t CameraService::cameraStill(PsychicRequest *request) {
camera_fb_t *fb = safe_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "Camera capture failed");
request->reply(500, "text/plain", "Camera capture failed");
return ESP_FAIL;
}
PsychicStreamResponse response = PsychicStreamResponse(request, "image/jpeg", "capture.jpg");
response.beginSend();
response.write(fb->buf, fb->len);
esp_camera_fb_return(fb);
return response.endSend();
}
void streamTask(void *pv) {
esp_err_t res = ESP_OK;
PsychicRequest *request = static_cast<PsychicRequest *>(pv);
httpd_req_t *copy = nullptr;
res = httpd_req_async_handler_begin(request->request(), &copy);
if (res != ESP_OK) {
return;
}
PsychicHttpServer *server = request->server();
PsychicRequest new_request = PsychicRequest(server, copy);
request = &new_request;
PsychicStreamResponse response = PsychicStreamResponse(request, _STREAM_CONTENT_TYPE);
camera_fb_t *fb = NULL;
char *part_buf[64];
size_t buf_len = 0;
uint8_t *buf = NULL;
int64_t fr_start = esp_timer_get_time();
response.beginSend();
for (;;) {
fb = safe_camera_fb_get();
if (!fb) {
ESP_LOGE("Stream", "Camera capture failed");
break;
}
if (fb->format != PIXFORMAT_JPEG) {
if (!frame2jpg(fb, 80, &buf, &buf_len)) break;
} else {
buf_len = fb->len;
buf = fb->buf;
}
size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, buf_len);
size_t w = response.write((const char *)part_buf, hlen);
w += response.write((const char *)buf, buf_len);
w += response.write((char *)_STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
if (w == 62) break;
esp_camera_fb_return(fb);
safe_sensor_return();
buf = NULL;
taskYIELD();
int64_t delay = 30000ll - esp_timer_get_time() - fr_start;
if (delay > 0) vTaskDelay(pdMS_TO_TICKS(delay));
}
ESP_LOGI("Stream", "Stream ended");
response.endSend();
httpd_req_async_handler_complete(copy);
vTaskDelete(NULL);
}
esp_err_t CameraService::cameraStream(PsychicRequest *request) {
g_taskManager.createTask(streamTask, "Stream client task", 4096, request, 4);
vTaskDelay(pdMS_TO_TICKS(100));
return ESP_OK;
}
void CameraService::updateCamera() {
ESP_LOGI("CameraSettings", "Updating camera settings");
sensor_t *s = safe_sensor_get();
if (!s) {
ESP_LOGE("CameraSettings", "Failed to update camera settings");
safe_sensor_return();
return;
}
s->set_pixformat(s, state().pixformat);
s->set_framesize(s, state().framesize);
s->set_brightness(s, state().brightness);
s->set_contrast(s, state().contrast);
s->set_saturation(s, state().saturation);
s->set_sharpness(s, state().sharpness);
s->set_denoise(s, state().denoise);
s->set_gainceiling(s, state().gainceiling);
s->set_quality(s, state().quality);
s->set_colorbar(s, state().colorbar);
s->set_awb_gain(s, state().awb_gain);
s->set_wb_mode(s, state().wb_mode);
s->set_aec2(s, state().aec2);
s->set_ae_level(s, state().ae_level);
s->set_aec_value(s, state().aec_value);
s->set_agc_gain(s, state().agc_gain);
s->set_bpc(s, state().bpc);
s->set_wpc(s, state().wpc);
s->set_special_effect(s, state().special_effect);
s->set_raw_gma(s, state().raw_gma);
s->set_lenc(s, state().lenc);
s->set_hmirror(s, state().hmirror);
s->set_vflip(s, state().vflip);
s->set_dcw(s, state().dcw);
safe_sensor_return();
}
} // namespace Camera
+182
View File
@@ -0,0 +1,182 @@
#include "system_service.h"
namespace system_service {
static const char *TAG = "SystemService";
static JsonDocument analyticsDoc;
static char analyticsMessage[MAX_ESP_ANALYTICS_SIZE];
esp_err_t handleReset(PsychicRequest *request) {
reset();
return request->reply(200);
}
esp_err_t handleRestart(PsychicRequest *request) {
restart();
return request->reply(200);
}
esp_err_t handleSleep(PsychicRequest *request) {
sleep();
return request->reply(200);
}
esp_err_t getStatus(PsychicRequest *request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject root = response.getRoot();
status(root);
return response.send();
}
esp_err_t getMetrics(PsychicRequest *request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject root = response.getRoot();
metrics(root);
return response.send();
}
void reset() {
ESP_LOGI(TAG, "Resetting device");
File root = ESPFS.open(FS_CONFIG_DIRECTORY);
File file;
while (file = root.openNextFile()) {
String path = file.path();
file.close();
ESPFS.remove(path);
}
restart();
}
void restart() {
xTaskCreate(
[](void *pvParameters) {
for (;;) {
delay(250);
MDNS.end();
delay(100);
WiFi.disconnect(true);
delay(500);
ESP.restart();
}
},
"Restart task", 4096, nullptr, 10, nullptr);
}
void sleep() {
xTaskCreate(
[](void *pvParameters) {
for (;;) {
delay(250);
MDNS.end();
delay(100);
WiFi.disconnect(true);
delay(500);
uint64_t bitmask = (uint64_t)1 << (WAKEUP_PIN_NUMBER);
#ifdef CONFIG_IDF_TARGET_ESP32C3
esp_deep_sleep_enable_gpio_wakeup(bitmask, (esp_deepsleep_gpio_wake_up_mode_t)WAKEUP_SIGNAL);
#else
esp_sleep_enable_ext1_wakeup(bitmask, (esp_sleep_ext1_wakeup_mode_t)WAKEUP_SIGNAL);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
#endif
esp_deep_sleep_start();
}
},
"Sleep task", 4096, nullptr, 10, nullptr);
ESP_LOGI(TAG, "Setting device to sleep");
}
void status(JsonObject &root) {
root["esp_platform"] = ESP_PLATFORM;
root["firmware_version"] = APP_VERSION;
root["max_alloc_heap"] = ESP.getMaxAllocHeap();
root["psram_size"] = ESP.getPsramSize();
root["free_psram"] = ESP.getFreePsram();
root["cpu_freq_mhz"] = ESP.getCpuFreqMHz();
root["cpu_type"] = ESP.getChipModel();
root["cpu_rev"] = ESP.getChipRevision();
root["cpu_cores"] = ESP.getChipCores();
root["free_heap"] = ESP.getFreeHeap();
root["min_free_heap"] = ESP.getMinFreeHeap();
root["sketch_size"] = ESP.getSketchSize();
root["free_sketch_space"] = ESP.getFreeSketchSpace();
root["sdk_version"] = ESP.getSdkVersion();
root["arduino_version"] = ARDUINO_VERSION;
root["flash_chip_size"] = ESP.getFlashChipSize();
root["flash_chip_speed"] = ESP.getFlashChipSpeed();
root["fs_total"] = ESPFS.totalBytes();
root["fs_used"] = ESPFS.usedBytes();
root["core_temp"] = temperatureRead();
root["cpu_reset_reason"] = resetReason(esp_reset_reason());
root["uptime"] = millis() / 1000;
}
void metrics(JsonObject &root) {
root["uptime"] = millis() / 1000;
root["free_heap"] = ESP.getFreeHeap();
root["total_heap"] = ESP.getHeapSize();
root["min_free_heap"] = ESP.getMinFreeHeap();
root["max_alloc_heap"] = ESP.getMaxAllocHeap();
root["fs_used"] = ESPFS.usedBytes();
root["fs_total"] = ESPFS.totalBytes();
root["core_temp"] = temperatureRead();
root["cpu0_usage"] = g_taskManager.getCpuUsage(0);
root["cpu1_usage"] = g_taskManager.getCpuUsage(1);
root["cpu_usage"] = g_taskManager.getCpuUsage();
JsonArray tasks = root["tasks"].to<JsonArray>();
for (auto const &task : g_taskManager.getTasks()) {
JsonObject nested = tasks.add<JsonObject>();
nested["name"] = task.name;
nested["stackSize"] = task.stackSize;
nested["priority"] = task.priority;
nested["coreId"] = task.coreId;
}
}
void emitMetrics() {
if (!socket.hasSubscribers(EVENT_ANALYTICS)) return;
analyticsDoc.clear();
JsonObject root = analyticsDoc.to<JsonObject>();
system_service::metrics(root);
JsonVariant data = analyticsDoc.as<JsonVariant>();
socket.emit(EVENT_ANALYTICS, data);
}
const char *resetReason(esp_reset_reason_t reason) {
switch (reason) {
case ESP_RST_UNKNOWN: return "Reset reason can not be determined";
case ESP_RST_POWERON: return "Reset due to power-on event";
case ESP_RST_EXT: return "Reset by external pin (not applicable for ESP32)";
case ESP_RST_SW: return "Software reset via esp_restart";
case ESP_RST_PANIC: return "Software reset due to exception/panic";
case ESP_RST_INT_WDT: return "Reset (software or hardware) due to interrupt watchdog";
case ESP_RST_TASK_WDT: return "Reset due to task watchdog";
case ESP_RST_WDT: return "Reset due to other watchdogs";
case ESP_RST_DEEPSLEEP: return "Reset after exiting deep sleep mode";
case ESP_RST_BROWNOUT: return "Brownout reset (software or hardware)";
case ESP_RST_SDIO: return "Reset over SDIO";
#ifdef ESP_RST_USB
case ESP_RST_USB: return "Reset by USB peripheral";
#endif
#ifdef ESP_RST_JTAG
case ESP_RST_JTAG: return "Reset by JTAG";
#endif
#ifdef ESP_RST_EFUSE
case ESP_RST_EFUSE: return "Reset due to efuse error";
#endif
#ifdef ESP_RST_PWR_GLITCH
case ESP_RST_PWR_GLITCH: return "Reset due to power glitch detected";
#endif
#ifdef ESP_RST_CPU_LOCKUP
case ESP_RST_CPU_LOCKUP: return "Reset due to CPU lock up (double exception)";
#endif
default:
static char buffer[48];
snprintf(buffer, sizeof(buffer), "Unknown reset reason (%d)", reason);
return buffer;
}
}
} // namespace system_service
+3
View File
@@ -0,0 +1,3 @@
#include <task_manager.h>
TaskManager g_taskManager;
+213
View File
@@ -0,0 +1,213 @@
#include <wifi_service.h>
WiFiService::WiFiService()
: endpoint(WiFiSettings::read, WiFiSettings::update, this),
_persistence(WiFiSettings::read, WiFiSettings::update, this, WIFI_SETTINGS_FILE) {
addUpdateHandler([&](const String &originId) { reconfigureWiFiConnection(); }, false);
}
WiFiService::~WiFiService() {}
void WiFiService::begin() {
WiFi.mode(WIFI_MODE_STA);
WiFi.persistent(false);
WiFi.setAutoReconnect(false);
WiFi.onEvent(std::bind(&WiFiService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
WiFi.onEvent(std::bind(&WiFiService::onStationModeStop, this, std::placeholders::_1, std::placeholders::_2),
WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_STOP);
WiFi.onEvent(onStationModeGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);
_persistence.readFromFS();
reconfigureWiFiConnection();
if (state().wifiSettings.size() == 1) {
configureNetwork(state().wifiSettings[0]);
delay(500);
}
}
void WiFiService::reconfigureWiFiConnection() {
_lastConnectionAttempt = 0;
if (WiFi.disconnect(true)) _stopping = true;
}
void WiFiService::loop() { EXECUTE_EVERY_N_MS(reconnectDelay, manageSTA()); }
esp_err_t WiFiService::handleScan(PsychicRequest *request) {
if (WiFi.scanComplete() != -1) {
WiFi.scanDelete();
WiFi.scanNetworks(true);
}
return request->reply(202);
}
esp_err_t WiFiService::getNetworks(PsychicRequest *request) {
int numNetworks = WiFi.scanComplete();
if (numNetworks == -1)
return request->reply(202);
else if (numNetworks < -1)
return handleScan(request);
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject root = response.getRoot();
getNetworks(root);
return response.send();
}
void WiFiService::setupMDNS(const char *hostname) {
MDNS.begin(state().hostname.c_str());
MDNS.setInstanceName(hostname);
MDNS.addService("http", "tcp", 80);
MDNS.addService("ws", "tcp", 80);
MDNS.addServiceTxt("http", "tcp", "Firmware Version", APP_VERSION);
}
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);
network["bssid"] = WiFi.BSSIDstr(i);
network["channel"] = WiFi.channel(i);
network["encryption_type"] = (uint8_t)WiFi.encryptionType(i);
}
}
esp_err_t WiFiService::getNetworkStatus(PsychicRequest *request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject root = response.getRoot();
getNetworkStatus(root);
return response.send();
}
void WiFiService::getNetworkStatus(JsonObject &root) {
wl_status_t status = WiFi.status();
root["status"] = (uint8_t)status;
if (status == WL_CONNECTED) {
root["local_ip"] = WiFi.localIP().toString();
root["mac_address"] = WiFi.macAddress();
root["rssi"] = WiFi.RSSI();
root["ssid"] = WiFi.SSID();
root["bssid"] = WiFi.BSSIDstr();
root["channel"] = WiFi.channel();
root["subnet_mask"] = WiFi.subnetMask().toString();
root["gateway_ip"] = WiFi.gatewayIP().toString();
IPAddress dnsIP1 = WiFi.dnsIP(0);
IPAddress dnsIP2 = WiFi.dnsIP(1);
if (dnsIP1 != INADDR_NONE) {
root["dns_ip_1"] = dnsIP1.toString();
}
if (dnsIP2 != INADDR_NONE) {
root["dns_ip_2"] = dnsIP2.toString();
}
}
}
void WiFiService::manageSTA() {
if (WiFi.isConnected() || state().wifiSettings.empty()) return;
if ((WiFi.getMode() & WIFI_STA) == 0) connectToWiFi();
}
void WiFiService::connectToWiFi() {
// reset availability flag for all stored networks
for (auto &network : state().wifiSettings) {
network.available = false;
}
// scanning for available networks
int scanResult = WiFi.scanNetworks();
if (scanResult == WIFI_SCAN_FAILED) {
ESP_LOGE("WiFiSettingsService", "WiFi scan failed.");
} else if (scanResult == 0) {
ESP_LOGI("WiFiSettingsService", "No networks found.");
} else {
ESP_LOGI("WiFiSettingsService", "%d networks found.", scanResult);
// find the best network to connect
wifi_settings_t *bestNetwork = nullptr;
int32_t bestNetworkDb = FACTORY_WIFI_RSSI_THRESHOLD;
for (int i = 0; i < scanResult; ++i) {
String ssid_scan;
int32_t rssi_scan;
uint8_t sec_scan;
uint8_t *BSSID_scan;
int32_t chan_scan;
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan);
for (auto &network : state().wifiSettings) {
if (ssid_scan == network.ssid) {
if (rssi_scan >= FACTORY_WIFI_RSSI_THRESHOLD) {
network.available = true;
}
if (rssi_scan > bestNetworkDb) {
bestNetworkDb = rssi_scan;
bestNetwork = &network;
}
}
}
}
if (!state().priorityBySignalStrength) {
for (auto &network : state().wifiSettings) {
if (network.available == true) {
ESP_LOGI("WiFiSettingsService", "Connecting to first available network: %s", network.ssid.c_str());
configureNetwork(network);
break;
}
}
} else if (state().priorityBySignalStrength && bestNetwork) {
ESP_LOGI("WiFiSettingsService", "Connecting to strongest network: %s", bestNetwork->ssid.c_str());
configureNetwork(*bestNetwork);
WiFi.begin(bestNetwork->ssid.c_str(), bestNetwork->password.c_str());
} else {
ESP_LOGI("WiFiSettingsService", "No known networks found.");
}
WiFi.scanDelete();
}
}
void WiFiService::configureNetwork(wifi_settings_t &network) {
if (network.staticIPConfig) {
// configure for static IP
WiFi.config(network.localIP, network.gatewayIP, network.subnetMask, network.dnsIP1, network.dnsIP2);
} else {
// configure for DHCP
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
}
WiFi.setHostname(state().hostname.c_str());
// attempt to connect to the network
WiFi.begin(network.ssid.c_str(), network.password.c_str());
#if CONFIG_IDF_TARGET_ESP32C3
WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
#endif
}
void WiFiService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
WiFi.disconnect(true);
ESP_LOGI("WiFiStatus", "WiFi Disconnected. Reason code=%d", info.wifi_sta_disconnected.reason);
}
void WiFiService::onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info) {
if (_stopping) {
_lastConnectionAttempt = 0;
_stopping = false;
}
ESP_LOGI("WiFiStatus", "WiFi Connected.");
}
void WiFiService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
ESP_LOGI("WiFiStatus", "WiFi Got IP. localIP=%s, hostName=%s", WiFi.localIP().toString().c_str(),
WiFi.getHostname());
}