chore: refactoring and docs
This commit is contained in:
28
README.md
28
README.md
@@ -1,30 +1,46 @@
|
|||||||
# SPORE
|
# SPORE
|
||||||
|
|
||||||
SProcket ORchestration Engine
|
> SProcket ORchestration Engine
|
||||||
|
|
||||||
|
SPORE is a simple cluster engine for ESP8266 microcontrollers.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- WiFi STA / AP
|
- WiFi STA / AP
|
||||||
- node auto discovery over UDP
|
- auto discovery over UDP
|
||||||
- service registry
|
- service registry
|
||||||
- pub/sub event system
|
- pub/sub event system
|
||||||
- Over-The-Air updates
|
- Over-The-Air updates
|
||||||
|
|
||||||
## Supported Hardware
|
## Supported Hardware
|
||||||
|
|
||||||
- ESP8266
|
- ESP-01
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
The core architecture consists for following components:
|
||||||
|
|
||||||
|
- Network Manager: WiFi connection handling
|
||||||
|
- Cluster Manager: node discovery and memberlist management
|
||||||
|
- API Server: HTTP API for interacting with node and cluster
|
||||||
|
- Task Scheduler: internal scheduler used for system and user defined tasks
|
||||||
|
|
||||||
### Auto Discovery
|
### Auto Discovery
|
||||||
|
|
||||||
A node periodically executes 2 tasks responsible for auto discovers:
|
A node periodically executes 2 tasks responsible for auto discovery:
|
||||||
|
|
||||||
- send discovery: send UDP packet on broadcast address to discover nodes
|
- send discovery: send UDP packet on broadcast address to discover nodes
|
||||||
- listen for discovery: receive UDP packets and send response back to the node who initiated discovery
|
- listen for discovery: receive UDP packets and send response back to the node who initiated discovery
|
||||||
|
|
||||||
Discovered nodes are added to the so cluster memberlist.
|
Discovered nodes are added to the so clusters memberlist.
|
||||||
Another periodic task will then call the `/api/node/status` endpoint over HTTP on each node in the memberlist to get detailed informations about the node (e.g. freeHeap, available API endpoints).
|
Another periodic task will then call the `/api/node/status` endpoint over HTTP on each node in the memberlist to get system resources and available API endpoints.
|
||||||
|
|
||||||
|
### Event System
|
||||||
|
|
||||||
|
The `NodeContext` implements an event system for publishing and subscribing to local and cluster wide events (TODO).
|
||||||
|
It is used internally for communication between different components and tasks.
|
||||||
|
|
||||||
## Develop
|
## Develop
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "RestApiServer.h"
|
#include "ApiServer.h"
|
||||||
|
|
||||||
RestApiServer::RestApiServer(NodeContext& ctx, uint16_t port) : server(port), ctx(ctx) {}
|
ApiServer::ApiServer(NodeContext& ctx, uint16_t port) : server(port), ctx(ctx) {}
|
||||||
|
|
||||||
void RestApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler) {
|
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler) {
|
||||||
serviceRegistry.push_back(std::make_tuple(uri, method));
|
serviceRegistry.push_back(std::make_tuple(uri, method));
|
||||||
// Store in NodeInfo for local node
|
// Store in NodeInfo for local node
|
||||||
if (ctx.memberList && !ctx.memberList->empty()) {
|
if (ctx.memberList && !ctx.memberList->empty()) {
|
||||||
@@ -11,7 +11,7 @@ void RestApiServer::addEndpoint(const String& uri, int method, std::function<voi
|
|||||||
server.on(uri.c_str(), method, requestHandler);
|
server.on(uri.c_str(), method, requestHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
|
void ApiServer::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) {
|
std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler) {
|
||||||
serviceRegistry.push_back(std::make_tuple(uri, method));
|
serviceRegistry.push_back(std::make_tuple(uri, method));
|
||||||
if (ctx.memberList && !ctx.memberList->empty()) {
|
if (ctx.memberList && !ctx.memberList->empty()) {
|
||||||
@@ -20,21 +20,21 @@ void RestApiServer::addEndpoint(const String& uri, int method, std::function<voi
|
|||||||
server.on(uri.c_str(), method, requestHandler, uploadHandler);
|
server.on(uri.c_str(), method, requestHandler, uploadHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestApiServer::begin() {
|
void ApiServer::begin() {
|
||||||
addEndpoint("/api/node/status", HTTP_GET,
|
addEndpoint("/api/node/status", HTTP_GET,
|
||||||
std::bind(&RestApiServer::getSystemStatusJson, this, std::placeholders::_1));
|
std::bind(&ApiServer::onSystemStatusRequest, this, std::placeholders::_1));
|
||||||
addEndpoint("/api/cluster/members", HTTP_GET,
|
addEndpoint("/api/cluster/members", HTTP_GET,
|
||||||
std::bind(&RestApiServer::getClusterMembersJson, this, std::placeholders::_1));
|
std::bind(&ApiServer::onClusterMembersRequest, this, std::placeholders::_1));
|
||||||
addEndpoint("/api/node/update", HTTP_POST,
|
addEndpoint("/api/node/update", HTTP_POST,
|
||||||
std::bind(&RestApiServer::onFirmwareUpdateRequest, this, std::placeholders::_1),
|
std::bind(&ApiServer::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)
|
std::bind(&ApiServer::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,
|
addEndpoint("/api/node/restart", HTTP_POST,
|
||||||
std::bind(&RestApiServer::onRestartRequest, this, std::placeholders::_1));
|
std::bind(&ApiServer::onRestartRequest, this, std::placeholders::_1));
|
||||||
server.begin();
|
server.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestApiServer::getSystemStatusJson(AsyncWebServerRequest *request) {
|
void ApiServer::onSystemStatusRequest(AsyncWebServerRequest *request) {
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc["freeHeap"] = ESP.getFreeHeap();
|
doc["freeHeap"] = ESP.getFreeHeap();
|
||||||
doc["chipId"] = ESP.getChipId();
|
doc["chipId"] = ESP.getChipId();
|
||||||
@@ -52,7 +52,7 @@ void RestApiServer::getSystemStatusJson(AsyncWebServerRequest *request) {
|
|||||||
request->send(200, "application/json", json);
|
request->send(200, "application/json", json);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestApiServer::getClusterMembersJson(AsyncWebServerRequest *request) {
|
void ApiServer::onClusterMembersRequest(AsyncWebServerRequest *request) {
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
JsonArray arr = doc["members"].to<JsonArray>();
|
JsonArray arr = doc["members"].to<JsonArray>();
|
||||||
for (const auto& node : *ctx.memberList) {
|
for (const auto& node : *ctx.memberList) {
|
||||||
@@ -79,7 +79,7 @@ void RestApiServer::getClusterMembersJson(AsyncWebServerRequest *request) {
|
|||||||
request->send(200, "application/json", json);
|
request->send(200, "application/json", json);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestApiServer::methodToStr(const std::tuple<String, int> &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj)
|
void ApiServer::methodToStr(const std::tuple<String, int> &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj)
|
||||||
{
|
{
|
||||||
int method = std::get<1>(endpoint);
|
int method = std::get<1>(endpoint);
|
||||||
const char *methodStr = nullptr;
|
const char *methodStr = nullptr;
|
||||||
@@ -107,7 +107,7 @@ void RestApiServer::methodToStr(const std::tuple<String, int> &endpoint, Arduino
|
|||||||
apiObj["method"] = methodStr;
|
apiObj["method"] = methodStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestApiServer::onFirmwareUpdateRequest(AsyncWebServerRequest *request) {
|
void ApiServer::onFirmwareUpdateRequest(AsyncWebServerRequest *request) {
|
||||||
bool hasError = !Update.hasError();
|
bool hasError = !Update.hasError();
|
||||||
AsyncWebServerResponse *response = request->beginResponse(200, "application/json", hasError ? "{\"status\": \"OK\"}" : "{\"status\": \"FAIL\"}");
|
AsyncWebServerResponse *response = request->beginResponse(200, "application/json", hasError ? "{\"status\": \"OK\"}" : "{\"status\": \"FAIL\"}");
|
||||||
response->addHeader("Connection", "close");
|
response->addHeader("Connection", "close");
|
||||||
@@ -119,7 +119,7 @@ void RestApiServer::onFirmwareUpdateRequest(AsyncWebServerRequest *request) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestApiServer::onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) {
|
void ApiServer::onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||||
if (!index) {
|
if (!index) {
|
||||||
Serial.print("[OTA] Update Start ");
|
Serial.print("[OTA] Update Start ");
|
||||||
Serial.println(filename);
|
Serial.println(filename);
|
||||||
@@ -157,7 +157,7 @@ void RestApiServer::onFirmwareUpload(AsyncWebServerRequest *request, const Strin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestApiServer::onRestartRequest(AsyncWebServerRequest *request) {
|
void ApiServer::onRestartRequest(AsyncWebServerRequest *request) {
|
||||||
AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"status\": \"restarting\"}");
|
AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"status\": \"restarting\"}");
|
||||||
response->addHeader("Connection", "close");
|
response->addHeader("Connection", "close");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
@@ -10,14 +10,12 @@
|
|||||||
#include "NodeContext.h"
|
#include "NodeContext.h"
|
||||||
#include "NodeInfo.h"
|
#include "NodeInfo.h"
|
||||||
|
|
||||||
#define DEBUG_UPDATER 1
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
|
|
||||||
class RestApiServer {
|
class ApiServer {
|
||||||
public:
|
public:
|
||||||
RestApiServer(NodeContext& ctx, uint16_t port = 80);
|
ApiServer(NodeContext& ctx, uint16_t port = 80);
|
||||||
void begin();
|
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);
|
||||||
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
|
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
|
||||||
@@ -26,9 +24,9 @@ private:
|
|||||||
AsyncWebServer server;
|
AsyncWebServer server;
|
||||||
NodeContext& ctx;
|
NodeContext& ctx;
|
||||||
std::vector<std::tuple<String, int>> serviceRegistry;
|
std::vector<std::tuple<String, int>> serviceRegistry;
|
||||||
void getClusterMembersJson(AsyncWebServerRequest *request);
|
void onClusterMembersRequest(AsyncWebServerRequest *request);
|
||||||
void methodToStr(const std::tuple<String, int> &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj);
|
void methodToStr(const std::tuple<String, int> &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj);
|
||||||
void getSystemStatusJson(AsyncWebServerRequest *request);
|
void onSystemStatusRequest(AsyncWebServerRequest *request);
|
||||||
void onFirmwareUpdateRequest(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 onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||||
void onRestartRequest(AsyncWebServerRequest *request);
|
void onRestartRequest(AsyncWebServerRequest *request);
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
ClusterManager::ClusterManager(NodeContext& ctx) : ctx(ctx) {
|
ClusterManager::ClusterManager(NodeContext& ctx) : ctx(ctx) {
|
||||||
// Register callback for node_discovered event
|
// Register callback for node_discovered event
|
||||||
ctx.registerEvent("node_discovered", [this](void* data) {
|
ctx.on("node_discovered", [this](void* data) {
|
||||||
NodeInfo* node = static_cast<NodeInfo*>(data);
|
NodeInfo* node = static_cast<NodeInfo*>(data);
|
||||||
this->addOrUpdateNode(node->hostname, node->ip);
|
this->addOrUpdateNode(node->hostname, node->ip);
|
||||||
});
|
});
|
||||||
@@ -111,7 +111,7 @@ void ClusterManager::heartbeatTaskCallback() {
|
|||||||
node.lastSeen = millis();
|
node.lastSeen = millis();
|
||||||
node.status = NodeInfo::ACTIVE;
|
node.status = NodeInfo::ACTIVE;
|
||||||
updateLocalNodeResources();
|
updateLocalNodeResources();
|
||||||
ctx.triggerEvent("node_discovered", &node);
|
ctx.fire("node_discovered", &node);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,5 +55,5 @@ void NetworkManager::setupWiFi() {
|
|||||||
}
|
}
|
||||||
self.lastSeen = millis();
|
self.lastSeen = millis();
|
||||||
self.status = NodeInfo::ACTIVE;
|
self.status = NodeInfo::ACTIVE;
|
||||||
ctx.triggerEvent("node_discovered", &self);
|
ctx.fire("node_discovered", &self);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ NodeContext::~NodeContext() {
|
|||||||
delete memberList;
|
delete memberList;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeContext::registerEvent(const std::string& event, EventCallback cb) {
|
void NodeContext::on(const std::string& event, EventCallback cb) {
|
||||||
eventRegistry[event].push_back(cb);
|
eventRegistry[event].push_back(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeContext::triggerEvent(const std::string& event, void* data) {
|
void NodeContext::fire(const std::string& event, void* data) {
|
||||||
for (auto& cb : eventRegistry[event]) {
|
for (auto& cb : eventRegistry[event]) {
|
||||||
cb(data);
|
cb(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ public:
|
|||||||
using EventCallback = std::function<void(void*)>;
|
using EventCallback = std::function<void(void*)>;
|
||||||
std::map<std::string, std::vector<EventCallback>> eventRegistry;
|
std::map<std::string, std::vector<EventCallback>> eventRegistry;
|
||||||
|
|
||||||
void registerEvent(const std::string& event, EventCallback cb);
|
void on(const std::string& event, EventCallback cb);
|
||||||
void triggerEvent(const std::string& event, void* data);
|
void fire(const std::string& event, void* data);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
#include "NodeContext.h"
|
#include "NodeContext.h"
|
||||||
#include "NetworkManager.h"
|
#include "NetworkManager.h"
|
||||||
#include "ClusterManager.h"
|
#include "ClusterManager.h"
|
||||||
#include "RestApiServer.h"
|
#include "ApiServer.h"
|
||||||
|
|
||||||
NodeContext ctx;
|
NodeContext ctx;
|
||||||
NetworkManager network(ctx);
|
NetworkManager network(ctx);
|
||||||
ClusterManager cluster(ctx);
|
ClusterManager cluster(ctx);
|
||||||
RestApiServer apiServer(ctx);
|
ApiServer apiServer(ctx);
|
||||||
|
|
||||||
Task tSendDiscovery(TaskIntervals::SEND_DISCOVERY, TASK_FOREVER, [](){ cluster.sendDiscovery(); });
|
Task tSendDiscovery(TaskIntervals::SEND_DISCOVERY, TASK_FOREVER, [](){ cluster.sendDiscovery(); });
|
||||||
Task tListenForDiscovery(TaskIntervals::LISTEN_FOR_DISCOVERY, TASK_FOREVER, [](){ cluster.listenForDiscovery(); });
|
Task tListenForDiscovery(TaskIntervals::LISTEN_FOR_DISCOVERY, TASK_FOREVER, [](){ cluster.listenForDiscovery(); });
|
||||||
|
|||||||
Reference in New Issue
Block a user