refactor: implement all core services
This commit is contained in:
7
ctl.sh
7
ctl.sh
@@ -14,8 +14,7 @@ function build {
|
|||||||
pio run -e $1
|
pio run -e $1
|
||||||
}
|
}
|
||||||
function all {
|
function all {
|
||||||
target esp01_1m
|
pio run
|
||||||
target d1_mini
|
|
||||||
}
|
}
|
||||||
${@:-all}
|
${@:-all}
|
||||||
}
|
}
|
||||||
@@ -51,4 +50,8 @@ function cluster {
|
|||||||
${@:-info}
|
${@:-info}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function monitor {
|
||||||
|
pio run --target monitor
|
||||||
|
}
|
||||||
|
|
||||||
${@:-info}
|
${@:-info}
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
#include "ApiServer.h"
|
#include "ApiServer.h"
|
||||||
#include "TaskManager.h"
|
#include "TaskManager.h"
|
||||||
|
|
||||||
|
// Services
|
||||||
|
#include "NodeService.h"
|
||||||
|
#include "NetworkService.h"
|
||||||
|
#include "ClusterService.h"
|
||||||
|
#include "TaskService.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
NodeContext ctx({
|
NodeContext ctx({
|
||||||
@@ -18,6 +24,12 @@ TaskManager taskManager(ctx);
|
|||||||
ClusterManager cluster(ctx, taskManager);
|
ClusterManager cluster(ctx, taskManager);
|
||||||
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
|
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
|
||||||
|
|
||||||
|
// Create services
|
||||||
|
NodeService nodeService(ctx);
|
||||||
|
NetworkService networkService(network);
|
||||||
|
ClusterService clusterService(ctx);
|
||||||
|
TaskService taskService(taskManager);
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
@@ -27,7 +39,11 @@ void setup() {
|
|||||||
// Initialize and start all tasks
|
// Initialize and start all tasks
|
||||||
taskManager.initialize();
|
taskManager.initialize();
|
||||||
|
|
||||||
// Start the API server
|
// Register services and start API server
|
||||||
|
apiServer.addService(nodeService);
|
||||||
|
apiServer.addService(networkService);
|
||||||
|
apiServer.addService(clusterService);
|
||||||
|
apiServer.addService(taskService);
|
||||||
apiServer.begin();
|
apiServer.begin();
|
||||||
|
|
||||||
// Print initial task status
|
// Print initial task status
|
||||||
|
|||||||
187
examples/neopattern/NeoPatternService.cpp
Normal file
187
examples/neopattern/NeoPatternService.cpp
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#include "NeoPatternService.h"
|
||||||
|
#include "ApiServer.h"
|
||||||
|
|
||||||
|
NeoPatternService::NeoPatternService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, uint8_t type)
|
||||||
|
: taskManager(taskMgr),
|
||||||
|
pixels(numPixels, pin, type),
|
||||||
|
updateIntervalMs(100),
|
||||||
|
brightness(48) {
|
||||||
|
pixels.setBrightness(brightness);
|
||||||
|
pixels.show();
|
||||||
|
|
||||||
|
registerTasks();
|
||||||
|
setPatternByName("rainbow_cycle");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoPatternService::registerEndpoints(ApiServer& api) {
|
||||||
|
api.addEndpoint("/api/neopattern/status", HTTP_GET,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
|
||||||
|
api.addEndpoint("/api/neopattern/patterns", HTTP_GET,
|
||||||
|
[this](AsyncWebServerRequest* request) { handlePatternsRequest(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
|
||||||
|
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")}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) {
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["pin"] = pixels.getPin();
|
||||||
|
doc["count"] = pixels.numPixels();
|
||||||
|
doc["interval_ms"] = updateIntervalMs;
|
||||||
|
doc["brightness"] = brightness;
|
||||||
|
doc["pattern"] = currentPatternName();
|
||||||
|
doc["total_steps"] = pixels.TotalSteps;
|
||||||
|
doc["color1"] = pixels.Color1;
|
||||||
|
doc["color2"] = pixels.Color2;
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
request->send(200, "application/json", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) {
|
||||||
|
JsonDocument doc;
|
||||||
|
JsonArray arr = doc.to<JsonArray>();
|
||||||
|
for (auto& kv : patternSetters) arr.add(kv.first);
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
request->send(200, "application/json", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoPatternService::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("neopattern_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)) {
|
||||||
|
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)) {
|
||||||
|
pixels.TotalSteps = request->getParam("total_steps", true)->value().toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 NeoPatternService::setBrightness(uint8_t b) {
|
||||||
|
brightness = b;
|
||||||
|
pixels.setBrightness(brightness);
|
||||||
|
pixels.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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; };
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
return String("off");
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoPatternService::update() {
|
||||||
|
pixels.Update();
|
||||||
|
}
|
||||||
34
examples/neopattern/NeoPatternService.h
Normal file
34
examples/neopattern/NeoPatternService.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Service.h"
|
||||||
|
#include "TaskManager.h"
|
||||||
|
#include "NeoPattern.cpp"
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class NeoPatternService : public Service {
|
||||||
|
public:
|
||||||
|
NeoPatternService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, uint8_t type);
|
||||||
|
void registerEndpoints(ApiServer& api) override;
|
||||||
|
const char* getName() const override { return "NeoPattern"; }
|
||||||
|
|
||||||
|
void setBrightness(uint8_t b);
|
||||||
|
void setPatternByName(const String& name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void registerTasks();
|
||||||
|
void registerPatterns();
|
||||||
|
std::vector<String> patternNamesVector();
|
||||||
|
String currentPatternName();
|
||||||
|
void update();
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
void handleStatusRequest(AsyncWebServerRequest* request);
|
||||||
|
void handlePatternsRequest(AsyncWebServerRequest* request);
|
||||||
|
void handleControlRequest(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
|
TaskManager& taskManager;
|
||||||
|
NeoPattern pixels;
|
||||||
|
unsigned long updateIntervalMs;
|
||||||
|
uint8_t brightness;
|
||||||
|
std::map<String, std::function<void()>> patternSetters;
|
||||||
|
};
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "NodeContext.h"
|
#include "NodeContext.h"
|
||||||
#include "NetworkManager.h"
|
#include "NetworkManager.h"
|
||||||
@@ -10,8 +7,12 @@
|
|||||||
#include "ApiServer.h"
|
#include "ApiServer.h"
|
||||||
#include "TaskManager.h"
|
#include "TaskManager.h"
|
||||||
|
|
||||||
// Include the NeoPattern implementation from this example folder
|
// Services
|
||||||
#include "NeoPattern.cpp"
|
#include "NodeService.h"
|
||||||
|
#include "NetworkService.h"
|
||||||
|
#include "ClusterService.h"
|
||||||
|
#include "TaskService.h"
|
||||||
|
#include "NeoPatternService.h"
|
||||||
|
|
||||||
#ifndef LED_STRIP_PIN
|
#ifndef LED_STRIP_PIN
|
||||||
#define LED_STRIP_PIN 2
|
#define LED_STRIP_PIN 2
|
||||||
@@ -21,206 +22,10 @@
|
|||||||
#define LED_STRIP_LENGTH 8
|
#define LED_STRIP_LENGTH 8
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef LED_STRIP_BRIGHTNESS
|
|
||||||
#define LED_STRIP_BRIGHTNESS 48
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef LED_STRIP_UPDATE_INTERVAL
|
|
||||||
#define LED_STRIP_UPDATE_INTERVAL 100
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef LED_STRIP_TYPE
|
#ifndef LED_STRIP_TYPE
|
||||||
#define LED_STRIP_TYPE (NEO_GRB + NEO_KHZ800)
|
#define LED_STRIP_TYPE (NEO_GRB + NEO_KHZ800)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class NeoPatternService {
|
|
||||||
public:
|
|
||||||
NeoPatternService(NodeContext &ctx, TaskManager &taskMgr,
|
|
||||||
uint16_t numPixels,
|
|
||||||
uint8_t pin,
|
|
||||||
uint8_t type)
|
|
||||||
: ctx(ctx), taskManager(taskMgr),
|
|
||||||
pixels(numPixels, pin, type),
|
|
||||||
updateIntervalMs(LED_STRIP_UPDATE_INTERVAL),
|
|
||||||
brightness(LED_STRIP_BRIGHTNESS) {
|
|
||||||
pixels.setBrightness(brightness);
|
|
||||||
pixels.show();
|
|
||||||
|
|
||||||
registerTasks();
|
|
||||||
setPatternByName("rainbow_cycle");
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerApi(ApiServer &api) {
|
|
||||||
api.addEndpoint("/api/neopattern/status", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
|
||||||
JsonDocument doc;
|
|
||||||
doc["pin"] = LED_STRIP_PIN;
|
|
||||||
doc["count"] = pixels.numPixels();
|
|
||||||
doc["interval_ms"] = updateIntervalMs;
|
|
||||||
doc["brightness"] = brightness;
|
|
||||||
doc["pattern"] = currentPatternName();
|
|
||||||
doc["total_steps"] = pixels.TotalSteps;
|
|
||||||
doc["color1"] = pixels.Color1;
|
|
||||||
doc["color2"] = pixels.Color2;
|
|
||||||
String json;
|
|
||||||
serializeJson(doc, json);
|
|
||||||
request->send(200, "application/json", json);
|
|
||||||
});
|
|
||||||
|
|
||||||
api.addEndpoint("/api/neopattern/patterns", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
|
||||||
JsonDocument doc;
|
|
||||||
JsonArray arr = doc.to<JsonArray>();
|
|
||||||
for (auto &kv : patternSetters) arr.add(kv.first);
|
|
||||||
String json;
|
|
||||||
serializeJson(doc, json);
|
|
||||||
request->send(200, "application/json", json);
|
|
||||||
});
|
|
||||||
|
|
||||||
api.addEndpoint("/api/neopattern", HTTP_POST,
|
|
||||||
[this](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("neopattern_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)) {
|
|
||||||
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)) {
|
|
||||||
pixels.TotalSteps = request->getParam("total_steps", true)->value().toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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"), {}, String("100") },
|
|
||||||
ApiServer::ParamSpec{ String("brightness"), false, String("body"), String("number"), {}, String("50") },
|
|
||||||
ApiServer::ParamSpec{ String("color"), false, String("body"), String("color"), {} },
|
|
||||||
ApiServer::ParamSpec{ String("color2"), false, String("body"), String("color"), {} },
|
|
||||||
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"), {} },
|
|
||||||
ApiServer::ParamSpec{ String("r2"), false, String("body"), String("number"), {} },
|
|
||||||
ApiServer::ParamSpec{ String("g2"), false, String("body"), String("number"), {} },
|
|
||||||
ApiServer::ParamSpec{ String("b2"), false, String("body"), String("number"), {} },
|
|
||||||
ApiServer::ParamSpec{ String("total_steps"), false, String("body"), String("number"), {} },
|
|
||||||
ApiServer::ParamSpec{ String("direction"), false, String("body"), String("string"), { String("forward"), String("reverse") } },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBrightness(uint8_t b) {
|
|
||||||
brightness = b;
|
|
||||||
pixels.setBrightness(brightness);
|
|
||||||
pixels.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void 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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void 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; };
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<String> 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 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");
|
|
||||||
}
|
|
||||||
return String("off");
|
|
||||||
}
|
|
||||||
|
|
||||||
void 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void update() {
|
|
||||||
pixels.Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
NodeContext &ctx;
|
|
||||||
TaskManager &taskManager;
|
|
||||||
NeoPattern pixels;
|
|
||||||
unsigned long updateIntervalMs;
|
|
||||||
uint8_t brightness;
|
|
||||||
std::map<String, std::function<void()>> patternSetters;
|
|
||||||
};
|
|
||||||
|
|
||||||
NodeContext ctx({
|
NodeContext ctx({
|
||||||
{"app", "neopattern"},
|
{"app", "neopattern"},
|
||||||
{"device", "light"},
|
{"device", "light"},
|
||||||
@@ -231,18 +36,32 @@ NetworkManager network(ctx);
|
|||||||
TaskManager taskManager(ctx);
|
TaskManager taskManager(ctx);
|
||||||
ClusterManager cluster(ctx, taskManager);
|
ClusterManager cluster(ctx, taskManager);
|
||||||
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
|
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
|
||||||
NeoPatternService neoService(ctx, taskManager, LED_STRIP_LENGTH, LED_STRIP_PIN, LED_STRIP_TYPE);
|
|
||||||
|
// Create services
|
||||||
|
NodeService nodeService(ctx);
|
||||||
|
NetworkService networkService(network);
|
||||||
|
ClusterService clusterService(ctx);
|
||||||
|
TaskService taskService(taskManager);
|
||||||
|
NeoPatternService neoPatternService(taskManager, LED_STRIP_LENGTH, LED_STRIP_PIN, LED_STRIP_TYPE);
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
// Setup WiFi first
|
||||||
network.setupWiFi();
|
network.setupWiFi();
|
||||||
|
|
||||||
|
// Initialize and start all tasks
|
||||||
taskManager.initialize();
|
taskManager.initialize();
|
||||||
|
|
||||||
|
// Register services and start API server
|
||||||
|
apiServer.addService(nodeService);
|
||||||
|
apiServer.addService(networkService);
|
||||||
|
apiServer.addService(clusterService);
|
||||||
|
apiServer.addService(taskService);
|
||||||
|
apiServer.addService(neoPatternService);
|
||||||
apiServer.begin();
|
apiServer.begin();
|
||||||
neoService.registerApi(apiServer);
|
|
||||||
|
|
||||||
|
// Print initial task status
|
||||||
taskManager.printTaskStatus();
|
taskManager.printTaskStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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 "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 <Arduino.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
|
||||||
#include <vector>
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
|
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "NodeContext.h"
|
#include "NodeContext.h"
|
||||||
#include "NetworkManager.h"
|
#include "NetworkManager.h"
|
||||||
@@ -11,6 +7,13 @@
|
|||||||
#include "ApiServer.h"
|
#include "ApiServer.h"
|
||||||
#include "TaskManager.h"
|
#include "TaskManager.h"
|
||||||
|
|
||||||
|
// Services
|
||||||
|
#include "NodeService.h"
|
||||||
|
#include "NetworkService.h"
|
||||||
|
#include "ClusterService.h"
|
||||||
|
#include "TaskService.h"
|
||||||
|
#include "NeoPixelService.h"
|
||||||
|
|
||||||
#ifndef NEOPIXEL_PIN
|
#ifndef NEOPIXEL_PIN
|
||||||
#define NEOPIXEL_PIN 2
|
#define NEOPIXEL_PIN 2
|
||||||
#endif
|
#endif
|
||||||
@@ -23,322 +26,6 @@
|
|||||||
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800)
|
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800)
|
||||||
#endif
|
#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"), {}, String("100") },
|
|
||||||
ApiServer::ParamSpec{ String("brightness"), false, String("body"), String("number"), {}, String("50") },
|
|
||||||
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({
|
NodeContext ctx({
|
||||||
{"app", "neopixel"},
|
{"app", "neopixel"},
|
||||||
{"device", "light"},
|
{"device", "light"},
|
||||||
@@ -349,18 +36,32 @@ NetworkManager network(ctx);
|
|||||||
TaskManager taskManager(ctx);
|
TaskManager taskManager(ctx);
|
||||||
ClusterManager cluster(ctx, taskManager);
|
ClusterManager cluster(ctx, taskManager);
|
||||||
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
|
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() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
// Setup WiFi first
|
||||||
network.setupWiFi();
|
network.setupWiFi();
|
||||||
|
|
||||||
|
// Initialize and start all tasks
|
||||||
taskManager.initialize();
|
taskManager.initialize();
|
||||||
|
|
||||||
|
// Register services and start API server
|
||||||
|
apiServer.addService(nodeService);
|
||||||
|
apiServer.addService(networkService);
|
||||||
|
apiServer.addService(clusterService);
|
||||||
|
apiServer.addService(taskService);
|
||||||
|
apiServer.addService(neoPixelService);
|
||||||
apiServer.begin();
|
apiServer.begin();
|
||||||
neoService.registerApi(apiServer);
|
|
||||||
|
|
||||||
|
// Print initial task status
|
||||||
taskManager.printTaskStatus();
|
taskManager.printTaskStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
89
examples/relay/RelayService.cpp
Normal file
89
examples/relay/RelayService.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#include "RelayService.h"
|
||||||
|
#include "ApiServer.h"
|
||||||
|
|
||||||
|
RelayService::RelayService(TaskManager& taskMgr, int pin)
|
||||||
|
: taskManager(taskMgr), relayPin(pin), relayOn(false) {
|
||||||
|
pinMode(relayPin, OUTPUT);
|
||||||
|
// Many relay modules are active LOW. Start in OFF state (relay de-energized).
|
||||||
|
digitalWrite(relayPin, HIGH);
|
||||||
|
registerTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RelayService::registerEndpoints(ApiServer& api) {
|
||||||
|
api.addEndpoint("/api/relay/status", HTTP_GET,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
|
||||||
|
api.addEndpoint("/api/relay", HTTP_POST,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleControlRequest(request); },
|
||||||
|
std::vector<ParamSpec>{
|
||||||
|
ParamSpec{String("state"), true, String("body"), String("string"),
|
||||||
|
{String("on"), String("off"), String("toggle")}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RelayService::handleStatusRequest(AsyncWebServerRequest* request) {
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["pin"] = relayPin;
|
||||||
|
doc["state"] = relayOn ? "on" : "off";
|
||||||
|
doc["uptime"] = millis();
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
request->send(200, "application/json", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RelayService::handleControlRequest(AsyncWebServerRequest* request) {
|
||||||
|
String state = request->hasParam("state", true) ? request->getParam("state", true)->value() : "";
|
||||||
|
bool ok = false;
|
||||||
|
|
||||||
|
if (state.equalsIgnoreCase("on")) {
|
||||||
|
turnOn();
|
||||||
|
ok = true;
|
||||||
|
} else if (state.equalsIgnoreCase("off")) {
|
||||||
|
turnOff();
|
||||||
|
ok = true;
|
||||||
|
} else if (state.equalsIgnoreCase("toggle")) {
|
||||||
|
toggle();
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDocument resp;
|
||||||
|
resp["success"] = ok;
|
||||||
|
resp["state"] = relayOn ? "on" : "off";
|
||||||
|
if (!ok) {
|
||||||
|
resp["message"] = "Invalid state. Use: on, off, or toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(resp, json);
|
||||||
|
request->send(ok ? 200 : 400, "application/json", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RelayService::turnOn() {
|
||||||
|
relayOn = true;
|
||||||
|
// Active LOW relay
|
||||||
|
digitalWrite(relayPin, LOW);
|
||||||
|
Serial.println("[RelayService] Relay ON");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RelayService::turnOff() {
|
||||||
|
relayOn = false;
|
||||||
|
digitalWrite(relayPin, HIGH);
|
||||||
|
Serial.println("[RelayService] Relay OFF");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RelayService::toggle() {
|
||||||
|
if (relayOn) {
|
||||||
|
turnOff();
|
||||||
|
} else {
|
||||||
|
turnOn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RelayService::registerTasks() {
|
||||||
|
taskManager.registerTask("relay_status_print", 5000, [this]() {
|
||||||
|
Serial.printf("[RelayService] Status - pin: %d, state: %s\n",
|
||||||
|
relayPin, relayOn ? "ON" : "OFF");
|
||||||
|
});
|
||||||
|
}
|
||||||
25
examples/relay/RelayService.h
Normal file
25
examples/relay/RelayService.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Service.h"
|
||||||
|
#include "TaskManager.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
class RelayService : public Service {
|
||||||
|
public:
|
||||||
|
RelayService(TaskManager& taskMgr, int pin);
|
||||||
|
void registerEndpoints(ApiServer& api) override;
|
||||||
|
const char* getName() const override { return "Relay"; }
|
||||||
|
|
||||||
|
void turnOn();
|
||||||
|
void turnOff();
|
||||||
|
void toggle();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void registerTasks();
|
||||||
|
|
||||||
|
TaskManager& taskManager;
|
||||||
|
int relayPin;
|
||||||
|
bool relayOn;
|
||||||
|
|
||||||
|
void handleStatusRequest(AsyncWebServerRequest* request);
|
||||||
|
void handleControlRequest(AsyncWebServerRequest* request);
|
||||||
|
};
|
||||||
@@ -7,6 +7,13 @@
|
|||||||
#include "ApiServer.h"
|
#include "ApiServer.h"
|
||||||
#include "TaskManager.h"
|
#include "TaskManager.h"
|
||||||
|
|
||||||
|
// Services
|
||||||
|
#include "NodeService.h"
|
||||||
|
#include "NetworkService.h"
|
||||||
|
#include "ClusterService.h"
|
||||||
|
#include "TaskService.h"
|
||||||
|
#include "RelayService.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
// Choose a default relay pin. For ESP-01 this is GPIO0. Adjust as needed for your board.
|
// Choose a default relay pin. For ESP-01 this is GPIO0. Adjust as needed for your board.
|
||||||
@@ -14,88 +21,6 @@ using namespace std;
|
|||||||
#define RELAY_PIN 0
|
#define RELAY_PIN 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class RelayService {
|
|
||||||
public:
|
|
||||||
RelayService(NodeContext& ctx, TaskManager& taskMgr, int pin)
|
|
||||||
: ctx(ctx), taskManager(taskMgr), relayPin(pin), relayOn(false) {
|
|
||||||
pinMode(relayPin, OUTPUT);
|
|
||||||
// Many relay modules are active LOW. Start in OFF state (relay de-energized).
|
|
||||||
digitalWrite(relayPin, HIGH);
|
|
||||||
|
|
||||||
registerTasks();
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerApi(ApiServer& api) {
|
|
||||||
api.addEndpoint("/api/relay/status", HTTP_GET, [this](AsyncWebServerRequest* request) {
|
|
||||||
JsonDocument doc;
|
|
||||||
doc["pin"] = relayPin;
|
|
||||||
doc["state"] = relayOn ? "on" : "off";
|
|
||||||
doc["uptime"] = millis();
|
|
||||||
String json;
|
|
||||||
serializeJson(doc, json);
|
|
||||||
request->send(200, "application/json", json);
|
|
||||||
});
|
|
||||||
|
|
||||||
api.addEndpoint("/api/relay", HTTP_POST, [this](AsyncWebServerRequest* request) {
|
|
||||||
String state = request->hasParam("state", true) ? request->getParam("state", true)->value() : "";
|
|
||||||
bool ok = false;
|
|
||||||
if (state.equalsIgnoreCase("on")) {
|
|
||||||
turnOn();
|
|
||||||
ok = true;
|
|
||||||
} else if (state.equalsIgnoreCase("off")) {
|
|
||||||
turnOff();
|
|
||||||
ok = true;
|
|
||||||
} else if (state.equalsIgnoreCase("toggle")) {
|
|
||||||
toggle();
|
|
||||||
ok = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonDocument resp;
|
|
||||||
resp["success"] = ok;
|
|
||||||
resp["state"] = relayOn ? "on" : "off";
|
|
||||||
if (!ok) {
|
|
||||||
resp["message"] = "Invalid state. Use: on, off, or toggle";
|
|
||||||
}
|
|
||||||
String json;
|
|
||||||
serializeJson(resp, json);
|
|
||||||
request->send(ok ? 200 : 400, "application/json", json);
|
|
||||||
}, std::vector<ApiServer::ParamSpec>{ ApiServer::ParamSpec{ String("state"), true, String("body"), String("string"), { String("on"), String("off"), String("toggle") } } });
|
|
||||||
}
|
|
||||||
|
|
||||||
void turnOn() {
|
|
||||||
relayOn = true;
|
|
||||||
// Active LOW relay
|
|
||||||
digitalWrite(relayPin, LOW);
|
|
||||||
Serial.println("[RelayService] Relay ON");
|
|
||||||
}
|
|
||||||
|
|
||||||
void turnOff() {
|
|
||||||
relayOn = false;
|
|
||||||
digitalWrite(relayPin, HIGH);
|
|
||||||
Serial.println("[RelayService] Relay OFF");
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggle() {
|
|
||||||
if (relayOn) {
|
|
||||||
turnOff();
|
|
||||||
} else {
|
|
||||||
turnOn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void registerTasks() {
|
|
||||||
taskManager.registerTask("relay_status_print", 5000, [this]() {
|
|
||||||
Serial.printf("[RelayService] Status - pin: %d, state: %s\n", relayPin, relayOn ? "ON" : "OFF");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeContext& ctx;
|
|
||||||
TaskManager& taskManager;
|
|
||||||
int relayPin;
|
|
||||||
bool relayOn;
|
|
||||||
};
|
|
||||||
|
|
||||||
NodeContext ctx({
|
NodeContext ctx({
|
||||||
{"app", "relay"},
|
{"app", "relay"},
|
||||||
{"device", "actuator"},
|
{"device", "actuator"},
|
||||||
@@ -105,7 +30,13 @@ NetworkManager network(ctx);
|
|||||||
TaskManager taskManager(ctx);
|
TaskManager taskManager(ctx);
|
||||||
ClusterManager cluster(ctx, taskManager);
|
ClusterManager cluster(ctx, taskManager);
|
||||||
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
|
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
|
||||||
RelayService relayService(ctx, taskManager, RELAY_PIN);
|
|
||||||
|
// Create services
|
||||||
|
NodeService nodeService(ctx);
|
||||||
|
NetworkService networkService(network);
|
||||||
|
ClusterService clusterService(ctx);
|
||||||
|
TaskService taskService(taskManager);
|
||||||
|
RelayService relayService(taskManager, RELAY_PIN);
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
@@ -116,9 +47,13 @@ void setup() {
|
|||||||
// Initialize and start all tasks
|
// Initialize and start all tasks
|
||||||
taskManager.initialize();
|
taskManager.initialize();
|
||||||
|
|
||||||
// Start the API server and expose relay endpoints
|
// Register services and start API server
|
||||||
|
apiServer.addService(nodeService);
|
||||||
|
apiServer.addService(networkService);
|
||||||
|
apiServer.addService(clusterService);
|
||||||
|
apiServer.addService(taskService);
|
||||||
|
apiServer.addService(relayService);
|
||||||
apiServer.begin();
|
apiServer.begin();
|
||||||
relayService.registerApi(apiServer);
|
|
||||||
|
|
||||||
// Print initial task status
|
// Print initial task status
|
||||||
taskManager.printTaskStatus();
|
taskManager.printTaskStatus();
|
||||||
|
|||||||
@@ -9,9 +9,12 @@
|
|||||||
#include "NodeService.h"
|
#include "NodeService.h"
|
||||||
#include "ClusterService.h"
|
#include "ClusterService.h"
|
||||||
#include "TaskService.h"
|
#include "TaskService.h"
|
||||||
#include "WifiScanService.h"
|
#include "NetworkService.h"
|
||||||
|
|
||||||
NodeContext ctx;
|
NodeContext ctx({
|
||||||
|
{"app", "network"},
|
||||||
|
{"role", "demo"}
|
||||||
|
});
|
||||||
TaskManager taskManager(ctx);
|
TaskManager taskManager(ctx);
|
||||||
NetworkManager networkManager(ctx);
|
NetworkManager networkManager(ctx);
|
||||||
ApiServer apiServer(ctx, taskManager);
|
ApiServer apiServer(ctx, taskManager);
|
||||||
@@ -21,32 +24,24 @@ ClusterManager cluster(ctx, taskManager);
|
|||||||
NodeService nodeService(ctx);
|
NodeService nodeService(ctx);
|
||||||
ClusterService clusterService(ctx);
|
ClusterService clusterService(ctx);
|
||||||
TaskService taskService(taskManager);
|
TaskService taskService(taskManager);
|
||||||
WifiScanService wifiScanService(networkManager);
|
NetworkService networkService(networkManager);
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
Serial.println("\n[WiFiScan] Starting...");
|
|
||||||
|
|
||||||
// Initialize networking
|
// Initialize networking
|
||||||
networkManager.setupWiFi();
|
networkManager.setupWiFi();
|
||||||
|
|
||||||
taskManager.initialize();
|
taskManager.initialize();
|
||||||
|
|
||||||
// Add all services to API server
|
// Setup API
|
||||||
apiServer.addService(nodeService);
|
apiServer.addService(nodeService);
|
||||||
apiServer.addService(clusterService);
|
apiServer.addService(clusterService);
|
||||||
apiServer.addService(taskService);
|
apiServer.addService(taskService);
|
||||||
apiServer.addService(wifiScanService);
|
apiServer.addService(networkService);
|
||||||
|
|
||||||
// Start API server
|
|
||||||
apiServer.begin();
|
apiServer.begin();
|
||||||
|
|
||||||
taskManager.printTaskStatus();
|
taskManager.printTaskStatus();
|
||||||
|
|
||||||
Serial.println("[WiFiScan] Ready!");
|
|
||||||
Serial.println("[WiFiScan] Available endpoints:");
|
|
||||||
Serial.println(" POST /api/wifi/scan - Start WiFi scan");
|
|
||||||
Serial.println(" GET /api/wifi - Get scan results");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
|||||||
@@ -23,6 +23,25 @@ public:
|
|||||||
void processAccessPoints();
|
void processAccessPoints();
|
||||||
std::vector<AccessPoint> getAccessPoints() const;
|
std::vector<AccessPoint> getAccessPoints() const;
|
||||||
|
|
||||||
|
// WiFi configuration methods
|
||||||
|
void setWiFiConfig(const String& ssid, const String& password,
|
||||||
|
uint32_t connect_timeout_ms = 10000,
|
||||||
|
uint32_t retry_delay_ms = 500);
|
||||||
|
|
||||||
|
// Network status methods
|
||||||
|
bool isConnected() const { return WiFi.isConnected(); }
|
||||||
|
String getSSID() const { return WiFi.SSID(); }
|
||||||
|
IPAddress getLocalIP() const { return WiFi.localIP(); }
|
||||||
|
String getMacAddress() const { return WiFi.macAddress(); }
|
||||||
|
String getHostname() const { return WiFi.hostname(); }
|
||||||
|
int32_t getRSSI() const { return WiFi.RSSI(); }
|
||||||
|
WiFiMode_t getMode() const { return WiFi.getMode(); }
|
||||||
|
|
||||||
|
// AP mode specific methods
|
||||||
|
IPAddress getAPIP() const { return WiFi.softAPIP(); }
|
||||||
|
String getAPMacAddress() const { return WiFi.softAPmacAddress(); }
|
||||||
|
uint8_t getConnectedStations() const { return WiFi.softAPgetStationNum(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NodeContext& ctx;
|
NodeContext& ctx;
|
||||||
std::vector<AccessPoint> accessPoints;
|
std::vector<AccessPoint> accessPoints;
|
||||||
|
|||||||
22
include/NetworkService.h
Normal file
22
include/NetworkService.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Service.h"
|
||||||
|
#include "NetworkManager.h"
|
||||||
|
#include "NodeContext.h"
|
||||||
|
|
||||||
|
class NetworkService : public Service {
|
||||||
|
public:
|
||||||
|
NetworkService(NetworkManager& networkManager);
|
||||||
|
void registerEndpoints(ApiServer& api) override;
|
||||||
|
const char* getName() const override { return "Network"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
NetworkManager& networkManager;
|
||||||
|
|
||||||
|
// WiFi scanning endpoints
|
||||||
|
void handleWifiScanRequest(AsyncWebServerRequest* request);
|
||||||
|
void handleGetWifiNetworks(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
|
// Network status endpoints
|
||||||
|
void handleNetworkStatus(AsyncWebServerRequest* request);
|
||||||
|
void handleSetWifiConfig(AsyncWebServerRequest* request);
|
||||||
|
};
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Service.h"
|
|
||||||
#include "NetworkManager.h"
|
|
||||||
#include "NodeContext.h"
|
|
||||||
|
|
||||||
class WifiScanService : public Service {
|
|
||||||
public:
|
|
||||||
WifiScanService(NetworkManager& networkManager);
|
|
||||||
void registerEndpoints(ApiServer& api) override;
|
|
||||||
const char* getName() const override { return "WifiScan"; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
NetworkManager& networkManager;
|
|
||||||
|
|
||||||
void handleScanRequest(AsyncWebServerRequest* request);
|
|
||||||
void handleGetAccessPoints(AsyncWebServerRequest* request);
|
|
||||||
};
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[platformio]
|
[platformio]
|
||||||
default_envs = esp01_1m
|
;default_envs = esp01_1m
|
||||||
src_dir = .
|
src_dir = .
|
||||||
|
|
||||||
[common]
|
[common]
|
||||||
@@ -106,7 +106,7 @@ lib_deps = ${common.lib_deps}
|
|||||||
adafruit/Adafruit NeoPixel@^1.15.1
|
adafruit/Adafruit NeoPixel@^1.15.1
|
||||||
build_flags = -DLED_STRIP_PIN=2
|
build_flags = -DLED_STRIP_PIN=2
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
+<examples/neopattern/main.cpp>
|
+<examples/neopattern/*.cpp>
|
||||||
+<src/*.c>
|
+<src/*.c>
|
||||||
+<src/*.cpp>
|
+<src/*.cpp>
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ board_build.flash_mode = dout
|
|||||||
board_build.flash_size = 1M
|
board_build.flash_size = 1M
|
||||||
lib_deps = ${common.lib_deps}
|
lib_deps = ${common.lib_deps}
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
+<examples/wifiscan/main.cpp>
|
+<examples/wifiscan/*.cpp>
|
||||||
+<src/*.c>
|
+<src/*.c>
|
||||||
+<src/*.cpp>
|
+<src/*.cpp>
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ board_build.flash_mode = dio
|
|||||||
board_build.flash_size = 4M
|
board_build.flash_size = 4M
|
||||||
lib_deps = ${common.lib_deps}
|
lib_deps = ${common.lib_deps}
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
+<examples/wifiscan/main.cpp>
|
+<examples/wifiscan/*.cpp>
|
||||||
+<src/*.c>
|
+<src/*.c>
|
||||||
+<src/*.cpp>
|
+<src/*.cpp>
|
||||||
|
|
||||||
@@ -150,6 +150,6 @@ board_build.flash_size = 4M
|
|||||||
lib_deps = ${common.lib_deps}
|
lib_deps = ${common.lib_deps}
|
||||||
adafruit/Adafruit NeoPixel@^1.15.1
|
adafruit/Adafruit NeoPixel@^1.15.1
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
+<examples/neopattern/main.cpp>
|
+<examples/neopattern/*.cpp>
|
||||||
+<src/*.c>
|
+<src/*.c>
|
||||||
+<src/*.cpp>
|
+<src/*.cpp>
|
||||||
|
|||||||
@@ -57,6 +57,14 @@ std::vector<AccessPoint> NetworkManager::getAccessPoints() const {
|
|||||||
|
|
||||||
NetworkManager::NetworkManager(NodeContext& ctx) : ctx(ctx) {}
|
NetworkManager::NetworkManager(NodeContext& ctx) : ctx(ctx) {}
|
||||||
|
|
||||||
|
void NetworkManager::setWiFiConfig(const String& ssid, const String& password,
|
||||||
|
uint32_t connect_timeout_ms, uint32_t retry_delay_ms) {
|
||||||
|
ctx.config.wifi_ssid = ssid;
|
||||||
|
ctx.config.wifi_password = password;
|
||||||
|
ctx.config.wifi_connect_timeout_ms = connect_timeout_ms;
|
||||||
|
ctx.config.wifi_retry_delay_ms = retry_delay_ms;
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkManager::setHostnameFromMac() {
|
void NetworkManager::setHostnameFromMac() {
|
||||||
uint8_t mac[6];
|
uint8_t mac[6];
|
||||||
WiFi.macAddress(mac);
|
WiFi.macAddress(mac);
|
||||||
|
|||||||
132
src/NetworkService.cpp
Normal file
132
src/NetworkService.cpp
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#include "NetworkService.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
NetworkService::NetworkService(NetworkManager& networkManager)
|
||||||
|
: networkManager(networkManager) {}
|
||||||
|
|
||||||
|
void NetworkService::registerEndpoints(ApiServer& api) {
|
||||||
|
// WiFi scanning endpoints
|
||||||
|
api.addEndpoint("/api/network/wifi/scan", HTTP_POST,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleWifiScanRequest(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
|
||||||
|
api.addEndpoint("/api/network/wifi/networks", HTTP_GET,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleGetWifiNetworks(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
|
||||||
|
// Network status and configuration endpoints
|
||||||
|
api.addEndpoint("/api/network/status", HTTP_GET,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleNetworkStatus(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
|
||||||
|
api.addEndpoint("/api/network/wifi/config", HTTP_POST,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleSetWifiConfig(request); },
|
||||||
|
std::vector<ParamSpec>{
|
||||||
|
ParamSpec{String("ssid"), true, String("body"), String("string"), {}, String("")},
|
||||||
|
ParamSpec{String("password"), true, String("body"), String("string"), {}, String("")},
|
||||||
|
ParamSpec{String("connect_timeout_ms"), false, String("body"), String("number"), {}, String("10000")},
|
||||||
|
ParamSpec{String("retry_delay_ms"), false, String("body"), String("number"), {}, String("500")}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkService::handleWifiScanRequest(AsyncWebServerRequest* request) {
|
||||||
|
networkManager.scanWifi();
|
||||||
|
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["status"] = "scanning";
|
||||||
|
doc["message"] = "WiFi scan started";
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
request->send(200, "application/json", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkService::handleGetWifiNetworks(AsyncWebServerRequest* request) {
|
||||||
|
auto accessPoints = networkManager.getAccessPoints();
|
||||||
|
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["access_points"].to<JsonArray>();
|
||||||
|
|
||||||
|
for (const auto& ap : accessPoints) {
|
||||||
|
JsonObject apObj = doc["access_points"].add<JsonObject>();
|
||||||
|
apObj["ssid"] = ap.ssid;
|
||||||
|
apObj["rssi"] = ap.rssi;
|
||||||
|
apObj["channel"] = ap.channel;
|
||||||
|
apObj["encryption_type"] = ap.encryptionType;
|
||||||
|
apObj["hidden"] = ap.isHidden;
|
||||||
|
|
||||||
|
// Convert BSSID to string
|
||||||
|
char bssid[18];
|
||||||
|
sprintf(bssid, "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||||
|
ap.bssid[0], ap.bssid[1], ap.bssid[2],
|
||||||
|
ap.bssid[3], ap.bssid[4], ap.bssid[5]);
|
||||||
|
apObj["bssid"] = bssid;
|
||||||
|
}
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
request->send(200, "application/json", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkService::handleNetworkStatus(AsyncWebServerRequest* request) {
|
||||||
|
JsonDocument doc;
|
||||||
|
|
||||||
|
// WiFi status
|
||||||
|
doc["wifi"]["connected"] = networkManager.isConnected();
|
||||||
|
doc["wifi"]["mode"] = networkManager.getMode() == WIFI_AP ? "AP" : "STA";
|
||||||
|
doc["wifi"]["ssid"] = networkManager.getSSID();
|
||||||
|
doc["wifi"]["ip"] = networkManager.getLocalIP().toString();
|
||||||
|
doc["wifi"]["mac"] = networkManager.getMacAddress();
|
||||||
|
doc["wifi"]["hostname"] = networkManager.getHostname();
|
||||||
|
doc["wifi"]["rssi"] = networkManager.getRSSI();
|
||||||
|
|
||||||
|
// If in AP mode, add AP specific info
|
||||||
|
if (networkManager.getMode() == WIFI_AP) {
|
||||||
|
doc["wifi"]["ap_ip"] = networkManager.getAPIP().toString();
|
||||||
|
doc["wifi"]["ap_mac"] = networkManager.getAPMacAddress();
|
||||||
|
doc["wifi"]["stations_connected"] = networkManager.getConnectedStations();
|
||||||
|
}
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
request->send(200, "application/json", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkService::handleSetWifiConfig(AsyncWebServerRequest* request) {
|
||||||
|
if (!request->hasParam("ssid", true) || !request->hasParam("password", true)) {
|
||||||
|
request->send(400, "application/json", "{\"error\": \"Missing required parameters\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ssid = request->getParam("ssid", true)->value();
|
||||||
|
String password = request->getParam("password", true)->value();
|
||||||
|
|
||||||
|
// Optional parameters with defaults
|
||||||
|
uint32_t connect_timeout_ms = 10000; // Default 10 seconds
|
||||||
|
uint32_t retry_delay_ms = 500; // Default 500ms
|
||||||
|
|
||||||
|
if (request->hasParam("connect_timeout_ms", true)) {
|
||||||
|
connect_timeout_ms = request->getParam("connect_timeout_ms", true)->value().toInt();
|
||||||
|
}
|
||||||
|
if (request->hasParam("retry_delay_ms", true)) {
|
||||||
|
retry_delay_ms = request->getParam("retry_delay_ms", true)->value().toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update configuration
|
||||||
|
networkManager.setWiFiConfig(ssid, password, connect_timeout_ms, retry_delay_ms);
|
||||||
|
|
||||||
|
// Attempt to connect with new settings
|
||||||
|
networkManager.setupWiFi();
|
||||||
|
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["status"] = "success";
|
||||||
|
doc["message"] = "WiFi configuration updated";
|
||||||
|
doc["connected"] = WiFi.isConnected();
|
||||||
|
if (WiFi.isConnected()) {
|
||||||
|
doc["ip"] = WiFi.localIP().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
request->send(200, "application/json", json);
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#include "WifiScanService.h"
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
WifiScanService::WifiScanService(NetworkManager& networkManager)
|
|
||||||
: networkManager(networkManager) {}
|
|
||||||
|
|
||||||
void WifiScanService::registerEndpoints(ApiServer& api) {
|
|
||||||
// POST /api/wifi/scan - Start WiFi scan
|
|
||||||
api.addEndpoint("/api/wifi/scan", HTTP_POST,
|
|
||||||
[this](AsyncWebServerRequest* request) { this->handleScanRequest(request); },
|
|
||||||
std::vector<ParamSpec>{});
|
|
||||||
|
|
||||||
// GET /api/wifi - Get all access points
|
|
||||||
api.addEndpoint("/api/wifi", HTTP_GET,
|
|
||||||
[this](AsyncWebServerRequest* request) { this->handleGetAccessPoints(request); },
|
|
||||||
std::vector<ParamSpec>{});
|
|
||||||
}
|
|
||||||
|
|
||||||
void WifiScanService::handleScanRequest(AsyncWebServerRequest* request) {
|
|
||||||
networkManager.scanWifi();
|
|
||||||
|
|
||||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
|
||||||
JsonDocument doc;
|
|
||||||
doc["status"] = "scanning";
|
|
||||||
doc["message"] = "WiFi scan started";
|
|
||||||
|
|
||||||
serializeJson(doc, *response);
|
|
||||||
request->send(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WifiScanService::handleGetAccessPoints(AsyncWebServerRequest* request) {
|
|
||||||
auto accessPoints = networkManager.getAccessPoints();
|
|
||||||
|
|
||||||
JsonDocument doc;
|
|
||||||
|
|
||||||
doc["access_points"].to<JsonArray>();
|
|
||||||
|
|
||||||
for (const auto& ap : accessPoints) {
|
|
||||||
JsonObject apObj = doc["access_points"].add<JsonObject>();
|
|
||||||
apObj["ssid"] = ap.ssid;
|
|
||||||
apObj["rssi"] = ap.rssi;
|
|
||||||
apObj["channel"] = ap.channel;
|
|
||||||
apObj["encryption_type"] = ap.encryptionType;
|
|
||||||
apObj["hidden"] = ap.isHidden;
|
|
||||||
|
|
||||||
// Convert BSSID to string
|
|
||||||
char bssid[18];
|
|
||||||
sprintf(bssid, "%02X:%02X:%02X:%02X:%02X:%02X",
|
|
||||||
ap.bssid[0], ap.bssid[1], ap.bssid[2],
|
|
||||||
ap.bssid[3], ap.bssid[4], ap.bssid[5]);
|
|
||||||
apObj["bssid"] = bssid;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
|
||||||
serializeJson(doc, *response);
|
|
||||||
request->send(response);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user