Merge branch 'main' into large-ledmap

This commit is contained in:
Blaž Kristan
2025-01-20 09:03:55 +01:00
committed by GitHub
88 changed files with 4081 additions and 3053 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@
#include <vector>
#include "const.h"
#include "bus_manager.h"
#define FASTLED_INTERNAL //remove annoying pragma messages
#define USE_GET_MILLISECOND_TIMER
@@ -42,10 +43,21 @@
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
#endif
extern bool realtimeRespectLedMaps; // used in getMappedPixelIndex()
extern byte realtimeMode; // used in getMappedPixelIndex()
/* Not used in all effects yet */
#define WLED_FPS 42
#define FRAMETIME_FIXED (1000/WLED_FPS)
#define FRAMETIME strip.getFrameTime()
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
#define MIN_FRAME_DELAY 2 // minimum wait between repaints, to keep other functions like WiFi alive
#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
#define MIN_FRAME_DELAY 3 // S2/C3 are slower than normal esp32, and only have one core
#else
#define MIN_FRAME_DELAY 8 // 8266 legacy MIN_SHOW_DELAY
#endif
#define FPS_UNLIMITED 0
// FPS calculation (can be defined as compile flag for debugging)
#ifndef FPS_CALC_AVG
@@ -67,9 +79,9 @@
#define MAX_NUM_SEGMENTS 32
#endif
#if defined(ARDUINO_ARCH_ESP32S2)
#define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*768 // 24k by default (S2 is short on free RAM)
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*768) // 24k by default (S2 is short on free RAM)
#else
#define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*1280 // 40k by default
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*1280) // 40k by default
#endif
#endif
@@ -77,16 +89,14 @@
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / strip.getMaxSegments())
#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
#define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT strip._segments[strip.getCurrSegmentId()]
#define SEGENV strip._segments[strip.getCurrSegmentId()]
//#define SEGCOLOR(x) strip._segments[strip.getCurrSegmentId()].currentColor(x, strip._segments[strip.getCurrSegmentId()].colors[x])
//#define SEGLEN strip._segments[strip.getCurrSegmentId()].virtualLength()
#define SEGCOLOR(x) strip.segColor(x) /* saves us a few kbytes of code */
#define SEGCOLOR(x) Segment::getCurrentColor(x)
#define SEGPALETTE Segment::getCurrentPalette()
#define SEGLEN strip._virtualSegmentLength /* saves us a few kbytes of code */
#define SEGLEN Segment::vLength()
#define SEG_W Segment::vWidth()
#define SEG_H Segment::vHeight()
#define SPEED_FORMULA_L (5U + (50U*(255U - SEGMENT.speed))/SEGLEN)
// some common colors
@@ -198,7 +208,7 @@
#define FX_MODE_COLORTWINKLE 74
#define FX_MODE_LAKE 75
#define FX_MODE_METEOR 76
#define FX_MODE_METEOR_SMOOTH 77
//#define FX_MODE_METEOR_SMOOTH 77 // merged with meteor
#define FX_MODE_RAILWAY 78
#define FX_MODE_RIPPLE 79
#define FX_MODE_TWINKLEFOX 80
@@ -363,6 +373,7 @@ typedef struct Segment {
};
uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows
uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows
// note: two bytes of padding are added here
char *name;
// runtime data
@@ -391,7 +402,7 @@ typedef struct Segment {
uint32_t _stepT;
uint32_t _callT;
uint8_t *_dataT;
uint16_t _dataLenT;
unsigned _dataLenT;
TemporarySegmentData()
: _dataT(nullptr) // just in case...
, _dataLenT(0)
@@ -409,15 +420,20 @@ typedef struct Segment {
uint8_t _reserved : 4;
};
};
uint16_t _dataLen;
static uint16_t _usedSegmentData;
// perhaps this should be per segment, not static
uint8_t _default_palette; // palette number that gets assigned to pal0
unsigned _dataLen;
static unsigned _usedSegmentData;
static uint8_t _segBri; // brightness of segment for current effect
static unsigned _vLength; // 1D dimension used for current effect
static unsigned _vWidth, _vHeight; // 2D dimensions used for current effect
static uint32_t _currentColors[NUM_COLORS]; // colors used for current effect
static bool _colorScaled; // color has been scaled prior to setPixelColor() call
static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette())
static CRGBPalette16 _randomPalette; // actual random palette
static CRGBPalette16 _newRandomPalette; // target random palette
static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000
static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF
static uint16_t _transitionprogress; // current transition progress 0 - 0xFFFF
#ifndef WLED_DISABLE_MODE_BLEND
static bool _modeBlend; // mode/effect blending semaphore
#endif
@@ -444,6 +460,8 @@ typedef struct Segment {
{}
} *_t;
[[gnu::hot]] void _setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const; // set pixel without mapping (internal use only)
public:
Segment(uint16_t sStart=0, uint16_t sStop=30) :
@@ -476,6 +494,7 @@ typedef struct Segment {
aux1(0),
data(nullptr),
_capabilities(0),
_default_palette(0),
_dataLen(0),
_t(nullptr)
{
@@ -499,7 +518,7 @@ typedef struct Segment {
//if (data) Serial.printf(" %d->(%p)", (int)_dataLen, data);
//Serial.println();
#endif
if (name) { delete[] name; name = nullptr; }
if (name) { free(name); name = nullptr; }
stopTransition();
deallocateData();
}
@@ -515,7 +534,6 @@ typedef struct Segment {
inline bool isSelected() const { return selected; }
inline bool isInTransition() const { return _t != nullptr; }
inline bool isActive() const { return stop > start; }
inline bool is2D() const { return (width()>1 && height()>1); }
inline bool hasRGB() const { return _isRGB; }
inline bool hasWhite() const { return _hasW; }
inline bool isCCT() const { return _isCCT; }
@@ -524,23 +542,30 @@ typedef struct Segment {
inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels
inline uint16_t groupLength() const { return grouping + spacing; }
inline uint8_t getLightCapabilities() const { return _capabilities; }
inline void deactivate() { setGeometry(0,0); }
inline static uint16_t getUsedSegmentData() { return _usedSegmentData; }
inline static void addUsedSegmentData(int len) { _usedSegmentData += len; }
inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; }
inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; }
#ifndef WLED_DISABLE_MODE_BLEND
inline static void modeBlend(bool blend) { _modeBlend = blend; }
inline static void modeBlend(bool blend) { _modeBlend = blend; }
#endif
static void handleRandomPalette();
inline static unsigned vLength() { return Segment::_vLength; }
inline static unsigned vWidth() { return Segment::_vWidth; }
inline static unsigned vHeight() { return Segment::_vHeight; }
inline static uint32_t getCurrentColor(unsigned i) { return Segment::_currentColors[i]; } // { return i < 3 ? Segment::_currentColors[i] : 0; }
inline static const CRGBPalette16 &getCurrentPalette() { return Segment::_currentPalette; }
inline static uint8_t getCurrentBrightness() { return Segment::_segBri; }
static void handleRandomPalette();
void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1);
void beginDraw(); // set up parameters for current effect
void setGeometry(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1, uint8_t m12=0);
Segment &setColor(uint8_t slot, uint32_t c);
Segment &setCCT(uint16_t k);
Segment &setOpacity(uint8_t o);
Segment &setOption(uint8_t n, bool val);
Segment &setMode(uint8_t fx, bool loadDefaults = false);
Segment &setPalette(uint8_t pal);
uint8_t differs(Segment& b) const;
uint8_t differs(const Segment& b) const;
void refreshLightCapabilities();
// runtime data functions
@@ -559,28 +584,28 @@ typedef struct Segment {
// transition functions
void startTransition(uint16_t dur); // transition has to start before actual segment values change
void stopTransition(); // ends transition mode by destroying transition structure (does nothing if not in transition)
inline void handleTransition() { if (progress() == 0xFFFFU) stopTransition(); }
inline void handleTransition() { updateTransitionProgress(); if (progress() == 0xFFFFU) stopTransition(); }
#ifndef WLED_DISABLE_MODE_BLEND
void swapSegenv(tmpsegd_t &tmpSegD); // copies segment data into specifed buffer, if buffer is not a transition buffer, segment data is overwritten from transition buffer
void restoreSegenv(tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer
void restoreSegenv(const tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer
#endif
[[gnu::hot]] uint16_t progress() const; // transition progression between 0-65535
[[gnu::hot]] void updateTransitionProgress(); // set current progression of transition
inline uint16_t progress() const { return Segment::_transitionprogress; } // transition progression between 0-65535
[[gnu::hot]] uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition)
uint8_t currentMode() const; // currently active effect/mode (while in transition)
[[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition)
CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal);
void setCurrentPalette();
// 1D strip
[[gnu::hot]] uint16_t virtualLength() const;
[[gnu::hot]] void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color
inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); }
inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); }
[[gnu::hot]] void setPixelColor(int i, uint32_t c) const; // set relative pixel within segment with color
inline void setPixelColor(unsigned n, uint32_t c) const { setPixelColor(int(n), c); }
inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(int n, CRGB c) const { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); }
#ifdef WLED_USE_AA_PIXELS
void setPixelColor(float i, uint32_t c, bool aa = true);
inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); }
inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); }
void setPixelColor(float i, uint32_t c, bool aa = true) const;
inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) const { setPixelColor(i, RGBW32(r,g,b,w), aa); }
inline void setPixelColor(float i, CRGB c, bool aa = true) const { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); }
#endif
[[gnu::hot]] uint32_t getPixelColor(int i) const;
// 1D support functions (some implement 2D as well)
@@ -590,21 +615,19 @@ typedef struct Segment {
void fadeToBlackBy(uint8_t fadeBy);
inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); }
inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); }
inline void addPixelColor(int n, uint32_t color, bool fast = false) { setPixelColor(n, color_add(getPixelColor(n), color, fast)); }
inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); }
inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); }
inline void addPixelColor(int n, uint32_t color, bool preserveCR = true) { setPixelColor(n, color_add(getPixelColor(n), color, preserveCR)); }
inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColor(n, RGBW32(r,g,b,w), preserveCR); }
inline void addPixelColor(int n, CRGB c, bool preserveCR = true) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), preserveCR); }
inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); }
[[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const;
[[gnu::hot]] uint32_t color_wheel(uint8_t pos) const;
// 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur)
inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns
const unsigned cols = virtualWidth();
for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear);
blur2D(0, blur_amount, smear);
}
inline void blurRows(fract8 blur_amount, bool smear = false) { // blur all rows
const unsigned rows = virtualHeight();
for ( unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear);
blur2D(blur_amount, 0, smear);
}
// 2D matrix
@@ -618,46 +641,45 @@ typedef struct Segment {
#endif
}
#ifndef WLED_DISABLE_2D
[[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment
[[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color
inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); }
inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); }
inline bool is2D() const { return (width()>1 && height()>1); }
[[gnu::hot]] int XY(int x, int y) const; // support function to get relative index within segment
[[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c) const; // set relative pixel within segment with color
inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) const { setPixelColorXY(int(x), int(y), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); }
inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) const { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); }
#ifdef WLED_USE_AA_PIXELS
void setPixelColorXY(float x, float y, uint32_t c, bool aa = true);
inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); }
inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); }
void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) const;
inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) const { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); }
inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); }
#endif
[[gnu::hot]] uint32_t getPixelColorXY(int x, int y) const;
// 2D support functions
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); }
inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, fast)); }
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); }
inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); }
void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur
void blur2D(uint8_t blur_amount, bool smear = false);
void blurRow(uint32_t row, fract8 blur_amount, bool smear = false);
void blurCol(uint32_t col, fract8 blur_amount, bool smear = false);
void moveX(int8_t delta, bool wrap = false);
void moveY(int8_t delta, bool wrap = false);
void move(uint8_t dir, uint8_t delta, bool wrap = false);
inline void addPixelColorXY(int x, int y, uint32_t color, bool preserveCR = true) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, preserveCR)); }
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(r,g,b,w), preserveCR); }
inline void addPixelColorXY(int x, int y, CRGB c, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), preserveCR); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); }
//void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur
void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false);
void moveX(int delta, bool wrap = false);
void moveY(int delta, bool wrap = false);
void move(unsigned dir, unsigned delta, bool wrap = false);
void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false);
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); }
void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false);
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); }
void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false);
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline
void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0);
void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0, bool usePalGrad = false);
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0, bool usePalGrad = false) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate, usePalGrad); } // automatic inline
void wu_pixel(uint32_t x, uint32_t y, CRGB c);
inline void blur2d(fract8 blur_amount) { blur(blur_amount); }
inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); }
#else
inline uint16_t XY(uint16_t x, uint16_t y) { return x; }
inline constexpr bool is2D() const { return false; }
inline int XY(int x, int y) const { return x; }
inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); }
inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); }
@@ -671,16 +693,16 @@ typedef struct Segment {
inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); }
inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); }
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); }
inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); }
inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) { addPixelColor(x, color, saturate); }
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColor(x, RGBW32(r,g,b,w), saturate); }
inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); }
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); }
inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {}
inline void blur2D(uint8_t blur_amount, bool smear = false) {}
inline void blurRow(uint32_t row, fract8 blur_amount, bool smear = false) {}
inline void blurCol(uint32_t col, fract8 blur_amount, bool smear = false) {}
inline void moveX(int8_t delta, bool wrap = false) {}
inline void moveY(int8_t delta, bool wrap = false) {}
//inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {}
inline void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) {}
inline void blurRow(int row, fract8 blur_amount, bool smear = false) {}
inline void blurCol(int col, fract8 blur_amount, bool smear = false) {}
inline void moveX(int delta, bool wrap = false) {}
inline void moveY(int delta, bool wrap = false) {}
inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {}
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {}
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {}
@@ -688,9 +710,9 @@ typedef struct Segment {
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {}
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {}
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0, bool = false) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) {}
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0, bool usePalGrad = false) {}
inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {}
#endif
} segment;
@@ -728,9 +750,6 @@ class WS2812FX { // 96 bytes
#endif
correctWB(false),
cctFromRgb(false),
// semi-private (just obscured) used in effect functions through macros
_colors_t{0,0,0},
_virtualSegmentLength(0),
// true private variables
_suspend(false),
_length(DEFAULT_LED_COUNT),
@@ -748,6 +767,7 @@ class WS2812FX { // 96 bytes
customMappingTable(nullptr),
customMappingSize(0),
_lastShow(0),
_lastServiceShow(0),
_segment_index(0),
_mainSegment(0)
{
@@ -759,7 +779,7 @@ class WS2812FX { // 96 bytes
}
~WS2812FX() {
if (customMappingTable) delete[] customMappingTable;
if (customMappingTable) free(customMappingTable);
_mode.clear();
_modeData.clear();
_segments.clear();
@@ -777,29 +797,25 @@ class WS2812FX { // 96 bytes
#endif
finalizeInit(), // initialises strip components
service(), // executes effect functions when due and calls strip.show()
setMode(uint8_t segid, uint8_t m), // sets effect/mode for given segment (high level API)
setColor(uint8_t slot, uint32_t c), // sets color (in slot) for given segment (high level API)
setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K)
setBrightness(uint8_t b, bool direct = false), // sets strip brightness
setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay
purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint)
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1),
setMainSegmentId(uint8_t n),
setMainSegmentId(unsigned n = 0),
resetSegments(), // marks all segments for reset
makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs
fixInvalidSegments(), // fixes incorrect segment configuration
setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c
setPixelColor(unsigned i, uint32_t c) const, // paints absolute strip pixel with index n and color c
show(), // initiates LED output
setTargetFps(uint8_t fps),
setTargetFps(unsigned fps),
setupEffectData(); // add default effects to the list; defined in FX.cpp
inline void resetTimebase() { timebase = 0UL - millis(); }
inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } }
inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); }
inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); }
inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); }
inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline)
inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(unsigned n, CRGB c) const { setPixelColor(n, c.red, c.green, c.blue); }
inline void fill(uint32_t c) const { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline)
inline void trigger() { _triggered = true; } // Forces the next frame to be computed on all active segments.
inline void setShowCallback(show_callback cb) { _callback = cb; }
inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms)
@@ -809,12 +825,12 @@ class WS2812FX { // 96 bytes
bool
paletteFade,
checkSegmentAlignment(),
checkSegmentAlignment() const,
hasRGBWBus() const,
hasCCTBus() const,
isUpdating() const, // return true if the strip is being sent pixel updates
deserializeMap(uint8_t n=0);
deserializeMap(unsigned n = 0);
inline bool isUpdating() const { return !BusManager::canAllShow(); } // return true if the strip is being sent pixel updates
inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing
inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel
inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset)
@@ -831,7 +847,7 @@ class WS2812FX { // 96 bytes
addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp;
inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness
inline uint8_t getMaxSegments() const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value)
inline static constexpr unsigned getMaxSegments() { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value)
inline uint8_t getSegmentsNum() const { return _segments.size(); } // returns currently present segments
inline uint8_t getCurrSegmentId() const { return _segment_index; } // returns current segment index (only valid while strip.isServicing())
inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index
@@ -841,28 +857,27 @@ class WS2812FX { // 96 bytes
uint16_t
getLengthPhysical() const,
getLengthTotal() const, // will include virtual/nonexistent pixels in matrix
getFps() const,
getMappedPixelIndex(uint16_t index) const;
getLengthTotal() const; // will include virtual/nonexistent pixels in matrix
inline uint16_t getFps() const { return (millis() - _lastShow > 2000) ? 0 : (FPS_MULTIPLIER * _cumulativeFps) >> FPS_CALC_SHIFT; } // Returns the refresh rate of the LED strip (_cumulativeFps is stored in fixed point)
inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms)
inline uint16_t getMinShowDelay() const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant)
inline uint16_t getMinShowDelay() const { return MIN_FRAME_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant)
inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H)
inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms)
inline uint16_t getMappedPixelIndex(uint16_t index) const { // convert logical address to physical
if (index < customMappingSize && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index];
return index;
};
unsigned long now, timebase;
uint32_t getPixelColor(unsigned) const;
uint32_t getPixelColor(unsigned i) const;
inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call
inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition
inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call
const char *
getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); }
const char *getModeData(unsigned id = 0) const { return (id && id < _modeCount) ? _modeData[id] : PSTR("Solid"); }
inline const char **getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data
const char **
getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data
Segment& getSegment(uint8_t id);
Segment& getSegment(unsigned id);
inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected"
inline Segment& getMainSegment() { return _segments[getMainSegmentId()]; } // returns reference to main segment
inline Segment* getSegments() { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully)
@@ -904,11 +919,11 @@ class WS2812FX { // 96 bytes
void setUpMatrix(); // sets up automatic matrix ledmap from panel configuration
// outsmart the compiler :) by correctly overloading
inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor((unsigned)(y * Segment::maxWidth + x), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); }
inline void setPixelColorXY(int x, int y, uint32_t c) const { setPixelColor((unsigned)(y * Segment::maxWidth + x), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); }
inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x); }
inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x); }
// end 2D support
@@ -921,13 +936,8 @@ class WS2812FX { // 96 bytes
bool cctFromRgb : 1;
};
// using public variables to reduce code size increase due to inline function getSegment() (with bounds checking)
// and color transitions
uint32_t _colors_t[3]; // color used for effect (includes transition)
uint16_t _virtualSegmentLength;
std::vector<segment> _segments;
friend class Segment;
friend struct Segment;
private:
volatile bool _suspend;
@@ -958,6 +968,7 @@ class WS2812FX { // 96 bytes
uint16_t customMappingSize;
unsigned long _lastShow;
unsigned long _lastServiceShow;
uint8_t _segment_index;
uint8_t _mainSegment;

View File

@@ -50,8 +50,8 @@ void WS2812FX::setUpMatrix() {
customMappingSize = 0; // prevent use of mapping if anything goes wrong
if (customMappingTable) delete[] customMappingTable;
customMappingTable = new uint16_t[getLengthTotal()];
if (customMappingTable) free(customMappingTable);
customMappingTable = static_cast<uint16_t*>(malloc(sizeof(uint16_t)*getLengthTotal()));
if (customMappingTable) {
customMappingSize = getLengthTotal();
@@ -68,7 +68,7 @@ void WS2812FX::setUpMatrix() {
// content of the file is just raw JSON array in the form of [val1,val2,val3,...]
// there are no other "key":"value" pairs in it
// allowed values are: -1 (missing pixel/no LED attached), 0 (inactive/unused pixel), 1 (active/used pixel)
char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json")); // reduce flash footprint
char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json"));
bool isFile = WLED_FS.exists(fileName);
size_t gapSize = 0;
int8_t *gapTable = nullptr;
@@ -85,7 +85,7 @@ void WS2812FX::setUpMatrix() {
JsonArray map = pDoc->as<JsonArray>();
gapSize = map.size();
if (!map.isNull() && gapSize >= matrixSize) { // not an empty map
gapTable = new int8_t[gapSize];
gapTable = static_cast<int8_t*>(malloc(gapSize));
if (gapTable) for (size_t i = 0; i < gapSize; i++) {
gapTable[i] = constrain(map[i], -1, 1);
}
@@ -113,7 +113,7 @@ void WS2812FX::setUpMatrix() {
}
// delete gap array as we no longer need it
if (gapTable) delete[] gapTable;
if (gapTable) free(gapTable);
#ifdef WLED_DEBUG
DEBUG_PRINT(F("Matrix ledmap:"));
@@ -146,74 +146,82 @@ void WS2812FX::setUpMatrix() {
#ifndef WLED_DISABLE_2D
// XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element)
uint16_t IRAM_ATTR_YN Segment::XY(int x, int y)
int IRAM_ATTR_YN Segment::XY(int x, int y) const
{
unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1)
return isActive() ? (x%width) + (y%height) * width : 0;
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
return isActive() ? (x%vW) + (y%vH) * vW : 0;
}
void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col)
// raw setColor function without checks (checks are done in setPixelColorXY())
void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const
{
const int baseX = start + x;
const int baseY = startY + y;
#ifndef WLED_DISABLE_MODE_BLEND
// if blending modes, blend with underlying pixel
if (_modeBlend) col = color_blend16(strip.getPixelColorXY(baseX, baseY), col, 0xFFFFU - progress());
#endif
strip.setPixelColorXY(baseX, baseY, col);
// Apply mirroring
if (mirror || mirror_y) {
auto setMirroredPixel = [&](int mx, int my) {
strip.setPixelColorXY(mx, my, col);
};
const int mirrorX = start + width() - x - 1;
const int mirrorY = startY + height() - y - 1;
if (mirror) setMirroredPixel(transpose ? baseX : mirrorX, transpose ? mirrorY : baseY);
if (mirror_y) setMirroredPixel(transpose ? mirrorX : baseX, transpose ? baseY : mirrorY);
if (mirror && mirror_y) setMirroredPixel(mirrorX, mirrorY);
}
}
void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const
{
if (!isActive()) return; // not active
if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit
uint8_t _bri_t = currentBri();
if (_bri_t < 255) {
col = color_fade(col, _bri_t);
}
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
// negative values of x & y cast into unsigend will become very large values and will therefore be greater than vW/vH
if (unsigned(x) >= unsigned(vW) || unsigned(y) >= unsigned(vH)) return; // if pixel would fall out of virtual segment just exit
if (reverse ) x = virtualWidth() - x - 1;
if (reverse_y) y = virtualHeight() - y - 1;
// if color is unscaled
if (!_colorScaled) col = color_fade(col, _segBri);
if (reverse ) x = vW - x - 1;
if (reverse_y) y = vH - y - 1;
if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
unsigned groupLen = groupLength();
x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels
int W = width();
int H = height();
if (x >= W || y >= H) return; // if pixel would fall out of segment just exit
uint32_t tmpCol = col;
for (int j = 0; j < grouping; j++) { // groupping vertically
for (int g = 0; g < grouping; g++) { // groupping horizontally
int xX = (x+g), yY = (y+j);
if (xX >= W || yY >= H) continue; // we have reached one dimension's end
#ifndef WLED_DISABLE_MODE_BLEND
// if blending modes, blend with underlying pixel
if (_modeBlend) tmpCol = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true);
#endif
strip.setPixelColorXY(start + xX, startY + yY, tmpCol);
if (mirror) { //set the corresponding horizontally mirrored pixel
if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol);
else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol);
}
if (mirror_y) { //set the corresponding vertically mirrored pixel
if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol);
else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol);
}
if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel
strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, tmpCol);
if (groupLen > 1) {
int W = width();
int H = height();
x *= groupLen; // expand to physical pixels
y *= groupLen; // expand to physical pixels
const int maxY = std::min(y + grouping, H);
const int maxX = std::min(x + grouping, W);
for (int yY = y; yY < maxY; yY++) {
for (int xX = x; xX < maxX; xX++) {
_setPixelColorXY_raw(xX, yY, col);
}
}
} else {
_setPixelColorXY_raw(x, y, col);
}
}
#ifdef WLED_USE_AA_PIXELS
// anti-aliased version of setPixelColorXY()
void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const
{
if (!isActive()) return; // not active
if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
float fX = x * (cols-1);
float fY = y * (rows-1);
float fX = x * (vWidth()-1);
float fY = y * (vHeight()-1);
if (aa) {
unsigned xL = roundf(fX-0.49f);
unsigned xR = roundf(fX+0.49f);
@@ -251,9 +259,11 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
// returns RGBW values of pixel
uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
if (!isActive()) return 0; // not active
if ((unsigned)x >= virtualWidth() || (unsigned)y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit
if (reverse ) x = virtualWidth() - x - 1;
if (reverse_y) y = virtualHeight() - y - 1;
const int vW = vWidth();
const int vH = vHeight();
if (unsigned(x) >= unsigned(vW) || unsigned(y) >= unsigned(vH)) return 0; // if pixel would fall out of virtual segment just exit
if (reverse ) x = vW - x - 1;
if (reverse_y) y = vH - y - 1;
if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
x *= groupLength(); // expand to physical pixels
y *= groupLength(); // expand to physical pixels
@@ -261,128 +271,69 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
return strip.getPixelColorXY(start + x, startY + y);
}
// blurRow: perform a blur on a row of a rectangular matrix
void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){
if (!isActive() || blur_amount == 0) return; // not active
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
if (row >= rows) return;
// blur one row
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
uint32_t carryover = BLACK;
uint32_t lastnew;
// 2D blurring, can be asymmetrical
void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) {
if (!isActive()) return; // not active
const unsigned cols = vWidth();
const unsigned rows = vHeight();
uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
uint32_t last;
uint32_t curnew = BLACK;
for (unsigned x = 0; x < cols; x++) {
uint32_t cur = getPixelColorXY(x, row);
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (x > 0) {
if (carryover)
curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
if (last != prev) // optimization: only set pixel if color has changed
setPixelColorXY(x - 1, row, prev);
} else // first pixel
setPixelColorXY(x, row, curnew);
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(cols-1, row, curnew); // set last pixel
}
// blurCol: perform a blur on a column of a rectangular matrix
void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) {
if (!isActive() || blur_amount == 0) return; // not active
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
if (col >= cols) return;
// blur one column
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
uint32_t carryover = BLACK;
uint32_t lastnew;
uint32_t last;
uint32_t curnew = BLACK;
for (unsigned y = 0; y < rows; y++) {
uint32_t cur = getPixelColorXY(col, y);
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (y > 0) {
if (carryover)
curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
if (last != prev) // optimization: only set pixel if color has changed
setPixelColorXY(col, y - 1, prev);
} else // first pixel
setPixelColorXY(col, y, curnew);
lastnew = curnew;
last = cur; //save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(col, rows - 1, curnew);
}
void Segment::blur2D(uint8_t blur_amount, bool smear) {
if (!isActive() || blur_amount == 0) return; // not active
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
const uint8_t keep = smear ? 255 : 255 - blur_amount;
const uint8_t seep = blur_amount >> (1 + smear);
uint32_t lastnew;
uint32_t last;
for (unsigned row = 0; row < rows; row++) {
uint32_t carryover = BLACK;
uint32_t curnew = BLACK;
for (unsigned x = 0; x < cols; x++) {
uint32_t cur = getPixelColorXY(x, row);
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (x > 0) {
if (carryover) curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(x - 1, row, prev);
} else setPixelColorXY(x, row, curnew); // first pixel
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
if (blur_x) {
const uint8_t keepx = smear ? 255 : 255 - blur_x;
const uint8_t seepx = blur_x >> 1;
for (unsigned row = 0; row < rows; row++) { // blur rows (x direction)
uint32_t carryover = BLACK;
uint32_t curnew = BLACK;
for (unsigned x = 0; x < cols; x++) {
uint32_t cur = getPixelColorXY(x, row);
uint32_t part = color_fade(cur, seepx);
curnew = color_fade(cur, keepx);
if (x > 0) {
if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(x - 1, row, prev);
} else setPixelColorXY(x, row, curnew); // first pixel
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(cols-1, row, curnew); // set last pixel
}
setPixelColorXY(cols-1, row, curnew); // set last pixel
}
for (unsigned col = 0; col < cols; col++) {
uint32_t carryover = BLACK;
uint32_t curnew = BLACK;
for (unsigned y = 0; y < rows; y++) {
uint32_t cur = getPixelColorXY(col, y);
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (y > 0) {
if (carryover) curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(col, y - 1, prev);
} else setPixelColorXY(col, y, curnew); // first pixel
lastnew = curnew;
last = cur; //save original value for comparison on next iteration
carryover = part;
if (blur_y) {
const uint8_t keepy = smear ? 255 : 255 - blur_y;
const uint8_t seepy = blur_y >> 1;
for (unsigned col = 0; col < cols; col++) {
uint32_t carryover = BLACK;
uint32_t curnew = BLACK;
for (unsigned y = 0; y < rows; y++) {
uint32_t cur = getPixelColorXY(col, y);
uint32_t part = color_fade(cur, seepy);
curnew = color_fade(cur, keepy);
if (y > 0) {
if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColorXY(col, y - 1, prev);
} else setPixelColorXY(col, y, curnew); // first pixel
lastnew = curnew;
last = cur; //save original value for comparison on next iteration
carryover = part;
}
setPixelColorXY(col, rows - 1, curnew);
}
setPixelColorXY(col, rows - 1, curnew);
}
}
/*
// 2D Box blur
void Segment::box_blur(unsigned radius, bool smear) {
if (!isActive() || radius == 0) return; // not active
if (radius > 3) radius = 3;
const unsigned d = (1 + 2*radius) * (1 + 2*radius); // averaging divisor
const unsigned cols = virtualWidth();
const unsigned rows = virtualHeight();
const unsigned cols = vWidth();
const unsigned rows = vHeight();
uint16_t *tmpRSum = new uint16_t[cols*rows];
uint16_t *tmpGSum = new uint16_t[cols*rows];
uint16_t *tmpBSum = new uint16_t[cols*rows];
@@ -448,40 +399,56 @@ void Segment::box_blur(unsigned radius, bool smear) {
delete[] tmpBSum;
delete[] tmpWSum;
}
void Segment::moveX(int8_t delta, bool wrap) {
if (!isActive()) return; // not active
const int cols = virtualWidth();
const int rows = virtualHeight();
if (!delta || abs(delta) >= cols) return;
uint32_t newPxCol[cols];
for (int y = 0; y < rows; y++) {
if (delta > 0) {
for (int x = 0; x < cols-delta; x++) newPxCol[x] = getPixelColorXY((x + delta), y);
for (int x = cols-delta; x < cols; x++) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) - cols : x, y);
} else {
for (int x = cols-1; x >= -delta; x--) newPxCol[x] = getPixelColorXY((x + delta), y);
for (int x = -delta-1; x >= 0; x--) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) + cols : x, y);
*/
void Segment::moveX(int delta, bool wrap) {
if (!isActive() || !delta) return; // not active
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
int absDelta = abs(delta);
if (absDelta >= vW) return;
uint32_t newPxCol[vW];
int newDelta;
int stop = vW;
int start = 0;
if (wrap) newDelta = (delta + vW) % vW; // +cols in case delta < 0
else {
if (delta < 0) start = absDelta;
stop = vW - absDelta;
newDelta = delta > 0 ? delta : 0;
}
for (int y = 0; y < vH; y++) {
for (int x = 0; x < stop; x++) {
int srcX = x + newDelta;
if (wrap) srcX %= vW; // Wrap using modulo when `wrap` is true
newPxCol[x] = getPixelColorXY(srcX, y);
}
for (int x = 0; x < cols; x++) setPixelColorXY(x, y, newPxCol[x]);
for (int x = 0; x < stop; x++) setPixelColorXY(x + start, y, newPxCol[x]);
}
}
void Segment::moveY(int8_t delta, bool wrap) {
if (!isActive()) return; // not active
const int cols = virtualWidth();
const int rows = virtualHeight();
if (!delta || abs(delta) >= rows) return;
uint32_t newPxCol[rows];
for (int x = 0; x < cols; x++) {
if (delta > 0) {
for (int y = 0; y < rows-delta; y++) newPxCol[y] = getPixelColorXY(x, (y + delta));
for (int y = rows-delta; y < rows; y++) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) - rows : y);
} else {
for (int y = rows-1; y >= -delta; y--) newPxCol[y] = getPixelColorXY(x, (y + delta));
for (int y = -delta-1; y >= 0; y--) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) + rows : y);
void Segment::moveY(int delta, bool wrap) {
if (!isActive() || !delta) return; // not active
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
int absDelta = abs(delta);
if (absDelta >= vH) return;
uint32_t newPxCol[vH];
int newDelta;
int stop = vH;
int start = 0;
if (wrap) newDelta = (delta + vH) % vH; // +rows in case delta < 0
else {
if (delta < 0) start = absDelta;
stop = vH - absDelta;
newDelta = delta > 0 ? delta : 0;
}
for (int x = 0; x < vW; x++) {
for (int y = 0; y < stop; y++) {
int srcY = y + newDelta;
if (wrap) srcY %= vH; // Wrap using modulo when `wrap` is true
newPxCol[y] = getPixelColorXY(x, srcY);
}
for (int y = 0; y < rows; y++) setPixelColorXY(x, y, newPxCol[y]);
for (int y = 0; y < stop; y++) setPixelColorXY(x, y + start, newPxCol[y]);
}
}
@@ -489,7 +456,7 @@ void Segment::moveY(int8_t delta, bool wrap) {
// @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down
// @param delta number of pixels to move
// @param wrap around
void Segment::move(uint8_t dir, uint8_t delta, bool wrap) {
void Segment::move(unsigned dir, unsigned delta, bool wrap) {
if (delta==0) return;
switch (dir) {
case 0: moveX( delta, wrap); break;
@@ -507,46 +474,49 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col,
if (!isActive() || radius == 0) return; // not active
if (soft) {
// Xiaolin Wus algorithm
int rsq = radius*radius;
const int rsq = radius*radius;
int x = 0;
int y = radius;
unsigned oldFade = 0;
while (x < y) {
float yf = sqrtf(float(rsq - x*x)); // needs to be floating point
unsigned fade = float(0xFFFF) * (ceilf(yf) - yf); // how much color to keep
uint8_t fade = float(0xFF) * (ceilf(yf) - yf); // how much color to keep
if (oldFade > fade) y--;
oldFade = fade;
setPixelColorXY(cx+x, cy+y, color_blend(col, getPixelColorXY(cx+x, cy+y), fade, true));
setPixelColorXY(cx-x, cy+y, color_blend(col, getPixelColorXY(cx-x, cy+y), fade, true));
setPixelColorXY(cx+x, cy-y, color_blend(col, getPixelColorXY(cx+x, cy-y), fade, true));
setPixelColorXY(cx-x, cy-y, color_blend(col, getPixelColorXY(cx-x, cy-y), fade, true));
setPixelColorXY(cx+y, cy+x, color_blend(col, getPixelColorXY(cx+y, cy+x), fade, true));
setPixelColorXY(cx-y, cy+x, color_blend(col, getPixelColorXY(cx-y, cy+x), fade, true));
setPixelColorXY(cx+y, cy-x, color_blend(col, getPixelColorXY(cx+y, cy-x), fade, true));
setPixelColorXY(cx-y, cy-x, color_blend(col, getPixelColorXY(cx-y, cy-x), fade, true));
setPixelColorXY(cx+x, cy+y-1, color_blend(getPixelColorXY(cx+x, cy+y-1), col, fade, true));
setPixelColorXY(cx-x, cy+y-1, color_blend(getPixelColorXY(cx-x, cy+y-1), col, fade, true));
setPixelColorXY(cx+x, cy-y+1, color_blend(getPixelColorXY(cx+x, cy-y+1), col, fade, true));
setPixelColorXY(cx-x, cy-y+1, color_blend(getPixelColorXY(cx-x, cy-y+1), col, fade, true));
setPixelColorXY(cx+y-1, cy+x, color_blend(getPixelColorXY(cx+y-1, cy+x), col, fade, true));
setPixelColorXY(cx-y+1, cy+x, color_blend(getPixelColorXY(cx-y+1, cy+x), col, fade, true));
setPixelColorXY(cx+y-1, cy-x, color_blend(getPixelColorXY(cx+y-1, cy-x), col, fade, true));
setPixelColorXY(cx-y+1, cy-x, color_blend(getPixelColorXY(cx-y+1, cy-x), col, fade, true));
int px, py;
for (uint8_t i = 0; i < 16; i++) {
int swaps = (i & 0x4 ? 1 : 0); // 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1
int adj = (i < 8) ? 0 : 1; // 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1
int dx = (i & 1) ? -1 : 1; // 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1
int dy = (i & 2) ? -1 : 1; // 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1
if (swaps) {
px = cx + (y - adj) * dx;
py = cy + x * dy;
} else {
px = cx + x * dx;
py = cy + (y - adj) * dy;
}
uint32_t pixCol = getPixelColorXY(px, py);
setPixelColorXY(px, py, adj ?
color_blend(pixCol, col, fade) :
color_blend(col, pixCol, fade));
}
x++;
}
} else {
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
// Bresenhams Algorithm
int d = 3 - (2*radius);
int y = radius, x = 0;
while (y >= x) {
setPixelColorXY(cx+x, cy+y, col);
setPixelColorXY(cx-x, cy+y, col);
setPixelColorXY(cx+x, cy-y, col);
setPixelColorXY(cx-x, cy-y, col);
setPixelColorXY(cx+y, cy+x, col);
setPixelColorXY(cx-y, cy+x, col);
setPixelColorXY(cx+y, cy-x, col);
setPixelColorXY(cx-y, cy-x, col);
for (int i = 0; i < 4; i++) {
int dx = (i & 1) ? -x : x;
int dy = (i & 2) ? -y : y;
setPixelColorXY(cx + dx, cy + dy, col);
setPixelColorXY(cx + dy, cy + dx, col);
}
x++;
if (d > 0) {
y--;
@@ -555,33 +525,38 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col,
d += 4 * x + 6;
}
}
_colorScaled = false;
}
}
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) {
if (!isActive() || radius == 0) return; // not active
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
// draw soft bounding circle
if (soft) drawCircle(cx, cy, radius, col, soft);
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
// fill it
const int cols = virtualWidth();
const int rows = virtualHeight();
for (int y = -radius; y <= radius; y++) {
for (int x = -radius; x <= radius; x++) {
if (x * x + y * y <= radius * radius &&
int(cx)+x>=0 && int(cy)+y>=0 &&
int(cx)+x<cols && int(cy)+y<rows)
int(cx)+x >= 0 && int(cy)+y >= 0 &&
int(cx)+x < vW && int(cy)+y < vH)
setPixelColorXY(cx + x, cy + y, col);
}
}
_colorScaled = false;
}
//line function
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) {
if (!isActive()) return; // not active
const int cols = virtualWidth();
const int rows = virtualHeight();
if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return;
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
if (x0 >= vW || x1 >= vW || y0 >= vH || y1 >= vH) return;
const int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; // x distance & step
const int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; // y distance & step
@@ -608,17 +583,20 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0);
float intersectY = y0;
for (int x = x0; x <= x1; x++) {
unsigned keep = float(0xFFFF) * (intersectY-int(intersectY)); // how much color to keep
unsigned seep = 0xFFFF - keep; // how much background to keep
uint8_t keep = float(0xFF) * (intersectY-int(intersectY)); // how much color to keep
uint8_t seep = 0xFF - keep; // how much background to keep
int y = int(intersectY);
if (steep) std::swap(x,y); // temporaryly swap if steep
// pixel coverage is determined by fractional part of y co-ordinate
setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true));
setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep, true));
setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep));
setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep));
intersectY += gradient;
if (steep) std::swap(x,y); // restore if steep
}
} else {
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true;
// Bresenham's algorithm
int err = (dx>dy ? dx : -dy)/2; // error direction
for (;;) {
@@ -628,6 +606,7 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
if (e2 >-dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
}
_colorScaled = false;
}
}
@@ -639,16 +618,15 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
// draws a raster font character on canvas
// only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM
void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate) {
void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate, bool usePalGrad) {
if (!isActive()) return; // not active
if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
chr -= 32; // align with font table entries
const int cols = virtualWidth();
const int rows = virtualHeight();
const int font = w*h;
CRGB col = CRGB(color);
CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col);
if(usePalGrad) grad = SEGPALETTE; // selected palette as gradient
//if (w<5 || w>6 || h!=8) return;
for (int i = 0; i<h; i++) { // character height
@@ -661,7 +639,10 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font
default: return;
}
col = ColorFromPalette(grad, (i+1)*255/h, 255, NOBLEND);
uint32_t c = ColorFromPaletteWLED(grad, (i+1)*255/h, 255, NOBLEND);
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true;
for (int j = 0; j<w; j++) { // character width
int x0, y0;
switch (rotate) {
@@ -671,11 +652,12 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
case 1: x0 = x + i; y0 = y + j; break; // +90 deg
default: x0 = x + (w-1) - j; y0 = y + i; break; // no rotation
}
if (x0 < 0 || x0 >= cols || y0 < 0 || y0 >= rows) continue; // drawing off-screen
if (x0 < 0 || x0 >= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen
if (((bits>>(j+(8-w))) & 0x01)) { // bit set
setPixelColorXY(x0, y0, col);
setPixelColorXY(x0, y0, c);
}
}
_colorScaled = false;
}
}

View File

@@ -66,15 +66,21 @@ static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTy
///////////////////////////////////////////////////////////////////////////////
// Segment class implementation
///////////////////////////////////////////////////////////////////////////////
uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[]
uint16_t Segment::maxWidth = DEFAULT_LED_COUNT;
uint16_t Segment::maxHeight = 1;
unsigned Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[]
uint16_t Segment::maxWidth = DEFAULT_LED_COUNT;
uint16_t Segment::maxHeight = 1;
unsigned Segment::_vLength = 0;
unsigned Segment::_vWidth = 0;
unsigned Segment::_vHeight = 0;
uint8_t Segment::_segBri = 0;
uint32_t Segment::_currentColors[NUM_COLORS] = {0,0,0};
bool Segment::_colorScaled = false;
CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black);
CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment
uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only)
uint16_t Segment::_transitionprogress = 0xFFFF;
#ifndef WLED_DISABLE_MODE_BLEND
bool Segment::_modeBlend = false;
@@ -88,7 +94,7 @@ Segment::Segment(const Segment &orig) {
name = nullptr;
data = nullptr;
_dataLen = 0;
if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
}
@@ -107,7 +113,7 @@ Segment& Segment::operator= (const Segment &orig) {
//DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
// clean destination
if (name) { delete[] name; name = nullptr; }
if (name) { free(name); name = nullptr; }
stopTransition();
deallocateData();
// copy source
@@ -116,7 +122,7 @@ Segment& Segment::operator= (const Segment &orig) {
data = nullptr;
_dataLen = 0;
// copy source data
if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
}
return *this;
@@ -126,7 +132,7 @@ Segment& Segment::operator= (const Segment &orig) {
Segment& Segment::operator= (Segment &&orig) noexcept {
//DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
if (name) { delete[] name; name = nullptr; } // free old name
if (name) { free(name); name = nullptr; } // free old name
stopTransition();
deallocateData(); // free old runtime data
memcpy((void*)this, (void*)&orig, sizeof(Segment));
@@ -195,24 +201,12 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0;
if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip
//default palette. Differs depending on effect
if (pal == 0) switch (mode) {
case FX_MODE_FIRE_2012 : pal = 35; break; // heat palette
case FX_MODE_COLORWAVES : pal = 26; break; // landscape 33
case FX_MODE_FILLNOISE8 : pal = 9; break; // ocean colors
case FX_MODE_NOISE16_1 : pal = 20; break; // Drywet
case FX_MODE_NOISE16_2 : pal = 43; break; // Blue cyan yellow
case FX_MODE_NOISE16_3 : pal = 35; break; // heat palette
case FX_MODE_NOISE16_4 : pal = 26; break; // landscape 33
case FX_MODE_GLITTER : pal = 11; break; // rainbow colors
case FX_MODE_SUNRISE : pal = 35; break; // heat palette
case FX_MODE_RAILWAY : pal = 3; break; // prim + sec
case FX_MODE_2DSOAP : pal = 11; break; // rainbow colors
}
if (pal == 0) pal = _default_palette; //load default palette set in FX _data, party colors as default
switch (pal) {
case 0: //default palette. Exceptions for specific effects above
targetPalette = PartyColors_p; break;
case 1: //randomly generated palette
targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette()
targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette()
break;
case 2: {//primary color only
CRGB prim = gamma32(colors[0]);
@@ -236,23 +230,11 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec);
}
break;}
case 6: //Party colors
targetPalette = PartyColors_p; break;
case 7: //Cloud colors
targetPalette = CloudColors_p; break;
case 8: //Lava colors
targetPalette = LavaColors_p; break;
case 9: //Ocean colors
targetPalette = OceanColors_p; break;
case 10: //Forest colors
targetPalette = ForestColors_p; break;
case 11: //Rainbow colors
targetPalette = RainbowColors_p; break;
case 12: //Rainbow stripe colors
targetPalette = RainbowStripeColors_p; break;
default: //progmem palettes
if (pal>245) {
targetPalette = strip.customPalettes[255-pal]; // we checked bounds above
} else if (pal < 13) { // palette 6 - 12, fastled palettes
targetPalette = *fastledPalettes[pal-6];
} else {
byte tcp[72];
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72);
@@ -271,7 +253,7 @@ void Segment::startTransition(uint16_t dur) {
if (isInTransition()) return; // already in transition no need to store anything
// starting a transition has to occur before change so we get current values 1st
_t = new Transition(dur); // no previous transition running
_t = new(std::nothrow) Transition(dur); // no previous transition running
if (!_t) return; // failed to allocate data
//DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t);
@@ -314,15 +296,16 @@ void Segment::stopTransition() {
delete _t;
_t = nullptr;
}
_transitionprogress = 0xFFFFU; // stop means stop - transition has ended
}
// transition progression between 0-65535
uint16_t IRAM_ATTR Segment::progress() const {
inline void Segment::updateTransitionProgress() {
_transitionprogress = 0xFFFFU;
if (isInTransition()) {
unsigned diff = millis() - _t->_start;
if (_t->_dur > 0 && diff < _t->_dur) return diff * 0xFFFFU / _t->_dur;
if (_t->_dur > 0 && diff < _t->_dur) _transitionprogress = diff * 0xFFFFU / _t->_dur;
}
return 0xFFFFU;
}
#ifndef WLED_DISABLE_MODE_BLEND
@@ -344,7 +327,7 @@ void Segment::swapSegenv(tmpsegd_t &tmpSeg) {
tmpSeg._callT = call;
tmpSeg._dataT = data;
tmpSeg._dataLenT = _dataLen;
if (_t && &tmpSeg != &(_t->_segT)) {
if (isInTransition() && &tmpSeg != &(_t->_segT)) {
// swap SEGENV with transitional data
options = _t->_segT._optionsT;
for (size_t i=0; i<NUM_COLORS; i++) colors[i] = _t->_segT._colorT[i];
@@ -365,9 +348,9 @@ void Segment::swapSegenv(tmpsegd_t &tmpSeg) {
}
}
void Segment::restoreSegenv(tmpsegd_t &tmpSeg) {
void Segment::restoreSegenv(const tmpsegd_t &tmpSeg) {
//DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data);
if (_t && &(_t->_segT) != &tmpSeg) {
if (isInTransition() && &(_t->_segT) != &tmpSeg) {
// update possibly changed variables to keep old effect running correctly
_t->_segT._aux0T = aux0;
_t->_segT._aux1T = aux1;
@@ -396,9 +379,9 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) {
}
#endif
uint8_t IRAM_ATTR Segment::currentBri(bool useCct) const {
unsigned prog = progress();
if (prog < 0xFFFFU) {
uint8_t Segment::currentBri(bool useCct) const {
unsigned prog = isInTransition() ? progress() : 0xFFFFU;
if (prog < 0xFFFFU) { // progress() < 0xFFFF implies that _t is a valid pointer
unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog;
curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog);
return curBri / 0xFFFFU;
@@ -408,24 +391,39 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) const {
uint8_t Segment::currentMode() const {
#ifndef WLED_DISABLE_MODE_BLEND
unsigned prog = progress();
if (modeBlending && prog < 0xFFFFU) return _t->_modeT;
unsigned prog = isInTransition() ? progress() : 0xFFFFU;
if (modeBlending && prog < 0xFFFFU) return _t->_modeT; // progress() < 0xFFFF implies that _t is a valid pointer
#endif
return mode;
}
uint32_t IRAM_ATTR_YN Segment::currentColor(uint8_t slot) const {
uint32_t Segment::currentColor(uint8_t slot) const {
if (slot >= NUM_COLORS) slot = 0;
#ifndef WLED_DISABLE_MODE_BLEND
return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot];
return isInTransition() ? color_blend16(_t->_segT._colorT[slot], colors[slot], progress()) : colors[slot];
#else
return isInTransition() ? color_blend(_t->_colorT[slot], colors[slot], progress(), true) : colors[slot];
return isInTransition() ? color_blend16(_t->_colorT[slot], colors[slot], progress()) : colors[slot];
#endif
}
void Segment::setCurrentPalette() {
// pre-calculate drawing parameters for faster access (based on the idea from @softhack007 from MM fork)
void Segment::beginDraw() {
_vWidth = virtualWidth();
_vHeight = virtualHeight();
_vLength = virtualLength();
_segBri = currentBri();
unsigned prog = isInTransition() ? progress() : 0xFFFFU; // transition progress; 0xFFFFU = no transition active
// adjust gamma for effects
for (unsigned i = 0; i < NUM_COLORS; i++) {
#ifndef WLED_DISABLE_MODE_BLEND
uint32_t col = isInTransition() ? color_blend16(_t->_segT._colorT[i], colors[i], prog) : colors[i];
#else
uint32_t col = isInTransition() ? color_blend16(_t->_colorT[i], colors[i], prog) : colors[i];
#endif
_currentColors[i] = gamma32(col);
}
// load palette into _currentPalette
loadPalette(_currentPalette, palette);
unsigned prog = progress();
if (strip.paletteFade && prog < 0xFFFFU) {
// blend palettes
// there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time)
@@ -442,7 +440,7 @@ void Segment::handleRandomPalette() {
if ((uint16_t)((uint16_t)(millis() / 1000U) - _lastPaletteChange) > randomPaletteChangeTime){
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = (uint16_t)(millis() / 1000U);
_lastPaletteBlend = (uint16_t)((uint16_t)millis() - 512); // starts blending immediately
_lastPaletteBlend = (uint16_t)((uint16_t)millis() - 512); // starts blending immediately
}
// if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls)
@@ -455,8 +453,10 @@ void Segment::handleRandomPalette() {
nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48);
}
// segId is given when called from network callback, changes are queued if that segment is currently in its effect function
void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) {
// sets Segment geometry (length or width/height and grouping, spacing and offset as well as 2D mapping)
// strip must be suspended (strip.suspend()) before calling this function
// this function may call fill() to clear pixels if spacing or mapping changed (which requires setting _vWidth, _vHeight, _vLength or beginDraw())
void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y, uint8_t m12) {
// return if neither bounds nor grouping have changed
bool boundsUnchanged = (start == i1 && stop == i2);
#ifndef WLED_DISABLE_2D
@@ -464,11 +464,19 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t
#endif
if (boundsUnchanged
&& (!grp || (grouping == grp && spacing == spc))
&& (ofs == UINT16_MAX || ofs == offset)) return;
&& (ofs == UINT16_MAX || ofs == offset)
&& (m12 == map1D2D)
) return;
stateChanged = true; // send UDP/WS broadcast
if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing)
if (stop || spc != spacing || m12 != map1D2D) {
_vWidth = virtualWidth();
_vHeight = virtualHeight();
_vLength = virtualLength();
_segBri = currentBri();
fill(BLACK); // turn old segment range off or clears pixels if changing spacing (requires _vWidth/_vHeight/_vLength/_segBri)
}
if (grp) { // prevent assignment of 0
grouping = grp;
spacing = spc;
@@ -477,6 +485,7 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t
spacing = 0;
}
if (ofs < UINT16_MAX) offset = ofs;
map1D2D = constrain(m12, 0, 7);
DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1);
DEBUG_PRINT(','); DEBUG_PRINT(i2);
@@ -565,9 +574,9 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
if (modeBlending) startTransition(strip.getTransition()); // set effect transitions
#endif
mode = fx;
int sOpt;
// load default values from effect string
if (loadDefaults) {
int sOpt;
sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED;
sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY;
sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1;
@@ -584,6 +593,9 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business
sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) setPalette(sOpt); //else setPalette(0);
}
sOpt = extractModeDefaults(fx, "pal"); // always extract 'pal' to set _default_palette
if(sOpt <= 0) sOpt = 6; // partycolors if zero or not set
_default_palette = sOpt; // _deault_palette is loaded into pal0 in loadPalette() (if selected)
markForReset();
stateChanged = true; // send UDP/WS broadcast
}
@@ -602,14 +614,14 @@ Segment &Segment::setPalette(uint8_t pal) {
}
// 2D matrix
unsigned IRAM_ATTR Segment::virtualWidth() const {
unsigned Segment::virtualWidth() const {
unsigned groupLen = groupLength();
unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen;
if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED
return vWidth;
}
unsigned IRAM_ATTR Segment::virtualHeight() const {
unsigned Segment::virtualHeight() const {
unsigned groupLen = groupLength();
unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen;
if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED
@@ -653,7 +665,7 @@ static int getPinwheelLength(int vW, int vH) {
#endif
// 1D strip
uint16_t IRAM_ATTR Segment::virtualLength() const {
uint16_t Segment::virtualLength() const {
#ifndef WLED_DISABLE_2D
if (is2D()) {
unsigned vW = virtualWidth();
@@ -667,7 +679,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const {
vLen = max(vW,vH); // get the longest dimension
break;
case M12_pArc:
vLen = sqrt16(vH*vH + vW*vW); // use diagonal
vLen = sqrt32_bw(vH*vH + vW*vW); // use diagonal
break;
case M12_sPinwheel:
vLen = getPinwheelLength(vW, vH);
@@ -685,20 +697,33 @@ uint16_t IRAM_ATTR Segment::virtualLength() const {
return vLength;
}
void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const
{
if (!isActive()) return; // not active
if (!isActive() || i < 0) return; // not active or invalid index
#ifndef WLED_DISABLE_2D
int vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows)
int vStrip = 0;
#endif
i &= 0xFFFF;
if (i >= virtualLength() || i<0) return; // if pixel would fall out of segment just exit
int vL = vLength();
// if the 1D effect is using virtual strips "i" will have virtual strip id stored in upper 16 bits
// in such case "i" will be > virtualLength()
if (i >= vL) {
// check if this is a virtual strip
#ifndef WLED_DISABLE_2D
vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows)
i &= 0xFFFF; //truncate vstrip index
if (i >= vL) return; // if pixel would still fall out of segment just exit
#else
return;
#endif
}
#ifndef WLED_DISABLE_2D
if (is2D()) {
int vH = virtualHeight(); // segment height in logical pixels
int vW = virtualWidth();
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
// pre-scale color for all pixels
col = color_fade(col, _segBri);
_colorScaled = true;
switch (map1D2D) {
case M12_Pixels:
// use all available pixels as a long strip
@@ -706,12 +731,12 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
break;
case M12_pBar:
// expand 1D effect vertically or have it play on virtual strips
if (vStrip>0) setPixelColorXY(vStrip - 1, vH - i - 1, col);
else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col);
if (vStrip > 0) setPixelColorXY(vStrip - 1, vH - i - 1, col);
else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col);
break;
case M12_pArc:
// expand in circular fashion from center
if (i==0)
if (i == 0)
setPixelColorXY(0, 0, col);
else {
float r = i;
@@ -768,7 +793,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
// Odd rays start further from center if prevRay started at center.
static int prevRay = INT_MIN; // previous ray number
if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) {
int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel
int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel
posx += inc_x * jump;
posy += inc_y * jump;
}
@@ -790,13 +815,14 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
break;
}
}
_colorScaled = false;
return;
} else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) {
} else if (Segment::maxHeight != 1 && (width() == 1 || height() == 1)) {
if (start < Segment::maxWidth*Segment::maxHeight) {
// we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed)
int x = 0, y = 0;
if (virtualHeight()>1) y = i;
if (virtualWidth() >1) x = i;
if (vHeight() > 1) y = i;
if (vWidth() > 1) x = i;
setPixelColorXY(x, y, col);
return;
}
@@ -804,10 +830,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
#endif
unsigned len = length();
uint8_t _bri_t = currentBri();
if (_bri_t < 255) {
col = color_fade(col, _bri_t);
}
// if color is unscaled
if (!_colorScaled) col = color_fade(col, _segBri);
// expand pixel (taking into account start, grouping, spacing [and offset])
i = i * groupLength();
@@ -830,14 +854,14 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
indexMir += offset; // offset/phase
if (indexMir >= stop) indexMir -= len; // wrap
#ifndef WLED_DISABLE_MODE_BLEND
if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true);
if (_modeBlend) tmpCol = color_blend16(strip.getPixelColor(indexMir), col, uint16_t(0xFFFFU - progress()));
#endif
strip.setPixelColor(indexMir, tmpCol);
}
indexSet += offset; // offset/phase
if (indexSet >= stop) indexSet -= len; // wrap
#ifndef WLED_DISABLE_MODE_BLEND
if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true);
if (_modeBlend) tmpCol = color_blend16(strip.getPixelColor(indexSet), col, uint16_t(0xFFFFU - progress()));
#endif
strip.setPixelColor(indexSet, tmpCol);
}
@@ -846,7 +870,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
#ifdef WLED_USE_AA_PIXELS
// anti-aliased normalized version of setPixelColor()
void Segment::setPixelColor(float i, uint32_t col, bool aa)
void Segment::setPixelColor(float i, uint32_t col, bool aa) const
{
if (!isActive()) return; // not active
int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows)
@@ -882,26 +906,23 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa)
uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
{
if (!isActive()) return 0; // not active
#ifndef WLED_DISABLE_2D
int vStrip = i>>16;
#endif
i &= 0xFFFF;
#ifndef WLED_DISABLE_2D
if (is2D()) {
int vH = virtualHeight(); // segment height in logical pixels
int vW = virtualWidth();
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
switch (map1D2D) {
case M12_Pixels:
return getPixelColorXY(i % vW, i / vW);
break;
case M12_pBar:
if (vStrip>0) return getPixelColorXY(vStrip - 1, vH - i -1);
else return getPixelColorXY(0, vH - i -1);
break;
case M12_pBar: {
int vStrip = i>>16; // virtual strips are only relevant in Bar expansion mode
if (vStrip > 0) return getPixelColorXY(vStrip - 1, vH - (i & 0xFFFF) -1);
else return getPixelColorXY(0, vH - i -1);
break; }
case M12_pArc:
if (i >= vW && i >= vH) {
unsigned vI = sqrt16(i*i/2);
unsigned vI = sqrt32_bw(i*i/2);
return getPixelColorXY(vI,vI); // use diagonal
}
case M12_pCorner:
@@ -942,7 +963,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
}
#endif
if (reverse) i = virtualLength() - i - 1;
if (reverse) i = vLength() - i - 1;
i *= groupLength();
i += start;
// offset/phase
@@ -951,7 +972,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
return strip.getPixelColor(i);
}
uint8_t Segment::differs(Segment& b) const {
uint8_t Segment::differs(const Segment& b) const {
uint8_t d = 0;
if (start != b.start) d |= SEG_DIFFERS_BOUNDS;
if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS;
@@ -1031,12 +1052,16 @@ void Segment::refreshLightCapabilities() {
*/
void Segment::fill(uint32_t c) {
if (!isActive()) return; // not active
const int cols = is2D() ? virtualWidth() : virtualLength();
const int rows = virtualHeight(); // will be 1 for 1D
const int cols = is2D() ? vWidth() : vLength();
const int rows = vHeight(); // will be 1 for 1D
// pre-scale color for all pixels
c = color_fade(c, _segBri);
_colorScaled = true;
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
if (is2D()) setPixelColorXY(x, y, c);
else setPixelColor(x, c);
}
_colorScaled = false;
}
/*
@@ -1044,8 +1069,8 @@ void Segment::fill(uint32_t c) {
*/
void Segment::fade_out(uint8_t rate) {
if (!isActive()) return; // not active
const int cols = is2D() ? virtualWidth() : virtualLength();
const int rows = virtualHeight(); // will be 1 for 1D
const int cols = is2D() ? vWidth() : vLength();
const int rows = vHeight(); // will be 1 for 1D
rate = (255-rate) >> 1;
float mappedRate = 1.0f / (float(rate) + 1.1f);
@@ -1083,8 +1108,8 @@ void Segment::fade_out(uint8_t rate) {
// fades all pixels to black using nscale8()
void Segment::fadeToBlackBy(uint8_t fadeBy) {
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
const int cols = is2D() ? virtualWidth() : virtualLength();
const int rows = virtualHeight(); // will be 1 for 1D
const int cols = is2D() ? vWidth() : vLength();
const int rows = vHeight(); // will be 1 for 1D
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy));
@@ -1094,22 +1119,23 @@ void Segment::fadeToBlackBy(uint8_t fadeBy) {
/*
* blurs segment content, source: FastLED colorutils.cpp
* Note: for blur_amount > 215 this function does not work properly (creates alternating pattern)
*/
void Segment::blur(uint8_t blur_amount, bool smear) {
if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur"
#ifndef WLED_DISABLE_2D
if (is2D()) {
// compatibility with 2D
blur2D(blur_amount, smear);
blur2D(blur_amount, blur_amount, smear); // symmetrical 2D blur
//box_blur(map(blur_amount,1,255,1,3), smear);
return;
}
#endif
uint8_t keep = smear ? 255 : 255 - blur_amount;
uint8_t seep = blur_amount >> (1 + smear);
unsigned vlength = virtualLength();
uint8_t seep = blur_amount >> 1;
unsigned vlength = vLength();
uint32_t carryover = BLACK;
uint32_t lastnew;
uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
uint32_t last;
uint32_t curnew = BLACK;
for (unsigned i = 0; i < vlength; i++) {
@@ -1117,12 +1143,11 @@ void Segment::blur(uint8_t blur_amount, bool smear) {
uint32_t part = color_fade(cur, seep);
curnew = color_fade(cur, keep);
if (i > 0) {
if (carryover) curnew = color_add(curnew, carryover, true);
uint32_t prev = color_add(lastnew, part, true);
if (carryover) curnew = color_add(curnew, carryover);
uint32_t prev = color_add(lastnew, part);
// optimization: only set pixel if color has changed
if (last != prev) setPixelColor(i - 1, prev);
} else // first pixel
setPixelColor(i, curnew);
} else setPixelColor(i, curnew); // first pixel
lastnew = curnew;
last = cur; // save original value for comparison on next iteration
carryover = part;
@@ -1137,11 +1162,11 @@ void Segment::blur(uint8_t blur_amount, bool smear) {
*/
uint32_t Segment::color_wheel(uint8_t pos) const {
if (palette) return color_from_palette(pos, false, true, 0); // perhaps "strip.paletteBlend < 2" should be better instead of "true"
uint8_t w = W(currentColor(0));
uint8_t w = W(getCurrentColor(0));
pos = 255 - pos;
if (pos < 85) {
return RGBW32((255 - pos * 3), 0, (pos * 3), w);
} else if(pos < 170) {
} else if (pos < 170) {
pos -= 85;
return RGBW32(0, (pos * 3), (255 - pos * 3), w);
} else {
@@ -1160,18 +1185,21 @@ uint32_t Segment::color_wheel(uint8_t pos) const {
* @returns Single color from palette
*/
uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) const {
uint32_t color = gamma32(currentColor(mcol));
uint32_t color = getCurrentColor(mcol < NUM_COLORS ? mcol : 0);
// default palette or no RGB support on segment
if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true);
if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) {
return color_fade(color, pbri, true);
}
const int vL = vLength();
unsigned paletteIndex = i;
if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1);
if (mapping && vL > 1) paletteIndex = (i*255)/(vL -1);
// paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined)
if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
CRGB fastled_col = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global
CRGBW palcol = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global
palcol.w = W(color);
return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color));
return palcol.color32;
}
@@ -1204,7 +1232,7 @@ void WS2812FX::finalizeInit() {
static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins),
"The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES");
unsigned prevLen = 0;
unsigned pinsIndex = 0;
for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
@@ -1215,7 +1243,7 @@ void WS2812FX::finalizeInit() {
// if we need more pins than available all outputs have been configured
if (pinsIndex + busPins > defNumPins) break;
// Assign all pins first so we can check for conflicts on this bus
for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j];
@@ -1286,14 +1314,9 @@ void WS2812FX::finalizeInit() {
_isOffRefreshRequired |= bus->isOffRefreshRequired() && !bus->isPWM(); // use refresh bit for phase shift with analog
unsigned busEnd = bus->getStart() + bus->getLength();
if (busEnd > _length) _length = busEnd;
#ifdef ESP8266
// why do we need to reinitialise GPIO3???
//if (!bus->isDigital() || bus->is2Pin()) continue;
//uint8_t pins[5];
//if (!bus->getPins(pins)) continue;
//BusDigital* bd = static_cast<BusDigital*>(bus);
//if (pins[0] == 3) bd->reinit();
#endif
// This must be done after all buses have been created, as some kinds (parallel I2S) interact
bus->begin();
}
Segment::maxWidth = _length;
@@ -1309,7 +1332,14 @@ void WS2812FX::finalizeInit() {
void WS2812FX::service() {
unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days
now = nowUp + timebase;
if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return;
if (_suspend) return;
unsigned long elapsed = nowUp - _lastServiceShow;
if (elapsed <= MIN_FRAME_DELAY) return; // keep wifi alive - no matter if triggered or unlimited
if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { // unlimited mode = no frametime
if (elapsed < _frametime) return; // too early for service
}
bool doShow = false;
_isServicing = true;
@@ -1326,18 +1356,13 @@ void WS2812FX::service() {
if (!seg.isActive()) continue;
// last condition ensures all solid segments are updated at the same time
if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC))
if (nowUp >= seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC))
{
doShow = true;
unsigned frameDelay = FRAMETIME;
if (!seg.freeze) { //only run effect function if not frozen
int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based)
_virtualSegmentLength = seg.virtualLength(); //SEGLEN
_colors_t[0] = gamma32(seg.currentColor(0));
_colors_t[1] = gamma32(seg.currentColor(1));
_colors_t[2] = gamma32(seg.currentColor(2));
seg.setCurrentPalette(); // load actual palette
// when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values
if (cctFromRgb) BusManager::setSegmentCCT(-1);
@@ -1349,13 +1374,14 @@ void WS2812FX::service() {
// overwritten by later effect. To enable seamless blending for every effect, additional LED buffer
// would need to be allocated for each effect and then blended together for each pixel.
[[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition
frameDelay = (*_mode[seg.mode])(); // run new/current mode
seg.beginDraw(); // set up parameters for get/setPixelColor()
frameDelay = (*_mode[seg.mode])(); // run new/current mode
#ifndef WLED_DISABLE_MODE_BLEND
if (modeBlending && seg.mode != tmpMode) {
Segment::tmpsegd_t _tmpSegData;
Segment::modeBlend(true); // set semaphore
seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state)
_virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed)
seg.beginDraw(); // set up parameters for get/setPixelColor()
unsigned d2 = (*_mode[tmpMode])(); // run old mode
seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state)
frameDelay = min(frameDelay,d2); // use shortest delay
@@ -1371,24 +1397,24 @@ void WS2812FX::service() {
}
_segment_index++;
}
_virtualSegmentLength = 0;
_isServicing = false;
_triggered = false;
#ifdef WLED_DEBUG
if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
#endif
if (doShow) {
yield();
Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette
show();
_lastServiceShow = nowUp; // update timestamp, for precise FPS control
}
#ifdef WLED_DEBUG
if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
#endif
}
void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) {
void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) const {
i = getMappedPixelIndex(i);
if (i >= _length) return;
BusManager::setPixelColor(i, col);
@@ -1404,13 +1430,13 @@ void WS2812FX::show() {
// avoid race condition, capture _callback value
show_callback callback = _callback;
if (callback) callback();
unsigned long showNow = millis();
// some buses send asynchronously and this method will return before
// all of the data has been sent.
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
BusManager::show();
unsigned long showNow = millis();
size_t diff = showNow - _lastShow;
if (diff > 0) { // skip calculation if no time has passed
@@ -1420,47 +1446,10 @@ void WS2812FX::show() {
}
}
/**
* Returns a true value if any of the strips are still being updated.
* On some hardware (ESP32), strip updates are done asynchronously.
*/
bool WS2812FX::isUpdating() const {
return !BusManager::canAllShow();
}
/**
* Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough.
* Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies
*/
uint16_t WS2812FX::getFps() const {
if (millis() - _lastShow > 2000) return 0;
return (FPS_MULTIPLIER * _cumulativeFps) >> FPS_CALC_SHIFT; // _cumulativeFps is stored in fixed point
}
void WS2812FX::setTargetFps(uint8_t fps) {
if (fps > 0 && fps <= 120) _targetFps = fps;
_frametime = 1000 / _targetFps;
}
void WS2812FX::setMode(uint8_t segid, uint8_t m) {
if (segid >= _segments.size()) return;
if (m >= getModeCount()) m = getModeCount() - 1;
if (_segments[segid].mode != m) {
_segments[segid].setMode(m); // do not load defaults
}
}
//applies to all active and selected segments
void WS2812FX::setColor(uint8_t slot, uint32_t c) {
if (slot >= NUM_COLORS) return;
for (segment &seg : _segments) {
if (seg.isActive() && seg.isSelected()) {
seg.setColor(slot, c);
}
}
void WS2812FX::setTargetFps(unsigned fps) {
if (fps <= 250) _targetFps = fps;
if (_targetFps > 0) _frametime = 1000 / _targetFps;
else _frametime = MIN_FRAME_DELAY; // unlimited mode
}
void WS2812FX::setCCT(uint16_t k) {
@@ -1487,7 +1476,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) {
BusManager::setBrightness(b);
if (!direct) {
unsigned long t = millis();
if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon
if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_FRAME_DELAY) trigger(); //apply brightness change immediately if no refresh soon
}
}
@@ -1509,7 +1498,7 @@ uint8_t WS2812FX::getFirstSelectedSegId() const {
return getMainSegmentId();
}
void WS2812FX::setMainSegmentId(uint8_t n) {
void WS2812FX::setMainSegmentId(unsigned n) {
_mainSegment = 0;
if (n < _segments.size()) {
_mainSegment = n;
@@ -1585,23 +1574,10 @@ void WS2812FX::purgeSegments() {
}
}
Segment& WS2812FX::getSegment(uint8_t id) {
Segment& WS2812FX::getSegment(unsigned id) {
return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors
}
// sets new segment bounds, queues if that segment is currently running
void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) {
if (segId >= getSegmentsNum()) {
if (i2 <= i1) return; // do not append empty/inactive segments
appendSegment(Segment(0, strip.getLengthTotal()));
segId = getSegmentsNum()-1; // segments are added at the end of list
}
suspend();
_segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY);
resume();
if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector
}
void WS2812FX::resetSegments() {
_segments.clear(); // destructs all Segment as part of clearing
#ifndef WLED_DISABLE_2D
@@ -1719,9 +1695,9 @@ void WS2812FX::fixInvalidSegments() {
//true if all segments align with a bus, or if a segment covers the total length
//irrelevant in 2D set-up
bool WS2812FX::checkSegmentAlignment() {
bool WS2812FX::checkSegmentAlignment() const {
bool aligned = false;
for (segment &seg : _segments) {
for (const segment &seg : _segments) {
for (unsigned b = 0; b<BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b);
if (seg.start == bus->getStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true;
@@ -1800,7 +1776,7 @@ void WS2812FX::loadCustomPalettes() {
}
//load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
bool WS2812FX::deserializeMap(uint8_t n) {
bool WS2812FX::deserializeMap(unsigned n) {
// 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one.
char fileName[32];
@@ -1838,8 +1814,8 @@ bool WS2812FX::deserializeMap(uint8_t n) {
Segment::maxHeight = min(max(root[F("height")].as<int>(), 1), 128);
}
if (customMappingTable) delete[] customMappingTable;
customMappingTable = new uint16_t[getLengthTotal()];
if (customMappingTable) free(customMappingTable);
customMappingTable = static_cast<uint16_t*>(malloc(sizeof(uint16_t)*getLengthTotal()));
if (customMappingTable) {
DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName);
@@ -1893,14 +1869,6 @@ bool WS2812FX::deserializeMap(uint8_t n) {
return (customMappingSize > 0);
}
uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) const {
// convert logical address to physical
if (index < customMappingSize
&& (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index];
return index;
}
WS2812FX* WS2812FX::instance = nullptr;
@@ -1913,5 +1881,5 @@ const char JSON_palette_names[] PROGMEM = R"=====([
"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura",
"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf",
"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide",
"Candy2"
"Candy2","Traffic Light"
])=====";

View File

@@ -126,10 +126,10 @@ void onAlexaChange(EspalexaDevice* dev)
} else {
colorKtoRGB(k, rgbw);
}
strip.setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
strip.getMainSegment().setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
} else {
uint32_t color = dev->getRGB();
strip.setColor(0, color);
strip.getMainSegment().setColor(0, color);
}
stateUpdated(CALL_MODE_ALEXA);
}

View File

@@ -27,7 +27,7 @@ extern bool cctICused;
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
//udp.cpp
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri=255, bool isRGBW=false);
// enable additional debug output
#if defined(WLED_DEBUG_HOST)
@@ -121,7 +121,7 @@ uint8_t *Bus::allocateData(size_t size) {
}
BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
BusDigital::BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814))
, _skip(bc.skipAmount) //sacrificial pixels
, _colorOrder(bc.colorOrder)
@@ -150,21 +150,11 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
//_buffering = bc.doubleBuffer;
uint16_t lenToCreate = bc.count;
if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus
_busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr, _frequencykHz);
_busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr);
_valid = (_busPtr != nullptr);
DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax);
}
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
#ifndef MA_FOR_ESP
#ifdef ESP8266
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
#else
#define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA)
#endif
#endif
//DISCLAIMER
//The following function attemps to calculate the current LED power usage,
//and will limit the brightness to stay below a set amperage threshold.
@@ -306,22 +296,22 @@ void BusDigital::setStatusPixel(uint32_t c) {
}
}
void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) {
void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid) return;
uint8_t cctWW = 0, cctCW = 0;
if (hasWhite()) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
if (_data) {
size_t offset = pix * getNumberOfChannels();
uint8_t* dataptr = _data + offset;
if (hasRGB()) {
_data[offset++] = R(c);
_data[offset++] = G(c);
_data[offset++] = B(c);
*dataptr++ = R(c);
*dataptr++ = G(c);
*dataptr++ = B(c);
}
if (hasWhite()) _data[offset++] = W(c);
if (hasWhite()) *dataptr++ = W(c);
// unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT
// we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio)
if (hasCCT()) _data[offset] = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it
if (hasCCT()) *dataptr = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it
} else {
if (_reversed) pix = _len - pix -1;
pix += _skip;
@@ -336,16 +326,22 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) {
case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break;
}
}
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW);
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW);
uint16_t wwcw = 0;
if (hasCCT()) {
uint8_t cctWW = 0, cctCW = 0;
Bus::calculateCCT(c, cctWW, cctCW);
wwcw = (cctCW<<8) | cctWW;
}
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
}
}
// returns original color if global buffering is enabled, else returns lossly restored color from bus
uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const {
uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
if (!_valid) return 0;
if (_data) {
size_t offset = pix * getNumberOfChannels();
const size_t offset = pix * getNumberOfChannels();
uint32_t c;
if (!hasRGB()) {
c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]);
@@ -356,7 +352,7 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const {
} else {
if (_reversed) pix = _len - pix -1;
pix += _skip;
unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri);
if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
unsigned r = R(c);
@@ -410,9 +406,9 @@ std::vector<LEDType> BusDigital::getLEDTypes() {
};
}
void BusDigital::reinit() {
void BusDigital::begin() {
if (!_valid) return;
PolyBus::begin(_busPtr, _iType, _pins);
PolyBus::begin(_busPtr, _iType, _pins, _frequencykHz);
}
void BusDigital::cleanup() {
@@ -452,7 +448,7 @@ void BusDigital::cleanup() {
#endif
#endif
BusPwm::BusPwm(BusConfig &bc)
BusPwm::BusPwm(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed, bc.refreshReq) // hijack Off refresh flag to indicate usage of dithering
{
if (!isPWM(bc.type)) return;
@@ -501,7 +497,7 @@ BusPwm::BusPwm(BusConfig &bc)
DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]);
}
void BusPwm::setPixelColor(uint16_t pix, uint32_t c) {
void BusPwm::setPixelColor(unsigned pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel
if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
@@ -538,7 +534,7 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) {
}
//does no index check
uint32_t BusPwm::getPixelColor(uint16_t pix) const {
uint32_t BusPwm::getPixelColor(unsigned pix) const {
if (!_valid) return 0;
// TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented)
switch (_type) {
@@ -567,19 +563,15 @@ void BusPwm::show() {
const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8)
[[maybe_unused]] const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits)
// use CIE brightness formula (cubic) to fit (or approximate linearity of) human eye perceived brightness
// the formula is based on 12 bit resolution as there is no need for greater precision
// use CIE brightness formula (linear + cubic) to approximate human eye perceived brightness
// see: https://en.wikipedia.org/wiki/Lightness
unsigned pwmBri = (unsigned)_bri * 100; // enlarge to use integer math for linear response
if (pwmBri < 2040) {
// linear response for values [0-20]
pwmBri = ((pwmBri << 12) + 115043) / 230087; //adding '0.5' before division for correct rounding
} else {
// cubic response for values [21-255]
pwmBri += 4080;
float temp = (float)pwmBri / 29580.0f;
temp = temp * temp * temp * (float)maxBri;
pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri]
unsigned pwmBri = _bri;
if (pwmBri < 21) { // linear response for values [0-20]
pwmBri = (pwmBri * maxBri + 2300 / 2) / 2300 ; // adding '0.5' before division for correct rounding, 2300 gives a good match to CIE curve
} else { // cubic response for values [21-255]
float temp = float(pwmBri + 41) / float(255 + 41); // 41 is to match offset & slope to linear part
temp = temp * temp * temp * (float)maxBri;
pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri] C
}
[[maybe_unused]] unsigned hPoint = 0; // phase shift (0 - maxBri)
@@ -654,7 +646,7 @@ void BusPwm::deallocatePins() {
}
BusOnOff::BusOnOff(BusConfig &bc)
BusOnOff::BusOnOff(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed)
, _onoffdata(0)
{
@@ -674,7 +666,7 @@ BusOnOff::BusOnOff(BusConfig &bc)
DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin);
}
void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) {
void BusOnOff::setPixelColor(unsigned pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel
c = autoWhiteCalc(c);
uint8_t r = R(c);
@@ -684,7 +676,7 @@ void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) {
_data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0;
}
uint32_t BusOnOff::getPixelColor(uint16_t pix) const {
uint32_t BusOnOff::getPixelColor(unsigned pix) const {
if (!_valid) return 0;
return RGBW32(_data[0], _data[0], _data[0], _data[0]);
}
@@ -707,7 +699,7 @@ std::vector<LEDType> BusOnOff::getLEDTypes() {
};
}
BusNetwork::BusNetwork(BusConfig &bc)
BusNetwork::BusNetwork(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, bc.count)
, _broadcastLock(false)
{
@@ -734,7 +726,7 @@ BusNetwork::BusNetwork(BusConfig &bc)
DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]);
}
void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
void BusNetwork::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid || pix >= _len) return;
if (_hasWhite) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
@@ -745,7 +737,7 @@ void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
if (_hasWhite) _data[offset+3] = W(c);
}
uint32_t BusNetwork::getPixelColor(uint16_t pix) const {
uint32_t BusNetwork::getPixelColor(unsigned pix) const {
if (!_valid || pix >= _len) return 0;
unsigned offset = pix * _UDPchannels;
return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0));
@@ -786,7 +778,7 @@ void BusNetwork::cleanup() {
//utility to get the approx. memory usage of a given BusConfig
uint32_t BusManager::memUsage(BusConfig &bc) {
uint32_t BusManager::memUsage(const BusConfig &bc) {
if (Bus::isOnOff(bc.type) || Bus::isPWM(bc.type)) return OUTPUT_MAX_PINS;
unsigned len = bc.count + bc.skipAmount;
@@ -811,7 +803,7 @@ uint32_t BusManager::memUsage(unsigned maxChannels, unsigned maxCount, unsigned
return (maxChannels * maxCount * minBuses * multiplier);
}
int BusManager::add(BusConfig &bc) {
int BusManager::add(const BusConfig &bc) {
if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1;
if (Bus::isVirtual(bc.type)) {
busses[numBusses] = new BusNetwork(bc);
@@ -910,7 +902,7 @@ void BusManager::on() {
if (busses[i]->isDigital() && busses[i]->getPins(pins)) {
if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) {
BusDigital *bus = static_cast<BusDigital*>(busses[i]);
bus->reinit();
bus->begin();
break;
}
}
@@ -943,7 +935,6 @@ void BusManager::show() {
busses[i]->show();
_milliAmpsUsed += busses[i]->getUsedCurrent();
}
if (_milliAmpsUsed) _milliAmpsUsed += MA_FOR_ESP;
}
void BusManager::setStatusPixel(uint32_t c) {
@@ -952,7 +943,7 @@ void BusManager::setStatusPixel(uint32_t c) {
}
}
void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c) {
void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) {
for (unsigned i = 0; i < numBusses; i++) {
unsigned bstart = busses[i]->getStart();
if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue;
@@ -975,7 +966,7 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) {
Bus::setCCT(cct);
}
uint32_t BusManager::getPixelColor(uint16_t pix) {
uint32_t BusManager::getPixelColor(unsigned pix) {
for (unsigned i = 0; i < numBusses; i++) {
unsigned bstart = busses[i]->getStart();
if (!busses[i]->containsPixel(pix)) continue;

View File

@@ -6,6 +6,7 @@
*/
#include "const.h"
#include "pin_manager.h"
#include <vector>
//colors.cpp
@@ -79,13 +80,14 @@ class Bus {
virtual ~Bus() {} //throw the bus under the bus
virtual void begin() {};
virtual void show() = 0;
virtual bool canShow() const { return true; }
virtual void setStatusPixel(uint32_t c) {}
virtual void setPixelColor(uint16_t pix, uint32_t c) = 0;
virtual void setPixelColor(unsigned pix, uint32_t c) = 0;
virtual void setBrightness(uint8_t b) { _bri = b; };
virtual void setColorOrder(uint8_t co) {}
virtual uint32_t getPixelColor(uint16_t pix) const { return 0; }
virtual uint32_t getPixelColor(unsigned pix) const { return 0; }
virtual uint8_t getPins(uint8_t* pinArray = nullptr) const { return 0; }
virtual uint16_t getLength() const { return isOk() ? _len : 0; }
virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; }
@@ -109,7 +111,7 @@ class Bus {
inline void setStart(uint16_t start) { _start = start; }
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; }
inline uint8_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); }
inline uint32_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); }
inline uint16_t getStart() const { return _start; }
inline uint8_t getType() const { return _type; }
inline bool isOk() const { return _valid; }
@@ -118,8 +120,8 @@ class Bus {
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes
static constexpr uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); }
static constexpr uint32_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr uint32_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); }
static constexpr bool hasRGB(uint8_t type) {
return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF);
}
@@ -196,16 +198,16 @@ class Bus {
class BusDigital : public Bus {
public:
BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com);
BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com);
~BusDigital() { cleanup(); }
void show() override;
bool canShow() const override;
void setBrightness(uint8_t b) override;
void setStatusPixel(uint32_t c) override;
[[gnu::hot]] void setPixelColor(uint16_t pix, uint32_t c) override;
[[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
void setColorOrder(uint8_t colorOrder) override;
[[gnu::hot]] uint32_t getPixelColor(uint16_t pix) const override;
[[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override;
uint8_t getColorOrder() const override { return _colorOrder; }
uint8_t getPins(uint8_t* pinArray = nullptr) const override;
uint8_t skippedLeds() const override { return _skip; }
@@ -213,7 +215,7 @@ class BusDigital : public Bus {
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
uint16_t getUsedCurrent() const override { return _milliAmpsTotal; }
uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
void reinit();
void begin() override;
void cleanup();
static std::vector<LEDType> getLEDTypes();
@@ -248,11 +250,11 @@ class BusDigital : public Bus {
class BusPwm : public Bus {
public:
BusPwm(BusConfig &bc);
BusPwm(const BusConfig &bc);
~BusPwm() { cleanup(); }
void setPixelColor(uint16_t pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override; //does no index check
void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(unsigned pix) const override; //does no index check
uint8_t getPins(uint8_t* pinArray = nullptr) const override;
uint16_t getFrequency() const override { return _frequency; }
void show() override;
@@ -275,11 +277,11 @@ class BusPwm : public Bus {
class BusOnOff : public Bus {
public:
BusOnOff(BusConfig &bc);
BusOnOff(const BusConfig &bc);
~BusOnOff() { cleanup(); }
void setPixelColor(uint16_t pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override;
void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(unsigned pix) const override;
uint8_t getPins(uint8_t* pinArray) const override;
void show() override;
void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); }
@@ -294,12 +296,12 @@ class BusOnOff : public Bus {
class BusNetwork : public Bus {
public:
BusNetwork(BusConfig &bc);
BusNetwork(const BusConfig &bc);
~BusNetwork() { cleanup(); }
bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out
void setPixelColor(uint16_t pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override;
void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(unsigned pix) const override;
uint8_t getPins(uint8_t* pinArray = nullptr) const override;
void show() override;
void cleanup();
@@ -362,17 +364,27 @@ struct BusConfig {
};
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
#ifndef MA_FOR_ESP
#ifdef ESP8266
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
#else
#define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA)
#endif
#endif
class BusManager {
public:
BusManager() {};
//utility to get the approx. memory usage of a given BusConfig
static uint32_t memUsage(BusConfig &bc);
static uint32_t memUsage(const BusConfig &bc);
static uint32_t memUsage(unsigned channels, unsigned count, unsigned buses = 1);
static uint16_t currentMilliamps() { return _milliAmpsUsed; }
static uint16_t currentMilliamps() { return _milliAmpsUsed + MA_FOR_ESP; }
static uint16_t ablMilliampsMax() { return _milliAmpsMax; }
static int add(BusConfig &bc);
static int add(const BusConfig &bc);
static void useParallelOutput(); // workaround for inaccessible PolyBus
//do not call this method from system context (network callback)
@@ -384,13 +396,13 @@ class BusManager {
static void show();
static bool canAllShow();
static void setStatusPixel(uint32_t c);
[[gnu::hot]] static void setPixelColor(uint16_t pix, uint32_t c);
[[gnu::hot]] static void setPixelColor(unsigned pix, uint32_t c);
static void setBrightness(uint8_t b);
// for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K
// WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT()
static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false);
static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;}
static uint32_t getPixelColor(uint16_t pix);
[[gnu::hot]] static uint32_t getPixelColor(unsigned pix);
static inline int16_t getSegmentCCT() { return Bus::getCCT(); }
static Bus* getBus(uint8_t busNr);

View File

@@ -336,7 +336,7 @@ class PolyBus {
// initialize SPI bus speed for DotStar methods
template <class T>
static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz = 0U) {
static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz /* 0 == use default */) {
T dotStar_strip = static_cast<T>(busPtr);
#ifdef ESP8266
dotStar_strip->Begin();
@@ -363,7 +363,7 @@ class PolyBus {
tm1914_strip->SetPixelSettings(NeoTm1914Settings()); //NeoTm1914_Mode_DinFdinAutoSwitch, NeoTm1914_Mode_DinOnly, NeoTm1914_Mode_FdinOnly
}
static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz = 0U) {
static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz /* only used by DotStar */) {
switch (busType) {
case I_NONE: break;
#ifdef ESP8266
@@ -480,7 +480,7 @@ class PolyBus {
}
}
static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel, uint16_t clock_kHz = 0U) {
static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) {
#if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
// NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation
// since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation
@@ -597,7 +597,7 @@ class PolyBus {
case I_HS_P98_3: busPtr = new B_HS_P98_3(len, pins[1], pins[0]); break;
case I_SS_P98_3: busPtr = new B_SS_P98_3(len, pins[1], pins[0]); break;
}
begin(busPtr, busType, pins, clock_kHz);
return busPtr;
}

View File

@@ -29,7 +29,7 @@ void shortPressAction(uint8_t b)
#ifndef WLED_DISABLE_MQTT
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
char subuf[MQTT_MAX_TOPIC_LEN + 32];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "short");
}
@@ -62,7 +62,7 @@ void longPressAction(uint8_t b)
#ifndef WLED_DISABLE_MQTT
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
char subuf[MQTT_MAX_TOPIC_LEN + 32];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "long");
}
@@ -83,19 +83,19 @@ void doublePressAction(uint8_t b)
#ifndef WLED_DISABLE_MQTT
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
char subuf[MQTT_MAX_TOPIC_LEN + 32];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "double");
}
#endif
}
bool isButtonPressed(uint8_t i)
bool isButtonPressed(uint8_t b)
{
if (btnPin[i]<0) return false;
unsigned pin = btnPin[i];
if (btnPin[b]<0) return false;
unsigned pin = btnPin[b];
switch (buttonType[i]) {
switch (buttonType[b]) {
case BTN_TYPE_NONE:
case BTN_TYPE_RESERVED:
break;
@@ -113,7 +113,7 @@ bool isButtonPressed(uint8_t i)
#ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt)
if (touchInterruptGetLastStatus(pin)) return true;
#else
if (digitalPinToTouchChannel(btnPin[i]) >= 0 && touchRead(pin) <= touchThreshold) return true;
if (digitalPinToTouchChannel(btnPin[b]) >= 0 && touchRead(pin) <= touchThreshold) return true;
#endif
#endif
break;
@@ -151,7 +151,7 @@ void handleSwitch(uint8_t b)
#ifndef WLED_DISABLE_MQTT
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
char subuf[MQTT_MAX_TOPIC_LEN + 32];
if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b);
else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on");
@@ -375,6 +375,7 @@ void handleIO()
if (rlyPin>=0) {
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
digitalWrite(rlyPin, rlyMde);
delay(50); // wait for relay to switch and power to stabilize
}
offMode = false;
}

View File

@@ -436,13 +436,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
else gammaCorrectBri = false;
if (light_gc_col > 1.0f) gammaCorrectCol = true;
else gammaCorrectCol = false;
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) {
if (gammaCorrectVal != 2.8f) NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal);
} else {
if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) {
gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false;
gammaCorrectCol = false;
}
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table
JsonObject light_tr = light["tr"];
CJSON(fadeTransition, light_tr["mode"]);
@@ -523,6 +522,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
tdd = if_live[F("timeout")] | -1;
if (tdd >= 0) realtimeTimeoutMs = tdd * 100;
#ifdef WLED_ENABLE_DMX_INPUT
CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]);
CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]);
CJSON(dmxInputEnablePin, if_live_dmx[F("inputEnablePin")]);
CJSON(dmxInputPort, if_live_dmx[F("dmxInputPort")]);
#endif
CJSON(arlsForceMaxBri, if_live[F("maxbri")]);
CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false
CJSON(arlsOffset, if_live[F("offset")]); // 0
@@ -1002,6 +1009,12 @@ void serializeConfig() {
if_live_dmx[F("addr")] = DMXAddress;
if_live_dmx[F("dss")] = DMXSegmentSpacing;
if_live_dmx["mode"] = DMXMode;
#ifdef WLED_ENABLE_DMX_INPUT
if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin;
if_live_dmx[F("inputTxPin")] = dmxInputReceivePin;
if_live_dmx[F("inputEnablePin")] = dmxInputEnablePin;
if_live_dmx[F("dmxInputPort")] = dmxInputPort;
#endif
if_live[F("timeout")] = realtimeTimeoutMs / 100;
if_live[F("maxbri")] = arlsForceMaxBri;

View File

@@ -5,61 +5,56 @@
*/
/*
* color blend function
* color blend function, based on FastLED blend function
* the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB
*/
uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) {
if (blend == 0) return color1;
unsigned blendmax = b16 ? 0xFFFF : 0xFF;
if (blend == blendmax) return color2;
unsigned shift = b16 ? 16 : 8;
uint32_t w1 = W(color1);
uint32_t r1 = R(color1);
uint32_t g1 = G(color1);
uint32_t b1 = B(color1);
uint32_t w2 = W(color2);
uint32_t r2 = R(color2);
uint32_t g2 = G(color2);
uint32_t b2 = B(color2);
uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift;
uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift;
uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift;
uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift;
return RGBW32(r3, g3, b3, w3);
uint32_t color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
// min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance
uint32_t rb1 = color1 & 0x00FF00FF;
uint32_t wg1 = (color1>>8) & 0x00FF00FF;
uint32_t rb2 = color2 & 0x00FF00FF;
uint32_t wg2 = (color2>>8) & 0x00FF00FF;
uint32_t rb3 = ((((rb1 << 8) | rb2) + (rb2 * blend) - (rb1 * blend)) >> 8) & 0x00FF00FF;
uint32_t wg3 = ((((wg1 << 8) | wg2) + (wg2 * blend) - (wg1 * blend))) & 0xFF00FF00;
return rb3 | wg3;
}
/*
* color add function that preserves ratio
* idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule
* original idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule
* speed optimisations by @dedehai
*/
uint32_t color_add(uint32_t c1, uint32_t c2, bool fast)
uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR)
{
if (c1 == BLACK) return c2;
if (c2 == BLACK) return c1;
if (fast) {
uint8_t r = R(c1);
uint8_t g = G(c1);
uint8_t b = B(c1);
uint8_t w = W(c1);
r = qadd8(r, R(c2));
g = qadd8(g, G(c2));
b = qadd8(b, B(c2));
w = qadd8(w, W(c2));
return RGBW32(r,g,b,w);
uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); // mask and add two colors at once
uint32_t wg = ((c1>>8) & 0x00FF00FF) + ((c2>>8) & 0x00FF00FF);
uint32_t r = rb >> 16; // extract single color values
uint32_t b = rb & 0xFFFF;
uint32_t w = wg >> 16;
uint32_t g = wg & 0xFFFF;
if (preserveCR) { // preserve color ratios
uint32_t max = std::max(r,g); // check for overflow note
max = std::max(max,b);
max = std::max(max,w);
//unsigned max = r; // check for overflow note
//max = g > max ? g : max;
//max = b > max ? b : max;
//max = w > max ? w : max;
if (max > 255) {
uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead
rb = ((rb * scale) >> 8) & 0x00FF00FF; //
wg = (wg * scale) & 0xFF00FF00;
} else wg = wg << 8; //shift white and green back to correct position
return rb | wg;
} else {
uint32_t r = R(c1) + R(c2);
uint32_t g = G(c1) + G(c2);
uint32_t b = B(c1) + B(c2);
uint32_t w = W(c1) + W(c2);
unsigned max = r;
if (g > max) max = g;
if (b > max) max = b;
if (w > max) max = w;
if (max < 256) return RGBW32(r, g, b, w);
else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max);
r = r > 255 ? 255 : r;
g = g > 255 ? 255 : g;
b = b > 255 ? 255 : b;
w = w > 255 ? 255 : w;
return RGBW32(r,g,b,w);
}
}
@@ -70,27 +65,53 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast)
uint32_t color_fade(uint32_t c1, uint8_t amount, bool video)
{
if (c1 == BLACK || amount + video == 0) return BLACK;
if (amount == 255) return c1;
if (c1 == BLACK || amount == 0) return BLACK;
uint32_t scaledcolor; // color order is: W R G B from MSB to LSB
uint32_t r = R(c1);
uint32_t g = G(c1);
uint32_t b = B(c1);
uint32_t w = W(c1);
uint32_t scale = amount; // 32bit for faster calculation
if (video) {
scaledcolor = (((r * scale) >> 8) + ((r && scale) ? 1 : 0)) << 16;
scaledcolor |= (((g * scale) >> 8) + ((g && scale) ? 1 : 0)) << 8;
scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0);
scaledcolor |= (((w * scale) >> 8) + ((w && scale) ? 1 : 0)) << 24;
} else {
scaledcolor = ((r * scale) >> 8) << 16;
scaledcolor |= ((g * scale) >> 8) << 8;
scaledcolor |= (b * scale) >> 8;
scaledcolor |= ((w * scale) >> 8) << 24;
uint32_t addRemains = 0;
if (!video) scale++; // add one for correct scaling using bitshifts
else { // video scaling: make sure colors do not dim to zero if they started non-zero
addRemains = R(c1) ? 0x00010000 : 0;
addRemains |= G(c1) ? 0x00000100 : 0;
addRemains |= B(c1) ? 0x00000001 : 0;
addRemains |= W(c1) ? 0x01000000 : 0;
}
uint32_t rb = (((c1 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
uint32_t wg = (((c1 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
scaledcolor = (rb | wg) + addRemains;
return scaledcolor;
}
// 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes)
uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType)
{
if (blendType == LINEARBLEND_NOWRAP) {
index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping
}
unsigned hi4 = byte(index) >> 4;
const CRGB* entry = (CRGB*)((uint8_t*)(&(pal[0])) + (hi4 * sizeof(CRGB)));
unsigned red1 = entry->r;
unsigned green1 = entry->g;
unsigned blue1 = entry->b;
if (blendType != NOBLEND) {
if (hi4 == 15) entry = &(pal[0]);
else ++entry;
unsigned f2 = ((index & 0x0F) << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8
unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max
red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8;
green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8;
blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8;
}
if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted
uint32_t scale = brightness + 1; // adjust for rounding (bitshift)
red1 = (red1 * scale) >> 8;
green1 = (green1 * scale) >> 8;
blue1 = (blue1 * scale) >> 8;
}
return RGBW32(red1,green1,blue1,0);
}
void setRandomColor(byte* rgb)
{
lastRandomIndex = get_random_wheel_index(lastRandomIndex);
@@ -101,93 +122,93 @@ void setRandomColor(byte* rgb)
* generates a random palette based on harmonic color theory
* takes a base palette as the input, it will choose one color of the base palette and keep it
*/
CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette)
{
CHSV palettecolors[4]; //array of colors for the new palette
uint8_t keepcolorposition = random8(4); //color position of current random palette to keep
palettecolors[keepcolorposition] = rgb2hsv_approximate(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette
palettecolors[keepcolorposition].hue += random8(10)-5; // +/- 5 randomness of base color
//generate 4 saturation and brightness value numbers
//only one saturation is allowed to be below 200 creating mostly vibrant colors
//only one brightness value number is allowed below 200, creating mostly bright palettes
CHSV palettecolors[4]; // array of colors for the new palette
uint8_t keepcolorposition = hw_random8(4); // color position of current random palette to keep
palettecolors[keepcolorposition] = rgb2hsv(basepalette.entries[keepcolorposition*5]); // read one of the base colors of the current palette
palettecolors[keepcolorposition].hue += hw_random8(10)-5; // +/- 5 randomness of base color
// generate 4 saturation and brightness value numbers
// only one saturation is allowed to be below 200 creating mostly vibrant colors
// only one brightness value number is allowed below 200, creating mostly bright palettes
for (int i = 0; i < 3; i++) { //generate three high values
palettecolors[i].saturation = random8(200,255);
palettecolors[i].value = random8(220,255);
for (int i = 0; i < 3; i++) { // generate three high values
palettecolors[i].saturation = hw_random8(200,255);
palettecolors[i].value = hw_random8(220,255);
}
//allow one to be lower
palettecolors[3].saturation = random8(20,255);
palettecolors[3].value = random8(80,255);
// allow one to be lower
palettecolors[3].saturation = hw_random8(20,255);
palettecolors[3].value = hw_random8(80,255);
//shuffle the arrays
// shuffle the arrays
for (int i = 3; i > 0; i--) {
std::swap(palettecolors[i].saturation, palettecolors[random8(i + 1)].saturation);
std::swap(palettecolors[i].value, palettecolors[random8(i + 1)].value);
std::swap(palettecolors[i].saturation, palettecolors[hw_random8(i + 1)].saturation);
std::swap(palettecolors[i].value, palettecolors[hw_random8(i + 1)].value);
}
//now generate three new hues based off of the hue of the chosen current color
// now generate three new hues based off of the hue of the chosen current color
uint8_t basehue = palettecolors[keepcolorposition].hue;
uint8_t harmonics[3]; //hues that are harmonic but still a little random
uint8_t type = random8(5); //choose a harmony type
uint8_t harmonics[3]; // hues that are harmonic but still a little random
uint8_t type = hw_random8(5); // choose a harmony type
switch (type) {
case 0: // analogous
harmonics[0] = basehue + random8(30, 50);
harmonics[1] = basehue + random8(10, 30);
harmonics[2] = basehue - random8(10, 30);
harmonics[0] = basehue + hw_random8(30, 50);
harmonics[1] = basehue + hw_random8(10, 30);
harmonics[2] = basehue - hw_random8(10, 30);
break;
case 1: // triadic
harmonics[0] = basehue + 113 + random8(15);
harmonics[1] = basehue + 233 + random8(15);
harmonics[2] = basehue - 7 + random8(15);
harmonics[0] = basehue + 113 + hw_random8(15);
harmonics[1] = basehue + 233 + hw_random8(15);
harmonics[2] = basehue - 7 + hw_random8(15);
break;
case 2: // split-complementary
harmonics[0] = basehue + 145 + random8(10);
harmonics[1] = basehue + 205 + random8(10);
harmonics[2] = basehue - 5 + random8(10);
harmonics[0] = basehue + 145 + hw_random8(10);
harmonics[1] = basehue + 205 + hw_random8(10);
harmonics[2] = basehue - 5 + hw_random8(10);
break;
case 3: // square
harmonics[0] = basehue + 85 + random8(10);
harmonics[1] = basehue + 175 + random8(10);
harmonics[2] = basehue + 265 + random8(10);
harmonics[0] = basehue + 85 + hw_random8(10);
harmonics[1] = basehue + 175 + hw_random8(10);
harmonics[2] = basehue + 265 + hw_random8(10);
break;
case 4: // tetradic
harmonics[0] = basehue + 80 + random8(20);
harmonics[1] = basehue + 170 + random8(20);
harmonics[2] = basehue - 15 + random8(30);
harmonics[0] = basehue + 80 + hw_random8(20);
harmonics[1] = basehue + 170 + hw_random8(20);
harmonics[2] = basehue - 15 + hw_random8(30);
break;
}
if (random8() < 128) {
//50:50 chance of shuffling hues or keep the color order
if (hw_random8() < 128) {
// 50:50 chance of shuffling hues or keep the color order
for (int i = 2; i > 0; i--) {
std::swap(harmonics[i], harmonics[random8(i + 1)]);
std::swap(harmonics[i], harmonics[hw_random8(i + 1)]);
}
}
//now set the hues
// now set the hues
int j = 0;
for (int i = 0; i < 4; i++) {
if (i==keepcolorposition) continue; //skip the base color
if (i==keepcolorposition) continue; // skip the base color
palettecolors[i].hue = harmonics[j];
j++;
}
bool makepastelpalette = false;
if (random8() < 25) { //~10% chance of desaturated 'pastel' colors
if (hw_random8() < 25) { // ~10% chance of desaturated 'pastel' colors
makepastelpalette = true;
}
//apply saturation & gamma correction
// apply saturation & gamma correction
CRGB RGBpalettecolors[4];
for (int i = 0; i < 4; i++) {
if (makepastelpalette && palettecolors[i].saturation > 180) {
if (makepastelpalette && palettecolors[i].saturation > 180) {
palettecolors[i].saturation -= 160; //desaturate all four colors
}
}
RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB
RGBpalettecolors[i] = gamma32(((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU); //strip alpha from CRGB
}
@@ -198,34 +219,72 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
RGBpalettecolors[3]);
}
CRGBPalette16 generateRandomPalette() //generate fully random palette
CRGBPalette16 generateRandomPalette() // generate fully random palette
{
return CRGBPalette16(CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)));
return CRGBPalette16(CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)),
CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)),
CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)),
CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)));
}
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0)
{
float h = ((float)hue)/10922.5f; // hue*6/65535
float s = ((float)sat)/255.0f;
int i = int(h);
float f = h - i;
int p = int(255.0f * (1.0f-s));
int q = int(255.0f * (1.0f-s*f));
int t = int(255.0f * (1.0f-s*(1.0f-f)));
p = constrain(p, 0, 255);
q = constrain(q, 0, 255);
t = constrain(t, 0, 255);
switch (i%6) {
case 0: rgb[0]=255,rgb[1]=t, rgb[2]=p; break;
case 1: rgb[0]=q, rgb[1]=255,rgb[2]=p; break;
case 2: rgb[0]=p, rgb[1]=255,rgb[2]=t; break;
case 3: rgb[0]=p, rgb[1]=q, rgb[2]=255;break;
case 4: rgb[0]=t, rgb[1]=p, rgb[2]=255;break;
case 5: rgb[0]=255,rgb[1]=p, rgb[2]=q; break;
unsigned int remainder, region, p, q, t;
unsigned int h = hsv.h;
unsigned int s = hsv.s;
unsigned int v = hsv.v;
if (s == 0) {
rgb = v << 16 | v << 8 | v;
return;
}
region = h / 10923; // 65536 / 6 = 10923
remainder = (h - (region * 10923)) * 6;
p = (v * (255 - s)) >> 8;
q = (v * (255 - ((s * remainder) >> 16))) >> 8;
t = (v * (255 - ((s * (65535 - remainder)) >> 16))) >> 8;
switch (region) {
case 0:
rgb = v << 16 | t << 8 | p; break;
case 1:
rgb = q << 16 | v << 8 | p; break;
case 2:
rgb = p << 16 | v << 8 | t; break;
case 3:
rgb = p << 16 | q << 8 | v; break;
case 4:
rgb = t << 16 | p << 8 | v; break;
default:
rgb = v << 16 | p << 8 | q; break;
}
}
void rgb2hsv(const uint32_t rgb, CHSV32& hsv) // convert RGB to HSV (16bit hue), much more accurate and faster than fastled version
{
hsv.raw = 0;
int32_t r = (rgb>>16)&0xFF;
int32_t g = (rgb>>8)&0xFF;
int32_t b = rgb&0xFF;
int32_t minval, maxval, delta;
minval = min(r, g);
minval = min(minval, b);
maxval = max(r, g);
maxval = max(maxval, b);
if (maxval == 0) return; // black
hsv.v = maxval;
delta = maxval - minval;
hsv.s = (255 * delta) / maxval;
if (hsv.s == 0) return; // gray value
if (maxval == r) hsv.h = (10923 * (g - b)) / delta;
else if (maxval == g) hsv.h = 21845 + (10923 * (b - r)) / delta;
else hsv.h = 43690 + (10923 * (r - g)) / delta;
}
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) { //hue, sat to rgb
uint32_t crgb;
hsv2rgb(CHSV32(hue, sat, 255), crgb);
rgb[0] = byte((crgb) >> 16);
rgb[1] = byte((crgb) >> 8);
rgb[2] = byte(crgb);
}
//get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html)
@@ -332,7 +391,7 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www
rgb[2] = byte(255.0f*b);
}
void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
void colorRGBtoXY(const byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
{
float X = rgb[0] * 0.664511f + rgb[1] * 0.154324f + rgb[2] * 0.162028f;
float Y = rgb[0] * 0.283881f + rgb[1] * 0.668433f + rgb[2] * 0.047685f;
@@ -343,7 +402,7 @@ void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.develo
#endif // WLED_DISABLE_HUESYNC
//RRGGBB / WWRRGGBB order for hex
void colorFromDecOrHexString(byte* rgb, char* in)
void colorFromDecOrHexString(byte* rgb, const char* in)
{
if (in[0] == 0) return;
char first = in[0];
@@ -452,24 +511,8 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) {
}
}
//gamma 2.8 lookup table used for color correction
uint8_t NeoGammaWLEDMethod::gammaT[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
// gamma lookup table used for color correction (filled on 1st use (cfg.cpp & set.cpp))
uint8_t NeoGammaWLEDMethod::gammaT[256];
// re-calculates & fills gamma table
void NeoGammaWLEDMethod::calcGammaTable(float gamma)

View File

@@ -5,7 +5,7 @@
* Readability defines and their associated numerical values + compile-time constants
*/
#define GRADIENT_PALETTE_COUNT 58
#define GRADIENT_PALETTE_COUNT 59
// You can define custom product info from build flags.
// This is useful to allow API consumer to identify what type of WLED version
@@ -203,6 +203,8 @@
#define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h"
#define USERMOD_ID_POV_DISPLAY 53 //Usermod "usermod_pov_display.h"
#define USERMOD_ID_PIXELS_DICE_TRAY 54 //Usermod "pixels_dice_tray.h"
#define USERMOD_ID_DEEP_SLEEP 55 //Usermod "usermod_deep_sleep.h"
#define USERMOD_ID_RF433 56 //Usermod "usermod_v2_RF433.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@@ -248,6 +250,7 @@
#define REALTIME_MODE_ARTNET 6
#define REALTIME_MODE_TPM2NET 7
#define REALTIME_MODE_DDP 8
#define REALTIME_MODE_DMX 9
//realtime override modes
#define REALTIME_OVERRIDE_NONE 0

View File

@@ -167,9 +167,10 @@
</div>
<div style="display: flex; justify-content: center;">
<div id="palettes" class="palettesMain">
<div id="palTop" class="palTop">
Currently in use custom palettes
</div>
<div id="distDiv" class="palTop"></div>
<div id="palTop" class="palTop">
Currently in use custom palettes
</div>
</div>
</div>
@@ -187,7 +188,7 @@
Available static palettes
</div>
</div>
</div>
</div>
</body>
@@ -204,6 +205,13 @@
var paletteName = []; // Holds the names of the palettes after load.
var svgSave = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M7,12L12,17V14H16V10H12V7L7,12Z"/></svg>'
var svgEdit = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M15.1,7.07C15.24,7.07 15.38,7.12 15.5,7.23L16.77,8.5C17,8.72 17,9.07 16.77,9.28L15.77,10.28L13.72,8.23L14.72,7.23C14.82,7.12 14.96,7.07 15.1,7.07M13.13,8.81L15.19,10.87L9.13,16.93H7.07V14.87L13.13,8.81Z"/></svg>'
var svgDist = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M4 22H2V2H4V22M22 2H20V22H22V2M13.5 7H10.5V17H13.5V7Z"/></svg>'
var svgTrash = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="30px" height="30px"><path style="fill:#880000; stroke: #888888; stroke-width: -2px;stroke-dasharray: 0.1, 8;" d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z"/></svg>'
const distDiv = gId("distDiv");
distDiv.addEventListener('click', distribute);
distDiv.setAttribute('title', 'Distribute colors equally');
distDiv.innerHTML = svgDist;
function recOf() {
rect = gradientBox.getBoundingClientRect();
@@ -433,7 +441,7 @@
renderY = e.srcElement.getBoundingClientRect().y + 13;
trash.id = "trash";
trash.innerHTML = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="30px" height="30px"><path style="fill:#880000; stroke: #888888; stroke-width: -2px;stroke-dasharray: 0.1, 8;" d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z"/></svg>';
trash.innerHTML = svgTrash;
trash.style.position = "absolute";
trash.style.left = (renderX) + "px";
trash.style.top = (renderY) + "px";
@@ -712,9 +720,27 @@
}
}
function distribute() {
let colorMarkers = [...gradientBox.querySelectorAll('.color-marker')];
colorMarkers.sort((a, b) => a.getAttribute('data-truepos') - b.getAttribute('data-truepos'));
colorMarkers = colorMarkers.slice(1, -1);
const spacing = Math.round(256 / (colorMarkers.length + 1));
colorMarkers.forEach((e, i) => {
const markerId = e.id.match(/\d+/)[0];
const trueCol = e.getAttribute("data-truecol");
gradientBox.removeChild(e);
gradientBox.removeChild(gId(`colorPicker${markerId}`));
gradientBox.removeChild(gId(`colorPickerMarker${markerId}`));
gradientBox.removeChild(gId(`deleteMarker${markerId}`));
addC(spacing * (i + 1), trueCol);
});
}
function rgbToHex(r, g, b) {
const hex = ((r << 16) | (g << 8) | b).toString(16);
return "#" + "0".repeat(6 - hex.length) + hex;
}
</script>
</html>

View File

@@ -97,6 +97,7 @@ button {
.labels {
margin: 0;
padding: 8px 0 2px 0;
font-size: 19px;
}
#namelabel {
@@ -890,12 +891,12 @@ a.btn {
line-height: 28px;
}
/* Quick color select Black button (has white border) */
.qcsb {
/* Quick color select Black and White button (has white/black border, depending on the theme) */
.qcsb, .qcsw {
width: 26px;
height: 26px;
line-height: 26px;
border: 1px solid #fff;
border: 1px solid var(--c-f);
}
/* Hex color input wrapper div */
@@ -1299,6 +1300,14 @@ TD .checkmark, TD .radiomark {
width: 100%;
}
#segutil {
margin-bottom: 12px;
}
#segcont > div:first-child, #fxFind {
margin-top: 4px;
}
/* Simplify segments */
.simplified #segcont .lstI {
margin-top: 4px;
@@ -1438,6 +1447,11 @@ dialog {
position: relative;
}
.presin {
width: 100%;
box-sizing: border-box;
}
.btn-s,
.btn-n {
border: 1px solid var(--c-2);

View File

@@ -106,7 +106,7 @@
<div class="qcs" onclick="pC('#ffa000');" style="background-color:#ffa000;"></div>
<div class="qcs" onclick="pC('#ffc800');" style="background-color:#ffc800;"></div>
<div class="qcs" onclick="pC('#ffe0a0');" style="background-color:#ffe0a0;"></div>
<div class="qcs" onclick="pC('#ffffff');" style="background-color:#ffffff;"></div>
<div class="qcs qcsw" onclick="pC('#ffffff');" style="background-color:#ffffff;"></div>
<div class="qcs qcsb" onclick="pC('#000000');" style="background-color:#000000;"></div><br>
<div class="qcs" onclick="pC('#ff00ff');" style="background-color:#ff00ff;"></div>
<div class="qcs" onclick="pC('#0000ff');" style="background-color:#0000ff;"></div>

View File

@@ -2827,7 +2827,7 @@ function search(field, listId = null) {
// restore default preset sorting if no search term is entered
if (!search) {
if (listId === 'pcont') { populatePresets(); return; }
if (listId === 'pcont') { populatePresets(); return; }
if (listId === 'pallist') {
let id = parseInt(d.querySelector('#pallist input[name="palette"]:checked').value); // preserve selected palette
populatePalettes();
@@ -2846,12 +2846,16 @@ function search(field, listId = null) {
// filter list items but leave (Default & Solid) always visible
const listItems = gId(listId).querySelectorAll('.lstI');
listItems.forEach((listItem,i)=>{
if (listId!=='pcont' && i===0) return;
listItems.forEach((listItem, i) => {
if (listId !== 'pcont' && i === 0) return;
const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase();
const searchIndex = listItemName.indexOf(field.value.toUpperCase());
listItem.style.display = (searchIndex < 0) ? 'none' : '';
listItem.dataset.searchIndex = searchIndex;
if (searchIndex < 0) {
listItem.dataset.searchIndex = Number.MAX_SAFE_INTEGER;
} else {
listItem.dataset.searchIndex = searchIndex;
}
listItem.style.display = (searchIndex < 0) && !listItem.classList.contains("selected") ? 'none' : '';
});
// sort list items by search index and name
@@ -2920,11 +2924,11 @@ function filterFx() {
inputField.value = '';
inputField.focus();
clean(inputField.nextElementSibling);
gId("fxlist").querySelectorAll('.lstI').forEach((listItem,i) => {
gId("fxlist").querySelectorAll('.lstI').forEach((listItem, i) => {
const listItemName = listItem.querySelector('.lstIname').innerText;
let hide = false;
gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i>0 /*true*/; });
listItem.style.display = hide ? 'none' : '';
gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i > 0 /*true*/; });
listItem.style.display = hide && !listItem.classList.contains("selected") ? 'none' : '';
});
}

View File

@@ -54,8 +54,8 @@ Orientation: <select name="P${i}V" oninput="UI()">
</select><br>
Serpentine: <input type="checkbox" name="P${i}S" oninput="UI()"><br>
Dimensions (WxH): <input name="P${i}W" type="number" min="1" max="255" value="${pw}" oninput="UI()"> x <input name="P${i}H" type="number" min="1" max="255" value="${ph}" oninput="UI()"><br>
Offset X:<input name="P${i}X" type="number" min="0" max="255" value="0" oninput="UI()">
Y:<input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()"><br><i>(offset from top-left corner in # LEDs)</i>
Offset X: <input name="P${i}X" type="number" min="0" max="255" value="0" oninput="UI()">
Y: <input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()"><br><i>(offset from top-left corner in # LEDs)</i>
</div>`;
p.insertAdjacentHTML("beforeend", b);
}

View File

@@ -6,7 +6,7 @@
<title>LED Settings</title>
<script src="common.js" async type="text/javascript"></script>
<script>
var laprev=55,maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var oMaxB=1;
var customStarts=false,startsDirty=[];
function off(n) { gN(n).value = -1;}
@@ -42,15 +42,14 @@
if (loc) d.Sf.action = getURL('/settings/leds');
}
function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
// maxB - max buses (can be changed if using ESP32 parallel I2S)
// maxD - max digital channels (can be changed if using ESP32 parallel I2S)
// maxA - max analog channels
// maxV - min virtual buses
// maxPB - max LEDs per bus
// maxM - max LED memory
// maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
// maxCO - max Color Order mappings
oMaxB = maxB = b; maxD = d, maxA = a, maxV = v; maxM = m; maxPB = p; maxL = l; maxCO = o;
oMaxB = maxB = b; // maxB - max buses (can be changed if using ESP32 parallel I2S)
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S)
maxA = a; // maxA - max analog channels
maxV = v; // maxV - min virtual buses
maxPB = p; // maxPB - max LEDs per bus
maxM = m; // maxM - max LED memory
maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
maxCO = o; // maxCO - max Color Order mappings
}
function pinsOK() {
var ok = true;
@@ -380,6 +379,11 @@
gId('psu').innerHTML = s;
gId('psu2').innerHTML = s2;
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
// show/hide FPS warning messages
gId('fpsNone').style.display = (d.Sf.FR.value == 0) ? 'block':'none';
gId('fpsWarn').style.display = (d.Sf.FR.value == 0) || (d.Sf.FR.value >= 80) ? 'block':'none';
gId('fpsHigh').style.display = (d.Sf.FR.value >= 80) ? 'block':'none';
}
function lastEnd(i) {
if (i-- < 1) return 0;
@@ -472,6 +476,8 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
if (i >= maxB || twopinB >= 1) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P()
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM()
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
// initialize current limiter
enLA(d.Sf["LAsel"+s],s);
}
if (n==-1) {
o[--i].remove();--i;
@@ -755,7 +761,7 @@ Swap: <select id="xw${s}" name="XW${s}">
Enable automatic brightness limiter: <input type="checkbox" name="ABL" onchange="enABL()"><br>
<div id="abl">
<i>Automatically limits brightness to stay close to the limit.<br>
Keep at &lt;1A if poweing LEDs directly from the ESP 5V pin!<br>
Keep at &lt;1A if powering LEDs directly from the ESP 5V pin!<br>
If using multiple outputs it is recommended to use per-output limiter.<br>
Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.<br></i>
<div id="psuMA">Maximum PSU Current: <input name="MA" type="number" class="xl" min="250" max="65000" oninput="UI()" required> mA<br></div>
@@ -870,7 +876,10 @@ Swap: <select id="xw${s}" name="XW${s}">
<option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option>
</select><br>
Target refresh rate: <input type="number" class="s" min="1" max="120" name="FR" required> FPS
Target refresh rate: <input type="number" class="s" min="0" max="250" name="FR" oninput="UI()" required> FPS
<div id="fpsNone" class="warn" style="display: none;">&#9888; Unlimited FPS Mode is experimental &#9888;<br></div>
<div id="fpsHigh" class="warn" style="display: none;">&#9888; High FPS Mode is experimental.<br></div>
<div id="fpsWarn" class="warn" style="display: none;">Please <a class="lnk" href="sec#backup">backup</a> WLED configuration and presets first!<br></div>
<hr class="sml">
<div id="cfg">Config template: <input type="file" name="data2" accept=".json"><button type="button" class="sml" onclick="loadCfg(d.Sf.data2)">Apply</button><br></div>
<hr>

View File

@@ -57,11 +57,11 @@
<h3>Software Update</h3>
<button type="button" onclick="U()">Manual OTA Update</button><br>
Enable ArduinoOTA: <input type="checkbox" name="AO">
<hr>
<hr id="backup">
<h3>Backup & Restore</h3>
<div class="warn">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.</div>
For security reasons, passwords are not backed up.
Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.<br>
For security reasons, passwords are not backed up.</div>
<a class="btn lnk" id="bckcfg" href="/presets.json" download="presets">Backup presets</a><br>
<div>Restore presets<br><input type="file" name="data" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data,'/presets.json');">Upload</button><br></div><br>
<a class="btn lnk" id="bckpresets" href="/cfg.json" download="cfg">Backup configuration</a><br>
@@ -78,4 +78,4 @@
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>
</html>

View File

@@ -151,6 +151,19 @@ Timeout: <input name="ET" type="number" min="1" max="65000" required> ms<br>
Force max brightness: <input type="checkbox" name="FB"><br>
Disable realtime gamma correction: <input type="checkbox" name="RG"><br>
Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required>
<div id="dmxInput">
<h4>Wired DMX Input Pins</h4>
DMX RX: <input name="IDMR" type="number" min="-1" max="99">RO<br/>
DMX TX: <input name="IDMT" type="number" min="-1" max="99">DI<br/>
DMX Enable: <input name="IDME" type="number" min="-1" max="99">RE+DE<br/>
DMX Port: <input name="IDMP" type="number" min="1" max="2"><br/>
</div>
<div id="dmxInputOff">
<br><em style="color:darkorange">This firmware build does not include DMX Input support. <br></em>
</div>
<div id="dmxOnOff2">
<br><em style="color:darkorange">This firmware build does not include DMX output support. <br></em>
</div>
<hr class="sml">
<h3>Alexa Voice Assistant</h3>
<div id="NoAlexa" class="hide">

View File

@@ -17,7 +17,7 @@
<h2>WLED Software Update</h2>
<form method='POST' action='./update' id='uf' enctype='multipart/form-data' onsubmit="U()">
Installed version: <span class="sip">##VERSION##</span><br>
Download the latest binary:&nbsp;<a href="https://github.com/Aircoookie/WLED/releases" target="_blank"
Download the latest binary: <a href="https://github.com/Aircoookie/WLED/releases" target="_blank"
style="vertical-align: text-bottom; display: inline-flex;">
<img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br>
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->

280
wled00/dmx_input.cpp Normal file
View File

@@ -0,0 +1,280 @@
#include "wled.h"
#ifdef WLED_ENABLE_DMX_INPUT
#ifdef ESP8266
#error DMX input is only supported on ESP32
#endif
#include "dmx_input.h"
#include <rdm/responder.h>
void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context)
{
DMXInput *dmx = static_cast<DMXInput *>(context);
if (!dmx) {
DEBUG_PRINTLN("DMX: Error: no context in rdmPersonalityChangedCb");
return;
}
if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
const uint8_t personality = dmx_get_current_personality(dmx->inputPortNum);
DMXMode = std::min(DMX_MODE_PRESET, std::max(DMX_MODE_SINGLE_RGB, int(personality)));
doSerializeConfig = true;
DEBUG_PRINTF("DMX personality changed to to: %d\n", DMXMode);
}
}
void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context)
{
DMXInput *dmx = static_cast<DMXInput *>(context);
if (!dmx) {
DEBUG_PRINTLN("DMX: Error: no context in rdmAddressChangedCb");
return;
}
if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
const uint16_t addr = dmx_get_start_address(dmx->inputPortNum);
DMXAddress = std::min(512, int(addr));
doSerializeConfig = true;
DEBUG_PRINTF("DMX start addr changed to: %d\n", DMXAddress);
}
}
static dmx_config_t createConfig()
{
dmx_config_t config;
config.pd_size = 255;
config.dmx_start_address = DMXAddress;
config.model_id = 0;
config.product_category = RDM_PRODUCT_CATEGORY_FIXTURE;
config.software_version_id = VERSION;
strcpy(config.device_label, "WLED_MM");
const std::string versionString = "WLED_V" + std::to_string(VERSION);
strncpy(config.software_version_label, versionString.c_str(), 32);
config.software_version_label[32] = '\0'; // zero termination in case versionString string was longer than 32 chars
config.personalities[0].description = "SINGLE_RGB";
config.personalities[0].footprint = 3;
config.personalities[1].description = "SINGLE_DRGB";
config.personalities[1].footprint = 4;
config.personalities[2].description = "EFFECT";
config.personalities[2].footprint = 15;
config.personalities[3].description = "MULTIPLE_RGB";
config.personalities[3].footprint = std::min(512, int(strip.getLengthTotal()) * 3);
config.personalities[4].description = "MULTIPLE_DRGB";
config.personalities[4].footprint = std::min(512, int(strip.getLengthTotal()) * 3 + 1);
config.personalities[5].description = "MULTIPLE_RGBW";
config.personalities[5].footprint = std::min(512, int(strip.getLengthTotal()) * 4);
config.personalities[6].description = "EFFECT_W";
config.personalities[6].footprint = 18;
config.personalities[7].description = "EFFECT_SEGMENT";
config.personalities[7].footprint = std::min(512, strip.getSegmentsNum() * 15);
config.personalities[8].description = "EFFECT_SEGMENT_W";
config.personalities[8].footprint = std::min(512, strip.getSegmentsNum() * 18);
config.personalities[9].description = "PRESET";
config.personalities[9].footprint = 1;
config.personality_count = 10;
// rdm personalities are numbered from 1, thus we can just set the DMXMode directly.
config.current_personality = DMXMode;
return config;
}
void dmxReceiverTask(void *context)
{
DMXInput *instance = static_cast<DMXInput *>(context);
if (instance == nullptr) {
return;
}
if (instance->installDriver()) {
while (true) {
instance->updateInternal();
}
}
}
bool DMXInput::installDriver()
{
const auto config = createConfig();
DEBUG_PRINTF("DMX port: %u\n", inputPortNum);
if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) {
DEBUG_PRINTF("Error: Failed to install dmx driver\n");
return false;
}
DEBUG_PRINTF("Listening for DMX on pin %u\n", rxPin);
DEBUG_PRINTF("Sending DMX on pin %u\n", txPin);
DEBUG_PRINTF("DMX enable pin is: %u\n", enPin);
dmx_set_pin(inputPortNum, txPin, rxPin, enPin);
rdm_register_dmx_start_address(inputPortNum, rdmAddressChangedCb, this);
rdm_register_dmx_personality(inputPortNum, rdmPersonalityChangedCb, this);
initialized = true;
return true;
}
void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum)
{
#ifdef WLED_ENABLE_DMX_OUTPUT
//TODO add again once dmx output has been merged
// if(inputPortNum == dmxOutputPort)
// {
// DEBUG_PRINTF("DMXInput: Error: Input port == output port");
// return;
// }
#endif
if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) {
this->inputPortNum = inputPortNum;
}
else {
DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d\n", inputPortNum);
return;
}
if (rxPin > 0 && enPin > 0 && txPin > 0) {
const managed_pin_type pins[] = {
{(int8_t)txPin, false}, // these are not used as gpio pins, thus isOutput is always false.
{(int8_t)rxPin, false},
{(int8_t)enPin, false}};
const bool pinsAllocated = PinManager::allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT);
if (!pinsAllocated) {
DEBUG_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n");
DEBUG_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(rxPin).c_str());
DEBUG_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(txPin).c_str());
DEBUG_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(enPin).c_str());
return;
}
this->rxPin = rxPin;
this->txPin = txPin;
this->enPin = enPin;
// put dmx receiver into seperate task because it should not be blocked
// pin to core 0 because wled is running on core 1
xTaskCreatePinnedToCore(dmxReceiverTask, "DMX_RCV_TASK", 10240, this, 2, &task, 0);
if (!task) {
DEBUG_PRINTF("Error: Failed to create dmx rcv task");
}
}
else {
DEBUG_PRINTLN("DMX input disabled due to rxPin, enPin or txPin not set");
return;
}
}
void DMXInput::updateInternal()
{
if (!initialized) {
return;
}
checkAndUpdateConfig();
dmx_packet_t packet;
unsigned long now = millis();
if (dmx_receive(inputPortNum, &packet, DMX_TIMEOUT_TICK)) {
if (!packet.err) {
if(!connected) {
DEBUG_PRINTLN("DMX Input - connected");
}
connected = true;
identify = isIdentifyOn();
if (!packet.is_rdm) {
const std::lock_guard<std::mutex> lock(dmxDataLock);
dmx_read(inputPortNum, dmxdata, packet.size);
}
}
else {
connected = false;
}
}
else {
if(connected) {
DEBUG_PRINTLN("DMX Input - disconnected");
}
connected = false;
}
}
void DMXInput::update()
{
if (identify) {
turnOnAllLeds();
}
else if (connected) {
const std::lock_guard<std::mutex> lock(dmxDataLock);
handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0);
}
}
void DMXInput::turnOnAllLeds()
{
// TODO not sure if this is the correct way?
const uint16_t numPixels = strip.getLengthTotal();
for (uint16_t i = 0; i < numPixels; ++i)
{
strip.setPixelColor(i, 255, 255, 255, 255);
}
strip.setBrightness(255, true);
strip.show();
}
void DMXInput::disable()
{
if (initialized) {
dmx_driver_disable(inputPortNum);
}
}
void DMXInput::enable()
{
if (initialized) {
dmx_driver_enable(inputPortNum);
}
}
bool DMXInput::isIdentifyOn() const
{
uint8_t identify = 0;
const bool gotIdentify = rdm_get_identify_device(inputPortNum, &identify);
// gotIdentify should never be false because it is a default parameter in rdm
// but just in case we check for it anyway
return bool(identify) && gotIdentify;
}
void DMXInput::checkAndUpdateConfig()
{
/**
* The global configuration variables are modified by the web interface.
* If they differ from the driver configuration, we have to update the driver
* configuration.
*/
const uint8_t currentPersonality = dmx_get_current_personality(inputPortNum);
if (currentPersonality != DMXMode) {
DEBUG_PRINTF("DMX personality has changed from %d to %d\n", currentPersonality, DMXMode);
dmx_set_current_personality(inputPortNum, DMXMode);
}
const uint16_t currentAddr = dmx_get_start_address(inputPortNum);
if (currentAddr != DMXAddress) {
DEBUG_PRINTF("DMX address has changed from %d to %d\n", currentAddr, DMXAddress);
dmx_set_start_address(inputPortNum, DMXAddress);
}
}
#endif

73
wled00/dmx_input.h Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include <cstdint>
#include <esp_dmx.h>
#include <atomic>
#include <mutex>
/*
* Support for DMX/RDM input via serial (e.g. max485) on ESP32
* ESP32 Library from:
* https://github.com/someweisguy/esp_dmx
*/
class DMXInput
{
public:
void init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum);
void update();
/**disable dmx receiver (do this before disabling the cache)*/
void disable();
void enable();
private:
/// @return true if rdm identify is active
bool isIdentifyOn() const;
/**
* Checks if the global dmx config has changed and updates the changes in rdm
*/
void checkAndUpdateConfig();
/// overrides everything and turns on all leds
void turnOnAllLeds();
/// installs the dmx driver
/// @return false on fail
bool installDriver();
/// is called by the dmx receive task regularly to receive new dmx data
void updateInternal();
// is invoked whenver the dmx start address is changed via rdm
friend void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context);
// is invoked whenever the personality is changed via rdm
friend void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context);
/// The internal dmx task.
/// This is the main loop of the dmx receiver. It never returns.
friend void dmxReceiverTask(void * context);
uint8_t inputPortNum = 255;
uint8_t rxPin = 255;
uint8_t txPin = 255;
uint8_t enPin = 255;
/// is written to by the dmx receive task.
byte dmxdata[DMX_PACKET_SIZE];
/// True once the dmx input has been initialized successfully
bool initialized = false; // true once init finished successfully
/// True if dmx is currently connected
std::atomic<bool> connected{false};
std::atomic<bool> identify{false};
/// Timestamp of the last time a dmx frame was received
unsigned long lastUpdate = 0;
/// Taskhandle of the dmx task that is running in the background
TaskHandle_t task;
/// Guards access to dmxData
std::mutex dmxDataLock;
};

View File

@@ -1,7 +1,7 @@
#include "wled.h"
/*
* Support for DMX Output via MAX485.
* Support for DMX output via serial (e.g. MAX485).
* Change the output pin in src/dependencies/ESPDMX.cpp, if needed (ESP8266)
* Change the output pin in src/dependencies/SparkFunDMX.cpp, if needed (ESP32)
* ESP8266 Library from:
@@ -12,7 +12,7 @@
#ifdef WLED_ENABLE_DMX
void handleDMX()
void handleDMXOutput()
{
// don't act, when in DMX Proxy mode
if (e131ProxyUniverse != 0) return;
@@ -68,11 +68,14 @@ void handleDMX()
dmx.update(); // update the DMX bus
}
void initDMX() {
void initDMXOutput() {
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2)
dmx.init(512); // initialize with bus length
#else
dmx.initWrite(512); // initialize with bus length
#endif
}
#else
void initDMXOutput(){}
void handleDMXOutput() {}
#endif

View File

@@ -39,6 +39,7 @@ void handleDDPPacket(e131_packet_t* p) {
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) {
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = start; i < stop; i++, c += ddpChannelsPerLed) {
setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0);
}
@@ -115,6 +116,11 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
// update status info
realtimeIP = clientIP;
handleDMXData(uni, dmxChannels, e131_data, mde, previousUniverses);
}
void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses) {
byte wChannel = 0;
unsigned totalLen = strip.getLengthTotal();
unsigned availDMXLen = 0;
@@ -129,7 +135,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
}
// DMX data in Art-Net packet starts at index 0, for E1.31 at index 1
if (protocol == P_ARTNET && dataOffset > 0) {
if (mde == REALTIME_MODE_ARTNET && dataOffset > 0) {
dataOffset--;
}
@@ -147,6 +153,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0;
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel);
break;
@@ -164,6 +171,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
strip.setBrightness(bri, true);
}
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
for (unsigned i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel);
break;
@@ -208,7 +216,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
else
dataOffset = DMXAddress;
// Modify address for Art-Net data
if (protocol == P_ARTNET && dataOffset > 0)
if (mde == REALTIME_MODE_ARTNET && dataOffset > 0)
dataOffset--;
// Skip out of universe addresses
if (dataOffset > dmxChannels - dmxEffectChannels + 1)
@@ -282,7 +290,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
}
} else {
// All subsequent universes start at the first channel.
dmxOffset = (protocol == P_ARTNET) ? 0 : 1;
dmxOffset = (mde == REALTIME_MODE_ARTNET) ? 0 : 1;
const unsigned dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0;
unsigned ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed;
previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse;
@@ -308,6 +316,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
}
}
if (useMainSegmentOnly) strip.getMainSegment().beginDraw();
if (!is4Chan) {
for (unsigned i = previousLeds; i < ledsTotal; i++) {
setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0);

View File

@@ -66,6 +66,89 @@ typedef struct WiFiConfig {
} wifi_config;
//colors.cpp
#define ColorFromPalette ColorFromPaletteWLED // override fastled version
// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color
// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts
// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB
struct CRGBW {
union {
uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB)
struct {
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t w;
};
uint8_t raw[4]; // Access as an array in the order B, G, R, W
};
// Default constructor
inline CRGBW() __attribute__((always_inline)) = default;
// Constructor from a 32-bit color (0xWWRRGGBB)
constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {}
// Constructor with r, g, b, w values
constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {}
// Constructor from CRGB
constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {}
// Access as an array
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; }
// Assignment from 32-bit color
inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; }
// Assignment from r, g, b, w
inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; }
// Conversion operator to uint32_t
inline operator uint32_t() const __attribute__((always_inline)) {
return color32;
}
/*
// Conversion operator to CRGB
inline operator CRGB() const __attribute__((always_inline)) {
return CRGB(r, g, b);
}
CRGBW& scale32 (uint8_t scaledown) // 32bit math
{
if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit
uint32_t scale = scaledown + 1;
uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
color32 = rb | wg;
return *this;
}*/
};
struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions
union {
struct {
uint16_t h; // hue
uint8_t s; // saturation
uint8_t v; // value
};
uint32_t raw; // 32bit access
};
inline CHSV32() __attribute__((always_inline)) = default; // default constructor
/// Allow construction from hue, saturation, and value
/// @param ih input hue
/// @param is input saturation
/// @param iv input value
inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v
: h(ih), s(is), v(iv) {}
inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v
: h((uint16_t)ih << 8), s(is), v(iv) {}
inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV
: h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {}
inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV
};
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
class NeoGammaWLEDMethod {
public:
@@ -78,29 +161,39 @@ class NeoGammaWLEDMethod {
};
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
[[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false);
[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t, bool fast=false);
[[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false);
CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette);
[[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend);
inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); };
[[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false);
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false);
[[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND);
CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette);
CRGBPalette16 generateRandomPalette();
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
void rgb2hsv(const uint32_t rgb, CHSV32& hsv);
inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv
void colorKtoRGB(uint16_t kelvin, byte* rgb);
void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb
void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO
void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO
void colorFromDecOrHexString(byte* rgb, char* in);
void colorRGBtoXY(const byte* rgb, float* xy); // only defined if huesync disabled TODO
void colorFromDecOrHexString(byte* rgb, const char* in);
bool colorFromHexString(byte* rgb, const char* in);
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb);
void setRandomColor(byte* rgb);
//dmx.cpp
void initDMX();
void handleDMX();
//dmx_output.cpp
void initDMXOutput();
void handleDMXOutput();
//dmx_input.cpp
void initDMXInput();
void handleDMXInput();
//e131.cpp
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol);
void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses);
void handleArtnetPollReply(IPAddress ipAddress);
void prepareArtnetPollReply(ArtPollReply* reply);
void sendArtnetPollReply(ArtPollReply* reply, IPAddress ipAddress, uint16_t portAddress);
@@ -155,11 +248,11 @@ void handleIR();
bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0);
bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, byte presetId = 0);
void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false);
void serializeInfo(JsonObject root);
void serializeModeNames(JsonArray root);
void serializeModeData(JsonArray root);
void serializeModeNames(JsonArray arr);
void serializeModeData(JsonArray fxdata);
void serveJson(AsyncWebServerRequest* request);
#ifdef WLED_ENABLE_JSONLIVE
bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0);
@@ -230,7 +323,8 @@ void deletePreset(byte index);
bool getPresetName(byte index, String& name);
//remote.cpp
void handleRemote(uint8_t *data, size_t len);
void handleWiZdata(uint8_t *incomingData, size_t len);
void handleRemote();
//set.cpp
bool isAsterisksOnly(const char* str, byte maxLen);
@@ -239,7 +333,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=tru
//udp.cpp
void notify(byte callMode, bool followUp=false);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri=255, bool isRGBW=false);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri=255, bool isRGBW=false);
void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC);
void exitRealtime();
void handleNotifications();
@@ -329,36 +423,33 @@ class Usermod {
#endif
};
class UsermodManager {
private:
static Usermod* ums[WLED_MAX_USERMODS];
static byte numMods;
namespace UsermodManager {
extern byte numMods;
public:
static void loop();
static void handleOverlayDraw();
static bool handleButton(uint8_t b);
static bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods
static void setup();
static void connected();
static void appendConfigData(Print&);
static void addToJsonState(JsonObject& obj);
static void addToJsonInfo(JsonObject& obj);
static void readFromJsonState(JsonObject& obj);
static void addToConfig(JsonObject& obj);
static bool readFromConfig(JsonObject& obj);
void loop();
void handleOverlayDraw();
bool handleButton(uint8_t b);
bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods
void setup();
void connected();
void appendConfigData(Print&);
void addToJsonState(JsonObject& obj);
void addToJsonInfo(JsonObject& obj);
void readFromJsonState(JsonObject& obj);
void addToConfig(JsonObject& obj);
bool readFromConfig(JsonObject& obj);
#ifndef WLED_DISABLE_MQTT
static void onMqttConnect(bool sessionPresent);
static bool onMqttMessage(char* topic, char* payload);
void onMqttConnect(bool sessionPresent);
bool onMqttMessage(char* topic, char* payload);
#endif
#ifndef WLED_DISABLE_ESPNOW
static bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len);
bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len);
#endif
static void onUpdateBegin(bool);
static void onStateChange(uint8_t);
static bool add(Usermod* um);
static Usermod* lookup(uint16_t mod_id);
static inline byte getModCount() {return numMods;};
void onUpdateBegin(bool);
void onStateChange(uint8_t);
bool add(Usermod* um);
Usermod* lookup(uint16_t mod_id);
inline byte getModCount() {return numMods;};
};
//usermods_list.cpp
@@ -370,10 +461,16 @@ void userConnected();
void userLoop();
//util.cpp
int getNumVal(const String* req, uint16_t pos);
#ifdef ESP8266
#define HW_RND_REGISTER RANDOM_REG32
#else // ESP32 family
#include "soc/wdev_reg.h"
#define HW_RND_REGISTER REG_READ(WDEV_RND_REG)
#endif
[[gnu::pure]] int getNumVal(const String* req, uint16_t pos);
void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255);
bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255);
bool getBoolVal(JsonVariant elem, bool dflt);
bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form)
[[gnu::pure]] bool getBoolVal(const JsonVariant &elem, bool dflt);
bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255);
size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val);
size_t printSetFormValue(Print& settingsScript, const char* key, int val);
@@ -381,18 +478,38 @@ size_t printSetFormValue(Print& settingsScript, const char* key, const char* val
size_t printSetFormIndex(Print& settingsScript, const char* key, int index);
size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val);
void prepareHostname(char* hostname);
bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t module=255);
[[gnu::pure]] bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t moduleID=255);
void releaseJSONBufferLock();
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen);
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr);
int16_t extractModeDefaults(uint8_t mode, const char *segVar);
void checkSettingsPIN(const char *pin);
uint16_t crc16(const unsigned char* data_p, size_t length);
uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0);
um_data_t* simulateSound(uint8_t simulationId);
void enumerateLedmaps();
uint8_t get_random_wheel_index(uint8_t pos);
float mapf(float x, float in_min, float in_max, float out_min, float out_max);
[[gnu::hot]] uint8_t get_random_wheel_index(uint8_t pos);
[[gnu::hot, gnu::pure]] float mapf(float x, float in_min, float in_max, float out_min, float out_max);
// fast (true) random numbers using hardware RNG, all functions return values in the range lowerlimit to upperlimit-1
// note: for true random numbers with high entropy, do not call faster than every 200ns (5MHz)
// tests show it is still highly random reading it quickly in a loop (better than fastled PRNG)
// for 8bit and 16bit random functions: no limit check is done for best speed
// 32bit inputs are used for speed and code size, limits don't work if inverted or out of range
// inlining does save code size except for random(a,b) and 32bit random with limits
#define random hw_random // replace arduino random()
inline uint32_t hw_random() { return HW_RND_REGISTER; };
uint32_t hw_random(uint32_t upperlimit); // not inlined for code size
int32_t hw_random(int32_t lowerlimit, int32_t upperlimit);
inline uint16_t hw_random16() { return HW_RND_REGISTER; };
inline uint16_t hw_random16(uint32_t upperlimit) { return (hw_random16() * upperlimit) >> 16; }; // input range 0-65535 (uint16_t)
inline int16_t hw_random16(int32_t lowerlimit, int32_t upperlimit) { int32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random16(range); }; // signed limits, use int16_t ranges
inline uint8_t hw_random8() { return HW_RND_REGISTER; };
inline uint8_t hw_random8(uint32_t upperlimit) { return (hw_random8() * upperlimit) >> 8; }; // input range 0-255
inline uint8_t hw_random8(uint32_t lowerlimit, uint32_t upperlimit) { uint32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random8(range); }; // input range 0-255
// RAII guard class for the JSON Buffer lock
// Modeled after std::lock_guard
@@ -419,27 +536,38 @@ void clearEEPROM();
#endif
//wled_math.cpp
#if defined(ESP8266) && !defined(WLED_USE_REAL_MATH)
template <typename T> T atan_t(T x);
float cos_t(float phi);
float sin_t(float x);
float tan_t(float x);
float acos_t(float x);
float asin_t(float x);
float floor_t(float x);
float fmod_t(float num, float denom);
#else
#include <math.h>
#define sin_t sinf
#define cos_t cosf
#define tan_t tanf
#define asin_t asinf
#define acos_t acosf
#define atan_t atanf
#define fmod_t fmodf
#define floor_t floorf
#endif
//float cos_t(float phi); // use float math
//float sin_t(float phi);
//float tan_t(float x);
int16_t sin16_t(uint16_t theta);
int16_t cos16_t(uint16_t theta);
uint8_t sin8_t(uint8_t theta);
uint8_t cos8_t(uint8_t theta);
float sin_approx(float theta); // uses integer math (converted to float), accuracy +/-0.0015 (compared to sinf())
float cos_approx(float theta);
float tan_approx(float x);
float atan2_t(float y, float x);
float acos_t(float x);
float asin_t(float x);
template <typename T> T atan_t(T x);
float floor_t(float x);
float fmod_t(float num, float denom);
uint32_t sqrt32_bw(uint32_t x);
#define sin_t sin_approx
#define cos_t cos_approx
#define tan_t tan_approx
/*
#include <math.h> // standard math functions. use a lot of flash
#define sin_t sinf
#define cos_t cosf
#define tan_t tanf
#define asin_t asinf
#define acos_t acosf
#define atan_t atanf
#define fmod_t fmodf
#define floor_t floorf
*/
//wled_serial.cpp
void handleSerial();
void updateBaudRate(uint32_t rate);

View File

@@ -176,7 +176,7 @@ static void writeSpace(size_t l)
if (knownLargestSpace < l) knownLargestSpace = l;
}
bool appendObjectToFile(const char* key, const JsonDocument* content, uint32_t s, uint32_t contentLen = 0)
static bool appendObjectToFile(const char* key, const JsonDocument* content, uint32_t s, uint32_t contentLen = 0)
{
#ifdef WLED_DEBUG_FS
DEBUGFS_PRINTLN(F("Append"));

View File

@@ -129,7 +129,7 @@ static void changeEffectSpeed(int8_t amount)
} else { // if Effect == "solid Color", change the hue of the primary color
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col = CRGB(sseg.colors[0]);
CHSV prim_hsv = rgb2hsv_approximate(fastled_col);
CHSV prim_hsv = rgb2hsv(fastled_col);
int16_t new_val = (int16_t)prim_hsv.h + amount;
if (new_val > 255) new_val -= 255; // roll-over if bigger than 255
if (new_val < 0) new_val += 255; // roll-over if smaller than 0
@@ -173,7 +173,7 @@ static void changeEffectIntensity(int8_t amount)
} else { // if Effect == "solid Color", change the saturation of the primary color
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col = CRGB(sseg.colors[0]);
CHSV prim_hsv = rgb2hsv_approximate(fastled_col);
CHSV prim_hsv = rgb2hsv(fastled_col);
int16_t new_val = (int16_t) prim_hsv.s + amount;
prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255
hsv2rgb_rainbow(prim_hsv, fastled_col);
@@ -435,7 +435,7 @@ static void decodeIR44(uint32_t code)
case IR44_DIY2 : presetFallback(2, FX_MODE_BREATH, 0); break;
case IR44_DIY3 : presetFallback(3, FX_MODE_FIRE_FLICKER, 0); break;
case IR44_DIY4 : presetFallback(4, FX_MODE_RAINBOW, 0); break;
case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR_SMOOTH, 0); break;
case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR, 0); break;
case IR44_DIY6 : presetFallback(6, FX_MODE_RAIN, 0); break;
case IR44_AUTO : changeEffect(FX_MODE_STATIC); break;
case IR44_FLASH : changeEffect(FX_MODE_PALETTE); break;
@@ -593,7 +593,7 @@ static void decodeIRJson(uint32_t code)
decBrightness();
} else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback
uint8_t p1 = fdo["PL"] | 1;
uint8_t p2 = fdo["FX"] | random8(strip.getModeCount() -1);
uint8_t p2 = fdo["FX"] | hw_random8(strip.getModeCount() -1);
uint8_t p3 = fdo["FP"] | 0;
presetFallback(p1, p2, p3);
}

View File

@@ -34,7 +34,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
//DEBUG_PRINTLN(F("-- JSON deserialize segment."));
Segment& seg = strip.getSegment(id);
//DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data);
Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor)
const Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor)
//DEBUG_PRINTF_P(PSTR("-- Duplicate segment: %p (%p)\n"), &prev, prev.data);
int start = elem["start"] | seg.start;
@@ -68,7 +68,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (elem["n"]) {
// name field exists
if (seg.name) { //clear old name
delete[] seg.name;
free(seg.name);
seg.name = nullptr;
}
@@ -77,7 +77,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (name != nullptr) len = strlen(name);
if (len > 0) {
if (len > WLED_MAX_SEGNAME_LEN) len = WLED_MAX_SEGNAME_LEN;
seg.name = new char[len+1];
seg.name = static_cast<char*>(malloc(len+1));
if (seg.name) strlcpy(seg.name, name, WLED_MAX_SEGNAME_LEN+1);
} else {
// but is empty (already deleted above)
@@ -86,7 +86,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
} else if (start != seg.start || stop != seg.stop) {
// clearing or setting segment without name field
if (seg.name) {
delete[] seg.name;
free(seg.name);
seg.name = nullptr;
}
}
@@ -96,17 +96,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
uint16_t of = seg.offset;
uint8_t soundSim = elem["si"] | seg.soundSim;
uint8_t map1D2D = elem["m12"] | seg.map1D2D;
if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps
seg.map1D2D = constrain(map1D2D, 0, 7);
uint8_t set = elem[F("set")] | seg.set;
seg.set = constrain(set, 0, 3);
seg.soundSim = constrain(soundSim, 0, 3);
uint8_t set = elem[F("set")] | seg.set;
seg.set = constrain(set, 0, 3);
int len = 1;
if (stop > start) len = stop - start;
int len = (stop > start) ? stop - start : 1;
int offset = elem[F("of")] | INT32_MAX;
if (offset != INT32_MAX) {
int offsetAbs = abs(offset);
@@ -117,7 +111,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (stop > start && of > len -1) of = len -1;
// update segment (delete if necessary)
seg.setUp(start, stop, grp, spc, of, startY, stopY); // strip needs to be suspended for this to work without issues
seg.setGeometry(start, stop, grp, spc, of, startY, stopY, map1D2D); // strip needs to be suspended for this to work without issues
if (newSeg) seg.refreshLightCapabilities(); // fix for #3403
@@ -223,30 +217,17 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
#endif
byte fx = seg.mode;
byte last = strip.getModeCount();
// partial fix for #3605
if (!elem["fx"].isNull() && elem["fx"].is<const char*>()) {
const char *tmp = elem["fx"].as<const char *>();
if (strlen(tmp) > 3 && (strchr(tmp,'r') || strchr(tmp,'~') != strrchr(tmp,'~'))) last = 0; // we have "X~Y(r|[w]~[-])" form
}
// end fix
if (getVal(elem["fx"], &fx, 0, last)) { //load effect ('r' random, '~' inc/dec, 0-255 exact value, 5~10r pick random between 5 & 10)
if (getVal(elem["fx"], &fx, 0, strip.getModeCount())) {
if (!presetId && currentPlaylist>=0) unloadPlaylist();
if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")]);
}
//getVal also supports inc/decrementing and random
getVal(elem["sx"], &seg.speed);
getVal(elem["ix"], &seg.intensity);
uint8_t pal = seg.palette;
last = strip.getPaletteCount();
if (!elem["pal"].isNull() && elem["pal"].is<const char*>()) {
const char *tmp = elem["pal"].as<const char *>();
if (strlen(tmp) > 3 && (strchr(tmp,'r') || strchr(tmp,'~') != strrchr(tmp,'~'))) last = 0; // we have "X~Y(r|[w]~[-])" form
}
if (seg.getLightCapabilities() & 1) { // ignore palette for White and On/Off segments
if (getVal(elem["pal"], &pal, 0, last)) seg.setPalette(pal);
if (getVal(elem["pal"], &pal, 0, strip.getPaletteCount())) seg.setPalette(pal);
}
getVal(elem["c1"], &seg.custom1);
@@ -467,7 +448,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
DEBUG_PRINTF_P(PSTR("Preset direct: %d\n"), currentPreset);
} else if (!root["ps"].isNull()) {
// we have "ps" call (i.e. from button or external API call) or "pd" that includes "ps" (i.e. from UI call)
if (root["win"].isNull() && getVal(root["ps"], &presetCycCurr, 0, 0) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) {
if (root["win"].isNull() && getVal(root["ps"], &presetCycCurr, 1, 250) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) {
DEBUG_PRINTF_P(PSTR("Preset select: %d\n"), presetCycCurr);
// b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal())
applyPreset(presetCycCurr, callMode); // async load from file system (only preset ID was specified)
@@ -512,7 +493,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
return stateResponse;
}
void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset, bool segmentBounds)
void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset, bool segmentBounds)
{
root["id"] = id;
if (segmentBounds) {
@@ -902,10 +883,7 @@ void serializePalettes(JsonObject root, int page)
setPaletteColors(curPalette, PartyColors_p);
break;
case 1: //random
curPalette.add("r");
curPalette.add("r");
curPalette.add("r");
curPalette.add("r");
for (int j = 0; j < 4; j++) curPalette.add("r");
break;
case 2: //primary color only
curPalette.add("c1");
@@ -922,53 +900,20 @@ void serializePalettes(JsonObject root, int page)
curPalette.add("c1");
break;
case 5: //primary + secondary (+tertiary if not off), more distinct
for (int j = 0; j < 5; j++) curPalette.add("c1");
for (int j = 0; j < 5; j++) curPalette.add("c2");
for (int j = 0; j < 5; j++) curPalette.add("c3");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c1");
break;
case 6: //Party colors
setPaletteColors(curPalette, PartyColors_p);
break;
case 7: //Cloud colors
setPaletteColors(curPalette, CloudColors_p);
break;
case 8: //Lava colors
setPaletteColors(curPalette, LavaColors_p);
break;
case 9: //Ocean colors
setPaletteColors(curPalette, OceanColors_p);
break;
case 10: //Forest colors
setPaletteColors(curPalette, ForestColors_p);
break;
case 11: //Rainbow colors
setPaletteColors(curPalette, RainbowColors_p);
break;
case 12: //Rainbow stripe colors
setPaletteColors(curPalette, RainbowStripeColors_p);
break;
default:
{
if (i>=palettesCount) {
if (i >= palettesCount)
setPaletteColors(curPalette, strip.customPalettes[i - palettesCount]);
} else {
else if (i < 13) // palette 6 - 12, fastled palettes
setPaletteColors(curPalette, *fastledPalettes[i-6]);
else {
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - 13])), 72);
setPaletteColors(curPalette, tcp);
}
}
break;
}
}

View File

@@ -73,8 +73,8 @@ byte scaledBri(byte in)
//applies global brightness
void applyBri() {
if (!realtimeMode || !arlsForceMaxBri)
{
if (!(realtimeMode && arlsForceMaxBri)) {
//DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld);
strip.setBrightness(scaledBri(briT));
}
}
@@ -85,6 +85,7 @@ void applyFinalBri() {
briOld = bri;
briT = bri;
applyBri();
strip.trigger();
}
@@ -139,14 +140,12 @@ void stateUpdated(byte callMode) {
if (transitionActive) {
briOld = briT;
tperLast = 0;
} else
strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true;
transitionStartTime = millis();
} else {
applyFinalBri();
strip.trigger();
}
}
@@ -179,22 +178,21 @@ void handleTransitions()
updateInterfaces(interfaceUpdateCallMode);
if (transitionActive && strip.getTransition() > 0) {
float tper = (millis() - transitionStartTime)/(float)strip.getTransition();
if (tper >= 1.0f) {
int ti = millis() - transitionStartTime;
int tr = strip.getTransition();
if (ti/tr) {
strip.setTransitionMode(false); // stop all transitions
// restore (global) transition time if not called from UDP notifier or single/temporary transition from JSON (also playlist)
if (jsonTransitionOnce) strip.setTransition(transitionDelay);
transitionActive = false;
jsonTransitionOnce = false;
tperLast = 0;
applyFinalBri();
return;
}
if (tper - tperLast < 0.004f) return; // less than 1 bit change (1/255)
tperLast = tper;
briT = briOld + ((bri - briOld) * tper);
applyBri();
byte briTO = briT;
int deltaBri = (int)bri - (int)briOld;
briT = briOld + (deltaBri * ti / tr);
if (briTO != briT) applyBri();
}
}
@@ -229,8 +227,8 @@ void handleNightlight()
colNlT[1] = effectSpeed;
colNlT[2] = effectPalette;
strip.setMode(strip.getFirstSelectedSegId(), FX_MODE_STATIC); // make sure seg runtime is reset if it was in sunrise mode
effectCurrent = FX_MODE_SUNRISE;
strip.getFirstSelectedSeg().setMode(FX_MODE_STATIC); // make sure seg runtime is reset if it was in sunrise mode
effectCurrent = FX_MODE_SUNRISE; // colorUpdated() will take care of assigning that to all selected segments
effectSpeed = nightlightDelayMins;
effectPalette = 0;
if (effectSpeed > 60) effectSpeed = 60; //currently limited to 60 minutes

View File

@@ -22,7 +22,7 @@ bool parseLx(int lxValue, byte* rgbw)
} else if ((lxValue >= 200000000) && (lxValue <= 201006500)) {
// Loxone Lumitech
ok = true;
float tmpBri = floor((lxValue - 200000000) / 10000); ;
float tmpBri = floor((lxValue - 200000000) / 10000);
uint16_t ct = (lxValue - 200000000) - (((uint8_t)tmpBri) * 10000);
tmpBri *= 2.55f;

View File

@@ -7,6 +7,10 @@
#ifndef WLED_DISABLE_MQTT
#define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds
#if MQTT_MAX_TOPIC_LEN > 32
#warning "MQTT topics length > 32 is not recommended for compatibility with usermods!"
#endif
static void parseMQTTBriPayload(char* payload)
{
if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);}
@@ -23,24 +27,24 @@ static void parseMQTTBriPayload(char* payload)
static void onMqttConnect(bool sessionPresent)
{
//(re)subscribe to required topics
char subuf[38];
char subuf[MQTT_MAX_TOPIC_LEN + 6];
if (mqttDeviceTopic[0] != 0) {
strlcpy(subuf, mqttDeviceTopic, 33);
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0);
strlcpy(subuf, mqttDeviceTopic, 33);
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/api"));
mqtt->subscribe(subuf, 0);
}
if (mqttGroupTopic[0] != 0) {
strlcpy(subuf, mqttGroupTopic, 33);
strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1);
mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0);
strlcpy(subuf, mqttGroupTopic, 33);
strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/api"));
mqtt->subscribe(subuf, 0);
}
@@ -64,8 +68,8 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
}
if (index == 0) { // start (1st partial packet or the only packet)
if (payloadStr) delete[] payloadStr; // fail-safe: release buffer
payloadStr = new char[total+1]; // allocate new buffer
if (payloadStr) free(payloadStr); // fail-safe: release buffer
payloadStr = static_cast<char*>(malloc(total+1)); // allocate new buffer
}
if (payloadStr == nullptr) return; // buffer not allocated
@@ -90,7 +94,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
} else {
// Non-Wled Topic used here. Probably a usermod subscribed to this topic.
UsermodManager::onMqttMessage(topic, payloadStr);
delete[] payloadStr;
free(payloadStr);
payloadStr = nullptr;
return;
}
@@ -120,7 +124,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
// topmost topic (just wled/MAC)
parseMQTTBriPayload(payloadStr);
}
delete[] payloadStr;
free(payloadStr);
payloadStr = nullptr;
}
@@ -158,19 +162,19 @@ void publishMqtt()
#ifndef USERMOD_SMARTNEST
char s[10];
char subuf[48];
char subuf[MQTT_MAX_TOPIC_LEN + 16];
sprintf_P(s, PSTR("%u"), bri);
strlcpy(subuf, mqttDeviceTopic, 33);
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/g"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2]));
strlcpy(subuf, mqttDeviceTopic, 33);
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/c"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
strlcpy(subuf, mqttDeviceTopic, 33);
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/status"));
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
@@ -178,7 +182,7 @@ void publishMqtt()
DynamicBuffer buf(1024);
bufferPrint pbuf(buf.data(), buf.size());
XML_response(pbuf);
strlcpy(subuf, mqttDeviceTopic, 33);
strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(subuf, PSTR("/v"));
mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263)
#endif
@@ -211,7 +215,7 @@ bool initMqtt()
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
#ifndef USERMOD_SMARTNEST
strlcpy(mqttStatusTopic, mqttDeviceTopic, 33);
strlcpy(mqttStatusTopic, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1);
strcat_P(mqttStatusTopic, PSTR("/status"));
mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message
#endif

View File

@@ -207,6 +207,7 @@ void WiFiEvent(WiFiEvent_t event)
break;
#endif
default:
DEBUG_PRINTF_P(PSTR("Network event: %d\n"), (int)event);
break;
}
}

View File

@@ -224,7 +224,7 @@ void sendNTPPacket()
ntpUdp.endPacket();
}
static bool isValidNtpResponse(byte * ntpPacket) {
static bool isValidNtpResponse(const byte* ntpPacket) {
// Perform a few validity checks on the packet
// based on https://github.com/taranais/NTPClient/blob/master/NTPClient.cpp
if((ntpPacket[0] & 0b11000000) == 0b11000000) return false; //reject LI=UNSYNC

View File

@@ -1,5 +1,6 @@
/*
* Color palettes for FastLED effects (65-73).
* 4 bytes per color: index, red, green, blue
*/
// From ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb
@@ -844,6 +845,23 @@ const byte candy2_gp[] PROGMEM = {
211, 39, 33, 34,
255, 1, 1, 1};
const byte trafficlight_gp[] PROGMEM = {
0, 0, 0, 0, //black
85, 0, 255, 0, //green
170, 255, 255, 0, //yellow
255, 255, 0, 0}; //red
// array of fastled palettes (palette 6 - 12)
const TProgmemRGBPalette16 *const fastledPalettes[] PROGMEM = {
&PartyColors_p, //06-00 Party
&CloudColors_p, //07-01 Cloud
&LavaColors_p, //08-02 Lava
&OceanColors_p, //09-03 Ocean
&ForestColors_p, //10-04 Forest
&RainbowColors_p, //11-05 Rainbow
&RainbowStripeColors_p //12-06 Rainbow Bands
};
// Single array of defined cpt-city color palettes.
// This will let us programmatically choose one based on
// a number, rather than having to activate each explicitly
@@ -906,7 +924,8 @@ const byte* const gGradientPalettes[] PROGMEM = {
blink_red_gp, //67-54 Blink Red
red_shift_gp, //68-55 Red Shift
red_tide_gp, //69-56 Red Tide
candy2_gp //70-57 Candy2
candy2_gp, //70-57 Candy2
trafficlight_gp //71-58 Traffic Light
};
#endif

View File

@@ -13,6 +13,16 @@
#endif
#endif
// Pin management state variables
#ifdef ESP8266
static uint32_t pinAlloc = 0UL; // 1 bit per pin, we use first 17bits
#else
static uint64_t pinAlloc = 0ULL; // 1 bit per pin, we use 50 bits on ESP32-S3
static uint16_t ledcAlloc = 0; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS)
#endif
static uint8_t i2cAllocCount = 0; // allow multiple allocation of I2C bus pins but keep track of allocations
static uint8_t spiAllocCount = 0; // allow multiple allocation of SPI bus pins but keep track of allocations
static PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None };
/// Actual allocation/deallocation routines
bool PinManager::deallocatePin(byte gpio, PinOwner tag)
@@ -131,7 +141,9 @@ bool PinManager::allocateMultiplePins(const managed_pin_type * mptArray, byte ar
bool PinManager::allocatePin(byte gpio, bool output, PinOwner tag)
{
// HW I2C & SPI pins have to be allocated using allocateMultiplePins variant since there is always SCL/SDA pair
if (!isPinOk(gpio, output) || (gpio >= WLED_NUM_PINS) || tag==PinOwner::HW_I2C || tag==PinOwner::HW_SPI) {
// DMX_INPUT pins have to be allocated using allocateMultiplePins variant since there is always RX/TX/EN triple
if (!isPinOk(gpio, output) || (gpio >= WLED_NUM_PINS) || tag==PinOwner::HW_I2C || tag==PinOwner::HW_SPI
|| tag==PinOwner::DMX_INPUT) {
#ifdef WLED_DEBUG
if (gpio < 255) { // 255 (-1) is the "not defined GPIO"
if (!isPinOk(gpio, output)) {
@@ -214,8 +226,20 @@ bool PinManager::isPinOk(byte gpio, bool output)
// JTAG: GPIO39-42 are usually used for inline debugging
// GPIO46 is input only and pulled down
#else
if (gpio > 5 && gpio < 12) return false; //SPI flash pins
if (strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0 && (gpio == 16 || gpio == 17)) return false; // PICO-D4: gpio16+17 are in use for onboard SPI FLASH
if ((strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0) || // this is the correct identifier, but....
(strncmp_P(PSTR("ESP32-PICO-D2"), ESP.getChipModel(), 13) == 0)) { // https://github.com/espressif/arduino-esp32/issues/10683
// this chip has 4 MB of internal Flash and different packaging, so available pins are different!
if (((gpio > 5) && (gpio < 9)) || (gpio == 11))
return false;
} else {
// for classic ESP32 (non-mini) modules, these are the SPI flash pins
if (gpio > 5 && gpio < 12) return false; //SPI flash pins
}
if (((strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0) ||
(strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0))
&& (gpio == 16 || gpio == 17)) return false; // PICO-D4/U4WDH: gpio16+17 are in use for onboard SPI FLASH
if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO)
#endif
if (output) return digitalPinCanOutput(gpio);
@@ -278,13 +302,3 @@ void PinManager::deallocateLedc(byte pos, byte channels)
}
}
#endif
#ifdef ESP8266
uint32_t PinManager::pinAlloc = 0UL;
#else
uint64_t PinManager::pinAlloc = 0ULL;
uint16_t PinManager::ledcAlloc = 0;
#endif
uint8_t PinManager::i2cAllocCount = 0;
uint8_t PinManager::spiAllocCount = 0;
PinOwner PinManager::ownerTag[WLED_NUM_PINS] = { PinOwner::None };

View File

@@ -9,6 +9,12 @@
#endif
#include "const.h" // for USERMOD_* values
#ifdef ESP8266
#define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17)
#else
#define WLED_NUM_PINS (GPIO_PIN_COUNT)
#endif
typedef struct PinManagerPinType {
int8_t pin;
bool isOutput;
@@ -29,15 +35,16 @@ enum struct PinOwner : uint8_t {
Ethernet = 0x81,
BusDigital = 0x82,
BusOnOff = 0x83,
BusPwm = 0x84, // 'BusP' == PWM output using BusPwm
Button = 0x85, // 'Butn' == button from configuration
IR = 0x86, // 'IR' == IR receiver pin from configuration
Relay = 0x87, // 'Rly' == Relay pin from configuration
SPI_RAM = 0x88, // 'SpiR' == SPI RAM
DebugOut = 0x89, // 'Dbg' == debug output always IO1
DMX = 0x8A, // 'DMX' == hard-coded to IO2
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32)
BusPwm = 0x84, // 'BusP' == PWM output using BusPwm
Button = 0x85, // 'Butn' == button from configuration
IR = 0x86, // 'IR' == IR receiver pin from configuration
Relay = 0x87, // 'Rly' == Relay pin from configuration
SPI_RAM = 0x88, // 'SpiR' == SPI RAM
DebugOut = 0x89, // 'Dbg' == debug output always IO1
DMX = 0x8A, // 'DMX' == hard-coded to IO2
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32)
DMX_INPUT = 0x8D, // 'DMX_INPUT' == DMX input via serial
// Use UserMod IDs from const.h here
UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01
UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h"
@@ -70,53 +77,39 @@ enum struct PinOwner : uint8_t {
};
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");
class PinManager {
private:
#ifdef ESP8266
#define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17)
static uint32_t pinAlloc; // 1 bit per pin, we use first 17bits
#else
#define WLED_NUM_PINS (GPIO_PIN_COUNT)
static uint64_t pinAlloc; // 1 bit per pin, we use 50 bits on ESP32-S3
static uint16_t ledcAlloc; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS)
#endif
static uint8_t i2cAllocCount; // allow multiple allocation of I2C bus pins but keep track of allocations
static uint8_t spiAllocCount; // allow multiple allocation of SPI bus pins but keep track of allocations
static PinOwner ownerTag[WLED_NUM_PINS];
namespace PinManager {
// De-allocates a single pin
bool deallocatePin(byte gpio, PinOwner tag);
// De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified)
bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag);
bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag);
// Allocates a single pin, with an owner tag.
// De-allocation requires the same owner tag (or override)
bool allocatePin(byte gpio, bool output, PinOwner tag);
// Allocates all the pins, or allocates none of the pins, with owner tag.
// Provided to simplify error condition handling in clients
// using more than one pin, such as I2C, SPI, rotary encoders,
// ethernet, etc..
bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag );
public:
// De-allocates a single pin
static bool deallocatePin(byte gpio, PinOwner tag);
// De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified)
static bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag);
static bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag);
// Allocates a single pin, with an owner tag.
// De-allocation requires the same owner tag (or override)
static bool allocatePin(byte gpio, bool output, PinOwner tag);
// Allocates all the pins, or allocates none of the pins, with owner tag.
// Provided to simplify error condition handling in clients
// using more than one pin, such as I2C, SPI, rotary encoders,
// ethernet, etc..
static bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag );
[[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]]
inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); }
[[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]]
inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); }
[[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]]
static inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); }
[[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]]
static inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); }
// will return true for reserved pins
bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None);
// will return false for reserved pins
bool isPinOk(byte gpio, bool output = true);
bool isReadOnlyPin(byte gpio);
// will return true for reserved pins
static bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None);
// will return false for reserved pins
static bool isPinOk(byte gpio, bool output = true);
static bool isReadOnlyPin(byte gpio);
PinOwner getPinOwner(byte gpio);
static PinOwner getPinOwner(byte gpio);
#ifdef ARDUINO_ARCH_ESP32
static byte allocateLedc(byte channels);
static void deallocateLedc(byte pos, byte channels);
#endif
#ifdef ARDUINO_ARCH_ESP32
byte allocateLedc(byte channels);
void deallocateLedc(byte pos, byte channels);
#endif
};
//extern PinManager pinManager;

View File

@@ -61,7 +61,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
if (playlistLen == 0) return -1;
if (playlistLen > 100) playlistLen = 100;
playlistEntries = new PlaylistEntry[playlistLen];
playlistEntries = new(std::nothrow) PlaylistEntry[playlistLen];
if (playlistEntries == nullptr) return -1;
byte it = 0;

View File

@@ -76,8 +76,8 @@ static void doSaveState() {
// clean up
saveLedmap = -1;
presetToSave = 0;
delete[] saveName;
delete[] quickLoad;
free(saveName);
free(quickLoad);
saveName = nullptr;
quickLoad = nullptr;
playlistSave = false;
@@ -164,6 +164,11 @@ void handlePresets()
DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset);
#if defined(ARDUINO_ARCH_ESP32S3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3)
unsigned long start = millis();
while (strip.isUpdating() && millis() - start < FRAMETIME_FIXED) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches
#endif
#ifdef ARDUINO_ARCH_ESP32
if (tmpPreset==255 && tmpRAMbuffer!=nullptr) {
deserializeJson(*pDoc,tmpRAMbuffer);
@@ -211,8 +216,8 @@ void handlePresets()
//called from handleSet(PS=) [network callback (sObj is empty), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)]
void savePreset(byte index, const char* pname, JsonObject sObj)
{
if (!saveName) saveName = new char[33];
if (!quickLoad) quickLoad = new char[9];
if (!saveName) saveName = static_cast<char*>(malloc(33));
if (!quickLoad) quickLoad = static_cast<char*>(malloc(9));
if (!saveName || !quickLoad) return;
if (index == 0 || (index > 250 && index < 255)) return;
@@ -258,8 +263,8 @@ void savePreset(byte index, const char* pname, JsonObject sObj)
presetsModifiedTime = toki.second(); //unix time
updateFSInfo();
}
delete[] saveName;
delete[] quickLoad;
free(saveName);
free(quickLoad);
saveName = nullptr;
quickLoad = nullptr;
} else {

View File

@@ -1,6 +1,8 @@
#include "wled.h"
#ifndef WLED_DISABLE_ESPNOW
#define ESPNOW_BUSWAIT_TIMEOUT 24 // one frame timeout to wait for bus to finish updating
#define NIGHT_MODE_DEACTIVATED -1
#define NIGHT_MODE_BRIGHTNESS 5
@@ -38,6 +40,7 @@ typedef struct WizMoteMessageStructure {
static uint32_t last_seq = UINT32_MAX;
static int brightnessBeforeNightMode = NIGHT_MODE_DEACTIVATED;
static int16_t ESPNowButton = -1; // set in callback if new button value is received
// Pulled from the IR Remote logic but reduced to 10 steps with a constant of 3
static const byte brightnessSteps[] = {
@@ -121,6 +124,9 @@ static bool remoteJson(int button)
sprintf_P(objKey, PSTR("\"%d\":"), button);
unsigned long start = millis();
while (strip.isUpdating() && millis()-start < ESPNOW_BUSWAIT_TIMEOUT) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches
// attempt to read command from remote.json
readObjectFromFile(PSTR("/remote.json"), objKey, pDoc);
JsonObject fdo = pDoc->as<JsonObject>();
@@ -146,7 +152,7 @@ static bool remoteJson(int button)
parsed = true;
} else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback
uint8_t p1 = fdo["PL"] | 1;
uint8_t p2 = fdo["FX"] | random8(strip.getModeCount() -1);
uint8_t p2 = fdo["FX"] | hw_random8(strip.getModeCount() -1);
uint8_t p3 = fdo["FP"] | 0;
presetWithFallback(p1, p2, p3);
parsed = true;
@@ -176,7 +182,7 @@ static bool remoteJson(int button)
}
// Callback function that will be executed when data is received
void handleRemote(uint8_t *incomingData, size_t len) {
void handleWiZdata(uint8_t *incomingData, size_t len) {
message_structure_t *incoming = reinterpret_cast<message_structure_t *>(incomingData);
if (strcmp(last_signal_src, linked_remote) != 0) {
@@ -202,8 +208,15 @@ void handleRemote(uint8_t *incomingData, size_t len) {
DEBUG_PRINT(F("] button: "));
DEBUG_PRINTLN(incoming->button);
if (!remoteJson(incoming->button))
switch (incoming->button) {
ESPNowButton = incoming->button; // save state, do not process in callback (can cause glitches)
last_seq = cur_seq;
}
// process ESPNow button data (acesses FS, should not be called while update to avoid glitches)
void handleRemote() {
if(ESPNowButton >= 0) {
if (!remoteJson(ESPNowButton))
switch (ESPNowButton) {
case WIZMOTE_BUTTON_ON : setOn(); break;
case WIZMOTE_BUTTON_OFF : setOff(); break;
case WIZMOTE_BUTTON_ONE : presetWithFallback(1, FX_MODE_STATIC, 0); break;
@@ -219,9 +232,10 @@ void handleRemote(uint8_t *incomingData, size_t len) {
case WIZ_SMART_BUTTON_BRIGHT_DOWN : brightnessDown(); break;
default: break;
}
last_seq = cur_seq;
}
ESPNowButton = -1;
}
#else
void handleRemote(uint8_t *incomingData, size_t len) {}
void handleRemote() {}
#endif

View File

@@ -209,12 +209,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax);
busConfigs[s] = new(std::nothrow) BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax);
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
// we will not bother with pre-allocating ColorOrderMappings vector
BusManager::getColorOrderMap().reset();
for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) {
int offset = s < 10 ? 48 : 55;
char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED
@@ -318,13 +319,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
gammaCorrectBri = request->hasArg(F("GB"));
gammaCorrectCol = request->hasArg(F("GC"));
gammaCorrectVal = request->arg(F("GV")).toFloat();
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3)
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal);
else {
if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) {
gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false;
gammaCorrectCol = false;
}
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table
fadeTransition = request->hasArg(F("TF"));
modeBlending = request->hasArg(F("EB"));
@@ -420,6 +420,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
t = request->arg(F("WO")).toInt();
if (t >= -255 && t <= 255) arlsOffset = t;
#ifdef WLED_ENABLE_DMX_INPUT
dmxInputTransmitPin = request->arg(F("IDMT")).toInt();
dmxInputReceivePin = request->arg(F("IDMR")).toInt();
dmxInputEnablePin = request->arg(F("IDME")).toInt();
dmxInputPort = request->arg(F("IDMP")).toInt();
if(dmxInputPort <= 0 || dmxInputPort > 2) dmxInputPort = 2;
#endif
#ifndef WLED_DISABLE_ALEXA
alexaEnabled = request->hasArg(F("AL"));
strlcpy(alexaInvocationName, request->arg(F("AI")).c_str(), 33);
@@ -838,8 +846,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
}
// temporary values, write directly to segments, globals are updated by setValuesFromFirstSelectedSeg()
uint32_t col0 = selseg.colors[0];
uint32_t col1 = selseg.colors[1];
uint32_t col0 = selseg.colors[0];
uint32_t col1 = selseg.colors[1];
uint32_t col2 = selseg.colors[2];
byte colIn[4] = {R(col0), G(col0), B(col0), W(col0)};
byte colInSec[4] = {R(col1), G(col1), B(col1), W(col1)};
byte effectIn = selseg.mode;
@@ -874,7 +883,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (pos > 0) {
spcI = std::max(0,getNumVal(&req, pos));
}
strip.setSegment(selectedSeg, startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY);
strip.suspend(); // must suspend strip operations before changing geometry
selseg.setGeometry(startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY, selseg.map1D2D);
strip.resume();
pos = req.indexOf(F("RV=")); //Segment reverse
if (pos > 0) selseg.reverse = req.charAt(pos+3) != '0';
@@ -920,7 +931,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set brightness
updateVal(req.c_str(), "&A=", &bri);
bool col0Changed = false, col1Changed = false;
bool col0Changed = false, col1Changed = false, col2Changed = false;
//set colors
col0Changed |= updateVal(req.c_str(), "&R=", &colIn[0]);
col0Changed |= updateVal(req.c_str(), "&G=", &colIn[1]);
@@ -977,23 +988,23 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
}
//set color from HEX or 32bit DEC
byte tmpCol[4];
pos = req.indexOf(F("CL="));
if (pos > 0) {
colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str());
colorFromDecOrHexString(colIn, req.substring(pos + 3).c_str());
col0Changed = true;
}
pos = req.indexOf(F("C2="));
if (pos > 0) {
colorFromDecOrHexString(colInSec, (char*)req.substring(pos + 3).c_str());
colorFromDecOrHexString(colInSec, req.substring(pos + 3).c_str());
col1Changed = true;
}
pos = req.indexOf(F("C3="));
if (pos > 0) {
colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str());
uint32_t col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]);
byte tmpCol[4];
colorFromDecOrHexString(tmpCol, req.substring(pos + 3).c_str());
col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]);
selseg.setColor(2, col2); // defined above (SS= or main)
if (!singleSegment) strip.setColor(2, col2); // will set color to all active & selected segments
col2Changed = true;
}
//set to random hue SR=0->1st SR=1->2nd
@@ -1004,29 +1015,22 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
col0Changed |= (!sec); col1Changed |= sec;
}
//swap 2nd & 1st
pos = req.indexOf(F("SC"));
if (pos > 0) {
byte temp;
for (unsigned i=0; i<4; i++) {
temp = colIn[i];
colIn[i] = colInSec[i];
colInSec[i] = temp;
}
col0Changed = col1Changed = true;
}
// apply colors to selected segment, and all selected segments if applicable
if (col0Changed) {
uint32_t colIn0 = RGBW32(colIn[0], colIn[1], colIn[2], colIn[3]);
selseg.setColor(0, colIn0);
if (!singleSegment) strip.setColor(0, colIn0); // will set color to all active & selected segments
col0 = RGBW32(colIn[0], colIn[1], colIn[2], colIn[3]);
selseg.setColor(0, col0);
}
if (col1Changed) {
uint32_t colIn1 = RGBW32(colInSec[0], colInSec[1], colInSec[2], colInSec[3]);
selseg.setColor(1, colIn1);
if (!singleSegment) strip.setColor(1, colIn1); // will set color to all active & selected segments
col1 = RGBW32(colInSec[0], colInSec[1], colInSec[2], colInSec[3]);
selseg.setColor(1, col1);
}
//swap 2nd & 1st
pos = req.indexOf(F("SC"));
if (pos > 0) {
std::swap(col0,col1);
col0Changed = col1Changed = true;
}
bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false;
@@ -1056,6 +1060,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (speedChanged) seg.speed = speedIn;
if (intensityChanged) seg.intensity = intensityIn;
if (paletteChanged) seg.setPalette(paletteIn);
if (col0Changed) seg.setColor(0, col0);
if (col1Changed) seg.setColor(1, col1);
if (col2Changed) seg.setColor(2, col2);
if (custom1Changed) seg.custom1 = custom1In;
if (custom2Changed) seg.custom2 = custom2In;
if (custom3Changed) seg.custom3 = custom3In;
@@ -1191,7 +1198,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
// internal call, does not send XML response
pos = req.indexOf(F("IN"));
if (pos < 1) {
if ((request != nullptr) && (pos < 1)) {
auto response = request->beginResponseStream("text/xml");
XML_response(*response);
request->send(response);

View File

@@ -34,8 +34,8 @@ static const int enablePin = -1; // disable the enable pin because it is not ne
static const int rxPin = -1; // disable the receiving pin because it is not needed - softhack007: Pin=-1 means "use default" not "disable"
static const int txPin = 2; // transmit DMX data over this pin (default is pin 2)
//DMX value array and size. Entry 0 will hold startbyte
static uint8_t dmxData[dmxMaxChannel] = { 0 };
//DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements
static uint8_t dmxData[dmxMaxChannel+1] = { 0 };
static int chanSize = 0;
#if !defined(DMX_SEND_ONLY)
static int currentChannel = 0;

View File

@@ -206,7 +206,7 @@ void notify(byte callMode, bool followUp)
notificationCount = followUp ? notificationCount + 1 : 0;
}
void parseNotifyPacket(uint8_t *udpIn) {
static void parseNotifyPacket(const uint8_t *udpIn) {
//ignore notification if received within a second after sending a notification ourselves
if (millis() - notificationSentTime < 1000) return;
if (udpIn[1] > 199) return; //do not receive custom versions
@@ -234,12 +234,12 @@ void parseNotifyPacket(uint8_t *udpIn) {
//apply colors from notification to main segment, only if not syncing full segments
if ((receiveNotificationColor || !someSel) && (version < 11 || !receiveSegmentOptions)) {
// primary color, only apply white if intented (version > 0)
strip.setColor(0, RGBW32(udpIn[3], udpIn[4], udpIn[5], (version > 0) ? udpIn[10] : 0));
strip.getMainSegment().setColor(0, RGBW32(udpIn[3], udpIn[4], udpIn[5], (version > 0) ? udpIn[10] : 0));
if (version > 1) {
strip.setColor(1, RGBW32(udpIn[12], udpIn[13], udpIn[14], udpIn[15])); // secondary color
strip.getMainSegment().setColor(1, RGBW32(udpIn[12], udpIn[13], udpIn[14], udpIn[15])); // secondary color
}
if (version > 6) {
strip.setColor(2, RGBW32(udpIn[20], udpIn[21], udpIn[22], udpIn[23])); // tertiary color
strip.getMainSegment().setColor(2, RGBW32(udpIn[20], udpIn[21], udpIn[22], udpIn[23])); // tertiary color
if (version > 9 && udpIn[37] < 255) { // valid CCT/Kelvin value
unsigned cct = udpIn[38];
if (udpIn[37] > 0) { //Kelvin
@@ -260,11 +260,12 @@ void parseNotifyPacket(uint8_t *udpIn) {
// are we syncing bounds and slave has more active segments than master?
if (receiveSegmentBounds && numSrcSegs < strip.getActiveSegmentsNum()) {
DEBUG_PRINTLN(F("Removing excessive segments."));
for (size_t i=strip.getSegmentsNum(); i>numSrcSegs; i--) {
if (strip.getSegment(i).isActive()) {
strip.setSegment(i-1,0,0); // delete segment
}
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
for (size_t i=strip.getSegmentsNum(); i>numSrcSegs && i>0; i--) {
Segment &seg = strip.getSegment(i-1);
if (seg.isActive()) seg.deactivate(); // delete segment
}
strip.resume();
}
size_t inactiveSegs = 0;
for (size_t i = 0; i < numSrcSegs && i < strip.getMaxSegments(); i++) {
@@ -300,7 +301,7 @@ void parseNotifyPacket(uint8_t *udpIn) {
if (!receiveSegmentOptions) {
DEBUG_PRINTF_P(PSTR("Set segment w/o options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY);
selseg.setGeometry(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY, selseg.map1D2D);
strip.resume();
continue; // we do receive bounds, but not options
}
@@ -342,12 +343,12 @@ void parseNotifyPacket(uint8_t *udpIn) {
if (receiveSegmentBounds) {
DEBUG_PRINTF_P(PSTR("Set segment w/ options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY);
selseg.setGeometry(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY, selseg.map1D2D);
strip.resume();
} else {
DEBUG_PRINTF_P(PSTR("Set segment grouping: %d [%d,%d]\n"), id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY);
selseg.setGeometry(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY, selseg.map1D2D);
strip.resume();
}
}
@@ -416,18 +417,18 @@ void realtimeLock(uint32_t timeoutMs, byte md)
start = mainseg.start;
stop = mainseg.stop;
mainseg.freeze = true;
// if WLED was off and using main segment only, freeze non-main segments so they stay off
if (bri == 0) {
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
strip.getSegment(s).freeze = true;
}
}
} else {
start = 0;
stop = strip.getLengthTotal();
}
// clear strip/segment
for (size_t i = start; i < stop; i++) strip.setPixelColor(i,BLACK);
// if WLED was off and using main segment only, freeze non-main segments so they stay off
if (useMainSegmentOnly && bri == 0) {
for (size_t s=0; s < strip.getSegmentsNum(); s++) {
strip.getSegment(s).freeze = true;
}
}
}
// if strip is off (bri==0) and not already in RTM
if (briT == 0 && !realtimeMode && !realtimeOverride) {
@@ -510,12 +511,10 @@ void handleNotifications()
rgbUdp.read(lbuf, packetSize);
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION);
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
unsigned id = 0;
unsigned totalLen = strip.getLengthTotal();
for (size_t i = 0; i < packetSize -2; i += 3)
{
if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
for (size_t i = 0, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) {
setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0);
id++; if (id >= totalLen) break;
}
if (!(realtimeMode && useMainSegmentOnly)) strip.show();
return;
@@ -595,17 +594,11 @@ void handleNotifications()
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
unsigned totalLen = strip.getLengthTotal();
for (size_t i = 6; i < tpmPayloadFrameSize + 4U; i += 3)
{
if (id < totalLen)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
}
else break;
if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
}
if (tpmPacketCount == numPackets) //reset packet count and show if all packets were received
{
if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
tpmPacketCount = 0;
strip.show();
}
@@ -629,6 +622,7 @@ void handleNotifications()
if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return;
unsigned totalLen = strip.getLengthTotal();
if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor()
if (udpIn[0] == 1 && packetSize > 5) //warls
{
for (size_t i = 2; i < packetSize -3; i += 4)
@@ -637,39 +631,29 @@ void handleNotifications()
}
} else if (udpIn[0] == 2 && packetSize > 4) //drgb
{
unsigned id = 0;
for (size_t i = 2; i < packetSize -2; i += 3)
for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++; if (id >= totalLen) break;
}
} else if (udpIn[0] == 3 && packetSize > 6) //drgbw
{
unsigned id = 0;
for (size_t i = 2; i < packetSize -3; i += 4)
for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++; if (id >= totalLen) break;
}
} else if (udpIn[0] == 4 && packetSize > 7) //dnrgb
{
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (size_t i = 4; i < packetSize -2; i += 3)
for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++)
{
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
}
} else if (udpIn[0] == 5 && packetSize > 8) //dnrgbw
{
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (size_t i = 4; i < packetSize -2; i += 4)
for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++)
{
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++;
}
}
strip.show();
@@ -704,11 +688,11 @@ void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w)
b = gamma8(b);
w = gamma8(w);
}
uint32_t col = RGBW32(r,g,b,w);
if (useMainSegmentOnly) {
Segment &seg = strip.getMainSegment();
if (pix<seg.length()) seg.setPixelColor(pix, r, g, b, w);
strip.getMainSegment().setPixelColor(pix, col); // this expects that strip.getMainSegment().beginDraw() has been called in handleNotification()
} else {
strip.setPixelColor(pix, r, g, b, w);
strip.setPixelColor(pix, col);
}
}
}
@@ -826,7 +810,7 @@ static size_t sequenceNumber = 0; // this needs to be shared across all ou
static const size_t ART_NET_HEADER_SIZE = 12;
static const byte ART_NET_HEADER[] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e};
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW) {
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri, bool isRGBW) {
if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap
WiFiUDP ddpUdp;
@@ -979,7 +963,7 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs
// handle WiZ Mote data
if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) {
handleRemote(data, len);
handleWiZdata(data, len);
return;
}

View File

@@ -3,6 +3,9 @@
* Registration and management utility for v2 usermods
*/
static Usermod* ums[WLED_MAX_USERMODS] = {nullptr};
byte UsermodManager::numMods = 0;
//Usermod Manager internals
void UsermodManager::setup() { for (unsigned i = 0; i < numMods; i++) ums[i]->setup(); }
void UsermodManager::connected() { for (unsigned i = 0; i < numMods; i++) ums[i]->connected(); }
@@ -69,8 +72,6 @@ bool UsermodManager::add(Usermod* um)
return true;
}
Usermod* UsermodManager::ums[WLED_MAX_USERMODS] = {nullptr};
byte UsermodManager::numMods = 0;
/* Usermod v2 interface shim for oappend */
Print* Usermod::oappend_shim = nullptr;

View File

@@ -45,7 +45,7 @@
#endif
#ifdef USERMOD_BH1750
#include "../usermods/BH1750_v2/usermod_BH1750.h"
#include "../usermods/BH1750_v2/usermod_bh1750.h"
#endif
// BME280 v2 usermod. Define "USERMOD_BME280" in my_config.h
@@ -242,6 +242,14 @@
#include "../usermods/LD2410_v2/usermod_ld2410.h"
#endif
#ifdef USERMOD_DEEP_SLEEP
#include "../usermods/deep_sleep/usermod_deep_sleep.h"
#endif
#ifdef USERMOD_RF433
#include "../usermods/usermod_v2_RF433/usermod_v2_RF433.h"
#endif
void registerUsermods()
{
/*
@@ -470,4 +478,12 @@ void registerUsermods()
#ifdef USERMOD_POV_DISPLAY
UsermodManager::add(new PovDisplayUsermod());
#endif
#ifdef USERMOD_DEEP_SLEEP
UsermodManager::add(new DeepSleepUsermod());
#endif
#ifdef USERMOD_RF433
UsermodManager::add(new RF433Usermod());
#endif
}

View File

@@ -14,7 +14,7 @@ int getNumVal(const String* req, uint16_t pos)
void parseNumber(const char* str, byte* val, byte minv, byte maxv)
{
if (str == nullptr || str[0] == '\0') return;
if (str[0] == 'r') {*val = random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0
if (str[0] == 'r') {*val = hw_random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0
bool wrap = false;
if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;}
if (str[0] == '~') {
@@ -52,7 +52,7 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv)
*val = atoi(str);
}
//getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form)
bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
if (elem.is<int>()) {
if (elem < 0) return false; //ignore e.g. {"ps":-1}
@@ -60,8 +60,12 @@ bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
return true;
} else if (elem.is<const char*>()) {
const char* str = elem;
size_t len = strnlen(str, 12);
if (len == 0 || len > 10) return false;
size_t len = strnlen(str, 14);
if (len == 0 || len > 12) return false;
// fix for #3605 & #4346
// ignore vmin and vmax and use as specified in API
if (len > 3 && (strchr(str,'r') || strchr(str,'~') != strrchr(str,'~'))) vmax = vmin = 0; // we have "X~Y(r|~[w][-][Z])" form
// end fix
parseNumber(str, val, vmin, vmax);
return true;
}
@@ -69,7 +73,7 @@ bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
}
bool getBoolVal(JsonVariant elem, bool dflt) {
bool getBoolVal(const JsonVariant &elem, bool dflt) {
if (elem.is<const char*>() && elem.as<const char*>()[0] == 't') {
return !dflt;
} else {
@@ -147,7 +151,7 @@ bool isAsterisksOnly(const char* str, byte maxLen)
//threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994
bool requestJSONBufferLock(uint8_t module)
bool requestJSONBufferLock(uint8_t moduleID)
{
if (pDoc == nullptr) {
DEBUG_PRINTLN(F("ERROR: JSON buffer not allocated!"));
@@ -171,14 +175,14 @@ bool requestJSONBufferLock(uint8_t module)
#endif
// If the lock is still held - by us, or by another task
if (jsonBufferLock) {
DEBUG_PRINTF_P(PSTR("ERROR: Locking JSON buffer (%d) failed! (still locked by %d)\n"), module, jsonBufferLock);
DEBUG_PRINTF_P(PSTR("ERROR: Locking JSON buffer (%d) failed! (still locked by %d)\n"), moduleID, jsonBufferLock);
#ifdef ARDUINO_ARCH_ESP32
xSemaphoreGiveRecursive(jsonBufferLockMutex);
#endif
return false;
}
jsonBufferLock = module ? module : 255;
jsonBufferLock = moduleID ? moduleID : 255;
DEBUG_PRINTF_P(PSTR("JSON buffer locked. (%d)\n"), jsonBufferLock);
pDoc->clear();
return true;
@@ -261,16 +265,16 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
if (mode < strip.getModeCount()) {
String lineBuffer = FPSTR(strip.getModeData(mode));
if (lineBuffer.length() > 0) {
unsigned start = lineBuffer.indexOf('@');
unsigned stop = lineBuffer.indexOf(';', start);
int start = lineBuffer.indexOf('@'); // String::indexOf() returns an int, not an unsigned; -1 means "not found"
int stop = lineBuffer.indexOf(';', start);
if (start>0 && stop>0) {
String names = lineBuffer.substring(start, stop); // include @
unsigned nameBegin = 1, nameEnd, nameDefault;
int nameBegin = 1, nameEnd, nameDefault;
if (slider < 10) {
for (size_t i=0; i<=slider; i++) {
const char *tmpstr;
dest[0] = '\0'; //clear dest buffer
if (nameBegin == 0) break; // there are no more names
if (nameBegin <= 0) break; // there are no more names
nameEnd = names.indexOf(',', nameBegin);
if (i == slider) {
nameDefault = names.indexOf('=', nameBegin); // find default value
@@ -372,6 +376,39 @@ uint16_t crc16(const unsigned char* data_p, size_t length) {
return crc;
}
// fastled beatsin: 1:1 replacements to remove the use of fastled sin16()
// Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
{
uint16_t beat = beat88( beats_per_minute_88, timebase);
uint16_t beatsin (sin16_t( beat + phase_offset) + 32768);
uint16_t rangewidth = highest - lowest;
uint16_t scaledbeat = scale16( beatsin, rangewidth);
uint16_t result = lowest + scaledbeat;
return result;
}
// Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
{
uint16_t beat = beat16( beats_per_minute, timebase);
uint16_t beatsin = (sin16_t( beat + phase_offset) + 32768);
uint16_t rangewidth = highest - lowest;
uint16_t scaledbeat = scale16( beatsin, rangewidth);
uint16_t result = lowest + scaledbeat;
return result;
}
// Generates an 8-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest, uint8_t highest, uint32_t timebase, uint8_t phase_offset)
{
uint8_t beat = beat8( beats_per_minute, timebase);
uint8_t beatsin = sin8_t( beat + phase_offset);
uint8_t rangewidth = highest - lowest;
uint8_t scaledbeat = scale8( beatsin, rangewidth);
uint8_t result = lowest + scaledbeat;
return result;
}
///////////////////////////////////////////////////////////////////////////////
// Begin simulateSound (to enable audio enhanced effects to display something)
@@ -431,15 +468,15 @@ um_data_t* simulateSound(uint8_t simulationId)
default:
case UMS_BeatSin:
for (int i = 0; i<16; i++)
fftResult[i] = beatsin8(120 / (i+1), 0, 255);
// fftResult[i] = (beatsin8(120, 0, 255) + (256/16 * i)) % 256;
volumeSmth = fftResult[8];
fftResult[i] = beatsin8_t(120 / (i+1), 0, 255);
// fftResult[i] = (beatsin8_t(120, 0, 255) + (256/16 * i)) % 256;
volumeSmth = fftResult[8];
break;
case UMS_WeWillRockYou:
if (ms%2000 < 200) {
volumeSmth = random8(255);
volumeSmth = hw_random8();
for (int i = 0; i<5; i++)
fftResult[i] = random8(255);
fftResult[i] = hw_random8();
}
else if (ms%2000 < 400) {
volumeSmth = 0;
@@ -447,9 +484,9 @@ um_data_t* simulateSound(uint8_t simulationId)
fftResult[i] = 0;
}
else if (ms%2000 < 600) {
volumeSmth = random8(255);
volumeSmth = hw_random8();
for (int i = 5; i<11; i++)
fftResult[i] = random8(255);
fftResult[i] = hw_random8();
}
else if (ms%2000 < 800) {
volumeSmth = 0;
@@ -457,9 +494,9 @@ um_data_t* simulateSound(uint8_t simulationId)
fftResult[i] = 0;
}
else if (ms%2000 < 1000) {
volumeSmth = random8(255);
volumeSmth = hw_random8();
for (int i = 11; i<16; i++)
fftResult[i] = random8(255);
fftResult[i] = hw_random8();
}
else {
volumeSmth = 0;
@@ -469,17 +506,17 @@ um_data_t* simulateSound(uint8_t simulationId)
break;
case UMS_10_13:
for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
volumeSmth = fftResult[8];
fftResult[i] = inoise8(beatsin8_t(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
volumeSmth = fftResult[8];
break;
case UMS_14_3:
for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3);
fftResult[i] = inoise8(beatsin8_t(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3);
volumeSmth = fftResult[8];
break;
}
samplePeak = random8() > 250;
samplePeak = hw_random8() > 250;
FFT_MajorPeak = 21 + (volumeSmth*volumeSmth) / 8.0f; // walk thru full range of 21hz...8200hz
maxVol = 31; // this gets feedback fro UI
binNum = 8; // this gets feedback fro UI
@@ -503,7 +540,7 @@ void enumerateLedmaps() {
#ifndef ESP8266
if (ledmapNames[i-1]) { //clear old name
delete[] ledmapNames[i-1];
free(ledmapNames[i-1]);
ledmapNames[i-1] = nullptr;
}
#endif
@@ -521,7 +558,7 @@ void enumerateLedmaps() {
const char *name = root["n"].as<const char*>();
if (name != nullptr) len = strlen(name);
if (len > 0 && len < 33) {
ledmapNames[i-1] = new char[len+1];
ledmapNames[i-1] = static_cast<char*>(malloc(len+1));
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, 33);
}
}
@@ -529,7 +566,7 @@ void enumerateLedmaps() {
char tmp[33];
snprintf_P(tmp, 32, s_ledmap_tmpl, i);
len = strlen(tmp);
ledmapNames[i-1] = new char[len+1];
ledmapNames[i-1] = static_cast<char*>(malloc(len+1));
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], tmp, 33);
}
}
@@ -547,7 +584,7 @@ void enumerateLedmaps() {
uint8_t get_random_wheel_index(uint8_t pos) {
uint8_t r = 0, x = 0, y = 0, d = 0;
while (d < 42) {
r = random8();
r = hw_random8();
x = abs(pos - r);
y = 255 - x;
d = MIN(x, y);
@@ -559,3 +596,18 @@ uint8_t get_random_wheel_index(uint8_t pos) {
float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
// 32 bit random number generator, inlining uses more code, use hw_random16() if speed is critical (see fcn_declare.h)
uint32_t hw_random(uint32_t upperlimit) {
uint32_t rnd = hw_random();
uint64_t scaled = uint64_t(rnd) * uint64_t(upperlimit);
return scaled >> 32;
}
int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
if(lowerlimit >= upperlimit) {
return lowerlimit;
}
uint32_t diff = upperlimit - lowerlimit;
return hw_random(diff) + lowerlimit;
}

View File

@@ -65,7 +65,10 @@ void WLED::loop()
handleNotifications();
handleTransitions();
#ifdef WLED_ENABLE_DMX
handleDMX();
handleDMXOutput();
#endif
#ifdef WLED_ENABLE_DMX_INPUT
dmxInput.update();
#endif
#ifdef WLED_DEBUG
@@ -84,6 +87,9 @@ void WLED::loop()
#ifndef WLED_DISABLE_INFRARED
handleIR();
#endif
#ifndef WLED_DISABLE_ESPNOW
handleRemote();
#endif
#ifndef WLED_DISABLE_ALEXA
handleAlexa();
#endif
@@ -222,6 +228,7 @@ void WLED::loop()
BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005
if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments();
BusManager::setBrightness(bri); // fix re-initialised bus' brightness
doSerializeConfig = true;
}
if (loadLedmap >= 0) {
@@ -478,10 +485,7 @@ void WLED::setup()
if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0)
showWelcomePage = true;
WiFi.persistent(false);
#ifdef WLED_USE_ETHERNET
WiFi.onEvent(WiFiEvent);
#endif
WiFi.mode(WIFI_STA); // enable scanning
findWiFi(true); // start scanning for available WiFi-s
@@ -526,7 +530,10 @@ void WLED::setup()
}
#endif
#ifdef WLED_ENABLE_DMX
initDMX();
initDMXOutput();
#endif
#ifdef WLED_ENABLE_DMX_INPUT
dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPort);
#endif
#ifdef WLED_ENABLE_ADALIGHT
@@ -546,14 +553,8 @@ void WLED::setup()
#endif
// Seed FastLED random functions with an esp random value, which already works properly at this point.
#if defined(ARDUINO_ARCH_ESP32)
const uint32_t seed32 = esp_random();
#elif defined(ARDUINO_ARCH_ESP8266)
const uint32_t seed32 = RANDOM_REG32;
#else
const uint32_t seed32 = random(std::numeric_limits<long>::max());
#endif
random16_set_seed((uint16_t)((seed32 & 0xFFFF) ^ (seed32 >> 16)));
const uint32_t seed32 = hw_random();
random16_set_seed((uint16_t)seed32);
#if WLED_WATCHDOG_TIMEOUT > 0
enableWatchdog();
@@ -578,10 +579,11 @@ void WLED::beginStrip()
} else {
// fix for #3196
if (bootPreset > 0) {
bool oldTransition = fadeTransition; // workaround if transitions are enabled
fadeTransition = false; // ignore transitions temporarily
strip.setColor(0, BLACK); // set all segments black
fadeTransition = oldTransition; // restore transitions
// set all segments black (no transition)
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment &seg = strip.getSegment(i);
if (seg.isActive()) seg.colors[0] = BLACK;
}
col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated()
}
briLast = briS; bri = 0;
@@ -781,8 +783,7 @@ int8_t WLED::findWiFi(bool doScan) {
void WLED::initConnection()
{
DEBUG_PRINTLN(F("initConnection() called."));
DEBUG_PRINTF_P(PSTR("initConnection() called @ %lus.\n"), millis()/1000);
#ifdef WLED_ENABLE_WEBSOCKETS
ws.onEvent(wsEvent);
#endif
@@ -811,6 +812,7 @@ void WLED::initConnection()
if (!WLED_WIFI_CONFIGURED) {
DEBUG_PRINTLN(F("No connection configured."));
if (!apActive) initAP(); // instantly go to ap mode
return;
} else if (!apActive) {
if (apBehavior == AP_BEHAVIOR_ALWAYS) {
DEBUG_PRINTLN(F("Access point ALWAYS enabled."));
@@ -825,9 +827,7 @@ void WLED::initConnection()
if (WLED_WIFI_CONFIGURED) {
showWelcomePage = false;
DEBUG_PRINT(F("Connecting to "));
DEBUG_PRINT(multiWiFi[selectedWiFi].clientSSID);
DEBUG_PRINTLN(F("..."));
DEBUG_PRINTF_P(PSTR("Connecting to %s...\n"), multiWiFi[selectedWiFi].clientSSID);
// convert the "serverDescription" into a valid DNS hostname (alphanumeric)
char hostname[25];
@@ -926,7 +926,8 @@ void WLED::handleConnection()
{
static bool scanDone = true;
static byte stacO = 0;
unsigned long now = millis();
const unsigned long now = millis();
const unsigned long nowS = now/1000;
const bool wifiConfigured = WLED_WIFI_CONFIGURED;
// ignore connection handling if WiFi is configured and scan still running
@@ -935,7 +936,7 @@ void WLED::handleConnection()
return;
if (lastReconnectAttempt == 0 || forceReconnect) {
DEBUG_PRINTLN(F("Initial connect or forced reconnect."));
DEBUG_PRINTF_P(PSTR("Initial connect or forced reconnect (@ %lus).\n"), nowS);
selectedWiFi = findWiFi(); // find strongest WiFi
initConnection();
interfacesInited = false;
@@ -955,8 +956,7 @@ void WLED::handleConnection()
#endif
if (stac != stacO) {
stacO = stac;
DEBUG_PRINT(F("Connected AP clients: "));
DEBUG_PRINTLN(stac);
DEBUG_PRINTF_P(PSTR("Connected AP clients: %d\n"), (int)stac);
if (!WLED_CONNECTED && wifiConfigured) { // trying to connect, but not connected
if (stac)
WiFi.disconnect(); // disable search so that AP can work
@@ -979,6 +979,7 @@ void WLED::handleConnection()
initConnection();
interfacesInited = false;
scanDone = true;
return;
}
//send improv failed 6 seconds after second init attempt (24 sec. after provisioning)
if (improvActive > 2 && now - lastReconnectAttempt > 6000) {
@@ -987,13 +988,13 @@ void WLED::handleConnection()
}
if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && wifiConfigured) {
if (improvActive == 2) improvActive = 3;
DEBUG_PRINTLN(F("Last reconnect too old."));
DEBUG_PRINTF_P(PSTR("Last reconnect (%lus) too old (@ %lus).\n"), lastReconnectAttempt/1000, nowS);
if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list
initConnection();
}
if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) {
if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) {
DEBUG_PRINTLN(F("Not connected AP."));
DEBUG_PRINTF_P(PSTR("Not connected AP (@ %lus).\n"), nowS);
initAP(); // start AP only within first 5min
}
}
@@ -1003,7 +1004,7 @@ void WLED::handleConnection()
dnsServer.stop();
WiFi.softAPdisconnect(true);
apActive = false;
DEBUG_PRINTLN(F("Temporary AP disabled."));
DEBUG_PRINTF_P(PSTR("Temporary AP disabled (@ %lus).\n"), nowS);
}
}
} else if (!interfacesInited) { //newly connected

View File

@@ -3,12 +3,11 @@
/*
Main sketch, global variable declarations
@title WLED project sketch
@version 0.15.0-b7
@author Christian Schwinne
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2410270
#define VERSION 2412040
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -145,6 +144,10 @@
#endif
#endif
#ifdef WLED_ENABLE_DMX_INPUT
#include "dmx_input.h"
#endif
#include "src/dependencies/e131/ESPAsyncE131.h"
#ifndef WLED_DISABLE_MQTT
#include "src/dependencies/async-mqtt-client/AsyncMqttClient.h"
@@ -264,13 +267,13 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#define WLED_VERSION dev
#endif
#ifndef WLED_RELEASE_NAME
#define WLED_RELEASE_NAME dev_release
#define WLED_RELEASE_NAME "Custom"
#endif
// Global Variable definitions
WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION));
WLED_GLOBAL char releaseString[] _INIT(TOSTRING(WLED_RELEASE_NAME)); // somehow this will not work if using "const char releaseString[]
#define WLED_CODENAME "Kōsen"
WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // must include the quotes when defining, e.g -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\"
#define WLED_CODENAME "Niji"
// AP and OTA default passwords (for maximum security change them!)
WLED_GLOBAL char apPass[65] _INIT(WLED_AP_PASS);
@@ -460,7 +463,15 @@ WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to f
WLED_GLOBAL uint16_t DMXStart _INIT(10); // start address of the first fixture
WLED_GLOBAL uint16_t DMXStartLED _INIT(0); // LED from which DMX fixtures start
#endif
WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consecutive universes)
#ifdef WLED_ENABLE_DMX_INPUT
WLED_GLOBAL int dmxInputTransmitPin _INIT(0);
WLED_GLOBAL int dmxInputReceivePin _INIT(0);
WLED_GLOBAL int dmxInputEnablePin _INIT(0);
WLED_GLOBAL int dmxInputPort _INIT(2);
WLED_GLOBAL DMXInput dmxInput;
#endif
WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes)
WLED_GLOBAL uint16_t e131Port _INIT(5568); // DMX in port. E1.31 default is 5568, Art-Net is 6454
WLED_GLOBAL byte e131Priority _INIT(0); // E1.31 port priority (if != 0 priority handling is active)
WLED_GLOBAL E131Priority highPriority _INIT(3); // E1.31 highest priority tracking, init = timeout in seconds
@@ -483,10 +494,10 @@ WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other
#endif
WLED_GLOBAL AsyncMqttClient *mqtt _INIT(NULL);
WLED_GLOBAL bool mqttEnabled _INIT(false);
WLED_GLOBAL char mqttStatusTopic[40] _INIT(""); // this must be global because of async handlers
WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN+1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac)
WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN+1] _INIT("wled/all"); // second MQTT topic (for example to group devices)
WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN+1] _INIT(""); // both domains and IPs should work (no SSL)
WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers
WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac)
WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT("wled/all"); // second MQTT topic (for example to group devices)
WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN + 1] _INIT(""); // both domains and IPs should work (no SSL)
WLED_GLOBAL char mqttUser[41] _INIT(""); // optional: username for MQTT auth
WLED_GLOBAL char mqttPass[65] _INIT(""); // optional: password for MQTT auth
WLED_GLOBAL char mqttClientID[41] _INIT(""); // override the client ID
@@ -583,7 +594,6 @@ WLED_GLOBAL bool transitionActive _INIT(false);
WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration
WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json)
WLED_GLOBAL unsigned long transitionStartTime;
WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f
WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt")
WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s)
WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random
@@ -897,9 +907,6 @@ WLED_GLOBAL uint32_t ledMaps _INIT(0); // bitfield representation of available l
WLED_GLOBAL uint16_t ledMaps _INIT(0); // bitfield representation of available ledmaps
#endif
// Usermod manager
WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
// global I2C SDA pin (used for usermods)
#ifndef I2CSDAPIN
WLED_GLOBAL int8_t i2c_sda _INIT(-1);

4
wled00/wled_eeprom.cpp Executable file → Normal file
View File

@@ -2,6 +2,10 @@
#include <EEPROM.h>
#include "wled.h"
#if defined(WLED_ENABLE_MQTT) && MQTT_MAX_TOPIC_LEN < 32
#error "MQTT topics length < 32 is not supported by the EEPROM module!"
#endif
/*
* DEPRECATED, do not use for new settings
* Only used to restore config from pre-0.11 installations using the deEEP() methods

View File

@@ -10,16 +10,25 @@
//#define WLED_DEBUG_MATH
// Note: cos_t, sin_t and tan_t are very accurate but slow
// the math.h functions use several kB of flash and are to be avoided if possible
// sin16_t / cos16_t are faster and much more accurate than the fastled variants
// sin_approx and cos_approx are float wrappers for sin16_t/cos16_t and have an accuracy better than +/-0.0015 compared to sinf()
// sin8_t / cos8_t are fastled replacements and use sin16_t / cos16_t. Slightly slower than fastled version but very accurate
// Taylor series approximations, replaced with Bhaskara I's approximation
/*
#define modd(x, y) ((x) - (int)((x) / (y)) * (y))
float cos_t(float phi)
{
float x = modd(phi, TWO_PI);
float x = modd(phi, M_TWOPI);
if (x < 0) x = -1 * x;
int8_t sign = 1;
if (x > PI)
if (x > M_PI)
{
x -= PI;
x -= M_PI;
sign = -1;
}
float xx = x * x;
@@ -31,8 +40,8 @@ float cos_t(float phi)
return res;
}
float sin_t(float x) {
float res = cos_t(HALF_PI - x);
float sin_t(float phi) {
float res = cos_t(M_PI_2 - phi);
#ifdef WLED_DEBUG_MATH
Serial.printf("sin: %f,%f,%f,(%f)\n",x,res,sin(x),res-sin(x));
#endif
@@ -48,6 +57,80 @@ float tan_t(float x) {
#endif
return res;
}
*/
// 16-bit, integer based Bhaskara I's sine approximation: 16*x*(pi - x) / (5*pi^2 - 4*x*(pi - x))
// input is 16bit unsigned (0-65535), output is 16bit signed (-32767 to +32767)
// optimized integer implementation by @dedehai
int16_t sin16_t(uint16_t theta) {
int scale = 1;
if (theta > 0x7FFF) {
theta = 0xFFFF - theta;
scale = -1; // second half of the sine function is negative (pi - 2*pi)
}
uint32_t precal = theta * (0x7FFF - theta);
uint64_t numerator = (uint64_t)precal * (4 * 0x7FFF); // 64bit required
int32_t denominator = 1342095361 - precal; // 1342095361 is 5 * 0x7FFF^2 / 4
int16_t result = numerator / denominator;
return result * scale;
}
int16_t cos16_t(uint16_t theta) {
return sin16_t(theta + 0x4000); //cos(x) = sin(x+pi/2)
}
uint8_t sin8_t(uint8_t theta) {
int32_t sin16 = sin16_t((uint16_t)theta * 257); // 255 * 257 = 0xFFFF
sin16 += 0x7FFF + 128; //shift result to range 0-0xFFFF, +128 for rounding
return min(sin16, int32_t(0xFFFF)) >> 8; // min performs saturation, and prevents overflow
}
uint8_t cos8_t(uint8_t theta) {
return sin8_t(theta + 64); //cos(x) = sin(x+pi/2)
}
float sin_approx(float theta) {
uint16_t scaled_theta = (int)(theta * (float)(0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3)
int32_t result = sin16_t(scaled_theta);
float sin = float(result) / 0x7FFF;
return sin;
}
float cos_approx(float theta) {
uint16_t scaled_theta = (int)(theta * (float)(0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3)
int32_t result = sin16_t(scaled_theta + 0x4000);
float cos = float(result) / 0x7FFF;
return cos;
}
float tan_approx(float x) {
float c = cos_approx(x);
if (c==0.0f) return 0;
float res = sin_approx(x) / c;
return res;
}
#define ATAN2_CONST_A 0.1963f
#define ATAN2_CONST_B 0.9817f
// atan2_t approximation, with the idea from https://gist.github.com/volkansalma/2972237?permalink_comment_id=3872525#gistcomment-3872525
float atan2_t(float y, float x) {
float abs_y = fabs(y);
float abs_x = fabs(x);
float r = (abs_x - abs_y) / (abs_y + abs_x + 1e-10f); // avoid division by zero by adding a small nubmer
float angle;
if(x < 0) {
r = -r;
angle = M_PI_2 + M_PI_4;
}
else
angle = M_PI_2 - M_PI_4;
float add = (ATAN2_CONST_A * (r * r) - ATAN2_CONST_B) * r;
angle += add;
angle = y < 0 ? -angle : angle;
return angle;
}
//https://stackoverflow.com/questions/3380628
// Absolute error <= 6.7e-5
@@ -60,10 +143,10 @@ float acos_t(float x) {
ret = ret * xabs;
ret = ret - 0.2121144f;
ret = ret * xabs;
ret = ret + HALF_PI;
ret = ret + M_PI_2;
ret = ret * sqrt(1.0f-xabs);
ret = ret - 2 * negate * ret;
float res = negate * PI + ret;
float res = negate * M_PI + ret;
#ifdef WLED_DEBUG_MATH
Serial.printf("acos: %f,%f,%f,(%f)\n",x,res,acos(x),res-acos(x));
#endif
@@ -71,7 +154,7 @@ float acos_t(float x) {
}
float asin_t(float x) {
float res = HALF_PI - acos_t(x);
float res = M_PI_2 - acos_t(x);
#ifdef WLED_DEBUG_MATH
Serial.printf("asin: %f,%f,%f,(%f)\n",x,res,asin(x),res-asin(x));
#endif
@@ -87,7 +170,7 @@ float atan_t(float x) {
//For A/B/C, see https://stackoverflow.com/a/42542593
static const double A { 0.0776509570923569 };
static const double B { -0.287434475393028 };
static const double C { ((HALF_PI/2) - A - B) };
static const double C { ((M_PI_4) - A - B) };
// polynominal factors for approximation between 1 and 5
static const float C0 { 0.089494f };
static const float C1 { 0.974207f };
@@ -102,7 +185,7 @@ float atan_t(float x) {
x = std::abs(x);
float res;
if (x > 5.0f) { // atan(x) converges to pi/2 - (1/x) for large values
res = HALF_PI - (1.0f/x);
res = M_PI_2 - (1.0f/x);
} else if (x > 1.0f) { //1 < x < 5
float xx = x * x;
res = (C4*xx*xx)+(C3*xx*x)+(C2*xx)+(C1*x)+C0;
@@ -137,3 +220,27 @@ float fmod_t(float num, float denom) {
#endif
return res;
}
// bit-wise integer square root calculation (exact)
uint32_t sqrt32_bw(uint32_t x) {
uint32_t res = 0;
uint32_t bit;
uint32_t num = x; // use 32bit for faster calculation
if(num < 1 << 10) bit = 1 << 10; // speed optimization for small numbers < 32^2
else if (num < 1 << 20) bit = 1 << 20; // speed optimization for medium numbers < 1024^2
else bit = 1 << 30; // start with highest power of 4 <= 2^32
while (bit > num) bit >>= 2; // reduce iterations
while (bit != 0) {
if (num >= res + bit) {
num -= res + bit;
res = (res >> 1) + bit;
} else {
res >>= 1;
}
bit >>= 2;
}
return res;
}

View File

@@ -113,8 +113,8 @@ void handleSerial()
//only send response if TX pin is unused for other purposes
if (verboseResponse && serialCanTX) {
pDoc->clear();
JsonObject state = pDoc->createNestedObject("state");
serializeState(state);
JsonObject stateDoc = pDoc->createNestedObject("state");
serializeState(stateDoc);
JsonObject info = pDoc->createNestedObject("info");
serializeInfo(info);

View File

@@ -21,7 +21,7 @@ static const char s_accessdenied[] PROGMEM = "Access Denied";
static const char _common_js[] PROGMEM = "/common.js";
//Is this an IP?
static bool isIp(String str) {
static bool isIp(const String &str) {
for (size_t i = 0; i < str.length(); i++) {
int c = str.charAt(i);
if (c != '.' && (c < '0' || c > '9')) {
@@ -152,9 +152,9 @@ static String msgProcessor(const String& var)
return String();
}
static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool isFinal) {
if (!correctPIN) {
if (final) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg));
if (isFinal) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg));
return;
}
if (!index) {
@@ -170,7 +170,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename,
if (len) {
request->_tempFile.write(data,len);
}
if (final) {
if (isFinal) {
request->_tempFile.close();
if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash
doReboot = true;
@@ -359,7 +359,7 @@ void initServer()
server.on(F("/upload"), HTTP_POST, [](AsyncWebServerRequest *request) {},
[](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data,
size_t len, bool final) {handleUpload(request, filename, index, data, len, final);}
size_t len, bool isFinal) {handleUpload(request, filename, index, data, len, isFinal);}
);
createEditHandler(correctPIN);
@@ -389,7 +389,7 @@ void initServer()
serveMessage(request, 200, F("Update successful!"), F("Rebooting..."), 131);
doReboot = true;
}
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
if (!correctPIN || otaLock) return;
if(!index){
DEBUG_PRINTLN(F("OTA Update Start"));
@@ -406,7 +406,7 @@ void initServer()
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
}
if(!Update.hasError()) Update.write(data, len);
if(final){
if(isFinal){
if(Update.end(true)){
DEBUG_PRINTLN(F("Update Success"));
} else {

View File

@@ -26,7 +26,7 @@ void XML_response(Print& dest)
);
}
static void extractPin(Print& settingsScript, JsonObject &obj, const char *key) {
static void extractPin(Print& settingsScript, const JsonObject &obj, const char *key) {
if (obj[key].is<JsonArray>()) {
JsonArray pins = obj[key].as<JsonArray>();
for (JsonVariant pv : pins) {
@@ -38,7 +38,7 @@ static void extractPin(Print& settingsScript, JsonObject &obj, const char *key)
}
// print used pins by scanning JsonObject (1 level deep)
static void fillUMPins(Print& settingsScript, JsonObject &mods)
static void fillUMPins(Print& settingsScript, const JsonObject &mods)
{
for (JsonPair kv : mods) {
// kv.key() is usermod name or subobject key
@@ -83,7 +83,7 @@ void appendGPIOinfo(Print& settingsScript) {
// usermod pin reservations will become unnecessary when settings pages will read cfg.json directly
if (requestJSONBufferLock(6)) {
// if we can't allocate JSON buffer ignore usermod pins
JsonObject mods = pDoc->createNestedObject(F("um"));
JsonObject mods = pDoc->createNestedObject("um");
UsermodManager::addToConfig(mods);
if (!mods.isNull()) fillUMPins(settingsScript, mods);
releaseJSONBufferLock();
@@ -91,35 +91,42 @@ void appendGPIOinfo(Print& settingsScript) {
settingsScript.print(F("];"));
// add reserved (unusable) pins
bool firstPin = true;
settingsScript.print(F("d.rsvd=["));
for (unsigned i = 0; i < WLED_NUM_PINS; i++) {
if (!PinManager::isPinOk(i, false)) { // include readonly pins
settingsScript.print(i); settingsScript.print(",");
if (!firstPin) settingsScript.print(',');
settingsScript.print(i);
firstPin = false;
}
}
#ifdef WLED_ENABLE_DMX
settingsScript.print(F("2,")); // DMX hardcoded pin
if (!firstPin) settingsScript.print(',');
settingsScript.print(2); // DMX hardcoded pin
firstPin = false;
#endif
#if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST)
settingsScript.printf_P(PSTR(",%d"),hardwareTX); // debug output (TX) pin
if (!firstPin) settingsScript.print(',');
settingsScript.print(hardwareTX); // debug output (TX) pin
firstPin = false;
#endif
//Note: Using pin 3 (RX) disables Adalight / Serial JSON
#ifdef WLED_USE_ETHERNET
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
for (unsigned p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) { settingsScript.printf(",%d", esp32_nonconfigurable_ethernet_pins[p].pin); }
if (ethernetBoards[ethernetType].eth_power>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_power); }
if (ethernetBoards[ethernetType].eth_mdc>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_mdc); }
if (ethernetBoards[ethernetType].eth_mdio>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_mdio); }
switch (ethernetBoards[ethernetType].eth_clk_mode) {
if (!firstPin) settingsScript.print(',');
for (unsigned p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) { settingsScript.printf("%d,",esp32_nonconfigurable_ethernet_pins[p].pin); }
if (ethernetBoards[ethernetType].eth_power >= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_power); }
if (ethernetBoards[ethernetType].eth_mdc >= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_mdc); }
if (ethernetBoards[ethernetType].eth_mdio >= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_mdio); }
switch (ethernetBoards[ethernetType].eth_clk_mode) {
case ETH_CLOCK_GPIO0_IN:
case ETH_CLOCK_GPIO0_OUT:
settingsScript.print(F("0"));
settingsScript.print(0);
break;
case ETH_CLOCK_GPIO16_OUT:
settingsScript.print(F("16"));
settingsScript.print(16);
break;
case ETH_CLOCK_GPIO17_OUT:
settingsScript.print(F("17"));
settingsScript.print(17);
break;
}
}
@@ -128,11 +135,11 @@ void appendGPIOinfo(Print& settingsScript) {
// add info for read-only GPIO
settingsScript.print(F("d.ro_gpio=["));
bool firstPin = true;
firstPin = true;
for (unsigned i = 0; i < WLED_NUM_PINS; i++) {
if (PinManager::isReadOnlyPin(i)) {
// No comma before the first pin
if (!firstPin) settingsScript.print(F(","));
if (!firstPin) settingsScript.print(',');
settingsScript.print(i);
firstPin = false;
}
@@ -140,9 +147,7 @@ void appendGPIOinfo(Print& settingsScript) {
settingsScript.print(F("];"));
// add info about max. # of pins
settingsScript.print(F("d.max_gpio="));
settingsScript.print(WLED_NUM_PINS);
settingsScript.print(F(";"));
settingsScript.printf_P(PSTR("d.max_gpio=%d;"),WLED_NUM_PINS);
}
//get values for settings form in javascript
@@ -152,6 +157,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
DEBUG_PRINTF_P(PSTR("settings resp %u\n"), (unsigned)subPage);
if (subPage <0 || subPage >10) return;
char nS[32];
if (subPage == SUBPAGE_MENU)
{
@@ -259,11 +265,9 @@ void getSettingsJS(byte subPage, Print& settingsScript)
if (subPage == SUBPAGE_LEDS)
{
char nS[32];
appendGPIOinfo(settingsScript);
settingsScript.print(F("d.ledTypes=")); settingsScript.print(BusManager::getLEDTypesJSONString().c_str()); settingsScript.print(";");
settingsScript.printf_P(PSTR("d.ledTypes=%s;"), BusManager::getLEDTypesJSONString().c_str());
// set limits
settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"),
@@ -399,7 +403,6 @@ void getSettingsJS(byte subPage, Print& settingsScript)
if (subPage == SUBPAGE_SYNC)
{
[[maybe_unused]] char nS[32];
printSetFormValue(settingsScript,PSTR("UP"),udpPort);
printSetFormValue(settingsScript,PSTR("U2"),udpPort2);
#ifndef WLED_DISABLE_ESPNOW
@@ -433,6 +436,18 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormCheckbox(settingsScript,PSTR("ES"),e131SkipOutOfSequence);
printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast);
printSetFormValue(settingsScript,PSTR("EU"),e131Universe);
#ifdef WLED_ENABLE_DMX
settingsScript.print(SET_F("hideNoDMX();")); // hide "not compiled in" message
#endif
#ifndef WLED_ENABLE_DMX_INPUT
settingsScript.print(SET_F("hideDMXInput();")); // hide "dmx input" settings
#else
settingsScript.print(SET_F("hideNoDMXInput();")); //hide "not compiled in" message
printSetFormValue(settingsScript,SET_F("IDMT"),dmxInputTransmitPin);
printSetFormValue(settingsScript,SET_F("IDMR"),dmxInputReceivePin);
printSetFormValue(settingsScript,SET_F("IDME"),dmxInputEnablePin);
printSetFormValue(settingsScript,SET_F("IDMP"),dmxInputPort);
#endif
printSetFormValue(settingsScript,PSTR("DA"),DMXAddress);
printSetFormValue(settingsScript,PSTR("XX"),DMXSegmentSpacing);
printSetFormValue(settingsScript,PSTR("PY"),e131Priority);
@@ -465,7 +480,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic);
printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt);
printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg);
settingsScript.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"),
settingsScript.printf_P(PSTR("d.Sf.MD.maxLength=%d;d.Sf.MG.maxLength=%d;d.Sf.MS.maxLength=%d;"),
MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN);
#else
settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings
@@ -637,7 +652,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
#if defined(ARDUINO_ARCH_ESP32)
ESP.getChipModel(),
#else
F("esp8266"),
"esp8266",
#endif
VERSION);
@@ -648,8 +663,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
{
printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix);
#ifndef WLED_DISABLE_2D
settingsScript.printf_P(PSTR("maxPanels=%d;"),WLED_MAX_PANELS);
settingsScript.print(F("resetPanels();"));
settingsScript.printf_P(PSTR("maxPanels=%d;resetPanels();"),WLED_MAX_PANELS);
if (strip.isMatrix) {
if(strip.panels>0){
printSetFormValue(settingsScript,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience
@@ -658,12 +672,9 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("MPC"),strip.panels);
// panels
for (unsigned i=0; i<strip.panels; i++) {
char n[5];
settingsScript.print(F("addPanel("));
settingsScript.print(itoa(i,n,10));
settingsScript.print(F(");"));
settingsScript.printf_P(PSTR("addPanel(%d);"), i);
char pO[8] = { '\0' };
snprintf_P(pO, 7, PSTR("P%d"), i); // MAX_PANELS is 64 so pO will always only be 4 characters or less
snprintf_P(pO, 7, PSTR("P%d"), i); // WLED_MAX_PANELS is 18 so pO will always only be 4 characters or less
pO[7] = '\0';
unsigned l = strlen(pO);
// create P0B, P1B, ..., P63B, etc for other PxxX