Compare commits

...

216 Commits

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

* Missing json.cpp changes.

* Async fix.

* Added conditional compile (WLED_USE_DYNAMIC_JSON).

* Advanced locking with time-out.

* Missing releaseJSONBufferLock() on error response.

* Fix for config saving.

* Fixes and optimisations.
Dadded debugging information.

* Fix for ledmaps.

* No unsolicited serial sending if GPIO1 allocated

* Stray semicolons

* Fix JSON ledmap

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

* Improv functional

* Cast fix

* Minor fix for two back-to-back Improv packets

* Improv checksum update and logic simplification

* Improved improv failed connection behavior
2021-11-16 23:20:26 +01:00
Blaz Kristan
eb92c0bbf5 Fix for ACCURATE auto-white mode. 2021-11-11 17:34:38 +01:00
Jeff Cooper
6df64d0d31 Fix a bug which prevents DMX channel mappings from loading correctly. (#1525) (#2328) 2021-11-10 00:29:04 +01:00
cschwinne
83753a5f81 Fixed no gamma correction for JSON individual LED control 2021-11-09 17:50:29 +01:00
Blaz Kristan
3161f5fa47 Fix for network 'pin' conflicts. 2021-11-09 17:46:05 +01:00
Christian Schwinne
5784092c1b Fix settings JS buffer too small (#2323) 2021-11-09 09:56:02 +01:00
Blaž Kristan
d6ad089c60 Merge pull request #2324 from yoeywire/DMXdefine
Corrected WLED_USE_DMX to WLED_ENABLE_DMX
2021-11-09 07:57:41 +01:00
yoeywire
446b4b084c Changed WLED_USE_DMX to WLED_ENABLE_DMX 2021-11-08 17:05:47 +01:00
Blaz Kristan
d590e01a58 Merge branch 'master' into CCT-support 2021-11-05 21:27:52 +01:00
Shaun Eccles-Smith
adeb9bccb1 Use New Issue Forms for Bug Reports (#2312)
* Convert to Github Issue Forms

* Remove pre-filled title

* Change bug report from textarea to input
2021-11-04 12:23:53 +01:00
cschwinne
b44ffffed8 Fixed DDP override 2021-11-03 16:14:01 +01:00
Christian Schwinne
2bdaf53ecf Merge pull request #2311 from underritoSR/master
Add MX-CST/CDT Time Zone
2021-11-03 09:14:22 +01:00
Rod Minor
46e7db6d94 Add MX-CST/CDT Time Zone
Add the Mexico City CST/CDT timezone.
The start and finish date for DLS differs with the US-CST/CDT rules.
2021-11-02 21:26:11 -06:00
Christian Schwinne
7e1920dc4b Remove ledCount (#2300)
Bus initialization on reading from eeprom
2021-10-31 11:57:41 +01:00
Blaž Kristan
a93f05c047 Multirelay button support. (#2284)
* Multirelay button support.
Added button hook for usermods.

* Added MultiRelay relay states to JSON state object

* Move button timings to constants

No delay waiting for double press on button 0 if no macro set

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
2021-10-31 11:57:03 +01:00
Christian Schwinne
00238247cd JSON in/decrementing (#2258)
* Initial JSON in/decrementing

* Segment brightness in/decrement

* Update json-increment (#2290)

* Add Basic Overlay support to Usermods.

* Add seven segment overlay usermod

* Add seven_seg debug build

* Add scrolling message to seven seg um

* Fixed red color on IP address

* bh1750

* Add msg scroll. Add MQTT and Config support

* Add readme

* Restore platformio.inii

* Edit comments

* Add strip off refresh option in LED settings. (#2259)

* Add strip off refresh option in LED settings.
New strip initialization logic.
Minor code clen-up.

* Dev code removal.

* Missing ethernet include

* Renamed mainseg to selseg

* Fix for preset cycling bounds.

* "Preset 0" bugfix.

* Auto segments only if segments were not modified

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>

* Exclude virtual busses from current calculation (#2262)

* Refactor string usage

* 0.13.0-b4

* Fix MQTT Null publish

* Additional Flash string concat

* Add AKST/AKDT

* UM RGB-Rotary-Encoder: Properly used PinOwner

* Cycling bugfix.

Co-authored-by: Gregory Schmidt <gregory.b.schmidt@hotmail.com>
Co-authored-by: Blaž Kristan <blaz@kristan-sp.si>
Co-authored-by: Caleb Mah <calebmah@gmail.com>
Co-authored-by: ezcGman <ich@andy-hofmann.com>

* Working JSON preset cycle

* Fix some Codacy style issues

Co-authored-by: Gregory Schmidt <gregory.b.schmidt@hotmail.com>
Co-authored-by: Blaž Kristan <blaz@kristan-sp.si>
Co-authored-by: Caleb Mah <calebmah@gmail.com>
Co-authored-by: ezcGman <ich@andy-hofmann.com>
2021-10-30 14:42:17 +02:00
Andy Hofmann
b33e28835d New Usermod: QuinLED-An-Penta (#2296)
* UM QuinLED-An-Penta: First version

* UM QuinLED-An-Penta: Made OLED seconds a setting; small improvements

* UM QuinLED-An-Penta: Fixed unique ID

* Merge branch 'master' of https://github.com/Aircoookie/WLED

* UM QuinLED-An-Penta: Fixed config loading

* UM QuinLED-An-Penta: Replaced ledcRead() with calculating the percentage

* UM QuinLED-An-Penta: Fixed temp sensor readings

* UM QuinLED-An-Penta: Removing OLED bus clk setting

* UM QuinLED-An-Penta: ETH support, lots of OLED improvements
2021-10-30 11:38:09 +02:00
Blaž Kristan
f55f803531 Updated aut-white calculation. 2021-10-27 14:02:48 +02:00
Blaz Kristan
8ca298b299 Removed legacy auto-white calculation.
Introduced color mangling macros.
Minor optimizations/fixes.
2021-10-26 19:17:42 +02:00
Blaz Kristan
090e29effd Moved auto white calculation to bus manager.
Other minor fixes.
2021-10-25 20:15:42 +02:00
Blaz Kristan
0acca2e313 Cycling bugfix. 2021-10-22 23:24:41 +02:00
Blaz Kristan
0d77027f60 Bugfix for white value. 2021-10-22 07:21:47 +02:00
Blaz Kristan
39b7b3ad53 CCT (color white balance support) 2021-10-20 20:29:13 +02:00
Blaž Kristan
00f1b483eb Merge pull request #2279 from ezcGman/um-rgb-rotary-encoder
UM RGB Rotary Encoder: Now properly using the PinOwner class and unique ID
2021-10-17 14:16:18 +02:00
ezcGman
c3d48acb4c UM RGB-Rotary-Encoder: Properly used PinOwner 2021-10-17 01:24:23 +02:00
Blaž Kristan
392bda7d8c Merge pull request #2261 from AK5nowman/overlayum
Basic Usermod Overlay support & Seven Segment UM
2021-10-13 06:14:32 +02:00
Blaž Kristan
10cfcdab8c Merge pull request #2264 from AK5nowman/Alaskatime
Add AKST/AKDT Time Zone
2021-10-11 07:37:52 +02:00
Gregory Schmidt
3f71d3b250 Add AKST/AKDT 2021-10-10 19:09:48 -08:00
Gregory Schmidt
1b50fbab22 Additional Flash string concat 2021-10-10 17:24:36 -08:00
Gregory Schmidt
303fc65a6a Merge branch 'master' of https://github.com/Aircoookie/WLED into overlayum 2021-10-10 17:06:12 -08:00
Gregory Schmidt
445b6ee13f Fix MQTT Null publish 2021-10-10 17:05:55 -08:00
cschwinne
8afaac1e30 0.13.0-b4 2021-10-11 02:42:58 +02:00
Gregory Schmidt
0327f9428e Merge branch 'master' of https://github.com/Aircoookie/WLED into overlayum 2021-10-10 16:27:47 -08:00
Gregory Schmidt
a5de66bbd5 Merge branch 'overlayum' of https://github.com/AK5nowman/WLED into overlayum 2021-10-10 16:26:42 -08:00
Gregory Schmidt
d47157eec3 Refactor string usage 2021-10-10 16:26:14 -08:00
Christian Schwinne
f4b47ed399 Exclude virtual busses from current calculation (#2262) 2021-10-11 02:19:53 +02:00
Blaž Kristan
8b2145bd88 Add strip off refresh option in LED settings. (#2259)
* Add strip off refresh option in LED settings.
New strip initialization logic.
Minor code clen-up.

* Dev code removal.

* Missing ethernet include

* Renamed mainseg to selseg

* Fix for preset cycling bounds.

* "Preset 0" bugfix.

* Auto segments only if segments were not modified

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
2021-10-11 02:19:33 +02:00
Christian Schwinne
de454e8b78 Edit comments 2021-10-11 01:29:13 +02:00
Gregory Schmidt
6cd770b4c7 Restore platformio.inii 2021-10-09 11:29:41 -08:00
Gregory Schmidt
355525c248 Add readme 2021-10-09 11:14:52 -08:00
Gregory Schmidt
47d4e7381f Merge 'master' of Aircoookie/WLED into overlayum 2021-10-08 00:00:09 -08:00
Gregory Schmidt
5dac6690d7 Add msg scroll. Add MQTT and Config support 2021-10-07 23:56:57 -08:00
Christian Schwinne
b89f7180db Merge pull request #2257 from calebmah/bh1750-usermod
Add BH1750 usermod
2021-10-07 23:52:48 +02:00
Caleb Mah
2ebb837a15 bh1750 2021-10-08 02:11:39 +08:00
Blaž Kristan
849aa64678 Fixed red color on IP address 2021-10-07 14:11:47 +02:00
cschwinne
cbb12e1b7c Updated vid and changelog 2021-10-06 20:46:49 +02:00
Christian Schwinne
cc87ba4962 Merge pull request #2245 from Aircoookie/network-bus
Network bus/virtual WLED instances
2021-10-06 20:05:32 +02:00
Blaz Kristan
fb2e556726 Allow playlist as end preset in playlist.
Playlist chaining.
2021-10-06 19:01:56 +02:00
Blaz Kristan
3f0eb0a046 Code optimization, updated URL links. 2021-10-06 16:29:04 +02:00
cschwinne
7d6d9eddc4 Change virtual bus type range 2021-10-06 14:30:41 +02:00
Blaž Kristan
cf87da0ef3 Minor UI fixes. 2021-10-06 08:37:27 +02:00
Blaž Kristan
0775acedc0 Merge pull request #2250 from Proto-molecule/master
Fix Glyphs for 4 line display
2021-10-04 21:15:19 +02:00
Blaz Kristan
8f1cee2e61 Fixed mem calculation. 2021-10-04 19:44:46 +02:00
Blaz Kristan
caa9cc32d7 Removed double buffer.
Moved bri scaling into UDP function.
Prevent double DDP port allocation.
2021-10-04 19:41:20 +02:00
Blaž Kristan
b750f827c5 Merge pull request #2168 from itCarl/usermod_battery_status
Updated Usermod Battery Status Basic
2021-10-04 14:56:33 +02:00
Blaž Kristan
5d147163e5 Merge pull request #2251 from blazoncek/pwm-fan-usermod
Pwm fan usermod enhancement
2021-10-04 13:12:22 +02:00
Blaž Kristan
75fe1a19eb Merge master 2021-10-04 13:01:06 +02:00
Blaž Kristan
5c9405fffc Added configurable PWM fan parameters:
- min PWM value (%)
- IRQs per rotation
2021-10-04 11:48:03 +02:00
Proto-molecule
6457314794 Glyph fix for 4 line 2021-10-03 21:23:52 -07:00
Proto-molecule
84f4e3eedc Merge branch 'Aircoookie:master' into master 2021-10-03 18:13:01 -07:00
Blaž Kristan
b003ed3f03 PWM fan with temperature control usermod (#2246)
* PWM fan with temperature control usermod

* Fix for incorrect RPM reported.
2021-10-03 23:34:21 +02:00
cschwinne
330da137db Fixed virtual getPixelColor() returning scaled values 2021-10-03 22:01:50 +02:00
cschwinne
9e5d45d0de Optional custom start indices 2021-10-03 20:48:08 +02:00
Blaz Kristan
b5c15d97fa Fix for incorrect RPM reported. 2021-10-03 14:01:05 +02:00
cschwinne
6ddcba8917 Change currentPreset to byte
JSON API still returns -1 for no preset
2021-10-03 13:53:37 +02:00
cschwinne
91598cbbbf Remove Total LEDs field 2021-10-03 12:23:24 +02:00
Blaz Kristan
772c80aa85 PWM fan with temperature control usermod 2021-10-03 10:33:17 +02:00
Proto-molecule
a28345d858 Merge branch 'Aircoookie:master' into master 2021-10-02 12:33:02 -07:00
Proto-molecule
05b532b9eb Add new Usermod (#2244) 2021-10-02 20:19:12 +02:00
cschwinne
0b0d18f182 Fix preset variable 2021-10-02 20:10:52 +02:00
Blaz Kristan
c1b0877956 Bus implementation.
Added separate DDP listener.
LED settings overhaul.
Minor fixes:
- reduced LED memory
- boot brightness fix
- reduced debug frequency
- added usermod time spent debug
- mDNS glitch fix
2021-10-02 15:07:02 +02:00
Blaz Kristan
46b66c76ef Merge pbolduc/WLED/feature/upd-ddp-send into network-bus 2021-10-02 10:48:48 +02:00
Gregory Schmidt
d00b4335b5 Add scrolling message to seven seg um 2021-10-01 21:34:20 -08:00
Proto-molecule
7a129e6de1 Add new Usermod 2021-10-01 20:46:58 -07:00
cschwinne
17c20276a9 Make sbuff local
This should save 4 bytes per ESPAsyncE131 instance
2021-10-01 20:26:23 +02:00
Blaž Kristan
dc9dedf220 Fixed pin reservations. (#2214)
* Fixed pin reservations.
Added ethernet pin reservations.
Minor tweaks in usermods.

* Optional ADA compile (not default, free GPIO3 use)

* Move ethernet definitions

Remove pin 3 used check

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
2021-09-30 16:30:44 +02:00
Gregory Schmidt
3ac772badc Add seven_seg debug build 2021-09-29 20:59:02 -08:00
Gregory Schmidt
22fc58d93b Add seven segment overlay usermod 2021-09-29 20:01:26 -08:00
Gregory Schmidt
ccd3152b24 Add Basic Overlay support to Usermods. 2021-09-29 19:23:32 -08:00
Blaž Kristan
7d929dcde6 Merge pull request #2236 from scottrbailey/ir_bri_fix
Add colorUpdated call
2021-09-29 17:44:20 +02:00
Scott Bailey
3a874bc8c7 Add colorUpdated call 2021-09-28 09:56:00 -07:00
cschwinne
8453cd82e9 Fixed DMXmap 2021-09-27 22:51:40 +02:00
cschwinne
f62e56b7ec Merge branch 'master' of https://github.com/Aircoookie/WLED 2021-09-27 14:20:41 +02:00
cschwinne
2ac90bbb96 Fixed sunrise/set UTC offset 2021-09-27 14:20:21 +02:00
Blaž Kristan
f85f2d5d22 Merge pull request #2203 from protoplasma-mx/master
Updated links in files generated by cdata.js
2021-09-26 20:20:23 +02:00
Blaz Kristan
a94269ceb9 Novosibirsk time-zone. 2021-09-26 17:21:32 +02:00
Blaž Kristan
476ac263fb Merge pull request #2227 from blazoncek/master
Add "on":true to playlist JSON
2021-09-24 16:07:01 +02:00
Blaž Kristan
51a4f61a8f Add "on":true to playlist JSON 2021-09-24 12:20:20 +02:00
Blaz Kristan
267f5159a3 Wled math bugfix. 2021-09-23 20:38:50 +02:00
cschwinne
96422de031 0.13.0-b3 2021-09-21 23:37:35 +02:00
Blaž Kristan
f2043dc181 Merge pull request #2219 from blazoncek/analog-invert
Fix for missing inverted analog.
2021-09-21 12:19:15 +02:00
Blaž Kristan
e416ec9279 Removed dev types. 2021-09-21 12:05:28 +02:00
Blaž Kristan
5eb4ffb1cc Fix for missing inverted analog. 2021-09-21 11:59:23 +02:00
Christian Schwinne
8fae964ee8 Allocate segment data based on currently active segments (#2217) 2021-09-20 21:22:50 +02:00
cschwinne
baf49b88f4 Semi-working segment on/off transition 2021-09-20 21:22:04 +02:00
Phil Bolduc
3577da05ac Avoid redundant localIP calls, each call takes 0.700 us on ESP32 240Mhz (#2206)
* Avoid redundant localIP calls, each call takes 0.700 us on ESP32 240Mhz

* Fall through to check Wifi local ip if not connected to ETH

* Changed local var from ipAddress to localIP to better reflect content
2021-09-19 19:51:54 +02:00
Christian Schwinne
b8e8028eb9 Merge pull request #2184 from Aircoookie/fx-mods
FX optimisations and segment names
2021-09-19 15:16:23 +02:00
Gabriel
a899666e68 Merge branch 'master' of https://github.com/protoplasma-mx/WLED 2021-09-18 09:04:54 -05:00
Gabriel
10a52f8cf9 Updated cdata.js so generated files point to knoWLEDge 2021-09-18 09:02:37 -05:00
Gabriel
72d04a0120 Updated reference to docs on readme to knoWLEDge 2021-09-18 00:23:51 -05:00
Gabriel
6dbed30008 Updated Web UI dev docs reference 2021-09-18 00:03:59 -05:00
cschwinne
c5eac298e6 Do not delete name if segment bounds are unchanged 2021-09-18 01:20:17 +02:00
cschwinne
bc18eda336 Segment name UI changes
Text field accessible by edit icon
Replaced magnifier with iconfont
Use woff2 font format
Fix scaled checkboxes visible in UI settings header
2021-09-18 00:31:39 +02:00
Blaž Kristan
3cefb14297 Merge pull request #2195 from scottrbailey/sp511
add env for sp511
2021-09-16 08:53:47 +02:00
Scott Bailey
4bc401278e add env for sp511 2021-09-15 18:44:09 -07:00
cschwinne
d7e3765efe Fix segment creation 2021-09-14 23:35:04 +02:00
Christian Schwinne
3d51d1e345 Merge pull request #2175 from henrygab/revert_pr1902
Revert changes from PR1902
2021-09-14 00:29:59 +02:00
cschwinne
bd23942893 Fixed IR JSON cmd string (closes #2187 ) 2021-09-12 01:37:41 +02:00
cschwinne
c8610b8ad2 Small improvements to segment names 2021-09-12 01:15:51 +02:00
Blaz Kristan
d0440122b9 Bugfix for AutoSave & 4LD. 2021-09-12 01:08:19 +02:00
Christian Schwinne
8d4636bbab Merge pull request #2170 from scottrbailey/error-12-fix
Fix error 12 issues
2021-09-11 14:32:06 +02:00
cschwinne
f59c6e7a7c Replace wiki link in readme to knoWLEDge 2021-09-11 11:18:39 +02:00
Christian Schwinne
c24ab1b21d Auto create segments setting (#2183) 2021-09-11 01:17:42 +02:00
cschwinne
6a01658355 Use pbolduc fork of AsyncTCP
(fixing flicker with upstream AsyncTCP v1.1.1)
2021-09-09 17:50:59 +02:00
cschwinne
f1e2439e66 Slight IR JSON simplefication
Check for missing file
No duplicate cmd object
2021-09-09 12:05:02 +02:00
Blaz Kristan
4d89ed701d FX optimisations.
Added segment names.
2021-09-08 23:10:54 +02:00
Blaz Kristan
e80594d61d Bugfix (sclPin, sdaPin).
Removed unnecessary static_cast.
2021-09-07 17:03:46 +02:00
cschwinne
83c6f72eb0 Fix segment runtime not reset on FX change via HTTP API 2021-09-05 01:28:00 +02:00
cschwinne
e26299b998 Revert some small syntactical changes 2021-09-05 00:39:47 +02:00
Scott Bailey
a839809eb8 change random mode choice on presetFallback 2021-09-03 00:14:07 -07:00
Scott Bailey
88ceba59cf Fix error 12 issues 2021-09-02 22:56:49 -07:00
itCarl
f368bbec32 added MQTT support, Battery voltage to Info, circuit diagram to readme, minor fixes 2021-09-02 15:41:19 +02:00
Henry Gabryjelski
021c4ba68a Revert changes from PR1902 2021-08-29 11:49:06 -07:00
Christian Schwinne
54f4658dae Added JSON API over serial support (#2156)
* Added JSON API over serial support

* Disable Serial API if pin 3 is used

Disable serial response if pin 1 is used
2021-08-26 11:04:27 +02:00
Blaž Kristan
dbc67e077d Merge pull request #2134 from scottrbailey/sr_palettes
Add new palettes from SR branch
2021-08-26 06:52:56 +02:00
Scott Bailey
e968917dbc rename palette arrays 2021-08-25 10:16:30 -07:00
Scott Bailey
d8240bb683 Changing some palette names 2021-08-25 09:17:03 -07:00
Blaž Kristan
b481c13829 Sync groups (#2150)
* Added UDP sync groups.

* Shortened string.

* Changed sync default to group 1 only.

* Make packets with version < 9 group 1

* Send sync group options as bytes, parse in JS

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
2021-08-25 16:39:12 +02:00
Blaž Kristan
77c0ba990d Bugfix for calling FX=~ from within playlist preset. 2021-08-24 06:10:59 +02:00
Henry Gabryjelski
1d4487b6cd Ethernet configuration fix, improve PinManager (#2123)
* Improved pin manager, ethernet config

* Ethernet is configured prior even to LED pins
* Pin Manager allocation / deallocation functions
   now take an "ownership" tag parameter, helping
   avoid accidentally free'ing pins that were allocated
   by other code
* Pin Manager now has ability to allocate multiple
  pins at once; Simplifies error handling

* Fix operator precedence error

Bitwise AND has lower precedence than the
relational "greater than" operator.

* PinManager update for some user modules

* don't build everything...

* Final step to reduce RAM overhead

* update comment

* remove macros

* Remove leftover allocated

* Init ethernet after settings saved

Co-authored-by: Christian Schwinne <dev.aircoookie@gmail.com>
2021-08-23 14:14:48 +02:00
Blaž Kristan
ff8145b745 Merge pull request #2101 from blazoncek/fix-mqtt-pir
Fix for missing off-only MQTT messages.
2021-08-21 11:10:41 +02:00
Blaz Kristan
530e8b39e5 Added SSD1306 SPI display option to 4 Line Display 2021-08-20 23:58:09 +02:00
Blaz Kristan
50aeee288b Merge branch 'master' of https://github.com/aircoookie/WLED 2021-08-20 23:22:53 +02:00
Blaz Kristan
72e001b0d5 Bash and Wnindows CMD scripts for updating multiple WLEDs. 2021-08-20 23:20:04 +02:00
Maximilian Mewes
f04c9d101e Added usermod "battery status basic" (#2127)
* added usermod battery_status_basic

* test.. something is wrong

* Squashed commit of the following:

commit 0f845527c53f838e2c68d50ec3e9d6c68c4cee46
Author: itCarl <mewes.maximilian@gmx.de>
Date:   Tue Aug 10 18:35:15 2021 +0200

    updated readme and added image showing info modal

commit 055579fcf71796519d00566452030f31798121d0
Author: itCarl <mewes.maximilian@gmx.de>
Date:   Mon Aug 9 20:53:07 2021 +0200

    small map function fix

commit 811614cf9e73f4731acb234d0d210a7b19565e9a
Author: itCarl <mewes.maximilian@gmx.de>
Date:   Mon Aug 9 19:35:21 2021 +0200

    updated ui

commit cadf2e23b7
Author: itCarl <mewes.maximilian@gmx.de>
Date:   Mon Aug 9 16:07:32 2021 +0200

    added usermod battery_status_basic

* updated readme, changed USERMOD_BATTERY_MIN_VOLTAGE default to 2.6 volt

* fixed readme image file naming

* added usermod settings for runtime changes

* fixed copy and paste mistake

* undo ui changes

* reworked addToJsonInfo() to make it compatible with the standard Info page.

* removed images from readme

* added ESP32 support

* updated readme
2021-08-20 20:42:46 +02:00
Ahmed Shehata
2ecc53ba56 UDP Signal color correction (#1902)
* added ui changes for saturation in sync

* added setters/getters for hsv settings

* added color correction logic

* faster algorithm for color conversion

* added save/load config to fs

* adjusted value scale

* move color functions to colors.cpp

* remove unchecked file

* Various small changes

Moved settings location in sync settings
Changed wording from hyperion to live
Moved code into setRealtimePixel(), reducing duplication and enabling the functionality for DMX streams

Co-authored-by: Christian Schwinne <dev.aircoookie@gmail.com>
2021-08-19 18:24:41 +02:00
Christian Schwinne
3eb1fe0eb2 Merge pull request #2144 from coliss86/patch-1
Fix formatting of the first title
2021-08-19 08:44:43 +02:00
coliss86
aec998acc1 Fix formatting of the first title 2021-08-18 22:21:31 +02:00
cschwinne
91e758f66f Fixed JSON IR remote not working with codes greater than 0xFFFFFF (fixes #2135) 2021-08-18 02:10:40 +02:00
cschwinne
441416b241 Fixed edge case with transition 0 2021-08-18 01:59:01 +02:00
Christian Schwinne
e541d8697e Merge pull request #2140 from Aircoookie/ws-ping
Added application level pong websockets reply (#2139)
2021-08-17 17:56:29 +02:00
cschwinne
4b817208aa Added application level pong websockets reply (#2139) 2021-08-17 12:47:01 +02:00
Scott Bailey
7fea0c3244 Add new palettes from SR branch 2021-08-12 12:58:51 -07:00
cschwinne
bd13336256 Fixed undesirable boot color transition 2021-08-06 02:08:36 +02:00
Christian Schwinne
815940913b Merge pull request #2113 from tschundler/master
Fix ordering in platformio_override.ini.sample
2021-08-03 22:17:43 +02:00
Ted Schundler
f7191c0381 Fix ordering platformio_override.ini.sample
The flag examples must be after the build_flags line to be usable.
2021-08-01 20:23:05 -07:00
Blaz Kristan
07d11c845c Fix for missing off-only MQTT messages. 2021-07-28 22:50:29 +02:00
Blaž Kristan
2e9bd477d9 Upload files & skinning (#2084)
* Skinning WLED & uploading files.
Backup & restore configuration & presets.
External holidays.json

* Option for segment count instead of stop.

* Small fixes and improvements

* Further improvements

* Enable custom CSS by default

Co-authored-by: Christian Schwinne <dev.aircoookie@gmail.com>
2021-07-26 00:10:36 +02:00
Christian Schwinne
b058fb8db4 Merge pull request #2093 from blazoncek/PIR-sensor-update
Added PIR  option to trigger only if WLED is off.
2021-07-23 19:35:57 +02:00
Blaz Kristan
9f0f6181a1 Added PIR option to trigger only if WLED is off. 2021-07-23 18:43:51 +02:00
Christian Schwinne
f702e1a80d Merge pull request #2091 from blazoncek/white-slider-fix
White slider fix.
2021-07-22 23:55:12 +02:00
Blaz Kristan
e1527fcbb9 White slider fix. 2021-07-22 15:36:33 +02:00
Blaž Kristan
9ba7e5d567 Fix for not honouring enabled state for PIR usermod. (#2090) 2021-07-22 14:41:11 +02:00
Blaž Kristan
02b6d53544 Rotary Encoder Compilation fix. (#2085)
* Compilation fix.

* Make rotary encoder usermod runtime configurable.
2021-07-20 13:41:30 +02:00
cschwinne
123bd0bb92 v0.13.0-b2
Reduced unneeded websockets pushes
2021-07-11 02:38:31 +02:00
cschwinne
6a8ed1192f Accept hex color strings in individual LED API
Version bump to 0.13.0-b2 "Toki"
Fixed transition property not applying unless power/bri/color changed next
Moved transition field below segments (temporarily)
2021-07-10 16:14:17 +02:00
Andy Hofmann
0862859f93 New usermod: Support for RGB Rotary Encoder Board (#2068)
* Added RGB Rotary Encoder usermod v1

* RGB rotary encoder UM: Readme; Added example video

* RGB rotary encoder UM: Readme; Added example video

* RGB rotary encoder UM: Fixed getJsonValue usage

* RGB rotary encoder UM: Removed spaces in JSON keys

* RGB rotary encoder UM: Cleanup readFromConfig

* RGB rotary encoder UM: Cleaned up type usages

* RGB rotary encoder UM: Fixed crash on re-enable
2021-07-09 20:25:35 +02:00
Henry Gabryjelski
3ad336a1eb Bug 2064, 2063 - PinManager usage (#2066)
* Fix 2063 - Do not free pins unless allocated

* Fix 2064: Allocate pins used for Ethernet

* Fix obvious compilation errors.

* Fix multiple bugs...

* pinsAllocated[2] set twice due to copy/paste bug.
* wrong pin allocated for ETH_CLOCK_GPIO17_OUT due to copy/paste bug

* Stylistic change per PR review

* Stylistic change per PR review

* attempt to allocate pin for "default" button

* remove extra local variable

* check return value from ETH.begin()

Co-authored-by: Christian Schwinne <dev.aircoookie@gmail.com>
2021-07-09 20:06:48 +02:00
cschwinne
a17f83cedd Renamed NOTIFIER_CALL_MODE_ to CALL_MODE_ 2021-07-09 18:54:28 +02:00
cschwinne
2c6850f6e4 Fixed presets using wrong call mode (e.g. causing buttons to send UDP under direct change type)
Increased hue buffer
2021-07-09 18:42:52 +02:00
cschwinne
5da47636cf Busses extend total configured LEDs if required (closes #2056 )
Fixed extra button pins defaulting to 0 on first boot
2021-07-09 16:25:23 +02:00
cschwinne
e04b965659 Peek uses the main websocket connection 2021-07-08 02:01:17 +02:00
Aircoookie
17d2fb80f2 More robust initial resource loading in UI 2021-07-07 23:45:53 +02:00
Blaž Kristan
14b7ec2a80 Added support for ESP32 S2. (#2067)
Updated Wemos Shield parameters.
2021-07-07 01:56:07 +02:00
srg74
f27b31b581 Quick fix fro paltformio.ini (#2060)
* Added support for H803FW controller

* Create usermod_bme280.cpp

* Create usermod_bme280.cpp

* Added BME280 sensor

* Update readme.md

* Update usermod_bme280.cpp

* Update platformio.ini

* Update for lightweight sensor

* Added travis build badge

* Update readme.md

* Update readme.md

* Update .gitignore

* Changed ldscript for a file system

* Update NpbWrapper.h

* Update .gitignore

* Delete wled-ci.yml

* Added usermod for ST7789 display

Functionality tested with ESP32. Works with main WLED and @blazoncek fork.

* fixes

* Update .gitignore

* Firmware updates!!!

Updated official @Aircoookie firmware to v0.12.0-b4 build 2103290 and developer @blazoncek firmware to latest v0.12.0-b3 build 2103282.

* Updated platformio.ini for Universal Wemos Shield board

* Fixed errors in env:wemos_shield_esp32

- lib_deps must be for esp32;
- Commented global lib_deps for Dallas sensor.
2021-07-06 09:51:20 +02:00
Louis Beaudoin
8c44147a45 Usermod Settings polishing/documentation (#2061)
* Testing new wrapper functions to read Usermod config

* Usermod Settings polishing
- remove getBoolFromJsonKey() (no longer needed), fix getValueFromJsonKey(element, destination, defaultvalue)
- Update Usermod Settings html "number" field to use step="any", and make wider to make maximum values fully visible
  - step="any" allows viewing/submitting full 7/8-digit float values, and the arrow buttons step by 1 now, instead of .00001 (which wasn't good for integers or floats)
  - html wasn't generated/compressed yet

* Update usermod_v2_example.h with more complete example and documentation for Usermod Settings
- readFromConfig() has three options for how to load values from the config JSON, we need to pick one

* Update/rename usermode_rotary_brightness_color, to be used as an example of more robust parsing Usermod Settings values

* Update Usermod example, rename getValueFromJsonKey() to getJsonValue()
- chose single readFromConfig() pattern
- demonstrating 3-argument getJsonValue()
- remove leftover printf in getJsonValue()

Co-authored-by: Louis Beaudoin <louis@embedded-creations.com>
2021-07-05 23:14:57 +02:00
cschwinne
ec05215a5e Update ArduinoJSON to 6.18.1 2021-07-04 18:52:05 +02:00
cschwinne
5903e8256f Fixed preset immediately deselecting when set via HTTP API PL= 2021-07-04 13:29:59 +02:00
cschwinne
c879351063 JSON IR improvements
Restored support for "PL=~" mistakenly removed in 2106300
2021-07-04 13:23:45 +02:00
cschwinne
1bb7e36a65 More compact playlist entries 2021-07-04 00:55:32 +02:00
Christian Schwinne
793a01f7ca Bus wrapper simplification (#2054)
* Use RMTN method

* Simplify BusWrapper

* Update PlatformIO configuration

* Fix non-included dependencies
2021-07-03 13:52:23 +02:00
cschwinne
40c8fdbf64 Added WebSockets support to UI 2021-07-02 01:46:42 +02:00
cschwinne
dc01c907f1 Send websockets on every state change 2021-07-02 00:24:14 +02:00
cschwinne
801df94446 Update libraries 2021-07-01 21:20:52 +02:00
cschwinne
0197d89976 Added MQTT button option 2021-07-01 20:51:52 +02:00
Christian Schwinne
e16a67242e Merge pull request #2011 from blazoncek/multi-button-update
Added MQTT support for buttons and simplified switch.
2021-07-01 14:57:12 +02:00
cschwinne
4c678a5010 Fixed a minor visual issue with slider trail not reaching thumb on low values 2021-07-01 14:56:18 +02:00
srg74
3754088a44 Updated [env] for Universal Wemos Shield board (#2049)
* Added support for H803FW controller

* Create usermod_bme280.cpp

* Create usermod_bme280.cpp

* Added BME280 sensor

* Update readme.md

* Update usermod_bme280.cpp

* Update platformio.ini

* Update for lightweight sensor

* Added travis build badge

* Update readme.md

* Update readme.md

* Update .gitignore

* Changed ldscript for a file system

* Update NpbWrapper.h

* Update .gitignore

* Delete wled-ci.yml

* Added usermod for ST7789 display

Functionality tested with ESP32. Works with main WLED and @blazoncek fork.

* fixes

* Update .gitignore

* Firmware updates!!!

Updated official @Aircoookie firmware to v0.12.0-b4 build 2103290 and developer @blazoncek firmware to latest v0.12.0-b3 build 2103282.

* Updated platformio.ini for Universal Wemos Shield board
2021-07-01 12:05:02 +02:00
Christian Schwinne
c4f084a991 Merge JSON ircodes (#2048)
* add decodeIRJson and JSON remote option

* handle JSON API commands also

* removed code that forced IR codes in a certain range to be decoded by decodeIR24. Generate default ir.json files for currently supported remotes.

* comment out printing API commands in IR handling

* refactor decodeIRJson to change how ir.json is loaded add support for calling several c functions

* Handle setting palette when effect is still on default solid and will not display it

* remove colorUpdated notifier that was pasted in accidentally

* Update to handle both 24-key and 24-key old remotes (#1969)

* Update readme.md

* Update ir.cpp

Handle both 24-key and 24-key old in decodeIR switch statement

* Re-add JSON remote option

Co-authored-by: Scott Bailey <scottrbailey@gmail.com>
Co-authored-by: Artacus <40248830+scottrbailey@users.noreply.github.com>
2021-07-01 00:17:07 +02:00
cschwinne
4c73df4ba6 Shorten input size class names 2021-07-01 00:01:27 +02:00
cschwinne
4aa53aa5a5 Adjust input field widths 2021-06-30 21:53:22 +02:00
Blaž Kristan
d6337f7500 Added MQTT support for buttons and simplified switch.
Added PIR sensor option.
2021-06-03 12:18:11 +02:00
136 changed files with 15804 additions and 6144 deletions

View File

@@ -1,27 +0,0 @@
---
name: Bug
about: Noticed an issue with your lights?
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is. Please quickly search existing issues first!
**To Reproduce**
Steps to reproduce the behavior, if consistently possible
**Expected behavior**
A clear and concise description of what you expected to happen.
**WLED version**
- Board: [e.g. Wemos D1, ESP32 dev]
- Version [e.g. 0.10.0, dev200603]
- Format [e.g. Binary, self-compiled]
**Additional context**
Anything else you'd like to say about the problem?
Thank you for your help!

83
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -0,0 +1,83 @@
name: Bug Report
description: File a bug report
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Please quickly search existing issues first before submitting a bug.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: A clear and concise description of what the bug is.
placeholder: Tell us what the problem is.
validations:
required: true
- type: textarea
id: how-to-reproduce
attributes:
label: To Reproduce Bug
description: Steps to reproduce the behavior, if consistently possible.
placeholder: Tell us how to make the bug appear.
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
placeholder: Tell us what you expected to happen.
validations:
required: true
- type: dropdown
id: install_format
attributes:
label: Install Method
description: How did you install WLED?
options:
- Binary from WLED.me
- Self-Compiled
validations:
required: true
- type: input
id: version
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-b4 (build 2110110)"
validations:
required: true
- type: dropdown
id: Board
attributes:
label: Which microcontroller/board are you seeing the problem on?
multiple: true
options:
- ESP8266
- ESP32
- Other
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log/trace output
description: Please copy and paste any relevant log output if you have it. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/Aircoookie/WLED/blob/master/CODE_OF_CONDUCT.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@@ -2,6 +2,184 @@
### Builds after release 0.12.0
#### Build 2112080
- Version bump to 0.13.0-b6 "Toki"
- Added "ESP02" (ESP8266 with 2M of flash) to PIO/release binaries
#### Build 2112070
- Added new effect "Fairy", replacing "Police All"
- Added new effect "Fairytwinkle", replacing "Two Areas"
- Static single JSON buffer (performance and stability improvement) (PR #2336)
#### Build 2112030
- Fixed ESP32 crash on Colortwinkles brightness change
- Fixed setting picker to black resetting hue and saturation
- Fixed auto white mode not saved to config
#### Build 2111300
- Added CCT and white balance correction support (PR #2285)
- Unified UI slider style
- Added LED settings config template upload
#### Build 2111220
- Fixed preset cycle not working from preset called by UI
- Reintroduced permanent min. and max. cycle bounds
#### Build 2111190
- Changed default ESP32 LED pin from 16 to 2
- Renamed "Running 2" to "Chase 2"
- Renamed "Tri Chase" to "Chase 3"
#### Build 2111170
- Version bump to 0.13.0-b5 "Toki"
- Improv Serial support (PR #2334)
- Button improvements (PR #2284)
- Added two time zones (PR #2264, 2311)
- JSON in/decrementing support for brightness and presets
- Fixed no gamma correction for JSON individual LED control
- Preset cycle bugfix
- Removed ledCount
- LED settings buffer bugfix
- Network pin conflict bugfix
- Changed default ESP32 partition layout to 4M, 1M FS
#### Build 2110110
- Version bump to 0.13.0-b4 "Toki"
- Added option for bus refresh if off (PR #2259)
- New auto segment logic
- Fixed current calculations for virtual or non-linear configs (PR #2262)
#### Build 2110060
- Added virtual network DDP busses (PR #2245)
- Allow playlist as end preset in playlist
- Improved bus start field UX
- Pin reservations improvements (PR #2214)
#### Build 2109220
- Version bump to 0.13.0-b3 "Toki"
- Added segment names (PR #2184)
- Improved Police and other effects (PR #2184)
- Reverted PR #1902 (Live color correction - will be implemented as usermod) (PR #2175)
- Added transitions for segment on/off
- Improved number of sparks/stars in Fireworks effect with low number of segments
- Fixed segment name edit pencil disappearing with request
- Fixed color transition active even if the segment is off
- Disallowed file upload with OTA lock active
- Fixed analog invert option missing (PR #2219)
#### Build 2109100
- Added an auto create segments per bus setting
- Added 15 new palettes from SR branch (PR #2134)
- Fixed segment runtime not reset on FX change via HTTP API
- Changed AsyncTCP dependency to pbolduc fork v1.2.0
#### Build 2108250
- Added Sync groups (PR #2150)
- Added JSON API over Serial support
- Live color correction (PR #1902)
#### Build 2108180
- Fixed JSON IR remote not working with codes greater than 0xFFFFFF (fixes #2135)
- Fixed transition 0 edge case
#### Build 2108170
- Added application level pong websockets reply (#2139)
- Use AsyncTCP 1.0.3 as it mitigates the flickering issue from 0.13.0-b2
- Fixed transition manually updated in preset overriden by field value
#### Build 2108050
- Fixed undesirable color transition from Orange to boot preset color on first boot
- Removed misleading Delete button on new playlist with one entry
- Updated NeoPixelBus to 2.6.7 and AsyncTCP to 1.1.1
#### Build 2107230
- Added skinning (extra custom CSS) (PR #2084)
- Added presets/config backup/restore (PR #2084)
- Added option for using length instead of Stop LED in UI (PR #2048)
- Added custom `holidays.json` holiday list (PR #2048)
#### Build 2107100
- Version bump to 0.13.0-b2 "Toki"
- Accept hex color strings in individual LED API
- Fixed transition property not applying unless power/bri/color changed next
- Moved transition field below segments (temporarily)
- Reduced unneeded websockets pushes
#### Build 2107091
- Fixed presets using wrong call mode (e.g. causing buttons to send UDP under direct change type)
- Increased hue buffer
- Renamed `NOTIFIER_CALL_MODE_` to `CALL_MODE_`
#### Build 2107090
- Busses extend total configured LEDs if required
- Fixed extra button pins defaulting to 0 on first boot
#### Build 2107080
- Made Peek use the main websocket connection instead of opening a second one
- Temperature usermod fix (from @blazoncek's dev branch)
#### Build 2107070
- More robust initial resource loading in UI
- Added `getJsonValue()` for usermod config parsing (PR #2061)
- Fixed preset saving over websocket
- Alpha ESP32 S2 support (filesystem does not work) (PR #2067)
#### Build 2107042
- Updated ArduinoJson to 6.18.1
- Improved Twinkleup effect
- Fixed preset immediately deselecting when set via HTTP API `PL=`
#### Build 2107041
- Restored support for "PL=~" mistakenly removed in 2106300
- JSON IR improvements
#### Build 2107040
- Playlist entries are now more compact
- Added the possibility to enter negative numbers for segment offset
#### Build 2107021
- Added WebSockets support to UI
#### Build 2107020
- Send websockets on every state change
- Improved Aurora effect
#### Build 2107011
- Added MQTT button feedback option (PR #2011)
#### Build 2107010
- Added JSON IR codes (PR #1941)
- Adjusted the width of WiFi and LED settings input fields
- Fixed a minor visual issue with slider trail not reaching thumb on low values
#### Build 2106302
- Fixed settings page broken by using "%" in input fields
@@ -244,6 +422,7 @@
- Added support for WESP32 ethernet board (PR #1764)
- Added Caching for main UI (PR #1704)
- Added Tetrix mode (PR #1729)
- Removed Merry Christmas mode (use "Chase 2" - called Running 2 before 0.13.0)
- Added memory check on Bus creation
#### Build 2102050

2
package-lock.json generated
View File

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

View File

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

View File

@@ -32,7 +32,7 @@ def bin_rename_copy(source, target, env):
release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME")
if release_name and os.getenv("WLED_RELEASE"):
if release_name:
_create_dirs(["release"])
version = _get_cpp_define_value(env, "WLED_VERSION")
release_file = "{}release{}WLED_{}_{}.bin".format(OUTPUT_DIR, os.path.sep, version, release_name)

View File

@@ -8,15 +8,19 @@
# Please uncomment one of the lines below to select your board(s)
# ------------------------------------------------------------------------------
# Travis CI binaries (comment this out with a ';' when building for your own board)
;default_envs = travis_esp8266, travis_esp32
# Travis CI binaries (use `platformio_override.ini` when building for your own board; see `platformio_override.ini.sample` for an example)
; default_envs = travis_esp8266, travis_esp32
# Release binaries
default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth
# Build everything
; default_envs = esp32dev, esp8285_4CH_MagicHome, esp8285_4CH_H801, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_5CH_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
# Single binaries (uncomment your board)
; default_envs = elekstube_ips
; default_envs = nodemcuv2
; default_envs = esp8266_2m
; default_envs = esp01_1m_full
; default_envs = esp07
; default_envs = d1_mini
@@ -73,7 +77,6 @@ debug_flags = -D DEBUG=1 -D WLED_DEBUG -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT
# ------------------------------------------------------------------------------
# FLAGS: ldscript (available ldscripts at https://github.com/esp8266/Arduino/tree/master/tools/sdk/ld)
# ldscript_512k ( 512 KB) = 487 KB sketch, 4 KB eeprom, no spiffs, 16 KB reserved
# ldscript_1m0m (1024 KB) = 999 KB sketch, 4 KB eeprom, no spiffs, 16 KB reserved
# ldscript_2m1m (2048 KB) = 1019 KB sketch, 4 KB eeprom, 1004 KB spiffs, 16 KB reserved
# ldscript_4m1m (4096 KB) = 1019 KB sketch, 4 KB eeprom, 1002 KB spiffs, 16 KB reserved, 2048 KB empty/ota?
@@ -129,28 +132,6 @@ ldscript_2m512k = eagle.flash.2m512.ld
ldscript_2m1m = eagle.flash.2m1m.ld
ldscript_4m1m = eagle.flash.4m1m.ld
[esp8266]
build_flags =
-DESP8266
-DFP_IN_IROM
;-Wno-deprecated-declarations
;-Wno-register
; NONOSDK22x_190703 = 2.2.2-dev(38a443e)
-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703
; lwIP 2 - Higher Bandwidth no Features
; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
; lwIP 1.4 - Higher Bandwidth (Aircoookie has)
-DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
; VTABLES in Flash
-DVTABLES_IN_FLASH
; restrict to minimal mime-types
-DMIMETYPE_MINIMAL
[esp32]
build_flags = -g
-DARDUINO_ARCH_ESP32
-DCONFIG_LITTLEFS_FOR_IDF_3_2
[scripts_defaults]
extra_scripts =
pre:pio-scripts/set_version.py
@@ -179,12 +160,8 @@ upload_speed = 115200
# ------------------------------------------------------------------------------
lib_compat_mode = strict
lib_deps =
fastled/FastLED @ 3.3.2
NeoPixelBus @ 2.6.0
ESPAsyncTCP @ 1.2.0
ESPAsyncUDP
AsyncTCP @ 1.0.3
IRremoteESP8266 @ 2.7.3
fastled/FastLED @ 3.4.0
IRremoteESP8266 @ 2.7.18
https://github.com/lorol/LITTLEFS.git
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.2
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
@@ -192,19 +169,66 @@ lib_deps =
#For use SSD1306 OLED display uncomment following
#U8g2@~2.27.2
#For Dallas sensor uncomment following 2 lines
OneWire@~2.3.5
milesburton/DallasTemperature@^3.9.0
#OneWire@~2.3.5
#milesburton/DallasTemperature@^3.9.0
#For BME280 sensor uncomment following
#BME280@~3.0.0
; adafruit/Adafruit BMP280 Library @ 2.1.0
; adafruit/Adafruit CCS811 Library @ 1.0.4
; adafruit/Adafruit Si7021 Library @ 1.4.0
lib_ignore =
AsyncTCP
extra_scripts = ${scripts_defaults.extra_scripts}
[esp8266]
build_flags =
-DESP8266
-DFP_IN_IROM
;-Wno-deprecated-declarations
;-Wno-register
; NONOSDK22x_190703 = 2.2.2-dev(38a443e)
-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703
; lwIP 2 - Higher Bandwidth no Features
; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
; lwIP 1.4 - Higher Bandwidth (Aircoookie has)
-DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
; VTABLES in Flash
-DVTABLES_IN_FLASH
; restrict to minimal mime-types
-DMIMETYPE_MINIMAL
lib_deps =
${env.lib_deps}
# ESPAsyncTCP @ 1.2.0
ESPAsyncUDP
makuna/NeoPixelBus @ 2.6.7 # 2.6.5/2.6.6 and newer do not compile on ESP core < 3.0.0
[esp32]
build_flags = -g
-DARDUINO_ARCH_ESP32
-DCONFIG_LITTLEFS_FOR_IDF_3_2
-D CONFIG_ASYNC_TCP_USE_WDT=0
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
lib_deps =
${env.lib_deps}
makuna/NeoPixelBus @ 2.6.7
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
[esp32s2]
build_flags = -g
-DARDUINO_ARCH_ESP32
-DCONFIG_LITTLEFS_FOR_IDF_3_2
-DARDUINO_ARCH_ESP32S2
-DCONFIG_IDF_TARGET_ESP32S2
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DCO
lib_deps =
${env.lib_deps}
makuna/NeoPixelBus @ 2.6.7
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
# ------------------------------------------------------------------------------
# WLED BUILDS
# ------------------------------------------------------------------------------
@@ -216,6 +240,14 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266
lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full]
board = esp01_1m
@@ -224,6 +256,7 @@ 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
lib_deps = ${esp8266.lib_deps}
[env:esp07]
board = esp07
@@ -232,6 +265,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
lib_deps = ${esp8266.lib_deps}
[env:d1_mini]
board = d1_mini
@@ -241,6 +275,7 @@ upload_speed = 921600
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder
[env:heltec_wifi_kit_8]
@@ -250,6 +285,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
lib_deps = ${esp8266.lib_deps}
[env:h803wf]
board = d1_mini
@@ -258,25 +294,37 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
[env:esp32dev]
board = esp32dev
platform = espressif32@3.2
platform = espressif32@2.0
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32
lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32.lib_deps}
board_build.partitions = ${esp32.default_partitions}
[env:esp32_eth]
board = esp32-poe
platform = espressif32@3.2
platform = espressif32@2.0
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
lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
lib_deps = ${esp32.lib_deps}
board_build.partitions = ${esp32.default_partitions}
[env:esp32s2_saola]
board = esp32dev
board_build.mcu = esp32s2
platform = espressif32
platform_packages =
toolchain-xtensa32s2
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.0-alpha1
framework = arduino
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
upload_speed = 460800
build_unflags = ${common.build_unflags}
lib_deps = ${esp32s2.lib_deps}
[env:esp8285_4CH_MagicHome]
board = esp8285
@@ -285,6 +333,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA
lib_deps = ${esp8266.lib_deps}
[env:esp8285_4CH_H801]
board = esp8285
@@ -293,6 +342,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA
lib_deps = ${esp8266.lib_deps}
[env:esp8285_5CH_H801]
board = esp8285
@@ -301,6 +351,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA
lib_deps = ${esp8266.lib_deps}
[env:d1_mini_5CH_Shojo_PCB]
board = d1_mini
@@ -309,6 +360,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_USE_SHOJO_PCB
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
# DEVELOPMENT BOARDS
@@ -322,6 +374,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} ${common.debug_flags}
lib_deps = ${esp8266.lib_deps}
[env:d1_mini_ota]
board = d1_mini
@@ -333,6 +386,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
lib_deps = ${esp8266.lib_deps}
[env:anavi_miracle_controller]
board = d1_mini
@@ -341,105 +395,59 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
# custom board configurations
# ------------------------------------------------------------------------------
[env:custom_LEDPIN_4]
board = d1_mini
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=4 -D IRPIN=5
[env:custom_LEDPIN_16]
board = d1_mini
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=16
[env:custom_LEDPIN_3]
board = d1_mini
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3
[env:custom_APA102]
board = d1_mini
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D USE_APA102
[env:custom_WS2801]
board = d1_mini
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D USE_WS2801
[env:custom32_LEDPIN_16]
board = esp32dev
platform = espressif32@3.2
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D LEDPIN=16 -D RLYPIN=19
lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
[env:custom32_APA102]
board = esp32dev
platform = espressif32@3.2
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D USE_APA102
lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
[env:custom32_TOUCHPIN_T0]
board = esp32dev
platform = espressif32@3.2
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D TOUCHPIN=T0
lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
[env:wemos_shield_esp32]
board = esp32dev
platform = espressif32@3.2
upload_port = /dev/cu.SLAB_USBtoUART
monitor_port = /dev/cu.SLAB_USBtoUART
upload_speed = 460800
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D LEDPIN=16 -D RLYPIN=19 -D BTNPIN=17
lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
build_flags = ${common.build_flags_esp32}
-D LEDPIN=16
-D RLYPIN=19
-D BTNPIN=17
-D IRPIN=18
-D UWLED_USE_MY_CONFIG
-D USERMOD_DALLASTEMPERATURE
-D USERMOD_FOUR_LINE_DISPLAY
-D TEMPERATURE_PIN=23
lib_deps = ${esp32.lib_deps}
OneWire@~2.3.5
olikraus/U8g2 @ ^2.28.8
board_build.partitions = ${esp32.default_partitions}
[env:m5atom]
board = esp32dev
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39
lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
lib_deps = ${esp32.lib_deps}
platform = espressif32@3.2
board_build.partitions = ${esp32.default_partitions}
[env:sp501e]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=1
lib_deps = ${esp8266.lib_deps}
[env:sp511e]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
lib_deps = ${esp8266.lib_deps}
[env:athom7w]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D WLED_MAX_CCT_BLEND=0 -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
# travis test board configurations
@@ -469,6 +477,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
lib_deps = ${esp8266.lib_deps}
[env:codm-controller-0.6-rev2]
board = esp_wroom_02
@@ -477,6 +486,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
# EleksTube-IPS
@@ -485,8 +495,6 @@ build_flags = ${common.build_flags_esp8266}
board = esp32dev
platform = espressif32@3.2
upload_speed = 921600
lib_deps = ${env.lib_deps}
TFT_eSPI
build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED
-D USERMOD_RTC
-D USERMOD_ELEKSTUBE_IPS
@@ -508,6 +516,7 @@ build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_D
-D SPI_FREQUENCY=40000000
-D USER_SETUP_LOADED
monitor_filters = esp32_exception_decoder
lib_ignore =
ESPAsyncTCP
ESPAsyncUDP
lib_deps =
${esp32.lib_deps}
TFT_eSPI @ ^2.3.70
board_build.partitions = ${esp32.default_partitions}

View File

@@ -12,6 +12,7 @@ board = esp01_1m
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
lib_deps = ${esp8266.lib_deps}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
; *********************************************************************
@@ -43,4 +44,4 @@ build_flags = ${common.build_flags_esp8266}
; configure the settings in the UI as follows (hard):
; 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
; for the BW-LT11 controller use PINs 12,4,14,5

View File

@@ -4,12 +4,12 @@
<a href="https://raw.githubusercontent.com/Aircoookie/WLED/master/LICENSE"><img src="https://img.shields.io/github/license/Aircoookie/wled?color=blue&style=flat-square"></a>
<a href="https://wled.discourse.group"><img src="https://img.shields.io/discourse/topics?colorB=blue&label=forum&server=https%3A%2F%2Fwled.discourse.group%2F&style=flat-square"></a>
<a href="https://discord.gg/KuqP7NE"><img src="https://img.shields.io/discord/473448917040758787.svg?colorB=blue&label=discord&style=flat-square"></a>
<a href="https://github.com/Aircoookie/WLED/wiki"><img src="https://img.shields.io/badge/quick_start-wiki-blue.svg?style=flat-square"></a>
<a href="https://kno.wled.ge"><img src="https://img.shields.io/badge/quick_start-wiki-blue.svg?style=flat-square"></a>
<a href="https://github.com/Aircoookie/WLED-App"><img src="https://img.shields.io/badge/app-wled-blue.svg?style=flat-square"></a>
<a href="https://gitpod.io/#https://github.com/Aircoookie/WLED"><img src="https://img.shields.io/badge/Gitpod-ready--to--code-blue?style=flat-square&logo=gitpod"></a>
</p>
# Welcome to my project WLED! ✨
A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102!
@@ -49,9 +49,9 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control
## 📲 Quick start guide and documentation
See the [wiki](https://github.com/Aircoookie/WLED/wiki)!
See the [documentation on our official site](https://kno.wled.ge)!
[On this page](https://github.com/Aircoookie/WLED/wiki/Learning-the-ropes) you can find excellent tutorials made by the community and helpful tools to help you get your new lamp up and running!
[On this page](https://kno.wled.ge/basics/tutorials/) you can find excellent tutorials made by the community and helpful tools to help you get your new lamp up and running!
## 🖼️ Images
<img src="/images/macbook-pro-space-gray-on-the-wooden-table.jpg" width="50%"><img src="/images/walking-with-iphone-x.jpg" width="50%">
@@ -82,7 +82,7 @@ Any | 5v 3-pin ARGB for PC | Any PC RGB device that supports the 5v 3-pin ARGB m
## ✌️ Other
Licensed under the MIT license
Credits [here](https://github.com/Aircoookie/WLED/wiki/Contributors-&-About)!
Credits [here](https://kno.wled.ge/about/contributors/)!
Uses Linearicons by Perxis!

View File

@@ -90,7 +90,7 @@ function writeHtmlGzipped(sourceFile, resultFile) {
* Binary array for the Web UI.
* gzip is used for smaller size and improved speeds.
*
* Please see https://github.com/Aircoookie/WLED/wiki/Add-own-functionality#web-ui
* Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
* to find out how to easily modify the web UI source!
*/
@@ -175,7 +175,7 @@ function writeChunks(srcDir, specs, resultFile) {
let src = `/*
* More web UI HTML source arrays.
* This file is auto generated, please don't make any changes manually.
* Instead, see https://github.com/Aircoookie/WLED/wiki/Add-own-functionality#web-ui
* Instead, see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
* to find out how to easily modify the web UI source!
*/
`;

16
tools/multi-update.cmd Normal file
View File

@@ -0,0 +1,16 @@
@echo off
SETLOCAL
SET FWPATH=c:\path\to\your\WLED\build_output\firmware
GOTO ESPS
:UPDATEONE
IF NOT EXIST %FWPATH%\%2 GOTO SKIP
ping -w 1000 -n 1 %1 | find "TTL=" || GOTO SKIP
ECHO Updating %1
curl -s -F "update=@%FWPATH%/%2" %1/update >nul
:SKIP
GOTO:EOF
:ESPS
call :UPDATEONE 192.168.x.x firmware.bin
call :UPDATEONE ....

19
tools/multi-update.sh Normal file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
FWPATH=/path/to/your/WLED/build_output/firmware
update_one() {
if [ -f $FWPATH/$2 ]; then
ping -c 1 $1 >/dev/null
PINGRESULT=$?
if [ $PINGRESULT -eq 0 ]; then
echo Updating $1
curl -s -F "update=@${FWPATH}/$2" $1/update >/dev/null
return 0
fi
return 1
fi
}
update_one 192.168.x.x firmware.bin
update_one 192.168.x.x firmware.bin
# ...

View File

@@ -123,7 +123,7 @@ class Animated_Staircase : public Usermod {
// Always mark segments as "transitional", we are animating the staircase
segments->setOption(SEG_OPTION_TRANSITIONAL, 1, 1);
}
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
colorUpdated(CALL_MODE_DIRECT_CHANGE);
}
/*
@@ -298,7 +298,7 @@ class Animated_Staircase : public Usermod {
}
segments->setOption(SEG_OPTION_ON, 1, 1);
}
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
colorUpdated(CALL_MODE_DIRECT_CHANGE);
DEBUG_PRINTLN(F("Animated Staircase disabled."));
}
enabled = enable;
@@ -306,22 +306,26 @@ class Animated_Staircase : public Usermod {
public:
void setup() {
// standardize invalid pin numbers to -1
if (topPIRorTriggerPin < 0) topPIRorTriggerPin = -1;
if (topEchoPin < 0) topEchoPin = -1;
if (bottomPIRorTriggerPin < 0) bottomPIRorTriggerPin = -1;
if (bottomEchoPin < 0) bottomEchoPin = -1;
// allocate pins
if (topPIRorTriggerPin >= 0) {
if (!pinManager.allocatePin(topPIRorTriggerPin,useUSSensorTop))
topPIRorTriggerPin = -1;
}
if (topEchoPin >= 0) {
if (!pinManager.allocatePin(topEchoPin,false))
topEchoPin = -1;
}
if (bottomPIRorTriggerPin >= 0) {
if (!pinManager.allocatePin(bottomPIRorTriggerPin,useUSSensorBottom))
bottomPIRorTriggerPin = -1;
}
if (bottomEchoPin >= 0) {
if (!pinManager.allocatePin(bottomEchoPin,false))
bottomEchoPin = -1;
PinManagerPinType pins[4] = {
{ topPIRorTriggerPin, useUSSensorTop },
{ topEchoPin, false },
{ bottomPIRorTriggerPin, useUSSensorBottom },
{ bottomEchoPin, false },
};
// NOTE: this *WILL* return TRUE if all the pins are set to -1.
// this is *BY DESIGN*.
if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_AnimatedStaircase)) {
topPIRorTriggerPin = -1;
topEchoPin = -1;
bottomPIRorTriggerPin = -1;
bottomEchoPin = -1;
enabled = false;
}
enable(enabled);
initDone = true;
@@ -480,10 +484,10 @@ class Animated_Staircase : public Usermod {
(oldBottomAPin != bottomPIRorTriggerPin) ||
(oldBottomBPin != bottomEchoPin)) {
changed = true;
pinManager.deallocatePin(oldTopAPin);
pinManager.deallocatePin(oldTopBPin);
pinManager.deallocatePin(oldBottomAPin);
pinManager.deallocatePin(oldBottomBPin);
pinManager.deallocatePin(oldTopAPin, PinOwner::UM_AnimatedStaircase);
pinManager.deallocatePin(oldTopBPin, PinOwner::UM_AnimatedStaircase);
pinManager.deallocatePin(oldBottomAPin, PinOwner::UM_AnimatedStaircase);
pinManager.deallocatePin(oldBottomBPin, PinOwner::UM_AnimatedStaircase);
}
if (changed) setup();
}

View File

@@ -24,7 +24,7 @@ void RGBNET_readValues() {
int channel = UDP.read();
//channel data is not used we only supports one channel
int len = UDP.read(RGBNET_packet, ledCount*3);
int len = UDP.read(RGBNET_packet, strip.getLengthTotal()*3);
if(len==0){
return;
}
@@ -50,7 +50,7 @@ void handleConfig(AsyncWebServerRequest *request)
\"channels\": [\
{\
\"channel\": 1,\
\"leds\": " + ledCount + "\
\"leds\": " + strip.getLengthTotal() + "\
},\
{\
\"channel\": 2,\

View File

@@ -0,0 +1,16 @@
; Options
; -------
; USERMOD_BH1750 - define this to have this user mod included wled00\usermods_list.cpp
; USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - the max number of milliseconds between measurements, defaults to 10000ms
; USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL - the min number of milliseconds between measurements, defaults to 500ms
; USERMOD_BH1750_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 10 seconds
; USERMOD_BH1750_OFFSET_VALUE - the offset value to report on, defaults to 1
;
[env:usermod_BH1750_d1_mini]
extends = env:d1_mini
build_flags =
${common.build_flags_esp8266}
-D USERMOD_BH1750
lib_deps =
${env.lib_deps}
claws/BH1750 @ ^1.2.0

View File

@@ -0,0 +1,24 @@
# BH1750 usermod
This usermod will read from an ambient light sensor like the BH1750 sensor.
The luminance is displayed both in the Info section of the web UI as well as published to the `/luminance` MQTT topic if enabled.
## Installation
Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`.
### Define Your Options
* `USERMOD_BH1750` - define this to have this user mod included wled00\usermods_list.cpp
* `USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL` - the max number of milliseconds between measurements, defaults to 10000ms
* `USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL` - the min number of milliseconds between measurements, defaults to 500ms
* `USERMOD_BH1750_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 10 seconds
* `USERMOD_BH1750_OFFSET_VALUE` - the offset value to report on, defaults to 1
All parameters can be configured at runtime using Usermods settings page.
### PlatformIO requirements
If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:usermod_BH1750_d1_mini`.
## Change Log

View File

@@ -0,0 +1,177 @@
#pragma once
#include "wled.h"
#include <Wire.h>
#include <BH1750.h>
// the max frequency to check photoresistor, 10 seconds
#ifndef USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL
#define USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL 10000
#endif
// the min frequency to check photoresistor, 500 ms
#ifndef USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL
#define USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL 500
#endif
// how many seconds after boot to take first measurement, 10 seconds
#ifndef USERMOD_BH1750_FIRST_MEASUREMENT_AT
#define USERMOD_BH1750_FIRST_MEASUREMENT_AT 10000
#endif
// only report if differance grater than offset value
#ifndef USERMOD_BH1750_OFFSET_VALUE
#define USERMOD_BH1750_OFFSET_VALUE 1
#endif
class Usermod_BH1750 : public Usermod
{
private:
int8_t offset = USERMOD_BH1750_OFFSET_VALUE;
unsigned long maxReadingInterval = USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL;
unsigned long minReadingInterval = USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL;
unsigned long lastMeasurement = UINT32_MAX - (USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - USERMOD_BH1750_FIRST_MEASUREMENT_AT);
unsigned long lastSend = UINT32_MAX - (USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - USERMOD_BH1750_FIRST_MEASUREMENT_AT);
// flag to indicate we have finished the first readLightLevel call
// allows this library to report to the user how long until the first
// measurement
bool getLuminanceComplete = false;
// flag set at startup
bool disabled = false;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
static const char _maxReadInterval[];
static const char _minReadInterval[];
static const char _offset[];
BH1750 lightMeter;
float lastLux = -1000;
bool checkBoundSensor(float newValue, float prevValue, float maxDiff)
{
return isnan(prevValue) || newValue <= prevValue - maxDiff || newValue >= prevValue + maxDiff || (newValue == 0.0 && prevValue > 0.0);
}
public:
void setup()
{
Wire.begin();
lightMeter.begin();
}
void loop()
{
if (disabled || strip.isUpdating())
return;
unsigned long now = millis();
// check to see if we are due for taking a measurement
// lastMeasurement will not be updated until the conversion
// is complete the the reading is finished
if (now - lastMeasurement < minReadingInterval)
{
return;
}
bool shouldUpdate = now - lastSend > maxReadingInterval;
float lux = lightMeter.readLightLevel();
lastMeasurement = millis();
getLuminanceComplete = true;
if (shouldUpdate || checkBoundSensor(lux, lastLux, offset))
{
lastLux = lux;
lastSend = millis();
if (WLED_MQTT_CONNECTED)
{
char subuf[45];
strcpy(subuf, mqttDeviceTopic);
strcat_P(subuf, PSTR("/luminance"));
mqtt->publish(subuf, 0, true, String(lux).c_str());
}
else
{
DEBUG_PRINTLN("Missing MQTT connection. Not publishing data");
}
}
}
void addToJsonInfo(JsonObject &root)
{
JsonObject user = root[F("u")];
if (user.isNull())
user = root.createNestedObject(F("u"));
JsonArray lux_json = user.createNestedArray(F("Luminance"));
if (!getLuminanceComplete)
{
// if we haven't read the sensor yet, let the user know
// that we are still waiting for the first measurement
lux_json.add((USERMOD_BH1750_FIRST_MEASUREMENT_AT - millis()) / 1000);
lux_json.add(F(" sec until read"));
return;
}
lux_json.add(lastLux);
lux_json.add(F(" lx"));
}
uint16_t getId()
{
return USERMOD_ID_BH1750;
}
/**
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
*/
void addToConfig(JsonObject &root)
{
// we add JSON object.
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = !disabled;
top[FPSTR(_maxReadInterval)] = maxReadingInterval;
top[FPSTR(_minReadInterval)] = minReadingInterval;
top[FPSTR(_offset)] = offset;
DEBUG_PRINTLN(F("Photoresistor config saved."));
}
/**
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*/
bool readFromConfig(JsonObject &root)
{
// we look for JSON object.
JsonObject top = root[FPSTR(_name)];
if (top.isNull())
{
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
disabled = !(top[FPSTR(_enabled)] | !disabled);
maxReadingInterval = (top[FPSTR(_maxReadInterval)] | maxReadingInterval); // ms
minReadingInterval = (top[FPSTR(_minReadInterval)] | minReadingInterval); // ms
offset = top[FPSTR(_offset)] | offset;
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(" config (re)loaded."));
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
}
};
// strings to reduce flash memory usage (used more than twice)
const char Usermod_BH1750::_name[] PROGMEM = "BH1750";
const char Usermod_BH1750::_enabled[] PROGMEM = "enabled";
const char Usermod_BH1750::_maxReadInterval[] PROGMEM = "max-read-interval-ms";
const char Usermod_BH1750::_minReadInterval[] PROGMEM = "min-read-interval-ms";
const char Usermod_BH1750::_offset[] PROGMEM = "offset-lx";

View File

@@ -0,0 +1,14 @@
#include "wled.h"
/*
* Register your v2 usermods here!
*/
#ifdef USERMOD_BH1750
#include "../usermods/BH1750_v2/usermod_BH1750.h"
#endif
void registerUsermods()
{
#ifdef USERMOD_BH1750
usermods.add(new Usermod_BH1750());
#endif
}

View File

@@ -23,12 +23,20 @@
//class name. Use something descriptive and leave the ": public Usermod" part :)
class MyExampleUsermod : public Usermod {
private:
// sample usermod default value for variable (you can also use constructor)
int userVar0 = 42;
//Private class members. You can declare variables and functions only accessible to your usermod here
unsigned long lastTime = 0;
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
bool testBool = false;
unsigned long testULong = 42424242;
float testFloat = 42.42;
String testString = "Forty-Two";
// These config variables have defaults set inside readFromConfig()
int testInt;
long testLong;
int8_t testPins[2];
public:
//Functions called by WLED
@@ -118,40 +126,85 @@ class MyExampleUsermod : public Usermod {
* 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 also not yet add your setting to one of the settings pages automatically.
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
* 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("exampleUsermod");
top["great"] = userVar0; //save this var persistently whenever settings are saved
top["great"] = userVar0; //save these vars persistently whenever settings are saved
top["testBool"] = testBool;
top["testInt"] = testInt;
top["testLong"] = testLong;
top["testULong"] = testULong;
top["testFloat"] = testFloat;
top["testString"] = testString;
JsonArray pinArray = top.createNestedArray("pin");
pinArray.add(testPins[0]);
pinArray.add(testPins[1]);
}
/*
* 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 once immediately after boot)
* 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 your config was complete, or false if you'd like WLED to save your defaults to disk
* 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)
{
//set defaults for variables when declaring the variable (class definition or constructor)
// 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["exampleUsermod"];
if (!top.isNull()) return false;
userVar0 = top["great"] | userVar0;
bool configComplete = !top.isNull();
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
configComplete &= getJsonValue(top["great"], userVar0);
configComplete &= getJsonValue(top["testBool"], testBool);
configComplete &= getJsonValue(top["testULong"], testULong);
configComplete &= getJsonValue(top["testFloat"], testFloat);
configComplete &= getJsonValue(top["testString"], testString);
// A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing
configComplete &= getJsonValue(top["testInt"], testInt, 42);
configComplete &= getJsonValue(top["testLong"], testLong, -42424242);
configComplete &= getJsonValue(top["pin"][0], testPins[0], -1);
configComplete &= getJsonValue(top["pin"][1], testPins[1], -1);
return configComplete;
}

View File

@@ -58,12 +58,12 @@ public:
void setMinutesTens() { setDigit(MINUTES_TENS); }
void setHoursOnes() { setDigit(HOURS_ONES); }
void setHoursTens() { setDigit(HOURS_TENS); }
bool isSecondsOnes() { return (digits_map&SECONDS_ONES_MAP > 0); }
bool isSecondsTens() { return (digits_map&SECONDS_TENS_MAP > 0); }
bool isMinutesOnes() { return (digits_map&MINUTES_ONES_MAP > 0); }
bool isMinutesTens() { return (digits_map&MINUTES_TENS_MAP > 0); }
bool isHoursOnes() { return (digits_map&HOURS_ONES_MAP > 0); }
bool isHoursTens() { return (digits_map&HOURS_TENS_MAP > 0); }
bool isSecondsOnes() { return ((digits_map & SECONDS_ONES_MAP) > 0); }
bool isSecondsTens() { return ((digits_map & SECONDS_TENS_MAP) > 0); }
bool isMinutesOnes() { return ((digits_map & MINUTES_ONES_MAP) > 0); }
bool isMinutesTens() { return ((digits_map & MINUTES_TENS_MAP) > 0); }
bool isHoursOnes() { return ((digits_map & HOURS_ONES_MAP) > 0); }
bool isHoursTens() { return ((digits_map & HOURS_TENS_MAP) > 0); }
};

View File

@@ -0,0 +1,119 @@
{
"desc": "21-key",
"0xFFA25D": {
"label": "On",
"pos": "1x1",
"cmd": "T=1"
},
"0xFF629D": {
"label": "Off",
"pos": "1x2",
"cmd": "T=0"
},
"0xFFE21D": {
"label": "Flash",
"pos": "1x3",
"cmnt": "Cycle Effects",
"cmd": "CY=0&FX=~"
},
"0xFF22DD": {
"label": "Strobe",
"pos": "2x1",
"cmnt": "Sinelon Dual",
"cmd": "CY=0&FX=93"
},
"0xFF02FD": {
"label": "Fade",
"pos": "2x2",
"cmnt": "Rain",
"cmd": "CY=0&FX=43"
},
"0xFFC23D": {
"label": "Smooth",
"pos": "2x3",
"cmnt": "Aurora",
"cmd": "CY=0&FX=38"
},
"0xFFE01F": {
"label": "Bright +",
"pos": "3x1",
"cmd": "A=~16"
},
"0xFFA857": {
"label": "Bright -",
"pos": "3x2",
"cmd": "A=~-16"
},
"0xFF906F": {
"label": "White",
"pos": "3x3",
"cmd": "FP=5&CL=hFFFFFF&C2=hFFFFFF&C3=hA8A8A8"
},
"0xFF6897": {
"label": "Red",
"pos": "4x1",
"cmnt": "Lava",
"cmd": "FP=8"
},
"0xFF9867": {
"label": "Green",
"pos": "4x2",
"cmnt": "Forest",
"cmd": "FP=10"
},
"0xFFB04F": {
"label": "Blue",
"pos": "4x3",
"cmnt": "Breeze",
"cmd": "FP=15"
},
"0xFF30CF": {
"label": "Tomato",
"pos": "5x1",
"cmd": "FP=5&CL=hFF6347&C2=hFFBF47&C3=hA85859"
},
"0xFF18E7": {
"label": "LightGreen",
"pos": "5x2",
"cmnt": "Rivendale",
"cmd": "FP=14"
},
"0xFF7A85": {
"label": "SkyBlue",
"pos": "5x3",
"cmnt": "Ocean",
"cmd": "FP=9"
},
"0xFF10EF": {
"label": "Orange",
"pos": "6x1",
"cmnt": "Orangery",
"cmd": "FP=47"
},
"0xFF38C7": {
"label": "Aqua",
"pos": "6x2",
"cmd": "FP=5&CL=hFFFF&C2=h7FFF&C3=h39A895"
},
"0xFF5AA5": {
"label": "Purple",
"pos": "6x3",
"cmd": "FP=5&CL=h663399&C2=h993399&C3=h473864"
},
"0xFF42BD": {
"label": "Yellow",
"pos": "7x1",
"cmd": "FP=5&CL=hFFFF00&C2=hFFC800&C3=hFDFFDE"
},
"0xFF4AB5": {
"label": "Cyan",
"pos": "7x2",
"cmnt": "Beech",
"cmd": "FP=22"
},
"0xFF52AD": {
"label": "Pink",
"pos": "7x3",
"cmd": "FP=5&CL=hFFC0CB&C2=hFFD4C0&C3=hA88C96"
}
}

View File

@@ -0,0 +1,147 @@
{
"desc": "24-key",
"0xF700FF": {
"label": "+",
"pos": "1x1",
"cmnt": "Speed +",
"cmd": "SX=~16"
},
"0xF7807F": {
"label": "-",
"pos": "1x2",
"cmnt": "Speed -",
"cmd": "SX=~-16"
},
"0xF740BF": {
"label": "On/Off",
"pos": "1x3",
"cmnt": "Toggle On/Off",
"cmd": "T=2"
},
"0xF7C03F": {
"label": "W",
"pos": "1x4",
"cmnt": "Cycle color palette",
"cmd": "FP=~"
},
"0xF720DF": {
"label": "R",
"pos": "2x1",
"cmnt": "Lava",
"cmd": "FP=8"
},
"0xF7A05F": {
"label": "G",
"pos": "2x2",
"cmnt": "Forest",
"cmd": "FP=10"
},
"0xF7609F": {
"label": "B",
"pos": "2x3",
"cmnt": "Breeze",
"cmd": "FP=15"
},
"0xF7E01F": {
"label": "Bright -",
"pos": "2x4",
"cmnt": "Bright -",
"cmd": "A=~-16"
},
"0xF710EF": {
"label": "Timer1H",
"pos": "3x1",
"cmnt": "Timer 60 min",
"cmd": "NL=60&NT=0"
},
"0xF7906F": {
"label": "Timer4H",
"pos": "3x2",
"cmnt": "Timer 30 min",
"cmd": "NL=30&NT=0"
},
"0xF750AF": {
"label": "Timer8H",
"pos": "3x3",
"cmnt": "Timer 15 min",
"cmd": "NL=15&NT=0"
},
"0xF7D02F": {
"label": "Bright128",
"pos": "3x4",
"cmnt": "Bright 128",
"cmd": "A=128"
},
"0xF730CF": {
"label": "Music1",
"pos": "4x1",
"cmnt": "Cycle FX +",
"cmd": "FX=~"
},
"0xF7B04F": {
"label": "Music2",
"pos": "4x2",
"cmnt": "Cycle FX -",
"cmd": "FX=~-1"
},
"0xF7708F": {
"label": "Music3",
"pos": "4x3",
"cmnt": "Reset FX and FP",
"cmd": "FX=1&PF=6"
},
"0xF7F00F": {
"label": "Bright +",
"pos": "4x4",
"cmnt": "Bright +",
"cmd": "A=~16"
},
"0xF708F7": {
"label": "Mode1",
"pos": "5x1",
"cmnt": "Preset 1",
"cmd": "PL=1"
},
"0xF78877": {
"label": "Mode2",
"pos": "5x2",
"cmnt": "Preset 2",
"cmd": "PL=2"
},
"0xF748B7": {
"label": "Mode3",
"pos": "5x3",
"cmnt": "Preset 3",
"cmd": "PL=3"
},
"0xF7C837": {
"label": "Up",
"pos": "5x4",
"cmnt": "Intensity +",
"cmd": "IX=~16"
},
"0xF728D7": {
"label": "Mode4",
"pos": "6x1",
"cmnt": "Preset 4",
"cmd": "PL=4"
},
"0xF7A857": {
"label": "Mode5",
"pos": "6x2",
"cmnt": "Preset 5",
"cmd": "PL=5"
},
"0xF76897": {
"label": "Cycle",
"pos": "6x3",
"cmnt": "Toggle preset cycle",
"cmd": "CY=1&PT=60000"
},
"0xF7E817": {
"label": "Down",
"pos": "6x4",
"cmnt": "Intensity -",
"cmd": "IX=~-16"
}
}

View File

@@ -0,0 +1,185 @@
{
"desc": "32-key",
"0xFF08F7": {
"label": "On",
"pos": "1x1",
"cmd": "T=1"
},
"0xFFC03F": {
"label": "Off",
"pos": "1x2",
"cmd": "T=0"
},
"0xFF807F": {
"label": "Auto",
"pos": "1x3",
"cmnt": "Toggle preset cycle",
"cmd": "CY=2"
},
"0xFF609F": {
"label": "Mode",
"pos": "1x4",
"cmnt": "Cycle effects",
"cmd": "FX=~&CY=0"
},
"0xFF906F": {
"label": "4H",
"pos": "2x1",
"cmnt": "Timer 60min",
"cmd": "NL=60&NT=0"
},
"0xFFB847": {
"label": "6H",
"pos": "2x2",
"cmnt": "Timer 90min",
"cmd": "NL=90&NT=0"
},
"0xFFF807": {
"label": "8H",
"pos": "2x3",
"cmnt": "Timer 120min",
"cmd": "NL=120&NT=0"
},
"0xFFB04F": {
"label": "Timer Off",
"pos": "2x4",
"cmd": "NL=0"
},
"0xFF9867": {
"label": "Red",
"pos": "3x1",
"cmnt": "Lava",
"cmd": "FP=8"
},
"0xFFD827": {
"label": "Green",
"pos": "3x2",
"cmnt": "Forest",
"cmd": "FP=10"
},
"0xFF8877": {
"label": "Blue",
"pos": "3x3",
"cmnt": "Breeze",
"cmd": "FP=15"
},
"0xFFA857": {
"label": "White",
"pos": "3x4",
"cmd": "FP=5&CL=hFFFFFF&C2=hFFE4CD&C3=hE4E4FF"
},
"0xFFE817": {
"label": "OrangeRed",
"pos": "4x1",
"cmnt": "Sakura",
"cmd": "FP=49"
},
"0xFF48B7": {
"label": "SeaGreen",
"pos": "4x2",
"cmnt": "Rivendale",
"cmd": "FP=14"
},
"0xFF6897": {
"label": "RoyalBlue",
"pos": "4x3",
"cmnt": "Ocean",
"cmd": "FP=9"
},
"0xFFB24D": {
"label": "DarkBlue",
"pos": "4x4",
"cmnt": "Breeze",
"cmd": "FP=15"
},
"0xFF02FD": {
"label": "Orange",
"pos": "5x1",
"cmnt": "Orangery",
"cmd": "FP=47"
},
"0xFF32CD": {
"label": "YellowGreen",
"pos": "5x2",
"cmnt": "Aurora",
"cmd": "FP=37"
},
"0xFF20DF": {
"label": "SkyBlue",
"pos": "5x3",
"cmnt": "Beech",
"cmd": "FP=22"
},
"0xFF00FF": {
"label": "Orchid",
"pos": "5x4",
"cmd": "FP=5&CL=hDA70D6&C2=hDA70A0&C3=h89618F"
},
"0xFF50AF": {
"label": "Yellow",
"pos": "6x1",
"cmd": "FP=5&CL=hFFFF00&C2=hFFC800&C3=hFDFFDE"
},
"0xFF7887": {
"label": "DarkGreen",
"pos": "6x2",
"cmnt": "Orange and Teal",
"cmd": "FP=44"
},
"0xFF708F": {
"label": "RebeccaPurple",
"pos": "6x3",
"cmd": "FP=5&CL=h800080&C2=h800040&C3=h4B1C54"
},
"0xFF58A7": {
"label": "Plum",
"pos": "6x4",
"cmd": "FP=5&CL=hDDA0DD&C2=hDDA0BE&C3=h8D7791"
},
"0xFF38C7": {
"label": "Strobe",
"pos": "7x1",
"cmnt": "Dancing Shadows",
"cmd": "FX=112&CY=0"
},
"0xFF28D7": {
"label": "In Waves",
"pos": "7x2",
"cmnt": "Noise 1",
"cmd": "FX=70&CY=0"
},
"0xFFF00F": {
"label": "Speed +",
"pos": "7x3",
"cmd": "SX=~16"
},
"0xFF30CF": {
"label": "Speed -",
"pos": "7x4",
"cmd": "SX=~-16"
},
"0xFF40BF": {
"label": "Jump",
"pos": "8x1",
"cmnt": "Colortwinkles",
"cmd": "FX=74&CY=0"
},
"0xFF12ED": {
"label": "Fade",
"pos": "8x2",
"cmnt": "Sunrise",
"cmd": "FX=104&CY=0"
},
"0xFF2AD5": {
"label": "Flash",
"pos": "8x3",
"cmnt": "Railway",
"cmd": "FX=78&CY=0"
},
"0xFFA05F": {
"label": "Chase Flash",
"pos": "8x4",
"cmnt": "Washing Machine",
"cmd": "FX=113&CY=0"
}
}

View File

@@ -0,0 +1,233 @@
{
"desc": "40-key-black",
"0xFF3AC5": {
"label": "Bright +",
"pos": "1x1",
"cmd": "A=~16"
},
"0xFFBA45": {
"label": "Bright -",
"pos": "1x2",
"cmd": "A=~-16"
},
"0xFF827D": {
"label": "Off",
"pos": "1x3",
"cmd": "T=0"
},
"0xFF02FD": {
"label": "On",
"pos": "1x4",
"cmd": "T=1"
},
"0xFF1AE5": {
"label": "Red",
"pos": "2x1",
"cmnt": "Lava",
"cmd": "FP=8"
},
"0xFF9A65": {
"label": "Green",
"pos": "2x2",
"cmnt": "Forest",
"cmd": "FP=10"
},
"0xFFA25D": {
"label": "Blue",
"pos": "2x3",
"cmnt": "Breeze",
"cmd": "FP=15"
},
"0xFF22DD": {
"label": "White",
"pos": "2x4",
"cmd": "FP=5&CL=hFFFFFF&C2=hFFFFFF&C3=hA8A8A8"
},
"0xFF2AD5": {
"label": "Tomato",
"pos": "3x1",
"cmnt": "Yelmag",
"cmd": "FP=5&CL=hFF6347&C2=hFFBF47&C3=hA85859"
},
"0xFFAA55": {
"label": "LightGreen",
"pos": "3x2",
"cmnt": "Rivendale",
"cmd": "FP=14"
},
"0xFF926D": {
"label": "SkyBlue",
"pos": "3x3",
"cmnt": "Ocean",
"cmd": "FP=9"
},
"0xFF12ED": {
"label": "WarmWhite",
"pos": "3x4",
"cmnt": "Warm White",
"cmd": "FP=5&CL=hFFE4CD&C2=hFFFCCD&C3=hA89892"
},
"0xFF0AF5": {
"label": "OrangeRed",
"pos": "4x1",
"cmnt": "Sakura",
"cmd": "FP=49"
},
"0xFF8A75": {
"label": "Cyan",
"pos": "4x2",
"cmnt": "Beech",
"cmd": "FP=22"
},
"0xFFB24D": {
"label": "RebeccaPurple",
"pos": "4x3",
"cmd": "FP=5&CL=h663399&C2=h993399&C3=h473864"
},
"0xFF32CD": {
"label": "CoolWhite",
"pos": "4x4",
"cmnt": "Cool White",
"cmd": "FP=5&CL=hE4E4FF&C2=hF1E4FF&C3=h9C9EA8"
},
"0xFF38C7": {
"label": "Orange",
"pos": "5x1",
"cmnt": "Orangery",
"cmd": "FP=47"
},
"0xFFB847": {
"label": "Turquoise",
"pos": "5x2",
"cmd": "FP=5&CL=h40E0D0&C2=h40A0E0&C3=h4E9381"
},
"0xFF7887": {
"label": "Purple",
"pos": "5x3",
"cmd": "FP=5&CL=h800080&C2=h800040&C3=h4B1C54"
},
"0xFFF807": {
"label": "MedGray",
"pos": "5x4",
"cmnt": "Cycle palette +",
"cmd": "FP=~"
},
"0xFF18E7": {
"label": "Yellow",
"pos": "6x1",
"cmd": "FP=5&CL=hFFFF00&C2=h7FFF00&C3=hA89539"
},
"0xFF9867": {
"label": "DarkCyan",
"pos": "6x2",
"cmd": "FP=5&CL=h8B8B&C2=h458B&C3=h1F5B51"
},
"0xFF58A7": {
"label": "Plum",
"pos": "6x3",
"cmnt": "Magenta",
"cmd": "FP=40"
},
"0xFFD827": {
"label": "DarkGray",
"pos": "6x4",
"cmnt": "Cycle palette -",
"cmd": "FP=~-"
},
"0xFF28D7": {
"label": "Jump3",
"pos": "7x1",
"cmnt": "Colortwinkles",
"cmd": "CY=0&FX=74"
},
"0xFFA857": {
"label": "Fade3",
"pos": "7x2",
"cmnt": "Rain",
"cmd": "CY=0&FX=43"
},
"0xFF6897": {
"label": "Flash",
"pos": "7x3",
"cmnt": "Cycle Effects",
"cmd": "CY=0&FX=~"
},
"0xFFE817": {
"label": "Quick",
"pos": "7x4",
"cmnt": "Fx speed +16",
"cmd": "SX=~16"
},
"0xFF08F7": {
"label": "Jump7",
"pos": "8x1",
"cmnt": "Sinelon Dual",
"cmd": "CY=0&FX=93"
},
"0xFF8877": {
"label": "Fade7",
"pos": "8x2",
"cmnt": "Lighthouse",
"cmd": "CY=0&FX=41"
},
"0xFF48B7": {
"label": "Auto",
"pos": "8x3",
"cmnt": "Toggle preset cycle",
"cmd": "CY=2"
},
"0xFFC837": {
"label": "Slow",
"pos": "8x4",
"cmnt": "FX speed -16",
"cmd": "SX=~-16"
},
"0xFF30CF": {
"label": "Custom1",
"pos": "9x1",
"cmnt": "Noise 1",
"cmd": "CY=0&FX=70"
},
"0xFFB04F": {
"label": "Custom2",
"pos": "9x2",
"cmnt": "Dancing Shadows",
"cmd": "CY=0&FX=112"
},
"0xFF708F": {
"label": "Music +",
"pos": "9x3",
"cmnt": "FX Intensity +16",
"cmd": "IX=~16"
},
"0xFFF00F": {
"label": "Timer60",
"pos": "9x4",
"cmnt": "Timer 60 min",
"cmd": "NL=60&NT=0"
},
"0xFF10EF": {
"label": "Custom3",
"pos": "10x1",
"cmnt": "Twinklefox",
"cmd": "CY=0&FX=80"
},
"0xFF906F": {
"label": "Custom4",
"pos": "10x2",
"cmnt": "Twinklecat",
"cmd": "CY=0&FX=81"
},
"0xFF50AF": {
"label": "Music -",
"pos": "10x3",
"cmnt": "FX Intesity -16",
"cmd": "IX=~-16"
},
"0xFFD02F": {
"label": "Timer120",
"pos": "10x4",
"cmnt": "Timer 120 min",
"cmd": "NL=120&NT=0"
}
}

View File

@@ -0,0 +1,217 @@
{
"desc": "40-key-blue",
"0xFF3AC5": {
"label": "Bright +",
"pos": "1x1",
"cmd": "A=~16"
},
"0xFFBA45": {
"label": "Bright -",
"pos": "1x2",
"cmd": "A=~-16"
},
"0xFF827D": {
"label": "Off",
"pos": "1x3",
"cmd": "T=0"
},
"0xFF02FD": {
"label": "On",
"pos": "1x4",
"cmd": "T=1"
},
"0xFF1AE5": {
"label": "Red",
"pos": "2x1",
"cmnt": "Lava",
"cmd": "FP=8"
},
"0xFF9A65": {
"label": "Green",
"pos": "2x2",
"cmnt": "Forest",
"cmd": "FP=10"
},
"0xFFA25D": {
"label": "Blue",
"pos": "2x3",
"cmnt": "Breeze",
"cmd": "FP=15"
},
"0xFF22DD": {
"label": "White",
"pos": "2x4",
"cmd": "FP=5&CL=hFFFFFF&C2=hFFFFFF&C3=hA8A8A8"
},
"0xFF2AD5": {
"label": "Tomato",
"pos": "3x1",
"cmnt": "Yelmag",
"cmd": "FP=5&CL=hFF6347&C2=hFFBF47&C3=hA85859"
},
"0xFFAA55": {
"label": "LightGreen",
"pos": "3x2",
"cmnt": "Rivendale",
"cmd": "FP=14"
},
"0xFF926D": {
"label": "SkyBlue",
"pos": "3x3",
"cmnt": "Ocean",
"cmd": "FP=9"
},
"0xFF12ED": {
"label": "WarmWhite",
"pos": "3x4",
"cmnt": "Warm White",
"cmd": "FP=5&CL=hFFE4CD&C2=hFFFCCD&C3=hA89892"
},
"0xFF0AF5": {
"label": "OrangeRed",
"pos": "4x1",
"cmnt": "Sakura",
"cmd": "FP=49"
},
"0xFF8A75": {
"label": "Cyan",
"pos": "4x2",
"cmnt": "Beech",
"cmd": "FP=22"
},
"0xFFB24D": {
"label": "RebeccaPurple",
"pos": "4x3",
"cmd": "FP=5&CL=h663399&C2=h993399&C3=h473864"
},
"0xFF32CD": {
"label": "CoolWhite",
"pos": "4x4",
"cmnt": "Cool White",
"cmd": "FP=5&CL=hE4E4FF&C2=hF1E4FF&C3=h9C9EA8"
},
"0xFF38C7": {
"label": "Orange",
"pos": "5x1",
"cmnt": "Orangery",
"cmd": "FP=47"
},
"0xFFB847": {
"label": "Turquoise",
"pos": "5x2",
"cmd": "FP=5&CL=h40E0D0&C2=h40A0E0&C3=h4E9381"
},
"0xFF7887": {
"label": "Purple",
"pos": "5x3",
"cmd": "FP=5&CL=h800080&C2=h800040&C3=h4B1C54"
},
"0xFFF807": {
"label": "MedGray",
"pos": "5x4",
"cmnt": "Cycle palette +",
"cmd": "FP=~"
},
"0xFF18E7": {
"label": "Yellow",
"pos": "6x1",
"cmd": "FP=5&CL=hFFFF00&C2=h7FFF00&C3=hA89539"
},
"0xFF9867": {
"label": "DarkCyan",
"pos": "6x2",
"cmd": "FP=5&CL=h8B8B&C2=h458B&C3=h1F5B51"
},
"0xFF58A7": {
"label": "Plum",
"pos": "6x3",
"cmnt": "Magenta",
"cmd": "FP=40"
},
"0xFFD827": {
"label": "DarkGray",
"pos": "6x4",
"cmnt": "Cycle palette -",
"cmd": "FP=~-"
},
"0xFF28D7": {
"label": "W +",
"pos": "7x1"
},
"0xFFA857": {
"label": "W -",
"pos": "7x2"
},
"0xFF6897": {
"label": "W On",
"pos": "7x3"
},
"0xFFE817": {
"label": "W Off",
"pos": "7x4"
},
"0xFF08F7": {
"label": "W25",
"pos": "8x1"
},
"0xFF8877": {
"label": "W50",
"pos": "8x2"
},
"0xFF48B7": {
"label": "W75",
"pos": "8x3"
},
"0xFFC837": {
"label": "W100",
"pos": "8x4"
},
"0xFF30CF": {
"label": "Jump3",
"pos": "9x1",
"cmnt": "Colortwinkles",
"cmd": "CY=0&FX=74"
},
"0xFFB04F": {
"label": "Fade3",
"pos": "9x2",
"cmnt": "Rain",
"cmd": "CY=0&FX=43"
},
"0xFF708F": {
"label": "Jump7",
"pos": "9x3",
"cmnt": "Sinelon Dual",
"cmd": "CY=0&FX=93"
},
"0xFFF00F": {
"label": "Quick",
"pos": "9x4",
"cmnt": "Fx speed +16",
"cmd": "SX=~16"
},
"0xFF10EF": {
"label": "Fade",
"pos": "10x1",
"cmnt": "Lighthouse",
"cmd": "CY=0&FX=41"
},
"0xFF906F": {
"label": "Flash",
"pos": "10x2",
"cmnt": "Cycle Effects",
"cmd": "CY=0&FX=~"
},
"0xFF50AF": {
"label": "Auto",
"pos": "10x3",
"cmnt": "Toggle preset cycle",
"cmd": "CY=2"
},
"0xFFD02F": {
"label": "Slow",
"pos": "10x4",
"cmnt": "Sinelon Dual",
"cmd": "CY=0&FX=93"
}
}

View File

@@ -0,0 +1,241 @@
{
"desc": "44-key",
"0xFF3AC5": {
"label": "Bright +",
"pos": "1x1",
"cmd": "A=~16"
},
"0xFFBA45": {
"label": "Bright -",
"pos": "1x2",
"cmd": "A=~-16"
},
"0xFF827D": {
"label": "Off",
"pos": "1x3",
"cmd": "T=0"
},
"0xFF02FD": {
"label": "On",
"pos": "1x4",
"cmd": "T=1"
},
"0xFF1AE5": {
"label": "Red",
"pos": "2x1",
"cmnt": "Lava",
"cmd": "FP=8"
},
"0xFF9A65": {
"label": "Green",
"pos": "2x2",
"cmnt": "Forest",
"cmd": "FP=10"
},
"0xFFA25D": {
"label": "Blue",
"pos": "2x3",
"cmnt": "Breeze",
"cmd": "FP=15"
},
"0xFF22DD": {
"label": "White",
"pos": "2x4",
"cmd": "FP=5&CL=hFFFFFF&C2=hFFFFFF&C3=hA8A8A8"
},
"0xFF2AD5": {
"label": "Tomato",
"pos": "3x1",
"cmd": "FP=5&CL=hFF6347&C2=hFFBF47&C3=hA85859"
},
"0xFFAA55": {
"label": "LightGreen",
"pos": "3x2",
"cmnt": "Rivendale",
"cmd": "FP=14"
},
"0xFF926D": {
"label": "DeepBlue",
"pos": "3x3",
"cmnt": "Ocean",
"cmd": "FP=9"
},
"0xFF12ED": {
"label": "Warmwhite2",
"pos": "3x4",
"cmnt": "Warm White",
"cmd": "FP=5&CL=hFFE4CD&C2=hFFFCCD&C3=hA89892"
},
"0xFF0AF5": {
"label": "Orange",
"pos": "4x1",
"cmnt": "Sakura",
"cmd": "FP=49"
},
"0xFF8A75": {
"label": "Turquoise",
"pos": "4x2",
"cmnt": "Beech",
"cmd": "FP=22"
},
"0xFFB24D": {
"label": "Purple",
"pos": "4x3",
"cmd": "FP=5&CL=h663399&C2=h993399&C3=h473864"
},
"0xFF32CD": {
"label": "WarmWhite",
"pos": "4x4",
"cmd": "FP=5&CL=hE4E4FF&C2=hF1E4FF&C3=h9C9EA8"
},
"0xFF38C7": {
"label": "Yellowish",
"pos": "5x1",
"cmnt": "Orangery",
"cmd": "FP=47"
},
"0xFFB847": {
"label": "Cyan",
"pos": "5x2",
"cmnt": "Beech",
"cmd": "FP=22"
},
"0xFF7887": {
"label": "Magenta",
"pos": "5x3",
"cmd": "FP=5&CL=hFF00FF&C2=hFF007F&C3=h9539A8"
},
"0xFFF807": {
"label": "ColdWhite",
"pos": "5x4",
"cmd": "FP=5&CL=hE4E4FF&C2=hF1E4FF&C3=h9C9EA8"
},
"0xFF18E7": {
"label": "Yellow",
"pos": "6x1",
"cmd": "FP=5&CL=hFFFF00&C2=hFFC800&C3=hFDFFDE"
},
"0xFF9867": {
"label": "Aqua",
"pos": "6x2",
"cmd": "FP=5&CL=hFFFF&C2=h7FFF&C3=h39A895"
},
"0xFF58A7": {
"label": "Pink",
"pos": "6x3",
"cmd": "FP=5&CL=hFFC0CB&C2=hFFD4C0&C3=hA88C96"
},
"0xFFD827": {
"label": "ColdWhite2",
"pos": "6x4",
"cmd": "FP=5&CL=hE4E4FF&C2=hF1E4FF&C3=h9C9EA8"
},
"0xFF28D7": {
"label": "Red +",
"pos": "7x1",
"cmd": "FP=5&R=~16"
},
"0xFFA857": {
"label": "Green +",
"pos": "7x2",
"cmd": "FP=5&G=~16"
},
"0xFF6897": {
"label": "Blue +",
"pos": "7x3",
"cmd": "FP=5&B=~16"
},
"0xFFE817": {
"label": "Quick",
"pos": "7x4",
"cmnt": "Fx speed +16",
"cmd": "SX=~16"
},
"0xFF08F7": {
"label": "Red -",
"pos": "8x1",
"cmd": "FP=5&R=~-16"
},
"0xFF8877": {
"label": "Green -",
"pos": "8x2",
"cmd": "FP=5&G=~-16"
},
"0xFF48B7": {
"label": "Blue -",
"pos": "8x3",
"cmd": "FP=5&B=~-16"
},
"0xFFC837": {
"label": "Slow",
"pos": "8x4",
"cmnt": "FX speed -16",
"cmd": "SX=~-16"
},
"0xFF30CF": {
"label": "Diy1",
"pos": "9x1",
"cmd": "CY=0&PL=1"
},
"0xFFB04F": {
"label": "Diy2",
"pos": "9x2",
"cmd": "CY=0&PL=2"
},
"0xFF708F": {
"label": "Diy3",
"pos": "9x3",
"cmd": "CY=0&PL=3"
},
"0xFFF00F": {
"label": "Auto",
"pos": "9x4",
"cmnt": "Toggle preset cycle",
"cmd": "CY=2"
},
"0xFF10EF": {
"label": "Diy4",
"pos": "10x1",
"cmd": "CY=0&PL=4"
},
"0xFF906F": {
"label": "Diy5",
"pos": "10x2",
"cmd": "CY=0&PL=5"
},
"0xFF50AF": {
"label": "Diy6",
"pos": "10x3",
"cmd": "CY=0&PL=6"
},
"0xFFD02F": {
"label": "Flash",
"pos": "10x4",
"cmnt": "Cycle Effects",
"cmd": "CY=0&FX=~"
},
"0xFF20DF": {
"label": "Jump3",
"pos": "11x1",
"cmnt": "Colortwinkles",
"cmd": "CY=0&FX=74"
},
"0xFFA05F": {
"label": "Jump7",
"pos": "11x2",
"cmnt": "Sinelon Dual",
"cmd": "CY=0&FX=93"
},
"0xFF609F": {
"label": "Fade3",
"pos": "11x3",
"cmnt": "Rain",
"cmd": "CY=0&FX=43"
},
"0xFFE01F": {
"label": "Fade7",
"pos": "11x4",
"cmnt": "Lighthouse",
"cmd": "CY=0&FX=41"
}
}

View File

@@ -0,0 +1,38 @@
{
"desc": "6-key",
"0xFF0FF0": {
"label": "Power",
"pos": "1x1",
"cmd": "T=2"
},
"0xFF8F70": {
"label": "Channel +",
"pos": "2x1",
"cmnt": "Cycle palette up",
"cmd": "FP=~"
},
"0xFF4FB0": {
"label": "Channel -",
"pos": "3x1",
"cmnt": "Cycle palette down",
"cmd": "FP=~-"
},
"0xFFCF30": {
"label": "Volume +",
"pos": "4x1",
"cmnt": "Brighten",
"cmd": "A=~16"
},
"0xFF2FD0": {
"label": "Volume -",
"pos": "5x1",
"cmnt": "Dim",
"cmd": "A=~-16"
},
"0xFFAF50": {
"label": "Mute",
"pos": "6x1",
"cmnt": "Cycle effects",
"cmd": "CY=0&FX=~"
}
}

View File

@@ -0,0 +1,47 @@
{
"desc": "9-key",
"0xFF629D": {
"label": "Power",
"cmd": "T=2"
},
"0xFF22DD": {
"label": "A",
"cmnt": "Preset 1",
"cmd": "PL=1"
},
"0xFF02FD": {
"label": "B",
"cmnt": "Preset 2",
"cmd": "PL=2"
},
"0xFFC23D": {
"label": "C",
"cmnt": "Preset 3",
"cmd": "PL=3"
},
"0xFF30CF": {
"label": "Left",
"cmnt": "Speed -",
"cmd": "SI=~-16"
},
"0xFF7A85": {
"label": "Right",
"cmnt": "Speed +",
"cmd": "SI=~16"
},
"0xFF9867": {
"label": "Up",
"cmnt": "Bright +",
"cmd": "A=~16"
},
"0xFF38C7": {
"label": "Down",
"cmnt": "Bright -",
"cmd": "A=~-16"
},
"0xFF18E7": {
"label": "Select",
"cmnt": "Cycle effects",
"cmd": "CY=0&FX=~"
}
}

Binary file not shown.

View File

@@ -0,0 +1,108 @@
import colorsys
import json
import openpyxl
named_colors = {'AliceBlue': '0xF0F8FF', 'AntiqueWhite': '0xFAEBD7', 'Aqua': '0x00FFFF',
'Aquamarine': '0x7FFFD4', 'Azure': '0xF0FFFF', 'Beige': '0xF5F5DC', 'Bisque': '0xFFE4C4',
'Black': '0x000000', 'BlanchedAlmond': '0xFFEBCD', 'Blue': '0x0000FF',
'BlueViolet': '0x8A2BE2', 'Brown': '0xA52A2A', 'BurlyWood': '0xDEB887',
'CadetBlue': '0x5F9EA0', 'Chartreuse': '0x7FFF00', 'Chocolate': '0xD2691E',
'Coral': '0xFF7F50', 'CornflowerBlue': '0x6495ED', 'Cornsilk': '0xFFF8DC',
'Crimson': '0xDC143C', 'Cyan': '0x00FFFF', 'DarkBlue': '0x00008B', 'DarkCyan': '0x008B8B',
'DarkGoldenRod': '0xB8860B', 'DarkGray': '0xA9A9A9', 'DarkGrey': '0xA9A9A9',
'DarkGreen': '0x006400', 'DarkKhaki': '0xBDB76B', 'DarkMagenta': '0x8B008B',
'DarkOliveGreen': '0x556B2F', 'DarkOrange': '0xFF8C00', 'DarkOrchid': '0x9932CC',
'DarkRed': '0x8B0000', 'DarkSalmon': '0xE9967A', 'DarkSeaGreen': '0x8FBC8F',
'DarkSlateBlue': '0x483D8B', 'DarkSlateGray': '0x2F4F4F', 'DarkSlateGrey': '0x2F4F4F',
'DarkTurquoise': '0x00CED1', 'DarkViolet': '0x9400D3', 'DeepPink': '0xFF1493',
'DeepSkyBlue': '0x00BFFF', 'DimGray': '0x696969', 'DimGrey': '0x696969',
'DodgerBlue': '0x1E90FF', 'FireBrick': '0xB22222', 'FloralWhite': '0xFFFAF0',
'ForestGreen': '0x228B22', 'Fuchsia': '0xFF00FF', 'Gainsboro': '0xDCDCDC',
'GhostWhite': '0xF8F8FF', 'Gold': '0xFFD700', 'GoldenRod': '0xDAA520', 'Gray': '0x808080',
'Grey': '0x808080', 'Green': '0x008000', 'GreenYellow': '0xADFF2F', 'HoneyDew': '0xF0FFF0',
'HotPink': '0xFF69B4', 'IndianRed': '0xCD5C5C', 'Indigo': '0x4B0082', 'Ivory': '0xFFFFF0',
'Khaki': '0xF0E68C', 'Lavender': '0xE6E6FA', 'LavenderBlush': '0xFFF0F5',
'LawnGreen': '0x7CFC00', 'LemonChiffon': '0xFFFACD', 'LightBlue': '0xADD8E6',
'LightCoral': '0xF08080', 'LightCyan': '0xE0FFFF', 'LightGoldenRodYellow': '0xFAFAD2',
'LightGray': '0xD3D3D3', 'LightGrey': '0xD3D3D3', 'LightGreen': '0x90EE90',
'LightPink': '0xFFB6C1', 'LightSalmon': '0xFFA07A', 'LightSeaGreen': '0x20B2AA',
'LightSkyBlue': '0x87CEFA', 'LightSlateGray': '0x778899', 'LightSlateGrey': '0x778899',
'LightSteelBlue': '0xB0C4DE', 'LightYellow': '0xFFFFE0', 'Lime': '0x00FF00',
'LimeGreen': '0x32CD32', 'Linen': '0xFAF0E6', 'Magenta': '0xFF00FF', 'Maroon': '0x800000',
'MediumAquaMarine': '0x66CDAA', 'MediumBlue': '0x0000CD', 'MediumOrchid': '0xBA55D3',
'MediumPurple': '0x9370DB', 'MediumSeaGreen': '0x3CB371', 'MediumSlateBlue': '0x7B68EE',
'MediumSpringGreen': '0x00FA9A', 'MediumTurquoise': '0x48D1CC', 'MediumVioletRed': '0xC71585',
'MidnightBlue': '0x191970', 'MintCream': '0xF5FFFA', 'MistyRose': '0xFFE4E1',
'Moccasin': '0xFFE4B5', 'NavajoWhite': '0xFFDEAD', 'Navy': '0x000080', 'OldLace': '0xFDF5E6',
'Olive': '0x808000', 'OliveDrab': '0x6B8E23', 'Orange': '0xFFA500', 'OrangeRed': '0xFF4500',
'Orchid': '0xDA70D6', 'PaleGoldenRod': '0xEEE8AA', 'PaleGreen': '0x98FB98',
'PaleTurquoise': '0xAFEEEE', 'PaleVioletRed': '0xDB7093', 'PapayaWhip': '0xFFEFD5',
'PeachPuff': '0xFFDAB9', 'Peru': '0xCD853F', 'Pink': '0xFFC0CB', 'Plum': '0xDDA0DD',
'PowderBlue': '0xB0E0E6', 'Purple': '0x800080', 'RebeccaPurple': '0x663399', 'Red': '0xFF0000',
'RosyBrown': '0xBC8F8F', 'RoyalBlue': '0x4169E1', 'SaddleBrown': '0x8B4513', 'Salmon': '0xFA8072',
'SandyBrown': '0xF4A460', 'SeaGreen': '0x2E8B57', 'SeaShell': '0xFFF5EE', 'Sienna': '0xA0522D',
'Silver': '0xC0C0C0', 'SkyBlue': '0x87CEEB', 'SlateBlue': '0x6A5ACD', 'SlateGray': '0x708090',
'SlateGrey': '0x708090', 'Snow': '0xFFFAFA', 'SpringGreen': '0x00FF7F', 'SteelBlue': '0x4682B4',
'Tan': '0xD2B48C', 'Teal': '0x008080', 'Thistle': '0xD8BFD8', 'Tomato': '0xFF6347',
'Turquoise': '0x40E0D0', 'Violet': '0xEE82EE', 'Wheat': '0xF5DEB3', 'White': '0xFFFFFF',
'WhiteSmoke': '0xF5F5F5', 'Yellow': '0xFFFF00', 'YellowGreen': '0x9ACD32'}
def shift_color(col, shift=30, sat=1.0, val=1.0):
r = (col & (255 << 16)) >> 16
g = (col & (255 << 8)) >> 8
b = col & 255
hsv = colorsys.rgb_to_hsv(r, g, b)
h = (((hsv[0] * 360) + shift) % 360) / 360
rgb = colorsys.hsv_to_rgb(h, hsv[1] * sat, hsv[2] * val)
return (int(rgb[0]) << 16) + (int(rgb[1]) << 8) + int(rgb[2])
def parse_sheet(ws):
print(f'Parsing worksheet {ws.title}')
ir = {"desc": ws.title}
rows = ws.rows
keys = [col.value.lower() for col in next(rows)]
for row in rows:
rec = dict(zip(keys, [col.value for col in row]))
if rec.get('code') is None:
continue
cd = {"label": rec.get('label')}
if rec.get('row'):
cd['pos'] = f'{rec["row"]}x{rec["col"]}'
if rec.get('comment'):
cd['cmnt'] = rec.get('comment')
if rec.get('rpt'):
cd['rpt'] = bool(rec['rpt'])
if rec.get('cmd'):
cd['cmd'] = rec['cmd']
elif all((rec.get('primary'), rec.get('secondary'), rec.get('tertiary'))):
c1 = int(rec.get('primary'), 16)
c2 = int(rec.get('secondary'), 16)
c3 = int(rec.get('tertiary'), 16)
cd['cmd'] = f'FP=5&CL=h{c1:X}&C2=h{c2:X}&C3=h{c3:X}'
elif all((rec.get('primary'), rec.get('secondary'))):
c1 = int(rec.get('primary'), 16)
c2 = int(rec.get('secondary'), 16)
c3 = shift_color(c1, -1, sat=0.66, val=0.66)
cd['cmd'] = f'FP=5&CL=h{c1:X}&C2=h{c2:X}&C3=h{c3:X}'
elif rec.get('primary'):
c1 = int(rec.get('primary'), 16)
c2 = shift_color(c1, 30)
c3 = shift_color(c1, -10, sat=0.66, val=0.66)
cd['cmd'] = f'FP=5&CL=h{c1:X}&C2=h{c2:X}&C3=h{c3:X}'
elif rec.get('label') in named_colors:
c1 = int(named_colors[rec.get('label')], 16)
c2 = shift_color(c1, 30)
c3 = shift_color(c1, -10, sat=0.66, val=0.66)
cd['cmd'] = f'FP=5&CL=h{c1:X}&C2=h{c2:X}&C3=h{c3:X}'
else:
print(f'Did not find a command or color for {rec["label"]}. Hint use named CSS colors as labels')
ir[rec['code']] = cd
with open(f'{ws.title}_ir.json', 'w') as fp:
json.dump(ir, fp, indent=2)
if __name__ == '__main__':
wb = openpyxl.load_workbook('IR_Remote_Codes.xlsx')
for ws in wb.worksheets:
parse_sheet(ws)

View File

@@ -0,0 +1,33 @@
# JSON IR remote
## Purpose
The JSON IR remote allows users to customize IR remote behavior without writing custom code and compiling.
It also enables using any remote that is compatible with your IR receiver. Using the JSON IR remote, you can
map buttons from any remote to any HTTP request API or JSON API command.
## Usage
* Upload the IR config file, named _ir.json_ to your board using the [ip address]/edit url. Pick from one of the included files or create your own.
* On the config > LED settings page, set the correct IR pin.
* On the config > Sync Interfaces page, select "JSON Remote" as the Infrared remote.
## Modification
* See if there is a json file with the same number of buttons as your remote. Many remotes will have the same internals and emit the same codes but have different labels.
* In the ir.json file, each key will be the hex encoded IR code.
* The "cmd" property will be the HTTP Request API or JSON API to execute when that button is pressed.
* A limited number of c functions are supported (!incBrightness, !decBrightness, !presetFallback)
* When using !presetFallback, include properties PL (preset to load), FX (effect to fall back to) and FP (palette to fall back to)
* If the command is _repeatable_ and does not contain the "~" character, add a "rpt": true property.
* Other properties are ignored, but having a label property may help when editing.
Sample:
{
"0xFF629D": {"cmd": "T=2", "rpt": true, "label": "Toggle on/off"}, // HTTP command
"0xFF9867": {"cmd": "A=~16", "label": "Inc brightness"}, // HTTP command with incrementing
"0xFF38C7": {"cmd": {"bri": 10}, "label": "Dim to 10"}, // JSON command
"0xFF22DD": {"cmd": "!presetFallback", "PL": 1, "FX": 16, "FP": 6,
"label": "Preset 1 or fallback to Saw - Party"}, // c function
}

View File

@@ -62,7 +62,7 @@ class PIRsensorSwitch : public Usermod {
// PIR sensor pin
const uint8_t PIRsensorPin = 13; // D7 on D1 mini
// notification mode for colorUpdated()
const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE
const byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // CALL_MODE_DIRECT_CHANGE
// 1 min delay before switch off after the sensor state goes LOW
uint32_t m_switchOffDelay = 60000;
// off timer start time

View File

@@ -58,7 +58,7 @@ private:
// PIR sensor pin
int8_t PIRsensorPin = PIR_SENSOR_PIN;
// notification mode for colorUpdated()
const byte NotifyUpdateMode = NOTIFIER_CALL_MODE_NO_NOTIFY; // NOTIFIER_CALL_MODE_DIRECT_CHANGE
const byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // CALL_MODE_DIRECT_CHANGE
// delay before switch off after the sensor state goes LOW
uint32_t m_switchOffDelay = 600000; // 10min
// off timer start time
@@ -76,6 +76,9 @@ private:
bool m_nightTimeOnly = false;
// flag to send MQTT message only (assuming it is enabled)
bool m_mqttOnly = false;
// flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR)
bool m_offOnly = false;
bool PIRtriggered = false;
unsigned long lastLoop = 0;
@@ -87,6 +90,7 @@ private:
static const char _offPreset[];
static const char _nightTime[];
static const char _mqttOnly[];
static const char _offOnly[];
/**
* check if it is daytime
@@ -118,6 +122,8 @@ private:
*/
void switchStrip(bool switchOn)
{
if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return;
PIRtriggered = switchOn;
if (switchOn && m_onPreset) {
applyPreset(m_onPreset);
} else if (!switchOn && m_offPreset) {
@@ -193,16 +199,18 @@ public:
*/
void setup()
{
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (!pinManager.allocatePin(PIRsensorPin,false)) {
PIRsensorPin = -1; // allocation failed
enabled = false;
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
} else {
// PIR Sensor mode INPUT_PULLUP
pinMode(PIRsensorPin, INPUT_PULLUP);
if (enabled) {
if (enabled) {
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
// PIR Sensor mode INPUT_PULLUP
pinMode(PIRsensorPin, INPUT_PULLUP);
sensorPinState = digitalRead(PIRsensorPin);
} else {
if (PIRsensorPin >= 0) {
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
}
PIRsensorPin = -1; // allocation failed
enabled = false;
}
}
initDone = true;
@@ -221,8 +229,8 @@ public:
*/
void loop()
{
// only check sensors 10x/s
if (millis() - lastLoop < 100 || strip.isUpdating()) return;
// only check sensors 4x/s
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
lastLoop = millis();
if (!updatePIRsensorState()) {
@@ -313,6 +321,7 @@ public:
top[FPSTR(_offPreset)] = m_offPreset;
top[FPSTR(_nightTime)] = m_nightTimeOnly;
top[FPSTR(_mqttOnly)] = m_mqttOnly;
top[FPSTR(_offOnly)] = m_offOnly;
DEBUG_PRINTLN(F("PIR config saved."));
}
@@ -348,6 +357,7 @@ public:
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
DEBUG_PRINT(FPSTR(_name));
if (!initDone) {
@@ -359,8 +369,8 @@ public:
if (oldPin != PIRsensorPin && oldPin >= 0) {
// if we are changing pin in settings page
// deallocate old pin
pinManager.deallocatePin(oldPin);
if (pinManager.allocatePin(PIRsensorPin,false)) {
pinManager.deallocatePin(oldPin, PinOwner::UM_PIR);
if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
pinMode(PIRsensorPin, INPUT_PULLUP);
} else {
// allocation failed
@@ -375,7 +385,7 @@ public:
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
return !top[FPSTR(_offOnly)].isNull();
}
/**
@@ -396,3 +406,4 @@ const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";

View File

@@ -0,0 +1,36 @@
# PWM fan
v2 Usermod to to control PWM fan with RPM feedback and temperature control
This usermod requires Dallas Temperature usermod to obtain temperature information. If this is not available the fan will always run at 100% speed.
If the fan does not have _tacho_ (RPM) output you can set the _tacho-pin_ to -1 to not use that feature.
You can also set the thershold temperature at which fan runs at lowest speed. If the actual temperature measured will be 3°C greater than threshold temperature the fan will run at 100%.
If the _tacho_ is supported the current speed (in RPM) will be repored in WLED Info page.
## Installation
Add the compile-time option `-D USERMOD_PWM_FAN` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_PWM_FAN` in `myconfig.h`.
You will also need `-D USERMOD_DALLASTEMPERATURE`.
### Define Your Options
All of the parameters are configured during run-time using Usermods settings page.
This includes:
* PWM output pin
* tacho input pin
* sampling frequency in seconds
* threshold temperature in degees C
_NOTE:_ You may also need to tweak Dallas Temperature usermod sampling frequency to match PWM fan sampling frequency.
### PlatformIO requirements
No special requirements.
## Change Log
2021-10
* First public release

View File

@@ -0,0 +1,332 @@
#pragma once
#ifndef USERMOD_DALLASTEMPERATURE
#error The "PWM fan" usermod requires "Dallas Temeprature" usermod to function properly.
#endif
#include "wled.h"
// PWM & tacho code curtesy of @KlausMu
// https://github.com/KlausMu/esp32-fan-controller/tree/main/src
// adapted for WLED usermod by @blazoncek
// tacho counter
static volatile unsigned long counter_rpm = 0;
// Interrupt counting every rotation of the fan
// https://desire.giesecke.tk/index.php/2018/01/30/change-global-variables-from-isr/
static void IRAM_ATTR rpm_fan() {
counter_rpm++;
}
class PWMFanUsermod : public Usermod {
private:
bool initDone = false;
bool enabled = true;
unsigned long msLastTachoMeasurement = 0;
uint16_t last_rpm = 0;
#ifdef ARDUINO_ARCH_ESP32
uint8_t pwmChannel = 255;
#endif
#ifdef USERMOD_DALLASTEMPERATURE
UsermodTemperature* tempUM;
#endif
// configurable parameters
int8_t tachoPin = -1;
int8_t pwmPin = -1;
uint8_t tachoUpdateSec = 30;
float targetTemperature = 25.0;
uint8_t minPWMValuePct = 50;
uint8_t numberOfInterrupsInOneSingleRotation = 2; // Number of interrupts ESP32 sees on tacho signal on a single fan rotation. All the fans I've seen trigger two interrups.
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
static const char _tachoPin[];
static const char _pwmPin[];
static const char _temperature[];
static const char _tachoUpdateSec[];
static const char _minPWMValuePct[];
static const char _IRQperRotation[];
void initTacho(void) {
if (tachoPin < 0 || !pinManager.allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){
tachoPin = -1;
return;
}
pinMode(tachoPin, INPUT);
digitalWrite(tachoPin, HIGH);
attachInterrupt(digitalPinToInterrupt(tachoPin), rpm_fan, FALLING);
DEBUG_PRINTLN(F("Tacho sucessfully initialized."));
}
void deinitTacho(void) {
if (tachoPin < 0) return;
detachInterrupt(digitalPinToInterrupt(tachoPin));
pinManager.deallocatePin(tachoPin, PinOwner::UM_Unspecified);
tachoPin = -1;
}
void updateTacho(void) {
if (tachoPin < 0) return;
// start of tacho measurement
// detach interrupt while calculating rpm
detachInterrupt(digitalPinToInterrupt(tachoPin));
// calculate rpm
last_rpm = (counter_rpm * 60) / numberOfInterrupsInOneSingleRotation;
last_rpm /= tachoUpdateSec;
// reset counter
counter_rpm = 0;
// store milliseconds when tacho was measured the last time
msLastTachoMeasurement = millis();
// attach interrupt again
attachInterrupt(digitalPinToInterrupt(tachoPin), rpm_fan, FALLING);
}
// https://randomnerdtutorials.com/esp32-pwm-arduino-ide/
void initPWMfan(void) {
if (pwmPin < 0 || !pinManager.allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) {
pwmPin = -1;
return;
}
#ifdef ESP8266
analogWriteRange(255);
analogWriteFreq(WLED_PWM_FREQ);
#else
pwmChannel = pinManager.allocateLedc(1);
if (pwmChannel == 255) { //no more free LEDC channels
deinitPWMfan(); return;
}
// configure LED PWM functionalitites
ledcSetup(pwmChannel, 25000, 8);
// attach the channel to the GPIO to be controlled
ledcAttachPin(pwmPin, pwmChannel);
#endif
DEBUG_PRINTLN(F("Fan PWM sucessfully initialized."));
}
void deinitPWMfan(void) {
if (pwmPin < 0) return;
pinManager.deallocatePin(pwmPin, PinOwner::UM_Unspecified);
#ifdef ARDUINO_ARCH_ESP32
pinManager.deallocateLedc(pwmChannel, 1);
#endif
pwmPin = -1;
}
void updateFanSpeed(uint8_t pwmValue){
if (pwmPin < 0) return;
#ifdef ESP8266
analogWrite(pwmPin, pwmValue);
#else
ledcWrite(pwmChannel, pwmValue);
#endif
}
float getActualTemperature(void) {
#ifdef USERMOD_DALLASTEMPERATURE
if (tempUM != nullptr)
return tempUM->getTemperatureC();
#endif
return -127.0f;
}
void setFanPWMbasedOnTemperature(void) {
float temp = getActualTemperature();
float difftemp = temp - targetTemperature;
// Default to run fan at full speed.
int newPWMvalue = 255;
int pwmStep = ((100 - minPWMValuePct) * newPWMvalue) / (7*100);
int pwmMinimumValue = (minPWMValuePct * newPWMvalue) / 100;
if ((temp == NAN) || (temp <= 0.0)) {
DEBUG_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255."));
} else if (difftemp <= 0.0) {
// Temperature is below target temperature. Run fan at minimum speed.
newPWMvalue = pwmMinimumValue;
} else if (difftemp <= 0.5) {
newPWMvalue = pwmMinimumValue + pwmStep;
} else if (difftemp <= 1.0) {
newPWMvalue = pwmMinimumValue + 2*pwmStep;
} else if (difftemp <= 1.5) {
newPWMvalue = pwmMinimumValue + 3*pwmStep;
} else if (difftemp <= 2.0) {
newPWMvalue = pwmMinimumValue + 4*pwmStep;
} else if (difftemp <= 2.5) {
newPWMvalue = pwmMinimumValue + 5*pwmStep;
} else if (difftemp <= 3.0) {
newPWMvalue = pwmMinimumValue + 6*pwmStep;
}
updateFanSpeed(newPWMvalue);
}
public:
// gets called once at boot. Do all initialization that doesn't depend on
// network here
void setup() {
#ifdef USERMOD_DALLASTEMPERATURE
// This Usermod requires Temperature usermod
tempUM = (UsermodTemperature*) usermods.lookup(USERMOD_ID_TEMPERATURE);
#endif
initTacho();
initPWMfan();
updateFanSpeed((minPWMValuePct * 255) / 100); // inital fan speed
initDone = true;
}
// gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here
void connected() {}
/*
* Da loop.
*/
void loop() {
if (!enabled || strip.isUpdating()) return;
unsigned long now = millis();
if ((now - msLastTachoMeasurement) < (tachoUpdateSec * 1000)) return;
updateTacho();
setFanPWMbasedOnTemperature();
}
/*
* 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) {
if (tachoPin < 0) return;
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
JsonArray data = user.createNestedArray(FPSTR(_name));
data.add(last_rpm);
data.add(F("rpm"));
}
/*
* 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) {
// if (!initDone) return; // prevent crash on boot applyPreset()
//}
/*
* 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 also not yet add your setting to one of the settings pages automatically.
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
*
* 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(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = enabled;
top[FPSTR(_pwmPin)] = pwmPin;
top[FPSTR(_tachoPin)] = tachoPin;
top[FPSTR(_tachoUpdateSec)] = tachoUpdateSec;
top[FPSTR(_temperature)] = targetTemperature;
top[FPSTR(_minPWMValuePct)] = minPWMValuePct;
top[FPSTR(_IRQperRotation)] = numberOfInterrupsInOneSingleRotation;
DEBUG_PRINTLN(F("Autosave config saved."));
}
/*
* 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 once immediately after boot)
*
* 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 :)
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool readFromConfig(JsonObject& root) {
int8_t newTachoPin = tachoPin;
int8_t newPwmPin = pwmPin;
JsonObject top = root[FPSTR(_name)];
DEBUG_PRINT(FPSTR(_name));
if (top.isNull()) {
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
enabled = top[FPSTR(_enabled)] | enabled;
newTachoPin = top[FPSTR(_tachoPin)] | newTachoPin;
newPwmPin = top[FPSTR(_pwmPin)] | newPwmPin;
tachoUpdateSec = top[FPSTR(_tachoUpdateSec)] | tachoUpdateSec;
tachoUpdateSec = (uint8_t) max(1,(int)tachoUpdateSec); // bounds checking
targetTemperature = top[FPSTR(_temperature)] | targetTemperature;
minPWMValuePct = top[FPSTR(_minPWMValuePct)] | minPWMValuePct;
minPWMValuePct = (uint8_t) min(100,max(0,(int)minPWMValuePct)); // bounds checking
numberOfInterrupsInOneSingleRotation = top[FPSTR(_IRQperRotation)] | numberOfInterrupsInOneSingleRotation;
numberOfInterrupsInOneSingleRotation = (uint8_t) max(1,(int)numberOfInterrupsInOneSingleRotation); // bounds checking
if (!initDone) {
// first run: reading from cfg.json
tachoPin = newTachoPin;
pwmPin = newPwmPin;
DEBUG_PRINTLN(F(" config loaded."));
} else {
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing paramters from settings page
if (tachoPin != newTachoPin || pwmPin != newPwmPin) {
DEBUG_PRINTLN(F("Re-init pins."));
// deallocate pin and release interrupts
deinitTacho();
deinitPWMfan();
tachoPin = newTachoPin;
pwmPin = newPwmPin;
// initialise
setup();
}
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_IRQperRotation)].isNull();
}
/*
* 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_PWM_FAN;
}
};
// strings to reduce flash memory usage (used more than twice)
const char PWMFanUsermod::_name[] PROGMEM = "PWM-fan";
const char PWMFanUsermod::_enabled[] PROGMEM = "enabled";
const char PWMFanUsermod::_tachoPin[] PROGMEM = "tacho-pin";
const char PWMFanUsermod::_pwmPin[] PROGMEM = "PWM-pin";
const char PWMFanUsermod::_temperature[] PROGMEM = "target-temp-C";
const char PWMFanUsermod::_tachoUpdateSec[] PROGMEM = "tacho-update-s";
const char PWMFanUsermod::_minPWMValuePct[] PROGMEM = "min-PWM-percent";
const char PWMFanUsermod::_IRQperRotation[] PROGMEM = "IRQs-per-rotation";

View File

@@ -0,0 +1,72 @@
# ST7789 TFT IPS Color display 240x240pxwith ESP32 boards
This usermod allow to use 240x240 display to display following:
* Network SSID;
* IP address;
* Brightness;
* Chosen effect;
* Chosen palette;
* Estimated current in mA;
## Hardware
***
![Hardware](images/ST7789_guide.jpg)
## Library used
[Bodmer/TFT_eSPI](https://github.com/Bodmer/TFT_eSPI)
## Setup
***
### Platformio.ini changes
In the `platformio.ini` file, uncomment the `TFT_eSPI` line within the [common] section, under `lib_deps`:
```ini
# platformio.ini
...
[common]
...
lib_deps =
...
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
#TFT_eSPI
...
```
Also, while in the `platformio.ini` file, you must change the environment setup to build for just the esp32dev platform as follows:
Add lines to section:
```ini
default_envs = esp32dev
build_flags = ${common.build_flags_esp32}
-D USERMOD_ST7789_DISPLAY
```
Save the `platformio.ini` file. Once this is saved, the required library files should be automatically downloaded for modifications in a later step.
### TFT_eSPI Library Adjustments
We need to modify a file in the `TFT_eSPI` library. If you followed the directions to modify and save the `platformio.ini` file above, the `User_Setup_Select.h` file can be found in the `/.pio/libdeps/esp32dev/TFT_eSPI` folder.
Modify the `User_Setup_Select.h` file as follows:
* Comment out the following line (which is the 'default' setup file):
```ini
//#include <User_Setup.h> // Default setup is root library folder
```
* Add following line:
```ini
#include <User_Setups/Setup_ST7789_Display.h> // Setup file for ESP32 ST7789V SPI bus TFT
```
* Copy file `"Setup_ST7789_Display.h"` from usermod folder to `/.pio/libdeps/esp32dev/TFT_eSPI/User_Setups`

View File

@@ -0,0 +1,350 @@
// Credits to @mrVanboy, @gwaland and my dearest friend @westward
// Also for @spiff72 for usermod TTGO-T-Display
// 210217
#pragma once
#include "wled.h"
#include <TFT_eSPI.h>
#include <SPI.h>
#define USERMOD_ST7789_DISPLAY 97
#ifndef TFT_DISPOFF
#define TFT_DISPOFF 0x28
#endif
#ifndef TFT_SLPIN
#define TFT_SLPIN 0x10
#endif
#define TFT_MOSI 21
#define TFT_SCLK 22
#define TFT_DC 18
#define TFT_RST 5
#define TFT_BL 26 // Display backlight control pin
TFT_eSPI tft = TFT_eSPI(240, 240); // Invoke custom library
// How often we are redrawing screen
#define USER_LOOP_REFRESH_RATE_MS 1000
//class name. Use something descriptive and leave the ": public Usermod" part :)
class St7789DisplayUsermod : public Usermod {
private:
//Private class members. You can declare variables and functions only accessible to your usermod here
unsigned long lastTime = 0;
bool displayTurnedOff = false;
long lastRedraw = 0;
// needRedraw marks if redraw is required to prevent often redrawing.
bool needRedraw = true;
// Next variables hold the previous known values to determine if redraw is required.
String knownSsid = "";
IPAddress knownIp;
uint8_t knownBrightness = 0;
uint8_t knownMode = 0;
uint8_t knownPalette = 0;
uint8_t tftcharwidth = 19; // Number of chars that fit on screen with text size set to 2
long lastUpdate = 0;
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()
{
tft.init();
tft.setRotation(0); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip.
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_RED);
tft.setCursor(60, 100);
tft.setTextDatum(MC_DATUM);
tft.setTextSize(2);
tft.print("Loading...");
if (TFT_BL > 0)
{ // TFT_BL has been set in the TFT_eSPI library
pinMode(TFT_BL, OUTPUT); // Set backlight pin to output mode
digitalWrite(TFT_BL, HIGH); // Turn backlight on.
}
}
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected() {
//Serial.println("Connected to WiFi!");
}
/*
* 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() {
// Check if we time interval for redrawing passes.
if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS)
{
return;
}
lastUpdate = millis();
// Turn off display after 5 minutes with no change.
if(!displayTurnedOff && millis() - lastRedraw > 5*60*1000)
{
digitalWrite(TFT_BL, LOW); // Turn backlight off.
displayTurnedOff = true;
}
// Check if values which are shown on display changed from the last time.
if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid)
{
needRedraw = true;
}
else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP()))
{
needRedraw = true;
}
else if (knownBrightness != bri)
{
needRedraw = true;
}
else if (knownMode != strip.getMode())
{
needRedraw = true;
}
else if (knownPalette != strip.getSegment(0).palette)
{
needRedraw = true;
}
if (!needRedraw)
{
return;
}
needRedraw = false;
if (displayTurnedOff)
{
digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); // Turn backlight on.
displayTurnedOff = false;
}
lastRedraw = millis();
// Update last known values.
#if defined(ESP8266)
knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
#else
knownSsid = WiFi.SSID();
#endif
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
knownBrightness = bri;
knownMode = strip.getMode();
knownPalette = strip.getSegment(0).palette;
tft.fillScreen(TFT_BLACK);
tft.setTextSize(2);
// First row with Wifi name
tft.setTextColor(TFT_SILVER);
tft.setCursor(3, 40);
tft.print(knownSsid.substring(0, tftcharwidth > 1 ? tftcharwidth - 1 : 0));
// Print `~` char to indicate that SSID is longer, than our dicplay
if (knownSsid.length() > tftcharwidth)
tft.print("~");
// Second row with AP IP and Password or IP
tft.setTextColor(TFT_GREEN);
tft.setTextSize(2);
tft.setCursor(3, 64);
// Print AP IP and password in AP mode or knownIP if AP not active.
if (apActive)
{
tft.setTextColor(TFT_YELLOW);
tft.print("AP IP: ");
tft.print(knownIp);
tft.setCursor(3,86);
tft.setTextColor(TFT_YELLOW);
tft.print("AP Pass:");
tft.print(apPass);
}
else
{
tft.setTextColor(TFT_GREEN);
tft.print("IP: ");
tft.print(knownIp);
tft.setCursor(3,86);
//tft.print("Signal Strength: ");
//tft.print(i.wifi.signal);
tft.setTextColor(TFT_WHITE);
tft.print("Bri: ");
tft.print(((float(bri)/255)*100),0);
tft.print("%");
}
// Third row with mode name
tft.setCursor(3, 108);
uint8_t qComma = 0;
bool insideQuotes = false;
uint8_t printedChars = 0;
char singleJsonSymbol;
// Find the mode name in JSON
for (size_t i = 0; i < strlen_P(JSON_mode_names); i++)
{
singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i);
switch (singleJsonSymbol)
{
case '"':
insideQuotes = !insideQuotes;
break;
case '[':
case ']':
break;
case ',':
qComma++;
default:
if (!insideQuotes || (qComma != knownMode))
break;
tft.setTextColor(TFT_MAGENTA);
tft.print(singleJsonSymbol);
printedChars++;
}
if ((qComma > knownMode) || (printedChars > tftcharwidth - 1))
break;
}
// Fourth row with palette name
tft.setTextColor(TFT_YELLOW);
tft.setCursor(3, 130);
qComma = 0;
insideQuotes = false;
printedChars = 0;
// Looking for palette name in JSON.
for (size_t i = 0; i < strlen_P(JSON_palette_names); i++)
{
singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i);
switch (singleJsonSymbol)
{
case '"':
insideQuotes = !insideQuotes;
break;
case '[':
case ']':
break;
case ',':
qComma++;
default:
if (!insideQuotes || (qComma != knownPalette))
break;
tft.print(singleJsonSymbol);
printedChars++;
}
// The following is modified from the code from the u8g2/u8g8 based code (knownPalette was knownMode)
if ((qComma > knownPalette) || (printedChars > tftcharwidth - 1))
break;
}
// Fifth row with estimated mA usage
tft.setTextColor(TFT_SILVER);
tft.setCursor(3, 152);
// Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate).
tft.print("Current: ");
tft.print(strip.currentMilliamps);
tft.print("mA");
}
/*
* 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)
{
int reading = 20;
//this code adds "u":{"Light":[20," lux"]} to the info object
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
JsonArray lightArr = user.createNestedArray("Light"); //name
lightArr.add(reading); //value
lightArr.add(" lux"); //unit
}
*/
/*
* 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["user0"] = userVar0;
}
/*
* 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)
{
userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
}
/*
* 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 also not yet add your setting to one of the settings pages automatically.
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
*
* 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("exampleUsermod");
top["great"] = userVar0; //save this var persistently whenever settings are saved
}
/*
* 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 once immediately after boot)
*
* 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 :)
*/
void readFromConfig(JsonObject& root)
{
JsonObject top = root["top"];
userVar0 = top["great"] | 42; //The value right of the pipe "|" is the default value in case your setting was not present in cfg.json (e.g. first boot)
}
/*
* 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_ST7789_DISPLAY;
}
//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,39 @@
// Setup for the ESP32 board with 1.5" 240x240 display
// See SetupX_Template.h for all options available
#define ST7789_DRIVER
#define TFT_SDA_READ // Display has a bidirectionsl SDA pin
#define TFT_WIDTH 240
#define TFT_HEIGHT 240
#define CGRAM_OFFSET // Library will add offsets required
//#define TFT_MISO -1
#define TFT_MOSI 21
#define TFT_SCLK 22
//#define TFT_CS 5
#define TFT_DC 18
#define TFT_RST 5
#define TFT_BL 26 // Display backlight control pin
#define TFT_BACKLIGHT_ON HIGH // HIGH or LOW are options
#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF
//#define SMOOTH_FONT
//#define SPI_FREQUENCY 27000000
#define SPI_FREQUENCY 40000000 // Maximum for ILI9341
#define SPI_READ_FREQUENCY 6000000 // 6 MHz is the maximum SPI read speed for the ST7789V

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

View File

@@ -42,6 +42,8 @@ class UsermodTemperature : public Usermod {
bool waitingForConversion = false;
// flag set at startup if DS18B20 sensor not found, avoids trying to keep getting
// temperature if flashed to a board without a sensor attached
bool sensorFound = false;
bool enabled = true;
// strings to reduce flash memory usage (used more than twice)
@@ -79,7 +81,9 @@ class UsermodTemperature : public Usermod {
temperature = readDallas();
lastMeasurement = millis();
waitingForConversion = false;
DEBUG_PRINTF("Read temperature %2.1f.\n", temperature);
//DEBUG_PRINTF("Read temperature %2.1f.\n", temperature); // does not work properly on 8266
DEBUG_PRINT(F("Read temperature "));
DEBUG_PRINTLN(temperature);
}
bool findSensor() {
@@ -87,7 +91,9 @@ class UsermodTemperature : public Usermod {
uint8_t deviceAddress[8] = {0,0,0,0,0,0,0,0};
// find out if we have DS18xxx sensor attached
oneWire->reset_search();
delay(10);
while (oneWire->search(deviceAddress)) {
DEBUG_PRINTLN(F("Found something..."));
if (oneWire->crc8(deviceAddress, 7) == deviceAddress[7]) {
switch (deviceAddress[0]) {
case 0x10: // DS18S20
@@ -107,21 +113,28 @@ class UsermodTemperature : public Usermod {
void setup() {
int retries = 10;
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (!pinManager.allocatePin(temperaturePin,false)) {
temperaturePin = -1; // allocation failed
enabled = false;
DEBUG_PRINTLN(F("Temperature pin allocation failed."));
} else {
if (enabled) {
// config says we are enabled
if (enabled) {
// config says we are enabled
DEBUG_PRINTLN(F("Allocating temperature pin..."));
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) {
oneWire = new OneWire(temperaturePin);
if (!oneWire->reset())
enabled = false; // resetting 1-Wire bus yielded an error
else
while ((enabled=findSensor()) && retries--) delay(25); // try to find sensor
if (!oneWire->reset()) {
sensorFound = false; // resetting 1-Wire bus yielded an error
} else {
while ((sensorFound=findSensor()) && retries--) {
delay(25); // try to find sensor
}
}
} else {
if (temperaturePin >= 0) {
DEBUG_PRINTLN(F("Temperature pin allocation failed."));
}
temperaturePin = -1; // allocation failed
sensorFound = false;
}
}
lastMeasurement = millis() - readingInterval + 10000;
initDone = true;
}
@@ -189,7 +202,7 @@ class UsermodTemperature : public Usermod {
JsonArray temp = user.createNestedArray(FPSTR(_name));
//temp.add(F("Loaded."));
if (temperature <= -100) {
if (temperature <= -100.0 || (!sensorFound && temperature == -1.0)) {
temp.add(0);
temp.add(F(" Sensor Error!"));
return;
@@ -249,6 +262,7 @@ class UsermodTemperature : public Usermod {
enabled = top[FPSTR(_enabled)] | enabled;
newTemperaturePin = top["pin"] | newTemperaturePin;
// newTemperaturePin = min(33,max(-1,(int)newTemperaturePin)); // bounds check
degC = top["degC"] | degC;
readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000;
readingInterval = min(120,max(10,(int)readingInterval)) * 1000; // convert to ms
@@ -260,16 +274,17 @@ class UsermodTemperature : public Usermod {
temperaturePin = newTemperaturePin;
DEBUG_PRINTLN(F(" config loaded."));
} else {
// changing parameters from settings page
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing paramters from settings page
if (newTemperaturePin != temperaturePin) {
DEBUG_PRINTLN(F("Re-init temperature."));
// deallocate pin and release memory
delete oneWire;
pinManager.deallocatePin(temperaturePin);
pinManager.deallocatePin(temperaturePin, PinOwner::UM_Temperature);
temperaturePin = newTemperaturePin;
// initialise
setup();
}
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_parasite)].isNull();

View File

@@ -54,46 +54,46 @@ void userLoop()
switch (myKey) {
case '1':
applyPreset(1);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '2':
applyPreset(2);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '3':
applyPreset(3);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '4':
applyPreset(4);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '5':
applyPreset(5);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '6':
applyPreset(6);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case 'A':
applyPreset(7);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case 'B':
applyPreset(8);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '7':
effectCurrent += 1;
if (effectCurrent >= MODE_COUNT) effectCurrent = 0;
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '*':
effectCurrent -= 1;
if (effectCurrent < 0) effectCurrent = (MODE_COUNT-1);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '8':
@@ -102,7 +102,7 @@ void userLoop()
} else if (effectSpeed < 255) {
effectSpeed += 1;
}
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '0':
if (effectSpeed > 15) {
@@ -110,7 +110,7 @@ void userLoop()
} else if (effectSpeed > 0) {
effectSpeed -= 1;
}
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '9':
@@ -119,7 +119,7 @@ void userLoop()
} else if (effectIntensity < 255) {
effectIntensity += 1;
}
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case '#':
if (effectIntensity > 15) {
@@ -127,18 +127,18 @@ void userLoop()
} else if (effectIntensity > 0) {
effectIntensity -= 1;
}
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case 'C':
effectPalette += 1;
if (effectPalette >= 50) effectPalette = 0;
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
case 'D':
effectPalette -= 1;
if (effectPalette <= 0) effectPalette = 50;
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
break;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -0,0 +1,69 @@
# :battery: Battery status/level Usermod :battery:
This Usermod allows you to monitor the battery level of your battery powered project.
You can see the battery level and voltage in the `info modal`.
For this to work the positive side of the (18650) battery must be connected to pin `A0` of the d1mini/esp8266 with a 100k ohm resistor (see [Useful Links](#useful-links)).
If you have a esp32 board it is best to connect the positive side of the battery to ADC1 (GPIO32 - GPIO39)
<p align="center">
<img width="300" src="assets/battery_info_screen.png">
</p>
## Installation
define `USERMOD_BATTERY_STATUS_BASIC` in `my_config.h`
### Basic wiring diagram
<p align="center">
<img width="300" src="assets/battery_connection_schematic_01.png">
</p>
### Define Your Options
* `USERMOD_BATTERY_STATUS_BASIC` - define this (in `my_config.h`) to have this user mod included wled00\usermods_list.cpp
* `USERMOD_BATTERY_MEASUREMENT_PIN` - defaults to A0 on esp8266 and GPIO32 on esp32
* `USERMOD_BATTERY_MEASUREMENT_INTERVAL` - the frequency to check the battery, defaults to 30 seconds
* `USERMOD_BATTERY_MIN_VOLTAGE` - minimum voltage of the Battery used, default is 2.6 (18650 battery standard)
* `USERMOD_BATTERY_MAX_VOLTAGE` - maximum voltage of the Battery used, default is 4.2 (18650 battery standard)
All parameters can be configured at runtime using Usermods settings page.
## Important :warning:
* Make sure you know your battery specification ! not every battery is the same !
* Example:
| Your battery specification table | | Options you can define |
| :-------------------------------- |:--------------- | :---------------------------- |
| Capacity | 3500mAh 12,5 Wh | |
| Minimum capacity | 3350mAh 11,9 Wh | |
| Rated voltage | 3.6V - 3.7V | |
| **Charging end voltage** | **4,2V ± 0,05** | `USERMOD_BATTERY_MAX_VOLTAGE` |
| **Discharge voltage** | **2,5V** | `USERMOD_BATTERY_MIN_VOLTAGE` |
| Max. discharge current (constant) | 10A (10000mA) | |
| max. charging current | 1.7A (1700mA) | |
| ... | ... | ... |
| .. | .. | .. |
Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.6V - 3.7V](https://www.akkuteile.de/lithium-ionen-akkus/18650/molicel/molicel-inr18650-m35a-3500mah-10a-lithium-ionen-akku-3-6v-3-7v_100833)
## Useful Links
* https://lazyzero.de/elektronik/esp8266/wemos_d1_mini_a0/start
* https://arduinodiy.wordpress.com/2016/12/25/monitoring-lipo-battery-voltage-with-wemos-d1-minibattery-shield-and-thingspeak/
## Change Log
2021-09-02
* added "Battery voltage" to info
* added circuit diagram to readme
* added MQTT support, sending battery voltage
* minor fixes
2021-08-15
* changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries
* Updated readme, added specification table
2021-08-10
* Created

View File

@@ -0,0 +1,398 @@
#pragma once
#include "wled.h"
// pin defaults
// for the esp32 it is best to use the ADC1: GPIO32 - GPIO39
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html
#ifndef USERMOD_BATTERY_MEASUREMENT_PIN
#ifdef ARDUINO_ARCH_ESP32
#define USERMOD_BATTERY_MEASUREMENT_PIN 32
#else //ESP8266 boards
#define USERMOD_BATTERY_MEASUREMENT_PIN A0
#endif
#endif
// esp32 has a 12bit adc resolution
// esp8266 only 10bit
#ifndef USERMOD_BATTERY_ADC_PRECISION
#ifdef ARDUINO_ARCH_ESP32
// 12 bits
#define USERMOD_BATTERY_ADC_PRECISION 4095.0
#else
// 10 bits
#define USERMOD_BATTERY_ADC_PRECISION 1024.0
#endif
#endif
// the frequency to check the battery, 30 sec
#ifndef USERMOD_BATTERY_MEASUREMENT_INTERVAL
#define USERMOD_BATTERY_MEASUREMENT_INTERVAL 30000
#endif
// default for 18650 battery
// https://batterybro.com/blogs/18650-wholesale-battery-reviews/18852515-when-to-recycle-18650-batteries-and-how-to-start-a-collection-center-in-your-vape-shop
// Discharge voltage: 2.5 volt + .1 for personal safety
#ifndef USERMOD_BATTERY_MIN_VOLTAGE
#define USERMOD_BATTERY_MIN_VOLTAGE 2.6
#endif
#ifndef USERMOD_BATTERY_MAX_VOLTAGE
#define USERMOD_BATTERY_MAX_VOLTAGE 4.2
#endif
class UsermodBatteryBasic : public Usermod
{
private:
// battery pin can be defined in my_config.h
int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN;
// how often to read the battery voltage
unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL;
unsigned long nextReadTime = 0;
unsigned long lastReadTime = 0;
// battery min. voltage
float minBatteryVoltage = USERMOD_BATTERY_MIN_VOLTAGE;
// battery max. voltage
float maxBatteryVoltage = USERMOD_BATTERY_MAX_VOLTAGE;
// 0 - 1024 for esp8266 (10-bit resolution)
// 0 - 4095 for esp32 (Default is 12-bit resolution)
float adcPrecision = USERMOD_BATTERY_ADC_PRECISION;
// raw analog reading
float rawValue = 0.0;
// calculated voltage
float voltage = 0.0;
// mapped battery level based on voltage
long batteryLevel = 0;
bool initDone = false;
bool initializing = true;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _readInterval[];
// custom map function
// https://forum.arduino.cc/t/floating-point-using-map-function/348113/2
double mapf(double x, double in_min, double in_max, double out_min, double out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
float truncate(float val, byte dec)
{
float x = val * pow(10, dec);
float y = round(x);
float z = x - y;
if ((int)z == 5)
{
y++;
}
x = y / pow(10, dec);
return x;
}
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()
{
#ifdef ARDUINO_ARCH_ESP32
DEBUG_PRINTLN(F("Allocating battery pin..."));
if (batteryPin >= 0 && pinManager.allocatePin(batteryPin, false))
{
DEBUG_PRINTLN(F("Battery pin allocation succeeded."));
} else {
if (batteryPin >= 0) DEBUG_PRINTLN(F("Battery pin allocation failed."));
batteryPin = -1; // allocation failed
}
#else //ESP8266 boards have only one analog input pin A0
pinMode(batteryPin, INPUT);
#endif
nextReadTime = millis() + readingInterval;
lastReadTime = millis();
initDone = true;
}
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
//Serial.println("Connected to WiFi!");
}
/*
* loop() is called continuously. Here you can check for events, read sensors, etc.
*
*/
void loop()
{
if(strip.isUpdating()) return;
// check the battery level every USERMOD_BATTERY_MEASUREMENT_INTERVAL (ms)
if (millis() < nextReadTime) return;
nextReadTime = millis() + readingInterval;
lastReadTime = millis();
initializing = false;
// read battery raw input
rawValue = analogRead(batteryPin);
// calculate the voltage
voltage = (rawValue / adcPrecision) * maxBatteryVoltage ;
// check if voltage is within specified voltage range
voltage = voltage<minBatteryVoltage||voltage>maxBatteryVoltage?-1.0f:voltage;
// translate battery voltage into percentage
/*
the standard "map" function doesn't work
https://www.arduino.cc/reference/en/language/functions/math/map/ notes and warnings at the bottom
*/
batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100);
// SmartHome stuff
if (WLED_MQTT_CONNECTED) {
char subuf[64];
strcpy(subuf, mqttDeviceTopic);
strcat_P(subuf, PSTR("/voltage"));
mqtt->publish(subuf, 0, false, String(voltage).c_str());
}
}
/*
* 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)
{
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
// info modal display names
JsonArray batteryPercentage = user.createNestedArray("Battery level");
JsonArray batteryVoltage = user.createNestedArray("Battery voltage");
if (initializing) {
batteryPercentage.add((nextReadTime - millis()) / 1000);
batteryPercentage.add(" sec");
batteryVoltage.add((nextReadTime - millis()) / 1000);
batteryVoltage.add(" sec");
return;
}
if(batteryLevel < 0) {
batteryPercentage.add(F("invalid"));
} else {
batteryPercentage.add(batteryLevel);
}
batteryPercentage.add(F(" %"));
if(voltage < 0) {
batteryVoltage.add(F("invalid"));
} else {
batteryVoltage.add(truncate(voltage, 2));
}
batteryVoltage.add(F(" V"));
}
/*
* 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)
{
// created JSON object:
/*
{
"Battery-Level": {
"pin": "A0", <--- only when using esp32 boards
"minBatteryVoltage": 2.6,
"maxBatteryVoltage": 4.2,
"read-interval-ms": 30000
}
}
*/
JsonObject battery = root.createNestedObject(FPSTR(_name)); // usermodname
#ifdef ARDUINO_ARCH_ESP32
battery["pin"] = batteryPin; // usermodparam
#endif
battery["minBatteryVoltage"] = minBatteryVoltage; // usermodparam
battery["maxBatteryVoltage"] = maxBatteryVoltage; // usermodparam
battery[FPSTR(_readInterval)] = readingInterval;
DEBUG_PRINTLN(F("Battery config saved."));
}
/*
* 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)
{
// looking for JSON object:
/*
{
"BatteryLevel": {
"pin": "A0", <--- only when using esp32 boards
"minBatteryVoltage": 2.6,
"maxBatteryVoltage": 4.2,
"read-interval-ms": 30000
}
}
*/
#ifdef ARDUINO_ARCH_ESP32
int8_t newBatteryPin = batteryPin;
#endif
JsonObject battery = root[FPSTR(_name)];
if (battery.isNull())
{
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
#ifdef ARDUINO_ARCH_ESP32
newBatteryPin = battery["pin"] | newBatteryPin;
#endif
minBatteryVoltage = battery["minBatteryVoltage"] | minBatteryVoltage;
//minBatteryVoltage = min(12.0f, (int)readingInterval);
maxBatteryVoltage = battery["maxBatteryVoltage"] | maxBatteryVoltage;
//maxBatteryVoltage = min(14.4f, max(3.3f,(int)readingInterval));
readingInterval = battery["read-interval-ms"] | readingInterval;
readingInterval = max(3000, (int)readingInterval); // minimum repetition is >5000ms (5s)
DEBUG_PRINT(FPSTR(_name));
#ifdef ARDUINO_ARCH_ESP32
if (!initDone)
{
// first run: reading from cfg.json
newBatteryPin = batteryPin;
DEBUG_PRINTLN(F(" config loaded."));
}
else
{
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing paramters from settings page
if (newBatteryPin != batteryPin)
{
// deallocate pin
pinManager.deallocatePin(batteryPin);
batteryPin = newBatteryPin;
// initialise
setup();
}
}
#endif
return !battery[FPSTR(_readInterval)].isNull();
}
/*
* 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_BATTERY_STATUS_BASIC;
}
};
// strings to reduce flash memory usage (used more than twice)
const char UsermodBatteryBasic::_name[] PROGMEM = "Battery-level";
const char UsermodBatteryBasic::_readInterval[] PROGMEM = "read-interval-ms";

View File

@@ -16,6 +16,12 @@ Examples
1. 4 relays at all, relay 2 will be toggled: `http://[device-ip]/relays?toggle=0,1,0,0`
2. 3 relays at all, relay 1&3 will be switched on: `http://[device-ip]/relays?switch=1,0,1`
## JSON API
You can switch relay state using the following JSON object transmitted to: `http://[device-ip]/json`
Switch relay 0 on: `{"MultiRelay":{"relay":0,"on":true}}`
Switch relay4 3 & 4 off: `{"MultiRelay":[{"relay":2,"on":false},{"relay":3,"on":false}]}`
## MQTT API
wled/deviceMAC/relay/0/command on|off|toggle

View File

@@ -6,6 +6,8 @@
#define MULTI_RELAY_MAX_RELAYS 4
#endif
#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
#define ON true
#define OFF false
@@ -23,6 +25,7 @@ typedef struct relay_t {
bool state;
bool external;
uint16_t delay;
int8_t button;
} Relay;
@@ -35,7 +38,7 @@ class MultiRelay : public Usermod {
// switch timer start time
uint32_t _switchTimerStart = 0;
// old brightness
bool _oldBrightness = 0;
bool _oldMode;
// usermod enabled
bool enabled = false; // needs to be configured (no default config)
@@ -49,6 +52,7 @@ class MultiRelay : public Usermod {
static const char _delay_str[];
static const char _activeHigh[];
static const char _external[];
static const char _button[];
void publishMqtt(const char* state, int relay) {
@@ -170,6 +174,7 @@ class MultiRelay : public Usermod {
_relay[i].active = false;
_relay[i].state = false;
_relay[i].external = false;
_relay[i].button = -1;
}
}
/**
@@ -258,14 +263,15 @@ class MultiRelay : public Usermod {
// pins retrieved from cfg.json (readFromConfig()) prior to running setup()
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin<0) continue;
if (!pinManager.allocatePin(_relay[i].pin,true)) {
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
_relay[i].pin = -1; // allocation failed
} else {
switchRelay(i, _relay[i].state = (bool)bri);
if (!_relay[i].external) _relay[i].state = offMode;
switchRelay(i, _relay[i].state);
_relay[i].active = false;
}
}
_oldBrightness = (bool)bri;
_oldMode = offMode;
initDone = true;
}
@@ -281,24 +287,110 @@ class MultiRelay : public Usermod {
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop() {
yield();
if (!enabled || strip.isUpdating()) return;
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate < 200) return; // update only 5 times/s
if (millis() - lastUpdate < 100) return; // update only 10 times/s
lastUpdate = millis();
//set relay when LEDs turn on
if (_oldBrightness != (bool)bri) {
_oldBrightness = (bool)bri;
if (_oldMode != offMode) {
_oldMode = offMode;
_switchTimerStart = millis();
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin>=0) _relay[i].active = true;
if (_relay[i].pin>=0 && !_relay[i].external) _relay[i].active = true;
}
}
handleOffTimer();
}
/**
* handleButton() can be used to override default button behaviour. Returning true
* will prevent button working in a default way.
* Replicating button.cpp
*/
bool handleButton(uint8_t b) {
yield();
if (buttonType[b] == BTN_TYPE_NONE || buttonType[b] == BTN_TYPE_RESERVED || buttonType[b] == BTN_TYPE_PIR_SENSOR || buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
return false;
}
bool handled = false;
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].button == b) {
handled = true;
}
}
if (!handled) return false;
unsigned long now = millis();
//button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
if (buttonType[b] == BTN_TYPE_SWITCH) {
//handleSwitch(b);
if (buttonPressedBefore[b] != isButtonPressed(b)) {
buttonPressedTime[b] = now;
buttonPressedBefore[b] = !buttonPressedBefore[b];
}
if (buttonLongPressed[b] == buttonPressedBefore[b]) return handled;
if (now - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin>=0 && _relay[i].button == b) {
switchRelay(i, buttonPressedBefore[b]);
buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
}
}
}
return handled;
}
//momentary button logic
if (isButtonPressed(b)) { //pressed
if (!buttonPressedBefore[b]) buttonPressedTime[b] = now;
buttonPressedBefore[b] = true;
if (now - buttonPressedTime[b] > 600) { //long press
buttonLongPressed[b] = true;
}
} else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
long dur = now - buttonPressedTime[b];
if (dur < WLED_DEBOUNCE_THRESHOLD) {
buttonPressedBefore[b] = false;
return handled;
} //too short "press", debounce
bool doublePress = buttonWaitTime[b]; //did we have short press before?
buttonWaitTime[b] = 0;
if (!buttonLongPressed[b]) { //short press
// if this is second release within 350ms it is a double press (buttonWaitTime!=0)
if (doublePress) {
//doublePressAction(b);
} else {
buttonWaitTime[b] = now;
}
}
buttonPressedBefore[b] = false;
buttonLongPressed[b] = false;
}
// if 450ms elapsed since last press/release it is a short press
if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) {
buttonWaitTime[b] = 0;
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin>=0 && _relay[i].button == b) {
toggleRelay(i);
}
}
}
return handled;
}
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*/
@@ -310,6 +402,26 @@ class MultiRelay : public Usermod {
JsonArray infoArr = user.createNestedArray(F("Number of relays")); //name
infoArr.add(String(getActiveRelayCount()));
String uiDomString;
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin<0 || !_relay[i].external) continue;
uiDomString = F("<button class=\"btn\" onclick=\"requestJson({");
uiDomString += FPSTR(_name);
uiDomString += F(":{");
uiDomString += FPSTR(_relay_str);
uiDomString += F(":");
uiDomString += i;
uiDomString += F(",on:");
uiDomString += _relay[i].state ? "false" : "true";
uiDomString += F("}});\">");
uiDomString += F("Relay ");
uiDomString += i;
uiDomString += F(" <i class=\"icons\">&#xe08f;</i></button>");
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
infoArr.add(_relay[i].state ? "on" : "off");
}
}
}
@@ -318,6 +430,23 @@ class MultiRelay : public Usermod {
* Values in the state object may be modified by connected clients
*/
void addToJsonState(JsonObject &root) {
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
JsonObject multiRelay = root[FPSTR(_name)];
if (multiRelay.isNull()) {
multiRelay = root.createNestedObject(FPSTR(_name));
}
#if MULTI_RELAY_MAX_RELAYS > 1
JsonArray rel_arr = multiRelay.createNestedArray(F("relays"));
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin < 0) continue;
JsonObject relay = rel_arr.createNestedObject();
relay[FPSTR(_relay_str)] = i;
relay[F("state")] = _relay[i].state;
}
#else
multiRelay[FPSTR(_relay_str)] = 0;
multiRelay[F("state")] = _relay[0].state;
#endif
}
/**
@@ -325,6 +454,20 @@ class MultiRelay : public Usermod {
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject &root) {
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
JsonObject usermod = root[FPSTR(_name)];
if (!usermod.isNull()) {
if (usermod["on"].is<bool>() && usermod[FPSTR(_relay_str)].is<int>() && usermod[FPSTR(_relay_str)].as<int>()>=0) {
switchRelay(usermod[FPSTR(_relay_str)].as<int>(), usermod["on"].as<bool>());
}
} else if (root[FPSTR(_name)].is<JsonArray>()) {
JsonArray relays = root[FPSTR(_name)].as<JsonArray>();
for (JsonVariant r : relays) {
if (r["on"].is<bool>() && r[FPSTR(_relay_str)].is<int>() && r[FPSTR(_relay_str)].as<int>()>=0) {
switchRelay(r[FPSTR(_relay_str)].as<int>(), r["on"].as<bool>());
}
}
}
}
/**
@@ -335,11 +478,13 @@ class MultiRelay : public Usermod {
top[FPSTR(_enabled)] = enabled;
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
String parName = FPSTR(_relay_str); parName += "-"; parName += i; parName += "-";
top[parName+"pin"] = _relay[i].pin;
top[parName+FPSTR(_activeHigh)] = _relay[i].mode;
top[parName+FPSTR(_delay_str)] = _relay[i].delay;
top[parName+FPSTR(_external)] = _relay[i].external;
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
JsonObject relay = top.createNestedObject(parName);
relay["pin"] = _relay[i].pin;
relay[FPSTR(_activeHigh)] = _relay[i].mode;
relay[FPSTR(_delay_str)] = _relay[i].delay;
relay[FPSTR(_external)] = _relay[i].external;
relay[FPSTR(_button)] = _relay[i].button;
}
DEBUG_PRINTLN(F("MultiRelay config saved."));
}
@@ -363,12 +508,20 @@ class MultiRelay : public Usermod {
enabled = top[FPSTR(_enabled)] | enabled;
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
String parName = FPSTR(_relay_str); parName += "-"; parName += i; parName += "-";
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
oldPin[i] = _relay[i].pin;
_relay[i].pin = top[parName]["pin"] | _relay[i].pin;
_relay[i].mode = top[parName][FPSTR(_activeHigh)] | _relay[i].mode;
_relay[i].external = top[parName][FPSTR(_external)] | _relay[i].external;
_relay[i].delay = top[parName][FPSTR(_delay_str)] | _relay[i].delay;
_relay[i].button = top[parName][FPSTR(_button)] | _relay[i].button;
// begin backwards compatibility (beta) remove when 0.13 is released
parName += '-';
_relay[i].pin = top[parName+"pin"] | _relay[i].pin;
_relay[i].mode = top[parName+FPSTR(_activeHigh)] | _relay[i].mode;
_relay[i].external = top[parName+FPSTR(_external)] | _relay[i].external;
_relay[i].delay = top[parName+FPSTR(_delay_str)] | _relay[i].delay;
// end compatibility
_relay[i].delay = min(600,max(0,abs((int)_relay[i].delay))); // bounds checking max 10min
}
@@ -380,12 +533,14 @@ class MultiRelay : public Usermod {
// deallocate all pins 1st
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
if (oldPin[i]>=0) {
pinManager.deallocatePin(oldPin[i]);
pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay);
}
// allocate new pins
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin,true)) {
if (!_relay[i].external) switchRelay(i, _relay[i].state = (bool)bri);
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) {
if (!_relay[i].external) {
switchRelay(i, offMode);
}
} else {
_relay[i].pin = -1;
}
@@ -394,7 +549,7 @@ class MultiRelay : public Usermod {
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
return !top[F("relay-0")][FPSTR(_button)].isNull();
}
/**
@@ -414,3 +569,4 @@ const char MultiRelay::_relay_str[] PROGMEM = "relay";
const char MultiRelay::_delay_str[] PROGMEM = "delay-s";
const char MultiRelay::_activeHigh[] PROGMEM = "active-high";
const char MultiRelay::_external[] PROGMEM = "external";
const char MultiRelay::_button[] PROGMEM = "button";

View File

@@ -5,6 +5,8 @@
* I've had good results with settings around 5 (20 fps).
*
*/
#include "wled.h"
const uint8_t PCARS_dimcolor = 20;
WiFiUDP UDP;
const unsigned int PCARS_localUdpPort = 5606; // local port to listen on
@@ -49,11 +51,12 @@ void PCARS_readValues() {
void PCARS_buildcolorbars() {
boolean activated = false;
float ledratio = 0;
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 0; i < ledCount; i++) {
for (uint16_t i = 0; i < totalLen; i++) {
if (PCARS_rpmRatio < .95 || (millis() % 100 > 70 )) {
ledratio = (float)i / (float)ledCount;
ledratio = (float)i / (float)totalLen;
if (ledratio < PCARS_rpmRatio) {
activated = true;
} else {

View File

@@ -0,0 +1,755 @@
#pragma once
#include "U8g2lib.h"
#include "SHT85.h"
#include "Wire.h"
#include "wled.h"
class QuinLEDAnPentaUsermod : public Usermod
{
private:
bool enabled = false;
bool firstRunDone = false;
bool initDone = false;
U8G2 *oledDisplay = nullptr;
SHT *sht30TempHumidSensor;
// Network info vars
bool networkHasChanged = false;
bool lastKnownNetworkConnected;
IPAddress lastKnownIp;
bool lastKnownWiFiConnected;
String lastKnownSsid;
bool lastKnownApActive;
char *lastKnownApSsid;
char *lastKnownApPass;
byte lastKnownApChannel;
int lastKnownEthType;
bool lastKnownEthLinkUp;
// Brightness / LEDC vars
byte lastKnownBri = 0;
int8_t currentBussesNumPins[5] = {0, 0, 0, 0, 0};
int8_t currentLedPins[5] = {0, 0, 0, 0, 0};
uint8_t currentLedcReads[5] = {0, 0, 0, 0, 0};
uint8_t lastKnownLedcReads[5] = {0, 0, 0, 0, 0};
// OLED vars
bool oledEnabled = false;
bool oledInitDone = false;
bool oledUseProgressBars = false;
bool oledFlipScreen = false;
bool oledFixBuggedScreen = false;
byte oledMaxPage = 3;
byte oledCurrentPage = 3; // Start with the network page to help identifying the IP
byte oledSecondsPerPage = 10;
unsigned long oledLogoDrawn = 0;
unsigned long oledLastTimeUpdated = 0;
unsigned long oledLastTimePageChange = 0;
unsigned long oledLastTimeFixBuggedScreen = 0;
// SHT30 vars
bool shtEnabled = false;
bool shtInitDone = false;
bool shtReadDataSuccess = false;
byte shtI2cAddress = 0x44;
unsigned long shtLastTimeUpdated = 0;
bool shtDataRequested = false;
float shtCurrentTemp = 0;
float shtLastKnownTemp = 0;
float shtCurrentHumidity = 0;
float shtLastKnownHumidity = 0;
// Pin/IO vars
const int8_t anPentaPins[5] = {14, 13, 12, 4, 2};
int8_t oledSpiClk = 15;
int8_t oledSpiData = 16;
int8_t oledSpiCs = 0;
int8_t oledSpiDc = 32;
int8_t oledSpiRst = 33;
int8_t shtSda = 1;
int8_t shtScl = 3;
bool isAnPentaLedPin(int8_t pin)
{
for(int8_t i = 0; i <= 4; i++)
{
if(anPentaPins[i] == pin)
return true;
}
return false;
}
void getCurrentUsedLedPins()
{
for (int8_t lp = 0; lp <= 4; lp++) currentLedPins[lp] = 0;
byte numBusses = busses.getNumBusses();
byte numUsedPins = 0;
for (int8_t b = 0; b < numBusses; b++) {
Bus* curBus = busses.getBus(b);
if (curBus != nullptr) {
uint8_t pins[5] = {0, 0, 0, 0, 0};
currentBussesNumPins[b] = curBus->getPins(pins);
for (int8_t p = 0; p < currentBussesNumPins[b]; p++) {
if (isAnPentaLedPin(pins[p])) {
currentLedPins[numUsedPins] = pins[p];
numUsedPins++;
}
}
}
}
}
void getCurrentLedcValues()
{
byte numBusses = busses.getNumBusses();
byte numLedc = 0;
for (int8_t b = 0; b < numBusses; b++) {
Bus* curBus = busses.getBus(b);
if (curBus != nullptr) {
uint32_t curPixColor = curBus->getPixelColor(0);
uint8_t _data[5] = {255, 255, 255, 255, 255};
_data[3] = curPixColor >> 24;
_data[0] = curPixColor >> 16;
_data[1] = curPixColor >> 8;
_data[2] = curPixColor;
for (uint8_t i = 0; i < currentBussesNumPins[b]; i++) {
currentLedcReads[numLedc] = (_data[i] * bri) / 255;
numLedc++;
}
}
}
}
void initOledDisplay()
{
PinManagerPinType pins[5] = { { oledSpiClk, true }, { oledSpiData, true }, { oledSpiCs, true }, { oledSpiDc, true }, { oledSpiRst, true } };
if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) {
DEBUG_PRINTF("[%s] OLED pin allocation failed!\n", _name);
oledEnabled = oledInitDone = false;
return;
}
oledDisplay = (U8G2 *) new U8G2_SSD1306_128X64_NONAME_2_4W_SW_SPI(U8G2_R0, oledSpiClk, oledSpiData, oledSpiCs, oledSpiDc, oledSpiRst);
if (oledDisplay == nullptr) {
DEBUG_PRINTF("[%s] OLED init failed!\n", _name);
oledEnabled = oledInitDone = false;
return;
}
oledDisplay->begin();
oledDisplay->setBusClock(40 * 1000 * 1000);
oledDisplay->setContrast(10);
oledDisplay->setPowerSave(0);
oledDisplay->setFont(u8g2_font_6x10_tf);
oledDisplay->setFlipMode(oledFlipScreen);
oledDisplay->firstPage();
do {
oledDisplay->drawXBMP(0, 16, 128, 36, quinLedLogo);
} while (oledDisplay->nextPage());
oledLogoDrawn = millis();
oledInitDone = true;
}
void cleanupOledDisplay()
{
if (oledInitDone) {
oledDisplay->clear();
}
pinManager.deallocatePin(oledSpiClk, PinOwner::UM_QuinLEDAnPenta);
pinManager.deallocatePin(oledSpiData, PinOwner::UM_QuinLEDAnPenta);
pinManager.deallocatePin(oledSpiCs, PinOwner::UM_QuinLEDAnPenta);
pinManager.deallocatePin(oledSpiDc, PinOwner::UM_QuinLEDAnPenta);
pinManager.deallocatePin(oledSpiRst, PinOwner::UM_QuinLEDAnPenta);
delete oledDisplay;
oledEnabled = false;
oledInitDone = false;
}
bool isOledReady()
{
return oledEnabled && oledInitDone;
}
void initSht30TempHumiditySensor()
{
PinManagerPinType pins[2] = { { shtSda, true }, { shtScl, true } };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) {
DEBUG_PRINTF("[%s] SHT30 pin allocation failed!\n", _name);
shtEnabled = shtInitDone = false;
return;
}
TwoWire *wire = new TwoWire(1);
wire->setClock(400000);
sht30TempHumidSensor = (SHT *) new SHT30();
sht30TempHumidSensor->begin(shtI2cAddress, wire);
// The SHT lib calls wire.begin() again without the SDA and SCL pins... So call it again here...
wire->begin(shtSda, shtScl);
if (sht30TempHumidSensor->readStatus() == 0xFFFF) {
DEBUG_PRINTF("[%s] SHT30 init failed!\n", _name);
shtEnabled = shtInitDone = false;
return;
}
shtInitDone = true;
}
void cleanupSht30TempHumiditySensor()
{
if (shtInitDone) {
sht30TempHumidSensor->reset();
}
pinManager.deallocatePin(shtSda, PinOwner::UM_QuinLEDAnPenta);
pinManager.deallocatePin(shtScl, PinOwner::UM_QuinLEDAnPenta);
delete sht30TempHumidSensor;
shtEnabled = false;
shtInitDone = false;
}
void cleanup()
{
if (isOledReady()) {
cleanupOledDisplay();
}
if (isShtReady()) {
cleanupSht30TempHumiditySensor();
}
enabled = false;
}
bool oledCheckForNetworkChanges()
{
if (lastKnownNetworkConnected != Network.isConnected() || lastKnownIp != Network.localIP()
|| lastKnownWiFiConnected != WiFi.isConnected() || lastKnownSsid != WiFi.SSID()
|| lastKnownApActive != apActive || lastKnownApSsid != apSSID || lastKnownApPass != apPass || lastKnownApChannel != apChannel) {
lastKnownNetworkConnected = Network.isConnected();
lastKnownIp = Network.localIP();
lastKnownWiFiConnected = WiFi.isConnected();
lastKnownSsid = WiFi.SSID();
lastKnownApActive = apActive;
lastKnownApSsid = apSSID;
lastKnownApPass = apPass;
lastKnownApChannel = apChannel;
return networkHasChanged = true;
}
#ifdef WLED_USE_ETHERNET
if (lastKnownEthType != ethernetType || lastKnownEthLinkUp != ETH.linkUp()) {
lastKnownEthType = ethernetType;
lastKnownEthLinkUp = ETH.linkUp();
return networkHasChanged = true;
}
#endif
return networkHasChanged = false;
}
byte oledGetNextPage()
{
return oledCurrentPage + 1 <= oledMaxPage ? oledCurrentPage + 1 : 1;
}
void oledShowPage(byte page, bool updateLastTimePageChange = false)
{
oledCurrentPage = page;
updateOledDisplay();
oledLastTimeUpdated = millis();
if (updateLastTimePageChange) oledLastTimePageChange = oledLastTimeUpdated;
}
/*
* Page 1: Overall brightness and LED outputs
* Page 2: General info like temp, humidity and others
* Page 3: Network info
*/
void updateOledDisplay()
{
if (!isOledReady()) return;
oledDisplay->firstPage();
do {
oledDisplay->setFont(u8g2_font_chroma48medium8_8r);
oledDisplay->drawStr(0, 8, serverDescription);
oledDisplay->drawHLine(0, 13, 127);
oledDisplay->setFont(u8g2_font_6x10_tf);
byte charPerRow = 21;
byte oledRow = 23;
switch (oledCurrentPage) {
// LED Outputs
case 1:
{
char charCurrentBrightness[charPerRow+1] = "Brightness:";
if (oledUseProgressBars) {
oledDisplay->drawStr(0, oledRow, charCurrentBrightness);
// There is no method to draw a filled box with rounded corners. So draw the rounded frame first, then fill that frame accordingly to LED percentage
oledDisplay->drawRFrame(68, oledRow - 6, 60, 7, 2);
oledDisplay->drawBox(69, oledRow - 5, int(round(58*getPercentageForBrightness(bri)) / 100), 5);
}
else {
sprintf(charCurrentBrightness, "%s %d%%", charCurrentBrightness, getPercentageForBrightness(bri));
oledDisplay->drawStr(0, oledRow, charCurrentBrightness);
}
oledRow += 8;
byte drawnLines = 0;
for (int8_t app = 0; app <= 4; app++) {
for (int8_t clp = 0; clp <= 4; clp++) {
if (anPentaPins[app] == currentLedPins[clp]) {
char charCurrentLedcReads[17];
sprintf(charCurrentLedcReads, "LED %d:", app+1);
if (oledUseProgressBars) {
oledDisplay->drawStr(0, oledRow+(drawnLines*8), charCurrentLedcReads);
oledDisplay->drawRFrame(38, oledRow - 6 + (drawnLines * 8), 90, 7, 2);
oledDisplay->drawBox(39, oledRow - 5 + (drawnLines * 8), int(round(88*getPercentageForBrightness(currentLedcReads[clp])) / 100), 5);
}
else {
sprintf(charCurrentLedcReads, "%s %d%%", charCurrentLedcReads, getPercentageForBrightness(currentLedcReads[clp]));
oledDisplay->drawStr(0, oledRow+(drawnLines*8), charCurrentLedcReads);
}
drawnLines++;
}
}
}
break;
}
// Various info
case 2:
{
if (isShtReady() && shtReadDataSuccess) {
char charShtCurrentTemp[charPerRow+4]; // Reserve 3 more bytes than usual as we gonna have one UTF8 char which can be up to 4 bytes.
sprintf(charShtCurrentTemp, "Temperature: %.02f°C", shtCurrentTemp);
char charShtCurrentHumidity[charPerRow+1];
sprintf(charShtCurrentHumidity, "Humidity: %.02f RH", shtCurrentHumidity);
oledDisplay->drawUTF8(0, oledRow, charShtCurrentTemp);
oledDisplay->drawStr(0, oledRow + 10, charShtCurrentHumidity);
oledRow += 20;
}
if (mqttEnabled && mqttServer[0] != 0) {
char charMqttStatus[charPerRow+1];
sprintf(charMqttStatus, "MQTT: %s", (WLED_MQTT_CONNECTED ? "Connected" : "Disconnected"));
oledDisplay->drawStr(0, oledRow, charMqttStatus);
oledRow += 10;
}
// Always draw these two on the bottom
char charUptime[charPerRow+1];
sprintf(charUptime, "Uptime: %ds", int(millis()/1000 + rolloverMillis*4294967)); // From json.cpp
oledDisplay->drawStr(0, 53, charUptime);
char charWledVersion[charPerRow+1];
sprintf(charWledVersion, "WLED v%s", versionString);
oledDisplay->drawStr(0, 63, charWledVersion);
break;
}
// Network Info
case 3:
#ifdef WLED_USE_ETHERNET
if (lastKnownEthType == WLED_ETH_NONE) {
oledDisplay->drawStr(0, oledRow, "Ethernet: No board selected");
oledRow += 10;
}
else if (!lastKnownEthLinkUp) {
oledDisplay->drawStr(0, oledRow, "Ethernet: Link Down");
oledRow += 10;
}
#endif
if (lastKnownNetworkConnected) {
#ifdef WLED_USE_ETHERNET
if (lastKnownEthLinkUp) {
oledDisplay->drawStr(0, oledRow, "Ethernet: Link Up");
oledRow += 10;
}
else
#endif
// Wi-Fi can be active with ETH being connected, but we don't mind...
if (lastKnownWiFiConnected) {
#ifdef WLED_USE_ETHERNET
if (!lastKnownEthLinkUp) {
#endif
oledDisplay->drawStr(0, oledRow, "Wi-Fi: Connected");
char currentSsidChar[lastKnownSsid.length() + 1];
lastKnownSsid.toCharArray(currentSsidChar, lastKnownSsid.length() + 1);
char charCurrentSsid[50];
sprintf(charCurrentSsid, "SSID: %s", currentSsidChar);
oledDisplay->drawStr(0, oledRow + 10, charCurrentSsid);
oledRow += 20;
#ifdef WLED_USE_ETHERNET
}
#endif
}
String currentIpStr = lastKnownIp.toString();
char currentIpChar[currentIpStr.length() + 1];
currentIpStr.toCharArray(currentIpChar, currentIpStr.length() + 1);
char charCurrentIp[30];
sprintf(charCurrentIp, "IP: %s", currentIpChar);
oledDisplay->drawStr(0, oledRow, charCurrentIp);
}
// If WLED AP is active. Theoretically, it can even be active with ETH being connected, but we don't mind...
else if (lastKnownApActive) {
char charCurrentApStatus[charPerRow+1];
sprintf(charCurrentApStatus, "WLED AP: %s (Ch: %d)", (lastKnownApActive ? "On" : "Off"), lastKnownApChannel);
oledDisplay->drawStr(0, oledRow, charCurrentApStatus);
char charCurrentApSsid[charPerRow+1];
sprintf(charCurrentApSsid, "SSID: %s", lastKnownApSsid);
oledDisplay->drawStr(0, oledRow + 10, charCurrentApSsid);
char charCurrentApPass[charPerRow+1];
sprintf(charCurrentApPass, "PW: %s", lastKnownApPass);
oledDisplay->drawStr(0, oledRow + 20, charCurrentApPass);
// IP is hardcoded / no var exists in WLED at the time this mod was coded, so also hardcode it here
oledDisplay->drawStr(0, oledRow + 30, "IP: 4.3.2.1");
}
break;
}
} while (oledDisplay->nextPage());
}
bool isShtReady()
{
return shtEnabled && shtInitDone;
}
public:
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
static const char _oledEnabled[];
static const char _oledUseProgressBars[];
static const char _oledFlipScreen[];
static const char _oledSecondsPerPage[];
static const char _oledFixBuggedScreen[];
static const char _shtEnabled[];
static const unsigned char quinLedLogo[];
static int8_t getPercentageForBrightness(byte brightness)
{
return int(((float)brightness / (float)255) * 100);
}
/*
* 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()
{
if (enabled) {
lastKnownBri = bri;
if (oledEnabled) {
initOledDisplay();
}
if (shtEnabled) {
initSht30TempHumiditySensor();
}
getCurrentUsedLedPins();
initDone = true;
}
firstRunDone = true;
}
/*
* 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()
{
if (!enabled || !initDone || strip.isUpdating()) return;
if (isShtReady()) {
if (millis() - shtLastTimeUpdated > 30000 && !shtDataRequested) {
sht30TempHumidSensor->requestData();
shtDataRequested = true;
shtLastTimeUpdated = millis();
}
if (shtDataRequested) {
if (sht30TempHumidSensor->dataReady()) {
if (sht30TempHumidSensor->readData()) {
shtCurrentTemp = sht30TempHumidSensor->getTemperature();
shtCurrentHumidity = sht30TempHumidSensor->getHumidity();
shtReadDataSuccess = true;
}
else {
shtReadDataSuccess = false;
}
shtDataRequested = false;
}
}
}
if (isOledReady() && millis() - oledLogoDrawn > 3000) {
// Check for changes on the current page and update the OLED if a change is detected
if (millis() - oledLastTimeUpdated > 150) {
// If there was a network change, force page 3 (network page)
if (oledCheckForNetworkChanges()) {
oledCurrentPage = 3;
}
// Only redraw a page if there was a change for that page
switch (oledCurrentPage) {
case 1:
lastKnownBri = bri;
// Probably causes lag to always do ledcRead(), so rather re-do the math, 'cause we can't easily get it...
getCurrentLedcValues();
if (bri != lastKnownBri || lastKnownLedcReads[0] != currentLedcReads[0] || lastKnownLedcReads[1] != currentLedcReads[1] || lastKnownLedcReads[2] != currentLedcReads[2]
|| lastKnownLedcReads[3] != currentLedcReads[3] || lastKnownLedcReads[4] != currentLedcReads[4]) {
lastKnownLedcReads[0] = currentLedcReads[0]; lastKnownLedcReads[1] = currentLedcReads[1]; lastKnownLedcReads[2] = currentLedcReads[2]; lastKnownLedcReads[3] = currentLedcReads[3]; lastKnownLedcReads[4] = currentLedcReads[4];
oledShowPage(1);
}
break;
case 2:
if (shtLastKnownTemp != shtCurrentTemp || shtLastKnownHumidity != shtCurrentHumidity) {
shtLastKnownTemp = shtCurrentTemp;
shtLastKnownHumidity = shtCurrentHumidity;
oledShowPage(2);
}
break;
case 3:
if (networkHasChanged) {
networkHasChanged = false;
oledShowPage(3, true);
}
break;
}
}
// Cycle through OLED pages
if (millis() - oledLastTimePageChange > oledSecondsPerPage * 1000) {
// Periodically fixing a "bugged out" OLED. More details in the ReadMe
if (oledFixBuggedScreen && millis() - oledLastTimeFixBuggedScreen > 60000) {
oledDisplay->begin();
oledLastTimeFixBuggedScreen = millis();
}
oledShowPage(oledGetNextPage(), true);
}
}
}
void addToConfig(JsonObject &root)
{
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = enabled;
top[FPSTR(_oledEnabled)] = oledEnabled;
top[FPSTR(_oledUseProgressBars)] = oledUseProgressBars;
top[FPSTR(_oledFlipScreen)] = oledFlipScreen;
top[FPSTR(_oledSecondsPerPage)] = oledSecondsPerPage;
top[FPSTR(_oledFixBuggedScreen)] = oledFixBuggedScreen;
top[FPSTR(_shtEnabled)] = shtEnabled;
// Update LED pins on config save
getCurrentUsedLedPins();
}
/**
* 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)
{
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name);
return false;
}
bool oldEnabled = enabled;
bool oldOledEnabled = oledEnabled;
bool oldOledFlipScreen = oledFlipScreen;
bool oldShtEnabled = shtEnabled;
getJsonValue(top[FPSTR(_enabled)], enabled);
getJsonValue(top[FPSTR(_oledEnabled)], oledEnabled);
getJsonValue(top[FPSTR(_oledUseProgressBars)], oledUseProgressBars);
getJsonValue(top[FPSTR(_oledFlipScreen)], oledFlipScreen);
getJsonValue(top[FPSTR(_oledSecondsPerPage)], oledSecondsPerPage);
getJsonValue(top[FPSTR(_oledFixBuggedScreen)], oledFixBuggedScreen);
getJsonValue(top[FPSTR(_shtEnabled)], shtEnabled);
// First run: reading from cfg.json, nothing to do here, will be all done in setup()
if (!firstRunDone) {
DEBUG_PRINTF("[%s] First run, nothing to do\n", _name);
}
// Check if mod has been en-/disabled
else if (enabled != oldEnabled) {
enabled ? setup() : cleanup();
DEBUG_PRINTF("[%s] Usermod has been en-/disabled\n", _name);
}
// Config has been changed, so adopt to changes
else if (enabled) {
if (oldOledEnabled != oledEnabled) {
oledEnabled ? initOledDisplay() : cleanupOledDisplay();
}
else if (oledEnabled && oldOledFlipScreen != oledFlipScreen) {
oledDisplay->clear();
oledDisplay->setFlipMode(oledFlipScreen);
oledShowPage(oledCurrentPage);
}
if (oldShtEnabled != shtEnabled) {
shtEnabled ? initSht30TempHumiditySensor() : cleanupSht30TempHumiditySensor();
}
DEBUG_PRINTF("[%s] Config (re)loaded\n", _name);
}
return true;
}
void addToJsonInfo(JsonObject& root)
{
if (!enabled && !isShtReady()) {
return;
}
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
JsonArray jsonTemp = user.createNestedArray("Temperature");
JsonArray jsonHumidity = user.createNestedArray("Humidity");
if (shtLastTimeUpdated == 0 || !shtReadDataSuccess) {
jsonTemp.add(0);
jsonHumidity.add(0);
if (shtLastTimeUpdated == 0) {
jsonTemp.add(" Not read yet");
jsonHumidity.add(" Not read yet");
}
else {
jsonTemp.add(" Error");
jsonHumidity.add(" Error");
}
return;
}
jsonHumidity.add(shtCurrentHumidity);
jsonHumidity.add(" RH");
jsonTemp.add(shtCurrentTemp);
jsonTemp.add(" °C");
}
/*
* 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_QUINLED_AN_PENTA;
}
};
// strings to reduce flash memory usage (used more than twice)
// Config settings
const char QuinLEDAnPentaUsermod::_name[] PROGMEM = "QuinLED-An-Penta";
const char QuinLEDAnPentaUsermod::_enabled[] PROGMEM = "Enabled";
const char QuinLEDAnPentaUsermod::_oledEnabled[] PROGMEM = "Enable-OLED";
const char QuinLEDAnPentaUsermod::_oledUseProgressBars[] PROGMEM = "OLED-Use-Progress-Bars";
const char QuinLEDAnPentaUsermod::_oledFlipScreen[] PROGMEM = "OLED-Flip-Screen-180";
const char QuinLEDAnPentaUsermod::_oledSecondsPerPage[] PROGMEM = "OLED-Seconds-Per-Page";
const char QuinLEDAnPentaUsermod::_oledFixBuggedScreen[] PROGMEM = "OLED-Fix-Bugged-Screen";
const char QuinLEDAnPentaUsermod::_shtEnabled[] PROGMEM = "Enable-SHT30-Temp-Humidity-Sensor";
// Other strings
const unsigned char QuinLEDAnPentaUsermod::quinLedLogo[] PROGMEM = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xFD, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x80, 0xFF,
0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x3F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0x07, 0xFE, 0xFF, 0xFF, 0x0F, 0xFC,
0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFC, 0x0F, 0xFE,
0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0xE3, 0xFF, 0xA5, 0xFF, 0xFF, 0xFF,
0x0F, 0xFC, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0xE1, 0xFF,
0x00, 0xF0, 0xE3, 0xFF, 0x0F, 0xFE, 0x1F, 0xFE, 0xFF, 0xFF, 0x3F, 0xFF,
0xFF, 0xFF, 0xE3, 0xFF, 0x00, 0xF0, 0x00, 0xFF, 0x07, 0xFE, 0x1F, 0xFC,
0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xFF, 0x00, 0xF0, 0x00, 0xFE,
0x07, 0xFF, 0x1F, 0xFC, 0xF0, 0xC7, 0x3F, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF,
0xF1, 0xFF, 0x00, 0xFC, 0x07, 0xFF, 0x1F, 0xFE, 0xF0, 0xC3, 0x1F, 0xFE,
0x00, 0xFF, 0xE1, 0xFF, 0xF1, 0xFF, 0x30, 0xF8, 0x07, 0xFF, 0x1F, 0xFE,
0xF0, 0xC3, 0x1F, 0xFE, 0x00, 0xFC, 0xC3, 0xFF, 0xE1, 0xFF, 0xF0, 0xF0,
0x03, 0xFF, 0x0F, 0x7E, 0xF0, 0xC3, 0x1F, 0x7E, 0x00, 0xF8, 0xE3, 0xFF,
0xE1, 0xFF, 0xF1, 0xF1, 0x83, 0xFF, 0x0F, 0x7E, 0xF0, 0xC3, 0x1F, 0x7E,
0x00, 0xF0, 0xC3, 0xFF, 0xE1, 0xFF, 0xF1, 0xE1, 0x83, 0xFF, 0x0F, 0xFE,
0xF0, 0xC3, 0x1F, 0xFE, 0xF8, 0xF0, 0xC3, 0xFF, 0xA1, 0xFF, 0xF1, 0xE3,
0x81, 0xFF, 0x0F, 0x7E, 0xF0, 0xC1, 0x1F, 0x7E, 0xF0, 0xF0, 0xC3, 0xFF,
0x01, 0xF8, 0xE1, 0xC3, 0x83, 0xFF, 0x0F, 0x7F, 0xF8, 0xC3, 0x1F, 0x7E,
0xF8, 0xF0, 0xC3, 0xFF, 0x03, 0xF8, 0xE1, 0xC7, 0x81, 0xE4, 0x0F, 0x7F,
0xF0, 0xC3, 0x1F, 0xFE, 0xF8, 0xF0, 0xC3, 0xFF, 0x01, 0xF8, 0xE3, 0xC7,
0x01, 0xC0, 0x07, 0x7F, 0xF8, 0xC1, 0x1F, 0x7E, 0xF0, 0xE1, 0xC3, 0xFF,
0xC3, 0xFD, 0xE1, 0x87, 0x01, 0x00, 0x07, 0x7F, 0xF8, 0xC3, 0x1F, 0x7E,
0xF8, 0xF0, 0xC3, 0xFF, 0xE3, 0xFF, 0xE3, 0x87, 0x01, 0x00, 0x82, 0x3F,
0xF8, 0xE1, 0x1F, 0xFE, 0xF8, 0xE1, 0xC3, 0xFF, 0xC3, 0xFF, 0xC3, 0x87,
0x01, 0x00, 0x80, 0x3F, 0xF8, 0xC1, 0x1F, 0x7E, 0xF0, 0xF1, 0xC3, 0xFF,
0xC3, 0xFF, 0xC3, 0x87, 0x03, 0x0F, 0x80, 0x3F, 0xF8, 0xE1, 0x0F, 0x7E,
0xF8, 0xE1, 0x87, 0xFF, 0xC3, 0xFF, 0xC7, 0x87, 0x03, 0x04, 0xC0, 0x7F,
0xF0, 0xE1, 0x0F, 0xFF, 0xF8, 0xF1, 0x87, 0xFF, 0xC3, 0xFF, 0xC3, 0x87,
0x07, 0x00, 0xE0, 0x7F, 0x00, 0xE0, 0x1F, 0x7E, 0xF0, 0xE0, 0xC3, 0xFF,
0xC7, 0xFF, 0x87, 0x87, 0x0F, 0x00, 0xE0, 0x7F, 0x00, 0xE0, 0x0F, 0x7F,
0xF8, 0xE1, 0x07, 0x80, 0x07, 0xEA, 0x87, 0xC1, 0x0F, 0x00, 0x80, 0xFF,
0x00, 0xE0, 0x1F, 0x7E, 0xF0, 0xE1, 0x07, 0x00, 0x03, 0x80, 0x07, 0xC0,
0x7F, 0x00, 0x00, 0xFF, 0x01, 0xE0, 0x1F, 0xFF, 0xF8, 0xE1, 0x07, 0x00,
0x07, 0x00, 0x07, 0xE0, 0xFF, 0xF7, 0x01, 0xFF, 0x57, 0xF7, 0x9F, 0xFF,
0xFC, 0xF1, 0x0F, 0x00, 0x07, 0x80, 0x0F, 0xE0, 0xFF, 0xFF, 0x03, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xBF, 0xFE,
0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};

View File

@@ -0,0 +1,69 @@
# QuinLED-An-Penta
The (un)official usermod to get the best out of the QuinLED-An-Penta, like using the OLED and the SHT30 temperature/humidity sensor.
## Requirements
* "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2
* "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85
## Usermod installation
Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one and add the buildflag `-D QUINLED_AN_PENTA`.
ESP32 (**without** ethernet):
```
[env:custom_esp32dev_usermod_quinled_an_penta]
extends = env:esp32dev
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D QUINLED_AN_PENTA
lib_deps = ${esp32.lib_deps}
olikraus/U8g2@~2.28.8
robtillaart/SHT85@~0.2.0
```
ESP32 (**with** ethernet):
```
[env:custom_esp32dev_usermod_quinled_an_penta]
extends = env:esp32dev
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D WLED_USE_ETHERNET -D QUINLED_AN_PENTA
lib_deps = ${esp32.lib_deps}
olikraus/U8g2@~2.28.8
robtillaart/SHT85@~0.2.0
```
## Some words about the (optional) OLED
This mod has been optimized for an SSD1306 driven 128x64 OLED. Using a smaller OLED or an OLED using a different driver will result in unexpected results.
I highly recommend using these "two color monochromatic OLEDs", which have the first 16 pixels in a different color than the other 48, e.g. a yellow/blue OLED.
Also note, you need to have an **SPI** driven OLED, **not i2c**!
### My OLED flickers after some time, what should I do?
That's a tricky one: During development I saw that the OLED sometimes starts to "bug out" / flicker and won't work anymore. This seems to be caused by the high PWM interference the board produces. It seems to loose it's settings and then doesn't know how to draw anymore. Turns out the only way to fix this is to call the libraries `begin()` method again which will re-initialize the display.
If you're facing this issue, you can enable a setting I've added which will call the `begin()` roughly every 60 seconds between a page change. This will make the page change take ~500ms, but will fix the display.
## Configuration
Navigate to the "Config" and then to the "Usermods" section. If you compiled WLED with `-D QUINLED_AN_PENTA`, you will see the config for it there:
* Enable-OLED:
* What it does: Enabled the optional SPI driven OLED that can be mounted to the 7-pin female header
* Possible values: Enabled/Disabled
* Default: Disabled
* OLED-Use-Progress-Bars:
* What it does: Toggle between showing percentage numbers or a progress-bar-like visualization for overall brightness and each LED channels brightness level
* Possible values: Enabled/Disabled
* Default: Disabled
* OLED-Flip-Screen-180:
* What it does: Flips the screen 180° / upside-down
* Possible values: Enabled/Disabled
* Default: Disabled
* OLED-Seconds-Per-Page:
* What it does: Defines how long the OLED should stay on one page in seconds before changing to the next
* Possible values: Enabled/Disabled
* Default: 10
* OLED-Fix-Bugged-Screen:
* What it does: Enable this if your OLED flickers after some time. For more info read above under ["My OLED flickers after some time, what should I do?"](#My-OLED-flickers-after-some-time-what-should-I-do)
* Possible values: Enabled/Disabled
* Default: Disabled
* Enable-SHT30-Temp-Humidity-Sensor:
* What it does: Enabled the onboard SHT30 temperature and humidity sensor
* Possible values: Enabled/Disabled
* Default: Disabled
## Change log
2021-10
* First implementation.

View File

@@ -0,0 +1,86 @@
# RGB Encoder Board
This usermod-v2 adds support for the awesome RGB Rotary Encoder Board by Adam Zeloof / "Isotope Engineering" to control the overall brightness of your WLED instance: https://github.com/isotope-engineering/RGB-Encoder-Board. A great DIY rotary encoder with 20 tiny SK6805 / "NeoPixel Nano" LEDs.
https://user-images.githubusercontent.com/3090131/124680599-0180ab80-dec7-11eb-9065-a6d08ebe0287.mp4
## Credits
The actual / original code that does the different LED modes is from Adam Zeloof. So I don't take credit for these. But I ported it to WLED, which involved replacing the LED library he used (because, guess what, WLED already has one; so no need to add another one, but use whatever WLED uses), plus the rotary encoder library, because that one was not compatible with ESP, only Arduino.
So it was quite more work than I hoped, but I got there eventually :)
## Requirements
* "ESP Rotary" by Lennart Hennigs, v1.5.0 or higher: https://github.com/LennartHennigs/ESPRotary
## Usermod installation
Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one and add the buildflag `-D RGB_ROTARY_ENCODER`.
ESP32:
```
[env:custom_esp32dev_usermod_rgb_encoder_board]
extends = env:esp32dev
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D RGB_ROTARY_ENCODER
lib_deps = ${esp32.lib_deps}
lennarthennigs/ESP Rotary@^1.5.0
```
ESP8266 / D1 Mini:
```
[env:custom_d1_mini_usermod_rgb_encoder_board]
extends = env:d1_mini
build_flags = ${common.build_flags_esp8266} -D RGB_ROTARY_ENCODER
lib_deps = ${esp8266.lib_deps}
lennarthennigs/ESP Rotary@^1.5.0
```
## How to connect the board to your ESP
We gonna need (minimum) three or (maximum) four GPIOs for the board:
* "ea": Basically tells if the encoder goes into one or the other direction
* "eb": Same thing, but the other direction
* "di": LED data in. To actually control the LEDs
* *(optional)* "sw": The integrated switch in the rotary encoder. Can be omitted for the bare functionality of just controlling the brightness
We also gonna need some power, so:
* "vdd": Needs to be connected to **+5V**.
* "gnd": Well, it's GND.
You can freely pick the GPIOs, it doesn't matter. Those will be configured in the "Usermods" section in the WLED web panel:
## Configuration
Navigate to the "Config" and then to the "Usermods" section. If you compiled WLED with `-D RGB_ROTARY_ENCODER`, you will see the config for it there. The settings there are the GPIOs we mentioned before (*Note: The switch pin is not there, as this can just be configured the "normal" button on the "LED Preferences" page*), plus a few more:
* LED pin:
* Possible values: Any valid and available GPIO
* Default: 3
* What it does: Pin to control the LED ring
* ea pin:
* Possible values: Any valid and available GPIO
* Default: 15
* What it does: First of the two rotary encoder pins
* eb pin:
* Possible values: Any valid and available GPIO
* Default: 32
* What it does: Second of the two rotary encoder pins
* LED Mode:
* Possible values: 1-3
* Default: 3
* What it does: The usermod provides three different modes of how the LEDs can look like. Here's an example: https://github.com/isotope-engineering/RGB-Encoder-Board/blob/master/images/rgb-encoder-animations.gif
* Up left is "1"
* Up right is not supported / doesn't make sense for brightness control
* Bottom left is "2"
* Bottom right is "3"
* LED Brightness:
* Possible values: 1-255
* Default: 64
* What it does: Brightness of the LED ring
* Steps per click:
* Possible values: Any positive number
* Default: 4
* What it does: With each "click", a rotary encoder actually increments it's "steps". Most rotary encoder do four "steps" per "click". I know this sounds super weird, so just leave this the default value, unless your rotary encoder behaves weirdly, like with one click, it makes two LEDs light up, or you sometimes need two click for one LED. Then you should play around with this value or write a small sketch using the same "ESP Rotary" library and read out the steps it does.
* Increment per click:
* Possible values: Any positive number
* Default: 5
* What it does: Most rotary encoder have 20 "clicks", so basically 20 positions. This value should be set to 100 / `number of clicks`
## Change log
2021-07
* First implementation.

View File

@@ -0,0 +1,343 @@
#pragma once
#include "ESPRotary.h"
#include <math.h>
#include "wled.h"
class RgbRotaryEncoderUsermod : public Usermod
{
private:
bool enabled = false;
bool initDone = false;
bool isDirty = false;
BusDigital *ledBus;
/*
* Green - eb - Q4 - 32
* Red - ea - Q1 - 15
* Black - sw - Q2 - 12
*/
ESPRotary *rotaryEncoder;
int8_t ledIo = 3; // GPIO to control the LEDs
int8_t eaIo = 15; // "ea" from RGB Encoder Board
int8_t ebIo = 32; // "eb" from RGB Encoder Board
byte stepsPerClick = 4; // How many "steps" your rotary encoder does per click. This varies per rotary encoder
/* This could vary per rotary encoder: Usually rotary encoders have 20 "clicks".
If yours has less/more, adjust this to: 100% = 20 LEDs * incrementPerClick */
byte incrementPerClick = 5;
byte ledMode = 3;
byte ledBrightness = 64;
// This is all needed to calculate the brightness, rotary position, etc.
const byte minPos = 5; // minPos is not zero, because if we want to turn the LEDs off, we use the built-in button ;)
const byte maxPos = 100; // maxPos=100, like 100%
const byte numLeds = 20;
byte lastKnownPos = 0;
byte currentColors[3];
byte lastKnownBri = 0;
void initRotaryEncoder()
{
PinManagerPinType pins[2] = { { eaIo, false }, { ebIo, false } };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) {
eaIo = -1;
ebIo = -1;
cleanup();
return;
}
// I don't know why, but setting the upper bound here does not work. It results into 1717922932 O_o
rotaryEncoder = new ESPRotary(eaIo, ebIo, stepsPerClick, incrementPerClick, maxPos, currentPos, incrementPerClick);
rotaryEncoder->setUpperBound(maxPos); // I have to again set it here and then it works / is actually 100...
rotaryEncoder->setChangedHandler(RgbRotaryEncoderUsermod::cbRotate);
}
void initLedBus()
{
byte _pins[5] = {(byte)ledIo, 255, 255, 255, 255};
BusConfig busCfg = BusConfig(TYPE_WS2812_RGB, _pins, 0, numLeds, COL_ORDER_GRB, false, 0);
ledBus = new BusDigital(busCfg, WLED_MAX_BUSSES - 1);
if (!ledBus->isOk()) {
cleanup();
return;
}
ledBus->setBrightness(ledBrightness);
}
void updateLeds()
{
switch (ledMode) {
case 2:
{
currentColors[0] = 255; currentColors[1] = 0; currentColors[2] = 0;
for (int i = 0; i < currentPos / incrementPerClick - 1; i++) {
ledBus->setPixelColor(i, 0);
}
ledBus->setPixelColor(currentPos / incrementPerClick - 1, colorFromRgbw(currentColors));
for (int i = currentPos / incrementPerClick; i < numLeds; i++) {
ledBus->setPixelColor(i, 0);
}
}
break;
default:
case 1:
case 3:
// WLED orange (of course), which we will use in mode 1
currentColors[0] = 255; currentColors[1] = 160; currentColors[2] = 0;
for (int i = 0; i < currentPos / incrementPerClick; i++) {
if (ledMode == 3) {
hsv2rgb((i) / float(numLeds), 1, .25);
}
ledBus->setPixelColor(i, colorFromRgbw(currentColors));
}
for (int i = currentPos / incrementPerClick; i < numLeds; i++) {
ledBus->setPixelColor(i, 0);
}
break;
}
isDirty = true;
}
void cleanup()
{
// Only deallocate pins if we allocated them ;)
if (eaIo != -1) {
pinManager.deallocatePin(eaIo, PinOwner::UM_RGBRotaryEncoder);
eaIo = -1;
}
if (ebIo != -1) {
pinManager.deallocatePin(ebIo, PinOwner::UM_RGBRotaryEncoder);
ebIo = -1;
}
delete rotaryEncoder;
delete ledBus;
enabled = false;
}
int getPositionForBrightness()
{
return int(((float)bri / (float)255) * 100);
}
float fract(float x) { return x - int(x); }
float mix(float a, float b, float t) { return a + (b - a) * t; }
void hsv2rgb(float h, float s, float v) {
currentColors[0] = int((v * mix(1.0, constrain(abs(fract(h + 1.0) * 6.0 - 3.0) - 1.0, 0.0, 1.0), s)) * 255);
currentColors[1] = int((v * mix(1.0, constrain(abs(fract(h + 0.6666666) * 6.0 - 3.0) - 1.0, 0.0, 1.0), s)) * 255);
currentColors[2] = int((v * mix(1.0, constrain(abs(fract(h + 0.3333333) * 6.0 - 3.0) - 1.0, 0.0, 1.0), s)) * 255);
}
public:
static byte currentPos;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
static const char _ledIo[];
static const char _eaIo[];
static const char _ebIo[];
static const char _ledMode[];
static const char _ledBrightness[];
static const char _stepsPerClick[];
static const char _incrementPerClick[];
static void cbRotate(ESPRotary& r) {
currentPos = r.getPosition();
}
/**
* Enable/Disable the usermod
*/
// inline void enable(bool enable) { enabled = enable; }
/**
* Get usermod enabled/disabled state
*/
// inline bool isEnabled() { return enabled; }
/*
* 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()
{
if (enabled) {
currentPos = getPositionForBrightness();
lastKnownBri = bri;
initRotaryEncoder();
initLedBus();
// No updating of LEDs here, as that's sometimes not working; loop() will take care of that
initDone = true;
}
}
/*
* 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()
{
if (!enabled || strip.isUpdating()) return;
rotaryEncoder->loop();
// If the rotary was changed
if(lastKnownPos != currentPos) {
lastKnownPos = currentPos;
bri = min(int(round((2.55 * currentPos))), 255);
lastKnownBri = bri;
updateLeds();
colorUpdated(CALL_MODE_DIRECT_CHANGE);
}
// If the brightness is changed not with the rotary, update the rotary
if (bri != lastKnownBri) {
currentPos = lastKnownPos = getPositionForBrightness();
lastKnownBri = bri;
rotaryEncoder->resetPosition(currentPos);
updateLeds();
}
// Update LEDs here in loop to also validate that we can update/show
if (isDirty && ledBus->canShow()) {
isDirty = false;
ledBus->show();
}
}
void addToConfig(JsonObject &root)
{
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = enabled;
top[FPSTR(_ledIo)] = ledIo;
top[FPSTR(_eaIo)] = eaIo;
top[FPSTR(_ebIo)] = ebIo;
top[FPSTR(_ledMode)] = ledMode;
top[FPSTR(_ledBrightness)] = ledBrightness;
top[FPSTR(_stepsPerClick)] = stepsPerClick;
top[FPSTR(_incrementPerClick)] = incrementPerClick;
}
/**
* 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)
{
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name);
return false;
}
bool oldEnabled = enabled;
int8_t oldLedIo = ledIo;
int8_t oldEaIo = eaIo;
int8_t oldEbIo = ebIo;
byte oldLedMode = ledMode;
byte oldStepsPerClick = stepsPerClick;
byte oldIncrementPerClick = incrementPerClick;
byte oldLedBrightness = ledBrightness;
getJsonValue(top[FPSTR(_enabled)], enabled);
getJsonValue(top[FPSTR(_ledIo)], ledIo);
getJsonValue(top[FPSTR(_eaIo)], eaIo);
getJsonValue(top[FPSTR(_ebIo)], ebIo);
getJsonValue(top[FPSTR(_stepsPerClick)], stepsPerClick);
getJsonValue(top[FPSTR(_incrementPerClick)], incrementPerClick);
ledMode = top[FPSTR(_ledMode)] > 0 && top[FPSTR(_ledMode)] < 4 ? top[FPSTR(_ledMode)] : ledMode;
ledBrightness = top[FPSTR(_ledBrightness)] > 0 && top[FPSTR(_ledBrightness)] <= 255 ? top[FPSTR(_ledBrightness)] : ledBrightness;
if (!initDone) {
// First run: reading from cfg.json
// Nothing to do here, will be all done in setup()
}
// Mod was disabled, so run setup()
else if (enabled && enabled != oldEnabled) {
DEBUG_PRINTF("[%s] Usermod has been re-enabled\n", _name);
setup();
}
// Config has been changed, so adopt to changes
else {
if (!enabled) {
DEBUG_PRINTF("[%s] Usermod has been disabled\n", _name);
cleanup();
}
else {
DEBUG_PRINTF("[%s] Usermod is enabled\n", _name);
if (ledIo != oldLedIo) {
delete ledBus;
initLedBus();
}
if (ledBrightness != oldLedBrightness) {
ledBus->setBrightness(ledBrightness);
isDirty = true;
}
if (ledMode != oldLedMode) {
updateLeds();
}
if (eaIo != oldEaIo || ebIo != oldEbIo || stepsPerClick != oldStepsPerClick || incrementPerClick != oldIncrementPerClick) {
pinManager.deallocatePin(oldEaIo, PinOwner::UM_RGBRotaryEncoder);
pinManager.deallocatePin(oldEbIo, PinOwner::UM_RGBRotaryEncoder);
delete rotaryEncoder;
initRotaryEncoder();
}
}
DEBUG_PRINTF("[%s] Config (re)loaded\n", _name);
}
return true;
}
/*
* 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_RGB_ROTARY_ENCODER;
}
//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!
};
byte RgbRotaryEncoderUsermod::currentPos = 5;
// strings to reduce flash memory usage (used more than twice)
const char RgbRotaryEncoderUsermod::_name[] PROGMEM = "RGB-Rotary-Encoder";
const char RgbRotaryEncoderUsermod::_enabled[] PROGMEM = "Enabled";
const char RgbRotaryEncoderUsermod::_ledIo[] PROGMEM = "LED-pin";
const char RgbRotaryEncoderUsermod::_eaIo[] PROGMEM = "ea-pin";
const char RgbRotaryEncoderUsermod::_ebIo[] PROGMEM = "eb-pin";
const char RgbRotaryEncoderUsermod::_ledMode[] PROGMEM = "LED-Mode";
const char RgbRotaryEncoderUsermod::_ledBrightness[] PROGMEM = "LED-Brightness";
const char RgbRotaryEncoderUsermod::_stepsPerClick[] PROGMEM = "Steps-per-Click";
const char RgbRotaryEncoderUsermod::_incrementPerClick[] PROGMEM = "Increment-per-Click";

View File

@@ -1,62 +0,0 @@
#include "wled.h"
/*
* This file allows you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
* EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h)
* bytes 2400+ are currently ununsed, but might be used for future wled features
*/
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
/*
** Rotary Encoder Example
** Use the Sparkfun Rotary Encoder to vary brightness of LED
**
** Sample the encoder at 500Hz using the millis() function
*/
int fadeAmount = 5; // how many points to fade the Neopixel with each step
unsigned long currentTime;
unsigned long loopTime;
const int pinA = D6; // DT from encoder
const int pinB = D7; // CLK from encoder
unsigned char Enc_A;
unsigned char Enc_B;
unsigned char Enc_A_prev = 0;
//gets called once at boot. Do all initialization that doesn't depend on network here
void userSetup() {
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
currentTime = millis();
loopTime = currentTime;
}
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
void userConnected() {
}
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
void userLoop() {
currentTime = millis(); // get the current elapsed time
if(currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
{
int Enc_A = digitalRead(pinA); // Read encoder pins
int Enc_B = digitalRead(pinB);
if((! Enc_A) && (Enc_A_prev)) { // A has gone from high to low
if(Enc_B == HIGH) { // B is high so clockwise
if(bri + fadeAmount <= 255) bri += fadeAmount; // increase the brightness, dont go over 255
} else if (Enc_B == LOW) { // B is low so counter-clockwise
if(bri - fadeAmount >= 0) bri -= fadeAmount; // decrease the brightness, dont go below 0
}
}
Enc_A_prev = Enc_A; // Store value of A for next time
loopTime = currentTime; // Updates loopTime
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
colorUpdated(6);
}
}

View File

@@ -1,211 +0,0 @@
#pragma once
#include "wled.h"
//v2 usermod that allows to change brightness and color using a rotary encoder,
//change between modes by pressing a button (many encoder have one included)
class RotaryEncoderSet : public Usermod
{
private:
//Private class members. You can declare variables and functions only accessible to your usermod here
unsigned long lastTime = 0;
/*
** Rotary Encoder Example
** Use the Sparkfun Rotary Encoder to vary brightness of LED
**
** Sample the encoder at 500Hz using the millis() function
*/
int fadeAmount = 5; // how many points to fade the Neopixel with each step
unsigned long currentTime;
unsigned long loopTime;
const int pinA = 5; // DT from encoder
const int pinB = 18; // CLK from encoder
const int pinC = 23; // SW from encoder
unsigned char select_state = 0; // 0 = brightness 1 = color
unsigned char button_state = HIGH;
unsigned char prev_button_state = HIGH;
CRGB fastled_col;
CHSV prim_hsv;
int16_t new_val;
unsigned char Enc_A;
unsigned char Enc_B;
unsigned char Enc_A_prev = 0;
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()
{
//Serial.println("Hello from my usermod!");
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
pinMode(pinC, INPUT_PULLUP);
currentTime = millis();
loopTime = currentTime;
}
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
//Serial.println("Connected to WiFi!");
}
/*
* 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()
{
currentTime = millis(); // get the current elapsed time
if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
{
button_state = digitalRead(pinC);
if (prev_button_state != button_state)
{
if (button_state == LOW)
{
if (select_state == 1)
{
select_state = 0;
}
else
{
select_state = 1;
}
prev_button_state = button_state;
}
else
{
prev_button_state = button_state;
}
}
int Enc_A = digitalRead(pinA); // Read encoder pins
int Enc_B = digitalRead(pinB);
if ((!Enc_A) && (Enc_A_prev))
{ // A has gone from high to low
if (Enc_B == HIGH)
{ // B is high so clockwise
if (select_state == 0)
{
if (bri + fadeAmount <= 255)
bri += fadeAmount; // increase the brightness, dont go over 255
}
else
{
fastled_col.red = col[0];
fastled_col.green = col[1];
fastled_col.blue = col[2];
prim_hsv = rgb2hsv_approximate(fastled_col);
new_val = (int16_t)prim_hsv.h + fadeAmount;
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;
}
}
else if (Enc_B == LOW)
{ // B is low so counter-clockwise
if (select_state == 0)
{
if (bri - fadeAmount >= 0)
bri -= fadeAmount; // decrease the brightness, dont go below 0
}
else
{
fastled_col.red = col[0];
fastled_col.green = col[1];
fastled_col.blue = col[2];
prim_hsv = rgb2hsv_approximate(fastled_col);
new_val = (int16_t)prim_hsv.h - fadeAmount;
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;
}
}
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
colorUpdated(NOTIFIER_CALL_MODE_BUTTON);
updateInterfaces()
}
Enc_A_prev = Enc_A; // Store value of A for next time
loopTime = currentTime; // Updates loopTime
}
}
/*
* 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)
{
int reading = 20;
//this code adds "u":{"Light":[20," lux"]} to the info object
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
JsonArray lightArr = user.createNestedArray("Light"); //name
lightArr.add(reading); //value
lightArr.add(" lux"); //unit
}
*/
/*
* 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["user0"] = userVar0;
}
/*
* 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)
{
userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
}
/*
* 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 0xABCD;
}
//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

@@ -39,7 +39,7 @@ void userLoop() {
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
lastTime = millis();
}
}

View File

@@ -0,0 +1,55 @@
# Seven Segment Display
Usermod that uses the overlay feature to create a configurable seven segment display.
This has only been tested on a single configuration. Colon support is entirely untested.
## Installation
Add the compile-time option `-D USERMOD_SEVEN_SEGMENT` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SEVEN_SEGMENT` in `my_config.h`.
## Settings
Settings can be controlled through both the usermod setting page and through MQTT with a raw payload.
##### Example
Topic ```<mqttDeviceTopic||mqttGroupTopic>/sevenSeg/perSegment/set```
Payload ```3```
#### perSegment -- ssLEDPerSegment
The number of individual LEDs per segment. There are 7 segments per digit.
#### perPeriod -- ssLEDPerPeriod
The number of individual LEDs per period. A ':' has 2x periods.
#### startIdx -- ssStartLED
Index of the LED that the display starts at. Allows a seven segment display to be in the middle of a string.
#### timeEnable -- ssTimeEnabled
When true, when displayMask is configured for a time output and no message is set the time will be displayed.
#### scrollSpd -- ssScrollSpeed
Time, in milliseconds, between message shifts when the length of displayMsg exceeds the length of the displayMask.
#### displayMask -- ssDisplayMask
This should represent the configuration of the physical display.
<pre>
HH - 0-23. hh - 1-12, kk - 1-24 hours
MM or mm - 0-59 minutes
SS or ss = 0-59 seconds
: for a colon
All others for alpha numeric, (will be blank when displaying time)
</pre>
##### Example
```HHMMSS ```
```hh:MM:SS ```
#### displayMsg -- ssDisplayMessage
Message to be displayed across the display. If the length exceeds the length of the displayMask the message will scroll at scrollSpd. To 'remove' a message or revert back to time, if timeEnabled is true, set the message to '~'.
#### displayCfg -- ssDisplayConfig
The order that your LEDs are configured. All seven segments in the display need to be wired the same way.
<pre>
-------
/ A / 0 - EDCGFAB
/ F / B 1 - EDCBAFG
/ / 2 - GCDEFAB
------- 3 - GBAFEDC
/ G / 4 - FABGEDC
/ E / C 5 - FABCDEG
/ /
-------
D
</pre>
## Version
20211009 - Initial release

View File

@@ -0,0 +1,497 @@
#pragma once
#include "wled.h"
class SevenSegmentDisplay : public Usermod
{
#define WLED_SS_BUFFLEN 6
#define REFRESHTIME 497
private:
//Runtime variables.
unsigned long lastRefresh = 0;
unsigned long lastCharacterStep = 0;
String ssDisplayBuffer = "";
char ssCharacterMask[36] = {0x77, 0x11, 0x6B, 0x3B, 0x1D, 0x3E, 0x7E, 0x13, 0x7F, 0x1F, 0x5F, 0x7C, 0x66, 0x79, 0x6E, 0x4E, 0x76, 0x5D, 0x44, 0x71, 0x5E, 0x64, 0x27, 0x58, 0x77, 0x4F, 0x1F, 0x48, 0x3E, 0x6C, 0x75, 0x25, 0x7D, 0x2A, 0x3D, 0x6B};
int ssDisplayMessageIdx = 0; //Position of the start of the message to be physically displayed.
bool ssDoDisplayTime = true;
int ssVirtualDisplayMessageIdxStart = 0;
int ssVirtualDisplayMessageIdxEnd = 0;
unsigned long resfreshTime = 497;
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
int ssLEDPerSegment = 1; //The number of LEDs in each segment of the 7 seg (total per digit is 7 * ssLedPerSegment)
int ssLEDPerPeriod = 1; //A Period will have 1x and a Colon will have 2x
int ssStartLED = 0; //The pixel that the display starts at.
/* HH - 0-23. hh - 1-12, kk - 1-24 hours
// MM or mm - 0-59 minutes
// SS or ss = 0-59 seconds
// : for a colon
// All others for alpha numeric, (will be blank when displaying time)
*/
String ssDisplayMask = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment.
/* ssDisplayConfig
// -------
// / A / 0 - EDCGFAB
// / F / B 1 - EDCBAFG
// / / 2 - GCDEFAB
// ------- 3 - GBAFEDC
// / G / 4 - FABGEDC
// / E / C 5 - FABCDEG
// / /
// -------
// D
*/
int ssDisplayConfig = 5; //Physical configuration of the Seven segment display
String ssDisplayMessage = "~";
bool ssTimeEnabled = true; //If not, display message.
unsigned int ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds.
//String to reduce flash memory usage
static const char _str_perSegment[];
static const char _str_perPeriod[];
static const char _str_startIdx[];
static const char _str_displayCfg[];
static const char _str_timeEnabled[];
static const char _str_scrollSpd[];
static const char _str_displayMask[];
static const char _str_displayMsg[];
static const char _str_sevenSeg[];
static const char _str_subFormat[];
static const char _str_topicFormat[];
unsigned long _overlaySevenSegmentProcess()
{
//Do time for now.
if (ssDoDisplayTime)
{
//Format the ssDisplayBuffer based on ssDisplayMask
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
for (int index = 0; index < displayMaskLen; index++)
{
//Only look for time formatting if there are at least 2 characters left in the buffer.
if ((index < displayMaskLen - 1) && (ssDisplayMask[index] == ssDisplayMask[index + 1]))
{
int timeVar = 0;
switch (ssDisplayMask[index])
{
case 'h':
timeVar = hourFormat12(localTime);
break;
case 'H':
timeVar = hour(localTime);
break;
case 'k':
timeVar = hour(localTime) + 1;
break;
case 'M':
case 'm':
timeVar = minute(localTime);
break;
case 'S':
case 's':
timeVar = second(localTime);
break;
}
//Only want to leave a blank in the hour formatting.
if ((ssDisplayMask[index] == 'h' || ssDisplayMask[index] == 'H' || ssDisplayMask[index] == 'k') && timeVar < 10)
ssDisplayBuffer[index] = ' ';
else
ssDisplayBuffer[index] = 0x30 + (timeVar / 10);
ssDisplayBuffer[index + 1] = 0x30 + (timeVar % 10);
//Need to increment the index because of the second digit.
index++;
}
else
{
ssDisplayBuffer[index] = (ssDisplayMask[index] == ':' ? ':' : ' ');
}
}
return REFRESHTIME;
}
else
{
/* This will handle displaying a message and the scrolling of the message if its longer than the buffer length */
//Check to see if the message has scrolled completely
int len = static_cast<int>(ssDisplayMessage.length());
if (ssDisplayMessageIdx > len)
{
//If it has scrolled the whole message, reset it.
setSevenSegmentMessage(ssDisplayMessage);
return REFRESHTIME;
}
//Display message
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
for (int index = 0; index < displayMaskLen; index++)
{
if (ssDisplayMessageIdx + index < len && ssDisplayMessageIdx + index >= 0)
ssDisplayBuffer[index] = ssDisplayMessage[ssDisplayMessageIdx + index];
else
ssDisplayBuffer[index] = ' ';
}
//Increase the displayed message index to progress it one character if the length exceeds the display length.
if (len > displayMaskLen)
ssDisplayMessageIdx++;
return ssScrollSpeed;
}
}
void _overlaySevenSegmentDraw()
{
//Start pixels at ssStartLED, Use ssLEDPerSegment, ssLEDPerPeriod, ssDisplayBuffer
int indexLED = ssStartLED;
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
for (int indexBuffer = 0; indexBuffer < displayMaskLen; indexBuffer++)
{
if (ssDisplayBuffer[indexBuffer] == 0)
break;
else if (ssDisplayBuffer[indexBuffer] == '.')
{
//Won't ever turn off LED lights for a period. (or will we?)
indexLED += ssLEDPerPeriod;
continue;
}
else if (ssDisplayBuffer[indexBuffer] == ':')
{
//Turn off colon if odd second?
indexLED += ssLEDPerPeriod * 2;
}
else if (ssDisplayBuffer[indexBuffer] == ' ')
{
//Turn off all 7 segments.
_overlaySevenSegmentLEDOutput(0, indexLED);
indexLED += ssLEDPerSegment * 7;
}
else
{
//Turn off correct segments.
_overlaySevenSegmentLEDOutput(_overlaySevenSegmentGetCharMask(ssDisplayBuffer[indexBuffer]), indexLED);
indexLED += ssLEDPerSegment * 7;
}
}
}
void _overlaySevenSegmentLEDOutput(char mask, int indexLED)
{
for (char index = 0; index < 7; index++)
{
if ((mask & (0x40 >> index)) != (0x40 >> index))
{
for (int numPerSeg = 0; numPerSeg < ssLEDPerSegment; numPerSeg++)
{
strip.setPixelColor(indexLED + numPerSeg, 0x000000);
}
}
indexLED += ssLEDPerSegment;
}
}
char _overlaySevenSegmentGetCharMask(char var)
{
if (var >= 0x30 && var <= 0x39)
{ /*If its a number, shift to index 0.*/
var -= 0x30;
}
else if (var >= 0x41 && var <= 0x5a)
{ /*If its an Upper case, shift to index 0xA.*/
var -= 0x37;
}
else if (var >= 0x61 && var <= 0x7A)
{ /*If its a lower case, shift to index 0xA.*/
var -= 0x57;
}
else
{ /* Else unsupported, return 0; */
return 0;
}
char mask = ssCharacterMask[static_cast<int>(var)];
/*
0 - EDCGFAB
1 - EDCBAFG
2 - GCDEFAB
3 - GBAFEDC
4 - FABGEDC
5 - FABCDEG
*/
switch (ssDisplayConfig)
{
case 1:
mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1);
mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1);
break;
case 2:
mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1);
mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1);
break;
case 3:
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1);
mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1);
break;
case 4:
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
break;
case 5:
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1);
mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1);
break;
}
return mask;
}
char _overlaySevenSegmentSwapBits(char x, char p1, char p2, char n)
{
/* Move all bits of first set to rightmost side */
char set1 = (x >> p1) & ((1U << n) - 1);
/* Move all bits of second set to rightmost side */
char set2 = (x >> p2) & ((1U << n) - 1);
/* Xor the two sets */
char Xor = (set1 ^ set2);
/* Put the Xor bits back to their original positions */
Xor = (Xor << p1) | (Xor << p2);
/* Xor the 'Xor' with the original number so that the
two sets are swapped */
char result = x ^ Xor;
return result;
}
void _publishMQTTint_P(const char *subTopic, int value)
{
if(mqtt == NULL) return;
char buffer[64];
char valBuffer[12];
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_sevenSeg, subTopic);
sprintf_P(valBuffer, PSTR("%d"), value);
mqtt->publish(buffer, 2, true, valBuffer);
}
void _publishMQTTstr_P(const char *subTopic, String Value)
{
if(mqtt == NULL) return;
char buffer[64];
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_sevenSeg, subTopic);
mqtt->publish(buffer, 2, true, Value.c_str(), Value.length());
}
void _updateMQTT()
{
_publishMQTTint_P(_str_perSegment, ssLEDPerSegment);
_publishMQTTint_P(_str_perPeriod, ssLEDPerPeriod);
_publishMQTTint_P(_str_startIdx, ssStartLED);
_publishMQTTint_P(_str_displayCfg, ssDisplayConfig);
_publishMQTTint_P(_str_timeEnabled, ssTimeEnabled);
_publishMQTTint_P(_str_scrollSpd, ssScrollSpeed);
_publishMQTTstr_P(_str_displayMask, ssDisplayMask);
_publishMQTTstr_P(_str_displayMsg, ssDisplayMessage);
}
bool _cmpIntSetting_P(char *topic, char *payload, const char *setting, void *value)
{
if (strcmp_P(topic, setting) == 0)
{
*((int *)value) = strtol(payload, NULL, 10);
_publishMQTTint_P(setting, *((int *)value));
return true;
}
return false;
}
bool _handleSetting(char *topic, char *payload)
{
if (_cmpIntSetting_P(topic, payload, _str_perSegment, &ssLEDPerSegment))
return true;
if (_cmpIntSetting_P(topic, payload, _str_perPeriod, &ssLEDPerPeriod))
return true;
if (_cmpIntSetting_P(topic, payload, _str_startIdx, &ssStartLED))
return true;
if (_cmpIntSetting_P(topic, payload, _str_displayCfg, &ssDisplayConfig))
return true;
if (_cmpIntSetting_P(topic, payload, _str_timeEnabled, &ssTimeEnabled))
return true;
if (_cmpIntSetting_P(topic, payload, _str_scrollSpd, &ssScrollSpeed))
return true;
if (strcmp_P(topic, _str_displayMask) == 0)
{
ssDisplayMask = String(payload);
ssDisplayBuffer = ssDisplayMask;
_publishMQTTstr_P(_str_displayMask, ssDisplayMask);
return true;
}
if (strcmp_P(topic, _str_displayMsg) == 0)
{
setSevenSegmentMessage(String(payload));
return true;
}
return false;
}
public:
void setSevenSegmentMessage(String message)
{
//If the message isn't blank display it otherwise show time, if enabled.
if (message.length() < 1 || message == "~")
ssDoDisplayTime = ssTimeEnabled;
else
ssDoDisplayTime = false;
//Determine is the message is longer than the display, if it is configure it to scroll the message.
if (message.length() > ssDisplayMask.length())
ssDisplayMessageIdx = -ssDisplayMask.length();
else
ssDisplayMessageIdx = 0;
//If the message isn't the same, update runtime/mqtt (most calls will be resetting message scroll)
if (!ssDisplayMessage.equals(message))
{
_publishMQTTstr_P(_str_displayMsg, message);
ssDisplayMessage = message;
}
}
//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()
{
ssDisplayBuffer = ssDisplayMask;
}
/*
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop()
{
if (millis() - lastRefresh > resfreshTime)
{
//In theory overlaySevenSegmentProcess should return the amount of time until it changes next.
//So we should be okay to trigger the stripi on every process loop.
resfreshTime = _overlaySevenSegmentProcess();
lastRefresh = millis();
strip.trigger();
}
}
void handleOverlayDraw()
{
_overlaySevenSegmentDraw();
}
void onMqttConnect(bool sessionPresent)
{
char subBuffer[48];
if (mqttDeviceTopic[0] != 0)
{
_updateMQTT();
//subscribe for sevenseg messages on the device topic
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttDeviceTopic, _str_sevenSeg);
mqtt->subscribe(subBuffer, 2);
}
if (mqttGroupTopic[0] != 0)
{
//subcribe for sevenseg messages on the group topic
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_sevenSeg);
mqtt->subscribe(subBuffer, 2);
}
}
bool onMqttMessage(char *topic, char *payload)
{
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
size_t topicPrefixLen = strlen_P(PSTR("/sevenSeg/"));
if (strncmp_P(topic, PSTR("/sevenSeg/"), topicPrefixLen) == 0)
topic += topicPrefixLen;
else
return false;
//We only care if the topic ends with /set
size_t topicLen = strlen(topic);
if (topicLen > 4 &&
topic[topicLen - 4] == '/' &&
topic[topicLen - 3] == 's' &&
topic[topicLen - 2] == 'e' &&
topic[topicLen - 1] == 't')
{
//Trim /set and handle it
topic[topicLen - 4] = '\0';
_handleSetting(topic, payload);
}
return true;
}
void addToConfig(JsonObject &root)
{
JsonObject top = root[FPSTR(_str_sevenSeg)];
if (top.isNull())
{
top = root.createNestedObject(FPSTR(_str_sevenSeg));
}
top[FPSTR(_str_perSegment)] = ssLEDPerSegment;
top[FPSTR(_str_perPeriod)] = ssLEDPerPeriod;
top[FPSTR(_str_startIdx)] = ssStartLED;
top[FPSTR(_str_displayMask)] = ssDisplayMask;
top[FPSTR(_str_displayCfg)] = ssDisplayConfig;
top[FPSTR(_str_displayMsg)] = ssDisplayMessage;
top[FPSTR(_str_timeEnabled)] = ssTimeEnabled;
top[FPSTR(_str_scrollSpd)] = ssScrollSpeed;
}
bool readFromConfig(JsonObject &root)
{
JsonObject top = root[FPSTR(_str_sevenSeg)];
bool configComplete = !top.isNull();
//if sevenseg section doesn't exist return
if (!configComplete)
return configComplete;
configComplete &= getJsonValue(top[FPSTR(_str_perSegment)], ssLEDPerSegment);
configComplete &= getJsonValue(top[FPSTR(_str_perPeriod)], ssLEDPerPeriod);
configComplete &= getJsonValue(top[FPSTR(_str_startIdx)], ssStartLED);
configComplete &= getJsonValue(top[FPSTR(_str_displayMask)], ssDisplayMask);
configComplete &= getJsonValue(top[FPSTR(_str_displayCfg)], ssDisplayConfig);
String newDisplayMessage;
configComplete &= getJsonValue(top[FPSTR(_str_displayMsg)], newDisplayMessage);
setSevenSegmentMessage(newDisplayMessage);
configComplete &= getJsonValue(top[FPSTR(_str_timeEnabled)], ssTimeEnabled);
configComplete &= getJsonValue(top[FPSTR(_str_scrollSpd)], ssScrollSpeed);
return configComplete;
}
/*
* 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_SEVEN_SEGMENT_DISPLAY;
}
};
const char SevenSegmentDisplay::_str_perSegment[] PROGMEM = "perSegment";
const char SevenSegmentDisplay::_str_perPeriod[] PROGMEM = "perPeriod";
const char SevenSegmentDisplay::_str_startIdx[] PROGMEM = "startIdx";
const char SevenSegmentDisplay::_str_displayCfg[] PROGMEM = "displayCfg";
const char SevenSegmentDisplay::_str_timeEnabled[] PROGMEM = "timeEnabled";
const char SevenSegmentDisplay::_str_scrollSpd[] PROGMEM = "scrollSpd";
const char SevenSegmentDisplay::_str_displayMask[] PROGMEM = "displayMask";
const char SevenSegmentDisplay::_str_displayMsg[] PROGMEM = "displayMsg";
const char SevenSegmentDisplay::_str_sevenSeg[] PROGMEM = "sevenSeg";

View File

@@ -42,7 +42,7 @@ class StairwayWipeUsermod : public Usermod {
if (millis() + strip.timebase > (cycleTime - 25)) { //wipe complete
effectCurrent = FX_MODE_STATIC;
timeStaticStart = millis();
colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION);
colorUpdated(CALL_MODE_NOTIFICATION);
wipeState = 2;
}
} else if (wipeState == 2) { //static
@@ -54,7 +54,7 @@ class StairwayWipeUsermod : public Usermod {
#ifdef STAIRCASE_WIPE_OFF
effectCurrent = FX_MODE_COLOR_WIPE;
strip.timebase = 360 + (255 - effectSpeed)*75 - millis(); //make sure wipe starts fully lit
colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION);
colorUpdated(CALL_MODE_NOTIFICATION);
wipeState = 4;
#else
turnOff();
@@ -100,7 +100,7 @@ class StairwayWipeUsermod : public Usermod {
bool doReverse = (userVar0 == 2);
seg.setOption(1, doReverse);
colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION);
colorUpdated(CALL_MODE_NOTIFICATION);
}
void turnOff()
@@ -111,7 +111,7 @@ class StairwayWipeUsermod : public Usermod {
transitionDelayTemp = 4000; //fade out slowly
#endif
bri = 0;
colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION);
colorUpdated(CALL_MODE_NOTIFICATION);
wipeState = 0;
userVar0 = 0;
previousUserVar0 = 0;

View File

@@ -47,7 +47,7 @@ void userLoop()
if (millis() + strip.timebase > (cycleTime - 25)) { //wipe complete
effectCurrent = FX_MODE_STATIC;
timeStaticStart = millis();
colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION);
colorUpdated(CALL_MODE_NOTIFICATION);
wipeState = 2;
}
} else if (wipeState == 2) { //static
@@ -59,7 +59,7 @@ void userLoop()
#ifdef STAIRCASE_WIPE_OFF
effectCurrent = FX_MODE_COLOR_WIPE;
strip.timebase = 360 + (255 - effectSpeed)*75 - millis(); //make sure wipe starts fully lit
colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION);
colorUpdated(CALL_MODE_NOTIFICATION);
wipeState = 4;
#else
turnOff();
@@ -93,7 +93,7 @@ void startWipe()
bool doReverse = (userVar0 == 2);
seg.setOption(1, doReverse);
colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION);
colorUpdated(CALL_MODE_NOTIFICATION);
}
void turnOff()
@@ -104,7 +104,7 @@ void turnOff()
transitionDelayTemp = 4000; //fade out slowly
#endif
bri = 0;
colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION);
colorUpdated(CALL_MODE_NOTIFICATION);
wipeState = 0;
userVar0 = 0;
previousUserVar0 = 0;

View File

@@ -0,0 +1,42 @@
# Rotary Encoder (Brightness and Color)
V2 usermod that allows changing brightness and color using a rotary encoder,
change between modes by pressing a button (many encoders have one included)
but it will wait for AUTOSAVE_SETTLE_MS milliseconds, a "settle"
period in case there are other changes (any change will
extend the "settle" window).
It will additionally load preset AUTOSAVE_PRESET_NUM at startup.
during the first `loop()`. Reasoning below.
AutoSaveUsermod is standalone, but if FourLineDisplayUsermod is installed, it will notify the user of the saved changes.
Note: I don't love that WLED doesn't respect the brightness of the preset being auto loaded, so the AutoSaveUsermod will set the AUTOSAVE_PRESET_NUM preset in the first loop, so brightness IS honored. This means WLED will effectively ignore Default brightness and Apply N preset at boot when the AutoSaveUsermod is installed.
## Installation
define `USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR` e.g.
`#define USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR` in my_config.h
or add `-D USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR` to `build_flags` in platformio_override.ini
### Define Your Options
Open Usermod Settings in WLED to change settings:
`fadeAmount` - how many points to fade the Neopixel with each step of the rotary encoder (default 5)
`pin[3]` - pins to connect to the rotary encoder:
- `pin[0]` is pin A on your rotary encoder
- `pin[1]` is pin B on your rotary encoder
- `pin[2]` is the button on your rotary encoder (optional, set to -1 to disable the button and the rotary encoder will control brightness only)
### PlatformIO requirements
No special requirements.
## Change Log
2021-07
* Upgraded to work with the latest WLED code, and make settings configurable in Usermod Settings

View File

@@ -0,0 +1,189 @@
#pragma once
#include "wled.h"
//v2 usermod that allows to change brightness and color using a rotary encoder,
//change between modes by pressing a button (many encoders have one included)
class RotaryEncoderBrightnessColor : public Usermod
{
private:
//Private class members. You can declare variables and functions only accessible to your usermod here
unsigned long lastTime = 0;
unsigned long currentTime;
unsigned long loopTime;
unsigned char select_state = 0; // 0 = brightness 1 = color
unsigned char button_state = HIGH;
unsigned char prev_button_state = HIGH;
CRGB fastled_col;
CHSV prim_hsv;
int16_t new_val;
unsigned char Enc_A;
unsigned char Enc_B;
unsigned char Enc_A_prev = 0;
// private class memebers configurable by Usermod Settings (defaults set inside readFromConfig())
int8_t pins[3]; // pins[0] = DT from encoder, pins[1] = CLK from encoder, pins[2] = CLK from encoder (optional)
int fadeAmount; // how many points to fade the Neopixel with each step
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()
{
//Serial.println("Hello from my usermod!");
pinMode(pins[0], INPUT_PULLUP);
pinMode(pins[1], INPUT_PULLUP);
if(pins[2] >= 0) pinMode(pins[2], INPUT_PULLUP);
currentTime = millis();
loopTime = currentTime;
}
/*
* 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()
{
currentTime = millis(); // get the current elapsed time
if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
{
if(pins[2] >= 0) {
button_state = digitalRead(pins[2]);
if (prev_button_state != button_state)
{
if (button_state == LOW)
{
if (select_state == 1)
{
select_state = 0;
}
else
{
select_state = 1;
}
prev_button_state = button_state;
}
else
{
prev_button_state = button_state;
}
}
}
int Enc_A = digitalRead(pins[0]); // Read encoder pins
int Enc_B = digitalRead(pins[1]);
if ((!Enc_A) && (Enc_A_prev))
{ // A has gone from high to low
if (Enc_B == HIGH)
{ // B is high so clockwise
if (select_state == 0)
{
if (bri + fadeAmount <= 255)
bri += fadeAmount; // increase the brightness, dont go over 255
}
else
{
fastled_col.red = col[0];
fastled_col.green = col[1];
fastled_col.blue = col[2];
prim_hsv = rgb2hsv_approximate(fastled_col);
new_val = (int16_t)prim_hsv.h + fadeAmount;
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;
}
}
else if (Enc_B == LOW)
{ // B is low so counter-clockwise
if (select_state == 0)
{
if (bri - fadeAmount >= 0)
bri -= fadeAmount; // decrease the brightness, dont go below 0
}
else
{
fastled_col.red = col[0];
fastled_col.green = col[1];
fastled_col.blue = col[2];
prim_hsv = rgb2hsv_approximate(fastled_col);
new_val = (int16_t)prim_hsv.h - fadeAmount;
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;
}
}
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
colorUpdated(CALL_MODE_BUTTON);
updateInterfaces(CALL_MODE_BUTTON);
}
Enc_A_prev = Enc_A; // Store value of A for next time
loopTime = currentTime; // Updates loopTime
}
}
void addToConfig(JsonObject& root)
{
JsonObject top = root.createNestedObject("rotEncBrightness");
top["fadeAmount"] = fadeAmount;
JsonArray pinArray = top.createNestedArray("pin");
pinArray.add(pins[0]);
pinArray.add(pins[1]);
pinArray.add(pins[2]);
}
/*
* This example uses a more robust method of checking for missing values in the config, and setting back to defaults:
* - The getJsonValue() function copies the value to the variable only if the key requested is present, returning false with no copy if the value isn't present
* - configComplete is used to return false if any value is missing, not just if the main object is missing
* - The defaults are loaded every time readFromConfig() is run, not just once after boot
*
* This ensures that missing values are added to the config, with their default values, in the rare but plauible cases of:
* - a single value being missing at boot, e.g. if the Usermod was upgraded and a new setting was added
* - a single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
*
* If configComplete is false, the default values are already set, and by returning false, WLED now knows it needs to save the defaults by calling addToConfig()
*/
bool readFromConfig(JsonObject& root)
{
// set defaults here, they will be set before setup() is called, and if any values parsed from ArduinoJson below are missing, the default will be used instead
fadeAmount = 5;
pins[0] = -1;
pins[1] = -1;
pins[2] = -1;
JsonObject top = root["rotEncBrightness"];
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top["fadeAmount"], fadeAmount);
configComplete &= getJsonValue(top["pin"][0], pins[0]);
configComplete &= getJsonValue(top["pin"][1], pins[1]);
configComplete &= getJsonValue(top["pin"][2], pins[2]);
return configComplete;
}
};

View File

@@ -38,7 +38,7 @@ class AutoSaveUsermod : public Usermod {
bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot?
// If we've detected the need to auto save, this will be non zero.
uint16_t autoSaveAfter = 0;
unsigned long autoSaveAfter = 0;
uint8_t knownBrightness = 0;
uint8_t knownEffectSpeed = 0;
@@ -87,6 +87,12 @@ class AutoSaveUsermod : public Usermod {
display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
#endif
initDone = true;
if (enabled && applyAutoSaveOnBoot) applyPreset(autoSavePreset);
knownBrightness = bri;
knownEffectSpeed = effectSpeed;
knownEffectIntensity = effectIntensity;
knownMode = strip.getMode();
knownPalette = strip.getSegment(0).palette;
}
// gets called every time WiFi is (re-)connected. Initialize own network
@@ -97,21 +103,11 @@ class AutoSaveUsermod : public Usermod {
* Da loop.
*/
void loop() {
if (!autoSaveAfterSec || !enabled || strip.isUpdating()) return; // setting 0 as autosave seconds disables autosave
if (!autoSaveAfterSec || !enabled || strip.isUpdating() || currentPreset>0) return; // setting 0 as autosave seconds disables autosave
unsigned long now = millis();
uint8_t currentMode = strip.getMode();
uint8_t currentPalette = strip.getSegment(0).palette;
if (firstLoop) {
firstLoop = false;
if (applyAutoSaveOnBoot) applyPreset(autoSavePreset);
knownBrightness = bri;
knownEffectSpeed = effectSpeed;
knownEffectIntensity = effectIntensity;
knownMode = currentMode;
knownPalette = currentPalette;
return;
}
unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000;
if (knownBrightness != bri) {

View File

@@ -31,6 +31,21 @@
#ifndef FLD_PIN_SDA
#define FLD_PIN_SDA 21
#endif
#ifndef FLD_PIN_CLOCKSPI
#define FLD_PIN_CLOCKSPI 18
#endif
#ifndef FLD_PIN_DATASPI
#define FLD_PIN_DATASPI 23
#endif
#ifndef FLD_PIN_DC
#define FLD_PIN_DC 19
#endif
#ifndef FLD_PIN_CS
#define FLD_PIN_CS 5
#endif
#ifndef FLD_PIN_RESET
#define FLD_PIN_RESET 26
#endif
#else
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 5
@@ -38,6 +53,21 @@
#ifndef FLD_PIN_SDA
#define FLD_PIN_SDA 4
#endif
#ifndef FLD_PIN_CLOCKSPI
#define FLD_PIN_CLOCKSPI 14
#endif
#ifndef FLD_PIN_DATASPI
#define FLD_PIN_DATASPI 13
#endif
#ifndef FLD_PIN_DC
#define FLD_PIN_DC 12
#endif
#ifndef FLD_PIN_CS
#define FLD_PIN_CS 15
#endif
#ifndef FLD_PIN_RESET
#define FLD_PIN_RESET 16
#endif
#endif
// When to time out to the clock or blank the screen
@@ -64,11 +94,13 @@ typedef enum {
typedef enum {
NONE = 0,
SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C
SH1106, // U8X8_SH1106_128X64_WINSTAR_HW_I2C
SSD1306_64, // U8X8_SSD1306_128X64_NONAME_HW_I2C
SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C
SSD1305_64 // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C
SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C
SH1106, // U8X8_SH1106_128X64_WINSTAR_HW_I2C
SSD1306_64, // U8X8_SSD1306_128X64_NONAME_HW_I2C
SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C
SSD1305_64, // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C
SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI
SSD1306_SPI64 // U8X8_SSD1306_128X64_NONAME_HW_SPI
} DisplayType;
class FourLineDisplayUsermod : public Usermod {
@@ -80,8 +112,14 @@ class FourLineDisplayUsermod : public Usermod {
// HW interface & configuration
U8X8 *u8x8 = nullptr; // pointer to U8X8 display object
int8_t sclPin=FLD_PIN_SCL, sdaPin=FLD_PIN_SDA; // I2C pins for interfacing, get initialised in readFromConfig()
#ifndef FLD_SPI_DEFAULT
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA
uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000)
DisplayType type = SSD1306; // display type
#else
int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST
DisplayType type = SSD1306_SPI; // display type
#endif
bool flip = false; // flip display 180°
uint8_t contrast = 10; // screen contrast
uint8_t lineHeight = 1; // 1 row or 2 rows
@@ -118,6 +156,7 @@ class FourLineDisplayUsermod : public Usermod {
static const char _flip[];
static const char _sleepMode[];
static const char _clockMode[];
static const char _busClkFrequency[];
// If display does not work or looks corrupted check the
// constructor reference:
@@ -131,65 +170,92 @@ class FourLineDisplayUsermod : public Usermod {
// network here
void setup() {
if (type == NONE) return;
if (!pinManager.allocatePin(sclPin)) { sclPin = -1; type = NONE; return;}
if (!pinManager.allocatePin(sdaPin)) { pinManager.deallocatePin(sclPin); sclPin = sdaPin = -1; type = NONE; return; }
if (type == SSD1306_SPI || type == SSD1306_SPI64) {
PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true}, { ioPin[2], true }, { ioPin[3], true}, { ioPin[4], true }};
if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
} else {
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true} };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
}
DEBUG_PRINTLN(F("Allocating display."));
switch (type) {
case SSD1306:
#ifdef ESP8266
if (!(sclPin==5 && sdaPin==4))
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
if (!(ioPin[0]==5 && ioPin[1]==4))
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 1;
break;
case SH1106:
#ifdef ESP8266
if (!(sclPin==5 && sdaPin==4))
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
if (!(ioPin[0]==5 && ioPin[1]==4))
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 2;
break;
case SSD1306_64:
#ifdef ESP8266
if (!(sclPin==5 && sdaPin==4))
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
if (!(ioPin[0]==5 && ioPin[1]==4))
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 2;
break;
case SSD1305:
#ifdef ESP8266
if (!(sclPin==5 && sdaPin==4))
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
if (!(ioPin[0]==5 && ioPin[1]==4))
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 1;
break;
case SSD1305_64:
#ifdef ESP8266
if (!(sclPin==5 && sdaPin==4))
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
if (!(ioPin[0]==5 && ioPin[1]==4))
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 2;
break;
case SSD1306_SPI:
if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
lineHeight = 1;
break;
case SSD1306_SPI64:
if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
lineHeight = 2;
break;
default:
u8x8 = nullptr;
}
if (nullptr == u8x8) {
DEBUG_PRINTLN(F("Display init failed."));
for (byte i=0; i<5 && ioPin[i]>=0; i++) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay);
type = NONE;
return;
}
(static_cast<U8X8*>(u8x8))->begin();
initDone = true;
DEBUG_PRINTLN(F("Starting display."));
if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too
u8x8->begin();
setFlipMode(flip);
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
setPowerSave(0);
drawString(0, 0, "Loading...");
initDone = true;
}
// gets called every time WiFi is (re-)connected. Initialize own network
@@ -211,40 +277,46 @@ class FourLineDisplayUsermod : public Usermod {
*/
void setFlipMode(uint8_t mode) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setFlipMode(mode);
u8x8->setFlipMode(mode);
}
void setContrast(uint8_t contrast) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setContrast(contrast);
u8x8->setContrast(contrast);
}
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
if (!ignoreLH && lineHeight==2) (static_cast<U8X8*>(u8x8))->draw1x2String(col, row, string);
else (static_cast<U8X8*>(u8x8))->drawString(col, row, string);
u8x8->setFont(u8x8_font_chroma48medium8_r);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string);
else u8x8->drawString(col, row, string);
}
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
(static_cast<U8X8*>(u8x8))->draw2x2String(col, row, string);
u8x8->setFont(u8x8_font_chroma48medium8_r);
u8x8->draw2x2String(col, row, string);
}
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setFont(font);
if (!ignoreLH && lineHeight==2) (static_cast<U8X8*>(u8x8))->draw1x2Glyph(col, row, glyph);
else (static_cast<U8X8*>(u8x8))->drawGlyph(col, row, glyph);
u8x8->setFont(font);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph);
else u8x8->drawGlyph(col, row, glyph);
}
uint8_t getCols() {
if (type==NONE) return 0;
return (static_cast<U8X8*>(u8x8))->getCols();
return u8x8->getCols();
}
void clear() {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->clear();
u8x8->clear();
}
void setPowerSave(uint8_t save) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setPowerSave(save);
u8x8->setPowerSave(save);
}
void center(String &line, uint8_t width) {
int len = line.length();
if (len<width) for (byte i=(width-len)/2; i>0; i--) line = ' ' + line;
for (byte i=line.length(); i<width; i++) line += ' ';
}
/**
@@ -277,8 +349,8 @@ class FourLineDisplayUsermod : public Usermod {
(knownEffectIntensity != effectIntensity) ||
(knownMode != strip.getMode()) ||
(knownPalette != strip.getSegment(0).palette)) {
knownHour = 99; // force time update
clear();
knownHour = 99; // force time update
lastRedraw = now; // update lastRedraw marker
} else if (sleepMode && !displayTurnedOff && ((now - lastRedraw)/1000)%5 == 0) {
// change line every 5s
showName = !showName;
@@ -303,13 +375,13 @@ class FourLineDisplayUsermod : public Usermod {
break;
}
knownHour = 99; // force time update
// do not update lastRedraw marker if just switching row contenet
} else {
// Nothing to change.
// Turn off display after 3 minutes with no change.
if(sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) {
// We will still check if there is a change in redraw()
// and turn it back on if it changed.
clear(); // force screen clear
sleepOrClock(true);
} else if (displayTurnedOff && clockMode) {
showTime();
@@ -317,9 +389,6 @@ class FourLineDisplayUsermod : public Usermod {
return;
}
// do not update lastRedraw marker if just switching row contenet
if (((now - lastRedraw)/1000)%5 != 0) lastRedraw = now;
// Turn the display back on
if (displayTurnedOff) sleepOrClock(false);
@@ -333,13 +402,14 @@ class FourLineDisplayUsermod : public Usermod {
knownEffectIntensity = effectIntensity;
// Do the actual drawing
String line;
// First row with Wifi name
drawGlyph(0, 0, 80, u8x8_font_open_iconic_embedded_1x1); // home icon
String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0);
drawString(1, 0, ssidString.c_str());
line = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0);
center(line, getCols()-2);
drawString(1, 0, line.c_str());
// Print `~` char to indicate that SSID is longer, than our display
if (knownSsid.length() > getCols()) {
if (knownSsid.length() > (int)getCols()-1) {
drawString(getCols() - 1, 0, "~");
}
@@ -350,12 +420,12 @@ class FourLineDisplayUsermod : public Usermod {
drawString(1, lineHeight, apPass);
} else {
// alternate IP address and server name
String secondLine = knownIp.toString();
line = knownIp.toString();
if (showName && strcmp(serverDescription, "WLED") != 0) {
secondLine = serverDescription;
line = serverDescription;
}
for (uint8_t i=secondLine.length(); i<getCols()-1; i++) secondLine += ' ';
drawString(1, lineHeight, secondLine.c_str());
center(line, getCols()-1);
drawString(1, lineHeight, line.c_str());
}
// draw third and fourth row
@@ -388,10 +458,8 @@ class FourLineDisplayUsermod : public Usermod {
showCurrentEffectOrPalette(knownPalette, JSON_palette_names, line);
break;
case FLD_LINE_TIME:
showTime(false);
break;
default:
// unknown type, do nothing
showTime(false);
break;
}
}
@@ -455,17 +523,30 @@ class FourLineDisplayUsermod : public Usermod {
*/
void overlay(const char* line1, const char *line2, long showHowLong) {
if (displayTurnedOff) {
// Turn the display back on
// Turn the display back on (includes clear())
sleepOrClock(false);
} else {
clear();
}
// Print the overlay
clear();
if (line1) drawString(0, 1*lineHeight, line1);
if (line2) drawString(0, 2*lineHeight, line2);
if (line1) {
String buf = line1;
center(buf, getCols());
drawString(0, 1*lineHeight, buf.c_str());
}
if (line2) {
String buf = line2;
center(buf, getCols());
drawString(0, 2*lineHeight, buf.c_str());
}
overlayUntil = millis() + showHowLong;
}
void setLineType(byte lT) {
lineType = (Line4Type) lT;
}
/**
* Line 3 or 4 (last two lines) can be marked with an
* arrow in the first column. Pass 2 or 3 to this to
@@ -485,6 +566,7 @@ class FourLineDisplayUsermod : public Usermod {
* Enable sleep (turn the display off) or clock mode.
*/
void sleepOrClock(bool enabled) {
clear();
if (enabled) {
if (clockMode) showTime();
else setPowerSave(1);
@@ -510,8 +592,6 @@ class FourLineDisplayUsermod : public Usermod {
if (knownMinute == minuteCurrent && knownHour == hourCurrent) {
// Time hasn't changed.
if (!fullScreen) return;
} else {
//if (fullScreen) clear();
}
knownMinute = minuteCurrent;
knownHour = hourCurrent;
@@ -595,10 +675,10 @@ class FourLineDisplayUsermod : public Usermod {
* 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(FPSTR(_name));
JsonArray i2c_pin = top.createNestedArray("pin");
i2c_pin.add(sclPin);
i2c_pin.add(sdaPin);
JsonObject top = root.createNestedObject(FPSTR(_name));
JsonArray io_pin = top.createNestedArray("pin");
for (byte i=0; i<5; i++) io_pin.add(ioPin[i]);
top["help4PinTypes"] = F("Clk,Data,CS,DC,RST"); // help for Settings page
top["type"] = type;
top[FPSTR(_flip)] = (bool) flip;
top[FPSTR(_contrast)] = contrast;
@@ -606,6 +686,7 @@ class FourLineDisplayUsermod : public Usermod {
top[FPSTR(_screenTimeOut)] = screenTimeout/1000;
top[FPSTR(_sleepMode)] = (bool) sleepMode;
top[FPSTR(_clockMode)] = (bool) clockMode;
top[FPSTR(_busClkFrequency)] = ioFrequency/1000;
DEBUG_PRINTLN(F("4 Line Display config saved."));
}
@@ -620,8 +701,7 @@ class FourLineDisplayUsermod : public Usermod {
bool readFromConfig(JsonObject& root) {
bool needsRedraw = false;
DisplayType newType = type;
int8_t newScl = sclPin;
int8_t newSda = sdaPin;
int8_t newPin[5]; for (byte i=0; i<5; i++) newPin[i] = ioPin[i];
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
@@ -630,45 +710,47 @@ class FourLineDisplayUsermod : public Usermod {
return false;
}
newScl = top["pin"][0] | newScl;
newSda = top["pin"][1] | newSda;
newType = top["type"] | newType;
for (byte i=0; i<5; i++) newPin[i] = top["pin"][i] | ioPin[i];
flip = top[FPSTR(_flip)] | flip;
contrast = top[FPSTR(_contrast)] | contrast;
refreshRate = (top[FPSTR(_refreshRate)] | refreshRate/1000) * 1000;
screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000;
sleepMode = top[FPSTR(_sleepMode)] | sleepMode;
clockMode = top[FPSTR(_clockMode)] | clockMode;
ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
DEBUG_PRINT(FPSTR(_name));
if (!initDone) {
// first run: reading from cfg.json
sclPin = newScl;
sdaPin = newSda;
for (byte i=0; i<5; i++) ioPin[i] = newPin[i];
type = newType;
DEBUG_PRINTLN(F(" config loaded."));
} else {
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
if (sclPin!=newScl || sdaPin!=newSda || type!=newType) {
if (type != NONE) delete (static_cast<U8X8*>(u8x8));
pinManager.deallocatePin(sclPin);
pinManager.deallocatePin(sdaPin);
sclPin = newScl;
sdaPin = newSda;
if (newScl<0 || newSda<0) {
bool pinsChanged = false;
for (byte i=0; i<5; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; }
if (pinsChanged || type!=newType) {
if (type != NONE) delete u8x8;
for (byte i=0; i<5; i++) {
if (ioPin[i]>=0) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay);
ioPin[i] = newPin[i];
}
if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1
type = NONE;
return true;
} else type = newType;
setup();
needsRedraw |= true;
}
if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too
setContrast(contrast);
setFlipMode(flip);
if (needsRedraw && !wakeDisplay()) redraw(true);
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
return !(top[_busClkFrequency]).isNull();
}
/*
@@ -681,10 +763,11 @@ class FourLineDisplayUsermod : public Usermod {
};
// strings to reduce flash memory usage (used more than twice)
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRateSec";
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRateSec";
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz";

View File

@@ -0,0 +1,45 @@
# I2C 4 Line Display Usermod ALT
Thank you to the authors of the original version of these usermods. It would not have been possible without them!
"usermod_v2_four_line_display"
"usermod_v2_rotary_encoder_ui"
The core of these usermods are a copy of the originals. The main changes are done to the FourLineDisplay usermod.
The display usermod UI has been completely changed.
The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod.
Without the display it functions identical to the original.
The original "usermod_v2_auto_save" will not work with the display just yet.
Press the encoder to cycle through the options:
*Brightness
*Speed
*Intensity
*Palette
*Effect
*Main Color (only if display is used)
*Saturation (only if display is used)
Press and hold the encoder to display Network Info
if AP is active then it will display AP ssid and Password
Also shows if the timer is enabled
[See the pair of usermods in action](https://www.youtube.com/watch?v=ulZnBt9z3TI)
## Installation
Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions
Then to activate this alternative usermod add `#define USE_ALT_DISPlAY` to the `usermods_list.cpp` file,
or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file
### PlatformIO requirements
Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`.
## Change Log
2021-10
* First public release

View File

@@ -0,0 +1,970 @@
#pragma once
#include "wled.h"
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
//
// Insired by the usermod_v2_four_line_display
//
// v2 usermod for using 128x32 or 128x64 i2c
// OLED displays to provide a four line display
// for WLED.
//
// Dependencies
// * This usermod REQURES the ModeSortUsermod
// * This Usermod works best, by far, when coupled
// with RotaryEncoderUIUsermod.
//
// Make sure to enable NTP and set your time zone in WLED Config | Time.
//
// REQUIREMENT: You must add the following requirements to
// REQUIREMENT: "lib_deps" within platformio.ini / platformio_override.ini
// REQUIREMENT: * U8g2 (the version already in platformio.ini is fine)
// REQUIREMENT: * Wire
//
//The SCL and SDA pins are defined here.
#ifdef ARDUINO_ARCH_ESP32
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 22
#endif
#ifndef FLD_PIN_SDA
#define FLD_PIN_SDA 21
#endif
#ifndef FLD_PIN_CLOCKSPI
#define FLD_PIN_CLOCKSPI 18
#endif
#ifndef FLD_PIN_DATASPI
#define FLD_PIN_DATASPI 23
#endif
#ifndef FLD_PIN_DC
#define FLD_PIN_DC 19
#endif
#ifndef FLD_PIN_CS
#define FLD_PIN_CS 5
#endif
#ifndef FLD_PIN_RESET
#define FLD_PIN_RESET 26
#endif
#else
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 5
#endif
#ifndef FLD_PIN_SDA
#define FLD_PIN_SDA 4
#endif
#ifndef FLD_PIN_CLOCKSPI
#define FLD_PIN_CLOCKSPI 14
#endif
#ifndef FLD_PIN_DATASPI
#define FLD_PIN_DATASPI 13
#endif
#ifndef FLD_PIN_DC
#define FLD_PIN_DC 12
#endif
#ifndef FLD_PIN_CS
#define FLD_PIN_CS 15
#endif
#ifndef FLD_PIN_RESET
#define FLD_PIN_RESET 16
#endif
#endif
// When to time out to the clock or blank the screen
// if SLEEP_MODE_ENABLED.
#define SCREEN_TIMEOUT_MS 60*1000 // 1 min
#define TIME_INDENT 0
#define DATE_INDENT 2
// Minimum time between redrawing screen in ms
#define USER_LOOP_REFRESH_RATE_MS 100
// Extra char (+1) for null
#define LINE_BUFFER_SIZE 16+1
#define MAX_JSON_CHARS 19+1
#define MAX_MODE_LINE_SPACE 13+1
typedef enum {
NONE = 0,
SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C
SH1106, // U8X8_SH1106_128X64_WINSTAR_HW_I2C
SSD1306_64, // U8X8_SSD1306_128X64_NONAME_HW_I2C
SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C
SSD1305_64, // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C
SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI
SSD1306_SPI64 // U8X8_SSD1306_128X64_NONAME_HW_SPI
} DisplayType;
/*
Fontname: benji_custom_icons_1x
Copyright:
Glyphs: 1/1
BBX Build Mode: 3
* 4 = custom palette
*/
const uint8_t u8x8_font_benji_custom_icons_1x1[13] U8X8_FONT_SECTION("u8x8_font_benji_custom_icons_1x1") =
"\4\4\1\1<n\372\377\275\277\26\34";
/*
Fontname: benji_custom_icons_2x
Copyright:
Glyphs: 8/8
BBX Build Mode: 3
// all the icons uses are consolidated into a single library to simplify code
// these are just the required icons stripped from the U8x8 libraries in addition to a few new custom icons
* 1 = sun
* 2 = skip forward
* 3 = fire
* 4 = custom palette
* 5 = puzzle piece
* 6 = moon
* 7 = brush
* 8 = custom saturation
*/
const uint8_t u8x8_font_benji_custom_icons_2x2[261] U8X8_FONT_SECTION("u8x8_font_benji_custom_icons_2x2") =
"\1\10\2\2\200\200\14\14\300\340\360\363\363\360\340\300\14\14\200\200\1\1\60\60\3\7\17\317\317\17\7\3"
"\60\60\1\1\374\370\360\340\340\300\200\0\374\370\360\340\340\300\200\0\77\37\17\7\7\3\1\0\77\37\17\7"
"\7\3\1\0\0\200\340\360\377\376\374\360\0\0\300\200\0\0\0\0\17\77\177\377\17\7\301\340\370\374\377\377"
"\377|\0\0\360\370\234\236\376\363\363\377\377\363\363\376><\370\360\3\17\77yy\377\377\377\377\317\17\17"
"\17\17\7\3\360\360\360\360\366\377\377\366\360\360\360\360\0\0\0\0\377\377\377\377\237\17\17\237\377\377\377\377"
"\6\17\17\6\340\370\374\376\377\340\200\0\0\0\0\0\0\0\0\0\3\17\37\77\177\177\177\377\376|||"
"\70\30\14\0\0\0\0\0\0\0\0``\360\370|<\36\7\2\0\300\360\376\377\177\77\36\0\1\1\0"
"\0\0\0\0\200\200\14\14\300\340\360\363\363\360\340\300\14\14\200\200\1\1\60\60\3\4\10\310\310\10\4\3"
"\60\60\1\1";
/*
Fontname: benji_custom_icons_6x
Copyright:
Glyphs: 8/8
BBX Build Mode: 3
// 6x6 icons libraries take up a lot of memory thus all the icons uses are consolidated into a single library
// these are just the required icons stripped from the U8x8 libraries in addition to a few new custom icons
* 1 = sun
* 2 = skip forward
* 3 = fire
* 4 = custom palette
* 5 = puzzle piece
* 6 = moon
* 7 = brush
* 8 = custom saturation
*/
const uint8_t u8x8_font_benji_custom_icons_6x6[2308] U8X8_FONT_SECTION("u8x8_font_benji_custom_icons_6x6") =
"\1\10\6\6\0\0\0\0\0\0\200\300\300\300\300\200\0\0\0\0\0\0\0\0\0\36\77\77\77\77\36\0"
"\0\0\0\0\0\0\0\0\200\300\300\300\300\200\0\0\0\0\0\0\0\0\0\0\0\0\7\17\17\17\17\7"
"\0\0\0\0\200\300\340\340\340\360\360\360\360\360\360\340\340\340\300\200\0\0\0\0\7\17\17\17\17\7\0\0"
"\0\0\0\0\300\340\340\340\340\300\0\0\0\0\0\0\340\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\376\374\340\0\0\0\0\0\0\300\340\340\340\340\300\3\7\7\7\7\3\0\0\0\0\0\0"
"\7\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\7\0\0\0\0\0\0\3\7"
"\7\7\7\3\0\0\0\0\0\0\340\360\360\360\360\340\0\0\0\0\1\3\7\7\7\17\17\17\17\17\17\7"
"\7\7\3\1\0\0\0\0\340\360\360\360\360\340\0\0\0\0\0\0\0\0\0\0\0\0\1\3\3\3\3\1"
"\0\0\0\0\0\0\0\0\0x\374\374\374\374x\0\0\0\0\0\0\0\0\0\1\3\3\3\3\1\0\0"
"\0\0\0\0\300\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\200\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\376\376\374\370\360\360\340\300\200"
"\200\0\0\0\0\0\0\0\0\0\0\0\377\377\377\376\376\374\370\360\360\340\300\200\200\0\0\0\0\0\0\0"
"\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\177\77\77\37\17\7\7\3\1\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\77\37\17\7"
"\7\3\1\0\377\377\377\177\177\77\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\377\377\377\177"
"\177\77\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\3\1\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\3\1\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\1\376\374\374\370\360\340\300\200\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\200\340\360\374"
"\377\377\377\377\377\377\377\377\377\376\370\300\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\300\340\360\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\37\0\0\0\0"
"\0\0\4\370\360\360\340\300\200\0\0\0\0\0\0\0\0\0\0\0\370\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\177\77\37\7\3\0\0\0\0\0\200\300\360\374\377\377\377\377\377\377\377\376\370\340\0\0\0"
"\0\0\0\0\3\37\177\377\377\377\377\377\377\377\377\377\77\17\7\1\0\0\0\0\0\200\300\360\370\374\376\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\1\3\7\17\37\77\77\177\200"
"\0\0\0\0\0\0\340\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\17\1\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\200\300\340\340\360\360\370|<>>>~\377\377\377\377\377\377\377\177"
"\77\36\36\36\36<|\370\370\360\360\340\340\200\0\0\0\0\0\0\0\0\300\360\374\376\377\377\377\377\377\377"
"\377\360\340\300\300\300\300\340\360\377\377\377\377\377\377\370\360\340\340\340\340\360\370\377\377\377\377\377\377\377\377\377"
"\374\360\340\200\360\377\377\377\377\377\207\3\1\1\1\1\3\207\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\207\3\1\1\1\1\3\207\377\377\377\377\377\17\377\377\377\377\377\377\377\376~>>"
"\77\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\376\376\376\377\377\377"
"\177\77\37\7\0\0\3\17\77\177\377\377\360\340\300\300\300\300\340\360\377\377\377\377\377\377\377\377\377\377\77\17"
"\17\7\7\7\7\7\7\7\7\7\3\3\3\3\1\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37"
"\37\77\77\177\177\177\377\377\377\377\377\377\377\377\377~\30\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\370\374\376\377\377\377\377\377\377\376\374\360\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\360\360\360\360\360\360\360\360\360\360\360\360"
"\360\363\377\377\377\377\377\377\377\377\363\360\360\360\360\360\360\360\360\360\360\360\360\360\0\0\0\0\0\0\0\0"
"\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\374\374\376\376\377\377\377\377"
"\377\376\374\360\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\17\17\17\17\17\17\37\77\177\377\377\377\377"
"\377\377\377\377\377\377\377\377\3\3\7\7\17\17\17\17\7\7\3\0\377\377\377\377\377\377\377\377\377\377\377\377"
"\360\300\0\0\0\0\0\0\0\0\300\360\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\200\300\340\360\360\370\374\374\376\376\7\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\300\360\374\376\377\377\377\377\377\377\377"
"\377\377\377\340\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\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\374\360\300\200\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\17\177\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\376\374\370\360\360\340\340\300\300\300\200\200\200\200\0\0\0\0\0\0\200\200"
"\200\200\0\0\0\0\1\7\37\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\7\1\0\0\0\0\0\0\0\0\0\0\1\3\3\7"
"\17\17\37\37\37\77\77\77\77\177\177\177\177\177\177\77\77\77\77\37\37\37\17\17\7\3\3\1\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\200\200\300\340\360\360\370\374\374\376\377~\34\10\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\200\300\300\340\360\360\370\374\376\376\377\377\377\377\377\377\177\77\17\7\3"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\4\6\17\17\37\77\177\377"
"\377\377\377\377\377\377\77\37\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\370\374\376"
"\376\377\377\377\377\377\377\376\376\374\370\340\0\0\0\0\3\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\200\360\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\17\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`px\374\376\377\377\377\377\377\377"
"\177\177\177\77\77\37\17\7\3\1\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\200\300\300\200\0\0\0\0\0\0\0\0\0\14\36\77\77\36\14\0\0"
"\0\0\0\0\0\0\0\200\300\300\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\7\17\17\7\3"
"\0\200\300\340\360\360\370\370\370\374\374\374\374\370\370\370\360\360\340\300\200\0\3\7\17\17\7\3\0\0\0\0"
"\0\0\0\0\300\340\360\360\340\300\0\0\0\0\340\374\377\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177"
"\177\177\177\177\177\377\374\340\0\0\0\0\300\340\360\360\340\300\0\0\0\1\3\3\1\0\0\0\0\0\1\17"
"\77\177\370\340\300\200\200\0\0\0\0\0\0\0\0\200\200\300\340\370\177\77\17\1\0\0\0\0\0\1\3\3"
"\1\0\0\0\0\0\0\0\0\0\60x\374\374x\60\0\0\0\1\3\3\7\7\7\16\16\16\16\7\7\7"
"\3\3\1\0\0\0\60x\374\374x\60\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\14\36\77\77\36\14\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0";
class FourLineDisplayUsermod : public Usermod {
private:
bool initDone = false;
unsigned long lastTime = 0;
// HW interface & configuration
U8X8 *u8x8 = nullptr; // pointer to U8X8 display object
#ifndef FLD_SPI_DEFAULT
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA
uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000)
DisplayType type = SSD1306_64; // display type
#else
int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST
DisplayType type = SSD1306_SPI; // display type
#endif
bool flip = false; // flip display 180°
uint8_t contrast = 10; // screen contrast
uint8_t lineHeight = 1; // 1 row or 2 rows
uint32_t refreshRate = USER_LOOP_REFRESH_RATE_MS; // in ms
uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms
bool sleepMode = true; // allow screen sleep?
bool clockMode = false; // display clock
// needRedraw marks if redraw is required to prevent often redrawing.
bool needRedraw = true;
// Next variables hold the previous known values to determine if redraw is
// required.
String knownSsid = "";
IPAddress knownIp;
uint8_t knownBrightness = 0;
uint8_t knownEffectSpeed = 0;
uint8_t knownEffectIntensity = 0;
uint8_t knownMode = 0;
uint8_t knownPalette = 0;
uint8_t knownMinute = 99;
byte brightness100;
byte fxspeed100;
byte fxintensity100;
bool knownnightlight = nightlightActive;
bool wificonnected = interfacesInited;
bool powerON = true;
bool displayTurnedOff = false;
unsigned long lastUpdate = 0;
unsigned long lastRedraw = 0;
unsigned long overlayUntil = 0;
// Set to 2 or 3 to mark lines 2 or 3. Other values ignored.
byte markLineNum = 0;
byte markColNum = 0;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _contrast[];
static const char _refreshRate[];
static const char _screenTimeOut[];
static const char _flip[];
static const char _sleepMode[];
static const char _clockMode[];
static const char _busClkFrequency[];
// If display does not work or looks corrupted check the
// constructor reference:
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
// or check the gallery:
// https://github.com/olikraus/u8g2/wiki/gallery
public:
// gets called once at boot. Do all initialization that doesn't depend on
// network here
void setup() {
if (type == NONE) return;
if (type == SSD1306_SPI || type == SSD1306_SPI64) {
PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true}, { ioPin[2], true }, { ioPin[3], true}, { ioPin[4], true }};
if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
} else {
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true} };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
}
DEBUG_PRINTLN(F("Allocating display."));
switch (type) {
case SSD1306:
#ifdef ESP8266
if (!(ioPin[0]==5 && ioPin[1]==4))
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 1;
break;
case SH1106:
#ifdef ESP8266
if (!(ioPin[0]==5 && ioPin[1]==4))
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 2;
break;
case SSD1306_64:
#ifdef ESP8266
if (!(ioPin[0]==5 && ioPin[1]==4))
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 2;
break;
case SSD1305:
#ifdef ESP8266
if (!(ioPin[0]==5 && ioPin[1]==4))
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 1;
break;
case SSD1305_64:
#ifdef ESP8266
if (!(ioPin[0]==5 && ioPin[1]==4))
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
lineHeight = 2;
break;
case SSD1306_SPI:
if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
lineHeight = 1;
break;
case SSD1306_SPI64:
if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
lineHeight = 2;
break;
default:
u8x8 = nullptr;
}
if (nullptr == u8x8) {
DEBUG_PRINTLN(F("Display init failed."));
for (byte i=0; i<5 && ioPin[i]>=0; i++) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay);
type = NONE;
return;
}
initDone = true;
DEBUG_PRINTLN(F("Starting display."));
if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too
u8x8->begin();
setFlipMode(flip);
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
setPowerSave(0);
drawString(0, 0, "Loading...");
}
// gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here
void connected() {}
/**
* Da loop.
*/
void loop() {
if (displayTurnedOff && millis() - lastUpdate < 1000) {
return;
}else if (millis() - lastUpdate < refreshRate){
return;}
redraw(false);
lastUpdate = millis();
}
/**
* Wrappers for screen drawing
*/
void setFlipMode(uint8_t mode) {
if (type==NONE) return;
u8x8->setFlipMode(mode);
}
void setContrast(uint8_t contrast) {
if (type==NONE) return;
u8x8->setContrast(contrast);
}
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) {
if (type==NONE) return;
u8x8->setFont(u8x8_font_chroma48medium8_r);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string);
else u8x8->drawString(col, row, string);
}
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
if (type==NONE) return;
u8x8->setFont(u8x8_font_chroma48medium8_r);
u8x8->draw2x2String(col, row, string);
}
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) {
if (type==NONE) return;
u8x8->setFont(font);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph);
else u8x8->drawGlyph(col, row, glyph);
}
uint8_t getCols() {
if (type==NONE) return 0;
return u8x8->getCols();
}
void clear() {
if (type==NONE) return;
u8x8->clear();
}
void setPowerSave(uint8_t save) {
if (type==NONE) return;
u8x8->setPowerSave(save);
}
void center(String &line, uint8_t width) {
int len = line.length();
if (len<width) for (byte i=(width-len)/2; i>0; i--) line = ' ' + line;
for (byte i=line.length(); i<width; i++) line += ' ';
}
//function to update lastredraw
void updateRedrawTime(){
lastRedraw = millis();
}
/**
* Redraw the screen (but only if things have changed
* or if forceRedraw).
*/
void redraw(bool forceRedraw) {
if (type==NONE) return;
if (overlayUntil > 0) {
if (millis() >= overlayUntil) {
// Time to display the overlay has elapsed.
overlayUntil = 0;
forceRedraw = true;
} else {
// We are still displaying the overlay
// Don't redraw.
return;
}
}
// Check if values which are shown on display changed from the last time.
if (forceRedraw) {
needRedraw = true;
} else if ((bri == 0 && powerON) || (bri > 0 && !powerON)) { //trigger power icon
powerON = !powerON;
drawStatusIcons();
lastRedraw = millis();
} else if (knownnightlight != nightlightActive) { //trigger moon icon
knownnightlight = nightlightActive;
drawStatusIcons();
if (knownnightlight) overlay(" Timer On", 1000, 6);
lastRedraw = millis();
}else if (wificonnected != interfacesInited){ //trigger wifi icon
wificonnected = interfacesInited;
drawStatusIcons();
lastRedraw = millis();
} else if (knownMode != effectCurrent) {
knownMode = effectCurrent;
if(displayTurnedOff)needRedraw = true;
else showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3);
} else if (knownPalette != effectPalette) {
knownPalette = effectPalette;
if(displayTurnedOff)needRedraw = true;
else showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 2);
} else if (knownBrightness != bri) {
if(displayTurnedOff && nightlightActive){needRedraw = false; knownBrightness = bri;}
else if(displayTurnedOff)needRedraw = true;
else updateBrightness();
} else if (knownEffectSpeed != effectSpeed) {
if(displayTurnedOff)needRedraw = true;
else updateSpeed();
} else if (knownEffectIntensity != effectIntensity) {
if(displayTurnedOff)needRedraw = true;
else updateIntensity();
}
if (!needRedraw) {
// Nothing to change.
// Turn off display after 1 minutes with no change.
if(sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) {
// We will still check if there is a change in redraw()
// and turn it back on if it changed.
sleepOrClock(true);
} else if (displayTurnedOff && clockMode) {
showTime();
}
return;
} else {
clear();
}
needRedraw = false;
lastRedraw = millis();
if (displayTurnedOff) {
// Turn the display back on
sleepOrClock(false);
}
// Update last known values.
knownSsid = apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() :
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
knownBrightness = bri;
knownMode = effectCurrent;
knownPalette = effectPalette;
knownEffectSpeed = effectSpeed;
knownEffectIntensity = effectIntensity;
knownnightlight = nightlightActive;
wificonnected = interfacesInited;
// Do the actual drawing
// First row: Icons
draw2x2GlyphIcons();
drawArrow();
drawStatusIcons();
// Second row
updateBrightness();
updateSpeed();
updateIntensity();
// Third row
showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 2); //Palette info
// Fourth row
showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info
}
void updateBrightness(){
knownBrightness = bri;
if(overlayUntil == 0){
brightness100 = (((float)(bri)/255)*100);
char lineBuffer[4];
sprintf_P(lineBuffer, PSTR("%-3d"), brightness100);
drawString(1, lineHeight, lineBuffer);
lastRedraw = millis();}
}
void updateSpeed(){
knownEffectSpeed = effectSpeed;
if(overlayUntil == 0){
fxspeed100 = (((float)(effectSpeed)/255)*100);
char lineBuffer[4];
sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100);
drawString(5, lineHeight, lineBuffer);
lastRedraw = millis();}
}
void updateIntensity(){
knownEffectIntensity = effectIntensity;
if(overlayUntil == 0){
fxintensity100 = (((float)(effectIntensity)/255)*100);
char lineBuffer[4];
sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100);
drawString(9, lineHeight, lineBuffer);
lastRedraw = millis();}
}
void draw2x2GlyphIcons(){
if(lineHeight == 2){
drawGlyph(1, 0, 1, u8x8_font_benji_custom_icons_2x2, true);//brightness icon
drawGlyph(5, 0, 2, u8x8_font_benji_custom_icons_2x2, true);//speed icon
drawGlyph(9, 0, 3, u8x8_font_benji_custom_icons_2x2, true);//intensity icon
drawGlyph(14, 2*lineHeight, 4, u8x8_font_benji_custom_icons_2x2, true);//palette icon
drawGlyph(14, 3*lineHeight, 5, u8x8_font_benji_custom_icons_2x2, true);//effect icon
}
else{
drawGlyph(2, 0, 69, u8x8_font_open_iconic_weather_1x1);//brightness icon
drawGlyph(6, 0, 72, u8x8_font_open_iconic_play_1x1);//speed icon
drawGlyph(10, 0, 78, u8x8_font_open_iconic_thing_1x1);//intensity icon
drawGlyph(15, 2*lineHeight, 4, u8x8_font_benji_custom_icons_1x1);//palette icon
drawGlyph(15, 3*lineHeight, 70, u8x8_font_open_iconic_thing_1x1);//effect icon
}
}
void drawStatusIcons(){
drawGlyph(14, 0, 80 + (wificonnected?0:1), u8x8_font_open_iconic_embedded_1x1, true); // wifi icon
drawGlyph(15, 0, 78 + (bri > 0 ? 0 : 3), u8x8_font_open_iconic_embedded_1x1, true); // power icon
drawGlyph(13, 0, 66 + (nightlightActive?0:4), u8x8_font_open_iconic_weather_1x1, true); // moon icon for nighlight mode
}
/**
* marks the position of the arrow showing
* the current setting being changed
* pass line and colum info
*/
void setMarkLine(byte newMarkLineNum, byte newMarkColNum) {
markLineNum = newMarkLineNum;
markColNum = newMarkColNum;
}
//Draw the arrow for the current setting beiong changed
void drawArrow(){
if(markColNum != 255 && markLineNum !=255)drawGlyph(markColNum, markLineNum*lineHeight, 69, u8x8_font_open_iconic_play_1x1);
}
//Display the current effect or palette (desiredEntry)
// on the appropriate line (row).
void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) {
knownMode = effectCurrent;
knownPalette = effectPalette;
if(overlayUntil == 0){
char lineBuffer[MAX_JSON_CHARS];
char smallBuffer1[MAX_MODE_LINE_SPACE];
char smallBuffer2[MAX_MODE_LINE_SPACE];
char smallBuffer3[MAX_MODE_LINE_SPACE+1];
uint8_t qComma = 0;
bool insideQuotes = false;
bool spaceHit = false;
uint8_t printedChars = 0;
uint8_t smallChars1 = 0;
uint8_t smallChars2 = 0;
uint8_t smallChars3 = 0;
uint8_t totalCount = 0;
char singleJsonSymbol;
// Find the mode name in JSON
for (size_t i = 0; i < strlen_P(qstring); i++) { //find and get the full text for printing
singleJsonSymbol = pgm_read_byte_near(qstring + i);
if (singleJsonSymbol == '\0') break;
switch (singleJsonSymbol) {
case '"':
insideQuotes = !insideQuotes;
break;
case '[':
case ']':
break;
case ',':
qComma++;
default:
if (!insideQuotes || (qComma != inputEffPal)) break;
lineBuffer[printedChars++] = singleJsonSymbol;
totalCount++;
}
if ((qComma > inputEffPal)) break;
}
if(lineHeight ==2){ // use this code for 8 line display
if(printedChars < (MAX_MODE_LINE_SPACE)){ // use big font if the text fits
for (;printedChars < (MAX_MODE_LINE_SPACE-1); printedChars++) {lineBuffer[printedChars]=' '; }
lineBuffer[printedChars] = 0;
drawString(1, row*lineHeight, lineBuffer);
lastRedraw = millis();
}else{ // for long names divide the text into 2 lines and print them small
for (uint8_t i = 0; i < printedChars; i++){
switch (lineBuffer[i]){
case ' ':
if(i > 4 && !spaceHit) {
spaceHit = true;
break;}
if(!spaceHit) smallBuffer1[smallChars1++] = lineBuffer[i];
if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i];
break;
default:
if(!spaceHit) smallBuffer1[smallChars1++] = lineBuffer[i];
if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i];
break;
}
}
for (; smallChars1 < (MAX_MODE_LINE_SPACE-1); smallChars1++) smallBuffer1[smallChars1]=' ';
smallBuffer1[smallChars1] = 0;
drawString(1, row*lineHeight, smallBuffer1, true);
for (; smallChars2 < (MAX_MODE_LINE_SPACE-1); smallChars2++) smallBuffer2[smallChars2]=' ';
smallBuffer2[smallChars2] = 0;
drawString(1, row*lineHeight+1, smallBuffer2, true);
lastRedraw = millis();
}
}
else{ // use this code for 4 ling displays
if (printedChars > MAX_MODE_LINE_SPACE) printedChars = MAX_MODE_LINE_SPACE;
for (uint8_t i = 0; i < printedChars; i++){
smallBuffer3[smallChars3++] = lineBuffer[i];
}
for (; smallChars3 < (MAX_MODE_LINE_SPACE); smallChars3++) smallBuffer3[smallChars3]=' ';
smallBuffer3[smallChars3] = 0;
drawString(1, row*lineHeight, smallBuffer3, true);
lastRedraw = millis();
}
}
}
/**
* If there screen is off or in clock is displayed,
* this will return true. This allows us to throw away
* the first input from the rotary encoder but
* to wake up the screen.
*/
bool wakeDisplay() {
//knownHour = 99;
if (displayTurnedOff) {
// Turn the display back on
sleepOrClock(false);
redraw(true);
return true;
}
return false;
}
/**
* Allows you to show one line and a glyph as overlay for a
* period of time.
* Clears the screen and prints.
*/
void overlay(const char* line1, long showHowLong, byte glyphType) {
if (displayTurnedOff) {
// Turn the display back on
sleepOrClock(false);
}
// Print the overlay
clear();
if (glyphType > 0){
if ( lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_font_benji_custom_icons_6x6, true);
else drawGlyph(7, lineHeight, glyphType, u8x8_font_benji_custom_icons_2x2, true);
}
if (line1) drawString(0, 3*lineHeight, line1);
overlayUntil = millis() + showHowLong;
}
void networkOverlay(const char* line1, long showHowLong) {
if (displayTurnedOff) {
// Turn the display back on
sleepOrClock(false);
}
// Print the overlay
clear();
// First row string
if (line1) drawString(0, 0, line1);
// Second row with Wifi name
String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); //
drawString(0, lineHeight, ssidString.c_str());
// Print `~` char to indicate that SSID is longer, than our display
if (knownSsid.length() > getCols()) {
drawString(getCols() - 1, 0, "~");
}
// Third row with IP and Psssword in AP Mode
drawString(0, lineHeight*2, (knownIp.toString()).c_str());
if (apActive) {
String appassword = apPass;
drawString(0, lineHeight*3, appassword.c_str());
}
overlayUntil = millis() + showHowLong;
}
/**
* Enable sleep (turn the display off) or clock mode.
*/
void sleepOrClock(bool enabled) {
if (enabled) {
if (clockMode) {
clear();
knownMinute = 99;
showTime();
}else setPowerSave(1);
displayTurnedOff = true;
}
else {
setPowerSave(0);
displayTurnedOff = false;
}
}
/**
* Display the current date and time in large characters
* on the middle rows. Based 24 or 12 hour depending on
* the useAMPM configuration.
*/
void showTime() {
if(knownMinute != minute(localTime)){ //only redraw clock if it has changed
char lineBuffer[LINE_BUFFER_SIZE];
//updateLocalTime();
byte AmPmHour = hour(localTime);
boolean isitAM = true;
if (useAMPM) {
if (AmPmHour > 11) AmPmHour -= 12;
if (AmPmHour == 0) AmPmHour = 12;
if (hour(localTime) > 11) isitAM = false;
}
clear();
drawStatusIcons(); //icons power, wifi, timer, etc
sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime));
draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day
sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hour(localTime)), minute(localTime));
draw2x2String(TIME_INDENT+2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds
if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time
knownMinute = minute(localTime);
}
}
/*
* 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) {
//JsonObject user = root["u"];
//if (user.isNull()) user = root.createNestedObject("u");
//JsonArray data = user.createNestedArray(F("4LineDisplay"));
//data.add(F("Loaded."));
//}
/*
* 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) {
// if (!initDone) return; // prevent crash on boot applyPreset()
//}
/*
* 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 also not yet add your setting to one of the settings pages automatically.
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
*
* 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(FPSTR(_name));
JsonArray io_pin = top.createNestedArray("pin");
for (byte i=0; i<5; i++) io_pin.add(ioPin[i]);
top["help4PinTypes"] = F("Clk,Data,CS,DC,RST"); // help for Settings page
top["type"] = type;
top[FPSTR(_flip)] = (bool) flip;
top[FPSTR(_contrast)] = contrast;
top[FPSTR(_refreshRate)] = refreshRate/10;
top[FPSTR(_screenTimeOut)] = screenTimeout/1000;
top[FPSTR(_sleepMode)] = (bool) sleepMode;
top[FPSTR(_clockMode)] = (bool) clockMode;
top[FPSTR(_busClkFrequency)] = ioFrequency/1000;
DEBUG_PRINTLN(F("4 Line Display config saved."));
}
/*
* 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 once immediately after boot)
*
* 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 :)
*/
bool readFromConfig(JsonObject& root) {
bool needsRedraw = false;
DisplayType newType = type;
int8_t newPin[5]; for (byte i=0; i<5; i++) newPin[i] = ioPin[i];
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
newType = top["type"] | newType;
for (byte i=0; i<5; i++) newPin[i] = top["pin"][i] | ioPin[i];
flip = top[FPSTR(_flip)] | flip;
contrast = top[FPSTR(_contrast)] | contrast;
refreshRate = (top[FPSTR(_refreshRate)] | refreshRate/10) * 10;
screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000;
sleepMode = top[FPSTR(_sleepMode)] | sleepMode;
clockMode = top[FPSTR(_clockMode)] | clockMode;
ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
DEBUG_PRINT(FPSTR(_name));
if (!initDone) {
// first run: reading from cfg.json
for (byte i=0; i<5; i++) ioPin[i] = newPin[i];
type = newType;
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<5; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; }
if (pinsChanged || type!=newType) {
if (type != NONE) delete u8x8;
for (byte i=0; i<5; i++) {
if (ioPin[i]>=0) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay);
ioPin[i] = newPin[i];
}
if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1
type = NONE;
return true;
} else type = newType;
setup();
needsRedraw |= true;
}
if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too
setContrast(contrast);
setFlipMode(flip);
if (needsRedraw && !wakeDisplay()) redraw(true);
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !(top[_busClkFrequency]).isNull();
}
/*
* 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_FOUR_LINE_DISP;
}
};
// strings to reduce flash memory usage (used more than twice)
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRate0.01Sec";
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz";

View File

@@ -39,10 +39,11 @@
#ifndef USERMOD_FOUR_LINE_DISPLAY
// These constants won't be defined if we aren't using FourLineDisplay.
#define FLD_LINE_3_BRIGHTNESS 0
#define FLD_LINE_3_EFFECT_SPEED 0
#define FLD_LINE_3_EFFECT_INTENSITY 0
#define FLD_LINE_3_PALETTE 0
#define FLD_LINE_BRIGHTNESS 0
#define FLD_LINE_MODE 0
#define FLD_LINE_EFFECT_SPEED 0
#define FLD_LINE_EFFECT_INTENSITY 0
#define FLD_LINE_PALETTE 0
#endif
@@ -55,10 +56,10 @@ private:
int fadeAmount = 10; // Amount to change every step (brightness)
unsigned long currentTime;
unsigned long loopTime;
const int pinA = ENCODER_DT_PIN; // DT from encoder
const int pinB = ENCODER_CLK_PIN; // CLK from encoder
const int pinC = ENCODER_SW_PIN; // SW from encoder
unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed
int8_t pinA = ENCODER_DT_PIN; // DT from encoder
int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder
int8_t pinC = ENCODER_SW_PIN; // SW from encoder
unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed
unsigned char button_state = HIGH;
unsigned char prev_button_state = HIGH;
@@ -75,10 +76,20 @@ private:
unsigned char Enc_B;
unsigned char Enc_A_prev = 0;
bool currentEffectAndPaleeteInitialized = false;
bool currentEffectAndPaletteInitialized = false;
uint8_t effectCurrentIndex = 0;
uint8_t effectPaletteIndex = 0;
bool initDone = false;
bool enabled = true;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
static const char _DT_pin[];
static const char _CLK_pin[];
static const char _SW_pin[];
public:
/*
* setup() is called once at boot. WiFi is not yet connected at this point.
@@ -86,6 +97,18 @@ public:
*/
void setup()
{
PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } };
if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) {
// BUG: configuring this usermod with conflicting pins
// will cause it to de-allocate pins it does not own
// (at second config)
// This is the exact type of bug solved by pinManager
// tracking the owner tags....
pinA = pinB = pinC = -1;
enabled = false;
return;
}
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
pinMode(pinC, INPUT_PULLUP);
@@ -101,10 +124,12 @@ public:
// But it's optional. But you want it.
display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
if (display != nullptr) {
display->setLineThreeType(FLD_LINE_3_BRIGHTNESS);
display->setLineType(FLD_LINE_BRIGHTNESS);
display->setMarkLine(3);
}
#endif
initDone = true;
}
/*
@@ -128,12 +153,14 @@ public:
*/
void loop()
{
if (!enabled) return;
currentTime = millis(); // get the current elapsed time
// Initialize effectCurrentIndex and effectPaletteIndex to
// current state. We do it here as (at least) effectCurrent
// is not yet initialized when setup is called.
if (!currentEffectAndPaleeteInitialized) {
if (!currentEffectAndPaletteInitialized) {
findCurrentEffectAndPalette();
}
@@ -153,19 +180,19 @@ public:
if (display != nullptr) {
switch(newState) {
case 0:
changedState = changeState("Brightness", FLD_LINE_3_BRIGHTNESS, 3);
changedState = changeState("Brightness", FLD_LINE_BRIGHTNESS, 3);
break;
case 1:
changedState = changeState("Select FX", FLD_LINE_3_EFFECT_SPEED, 2);
changedState = changeState("Select FX", FLD_LINE_MODE, 2);
break;
case 2:
changedState = changeState("FX Speed", FLD_LINE_3_EFFECT_SPEED, 3);
changedState = changeState("FX Speed", FLD_LINE_EFFECT_SPEED, 3);
break;
case 3:
changedState = changeState("FX Intensity", FLD_LINE_3_EFFECT_INTENSITY, 3);
changedState = changeState("FX Intensity", FLD_LINE_EFFECT_INTENSITY, 3);
break;
case 4:
changedState = changeState("Palette", FLD_LINE_3_PALETTE, 3);
changedState = changeState("Palette", FLD_LINE_PALETTE, 3);
break;
}
}
@@ -229,9 +256,9 @@ public:
}
void findCurrentEffectAndPalette() {
currentEffectAndPaleeteInitialized = true;
currentEffectAndPaletteInitialized = true;
for (uint8_t i = 0; i < strip.getModeCount(); i++) {
byte value = modes_alpha_indexes[i];
//byte value = modes_alpha_indexes[i];
if (modes_alpha_indexes[i] == effectCurrent) {
effectCurrentIndex = i;
break;
@@ -239,7 +266,7 @@ public:
}
for (uint8_t i = 0; i < strip.getPaletteCount(); i++) {
byte value = palettes_alpha_indexes[i];
//byte value = palettes_alpha_indexes[i];
if (palettes_alpha_indexes[i] == strip.getSegment(0).palette) {
effectPaletteIndex = i;
break;
@@ -255,7 +282,7 @@ public:
return false;
}
display->overlay("Mode change", stateName, 1500);
display->setLineThreeType(lineThreeMode);
display->setLineType(lineThreeMode);
display->setMarkLine(markedLine);
}
#endif
@@ -267,8 +294,8 @@ public:
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
updateInterfaces(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
colorUpdated(CALL_MODE_DIRECT_CHANGE);
updateInterfaces(CALL_MODE_DIRECT_CHANGE);
}
void changeBrightness(bool increase) {
@@ -386,10 +413,73 @@ public:
*/
void readFromJsonState(JsonObject &root)
{
userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
//userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
}
/**
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
*/
void addToConfig(JsonObject &root) {
// we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = enabled;
top[FPSTR(_DT_pin)] = pinA;
top[FPSTR(_CLK_pin)] = pinB;
top[FPSTR(_SW_pin)] = pinC;
DEBUG_PRINTLN(F("Rotary Encoder 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: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
int8_t newDTpin = pinA;
int8_t newCLKpin = pinB;
int8_t newSWpin = pinC;
enabled = top[FPSTR(_enabled)] | enabled;
newDTpin = top[FPSTR(_DT_pin)] | newDTpin;
newCLKpin = top[FPSTR(_CLK_pin)] | newCLKpin;
newSWpin = top[FPSTR(_SW_pin)] | newSWpin;
DEBUG_PRINT(FPSTR(_name));
if (!initDone) {
// first run: reading from cfg.json
pinA = newDTpin;
pinB = newCLKpin;
pinC = newSWpin;
DEBUG_PRINTLN(F(" config loaded."));
} else {
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) {
pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI);
pinA = newDTpin;
pinB = newCLKpin;
pinC = newSWpin;
if (pinA<0 || pinB<0 || pinC<0) {
enabled = false;
return true;
}
setup();
}
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_enabled)].isNull();
}
/*
* 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.
@@ -399,3 +489,10 @@ public:
return USERMOD_ID_ROTARY_ENC_UI;
}
};
// strings to reduce flash memory usage (used more than twice)
const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder";
const char RotaryEncoderUIUsermod::_enabled[] PROGMEM = "enabled";
const char RotaryEncoderUIUsermod::_DT_pin[] PROGMEM = "DT-pin";
const char RotaryEncoderUIUsermod::_CLK_pin[] PROGMEM = "CLK-pin";
const char RotaryEncoderUIUsermod::_SW_pin[] PROGMEM = "SW-pin";

View File

@@ -0,0 +1,45 @@
# Rotary Encoder UI Usermod ALT
Thank you to the authors of the original version of these usermods. It would not have been possible without them!
"usermod_v2_four_line_display"
"usermod_v2_rotary_encoder_ui"
The core of these usermods are a copy of the originals. The main changes are done to the FourLineDisplay usermod.
The display usermod UI has been completely changed.
The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod.
Without the display it functions identical to the original.
The original "usermod_v2_auto_save" will not work with the display just yet.
Press the encoder to cycle through the options:
*Brightness
*Speed
*Intensity
*Palette
*Effect
*Main Color (only if display is used)
*Saturation (only if display is used)
Press and hold the encoder to display Network Info
if AP is active then it will display AP ssid and Password
Also shows if the timer is enabled
[See the pair of usermods in action](https://www.youtube.com/watch?v=ulZnBt9z3TI)
## Installation
Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions
Then to activate this alternative usermod add `#define USE_ALT_DISPlAY` to the `usermods_list.cpp` file,
or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file
### PlatformIO requirements
Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`.
## Change Log
2021-10
* First public release

View File

@@ -0,0 +1,569 @@
#pragma once
#include "wled.h"
//
// Inspired by the original v2 usermods
// * usermod_v2_rotaty_encoder_ui
//
// v2 usermod that provides a rotary encoder-based UI.
//
// This usermod allows you to control:
//
// * Brightness
// * Selected Effect
// * Effect Speed
// * Effect Intensity
// * Palette
//
// Change between modes by pressing a button.
//
// Dependencies
// * This usermod REQURES the ModeSortUsermod
// * This Usermod works best coupled with
// FourLineDisplayUsermod.
//
// If FourLineDisplayUsermod is used the folowing options are also inabled
//
// * main color
// * saturation of main color
// * display network (long press buttion)
//
#ifndef ENCODER_DT_PIN
#define ENCODER_DT_PIN 18
#endif
#ifndef ENCODER_CLK_PIN
#define ENCODER_CLK_PIN 5
#endif
#ifndef ENCODER_SW_PIN
#define ENCODER_SW_PIN 19
#endif
// The last UI state, remove color and saturation option if diplay not active(too many options)
#ifdef USERMOD_FOUR_LINE_DISPLAY
#define LAST_UI_STATE 6
#else
#define LAST_UI_STATE 4
#endif
class RotaryEncoderUIUsermod : public Usermod {
private:
int fadeAmount = 5; // Amount to change every step (brightness)
unsigned long currentTime;
unsigned long loopTime;
unsigned long buttonHoldTIme;
int8_t pinA = ENCODER_DT_PIN; // DT from encoder
int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder
int8_t pinC = ENCODER_SW_PIN; // SW from encoder
unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed
unsigned char button_state = HIGH;
unsigned char prev_button_state = HIGH;
bool networkShown = false;
uint16_t currentHue1 = 6425; // default reboot color
byte currentSat1 = 255;
#ifdef USERMOD_FOUR_LINE_DISPLAY
FourLineDisplayUsermod *display;
#else
void* display = nullptr;
#endif
byte *modes_alpha_indexes = nullptr;
byte *palettes_alpha_indexes = nullptr;
unsigned char Enc_A;
unsigned char Enc_B;
unsigned char Enc_A_prev = 0;
bool currentEffectAndPaletteInitialized = false;
uint8_t effectCurrentIndex = 0;
uint8_t effectPaletteIndex = 0;
uint8_t knownMode = 0;
uint8_t knownPalette = 0;
bool initDone = false;
bool enabled = true;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
static const char _DT_pin[];
static const char _CLK_pin[];
static const char _SW_pin[];
public:
/*
* 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()
{
PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } };
if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) {
// BUG: configuring this usermod with conflicting pins
// will cause it to de-allocate pins it does not own
// (at second config)
// This is the exact type of bug solved by pinManager
// tracking the owner tags....
pinA = pinB = pinC = -1;
enabled = false;
return;
}
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
pinMode(pinC, INPUT_PULLUP);
currentTime = millis();
loopTime = currentTime;
ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT);
modes_alpha_indexes = modeSortUsermod->getModesAlphaIndexes();
palettes_alpha_indexes = modeSortUsermod->getPalettesAlphaIndexes();
#ifdef USERMOD_FOUR_LINE_DISPLAY
// This Usermod uses FourLineDisplayUsermod for the best experience.
// But it's optional. But you want it.
display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
if (display != nullptr) {
display->setMarkLine(1, 0);
}
#endif
initDone = true;
Enc_A = digitalRead(pinA); // Read encoder pins
Enc_B = digitalRead(pinB);
Enc_A_prev = Enc_A;
}
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
//Serial.println("Connected to WiFi!");
}
/*
* 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()
{
currentTime = millis(); // get the current elapsed time
// Initialize effectCurrentIndex and effectPaletteIndex to
// current state. We do it here as (at least) effectCurrent
// is not yet initialized when setup is called.
if (!currentEffectAndPaletteInitialized) {
findCurrentEffectAndPalette();}
if(modes_alpha_indexes[effectCurrentIndex] != effectCurrent
|| palettes_alpha_indexes[effectPaletteIndex] != effectPalette){
currentEffectAndPaletteInitialized = false;
}
if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
{
button_state = digitalRead(pinC);
if (prev_button_state != button_state)
{
if (button_state == HIGH && (millis()-buttonHoldTIme < 3000))
{
prev_button_state = button_state;
char newState = select_state + 1;
if (newState > LAST_UI_STATE) newState = 0;
bool changedState = true;
if (display != nullptr) {
switch(newState) {
case 0:
changedState = changeState(" Brightness", 1, 0, 1);
break;
case 1:
changedState = changeState(" Speed", 1, 4, 2);
break;
case 2:
changedState = changeState(" Intensity", 1 ,8, 3);
break;
case 3:
changedState = changeState(" Color Palette", 2, 0, 4);
break;
case 4:
changedState = changeState(" Effect", 3, 0, 5);
break;
case 5:
changedState = changeState(" Main Color", 255, 255, 7);
break;
case 6:
changedState = changeState(" Saturation", 255, 255, 8);
break;
}
}
if (changedState) {
select_state = newState;
}
}
else
{
prev_button_state = button_state;
networkShown = false;
if(!prev_button_state)buttonHoldTIme = millis();
}
}
if (!prev_button_state && (millis()-buttonHoldTIme > 3000) && !networkShown) displayNetworkInfo(); //long press for network info
Enc_A = digitalRead(pinA); // Read encoder pins
Enc_B = digitalRead(pinB);
if ((Enc_A) && (!Enc_A_prev))
{ // A has gone from high to low
if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse
{ // B is high so clockwise
switch(select_state) {
case 0:
changeBrightness(true);
break;
case 1:
changeEffectSpeed(true);
break;
case 2:
changeEffectIntensity(true);
break;
case 3:
changePalette(true);
break;
case 4:
changeEffect(true);
break;
case 5:
changeHue(true);
break;
case 6:
changeSat(true);
break;
}
}
else if (Enc_B == HIGH)
{ // B is low so counter-clockwise
switch(select_state) {
case 0:
changeBrightness(false);
break;
case 1:
changeEffectSpeed(false);
break;
case 2:
changeEffectIntensity(false);
break;
case 3:
changePalette(false);
break;
case 4:
changeEffect(false);
break;
case 5:
changeHue(false);
break;
case 6:
changeSat(false);
break;
}
}
}
Enc_A_prev = Enc_A; // Store value of A for next time
loopTime = currentTime; // Updates loopTime
}
}
void displayNetworkInfo(){
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->networkOverlay(" NETWORK INFO", 15000);
networkShown = true;
#endif
}
void findCurrentEffectAndPalette() {
currentEffectAndPaletteInitialized = true;
for (uint8_t i = 0; i < strip.getModeCount(); i++) {
if (modes_alpha_indexes[i] == effectCurrent) {
effectCurrentIndex = i;
break;
}
}
for (uint8_t i = 0; i < strip.getPaletteCount(); i++) {
if (palettes_alpha_indexes[i] == effectPalette) {
effectPaletteIndex = i;
break;
}
}
}
boolean changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display != nullptr) {
if (display->wakeDisplay()) {
// Throw away wake up input
return false;
}
display->overlay(stateName, 750, glyph);
display->setMarkLine(markedLine, markedCol);
}
#endif
return true;
}
void lampUdated() {
//bool fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette);
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
colorUpdated(CALL_MODE_DIRECT_CHANGE);
updateInterfaces(CALL_MODE_DIRECT_CHANGE);
}
void changeBrightness(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
// Throw away wake up input
return;
}
#endif
if (increase) bri = (bri + fadeAmount <= 255) ? (bri + fadeAmount) : 255;
else bri = (bri - fadeAmount >= 0) ? (bri - fadeAmount) : 0;
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateBrightness();
#endif
}
void changeEffect(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
// Throw away wake up input
return;
}
#endif
if (increase) effectCurrentIndex = (effectCurrentIndex + 1 >= strip.getModeCount()) ? 0 : (effectCurrentIndex + 1);
else effectCurrentIndex = (effectCurrentIndex - 1 < 0) ? (strip.getModeCount() - 1) : (effectCurrentIndex - 1);
effectCurrent = modes_alpha_indexes[effectCurrentIndex];
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3);
#endif
}
void changeEffectSpeed(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
// Throw away wake up input
return;
}
#endif
if (increase) effectSpeed = (effectSpeed + fadeAmount <= 255) ? (effectSpeed + fadeAmount) : 255;
else effectSpeed = (effectSpeed - fadeAmount >= 0) ? (effectSpeed - fadeAmount) : 0;
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateSpeed();
#endif
}
void changeEffectIntensity(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
// Throw away wake up input
return;
}
#endif
if (increase) effectIntensity = (effectIntensity + fadeAmount <= 255) ? (effectIntensity + fadeAmount) : 255;
else effectIntensity = (effectIntensity - fadeAmount >= 0) ? (effectIntensity - fadeAmount) : 0;
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateIntensity();
#endif
}
void changePalette(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
// Throw away wake up input
return;
}
#endif
if (increase) effectPaletteIndex = (effectPaletteIndex + 1 >= strip.getPaletteCount()) ? 0 : (effectPaletteIndex + 1);
else effectPaletteIndex = (effectPaletteIndex - 1 < 0) ? (strip.getPaletteCount() - 1) : (effectPaletteIndex - 1);
effectPalette = palettes_alpha_indexes[effectPaletteIndex];
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2);
#endif
}
void changeHue(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
// Throw away wake up input
return;
}
#endif
if(increase) currentHue1 += 321;
else currentHue1 -= 321;
colorHStoRGB(currentHue1, currentSat1, col);
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateRedrawTime();
#endif
}
void changeSat(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
// Throw away wake up input
return;
}
#endif
if(increase) currentSat1 = (currentSat1 + 5 <= 255 ? (currentSat1 + 5) : 255);
else currentSat1 = (currentSat1 - 5 >= 0 ? (currentSat1 - 5) : 0);
colorHStoRGB(currentHue1, currentSat1, col);
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateRedrawTime();
#endif
}
/*
* 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)
{
int reading = 20;
//this code adds "u":{"Light":[20," lux"]} to the info object
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
JsonArray lightArr = user.createNestedArray("Light"); //name
lightArr.add(reading); //value
lightArr.add(" lux"); //unit
}
*/
/*
* 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["user0"] = userVar0;
}
/*
* 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)
{
//userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
}
/**
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
*/
void addToConfig(JsonObject &root) {
// we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = enabled;
top[FPSTR(_DT_pin)] = pinA;
top[FPSTR(_CLK_pin)] = pinB;
top[FPSTR(_SW_pin)] = pinC;
DEBUG_PRINTLN(F("Rotary Encoder 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: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
int8_t newDTpin = pinA;
int8_t newCLKpin = pinB;
int8_t newSWpin = pinC;
enabled = top[FPSTR(_enabled)] | enabled;
newDTpin = top[FPSTR(_DT_pin)] | newDTpin;
newCLKpin = top[FPSTR(_CLK_pin)] | newCLKpin;
newSWpin = top[FPSTR(_SW_pin)] | newSWpin;
DEBUG_PRINT(FPSTR(_name));
if (!initDone) {
// first run: reading from cfg.json
pinA = newDTpin;
pinB = newCLKpin;
pinC = newSWpin;
DEBUG_PRINTLN(F(" config loaded."));
} else {
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) {
pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI);
pinA = newDTpin;
pinB = newCLKpin;
pinC = newSWpin;
if (pinA<0 || pinB<0 || pinC<0) {
enabled = false;
return true;
}
setup();
}
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_enabled)].isNull();
}
/*
* 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_ROTARY_ENC_UI;
}
};
// strings to reduce flash memory usage (used more than twice)
const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder";
const char RotaryEncoderUIUsermod::_enabled[] PROGMEM = "enabled";
const char RotaryEncoderUIUsermod::_DT_pin[] PROGMEM = "DT-pin";
const char RotaryEncoderUIUsermod::_CLK_pin[] PROGMEM = "CLK-pin";
const char RotaryEncoderUIUsermod::_SW_pin[] PROGMEM = "SW-pin";

View File

@@ -64,13 +64,13 @@ void hourChime()
{
//strip.resetSegments();
selectWordSegments(true);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
savePreset(13, false);
selectWordSegments(false);
//strip.getSegment(0).setOption(0, true);
strip.getSegment(0).setOption(2, true);
applyPreset(12);
colorUpdated(NOTIFIER_CALL_MODE_FX_CHANGED);
colorUpdated(CALL_MODE_FX_CHANGED);
}
void displayTime(byte hour, byte minute)

View File

@@ -388,41 +388,12 @@ uint16_t WS2812FX::mode_rainbow_cycle(void) {
}
/*
* theater chase function
*/
uint16_t WS2812FX::theater_chase(uint32_t color1, uint32_t color2, bool do_palette) {
byte gap = 2 + ((255 - SEGMENT.intensity) >> 5);
uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*2;
uint32_t it = now / cycleTime;
if (it != SEGENV.step) //new color
{
SEGENV.aux0 = (SEGENV.aux0 +1) % gap;
SEGENV.step = it;
}
for(uint16_t i = 0; i < SEGLEN; i++) {
if((i % gap) == SEGENV.aux0) {
if (do_palette)
{
setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0));
} else {
setPixelColor(i, color1);
}
} else {
setPixelColor(i, color2);
}
}
return FRAMETIME;
}
/*
* Theatre-style crawling lights.
* Inspired by the Adafruit examples.
*/
uint16_t WS2812FX::mode_theater_chase(void) {
return theater_chase(SEGCOLOR(0), SEGCOLOR(1), true);
return running(SEGCOLOR(0), SEGCOLOR(1), true);
}
@@ -431,7 +402,7 @@ uint16_t WS2812FX::mode_theater_chase(void) {
* Inspired by the Adafruit examples.
*/
uint16_t WS2812FX::mode_theater_chase_rainbow(void) {
return theater_chase(color_wheel(SEGENV.step), SEGCOLOR(1), false);
return running(color_wheel(SEGENV.step), SEGCOLOR(1), true);
}
@@ -976,29 +947,27 @@ uint16_t WS2812FX::mode_chase_flash_random(void) {
/*
* Alternating pixels running function.
*/
uint16_t WS2812FX::running(uint32_t color1, uint32_t color2) {
uint8_t pxw = 1 + (SEGMENT.intensity >> 5);
uint32_t cycleTime = 35 + (255 - SEGMENT.speed);
uint16_t WS2812FX::running(uint32_t color1, uint32_t color2, bool theatre) {
uint8_t width = (theatre ? 3 : 1) + (SEGMENT.intensity >> 4); // window
uint32_t cycleTime = 50 + (255 - SEGMENT.speed);
uint32_t it = now / cycleTime;
if (SEGMENT.speed == 0) it = 0;
bool usePalette = color1 == SEGCOLOR(0);
for(uint16_t i = 0; i < SEGLEN; i++) {
if((i + SEGENV.aux0) % (pxw*2) < pxw) {
if (color1 == SEGCOLOR(0))
{
setPixelColor(SEGLEN -i -1, color_from_palette(SEGLEN -i -1, true, PALETTE_SOLID_WRAP, 0));
} else
{
setPixelColor(SEGLEN -i -1, color1);
}
uint32_t col = color2;
if (usePalette) color1 = color_from_palette(i, true, PALETTE_SOLID_WRAP, 0);
if (theatre) {
if ((i % width) == SEGENV.aux0) col = color1;
} else {
setPixelColor(SEGLEN -i -1, color2);
int8_t pos = (i % (width<<1));
if ((pos < SEGENV.aux0-width) || ((pos >= SEGENV.aux0) && (pos < SEGENV.aux0+width))) col = color1;
}
setPixelColor(i,col);
}
if (it != SEGENV.step )
{
SEGENV.aux0 = (SEGENV.aux0 +1) % (pxw*2);
SEGENV.aux0 = (SEGENV.aux0 +1) % (theatre ? width : (width<<1));
SEGENV.step = it;
}
return FRAMETIME;
@@ -1181,10 +1150,10 @@ uint16_t WS2812FX::mode_fire_flicker(void) {
uint32_t it = now / cycleTime;
if (SEGENV.step == it) return FRAMETIME;
byte w = (SEGCOLOR(0) >> 24) & 0xFF;
byte r = (SEGCOLOR(0) >> 16) & 0xFF;
byte g = (SEGCOLOR(0) >> 8) & 0xFF;
byte b = (SEGCOLOR(0) & 0xFF);
byte w = (SEGCOLOR(0) >> 24);
byte r = (SEGCOLOR(0) >> 16);
byte g = (SEGCOLOR(0) >> 8);
byte b = (SEGCOLOR(0) );
byte lum = (SEGMENT.palette == 0) ? MAX(w, MAX(r, MAX(g, b))) : 255;
lum /= (((256-SEGMENT.intensity)/16)+1);
for(uint16_t i = 0; i < SEGLEN; i++) {
@@ -1247,68 +1216,29 @@ uint16_t WS2812FX::mode_loading(void) {
//American Police Light with all LEDs Red and Blue
uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, bool all)
uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2)
{
uint16_t counter = now * ((SEGMENT.speed >> 2) +1);
uint16_t idexR = (counter * SEGLEN) >> 16;
if (idexR >= SEGLEN) idexR = 0;
uint16_t topindex = SEGLEN >> 1;
uint16_t idexB = (idexR > topindex) ? idexR - topindex : idexR + topindex;
if (SEGENV.call == 0) SEGENV.aux0 = idexR;
if (idexB >= SEGLEN) idexB = 0; //otherwise overflow on odd number of LEDs
if (all) { //different algo, ensuring immediate fill
if (idexB > idexR) {
fill(color2);
for (uint16_t i = idexR; i < idexB; i++) setPixelColor(i, color1);
} else {
fill(color1);
for (uint16_t i = idexB; i < idexR; i++) setPixelColor(i, color2);
}
} else { //regular dot-only mode
uint8_t size = 1 + (SEGMENT.intensity >> 3);
if (size > SEGLEN/2) size = 1+ SEGLEN/2;
for (uint8_t i=0; i <= size; i++) {
setPixelColor(idexR+i, color1);
setPixelColor(idexB+i, color2);
}
if (SEGENV.aux0 != idexR) {
uint8_t gap = (SEGENV.aux0 < idexR)? idexR - SEGENV.aux0:SEGLEN - SEGENV.aux0 + idexR;
for (uint8_t i = 0; i <= gap ; i++) {
if ((idexR - i) < 0) idexR = SEGLEN-1 + i;
if ((idexB - i) < 0) idexB = SEGLEN-1 + i;
setPixelColor(idexR-i, color1);
setPixelColor(idexB-i, color2);
}
SEGENV.aux0 = idexR;
}
}
uint16_t delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster
uint32_t it = now / map(SEGMENT.speed, 0, 255, delay<<4, delay);
uint16_t offset = it % SEGLEN;
uint16_t width = ((SEGLEN*(SEGMENT.intensity+1))>>9); //max width is half the strip
if (!width) width = 1;
for (uint16_t i = 0; i < width; i++) {
uint16_t indexR = (offset + i) % SEGLEN;
uint16_t indexB = (offset + i + (SEGLEN>>1)) % SEGLEN;
setPixelColor(indexR, color1);
setPixelColor(indexB, color2);
}
return FRAMETIME;
}
//American Police Light with all LEDs Red and Blue
uint16_t WS2812FX::mode_police_all()
{
return police_base(RED, BLUE, true);
}
//Police Lights Red and Blue
uint16_t WS2812FX::mode_police()
{
fill(SEGCOLOR(1));
return police_base(RED, BLUE, false);
}
//Police All with custom colors
uint16_t WS2812FX::mode_two_areas()
{
return police_base(SEGCOLOR(0), SEGCOLOR(1), true);
return police_base(RED, BLUE);
}
@@ -1318,7 +1248,142 @@ uint16_t WS2812FX::mode_two_dots()
fill(SEGCOLOR(2));
uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1);
return police_base(SEGCOLOR(0), color2, false);
return police_base(SEGCOLOR(0), color2);
}
/*
* Fairy, inspired by https://www.youtube.com/watch?v=zeOw5MZWq24
*/
//4 bytes
typedef struct Flasher {
uint16_t stateStart;
uint8_t stateDur;
bool stateOn;
} flasher;
#define FLASHERS_PER_ZONE 6
#define MAX_SHIMMER 92
uint16_t WS2812FX::mode_fairy() {
//set every pixel to a 'random' color from palette (using seed so it doesn't change between frames)
uint16_t PRNG16 = 5100 + _segment_index;
for (uint16_t i = 0; i < SEGLEN; i++) {
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0));
}
//amount of flasher pixels depending on intensity (0: none, 255: every LED)
if (SEGMENT.intensity == 0) return FRAMETIME;
uint8_t flasherDistance = ((255 - SEGMENT.intensity) / 28) +1; //1-10
uint16_t numFlashers = (SEGLEN / flasherDistance) +1;
uint16_t dataSize = sizeof(flasher) * numFlashers;
if (!SEGENV.allocateData(dataSize)) return FRAMETIME; //allocation failed
Flasher* flashers = reinterpret_cast<Flasher*>(SEGENV.data);
uint16_t now16 = now & 0xFFFF;
//Up to 11 flashers in one brightness zone, afterwards a new zone for every 6 flashers
uint16_t zones = numFlashers/FLASHERS_PER_ZONE;
if (!zones) zones = 1;
uint8_t flashersInZone = numFlashers/zones;
uint8_t flasherBri[FLASHERS_PER_ZONE*2 -1];
for (uint16_t z = 0; z < zones; z++) {
uint16_t flasherBriSum = 0;
uint16_t firstFlasher = z*flashersInZone;
if (z == zones-1) flashersInZone = numFlashers-(flashersInZone*(zones-1));
for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) {
uint16_t stateTime = now16 - flashers[f].stateStart;
//random on/off time reached, switch state
if (stateTime > flashers[f].stateDur * 10) {
flashers[f].stateOn = !flashers[f].stateOn;
if (flashers[f].stateOn) {
flashers[f].stateDur = 12 + random8(12 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms
} else {
flashers[f].stateDur = 20 + random8(6 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms
}
//flashers[f].stateDur = 51 + random8(2 + ((255 - SEGMENT.speed) >> 1));
flashers[f].stateStart = now16;
if (stateTime < 255) {
flashers[f].stateStart -= 255 -stateTime; //start early to get correct bri
flashers[f].stateDur += 26 - stateTime/10;
stateTime = 255 - stateTime;
} else {
stateTime = 0;
}
}
if (stateTime > 255) stateTime = 255; //for flasher brightness calculation, fades in first 255 ms of state
//flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? 255-gamma8((510 - stateTime) >> 1) : gamma8((510 - stateTime) >> 1);
flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? stateTime : 255 - (stateTime >> 0);
flasherBriSum += flasherBri[f - firstFlasher];
}
//dim factor, to create "shimmer" as other pixels get less voltage if a lot of flashers are on
uint8_t avgFlasherBri = flasherBriSum / flashersInZone;
uint8_t globalPeakBri = 255 - ((avgFlasherBri * MAX_SHIMMER) >> 8); //183-255, suitable for 1/5th of LEDs flashers
for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) {
uint8_t bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255;
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
uint16_t flasherPos = f*flasherDistance;
setPixelColor(flasherPos, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), bri));
for (uint16_t i = flasherPos+1; i < flasherPos+flasherDistance && i < SEGLEN; i++) {
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0, globalPeakBri));
}
}
}
return FRAMETIME;
}
/*
* Fairytwinkle. Like Colortwinkle, but starting from all lit and not relying on getPixelColor
* Warning: Uses 4 bytes of segment data per pixel
*/
uint16_t WS2812FX::mode_fairytwinkle() {
uint16_t dataSize = sizeof(flasher) * SEGLEN;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
Flasher* flashers = reinterpret_cast<Flasher*>(SEGENV.data);
uint16_t now16 = now & 0xFFFF;
uint16_t PRNG16 = 5100 + _segment_index;
uint16_t riseFallTime = 400 + (255-SEGMENT.speed)*3;
uint16_t maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1);
for (uint16_t f = 0; f < SEGLEN; f++) {
uint16_t stateTime = now16 - flashers[f].stateStart;
//random on/off time reached, switch state
if (stateTime > flashers[f].stateDur * 100) {
flashers[f].stateOn = !flashers[f].stateOn;
bool init = !flashers[f].stateDur;
if (flashers[f].stateOn) {
flashers[f].stateDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +1;
} else {
flashers[f].stateDur = riseFallTime/100 + random8(3 + ((255 - SEGMENT.speed) >> 6)) +1;
}
flashers[f].stateStart = now16;
stateTime = 0;
if (init) {
flashers[f].stateStart -= riseFallTime; //start lit
flashers[f].stateDur = riseFallTime/100 + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +5; //fire up a little quicker
stateTime = riseFallTime;
}
}
if (flashers[f].stateOn && flashers[f].stateDur > maxDur) flashers[f].stateDur = maxDur; //react more quickly on intensity change
if (stateTime > riseFallTime) stateTime = riseFallTime; //for flasher brightness calculation, fades in first 255 ms of state
uint8_t fadeprog = 255 - ((stateTime * 255) / riseFallTime);
uint8_t flasherBri = (flashers[f].stateOn) ? 255-gamma8(fadeprog) : gamma8(fadeprog);
uint16_t lastR = PRNG16;
uint16_t diff = 0;
while (diff < 0x4000) { //make sure colors of two adjacent LEDs differ enough
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number
diff = (PRNG16 > lastR) ? PRNG16 - lastR : lastR - PRNG16;
}
setPixelColor(f, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), flasherBri));
}
return FRAMETIME;
}
@@ -1326,21 +1391,20 @@ uint16_t WS2812FX::mode_two_dots()
* Tricolor chase function
*/
uint16_t WS2812FX::tricolor_chase(uint32_t color1, uint32_t color2) {
uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*2;
uint32_t it = now / cycleTime;
uint8_t width = (1 + SEGMENT.intensity/32) * 3; //value of 1-8 for each colour
uint8_t index = it % width;
uint32_t cycleTime = 50 + ((255 - SEGMENT.speed)<<1);
uint32_t it = now / cycleTime; // iterator
uint8_t width = (1 + (SEGMENT.intensity>>4)); // value of 1-16 for each colour
uint8_t index = it % (width*3);
for(uint16_t i = 0; i < SEGLEN; i++, index++) {
if(index > width-1) index = 0;
for (uint16_t i = 0; i < SEGLEN; i++, index++) {
if (index > (width*3)-1) index = 0;
uint32_t color = color1;
if(index > width*2/3-1) color = color_from_palette(i, true, PALETTE_SOLID_WRAP, 1);
else if(index > width/3-1) color = color2;
if (index > (width<<1)-1) color = color_from_palette(i, true, PALETTE_SOLID_WRAP, 1);
else if (index > width-1) color = color2;
setPixelColor(SEGLEN - i -1, color);
}
return FRAMETIME;
}
@@ -1548,7 +1612,7 @@ uint16_t WS2812FX::mode_random_chase(void)
return FRAMETIME;
}
//7 bytes
typedef struct Oscillator {
int16_t pos;
int8_t size;
@@ -1780,7 +1844,7 @@ uint16_t WS2812FX::mode_fire_2012()
// Step 1. Cool down every cell a little
for (uint16_t i = 0; i < SEGLEN; i++) {
uint8_t temp = qsub8(heat[i], random8(0, (((20 + SEGMENT.speed /3) * 10) / SEGLEN) + 2));
heat[i] = (temp==0 && i<ignition) ? 2 : temp; // prevent ignition area from becoming black
heat[i] = (temp==0 && i<ignition) ? 16 : temp; // prevent ignition area from becoming black
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
@@ -1994,7 +2058,7 @@ uint16_t WS2812FX::mode_colortwinkle()
if (fadeUp) {
CRGB incrementalColor = fastled_col;
incrementalColor.nscale8_video( fadeUpAmount);
incrementalColor.nscale8_video(fadeUpAmount);
fastled_col += incrementalColor;
if (fastled_col.red == 255 || fastled_col.green == 255 || fastled_col.blue == 255) {
@@ -2002,24 +2066,21 @@ uint16_t WS2812FX::mode_colortwinkle()
}
setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue);
if (col_to_crgb(getPixelColor(i)) == prev) //fix "stuck" pixels
{
if (col_to_crgb(getPixelColor(i)) == prev) { //fix "stuck" pixels
fastled_col += fastled_col;
setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue);
}
} else {
fastled_col.nscale8( 255 - fadeDownAmount);
fastled_col.nscale8(255 - fadeDownAmount);
setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue);
}
}
for (uint16_t j = 0; j <= SEGLEN / 50; j++)
{
for (uint16_t j = 0; j <= SEGLEN / 50; j++) {
if (random8() <= SEGMENT.intensity) {
for (uint8_t times = 0; times < 5; times++) //attempt to spawn a new pixel 5 times
{
for (uint8_t times = 0; times < 5; times++) { //attempt to spawn a new pixel 5 times
int i = random16(SEGLEN);
if(getPixelColor(i) == 0) {
if (getPixelColor(i) == 0) {
fastled_col = ColorFromPalette(currentPalette, random8(), 64, NOBLEND);
uint16_t index = i >> 3;
uint8_t bitNum = i & 0x07;
@@ -2170,10 +2231,14 @@ typedef struct Ripple {
uint16_t pos;
} ripple;
#ifdef ESP8266
#define MAX_RIPPLES 56
#else
#define MAX_RIPPLES 100
#endif
uint16_t WS2812FX::ripple_base(bool rainbow)
{
uint16_t maxRipples = 1 + (SEGLEN >> 2);
if (maxRipples > 100) maxRipples = 100;
uint16_t maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266
uint16_t dataSize = sizeof(ripple) * maxRipples;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
@@ -2241,6 +2306,7 @@ uint16_t WS2812FX::ripple_base(bool rainbow)
}
return FRAMETIME;
}
#undef MAX_RIPPLES
uint16_t WS2812FX::mode_ripple(void) {
return ripple_base(false);
@@ -2541,7 +2607,6 @@ uint16_t WS2812FX::mode_spots_fade()
//each needs 12 bytes
//Spark type is used for popcorn and 1D fireworks
typedef struct Ball {
unsigned long lastBounceTime;
float impactVelocity;
@@ -2651,7 +2716,7 @@ uint16_t WS2812FX::mode_sinelon_dual(void) {
}
uint16_t WS2812FX::mode_sinelon_rainbow(void) {
return sinelon_base(true, true);
return sinelon_base(false, true);
}
@@ -2685,7 +2750,7 @@ typedef struct Spark {
*/
uint16_t WS2812FX::mode_popcorn(void) {
//allocate segment data
uint16_t maxNumPopcorn = 24;
uint16_t maxNumPopcorn = 21; // max 21 on 16 segment ESP8266
uint16_t dataSize = sizeof(spark) * maxNumPopcorn;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
@@ -2744,7 +2809,7 @@ uint16_t WS2812FX::candle(bool multi)
if (multi)
{
//allocate segment data
uint16_t dataSize = (SEGLEN -1) *3;
uint16_t dataSize = (SEGLEN -1) *3; //max. 1365 pixels (ESP8266)
if (!SEGENV.allocateData(dataSize)) return candle(false); //allocation failed
}
@@ -2831,9 +2896,12 @@ uint16_t WS2812FX::mode_candle_multi()
/ based on the video: https://www.reddit.com/r/arduino/comments/c3sd46/i_made_this_fireworks_effect_for_my_led_strips/
/ Speed sets frequency of new starbursts, intensity is the intensity of the burst
*/
#define STARBURST_MAX_FRAG 12
//each needs 64 byte
#ifdef ESP8266
#define STARBURST_MAX_FRAG 8 //52 bytes / star
#else
#define STARBURST_MAX_FRAG 10 //60 bytes / star
#endif
//each needs 20+STARBURST_MAX_FRAG*4 bytes
typedef struct particle {
CRGB color;
uint32_t birth =0;
@@ -2844,8 +2912,14 @@ typedef struct particle {
} star;
uint16_t WS2812FX::mode_starburst(void) {
uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640
uint8_t segs = getActiveSegmentsNum();
if (segs <= (MAX_NUM_SEGMENTS /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs
if (segs <= (MAX_NUM_SEGMENTS /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs
uint16_t maxStars = maxData / sizeof(star); //ESP8266: max. 4/9/19 stars/seg, ESP32: max. 10/21/42 stars/seg
uint8_t numStars = 1 + (SEGLEN >> 3);
if (numStars > 15) numStars = 15;
if (numStars > maxStars) numStars = maxStars;
uint16_t dataSize = sizeof(star) * numStars;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
@@ -2946,21 +3020,30 @@ uint16_t WS2812FX::mode_starburst(void) {
}
return FRAMETIME;
}
#undef STARBURST_MAX_FRAG
/*
* Exploding fireworks effect
* adapted from: http://www.anirama.com/1000leds/1d-fireworks/
*/
uint16_t WS2812FX::mode_exploding_fireworks(void)
{
//allocate segment data
uint16_t numSparks = 2 + (SEGLEN >> 1);
if (numSparks > 80) numSparks = 80;
uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640
uint8_t segs = getActiveSegmentsNum();
if (segs <= (MAX_NUM_SEGMENTS /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs
if (segs <= (MAX_NUM_SEGMENTS /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs
int maxSparks = maxData / sizeof(spark); //ESP8266: max. 21/42/85 sparks/seg, ESP32: max. 53/106/213 sparks/seg
uint16_t numSparks = min(2 + (SEGLEN >> 1), maxSparks);
uint16_t dataSize = sizeof(spark) * numSparks;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
if (dataSize != SEGENV.aux1) { //reset to flare if sparks were reallocated
SEGENV.aux0 = 0;
SEGENV.aux1 = dataSize;
}
fill(BLACK);
bool actuallyReverse = SEGMENT.getOption(SEG_OPTION_REVERSED);
@@ -3052,7 +3135,7 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
SEGENV.aux0--;
if (SEGENV.aux0 < 4) {
SEGENV.aux0 = 0; //back to flare
SEGENV.step = (SEGMENT.intensity > random8()); //decide firing side
SEGENV.step = actuallyReverse ^ (SEGMENT.intensity > random8()); //decide firing side
}
}
@@ -3060,6 +3143,7 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
return FRAMETIME;
}
#undef MAX_SPARKS
/*
@@ -3069,7 +3153,7 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
uint16_t WS2812FX::mode_drip(void)
{
//allocate segment data
uint16_t numDrops = 4;
uint8_t numDrops = 4;
uint16_t dataSize = sizeof(spark) * numDrops;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
@@ -3077,7 +3161,7 @@ uint16_t WS2812FX::mode_drip(void)
Spark* drops = reinterpret_cast<Spark*>(SEGENV.data);
numDrops = 1 + (SEGMENT.intensity >> 6);
numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3
float gravity = -0.0005 - (SEGMENT.speed/50000.0);
gravity *= SEGLEN;
@@ -3107,13 +3191,13 @@ uint16_t WS2812FX::mode_drip(void)
if (drops[j].pos > 0) { // fall until end of segment
drops[j].pos += drops[j].vel;
if (drops[j].pos < 0) drops[j].pos = 0;
drops[j].vel += gravity;
drops[j].vel += gravity; // gravity is negative
for (uint16_t i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets
uint16_t pos = constrain(uint16_t(drops[j].pos) +i, 0, SEGLEN-1); //this is BAD, returns a pos >= SEGLEN occasionally
setPixelColor(pos,color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling
}
if (drops[j].colIndex > 2) { // during bounce, some water is on the floor
setPixelColor(0,color_blend(SEGCOLOR(0),BLACK,drops[j].col));
}
@@ -3142,6 +3226,7 @@ uint16_t WS2812FX::mode_drip(void)
* Tetris or Stacking (falling bricks) Effect
* by Blaz Kristan (https://github.com/blazoncek, https://blaz.at/home)
*/
//12 bytes
typedef struct Tetris {
float pos;
float speed;
@@ -3163,8 +3248,8 @@ uint16_t WS2812FX::mode_tetrix(void) {
}
if (SEGENV.step == 0) { //init
drop->speed = 0.0238 * (SEGMENT.speed ? (SEGMENT.speed>>3)+1 : random8(6,40)); // set speed
drop->pos = SEGLEN-1; // start at end of segment
drop->speed = 0.0238 * (SEGMENT.speed ? (SEGMENT.speed>>2)+1 : random8(6,64)); // set speed
drop->pos = SEGLEN; // start at end of segment (no need to subtract 1)
drop->col = color_from_palette(random8(0,15)<<4,false,false,0); // limit color choices so there is enough HUE gap
SEGENV.step = 1; // drop state (0 init, 1 forming, 2 falling)
SEGENV.aux0 = (SEGMENT.intensity ? (SEGMENT.intensity>>5)+1 : random8(1,5)) * (1+(SEGLEN>>6)); // size of brick
@@ -3196,13 +3281,17 @@ uint16_t WS2812FX::mode_tetrix(void) {
/ adapted from https://github.com/atuline/FastLED-Demos/blob/master/plasma/plasma.ino
*/
uint16_t WS2812FX::mode_plasma(void) {
uint8_t thisPhase = beatsin8(6,-64,64); // Setting phase change for a couple of waves.
uint8_t thatPhase = beatsin8(7,-64,64);
// initialize phases on start
if (SEGENV.call == 0) {
SEGENV.aux0 = random8(0,2); // add a bit of randomness
}
uint8_t thisPhase = beatsin8(6+SEGENV.aux0,-64,64);
uint8_t thatPhase = beatsin8(7+SEGENV.aux0,-64,64);
for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set color & brightness based on a wave as follows:
uint8_t colorIndex = cubicwave8(((i*(1+ 3*(SEGMENT.speed >> 5)))+(thisPhase)) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change.
+ cos8(((i*(1+ 2*(SEGMENT.speed >> 5)))+(thatPhase)) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish.
uint8_t thisBright = qsub8(colorIndex, beatsin8(6,0, (255 - SEGMENT.intensity)|0x01 ));
uint8_t colorIndex = cubicwave8((i*(2+ 3*(SEGMENT.speed >> 5))+thisPhase) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change.
+ cos8((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish.
uint8_t thisBright = qsub8(colorIndex, beatsin8(7,0, (128 - (SEGMENT.intensity>>1))));
CRGB color = ColorFromPalette(currentPalette, colorIndex, thisBright, LINEARBLEND);
setPixelColor(i, color.red, color.green, color.blue);
}
@@ -3519,7 +3608,7 @@ uint16_t WS2812FX::mode_twinkleup(void) { // A very short twinkl
uint8_t ranstart = random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work.
uint8_t pixBri = sin8(ranstart + 16 * now/(256-SEGMENT.speed));
if (random8() > SEGMENT.intensity) pixBri = 0;
setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(i*20, false, PALETTE_SOLID_WRAP, 0), pixBri));
setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(random8()+now/100, false, PALETTE_SOLID_WRAP, 0), pixBri));
}
return FRAMETIME;
@@ -3531,7 +3620,7 @@ uint16_t WS2812FX::mode_noisepal(void) { // S
uint16_t scale = 15 + (SEGMENT.intensity >> 2); //default was 30
//#define scale 30
uint16_t dataSize = sizeof(CRGBPalette16) * 2; //allocate space for 2 Palettes
uint16_t dataSize = sizeof(CRGBPalette16) * 2; //allocate space for 2 Palettes (2 * 16 * 3 = 96 bytes)
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
CRGBPalette16* palettes = reinterpret_cast<CRGBPalette16*>(SEGENV.data);
@@ -3644,7 +3733,7 @@ uint16_t WS2812FX::mode_chunchun(void)
return FRAMETIME;
}
//13 bytes
typedef struct Spotlight {
float speed;
uint8_t colorIdx;
@@ -3661,6 +3750,11 @@ typedef struct Spotlight {
#define SPOT_TYPE_3X_DOT 4
#define SPOT_TYPE_4X_DOT 5
#define SPOT_TYPES_COUNT 6
#ifdef ESP8266
#define SPOT_MAX_COUNT 17 //Number of simultaneous waves
#else
#define SPOT_MAX_COUNT 49 //Number of simultaneous waves
#endif
/*
* Spotlights moving back and forth that cast dancing shadows.
@@ -3671,7 +3765,7 @@ typedef struct Spotlight {
*/
uint16_t WS2812FX::mode_dancing_shadows(void)
{
uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, 50);
uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 16 segment ESP8266
bool initialize = SEGENV.aux0 != numSpotlights;
SEGENV.aux0 = numSpotlights;
@@ -3810,7 +3904,7 @@ uint16_t WS2812FX::mode_washing_machine(void) {
Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e
*/
uint16_t WS2812FX::mode_blends(void) {
uint16_t dataSize = sizeof(uint32_t) * SEGLEN;
uint16_t dataSize = sizeof(uint32_t) * SEGLEN; // max segment length of 56 pixels on 16 segment ESP8266
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data);
uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128);
@@ -3825,6 +3919,11 @@ uint16_t WS2812FX::mode_blends(void) {
return FRAMETIME;
}
/*
TV Simulator
Modified and adapted to WLED by Def3nder, based on "Fake TV Light for Engineers" by Phillip Burgess https://learn.adafruit.com/fake-tv-light-for-engineers/arduino-sketch
*/
//43 bytes
typedef struct TvSim {
uint32_t totalTime = 0;
uint32_t fadeTime = 0;
@@ -3845,11 +3944,6 @@ typedef struct TvSim {
uint16_t pb = 0;
} tvSim;
/*
TV Simulator
Modified and adapted to WLED by Def3nder, based on "Fake TV Light for Engineers" by Phillip Burgess https://learn.adafruit.com/fake-tv-light-for-engineers/arduino-sketch
*/
uint16_t WS2812FX::mode_tv_simulator(void) {
uint16_t nr, ng, nb, r, g, b, i, hue;
uint8_t sat, bri, j;
@@ -3957,11 +4051,15 @@ uint16_t WS2812FX::mode_tv_simulator(void) {
*/
//CONFIG
#define BACKLIGHT 5
#define W_MAX_COUNT 20 //Number of simultaneous waves
#ifdef ESP8266
#define W_MAX_COUNT 9 //Number of simultaneous waves
#else
#define W_MAX_COUNT 20 //Number of simultaneous waves
#endif
#define W_MAX_SPEED 6 //Higher number, higher speed
#define W_WIDTH_FACTOR 6 //Higher number, smaller waves
//24 bytes
class AuroraWave {
private:
uint16_t ttl;
@@ -4056,10 +4154,10 @@ uint16_t WS2812FX::mode_aurora(void) {
if(SEGENV.aux0 != SEGMENT.intensity || SEGENV.call == 0) {
//Intensity slider changed or first call
SEGENV.aux1 = ((float)SEGMENT.intensity / 255) * W_MAX_COUNT;
SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT);
SEGENV.aux0 = SEGMENT.intensity;
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) {
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 16 segment ESP8266
return mode_static(); //allocation failed
}
@@ -4082,9 +4180,13 @@ uint16_t WS2812FX::mode_aurora(void) {
}
}
uint8_t backlight = 1; //dimmer backlight if less active colors
if (SEGCOLOR(0)) backlight++;
if (SEGCOLOR(1)) backlight++;
if (SEGCOLOR(2)) backlight++;
//Loop through LEDs to determine color
for(int i = 0; i < SEGLEN; i++) {
CRGB mixedRgb = CRGB(BACKLIGHT, BACKLIGHT, BACKLIGHT);
CRGB mixedRgb = CRGB(backlight, backlight, backlight);
//For each LED we must check each wave if it is "active" at this position.
//If there are multiple waves active on a LED we multiply their values.
@@ -4096,7 +4198,7 @@ uint16_t WS2812FX::mode_aurora(void) {
}
}
setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2], BACKLIGHT);
setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2]);
}
return FRAMETIME;

View File

@@ -24,8 +24,6 @@
Modified for WLED
*/
#include "wled.h"
#ifndef WS2812FX_h
#define WS2812FX_h
@@ -55,19 +53,23 @@
/* each segment uses 52 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
#ifdef ESP8266
#define MAX_NUM_SEGMENTS 12
#define MAX_NUM_SEGMENTS 16
/* How many color transitions can run at once */
#define MAX_NUM_TRANSITIONS 8
/* How much data bytes all segments combined may allocate */
#define MAX_SEGMENT_DATA 2048
#define MAX_SEGMENT_DATA 4096
#else
#ifndef MAX_NUM_SEGMENTS
#define MAX_NUM_SEGMENTS 16
#endif
#define MAX_NUM_TRANSITIONS 16
#define MAX_SEGMENT_DATA 8192
#ifndef MAX_NUM_SEGMENTS
#define MAX_NUM_SEGMENTS 32
#endif
#define MAX_NUM_TRANSITIONS 24
#define MAX_SEGMENT_DATA 20480
#endif
/* How much data bytes each segment should max allocate to leave enough space for other segments,
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 15
@@ -159,16 +161,16 @@
#define FX_MODE_COMET 41
#define FX_MODE_FIREWORKS 42
#define FX_MODE_RAIN 43
#define FX_MODE_TETRIX 44
#define FX_MODE_TETRIX 44 //was Merry Christmas prior to 0.12.0 (use "Chase 2" with Red/Green)
#define FX_MODE_FIRE_FLICKER 45
#define FX_MODE_GRADIENT 46
#define FX_MODE_LOADING 47
#define FX_MODE_POLICE 48
#define FX_MODE_POLICE_ALL 49
#define FX_MODE_POLICE 48 // candidate for removal (after below three)
#define FX_MODE_FAIRY 49 //was Police All prior to 0.13.0-b6 (use "Two Dots" with Red/Blue and full intensity)
#define FX_MODE_TWO_DOTS 50
#define FX_MODE_TWO_AREAS 51
#define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity)
#define FX_MODE_RUNNING_DUAL 52
#define FX_MODE_HALLOWEEN 53
#define FX_MODE_HALLOWEEN 53 // candidate for removal
#define FX_MODE_TRICOLOR_CHASE 54
#define FX_MODE_TRICOLOR_WIPE 55
#define FX_MODE_TRICOLOR_FADE 56
@@ -229,7 +231,7 @@
#define FX_MODE_CHUNCHUN 111
#define FX_MODE_DANCING_SHADOWS 112
#define FX_MODE_WASHING_MACHINE 113
#define FX_MODE_CANDY_CANE 114
#define FX_MODE_CANDY_CANE 114 // candidate for removal
#define FX_MODE_BLENDS 115
#define FX_MODE_TV_SIMULATOR 116
#define FX_MODE_DYNAMIC_SMOOTH 117
@@ -245,70 +247,82 @@ class WS2812FX {
// segment parameters
public:
typedef struct Segment { // 25 (28 in memory?) bytes
typedef struct Segment { // 30 (32 in memory) bytes
uint16_t start;
uint16_t stop; //segment invalid if stop == 0
uint16_t offset;
uint8_t speed;
uint8_t intensity;
uint8_t palette;
uint8_t mode;
uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected
uint8_t grouping, spacing;
uint8_t opacity;
uint8_t speed;
uint8_t intensity;
uint8_t palette;
uint8_t mode;
uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected
uint8_t grouping, spacing;
uint8_t opacity;
uint32_t colors[NUM_COLORS];
uint8_t cct; //0==1900K, 255==10091K
char *name;
bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed
if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false;
if (c == colors[slot]) return false;
ColorTransition::startTransition(opacity, colors[slot], instance->_transitionDur, segn, slot);
uint8_t b = (slot == 1) ? cct : opacity;
ColorTransition::startTransition(b, colors[slot], instance->_transitionDur, segn, slot);
colors[slot] = c; return true;
}
void setCCT(uint16_t k, uint8_t segn) {
if (segn >= MAX_NUM_SEGMENTS) return;
if (k > 255) { //kelvin value, convert to 0-255
if (k < 1900) k = 1900;
if (k > 10091) k = 10091;
k = (k - 1900) >> 5;
}
if (cct == k) return;
ColorTransition::startTransition(cct, colors[1], instance->_transitionDur, segn, 1);
cct = k;
}
void setOpacity(uint8_t o, uint8_t segn) {
if (segn >= MAX_NUM_SEGMENTS) return;
if (opacity == o) return;
ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0);
opacity = o;
}
/*uint8_t actualOpacity() { //respects On/Off state
if (!getOption(SEG_OPTION_ON)) return 0;
return opacity;
}*/
void setOption(uint8_t n, bool val, uint8_t segn = 255)
{
//bool prevOn = false;
//if (n == SEG_OPTION_ON) prevOn = getOption(SEG_OPTION_ON);
bool prevOn = false;
if (n == SEG_OPTION_ON) {
prevOn = getOption(SEG_OPTION_ON);
if (!val && prevOn) { //fade off
ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0);
}
}
if (val) {
options |= 0x01 << n;
} else
{
options &= ~(0x01 << n);
}
//transitions on segment on/off don't work correctly at this point
/*if (n == SEG_OPTION_ON && segn < MAX_NUM_SEGMENTS && getOption(SEG_OPTION_ON) != prevOn) {
if (getOption(SEG_OPTION_ON)) {
ColorTransition::startTransition(0, colors[0], instance->_transitionDur, segn, 0);
} else {
ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0);
}
}*/
if (n == SEG_OPTION_ON && val && !prevOn) { //fade on
ColorTransition::startTransition(0, colors[0], instance->_transitionDur, segn, 0);
}
}
bool getOption(uint8_t n)
{
return ((options >> n) & 0x01);
}
bool isSelected()
inline bool isSelected()
{
return getOption(0);
}
bool isActive()
inline bool isActive()
{
return stop > start;
}
uint16_t length()
inline uint16_t length()
{
return stop - start;
}
uint16_t groupLength()
inline uint16_t groupLength()
{
return grouping + spacing;
}
@@ -320,21 +334,48 @@ class WS2812FX {
vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED
return vLength;
}
uint8_t differs(Segment& b) {
uint8_t d = 0;
if (start != b.start) d |= SEG_DIFFERS_BOUNDS;
if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS;
if (offset != b.offset) d |= SEG_DIFFERS_GSO;
if (grouping != b.grouping) d |= SEG_DIFFERS_GSO;
if (spacing != b.spacing) d |= SEG_DIFFERS_GSO;
if (opacity != b.opacity) d |= SEG_DIFFERS_BRI;
if (mode != b.mode) d |= SEG_DIFFERS_FX;
if (speed != b.speed) d |= SEG_DIFFERS_FX;
if (intensity != b.intensity) d |= SEG_DIFFERS_FX;
if (palette != b.palette) d |= SEG_DIFFERS_FX;
if ((options & 0b00101111) != (b.options & 0b00101111)) d |= SEG_DIFFERS_OPT;
for (uint8_t i = 0; i < NUM_COLORS; i++)
{
if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL;
}
return d;
}
} segment;
// segment runtime parameters
typedef struct Segment_runtime { // 28 bytes
unsigned long next_time;
uint32_t step;
uint32_t call;
uint16_t aux0;
uint16_t aux1;
unsigned long next_time; // millis() of next update
uint32_t step; // custom "step" var
uint32_t call; // call counter
uint16_t aux0; // custom var
uint16_t aux1; // custom var
byte* data = nullptr;
bool allocateData(uint16_t len){
if (data && _dataLen == len) return true; //already allocated
deallocateData();
if (WS2812FX::instance->_usedSegmentData + len > MAX_SEGMENT_DATA) return false; //not enough memory
data = new (std::nothrow) byte[len];
// if possible use SPI RAM on ESP32
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
if (psramFound())
data = (byte*) ps_malloc(len);
else
#endif
data = (byte*) malloc(len);
if (!data) return false; //allocation failed
WS2812FX::instance->_usedSegmentData += len;
_dataLen = len;
@@ -342,7 +383,7 @@ class WS2812FX {
return true;
}
void deallocateData(){
delete[] data;
free(data);
data = nullptr;
WS2812FX::instance->_usedSegmentData -= _dataLen;
_dataLen = 0;
@@ -368,7 +409,7 @@ class WS2812FX {
* the internal segment state should be reset.
* Call resetIfRequired before calling the next effect function.
*/
void reset() { _requiresReset = true; }
inline void reset() { _requiresReset = true; }
private:
uint16_t _dataLen = 0;
bool _requiresReset = false;
@@ -383,6 +424,7 @@ class WS2812FX {
static void startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot) {
if (segn >= MAX_NUM_SEGMENTS || slot >= NUM_COLORS || dur == 0) return;
if (instance->_brightness == 0) return; //do not need transitions if master bri is off
if (!instance->_segments[segn].getOption(SEG_OPTION_ON)) return; //not if segment is off either
uint8_t tIndex = 0xFF; //none found
uint16_t tProgression = 0;
uint8_t s = segn + (slot << 6); //merge slot and segment into one byte
@@ -411,7 +453,8 @@ class WS2812FX {
ColorTransition& t = instance->transitions[tIndex];
if (t.segment == s) //this is an active transition on the same segment+color
{
t.briOld = t.currentBri();
bool wasTurningOff = (oldBri == 0);
t.briOld = t.currentBri(wasTurningOff, slot);
t.colorOld = t.currentColor(oldCol);
} else {
t.briOld = oldBri;
@@ -443,10 +486,15 @@ class WS2812FX {
uint32_t currentColor(uint32_t colorNew) {
return instance->color_blend(colorOld, colorNew, progress(true), true);
}
uint8_t currentBri() {
uint8_t currentBri(bool turningOff = false, uint8_t slot = 0) {
uint8_t segn = segment & 0x3F;
if (segn >= MAX_NUM_SEGMENTS) return 0;
uint8_t briNew = instance->_segments[segn].opacity;
if (slot == 0) {
if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0;
} else { //transition slot 1 brightness for CCT transition
briNew = instance->_segments[segn].cct;
}
uint32_t prog = progress() + 1;
return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16;
}
@@ -502,9 +550,9 @@ class WS2812FX {
_mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient;
_mode[FX_MODE_LOADING] = &WS2812FX::mode_loading;
_mode[FX_MODE_POLICE] = &WS2812FX::mode_police;
_mode[FX_MODE_POLICE_ALL] = &WS2812FX::mode_police_all;
_mode[FX_MODE_FAIRY] = &WS2812FX::mode_fairy;
_mode[FX_MODE_TWO_DOTS] = &WS2812FX::mode_two_dots;
_mode[FX_MODE_TWO_AREAS] = &WS2812FX::mode_two_areas;
_mode[FX_MODE_FAIRYTWINKLE] = &WS2812FX::mode_fairytwinkle;
_mode[FX_MODE_RUNNING_DUAL] = &WS2812FX::mode_running_dual;
_mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween;
_mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase;
@@ -584,7 +632,7 @@ class WS2812FX {
}
void
finalizeInit(uint16_t countPixels),
finalizeInit(),
service(void),
blur(uint8_t),
fill(uint32_t),
@@ -599,13 +647,15 @@ class WS2812FX {
setTransitionMode(bool t),
calcGammaTable(float),
trigger(void),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0, uint16_t offset = UINT16_MAX),
resetSegments(),
makeAutoSegments(),
fixInvalidSegments(),
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),
setColorOrder(uint8_t co),
setPixelSegment(uint8_t n);
setPixelSegment(uint8_t n),
deserializeMap(uint8_t n=0);
bool
isRgbw = false,
@@ -613,46 +663,41 @@ class WS2812FX {
gammaCorrectBri = false,
gammaCorrectCol = true,
applyToAllSelected = true,
segmentsAreIdentical(Segment* a, Segment* b),
setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p),
checkSegmentAlignment(void),
hasCCTBus(void),
// return true if the strip is being sent pixel updates
isUpdating(void);
uint8_t
mainSegment = 0,
rgbwMode = RGBW_MODE_DUAL,
paletteFade = 0,
paletteBlend = 0,
milliampsPerLed = 55,
// getStripType(uint8_t strip=0),
// setStripType(uint8_t type, uint8_t strip=0),
cctBlending = 0,
getBrightness(void),
getMode(void),
getSpeed(void),
getModeCount(void),
getPaletteCount(void),
getMaxSegments(void),
getActiveSegmentsNum(void),
//getFirstSelectedSegment(void),
getMainSegmentId(void),
getColorOrder(void),
gamma8(uint8_t),
gamma8_cal(uint8_t, float),
sin_gap(uint16_t),
get_random_wheel_index(uint8_t);
int8_t
// setStripPin(uint8_t strip, int8_t pin),
// getStripPin(uint8_t strip=0),
// setStripPinClk(uint8_t strip, int8_t pin),
// getStripPinClk(uint8_t strip=0),
tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec);
uint16_t
ablMilliampsMax,
currentMilliamps,
// setStripLen(uint8_t strip, uint16_t len),
// getStripLen(uint8_t strip=0),
triwave16(uint16_t),
getLengthTotal(void),
getLengthPhysical(void),
getFps();
uint32_t
@@ -728,9 +773,9 @@ class WS2812FX {
mode_gradient(void),
mode_loading(void),
mode_police(void),
mode_police_all(void),
mode_fairy(void),
mode_two_dots(void),
mode_two_areas(void),
mode_fairytwinkle(void),
mode_running_dual(void),
mode_bicolor_chase(void),
mode_tricolor_chase(void),
@@ -812,9 +857,6 @@ class WS2812FX {
uint16_t _cumulativeFps = 2;
void load_gradient_palette(uint8_t);
void handle_palette(void);
bool
_triggered;
@@ -829,7 +871,6 @@ class WS2812FX {
color_wipe(bool, bool),
dynamic(bool),
scan(bool),
theater_chase(uint32_t, uint32_t, bool),
running_base(bool,bool),
larson_scanner(bool),
sinelon_base(bool,bool),
@@ -837,8 +878,8 @@ class WS2812FX {
chase(uint32_t, uint32_t, uint32_t, bool),
gradient_base(bool),
ripple_base(bool),
police_base(uint32_t, uint32_t, bool),
running(uint32_t, uint32_t),
police_base(uint32_t, uint32_t),
running(uint32_t, uint32_t, bool theatre=false),
tricolor_chase(uint32_t, uint32_t),
twinklefox_base(bool),
spots_base(uint16_t),
@@ -850,7 +891,9 @@ class WS2812FX {
void
blendPixelColor(uint16_t n, uint32_t color, uint8_t blend),
startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot),
deserializeMap(void);
estimateCurrentAndLimitBri(void),
load_gradient_palette(uint8_t),
handle_palette(void);
uint16_t* customMappingTable = nullptr;
uint16_t customMappingSize = 0;
@@ -883,9 +926,9 @@ const char JSON_mode_names[] PROGMEM = R"=====([
"Solid","Blink","Breathe","Wipe","Wipe Random","Random Colors","Sweep","Dynamic","Colorloop","Rainbow",
"Scan","Scan Dual","Fade","Theater","Theater Rainbow","Running","Saw","Twinkle","Dissolve","Dissolve Rnd",
"Sparkle","Sparkle Dark","Sparkle+","Strobe","Strobe Rainbow","Strobe Mega","Blink Rainbow","Android","Chase","Chase Random",
"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Aurora","Stream",
"Scanner","Lighthouse","Fireworks","Rain","Tetrix","Fire Flicker","Gradient","Loading","Police","Police All",
"Two Dots","Two Areas","Running Dual","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet",
"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Chase 2","Aurora","Stream",
"Scanner","Lighthouse","Fireworks","Rain","Tetrix","Fire Flicker","Gradient","Loading","Police","Fairy",
"Two Dots","Fairytwinkle","Running Dual","Halloween","Chase 3","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet",
"Scanner Dual","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","Bpm","Fill Noise",
"Noise 1","Noise 2","Noise 3","Noise 4","Colortwinkles","Lake","Meteor","Meteor Smooth","Railway","Ripple",
"Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst",
@@ -901,7 +944,9 @@ const char JSON_palette_names[] PROGMEM = R"=====([
"Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64",
"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn",
"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura",
"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2"
"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf",
"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide",
"Candy2"
])=====";
#endif

View File

@@ -23,7 +23,7 @@
Modified heavily for WLED
*/
#include "wled.h"
#include "FX.h"
#include "palettes.h"
@@ -40,7 +40,7 @@
another example. Switches direction every 5 LEDs.
{"map":[
0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14,
19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]
19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]}
*/
//factory defaults LED setup
@@ -65,71 +65,51 @@
#endif
//do not call this method from system context (network callback)
void WS2812FX::finalizeInit(uint16_t countPixels)
void WS2812FX::finalizeInit(void)
{
RESET_RUNTIME;
_length = countPixels;
isRgbw = isOffRefreshRequred = false;
//if busses failed to load, add default (FS issue...)
//if busses failed to load, add default (fresh install, FS issue, ...)
if (busses.getNumBusses() == 0) {
const uint8_t defDataPins[] = {DATA_PINS};
const uint16_t defCounts[] = {PIXEL_COUNTS};
const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0]));
const uint8_t defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0]));
uint16_t prevLen = 0;
for (uint8_t i = 0; i < defNumBusses; i++) {
for (uint8_t i = 0; i < defNumBusses && i < WLED_MAX_BUSSES; i++) {
uint8_t defPin[] = {defDataPins[i]};
uint16_t start = prevLen;
uint16_t count = _length;
if (defNumBusses > 1 && defNumCounts) {
count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
}
uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
prevLen += count;
BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, COL_ORDER_GRB);
busses.add(defCfg);
}
}
deserializeMap();
uint16_t segStarts[MAX_NUM_SEGMENTS] = {0};
uint16_t segStops [MAX_NUM_SEGMENTS] = {0};
setBrightness(_brightness);
//TODO make sure segments are only refreshed when bus config actually changed (new settings page)
//make one segment per bus
uint8_t s = 0;
for (uint8_t i = 0; i < busses.getNumBusses(); i++) {
Bus* b = busses.getBus(i);
segStarts[s] = b->getStart();
segStops[s] = segStarts[s] + b->getLength();
//check for overlap with previous segments
for (uint8_t j = 0; j < s; j++) {
if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) {
//segments overlap, merge
segStarts[j] = min(segStarts[s],segStarts[j]);
segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0;
s--;
}
}
s++;
_length = 0;
for (uint8_t i=0; i<busses.getNumBusses(); i++) {
Bus *bus = busses.getBus(i);
if (bus == nullptr) continue;
if (bus->getStart() + bus->getLength() > MAX_LEDS) break;
//RGBW mode is enabled if at least one of the strips is RGBW
isRgbw |= bus->isRgbw();
//refresh is required to remain off if at least one of the strips requires the refresh.
isOffRefreshRequred |= bus->isOffRefreshRequired();
uint16_t busEnd = bus->getStart() + bus->getLength();
if (busEnd > _length) _length = busEnd;
#ifdef ESP8266
if ((!IS_DIGITAL(b->getType()) || IS_2PIN(b->getType()))) continue;
if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue;
uint8_t pins[5];
b->getPins(pins);
BusDigital* bd = static_cast<BusDigital*>(b);
if (!bus->getPins(pins)) continue;
BusDigital* bd = static_cast<BusDigital*>(bus);
if (pins[0] == 3) bd->reinit();
#endif
}
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) {
_segments[i].start = segStarts[i];
_segments[i].stop = segStops [i];
}
//segments are created in makeAutoSegments();
setBrightness(_brightness);
}
void WS2812FX::service() {
@@ -157,13 +137,16 @@ void WS2812FX::service() {
if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen
_virtualSegmentLength = SEGMENT.virtualLength();
_bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2];
uint8_t _cct_t = SEGMENT.cct;
if (!IS_SEGMENT_ON) _bri_t = 0;
for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) {
if ((transitions[t].segment & 0x3F) != i) continue;
uint8_t slot = transitions[t].segment >> 6;
if (slot == 0) _bri_t = transitions[t].currentBri();
if (slot == 1) _cct_t = transitions[t].currentBri(false, 1);
_colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]);
}
if (!cctFromRgb || correctWB) busses.setSegmentCCT(_cct_t, correctWB);
for (uint8_t c = 0; c < 3; c++) _colors_t[c] = gamma32(_colors_t[c]);
handle_palette();
delay = (this->*_mode[SEGMENT.mode])(); //effect function
@@ -174,6 +157,7 @@ void WS2812FX::service() {
}
}
_virtualSegmentLength = 0;
busses.setSegmentCCT(-1);
if(doShow) {
yield();
show();
@@ -182,11 +166,7 @@ void WS2812FX::service() {
}
void WS2812FX::setPixelColor(uint16_t n, uint32_t c) {
uint8_t w = (c >> 24);
uint8_t r = (c >> 16);
uint8_t g = (c >> 8);
uint8_t b = c ;
setPixelColor(n, r, g, b, w);
setPixelColor(n, R(c), G(c), B(c), W(c));
}
//used to map from segment index to physical pixel, taking into account grouping, offsets, reverse and mirroring
@@ -197,34 +177,21 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) {
int16_t realIndex = iGroup;
if (IS_REVERSE) {
if (IS_MIRROR) {
realIndex = (SEGMENT.length() -1) / 2 - iGroup; //only need to index half the pixels
realIndex = (SEGMENT.length() - 1) / 2 - iGroup; //only need to index half the pixels
} else {
realIndex = SEGMENT.length() - iGroup - 1;
realIndex = (SEGMENT.length() - 1) - iGroup;
}
}
realIndex += SEGMENT.start;
return realIndex;
}
void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
{
//auto calculate white channel value if enabled
if (isRgbw) {
if (rgbwMode == RGBW_MODE_AUTO_BRIGHTER || (w == 0 && (rgbwMode == RGBW_MODE_DUAL || rgbwMode == RGBW_MODE_LEGACY)))
{
//white value is set to lowest RGB channel
//thank you to @Def3nder!
w = r < g ? (r < b ? r : b) : (g < b ? g : b);
} else if (rgbwMode == RGBW_MODE_AUTO_ACCURATE && w == 0)
{
w = r < g ? (r < b ? r : b) : (g < b ? g : b);
r -= w; g -= w; b -= w;
}
}
if (SEGLEN) {//from segment
uint16_t realIndex = realPixelIndex(i);
uint16_t len = SEGMENT.length();
//color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments)
if (_bri_t < 255) {
@@ -233,14 +200,11 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
b = scale8(b, _bri_t);
w = scale8(w, _bri_t);
}
uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
bool reversed = IS_REVERSE;
uint16_t realIndex = realPixelIndex(i);
uint16_t len = SEGMENT.length();
uint32_t col = RGBW32(r, g, b, w);
/* Set all the pixels in the group */
for (uint16_t j = 0; j < SEGMENT.grouping; j++) {
int indexSet = realIndex + (reversed ? -j : 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;
@@ -252,8 +216,8 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
busses.setPixelColor(indexMir, col);
}
/* offset/phase */
indexSet += SEGMENT.offset;
if (indexSet >= SEGMENT.stop) indexSet -= len;
indexSet += SEGMENT.offset;
if (indexSet >= SEGMENT.stop) indexSet -= len;
if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet];
busses.setPixelColor(indexSet, col);
@@ -261,9 +225,7 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
}
} else { //live data, etc.
if (i < customMappingSize) i = customMappingTable[i];
uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
busses.setPixelColor(i, col);
busses.setPixelColor(i, RGBW32(r, g, b, w));
}
}
@@ -279,12 +241,7 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA)
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
void WS2812FX::show(void) {
// avoid race condition, caputre _callback value
show_callback callback = _callback;
if (callback) callback();
void WS2812FX::estimateCurrentAndLimitBri() {
//power limit calculation
//each LED can draw up 195075 "power units" (approx. 53mA)
//one PU is the power it takes to have 1 channel 1 step brighter per brightness step
@@ -297,65 +254,72 @@ void WS2812FX::show(void) {
actualMilliampsPerLed = 12; // from testing an actual strip
}
if (ablMilliampsMax > 149 && actualMilliampsPerLed > 0) //0 mA per LED and too low numbers turn off calculation
{
uint32_t puPerMilliamp = 195075 / actualMilliampsPerLed;
uint32_t powerBudget = (ablMilliampsMax - MA_FOR_ESP) * puPerMilliamp; //100mA for ESP power
if (powerBudget > puPerMilliamp * _length) //each LED uses about 1mA in standby, exclude that from power budget
{
powerBudget -= puPerMilliamp * _length;
} else
{
powerBudget = 0;
}
uint32_t powerSum = 0;
for (uint16_t i = 0; i < _length; i++) //sum up the usage of each LED
{
uint32_t c = busses.getPixelColor(i);
byte r = c >> 16, g = c >> 8, b = c, w = c >> 24;
if(useWackyWS2815PowerModel)
{
// ignore white component on WS2815 power calculation
powerSum += (MAX(MAX(r,g),b)) * 3;
}
else
{
powerSum += (r + g + b + w);
}
}
if (isRgbw) //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
{
powerSum *= 3;
powerSum = powerSum >> 2; //same as /= 4
}
uint32_t powerSum0 = powerSum;
powerSum *= _brightness;
if (powerSum > powerBudget) //scale brightness down to stay in current limit
{
float scale = (float)powerBudget / (float)powerSum;
uint16_t scaleI = scale * 255;
uint8_t scaleB = (scaleI > 255) ? 255 : scaleI;
uint8_t newBri = scale8(_brightness, scaleB);
busses.setBrightness(newBri);
currentMilliamps = (powerSum0 * newBri) / puPerMilliamp;
} else
{
currentMilliamps = powerSum / puPerMilliamp;
busses.setBrightness(_brightness);
}
currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate
currentMilliamps += _length; //add standby power back to estimate
} else {
if (ablMilliampsMax < 150 || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation
currentMilliamps = 0;
busses.setBrightness(_brightness);
return;
}
uint16_t pLen = getLengthPhysical();
uint32_t puPerMilliamp = 195075 / actualMilliampsPerLed;
uint32_t powerBudget = (ablMilliampsMax - MA_FOR_ESP) * puPerMilliamp; //100mA for ESP power
if (powerBudget > puPerMilliamp * pLen) { //each LED uses about 1mA in standby, exclude that from power budget
powerBudget -= puPerMilliamp * pLen;
} else {
powerBudget = 0;
}
uint32_t powerSum = 0;
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses
uint16_t len = bus->getLength();
uint32_t busPowerSum = 0;
for (uint16_t i = 0; i < len; i++) { //sum up the usage of each LED
uint32_t c = bus->getPixelColor(i);
byte r = R(c), g = G(c), b = B(c), w = W(c);
if(useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
busPowerSum += (MAX(MAX(r,g),b)) * 3;
} else {
busPowerSum += (r + g + b + w);
}
}
if (bus->isRgbw()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
busPowerSum *= 3;
busPowerSum = busPowerSum >> 2; //same as /= 4
}
powerSum += busPowerSum;
}
uint32_t powerSum0 = powerSum;
powerSum *= _brightness;
if (powerSum > powerBudget) //scale brightness down to stay in current limit
{
float scale = (float)powerBudget / (float)powerSum;
uint16_t scaleI = scale * 255;
uint8_t scaleB = (scaleI > 255) ? 255 : scaleI;
uint8_t newBri = scale8(_brightness, scaleB);
busses.setBrightness(newBri); //to keep brightness uniform, sets virtual busses too
currentMilliamps = (powerSum0 * newBri) / puPerMilliamp;
} else {
currentMilliamps = powerSum / puPerMilliamp;
busses.setBrightness(_brightness);
}
currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate
currentMilliamps += pLen; //add standby power back to estimate
}
void WS2812FX::show(void) {
// avoid race condition, caputre _callback value
show_callback callback = _callback;
if (callback) callback();
estimateCurrentAndLimitBri();
// some buses send asynchronously and this method will return before
// all of the data has been sent.
@@ -450,7 +414,7 @@ bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t p) {
}
void WS2812FX::setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
setColor(slot, ((uint32_t)w << 24) |((uint32_t)r << 16) | ((uint32_t)g << 8) | b);
setColor(slot, RGBW32(r, g, b, w));
}
void WS2812FX::setColor(uint8_t slot, uint32_t c) {
@@ -478,14 +442,14 @@ void WS2812FX::setBrightness(uint8_t b) {
if (gammaCorrectBri) b = gamma8(b);
if (_brightness == b) return;
_brightness = b;
_segment_index = 0;
if (_brightness == 0) { //unfreeze all segments on power off
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
_segments[i].setOption(SEG_OPTION_FREEZE, false);
}
}
if (SEGENV.next_time > millis() + 22 && millis() - _lastShow > MIN_SHOW_DELAY) show();//apply brightness change immediately if no refresh soon
unsigned long t = millis();
if (_segment_runtimes[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) show(); //apply brightness change immediately if no refresh soon
}
uint8_t WS2812FX::getMode(void) {
@@ -527,6 +491,15 @@ uint8_t WS2812FX::getMainSegmentId(void) {
return 0;
}
uint8_t WS2812FX::getActiveSegmentsNum(void) {
uint8_t c = 0;
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].isActive()) c++;
}
return c;
}
uint32_t WS2812FX::getColor(void) {
return _segments[getMainSegmentId()].colors[0];
}
@@ -542,6 +515,7 @@ uint32_t WS2812FX::getPixelColor(uint16_t i)
}
if (i < customMappingSize) i = customMappingTable[i];
if (i >= _length) return 0;
return busses.getPixelColor(i);
}
@@ -563,26 +537,51 @@ uint32_t WS2812FX::getLastShow(void) {
return _lastShow;
}
//TODO these need to be on a per-strip basis
uint8_t WS2812FX::getColorOrder(void) {
return COL_ORDER_GRB;
uint16_t WS2812FX::getLengthTotal(void) {
return _length;
}
void WS2812FX::setColorOrder(uint8_t co) {
//bus->SetColorOrder(co);
uint16_t WS2812FX::getLengthPhysical(void) {
uint16_t len = 0;
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses
len += bus->getLength();
}
return len;
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) {
bool WS2812FX::hasCCTBus(void) {
if (cctFromRgb && !correctWB) return false;
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (bus == nullptr || bus->getLength()==0) break;
switch (bus->getType()) {
case TYPE_ANALOG_5CH:
case TYPE_ANALOG_2CH:
return true;
}
}
return false;
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset) {
if (n >= MAX_NUM_SEGMENTS) return;
Segment& seg = _segments[n];
//return if neither bounds nor grouping have changed
if (seg.start == i1 && seg.stop == i2 && (!grouping || (seg.grouping == grouping && seg.spacing == spacing))) return;
if (seg.start == i1 && seg.stop == i2
&& (!grouping || (seg.grouping == grouping && seg.spacing == spacing))
&& (offset == UINT16_MAX || offset == seg.offset)) return;
if (seg.stop) setRange(seg.start, seg.stop -1, 0); //turn old segment range off
if (i2 <= i1) //disable segment
{
seg.stop = 0;
seg.stop = 0;
if (seg.name) {
delete[] seg.name;
seg.name = nullptr;
}
if (n == mainSegment) //if main segment is deleted, set first active as main segment
{
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
@@ -603,10 +602,12 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
seg.grouping = grouping;
seg.spacing = spacing;
}
if (offset < UINT16_MAX) seg.offset = offset;
_segment_runtimes[n].reset();
}
void WS2812FX::resetSegments() {
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) if (_segments[i].name) delete _segments[i].name;
mainSegment = 0;
memset(_segments, 0, sizeof(_segments));
//memset(_segment_runtimes, 0, sizeof(_segment_runtimes));
@@ -621,6 +622,7 @@ void WS2812FX::resetSegments() {
_segments[0].setOption(SEG_OPTION_SELECTED, 1);
_segments[0].setOption(SEG_OPTION_ON, 1);
_segments[0].opacity = 255;
_segments[0].cct = 127;
for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++)
{
@@ -628,6 +630,7 @@ void WS2812FX::resetSegments() {
_segments[i].grouping = 1;
_segments[i].setOption(SEG_OPTION_ON, 1);
_segments[i].opacity = 255;
_segments[i].cct = 127;
_segments[i].speed = DEFAULT_SPEED;
_segments[i].intensity = DEFAULT_INTENSITY;
_segment_runtimes[i].reset();
@@ -635,15 +638,98 @@ void WS2812FX::resetSegments() {
_segment_runtimes[0].reset();
}
void WS2812FX::makeAutoSegments() {
if (autoSegments) { //make one segment per bus
uint16_t segStarts[MAX_NUM_SEGMENTS] = {0};
uint16_t segStops [MAX_NUM_SEGMENTS] = {0};
uint8_t s = 0;
for (uint8_t i = 0; i < busses.getNumBusses(); i++) {
Bus* b = busses.getBus(i);
segStarts[s] = b->getStart();
segStops[s] = segStarts[s] + b->getLength();
//check for overlap with previous segments
for (uint8_t j = 0; j < s; j++) {
if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) {
//segments overlap, merge
segStarts[j] = min(segStarts[s],segStarts[j]);
segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0;
s--;
}
}
s++;
}
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) {
setSegment(i, segStarts[i], segStops[i]);
}
} else {
//expand the main seg to the entire length, but only if there are no other segments
uint8_t mainSeg = getMainSegmentId();
if (getActiveSegmentsNum() < 2) {
setSegment(mainSeg, 0, _length);
}
}
fixInvalidSegments();
}
void WS2812FX::fixInvalidSegments() {
//make sure no segment is longer than total (sanity check)
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].start >= _length) setSegment(i, 0, 0);
if (_segments[i].stop > _length) setSegment(i, _segments[i].start, _length);
}
}
//true if all segments align with a bus, or if a segment covers the total length
bool WS2812FX::checkSegmentAlignment() {
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].start >= _segments[i].stop) continue; //inactive segment
bool aligned = false;
for (uint8_t b = 0; b<busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (_segments[i].start == bus->getStart() && _segments[i].stop == bus->getStart() + bus->getLength()) aligned = true;
}
if (_segments[i].start == 0 && _segments[i].stop == _length) aligned = true;
if (!aligned) return false;
}
return true;
}
//After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply)
//Note: If called in an interrupt (e.g. JSON API), it must be reset with "setPixelColor(255)",
//otherwise it can lead to a crash on ESP32 because _segment_index is modified while in use by the main thread
#ifdef ARDUINO_ARCH_ESP32
uint8_t _segment_index_prev = 0;
uint16_t _virtualSegmentLength_prev = 0;
bool _ps_set = false;
#endif
void WS2812FX::setPixelSegment(uint8_t n)
{
if (n < MAX_NUM_SEGMENTS) {
#ifdef ARDUINO_ARCH_ESP32
if (!_ps_set) {
_segment_index_prev = _segment_index;
_virtualSegmentLength_prev = _virtualSegmentLength;
_ps_set = true;
}
#endif
_segment_index = n;
_virtualSegmentLength = SEGMENT.length();
_virtualSegmentLength = SEGMENT.virtualLength();
} else {
_segment_index = 0;
_virtualSegmentLength = 0;
_virtualSegmentLength = 0;
#ifdef ARDUINO_ARCH_ESP32
if (_ps_set) {
_segment_index = _segment_index_prev;
_virtualSegmentLength = _virtualSegmentLength_prev;
_ps_set = false;
}
#endif
}
}
@@ -670,13 +756,13 @@ void WS2812FX::setTransition(uint16_t t)
void WS2812FX::setTransitionMode(bool t)
{
unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled
unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled
for (uint16_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
_segment_index = i;
SEGMENT.setOption(SEG_OPTION_TRANSITIONAL, t);
_segments[i].setOption(SEG_OPTION_TRANSITIONAL, t);
if (t && SEGMENT.mode == FX_MODE_STATIC && SEGENV.next_time > waitMax) SEGENV.next_time = waitMax;
if (t && _segments[i].mode == FX_MODE_STATIC && _segment_runtimes[i].next_time > waitMax)
_segment_runtimes[i].next_time = waitMax;
}
}
@@ -689,22 +775,22 @@ uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint16_t blend,
if(blend == blendmax) return color2;
uint8_t shift = b16 ? 16 : 8;
uint32_t w1 = (color1 >> 24) & 0xFF;
uint32_t r1 = (color1 >> 16) & 0xFF;
uint32_t g1 = (color1 >> 8) & 0xFF;
uint32_t b1 = color1 & 0xFF;
uint32_t w1 = W(color1);
uint32_t r1 = R(color1);
uint32_t g1 = G(color1);
uint32_t b1 = B(color1);
uint32_t w2 = (color2 >> 24) & 0xFF;
uint32_t r2 = (color2 >> 16) & 0xFF;
uint32_t g2 = (color2 >> 8) & 0xFF;
uint32_t b2 = color2 & 0xFF;
uint32_t w2 = W(color2);
uint32_t r2 = R(color2);
uint32_t g2 = G(color2);
uint32_t b2 = B(color2);
uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift;
uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift;
uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift;
uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift;
return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3));
return RGBW32(r3, g3, b3, w3);
}
/*
@@ -732,17 +818,17 @@ void WS2812FX::fade_out(uint8_t rate) {
float mappedRate = float(rate) +1.1;
uint32_t color = SEGCOLOR(1); // target color
int w2 = (color >> 24) & 0xff;
int r2 = (color >> 16) & 0xff;
int g2 = (color >> 8) & 0xff;
int b2 = color & 0xff;
int w2 = W(color);
int r2 = R(color);
int g2 = G(color);
int b2 = B(color);
for(uint16_t i = 0; i < SEGLEN; i++) {
color = getPixelColor(i);
int w1 = (color >> 24) & 0xff;
int r1 = (color >> 16) & 0xff;
int g1 = (color >> 8) & 0xff;
int b1 = color & 0xff;
int w1 = W(color);
int r1 = R(color);
int g1 = G(color);
int b1 = B(color);
int wdelta = (w2 - w1) / mappedRate;
int rdelta = (r2 - r1) / mappedRate;
@@ -776,9 +862,9 @@ void WS2812FX::blur(uint8_t blur_amount)
cur += carryover;
if(i > 0) {
uint32_t c = getPixelColor(i-1);
uint8_t r = (c >> 16 & 0xFF);
uint8_t g = (c >> 8 & 0xFF);
uint8_t b = (c & 0xFF);
uint8_t r = R(c);
uint8_t g = G(c);
uint8_t b = B(c);
setPixelColor(i-1, qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue));
}
setPixelColor(i,cur.red, cur.green, cur.blue);
@@ -861,16 +947,16 @@ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) {
uint32_t WS2812FX::crgb_to_col(CRGB fastled)
{
return (((uint32_t)fastled.red << 16) | ((uint32_t)fastled.green << 8) | fastled.blue);
return RGBW32(fastled.red, fastled.green, fastled.blue, 0);
}
CRGB WS2812FX::col_to_crgb(uint32_t color)
{
CRGB fastled_col;
fastled_col.red = (color >> 16 & 0xFF);
fastled_col.green = (color >> 8 & 0xFF);
fastled_col.blue = (color & 0xFF);
fastled_col.red = R(color);
fastled_col.green = G(color);
fastled_col.blue = B(color);
return fastled_col;
}
@@ -1010,37 +1096,44 @@ uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8
return crgb_to_col(fastled_col);
}
//@returns `true` if color, mode, speed, intensity and palette match
bool WS2812FX::segmentsAreIdentical(Segment* a, Segment* b)
{
//if (a->start != b->start) return false;
//if (a->stop != b->stop) return false;
for (uint8_t i = 0; i < NUM_COLORS; i++)
{
if (a->colors[i] != b->colors[i]) return false;
//load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
void WS2812FX::deserializeMap(uint8_t n) {
char fileName[32];
strcpy_P(fileName, PSTR("/ledmap"));
if (n) sprintf(fileName +7, "%d", n);
strcat(fileName, ".json");
bool isFile = WLED_FS.exists(fileName);
if (!isFile) {
// erase custom mapping if selecting nonexistent ledmap.json (n==0)
if (!n && customMappingTable != nullptr) {
customMappingSize = 0;
delete[] customMappingTable;
customMappingTable = nullptr;
}
return;
}
if (a->mode != b->mode) return false;
if (a->speed != b->speed) return false;
if (a->intensity != b->intensity) return false;
if (a->palette != b->palette) return false;
//if (a->getOption(SEG_OPTION_REVERSED) != b->getOption(SEG_OPTION_REVERSED)) return false;
return true;
}
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(7)) return;
#endif
//load custom mapping table from JSON file
void WS2812FX::deserializeMap(void) {
if (!WLED_FS.exists("/ledmap.json")) return;
DynamicJsonDocument doc(JSON_BUFFER_SIZE); // full sized buffer for larger maps
DEBUG_PRINT(F("Reading LED map from "));
DEBUG_PRINTLN(fileName);
DEBUG_PRINTLN(F("Reading LED map from /ledmap.json..."));
if (!readObjectFromFile("/ledmap.json", nullptr, &doc)) return; //if file does not exist just exit
if (!readObjectFromFile(fileName, nullptr, &doc)) {
releaseJSONBufferLock();
return; //if file does not exist just exit
}
// erase old custom ledmap
if (customMappingTable != nullptr) {
customMappingSize = 0;
delete[] customMappingTable;
customMappingTable = nullptr;
customMappingSize = 0;
}
JsonArray map = doc[F("map")];
@@ -1051,6 +1144,8 @@ void WS2812FX::deserializeMap(void) {
customMappingTable[i] = (uint16_t) map[i];
}
}
releaseJSONBufferLock();
}
//gamma 2.8 lookup table used for color correction
@@ -1091,15 +1186,20 @@ uint8_t WS2812FX::gamma8(uint8_t b)
uint32_t WS2812FX::gamma32(uint32_t color)
{
if (!gammaCorrectCol) return color;
uint8_t w = (color >> 24);
uint8_t r = (color >> 16);
uint8_t g = (color >> 8);
uint8_t b = color;
uint8_t w = W(color);
uint8_t r = R(color);
uint8_t g = G(color);
uint8_t b = B(color);
w = gammaT[w];
r = gammaT[r];
g = gammaT[g];
b = gammaT[b];
return ((w << 24) | (r << 16) | (g << 8) | (b));
return RGBW32(r, g, b, w);
}
WS2812FX* WS2812FX::instance = nullptr;
WS2812FX* WS2812FX::instance = nullptr;
//Bus static member definition, would belong in bus_manager.cpp
int16_t Bus::_cct = -1;
uint8_t Bus::_cctBlend = 0;
uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL;

View File

@@ -44,10 +44,10 @@ void onAlexaChange(EspalexaDevice* dev)
if (bri == 0)
{
bri = briLast;
colorUpdated(NOTIFIER_CALL_MODE_ALEXA);
colorUpdated(CALL_MODE_ALEXA);
}
} else {
applyPreset(macroAlexaOn);
applyPreset(macroAlexaOn, CALL_MODE_ALEXA);
if (bri == 0) espalexaDevice->setValue(briLast); //stop Alexa from complaining if macroAlexaOn does not actually turn on
}
} else if (m == EspalexaDeviceProperty::off)
@@ -58,32 +58,42 @@ void onAlexaChange(EspalexaDevice* dev)
{
briLast = bri;
bri = 0;
colorUpdated(NOTIFIER_CALL_MODE_ALEXA);
colorUpdated(CALL_MODE_ALEXA);
}
} else {
applyPreset(macroAlexaOff);
applyPreset(macroAlexaOff, CALL_MODE_ALEXA);
if (bri != 0) espalexaDevice->setValue(0); //stop Alexa from complaining if macroAlexaOff does not actually turn off
}
} else if (m == EspalexaDeviceProperty::bri)
{
bri = espalexaDevice->getValue();
colorUpdated(NOTIFIER_CALL_MODE_ALEXA);
colorUpdated(CALL_MODE_ALEXA);
} else //color
{
if (espalexaDevice->getColorMode() == EspalexaColorMode::ct) //shade of white
{
uint16_t ct = espalexaDevice->getCt();
if (strip.isRgbw)
{
if (!ct) return;
uint16_t k = 1000000 / ct; //mireds to kelvin
if (strip.hasCCTBus()) {
uint8_t segid = strip.getMainSegmentId();
WS2812FX::Segment& seg = strip.getSegment(segid);
uint8_t cctPrev = seg.cct;
seg.setCCT(k, segid);
if (seg.cct != cctPrev) effectChanged = true; //send UDP
col[0]= 0; col[1]= 0; col[2]= 0; col[3]= 255;
} else if (strip.isRgbw) {
switch (ct) { //these values empirically look good on RGBW
case 199: col[0]=255; col[1]=255; col[2]=255; col[3]=255; break;
case 234: col[0]=127; col[1]=127; col[2]=127; col[3]=255; break;
case 284: col[0]= 0; col[1]= 0; col[2]= 0; col[3]=255; break;
case 350: col[0]=130; col[1]= 90; col[2]= 0; col[3]=255; break;
case 383: col[0]=255; col[1]=153; col[2]= 0; col[3]=255; break;
default : colorKtoRGB(k, col);
}
} else {
colorCTtoRGB(ct, col);
colorKtoRGB(k, col);
}
} else {
uint32_t color = espalexaDevice->getRGB();
@@ -93,7 +103,7 @@ void onAlexaChange(EspalexaDevice* dev)
col[2] = ( color & 0xFF);
col[3] = 0;
}
colorUpdated(NOTIFIER_CALL_MODE_ALEXA);
colorUpdated(CALL_MODE_ALEXA);
}
}

View File

@@ -44,45 +44,45 @@ void updateBlynk()
BLYNK_WRITE(V0)
{
bri = param.asInt();//bri
colorUpdated(NOTIFIER_CALL_MODE_BLYNK);
colorUpdated(CALL_MODE_BLYNK);
}
BLYNK_WRITE(V1)
{
blHue = param.asInt();//hue
colorHStoRGB(blHue*10,blSat,(false)? colSec:col);
colorUpdated(NOTIFIER_CALL_MODE_BLYNK);
colorUpdated(CALL_MODE_BLYNK);
}
BLYNK_WRITE(V2)
{
blSat = param.asInt();//sat
colorHStoRGB(blHue*10,blSat,(false)? colSec:col);
colorUpdated(NOTIFIER_CALL_MODE_BLYNK);
colorUpdated(CALL_MODE_BLYNK);
}
BLYNK_WRITE(V3)
{
bool on = (param.asInt()>0);
if (!on != !bri) {toggleOnOff(); colorUpdated(NOTIFIER_CALL_MODE_BLYNK);}
if (!on != !bri) {toggleOnOff(); colorUpdated(CALL_MODE_BLYNK);}
}
BLYNK_WRITE(V4)
{
effectCurrent = param.asInt()-1;//fx
colorUpdated(NOTIFIER_CALL_MODE_BLYNK);
colorUpdated(CALL_MODE_BLYNK);
}
BLYNK_WRITE(V5)
{
effectSpeed = param.asInt();//sx
colorUpdated(NOTIFIER_CALL_MODE_BLYNK);
colorUpdated(CALL_MODE_BLYNK);
}
BLYNK_WRITE(V6)
{
effectIntensity = param.asInt();//ix
colorUpdated(NOTIFIER_CALL_MODE_BLYNK);
colorUpdated(CALL_MODE_BLYNK);
}
BLYNK_WRITE(V7)

View File

@@ -10,96 +10,143 @@
#include "bus_wrapper.h"
#include <Arduino.h>
//colors.cpp
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
void colorRGBtoRGBW(byte* rgb);
// enable additional debug output
#ifdef WLED_DEBUG
#ifndef ESP8266
#include <rom/rtc.h>
#endif
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#define DEBUG_PRINTF(x...) Serial.printf(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINTF(x...)
#endif
#define GET_BIT(var,bit) (((var)>>(bit))&0x01)
#define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit)))
#define UNSET_BIT(var,bit) ((var)&=(~(uint16_t)(0x0001<<(bit))))
//color mangling macros
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
#define R(c) (byte((c) >> 16))
#define G(c) (byte((c) >> 8))
#define B(c) (byte(c))
#define W(c) (byte((c) >> 24))
//temporary struct for passing bus configuration to bus
struct BusConfig {
uint8_t type = TYPE_WS2812_RGB;
uint16_t count = 1;
uint16_t start = 0;
uint8_t colorOrder = COL_ORDER_GRB;
bool reversed = false;
uint16_t count;
uint16_t start;
uint8_t colorOrder;
bool reversed;
uint8_t skipAmount;
bool refreshReq;
uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255};
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip=0) {
type = busType; count = len; start = pstart;
colorOrder = pcolorOrder; reversed = rev; skipAmount = skip;
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0) {
refreshReq = (bool) GET_BIT(busType,7);
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)
count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip;
uint8_t nPins = 1;
if (type > 47) nPins = 2;
if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address
else if (type > 47) nPins = 2;
else if (type > 40 && type < 46) nPins = NUM_PWM_PINS(type);
for (uint8_t i = 0; i < nPins; i++) pins[i] = ppins[i];
}
//validates start and length and extends total if needed
bool adjustBounds(uint16_t& total) {
if (!count) count = 1;
if (count > MAX_LEDS_PER_BUS) count = MAX_LEDS_PER_BUS;
if (start >= MAX_LEDS) return false;
//limit length of strip if it would exceed total permissible LEDs
if (start + count > MAX_LEDS) count = MAX_LEDS - start;
//extend total count accordingly
if (start + count > total) total = start + count;
return true;
}
};
//parent class of BusDigital and BusPwm
//parent class of BusDigital, BusPwm, and BusNetwork
class Bus {
public:
Bus(uint8_t type, uint16_t start) {
_type = type;
_start = start;
};
virtual void show() {}
virtual bool canShow() { return true; }
Bus(uint8_t type, uint16_t start) {
_type = type;
_start = start;
};
virtual void setPixelColor(uint16_t pix, uint32_t c) {};
virtual ~Bus() {} //throw the bus under the bus
virtual void setBrightness(uint8_t b) {};
virtual void show() {}
virtual bool canShow() { return true; }
virtual void setStatusPixel(uint32_t c) {}
virtual void setPixelColor(uint16_t pix, uint32_t c) {}
virtual uint32_t getPixelColor(uint16_t pix) { return 0; }
virtual void setBrightness(uint8_t b) {}
virtual void cleanup() {}
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
inline uint16_t getLength() { return _len; }
virtual void setColorOrder() {}
virtual uint8_t getColorOrder() { return COL_ORDER_RGB; }
virtual uint8_t skippedLeds() { return 0; }
inline uint16_t getStart() { return _start; }
inline void setStart(uint16_t start) { _start = start; }
inline uint8_t getType() { return _type; }
inline bool isOk() { return _valid; }
inline bool isOffRefreshRequired() { return _needsRefresh; }
bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; }
virtual uint32_t getPixelColor(uint16_t pix) { return 0; };
virtual bool isRgbw() { return Bus::isRgbw(_type); }
static bool isRgbw(uint8_t type) {
if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true;
if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
return false;
}
static void setCCT(uint16_t cct) {
_cct = cct;
}
static void setCCTBlend(uint8_t b) {
if (b > 100) b = 100;
_cctBlend = (b * 127) / 100;
//compile-time limiter for hardware that can't power both white channels at max
#ifdef WLED_MAX_CCT_BLEND
if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND;
#endif
}
inline static void setAutoWhiteMode(uint8_t m) { if (m < 4) _autoWhiteMode = m; }
inline static uint8_t getAutoWhiteMode() { return _autoWhiteMode; }
virtual void cleanup() {};
virtual ~Bus() { //throw the bus under the bus
}
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
inline uint16_t getStart() {
return _start;
}
inline void setStart(uint16_t start) {
_start = start;
}
virtual uint16_t getLength() {
return 1;
}
virtual void setColorOrder() {}
virtual uint8_t getColorOrder() {
return COL_ORDER_RGB;
}
virtual bool isRgbw() {
return false;
}
virtual uint8_t skippedLeds() {
return 0;
}
inline uint8_t getType() {
return _type;
}
inline bool isOk() {
return _valid;
}
static bool isRgbw(uint8_t type) {
if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true;
if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
return false;
}
bool reversed = false;
bool reversed = false;
protected:
uint8_t _type = TYPE_NONE;
uint8_t _bri = 255;
uint16_t _start = 0;
bool _valid = false;
uint8_t _type = TYPE_NONE;
uint8_t _bri = 255;
uint16_t _start = 0;
uint16_t _len = 1;
bool _valid = false;
bool _needsRefresh = false;
static uint8_t _autoWhiteMode;
static int16_t _cct;
static uint8_t _cctBlend;
uint32_t autoWhiteCalc(uint32_t c) {
if (_autoWhiteMode == RGBW_MODE_MANUAL_ONLY) return c;
uint8_t w = W(c);
//ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0)
if (w > 0 && _autoWhiteMode == RGBW_MODE_DUAL) return c;
uint8_t r = R(c);
uint8_t g = G(c);
uint8_t b = B(c);
w = r < g ? (r < b ? r : b) : (g < b ? g : b);
if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode
return RGBW32(r, g, b, w);
}
};
@@ -107,23 +154,24 @@ class BusDigital : public Bus {
public:
BusDigital(BusConfig &bc, uint8_t nr) : Bus(bc.type, bc.start) {
if (!IS_DIGITAL(bc.type) || !bc.count) return;
if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return;
_pins[0] = bc.pins[0];
if (!pinManager.allocatePin(_pins[0])) return;
if (IS_2PIN(bc.type)) {
_pins[1] = bc.pins[1];
if (!pinManager.allocatePin(_pins[1])) {
if (!pinManager.allocatePin(bc.pins[1], true, PinOwner::BusDigital)) {
cleanup(); return;
}
_pins[1] = bc.pins[1];
}
reversed = bc.reversed;
_needsRefresh = bc.refreshReq || bc.type == TYPE_TM1814;
_skip = bc.skipAmount; //sacrificial pixels
_len = bc.count + _skip;
_iType = PolyBus::getI(bc.type, _pins, nr);
if (_iType == I_NONE) return;
_busPtr = PolyBus::create(_iType, _pins, _len);
_busPtr = PolyBus::create(_iType, _pins, _len, nr);
_valid = (_busPtr != nullptr);
_colorOrder = bc.colorOrder;
//Serial.printf("Successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n",nr, len, type, pins[0],pins[1],_iType);
DEBUG_PRINTF("Successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n",nr, _len, bc.type, _pins[0],_pins[1],_iType);
};
inline void show() {
@@ -145,7 +193,18 @@ class BusDigital : public Bus {
PolyBus::setBrightness(_busPtr, _iType, b);
}
//If LEDs are skipped, it is possible to use the first as a status LED.
//TODO only show if no new show due in the next 50ms
void setStatusPixel(uint32_t c) {
if (_skip && canShow()) {
PolyBus::setPixelColor(_busPtr, _iType, 0, c, _colorOrder);
PolyBus::show(_busPtr, _iType);
}
}
void setPixelColor(uint16_t pix, uint32_t c) {
if (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814) c = autoWhiteCalc(c);
if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
if (reversed) pix = _len - pix -1;
else pix += _skip;
PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder);
@@ -176,10 +235,6 @@ class BusDigital : public Bus {
_colorOrder = colorOrder;
}
inline bool isRgbw() {
return (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814);
}
inline uint8_t skippedLeds() {
return _skip;
}
@@ -189,13 +244,13 @@ class BusDigital : public Bus {
}
void cleanup() {
//Serial.println("Digital Cleanup");
DEBUG_PRINTLN(F("Digital Cleanup."));
PolyBus::cleanup(_busPtr, _iType);
_iType = I_NONE;
_valid = false;
_busPtr = nullptr;
pinManager.deallocatePin(_pins[0]);
pinManager.deallocatePin(_pins[1]);
pinManager.deallocatePin(_pins[1], PinOwner::BusDigital);
pinManager.deallocatePin(_pins[0], PinOwner::BusDigital);
}
~BusDigital() {
@@ -206,7 +261,6 @@ class BusDigital : public Bus {
uint8_t _colorOrder = COL_ORDER_GRB;
uint8_t _pins[2] = {255, 255};
uint8_t _iType = I_NONE;
uint16_t _len = 0;
uint8_t _skip = 0;
void * _busPtr = nullptr;
};
@@ -215,6 +269,7 @@ class BusDigital : public Bus {
class BusPwm : public Bus {
public:
BusPwm(BusConfig &bc) : Bus(bc.type, bc.start) {
_valid = false;
if (!IS_PWM(bc.type)) return;
uint8_t numPins = NUM_PWM_PINS(bc.type);
@@ -229,10 +284,11 @@ class BusPwm : public Bus {
#endif
for (uint8_t i = 0; i < numPins; i++) {
_pins[i] = bc.pins[i];
if (!pinManager.allocatePin(_pins[i])) {
uint8_t currentPin = bc.pins[i];
if (!pinManager.allocatePin(currentPin, true, PinOwner::BusPwm)) {
deallocatePins(); return;
}
_pins[i] = currentPin; // store only after allocatePin() succeeds
#ifdef ESP8266
pinMode(_pins[i], OUTPUT);
#else
@@ -246,31 +302,66 @@ class BusPwm : public Bus {
void setPixelColor(uint16_t pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel
uint8_t r = c >> 16;
uint8_t g = c >> 8;
uint8_t b = c ;
uint8_t w = c >> 24;
if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c);
if (_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
}
uint8_t r = R(c);
uint8_t g = G(c);
uint8_t b = B(c);
uint8_t w = W(c);
uint8_t cct = 0; //0 - full warm white, 255 - full cold white
if (_cct > -1) {
if (_cct >= 1900) cct = (_cct - 1900) >> 5;
else if (_cct < 256) cct = _cct;
} else {
cct = (approximateKelvinFromRGB(c) - 1900) >> 5;
}
uint8_t ww, cw;
#ifdef WLED_USE_IC_CCT
ww = w;
cw = cct;
#else
//0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold)
if (cct < _cctBlend) ww = 255;
else ww = ((255-cct) * 255) / (255 - _cctBlend);
if ((255-cct) < _cctBlend) cw = 255;
else cw = (cct * 255) / (255 - _cctBlend);
ww = (w * ww) / 255; //brightness scaling
cw = (w * cw) / 255;
#endif
switch (_type) {
case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value
_data[0] = max(r, max(g, max(b, w))); break;
case TYPE_ANALOG_2CH: //warm white + cold white, we'll need some nice handling here, for now just R+G channels
case TYPE_ANALOG_3CH: //standard dumb RGB
case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation
_data[0] = w;
break;
case TYPE_ANALOG_2CH: //warm white + cold white
_data[1] = cw;
_data[0] = ww;
break;
case TYPE_ANALOG_5CH: //RGB + warm white + cold white
// perhaps a non-linear adjustment would be in order. need to test
_data[4] = cw;
w = ww;
case TYPE_ANALOG_4CH: //RGBW
case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB
_data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = 0; break;
default: return;
_data[3] = w;
case TYPE_ANALOG_3CH: //standard dumb RGB
_data[0] = r; _data[1] = g; _data[2] = b;
break;
}
}
//does no index check
uint32_t getPixelColor(uint16_t pix) {
return ((_data[3] << 24) | (_data[0] << 16) | (_data[1] << 8) | (_data[2]));
if (!_valid) return 0;
return RGBW32(_data[0], _data[1], _data[2], _data[3]);
}
void show() {
if (!_valid) return;
uint8_t numPins = NUM_PWM_PINS(_type);
for (uint8_t i = 0; i < numPins; i++) {
uint8_t scaled = (_data[i] * _bri) / 255;
@@ -288,15 +379,14 @@ class BusPwm : public Bus {
}
uint8_t getPins(uint8_t* pinArray) {
if (!_valid) return 0;
uint8_t numPins = NUM_PWM_PINS(_type);
for (uint8_t i = 0; i < numPins; i++) pinArray[i] = _pins[i];
for (uint8_t i = 0; i < numPins; i++) {
pinArray[i] = _pins[i];
}
return numPins;
}
bool isRgbw() {
return (_type > TYPE_ONOFF && _type <= TYPE_ANALOG_5CH && _type != TYPE_ANALOG_3CH);
}
inline void cleanup() {
deallocatePins();
}
@@ -315,13 +405,13 @@ class BusPwm : public Bus {
void deallocatePins() {
uint8_t numPins = NUM_PWM_PINS(_type);
for (uint8_t i = 0; i < numPins; i++) {
pinManager.deallocatePin(_pins[i], PinOwner::BusPwm);
if (!pinManager.isPinOk(_pins[i])) continue;
#ifdef ESP8266
digitalWrite(_pins[i], LOW); //turn off PWM interrupt
#else
if (_ledcStart < 16) ledcDetachPin(_pins[i]);
#endif
pinManager.deallocatePin(_pins[i]);
}
#ifdef ARDUINO_ARCH_ESP32
pinManager.deallocateLedc(_ledcStart, numPins);
@@ -329,6 +419,109 @@ class BusPwm : public Bus {
}
};
class BusNetwork : public Bus {
public:
BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start) {
_valid = false;
// switch (bc.type) {
// case TYPE_NET_ARTNET_RGB:
// _rgbw = false;
// _UDPtype = 2;
// break;
// case TYPE_NET_E131_RGB:
// _rgbw = false;
// _UDPtype = 1;
// break;
// case TYPE_NET_DDP_RGB:
// _rgbw = false;
// _UDPtype = 0;
// break;
// default:
_rgbw = false;
_UDPtype = bc.type - TYPE_NET_DDP_RGB;
// break;
// }
_UDPchannels = _rgbw ? 4 : 3;
_data = (byte *)malloc(bc.count * _UDPchannels);
if (_data == nullptr) return;
memset(_data, 0, bc.count * _UDPchannels);
_len = bc.count;
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
_broadcastLock = false;
_valid = true;
};
void setPixelColor(uint16_t pix, uint32_t c) {
if (!_valid || pix >= _len) return;
if (isRgbw()) c = autoWhiteCalc(c);
if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT
uint16_t offset = pix * _UDPchannels;
_data[offset] = R(c);
_data[offset+1] = G(c);
_data[offset+2] = B(c);
if (_rgbw) _data[offset+3] = W(c);
}
uint32_t getPixelColor(uint16_t pix) {
if (!_valid || pix >= _len) return 0;
uint16_t offset = pix * _UDPchannels;
return RGBW32(_data[offset], _data[offset+1], _data[offset+2], _rgbw ? (_data[offset+3] << 24) : 0);
}
void show() {
if (!_valid || !canShow()) return;
_broadcastLock = true;
realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw);
_broadcastLock = false;
}
inline bool canShow() {
// this should be a return value from UDP routine if it is still sending data out
return !_broadcastLock;
}
inline void setBrightness(uint8_t b) {
_bri = b;
}
uint8_t getPins(uint8_t* pinArray) {
for (uint8_t i = 0; i < 4; i++) {
pinArray[i] = _client[i];
}
return 4;
}
inline bool isRgbw() {
return _rgbw;
}
inline uint16_t getLength() {
return _len;
}
void cleanup() {
_type = I_NONE;
_valid = false;
if (_data != nullptr) free(_data);
_data = nullptr;
}
~BusNetwork() {
cleanup();
}
private:
IPAddress _client;
uint8_t _bri = 255;
uint8_t _UDPtype;
uint8_t _UDPchannels;
bool _rgbw;
bool _broadcastLock;
byte *_data;
};
class BusManager {
public:
BusManager() {
@@ -339,7 +532,7 @@ class BusManager {
static uint32_t memUsage(BusConfig &bc) {
uint8_t type = bc.type;
uint16_t len = bc.count;
if (type < 32) {
if (type > 15 && type < 32) {
#ifdef ESP8266
if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem
if (type > 29) return len*20; //RGBW
@@ -352,15 +545,16 @@ class BusManager {
return len*6;
#endif
}
if (type > 31 && type < 48) return 5;
if (type > 31 && type < 48) return 5;
if (type == 44 || type == 45) return len*4; //RGBW
return len*3;
return len*3; //RGB
}
int add(BusConfig &bc) {
if (numBusses >= WLED_MAX_BUSSES) return -1;
if (IS_DIGITAL(bc.type)) {
if (bc.type >= TYPE_NET_DDP_RGB && bc.type < 96) {
busses[numBusses] = new BusNetwork(bc);
} else if (IS_DIGITAL(bc.type)) {
busses[numBusses] = new BusDigital(bc, numBusses);
} else {
busses[numBusses] = new BusPwm(bc);
@@ -370,7 +564,7 @@ class BusManager {
//do not call this method from system context (network callback)
void removeAll() {
//Serial.println("Removing all.");
DEBUG_PRINTLN(F("Removing all."));
//prevents crashes due to deleting busses while in use.
while (!canAllShow()) yield();
for (uint8_t i = 0; i < numBusses; i++) delete busses[i];
@@ -383,13 +577,18 @@ class BusManager {
}
}
void setPixelColor(uint16_t pix, uint32_t c) {
void setStatusPixel(uint32_t c) {
for (uint8_t i = 0; i < numBusses; i++) {
busses[i]->setStatusPixel(c);
}
}
void setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1) {
for (uint8_t i = 0; i < numBusses; i++) {
Bus* b = busses[i];
uint16_t bstart = b->getStart();
if (pix < bstart || pix >= bstart + b->getLength()) continue;
busses[i]->setPixelColor(pix - bstart, c);
break;
}
}
@@ -399,6 +598,15 @@ class BusManager {
}
}
void setSegmentCCT(int16_t cct, bool allowWBCorrection = false) {
if (cct > 255) cct = 255;
if (cct >= 0) {
//if white balance correction allowed, save as kelvin value instead of 0-255
if (allowWBCorrection) cct = 1900 + (cct << 5);
} else cct = -1;
Bus::setCCT(cct);
}
uint32_t getPixelColor(uint16_t pix) {
for (uint8_t i = 0; i < numBusses; i++) {
Bus* b = busses[i];
@@ -425,23 +633,15 @@ class BusManager {
return numBusses;
}
//semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit())
uint16_t getTotalLength() {
uint16_t len = 0;
for (uint8_t i=0; i<numBusses; i++ ) len += busses[i]->getLength();
for (uint8_t i=0; i<numBusses; i++) len += busses[i]->getLength();
return len;
}
static inline bool isRgbw(uint8_t type) {
return Bus::isRgbw(type);
}
//Return true if the strip requires a refresh to stay off.
static bool isOffRefreshRequred(uint8_t type) {
return type == TYPE_TM1814;
}
private:
uint8_t numBusses = 0;
Bus* busses[WLED_MAX_BUSSES];
};
#endif
#endif

View File

@@ -36,66 +36,38 @@
/*** ESP32 Neopixel methods ***/
//RGB
#define I_32_R0_NEO_3 17
#define I_32_R1_NEO_3 18
#define I_32_R2_NEO_3 19
#define I_32_R3_NEO_3 20
#define I_32_R4_NEO_3 21
#define I_32_R5_NEO_3 22
#define I_32_R6_NEO_3 23
#define I_32_R7_NEO_3 24
#define I_32_I0_NEO_3 25
#define I_32_I1_NEO_3 26
#define I_32_RN_NEO_3 17
#define I_32_I0_NEO_3 18
#define I_32_I1_NEO_3 19
//RGBW
#define I_32_R0_NEO_4 27
#define I_32_R1_NEO_4 28
#define I_32_R2_NEO_4 29
#define I_32_R3_NEO_4 30
#define I_32_R4_NEO_4 31
#define I_32_R5_NEO_4 32
#define I_32_R6_NEO_4 33
#define I_32_R7_NEO_4 34
#define I_32_I0_NEO_4 35
#define I_32_I1_NEO_4 36
#define I_32_RN_NEO_4 20
#define I_32_I0_NEO_4 21
#define I_32_I1_NEO_4 22
//400Kbps
#define I_32_R0_400_3 37
#define I_32_R1_400_3 38
#define I_32_R2_400_3 39
#define I_32_R3_400_3 40
#define I_32_R4_400_3 41
#define I_32_R5_400_3 42
#define I_32_R6_400_3 43
#define I_32_R7_400_3 44
#define I_32_I0_400_3 45
#define I_32_I1_400_3 46
#define I_32_RN_400_3 23
#define I_32_I0_400_3 24
#define I_32_I1_400_3 25
//TM1814 (RGBW)
#define I_32_R0_TM1_4 47
#define I_32_R1_TM1_4 48
#define I_32_R2_TM1_4 49
#define I_32_R3_TM1_4 50
#define I_32_R4_TM1_4 51
#define I_32_R5_TM1_4 52
#define I_32_R6_TM1_4 53
#define I_32_R7_TM1_4 54
#define I_32_I0_TM1_4 55
#define I_32_I1_TM1_4 56
#define I_32_RN_TM1_4 26
#define I_32_I0_TM1_4 27
#define I_32_I1_TM1_4 28
//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S)
//APA102
#define I_HS_DOT_3 57 //hardware SPI
#define I_SS_DOT_3 58 //soft SPI
#define I_HS_DOT_3 29 //hardware SPI
#define I_SS_DOT_3 30 //soft SPI
//LPD8806
#define I_HS_LPD_3 59
#define I_SS_LPD_3 60
#define I_HS_LPD_3 31
#define I_SS_LPD_3 32
//WS2801
#define I_HS_WS1_3 61
#define I_SS_WS1_3 62
#define I_HS_WS1_3 33
#define I_SS_WS1_3 34
//P9813
#define I_HS_P98_3 63
#define I_SS_P98_3 64
#define I_HS_P98_3 35
#define I_SS_P98_3 36
/*** ESP8266 Neopixel methods ***/
@@ -125,63 +97,46 @@
/*** ESP32 Neopixel methods ***/
#ifdef ARDUINO_ARCH_ESP32
//RGB
#define B_32_R0_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt0Ws2812xMethod>
#define B_32_R1_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt1Ws2812xMethod>
#define B_32_R2_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt2Ws2812xMethod>
#define B_32_R3_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt3Ws2812xMethod>
#define B_32_R4_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt4Ws2812xMethod>
#define B_32_R5_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt5Ws2812xMethod>
#define B_32_R6_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt6Ws2812xMethod>
#define B_32_R7_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt7Ws2812xMethod>
#define B_32_RN_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32RmtNWs2812xMethod>
#define B_32_I0_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32I2s0800KbpsMethod>
#ifndef CONFIG_IDF_TARGET_ESP32S2
#define B_32_I1_NEO_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32I2s1800KbpsMethod>
#endif
//RGBW
#define B_32_R0_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32Rmt0Ws2812xMethod>
#define B_32_R1_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32Rmt1Ws2812xMethod>
#define B_32_R2_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32Rmt2Ws2812xMethod>
#define B_32_R3_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32Rmt3Ws2812xMethod>
#define B_32_R4_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32Rmt4Ws2812xMethod>
#define B_32_R5_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32Rmt5Ws2812xMethod>
#define B_32_R6_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32Rmt6Ws2812xMethod>
#define B_32_R7_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32Rmt7Ws2812xMethod>
#define B_32_RN_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32RmtNWs2812xMethod>
#define B_32_I0_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32I2s0800KbpsMethod>
#ifndef CONFIG_IDF_TARGET_ESP32S2
#define B_32_I1_NEO_4 NeoPixelBrightnessBus<NeoGrbwFeature, NeoEsp32I2s1800KbpsMethod>
#endif
//400Kbps
#define B_32_R0_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt0400KbpsMethod>
#define B_32_R1_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt1400KbpsMethod>
#define B_32_R2_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt2400KbpsMethod>
#define B_32_R3_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt3400KbpsMethod>
#define B_32_R4_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt4400KbpsMethod>
#define B_32_R5_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt5400KbpsMethod>
#define B_32_R6_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt6400KbpsMethod>
#define B_32_R7_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32Rmt7400KbpsMethod>
#define B_32_RN_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod>
#define B_32_I0_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32I2s0400KbpsMethod>
#ifndef CONFIG_IDF_TARGET_ESP32S2
#define B_32_I1_400_3 NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32I2s1400KbpsMethod>
#endif
//TM1814 (RGBW)
#define B_32_R0_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32Rmt0Tm1814Method>
#define B_32_R1_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32Rmt1Tm1814Method>
#define B_32_R2_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32Rmt2Tm1814Method>
#define B_32_R3_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32Rmt3Tm1814Method>
#define B_32_R4_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32Rmt4Tm1814Method>
#define B_32_R5_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32Rmt5Tm1814Method>
#define B_32_R6_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32Rmt6Tm1814Method>
#define B_32_R7_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32Rmt7Tm1814Method>
#define B_32_RN_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method>
#define B_32_I0_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32I2s0Tm1814Method>
#ifndef CONFIG_IDF_TARGET_ESP32S2
#define B_32_I1_TM1_4 NeoPixelBrightnessBus<NeoWrgbTm1814Feature, NeoEsp32I2s1Tm1814Method>
#endif
//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S)
#endif
//APA102
#define B_HS_DOT_3 NeoPixelBrightnessBus<DotStarBgrFeature, DotStarSpiMethod> //hardware SPI
#define B_SS_DOT_3 NeoPixelBrightnessBus<DotStarBgrFeature, DotStarMethod> //soft 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>
//WS2801
#define B_HS_WS1_3 NeoPixelBrightnessBus<NeoRbgFeature, NeoWs2801SpiMethod>
//#define B_HS_WS1_3 NeoPixelBrightnessBus<NeoRbgFeature, NeoWs2801Spi40MhzMethod>
//#define B_HS_WS1_3 NeoPixelBrightnessBus<NeoRbgFeature, NeoWs2801Spi20MhzMethod>
//#define B_HS_WS1_3 NeoPixelBrightnessBus<NeoRbgFeature, NeoWs2801SpiMethod> // 10MHz
#define B_HS_WS1_3 NeoPixelBrightnessBus<NeoRbgFeature, NeoWs2801Spi2MhzMethod> //slower, more compatible
#define B_SS_WS1_3 NeoPixelBrightnessBus<NeoRbgFeature, NeoWs2801Method>
//P9813
@@ -225,46 +180,26 @@ class PolyBus {
case I_HS_P98_3: (static_cast<B_HS_P98_3*>(busPtr))->Begin(); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_R0_NEO_3: (static_cast<B_32_R0_NEO_3*>(busPtr))->Begin(); break;
case I_32_R1_NEO_3: (static_cast<B_32_R1_NEO_3*>(busPtr))->Begin(); break;
case I_32_R2_NEO_3: (static_cast<B_32_R2_NEO_3*>(busPtr))->Begin(); break;
case I_32_R3_NEO_3: (static_cast<B_32_R3_NEO_3*>(busPtr))->Begin(); break;
case I_32_R4_NEO_3: (static_cast<B_32_R4_NEO_3*>(busPtr))->Begin(); break;
case I_32_R5_NEO_3: (static_cast<B_32_R5_NEO_3*>(busPtr))->Begin(); break;
case I_32_R6_NEO_3: (static_cast<B_32_R6_NEO_3*>(busPtr))->Begin(); break;
case I_32_R7_NEO_3: (static_cast<B_32_R7_NEO_3*>(busPtr))->Begin(); break;
case I_32_RN_NEO_3: (static_cast<B_32_RN_NEO_3*>(busPtr))->Begin(); break;
case I_32_I0_NEO_3: (static_cast<B_32_I0_NEO_3*>(busPtr))->Begin(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_3: (static_cast<B_32_I1_NEO_3*>(busPtr))->Begin(); break;
case I_32_R0_NEO_4: (static_cast<B_32_R0_NEO_4*>(busPtr))->Begin(); break;
case I_32_R1_NEO_4: (static_cast<B_32_R1_NEO_4*>(busPtr))->Begin(); break;
case I_32_R2_NEO_4: (static_cast<B_32_R2_NEO_4*>(busPtr))->Begin(); break;
case I_32_R3_NEO_4: (static_cast<B_32_R3_NEO_4*>(busPtr))->Begin(); break;
case I_32_R4_NEO_4: (static_cast<B_32_R4_NEO_4*>(busPtr))->Begin(); break;
case I_32_R5_NEO_4: (static_cast<B_32_R5_NEO_4*>(busPtr))->Begin(); break;
case I_32_R6_NEO_4: (static_cast<B_32_R6_NEO_4*>(busPtr))->Begin(); break;
case I_32_R7_NEO_4: (static_cast<B_32_R7_NEO_4*>(busPtr))->Begin(); break;
#endif
case I_32_RN_NEO_4: (static_cast<B_32_RN_NEO_4*>(busPtr))->Begin(); break;
case I_32_I0_NEO_4: (static_cast<B_32_I0_NEO_4*>(busPtr))->Begin(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_4: (static_cast<B_32_I1_NEO_4*>(busPtr))->Begin(); break;
case I_32_R0_400_3: (static_cast<B_32_R0_400_3*>(busPtr))->Begin(); break;
case I_32_R1_400_3: (static_cast<B_32_R1_400_3*>(busPtr))->Begin(); break;
case I_32_R2_400_3: (static_cast<B_32_R2_400_3*>(busPtr))->Begin(); break;
case I_32_R3_400_3: (static_cast<B_32_R3_400_3*>(busPtr))->Begin(); break;
case I_32_R4_400_3: (static_cast<B_32_R4_400_3*>(busPtr))->Begin(); break;
case I_32_R5_400_3: (static_cast<B_32_R5_400_3*>(busPtr))->Begin(); break;
case I_32_R6_400_3: (static_cast<B_32_R6_400_3*>(busPtr))->Begin(); break;
case I_32_R7_400_3: (static_cast<B_32_R7_400_3*>(busPtr))->Begin(); break;
#endif
case I_32_RN_400_3: (static_cast<B_32_RN_400_3*>(busPtr))->Begin(); break;
case I_32_I0_400_3: (static_cast<B_32_I0_400_3*>(busPtr))->Begin(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_400_3: (static_cast<B_32_I1_400_3*>(busPtr))->Begin(); break;
case I_32_R0_TM1_4: beginTM1814<B_32_R0_TM1_4*>(busPtr); break;
case I_32_R1_TM1_4: beginTM1814<B_32_R1_TM1_4*>(busPtr); break;
case I_32_R2_TM1_4: beginTM1814<B_32_R2_TM1_4*>(busPtr); break;
case I_32_R3_TM1_4: beginTM1814<B_32_R3_TM1_4*>(busPtr); break;
case I_32_R4_TM1_4: beginTM1814<B_32_R4_TM1_4*>(busPtr); break;
case I_32_R5_TM1_4: beginTM1814<B_32_R5_TM1_4*>(busPtr); break;
case I_32_R6_TM1_4: beginTM1814<B_32_R6_TM1_4*>(busPtr); break;
case I_32_R7_TM1_4: beginTM1814<B_32_R7_TM1_4*>(busPtr); break;
#endif
case I_32_RN_TM1_4: beginTM1814<B_32_RN_TM1_4*>(busPtr); break;
case I_32_I0_TM1_4: beginTM1814<B_32_I0_TM1_4*>(busPtr); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_TM1_4: beginTM1814<B_32_I1_TM1_4*>(busPtr); break;
#endif
// 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;
@@ -277,7 +212,7 @@ class PolyBus {
case I_SS_P98_3: (static_cast<B_SS_P98_3*>(busPtr))->Begin(); break;
}
};
static void* create(uint8_t busType, uint8_t* pins, uint16_t len) {
static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) {
void* busPtr = nullptr;
switch (busType) {
case I_NONE: break;
@@ -300,46 +235,26 @@ class PolyBus {
case I_8266_BB_TM1_4: busPtr = new B_8266_BB_TM1_4(len, pins[0]); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_R0_NEO_3: busPtr = new B_32_R0_NEO_3(len, pins[0]); break;
case I_32_R1_NEO_3: busPtr = new B_32_R1_NEO_3(len, pins[0]); break;
case I_32_R2_NEO_3: busPtr = new B_32_R2_NEO_3(len, pins[0]); break;
case I_32_R3_NEO_3: busPtr = new B_32_R3_NEO_3(len, pins[0]); break;
case I_32_R4_NEO_3: busPtr = new B_32_R4_NEO_3(len, pins[0]); break;
case I_32_R5_NEO_3: busPtr = new B_32_R5_NEO_3(len, pins[0]); break;
case I_32_R6_NEO_3: busPtr = new B_32_R6_NEO_3(len, pins[0]); break;
case I_32_R7_NEO_3: busPtr = new B_32_R7_NEO_3(len, pins[0]); break;
case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break;
case I_32_I0_NEO_3: busPtr = new B_32_I0_NEO_3(len, pins[0]); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_3: busPtr = new B_32_I1_NEO_3(len, pins[0]); break;
case I_32_R0_NEO_4: busPtr = new B_32_R0_NEO_4(len, pins[0]); break;
case I_32_R1_NEO_4: busPtr = new B_32_R1_NEO_4(len, pins[0]); break;
case I_32_R2_NEO_4: busPtr = new B_32_R2_NEO_4(len, pins[0]); break;
case I_32_R3_NEO_4: busPtr = new B_32_R3_NEO_4(len, pins[0]); break;
case I_32_R4_NEO_4: busPtr = new B_32_R4_NEO_4(len, pins[0]); break;
case I_32_R5_NEO_4: busPtr = new B_32_R5_NEO_4(len, pins[0]); break;
case I_32_R6_NEO_4: busPtr = new B_32_R6_NEO_4(len, pins[0]); break;
case I_32_R7_NEO_4: busPtr = new B_32_R7_NEO_4(len, pins[0]); break;
#endif
case I_32_RN_NEO_4: busPtr = new B_32_RN_NEO_4(len, pins[0], (NeoBusChannel)channel); break;
case I_32_I0_NEO_4: busPtr = new B_32_I0_NEO_4(len, pins[0]); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_4: busPtr = new B_32_I1_NEO_4(len, pins[0]); break;
case I_32_R0_400_3: busPtr = new B_32_R0_400_3(len, pins[0]); break;
case I_32_R1_400_3: busPtr = new B_32_R1_400_3(len, pins[0]); break;
case I_32_R2_400_3: busPtr = new B_32_R2_400_3(len, pins[0]); break;
case I_32_R3_400_3: busPtr = new B_32_R3_400_3(len, pins[0]); break;
case I_32_R4_400_3: busPtr = new B_32_R4_400_3(len, pins[0]); break;
case I_32_R5_400_3: busPtr = new B_32_R5_400_3(len, pins[0]); break;
case I_32_R6_400_3: busPtr = new B_32_R6_400_3(len, pins[0]); break;
case I_32_R7_400_3: busPtr = new B_32_R7_400_3(len, pins[0]); break;
#endif
case I_32_RN_400_3: busPtr = new B_32_RN_400_3(len, pins[0], (NeoBusChannel)channel); break;
case I_32_I0_400_3: busPtr = new B_32_I0_400_3(len, pins[0]); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_400_3: busPtr = new B_32_I1_400_3(len, pins[0]); break;
case I_32_R0_TM1_4: busPtr = new B_32_R0_TM1_4(len, pins[0]); break;
case I_32_R1_TM1_4: busPtr = new B_32_R1_TM1_4(len, pins[0]); break;
case I_32_R2_TM1_4: busPtr = new B_32_R2_TM1_4(len, pins[0]); break;
case I_32_R3_TM1_4: busPtr = new B_32_R3_TM1_4(len, pins[0]); break;
case I_32_R4_TM1_4: busPtr = new B_32_R4_TM1_4(len, pins[0]); break;
case I_32_R5_TM1_4: busPtr = new B_32_R5_TM1_4(len, pins[0]); break;
case I_32_R6_TM1_4: busPtr = new B_32_R6_TM1_4(len, pins[0]); break;
case I_32_R7_TM1_4: busPtr = new B_32_R7_TM1_4(len, pins[0]); break;
#endif
case I_32_RN_TM1_4: busPtr = new B_32_RN_TM1_4(len, pins[0], (NeoBusChannel)channel); break;
case I_32_I0_TM1_4: busPtr = new B_32_I0_TM1_4(len, pins[0]); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_TM1_4: busPtr = new B_32_I1_TM1_4(len, pins[0]); break;
#endif
#endif
// for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat)
case I_HS_DOT_3: busPtr = new B_HS_DOT_3(len, pins[1], pins[0]); break;
@@ -376,46 +291,26 @@ class PolyBus {
case I_8266_BB_TM1_4: (static_cast<B_8266_BB_TM1_4*>(busPtr))->Show(); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_R0_NEO_3: (static_cast<B_32_R0_NEO_3*>(busPtr))->Show(); break;
case I_32_R1_NEO_3: (static_cast<B_32_R1_NEO_3*>(busPtr))->Show(); break;
case I_32_R2_NEO_3: (static_cast<B_32_R2_NEO_3*>(busPtr))->Show(); break;
case I_32_R3_NEO_3: (static_cast<B_32_R3_NEO_3*>(busPtr))->Show(); break;
case I_32_R4_NEO_3: (static_cast<B_32_R4_NEO_3*>(busPtr))->Show(); break;
case I_32_R5_NEO_3: (static_cast<B_32_R5_NEO_3*>(busPtr))->Show(); break;
case I_32_R6_NEO_3: (static_cast<B_32_R6_NEO_3*>(busPtr))->Show(); break;
case I_32_R7_NEO_3: (static_cast<B_32_R7_NEO_3*>(busPtr))->Show(); break;
case I_32_RN_NEO_3: (static_cast<B_32_RN_NEO_3*>(busPtr))->Show(); break;
case I_32_I0_NEO_3: (static_cast<B_32_I0_NEO_3*>(busPtr))->Show(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_3: (static_cast<B_32_I1_NEO_3*>(busPtr))->Show(); break;
case I_32_R0_NEO_4: (static_cast<B_32_R0_NEO_4*>(busPtr))->Show(); break;
case I_32_R1_NEO_4: (static_cast<B_32_R1_NEO_4*>(busPtr))->Show(); break;
case I_32_R2_NEO_4: (static_cast<B_32_R2_NEO_4*>(busPtr))->Show(); break;
case I_32_R3_NEO_4: (static_cast<B_32_R3_NEO_4*>(busPtr))->Show(); break;
case I_32_R4_NEO_4: (static_cast<B_32_R4_NEO_4*>(busPtr))->Show(); break;
case I_32_R5_NEO_4: (static_cast<B_32_R5_NEO_4*>(busPtr))->Show(); break;
case I_32_R6_NEO_4: (static_cast<B_32_R6_NEO_4*>(busPtr))->Show(); break;
case I_32_R7_NEO_4: (static_cast<B_32_R7_NEO_4*>(busPtr))->Show(); break;
#endif
case I_32_RN_NEO_4: (static_cast<B_32_RN_NEO_4*>(busPtr))->Show(); break;
case I_32_I0_NEO_4: (static_cast<B_32_I0_NEO_4*>(busPtr))->Show(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_4: (static_cast<B_32_I1_NEO_4*>(busPtr))->Show(); break;
case I_32_R0_400_3: (static_cast<B_32_R0_400_3*>(busPtr))->Show(); break;
case I_32_R1_400_3: (static_cast<B_32_R1_400_3*>(busPtr))->Show(); break;
case I_32_R2_400_3: (static_cast<B_32_R2_400_3*>(busPtr))->Show(); break;
case I_32_R3_400_3: (static_cast<B_32_R3_400_3*>(busPtr))->Show(); break;
case I_32_R4_400_3: (static_cast<B_32_R4_400_3*>(busPtr))->Show(); break;
case I_32_R5_400_3: (static_cast<B_32_R5_400_3*>(busPtr))->Show(); break;
case I_32_R6_400_3: (static_cast<B_32_R6_400_3*>(busPtr))->Show(); break;
case I_32_R7_400_3: (static_cast<B_32_R7_400_3*>(busPtr))->Show(); break;
#endif
case I_32_RN_400_3: (static_cast<B_32_RN_400_3*>(busPtr))->Show(); break;
case I_32_I0_400_3: (static_cast<B_32_I0_400_3*>(busPtr))->Show(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_400_3: (static_cast<B_32_I1_400_3*>(busPtr))->Show(); break;
case I_32_R0_TM1_4: (static_cast<B_32_R0_TM1_4*>(busPtr))->Show(); break;
case I_32_R1_TM1_4: (static_cast<B_32_R1_TM1_4*>(busPtr))->Show(); break;
case I_32_R2_TM1_4: (static_cast<B_32_R2_TM1_4*>(busPtr))->Show(); break;
case I_32_R3_TM1_4: (static_cast<B_32_R3_TM1_4*>(busPtr))->Show(); break;
case I_32_R4_TM1_4: (static_cast<B_32_R4_TM1_4*>(busPtr))->Show(); break;
case I_32_R5_TM1_4: (static_cast<B_32_R5_TM1_4*>(busPtr))->Show(); break;
case I_32_R6_TM1_4: (static_cast<B_32_R6_TM1_4*>(busPtr))->Show(); break;
case I_32_R7_TM1_4: (static_cast<B_32_R7_TM1_4*>(busPtr))->Show(); break;
#endif
case I_32_RN_TM1_4: (static_cast<B_32_RN_TM1_4*>(busPtr))->Show(); break;
case I_32_I0_TM1_4: (static_cast<B_32_I0_TM1_4*>(busPtr))->Show(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_TM1_4: (static_cast<B_32_I1_TM1_4*>(busPtr))->Show(); break;
#endif
#endif
case I_HS_DOT_3: (static_cast<B_HS_DOT_3*>(busPtr))->Show(); break;
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->Show(); break;
@@ -449,46 +344,26 @@ class PolyBus {
case I_8266_BB_TM1_4: return (static_cast<B_8266_BB_TM1_4*>(busPtr))->CanShow(); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_R0_NEO_3: return (static_cast<B_32_R0_NEO_3*>(busPtr))->CanShow(); break;
case I_32_R1_NEO_3: return (static_cast<B_32_R1_NEO_3*>(busPtr))->CanShow(); break;
case I_32_R2_NEO_3: return (static_cast<B_32_R2_NEO_3*>(busPtr))->CanShow(); break;
case I_32_R3_NEO_3: return (static_cast<B_32_R3_NEO_3*>(busPtr))->CanShow(); break;
case I_32_R4_NEO_3: return (static_cast<B_32_R4_NEO_3*>(busPtr))->CanShow(); break;
case I_32_R5_NEO_3: return (static_cast<B_32_R5_NEO_3*>(busPtr))->CanShow(); break;
case I_32_R6_NEO_3: return (static_cast<B_32_R6_NEO_3*>(busPtr))->CanShow(); break;
case I_32_R7_NEO_3: return (static_cast<B_32_R7_NEO_3*>(busPtr))->CanShow(); break;
case I_32_RN_NEO_3: return (static_cast<B_32_RN_NEO_3*>(busPtr))->CanShow(); break;
case I_32_I0_NEO_3: return (static_cast<B_32_I0_NEO_3*>(busPtr))->CanShow(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_3: return (static_cast<B_32_I1_NEO_3*>(busPtr))->CanShow(); break;
case I_32_R0_NEO_4: return (static_cast<B_32_R0_NEO_4*>(busPtr))->CanShow(); break;
case I_32_R1_NEO_4: return (static_cast<B_32_R1_NEO_4*>(busPtr))->CanShow(); break;
case I_32_R2_NEO_4: return (static_cast<B_32_R2_NEO_4*>(busPtr))->CanShow(); break;
case I_32_R3_NEO_4: return (static_cast<B_32_R3_NEO_4*>(busPtr))->CanShow(); break;
case I_32_R4_NEO_4: return (static_cast<B_32_R4_NEO_4*>(busPtr))->CanShow(); break;
case I_32_R5_NEO_4: return (static_cast<B_32_R5_NEO_4*>(busPtr))->CanShow(); break;
case I_32_R6_NEO_4: return (static_cast<B_32_R6_NEO_4*>(busPtr))->CanShow(); break;
case I_32_R7_NEO_4: return (static_cast<B_32_R7_NEO_4*>(busPtr))->CanShow(); break;
#endif
case I_32_RN_NEO_4: return (static_cast<B_32_RN_NEO_4*>(busPtr))->CanShow(); break;
case I_32_I0_NEO_4: return (static_cast<B_32_I0_NEO_4*>(busPtr))->CanShow(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_4: return (static_cast<B_32_I1_NEO_4*>(busPtr))->CanShow(); break;
case I_32_R0_400_3: return (static_cast<B_32_R0_400_3*>(busPtr))->CanShow(); break;
case I_32_R1_400_3: return (static_cast<B_32_R1_400_3*>(busPtr))->CanShow(); break;
case I_32_R2_400_3: return (static_cast<B_32_R2_400_3*>(busPtr))->CanShow(); break;
case I_32_R3_400_3: return (static_cast<B_32_R3_400_3*>(busPtr))->CanShow(); break;
case I_32_R4_400_3: return (static_cast<B_32_R4_400_3*>(busPtr))->CanShow(); break;
case I_32_R5_400_3: return (static_cast<B_32_R5_400_3*>(busPtr))->CanShow(); break;
case I_32_R6_400_3: return (static_cast<B_32_R6_400_3*>(busPtr))->CanShow(); break;
case I_32_R7_400_3: return (static_cast<B_32_R7_400_3*>(busPtr))->CanShow(); break;
#endif
case I_32_RN_400_3: return (static_cast<B_32_RN_400_3*>(busPtr))->CanShow(); break;
case I_32_I0_400_3: return (static_cast<B_32_I0_400_3*>(busPtr))->CanShow(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_400_3: return (static_cast<B_32_I1_400_3*>(busPtr))->CanShow(); break;
case I_32_R0_TM1_4: return (static_cast<B_32_R0_TM1_4*>(busPtr))->CanShow(); break;
case I_32_R1_TM1_4: return (static_cast<B_32_R1_TM1_4*>(busPtr))->CanShow(); break;
case I_32_R2_TM1_4: return (static_cast<B_32_R2_TM1_4*>(busPtr))->CanShow(); break;
case I_32_R3_TM1_4: return (static_cast<B_32_R3_TM1_4*>(busPtr))->CanShow(); break;
case I_32_R4_TM1_4: return (static_cast<B_32_R4_TM1_4*>(busPtr))->CanShow(); break;
case I_32_R5_TM1_4: return (static_cast<B_32_R5_TM1_4*>(busPtr))->CanShow(); break;
case I_32_R6_TM1_4: return (static_cast<B_32_R6_TM1_4*>(busPtr))->CanShow(); break;
case I_32_R7_TM1_4: return (static_cast<B_32_R7_TM1_4*>(busPtr))->CanShow(); break;
#endif
case I_32_RN_TM1_4: return (static_cast<B_32_RN_TM1_4*>(busPtr))->CanShow(); break;
case I_32_I0_TM1_4: return (static_cast<B_32_I0_TM1_4*>(busPtr))->CanShow(); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_TM1_4: return (static_cast<B_32_I1_TM1_4*>(busPtr))->CanShow(); break;
#endif
#endif
case I_HS_DOT_3: return (static_cast<B_HS_DOT_3*>(busPtr))->CanShow(); break;
case I_SS_DOT_3: return (static_cast<B_SS_DOT_3*>(busPtr))->CanShow(); break;
@@ -546,46 +421,26 @@ class PolyBus {
case I_8266_BB_TM1_4: (static_cast<B_8266_BB_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_R0_NEO_3: (static_cast<B_32_R0_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R1_NEO_3: (static_cast<B_32_R1_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R2_NEO_3: (static_cast<B_32_R2_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R3_NEO_3: (static_cast<B_32_R3_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R4_NEO_3: (static_cast<B_32_R4_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R5_NEO_3: (static_cast<B_32_R5_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R6_NEO_3: (static_cast<B_32_R6_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R7_NEO_3: (static_cast<B_32_R7_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_RN_NEO_3: (static_cast<B_32_RN_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_I0_NEO_3: (static_cast<B_32_I0_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_3: (static_cast<B_32_I1_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R0_NEO_4: (static_cast<B_32_R0_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R1_NEO_4: (static_cast<B_32_R1_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R2_NEO_4: (static_cast<B_32_R2_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R3_NEO_4: (static_cast<B_32_R3_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R4_NEO_4: (static_cast<B_32_R4_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R5_NEO_4: (static_cast<B_32_R5_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R6_NEO_4: (static_cast<B_32_R6_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R7_NEO_4: (static_cast<B_32_R7_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
#endif
case I_32_RN_NEO_4: (static_cast<B_32_RN_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_I0_NEO_4: (static_cast<B_32_I0_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_4: (static_cast<B_32_I1_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R0_400_3: (static_cast<B_32_R0_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R1_400_3: (static_cast<B_32_R1_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R2_400_3: (static_cast<B_32_R2_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R3_400_3: (static_cast<B_32_R3_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R4_400_3: (static_cast<B_32_R4_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R5_400_3: (static_cast<B_32_R5_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R6_400_3: (static_cast<B_32_R6_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R7_400_3: (static_cast<B_32_R7_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
#endif
case I_32_RN_400_3: (static_cast<B_32_RN_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_I0_400_3: (static_cast<B_32_I0_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_400_3: (static_cast<B_32_I1_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_32_R0_TM1_4: (static_cast<B_32_R0_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R1_TM1_4: (static_cast<B_32_R1_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R2_TM1_4: (static_cast<B_32_R2_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R3_TM1_4: (static_cast<B_32_R3_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R4_TM1_4: (static_cast<B_32_R4_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R5_TM1_4: (static_cast<B_32_R5_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R6_TM1_4: (static_cast<B_32_R6_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_R7_TM1_4: (static_cast<B_32_R7_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
#endif
case I_32_RN_TM1_4: (static_cast<B_32_RN_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
case I_32_I0_TM1_4: (static_cast<B_32_I0_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_TM1_4: (static_cast<B_32_I1_TM1_4*>(busPtr))->SetPixelColor(pix, col); break;
#endif
#endif
case I_HS_DOT_3: (static_cast<B_HS_DOT_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break;
@@ -619,46 +474,26 @@ class PolyBus {
case I_8266_BB_TM1_4: (static_cast<B_8266_BB_TM1_4*>(busPtr))->SetBrightness(b); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_R0_NEO_3: (static_cast<B_32_R0_NEO_3*>(busPtr))->SetBrightness(b); break;
case I_32_R1_NEO_3: (static_cast<B_32_R1_NEO_3*>(busPtr))->SetBrightness(b); break;
case I_32_R2_NEO_3: (static_cast<B_32_R2_NEO_3*>(busPtr))->SetBrightness(b); break;
case I_32_R3_NEO_3: (static_cast<B_32_R3_NEO_3*>(busPtr))->SetBrightness(b); break;
case I_32_R4_NEO_3: (static_cast<B_32_R4_NEO_3*>(busPtr))->SetBrightness(b); break;
case I_32_R5_NEO_3: (static_cast<B_32_R5_NEO_3*>(busPtr))->SetBrightness(b); break;
case I_32_R6_NEO_3: (static_cast<B_32_R6_NEO_3*>(busPtr))->SetBrightness(b); break;
case I_32_R7_NEO_3: (static_cast<B_32_R7_NEO_3*>(busPtr))->SetBrightness(b); break;
case I_32_RN_NEO_3: (static_cast<B_32_RN_NEO_3*>(busPtr))->SetBrightness(b); break;
case I_32_I0_NEO_3: (static_cast<B_32_I0_NEO_3*>(busPtr))->SetBrightness(b); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_3: (static_cast<B_32_I1_NEO_3*>(busPtr))->SetBrightness(b); break;
case I_32_R0_NEO_4: (static_cast<B_32_R0_NEO_4*>(busPtr))->SetBrightness(b); break;
case I_32_R1_NEO_4: (static_cast<B_32_R1_NEO_4*>(busPtr))->SetBrightness(b); break;
case I_32_R2_NEO_4: (static_cast<B_32_R2_NEO_4*>(busPtr))->SetBrightness(b); break;
case I_32_R3_NEO_4: (static_cast<B_32_R3_NEO_4*>(busPtr))->SetBrightness(b); break;
case I_32_R4_NEO_4: (static_cast<B_32_R4_NEO_4*>(busPtr))->SetBrightness(b); break;
case I_32_R5_NEO_4: (static_cast<B_32_R5_NEO_4*>(busPtr))->SetBrightness(b); break;
case I_32_R6_NEO_4: (static_cast<B_32_R6_NEO_4*>(busPtr))->SetBrightness(b); break;
case I_32_R7_NEO_4: (static_cast<B_32_R7_NEO_4*>(busPtr))->SetBrightness(b); break;
#endif
case I_32_RN_NEO_4: (static_cast<B_32_RN_NEO_4*>(busPtr))->SetBrightness(b); break;
case I_32_I0_NEO_4: (static_cast<B_32_I0_NEO_4*>(busPtr))->SetBrightness(b); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_4: (static_cast<B_32_I1_NEO_4*>(busPtr))->SetBrightness(b); break;
case I_32_R0_400_3: (static_cast<B_32_R0_400_3*>(busPtr))->SetBrightness(b); break;
case I_32_R1_400_3: (static_cast<B_32_R1_400_3*>(busPtr))->SetBrightness(b); break;
case I_32_R2_400_3: (static_cast<B_32_R2_400_3*>(busPtr))->SetBrightness(b); break;
case I_32_R3_400_3: (static_cast<B_32_R3_400_3*>(busPtr))->SetBrightness(b); break;
case I_32_R4_400_3: (static_cast<B_32_R4_400_3*>(busPtr))->SetBrightness(b); break;
case I_32_R5_400_3: (static_cast<B_32_R5_400_3*>(busPtr))->SetBrightness(b); break;
case I_32_R6_400_3: (static_cast<B_32_R6_400_3*>(busPtr))->SetBrightness(b); break;
case I_32_R7_400_3: (static_cast<B_32_R7_400_3*>(busPtr))->SetBrightness(b); break;
#endif
case I_32_RN_400_3: (static_cast<B_32_RN_400_3*>(busPtr))->SetBrightness(b); break;
case I_32_I0_400_3: (static_cast<B_32_I0_400_3*>(busPtr))->SetBrightness(b); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_400_3: (static_cast<B_32_I1_400_3*>(busPtr))->SetBrightness(b); break;
case I_32_R0_TM1_4: (static_cast<B_32_R0_TM1_4*>(busPtr))->SetBrightness(b); break;
case I_32_R1_TM1_4: (static_cast<B_32_R1_TM1_4*>(busPtr))->SetBrightness(b); break;
case I_32_R2_TM1_4: (static_cast<B_32_R2_TM1_4*>(busPtr))->SetBrightness(b); break;
case I_32_R3_TM1_4: (static_cast<B_32_R3_TM1_4*>(busPtr))->SetBrightness(b); break;
case I_32_R4_TM1_4: (static_cast<B_32_R4_TM1_4*>(busPtr))->SetBrightness(b); break;
case I_32_R5_TM1_4: (static_cast<B_32_R5_TM1_4*>(busPtr))->SetBrightness(b); break;
case I_32_R6_TM1_4: (static_cast<B_32_R6_TM1_4*>(busPtr))->SetBrightness(b); break;
case I_32_R7_TM1_4: (static_cast<B_32_R7_TM1_4*>(busPtr))->SetBrightness(b); break;
#endif
case I_32_RN_TM1_4: (static_cast<B_32_RN_TM1_4*>(busPtr))->SetBrightness(b); break;
case I_32_I0_TM1_4: (static_cast<B_32_I0_TM1_4*>(busPtr))->SetBrightness(b); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_TM1_4: (static_cast<B_32_I1_TM1_4*>(busPtr))->SetBrightness(b); break;
#endif
#endif
case I_HS_DOT_3: (static_cast<B_HS_DOT_3*>(busPtr))->SetBrightness(b); break;
case I_SS_DOT_3: (static_cast<B_SS_DOT_3*>(busPtr))->SetBrightness(b); break;
@@ -693,46 +528,26 @@ class PolyBus {
case I_8266_BB_TM1_4: col = (static_cast<B_8266_BB_TM1_4*>(busPtr))->GetPixelColor(pix); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_R0_NEO_3: col = (static_cast<B_32_R0_NEO_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R1_NEO_3: col = (static_cast<B_32_R1_NEO_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R2_NEO_3: col = (static_cast<B_32_R2_NEO_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R3_NEO_3: col = (static_cast<B_32_R3_NEO_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R4_NEO_3: col = (static_cast<B_32_R4_NEO_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R5_NEO_3: col = (static_cast<B_32_R5_NEO_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R6_NEO_3: col = (static_cast<B_32_R6_NEO_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R7_NEO_3: col = (static_cast<B_32_R7_NEO_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_RN_NEO_3: col = (static_cast<B_32_RN_NEO_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_I0_NEO_3: col = (static_cast<B_32_I0_NEO_3*>(busPtr))->GetPixelColor(pix); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_3: col = (static_cast<B_32_I1_NEO_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R0_NEO_4: col = (static_cast<B_32_R0_NEO_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R1_NEO_4: col = (static_cast<B_32_R1_NEO_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R2_NEO_4: col = (static_cast<B_32_R2_NEO_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R3_NEO_4: col = (static_cast<B_32_R3_NEO_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R4_NEO_4: col = (static_cast<B_32_R4_NEO_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R5_NEO_4: col = (static_cast<B_32_R5_NEO_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R6_NEO_4: col = (static_cast<B_32_R6_NEO_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R7_NEO_4: col = (static_cast<B_32_R7_NEO_4*>(busPtr))->GetPixelColor(pix); break;
#endif
case I_32_RN_NEO_4: col = (static_cast<B_32_RN_NEO_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_I0_NEO_4: col = (static_cast<B_32_I0_NEO_4*>(busPtr))->GetPixelColor(pix); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_4: col = (static_cast<B_32_I1_NEO_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R0_400_3: col = (static_cast<B_32_R0_400_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R1_400_3: col = (static_cast<B_32_R1_400_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R2_400_3: col = (static_cast<B_32_R2_400_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R3_400_3: col = (static_cast<B_32_R3_400_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R4_400_3: col = (static_cast<B_32_R4_400_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R5_400_3: col = (static_cast<B_32_R5_400_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R6_400_3: col = (static_cast<B_32_R6_400_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R7_400_3: col = (static_cast<B_32_R7_400_3*>(busPtr))->GetPixelColor(pix); break;
#endif
case I_32_RN_400_3: col = (static_cast<B_32_RN_400_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_I0_400_3: col = (static_cast<B_32_I0_400_3*>(busPtr))->GetPixelColor(pix); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_400_3: col = (static_cast<B_32_I1_400_3*>(busPtr))->GetPixelColor(pix); break;
case I_32_R0_TM1_4: col = (static_cast<B_32_R0_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R1_TM1_4: col = (static_cast<B_32_R1_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R2_TM1_4: col = (static_cast<B_32_R2_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R3_TM1_4: col = (static_cast<B_32_R3_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R4_TM1_4: col = (static_cast<B_32_R4_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R5_TM1_4: col = (static_cast<B_32_R5_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R6_TM1_4: col = (static_cast<B_32_R6_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_R7_TM1_4: col = (static_cast<B_32_R7_TM1_4*>(busPtr))->GetPixelColor(pix); break;
#endif
case I_32_RN_TM1_4: col = (static_cast<B_32_RN_TM1_4*>(busPtr))->GetPixelColor(pix); break;
case I_32_I0_TM1_4: col = (static_cast<B_32_I0_TM1_4*>(busPtr))->GetPixelColor(pix); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_TM1_4: col = (static_cast<B_32_I1_TM1_4*>(busPtr))->GetPixelColor(pix); break;
#endif
#endif
case I_HS_DOT_3: col = (static_cast<B_HS_DOT_3*>(busPtr))->GetPixelColor(pix); break;
case I_SS_DOT_3: col = (static_cast<B_SS_DOT_3*>(busPtr))->GetPixelColor(pix); break;
@@ -784,46 +599,26 @@ class PolyBus {
case I_8266_BB_TM1_4: delete (static_cast<B_8266_BB_TM1_4*>(busPtr)); break;
#endif
#ifdef ARDUINO_ARCH_ESP32
case I_32_R0_NEO_3: delete (static_cast<B_32_R0_NEO_3*>(busPtr)); break;
case I_32_R1_NEO_3: delete (static_cast<B_32_R1_NEO_3*>(busPtr)); break;
case I_32_R2_NEO_3: delete (static_cast<B_32_R2_NEO_3*>(busPtr)); break;
case I_32_R3_NEO_3: delete (static_cast<B_32_R3_NEO_3*>(busPtr)); break;
case I_32_R4_NEO_3: delete (static_cast<B_32_R4_NEO_3*>(busPtr)); break;
case I_32_R5_NEO_3: delete (static_cast<B_32_R5_NEO_3*>(busPtr)); break;
case I_32_R6_NEO_3: delete (static_cast<B_32_R6_NEO_3*>(busPtr)); break;
case I_32_R7_NEO_3: delete (static_cast<B_32_R7_NEO_3*>(busPtr)); break;
case I_32_RN_NEO_3: delete (static_cast<B_32_RN_NEO_3*>(busPtr)); break;
case I_32_I0_NEO_3: delete (static_cast<B_32_I0_NEO_3*>(busPtr)); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_3: delete (static_cast<B_32_I1_NEO_3*>(busPtr)); break;
case I_32_R0_NEO_4: delete (static_cast<B_32_R0_NEO_4*>(busPtr)); break;
case I_32_R1_NEO_4: delete (static_cast<B_32_R1_NEO_4*>(busPtr)); break;
case I_32_R2_NEO_4: delete (static_cast<B_32_R2_NEO_4*>(busPtr)); break;
case I_32_R3_NEO_4: delete (static_cast<B_32_R3_NEO_4*>(busPtr)); break;
case I_32_R4_NEO_4: delete (static_cast<B_32_R4_NEO_4*>(busPtr)); break;
case I_32_R5_NEO_4: delete (static_cast<B_32_R5_NEO_4*>(busPtr)); break;
case I_32_R6_NEO_4: delete (static_cast<B_32_R6_NEO_4*>(busPtr)); break;
case I_32_R7_NEO_4: delete (static_cast<B_32_R7_NEO_4*>(busPtr)); break;
#endif
case I_32_RN_NEO_4: delete (static_cast<B_32_RN_NEO_4*>(busPtr)); break;
case I_32_I0_NEO_4: delete (static_cast<B_32_I0_NEO_4*>(busPtr)); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_NEO_4: delete (static_cast<B_32_I1_NEO_4*>(busPtr)); break;
case I_32_R0_400_3: delete (static_cast<B_32_R0_400_3*>(busPtr)); break;
case I_32_R1_400_3: delete (static_cast<B_32_R1_400_3*>(busPtr)); break;
case I_32_R2_400_3: delete (static_cast<B_32_R2_400_3*>(busPtr)); break;
case I_32_R3_400_3: delete (static_cast<B_32_R3_400_3*>(busPtr)); break;
case I_32_R4_400_3: delete (static_cast<B_32_R4_400_3*>(busPtr)); break;
case I_32_R5_400_3: delete (static_cast<B_32_R5_400_3*>(busPtr)); break;
case I_32_R6_400_3: delete (static_cast<B_32_R6_400_3*>(busPtr)); break;
case I_32_R7_400_3: delete (static_cast<B_32_R7_400_3*>(busPtr)); break;
#endif
case I_32_RN_400_3: delete (static_cast<B_32_RN_400_3*>(busPtr)); break;
case I_32_I0_400_3: delete (static_cast<B_32_I0_400_3*>(busPtr)); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_400_3: delete (static_cast<B_32_I1_400_3*>(busPtr)); break;
case I_32_R0_TM1_4: delete (static_cast<B_32_R0_TM1_4*>(busPtr)); break;
case I_32_R1_TM1_4: delete (static_cast<B_32_R1_TM1_4*>(busPtr)); break;
case I_32_R2_TM1_4: delete (static_cast<B_32_R2_TM1_4*>(busPtr)); break;
case I_32_R3_TM1_4: delete (static_cast<B_32_R3_TM1_4*>(busPtr)); break;
case I_32_R4_TM1_4: delete (static_cast<B_32_R4_TM1_4*>(busPtr)); break;
case I_32_R5_TM1_4: delete (static_cast<B_32_R5_TM1_4*>(busPtr)); break;
case I_32_R6_TM1_4: delete (static_cast<B_32_R6_TM1_4*>(busPtr)); break;
case I_32_R7_TM1_4: delete (static_cast<B_32_R7_TM1_4*>(busPtr)); break;
#endif
case I_32_RN_TM1_4: delete (static_cast<B_32_RN_TM1_4*>(busPtr)); break;
case I_32_I0_TM1_4: delete (static_cast<B_32_I0_TM1_4*>(busPtr)); break;
#ifndef CONFIG_IDF_TARGET_ESP32S2
case I_32_I1_TM1_4: delete (static_cast<B_32_I1_TM1_4*>(busPtr)); break;
#endif
#endif
case I_HS_DOT_3: delete (static_cast<B_HS_DOT_3*>(busPtr)); break;
case I_SS_DOT_3: delete (static_cast<B_SS_DOT_3*>(busPtr)); break;
@@ -872,18 +667,24 @@ class PolyBus {
return I_8266_U0_TM1_4 + offset;
}
#else //ESP32
uint8_t offset = num; //RMT bus # == bus index in BusManager
if (offset > 9) return I_NONE;
uint8_t offset = 0; //0 = RMT (num 0-7) 8 = I2S0 9 = I2S1
#ifndef CONFIG_IDF_TARGET_ESP32S2
if (num > 9) return I_NONE;
if (num > 7) offset = num -7;
#else //ESP32 S2 only has 4 RMT channels
if (num > 5) return I_NONE;
if (num > 4) offset = num -4;
#endif
switch (busType) {
case TYPE_WS2812_RGB:
case TYPE_WS2812_WWA:
return I_32_R0_NEO_3 + offset;
return I_32_RN_NEO_3 + offset;
case TYPE_SK6812_RGBW:
return I_32_R0_NEO_4 + offset;
return I_32_RN_NEO_4 + offset;
case TYPE_WS2811_400KHZ:
return I_32_R0_400_3 + offset;
return I_32_RN_400_3 + offset;
case TYPE_TM1814:
return I_32_R0_TM1_4 + offset;
return I_32_RN_TM1_4 + offset;
}
#endif
}

View File

@@ -5,15 +5,67 @@
*/
#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
static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage
void shortPressAction(uint8_t b)
{
if (!macroButton[b])
{
toggleOnOff();
colorUpdated(NOTIFIER_CALL_MODE_BUTTON);
if (!macroButton[b]) {
switch (b) {
case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break;
default: ++effectCurrent %= strip.getModeCount(); colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
applyPreset(macroButton[b]);
applyPreset(macroButton[b], CALL_MODE_BUTTON);
}
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "short");
}
}
void longPressAction(uint8_t b)
{
if (!macroLongPress[b]) {
switch (b) {
case 0: _setRandomColor(false,true); break;
default: bri += 8; colorUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action
}
} else {
applyPreset(macroLongPress[b], CALL_MODE_BUTTON);
}
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "long");
}
}
void doublePressAction(uint8_t b)
{
if (!macroDoublePress[b]) {
switch (b) {
//case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set
default: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
applyPreset(macroDoublePress[b], CALL_MODE_BUTTON);
}
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "double");
}
}
@@ -29,7 +81,7 @@ bool isButtonPressed(uint8_t i)
if (digitalRead(btnPin[i]) == LOW) return true;
break;
case BTN_TYPE_PUSH_ACT_HIGH:
case BTN_TYPE_SWITCH_ACT_HIGH:
case BTN_TYPE_PIR_SENSOR:
if (digitalRead(btnPin[i]) == HIGH) return true;
break;
case BTN_TYPE_TOUCH:
@@ -43,6 +95,7 @@ bool isButtonPressed(uint8_t i)
void handleSwitch(uint8_t b)
{
// isButtonPressed() handles inverted/noninverted logic
if (buttonPressedBefore[b] != isButtonPressed(b)) {
buttonPressedTime[b] = millis();
buttonPressedBefore[b] = !buttonPressedBefore[b];
@@ -51,17 +104,26 @@ void handleSwitch(uint8_t b)
if (buttonLongPressed[b] == buttonPressedBefore[b]) return;
if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
if (buttonPressedBefore[b]) { //LOW, falling edge, switch closed
if (macroButton[b]) applyPreset(macroButton[b]);
if (!buttonPressedBefore[b]) { // on -> off
if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON);
else { //turn on
if (!bri) {toggleOnOff(); colorUpdated(NOTIFIER_CALL_MODE_BUTTON);}
if (!bri) {toggleOnOff(); colorUpdated(CALL_MODE_BUTTON);}
}
} else { //HIGH, rising edge, switch opened
if (macroLongPress[b]) applyPreset(macroLongPress[b]);
} else { // off -> on
if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON);
else { //turn off
if (bri) {toggleOnOff(); colorUpdated(NOTIFIER_CALL_MODE_BUTTON);}
if (bri) {toggleOnOff(); colorUpdated(CALL_MODE_BUTTON);}
}
}
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b);
else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on");
}
buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
}
}
@@ -74,6 +136,9 @@ void handleAnalog(uint8_t b)
#else
uint16_t aRead = analogRead(btnPin[b]) >> 4; // convert 12bit read to 8bit
#endif
if (buttonType[b] == BTN_TYPE_ANALOG_INVERTED) aRead = 255 - aRead;
// remove noise & reduce frequency of UI updates
aRead &= 0xFC;
@@ -132,18 +197,14 @@ void handleAnalog(uint8_t b)
seg.setOption(SEG_OPTION_ON, 1);
}
// this will notify clients of update (websockets,mqtt,etc)
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
updateInterfaces(NOTIFIER_CALL_MODE_BUTTON);
updateInterfaces(CALL_MODE_BUTTON);
}
} else {
//TODO:
// we can either trigger a preset depending on the level (between short and long entries)
// or use it for RGBW direct control
}
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
colorUpdated(NOTIFIER_CALL_MODE_BUTTON);
colorUpdated(CALL_MODE_BUTTON);
}
void handleButton()
@@ -152,61 +213,64 @@ void handleButton()
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_NONE) continue;
if ((btnPin[b]<0 && !(buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED)) || buttonType[b] == BTN_TYPE_NONE) continue;
#else
if (btnPin[b]<0 || buttonType[b] == BTN_TYPE_NONE) continue;
#endif
if (buttonType[b] == BTN_TYPE_ANALOG && millis() - lastRead > 250) { // button is not a button but a potentiometer
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 (b+1 == WLED_MAX_BUTTONS) lastRead = millis();
handleAnalog(b); continue;
}
if (buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_SWITCH_ACT_HIGH) { //button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
//button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
if (buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_PIR_SENSOR) {
handleSwitch(b); continue;
}
//momentary button logic
if (isButtonPressed(b)) //pressed
{
if (isButtonPressed(b)) { //pressed
if (!buttonPressedBefore[b]) buttonPressedTime[b] = millis();
buttonPressedBefore[b] = true;
if (millis() - buttonPressedTime[b] > 600) //long press
{
if (!buttonLongPressed[b])
{
if (macroLongPress[b]) {applyPreset(macroLongPress[b]);}
else _setRandomColor(false,true);
buttonLongPressed[b] = true;
if (millis() - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press
if (!buttonLongPressed[b]) longPressAction(b);
else if (b) { //repeatable action (~3 times per s) on button > 0
longPressAction(b);
buttonPressedTime[b] = millis() - WLED_LONG_REPEATED_ACTION; //300ms
}
buttonLongPressed[b] = true;
}
}
else if (!isButtonPressed(b) && buttonPressedBefore[b]) //released
{
} else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
long dur = millis() - buttonPressedTime[b];
if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} //too short "press", debounce
bool doublePress = buttonWaitTime[b];
bool doublePress = buttonWaitTime[b]; //did we have a short press before?
buttonWaitTime[b] = 0;
if (dur > 6000 && b==0) //long press on button 0
{
if (b == 0 && dur > WLED_LONG_AP) { //long press on button 0 (when released)
WLED::instance().initAP(true);
}
else if (!buttonLongPressed[b]) { //short press
if (macroDoublePress[b])
{
if (doublePress) applyPreset(macroDoublePress[b]);
else buttonWaitTime[b] = millis();
} else shortPressAction(b);
} else if (!buttonLongPressed[b]) { //short press
if (b == 0 && !macroDoublePress[b]) { //don't wait for double press on button 0 if no double press macro set
shortPressAction(b);
} else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0)
if (doublePress) {
doublePressAction(b);
} else {
buttonWaitTime[b] = millis();
}
}
}
buttonPressedBefore[b] = false;
buttonLongPressed[b] = false;
}
if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > 450 && !buttonPressedBefore[b])
{
//if 350ms elapsed since last short press release it is a short press
if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > WLED_DOUBLE_PRESS && !buttonPressedBefore[b]) {
buttonWaitTime[b] = 0;
shortPressAction(b);
}
@@ -235,8 +299,11 @@ void handleIO()
#ifdef ESP8266
// turn off built-in LED if strip is turned off
// this will break digital bus so will need to be reinitialised on On
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
PinOwner ledPinOwner = pinManager.getPinOwner(LED_BUILTIN);
if (!strip.isOffRefreshRequred && (ledPinOwner == PinOwner::None || ledPinOwner == PinOwner::BusDigital)) {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
}
#endif
if (rlyPin>=0) {
pinMode(rlyPin, OUTPUT);

View File

@@ -1,4 +1,5 @@
#include "wled.h"
#include "wled_ethernet.h"
/*
* Serializes and parses the cfg.json and wsec.json settings files, stored in internal FS.
@@ -13,11 +14,19 @@ void getStringFromJson(char* dest, const char* src, size_t len) {
}
bool deserializeConfig(JsonObject doc, bool fromFS) {
bool needsSave = false;
//int rev_major = doc["rev"][0]; // 1
//int rev_minor = doc["rev"][1]; // 0
//long vid = doc[F("vid")]; // 2010020
#ifdef WLED_USE_ETHERNET
JsonObject ethernet = doc[F("eth")];
CJSON(ethernetType, ethernet["type"]);
// NOTE: Ethernet configuration takes priority over other use of pins
WLED::instance().initEthernet();
#endif
JsonObject id = doc["id"];
getStringFromJson(cmDNS, id[F("mdns")], 33);
getStringFromJson(serverDescription, id[F("name")], 33);
@@ -53,11 +62,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(apBehavior, ap[F("behav")]);
#ifdef WLED_USE_ETHERNET
JsonObject ethernet = doc[F("eth")];
CJSON(ethernetType, ethernet["type"]);
#endif
/*
JsonArray ap_ip = ap["ip"];
for (byte i = 0; i < 4; i++) {
@@ -71,20 +75,21 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject hw = doc[F("hw")];
// initialize LED pins and lengths prior to other HW
// initialize LED pins and lengths prior to other HW (except for ethernet)
JsonObject hw_led = hw[F("led")];
CJSON(ledCount, hw_led[F("total")]);
if (ledCount > MAX_LEDS) ledCount = MAX_LEDS;
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
CJSON(strip.rgbwMode, hw_led[F("rgbwm")]);
Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | Bus::getAutoWhiteMode());
CJSON(correctWB, hw_led["cct"]);
CJSON(cctFromRgb, hw_led[F("cr")]);
CJSON(strip.cctBlending, hw_led[F("cb")]);
Bus::setCCTBlend(strip.cctBlending);
JsonArray ins = hw_led["ins"];
if (fromFS || !ins.isNull()) {
uint8_t s = 0; //bus iterator
strip.isRgbw = false;
uint8_t s = 0; // bus iterator
busses.removeAll();
uint32_t mem = 0;
for (JsonObject elm : ins) {
@@ -99,44 +104,39 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (i>4) break;
}
uint16_t length = elm[F("len")];
if (length==0) continue;
uint16_t length = elm[F("len")] | 1;
uint8_t colorOrder = (int)elm[F("order")];
//only use skip from the first strip (this shouldn't have been in ins obj. but remains here for compatibility)
uint8_t skipFirst = elm[F("skip")];
uint16_t start = elm[F("start")] | 0;
if (start >= ledCount) continue;
//limit length of strip if it would exceed total configured LEDs
if (start + length > ledCount) length = ledCount - start;
uint16_t start = elm["start"] | 0;
if (length==0 || start + length > MAX_LEDS) continue; // zero length or we reached max. number of LEDs, just stop
uint8_t ledType = elm["type"] | TYPE_WS2812_RGB;
bool reversed = elm["rev"];
//RGBW mode is enabled if at least one of the strips is RGBW
strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(ledType));
//refresh is required to remain off if at least one of the strips requires the refresh.
strip.isOffRefreshRequred |= BusManager::isOffRefreshRequred(ledType);
bool refresh = elm["ref"] | false;
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
s++;
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
mem += busses.memUsage(bc);
if (mem <= MAX_LED_MEMORY) busses.add(bc);
mem += BusManager::memUsage(bc);
if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip()
}
strip.finalizeInit(ledCount);
// finalization done in beginStrip()
}
if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus
// read multiple button configuration
JsonArray hw_btn_ins = hw[F("btn")][F("ins")];
JsonObject btn_obj = hw["btn"];
JsonArray hw_btn_ins = btn_obj[F("ins")];
if (!hw_btn_ins.isNull()) {
uint8_t s = 0;
for (JsonObject btn : hw_btn_ins) {
CJSON(buttonType[s], btn["type"]);
int8_t pin = btn["pin"][0] | -1;
if (pin > -1 && pinManager.allocatePin(pin,false)) {
if (pin > -1 && pinManager.allocatePin(pin, false, PinOwner::Button)) {
btnPin[s] = pin;
pinMode(btnPin[s], INPUT_PULLUP);
} else {
btnPin[s] = -1;
}
JsonArray hw_btn_ins_0_macros = btn[F("macros")];
JsonArray hw_btn_ins_0_macros = btn["macros"];
CJSON(macroButton[s], hw_btn_ins_0_macros[0]);
CJSON(macroLongPress[s],hw_btn_ins_0_macros[1]);
CJSON(macroDoublePress[s], hw_btn_ins_0_macros[2]);
@@ -152,20 +152,27 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
} else {
// new install/missing configuration (button 0 has defaults)
if (fromFS)
for (uint8_t s=1; s<WLED_MAX_BUTTONS; s++) {
if (fromFS) {
// relies upon only being called once with fromFS == true, which is currently true.
uint8_t s = 0;
if (pinManager.allocatePin(btnPin[0], false, PinOwner::Button)) { // initialized to #define value BTNPIN, or zero if not defined(!)
++s; // do not clear default button if allocated successfully
}
for (; s<WLED_MAX_BUTTONS; s++) {
btnPin[s] = -1;
buttonType[s] = BTN_TYPE_NONE;
macroButton[s] = 0;
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
}
}
}
CJSON(touchThreshold,hw[F("btn")][F("tt")]);
CJSON(touchThreshold,btn_obj[F("tt")]);
CJSON(buttonPublishMqtt,btn_obj["mqtt"]);
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
if (hw_ir_pin > -2) {
if (pinManager.allocatePin(hw_ir_pin,false)) {
if (pinManager.allocatePin(hw_ir_pin, false, PinOwner::IR)) {
irPin = hw_ir_pin;
} else {
irPin = -1;
@@ -176,7 +183,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject relay = hw[F("relay")];
int hw_relay_pin = relay["pin"] | -2;
if (hw_relay_pin > -2) {
if (pinManager.allocatePin(hw_relay_pin,true)) {
if (pinManager.allocatePin(hw_relay_pin,true, PinOwner::Relay)) {
rlyPin = hw_relay_pin;
pinMode(rlyPin, OUTPUT);
} else {
@@ -192,9 +199,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject light = doc[F("light")];
CJSON(briMultiplier, light[F("scale-bri")]);
CJSON(strip.paletteBlend, light[F("pal-mode")]);
CJSON(autoSegments, light[F("aseg")]);
float light_gc_bri = light[F("gc")]["bri"];
float light_gc_col = light[F("gc")]["col"]; // 2.8
float light_gc_bri = light["gc"]["bri"];
float light_gc_col = light["gc"]["col"]; // 2.8
if (light_gc_bri > 1.5) strip.gammaCorrectBri = true;
else if (light_gc_bri > 0.5) strip.gammaCorrectBri = false;
if (light_gc_col > 1.5) strip.gammaCorrectCol = true;
@@ -213,10 +221,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (nightlightDelayMinsDefault != prev) nightlightDelayMins = nightlightDelayMinsDefault;
CJSON(nightlightTargetBri, light_nl[F("tbri")]);
CJSON(macroNl, light_nl[F("macro")]);
CJSON(macroNl, light_nl["macro"]);
JsonObject def = doc[F("def")];
CJSON(bootPreset, def[F("ps")]);
CJSON(bootPreset, def["ps"]);
CJSON(turnOnAtBoot, def["on"]); // true
CJSON(briS, def["bri"]); // 128
@@ -229,7 +237,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject if_sync_recv = if_sync["recv"];
CJSON(receiveNotificationBrightness, if_sync_recv["bri"]);
CJSON(receiveNotificationColor, if_sync_recv["col"]);
CJSON(receiveNotificationEffects, if_sync_recv[F("fx")]);
CJSON(receiveNotificationEffects, if_sync_recv["fx"]);
CJSON(receiveGroups, if_sync_recv["grp"]);
//! following line might be a problem if called after boot
receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects);
@@ -237,11 +246,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
prev = notifyDirectDefault;
CJSON(notifyDirectDefault, if_sync_send[F("dir")]);
if (notifyDirectDefault != prev) notifyDirect = notifyDirectDefault;
CJSON(notifyButton, if_sync_send[F("btn")]);
CJSON(notifyAlexa, if_sync_send[F("va")]);
CJSON(notifyHue, if_sync_send[F("hue")]);
CJSON(notifyMacro, if_sync_send[F("macro")]);
CJSON(notifyButton, if_sync_send["btn"]);
CJSON(notifyAlexa, if_sync_send["va"]);
CJSON(notifyHue, if_sync_send["hue"]);
CJSON(notifyMacro, if_sync_send["macro"]);
CJSON(notifyTwice, if_sync_send[F("twice")]);
CJSON(syncGroups, if_sync_send["grp"]);
JsonObject if_nodes = interfaces["nodes"];
CJSON(nodeListEnabled, if_nodes[F("list")]);
@@ -250,6 +260,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject if_live = interfaces["live"];
CJSON(receiveDirect, if_live["en"]);
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")]);
JsonObject if_live_dmx = if_live[F("dmx")];
@@ -264,10 +275,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false
CJSON(arlsOffset, if_live[F("offset")]); // 0
CJSON(alexaEnabled, interfaces[F("va")][F("alexa")]); // false
CJSON(alexaEnabled, interfaces["va"][F("alexa")]); // false
CJSON(macroAlexaOn, interfaces[F("va")][F("macros")][0]);
CJSON(macroAlexaOff, interfaces[F("va")][F("macros")][1]);
CJSON(macroAlexaOn, interfaces["va"]["macros"][0]);
CJSON(macroAlexaOff, interfaces["va"]["macros"][1]);
#ifndef WLED_DISABLE_BLYNK
const char* apikey = interfaces["blynk"][F("token")] | "Hidden";
@@ -294,7 +305,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
#endif
#ifndef WLED_DISABLE_HUESYNC
JsonObject if_hue = interfaces[F("hue")];
JsonObject if_hue = interfaces["hue"];
CJSON(huePollingEnabled, if_hue["en"]);
CJSON(huePollLightId, if_hue["id"]);
tdd = if_hue[F("iv")] | -1;
@@ -342,17 +353,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(countdownHour, cntdwn_goal[3]);
CJSON(countdownMin, cntdwn_goal[4]);
CJSON(countdownSec, cntdwn_goal[5]);
CJSON(macroCountdown, cntdwn[F("macro")]);
CJSON(macroCountdown, cntdwn["macro"]);
setCountdown();
JsonArray timers = tm[F("ins")];
JsonArray timers = tm["ins"];
uint8_t it = 0;
for (JsonObject timer : timers) {
if (it > 9) break;
if (it<8 && timer[F("hour")]==255) it=8; // hour==255 -> sunrise/sunset
CJSON(timerHours[it], timer[F("hour")]);
CJSON(timerMinutes[it], timer["min"]);
CJSON(timerMacro[it], timer[F("macro")]);
CJSON(timerMacro[it], timer["macro"]);
byte dowPrev = timerWeekday[it];
//note: act is currently only 0 or 1.
@@ -385,26 +396,25 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject dmx = doc["dmx"];
CJSON(DMXChannels, dmx[F("chan")]);
CJSON(DMXGap,dmx[F("gap")]);
CJSON(DMXStart, dmx[F("start")]);
CJSON(DMXStart, dmx["start"]);
CJSON(DMXStartLED,dmx[F("start-led")]);
JsonArray dmx_fixmap = dmx[F("fixmap")];
it = 0;
for (int i : dmx_fixmap) {
if (it > 14) break;
for (int i = 0; i < dmx_fixmap.size(); i++) {
if (i > 14) break;
CJSON(DMXFixtureMap[i],dmx_fixmap[i]);
it++;
}
CJSON(e131ProxyUniverse, dmx[F("e131proxy")]);
#endif
DEBUG_PRINTLN(F("Starting usermod config."));
JsonObject usermods_settings = doc["um"];
if (!usermods_settings.isNull()) {
bool allComplete = usermods.readFromConfig(usermods_settings);
if (!allComplete && fromFS) serializeConfig();
needsSave = !usermods.readFromConfig(usermods_settings);
}
if (fromFS) return false;
if (fromFS) return needsSave;
doReboot = doc[F("rb")] | doReboot;
return (doc["sv"] | true);
}
@@ -416,17 +426,27 @@ void deserializeConfigFromFS() {
return;
}
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(1)) return;
#endif
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
success = readObjectFromFile("/cfg.json", nullptr, &doc);
if (!success) { //if file does not exist, try reading from EEPROM
deEEPSettings();
releaseJSONBufferLock();
return;
}
deserializeConfig(doc.as<JsonObject>(), true);
// NOTE: This routine deserializes *and* applies the configuration
// Therefore, must also initialize ethernet from this function
bool needsSave = deserializeConfig(doc.as<JsonObject>(), true);
releaseJSONBufferLock();
if (needsSave) serializeConfig(); // usermods required new prameters
}
void serializeConfig() {
@@ -434,7 +454,11 @@ void serializeConfig() {
DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(2)) return;
#endif
JsonArray rev = doc.createNestedArray("rev");
rev.add(1); //major settings revision
@@ -480,20 +504,42 @@ void serializeConfig() {
JsonObject wifi = doc.createNestedObject("wifi");
wifi[F("sleep")] = !noWifiSleep;
wifi[F("phy")] = 1;
//wifi[F("phy")] = 1;
#ifdef WLED_USE_ETHERNET
JsonObject ethernet = doc.createNestedObject("eth");
ethernet["type"] = ethernetType;
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
JsonArray pins = ethernet.createNestedArray("pin");
for (uint8_t p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) pins.add(esp32_nonconfigurable_ethernet_pins[p].pin);
if (ethernetBoards[ethernetType].eth_power>=0) pins.add(ethernetBoards[ethernetType].eth_power);
if (ethernetBoards[ethernetType].eth_mdc>=0) pins.add(ethernetBoards[ethernetType].eth_mdc);
if (ethernetBoards[ethernetType].eth_mdio>=0) pins.add(ethernetBoards[ethernetType].eth_mdio);
switch (ethernetBoards[ethernetType].eth_clk_mode) {
case ETH_CLOCK_GPIO0_IN:
case ETH_CLOCK_GPIO0_OUT:
pins.add(0);
break;
case ETH_CLOCK_GPIO16_OUT:
pins.add(16);
break;
case ETH_CLOCK_GPIO17_OUT:
pins.add(17);
break;
}
}
#endif
JsonObject hw = doc.createNestedObject("hw");
JsonObject hw_led = hw.createNestedObject("led");
hw_led[F("total")] = ledCount;
hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade
hw_led[F("maxpwr")] = strip.ablMilliampsMax;
hw_led[F("ledma")] = strip.milliampsPerLed;
hw_led[F("rgbwm")] = strip.rgbwMode;
hw_led["cct"] = correctWB;
hw_led[F("cr")] = cctFromRgb;
hw_led[F("cb")] = strip.cctBlending;
hw_led[F("rgbwm")] = Bus::getAutoWhiteMode();
JsonArray hw_led_ins = hw_led.createNestedArray("ins");
@@ -501,7 +547,7 @@ void serializeConfig() {
Bus *bus = busses.getBus(s);
if (!bus || bus->getLength()==0) break;
JsonObject ins = hw_led_ins.createNestedObject();
ins[F("start")] = bus->getStart();
ins["start"] = bus->getStart();
ins[F("len")] = bus->getLength();
JsonArray ins_pin = ins.createNestedArray("pin");
uint8_t pins[5];
@@ -510,7 +556,9 @@ void serializeConfig() {
ins[F("order")] = bus->getColorOrder();
ins["rev"] = bus->reversed;
ins[F("skip")] = bus->skippedLeds();
ins["type"] = bus->getType();
ins["type"] = bus->getType() & 0x7F;
ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbw")] = bus->isRgbw();
}
// button(s)
@@ -518,33 +566,24 @@ void serializeConfig() {
hw_btn["max"] = WLED_MAX_BUTTONS; // just information about max number of buttons (not actually used)
JsonArray hw_btn_ins = hw_btn.createNestedArray("ins");
// there is always at least one button
JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject();
hw_btn_ins_0["type"] = buttonType[0];
JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin");
hw_btn_ins_0_pin.add(btnPin[0]);
JsonArray hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros");
hw_btn_ins_0_macros.add(macroButton[0]);
hw_btn_ins_0_macros.add(macroLongPress[0]);
hw_btn_ins_0_macros.add(macroDoublePress[0]);
// additional buttons
for (uint8_t i=1; i<WLED_MAX_BUTTONS; i++) {
//if (btnPin[i]<0) continue;
hw_btn_ins_0 = hw_btn_ins.createNestedObject();
// configuration for all buttons
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject();
hw_btn_ins_0["type"] = buttonType[i];
hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin");
JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin");
hw_btn_ins_0_pin.add(btnPin[i]);
hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros");
JsonArray hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros");
hw_btn_ins_0_macros.add(macroButton[i]);
hw_btn_ins_0_macros.add(macroLongPress[i]);
hw_btn_ins_0_macros.add(macroDoublePress[i]);
}
hw_btn[F("tt")] = touchThreshold;
hw_btn["mqtt"] = buttonPublishMqtt;
JsonObject hw_ir = hw.createNestedObject("ir");
hw_ir["pin"] = irPin;
hw_ir[F("type")] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled )
hw_ir["type"] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled )
JsonObject hw_relay = hw.createNestedObject(F("relay"));
hw_relay["pin"] = rlyPin;
@@ -556,6 +595,7 @@ void serializeConfig() {
JsonObject light = doc.createNestedObject(F("light"));
light[F("scale-bri")] = briMultiplier;
light[F("pal-mode")] = strip.paletteBlend;
light[F("aseg")] = autoSegments;
JsonObject light_gc = light.createNestedObject("gc");
light_gc["bri"] = (strip.gammaCorrectBri) ? 2.8 : 1.0;
@@ -570,10 +610,10 @@ void serializeConfig() {
light_nl[F("mode")] = nightlightMode;
light_nl["dur"] = nightlightDelayMinsDefault;
light_nl[F("tbri")] = nightlightTargetBri;
light_nl[F("macro")] = macroNl;
light_nl["macro"] = macroNl;
JsonObject def = doc.createNestedObject("def");
def[F("ps")] = bootPreset;
def["ps"] = bootPreset;
def["on"] = turnOnAtBoot;
def["bri"] = briS;
@@ -587,14 +627,16 @@ void serializeConfig() {
if_sync_recv["bri"] = receiveNotificationBrightness;
if_sync_recv["col"] = receiveNotificationColor;
if_sync_recv["fx"] = receiveNotificationEffects;
if_sync_recv["grp"] = receiveGroups;
JsonObject if_sync_send = if_sync.createNestedObject("send");
if_sync_send[F("dir")] = notifyDirect;
if_sync_send[F("btn")] = notifyButton;
if_sync_send[F("va")] = notifyAlexa;
if_sync_send[F("hue")] = notifyHue;
if_sync_send[F("macro")] = notifyMacro;
if_sync_send["btn"] = notifyButton;
if_sync_send["va"] = notifyAlexa;
if_sync_send["hue"] = notifyHue;
if_sync_send["macro"] = notifyMacro;
if_sync_send[F("twice")] = notifyTwice;
if_sync_send["grp"] = syncGroups;
JsonObject if_nodes = interfaces.createNestedObject("nodes");
if_nodes[F("list")] = nodeListEnabled;
@@ -610,6 +652,7 @@ void serializeConfig() {
if_live_dmx[F("seqskip")] = e131SkipOutOfSequence;
if_live_dmx[F("addr")] = DMXAddress;
if_live_dmx[F("mode")] = DMXMode;
if_live[F("timeout")] = realtimeTimeoutMs / 100;
if_live[F("maxbri")] = arlsForceMaxBri;
if_live[F("no-gc")] = arlsDisableGammaCorrection;
@@ -685,7 +728,7 @@ void serializeConfig() {
JsonArray goal = cntdwn.createNestedArray(F("goal"));
goal.add(countdownYear); goal.add(countdownMonth); goal.add(countdownDay);
goal.add(countdownHour); goal.add(countdownMin); goal.add(countdownSec);
cntdwn[F("macro")] = macroCountdown;
cntdwn["macro"] = macroCountdown;
JsonArray timers_ins = timers.createNestedArray("ins");
@@ -695,7 +738,7 @@ void serializeConfig() {
timers_ins0["en"] = (timerWeekday[i] & 0x01);
timers_ins0[F("hour")] = timerHours[i];
timers_ins0["min"] = timerMinutes[i];
timers_ins0[F("macro")] = timerMacro[i];
timers_ins0["macro"] = timerMacro[i];
timers_ins0[F("dow")] = timerWeekday[i] >> 1;
}
@@ -709,12 +752,15 @@ void serializeConfig() {
JsonObject dmx = doc.createNestedObject("dmx");
dmx[F("chan")] = DMXChannels;
dmx[F("gap")] = DMXGap;
dmx[F("start")] = DMXStart;
dmx["start"] = DMXStart;
dmx[F("start-led")] = DMXStartLED;
JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap"));
for (byte i = 0; i < 15; i++)
for (byte i = 0; i < 15; i++) {
dmx_fixmap.add(DMXFixtureMap[i]);
}
dmx[F("e131proxy")] = e131ProxyUniverse;
#endif
JsonObject usermods_settings = doc.createNestedObject("um");
@@ -723,16 +769,24 @@ void serializeConfig() {
File f = WLED_FS.open("/cfg.json", "w");
if (f) serializeJson(doc, f);
f.close();
releaseJSONBufferLock();
}
//settings in /wsec.json, not accessible via webserver, for passwords and tokens
bool deserializeConfigSec() {
DEBUG_PRINTLN(F("Reading settings from /wsec.json..."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(3)) return false;
#endif
bool success = readObjectFromFile("/wsec.json", nullptr, &doc);
if (!success) return false;
if (!success) {
releaseJSONBufferLock();
return false;
}
JsonObject nw_ins_0 = doc["nw"]["ins"][0];
getStringFromJson(clientPass, nw_ins_0["psk"], 65);
@@ -755,7 +809,7 @@ bool deserializeConfigSec() {
#endif
#ifndef WLED_DISABLE_HUESYNC
getStringFromJson(hueApiKey, interfaces[F("hue")][F("key")], 47);
getStringFromJson(hueApiKey, interfaces["hue"][F("key")], 47);
#endif
JsonObject ota = doc["ota"];
@@ -764,13 +818,18 @@ bool deserializeConfigSec() {
CJSON(wifiLock, ota[F("lock-wifi")]);
CJSON(aOtaEnabled, ota[F("aota")]);
releaseJSONBufferLock();
return true;
}
void serializeConfigSec() {
DEBUG_PRINTLN(F("Writing settings to /wsec.json..."));
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(4)) return;
#endif
JsonObject nw = doc.createNestedObject("nw");
@@ -805,4 +864,5 @@ void serializeConfigSec() {
File f = WLED_FS.open("/wsec.json", "w");
if (f) serializeJson(doc, f);
f.close();
releaseJSONBufferLock();
}

View File

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

View File

@@ -58,6 +58,12 @@
#define USERMOD_ID_RTC 15 //Usermod "usermod_rtc.h"
#define USERMOD_ID_ELEKSTUBE_IPS 16 //Usermod "usermod_elekstube_ips.h"
#define USERMOD_ID_SN_PHOTORESISTOR 17 //Usermod "usermod_sn_photoresistor.h"
#define USERMOD_ID_BATTERY_STATUS_BASIC 18 //Usermod "usermod_v2_battery_status_basic.h"
#define USERMOD_ID_PWM_FAN 19 //Usermod "usermod_PWM_fan.h"
#define USERMOD_ID_BH1750 20 //Usermod "usermod_bh1750.h"
#define USERMOD_ID_SEVEN_SEGMENT_DISPLAY 21 //Usermod "usermod_v2_seven_segment_display.h"
#define USERMOD_RGB_ROTARY_ENCODER 22 //Usermod "rgb-rotary-encoder.h"
#define USERMOD_ID_QUINLED_AN_PENTA 23 //Usermod "quinled-an-penta.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@@ -66,17 +72,18 @@
#define AP_BEHAVIOR_BUTTON_ONLY 3 //Only when button pressed for 6 sec
//Notifier callMode
#define NOTIFIER_CALL_MODE_INIT 0 //no updates on init, can be used to disable updates
#define NOTIFIER_CALL_MODE_DIRECT_CHANGE 1
#define NOTIFIER_CALL_MODE_BUTTON 2
#define NOTIFIER_CALL_MODE_NOTIFICATION 3
#define NOTIFIER_CALL_MODE_NIGHTLIGHT 4
#define NOTIFIER_CALL_MODE_NO_NOTIFY 5
#define NOTIFIER_CALL_MODE_FX_CHANGED 6 //no longer used
#define NOTIFIER_CALL_MODE_HUE 7
#define NOTIFIER_CALL_MODE_PRESET_CYCLE 8
#define NOTIFIER_CALL_MODE_BLYNK 9
#define NOTIFIER_CALL_MODE_ALEXA 10
#define CALL_MODE_INIT 0 //no updates on init, can be used to disable updates
#define CALL_MODE_DIRECT_CHANGE 1
#define CALL_MODE_BUTTON 2
#define CALL_MODE_NOTIFICATION 3
#define CALL_MODE_NIGHTLIGHT 4
#define CALL_MODE_NO_NOTIFY 5
#define CALL_MODE_FX_CHANGED 6 //no longer used
#define CALL_MODE_HUE 7
#define CALL_MODE_PRESET_CYCLE 8
#define CALL_MODE_BLYNK 9
#define CALL_MODE_ALEXA 10
#define CALL_MODE_WS_SEND 11 //special call mode, not for notifier, updates websocket only
//RGB to RGBW conversion mode
#define RGBW_MODE_MANUAL_ONLY 0 //No automatic white channel calculation. Manual white channel slider
@@ -110,13 +117,17 @@
#define DMX_MODE_MULTIPLE_DRGB 5 //every LED is addressed with its own RGB and share a master dimmer (ledCount * 3 + 1 channels)
#define DMX_MODE_MULTIPLE_RGBW 6 //every LED is addressed with its own RGBW (ledCount * 4 channels)
//Light capability byte (unused) 0bRRCCTTTT
//Light capability byte (unused) 0bRCCCTTTT
//bits 0/1/2/3: specifies a type of LED driver. A single "driver" may have different chip models but must have the same protocol/behavior
//bits 4/5: specifies the class of LED driver - 0b00 (dec. 0-15) unconfigured/reserved
// - 0b01 (dec. 16-31) digital (data pin only)
// - 0b10 (dec. 32-47) analog (PWM)
// - 0b11 (dec. 48-63) digital (data + clock / SPI)
//bits 6/7 are reserved and set to 0b00
//bits 4/5/6: specifies the class of LED driver - 0b000 (dec. 0-15) unconfigured/reserved
// - 0b001 (dec. 16-31) digital (data pin only)
// - 0b010 (dec. 32-47) analog (PWM)
// - 0b011 (dec. 48-63) digital (data + clock / SPI)
// - 0b100 (dec. 64-79) unused/reserved
// - 0b101 (dec. 80-95) virtual network busses
// - 0b110 (dec. 96-111) unused/reserved
// - 0b111 (dec. 112-127) unused/reserved
//bit 7 is reserved and set to 0
#define TYPE_NONE 0 //light is not configured
#define TYPE_RESERVED 1 //unused. Might indicate a "virtual" light
@@ -140,6 +151,10 @@
#define TYPE_APA102 51
#define TYPE_LPD8806 52
#define TYPE_P9813 53
//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)
#define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus)
#define IS_DIGITAL(t) ((t) & 0x10) //digital are 16-31 and 48-63
#define IS_PWM(t) ((t) > 40 && (t) < 46)
@@ -161,9 +176,10 @@
#define BTN_TYPE_PUSH 2
#define BTN_TYPE_PUSH_ACT_HIGH 3
#define BTN_TYPE_SWITCH 4
#define BTN_TYPE_SWITCH_ACT_HIGH 5
#define BTN_TYPE_PIR_SENSOR 5
#define BTN_TYPE_TOUCH 6
#define BTN_TYPE_ANALOG 7
#define BTN_TYPE_ANALOG_INVERTED 8
//Ethernet board types
#define WLED_NUM_ETH_TYPES 7
@@ -194,6 +210,14 @@
#define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed
#define SEG_OPTION_TRANSITIONAL 7
//Segment differs return byte
#define SEG_DIFFERS_BRI 0x01
#define SEG_DIFFERS_OPT 0x02
#define SEG_DIFFERS_COL 0x04
#define SEG_DIFFERS_FX 0x08
#define SEG_DIFFERS_BOUNDS 0x10
#define SEG_DIFFERS_GSO 0x20
//Playlist option byte
#define PL_OPTION_SHUFFLE 0x01
@@ -204,6 +228,7 @@
#define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?)
#define ERR_FS_QUOTA 11 // The FS is full or the maximum file size is reached
#define ERR_FS_PLOAD 12 // It was attempted to load a preset that does not exist
#define ERR_FS_IRLOAD 13 // It was attempted to load an IR JSON cmd, but the "ir.json" file does not exist
#define ERR_FS_GENERAL 19 // A general unspecified filesystem error occured
#define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented)
#define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented)
@@ -218,10 +243,10 @@
#define NTP_PACKET_SIZE 48
// maximum number of LEDs - more than 1500 LEDs (or 500 DMA "LEDPIN 3" driven ones) will cause a low memory condition on ESP8266
//maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses
#ifndef MAX_LEDS
#ifdef ESP8266
#define MAX_LEDS 1664 // can't rely on memory limit to limit this to 1600 LEDs
#define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs
#else
#define MAX_LEDS 8192
#endif
@@ -229,7 +254,7 @@
#ifndef MAX_LED_MEMORY
#ifdef ESP8266
#define MAX_LED_MEMORY 5000
#define MAX_LED_MEMORY 4000
#else
#define MAX_LED_MEMORY 64000
#endif
@@ -240,12 +265,20 @@
#endif
// string temp buffer (now stored in stack locally)
#define OMAX 2048
#ifdef ESP8266
#define SETTINGS_STACK_BUF_SIZE 2048
#else
#define SETTINGS_STACK_BUF_SIZE 3096
#endif
#ifdef WLED_USE_ETHERNET
#define E131_MAX_UNIVERSE_COUNT 20
#define E131_MAX_UNIVERSE_COUNT 20
#else
#define E131_MAX_UNIVERSE_COUNT 10
#ifdef ESP8266
#define E131_MAX_UNIVERSE_COUNT 9
#else
#define E131_MAX_UNIVERSE_COUNT 12
#endif
#endif
#define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit
@@ -270,7 +303,7 @@
// Maximum size of node map (list of other WLED instances)
#ifdef ESP8266
#define WLED_MAX_NODES 15
#define WLED_MAX_NODES 24
#else
#define WLED_MAX_NODES 150
#endif
@@ -280,15 +313,15 @@
#ifdef ESP8266
#define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards
#else
#define LEDPIN 16 // alligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards
#define LEDPIN 2 // Changed from 16 to restore compatibility with ESP32-pico
#endif
#endif
#ifdef WLED_ENABLE_DMX
#if (LEDPIN == 2)
#undef LEDPIN
#define LEDPIN 3
#warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 3."
#define LEDPIN 1
#warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 1."
#endif
#endif

View File

@@ -16,11 +16,7 @@
}
DMXMap = "";
for (i=0;i<512;i++) {
isstart = "";
if ((i+1) % 10 == 0) {
isstart="S"
}
DMXMap += "<div class=\"anytype " + isstart + " type" + dmxchans[i] + "\">" + String(i+1) + "<br />" + dmxlabels[dmxchans[i]] + "</div>";
DMXMap += "<div class=\"anytype type" + dmxchans[i] + "\">" + String(i+1) + "<br />" + dmxlabels[dmxchans[i]] + "</div>";
}
document.getElementById("map").innerHTML = DMXMap;
}</script>

File diff suppressed because one or more lines are too long

View File

@@ -45,17 +45,30 @@
<div class ="container">
<div id="Colors" class="tabcontent">
<div id="picker" class="noslide"></div>
<div id="vwrap">
<div class="sliderwrap il" id="vwrap">
<input id="sliderV" class="noslide" oninput="fromV()" onchange="setColor(0)" max="100" min="0" type="range" value="128" step="any" />
<div class="sliderdisplay"></div>
</div><br>
</div>
<div id="kwrap">
<div class="sliderwrap il">
<input id="sliderK" class="noslide" oninput="fromK()" onchange="setColor(0)" max="10091" min="1900" type="range" value="6550" />
<div class="sliderdisplay"></div>
</div>
</div>
<div id="rgbwrap">
<div class="sliderwrap il">
<input id="sliderR" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,1)" max="255" min="0" type="range" value="128" />
<p class="labels">RGB color</p>
<div class="sliderwrap il" id="rwrap">
<input id="sliderR" class="noslide" onchange="setColor(0)" oninput="fromRgb()" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div>
</div><br>
<div class="sliderwrap il">
<input id="sliderG" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,2)" max="255" min="0" type="range" value="128" />
<div class="sliderwrap il" id="gwrap">
<input id="sliderG" class="noslide" onchange="setColor(0)" oninput="fromRgb()" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div>
</div><br>
<div class="sliderwrap il">
<input id="sliderB" class="noslide" onchange="fromRgb()" oninput="updateTrail(this,3)" max="255" min="0" type="range" value="128" />
<div class="sliderwrap il" id="bwrap">
<input id="sliderB" class="noslide" onchange="setColor(0)" oninput="fromRgb()" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div>
</div><br>
</div>
@@ -66,6 +79,13 @@
<div class="sliderdisplay"></div>
</div>
</div>
<div id="wbal">
<p class="labels">White balance</p>
<div class="sliderwrap il">
<input id="sliderA" class="noslide" onchange="setBalance(this.value)" max="255" min="0" type="range" value="128" />
<div class="sliderdisplay"></div>
</div>
</div>
<div id="qcs-w">
<div class="qcs" onclick="pC('#ff0000');" title="Red" style="background-color:#ff0000;"></div>
<div class="qcs" onclick="pC('#ffa000');" title="Orange" style="background-color:#ffa000;"></div>
@@ -93,12 +113,12 @@
Color palette
</p>
<div class="il">
<div id="selectPalette" class="list">
<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="checkmark schk"></span>
<span class="radiomark schk"></span>
</label>
<div class="lstIcontent">
<span class="lstIname">
@@ -153,9 +173,10 @@
<div id="segutil2">
<button class="btn btn-s" id="rsbtn" onclick="rSegs()">Reset segments</button>
</div>
<p>Transition: <input id="tt" class="noslide" type="number" min="0" max="65.5" step="0.1" value="0.7">s</p>
</div>
<div id="Favorites" class="tabcontent">
<div id="Presets" class="tabcontent">
<div id="putil">
</div>
@@ -164,8 +185,7 @@
</div>
<div id="pcont">
Loading...
</div><br>
Transition <input id="tt" class="noslide" type="number" min="0" max="65.5" step="0.1" value="0.7">s
</div>
</div>
</div>
@@ -173,7 +193,7 @@
<button class="tablinks" onclick="openTab(0)"><i class="icons">&#xe2b3;</i><p class="tab-label">Colors</p></button>
<button class="tablinks" onclick="openTab(1)"><i class="icons">&#xe23d;</i><p class="tab-label">Effects</p></button>
<button class="tablinks" onclick="openTab(2)"><i class="icons">&#xe34b;</i><p class="tab-label">Segments</p></button>
<button class="tablinks" onclick="openTab(3)"><i class="icons">&#xe04c;</i><p class="tab-label">Favorites</p></button>
<button class="tablinks" onclick="openTab(3)"><i class="icons">&#xe04c;</i><p class="tab-label">Presets</p></button>
</div>
<div id="connind"></div>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -21,18 +21,6 @@
<body>
<div id="canv" />
<script>
console.info("Live-Preview websocket opening");
var socket = new WebSocket("ws://"+document.location.host+"/ws");
socket.onopen = function () {
console.info("Live-Preview websocket is opened");
socket.send("{'lv':true}");
}
socket.onclose = function () { console.info("Live-Preview websocket is closing"); }
socket.onerror = function (event) { console.error("Live-Preview websocket error:", event); }
function updatePreview(leds) {
var str = "linear-gradient(90deg,";
var len = leds.length;
@@ -46,7 +34,7 @@
document.getElementById("canv").style.background = str;
}
socket.onmessage = function (event) {
function getLiveJson(event) {
try {
var json = JSON.parse(event.data);
if (json && json.leds) {
@@ -54,9 +42,23 @@
}
}
catch (err) {
console.error("Live-Preview websocket error:",err);
}
console.error("Live-Preview ws error:",err);
}
}
var ws = top.window.ws;
if (ws && ws.readyState === WebSocket.OPEN) {
console.info("Use top WS for peek");
ws.send("{'lv':true}");
} else {
console.info("Peek ws opening");
ws = new WebSocket("ws://"+document.location.host+"/ws");
ws.onopen = function () {
console.info("Peek WS opened");
ws.send("{'lv':true}");
}
}
ws.addEventListener('message',getLiveJson);
</script>
</body>
</html>

View File

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

View File

@@ -5,10 +5,11 @@
<meta name="viewport" content="width=500">
<title>LED Settings</title>
<script>
var d=document,laprev=55,maxB=1,maxM=5000,maxPB=4096,bquot=0; //maximum bytes for LED allocation: 5kB for 8266, 32kB for 32
function H()
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=[];
function H()
{
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#led-settings");
window.open("https://kno.wled.ge/features/settings/#led-settings");
}
function B()
{
@@ -18,35 +19,59 @@
function off(n){
d.getElementsByName(n)[0].value = -1;
}
function bLimits(b,p,m) {
maxB = b; maxM = m; maxPB = p;
var timeout;
function showToast(text, error = false)
{
var x = gId("toast");
x.innerHTML = text;
x.className = error ? "error":"show";
clearTimeout(timeout);
x.style.animation = 'none';
timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900);
}
function bLimits(b,p,m,l) {
maxB = b; maxM = m; maxPB = p; maxL = l;
}
function pinsOK() {
var LCs = d.getElementsByTagName("input");
for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2);
// ignore IP address
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") {
var n = LCs[i].name.substring(2);
var t = parseInt(d.getElementsByName("LT"+n)[0].value, 10); // LED type SELECT
if (t>=80) continue;
}
//check for pin conflicts
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR")
if (LCs[i].value!="" && LCs[i].value!="-1") {
if (d.um_p && d.um_p.some((e)=>e==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(d.um_p)} can't be used.`);LCs[i].value="";LCs[i].focus();return false;}
else if (LCs[i].value > 5 && LCs[i].value < 12) {alert("Sorry, pins 6-11 can not be used.");LCs[i].value="";LCs[i].focus();return false;}
else if (!(nm == "IR" || nm=="BT") && LCs[i].value > 33) {alert("Sorry, pins >33 are input only.");LCs[i].value="";LCs[i].focus();return false;}
for (j=i+1; j<LCs.length; j++)
{
var n2 = LCs[j].name.substring(0,2);
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4" || n2=="RL" || n2=="BT" || n2=="IR")
if (LCs[j].value!="" && LCs[i].value==LCs[j].value) {alert(`Pin conflict between ${nm}/${n2}!`);LCs[j].value="";LCs[j].focus();return false;}
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4" || n2=="RL" || n2=="BT" || n2=="IR") {
if (n2.substring(0,1)==="L") {
var m = LCs[j].name.substring(2);
var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10);
if (t2>=80) continue;
}
if (LCs[j].value!="" && LCs[i].value==LCs[j].value) {alert(`Pin conflict between ${LCs[i].name}/${LCs[j].name}!`);LCs[j].value="";LCs[j].focus();return false;}
}
}
}
}
return true;
}
function trySubmit(e) {
d.Sf.data.value = '';
e.preventDefault();
if (!pinsOK()) {e.stopPropagation();return false;} // Prevent form submission and contact with server
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();setABL();}
function S(){GetV();checkSi();setABL();}
function enABL()
{
var en = gId('able').checked;
@@ -79,21 +104,21 @@
UI();
}
//returns mem usage
function getMem(type, len, p0) {
if (type < 32) {
function getMem(t, len, p0) {
if (t < 32) {
if (maxM < 10000 && p0==3) { //8266 DMA uses 5x the mem
if (type > 29) return len*20; //RGBW
if (t > 29) return len*20; //RGBW
return len*15;
} else if (maxM >= 10000) //ESP32 RMT uses double buffer?
{
if (type > 29) return len*8; //RGBW
if (t > 29) return len*8; //RGBW
return len*6;
}
if (type > 29) return len*4; //RGBW
if (t > 29) return len*4; //RGBW
return len*3;
}
if (type > 31 && type < 48) return 5;
if (type == 44 || type == 45) return len*4; //RGBW
if (t > 31 && t < 48) return 5;
if (t == 44 || t == 45) return len*4; //RGBW
return len*3;
}
function UI(change=false)
@@ -105,85 +130,128 @@
if (d.Sf.LA.value == 255) laprev = 12;
else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value;
// enable/disable LED fields
var s = d.getElementsByTagName("select");
for (i=0; i<s.length; i++) {
// is the field a LED type?
if (s[i].name.substring(0,2)=="LT") {
n=s[i].name.substring(2);
var type = parseInt(s[i].value,10);
gId("p0d"+n).innerHTML = (type > 49) ? "Data:" : (type >41) ? "Pins:" : "Pin:";
gId("p1d"+n).innerHTML = (type > 49) ? "Clk:" : "";
var LK = d.getElementsByName("L1"+n)[0];
var n = s[i].name.substring(2);
var t = parseInt(s[i].value,10);
gId("p0d"+n).innerHTML = (t>=80 && t<96) ? "IP address:" : (t > 49) ? "Data GPIO:" : (t >41) ? "GPIOs:" : "GPIO:";
gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : "";
var LK = d.getElementsByName("L1"+n)[0]; // clock pin
memu += getMem(type, d.getElementsByName("LC"+n)[0].value, d.getElementsByName("L0"+n)[0].value);
memu += getMem(t, d.getElementsByName("LC"+n)[0].value, d.getElementsByName("L0"+n)[0].value); // calc memory
// enumerate pins
for (p=1; p<5; p++) {
var LK = d.getElementsByName("L"+p+n)[0];
var LK = d.getElementsByName("L"+p+n)[0]; // secondary pins
if (!LK) continue;
if ((type>49 && p==1) || (type>41 && type < 50 && (p+40 < type))) // TYPE_xxxx values from const.h
if (((t>=80 && t<96) && p<4) || (t>49 && p==1) || (t>41 && t < 50 && (p+40 < t))) // TYPE_xxxx values from const.h
{
// display pin field
LK.style.display = "inline";
LK.required = true;
} else {
// hide pin field
LK.style.display = "none";
LK.required = false;
LK.value="";
}
}
if (type == 30 || type == 31 || (type > 40 && type < 46 && type != 43)) isRGBW = true;
gId("dig"+n).style.display = (type > 31 && type < 48) ? "none":"inline";
gId("psd"+n).innerHTML = (type > 31 && type < 48) ? "Index:":"Start:";
if (change) {
gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state
if (t > 31 && t < 48) d.getElementsByName("LC"+n)[0].value = 1; // for sanity change analog count just to 1 LED
}
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("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
gId("dig"+n+"f").style.display = (t>=16 && t<32 || t>=50 && t<64) ? "inline":"none"; // hide refresh
gId("rev"+n).innerHTML = (t > 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog
gId("psd"+n).innerHTML = (t > 40 && t < 48) ? "Index:":"Start:"; // change analog start description
}
}
// display white channel calculation method
var myC = d.querySelectorAll('.wc'),
l = myC.length;
for (i = 0; i < l; i++) {
myC[i].style.display = (isRGBW) ? 'inline':'none';
}
if (d.activeElement == d.getElementsByName("LC")[0]) {
var o = d.getElementsByClassName("iST");
var i = o.length;
if (i == 1) d.getElementsByName("LC0")[0].value = d.getElementsByName("LC")[0].value;
}
// check for pin conflicts
var LCs = d.getElementsByTagName("input");
var sLC = 0, maxLC = 0;
var sLC = 0, sPC = 0, maxLC = 0;
for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2);
if (nm=="LC" && LCs[i].name !== "LC") {
var n=LCs[i].name.substring(2);
var nm = LCs[i].name.substring(0,2); // field name
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(gId("ls"+n).readOnly) gId("ls"+n).value=sLC;
if(c){sLC+=c;if(c>maxLC)maxLC=c;}
if (!customStarts || !startsDirty[n]) gId("ls"+n).value=sLC;
gId("ls"+n).disabled = !customStarts;
if(c){
var s = parseInt(gId("ls"+n).value);
if (s+c > sLC) sLC = s+c;
if(c>maxLC)maxLC=c;
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
continue;
}
// do we have led pins for digital leds
if (nm=="L0" || nm=="L1") {
var lc=d.getElementsByName("LC"+LCs[i].name.substring(2))[0];
lc.max=maxPB;
var lc=d.getElementsByName("LC"+n)[0];
lc.max=maxPB; // update max led count value
}
// ignore IP address (stored in pins for virtual busses)
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") {
var t = parseInt(d.getElementsByName("LT"+n)[0].value); // LED type SELECT
if (t>=80) {
LCs[i].max = 255;
LCs[i].min = 0;
LCs[i].style.color="#fff";
continue; // do not check conflicts
} else {
LCs[i].max = 33;
LCs[i].min = -1;
}
}
// check for pin conflicts
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR")
if (LCs[i].value!="" && LCs[i].value!="-1") {
var p = [];
if (d.um_p && Array.isArray(d.um_p)) for (k=0;k<d.um_p.length;k++) p.push(d.um_p[k]);
var p = []; // used pin array
if (d.um_p && Array.isArray(d.um_p)) for (k=0;k<d.um_p.length;k++) p.push(d.um_p[k]); // fill with reservations
for (j=0; j<LCs.length; j++) {
if (i==j) continue;
var n2 = LCs[j].name.substring(0,2);
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4" || n2=="RL" || n2=="BT" || n2=="IR")
if (LCs[j].value!="" && LCs[j].value!="-1") p.push(parseInt(LCs[j].value,10));
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4" || n2=="RL" || n2=="BT" || n2=="IR") {
if (n2.substring(0,1)==="L") {
var m = LCs[j].name.substring(2);
var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10);
if (t2>=80) continue;
}
if (LCs[j].value!="" && LCs[j].value!="-1") p.push(parseInt(LCs[j].value,10)); // add current pin
}
}
if (p.some((e)=>e==parseInt(LCs[i].value,10))) LCs[i].style.color="red"; else LCs[i].style.color="#fff";
// now check for conflicts
if (p.some((e)=>e==parseInt(LCs[i].value,10))) LCs[i].style.color="red"; else LCs[i].style.color=parseInt(LCs[i].value,10)>33?"orange":"#fff";
}
}
// update total led count
gId("lc").textContent = sLC;
gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)";
// memory usage and warnings
gId('m0').innerHTML = memu;
bquot = memu / maxM * 100;
gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`;
gId('ledwarning').style.display = (sLC > maxPB || maxLC > 800 || bquot > 80) ? 'inline':'none';
gId('ledwarning').style.color = (sLC > maxPB || maxLC > maxPB || bquot > 100) ? 'red':'orange';
gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>WARNING: Using over ${maxM}B!</b>)` : "") : "800 LEDs per pin";
var val = Math.ceil((100 + sLC * laprev)/500)/2;
gId('ledwarning').style.display = (maxLC > Math.min(maxPB,800) || bquot > 80) ? 'inline':'none';
gId('ledwarning').style.color = (maxLC > Math.max(maxPB,800) || bquot > 100) ? 'red':'orange';
gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>ERROR: Using over ${maxM}B!</b>)` : "") : "800 LEDs per output";
// calculate power
var val = Math.ceil((100 + sPC * laprev)/500)/2;
val = (val > 5) ? Math.ceil(val) : val;
var s = "";
var is12V = (d.Sf.LAsel.value == 30);
@@ -197,26 +265,25 @@
s += val;
s += "A supply connected to LEDs";
}
var val2 = Math.ceil((100 + sLC * laprev)/1500)/2;
var val2 = Math.ceil((100 + sPC * laprev)/1500)/2;
val2 = (val2 > 5) ? Math.ceil(val2) : val2;
var s2 = "(for most effects, ~";
s2 += val2;
s2 += "A is enough)<br>";
gId('psu').innerHTML = s;
gId('psu2').innerHTML = isWS2815 ? "" : s2;
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
}
function lastEnd(i) {
if (i<1) return 0;
v = parseInt(d.getElementsByName("LS"+(i-1))[0].value) + parseInt(d.getElementsByName("LC"+(i-1))[0].value);
var type = parseInt(d.getElementsByName("LT"+(i-1))[0].value);
if (type > 31 && type < 48) v = 1; //PWM busses
var t = parseInt(d.getElementsByName("LT"+(i-1))[0].value);
if (t > 31 && t < 48) v = 1; //PWM busses
if (isNaN(v)) return 0;
return v;
}
function addLEDs(n)
function addLEDs(n,init=true)
{
if (n>1) {maxB=n; gId("+").style.display="inline"; return;}
var o = d.getElementsByClassName("iST");
var i = o.length;
@@ -226,10 +293,10 @@
if (n==1) {
// npm run build has trouble minimizing spaces inside string
var cn = `<div class="iST">
${i>0?'<hr style="width:260px">':''}
<hr style="width:260px">
${i+1}:
<select name="LT${i}" onchange="UI()">
<option value="22">WS281x</option>
<select name="LT${i}" onchange="UI(true)">
<option value="22" selected>WS281x</option>
<option value="30">SK6812 RGBW</option>
<option value="31">TM1814</option>
<option value="24">400kHz</option>
@@ -238,12 +305,16 @@ ${i+1}:
<option value="52">LPD8806</option>
<option value="53">P9813</option>
<option value="41">PWM White</option>
<option value="42">PWM WWCW</option>
<option value="42">PWM CCT</option>
<option value="43">PWM RGB</option>
<option value="44">PWM RGBW</option>
<option value="45">PWM RGBWC</option>
<option value="45">PWM RGB+CCT</option>
<!--option value="46">PWM RGB+DCCT</option-->
<option value="80">DDP RGB (network)</option>
<!--option value="81">E1.31 RGB (network)</option-->
<!--option value="82">ArtNet RGB (network)</option-->
</select>&nbsp;
Color Order:
<div id="co${i}" style="display:inline">Color Order:
<select name="CO${i}">
<option value="0">GRB</option>
<option value="1">RGB</option>
@@ -251,19 +322,19 @@ Color Order:
<option value="3">RBG</option>
<option value="4">BGR</option>
<option value="5">GBR</option>
</select><br>
<span id="p0d${i}">Pin:</span> <input type="number" name="L0${i}" min="0" max="40" required style="width:35px" onchange="UI()"/>
<span id="p1d${i}">Clock:</span> <input type="number" name="L1${i}" min="0" max="40" style="width:35px" onchange="UI()"/>
<span id="p2d${i}"></span><input type="number" name="L2${i}" min="0" max="40" style="width:35px" onchange="UI()"/>
<span id="p3d${i}"></span><input type="number" name="L3${i}" min="0" max="40" style="width:35px" onchange="UI()"/>
<span id="p4d${i}"></span><input type="number" name="L4${i}" min="0" max="40" style="width:35px" onchange="UI()"/>
</select></div>
<br>
<span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" min="0" max="8191" value="${lastEnd(i)}" required />&nbsp;
<div id="dig${i}" style="display:inline">
Count: <input type="number" name="LC${i}" min="0" max="${maxPB}" value="1" required oninput="UI()" /><br>
Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
&nbsp;Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}"><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>
<span id="p0d${i}">GPIO:</span> <input type="number" name="L0${i}" min="0" max="33" required class="xs" onchange="UI()"/>
<span id="p1d${i}"></span><input type="number" name="L1${i}" min="0" max="33" class="xs" onchange="UI()"/>
<span id="p2d${i}"></span><input type="number" name="L2${i}" min="0" max="33" class="xs" onchange="UI()"/>
<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}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}"></div>
</div>`;
f.insertAdjacentHTML("beforeend", cn);
}
@@ -274,29 +345,116 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
gId("+").style.display = (i<maxB-1) ? "inline":"none";
gId("-").style.display = (i>0) ? "inline":"none";
UI();
if (!init) UI();
}
function addBtn(i,p,t) {
var c = gId("btns").innerHTML;
var bt = "BT" + i;
var be = "BE" + i;
c += `Button ${i} pin: <input type="number" min="-1" max="40" name="${bt}" onchange="UI()" style="width:35px" value="${p}">&nbsp;`;
c += `<select name="${be}">`
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>`;
c += `<option value="2" ${t==2?"selected":""}>Pushbutton</option>`;
c += `<option value="3" ${t==3?"selected":""}>Push inverted</option>`;
c += `<option value="4" ${t==4?"selected":""}>Switch</option>`;
c += `<option value="5" ${t==4?"selected":""}>Switch inverted</option>`;
c += `<option value="5" ${t==5?"selected":""}>PIR sensor</option>`;
c += `<option value="6" ${t==6?"selected":""}>Touch</option>`;
c += `<option value="7" ${t==7?"selected":""}>Analog</option>`;
c += `<option value="8" ${t==8?"selected":""}>Analog inverted</option>`;
c += `</select>`;
c += `<span style="cursor: pointer;" onclick="off('${bt}')">&nbsp;&#215;</span><br>`;
gId("btns").innerHTML = c;
}
function tglSi(cs) {
customStarts = cs;
if (!customStarts) startsDirty = []; //set all starts to clean
UI();
}
function checkSi() { //on load, checks whether there are custom start fields
var cs = false;
for (var i=1; i < d.getElementsByClassName("iST").length; i++) {
var v = parseInt(gId("ls"+(i-1)).value) + parseInt(d.getElementsByName("LC"+(i-1))[0].value);
if (v != parseInt(gId("ls"+i).value)) {cs = true; startsDirty[i] = true;}
}
if (parseInt(gId("ls0").value) != 0) {cs = true; startsDirty[0] = true;}
gId("si").checked = cs;
tglSi(cs);
}
function uploadFile(name) {
var req = new XMLHttpRequest();
req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)});
req.addEventListener('error', function(e){showToast(e.stack,true);});
req.open("POST", "/upload");
var formData = new FormData();
formData.append("data", d.Sf.data.files[0], name);
req.send(formData);
d.Sf.data.value = '';
return false;
}
// https://stackoverflow.com/questions/7346563/loading-local-json-file
function loadCfg(o) {
var f, fr;
if (typeof window.FileReader !== 'function') {
alert("The file API isn't supported on this browser yet.");
return;
}
if (!o.files) {
alert("This browser doesn't support the `files` property of file inputs.");
} else if (!o.files[0]) {
alert("Please select a JSON file first!");
} else {
f = o.files[0];
fr = new FileReader();
fr.onload = receivedText;
fr.readAsText(f);
}
o.value = '';
function receivedText(e) {
let lines = e.target.result;
var c = JSON.parse(lines);
if (c.hw) {
if (c.hw.led) {
for (var i=0; i<10; i++) addLEDs(-1);
var l = c.hw.led;
l.ins.forEach((v,i,a)=>{
addLEDs(1);
for (var j=0; j<v.pin.length; j++) d.getElementsByName(`L${j}${i}`)[0].value = v.pin[j];
d.getElementsByName("LT"+i)[0].value = v.type;
d.getElementsByName("LS"+i)[0].value = v.start;
d.getElementsByName("LC"+i)[0].value = v.len;
d.getElementsByName("CO"+i)[0].value = v.order;
d.getElementsByName("SL"+i)[0].checked = v.skip;
d.getElementsByName("RF"+i)[0].checked = v.ref;
d.getElementsByName("CV"+i)[0].checked = v.rev;
});
}
if (c.hw.btn) {
var b = c.hw.btn;
if (Array.isArray(b.ins)) gId("btns").innerHTML = "";
b.ins.forEach((v,i,a)=>{
addBtn(i,v.pin[0],v.type);
});
d.getElementsByName("TT")[0].value = b.tt;
}
if (c.hw.ir) {
d.getElementsByName("IR")[0].value = c.hw.ir.pin;
d.getElementsByName("IT")[0].value = c.hw.ir.type;
}
if (c.hw.relay) {
d.getElementsByName("RL")[0].value = c.hw.relay.pin;
d.getElementsByName("RM")[0].checked = c.hw.relay.inv;
}
UI();
}
}
}
function GetV()
{
//values injected by server while sending HTML
//maxM=5000;maxPB=1536;d.um_p=[1,6,7,8,9,10,11];addLEDs(3);d.Sf.LC.value=250;addLEDs(1);d.Sf.L00.value=2;d.Sf.L10.value=0;d.Sf.LC0.value=250;d.Sf.LT0.value=22;d.Sf.CO0.value=0;d.Sf.LS0.value=0;d.Sf.LS0.checked=0;d.Sf.MA.value=5400;d.Sf.LA.value=55;d.getElementsByClassName("pow")[0].innerHTML="350mA";d.Sf.CA.value=40;d.Sf.AW.value=3;d.Sf.BO.checked=0;d.Sf.BP.value=3;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=0;d.Sf.BF.value=64;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=1;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=12;d.Sf.RM.checked=0;addBtn(0,0,2);addBtn(1,3,4);addBtn(2,-1,0);d.Sf.IR.value=-1;
//d.um_p=[6,7,8,9,10,11,14,15,13,1,21,19,22,25,26,27,5,23,18,17];bLimits(10,2048,64000,8192);d.Sf.MS.checked=1;d.Sf.CCT.checked=0;addLEDs(1);d.Sf.L00.value=192;d.Sf.L10.value=168;d.Sf.L20.value=0;d.Sf.L30.value=61;d.Sf.LC0.value=421;d.Sf.LT0.value=80;d.Sf.CO0.value=1;d.Sf.LS0.value=0;d.Sf.CV0.checked=0;d.Sf.SL0.checked=0;d.Sf.RF0.checked=0;d.Sf.MA.value=850;d.Sf.LA.value=0;d.Sf.CA.value=127;d.Sf.AW.value=3;d.Sf.BO.checked=0;d.Sf.BP.value=0;d.Sf.GB.checked=0;d.Sf.GC.checked=1;d.Sf.TF.checked=1;d.Sf.TD.value=700;d.Sf.PF.checked=1;d.Sf.BF.value=100;d.Sf.TB.value=0;d.Sf.TL.value=60;d.Sf.TW.value=1;d.Sf.PB.selectedIndex=0;d.Sf.RL.value=-1;d.Sf.RM.checked=1;addBtn(0,-1,0);addBtn(1,-1,0);addBtn(2,-1,0);addBtn(3,-1,0);d.Sf.TT.value=32;d.Sf.IR.value=-1;d.Sf.IT.value=8;
}
</script>
<style>
@@ -308,14 +466,14 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
<h2>LED &amp; Hardware setup</h2>
Total LED count: <input name="LC" id="LC" type="number" min="1" max="8192" oninput="UI()" required><br>
Total LEDs: <span id="lc">?</span> <span id="pc"></span><br>
<i>Recommended power supply for brightest white:</i><br>
<b><span id="psu">?</span></b><br>
<span id="psu2"><br></span>
<br>
Enable automatic brightness limiter: <input type="checkbox" name="ABen" onchange="enABL()" id="able"><br>
<div id="abl">
Maximum Current: <input name="MA" type="number" min="250" max="65000" oninput="UI()" required> mA<br>
Maximum Current: <input name="MA" type="number" class="l" min="250" max="65000" oninput="UI()" required> mA<br>
<div id="ampwarning" style="color: orange; display: none;">
&#9888; Your power supply provides high current.<br>
To improve the safety of your setup,<br>
@@ -339,44 +497,52 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
</div>
<h3>Hardware setup</h3>
<div id="mLC">LED outputs:</div>
<button type="button" id="+" onclick="addLEDs(1)" style="display:none;border-radius:20px;height:36px;">+</button>
<button type="button" id="-" onclick="addLEDs(-1)" style="display:none;border-radius:20px;width:36px;height:36px;">-</button><br>
<hr style="width:260px">
<button type="button" id="+" onclick="addLEDs(1,false)" style="display:none;border-radius:20px;height:36px;">+</button>
<button type="button" id="-" onclick="addLEDs(-1,false)" style="display:none;border-radius:20px;width:36px;height:36px;">-</button><br>
LED Memory Usage: <span id="m0">0</span> / <span id="m1">?</span> B<br>
<div id="dbar" style="display:inline-block; width: 100px; height: 10px; border-radius: 20px;"></div><br>
<div id="ledwarning" style="color: orange; display: none;">
&#9888; You might run into stability or lag issues.<br>
Use less than <span id="wreason">800 LEDs per pin</span> for the best experience!<br>
</div><hr style="width:260px">
Use less than <span id="wreason">800 LEDs per output</span> for the best experience!<br>
</div>
<hr style="width:260px">
Make a segment for each output: <input type="checkbox" name="MS"> <br>
Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"> <br>
<hr style="width:260px">
<div id="btns"></div>
Touch threshold: <input type="number" min="0" max="100" name="TT" required><br>
IR pin: <input type="number" min="-1" max="40" name="IR" onchange="UI()" style="width:35px">&nbsp;<select name="IT">
<option value="0">Remote disabled</option>
<option value="1">24-key RGB</option>
<option value="2">24-key with CT</option>
<option value="3">40-key blue</option>
<option value="4">44-key RGB</option>
<option value="5">21-key RGB</option>
<option value="6">6-key black</option>
<option value="7">9-key red</option>
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br>
IR GPIO: <input type="number" min="-1" max="40" name="IR" onchange="UI()" class="xs"><select name="IT" onchange="UI()">
<option value=0>Remote disabled</option>
<option value=1>24-key RGB</option>
<option value=2>24-key with CT</option>
<option value=3>40-key blue</option>
<option value=4>44-key RGB</option>
<option value=5>21-key RGB</option>
<option value=6>6-key black</option>
<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>
<a href="https://github.com/Aircoookie/WLED/wiki/Infrared-Control" target="_blank">IR info</a><br>
Relay pin: <input type="number" min="-1" max="40" name="RL" onchange="UI()" style="width:35px"> Invert <input type="checkbox" name="RM"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#215;</span><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">
<h3>Defaults</h3>
Turn LEDs on after power up/reset: <input type="checkbox" name="BO"><br>
Default brightness: <input name="CA" type="number" min="0" max="255" required> (0-255)<br><br>
Apply preset <input name="BP" type="number" min="0" max="250" required> at boot (0 uses defaults)
Default brightness: <input name="CA" type="number" class="s" min="0" max="255" required> (0-255)<br><br>
Apply preset <input name="BP" type="number" class="s" min="0" max="250" required> at boot (0 uses defaults)
<br><br>
Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br>
Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br><br>
Brightness factor: <input name="BF" type="number" min="1" max="255" required> %
Brightness factor: <input name="BF" type="number" class="s" min="1" max="255" required> %%
<h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br>
Transition Time: <input name="TD" maxlength="5" size="2"> ms<br>
Transition Time: <input name="TD" type="number" class="l" min="0" max="65500"> ms<br>
Enable Palette transitions: <input type="checkbox" name="PF">
<h3>Timed light</h3>
Default Duration: <input name="TL" type="number" min="1" max="255" required> min<br>
Default Target brightness: <input name="TB" type="number" min="0" max="255" required><br>
Default Duration: <input name="TL" type="number" class="s" min="1" max="255" required> min<br>
Default Target brightness: <input name="TB" type="number" class="s" min="0" max="255" required><br>
Mode:
<select name="TW">
<option value="0">Wait and set</option>
@@ -384,6 +550,19 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
<option value="2">Fade Color</option>
<option value="3">Sunrise</option>
</select>
<h3>White management</h3>
White Balance correction: <input type="checkbox" name="CCT"> <br>
<span class="wc">
Auto-calculate white channel from RGB:<br>
<select name="AW">
<option value=0>None</option>
<option value=1>Brighter</option>
<option value=2>Accurate</option>
<option value=3>Dual</option>
</select>
<br>
Calculate CCT from RGB: <input type="checkbox" name="CR"> <br>
CCT additive blending: <input type="number" class="s" min="0" max="100" name="CB" required> %%</span>
<h3>Advanced</h3>
Palette blending:
<select name="PB">
@@ -392,17 +571,10 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
<option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option>
</select><br>
<span class="wc">
Auto-calculate white channel from RGB:<br>
<select name="AW">
<option value=0>None</option>
<option value=1>Brighter</option>
<option value=2>Accurate</option>
<option value=3>Dual</option>
<option value=4>Legacy</option>
</select>
<br></span><hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
<hr style="width:260px">
<div id="cfg">Config template: <input type="file" name="data2" accept=".json"> <input type="button" value="Apply" onclick="loadCfg(d.Sf.data2);"><br></div>
<hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>

View File

@@ -5,6 +5,7 @@
<meta charset="utf-8">
<title>Misc Settings</title>
<script>
var d = document;
function H()
{
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#security-settings");
@@ -17,6 +18,34 @@
{
window.open("/update","_self");
}
function gId(s)
{
return d.getElementById(s);
}
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
var timeout;
function showToast(text, error = false)
{
var x = gId("toast");
x.innerHTML = text;
x.className = error ? "error":"show";
clearTimeout(timeout);
x.style.animation = 'none';
timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900);
}
function uploadFile(fO,name) {
var req = new XMLHttpRequest();
req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)});
req.addEventListener('error', function(e){showToast(e.stack,true);});
req.open("POST", "/upload");
var formData = new FormData();
formData.append("data", fO.files[0], name);
req.send(formData);
fO.value = '';
return false;
}
function GetV()
{
//values injected by server while sending HTML
@@ -44,6 +73,14 @@
<h3>Software Update</h3>
<button type="button" onclick="U()">Manual OTA Update</button><br>
Enable ArduinoOTA: <input type="checkbox" name="AO"><br>
<h3>Backup & Restore</h3>
<a class="btn lnk" href="/presets.json?download" target="download-frame">Backup presets</a><br>
<div>Restore presets<br><input type="file" name="data" accept=".json"> <input type="button" value="Upload" onclick="uploadFile(d.Sf.data,'/presets.json');"><br></div><br>
<a class="btn lnk" href="/cfg.json?download" target="download-frame">Backup configuration</a><br>
<div>Restore configuration<br><input type="file" name="data2" accept=".json"> <input type="button" value="Upload" onclick="uploadFile(d.Sf.data2,'/cfg.json');"><br></div>
<div style="color: #fa0;">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
Incorrect configuration may require a factory reset or re-flashing of your ESP.</div>
For security reasons, passwords are not backed up.
<h3>About</h3>
<a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a> version ##VERSION##<!-- Autoreplaced from package.json --><br><br>
<a href="https://github.com/Aircoookie/WLED/wiki/Contributors-and-credits" target="_blank">Contributors, dependencies and special thanks</a><br>
@@ -51,7 +88,9 @@
(c) 2016-2021 Christian Schwinne <br>
<i>Licensed under the <a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">MIT license</a></i><br><br>
Server message: <span class="sip"> Response error! </span><hr>
<div id="toast"></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save & Reboot</button>
</form>
<iframe name=download-frame style='display:none;'></iframe>
</body>
</html>

View File

@@ -1,23 +1,86 @@
<!DOCTYPE html>
<html lang="en"><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>Sync Settings</title>
<script>var d=document;
function gId(s)
{
return d.getElementById(s);
}
function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#sync-settings");}function B(){window.open("/settings","_self");}
function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.DA.value == 1) d.Sf.DA.value = 0; if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;}
else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} }
function SP(){var p = d.Sf.DI.value; d.getElementById("xp").style.display = (p > 0)?"none":"block"; if (p > 0) d.Sf.EP.value = p;}
function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 5568; break; case 6454: d.Sf.DI.value = 6454; break; case 4048: d.Sf.DI.value = 4048; break; }; SP();}
function FC()
{
for(j=0;j<8;j++)
{
gId("G"+(j+1)).checked=gId("GS").value>>j&1;
gId("R"+(j+1)).checked=gId("GR").value>>j&1;
}
}
function GC()
{
var a=0, b=0;
var m=1;
for(j=0;j<8;j++)
{
a+=gId("G"+(j+1)).checked*m;
b+=gId("R"+(j+1)).checked*m;
m*=2;
}
gId("GS").value=a;
gId("GR").value=b;
}
function SP(){var p = d.Sf.DI.value; gId("xp").style.display = (p > 0)?"none":"block"; if (p > 0) d.Sf.EP.value = p;}
function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 5568; break; case 6454: d.Sf.DI.value = 6454; break; case 4048: d.Sf.DI.value = 4048; break; }; SP();FC();}
function S(){GetV();SetVal();}
function GetV(){var d=document;}
</script>
<style>@import url("style.css");</style></head>
<body onload="S()">
<form id="form_s" name="Sf" method="post">
<form id="form_s" name="Sf" method="post" onsubmit="GC()">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
<h2>Sync setup</h2>
<h3>WLED Broadcast</h3>
UDP Port: <input name="UP" type="number" min="1" max="65535" class="d5" required><br>
2nd Port: <input name="U2" type="number" min="1" max="65535" class="d5" required><br>
2nd Port: <input name="U2" type="number" min="1" max="65535" class="d5" required><br><br>
<input name="GS" id="GS" type="number" style="display: none;"> <!-- hidden inputs for bitwise group checkboxes -->
<input name="GR" id="GR" type="number" style="display: none;">
<table style="margin: 0 auto;">
<tr>
<td>Sync groups</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
</tr>
<tr>
<td>Send:</td>
<td><input type="checkbox" id="G1" name="G1"></td>
<td><input type="checkbox" id="G2" name="G2"></td>
<td><input type="checkbox" id="G3" name="G3"></td>
<td><input type="checkbox" id="G4" name="G4"></td>
<td><input type="checkbox" id="G5" name="G5"></td>
<td><input type="checkbox" id="G6" name="G6"></td>
<td><input type="checkbox" id="G7" name="G7"></td>
<td><input type="checkbox" id="G8" name="G8"></td>
</tr>
<tr>
<td>Receive:</td>
<td><input type="checkbox" id="R1" name="R1"></td>
<td><input type="checkbox" id="R2" name="R2"></td>
<td><input type="checkbox" id="R3" name="R3"></td>
<td><input type="checkbox" id="R4" name="R4"></td>
<td><input type="checkbox" id="R5" name="R5"></td>
<td><input type="checkbox" id="R6" name="R6"></td>
<td><input type="checkbox" id="R7" name="R7"></td>
<td><input type="checkbox" id="R8" name="R8"></td>
</tr>
</table><br>
Receive: <input type="checkbox" name="RB">Brightness, <input type="checkbox" name="RC">Color, and <input type="checkbox" name="RX">Effects<br>
Send notifications on direct change: <input type="checkbox" name="SD"><br>
Send notifications on button press or IR: <input type="checkbox" name="SB"><br>
@@ -28,7 +91,7 @@ Send notifications twice: <input type="checkbox" name="S2"><br>
<i>Reboot required to apply changes. </i>
<h3>Instance List</h3>
Enable instance list: <input type="checkbox" name="NL"><br>
Make this instance discoverable: <input type="checkbox" name="NB"><br>
Make this instance discoverable: <input type="checkbox" name="NB">
<h3>Realtime</h3>
Receive UDP realtime: <input type="checkbox" name="RD"><br><br>
<i>Network DMX input</i><br>
@@ -36,7 +99,6 @@ Type:
<select name=DI onchange="SP(); adj();">
<option value=5568>E1.31 (sACN)</option>
<option value=6454>Art-Net</option>
<option value=4048>DDP</option>
<option value=0 selected>Custom port</option>
</select><br>
<div id=xp>Port: <input name="EP" type="number" min="1" max="65535" value="5568" class="d5" required><br></div>
@@ -83,6 +145,7 @@ Password: <input type="password" name="MQPASS" maxlength="64"><br>
Client ID: <input name="MQCID" maxlength="40"><br>
Device Topic: <input name="MD" maxlength="32"><br>
Group Topic: <input name="MG" maxlength="32"><br>
Publish on button press: <input type="checkbox" name="BM"><br>
<i>Reboot required to apply changes. </i><a href="https://github.com/Aircoookie/WLED/wiki/MQTT" target="_blank">MQTT info</a>
<h3>Philips Hue</h3>
<i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br>

View File

@@ -128,6 +128,9 @@
<option value="16">ACST</option>
<option value="17">ACST/ACDT</option>
<option value="18">HST (Hawaii)</option>
<option value="19">NOVT (Novosibirsk)</option>
<option value="20">AKST/AKDT (Anchorage)</option>
<option value="21">MX-CST/CDT</option>
</select><br>
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>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<meta charset="utf-8">
<meta name="viewport" content="width=500">
<title>UI Settings</title>
<script>
@@ -10,16 +10,19 @@
var sett = null;
var l = {
"comp":{
"labels":"Show button labels",
"colors":{
"LABEL":"Color selection methods",
"picker": "Color Wheel",
"rgb": "RGB sliders",
"quick": "Quick color selectors",
"hex": "HEX color input"
},
"pcmbot": "Show bottom tab bar in PC mode",
"pid": "Show preset IDs"
"labels":"Show button labels",
"colors":{
"LABEL":"Color selection methods",
"picker": "Color Wheel",
"rgb": "RGB sliders",
"quick": "Quick color selectors",
"hex": "HEX color input"
},
"pcmbot": "Show bottom tab bar in PC mode",
"pid": "Show preset IDs",
"seglen": "Set segment length instead of stop LED",
"css": "Enable custom CSS",
"hdays": "Enable custom Holidays list"
},
"theme":{
"alpha": {
@@ -34,7 +37,6 @@
"bg":"BG HEX color"
}
}
};
function gId(s)
{
@@ -52,10 +54,18 @@
if( !tar[elem] ) tar[elem] = {}
tar = tar[elem];
}
tar[pList[len-1]] = val;
}
var timeout;
function showToast(text, error = false)
{
var x = gId("toast");
x.innerHTML = text;
x.className = error ? "error":"show";
clearTimeout(timeout);
x.style.animation = 'none';
timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900);
}
function addRec(s, path = "", label = null)
{
var str = "";
@@ -181,16 +191,24 @@
gId("theme_bg_random").checked = false;
}
}
function uploadFile(fO,name) {
var req = new XMLHttpRequest();
req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)});
req.addEventListener('error', function(e){showToast(e.stack,true);});
req.open("POST", "/upload");
var formData = new FormData();
formData.append("data", fO.files[0], name);
req.send(formData);
fO.value = '';
return false;
}
function GetV(){var d=document;}
</script>
<style>
@import url("style.css");
</style>
<style>@import url("style.css");</style>
</head>
<body onload="S()">
<form id="form_s" name="Sf" method="post">
<div style="position:sticky;top:0;background-color:#222;">
<div style="position:sticky;top:0;background-color:#222;z-index:1;">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="button" onclick="Save()">Save</button><br>
<span id="lssuc" style="color:green; display:none">&#10004; Local UI settings saved!</span>
@@ -198,7 +216,7 @@
</div>
<h2>Web Setup</h2>
Server description: <input name="DS" maxlength="32"><br>
Sync button toggles both send and receive: <input type="checkbox" name="ST"><br>
Sync button toggles both send and receive: <input type="checkbox" name="ST"><br>
<i>The following UI customization settings are unique both to the WLED device and this browser.<br>
You will need to set them again if using a different browser, device or WLED IP address.<br>
Refresh the main UI to apply changes.</i><br>
@@ -207,8 +225,9 @@
<h3>UI Appearance</h3>
<span class="l"></span>: <input type="checkbox" id="comp_labels" class="agi cb"><br>
<span class="l"></span>: <input type="checkbox" id="comp_pcmbot" class="agi cb"><br>
<span class="l"></span>: <input type="checkbox" id="comp_pid" class="agi cb"><br>
<span class="l"></span>: <input type="checkbox" id="comp_pcmbot" class="agi cb"><br>
<span class="l"></span>: <input type="checkbox" id="comp_pid" class="agi cb"><br>
<span class="l"></span>: <input type="checkbox" id="comp_seglen" class="agi cb"><br>
I hate dark mode: <input type="checkbox" id="dm" onchange="UI()"><br>
<span id="idonthateyou" style="display:none"><i>Why would you? </i>&#x1F97A;<br></span>
<span class="l"></span>: <input type="number" min=0.0 max=1.0 step=0.01 id="theme_alpha_tab" class="agi"><br>
@@ -217,6 +236,11 @@
<span class="l">BG image URL</span>: <input id="theme_bg_url" class="agi" oninput="checkRandomBg()"><br>
<span class="l">Random BG image</span>: <input type="checkbox" id="theme_bg_random" class="agi cb" onchange="setRandomBg()"><br>
<input id="theme_base" class="agi" style="display:none">
<span class="l"></span>: <input type="checkbox" id="comp_css" class="agi cb"><br>
<div id="skin">Custom CSS: <input type="file" name="data" accept=".css"> <input type="button" value="Upload" onclick="uploadFile(d.Sf.data,'/skin.css');"><br></div>
<span class="l"></span>: <input type="checkbox" id="comp_hdays" class="agi cb"><br>
<div id="holidays">Holidays: <input type="file" name="data2" accept=".json"> <input type="button" value="Upload" onclick="uploadFile(d.Sf.data2,'/holidays.json');"><br></div>
<div id="toast"></div>
<hr><button type="button" onclick="B()">Back</button><button type="button" onclick="Save()">Save</button>
</form>
</body>

View File

@@ -84,7 +84,7 @@
c += ' max="39" min="-1" style="width:40px;"';
t = "int";
} else {
c += ' step="0.00001" style="width:80px;"';
c += ' step="any" style="width:100px;"';
}
break;
default:

View File

@@ -31,20 +31,20 @@
Network name (SSID, empty to not connect): <br><input name="CS" maxlength="32"><br>
Network password: <br> <input type="password" name="CP" maxlength="63"><br>
Static IP (leave at 0.0.0.0 for DHCP):<br>
<input name="I0" type="number" min="0" max="255" required> .
<input name="I1" type="number" min="0" max="255" required> .
<input name="I2" type="number" min="0" max="255" required> .
<input name="I3" type="number" min="0" max="255" required><br>
<input name="I0" type="number" class="s" min="0" max="255" required> .
<input name="I1" type="number" class="s" min="0" max="255" required> .
<input name="I2" type="number" class="s" min="0" max="255" required> .
<input name="I3" type="number" class="s" min="0" max="255" required><br>
Static gateway:<br>
<input name="G0" type="number" min="0" max="255" required> .
<input name="G1" type="number" min="0" max="255" required> .
<input name="G2" type="number" min="0" max="255" required> .
<input name="G3" type="number" min="0" max="255" required><br>
<input name="G0" type="number" class="s" min="0" max="255" required> .
<input name="G1" type="number" class="s" min="0" max="255" required> .
<input name="G2" type="number" class="s" min="0" max="255" required> .
<input name="G3" type="number" class="s" min="0" max="255" required><br>
Static subnet mask:<br>
<input name="S0" type="number" min="0" max="255" required> .
<input name="S1" type="number" min="0" max="255" required> .
<input name="S2" type="number" min="0" max="255" required> .
<input name="S3" type="number" min="0" max="255" required><br>
<input name="S0" type="number" class="s" min="0" max="255" required> .
<input name="S1" type="number" class="s" min="0" max="255" required> .
<input name="S2" type="number" class="s" min="0" max="255" required> .
<input name="S3" type="number" class="s" min="0" max="255" required><br>
mDNS address (leave empty for no mDNS):<br/>
http:// <input name="CM" maxlength="32"> .local<br>
Client IP: <span class="sip"> Not connected </span> <br>
@@ -52,7 +52,7 @@
AP SSID (leave empty for no AP):<br> <input name="AS" maxlength="32"><br>
Hide AP name: <input type="checkbox" name="AH"><br>
AP password (leave empty for open):<br> <input type="password" name="AP" maxlength="63" pattern="(.{8,63})|()" title="Empty or min. 8 characters"><br>
Access Point WiFi channel: <input name="AC" type="number" min="1" max="13" required><br>
Access Point WiFi channel: <input name="AC" type="number" class="xs" min="1" max="13" required><br>
AP opens:
<select name="AB">
<option value="0">No connection after boot</option>

View File

@@ -9,16 +9,24 @@ body {
hr {
border-color: #666;
}
button {
a {
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: 8px;
margin-top: 12px;
margin: 12px 8px 8px;
padding: 1px 6px;
cursor: pointer;
text-decoration: none;
}
.lnk {
border: 0;
}
.helpB {
text-align: left;
@@ -31,6 +39,9 @@ input {
font-family: Verdana, sans-serif;
border: 0.5ch solid #333;
}
input:disabled {
color: #888;
}
input[type="number"] {
width: 4em;
margin: 2px;
@@ -38,14 +49,20 @@ input[type="number"] {
input[type="number"].xxl {
width: 100px;
}
input[type="number"].big {
input[type="number"].xl {
width: 85px;
}
input[type="number"].med {
width: 55px;
input[type="number"].l {
width: 63px;
}
input[type="number"].small {
width: 40px;
input[type="number"].m {
width: 56px;
}
input[type="number"].s {
width: 49px;
}
input[type="number"].xs {
width: 42px;
}
input[type="checkbox"] {
transform: scale(1.5);
@@ -63,3 +80,32 @@ td {
.d5 {
width: 4.5em !important;
}
#toast {
opacity: 0;
background-color: #444;
border-radius: 5px;
bottom: 64px;
color: #fff;
font-size: 17px;
padding: 16px;
pointer-events: none;
position: fixed;
text-align: center;
z-index: 5;
transform: translateX(-50%%); /* %% because of AsyncWebServer */
max-width: 90%%; /* %% because of AsyncWebServer */
left: 50%%; /* %% because of AsyncWebServer */
}
#toast.show {
opacity: 1;
background-color: #264;
animation: fadein 0.5s, fadein 0.5s 2.5s reverse;
}
#toast.error {
opacity: 1;
background-color: #b21;
animation: fadein 0.5s;
}

View File

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

View File

@@ -34,9 +34,11 @@ void handleDDPPacket(e131_packet_t* p) {
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
for (uint16_t i = start; i < stop; i++) {
setRealtimePixel(i, data[c], data[c+1], data[c+2], 0);
c+=3;
if (!realtimeOverride) {
for (uint16_t i = start; i < stop; i++) {
setRealtimePixel(i, data[c], data[c+1], data[c+2], 0);
c+=3;
}
}
bool push = p->flags & DDP_PUSH_FLAG;
@@ -102,6 +104,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
// update status info
realtimeIP = clientIP;
byte wChannel = 0;
uint16_t totalLen = strip.getLengthTotal();
switch (DMXMode) {
case DMX_MODE_DISABLED:
@@ -114,7 +117,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
realtimeLock(realtimeTimeoutMs, mde);
if (realtimeOverride) return;
wChannel = (dmxChannels-DMXAddress+1 > 3) ? e131_data[DMXAddress+3] : 0;
for (uint16_t i = 0; i < ledCount; i++)
for (uint16_t i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[DMXAddress+0], e131_data[DMXAddress+1], e131_data[DMXAddress+2], wChannel);
break;
@@ -129,7 +132,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
bri = e131_data[DMXAddress+0];
strip.setBrightness(bri);
}
for (uint16_t i = 0; i < ledCount; i++)
for (uint16_t i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[DMXAddress+1], e131_data[DMXAddress+2], e131_data[DMXAddress+3], wChannel);
break;
@@ -156,9 +159,9 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
col[3] = e131_data[DMXAddress+11]; //white
colSec[3] = e131_data[DMXAddress+12];
}
transitionDelayTemp = 0; // act fast
colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); // don't send UDP
return; // don't activate realtime live mode
transitionDelayTemp = 0; // act fast
colorUpdated(CALL_MODE_NOTIFICATION); // don't send UDP
return; // don't activate realtime live mode
break;
case DMX_MODE_MULTIPLE_DRGB:

View File

@@ -32,6 +32,27 @@ bool deserializeConfigSec();
void serializeConfig();
void serializeConfigSec();
template<typename DestType>
bool getJsonValue(const JsonVariant& element, DestType& destination) {
if (element.isNull()) {
return false;
}
destination = element.as<DestType>();
return true;
}
template<typename DestType, typename DefaultType>
bool getJsonValue(const JsonVariant& element, DestType& destination, const DefaultType defaultValue) {
if(!getJsonValue(element, destination)) {
destination = defaultValue;
return false;
}
return true;
}
//colors.cpp
void colorFromUint32(uint32_t in, bool secondary = false);
void colorFromUint24(uint32_t in, bool secondary = false);
@@ -46,7 +67,9 @@ void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TOD
void colorFromDecOrHexString(byte* rgb, char* in);
bool colorFromHexString(byte* rgb, const char* in);
void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY)
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb);
//dmx.cpp
void initDMX();
@@ -72,6 +95,12 @@ void onHueConnect(void* arg, AsyncClient* client);
void sendHuePoll();
void onHueData(void* arg, AsyncClient* client, void *data, size_t len);
//improv.cpp
void handleImprovPacket();
void sendImprovStateResponse(uint8_t state, bool error = false);
void sendImprovInfoResponse();
void sendImprovRPCResponse(uint8_t commandId);
//ir.cpp
bool decodeIRCustom(uint32_t code);
void applyRepeatActions();
@@ -87,6 +116,7 @@ void decodeIR44(uint32_t code);
void decodeIR21(uint32_t code);
void decodeIR6(uint32_t code);
void decodeIR9(uint32_t code);
void decodeIRJson(uint32_t code);
void initIR();
void handleIR();
@@ -98,7 +128,7 @@ void handleIR();
#include "FX.h"
void deserializeSegment(JsonObject elem, byte it, byte presetId = 0);
bool deserializeState(JsonObject root, byte presetId = 0);
bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, byte presetId = 0);
void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true);
void serializeInfo(JsonObject root);
@@ -155,11 +185,11 @@ void _drawOverlayCronixie();
//playlist.cpp
void shufflePlaylist();
void unloadPlaylist();
void loadPlaylist(JsonObject playlistObject, byte presetId = 0);
int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0);
void handlePlaylist();
//presets.cpp
bool applyPreset(byte index);
bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE);
void savePreset(byte index, bool persist = true, const char* pname = nullptr, JsonObject saveobj = JsonObject());
void deletePreset(byte index);
@@ -169,27 +199,42 @@ bool isAsterisksOnly(const char* str, byte maxLen);
void handleSettingsSet(AsyncWebServerRequest *request, byte subPage);
bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=true);
int getNumVal(const String* req, uint16_t pos);
void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255);
bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255);
//udp.cpp
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 handleNotifications();
void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w);
void refreshNodeList();
void sendSysInfoUDP();
//util.cpp
//bool oappend(const char* txt); // append new c string to temp buffer efficiently
//bool oappendi(int i); // append new number to temp buffer efficiently
//void sappend(char stype, const char* key, int val);
//void sappends(char stype, const char* key, char* val);
//void prepareHostname(char* hostname);
//void _setRandomColor(bool _sec, bool fromButton);
//bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t module=255);
void releaseJSONBufferLock();
//um_manager.cpp
class Usermod {
public:
virtual void loop() {}
virtual void handleOverlayDraw() {}
virtual bool handleButton(uint8_t b) { return false; }
virtual void setup() {}
virtual void connected() {}
virtual void addToJsonState(JsonObject& obj) {}
virtual void addToJsonInfo(JsonObject& obj) {}
virtual void readFromJsonState(JsonObject& obj) {}
virtual void addToConfig(JsonObject& obj) {}
virtual bool readFromConfig(JsonObject& obj) { return true; } //Heads up! readFromConfig() now needs to return a bool
virtual bool readFromConfig(JsonObject& obj) { return true; } // Note as of 2021-06 readFromConfig() now needs to return a bool, see usermod_v2_example.h
virtual void onMqttConnect(bool sessionPresent) {}
virtual bool onMqttMessage(char* topic, char* payload) { return false; }
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
@@ -202,14 +247,13 @@ class UsermodManager {
public:
void loop();
void handleOverlayDraw();
bool handleButton(uint8_t b);
void setup();
void connected();
void addToJsonState(JsonObject& obj);
void addToJsonInfo(JsonObject& obj);
void readFromJsonState(JsonObject& obj);
void addToConfig(JsonObject& obj);
bool readFromConfig(JsonObject& obj);
void onMqttConnect(bool sessionPresent);

View File

@@ -379,7 +379,7 @@ String getContentType(AsyncWebServerRequest* request, String filename){
if(request->hasArg("download")) return "application/octet-stream";
else if(filename.endsWith(".htm")) return "text/html";
else if(filename.endsWith(".html")) return "text/html";
// else if(filename.endsWith(".css")) return "text/css";
else if(filename.endsWith(".css")) return "text/css";
// else if(filename.endsWith(".js")) return "application/javascript";
else if(filename.endsWith(".json")) return "application/json";
else if(filename.endsWith(".png")) return "image/png";

Some files were not shown because too many files have changed in this diff Show More