feat: rewrite NeoPattern example
This commit is contained in:
@@ -1,56 +1,90 @@
|
||||
#include "NeoPatternService.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),
|
||||
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<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();
|
||||
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<ParamSpec>{});
|
||||
|
||||
// Patterns list endpoint
|
||||
api.addEndpoint("/api/neopattern/patterns", HTTP_GET,
|
||||
[this](AsyncWebServerRequest* request) { handlePatternsRequest(request); },
|
||||
std::vector<ParamSpec>{});
|
||||
|
||||
// Control endpoint
|
||||
api.addEndpoint("/api/neopattern", 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")}}
|
||||
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<ParamSpec>{});
|
||||
}
|
||||
|
||||
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<JsonArray>();
|
||||
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<uint8_t>(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<uint16_t>(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<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() {
|
||||
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<String> NeoPatternService::patternNamesVector() {
|
||||
if (patternSetters.empty()) registerPatterns();
|
||||
std::vector<String> 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<String> NeoPatternService::patternNamesVector() const {
|
||||
std::vector<String> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user