2025-08-21 15:54:05 +02:00
2025-08-21 15:54:05 +02:00
2025-08-22 07:30:47 +02:00
2025-08-21 15:54:05 +02:00
2025-08-21 15:54:05 +02:00
2025-08-21 15:54:05 +02:00
2025-08-21 15:54:05 +02:00
2025-08-21 15:54:05 +02:00
2025-08-21 15:54:05 +02:00
2025-08-21 22:34:06 +02:00

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:

  1. Discovery Broadcast: Nodes periodically send UDP packets on port 4210
  2. Response Handling: Nodes respond with their hostname and IP address
  3. Member Management: Discovered nodes are automatically added to the cluster
  4. 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 server
  • bblanchon/ArduinoJson@^7.4.2 - JSON processing
  • arkhipenko/TaskScheduler@^3.8.5 - Cooperative multitasking

Development

Prerequisites

  • PlatformIO Core or PlatformIO IDE
  • ESP8266 development tools
  • jq for JSON processing in scripts

Building

Build the firmware:

./ctl.sh build

Flashing

Flash firmware to a connected device:

./ctl.sh flash

Over-The-Air Updates

Update a specific node:

./ctl.sh ota update 192.168.1.100

Update all nodes in the cluster:

./ctl.sh ota all

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:

  1. Attempts to connect to configured WiFi network
  2. If connection fails, creates an access point
  3. 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

  1. Create your service class:

    class SensorService {
    public:
        void readTemperature() {
            // Read sensor logic
            Serial.println("Reading temperature");
        }
    
        void calibrateSensors() {
            // Calibration logic
            Serial.println("Calibrating sensors");
        }
    };
    
  2. 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

  1. Define your task function:

    void myCustomTask() {
        // Your task logic here
        Serial.println("Custom task executed");
    }
    
  2. 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

  1. Discovery Failures: Check UDP port 4210 is not blocked
  2. WiFi Connection: Verify SSID/password in Config.cpp
  3. OTA Updates: Ensure sufficient flash space (1MB minimum)
  4. Cluster Split: Check network connectivity between nodes

Debug Output

Enable serial monitoring to see cluster activity:

pio device monitor

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Test thoroughly on ESP8266 hardware
  5. Submit a pull request

License

[Add your license information here]

Acknowledgments

Description
SProcket ORchestration Engine
Readme 1.2 MiB
Languages
C++ 53.5%
JavaScript 35.5%
Shell 8.9%
HTML 1.6%
C 0.5%