basic functionality
This commit is contained in:
24
src/ClusterContext.cpp
Normal file
24
src/ClusterContext.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "ClusterContext.h"
|
||||
|
||||
ClusterContext::ClusterContext() {
|
||||
scheduler = new Scheduler();
|
||||
udp = new WiFiUDP();
|
||||
memberList = new std::vector<NodeInfo>();
|
||||
hostname = "";
|
||||
}
|
||||
|
||||
ClusterContext::~ClusterContext() {
|
||||
delete scheduler;
|
||||
delete udp;
|
||||
delete memberList;
|
||||
}
|
||||
|
||||
void ClusterContext::registerEvent(const std::string& event, EventCallback cb) {
|
||||
eventRegistry[event].push_back(cb);
|
||||
}
|
||||
|
||||
void ClusterContext::triggerEvent(const std::string& event, void* data) {
|
||||
for (auto& cb : eventRegistry[event]) {
|
||||
cb(data);
|
||||
}
|
||||
}
|
||||
25
src/ClusterContext.h
Normal file
25
src/ClusterContext.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <vector>
|
||||
#include "NodeInfo.h"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class ClusterContext {
|
||||
public:
|
||||
ClusterContext();
|
||||
~ClusterContext();
|
||||
Scheduler* scheduler;
|
||||
WiFiUDP* udp;
|
||||
String hostname;
|
||||
IPAddress localIP;
|
||||
std::vector<NodeInfo>* memberList;
|
||||
|
||||
using EventCallback = std::function<void(void*)>;
|
||||
std::map<std::string, std::vector<EventCallback>> eventRegistry;
|
||||
|
||||
void registerEvent(const std::string& event, EventCallback cb);
|
||||
void triggerEvent(const std::string& event, void* data);
|
||||
};
|
||||
175
src/ClusterManager.cpp
Normal file
175
src/ClusterManager.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "ClusterManager.h"
|
||||
|
||||
ClusterManager::ClusterManager(ClusterContext& ctx) : ctx(ctx) {
|
||||
// Register callback for node_discovered event
|
||||
ctx.registerEvent("node_discovered", [this](void* data) {
|
||||
NodeInfo* node = static_cast<NodeInfo*>(data);
|
||||
this->addOrUpdateNode(node->hostname, node->ip);
|
||||
});
|
||||
}
|
||||
|
||||
void ClusterManager::sendDiscovery() {
|
||||
//Serial.println("[Cluster] Sending discovery packet...");
|
||||
ctx.udp->beginPacket("255.255.255.255", ClusterProtocol::UDP_PORT);
|
||||
ctx.udp->write(ClusterProtocol::DISCOVERY_MSG);
|
||||
ctx.udp->endPacket();
|
||||
}
|
||||
|
||||
void ClusterManager::listenForDiscovery() {
|
||||
int packetSize = ctx.udp->parsePacket();
|
||||
if (packetSize) {
|
||||
char incoming[ClusterProtocol::UDP_BUF_SIZE];
|
||||
int len = ctx.udp->read(incoming, ClusterProtocol::UDP_BUF_SIZE);
|
||||
if (len > 0) {
|
||||
incoming[len] = 0;
|
||||
}
|
||||
//Serial.printf("[UDP] Packet received: %s\n", incoming);
|
||||
if (strcmp(incoming, ClusterProtocol::DISCOVERY_MSG) == 0) {
|
||||
//Serial.printf("[UDP] Discovery request from: %s\n", ctx.udp->remoteIP().toString().c_str());
|
||||
ctx.udp->beginPacket(ctx.udp->remoteIP(), ClusterProtocol::UDP_PORT);
|
||||
String response = String(ClusterProtocol::RESPONSE_MSG) + ":" + ctx.hostname;
|
||||
ctx.udp->write(response.c_str());
|
||||
ctx.udp->endPacket();
|
||||
//Serial.printf("[UDP] Sent response with hostname: %s\n", ctx.hostname.c_str());
|
||||
} else if (strncmp(incoming, ClusterProtocol::RESPONSE_MSG, strlen(ClusterProtocol::RESPONSE_MSG)) == 0) {
|
||||
char* hostPtr = incoming + strlen(ClusterProtocol::RESPONSE_MSG) + 1;
|
||||
String nodeHost = String(hostPtr);
|
||||
addOrUpdateNode(nodeHost, ctx.udp->remoteIP());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClusterManager::addOrUpdateNode(const String& nodeHost, IPAddress nodeIP) {
|
||||
auto& memberList = *ctx.memberList;
|
||||
for (auto& node : memberList) {
|
||||
if (node.hostname == nodeHost) {
|
||||
node.ip = nodeIP;
|
||||
//fetchNodeInfo(nodeIP); // Do not fetch here, handled by periodic task
|
||||
return;
|
||||
}
|
||||
}
|
||||
NodeInfo newNode;
|
||||
newNode.hostname = nodeHost;
|
||||
newNode.ip = nodeIP;
|
||||
newNode.lastSeen = millis();
|
||||
updateNodeStatus(newNode, newNode.lastSeen);
|
||||
memberList.push_back(newNode);
|
||||
Serial.printf("[Cluster] Added node: %s @ %s | Status: %s | last update: 0\n",
|
||||
nodeHost.c_str(),
|
||||
newNode.ip.toString().c_str(),
|
||||
statusToStr(newNode.status));
|
||||
//fetchNodeInfo(nodeIP); // Do not fetch here, handled by periodic task
|
||||
}
|
||||
|
||||
void ClusterManager::fetchNodeInfo(const IPAddress& ip) {
|
||||
if(ip == ctx.localIP) {
|
||||
Serial.println("[Cluster] Skipping fetch for local node");
|
||||
return;
|
||||
}
|
||||
HTTPClient http;
|
||||
WiFiClient client;
|
||||
String url = "http://" + ip.toString() + ClusterProtocol::API_NODE_STATUS;
|
||||
http.begin(client, url);
|
||||
int httpCode = http.GET();
|
||||
if (httpCode == 200) {
|
||||
String payload = http.getString();
|
||||
JsonDocument doc;
|
||||
DeserializationError err = deserializeJson(doc, payload);
|
||||
if (!err) {
|
||||
auto& memberList = *ctx.memberList;
|
||||
for (auto& node : memberList) {
|
||||
if (node.ip == ip) {
|
||||
node.resources.freeHeap = doc["freeHeap"];
|
||||
node.resources.chipId = doc["chipId"];
|
||||
node.resources.sdkVersion = (const char*)doc["sdkVersion"];
|
||||
node.resources.cpuFreqMHz = doc["cpuFreqMHz"];
|
||||
node.resources.flashChipSize = doc["flashChipSize"];
|
||||
node.status = NodeInfo::ACTIVE;
|
||||
node.lastSeen = millis();
|
||||
node.apiEndpoints.clear();
|
||||
if (doc["api"].is<JsonArray>()) {
|
||||
JsonArray apiArr = doc["api"].as<JsonArray>();
|
||||
for (JsonObject apiObj : apiArr) {
|
||||
String uri = (const char*)apiObj["uri"];
|
||||
int method = apiObj["method"];
|
||||
node.apiEndpoints.push_back(std::make_tuple(uri, method));
|
||||
}
|
||||
}
|
||||
Serial.printf("[Cluster] Fetched info for node: %s @ %s\n", node.hostname.c_str(), ip.toString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Serial.printf("[Cluster] Failed to fetch info for node @ %s, HTTP code: %d\n", ip.toString().c_str(), httpCode);
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
|
||||
void ClusterManager::heartbeatTaskCallback() {
|
||||
for (auto& node : *ctx.memberList) {
|
||||
if (node.hostname == ctx.hostname) {
|
||||
node.lastSeen = millis();
|
||||
node.status = NodeInfo::ACTIVE;
|
||||
updateLocalNodeResources();
|
||||
ctx.triggerEvent("node_discovered", &node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClusterManager::updateAllMembersInfoTaskCallback() {
|
||||
auto& memberList = *ctx.memberList;
|
||||
for (const auto& node : memberList) {
|
||||
if (node.ip != ctx.localIP) {
|
||||
fetchNodeInfo(node.ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClusterManager::updateAllNodeStatuses() {
|
||||
auto& memberList = *ctx.memberList;
|
||||
unsigned long now = millis();
|
||||
for (auto& node : memberList) {
|
||||
updateNodeStatus(node, now);
|
||||
node.latency = now - node.lastSeen;
|
||||
}
|
||||
}
|
||||
|
||||
void ClusterManager::removeDeadNodes() {
|
||||
auto& memberList = *ctx.memberList;
|
||||
unsigned long now = millis();
|
||||
for (size_t i = 0; i < memberList.size(); ) {
|
||||
unsigned long diff = now - memberList[i].lastSeen;
|
||||
if (memberList[i].status == NodeInfo::DEAD && diff > NODE_DEAD_THRESHOLD) {
|
||||
Serial.printf("[Cluster] Removing node: %s\n", memberList[i].hostname.c_str());
|
||||
memberList.erase(memberList.begin() + i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClusterManager::printMemberList() {
|
||||
auto& memberList = *ctx.memberList;
|
||||
if (memberList.empty()) {
|
||||
Serial.println("[Cluster] Member List: empty");
|
||||
return;
|
||||
}
|
||||
Serial.println("[Cluster] Member List:");
|
||||
for (const auto& node : memberList) {
|
||||
Serial.printf(" %s @ %s | Status: %s | last seen: %lu\n", node.hostname.c_str(), node.ip.toString().c_str(), statusToStr(node.status), millis() - node.lastSeen);
|
||||
}
|
||||
}
|
||||
|
||||
void ClusterManager::updateLocalNodeResources() {
|
||||
for (auto& node : *ctx.memberList) {
|
||||
if (node.hostname == ctx.hostname) {
|
||||
node.resources.freeHeap = ESP.getFreeHeap();
|
||||
node.resources.chipId = ESP.getChipId();
|
||||
node.resources.sdkVersion = String(ESP.getSdkVersion());
|
||||
node.resources.cpuFreqMHz = ESP.getCpuFreqMHz();
|
||||
node.resources.flashChipSize = ESP.getFlashChipSize();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/ClusterManager.h
Normal file
26
src/ClusterManager.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "Globals.h"
|
||||
#include "ClusterContext.h"
|
||||
#include "NodeInfo.h"
|
||||
#include <Arduino.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
|
||||
class ClusterManager {
|
||||
public:
|
||||
ClusterManager(ClusterContext& ctx);
|
||||
void sendDiscovery();
|
||||
void listenForDiscovery();
|
||||
void addOrUpdateNode(const String& nodeHost, IPAddress nodeIP);
|
||||
void updateAllNodeStatuses();
|
||||
void removeDeadNodes();
|
||||
void printMemberList();
|
||||
const std::vector<NodeInfo>& getMemberList() const { return *ctx.memberList; }
|
||||
void fetchNodeInfo(const IPAddress& ip);
|
||||
void updateLocalNodeResources();
|
||||
void heartbeatTaskCallback();
|
||||
void updateAllMembersInfoTaskCallback();
|
||||
private:
|
||||
ClusterContext& ctx;
|
||||
};
|
||||
54
src/NetworkManager.cpp
Normal file
54
src/NetworkManager.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "NetworkManager.h"
|
||||
|
||||
const char* STA_SSID = "shroud";
|
||||
const char* STA_PASS = "th3r31sn0sp00n";
|
||||
|
||||
NetworkManager::NetworkManager(ClusterContext& ctx) : ctx(ctx) {}
|
||||
|
||||
void NetworkManager::setHostnameFromMac() {
|
||||
uint8_t mac[6];
|
||||
WiFi.macAddress(mac);
|
||||
char buf[32];
|
||||
sprintf(buf, "esp-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
WiFi.hostname(buf);
|
||||
ctx.hostname = String(buf);
|
||||
}
|
||||
|
||||
void NetworkManager::setupWiFi() {
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(STA_SSID, STA_PASS);
|
||||
Serial.println("[WiFi] Connecting to AP...");
|
||||
unsigned long startAttemptTime = millis();
|
||||
while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 15000) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.println();
|
||||
Serial.print("[WiFi] Connected to AP, IP: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
} else {
|
||||
Serial.println();
|
||||
Serial.println("[WiFi] Failed to connect to AP. Creating AP...");
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(STA_SSID, STA_PASS);
|
||||
Serial.print("[WiFi] AP created, IP: ");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
}
|
||||
setHostnameFromMac();
|
||||
ctx.udp->begin(4210);
|
||||
ctx.localIP = WiFi.localIP();
|
||||
ctx.hostname = WiFi.hostname();
|
||||
Serial.print("[WiFi] Hostname set to: ");
|
||||
Serial.println(ctx.hostname);
|
||||
Serial.print("[WiFi] UDP listening on port 4210\n");
|
||||
|
||||
// Register this node in the memberlist via event system
|
||||
NodeInfo self;
|
||||
self.hostname = ctx.hostname;
|
||||
self.ip = WiFi.localIP();
|
||||
self.lastSeen = millis();
|
||||
self.status = NodeInfo::ACTIVE;
|
||||
ctx.triggerEvent("node_discovered", &self);
|
||||
}
|
||||
12
src/NetworkManager.h
Normal file
12
src/NetworkManager.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "ClusterContext.h"
|
||||
#include <ESP8266WiFi.h>
|
||||
|
||||
class NetworkManager {
|
||||
public:
|
||||
NetworkManager(ClusterContext& ctx);
|
||||
void setupWiFi();
|
||||
void setHostnameFromMac();
|
||||
private:
|
||||
ClusterContext& ctx;
|
||||
};
|
||||
21
src/NodeInfo.cpp
Normal file
21
src/NodeInfo.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "NodeInfo.h"
|
||||
|
||||
const char* statusToStr(NodeInfo::Status status) {
|
||||
switch (status) {
|
||||
case NodeInfo::ACTIVE: return "active";
|
||||
case NodeInfo::INACTIVE: return "inactive";
|
||||
case NodeInfo::DEAD: return "dead";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void updateNodeStatus(NodeInfo &node, unsigned long now) {
|
||||
unsigned long diff = now - node.lastSeen;
|
||||
if (diff < NODE_INACTIVE_THRESHOLD) {
|
||||
node.status = NodeInfo::ACTIVE;
|
||||
} else if (diff < NODE_INACTIVE_THRESHOLD) {
|
||||
node.status = NodeInfo::INACTIVE;
|
||||
} else {
|
||||
node.status = NodeInfo::DEAD;
|
||||
}
|
||||
}
|
||||
24
src/NodeInfo.h
Normal file
24
src/NodeInfo.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "Globals.h"
|
||||
#include <IPAddress.h>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
struct NodeInfo {
|
||||
String hostname;
|
||||
IPAddress ip;
|
||||
unsigned long lastSeen;
|
||||
enum Status { ACTIVE, INACTIVE, DEAD } status;
|
||||
struct Resources {
|
||||
uint32_t freeHeap = 0;
|
||||
uint32_t chipId = 0;
|
||||
String sdkVersion;
|
||||
uint32_t cpuFreqMHz = 0;
|
||||
uint32_t flashChipSize = 0;
|
||||
} resources;
|
||||
unsigned long latency = 0; // ms since lastSeen
|
||||
std::vector<std::tuple<String, int>> apiEndpoints; // List of registered endpoints
|
||||
};
|
||||
|
||||
const char* statusToStr(NodeInfo::Status status);
|
||||
void updateNodeStatus(NodeInfo &node, unsigned long now = millis());
|
||||
169
src/RestApiServer.cpp
Normal file
169
src/RestApiServer.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "RestApiServer.h"
|
||||
|
||||
RestApiServer::RestApiServer(ClusterContext& ctx, uint16_t port) : server(port), ctx(ctx) {}
|
||||
|
||||
void RestApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler) {
|
||||
serviceRegistry.push_back(std::make_tuple(uri, method));
|
||||
// Store in NodeInfo for local node
|
||||
if (ctx.memberList && !ctx.memberList->empty()) {
|
||||
(*ctx.memberList)[0].apiEndpoints.push_back(std::make_tuple(uri, method));
|
||||
}
|
||||
server.on(uri.c_str(), method, requestHandler);
|
||||
}
|
||||
|
||||
void RestApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
|
||||
std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler) {
|
||||
serviceRegistry.push_back(std::make_tuple(uri, method));
|
||||
if (ctx.memberList && !ctx.memberList->empty()) {
|
||||
(*ctx.memberList)[0].apiEndpoints.push_back(std::make_tuple(uri, method));
|
||||
}
|
||||
server.on(uri.c_str(), method, requestHandler, uploadHandler);
|
||||
}
|
||||
|
||||
void RestApiServer::begin() {
|
||||
addEndpoint("/api/node/status", HTTP_GET,
|
||||
std::bind(&RestApiServer::getSystemStatusJson, this, std::placeholders::_1));
|
||||
addEndpoint("/api/cluster/members", HTTP_GET,
|
||||
std::bind(&RestApiServer::getClusterMembersJson, this, std::placeholders::_1));
|
||||
addEndpoint("/api/node/update", HTTP_POST,
|
||||
std::bind(&RestApiServer::onFirmwareUpdateRequest, this, std::placeholders::_1),
|
||||
std::bind(&RestApiServer::onFirmwareUpload, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)
|
||||
);
|
||||
addEndpoint("/api/node/restart", HTTP_POST,
|
||||
std::bind(&RestApiServer::onRestartRequest, this, std::placeholders::_1));
|
||||
server.begin();
|
||||
}
|
||||
|
||||
void RestApiServer::getSystemStatusJson(AsyncWebServerRequest *request) {
|
||||
JsonDocument doc;
|
||||
doc["freeHeap"] = ESP.getFreeHeap();
|
||||
doc["chipId"] = ESP.getChipId();
|
||||
doc["sdkVersion"] = ESP.getSdkVersion();
|
||||
doc["cpuFreqMHz"] = ESP.getCpuFreqMHz();
|
||||
doc["flashChipSize"] = ESP.getFlashChipSize();
|
||||
JsonArray apiArr = doc["api"].to<JsonArray>();
|
||||
for (const auto& entry : serviceRegistry) {
|
||||
JsonObject apiObj = apiArr.add<JsonObject>();
|
||||
apiObj["uri"] = std::get<0>(entry);
|
||||
apiObj["method"] = std::get<1>(entry);
|
||||
}
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(200, "application/json", json);
|
||||
}
|
||||
|
||||
void RestApiServer::getClusterMembersJson(AsyncWebServerRequest *request) {
|
||||
JsonDocument doc;
|
||||
JsonArray arr = doc["members"].to<JsonArray>();
|
||||
for (const auto& node : *ctx.memberList) {
|
||||
JsonObject obj = arr.add<JsonObject>();
|
||||
obj["hostname"] = node.hostname;
|
||||
obj["ip"] = node.ip.toString();
|
||||
obj["lastSeen"] = node.lastSeen;
|
||||
obj["latency"] = node.latency;
|
||||
obj["status"] = statusToStr(node.status);
|
||||
obj["resources"]["freeHeap"] = node.resources.freeHeap;
|
||||
obj["resources"]["chipId"] = node.resources.chipId;
|
||||
obj["resources"]["sdkVersion"] = node.resources.sdkVersion;
|
||||
obj["resources"]["cpuFreqMHz"] = node.resources.cpuFreqMHz;
|
||||
obj["resources"]["flashChipSize"] = node.resources.flashChipSize;
|
||||
JsonArray apiArr = obj["api"].to<JsonArray>();
|
||||
for (const auto& endpoint : node.apiEndpoints) {
|
||||
JsonObject apiObj = apiArr.add<JsonObject>();
|
||||
apiObj["uri"] = std::get<0>(endpoint);
|
||||
methodToStr(endpoint, apiObj);
|
||||
}
|
||||
}
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(200, "application/json", json);
|
||||
}
|
||||
|
||||
void RestApiServer::methodToStr(const std::tuple<String, int> &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj)
|
||||
{
|
||||
int method = std::get<1>(endpoint);
|
||||
const char *methodStr = nullptr;
|
||||
switch (method)
|
||||
{
|
||||
case HTTP_GET:
|
||||
methodStr = "GET";
|
||||
break;
|
||||
case HTTP_POST:
|
||||
methodStr = "POST";
|
||||
break;
|
||||
case HTTP_PUT:
|
||||
methodStr = "PUT";
|
||||
break;
|
||||
case HTTP_DELETE:
|
||||
methodStr = "DELETE";
|
||||
break;
|
||||
case HTTP_PATCH:
|
||||
methodStr = "PATCH";
|
||||
break;
|
||||
default:
|
||||
methodStr = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
apiObj["method"] = methodStr;
|
||||
}
|
||||
|
||||
void RestApiServer::onFirmwareUpdateRequest(AsyncWebServerRequest *request) {
|
||||
bool hasError = !Update.hasError();
|
||||
AsyncWebServerResponse *response = request->beginResponse(200, "application/json", hasError ? "{\"status\": \"OK\"}" : "{\"status\": \"FAIL\"}");
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
request->onDisconnect([]() {
|
||||
Serial.println("[API] Restart device");
|
||||
delay(10);
|
||||
ESP.restart();
|
||||
});
|
||||
}
|
||||
|
||||
void RestApiServer::onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
if (!index) {
|
||||
Serial.print("[OTA] Update Start ");
|
||||
Serial.println(filename);
|
||||
Update.runAsync(true);
|
||||
if(!Update.begin(request->contentLength(), U_FLASH)) {
|
||||
Serial.println("[OTA] Update failed: not enough space");
|
||||
Update.printError(Serial);
|
||||
AsyncWebServerResponse *response = request->beginResponse(500, "application/json", "{\"status\": \"FAIL\"}");
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
return;
|
||||
} else {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
if (!Update.hasError()){
|
||||
if (Update.write(data, len) != len) {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
if (final) {
|
||||
if (Update.end(true)) {
|
||||
if(Update.isFinished()) {
|
||||
Serial.print("[OTA] Update Success with ");
|
||||
Serial.print(index + len);
|
||||
Serial.println("B");
|
||||
} else {
|
||||
Serial.println("[OTA] Update not finished");
|
||||
}
|
||||
} else {
|
||||
Serial.print("[OTA] Update failed: ");
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void RestApiServer::onRestartRequest(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"status\": \"restarting\"}");
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
request->onDisconnect([]() {
|
||||
Serial.println("[API] Restart device");
|
||||
delay(10);
|
||||
ESP.restart();
|
||||
});
|
||||
}
|
||||
35
src/RestApiServer.h
Normal file
35
src/RestApiServer.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <Updater.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include "ClusterContext.h"
|
||||
#include "NodeInfo.h"
|
||||
|
||||
#define DEBUG_UPDATER 1
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
|
||||
class RestApiServer {
|
||||
public:
|
||||
RestApiServer(ClusterContext& ctx, uint16_t port = 80);
|
||||
void begin();
|
||||
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler);
|
||||
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
|
||||
std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler);
|
||||
private:
|
||||
AsyncWebServer server;
|
||||
ClusterContext& ctx;
|
||||
std::vector<std::tuple<String, int>> serviceRegistry;
|
||||
void getClusterMembersJson(AsyncWebServerRequest *request);
|
||||
void methodToStr(const std::tuple<String, int> &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj);
|
||||
void getSystemStatusJson(AsyncWebServerRequest *request);
|
||||
void onFirmwareUpdateRequest(AsyncWebServerRequest *request);
|
||||
void onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||
void onRestartRequest(AsyncWebServerRequest *request);
|
||||
};
|
||||
5
src/TaskScheduler.cpp
Normal file
5
src/TaskScheduler.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* https://github.com/arkhipenko/TaskScheduler/tree/master/examples/Scheduler_example16_Multitab
|
||||
*/
|
||||
|
||||
#include <TaskScheduler.h>
|
||||
40
src/main.cpp
Normal file
40
src/main.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include <Arduino.h>
|
||||
#include "Globals.h"
|
||||
#include "ClusterContext.h"
|
||||
#include "NetworkManager.h"
|
||||
#include "ClusterManager.h"
|
||||
#include "RestApiServer.h"
|
||||
|
||||
ClusterContext ctx;
|
||||
NetworkManager network(ctx);
|
||||
ClusterManager cluster(ctx);
|
||||
RestApiServer apiServer(ctx);
|
||||
|
||||
Task tSendDiscovery(TaskIntervals::SEND_DISCOVERY, TASK_FOREVER, [](){ cluster.sendDiscovery(); });
|
||||
Task tListenForDiscovery(TaskIntervals::LISTEN_FOR_DISCOVERY, TASK_FOREVER, [](){ cluster.listenForDiscovery(); });
|
||||
Task tUpdateStatus(TaskIntervals::UPDATE_STATUS, TASK_FOREVER, [](){ cluster.updateAllNodeStatuses(); cluster.removeDeadNodes(); });
|
||||
Task tPrintMemberList(TaskIntervals::PRINT_MEMBER_LIST, TASK_FOREVER, [](){ cluster.printMemberList(); });
|
||||
Task tHeartbeat(TaskIntervals::HEARTBEAT, TASK_FOREVER, [](){ cluster.heartbeatTaskCallback(); });
|
||||
Task tUpdateAllMembersInfo(TaskIntervals::UPDATE_ALL_MEMBERS_INFO, TASK_FOREVER, [](){ cluster.updateAllMembersInfoTaskCallback(); });
|
||||
|
||||
void setup() {
|
||||
network.setupWiFi();
|
||||
ctx.scheduler->init();
|
||||
ctx.scheduler->addTask(tSendDiscovery);
|
||||
ctx.scheduler->addTask(tListenForDiscovery);
|
||||
ctx.scheduler->addTask(tUpdateStatus);
|
||||
ctx.scheduler->addTask(tPrintMemberList);
|
||||
ctx.scheduler->addTask(tHeartbeat);
|
||||
ctx.scheduler->addTask(tUpdateAllMembersInfo);
|
||||
tSendDiscovery.enable();
|
||||
tListenForDiscovery.enable();
|
||||
tUpdateStatus.enable();
|
||||
tPrintMemberList.enable();
|
||||
tHeartbeat.enable();
|
||||
tUpdateAllMembersInfo.enable();
|
||||
apiServer.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ctx.scheduler->execute();
|
||||
}
|
||||
Reference in New Issue
Block a user