Compare commits

...

128 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
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
itCarl
f368bbec32 added MQTT support, Battery voltage to Info, circuit diagram to readme, minor fixes 2021-09-02 15:41:19 +02:00
90 changed files with 9578 additions and 4054 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,68 @@
### 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"
@@ -360,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-b3",
"version": "0.13.0-b6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

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

View File

@@ -12,7 +12,7 @@
; default_envs = travis_esp8266, travis_esp32
# Release binaries
default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth
# Build everything
; default_envs = esp32dev, esp8285_4CH_MagicHome, esp8285_4CH_H801, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_5CH_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
@@ -20,6 +20,7 @@ default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth
# Single binaries (uncomment your board)
; default_envs = elekstube_ips
; default_envs = nodemcuv2
; default_envs = esp8266_2m
; default_envs = esp01_1m_full
; default_envs = esp07
; default_envs = d1_mini
@@ -207,6 +208,8 @@ build_flags = -g
-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
@@ -239,6 +242,13 @@ build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266
lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full]
board = esp01_1m
platform = ${common.platform_wled_default}
@@ -292,6 +302,7 @@ platform = espressif32@2.0
build_unflags = ${common.build_unflags}
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
@@ -300,6 +311,7 @@ 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_deps = ${esp32.lib_deps}
board_build.partitions = ${esp32.default_partitions}
[env:esp32s2_saola]
board = esp32dev
@@ -406,6 +418,7 @@ build_flags = ${common.build_flags_esp32}
lib_deps = ${esp32.lib_deps}
OneWire@~2.3.5
olikraus/U8g2 @ ^2.28.8
board_build.partitions = ${esp32.default_partitions}
[env:m5atom]
board = esp32dev
@@ -413,6 +426,7 @@ build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39
lib_deps = ${esp32.lib_deps}
platform = espressif32@3.2
board_build.partitions = ${esp32.default_partitions}
[env:sp501e]
board = esp_wroom_02
@@ -428,6 +442,13 @@ board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
lib_deps = ${esp8266.lib_deps}
[env:athom7w]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D WLED_MAX_CCT_BLEND=0 -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
# travis test board configurations
# ------------------------------------------------------------------------------
@@ -498,3 +519,4 @@ monitor_filters = esp32_exception_decoder
lib_deps =
${esp32.lib_deps}
TFT_eSPI @ ^2.3.70
board_build.partitions = ${esp32.default_partitions}

View File

@@ -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!
*/
`;

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

@@ -1,33 +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
}
# 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

@@ -1,409 +1,409 @@
#pragma once
#include "wled.h"
#ifndef PIR_SENSOR_PIN
// compatible with QuinLED-Dig-Uno
#ifdef ARDUINO_ARCH_ESP32
#define PIR_SENSOR_PIN 23 // Q4
#else //ESP8266 boards
#define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini)
#endif
#endif
/*
* This usermod handles PIR sensor states.
* The strip will be switched on and the off timer will be resetted when the sensor goes HIGH.
* When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off.
*
*
* Usermods allow you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
*
* v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example.
* Multiple v2 usermods can be added to one compilation easily.
*
* Creating a usermod:
* This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template.
* Please remember to rename the class and file to a descriptive name.
* You may also use multiple .h and .cpp files.
*
* Using a usermod:
* 1. Copy the usermod into the sketch folder (same folder as wled00.ino)
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
*/
class PIRsensorSwitch : public Usermod
{
public:
/**
* constructor
*/
PIRsensorSwitch() {}
/**
* desctructor
*/
~PIRsensorSwitch() {}
/**
* Enable/Disable the PIR sensor
*/
void EnablePIRsensor(bool en) { enabled = en; }
/**
* Get PIR sensor enabled/disabled state
*/
bool PIRsensorEnabled() { return enabled; }
private:
// PIR sensor pin
int8_t PIRsensorPin = PIR_SENSOR_PIN;
// notification mode for colorUpdated()
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
uint32_t m_offTimerStart = 0;
// current PIR sensor pin state
byte sensorPinState = LOW;
// PIR sensor enabled
bool enabled = true;
// status of initialisation
bool initDone = false;
// on and off presets
uint8_t m_onPreset = 0;
uint8_t m_offPreset = 0;
// flag to indicate that PIR sensor should activate WLED during nighttime only
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;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _switchOffDelay[];
static const char _enabled[];
static const char _onPreset[];
static const char _offPreset[];
static const char _nightTime[];
static const char _mqttOnly[];
static const char _offOnly[];
/**
* check if it is daytime
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
*/
bool isDayTime() {
bool isDayTime = false;
updateLocalTime();
uint8_t hr = hour(localTime);
uint8_t mi = minute(localTime);
if (sunrise && sunset) {
if (hour(sunrise)<hr && hour(sunset)>hr) {
isDayTime = true;
} else {
if (hour(sunrise)==hr && minute(sunrise)<mi) {
isDayTime = true;
}
if (hour(sunset)==hr && minute(sunset)>mi) {
isDayTime = true;
}
}
}
return isDayTime;
}
/**
* switch strip on/off
*/
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) {
applyPreset(m_offPreset);
} else if (switchOn && bri == 0) {
bri = briLast;
colorUpdated(NotifyUpdateMode);
} else if (!switchOn && bri != 0) {
briLast = bri;
bri = 0;
colorUpdated(NotifyUpdateMode);
}
}
void publishMqtt(const char* state)
{
//Check if MQTT Connected, otherwise it will crash the 8266
if (WLED_MQTT_CONNECTED){
char subuf[64];
strcpy(subuf, mqttDeviceTopic);
strcat_P(subuf, PSTR("/motion"));
mqtt->publish(subuf, 0, false, state);
}
}
/**
* Read and update PIR sensor state.
* Initilize/reset switch off timer
*/
bool updatePIRsensorState()
{
bool pinState = digitalRead(PIRsensorPin);
if (pinState != sensorPinState) {
sensorPinState = pinState; // change previous state
if (sensorPinState == HIGH) {
m_offTimerStart = 0;
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
publishMqtt("on");
} else /*if (bri != 0)*/ {
// start switch off timer
m_offTimerStart = millis();
}
return true;
}
return false;
}
/**
* switch off the strip if the delay has elapsed
*/
bool handleOffTimer()
{
if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay)
{
if (enabled == true)
{
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
publishMqtt("off");
}
m_offTimerStart = 0;
return true;
}
return false;
}
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()
{
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;
}
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
}
/**
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop()
{
// only check sensors 4x/s
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
lastLoop = millis();
if (!updatePIRsensorState()) {
handleOffTimer();
}
}
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*
* Add PIR sensor state and switch off timer duration to jsoninfo
*/
void addToJsonInfo(JsonObject &root)
{
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
if (enabled)
{
// off timer
String uiDomString = F("PIR <i class=\"icons\">&#xe325;</i>");
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
if (m_offTimerStart > 0)
{
uiDomString = "";
unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000;
if (offSeconds >= 3600)
{
uiDomString += (offSeconds / 3600);
uiDomString += F("h ");
offSeconds %= 3600;
}
if (offSeconds >= 60)
{
uiDomString += (offSeconds / 60);
offSeconds %= 60;
}
else if (uiDomString.length() > 0)
{
uiDomString += 0;
}
if (uiDomString.length() > 0)
{
uiDomString += F("min ");
}
uiDomString += (offSeconds);
infoArr.add(uiDomString + F("s"));
} else {
infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
}
} else {
String uiDomString = F("PIR sensor");
JsonArray infoArr = user.createNestedArray(uiDomString);
infoArr.add(F("disabled"));
}
}
/**
* 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)
{
}
*/
/**
* provide the changeable values
*/
void addToConfig(JsonObject &root)
{
JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled;
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
top["pin"] = PIRsensorPin;
top[FPSTR(_onPreset)] = m_onPreset;
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."));
}
/**
* restore the changeable values
* 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)
{
bool oldEnabled = enabled;
int8_t oldPin = PIRsensorPin;
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
PIRsensorPin = top["pin"] | PIRsensorPin;
enabled = top[FPSTR(_enabled)] | enabled;
m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
m_onPreset = max(0,min(250,(int)m_onPreset));
m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
m_offPreset = max(0,min(250,(int)m_offPreset));
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) {
// reading config prior to setup()
DEBUG_PRINTLN(F(" config loaded."));
} else {
if (oldPin != PIRsensorPin || oldEnabled != enabled) {
// check if pin is OK
if (oldPin != PIRsensorPin && oldPin >= 0) {
// if we are changing pin in settings page
// deallocate old pin
pinManager.deallocatePin(oldPin, PinOwner::UM_PIR);
if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
pinMode(PIRsensorPin, INPUT_PULLUP);
} else {
// allocation failed
PIRsensorPin = -1;
enabled = false;
}
}
if (enabled) {
sensorPinState = digitalRead(PIRsensorPin);
}
}
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_offOnly)].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_PIRSWITCH;
}
};
// strings to reduce flash memory usage (used more than twice)
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
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";
#pragma once
#include "wled.h"
#ifndef PIR_SENSOR_PIN
// compatible with QuinLED-Dig-Uno
#ifdef ARDUINO_ARCH_ESP32
#define PIR_SENSOR_PIN 23 // Q4
#else //ESP8266 boards
#define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini)
#endif
#endif
/*
* This usermod handles PIR sensor states.
* The strip will be switched on and the off timer will be resetted when the sensor goes HIGH.
* When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off.
*
*
* Usermods allow you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
*
* v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example.
* Multiple v2 usermods can be added to one compilation easily.
*
* Creating a usermod:
* This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template.
* Please remember to rename the class and file to a descriptive name.
* You may also use multiple .h and .cpp files.
*
* Using a usermod:
* 1. Copy the usermod into the sketch folder (same folder as wled00.ino)
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
*/
class PIRsensorSwitch : public Usermod
{
public:
/**
* constructor
*/
PIRsensorSwitch() {}
/**
* desctructor
*/
~PIRsensorSwitch() {}
/**
* Enable/Disable the PIR sensor
*/
void EnablePIRsensor(bool en) { enabled = en; }
/**
* Get PIR sensor enabled/disabled state
*/
bool PIRsensorEnabled() { return enabled; }
private:
// PIR sensor pin
int8_t PIRsensorPin = PIR_SENSOR_PIN;
// notification mode for colorUpdated()
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
uint32_t m_offTimerStart = 0;
// current PIR sensor pin state
byte sensorPinState = LOW;
// PIR sensor enabled
bool enabled = true;
// status of initialisation
bool initDone = false;
// on and off presets
uint8_t m_onPreset = 0;
uint8_t m_offPreset = 0;
// flag to indicate that PIR sensor should activate WLED during nighttime only
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;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _switchOffDelay[];
static const char _enabled[];
static const char _onPreset[];
static const char _offPreset[];
static const char _nightTime[];
static const char _mqttOnly[];
static const char _offOnly[];
/**
* check if it is daytime
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
*/
bool isDayTime() {
bool isDayTime = false;
updateLocalTime();
uint8_t hr = hour(localTime);
uint8_t mi = minute(localTime);
if (sunrise && sunset) {
if (hour(sunrise)<hr && hour(sunset)>hr) {
isDayTime = true;
} else {
if (hour(sunrise)==hr && minute(sunrise)<mi) {
isDayTime = true;
}
if (hour(sunset)==hr && minute(sunset)>mi) {
isDayTime = true;
}
}
}
return isDayTime;
}
/**
* switch strip on/off
*/
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) {
applyPreset(m_offPreset);
} else if (switchOn && bri == 0) {
bri = briLast;
colorUpdated(NotifyUpdateMode);
} else if (!switchOn && bri != 0) {
briLast = bri;
bri = 0;
colorUpdated(NotifyUpdateMode);
}
}
void publishMqtt(const char* state)
{
//Check if MQTT Connected, otherwise it will crash the 8266
if (WLED_MQTT_CONNECTED){
char subuf[64];
strcpy(subuf, mqttDeviceTopic);
strcat_P(subuf, PSTR("/motion"));
mqtt->publish(subuf, 0, false, state);
}
}
/**
* Read and update PIR sensor state.
* Initilize/reset switch off timer
*/
bool updatePIRsensorState()
{
bool pinState = digitalRead(PIRsensorPin);
if (pinState != sensorPinState) {
sensorPinState = pinState; // change previous state
if (sensorPinState == HIGH) {
m_offTimerStart = 0;
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
publishMqtt("on");
} else /*if (bri != 0)*/ {
// start switch off timer
m_offTimerStart = millis();
}
return true;
}
return false;
}
/**
* switch off the strip if the delay has elapsed
*/
bool handleOffTimer()
{
if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay)
{
if (enabled == true)
{
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
publishMqtt("off");
}
m_offTimerStart = 0;
return true;
}
return false;
}
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()
{
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;
}
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
}
/**
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop()
{
// only check sensors 4x/s
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
lastLoop = millis();
if (!updatePIRsensorState()) {
handleOffTimer();
}
}
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*
* Add PIR sensor state and switch off timer duration to jsoninfo
*/
void addToJsonInfo(JsonObject &root)
{
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
if (enabled)
{
// off timer
String uiDomString = F("PIR <i class=\"icons\">&#xe325;</i>");
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
if (m_offTimerStart > 0)
{
uiDomString = "";
unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000;
if (offSeconds >= 3600)
{
uiDomString += (offSeconds / 3600);
uiDomString += F("h ");
offSeconds %= 3600;
}
if (offSeconds >= 60)
{
uiDomString += (offSeconds / 60);
offSeconds %= 60;
}
else if (uiDomString.length() > 0)
{
uiDomString += 0;
}
if (uiDomString.length() > 0)
{
uiDomString += F("min ");
}
uiDomString += (offSeconds);
infoArr.add(uiDomString + F("s"));
} else {
infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
}
} else {
String uiDomString = F("PIR sensor");
JsonArray infoArr = user.createNestedArray(uiDomString);
infoArr.add(F("disabled"));
}
}
/**
* 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)
{
}
*/
/**
* provide the changeable values
*/
void addToConfig(JsonObject &root)
{
JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled;
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
top["pin"] = PIRsensorPin;
top[FPSTR(_onPreset)] = m_onPreset;
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."));
}
/**
* restore the changeable values
* 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)
{
bool oldEnabled = enabled;
int8_t oldPin = PIRsensorPin;
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
PIRsensorPin = top["pin"] | PIRsensorPin;
enabled = top[FPSTR(_enabled)] | enabled;
m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
m_onPreset = max(0,min(250,(int)m_onPreset));
m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
m_offPreset = max(0,min(250,(int)m_offPreset));
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) {
// reading config prior to setup()
DEBUG_PRINTLN(F(" config loaded."));
} else {
if (oldPin != PIRsensorPin || oldEnabled != enabled) {
// check if pin is OK
if (oldPin != PIRsensorPin && oldPin >= 0) {
// if we are changing pin in settings page
// deallocate old pin
pinManager.deallocatePin(oldPin, PinOwner::UM_PIR);
if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
pinMode(PIRsensorPin, INPUT_PULLUP);
} else {
// allocation failed
PIRsensorPin = -1;
enabled = false;
}
}
if (enabled) {
sensorPinState = digitalRead(PIRsensorPin);
}
}
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_offOnly)].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_PIRSWITCH;
}
};
// strings to reduce flash memory usage (used more than twice)
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
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

@@ -81,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() {

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

@@ -2,16 +2,25 @@
This Usermod allows you to monitor the battery level of your battery powered project.
You can see the battery level in the `info modal` right under the `estimated current`.
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
@@ -45,6 +54,11 @@ Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.
* 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

View File

@@ -29,7 +29,7 @@
#endif
// the frequency to check the battery, 1 minute
// the frequency to check the battery, 30 sec
#ifndef USERMOD_BATTERY_MEASUREMENT_INTERVAL
#define USERMOD_BATTERY_MEASUREMENT_INTERVAL 30000
#endif
@@ -53,7 +53,8 @@ class UsermodBatteryBasic : public Usermod
int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN;
// how often to read the battery voltage
unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL;
unsigned long lastTime = 0;
unsigned long nextReadTime = 0;
unsigned long lastReadTime = 0;
// battery min. voltage
float minBatteryVoltage = USERMOD_BATTERY_MIN_VOLTAGE;
// battery max. voltage
@@ -68,6 +69,7 @@ class UsermodBatteryBasic : public Usermod
// 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)
@@ -82,6 +84,19 @@ class UsermodBatteryBasic : public Usermod
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:
@@ -107,6 +122,9 @@ class UsermodBatteryBasic : public Usermod
pinMode(batteryPin, INPUT);
#endif
nextReadTime = millis() + readingInterval;
lastReadTime = millis();
initDone = true;
}
@@ -129,26 +147,38 @@ class UsermodBatteryBasic : public Usermod
{
if(strip.isUpdating()) return;
unsigned long now = millis();
// check the battery level every USERMOD_BATTERY_MEASUREMENT_INTERVAL (ms)
if (now - lastTime >= readingInterval) {
if (millis() < nextReadTime) return;
// read battery raw input
rawValue = analogRead(batteryPin);
// calculate the voltage
voltage = (rawValue / adcPrecision) * maxBatteryVoltage ;
nextReadTime = millis() + readingInterval;
lastReadTime = millis();
initializing = false;
// 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);
// read battery raw input
rawValue = analogRead(batteryPin);
lastTime = now;
// 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());
}
}
@@ -163,9 +193,31 @@ class UsermodBatteryBasic : public Usermod
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
JsonArray battery = user.createNestedArray("Battery level");
battery.add(batteryLevel);
battery.add(F(" %"));
// 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"));
}

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;
}
}
/**
@@ -261,11 +266,12 @@ class MultiRelay : public Usermod {
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
}
@@ -386,7 +539,7 @@ class MultiRelay : public Usermod {
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) {
if (!_relay[i].external) {
switchRelay(i, _relay[i].state = (bool)bri);
switchRelay(i, offMode);
}
} else {
_relay[i].pin = -1;
@@ -396,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();
}
/**
@@ -416,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

@@ -40,7 +40,7 @@ class RgbRotaryEncoderUsermod : public Usermod
void initRotaryEncoder()
{
PinManagerPinType pins[2] = { { eaIo, false }, { ebIo, false } };
if (!pinManager.allocateMultiplePins(pins, 2, UM_RGBRotaryEncoder)) {
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) {
eaIo = -1;
ebIo = -1;
cleanup();
@@ -208,7 +208,7 @@ class RgbRotaryEncoderUsermod : public Usermod
lastKnownBri = bri;
updateLeds();
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
colorUpdated(CALL_MODE_DIRECT_CHANGE);
}
// If the brightness is changed not with the rotary, update the rotary
@@ -323,7 +323,7 @@ class RgbRotaryEncoderUsermod : public Usermod
*/
uint16_t getId()
{
return 0x4711;
return USERMOD_RGB_ROTARY_ENCODER;
}
//More methods can be added in the future, this example will then be extended.

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

@@ -114,6 +114,7 @@ class FourLineDisplayUsermod : public Usermod {
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; // 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
@@ -155,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:
@@ -248,6 +250,7 @@ class FourLineDisplayUsermod : public Usermod {
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
@@ -683,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."));
}
@@ -714,6 +718,7 @@ class FourLineDisplayUsermod : public Usermod {
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) {
@@ -739,12 +744,13 @@ class FourLineDisplayUsermod : public Usermod {
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["pin"][2]).isNull();
return !(top[_busClkFrequency]).isNull();
}
/*
@@ -757,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

@@ -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

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

View File

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

View File

@@ -65,87 +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)
uint8_t s = 0;
for (uint8_t i = 0; i < busses.getNumBusses(); i++) {
Bus* b = busses.getBus(i);
if (autoSegments) { //make one segment per bus
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
}
if (autoSegments) {
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);
} else {
//there are multiple segments, leave them, but prune length to total
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);
}
}
}
//segments are created in makeAutoSegments();
setBrightness(_brightness);
}
void WS2812FX::service() {
@@ -173,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
@@ -190,6 +157,7 @@ void WS2812FX::service() {
}
}
_virtualSegmentLength = 0;
busses.setSegmentCCT(-1);
if(doShow) {
yield();
show();
@@ -198,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
@@ -225,21 +189,10 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) {
void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
{
//auto calculate white channel value if enabled
if (isRgbw) {
if (rgbwMode == RGBW_MODE_AUTO_BRIGHTER || (w == 0 && (rgbwMode == RGBW_MODE_DUAL || rgbwMode == RGBW_MODE_LEGACY)))
{
//white value is set to lowest RGB channel
//thank you to @Def3nder!
w = r < g ? (r < b ? r : b) : (g < b ? g : b);
} else if (rgbwMode == RGBW_MODE_AUTO_ACCURATE && w == 0)
{
w = r < g ? (r < b ? r : b) : (g < b ? g : b);
r -= w; g -= w; b -= w;
}
}
if (SEGLEN) {//from segment
uint16_t realIndex = realPixelIndex(i);
uint16_t len = SEGMENT.length();
//color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments)
if (_bri_t < 255) {
r = scale8(r, _bri_t);
@@ -247,12 +200,9 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
b = scale8(b, _bri_t);
w = scale8(w, _bri_t);
}
uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
uint32_t col = RGBW32(r, g, b, w);
/* Set all the pixels in the group */
uint16_t realIndex = realPixelIndex(i);
uint16_t len = SEGMENT.length();
for (uint16_t j = 0; j < SEGMENT.grouping; j++) {
uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j);
if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) {
@@ -275,8 +225,7 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
}
} else { //live data, etc.
if (i < customMappingSize) i = customMappingTable[i];
uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
busses.setPixelColor(i, col);
busses.setPixelColor(i, RGBW32(r, g, b, w));
}
}
@@ -292,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
@@ -310,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.
@@ -463,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) {
@@ -491,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) {
@@ -586,12 +537,42 @@ uint32_t WS2812FX::getLastShow(void) {
return _lastShow;
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) {
uint16_t WS2812FX::getLengthTotal(void) {
return _length;
}
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;
}
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
@@ -621,6 +602,7 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
seg.grouping = grouping;
seg.spacing = spacing;
}
if (offset < UINT16_MAX) seg.offset = offset;
_segment_runtimes[n].reset();
}
@@ -640,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++)
{
@@ -647,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();
@@ -654,34 +638,98 @@ void WS2812FX::resetSegments() {
_segment_runtimes[0].reset();
}
void WS2812FX::populateDefaultSegments() {
uint16_t length = 0;
for (uint8_t i=0; i<busses.getNumBusses(); i++) {
Bus *bus = busses.getBus(i);
if (bus == nullptr) continue;
_segments[i].start = bus->getStart();
length += bus->getLength();
_segments[i].stop = _segments[i].start + bus->getLength();
_segments[i].mode = DEFAULT_MODE;
_segments[i].colors[0] = DEFAULT_COLOR;
_segments[i].speed = DEFAULT_SPEED;
_segments[i].intensity = DEFAULT_INTENSITY;
_segments[i].grouping = 1;
_segments[i].setOption(SEG_OPTION_SELECTED, 1);
_segments[i].setOption(SEG_OPTION_ON, 1);
_segments[i].opacity = 255;
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
}
}
@@ -708,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;
}
}
@@ -727,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);
}
/*
@@ -770,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;
@@ -814,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);
@@ -899,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;
}
@@ -1049,7 +1097,7 @@ uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8
}
//load custom mapping table from JSON file
//load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
void WS2812FX::deserializeMap(uint8_t n) {
char fileName[32];
strcpy_P(fileName, PSTR("/ledmap"));
@@ -1067,11 +1115,19 @@ void WS2812FX::deserializeMap(uint8_t n) {
return;
}
DynamicJsonDocument doc(JSON_BUFFER_SIZE); // full sized buffer for larger maps
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(7)) return;
#endif
DEBUG_PRINT(F("Reading LED map from "));
DEBUG_PRINTLN(fileName);
if (!readObjectFromFile(fileName, nullptr, &doc)) return; //if file does not exist just exit
if (!readObjectFromFile(fileName, nullptr, &doc)) {
releaseJSONBufferLock();
return; //if file does not exist just exit
}
// erase old custom ledmap
if (customMappingTable != nullptr) {
@@ -1088,6 +1144,8 @@ void WS2812FX::deserializeMap(uint8_t n) {
customMappingTable[i] = (uint16_t) map[i];
}
}
releaseJSONBufferLock();
}
//gamma 2.8 lookup table used for color correction
@@ -1128,15 +1186,20 @@ uint8_t WS2812FX::gamma8(uint8_t b)
uint32_t WS2812FX::gamma32(uint32_t color)
{
if (!gammaCorrectCol) return color;
uint8_t w = (color >> 24);
uint8_t r = (color >> 16);
uint8_t g = (color >> 8);
uint8_t b = color;
uint8_t w = W(color);
uint8_t r = R(color);
uint8_t g = G(color);
uint8_t b = B(color);
w = gammaT[w];
r = gammaT[r];
g = gammaT[g];
b = gammaT[b];
return ((w << 24) | (r << 16) | (g << 8) | (b));
return RGBW32(r, g, b, w);
}
WS2812FX* WS2812FX::instance = nullptr;
WS2812FX* WS2812FX::instance = nullptr;
//Bus static member definition, would belong in bus_manager.cpp
int16_t Bus::_cct = -1;
uint8_t Bus::_cctBlend = 0;
uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL;

View File

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

View File

@@ -10,20 +10,52 @@
#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];
}
@@ -41,77 +73,80 @@ struct BusConfig {
}
};
//parent class of BusDigital and BusPwm
//parent class of BusDigital, BusPwm, and BusNetwork
class Bus {
public:
Bus(uint8_t type, uint16_t start) {
_type = type;
_start = start;
};
virtual void show() {}
virtual bool canShow() { return true; }
Bus(uint8_t type, uint16_t start) {
_type = type;
_start = start;
};
virtual void setPixelColor(uint16_t pix, uint32_t c) {};
virtual ~Bus() {} //throw the bus under the bus
virtual void setBrightness(uint8_t b) {};
virtual void show() {}
virtual bool canShow() { return true; }
virtual void setStatusPixel(uint32_t c) {}
virtual void setPixelColor(uint16_t pix, uint32_t c) {}
virtual uint32_t getPixelColor(uint16_t pix) { return 0; }
virtual void setBrightness(uint8_t b) {}
virtual void cleanup() {}
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
inline uint16_t getLength() { return _len; }
virtual void setColorOrder() {}
virtual uint8_t getColorOrder() { return COL_ORDER_RGB; }
virtual uint8_t skippedLeds() { return 0; }
inline uint16_t getStart() { return _start; }
inline void setStart(uint16_t start) { _start = start; }
inline uint8_t getType() { return _type; }
inline bool isOk() { return _valid; }
inline bool isOffRefreshRequired() { return _needsRefresh; }
bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; }
virtual uint32_t getPixelColor(uint16_t pix) { return 0; };
virtual bool isRgbw() { return Bus::isRgbw(_type); }
static bool isRgbw(uint8_t type) {
if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true;
if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
return false;
}
static void setCCT(uint16_t cct) {
_cct = cct;
}
static void setCCTBlend(uint8_t b) {
if (b > 100) b = 100;
_cctBlend = (b * 127) / 100;
//compile-time limiter for hardware that can't power both white channels at max
#ifdef WLED_MAX_CCT_BLEND
if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND;
#endif
}
inline static void setAutoWhiteMode(uint8_t m) { if (m < 4) _autoWhiteMode = m; }
inline static uint8_t getAutoWhiteMode() { return _autoWhiteMode; }
virtual void cleanup() {};
virtual ~Bus() { //throw the bus under the bus
}
virtual uint8_t getPins(uint8_t* pinArray) { return 0; }
inline uint16_t getStart() {
return _start;
}
inline void setStart(uint16_t start) {
_start = start;
}
virtual uint16_t getLength() {
return 1;
}
virtual void setColorOrder() {}
virtual uint8_t getColorOrder() {
return COL_ORDER_RGB;
}
virtual bool isRgbw() {
return false;
}
virtual uint8_t skippedLeds() {
return 0;
}
inline uint8_t getType() {
return _type;
}
inline bool isOk() {
return _valid;
}
static bool isRgbw(uint8_t type) {
if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true;
if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true;
return false;
}
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);
}
};
@@ -128,6 +163,7 @@ class BusDigital : public Bus {
_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);
@@ -135,7 +171,7 @@ class BusDigital : public Bus {
_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() {
@@ -157,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);
@@ -188,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;
}
@@ -201,7 +244,7 @@ class BusDigital : public Bus {
}
void cleanup() {
//Serial.println("Digital Cleanup");
DEBUG_PRINTLN(F("Digital Cleanup."));
PolyBus::cleanup(_busPtr, _iType);
_iType = I_NONE;
_valid = false;
@@ -218,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;
};
@@ -227,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);
@@ -259,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;
@@ -301,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();
}
@@ -328,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], PinOwner::BusPwm);
}
#ifdef ARDUINO_ARCH_ESP32
pinManager.deallocateLedc(_ledcStart, numPins);
@@ -342,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() {
@@ -352,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
@@ -365,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);
@@ -383,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];
@@ -396,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;
}
}
@@ -412,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];
@@ -438,21 +633,13 @@ 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];

View File

@@ -5,15 +5,20 @@
*/
#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(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], CALL_MODE_BUTTON);
}
@@ -26,6 +31,44 @@ void shortPressAction(uint8_t b)
}
}
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");
}
}
bool isButtonPressed(uint8_t i)
{
if (btnPin[i]<0) return false;
@@ -175,6 +218,8 @@ void handleButton()
if (btnPin[b]<0 || buttonType[b] == BTN_TYPE_NONE) continue;
#endif
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;
@@ -186,61 +231,46 @@ void handleButton()
}
//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], CALL_MODE_BUTTON);}
else _setRandomColor(false,true);
// 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");
}
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])
{
} 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) {
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");
}
} else buttonWaitTime[b] = millis();
} else shortPressAction(b);
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);
}
@@ -269,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,6 +14,7 @@ void getStringFromJson(char* dest, const char* src, size_t len) {
}
bool deserializeConfig(JsonObject doc, bool fromFS) {
bool needsSave = false;
//int rev_major = doc["rev"][0]; // 1
//int rev_minor = doc["rev"][1]; // 0
@@ -60,7 +62,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(apBehavior, ap[F("behav")]);
/*
JsonArray ap_ip = ap["ip"];
for (byte i = 0; i < 4; i++) {
@@ -77,18 +78,18 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
// 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;
strip.isOffRefreshRequred = false;
uint8_t s = 0; // bus iterator
busses.removeAll();
uint32_t mem = 0;
for (JsonObject elm : ins) {
@@ -107,21 +108,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t colorOrder = (int)elm[F("order")];
uint8_t skipFirst = elm[F("skip")];
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"];
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);
if (bc.adjustBounds(ledCount)) {
//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);
s++;
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
@@ -227,7 +224,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(macroNl, light_nl["macro"]);
JsonObject def = doc[F("def")];
CJSON(bootPreset, def[F("ps")]);
CJSON(bootPreset, def["ps"]);
CJSON(turnOnAtBoot, def["on"]); // true
CJSON(briS, def["bri"]); // 128
@@ -263,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")];
@@ -402,22 +400,21 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
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);
}
@@ -429,19 +426,27 @@ void deserializeConfigFromFS() {
return;
}
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(1)) return;
#endif
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
success = readObjectFromFile("/cfg.json", nullptr, &doc);
if (!success) { //if file does not exist, try reading from EEPROM
deEEPSettings();
releaseJSONBufferLock();
return;
}
// NOTE: This routine deserializes *and* applies the configuration
// Therefore, must also initialize ethernet from this function
deserializeConfig(doc.as<JsonObject>(), true);
bool needsSave = deserializeConfig(doc.as<JsonObject>(), true);
releaseJSONBufferLock();
if (needsSave) serializeConfig(); // usermods required new prameters
}
void serializeConfig() {
@@ -449,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
@@ -500,15 +509,37 @@ void serializeConfig() {
#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");
@@ -525,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)
@@ -550,7 +583,7 @@ void serializeConfig() {
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;
@@ -580,7 +613,7 @@ void serializeConfig() {
light_nl["macro"] = macroNl;
JsonObject def = doc.createNestedObject("def");
def[F("ps")] = bootPreset;
def["ps"] = bootPreset;
def["on"] = turnOnAtBoot;
def["bri"] = briS;
@@ -723,8 +756,11 @@ void serializeConfig() {
dmx[F("start-led")] = DMXStartLED;
JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap"));
for (byte i = 0; i < 15; i++)
for (byte i = 0; i < 15; i++) {
dmx_fixmap.add(DMXFixtureMap[i]);
}
dmx[F("e131proxy")] = e131ProxyUniverse;
#endif
JsonObject usermods_settings = doc.createNestedObject("um");
@@ -733,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);
@@ -774,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");
@@ -815,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

@@ -59,6 +59,11 @@
#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
@@ -112,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
@@ -142,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)
@@ -230,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
@@ -241,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
@@ -252,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
@@ -282,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
@@ -292,15 +313,15 @@
#ifdef ESP8266
#define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards
#else
#define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards
#define LEDPIN 2 // Changed from 16 to restore compatibility with ESP32-pico
#endif
#endif
#ifdef WLED_ENABLE_DMX
#if (LEDPIN == 2)
#undef LEDPIN
#define LEDPIN 3
#warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 3."
#define LEDPIN 1
#warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 1."
#endif
#endif

View File

@@ -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>

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -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()
{
@@ -28,35 +29,49 @@
x.style.animation = 'none';
timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900);
}
function bLimits(b,p,m) {
maxB = b; maxM = m; maxPB = p;
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;
@@ -89,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)
@@ -115,87 +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+"c").style.display = (type > 40 && type < 48) ? "none":"inline"; // hide count for analog
gId("dig"+n+"s").style.display = (type > 40 && type < 48) ? "none":"inline"; // hide skip 1st for virtual & analog
gId("rev"+n).innerHTML = (type > 40 && type < 48) ? "Inverted":"Reverse (rotated 180°)"; // change reverse text for analog
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);
@@ -209,7 +265,7 @@
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;
@@ -221,15 +277,13 @@
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;
@@ -239,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>
@@ -251,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>
@@ -264,18 +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" class="xs" name="L0${i}" min="0" max="33" required onchange="UI()"/>
<span id="p1d${i}">Clock:</span> <input type="number" class="xs" name="L1${i}" min="0" max="33" onchange="UI()"/>
<span id="p2d${i}"></span><input type="number" class="xs" name="L2${i}" min="0" max="33" onchange="UI()"/>
<span id="p3d${i}"></span><input type="number" class="xs" name="L3${i}" min="0" max="33" onchange="UI()"/>
<span id="p4d${i}"></span><input type="number" class="xs" name="L4${i}" min="0" max="33" 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}c" style="display:inline">Count: <input type="number" name="LC${i}" class="l" min="0" max="${maxPB}" value="1" required oninput="UI()" /></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>
<div id="dig${i}r" style="display:inline"><span id="rev${i}">Reverse</span>: <input type="checkbox" name="CV${i}"></div>&nbsp;
<div id="dig${i}s" style="display:inline">Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}"></div>
<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);
}
@@ -286,14 +345,14 @@ Color Order:
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" class="xs" min="-1" max="40" name="${bt}" onchange="UI()" 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>`;
@@ -306,6 +365,21 @@ Color Order:
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)});
@@ -316,11 +390,71 @@ Color Order:
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>
@@ -332,7 +466,7 @@ Color Order:
<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>
@@ -363,19 +497,22 @@ Color Order:
</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>
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" class="s" min="0" max="100" name="TT" required><br>
IR pin: <input type="number" class="xs" min="-1" max="40" name="IR" onchange="UI()">&nbsp;<select name="IT" onchange="UI()">
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>
@@ -388,8 +525,8 @@ Color Order:
</select><span style="cursor: pointer;" onclick="off('IR')">&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://github.com/Aircoookie/WLED/wiki/Infrared-Control" target="_blank">IR info</a><br>
Relay pin: <input type="number" class="xs" min="-1" max="33" name="RL" onchange="UI()"> Invert <input type="checkbox" name="RM"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#215;</span><br>
<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>
@@ -398,7 +535,7 @@ Color Order:
<br><br>
Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br>
Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br><br>
Brightness factor: <input name="BF" type="number" class="s" min="1" max="255" required> %
Brightness factor: <input name="BF" type="number" class="s" min="1" max="255" required> %%
<h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br>
Transition Time: <input name="TD" type="number" class="l" min="0" max="65500"> ms<br>
@@ -413,6 +550,19 @@ Color Order:
<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">
@@ -421,16 +571,9 @@ Color Order:
<option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option>
</select><br>
<span class="wc">
Auto-calculate white channel from RGB:<br>
<select name="AW">
<option value=0>None</option>
<option value=1>Brighter</option>
<option value=2>Accurate</option>
<option value=3>Dual</option>
<option value=4>Legacy</option>
</select>
<br></span><hr>
<hr style="width:260px">
<div id="cfg">Config template: <input type="file" name="data2" accept=".json"> <input type="button" value="Apply" onclick="loadCfg(d.Sf.data2);"><br></div>
<hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>

View File

@@ -99,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>

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

@@ -9,6 +9,10 @@ body {
hr {
border-color: #666;
}
a {
color: #28f;
text-decoration: none;
}
button, .btn {
background: #333;
color: #fff;
@@ -35,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;

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;

View File

@@ -67,7 +67,9 @@ void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TOD
void colorFromDecOrHexString(byte* rgb, char* in);
bool colorFromHexString(byte* rgb, const char* in);
void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY)
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb);
//dmx.cpp
void initDMX();
@@ -93,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();
@@ -191,20 +199,35 @@ 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) {}
@@ -224,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

@@ -1,7 +1,7 @@
/*
* 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!
*/
@@ -24,7 +24,7 @@ function B(){window.history.back()}function RS(){window.location="/settings"}fun
const char PAGE_dmxmap[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta content="width=device-width" name="viewport">
<title>DMX Map</title><script>
function B(){window.history.back()}function RS(){window.location="/settings"}function RP(){top.location.href="/"}function FM() {%DMXVARS%
var t=["SET 0","RED","GREEN","BLUE","WHITE","SHUTTER","SET 255","DISABLED"],n=[];for(i=0;i<512;i++)n.push(7);for(i=0;i<LC;i++)for(FS=CS+CG*i,j=0;j<CN;j++)DA=FS+j,n[DA-1]=CH[j];for(DMXMap="",i=0;i<512;i++)isstart="",(i+1)%10==0&&(isstart="S"),DMXMap+='<div class="anytype '+isstart+" type"+n[i]+'">'+String(i+1)+"<br />"+t[n[i]]+"</div>";document.getElementById("map").innerHTML=DMXMap}
var n=["SET 0","RED","GREEN","BLUE","WHITE","SHUTTER","SET 255","DISABLED"],o=[];for(i=0;i<512;i++)o.push(7);for(i=0;i<LC;i++)for(FS=CS+CG*i,j=0;j<CN;j++)DA=FS+j,o[DA-1]=CH[j];for(DMXMap="",i=0;i<512;i++)DMXMap+='<div class="anytype type'+o[i]+'">'+String(i+1)+"<br />"+n[o[i]]+"</div>";document.getElementById("map").innerHTML=DMXMap}
</script><style>
.anytype{border:1px solid #fff;margin:1px;float:left;width:100px;height:100px}.S{margin:0;border:2px solid #fff}.type7{color:#888;border:1px dotted grey}.type6{color:#fff}.type4{color:#fff;font-weight:700}.type3{color:#00f;font-weight:700}.type2{color:#0f0;font-weight:700}.type1{color:red;font-weight:700}.bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%%;margin:0}
</style></head><body onload="FM()"><div id="map">...</div></body></html>)=====";
@@ -42,7 +42,7 @@ function B(){window.history.back()}function U(){document.getElementById("uf").st
.bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none}
</style></head><body><h2>WLED Software Update</h2><form method="POST"
action="/update" id="uf" enctype="multipart/form-data" onsubmit="U()">
Installed version: 0.13.0-b3<br>Download the latest binary: <a
Installed version: 0.13.0-b6<br>Download the latest binary: <a
href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img
src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square">
</a><br><input type="file" class="bt" name="update" required><br><input

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

244
wled00/improv.cpp Normal file
View File

@@ -0,0 +1,244 @@
#include "wled.h"
#ifdef WLED_DEBUG_IMPROV
#define DIMPROV_PRINT(x) Serial.print(x)
#define DIMPROV_PRINTLN(x) Serial.println(x)
#define DIMPROV_PRINTF(x...) Serial.printf(x)
#else
#define DIMPROV_PRINT(x)
#define DIMPROV_PRINTLN(x)
#define DIMPROV_PRINTF(x...)
#endif
#define IMPROV_VERSION 1
void parseWiFiCommand(char *rpcData);
enum ImprovPacketType {
Current_State = 0x01,
Error_State = 0x02,
RPC_Command = 0x03,
RPC_Response = 0x04
};
enum ImprovPacketByte {
Version = 6,
PacketType = 7,
Length = 8,
RPC_CommandType = 9
};
enum ImprovRPCType {
Command_Wifi = 0x01,
Request_State = 0x02,
Request_Info = 0x03
};
//File dbgf;
//blocking function to parse an Improv Serial packet
void handleImprovPacket() {
uint8_t header[6] = {'I','M','P','R','O','V'};
//dbgf = WLED_FS.open("/improv.log","a");
bool timeout = false;
uint8_t waitTime = 25;
uint16_t packetByte = 0;
uint8_t packetLen = 9;
uint8_t checksum = 0;
uint8_t rpcCommandType = 0;
char rpcData[128];
rpcData[0] = 0;
while (!timeout) {
if (Serial.available() < 1) {
delay(1);
waitTime--;
if (!waitTime) timeout = true;
continue;
}
byte next = Serial.read();
DIMPROV_PRINT("Received improv byte: "); DIMPROV_PRINTF("%x\r\n",next);
//f.write(next);
switch (packetByte) {
case ImprovPacketByte::Version: {
if (next != IMPROV_VERSION) {
DIMPROV_PRINTLN(F("Invalid version"));
//dbgf.close();
return;
}
break;
}
case ImprovPacketByte::PacketType: {
if (next != ImprovPacketType::RPC_Command) {
DIMPROV_PRINTF("Non RPC-command improv packet type %i\n",next);
//dbgf.close();
return;
}
if (!improvActive) improvActive = 1;
break;
}
case ImprovPacketByte::Length: packetLen = 9 + next; break;
case ImprovPacketByte::RPC_CommandType: rpcCommandType = next; break;
default: {
if (packetByte >= packetLen) { //end of packet, check checksum match
if (checksum != next) {
DIMPROV_PRINTF("Got RPC checksum %i, expected %i",next,checksum);
sendImprovStateResponse(0x01, true);
//dbgf.close();
return;
}
switch (rpcCommandType) {
case ImprovRPCType::Command_Wifi: parseWiFiCommand(rpcData); break;
case ImprovRPCType::Request_State: {
uint8_t improvState = 0x02; //authorized
if (WLED_WIFI_CONFIGURED) improvState = 0x03; //provisioning
if (Network.isConnected()) improvState = 0x04; //provisioned
sendImprovStateResponse(improvState, false);
if (improvState == 0x04) sendImprovRPCResponse(ImprovRPCType::Request_State);
break;
}
case ImprovRPCType::Request_Info: sendImprovInfoResponse(); break;
default: {
DIMPROV_PRINTF("Unknown RPC command %i\n",next);
sendImprovStateResponse(0x02, true);
}
}
//dbgf.close();
return;
}
if (packetByte < 6) { //check header
if (next != header[packetByte]) {
DIMPROV_PRINTLN(F("Invalid improv header"));
//dbgf.close();
return;
}
} else if (packetByte > 9) { //RPC data
rpcData[packetByte - 10] = next;
if (packetByte > 137) return; //prevent buffer overflow
}
}
}
checksum += next;
packetByte++;
}
//dbgf.close();
}
void sendImprovStateResponse(uint8_t state, bool error) {
if (!error && improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
if (error) improvError = state;
char out[11] = {'I','M','P','R','O','V'};
out[6] = IMPROV_VERSION;
out[7] = error? ImprovPacketType::Error_State : ImprovPacketType::Current_State;
out[8] = 1;
out[9] = state;
uint8_t checksum = 0;
for (uint8_t i = 0; i < 10; i++) checksum += out[i];
out[10] = checksum;
Serial.write((uint8_t*)out, 11);
Serial.write('\n');
}
void sendImprovRPCResponse(byte commandId) {
if (improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
uint8_t packetLen = 12;
char out[64] = {'I','M','P','R','O','V'};
out[6] = IMPROV_VERSION;
out[7] = ImprovPacketType::RPC_Response;
out[8] = 2; //Length (set below)
out[9] = commandId;
out[10] = 0; //Data len (set below)
out[11] = '\0'; //URL len (set below)
if (Network.isConnected())
{
IPAddress localIP = Network.localIP();
uint8_t len = sprintf(out+12, "http://%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
if (len > 24) return; //sprintf fail?
out[11] = len;
out[10] = 1 + len;
out[8] = 3 + len; //RPC command type + data len + url len + url
packetLen = 13 + len;
}
uint8_t checksum = 0;
for (uint8_t i = 0; i < packetLen -1; i++) checksum += out[i];
out[packetLen -1] = checksum;
Serial.write((uint8_t*)out, packetLen);
Serial.write('\n');
improvActive = 1; //no longer provisioning
}
void sendImprovInfoResponse() {
if (improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
uint8_t packetLen = 12;
char out[128] = {'I','M','P','R','O','V'};
out[6] = IMPROV_VERSION;
out[7] = ImprovPacketType::RPC_Response;
//out[8] = 2; //Length (set below)
out[9] = ImprovRPCType::Request_Info;
//out[10] = 0; //Data len (set below)
out[11] = 4; //Firmware len ("WLED")
out[12] = 'W'; out[13] = 'L'; out[14] = 'E'; out[15] = 'D';
uint8_t lengthSum = 17;
uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.13.0-b6/%i"),VERSION);
out[16] = vlen; lengthSum += vlen;
uint8_t hlen = 7;
#ifdef ESP8266
strcpy(out+lengthSum+1,"esp8266");
#else
hlen = 5;
strcpy(out+lengthSum+1,"esp32");
#endif
out[lengthSum] = hlen;
lengthSum += hlen + 1;
//Use serverDescription if it has been changed from the default "WLED", else mDNS name
bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0);
strcpy(out+lengthSum+1,useMdnsName ? cmDNS : serverDescription);
uint8_t nlen = strlen(useMdnsName ? cmDNS : serverDescription);
out[lengthSum] = nlen;
lengthSum += nlen + 1;
packetLen = lengthSum +1;
out[8] = lengthSum -9;
out[10] = lengthSum -11;
uint8_t checksum = 0;
for (uint8_t i = 0; i < packetLen -1; i++) checksum += out[i];
out[packetLen -1] = checksum;
Serial.write((uint8_t*)out, packetLen);
Serial.write('\n');
DIMPROV_PRINT("Info checksum");
DIMPROV_PRINTLN(checksum);
}
void parseWiFiCommand(char* rpcData) {
uint8_t len = rpcData[0];
if (!len || len > 126) return;
uint8_t ssidLen = rpcData[1];
if (ssidLen > len -1 || ssidLen > 32) return;
memset(clientSSID, 0, 32);
memcpy(clientSSID, rpcData+2, ssidLen);
memset(clientPass, 0, 64);
if (len > ssidLen +1) {
uint8_t passLen = rpcData[2+ssidLen];
memset(clientPass, 0, 64);
memcpy(clientPass, rpcData+3+ssidLen, passLen);
}
sendImprovStateResponse(0x03); //provisioning
improvActive = 2;
forceReconnect = true;
serializeConfig();
}

View File

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

View File

@@ -6,6 +6,21 @@
* JSON API (De)serialization
*/
bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255) {
if (elem.is<int>()) {
if (elem < 0) return false; //ignore e.g. {"ps":-1}
*val = elem;
return true;
} else if (elem.is<const char*>()) {
const char* str = elem;
size_t len = strnlen(str, 12);
if (len == 0 || len > 10) return false;
parseNumber(str, val, vmin, vmax);
return true;
}
return false; //key does not exist
}
void deserializeSegment(JsonObject elem, byte it, byte presetId)
{
byte id = elem["id"] | it;
@@ -49,7 +64,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
uint16_t grp = elem["grp"] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing;
strip.setSegment(id, start, stop, grp, spc);
uint16_t of = seg.offset;
uint16_t len = 1;
if (stop > start) len = stop - start;
@@ -58,22 +73,25 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
int offsetAbs = abs(offset);
if (offsetAbs > len - 1) offsetAbs %= len;
if (offset < 0) offsetAbs = len - offsetAbs;
seg.offset = offsetAbs;
of = offsetAbs;
}
if (stop > start && seg.offset > len -1) seg.offset = len -1;
if (stop > start && of > len -1) of = len -1;
strip.setSegment(id, start, stop, grp, spc, of);
int segbri = elem["bri"] | -1;
if (segbri == 0) {
seg.setOption(SEG_OPTION_ON, 0, id);
} else if (segbri > 0) {
seg.setOpacity(segbri, id);
seg.setOption(SEG_OPTION_ON, 1, id);
byte segbri = 0;
if (getVal(elem["bri"], &segbri)) {
if (segbri > 0) seg.setOpacity(segbri, id);
seg.setOption(SEG_OPTION_ON, segbri, id);
}
bool on = elem["on"] | seg.getOption(SEG_OPTION_ON);
if (elem["on"].is<const char*>() && elem["on"].as<const char*>()[0] == 't') on = !on;
seg.setOption(SEG_OPTION_ON, on, id);
uint8_t cctPrev = seg.cct;
seg.setCCT(elem["cct"] | seg.cct, id);
if (seg.cct != cctPrev && id == strip.getMainSegmentId()) effectChanged = true; //send UDP
JsonArray colarr = elem["col"];
if (!colarr.isNull())
{
@@ -131,26 +149,28 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
//if (pal != seg.palette && pal < strip.getPaletteCount()) strip.setPalette(pal);
seg.setOption(SEG_OPTION_SELECTED, elem[F("sel")] | seg.getOption(SEG_OPTION_SELECTED));
seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED));
seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED));
seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR ));
//temporary, strip object gets updated via colorUpdated()
if (id == strip.getMainSegmentId()) {
byte effectPrev = effectCurrent;
effectCurrent = elem["fx"] | effectCurrent;
if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
byte effectPrev = effectCurrent;
if (getVal(elem["fx"], &effectCurrent, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 0-255 exact value)
if (!presetId && effectCurrent != effectPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
}
effectSpeed = elem[F("sx")] | effectSpeed;
effectIntensity = elem[F("ix")] | effectIntensity;
effectPalette = elem["pal"] | effectPalette;
getVal(elem["pal"], &effectPalette, 1, strip.getPaletteCount());
} else { //permanent
byte fx = elem["fx"] | seg.mode;
if (fx != seg.mode && fx < strip.getModeCount()) {
byte fx = seg.mode;
byte fxPrev = fx;
if (getVal(elem["fx"], &fx, 1, strip.getModeCount())) { //load effect ('r' random, '~' inc/dec, 0-255 exact value)
strip.setMode(id, fx);
if (!presetId) unloadPlaylist(); //stop playlist if active and FX changed manually
if (!presetId && seg.mode != fxPrev) unloadPlaylist(); //stop playlist if active and FX changed manually
}
seg.speed = elem[F("sx")] | seg.speed;
seg.intensity = elem[F("ix")] | seg.intensity;
seg.palette = elem["pal"] | seg.palette;
getVal(elem["pal"], &seg.palette, 1, strip.getPaletteCount());
}
JsonArray iarr = elem[F("i")]; //set individual LEDs
@@ -191,7 +211,11 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
if (set < 2) stop = start + 1;
for (uint16_t i = start; i < stop; i++) {
strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
if (strip.gammaCorrectCol) {
strip.setPixelColor(i, strip.gamma8(rgbw[0]), strip.gamma8(rgbw[1]), strip.gamma8(rgbw[2]), strip.gamma8(rgbw[3]));
} else {
strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
}
}
if (!set) start++;
set = 0;
@@ -210,7 +234,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
strip.applyToAllSelected = false;
bool stateResponse = root[F("v")] | false;
bri = root["bri"] | bri;
getVal(root["bri"], &bri);
bool on = root["on"] | (bri > 0);
if (!on != !bri) toggleOnOff();
@@ -314,17 +338,21 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
usermods.readFromJsonState(root);
int ps = root[F("psave")] | -1;
loadLedmap = root[F("ledmap")] | loadLedmap;
byte ps = root[F("psave")];
if (ps > 0) {
savePreset(ps, true, nullptr, root);
} else {
ps = root[F("pdel")] | -1; //deletion
ps = root[F("pdel")]; //deletion
if (ps > 0) {
deletePreset(ps);
}
ps = root["ps"] | -1; //load preset (clears state request!)
if (ps >= 0) {
ps = presetCycCurr;
if (getVal(root["ps"], &ps, presetCycMin, presetCycMax)) { //load preset (clears state request!)
if (!presetId) unloadPlaylist(); //stop playlist if preset changed manually
if (ps >= presetCycMin && ps <= presetCycMax) presetCycCurr = ps;
applyPreset(ps, callMode);
return stateResponse;
}
@@ -365,6 +393,7 @@ void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool fo
root["on"] = seg.getOption(SEG_OPTION_ON);
byte segbri = seg.opacity;
root["bri"] = (segbri) ? segbri : 255;
root["cct"] = seg.cct;
if (segmentBounds && seg.name != nullptr) root["n"] = reinterpret_cast<const char *>(seg.name); //not good practice, but decreases required JSON buffer
@@ -411,7 +440,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
if (!forPreset) {
if (errorFlag) root[F("error")] = errorFlag;
root[F("ps")] = currentPreset;
root["ps"] = (currentPreset > 0) ? currentPreset : -1;
root[F("pl")] = currentPlaylist;
usermods.addToJsonState(root);
@@ -478,14 +507,22 @@ void serializeInfo(JsonObject root)
//root[F("cn")] = WLED_CODENAME;
JsonObject leds = root.createNestedObject("leds");
leds[F("count")] = ledCount;
leds[F("count")] = strip.getLengthTotal();
leds[F("rgbw")] = strip.isRgbw;
leds[F("wv")] = strip.isRgbw && (strip.rgbwMode == RGBW_MODE_MANUAL_ONLY || strip.rgbwMode == RGBW_MODE_DUAL); //should a white channel slider be displayed?
leds[F("wv")] = false;
leds["cct"] = correctWB || strip.hasCCTBus();
switch (Bus::getAutoWhiteMode()) {
case RGBW_MODE_MANUAL_ONLY:
case RGBW_MODE_DUAL:
if (strip.isRgbw) leds[F("wv")] = true;
break;
}
leds[F("pwr")] = strip.currentMilliamps;
leds[F("fps")] = strip.getFps();
leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0;
leds[F("maxseg")] = strip.getMaxSegments();
leds[F("seglock")] = false; //will be used in the future to prevent modifications to segment config
//leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config
root[F("str")] = syncToggleReceive;
@@ -547,7 +584,7 @@ void serializeInfo(JsonObject root)
root[F("resetReason0")] = (int)rtc_get_reset_reason(0);
root[F("resetReason1")] = (int)rtc_get_reset_reason(1);
#endif
root[F("lwip")] = 0;
root[F("lwip")] = 0; //deprecated
#else
root[F("arch")] = "esp8266";
root[F("core")] = ESP.getCoreVersion();
@@ -797,23 +834,29 @@ void serveJson(AsyncWebServerRequest* request)
return;
}
#ifdef WLED_USE_DYNAMIC_JSON
AsyncJsonResponse* response = new AsyncJsonResponse(JSON_BUFFER_SIZE);
JsonObject doc = response->getRoot();
#else
if (!requestJSONBufferLock(17)) return;
AsyncJsonResponse *response = new AsyncJsonResponse(&doc);
#endif
JsonObject lDoc = response->getRoot();
switch (subJson)
{
case 1: //state
serializeState(doc); break;
serializeState(lDoc); break;
case 2: //info
serializeInfo(doc); break;
serializeInfo(lDoc); break;
case 4: //node list
serializeNodes(doc); break;
serializeNodes(lDoc); break;
case 5: //palettes
serializePalettes(doc, request); break;
serializePalettes(lDoc, request); break;
default: //all
JsonObject state = doc.createNestedObject("state");
JsonObject state = lDoc.createNestedObject("state");
serializeState(state);
JsonObject info = doc.createNestedObject("info");
JsonObject info = lDoc.createNestedObject("info");
serializeInfo(info);
if (subJson != 3)
{
@@ -823,10 +866,11 @@ void serveJson(AsyncWebServerRequest* request)
}
DEBUG_PRINT("JSON buffer size: ");
DEBUG_PRINTLN(doc.memoryUsage());
DEBUG_PRINTLN(lDoc.memoryUsage());
response->setLength();
request->send(response);
releaseJSONBufferLock();
}
#define MAX_LIVE_LEDS 180
@@ -841,7 +885,7 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient)
#endif
}
uint16_t used = ledCount;
uint16_t used = strip.getLengthTotal();
uint16_t n = (used -1) /MAX_LIVE_LEDS +1; //only serve every n'th LED if count over MAX_LIVE_LEDS
char buffer[2000];
strcpy_P(buffer, PSTR("{\"leds\":["));

View File

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

View File

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

View File

@@ -29,6 +29,9 @@ Timezone* tz;
#define TZ_AUSTRALIA_NORTHERN 16
#define TZ_AUSTRALIA_SOUTHERN 17
#define TZ_HAWAII 18
#define TZ_NOVOSIBIRSK 19
#define TZ_ANCHORAGE 20
#define TZ_MX_CENTRAL 21
#define TZ_INIT 255
byte tzCurrent = TZ_INIT; //uninitialized
@@ -129,6 +132,21 @@ void updateTimezone() {
tcrStandard = tcrDaylight;
break;
}
case TZ_NOVOSIBIRSK : {
tcrDaylight = {Last, Sun, Mar, 1, 420}; //CST = UTC + 7 hours
tcrStandard = tcrDaylight;
break;
}
case TZ_ANCHORAGE : {
tcrDaylight = {Second, Sun, Mar, 2, -480}; //AKDT = UTC - 8 hours
tcrStandard = {First, Sun, Nov, 2, -540}; //AKST = UTC - 9 hours
break;
}
case TZ_MX_CENTRAL : {
tcrDaylight = {First, Sun, Apr, 2, -300}; //CDT = UTC - 5 hours
tcrStandard = {Last, Sun, Oct, 2, -360}; //CST = UTC - 6 hours
break;
}
}
tzCurrent = currentTimezone;
@@ -420,7 +438,7 @@ void calculateSunriseAndSunset() {
// there is a sunrise
tim_0.tm_hour = minUTC / 60;
tim_0.tm_min = minUTC % 60;
sunrise = tz->toLocal(mktime(&tim_0) - utcOffsetSecs);
sunrise = tz->toLocal(mktime(&tim_0) + utcOffsetSecs);
DEBUG_PRINTF("Sunrise: %02d:%02d\n", hour(sunrise), minute(sunrise));
} else {
sunrise = 0;
@@ -431,7 +449,7 @@ void calculateSunriseAndSunset() {
// there is a sunset
tim_0.tm_hour = minUTC / 60;
tim_0.tm_min = minUTC % 60;
sunset = tz->toLocal(mktime(&tim_0) - utcOffsetSecs);
sunset = tz->toLocal(mktime(&tim_0) + utcOffsetSecs);
DEBUG_PRINTF("Sunset: %02d:%02d\n", hour(sunset), minute(sunset));
} else {
sunset = 0;

View File

@@ -18,6 +18,7 @@ void initCronixie()
}
//handleOverlays is essentially the equivalent of usermods.loop
void handleOverlays()
{
initCronixie();
@@ -111,8 +112,8 @@ void _overlayAnalogCountdown()
}
}
void handleOverlayDraw() {
usermods.handleOverlayDraw();
if (!overlayCurrent) return;
switch (overlayCurrent)
{
@@ -121,7 +122,6 @@ void handleOverlayDraw() {
}
}
/*
* Support for the Cronixie clock
*/

View File

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

View File

@@ -36,7 +36,6 @@ enum struct PinOwner : uint8_t {
DMX = 0x8A, // 'DMX' == hard-coded to IO2
// Use UserMod IDs from const.h here
UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01
UM_RGBRotaryEncoder = USERMOD_ID_UNSPECIFIED, // 0x01 // No define in const.h for this user module -- consider adding?
UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h"
UM_Temperature = USERMOD_ID_TEMPERATURE, // 0x03 // Usermod "usermod_temperature.h"
// #define USERMOD_ID_FIXNETSERVICES // 0x04 // Usermod "usermod_Fix_unreachable_netservices.h" -- Does not allocate pins
@@ -53,6 +52,8 @@ enum struct PinOwner : uint8_t {
// #define USERMOD_ID_RTC // 0x0F // Usermod "usermod_rtc.h" -- Uses "standard" I2C pins ... TODO -- enable shared I2C bus use
// #define USERMOD_ID_ELEKSTUBE_IPS // 0x10 // Usermod "usermod_elekstube_ips.h" -- Uses quite a few pins ... see Hardware.h and User_Setup.h
// #define USERMOD_ID_SN_PHOTORESISTOR // 0x11 // Usermod "usermod_sn_photoresistor.h" -- Uses hard-coded pin (PHOTORESISTOR_PIN == A0), but could be easily updated to use pinManager
UM_RGBRotaryEncoder = USERMOD_RGB_ROTARY_ENCODER, // 0x16 // Usermod "rgb-rotary-encoder.h"
UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA, // 0x17 // Usermod "quinled-an-penta.h"
};
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");
@@ -91,6 +92,8 @@ class PinManagerClass {
bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None);
bool isPinOk(byte gpio, bool output = true);
PinOwner getPinOwner(byte gpio);
#ifdef ARDUINO_ARCH_ESP32
byte allocateLedc(byte channels);
void deallocateLedc(byte pos, byte channels);

View File

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

View File

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

View File

@@ -90,12 +90,16 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
}
strip.isRgbw = false;
uint8_t colorOrder, type, skip;
uint16_t length, start;
uint8_t pins[5] = {255, 255, 255, 255, 255};
autoSegments = request->hasArg(F("MS"));
correctWB = request->hasArg(F("CCT"));
cctFromRgb = request->hasArg(F("CR"));
strip.cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(strip.cctBlending);
Bus::setAutoWhiteMode(request->arg(F("AW")).toInt());
for (uint8_t s = 0; s < WLED_MAX_BUSSES; s++) {
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
@@ -105,6 +109,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char ls[4] = "LS"; ls[2] = 48+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = 48+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip 1st LED
char rf[4] = "RF"; rf[2] = 48+s; rf[3] = 0; //refresh required
if (!request->hasArg(lp)) {
DEBUG_PRINTLN(F("No data.")); break;
}
@@ -114,26 +119,22 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
pins[i] = (request->arg(lp).length() > 0) ? request->arg(lp).toInt() : 255;
}
type = request->arg(lt).toInt();
strip.isRgbw = strip.isRgbw || BusManager::isRgbw(type);
type |= request->hasArg(rf) << 7; // off refresh override
skip = request->hasArg(sl) ? LED_SKIP_AMOUNT : 0;
colorOrder = request->arg(co).toInt();
start = (request->hasArg(ls)) ? request->arg(ls).toInt() : t;
if (request->hasArg(lc) && request->arg(lc).toInt() > 0) {
length = request->arg(lc).toInt();
t += length = request->arg(lc).toInt();
} else {
break; // no parameter
}
colorOrder = request->arg(co).toInt();
start = (request->hasArg(ls)) ? request->arg(ls).toInt() : 0;
// actual finalization is done in WLED::loop() (removing old busses and adding new)
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder, request->hasArg(cv), skip);
doInitBusses = true;
}
t = request->arg(F("LC")).toInt();
if (t > 0 && t <= MAX_LEDS) ledCount = t;
// upate other pins
int hw_ir_pin = request->arg(F("IR")).toInt();
if (pinManager.allocatePin(hw_ir_pin,false, PinOwner::IR)) {
@@ -169,8 +170,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strip.ablMilliampsMax = request->arg(F("MA")).toInt();
strip.milliampsPerLed = request->arg(F("LA")).toInt();
strip.rgbwMode = request->arg(F("AW")).toInt();
briS = request->arg(F("CA")).toInt();
turnOnAtBoot = request->hasArg(F("BO"));
@@ -181,7 +180,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
fadeTransition = request->hasArg(F("TF"));
t = request->arg(F("TD")).toInt();
if (t > 0) transitionDelay = t;
if (t >= 0) transitionDelay = t;
transitionDelayDefault = t;
strip.paletteFade = request->hasArg(F("PF"));
@@ -434,7 +433,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
//USERMODS
if (subPage == 8)
{
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(5)) return;
#endif
JsonObject um = doc.createNestedObject("um");
size_t args = request->args();
@@ -506,12 +510,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
DEBUG_PRINTLN(value);
}
}
#ifdef WLED_DEBUG
serializeJson(um,Serial); DEBUG_PRINTLN();
#endif
usermods.readFromConfig(um); // force change of usermod parameters
}
releaseJSONBufferLock();
if (subPage != 2 && (subPage != 6 || !doReboot)) serializeConfig(); //do not save if factory reset or LED settings (which are saved after LED re-init)
if (subPage == 4) alexaInit();
}
@@ -525,21 +528,21 @@ int getNumVal(const String* req, uint16_t pos)
}
//helper to get int value at a position in string
bool updateVal(const String* req, const char* key, byte* val, byte minv, byte maxv)
//helper to get int value with in/decrementing support via ~ syntax
void parseNumber(const char* str, byte* val, byte minv, byte maxv)
{
int pos = req->indexOf(key);
if (pos < 1) return false;
if (req->charAt(pos+3) == '~') {
int out = getNumVal(req, pos+1);
if (str == nullptr || str[0] == '\0') return;
if (str[0] == 'r') {*val = random8(minv,maxv); return;}
if (str[0] == '~') {
int out = atoi(str +1);
if (out == 0)
{
if (req->charAt(pos+4) == '-')
if (str[1] == '0') return;
if (str[1] == '-')
{
*val = (*val <= minv)? maxv : *val -1;
*val = (int)(*val -1) < (int)minv ? maxv : min((int)maxv,(*val -1)); //-1, wrap around
} else {
*val = (*val >= maxv)? minv : *val +1;
*val = (int)(*val +1) > (int)maxv ? minv : max((int)minv,(*val +1)); //+1, wrap around
}
} else {
out += *val;
@@ -549,8 +552,26 @@ bool updateVal(const String* req, const char* key, byte* val, byte minv, byte ma
}
} else
{
*val = getNumVal(req, pos);
byte p1 = atoi(str);
const char* str2 = strchr(str,'~'); //min/max range (for preset cycle, e.g. "1~5~")
if (str2) {
byte p2 = atoi(str2+1);
presetCycMin = p1; presetCycMax = p2;
while (isdigit((str2+1)[0])) str2++;
parseNumber(str2+1, val, p1, p2);
} else {
*val = p1;
}
}
}
bool updateVal(const String* req, const char* key, byte* val, byte minv, byte maxv)
{
int pos = req->indexOf(key);
if (pos < 1) return false;
if (req->length() < (unsigned int)(pos + 4)) return false;
parseNumber(req->c_str() + pos +3, val, minv, maxv);
return true;
}
@@ -581,7 +602,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (t < strip.getMaxSegments()) selectedSeg = t;
}
WS2812FX::Segment& mainseg = strip.getSegment(selectedSeg);
WS2812FX::Segment& selseg = strip.getSegment(selectedSeg);
pos = req.indexOf(F("SV=")); //segment selected
if (pos > 0) {
byte t = getNumVal(&req, pos);
@@ -591,13 +612,13 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
strip.getSegment(i).setOption(SEG_OPTION_SELECTED, 0);
}
}
mainseg.setOption(SEG_OPTION_SELECTED, t);
selseg.setOption(SEG_OPTION_SELECTED, t);
}
uint16_t startI = mainseg.start;
uint16_t stopI = mainseg.stop;
uint8_t grpI = mainseg.grouping;
uint16_t spcI = mainseg.spacing;
uint16_t startI = selseg.start;
uint16_t stopI = selseg.stop;
uint8_t grpI = selseg.grouping;
uint16_t spcI = selseg.spacing;
pos = req.indexOf(F("&S=")); //segment start
if (pos > 0) {
startI = getNumVal(&req, pos);
@@ -617,17 +638,42 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
}
strip.setSegment(selectedSeg, startI, stopI, grpI, spcI);
pos = req.indexOf(F("RV=")); //Segment reverse
if (pos > 0) selseg.setOption(SEG_OPTION_REVERSED, req.charAt(pos+3) != '0');
pos = req.indexOf(F("MI=")); //Segment mirror
if (pos > 0) selseg.setOption(SEG_OPTION_MIRROR, req.charAt(pos+3) != '0');
pos = req.indexOf(F("SB=")); //Segment brightness/opacity
if (pos > 0) {
byte segbri = getNumVal(&req, pos);
selseg.setOption(SEG_OPTION_ON, segbri, selectedSeg);
if (segbri) {
selseg.setOpacity(segbri, selectedSeg);
}
}
pos = req.indexOf(F("SW=")); //segment power
if (pos > 0) {
switch (getNumVal(&req, pos)) {
case 0: selseg.setOption(SEG_OPTION_ON, false); break;
case 1: selseg.setOption(SEG_OPTION_ON, true); break;
default: selseg.setOption(SEG_OPTION_ON, !selseg.getOption(SEG_OPTION_ON)); break;
}
}
pos = req.indexOf(F("PS=")); //saves current in preset
if (pos > 0) savePreset(getNumVal(&req, pos));
pos = req.indexOf(F("P1=")); //sets first preset for cycle
if (pos > 0) presetCycleMin = getNumVal(&req, pos);
if (pos > 0) presetCycMin = getNumVal(&req, pos);
pos = req.indexOf(F("P2=")); //sets last preset for cycle
if (pos > 0) presetCycleMax = getNumVal(&req, pos);
if (pos > 0) presetCycMax = getNumVal(&req, pos);
//apply preset
if (updateVal(&req, "PL=", &presetCycCurr, presetCycleMin, presetCycleMax)) {
if (updateVal(&req, "PL=", &presetCycCurr, presetCycMin, presetCycMax)) {
unloadPlaylist();
applyPreset(presetCycCurr);
}
@@ -707,7 +753,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
strip.applyToAllSelected = true;
strip.setColor(2, t[0], t[1], t[2], t[3]);
} else {
strip.getSegment(selectedSeg).setColor(2,((t[0] << 16) + (t[1] << 8) + t[2] + (t[3] << 24)), selectedSeg);
selseg.setColor(2,((t[0] << 16) + (t[1] << 8) + t[2] + (t[3] << 24)), selectedSeg); // defined above (SS=)
}
}
@@ -811,24 +857,6 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("TT="));
if (pos > 0) transitionDelay = getNumVal(&req, pos);
//Segment reverse
pos = req.indexOf(F("RV="));
if (pos > 0) strip.getSegment(selectedSeg).setOption(SEG_OPTION_REVERSED, req.charAt(pos+3) != '0');
//Segment reverse
pos = req.indexOf(F("MI="));
if (pos > 0) strip.getSegment(selectedSeg).setOption(SEG_OPTION_MIRROR, req.charAt(pos+3) != '0');
//Segment brightness/opacity
pos = req.indexOf(F("SB="));
if (pos > 0) {
byte segbri = getNumVal(&req, pos);
strip.getSegment(selectedSeg).setOption(SEG_OPTION_ON, segbri, selectedSeg);
if (segbri) {
strip.getSegment(selectedSeg).setOpacity(segbri, selectedSeg);
}
}
//set time (unix timestamp)
pos = req.indexOf(F("ST="));
if (pos > 0) {

View File

@@ -101,7 +101,7 @@ void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) {
bool error = false;
uint8_t protocol = P_E131;
sbuff = reinterpret_cast<e131_packet_t *>(_packet.data());
e131_packet_t *sbuff = reinterpret_cast<e131_packet_t *>(_packet.data());
//E1.31 packet identifier ("ACS-E1.17")
if (memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id)))

View File

@@ -163,7 +163,6 @@ class ESPAsyncE131 {
static const uint32_t VECTOR_FRAME = 2;
static const uint8_t VECTOR_DMP = 2;
e131_packet_t *sbuff; // Pointer to scratch packet buffer
AsyncUDP udp; // AsyncUDP
// Internal Initializers

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
* UDP sync notifier / Realtime / Hyperion / TPM2.NET
*/
#define WLEDPACKETSIZE 37
#define WLEDPACKETSIZE 39
#define UDP_IN_MAXSIZE 1472
#define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times
@@ -25,6 +25,7 @@ void notify(byte callMode, bool followUp)
default: return;
}
byte udpOut[WLEDPACKETSIZE];
WS2812FX::Segment& mainseg = strip.getSegment(strip.getMainSegmentId());
udpOut[0] = 0; //0: wled notifier protocol 1: WARLS protocol
udpOut[1] = callMode;
udpOut[2] = bri;
@@ -40,8 +41,8 @@ void notify(byte callMode, bool followUp)
//0: old 1: supports white 2: supports secondary color
//3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette
//6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet
//9: supports sync groups, 37 byte packet
udpOut[11] = 9;
//9: supports sync groups, 37 byte packet 10: supports CCT, 39 byte packet
udpOut[11] = 10;
udpOut[12] = colSec[0];
udpOut[13] = colSec[1];
udpOut[14] = colSec[2];
@@ -50,7 +51,7 @@ void notify(byte callMode, bool followUp)
udpOut[17] = (transitionDelay >> 0) & 0xFF;
udpOut[18] = (transitionDelay >> 8) & 0xFF;
udpOut[19] = effectPalette;
uint32_t colTer = strip.getSegment(strip.getMainSegmentId()).colors[2];
uint32_t colTer = mainseg.colors[2];
udpOut[20] = (colTer >> 16) & 0xFF;
udpOut[21] = (colTer >> 8) & 0xFF;
udpOut[22] = (colTer >> 0) & 0xFF;
@@ -77,6 +78,11 @@ void notify(byte callMode, bool followUp)
//sync groups
udpOut[36] = syncGroups;
//Might be changed to Kelvin in the future, receiver code should handle that case
//0: byte 38 contains 0-255 value, 255: no valid CCT, 1-254: Kelvin value MSB
udpOut[37] = strip.hasCCTBus() ? 0 : 255; //check this is 0 for the next value to be significant
udpOut[38] = mainseg.cct;
IPAddress broadcastIp;
broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP());
@@ -89,11 +95,11 @@ void notify(byte callMode, bool followUp)
notificationTwoRequired = (followUp)? false:notifyTwice;
}
void realtimeLock(uint32_t timeoutMs, byte md)
{
if (!realtimeMode && !realtimeOverride){
for (uint16_t i = 0; i < ledCount; i++)
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 0; i < totalLen; i++)
{
strip.setPixelColor(i,0,0,0,0);
}
@@ -101,6 +107,10 @@ void realtimeLock(uint32_t timeoutMs, byte md)
realtimeTimeout = millis() + timeoutMs;
if (timeoutMs == 255001 || timeoutMs == 65000) realtimeTimeout = UINT32_MAX;
// if strip is off (bri==0) and not already in RTM
if (bri == 0 && !realtimeMode) {
strip.setBrightness(scaledBri(briLast));
}
realtimeMode = md;
if (arlsForceMaxBri && !realtimeOverride) strip.setBrightness(scaledBri(255));
@@ -165,10 +175,11 @@ void handleNotifications()
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION);
if (realtimeOverride) return;
uint16_t id = 0;
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 0; i < packetSize -2; i += 3)
{
setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0);
id++; if (id >= ledCount) break;
id++; if (id >= totalLen) break;
}
strip.show();
return;
@@ -255,6 +266,14 @@ void handleNotifications()
{
strip.setColor(2, udpIn[20], udpIn[21], udpIn[22], udpIn[23]); //tertiary color
}
if (version > 9 && version < 200 && udpIn[37] < 255) { //valid CCT/Kelvin value
uint8_t cct = udpIn[38];
if (udpIn[37] > 0) { //Kelvin
cct = (((udpIn[37] << 8) + udpIn[38]) - 1900) >> 5;
}
uint8_t segid = strip.getMainSegmentId();
strip.getSegment(segid).setCCT(cct, segid);
}
}
}
@@ -336,9 +355,10 @@ void handleNotifications()
byte numPackets = udpIn[5];
uint16_t id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 6; i < tpmPayloadFrameSize + 4; i += 3)
{
if (id < ledCount)
if (id < totalLen)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
@@ -369,6 +389,7 @@ void handleNotifications()
}
if (realtimeOverride) return;
uint16_t totalLen = strip.getLengthTotal();
if (udpIn[0] == 1) //warls
{
for (uint16_t i = 2; i < packetSize -3; i += 4)
@@ -382,7 +403,7 @@ void handleNotifications()
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++; if (id >= ledCount) break;
id++; if (id >= totalLen) break;
}
} else if (udpIn[0] == 3) //drgbw
{
@@ -391,14 +412,14 @@ void handleNotifications()
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++; if (id >= ledCount) break;
id++; if (id >= totalLen) break;
}
} else if (udpIn[0] == 4) //dnrgb
{
uint16_t id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (uint16_t i = 4; i < packetSize -2; i += 3)
{
if (id >= ledCount) break;
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
}
@@ -407,7 +428,7 @@ void handleNotifications()
uint16_t id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (uint16_t i = 4; i < packetSize -2; i += 4)
{
if (id >= ledCount) break;
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++;
}
@@ -435,7 +456,7 @@ void handleNotifications()
void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w)
{
uint16_t pix = i + arlsOffset;
if (pix < ledCount)
if (pix < strip.getLengthTotal())
{
if (!arlsDisableGammaCorrection && strip.gammaCorrectCol)
{
@@ -476,6 +497,7 @@ void sendSysInfoUDP()
if (!udp2Connected) return;
IPAddress ip = Network.localIP();
if (!ip || ip == IPAddress(255,255,255,255)) ip = IPAddress(4,3,2,1);
// TODO: make a nice struct of it and clean up
// 0: 1 byte 'binary token 255'
@@ -514,3 +536,121 @@ void sendSysInfoUDP()
notifier2Udp.write(data, sizeof(data));
notifier2Udp.endPacket();
}
/*********************************************************************************************\
* Art-Net, DDP, E131 output - work in progress
\*********************************************************************************************/
#define DDP_HEADER_LEN 10
#define DDP_SYNCPACKET_LEN 10
#define DDP_FLAGS1_VER 0xc0 // version mask
#define DDP_FLAGS1_VER1 0x40 // version=1
#define DDP_FLAGS1_PUSH 0x01
#define DDP_FLAGS1_QUERY 0x02
#define DDP_FLAGS1_REPLY 0x04
#define DDP_FLAGS1_STORAGE 0x08
#define DDP_FLAGS1_TIME 0x10
#define DDP_ID_DISPLAY 1
#define DDP_ID_CONFIG 250
#define DDP_ID_STATUS 251
// 1440 channels per packet
#define DDP_CHANNELS_PER_PACKET 1440 // 480 leds
//
// Send real time UDP updates to the specified client
//
// type - protocol type (0=DDP, 1=E1.31, 2=ArtNet)
// client - the IP address to send to
// length - the number of pixels
// buffer - a buffer of at least length*4 bytes long
// isRGBW - true if the buffer contains 4 components per pixel
uint8_t sequenceNumber = 0; // this needs to be shared across all outputs
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW) {
if (!interfacesInited) return 1; // network not initialised
WiFiUDP ddpUdp;
switch (type) {
case 0: // DDP
{
// calclate the number of UDP packets we need to send
uint16_t channelCount = length * 3; // 1 channel for every R,G,B value
uint16_t packetCount = channelCount / DDP_CHANNELS_PER_PACKET;
if (channelCount % DDP_CHANNELS_PER_PACKET) {
packetCount++;
}
// there are 3 channels per RGB pixel
uint32_t channel = 0; // TODO: allow specifying the start channel
// the current position in the buffer
uint16_t bufferOffset = 0;
for (uint16_t currentPacket = 0; currentPacket < packetCount; currentPacket++) {
if (sequenceNumber > 15) sequenceNumber = 0;
if (!ddpUdp.beginPacket(client, DDP_DEFAULT_PORT)) { // port defined in ESPAsyncE131.h
DEBUG_PRINTLN(F("WiFiUDP.beginPacket returned an error"));
return 1; // problem
}
// the amount of data is AFTER the header in the current packet
uint16_t packetSize = DDP_CHANNELS_PER_PACKET;
uint8_t flags = DDP_FLAGS1_VER1;
if (currentPacket == (packetCount - 1)) {
// last packet, set the push flag
// TODO: determine if we want to send an empty push packet to each destination after sending the pixel data
flags = DDP_FLAGS1_VER1 | DDP_FLAGS1_PUSH;
if (channelCount % DDP_CHANNELS_PER_PACKET) {
packetSize = channelCount % DDP_CHANNELS_PER_PACKET;
}
}
// write the header
/*0*/ddpUdp.write(flags);
/*1*/ddpUdp.write(sequenceNumber++ & 0x0F); // sequence may be unnecessary unless we are sending twice (as requested in Sync settings)
/*2*/ddpUdp.write(0);
/*3*/ddpUdp.write(DDP_ID_DISPLAY);
// data offset in bytes, 32-bit number, MSB first
/*4*/ddpUdp.write(0xFF & (channel >> 24));
/*5*/ddpUdp.write(0xFF & (channel >> 16));
/*6*/ddpUdp.write(0xFF & (channel >> 8));
/*7*/ddpUdp.write(0xFF & (channel ));
// data length in bytes, 16-bit number, MSB first
/*8*/ddpUdp.write(0xFF & (packetSize >> 8));
/*9*/ddpUdp.write(0xFF & (packetSize ));
// write the colors, the write write(const uint8_t *buffer, size_t size)
// function is just a loop internally too
for (uint16_t i = 0; i < packetSize; i += 3) {
ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // R
ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // G
ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // B
if (isRGBW) bufferOffset++;
}
if (!ddpUdp.endPacket()) {
DEBUG_PRINTLN(F("WiFiUDP.endPacket returned an error"));
return 1; // problem
}
channel += packetSize;
}
} break;
case 1: //E1.31
{
} break;
case 2: //ArtNet
{
} break;
}
return 0;
}

View File

@@ -4,7 +4,15 @@
*/
//Usermod Manager internals
void UsermodManager::loop() { for (byte i = 0; i < numMods; i++) ums[i]->loop(); }
void UsermodManager::loop() { for (byte i = 0; i < numMods; i++) ums[i]->loop(); }
void UsermodManager::handleOverlayDraw() { for (byte i = 0; i < numMods; i++) ums[i]->handleOverlayDraw(); }
bool UsermodManager::handleButton(uint8_t b) {
bool overrideIO = false;
for (byte i = 0; i < numMods; i++) {
if (ums[i]->handleButton(b)) overrideIO = true;
}
return overrideIO;
}
void UsermodManager::setup() { for (byte i = 0; i < numMods; i++) ums[i]->setup(); }
void UsermodManager::connected() { for (byte i = 0; i < numMods; i++) ums[i]->connected(); }

View File

@@ -23,7 +23,9 @@
#include "../usermods/SN_Photoresistor/usermod_sn_photoresistor.h"
#endif
//#include "usermod_v2_empty.h"
#ifdef USERMOD_PWM_FAN
#include "../usermods/PWM_fan/usermod_PWM_fan.h"
#endif
#ifdef USERMOD_BUZZER
#include "../usermods/buzzer/usermod_v2_buzzer.h"
@@ -44,10 +46,18 @@
#include "../usermods/BME280_v2/usermod_bme280.h"
#endif
#ifdef USERMOD_FOUR_LINE_DISPLAY
#include "../usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h"
#ifdef USE_ALT_DISPlAY
#include "../usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h"
#else
#include "../usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h"
#endif
#endif
#ifdef USERMOD_ROTARY_ENCODER_UI
#include "../usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h"
#ifdef USE_ALT_DISPlAY
#include "../usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h"
#else
#include "../usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h"
#endif
#endif
#ifdef USERMOD_AUTO_SAVE
#include "../usermods/usermod_v2_auto_save/usermod_v2_auto_save.h"
@@ -86,6 +96,14 @@
#include "../usermods/rgb-rotary-encoder/rgb-rotary-encoder.h"
#endif
#ifdef USERMOD_SEVEN_SEGMENT
#include "../usermods/seven_segment_display/usermod_v2_seven_segment_display.h"
#endif
#ifdef QUINLED_AN_PENTA
#include "../usermods/quinled-an-penta/quinled-an-penta.h"
#endif
void registerUsermods()
{
/*
@@ -107,7 +125,9 @@ void registerUsermods()
usermods.add(new Usermod_SN_Photoresistor());
#endif
//usermods.add(new UsermodRenameMe());
#ifdef USERMOD_PWM_FAN
usermods.add(new PWMFanUsermod());
#endif
#ifdef USERMOD_BUZZER
usermods.add(new BuzzerUsermod());
@@ -167,4 +187,12 @@ void registerUsermods()
#ifdef RGB_ROTARY_ENCODER
usermods.add(new RgbRotaryEncoderUsermod());
#endif
#ifdef USERMOD_SEVEN_SEGMENT
usermods.add(new SevenSegmentDisplay());
#endif
#ifdef QUINLED_AN_PENTA
usermods.add(new QuinLEDAnPentaUsermod());
#endif
}

33
wled00/util.cpp Normal file
View File

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

View File

@@ -42,7 +42,7 @@ bool oappendi(int i)
bool oappend(const char* txt)
{
uint16_t len = strlen(txt);
if (olen + len >= OMAX)
if (olen + len >= SETTINGS_STACK_BUF_SIZE)
return false; // buffer full
strcpy(obuf + olen, txt);
olen += len;
@@ -120,6 +120,10 @@ void WiFiEvent(WiFiEvent_t event)
void WLED::loop()
{
#ifdef WLED_DEBUG
static unsigned long maxUsermodMillis = 0;
#endif
handleTime();
handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too
handleConnection();
@@ -130,7 +134,15 @@ void WLED::loop()
handleDMX();
#endif
userLoop();
#ifdef WLED_DEBUG
unsigned long usermodMillis = millis();
#endif
usermods.loop();
#ifdef WLED_DEBUG
usermodMillis = millis() - usermodMillis;
if (usermodMillis > maxUsermodMillis) maxUsermodMillis = usermodMillis;
#endif
yield();
handleIO();
@@ -159,7 +171,9 @@ void WLED::loop()
yield();
handleHue();
#ifndef WLED_DISABLE_BLYNK
handleBlynk();
#endif
yield();
@@ -195,36 +209,36 @@ void WLED::loop()
if (doInitBusses) {
doInitBusses = false;
DEBUG_PRINTLN(F("Re-init busses."));
bool aligned = strip.checkSegmentAlignment(); //see if old segments match old bus(ses)
busses.removeAll();
uint32_t mem = 0;
strip.isRgbw = false;
for (uint8_t i = 0; i < WLED_MAX_BUSSES; i++) {
if (busConfigs[i] == nullptr) break;
if (busConfigs[i]->adjustBounds(ledCount)) {
mem += busses.memUsage(*busConfigs[i]);
if (mem <= MAX_LED_MEMORY) {
busses.add(*busConfigs[i]);
//RGBW mode is enabled if at least one of the strips is RGBW
strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(busConfigs[i]->type));
//refresh is required to remain off if at least one of the strips requires the refresh.
strip.isOffRefreshRequred |= BusManager::isOffRefreshRequred(busConfigs[i]->type);
}
mem += BusManager::memUsage(*busConfigs[i]);
if (mem <= MAX_LED_MEMORY) {
busses.add(*busConfigs[i]);
}
delete busConfigs[i]; busConfigs[i] = nullptr;
}
strip.finalizeInit(ledCount);
strip.finalizeInit();
loadLedmap = 0;
if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments();
yield();
serializeConfig();
}
if (loadLedmap >= 0) {
strip.deserializeMap(loadLedmap);
loadLedmap = -1;
}
yield();
handleWs();
handleStatusLED();
// DEBUG serial logging
// DEBUG serial logging (every 30s)
#ifdef WLED_DEBUG
if (millis() - debugTime > 9999) {
if (millis() - debugTime > 29999) {
DEBUG_PRINTLN(F("---DEBUG INFO---"));
DEBUG_PRINT(F("Runtime: ")); DEBUG_PRINTLN(millis());
DEBUG_PRINT(F("Unix time: ")); toki.printTime(toki.getTime());
@@ -236,17 +250,19 @@ void WLED::loop()
} else
DEBUG_PRINTLN(F("No PSRAM"));
#endif
DEBUG_PRINT(F("Wifi state: ")); DEBUG_PRINTLN(WiFi.status());
DEBUG_PRINT(F("Wifi state: ")); DEBUG_PRINTLN(WiFi.status());
if (WiFi.status() != lastWifiState) {
wifiStateChangedTime = millis();
}
lastWifiState = WiFi.status();
DEBUG_PRINT(F("State time: ")); DEBUG_PRINTLN(wifiStateChangedTime);
DEBUG_PRINT(F("NTP last sync: ")); DEBUG_PRINTLN(ntpLastSyncTime);
DEBUG_PRINT(F("Client IP: ")); DEBUG_PRINTLN(Network.localIP());
DEBUG_PRINT(F("Loops/sec: ")); DEBUG_PRINTLN(loops / 10);
DEBUG_PRINT(F("State time: ")); DEBUG_PRINTLN(wifiStateChangedTime);
DEBUG_PRINT(F("NTP last sync: ")); DEBUG_PRINTLN(ntpLastSyncTime);
DEBUG_PRINT(F("Client IP: ")); DEBUG_PRINTLN(Network.localIP());
DEBUG_PRINT(F("Loops/sec: ")); DEBUG_PRINTLN(loops / 30);
DEBUG_PRINT(F("Max UM time[ms]: ")); DEBUG_PRINTLN(maxUsermodMillis);
loops = 0;
maxUsermodMillis = 0;
debugTime = millis();
}
loops++;
@@ -277,7 +293,6 @@ void WLED::setup()
#endif
DEBUG_PRINT(F("heap "));
DEBUG_PRINTLN(ESP.getFreeHeap());
registerUsermods();
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
if (psramFound()) {
@@ -293,10 +308,13 @@ void WLED::setup()
#ifdef WLED_DEBUG
pinManager.allocatePin(1, true, PinOwner::DebugOut); // GPIO1 reserved for debug output
#endif
#ifdef WLED_USE_DMX //reserve GPIO2 as hardcoded DMX pin
#ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin
pinManager.allocatePin(2, true, PinOwner::DMX);
#endif
DEBUG_PRINTLN(F("Registering usermods ..."));
registerUsermods();
for (uint8_t i=1; i<WLED_MAX_BUTTONS; i++) btnPin[i] = -1;
bool fsinit = false;
@@ -329,6 +347,7 @@ void WLED::setup()
DEBUG_PRINTLN(F("Usermods setup"));
userSetup();
usermods.setup();
if (strcmp(clientSSID, DEFAULT_CLIENT_SSID) == 0)
showWelcomePage = true;
WiFi.persistent(false);
@@ -337,7 +356,9 @@ void WLED::setup()
#endif
#ifdef WLED_ENABLE_ADALIGHT
if (!pinManager.isPinAllocated(3)) {
//Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused
//Serial TX (Debug, Improv, Serial JSON) only possible if GPIO1 unused
if (!pinManager.isPinAllocated(3) && !pinManager.isPinAllocated(1)) {
Serial.println(F("Ada"));
}
#endif
@@ -360,6 +381,8 @@ void WLED::setup()
sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6);
}
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
strip.service();
#ifndef WLED_DISABLE_OTA
@@ -377,6 +400,8 @@ void WLED::setup()
#ifdef WLED_ENABLE_DMX
initDMX();
#endif
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
// HTTP server page init
initServer();
@@ -388,22 +413,21 @@ void WLED::setup()
void WLED::beginStrip()
{
// Initialize NeoPixel Strip and button
if (ledCount > MAX_LEDS || ledCount == 0)
ledCount = 30;
strip.finalizeInit(ledCount);
strip.finalizeInit(); // busses created during deserializeConfig()
strip.deserializeMap();
strip.makeAutoSegments();
strip.setBrightness(0);
strip.setShowCallback(handleOverlayDraw);
if (bootPreset > 0) {
applyPreset(bootPreset, CALL_MODE_INIT);
} else if (turnOnAtBoot) {
if (turnOnAtBoot) {
if (briS > 0) bri = briS;
else if (bri == 0) bri = 128;
} else {
briLast = briS; bri = 0;
}
if (bootPreset > 0) {
applyPreset(bootPreset, CALL_MODE_INIT);
}
colorUpdated(CALL_MODE_INIT);
// init relay pin
@@ -439,6 +463,7 @@ void WLED::initAP(bool resetAP)
udp2Connected = notifier2Udp.begin(udpPort2);
}
e131.begin(false, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT);
ddp.begin(false, DDP_DEFAULT_PORT);
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
dnsServer.start(53, "*", WiFi.softAPIP());
@@ -591,10 +616,10 @@ void WLED::initConnection()
void WLED::initInterfaces()
{
IPAddress ipAddress = Network.localIP();
DEBUG_PRINTLN(F("Init STA interfaces"));
#ifndef WLED_DISABLE_HUESYNC
IPAddress ipAddress = Network.localIP();
if (hueIP[0] == 0) {
hueIP[0] = ipAddress[0];
hueIP[1] = ipAddress[1];
@@ -612,14 +637,13 @@ void WLED::initInterfaces()
#endif
strip.service();
// Set up mDNS responder:
if (strlen(cmDNS) > 0) {
#ifndef WLED_DISABLE_OTA
if (!aOtaEnabled) //ArduinoOTA begins mDNS for us if enabled
MDNS.begin(cmDNS);
#else
// "end" must be called before "begin" is called a 2nd time
// see https://github.com/esp8266/Arduino/issues/7213
MDNS.end();
MDNS.begin(cmDNS);
#endif
DEBUG_PRINTLN(F("mDNS started"));
MDNS.addService("http", "tcp", 80);
@@ -642,25 +666,27 @@ void WLED::initInterfaces()
initBlynk(blynkApiKey, blynkHost, blynkPort);
#endif
e131.begin(e131Multicast, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT);
ddp.begin(false, DDP_DEFAULT_PORT);
reconnectHue();
initMqtt();
interfacesInited = true;
wasConnected = true;
}
byte stacO = 0;
uint32_t lastHeap;
unsigned long heapTime = 0;
void WLED::handleConnection()
{
if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS))
static byte stacO = 0;
static uint32_t lastHeap = UINT32_MAX;
static unsigned long heapTime = 0;
unsigned long now = millis();
if (now < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS))
return;
if (lastReconnectAttempt == 0)
initConnection();
// reconnect WiFi to clear stale allocations if heap gets too low
if (millis() - heapTime > 5000) {
if (now - heapTime > 5000) {
uint32_t heap = ESP.getFreeHeap();
if (heap < JSON_BUFFER_SIZE+512 && lastHeap < JSON_BUFFER_SIZE+512) {
DEBUG_PRINT(F("Heap too low! "));
@@ -668,7 +694,7 @@ void WLED::handleConnection()
forceReconnect = true;
}
lastHeap = heap;
heapTime = millis();
heapTime = now;
}
byte stac = 0;
@@ -688,7 +714,7 @@ void WLED::handleConnection()
if (stac)
WiFi.disconnect(); // disable search so that AP can work
else
initConnection(); // restart search
initConnection(); // restart search
}
}
}
@@ -706,14 +732,26 @@ void WLED::handleConnection()
interfacesInited = false;
initConnection();
}
if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED)
//send improv failed 6 seconds after second init attempt (24 sec. after provisioning)
if (improvActive > 2 && now - lastReconnectAttempt > 6000) {
sendImprovStateResponse(0x03, true);
improvActive = 2;
}
if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && WLED_WIFI_CONFIGURED) {
if (improvActive == 2) improvActive = 3;
initConnection();
if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN))
}
if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN))
initAP();
} else if (!interfacesInited) { // newly connected
} else if (!interfacesInited) { //newly connected
DEBUG_PRINTLN("");
DEBUG_PRINT(F("Connected! IP address: "));
DEBUG_PRINTLN(Network.localIP());
if (improvActive) {
if (improvError == 3) sendImprovStateResponse(0x00, true);
sendImprovStateResponse(0x04);
if (improvActive > 1) sendImprovRPCResponse(0x01);
}
initInterfaces();
userConnected();
usermods.connected();
@@ -762,4 +800,4 @@ void WLED::handleStatusLED()
}
#endif
}
}

View File

@@ -3,12 +3,12 @@
/*
Main sketch, global variable declarations
@title WLED project sketch
@version 0.13.0-b3
@version 0.13.0-b6
@author Christian Schwinne
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2109220
#define VERSION 2112080
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -31,9 +31,11 @@
#ifndef WLED_DISABLE_MQTT
#define WLED_ENABLE_MQTT // saves 12kb
#endif
#define WLED_ENABLE_ADALIGHT // saves 500b only
#define WLED_ENABLE_ADALIGHT // saves 500b only (uses GPIO3 (RX) for serial)
//#define WLED_ENABLE_DMX // uses 3.5kb (use LEDPIN other than 2)
#define WLED_ENABLE_LOXONE // uses 1.2kb
#ifndef WLED_DISABLE_LOXONE
#define WLED_ENABLE_LOXONE // uses 1.2kb
#endif
#ifndef WLED_DISABLE_WEBSOCKETS
#define WLED_ENABLE_WEBSOCKETS
#endif
@@ -85,6 +87,7 @@
#include <WiFiUdp.h>
#include <DNSServer.h>
#ifndef WLED_DISABLE_OTA
#define NO_OTA_PORT
#include <ArduinoOTA.h>
#endif
#include <SPIFFSEditor.h>
@@ -224,11 +227,7 @@ WLED_GLOBAL bool rlyMde _INIT(true);
WLED_GLOBAL bool rlyMde _INIT(RLYMDE);
#endif
#ifndef IRPIN
#ifdef WLED_DISABLE_INFRARED
WLED_GLOBAL int8_t irPin _INIT(-1);
#else
WLED_GLOBAL int8_t irPin _INIT(4);
#endif
WLED_GLOBAL int8_t irPin _INIT(-1);
#else
WLED_GLOBAL int8_t irPin _INIT(IRPIN);
#endif
@@ -263,7 +262,6 @@ WLED_GLOBAL bool noWifiSleep _INIT(false);
#endif
// LED CONFIG
WLED_GLOBAL uint16_t ledCount _INIT(DEFAULT_LED_COUNT); // overcurrent prevented by ABL
WLED_GLOBAL bool turnOnAtBoot _INIT(true); // turn on LEDs at power-up
WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load after power-up
@@ -271,6 +269,8 @@ WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load
//if false, only one segment spanning the total LEDs is created,
//but not on LED settings save if there is more than one segment currently
WLED_GLOBAL bool autoSegments _INIT(false);
WLED_GLOBAL bool correctWB _INIT(false); //CCT color correction of RGB color
WLED_GLOBAL bool cctFromRgb _INIT(false); //CCT is calculated from RGB instead of using seg.cct
WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color.
WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color
@@ -367,7 +367,7 @@ WLED_GLOBAL byte currentTimezone _INIT(0); // Timezone ID. Refer to timez
WLED_GLOBAL int utcOffsetSecs _INIT(0); // Seconds to offset from UTC before timzone calculation
WLED_GLOBAL byte overlayDefault _INIT(0); // 0: no overlay 1: analog clock 2: single-digit clock 3: cronixie
WLED_GLOBAL byte overlayMin _INIT(0), overlayMax _INIT(ledCount - 1); // boundaries of overlay mode
WLED_GLOBAL byte overlayMin _INIT(0), overlayMax _INIT(DEFAULT_LED_COUNT - 1); // boundaries of overlay mode
WLED_GLOBAL byte analogClock12pixel _INIT(0); // The pixel in your strip where "midnight" would be
WLED_GLOBAL bool analogClockSecondsTrail _INIT(false); // Display seconds as trail of LEDs instead of a single pixel
@@ -508,17 +508,21 @@ WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, 25
// blynk
WLED_GLOBAL bool blynkEnabled _INIT(false);
//improv
WLED_GLOBAL byte improvActive _INIT(0); //0: no improv packet received, 1: improv active, 2: provisioning
WLED_GLOBAL byte improvError _INIT(0);
//playlists
WLED_GLOBAL unsigned long presetCycledTime _INIT(0);
WLED_GLOBAL int16_t currentPlaylist _INIT(-1);
//still used for "PL=~" HTTP API command
WLED_GLOBAL byte presetCycleMin _INIT(1), presetCycleMax _INIT(5);
WLED_GLOBAL byte presetCycCurr _INIT(presetCycleMin);
WLED_GLOBAL byte presetCycCurr _INIT(0);
WLED_GLOBAL byte presetCycMin _INIT(1);
WLED_GLOBAL byte presetCycMax _INIT(5);
// realtime
WLED_GLOBAL byte realtimeMode _INIT(REALTIME_MODE_INACTIVE);
WLED_GLOBAL byte realtimeOverride _INIT(REALTIME_OVERRIDE_NONE);
WLED_GLOBAL IPAddress realtimeIP _INIT_N(((0, 0, 0, 0)));;
WLED_GLOBAL IPAddress realtimeIP _INIT_N(((0, 0, 0, 0)));
WLED_GLOBAL unsigned long realtimeTimeout _INIT(0);
WLED_GLOBAL uint8_t tpmPacketCount _INIT(0);
WLED_GLOBAL uint16_t tpmPayloadFrameSize _INIT(0);
@@ -565,7 +569,7 @@ WLED_GLOBAL JsonDocument* fileDoc;
WLED_GLOBAL bool doCloseFile _INIT(false);
// presets
WLED_GLOBAL int16_t currentPreset _INIT(-1);
WLED_GLOBAL byte currentPreset _INIT(0);
WLED_GLOBAL byte errorFlag _INIT(0);
@@ -587,6 +591,7 @@ WLED_GLOBAL AsyncMqttClient* mqtt _INIT(NULL);
WLED_GLOBAL WiFiUDP notifierUdp, rgbUdp, notifier2Udp;
WLED_GLOBAL WiFiUDP ntpUdp;
WLED_GLOBAL ESPAsyncE131 e131 _INIT_N(((handleE131Packet)));
WLED_GLOBAL ESPAsyncE131 ddp _INIT_N(((handleE131Packet)));
WLED_GLOBAL bool e131NewData _INIT(false);
// led fx library object
@@ -594,10 +599,16 @@ WLED_GLOBAL BusManager busses _INIT(BusManager());
WLED_GLOBAL WS2812FX strip _INIT(WS2812FX());
WLED_GLOBAL BusConfig* busConfigs[WLED_MAX_BUSSES] _INIT({nullptr}); //temporary, to remember values from network callback until after
WLED_GLOBAL bool doInitBusses _INIT(false);
WLED_GLOBAL int8_t loadLedmap _INIT(-1);
// Usermod manager
WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
#ifndef WLED_USE_DYNAMIC_JSON
// global ArduinoJson buffer
WLED_GLOBAL StaticJsonDocument<JSON_BUFFER_SIZE> doc;
#endif
WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0);
// enable additional debug output
#ifdef WLED_DEBUG
@@ -628,7 +639,7 @@ WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
WLED_GLOBAL unsigned long debugTime _INIT(0);
WLED_GLOBAL int lastWifiState _INIT(3);
WLED_GLOBAL unsigned long wifiStateChangedTime _INIT(0);
WLED_GLOBAL int loops _INIT(0);
WLED_GLOBAL unsigned long loops _INIT(0);
#endif
#ifdef ARDUINO_ARCH_ESP32
@@ -639,6 +650,13 @@ WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0)
#define WLED_MQTT_CONNECTED (mqtt != nullptr && mqtt->connected())
//color mangling macros
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
#define R(c) (byte((c) >> 16))
#define G(c) (byte((c) >> 8))
#define B(c) (byte(c))
#define W(c) (byte((c) >> 24))
// append new c string to temp buffer efficiently
bool oappend(const char* txt);
// append new number to temp buffer efficiently

View File

@@ -89,7 +89,16 @@ void loadSettingsFromEEPROM()
if (apChannel > 13 || apChannel < 1) apChannel = 1;
apHide = EEPROM.read(228);
if (apHide > 1) apHide = 1;
ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); if (ledCount > MAX_LEDS || ledCount == 0) ledCount = 30;
uint16_t length = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); //was ledCount
if (length > MAX_LEDS || length == 0) length = 30;
uint8_t pins[5] = {2, 255, 255, 255, 255};
uint8_t colorOrder = COL_ORDER_GRB;
if (lastEEPROMversion > 9) colorOrder = EEPROM.read(383);
if (colorOrder > COL_ORDER_GBR) colorOrder = COL_ORDER_GRB;
bool skipFirst = EEPROM.read(2204);
bool reversed = EEPROM.read(252);
BusConfig bc = BusConfig(EEPROM.read(372) ? TYPE_SK6812_RGBW : TYPE_WS2812_RGB, pins, 0, length, colorOrder, reversed, skipFirst);
busses.add(bc);
notifyButton = EEPROM.read(230);
notifyTwice = EEPROM.read(231);
@@ -143,7 +152,7 @@ void loadSettingsFromEEPROM()
arlsOffset = EEPROM.read(368);
if (!EEPROM.read(367)) arlsOffset = -arlsOffset;
turnOnAtBoot = EEPROM.read(369);
strip.isRgbw = EEPROM.read(372);
//strip.isRgbw = EEPROM.read(372);
//374 - strip.paletteFade
apBehavior = EEPROM.read(376);
@@ -320,7 +329,7 @@ void loadSettingsFromEEPROM()
receiveDirect = !EEPROM.read(2200);
notifyMacro = EEPROM.read(2201);
strip.rgbwMode = EEPROM.read(2203);
//strip.rgbwMode = EEPROM.read(2203);
//skipFirstLed = EEPROM.read(2204);
bootPreset = EEPROM.read(389);
@@ -373,8 +382,13 @@ void deEEP() {
DEBUG_PRINTLN(F("Preset file not found, attempting to load from EEPROM"));
DEBUGFS_PRINTLN(F("Allocating saving buffer for dEEP"));
DynamicJsonDocument dDoc(JSON_BUFFER_SIZE *2);
JsonObject sObj = dDoc.to<JsonObject>();
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(8)) return;
#endif
JsonObject sObj = doc.to<JsonObject>();
sObj.createNestedObject("0");
EEPROM.begin(EEPSIZE);
@@ -433,8 +447,6 @@ void deEEP() {
}
}
for (uint16_t index = 1; index <= 16; index++) { //copy macros to presets.json
char m[65];
readStringFromEEPROM(1024+64*(index-1), m, 64);
@@ -454,10 +466,14 @@ void deEEP() {
File f = WLED_FS.open("/presets.json", "w");
if (!f) {
errorFlag = ERR_FS_GENERAL;
releaseJSONBufferLock();
return;
}
serializeJson(dDoc, f);
serializeJson(doc, f);
f.close();
releaseJSONBufferLock();
DEBUG_PRINTLN(F("deEEP complete!"));
}

View File

@@ -4,18 +4,6 @@
#ifdef WLED_USE_ETHERNET
#include "pin_manager.h"
// The following six pins are neither configurable nor
// can they be re-assigned through IOMUX / GPIO matrix.
// See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-ethernet-kit-v1.1.html#ip101gri-phy-interface
const managed_pin_type esp32_nonconfigurable_ethernet_pins[6] = {
{ 21, true }, // RMII EMAC TX EN == When high, clocks the data on TXD0 and TXD1 to transmitter
{ 19, true }, // RMII EMAC TXD0 == First bit of transmitted data
{ 22, true }, // RMII EMAC TXD1 == Second bit of transmitted data
{ 25, false }, // RMII EMAC RXD0 == First bit of received data
{ 26, false }, // RMII EMAC RXD1 == Second bit of received data
{ 27, true }, // RMII EMAC CRS_DV == Carrier Sense and RX Data Valid
};
// For ESP32, the remaining five pins are at least somewhat configurable.
// eth_address is in range [0..31], indicates which PHY (MAC?) address should be allocated to the interface
// eth_power is an output GPIO pin used to enable/disable the ethernet port (and/or external oscillator)
@@ -37,15 +25,16 @@ typedef struct EthernetSettings {
eth_clock_mode_t eth_clk_mode;
} ethernet_settings;
ethernet_settings ethernetBoards[] = {
const ethernet_settings ethernetBoards[] = {
// None
{
},
// WT32-EHT01
// (*) NOTE: silkscreen on board revision v1.2 may be wrong:
// silkscreen on v1.2 says IO35, but appears to be IO5
// silkscreen on v1.2 says RXD, and appears to be IO35
// Please note, from my testing only these pins work for LED outputs:
// IO2, IO4, IO12, IO14, IO15
// These pins do not appear to work from my testing:
// IO35, IO36, IO39
{
1, // eth_address,
16, // eth_power,
@@ -97,14 +86,27 @@ ethernet_settings ethernetBoards[] = {
// ESP3DEUXQuattro
{
1, // eth_address,
-1, // eth_power,
23, // eth_mdc,
18, // eth_mdio,
ETH_PHY_LAN8720, // eth_type,
ETH_CLOCK_GPIO17_OUT // eth_clk_mode
1, // eth_address,
-1, // eth_power,
23, // eth_mdc,
18, // eth_mdio,
ETH_PHY_LAN8720, // eth_type,
ETH_CLOCK_GPIO17_OUT // eth_clk_mode
}
};
#endif
#define WLED_ETH_RSVD_PINS_COUNT 6
// The following six pins are neither configurable nor
// can they be re-assigned through IOMUX / GPIO matrix.
// See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-ethernet-kit-v1.1.html#ip101gri-phy-interface
const managed_pin_type esp32_nonconfigurable_ethernet_pins[WLED_ETH_RSVD_PINS_COUNT] = {
{ 21, true }, // RMII EMAC TX EN == When high, clocks the data on TXD0 and TXD1 to transmitter
{ 19, true }, // RMII EMAC TXD0 == First bit of transmitted data
{ 22, true }, // RMII EMAC TXD1 == Second bit of transmitted data
{ 25, false }, // RMII EMAC RXD0 == First bit of received data
{ 26, false }, // RMII EMAC RXD1 == Second bit of received data
{ 27, true }, // RMII EMAC CRS_DV == Carrier Sense and RX Data Valid
};
#endif

View File

@@ -15,10 +15,10 @@
#define modd(x, y) ((x) - (int)((x) / (y)) * (y))
float cos_t(float x)
float cos_t(float phi)
{
x = modd(x, TWO_PI);
char sign = 1;
float x = modd(phi, TWO_PI);
int8_t sign = 1;
if (x > PI)
{
x -= PI;
@@ -28,7 +28,7 @@ float cos_t(float x)
float res = sign * (1 - ((xx) / (2)) + ((xx * xx) / (24)) - ((xx * xx * xx) / (720)) + ((xx * xx * xx * xx) / (40320)) - ((xx * xx * xx * xx * xx) / (3628800)) + ((xx * xx * xx * xx * xx * xx) / (479001600)));
#ifdef WLED_DEBUG_MATH
Serial.printf("cos: %f,%f\n",res,cos(x));
Serial.printf("cos: %f,%f,%f,(%f)\n",phi,res,cos(x),res-cos(x));
#endif
return res;
}
@@ -36,7 +36,7 @@ float cos_t(float x)
float sin_t(float x) {
float res = cos_t(HALF_PI - x);
#ifdef WLED_DEBUG_MATH
Serial.printf("sin: %f,%f\n",res,sin(x));
Serial.printf("sin: %f,%f,%f,(%f)\n",x,res,sin(x),res-sin(x));
#endif
return res;
}
@@ -46,7 +46,7 @@ float tan_t(float x) {
if (c==0.0) return 0;
float res = sin_t(x) / c;
#ifdef WLED_DEBUG_MATH
Serial.printf("tan: %f,%f\n",res,tan(x));
Serial.printf("tan: %f,%f,%f,(%f)\n",x,res,tan(x),res-tan(x));
#endif
return res;
}
@@ -67,7 +67,7 @@ float acos_t(float x) {
ret = ret - 2 * negate * ret;
float res = negate * PI + ret;
#ifdef WLED_DEBUG_MATH
Serial.printf("acos,%f,%f,%f\n",x,res,acos(x));
Serial.printf("acos: %f,%f,%f,(%f)\n",x,res,acos(x),res-acos(x));
#endif
return res;
}
@@ -75,7 +75,7 @@ float acos_t(float x) {
float asin_t(float x) {
float res = HALF_PI - acos_t(x);
#ifdef WLED_DEBUG_MATH
Serial.printf("asin,%f,%f,%f\n",x,res,asin(x));
Serial.printf("asin: %f,%f,%f,(%f)\n",x,res,asin(x),res-asin(x));
#endif
return res;
}
@@ -121,7 +121,7 @@ float floor_t(float x) {
int val = x;
if (neg) val--;
#ifdef WLED_DEBUG_MATH
Serial.printf("floor: %f,%f\n",val,floor(x));
Serial.printf("floor: %f,%f,%f\n",x,(float)val,floor(x));
#endif
return val;
}
@@ -130,7 +130,7 @@ float fmod_t(float num, float denom) {
int tquot = num / denom;
float res = num - tquot * denom;
#ifdef WLED_DEBUG_MATH
Serial.printf("fmod: %f,%f\n",res,fmod(num,denom));
Serial.printf("fmod: %f,%f,(%f)\n",res,fmod(num,denom),res-fmod(num,denom));
#endif
return res;
}

View File

@@ -16,7 +16,7 @@ enum class AdaState {
Data_Blue,
TPM2_Header_Type,
TPM2_Header_CountHi,
TPM2_Header_CountLo
TPM2_Header_CountLo,
};
void handleSerial()
@@ -41,27 +41,37 @@ void handleSerial()
else if (next == 0xC9) { //TPM2 start byte
state = AdaState::TPM2_Header_Type;
}
else if (next == '{') { //JSON API
else if (next == 'I') {
handleImprovPacket();
return;
} else if (next == 'v') {
Serial.print("WLED"); Serial.write(' '); Serial.println(VERSION);
} else if (next == '{') { //JSON API
bool verboseResponse = false;
{
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
Serial.setTimeout(100);
DeserializationError error = deserializeJson(doc, Serial);
if (error) return;
fileDoc = &doc;
verboseResponse = deserializeState(doc.as<JsonObject>());
fileDoc = nullptr;
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(16)) return;
#endif
Serial.setTimeout(100);
DeserializationError error = deserializeJson(doc, Serial);
if (error) {
releaseJSONBufferLock();
return;
}
verboseResponse = deserializeState(doc.as<JsonObject>());
//only send response if TX pin is unused for other purposes
if (verboseResponse && !pinManager.isPinAllocated(1)) {
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
doc.clear();
JsonObject state = doc.createNestedObject("state");
serializeState(state);
JsonObject info = doc.createNestedObject("info");
serializeInfo(info);
serializeJson(doc, Serial);
Serial.println();
}
releaseJSONBufferLock();
}
break;
case AdaState::Header_d:

View File

@@ -106,11 +106,18 @@ void initServer()
bool verboseResponse = false;
bool isConfig = false;
{ //scope JsonDocument so it releases its buffer
DynamicJsonDocument jsonBuffer(JSON_BUFFER_SIZE);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
JsonObject root = jsonBuffer.as<JsonObject>();
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
#else
if (!requestJSONBufferLock(14)) return;
#endif
DeserializationError error = deserializeJson(doc, (uint8_t*)(request->_tempObject));
JsonObject root = doc.as<JsonObject>();
if (error || root.isNull()) {
request->send(400, "application/json", F("{\"error\":9}")); return;
releaseJSONBufferLock();
request->send(400, "application/json", F("{\"error\":9}"));
return;
}
const String& url = request->url();
isConfig = url.indexOf("cfg") > -1;
@@ -120,12 +127,11 @@ void initServer()
serializeJson(root,Serial);
DEBUG_PRINTLN();
#endif
fileDoc = &jsonBuffer; // used for applying presets (presets.cpp)
verboseResponse = deserializeState(root);
fileDoc = nullptr;
} else {
verboseResponse = deserializeConfig(root); //use verboseResponse to determine whether cfg change should be saved immediately
}
releaseJSONBufferLock();
}
if (verboseResponse) {
if (!isConfig) {
@@ -362,9 +368,10 @@ void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& h
String settingsProcessor(const String& var)
{
if (var == "CSS") {
char buf[2048];
char buf[SETTINGS_STACK_BUF_SIZE];
buf[0] = 0;
getSettingsJS(optionType, buf);
//Serial.println(uxTaskGetStackHighWaterMark(NULL));
return String(buf);
}
@@ -387,7 +394,7 @@ String dmxProcessor(const String& var)
mapJS += "\nCN=" + String(DMXChannels) + ";\n";
mapJS += "CS=" + String(DMXStart) + ";\n";
mapJS += "CG=" + String(DMXGap) + ";\n";
mapJS += "LC=" + String(ledCount) + ";\n";
mapJS += "LC=" + String(strip.getLengthTotal()) + ";\n";
mapJS += "var CH=[";
for (int i=0;i<15;i++) {
mapJS += String(DMXFixtureMap[i]) + ",";

View File

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

View File

@@ -1,4 +1,5 @@
#include "wled.h"
#include "wled_ethernet.h"
/*
* Sending XML status files to client
@@ -59,9 +60,9 @@ void XML_response(AsyncWebServerRequest *request, char* dest)
oappend(SET_F("</wv><ws>"));
oappendi(colSec[3]);
oappend(SET_F("</ws><ps>"));
oappendi((currentPreset < 1) ? 0:currentPreset);
oappendi(currentPreset);
oappend(SET_F("</ps><cy>"));
oappendi(currentPlaylist > 0);
oappendi(currentPlaylist >= 0);
oappend(SET_F("</cy><ds>"));
oappend(serverDescription);
if (realtimeMode)
@@ -186,6 +187,52 @@ void sappends(char stype, const char* key, char* val)
}
}
void extractPin(JsonObject &obj, const char *key) {
if (obj[key].is<JsonArray>()) {
JsonArray pins = obj[key].as<JsonArray>();
for (JsonVariant pv : pins) {
if (pv.as<int>() > -1) { oappend(","); oappendi(pv.as<int>()); }
}
} else {
if (obj[key].as<int>() > -1) { oappend(","); oappendi(obj[key].as<int>()); }
}
}
// oappend used pins by scanning JsonObject (1 level deep)
void fillUMPins(JsonObject &mods)
{
for (JsonPair kv : mods) {
// kv.key() is usermod name or subobject key
// kv.value() is object itself
JsonObject obj = kv.value();
if (!obj.isNull()) {
// element is an JsonObject
if (!obj["pin"].isNull()) {
extractPin(obj, "pin");
} else {
// scan keys (just one level deep as is possible with usermods)
for (JsonPair so : obj) {
const char *key = so.key().c_str();
if (strstr(key, "pin")) {
// we found a key containing "pin" substring
if (strlen(strstr(key, "pin")) == 3) {
// and it is at the end, we found another pin
extractPin(obj, key);
continue;
}
}
if (!obj[so.key()].is<JsonObject>()) continue;
JsonObject subObj = obj[so.key()];
if (!subObj["pin"].isNull()) {
// get pins from subobject
extractPin(subObj, "pin");
}
}
}
}
}
}
//get values for settings form in javascript
void getSettingsJS(byte subPage, char* dest)
@@ -198,7 +245,8 @@ void getSettingsJS(byte subPage, char* dest)
if (subPage <1 || subPage >8) return;
if (subPage == 1) {
if (subPage == 1)
{
sappends('s',SET_F("CS"),clientSSID);
byte l = strlen(clientPass);
@@ -264,68 +312,81 @@ void getSettingsJS(byte subPage, char* dest)
}
}
if (subPage == 2) {
if (subPage == 2)
{
char nS[8];
// Pin reservations will become unnecessary when settings pages will read cfg.json directly
// add reserved and usermod pins as d.um_p array
DynamicJsonDocument doc(JSON_BUFFER_SIZE/2);
oappend(SET_F("d.um_p=[6,7,8,9,10,11"));
{ // scope so buffer can be released earlier
#ifdef WLED_USE_DYNAMIC_JSON
DynamicJsonDocument doc(3072);
#else
if (!requestJSONBufferLock(6)) return;
#endif
JsonObject mods = doc.createNestedObject(F("um"));
usermods.addToConfig(mods);
oappend(SET_F("d.um_p=["));
if (!mods.isNull()) {
uint8_t i=0;
for (JsonPair kv : mods) {
if (!kv.value().isNull()) {
// element is an JsonObject
JsonObject obj = kv.value();
if (obj["pin"] != nullptr) {
if (obj["pin"].is<JsonArray>()) {
JsonArray pins = obj["pin"].as<JsonArray>();
for (JsonVariant pv : pins) {
if (i++) oappend(SET_F(","));
oappendi(pv.as<int>());
}
} else {
if (i++) oappend(SET_F(","));
oappendi(obj["pin"].as<int>());
}
}
}
}
if (i) oappend(SET_F(","));
oappend(SET_F("6,7,8,9,10,11")); // flash memory pins
#ifdef WLED_ENABLE_DMX
oappend(SET_F(",2")); // DMX hardcoded pin
#endif
//Adalight / Serial in requires pin 3 to be unused. However, Serial input can not be prevented by WLED
#ifdef WLED_DEBUG
oappend(SET_F(",1")); // debug output (TX) pin
#endif
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
if (psramFound()) oappend(SET_F(",16,17")); // GPIO16 & GPIO17 reserved for SPI RAM
#endif
//TODO: add reservations for Ethernet shield pins
#ifdef WLED_USE_ETHERNET
#endif
if (!mods.isNull()) fillUMPins(mods);
releaseJSONBufferLock();
}
#ifdef WLED_ENABLE_DMX
oappend(SET_F(",2")); // DMX hardcoded pin
#endif
//Note: Using pin 3 (RX) disables Adalight / Serial JSON
#ifdef WLED_DEBUG
oappend(SET_F(",1")); // debug output (TX) pin
#endif
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
if (psramFound()) oappend(SET_F(",16,17")); // GPIO16 & GPIO17 reserved for SPI RAM
#endif
#ifdef WLED_USE_ETHERNET
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
for (uint8_t p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) { oappend(","); oappend(itoa(esp32_nonconfigurable_ethernet_pins[p].pin,nS,10)); }
if (ethernetBoards[ethernetType].eth_power>=0) { oappend(","); oappend(itoa(ethernetBoards[ethernetType].eth_power,nS,10)); }
if (ethernetBoards[ethernetType].eth_mdc>=0) { oappend(","); oappend(itoa(ethernetBoards[ethernetType].eth_mdc,nS,10)); }
if (ethernetBoards[ethernetType].eth_mdio>=0) { oappend(","); oappend(itoa(ethernetBoards[ethernetType].eth_mdio,nS,10)); }
switch (ethernetBoards[ethernetType].eth_clk_mode) {
case ETH_CLOCK_GPIO0_IN:
case ETH_CLOCK_GPIO0_OUT:
oappend(SET_F(",0"));
break;
case ETH_CLOCK_GPIO16_OUT:
oappend(SET_F(",16"));
break;
case ETH_CLOCK_GPIO17_OUT:
oappend(SET_F(",17"));
break;
}
}
#endif
oappend(SET_F("];"));
// set limits
oappend(SET_F("bLimits("));
oappend(itoa(WLED_MAX_BUSSES,nS,10)); oappend(",");
oappend(itoa(MAX_LEDS_PER_BUS,nS,10)); oappend(",");
oappend(itoa(MAX_LED_MEMORY,nS,10));
oappend(itoa(MAX_LED_MEMORY,nS,10)); oappend(",");
oappend(itoa(MAX_LEDS,nS,10));
oappend(SET_F(");"));
oappend(SET_F("d.Sf.LC.max=")); //TODO Formula for max LEDs on ESP8266 depending on types. 500 DMA or 1500 UART (about 4kB mem usage)
oappendi(MAX_LEDS);
oappend(";");
sappend('v',SET_F("LC"),ledCount);
sappend('c',SET_F("MS"),autoSegments);
sappend('c',SET_F("CCT"),correctWB);
sappend('c',SET_F("CR"),cctFromRgb);
sappend('v',SET_F("CB"),strip.cctBlending);
sappend('v',SET_F("AW"),Bus::getAutoWhiteMode());
for (uint8_t s=0; s < busses.getNumBusses(); s++) {
Bus* bus = busses.getBus(s);
if (bus == nullptr) continue;
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = 48+s; co[3] = 0; //strip color order
@@ -333,19 +394,21 @@ void getSettingsJS(byte subPage, char* dest)
char ls[4] = "LS"; ls[2] = 48+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = 48+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip 1st LED
char rf[4] = "RF"; rf[2] = 48+s; rf[3] = 0; //off refresh
oappend(SET_F("addLEDs(1);"));
uint8_t pins[5];
uint8_t nPins = bus->getPins(pins);
for (uint8_t i = 0; i < nPins; i++) {
lp[1] = 48+i;
if (pinManager.isPinOk(pins[i])) sappend('v', lp, pins[i]);
if (pinManager.isPinOk(pins[i]) || bus->getType()>=TYPE_NET_DDP_RGB) sappend('v',lp,pins[i]);
}
sappend('v', lc, bus->getLength());
sappend('v',lc,bus->getLength());
sappend('v',lt,bus->getType());
sappend('v',co,bus->getColorOrder());
sappend('v',ls,bus->getStart());
sappend('c',cv,bus->reversed);
sappend('c',sl,bus->skippedLeds());
sappend('c',rf,bus->isOffRefreshRequired());
}
sappend('v',SET_F("MA"),strip.ablMilliampsMax);
sappend('v',SET_F("LA"),strip.milliampsPerLed);
@@ -358,7 +421,6 @@ void getSettingsJS(byte subPage, char* dest)
}
sappend('v',SET_F("CA"),briS);
sappend('v',SET_F("AW"),strip.rgbwMode);
sappend('c',SET_F("BO"),turnOnAtBoot);
sappend('v',SET_F("BP"),bootPreset);