refactor: reorganize project structure with modern C++ namespace organization

- Restructure include/ and src/ directories with logical grouping
- Move core components to spore/core/ (NodeContext, NetworkManager, TaskManager, ClusterManager, ApiServer)
- Move services to spore/services/ (NodeService, NetworkService, ClusterService, TaskService)
- Move types to spore/types/ (NodeInfo, ApiTypes, Config)
- Move internal components to spore/internal/ (Globals)
- Update all #include statements to use new namespace paths
- Update platformio.ini build filters for all environments
- Update all example files to use new include paths
- Maintain backward compatibility for public API
- Improve code organization, maintainability, and scalability

This reorganization follows modern C++ project structure patterns and provides
clear separation between public API, internal implementation, and utilities.
All examples compile successfully with the new structure.
This commit is contained in:
2025-09-13 21:30:07 +02:00
parent bf17684dc6
commit 554c6ff38d
38 changed files with 78 additions and 72 deletions

9
include/spore/Service.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include "spore/core/ApiServer.h"
class Service {
public:
virtual ~Service() = default;
virtual void registerEndpoints(ApiServer& api) = 0;
virtual const char* getName() const = 0;
};

51
include/spore/Spore.h Normal file
View File

@@ -0,0 +1,51 @@
#pragma once
#include <vector>
#include <memory>
#include <initializer_list>
#include <utility>
#include "core/NodeContext.h"
#include "core/NetworkManager.h"
#include "core/ClusterManager.h"
#include "core/ApiServer.h"
#include "core/TaskManager.h"
#include "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

@@ -0,0 +1,48 @@
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <Updater.h>
#include <functional>
#include <vector>
#include <tuple>
#include "spore/core/NodeContext.h"
#include "spore/types/NodeInfo.h"
#include "spore/core/TaskManager.h"
#include "spore/types/ApiTypes.h"
class Service; // Forward declaration
class ApiServer {
public:
ApiServer(NodeContext& ctx, TaskManager& taskMgr, uint16_t port = 80);
void begin();
void addService(Service& service);
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);
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
const std::vector<ParamSpec>& params);
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,
const std::vector<ParamSpec>& params);
static const char* methodToStr(int method);
// Access to endpoints for endpoints endpoint
const std::vector<EndpointInfo>& getEndpoints() const { return endpoints; }
private:
AsyncWebServer server;
NodeContext& ctx;
TaskManager& taskManager;
std::vector<std::reference_wrapper<Service>> services;
std::vector<EndpointInfo> endpoints; // Single source of truth for endpoints
// Internal helpers
void registerEndpoint(const String& uri, int method,
const std::vector<ParamSpec>& params,
const String& serviceName);
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include "spore/core/NodeContext.h"
#include "spore/types/NodeInfo.h"
#include "spore/core/TaskManager.h"
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include <map>
class ClusterManager {
public:
ClusterManager(NodeContext& ctx, TaskManager& taskMgr);
void registerTasks();
void sendDiscovery();
void listenForDiscovery();
void addOrUpdateNode(const String& nodeHost, IPAddress nodeIP);
void updateAllNodeStatuses();
void removeDeadNodes();
void printMemberList();
const std::map<String, NodeInfo>& getMemberList() const { return *ctx.memberList; }
void fetchNodeInfo(const IPAddress& ip);
void updateLocalNodeResources();
void heartbeatTaskCallback();
void updateAllMembersInfoTaskCallback();
private:
NodeContext& ctx;
TaskManager& taskManager;
};

View File

@@ -0,0 +1,49 @@
#pragma once
#include "spore/core/NodeContext.h"
#include <ESP8266WiFi.h>
#include <vector>
struct AccessPoint {
String ssid;
int32_t rssi;
uint8_t encryptionType;
uint8_t* bssid;
int32_t channel;
bool isHidden;
};
class NetworkManager {
public:
NetworkManager(NodeContext& ctx);
void setupWiFi();
void setHostnameFromMac();
// WiFi scanning methods
void scanWifi();
void processAccessPoints();
std::vector<AccessPoint> getAccessPoints() const;
// WiFi configuration methods
void setWiFiConfig(const String& ssid, const String& password,
uint32_t connect_timeout_ms = 10000,
uint32_t retry_delay_ms = 500);
// Network status methods
bool isConnected() const { return WiFi.isConnected(); }
String getSSID() const { return WiFi.SSID(); }
IPAddress getLocalIP() const { return WiFi.localIP(); }
String getMacAddress() const { return WiFi.macAddress(); }
String getHostname() const { return WiFi.hostname(); }
int32_t getRSSI() const { return WiFi.RSSI(); }
WiFiMode_t getMode() const { return WiFi.getMode(); }
// AP mode specific methods
IPAddress getAPIP() const { return WiFi.softAPIP(); }
String getAPMacAddress() const { return WiFi.softAPmacAddress(); }
uint8_t getConnectedStations() const { return WiFi.softAPgetStationNum(); }
private:
NodeContext& ctx;
std::vector<AccessPoint> accessPoints;
bool isScanning = false;
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include <WiFiUdp.h>
#include <map>
#include "spore/types/NodeInfo.h"
#include <functional>
#include <string>
#include <initializer_list>
#include "spore/types/Config.h"
#include "spore/types/ApiTypes.h"
class NodeContext {
public:
NodeContext();
NodeContext(std::initializer_list<std::pair<String, String>> initialLabels);
~NodeContext();
WiFiUDP* udp;
String hostname;
IPAddress localIP;
NodeInfo self;
std::map<String, NodeInfo>* memberList;
Config config;
using EventCallback = std::function<void(void*)>;
std::map<std::string, std::vector<EventCallback>> eventRegistry;
void on(const std::string& event, EventCallback cb);
void fire(const std::string& event, void* data);
};

View File

@@ -0,0 +1,63 @@
#pragma once
#include <vector>
#include <functional>
#include <string>
#include <map>
#include "spore/core/NodeContext.h"
#include <ArduinoJson.h>
// Define our own callback type to avoid conflict with TaskScheduler
using TaskFunction = std::function<void()>;
struct TaskDefinition {
std::string name;
unsigned long interval;
TaskFunction callback;
bool enabled;
bool autoStart;
TaskDefinition(const std::string& n, unsigned long intv, TaskFunction cb, bool en = true, bool autoS = true)
: name(n), interval(intv), callback(cb), enabled(en), autoStart(autoS) {}
};
class TaskManager {
public:
TaskManager(NodeContext& ctx);
~TaskManager();
// Task registration methods
void registerTask(const std::string& name, unsigned long interval, TaskFunction callback, bool enabled = true, bool autoStart = true);
void registerTask(const TaskDefinition& taskDef);
// Task control methods
void enableTask(const std::string& name);
void disableTask(const std::string& name);
void setTaskInterval(const std::string& name, unsigned long interval);
void startTask(const std::string& name);
void stopTask(const std::string& name);
// Task status methods
bool isTaskEnabled(const std::string& name) const;
bool isTaskRunning(const std::string& name) const;
unsigned long getTaskInterval(const std::string& name) const;
// Get comprehensive task status information
std::vector<std::pair<std::string, JsonObject>> getAllTaskStatuses(JsonDocument& doc) const;
// Management methods
void initialize();
void enableAllTasks();
void disableAllTasks();
void printTaskStatus() const;
// Task execution
void execute();
private:
NodeContext& ctx;
std::vector<TaskDefinition> taskDefinitions;
std::vector<unsigned long> lastExecutionTimes;
int findTaskIndex(const std::string& name) const;
};

View File

@@ -0,0 +1,27 @@
#pragma once
#include <cstddef>
#include <cstdint>
// Cluster protocol and API constants
namespace ClusterProtocol {
constexpr const char* DISCOVERY_MSG = "CLUSTER_DISCOVERY";
constexpr const char* RESPONSE_MSG = "CLUSTER_RESPONSE";
constexpr uint16_t UDP_PORT = 4210;
constexpr size_t UDP_BUF_SIZE = 64;
constexpr const char* API_NODE_STATUS = "/api/node/status";
}
namespace TaskIntervals {
constexpr unsigned long SEND_DISCOVERY = 1000;
constexpr unsigned long LISTEN_FOR_DISCOVERY = 100;
constexpr unsigned long UPDATE_STATUS = 1000;
constexpr unsigned long PRINT_MEMBER_LIST = 5000;
constexpr unsigned long HEARTBEAT = 2000;
constexpr unsigned long UPDATE_ALL_MEMBERS_INFO = 10000;
}
constexpr unsigned long NODE_ACTIVE_THRESHOLD = 10000;
constexpr unsigned long NODE_INACTIVE_THRESHOLD = 60000;
constexpr unsigned long NODE_DEAD_THRESHOLD = 120000;

View File

@@ -0,0 +1,16 @@
#pragma once
#include "spore/Service.h"
#include "spore/core/NodeContext.h"
#include <ArduinoJson.h>
class ClusterService : public Service {
public:
ClusterService(NodeContext& ctx);
void registerEndpoints(ApiServer& api) override;
const char* getName() const override { return "Cluster"; }
private:
NodeContext& ctx;
void handleMembersRequest(AsyncWebServerRequest* request);
};

View File

@@ -0,0 +1,22 @@
#pragma once
#include "spore/Service.h"
#include "spore/core/NetworkManager.h"
#include "spore/core/NodeContext.h"
class NetworkService : public Service {
public:
NetworkService(NetworkManager& networkManager);
void registerEndpoints(ApiServer& api) override;
const char* getName() const override { return "Network"; }
private:
NetworkManager& networkManager;
// WiFi scanning endpoints
void handleWifiScanRequest(AsyncWebServerRequest* request);
void handleGetWifiNetworks(AsyncWebServerRequest* request);
// Network status endpoints
void handleNetworkStatus(AsyncWebServerRequest* request);
void handleSetWifiConfig(AsyncWebServerRequest* request);
};

View File

@@ -0,0 +1,22 @@
#pragma once
#include "spore/Service.h"
#include "spore/core/NodeContext.h"
#include <ArduinoJson.h>
#include <Updater.h>
class NodeService : public Service {
public:
NodeService(NodeContext& ctx, ApiServer& apiServer);
void registerEndpoints(ApiServer& api) override;
const char* getName() const override { return "Node"; }
private:
NodeContext& ctx;
ApiServer& apiServer;
void handleStatusRequest(AsyncWebServerRequest* request);
void handleUpdateRequest(AsyncWebServerRequest* request);
void handleUpdateUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final);
void handleRestartRequest(AsyncWebServerRequest* request);
void handleEndpointsRequest(AsyncWebServerRequest* request);
};

View File

@@ -0,0 +1,17 @@
#pragma once
#include "spore/Service.h"
#include "spore/core/TaskManager.h"
#include <ArduinoJson.h>
class TaskService : public Service {
public:
TaskService(TaskManager& taskManager);
void registerEndpoints(ApiServer& api) override;
const char* getName() const override { return "Task"; }
private:
TaskManager& taskManager;
void handleStatusRequest(AsyncWebServerRequest* request);
void handleControlRequest(AsyncWebServerRequest* request);
};

View File

@@ -0,0 +1,37 @@
#pragma once
#include <Arduino.h>
#include <vector>
struct ParamSpec {
String name;
bool required;
String location; // "query" | "body" | "path" | "header"
String type; // e.g. "string", "number", "boolean"
std::vector<String> values; // optional allowed values
String defaultValue; // optional default value (stringified)
};
struct EndpointCapability {
String uri;
int method;
std::vector<ParamSpec> params;
};
struct EndpointInfo {
String uri;
int method;
std::vector<ParamSpec> params;
String serviceName; // Name of the service that registered this endpoint
bool isLocal; // Whether this endpoint is on the local node
// Constructor for individual parameters
EndpointInfo(const String& u, int m, const std::vector<ParamSpec>& p, const String& service, bool local)
: uri(u), method(m), params(p), serviceName(service), isLocal(local) {}
// Constructor for easy conversion from EndpointCapability
EndpointInfo(const EndpointCapability& cap, const String& service = "", bool local = true)
: uri(cap.uri), method(cap.method), params(cap.params), serviceName(service), isLocal(local) {}
// Default constructor
EndpointInfo() : isLocal(true) {}
};

View File

@@ -0,0 +1,37 @@
#pragma once
#include <Arduino.h>
class Config {
public:
// WiFi Configuration
String wifi_ssid;
String wifi_password;
// Network Configuration
uint16_t udp_port;
uint16_t api_server_port;
// Cluster Configuration
unsigned long discovery_interval_ms;
unsigned long heartbeat_interval_ms;
unsigned long status_update_interval_ms;
unsigned long member_info_update_interval_ms;
unsigned long print_interval_ms;
// Node Status Thresholds
unsigned long node_active_threshold_ms;
unsigned long node_inactive_threshold_ms;
unsigned long node_dead_threshold_ms;
// WiFi Connection
unsigned long wifi_connect_timeout_ms;
unsigned long wifi_retry_delay_ms;
// System Configuration
unsigned long restart_delay_ms;
uint16_t json_doc_size;
// Constructor
Config();
};

View File

@@ -0,0 +1,27 @@
#pragma once
#include <IPAddress.h>
#include <vector>
#include <tuple>
#include <map>
#include "ApiTypes.h"
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<EndpointInfo> endpoints; // List of registered endpoints
std::map<String, String> labels; // Arbitrary node labels (key -> value)
};
const char* statusToStr(NodeInfo::Status status);
void updateNodeStatus(NodeInfo &node, unsigned long now, unsigned long inactive_threshold, unsigned long dead_threshold);
void updateNodeStatus(NodeInfo &node, unsigned long now = millis()); // Legacy overload for backward compatibility