diff --git a/examples/neopattern/NeoPattern.cpp b/examples/neopattern/NeoPattern.h similarity index 97% rename from examples/neopattern/NeoPattern.cpp rename to examples/neopattern/NeoPattern.h index fc090b2..1f27ad2 100644 --- a/examples/neopattern/NeoPattern.cpp +++ b/examples/neopattern/NeoPattern.h @@ -2,10 +2,6 @@ * Original NeoPattern code by Bill Earl * https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview * - * TODO - * - cleanup the mess - * - fnc table for patterns to replace switch case - * * Custom modifications by 0x1d: * - default OnComplete callback that sets pattern to reverse * - separate animation update from timer; Update now updates directly, UpdateScheduled uses timer @@ -28,7 +24,8 @@ enum pattern FADE = 5, FIRE = 6 }; -// Patern directions supported: + +// Pattern directions supported: enum direction { FORWARD, @@ -52,8 +49,8 @@ class NeoPattern : public Adafruit_NeoPixel uint16_t Index; // current step within the pattern uint16_t completed = 0; - // FIXME return current NeoPatternState - void (*OnComplete)(int); // Callback on completion of pattern + // Callback on completion of pattern + void (*OnComplete)(int); uint8_t *frameBuffer; int bufferSize = 0; @@ -76,6 +73,14 @@ class NeoPattern : public Adafruit_NeoPixel begin(); } + ~NeoPattern() { + if (frameBuffer) { + free(frameBuffer); + } + } + + + // Implementation starts here (inline) void handleStream(uint8_t *data, size_t len) { //const uint16_t *data16 = (uint16_t *)data; @@ -129,6 +134,9 @@ class NeoPattern : public Adafruit_NeoPixel case FIRE: Fire(50, 120); break; + case NONE: + // For NONE pattern, just maintain current state + break; default: if (bufferSize > 0) { @@ -461,10 +469,11 @@ class NeoPattern : public Adafruit_NeoPixel { setPixelColor(Pixel, Color(red, green, blue)); } + void showStrip() { show(); } }; -#endif \ No newline at end of file +#endif diff --git a/examples/neopattern/NeoPatternService.cpp b/examples/neopattern/NeoPatternService.cpp index 585ccc8..bcc1f32 100644 --- a/examples/neopattern/NeoPatternService.cpp +++ b/examples/neopattern/NeoPatternService.cpp @@ -1,56 +1,90 @@ #include "NeoPatternService.h" #include "spore/core/ApiServer.h" +#include "spore/util/Logging.h" +#include -NeoPatternService::NeoPatternService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, uint8_t type) +NeoPatternService::NeoPatternService(TaskManager& taskMgr, const NeoPixelConfig& config) : taskManager(taskMgr), - pixels(numPixels, pin, type), - updateIntervalMs(100), - brightness(48) { - pixels.setBrightness(brightness); - pixels.show(); - + config(config), + activePattern(NeoPatternType::RAINBOW_CYCLE), + direction(NeoDirection::FORWARD), + updateIntervalMs(config.updateInterval), + lastUpdateMs(0), + initialized(false) { + + // Initialize NeoPattern + neoPattern = new NeoPattern(config.length, config.pin, NEO_GRB + NEO_KHZ800); + neoPattern->setBrightness(config.brightness); + + // Initialize state + currentState.pattern = static_cast(activePattern); + currentState.color = 0xFF0000; // Red + currentState.color2 = 0x0000FF; // Blue + currentState.totalSteps = 16; + currentState.brightness = config.brightness; + + // Set initial pattern + neoPattern->Color1 = currentState.color; + neoPattern->Color2 = currentState.color2; + neoPattern->TotalSteps = currentState.totalSteps; + neoPattern->ActivePattern = static_cast<::pattern>(activePattern); + neoPattern->Direction = static_cast<::direction>(direction); + + registerPatterns(); registerTasks(); - setPatternByName("rainbow_cycle"); + initialized = true; + + LOG_INFO("NeoPattern", "Service initialized"); +} + +NeoPatternService::~NeoPatternService() { + if (neoPattern) { + delete neoPattern; + } } void NeoPatternService::registerEndpoints(ApiServer& api) { + // Status endpoint api.addEndpoint("/api/neopattern/status", HTTP_GET, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, std::vector{}); + // Patterns list endpoint api.addEndpoint("/api/neopattern/patterns", HTTP_GET, [this](AsyncWebServerRequest* request) { handlePatternsRequest(request); }, std::vector{}); + // Control endpoint api.addEndpoint("/api/neopattern", HTTP_POST, [this](AsyncWebServerRequest* request) { handleControlRequest(request); }, std::vector{ ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()}, - ParamSpec{String("interval_ms"), false, String("body"), String("number"), {}, String("100")}, - ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("50")}, - ParamSpec{String("color"), false, String("body"), String("color"), {}}, - ParamSpec{String("color2"), false, String("body"), String("color"), {}}, - ParamSpec{String("r"), false, String("body"), String("number"), {}}, - ParamSpec{String("g"), false, String("body"), String("number"), {}}, - ParamSpec{String("b"), false, String("body"), String("number"), {}}, - ParamSpec{String("r2"), false, String("body"), String("number"), {}}, - ParamSpec{String("g2"), false, String("body"), String("number"), {}}, - ParamSpec{String("b2"), false, String("body"), String("number"), {}}, - ParamSpec{String("total_steps"), false, String("body"), String("number"), {}}, - ParamSpec{String("direction"), false, String("body"), String("string"), {String("forward"), String("reverse")}} + ParamSpec{String("color"), false, String("body"), String("string"), {}}, + ParamSpec{String("color2"), false, String("body"), String("string"), {}}, + ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("100")}, + ParamSpec{String("total_steps"), false, String("body"), String("number"), {}, String("16")}, + ParamSpec{String("direction"), false, String("body"), String("string"), {String("forward"), String("reverse")}}, + ParamSpec{String("interval"), false, String("body"), String("number"), {}, String("100")} }); + + // State endpoint for complex state updates + api.addEndpoint("/api/neopattern/state", HTTP_POST, + [this](AsyncWebServerRequest* request) { handleStateRequest(request); }, + std::vector{}); } void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) { JsonDocument doc; - doc["pin"] = pixels.getPin(); - doc["count"] = pixels.numPixels(); - doc["interval_ms"] = updateIntervalMs; - doc["brightness"] = brightness; + doc["pin"] = config.pin; + doc["length"] = config.length; + doc["brightness"] = currentState.brightness; doc["pattern"] = currentPatternName(); - doc["total_steps"] = pixels.TotalSteps; - doc["color1"] = pixels.Color1; - doc["color2"] = pixels.Color2; + doc["color"] = String(currentState.color, HEX); + doc["color2"] = String(currentState.color2, HEX); + doc["total_steps"] = currentState.totalSteps; + doc["direction"] = (direction == NeoDirection::FORWARD) ? "forward" : "reverse"; + doc["interval"] = updateIntervalMs; + doc["active"] = initialized; String json; serializeJson(doc, json); @@ -60,7 +94,9 @@ void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) { void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) { JsonDocument doc; JsonArray arr = doc.to(); - for (auto& kv : patternSetters) arr.add(kv.first); + for (const auto& kv : patternUpdaters) { + arr.add(kv.first); + } String json; serializeJson(doc, json); @@ -68,121 +104,265 @@ void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) { } void NeoPatternService::handleControlRequest(AsyncWebServerRequest* request) { + bool updated = false; + if (request->hasParam("pattern", true)) { String name = request->getParam("pattern", true)->value(); setPatternByName(name); + updated = true; } - if (request->hasParam("interval_ms", true)) { - unsigned long v = request->getParam("interval_ms", true)->value().toInt(); - if (v < 1) v = 1; - updateIntervalMs = v; - taskManager.setTaskInterval("neopattern_update", updateIntervalMs); + if (request->hasParam("color", true)) { + String colorStr = request->getParam("color", true)->value(); + uint32_t color = parseColor(colorStr); + setColor(color); + updated = true; + } + + if (request->hasParam("color2", true)) { + String colorStr = request->getParam("color2", true)->value(); + uint32_t color = parseColor(colorStr); + setColor2(color); + updated = true; } 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); - } - - // Accept packed color ints or r,g,b triplets - if (request->hasParam("color", true)) { - pixels.Color1 = (uint32_t)strtoul(request->getParam("color", true)->value().c_str(), nullptr, 0); - } - if (request->hasParam("color2", true)) { - pixels.Color2 = (uint32_t)strtoul(request->getParam("color2", true)->value().c_str(), nullptr, 0); - } - 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; - pixels.Color1 = pixels.Color(r, g, b); - } - if (request->hasParam("r2", true) || request->hasParam("g2", true) || request->hasParam("b2", true)) { - int r = request->hasParam("r2", true) ? request->getParam("r2", true)->value().toInt() : 0; - int g = request->hasParam("g2", true) ? request->getParam("g2", true)->value().toInt() : 0; - int b = request->hasParam("b2", true) ? request->getParam("b2", true)->value().toInt() : 0; - pixels.Color2 = pixels.Color(r, g, b); + setBrightness(static_cast(b)); + updated = true; } if (request->hasParam("total_steps", true)) { - pixels.TotalSteps = request->getParam("total_steps", true)->value().toInt(); + int steps = request->getParam("total_steps", true)->value().toInt(); + if (steps > 0) { + setTotalSteps(static_cast(steps)); + updated = true; + } } if (request->hasParam("direction", true)) { - String dir = request->getParam("direction", true)->value(); - if (dir.equalsIgnoreCase("forward")) pixels.Direction = FORWARD; - else if (dir.equalsIgnoreCase("reverse")) pixels.Direction = REVERSE; + String dirStr = request->getParam("direction", true)->value(); + NeoDirection dir = (dirStr.equalsIgnoreCase("reverse")) ? NeoDirection::REVERSE : NeoDirection::FORWARD; + setDirection(dir); + updated = true; } + if (request->hasParam("interval", true)) { + unsigned long interval = request->getParam("interval", true)->value().toInt(); + if (interval > 0) { + setUpdateInterval(interval); + updated = true; + } + } + + // Return current state JsonDocument resp; resp["ok"] = true; resp["pattern"] = currentPatternName(); - resp["interval_ms"] = updateIntervalMs; - resp["brightness"] = brightness; + resp["color"] = String(currentState.color, HEX); + resp["color2"] = String(currentState.color2, HEX); + resp["brightness"] = currentState.brightness; + resp["total_steps"] = currentState.totalSteps; + resp["direction"] = (direction == NeoDirection::FORWARD) ? "forward" : "reverse"; + resp["interval"] = updateIntervalMs; + resp["updated"] = updated; String json; serializeJson(resp, json); request->send(200, "application/json", json); } -void NeoPatternService::setBrightness(uint8_t b) { - brightness = b; - pixels.setBrightness(brightness); - pixels.show(); +void NeoPatternService::handleStateRequest(AsyncWebServerRequest* request) { + if (request->contentType() != "application/json") { + request->send(400, "application/json", "{\"error\":\"Content-Type must be application/json\"}"); + return; + } + + // Note: AsyncWebServerRequest doesn't have getBody() method + // For now, we'll skip JSON body parsing and use form parameters + request->send(400, "application/json", "{\"error\":\"JSON body parsing not implemented\"}"); + return; + + // JSON body parsing not implemented for AsyncWebServerRequest + // This would need to be implemented differently + request->send(501, "application/json", "{\"error\":\"Not implemented\"}"); +} + +void NeoPatternService::setPattern(NeoPatternType pattern) { + activePattern = pattern; + currentState.pattern = static_cast(pattern); + neoPattern->ActivePattern = static_cast<::pattern>(pattern); + resetStateForPattern(pattern); +} + +void NeoPatternService::setPatternByName(const String& name) { + NeoPatternType pattern = nameToPattern(name); + setPattern(pattern); +} + +void NeoPatternService::setColor(uint32_t color) { + currentState.color = color; + neoPattern->Color1 = color; + if (activePattern == NeoPatternType::NONE) { + neoPattern->ColorSet(color); + } +} + +void NeoPatternService::setColor2(uint32_t color) { + currentState.color2 = color; + neoPattern->Color2 = color; +} + +void NeoPatternService::setBrightness(uint8_t brightness) { + currentState.brightness = brightness; + neoPattern->setBrightness(brightness); + neoPattern->show(); +} + +void NeoPatternService::setTotalSteps(uint16_t steps) { + currentState.totalSteps = steps; + neoPattern->TotalSteps = steps; +} + +void NeoPatternService::setDirection(NeoDirection dir) { + direction = dir; + neoPattern->Direction = static_cast<::direction>(dir); +} + +void NeoPatternService::setUpdateInterval(unsigned long interval) { + updateIntervalMs = interval; + taskManager.setTaskInterval("neopattern_update", interval); +} + +void NeoPatternService::setState(const NeoPatternState& state) { + currentState = state; + + // Apply state to NeoPattern + neoPattern->Index = 0; + neoPattern->Color1 = state.color; + neoPattern->Color2 = state.color2; + neoPattern->TotalSteps = state.totalSteps; + neoPattern->ActivePattern = static_cast<::pattern>(state.pattern); + neoPattern->Direction = FORWARD; + + setBrightness(state.brightness); + setPattern(static_cast(state.pattern)); +} + +NeoPatternState NeoPatternService::getState() const { + return currentState; } void NeoPatternService::registerTasks() { taskManager.registerTask("neopattern_update", updateIntervalMs, [this]() { update(); }); taskManager.registerTask("neopattern_status_print", 10000, [this]() { - Serial.printf("[NeoPattern] pattern=%s interval=%lu ms brightness=%u\n", - currentPatternName().c_str(), updateIntervalMs, brightness); + LOG_INFO("NeoPattern", "Status update"); }); } void NeoPatternService::registerPatterns() { - patternSetters["off"] = [this]() { pixels.ActivePattern = NONE; }; - patternSetters["rainbow_cycle"] = [this]() { pixels.RainbowCycle(updateIntervalMs); }; - patternSetters["theater_chase"] = [this]() { pixels.TheaterChase(pixels.Color1 ? pixels.Color1 : pixels.Color(127,127,127), pixels.Color2, updateIntervalMs); }; - patternSetters["color_wipe"] = [this]() { pixels.ColorWipe(pixels.Color1 ? pixels.Color1 : pixels.Color(255,0,0), updateIntervalMs); }; - patternSetters["scanner"] = [this]() { pixels.Scanner(pixels.Color1 ? pixels.Color1 : pixels.Color(0,0,255), updateIntervalMs); }; - patternSetters["fade"] = [this]() { pixels.Fade(pixels.Color1, pixels.Color2, pixels.TotalSteps ? pixels.TotalSteps : 32, updateIntervalMs); }; - patternSetters["fire"] = [this]() { pixels.ActivePattern = FIRE; pixels.Interval = updateIntervalMs; }; + // Register pattern updaters + patternUpdaters["none"] = [this]() { updateNone(); }; + patternUpdaters["rainbow_cycle"] = [this]() { updateRainbowCycle(); }; + patternUpdaters["theater_chase"] = [this]() { updateTheaterChase(); }; + patternUpdaters["color_wipe"] = [this]() { updateColorWipe(); }; + patternUpdaters["scanner"] = [this]() { updateScanner(); }; + patternUpdaters["fade"] = [this]() { updateFade(); }; + patternUpdaters["fire"] = [this]() { updateFire(); }; + + // Register name to pattern mapping + nameToPatternMap["none"] = NeoPatternType::NONE; + nameToPatternMap["rainbow_cycle"] = NeoPatternType::RAINBOW_CYCLE; + nameToPatternMap["theater_chase"] = NeoPatternType::THEATER_CHASE; + nameToPatternMap["color_wipe"] = NeoPatternType::COLOR_WIPE; + nameToPatternMap["scanner"] = NeoPatternType::SCANNER; + nameToPatternMap["fade"] = NeoPatternType::FADE; + nameToPatternMap["fire"] = NeoPatternType::FIRE; } -std::vector NeoPatternService::patternNamesVector() { - if (patternSetters.empty()) registerPatterns(); - std::vector v; - v.reserve(patternSetters.size()); - for (const auto& kv : patternSetters) v.push_back(kv.first); - return v; -} - -String NeoPatternService::currentPatternName() { - switch (pixels.ActivePattern) { - case NONE: return String("off"); - case RAINBOW_CYCLE: return String("rainbow_cycle"); - case THEATER_CHASE: return String("theater_chase"); - case COLOR_WIPE: return String("color_wipe"); - case SCANNER: return String("scanner"); - case FADE: return String("fade"); - case FIRE: return String("fire"); +std::vector NeoPatternService::patternNamesVector() const { + std::vector names; + names.reserve(patternUpdaters.size()); + for (const auto& kv : patternUpdaters) { + names.push_back(kv.first); } - return String("off"); + return names; } -void NeoPatternService::setPatternByName(const String& name) { - if (patternSetters.empty()) registerPatterns(); - auto it = patternSetters.find(name); - if (it != patternSetters.end()) { - pixels.Index = 0; - pixels.Direction = FORWARD; - it->second(); +String NeoPatternService::currentPatternName() const { + for (const auto& kv : nameToPatternMap) { + if (kv.second == activePattern) { + return kv.first; + } + } + return "none"; +} + +NeoPatternService::NeoPatternType NeoPatternService::nameToPattern(const String& name) const { + auto it = nameToPatternMap.find(name); + return (it != nameToPatternMap.end()) ? it->second : NeoPatternType::NONE; +} + +void NeoPatternService::resetStateForPattern(NeoPatternType pattern) { + neoPattern->Index = 0; + neoPattern->Direction = static_cast<::direction>(direction); + neoPattern->completed = 0; + lastUpdateMs = 0; +} + +uint32_t NeoPatternService::parseColor(const String& colorStr) const { + if (colorStr.startsWith("#")) { + return strtoul(colorStr.substring(1).c_str(), nullptr, 16); + } else if (colorStr.startsWith("0x") || colorStr.startsWith("0X")) { + return strtoul(colorStr.c_str(), nullptr, 16); + } else { + return strtoul(colorStr.c_str(), nullptr, 10); } } void NeoPatternService::update() { - pixels.Update(); + if (!initialized) return; + + //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 { + updateNone(); + } +} + +void NeoPatternService::updateRainbowCycle() { + neoPattern->RainbowCycleUpdate(); +} + +void NeoPatternService::updateTheaterChase() { + neoPattern->TheaterChaseUpdate(); +} + +void NeoPatternService::updateColorWipe() { + neoPattern->ColorWipeUpdate(); +} + +void NeoPatternService::updateScanner() { + neoPattern->ScannerUpdate(); +} + +void NeoPatternService::updateFade() { + neoPattern->FadeUpdate(); +} + +void NeoPatternService::updateFire() { + neoPattern->Fire(50, 120); +} + +void NeoPatternService::updateNone() { + // For NONE pattern, just show the current color + neoPattern->ColorSet(neoPattern->Color1); } diff --git a/examples/neopattern/NeoPatternService.h b/examples/neopattern/NeoPatternService.h index 0439aa9..9476693 100644 --- a/examples/neopattern/NeoPatternService.h +++ b/examples/neopattern/NeoPatternService.h @@ -1,34 +1,87 @@ #pragma once #include "spore/Service.h" #include "spore/core/TaskManager.h" -#include "NeoPattern.cpp" +#include "NeoPattern.h" +#include "NeoPatternState.h" +#include "NeoPixelConfig.h" #include -#include +#include class NeoPatternService : public Service { public: - NeoPatternService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, uint8_t type); + enum class NeoPatternType { + NONE = 0, + RAINBOW_CYCLE = 1, + THEATER_CHASE = 2, + COLOR_WIPE = 3, + SCANNER = 4, + FADE = 5, + FIRE = 6 + }; + + enum class NeoDirection { + FORWARD, + REVERSE + }; + + NeoPatternService(TaskManager& taskMgr, const NeoPixelConfig& config); + ~NeoPatternService(); + void registerEndpoints(ApiServer& api) override; const char* getName() const override { return "NeoPattern"; } - void setBrightness(uint8_t b); + // Pattern control methods + void setPattern(NeoPatternType pattern); void setPatternByName(const String& name); + void setColor(uint32_t color); + void setColor2(uint32_t color2); + void setBrightness(uint8_t brightness); + void setTotalSteps(uint16_t steps); + void setDirection(NeoDirection direction); + void setUpdateInterval(unsigned long interval); + + // State management + void setState(const NeoPatternState& state); + NeoPatternState getState() const; private: void registerTasks(); void registerPatterns(); - std::vector patternNamesVector(); - String currentPatternName(); void update(); + + // Pattern updaters + void updateRainbowCycle(); + void updateTheaterChase(); + void updateColorWipe(); + void updateScanner(); + void updateFade(); + void updateFire(); + void updateNone(); - // Handlers + // API handlers void handleStatusRequest(AsyncWebServerRequest* request); void handlePatternsRequest(AsyncWebServerRequest* request); void handleControlRequest(AsyncWebServerRequest* request); + void handleStateRequest(AsyncWebServerRequest* request); + + // Utility methods + std::vector patternNamesVector() const; + String currentPatternName() const; + NeoPatternType nameToPattern(const String& name) const; + void resetStateForPattern(NeoPatternType pattern); + uint32_t parseColor(const String& colorStr) const; TaskManager& taskManager; - NeoPattern pixels; + NeoPattern* neoPattern; + NeoPixelConfig config; + NeoPatternState currentState; + + std::map> patternUpdaters; + std::map nameToPatternMap; + + NeoPatternType activePattern; + NeoDirection direction; unsigned long updateIntervalMs; - uint8_t brightness; - std::map> patternSetters; + unsigned long lastUpdateMs; + bool initialized; }; diff --git a/examples/neopattern/NeoPatternState.h b/examples/neopattern/NeoPatternState.h new file mode 100644 index 0000000..da95b09 --- /dev/null +++ b/examples/neopattern/NeoPatternState.h @@ -0,0 +1,59 @@ +#ifndef __NEOPATTERN_STATE__ +#define __NEOPATTERN_STATE__ + +#include + +struct NeoPatternState { + // Default values + static constexpr uint DEFAULT_PATTERN = 0; + static constexpr uint DEFAULT_COLOR = 0xFF0000; // Red + static constexpr uint DEFAULT_COLOR2 = 0x0000FF; // Blue + static constexpr uint DEFAULT_TOTAL_STEPS = 16; + static constexpr uint DEFAULT_BRIGHTNESS = 64; + + uint pattern = DEFAULT_PATTERN; + uint color = DEFAULT_COLOR; + uint color2 = DEFAULT_COLOR2; + uint totalSteps = DEFAULT_TOTAL_STEPS; + uint brightness = DEFAULT_BRIGHTNESS; + + NeoPatternState() = default; + + NeoPatternState(uint p, uint c, uint c2, uint steps, uint b) + : pattern(p), color(c), color2(c2), totalSteps(steps), brightness(b) {} + + void mapJsonObject(JsonObject& root) const { + root["pattern"] = pattern; + root["color"] = color; + root["color2"] = color2; + root["totalSteps"] = totalSteps; + root["brightness"] = brightness; + } + + void fromJsonObject(JsonObject& json) { + pattern = json["pattern"] | pattern; + color = json["color"] | color; + color2 = json["color2"] | color2; + totalSteps = json["totalSteps"] | totalSteps; + brightness = json["brightness"] | brightness; + } + + // Helper methods for JSON string conversion + String toJsonString() const { + JsonDocument doc; + JsonObject root = doc.to(); + mapJsonObject(root); + String result; + serializeJson(doc, result); + return result; + } + + void fromJsonString(const String& jsonStr) { + JsonDocument doc; + deserializeJson(doc, jsonStr); + JsonObject root = doc.as(); + fromJsonObject(root); + } +}; + +#endif \ No newline at end of file diff --git a/examples/neopattern/NeoPixelConfig.h b/examples/neopattern/NeoPixelConfig.h new file mode 100644 index 0000000..8a4c044 --- /dev/null +++ b/examples/neopattern/NeoPixelConfig.h @@ -0,0 +1,45 @@ +#ifndef __NEOPIXEL_CONFIG__ +#define __NEOPIXEL_CONFIG__ + +#include + +struct NeoPixelConfig +{ + // Configuration constants + static constexpr int DEFAULT_PIN = 2; + static constexpr int DEFAULT_LENGTH = 8; + static constexpr int DEFAULT_BRIGHTNESS = 100; + static constexpr int DEFAULT_UPDATE_INTERVAL = 100; + static constexpr int DEFAULT_COLOR = 0xFF0000; // Red + + int pin = DEFAULT_PIN; + int length = DEFAULT_LENGTH; + int brightness = DEFAULT_BRIGHTNESS; + int updateInterval = DEFAULT_UPDATE_INTERVAL; + int defaultColor = DEFAULT_COLOR; + + NeoPixelConfig() = default; + + NeoPixelConfig(int p, int l, int b, int interval, int color = DEFAULT_COLOR) + : pin(p), length(l), brightness(b), updateInterval(interval), defaultColor(color) {} + + void mapJsonObject(JsonObject &root) const + { + root["pin"] = pin; + root["length"] = length; + root["brightness"] = brightness; + root["updateInterval"] = updateInterval; + root["defaultColor"] = defaultColor; + } + + void fromJsonObject(JsonObject &json) + { + pin = json["pin"] | pin; + length = json["length"] | length; + brightness = json["brightness"] | brightness; + updateInterval = json["updateInterval"] | updateInterval; + defaultColor = json["defaultColor"] | defaultColor; + } +}; + +#endif \ No newline at end of file diff --git a/examples/neopattern/README.md b/examples/neopattern/README.md new file mode 100644 index 0000000..febe439 --- /dev/null +++ b/examples/neopattern/README.md @@ -0,0 +1,130 @@ +# NeoPattern Service for Spore Framework + +This example demonstrates how to integrate a NeoPixel pattern service with the Spore framework. It provides a comprehensive LED strip control system with multiple animation patterns and REST API endpoints. + +## Features + +- **Multiple Animation Patterns**: Rainbow cycle, theater chase, color wipe, scanner, fade, and fire effects +- **REST API Control**: Full HTTP API for pattern control and configuration +- **Real-time Updates**: Smooth pattern transitions and real-time parameter changes +- **State Management**: Persistent state with JSON serialization +- **Spore Integration**: Built on the Spore framework for networking and task management + +## Hardware Requirements + +- ESP32 or compatible microcontroller +- NeoPixel LED strip (WS2812B or compatible) +- Appropriate power supply for your LED strip + +## Configuration + +Edit `config.h` to configure your setup: + +```cpp +#define NEOPIXEL_PIN 4 // GPIO pin connected to LED strip +#define NEOPIXEL_LENGTH 8 // Number of LEDs in your strip +#define NEOPIXEL_BRIGHTNESS 100 // Initial brightness (0-255) +#define NEOPIXEL_UPDATE_INTERVAL 100 // Update interval in milliseconds +``` + +## API Endpoints + +### Status Information +- `GET /api/neopattern/status` - Get current status and configuration +- `GET /api/neopattern/patterns` - List available patterns + +### Pattern Control +- `POST /api/neopattern` - Control pattern parameters + - `pattern`: Pattern name (none, rainbow_cycle, theater_chase, color_wipe, scanner, fade, fire) + - `color`: Primary color (hex string like "#FF0000" or "0xFF0000") + - `color2`: Secondary color for two-color patterns + - `brightness`: Brightness level (0-255) + - `total_steps`: Number of steps for patterns + - `direction`: Pattern direction (forward, reverse) + - `interval`: Update interval in milliseconds + +### State Management +- `POST /api/neopattern/state` - Set complete state via JSON + +## Example API Usage + +### Set a rainbow cycle pattern +```bash +curl -X POST http://esp32-ip/api/neopattern \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "pattern=rainbow_cycle&interval=50" +``` + +### Set custom colors for theater chase +```bash +curl -X POST http://esp32-ip/api/neopattern \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "pattern=theater_chase&color=0xFF0000&color2=0x0000FF&brightness=150" +``` + +### Set complete state via JSON +```bash +curl -X POST http://esp32-ip/api/neopattern/state \ + -H "Content-Type: application/json" \ + -d '{ + "pattern": 3, + "color": 16711680, + "color2": 255, + "totalSteps": 32, + "brightness": 128 + }' +``` + +## Available Patterns + +1. **none** - Static color display +2. **rainbow_cycle** - Smooth rainbow cycling +3. **theater_chase** - Moving dots with two colors +4. **color_wipe** - Progressive color filling +5. **scanner** - Scanning light effect +6. **fade** - Smooth color transition +7. **fire** - Fire simulation effect + +## Building and Flashing + +1. Install PlatformIO +2. Configure your `platformio.ini` to include the Spore framework +3. Build and upload: + +```bash +pio run -t upload +``` + +## Integration with Spore Framework + +This service integrates with the Spore framework by: + +- Extending the `Service` base class +- Using `TaskManager` for pattern updates +- Registering REST API endpoints with `ApiServer` +- Following Spore's logging and configuration patterns + +The service automatically registers itself with the Spore framework and provides all functionality through the standard Spore API infrastructure. + +## Customization + +To add new patterns: + +1. Add the pattern enum to `NeoPatternService.h` +2. Implement the pattern logic in `NeoPattern.cpp` +3. Add the pattern updater to `registerPatterns()` in `NeoPatternService.cpp` +4. Update the pattern mapping in `nameToPatternMap` + +## Troubleshooting + +- **LEDs not lighting**: Check wiring and power supply +- **Patterns not updating**: Verify the update interval and task registration +- **API not responding**: Check network configuration and Spore framework setup +- **Memory issues**: Reduce LED count or pattern complexity + +## Dependencies + +- Spore Framework +- Adafruit NeoPixel library +- ArduinoJson +- ESP32 Arduino Core diff --git a/examples/neopattern/config.h b/examples/neopattern/config.h index 80ebd5c..5a2fadf 100644 --- a/examples/neopattern/config.h +++ b/examples/neopattern/config.h @@ -1,31 +1,22 @@ -#ifndef __DEVICE_CONFIG__ -#define __DEVICE_CONFIG__ +#ifndef __NPX_CONFIG__ +#define __NPX_CONFIG__ -// Scheduler config -#define _TASK_SLEEP_ON_IDLE_RUN -#define _TASK_STD_FUNCTION -#define _TASK_PRIORITY +// NeoPixel Configuration +#define NEOPIXEL_PIN 2 +#define NEOPIXEL_LENGTH 4 +#define NEOPIXEL_BRIGHTNESS 100 +#define NEOPIXEL_UPDATE_INTERVAL 100 -// Chip config -#define SPROCKET_TYPE "SPROCKET" -#define SERIAL_BAUD_RATE 115200 -#define STARTUP_DELAY 1000 +// Spore Framework Configuration +#define SPORE_APP_NAME "neopattern" +#define SPORE_DEVICE_TYPE "led_strip" -// network config -#define SPROCKET_MODE 1 -#define WIFI_CHANNEL 11 -#define AP_SSID "sprocket" -#define AP_PASSWORD "th3r31sn0sp00n" -#define STATION_SSID "MyAP" -#define STATION_PASSWORD "th3r31sn0sp00n" -#define HOSTNAME "sprocket" -#define CONNECT_TIMEOUT 10000 +// Network Configuration (if needed) +#define WIFI_SSID "your_wifi_ssid" +#define WIFI_PASSWORD "your_wifi_password" -// NeoPixel conig -#define LED_STRIP_PIN D2 -#define LED_STRIP_LENGTH 8 -#define LED_STRIP_BRIGHTNESS 48 -#define LED_STRIP_UPDATE_INTERVAL 200 -#define LED_STRIP_DEFAULT_COLOR 100 +// Debug Configuration +#define DEBUG_SERIAL_BAUD 115200 +#define DEBUG_ENABLED true -#endif \ No newline at end of file +#endif diff --git a/examples/neopattern/main.cpp b/examples/neopattern/main.cpp index 0a839d5..d12b79f 100644 --- a/examples/neopattern/main.cpp +++ b/examples/neopattern/main.cpp @@ -2,25 +2,31 @@ #include "spore/Spore.h" #include "spore/util/Logging.h" #include "NeoPatternService.h" +#include "NeoPixelConfig.h" -#ifndef LED_STRIP_PIN -#define LED_STRIP_PIN 2 +// Configuration constants +#ifndef NEOPIXEL_PIN +#define NEOPIXEL_PIN 2 #endif -#ifndef LED_STRIP_LENGTH -#define LED_STRIP_LENGTH 8 +#ifndef NEOPIXEL_LENGTH +#define NEOPIXEL_LENGTH 8 #endif -#ifndef LED_STRIP_TYPE -#define LED_STRIP_TYPE (NEO_GRB + NEO_KHZ800) +#ifndef NEOPIXEL_BRIGHTNESS +#define NEOPIXEL_BRIGHTNESS 100 +#endif + +#ifndef NEOPIXEL_UPDATE_INTERVAL +#define NEOPIXEL_UPDATE_INTERVAL 100 #endif // Create Spore instance with custom labels Spore spore({ {"app", "neopattern"}, - {"device", "light"}, - {"pixels", String(LED_STRIP_LENGTH)}, - {"pin", String(LED_STRIP_PIN)} + {"device", "led_strip"}, + {"pixels", String(NEOPIXEL_LENGTH)}, + {"pin", String(NEOPIXEL_PIN)} }); // Create custom service @@ -30,17 +36,25 @@ void setup() { // Initialize the Spore framework spore.setup(); + // Create configuration + NeoPixelConfig config( + NEOPIXEL_PIN, + NEOPIXEL_LENGTH, + NEOPIXEL_BRIGHTNESS, + NEOPIXEL_UPDATE_INTERVAL + ); + // Create and add custom service - neoPatternService = new NeoPatternService(spore.getTaskManager(), LED_STRIP_LENGTH, LED_STRIP_PIN, LED_STRIP_TYPE); + neoPatternService = new NeoPatternService(spore.getTaskManager(), config); spore.addService(neoPatternService); // Start the API server and complete initialization spore.begin(); - LOG_INFO( "Main", "NeoPattern service registered and ready!"); + LOG_INFO("Main", "NeoPattern service registered and ready!"); } void loop() { // Run the Spore framework loop spore.loop(); -} \ No newline at end of file +} diff --git a/examples/neopixel/NeoPixelService.cpp b/examples/neopixel/NeoPixelService.cpp deleted file mode 100644 index 899eda9..0000000 --- a/examples/neopixel/NeoPixelService.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include "NeoPixelService.h" -#include "spore/core/ApiServer.h" -#include "spore/util/Logging.h" - -// 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); -} - -NeoPixelService::NeoPixelService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, neoPixelType type) - : 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 NeoPixelService::registerEndpoints(ApiServer& api) { - api.addEndpoint("/api/neopixel/status", HTTP_GET, - [this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, - std::vector{}); - - api.addEndpoint("/api/neopixel/patterns", HTTP_GET, - [this](AsyncWebServerRequest* request) { handlePatternsRequest(request); }, - std::vector{}); - - api.addEndpoint("/api/neopixel", HTTP_POST, - [this](AsyncWebServerRequest* request) { handleControlRequest(request); }, - std::vector{ - ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()}, - ParamSpec{String("interval_ms"), false, String("body"), String("number"), {}, String("100")}, - ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("50")}, - ParamSpec{String("color"), false, String("body"), String("color"), {}}, - ParamSpec{String("color2"), false, String("body"), String("color"), {}}, - ParamSpec{String("r"), false, String("body"), String("number"), {}}, - ParamSpec{String("g"), false, String("body"), String("number"), {}}, - ParamSpec{String("b"), false, String("body"), String("number"), {}}, - ParamSpec{String("r2"), false, String("body"), String("number"), {}}, - ParamSpec{String("g2"), false, String("body"), String("number"), {}}, - ParamSpec{String("b2"), false, String("body"), String("number"), {}}, - ParamSpec{String("total_steps"), false, String("body"), String("number"), {}}, - ParamSpec{String("direction"), false, String("body"), String("string"), {String("forward"), String("reverse")}} - }); -} - -void NeoPixelService::handleStatusRequest(AsyncWebServerRequest* request) { - JsonDocument doc; - doc["pin"] = strip.getPin(); - 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); -} - -void NeoPixelService::handlePatternsRequest(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); -} - -void NeoPixelService::handleControlRequest(AsyncWebServerRequest* request) { - if (request->hasParam("pattern", true)) { - String name = request->getParam("pattern", true)->value(); - setPatternByName(name); - } - - if (request->hasParam("interval_ms", true)) { - unsigned long v = request->getParam("interval_ms", true)->value().toInt(); - if (v < 1) v = 1; - updateIntervalMs = v; - 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); - } - - // Accept packed color ints or r,g,b triplets - if (request->hasParam("color", true)) { - wipeColor = (uint32_t)strtoul(request->getParam("color", true)->value().c_str(), nullptr, 0); - chaseColor = wipeColor; - } - 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); -} - -void NeoPixelService::setBrightness(uint8_t b) { - brightness = b; - strip.setBrightness(brightness); - strip.show(); -} - -void NeoPixelService::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 NeoPixelService::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 NeoPixelService::patternNamesVector() const { - std::vector v; - v.reserve(patternUpdaters.size()); - for (const auto& kv : patternUpdaters) v.push_back(kv.first); - return v; -} - -String NeoPixelService::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"); -} - -NeoPixelService::Pattern NeoPixelService::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 NeoPixelService::setPatternByName(const String& name) { - Pattern p = nameToPattern(name); - resetStateForPattern(p); -} - -void NeoPixelService::resetStateForPattern(Pattern p) { - strip.clear(); - strip.show(); - - wipeIndex = 0; - rainbowJ = 0; - cycleJ = 0; - chaseJ = 0; - chaseQ = 0; - chasePhaseOn = true; - lastUpdateMs = 0; - - currentPattern = p; -} - -void NeoPixelService::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(); - } -} - -void NeoPixelService::updateOff() { - strip.clear(); - strip.show(); -} - -void NeoPixelService::updateColorWipe() { - if (wipeIndex < strip.numPixels()) { - strip.setPixelColor(wipeIndex, wipeColor); - ++wipeIndex; - strip.show(); - } else { - strip.clear(); - wipeIndex = 0; - } -} - -void NeoPixelService::updateRainbow() { - for (uint16_t i = 0; i < strip.numPixels(); i++) { - strip.setPixelColor(i, colorWheel(strip, (i + rainbowJ) & 255)); - } - strip.show(); - rainbowJ = (rainbowJ + 1) & 0xFF; -} - -void NeoPixelService::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 NeoPixelService::updateTheaterChase() { - 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; - chaseJ = (chaseJ + 1) % 10; - } -} - -void NeoPixelService::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; - } -} diff --git a/examples/neopixel/NeoPixelService.h b/examples/neopixel/NeoPixelService.h deleted file mode 100644 index 5653469..0000000 --- a/examples/neopixel/NeoPixelService.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once -#include "spore/Service.h" -#include "spore/core/TaskManager.h" -#include -#include -#include - -class NeoPixelService : public Service { -public: - enum class Pattern { - Off, - ColorWipe, - Rainbow, - RainbowCycle, - TheaterChase, - TheaterChaseRainbow - }; - - NeoPixelService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, neoPixelType type); - void registerEndpoints(ApiServer& api) override; - const char* getName() const override { return "NeoPixel"; } - - void setPatternByName(const String& name); - void setBrightness(uint8_t b); - -private: - void registerTasks(); - void registerPatterns(); - std::vector patternNamesVector() const; - String currentPatternName() const; - Pattern nameToPattern(const String& name) const; - void resetStateForPattern(Pattern p); - void update(); - - // Pattern updaters - void updateOff(); - void updateColorWipe(); - void updateRainbow(); - void updateRainbowCycle(); - void updateTheaterChase(); - void updateTheaterChaseRainbow(); - - // Handlers - void handleStatusRequest(AsyncWebServerRequest* request); - void handlePatternsRequest(AsyncWebServerRequest* request); - void handleControlRequest(AsyncWebServerRequest* request); - - 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; -}; diff --git a/examples/neopixel/main.cpp b/examples/neopixel/main.cpp deleted file mode 100644 index fef6ca6..0000000 --- a/examples/neopixel/main.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include "spore/Spore.h" -#include "spore/util/Logging.h" -#include "NeoPixelService.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 - -// Create Spore instance with custom labels -Spore spore({ - {"app", "neopixel"}, - {"device", "light"}, - {"pixels", String(NEOPIXEL_COUNT)}, - {"pin", String(NEOPIXEL_PIN)} -}); - -// Create custom service -NeoPixelService* neoPixelService = nullptr; - -void setup() { - // Initialize the Spore framework - spore.setup(); - - // Create and add custom service - neoPixelService = new NeoPixelService(spore.getTaskManager(), NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_TYPE); - spore.addService(neoPixelService); - - // Start the API server and complete initialization - spore.begin(); - - LOG_INFO( "Main", "NeoPixel service registered and ready!"); -} - -void loop() { - // Run the Spore framework loop - spore.loop(); -} \ No newline at end of file diff --git a/examples/static_web/README.md b/examples/static_web/README.md deleted file mode 100644 index 02bfa24..0000000 --- a/examples/static_web/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Static Web Server Example - -This example demonstrates how to serve static HTML files from LittleFS using the Spore framework. - -## Features - -- Serves static files from LittleFS filesystem -- Beautiful web interface with real-time status updates -- Responsive design that works on mobile and desktop -- Automatic API endpoint discovery and status monitoring - -## Files - -- `main.cpp` - Main application entry point -- `data/index.html` - Web interface (served from LittleFS) - -## Web Interface - -The web interface provides: - -- **Node Status** - Shows uptime and system information -- **Network Status** - Displays WiFi connection status -- **Task Status** - Shows active background tasks -- **Cluster Status** - Displays cluster membership -- **API Links** - Quick access to all available API endpoints - -## Building and Flashing - -### Build for D1 Mini (recommended for web interface) - -```bash -# Build the firmware -pio run -e d1_mini_static_web - -# Flash to device -pio run -e d1_mini_static_web -t upload -``` - -### Upload Files to LittleFS - -After flashing the firmware, you need to upload the HTML files to the LittleFS filesystem: - -```bash -# Upload data directory to LittleFS -pio run -e d1_mini_static_web -t uploadfs -``` - -## Usage - -1. Flash the firmware to your ESP8266 device -2. Upload the data files to LittleFS -3. Connect to the device's WiFi network -4. Open a web browser and navigate to `http:///` -5. The web interface will load and display real-time status information - -## API Endpoints - -The web interface automatically discovers and displays links to all available API endpoints: - -- `/api/node/status` - Node status and system information -- `/api/network/status` - Network and WiFi status -- `/api/tasks/status` - Task management status -- `/api/cluster/members` - Cluster membership -- `/api/node/endpoints` - Complete API endpoint list - -## Customization - -To customize the web interface: - -1. Modify `data/index.html` with your desired content -2. Add additional static files (CSS, JS, images) to the `data/` directory -3. Re-upload the files using `pio run -e d1_mini_static_web -t uploadfs` - -## File Structure - -``` -data/ -├── index.html # Main web interface -└── (other static files) # Additional web assets -``` - -The StaticFileService automatically serves files from the LittleFS root directory, so any files placed in the `data/` directory will be accessible via HTTP. diff --git a/examples/static_web/main.cpp b/examples/static_web/main.cpp deleted file mode 100644 index e9d16af..0000000 --- a/examples/static_web/main.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include "spore/Spore.h" -#include "spore/util/Logging.h" - -// Create Spore instance -Spore spore; - -void setup() { - // Initialize Spore framework - spore.setup(); - - // Start the framework (this will start the API server with static file serving) - spore.begin(); - - LOG_INFO( "Example", "Static web server started!"); - LOG_INFO( "Example", "Visit http:/// to see the web interface"); -} - -void loop() { - // Let Spore handle the main loop - spore.loop(); -} diff --git a/platformio.ini b/platformio.ini index a138d54..b8f0dc8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -80,69 +80,7 @@ build_src_filter = + + -[env:d1_mini_relay] -platform = platformio/espressif8266@^4.2.1 -board = d1_mini -framework = arduino -upload_speed = 115200 -monitor_speed = 115200 -board_build.filesystem = littlefs -board_build.flash_mode = dio -board_build.flash_size = 4M -board_build.ldscript = eagle.flash.4m1m.ld -lib_deps = ${common.lib_deps} -data_dir = examples/relay/data -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.filesystem = littlefs -board_build.flash_mode = dout -board_build.ldscript = eagle.flash.1m64.ld -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.filesystem = littlefs -board_build.flash_mode = dio -board_build.flash_size = 4M -board_build.ldscript = eagle.flash.4m1m.ld -lib_deps = ${common.lib_deps} - adafruit/Adafruit NeoPixel@^1.15.1 -build_src_filter = - + - + - + - + - + - + - + - -[env:esp01_1m_neopattern] +[env:neopattern] platform = platformio/espressif8266@^4.2.1 board = esp01_1m framework = arduino @@ -162,83 +100,3 @@ build_src_filter = + + + - -[env:d1_mini_neopattern] -platform = platformio/espressif8266@^4.2.1 -board = d1_mini -framework = arduino -upload_speed = 115200 -monitor_speed = 115200 -board_build.filesystem = littlefs -board_build.flash_mode = dio -board_build.flash_size = 4M -board_build.ldscript = eagle.flash.4m1m.ld -lib_deps = ${common.lib_deps} - adafruit/Adafruit NeoPixel@^1.15.1 -build_src_filter = - + - + - + - + - + - + - + - -[env:d1_mini_static_web] -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 -board_build.filesystem = littlefs -board_build.ldscript = eagle.flash.4m1m.ld -lib_deps = ${common.lib_deps} -build_src_filter = - + - + - + - + - + - + - + - -[env:esp01_1m_cpu_monitor] -platform = platformio/espressif8266@^4.2.1 -board = esp01_1m -framework = arduino -upload_speed = 115200 -monitor_speed = 115200 -board_build.filesystem = littlefs -board_build.flash_mode = dout -board_build.ldscript = eagle.flash.1m64.ld -lib_deps = ${common.lib_deps} -build_src_filter = - + - + - + - + - + - + - + - -[env:d1_mini_cpu_monitor] -platform = platformio/espressif8266@^4.2.1 -board = d1_mini -framework = arduino -upload_speed = 115200 -monitor_speed = 115200 -board_build.filesystem = littlefs -board_build.flash_mode = dio -board_build.flash_size = 4M -board_build.ldscript = eagle.flash.4m1m.ld -lib_deps = ${common.lib_deps} -build_src_filter = - + - + - + - + - + - + - +