feat: implement Spore framework class as main orchestration layer

- Add Spore class as unified interface for all core framework functionality
- Implement setup() and begin() methods for flexible initialization pattern
- Add service registration with addService() for both raw and smart pointers
- Provide accessor methods for core components (getTaskManager, getContext, etc.)
- Automatically include core services (Node, Network, Cluster, Task)
- Update all examples to use simplified Spore framework approach
- Fix circular dependency issues in include structure
- Remove setHostname and setApiPort configuration methods
- Add comprehensive documentation and usage examples

The Spore class encapsulates all core functionality from the base example
and provides a clean, unified API for the entire framework. Examples now
use just spore.setup() -> add services -> spore.begin() -> spore.loop().
This commit is contained in:
2025-09-13 21:17:54 +02:00
parent 72b559e047
commit bf17684dc6
12 changed files with 356 additions and 184 deletions

View File

@@ -41,6 +41,7 @@ SPORE is a cluster engine for ESP8266 microcontrollers that provides automatic n
SPORE uses a modular architecture with automatic node discovery, health monitoring, and distributed task management. SPORE uses a modular architecture with automatic node discovery, health monitoring, and distributed task management.
**Core Components:** **Core Components:**
- **Spore Framework**: Main framework class that orchestrates all components
- **Network Manager**: WiFi connection handling and hostname configuration - **Network Manager**: WiFi connection handling and hostname configuration
- **Cluster Manager**: Node discovery, member list management, and health monitoring - **Cluster Manager**: Node discovery, member list management, and health monitoring
- **API Server**: HTTP API server with dynamic endpoint registration - **API Server**: HTTP API server with dynamic endpoint registration
@@ -58,6 +59,50 @@ SPORE uses a modular architecture with automatic node discovery, health monitori
📖 **Detailed Architecture:** See [`docs/Architecture.md`](./docs/Architecture.md) for comprehensive system design and implementation details. 📖 **Detailed Architecture:** See [`docs/Architecture.md`](./docs/Architecture.md) for comprehensive system design and implementation details.
## Quick Start
The Spore framework provides a simple, unified interface for all core functionality:
```cpp
#include <Arduino.h>
#include "Spore.h"
// Create Spore instance with custom labels
Spore spore({
{"app", "my_app"},
{"role", "controller"}
});
void setup() {
spore.setup();
spore.begin();
}
void loop() {
spore.loop();
}
```
**Adding Custom Services:**
```cpp
void setup() {
spore.setup();
// Create and register custom services
RelayService* relayService = new RelayService(spore.getTaskManager(), 2);
spore.addService(relayService);
// Or using smart pointers
auto sensorService = std::make_shared<SensorService>();
spore.addService(sensorService);
// Start the API server and complete initialization
spore.begin();
}
```
**Examples:** See [`examples/base/`](./examples/base/) for basic usage and [`examples/relay/`](./examples/relay/) for custom service integration.
## API Reference ## API Reference
The system provides a comprehensive RESTful API for monitoring and controlling the embedded device. All endpoints return JSON responses and support standard HTTP status codes. The system provides a comprehensive RESTful API for monitoring and controlling the embedded device. All endpoints return JSON responses and support standard HTTP status codes.

View File

@@ -1,56 +1,21 @@
#include <Arduino.h> #include <Arduino.h>
#include <functional> #include "Spore.h"
#include "Globals.h"
#include "NodeContext.h"
#include "NetworkManager.h"
#include "ClusterManager.h"
#include "ApiServer.h"
#include "TaskManager.h"
// Services // Create Spore instance with custom labels
#include "services/NodeService.h" Spore spore({
#include "services/NetworkService.h"
#include "services/ClusterService.h"
#include "services/TaskService.h"
using namespace std;
NodeContext ctx({
{"app", "base"}, {"app", "base"},
{"role", "demo"} {"role", "demo"}
}); });
NetworkManager network(ctx);
TaskManager taskManager(ctx);
ClusterManager cluster(ctx, taskManager);
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
// Create services
NodeService nodeService(ctx, apiServer);
NetworkService networkService(network);
ClusterService clusterService(ctx);
TaskService taskService(taskManager);
void setup() { void setup() {
Serial.begin(115200); // Initialize the Spore framework
spore.setup();
// Setup WiFi first // Start the API server and complete initialization
network.setupWiFi(); spore.begin();
// Initialize and start all tasks
taskManager.initialize();
// Register services and start API server
apiServer.addService(nodeService);
apiServer.addService(networkService);
apiServer.addService(clusterService);
apiServer.addService(taskService);
apiServer.begin();
// Print initial task status
taskManager.printTaskStatus();
} }
void loop() { void loop() {
taskManager.execute(); // Run the Spore framework loop
yield(); spore.loop();
} }

View File

@@ -1,17 +1,5 @@
#include <Arduino.h> #include <Arduino.h>
#include <functional> #include "Spore.h"
#include "Globals.h"
#include "NodeContext.h"
#include "NetworkManager.h"
#include "ClusterManager.h"
#include "ApiServer.h"
#include "TaskManager.h"
// Services
#include "services/NodeService.h"
#include "services/NetworkService.h"
#include "services/ClusterService.h"
#include "services/TaskService.h"
#include "NeoPatternService.h" #include "NeoPatternService.h"
#ifndef LED_STRIP_PIN #ifndef LED_STRIP_PIN
@@ -26,46 +14,32 @@
#define LED_STRIP_TYPE (NEO_GRB + NEO_KHZ800) #define LED_STRIP_TYPE (NEO_GRB + NEO_KHZ800)
#endif #endif
NodeContext ctx({ // Create Spore instance with custom labels
Spore spore({
{"app", "neopattern"}, {"app", "neopattern"},
{"device", "light"}, {"device", "light"},
{"pixels", String(LED_STRIP_LENGTH)}, {"pixels", String(LED_STRIP_LENGTH)},
{"pin", String(LED_STRIP_PIN)} {"pin", String(LED_STRIP_PIN)}
}); });
NetworkManager network(ctx);
TaskManager taskManager(ctx);
ClusterManager cluster(ctx, taskManager);
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
// Create services // Create custom service
NodeService nodeService(ctx, apiServer); NeoPatternService* neoPatternService = nullptr;
NetworkService networkService(network);
ClusterService clusterService(ctx);
TaskService taskService(taskManager);
NeoPatternService neoPatternService(taskManager, LED_STRIP_LENGTH, LED_STRIP_PIN, LED_STRIP_TYPE);
void setup() { void setup() {
Serial.begin(115200); // Initialize the Spore framework
spore.setup();
// Setup WiFi first // Create and add custom service
network.setupWiFi(); neoPatternService = new NeoPatternService(spore.getTaskManager(), LED_STRIP_LENGTH, LED_STRIP_PIN, LED_STRIP_TYPE);
spore.addService(neoPatternService);
// Initialize and start all tasks // Start the API server and complete initialization
taskManager.initialize(); spore.begin();
// Register services and start API server Serial.println("[Main] NeoPattern service registered and ready!");
apiServer.addService(nodeService);
apiServer.addService(networkService);
apiServer.addService(clusterService);
apiServer.addService(taskService);
apiServer.addService(neoPatternService);
apiServer.begin();
// Print initial task status
taskManager.printTaskStatus();
} }
void loop() { void loop() {
taskManager.execute(); // Run the Spore framework loop
yield(); spore.loop();
} }

View File

@@ -1,17 +1,5 @@
#include <Arduino.h> #include <Arduino.h>
#include <functional> #include "Spore.h"
#include "Globals.h"
#include "NodeContext.h"
#include "NetworkManager.h"
#include "ClusterManager.h"
#include "ApiServer.h"
#include "TaskManager.h"
// Services
#include "services/NodeService.h"
#include "services/NetworkService.h"
#include "services/ClusterService.h"
#include "services/TaskService.h"
#include "NeoPixelService.h" #include "NeoPixelService.h"
#ifndef NEOPIXEL_PIN #ifndef NEOPIXEL_PIN
@@ -26,46 +14,32 @@
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800)
#endif #endif
NodeContext ctx({ // Create Spore instance with custom labels
Spore spore({
{"app", "neopixel"}, {"app", "neopixel"},
{"device", "light"}, {"device", "light"},
{"pixels", String(NEOPIXEL_COUNT)}, {"pixels", String(NEOPIXEL_COUNT)},
{"pin", String(NEOPIXEL_PIN)} {"pin", String(NEOPIXEL_PIN)}
}); });
NetworkManager network(ctx);
TaskManager taskManager(ctx);
ClusterManager cluster(ctx, taskManager);
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
// Create services // Create custom service
NodeService nodeService(ctx, apiServer); NeoPixelService* neoPixelService = nullptr;
NetworkService networkService(network);
ClusterService clusterService(ctx);
TaskService taskService(taskManager);
NeoPixelService neoPixelService(taskManager, NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_TYPE);
void setup() { void setup() {
Serial.begin(115200); // Initialize the Spore framework
spore.setup();
// Setup WiFi first // Create and add custom service
network.setupWiFi(); neoPixelService = new NeoPixelService(spore.getTaskManager(), NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_TYPE);
spore.addService(neoPixelService);
// Initialize and start all tasks // Start the API server and complete initialization
taskManager.initialize(); spore.begin();
// Register services and start API server Serial.println("[Main] NeoPixel service registered and ready!");
apiServer.addService(nodeService);
apiServer.addService(networkService);
apiServer.addService(clusterService);
apiServer.addService(taskService);
apiServer.addService(neoPixelService);
apiServer.begin();
// Print initial task status
taskManager.printTaskStatus();
} }
void loop() { void loop() {
taskManager.execute(); // Run the Spore framework loop
yield(); spore.loop();
} }

View File

@@ -1,10 +1,48 @@
# Relay Service Example # Relay Service Example
A minimal example that uses the framework's `NodeContext`, `NetworkManager`, `TaskManager`, and `ApiServer` to control a relay via REST and log status periodically as a task. A minimal example that demonstrates the Spore framework with a custom RelayService. The Spore framework automatically handles all core functionality (WiFi, clustering, API server, task management) while allowing easy registration of custom services.
- Default relay pin: `GPIO0` (ESP-01). Override with `-DRELAY_PIN=<pin>`. - Default relay pin: `GPIO0` (ESP-01). Override with `-DRELAY_PIN=<pin>`.
- WiFi and API port are configured in `src/Config.cpp`. - WiFi and API port are configured in `src/Config.cpp`.
## Spore Framework Usage
This example demonstrates the simplified Spore framework approach:
```cpp
#include <Arduino.h>
#include "Spore.h"
#include "RelayService.h"
Spore spore({
{"app", "relay"},
{"device", "actuator"},
{"pin", String(RELAY_PIN)}
});
RelayService* relayService = nullptr;
void setup() {
spore.setup();
relayService = new RelayService(spore.getTaskManager(), RELAY_PIN);
spore.addService(relayService);
spore.begin();
}
void loop() {
spore.loop();
}
```
The Spore framework automatically provides:
- WiFi connectivity management
- Cluster discovery and management
- REST API server with core endpoints
- Task scheduling and execution
- Node status monitoring
## Build & Upload ## Build & Upload
- ESP01S: - ESP01S:
@@ -32,17 +70,17 @@ curl http://192.168.1.50/api/relay/status
- Turn relay ON - Turn relay ON
```bash ```bash
curl -X POST http://192.168.1.50/api/relay/set -d state=on curl -X POST http://192.168.1.50/api/relay -d state=on
``` ```
- Turn relay OFF - Turn relay OFF
```bash ```bash
curl -X POST http://192.168.1.50/api/relay/set -d state=off curl -X POST http://192.168.1.50/api/relay -d state=off
``` ```
- Toggle relay - Toggle relay
```bash ```bash
curl -X POST http://192.168.1.50/api/relay/set -d state=toggle curl -X POST http://192.168.1.50/api/relay -d state=toggle
``` ```
Notes: Notes:

View File

@@ -1,65 +1,37 @@
#include <Arduino.h> #include <Arduino.h>
#include <functional> #include "Spore.h"
#include "Globals.h"
#include "NodeContext.h"
#include "NetworkManager.h"
#include "ClusterManager.h"
#include "ApiServer.h"
#include "TaskManager.h"
// Services
#include "services/NodeService.h"
#include "services/NetworkService.h"
#include "services/ClusterService.h"
#include "services/TaskService.h"
#include "RelayService.h" #include "RelayService.h"
using namespace std;
// Choose a default relay pin. For ESP-01 this is GPIO0. Adjust as needed for your board. // Choose a default relay pin. For ESP-01 this is GPIO0. Adjust as needed for your board.
#ifndef RELAY_PIN #ifndef RELAY_PIN
#define RELAY_PIN 0 #define RELAY_PIN 0
#endif #endif
NodeContext ctx({ // Create Spore instance with custom labels
Spore spore({
{"app", "relay"}, {"app", "relay"},
{"device", "actuator"}, {"device", "actuator"},
{"pin", String(RELAY_PIN)} {"pin", String(RELAY_PIN)}
}); });
NetworkManager network(ctx);
TaskManager taskManager(ctx);
ClusterManager cluster(ctx, taskManager);
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
// Create services // Create custom service
NodeService nodeService(ctx, apiServer); RelayService* relayService = nullptr;
NetworkService networkService(network);
ClusterService clusterService(ctx);
TaskService taskService(taskManager);
RelayService relayService(taskManager, RELAY_PIN);
void setup() { void setup() {
Serial.begin(115200); // Initialize the Spore framework
spore.setup();
// Setup WiFi first // Create and add custom service
network.setupWiFi(); relayService = new RelayService(spore.getTaskManager(), RELAY_PIN);
spore.addService(relayService);
// Initialize and start all tasks // Start the API server and complete initialization
taskManager.initialize(); spore.begin();
// Register services and start API server Serial.println("[Main] Relay service registered and ready!");
apiServer.addService(nodeService);
apiServer.addService(networkService);
apiServer.addService(clusterService);
apiServer.addService(taskService);
apiServer.addService(relayService);
apiServer.begin();
// Print initial task status
taskManager.printTaskStatus();
} }
void loop() { void loop() {
taskManager.execute(); // Run the Spore framework loop
yield(); spore.loop();
} }

View File

@@ -1,5 +1,4 @@
#pragma once #pragma once
#include "Globals.h"
#include "NodeContext.h" #include "NodeContext.h"
#include "NodeInfo.h" #include "NodeInfo.h"
#include "TaskManager.h" #include "TaskManager.h"

View File

@@ -1,5 +1,4 @@
#pragma once #pragma once
#include "Globals.h"
#include <IPAddress.h> #include <IPAddress.h>
#include <vector> #include <vector>
#include <tuple> #include <tuple>

51
include/Spore.h Normal file
View File

@@ -0,0 +1,51 @@
#pragma once
#include <vector>
#include <memory>
#include <initializer_list>
#include <utility>
#include "NodeContext.h"
#include "NetworkManager.h"
#include "ClusterManager.h"
#include "ApiServer.h"
#include "TaskManager.h"
#include "services/Service.h"
class Spore {
public:
Spore();
Spore(std::initializer_list<std::pair<String, String>> initialLabels);
~Spore();
// Core lifecycle methods
void setup();
void begin();
void loop();
// Service management
void addService(std::shared_ptr<Service> service);
void addService(Service* service);
// Access to core components
NodeContext& getContext() { return ctx; }
NetworkManager& getNetwork() { return network; }
TaskManager& getTaskManager() { return taskManager; }
ClusterManager& getCluster() { return cluster; }
ApiServer& getApiServer() { return apiServer; }
private:
void initializeCore();
void registerCoreServices();
void startApiServer();
NodeContext ctx;
NetworkManager network;
TaskManager taskManager;
ClusterManager cluster;
ApiServer apiServer;
std::vector<std::shared_ptr<Service>> services;
bool initialized;
bool apiServerStarted;
};

View File

@@ -1,4 +1,5 @@
#include "ClusterManager.h" #include "ClusterManager.h"
#include "Globals.h"
ClusterManager::ClusterManager(NodeContext& ctx, TaskManager& taskMgr) : ctx(ctx), taskManager(taskMgr) { ClusterManager::ClusterManager(NodeContext& ctx, TaskManager& taskMgr) : ctx(ctx), taskManager(taskMgr) {
// Register callback for node_discovered event // Register callback for node_discovered event

View File

@@ -1,4 +1,5 @@
#include "NodeInfo.h" #include "NodeInfo.h"
#include "Globals.h"
const char* statusToStr(NodeInfo::Status status) { const char* statusToStr(NodeInfo::Status status) {
switch (status) { switch (status) {

153
src/Spore.cpp Normal file
View File

@@ -0,0 +1,153 @@
#include "Spore.h"
#include "services/NodeService.h"
#include "services/NetworkService.h"
#include "services/ClusterService.h"
#include "services/TaskService.h"
#include <Arduino.h>
Spore::Spore() : ctx(), network(ctx), taskManager(ctx), cluster(ctx, taskManager),
apiServer(ctx, taskManager, ctx.config.api_server_port),
initialized(false), apiServerStarted(false) {
}
Spore::Spore(std::initializer_list<std::pair<String, String>> initialLabels)
: ctx(initialLabels), network(ctx), taskManager(ctx), cluster(ctx, taskManager),
apiServer(ctx, taskManager, ctx.config.api_server_port),
initialized(false), apiServerStarted(false) {
}
Spore::~Spore() {
// Services will be automatically cleaned up by shared_ptr
}
void Spore::setup() {
if (initialized) {
Serial.println("[Spore] Already initialized, skipping setup");
return;
}
Serial.begin(115200);
Serial.println("[Spore] Starting Spore framework...");
// Initialize core components
initializeCore();
// Register core services
registerCoreServices();
initialized = true;
Serial.println("[Spore] Framework setup complete - call begin() to start API server");
}
void Spore::begin() {
if (!initialized) {
Serial.println("[Spore] Framework not initialized, call setup() first");
return;
}
if (apiServerStarted) {
Serial.println("[Spore] API server already started");
return;
}
Serial.println("[Spore] Starting API server...");
// Start API server
startApiServer();
// Print initial task status
taskManager.printTaskStatus();
Serial.println("[Spore] Framework ready!");
}
void Spore::loop() {
if (!initialized) {
Serial.println("[Spore] Framework not initialized, call setup() first");
return;
}
taskManager.execute();
yield();
}
void Spore::addService(std::shared_ptr<Service> service) {
if (!service) {
Serial.println("[Spore] Warning: Attempted to add null service");
return;
}
services.push_back(service);
if (apiServerStarted) {
// If API server is already started, register the service immediately
apiServer.addService(*service);
Serial.printf("[Spore] Added service '%s' to running API server\n", service->getName());
} else {
Serial.printf("[Spore] Registered service '%s' (will be added to API server when begin() is called)\n", service->getName());
}
}
void Spore::addService(Service* service) {
if (!service) {
Serial.println("[Spore] Warning: Attempted to add null service");
return;
}
// Wrap raw pointer in shared_ptr with no-op deleter to avoid double-delete
addService(std::shared_ptr<Service>(service, [](Service*){}));
}
void Spore::initializeCore() {
Serial.println("[Spore] Initializing core components...");
// Setup WiFi first
network.setupWiFi();
// Initialize task manager
taskManager.initialize();
Serial.println("[Spore] Core components initialized");
}
void Spore::registerCoreServices() {
Serial.println("[Spore] Registering core services...");
// Create core services
auto nodeService = std::make_shared<NodeService>(ctx, apiServer);
auto networkService = std::make_shared<NetworkService>(network);
auto clusterService = std::make_shared<ClusterService>(ctx);
auto taskService = std::make_shared<TaskService>(taskManager);
// Add to services list
services.push_back(nodeService);
services.push_back(networkService);
services.push_back(clusterService);
services.push_back(taskService);
Serial.println("[Spore] Core services registered");
}
void Spore::startApiServer() {
if (apiServerStarted) {
Serial.println("[Spore] API server already started");
return;
}
Serial.println("[Spore] Starting API server...");
// Register all services with API server
for (auto& service : services) {
if (service) {
apiServer.addService(*service);
Serial.printf("[Spore] Added service '%s' to API server\n", service->getName());
}
}
// Start the API server
apiServer.begin();
apiServerStarted = true;
Serial.println("[Spore] API server started");
}