feat: Add NeoPattern example and update API server functionality
- Add new NeoPattern example with pattern-based LED control - Update ApiServer with enhanced functionality - Modify neopixel example - Update platformio configuration
This commit is contained in:
470
examples/neopattern/NeoPattern.cpp
Normal file
470
examples/neopattern/NeoPattern.cpp
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
/**
|
||||||
|
* Original NeoPattern code by Bill Earl
|
||||||
|
* https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview
|
||||||
|
*
|
||||||
|
* TODO
|
||||||
|
* - cleanup the mess
|
||||||
|
* - fnc table for patterns to replace switch case
|
||||||
|
*
|
||||||
|
* Custom modifications by 0x1d:
|
||||||
|
* - default OnComplete callback that sets pattern to reverse
|
||||||
|
* - separate animation update from timer; Update now updates directly, UpdateScheduled uses timer
|
||||||
|
*/
|
||||||
|
#ifndef __NeoPattern_INCLUDED__
|
||||||
|
#define __NeoPattern_INCLUDED__
|
||||||
|
|
||||||
|
#include <Adafruit_NeoPixel.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// Pattern types supported:
|
||||||
|
enum pattern
|
||||||
|
{
|
||||||
|
NONE = 0,
|
||||||
|
RAINBOW_CYCLE = 1,
|
||||||
|
THEATER_CHASE = 2,
|
||||||
|
COLOR_WIPE = 3,
|
||||||
|
SCANNER = 4,
|
||||||
|
FADE = 5,
|
||||||
|
FIRE = 6
|
||||||
|
};
|
||||||
|
// Patern directions supported:
|
||||||
|
enum direction
|
||||||
|
{
|
||||||
|
FORWARD,
|
||||||
|
REVERSE
|
||||||
|
};
|
||||||
|
|
||||||
|
// NeoPattern Class - derived from the Adafruit_NeoPixel class
|
||||||
|
class NeoPattern : public Adafruit_NeoPixel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Member Variables:
|
||||||
|
pattern ActivePattern = RAINBOW_CYCLE; // which pattern is running
|
||||||
|
direction Direction = FORWARD; // direction to run the pattern
|
||||||
|
|
||||||
|
unsigned long Interval = 150; // milliseconds between updates
|
||||||
|
unsigned long lastUpdate = 0; // last update of position
|
||||||
|
|
||||||
|
uint32_t Color1 = 0;
|
||||||
|
uint32_t Color2 = 0; // What colors are in use
|
||||||
|
uint16_t TotalSteps = 32; // total number of steps in the pattern
|
||||||
|
uint16_t Index; // current step within the pattern
|
||||||
|
uint16_t completed = 0;
|
||||||
|
|
||||||
|
// FIXME return current NeoPatternState
|
||||||
|
void (*OnComplete)(int); // Callback on completion of pattern
|
||||||
|
|
||||||
|
uint8_t *frameBuffer;
|
||||||
|
int bufferSize = 0;
|
||||||
|
|
||||||
|
// Constructor - calls base-class constructor to initialize strip
|
||||||
|
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int))
|
||||||
|
: Adafruit_NeoPixel(pixels, pin, type)
|
||||||
|
{
|
||||||
|
frameBuffer = (uint8_t *)malloc(768);
|
||||||
|
OnComplete = callback;
|
||||||
|
TotalSteps = numPixels();
|
||||||
|
begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type)
|
||||||
|
: Adafruit_NeoPixel(pixels, pin, type)
|
||||||
|
{
|
||||||
|
frameBuffer = (uint8_t *)malloc(768);
|
||||||
|
TotalSteps = numPixels();
|
||||||
|
begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleStream(uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
//const uint16_t *data16 = (uint16_t *)data;
|
||||||
|
bufferSize = len;
|
||||||
|
memcpy(frameBuffer, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawFrameBuffer(int w, uint8_t *frame, int length)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
uint8_t r = frame[i];
|
||||||
|
uint8_t g = frame[i + 1];
|
||||||
|
uint8_t b = frame[i + 2];
|
||||||
|
setPixelColor(i, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onCompleteDefault(int pixels)
|
||||||
|
{
|
||||||
|
//Serial.println("onCompleteDefault");
|
||||||
|
// FIXME no specific code
|
||||||
|
if (ActivePattern == THEATER_CHASE)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Reverse();
|
||||||
|
//Serial.println("pattern completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the pattern
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
switch (ActivePattern)
|
||||||
|
{
|
||||||
|
case RAINBOW_CYCLE:
|
||||||
|
RainbowCycleUpdate();
|
||||||
|
break;
|
||||||
|
case THEATER_CHASE:
|
||||||
|
TheaterChaseUpdate();
|
||||||
|
break;
|
||||||
|
case COLOR_WIPE:
|
||||||
|
ColorWipeUpdate();
|
||||||
|
break;
|
||||||
|
case SCANNER:
|
||||||
|
ScannerUpdate();
|
||||||
|
break;
|
||||||
|
case FADE:
|
||||||
|
FadeUpdate();
|
||||||
|
break;
|
||||||
|
case FIRE:
|
||||||
|
Fire(50, 120);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (bufferSize > 0)
|
||||||
|
{
|
||||||
|
drawFrameBuffer(TotalSteps, frameBuffer, bufferSize);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateScheduled()
|
||||||
|
{
|
||||||
|
if ((millis() - lastUpdate) > Interval) // time to update
|
||||||
|
{
|
||||||
|
lastUpdate = millis();
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the Index and reset at the end
|
||||||
|
void Increment()
|
||||||
|
{
|
||||||
|
completed = 0;
|
||||||
|
if (Direction == FORWARD)
|
||||||
|
{
|
||||||
|
Index++;
|
||||||
|
if (Index >= TotalSteps)
|
||||||
|
{
|
||||||
|
Index = 0;
|
||||||
|
completed = 1;
|
||||||
|
if (OnComplete != NULL)
|
||||||
|
{
|
||||||
|
OnComplete(numPixels()); // call the comlpetion callback
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onCompleteDefault(numPixels());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // Direction == REVERSE
|
||||||
|
{
|
||||||
|
--Index;
|
||||||
|
if (Index <= 0)
|
||||||
|
{
|
||||||
|
Index = TotalSteps - 1;
|
||||||
|
completed = 1;
|
||||||
|
if (OnComplete != NULL)
|
||||||
|
{
|
||||||
|
OnComplete(numPixels()); // call the comlpetion callback
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onCompleteDefault(numPixels());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse pattern direction
|
||||||
|
void Reverse()
|
||||||
|
{
|
||||||
|
if (Direction == FORWARD)
|
||||||
|
{
|
||||||
|
Direction = REVERSE;
|
||||||
|
Index = TotalSteps - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Direction = FORWARD;
|
||||||
|
Index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize for a RainbowCycle
|
||||||
|
void RainbowCycle(uint8_t interval, direction dir = FORWARD)
|
||||||
|
{
|
||||||
|
ActivePattern = RAINBOW_CYCLE;
|
||||||
|
Interval = interval;
|
||||||
|
TotalSteps = 255;
|
||||||
|
Index = 0;
|
||||||
|
Direction = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the Rainbow Cycle Pattern
|
||||||
|
void RainbowCycleUpdate()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numPixels(); i++)
|
||||||
|
{
|
||||||
|
setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
|
||||||
|
}
|
||||||
|
show();
|
||||||
|
Increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize for a Theater Chase
|
||||||
|
void TheaterChase(uint32_t color1, uint32_t color2, uint16_t interval, direction dir = FORWARD)
|
||||||
|
{
|
||||||
|
ActivePattern = THEATER_CHASE;
|
||||||
|
Interval = interval;
|
||||||
|
TotalSteps = numPixels();
|
||||||
|
Color1 = color1;
|
||||||
|
Color2 = color2;
|
||||||
|
Index = 0;
|
||||||
|
Direction = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the Theater Chase Pattern
|
||||||
|
void TheaterChaseUpdate()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numPixels(); i++)
|
||||||
|
{
|
||||||
|
if ((i + Index) % 3 == 0)
|
||||||
|
{
|
||||||
|
setPixelColor(i, Color1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setPixelColor(i, Color2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
show();
|
||||||
|
Increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize for a ColorWipe
|
||||||
|
void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
|
||||||
|
{
|
||||||
|
ActivePattern = COLOR_WIPE;
|
||||||
|
Interval = interval;
|
||||||
|
TotalSteps = numPixels();
|
||||||
|
Color1 = color;
|
||||||
|
Index = 0;
|
||||||
|
Direction = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the Color Wipe Pattern
|
||||||
|
void ColorWipeUpdate()
|
||||||
|
{
|
||||||
|
setPixelColor(Index, Color1);
|
||||||
|
show();
|
||||||
|
Increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize for a SCANNNER
|
||||||
|
void Scanner(uint32_t color1, uint8_t interval)
|
||||||
|
{
|
||||||
|
ActivePattern = SCANNER;
|
||||||
|
Interval = interval;
|
||||||
|
TotalSteps = (numPixels() - 1) * 2;
|
||||||
|
Color1 = color1;
|
||||||
|
Index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the Scanner Pattern
|
||||||
|
void ScannerUpdate()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numPixels(); i++)
|
||||||
|
{
|
||||||
|
if (i == Index) // Scan Pixel to the right
|
||||||
|
{
|
||||||
|
setPixelColor(i, Color1);
|
||||||
|
}
|
||||||
|
else if (i == TotalSteps - Index) // Scan Pixel to the left
|
||||||
|
{
|
||||||
|
setPixelColor(i, Color1);
|
||||||
|
}
|
||||||
|
else // Fading tail
|
||||||
|
{
|
||||||
|
setPixelColor(i, DimColor(getPixelColor(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
show();
|
||||||
|
Increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize for a Fade
|
||||||
|
void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
|
||||||
|
{
|
||||||
|
ActivePattern = FADE;
|
||||||
|
Interval = interval;
|
||||||
|
TotalSteps = steps;
|
||||||
|
Color1 = color1;
|
||||||
|
Color2 = color2;
|
||||||
|
Index = 0;
|
||||||
|
Direction = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the Fade Pattern
|
||||||
|
void FadeUpdate()
|
||||||
|
{
|
||||||
|
// Calculate linear interpolation between Color1 and Color2
|
||||||
|
// Optimise order of operations to minimize truncation error
|
||||||
|
uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps;
|
||||||
|
uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps;
|
||||||
|
uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps;
|
||||||
|
|
||||||
|
ColorSet(Color(red, green, blue));
|
||||||
|
show();
|
||||||
|
Increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate 50% dimmed version of a color (used by ScannerUpdate)
|
||||||
|
uint32_t DimColor(uint32_t color)
|
||||||
|
{
|
||||||
|
// Shift R, G and B components one bit to the right
|
||||||
|
uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
|
||||||
|
return dimColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set all pixels to a color (synchronously)
|
||||||
|
void ColorSet(uint32_t color)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numPixels(); i++)
|
||||||
|
{
|
||||||
|
setPixelColor(i, color);
|
||||||
|
}
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the Red component of a 32-bit color
|
||||||
|
uint8_t Red(uint32_t color)
|
||||||
|
{
|
||||||
|
return (color >> 16) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the Green component of a 32-bit color
|
||||||
|
uint8_t Green(uint32_t color)
|
||||||
|
{
|
||||||
|
return (color >> 8) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the Blue component of a 32-bit color
|
||||||
|
uint8_t Blue(uint32_t color)
|
||||||
|
{
|
||||||
|
return color & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input a value 0 to 255 to get a color value.
|
||||||
|
// The colours are a transition r - g - b - back to r.
|
||||||
|
uint32_t Wheel(uint8_t WheelPos)
|
||||||
|
{
|
||||||
|
//if(WheelPos == 0) return Color(0,0,0);
|
||||||
|
WheelPos = 255 - WheelPos;
|
||||||
|
if (WheelPos < 85)
|
||||||
|
{
|
||||||
|
return Color(255 - WheelPos * 3, 0, WheelPos * 3);
|
||||||
|
}
|
||||||
|
else if (WheelPos < 170)
|
||||||
|
{
|
||||||
|
WheelPos -= 85;
|
||||||
|
return Color(0, WheelPos * 3, 255 - WheelPos * 3);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WheelPos -= 170;
|
||||||
|
return Color(WheelPos * 3, 255 - WheelPos * 3, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Effects from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/
|
||||||
|
*/
|
||||||
|
void Fire(int Cooling, int Sparking)
|
||||||
|
{
|
||||||
|
uint8_t heat[numPixels()];
|
||||||
|
int cooldown;
|
||||||
|
|
||||||
|
// Step 1. Cool down every cell a little
|
||||||
|
for (int i = 0; i < numPixels(); i++)
|
||||||
|
{
|
||||||
|
cooldown = random(0, ((Cooling * 10) / numPixels()) + 2);
|
||||||
|
|
||||||
|
if (cooldown > heat[i])
|
||||||
|
{
|
||||||
|
heat[i] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
heat[i] = heat[i] - cooldown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2. Heat from each cell drifts 'up' and diffuses a little
|
||||||
|
for (int k = numPixels() - 1; k >= 2; k--)
|
||||||
|
{
|
||||||
|
heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3. Randomly ignite new 'sparks' near the bottom
|
||||||
|
if (random(255) < Sparking)
|
||||||
|
{
|
||||||
|
int y = random(7);
|
||||||
|
heat[y] = heat[y] + random(160, 255);
|
||||||
|
//heat[y] = random(160,255);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4. Convert heat to LED colors
|
||||||
|
for (int j = 0; j < numPixels(); j++)
|
||||||
|
{
|
||||||
|
setPixelHeatColor(j, heat[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
showStrip();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPixelHeatColor(int Pixel, uint8_t temperature)
|
||||||
|
{
|
||||||
|
// Scale 'heat' down from 0-255 to 0-191
|
||||||
|
uint8_t t192 = round((temperature / 255.0) * 191);
|
||||||
|
|
||||||
|
// calculate ramp up from
|
||||||
|
uint8_t heatramp = t192 & 0x3F; // 0..63
|
||||||
|
heatramp <<= 2; // scale up to 0..252
|
||||||
|
|
||||||
|
// figure out which third of the spectrum we're in:
|
||||||
|
if (t192 > 0x80)
|
||||||
|
{ // hottest
|
||||||
|
setPixel(Pixel, 255, 255, heatramp);
|
||||||
|
}
|
||||||
|
else if (t192 > 0x40)
|
||||||
|
{ // middle
|
||||||
|
setPixel(Pixel, 255, heatramp, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // coolest
|
||||||
|
setPixel(Pixel, heatramp, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPixel(int Pixel, uint8_t red, uint8_t green, uint8_t blue)
|
||||||
|
{
|
||||||
|
setPixelColor(Pixel, Color(red, green, blue));
|
||||||
|
}
|
||||||
|
void showStrip()
|
||||||
|
{
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
31
examples/neopattern/config.h
Normal file
31
examples/neopattern/config.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef __DEVICE_CONFIG__
|
||||||
|
#define __DEVICE_CONFIG__
|
||||||
|
|
||||||
|
// Scheduler config
|
||||||
|
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||||
|
#define _TASK_STD_FUNCTION
|
||||||
|
#define _TASK_PRIORITY
|
||||||
|
|
||||||
|
// Chip config
|
||||||
|
#define SPROCKET_TYPE "SPROCKET"
|
||||||
|
#define SERIAL_BAUD_RATE 115200
|
||||||
|
#define STARTUP_DELAY 1000
|
||||||
|
|
||||||
|
// network config
|
||||||
|
#define SPROCKET_MODE 1
|
||||||
|
#define WIFI_CHANNEL 11
|
||||||
|
#define AP_SSID "sprocket"
|
||||||
|
#define AP_PASSWORD "th3r31sn0sp00n"
|
||||||
|
#define STATION_SSID "MyAP"
|
||||||
|
#define STATION_PASSWORD "th3r31sn0sp00n"
|
||||||
|
#define HOSTNAME "sprocket"
|
||||||
|
#define CONNECT_TIMEOUT 10000
|
||||||
|
|
||||||
|
// NeoPixel conig
|
||||||
|
#define LED_STRIP_PIN D2
|
||||||
|
#define LED_STRIP_LENGTH 8
|
||||||
|
#define LED_STRIP_BRIGHTNESS 48
|
||||||
|
#define LED_STRIP_UPDATE_INTERVAL 200
|
||||||
|
#define LED_STRIP_DEFAULT_COLOR 100
|
||||||
|
|
||||||
|
#endif
|
||||||
252
examples/neopattern/main.cpp
Normal file
252
examples/neopattern/main.cpp
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Globals.h"
|
||||||
|
#include "NodeContext.h"
|
||||||
|
#include "NetworkManager.h"
|
||||||
|
#include "ClusterManager.h"
|
||||||
|
#include "ApiServer.h"
|
||||||
|
#include "TaskManager.h"
|
||||||
|
|
||||||
|
// Include the NeoPattern implementation from this example folder
|
||||||
|
#include "NeoPattern.cpp"
|
||||||
|
|
||||||
|
#ifndef LED_STRIP_PIN
|
||||||
|
#define LED_STRIP_PIN 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LED_STRIP_LENGTH
|
||||||
|
#define LED_STRIP_LENGTH 8
|
||||||
|
#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
|
||||||
|
#define LED_STRIP_TYPE (NEO_GRB + NEO_KHZ800)
|
||||||
|
#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({
|
||||||
|
{"app", "neopattern"},
|
||||||
|
{"device", "light"},
|
||||||
|
{"pixels", String(LED_STRIP_LENGTH)},
|
||||||
|
{"pin", String(LED_STRIP_PIN)}
|
||||||
|
});
|
||||||
|
NetworkManager network(ctx);
|
||||||
|
TaskManager taskManager(ctx);
|
||||||
|
ClusterManager cluster(ctx, taskManager);
|
||||||
|
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
|
||||||
|
NeoPatternService neoService(ctx, taskManager, LED_STRIP_LENGTH, LED_STRIP_PIN, LED_STRIP_TYPE);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
network.setupWiFi();
|
||||||
|
|
||||||
|
taskManager.initialize();
|
||||||
|
|
||||||
|
apiServer.begin();
|
||||||
|
neoService.registerApi(apiServer);
|
||||||
|
|
||||||
|
taskManager.printTaskStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
taskManager.execute();
|
||||||
|
yield();
|
||||||
|
}
|
||||||
@@ -135,8 +135,8 @@ public:
|
|||||||
},
|
},
|
||||||
std::vector<ApiServer::ParamSpec>{
|
std::vector<ApiServer::ParamSpec>{
|
||||||
ApiServer::ParamSpec{ String("pattern"), false, String("body"), String("string"), patternNamesVector() },
|
ApiServer::ParamSpec{ String("pattern"), false, String("body"), String("string"), patternNamesVector() },
|
||||||
ApiServer::ParamSpec{ String("interval_ms"), false, String("body"), String("number"), {} },
|
ApiServer::ParamSpec{ String("interval_ms"), false, String("body"), String("number"), {}, String("100") },
|
||||||
ApiServer::ParamSpec{ String("brightness"), false, String("body"), String("number"), {} },
|
ApiServer::ParamSpec{ String("brightness"), false, String("body"), String("number"), {}, String("50") },
|
||||||
ApiServer::ParamSpec{ String("r"), 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("g"), false, String("body"), String("number"), {} },
|
||||||
ApiServer::ParamSpec{ String("b"), false, String("body"), String("number"), {} },
|
ApiServer::ParamSpec{ String("b"), false, String("body"), String("number"), {} },
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public:
|
|||||||
String location; // "query" | "body" | "path" | "header"
|
String location; // "query" | "body" | "path" | "header"
|
||||||
String type; // e.g. "string", "number", "boolean"
|
String type; // e.g. "string", "number", "boolean"
|
||||||
std::vector<String> values; // optional allowed values
|
std::vector<String> values; // optional allowed values
|
||||||
|
String defaultValue; // optional default value (stringified)
|
||||||
};
|
};
|
||||||
struct EndpointCapability {
|
struct EndpointCapability {
|
||||||
String uri;
|
String uri;
|
||||||
|
|||||||
@@ -92,3 +92,35 @@ build_src_filter =
|
|||||||
+<examples/neopixel/*.cpp>
|
+<examples/neopixel/*.cpp>
|
||||||
+<src/*.c>
|
+<src/*.c>
|
||||||
+<src/*.cpp>
|
+<src/*.cpp>
|
||||||
|
|
||||||
|
[env:esp01_1m_neopattern]
|
||||||
|
platform = platformio/espressif8266@^4.2.1
|
||||||
|
board = esp01_1m
|
||||||
|
framework = arduino
|
||||||
|
upload_speed = 115200
|
||||||
|
monitor_speed = 115200
|
||||||
|
board_build.partitions = partitions_ota_1M.csv
|
||||||
|
board_build.flash_mode = dout
|
||||||
|
board_build.flash_size = 1M
|
||||||
|
lib_deps = ${common.lib_deps}
|
||||||
|
adafruit/Adafruit NeoPixel@^1.15.1
|
||||||
|
build_flags = -DLED_STRIP_PIN=2
|
||||||
|
build_src_filter =
|
||||||
|
+<examples/neopattern/main.cpp>
|
||||||
|
+<src/*.c>
|
||||||
|
+<src/*.cpp>
|
||||||
|
|
||||||
|
[env:d1_mini_neopattern]
|
||||||
|
platform = platformio/espressif8266@^4.2.1
|
||||||
|
board = d1_mini
|
||||||
|
framework = arduino
|
||||||
|
upload_speed = 115200
|
||||||
|
monitor_speed = 115200
|
||||||
|
board_build.flash_mode = dio
|
||||||
|
board_build.flash_size = 4M
|
||||||
|
lib_deps = ${common.lib_deps}
|
||||||
|
adafruit/Adafruit NeoPixel@^1.15.1
|
||||||
|
build_src_filter =
|
||||||
|
+<examples/neopattern/main.cpp>
|
||||||
|
+<src/*.c>
|
||||||
|
+<src/*.cpp>
|
||||||
|
|||||||
@@ -365,6 +365,9 @@ void ApiServer::onCapabilitiesRequest(AsyncWebServerRequest *request) {
|
|||||||
allowed.add(v);
|
allowed.add(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ps.defaultValue.length() > 0) {
|
||||||
|
p["default"] = ps.defaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user