Compare commits

...

39 Commits

Author SHA1 Message Date
cschwinne
9e6866c160 0.13.0-b6 2021-12-08 01:22:48 +01:00
cschwinne
7101ad81c4 No auto white for RGB DDP bus 2021-12-07 13:37:28 +01:00
cschwinne
8643263227 Add two new Fairy FX modes 2021-12-07 11:03:41 +01:00
Christian Schwinne
66bad2b6f8 Single json buffer (#2336)
* Single/static JSON buffer for all requests.

* Missing json.cpp changes.

* Async fix.

* Added conditional compile (WLED_USE_DYNAMIC_JSON).

* Advanced locking with time-out.

* Missing releaseJSONBufferLock() on error response.

* Fix for config saving.

* Fixes and optimisations.
Dadded debugging information.

* Fix for ledmaps.

* No unsolicited serial sending if GPIO1 allocated

* Stray semicolons

* Fix JSON ledmap

Co-authored-by: Blaz Kristan <blaz@kristan-sp.si>
2021-12-04 01:05:01 +01:00
cschwinne
46ec504743 Various fixes
Fixed ESP32 crash on Colortwinkles brightness change
Fixed setting picker to black resetting hue and saturation
Fixed auto white mode not saved to config
2021-12-03 20:36:37 +01:00
cschwinne
cadda12371 Fixed spacing LEDs not blanked if offset is changed 2021-12-02 00:52:36 +01:00
cschwinne
a643b56555 Fixed no color updated on full JSON state request 2021-12-01 23:03:30 +01:00
cschwinne
f7404085de Unload playlist on PL= 2021-12-01 00:20:33 +01:00
cschwinne
33036e7599 Fix sliders on page load if black is set 2021-12-01 00:16:43 +01:00
Christian Schwinne
f6e5b67f0d Merge pull request #2285 from Aircoookie/CCT-support
CCT (color white balance support)
2021-11-30 23:41:13 +01:00
Jeff Cooper
9547ac353d Save and load the e131 Proxy Universe setting in the DMX menu (#2365) 2021-11-30 23:10:23 +01:00
cschwinne
48339b19d4 Status LED support in Bus manager 2021-11-30 22:52:17 +01:00
cschwinne
11c7ffad4e Alexa and UDP sync CCT support 2021-11-28 04:01:58 +01:00
cschwinne
1973424e05 Remove JSON ledmap
(to be implemented in #2336 )
2021-11-28 01:27:51 +01:00
Christian Schwinne
16d97d3c63 Merge branch 'master' into CCT-support 2021-11-28 01:21:57 +01:00
cschwinne
3e6728fedb Only do auto white calc for busses with white channel
Revert auto white to global setting
Rounded /settings buttons by blazoncek
Removed obsolete script from /settings
2021-11-28 01:21:17 +01:00
cschwinne
3e9aea072d CCT blending setting 2021-11-27 16:49:42 +01:00
Blaz Kristan
9f3e66fff0 Cleaning up indentations (sorry but my OCD is strong). 2021-11-27 11:50:18 +01:00
cschwinne
624993fc89 CCT transitions 2021-11-27 01:33:48 +01:00
cschwinne
ba8a00764a cctFromRgb option 2021-11-26 20:18:38 +01:00
cschwinne
3dec4a6651 UI slider and CCT adjustments 2021-11-26 02:06:05 +01:00
cschwinne
02fb2550d0 Shorter link color 2021-11-24 12:38:54 +01:00
cschwinne
37bd525638 Improve link contrast 2021-11-24 11:04:50 +01:00
cschwinne
ea0f37f5b9 CCT bus manager logic simplification
CCT from RGB if none set (-1)
2021-11-24 11:02:25 +01:00
Blaz Kristan
97b3c3db7b Incrementing & random effects, palettes via JSON. 2021-11-23 20:05:51 +01:00
cschwinne
b97b6dc144 Remove F macro for "ps" 2021-11-23 13:17:33 +01:00
cschwinne
c8d5218c65 Updated outdated wiki links in readme 2021-11-22 22:23:51 +01:00
cschwinne
80a657965e Fixed preset cycle not working from preset called by UI 2021-11-22 21:41:04 +01:00
cschwinne
b3324d22f5 allowCCT performance improvement 2021-11-21 23:46:44 +01:00
cschwinne
31b7cdff9b Change effect names to be more consistent 2021-11-19 12:34:14 +01:00
Christian Schwinne
0465298507 Merge branch 'master' into CCT-support 2021-11-18 16:50:24 +01:00
cschwinne
d31e4c7815 Added getPinOwner
Only disable builtin LED if bus to avoid breaking other things on the pin
2021-11-17 11:13:07 +01:00
Blaz Kristan
eb92c0bbf5 Fix for ACCURATE auto-white mode. 2021-11-11 17:34:38 +01:00
Blaz Kristan
d590e01a58 Merge branch 'master' into CCT-support 2021-11-05 21:27:52 +01:00
Blaž Kristan
f55f803531 Updated aut-white calculation. 2021-10-27 14:02:48 +02:00
Blaz Kristan
8ca298b299 Removed legacy auto-white calculation.
Introduced color mangling macros.
Minor optimizations/fixes.
2021-10-26 19:17:42 +02:00
Blaz Kristan
090e29effd Moved auto white calculation to bus manager.
Other minor fixes.
2021-10-25 20:15:42 +02:00
Blaz Kristan
0d77027f60 Bugfix for white value. 2021-10-22 07:21:47 +02:00
Blaz Kristan
39b7b3ad53 CCT (color white balance support) 2021-10-20 20:29:13 +02:00
47 changed files with 3626 additions and 2918 deletions

View File

@@ -2,7 +2,41 @@
### Builds after release 0.12.0
#### Build 2111160
#### Build 2112080
- Version bump to 0.13.0-b6 "Toki"
- Added "ESP02" (ESP8266 with 2M of flash) to PIO/release binaries
#### Build 2112070
- Added new effect "Fairy", replacing "Police All"
- Added new effect "Fairytwinkle", replacing "Two Areas"
- Static single JSON buffer (performance and stability improvement) (PR #2336)
#### Build 2112030
- Fixed ESP32 crash on Colortwinkles brightness change
- Fixed setting picker to black resetting hue and saturation
- Fixed auto white mode not saved to config
#### Build 2111300
- Added CCT and white balance correction support (PR #2285)
- Unified UI slider style
- Added LED settings config template upload
#### Build 2111220
- Fixed preset cycle not working from preset called by UI
- Reintroduced permanent min. and max. cycle bounds
#### Build 2111190
- Changed default ESP32 LED pin from 16 to 2
- Renamed "Running 2" to "Chase 2"
- Renamed "Tri Chase" to "Chase 3"
#### Build 2111170
- Version bump to 0.13.0-b5 "Toki"
- Improv Serial support (PR #2334)
@@ -388,6 +422,7 @@
- Added support for WESP32 ethernet board (PR #1764)
- Added Caching for main UI (PR #1704)
- Added Tetrix mode (PR #1729)
- Removed Merry Christmas mode (use "Chase 2" - called Running 2 before 0.13.0)
- Added memory check on Bus creation
#### Build 2102050

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "wled",
"version": "0.13.0-b5",
"version": "0.13.0-b6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "wled",
"version": "0.13.0-b5",
"version": "0.13.0-b6",
"description": "Tools for WLED project",
"main": "tools/cdata.js",
"directories": {

View File

@@ -12,7 +12,7 @@
; default_envs = travis_esp8266, travis_esp32
# Release binaries
default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth
# Build everything
; default_envs = esp32dev, esp8285_4CH_MagicHome, esp8285_4CH_H801, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_5CH_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
@@ -20,6 +20,7 @@ default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth
# Single binaries (uncomment your board)
; default_envs = elekstube_ips
; default_envs = nodemcuv2
; default_envs = esp8266_2m
; default_envs = esp01_1m_full
; default_envs = esp07
; default_envs = d1_mini
@@ -241,6 +242,13 @@ build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266
lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full]
board = esp01_1m
platform = ${common.platform_wled_default}
@@ -434,6 +442,13 @@ board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
lib_deps = ${esp8266.lib_deps}
[env:athom7w]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D WLED_MAX_CCT_BLEND=0 -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
# travis test board configurations
# ------------------------------------------------------------------------------

View File

@@ -51,7 +51,7 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control
See the [documentation on our official site](https://kno.wled.ge)!
[On this page](https://github.com/Aircoookie/WLED/wiki/Learning-the-ropes) you can find excellent tutorials made by the community and helpful tools to help you get your new lamp up and running!
[On this page](https://kno.wled.ge/basics/tutorials/) you can find excellent tutorials made by the community and helpful tools to help you get your new lamp up and running!
## 🖼️ Images
<img src="/images/macbook-pro-space-gray-on-the-wooden-table.jpg" width="50%"><img src="/images/walking-with-iphone-x.jpg" width="50%">
@@ -82,7 +82,7 @@ Any | 5v 3-pin ARGB for PC | Any PC RGB device that supports the 5v 3-pin ARGB m
## ✌️ Other
Licensed under the MIT license
Credits [here](https://github.com/Aircoookie/WLED/wiki/Contributors-&-About)!
Credits [here](https://kno.wled.ge/about/contributors/)!
Uses Linearicons by Perxis!

View File

@@ -1150,10 +1150,10 @@ uint16_t WS2812FX::mode_fire_flicker(void) {
uint32_t it = now / cycleTime;
if (SEGENV.step == it) return FRAMETIME;
byte w = (SEGCOLOR(0) >> 24) & 0xFF;
byte r = (SEGCOLOR(0) >> 16) & 0xFF;
byte g = (SEGCOLOR(0) >> 8) & 0xFF;
byte b = (SEGCOLOR(0) & 0xFF);
byte w = (SEGCOLOR(0) >> 24);
byte r = (SEGCOLOR(0) >> 16);
byte g = (SEGCOLOR(0) >> 8);
byte b = (SEGCOLOR(0) );
byte lum = (SEGMENT.palette == 0) ? MAX(w, MAX(r, MAX(g, b))) : 255;
lum /= (((256-SEGMENT.intensity)/16)+1);
for(uint16_t i = 0; i < SEGLEN; i++) {
@@ -1216,12 +1216,13 @@ uint16_t WS2812FX::mode_loading(void) {
//American Police Light with all LEDs Red and Blue
uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, uint16_t width)
uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2)
{
uint16_t delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster
uint32_t it = now / map(SEGMENT.speed, 0, 255, delay<<4, delay);
uint16_t offset = it % SEGLEN;
uint16_t width = ((SEGLEN*(SEGMENT.intensity+1))>>9); //max width is half the strip
if (!width) width = 1;
for (uint16_t i = 0; i < width; i++) {
uint16_t indexR = (offset + i) % SEGLEN;
@@ -1233,26 +1234,11 @@ uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, uint16_t width)
}
//American Police Light with all LEDs Red and Blue
uint16_t WS2812FX::mode_police_all()
{
return police_base(RED, BLUE, (SEGLEN>>1));
}
//Police Lights Red and Blue
uint16_t WS2812FX::mode_police()
{
fill(SEGCOLOR(1));
return police_base(RED, BLUE, ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip
}
//Police All with custom colors
uint16_t WS2812FX::mode_two_areas()
{
fill(SEGCOLOR(2));
return police_base(SEGCOLOR(0), SEGCOLOR(1), ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip
return police_base(RED, BLUE);
}
@@ -1262,7 +1248,142 @@ uint16_t WS2812FX::mode_two_dots()
fill(SEGCOLOR(2));
uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1);
return police_base(SEGCOLOR(0), color2, ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip
return police_base(SEGCOLOR(0), color2);
}
/*
* Fairy, inspired by https://www.youtube.com/watch?v=zeOw5MZWq24
*/
//4 bytes
typedef struct Flasher {
uint16_t stateStart;
uint8_t stateDur;
bool stateOn;
} flasher;
#define FLASHERS_PER_ZONE 6
#define MAX_SHIMMER 92
uint16_t WS2812FX::mode_fairy() {
//set every pixel to a 'random' color from palette (using seed so it doesn't change between frames)
uint16_t PRNG16 = 5100 + _segment_index;
for (uint16_t i = 0; i < SEGLEN; i++) {
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0));
}
//amount of flasher pixels depending on intensity (0: none, 255: every LED)
if (SEGMENT.intensity == 0) return FRAMETIME;
uint8_t flasherDistance = ((255 - SEGMENT.intensity) / 28) +1; //1-10
uint16_t numFlashers = (SEGLEN / flasherDistance) +1;
uint16_t dataSize = sizeof(flasher) * numFlashers;
if (!SEGENV.allocateData(dataSize)) return FRAMETIME; //allocation failed
Flasher* flashers = reinterpret_cast<Flasher*>(SEGENV.data);
uint16_t now16 = now & 0xFFFF;
//Up to 11 flashers in one brightness zone, afterwards a new zone for every 6 flashers
uint16_t zones = numFlashers/FLASHERS_PER_ZONE;
if (!zones) zones = 1;
uint8_t flashersInZone = numFlashers/zones;
uint8_t flasherBri[FLASHERS_PER_ZONE*2 -1];
for (uint16_t z = 0; z < zones; z++) {
uint16_t flasherBriSum = 0;
uint16_t firstFlasher = z*flashersInZone;
if (z == zones-1) flashersInZone = numFlashers-(flashersInZone*(zones-1));
for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) {
uint16_t stateTime = now16 - flashers[f].stateStart;
//random on/off time reached, switch state
if (stateTime > flashers[f].stateDur * 10) {
flashers[f].stateOn = !flashers[f].stateOn;
if (flashers[f].stateOn) {
flashers[f].stateDur = 12 + random8(12 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms
} else {
flashers[f].stateDur = 20 + random8(6 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms
}
//flashers[f].stateDur = 51 + random8(2 + ((255 - SEGMENT.speed) >> 1));
flashers[f].stateStart = now16;
if (stateTime < 255) {
flashers[f].stateStart -= 255 -stateTime; //start early to get correct bri
flashers[f].stateDur += 26 - stateTime/10;
stateTime = 255 - stateTime;
} else {
stateTime = 0;
}
}
if (stateTime > 255) stateTime = 255; //for flasher brightness calculation, fades in first 255 ms of state
//flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? 255-gamma8((510 - stateTime) >> 1) : gamma8((510 - stateTime) >> 1);
flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? stateTime : 255 - (stateTime >> 0);
flasherBriSum += flasherBri[f - firstFlasher];
}
//dim factor, to create "shimmer" as other pixels get less voltage if a lot of flashers are on
uint8_t avgFlasherBri = flasherBriSum / flashersInZone;
uint8_t globalPeakBri = 255 - ((avgFlasherBri * MAX_SHIMMER) >> 8); //183-255, suitable for 1/5th of LEDs flashers
for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) {
uint8_t bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255;
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
uint16_t flasherPos = f*flasherDistance;
setPixelColor(flasherPos, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), bri));
for (uint16_t i = flasherPos+1; i < flasherPos+flasherDistance && i < SEGLEN; i++) {
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0, globalPeakBri));
}
}
}
return FRAMETIME;
}
/*
* Fairytwinkle. Like Colortwinkle, but starting from all lit and not relying on getPixelColor
* Warning: Uses 4 bytes of segment data per pixel
*/
uint16_t WS2812FX::mode_fairytwinkle() {
uint16_t dataSize = sizeof(flasher) * SEGLEN;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
Flasher* flashers = reinterpret_cast<Flasher*>(SEGENV.data);
uint16_t now16 = now & 0xFFFF;
uint16_t PRNG16 = 5100 + _segment_index;
uint16_t riseFallTime = 400 + (255-SEGMENT.speed)*3;
uint16_t maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1);
for (uint16_t f = 0; f < SEGLEN; f++) {
uint16_t stateTime = now16 - flashers[f].stateStart;
//random on/off time reached, switch state
if (stateTime > flashers[f].stateDur * 100) {
flashers[f].stateOn = !flashers[f].stateOn;
bool init = !flashers[f].stateDur;
if (flashers[f].stateOn) {
flashers[f].stateDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +1;
} else {
flashers[f].stateDur = riseFallTime/100 + random8(3 + ((255 - SEGMENT.speed) >> 6)) +1;
}
flashers[f].stateStart = now16;
stateTime = 0;
if (init) {
flashers[f].stateStart -= riseFallTime; //start lit
flashers[f].stateDur = riseFallTime/100 + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +5; //fire up a little quicker
stateTime = riseFallTime;
}
}
if (flashers[f].stateOn && flashers[f].stateDur > maxDur) flashers[f].stateDur = maxDur; //react more quickly on intensity change
if (stateTime > riseFallTime) stateTime = riseFallTime; //for flasher brightness calculation, fades in first 255 ms of state
uint8_t fadeprog = 255 - ((stateTime * 255) / riseFallTime);
uint8_t flasherBri = (flashers[f].stateOn) ? 255-gamma8(fadeprog) : gamma8(fadeprog);
uint16_t lastR = PRNG16;
uint16_t diff = 0;
while (diff < 0x4000) { //make sure colors of two adjacent LEDs differ enough
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
diff = (PRNG16 > lastR) ? PRNG16 - lastR : lastR - PRNG16;
}
setPixelColor(f, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), flasherBri));
}
return FRAMETIME;
}
@@ -2117,7 +2238,7 @@ typedef struct Ripple {
#endif
uint16_t WS2812FX::ripple_base(bool rainbow)
{
uint16_t maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 18 segment ESP8266
uint16_t maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266
uint16_t dataSize = sizeof(ripple) * maxRipples;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
@@ -2900,7 +3021,6 @@ uint16_t WS2812FX::mode_starburst(void) {
return FRAMETIME;
}
#undef STARBURST_MAX_FRAG
#undef STARBURST_MAX_STARS
/*
* Exploding fireworks effect
@@ -3645,7 +3765,7 @@ typedef struct Spotlight {
*/
uint16_t WS2812FX::mode_dancing_shadows(void)
{
uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 18 segment ESP8266
uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 16 segment ESP8266
bool initialize = SEGENV.aux0 != numSpotlights;
SEGENV.aux0 = numSpotlights;
@@ -3784,7 +3904,7 @@ uint16_t WS2812FX::mode_washing_machine(void) {
Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e
*/
uint16_t WS2812FX::mode_blends(void) {
uint16_t dataSize = sizeof(uint32_t) * SEGLEN; // max segment length of 56 pixels on 18 segment ESP8266
uint16_t dataSize = sizeof(uint32_t) * SEGLEN; // max segment length of 56 pixels on 16 segment ESP8266
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data);
uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128);
@@ -4037,7 +4157,7 @@ uint16_t WS2812FX::mode_aurora(void) {
SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT);
SEGENV.aux0 = SEGMENT.intensity;
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 18 segment ESP8266
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 16 segment ESP8266
return mode_static(); //allocation failed
}

View File

@@ -161,16 +161,16 @@
#define FX_MODE_COMET 41
#define FX_MODE_FIREWORKS 42
#define FX_MODE_RAIN 43
#define FX_MODE_TETRIX 44
#define FX_MODE_TETRIX 44 //was Merry Christmas prior to 0.12.0 (use "Chase 2" with Red/Green)
#define FX_MODE_FIRE_FLICKER 45
#define FX_MODE_GRADIENT 46
#define FX_MODE_LOADING 47
#define FX_MODE_POLICE 48
#define FX_MODE_POLICE_ALL 49
#define FX_MODE_POLICE 48 // candidate for removal (after below three)
#define FX_MODE_FAIRY 49 //was Police All prior to 0.13.0-b6 (use "Two Dots" with Red/Blue and full intensity)
#define FX_MODE_TWO_DOTS 50
#define FX_MODE_TWO_AREAS 51
#define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity)
#define FX_MODE_RUNNING_DUAL 52
#define FX_MODE_HALLOWEEN 53
#define FX_MODE_HALLOWEEN 53 // candidate for removal
#define FX_MODE_TRICOLOR_CHASE 54
#define FX_MODE_TRICOLOR_WIPE 55
#define FX_MODE_TRICOLOR_FADE 56
@@ -231,7 +231,7 @@
#define FX_MODE_CHUNCHUN 111
#define FX_MODE_DANCING_SHADOWS 112
#define FX_MODE_WASHING_MACHINE 113
#define FX_MODE_CANDY_CANE 114
#define FX_MODE_CANDY_CANE 114 // candidate for removal
#define FX_MODE_BLENDS 115
#define FX_MODE_TV_SIMULATOR 116
#define FX_MODE_DYNAMIC_SMOOTH 117
@@ -247,35 +247,44 @@ class WS2812FX {
// segment parameters
public:
typedef struct Segment { // 29 (32 in memory?) bytes
typedef struct Segment { // 30 (32 in memory) bytes
uint16_t start;
uint16_t stop; //segment invalid if stop == 0
uint16_t offset;
uint8_t speed;
uint8_t intensity;
uint8_t palette;
uint8_t mode;
uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected
uint8_t grouping, spacing;
uint8_t opacity;
uint8_t speed;
uint8_t intensity;
uint8_t palette;
uint8_t mode;
uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected
uint8_t grouping, spacing;
uint8_t opacity;
uint32_t colors[NUM_COLORS];
uint8_t cct; //0==1900K, 255==10091K
char *name;
bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed
if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false;
if (c == colors[slot]) return false;
ColorTransition::startTransition(opacity, colors[slot], instance->_transitionDur, segn, slot);
uint8_t b = (slot == 1) ? cct : opacity;
ColorTransition::startTransition(b, colors[slot], instance->_transitionDur, segn, slot);
colors[slot] = c; return true;
}
void setCCT(uint16_t k, uint8_t segn) {
if (segn >= MAX_NUM_SEGMENTS) return;
if (k > 255) { //kelvin value, convert to 0-255
if (k < 1900) k = 1900;
if (k > 10091) k = 10091;
k = (k - 1900) >> 5;
}
if (cct == k) return;
ColorTransition::startTransition(cct, colors[1], instance->_transitionDur, segn, 1);
cct = k;
}
void setOpacity(uint8_t o, uint8_t segn) {
if (segn >= MAX_NUM_SEGMENTS) return;
if (opacity == o) return;
ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0);
opacity = o;
}
/*uint8_t actualOpacity() { //respects On/Off state
if (!getOption(SEG_OPTION_ON)) return 0;
return opacity;
}*/
void setOption(uint8_t n, bool val, uint8_t segn = 255)
{
bool prevOn = false;
@@ -445,7 +454,7 @@ class WS2812FX {
if (t.segment == s) //this is an active transition on the same segment+color
{
bool wasTurningOff = (oldBri == 0);
t.briOld = t.currentBri(wasTurningOff);
t.briOld = t.currentBri(wasTurningOff, slot);
t.colorOld = t.currentColor(oldCol);
} else {
t.briOld = oldBri;
@@ -477,11 +486,15 @@ class WS2812FX {
uint32_t currentColor(uint32_t colorNew) {
return instance->color_blend(colorOld, colorNew, progress(true), true);
}
uint8_t currentBri(bool turningOff = false) {
uint8_t currentBri(bool turningOff = false, uint8_t slot = 0) {
uint8_t segn = segment & 0x3F;
if (segn >= MAX_NUM_SEGMENTS) return 0;
uint8_t briNew = instance->_segments[segn].opacity;
if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0;
if (slot == 0) {
if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0;
} else { //transition slot 1 brightness for CCT transition
briNew = instance->_segments[segn].cct;
}
uint32_t prog = progress() + 1;
return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16;
}
@@ -537,9 +550,9 @@ class WS2812FX {
_mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient;
_mode[FX_MODE_LOADING] = &WS2812FX::mode_loading;
_mode[FX_MODE_POLICE] = &WS2812FX::mode_police;
_mode[FX_MODE_POLICE_ALL] = &WS2812FX::mode_police_all;
_mode[FX_MODE_FAIRY] = &WS2812FX::mode_fairy;
_mode[FX_MODE_TWO_DOTS] = &WS2812FX::mode_two_dots;
_mode[FX_MODE_TWO_AREAS] = &WS2812FX::mode_two_areas;
_mode[FX_MODE_FAIRYTWINKLE] = &WS2812FX::mode_fairytwinkle;
_mode[FX_MODE_RUNNING_DUAL] = &WS2812FX::mode_running_dual;
_mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween;
_mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase;
@@ -634,7 +647,7 @@ class WS2812FX {
setTransitionMode(bool t),
calcGammaTable(float),
trigger(void),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0, uint16_t offset = UINT16_MAX),
resetSegments(),
makeAutoSegments(),
fixInvalidSegments(),
@@ -652,15 +665,16 @@ class WS2812FX {
applyToAllSelected = true,
setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p),
checkSegmentAlignment(void),
hasCCTBus(void),
// return true if the strip is being sent pixel updates
isUpdating(void);
uint8_t
mainSegment = 0,
rgbwMode = RGBW_MODE_DUAL,
paletteFade = 0,
paletteBlend = 0,
milliampsPerLed = 55,
cctBlending = 0,
getBrightness(void),
getMode(void),
getSpeed(void),
@@ -759,9 +773,9 @@ class WS2812FX {
mode_gradient(void),
mode_loading(void),
mode_police(void),
mode_police_all(void),
mode_fairy(void),
mode_two_dots(void),
mode_two_areas(void),
mode_fairytwinkle(void),
mode_running_dual(void),
mode_bicolor_chase(void),
mode_tricolor_chase(void),
@@ -864,7 +878,7 @@ class WS2812FX {
chase(uint32_t, uint32_t, uint32_t, bool),
gradient_base(bool),
ripple_base(bool),
police_base(uint32_t, uint32_t, uint16_t),
police_base(uint32_t, uint32_t),
running(uint32_t, uint32_t, bool theatre=false),
tricolor_chase(uint32_t, uint32_t),
twinklefox_base(bool),
@@ -912,9 +926,9 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Solid","Blink","Breathe","Wipe","Wipe Random","Random Colors","Sweep","Dynamic","Colorloop","Rainbow",
"Scan","Scan Dual","Fade","Theater","Theater Rainbow","Running","Saw","Twinkle","Dissolve","Dissolve Rnd",
"Sparkle","Sparkle Dark","Sparkle+","Strobe","Strobe Rainbow","Strobe Mega","Blink Rainbow","Android","Chase","Chase Random",
"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Aurora","Stream",
"Scanner","Lighthouse","Fireworks","Rain","Tetrix","Fire Flicker","Gradient","Loading","Police","Police All",
"Two Dots","Two Areas","Running Dual","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet",
"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Chase 2","Aurora","Stream",
"Scanner","Lighthouse","Fireworks","Rain","Tetrix","Fire Flicker","Gradient","Loading","Police","Fairy",
"Two Dots","Fairytwinkle","Running Dual","Halloween","Chase 3","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet",
"Scanner Dual","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","Bpm","Fill Noise",
"Noise 1","Noise 2","Noise 3","Noise 4","Colortwinkles","Lake","Meteor","Meteor Smooth","Railway","Ripple",
"Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst",

View File

@@ -86,8 +86,6 @@ void WS2812FX::finalizeInit(void)
busses.add(defCfg);
}
}
deserializeMap();
_length = 0;
for (uint8_t i=0; i<busses.getNumBusses(); i++) {
@@ -139,13 +137,16 @@ void WS2812FX::service() {
if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen
_virtualSegmentLength = SEGMENT.virtualLength();
_bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2];
uint8_t _cct_t = SEGMENT.cct;
if (!IS_SEGMENT_ON) _bri_t = 0;
for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) {
if ((transitions[t].segment & 0x3F) != i) continue;
uint8_t slot = transitions[t].segment >> 6;
if (slot == 0) _bri_t = transitions[t].currentBri();
if (slot == 1) _cct_t = transitions[t].currentBri(false, 1);
_colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]);
}
if (!cctFromRgb || correctWB) busses.setSegmentCCT(_cct_t, correctWB);
for (uint8_t c = 0; c < 3; c++) _colors_t[c] = gamma32(_colors_t[c]);
handle_palette();
delay = (this->*_mode[SEGMENT.mode])(); //effect function
@@ -156,6 +157,7 @@ void WS2812FX::service() {
}
}
_virtualSegmentLength = 0;
busses.setSegmentCCT(-1);
if(doShow) {
yield();
show();
@@ -164,11 +166,7 @@ void WS2812FX::service() {
}
void WS2812FX::setPixelColor(uint16_t n, uint32_t c) {
uint8_t w = (c >> 24);
uint8_t r = (c >> 16);
uint8_t g = (c >> 8);
uint8_t b = c ;
setPixelColor(n, r, g, b, w);
setPixelColor(n, R(c), G(c), B(c), W(c));
}
//used to map from segment index to physical pixel, taking into account grouping, offsets, reverse and mirroring
@@ -191,21 +189,10 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) {
void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
{
//auto calculate white channel value if enabled
if (isRgbw) {
if (rgbwMode == RGBW_MODE_AUTO_BRIGHTER || (w == 0 && (rgbwMode == RGBW_MODE_DUAL || rgbwMode == RGBW_MODE_LEGACY)))
{
//white value is set to lowest RGB channel
//thank you to @Def3nder!
w = r < g ? (r < b ? r : b) : (g < b ? g : b);
} else if (rgbwMode == RGBW_MODE_AUTO_ACCURATE && w == 0)
{
w = r < g ? (r < b ? r : b) : (g < b ? g : b);
r -= w; g -= w; b -= w;
}
}
if (SEGLEN) {//from segment
uint16_t realIndex = realPixelIndex(i);
uint16_t len = SEGMENT.length();
//color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments)
if (_bri_t < 255) {
r = scale8(r, _bri_t);
@@ -213,12 +200,9 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
b = scale8(b, _bri_t);
w = scale8(w, _bri_t);
}
uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
uint32_t col = RGBW32(r, g, b, w);
/* Set all the pixels in the group */
uint16_t realIndex = realPixelIndex(i);
uint16_t len = SEGMENT.length();
for (uint16_t j = 0; j < SEGMENT.grouping; j++) {
uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j);
if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) {
@@ -241,8 +225,7 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
}
} else { //live data, etc.
if (i < customMappingSize) i = customMappingTable[i];
uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
busses.setPixelColor(i, col);
busses.setPixelColor(i, RGBW32(r, g, b, w));
}
}
@@ -295,7 +278,7 @@ void WS2812FX::estimateCurrentAndLimitBri() {
uint32_t busPowerSum = 0;
for (uint16_t i = 0; i < len; i++) { //sum up the usage of each LED
uint32_t c = bus->getPixelColor(i);
byte r = c >> 16, g = c >> 8, b = c, w = c >> 24;
byte r = R(c), g = G(c), b = B(c), w = W(c);
if(useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
busPowerSum += (MAX(MAX(r,g),b)) * 3;
@@ -431,7 +414,7 @@ bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t p) {
}
void WS2812FX::setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
setColor(slot, ((uint32_t)w << 24) |((uint32_t)r << 16) | ((uint32_t)g << 8) | b);
setColor(slot, RGBW32(r, g, b, w));
}
void WS2812FX::setColor(uint8_t slot, uint32_t c) {
@@ -459,14 +442,14 @@ void WS2812FX::setBrightness(uint8_t b) {
if (gammaCorrectBri) b = gamma8(b);
if (_brightness == b) return;
_brightness = b;
_segment_index = 0;
if (_brightness == 0) { //unfreeze all segments on power off
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
_segments[i].setOption(SEG_OPTION_FREEZE, false);
}
}
if (SEGENV.next_time > millis() + 22 && millis() - _lastShow > MIN_SHOW_DELAY) show();//apply brightness change immediately if no refresh soon
unsigned long t = millis();
if (_segment_runtimes[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) show(); //apply brightness change immediately if no refresh soon
}
uint8_t WS2812FX::getMode(void) {
@@ -568,12 +551,28 @@ uint16_t WS2812FX::getLengthPhysical(void) {
return len;
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) {
bool WS2812FX::hasCCTBus(void) {
if (cctFromRgb && !correctWB) return false;
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (bus == nullptr || bus->getLength()==0) break;
switch (bus->getType()) {
case TYPE_ANALOG_5CH:
case TYPE_ANALOG_2CH:
return true;
}
}
return false;
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset) {
if (n >= MAX_NUM_SEGMENTS) return;
Segment& seg = _segments[n];
//return if neither bounds nor grouping have changed
if (seg.start == i1 && seg.stop == i2 && (!grouping || (seg.grouping == grouping && seg.spacing == spacing))) return;
if (seg.start == i1 && seg.stop == i2
&& (!grouping || (seg.grouping == grouping && seg.spacing == spacing))
&& (offset == UINT16_MAX || offset == seg.offset)) return;
if (seg.stop) setRange(seg.start, seg.stop -1, 0); //turn old segment range off
if (i2 <= i1) //disable segment
@@ -603,6 +602,7 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
seg.grouping = grouping;
seg.spacing = spacing;
}
if (offset < UINT16_MAX) seg.offset = offset;
_segment_runtimes[n].reset();
}
@@ -622,6 +622,7 @@ void WS2812FX::resetSegments() {
_segments[0].setOption(SEG_OPTION_SELECTED, 1);
_segments[0].setOption(SEG_OPTION_ON, 1);
_segments[0].opacity = 255;
_segments[0].cct = 127;
for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++)
{
@@ -629,6 +630,7 @@ void WS2812FX::resetSegments() {
_segments[i].grouping = 1;
_segments[i].setOption(SEG_OPTION_ON, 1);
_segments[i].opacity = 255;
_segments[i].cct = 127;
_segments[i].speed = DEFAULT_SPEED;
_segments[i].intensity = DEFAULT_INTENSITY;
_segment_runtimes[i].reset();
@@ -699,14 +701,35 @@ bool WS2812FX::checkSegmentAlignment() {
}
//After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply)
//Note: If called in an interrupt (e.g. JSON API), it must be reset with "setPixelColor(255)",
//otherwise it can lead to a crash on ESP32 because _segment_index is modified while in use by the main thread
#ifdef ARDUINO_ARCH_ESP32
uint8_t _segment_index_prev = 0;
uint16_t _virtualSegmentLength_prev = 0;
bool _ps_set = false;
#endif
void WS2812FX::setPixelSegment(uint8_t n)
{
if (n < MAX_NUM_SEGMENTS) {
#ifdef ARDUINO_ARCH_ESP32
if (!_ps_set) {
_segment_index_prev = _segment_index;
_virtualSegmentLength_prev = _virtualSegmentLength;
_ps_set = true;
}
#endif
_segment_index = n;
_virtualSegmentLength = SEGMENT.length();
_virtualSegmentLength = SEGMENT.virtualLength();
} else {
_segment_index = 0;
_virtualSegmentLength = 0;
_virtualSegmentLength = 0;
#ifdef ARDUINO_ARCH_ESP32
if (_ps_set) {
_segment_index = _segment_index_prev;
_virtualSegmentLength = _virtualSegmentLength_prev;
_ps_set = false;
}
#endif
}
}
@@ -733,13 +756,13 @@ void WS2812FX::setTransition(uint16_t t)
void WS2812FX::setTransitionMode(bool t)
{
unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled
unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled
for (uint16_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
_segment_index = i;
SEGMENT.setOption(SEG_OPTION_TRANSITIONAL, t);
_segments[i].setOption(SEG_OPTION_TRANSITIONAL, t);
if (t && SEGMENT.mode == FX_MODE_STATIC && SEGENV.next_time > waitMax) SEGENV.next_time = waitMax;
if (t && _segments[i].mode == FX_MODE_STATIC && _segment_runtimes[i].next_time > waitMax)
_segment_runtimes[i].next_time = waitMax;
}
}
@@ -752,22 +775,22 @@ uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint16_t blend,
if(blend == blendmax) return color2;
uint8_t shift = b16 ? 16 : 8;
uint32_t w1 = (color1 >> 24) & 0xFF;
uint32_t r1 = (color1 >> 16) & 0xFF;
uint32_t g1 = (color1 >> 8) & 0xFF;
uint32_t b1 = color1 & 0xFF;
uint32_t w1 = W(color1);
uint32_t r1 = R(color1);
uint32_t g1 = G(color1);
uint32_t b1 = B(color1);
uint32_t w2 = (color2 >> 24) & 0xFF;
uint32_t r2 = (color2 >> 16) & 0xFF;
uint32_t g2 = (color2 >> 8) & 0xFF;
uint32_t b2 = color2 & 0xFF;
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 ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3));
return RGBW32(r3, g3, b3, w3);
}
/*
@@ -795,17 +818,17 @@ void WS2812FX::fade_out(uint8_t rate) {
float mappedRate = float(rate) +1.1;
uint32_t color = SEGCOLOR(1); // target color
int w2 = (color >> 24) & 0xff;
int r2 = (color >> 16) & 0xff;
int g2 = (color >> 8) & 0xff;
int b2 = color & 0xff;
int w2 = W(color);
int r2 = R(color);
int g2 = G(color);
int b2 = B(color);
for(uint16_t i = 0; i < SEGLEN; i++) {
color = getPixelColor(i);
int w1 = (color >> 24) & 0xff;
int r1 = (color >> 16) & 0xff;
int g1 = (color >> 8) & 0xff;
int b1 = color & 0xff;
int w1 = W(color);
int r1 = R(color);
int g1 = G(color);
int b1 = B(color);
int wdelta = (w2 - w1) / mappedRate;
int rdelta = (r2 - r1) / mappedRate;
@@ -839,9 +862,9 @@ void WS2812FX::blur(uint8_t blur_amount)
cur += carryover;
if(i > 0) {
uint32_t c = getPixelColor(i-1);
uint8_t r = (c >> 16 & 0xFF);
uint8_t g = (c >> 8 & 0xFF);
uint8_t b = (c & 0xFF);
uint8_t r = R(c);
uint8_t g = G(c);
uint8_t b = B(c);
setPixelColor(i-1, qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue));
}
setPixelColor(i,cur.red, cur.green, cur.blue);
@@ -924,16 +947,16 @@ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) {
uint32_t WS2812FX::crgb_to_col(CRGB fastled)
{
return (((uint32_t)fastled.red << 16) | ((uint32_t)fastled.green << 8) | fastled.blue);
return RGBW32(fastled.red, fastled.green, fastled.blue, 0);
}
CRGB WS2812FX::col_to_crgb(uint32_t color)
{
CRGB fastled_col;
fastled_col.red = (color >> 16 & 0xFF);
fastled_col.green = (color >> 8 & 0xFF);
fastled_col.blue = (color & 0xFF);
fastled_col.red = R(color);
fastled_col.green = G(color);
fastled_col.blue = B(color);
return fastled_col;
}
@@ -1074,7 +1097,7 @@ uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8
}
//load custom mapping table from JSON file
//load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
void WS2812FX::deserializeMap(uint8_t n) {
char fileName[32];
strcpy_P(fileName, PSTR("/ledmap"));
@@ -1092,11 +1115,19 @@ void WS2812FX::deserializeMap(uint8_t n) {
return;
}
DynamicJsonDocument doc(JSON_BUFFER_SIZE); // full sized buffer for larger maps
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(7)) return;
#endif
DEBUG_PRINT(F("Reading LED map from "));
DEBUG_PRINTLN(fileName);
if (!readObjectFromFile(fileName, nullptr, &doc)) return; //if file does not exist just exit
if (!readObjectFromFile(fileName, nullptr, &doc)) {
releaseJSONBufferLock();
return; //if file does not exist just exit
}
// erase old custom ledmap
if (customMappingTable != nullptr) {
@@ -1113,6 +1144,8 @@ void WS2812FX::deserializeMap(uint8_t n) {
customMappingTable[i] = (uint16_t) map[i];
}
}
releaseJSONBufferLock();
}
//gamma 2.8 lookup table used for color correction
@@ -1153,15 +1186,20 @@ uint8_t WS2812FX::gamma8(uint8_t b)
uint32_t WS2812FX::gamma32(uint32_t color)
{
if (!gammaCorrectCol) return color;
uint8_t w = (color >> 24);
uint8_t r = (color >> 16);
uint8_t g = (color >> 8);
uint8_t b = color;
uint8_t w = W(color);
uint8_t r = R(color);
uint8_t g = G(color);
uint8_t b = B(color);
w = gammaT[w];
r = gammaT[r];
g = gammaT[g];
b = gammaT[b];
return ((w << 24) | (r << 16) | (g << 8) | (b));
return RGBW32(r, g, b, w);
}
WS2812FX* WS2812FX::instance = nullptr;
WS2812FX* WS2812FX::instance = nullptr;
//Bus static member definition, would belong in bus_manager.cpp
int16_t Bus::_cct = -1;
uint8_t Bus::_cctBlend = 0;
uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL;

View File

@@ -73,17 +73,27 @@ void onAlexaChange(EspalexaDevice* dev)
if (espalexaDevice->getColorMode() == EspalexaColorMode::ct) //shade of white
{
uint16_t ct = espalexaDevice->getCt();
if (strip.isRgbw)
{
if (!ct) return;
uint16_t k = 1000000 / ct; //mireds to kelvin
if (strip.hasCCTBus()) {
uint8_t segid = strip.getMainSegmentId();
WS2812FX::Segment& seg = strip.getSegment(segid);
uint8_t cctPrev = seg.cct;
seg.setCCT(k, segid);
if (seg.cct != cctPrev) effectChanged = true; //send UDP
col[0]= 0; col[1]= 0; col[2]= 0; col[3]= 255;
} else if (strip.isRgbw) {
switch (ct) { //these values empirically look good on RGBW
case 199: col[0]=255; col[1]=255; col[2]=255; col[3]=255; break;
case 234: col[0]=127; col[1]=127; col[2]=127; col[3]=255; break;
case 284: col[0]= 0; col[1]= 0; col[2]= 0; col[3]=255; break;
case 350: col[0]=130; col[1]= 90; col[2]= 0; col[3]=255; break;
case 383: col[0]=255; col[1]=153; col[2]= 0; col[3]=255; break;
default : colorKtoRGB(k, col);
}
} else {
colorCTtoRGB(ct, col);
colorKtoRGB(k, col);
}
} else {
uint32_t color = espalexaDevice->getRGB();

View File

@@ -10,6 +10,10 @@
#include "bus_wrapper.h"
#include <Arduino.h>
//colors.cpp
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
void colorRGBtoRGBW(byte* rgb);
// enable additional debug output
#ifdef WLED_DEBUG
#ifndef ESP8266
@@ -28,13 +32,20 @@
#define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit)))
#define UNSET_BIT(var,bit) ((var)&=(~(uint16_t)(0x0001<<(bit))))
//color mangling macros
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
#define R(c) (byte((c) >> 16))
#define G(c) (byte((c) >> 8))
#define B(c) (byte(c))
#define W(c) (byte((c) >> 24))
//temporary struct for passing bus configuration to bus
struct BusConfig {
uint8_t type = TYPE_WS2812_RGB;
uint16_t count = 1;
uint16_t start = 0;
uint8_t colorOrder = COL_ORDER_GRB;
bool reversed = false;
uint16_t count;
uint16_t start;
uint8_t colorOrder;
bool reversed;
uint8_t skipAmount;
bool refreshReq;
uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255};
@@ -62,82 +73,80 @@ struct BusConfig {
}
};
//parent class of BusDigital and BusPwm
//parent class of BusDigital, BusPwm, and BusNetwork
class Bus {
public:
Bus(uint8_t type, uint16_t start) {
_type = type;
_start = start;
};
virtual void show() {}
virtual bool canShow() { return true; }
Bus(uint8_t type, uint16_t start) {
_type = type;
_start = start;
};
virtual void setPixelColor(uint16_t pix, uint32_t c) {};
virtual ~Bus() {} //throw the bus under the bus
virtual void setBrightness(uint8_t b) {};
virtual void show() {}
virtual bool canShow() { return true; }
virtual void setStatusPixel(uint32_t c) {}
virtual void setPixelColor(uint16_t pix, uint32_t c) {}
virtual uint32_t getPixelColor(uint16_t pix) { return 0; }
virtual void setBrightness(uint8_t b) {}
virtual void cleanup() {}
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
inline uint16_t getLength() { return _len; }
virtual void setColorOrder() {}
virtual uint8_t getColorOrder() { return COL_ORDER_RGB; }
virtual uint8_t skippedLeds() { return 0; }
inline uint16_t getStart() { return _start; }
inline void setStart(uint16_t start) { _start = start; }
inline uint8_t getType() { return _type; }
inline bool isOk() { return _valid; }
inline bool isOffRefreshRequired() { return _needsRefresh; }
bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; }
virtual uint32_t getPixelColor(uint16_t pix) { return 0; };
virtual bool isRgbw() { return Bus::isRgbw(_type); }
static bool isRgbw(uint8_t type) {
if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true;
if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
return false;
}
static void setCCT(uint16_t cct) {
_cct = cct;
}
static void setCCTBlend(uint8_t b) {
if (b > 100) b = 100;
_cctBlend = (b * 127) / 100;
//compile-time limiter for hardware that can't power both white channels at max
#ifdef WLED_MAX_CCT_BLEND
if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND;
#endif
}
inline static void setAutoWhiteMode(uint8_t m) { if (m < 4) _autoWhiteMode = m; }
inline static uint8_t getAutoWhiteMode() { return _autoWhiteMode; }
virtual void cleanup() {};
virtual ~Bus() { //throw the bus under the bus
}
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
inline uint16_t getStart() {
return _start;
}
inline void setStart(uint16_t start) {
_start = start;
}
virtual uint16_t getLength() {
return 1;
}
virtual void setColorOrder() {}
virtual uint8_t getColorOrder() {
return COL_ORDER_RGB;
}
virtual bool isRgbw() {
return false;
}
virtual uint8_t skippedLeds() {
return 0;
}
inline uint8_t getType() {
return _type;
}
inline bool isOk() {
return _valid;
}
static bool isRgbw(uint8_t type) {
if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true;
if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
return false;
}
inline bool isOffRefreshRequired() {
return _needsRefresh;
}
bool reversed = false;
bool reversed = false;
protected:
uint8_t _type = TYPE_NONE;
uint8_t _bri = 255;
uint16_t _start = 0;
bool _valid = false;
bool _needsRefresh = false;
uint8_t _type = TYPE_NONE;
uint8_t _bri = 255;
uint16_t _start = 0;
uint16_t _len = 1;
bool _valid = false;
bool _needsRefresh = false;
static uint8_t _autoWhiteMode;
static int16_t _cct;
static uint8_t _cctBlend;
uint32_t autoWhiteCalc(uint32_t c) {
if (_autoWhiteMode == RGBW_MODE_MANUAL_ONLY) return c;
uint8_t w = W(c);
//ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0)
if (w > 0 && _autoWhiteMode == RGBW_MODE_DUAL) return c;
uint8_t r = R(c);
uint8_t g = G(c);
uint8_t b = B(c);
w = r < g ? (r < b ? r : b) : (g < b ? g : b);
if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode
return RGBW32(r, g, b, w);
}
};
@@ -184,7 +193,18 @@ class BusDigital : public Bus {
PolyBus::setBrightness(_busPtr, _iType, b);
}
//If LEDs are skipped, it is possible to use the first as a status LED.
//TODO only show if no new show due in the next 50ms
void setStatusPixel(uint32_t c) {
if (_skip && canShow()) {
PolyBus::setPixelColor(_busPtr, _iType, 0, c, _colorOrder);
PolyBus::show(_busPtr, _iType);
}
}
void setPixelColor(uint16_t pix, uint32_t c) {
if (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814) c = autoWhiteCalc(c);
if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
if (reversed) pix = _len - pix -1;
else pix += _skip;
PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder);
@@ -215,10 +235,6 @@ class BusDigital : public Bus {
_colorOrder = colorOrder;
}
inline bool isRgbw() {
return Bus::isRgbw(_type);
}
inline uint8_t skippedLeds() {
return _skip;
}
@@ -245,7 +261,6 @@ class BusDigital : public Bus {
uint8_t _colorOrder = COL_ORDER_GRB;
uint8_t _pins[2] = {255, 255};
uint8_t _iType = I_NONE;
uint16_t _len = 0;
uint8_t _skip = 0;
void * _busPtr = nullptr;
};
@@ -287,29 +302,62 @@ class BusPwm : public Bus {
void setPixelColor(uint16_t pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel
uint8_t r = c >> 16;
uint8_t g = c >> 8;
uint8_t b = c ;
uint8_t w = c >> 24;
if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c);
if (_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
}
uint8_t r = R(c);
uint8_t g = G(c);
uint8_t b = B(c);
uint8_t w = W(c);
uint8_t cct = 0; //0 - full warm white, 255 - full cold white
if (_cct > -1) {
if (_cct >= 1900) cct = (_cct - 1900) >> 5;
else if (_cct < 256) cct = _cct;
} else {
cct = (approximateKelvinFromRGB(c) - 1900) >> 5;
}
uint8_t ww, cw;
#ifdef WLED_USE_IC_CCT
ww = w;
cw = cct;
#else
//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
if (cct < _cctBlend) ww = 255;
else ww = ((255-cct) * 255) / (255 - _cctBlend);
if ((255-cct) < _cctBlend) cw = 255;
else cw = (cct * 255) / (255 - _cctBlend);
ww = (w * ww) / 255; //brightness scaling
cw = (w * cw) / 255;
#endif
switch (_type) {
case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value
_data[0] = max(r, max(g, max(b, w))); break;
case TYPE_ANALOG_2CH: //warm white + cold white, we'll need some nice handling here, for now just R+G channels
case TYPE_ANALOG_3CH: //standard dumb RGB
case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation
_data[0] = w;
break;
case TYPE_ANALOG_2CH: //warm white + cold white
_data[1] = cw;
_data[0] = ww;
break;
case TYPE_ANALOG_5CH: //RGB + warm white + cold white
// perhaps a non-linear adjustment would be in order. need to test
_data[4] = cw;
w = ww;
case TYPE_ANALOG_4CH: //RGBW
case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB
_data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = 0; break;
default: return;
_data[3] = w;
case TYPE_ANALOG_3CH: //standard dumb RGB
_data[0] = r; _data[1] = g; _data[2] = b;
break;
}
}
//does no index check
uint32_t getPixelColor(uint16_t pix) {
if (!_valid) return 0;
return ((_data[3] << 24) | (_data[0] << 16) | (_data[1] << 8) | (_data[2]));
return RGBW32(_data[0], _data[1], _data[2], _data[3]);
}
void show() {
@@ -333,14 +381,12 @@ class BusPwm : public Bus {
uint8_t getPins(uint8_t* pinArray) {
if (!_valid) return 0;
uint8_t numPins = NUM_PWM_PINS(_type);
for (uint8_t i = 0; i < numPins; i++) pinArray[i] = _pins[i];
for (uint8_t i = 0; i < numPins; i++) {
pinArray[i] = _pins[i];
}
return numPins;
}
bool isRgbw() {
return Bus::isRgbw(_type);
}
inline void cleanup() {
deallocatePins();
}
@@ -397,12 +443,10 @@ class BusNetwork : public Bus {
// break;
// }
_UDPchannels = _rgbw ? 4 : 3;
//_rgbw |= bc.rgbwOverride; // RGBW override in bit 7 or can have a special type
_data = (byte *)malloc(bc.count * _UDPchannels);
if (_data == nullptr) return;
memset(_data, 0, bc.count * _UDPchannels);
_len = bc.count;
//_colorOrder = bc.colorOrder;
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
_broadcastLock = false;
_valid = true;
@@ -410,22 +454,19 @@ class BusNetwork : public Bus {
void setPixelColor(uint16_t pix, uint32_t c) {
if (!_valid || pix >= _len) return;
if (isRgbw()) c = autoWhiteCalc(c);
if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
uint16_t offset = pix * _UDPchannels;
_data[offset] = 0xFF & (c >> 16);
_data[offset+1] = 0xFF & (c >> 8);
_data[offset+2] = 0xFF & (c );
if (_rgbw) _data[offset+3] = 0xFF & (c >> 24);
_data[offset] = R(c);
_data[offset+1] = G(c);
_data[offset+2] = B(c);
if (_rgbw) _data[offset+3] = W(c);
}
uint32_t getPixelColor(uint16_t pix) {
if (!_valid || pix >= _len) return 0;
uint16_t offset = pix * _UDPchannels;
return (
(_rgbw ? (_data[offset+3] << 24) : 0)
| (_data[offset] << 16)
| (_data[offset+1] << 8)
| (_data[offset+2] )
);
return RGBW32(_data[offset], _data[offset+1], _data[offset+2], _rgbw ? (_data[offset+3] << 24) : 0);
}
void show() {
@@ -472,8 +513,6 @@ class BusNetwork : public Bus {
private:
IPAddress _client;
uint16_t _len = 0;
//uint8_t _colorOrder;
uint8_t _bri = 255;
uint8_t _UDPtype;
uint8_t _UDPchannels;
@@ -538,7 +577,13 @@ class BusManager {
}
}
void setPixelColor(uint16_t pix, uint32_t c) {
void setStatusPixel(uint32_t c) {
for (uint8_t i = 0; i < numBusses; i++) {
busses[i]->setStatusPixel(c);
}
}
void setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1) {
for (uint8_t i = 0; i < numBusses; i++) {
Bus* b = busses[i];
uint16_t bstart = b->getStart();
@@ -553,6 +598,15 @@ class BusManager {
}
}
void setSegmentCCT(int16_t cct, bool allowWBCorrection = false) {
if (cct > 255) cct = 255;
if (cct >= 0) {
//if white balance correction allowed, save as kelvin value instead of 0-255
if (allowWBCorrection) cct = 1900 + (cct << 5);
} else cct = -1;
Bus::setCCT(cct);
}
uint32_t getPixelColor(uint16_t pix) {
for (uint8_t i = 0; i < numBusses; i++) {
Bus* b = busses[i];

View File

@@ -299,8 +299,11 @@ void handleIO()
#ifdef ESP8266
// turn off built-in LED if strip is turned off
// this will break digital bus so will need to be reinitialised on On
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
PinOwner ledPinOwner = pinManager.getPinOwner(LED_BUILTIN);
if (!strip.isOffRefreshRequred && (ledPinOwner == PinOwner::None || ledPinOwner == PinOwner::BusDigital)) {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
}
#endif
if (rlyPin>=0) {
pinMode(rlyPin, OUTPUT);

View File

@@ -14,6 +14,7 @@ void getStringFromJson(char* dest, const char* src, size_t len) {
}
bool deserializeConfig(JsonObject doc, bool fromFS) {
bool needsSave = false;
//int rev_major = doc["rev"][0]; // 1
//int rev_minor = doc["rev"][1]; // 0
@@ -61,7 +62,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(apBehavior, ap[F("behav")]);
/*
JsonArray ap_ip = ap["ip"];
for (byte i = 0; i < 4; i++) {
@@ -80,7 +80,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
CJSON(strip.rgbwMode, hw_led[F("rgbwm")]);
Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | Bus::getAutoWhiteMode());
CJSON(correctWB, hw_led["cct"]);
CJSON(cctFromRgb, hw_led[F("cr")]);
CJSON(strip.cctBlending, hw_led[F("cb")]);
Bus::setCCTBlend(strip.cctBlending);
JsonArray ins = hw_led["ins"];
@@ -220,7 +224,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(macroNl, light_nl["macro"]);
JsonObject def = doc[F("def")];
CJSON(bootPreset, def[F("ps")]);
CJSON(bootPreset, def["ps"]);
CJSON(turnOnAtBoot, def["on"]); // true
CJSON(briS, def["bri"]); // 128
@@ -400,16 +404,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (i > 14) break;
CJSON(DMXFixtureMap[i],dmx_fixmap[i]);
}
CJSON(e131ProxyUniverse, dmx[F("e131proxy")]);
#endif
DEBUG_PRINTLN(F("Starting usermod config."));
JsonObject usermods_settings = doc["um"];
if (!usermods_settings.isNull()) {
bool allComplete = usermods.readFromConfig(usermods_settings);
if (!allComplete && fromFS) serializeConfig();
needsSave = !usermods.readFromConfig(usermods_settings);
}
if (fromFS) return false;
if (fromFS) return needsSave;
doReboot = doc[F("rb")] | doReboot;
return (doc["sv"] | true);
}
@@ -421,19 +426,27 @@ void deserializeConfigFromFS() {
return;
}
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(1)) return;
#endif
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
success = readObjectFromFile("/cfg.json", nullptr, &doc);
if (!success) { //if file does not exist, try reading from EEPROM
deEEPSettings();
releaseJSONBufferLock();
return;
}
// NOTE: This routine deserializes *and* applies the configuration
// Therefore, must also initialize ethernet from this function
deserializeConfig(doc.as<JsonObject>(), true);
bool needsSave = deserializeConfig(doc.as<JsonObject>(), true);
releaseJSONBufferLock();
if (needsSave) serializeConfig(); // usermods required new prameters
}
void serializeConfig() {
@@ -441,7 +454,11 @@ void serializeConfig() {
DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(2)) return;
#endif
JsonArray rev = doc.createNestedArray("rev");
rev.add(1); //major settings revision
@@ -519,7 +536,10 @@ void serializeConfig() {
hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade
hw_led[F("maxpwr")] = strip.ablMilliampsMax;
hw_led[F("ledma")] = strip.milliampsPerLed;
hw_led[F("rgbwm")] = strip.rgbwMode;
hw_led["cct"] = correctWB;
hw_led[F("cr")] = cctFromRgb;
hw_led[F("cb")] = strip.cctBlending;
hw_led[F("rgbwm")] = Bus::getAutoWhiteMode();
JsonArray hw_led_ins = hw_led.createNestedArray("ins");
@@ -536,7 +556,7 @@ void serializeConfig() {
ins[F("order")] = bus->getColorOrder();
ins["rev"] = bus->reversed;
ins[F("skip")] = bus->skippedLeds();
ins["type"] = bus->getType() & 0x7F;;
ins["type"] = bus->getType() & 0x7F;
ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbw")] = bus->isRgbw();
}
@@ -593,7 +613,7 @@ void serializeConfig() {
light_nl["macro"] = macroNl;
JsonObject def = doc.createNestedObject("def");
def[F("ps")] = bootPreset;
def["ps"] = bootPreset;
def["on"] = turnOnAtBoot;
def["bri"] = briS;
@@ -736,8 +756,11 @@ void serializeConfig() {
dmx[F("start-led")] = DMXStartLED;
JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap"));
for (byte i = 0; i < 15; i++)
for (byte i = 0; i < 15; i++) {
dmx_fixmap.add(DMXFixtureMap[i]);
}
dmx[F("e131proxy")] = e131ProxyUniverse;
#endif
JsonObject usermods_settings = doc.createNestedObject("um");
@@ -746,16 +769,24 @@ void serializeConfig() {
File f = WLED_FS.open("/cfg.json", "w");
if (f) serializeJson(doc, f);
f.close();
releaseJSONBufferLock();
}
//settings in /wsec.json, not accessible via webserver, for passwords and tokens
bool deserializeConfigSec() {
DEBUG_PRINTLN(F("Reading settings from /wsec.json..."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(3)) return false;
#endif
bool success = readObjectFromFile("/wsec.json", nullptr, &doc);
if (!success) return false;
if (!success) {
releaseJSONBufferLock();
return false;
}
JsonObject nw_ins_0 = doc["nw"]["ins"][0];
getStringFromJson(clientPass, nw_ins_0["psk"], 65);
@@ -787,13 +818,18 @@ bool deserializeConfigSec() {
CJSON(wifiLock, ota[F("lock-wifi")]);
CJSON(aOtaEnabled, ota[F("aota")]);
releaseJSONBufferLock();
return true;
}
void serializeConfigSec() {
DEBUG_PRINTLN(F("Writing settings to /wsec.json..."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(4)) return;
#endif
JsonObject nw = doc.createNestedObject("nw");
@@ -828,4 +864,5 @@ void serializeConfigSec() {
File f = WLED_FS.open("/wsec.json", "w");
if (f) serializeJson(doc, f);
f.close();
releaseJSONBufferLock();
}

View File

@@ -6,36 +6,25 @@
void colorFromUint32(uint32_t in, bool secondary)
{
if (secondary) {
colSec[3] = in >> 24 & 0xFF;
colSec[0] = in >> 16 & 0xFF;
colSec[1] = in >> 8 & 0xFF;
colSec[2] = in & 0xFF;
} else {
col[3] = in >> 24 & 0xFF;
col[0] = in >> 16 & 0xFF;
col[1] = in >> 8 & 0xFF;
col[2] = in & 0xFF;
}
byte *_col = secondary ? colSec : col;
_col[0] = R(in);
_col[1] = G(in);
_col[2] = B(in);
_col[3] = W(in);
}
//load a color without affecting the white channel
void colorFromUint24(uint32_t in, bool secondary)
{
if (secondary) {
colSec[0] = in >> 16 & 0xFF;
colSec[1] = in >> 8 & 0xFF;
colSec[2] = in & 0xFF;
} else {
col[0] = in >> 16 & 0xFF;
col[1] = in >> 8 & 0xFF;
col[2] = in & 0xFF;
}
byte *_col = secondary ? colSec : col;
_col[0] = R(in);
_col[1] = G(in);
_col[2] = B(in);
}
//store color components in uint32_t
uint32_t colorFromRgbw(byte* rgbw) {
return (rgbw[0] << 16) + (rgbw[1] << 8) + rgbw[2] + (rgbw[3] << 24);
return RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
}
//relatively change white brightness, minumum A=5
@@ -64,9 +53,9 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb
case 4: rgb[0]=t,rgb[1]=p,rgb[2]=255;break;
case 5: rgb[0]=255,rgb[1]=p,rgb[2]=q;
}
if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col);
}
//get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html)
void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc
{
float r = 0, g = 0, b = 0;
@@ -84,7 +73,7 @@ void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc
g = round(288.1221695283 * pow((temp - 60), -0.0755148492));
b = 255;
}
g += 15; //mod by Aircoookie, a bit less accurate but visibly less pinkish
//g += 12; //mod by Aircoookie, a bit less accurate but visibly less pinkish
rgb[0] = (uint8_t) constrain(r, 0, 255);
rgb[1] = (uint8_t) constrain(g, 0, 255);
rgb[2] = (uint8_t) constrain(b, 0, 255);
@@ -111,7 +100,6 @@ void colorCTtoRGB(uint16_t mired, byte* rgb) //white spectrum to rgb, bins
} else {
rgb[0]=237;rgb[1]=255;rgb[2]=239;//150
}
if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col);
}
#ifndef WLED_DISABLE_HUESYNC
@@ -169,7 +157,6 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www
rgb[0] = 255.0*r;
rgb[1] = 255.0*g;
rgb[2] = 255.0*b;
if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col);
}
void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
@@ -197,10 +184,10 @@ void colorFromDecOrHexString(byte* rgb, char* in)
c = strtoul(in, NULL, 10);
}
rgb[3] = (c >> 24) & 0xFF;
rgb[0] = (c >> 16) & 0xFF;
rgb[1] = (c >> 8) & 0xFF;
rgb[2] = c & 0xFF;
rgb[0] = R(c);
rgb[1] = G(c);
rgb[2] = B(c);
rgb[3] = W(c);
}
//contrary to the colorFromDecOrHexString() function, this uses the more standard RRGGBB / RRGGBBWW order
@@ -212,14 +199,14 @@ bool colorFromHexString(byte* rgb, const char* in) {
uint32_t c = strtoul(in, NULL, 16);
if (inputSize == 6) {
rgb[0] = (c >> 16) & 0xFF;
rgb[1] = (c >> 8) & 0xFF;
rgb[2] = c & 0xFF;
rgb[0] = (c >> 16);
rgb[1] = (c >> 8);
rgb[2] = c ;
} else {
rgb[0] = (c >> 24) & 0xFF;
rgb[1] = (c >> 16) & 0xFF;
rgb[2] = (c >> 8) & 0xFF;
rgb[3] = c & 0xFF;
rgb[0] = (c >> 24);
rgb[1] = (c >> 16);
rgb[2] = (c >> 8);
rgb[3] = c ;
}
return true;
}
@@ -236,11 +223,80 @@ float maxf (float v, float w)
return v;
}
/*
uint32_t colorRGBtoRGBW(uint32_t c)
{
byte rgb[4];
rgb[0] = R(c);
rgb[1] = G(c);
rgb[2] = B(c);
rgb[3] = W(c);
colorRGBtoRGBW(rgb);
return RGBW32(rgb[0], rgb[1], rgb[2], rgb[3]);
}
void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY)
{
float low = minf(rgb[0],minf(rgb[1],rgb[2]));
float high = maxf(rgb[0],maxf(rgb[1],rgb[2]));
if (high < 0.1f) return;
float sat = 100.0f * ((high - low) / high);; // maximum saturation is 100 (corrected from 255)
float sat = 100.0f * ((high - low) / high); // maximum saturation is 100 (corrected from 255)
rgb[3] = (byte)((255.0f - sat) / 255.0f * (rgb[0] + rgb[1] + rgb[2]) / 3);
}
*/
byte correctionRGB[4] = {0,0,0,0};
uint16_t lastKelvin = 0;
// adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance)
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb)
{
//remember so that slow colorKtoRGB() doesn't have to run for every setPixelColor()
if (lastKelvin != kelvin) colorKtoRGB(kelvin, correctionRGB); // convert Kelvin to RGB
lastKelvin = kelvin;
byte rgbw[4];
rgbw[0] = ((uint16_t) correctionRGB[0] * R(rgb)) /255; // correct R
rgbw[1] = ((uint16_t) correctionRGB[1] * G(rgb)) /255; // correct G
rgbw[2] = ((uint16_t) correctionRGB[2] * B(rgb)) /255; // correct B
rgbw[3] = W(rgb);
return colorFromRgbw(rgbw);
}
//approximates a Kelvin color temperature from an RGB color.
//this does no check for the "whiteness" of the color,
//so should be used combined with a saturation check (as done by auto-white)
//values from http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html (10deg)
//equation spreadsheet at https://bit.ly/30RkHaN
//accuracy +-50K from 1900K up to 8000K
//minimum returned: 1900K, maximum returned: 10091K (range of 8192)
uint16_t approximateKelvinFromRGB(uint32_t rgb) {
//if not either red or blue is 255, color is dimmed. Scale up
uint8_t r = R(rgb), b = B(rgb);
if (r == b) return 6550; //red == blue at about 6600K (also can't go further if both R and B are 0)
if (r > b) {
//scale blue up as if red was at 255
uint16_t scale = 0xFFFF / r; //get scale factor (range 257-65535)
b = ((uint16_t)b * scale) >> 8;
//For all temps K<6600 R is bigger than B (for full bri colors R=255)
//-> Use 9 linear approximations for blackbody radiation blue values from 2000-6600K (blue is always 0 below 2000K)
if (b < 33) return 1900 + b *6;
if (b < 72) return 2100 + (b-33) *10;
if (b < 101) return 2492 + (b-72) *14;
if (b < 132) return 2900 + (b-101) *16;
if (b < 159) return 3398 + (b-132) *19;
if (b < 186) return 3906 + (b-159) *22;
if (b < 210) return 4500 + (b-186) *25;
if (b < 230) return 5100 + (b-210) *30;
return 5700 + (b-230) *34;
} else {
//scale red up as if blue was at 255
uint16_t scale = 0xFFFF / b; //get scale factor (range 257-65535)
r = ((uint16_t)r * scale) >> 8;
//For all temps K>6600 B is bigger than R (for full bri colors B=255)
//-> Use 2 linear approximations for blackbody radiation red values from 6600-10091K (blue is always 0 below 2000K)
if (r > 225) return 6600 + (254-r) *50;
uint16_t k = 8080 + (225-r) *86;
return (k > 10091) ? 10091 : k;
}
}

View File

@@ -124,7 +124,7 @@
// - 0b010 (dec. 32-47) analog (PWM)
// - 0b011 (dec. 48-63) digital (data + clock / SPI)
// - 0b100 (dec. 64-79) unused/reserved
// - 0b101 (dec. 80-95) digital (data + clock / SPI)
// - 0b101 (dec. 80-95) virtual network busses
// - 0b110 (dec. 96-111) unused/reserved
// - 0b111 (dec. 112-127) unused/reserved
//bit 7 is reserved and set to 0
@@ -313,15 +313,15 @@
#ifdef ESP8266
#define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards
#else
#define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards
#define LEDPIN 2 // Changed from 16 to restore compatibility with ESP32-pico
#endif
#endif
#ifdef WLED_ENABLE_DMX
#if (LEDPIN == 2)
#undef LEDPIN
#define LEDPIN 3
#warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 3."
#define LEDPIN 1
#warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 1."
#endif
#endif

View File

@@ -459,12 +459,20 @@ img {
.sliderdisplay {
content:'';
position: absolute;
top: 13px; bottom: 13px;
left: 10px; right: 10px;
top: 12.5px; bottom: 12.5px;
left: 13px; right: 13px;
background: var(--c-4);
border-radius: 17px;
pointer-events: none;
z-index: -1;
--bg: var(--c-f);
}
#rwrap .sliderdisplay { --bg: #f00; }
#gwrap .sliderdisplay { --bg: #0f0; }
#bwrap .sliderdisplay { --bg: #00f; }
#wbal .sliderdisplay, #kwrap .sliderdisplay {
background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #cbdbff);
}
.sliderbubble {
@@ -492,6 +500,7 @@ input[type=range] {
background-color: transparent;
cursor: pointer;
}
input[type=range]:focus {
outline: none;
}
@@ -527,8 +536,7 @@ input[type=range]:active + .sliderbubble {
display: inline;
transform: translateX(-50%);
}
#wwrap {
#wwrap, #wbal {
display: none;
}

View File

@@ -45,17 +45,30 @@
<div class ="container">
<div id="Colors" class="tabcontent">
<div id="picker" class="noslide"></div>
<div id="vwrap">
<div class="sliderwrap il" id="vwrap">
<input id="sliderV" class="noslide" oninput="fromV()" onchange="setColor(0)" max="100" min="0" type="range" value="128" step="any" />
<div class="sliderdisplay"></div>
</div><br>
</div>
<div id="kwrap">
<div class="sliderwrap il">
<input id="sliderK" class="noslide" oninput="fromK()" onchange="setColor(0)" max="10091" min="1900" type="range" value="6550" />
<div class="sliderdisplay"></div>
</div>
</div>
<div id="rgbwrap">
<div class="sliderwrap il">
<input id="sliderR" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,1)" max="255" min="0" type="range" value="128" />
<p class="labels">RGB color</p>
<div class="sliderwrap il" id="rwrap">
<input id="sliderR" class="noslide" onchange="setColor(0)" oninput="fromRgb()" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div>
</div><br>
<div class="sliderwrap il">
<input id="sliderG" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,2)" max="255" min="0" type="range" value="128" />
<div class="sliderwrap il" id="gwrap">
<input id="sliderG" class="noslide" onchange="setColor(0)" oninput="fromRgb()" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div>
</div><br>
<div class="sliderwrap il">
<input id="sliderB" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,3)" max="255" min="0" type="range" value="128" />
<div class="sliderwrap il" id="bwrap">
<input id="sliderB" class="noslide" onchange="setColor(0)" oninput="fromRgb()" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div>
</div><br>
</div>
@@ -66,6 +79,13 @@
<div class="sliderdisplay"></div>
</div>
</div>
<div id="wbal">
<p class="labels">White balance</p>
<div class="sliderwrap il">
<input id="sliderA" class="noslide" onchange="setBalance(this.value)" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div>
</div>
</div>
<div id="qcs-w">
<div class="qcs" onclick="pC('#ff0000');" title="Red" style="background-color:#ff0000;"></div>
<div class="qcs" onclick="pC('#ffa000');" title="Orange" style="background-color:#ffa000;"></div>
@@ -98,7 +118,7 @@
<label class="check schkl">
&nbsp;
<input type="radio" value="${palettes[i].id}" name="palette" onChange="setPalette()">
<span class="checkmark schk"></span>
<span class="radiomark schk"></span>
</label>
<div class="lstIcontent">
<span class="lstIname">

View File

@@ -40,25 +40,12 @@ var hol = [
var cpick = new iro.ColorPicker("#picker", {
width: 260,
wheelLightness: false,
wheelAngle: 90,
wheelAngle: 270,
wheelDirection: "clockwise",
layout: [
{
component: iro.ui.Wheel,
options: {}
},
{
component: iro.ui.Slider,
options: {
sliderType: 'value'
}
},
{
component: iro.ui.Slider,
options: {
sliderType: 'kelvin',
minTemperature: 2100,
maxTemperature: 10000
}
}
]
});
@@ -81,6 +68,8 @@ function applyCfg()
var ccfg = cfg.comp.colors;
d.getElementById('hexw').style.display = ccfg.hex ? "block":"none";
d.getElementById('picker').style.display = ccfg.picker ? "block":"none";
d.getElementById('vwrap').style.display = ccfg.picker ? "block":"none";
d.getElementById('kwrap').style.display = ccfg.picker ? "block":"none";
d.getElementById('rgbwrap').style.display = ccfg.rgb ? "block":"none";
d.getElementById('qcs-w').style.display = ccfg.quick ? "block":"none";
var l = cfg.comp.labels;
@@ -202,11 +191,10 @@ function onLoad() {
if (window.location.protocol == "file:") {
loc = true;
locip = localStorage.getItem('locIp');
if (!locip)
{
locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip);
}
if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip);
}
}
var sett = localStorage.getItem('wledUiCfg');
if (sett) cfg = mergeDeep(cfg, JSON.parse(sett));
@@ -229,9 +217,9 @@ function onLoad() {
.catch(function (error) {
console.log("holidays.json does not contain array of holidays. Defaults loaded.");
})
.finally(function(){
loadBg(cfg.theme.bg.url);
});
.finally(function(){
loadBg(cfg.theme.bg.url);
});
} else
loadBg(cfg.theme.bg.url);
if (cfg.comp.css) loadSkinCSS('skinCss');
@@ -246,6 +234,7 @@ function onLoad() {
cpick.on("input:end", function() {
setColor(1);
});
cpick.on("color:change", updatePSliders);
pmtLS = localStorage.getItem('wledPmt');
setTimeout(function(){requestJson(null, false);}, 50);
d.addEventListener("visibilitychange", handleVisibilityChange, false);
@@ -320,11 +309,10 @@ function inforow(key, val, unit = "")
function getLowestUnusedP()
{
var l = 1;
for (var key in pJson)
{
for (var key in pJson) {
if (key == l) l++;
}
if (l > 250) l = 250;
}
if (l > 250) l = 250;
return l;
}
@@ -357,16 +345,16 @@ function papiVal(i) {
function qlName(i) {
if (!pJson[i]) return "";
if (!pJson[i].ql) return "";
return pJson[i].ql;
if (!pJson[i].ql) return "";
return pJson[i].ql;
}
function cpBck() {
var copyText = d.getElementById("bck");
copyText.select();
copyText.setSelectionRange(0, 999999);
d.execCommand("copy");
copyText.select();
copyText.setSelectionRange(0, 999999);
d.execCommand("copy");
showToast("Copied to clipboard!");
}
@@ -452,21 +440,20 @@ function populateQL()
{
var cn = "";
if (pQL.length > 0) {
cn += `<p class="labels">Quick load</p>`;
cn += `<p class="labels">Quick load</p>`;
var it = 0;
for (var key of (pQL||[]))
{
cn += `<button class="xxs btn psts" id="p${key[0]}qlb" onclick="setPreset(${key[0]});">${key[1]}</button>`;
it++;
if (it > 4) {
it = 0;
cn += '<br>';
}
}
if (it != 0) cn+= '<br>';
var it = 0;
for (var key of (pQL||[])) {
cn += `<button class="xxs btn psts" id="p${key[0]}qlb" onclick="setPreset(${key[0]});">${key[1]}</button>`;
it++;
if (it > 4) {
it = 0;
cn += '<br>';
}
}
if (it != 0) cn+= '<br>';
cn += `<p class="labels">All presets</p>`;
cn += `<p class="labels">All presets</p>`;
}
d.getElementById('pql').innerHTML = cn;
}
@@ -478,25 +465,25 @@ function populatePresets(fromls)
var cn = "";
var arr = Object.entries(pJson);
arr.sort(cmpP);
pQL = [];
var is = [];
pNum = 0;
pQL = [];
var is = [];
pNum = 0;
for (var key of (arr||[]))
{
if (!isObject(key[1])) continue;
let i = parseInt(key[0]);
var qll = key[1].ql;
if (qll) pQL.push([i, qll]);
is.push(i);
if (qll) pQL.push([i, qll]);
is.push(i);
cn += `<div class="seg pres" id="p${i}o">`;
if (cfg.comp.pid) cn += `<div class="pid">${i}</div>`;
cn += `<div class="segname pname" onclick="setPreset(${i})">${isPlaylist(i)?"<i class='icons btn-icon'>&#xe139;</i>":""}${pName(i)}</div>
<i class="icons e-icon flr ${expanded[i+100] ? "exp":""}" id="sege${i+100}" onclick="expand(${i+100})">&#xe395;</i>
<div class="segin" id="seg${i+100}"></div>
</div><br>`;
pNum++;
cn += `<div class="seg pres" id="p${i}o">`;
if (cfg.comp.pid) cn += `<div class="pid">${i}</div>`;
cn += `<div class="segname pname" onclick="setPreset(${i})">${isPlaylist(i)?"<i class='icons btn-icon'>&#xe139;</i>":""}${pName(i)}</div>
<i class="icons e-icon flr ${expanded[i+100] ? "exp":""}" id="sege${i+100}" onclick="expand(${i+100})">&#xe395;</i>
<div class="segin" id="seg${i+100}"></div>
</div><br>`;
pNum++;
}
d.getElementById('pcont').innerHTML = cn;
@@ -526,16 +513,15 @@ function populateInfo(i)
var pwru = "Not calculated";
if (pwr > 1000) {pwr /= 1000; pwr = pwr.toFixed((pwr > 10) ? 0 : 1); pwru = pwr + " A";}
else if (pwr > 0) {pwr = 50 * Math.round(pwr/50); pwru = pwr + " mA";}
var urows="";
if (i.u) {
for (const [k, val] of Object.entries(i.u))
{
if (val[1]) {
urows += inforow(k,val[0],val[1]);
} else {
urows += inforow(k,val);
}
}
var urows="";
if (i.u) {
for (const [k, val] of Object.entries(i.u)) {
if (val[1]) {
urows += inforow(k,val[0],val[1]);
} else {
urows += inforow(k,val);
}
}
}
var vcn = "Kuuhaku";
@@ -580,7 +566,7 @@ function populateSegments(s)
</label>
<div class="segname">
<div class="segntxt" onclick="selSegEx(${i})">${inst.n ? inst.n : "Segment "+i}</div>
<i class="icons edit-icon ${expanded[i] ? "expanded":""}" id="seg${i}nedit" onclick="tglSegn(${i})">&#xe2c6;</i>
<i class="icons edit-icon ${expanded[i] ? "expanded":""}" id="seg${i}nedit" onclick="tglSegn(${i})">&#xe2c6;</i>
</div>
<i class="icons e-icon flr ${expanded[i] ? "exp":""}" id="sege${i}" onclick="expand(${i})">&#xe395;</i>
<div class="segin ${expanded[i] ? "expanded":""}" id="seg${i}">
@@ -651,7 +637,7 @@ function populateSegments(s)
function populateEffects(effects)
{
var html = `<div class="searchbar"><input type="text" class="search" placeholder="Search" oninput="search(this)" />
<i class="icons search-icon">&#xe0a1;</i><i class="icons search-cancel-icon" onclick="cancelSearch(this)">&#xe38f;</i></div>`;
<i class="icons search-icon">&#xe0a1;</i><i class="icons search-cancel-icon" onclick="cancelSearch(this)">&#xe38f;</i></div>`;
effects.shift(); //remove solid
for (let i = 0; i < effects.length; i++) {
@@ -697,7 +683,7 @@ function populatePalettes(palettes)
});
var html = `<div class="searchbar"><input type="text" class="search" placeholder="Search" oninput="search(this)" />
<i class="icons search-icon">&#xe0a1;</i><i class="icons search-cancel-icon" onclick="cancelSearch(this)">&#xe38f;</i></div>`;
<i class="icons search-icon">&#xe0a1;</i><i class="icons search-cancel-icon" onclick="cancelSearch(this)">&#xe38f;</i></div>`;
for (let i = 0; i < palettes.length; i++) {
html += generateListItemHtml(
'palette',
@@ -780,15 +766,15 @@ function genPalPrevCss(id)
function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', extraClass = '')
{
return `<div class="lstI btn fxbtn ${extraClass}" data-id="${id}" onClick="${clickAction}(${id})">
<label class="radio fxchkl">
<input type="radio" value="${id}" name="${listName}">
<span class="radiomark"></span>
</label>
<span class="lstIname">
${name}
</span>
${extraHtml}
</div>`;
<label class="radio fxchkl">
<input type="radio" value="${id}" name="${listName}">
<span class="radiomark"></span>
</label>
<span class="lstIname">
${name}
</span>
${extraHtml}
</div>`;
}
function btype(b){
@@ -815,16 +801,16 @@ function populateNodes(i,n)
if (o.name) {
var url = `<button class="btn btna-icon tab" onclick="location.assign('http://${o.ip}');">${bname(o)}</button>`;
urows += inforow(url,`${btype(o.type)}<br><i>${o.vid==0?"N/A":o.vid}</i>`);
nnodes++;
nnodes++;
}
}
}
if (i.ndc < 0) cn += `Instance List is disabled.`;
else if (nnodes == 0) cn += `No other instances found.`;
if (i.ndc < 0) cn += `Instance List is disabled.`;
else if (nnodes == 0) cn += `No other instances found.`;
cn += `<table class="infot">
${urows}
${inforow("Current instance:",i.name)}
</table>`;
${urows}
${inforow("Current instance:",i.name)}
</table>`;
d.getElementById('kn').innerHTML = cn;
}
@@ -854,38 +840,34 @@ function loadNodes()
});
}
function updateTrail(e, slidercol)
//update the 'sliderdisplay' background div of a slider for a visual indication of slider position
function updateTrail(e)
{
if (e==null) return;
var max = e.hasAttribute('max') ? e.attributes.max.value : 255;
var perc = e.value * 100 / max;
perc = parseInt(perc);
if (perc < 50) perc += 2;
var scol;
switch (slidercol) {
case 1: scol = "#f00"; break;
case 2: scol = "#0f0"; break;
case 3: scol = "#00f"; break;
default: scol = "var(--c-f)";
}
var val = `linear-gradient(90deg, ${scol} ${perc}%, var(--c-4) ${perc}%)`;
if (perc < 50) perc += 2;
var val = `linear-gradient(90deg, var(--bg) ${perc}%, var(--c-4) ${perc}%)`;
e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val;
}
//rangetouch slider function
function updateBubble(e)
{
var bubble = e.target.parentNode.getElementsByTagName('output')[0];
if (bubble) {
bubble.innerHTML = e.target.value;
}
}
//rangetouch slider function
function toggleBubble(e)
{
e.target.parentNode.querySelector('output').classList.toggle('hidden');
}
//updates segment length upon input of segment values
function updateLen(s)
{
if (!d.getElementById(`seg${s}s`)) return;
@@ -911,22 +893,23 @@ function updateLen(s)
d.getElementById(`seg${s}len`).innerHTML = out;
}
//updates background color of currently selected preset
function updatePA()
{
var ps = d.getElementsByClassName("seg");
var ps = d.getElementsByClassName("seg"); //reset all preset buttons
for (let i = 0; i < ps.length; i++) {
ps[i].style.backgroundColor = "var(--c-2)";
}
ps = d.getElementsByClassName("psts");
ps = d.getElementsByClassName("psts"); //reset all quick selectors
for (let i = 0; i < ps.length; i++) {
ps[i].style.backgroundColor = "var(--c-2)";
}
if (currentPreset > 0) {
var acv = d.getElementById(`p${currentPreset}o`);
if (acv && !expanded[currentPreset+100])
acv.style.background = "var(--c-6)";
acv.style.background = "var(--c-6)"; //highlight current preset
acv = d.getElementById(`p${currentPreset}qlb`);
if (acv) acv.style.background = "var(--c-6)";
if (acv) acv.style.background = "var(--c-6)"; //highlight quick selector
}
}
@@ -939,12 +922,12 @@ function updateUI()
updateTrail(d.getElementById('sliderBri'));
updateTrail(d.getElementById('sliderSpeed'));
updateTrail(d.getElementById('sliderIntensity'));
updateTrail(d.getElementById('sliderW'));
if (isRgbw) d.getElementById('wwrap').style.display = "block";
d.getElementById('wwrap').style.display = (isRgbw) ? "block":"none";
d.getElementById('wbal').style.display = (lastinfo.leds.cct) ? "block":"none";
d.getElementById('kwrap').style.display = (lastinfo.leds.cct) ? "none":"block";
updatePA();
updateHex();
updateRgb();
updatePSliders();
}
function displayRover(i,s)
@@ -961,32 +944,32 @@ function compare(a, b) {
}
function cmpP(a, b) {
if (!a[1].n) return (a[0] > b[0]);
return a[1].n.localeCompare(b[1].n,undefined, {numeric: true});
return a[1].n.localeCompare(b[1].n,undefined, {numeric: true});
}
function makeWS() {
if (ws) return;
ws = new WebSocket('ws://'+(loc?locip:window.location.hostname)+'/ws');
ws.onmessage = function(event) {
var json = JSON.parse(event.data);
if (json.leds) return; //liveview packet
clearTimeout(jsonTimeout);
if (ws) return;
ws = new WebSocket('ws://'+(loc?locip:window.location.hostname)+'/ws');
ws.onmessage = function(event) {
var json = JSON.parse(event.data);
if (json.leds) return; //liveview packet
clearTimeout(jsonTimeout);
jsonTimeout = null;
clearErrorToast();
d.getElementById('connind').style.backgroundColor = "#079";
var info = json.info;
d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none";
lastinfo = info;
if (isInfo) {
populateInfo(info);
}
s = json.state;
displayRover(info, s);
d.getElementById('connind').style.backgroundColor = "#079";
var info = json.info;
d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none";
lastinfo = info;
if (isInfo) {
populateInfo(info);
}
s = json.state;
displayRover(info, s);
readState(json.state);
};
ws.onclose = function(event) {
d.getElementById('connind').style.backgroundColor = "#831";
}
ws.onclose = function(event) {
d.getElementById('connind').style.backgroundColor = "#831";
}
}
function readState(s,command=false) {
@@ -1022,7 +1005,7 @@ function readState(s,command=false) {
if (isRgbw) whites[e] = parseInt(i.col[e][3]);
selectSlot(csel);
}
d.getElementById('sliderW').value = whites[csel];
if (i.cct != null && i.cct>=0) d.getElementById("sliderA").value = i.cct;
d.getElementById('sliderSpeed').value = i.sx;
d.getElementById('sliderIntensity').value = i.ix;
@@ -1081,7 +1064,7 @@ var reqsLegal = false;
function requestJson(command, rinfo = true) {
d.getElementById('connind').style.backgroundColor = "#a90";
if (command && !reqsLegal) return; //stop post requests from chrome onchange event on page restore
if (command && !reqsLegal) return; //stop post requests from chrome onchange event on page restore
lastUpdate = new Date();
if (!jsonTimeout) jsonTimeout = setTimeout(showErrorToast, 3000);
var req = null;
@@ -1091,7 +1074,7 @@ function requestJson(command, rinfo = true) {
url = `http://${locip}${url}`;
}
var useWs = ((command || rinfo) && ws && ws.readyState === WebSocket.OPEN);
var useWs = ((command || rinfo) && ws && ws.readyState === WebSocket.OPEN);
var type = command ? 'post':'get';
if (command)
@@ -1107,10 +1090,10 @@ function requestJson(command, rinfo = true) {
if (req.length > 1000) useWs = false; //do not send very long requests over websocket
}
if (useWs) {
ws.send(req?req:'{"v":true}');
return;
}
if (useWs) {
ws.send(req?req:'{"v":true}');
return;
}
fetch
(url, {
@@ -1153,7 +1136,7 @@ function requestJson(command, rinfo = true) {
});
},25);
reqsLegal = true;
reqsLegal = true;
}
var info = json.info;
@@ -1172,12 +1155,12 @@ function requestJson(command, rinfo = true) {
isRgbw = info.leds.wv;
ledCount = info.leds.count;
syncTglRecv = info.str;
maxSeg = info.leds.maxseg;
maxSeg = info.leds.maxseg;
pmt = info.fs.pmt;
if (!command && rinfo) setTimeout(loadPresets, 99);
d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none";
d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none";
lastinfo = info;
if (isInfo) {
populateInfo(info);
@@ -1186,7 +1169,7 @@ function requestJson(command, rinfo = true) {
displayRover(info, s);
}
readState(s,command);
readState(s,command);
})
.catch(function (error) {
showToast(error, true);
@@ -1231,7 +1214,7 @@ function toggleLiveview() {
var url = loc ? `http://${locip}/liveview`:"/liveview";
d.getElementById('liveview').src = (isLv) ? url:"about:blank";
d.getElementById('buttonSr').className = (isLv) ? "active":"";
if (!isLv && ws && ws.readyState === WebSocket.OPEN) ws.send('{"lv":false}');
if (!isLv && ws && ws.readyState === WebSocket.OPEN) ws.send('{"lv":false}');
size();
}
@@ -1244,11 +1227,11 @@ function toggleInfo() {
}
function toggleNodes() {
if (isInfo) toggleInfo();
if (isInfo) toggleInfo();
isNodes = !isNodes;
d.getElementById('nodes').style.transform = (isNodes) ? "translateY(0px)":"translateY(100%)";
d.getElementById('buttonNodes').className = (isNodes) ? "active":"";
if (isNodes) loadNodes();
d.getElementById('buttonNodes').className = (isNodes) ? "active":"";
if (isNodes) loadNodes();
}
function makeSeg() {
@@ -1258,27 +1241,27 @@ function makeSeg() {
if (pend < ledCount) ns = pend;
}
var cn = `<div class="seg">
<div class="segname newseg">
New segment ${lowestUnused}
<i class="icons edit-icon expanded" onclick="tglSegn(${lowestUnused})">&#xe2c6;</i>
</div>
<br>
<div class="segin expanded">
<input type="text" class="ptxt stxt noslide" id="seg${lowestUnused}t" autocomplete="off" maxlength=32 value="" placeholder="Enter name..."/>
<table class="segt">
<tr>
<td class="segtd">Start LED</td>
<td class="segtd">${cfg.comp.seglen?"Length":"Stop LED"}</td>
</tr>
<tr>
<td class="segtd"><input class="noslide segn" id="seg${lowestUnused}s" type="number" min="0" max="${ledCount-1}" value="${ns}" oninput="updateLen(${lowestUnused})"></td>
<td class="segtd"><input class="noslide segn" id="seg${lowestUnused}e" type="number" min="0" max="${ledCount-(cfg.comp.seglen?ns:0)}" value="${ledCount-(cfg.comp.seglen?ns:0)}" oninput="updateLen(${lowestUnused})"></td>
</tr>
</table>
<div class="h" id="seg${lowestUnused}len">${ledCount - ns} LED${ledCount - ns >1 ? "s":""}</div>
<i class="icons e-icon cnf cnf-s half" id="segc${lowestUnused}" onclick="setSeg(${lowestUnused}); resetUtil();">&#xe390;</i>
</div>
</div>`;
<div class="segname newseg">
New segment ${lowestUnused}
<i class="icons edit-icon expanded" onclick="tglSegn(${lowestUnused})">&#xe2c6;</i>
</div>
<br>
<div class="segin expanded">
<input type="text" class="ptxt stxt noslide" id="seg${lowestUnused}t" autocomplete="off" maxlength=32 value="" placeholder="Enter name..."/>
<table class="segt">
<tr>
<td class="segtd">Start LED</td>
<td class="segtd">${cfg.comp.seglen?"Length":"Stop LED"}</td>
</tr>
<tr>
<td class="segtd"><input class="noslide segn" id="seg${lowestUnused}s" type="number" min="0" max="${ledCount-1}" value="${ns}" oninput="updateLen(${lowestUnused})"></td>
<td class="segtd"><input class="noslide segn" id="seg${lowestUnused}e" type="number" min="0" max="${ledCount-(cfg.comp.seglen?ns:0)}" value="${ledCount-(cfg.comp.seglen?ns:0)}" oninput="updateLen(${lowestUnused})"></td>
</tr>
</table>
<div class="h" id="seg${lowestUnused}len">${ledCount - ns} LED${ledCount - ns >1 ? "s":""}</div>
<i class="icons e-icon cnf cnf-s half" id="segc${lowestUnused}" onclick="setSeg(${lowestUnused}); resetUtil();">&#xe390;</i>
</div>
</div>`;
d.getElementById('segutil').innerHTML = cn;
}
@@ -1376,75 +1359,74 @@ function makeP(i,pl) {
var content = "";
if (pl) {
var rep = plJson[i].repeat ? plJson[i].repeat : 0;
content = `
<div class="first c">Playlist Entries</div>
<div id="ple${i}"></div><label class="check revchkl">
Shuffle
<input type="checkbox" id="pl${i}rtgl" onchange="plR(${i})" ${plJson[i].r?"checked":""}>
<span class="checkmark schk"></span>
</label>
<label class="check revchkl">
Repeat indefinitely
<input type="checkbox" id="pl${i}rptgl" onchange="plR(${i})" ${rep?"":"checked"}>
<span class="checkmark schk"></span>
</label>
<div id="pl${i}o1" style="display:${rep?"block":"none"}">
<div class="c">Repeat <input class="noslide" type="number" id="pl${i}rp" oninput="plR(${i})" max=127 min=0 value=${rep>0?rep:1}> times</div>
End preset:<br>
<select class="btn sel sel-ple" id="pl${i}selEnd" onchange="plR(${i})" data-val=${plJson[i].end?plJson[i].end:0}>
content = `<div class="first c">Playlist Entries</div>
<div id="ple${i}"></div>
<label class="check revchkl">
Shuffle
<input type="checkbox" id="pl${i}rtgl" onchange="plR(${i})" ${plJson[i].r?"checked":""}>
<span class="checkmark schk"></span>
</label>
<label class="check revchkl">
Repeat indefinitely
<input type="checkbox" id="pl${i}rptgl" onchange="plR(${i})" ${rep?"":"checked"}>
<span class="checkmark schk"></span>
</label>
<div id="pl${i}o1" style="display:${rep?"block":"none"}">
<div class="c">Repeat <input class="noslide" type="number" id="pl${i}rp" oninput="plR(${i})" max=127 min=0 value=${rep>0?rep:1}> times</div>
End preset:<br>
<select class="btn sel sel-ple" id="pl${i}selEnd" onchange="plR(${i})" data-val=${plJson[i].end?plJson[i].end:0}>
<option value=0>None</option>
${makePlSel(true)}
</select>
</div>
<button class="btn btn-i btn-p" onclick="testPl(${i}, this)"><i class='icons btn-icon'>&#xe139;</i>Test</button>`;
${makePlSel(true)}
</select>
</div>
<button class="btn btn-i btn-p" onclick="testPl(${i}, this)"><i class='icons btn-icon'>&#xe139;</i>Test</button>`;
}
else content = `<label class="check revchkl">
Include brightness
<input type="checkbox" id="p${i}ibtgl" checked>
<span class="checkmark schk"></span>
</label>
<label class="check revchkl">
Save segment bounds
<input type="checkbox" id="p${i}sbtgl" checked>
<span class="checkmark schk"></span>
</label>`;
Include brightness
<input type="checkbox" id="p${i}ibtgl" checked>
<span class="checkmark schk"></span>
</label>
<label class="check revchkl">
Save segment bounds
<input type="checkbox" id="p${i}sbtgl" checked>
<span class="checkmark schk"></span>
</label>`;
return `
<input type="text" class="ptxt noslide" id="p${i}txt" autocomplete="off" maxlength=32 value="${(i>0)?pName(i):""}" placeholder="Enter name..."/><br>
<div class="c">Quick load label: <input type="text" class="qltxt noslide" maxlength=2 value="${qlName(i)}" id="p${i}ql" autocomplete="off"/></div>
<div class="h">(leave empty for no Quick load button)</div>
<div ${pl&&i==0?"style='display:none'":""}>
return `<input type="text" class="ptxt noslide" id="p${i}txt" autocomplete="off" maxlength=32 value="${(i>0)?pName(i):""}" placeholder="Enter name..."/><br>
<div class="c">Quick load label: <input type="text" class="qltxt noslide" maxlength=2 value="${qlName(i)}" id="p${i}ql" autocomplete="off"/></div>
<div class="h">(leave empty for no Quick load button)</div>
<div ${pl&&i==0?"style='display:none'":""}>
<label class="check revchkl">
${pl?"Show playlist editor":(i>0)?"Overwrite with state":"Use current state"}
<input type="checkbox" id="p${i}cstgl" onchange="tglCs(${i})" ${(i==0||pl)?"checked":""}>
<span class="checkmark schk"></span>
</label><br>
</div>
<div class="po2" id="p${i}o2">
API command<br>
<textarea class="noslide" id="p${i}api"></textarea>
</div>
<div class="po1" id="p${i}o1">
${content}
</div>
<div class="c">Save to ID <input class="noslide" id="p${i}id" type="number" oninput="checkUsed(${i})" max=250 min=1 value=${(i>0)?i:getLowestUnusedP()}></div>
<div class="c">
<button class="btn btn-i btn-p" onclick="saveP(${i},${pl})"><i class="icons btn-icon">&#xe390;</i>Save ${(pl)?"playlist":(i>0)?"changes":"preset"}</button>
${(i>0)?'<button class="btn btn-i btn-p" id="p'+i+'del" onclick="delP('+i+')"><i class="icons btn-icon">&#xe037;</i>Delete '+(pl?"playlist":"preset"):
'<button class="btn btn-p" onclick="resetPUtil()">Cancel'}</button>
</div>
<div class="pwarn ${(i>0)?"bp":""} c" id="p${i}warn">
${pl?"Show playlist editor":(i>0)?"Overwrite with state":"Use current state"}
<input type="checkbox" id="p${i}cstgl" onchange="tglCs(${i})" ${(i==0||pl)?"checked":""}>
<span class="checkmark schk"></span>
</label><br>
</div>
<div class="po2" id="p${i}o2">
API command<br>
<textarea class="noslide" id="p${i}api"></textarea>
</div>
<div class="po1" id="p${i}o1">
${content}
</div>
<div class="c">Save to ID <input class="noslide" id="p${i}id" type="number" oninput="checkUsed(${i})" max=250 min=1 value=${(i>0)?i:getLowestUnusedP()}></div>
<div class="c">
<button class="btn btn-i btn-p" onclick="saveP(${i},${pl})"><i class="icons btn-icon">&#xe390;</i>Save ${(pl)?"playlist":(i>0)?"changes":"preset"}</button>
${(i>0)?'<button class="btn btn-i btn-p" id="p'+i+'del" onclick="delP('+i+')"><i class="icons btn-icon">&#xe037;</i>Delete '+(pl?"playlist":"preset"):
'<button class="btn btn-p" onclick="resetPUtil()">Cancel'}</button>
</div>
<div class="pwarn ${(i>0)?"bp":""} c" id="p${i}warn">
</div>
${(i>0)? ('<div class="h">ID ' +i+ '</div>'):""}`;
</div>
${(i>0)? ('<div class="h">ID ' +i+ '</div>'):""}`;
}
function makePUtil() {
d.getElementById('putil').innerHTML = `<div class="seg pres">
<div class="segname newseg">
New preset</div>
<div class="segin expanded">
${makeP(0)}</div></div>`;
<div class="segname newseg">
New preset</div>
<div class="segin expanded">
${makeP(0)}</div></div>`;
}
function makePlEntry(p,i) {
@@ -1478,7 +1460,7 @@ function makePlUtil() {
function resetPUtil() {
var cn = `<button class="btn btn-s btn-i" onclick="makePUtil()"><i class="icons btn-icon">&#xe18a;</i>Create preset</button><br>
<button class="btn btn-s btn-i" onclick="makePlUtil()"><i class='icons btn-icon'>&#xe139;</i>Create playlist</button><br>`;
<button class="btn btn-s btn-i" onclick="makePlUtil()"><i class='icons btn-icon'>&#xe139;</i>Create playlist</button><br>`;
d.getElementById('putil').innerHTML = cn;
}
@@ -1490,7 +1472,7 @@ function tglCs(i){
function tglSegn(s)
{
d.getElementById(`seg${s}t`).style.display =
d.getElementById(`seg${s}t`).style.display =
(window.getComputedStyle(d.getElementById(`seg${s}t`)).display === "none") ? "inline":"none";
}
@@ -1660,7 +1642,7 @@ function saveP(i,pl) {
var pQN = d.getElementById(`p${i}ql`).value;
if (pQN.length > 0) obj.ql = pQN;
showToast("Saving " + pN +" (" + pI + ")");
showToast("Saving " + pN +" (" + pI + ")");
requestJson(obj);
if (obj.o) {
pJson[pI] = obj;
@@ -1721,14 +1703,15 @@ function selectSlot(b) {
cd[csel].style.border="5px solid white";
cd[csel].style.margin="2px";
cd[csel].style.width="50px";
cpick.color.set(cd[csel].style.backgroundColor);
setPicker(cd[csel].style.backgroundColor);
//force slider update on initial load (picker "color:change" not fired if black)
if (cpick.color.value == 0) updatePSliders();
d.getElementById('sliderW').value = whites[csel];
updateTrail(d.getElementById('sliderW'));
updateHex();
updateRgb();
redrawPalPrev();
}
//set the color from a hex string. Used by quick color selectors
var lasth = 0;
function pC(col)
{
@@ -1741,12 +1724,12 @@ function pC(col)
} while (Math.abs(col.h - lasth) < 50);
lasth = col.h;
}
cpick.color.set(col);
setPicker(col);
setColor(0);
}
function updateRgb()
{
function updatePSliders() {
//update RGB sliders
var col = cpick.color.rgb;
var s = d.getElementById('sliderR');
s.value = col.r; updateTrail(s,1);
@@ -1754,16 +1737,26 @@ function updateRgb()
s.value = col.g; updateTrail(s,2);
s = d.getElementById('sliderB');
s.value = col.b; updateTrail(s,3);
}
function updateHex()
{
var str = cpick.color.hexString;
str = str.substring(1);
//update hex field
var str = cpick.color.hexString.substring(1);
var w = whites[csel];
if (w > 0) str += w.toString(16);
d.getElementById('hexc').value = str;
d.getElementById('hexcnf').style.backgroundColor = "var(--c-3)";
//update value slider
var v = d.getElementById('sliderV');
v.value = cpick.color.value;
//background color as if color had full value
var hsv = {"h":cpick.color.hue,"s":cpick.color.saturation,"v":100};
var c = iro.Color.hsvToRgb(hsv);
var cs = 'rgb('+c.r+','+c.g+','+c.b+')';
v.parentNode.getElementsByClassName('sliderdisplay')[0].style.setProperty('--bg',cs);
updateTrail(v);
//update Kelvin slider
d.getElementById('sliderK').value = cpick.color.kelvin;
}
function hexEnter() {
@@ -1776,28 +1769,44 @@ function fromHex()
var str = d.getElementById('hexc').value;
whites[csel] = parseInt(str.substring(6), 16);
try {
cpick.color.set("#" + str.substring(0,6));
setPicker("#" + str.substring(0,6));
} catch (e) {
cpick.color.set("#ffaa00");
setPicker("#ffaa00");
}
if (isNaN(whites[csel])) whites[csel] = 0;
setColor(2);
}
function setPicker(rgb) {
var c = new iro.Color(rgb);
if (c.value > 0) cpick.color.set(c);
else cpick.color.setChannel('hsv', 'v', 0);
}
function fromV()
{
cpick.color.setChannel('hsv', 'v', d.getElementById('sliderV').value);
}
function fromK()
{
cpick.color.set({ kelvin: d.getElementById('sliderK').value });
}
function fromRgb()
{
var r = d.getElementById('sliderR').value;
var g = d.getElementById('sliderG').value;
var b = d.getElementById('sliderB').value;
cpick.color.set(`rgb(${r},${g},${b})`);
setColor(0);
setPicker(`rgb(${r},${g},${b})`);
}
//sr 0: from RGB sliders, 1: from picker, 2: from hex
function setColor(sr) {
var cd = d.getElementById('csl').children;
if (sr == 1 && cd[csel].style.backgroundColor == 'rgb(0, 0, 0)') cpick.color.setChannel('hsv', 'v', 100);
if (sr == 1 && cd[csel].style.backgroundColor == "rgb(0, 0, 0)") cpick.color.setChannel('hsv', 'v', 100);
cd[csel].style.backgroundColor = cpick.color.rgbString;
if (sr != 2) whites[csel] = d.getElementById('sliderW').value;
if (sr != 2) whites[csel] = parseInt(d.getElementById('sliderW').value);
var col = cpick.color.rgb;
var obj = {"seg": {"col": [[col.r, col.g, col.b, whites[csel]],[],[]]}};
if (csel == 1) {
@@ -1805,8 +1814,12 @@ function setColor(sr) {
} else if (csel == 2) {
obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}};
}
updateHex();
updateRgb();
requestJson(obj);
}
function setBalance(b)
{
var obj = {"seg": {"cct": parseInt(b)}};
requestJson(obj);
}
@@ -1825,9 +1838,9 @@ function cnfReset()
if (!cnfr)
{
var bt = d.getElementById('resetbtn');
bt.style.color = "#f00";
bt.innerHTML = "Confirm Reboot";
cnfr = true; return;
bt.style.color = "#f00";
bt.innerHTML = "Confirm Reboot";
cnfr = true; return;
}
window.location.href = "/reset";
}
@@ -1838,9 +1851,9 @@ function rSegs()
var bt = d.getElementById('rsbtn');
if (!cnfrS)
{
bt.style.color = "#f00";
bt.innerHTML = "Confirm reset";
cnfrS = true; return;
bt.style.color = "#f00";
bt.innerHTML = "Confirm reset";
cnfrS = true; return;
}
cnfrS = false;
bt.style.color = "#fff";
@@ -1916,7 +1929,7 @@ function getPalettesData(page, callback)
function search(searchField) {
var searchText = searchField.value.toUpperCase();
searchField.parentElement.getElementsByClassName('search-cancel-icon')[0].style.display = (searchText.length < 1)?"none":"inline";
searchField.parentElement.getElementsByClassName('search-cancel-icon')[0].style.display = (searchText.length < 1)?"none":"inline";
var elements = searchField.parentElement.parentElement.querySelectorAll('.lstI');
for (i = 0; i < elements.length; i++) {
var item = elements[i];
@@ -2039,10 +2052,10 @@ function move(e) {
var s = Math.sign(dx);
var f = +(s*dx/w).toFixed(2);
if((clientX != 0) &&
(iSlide > 0 || s < 0) && (iSlide < N - 1 || s > 0) &&
f > 0.12 &&
d.getElementsByClassName("tabcontent")[iSlide].scrollTop == scrollS) {
if ((clientX != 0) &&
(iSlide > 0 || s < 0) && (iSlide < N - 1 || s > 0) &&
f > 0.12 &&
d.getElementsByClassName("tabcontent")[iSlide].scrollTop == scrollS) {
_C.style.setProperty('--i', iSlide -= s);
f = 1 - f;
updateTablinks(iSlide);
@@ -2054,7 +2067,7 @@ function move(e) {
function size() {
w = window.innerWidth;
d.getElementById('buttonNodes').style.display = (lastinfo.ndc > 0 && w > 770) ? "block":"none";
d.getElementById('buttonNodes').style.display = (lastinfo.ndc > 0 && w > 770) ? "block":"none";
var h = d.getElementById('top').clientHeight;
sCol('--th', h + "px");
sCol('--bh', d.getElementById('bot').clientHeight + "px");
@@ -2079,7 +2092,7 @@ function togglePcMode(fromB = false)
d.getElementById('buttonPcm').className = (pcMode) ? "active":"";
d.getElementById('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto";
sCol('--bh', d.getElementById('bot').clientHeight + "px");
_C.style.width = (pcMode)?'100%':'400%';
_C.style.width = (pcMode)?'100%':'400%';
lastw = w;
}

File diff suppressed because one or more lines are too long

View File

@@ -17,23 +17,15 @@
color: #fff;
font-family: Verdana, Helvetica, sans-serif;
border: 1px solid #333;
border-radius: var(--h);
font-size: 6vmin;
height: var(--h);
width: 95%;
width: calc(100% - 40px);
margin-top: 2vh;
}
</style>
<script>
function BB()
{
if (window.frameElement) {
document.getElementById("b").style.display = "none";
document.documentElement.style.setProperty('--h',"13.86vh");
}
}
</script>
</head>
<body onload="BB()">
<body>
<form action="/"><button type=submit id="b">Back</button></form>
<form action="/settings/wifi"><button type="submit">WiFi Setup</button></form>
<form action="/settings/leds"><button type="submit">LED Preferences</button></form>

View File

@@ -247,8 +247,8 @@
gId('m0').innerHTML = memu;
bquot = memu / maxM * 100;
gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`;
gId('ledwarning').style.display = (sLC > maxPB || maxLC > 800 || bquot > 80) ? 'inline':'none';
gId('ledwarning').style.color = (sLC > maxPB || maxLC > maxPB || bquot > 100) ? 'red':'orange';
gId('ledwarning').style.display = (maxLC > Math.min(maxPB,800) || bquot > 80) ? 'inline':'none';
gId('ledwarning').style.color = (maxLC > Math.max(maxPB,800) || bquot > 100) ? 'red':'orange';
gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>ERROR: Using over ${maxM}B!</b>)` : "") : "800 LEDs per output";
// calculate power
var val = Math.ceil((100 + sPC * laprev)/500)/2;
@@ -305,10 +305,11 @@ ${i+1}:
<option value="52">LPD8806</option>
<option value="53">P9813</option>
<option value="41">PWM White</option>
<option value="42">PWM WWCW</option>
<option value="42">PWM CCT</option>
<option value="43">PWM RGB</option>
<option value="44">PWM RGBW</option>
<option value="45">PWM RGBWC</option>
<option value="45">PWM RGB+CCT</option>
<!--option value="46">PWM RGB+DCCT</option-->
<option value="80">DDP RGB (network)</option>
<!--option value="81">E1.31 RGB (network)</option-->
<!--option value="82">ArtNet RGB (network)</option-->
@@ -333,7 +334,7 @@ ${i+1}:
<span id="p4d${i}"></span><input type="number" name="L4${i}" min="0" max="33" class="xs" onchange="UI()"/>
<div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div>
<div id="dig${i}s" style="display:inline"><br>Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}"></div>
<div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}">&nbsp;</div>
<div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}"></div>
</div>`;
f.insertAdjacentHTML("beforeend", cn);
}
@@ -389,11 +390,71 @@ ${i+1}:
req.send(formData);
d.Sf.data.value = '';
return false;
}
// https://stackoverflow.com/questions/7346563/loading-local-json-file
function loadCfg(o) {
var f, fr;
if (typeof window.FileReader !== 'function') {
alert("The file API isn't supported on this browser yet.");
return;
}
if (!o.files) {
alert("This browser doesn't support the `files` property of file inputs.");
} else if (!o.files[0]) {
alert("Please select a JSON file first!");
} else {
f = o.files[0];
fr = new FileReader();
fr.onload = receivedText;
fr.readAsText(f);
}
o.value = '';
function receivedText(e) {
let lines = e.target.result;
var c = JSON.parse(lines);
if (c.hw) {
if (c.hw.led) {
for (var i=0; i<10; i++) addLEDs(-1);
var l = c.hw.led;
l.ins.forEach((v,i,a)=>{
addLEDs(1);
for (var j=0; j<v.pin.length; j++) d.getElementsByName(`L${j}${i}`)[0].value = v.pin[j];
d.getElementsByName("LT"+i)[0].value = v.type;
d.getElementsByName("LS"+i)[0].value = v.start;
d.getElementsByName("LC"+i)[0].value = v.len;
d.getElementsByName("CO"+i)[0].value = v.order;
d.getElementsByName("SL"+i)[0].checked = v.skip;
d.getElementsByName("RF"+i)[0].checked = v.ref;
d.getElementsByName("CV"+i)[0].checked = v.rev;
});
}
if (c.hw.btn) {
var b = c.hw.btn;
if (Array.isArray(b.ins)) gId("btns").innerHTML = "";
b.ins.forEach((v,i,a)=>{
addBtn(i,v.pin[0],v.type);
});
d.getElementsByName("TT")[0].value = b.tt;
}
if (c.hw.ir) {
d.getElementsByName("IR")[0].value = c.hw.ir.pin;
d.getElementsByName("IT")[0].value = c.hw.ir.type;
}
if (c.hw.relay) {
d.getElementsByName("RL")[0].value = c.hw.relay.pin;
d.getElementsByName("RM")[0].checked = c.hw.relay.inv;
}
UI();
}
}
}
function GetV()
{
//values injected by server while sending HTML
//d.um_p=[6,7,8,9,10,11,1];bLimits(3,4096,4000,1664);d.Sf.MS.checked=1;addLEDs(1);d.Sf.L00.value=2;d.Sf.LC0.value=30;d.Sf.LT0.value=22;d.Sf.CO0.value=0;d.Sf.LS0.value=15;d.Sf.CV0.checked=1;d.Sf.SL0.checked=0;addLEDs(1);d.Sf.L01.value=10;d.Sf.L11.value=10;d.Sf.L21.value=1;d.Sf.L31.value=10;d.Sf.LC1.value=60;d.Sf.LT1.value=80;d.Sf.CO1.value=1;d.Sf.LS1.value=0;d.Sf.CV1.checked=0;d.Sf.SL1.checked=0;d.Sf.MA.value=850;d.Sf.LA.value=0;d.Sf.CA.value=56;d.Sf.AW.value=3;d.Sf.BO.checked=1;d.Sf.BP.value=80;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=0;d.Sf.BF.value=100;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=0;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=12;d.Sf.RM.checked=1;addBtn(0,0,0);addBtn(1,-1,0);d.Sf.TT.value=32;d.Sf.IR.value=-1;d.Sf.IT.value=0;
//d.um_p=[6,7,8,9,10,11,14,15,13,1,21,19,22,25,26,27,5,23,18,17];bLimits(10,2048,64000,8192);d.Sf.MS.checked=1;d.Sf.CCT.checked=0;addLEDs(1);d.Sf.L00.value=192;d.Sf.L10.value=168;d.Sf.L20.value=0;d.Sf.L30.value=61;d.Sf.LC0.value=421;d.Sf.LT0.value=80;d.Sf.CO0.value=1;d.Sf.LS0.value=0;d.Sf.CV0.checked=0;d.Sf.SL0.checked=0;d.Sf.RF0.checked=0;d.Sf.MA.value=850;d.Sf.LA.value=0;d.Sf.CA.value=127;d.Sf.AW.value=3;d.Sf.BO.checked=0;d.Sf.BP.value=0;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=1;d.Sf.BF.value=100;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=1;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=-1;d.Sf.RM.checked=1;addBtn(0,-1,0);addBtn(1,-1,0);addBtn(2,-1,0);addBtn(3,-1,0);d.Sf.TT.value=32;d.Sf.IR.value=-1;d.Sf.IT.value=8;
}
</script>
<style>
@@ -474,7 +535,7 @@ ${i+1}:
<br><br>
Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br>
Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br><br>
Brightness factor: <input name="BF" type="number" class="s" min="1" max="255" required> %
Brightness factor: <input name="BF" type="number" class="s" min="1" max="255" required> %%
<h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br>
Transition Time: <input name="TD" type="number" class="l" min="0" max="65500"> ms<br>
@@ -489,6 +550,19 @@ ${i+1}:
<option value="2">Fade Color</option>
<option value="3">Sunrise</option>
</select>
<h3>White management</h3>
White Balance correction: <input type="checkbox" name="CCT"> <br>
<span class="wc">
Auto-calculate white channel from RGB:<br>
<select name="AW">
<option value=0>None</option>
<option value=1>Brighter</option>
<option value=2>Accurate</option>
<option value=3>Dual</option>
</select>
<br>
Calculate CCT from RGB: <input type="checkbox" name="CR"> <br>
CCT additive blending: <input type="number" class="s" min="0" max="100" name="CB" required> %%</span>
<h3>Advanced</h3>
Palette blending:
<select name="PB">
@@ -497,16 +571,9 @@ ${i+1}:
<option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option>
</select><br>
<span class="wc">
Auto-calculate white channel from RGB:<br>
<select name="AW">
<option value=0>None</option>
<option value=1>Brighter</option>
<option value=2>Accurate</option>
<option value=3>Dual</option>
<option value=4>Legacy</option>
</select>
<br></span><hr>
<hr style="width:260px">
<div id="cfg">Config template: <input type="file" name="data2" accept=".json"> <input type="button" value="Apply" onclick="loadCfg(d.Sf.data2);"><br></div>
<hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>

View File

@@ -9,6 +9,10 @@ body {
hr {
border-color: #666;
}
a {
color: #28f;
text-decoration: none;
}
button, .btn {
background: #333;
color: #fff;

View File

@@ -22,10 +22,10 @@ void handleDMX()
for (int i = DMXStartLED; i < len; i++) { // uses the amount of LEDs as fixture count
uint32_t in = strip.getPixelColor(i); // get the colors for the individual fixtures as suggested by Aircoookie in issue #462
byte w = in >> 24 & 0xFF;
byte r = in >> 16 & 0xFF;
byte g = in >> 8 & 0xFF;
byte b = in & 0xFF;
byte w = W(in);
byte r = R(in);
byte g = G(in);
byte b = B(in);
int DMXFixtureStart = DMXStart + (DMXGap * (i - DMXStartLED));
for (int j = 0; j < DMXChannels; j++) {

View File

@@ -67,7 +67,9 @@ void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TOD
void colorFromDecOrHexString(byte* rgb, char* in);
bool colorFromHexString(byte* rgb, const char* in);
void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY)
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb);
//dmx.cpp
void initDMX();
@@ -209,6 +211,17 @@ void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w);
void refreshNodeList();
void sendSysInfoUDP();
//util.cpp
//bool oappend(const char* txt); // append new c string to temp buffer efficiently
//bool oappendi(int i); // append new number to temp buffer efficiently
//void sappend(char stype, const char* key, int val);
//void sappends(char stype, const char* key, char* val);
//void prepareHostname(char* hostname);
//void _setRandomColor(bool _sec, bool fromButton);
//bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t module=255);
void releaseJSONBufferLock();
//um_manager.cpp
class Usermod {
public:

View File

@@ -42,7 +42,7 @@ function B(){window.history.back()}function U(){document.getElementById("uf").st
.bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none}
</style></head><body><h2>WLED Software Update</h2><form method="POST"
action="/update" id="uf" enctype="multipart/form-data" onsubmit="U()">
Installed version: 0.13.0-b5<br>Download the latest binary: <a
Installed version: 0.13.0-b6<br>Download the latest binary: <a
href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img
src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square">
</a><br><input type="file" class="bt" name="update" required><br><input

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -189,7 +189,7 @@ void sendImprovInfoResponse() {
out[11] = 4; //Firmware len ("WLED")
out[12] = 'W'; out[13] = 'L'; out[14] = 'E'; out[15] = 'D';
uint8_t lengthSum = 17;
uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.13.0-b5/%i"),VERSION);
uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.13.0-b6/%i"),VERSION);
out[16] = vlen; lengthSum += vlen;
uint8_t hlen = 7;
#ifdef ESP8266

View File

@@ -165,6 +165,7 @@ void decodeIR(uint32_t code)
if (decodeIRCustom(code)) return;
if (irEnabled == 8) { // any remote configurable with ir.json file
decodeIRJson(code);
colorUpdated(CALL_MODE_BUTTON);
return;
}
if (code > 0xFFFFFF) return; //invalid code
@@ -566,25 +567,33 @@ Sample:
void decodeIRJson(uint32_t code)
{
char objKey[10];
const char* cmd;
String cmdStr;
DynamicJsonDocument irDoc(JSON_BUFFER_SIZE);
JsonObject fdo;
JsonObject jsonCmdObj;
sprintf(objKey, "\"0x%X\":", code);
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(13)) return;
#endif
readObjectFromFile("/ir.json", objKey, &irDoc);
fdo = irDoc.as<JsonObject>();
sprintf_P(objKey, PSTR("\"0x%lX\":"), (unsigned long)code);
// attempt to read command from ir.json
// this may fail for two reasons: ir.json does not exist or IR code not found
// if the IR code is not found readObjectFromFile() will clean() doc JSON document
// so we can differentiate between the two
readObjectFromFile("/ir.json", objKey, &doc);
fdo = doc.as<JsonObject>();
lastValidCode = 0;
if (fdo.isNull()) {
//the received code does not exist
if (!WLED_FS.exists("/ir.json")) errorFlag = ERR_FS_IRLOAD; //warn if IR file itself doesn't exist
releaseJSONBufferLock();
return;
}
cmd = fdo["cmd"]; //string
cmdStr = String(cmd);
cmdStr = fdo["cmd"].as<String>();
jsonCmdObj = fdo["cmd"]; //object
if (!cmdStr.isEmpty())
@@ -617,16 +626,14 @@ void decodeIRJson(uint32_t code)
if (!cmdStr.startsWith("win&")) {
cmdStr = "win&" + cmdStr;
}
handleSet(nullptr, cmdStr, false);
handleSet(nullptr, cmdStr, false);
}
colorUpdated(CALL_MODE_BUTTON);
} else if (!jsonCmdObj.isNull()) {
// command is JSON object
//allow applyPreset() to reuse JSON buffer, or it would alloc. a second buffer and run out of mem.
fileDoc = &irDoc;
deserializeState(jsonCmdObj, CALL_MODE_BUTTON);
fileDoc = nullptr;
}
releaseJSONBufferLock();
}
void initIR()
@@ -654,9 +661,8 @@ void handleIR()
{
if (results.value != 0) // only print results if anything is received ( != 0 )
{
Serial.print("IR recv\r\n0x");
Serial.println((uint32_t)results.value, HEX);
Serial.println();
if (!pinManager.isPinAllocated(1)) //GPIO 1 - Serial TX pin
Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value);
}
decodeIR(results.value);
irrecv->resume();

View File

@@ -8,6 +8,7 @@
bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255) {
if (elem.is<int>()) {
if (elem < 0) return false; //ignore e.g. {"ps":-1}
*val = elem;
return true;
} else if (elem.is<const char*>()) {
@@ -63,7 +64,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
uint16_t grp = elem["grp"] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing;
strip.setSegment(id, start, stop, grp, spc);
uint16_t of = seg.offset;
uint16_t len = 1;
if (stop > start) len = stop - start;
@@ -72,9 +73,10 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
int offsetAbs = abs(offset);
if (offsetAbs > len - 1) offsetAbs %= len;
if (offset < 0) offsetAbs = len - offsetAbs;
seg.offset = offsetAbs;
of = offsetAbs;
}
if (stop > start && seg.offset > len -1) seg.offset = len -1;
if (stop > start && of > len -1) of = len -1;
strip.setSegment(id, start, stop, grp, spc, of);
byte segbri = 0;
if (getVal(elem["bri"], &segbri)) {
@@ -86,6 +88,10 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
if (elem["on"].is<const char*>() && elem["on"].as<const char*>()[0] == 't') on = !on;
seg.setOption(SEG_OPTION_ON, on, id);
uint8_t cctPrev = seg.cct;
seg.setCCT(elem["cct"] | seg.cct, id);
if (seg.cct != cctPrev && id == strip.getMainSegmentId()) effectChanged = true; //send UDP
JsonArray colarr = elem["col"];
if (!colarr.isNull())
{
@@ -143,26 +149,28 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
//if (pal != seg.palette && pal < strip.getPaletteCount()) strip.setPalette(pal);
seg.setOption(SEG_OPTION_SELECTED, elem[F("sel")] | seg.getOption(SEG_OPTION_SELECTED));
seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED));
seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED));
seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR ));
//temporary, strip object gets updated via colorUpdated()
if (id == strip.getMainSegmentId()) {
byte effectPrev = effectCurrent;
effectCurrent = elem["fx"] | effectCurrent;
if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
byte effectPrev = effectCurrent;
if (getVal(elem["fx"], &effectCurrent, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 0-255 exact value)
if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
}
effectSpeed = elem[F("sx")] | effectSpeed;
effectIntensity = elem[F("ix")] | effectIntensity;
effectPalette = elem["pal"] | effectPalette;
getVal(elem["pal"], &effectPalette, 1, strip.getPaletteCount());
} else { //permanent
byte fx = elem["fx"] | seg.mode;
if (fx != seg.mode && fx < strip.getModeCount()) {
byte fx = seg.mode;
byte fxPrev = fx;
if (getVal(elem["fx"], &fx, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 0-255 exact value)
strip.setMode(id, fx);
if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually
if (!presetId && seg.mode != fxPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
}
seg.speed = elem[F("sx")] | seg.speed;
seg.intensity = elem[F("ix")] | seg.intensity;
seg.palette = elem["pal"] | seg.palette;
getVal(elem["pal"], &seg.palette, 1, strip.getPaletteCount());
}
JsonArray iarr = elem[F("i")]; //set individual LEDs
@@ -330,6 +338,8 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
usermods.readFromJsonState(root);
loadLedmap = root[F("ledmap")] | loadLedmap;
byte ps = root[F("psave")];
if (ps > 0) {
savePreset(ps, true, nullptr, root);
@@ -339,9 +349,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
deletePreset(ps);
}
if (getVal(root["ps"], &presetCycCurr, 1, 5)) { //load preset (clears state request!)
ps = presetCycCurr;
if (getVal(root["ps"], &ps, presetCycMin, presetCycMax)) { //load preset (clears state request!)
if (!presetId) unloadPlaylist(); //stop playlist if preset changed manually
applyPreset(presetCycCurr, callMode);
if (ps >= presetCycMin && ps <= presetCycMax) presetCycCurr = ps;
applyPreset(ps, callMode);
return stateResponse;
}
@@ -381,6 +393,7 @@ void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool fo
root["on"] = seg.getOption(SEG_OPTION_ON);
byte segbri = seg.opacity;
root["bri"] = (segbri) ? segbri : 255;
root["cct"] = seg.cct;
if (segmentBounds && seg.name != nullptr) root["n"] = reinterpret_cast<const char *>(seg.name); //not good practice, but decreases required JSON buffer
@@ -427,7 +440,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
if (!forPreset) {
if (errorFlag) root[F("error")] = errorFlag;
root[F("ps")] = (currentPreset > 0) ? currentPreset : -1;
root["ps"] = (currentPreset > 0) ? currentPreset : -1;
root[F("pl")] = currentPlaylist;
usermods.addToJsonState(root);
@@ -496,7 +509,15 @@ void serializeInfo(JsonObject root)
JsonObject leds = root.createNestedObject("leds");
leds[F("count")] = strip.getLengthTotal();
leds[F("rgbw")] = strip.isRgbw;
leds[F("wv")] = strip.isRgbw && (strip.rgbwMode == RGBW_MODE_MANUAL_ONLY || strip.rgbwMode == RGBW_MODE_DUAL); //should a white channel slider be displayed?
leds[F("wv")] = false;
leds["cct"] = correctWB || strip.hasCCTBus();
switch (Bus::getAutoWhiteMode()) {
case RGBW_MODE_MANUAL_ONLY:
case RGBW_MODE_DUAL:
if (strip.isRgbw) leds[F("wv")] = true;
break;
}
leds[F("pwr")] = strip.currentMilliamps;
leds[F("fps")] = strip.getFps();
leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0;
@@ -813,23 +834,29 @@ void serveJson(AsyncWebServerRequest* request)
return;
}
#ifdef WLED_USE_DYNAMIC_JSON
AsyncJsonResponse* response = new AsyncJsonResponse(JSON_BUFFER_SIZE);
JsonObject doc = response->getRoot();
#else
if (!requestJSONBufferLock(17)) return;
AsyncJsonResponse *response = new AsyncJsonResponse(&doc);
#endif
JsonObject lDoc = response->getRoot();
switch (subJson)
{
case 1: //state
serializeState(doc); break;
serializeState(lDoc); break;
case 2: //info
serializeInfo(doc); break;
serializeInfo(lDoc); break;
case 4: //node list
serializeNodes(doc); break;
serializeNodes(lDoc); break;
case 5: //palettes
serializePalettes(doc, request); break;
serializePalettes(lDoc, request); break;
default: //all
JsonObject state = doc.createNestedObject("state");
JsonObject state = lDoc.createNestedObject("state");
serializeState(state);
JsonObject info = doc.createNestedObject("info");
JsonObject info = lDoc.createNestedObject("info");
serializeInfo(info);
if (subJson != 3)
{
@@ -839,10 +866,11 @@ void serveJson(AsyncWebServerRequest* request)
}
DEBUG_PRINT("JSON buffer size: ");
DEBUG_PRINTLN(doc.memoryUsage());
DEBUG_PRINTLN(lDoc.memoryUsage());
response->setLength();
request->send(response);
releaseJSONBufferLock();
}
#define MAX_LIVE_LEDS 180

View File

@@ -30,7 +30,6 @@ void toggleOnOff()
{
briLast = bri;
bri = 0;
unloadPlaylist();
}
}
@@ -38,25 +37,15 @@ void toggleOnOff()
//scales the brightness with the briMultiplier factor
byte scaledBri(byte in)
{
uint32_t d = in*briMultiplier;
uint32_t val = d/100;
uint16_t val = ((uint16_t)in*briMultiplier)/100;
if (val > 255) val = 255;
return (byte)val;
}
void setAllLeds() {
if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY)
{
colorRGBtoRGBW(col);
colorRGBtoRGBW(colSec);
}
strip.setColor(0, col[0], col[1], col[2], col[3]);
strip.setColor(1, colSec[0], colSec[1], colSec[2], colSec[3]);
if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY)
{
col[3] = 0; colSec[3] = 0;
}
if (!realtimeMode || !arlsForceMaxBri)
{
strip.setBrightness(scaledBri(briT));
@@ -190,8 +179,10 @@ void updateInterfaces(uint8_t callMode)
espalexaDevice->setColor(col[0], col[1], col[2]);
}
#endif
#ifndef WLED_DISABLE_BLYNK
if (callMode != CALL_MODE_BLYNK &&
callMode != CALL_MODE_NO_NOTIFY) updateBlynk();
#endif
doPublishMqtt = true;
lastInterfaceUpdate = millis();
}
@@ -285,7 +276,9 @@ void handleNightlight()
setLedsStandard();
}
}
#ifndef WLED_DISABLE_BLYNK
updateBlynk();
#endif
if (macroNl > 0)
applyPreset(macroNl);
nightlightActiveOld = false;

View File

@@ -91,11 +91,14 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
colorUpdated(CALL_MODE_DIRECT_CHANGE);
} else if (strcmp_P(topic, PSTR("/api")) == 0) {
if (payload[0] == '{') { //JSON API
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(15)) return;
#endif
deserializeJson(doc, payloadStr);
fileDoc = &doc;
deserializeState(doc.as<JsonObject>());
fileDoc = nullptr;
releaseJSONBufferLock();
} else { //HTTP API
String apireq = "win&";
apireq += (char*)payloadStr;
@@ -124,22 +127,22 @@ void publishMqtt()
sprintf_P(s, PSTR("%u"), bri);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/g"));
mqtt->publish(subuf, 0, true, s);
mqtt->publish(subuf, 0, true, s); // retain message
sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2]));
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/c"));
mqtt->publish(subuf, 0, true, s);
mqtt->publish(subuf, 0, true, s); // retain message
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/status"));
mqtt->publish(subuf, 0, true, "online");
mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT
char apires[1024];
char apires[1024]; // allocating 1024 bytes from stack can be risky
XML_response(nullptr, apires);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/v"));
mqtt->publish(subuf, 0, false, apires);
mqtt->publish(subuf, 0, false, apires); // do not retain message
}
@@ -169,7 +172,7 @@ bool initMqtt()
strlcpy(mqttStatusTopic, mqttDeviceTopic, 33);
strcat_P(mqttStatusTopic, PSTR("/status"));
mqtt->setWill(mqttStatusTopic, 0, true, "offline");
mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message
mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME);
mqtt->connect();
return true;

View File

@@ -125,6 +125,11 @@ bool PinManagerClass::isPinOk(byte gpio, bool output)
return false;
}
PinOwner PinManagerClass::getPinOwner(byte gpio) {
if (!isPinOk(gpio, false)) return PinOwner::None;
return ownerTag[gpio];
}
#ifdef ARDUINO_ARCH_ESP32
byte PinManagerClass::allocateLedc(byte channels)
{

View File

@@ -92,6 +92,8 @@ class PinManagerClass {
bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None);
bool isPinOk(byte gpio, bool output = true);
PinOwner getPinOwner(byte gpio);
#ifdef ARDUINO_ARCH_ESP32
byte allocateLedc(byte channels);
void deallocateLedc(byte pos, byte channels);

View File

@@ -118,6 +118,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
void handlePlaylist() {
static unsigned long presetCycledTime = 0;
if (currentPlaylist < 0 || playlistEntries == nullptr) return;
if (millis() - presetCycledTime > (100*playlistEntryDur)) {

View File

@@ -7,8 +7,11 @@
bool applyPreset(byte index, byte callMode)
{
if (index == 0) return false;
const char *filename = index < 255 ? "/presets.json" : "/tmp.json";
if (fileDoc) {
errorFlag = readObjectFromFileUsingId("/presets.json", index, fileDoc) ? ERR_NONE : ERR_FS_PLOAD;
errorFlag = readObjectFromFileUsingId(filename, index, fileDoc) ? ERR_NONE : ERR_FS_PLOAD;
JsonObject fdo = fileDoc->as<JsonObject>();
if (fdo["ps"] == index) fdo.remove("ps"); //remove load request for same presets to prevent recursive crash
#ifdef WLED_DEBUG_FS
@@ -17,41 +20,53 @@ bool applyPreset(byte index, byte callMode)
deserializeState(fdo, callMode, index);
} else {
DEBUGFS_PRINTLN(F("Make read buf"));
DynamicJsonDocument fDoc(JSON_BUFFER_SIZE);
errorFlag = readObjectFromFileUsingId("/presets.json", index, &fDoc) ? ERR_NONE : ERR_FS_PLOAD;
JsonObject fdo = fDoc.as<JsonObject>();
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(9)) return false;
#endif
errorFlag = readObjectFromFileUsingId(filename, index, &doc) ? ERR_NONE : ERR_FS_PLOAD;
JsonObject fdo = doc.as<JsonObject>();
if (fdo["ps"] == index) fdo.remove("ps");
#ifdef WLED_DEBUG_FS
serializeJson(fDoc, Serial);
serializeJson(doc, Serial);
#endif
deserializeState(fdo, callMode, index);
releaseJSONBufferLock();
}
if (!errorFlag) {
currentPreset = index;
if (index < 255) currentPreset = index;
return true;
}
return false;
}
//persist=false is not currently honored
void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj)
{
if (index == 0 || index > 250) return;
bool docAlloc = (fileDoc != nullptr);
if (index == 0 || (index > 250 && persist) || (index<255 && !persist)) return;
JsonObject sObj = saveobj;
if (!docAlloc) {
const char *filename = persist ? "/presets.json" : "/tmp.json";
if (!fileDoc) {
DEBUGFS_PRINTLN(F("Allocating saving buffer"));
DynamicJsonDocument lDoc(JSON_BUFFER_SIZE);
sObj = lDoc.to<JsonObject>();
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(10)) return;
#endif
sObj = doc.to<JsonObject>();
if (pname) sObj["n"] = pname;
DEBUGFS_PRINTLN(F("Save current state"));
serializeState(sObj, true);
currentPreset = index;
if (persist) currentPreset = index;
writeObjectToFileUsingId("/presets.json", index, &lDoc);
} else { //from JSON API
writeObjectToFileUsingId(filename, index, &doc);
releaseJSONBufferLock();
} else { //from JSON API (fileDoc != nullptr)
DEBUGFS_PRINTLN(F("Reuse recv buffer"));
sObj.remove(F("psave"));
sObj.remove(F("v"));
@@ -59,7 +74,7 @@ void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj)
if (!sObj["o"]) {
DEBUGFS_PRINTLN(F("Save current state"));
serializeState(sObj, true, sObj["ib"], sObj["sb"]);
currentPreset = index;
if (persist) currentPreset = index;
}
sObj.remove("o");
sObj.remove("ib");
@@ -67,9 +82,9 @@ void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj)
sObj.remove(F("error"));
sObj.remove(F("time"));
writeObjectToFileUsingId("/presets.json", index, fileDoc);
writeObjectToFileUsingId(filename, index, fileDoc);
}
presetsModifiedTime = toki.second(); //unix time
if (persist) presetsModifiedTime = toki.second(); //unix time
updateFSInfo();
}

View File

@@ -95,6 +95,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
uint8_t pins[5] = {255, 255, 255, 255, 255};
autoSegments = request->hasArg(F("MS"));
correctWB = request->hasArg(F("CCT"));
cctFromRgb = request->hasArg(F("CR"));
strip.cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(strip.cctBlending);
Bus::setAutoWhiteMode(request->arg(F("AW")).toInt());
for (uint8_t s = 0; s < WLED_MAX_BUSSES; s++) {
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
@@ -116,7 +121,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
type = request->arg(lt).toInt();
type |= request->hasArg(rf) << 7; // off refresh override
skip = request->hasArg(sl) ? LED_SKIP_AMOUNT : 0;
colorOrder = request->arg(co).toInt();
start = (request->hasArg(ls)) ? request->arg(ls).toInt() : t;
if (request->hasArg(lc) && request->arg(lc).toInt() > 0) {
@@ -166,8 +170,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strip.ablMilliampsMax = request->arg(F("MA")).toInt();
strip.milliampsPerLed = request->arg(F("LA")).toInt();
strip.rgbwMode = request->arg(F("AW")).toInt();
briS = request->arg(F("CA")).toInt();
turnOnAtBoot = request->hasArg(F("BO"));
@@ -431,7 +433,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
//USERMODS
if (subPage == 8)
{
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(5)) return;
#endif
JsonObject um = doc.createNestedObject("um");
size_t args = request->args();
@@ -506,6 +513,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
usermods.readFromConfig(um); // force change of usermod parameters
}
releaseJSONBufferLock();
if (subPage != 2 && (subPage != 6 || !doReboot)) serializeConfig(); //do not save if factory reset or LED settings (which are saved after LED re-init)
if (subPage == 4) alexaInit();
}
@@ -547,6 +556,7 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv)
const char* str2 = strchr(str,'~'); //min/max range (for preset cycle, e.g. "1~5~")
if (str2) {
byte p2 = atoi(str2+1);
presetCycMin = p1; presetCycMax = p2;
while (isdigit((str2+1)[0])) str2++;
parseNumber(str2+1, val, p1, p2);
} else {
@@ -655,17 +665,15 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("PS=")); //saves current in preset
if (pos > 0) savePreset(getNumVal(&req, pos));
byte presetCycleMin = 1;
byte presetCycleMax = 5;
pos = req.indexOf(F("P1=")); //sets first preset for cycle
if (pos > 0) presetCycleMin = getNumVal(&req, pos);
if (pos > 0) presetCycMin = getNumVal(&req, pos);
pos = req.indexOf(F("P2=")); //sets last preset for cycle
if (pos > 0) presetCycleMax = getNumVal(&req, pos);
if (pos > 0) presetCycMax = getNumVal(&req, pos);
//apply preset
if (updateVal(&req, "PL=", &presetCycCurr, presetCycleMin, presetCycleMax)) {
if (updateVal(&req, "PL=", &presetCycCurr, presetCycMin, presetCycMax)) {
unloadPlaylist();
applyPreset(presetCycCurr);
}

View File

@@ -22,7 +22,7 @@ private:
ColorCallbackFunction _callbackCol = nullptr;
uint8_t _val, _val_last, _sat = 0;
uint16_t _hue = 0, _ct = 0;
float _x = 0.5, _y = 0.5;
float _x = 0.5f, _y = 0.5f;
uint32_t _rgb = 0;
uint8_t _id = 0;
EspalexaDeviceType _type;

View File

@@ -64,6 +64,15 @@ class AsyncJsonResponse: public AsyncAbstractResponse {
public:
AsyncJsonResponse(JsonDocument *ref, bool isArray=false) : _jsonBuffer(1), _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
if(isArray)
_root = ref->to<JsonArray>();
else
_root = ref->to<JsonObject>();
}
AsyncJsonResponse(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE, bool isArray=false) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
@@ -84,7 +93,7 @@ class AsyncJsonResponse: public AsyncAbstractResponse {
return _contentLength;
}
size_t getSize() { return _jsonBuffer.size(); }
size_t getSize() { return _root.size(); }
size_t _fillBuffer(uint8_t *data, size_t len){
ChunkPrint dest(data, _sentLength, len);

View File

@@ -4,7 +4,7 @@
* UDP sync notifier / Realtime / Hyperion / TPM2.NET
*/
#define WLEDPACKETSIZE 37
#define WLEDPACKETSIZE 39
#define UDP_IN_MAXSIZE 1472
#define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times
@@ -25,6 +25,7 @@ void notify(byte callMode, bool followUp)
default: return;
}
byte udpOut[WLEDPACKETSIZE];
WS2812FX::Segment& mainseg = strip.getSegment(strip.getMainSegmentId());
udpOut[0] = 0; //0: wled notifier protocol 1: WARLS protocol
udpOut[1] = callMode;
udpOut[2] = bri;
@@ -40,8 +41,8 @@ void notify(byte callMode, bool followUp)
//0: old 1: supports white 2: supports secondary color
//3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette
//6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet
//9: supports sync groups, 37 byte packet
udpOut[11] = 9;
//9: supports sync groups, 37 byte packet 10: supports CCT, 39 byte packet
udpOut[11] = 10;
udpOut[12] = colSec[0];
udpOut[13] = colSec[1];
udpOut[14] = colSec[2];
@@ -50,7 +51,7 @@ void notify(byte callMode, bool followUp)
udpOut[17] = (transitionDelay >> 0) & 0xFF;
udpOut[18] = (transitionDelay >> 8) & 0xFF;
udpOut[19] = effectPalette;
uint32_t colTer = strip.getSegment(strip.getMainSegmentId()).colors[2];
uint32_t colTer = mainseg.colors[2];
udpOut[20] = (colTer >> 16) & 0xFF;
udpOut[21] = (colTer >> 8) & 0xFF;
udpOut[22] = (colTer >> 0) & 0xFF;
@@ -77,6 +78,11 @@ void notify(byte callMode, bool followUp)
//sync groups
udpOut[36] = syncGroups;
//Might be changed to Kelvin in the future, receiver code should handle that case
//0: byte 38 contains 0-255 value, 255: no valid CCT, 1-254: Kelvin value MSB
udpOut[37] = strip.hasCCTBus() ? 0 : 255; //check this is 0 for the next value to be significant
udpOut[38] = mainseg.cct;
IPAddress broadcastIp;
broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP());
@@ -260,6 +266,14 @@ void handleNotifications()
{
strip.setColor(2, udpIn[20], udpIn[21], udpIn[22], udpIn[23]); //tertiary color
}
if (version > 9 && version < 200 && udpIn[37] < 255) { //valid CCT/Kelvin value
uint8_t cct = udpIn[38];
if (udpIn[37] > 0) { //Kelvin
cct = (((udpIn[37] << 8) + udpIn[38]) - 1900) >> 5;
}
uint8_t segid = strip.getMainSegmentId();
strip.getSegment(segid).setCCT(cct, segid);
}
}
}

33
wled00/util.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include "wled.h"
#include "fcn_declare.h"
#include "const.h"
//threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994
bool requestJSONBufferLock(uint8_t module)
{
unsigned long now = millis();
while (jsonBufferLock && millis()-now < 1000) delay(1); // wait for a second for buffer lock
if (millis()-now >= 1000) {
DEBUG_PRINT(F("ERROR: Locking JSON buffer failed! ("));
DEBUG_PRINT(jsonBufferLock);
DEBUG_PRINTLN(")");
return false; // waiting time-outed
}
jsonBufferLock = module ? module : 255;
fileDoc = &doc; // used for applying presets (presets.cpp)
doc.clear();
return true;
}
void releaseJSONBufferLock()
{
DEBUG_PRINT(F("JSON buffer released. ("));
DEBUG_PRINT(jsonBufferLock);
DEBUG_PRINTLN(")");
fileDoc = nullptr;
jsonBufferLock = 0;
}

View File

@@ -221,11 +221,16 @@ void WLED::loop()
delete busConfigs[i]; busConfigs[i] = nullptr;
}
strip.finalizeInit();
loadLedmap = 0;
if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments();
yield();
serializeConfig();
}
if (loadLedmap >= 0) {
strip.deserializeMap(loadLedmap);
loadLedmap = -1;
}
yield();
handleWs();
@@ -351,7 +356,9 @@ void WLED::setup()
#endif
#ifdef WLED_ENABLE_ADALIGHT
if (!pinManager.isPinAllocated(3)) {
//Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused
//Serial TX (Debug, Improv, Serial JSON) only possible if GPIO1 unused
if (!pinManager.isPinAllocated(3) && !pinManager.isPinAllocated(1)) {
Serial.println(F("Ada"));
}
#endif
@@ -407,6 +414,7 @@ void WLED::beginStrip()
{
// Initialize NeoPixel Strip and button
strip.finalizeInit(); // busses created during deserializeConfig()
strip.deserializeMap();
strip.makeAutoSegments();
strip.setBrightness(0);
strip.setShowCallback(handleOverlayDraw);

View File

@@ -3,12 +3,12 @@
/*
Main sketch, global variable declarations
@title WLED project sketch
@version 0.13.0-b5
@version 0.13.0-b6
@author Christian Schwinne
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2111170
#define VERSION 2112080
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -31,7 +31,7 @@
#ifndef WLED_DISABLE_MQTT
#define WLED_ENABLE_MQTT // saves 12kb
#endif
#define WLED_ENABLE_ADALIGHT // saves 500b only
#define WLED_ENABLE_ADALIGHT // saves 500b only (uses GPIO3 (RX) for serial)
//#define WLED_ENABLE_DMX // uses 3.5kb (use LEDPIN other than 2)
#ifndef WLED_DISABLE_LOXONE
#define WLED_ENABLE_LOXONE // uses 1.2kb
@@ -269,6 +269,8 @@ WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load
//if false, only one segment spanning the total LEDs is created,
//but not on LED settings save if there is more than one segment currently
WLED_GLOBAL bool autoSegments _INIT(false);
WLED_GLOBAL bool correctWB _INIT(false); //CCT color correction of RGB color
WLED_GLOBAL bool cctFromRgb _INIT(false); //CCT is calculated from RGB instead of using seg.cct
WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color.
WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color
@@ -511,15 +513,16 @@ WLED_GLOBAL byte improvActive _INIT(0); //0: no improv packet received, 1: impro
WLED_GLOBAL byte improvError _INIT(0);
//playlists
WLED_GLOBAL unsigned long presetCycledTime _INIT(0);
WLED_GLOBAL int16_t currentPlaylist _INIT(-1);
//still used for "PL=~" HTTP API command
WLED_GLOBAL byte presetCycCurr _INIT(0);
WLED_GLOBAL byte presetCycMin _INIT(1);
WLED_GLOBAL byte presetCycMax _INIT(5);
// realtime
WLED_GLOBAL byte realtimeMode _INIT(REALTIME_MODE_INACTIVE);
WLED_GLOBAL byte realtimeOverride _INIT(REALTIME_OVERRIDE_NONE);
WLED_GLOBAL IPAddress realtimeIP _INIT_N(((0, 0, 0, 0)));;
WLED_GLOBAL IPAddress realtimeIP _INIT_N(((0, 0, 0, 0)));
WLED_GLOBAL unsigned long realtimeTimeout _INIT(0);
WLED_GLOBAL uint8_t tpmPacketCount _INIT(0);
WLED_GLOBAL uint16_t tpmPayloadFrameSize _INIT(0);
@@ -596,10 +599,17 @@ WLED_GLOBAL BusManager busses _INIT(BusManager());
WLED_GLOBAL WS2812FX strip _INIT(WS2812FX());
WLED_GLOBAL BusConfig* busConfigs[WLED_MAX_BUSSES] _INIT({nullptr}); //temporary, to remember values from network callback until after
WLED_GLOBAL bool doInitBusses _INIT(false);
WLED_GLOBAL int8_t loadLedmap _INIT(-1);
// Usermod manager
WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
#ifndef WLED_USE_DYNAMIC_JSON
// global ArduinoJson buffer
WLED_GLOBAL StaticJsonDocument<JSON_BUFFER_SIZE> doc;
#endif
WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0);
// enable additional debug output
#ifdef WLED_DEBUG
#ifndef ESP8266
@@ -640,6 +650,13 @@ WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0)
#define WLED_MQTT_CONNECTED (mqtt != nullptr && mqtt->connected())
//color mangling macros
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
#define R(c) (byte((c) >> 16))
#define G(c) (byte((c) >> 8))
#define B(c) (byte(c))
#define W(c) (byte((c) >> 24))
// append new c string to temp buffer efficiently
bool oappend(const char* txt);
// append new number to temp buffer efficiently

View File

@@ -329,7 +329,7 @@ void loadSettingsFromEEPROM()
receiveDirect = !EEPROM.read(2200);
notifyMacro = EEPROM.read(2201);
strip.rgbwMode = EEPROM.read(2203);
//strip.rgbwMode = EEPROM.read(2203);
//skipFirstLed = EEPROM.read(2204);
bootPreset = EEPROM.read(389);
@@ -382,8 +382,13 @@ void deEEP() {
DEBUG_PRINTLN(F("Preset file not found, attempting to load from EEPROM"));
DEBUGFS_PRINTLN(F("Allocating saving buffer for dEEP"));
DynamicJsonDocument dDoc(JSON_BUFFER_SIZE *2);
JsonObject sObj = dDoc.to<JsonObject>();
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(8)) return;
#endif
JsonObject sObj = doc.to<JsonObject>();
sObj.createNestedObject("0");
EEPROM.begin(EEPSIZE);
@@ -442,8 +447,6 @@ void deEEP() {
}
}
for (uint16_t index = 1; index <= 16; index++) { //copy macros to presets.json
char m[65];
readStringFromEEPROM(1024+64*(index-1), m, 64);
@@ -463,10 +466,14 @@ void deEEP() {
File f = WLED_FS.open("/presets.json", "w");
if (!f) {
errorFlag = ERR_FS_GENERAL;
releaseJSONBufferLock();
return;
}
serializeJson(dDoc, f);
serializeJson(doc, f);
f.close();
releaseJSONBufferLock();
DEBUG_PRINTLN(F("deEEP complete!"));
}

View File

@@ -48,18 +48,21 @@ void handleSerial()
Serial.print("WLED"); Serial.write(' '); Serial.println(VERSION);
} else if (next == '{') { //JSON API
bool verboseResponse = false;
{
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
Serial.setTimeout(100);
DeserializationError error = deserializeJson(doc, Serial);
if (error) return;
fileDoc = &doc;
verboseResponse = deserializeState(doc.as<JsonObject>());
fileDoc = nullptr;
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(16)) return;
#endif
Serial.setTimeout(100);
DeserializationError error = deserializeJson(doc, Serial);
if (error) {
releaseJSONBufferLock();
return;
}
verboseResponse = deserializeState(doc.as<JsonObject>());
//only send response if TX pin is unused for other purposes
if (verboseResponse && !pinManager.isPinAllocated(1)) {
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
doc.clear();
JsonObject state = doc.createNestedObject("state");
serializeState(state);
JsonObject info = doc.createNestedObject("info");
@@ -68,6 +71,7 @@ void handleSerial()
serializeJson(doc, Serial);
Serial.println();
}
releaseJSONBufferLock();
}
break;
case AdaState::Header_d:

View File

@@ -106,11 +106,18 @@ void initServer()
bool verboseResponse = false;
bool isConfig = false;
{ //scope JsonDocument so it releases its buffer
DynamicJsonDocument jsonBuffer(JSON_BUFFER_SIZE);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
JsonObject root = jsonBuffer.as<JsonObject>();
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(14)) return;
#endif
DeserializationError error = deserializeJson(doc, (uint8_t*)(request->_tempObject));
JsonObject root = doc.as<JsonObject>();
if (error || root.isNull()) {
request->send(400, "application/json", F("{\"error\":9}")); return;
releaseJSONBufferLock();
request->send(400, "application/json", F("{\"error\":9}"));
return;
}
const String& url = request->url();
isConfig = url.indexOf("cfg") > -1;
@@ -120,12 +127,11 @@ void initServer()
serializeJson(root,Serial);
DEBUG_PRINTLN();
#endif
fileDoc = &jsonBuffer; // used for applying presets (presets.cpp)
verboseResponse = deserializeState(root);
fileDoc = nullptr;
} else {
verboseResponse = deserializeConfig(root); //use verboseResponse to determine whether cfg change should be saved immediately
}
releaseJSONBufferLock();
}
if (verboseResponse) {
if (!isConfig) {

View File

@@ -34,11 +34,18 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
}
bool verboseResponse = false;
{ //scope JsonDocument so it releases its buffer
DynamicJsonDocument jsonBuffer(JSON_BUFFER_SIZE);
DeserializationError error = deserializeJson(jsonBuffer, data, len);
JsonObject root = jsonBuffer.as<JsonObject>();
if (error || root.isNull()) return;
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(11)) return;
#endif
DeserializationError error = deserializeJson(doc, data, len);
JsonObject root = doc.as<JsonObject>();
if (error || root.isNull()) {
releaseJSONBufferLock();
return;
}
if (root["v"] && root.size() == 1) {
//if the received value is just "{"v":true}", send only to this client
verboseResponse = true;
@@ -46,14 +53,13 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
{
wsLiveClientId = root["lv"] ? client->id() : 0;
} else {
fileDoc = &jsonBuffer;
verboseResponse = deserializeState(root);
fileDoc = nullptr;
if (!interfaceUpdateCallMode) {
//special case, only on playlist load, avoid sending twice in rapid succession
if (millis() - lastInterfaceUpdate > 1700) verboseResponse = false;
}
}
releaseJSONBufferLock(); // will clean fileDoc
}
//update if it takes longer than 300ms until next "broadcast"
if (verboseResponse && (millis() - lastInterfaceUpdate < 1700 || !interfaceUpdateCallMode)) sendDataWs(client);
@@ -92,16 +98,23 @@ void sendDataWs(AsyncWebSocketClient * client)
AsyncWebSocketMessageBuffer * buffer;
{ //scope JsonDocument so it releases its buffer
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(12)) return;
#endif
JsonObject state = doc.createNestedObject("state");
serializeState(state);
JsonObject info = doc.createNestedObject("info");
serializeInfo(info);
size_t len = measureJson(doc);
buffer = ws.makeBuffer(len);
if (!buffer) return; //out of memory
if (!buffer) {
releaseJSONBufferLock();
return; //out of memory
}
serializeJson(doc, (char *)buffer->get(), len +1);
releaseJSONBufferLock();
}
if (client) {
client->text(buffer);

View File

@@ -316,13 +316,22 @@ void getSettingsJS(byte subPage, char* dest)
{
char nS[8];
// Pin reservations will become unnecessary when settings pages will read cfg.json directly
// add reserved and usermod pins as d.um_p array
oappend(SET_F("d.um_p=[6,7,8,9,10,11"));
DynamicJsonDocument doc(JSON_BUFFER_SIZE/2);
{ // scope so buffer can be released earlier
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(3072);
#else
if (!requestJSONBufferLock(6)) return;
#endif
JsonObject mods = doc.createNestedObject(F("um"));
usermods.addToConfig(mods);
if (!mods.isNull()) fillUMPins(mods);
releaseJSONBufferLock();
}
#ifdef WLED_ENABLE_DMX
oappend(SET_F(",2")); // DMX hardcoded pin
@@ -370,9 +379,14 @@ void getSettingsJS(byte subPage, char* dest)
oappend(SET_F(");"));
sappend('c',SET_F("MS"),autoSegments);
sappend('c',SET_F("CCT"),correctWB);
sappend('c',SET_F("CR"),cctFromRgb);
sappend('v',SET_F("CB"),strip.cctBlending);
sappend('v',SET_F("AW"),Bus::getAutoWhiteMode());
for (uint8_t s=0; s < busses.getNumBusses(); s++) {
Bus* bus = busses.getBus(s);
if (bus == nullptr) continue;
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = 48+s; co[3] = 0; //strip color order
@@ -407,7 +421,6 @@ void getSettingsJS(byte subPage, char* dest)
}
sappend('v',SET_F("CA"),briS);
sappend('v',SET_F("AW"),strip.rgbwMode);
sappend('c',SET_F("BO"),turnOnAtBoot);
sappend('v',SET_F("BP"),bootPreset);