diff --git a/examples/neopixel/main.cpp b/examples/neopixel/main.cpp new file mode 100644 index 0000000..ae5cd34 --- /dev/null +++ b/examples/neopixel/main.cpp @@ -0,0 +1,363 @@ +#include +#include +#include +#include +#include + +#include "Globals.h" +#include "NodeContext.h" +#include "NetworkManager.h" +#include "ClusterManager.h" +#include "ApiServer.h" +#include "TaskManager.h" + +#ifndef NEOPIXEL_PIN +#define NEOPIXEL_PIN 2 +#endif + +#ifndef NEOPIXEL_COUNT +#define NEOPIXEL_COUNT 16 +#endif + +#ifndef NEOPIXEL_TYPE +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) +#endif + +// Wheel helper: map 0-255 to RGB rainbow +static uint32_t colorWheel(Adafruit_NeoPixel &strip, uint8_t pos) { + pos = 255 - pos; + if (pos < 85) { + return strip.Color(255 - pos * 3, 0, pos * 3); + } + if (pos < 170) { + pos -= 85; + return strip.Color(0, pos * 3, 255 - pos * 3); + } + pos -= 170; + return strip.Color(pos * 3, 255 - pos * 3, 0); +} + +class NeoPixelService { +public: + enum class Pattern { + Off, + ColorWipe, + Rainbow, + RainbowCycle, + TheaterChase, + TheaterChaseRainbow + }; + + NeoPixelService(NodeContext &ctx, TaskManager &taskMgr, + uint16_t numPixels, + uint8_t pin, + neoPixelType type) + : ctx(ctx), taskManager(taskMgr), + strip(numPixels, pin, type), + currentPattern(Pattern::Off), + updateIntervalMs(20), + lastUpdateMs(0), + wipeIndex(0), + wipeColor(strip.Color(255, 0, 0)), + rainbowJ(0), + cycleJ(0), + chaseJ(0), + chaseQ(0), + chasePhaseOn(true), + chaseColor(strip.Color(127, 127, 127)), + brightness(50) { + strip.begin(); + strip.setBrightness(brightness); + strip.show(); + + registerPatterns(); + registerTasks(); + } + + void registerApi(ApiServer &api) { + api.addEndpoint("/api/neopixel/status", HTTP_GET, [this](AsyncWebServerRequest *request) { + JsonDocument doc; + doc["pin"] = NEOPIXEL_PIN; + doc["count"] = strip.numPixels(); + doc["interval_ms"] = updateIntervalMs; + doc["brightness"] = brightness; + doc["pattern"] = currentPatternName(); + String json; + serializeJson(doc, json); + request->send(200, "application/json", json); + }); + + api.addEndpoint("/api/neopixel/patterns", HTTP_GET, [this](AsyncWebServerRequest *request) { + JsonDocument doc; + JsonArray arr = doc.to(); + for (auto &kv : patternUpdaters) arr.add(kv.first); + String json; + serializeJson(doc, json); + request->send(200, "application/json", json); + }); + + api.addEndpoint("/api/neopixel", HTTP_POST, + [this](AsyncWebServerRequest *request) { + String pattern = request->hasParam("pattern", true) ? request->getParam("pattern", true)->value() : ""; + if (pattern.length()) { + setPatternByName(pattern); + } + + if (request->hasParam("interval_ms", true)) { + updateIntervalMs = request->getParam("interval_ms", true)->value().toInt(); + if (updateIntervalMs < 1) updateIntervalMs = 1; + taskManager.setTaskInterval("neopixel_update", updateIntervalMs); + } + + if (request->hasParam("brightness", true)) { + int b = request->getParam("brightness", true)->value().toInt(); + if (b < 0) b = 0; if (b > 255) b = 255; + setBrightness((uint8_t)b); + } + + // Optional RGB for color_wipe and theater_chase + if (request->hasParam("r", true) || request->hasParam("g", true) || request->hasParam("b", true)) { + int r = request->hasParam("r", true) ? request->getParam("r", true)->value().toInt() : 0; + int g = request->hasParam("g", true) ? request->getParam("g", true)->value().toInt() : 0; + int b = request->hasParam("b", true) ? request->getParam("b", true)->value().toInt() : 0; + wipeColor = strip.Color(r, g, b); + chaseColor = strip.Color(r / 2, g / 2, b / 2); // dimmer for chase + } + + JsonDocument resp; + resp["ok"] = true; + resp["pattern"] = currentPatternName(); + resp["interval_ms"] = updateIntervalMs; + resp["brightness"] = brightness; + String json; + serializeJson(resp, json); + request->send(200, "application/json", json); + }, + std::vector{ + ApiServer::ParamSpec{ String("pattern"), false, String("body"), String("string"), patternNamesVector() }, + ApiServer::ParamSpec{ String("interval_ms"), false, String("body"), String("number"), {} }, + ApiServer::ParamSpec{ String("brightness"), false, String("body"), String("number"), {} }, + ApiServer::ParamSpec{ String("r"), false, String("body"), String("number"), {} }, + ApiServer::ParamSpec{ String("g"), false, String("body"), String("number"), {} }, + ApiServer::ParamSpec{ String("b"), false, String("body"), String("number"), {} }, + } + ); + } + + void setPatternByName(const String &name) { + auto it = patternUpdaters.find(name); + if (it != patternUpdaters.end()) { + // Map name to enum for status and reset state + currentPattern = nameToPattern(name); + resetStateForPattern(currentPattern); + } + } + + void setBrightness(uint8_t b) { + brightness = b; + strip.setBrightness(brightness); + strip.show(); + } + +private: + void registerTasks() { + taskManager.registerTask("neopixel_update", updateIntervalMs, [this]() { update(); }); + taskManager.registerTask("neopixel_status_print", 10000, [this]() { + Serial.printf("[NeoPixel] pattern=%s interval=%lu ms brightness=%u\n", + currentPatternName().c_str(), updateIntervalMs, brightness); + }); + } + + void registerPatterns() { + patternUpdaters["off"] = [this]() { updateOff(); }; + patternUpdaters["color_wipe"] = [this]() { updateColorWipe(); }; + patternUpdaters["rainbow"] = [this]() { updateRainbow(); }; + patternUpdaters["rainbow_cycle"] = [this]() { updateRainbowCycle(); }; + patternUpdaters["theater_chase"] = [this]() { updateTheaterChase(); }; + patternUpdaters["theater_chase_rainbow"] = [this]() { updateTheaterChaseRainbow(); }; + } + + std::vector patternNamesVector() const { + std::vector v; + v.reserve(patternUpdaters.size()); + for (const auto &kv : patternUpdaters) v.push_back(kv.first); + return v; + } + + String currentPatternName() const { + switch (currentPattern) { + case Pattern::Off: return String("off"); + case Pattern::ColorWipe: return String("color_wipe"); + case Pattern::Rainbow: return String("rainbow"); + case Pattern::RainbowCycle: return String("rainbow_cycle"); + case Pattern::TheaterChase: return String("theater_chase"); + case Pattern::TheaterChaseRainbow: return String("theater_chase_rainbow"); + } + return String("off"); + } + + Pattern nameToPattern(const String &name) const { + if (name.equalsIgnoreCase("color_wipe")) return Pattern::ColorWipe; + if (name.equalsIgnoreCase("rainbow")) return Pattern::Rainbow; + if (name.equalsIgnoreCase("rainbow_cycle")) return Pattern::RainbowCycle; + if (name.equalsIgnoreCase("theater_chase")) return Pattern::TheaterChase; + if (name.equalsIgnoreCase("theater_chase_rainbow")) return Pattern::TheaterChaseRainbow; + return Pattern::Off; + } + + void resetStateForPattern(Pattern p) { + // Clear strip by default when changing pattern + strip.clear(); + strip.show(); + + // Reset indexes/state variables + wipeIndex = 0; + rainbowJ = 0; + cycleJ = 0; + chaseJ = 0; + chaseQ = 0; + chasePhaseOn = true; + lastUpdateMs = 0; // force immediate update on next tick + + currentPattern = p; + } + + void update() { + unsigned long now = millis(); + if (now - lastUpdateMs < updateIntervalMs) return; + lastUpdateMs = now; + + const String name = currentPatternName(); + auto it = patternUpdaters.find(name); + if (it != patternUpdaters.end()) { + it->second(); + } else { + updateOff(); + } + } + + // Pattern updaters (non-blocking; advance a small step each call) + void updateOff() { + // Ensure off state + strip.clear(); + strip.show(); + } + + void updateColorWipe() { + if (wipeIndex < strip.numPixels()) { + strip.setPixelColor(wipeIndex, wipeColor); + ++wipeIndex; + strip.show(); + } else { + // Restart + strip.clear(); + wipeIndex = 0; + } + } + + void updateRainbow() { + for (uint16_t i = 0; i < strip.numPixels(); i++) { + strip.setPixelColor(i, colorWheel(strip, (i + rainbowJ) & 255)); + } + strip.show(); + rainbowJ = (rainbowJ + 1) & 0xFF; // 0..255 + } + + void updateRainbowCycle() { + for (uint16_t i = 0; i < strip.numPixels(); i++) { + uint8_t pos = ((i * 256 / strip.numPixels()) + cycleJ) & 0xFF; + strip.setPixelColor(i, colorWheel(strip, pos)); + } + strip.show(); + cycleJ = (cycleJ + 1) & 0xFF; + } + + void updateTheaterChase() { + // Phase toggles on/off for the current q offset + if (chasePhaseOn) { + for (uint16_t i = 0; i < strip.numPixels(); i += 3) { + uint16_t idx = i + chaseQ; + if (idx < strip.numPixels()) strip.setPixelColor(idx, chaseColor); + } + strip.show(); + chasePhaseOn = false; + } else { + for (uint16_t i = 0; i < strip.numPixels(); i += 3) { + uint16_t idx = i + chaseQ; + if (idx < strip.numPixels()) strip.setPixelColor(idx, 0); + } + strip.show(); + chasePhaseOn = true; + chaseQ = (chaseQ + 1) % 3; // move the crawl + chaseJ = (chaseJ + 1) % 10; // cycle count kept for status if needed + } + } + + void updateTheaterChaseRainbow() { + if (chasePhaseOn) { + for (uint16_t i = 0; i < strip.numPixels(); i += 3) { + uint16_t idx = i + chaseQ; + if (idx < strip.numPixels()) strip.setPixelColor(idx, colorWheel(strip, (idx + chaseJ) % 255)); + } + strip.show(); + chasePhaseOn = false; + } else { + for (uint16_t i = 0; i < strip.numPixels(); i += 3) { + uint16_t idx = i + chaseQ; + if (idx < strip.numPixels()) strip.setPixelColor(idx, 0); + } + strip.show(); + chasePhaseOn = true; + chaseQ = (chaseQ + 1) % 3; + chaseJ = (chaseJ + 1) & 0xFF; + } + } + +private: + NodeContext &ctx; + TaskManager &taskManager; + Adafruit_NeoPixel strip; + + std::map> patternUpdaters; + + Pattern currentPattern; + unsigned long updateIntervalMs; + unsigned long lastUpdateMs; + + // State for patterns + uint16_t wipeIndex; + uint32_t wipeColor; + + uint8_t rainbowJ; + uint8_t cycleJ; + + int chaseJ; + int chaseQ; + bool chasePhaseOn; + uint32_t chaseColor; + + uint8_t brightness; +}; + +NodeContext ctx; +NetworkManager network(ctx); +TaskManager taskManager(ctx); +ClusterManager cluster(ctx, taskManager); +ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port); +NeoPixelService neoService(ctx, taskManager, NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_TYPE); + +void setup() { + network.setupWiFi(); + + taskManager.initialize(); + + apiServer.begin(); + neoService.registerApi(apiServer); + + taskManager.printTaskStatus(); +} + +void loop() { + taskManager.execute(); + yield(); +} diff --git a/examples/relay/main.cpp b/examples/relay/main.cpp index 25fa5d9..0f406a0 100644 --- a/examples/relay/main.cpp +++ b/examples/relay/main.cpp @@ -36,7 +36,7 @@ public: request->send(200, "application/json", json); }); - api.addEndpoint("/api/relay/set", HTTP_POST, [this](AsyncWebServerRequest* request) { + api.addEndpoint("/api/relay", HTTP_POST, [this](AsyncWebServerRequest* request) { String state = request->hasParam("state", true) ? request->getParam("state", true)->value() : ""; bool ok = false; if (state.equalsIgnoreCase("on")) { diff --git a/examples/tasks/task_management_example.cpp b/examples/tasks/task_management_example.cpp deleted file mode 100644 index 4c6cd60..0000000 --- a/examples/tasks/task_management_example.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Task Management Example with std::bind and Class-Based Registration - * - * This example demonstrates how to use the TaskManager with std::bind - * and how classes can register their own tasks. - */ - -#include -#include -#include "TaskManager.h" -#include "NodeContext.h" - -// Example service class that registers its own tasks -class SensorService { -public: - SensorService(NodeContext& ctx, TaskManager& taskMgr) : ctx(ctx), taskManager(taskMgr) { - // Register all sensor-related tasks in constructor - registerTasks(); - } - - void readTemperature() { - Serial.println("[SensorService] Reading temperature"); - // Simulate temperature reading - temperature = random(20, 30); - Serial.printf("[SensorService] Temperature: %d°C\n", temperature); - } - - void readHumidity() { - Serial.println("[SensorService] Reading humidity"); - // Simulate humidity reading - humidity = random(40, 80); - Serial.printf("[SensorService] Humidity: %d%%\n", humidity); - } - - void calibrateSensors() { - Serial.println("[SensorService] Calibrating sensors"); - // Simulate calibration - calibrationOffset = random(-2, 3); - Serial.printf("[SensorService] Calibration offset: %d\n", calibrationOffset); - } - - void printStatus() { - Serial.printf("[SensorService] Status - Temp: %d°C, Humidity: %d%%, Offset: %d\n", - temperature, humidity, calibrationOffset); - } - -private: - void registerTasks() { - // Register all sensor tasks using std::bind - taskManager.registerTask("temp_read", 2000, - std::bind(&SensorService::readTemperature, this)); - taskManager.registerTask("humidity_read", 3000, - std::bind(&SensorService::readHumidity, this)); - taskManager.registerTask("calibrate", 10000, - std::bind(&SensorService::calibrateSensors, this)); - taskManager.registerTask("status_print", 5000, - std::bind(&SensorService::printStatus, this)); - - Serial.println("[SensorService] Registered all sensor tasks"); - } - - NodeContext& ctx; - TaskManager& taskManager; - int temperature = 25; - int humidity = 60; - int calibrationOffset = 0; -}; - -// Example network service class -class NetworkService { -public: - NetworkService(NodeContext& ctx, TaskManager& taskMgr) : ctx(ctx), taskManager(taskMgr) { - registerTasks(); - } - - void checkConnection() { - Serial.println("[NetworkService] Checking WiFi connection"); - if (WiFi.status() == WL_CONNECTED) { - Serial.printf("[NetworkService] Connected to %s\n", WiFi.SSID().c_str()); - } else { - Serial.println("[NetworkService] WiFi disconnected"); - } - } - - void sendHeartbeat() { - Serial.println("[NetworkService] Sending heartbeat"); - // Simulate heartbeat - heartbeatCount++; - Serial.printf("[NetworkService] Heartbeat #%d sent\n", heartbeatCount); - } - -private: - void registerTasks() { - taskManager.registerTask("connection_check", 5000, - std::bind(&NetworkService::checkConnection, this)); - taskManager.registerTask("heartbeat", 8000, - std::bind(&NetworkService::sendHeartbeat, this)); - - Serial.println("[NetworkService] Registered all network tasks"); - } - - NodeContext& ctx; - TaskManager& taskManager; - int heartbeatCount = 0; -}; - -// Example custom task functions (legacy style - still supported) -void customTask1() { - Serial.println("[CustomTask1] Executing custom task 1"); -} - -void customTask2() { - Serial.println("[CustomTask2] Executing custom task 2"); -} - -void periodicMaintenance() { - Serial.println("[Maintenance] Running periodic maintenance"); -} - -void setup() { - Serial.begin(115200); - Serial.println("Task Management Example with Class-Based Registration"); - - // Create context and task manager - NodeContext ctx; - TaskManager taskManager(ctx); - - // Create service instances - they will register their own tasks - SensorService sensors(ctx, taskManager); - NetworkService network(ctx, taskManager); - - // Register additional custom tasks (legacy style) - taskManager.registerTask("custom_task_1", 12000, customTask1); - taskManager.registerTask("custom_task_2", 15000, customTask2); - taskManager.registerTask("maintenance", 30000, periodicMaintenance); - - // Register a lambda function directly - taskManager.registerTask("lambda_function", 6000, []() { - Serial.println("[Lambda] Lambda function called"); - }); - - // Initialize and start all tasks - taskManager.initialize(); - - // Print initial task status - taskManager.printTaskStatus(); - - Serial.println("\n=== Task Registration Examples ==="); - Serial.println("1. SensorService: Registers its own tasks in constructor"); - Serial.println("2. NetworkService: Registers its own tasks in constructor"); - Serial.println("3. Custom tasks: Legacy function registration"); - Serial.println("4. Lambda function: Direct lambda registration"); - Serial.println("=====================================\n"); -} - -void loop() { - // The TaskManager handles all task execution - // No need to call individual task functions - yield(); -} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index c764471..14449c3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -61,3 +61,34 @@ build_src_filter = + + + + +[env:esp01_1m_neopixel] +platform = platformio/espressif8266@^4.2.1 +board = esp01_1m +framework = arduino +upload_speed = 115200 +monitor_speed = 115200 +board_build.partitions = partitions_ota_1M.csv +board_build.flash_mode = dout +board_build.flash_size = 1M +lib_deps = ${common.lib_deps} + adafruit/Adafruit NeoPixel@^1.15.1 +build_src_filter = + + + + + + + +[env:d1_mini_neopixel] +platform = platformio/espressif8266@^4.2.1 +board = d1_mini +framework = arduino +upload_speed = 115200 +monitor_speed = 115200 +board_build.flash_mode = dio +board_build.flash_size = 4M +lib_deps = ${common.lib_deps} + adafruit/Adafruit NeoPixel@^1.15.1 +build_src_filter = + + + + + +