feat: rewrite NeoPattern example

This commit is contained in:
2025-09-19 21:02:26 +02:00
parent 93f09c3bb4
commit 4727405be1
14 changed files with 637 additions and 808 deletions

View File

@@ -2,10 +2,6 @@
* Original NeoPattern code by Bill Earl * Original NeoPattern code by Bill Earl
* https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview * 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: * Custom modifications by 0x1d:
* - default OnComplete callback that sets pattern to reverse * - default OnComplete callback that sets pattern to reverse
* - separate animation update from timer; Update now updates directly, UpdateScheduled uses timer * - separate animation update from timer; Update now updates directly, UpdateScheduled uses timer
@@ -28,7 +24,8 @@ enum pattern
FADE = 5, FADE = 5,
FIRE = 6 FIRE = 6
}; };
// Patern directions supported:
// Pattern directions supported:
enum direction enum direction
{ {
FORWARD, FORWARD,
@@ -52,8 +49,8 @@ class NeoPattern : public Adafruit_NeoPixel
uint16_t Index; // current step within the pattern uint16_t Index; // current step within the pattern
uint16_t completed = 0; uint16_t completed = 0;
// FIXME return current NeoPatternState // Callback on completion of pattern
void (*OnComplete)(int); // Callback on completion of pattern void (*OnComplete)(int);
uint8_t *frameBuffer; uint8_t *frameBuffer;
int bufferSize = 0; int bufferSize = 0;
@@ -76,6 +73,14 @@ class NeoPattern : public Adafruit_NeoPixel
begin(); begin();
} }
~NeoPattern() {
if (frameBuffer) {
free(frameBuffer);
}
}
// Implementation starts here (inline)
void handleStream(uint8_t *data, size_t len) void handleStream(uint8_t *data, size_t len)
{ {
//const uint16_t *data16 = (uint16_t *)data; //const uint16_t *data16 = (uint16_t *)data;
@@ -129,6 +134,9 @@ class NeoPattern : public Adafruit_NeoPixel
case FIRE: case FIRE:
Fire(50, 120); Fire(50, 120);
break; break;
case NONE:
// For NONE pattern, just maintain current state
break;
default: default:
if (bufferSize > 0) if (bufferSize > 0)
{ {
@@ -461,6 +469,7 @@ class NeoPattern : public Adafruit_NeoPixel
{ {
setPixelColor(Pixel, Color(red, green, blue)); setPixelColor(Pixel, Color(red, green, blue));
} }
void showStrip() void showStrip()
{ {
show(); show();

View File

@@ -1,56 +1,90 @@
#include "NeoPatternService.h" #include "NeoPatternService.h"
#include "spore/core/ApiServer.h" #include "spore/core/ApiServer.h"
#include "spore/util/Logging.h"
#include <ArduinoJson.h>
NeoPatternService::NeoPatternService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, uint8_t type) NeoPatternService::NeoPatternService(TaskManager& taskMgr, const NeoPixelConfig& config)
: taskManager(taskMgr), : taskManager(taskMgr),
pixels(numPixels, pin, type), config(config),
updateIntervalMs(100), activePattern(NeoPatternType::RAINBOW_CYCLE),
brightness(48) { direction(NeoDirection::FORWARD),
pixels.setBrightness(brightness); updateIntervalMs(config.updateInterval),
pixels.show(); 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<uint>(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(); registerTasks();
setPatternByName("rainbow_cycle"); initialized = true;
LOG_INFO("NeoPattern", "Service initialized");
}
NeoPatternService::~NeoPatternService() {
if (neoPattern) {
delete neoPattern;
}
} }
void NeoPatternService::registerEndpoints(ApiServer& api) { void NeoPatternService::registerEndpoints(ApiServer& api) {
// Status endpoint
api.addEndpoint("/api/neopattern/status", HTTP_GET, api.addEndpoint("/api/neopattern/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Patterns list endpoint
api.addEndpoint("/api/neopattern/patterns", HTTP_GET, api.addEndpoint("/api/neopattern/patterns", HTTP_GET,
[this](AsyncWebServerRequest* request) { handlePatternsRequest(request); }, [this](AsyncWebServerRequest* request) { handlePatternsRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Control endpoint
api.addEndpoint("/api/neopattern", HTTP_POST, api.addEndpoint("/api/neopattern", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); }, [this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{ std::vector<ParamSpec>{
ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()}, ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()},
ParamSpec{String("interval_ms"), false, String("body"), String("number"), {}, String("100")}, ParamSpec{String("color"), false, String("body"), String("string"), {}},
ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("50")}, ParamSpec{String("color2"), false, String("body"), String("string"), {}},
ParamSpec{String("color"), false, String("body"), String("color"), {}}, ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("100")},
ParamSpec{String("color2"), false, String("body"), String("color"), {}}, ParamSpec{String("total_steps"), false, String("body"), String("number"), {}, String("16")},
ParamSpec{String("r"), false, String("body"), String("number"), {}}, ParamSpec{String("direction"), false, String("body"), String("string"), {String("forward"), String("reverse")}},
ParamSpec{String("g"), false, String("body"), String("number"), {}}, ParamSpec{String("interval"), false, String("body"), String("number"), {}, String("100")}
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")}}
}); });
// State endpoint for complex state updates
api.addEndpoint("/api/neopattern/state", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleStateRequest(request); },
std::vector<ParamSpec>{});
} }
void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) { void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) {
JsonDocument doc; JsonDocument doc;
doc["pin"] = pixels.getPin(); doc["pin"] = config.pin;
doc["count"] = pixels.numPixels(); doc["length"] = config.length;
doc["interval_ms"] = updateIntervalMs; doc["brightness"] = currentState.brightness;
doc["brightness"] = brightness;
doc["pattern"] = currentPatternName(); doc["pattern"] = currentPatternName();
doc["total_steps"] = pixels.TotalSteps; doc["color"] = String(currentState.color, HEX);
doc["color1"] = pixels.Color1; doc["color2"] = String(currentState.color2, HEX);
doc["color2"] = pixels.Color2; doc["total_steps"] = currentState.totalSteps;
doc["direction"] = (direction == NeoDirection::FORWARD) ? "forward" : "reverse";
doc["interval"] = updateIntervalMs;
doc["active"] = initialized;
String json; String json;
serializeJson(doc, json); serializeJson(doc, json);
@@ -60,7 +94,9 @@ void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) {
void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) { void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) {
JsonDocument doc; JsonDocument doc;
JsonArray arr = doc.to<JsonArray>(); JsonArray arr = doc.to<JsonArray>();
for (auto& kv : patternSetters) arr.add(kv.first); for (const auto& kv : patternUpdaters) {
arr.add(kv.first);
}
String json; String json;
serializeJson(doc, json); serializeJson(doc, json);
@@ -68,121 +104,265 @@ void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) {
} }
void NeoPatternService::handleControlRequest(AsyncWebServerRequest* request) { void NeoPatternService::handleControlRequest(AsyncWebServerRequest* request) {
bool updated = false;
if (request->hasParam("pattern", true)) { if (request->hasParam("pattern", true)) {
String name = request->getParam("pattern", true)->value(); String name = request->getParam("pattern", true)->value();
setPatternByName(name); setPatternByName(name);
updated = true;
} }
if (request->hasParam("interval_ms", true)) { if (request->hasParam("color", true)) {
unsigned long v = request->getParam("interval_ms", true)->value().toInt(); String colorStr = request->getParam("color", true)->value();
if (v < 1) v = 1; uint32_t color = parseColor(colorStr);
updateIntervalMs = v; setColor(color);
taskManager.setTaskInterval("neopattern_update", updateIntervalMs); 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)) { if (request->hasParam("brightness", true)) {
int b = request->getParam("brightness", true)->value().toInt(); int b = request->getParam("brightness", true)->value().toInt();
if (b < 0) b = 0; if (b < 0) b = 0;
if (b > 255) b = 255; if (b > 255) b = 255;
setBrightness((uint8_t)b); setBrightness(static_cast<uint8_t>(b));
} updated = true;
// 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);
} }
if (request->hasParam("total_steps", 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<uint16_t>(steps));
updated = true;
}
} }
if (request->hasParam("direction", true)) { if (request->hasParam("direction", true)) {
String dir = request->getParam("direction", true)->value(); String dirStr = request->getParam("direction", true)->value();
if (dir.equalsIgnoreCase("forward")) pixels.Direction = FORWARD; NeoDirection dir = (dirStr.equalsIgnoreCase("reverse")) ? NeoDirection::REVERSE : NeoDirection::FORWARD;
else if (dir.equalsIgnoreCase("reverse")) pixels.Direction = REVERSE; 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; JsonDocument resp;
resp["ok"] = true; resp["ok"] = true;
resp["pattern"] = currentPatternName(); resp["pattern"] = currentPatternName();
resp["interval_ms"] = updateIntervalMs; resp["color"] = String(currentState.color, HEX);
resp["brightness"] = brightness; 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; String json;
serializeJson(resp, json); serializeJson(resp, json);
request->send(200, "application/json", json); request->send(200, "application/json", json);
} }
void NeoPatternService::setBrightness(uint8_t b) { void NeoPatternService::handleStateRequest(AsyncWebServerRequest* request) {
brightness = b; if (request->contentType() != "application/json") {
pixels.setBrightness(brightness); request->send(400, "application/json", "{\"error\":\"Content-Type must be application/json\"}");
pixels.show(); 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<uint>(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<NeoPatternType>(state.pattern));
}
NeoPatternState NeoPatternService::getState() const {
return currentState;
} }
void NeoPatternService::registerTasks() { void NeoPatternService::registerTasks() {
taskManager.registerTask("neopattern_update", updateIntervalMs, [this]() { update(); }); taskManager.registerTask("neopattern_update", updateIntervalMs, [this]() { update(); });
taskManager.registerTask("neopattern_status_print", 10000, [this]() { taskManager.registerTask("neopattern_status_print", 10000, [this]() {
Serial.printf("[NeoPattern] pattern=%s interval=%lu ms brightness=%u\n", LOG_INFO("NeoPattern", "Status update");
currentPatternName().c_str(), updateIntervalMs, brightness);
}); });
} }
void NeoPatternService::registerPatterns() { void NeoPatternService::registerPatterns() {
patternSetters["off"] = [this]() { pixels.ActivePattern = NONE; }; // Register pattern updaters
patternSetters["rainbow_cycle"] = [this]() { pixels.RainbowCycle(updateIntervalMs); }; patternUpdaters["none"] = [this]() { updateNone(); };
patternSetters["theater_chase"] = [this]() { pixels.TheaterChase(pixels.Color1 ? pixels.Color1 : pixels.Color(127,127,127), pixels.Color2, updateIntervalMs); }; patternUpdaters["rainbow_cycle"] = [this]() { updateRainbowCycle(); };
patternSetters["color_wipe"] = [this]() { pixels.ColorWipe(pixels.Color1 ? pixels.Color1 : pixels.Color(255,0,0), updateIntervalMs); }; patternUpdaters["theater_chase"] = [this]() { updateTheaterChase(); };
patternSetters["scanner"] = [this]() { pixels.Scanner(pixels.Color1 ? pixels.Color1 : pixels.Color(0,0,255), updateIntervalMs); }; patternUpdaters["color_wipe"] = [this]() { updateColorWipe(); };
patternSetters["fade"] = [this]() { pixels.Fade(pixels.Color1, pixels.Color2, pixels.TotalSteps ? pixels.TotalSteps : 32, updateIntervalMs); }; patternUpdaters["scanner"] = [this]() { updateScanner(); };
patternSetters["fire"] = [this]() { pixels.ActivePattern = FIRE; pixels.Interval = updateIntervalMs; }; 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<String> NeoPatternService::patternNamesVector() { std::vector<String> NeoPatternService::patternNamesVector() const {
if (patternSetters.empty()) registerPatterns(); std::vector<String> names;
std::vector<String> v; names.reserve(patternUpdaters.size());
v.reserve(patternSetters.size()); for (const auto& kv : patternUpdaters) {
for (const auto& kv : patternSetters) v.push_back(kv.first); names.push_back(kv.first);
return v; }
return names;
} }
String NeoPatternService::currentPatternName() { String NeoPatternService::currentPatternName() const {
switch (pixels.ActivePattern) { for (const auto& kv : nameToPatternMap) {
case NONE: return String("off"); if (kv.second == activePattern) {
case RAINBOW_CYCLE: return String("rainbow_cycle"); return kv.first;
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");
} }
return String("off"); }
return "none";
} }
void NeoPatternService::setPatternByName(const String& name) { NeoPatternService::NeoPatternType NeoPatternService::nameToPattern(const String& name) const {
if (patternSetters.empty()) registerPatterns(); auto it = nameToPatternMap.find(name);
auto it = patternSetters.find(name); return (it != nameToPatternMap.end()) ? it->second : NeoPatternType::NONE;
if (it != patternSetters.end()) { }
pixels.Index = 0;
pixels.Direction = FORWARD; void NeoPatternService::resetStateForPattern(NeoPatternType pattern) {
it->second(); 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() { 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);
} }

View File

@@ -1,34 +1,87 @@
#pragma once #pragma once
#include "spore/Service.h" #include "spore/Service.h"
#include "spore/core/TaskManager.h" #include "spore/core/TaskManager.h"
#include "NeoPattern.cpp" #include "NeoPattern.h"
#include "NeoPatternState.h"
#include "NeoPixelConfig.h"
#include <map> #include <map>
#include <vector> #include <functional>
class NeoPatternService : public Service { class NeoPatternService : public Service {
public: 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; void registerEndpoints(ApiServer& api) override;
const char* getName() const override { return "NeoPattern"; } 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 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: private:
void registerTasks(); void registerTasks();
void registerPatterns(); void registerPatterns();
std::vector<String> patternNamesVector();
String currentPatternName();
void update(); void update();
// Handlers // Pattern updaters
void updateRainbowCycle();
void updateTheaterChase();
void updateColorWipe();
void updateScanner();
void updateFade();
void updateFire();
void updateNone();
// API handlers
void handleStatusRequest(AsyncWebServerRequest* request); void handleStatusRequest(AsyncWebServerRequest* request);
void handlePatternsRequest(AsyncWebServerRequest* request); void handlePatternsRequest(AsyncWebServerRequest* request);
void handleControlRequest(AsyncWebServerRequest* request); void handleControlRequest(AsyncWebServerRequest* request);
void handleStateRequest(AsyncWebServerRequest* request);
// Utility methods
std::vector<String> patternNamesVector() const;
String currentPatternName() const;
NeoPatternType nameToPattern(const String& name) const;
void resetStateForPattern(NeoPatternType pattern);
uint32_t parseColor(const String& colorStr) const;
TaskManager& taskManager; TaskManager& taskManager;
NeoPattern pixels; NeoPattern* neoPattern;
NeoPixelConfig config;
NeoPatternState currentState;
std::map<String, std::function<void()>> patternUpdaters;
std::map<String, NeoPatternType> nameToPatternMap;
NeoPatternType activePattern;
NeoDirection direction;
unsigned long updateIntervalMs; unsigned long updateIntervalMs;
uint8_t brightness; unsigned long lastUpdateMs;
std::map<String, std::function<void()>> patternSetters; bool initialized;
}; };

View File

@@ -0,0 +1,59 @@
#ifndef __NEOPATTERN_STATE__
#define __NEOPATTERN_STATE__
#include <ArduinoJson.h>
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<JsonObject>();
mapJsonObject(root);
String result;
serializeJson(doc, result);
return result;
}
void fromJsonString(const String& jsonStr) {
JsonDocument doc;
deserializeJson(doc, jsonStr);
JsonObject root = doc.as<JsonObject>();
fromJsonObject(root);
}
};
#endif

View File

@@ -0,0 +1,45 @@
#ifndef __NEOPIXEL_CONFIG__
#define __NEOPIXEL_CONFIG__
#include <ArduinoJson.h>
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

View File

@@ -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

View File

@@ -1,31 +1,22 @@
#ifndef __DEVICE_CONFIG__ #ifndef __NPX_CONFIG__
#define __DEVICE_CONFIG__ #define __NPX_CONFIG__
// Scheduler config // NeoPixel Configuration
#define _TASK_SLEEP_ON_IDLE_RUN #define NEOPIXEL_PIN 2
#define _TASK_STD_FUNCTION #define NEOPIXEL_LENGTH 4
#define _TASK_PRIORITY #define NEOPIXEL_BRIGHTNESS 100
#define NEOPIXEL_UPDATE_INTERVAL 100
// Chip config // Spore Framework Configuration
#define SPROCKET_TYPE "SPROCKET" #define SPORE_APP_NAME "neopattern"
#define SERIAL_BAUD_RATE 115200 #define SPORE_DEVICE_TYPE "led_strip"
#define STARTUP_DELAY 1000
// network config // Network Configuration (if needed)
#define SPROCKET_MODE 1 #define WIFI_SSID "your_wifi_ssid"
#define WIFI_CHANNEL 11 #define WIFI_PASSWORD "your_wifi_password"
#define AP_SSID "sprocket"
#define AP_PASSWORD "th3r31sn0sp00n"
#define STATION_SSID "MyAP"
#define STATION_PASSWORD "th3r31sn0sp00n"
#define HOSTNAME "sprocket"
#define CONNECT_TIMEOUT 10000
// NeoPixel conig // Debug Configuration
#define LED_STRIP_PIN D2 #define DEBUG_SERIAL_BAUD 115200
#define LED_STRIP_LENGTH 8 #define DEBUG_ENABLED true
#define LED_STRIP_BRIGHTNESS 48
#define LED_STRIP_UPDATE_INTERVAL 200
#define LED_STRIP_DEFAULT_COLOR 100
#endif #endif

View File

@@ -2,25 +2,31 @@
#include "spore/Spore.h" #include "spore/Spore.h"
#include "spore/util/Logging.h" #include "spore/util/Logging.h"
#include "NeoPatternService.h" #include "NeoPatternService.h"
#include "NeoPixelConfig.h"
#ifndef LED_STRIP_PIN // Configuration constants
#define LED_STRIP_PIN 2 #ifndef NEOPIXEL_PIN
#define NEOPIXEL_PIN 2
#endif #endif
#ifndef LED_STRIP_LENGTH #ifndef NEOPIXEL_LENGTH
#define LED_STRIP_LENGTH 8 #define NEOPIXEL_LENGTH 8
#endif #endif
#ifndef LED_STRIP_TYPE #ifndef NEOPIXEL_BRIGHTNESS
#define LED_STRIP_TYPE (NEO_GRB + NEO_KHZ800) #define NEOPIXEL_BRIGHTNESS 100
#endif
#ifndef NEOPIXEL_UPDATE_INTERVAL
#define NEOPIXEL_UPDATE_INTERVAL 100
#endif #endif
// Create Spore instance with custom labels // Create Spore instance with custom labels
Spore spore({ Spore spore({
{"app", "neopattern"}, {"app", "neopattern"},
{"device", "light"}, {"device", "led_strip"},
{"pixels", String(LED_STRIP_LENGTH)}, {"pixels", String(NEOPIXEL_LENGTH)},
{"pin", String(LED_STRIP_PIN)} {"pin", String(NEOPIXEL_PIN)}
}); });
// Create custom service // Create custom service
@@ -30,8 +36,16 @@ void setup() {
// Initialize the Spore framework // Initialize the Spore framework
spore.setup(); spore.setup();
// Create configuration
NeoPixelConfig config(
NEOPIXEL_PIN,
NEOPIXEL_LENGTH,
NEOPIXEL_BRIGHTNESS,
NEOPIXEL_UPDATE_INTERVAL
);
// Create and add custom service // 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); spore.addService(neoPatternService);
// Start the API server and complete initialization // Start the API server and complete initialization

View File

@@ -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<ParamSpec>{});
api.addEndpoint("/api/neopixel/patterns", HTTP_GET,
[this](AsyncWebServerRequest* request) { handlePatternsRequest(request); },
std::vector<ParamSpec>{});
api.addEndpoint("/api/neopixel", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{
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<JsonArray>();
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<String> NeoPixelService::patternNamesVector() const {
std::vector<String> 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;
}
}

View File

@@ -1,67 +0,0 @@
#pragma once
#include "spore/Service.h"
#include "spore/core/TaskManager.h"
#include <Adafruit_NeoPixel.h>
#include <map>
#include <vector>
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<String> 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<String, std::function<void()>> 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;
};

View File

@@ -1,46 +0,0 @@
#include <Arduino.h>
#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();
}

View File

@@ -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://<device-ip>/`
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.

View File

@@ -1,22 +0,0 @@
#include <Arduino.h>
#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://<node-ip>/ to see the web interface");
}
void loop() {
// Let Spore handle the main loop
spore.loop();
}

View File

@@ -80,69 +80,7 @@ build_src_filter =
+<src/spore/util/*.cpp> +<src/spore/util/*.cpp>
+<src/internal/*.cpp> +<src/internal/*.cpp>
[env:d1_mini_relay] [env: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}
data_dir = examples/relay/data
build_src_filter =
+<examples/relay/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp>
[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 =
+<examples/neopixel/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp>
[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 =
+<examples/neopixel/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp>
[env:esp01_1m_neopattern]
platform = platformio/espressif8266@^4.2.1 platform = platformio/espressif8266@^4.2.1
board = esp01_1m board = esp01_1m
framework = arduino framework = arduino
@@ -162,83 +100,3 @@ build_src_filter =
+<src/spore/types/*.cpp> +<src/spore/types/*.cpp>
+<src/spore/util/*.cpp> +<src/spore/util/*.cpp>
+<src/internal/*.cpp> +<src/internal/*.cpp>
[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 =
+<examples/neopattern/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp>
[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 =
+<examples/static_web/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp>
[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 =
+<examples/cpu_monitor/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp>
[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 =
+<examples/cpu_monitor/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp>