feat: services (#2)
This commit is contained in:
291
examples/neopixel/NeoPixelService.cpp
Normal file
291
examples/neopixel/NeoPixelService.cpp
Normal file
@@ -0,0 +1,291 @@
|
||||
#include "NeoPixelService.h"
|
||||
#include "ApiServer.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;
|
||||
}
|
||||
}
|
||||
67
examples/neopixel/NeoPixelService.h
Normal file
67
examples/neopixel/NeoPixelService.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
#include "services/Service.h"
|
||||
#include "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;
|
||||
};
|
||||
@@ -1,9 +1,5 @@
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
#include "Globals.h"
|
||||
#include "NodeContext.h"
|
||||
#include "NetworkManager.h"
|
||||
@@ -11,6 +7,13 @@
|
||||
#include "ApiServer.h"
|
||||
#include "TaskManager.h"
|
||||
|
||||
// Services
|
||||
#include "services/NodeService.h"
|
||||
#include "services/NetworkService.h"
|
||||
#include "services/ClusterService.h"
|
||||
#include "services/TaskService.h"
|
||||
#include "NeoPixelService.h"
|
||||
|
||||
#ifndef NEOPIXEL_PIN
|
||||
#define NEOPIXEL_PIN 2
|
||||
#endif
|
||||
@@ -23,348 +26,46 @@
|
||||
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800)
|
||||
#endif
|
||||
|
||||
// Wheel helper: map 0-255 to RGB rainbow
|
||||
static uint32_t colorWheel(Adafruit_NeoPixel &strip, uint8_t pos) {
|
||||
pos = 255 - pos;
|
||||
if (pos < 85) {
|
||||
return strip.Color(255 - pos * 3, 0, pos * 3);
|
||||
}
|
||||
if (pos < 170) {
|
||||
pos -= 85;
|
||||
return strip.Color(0, pos * 3, 255 - pos * 3);
|
||||
}
|
||||
pos -= 170;
|
||||
return strip.Color(pos * 3, 255 - pos * 3, 0);
|
||||
}
|
||||
|
||||
class NeoPixelService {
|
||||
public:
|
||||
enum class Pattern {
|
||||
Off,
|
||||
ColorWipe,
|
||||
Rainbow,
|
||||
RainbowCycle,
|
||||
TheaterChase,
|
||||
TheaterChaseRainbow
|
||||
};
|
||||
|
||||
NeoPixelService(NodeContext &ctx, TaskManager &taskMgr,
|
||||
uint16_t numPixels,
|
||||
uint8_t pin,
|
||||
neoPixelType type)
|
||||
: ctx(ctx), taskManager(taskMgr),
|
||||
strip(numPixels, pin, type),
|
||||
currentPattern(Pattern::Off),
|
||||
updateIntervalMs(20),
|
||||
lastUpdateMs(0),
|
||||
wipeIndex(0),
|
||||
wipeColor(strip.Color(255, 0, 0)),
|
||||
rainbowJ(0),
|
||||
cycleJ(0),
|
||||
chaseJ(0),
|
||||
chaseQ(0),
|
||||
chasePhaseOn(true),
|
||||
chaseColor(strip.Color(127, 127, 127)),
|
||||
brightness(50) {
|
||||
strip.begin();
|
||||
strip.setBrightness(brightness);
|
||||
strip.show();
|
||||
|
||||
registerPatterns();
|
||||
registerTasks();
|
||||
}
|
||||
|
||||
void registerApi(ApiServer &api) {
|
||||
api.addEndpoint("/api/neopixel/status", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
JsonDocument doc;
|
||||
doc["pin"] = NEOPIXEL_PIN;
|
||||
doc["count"] = strip.numPixels();
|
||||
doc["interval_ms"] = updateIntervalMs;
|
||||
doc["brightness"] = brightness;
|
||||
doc["pattern"] = currentPatternName();
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(200, "application/json", json);
|
||||
});
|
||||
|
||||
api.addEndpoint("/api/neopixel/patterns", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
JsonDocument doc;
|
||||
JsonArray arr = doc.to<JsonArray>();
|
||||
for (auto &kv : patternUpdaters) arr.add(kv.first);
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(200, "application/json", json);
|
||||
});
|
||||
|
||||
api.addEndpoint("/api/neopixel", HTTP_POST,
|
||||
[this](AsyncWebServerRequest *request) {
|
||||
String pattern = request->hasParam("pattern", true) ? request->getParam("pattern", true)->value() : "";
|
||||
if (pattern.length()) {
|
||||
setPatternByName(pattern);
|
||||
}
|
||||
|
||||
if (request->hasParam("interval_ms", true)) {
|
||||
updateIntervalMs = request->getParam("interval_ms", true)->value().toInt();
|
||||
if (updateIntervalMs < 1) updateIntervalMs = 1;
|
||||
taskManager.setTaskInterval("neopixel_update", updateIntervalMs);
|
||||
}
|
||||
|
||||
if (request->hasParam("brightness", true)) {
|
||||
int b = request->getParam("brightness", true)->value().toInt();
|
||||
if (b < 0) b = 0; if (b > 255) b = 255;
|
||||
setBrightness((uint8_t)b);
|
||||
}
|
||||
|
||||
// Optional RGB for color_wipe and theater_chase
|
||||
if (request->hasParam("r", true) || request->hasParam("g", true) || request->hasParam("b", true)) {
|
||||
int r = request->hasParam("r", true) ? request->getParam("r", true)->value().toInt() : 0;
|
||||
int g = request->hasParam("g", true) ? request->getParam("g", true)->value().toInt() : 0;
|
||||
int b = request->hasParam("b", true) ? request->getParam("b", true)->value().toInt() : 0;
|
||||
wipeColor = strip.Color(r, g, b);
|
||||
chaseColor = strip.Color(r / 2, g / 2, b / 2); // dimmer for chase
|
||||
}
|
||||
|
||||
JsonDocument resp;
|
||||
resp["ok"] = true;
|
||||
resp["pattern"] = currentPatternName();
|
||||
resp["interval_ms"] = updateIntervalMs;
|
||||
resp["brightness"] = brightness;
|
||||
String json;
|
||||
serializeJson(resp, json);
|
||||
request->send(200, "application/json", json);
|
||||
},
|
||||
std::vector<ApiServer::ParamSpec>{
|
||||
ApiServer::ParamSpec{ String("pattern"), false, String("body"), String("string"), patternNamesVector() },
|
||||
ApiServer::ParamSpec{ String("interval_ms"), false, String("body"), String("number"), {} },
|
||||
ApiServer::ParamSpec{ String("brightness"), false, String("body"), String("number"), {} },
|
||||
ApiServer::ParamSpec{ String("r"), false, String("body"), String("number"), {} },
|
||||
ApiServer::ParamSpec{ String("g"), false, String("body"), String("number"), {} },
|
||||
ApiServer::ParamSpec{ String("b"), false, String("body"), String("number"), {} },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void setPatternByName(const String &name) {
|
||||
auto it = patternUpdaters.find(name);
|
||||
if (it != patternUpdaters.end()) {
|
||||
// Map name to enum for status and reset state
|
||||
currentPattern = nameToPattern(name);
|
||||
resetStateForPattern(currentPattern);
|
||||
}
|
||||
}
|
||||
|
||||
void setBrightness(uint8_t b) {
|
||||
brightness = b;
|
||||
strip.setBrightness(brightness);
|
||||
strip.show();
|
||||
}
|
||||
|
||||
private:
|
||||
void registerTasks() {
|
||||
taskManager.registerTask("neopixel_update", updateIntervalMs, [this]() { update(); });
|
||||
taskManager.registerTask("neopixel_status_print", 10000, [this]() {
|
||||
Serial.printf("[NeoPixel] pattern=%s interval=%lu ms brightness=%u\n",
|
||||
currentPatternName().c_str(), updateIntervalMs, brightness);
|
||||
});
|
||||
}
|
||||
|
||||
void registerPatterns() {
|
||||
patternUpdaters["off"] = [this]() { updateOff(); };
|
||||
patternUpdaters["color_wipe"] = [this]() { updateColorWipe(); };
|
||||
patternUpdaters["rainbow"] = [this]() { updateRainbow(); };
|
||||
patternUpdaters["rainbow_cycle"] = [this]() { updateRainbowCycle(); };
|
||||
patternUpdaters["theater_chase"] = [this]() { updateTheaterChase(); };
|
||||
patternUpdaters["theater_chase_rainbow"] = [this]() { updateTheaterChaseRainbow(); };
|
||||
}
|
||||
|
||||
std::vector<String> patternNamesVector() const {
|
||||
std::vector<String> v;
|
||||
v.reserve(patternUpdaters.size());
|
||||
for (const auto &kv : patternUpdaters) v.push_back(kv.first);
|
||||
return v;
|
||||
}
|
||||
|
||||
String currentPatternName() const {
|
||||
switch (currentPattern) {
|
||||
case Pattern::Off: return String("off");
|
||||
case Pattern::ColorWipe: return String("color_wipe");
|
||||
case Pattern::Rainbow: return String("rainbow");
|
||||
case Pattern::RainbowCycle: return String("rainbow_cycle");
|
||||
case Pattern::TheaterChase: return String("theater_chase");
|
||||
case Pattern::TheaterChaseRainbow: return String("theater_chase_rainbow");
|
||||
}
|
||||
return String("off");
|
||||
}
|
||||
|
||||
Pattern nameToPattern(const String &name) const {
|
||||
if (name.equalsIgnoreCase("color_wipe")) return Pattern::ColorWipe;
|
||||
if (name.equalsIgnoreCase("rainbow")) return Pattern::Rainbow;
|
||||
if (name.equalsIgnoreCase("rainbow_cycle")) return Pattern::RainbowCycle;
|
||||
if (name.equalsIgnoreCase("theater_chase")) return Pattern::TheaterChase;
|
||||
if (name.equalsIgnoreCase("theater_chase_rainbow")) return Pattern::TheaterChaseRainbow;
|
||||
return Pattern::Off;
|
||||
}
|
||||
|
||||
void resetStateForPattern(Pattern p) {
|
||||
// Clear strip by default when changing pattern
|
||||
strip.clear();
|
||||
strip.show();
|
||||
|
||||
// Reset indexes/state variables
|
||||
wipeIndex = 0;
|
||||
rainbowJ = 0;
|
||||
cycleJ = 0;
|
||||
chaseJ = 0;
|
||||
chaseQ = 0;
|
||||
chasePhaseOn = true;
|
||||
lastUpdateMs = 0; // force immediate update on next tick
|
||||
|
||||
currentPattern = p;
|
||||
}
|
||||
|
||||
void update() {
|
||||
unsigned long now = millis();
|
||||
if (now - lastUpdateMs < updateIntervalMs) return;
|
||||
lastUpdateMs = now;
|
||||
|
||||
const String name = currentPatternName();
|
||||
auto it = patternUpdaters.find(name);
|
||||
if (it != patternUpdaters.end()) {
|
||||
it->second();
|
||||
} else {
|
||||
updateOff();
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern updaters (non-blocking; advance a small step each call)
|
||||
void updateOff() {
|
||||
// Ensure off state
|
||||
strip.clear();
|
||||
strip.show();
|
||||
}
|
||||
|
||||
void updateColorWipe() {
|
||||
if (wipeIndex < strip.numPixels()) {
|
||||
strip.setPixelColor(wipeIndex, wipeColor);
|
||||
++wipeIndex;
|
||||
strip.show();
|
||||
} else {
|
||||
// Restart
|
||||
strip.clear();
|
||||
wipeIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void updateRainbow() {
|
||||
for (uint16_t i = 0; i < strip.numPixels(); i++) {
|
||||
strip.setPixelColor(i, colorWheel(strip, (i + rainbowJ) & 255));
|
||||
}
|
||||
strip.show();
|
||||
rainbowJ = (rainbowJ + 1) & 0xFF; // 0..255
|
||||
}
|
||||
|
||||
void updateRainbowCycle() {
|
||||
for (uint16_t i = 0; i < strip.numPixels(); i++) {
|
||||
uint8_t pos = ((i * 256 / strip.numPixels()) + cycleJ) & 0xFF;
|
||||
strip.setPixelColor(i, colorWheel(strip, pos));
|
||||
}
|
||||
strip.show();
|
||||
cycleJ = (cycleJ + 1) & 0xFF;
|
||||
}
|
||||
|
||||
void updateTheaterChase() {
|
||||
// Phase toggles on/off for the current q offset
|
||||
if (chasePhaseOn) {
|
||||
for (uint16_t i = 0; i < strip.numPixels(); i += 3) {
|
||||
uint16_t idx = i + chaseQ;
|
||||
if (idx < strip.numPixels()) strip.setPixelColor(idx, chaseColor);
|
||||
}
|
||||
strip.show();
|
||||
chasePhaseOn = false;
|
||||
} else {
|
||||
for (uint16_t i = 0; i < strip.numPixels(); i += 3) {
|
||||
uint16_t idx = i + chaseQ;
|
||||
if (idx < strip.numPixels()) strip.setPixelColor(idx, 0);
|
||||
}
|
||||
strip.show();
|
||||
chasePhaseOn = true;
|
||||
chaseQ = (chaseQ + 1) % 3; // move the crawl
|
||||
chaseJ = (chaseJ + 1) % 10; // cycle count kept for status if needed
|
||||
}
|
||||
}
|
||||
|
||||
void updateTheaterChaseRainbow() {
|
||||
if (chasePhaseOn) {
|
||||
for (uint16_t i = 0; i < strip.numPixels(); i += 3) {
|
||||
uint16_t idx = i + chaseQ;
|
||||
if (idx < strip.numPixels()) strip.setPixelColor(idx, colorWheel(strip, (idx + chaseJ) % 255));
|
||||
}
|
||||
strip.show();
|
||||
chasePhaseOn = false;
|
||||
} else {
|
||||
for (uint16_t i = 0; i < strip.numPixels(); i += 3) {
|
||||
uint16_t idx = i + chaseQ;
|
||||
if (idx < strip.numPixels()) strip.setPixelColor(idx, 0);
|
||||
}
|
||||
strip.show();
|
||||
chasePhaseOn = true;
|
||||
chaseQ = (chaseQ + 1) % 3;
|
||||
chaseJ = (chaseJ + 1) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
NodeContext &ctx;
|
||||
TaskManager &taskManager;
|
||||
Adafruit_NeoPixel strip;
|
||||
|
||||
std::map<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;
|
||||
};
|
||||
|
||||
NodeContext ctx({
|
||||
{"app", "neopixel"},
|
||||
{"device", "light"},
|
||||
{"pixels", String(NEOPIXEL_COUNT)},
|
||||
{"pin", String(NEOPIXEL_PIN)}
|
||||
{"app", "neopixel"},
|
||||
{"device", "light"},
|
||||
{"pixels", String(NEOPIXEL_COUNT)},
|
||||
{"pin", String(NEOPIXEL_PIN)}
|
||||
});
|
||||
NetworkManager network(ctx);
|
||||
TaskManager taskManager(ctx);
|
||||
ClusterManager cluster(ctx, taskManager);
|
||||
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
|
||||
NeoPixelService neoService(ctx, taskManager, NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_TYPE);
|
||||
|
||||
// Create services
|
||||
NodeService nodeService(ctx);
|
||||
NetworkService networkService(network);
|
||||
ClusterService clusterService(ctx);
|
||||
TaskService taskService(taskManager);
|
||||
NeoPixelService neoPixelService(taskManager, NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_TYPE);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.begin(115200);
|
||||
|
||||
network.setupWiFi();
|
||||
// Setup WiFi first
|
||||
network.setupWiFi();
|
||||
|
||||
taskManager.initialize();
|
||||
// Initialize and start all tasks
|
||||
taskManager.initialize();
|
||||
|
||||
apiServer.begin();
|
||||
neoService.registerApi(apiServer);
|
||||
// Register services and start API server
|
||||
apiServer.addService(nodeService);
|
||||
apiServer.addService(networkService);
|
||||
apiServer.addService(clusterService);
|
||||
apiServer.addService(taskService);
|
||||
apiServer.addService(neoPixelService);
|
||||
apiServer.begin();
|
||||
|
||||
taskManager.printTaskStatus();
|
||||
// Print initial task status
|
||||
taskManager.printTaskStatus();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
taskManager.execute();
|
||||
yield();
|
||||
}
|
||||
taskManager.execute();
|
||||
yield();
|
||||
}
|
||||
Reference in New Issue
Block a user