Compare commits

...

97 Commits

Author SHA1 Message Date
cschwinne
102a28aef4 Release of WLED v0.13.3 2022-08-23 01:26:18 +02:00
cschwinne
cade1800f4 Update python dependencies 2022-08-21 12:50:52 +02:00
Blaz Kristan
8176f1141e Merge branch 'Sousanator-master'
Added LPD6803 chip support.
2022-08-16 20:35:57 +02:00
Blaz Kristan
515827c745 Merge branch 'master' of https://github.com/Sousanator/WLED into Sousanator-master 2022-08-16 20:35:17 +02:00
cschwinne
420f858d9b Release of WLED v0.13.2 2022-08-15 02:08:48 +02:00
Blaž Kristan
902c11d074 Merge pull request #2657 from poelzi/watchdog
Enable ESP watchdog by default
2022-07-22 09:26:46 +02:00
Blaž Kristan
38330b735c Merge pull request #2723 from albarlow/BME280-Enhancements
BME280 Usermod Enhancements
2022-07-21 18:42:19 +02:00
Blaz Kristan
bda3c4ab7a Minor optimisations. 2022-07-21 18:38:07 +02:00
Blaz Kristan
d8d01ac353 Merge branch 'BME280-Enhancements' of https://github.com/albarlow/WLED into BME280-Enhancements 2022-07-21 18:37:17 +02:00
dependabot[bot]
51d935f419 Bump terser from 4.8.0 to 4.8.1 (#2726)
Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-21 11:24:54 +02:00
albarlow
c96f83b076 Registered Usermod and Pins
Implemented PinManager and rerun setup of usermod after updating pins/

Registered in const.h and pin_manager.h   I tried to follow the existing formatting/numbering in these files.

Wrapped any strings I could in F()
2022-07-21 09:48:37 +01:00
albarlow
7308f5993c Added changelog 2022-07-20 22:53:45 +01:00
albarlow
22ac12dc36 Remove compile-time variables and revamp readme.md 2022-07-20 13:06:27 +01:00
albarlow
866296fefd Update usermod bme280 rounding
Apply rounding per usermod settings
2022-07-19 22:15:26 +01:00
albarlow
9d574397bc usermod bme280
Added public variables to the BME280 usermod based on those in the Temperature usermod.  Only complication is that this usermod utilises different function calls depending on whether user defines celsius or not.  I have handled this for the temperature, but the Dew Point and Heat Index are relative to the temperature.

I've also addressed some areas where I'd previously assumed Celsius for reporting purposes as my test case is using Farenheit.
2022-07-19 21:47:56 +01:00
albarlow
bee48dae7e Update readme 2022-07-19 14:52:39 +01:00
albarlow
e12f7b67e5 Usermod BME280 Enhancements
I added a Usermod interface for key settings.  I used a PinArray for the SDA/SCL pins, but you can't name these individually.

I have also made a display to show the temperature/humidity values in the web interface's Info screen.

I had to change the definition of those items in order to allow these new functions to work.  I have not noticed any negative side effects to this change.

At the moment, I've not figured out how to make Celsius/Farenheit toggleable due to the way the #define setup works.

Finally, I have added a routine to publish MQTT Discovery Topics for Home Assistant (toggleable in the Usermod screen).

I've been testing this on the only suitable device I have for a few months and haven't noticed any problems.
2022-07-19 14:52:10 +01:00
Blaz Kristan
a8908238d5 Prevent race condition when saving bus config.
(loop() is executed on different core than handleSet())
2022-07-02 14:28:09 +02:00
Blaž Kristan
fd4c0e795a Merge pull request #2693 from softhack007/analogread_smoothing_fix_2587
noise filter for potentiometer reading - fix for issue #2587
2022-06-22 13:20:16 +02:00
Frank
c79eb43347 disabled second check for strip.isUpdating()
commented out the second `strip.isUpdating()` check, because it should not be neccesary; Strip.service() is called after handleIO()/handleButton().
2022-06-22 12:36:47 +02:00
Blaž Kristan
860e74bffa Comment & float constant. 2022-06-22 09:58:21 +02:00
Frank
ed374684a6 Update button.cpp
overlooked one
2022-06-20 22:00:23 +02:00
Frank
169a46c38c button.cpp: marked literal constant as "float! 2022-06-20 21:56:16 +02:00
Frank
1dbea434a3 fix for issue #2587 2022-06-20 16:04:43 +02:00
dependabot[bot]
0dd12cf0a6 Bump bottle from 0.12.19 to 0.12.20 (#2683)
Bumps [bottle](https://github.com/bottlepy/bottle) from 0.12.19 to 0.12.20.
- [Release notes](https://github.com/bottlepy/bottle/releases)
- [Changelog](https://github.com/bottlepy/bottle/blob/master/docs/changelog.rst)
- [Commits](https://github.com/bottlepy/bottle/compare/0.12.19...0.12.20)

---
updated-dependencies:
- dependency-name: bottle
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-16 10:31:14 +02:00
Blaž Kristan
19c8b4fe2d Merge pull request #2665 from Stoom/segment_bri_fix
🐛 fix(json): allow for using `~-16` or `~16` when setting a segments brightness though the JSON api
2022-05-29 11:02:59 +02:00
Daniel Poelzleithner
26fa38d052 Watchdog: disable watchdog while OTA is running 2022-05-23 22:30:13 +00:00
Jamie Stoom
db8e1dec3e 🐛 fix(json): allow for using ~-16 or ~16 when setting a segments brightness though the JSON api 2022-05-09 18:06:45 -07:00
Daniel Poelzleithner
213e3e998a Enable ESP watchdog by default
Use the ESP watchdog to detect hanging ESP and reset the firmware.
Can be disable by defining WLED_WATCHDOG_TIMOUT 0
Default timeout is 3 seconds on ESP32 and 8 seconds on ESP2688
2022-05-05 11:33:00 +00:00
Luke Plassman
bef9c68f81 Working DMX Libraries (#2652)
* added SparkFunDMX library dependencies

* changed variable names to avoid conflicts with SparkFunDMX library

* board version checks

* minor changes to DMX

* fix brightness when no shutter DMX channel is set

* fix compile issue on newer ESP32 variants
2022-05-05 02:28:09 +02:00
ChuckMash
099d2fd03d WiZ Lights usermod - Adding more options and features (#2638)
* Update wizlights.h

adds new features and options for wizlights usermod

* Update wizlights.h

Change how IPs are numbered.
Non-programmers incorrectly start counting at 1

* Update wizlights.h

updated default cold white enhanced white setting to a lower value.

* Update wizlights.h

added logic for connection check before UDP sending.
Seems more important for ESP32

* Update readme.md
2022-05-03 12:18:21 +02:00
Blaz Kristan
23d39e5366 Compile time options for Multi Relay & PWM Fan 2022-04-29 09:56:48 +02:00
Thomas
1a513c7bbf wled.cpp: Wrap Serial calls in #ifdef WLED_ENABLE_ADALIGHT. (#2630)
handleImprovPacket() unconditionally gobbles serial data which a problem
if we want another feature to consume it. This patch uses the same guard
as the existing call in `handleSerial()`.

Co-authored-by: Thomas Fubar <FUB4R@users.noreply.github.com>
2022-04-17 00:08:27 +02:00
ChuckMash
d1f76042e1 bugfix for outgoing serial TPM2 message length (#2628)
bugfix for outgoing serial TPM2 message length
2022-04-12 10:20:08 +02:00
Christian Bartsch
9cd8acab43 Usermod: Add support for Si7021 temperature and humidity sensors (#2617)
* added first version (work in progress)

* added some sensors to publish

* typo

* added dependency

* mqtt si7021_* names + don't  retain

* timer to 60 s

* some changes to HA auto discovery

* added config entries (no function yet)

* renaming

* made configs work

* added getId()

* refactoring + wrong mqtt topics fixed

* retain HA auto discovery

* do not spam serial console on each sensor update

* added readme

* add update interval info

Co-authored-by: Christian Schwinne <dev.aircoookie@gmail.com>
2022-04-03 22:30:37 +02:00
4lloyd
8b79a9708b Next universe overflow and Art-Net DMX start address (#2607)
* Improve calculation LEDs in next universe

* Fix Art-Net DMX start address
2022-04-03 17:52:36 +02:00
cschwinne
e362b3b6aa Fixed sunset time off by an hour on DST change day (fixes #2603) 2022-04-01 02:07:50 +02:00
Christian Schwinne
d2ced93e58 Merge pull request #2601 from Aircoookie/minseg-udp
UDP on main segment only.
2022-04-01 01:24:13 +02:00
cschwinne
958cd35e21 Live mainseg improvements
Make override work in mainseg mode
Move unfreeze on turn on from UI to JSON parser
Fix mainseg not unfreezing on timeout
2022-04-01 00:59:19 +02:00
Spectre
46eae410c3 add My9291 light bulb driver (#2599)
Co-authored-by: Christian Schwinne <dev.aircoookie@gmail.com>
2022-03-31 20:49:00 +02:00
Stefan Riese
73a9e1c316 V2 usermod for wordclock with 11x10 LEDs and 4 minute dots (#2608)
* - implement V2 Usermod to handle wordclocks with 11x10 pixels and 4 additional dots for the minutes

* - fix wording issue for "six"
-

* - add some more comments

* - fix issue with "zwölf"
2022-03-31 20:31:37 +02:00
cschwinne
03862d4b6c Add icon font 2022-03-28 23:23:38 +02:00
Blaz Kristan
ae90aa4ccc Power off. 2022-03-28 23:07:37 +02:00
Blaz Kristan
b583def913 Using freeze instead of power for segment.
Fix for missing udp.cpp
2022-03-28 20:44:49 +02:00
dependabot[bot]
dd85da053f Bump minimist from 1.2.5 to 1.2.6 (#2602)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-28 09:44:17 +02:00
Blaz Kristan
6079effae3 UDP on main segment only. 2022-03-25 17:20:41 +01:00
Woody
8d2fe315db Fix cross-origin error in File Mode (#2585)
* fixed cross-origin error

* removed unnecessary code

* try/catch for parent WS

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
2022-03-24 12:17:10 +01:00
Eduardo Ibanez
22c3ac5be3 Add usermode to control Wiz lights (#2595)
* Add usermode to control Wiz lights (#1)

* Fix inclusion in usermods list

Co-authored-by: Christian Schwinne <dev.aircoookie@gmail.com>
2022-03-23 16:20:18 +01:00
cschwinne
a517f0df1d Add ESP32-ETHERNET-KIT-VE type 2022-03-20 22:12:18 +01:00
cschwinne
9c9854b6bf Fixed sunrise/set calculation 2022-03-19 19:27:32 +01:00
cschwinne
d280e16723 Fixed /json/cfg unable to set busses (fixes #2589) 2022-03-19 14:21:14 +01:00
cschwinne
b93a9cb8bc FIxed liveview 2022-03-18 14:24:10 +01:00
cschwinne
8601052179 Fixed liveview buffer over-write (fixes #2586 )
(with odd LED counts > 256)
2022-03-18 14:08:37 +01:00
cschwinne
eaa20ff4bf Add handleOverlayDraw() to example v2 usermod 2022-03-16 19:32:11 +01:00
Blaž Kristan
e4c6e4bc48 Merge pull request #2584 from tonyn0/master
DDP in AP mode.
2022-03-16 17:31:09 +01:00
Blaž Kristan
c52597205e Merge branch 'master' into master 2022-03-16 17:29:12 +01:00
tonyn0
c73033c0b4 udp.cpp update
added ap check for ddp in L657
2022-03-16 11:00:29 -05:00
cschwinne
522e752582 Add ability to skip up to 255 LEDs (#2342) 2022-03-16 01:45:07 +01:00
SpeakingOfBrad
854ed8cfa9 ABL milliamps no longer hardcoded to 850 at runtime (#2581) 2022-03-16 00:35:49 +01:00
SpeakingOfBrad
4642205768 Optionally set led strip color order at runtime. (#2582) 2022-03-16 00:17:49 +01:00
André Klitzing
40dbfbe092 Update IRremoteESP8266 to 2.8.2 (#2579)
This fixes some C3 issues
2022-03-16 00:16:49 +01:00
cschwinne
6c315e5a9c 0.13.1
Fix persistent preset bug
2022-03-15 03:45:20 +01:00
cschwinne
ef0f91d8d0 Release of WLED v0.13.0 2022-03-15 02:28:26 +01:00
cschwinne
9552784e72 Remove persistent argument from savePreset()
(fixes temp preset not applicable by APIs)
Default to 5Mhz hardware SPI driving (#2558)
2022-03-14 20:53:00 +01:00
SpeakingOfBrad
f068327307 Add option to set module name at runtime, and add extra examples in platformio_overrides.ini (#2578)
* Added option to set the name of the module at runtime.

* added example how to set number of LEDs at runtime

* Example to enable/set IR remote type at runtime

* Clarification on how to use platformio_overrides

* Example for setting abl milliamp limit at runtime

* Corrected example set LED count
2022-03-14 18:23:53 +01:00
cschwinne
1bc698ae78 Button 0 long press factory reset
JS simplification
2022-03-14 12:26:45 +01:00
cschwinne
1b2134d7a8 Add old blinds usermod 2022-03-11 09:20:01 +01:00
cschwinne
f922268af7 Remove unneeded brightness set in live Serial 2022-03-11 08:41:01 +01:00
cschwinne
4865ddb377 Fix realtime mode disabled by brightness change
Fix realtime mode not working immediately at turn on
Fix individual segment control not working immediately at turn on
2022-03-10 20:40:48 +01:00
cschwinne
a556732e4f Add ability to set presets from DMX
(effect mode with single channel)
2022-03-10 12:13:12 +01:00
cschwinne
0ea31cb088 Fix indefinite realtime terminated by new packet (fixes #2356 )
Add custom Aircoookie fork of ESP32 core (reduces bin size by >100kB)
2022-03-10 11:20:39 +01:00
cschwinne
b626c7620e Disabled auto white mode in segments with no RGB bus 2022-03-08 02:16:33 +01:00
cschwinne
5d90d8930e Fix non-0 terminated hostname str 2022-03-07 20:37:48 +01:00
PLCHome
b01309c3bf Mixed content exception in web browser in websocket communication on peek behind an https backproxy. (#2571)
"ws://" must be the change to the "wss://" for encryption
2022-03-07 18:26:53 +01:00
cschwinne
961d5591bd Fixed Popcorn mode not lighting first LED on pop 2022-03-07 00:53:20 +01:00
cschwinne
eca3f12fed Fixed analog overlay not settable 2022-03-07 00:11:43 +01:00
cschwinne
a2c8796e04 Replaced native Cronixie support with usermod 2022-03-06 23:47:36 +01:00
cschwinne
ad301fd087 Elekstube usermod enhancements
Coloring of grayscale images
Dimming control from configurable segment
2022-03-06 22:24:24 +01:00
cschwinne
02b08939cd No color order select on PWM busses (fixes #2569) 2022-03-06 11:48:17 +01:00
cschwinne
9b0d583f1b EleksTube usermod 1, 4, 8 BPP BMP support 2022-03-05 21:48:11 +01:00
RedNax67
4a0a07f158 Added digit dimming and support for .clk format (see https://github.c… (#2555)
* Added digit dimming and support for .clk format (see https://github.com/aly-fly/EleksTubeHAX)

* Small fixes and improvements, dimming optional

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
2022-03-05 03:10:32 +01:00
Christian Schwinne
9c864c9759 UI: show color controls based on segment light capabilities (#2567)
* Dynamic hiding of unused color controls in UI

(e.g. a PWM white bus with no auto white mode will not display the color wheel or palette list)

* Store segment capabilities

Don't use palettes if no RGB supported
Make it safe to update busses using `/json/cfg`
2022-03-05 01:05:26 +01:00
cschwinne
85b1c309d1 Relative value wrapping and operator fix (fixes #2566 ) 2022-03-04 14:42:35 +01:00
cschwinne
6fe43b7b5c Separate color memory from slot selectors
Slot selector dynamic text color black/white
Selected slot selector JS simplification
2022-03-03 20:54:54 +01:00
Blaz Kristan
25427ee60d Fix:
- disbled timed preset expanding
- incorrect calendar icon on Mac/Firefox
2022-03-03 10:57:07 +01:00
cschwinne
be90bf0188 Minor CSS simplifications 2022-03-01 18:22:54 +01:00
cschwinne
adcdaba199 Indentation: Consistent use of Tab
Added style guide
Updated changelog
2022-03-01 12:14:41 +01:00
cschwinne
17907589cc Indentation: Consistent use of Tab for CSS 2022-03-01 11:20:10 +01:00
cschwinne
f333df181f Small CSS alignment adjustments
mainseg is not highlighted by default, but can be enabled by CSS only
Simplify some CSS (new segment box, segment brightness slider)
Started labeling CSS classes
2022-03-01 11:13:56 +01:00
Blaz Kristan
4ce557a829 Multiple fixes:
- ability so select mainseg (UI)
- changed preset sort (UI)
- IR40 white +/- fix
- IR repeatable actions fix
- minor UI CSS change
- removed unnecessary color functions
2022-02-27 22:19:05 +01:00
Christian Schwinne
fc845dc936 Add locate button for easy lat/lon auto-fill (#2559) 2022-02-26 01:37:30 +01:00
Blaž Kristan
7beae93441 IR rewrite. (#2561)
* IR rewrite.
- added CCT (WW/CW) support
- support for applying change to main segment or all selected segments

* Remove extra setValuesFromFirstSelectedSeg()

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
2022-02-26 01:37:09 +01:00
Sousanator
afaa001738 Merge branch 'master' into master 2022-01-30 10:51:51 -05:00
Sousanator
71520f6709 Update const.h
Added option for LDP6803
2021-12-27 18:06:58 -05:00
Sousanator
3f5a09229d Update settings_leds.htm
Added option for LPD6803
2021-12-27 18:04:14 -05:00
Sousanator
5609771993 Update html_settings.h 2021-12-27 18:01:42 -05:00
Sousanator
79b01cdc3c Update bus_wrapper.h
Added support for LPD6803
2021-12-27 17:54:37 -05:00
92 changed files with 7766 additions and 4089 deletions

View File

@@ -45,7 +45,7 @@ body:
attributes:
label: What version of WLED?
description: You can find this in by going to Config -> Security & Updates -> Scroll to Bottom. Copy and paste the entire line after "Server message"
placeholder: "e.g. WLED 0.13.0-b7 (build 2202222)"
placeholder: "e.g. WLED 0.13.1 (build 2203150)"
validations:
required: true
- type: dropdown

View File

@@ -1,6 +1,87 @@
## WLED changelog
### Builds after release 0.12.0
### WLED release 0.13.3
- Version bump to v0.13.3 "Toki"
- Disable ESP watchdog by default (fixes flickering and boot issues on a fresh install)
- Added support for LPD6803
### WLED release 0.13.2
#### Build 2208140
- Version bump to v0.13.2 "Toki"
- Added option to receive live data on the main segment only (PR #2601)
- Enable ESP watchdog by default (PR #2657)
- Fixed race condition when saving bus config
- Better potentiometer filtering (PR #2693)
- More suitable DMX libraries (PR #2652)
- Fixed outgoing serial TPM2 message length (PR #2628)
- Fixed next universe overflow and Art-Net DMX start address (PR #2607)
- Fixed relative segment brightness (PR #2665)
### Builds between releases 0.13.1 and 0.13.2
#### Build 2203191
- Fixed sunrise/set calculation (once again)
#### Build 2203190
- Fixed `/json/cfg` unable to set busses (#2589)
- Fixed Peek with odd LED counts > 255 (#2586)
#### Build 2203160
- Version bump to v0.13.2-a0 "Toki"
- Add ability to skip up to 255 LEDs
- Dependency version bumps
### WLED release 0.13.1
#### Build 2203150
- Version bump to v0.13.1 "Toki"
- Fix persistent preset bug, preventing save of new presets
### WLED release 0.13.0
#### Build 2203142
- Release of WLED v0.13.0 "Toki"
- Reduce APA102 hardware SPI frequency to 5Mhz
- Remove `persistent` parameter in `savePreset()`
### Builds between releases 0.12.0 and 0.13.0
#### Build 2203140
- Added factory reset by pressing button 0 for >10 seconds
- Added ability to set presets from DMX Effect mode
- Simplified label hiding JS in user interface
- Fixed JSON `{"live":true}` indefinite realtime mode
#### Build 2203080
- Disabled auto white mode in segments with no RGB bus
- Fixed hostname string not 0-terminated
- Fixed Popcorn mode not lighting first LED on pop
#### Build 2203060
- Dynamic hiding of unused color controls in UI (PR #2567)
- Removed native Cronixie support and added Cronixie usermod
- Fixed disabled timed preset expanding calendar
- Fixed Color Order setting shown for analog busses
- Fixed incorrect operator (#2566)
#### Build 2203011
- IR rewrite (PR #2561), supports CCT
- Added locate button to Time settings
- CSS fixes and adjustments
- Consistent Tab indentation in index JS and CSS
- Added initial contribution style guideline
#### Build 2202222

78
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,78 @@
## Thank you for making WLED better!
Here are a few suggestions to make it easier for you to contribute!
### Code style
When in doubt, it is easiest to replicate the code style you find in the files you want to edit :)
Below are the guidelines we use in the WLED repository.
#### Indentation
We use tabs for Indentation in Web files (.html/.css/.js) and spaces (2 per indentation level) for all other files.
You are all set if you have enabled `Editor: Detect Indentation` in VS Code.
#### Blocks
Whether the opening bracket of e.g. an `if` block is in the same line as the condition or in a separate line is up to your discretion. If there is only one statement, leaving out block braches is acceptable.
Good:
```cpp
if (a == b) {
doStuff(a);
}
```
```cpp
if (a == b)
{
doStuff(a);
}
```
```cpp
if (a == b) doStuff(a);
```
There should always be a space between a keyword and its condition and between the condition and brace.
Within the condition, no space should be between the paranthesis and variables.
Spaces between variables and operators are up to the authors discretion.
There should be no space between function names and their argument parenthesis.
Good:
```cpp
if (a == b) {
doStuff(a);
}
```
Not good:
```cpp
if( a==b ){
doStuff ( a);
}
```
#### Comments
Comments should have a space between the delimiting characters (e.g. `//`) and the comment text.
Note: This is a recent change, the majority of the codebase still has comments without spaces.
Good:
```
// This is a comment.
/* This is a CSS inline comment */
/*
* This is a comment
* wrapping over multiple lines,
* used in WLED for file headers and function explanations
*/
<!-- This is an HTML comment -->
```
There is no set character limit for a comment within a line,
though as a rule of thumb you should wrap your comment if it exceeds the width of your editor window.
Inline comments are OK if they describe that line only and are not exceedingly wide.

14
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "wled",
"version": "0.13.0-b7",
"version": "0.13.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1339,9 +1339,9 @@
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"mkdirp": {
"version": "0.5.5",
@@ -2067,9 +2067,9 @@
"integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw=="
},
"terser": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz",
"integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==",
"requires": {
"commander": "^2.20.0",
"source-map": "~0.6.1",

View File

@@ -1,6 +1,6 @@
{
"name": "wled",
"version": "0.13.0-b7",
"version": "0.13.3",
"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, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32_eth_ota1mapp, esp32s2_saola, esp32c3
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3
# Build everything
; default_envs = esp32dev, esp8285_4CH_MagicHome, 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_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
@@ -35,6 +35,7 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32_
; default_envs = wemos_shield_esp32
; default_envs = m5atom
; default_envs = esp32_eth
; default_envs = esp32dev_qio80
; default_envs = esp32_eth_ota1mapp
; default_envs = esp32s2_saola
@@ -161,7 +162,7 @@ upload_speed = 115200
lib_compat_mode = strict
lib_deps =
fastled/FastLED @ 3.5.0
IRremoteESP8266 @ 2.8.1
IRremoteESP8266 @ 2.8.2
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.4
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
#TFT_eSPI
@@ -199,14 +200,16 @@ build_flags =
lib_deps =
${env.lib_deps}
#https://github.com/lorol/LITTLEFS.git
# ESPAsyncTCP @ 1.2.0
ESPAsyncTCP @ 1.2.2
ESPAsyncUDP
makuna/NeoPixelBus @ 2.6.7 # 2.6.5/2.6.6 and newer do not compile on ESP core < 3.0.0
makuna/NeoPixelBus @ 2.6.9
[esp32]
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2/platform-tasmota-espressif32-2.0.2.zip
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip
platform = espressif32@3.5.0
platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4
build_flags = -g
-DARDUINO_ARCH_ESP32
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
@@ -219,7 +222,7 @@ default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
lib_deps =
${env.lib_deps}
https://github.com/lorol/LITTLEFS.git
makuna/NeoPixelBus @ 2.6.7
makuna/NeoPixelBus @ 2.6.9
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
[esp32s2]
@@ -232,7 +235,7 @@ build_flags = -g
lib_deps =
${env.lib_deps}
makuna/NeoPixelBus @ 2.6.7
makuna/NeoPixelBus @ 2.6.9
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
[esp32c3]
@@ -245,7 +248,7 @@ build_flags = -g
lib_deps =
${env.lib_deps}
makuna/NeoPixelBus @ 2.6.7
makuna/NeoPixelBus @ 2.6.9
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
# ------------------------------------------------------------------------------
@@ -320,26 +323,35 @@ lib_deps = ${esp8266.lib_deps}
[env:esp32dev]
board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
[env:esp32dev_qio80]
board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_qio80 #-D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
[env:esp32_eth]
board = esp32-poe
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
upload_speed = 921600
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 -D WLED_DISABLE_BLYNK
lib_deps = ${esp32.lib_deps}
board_build.partitions = ${esp32.default_partitions}
# ESP32 ETH build that fits in old 1M app space (disables Blynk, Cronixie, and Hue sync)
[env:esp32_eth_ota1mapp]
extends = env:esp32_eth
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet_OTA -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 -D WLED_DISABLE_BLYNK -D WLED_DISABLE_CRONIXIE -D WLED_DISABLE_HUESYNC
[env:esp32s2_saola]
board = esp32-s2-saola-1
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip
@@ -482,6 +494,15 @@ board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D WLED_USE_IC_CCT -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
[env:MY9291]
board = esp01_1m
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -D USERMOD_MY9291
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
# travis test board configurations
# ------------------------------------------------------------------------------
@@ -534,7 +555,7 @@ build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_D
-D LEDPIN=12
-D RLYPIN=27
-D BTNPIN=34
-D WLED_DISABLE_INFRARED
-D WLED_DISABLE_BLYNK
-D DEFAULT_LED_COUNT=6
# Display config
-D ST7789_DRIVER

View File

@@ -19,10 +19,14 @@ build_flags = ${common.build_flags_esp8266}
; *** Use custom settings from file my_config.h
-DWLED_USE_MY_CONFIG
; *********************************************************************
;
;
; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above.
;
; disable specific features
; -D WLED_DISABLE_OTA
; -D WLED_DISABLE_ALEXA
; -D WLED_DISABLE_BLYNK
; -D WLED_DISABLE_CRONIXIE
; -D WLED_DISABLE_HUESYNC
; -D WLED_DISABLE_INFRARED
; -D WLED_DISABLE_WEBSOCKETS
@@ -45,3 +49,18 @@ build_flags = ${common.build_flags_esp8266}
; for the Magic Home LED Controller use PWM pins 5,12,13,15
; for the H801 controller use PINs 15,13,12,14 (W2 = 04)
; for the BW-LT11 controller use PINs 12,4,14,5
;
; set the name of the module - make sure there is a quote-backslash-quote before the name and a backslash-quote-quote after the name
; -D SERVERNAME="\"WLED\""
;
; set the number of LEDs
; -D DEFAULT_LED_COUNT=30
;
; set milliampere limit when using ESP pin to power leds
; -D ABL_MILLIAMPS_DEFAULT=850
;
; enable IR by setting remote type
; -D IRTYPE=0 ;0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote
;
; set default color order of your led strip
; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB

View File

@@ -27,7 +27,7 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control
- Presets can be used to automatically execute API calls
- Nightlight function (gradually dims down)
- Full OTA software updatability (HTTP + ArduinoOTA), password protectable
- Configurable analog clock + support for the Cronixie kit by Diamex
- Configurable analog clock (Cronixie, 7-segment and EleksTube IPS clock support via usermods)
- Configurable Auto Brightness limit for safer operation
- Filesystem-based config for easier backup of presets and settings

View File

@@ -1,54 +1,70 @@
#
# This file is autogenerated by pip-compile
# This file is autogenerated by pip-compile with python 3.8
# To update, run:
#
# pip-compile
#
aiofiles==0.6.0
aiofiles==0.8.0
# via platformio
ajsonrpc==1.1.0
ajsonrpc==1.2.0
# via platformio
bottle==0.12.19
anyio==3.6.1
# via starlette
async-timeout==4.0.2
# via zeroconf
bottle==0.12.23
# via platformio
certifi==2020.12.5
certifi==2022.6.15
# via requests
chardet==4.0.0
charset-normalizer==2.1.1
# via requests
click==7.1.2
click==8.1.3
# via
# platformio
# uvicorn
colorama==0.4.4
# via platformio
h11==0.12.0
colorama==0.4.5
# via
# click
# platformio
h11==0.13.0
# via
# uvicorn
# wsproto
idna==2.10
# via requests
ifaddr==0.1.7
idna==3.3
# via
# anyio
# requests
ifaddr==0.2.0
# via zeroconf
marshmallow==3.11.1
marshmallow==3.17.0
# via platformio
platformio==5.1.1
packaging==21.3
# via marshmallow
platformio==6.1.4
# via -r requirements.in
pyelftools==0.27
pyelftools==0.29
# via platformio
pyparsing==3.0.9
# via packaging
pyserial==3.5
# via platformio
requests==2.25.1
requests==2.28.1
# via platformio
semantic-version==2.8.5
semantic-version==2.10.0
# via platformio
starlette==0.14.2
sniffio==1.2.0
# via anyio
starlette==0.20.4
# via platformio
tabulate==0.8.9
tabulate==0.8.10
# via platformio
urllib3==1.26.5
typing-extensions==4.3.0
# via starlette
urllib3==1.26.11
# via requests
uvicorn==0.13.4
uvicorn==0.18.2
# via platformio
wsproto==1.0.0
wsproto==1.1.0
# via platformio
zeroconf==0.28.8
zeroconf==0.39.0
# via platformio

View File

@@ -1,40 +1,90 @@
Hello! I have written a v2 usermod for the BME280/BMP280 sensor based on the [existing v1 usermod](https://github.com/Aircoookie/WLED/blob/master/usermods/Wemos_D1_mini%2BWemos32_mini_shield/usermod_bme280.cpp). It is not just a refactor, there are many changes which I made to fit my use case, and I hope they will fit the use cases of others as well! Most notably, this usermod is *just* for the BME280 and does not control a display like in the v1 usermod designed for the WeMos shield.
# Usermod BME280
This Usermod is designed to read a `BME280` or `BMP280` sensor and output the following:
- Temperature
- Humidity (`BME280` only)
- Pressure
- Heat Index (`BME280` only)
- Dew Point (`BME280` only)
- Requires libraries `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280)) and `Wire`. Please add these under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
- Data is published over MQTT so make sure you've enabled the MQTT sync interface.
Configuration is all completed via the Usermod menu. There are no settings to set in code! The following settings can be configured in the Usermod Menu:
- Temperature Decimals (number of decimal places to output)
- Humidity Decimals
- Pressure Decimals
- Temperature Interval (how many seconds between reads of temperature and humidity)
- Pressure Interval
- Publish Always (turn off to only publish changes, on to publish whether or not value changed)
- Use Celsius (turn off to use Farenheit)
- Home Assistant Discovery (turn on to sent MQTT Discovery entries for Home Assistant)
- SCL/SDA GPIO Pins
Dependencies
- Libraries
- `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280))
- `Wire`
- These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
- Data is published over MQTT - make sure you've enabled the MQTT sync interface.
- This usermod also writes to serial (GPIO1 on ESP8266). Please make sure nothing else listening on the serial TX pin of your board will get confused by log messages!
To enable, compile with `USERMOD_BME280` defined (i.e. `platformio_override.ini`)
In addition to outputting via MQTT, you can read the values from the Info Screen on the dashboard page of the device's web interface.
Methods also exist to read the read/calculated values from other WLED modules through code.
- `getTemperatureC()`
- `getTemperatureF()`
- `getHumidity()`
- `getPressure()`
- `getDewPointC()`
- `getDewPointF()`
- `getHeatIndexC()`
- `getHeatIndexF()`
# Complilation
To enable, compile with `USERMOD_BME280` defined (e.g. in `platformio_override.ini`)
```ini
[env:usermod_bme280_d1_mini]
extends = env:d1_mini
build_flags =
${common.build_flags_esp8266}
-D USERMOD_BME280
```
or define `USERMOD_BME280` in `my_config.h`
```c++
#define USERMOD_BME280
lib_deps =
${esp8266.lib_deps}
BME280@~3.0.0
Wire
```
Changes include:
- Adjustable measure intervals
- Temperature and pressure have separate intervals due to pressure not frequently changing at any constant altitude
- Adjustment of number of decimal places in published sensor values
- Separate adjustment for temperature, humidity and pressure values
- Values are rounded to the specified number of decimal places
- Pressure measured in units of hPa instead of Pa
- Calculation of heat index (apparent temperature) and dew point
- These, along with humidity measurements, are disabled if the sensor is a BMP280
- 16x oversampling of sensor during measurement
- Values are only published if they are different from the previous value
- Values are published on startup (continually until the MQTT broker acknowledges a successful publication)
Adjustments are made through preprocessor definitions at the start of the class definition.
MQTT topics are as follows:
# MQTT
MQTT topics are as follows (`<deviceTopic>` is set in MQTT section of Sync Setup menu):
Measurement type | MQTT topic
--- | ---
Temperature | `<deviceTopic>/temperature`
Humidity | `<deviceTopic>/humidity`
Pressure | `<deviceTopic>/pressure`
Heat index | `<deviceTopic>/heat_index`
Dew point | `<deviceTopic>/dew_point`
Dew point | `<deviceTopic>/dew_point`
If you are using Home Assistant, and `Home Assistant Discovery` is turned on, Home Assistant should automatically detect a new device, provided you have the MQTT integration installed. The device is seperate from the main WLED device and will contain sensors for Pressure, Humidity, Temperature, Dew Point and Heat Index.
# Revision History
Jul 2022
- Added Home Assistant Discovery
- Added API interface to output data
- Removed compile-time variables
- Added usermod menu interface
- Added value outputs to info screen
- Updated `readme.md`
- Registered usermod
- Implemented PinManager for usermod
- Implemented reallocation of pins without reboot
Apr 2021
- Added `Publish Always` option
Dec 2020
- Ported to V2 Usermod format
- Customisable `measure intervals`
- Customisable number of `decimal places` in published sensor values
- Pressure measured in units of hPa instead of Pa
- Calculation of heat index (apparent temperature) and dew point
- `16x oversampling` of sensor during measurement
- Values only published if they are different from the previous value

View File

@@ -1,3 +1,6 @@
// force the compiler to show a warning to confirm that this file is included
#warning **** Included USERMOD_BME280 version 2.0 ****
#pragma once
#include "wled.h"
@@ -9,43 +12,30 @@
class UsermodBME280 : public Usermod
{
private:
// User-defined configuration
#define Celsius // Show temperature mesaurement in Celcius. Comment out for Fahrenheit
#define TemperatureDecimals 1 // Number of decimal places in published temperaure values
#define HumidityDecimals 2 // Number of decimal places in published humidity values
#define PressureDecimals 2 // Number of decimal places in published pressure values
#define TemperatureInterval 5 // Interval to measure temperature (and humidity, dew point if available) in seconds
#define PressureInterval 300 // Interval to measure pressure in seconds
#define PublishAlways 0 // Publish values even when they have not changed
// NOTE: Do not implement any compile-time variables, anything the user needs to configure
// should be configurable from the Usermod menu using the methods below
// key settings set via usermod menu
unsigned long TemperatureDecimals = 0; // Number of decimal places in published temperaure values
unsigned long HumidityDecimals = 0; // Number of decimal places in published humidity values
unsigned long PressureDecimals = 0; // Number of decimal places in published pressure values
unsigned long TemperatureInterval = 5; // Interval to measure temperature (and humidity, dew point if available) in seconds
unsigned long PressureInterval = 300; // Interval to measure pressure in seconds
bool PublishAlways = false; // Publish values even when they have not changed
bool UseCelsius = true; // Use Celsius for Reporting
bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information
// Sanity checks
#if !defined(TemperatureDecimals) || TemperatureDecimals < 0
#define TemperatureDecimals 0
#endif
#if !defined(HumidityDecimals) || HumidityDecimals < 0
#define HumidityDecimals 0
#endif
#if !defined(PressureDecimals) || PressureDecimals < 0
#define PressureDecimals 0
#endif
#if !defined(TemperatureInterval) || TemperatureInterval < 0
#define TemperatureInterval 1
#endif
#if !defined(PressureInterval) || PressureInterval < 0
#define PressureInterval TemperatureInterval
#endif
#if !defined(PublishAlways)
#define PublishAlways 0
#endif
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
uint8_t SCL_PIN = 22;
uint8_t SDA_PIN = 21;
#else // ESP8266 boards
uint8_t SCL_PIN = 5;
uint8_t SDA_PIN = 4;
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
#endif
// set the default pins based on the architecture, these get overridden by Usermod menu settings
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
#define HW_PIN_SCL 22
#define HW_PIN_SDA 21
#else // ESP8266 boards
#define HW_PIN_SCL 5
#define HW_PIN_SDA 4
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
#endif
int8_t ioPin[2] = {HW_PIN_SCL, HW_PIN_SDA}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup()
bool initDone = false;
// BME280 sensor settings
BME280I2C::Settings settings{
@@ -75,6 +65,7 @@ private:
float sensorHeatIndex;
float sensorDewPoint;
float sensorPressure;
String tempScale;
// Track previous sensor values
float lastTemperature;
float lastHumidity;
@@ -82,43 +73,122 @@ private:
float lastDewPoint;
float lastPressure;
// MQTT topic strings for publishing Home Assistant discovery topics
bool mqttInitialized = false;
String mqttTemperatureTopic = "";
String mqttHumidityTopic = "";
String mqttPressureTopic = "";
String mqttHeatIndexTopic = "";
String mqttDewPointTopic = "";
// Store packet IDs of MQTT publications
uint16_t mqttTemperaturePub = 0;
uint16_t mqttPressurePub = 0;
// Read the BME280/BMP280 Sensor (which one runs depends on whether Celsius or Farenheit being set in Usermod Menu)
void UpdateBME280Data(int SensorType)
{
float _temperature, _humidity, _pressure;
#ifdef Celsius
if (UseCelsius) {
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius);
#else
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
sensorTemperature = _temperature;
sensorHumidity = _humidity;
sensorPressure = _pressure;
tempScale = "°C";
if (sensorType == 1)
{
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
}
} else {
BME280::TempUnit tempUnit(BME280::TempUnit_Fahrenheit);
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Fahrenheit);
#endif
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
sensorTemperature = _temperature;
sensorHumidity = _humidity;
sensorPressure = _pressure;
if (sensorType == 1)
{
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
sensorTemperature = _temperature;
sensorHumidity = _humidity;
sensorPressure = _pressure;
tempScale = "°F";
if (sensorType == 1)
{
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
}
}
}
// Procedure to define all MQTT discovery Topics
void _mqttInitialize()
{
mqttTemperatureTopic = String(mqttDeviceTopic) + F("/temperature");
mqttPressureTopic = String(mqttDeviceTopic) + F("/pressure");
mqttHumidityTopic = String(mqttDeviceTopic) + F("/humidity");
mqttHeatIndexTopic = String(mqttDeviceTopic) + F("/heat_index");
mqttDewPointTopic = String(mqttDeviceTopic) + F("/dew_point");
if (HomeAssistantDiscovery) {
_createMqttSensor(F("Temperature"), mqttTemperatureTopic, F("temperature"), tempScale);
_createMqttSensor(F("Pressure"), mqttPressureTopic, F("pressure"), F("hPa"));
_createMqttSensor(F("Humidity"), mqttHumidityTopic, F("humidity"), F("%"));
_createMqttSensor(F("HeatIndex"), mqttHeatIndexTopic, F("temperature"), tempScale);
_createMqttSensor(F("DewPoint"), mqttDewPointTopic, F("temperature"), tempScale);
}
}
// Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
{
String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config");
StaticJsonDocument<600> doc;
doc[F("name")] = String(serverDescription) + " " + name;
doc[F("state_topic")] = topic;
doc[F("unique_id")] = String(mqttClientID) + name;
if (unitOfMeasurement != "")
doc[F("unit_of_measurement")] = unitOfMeasurement;
if (deviceClass != "")
doc[F("device_class")] = deviceClass;
doc[F("expire_after")] = 1800;
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
device[F("name")] = serverDescription;
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
device[F("manufacturer")] = F("WLED");
device[F("model")] = F("FOSS");
device[F("sw_version")] = versionString;
String temp;
serializeJson(doc, temp);
DEBUG_PRINTLN(t);
DEBUG_PRINTLN(temp);
mqtt->publish(t.c_str(), 0, true, temp.c_str());
}
public:
void setup()
{
Wire.begin(SDA_PIN, SCL_PIN);
bool HW_Pins_Used = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); // note whether architecture-based hardware SCL/SDA pins used
PinOwner po = PinOwner::UM_BME280; // defaults to being pinowner for SCL/SDA pins
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
if (!pinManager.allocateMultiplePins(pins, 2, po)) { sensorType=0; return; }
Wire.begin(ioPin[1], ioPin[0]);
if (!bme.begin())
{
sensorType = 0;
Serial.println("Could not find BME280I2C sensor!");
DEBUG_PRINTLN(F("Could not find BME280I2C sensor!"));
}
else
{
@@ -126,24 +196,25 @@ public:
{
case BME280::ChipModel_BME280:
sensorType = 1;
Serial.println("Found BME280 sensor! Success.");
DEBUG_PRINTLN(F("Found BME280 sensor! Success."));
break;
case BME280::ChipModel_BMP280:
sensorType = 2;
Serial.println("Found BMP280 sensor! No Humidity available.");
DEBUG_PRINTLN(F("Found BMP280 sensor! No Humidity available."));
break;
default:
sensorType = 0;
Serial.println("Found UNKNOWN sensor! Error!");
DEBUG_PRINTLN(F("Found UNKNOWN sensor! Error!"));
}
}
initDone=true;
}
void loop()
{
// BME280 sensor MQTT publishing
// Check if sensor present and MQTT Connected, otherwise it will crash the MCU
if (sensorType != 0 && mqtt != nullptr)
if (sensorType != 0 && WLED_MQTT_CONNECTED)
{
// Timer to fetch new temperature, humidity and pressure data at intervals
timer = millis();
@@ -154,9 +225,15 @@ public:
UpdateBME280Data(sensorType);
float temperature = roundf(sensorTemperature * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
float temperature = roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
float humidity, heatIndex, dewPoint;
if (WLED_MQTT_CONNECTED && !mqttInitialized)
{
_mqttInitialize();
mqttInitialized = true;
}
// If temperature has changed since last measure, create string populated with device topic
// from the UI and values read from sensor, then publish to broker
if (temperature != lastTemperature || PublishAlways)
@@ -169,25 +246,25 @@ public:
if (sensorType == 1) // Only if sensor is a BME280
{
humidity = roundf(sensorHumidity * pow(10, HumidityDecimals)) / pow(10, HumidityDecimals);
heatIndex = roundf(sensorHeatIndex * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
dewPoint = roundf(sensorDewPoint * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
humidity = roundf(sensorHumidity * powf(10, HumidityDecimals)) / powf(10, HumidityDecimals);
heatIndex = roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
dewPoint = roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
if (humidity != lastHumidity || PublishAlways)
{
String topic = String(mqttDeviceTopic) + "/humidity";
String topic = String(mqttDeviceTopic) + F("/humidity");
mqtt->publish(topic.c_str(), 0, false, String(humidity, HumidityDecimals).c_str());
}
if (heatIndex != lastHeatIndex || PublishAlways)
{
String topic = String(mqttDeviceTopic) + "/heat_index";
String topic = String(mqttDeviceTopic) + F("/heat_index");
mqtt->publish(topic.c_str(), 0, false, String(heatIndex, TemperatureDecimals).c_str());
}
if (dewPoint != lastDewPoint || PublishAlways)
{
String topic = String(mqttDeviceTopic) + "/dew_point";
String topic = String(mqttDeviceTopic) + F("/dew_point");
mqtt->publish(topic.c_str(), 0, false, String(dewPoint, TemperatureDecimals).c_str());
}
@@ -201,11 +278,11 @@ public:
{
lastPressureMeasure = timer;
float pressure = roundf(sensorPressure * pow(10, PressureDecimals)) / pow(10, PressureDecimals);
float pressure = roundf(sensorPressure * powf(10, PressureDecimals)) / powf(10, PressureDecimals);
if (pressure != lastPressure || PublishAlways)
{
String topic = String(mqttDeviceTopic) + "/pressure";
String topic = String(mqttDeviceTopic) + F("/pressure");
mqttPressurePub = mqtt->publish(topic.c_str(), 0, true, String(pressure, PressureDecimals).c_str());
}
@@ -213,4 +290,173 @@ public:
}
}
}
/*
* API calls te enable data exchange between WLED modules
*/
inline float getTemperatureC() {
if (UseCelsius) {
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
} else {
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
}
}
inline float getTemperatureF() {
if (UseCelsius) {
return ((float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
} else {
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
}
}
inline float getHumidity() {
return (float)roundf(sensorHumidity * powf(10, HumidityDecimals));
}
inline float getPressure() {
return (float)roundf(sensorPressure * powf(10, PressureDecimals));
}
inline float getDewPointC() {
if (UseCelsius) {
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
} else {
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
}
}
inline float getDewPointF() {
if (UseCelsius) {
return ((float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
} else {
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
}
}
inline float getHeatIndexC() {
if (UseCelsius) {
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
} else {
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
}
}inline float getHeatIndexF() {
if (UseCelsius) {
return ((float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
} else {
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
}
}
// Publish Sensor Information to Info Page
void addToJsonInfo(JsonObject &root)
{
JsonObject user = root[F("u")];
if (user.isNull()) user = root.createNestedObject(F("u"));
if (sensorType==0) //No Sensor
{
// if we sensor not detected, let the user know
JsonArray temperature_json = user.createNestedArray(F("BME/BMP280 Sensor"));
temperature_json.add(F("Not Found"));
}
else if (sensorType==2) //BMP280
{
JsonArray temperature_json = user.createNestedArray(F("Temperature"));
JsonArray pressure_json = user.createNestedArray(F("Pressure"));
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)));
temperature_json.add(tempScale);
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)));
pressure_json.add(F("hPa"));
}
else if (sensorType==1) //BME280
{
JsonArray temperature_json = user.createNestedArray(F("Temperature"));
JsonArray humidity_json = user.createNestedArray(F("Humidity"));
JsonArray pressure_json = user.createNestedArray(F("Pressure"));
JsonArray heatindex_json = user.createNestedArray(F("Heat Index"));
JsonArray dewpoint_json = user.createNestedArray(F("Dew Point"));
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
temperature_json.add(tempScale);
humidity_json.add(roundf(sensorHumidity * powf(10, HumidityDecimals)));
humidity_json.add(F("%"));
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)));
pressure_json.add(F("hPa"));
heatindex_json.add(roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
heatindex_json.add(tempScale);
dewpoint_json.add(roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
dewpoint_json.add(tempScale);
}
return;
}
// Save Usermod Config Settings
void addToConfig(JsonObject& root)
{
JsonObject top = root.createNestedObject(F("BME280/BMP280"));
top[F("TemperatureDecimals")] = TemperatureDecimals;
top[F("HumidityDecimals")] = HumidityDecimals;
top[F("PressureDecimals")] = PressureDecimals;
top[F("TemperatureInterval")] = TemperatureInterval;
top[F("PressureInterval")] = PressureInterval;
top[F("PublishAlways")] = PublishAlways;
top[F("UseCelsius")] = UseCelsius;
top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery;
JsonArray io_pin = top.createNestedArray(F("pin"));
for (byte i=0; i<2; i++) io_pin.add(ioPin[i]);
top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page
DEBUG_PRINTLN(F("BME280 config saved."));
}
// Read Usermod Config Settings
bool readFromConfig(JsonObject& root)
{
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins
JsonObject top = root[F("BME280/BMP280")];
if (top.isNull()) {
DEBUG_PRINT(F("BME280/BMP280"));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
bool configComplete = !top.isNull();
// A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing
configComplete &= getJsonValue(top[F("TemperatureDecimals")], TemperatureDecimals, 1);
configComplete &= getJsonValue(top[F("HumidityDecimals")], HumidityDecimals, 0);
configComplete &= getJsonValue(top[F("PressureDecimals")], PressureDecimals, 0);
configComplete &= getJsonValue(top[F("TemperatureInterval")], TemperatureInterval, 30);
configComplete &= getJsonValue(top[F("PressureInterval")], PressureInterval, 30);
configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false);
configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true);
configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false);
for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]);
DEBUG_PRINT(FPSTR(F("BME280/BMP280")));
if (!initDone) {
// first run: reading from cfg.json
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
DEBUG_PRINTLN(F(" config loaded."));
} else {
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
bool pinsChanged = false;
for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed
if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones
PinOwner po = PinOwner::UM_BME280;
if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
setup();
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[F("pin")].isNull();
}
return configComplete;
}
uint16_t getId() {
return USERMOD_ID_BME280;
}
};

View File

@@ -0,0 +1,8 @@
# Cronixie clock usermod
This usermod supports driving the Cronixie M and L clock kits by Diamex.
## Installation
Compile and upload after adding `-D USERMOD_CRONIXIE` to `build_flags` of your PlatformIO environment.
Make sure the Auto Brightness Limiter is enabled at 420mA (!) and configure 60 WS281x LEDs.

View File

@@ -0,0 +1,301 @@
#pragma once
#include "wled.h"
class UsermodCronixie : public Usermod {
private:
unsigned long lastTime = 0;
char cronixieDisplay[7] = "HHMMSS";
byte _digitOut[6] = {10,10,10,10,10,10};
byte dP[6] = {255, 255, 255, 255, 255, 255};
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
bool backlight = true;
public:
void initCronixie()
{
if (dP[0] == 255) // if dP[0] is 255, cronixie is not yet init'ed
{
setCronixie();
strip.getSegment(0).grouping = 10; // 10 LEDs per digit
}
}
void setup() {
}
void loop() {
if (!toki.isTick()) return;
initCronixie();
_overlayCronixie();
strip.trigger();
}
byte getSameCodeLength(char code, int index, char const cronixieDisplay[])
{
byte counter = 0;
for (int i = index+1; i < 6; i++)
{
if (cronixieDisplay[i] == code)
{
counter++;
} else {
return counter;
}
}
return counter;
}
void setCronixie()
{
/*
* digit purpose index
* 0-9 | 0-9 (incl. random)
* 10 | blank
* 11 | blank, bg off
* 12 | test upw.
* 13 | test dnw.
* 14 | binary AM/PM
* 15 | BB upper +50 for no trailing 0
* 16 | BBB
* 17 | BBBB
* 18 | BBBBB
* 19 | BBBBBB
* 20 | H
* 21 | HH
* 22 | HHH
* 23 | HHHH
* 24 | M
* 25 | MM
* 26 | MMM
* 27 | MMMM
* 28 | MMMMM
* 29 | MMMMMM
* 30 | S
* 31 | SS
* 32 | SSS
* 33 | SSSS
* 34 | SSSSS
* 35 | SSSSSS
* 36 | Y
* 37 | YY
* 38 | YYYY
* 39 | I
* 40 | II
* 41 | W
* 42 | WW
* 43 | D
* 44 | DD
* 45 | DDD
* 46 | V
* 47 | VV
* 48 | VVV
* 49 | VVVV
* 50 | VVVVV
* 51 | VVVVVV
* 52 | v
* 53 | vv
* 54 | vvv
* 55 | vvvv
* 56 | vvvvv
* 57 | vvvvvv
*/
//H HourLower | HH - Hour 24. | AH - Hour 12. | HHH Hour of Month | HHHH Hour of Year
//M MinuteUpper | MM Minute of Hour | MMM Minute of 12h | MMMM Minute of Day | MMMMM Minute of Month | MMMMMM Minute of Year
//S SecondUpper | SS Second of Minute | SSS Second of 10 Minute | SSSS Second of Hour | SSSSS Second of Day | SSSSSS Second of Week
//B AM/PM | BB 0-6/6-12/12-18/18-24 | BBB 0-3... | BBBB 0-1.5... | BBBBB 0-1 | BBBBBB 0-0.5
//Y YearLower | YY - Year LU | YYYY - Std.
//I MonthLower | II - Month of Year
//W Week of Month | WW Week of Year
//D Day of Week | DD Day Of Month | DDD Day Of Year
DEBUG_PRINT("cset ");
DEBUG_PRINTLN(cronixieDisplay);
for (int i = 0; i < 6; i++)
{
dP[i] = 10;
switch (cronixieDisplay[i])
{
case '_': dP[i] = 10; break;
case '-': dP[i] = 11; break;
case 'r': dP[i] = random(1,7); break; //random btw. 1-6
case 'R': dP[i] = random(0,10); break; //random btw. 0-9
//case 't': break; //Test upw.
//case 'T': break; //Test dnw.
case 'b': dP[i] = 14 + getSameCodeLength('b',i,cronixieDisplay); i = i+dP[i]-14; break;
case 'B': dP[i] = 14 + getSameCodeLength('B',i,cronixieDisplay); i = i+dP[i]-14; break;
case 'h': dP[i] = 70 + getSameCodeLength('h',i,cronixieDisplay); i = i+dP[i]-70; break;
case 'H': dP[i] = 20 + getSameCodeLength('H',i,cronixieDisplay); i = i+dP[i]-20; break;
case 'A': dP[i] = 108; i++; break;
case 'a': dP[i] = 58; i++; break;
case 'm': dP[i] = 74 + getSameCodeLength('m',i,cronixieDisplay); i = i+dP[i]-74; break;
case 'M': dP[i] = 24 + getSameCodeLength('M',i,cronixieDisplay); i = i+dP[i]-24; break;
case 's': dP[i] = 80 + getSameCodeLength('s',i,cronixieDisplay); i = i+dP[i]-80; break; //refresh more often bc. of secs
case 'S': dP[i] = 30 + getSameCodeLength('S',i,cronixieDisplay); i = i+dP[i]-30; break;
case 'Y': dP[i] = 36 + getSameCodeLength('Y',i,cronixieDisplay); i = i+dP[i]-36; break;
case 'y': dP[i] = 86 + getSameCodeLength('y',i,cronixieDisplay); i = i+dP[i]-86; break;
case 'I': dP[i] = 39 + getSameCodeLength('I',i,cronixieDisplay); i = i+dP[i]-39; break; //Month. Don't ask me why month and minute both start with M.
case 'i': dP[i] = 89 + getSameCodeLength('i',i,cronixieDisplay); i = i+dP[i]-89; break;
//case 'W': break;
//case 'w': break;
case 'D': dP[i] = 43 + getSameCodeLength('D',i,cronixieDisplay); i = i+dP[i]-43; break;
case 'd': dP[i] = 93 + getSameCodeLength('d',i,cronixieDisplay); i = i+dP[i]-93; break;
case '0': dP[i] = 0; break;
case '1': dP[i] = 1; break;
case '2': dP[i] = 2; break;
case '3': dP[i] = 3; break;
case '4': dP[i] = 4; break;
case '5': dP[i] = 5; break;
case '6': dP[i] = 6; break;
case '7': dP[i] = 7; break;
case '8': dP[i] = 8; break;
case '9': dP[i] = 9; break;
//case 'V': break; //user var0
//case 'v': break; //user var1
}
}
DEBUG_PRINT("result ");
for (int i = 0; i < 5; i++)
{
DEBUG_PRINT((int)dP[i]);
DEBUG_PRINT(" ");
}
DEBUG_PRINTLN((int)dP[5]);
_overlayCronixie(); // refresh
}
void _overlayCronixie()
{
byte h = hour(localTime);
byte h0 = h;
byte m = minute(localTime);
byte s = second(localTime);
byte d = day(localTime);
byte mi = month(localTime);
int y = year(localTime);
//this has to be changed in time for 22nd century
y -= 2000; if (y<0) y += 30; //makes countdown work
if (useAMPM && !countdownMode)
{
if (h>12) h-=12;
else if (h==0) h+=12;
}
for (int i = 0; i < 6; i++)
{
if (dP[i] < 12) _digitOut[i] = dP[i];
else {
if (dP[i] < 65)
{
switch(dP[i])
{
case 21: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; i++; break; //HH
case 25: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; i++; break; //MM
case 31: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; i++; break; //SS
case 20: _digitOut[i] = h- (h/10)*10; break; //H
case 24: _digitOut[i] = m/10; break; //M
case 30: _digitOut[i] = s/10; break; //S
case 43: _digitOut[i] = weekday(localTime); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //D
case 44: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; i++; break; //DD
case 40: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; i++; break; //II
case 37: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //YY
case 39: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //YYYY
//case 16: _digitOut[i+2] = ((h0/3)&1)?1:0; i++; //BBB (BBBB NI)
//case 15: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:0; i++; //BB
case 14: _digitOut[i] = (h0>11)?1:0; break; //B
}
} else
{
switch(dP[i])
{
case 71: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //hh
case 75: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //mm
case 81: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ss
//case 66: _digitOut[i+2] = ((h0/3)&1)?1:10; i++; //bbb (bbbb NI)
//case 65: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:10; i++; //bb
case 64: _digitOut[i] = (h0>11)?1:10; break; //b
case 93: _digitOut[i] = weekday(localTime); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //d
case 94: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //dd
case 90: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ii
case 87: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //yy
case 89: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //yyyy
}
}
}
}
}
void handleOverlayDraw()
{
byte offsets[] = {5, 0, 6, 1, 7, 2, 8, 3, 9, 4};
for (uint16_t i = 0; i < 6; i++)
{
byte o = 10*i;
byte excl = 10;
if(_digitOut[i] < 10) excl = offsets[_digitOut[i]];
excl += o;
if (backlight && _digitOut[i] <11)
{
uint32_t col = strip.gamma32(strip.getSegment(0).colors[1]);
for (uint16_t j=o; j< o+10; j++) {
if (j != excl) strip.setPixelColor(j, col);
}
} else
{
for (uint16_t j=o; j< o+10; j++) {
if (j != excl) strip.setPixelColor(j, 0);
}
}
}
}
void addToJsonState(JsonObject& root)
{
root["nx"] = cronixieDisplay;
}
void readFromJsonState(JsonObject& root)
{
if (root["nx"].is<const char*>()) {
strncpy(cronixieDisplay, root["nx"], 6);
}
}
void addToConfig(JsonObject& root)
{
JsonObject top = root.createNestedObject(F("Cronixie"));
top["backlight"] = backlight;
}
bool readFromConfig(JsonObject& root)
{
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
JsonObject top = root[F("Cronixie")];
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top["backlight"], backlight);
return configComplete;
}
uint16_t getId()
{
return USERMOD_ID_CRONIXIE;
}
};

View File

@@ -207,6 +207,17 @@ class MyExampleUsermod : public Usermod {
return configComplete;
}
/*
* handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors.
* Use this to blank out some LEDs or set them to a different color regardless of the set effect mode.
* Commonly used for custom clocks (Cronixie, 7 segment)
*/
void handleOverlayDraw()
{
//strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black
}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).

View File

@@ -12,6 +12,7 @@ class TFTs : public TFT_eSPI {
private:
uint8_t digits[NUM_DIGITS];
// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.
@@ -33,7 +34,16 @@ private:
}
uint16_t output_buffer[TFT_HEIGHT][TFT_WIDTH];
int16_t w = 135, h = 240, x = 0, y = 0, bufferedDigit = 255;
uint16_t digitR, digitG, digitB, dimming = 255;
uint32_t digitColor = 0;
void drawBuffer() {
bool oldSwapBytes = getSwapBytes();
setSwapBytes(true);
pushImage(x, y, w, h, (uint16_t *)output_buffer);
setSwapBytes(oldSwapBytes);
}
// These BMP functions are stolen directly from the TFT_SPIFFS_BMP example in the TFT_eSPI library.
// Unfortunately, they aren't part of the library itself, so I had to copy them.
@@ -41,44 +51,69 @@ private:
//// BEGIN STOLEN CODE
// Draw directly from file stored in RGB565 format
// Draw directly from file stored in RGB565 format. Fastest
bool drawBin(const char *filename) {
fs::File bmpFS;
// Open requested file on SD card
bmpFS = WLED_FS.open(filename, "r");
if (!bmpFS)
{
Serial.print(F("File not found: "));
Serial.println(filename);
return(false);
}
size_t sz = bmpFS.size();
if (sz <= 64800)
{
bool oldSwapBytes = getSwapBytes();
setSwapBytes(true);
int16_t h = sz / (135 * 2);
//draw img that is shorter than 240pix into the center
int16_t y = (height() - h) /2;
bmpFS.read((uint8_t *) output_buffer,sz);
if (!realtimeMode || realtimeOverride) strip.service();
pushImage(0, y, 135, h, (uint16_t *)output_buffer);
setSwapBytes(oldSwapBytes);
if (sz > 64800) {
bmpFS.close();
return false;
}
uint16_t r, g, b, dimming = 255;
int16_t row, col;
//draw img that is shorter than 240pix into the center
w = 135;
h = sz / (w * 2);
x = 0;
y = (height() - h) /2;
uint8_t lineBuffer[w * 2];
if (!realtimeMode || realtimeOverride) strip.service();
// 0,0 coordinates are top left
for (row = 0; row < h; row++) {
bmpFS.read(lineBuffer, sizeof(lineBuffer));
uint8_t PixM, PixL;
// Colors are already in 16-bit R5, G6, B5 format
for (col = 0; col < w; col++)
{
if (dimming == 255 && !digitColor) { // not needed, copy directly
output_buffer[row][col] = (lineBuffer[col*2+1] << 8) | (lineBuffer[col*2]);
} else {
// 16 BPP pixel format: R5, G6, B5 ; bin: RRRR RGGG GGGB BBBB
PixM = lineBuffer[col*2+1];
PixL = lineBuffer[col*2];
// align to 8-bit value (MSB left aligned)
r = (PixM) & 0xF8;
g = ((PixM << 5) | (PixL >> 3)) & 0xFC;
b = (PixL << 3) & 0xF8;
r *= dimming; g *= dimming; b *= dimming;
r = r >> 8; g = g >> 8; b = b >> 8;
if (digitColor) { // grayscale pixel coloring
uint8_t l = (r > g) ? ((r > b) ? r:b) : ((g > b) ? g:b);
r = g = b = l;
r *= digitR; g *= digitG; b *= digitB;
r = r >> 8; g = g >> 8; b = b >> 8;
}
output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
}
}
drawBuffer();
bmpFS.close();
return(true);
return true;
}
bool drawBmp(const char *filename) {
@@ -87,53 +122,52 @@ private:
// Open requested file on SD card
bmpFS = WLED_FS.open(filename, "r");
if (!bmpFS)
{
Serial.print(F("File not found: "));
Serial.println(filename);
return(false);
}
uint32_t seekOffset;
int16_t w, h, row;
uint8_t r, g, b;
uint32_t seekOffset, headerSize, paletteSize = 0;
int16_t row;
uint16_t r, g, b, dimming = 255, bitDepth;
uint16_t magic = read16(bmpFS);
if (magic == 0xFFFF) {
if (magic != ('B' | ('M' << 8))) { // File not found or not a BMP
Serial.println(F("BMP not found!"));
bmpFS.close();
return(false);
}
if (magic != 0x4D42) {
Serial.print(F("File not a BMP. Magic: "));
Serial.println(magic);
bmpFS.close();
return(false);
return false;
}
read32(bmpFS);
read32(bmpFS);
seekOffset = read32(bmpFS);
read32(bmpFS);
w = read32(bmpFS);
h = read32(bmpFS);
read32(bmpFS); // filesize in bytes
read32(bmpFS); // reserved
seekOffset = read32(bmpFS); // start of bitmap
headerSize = read32(bmpFS); // header size
w = read32(bmpFS); // width
h = read32(bmpFS); // height
read16(bmpFS); // color planes (must be 1)
bitDepth = read16(bmpFS);
if ((read16(bmpFS) != 1) || (read16(bmpFS) != 24) || (read32(bmpFS) != 0)) {
if (read32(bmpFS) != 0 || (bitDepth != 24 && bitDepth != 1 && bitDepth != 4 && bitDepth != 8)) {
Serial.println(F("BMP format not recognized."));
bmpFS.close();
return(false);
return false;
}
//draw img that is shorter than 240pix into the center
int16_t y = (height() - h) /2;
uint32_t palette[256];
if (bitDepth <= 8) // 1,4,8 bit bitmap: read color palette
{
read32(bmpFS); read32(bmpFS); read32(bmpFS); // size, w resolution, h resolution
paletteSize = read32(bmpFS);
if (paletteSize == 0) paletteSize = bitDepth * bitDepth; //if 0, size is 2^bitDepth
bmpFS.seek(14 + headerSize); // start of color palette
for (uint16_t i = 0; i < paletteSize; i++) {
palette[i] = read32(bmpFS);
}
}
// draw img that is shorter than 240pix into the center
x = (width() - w) /2;
y = (height() - h) /2;
bool oldSwapBytes = getSwapBytes();
setSwapBytes(true);
bmpFS.seek(seekOffset);
uint16_t padding = (4 - ((w * 3) & 3)) & 3;
uint8_t lineBuffer[w * 3 + padding];
uint32_t lineSize = ((bitDepth * w +31) >> 5) * 4;
uint8_t lineBuffer[lineSize];
uint8_t serviceStrip = (!realtimeMode || realtimeOverride) ? 7 : 0;
// row is decremented as the BMP image is drawn bottom up
@@ -142,23 +176,121 @@ private:
bmpFS.read(lineBuffer, sizeof(lineBuffer));
uint8_t* bptr = lineBuffer;
// Convert 24 to 16 bit colours while copying to output buffer.
// Convert 24 to 16 bit colors while copying to output buffer.
for (uint16_t col = 0; col < w; col++)
{
b = *bptr++;
g = *bptr++;
r = *bptr++;
output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
if (bitDepth == 24) {
b = *bptr++;
g = *bptr++;
r = *bptr++;
} else {
uint32_t c = 0;
if (bitDepth == 8) {
c = palette[*bptr++];
}
else if (bitDepth == 4) {
c = palette[(*bptr >> ((col & 0x01)?0:4)) & 0x0F];
if (col & 0x01) bptr++;
}
else { // bitDepth == 1
c = palette[(*bptr >> (7 - (col & 0x07))) & 0x01];
if ((col & 0x07) == 0x07) bptr++;
}
b = c; g = c >> 8; r = c >> 16;
}
if (dimming != 255) { // only dimm when needed
r *= dimming; g *= dimming; b *= dimming;
r = r >> 8; g = g >> 8; b = b >> 8;
}
if (digitColor) { // grayscale pixel coloring
uint8_t l = (r > g) ? ((r > b) ? r:b) : ((g > b) ? g:b);
r = g = b = l;
r *= digitR; g *= digitG; b *= digitB;
r = r >> 8; g = g >> 8; b = b >> 8;
}
output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xFF) >> 3);
}
}
pushImage(0, y, w, h, (uint16_t *)output_buffer);
setSwapBytes(oldSwapBytes);
drawBuffer();
bmpFS.close();
return(true);
return true;
}
bool drawClk(const char *filename) {
fs::File bmpFS;
// Open requested file on SD card
bmpFS = WLED_FS.open(filename, "r");
if (!bmpFS)
{
Serial.print("File not found: ");
Serial.println(filename);
return false;
}
uint16_t r, g, b, dimming = 255, magic;
int16_t row, col;
magic = read16(bmpFS);
if (magic != 0x4B43) { // look for "CK" header
Serial.print(F("File not a CLK. Magic: "));
Serial.println(magic);
bmpFS.close();
return false;
}
w = read16(bmpFS);
h = read16(bmpFS);
x = (width() - w) / 2;
y = (height() - h) / 2;
uint8_t lineBuffer[w * 2];
if (!realtimeMode || realtimeOverride) strip.service();
// 0,0 coordinates are top left
for (row = 0; row < h; row++) {
bmpFS.read(lineBuffer, sizeof(lineBuffer));
uint8_t PixM, PixL;
// Colors are already in 16-bit R5, G6, B5 format
for (col = 0; col < w; col++)
{
if (dimming == 255 && !digitColor) { // not needed, copy directly
output_buffer[row][col+x] = (lineBuffer[col*2+1] << 8) | (lineBuffer[col*2]);
} else {
// 16 BPP pixel format: R5, G6, B5 ; bin: RRRR RGGG GGGB BBBB
PixM = lineBuffer[col*2+1];
PixL = lineBuffer[col*2];
// align to 8-bit value (MSB left aligned)
r = (PixM) & 0xF8;
g = ((PixM << 5) | (PixL >> 3)) & 0xFC;
b = (PixL << 3) & 0xF8;
r *= dimming; g *= dimming; b *= dimming;
r = r >> 8; g = g >> 8; b = b >> 8;
if (digitColor) { // grayscale pixel coloring
uint8_t l = (r > g) ? ((r > b) ? r:b) : ((g > b) ? g:b);
r = g = b = l;
r *= digitR; g *= digitG; b *= digitB;
r = r >> 8; g = g >> 8; b = b >> 8;
}
output_buffer[row][col+x] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
}
}
drawBuffer();
bmpFS.close();
return true;
}
public:
TFTs() : TFT_eSPI(), chip_select()
{ for (uint8_t digit=0; digit < NUM_DIGITS; digit++) digits[digit] = 0; }
@@ -167,6 +299,9 @@ public:
enum show_t { no, yes, force };
// A digit of 0xFF means blank the screen.
const static uint8_t blanked = 255;
uint8_t tubeSegment = 1;
uint8_t digitOffset = 0;
void begin() {
pinMode(TFT_ENABLE_PIN, OUTPUT);
@@ -182,34 +317,60 @@ public:
void showDigit(uint8_t digit) {
chip_select.setDigit(digit);
uint8_t digitToDraw = digits[digit];
if (digitToDraw < 10) digitToDraw += digitOffset;
if (digits[digit] == blanked) {
fillScreen(TFT_BLACK);
if (digitToDraw == blanked) {
fillScreen(TFT_BLACK); return;
}
else {
// Filenames are no bigger than "255.bmp\0"
char file_name[10];
sprintf(file_name, "/%d.bmp", digits[digit]);
if (WLED_FS.exists(file_name)) {
drawBmp(file_name);
} else {
sprintf(file_name, "/%d.bin", digits[digit]);
drawBin(file_name);
}
// if last digit was the same, skip loading from FS to buffer
if (!digitColor && digitToDraw == bufferedDigit) drawBuffer();
digitR = R(digitColor); digitG = G(digitColor); digitB = B(digitColor);
// Filenames are no bigger than "254.bmp\0"
char file_name[10];
// Fastest, raw RGB565
sprintf(file_name, "/%d.bin", digitToDraw);
if (WLED_FS.exists(file_name)) {
if (drawBin(file_name)) bufferedDigit = digitToDraw;
return;
}
}
// Fast, raw RGB565, see https://github.com/aly-fly/EleksTubeHAX on how to create this clk format
sprintf(file_name, "/%d.clk", digitToDraw);
if (WLED_FS.exists(file_name)) {
if (drawClk(file_name)) bufferedDigit = digitToDraw;
return;
}
// Slow, regular RGB888 or 1,4,8 bit palette BMP
sprintf(file_name, "/%d.bmp", digitToDraw);
if (drawBmp(file_name)) bufferedDigit = digitToDraw;
return;
}
void setDigit(uint8_t digit, uint8_t value, show_t show=yes) {
uint8_t old_value = digits[digit];
digits[digit] = value;
// Color in grayscale bitmaps if Segment 1 exists
// TODO If secondary and tertiary are black, color all in primary,
// else color first three from Seg 1 color slots and last three from Seg 2 color slots
WS2812FX::Segment& seg1 = strip.getSegment(tubeSegment);
if (seg1.isActive()) {
digitColor = strip.getPixelColor(seg1.start + digit);
dimming = seg1.opacity;
} else {
digitColor = 0;
dimming = 255;
}
if (show != no && (old_value != value || show == force)) {
showDigit(digit);
}
}
uint8_t getDigit(uint8_t digit) { return digits[digit]; }
uint8_t getDigit(uint8_t digit) {return digits[digit];}
void showAllDigits() { for (uint8_t digit=0; digit < NUM_DIGITS; digit++) showDigit(digit); }
void showAllDigits() {for (uint8_t digit=0; digit < NUM_DIGITS; digit++) showDigit(digit);}
// Making chip_select public so we don't have to proxy all methods, and the caller can just use it directly.
ChipSelect chip_select;

View File

@@ -5,16 +5,17 @@ It enables running all WLED effects on the background SK6812 lighting, while dis
Code is largely based on https://github.com/SmittyHalibut/EleksTubeHAX by Mark Smith!
Supported:
- Display with custom bitmaps or raw RGB565 images (.bin) from filesystem
- Display with custom bitmaps (.bmp) or raw RGB565 images (.bin) from filesystem
- Background lighting
- Power button
- All 4 hardware buttons
- RTC (with RTC usermod)
- Standard WLED time features (NTP, DST, timezones)
Not supported:
- 3 navigation buttons, on-device setup
- On-device setup with buttons (WiFi setup only)
Your images must be exactly 135 pixels wide and 1-240 pixels high.
Your images must be 1-135 pixels wide and 1-240 pixels high.
For BMP, 1, 4, 8, and 24 bits per pixel formats are supported.
## Installation
@@ -25,7 +26,20 @@ Use LED pin 12, relay pin 27 and button pin 34.
## Use of RGB565 images
Binary 16-bit per pixel RGB565 format `.bin` images are now supported. This has the benefit of only using 2/3rds of the file size a `.bmp` has.
Binary 16-bit per pixel RGB565 format `.bin` and `.clk` images are now supported. This has the benefit of only using 2/3rds of the file size a 24 BPP `.bmp` has.
The drawback is that this format cannot be handled by common image programs and that an extra conversion step is needed.
You can use https://lvgl.io/tools/imageconverter to convert your .bmp to a .bin file (settings `True color` and `Binary RGB565`)
Thank you to @RedNax67 for adding .bin support.
You can use https://lvgl.io/tools/imageconverter to convert your .bmp to a .bin file (settings `True color` and `Binary RGB565`).
Thank you to @RedNax67 for adding .bin and .clk support.
For most clockface designs, using 4 or 8 BPP BMP formats will save even more file size:
| Bits per pixel | File size in kB (for 135x240 img) | % of 24 BPP BMP | Max unique colors
| --- | --- | --- | --- |
24 | 98 | 100% | 16M (66K)
16 (.clk) | 64.8 | 66% | 66K
8 | 33.7 | 34% | 256
4 | 16.4 | 17% | 16
1 | 4.9 | 5% | 2
Comparison 1 vs. 4 vs. 8 vs. 24 BPP. With this clockface on the actual clock, 4 bit looks good, and 8 bit is almost indistinguishable from 24 bit.
![comparison](https://user-images.githubusercontent.com/21045690/156899667-5b55ed9f-6e03-4066-b2aa-1260e9570369.png)

View File

@@ -6,6 +6,13 @@
class ElekstubeIPSUsermod : public Usermod {
private:
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _tubeSeg[];
static const char _digitOffset[];
char cronixieDisplay[7] = "HHMMSS";
TFTs tfts;
void updateClockDisplay(TFTs::show_t show=TFTs::yes) {
bool set[6] = {false};
@@ -21,6 +28,8 @@ class ElekstubeIPSUsermod : public Usermod {
set[i] = false; //display HHMMSS time
}
}
uint8_t hr = hour(localTime);
uint8_t hrTens = hr/10;
uint8_t mi = minute(localTime);
@@ -37,6 +46,10 @@ class ElekstubeIPSUsermod : public Usermod {
unsigned long lastTime = 0;
public:
uint8_t lastBri;
uint32_t lastCols[6];
TFTs::show_t fshow=TFTs::yes;
void setup() {
tfts.begin();
tfts.fillScreen(TFT_BLACK);
@@ -47,14 +60,99 @@ class ElekstubeIPSUsermod : public Usermod {
}
void loop() {
if (toki.isTick()) {
updateLocalTime();
updateClockDisplay();
if (!toki.isTick()) return;
updateLocalTime();
WS2812FX::Segment& seg1 = strip.getSegment(tfts.tubeSegment);
if (seg1.isActive()) {
bool update = false;
if (seg1.opacity != lastBri) update = true;
lastBri = seg1.opacity;
for (uint8_t i = 0; i < 6; i++) {
uint32_t c = strip.getPixelColor(seg1.start + i);
if (c != lastCols[i]) update = true;
lastCols[i] = c;
}
if (update) fshow=TFTs::force;
} else if (lastCols[0] != 0) { // Segment 1 deleted
fshow=TFTs::force;
lastCols[0] = 0;
}
updateClockDisplay(fshow);
fshow=TFTs::yes;
}
/**
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
*/
void addToConfig(JsonObject &root) {
// we add JSON object: {"EleksTubeIPS": {"tubeSegment": 1, "digitOffset": 0}}
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_tubeSeg)] = tfts.tubeSegment;
top[FPSTR(_digitOffset)] = tfts.digitOffset;
DEBUG_PRINTLN(F("EleksTube config saved."));
}
/**
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool readFromConfig(JsonObject &root) {
// we look for JSON object: {"EleksTubeIPS": {"tubeSegment": 1, "digitOffset": 0}}
DEBUG_PRINT(FPSTR(_name));
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
tfts.tubeSegment = top[FPSTR(_tubeSeg)] | tfts.tubeSegment;
uint8_t digitOffsetPrev = tfts.digitOffset;
tfts.digitOffset = top[FPSTR(_digitOffset)] | tfts.digitOffset;
if (tfts.digitOffset > 240) tfts.digitOffset = 240;
if (tfts.digitOffset != digitOffsetPrev) fshow=TFTs::force;
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_digitOffset)].isNull();
}
/*
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void addToJsonState(JsonObject& root)
{
root["nx"] = cronixieDisplay;
root[FPSTR(_digitOffset)] = tfts.digitOffset;
}
/*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject& root)
{
if (root["nx"].is<const char*>()) {
strncpy(cronixieDisplay, root["nx"], 6);
}
uint8_t digitOffsetPrev = tfts.digitOffset;
tfts.digitOffset = root[FPSTR(_digitOffset)] | tfts.digitOffset;
if (tfts.digitOffset > 240) tfts.digitOffset = 240;
if (tfts.digitOffset != digitOffsetPrev) fshow=TFTs::force;
}
uint16_t getId()
{
return USERMOD_ID_ELEKSTUBE_IPS;
}
};
};
// strings to reduce flash memory usage (used more than twice)
const char ElekstubeIPSUsermod::_name[] PROGMEM = "EleksTubeIPS";
const char ElekstubeIPSUsermod::_tubeSeg[] PROGMEM = "tubeSegment";
const char ElekstubeIPSUsermod::_digitOffset[] PROGMEM = "digitOffset";

321
usermods/MY9291/MY92xx.h Normal file
View File

@@ -0,0 +1,321 @@
/*
MY92XX LED Driver for Arduino
Based on the C driver by MaiKe Labs
Copyright (c) 2016 - 2026 MaiKe Labs
Copyright (C) 2017 - 2018 Xose Pérez for the Arduino compatible library
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _my92xx_h
#define _my92xx_h
#include <Arduino.h>
#ifdef DEBUG_MY92XX
#if ARDUINO_ARCH_ESP8266
#define DEBUG_MSG_MY92XX(...) DEBUG_MY92XX.printf( __VA_ARGS__ )
#elif ARDUINO_ARCH_AVR
#define DEBUG_MSG_MY92XX(...) { char buffer[80]; snprintf(buffer, sizeof(buffer), __VA_ARGS__ ); DEBUG_MY92XX.print(buffer); }
#endif
#else
#define DEBUG_MSG_MY92XX(...)
#endif
typedef enum my92xx_model_t {
MY92XX_MODEL_MY9291 = 0X00,
MY92XX_MODEL_MY9231 = 0X01,
} my92xx_model_t;
typedef enum my92xx_cmd_one_shot_t {
MY92XX_CMD_ONE_SHOT_DISABLE = 0X00,
MY92XX_CMD_ONE_SHOT_ENFORCE = 0X01,
} my92xx_cmd_one_shot_t;
typedef enum my92xx_cmd_reaction_t {
MY92XX_CMD_REACTION_FAST = 0X00,
MY92XX_CMD_REACTION_SLOW = 0X01,
} my92xx_cmd_reaction_t;
typedef enum my92xx_cmd_bit_width_t {
MY92XX_CMD_BIT_WIDTH_16 = 0X00,
MY92XX_CMD_BIT_WIDTH_14 = 0X01,
MY92XX_CMD_BIT_WIDTH_12 = 0X02,
MY92XX_CMD_BIT_WIDTH_8 = 0X03,
} my92xx_cmd_bit_width_t;
typedef enum my92xx_cmd_frequency_t {
MY92XX_CMD_FREQUENCY_DIVIDE_1 = 0X00,
MY92XX_CMD_FREQUENCY_DIVIDE_4 = 0X01,
MY92XX_CMD_FREQUENCY_DIVIDE_16 = 0X02,
MY92XX_CMD_FREQUENCY_DIVIDE_64 = 0X03,
} my92xx_cmd_frequency_t;
typedef enum my92xx_cmd_scatter_t {
MY92XX_CMD_SCATTER_APDM = 0X00,
MY92XX_CMD_SCATTER_PWM = 0X01,
} my92xx_cmd_scatter_t;
typedef struct {
my92xx_cmd_scatter_t scatter : 1;
my92xx_cmd_frequency_t frequency : 2;
my92xx_cmd_bit_width_t bit_width : 2;
my92xx_cmd_reaction_t reaction : 1;
my92xx_cmd_one_shot_t one_shot : 1;
unsigned char resv : 1;
} __attribute__((aligned(1), packed)) my92xx_cmd_t;
#define MY92XX_COMMAND_DEFAULT { \
.scatter = MY92XX_CMD_SCATTER_APDM, \
.frequency = MY92XX_CMD_FREQUENCY_DIVIDE_1, \
.bit_width = MY92XX_CMD_BIT_WIDTH_8, \
.reaction = MY92XX_CMD_REACTION_FAST, \
.one_shot = MY92XX_CMD_ONE_SHOT_DISABLE, \
.resv = 0 \
}
class my92xx {
public:
my92xx(my92xx_model_t model, unsigned char chips, unsigned char di, unsigned char dcki, my92xx_cmd_t command);
unsigned char getChannels();
void setChannel(unsigned char channel, unsigned int value);
unsigned int getChannel(unsigned char channel);
void setState(bool state);
bool getState();
void update();
private:
void _di_pulse(unsigned int times);
void _dcki_pulse(unsigned int times);
void _set_cmd(my92xx_cmd_t command);
void _send();
void _write(unsigned int data, unsigned char bit_length);
my92xx_cmd_t _command;
my92xx_model_t _model = MY92XX_MODEL_MY9291;
unsigned char _chips = 1;
unsigned char _channels;
uint16_t* _value;
bool _state = false;
unsigned char _pin_di;
unsigned char _pin_dcki;
};
#if ARDUINO_ARCH_ESP8266
extern "C" {
void os_delay_us(unsigned int);
}
#elif ARDUINO_ARCH_AVR
#define os_delay_us delayMicroseconds
#endif
void my92xx::_di_pulse(unsigned int times) {
for (unsigned int i = 0; i < times; i++) {
digitalWrite(_pin_di, HIGH);
digitalWrite(_pin_di, LOW);
}
}
void my92xx::_dcki_pulse(unsigned int times) {
for (unsigned int i = 0; i < times; i++) {
digitalWrite(_pin_dcki, HIGH);
digitalWrite(_pin_dcki, LOW);
}
}
void my92xx::_write(unsigned int data, unsigned char bit_length) {
unsigned int mask = (0x01 << (bit_length - 1));
for (unsigned int i = 0; i < bit_length / 2; i++) {
digitalWrite(_pin_dcki, LOW);
digitalWrite(_pin_di, (data & mask) ? HIGH : LOW);
digitalWrite(_pin_dcki, HIGH);
data = data << 1;
digitalWrite(_pin_di, (data & mask) ? HIGH : LOW);
digitalWrite(_pin_dcki, LOW);
digitalWrite(_pin_di, LOW);
data = data << 1;
}
}
void my92xx::_set_cmd(my92xx_cmd_t command) {
// ets_intr_lock();
// TStop > 12us.
os_delay_us(12);
// Send 12 DI pulse, after 6 pulse's falling edge store duty data, and 12
// pulse's rising edge convert to command mode.
_di_pulse(12);
// Delay >12us, begin send CMD data
os_delay_us(12);
// Send CMD data
unsigned char command_data = *(unsigned char*)(&command);
for (unsigned char i = 0; i < _chips; i++) {
_write(command_data, 8);
}
// TStart > 12us. Delay 12 us.
os_delay_us(12);
// Send 16 DI pulseat 14 pulse's falling edge store CMD data, and
// at 16 pulse's falling edge convert to duty mode.
_di_pulse(16);
// TStop > 12us.
os_delay_us(12);
// ets_intr_unlock();
}
void my92xx::_send() {
#ifdef DEBUG_MY92XX
DEBUG_MSG_MY92XX("[MY92XX] Refresh: %s (", _state ? "ON" : "OFF");
for (unsigned char channel = 0; channel < _channels; channel++) {
DEBUG_MSG_MY92XX(" %d", _value[channel]);
}
DEBUG_MSG_MY92XX(" )\n");
#endif
unsigned char bit_length = 8;
switch (_command.bit_width) {
case MY92XX_CMD_BIT_WIDTH_16:
bit_length = 16;
break;
case MY92XX_CMD_BIT_WIDTH_14:
bit_length = 14;
break;
case MY92XX_CMD_BIT_WIDTH_12:
bit_length = 12;
break;
case MY92XX_CMD_BIT_WIDTH_8:
bit_length = 8;
break;
default:
bit_length = 8;
break;
}
// ets_intr_lock();
// TStop > 12us.
os_delay_us(12);
// Send color data
for (unsigned char channel = 0; channel < _channels; channel++) {
_write(_state ? _value[channel] : 0, bit_length);
}
// TStart > 12us. Ready for send DI pulse.
os_delay_us(12);
// Send 8 DI pulse. After 8 pulse falling edge, store old data.
_di_pulse(8);
// TStop > 12us.
os_delay_us(12);
// ets_intr_unlock();
}
// -----------------------------------------------------------------------------
unsigned char my92xx::getChannels() {
return _channels;
}
void my92xx::setChannel(unsigned char channel, unsigned int value) {
if (channel < _channels) {
_value[channel] = value;
}
}
unsigned int my92xx::getChannel(unsigned char channel) {
if (channel < _channels) {
return _value[channel];
}
return 0;
}
bool my92xx::getState() {
return _state;
}
void my92xx::setState(bool state) {
_state = state;
}
void my92xx::update() {
_send();
}
// -----------------------------------------------------------------------------
my92xx::my92xx(my92xx_model_t model, unsigned char chips, unsigned char di, unsigned char dcki, my92xx_cmd_t command) : _command(command) {
_model = model;
_chips = chips;
_pin_di = di;
_pin_dcki = dcki;
// Init channels
if (_model == MY92XX_MODEL_MY9291) {
_channels = 4 * _chips;
}
else if (_model == MY92XX_MODEL_MY9231) {
_channels = 3 * _chips;
}
_value = new uint16_t[_channels];
for (unsigned char i = 0; i < _channels; i++) {
_value[i] = 0;
}
// Init GPIO
pinMode(_pin_di, OUTPUT);
pinMode(_pin_dcki, OUTPUT);
digitalWrite(_pin_di, LOW);
digitalWrite(_pin_dcki, LOW);
// Clear all duty register
_dcki_pulse(32 * _chips);
// Send init command
_set_cmd(command);
DEBUG_MSG_MY92XX("[MY92XX] Initialized\n");
}
#endif

View File

@@ -0,0 +1,45 @@
#pragma once
#include "wled.h"
#include "MY92xx.h"
#define MY92XX_MODEL MY92XX_MODEL_MY9291
#define MY92XX_CHIPS 1
#define MY92XX_DI_PIN 13
#define MY92XX_DCKI_PIN 15
#define MY92XX_RED 0
#define MY92XX_GREEN 1
#define MY92XX_BLUE 2
#define MY92XX_WHITE 3
class MY9291Usermod : public Usermod {
private:
my92xx _my92xx = my92xx(MY92XX_MODEL, MY92XX_CHIPS, MY92XX_DI_PIN, MY92XX_DCKI_PIN, MY92XX_COMMAND_DEFAULT);
public:
void setup() {
_my92xx.setState(true);
}
void connected() {
}
void loop() {
uint32_t c = strip.getPixelColor(0);
int w = ((c >> 24) & 0xff) * bri / 255.0;
int r = ((c >> 16) & 0xff) * bri / 255.0;
int g = ((c >> 8) & 0xff) * bri / 255.0;
int b = (c & 0xff) * bri / 255.0;
_my92xx.setChannel(MY92XX_RED, r);
_my92xx.setChannel(MY92XX_GREEN, g);
_my92xx.setChannel(MY92XX_BLUE, b);
_my92xx.setChannel(MY92XX_WHITE, w);
_my92xx.update();
}
uint16_t getId() {
return USERMOD_ID_MY9291;
}
};

View File

@@ -19,8 +19,8 @@ You will also need `-D USERMOD_DALLASTEMPERATURE`.
All of the parameters are configured during run-time using Usermods settings page.
This includes:
* PWM output pin
* tacho input pin
* PWM output pin (can be configured at compile time `-D PWM_PIN=xx`)
* tacho input pin (can be configured at compile time `-D TACHO_PIN=xx`)
* sampling frequency in seconds
* threshold temperature in degees C

View File

@@ -10,6 +10,13 @@
// https://github.com/KlausMu/esp32-fan-controller/tree/main/src
// adapted for WLED usermod by @blazoncek
#ifndef TACHO_PIN
#define TACHO_PIN -1
#endif
#ifndef PWM_PIN
#define PWM_PIN -1
#endif
// tacho counter
static volatile unsigned long counter_rpm = 0;
@@ -37,8 +44,8 @@ class PWMFanUsermod : public Usermod {
#endif
// configurable parameters
int8_t tachoPin = -1;
int8_t pwmPin = -1;
int8_t tachoPin = TACHO_PIN;
int8_t pwmPin = PWM_PIN;
uint8_t tachoUpdateSec = 30;
float targetTemperature = 25.0;
uint8_t minPWMValuePct = 50;

View File

@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html>
<head><meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta charset="utf-8">
<title>Blinds</title>
<script>
strA = "";
function send()
{
nocache = "&nocache=" + Math.random() * 1000000;
var request = new XMLHttpRequest();
// send HTTP request
request.open("GET", "win/" + strA +nocache, true);
request.send(null);
strA = "";
}
function up()
{
strA = "&U0=2";
send();
}
function down()
{
strA = "&U0=1";
send();
}
function OpenSettings()
{
window.open("/settings", "_self");
}
</script>
<style>
body {
text-align: center;
background: linear-gradient(45deg,#0ca,#0ac);
height: 100%;
margin: 0;
background-repeat: no-repeat;
background-attachment: fixed;
}
html {
height: 100%;
}
svg {
width: 30vw;
padding: 2vh;
}
.tool_box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
<style id="holderjs-style" type="text/css"></style></head>
<body class=" __plain_text_READY__">
<svg style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="icon-box-add" viewBox="0 0 32 32">
<path d="M26 2h-20l-6 6v21c0 0.552 0.448 1 1 1h30c0.552 0 1-0.448 1-1v-21l-6-6zM16 26l-10-8h6v-6h8v6h6l-10 8zM4.828 6l2-2h18.343l2 2h-22.343z"></path>
</symbol>
<symbol id="icon-box-remove" viewBox="0 0 32 32">
<path d="M26 2h-20l-6 6v21c0 0.552 0.448 1 1 1h30c0.552 0 1-0.448 1-1v-21l-6-6zM20 20v6h-8v-6h-6l10-8 10 8h-6zM4.828 6l2-2h18.343l2 2h-22.343z"></path>
</symbol>
<symbol id="icon-cog" viewBox="0 0 32 32">
<path d="M29.181 19.070c-1.679-2.908-0.669-6.634 2.255-8.328l-3.145-5.447c-0.898 0.527-1.943 0.829-3.058 0.829-3.361 0-6.085-2.742-6.085-6.125h-6.289c0.008 1.044-0.252 2.103-0.811 3.070-1.679 2.908-5.411 3.897-8.339 2.211l-3.144 5.447c0.905 0.515 1.689 1.268 2.246 2.234 1.676 2.903 0.672 6.623-2.241 8.319l3.145 5.447c0.895-0.522 1.935-0.82 3.044-0.82 3.35 0 6.067 2.725 6.084 6.092h6.289c-0.003-1.034 0.259-2.080 0.811-3.038 1.676-2.903 5.399-3.894 8.325-2.219l3.145-5.447c-0.899-0.515-1.678-1.266-2.232-2.226zM16 22.479c-3.578 0-6.479-2.901-6.479-6.479s2.901-6.479 6.479-6.479c3.578 0 6.479 2.901 6.479 6.479s-2.901 6.479-6.479 6.479z"></path>
</symbol>
</defs>
</svg>
<div id="tbB" class="tool_box">
<svg id="upb" onclick="up()"><use xlink:href="#icon-box-remove"></use></svg>
<svg id="dnb" onclick="down()"><use xlink:href="#icon-box-add"></use></svg>
<svg id="stb" onclick="OpenSettings()"><use xlink:href="#icon-cog"></use></svg>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
{"0":{},"2":{"n":"▲","win":"U0=2"},"1":{"n":"▼","win":"U0=1"}}

View File

@@ -0,0 +1,8 @@
# RelayBlinds usermod
This simple usermod toggles two relay pins momentarily (default for 500ms) when `userVar0` is set.
This can be used to e.g. "push" the buttons of a window blinds motor controller.
v1 usermod. Please replace usermod.cpp in the `wled00` directory with the one in this file.
You may upload `index.htm` to `[WLED-IP]/edit` to replace the default lighting UI with a simple Up/Down button one.
Also, a simple `presets.json` file is available, this makes the relay actions controllable via two presets to facilitate control e.g. via the default UI or Alexa.

View File

@@ -0,0 +1,83 @@
#include "wled.h"
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
//gets called once at boot. Do all initialization that doesn't depend on network here
void userSetup()
{
}
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
void userConnected()
{
}
/*
* Physical IO
*/
#define PIN_UP_RELAY 4
#define PIN_DN_RELAY 5
#define PIN_ON_TIME 500
bool upActive = false, upActiveBefore = false, downActive = false, downActiveBefore = false;
unsigned long upStartTime = 0, downStartTime = 0;
void handleRelay()
{
//up and down relays
if (userVar0) {
upActive = true;
if (userVar0 == 1) {
upActive = false;
downActive = true;
}
userVar0 = 0;
}
if (upActive)
{
if(!upActiveBefore)
{
pinMode(PIN_UP_RELAY, OUTPUT);
digitalWrite(PIN_UP_RELAY, LOW);
upActiveBefore = true;
upStartTime = millis();
DEBUG_PRINTLN("UPA");
}
if (millis()- upStartTime > PIN_ON_TIME)
{
upActive = false;
DEBUG_PRINTLN("UPN");
}
} else if (upActiveBefore)
{
pinMode(PIN_UP_RELAY, INPUT);
upActiveBefore = false;
}
if (downActive)
{
if(!downActiveBefore)
{
pinMode(PIN_DN_RELAY, OUTPUT);
digitalWrite(PIN_DN_RELAY, LOW);
downActiveBefore = true;
downStartTime = millis();
}
if (millis()- downStartTime > PIN_ON_TIME)
{
downActive = false;
}
} else if (downActiveBefore)
{
pinMode(PIN_DN_RELAY, INPUT);
downActiveBefore = false;
}
}
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
void userLoop()
{
handleRelay();
}

View File

@@ -0,0 +1,69 @@
# Si7021 to MQTT (with Home Assistant Auto Discovery) usermod
This usermod implements support for [Si7021 I²C temperature and humidity sensors](https://www.silabs.com/documents/public/data-sheets/Si7021-A20.pdf).
The sensor data will *not* be shown on the WLED UI (so far) but published via MQTT to WLED's "build in" MQTT device topic.
```
temperature: $mqttDeviceTopic/si7021_temperature
humidity: $mqttDeviceTopic/si7021_humidity
```
Additionally the following sensors can be published:
```
heat_index: $mqttDeviceTopic/si7021_heat_index
dew_point: $mqttDeviceTopic/si7021_dew_point
absolute_humidity: $mqttDeviceTopic/si7021_absolute_humidity
```
Sensor data will be updated/send every 60 seconds.
This usermod also supports Home Assistant Auto Discovery.
## Settings via Usermod Setup
- `enabled`: Enables this usermod
- `Send Dew Point, Abs. Humidity and Heat Index`: Enables additional sensors
- `Home Assistant MQTT Auto-Discovery`: Enables Home Assistant Auto Discovery
# Installation
## Hardware
Attach the Si7021 sensor to the I²C interface.
Default PINs ESP32:
```
SCL_PIN = 22;
SDA_PIN = 21;
```
Default PINs ESP8266:
```
SCL_PIN = 5;
SDA_PIN = 4;
```
## Software
Add to `build_flags` in platformio.ini:
```
-D USERMOD_SI7021_MQTT_HA
```
Add to `lib_deps` in platformio.ini:
```
adafruit/Adafruit Si7021 Library @ 1.4.0
BME280@~3.0.0
```
# Credits
- Aircoookie for making WLED
- Other usermod creators for example code (`sensors_to_mqtt` and `multi_relay` especially)
- You, for reading this

View File

@@ -0,0 +1,236 @@
#pragma once
// this is remixed from usermod_v2_SensorsToMqtt.h (sensors_to_mqtt usermod)
// and usermod_multi_relay.h (multi_relay usermod)
#include "wled.h"
#include <Adafruit_Si7021.h>
#include <EnvironmentCalculations.h> // EnvironmentCalculations::HeatIndex(), ::DewPoint(), ::AbsoluteHumidity()
Adafruit_Si7021 si7021;
#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards
uint8_t SCL_PIN = 22;
uint8_t SDA_PIN = 21;
#else //ESP8266 boards
uint8_t SCL_PIN = 5;
uint8_t SDA_PIN = 4;
#endif
class Si7021_MQTT_HA : public Usermod
{
private:
bool sensorInitialized = false;
bool mqttInitialized = false;
float sensorTemperature = 0;
float sensorHumidity = 0;
float sensorHeatIndex = 0;
float sensorDewPoint = 0;
float sensorAbsoluteHumidity= 0;
String mqttTemperatureTopic = "";
String mqttHumidityTopic = "";
String mqttHeatIndexTopic = "";
String mqttDewPointTopic = "";
String mqttAbsoluteHumidityTopic = "";
unsigned long nextMeasure = 0;
bool enabled = false;
bool haAutoDiscovery = true;
bool sendAdditionalSensors = true;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
static const char _sendAdditionalSensors[];
static const char _haAutoDiscovery[];
void _initializeSensor()
{
sensorInitialized = si7021.begin();
Serial.printf("Si7021_MQTT_HA: sensorInitialized = %d\n", sensorInitialized);
}
void _initializeMqtt()
{
mqttTemperatureTopic = String(mqttDeviceTopic) + "/si7021_temperature";
mqttHumidityTopic = String(mqttDeviceTopic) + "/si7021_humidity";
mqttHeatIndexTopic = String(mqttDeviceTopic) + "/si7021_heat_index";
mqttDewPointTopic = String(mqttDeviceTopic) + "/si7021_dew_point";
mqttAbsoluteHumidityTopic = String(mqttDeviceTopic) + "/si7021_absolute_humidity";
// Update and publish sensor data
_updateSensorData();
_publishSensorData();
if (haAutoDiscovery) {
_publishHAMqttSensor("temperature", "Temperature", mqttTemperatureTopic, "temperature", "°C");
_publishHAMqttSensor("humidity", "Humidity", mqttHumidityTopic, "humidity", "%");
if (sendAdditionalSensors) {
_publishHAMqttSensor("heat_index", "Heat Index", mqttHeatIndexTopic, "temperature", "°C");
_publishHAMqttSensor("dew_point", "Dew Point", mqttDewPointTopic, "", "°C");
_publishHAMqttSensor("absolute_humidity", "Absolute Humidity", mqttAbsoluteHumidityTopic, "", "g/m³");
}
}
mqttInitialized = true;
}
void _publishHAMqttSensor(
const String &name,
const String &friendly_name,
const String &state_topic,
const String &deviceClass,
const String &unitOfMeasurement)
{
if (WLED_MQTT_CONNECTED) {
String topic = String("homeassistant/sensor/") + mqttClientID + "/" + name + "/config";
StaticJsonDocument<300> doc;
doc["name"] = String(serverDescription) + " " + friendly_name;
doc["state_topic"] = state_topic;
doc["unique_id"] = String(mqttClientID) + name;
if (unitOfMeasurement != "")
doc["unit_of_measurement"] = unitOfMeasurement;
if (deviceClass != "")
doc["device_class"] = deviceClass;
doc["expire_after"] = 1800;
JsonObject device = doc.createNestedObject("device"); // attach the sensor to the same device
device["name"] = String(serverDescription);
device["model"] = "WLED";
device["manufacturer"] = "Aircoookie";
device["identifiers"] = String("wled-") + String(serverDescription);
device["sw_version"] = VERSION;
String payload;
serializeJson(doc, payload);
mqtt->publish(topic.c_str(), 0, true, payload.c_str());
}
}
void _updateSensorData()
{
sensorTemperature = si7021.readTemperature();
sensorHumidity = si7021.readHumidity();
// Serial.print("Si7021_MQTT_HA: Temperature: ");
// Serial.print(sensorTemperature, 2);
// Serial.print("\tHumidity: ");
// Serial.print(sensorHumidity, 2);
if (sendAdditionalSensors) {
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius);
sensorHeatIndex = EnvironmentCalculations::HeatIndex(sensorTemperature, sensorHumidity, envTempUnit);
sensorDewPoint = EnvironmentCalculations::DewPoint(sensorTemperature, sensorHumidity, envTempUnit);
sensorAbsoluteHumidity = EnvironmentCalculations::AbsoluteHumidity(sensorTemperature, sensorHumidity, envTempUnit);
// Serial.print("\tHeat Index: ");
// Serial.print(sensorHeatIndex, 2);
// Serial.print("\tDew Point: ");
// Serial.print(sensorDewPoint, 2);
// Serial.print("\tAbsolute Humidity: ");
// Serial.println(sensorAbsoluteHumidity, 2);
}
// else
// Serial.println("");
}
void _publishSensorData()
{
if (WLED_MQTT_CONNECTED) {
mqtt->publish(mqttTemperatureTopic.c_str(), 0, false, String(sensorTemperature).c_str());
mqtt->publish(mqttHumidityTopic.c_str(), 0, false, String(sensorHumidity).c_str());
if (sendAdditionalSensors) {
mqtt->publish(mqttHeatIndexTopic.c_str(), 0, false, String(sensorHeatIndex).c_str());
mqtt->publish(mqttDewPointTopic.c_str(), 0, false, String(sensorDewPoint).c_str());
mqtt->publish(mqttAbsoluteHumidityTopic.c_str(), 0, false, String(sensorAbsoluteHumidity).c_str());
}
}
}
public:
void addToConfig(JsonObject& root)
{
JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled;
top[FPSTR(_sendAdditionalSensors)] = sendAdditionalSensors;
top[FPSTR(_haAutoDiscovery)] = haAutoDiscovery;
}
bool readFromConfig(JsonObject& root)
{
JsonObject top = root[FPSTR(_name)];
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
configComplete &= getJsonValue(top[FPSTR(_sendAdditionalSensors)], sendAdditionalSensors);
configComplete &= getJsonValue(top[FPSTR(_haAutoDiscovery)], haAutoDiscovery);
return configComplete;
}
void onMqttConnect(bool sessionPresent) {
if (mqttDeviceTopic[0] != 0)
_initializeMqtt();
}
void setup()
{
if (enabled) {
Serial.println("Si7021_MQTT_HA: Starting!");
Wire.begin(SDA_PIN, SCL_PIN);
Serial.println("Si7021_MQTT_HA: Initializing sensors.. ");
_initializeSensor();
}
}
// gets called every time WiFi is (re-)connected.
void connected()
{
nextMeasure = millis() + 5000; // Schedule next measure in 5 seconds
}
void loop()
{
yield();
if (!enabled || strip.isUpdating()) return; // !sensorFound ||
unsigned long tempTimer = millis();
if (tempTimer > nextMeasure) {
nextMeasure = tempTimer + 60000; // Schedule next measure in 60 seconds
if (!sensorInitialized) {
Serial.println("Si7021_MQTT_HA: Error! Sensors not initialized in loop()!");
_initializeSensor();
return; // lets try again next loop
}
if (WLED_MQTT_CONNECTED) {
if (!mqttInitialized)
_initializeMqtt();
// Update and publish sensor data
_updateSensorData();
_publishSensorData();
}
else {
Serial.println("Si7021_MQTT_HA: Missing MQTT connection. Not publishing data");
mqttInitialized = false;
}
}
}
uint16_t getId()
{
return USERMOD_ID_SI7021_MQTT_HA;
}
};
// strings to reduce flash memory usage (used more than twice)
const char Si7021_MQTT_HA::_name[] PROGMEM = "Si7021 MQTT (Home Assistant)";
const char Si7021_MQTT_HA::_enabled[] PROGMEM = "enabled";
const char Si7021_MQTT_HA::_sendAdditionalSensors[] PROGMEM = "Send Dew Point, Abs. Humidity and Heat Index";
const char Si7021_MQTT_HA::_haAutoDiscovery[] PROGMEM = "Home Assistant MQTT Auto-Discovery";

View File

@@ -81,11 +81,13 @@ void registerUsermods()
Usermod can be configured in Usermods settings page.
* `enabled` - enable/disable usermod
* `pin` - GPIO pin where relay is attached to ESP
* `pin` - GPIO pin where relay is attached to ESP (can be configured at compile time `-D MULTI_RELAY_PINS=xx,xx,...`)
* `delay-s` - delay in seconds after on/off command is received
* `active-high` - toggle high/low activation of relay (can be used to reverse relay states)
* `external` - if enabled WLED does not control relay, it can only be triggered by external command (MQTT, HTTP, JSON or button)
* `button` - button (from LED Settings) that controls this relay
* `broadcast`- time in seconds between state broadcasts using MQTT
* `HA-discovery`- enable Home Assistant auto discovery
If there is no MultiRelay section, just save current configuration and re-open Usermods settings page.

View File

@@ -6,6 +6,10 @@
#define MULTI_RELAY_MAX_RELAYS 4
#endif
#ifndef MULTI_RELAY_PINS
#define MULTI_RELAY_PINS -1
#endif
#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
#define ON true
@@ -177,8 +181,9 @@ class MultiRelay : public Usermod {
* constructor
*/
MultiRelay() {
const int8_t defPins[] = {MULTI_RELAY_PINS};
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
_relay[i].pin = -1;
_relay[i].pin = i<sizeof(defPins) ? defPins[i] : -1;
_relay[i].delay = 0;
_relay[i].mode = false;
_relay[i].active = false;
@@ -619,7 +624,7 @@ class MultiRelay : public Usermod {
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_broadcast)].isNull();
return !top[FPSTR(_HAautodiscovery)].isNull();
}
/**

View File

@@ -64,7 +64,7 @@ class AutoSaveUsermod : public Usermod {
PSTR("~ %02d-%02d %02d:%02d:%02d ~"),
month(localTime), day(localTime),
hour(localTime), minute(localTime), second(localTime));
savePreset(autoSavePreset, true, presetNameBuffer);
savePreset(autoSavePreset, presetNameBuffer);
}
void inline displayOverlay() {

View File

@@ -0,0 +1,26 @@
# Word Clock Usermod V2
This usermod can be used to drive a wordclock with a 11x10 pixel matrix with WLED. There are also 4 additional dots for the minutes.
The visualisation is desribed in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr").
There are 2 parameters to chnage the behaviour:
active: enable/disable usermod
diplayItIs: enable/disable display of "Es ist" on the clock.
## Installation
Copy and update the example `platformio_override.ini.sample`
from the Rotary Encoder UI usermode folder to the root directory of your particular build.
This file should be placed in the same directory as `platformio.ini`.
### Define Your Options
* `USERMOD_WORDCLOCK` - define this to have this the Auto Save usermod included wled00\usermods_list.cpp
### PlatformIO requirements
No special requirements.
## Change Log
2022/03/30 initial commit

View File

@@ -0,0 +1,427 @@
#pragma once
#include "wled.h"
/*
* Usermods allow you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
*
* This usermod can be used to drive a wordclock with a 11x10 pixel matrix with WLED. There are also 4 additional dots for the minutes.
* The visualisation is desribed in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr").
* There are 2 parameters to chnage the behaviour:
*
* active: enable/disable usermod
* diplayItIs: enable/disable display of "Es ist" on the clock.
*/
class WordClockUsermod : public Usermod
{
private:
unsigned long lastTime = 0;
int lastTimeMinutes = -1;
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
bool usermodActive = false;
bool displayItIs = false;
// defines for mask sizes
#define maskSizeLeds 114
#define maskSizeMinutes 12
#define maskSizeHours 6
#define maskSizeItIs 5
#define maskSizeMinuteDots 4
// "minute" masks
const int maskMinutes[12][maskSizeMinutes] =
{
{107, 108, 109, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // :00
{ 7, 8, 9, 10, 40, 41, 42, 43, -1, -1, -1, -1}, // :05 fünf nach
{ 11, 12, 13, 14, 40, 41, 42, 43, -1, -1, -1, -1}, // :10 zehn nach
{ 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1}, // :15 viertel
{ 15, 16, 17, 18, 19, 20, 21, 40, 41, 42, 43, -1}, // :20 zwanzig nach
{ 7, 8, 9, 10, 33, 34, 35, 44, 45, 46, 47, -1}, // :25 fünf vor halb
{ 44, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1}, // :30 halb
{ 7, 8, 9, 10, 40, 41, 42, 43, 44, 45, 46, 47}, // :35 fünf nach halb
{ 15, 16, 17, 18, 19, 20, 21, 33, 34, 35, -1, -1}, // :40 zwanzig vor
{ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1}, // :45 dreiviertel
{ 11, 12, 13, 14, 33, 34, 35, -1, -1, -1, -1, -1}, // :50 zehn vor
{ 7, 8, 9, 10, 33, 34, 35, -1, -1, -1, -1, -1} // :55 fünf vor
};
// hour masks
const int maskHours[13][maskSizeHours] =
{
{ 55, 56, 57, -1, -1, -1}, // 01: ein
{ 55, 56, 57, 58, -1, -1}, // 01: eins
{ 62, 63, 64, 65, -1, -1}, // 02: zwei
{ 66, 67, 68, 69, -1, -1}, // 03: drei
{ 73, 74, 75, 76, -1, -1}, // 04: vier
{ 51, 52, 53, 54, -1, -1}, // 05: fünf
{ 77, 78, 79, 80, 81, -1}, // 06: sechs
{ 88, 89, 90, 91, 92, 93}, // 07: sieben
{ 84, 85, 86, 87, -1, -1}, // 08: acht
{102, 103, 104, 105, -1, -1}, // 09: neun
{ 99, 100, 101, 102, -1, -1}, // 10: zehn
{ 49, 50, 51, -1, -1, -1}, // 11: elf
{ 94, 95, 96, 97, 98, -1} // 12: zwölf and 00: null
};
// mask "it is"
const int maskItIs[maskSizeItIs] = {0, 1, 3, 4, 5};
// mask minute dots
const int maskMinuteDots[maskSizeMinuteDots] = {110, 111, 112, 113};
// overall mask to define which LEDs are on
int maskLedsOn[maskSizeLeds] =
{
0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0
};
// update led mask
void updateLedMask(const int wordMask[], int arraySize)
{
// loop over array
for (int x=0; x < arraySize; x++)
{
// check if mask has a valid LED number
if (wordMask[x] >= 0 && wordMask[x] < maskSizeLeds)
{
// turn LED on
maskLedsOn[wordMask[x]] = 1;
}
}
}
// set hours
void setHours(int hours, bool fullClock)
{
int index = hours;
// handle 00:xx as 12:xx
if (hours == 0)
{
index = 12;
}
// check if we get an overrun of 12 o´clock
if (hours == 13)
{
index = 1;
}
// special handling for "ein Uhr" instead of "eins Uhr"
if (hours == 1 && fullClock == true)
{
index = 0;
}
// update led mask
updateLedMask(maskHours[index], maskSizeHours);
}
// set minutes
void setMinutes(int index)
{
// update led mask
updateLedMask(maskMinutes[index], maskSizeMinutes);
}
// set minutes dot
void setSingleMinuteDots(int minutes)
{
// modulo to get minute dots
int minutesDotCount = minutes % 5;
// check if minute dots are active
if (minutesDotCount > 0)
{
// activate all minute dots until number is reached
for (int i = 0; i < minutesDotCount; i++)
{
// activate LED
maskLedsOn[maskMinuteDots[i]] = 1;
}
}
}
// update the display
void updateDisplay(uint8_t hours, uint8_t minutes)
{
// disable complete matrix at the bigging
for (int x = 0; x < maskSizeLeds; x++)
{
maskLedsOn[x] = 0;
}
// display it is/es ist if activated
if (displayItIs)
{
updateLedMask(maskItIs, maskSizeItIs);
}
// set single minute dots
setSingleMinuteDots(minutes);
// switch minutes
switch (minutes / 5)
{
case 0:
// full hour
setMinutes(0);
setHours(hours, true);
break;
case 1:
// 5 nach
setMinutes(1);
setHours(hours, false);
break;
case 2:
// 10 nach
setMinutes(2);
setHours(hours, false);
break;
case 3:
// viertel
setMinutes(3);
setHours(hours + 1, false);
break;
case 4:
// 20 nach
setMinutes(4);
setHours(hours, false);
break;
case 5:
// 5 vor halb
setMinutes(5);
setHours(hours + 1, false);
break;
case 6:
// halb
setMinutes(6);
setHours(hours + 1, false);
break;
case 7:
// 5 nach halb
setMinutes(7);
setHours(hours + 1, false);
break;
case 8:
// 20 vor
setMinutes(8);
setHours(hours + 1, false);
break;
case 9:
// viertel vor
setMinutes(9);
setHours(hours + 1, false);
break;
case 10:
// 10 vor
setMinutes(10);
setHours(hours + 1, false);
break;
case 11:
// 5 vor
setMinutes(11);
setHours(hours + 1, false);
break;
}
}
public:
//Functions called by WLED
/*
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup()
{
}
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
}
/*
* loop() is called continuously. Here you can check for events, read sensors, etc.
*
* Tips:
* 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection.
* Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker.
*
* 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
* Instead, use a timer check as shown here.
*/
void loop() {
// do it every 5 seconds
if (millis() - lastTime > 5000)
{
// check the time
int minutes = minute(localTime);
// check if we already updated this minute
if (lastTimeMinutes != minutes)
{
// update the display with new time
updateDisplay(hourFormat12(localTime), minute(localTime));
// remember last update time
lastTimeMinutes = minutes;
}
// remember last update
lastTime = millis();
}
}
/*
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor
*/
/*
void addToJsonInfo(JsonObject& root)
{
}
*/
/*
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void addToJsonState(JsonObject& root)
{
}
/*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject& root)
{
}
/*
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
* If you want to force saving the current state, use serializeConfig() in your loop().
*
* CAUTION: serializeConfig() will initiate a filesystem write operation.
* It might cause the LEDs to stutter and will cause flash wear if called too often.
* Use it sparingly and always in the loop, never in network callbacks!
*
* addToConfig() will make your settings editable through the Usermod Settings page automatically.
*
* Usermod Settings Overview:
* - Numeric values are treated as floats in the browser.
* - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float
* before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and
* doubles are not supported, numbers will be rounded to the nearest float value when being parsed.
* The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38.
* - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a
* C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod.
* Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type
* used in the Usermod when reading the value from ArduinoJson.
* - Pin values can be treated differently from an integer value by using the key name "pin"
* - "pin" can contain a single or array of integer values
* - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins
* - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin)
* - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used
*
* See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings
*
* If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work.
* You will have to add the setting to the HTML, xml.cpp and set.cpp manually.
* See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED
*
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
*/
void addToConfig(JsonObject& root)
{
JsonObject top = root.createNestedObject("WordClockUsermod");
top["active"] = usermodActive;
top["displayItIs"] = displayItIs;
}
/*
* readFromConfig() can be used to read back the custom settings you added with addToConfig().
* This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page)
*
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*
* Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings)
*
* getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present
* The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them
*
* This function is guaranteed to be called on boot, but could also be called every time settings are updated
*/
bool readFromConfig(JsonObject& root)
{
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
JsonObject top = root["WordClockUsermod"];
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top["active"], usermodActive);
configComplete &= getJsonValue(top["displayItIs"], displayItIs);
return configComplete;
}
/*
* handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors.
* Use this to blank out some LEDs or set them to a different color regardless of the set effect mode.
* Commonly used for custom clocks (Cronixie, 7 segment)
*/
void handleOverlayDraw()
{
// check if usermod is active
if (usermodActive == true)
{
// loop over all leds
for (int x = 0; x < maskSizeLeds; x++)
{
// check mask
if (maskLedsOn[x] == 0)
{
// set pixel off
strip.setPixelColor(x, RGBW32(0,0,0,0));
}
}
}
}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId()
{
return USERMOD_ID_WORDCLOCK;
}
//More methods can be added in the future, this example will then be extended.
//Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class!
};

View File

@@ -0,0 +1,35 @@
# Controlling Wiz lights
This usermod allows the control of [WiZ](https://www.wizconnected.com/en/consumer/) lights that are in the same network as the WLED controller.
The mod takes the colors from the first few pixels and sends them to the lights.
## Configuration
- Interval (ms)
- How frequently to update the WiZ lights, in milliseconds.
- Setting too low may causse ESP to become unresponsive.
- Send Delay (ms)
- An optional millisecond delay after updating each WiZ light.
- Can help smooth out effects when using a larger number of WiZ lights
- Use Enhanced White
- Enables using the WiZ lights onboard white LEDs instead of sending maximum RGB values.
- Tunable with warm and cool LEDs as supported by WiZ bulbs
- Note: Only sent when max RGB value is set, need to have automatic brightness limiter disabled
- ToDo: Have better logic for white value mixing to better take advantage of the lights capabilities
- Always Force Update
- Can be enabled to always send update message to light, even when color matches what was previously sent.
- Force update every x minutes
- Configuration option to allow adjusting the default force update timeout of 5 minutes.
- Setting to 0 has the same impact as enabling Always Force Update
-
Then enter the IPs for the lights to be controlled, in order. There is currently a limit of 15 devices that can be controled, but that number
can be easily changed by updating _MAX_WIZ_LIGHTS_.
## Related project
If you use these lights and python, make sure to check out the [pywizlight](https://github.com/sbidy/pywizlight) project. I learned how to
format the messages to control the lights from that project.

View File

@@ -0,0 +1,154 @@
#pragma once
#include "wled.h"
#include <WiFiUdp.h>
// Maximum number of lights supported
#define MAX_WIZ_LIGHTS 15
WiFiUDP UDP;
class WizLightsUsermod : public Usermod {
private:
unsigned long lastTime = 0;
long updateInterval;
long sendDelay;
long forceUpdateMinutes;
bool forceUpdate;
bool useEnhancedWhite;
long warmWhite;
long coldWhite;
IPAddress lightsIP[MAX_WIZ_LIGHTS]; // Stores Light IP addresses
bool lightsValid[MAX_WIZ_LIGHTS]; // Stores Light IP address validity
uint32_t colorsSent[MAX_WIZ_LIGHTS]; // Stores last color sent for each light
public:
// Send JSON blob to WiZ Light over UDP
// RGB or C/W white
// TODO:
// Better utilize WLED existing white mixing logic
void wizSendColor(IPAddress ip, uint32_t color) {
UDP.beginPacket(ip, 38899);
// If no LED color, turn light off. Note wiz light setting for "Off fade-out" will be applied by the light itself.
if (color == 0) {
UDP.print("{\"method\":\"setPilot\",\"params\":{\"state\":false}}");
// If color is WHITE, try and use the lights WHITE LEDs instead of mixing RGB LEDs
} else if (color == 16777215 && useEnhancedWhite){
// set cold white light only
if (coldWhite > 0 && warmWhite == 0){
UDP.print("{\"method\":\"setPilot\",\"params\":{\"c\":"); UDP.print(coldWhite) ;UDP.print("}}");}
// set warm white light only
if (warmWhite > 0 && coldWhite == 0){
UDP.print("{\"method\":\"setPilot\",\"params\":{\"w\":"); UDP.print(warmWhite) ;UDP.print("}}");}
// set combination of warm and cold white light
if (coldWhite > 0 && warmWhite > 0){
UDP.print("{\"method\":\"setPilot\",\"params\":{\"c\":"); UDP.print(coldWhite) ;UDP.print(",\"w\":"); UDP.print(warmWhite); UDP.print("}}");}
// Send color as RGB
} else {
UDP.print("{\"method\":\"setPilot\",\"params\":{\"r\":");
UDP.print(R(color));
UDP.print(",\"g\":");
UDP.print(G(color));
UDP.print(",\"b\":");
UDP.print(B(color));
UDP.print("}}");
}
UDP.endPacket();
}
// TODO: Check millis() rollover
void loop() {
// Make sure we are connected first
if (!WLED_CONNECTED) return;
unsigned long ellapsedTime = millis() - lastTime;
if (ellapsedTime > updateInterval) {
bool update = false;
for (uint8_t i = 0; i < MAX_WIZ_LIGHTS; i++) {
if (!lightsValid[i]) { continue; }
uint32_t newColor = strip.getPixelColor(i);
if (forceUpdate || (newColor != colorsSent[i]) || (ellapsedTime > forceUpdateMinutes*60000)){
wizSendColor(lightsIP[i], newColor);
colorsSent[i] = newColor;
update = true;
delay(sendDelay);
}
}
if (update) lastTime = millis();
}
}
void addToConfig(JsonObject& root)
{
JsonObject top = root.createNestedObject("wizLightsUsermod");
top["Interval (ms)"] = updateInterval;
top["Send Delay (ms)"] = sendDelay;
top["Use Enhanced White *"] = useEnhancedWhite;
top["* Warm White Value (0-255)"] = warmWhite;
top["* Cold White Value (0-255)"] = coldWhite;
top["Always Force Update"] = forceUpdate;
top["Force Update Every x Minutes"] = forceUpdateMinutes;
for (uint8_t i = 0; i < MAX_WIZ_LIGHTS; i++) {
top[getJsonLabel(i)] = lightsIP[i].toString();
}
}
bool readFromConfig(JsonObject& root)
{
JsonObject top = root["wizLightsUsermod"];
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top["Interval (ms)"], updateInterval, 1000); // How frequently to update the wiz lights
configComplete &= getJsonValue(top["Send Delay (ms)"], sendDelay, 0); // Optional delay after sending each UDP message
configComplete &= getJsonValue(top["Use Enhanced White *"], useEnhancedWhite, false); // When color is white use wiz white LEDs instead of mixing RGB
configComplete &= getJsonValue(top["* Warm White Value (0-255)"], warmWhite, 0); // Warm White LED value for Enhanced White
configComplete &= getJsonValue(top["* Cold White Value (0-255)"], coldWhite, 50); // Cold White LED value for Enhanced White
configComplete &= getJsonValue(top["Always Force Update"], forceUpdate, false); // Update wiz light every loop, even if color value has not changed
configComplete &= getJsonValue(top["Force Update Every x Minutes"], forceUpdateMinutes, 5); // Update wiz light if color value has not changed, every x minutes
// Read list of IPs
String tempIp;
for (uint8_t i = 0; i < MAX_WIZ_LIGHTS; i++) {
configComplete &= getJsonValue(top[getJsonLabel(i)], tempIp, "0.0.0.0");
lightsValid[i] = lightsIP[i].fromString(tempIp);
// If the IP is not valid, force the value to be empty
if (!lightsValid[i]){lightsIP[i].fromString("0.0.0.0");}
}
return configComplete;
}
// Create label for the usermod page (I cannot make it work with JSON arrays...)
String getJsonLabel(uint8_t i) {return "WiZ Light IP #" + String(i+1);}
uint16_t getId(){return USERMOD_ID_WIZLIGHTS;}
};

View File

@@ -65,7 +65,7 @@ void hourChime()
//strip.resetSegments();
selectWordSegments(true);
colorUpdated(CALL_MODE_FX_CHANGED);
savePreset(13, false);
//savePreset(255);
selectWordSegments(false);
//strip.getSegment(0).setOption(0, true);
strip.getSegment(0).setOption(2, true);
@@ -299,7 +299,7 @@ void userLoop()
if (minute(localTime) == 1){
//turn off background segment;
strip.getSegment(0).setOption(2, false);
//applyPreset(13);
//applyPreset(255);
}
}
}

View File

@@ -2346,7 +2346,7 @@ uint16_t WS2812FX::mode_ripple_rainbow(void) {
// incandescent bulbs change color as they get dim down.
#define COOL_LIKE_INCANDESCENT 1
CRGB WS2812FX::twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
CRGB IRAM_ATTR WS2812FX::twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
{
// Overall twinkle speed (changed)
uint16_t ticks = ms / SEGENV.aux0;
@@ -2784,16 +2784,9 @@ uint16_t WS2812FX::mode_popcorn(void) {
if (numPopcorn == 0) numPopcorn = 1;
for(uint8_t i = 0; i < numPopcorn; i++) {
bool isActive = popcorn[i].pos >= 0.0f;
if (isActive) { // if kernel is active, update its position
if (popcorn[i].pos >= 0.0f) { // if kernel is active, update its position
popcorn[i].pos += popcorn[i].vel;
popcorn[i].vel += gravity;
uint32_t col = color_wheel(popcorn[i].colIndex);
if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex);
uint16_t ledIndex = popcorn[i].pos;
if (ledIndex < SEGLEN) setPixelColor(ledIndex, col);
} else { // if kernel is inactive, randomly pop it
if (random8() < 2) { // POP!!!
popcorn[i].pos = 0.01f;
@@ -2812,6 +2805,13 @@ uint16_t WS2812FX::mode_popcorn(void) {
}
}
}
if (popcorn[i].pos >= 0.0f) { // draw now active popcorn (either active before or just popped)
uint32_t col = color_wheel(popcorn[i].colIndex);
if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex);
uint16_t ledIndex = popcorn[i].pos;
if (ledIndex < SEGLEN) setPixelColor(ledIndex, col);
}
}
return FRAMETIME;

View File

@@ -46,6 +46,11 @@
#define MAX(a,b) ((a)>(b)?(a):(b))
#endif
//color mangling macros
#ifndef RGBW32
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
#endif
/* Not used in all effects yet */
#define WLED_FPS 42
#define FRAMETIME_FIXED (1000/WLED_FPS)
@@ -71,7 +76,6 @@
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS)
#define LED_SKIP_AMOUNT 1
#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
#define NUM_COLORS 3 /* number of colors per segment */
@@ -247,7 +251,7 @@ class WS2812FX {
// segment parameters
public:
typedef struct Segment { // 30 (32 in memory) bytes
typedef struct Segment { // 31 (32 in memory) bytes
uint16_t start;
uint16_t stop; //segment invalid if stop == 0
uint16_t offset;
@@ -260,6 +264,7 @@ class WS2812FX {
uint8_t opacity;
uint32_t colors[NUM_COLORS];
uint8_t cct; //0==1900K, 255==10091K
uint8_t _capabilities;
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;
@@ -335,7 +340,8 @@ class WS2812FX {
return vLength;
}
uint8_t differs(Segment& b);
uint8_t getLightCapabilities();
inline uint8_t getLightCapabilities() {return _capabilities;}
void refreshLightCapabilities();
} segment;
// segment runtime parameters
@@ -607,7 +613,7 @@ class WS2812FX {
_brightness = DEFAULT_BRIGHTNESS;
currentPalette = CRGBPalette16(CRGB::Black);
targetPalette = CloudColors_p;
ablMilliampsMax = 850;
ablMilliampsMax = ABL_MILLIAMPS_DEFAULT;
currentMilliamps = 0;
timebase = 0;
resetSegments();
@@ -623,7 +629,7 @@ class WS2812FX {
setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
setColor(uint8_t slot, uint32_t c),
setCCT(uint16_t k),
setBrightness(uint8_t b),
setBrightness(uint8_t b, bool direct = false),
setRange(uint16_t i, uint16_t i2, uint32_t col),
setShowCallback(show_callback cb),
setTransition(uint16_t t),
@@ -636,12 +642,13 @@ class WS2812FX {
resetSegments(),
makeAutoSegments(bool forceReset = false),
fixInvalidSegments(),
setPixelColor(uint16_t n, uint32_t c),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
show(void),
setTargetFps(uint8_t fps),
deserializeMap(uint8_t n=0);
inline void setPixelColor(uint16_t n, uint32_t c) {setPixelColor(n, byte(c>>16), byte(c>>8), byte(c), byte(c>>24));}
bool
gammaCorrectBri = false,
gammaCorrectCol = true,
@@ -655,7 +662,8 @@ class WS2812FX {
paletteFade = 0,
paletteBlend = 0,
milliampsPerLed = 55,
cctBlending = 0,
autoWhiteMode = RGBW_MODE_DUAL,
cctBlending = 0,
getBrightness(void),
getModeCount(void),
getPaletteCount(void),
@@ -668,9 +676,13 @@ class WS2812FX {
setPixelSegment(uint8_t n),
gamma8(uint8_t),
gamma8_cal(uint8_t, float),
sin_gap(uint16_t),
get_random_wheel_index(uint8_t);
inline uint8_t sin_gap(uint16_t in) {
if (in & 0x100) return 0;
return sin8(in + 192); // correct phase shift of sine so that it starts and stops at 0
}
int8_t
tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec);
@@ -887,14 +899,15 @@ class WS2812FX {
uint32_t _colors_t[3];
uint8_t _bri_t;
bool _no_rgb = false;
uint8_t _segment_index = 0;
uint8_t _segment_index_palette_last = 99;
uint8_t _mainSegment;
segment _segments[MAX_NUM_SEGMENTS] = { // SRAM footprint: 24 bytes per element
// start, stop, offset, speed, intensity, palette, mode, options, grouping, spacing, opacity (unused), color[]
{0, 7, 0, DEFAULT_SPEED, 128, 0, DEFAULT_MODE, NO_OPTIONS, 1, 0, 255, {DEFAULT_COLOR}}
// start, stop, offset, speed, intensity, palette, mode, options, grouping, spacing, opacity (unused), color[], capabilities
{0, 7, 0, DEFAULT_SPEED, 128, 0, DEFAULT_MODE, NO_OPTIONS, 1, 0, 255, {DEFAULT_COLOR}, 0}
};
segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 28 bytes per element
friend class Segment_runtime;
@@ -903,7 +916,6 @@ class WS2812FX {
friend class ColorTransition;
uint16_t
realPixelIndex(uint16_t i),
transitionProgress(uint8_t tNr);
public:

View File

@@ -60,6 +60,11 @@
#define DEFAULT_LED_TYPE TYPE_WS2812_RGB
#endif
#ifndef DEFAULT_LED_COLOR_ORDER
#define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB
#endif
#if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES
#error "Max segments must be at least max number of busses!"
#endif
@@ -77,6 +82,7 @@ void WS2812FX::finalizeInit(void)
//if busses failed to load, add default (fresh install, FS issue, ...)
if (busses.getNumBusses() == 0) {
DEBUG_PRINTLN(F("No busses, init default"));
const uint8_t defDataPins[] = {DATA_PINS};
const uint16_t defCounts[] = {PIXEL_COUNTS};
const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0]));
@@ -87,7 +93,7 @@ void WS2812FX::finalizeInit(void)
uint16_t start = prevLen;
uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
prevLen += count;
BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, COL_ORDER_GRB);
BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER);
busses.add(defCfg);
}
}
@@ -125,6 +131,8 @@ void WS2812FX::service() {
for(uint8_t i=0; i < MAX_NUM_SEGMENTS; i++)
{
//if (realtimeMode && useMainSegmentOnly && i == getMainSegmentId()) continue;
_segment_index = i;
// reset the segment runtime data if needed, called before isActive to ensure deleted
@@ -133,7 +141,8 @@ void WS2812FX::service() {
if (!SEGMENT.isActive()) continue;
if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary
// last condition ensures all solid segments are updated at the same time
if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0))
{
if (SEGMENT.grouping == 0) SEGMENT.grouping = 1; //sanity check
doShow = true;
@@ -152,10 +161,18 @@ void WS2812FX::service() {
_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]);
for (uint8_t c = 0; c < NUM_COLORS; c++) {
_colors_t[c] = gamma32(_colors_t[c]);
}
handle_palette();
// if segment is not RGB capable, force None auto white mode
// If not RGB capable, also treat palette as if default (0), as palettes set white channel to 0
_no_rgb = !(SEGMENT.getLightCapabilities() & 0x01);
if (_no_rgb) Bus::setAutoWhiteMode(RGBW_MODE_MANUAL_ONLY);
delay = (this->*_mode[SEGMENT.mode])(); //effect function
if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++;
Bus::setAutoWhiteMode(strip.autoWhiteMode);
}
SEGENV.next_time = nowUp + delay;
@@ -170,34 +187,11 @@ void WS2812FX::service() {
_triggered = false;
}
void IRAM_ATTR WS2812FX::setPixelColor(uint16_t n, uint32_t c) {
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
uint16_t IRAM_ATTR WS2812FX::realPixelIndex(uint16_t i) {
int16_t iGroup = i * SEGMENT.groupLength();
/* reverse just an individual segment */
int16_t realIndex = iGroup;
if (IS_REVERSE) {
if (IS_MIRROR) {
realIndex = (SEGMENT.length() - 1) / 2 - iGroup; //only need to index half the pixels
} else {
realIndex = (SEGMENT.length() - 1) - iGroup;
}
}
realIndex += SEGMENT.start;
return realIndex;
}
void IRAM_ATTR WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
{
if (SEGLEN) {//from segment
uint16_t realIndex = realPixelIndex(i);
uint16_t len = SEGMENT.length();
uint8_t segIdx;
if (SEGLEN) { // SEGLEN!=0 -> from segment/FX
//color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments)
if (_bri_t < 255) {
r = scale8(r, _bri_t);
@@ -205,30 +199,48 @@ void IRAM_ATTR WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte
b = scale8(b, _bri_t);
w = scale8(w, _bri_t);
}
segIdx = _segment_index;
} else // from live/realtime
segIdx = _mainSegment;
if (SEGLEN || (realtimeMode && useMainSegmentOnly)) {
uint32_t col = RGBW32(r, g, b, w);
uint16_t len = _segments[segIdx].length();
/* Set all the pixels in the group */
for (uint16_t j = 0; j < SEGMENT.grouping; j++) {
uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j);
if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) {
if (IS_MIRROR) { //set the corresponding mirrored pixel
uint16_t indexMir = SEGMENT.stop - indexSet + SEGMENT.start - 1;
/* offset/phase */
indexMir += SEGMENT.offset;
if (indexMir >= SEGMENT.stop) indexMir -= len;
// get physical pixel address (taking into account start, grouping, spacing [and offset])
i = i * _segments[segIdx].groupLength();
if (_segments[segIdx].options & REVERSE) { // is segment reversed?
if (_segments[segIdx].options & MIRROR) { // is segment mirrored?
i = (len - 1) / 2 - i; //only need to index half the pixels
} else {
i = (len - 1) - i;
}
}
i += _segments[segIdx].start;
// set all the pixels in the group
for (uint16_t j = 0; j < _segments[segIdx].grouping; j++) {
uint16_t indexSet = i + ((_segments[segIdx].options & REVERSE) ? -j : j);
if (indexSet >= _segments[segIdx].start && indexSet < _segments[segIdx].stop) {
if (_segments[segIdx].options & MIRROR) { //set the corresponding mirrored pixel
uint16_t indexMir = _segments[segIdx].stop - indexSet + _segments[segIdx].start - 1;
indexMir += _segments[segIdx].offset; // offset/phase
if (indexMir >= _segments[segIdx].stop) indexMir -= len;
if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir];
busses.setPixelColor(indexMir, col);
}
/* offset/phase */
indexSet += SEGMENT.offset;
if (indexSet >= SEGMENT.stop) indexSet -= len;
indexSet += _segments[segIdx].offset; // offset/phase
if (indexSet >= _segments[segIdx].stop) indexSet -= len;
if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet];
busses.setPixelColor(indexSet, col);
}
}
} else { //live data, etc.
} else {
if (i < customMappingSize) i = customMappingTable[i];
busses.setPixelColor(i, RGBW32(r, g, b, w));
}
@@ -418,7 +430,7 @@ void WS2812FX::setCCT(uint16_t k) {
}
}
void WS2812FX::setBrightness(uint8_t b) {
void WS2812FX::setBrightness(uint8_t b, bool direct) {
if (gammaCorrectBri) b = gamma8(b);
if (_brightness == b) return;
_brightness = b;
@@ -428,8 +440,13 @@ void WS2812FX::setBrightness(uint8_t b) {
_segments[i].setOption(SEG_OPTION_FREEZE, false);
}
}
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
if (direct) {
// would be dangerous if applied immediately (could exceed ABL), but will not output until the next show()
busses.setBrightness(b);
} else {
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::getBrightness(void) {
@@ -488,7 +505,13 @@ uint8_t WS2812FX::getActiveSegmentsNum(void) {
uint32_t WS2812FX::getPixelColor(uint16_t i)
{
i = realPixelIndex(i);
// get physical pixel
i = i * SEGMENT.groupLength();;
if (IS_REVERSE) {
if (IS_MIRROR) i = (SEGMENT.length() - 1) / 2 - i; //only need to index half the pixels
else i = (SEGMENT.length() - 1) - i;
}
i += SEGMENT.start;
if (SEGLEN) {
/* offset/phase */
@@ -503,7 +526,7 @@ uint32_t WS2812FX::getPixelColor(uint16_t i)
}
WS2812FX::Segment& WS2812FX::getSegment(uint8_t id) {
if (id >= MAX_NUM_SEGMENTS) return _segments[0];
if (id >= MAX_NUM_SEGMENTS) return _segments[getMainSegmentId()];
return _segments[id];
}
@@ -561,11 +584,14 @@ uint8_t WS2812FX::Segment::differs(Segment& b) {
return d;
}
uint8_t WS2812FX::Segment::getLightCapabilities() {
if (!isActive()) return 0;
void WS2812FX::Segment::refreshLightCapabilities() {
if (!isActive()) {
_capabilities = 0; return;
}
uint8_t capabilities = 0;
uint8_t awm = Bus::getAutoWhiteMode();
uint8_t awm = instance->autoWhiteMode;
bool whiteSlider = (awm == RGBW_MODE_DUAL || awm == RGBW_MODE_MANUAL_ONLY);
bool segHasValidBus = false;
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
@@ -573,12 +599,13 @@ uint8_t WS2812FX::Segment::getLightCapabilities() {
if (bus->getStart() >= stop) continue;
if (bus->getStart() + bus->getLength() <= start) continue;
segHasValidBus = true;
uint8_t type = bus->getType();
if (!whiteSlider || (type != TYPE_ANALOG_1CH && (cctFromRgb || type != TYPE_ANALOG_2CH)))
if (type != TYPE_ANALOG_1CH && (cctFromRgb || type != TYPE_ANALOG_2CH))
{
capabilities |= 0x01; //segment supports RGB (full color)
capabilities |= 0x01; // segment supports RGB (full color)
}
if (bus->isRgbw() && whiteSlider) capabilities |= 0x02; //segment supports white channel
if (bus->isRgbw() && whiteSlider) capabilities |= 0x02; // segment supports white channel
if (!cctFromRgb) {
switch (type) {
case TYPE_ANALOG_5CH:
@@ -588,7 +615,10 @@ uint8_t WS2812FX::Segment::getLightCapabilities() {
}
if (correctWB && type != TYPE_ANALOG_1CH) capabilities |= 0x04; //white balance correction (uses CCT slider)
}
return capabilities;
// if seg has any bus, but no bus has RGB, it by definition supports white (at least for now)
// In case of no RGB, disregard auto white mode and always show a white slider
if (segHasValidBus && !(capabilities & 0x01)) capabilities |= 0x02; // segment supports white channel
_capabilities = capabilities;
}
//used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw.
@@ -627,7 +657,8 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
Segment& seg = _segments[n];
//return if neither bounds nor grouping have changed
if (seg.start == i1 && seg.stop == i2
bool boundsUnchanged = (seg.start == i1 && seg.stop == i2);
if (boundsUnchanged
&& (!grouping || (seg.grouping == grouping && seg.spacing == spacing))
&& (offset == UINT16_MAX || offset == seg.offset)) return;
@@ -652,6 +683,7 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
}
if (offset < UINT16_MAX) seg.offset = offset;
_segment_runtimes[n].markForReset();
if (!boundsUnchanged) seg.refreshLightCapabilities();
}
void WS2812FX::restartRuntime() {
@@ -741,6 +773,8 @@ void WS2812FX::fixInvalidSegments() {
{
if (_segments[i].start >= _length) setSegment(i, 0, 0);
if (_segments[i].stop > _length) setSegment(i, _segments[i].start, _length);
// this is always called as the last step after finalizeInit(), update covered bus types
getSegment(i).refreshLightCapabilities();
}
}
@@ -918,12 +952,6 @@ uint16_t IRAM_ATTR WS2812FX::triwave16(uint16_t in)
return 0xFFFF - (in - 0x8000)*2;
}
uint8_t IRAM_ATTR WS2812FX::sin_gap(uint16_t in) {
if (in & 0x100) return 0;
//if (in > 255) return 0;
return sin8(in + 192); //correct phase shift of sine so that it starts and stops at 0
}
/*
* Generates a tristate square wave w/ attac & decay
* @param x input value 0-255
@@ -1116,22 +1144,17 @@ void WS2812FX::handle_palette(void)
*/
uint32_t IRAM_ATTR WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri)
{
if (SEGMENT.palette == 0 && mcol < 3) {
if ((SEGMENT.palette == 0 && mcol < 3) || _no_rgb) {
uint32_t color = SEGCOLOR(mcol);
if (pbri != 255) {
CRGB crgb_color = col_to_crgb(color);
crgb_color.nscale8_video(pbri);
return crgb_to_col(crgb_color);
} else {
return color;
}
if (pbri == 255) return color;
return RGBW32(scale8_video(R(color),pbri), scale8_video(G(color),pbri), scale8_video(B(color),pbri), scale8_video(W(color),pbri));
}
uint8_t paletteIndex = i;
if (mapping && SEGLEN > 1) paletteIndex = (i*255)/(SEGLEN -1);
if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
CRGB fastled_col;
fastled_col = ColorFromPalette( currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND);
fastled_col = ColorFromPalette(currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND);
return crgb_to_col(fastled_col);
}
@@ -1242,4 +1265,4 @@ 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;
uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL;

View File

@@ -588,7 +588,7 @@ class BusManager {
//utility to get the approx. memory usage of a given BusConfig
static uint32_t memUsage(BusConfig &bc) {
uint8_t type = bc.type;
uint16_t len = bc.count;
uint16_t len = bc.count + bc.skipAmount;
if (type > 15 && type < 32) {
#ifdef ESP8266
if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem

View File

@@ -69,6 +69,10 @@
#define I_HS_P98_3 35
#define I_SS_P98_3 36
//LPD6803
#define I_HS_LPO_3 37
#define I_SS_LPO_3 38
/*** ESP8266 Neopixel methods ***/
#ifdef ESP8266
@@ -133,13 +137,17 @@
#endif
//APA102
#define B_HS_DOT_3 NeoPixelBrightnessBus<DotStarBgrFeature, DotStarSpiMethod> //hardware SPI
#define B_HS_DOT_3 NeoPixelBrightnessBus<DotStarBgrFeature, DotStarSpi5MhzMethod> //hardware SPI
#define B_SS_DOT_3 NeoPixelBrightnessBus<DotStarBgrFeature, DotStarMethod> //soft SPI
//LPD8806
#define B_HS_LPD_3 NeoPixelBrightnessBus<Lpd8806GrbFeature, Lpd8806SpiMethod>
#define B_SS_LPD_3 NeoPixelBrightnessBus<Lpd8806GrbFeature, Lpd8806Method>
//LPD6803
#define B_HS_LPO_3 NeoPixelBrightnessBus<Lpd6803GrbFeature, Lpd6803SpiMethod>
#define B_SS_LPO_3 NeoPixelBrightnessBus<Lpd6803GrbFeature, Lpd6803Method>
//WS2801
//#define B_HS_WS1_3 NeoPixelBrightnessBus<NeoRbgFeature, NeoWs2801Spi40MhzMethod>
//#define B_HS_WS1_3 NeoPixelBrightnessBus<NeoRbgFeature, NeoWs2801Spi20MhzMethod>
@@ -184,6 +192,7 @@ class PolyBus {
case I_8266_BB_TM1_4: beginTM1814<B_8266_BB_TM1_4*>(busPtr); break;
case I_HS_DOT_3: (static_cast<B_HS_DOT_3*>(busPtr))->Begin(); break;
case I_HS_LPD_3: (static_cast<B_HS_LPD_3*>(busPtr))->Begin(); break;
case I_HS_LPO_3: (static_cast<B_HS_LPO_3*>(busPtr))->Begin(); break;
case I_HS_WS1_3: (static_cast<B_HS_WS1_3*>(busPtr))->Begin(); break;
case I_HS_P98_3: (static_cast<B_HS_P98_3*>(busPtr))->Begin(); break;
#endif
@@ -219,11 +228,13 @@ class PolyBus {
// ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin()
case I_HS_DOT_3: (static_cast<B_HS_DOT_3*>(busPtr))->Begin(pins[1], -1, pins[0], -1); break;
case I_HS_LPD_3: (static_cast<B_HS_LPD_3*>(busPtr))->Begin(pins[1], -1, pins[0], -1); break;
case I_HS_LPO_3: (static_cast<B_HS_LPO_3*>(busPtr))->Begin(pins[1], -1, pins[0], -1); break;
case I_HS_WS1_3: (static_cast<B_HS_WS1_3*>(busPtr))->Begin(pins[1], -1, pins[0], -1); break;
case I_HS_P98_3: (static_cast<B_HS_P98_3*>(busPtr))->Begin(pins[1], -1, pins[0], -1); break;
#endif
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->Begin(); break;
case I_SS_LPD_3: (static_cast<B_SS_LPD_3*>(busPtr))->Begin(); break;
case I_SS_LPO_3: (static_cast<B_SS_LPO_3*>(busPtr))->Begin(); break;
case I_SS_WS1_3: (static_cast<B_SS_WS1_3*>(busPtr))->Begin(); break;
case I_SS_P98_3: (static_cast<B_SS_P98_3*>(busPtr))->Begin(); break;
}
@@ -285,6 +296,8 @@ class PolyBus {
case I_SS_DOT_3: busPtr = new B_SS_DOT_3(len, pins[1], pins[0]); break;
case I_HS_LPD_3: busPtr = new B_HS_LPD_3(len, pins[1], pins[0]); break;
case I_SS_LPD_3: busPtr = new B_SS_LPD_3(len, pins[1], pins[0]); break;
case I_HS_LPO_3: busPtr = new B_HS_LPO_3(len, pins[1], pins[0]); break;
case I_SS_LPO_3: busPtr = new B_SS_LPO_3(len, pins[1], pins[0]); break;
case I_HS_WS1_3: busPtr = new B_HS_WS1_3(len, pins[1], pins[0]); break;
case I_SS_WS1_3: busPtr = new B_SS_WS1_3(len, pins[1], pins[0]); break;
case I_HS_P98_3: busPtr = new B_HS_P98_3(len, pins[1], pins[0]); break;
@@ -348,6 +361,8 @@ class PolyBus {
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->Show(); break;
case I_HS_LPD_3: (static_cast<B_HS_LPD_3*>(busPtr))->Show(); break;
case I_SS_LPD_3: (static_cast<B_SS_LPD_3*>(busPtr))->Show(); break;
case I_HS_LPO_3: (static_cast<B_HS_LPO_3*>(busPtr))->Show(); break;
case I_SS_LPO_3: (static_cast<B_SS_LPO_3*>(busPtr))->Show(); break;
case I_HS_WS1_3: (static_cast<B_HS_WS1_3*>(busPtr))->Show(); break;
case I_SS_WS1_3: (static_cast<B_SS_WS1_3*>(busPtr))->Show(); break;
case I_HS_P98_3: (static_cast<B_HS_P98_3*>(busPtr))->Show(); break;
@@ -409,6 +424,8 @@ class PolyBus {
case I_SS_DOT_3: return (static_cast<B_SS_DOT_3*>(busPtr))->CanShow(); break;
case I_HS_LPD_3: return (static_cast<B_HS_LPD_3*>(busPtr))->CanShow(); break;
case I_SS_LPD_3: return (static_cast<B_SS_LPD_3*>(busPtr))->CanShow(); break;
case I_HS_LPO_3: return (static_cast<B_HS_LPO_3*>(busPtr))->CanShow(); break;
case I_SS_LPO_3: return (static_cast<B_SS_LPO_3*>(busPtr))->CanShow(); break;
case I_HS_WS1_3: return (static_cast<B_HS_WS1_3*>(busPtr))->CanShow(); break;
case I_SS_WS1_3: return (static_cast<B_SS_WS1_3*>(busPtr))->CanShow(); break;
case I_HS_P98_3: return (static_cast<B_HS_P98_3*>(busPtr))->CanShow(); break;
@@ -494,6 +511,8 @@ class PolyBus {
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_HS_LPD_3: (static_cast<B_HS_LPD_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_SS_LPD_3: (static_cast<B_SS_LPD_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_HS_LPO_3: (static_cast<B_HS_LPO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_SS_LPO_3: (static_cast<B_SS_LPO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_HS_WS1_3: (static_cast<B_HS_WS1_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_SS_WS1_3: (static_cast<B_SS_WS1_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_HS_P98_3: (static_cast<B_HS_P98_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
@@ -555,6 +574,8 @@ class PolyBus {
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->SetBrightness(b); break;
case I_HS_LPD_3: (static_cast<B_HS_LPD_3*>(busPtr))->SetBrightness(b); break;
case I_SS_LPD_3: (static_cast<B_SS_LPD_3*>(busPtr))->SetBrightness(b); break;
case I_HS_LPO_3: (static_cast<B_HS_LPO_3*>(busPtr))->SetBrightness(b); break;
case I_SS_LPO_3: (static_cast<B_SS_LPO_3*>(busPtr))->SetBrightness(b); break;
case I_HS_WS1_3: (static_cast<B_HS_WS1_3*>(busPtr))->SetBrightness(b); break;
case I_SS_WS1_3: (static_cast<B_SS_WS1_3*>(busPtr))->SetBrightness(b); break;
case I_HS_P98_3: (static_cast<B_HS_P98_3*>(busPtr))->SetBrightness(b); break;
@@ -617,6 +638,8 @@ class PolyBus {
case I_SS_DOT_3: col = (static_cast<B_SS_DOT_3*>(busPtr))->GetPixelColor(pix); break;
case I_HS_LPD_3: col = (static_cast<B_HS_LPD_3*>(busPtr))->GetPixelColor(pix); break;
case I_SS_LPD_3: col = (static_cast<B_SS_LPD_3*>(busPtr))->GetPixelColor(pix); break;
case I_HS_LPO_3: col = (static_cast<B_HS_LPO_3*>(busPtr))->GetPixelColor(pix); break;
case I_SS_LPO_3: col = (static_cast<B_SS_LPO_3*>(busPtr))->GetPixelColor(pix); break;
case I_HS_WS1_3: col = (static_cast<B_HS_WS1_3*>(busPtr))->GetPixelColor(pix); break;
case I_SS_WS1_3: col = (static_cast<B_SS_WS1_3*>(busPtr))->GetPixelColor(pix); break;
case I_HS_P98_3: col = (static_cast<B_HS_P98_3*>(busPtr))->GetPixelColor(pix); break;
@@ -696,6 +719,8 @@ class PolyBus {
case I_SS_DOT_3: delete (static_cast<B_SS_DOT_3*>(busPtr)); break;
case I_HS_LPD_3: delete (static_cast<B_HS_LPD_3*>(busPtr)); break;
case I_SS_LPD_3: delete (static_cast<B_SS_LPD_3*>(busPtr)); break;
case I_HS_LPO_3: delete (static_cast<B_HS_LPO_3*>(busPtr)); break;
case I_SS_LPO_3: delete (static_cast<B_SS_LPO_3*>(busPtr)); break;
case I_HS_WS1_3: delete (static_cast<B_HS_WS1_3*>(busPtr)); break;
case I_SS_WS1_3: delete (static_cast<B_SS_WS1_3*>(busPtr)); break;
case I_HS_P98_3: delete (static_cast<B_HS_P98_3*>(busPtr)); break;
@@ -717,6 +742,7 @@ class PolyBus {
switch (busType) {
case TYPE_APA102: t = I_SS_DOT_3; break;
case TYPE_LPD8806: t = I_SS_LPD_3; break;
case TYPE_LPD6803: t = I_SS_LPO_3; break;
case TYPE_WS2801: t = I_SS_WS1_3; break;
case TYPE_P9813: t = I_SS_P98_3; break;
default: t=I_NONE;

View File

@@ -4,11 +4,12 @@
* Physical IO
*/
#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
#define WLED_LONG_PRESS 600 //long press if button is released after held for at least 600ms
#define WLED_DOUBLE_PRESS 350 //double press if another press within 350ms after a short press
#define WLED_LONG_REPEATED_ACTION 300 //how often a repeated action (e.g. dimming) is fired on long press on button IDs >0
#define WLED_LONG_AP 6000 //how long the button needs to be held to activate WLED-AP
#define WLED_DEBOUNCE_THRESHOLD 50 // only consider button input of at least 50ms as valid (debouncing)
#define WLED_LONG_PRESS 600 // long press if button is released after held for at least 600ms
#define WLED_DOUBLE_PRESS 350 // double press if another press within 350ms after a short press
#define WLED_LONG_REPEATED_ACTION 300 // how often a repeated action (e.g. dimming) is fired on long press on button IDs >0
#define WLED_LONG_AP 5000 // how long button 0 needs to be held to activate WLED-AP
#define WLED_LONG_FACTORY_RESET 10000 // how long button 0 needs to be held to trigger a factory reset
static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage
@@ -130,21 +131,42 @@ void handleSwitch(uint8_t b)
}
}
#define ANALOG_BTN_READ_CYCLE 250 // min time between two analog reading cycles
#define STRIP_WAIT_TIME 6 // max wait time in case of strip.isUpdating()
#define POT_SMOOTHING 0.25f // smoothing factor for raw potentiometer readings
#define POT_SENSITIVITY 4 // changes below this amount are noise (POT scratching, or ADC noise)
void handleAnalog(uint8_t b)
{
static uint8_t oldRead[WLED_MAX_BUTTONS];
static uint8_t oldRead[WLED_MAX_BUTTONS] = {0};
static float filteredReading[WLED_MAX_BUTTONS] = {0.0f};
uint16_t rawReading; // raw value from analogRead, scaled to 12bit
#ifdef ESP8266
uint16_t aRead = analogRead(A0) >> 2; // convert 10bit read to 8bit
rawReading = analogRead(A0) << 2; // convert 10bit read to 12bit
#else
uint16_t aRead = analogRead(btnPin[b]) >> 4; // convert 12bit read to 8bit
rawReading = analogRead(btnPin[b]); // collect at full 12bit resolution
#endif
yield(); // keep WiFi task running - analog read may take several millis on ESP8266
filteredReading[b] += POT_SMOOTHING * ((float(rawReading) / 16.0f) - filteredReading[b]); // filter raw input, and scale to [0..255]
uint16_t aRead = max(min(int(filteredReading[b]), 255), 0); // squash into 8bit
if(aRead <= POT_SENSITIVITY) aRead = 0; // make sure that 0 and 255 are used
if(aRead >= 255-POT_SENSITIVITY) aRead = 255;
if (buttonType[b] == BTN_TYPE_ANALOG_INVERTED) aRead = 255 - aRead;
// remove noise & reduce frequency of UI updates
aRead &= 0xFC;
if (abs(int(aRead) - int(oldRead[b])) <= POT_SENSITIVITY) return; // no significant change in reading
// Unomment the next lines if you still see flickering related to potentiometer
// This waits until strip finishes updating (why: strip was not updating at the start of handleButton() but may have started during analogRead()?)
//unsigned long wait_started = millis();
//while(strip.isUpdating() && (millis() - wait_started < STRIP_WAIT_TIME)) {
// delay(1);
//}
//if (strip.isUpdating()) return; // give up
if (oldRead[b] == aRead) return; // no change in reading
oldRead[b] = aRead;
// if no macro for "short press" and "long press" is defined use brightness control
@@ -167,6 +189,7 @@ void handleAnalog(uint8_t b)
} else if (macroDoublePress[b] == 247) {
// selected palette
effectPalette = map(aRead, 0, 252, 0, strip.getPaletteCount()-1);
effectPalette = constrain(effectPalette, 0, strip.getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result
} else if (macroDoublePress[b] == 200) {
// primary color, hue, full saturation
colorHStoRGB(aRead*256,255,col);
@@ -195,6 +218,8 @@ void handleButton()
static unsigned long lastRead = 0UL;
bool analog = false;
if (strip.isUpdating()) return; // don't interfere with strip updates. Our button will still be there in 1ms (next cycle)
for (uint8_t b=0; b<WLED_MAX_BUTTONS; b++) {
#ifdef ESP8266
if ((btnPin[b]<0 && !(buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED)) || buttonType[b] == BTN_TYPE_NONE) continue;
@@ -204,7 +229,7 @@ void handleButton()
if (usermods.handleButton(b)) continue; // did usermod handle buttons
if ((buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) && millis() - lastRead > 250) { // button is not a button but a potentiometer
if ((buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) && millis() - lastRead > ANALOG_BTN_READ_CYCLE) { // button is not a button but a potentiometer
analog = true;
handleAnalog(b); continue;
}
@@ -236,8 +261,14 @@ void handleButton()
bool doublePress = buttonWaitTime[b]; //did we have a short press before?
buttonWaitTime[b] = 0;
if (b == 0 && dur > WLED_LONG_AP) { //long press on button 0 (when released)
WLED::instance().initAP(true);
if (b == 0 && dur > WLED_LONG_AP) { // long press on button 0 (when released)
if (dur > WLED_LONG_FACTORY_RESET) { // factory reset if pressed > 10 seconds
WLED_FS.format();
clearEEPROM();
doReboot = true;
} else {
WLED::instance().initAP(true);
}
} else if (!buttonLongPressed[b]) { //short press
if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set
shortPressAction(b);

View File

@@ -80,19 +80,22 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | Bus::getAutoWhiteMode());
CJSON(strip.autoWhiteMode, hw_led[F("rgbwm")]);
Bus::setAutoWhiteMode(strip.autoWhiteMode);
strip.fixInvalidSegments(); // refreshes segment light capabilities (in case auto white mode changed)
CJSON(correctWB, hw_led["cct"]);
CJSON(cctFromRgb, hw_led[F("cr")]);
CJSON(strip.cctBlending, hw_led[F("cb")]);
Bus::setCCTBlend(strip.cctBlending);
strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
CJSON(strip.cctBlending, hw_led[F("cb")]);
Bus::setCCTBlend(strip.cctBlending);
strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
JsonArray ins = hw_led["ins"];
if (fromFS || !ins.isNull()) {
uint8_t s = 0; // bus iterator
busses.removeAll();
if (fromFS) busses.removeAll(); // can't safely manipulate busses directly in network callback
uint32_t mem = 0;
bool busesChanged = false;
for (JsonObject elm : ins) {
if (s >= WLED_MAX_BUSSES) break;
uint8_t pins[5] = {255, 255, 255, 255, 255};
@@ -113,12 +116,19 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t ledType = elm["type"] | TYPE_WS2812_RGB;
bool reversed = elm["rev"];
bool refresh = elm["ref"] | false;
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
if (fromFS) {
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
mem += BusManager::memUsage(bc);
if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip()
} else {
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
busesChanged = true;
}
s++;
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
mem += BusManager::memUsage(bc);
if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip()
}
doInitBusses = busesChanged;
// finalization done in beginStrip()
}
if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus
@@ -196,6 +206,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
}
CJSON(irEnabled, hw["ir"]["type"]);
CJSON(irApplyToAllSelected, hw["ir"]["sel"]);
JsonObject relay = hw[F("relay")];
int hw_relay_pin = relay["pin"] | -2;
@@ -282,6 +293,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject if_live = interfaces["live"];
CJSON(receiveDirect, if_live["en"]);
CJSON(useMainSegmentOnly, if_live[F("mso")]);
CJSON(e131Port, if_live["port"]); // 5568
if (e131Port == DDP_DEFAULT_PORT) e131Port = E131_DEFAULT_PORT; // prevent double DDP port allocation
CJSON(e131Multicast, if_live[F("mc")]);
@@ -355,10 +367,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(latitude, if_ntp[F("lt")]);
JsonObject ol = doc[F("ol")];
prev = overlayDefault;
CJSON(overlayDefault ,ol[F("clock")]); // 0
CJSON(overlayCurrent ,ol[F("clock")]); // 0
CJSON(countdownMode, ol[F("cntdwn")]);
if (prev != overlayDefault) overlayCurrent = overlayDefault;
CJSON(overlayMin, ol["min"]);
CJSON(overlayMax, ol[F("max")]);
@@ -399,15 +409,15 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (act) timerWeekday[it]++;
}
if (it<8) {
JsonObject start = timer["start"];
byte startm = start["mon"];
if (startm) timerMonth[it] = (startm << 4);
JsonObject start = timer["start"];
byte startm = start["mon"];
if (startm) timerMonth[it] = (startm << 4);
CJSON(timerDay[it], start["day"]);
JsonObject end = timer["end"];
CJSON(timerDayEnd[it], end["day"]);
byte endm = end["mon"];
if (startm) timerMonth[it] += endm & 0x0F;
if (!(timerMonth[it] & 0x0F)) timerMonth[it] += 12; //default end month to 12
JsonObject end = timer["end"];
CJSON(timerDayEnd[it], end["day"]);
byte endm = end["mon"];
if (startm) timerMonth[it] += endm & 0x0F;
if (!(timerMonth[it] & 0x0F)) timerMonth[it] += 12; //default end month to 12
}
it++;
}
@@ -448,7 +458,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
if (fromFS) return needsSave;
// if from /json/cfg
doReboot = doc[F("rb")] | doReboot;
if (doInitBusses) return false; // no save needed, will do after bus init in wled.cpp loop
return (doc["sv"] | true);
}
@@ -571,9 +583,9 @@ void serializeConfig() {
hw_led[F("ledma")] = strip.milliampsPerLed;
hw_led["cct"] = correctWB;
hw_led[F("cr")] = cctFromRgb;
hw_led[F("cb")] = strip.cctBlending;
hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = Bus::getAutoWhiteMode();
hw_led[F("cb")] = strip.cctBlending;
hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = strip.autoWhiteMode;
JsonArray hw_led_ins = hw_led.createNestedArray("ins");
@@ -630,6 +642,7 @@ void serializeConfig() {
JsonObject hw_ir = hw.createNestedObject("ir");
hw_ir["pin"] = irPin;
hw_ir["type"] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled )
hw_ir["sel"] = irApplyToAllSelected;
JsonObject hw_relay = hw.createNestedObject(F("relay"));
hw_relay["pin"] = rlyPin;
@@ -694,6 +707,7 @@ void serializeConfig() {
JsonObject if_live = interfaces.createNestedObject("live");
if_live["en"] = receiveDirect;
if_live[F("mso")] = useMainSegmentOnly;
if_live["port"] = e131Port;
if_live[F("mc")] = e131Multicast;
@@ -763,7 +777,7 @@ void serializeConfig() {
if_ntp[F("lt")] = latitude;
JsonObject ol = doc.createNestedObject("ol");
ol[F("clock")] = overlayDefault;
ol[F("clock")] = overlayCurrent;
ol[F("cntdwn")] = countdownMode;
ol["min"] = overlayMin;
@@ -791,11 +805,11 @@ void serializeConfig() {
timers_ins0["macro"] = timerMacro[i];
timers_ins0[F("dow")] = timerWeekday[i] >> 1;
if (i<8) {
JsonObject start = timers_ins0.createNestedObject("start");
JsonObject start = timers_ins0.createNestedObject("start");
start["mon"] = (timerMonth[i] >> 4) & 0xF;
start["day"] = timerDay[i];
JsonObject end = timers_ins0.createNestedObject("end");
end["mon"] = timerMonth[i] & 0xF;
JsonObject end = timers_ins0.createNestedObject("end");
end["mon"] = timerMonth[i] & 0xF;
end["day"] = timerDayEnd[i];
}
}

View File

@@ -10,33 +10,6 @@ void setRandomColor(byte* rgb)
colorHStoRGB(lastRandomIndex*256,255,rgb);
}
void colorFromUint32(uint32_t in, bool secondary)
{
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)
{
byte *_col = secondary ? colSec : col;
_col[0] = R(in);
_col[1] = G(in);
_col[2] = B(in);
}
//relatively change white brightness, minumum A=5
void relativeChangeWhite(int8_t amount, byte lowerBoundary)
{
int16_t new_val = (int16_t) col[3] + amount;
if (new_val > 0xFF) new_val = 0xFF;
else if (new_val < lowerBoundary) new_val = lowerBoundary;
col[3] = new_val;
}
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb
{
float h = ((float)hue)/65535.0;

View File

@@ -71,6 +71,12 @@
#define USERMOD_RGB_ROTARY_ENCODER 22 //Usermod "rgb-rotary-encoder.h"
#define USERMOD_ID_QUINLED_AN_PENTA 23 //Usermod "quinled-an-penta.h"
#define USERMOD_ID_SSDR 24 //Usermod "usermod_v2_seven_segment_display_reloaded.h"
#define USERMOD_ID_CRONIXIE 25 //Usermod "usermod_cronixie.h"
#define USERMOD_ID_WIZLIGHTS 26 //Usermod "wizlights.h"
#define USERMOD_ID_WORDCLOCK 27 //Usermod "usermod_v2_word_clock.h"
#define USERMOD_ID_MY9291 28 //Usermod "usermod_MY9291.h"
#define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h"
#define USERMOD_ID_BME280 30 //Usermod "usermod_bme280.h
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@@ -159,6 +165,7 @@
#define TYPE_APA102 51
#define TYPE_LPD8806 52
#define TYPE_P9813 53
#define TYPE_LPD6803 54
//Network types (master broadcast) (80-95)
#define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus)
#define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus)
@@ -191,7 +198,7 @@
#define BTN_TYPE_ANALOG_INVERTED 8
//Ethernet board types
#define WLED_NUM_ETH_TYPES 7
#define WLED_NUM_ETH_TYPES 8
#define WLED_ETH_NONE 0
#define WLED_ETH_WT32_ETH01 1
@@ -291,7 +298,13 @@
#endif
#endif
#define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit
#ifndef ABL_MILLIAMPS_DEFAULT
#define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit
#else
#if ABL_MILLIAMPS_DEFAULT < 250 // make sure value is at least 250
#define ABL_MILLIAMPS_DEFAULT 250
#endif
#endif
// PWM settings
#ifndef WLED_PWM_FREQ

View File

@@ -0,0 +1,7 @@
Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures.
To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts
You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects.
You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection.

View File

@@ -0,0 +1,152 @@
body {
padding: 0;
margin: 0;
font-family: sans-serif;
font-size: 1em;
line-height: 1.5;
color: #555;
background: #fff;
}
h1 {
font-size: 1.5em;
font-weight: normal;
}
small {
font-size: .66666667em;
}
a {
color: #e74c3c;
text-decoration: none;
}
a:hover, a:focus {
box-shadow: 0 1px #e74c3c;
}
.bshadow0, input {
box-shadow: inset 0 -2px #e7e7e7;
}
input:hover {
box-shadow: inset 0 -2px #ccc;
}
input, fieldset {
font-family: sans-serif;
font-size: 1em;
margin: 0;
padding: 0;
border: 0;
}
input {
color: inherit;
line-height: 1.5;
height: 1.5em;
padding: .25em 0;
}
input:focus {
outline: none;
box-shadow: inset 0 -2px #449fdb;
}
.glyph {
font-size: 16px;
width: 15em;
padding-bottom: 1em;
margin-right: 4em;
margin-bottom: 1em;
float: left;
overflow: hidden;
}
.liga {
width: 80%;
width: calc(100% - 2.5em);
}
.talign-right {
text-align: right;
}
.talign-center {
text-align: center;
}
.bgc1 {
background: #f1f1f1;
}
.fgc1 {
color: #999;
}
.fgc0 {
color: #000;
}
p {
margin-top: 1em;
margin-bottom: 1em;
}
.mvm {
margin-top: .75em;
margin-bottom: .75em;
}
.mtn {
margin-top: 0;
}
.mtl, .mal {
margin-top: 1.5em;
}
.mbl, .mal {
margin-bottom: 1.5em;
}
.mal, .mhl {
margin-left: 1.5em;
margin-right: 1.5em;
}
.mhmm {
margin-left: 1em;
margin-right: 1em;
}
.mls {
margin-left: .25em;
}
.ptl {
padding-top: 1.5em;
}
.pbs, .pvs {
padding-bottom: .25em;
}
.pvs, .pts {
padding-top: .25em;
}
.unit {
float: left;
}
.unitRight {
float: right;
}
.size1of2 {
width: 50%;
}
.size1of1 {
width: 100%;
}
.clearfix:before, .clearfix:after {
content: " ";
display: table;
}
.clearfix:after {
clear: both;
}
.hidden-true {
display: none;
}
.textbox0 {
width: 3em;
background: #f1f1f1;
padding: .25em .5em;
line-height: 1.5;
height: 1.5em;
}
#testDrive {
display: block;
padding-top: 24px;
line-height: 1.5;
}
.fs0 {
font-size: 16px;
}
.fs1 {
font-size: 32px;
}

View File

@@ -0,0 +1,30 @@
if (!('boxShadow' in document.body.style)) {
document.body.setAttribute('class', 'noBoxShadow');
}
document.body.addEventListener("click", function(e) {
var target = e.target;
if (target.tagName === "INPUT" &&
target.getAttribute('class').indexOf('liga') === -1) {
target.select();
}
});
(function() {
var fontSize = document.getElementById('fontSize'),
testDrive = document.getElementById('testDrive'),
testText = document.getElementById('testText');
function updateTest() {
testDrive.innerHTML = testText.value || String.fromCharCode(160);
if (window.icomoonLiga) {
window.icomoonLiga(testDrive);
}
}
function updateSize() {
testDrive.style.fontSize = fontSize.value + 'px';
}
fontSize.addEventListener('change', updateSize, false);
testText.addEventListener('input', updateTest, false);
testText.addEventListener('change', updateTest, false);
updateSize();
}());

View File

@@ -0,0 +1,360 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>IcoMoon Demo</title>
<meta name="description" content="An Icon Font Generated By IcoMoon.io">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="demo-files/demo.css">
<link rel="stylesheet" href="style.css"></head>
<body>
<div class="bgc1 clearfix">
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> wled122 <small class="fgc1">(Glyphs:&nbsp;23)</small></h1>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: Unknown</h1>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-pattern"></span>
<span class="mls"> i-pattern</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e23d" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe23d;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-segments"></span>
<span class="mls"> i-segments</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e34b" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe34b;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-sun"></span>
<span class="mls"> i-sun</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e333" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe333;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-palette"></span>
<span class="mls"> i-palette</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e2b3" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe2b3;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-eye"></span>
<span class="mls"> i-eye</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e0e8" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe0e8;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-speed"></span>
<span class="mls"> i-speed</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e325" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe325;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-expand"></span>
<span class="mls"> i-expand</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e395" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe395;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-power"></span>
<span class="mls"> i-power</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e08f" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe08f;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-settings"></span>
<span class="mls"> i-settings</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e0a2" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe0a2;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-playlist"></span>
<span class="mls"> i-playlist</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e139" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe139;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-night"></span>
<span class="mls"> i-night</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e2a2" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe2a2;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-cancel"></span>
<span class="mls"> i-cancel</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e38f" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe38f;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-sync"></span>
<span class="mls"> i-sync</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e116" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe116;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-confirm"></span>
<span class="mls"> i-confirm</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e390" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe390;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-brightness"></span>
<span class="mls"> i-brightness</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e2a6" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe2a6;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-nodes"></span>
<span class="mls"> i-nodes</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e22d" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe22d;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-add"></span>
<span class="mls"> i-add</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e18a" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe18a;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-edit"></span>
<span class="mls"> i-edit</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e2c6" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe2c6;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-intensity"></span>
<span class="mls"> i-intensity</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e409" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe409;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-star"></span>
<span class="mls"> i-star</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e410" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe410;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-info"></span>
<span class="mls"> i-info</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e066" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe066;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-del"></span>
<span class="mls"> i-del</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e037" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe037;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-presets"></span>
<span class="mls"> i-presets</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e04c" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe04c;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
</div>
<!--[if gt IE 8]><!-->
<div class="mhl clearfix mbl">
<h1>Font Test Drive</h1>
<label>
Font Size: <input id="fontSize" type="number" class="textbox0 mbm"
min="8" value="48" />
px
</label>
<input id="testText" type="text" class="phl size1of1 mvl"
placeholder="Type some text to test..." value=""/>
<div id="testDrive" class="i-" style="font-family: wled122">&nbsp;
</div>
</div>
<!--<![endif]-->
<div class="bgc1 clearfix">
<p class="mhl">Generated by <a href="https://icomoon.io/app">IcoMoon</a></p>
</div>
<script src="demo-files/demo.js"></script>
</body>
</html>

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,96 @@
@font-face {
font-family: 'wled122';
src:
url('fonts/wled122.woff2?e3eban') format('woff2'),
url('fonts/wled122.ttf?e3eban') format('truetype'),
url('fonts/wled122.woff?e3eban') format('woff'),
url('fonts/wled122.svg?e3eban#wled122') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
[class^="i-"], [class*=" i-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'wled122' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.i-pattern:before {
content: "\e23d";
}
.i-segments:before {
content: "\e34b";
}
.i-sun:before {
content: "\e333";
}
.i-palette:before {
content: "\e2b3";
}
.i-eye:before {
content: "\e0e8";
}
.i-speed:before {
content: "\e325";
}
.i-expand:before {
content: "\e395";
}
.i-power:before {
content: "\e08f";
}
.i-settings:before {
content: "\e0a2";
}
.i-playlist:before {
content: "\e139";
}
.i-night:before {
content: "\e2a2";
}
.i-cancel:before {
content: "\e38f";
}
.i-sync:before {
content: "\e116";
}
.i-confirm:before {
content: "\e390";
}
.i-brightness:before {
content: "\e2a6";
}
.i-nodes:before {
content: "\e22d";
}
.i-add:before {
content: "\e18a";
}
.i-edit:before {
content: "\e2c6";
}
.i-intensity:before {
content: "\e409";
}
.i-star:before {
content: "\e410";
}
.i-info:before {
content: "\e066";
}
.i-del:before {
content: "\e037";
}
.i-presets:before {
content: "\e04c";
}

View File

@@ -45,12 +45,10 @@ body {
text-align: center;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
scrollbar-width: 6px;
scrollbar-color: var(--c-sb) transparent;
scrollbar-width: 6px;
scrollbar-color: var(--c-sb) transparent;
}
html,
@@ -93,7 +91,7 @@ button {
bottom: calc(var(--bh) + 6px);
right: 4px;
color: var(--c-6);
cursor: pointer;
cursor: pointer;
writing-mode: vertical-rl;
}
@@ -128,11 +126,6 @@ button {
width: 100%;
}
.segt {
table-layout: fixed;
width: 76%;
}
.segtd {
text-align: center;
text-transform: uppercase;
@@ -167,25 +160,25 @@ button {
}
.edit-icon {
position: absolute;
right: -26px;
top: 10px;
display: none;
position: absolute;
right: -26px;
top: 10px;
display: none;
}
.search-icon {
position: absolute;
left: 8px;
top: 10px;
pointer-events: none;
position: absolute;
left: 8px;
top: 10px;
pointer-events: none;
}
.search-cancel-icon {
position: absolute;
right: 8px;
top: 9px;
cursor: pointer;
display: none;
position: absolute;
right: 8px;
top: 9px;
cursor: pointer;
display: none;
}
.flr {
@@ -286,7 +279,7 @@ button {
padding-top: 0;
margin-top: 11px;
height: calc(100% - 11px);
-webkit-overflow-scrolling: touch;
-webkit-overflow-scrolling: touch;
}
.smooth { transition: transform calc(var(--f, 1)*.5s) ease-out }
@@ -294,6 +287,7 @@ button {
.tab-label {
margin: 0 0 -5px 0;
padding-bottom: 4px;
display: var(--bhd);
}
.overlay {
@@ -332,7 +326,7 @@ button {
#fxb0 {
margin-bottom: 2px;
filter: drop-shadow(0 0 1px #000);
filter: drop-shadow(0 0 1px #000);
}
.first {
@@ -382,7 +376,7 @@ button {
}
.modal button:hover {
background-color: var(--c-4);
background-color: var(--c-4);
}
#info {
@@ -394,7 +388,7 @@ button {
}
#ndlt {
margin: 12px 0;
margin: 12px 0;
}
.valtd i {
@@ -430,7 +424,7 @@ button {
}
#kn td {
padding-bottom: 12px;
padding-bottom: 12px;
}
#lv {
@@ -469,27 +463,27 @@ img {
border-radius: 17px;
pointer-events: none;
z-index: -1;
--bg: var(--c-f);
--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);
background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #cbdbff);
}
.sliderbubble {
width: 36px;
line-height: 24px;
background: var(--c-3);
position: absolute;
transform: translateX(-50%);
border-radius: 12px;
margin-left: 12px;
margin-top: 3px;
padding: 0px;
display: inline;
width: 36px;
line-height: 24px;
background: var(--c-3);
position: absolute;
transform: translateX(-50%);
border-radius: 12px;
margin-left: 12px;
margin-top: 3px;
padding: 0px;
display: inline;
}
.hidden {
@@ -540,28 +534,29 @@ input[type=range]:active + .sliderbubble {
display: inline;
transform: translateX(-50%);
}
#wwrap, #wbal {
/* hide color controls until enabled in updateUI() */
#pwrap, #wwrap, #wbal, #rgbwrap, #palwrap {
display: none;
}
/* Slider wrapper div */
.sliderwrap {
height: 30px;
width: 240px;
position: relative;
}
/* Segment power button + brightness slider wrapper div */
.sbs {
margin: 0px -20px 5px -6px;
}
/* Segment brightness slider wrapper div */
.sws {
width: 230px;
}
.sis {
width: 214px !important;
margin-left: -7px;
}
/* Dynamically hide brightness slider label */
.hd {
display: var(--bhd);
}
@@ -576,10 +571,6 @@ input[type=range]:active + .sliderbubble {
width: 260px;
}
#rgbwrap {
display: none;
}
.btn {
padding: 8px;
margin: 10px;
@@ -587,7 +578,7 @@ input[type=range]:active + .sliderbubble {
font-size: 19px;
background-color: var(--c-3);
color: var(--c-f);
cursor: pointer;
cursor: pointer;
border: 0px solid white;
border-radius: 25px;
transition-duration: 0.5s;
@@ -595,6 +586,14 @@ input[type=range]:active + .sliderbubble {
-webkit-transform:translate3d(0,0,0);*/
}
/* Small round button (color selectors, icon-only round buttons) */
.xxs {
width: 40px;
height: 40px;
margin: 6px;
}
/* Segments/presets auxiliary buttons (Add segment, Create preset, ...) */
.btn-s {
width: 276px;
background-color: var(--c-2);
@@ -606,61 +605,80 @@ input[type=range]:active + .sliderbubble {
margin: 0px 8px 4px 0;
vertical-align: middle;
}
.btna-icon {
margin: 0px;
}
/* Wide button used in presets (Save, playlist test, delete) */
.btn-p {
width: 216px;
}
.btn-xs {
width: 42px;
height: 42px;
margin: 2px 0 0 0;
/* Delete preset from playlist button */
.btn-pl-del {
margin: 0 0 0 3px;
}
/* Add preset to playlist "+" button */
.btn-pl-add {
margin-left: 5px;
margin: 3px 0 0 8px;
}
/* Quick color select buttons wrapper div */
#qcs-w {
margin-top: 10px;
display: none;
}
/* Quick color select buttons */
.qcs {
padding: 14px;
margin: 2px;
border-radius: 14px;
display: inline-block;
}
/* Quick color select Black button (has white border) */
.qcsb {
padding: 13px;
border: 1px solid var(--c-f);
}
/* Hex color input wrapper div */
#hexw {
margin-top: 5px;
display: none;
}
/* Transition time input */
#tt {
text-align: center;
text-align: center;
}
/* Color slot select buttons (1,2,3) */
.cl {
width: 42px;
width: 38.5px;
height: 38.5px;
margin: 7px;
background-color: #000;
box-shadow: 0 0 0 1.5px #fff;
}
.selected.cl {
box-shadow: 0 0 0 5px #fff;
}
/* Playlist preset select */
.sel-pl {
width: 192px;
background-position: 168px 16px;
margin: 8px 3px 0 0;
width: 192px;
background-position: 168px 16px;
margin: 8px 3px 0 0;
}
/* Playlist end preset select */
.sel-ple {
width: 216px;
background-position: 192px 16px;
background-position: 192px 16px;
}
select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='white'><polygon points='0,0 100,0 50,50'/></svg>") no-repeat;
background-size: 12px;
@@ -691,7 +709,6 @@ input[type=number], input[type=text] {
outline: none;
width: 50px;
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
}
@@ -708,7 +725,7 @@ textarea {
}
::selection {
background: var(--c-b);
background: var(--c-b);
}
input[type=text] {
@@ -716,18 +733,18 @@ input[type=text] {
text-align: center;
}
.ptxt {
width: 200px !important;
margin: 26px 0 6px 12px !important;
input[type=text].ptxt {
width: 200px;
margin: 26px 0 6px 12px;
}
.stxt {
display: none;
margin-top: 6px !important;
input[type=text].stxt {
display: none;
margin-top: 6px;
}
.qltxt {
width: 50px !important;
input[type=text].qltxt {
width: 50px;
}
input[type=number]:focus, input[type=text]:focus {
@@ -756,42 +773,50 @@ input[type=number]::-webkit-outer-spin-button {
}
.segn {
border-radius: 5px !important;
border-radius: 5px !important;
margin: 3px 0 6px 0 !important;
}
.segname {
.pname, .plname, .segname {
position: absolute;
top: 0px;
left: 50%;
padding: 9px 0;
transform: translateX(-50%);
white-space: nowrap;
cursor: pointer;
}
.segntxt {
max-width: 160px;
overflow: hidden;
text-overflow: clip;
max-width: 160px;
overflow: hidden;
text-overflow: clip;
}
.pname {
.segname {
top: 0px;
padding: 9px 0;
}
.pname, .plname {
width: 208px;
padding: 8px 0;
text-align: center;
overflow: hidden;
text-overflow: clip;
}
.pname {
top: 1px;
}
.plname {
top:0;
}
.pid {
position: absolute;
top: 0px;
left: 0px;
padding: 11px 0px 0px 11px;
font-size: 16px;
width: 20px;
text-align: center;
position: absolute;
top: 0px;
left: 0px;
padding: 11px 0px 0px 11px;
font-size: 16px;
width: 20px;
text-align: center;
color: var(--c-b);
}
@@ -803,11 +828,7 @@ input[type=number]::-webkit-outer-spin-button {
padding: 6px 0 0 0;
}
.xxs {
width: 40px;
margin: 6px;
}
/* Quick preset select buttons */
.psts {
background-color: var(--c-3);
color: var(--c-f);
@@ -816,20 +837,17 @@ input[type=number]::-webkit-outer-spin-button {
height: 40px;
}
/* Segment apply button (checkmark) */
.cnf {
color: var(--c-f);
cursor: pointer;
background: var(--c-3);
border-radius: 5px;
padding: 8.5px 21px 5px;
display: inline;
}
.cnf-s {
position: absolute;
bottom: 100px;
right: 23px;
padding: 7px 22px;
}
/* Segment power button icon */
.pwr {
color: var(--c-6);
transform: translate(2px, 3px);
@@ -840,17 +858,23 @@ input[type=number]::-webkit-outer-spin-button {
color: var(--c-f);
}
.half {
padding: 7.5px;
bottom: 35px;
}
.del {
position: absolute;
bottom: 8px;
right: 8px;
color: var(--c-f);
}
.frz {
left: 36px;
position: absolute;
top: 0px;
cursor: pointer;
padding: 8px;
}
/* TODO expanded does not seem to apply to .frz, what is this for? */
.expanded .frz {
display: none;
}
.check, .radio {
@@ -874,9 +898,9 @@ input[type=number]::-webkit-outer-spin-button {
}
.fxchkl {
position: absolute;
top: 0px;
left: 8px;
position: absolute;
top: 0px;
left: 8px;
}
.check input, .radio input {
@@ -889,19 +913,22 @@ input[type=number]::-webkit-outer-spin-button {
.checkmark, .radiomark {
position: absolute;
bottom: 0;
left: 0;
height: 25px;
width: 25px;
background-color: var(--c-3);
height: 24px;
width: 24px;
background-color: var(--c-4);
border-radius: 10px;
/*border: 1px solid var(--c-2);*/
}
.checkmark {
top: 6px;
}
.radiomark {
height: 24px;
width: 24px;
border-radius: 50%;
background-color: transparent;
background-color: transparent;
top: 7px;
}
.schk {
@@ -923,7 +950,7 @@ input[type=number]::-webkit-outer-spin-button {
.check:hover input ~ .checkmark {
background-color: var(--c-4);
background-color: var(--c-5);
}
.check input:checked ~ .checkmark {
@@ -941,8 +968,8 @@ input[type=number]::-webkit-outer-spin-button {
}
.check .checkmark:after {
left: 9px;
top: 5px;
left: 8px;
top: 4px;
width: 5px;
height: 10px;
border: solid var(--c-f);
@@ -972,7 +999,7 @@ input[type=number]::-webkit-outer-spin-button {
margin-bottom: 5px;
}
.seg {
.seg, .pres {
position: relative;
display: inline-block;
padding: 8px;
@@ -985,29 +1012,41 @@ input[type=number]::-webkit-outer-spin-button {
border-radius: 20px;
text-align: left;
transition: background-color 0.5s;
filter: brightness(1);
filter: brightness(1); /* required for slider background to render? */
}
.selected {
background-color: var(--c-4);
}
/* "selected" CSS class is applied to the segment when it is the main segment.
By default, do not highlight. Can be overridden by skin.css */
.selected.seg {
background-color: var(--c-2); /* var(--c-4); */
}
.selected .checkmark, .selected .radiokmark {
background-color: var(--c-4); /* var(--c-6); */
}
.list {
position: relative;
transition: background-color 0.5s;
margin: auto auto 10px;
padding-bottom: 10px;
width: 230px;
margin: auto auto 10px;
padding-bottom: 10px;
width: 230px;
}
.lstI {
position: sticky;
overflow: hidden;
position: sticky;
overflow: hidden;
}
.fxbtn {
margin: 20px auto;
padding: 8px 0;
margin: 20px auto;
padding: 8px 0;
}
.lstI:hover {
background: var(--c-4);
background: var(--c-4);
}
.lstI:last-child {
@@ -1030,7 +1069,7 @@ input[type=number]::-webkit-outer-spin-button {
.lstI.selected {
background: var(--c-5);
top: 95px;
bottom: -11px;
bottom: -11px;
}
.lstI.sticky {
@@ -1041,26 +1080,26 @@ input[type=number]::-webkit-outer-spin-button {
margin: 3px 0;
white-space: nowrap;
cursor: pointer;
font-size: 19px;
font-size: 19px;
}
.lstIprev {
width: 220px;
height: 5px;
margin: auto;
position: absolute;
bottom: 0px;
left: 5px;
position: absolute;
bottom: 0px;
left: 5px;
}
input[type="text"].search {
display: block;
width: 230px;
box-sizing: border-box;
padding: 8px 8px 9px 38px;
margin: 6px auto 0 auto;
padding: 8px 8px 9px 38px;
margin: 6px auto 0 auto;
text-align: left;
background-color: var(--c-3);
background-color: var(--c-3);
}
input[type="text"].search:focus {
@@ -1098,9 +1137,13 @@ input[type="text"].search:not(:placeholder-shown) {
}
.hrz {
width: auto;
height: 2px;
background-color: var(--c-b);
width: auto;
height: 2px;
background-color: var(--c-b);
}
.no-margin {
margin: 0;
}
::-webkit-scrollbar {
@@ -1120,8 +1163,8 @@ input[type="text"].search:not(:placeholder-shown) {
@media all and (max-width: 335px) {
.sliderbubble {
display: none;
}
display: none;
}
}
@media all and (max-width: 550px) and (min-width: 374px) {
@@ -1146,6 +1189,6 @@ input[type="text"].search:not(:placeholder-shown) {
@media all and (max-width: 1249px) {
#buttonPcm {
display: none;
display: none;
}
}

View File

@@ -44,13 +44,15 @@
<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="pwrap">
<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>
<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" />
@@ -106,34 +108,28 @@
</div>
<div id="hexw">
<input id="hexc" type="text" class="noslide" onkeydown="hexEnter()" autocomplete="off" maxlength="8" />
<button id="hexcnf" class="xxs btn" onclick="fromHex();"><i class="icons btna-icon">&#xe390;</i></button>
<button id="hexcnf" class="xxs btn" onclick="fromHex();"><i class="icons no-margin">&#xe390;</i></button>
</div>
<p class="labels">
<i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i>
Color palette
</p>
<div class="il">
<div id="pallist" class="list">
<div class="lstI" data-id="0">
<label class="check schkl">
&nbsp;
<input type="radio" value="${palettes[i].id}" name="palette" onChange="setPalette()">
<span class="radiomark schk"></span>
</label>
<div class="lstIcontent">
<span class="lstIname">
Default
</span>
<div id="palwrap">
<p class="labels">
<i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i>
Color palette
</p>
<div class="il">
<div id="pallist" class="list">
<div class="lstI" data-id="0">
<label class="check schkl">
&nbsp;
<input type="radio" value="${palettes[i].id}" name="palette" onChange="setPalette()">
<span class="radiomark schk"></span>
</label>
<div class="lstIcontent">
<span class="lstIname">
Default
</span>
</div>
</div>
</div>
<div class="lstI">
<div class="lstIcontent">
<span class="lstIname">
Loading...
</span>
</div>
</div>
</div>
</div>
</div>
@@ -141,7 +137,7 @@
<div id="Effects" class="tabcontent">
<p class="labels">Effect speed</p>
<div class="staytop">
<i class="icons slider-icon" onclick="tglFreeze()">&#xe325;</i>
<i class="icons slider-icon" style="cursor: pointer;" title="Freeze" onclick="tglFreeze()">&#xe325;</i>
<div class="sliderwrap il">
<input id="sliderSpeed" class="noslide" onchange="setSpeed()" oninput="updateTrail(this)" max="255" min="0" type="range" value="128" />
<output class="sliderbubble hidden"></output>
@@ -227,7 +223,6 @@
<button class="btn" onclick="setLor(2)">Override until reboot</button><br>
<span class="h">For best performance, it is recommended to turn off the streaming source when not in use.</span>
</div>
<i id="roverstar" class="icons huge" onclick="setLor(0)">&#xe410;</i><br>
<script src="iro.js"></script>
<script src="rangetouch.js"></script>

View File

@@ -1,15 +1,16 @@
//page js
var loc = false, locip;
var noNewSegs = false;
var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false, syncTglRecv = true, isRgbw = false;
var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false, syncTglRecv = true;
var hasWhite = false, hasRGB = false, hasCCT = false;
var whites = [0,0,0];
var selColors;
var colors = [[0,0,0],[0,0,0],[0,0,0]];
var expanded = [false];
var powered = [true];
var nlDur = 60, nlTar = 0;
var nlMode = false;
var selectedFx = 0;
var csel = 0;
var csel = 0; // selected color slot (0-2)
var currentPreset = -1;
var lastUpdate = 0;
var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0;
@@ -27,7 +28,7 @@ var fxlist = d.getElementById('fxlist'), pallist = d.getElementById('pallist');
var cfg = {
theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}},
comp :{colors:{picker: true, rgb: false, quick: true, hex: false},
labels:true, pcmbot:false, pid:true, seglen:false, css:true, hdays:false}
labels:true, pcmbot:false, pid:true, seglen:false, css:true, hdays:false}
};
var hol = [
[0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas
@@ -40,14 +41,14 @@ var hol = [
var cpick = new iro.ColorPicker("#picker", {
width: 260,
wheelLightness: false,
wheelAngle: 270,
wheelDirection: "clockwise",
layout: [
{
component: iro.ui.Wheel,
options: {}
}
]
wheelAngle: 270,
wheelDirection: "clockwise",
layout: [
{
component: iro.ui.Wheel,
options: {}
}
]
});
function handleVisibilityChange() {
@@ -60,27 +61,48 @@ function sCol(na, col) {
d.documentElement.style.setProperty(na, col);
}
function isRgbBlack(a, s) {
return (a[s][0] == 0 && a[s][1] == 0 && a[s][2] == 0);
}
// returns RGB color from a given slot s 0-2 from color array a
function rgbStr(a, s) {
return "rgb(" + a[s][0] + "," + a[s][1] + "," + a[s][2] + ")";
}
// brightness approximation for selecting white as text color if background bri < 127, and black if higher
function rgbBri(a, s) {
var R = a[s][0], G = a[s][1], B = a[s][2];
return 0.2126*R + 0.7152*G + 0.0722*B;
}
// sets background of color slot selectors
function setCSL(s) {
var cd = d.getElementsByClassName('cl')[s];
var w = whites[s];
if (hasRGB && !isRgbBlack(colors, s)) {
cd.style.background = rgbStr(colors, s);
cd.style.color = rgbBri(colors, s) > 127 ? "#000":"#fff";
if (hasWhite && w > 0) {
cd.style.background = `linear-gradient(180deg, ${rgbStr(colors, s)} 30%, ${rgbStr([[w,w,w]], 0)})`;
}
} else {
if (!hasWhite) w = 0;
cd.style.background = rgbStr([[w,w,w]], 0);
cd.style.color = w > 127 ? "#000":"#fff";
}
}
function applyCfg()
{
cTheme(cfg.theme.base === "light");
var bg = cfg.theme.color.bg;
if (bg) sCol('--c-1', bg);
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";
if (lastinfo.leds) updateUI(); // update component visibility
var l = cfg.comp.labels;
var e = d.querySelectorAll('.tab-label');
for (var i=0; i<e.length; i++)
e[i].style.display = l ? "block":"none";
e = d.querySelector('.hd');
e.style.display = l ? "block":"none";
sCol('--tbp',l ? "14px 14px 10px 14px":"10px 22px 4px 22px");
sCol('--bbp',l ? "9px 0 7px 0":"10px 0 4px 0");
sCol('--bhd',l ? "block":"none");
sCol('--bhd',l ? "block":"none"); // hides/shows button labels
sCol('--bmt',l ? "0px":"5px");
sCol('--t-b', cfg.theme.alpha.tab);
size();
@@ -107,45 +129,45 @@ function tglLabels()
function cTheme(light) {
if (light) {
sCol('--c-1','#eee');
sCol('--c-f','#000');
sCol('--c-2','#ddd');
sCol('--c-3','#bbb');
sCol('--c-4','#aaa');
sCol('--c-5','#999');
sCol('--c-6','#999');
sCol('--c-8','#888');
sCol('--c-b','#444');
sCol('--c-c','#333');
sCol('--c-e','#111');
sCol('--c-d','#222');
sCol('--c-r','#c42');
sCol('--c-o','rgba(204, 204, 204, 0.9)');
sCol('--c-sb','#0003'); sCol('--c-sbh','#0006');
sCol('--c-tb','rgba(204, 204, 204, var(--t-b))');
sCol('--c-tba','rgba(170, 170, 170, var(--t-b))');
sCol('--c-tbh','rgba(204, 204, 204, var(--t-b))');
d.getElementById('imgw').style.filter = "invert(0.8)";
} else {
sCol('--c-1','#111');
sCol('--c-f','#fff');
sCol('--c-2','#222');
sCol('--c-3','#333');
sCol('--c-4','#444');
sCol('--c-5','#555');
sCol('--c-6','#666');
sCol('--c-8','#888');
sCol('--c-b','#bbb');
sCol('--c-c','#ccc');
sCol('--c-e','#eee');
sCol('--c-d','#ddd');
sCol('--c-r','#831');
sCol('--c-o','rgba(34, 34, 34, 0.9)');
sCol('--c-sb','#fff3'); sCol('--c-sbh','#fff5');
sCol('--c-tb','rgba(34, 34, 34, var(--t-b))');
sCol('--c-tba','rgba(102, 102, 102, var(--t-b))');
sCol('--c-tbh','rgba(51, 51, 51, var(--t-b))');
d.getElementById('imgw').style.filter = "unset";
sCol('--c-1','#eee');
sCol('--c-f','#000');
sCol('--c-2','#ddd');
sCol('--c-3','#bbb');
sCol('--c-4','#aaa');
sCol('--c-5','#999');
sCol('--c-6','#999');
sCol('--c-8','#888');
sCol('--c-b','#444');
sCol('--c-c','#333');
sCol('--c-e','#111');
sCol('--c-d','#222');
sCol('--c-r','#c42');
sCol('--c-o','rgba(204, 204, 204, 0.9)');
sCol('--c-sb','#0003'); sCol('--c-sbh','#0006');
sCol('--c-tb','rgba(204, 204, 204, var(--t-b))');
sCol('--c-tba','rgba(170, 170, 170, var(--t-b))');
sCol('--c-tbh','rgba(204, 204, 204, var(--t-b))');
d.getElementById('imgw').style.filter = "invert(0.8)";
} else { // default dark theme
sCol('--c-1','#111');
sCol('--c-f','#fff');
sCol('--c-2','#222');
sCol('--c-3','#333');
sCol('--c-4','#444');
sCol('--c-5','#555');
sCol('--c-6','#666');
sCol('--c-8','#888');
sCol('--c-b','#bbb');
sCol('--c-c','#ccc');
sCol('--c-e','#eee');
sCol('--c-d','#ddd');
sCol('--c-r','#831');
sCol('--c-o','rgba(34, 34, 34, 0.9)');
sCol('--c-sb','#fff3'); sCol('--c-sbh','#fff5');
sCol('--c-tb','rgba(34, 34, 34, var(--t-b))');
sCol('--c-tba','rgba(102, 102, 102, var(--t-b))');
sCol('--c-tbh','rgba(51, 51, 51, var(--t-b))');
d.getElementById('imgw').style.filter = "unset";
}
}
@@ -224,10 +246,6 @@ function onLoad() {
loadBg(cfg.theme.bg.url);
if (cfg.comp.css) loadSkinCSS('skinCss');
var cd = d.getElementById('csl').children;
for (var i = 0; i < cd.length; i++) {
cd[i].style.backgroundColor = "rgb(0, 0, 0)";
}
selectSlot(0);
updateTablinks(0);
resetUtil();
@@ -357,7 +375,7 @@ function cpBck() {
copyText.select();
copyText.setSelectionRange(0, 999999);
d.execCommand("copy");
showToast("Copied to clipboard!");
}
@@ -370,7 +388,7 @@ function presetError(empty)
} catch (e) {
}
var cn = `<div class="seg c">`;
var cn = `<div class="pres c">`;
if (empty)
cn += `You have no presets yet!`;
else
@@ -418,7 +436,7 @@ function loadPresets(callback = null)
})
.then(res => {
if (!res.ok) {
showErrorToast();
showErrorToast();
}
return res.json();
})
@@ -479,9 +497,9 @@ function populatePresets(fromls)
if (qll) pQL.push([i, qll]);
is.push(i);
cn += `<div class="seg pres" id="p${i}o">`;
cn += `<div class="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>
cn += `<div class="${isPlaylist(i)?'plname':'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>`;
@@ -524,7 +542,7 @@ function populateInfo(i)
urows += inforow(k,val);
}
}
}
}
var vcn = "Kuuhaku";
if (i.ver.startsWith("0.13.")) vcn = "Toki";
@@ -536,8 +554,8 @@ function populateInfo(i)
${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")}
${inforow("Uptime",getRuntimeStr(i.uptime))}
${inforow("Free heap",heap," kB")}
${inforow("Estimated current",pwru)}
${inforow("Frames / second",i.leds.fps)}
${inforow("Estimated current",pwru)}
${inforow("Frames / second",i.leds.fps)}
${inforow("MAC address",i.mac)}
${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")}
${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")}
@@ -548,6 +566,7 @@ function populateInfo(i)
function populateSegments(s)
{
var cn = "";
let li = lastinfo;
segCount = 0; lowestUnused = 0; lSeg = 0;
for (var y = 0; y < (s.seg||[]).length; y++)
@@ -560,12 +579,13 @@ function populateSegments(s)
if (i == lowestUnused) lowestUnused = i+1;
if (i > lSeg) lSeg = i;
cn += `<div class="seg">
cn += `<div class="seg ${i==s.mainseg ? 'selected' : ''}">
<label class="check schkl">
&nbsp;
<input type="checkbox" id="seg${i}sel" onchange="selSeg(${i})" ${inst.sel ? "checked":""}>
<span class="checkmark schk"></span>
</label>
<i class="icons e-icon frz" id="seg${i}frz" onclick="event.preventDefault();tglFreeze(${i});" style="display:${inst.frz?"inline":"none"}">&#x${li.live && li.liveseg==i?'e410':'e325'};</i>
<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>
@@ -576,7 +596,7 @@ function populateSegments(s)
<div class="sbs">
<i class="icons e-icon pwr ${powered[i] ? "act":""}" id="seg${i}pwr" onclick="setSegPwr(${i})">&#xe08f;</i>
<div class="sliderwrap il sws">
<input id="seg${i}bri" class="noslide sis" onchange="setSegBri(${i})" oninput="updateTrail(this)" max="255" min="1" type="range" value="${inst.bri}" />
<input id="seg${i}bri" class="noslide" onchange="setSegBri(${i})" oninput="updateTrail(this)" max="255" min="1" type="range" value="${inst.bri}" />
<div class="sliderdisplay"></div>
</div>
</div>
@@ -601,7 +621,7 @@ function populateSegments(s)
<tr>
<td class="segtd"><input class="noslide segn" id="seg${i}grp" type="number" min="1" max="255" value="${inst.grp}" oninput="updateLen(${i})" onkeydown="segEnter(${i})"></td>
<td class="segtd"><input class="noslide segn" id="seg${i}spc" type="number" min="0" max="255" value="${inst.spc}" oninput="updateLen(${i})" onkeydown="segEnter(${i})"></td>
<td class="segtd"><i class="icons e-icon cnf cnf-s" id="segc${i}" onclick="setSeg(${i})">&#xe390;</i></td>
<td class="segtd"><i class="icons e-icon cnf" id="segc${i}" onclick="setSeg(${i})">&#xe390;</i></td>
</tr>
</table>
<div class="h" id="seg${i}len"></div>
@@ -616,8 +636,8 @@ function populateSegments(s)
<span class="checkmark schk"></span>
</label>
<div class="del">
<button class="btn btn-i btn-xs" id="segr${i}" title="Repeat until end" onclick="rptSeg(${i})"><i class="icons btn-icon">&#xe22d;</i></button>
<button class="btn btn-i btn-xs" id="segd${i}" title="Delete" onclick="delSeg(${i})"><i class="icons btn-icon">&#xe037;</i></button>
<button class="xxs btn no-margin" id="segr${i}" title="Repeat until end" onclick="rptSeg(${i})"><i class="icons no-margin">&#xe22d;</i></button>
<button class="xxs btn no-margin" id="segd${i}" title="Delete" onclick="delSeg(${i})"><i class="icons no-margin">&#xe037;</i></button>
</div>
</div>
</div><br>`;
@@ -695,14 +715,13 @@ function populatePalettes(palettes)
for (let i = 0; i < palettes.length; i++) {
html += generateListItemHtml(
'palette',
palettes[i].id,
palettes[i].name,
'setPalette',
palettes[i].id,
palettes[i].name,
'setPalette',
`<div class="lstIprev" style="${genPalPrevCss(palettes[i].id)}"></div>`,
palettes[i].class,
);
);
}
pallist.innerHTML=html;
}
@@ -754,11 +773,11 @@ function genPalPrevCss(id)
g = Math.random() * 255;
b = Math.random() * 255;
} else {
if (selColors) {
if (colors) {
let pos = element[1] - 1;
r = selColors[pos][0];
g = selColors[pos][1];
b = selColors[pos][2];
r = colors[pos][0];
g = colors[pos][1];
b = colors[pos][2];
}
}
if (index === false) {
@@ -773,7 +792,7 @@ function genPalPrevCss(id)
function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', extraClass = '')
{
return `<div class="lstI btn fxbtn ${extraClass}" data-id="${id}" onClick="${clickAction}(${id})">
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>
@@ -784,32 +803,32 @@ function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', e
${extraHtml}
</div>`;
}
function btype(b){
switch (b) {
case 32: return "ESP32";
case 82: return "ESP8266";
}
return "?";
switch (b) {
case 32: return "ESP32";
case 82: return "ESP8266";
}
return "?";
}
function bname(o){
if (o.name=="WLED") return o.ip;
return o.name;
if (o.name=="WLED") return o.ip;
return o.name;
}
function populateNodes(i,n)
{
var cn="";
var urows="";
var nnodes = 0;
var nnodes = 0;
if (n.nodes) {
n.nodes.sort((a,b) => (a.name).localeCompare(b.name));
for (var x=0;x<n.nodes.length;x++) {
var o = n.nodes[x];
if (o.name) {
var url = `<button class="btn btna-icon tab" onclick="location.assign('http://${o.ip}');">${bname(o)}</button>`;
var url = `<button class="btn no-margin 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++;
}
}
}
@@ -904,20 +923,21 @@ function updateLen(s)
//updates background color of currently selected preset
function updatePA()
{
var ps = d.getElementsByClassName("seg"); //reset all preset buttons
for (let i = 0; i < ps.length; i++) {
ps[i].style.backgroundColor = "var(--c-2)";
var ps = d.getElementsByClassName("pres"); //reset all preset buttons
for (var i of ps) {
i.classList.remove("selected");
}
ps = d.getElementsByClassName("psts"); //reset all quick selectors
for (let i = 0; i < ps.length; i++) {
ps[i].style.backgroundColor = "var(--c-2)";
for (var i of ps) {
i.classList.remove("selected");
}
if (currentPreset > 0) {
var acv = d.getElementById(`p${currentPreset}o`);
if (acv && !expanded[currentPreset+100])
acv.style.background = "var(--c-6)"; //highlight current preset
acv.classList.add("selected");
acv = d.getElementById(`p${currentPreset}qlb`);
if (acv) acv.style.background = "var(--c-6)"; //highlight quick selector
if (acv)
acv.classList.add("selected");
}
}
@@ -930,9 +950,15 @@ function updateUI()
updateTrail(d.getElementById('sliderBri'));
updateTrail(d.getElementById('sliderSpeed'));
updateTrail(d.getElementById('sliderIntensity'));
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";
d.getElementById('wwrap').style.display = (hasWhite) ? "block":"none";
d.getElementById('wbal').style.display = (hasCCT) ? "block":"none";
var ccfg = cfg.comp.colors;
d.getElementById('hexw').style.display = ccfg.hex ? "block":"none";
d.getElementById('pwrap').style.display = (hasRGB && ccfg.picker) ? "block":"none";
d.getElementById('kwrap').style.display = (hasRGB && !hasCCT && ccfg.picker) ? "block":"none";
d.getElementById('rgbwrap').style.display = (hasRGB && ccfg.rgb) ? "block":"none";
d.getElementById('qcs-w').style.display = (hasRGB && ccfg.quick) ? "block":"none";
d.getElementById('palwrap').style.display = hasRGB ? "block":"none";
updatePA();
updatePSliders();
@@ -940,7 +966,7 @@ function updateUI()
function displayRover(i,s)
{
d.getElementById('rover').style.transform = (i.live && s.lor == 0) ? "translateY(0px)":"translateY(100%)";
d.getElementById('rover').style.transform = (i.live && s.lor == 0 && i.liveseg<0) ? "translateY(0px)":"translateY(100%)";
var sour = i.lip ? i.lip:""; if (sour.length > 2) sour = " from " + sour;
d.getElementById('lv').innerHTML = `WLED is receiving live ${i.lm} data${sour}`;
d.getElementById('roverstar').style.display = (i.live && s.lor) ? "block":"none";
@@ -952,7 +978,14 @@ 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});
// sort playlists first, followed by presets with characters and last presets with special 1st character
const c = a[1].n.charCodeAt(0);
const d = b[1].n.charCodeAt(0);
if ((c>47 && c<58) || (c>64 && c<91) || (c>96 && c<123) || c>255) x = '='; else x = '>';
if ((d>47 && d<58) || (d>64 && d<91) || (d>96 && d<123) || d>255) y = '='; else y = '>';
const n = (a[1].playlist ? '<' : x) + a[1].n;
return n.localeCompare((b[1].playlist ? '<' : y) + b[1].n, undefined, {numeric: true});
}
//forces a WebSockets reconnect if timeout (error toast), or successful HTTP response to JSON request
@@ -964,10 +997,10 @@ function reconnectWS() {
function makeWS() {
if (ws) return;
ws = new WebSocket('ws://'+(loc?locip:window.location.hostname)+'/ws');
ws = new WebSocket((window.location.protocol == 'https:'?'wss':'ws')+'://'+(loc?locip:window.location.hostname)+'/ws');
ws.binaryType = "arraybuffer";
ws.onmessage = function(event) {
if (event.data instanceof ArrayBuffer) return; //liveview packet
if (event.data instanceof ArrayBuffer) return; //liveview packet
var json = JSON.parse(event.data);
clearTimeout(jsonTimeout);
jsonTimeout = null;
@@ -996,90 +1029,108 @@ function makeWS() {
}
function readState(s,command=false) {
isOn = s.on;
d.getElementById('sliderBri').value= s.bri;
nlA = s.nl.on;
nlDur = s.nl.dur;
nlTar = s.nl.tbri;
nlMode = s.nl.mode;
syncSend = s.udpn.send;
currentPreset = s.ps;
tr = s.transition;
d.getElementById('tt').value = tr/10;
isOn = s.on;
d.getElementById('sliderBri').value= s.bri;
nlA = s.nl.on;
nlDur = s.nl.dur;
nlTar = s.nl.tbri;
nlMode = s.nl.mode;
syncSend = s.udpn.send;
currentPreset = s.ps;
tr = s.transition;
d.getElementById('tt').value = tr/10;
var selc=0; var ind=0;
populateSegments(s);
for (let i = 0; i < (s.seg||[]).length; i++)
{
if(s.seg[i].sel) {selc = ind; break;} ind++;
}
var i=s.seg[selc];
if (!i) {
showToast('No Segments!', true);
updateUI();
return;
}
selColors = i.col;
var cd = d.getElementById('csl').children;
for (let e = 2; e >= 0; e--)
{
cd[e].style.backgroundColor = "rgb(" + i.col[e][0] + "," + i.col[e][1] + "," + i.col[e][2] + ")";
if (isRgbw) whites[e] = parseInt(i.col[e][3]);
selectSlot(csel);
}
if (i.cct != null && i.cct>=0) d.getElementById("sliderA").value = i.cct;
populateSegments(s);
var selc=0;
var sellvl=0; // 0: selc is invalid, 1: selc is mainseg, 2: selc is first selected
hasRGB = hasWhite = hasCCT = false;
for (let i = 0; i < (s.seg||[]).length; i++)
{
if (sellvl == 0 && s.seg[i].id == s.mainseg) {
selc = i;
sellvl = 1;
}
if (s.seg[i].sel) {
if (sellvl < 2) selc = i; // get first selected segment
sellvl = 2;
var lc = lastinfo.leds.seglc[s.seg[i].id];
hasRGB |= lc & 0x01;
hasWhite |= lc & 0x02;
hasCCT |= lc & 0x04;
}
}
var i=s.seg[selc];
if (sellvl == 1) {
var lc = lastinfo.leds.seglc[i.id];
hasRGB = lc & 0x01;
hasWhite = lc & 0x02;
hasCCT = lc & 0x04;
}
if (!i) {
showToast('No Segments!', true);
updateUI();
return;
}
colors = i.col;
for (let e = 0; e < 3; e++)
{
if (i.col[e].length > 3) whites[e] = parseInt(i.col[e][3]);
setCSL(e);
}
selectSlot(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;
d.getElementById('sliderSpeed').value = i.sx;
d.getElementById('sliderIntensity').value = i.ix;
// Effects
var selFx = fxlist.querySelector(`input[name="fx"][value="${i.fx}"]`);
if (selFx) selFx.checked = true;
else location.reload(); //effect list is gone (e.g. if restoring tab). Reload.
// Effects
var selFx = fxlist.querySelector(`input[name="fx"][value="${i.fx}"]`);
if (selFx) selFx.checked = true;
else location.reload(); //effect list is gone (e.g. if restoring tab). Reload.
var selElement = fxlist.querySelector('.selected');
if (selElement) {
selElement.classList.remove('selected')
}
var selectedEffect = fxlist.querySelector(`.lstI[data-id="${i.fx}"]`);
selectedEffect.classList.add('selected');
selectedFx = i.fx;
var selElement = fxlist.querySelector('.selected');
if (selElement) {
selElement.classList.remove('selected')
}
var selectedEffect = fxlist.querySelector(`.lstI[data-id="${i.fx}"]`);
selectedEffect.classList.add('selected');
selectedFx = i.fx;
// Palettes
pallist.querySelector(`input[name="palette"][value="${i.pal}"]`).checked = true;
selElement = pallist.querySelector('.selected');
if (selElement) {
selElement.classList.remove('selected')
}
pallist.querySelector(`.lstI[data-id="${i.pal}"]`).classList.add('selected');
// Palettes
pallist.querySelector(`input[name="palette"][value="${i.pal}"]`).checked = true;
selElement = pallist.querySelector('.selected');
if (selElement) {
selElement.classList.remove('selected')
}
pallist.querySelector(`.lstI[data-id="${i.pal}"]`).classList.add('selected');
if (!command) {
selectedEffect.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
});
}
if (!command) {
selectedEffect.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
});
}
if (s.error && s.error != 0) {
var errstr = "";
switch (s.error) {
case 10:
errstr = "Could not mount filesystem!";
break;
case 11:
errstr = "Not enough space to save preset!";
break;
case 12:
errstr = "Preset not found.";
break;
case 19:
errstr = "A filesystem error has occured.";
break;
}
showToast('Error ' + s.error + ": " + errstr, true);
}
updateUI();
if (s.error && s.error != 0) {
var errstr = "";
switch (s.error) {
case 10:
errstr = "Could not mount filesystem!";
break;
case 11:
errstr = "Not enough space to save preset!";
break;
case 12:
errstr = "Preset not found.";
break;
case 19:
errstr = "A filesystem error has occured.";
break;
}
showToast('Error ' + s.error + ": " + errstr, true);
}
updateUI();
}
var jsonTimeout;
@@ -1102,15 +1153,15 @@ function requestJson(command, rinfo = true) {
var type = command ? 'post':'get';
if (command)
{
command.v = true; //get complete API response
command.time = Math.floor(Date.now() / 1000);
var t = d.getElementById('tt');
if (t.validity.valid && command.transition===undefined) {
var tn = parseInt(t.value*10);
if (tn != tr) command.transition = tn;
}
command.v = true; //get complete API response
command.time = Math.floor(Date.now() / 1000);
var t = d.getElementById('tt');
if (t.validity.valid && command.transition===undefined) {
var tn = parseInt(t.value*10);
if (tn != tr) command.transition = tn;
}
req = JSON.stringify(command);
if (req.length > 1000) useWs = false; //do not send very long requests over websocket
if (req.length > 1000) useWs = false; //do not send very long requests over websocket
}
if (useWs) {
@@ -1176,7 +1227,6 @@ function requestJson(command, rinfo = true) {
name = "(L) " + name;
}
d.title = name;
isRgbw = info.leds.wv;
ledCount = info.leds.count;
syncTglRecv = info.str;
maxSeg = info.leds.maxseg;
@@ -1243,7 +1293,7 @@ function toggleLiveview() {
}
function toggleInfo() {
if (isNodes) toggleNodes();
if (isNodes) toggleNodes();
isInfo = !isInfo;
if (isInfo) populateInfo(lastinfo);
d.getElementById('info').style.transform = (isInfo) ? "translateY(0px)":"translateY(100%)";
@@ -1272,18 +1322,19 @@ function makeSeg() {
<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">
<table class="infot">
<tr>
<td class="segtd">Start LED</td>
<td class="segtd">${cfg.comp.seglen?"Length":"Stop LED"}</td>
<td class="segtd">Apply</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})" onkeydown="segEnter(${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})" onkeydown="segEnter(${lowestUnused})"></td>
<td class="segtd"><i class="icons e-icon cnf" id="segc${lowestUnused}" onclick="setSeg(${lowestUnused}); resetUtil();">&#xe390;</i></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;
@@ -1380,8 +1431,8 @@ function plR(p) {
}
function makeP(i,pl) {
var content = "";
if (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>
@@ -1405,7 +1456,7 @@ function makeP(i,pl) {
</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">
else content = `<label class="check revchkl">
Include brightness
<input type="checkbox" id="p${i}ibtgl" checked>
<span class="checkmark schk"></span>
@@ -1446,38 +1497,38 @@ ${(i>0)? ('<div class="h">ID ' +i+ '</div>'):""}`;
}
function makePUtil() {
d.getElementById('putil').innerHTML = `<div class="seg pres">
<div class="segname newseg">
d.getElementById('putil').innerHTML = `<div class="pres">
<div class="pname newseg">
New preset</div>
<div class="segin expanded">
${makeP(0)}</div></div>`;
}
function makePlEntry(p,i) {
return `
<div class="plentry">
<select class="btn sel sel-pl" onchange="plePs(${p},${i},this)" data-val=${plJson[p].ps[i]} data-index=${i}>
return `
<div class="plentry">
<select class="btn sel sel-pl" onchange="plePs(${p},${i},this)" data-val=${plJson[p].ps[i]} data-index=${i}>
${makePlSel()}
</select>
<button class="btn btn-i btn-xs btn-pl-del" onclick="delPl(${p},${i})"><i class="icons btn-icon">&#xe037;</i></button>
</select>
<button class="xxs btn btn-pl-del" onclick="delPl(${p},${i})"><i class="icons no-margin">&#xe037;</i></button>
<div class="h plnl">Duration</div><div class="h plnl">Transition</div><div class="h pli">#${i+1}</div><br>
<input class="noslide pln" type="number" max=6553.0 min=0.2 step=0.1 oninput="pleDur(${p},${i},this)" value=${plJson[p].dur[i]/10.0}>
<input class="noslide pln" type="number" max=65.0 min=0.0 step=0.1 oninput="pleTr(${p},${i},this)" value=${plJson[p].transition[i]/10.0}> s
<button class="btn btn-i btn-xs btn-pl-add" onclick="addPl(${p},${i})"><i class="icons btn-icon">&#xe18a;</i></button>
<div class="hrz"></div>
</div>`;
<button class="xxs btn btn-pl-add" onclick="addPl(${p},${i})"><i class="icons no-margin">&#xe18a;</i></button>
<div class="hrz"></div>
</div>`;
}
function makePlUtil() {
if (pNum < 1) {
showToast("Please make a preset first!"); return;
}
if (pNum < 1) {
showToast("Please make a preset first!"); return;
}
if (plJson[0].transition[0] < 0) plJson[0].transition[0] = tr;
d.getElementById('putil').innerHTML = `<div class="seg pres">
<div class="segname newseg">
New playlist</div>
<div class="segin expanded" id="seg100">
${makeP(0,true)}</div></div>`;
d.getElementById('putil').innerHTML = `<div class="pres">
<div class="pname newseg">
New playlist</div>
<div class="segin expanded" id="seg100">
${makeP(0,true)}</div></div>`;
refreshPlE(0);
}
@@ -1497,15 +1548,17 @@ function tglCs(i){
function tglSegn(s)
{
d.getElementById(`seg${s}t`).style.display =
(window.getComputedStyle(d.getElementById(`seg${s}t`)).display === "none") ? "inline":"none";
(window.getComputedStyle(d.getElementById(`seg${s}t`)).display === "none") ? "inline":"none";
}
// Select only the clicked segment and unselect all others
function selSegEx(s)
{
var obj = {"seg":[]};
for (let i=0; i<=lSeg; i++){
obj.seg.push({"sel":(i==s)?true:false});
}
for (let i=0; i<=lSeg; i++) obj.seg.push({"id":i,"sel":(i==s)});
// optionally, force mainseg to be first selected
// WLED internally regards the first selected as mainseg regardless of this as long as any segment is selected
//obj.mainseg = s;
requestJson(obj);
}
@@ -1520,7 +1573,7 @@ function rptSeg(s)
var name = d.getElementById(`seg${s}t`).value;
var start = parseInt(d.getElementById(`seg${s}s`).value);
var stop = parseInt(d.getElementById(`seg${s}e`).value);
if (stop == 0) {return;}
if (stop == 0) return;
var rev = d.getElementById(`seg${s}rev`).checked;
var mi = d.getElementById(`seg${s}mi`).checked;
var sel = d.getElementById(`seg${s}sel`).checked;
@@ -1592,7 +1645,11 @@ function setSegBri(s){
function tglFreeze(s=null)
{
var obj = {"seg": {"frz": "t"}}; // toggle
if (s!==null) obj.id = s;
if (s!==null) {
obj.seg.id = s;
// if live segment, enter live override (which also unfreezes)
if (lastinfo && s==lastinfo.liveseg && lastinfo.live) obj = {"lor":1};
}
requestJson(obj);
}
@@ -1748,16 +1805,12 @@ function delP(i) {
function selectSlot(b) {
csel = b;
var cd = d.getElementById('csl').children;
for (let i = 0; i < cd.length; i++) {
cd[i].style.border="2px solid white";
cd[i].style.margin="5px";
cd[i].style.width="42px";
var cd = d.getElementsByClassName('cl');
for (var i of cd) {
i.classList.remove("selected");
}
cd[csel].style.border="5px solid white";
cd[csel].style.margin="2px";
cd[csel].style.width="50px";
setPicker(cd[csel].style.backgroundColor);
cd[csel].classList.add("selected");
setPicker(rgbStr(colors, csel));
//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];
@@ -1792,7 +1845,7 @@ function updatePSliders() {
s = d.getElementById('sliderB');
s.value = col.b; updateTrail(s,3);
//update hex field
//update hex field
var str = cpick.color.hexString.substring(1);
var w = whites[csel];
if (w > 0) str += w.toString(16);
@@ -1800,24 +1853,26 @@ function updatePSliders() {
d.getElementById('hexcnf').style.backgroundColor = "var(--c-3)";
//update value slider
var v = d.getElementById('sliderV');
v.value = cpick.color.value;
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);
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;
// update Kelvin slider
d.getElementById('sliderK').value = cpick.color.kelvin;
}
// Fired when a key is pressed while in the HEX color input
function hexEnter() {
d.getElementById('hexcnf').style.backgroundColor = "var(--c-6)";
if(event.keyCode == 13) fromHex();
}
// Fired when a key is pressed while in a segment input
function segEnter(s) {
if(event.keyCode == 13) setSeg(s);
}
@@ -1861,17 +1916,13 @@ function fromRgb()
//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);
cd[csel].style.backgroundColor = cpick.color.rgbString;
if (sr == 1 && colors[csel][0] == 0 && colors[csel][1] == 0 && colors[csel][2] == 0) cpick.color.setChannel('hsv', 'v', 100);
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) {
obj = {"seg": {"col": [[],[col.r, col.g, col.b, whites[csel]],[]]}};
} else if (csel == 2) {
obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}};
}
colors[csel] = [col.r, col.g, col.b, whites[csel]];
setCSL(csel);
var obj = {"seg": {"col": [[],[],[]]}};
obj.seg.col[csel] = colors[csel];
requestJson(obj);
}
@@ -1934,7 +1985,6 @@ function loadPalettesData(callback = null)
var d = new Date();
if (palettesDataJson && palettesDataJson.vid == lastinfo.vid) {
palettesData = palettesDataJson.p;
//redrawPalPrev() //?
if (callback) callback();
return;
}
@@ -2001,10 +2051,10 @@ function search(searchField) {
}
function cancelSearch(ic) {
var searchField = ic.parentElement.getElementsByClassName('search')[0];
searchField.value = "";
search(searchField);
searchField.focus();
var searchField = ic.parentElement.getElementsByClassName('search')[0];
searchField.value = "";
search(searchField);
searchField.focus();
}
//make sure "dur" and "transition" are arrays with at least the length of "ps"
@@ -2041,9 +2091,9 @@ function expand(i,a)
d.getElementById('seg' +i).style.display = (expanded[i]) ? "block":"none";
d.getElementById('sege' +i).style.transform = (expanded[i]) ? "rotate(180deg)":"rotate(0deg)";
if (i < 100) {
d.getElementById(`seg${i}nedit`).style.display = (expanded[i]) ? "inline":"none";
return; //no preset, we are done
}
d.getElementById(`seg${i}nedit`).style.display = (expanded[i]) ? "inline":"none";
return; //no preset, we are done
}
var p = i-100;
d.getElementById(`p${p}o`).style.background = (expanded[i] || p != currentPreset)?"var(--c-2)":"var(--c-6)";
@@ -2111,9 +2161,9 @@ function move(e) {
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) {
(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);

View File

@@ -45,13 +45,16 @@
}
}
var ws = top.window.ws;
var ws;
try {
ws = top.window.ws;
} catch (e) {}
if (ws && ws.readyState === WebSocket.OPEN) {
console.info("Peek uses top WS");
ws.send("{'lv':true}");
} else {
console.info("Peek WS opening");
ws = new WebSocket("ws://"+document.location.host+"/ws");
ws = new WebSocket((window.location.protocol == "https:"?"wss":"ws")+"://"+document.location.host+"/ws");
ws.onopen = function () {
console.info("Peek WS open");
ws.send("{'lv':true}");

View File

@@ -3,22 +3,15 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=500">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>LED Settings</title>
<script>
var d=document,laprev=55,maxB=1,maxM=4000,maxPB=4096,maxL=1333,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var customStarts=false,startsDirty=[],maxCOOverrides=5;
function H()
{
window.open("https://kno.wled.ge/features/settings/#led-settings");
}
function B()
{
window.open("/settings","_self");
}
function H(){window.open("https://kno.wled.ge/features/settings/#led-settings");}
function B(){window.open("/settings","_self");}
function gId(n){return d.getElementById(n);}
function off(n){
d.getElementsByName(n)[0].value = -1;
}
function off(n){d.getElementsByName(n)[0].value = -1;}
var timeout;
function showToast(text, error = false)
{
@@ -71,7 +64,6 @@
if (bquot > 100) {var msg = "Too many LEDs for me to handle!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);}
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
}
function S(){GetV();checkSi();setABL();}
function enABL()
{
var en = gId('able').checked;
@@ -104,9 +96,11 @@
UI();
}
//returns mem usage
function getMem(t, len, p0) {
function getMem(t, n) {
let len = parseInt(d.getElementsByName("LC"+n)[0].value);
len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too
if (t < 32) {
if (maxM < 10000 && p0==3) { //8266 DMA uses 5x the mem
if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem
if (t > 29) return len*20; //RGBW
return len*15;
} else if (maxM >= 10000) //ESP32 RMT uses double buffer?
@@ -141,7 +135,7 @@
gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : "";
var LK = d.getElementsByName("L1"+n)[0]; // clock pin
memu += getMem(t, d.getElementsByName("LC"+n)[0].value, d.getElementsByName("L0"+n)[0].value); // calc memory
memu += getMem(t, n); // calc memory
// enumerate pins
for (p=1; p<5; p++) {
@@ -165,7 +159,7 @@
}
gId("rf"+n).onclick = (t == 31) ? (function(){return false}) : (function(){}); // prevent change for TM1814
isRGBW |= (t == 30 || t == 31 || (t > 40 && t < 46 && t != 43)); // RGBW checkbox, TYPE_xxxx values from const.h
gId("co"+n).style.display = ((t>=80 && t<96) || t == 41 || t == 42) ? "none":"inline"; // hide color order for PWM W & WW/CW
gId("co"+n).style.display = ((t>=80 && t<96) || (t > 40 && t < 48)) ? "none":"inline"; // hide color order for PWM
gId("dig"+n+"c").style.display = (t > 40 && t < 48) ? "none":"inline"; // hide count for analog
gId("dig"+n+"r").style.display = (t>=80 && t<96) ? "none":"inline"; // hide reversed for virtual
gId("dig"+n+"s").style.display = ((t>=80 && t<96) || (t > 40 && t < 48)) ? "none":"inline"; // hide skip 1st for virtual & analog
@@ -188,13 +182,13 @@
var n = LCs[i].name.substring(2); // bus number
// do we have a led count field
if (nm=="LC") {
var c=parseInt(LCs[i].value,10);
if (!customStarts || !startsDirty[n]) gId("ls"+n).value=sLC;
gId("ls"+n).disabled = !customStarts;
var c=parseInt(LCs[i].value,10); //get LED count
if (!customStarts || !startsDirty[n]) gId("ls"+n).value=sLC; //update start value
gId("ls"+n).disabled = !customStarts; //enable/disable field editing
if(c){
var s = parseInt(gId("ls"+n).value);
if (s+c > sLC) sLC = s+c;
if(c>maxLC)maxLC=c;
var s = parseInt(gId("ls"+n).value); //start value
if (s+c > sLC) sLC = s+c; //update total count
if(c>maxLC)maxLC=c; //max per output
var t = parseInt(d.getElementsByName("LT"+n)[0].value); // LED type SELECT
if (t<80) sPC+=c; //virtual out busses do not count towards physical LEDs
} // increase led count
@@ -303,6 +297,7 @@ ${i+1}:
<option value="50">WS2801</option>
<option value="51">APA102</option>
<option value="52">LPD8806</option>
<option value="54">LPD6803</option>
<option value="53">P9813</option>
<option value="41">PWM White</option>
<option value="42">PWM CCT</option>
@@ -313,7 +308,7 @@ ${i+1}:
<option value="80">DDP RGB (network)</option>
<!--option value="81">E1.31 RGB (network)</option-->
<!--option value="82">ArtNet RGB (network)</option-->
</select>&nbsp;
</select><br>
<div id="co${i}" style="display:inline">Color Order:
<select name="CO${i}">
<option value="0">GRB</option>
@@ -322,8 +317,7 @@ ${i+1}:
<option value="3">RBG</option>
<option value="4">BGR</option>
<option value="5">GBR</option>
</select></div>
<br>
</select><br></div>
<span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />&nbsp;
<div id="dig${i}c" style="display:inline">Length: <input type="number" name="LC${i}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div>
<br>
@@ -333,7 +327,7 @@ ${i+1}:
<span id="p3d${i}"></span><input type="number" name="L3${i}" min="0" max="33" class="xs" onchange="UI()"/>
<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}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${i}" min="0" max="255" value="0" oninput="UI()"></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);
@@ -369,6 +363,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
gId("com_entries").insertAdjacentHTML("beforeend", b);
gId("xo"+i).value = co;
btnCOM(i+1);
UI();
}
function remCOM() {
@@ -377,6 +372,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
if (i === 0) return;
entries[i-1].remove();
btnCOM(i-1);
UI();
}
function resetCOM(_newMaxCOOverrides=undefined) {
@@ -396,8 +392,8 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
function addBtn(i,p,t) {
var c = gId("btns").innerHTML;
var bt = "BT" + String.fromCharCode((i<10?48:55)+i);;
var be = "BE" + String.fromCharCode((i<10?48:55)+i);;
var bt = "BT" + String.fromCharCode((i<10?48:55)+i);
var be = "BE" + String.fromCharCode((i<10?48:55)+i);
c += `Button ${i} GPIO: <input type="number" min="-1" max="40" name="${bt}" onchange="UI()" class="xs" value="${p}">`;
c += `&nbsp;<select name="${be}">`
c += `<option value="0" ${t==0?"selected":""}>Disabled</option>`;
@@ -463,7 +459,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
let lines = e.target.result;
var c = JSON.parse(lines);
if (c.hw) {
if (c.hw.led) {
if (c.hw.led) {
for (var i=0; i<10; i++) addLEDs(-1);
var l = c.hw.led;
l.ins.forEach((v,i,a)=>{
@@ -473,7 +469,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
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("SL"+i)[0].value = v.skip;
d.getElementsByName("RF"+i)[0].checked = v.ref;
d.getElementsByName("CV"+i)[0].checked = v.rev;
});
@@ -504,6 +500,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
}
}
}
function S(){GetV();checkSi();setABL();}
function GetV()
{
//values injected by server while sending HTML
@@ -584,8 +581,8 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
<option value=7>9-key red</option>
<option value=8>JSON remote</option>
</select><span style="cursor: pointer;" onclick="off('IR')">&nbsp;&#215;</span><br>
Apply IR change to main segment only: <input type="checkbox" name="MSO"><br>
<div id="json" style="display:none;">JSON file: <input type="file" name="data" accept=".json"> <input type="button" value="Upload" onclick="uploadFile('/ir.json');"><br></div>
<div id="toast"></div>
<a href="https://kno.wled.ge/interfaces/infrared/" target="_blank">IR info</a><br>
Relay GPIO: <input type="number" min="-1" max="33" name="RL" onchange="UI()" class="xs"> Invert <input type="checkbox" name="RM"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#215;</span><br>
<hr style="width:260px">
@@ -638,5 +635,6 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
<hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
<div id="toast"></div>
</body>
</html>

View File

@@ -95,7 +95,8 @@ Send notifications twice: <input type="checkbox" name="S2"><br>
Enable instance list: <input type="checkbox" name="NL"><br>
Make this instance discoverable: <input type="checkbox" name="NB">
<h3>Realtime</h3>
Receive UDP realtime: <input type="checkbox" name="RD"><br><br>
Receive UDP realtime: <input type="checkbox" name="RD"><br>
Use main segment only: <input type="checkbox" name="MO"><br><br>
<i>Network DMX input</i><br>
Type:
<select name=DI onchange="SP(); adj();">

View File

@@ -6,6 +6,7 @@
<title>Time Settings</title>
<script>
var d=document;
var el=false;
var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
var cals = 'style="font-size:27px;margin-top:-6px;cursor:pointer"'; //hack as to not repeat CSS on all pages
function H()
@@ -31,26 +32,11 @@
{
var t = gId("WD"+i);
t.style.display = t.style.display!=="none" ? "none" : "";
o.innerHTML = t.style.display==="none" ? "&#128467;" : "&#x2715;";
o.innerHTML = t.style.display==="none" ? "&#128197;" : "&#x2715;";
}
function Cs()
{
gId("cac").style.display="none";
gId("coc").style.display="block";
gId("ccc").style.display="none";
if (gId("ca").selected)
{
gId("cac").style.display="block";
}
if (gId("cc").selected)
{
gId("coc").style.display="none";
gId("ccc").style.display="block";
}
if (gId("cn").selected)
{
gId("coc").style.display="none";
}
gId("cac").style.display=(gN("OL").checked)?"block":"none";
}
function BTa()
{
@@ -99,7 +85,7 @@
for(j=0;j<8;j++) {
gId("W"+i+j).checked=wd>>j&1;
}
if ((wd&127) != 127 || (i<8 && (gN("M"+i).value != 1 || gN("D"+i).value != 1 || gN("P"+i).value != 12 || gN("E"+i).value != 31))) {
if ((wd&254) != 254 || (i<8 && (gN("M"+i).value != 1 || gN("D"+i).value != 1 || gN("P"+i).value != 12 || gN("E"+i).value != 31))) {
expand(gId("CB"+i),i); //expand macros with custom DOW or date range set
}
}
@@ -130,6 +116,20 @@
td = tr.insertCell(3);
td.innerHTML = `<input name="MD${b}" type="number" class="s" min="0" max="250" value="${d}" required>`;
}
function getLoc() {
if (!el) {
window.addEventListener("message", (event) => {
if (event.origin !== "https://locate.wled.me") return;
if (event.data instanceof Object) {
d.Sf.LT.value = event.data.lat;
d.Sf.LN.value = event.data.lon;
updLoc();
}
}, false);
el = true;
}
window.open("https://locate.wled.me","_blank");
}
function updLoc(i) {
if (parseFloat(d.Sf.LT.value)<0) { d.Sf.LTR.value = "S"; d.Sf.LT.value = -1*parseFloat(d.Sf.LT.value); } else d.Sf.LTR.value = "N";
if (parseFloat(d.Sf.LN.value)<0) { d.Sf.LNR.value = "W"; d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); } else d.Sf.LNR.value = "E";
@@ -179,27 +179,18 @@
UTC offset: <input name="UO" type="number" min="-65500" max="65500" required> seconds (max. 18 hours)<br>
Current local time is <span class="times">unknown</span>.<br>
Latitude: <select name="LTR"><option value="N">N</option><option value="S">S</option></select><input name="LT" type="number" class="xl" min="0" max="66.6" step="0.01"><br>
Longitude: <select name="LNR"><option value="E">E</option><option value="W">W</option></select><input name="LN" type="number" class="xl" min="0" max="180" step="0.01">
Longitude: <select name="LNR"><option value="E">E</option><option value="W">W</option></select><input name="LN" type="number" class="xl" min="0" max="180" step="0.01"><br>
<button type="button" id="locbtn" onclick="getLoc()">Get location</button>
<div><i>(opens new tab, only works in browser)</i></div>
<div id="sun" class="times"></div>
<h3>Clock</h3>
Clock Overlay:
<select name="OL" onchange="Cs()">
<option value="0" id="cn" selected>None</option>
<option value="1" id="ca">Analog Clock</option>
<option value="2">Single Digit Clock</option>
<option value="3" id="cc">Cronixie Clock</option>
</select><br>
<div id="coc">
First LED: <input name="O1" type="number" min="0" max="255" required> Last LED: <input name="O2" type="number" min="0" max="255" required><br>
<div id="cac">
Analog Clock overlay: <input type="checkbox" name="OL" onchange="Cs()"><br>
<div id="cac">
First LED: <input name="O1" type="number" min="0" max="255" required> Last LED: <input name="O2" type="number" min="0" max="255" required><br>
12h LED: <input name="OM" type="number" min="0" max="255" required><br>
Show 5min marks: <input type="checkbox" name="O5"><br></div>
Show 5min marks: <input type="checkbox" name="O5"><br>
Seconds (as trail): <input type="checkbox" name="OS"><br>
</div>
<div id="ccc">
Cronixie Display: <input name="CX" maxlength="6"><br>
Cronixie Backlight: <input type="checkbox" name="CB"><br>
</div>
Countdown Mode: <input type="checkbox" name="CE"><br>
Countdown Goal:<br>
Date:&nbsp;<nowrap>20<input name="CY" class="xs" type="number" min="0" max="99" required>-<input name="CI" class="xs" type="number" min="1" max="12" required>-<input name="CD" class="xs" type="number" min="1" max="31" required></nowrap><br>

View File

@@ -63,18 +63,19 @@
<h3>Experimental</h3>
Disable WiFi sleep: <input type="checkbox" name="WS"><br>
<i>Can help with connectivity issues.<br>
Do not enable if WiFi is working correctly, increases power consumption.</i>
<div id="ethd">
Do not enable if WiFi is working correctly, increases power consumption.</i>
<div id="ethd">
<h3>Ethernet Type</h3>
<select name="ETH">
<option value="0">None</option>
<option value="2">ESP32-POE</option>
<option value="6">ESP32Deux</option>
<option value="4">QuinLED-ESP32</option>
<option value="5">TwilightLord-ESP32</option>
<option value="3">WESP32</option>
<option value="2">ESP32-POE</option>
<option value="6">ESP32Deux</option>
<option value="7">KIT-VE</option>
<option value="4">QuinLED-ESP32</option>
<option value="5">TwilightLord-ESP32</option>
<option value="3">WESP32</option>
<option value="1">WT32-ETH01</option>
</select><br><br></div>
</select><br><br></div>
<hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save & Connect</button>
</form>

View File

@@ -1,83 +1,83 @@
body {
font-family: Verdana, sans-serif;
text-align: center;
background: #222;
color: #fff;
line-height: 200%%; /* %% because of AsyncWebServer */
margin: 0;
font-family: Verdana, sans-serif;
text-align: center;
background: #222;
color: #fff;
line-height: 200%%; /* %% because of AsyncWebServer */
margin: 0;
}
hr {
border-color: #666;
border-color: #666;
}
a {
color: #28f;
text-decoration: none;
color: #28f;
text-decoration: none;
}
button, .btn {
background: #333;
color: #fff;
font-family: Verdana, sans-serif;
border: 0.3ch solid #333;
display: inline-block;
font-size: 20px;
margin: 12px 8px 8px;
padding: 1px 6px;
cursor: pointer;
text-decoration: none;
background: #333;
color: #fff;
font-family: Verdana, sans-serif;
border: 0.3ch solid #333;
display: inline-block;
font-size: 20px;
margin: 12px 8px 8px;
padding: 1px 6px;
cursor: pointer;
text-decoration: none;
}
.lnk {
border: 0;
border: 0;
}
.helpB {
text-align: left;
position: absolute;
width: 60px;
text-align: left;
position: absolute;
width: 60px;
}
input {
background: #333;
color: #fff;
font-family: Verdana, sans-serif;
border: 0.5ch solid #333;
background: #333;
color: #fff;
font-family: Verdana, sans-serif;
border: 0.5ch solid #333;
}
input:disabled {
color: #888;
color: #888;
}
input[type="number"] {
width: 4em;
margin: 2px;
width: 4em;
margin: 2px;
}
input[type="number"].xxl {
width: 100px;
width: 100px;
}
input[type="number"].xl {
width: 85px;
width: 85px;
}
input[type="number"].l {
width: 63px;
width: 63px;
}
input[type="number"].m {
width: 56px;
width: 56px;
}
input[type="number"].s {
width: 49px;
width: 49px;
}
input[type="number"].xs {
width: 42px;
width: 42px;
}
input[type="checkbox"] {
transform: scale(1.5);
transform: scale(1.5);
}
select {
background: #333;
color: #fff;
font-family: Verdana, sans-serif;
border: 0.5ch solid #333;
background: #333;
color: #fff;
font-family: Verdana, sans-serif;
border: 0.5ch solid #333;
}
td {
padding: 2px;
padding: 2px;
}
.d5 {
width: 4.5em !important;
width: 4.5em !important;
}
#toast {
opacity: 0;
@@ -92,7 +92,7 @@ td {
text-align: center;
z-index: 5;
transform: translateX(-50%%); /* %% because of AsyncWebServer */
max-width: 90%%; /* %% because of AsyncWebServer */
max-width: 90%%; /* %% because of AsyncWebServer */
left: 50%%; /* %% because of AsyncWebServer */
}

View File

@@ -1,10 +1,13 @@
#include "wled.h"
/*
* Support for DMX via MAX485.
* Change the output pin in src/dependencies/ESPDMX.cpp if needed.
* Library from:
* Support for DMX Output via MAX485.
* Change the output pin in src/dependencies/ESPDMX.cpp, if needed (ESP8266)
* Change the output pin in src/dependencies/SparkFunDMX.cpp, if needed (ESP32)
* ESP8266 Library from:
* https://github.com/Rickgg/ESP-Dmx
* ESP32 Library from:
* https://github.com/sparkfun/SparkFunDMX
*/
#ifdef WLED_ENABLE_DMX
@@ -14,10 +17,16 @@ void handleDMX()
// don't act, when in DMX Proxy mode
if (e131ProxyUniverse != 0) return;
// TODO: calculate brightness manually if no shutter channel is set
uint8_t brightness = strip.getBrightness();
bool calc_brightness = true;
// check if no shutter channel is set
for (byte i = 0; i < DMXChannels; i++)
{
if (DMXFixtureMap[i] == 5) calc_brightness = false;
}
uint16_t len = strip.getLengthTotal();
for (int i = DMXStartLED; i < len; i++) { // uses the amount of LEDs as fixture count
@@ -35,16 +44,16 @@ void handleDMX()
dmx.write(DMXAddr, 0);
break;
case 1: // Red
dmx.write(DMXAddr, r);
dmx.write(DMXAddr, calc_brightness ? (r * brightness) / 255 : r);
break;
case 2: // Green
dmx.write(DMXAddr, g);
dmx.write(DMXAddr, calc_brightness ? (g * brightness) / 255 : g);
break;
case 3: // Blue
dmx.write(DMXAddr, b);
dmx.write(DMXAddr, calc_brightness ? (b * brightness) / 255 : b);
break;
case 4: // White
dmx.write(DMXAddr, w);
dmx.write(DMXAddr, calc_brightness ? (w * brightness) / 255 : w);
break;
case 5: // Shutter channel. Controls the brightness.
dmx.write(DMXAddr, brightness);
@@ -60,7 +69,11 @@ void handleDMX()
}
void initDMX() {
#ifdef ESP8266
dmx.init(512); // initialize with bus length
#else
dmx.initWrite(512); // initialize with bus length
#endif
}
#else

View File

@@ -105,59 +105,70 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
realtimeIP = clientIP;
byte wChannel = 0;
uint16_t totalLen = strip.getLengthTotal();
uint16_t availDMXLen = dmxChannels - DMXAddress + 1;
uint16_t dataOffset = DMXAddress;
// DMX data in Art-Net packet starts at index 0, for E1.31 at index 1
if (protocol == P_ARTNET && dataOffset > 0) {
dataOffset--;
}
switch (DMXMode) {
case DMX_MODE_DISABLED:
return; // nothing to do
break;
case DMX_MODE_SINGLE_RGB:
case DMX_MODE_SINGLE_RGB: // RGB only
if (uni != e131Universe) return;
if (dmxChannels-DMXAddress+1 < 3) return;
if (availDMXLen < 3) return;
realtimeLock(realtimeTimeoutMs, mde);
if (realtimeOverride) return;
wChannel = (dmxChannels-DMXAddress+1 > 3) ? e131_data[DMXAddress+3] : 0;
wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0;
for (uint16_t i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[DMXAddress+0], e131_data[DMXAddress+1], e131_data[DMXAddress+2], wChannel);
setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel);
break;
case DMX_MODE_SINGLE_DRGB:
case DMX_MODE_SINGLE_DRGB: // Dimmer + RGB
if (uni != e131Universe) return;
if (dmxChannels-DMXAddress+1 < 4) return;
if (availDMXLen < 4) return;
realtimeLock(realtimeTimeoutMs, mde);
if (realtimeOverride) return;
wChannel = (dmxChannels-DMXAddress+1 > 4) ? e131_data[DMXAddress+4] : 0;
if (DMXOldDimmer != e131_data[DMXAddress+0]) {
DMXOldDimmer = e131_data[DMXAddress+0];
bri = e131_data[DMXAddress+0];
strip.setBrightness(bri);
wChannel = (availDMXLen > 4) ? e131_data[dataOffset+4] : 0;
if (DMXOldDimmer != e131_data[dataOffset+0]) {
DMXOldDimmer = e131_data[dataOffset+0];
bri = e131_data[dataOffset+0];
strip.setBrightness(bri, true);
}
for (uint16_t i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[DMXAddress+1], e131_data[DMXAddress+2], e131_data[DMXAddress+3], wChannel);
setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel);
break;
case DMX_MODE_EFFECT:
case DMX_MODE_EFFECT: // Length 1: Apply Preset ID, length 11-13: apply effect config
if (uni != e131Universe) return;
if (dmxChannels-DMXAddress+1 < 11) return;
if (DMXOldDimmer != e131_data[DMXAddress+0]) {
DMXOldDimmer = e131_data[DMXAddress+0];
bri = e131_data[DMXAddress+0];
if (availDMXLen < 11) {
if (availDMXLen > 1) return;
applyPreset(e131_data[dataOffset+0], CALL_MODE_NOTIFICATION);
return;
}
if (e131_data[DMXAddress+1] < MODE_COUNT)
effectCurrent = e131_data[DMXAddress+ 1];
effectSpeed = e131_data[DMXAddress+ 2]; // flickers
effectIntensity = e131_data[DMXAddress+ 3];
effectPalette = e131_data[DMXAddress+ 4];
col[0] = e131_data[DMXAddress+ 5];
col[1] = e131_data[DMXAddress+ 6];
col[2] = e131_data[DMXAddress+ 7];
colSec[0] = e131_data[DMXAddress+ 8];
colSec[1] = e131_data[DMXAddress+ 9];
colSec[2] = e131_data[DMXAddress+10];
if (dmxChannels-DMXAddress+1 > 11)
if (DMXOldDimmer != e131_data[dataOffset+0]) {
DMXOldDimmer = e131_data[dataOffset+0];
bri = e131_data[dataOffset+0];
}
if (e131_data[dataOffset+1] < MODE_COUNT)
effectCurrent = e131_data[dataOffset+ 1];
effectSpeed = e131_data[dataOffset+ 2]; // flickers
effectIntensity = e131_data[dataOffset+ 3];
effectPalette = e131_data[dataOffset+ 4];
col[0] = e131_data[dataOffset+ 5];
col[1] = e131_data[dataOffset+ 6];
col[2] = e131_data[dataOffset+ 7];
colSec[0] = e131_data[dataOffset+ 8];
colSec[1] = e131_data[dataOffset+ 9];
colSec[2] = e131_data[dataOffset+10];
if (availDMXLen > 11)
{
col[3] = e131_data[DMXAddress+11]; //white
colSec[3] = e131_data[DMXAddress+12];
col[3] = e131_data[dataOffset+11]; //white
colSec[3] = e131_data[dataOffset+12];
}
transitionDelayTemp = 0; // act fast
colorUpdated(CALL_MODE_NOTIFICATION); // don't send UDP
@@ -173,22 +184,26 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
const uint16_t dmxChannelsPerLed = is4Chan ? 4 : 3;
const uint16_t ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE;
if (realtimeOverride) return;
uint16_t previousLeds, dmxOffset;
uint16_t previousLeds, dmxOffset, ledsTotal;
if (previousUniverses == 0) {
if (dmxChannels-DMXAddress < 1) return;
dmxOffset = DMXAddress;
if (availDMXLen < 1) return;
dmxOffset = dataOffset;
previousLeds = 0;
// First DMX address is dimmer in DMX_MODE_MULTIPLE_DRGB mode.
if (DMXMode == DMX_MODE_MULTIPLE_DRGB) {
strip.setBrightness(e131_data[dmxOffset++]);
strip.setBrightness(e131_data[dmxOffset++], true);
ledsTotal = (availDMXLen - 1) / dmxChannelsPerLed;
} else {
ledsTotal = availDMXLen / dmxChannelsPerLed;
}
} else {
// All subsequent universes start at the first channel.
dmxOffset = (protocol == P_ARTNET) ? 0 : 1;
uint16_t ledsInFirstUniverse = (MAX_CHANNELS_PER_UNIVERSE - DMXAddress) / dmxChannelsPerLed;
uint16_t dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0;
uint16_t ledsInFirstUniverse = ((MAX_CHANNELS_PER_UNIVERSE - DMXAddress + 1) - dimmerOffset) / dmxChannelsPerLed;
previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse;
ledsTotal = previousLeds + (dmxChannels / dmxChannelsPerLed);
}
uint16_t ledsTotal = previousLeds + (dmxChannels - dmxOffset +1) / dmxChannelsPerLed;
if (!is4Chan) {
for (uint16_t i = previousLeds; i < ledsTotal; i++) {
setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0);

View File

@@ -58,10 +58,7 @@ bool getJsonValue(const JsonVariant& element, DestType& destination, const Defau
//colors.cpp
void colorFromUint32(uint32_t in, bool secondary = false);
void colorFromUint24(uint32_t in, bool secondary = false);
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
void relativeChangeWhite(int8_t amount, byte lowerBoundary = 0);
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb
void colorKtoRGB(uint16_t kelvin, byte* rgb);
void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb
@@ -108,11 +105,9 @@ void sendImprovInfoResponse();
void sendImprovRPCResponse(uint8_t commandId);
//ir.cpp
bool decodeIRCustom(uint32_t code);
//bool decodeIRCustom(uint32_t code);
void applyRepeatActions();
void relativeChange(byte* property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF);
void changeEffectSpeed(int8_t amount);
void changeEffectIntensity(int8_t amount);
byte relativeChange(byte property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF);
void decodeIR(uint32_t code);
void decodeIR24(uint32_t code);
void decodeIR24OLD(uint32_t code);
@@ -144,6 +139,8 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0);
#endif
//led.cpp
void setValuesFromSegment(uint8_t s);
void setValuesFromMainSeg();
void setValuesFromFirstSelectedSeg();
void resetTimebase();
void toggleOnOff();
@@ -180,17 +177,10 @@ void calculateSunriseAndSunset();
void setTimeFromAPI(uint32_t timein);
//overlay.cpp
void initCronixie();
void handleOverlays();
void handleOverlayDraw();
void _overlayAnalogCountdown();
void _overlayAnalogClock();
byte getSameCodeLength(char code, int index, char const cronixieDisplay[]);
void setCronixie();
void _overlayCronixie();
void _drawOverlayCronixie();
//playlist.cpp
void shufflePlaylist();
void unloadPlaylist();
@@ -200,8 +190,8 @@ void handlePlaylist();
//presets.cpp
bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE);
inline bool applyTemporaryPreset() {return applyPreset(255);};
void savePreset(byte index, bool persist = true, const char* pname = nullptr, JsonObject saveobj = JsonObject());
inline void saveTemporaryPreset() {savePreset(255, false);};
void savePreset(byte index, const char* pname = nullptr, JsonObject saveobj = JsonObject());
inline void saveTemporaryPreset() {savePreset(255);};
void deletePreset(byte index);
//set.cpp
@@ -216,6 +206,7 @@ bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte
void notify(byte callMode, bool followUp=false);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false);
void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC);
void exitRealtime();
void handleNotifications();
void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w);
void refreshNodeList();

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-b7<br>Download the latest binary: <a
Installed version: 0.13.3<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
@@ -85,7 +85,7 @@ charset="utf-8"><meta name="theme-color" content="#222222"><title>
WLED Live Preview</title><style>
body{margin:0}#canv{background:#000;filter:brightness(175%);width:100%;height:100%;position:absolute}
</style></head><body><div id="canv"><script>
function updatePreview(e){var n="linear-gradient(90deg,",t=e.length;for(i=2;i<t;i+=3)n+=`rgb(${e[i]},${e[i+1]},${e[i+2]})`,i<t-3&&(n+=",");n+=")",document.getElementById("canv").style.background=n}function getLiveJson(e){try{if("[object ArrayBuffer]"===toString.call(e.data)){let e=new Uint8Array(event.data);if(76!=e[0])return;updatePreview(e)}}catch(e){console.error("Peek WS error:",e)}}var ws=top.window.ws;ws&&ws.readyState===WebSocket.OPEN?(console.info("Peek uses top WS"),ws.send("{'lv':true}")):(console.info("Peek WS opening"),(ws=new WebSocket("ws://"+document.location.host+"/ws")).onopen=function(){console.info("Peek WS open"),ws.send("{'lv':true}")}),ws.binaryType="arraybuffer",ws.addEventListener("message",getLiveJson)
function updatePreview(e){var n="linear-gradient(90deg,",t=e.length;for(i=2;i<t;i+=3)n+=`rgb(${e[i]},${e[i+1]},${e[i+2]})`,i<t-3&&(n+=",");n+=")",document.getElementById("canv").style.background=n}function getLiveJson(e){try{if("[object ArrayBuffer]"===toString.call(e.data)){let e=new Uint8Array(event.data);if(76!=e[0])return;updatePreview(e)}}catch(e){console.error("Peek WS error:",e)}}var ws;try{ws=top.window.ws}catch(e){}ws&&ws.readyState===WebSocket.OPEN?(console.info("Peek uses top WS"),ws.send("{'lv':true}")):(console.info("Peek WS opening"),(ws=new WebSocket(("https:"==window.location.protocol?"wss":"ws")+"://"+document.location.host+"/ws")).onopen=function(){console.info("Peek WS open"),ws.send("{'lv':true}")}),ws.binaryType="arraybuffer",ws.addEventListener("message",getLiveJson)
</script></body></html>)=====";

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-b7/%i"),VERSION);
uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.13.3/%i"),VERSION);
out[16] = vlen; lengthSum += vlen;
uint8_t hlen = 7;
#ifdef ESP8266

View File

@@ -73,56 +73,94 @@ void presetFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID)
{
byte prevError = errorFlag;
if (!applyPreset(presetID, CALL_MODE_BUTTON_PRESET)) {
effectCurrent = effectID;
effectCurrent = effectID;
effectPalette = paletteID;
errorFlag = prevError; //clear error 12 from non-existent preset
}
}
//Add what your custom IR codes should trigger here. Guide: https://github.com/Aircoookie/WLED/wiki/Infrared-Control
//IR codes themselves can be defined directly after "case" or in "ir_codes.h"
bool decodeIRCustom(uint32_t code)
byte relativeChange(byte property, int8_t amount, byte lowerBoundary, byte higherBoundary)
{
switch (code)
{
//just examples, feel free to modify or remove
case IRCUSTOM_ONOFF : toggleOnOff(); break;
case IRCUSTOM_MACRO1 : applyPreset(1, CALL_MODE_BUTTON_PRESET); break;
default: return false;
}
if (code != IRCUSTOM_MACRO1) colorUpdated(CALL_MODE_BUTTON); //don't update color again if we apply macro, it already does it
return true;
int16_t new_val = (int16_t) property + amount;
if (lowerBoundary >= higherBoundary) return property;
if (new_val > higherBoundary) new_val = higherBoundary;
if (new_val < lowerBoundary) new_val = lowerBoundary;
return (byte)constrain(new_val, 0, 255);
}
void relativeChange(byte* property, int8_t amount, byte lowerBoundary, byte higherBoundary)
void changeEffect(uint8_t fx)
{
int16_t new_val = (int16_t) *property + amount;
if (new_val > higherBoundary) new_val = higherBoundary;
else if (new_val < lowerBoundary) new_val = lowerBoundary;
*property = (byte)constrain(new_val,0.1,255.1);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
strip.setMode(i, fx);
}
setValuesFromFirstSelectedSeg();
} else {
strip.setMode(strip.getMainSegmentId(), fx);
setValuesFromMainSeg();
}
stateChanged = true;
}
void changePalette(uint8_t pal)
{
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.palette = pal;
}
setValuesFromFirstSelectedSeg();
} else {
strip.getMainSegment().palette = pal;
setValuesFromMainSeg();
}
stateChanged = true;
}
void changeEffectSpeed(int8_t amount)
{
if (effectCurrent != 0) {
int16_t new_val = (int16_t) effectSpeed + amount;
effectSpeed = (byte)constrain(new_val,0.1,255.1);
} else { // if Effect == "solid Color", change the hue of the primary color
effectSpeed = (byte)constrain(new_val,0,255);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.speed = effectSpeed;
}
setValuesFromFirstSelectedSeg();
} else {
strip.getMainSegment().speed = effectSpeed;
setValuesFromMainSeg();
}
} else { // if Effect == "solid Color", change the hue of the primary color
WS2812FX::Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col;
fastled_col.red = col[0];
fastled_col.green = col[1];
fastled_col.blue = col[2];
fastled_col.red = R(sseg.colors[0]);
fastled_col.green = G(sseg.colors[0]);
fastled_col.blue = B(sseg.colors[0]);
CHSV prim_hsv = rgb2hsv_approximate(fastled_col);
int16_t new_val = (int16_t) prim_hsv.h + amount;
int16_t new_val = (int16_t)prim_hsv.h + amount;
if (new_val > 255) new_val -= 255; // roll-over if bigger than 255
if (new_val < 0) new_val += 255; // roll-over if smaller than 0
prim_hsv.h = (byte)new_val;
hsv2rgb_rainbow(prim_hsv, fastled_col);
col[0] = fastled_col.red;
col[1] = fastled_col.green;
col[2] = fastled_col.blue;
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
}
setValuesFromFirstSelectedSeg();
} else {
strip.getMainSegment().colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
setValuesFromMainSeg();
}
}
stateChanged = true;
if(amount > 0) lastRepeatableAction = ACTION_SPEED_UP;
if(amount < 0) lastRepeatableAction = ACTION_SPEED_DOWN;
@@ -133,65 +171,132 @@ void changeEffectIntensity(int8_t amount)
{
if (effectCurrent != 0) {
int16_t new_val = (int16_t) effectIntensity + amount;
effectIntensity = (byte)constrain(new_val,0.1,255.1);
} else { // if Effect == "solid Color", change the saturation of the primary color
effectIntensity = (byte)constrain(new_val,0,255);
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.intensity = effectIntensity;
}
setValuesFromFirstSelectedSeg();
} else {
strip.getMainSegment().speed = effectIntensity;
setValuesFromMainSeg();
}
} else { // if Effect == "solid Color", change the saturation of the primary color
WS2812FX::Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
CRGB fastled_col;
fastled_col.red = col[0];
fastled_col.green = col[1];
fastled_col.blue = col[2];
fastled_col.red = R(sseg.colors[0]);
fastled_col.green = G(sseg.colors[0]);
fastled_col.blue = B(sseg.colors[0]);
CHSV prim_hsv = rgb2hsv_approximate(fastled_col);
int16_t new_val = (int16_t) prim_hsv.s + amount;
prim_hsv.s = (byte)constrain(new_val,0.1,255.1); // constrain to 0-255
prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255
hsv2rgb_rainbow(prim_hsv, fastled_col);
col[0] = fastled_col.red;
col[1] = fastled_col.green;
col[2] = fastled_col.blue;
if (irApplyToAllSelected) {
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
}
setValuesFromFirstSelectedSeg();
} else {
strip.getMainSegment().colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
setValuesFromMainSeg();
}
}
stateChanged = true;
if(amount > 0) lastRepeatableAction = ACTION_INTENSITY_UP;
if(amount < 0) lastRepeatableAction = ACTION_INTENSITY_DOWN;
lastRepeatableValue = amount;
}
void changeColor(uint32_t c, int16_t cct=-1)
{
if (irApplyToAllSelected) {
// main segment may not be selected!
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
if (!seg.isActive() || !seg.isSelected()) continue;
byte capabilities = seg.getLightCapabilities();
uint32_t mask = 0;
bool isRGB = GET_BIT(capabilities, 0); // when RGBW_MODE_AUTO_ACCURATE this is always true
bool hasW = GET_BIT(capabilities, 1);
bool isCCT = GET_BIT(capabilities, 2);
if (isRGB) mask |= 0x00FFFFFF; // RGB
if (hasW) mask |= 0xFF000000; // white
if (hasW && (strip.autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) && (c & 0xFF000000)) { // white channel & white specified
seg.setColor(0, c | 0xFFFFFF, i); // for accurate mode we fake white
} else if (c & mask) seg.setColor(0, c & mask, i); // only apply if not black
if (isCCT && cct >= 0) seg.setCCT(cct, i);
}
setValuesFromFirstSelectedSeg();
} else {
byte i = strip.getMainSegmentId();
WS2812FX::Segment& seg = strip.getSegment(i);
byte capabilities = seg.getLightCapabilities();
uint32_t mask = 0;
bool isRGB = GET_BIT(capabilities, 0);
bool hasW = GET_BIT(capabilities, 1);
bool isCCT = GET_BIT(capabilities, 2);
if (isRGB) mask |= 0x00FFFFFF; // RGB
if (hasW) mask |= 0xFF000000; // white
if (hasW && (strip.autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) && (c & 0xFF000000)) { // white channel & white specified
seg.setColor(0, c | 0xFFFFFF, i); // for accurate mode we fake white
} else if (c & mask) seg.setColor(0, c & mask, i); // only apply if not black
if (isCCT && cct >= 0) seg.setCCT(cct, i);
setValuesFromMainSeg();
}
stateChanged = true;
}
void changeWhite(int8_t amount, int16_t cct=-1)
{
WS2812FX::Segment& seg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
byte r = R(seg.colors[0]);
byte g = G(seg.colors[0]);
byte b = B(seg.colors[0]);
byte w = relativeChange(W(seg.colors[0]), amount, 5);
changeColor(RGBW32(r, g, b, w), cct);
}
void decodeIR(uint32_t code)
{
if (code == 0xFFFFFFFF) //repeated code, continue brightness up/down
{
if (code == 0xFFFFFFFF) {
//repeated code, continue brightness up/down
irTimesRepeated++;
applyRepeatActions();
return;
}
lastValidCode = 0; irTimesRepeated = 0;
lastRepeatableAction = ACTION_NONE;
if (decodeIRCustom(code)) return;
if (irEnabled == 8) { // any remote configurable with ir.json file
decodeIRJson(code);
colorUpdated(CALL_MODE_BUTTON);
stateUpdated(CALL_MODE_BUTTON);
return;
}
if (code > 0xFFFFFF) return; //invalid code
switch (irEnabled) {
case 1:
if (code > 0xF80000) {
decodeIR24OLD(code); // white 24-key remote (old) - it sends 0xFF0000 values
} else {
decodeIR24(code); // 24-key remote - 0xF70000 to 0xF80000
}
if (code > 0xF80000) decodeIR24OLD(code); // white 24-key remote (old) - it sends 0xFF0000 values
else decodeIR24(code); // 24-key remote - 0xF70000 to 0xF80000
break;
case 2: decodeIR24CT(code); break; // white 24-key remote with CW, WW, CT+ and CT- keys
case 3: decodeIR40(code); break; // blue 40-key remote with 25%, 50%, 75% and 100% keys
case 4: decodeIR44(code); break; // white 44-key remote with color-up/down keys and DIY1 to 6 keys
case 5: decodeIR21(code); break; // white 21-key remote
case 6: decodeIR6(code); break; // black 6-key learning remote defaults: "CH" controls brightness,
// "VOL +" controls effect, "VOL -" controls colour/palette, "MUTE"
// sets bright plain white
case 2: decodeIR24CT(code); break; // white 24-key remote with CW, WW, CT+ and CT- keys
case 3: decodeIR40(code); break; // blue 40-key remote with 25%, 50%, 75% and 100% keys
case 4: decodeIR44(code); break; // white 44-key remote with color-up/down keys and DIY1 to 6 keys
case 5: decodeIR21(code); break; // white 21-key remote
case 6: decodeIR6(code); break; // black 6-key learning remote defaults: "CH" controls brightness,
// "VOL +" controls effect, "VOL -" controls colour/palette, "MUTE"
// sets bright plain white
case 7: decodeIR9(code); break;
//case 8: return; // ir.json file, handled above switch statement
default: return;
}
if (nightlightActive && bri == 0) nightlightActive = false;
colorUpdated(CALL_MODE_BUTTON); //for notifier, IR is considered a button input
stateUpdated(CALL_MODE_BUTTON); //for notifier, IR is considered a button input
}
void applyRepeatActions()
@@ -200,50 +305,50 @@ void applyRepeatActions()
decodeIRJson(lastValidCode);
return;
} else switch (lastRepeatableAction) {
case ACTION_BRIGHT_UP : incBrightness(); colorUpdated(CALL_MODE_BUTTON); return;
case ACTION_BRIGHT_DOWN : decBrightness(); colorUpdated(CALL_MODE_BUTTON); return;
case ACTION_SPEED_UP : changeEffectSpeed(lastRepeatableValue); colorUpdated(CALL_MODE_BUTTON); return;
case ACTION_SPEED_DOWN : changeEffectSpeed(lastRepeatableValue); colorUpdated(CALL_MODE_BUTTON); return;
case ACTION_INTENSITY_UP : changeEffectIntensity(lastRepeatableValue); colorUpdated(CALL_MODE_BUTTON); return;
case ACTION_INTENSITY_DOWN : changeEffectIntensity(lastRepeatableValue); colorUpdated(CALL_MODE_BUTTON); return;
case ACTION_BRIGHT_UP : incBrightness(); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_BRIGHT_DOWN : decBrightness(); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_SPEED_UP : changeEffectSpeed(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_SPEED_DOWN : changeEffectSpeed(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_INTENSITY_UP : changeEffectIntensity(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
case ACTION_INTENSITY_DOWN : changeEffectIntensity(lastRepeatableValue); stateUpdated(CALL_MODE_BUTTON); return;
default: break;
}
if (lastValidCode == IR40_WPLUS) {
relativeChangeWhite(10);
colorUpdated(CALL_MODE_BUTTON);
if (lastValidCode == IR40_WPLUS) {
changeWhite(10);
stateUpdated(CALL_MODE_BUTTON);
} else if (lastValidCode == IR40_WMINUS) {
relativeChangeWhite(-10, 5);
colorUpdated(CALL_MODE_BUTTON);
changeWhite(-10);
stateUpdated(CALL_MODE_BUTTON);
} else if ((lastValidCode == IR24_ON || lastValidCode == IR40_ON) && irTimesRepeated > 7 ) {
nightlightActive = true;
nightlightStartTime = millis();
colorUpdated(CALL_MODE_BUTTON);
stateUpdated(CALL_MODE_BUTTON);
}
}
void decodeIR24(uint32_t code)
{
switch (code) {
case IR24_BRIGHTER : incBrightness(); break;
case IR24_DARKER : decBrightness(); break;
case IR24_OFF : if (bri > 0) briLast = bri; bri = 0; break;
case IR24_ON : bri = briLast; break;
case IR24_RED : colorFromUint32(COLOR_RED); break;
case IR24_REDDISH : colorFromUint32(COLOR_REDDISH); break;
case IR24_ORANGE : colorFromUint32(COLOR_ORANGE); break;
case IR24_YELLOWISH : colorFromUint32(COLOR_YELLOWISH); break;
case IR24_YELLOW : colorFromUint32(COLOR_YELLOW); break;
case IR24_GREEN : colorFromUint32(COLOR_GREEN); break;
case IR24_GREENISH : colorFromUint32(COLOR_GREENISH); break;
case IR24_TURQUOISE : colorFromUint32(COLOR_TURQUOISE); break;
case IR24_CYAN : colorFromUint32(COLOR_CYAN); break;
case IR24_AQUA : colorFromUint32(COLOR_AQUA); break;
case IR24_BLUE : colorFromUint32(COLOR_BLUE); break;
case IR24_DEEPBLUE : colorFromUint32(COLOR_DEEPBLUE); break;
case IR24_PURPLE : colorFromUint32(COLOR_PURPLE); break;
case IR24_MAGENTA : colorFromUint32(COLOR_MAGENTA); break;
case IR24_PINK : colorFromUint32(COLOR_PINK); break;
case IR24_WHITE : colorFromUint32(COLOR_WHITE); effectCurrent = 0; break;
case IR24_BRIGHTER : incBrightness(); break;
case IR24_DARKER : decBrightness(); break;
case IR24_OFF : if (bri > 0) briLast = bri; bri = 0; break;
case IR24_ON : bri = briLast; break;
case IR24_RED : changeColor(COLOR_RED); break;
case IR24_REDDISH : changeColor(COLOR_REDDISH); break;
case IR24_ORANGE : changeColor(COLOR_ORANGE); break;
case IR24_YELLOWISH : changeColor(COLOR_YELLOWISH); break;
case IR24_YELLOW : changeColor(COLOR_YELLOW); break;
case IR24_GREEN : changeColor(COLOR_GREEN); break;
case IR24_GREENISH : changeColor(COLOR_GREENISH); break;
case IR24_TURQUOISE : changeColor(COLOR_TURQUOISE); break;
case IR24_CYAN : changeColor(COLOR_CYAN); break;
case IR24_AQUA : changeColor(COLOR_AQUA); break;
case IR24_BLUE : changeColor(COLOR_BLUE); break;
case IR24_DEEPBLUE : changeColor(COLOR_DEEPBLUE); break;
case IR24_PURPLE : changeColor(COLOR_PURPLE); break;
case IR24_MAGENTA : changeColor(COLOR_MAGENTA); break;
case IR24_PINK : changeColor(COLOR_PINK); break;
case IR24_WHITE : changeColor(COLOR_WHITE); changeEffect(FX_MODE_STATIC); break;
case IR24_FLASH : presetFallback(1, FX_MODE_COLORTWINKLE, effectPalette); break;
case IR24_STROBE : presetFallback(2, FX_MODE_RAINBOW_CYCLE, effectPalette); break;
case IR24_FADE : presetFallback(3, FX_MODE_BREATH, effectPalette); break;
@@ -256,30 +361,30 @@ void decodeIR24(uint32_t code)
void decodeIR24OLD(uint32_t code)
{
switch (code) {
case IR24_OLD_BRIGHTER : incBrightness(); break;
case IR24_OLD_DARKER : decBrightness(); break;
case IR24_OLD_OFF : if (bri > 0) briLast = bri; bri = 0; break;
case IR24_OLD_ON : bri = briLast; break;
case IR24_OLD_RED : colorFromUint32(COLOR_RED); break;
case IR24_OLD_REDDISH : colorFromUint32(COLOR_REDDISH); break;
case IR24_OLD_ORANGE : colorFromUint32(COLOR_ORANGE); break;
case IR24_OLD_YELLOWISH : colorFromUint32(COLOR_YELLOWISH); break;
case IR24_OLD_YELLOW : colorFromUint32(COLOR_YELLOW); break;
case IR24_OLD_GREEN : colorFromUint32(COLOR_GREEN); break;
case IR24_OLD_GREENISH : colorFromUint32(COLOR_GREENISH); break;
case IR24_OLD_TURQUOISE : colorFromUint32(COLOR_TURQUOISE); break;
case IR24_OLD_CYAN : colorFromUint32(COLOR_CYAN); break;
case IR24_OLD_AQUA : colorFromUint32(COLOR_AQUA); break;
case IR24_OLD_BLUE : colorFromUint32(COLOR_BLUE); break;
case IR24_OLD_DEEPBLUE : colorFromUint32(COLOR_DEEPBLUE); break;
case IR24_OLD_PURPLE : colorFromUint32(COLOR_PURPLE); break;
case IR24_OLD_MAGENTA : colorFromUint32(COLOR_MAGENTA); break;
case IR24_OLD_PINK : colorFromUint32(COLOR_PINK); break;
case IR24_OLD_WHITE : colorFromUint32(COLOR_WHITE); effectCurrent = 0; break;
case IR24_OLD_FLASH : presetFallback(1, FX_MODE_COLORTWINKLE, 0); break;
case IR24_OLD_STROBE : presetFallback(2, FX_MODE_RAINBOW_CYCLE, 0); break;
case IR24_OLD_FADE : presetFallback(3, FX_MODE_BREATH, 0); break;
case IR24_OLD_SMOOTH : presetFallback(4, FX_MODE_RAINBOW, 0); break;
case IR24_OLD_BRIGHTER : incBrightness(); break;
case IR24_OLD_DARKER : decBrightness(); break;
case IR24_OLD_OFF : if (bri > 0) briLast = bri; bri = 0; break;
case IR24_OLD_ON : bri = briLast; break;
case IR24_OLD_RED : changeColor(COLOR_RED); break;
case IR24_OLD_REDDISH : changeColor(COLOR_REDDISH); break;
case IR24_OLD_ORANGE : changeColor(COLOR_ORANGE); break;
case IR24_OLD_YELLOWISH : changeColor(COLOR_YELLOWISH); break;
case IR24_OLD_YELLOW : changeColor(COLOR_YELLOW); break;
case IR24_OLD_GREEN : changeColor(COLOR_GREEN); break;
case IR24_OLD_GREENISH : changeColor(COLOR_GREENISH); break;
case IR24_OLD_TURQUOISE : changeColor(COLOR_TURQUOISE); break;
case IR24_OLD_CYAN : changeColor(COLOR_CYAN); break;
case IR24_OLD_AQUA : changeColor(COLOR_AQUA); break;
case IR24_OLD_BLUE : changeColor(COLOR_BLUE); break;
case IR24_OLD_DEEPBLUE : changeColor(COLOR_DEEPBLUE); break;
case IR24_OLD_PURPLE : changeColor(COLOR_PURPLE); break;
case IR24_OLD_MAGENTA : changeColor(COLOR_MAGENTA); break;
case IR24_OLD_PINK : changeColor(COLOR_PINK); break;
case IR24_OLD_WHITE : changeColor(COLOR_WHITE); changeEffect(FX_MODE_STATIC); break;
case IR24_OLD_FLASH : presetFallback(1, FX_MODE_COLORTWINKLE, 0); break;
case IR24_OLD_STROBE : presetFallback(2, FX_MODE_RAINBOW_CYCLE, 0); break;
case IR24_OLD_FADE : presetFallback(3, FX_MODE_BREATH, 0); break;
case IR24_OLD_SMOOTH : presetFallback(4, FX_MODE_RAINBOW, 0); break;
default: return;
}
lastValidCode = code;
@@ -292,28 +397,26 @@ void decodeIR24CT(uint32_t code)
case IR24_CT_DARKER : decBrightness(); break;
case IR24_CT_OFF : if (bri > 0) briLast = bri; bri = 0; break;
case IR24_CT_ON : bri = briLast; break;
case IR24_CT_RED : colorFromUint32(COLOR_RED); break;
case IR24_CT_REDDISH : colorFromUint32(COLOR_REDDISH); break;
case IR24_CT_ORANGE : colorFromUint32(COLOR_ORANGE); break;
case IR24_CT_YELLOWISH : colorFromUint32(COLOR_YELLOWISH); break;
case IR24_CT_YELLOW : colorFromUint32(COLOR_YELLOW); break;
case IR24_CT_GREEN : colorFromUint32(COLOR_GREEN); break;
case IR24_CT_GREENISH : colorFromUint32(COLOR_GREENISH); break;
case IR24_CT_TURQUOISE : colorFromUint32(COLOR_TURQUOISE); break;
case IR24_CT_CYAN : colorFromUint32(COLOR_CYAN); break;
case IR24_CT_AQUA : colorFromUint32(COLOR_AQUA); break;
case IR24_CT_BLUE : colorFromUint32(COLOR_BLUE); break;
case IR24_CT_DEEPBLUE : colorFromUint32(COLOR_DEEPBLUE); break;
case IR24_CT_PURPLE : colorFromUint32(COLOR_PURPLE); break;
case IR24_CT_MAGENTA : colorFromUint32(COLOR_MAGENTA); break;
case IR24_CT_PINK : colorFromUint32(COLOR_PINK); break;
case IR24_CT_COLDWHITE : colorFromUint32(COLOR2_COLDWHITE); effectCurrent = 0; break;
case IR24_CT_WARMWHITE : colorFromUint32(COLOR2_WARMWHITE); effectCurrent = 0; break;
case IR24_CT_CTPLUS : colorFromUint32(COLOR2_COLDWHITE2); effectCurrent = 0; break;
case IR24_CT_CTMINUS : colorFromUint32(COLOR2_WARMWHITE2); effectCurrent = 0; break;
case IR24_CT_MEMORY : {
if (col[3] > 0) col[3] = 0;
else colorFromUint32(COLOR2_NEUTRALWHITE); effectCurrent = 0; } break;
case IR24_CT_RED : changeColor(COLOR_RED); break;
case IR24_CT_REDDISH : changeColor(COLOR_REDDISH); break;
case IR24_CT_ORANGE : changeColor(COLOR_ORANGE); break;
case IR24_CT_YELLOWISH : changeColor(COLOR_YELLOWISH); break;
case IR24_CT_YELLOW : changeColor(COLOR_YELLOW); break;
case IR24_CT_GREEN : changeColor(COLOR_GREEN); break;
case IR24_CT_GREENISH : changeColor(COLOR_GREENISH); break;
case IR24_CT_TURQUOISE : changeColor(COLOR_TURQUOISE); break;
case IR24_CT_CYAN : changeColor(COLOR_CYAN); break;
case IR24_CT_AQUA : changeColor(COLOR_AQUA); break;
case IR24_CT_BLUE : changeColor(COLOR_BLUE); break;
case IR24_CT_DEEPBLUE : changeColor(COLOR_DEEPBLUE); break;
case IR24_CT_PURPLE : changeColor(COLOR_PURPLE); break;
case IR24_CT_MAGENTA : changeColor(COLOR_MAGENTA); break;
case IR24_CT_PINK : changeColor(COLOR_PINK); break;
case IR24_CT_COLDWHITE : changeColor(COLOR_COLDWHITE2, 255); changeEffect(FX_MODE_STATIC); break;
case IR24_CT_WARMWHITE : changeColor(COLOR_WARMWHITE2, 0); changeEffect(FX_MODE_STATIC); break;
case IR24_CT_CTPLUS : changeColor(COLOR_COLDWHITE, strip.getSegment(strip.getMainSegmentId()).cct+1); changeEffect(FX_MODE_STATIC); break;
case IR24_CT_CTMINUS : changeColor(COLOR_WARMWHITE, strip.getSegment(strip.getMainSegmentId()).cct-1); changeEffect(FX_MODE_STATIC); break;
case IR24_CT_MEMORY : changeColor(COLOR_NEUTRALWHITE, 127); changeEffect(FX_MODE_STATIC); break;
default: return;
}
lastValidCode = code;
@@ -321,57 +424,53 @@ void decodeIR24CT(uint32_t code)
void decodeIR40(uint32_t code)
{
WS2812FX::Segment& seg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
byte r = R(seg.colors[0]);
byte g = G(seg.colors[0]);
byte b = B(seg.colors[0]);
byte w = W(seg.colors[0]);
switch (code) {
case IR40_BPLUS : incBrightness(); break;
case IR40_BMINUS : decBrightness(); break;
case IR40_OFF : if (bri > 0) briLast = bri; bri = 0; break;
case IR40_ON : bri = briLast; break;
case IR40_RED : colorFromUint24(COLOR_RED); break;
case IR40_REDDISH : colorFromUint24(COLOR_REDDISH); break;
case IR40_ORANGE : colorFromUint24(COLOR_ORANGE); break;
case IR40_YELLOWISH : colorFromUint24(COLOR_YELLOWISH); break;
case IR40_YELLOW : colorFromUint24(COLOR_YELLOW); break;
case IR40_GREEN : colorFromUint24(COLOR_GREEN); break;
case IR40_GREENISH : colorFromUint24(COLOR_GREENISH); break;
case IR40_TURQUOISE : colorFromUint24(COLOR_TURQUOISE); break;
case IR40_CYAN : colorFromUint24(COLOR_CYAN); break;
case IR40_AQUA : colorFromUint24(COLOR_AQUA); break;
case IR40_BLUE : colorFromUint24(COLOR_BLUE); break;
case IR40_DEEPBLUE : colorFromUint24(COLOR_DEEPBLUE); break;
case IR40_PURPLE : colorFromUint24(COLOR_PURPLE); break;
case IR40_MAGENTA : colorFromUint24(COLOR_MAGENTA); break;
case IR40_PINK : colorFromUint24(COLOR_PINK); break;
case IR40_WARMWHITE2 : {
if (strip.hasWhiteChannel()) {colorFromUint32(COLOR2_WARMWHITE2); effectCurrent = 0; }
else colorFromUint24(COLOR_WARMWHITE2); } break;
case IR40_WARMWHITE : {
if (strip.hasWhiteChannel()) {colorFromUint32(COLOR2_WARMWHITE); effectCurrent = 0; }
else colorFromUint24(COLOR_WARMWHITE); } break;
case IR40_WHITE : {
if (strip.hasWhiteChannel()) {colorFromUint32(COLOR2_NEUTRALWHITE); effectCurrent = 0; }
else colorFromUint24(COLOR_NEUTRALWHITE); } break;
case IR40_COLDWHITE : {
if (strip.hasWhiteChannel()) {colorFromUint32(COLOR2_COLDWHITE); effectCurrent = 0; }
else colorFromUint24(COLOR_COLDWHITE); } break;
case IR40_COLDWHITE2 : {
if (strip.hasWhiteChannel()) {colorFromUint32(COLOR2_COLDWHITE2); effectCurrent = 0; }
else colorFromUint24(COLOR_COLDWHITE2); } break;
case IR40_WPLUS : relativeChangeWhite(10); break;
case IR40_WMINUS : relativeChangeWhite(-10, 5); break;
case IR40_WOFF : whiteLast = col[3]; col[3] = 0; break;
case IR40_WON : col[3] = whiteLast; break;
case IR40_W25 : bri = 63; break;
case IR40_W50 : bri = 127; break;
case IR40_W75 : bri = 191; break;
case IR40_W100 : bri = 255; break;
case IR40_QUICK : changeEffectSpeed( 16); break;
case IR40_SLOW : changeEffectSpeed(-16); break;
case IR40_JUMP7 : changeEffectIntensity( 16); break;
case IR40_AUTO : changeEffectIntensity(-16); break;
case IR40_BPLUS : incBrightness(); break;
case IR40_BMINUS : decBrightness(); break;
case IR40_OFF : if (bri > 0) briLast = bri; bri = 0; break;
case IR40_ON : bri = briLast; break;
case IR40_RED : changeColor(COLOR_RED); break;
case IR40_REDDISH : changeColor(COLOR_REDDISH); break;
case IR40_ORANGE : changeColor(COLOR_ORANGE); break;
case IR40_YELLOWISH : changeColor(COLOR_YELLOWISH); break;
case IR40_YELLOW : changeColor(COLOR_YELLOW); break;
case IR40_GREEN : changeColor(COLOR_GREEN); break;
case IR40_GREENISH : changeColor(COLOR_GREENISH); break;
case IR40_TURQUOISE : changeColor(COLOR_TURQUOISE); break;
case IR40_CYAN : changeColor(COLOR_CYAN); break;
case IR40_AQUA : changeColor(COLOR_AQUA); break;
case IR40_BLUE : changeColor(COLOR_BLUE); break;
case IR40_DEEPBLUE : changeColor(COLOR_DEEPBLUE); break;
case IR40_PURPLE : changeColor(COLOR_PURPLE); break;
case IR40_MAGENTA : changeColor(COLOR_MAGENTA); break;
case IR40_PINK : changeColor(COLOR_PINK); break;
case IR40_WARMWHITE2 : changeColor(COLOR_WARMWHITE2, 0); changeEffect(FX_MODE_STATIC); break;
case IR40_WARMWHITE : changeColor(COLOR_WARMWHITE, 63); changeEffect(FX_MODE_STATIC); break;
case IR40_WHITE : changeColor(COLOR_NEUTRALWHITE, 127); changeEffect(FX_MODE_STATIC); break;
case IR40_COLDWHITE : changeColor(COLOR_COLDWHITE, 191); changeEffect(FX_MODE_STATIC); break;
case IR40_COLDWHITE2 : changeColor(COLOR_COLDWHITE2, 255); changeEffect(FX_MODE_STATIC); break;
case IR40_WPLUS : changeWhite(10); break;
case IR40_WMINUS : changeWhite(-10); break;
case IR40_WOFF : if (w) whiteLast = w; changeColor(RGBW32(r, g, b, 0)); break;
case IR40_WON : changeColor(RGBW32(r, g, b, whiteLast)); break;
case IR40_W25 : bri = 63; break;
case IR40_W50 : bri = 127; break;
case IR40_W75 : bri = 191; break;
case IR40_W100 : bri = 255; break;
case IR40_QUICK : changeEffectSpeed( 16); break;
case IR40_SLOW : changeEffectSpeed(-16); break;
case IR40_JUMP7 : changeEffectIntensity( 16); break;
case IR40_AUTO : changeEffectIntensity(-16); break;
case IR40_JUMP3 : presetFallback(1, FX_MODE_STATIC, 0); break;
case IR40_FADE3 : presetFallback(2, FX_MODE_BREATH, 0); break;
case IR40_FADE7 : presetFallback(3, FX_MODE_FIRE_FLICKER, 0); break;
case IR40_FLASH : presetFallback(4, FX_MODE_RAINBOW, 0); break;
default: return;
}
lastValidCode = code;
}
@@ -379,62 +478,51 @@ void decodeIR40(uint32_t code)
void decodeIR44(uint32_t code)
{
switch (code) {
case IR44_BPLUS : incBrightness(); break;
case IR44_BMINUS : decBrightness(); break;
case IR44_OFF : if (bri > 0) briLast = bri; bri = 0; break;
case IR44_ON : bri = briLast; break;
case IR44_RED : colorFromUint24(COLOR_RED); break;
case IR44_REDDISH : colorFromUint24(COLOR_REDDISH); break;
case IR44_ORANGE : colorFromUint24(COLOR_ORANGE); break;
case IR44_YELLOWISH : colorFromUint24(COLOR_YELLOWISH); break;
case IR44_YELLOW : colorFromUint24(COLOR_YELLOW); break;
case IR44_GREEN : colorFromUint24(COLOR_GREEN); break;
case IR44_GREENISH : colorFromUint24(COLOR_GREENISH); break;
case IR44_TURQUOISE : colorFromUint24(COLOR_TURQUOISE); break;
case IR44_CYAN : colorFromUint24(COLOR_CYAN); break;
case IR44_AQUA : colorFromUint24(COLOR_AQUA); break;
case IR44_BLUE : colorFromUint24(COLOR_BLUE); break;
case IR44_DEEPBLUE : colorFromUint24(COLOR_DEEPBLUE); break;
case IR44_PURPLE : colorFromUint24(COLOR_PURPLE); break;
case IR44_MAGENTA : colorFromUint24(COLOR_MAGENTA); break;
case IR44_PINK : colorFromUint24(COLOR_PINK); break;
case IR44_WHITE : {
if (strip.hasWhiteChannel()) {
if (col[3] > 0) col[3] = 0;
else { colorFromUint32(COLOR2_NEUTRALWHITE); effectCurrent = 0; }
} else colorFromUint24(COLOR_NEUTRALWHITE); } break;
case IR44_WARMWHITE2 : {
if (strip.hasWhiteChannel()) {colorFromUint32(COLOR2_WARMWHITE2); effectCurrent = 0; }
else colorFromUint24(COLOR_WARMWHITE2); } break;
case IR44_WARMWHITE : {
if (strip.hasWhiteChannel()) {colorFromUint32(COLOR2_WARMWHITE); effectCurrent = 0; }
else colorFromUint24(COLOR_WARMWHITE); } break;
case IR44_COLDWHITE : {
if (strip.hasWhiteChannel()) {colorFromUint32(COLOR2_COLDWHITE); effectCurrent = 0; }
else colorFromUint24(COLOR_COLDWHITE); } break;
case IR44_COLDWHITE2 : {
if (strip.hasWhiteChannel()) {colorFromUint32(COLOR2_COLDWHITE2); effectCurrent = 0; }
else colorFromUint24(COLOR_COLDWHITE2); } break;
case IR44_REDPLUS : relativeChange(&effectCurrent, 1, 0, MODE_COUNT); break;
case IR44_REDMINUS : relativeChange(&effectCurrent, -1, 0); break;
case IR44_GREENPLUS : relativeChange(&effectPalette, 1, 0, strip.getPaletteCount() -1); break;
case IR44_GREENMINUS : relativeChange(&effectPalette, -1, 0); break;
case IR44_BLUEPLUS : changeEffectIntensity( 16); break;
case IR44_BLUEMINUS : changeEffectIntensity(-16); break;
case IR44_QUICK : changeEffectSpeed( 16); break;
case IR44_SLOW : changeEffectSpeed(-16); break;
case IR44_BPLUS : incBrightness(); break;
case IR44_BMINUS : decBrightness(); break;
case IR44_OFF : if (bri > 0) briLast = bri; bri = 0; break;
case IR44_ON : bri = briLast; break;
case IR44_RED : changeColor(COLOR_RED); break;
case IR44_REDDISH : changeColor(COLOR_REDDISH); break;
case IR44_ORANGE : changeColor(COLOR_ORANGE); break;
case IR44_YELLOWISH : changeColor(COLOR_YELLOWISH); break;
case IR44_YELLOW : changeColor(COLOR_YELLOW); break;
case IR44_GREEN : changeColor(COLOR_GREEN); break;
case IR44_GREENISH : changeColor(COLOR_GREENISH); break;
case IR44_TURQUOISE : changeColor(COLOR_TURQUOISE); break;
case IR44_CYAN : changeColor(COLOR_CYAN); break;
case IR44_AQUA : changeColor(COLOR_AQUA); break;
case IR44_BLUE : changeColor(COLOR_BLUE); break;
case IR44_DEEPBLUE : changeColor(COLOR_DEEPBLUE); break;
case IR44_PURPLE : changeColor(COLOR_PURPLE); break;
case IR44_MAGENTA : changeColor(COLOR_MAGENTA); break;
case IR44_PINK : changeColor(COLOR_PINK); break;
case IR44_WHITE : changeColor(COLOR_NEUTRALWHITE, 127); changeEffect(FX_MODE_STATIC); break;
case IR44_WARMWHITE2 : changeColor(COLOR_WARMWHITE2, 0); changeEffect(FX_MODE_STATIC); break;
case IR44_WARMWHITE : changeColor(COLOR_WARMWHITE, 63); changeEffect(FX_MODE_STATIC); break;
case IR44_COLDWHITE : changeColor(COLOR_COLDWHITE, 191); changeEffect(FX_MODE_STATIC); break;
case IR44_COLDWHITE2 : changeColor(COLOR_COLDWHITE2, 255); changeEffect(FX_MODE_STATIC); break;
case IR44_REDPLUS : changeEffect(relativeChange(effectCurrent, 1, 0, MODE_COUNT -1)); break;
case IR44_REDMINUS : changeEffect(relativeChange(effectCurrent, -1, 0, MODE_COUNT -1)); break;
case IR44_GREENPLUS : changePalette(relativeChange(effectPalette, 1, 0, strip.getPaletteCount() -1)); break;
case IR44_GREENMINUS : changePalette(relativeChange(effectPalette, -1, 0, strip.getPaletteCount() -1)); break;
case IR44_BLUEPLUS : changeEffectIntensity( 16); break;
case IR44_BLUEMINUS : changeEffectIntensity(-16); break;
case IR44_QUICK : changeEffectSpeed( 16); break;
case IR44_SLOW : changeEffectSpeed(-16); break;
case IR44_DIY1 : presetFallback(1, FX_MODE_STATIC, 0); break;
case IR44_DIY2 : presetFallback(2, FX_MODE_BREATH, 0); break;
case IR44_DIY3 : presetFallback(3, FX_MODE_FIRE_FLICKER, 0); break;
case IR44_DIY4 : presetFallback(4, FX_MODE_RAINBOW, 0); break;
case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR_SMOOTH, 0); break;
case IR44_DIY6 : presetFallback(6, FX_MODE_RAIN, 0); break;
case IR44_AUTO : effectCurrent = FX_MODE_STATIC; break;
case IR44_FLASH : effectCurrent = FX_MODE_PALETTE; break;
case IR44_JUMP3 : bri = 63; break;
case IR44_JUMP7 : bri = 127; break;
case IR44_FADE3 : bri = 191; break;
case IR44_FADE7 : bri = 255; break;
case IR44_AUTO : changeEffect(FX_MODE_STATIC); break;
case IR44_FLASH : changeEffect(FX_MODE_PALETTE); break;
case IR44_JUMP3 : bri = 63; break;
case IR44_JUMP7 : bri = 127; break;
case IR44_FADE3 : bri = 191; break;
case IR44_FADE7 : bri = 255; break;
default: return;
}
lastValidCode = code;
}
@@ -442,28 +530,28 @@ void decodeIR44(uint32_t code)
void decodeIR21(uint32_t code)
{
switch (code) {
case IR21_BRIGHTER: incBrightness(); break;
case IR21_DARKER: decBrightness(); break;
case IR21_OFF: if (bri > 0) briLast = bri; bri = 0; break;
case IR21_ON: bri = briLast; break;
case IR21_RED: colorFromUint32(COLOR_RED); break;
case IR21_REDDISH: colorFromUint32(COLOR_REDDISH); break;
case IR21_ORANGE: colorFromUint32(COLOR_ORANGE); break;
case IR21_YELLOWISH: colorFromUint32(COLOR_YELLOWISH); break;
case IR21_GREEN: colorFromUint32(COLOR_GREEN); break;
case IR21_GREENISH: colorFromUint32(COLOR_GREENISH); break;
case IR21_TURQUOISE: colorFromUint32(COLOR_TURQUOISE); break;
case IR21_CYAN: colorFromUint32(COLOR_CYAN); break;
case IR21_BLUE: colorFromUint32(COLOR_BLUE); break;
case IR21_DEEPBLUE: colorFromUint32(COLOR_DEEPBLUE); break;
case IR21_PURPLE: colorFromUint32(COLOR_PURPLE); break;
case IR21_PINK: colorFromUint32(COLOR_PINK); break;
case IR21_WHITE: colorFromUint32(COLOR_WHITE); effectCurrent = 0; break;
case IR21_FLASH: presetFallback(1, FX_MODE_COLORTWINKLE, 0); break;
case IR21_STROBE: presetFallback(2, FX_MODE_RAINBOW_CYCLE, 0); break;
case IR21_FADE: presetFallback(3, FX_MODE_BREATH, 0); break;
case IR21_SMOOTH: presetFallback(4, FX_MODE_RAINBOW, 0); break;
default: return;
case IR21_BRIGHTER: incBrightness(); break;
case IR21_DARKER: decBrightness(); break;
case IR21_OFF: if (bri > 0) briLast = bri; bri = 0; break;
case IR21_ON: bri = briLast; break;
case IR21_RED: changeColor(COLOR_RED); break;
case IR21_REDDISH: changeColor(COLOR_REDDISH); break;
case IR21_ORANGE: changeColor(COLOR_ORANGE); break;
case IR21_YELLOWISH: changeColor(COLOR_YELLOWISH); break;
case IR21_GREEN: changeColor(COLOR_GREEN); break;
case IR21_GREENISH: changeColor(COLOR_GREENISH); break;
case IR21_TURQUOISE: changeColor(COLOR_TURQUOISE); break;
case IR21_CYAN: changeColor(COLOR_CYAN); break;
case IR21_BLUE: changeColor(COLOR_BLUE); break;
case IR21_DEEPBLUE: changeColor(COLOR_DEEPBLUE); break;
case IR21_PURPLE: changeColor(COLOR_PURPLE); break;
case IR21_PINK: changeColor(COLOR_PINK); break;
case IR21_WHITE: changeColor(COLOR_WHITE); changeEffect(FX_MODE_STATIC); break;
case IR21_FLASH: presetFallback(1, FX_MODE_COLORTWINKLE, 0); break;
case IR21_STROBE: presetFallback(2, FX_MODE_RAINBOW_CYCLE, 0); break;
case IR21_FADE: presetFallback(3, FX_MODE_BREATH, 0); break;
case IR21_SMOOTH: presetFallback(4, FX_MODE_RAINBOW, 0); break;
default: return;
}
lastValidCode = code;
}
@@ -471,31 +559,32 @@ void decodeIR21(uint32_t code)
void decodeIR6(uint32_t code)
{
switch (code) {
case IR6_POWER: toggleOnOff(); break;
case IR6_CHANNEL_UP: incBrightness(); break;
case IR6_CHANNEL_DOWN: decBrightness(); break;
case IR6_VOLUME_UP: relativeChange(&effectCurrent, 1, 0, MODE_COUNT); break; // next effect
case IR6_VOLUME_DOWN: // next palette
relativeChange(&effectPalette, 1, 0, strip.getPaletteCount() -1);
case IR6_POWER: toggleOnOff(); break;
case IR6_CHANNEL_UP: incBrightness(); break;
case IR6_CHANNEL_DOWN: decBrightness(); break;
case IR6_VOLUME_UP: changeEffect(relativeChange(effectCurrent, 1, 0, MODE_COUNT -1)); break;
case IR6_VOLUME_DOWN: changePalette(relativeChange(effectPalette, 1, 0, strip.getPaletteCount() -1));
switch(lastIR6ColourIdx) {
case 0: colorFromUint32(COLOR_RED); break;
case 1: colorFromUint32(COLOR_REDDISH); break;
case 2: colorFromUint32(COLOR_ORANGE); break;
case 3: colorFromUint32(COLOR_YELLOWISH); break;
case 4: colorFromUint32(COLOR_GREEN); break;
case 5: colorFromUint32(COLOR_GREENISH); break;
case 6: colorFromUint32(COLOR_TURQUOISE); break;
case 7: colorFromUint32(COLOR_CYAN); break;
case 8: colorFromUint32(COLOR_BLUE); break;
case 9: colorFromUint32(COLOR_DEEPBLUE); break;
case 10:colorFromUint32(COLOR_PURPLE); break;
case 11:colorFromUint32(COLOR_PINK); break;
case 12:colorFromUint32(COLOR_WHITE); break;
default: break;
case 0: changeColor(COLOR_RED); break;
case 1: changeColor(COLOR_REDDISH); break;
case 2: changeColor(COLOR_ORANGE); break;
case 3: changeColor(COLOR_YELLOWISH); break;
case 4: changeColor(COLOR_GREEN); break;
case 5: changeColor(COLOR_GREENISH); break;
case 6: changeColor(COLOR_TURQUOISE); break;
case 7: changeColor(COLOR_CYAN); break;
case 8: changeColor(COLOR_BLUE); break;
case 9: changeColor(COLOR_DEEPBLUE); break;
case 10:changeColor(COLOR_PURPLE); break;
case 11:changeColor(COLOR_PINK); break;
case 12:changeColor(COLOR_WHITE); break;
default: break;
}
lastIR6ColourIdx++;
if(lastIR6ColourIdx > 12) lastIR6ColourIdx = 0; break;
case IR6_MUTE: effectCurrent = 0; effectPalette = 0; colorFromUint32(COLOR_WHITE); bri=255; break;
if(lastIR6ColourIdx > 12) lastIR6ColourIdx = 0;
break;
case IR6_MUTE: changeEffect(FX_MODE_STATIC); changePalette(0); changeColor(COLOR_WHITE); bri=255; break;
default: return;
}
lastValidCode = code;
}
@@ -503,17 +592,15 @@ void decodeIR6(uint32_t code)
void decodeIR9(uint32_t code)
{
switch (code) {
case IR9_POWER : toggleOnOff(); break;
case IR9_A : presetFallback(1, FX_MODE_COLORTWINKLE, effectPalette); break;
case IR9_B : presetFallback(2, FX_MODE_RAINBOW_CYCLE, effectPalette); break;
case IR9_C : presetFallback(3, FX_MODE_BREATH, effectPalette); break;
case IR9_UP : incBrightness(); break;
case IR9_DOWN : decBrightness(); break;
//case IR9_UP : changeEffectIntensity(16); break;
//case IR9_DOWN : changeEffectIntensity(-16); break;
case IR9_LEFT : changeEffectSpeed(-16); break;
case IR9_RIGHT : changeEffectSpeed(16); break;
case IR9_SELECT : relativeChange(&effectCurrent, 1, 0, MODE_COUNT); break;
case IR9_POWER : toggleOnOff(); break;
case IR9_A : presetFallback(1, FX_MODE_COLORTWINKLE, effectPalette); break;
case IR9_B : presetFallback(2, FX_MODE_RAINBOW_CYCLE, effectPalette); break;
case IR9_C : presetFallback(3, FX_MODE_BREATH, effectPalette); break;
case IR9_UP : incBrightness(); break;
case IR9_DOWN : decBrightness(); break;
case IR9_LEFT : changeEffectSpeed(-16); break;
case IR9_RIGHT : changeEffectSpeed(16); break;
case IR9_SELECT : changeEffect(relativeChange(effectCurrent, 1, 0, MODE_COUNT -1)); break;
default: return;
}
lastValidCode = code;
@@ -575,7 +662,7 @@ void decodeIRJson(uint32_t code)
cmdStr = fdo["cmd"].as<String>();
jsonCmdObj = fdo["cmd"]; //object
if (jsonCmdObj.isNull())
if (jsonCmdObj.isNull()) // we could also use: fdo["cmd"].is<String>()
{
if (cmdStr.startsWith("!")) {
// call limited set of C functions
@@ -586,37 +673,33 @@ void decodeIRJson(uint32_t code)
lastValidCode = code;
decBrightness();
} else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback
uint8_t p1 = fdo["PL"] ? fdo["PL"] : 1;
uint8_t p2 = fdo["FX"] ? fdo["FX"] : random8(MODE_COUNT);
uint8_t p3 = fdo["FP"] ? fdo["FP"] : 0;
uint8_t p1 = fdo["PL"] | 1;
uint8_t p2 = fdo["FX"] | random8(MODE_COUNT -1);
uint8_t p3 = fdo["FP"] | 0;
presetFallback(p1, p2, p3);
}
} else {
// HTTP API command
if (cmdStr.indexOf("~") || fdo["rpt"])
{
// repeatable action
lastValidCode = code;
String apireq = "win"; apireq += '&'; // reduce flash string usage
if (cmdStr.indexOf("~") || fdo["rpt"]) lastValidCode = code; // repeatable action
if (!cmdStr.startsWith(apireq)) cmdStr = apireq + cmdStr; // if no "win&" prefix
if (!irApplyToAllSelected && cmdStr.indexOf(F("SS="))<0) {
char tmp[10];
sprintf_P(tmp, PSTR("&SS=%d"), strip.getMainSegmentId());
cmdStr += tmp;
}
if (effectCurrent == 0 && cmdStr.indexOf("FP=") > -1) {
// setting palette but it wont show because effect is solid
effectCurrent = FX_MODE_GRADIENT;
}
if (!cmdStr.startsWith("win&")) {
cmdStr = "win&" + cmdStr;
}
fdo.clear(); //clear JSON buffer (it is no longer needed)
handleSet(nullptr, cmdStr, false); // no colorUpdated() call here
fdo.clear(); // clear JSON buffer (it is no longer needed)
handleSet(nullptr, cmdStr, false); // no stateUpdated() call here
}
} else {
// command is JSON object
// command is JSON object (TODO: currently will not handle irApplyToAllSelected correctly)
if (jsonCmdObj[F("psave")].isNull()) deserializeState(jsonCmdObj, CALL_MODE_BUTTON_PRESET);
else {
uint8_t psave = jsonCmdObj[F("psave")].as<int>();
char pname[33];
sprintf_P(pname, PSTR("IR Preset %d"), psave);
fdo.clear();
if (psave > 0 && psave < 251) savePreset(psave, true, pname, fdo);
if (psave > 0 && psave < 251) savePreset(psave, pname, fdo);
}
}
releaseJSONBufferLock();

View File

@@ -26,7 +26,7 @@
//Infrared codes for 24-key remote from http://woodsgood.ca/projects/2015/02/13/rgb-led-strip-controllers-ir-codes/
#define IR24_BRIGHTER 0xF700FF
#define IR24_DARKER 0xF7807F
#define IR24_OFF 0xF740BF
#define IR24_OFF 0xF740BF
#define IR24_ON 0xF7C03F
#define IR24_RED 0xF720DF
#define IR24_REDDISH 0xF710EF
@@ -35,7 +35,7 @@
#define IR24_YELLOW 0xF728D7
#define IR24_GREEN 0xF7A05F
#define IR24_GREENISH 0xF7906F
#define IR24_TURQUOISE 0xF7B04F
#define IR24_TURQUOISE 0xF7B04F
#define IR24_CYAN 0xF78877
#define IR24_AQUA 0xF7A857
#define IR24_BLUE 0xF7609F
@@ -76,30 +76,30 @@
#define IR24_CT_MEMORY 0xF7E817 // MEMORY
// 24-key defs for old remote control
#define IR24_OLD_BRIGHTER 0xFF906F // Brightness Up
#define IR24_OLD_DARKER 0xFFB847 // Brightness Down
#define IR24_OLD_OFF 0xFFF807 // Power OFF
#define IR24_OLD_ON 0xFFB04F // Power On
#define IR24_OLD_RED 0xFF9867 // RED
#define IR24_OLD_REDDISH 0xFFE817 // Light RED
#define IR24_OLD_ORANGE 0xFF02FD // Orange
#define IR24_OLD_YELLOWISH 0xFF50AF // Light Orange
#define IR24_OLD_YELLOW 0xFF38C7 // YELLOW
#define IR24_OLD_GREEN 0xFFD827 // GREEN
#define IR24_OLD_GREENISH 0xFF48B7 // Light GREEN
#define IR24_OLD_TURQUOISE 0xFF32CD // TURQUOISE
#define IR24_OLD_BRIGHTER 0xFF906F // Brightness Up
#define IR24_OLD_DARKER 0xFFB847 // Brightness Down
#define IR24_OLD_OFF 0xFFF807 // Power OFF
#define IR24_OLD_ON 0xFFB04F // Power On
#define IR24_OLD_RED 0xFF9867 // RED
#define IR24_OLD_REDDISH 0xFFE817 // Light RED
#define IR24_OLD_ORANGE 0xFF02FD // Orange
#define IR24_OLD_YELLOWISH 0xFF50AF // Light Orange
#define IR24_OLD_YELLOW 0xFF38C7 // YELLOW
#define IR24_OLD_GREEN 0xFFD827 // GREEN
#define IR24_OLD_GREENISH 0xFF48B7 // Light GREEN
#define IR24_OLD_TURQUOISE 0xFF32CD // TURQUOISE
#define IR24_OLD_CYAN 0xFF7887 // CYAN
#define IR24_OLD_AQUA 0xFF28D7 // AQUA
#define IR24_OLD_BLUE 0xFF8877 // BLUE
#define IR24_OLD_AQUA 0xFF28D7 // AQUA
#define IR24_OLD_BLUE 0xFF8877 // BLUE
#define IR24_OLD_DEEPBLUE 0xFF6897 // Dark BLUE
#define IR24_OLD_PURPLE 0xFF20DF // PURPLE
#define IR24_OLD_MAGENTA 0xFF708F // MAGENTA
#define IR24_OLD_PINK 0xFFF00F // PINK
#define IR24_OLD_WHITE 0xFFA857 // WHITE
#define IR24_OLD_FLASH 0xFFB24D // FLASH Mode
#define IR24_OLD_STROBE 0xFF00FF // STROBE Mode
#define IR24_OLD_FADE 0xFF58A7 // FADE Mode
#define IR24_OLD_SMOOTH 0xFF30CF // SMOOTH Mode
#define IR24_OLD_PURPLE 0xFF20DF // PURPLE
#define IR24_OLD_MAGENTA 0xFF708F // MAGENTA
#define IR24_OLD_PINK 0xFFF00F // PINK
#define IR24_OLD_WHITE 0xFFA857 // WHITE
#define IR24_OLD_FLASH 0xFFB24D // FLASH Mode
#define IR24_OLD_STROBE 0xFF00FF // STROBE Mode
#define IR24_OLD_FADE 0xFF58A7 // FADE Mode
#define IR24_OLD_SMOOTH 0xFF30CF // SMOOTH Mode
// 40-key defs for blue remote control
#define IR40_BPLUS 0xFF3AC5 //
@@ -212,32 +212,27 @@
#define IR21_FADE 0xFF02FD
#define IR21_SMOOTH 0xFFC23D
#define COLOR_RED 0xFF0000
#define COLOR_REDDISH 0xFF7800
#define COLOR_ORANGE 0xFFA000
#define COLOR_YELLOWISH 0xFFC800
#define COLOR_YELLOW 0xFFFF00
#define COLOR_GREEN 0x00FF00
#define COLOR_GREENISH 0x00FF78
#define COLOR_TURQUOISE 0x00FFA0
#define COLOR_CYAN 0x00FFDC
#define COLOR_AQUA 0x00C8FF
#define COLOR_BLUE 0x00A0FF
#define COLOR_DEEPBLUE 0x0000FF
#define COLOR_PURPLE 0xAA00FF
#define COLOR_MAGENTA 0xFF00DC
#define COLOR_PINK 0xFF00A0
#define COLOR_WHITE 0xFFFFDC
#define COLOR_WARMWHITE2 0xFFAA69
#define COLOR_WARMWHITE 0xFFBF8E
#define COLOR_NEUTRALWHITE 0xFFD4B4
#define COLOR_COLDWHITE 0xFFE9D9
#define COLOR_COLDWHITE2 0xFFFFFF
#define COLOR2_WARMWHITE2 0xFFFF9900
#define COLOR2_WARMWHITE 0xFF825A00
#define COLOR2_NEUTRALWHITE 0xFF000000
#define COLOR2_COLDWHITE 0xFF7F7F7F
#define COLOR2_COLDWHITE2 0xFFFFFFFF
#define COLOR_RED 0xFF0000
#define COLOR_REDDISH 0xFF7800
#define COLOR_ORANGE 0xFFA000
#define COLOR_YELLOWISH 0xFFC800
#define COLOR_YELLOW 0xFFFF00
#define COLOR_GREEN 0x00FF00
#define COLOR_GREENISH 0x00FF78
#define COLOR_TURQUOISE 0x00FFA0
#define COLOR_CYAN 0x00FFDC
#define COLOR_AQUA 0x00C8FF
#define COLOR_BLUE 0x00A0FF
#define COLOR_DEEPBLUE 0x0000FF
#define COLOR_PURPLE 0xAA00FF
#define COLOR_MAGENTA 0xFF00DC
#define COLOR_PINK 0xFF00A0
#define COLOR_WHITE 0xFFFFFFFF
#define COLOR_WARMWHITE2 0xFFFFAA69
#define COLOR_WARMWHITE 0xFFFFBF8E
#define COLOR_NEUTRALWHITE 0xFFFFD4B4
#define COLOR_COLDWHITE 0xFFFFE9D9
#define COLOR_COLDWHITE2 0xFFFFFFFF
#define ACTION_NONE 0
#define ACTION_BRIGHT_UP 1
@@ -246,4 +241,4 @@
#define ACTION_SPEED_DOWN 4
#define ACTION_INTENSITY_UP 5
#define ACTION_INTENSITY_DOWN 6
#define ACTION_POWER 7
#define ACTION_POWER 7

View File

@@ -95,7 +95,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
if (stop > start && of > len -1) of = len -1;
strip.setSegment(id, start, stop, grp, spc, of);
byte segbri = 0;
byte segbri = seg.opacity;
if (getVal(elem["bri"], &segbri)) {
if (segbri > 0) seg.setOpacity(segbri, id);
seg.setOption(SEG_OPTION_ON, segbri, id);
@@ -177,7 +177,12 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
if (!iarr.isNull()) {
uint8_t oldSegId = strip.setPixelSegment(id);
//freeze and init to black
// set brightness immediately and disable transition
transitionDelayTemp = 0;
jsonTransitionOnce = true;
strip.setBrightness(scaledBri(bri), true);
// freeze and init to black
if (!seg.getOption(SEG_OPTION_FREEZE)) {
seg.setOption(SEG_OPTION_FREEZE, true);
strip.fill(0);
@@ -200,7 +205,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
JsonArray icol = iarr[i];
if (!icol.isNull()) { //array, e.g. [255,0,0]
byte sz = icol.size();
if (sz > 0 || sz < 5) copyArray(icol, rgbw);
if (sz > 0 && sz < 5) copyArray(icol, rgbw);
} else { //hex string, e.g. "FF0000"
byte brgbw[] = {0,0,0,0};
const char* hexCol = iarr[i];
@@ -223,8 +228,8 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
}
strip.setPixelSegment(oldSegId);
strip.trigger();
} else if (!elem["frz"] && iarr.isNull()) { //return to regular effect
seg.setOption(SEG_OPTION_FREEZE, false);
// } else if (!elem["frz"] && iarr.isNull()) { //return to regular effect
// seg.setOption(SEG_OPTION_FREEZE, false);
}
// send UDP if not in preset and something changed that is not just selection
//if (!presetId && (seg.differs(prev) & 0x7F)) stateChanged = true;
@@ -238,6 +243,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
{
bool stateResponse = root[F("v")] | false;
bool onBefore = bri;
getVal(root["bri"], &bri);
bool on = root["on"] | (bri > 0);
@@ -245,6 +251,15 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (root["on"].is<const char*>() && root["on"].as<const char*>()[0] == 't') toggleOnOff();
if (bri && !onBefore) { // unfreeze all segments when turning on
for (uint8_t s=0; s < strip.getMaxSegments(); s++) {
strip.getSegment(s).setOption(SEG_OPTION_FREEZE, false, s);
}
if (realtimeMode && !realtimeOverride && useMainSegmentOnly) { // keep live segment frozen if live
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, true, strip.getMainSegmentId());
}
}
int tr = -1;
if (!presetId || currentPlaylist < 0) { //do not apply transition time from preset if playlist active, as it would override playlist transition times
tr = root[F("transition")] | -1;
@@ -263,15 +278,15 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
transitionDelayTemp *= 100;
jsonTransitionOnce = true;
}
strip.setTransition(transitionDelayTemp);
strip.setTransition(transitionDelayTemp); // required here for color transitions to have correct duration
tr = root[F("tb")] | -1;
if (tr >= 0) strip.timebase = ((uint32_t)tr) - millis();
JsonObject nl = root["nl"];
nightlightActive = nl["on"] | nightlightActive;
nightlightDelayMins = nl["dur"] | nightlightDelayMins;
nightlightMode = nl["mode"] | nightlightMode;
nightlightDelayMins = nl["dur"] | nightlightDelayMins;
nightlightMode = nl["mode"] | nightlightMode;
nightlightTargetBri = nl[F("tbri")] | nightlightTargetBri;
JsonObject udpn = root["udpn"];
@@ -287,16 +302,23 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
doReboot = root[F("rb")] | doReboot;
strip.setMainSegmentId(root[F("mainseg")] | strip.getMainSegmentId()); // must be before realtimeLock() if "live"
realtimeOverride = root[F("lor")] | realtimeOverride;
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (root.containsKey("live")) {
bool lv = root["live"];
if (lv) realtimeLock(65000); //enter realtime without timeout
else realtimeTimeout = 0; //cancel realtime mode immediately
if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, !realtimeOverride, strip.getMainSegmentId());
}
strip.setMainSegmentId(root[F("mainseg")] | strip.getMainSegmentId());
if (root.containsKey("live")) {
if (root["live"].as<bool>()) {
transitionDelayTemp = 0;
jsonTransitionOnce = true;
realtimeLock(65000);
} else {
exitRealtime();
}
}
int it = 0;
JsonVariant segVar = root["seg"];
@@ -330,19 +352,13 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
}
}
#ifndef WLED_DISABLE_CRONIXIE
if (root["nx"].is<const char*>()) {
strncpy(cronixieDisplay, root["nx"], 6);
}
#endif
usermods.readFromJsonState(root);
loadLedmap = root[F("ledmap")] | loadLedmap;
byte ps = root[F("psave")];
if (ps > 0) {
savePreset(ps, true, nullptr, root);
savePreset(ps, nullptr, root);
} else {
ps = root[F("pdel")]; //deletion
if (ps > 0) {
@@ -503,15 +519,6 @@ void serializeInfo(JsonObject root)
JsonObject leds = root.createNestedObject("leds");
leds[F("count")] = strip.getLengthTotal();
leds[F("rgbw")] = strip.hasRGBWBus(); //deprecated, use info.leds.lc
leds[F("wv")] = false; //deprecated, use info.leds.lc
leds["cct"] = correctWB || strip.hasCCTBus(); //deprecated, use info.leds.lc
switch (Bus::getAutoWhiteMode()) {
case RGBW_MODE_MANUAL_ONLY:
case RGBW_MODE_DUAL:
if (strip.hasWhiteChannel()) leds[F("wv")] = true;
break;
}
leds[F("pwr")] = strip.currentMilliamps;
leds["fps"] = strip.getFps();
@@ -530,11 +537,17 @@ void serializeInfo(JsonObject root)
leds["lc"] = totalLC;
leds[F("rgbw")] = strip.hasRGBWBus(); // deprecated, use info.leds.lc
leds[F("wv")] = totalLC & 0x02; // deprecated, true if white slider should be displayed for any segment
leds["cct"] = totalLC & 0x04; // deprecated, use info.leds.lc
root[F("str")] = syncToggleReceive;
root[F("name")] = serverDescription;
root[F("udpport")] = udpPort;
root["live"] = (bool)realtimeMode;
root[F("liveseg")] = useMainSegmentOnly ? strip.getMainSegmentId() : -1; // if using main segment only for live
//root[F("mso")] = useMainSegmentOnly; // using main segment only for live
switch (realtimeMode) {
case REALTIME_MODE_INACTIVE: root["lm"] = ""; break;
@@ -619,7 +632,7 @@ void serializeInfo(JsonObject root)
#ifndef WLED_DISABLE_BLYNK
os += 0x20;
#endif
#ifndef WLED_DISABLE_CRONIXIE
#ifdef USERMOD_CRONIXIE
os += 0x10;
#endif
#ifndef WLED_DISABLE_FILESYSTEM

View File

@@ -3,15 +3,24 @@
/*
* LED methods
*/
void setValuesFromFirstSelectedSeg()
void setValuesFromMainSeg() { setValuesFromSegment(strip.getMainSegmentId()); }
void setValuesFromFirstSelectedSeg() { setValuesFromSegment(strip.getFirstSelectedSegId()); }
void setValuesFromSegment(uint8_t s)
{
WS2812FX::Segment& seg = strip.getFirstSelectedSeg();
colorFromUint32(seg.colors[0]);
colorFromUint32(seg.colors[1], true);
effectCurrent = seg.mode;
effectSpeed = seg.speed;
WS2812FX::Segment& seg = strip.getSegment(s);
col[0] = R(seg.colors[0]);
col[1] = G(seg.colors[0]);
col[2] = B(seg.colors[0]);
col[3] = W(seg.colors[0]);
colSec[0] = R(seg.colors[1]);
colSec[1] = G(seg.colors[1]);
colSec[2] = B(seg.colors[1]);
colSec[3] = W(seg.colors[1]);
effectCurrent = seg.mode;
effectSpeed = seg.speed;
effectIntensity = seg.intensity;
effectPalette = seg.palette;
effectPalette = seg.palette;
}
@@ -26,20 +35,14 @@ void applyValuesToSelectedSegs()
WS2812FX::Segment& seg = strip.getSegment(i);
if (i != firstSel && (!seg.isActive() || !seg.isSelected())) continue;
if (effectSpeed != selsegPrev.speed) {
seg.speed = effectSpeed; stateChanged = true;}
if (effectIntensity != selsegPrev.intensity) {
seg.intensity = effectIntensity; stateChanged = true;}
if (effectPalette != selsegPrev.palette) {
seg.palette = effectPalette; stateChanged = true;}
if (effectCurrent != selsegPrev.mode) {
strip.setMode(i, effectCurrent); stateChanged = true;}
uint32_t col0 = RGBW32(col[0],col[1],col[2],col[3]);
if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;}
if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;}
if (effectPalette != selsegPrev.palette) {seg.palette = effectPalette; stateChanged = true;}
if (effectCurrent != selsegPrev.mode) {strip.setMode(i, effectCurrent); stateChanged = true;}
uint32_t col0 = RGBW32( col[0], col[1], col[2], col[3]);
uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]);
if (col0 != selsegPrev.colors[0]) {
seg.setColor(0, col0, i); stateChanged = true;}
if (col1 != selsegPrev.colors[1]) {
seg.setColor(1, col1, i); stateChanged = true;}
if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0, i); stateChanged = true;}
if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1, i); stateChanged = true;}
}
}
@@ -97,7 +100,6 @@ void stateUpdated(byte callMode) {
setValuesFromFirstSelectedSeg();
if (bri != briOld || stateChanged) {
if (realtimeTimeout == UINT32_MAX) realtimeTimeout = 0;
if (stateChanged) currentPreset = 0; //something changed, so we are no longer in the preset
if (callMode != CALL_MODE_NOTIFICATION && callMode != CALL_MODE_NO_NOTIFY) notify(callMode);

View File

@@ -169,7 +169,6 @@ void handleTime() {
updateLocalTime();
checkTimers();
checkCountdown();
handleOverlays();
}
}
@@ -467,6 +466,7 @@ void calculateSunriseAndSunset() {
int minUTC = getSunriseUTC(year(localTime), month(localTime), day(localTime), latitude, longitude);
if (minUTC) {
// there is a sunrise
if (minUTC < 0) minUTC += 24*60; // add a day if negative
tim_0.tm_hour = minUTC / 60;
tim_0.tm_min = minUTC % 60;
sunrise = tz->toLocal(mktime(&tim_0) + utcOffsetSecs);
@@ -478,6 +478,7 @@ void calculateSunriseAndSunset() {
minUTC = getSunriseUTC(year(localTime), month(localTime), day(localTime), latitude, longitude, true);
if (minUTC) {
// there is a sunset
if (minUTC < 0) minUTC += 24*60; // add a day if negative
tim_0.tm_hour = minUTC / 60;
tim_0.tm_min = minUTC % 60;
sunset = tz->toLocal(mktime(&tim_0) + utcOffsetSecs);

View File

@@ -3,31 +3,6 @@
/*
* Used to draw clock overlays over the strip
*/
void initCronixie()
{
if (overlayCurrent == 3 && dP[0] == 255) //if dP[0] is 255, cronixie is not yet init'ed
{
setCronixie();
strip.getSegment(0).grouping = 10; //10 LEDs per digit
} else if (dP[0] < 255 && overlayCurrent != 3)
{
strip.getSegment(0).grouping = 1;
dP[0] = 255;
}
}
//handleOverlays is essentially the equivalent of usermods.loop
void handleOverlays()
{
initCronixie();
if (overlayCurrent == 3) {
_overlayCronixie();//Diamex cronixie clock kit
strip.trigger();
}
}
void _overlayAnalogClock()
{
@@ -114,253 +89,9 @@ void _overlayAnalogCountdown()
void handleOverlayDraw() {
usermods.handleOverlayDraw();
if (!overlayCurrent) return;
switch (overlayCurrent)
{
case 1: _overlayAnalogClock(); break;
case 3: _drawOverlayCronixie(); break;
}
if (overlayCurrent == 1) _overlayAnalogClock();
}
/*
* Support for the Cronixie clock
* Support for the Cronixie clock has moved to a usermod, compile with "-D USERMOD_CRONIXIE" to enable
*/
#ifndef WLED_DISABLE_CRONIXIE
byte _digitOut[6] = {10,10,10,10,10,10};
byte getSameCodeLength(char code, int index, char const cronixieDisplay[])
{
byte counter = 0;
for (int i = index+1; i < 6; i++)
{
if (cronixieDisplay[i] == code)
{
counter++;
} else {
return counter;
}
}
return counter;
}
void setCronixie()
{
/*
* digit purpose index
* 0-9 | 0-9 (incl. random)
* 10 | blank
* 11 | blank, bg off
* 12 | test upw.
* 13 | test dnw.
* 14 | binary AM/PM
* 15 | BB upper +50 for no trailing 0
* 16 | BBB
* 17 | BBBB
* 18 | BBBBB
* 19 | BBBBBB
* 20 | H
* 21 | HH
* 22 | HHH
* 23 | HHHH
* 24 | M
* 25 | MM
* 26 | MMM
* 27 | MMMM
* 28 | MMMMM
* 29 | MMMMMM
* 30 | S
* 31 | SS
* 32 | SSS
* 33 | SSSS
* 34 | SSSSS
* 35 | SSSSSS
* 36 | Y
* 37 | YY
* 38 | YYYY
* 39 | I
* 40 | II
* 41 | W
* 42 | WW
* 43 | D
* 44 | DD
* 45 | DDD
* 46 | V
* 47 | VV
* 48 | VVV
* 49 | VVVV
* 50 | VVVVV
* 51 | VVVVVV
* 52 | v
* 53 | vv
* 54 | vvv
* 55 | vvvv
* 56 | vvvvv
* 57 | vvvvvv
*/
//H HourLower | HH - Hour 24. | AH - Hour 12. | HHH Hour of Month | HHHH Hour of Year
//M MinuteUpper | MM Minute of Hour | MMM Minute of 12h | MMMM Minute of Day | MMMMM Minute of Month | MMMMMM Minute of Year
//S SecondUpper | SS Second of Minute | SSS Second of 10 Minute | SSSS Second of Hour | SSSSS Second of Day | SSSSSS Second of Week
//B AM/PM | BB 0-6/6-12/12-18/18-24 | BBB 0-3... | BBBB 0-1.5... | BBBBB 0-1 | BBBBBB 0-0.5
//Y YearLower | YY - Year LU | YYYY - Std.
//I MonthLower | II - Month of Year
//W Week of Month | WW Week of Year
//D Day of Week | DD Day Of Month | DDD Day Of Year
DEBUG_PRINT("cset ");
DEBUG_PRINTLN(cronixieDisplay);
for (int i = 0; i < 6; i++)
{
dP[i] = 10;
switch (cronixieDisplay[i])
{
case '_': dP[i] = 10; break;
case '-': dP[i] = 11; break;
case 'r': dP[i] = random(1,7); break; //random btw. 1-6
case 'R': dP[i] = random(0,10); break; //random btw. 0-9
//case 't': break; //Test upw.
//case 'T': break; //Test dnw.
case 'b': dP[i] = 14 + getSameCodeLength('b',i,cronixieDisplay); i = i+dP[i]-14; break;
case 'B': dP[i] = 14 + getSameCodeLength('B',i,cronixieDisplay); i = i+dP[i]-14; break;
case 'h': dP[i] = 70 + getSameCodeLength('h',i,cronixieDisplay); i = i+dP[i]-70; break;
case 'H': dP[i] = 20 + getSameCodeLength('H',i,cronixieDisplay); i = i+dP[i]-20; break;
case 'A': dP[i] = 108; i++; break;
case 'a': dP[i] = 58; i++; break;
case 'm': dP[i] = 74 + getSameCodeLength('m',i,cronixieDisplay); i = i+dP[i]-74; break;
case 'M': dP[i] = 24 + getSameCodeLength('M',i,cronixieDisplay); i = i+dP[i]-24; break;
case 's': dP[i] = 80 + getSameCodeLength('s',i,cronixieDisplay); i = i+dP[i]-80; break; //refresh more often bc. of secs
case 'S': dP[i] = 30 + getSameCodeLength('S',i,cronixieDisplay); i = i+dP[i]-30; break;
case 'Y': dP[i] = 36 + getSameCodeLength('Y',i,cronixieDisplay); i = i+dP[i]-36; break;
case 'y': dP[i] = 86 + getSameCodeLength('y',i,cronixieDisplay); i = i+dP[i]-86; break;
case 'I': dP[i] = 39 + getSameCodeLength('I',i,cronixieDisplay); i = i+dP[i]-39; break; //Month. Don't ask me why month and minute both start with M.
case 'i': dP[i] = 89 + getSameCodeLength('i',i,cronixieDisplay); i = i+dP[i]-89; break;
//case 'W': break;
//case 'w': break;
case 'D': dP[i] = 43 + getSameCodeLength('D',i,cronixieDisplay); i = i+dP[i]-43; break;
case 'd': dP[i] = 93 + getSameCodeLength('d',i,cronixieDisplay); i = i+dP[i]-93; break;
case '0': dP[i] = 0; break;
case '1': dP[i] = 1; break;
case '2': dP[i] = 2; break;
case '3': dP[i] = 3; break;
case '4': dP[i] = 4; break;
case '5': dP[i] = 5; break;
case '6': dP[i] = 6; break;
case '7': dP[i] = 7; break;
case '8': dP[i] = 8; break;
case '9': dP[i] = 9; break;
//case 'V': break; //user var0
//case 'v': break; //user var1
}
}
DEBUG_PRINT("result ");
for (int i = 0; i < 5; i++)
{
DEBUG_PRINT((int)dP[i]);
DEBUG_PRINT(" ");
}
DEBUG_PRINTLN((int)dP[5]);
_overlayCronixie(); //refresh
}
void _overlayCronixie()
{
byte h = hour(localTime);
byte h0 = h;
byte m = minute(localTime);
byte s = second(localTime);
byte d = day(localTime);
byte mi = month(localTime);
int y = year(localTime);
//this has to be changed in time for 22nd century
y -= 2000; if (y<0) y += 30; //makes countdown work
if (useAMPM && !countdownMode)
{
if (h>12) h-=12;
else if (h==0) h+=12;
}
for (int i = 0; i < 6; i++)
{
if (dP[i] < 12) _digitOut[i] = dP[i];
else {
if (dP[i] < 65)
{
switch(dP[i])
{
case 21: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; i++; break; //HH
case 25: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; i++; break; //MM
case 31: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; i++; break; //SS
case 20: _digitOut[i] = h- (h/10)*10; break; //H
case 24: _digitOut[i] = m/10; break; //M
case 30: _digitOut[i] = s/10; break; //S
case 43: _digitOut[i] = weekday(localTime); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //D
case 44: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; i++; break; //DD
case 40: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; i++; break; //II
case 37: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //YY
case 39: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //YYYY
//case 16: _digitOut[i+2] = ((h0/3)&1)?1:0; i++; //BBB (BBBB NI)
//case 15: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:0; i++; //BB
case 14: _digitOut[i] = (h0>11)?1:0; break; //B
}
} else
{
switch(dP[i])
{
case 71: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //hh
case 75: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //mm
case 81: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ss
//case 66: _digitOut[i+2] = ((h0/3)&1)?1:10; i++; //bbb (bbbb NI)
//case 65: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:10; i++; //bb
case 64: _digitOut[i] = (h0>11)?1:10; break; //b
case 93: _digitOut[i] = weekday(localTime); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //d
case 94: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //dd
case 90: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ii
case 87: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //yy
case 89: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //yyyy
}
}
}
}
}
void _drawOverlayCronixie()
{
byte offsets[] = {5, 0, 6, 1, 7, 2, 8, 3, 9, 4};
for (uint16_t i = 0; i < 6; i++)
{
byte o = 10*i;
byte excl = 10;
if(_digitOut[i] < 10) excl = offsets[_digitOut[i]];
excl += o;
if (cronixieBacklight && _digitOut[i] <11)
{
uint32_t col = strip.gamma32(strip.getSegment(0).colors[1]);
for (uint16_t j=o; j< o+10; j++) {
if (j != excl) strip.setPixelColor(j, col);
}
} else
{
for (uint16_t j=o; j< o+10; j++) {
if (j != excl) strip.setPixelColor(j, 0);
}
}
}
}
#else // WLED_DISABLE_CRONIXIE
byte getSameCodeLength(char code, int index, char const cronixieDisplay[]) { return 0; }
void setCronixie() {}
void _overlayCronixie() {}
void _drawOverlayCronixie() {}
#endif

View File

@@ -53,7 +53,8 @@ enum struct PinOwner : uint8_t {
// #define USERMOD_ID_ELEKSTUBE_IPS // 0x10 // Usermod "usermod_elekstube_ips.h" -- Uses quite a few pins ... see Hardware.h and User_Setup.h
// #define USERMOD_ID_SN_PHOTORESISTOR // 0x11 // Usermod "usermod_sn_photoresistor.h" -- Uses hard-coded pin (PHOTORESISTOR_PIN == A0), but could be easily updated to use pinManager
UM_RGBRotaryEncoder = USERMOD_RGB_ROTARY_ENCODER, // 0x16 // Usermod "rgb-rotary-encoder.h"
UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA // 0x17 // Usermod "quinled-an-penta.h"
UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA, // 0x17 // Usermod "quinled-an-penta.h"
UM_BME280 = USERMOD_ID_BME280 // 0x18 // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins
};
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");

View File

@@ -50,12 +50,13 @@ bool applyPreset(byte index, byte callMode)
return false;
}
void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj)
void savePreset(byte index, const char* pname, JsonObject saveobj)
{
if (index == 0 || (index > 250 && persist) || (index<255 && !persist)) return;
if (index == 0 || (index > 250 && index < 255)) return;
char tmp[12];
JsonObject sObj = saveobj;
bool persist = (index != 255);
const char *filename = persist ? "/presets.json" : "/tmp.json";
if (!fileDoc) {

View File

@@ -84,11 +84,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
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());
strip.setTargetFps(request->arg(F("FR")).toInt());
strip.cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(strip.cctBlending);
strip.autoWhiteMode = (request->arg(F("AW")).toInt());
Bus::setAutoWhiteMode(strip.autoWhiteMode);
strip.setTargetFps(request->arg(F("FR")).toInt());
bool busesChanged = false;
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
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
@@ -96,7 +98,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char lt[4] = "LT"; lt[2] = 48+s; lt[3] = 0; //strip type
char ls[4] = "LS"; ls[2] = 48+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = 48+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip 1st LED
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip first N LEDs
char rf[4] = "RF"; rf[2] = 48+s; rf[3] = 0; //refresh required
if (!request->hasArg(lp)) {
DEBUG_PRINTLN(F("No data.")); break;
@@ -108,7 +110,7 @@ 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;
skip = request->arg(sl).toInt();
colorOrder = request->arg(co).toInt();
start = (request->hasArg(ls)) ? request->arg(ls).toInt() : t;
if (request->hasArg(lc) && request->arg(lc).toInt() > 0) {
@@ -120,8 +122,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// actual finalization is done in WLED::loop() (removing old busses and adding new)
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder, request->hasArg(cv), skip);
doInitBusses = true;
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
ColorOrderMap com = {};
for (uint8_t s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) {
@@ -145,6 +148,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
irPin = -1;
}
irEnabled = request->arg(F("IT")).toInt();
irApplyToAllSelected = !request->hasArg(F("MSO"));
int hw_rly_pin = request->arg(F("RL")).toInt();
if (pinManager.allocatePin(hw_rly_pin,true, PinOwner::Relay)) {
@@ -196,6 +200,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (t >= 0 && t < 4) strip.paletteBlend = t;
t = request->arg(F("BF")).toInt();
if (t > 0) briMultiplier = t;
doInitBusses = busesChanged;
}
//UI
@@ -235,6 +241,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
nodeBroadcastEnabled = request->hasArg(F("NB"));
receiveDirect = request->hasArg(F("RD"));
useMainSegmentOnly = request->hasArg(F("MO"));
e131SkipOutOfSequence = request->hasArg(F("ES"));
e131Multicast = request->hasArg(F("EM"));
t = request->arg(F("EP")).toInt();
@@ -321,10 +328,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// force a sunrise/sunset re-calculation
calculateSunriseAndSunset();
if (request->hasArg(F("OL"))) {
overlayDefault = request->arg(F("OL")).toInt();
overlayCurrent = overlayDefault;
}
overlayCurrent = request->hasArg(F("OL")) ? 1 : 0;
overlayMin = request->arg(F("O1")).toInt();
overlayMax = request->arg(F("O2")).toInt();
@@ -332,10 +336,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
analogClock5MinuteMarks = request->hasArg(F("O5"));
analogClockSecondsTrail = request->hasArg(F("OS"));
#ifndef WLED_DISABLE_CRONIXIE
strlcpy(cronixieDisplay,request->arg(F("CX")).c_str(),7);
cronixieBacklight = request->hasArg(F("CB"));
#endif
countdownMode = request->hasArg(F("CE"));
countdownYear = request->arg(F("CY")).toInt();
countdownMonth = request->arg(F("CI")).toInt();
@@ -551,6 +551,8 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv)
{
if (str == nullptr || str[0] == '\0') return;
if (str[0] == 'r') {*val = random8(minv,maxv); return;}
bool wrap = false;
if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;}
if (str[0] == '~') {
int out = atoi(str +1);
if (out == 0)
@@ -563,9 +565,13 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv)
*val = (int)(*val +1) > (int)maxv ? minv : max((int)minv,(*val +1)); //+1, wrap around
}
} else {
out += *val;
if (out > maxv) out = maxv;
if (out < minv) out = minv;
if (wrap && *val == maxv && out > 0) out = minv;
else if (wrap && *val == minv && out < 0) out = maxv;
else {
out += *val;
if (out > maxv) out = maxv;
if (out < minv) out = minv;
}
*val = out;
}
} else
@@ -795,7 +801,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (pos > 0) {
byte temp;
for (uint8_t i=0; i<4; i++) {
temp = colIn[i];
temp = colIn[i];
colIn[i] = colInSec[i];
colInSec[i] = temp;
}
@@ -829,13 +835,14 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
stateChanged |= (fxModeChanged || speedChanged || intensityChanged || paletteChanged);
// apply to main and all selected segments to prevent #1618.
for (uint8_t i = 0; i < strip.getMaxSegments(); i++) {
WS2812FX::Segment& seg = strip.getSegment(i);
if (i != selectedSeg && (singleSegment || !seg.isActive() || !seg.isSelected())) continue;
if (i != selectedSeg && (singleSegment || !seg.isActive() || !seg.isSelected())) continue; // skip non main segments if not applying to all
if (fxModeChanged) strip.setMode(i, effectIn);
if (speedChanged) seg.speed = speedIn;
if (speedChanged) seg.speed = speedIn;
if (intensityChanged) seg.intensity = intensityIn;
if (paletteChanged) seg.palette = paletteIn;
if (paletteChanged) seg.palette = paletteIn;
}
//set advanced overlay
@@ -931,29 +938,17 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (pos > 0) {
realtimeOverride = getNumVal(&req, pos);
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, !realtimeOverride, strip.getMainSegmentId());
}
}
pos = req.indexOf(F("RB"));
if (pos > 0) doReboot = true;
//cronixie
#ifndef WLED_DISABLE_CRONIXIE
//mode, 1 countdown
// clock mode, 0: normal, 1: countdown
pos = req.indexOf(F("NM="));
if (pos > 0) countdownMode = (req.charAt(pos+3) != '0');
pos = req.indexOf(F("NX=")); //sets digits to code
if (pos > 0) {
strlcpy(cronixieDisplay, req.substring(pos + 3, pos + 9).c_str(), 7);
setCronixie();
}
pos = req.indexOf(F("NB="));
if (pos > 0) //sets backlight
{
cronixieBacklight = (req.charAt(pos+3) != '0');
}
#endif
pos = req.indexOf(F("U0=")); //user var 0
if (pos > 0) {

View File

@@ -11,6 +11,8 @@
// - - - - -
/* ----- LIBRARIES ----- */
#ifdef ESP8266
#include <Arduino.h>
#include "ESPDMX.h"
@@ -29,12 +31,12 @@ bool dmxStarted = false;
int sendPin = 2; //dafault on ESP8266
//DMX value array and size. Entry 0 will hold startbyte
uint8_t dmxData[dmxMaxChannel] = {};
int chanSize;
uint8_t dmxDataStore[dmxMaxChannel] = {};
int channelSize;
void DMXESPSerial::init() {
chanSize = defaultMax;
channelSize = defaultMax;
Serial1.begin(DMXSPEED);
pinMode(sendPin, OUTPUT);
@@ -48,7 +50,7 @@ void DMXESPSerial::init(int chanQuant) {
chanQuant = defaultMax;
}
chanSize = chanQuant;
channelSize = chanQuant;
Serial1.begin(DMXSPEED);
pinMode(sendPin, OUTPUT);
@@ -61,7 +63,7 @@ uint8_t DMXESPSerial::read(int Channel) {
if (Channel < 1) Channel = 1;
if (Channel > dmxMaxChannel) Channel = dmxMaxChannel;
return(dmxData[Channel]);
return(dmxDataStore[Channel]);
}
// Function to send DMX data
@@ -69,15 +71,15 @@ void DMXESPSerial::write(int Channel, uint8_t value) {
if (dmxStarted == false) init();
if (Channel < 1) Channel = 1;
if (Channel > chanSize) Channel = chanSize;
if (Channel > channelSize) Channel = channelSize;
if (value < 0) value = 0;
if (value > 255) value = 255;
dmxData[Channel] = value;
dmxDataStore[Channel] = value;
}
void DMXESPSerial::end() {
chanSize = 0;
channelSize = 0;
Serial1.end();
dmxStarted = false;
}
@@ -96,10 +98,12 @@ void DMXESPSerial::update() {
//send data
Serial1.begin(DMXSPEED, DMXFORMAT);
digitalWrite(sendPin, LOW);
Serial1.write(dmxData, chanSize);
Serial1.write(dmxDataStore, channelSize);
Serial1.flush();
delay(1);
Serial1.end();
}
// Function to update the DMX bus
#endif

View File

@@ -0,0 +1,55 @@
SparkFun License Information
============================
SparkFun uses two different licenses for our files — one for hardware and one for code.
Hardware
---------
**SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).**
Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode).
You are free to:
Share — copy and redistribute the material in any medium or format
Adapt — remix, transform, and build upon the material
for any purpose, even commercially.
The licensor cannot revoke these freedoms as long as you follow the license terms.
Under the following terms:
Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
Notices:
You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation.
No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
Code
--------
**SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT).**
The MIT License (MIT)
Copyright (c) 2016 SparkFun Electronics
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,160 @@
/******************************************************************************
SparkFunDMX.h
Arduino Library for the SparkFun ESP32 LED to DMX Shield
Andy England @ SparkFun Electronics
7/22/2019
Development environment specifics:
Arduino IDE 1.6.4
This code is released under the [MIT License](http://opensource.org/licenses/MIT).
Please review the LICENSE.md file included with this example. If you have any questions
or concerns with licensing, please contact techsupport@sparkfun.com.
Distributed as-is; no warranty is given.
******************************************************************************/
/* ----- LIBRARIES ----- */
#ifdef ESP32
#include <Arduino.h>
#include "SparkFunDMX.h"
#include <HardwareSerial.h>
#define dmxMaxChannel 512
#define defaultMax 32
#define DMXSPEED 250000
#define DMXFORMAT SERIAL_8N2
#define BREAKSPEED 83333
#define BREAKFORMAT SERIAL_8N1
int enablePin = -1; // disable the enable pin because it is not needed
int rxPin = -1; // disable the receiving pin because it is not needed
int txPin = 2; // transmit DMX data over this pin (default is pin 2)
//DMX value array and size. Entry 0 will hold startbyte
uint8_t dmxData[dmxMaxChannel] = {};
int chanSize;
int currentChannel = 0;
HardwareSerial DMXSerial(2);
/* Interrupt Timer for DMX Receive */
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
volatile int _interruptCounter;
volatile bool _startCodeDetected = false;
/* Start Code is detected by 21 low interrupts */
void IRAM_ATTR onTimer() {
if (digitalRead(rxPin) == 1)
{
_interruptCounter = 0; //If the RX Pin is high, we are not in an interrupt
}
else
{
_interruptCounter++;
}
if (_interruptCounter > 9)
{
portENTER_CRITICAL_ISR(&timerMux);
_startCodeDetected = true;
DMXSerial.begin(DMXSPEED, DMXFORMAT, rxPin, txPin);
portEXIT_CRITICAL_ISR(&timerMux);
_interruptCounter = 0;
}
}
void SparkFunDMX::initRead(int chanQuant) {
timer = timerBegin(0, 1, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 320, true);
timerAlarmEnable(timer);
_READWRITE = _READ;
if (chanQuant > dmxMaxChannel || chanQuant <= 0)
{
chanQuant = defaultMax;
}
chanSize = chanQuant;
pinMode(enablePin, OUTPUT);
digitalWrite(enablePin, LOW);
pinMode(rxPin, INPUT);
}
// Set up the DMX-Protocol
void SparkFunDMX::initWrite (int chanQuant) {
_READWRITE = _WRITE;
if (chanQuant > dmxMaxChannel || chanQuant <= 0) {
chanQuant = defaultMax;
}
chanSize = chanQuant + 1; //Add 1 for start code
DMXSerial.begin(DMXSPEED, DMXFORMAT, rxPin, txPin);
pinMode(enablePin, OUTPUT);
digitalWrite(enablePin, HIGH);
}
// Function to read DMX data
uint8_t SparkFunDMX::read(int Channel) {
if (Channel > chanSize) Channel = chanSize;
return(dmxData[Channel - 1]); //subtract one to account for start byte
}
// Function to send DMX data
void SparkFunDMX::write(int Channel, uint8_t value) {
if (Channel < 0) Channel = 0;
if (Channel > chanSize) chanSize = Channel;
dmxData[0] = 0;
dmxData[Channel] = value; //add one to account for start byte
}
void SparkFunDMX::update() {
if (_READWRITE == _WRITE)
{
//Send DMX break
digitalWrite(txPin, HIGH);
DMXSerial.begin(BREAKSPEED, BREAKFORMAT, rxPin, txPin);//Begin the Serial port
DMXSerial.write(0);
DMXSerial.flush();
delay(1);
DMXSerial.end();
//Send DMX data
DMXSerial.begin(DMXSPEED, DMXFORMAT, rxPin, txPin);//Begin the Serial port
DMXSerial.write(dmxData, chanSize);
DMXSerial.flush();
DMXSerial.end();//clear our DMX array, end the Hardware Serial port
}
else if (_READWRITE == _READ)//In a perfect world, this function ends serial communication upon packet completion and attaches RX to a CHANGE interrupt so the start code can be read again
{
if (_startCodeDetected == true)
{
while (DMXSerial.available())
{
dmxData[currentChannel++] = DMXSerial.read();
}
if (currentChannel > chanSize) //Set the channel counter back to 0 if we reach the known end size of our packet
{
portENTER_CRITICAL(&timerMux);
_startCodeDetected = false;
DMXSerial.flush();
DMXSerial.end();
portEXIT_CRITICAL(&timerMux);
currentChannel = 0;
}
}
}
}
// Function to update the DMX bus
#endif

View File

@@ -0,0 +1,38 @@
/******************************************************************************
SparkFunDMX.h
Arduino Library for the SparkFun ESP32 LED to DMX Shield
Andy England @ SparkFun Electronics
7/22/2019
Development environment specifics:
Arduino IDE 1.6.4
This code is released under the [MIT License](http://opensource.org/licenses/MIT).
Please review the LICENSE.md file included with this example. If you have any questions
or concerns with licensing, please contact techsupport@sparkfun.com.
Distributed as-is; no warranty is given.
******************************************************************************/
#include <inttypes.h>
#ifndef SparkFunDMX_h
#define SparkFunDMX_h
// ---- Methods ----
class SparkFunDMX {
public:
void initRead(int maxChan);
void initWrite(int maxChan);
uint8_t read(int Channel);
void write(int channel, uint8_t value);
void update();
private:
uint8_t _startCodeValue = 0xFF;
bool _READ = true;
bool _WRITE = false;
bool _READWRITE;
};
#endif

View File

@@ -140,24 +140,51 @@ void notify(byte callMode, bool followUp)
void realtimeLock(uint32_t timeoutMs, byte md)
{
if (!realtimeMode && !realtimeOverride){
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 0; i < totalLen; i++)
{
strip.setPixelColor(i,0,0,0,0);
if (!realtimeMode && !realtimeOverride) {
uint16_t stop, start;
if (useMainSegmentOnly) {
WS2812FX::Segment& mainseg = strip.getMainSegment();
start = mainseg.start;
stop = mainseg.stop;
mainseg.setOption(SEG_OPTION_FREEZE, true, strip.getMainSegmentId());
} else {
start = 0;
stop = strip.getLengthTotal();
}
// clear strip/segment
for (uint16_t i = start; i < stop; i++) strip.setPixelColor(i,0,0,0,0);
// if WLED was off and using main segment only, freeze non-main segments so they stay off
if (useMainSegmentOnly && bri == 0) {
for (uint8_t s=0; s < strip.getMaxSegments(); s++) {
strip.getSegment(s).setOption(SEG_OPTION_FREEZE, true, s);
}
}
}
realtimeTimeout = millis() + timeoutMs;
if (timeoutMs == 255001 || timeoutMs == 65000) realtimeTimeout = UINT32_MAX;
// if strip is off (bri==0) and not already in RTM
if (bri == 0 && !realtimeMode) {
strip.setBrightness(scaledBri(briLast));
if (briT == 0 && !realtimeMode && !realtimeOverride) {
strip.setBrightness(scaledBri(briLast), true);
}
if (realtimeTimeout != UINT32_MAX) {
realtimeTimeout = (timeoutMs == 255001 || timeoutMs == 65000) ? UINT32_MAX : millis() + timeoutMs;
}
realtimeMode = md;
if (arlsForceMaxBri && !realtimeOverride) strip.setBrightness(scaledBri(255));
if (md == REALTIME_MODE_GENERIC) strip.show();
if (realtimeOverride) return;
if (arlsForceMaxBri) strip.setBrightness(scaledBri(255), true);
if (briT > 0 && md == REALTIME_MODE_GENERIC) strip.show();
}
void exitRealtime() {
if (!realtimeMode) return;
if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE;
strip.setBrightness(scaledBri(bri));
realtimeTimeout = 0; // cancel realtime mode immediately
realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately
realtimeIP[0] = 0;
if (useMainSegmentOnly) { // unfreeze live segment again
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, false, strip.getMainSegmentId());
}
}
@@ -187,13 +214,7 @@ void handleNotifications()
}
//unlock strip when realtime UDP times out
if (realtimeMode && millis() > realtimeTimeout)
{
if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE;
strip.setBrightness(scaledBri(bri));
realtimeMode = REALTIME_MODE_INACTIVE;
realtimeIP[0] = 0;
}
if (realtimeMode && millis() > realtimeTimeout) exitRealtime();
//receive UDP notifications
if (!udpConnected) return;
@@ -652,19 +673,16 @@ void sendSysInfoUDP()
uint8_t sequenceNumber = 0; // this needs to be shared across all outputs
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW) {
if (!interfacesInited) return 1; // network not initialised
if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap
WiFiUDP ddpUdp;
switch (type) {
case 0: // DDP
{
// calclate the number of UDP packets we need to send
// calculate the number of UDP packets we need to send
uint16_t channelCount = length * 3; // 1 channel for every R,G,B value
uint16_t packetCount = channelCount / DDP_CHANNELS_PER_PACKET;
if (channelCount % DDP_CHANNELS_PER_PACKET) {
packetCount++;
}
uint16_t packetCount = ((channelCount-1) / DDP_CHANNELS_PER_PACKET) +1;
// there are 3 channels per RGB pixel
uint32_t channel = 0; // TODO: allow specifying the start channel

View File

@@ -104,10 +104,30 @@
#include "../usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h"
#endif
#ifdef USERMOD_CRONIXIE
#include "../usermods/Cronixie/usermod_cronixie.h"
#endif
#ifdef QUINLED_AN_PENTA
#include "../usermods/quinled-an-penta/quinled-an-penta.h"
#endif
#ifdef USERMOD_WIZLIGHTS
#include "../usermods/wizlights/wizlights.h"
#endif
#ifdef USERMOD_WORDCLOCK
#include "../usermods/usermod_v2_word_clock/usermod_v2_word_clock.h"
#endif
#ifdef USERMOD_MY9291
#include "../usermods/MY9291/usermode_MY9291.h"
#endif
#ifdef USERMOD_SI7021_MQTT_HA
#include "../usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h"
#endif
void registerUsermods()
{
/*
@@ -200,7 +220,27 @@ void registerUsermods()
usermods.add(new UsermodSSDR());
#endif
#ifdef USERMOD_CRONIXIE
usermods.add(new UsermodCronixie());
#endif
#ifdef QUINLED_AN_PENTA
usermods.add(new QuinLEDAnPentaUsermod());
#endif
#ifdef USERMOD_WIZLIGHTS
usermods.add(new WizLightsUsermod());
#endif
#ifdef USERMOD_WORDCLOCK
usermods.add(new WordClockUsermod());
#endif
#ifdef USERMOD_MY9291
usermods.add(new MY9291Usermod());
#endif
#ifdef USERMOD_SI7021_MQTT_HA
usermods.add(new Si7021_MQTT_HA());
#endif
}

View File

@@ -28,7 +28,7 @@ void WLED::reset()
yield(); // enough time to send response to client
}
applyBri();
DEBUG_PRINTLN(F("MODULE RESET"));
DEBUG_PRINTLN(F("WLED RESET"));
ESP.restart();
}
@@ -62,18 +62,19 @@ void prepareHostname(char* hostname)
hostname[pos] = '-';
pos++;
}
// else do nothing - no leading hyphens and do not include hyphens for all other characters.
pC++;
}
// if the hostname is left blank, use the mac address/default mdns name
if (pos < 6) {
sprintf(hostname + 5, "%*s", 6, escapedMac.c_str() + 6);
} else { //last character must not be hyphen
while (pos > 0 && hostname[pos -1] == '-') {
hostname[pos -1] = 0;
pos--;
}
// else do nothing - no leading hyphens and do not include hyphens for all other characters.
pC++;
}
// if the hostname is left blank, use the mac address/default mdns name
if (pos < 6) {
sprintf(hostname + 5, "%*s", 6, escapedMac.c_str() + 6);
} else { //last character must not be hyphen
hostname[pos] = '\0'; // terminate string
while (pos > 0 && hostname[pos -1] == '-') {
hostname[pos -1] = '\0';
pos--;
}
}
}
//handle Ethernet connection event
@@ -147,33 +148,38 @@ void WLED::loop()
yield();
handleIO();
handleIR();
#ifndef WLED_DISABLE_ALEXA
handleAlexa();
#endif
yield();
if (doReboot)
if (doReboot && !doInitBusses) // if busses have to be inited & saved, wait until next iteration
reset();
if (doCloseFile) {
closeFile();
yield();
}
if (!realtimeMode || realtimeOverride) // block stuff if WARLS/Adalight is enabled
if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) // block stuff if WARLS/Adalight is enabled
{
if (apActive)
dnsServer.processNextRequest();
#ifndef WLED_DISABLE_OTA
if (WLED_CONNECTED && aOtaEnabled)
ArduinoOTA.handle();
#endif
if (apActive) dnsServer.processNextRequest();
#ifndef WLED_DISABLE_OTA
if (WLED_CONNECTED && aOtaEnabled) ArduinoOTA.handle();
#endif
handleNightlight();
handlePlaylist();
yield();
#ifndef WLED_DISABLE_HUESYNC
handleHue();
#ifndef WLED_DISABLE_BLYNK
yield();
#endif
#ifndef WLED_DISABLE_BLYNK
handleBlynk();
#endif
yield();
#endif
yield();
@@ -270,6 +276,44 @@ void WLED::loop()
loops++;
#endif // WLED_DEBUG
toki.resetTick();
#if WLED_WATCHDOG_TIMEOUT > 0
// we finished our mainloop, reset the watchdog timer
#ifdef ARDUINO_ARCH_ESP32
esp_task_wdt_reset();
#else
ESP.wdtFeed();
#endif
#endif
}
void WLED::enableWatchdog() {
#if WLED_WATCHDOG_TIMEOUT > 0
#ifdef ARDUINO_ARCH_ESP32
esp_err_t watchdog = esp_task_wdt_init(WLED_WATCHDOG_TIMEOUT, true);
DEBUG_PRINT(F("Watchdog enabled: "));
if (watchdog == ESP_OK) {
DEBUG_PRINTLN(F("OK"));
} else {
DEBUG_PRINTLN(watchdog);
return;
}
esp_task_wdt_add(NULL);
#else
ESP.wdtEnable(WLED_WATCHDOG_TIMEOUT * 1000);
#endif
#endif
}
void WLED::disableWatchdog() {
#if WLED_WATCHDOG_TIMEOUT > 0
DEBUG_PRINTLN(F("Watchdog: disabled"));
#ifdef ARDUINO_ARCH_ESP32
esp_task_wdt_delete(NULL);
#else
ESP.wdtDisable();
#endif
#endif
}
void WLED::setup()
@@ -296,6 +340,8 @@ void WLED::setup()
DEBUG_PRINT(F("heap "));
DEBUG_PRINTLN(ESP.getFreeHeap());
enableWatchdog();
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
if (psramFound()) {
// GPIO16/GPIO17 reserved for SPI RAM
@@ -358,8 +404,8 @@ void WLED::setup()
#endif
#ifdef WLED_ENABLE_ADALIGHT
//Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused
//Serial TX (Debug, Improv, Serial JSON) only possible if GPIO1 unused
//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"));
}
@@ -383,7 +429,9 @@ void WLED::setup()
sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6);
}
#ifdef WLED_ENABLE_ADALIGHT
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
#endif
strip.service();
@@ -393,8 +441,13 @@ void WLED::setup()
#ifdef ESP8266
wifi_set_sleep_type(NONE_SLEEP_T);
#endif
WLED::instance().disableWatchdog();
DEBUG_PRINTLN(F("Start ArduinoOTA"));
});
ArduinoOTA.onError([](ota_error_t error) {
// reenable watchdog on failed update
WLED::instance().enableWatchdog();
});
if (strlen(cmDNS) > 0)
ArduinoOTA.setHostname(cmDNS);
}
@@ -403,7 +456,10 @@ void WLED::setup()
initDMX();
#endif
#ifdef WLED_ENABLE_ADALIGHT
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
#endif
// HTTP server page init
initServer();

View File

@@ -3,12 +3,12 @@
/*
Main sketch, global variable declarations
@title WLED project sketch
@version 0.13.0-b7
@version 0.13.3
@author Christian Schwinne
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2202222
#define VERSION 2208222
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -26,7 +26,6 @@
// You can choose some of these features to disable:
//#define WLED_DISABLE_ALEXA // saves 11kb
//#define WLED_DISABLE_BLYNK // saves 6kb
//#define WLED_DISABLE_CRONIXIE // saves 3kb
//#define WLED_DISABLE_HUESYNC // saves 4kb
//#define WLED_DISABLE_INFRARED // there is no pin left for this on ESP8266-01, saves 12kb
#ifndef WLED_DISABLE_MQTT
@@ -50,6 +49,12 @@
// filesystem specific debugging
//#define WLED_DEBUG_FS
#ifndef WLED_WATCHDOG_TIMEOUT
// 3 seconds should be enough to detect a lockup
// define WLED_WATCHDOG_TIMEOUT=0 to disable watchdog, default
#define WLED_WATCHDOG_TIMEOUT 0
#endif
//optionally disable brownout detector on ESP32.
//This is generally a terrible idea, but improves boot success on boards with a 3.3v regulator + cap setup that can't provide 400mA peaks
//#define WLED_DISABLE_BROWNOUT_DET
@@ -79,6 +84,7 @@
#else
#include <LittleFS.h>
#endif
#include "esp_task_wdt.h"
#endif
#include "src/dependencies/network/Network.h"
@@ -112,7 +118,11 @@
#endif
#ifdef WLED_ENABLE_DMX
#ifdef ESP8266
#include "src/dependencies/dmx/ESPDMX.h"
#else //ESP32
#include "src/dependencies/dmx/SparkFunDMX.h"
#endif
#endif
#include "src/dependencies/e131/ESPAsyncE131.h"
@@ -294,7 +304,11 @@ WLED_GLOBAL uint16_t transitionDelay _INIT(750); // default crossfade duratio
WLED_GLOBAL byte briMultiplier _INIT(100); // % of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127)
// User Interface CONFIG
WLED_GLOBAL char serverDescription[33] _INIT("WLED"); // Name of module
#ifndef SERVERNAME
WLED_GLOBAL char serverDescription[33] _INIT("WLED"); // Name of module - use default
#else
WLED_GLOBAL char serverDescription[33] _INIT(SERVERNAME); // use predefined name
#endif
WLED_GLOBAL bool syncToggleReceive _INIT(false); // UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise
// Sync CONFIG
@@ -303,7 +317,12 @@ WLED_GLOBAL bool nodeListEnabled _INIT(true);
WLED_GLOBAL bool nodeBroadcastEnabled _INIT(true);
WLED_GLOBAL byte buttonType[WLED_MAX_BUTTONS] _INIT({BTN_TYPE_PUSH});
WLED_GLOBAL byte irEnabled _INIT(0); // Infrared receiver
#if defined(IRTYPE) && defined(IRPIN)
WLED_GLOBAL byte irEnabled _INIT(IRTYPE); // Infrared receiver
#else
WLED_GLOBAL byte irEnabled _INIT(0); // Infrared receiver disabled
#endif
WLED_GLOBAL bool irApplyToAllSelected _INIT(true); //apply IR to all selected segments
WLED_GLOBAL uint16_t udpPort _INIT(21324); // WLED notifier default port
WLED_GLOBAL uint16_t udpPort2 _INIT(65506); // WLED notifier supplemental port
@@ -339,7 +358,11 @@ WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if
WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black
#ifdef WLED_ENABLE_DMX
WLED_GLOBAL DMXESPSerial dmx;
#ifdef ESP8266
WLED_GLOBAL DMXESPSerial dmx;
#else //ESP32
WLED_GLOBAL SparkFunDMX dmx;
#endif
WLED_GLOBAL uint16_t e131ProxyUniverse _INIT(0); // output this E1.31 (sACN) / ArtNet universe via MAX485 (0 = disabled)
#endif
WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes)
@@ -379,18 +402,13 @@ WLED_GLOBAL bool useAMPM _INIT(false); // 12h/24h clock format
WLED_GLOBAL byte currentTimezone _INIT(0); // Timezone ID. Refer to timezones array in wled10_ntp.ino
WLED_GLOBAL int utcOffsetSecs _INIT(0); // Seconds to offset from UTC before timzone calculation
WLED_GLOBAL byte overlayDefault _INIT(0); // 0: no overlay 1: analog clock 2: single-digit clock 3: cronixie
WLED_GLOBAL byte overlayCurrent _INIT(0); // 0: no overlay 1: analog clock 2: was single-digit clock 3: was cronixie
WLED_GLOBAL byte overlayMin _INIT(0), overlayMax _INIT(DEFAULT_LED_COUNT - 1); // boundaries of overlay mode
WLED_GLOBAL byte analogClock12pixel _INIT(0); // The pixel in your strip where "midnight" would be
WLED_GLOBAL bool analogClockSecondsTrail _INIT(false); // Display seconds as trail of LEDs instead of a single pixel
WLED_GLOBAL bool analogClock5MinuteMarks _INIT(false); // Light pixels at every 5-minute position
#ifndef WLED_DISABLE_CRONIXIE
WLED_GLOBAL char cronixieDisplay[7] _INIT("HHMMSS"); // Cronixie Display mask. See wled13_cronixie.ino
WLED_GLOBAL bool cronixieBacklight _INIT(true); // Allow digits to be back-illuminated
#endif
WLED_GLOBAL bool countdownMode _INIT(false); // Clock will count down towards date
WLED_GLOBAL byte countdownYear _INIT(20), countdownMonth _INIT(1); // Countdown target date, year is last two digits
WLED_GLOBAL byte countdownDay _INIT(1) , countdownHour _INIT(0);
@@ -497,12 +515,6 @@ WLED_GLOBAL bool hueAuthRequired _INIT(false);
WLED_GLOBAL bool hueReceived _INIT(false);
WLED_GLOBAL bool hueStoreAllowed _INIT(false), hueNewKey _INIT(false);
// overlays
WLED_GLOBAL byte overlayCurrent _INIT(overlayDefault);
// cronixie
WLED_GLOBAL byte dP[] _INIT_N(({ 255, 255, 255, 255, 255, 255 }));
// countdown
WLED_GLOBAL unsigned long countdownTime _INIT(1514764800L);
WLED_GLOBAL bool countdownOverTriggered _INIT(true);
@@ -540,6 +552,7 @@ 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);
WLED_GLOBAL bool useMainSegmentOnly _INIT(false);
// mqtt
WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0);
@@ -698,5 +711,7 @@ public:
void initConnection();
void initInterfaces();
void handleStatusLED();
void enableWatchdog();
void disableWatchdog();
};
#endif // WLED_H

View File

@@ -141,8 +141,7 @@ void loadSettingsFromEEPROM()
useAMPM = EEPROM.read(329);
strip.gammaCorrectBri = EEPROM.read(330);
strip.gammaCorrectCol = EEPROM.read(331);
overlayDefault = EEPROM.read(332);
if (lastEEPROMversion < 8 && overlayDefault > 0) overlayDefault--; //overlay mode 1 (solid) was removed
overlayCurrent = EEPROM.read(332);
alexaEnabled = EEPROM.read(333);
@@ -199,11 +198,6 @@ void loadSettingsFromEEPROM()
countdownSec = EEPROM.read(2161);
setCountdown();
#ifndef WLED_DISABLE_CRONIXIE
readStringFromEEPROM(2165, cronixieDisplay, 6);
cronixieBacklight = EEPROM.read(2171);
#endif
//macroBoot = EEPROM.read(2175);
macroAlexaOn = EEPROM.read(2176);
macroAlexaOff = EEPROM.read(2177);
@@ -365,8 +359,6 @@ void loadSettingsFromEEPROM()
//2551 - 2559 reserved for Usermods, usable by default
//2560 - 2943 usable, NOT reserved (need to increase EEPSIZE accordingly, new WLED core features may override this section)
//2944 - 3071 reserved for Usermods (need to increase EEPSIZE to 3072 in const.h)
overlayCurrent = overlayDefault;
}

View File

@@ -31,10 +31,6 @@ const ethernet_settings ethernetBoards[] = {
},
// WT32-EHT01
// Please note, from my testing only these pins work for LED outputs:
// IO2, IO4, IO12, IO14, IO15
// These pins do not appear to work from my testing:
// IO35, IO36, IO39
{
1, // eth_address,
16, // eth_power,
@@ -92,6 +88,16 @@ const ethernet_settings ethernetBoards[] = {
18, // eth_mdio,
ETH_PHY_LAN8720, // eth_type,
ETH_CLOCK_GPIO17_OUT // eth_clk_mode
},
// ESP32-ETHERNET-KIT-VE
{
0, // eth_address,
5, // eth_power,
23, // eth_mdc,
18, // eth_mdio,
ETH_PHY_IP101, // eth_type,
ETH_CLOCK_GPIO0_IN // eth_clk_mode
}
};
#endif

View File

@@ -18,6 +18,7 @@
float cos_t(float phi)
{
float x = modd(phi, TWO_PI);
if (x < 0) x = -1 * x;
int8_t sign = 1;
if (x > PI)
{

View File

@@ -86,8 +86,8 @@ void handleSerial()
Serial.write(0xC9); Serial.write(0xDA);
uint16_t used = strip.getLengthTotal();
uint16_t len = used*3;
Serial.write((len << 8) & 0xFF);
Serial.write( len & 0xFF);
Serial.write(highByte(len));
Serial.write(lowByte(len));
for (uint16_t i=0; i < used; i++) {
uint32_t c = strip.getPixelColor(i);
Serial.write(qadd8(W(c), R(c))); //R, add white channel to RGB channels as a simple RGBW -> RGB map
@@ -174,7 +174,6 @@ void handleSerial()
if (!realtimeOverride) setRealtimePixel(pixel++, red, green, blue, 0);
if (--count > 0) state = AdaState::Data_Red;
else {
if (!realtimeMode && bri == 0) strip.setBrightness(briLast);
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_ADALIGHT);
if (!realtimeOverride) strip.show();

View File

@@ -203,6 +203,7 @@ void initServer()
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
if(!index){
DEBUG_PRINTLN(F("OTA Update Start"));
WLED::instance().disableWatchdog();
#ifdef ESP8266
Update.runAsync(true);
#endif
@@ -214,6 +215,7 @@ void initServer()
DEBUG_PRINTLN(F("Update Success"));
} else {
DEBUG_PRINTLN(F("Update Failed"));
WLED::instance().enableWatchdog();
}
}
});

View File

@@ -136,14 +136,15 @@ bool sendLiveLedsWs(uint32_t wsClient)
uint16_t used = strip.getLengthTotal();
uint16_t n = ((used -1)/MAX_LIVE_LEDS_WS) +1; //only serve every n'th LED if count over MAX_LIVE_LEDS_WS
AsyncWebSocketMessageBuffer * wsBuf = ws.makeBuffer(2 + (used*3)/n);
uint16_t bufSize = 2 + (used/n)*3;
AsyncWebSocketMessageBuffer * wsBuf = ws.makeBuffer(bufSize);
if (!wsBuf) return false; //out of memory
uint8_t* buffer = wsBuf->get();
buffer[0] = 'L';
buffer[1] = 1; //version
uint16_t pos = 2;
for (uint16_t i= 0; i < used; i += n)
for (uint16_t i= 0; pos < bufSize -2; i += n)
{
uint32_t c = strip.getPixelColor(i);
buffer[pos++] = qadd8(W(c), R(c)); //R, add white channel to RGB channels as a simple RGBW -> RGB map

View File

@@ -382,9 +382,9 @@ void getSettingsJS(byte subPage, char* dest)
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("FR"),strip.getTargetFps());
sappend('v',SET_F("AW"),Bus::getAutoWhiteMode());
sappend('v',SET_F("CB"),strip.cctBlending);
sappend('v',SET_F("FR"),strip.getTargetFps());
sappend('v',SET_F("AW"),strip.autoWhiteMode);
for (uint8_t s=0; s < busses.getNumBusses(); s++) {
Bus* bus = busses.getBus(s);
@@ -409,7 +409,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',co,bus->getColorOrder());
sappend('v',ls,bus->getStart());
sappend('c',cv,bus->reversed);
sappend('c',sl,bus->skippedLeds());
sappend('v',sl,bus->skippedLeds());
sappend('c',rf,bus->isOffRefreshRequired());
}
sappend('v',SET_F("MA"),strip.ablMilliampsMax);
@@ -462,6 +462,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',SET_F("TT"),touchThreshold);
sappend('v',SET_F("IR"),irPin);
sappend('v',SET_F("IT"),irEnabled);
sappend('c',SET_F("MSO"),!irApplyToAllSelected);
}
if (subPage == 3)
@@ -492,6 +493,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',SET_F("NB"),nodeBroadcastEnabled);
sappend('c',SET_F("RD"),receiveDirect);
sappend('c',SET_F("MO"),useMainSegmentOnly);
sappend('v',SET_F("EP"),e131Port);
sappend('c',SET_F("ES"),e131SkipOutOfSequence);
sappend('c',SET_F("EM"),e131Multicast);
@@ -574,16 +576,13 @@ void getSettingsJS(byte subPage, char* dest)
sprintf_P(tm, PSTR("Sunrise: %02d:%02d Sunset: %02d:%02d"), hour(sunrise), minute(sunrise), hour(sunset), minute(sunset));
sappends('m',SET_F("(\"times\")[1]"),tm);
}
sappend('i',SET_F("OL"),overlayCurrent);
sappend('c',SET_F("OL"),overlayCurrent);
sappend('v',SET_F("O1"),overlayMin);
sappend('v',SET_F("O2"),overlayMax);
sappend('v',SET_F("OM"),analogClock12pixel);
sappend('c',SET_F("OS"),analogClockSecondsTrail);
sappend('c',SET_F("O5"),analogClock5MinuteMarks);
#ifndef WLED_DISABLE_CRONIXIE
sappends('s',SET_F("CX"),cronixieDisplay);
sappend('c',SET_F("CB"),cronixieBacklight);
#endif
sappend('c',SET_F("CE"),countdownMode);
sappend('v',SET_F("CY"),countdownYear);
sappend('v',SET_F("CI"),countdownMonth);