SPORE
SProcket ORchestration Engine
SPORE is a cluster engine for ESP8266 microcontrollers that provides automatic node discovery, health monitoring, and over-the-air updates in a distributed network environment.
Features
- WiFi Management: Automatic WiFi STA/AP configuration with MAC-based hostname generation
- Auto Discovery: UDP-based node discovery with automatic cluster membership
- Service Registry: Dynamic API endpoint discovery and registration
- Health Monitoring: Real-time node status tracking with resource monitoring
- Event System: Local and cluster-wide event publishing/subscription
- Over-The-Air Updates: Seamless firmware updates across the cluster
- REST API: HTTP-based cluster management and monitoring
Supported Hardware
- ESP-01 (1MB Flash)
- ESP-01S (1MB Flash)
- Other ESP8266 boards with 1MB+ flash
Architecture
Core Components
The system architecture consists of several key components working together:
- Network Manager: WiFi connection handling and hostname configuration
- Cluster Manager: Node discovery, member list management, and health monitoring
- API Server: HTTP API server with dynamic endpoint registration
- Task Scheduler: Cooperative multitasking system for background operations
- Node Context: Central context providing event system and shared resources
Auto Discovery Protocol
The cluster uses a UDP-based discovery protocol for automatic node detection:
- Discovery Broadcast: Nodes periodically send UDP packets on port 4210
- Response Handling: Nodes respond with their hostname and IP address
- Member Management: Discovered nodes are automatically added to the cluster
- Health Monitoring: Continuous status checking via HTTP API calls
Task Scheduling
The system runs several background tasks at different intervals:
- Discovery Tasks: Send/listen for discovery packets (1s/100ms)
- Status Updates: Monitor cluster member health (1s)
- Heartbeat: Maintain cluster connectivity (2s)
- Member Info: Update detailed node information (10s)
- Debug Output: Print cluster status (5s)
API Endpoints
Node Management
| Endpoint | Method | Description |
|---|---|---|
/api/node/status |
GET | Get system resources and API endpoints |
/api/node/update |
POST | Upload and install firmware update |
/api/node/restart |
POST | Restart the node |
Cluster Management
| Endpoint | Method | Description |
|---|---|---|
/api/cluster/members |
GET | Get cluster membership and status |
Node Status Response
{
"freeHeap": 12345,
"chipId": 12345678,
"sdkVersion": "2.2.2-dev(38a443e)",
"cpuFreqMHz": 80,
"flashChipSize": 1048576,
"api": [
{
"uri": "/api/node/status",
"method": "GET"
}
]
}
Cluster Members Response
{
"members": [
{
"hostname": "esp_123456",
"ip": "192.168.1.100",
"lastSeen": 1234567890,
"latency": 5,
"status": "active",
"resources": {
"freeHeap": 12345,
"chipId": 12345678,
"sdkVersion": "2.2.2-dev(38a443e)",
"cpuFreqMHz": 80,
"flashChipSize": 1048576
},
"api": [
{
"uri": "/api/node/status",
"method": "GET"
}
]
}
]
}
Configuration
Environment Setup
Create a .env file in your project root:
# API node IP for cluster management
export API_NODE=192.168.1.100
PlatformIO Configuration
The project uses PlatformIO with the following configuration:
- Framework: Arduino
- Board: ESP-01 with 1MB flash
- Upload Speed: 115200 baud
- Flash Mode: DOUT (required for ESP-01S)
Dependencies
The project requires the following libraries:
esp32async/ESPAsyncWebServer@^3.8.0- HTTP API serverbblanchon/ArduinoJson@^7.4.2- JSON processingarkhipenko/TaskScheduler@^3.8.5- Cooperative multitasking
Development
Prerequisites
- PlatformIO Core or PlatformIO IDE
- ESP8266 development tools
jqfor JSON processing in scripts
Building
Build the firmware for specific chip:
./ctl.sh build target esp01_1m
Flashing
Flash firmware to a connected device:
./ctl.sh flash target esp01_1m
Over-The-Air Updates
Update a specific node:
./ctl.sh ota update 192.168.1.100 esp01_1m
Update all nodes in the cluster:
./ctl.sh ota all esp01_1m
Cluster Management
View cluster members:
./ctl.sh cluster members
Implementation Details
Event System
The NodeContext provides an event-driven architecture:
// Subscribe to events
ctx.on("node_discovered", [](void* data) {
NodeInfo* node = static_cast<NodeInfo*>(data);
// Handle new node discovery
});
// Publish events
ctx.fire("node_discovered", &newNode);
Node Status Tracking
Nodes are automatically categorized by their activity:
- ACTIVE: Responding within 10 seconds
- INACTIVE: No response for 10-60 seconds
- DEAD: No response for over 60 seconds
Resource Monitoring
Each node tracks:
- Free heap memory
- Chip ID and SDK version
- CPU frequency
- Flash chip size
- API endpoint registry
WiFi Fallback
The system includes automatic WiFi fallback:
- Attempts to connect to configured WiFi network
- If connection fails, creates an access point
- Hostname is automatically generated from MAC address
Task Management
The SPORE system includes a comprehensive TaskManager that provides a clean interface for managing system tasks. This makes it easy to add, configure, and control background tasks without cluttering the main application code.
TaskManager Features
- Easy Task Registration: Simple API for adding new tasks with configurable intervals
- Dynamic Control: Enable/disable tasks at runtime
- Interval Management: Change task execution frequency on the fly
- Status Monitoring: View task status and configuration
- Automatic Lifecycle: Tasks are automatically managed and executed
Basic Usage
#include "TaskManager.h"
// Create task manager
TaskManager taskManager(ctx);
// Register tasks
taskManager.registerTask("heartbeat", 2000, heartbeatFunction);
taskManager.registerTask("maintenance", 30000, maintenanceFunction);
// Initialize and start all tasks
taskManager.initialize();
Using std::bind with Member Functions
#include <functional>
#include "TaskManager.h"
class MyService {
public:
void sendHeartbeat() {
Serial.println("Service heartbeat");
}
void performMaintenance() {
Serial.println("Running maintenance");
}
};
MyService service;
TaskManager taskManager(ctx);
// Register member functions using std::bind
taskManager.registerTask("heartbeat", 2000,
std::bind(&MyService::sendHeartbeat, &service));
taskManager.registerTask("maintenance", 30000,
std::bind(&MyService::performMaintenance, &service));
// Initialize and start all tasks
taskManager.initialize();
Using Lambda Functions
// Register lambda functions directly
taskManager.registerTask("counter", 1000, []() {
static int count = 0;
Serial.printf("Count: %d\n", ++count);
});
// Lambda with capture
int threshold = 100;
taskManager.registerTask("monitor", 5000, [&threshold]() {
if (ESP.getFreeHeap() < threshold) {
Serial.println("Low memory warning!");
}
});
Complex Task Registration
class NetworkManager {
public:
void checkConnection() { /* ... */ }
void sendData(String data) { /* ... */ }
};
NetworkManager network;
// Multiple operations in one task
taskManager.registerTask("network_ops", 3000,
std::bind([](NetworkManager* net) {
net->checkConnection();
net->sendData("status_update");
}, &network));
Task Control API
// Enable/disable tasks
taskManager.enableTask("heartbeat");
taskManager.disableTask("maintenance");
// Change intervals
taskManager.setTaskInterval("heartbeat", 5000); // 5 seconds
// Check status
bool isRunning = taskManager.isTaskEnabled("heartbeat");
unsigned long interval = taskManager.getTaskInterval("heartbeat");
// Print all task statuses
taskManager.printTaskStatus();
Remote Task Management
The TaskManager integrates with the API server to provide remote task control:
# Get task status
curl http://192.168.1.100/api/tasks/status
# Control tasks
curl -X POST http://192.168.1.100/api/tasks/control \
-d "task=heartbeat&action=disable"
# Available actions: enable, disable, start, stop
Adding Custom Tasks
Method 1: Using std::bind (Recommended)
-
Create your service class:
class SensorService { public: void readTemperature() { // Read sensor logic Serial.println("Reading temperature"); } void calibrateSensors() { // Calibration logic Serial.println("Calibrating sensors"); } }; -
Register with TaskManager:
SensorService sensors; taskManager.registerTask("temp_read", 1000, std::bind(&SensorService::readTemperature, &sensors)); taskManager.registerTask("calibrate", 60000, std::bind(&SensorService::calibrateSensors, &sensors));
Method 2: Traditional Functions
-
Define your task function:
void myCustomTask() { // Your task logic here Serial.println("Custom task executed"); } -
Register with TaskManager:
taskManager.registerTask("my_task", 10000, myCustomTask);
Task Configuration Options
When registering tasks, you can specify:
- Name: Unique identifier for the task
- Interval: Execution frequency in milliseconds
- Callback: Function, bound method, or lambda to execute
- Enabled: Whether the task starts enabled (default: true)
- AutoStart: Whether to start automatically (default: true)
// Traditional function
taskManager.registerTask("delayed_task", 5000, taskFunction, true, false);
// Member function with std::bind
taskManager.registerTask("service_task", 3000,
std::bind(&Service::method, &instance), true, false);
// Lambda function
taskManager.registerTask("lambda_task", 2000,
[]() { Serial.println("Lambda!"); }, true, false);
Current Limitations
- WiFi credentials are hardcoded in
Config.cpp(should be configurable) - Limited error handling for network failures
- No persistent storage for configuration
- Basic health monitoring without advanced metrics
Troubleshooting
Common Issues
- Discovery Failures: Check UDP port 4210 is not blocked
- WiFi Connection: Verify SSID/password in Config.cpp
- OTA Updates: Ensure sufficient flash space (1MB minimum)
- Cluster Split: Check network connectivity between nodes
Debug Output
Enable serial monitoring to see cluster activity:
pio device monitor
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly on ESP8266 hardware
- Submit a pull request
License
[Add your license information here]
Acknowledgments
- Built with PlatformIO
- Uses TaskScheduler for cooperative multitasking
- ESPAsyncWebServer for HTTP API
- ArduinoJson for JSON processing