Compare commits

...

60 Commits

Author SHA1 Message Date
cschwinne
96422de031 0.13.0-b3 2021-09-21 23:37:35 +02:00
Blaž Kristan
f2043dc181 Merge pull request #2219 from blazoncek/analog-invert
Fix for missing inverted analog.
2021-09-21 12:19:15 +02:00
Blaž Kristan
e416ec9279 Removed dev types. 2021-09-21 12:05:28 +02:00
Blaž Kristan
5eb4ffb1cc Fix for missing inverted analog. 2021-09-21 11:59:23 +02:00
Christian Schwinne
8fae964ee8 Allocate segment data based on currently active segments (#2217) 2021-09-20 21:22:50 +02:00
cschwinne
baf49b88f4 Semi-working segment on/off transition 2021-09-20 21:22:04 +02:00
Phil Bolduc
3577da05ac Avoid redundant localIP calls, each call takes 0.700 us on ESP32 240Mhz (#2206)
* Avoid redundant localIP calls, each call takes 0.700 us on ESP32 240Mhz

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

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

* Disable Serial API if pin 3 is used

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

* Shortened string.

* Changed sync default to group 1 only.

* Make packets with version < 9 group 1

* Send sync group options as bytes, parse in JS

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

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

* Fix operator precedence error

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

* PinManager update for some user modules

* don't build everything...

* Final step to reduce RAM overhead

* update comment

* remove macros

* Remove leftover allocated

* Init ethernet after settings saved

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

* test.. something is wrong

* Squashed commit of the following:

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

    updated readme and added image showing info modal

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

    small map function fix

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

    updated ui

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

    added usermod battery_status_basic

* updated readme, changed USERMOD_BATTERY_MIN_VOLTAGE default to 2.6 volt

* fixed readme image file naming

* added usermod settings for runtime changes

* fixed copy and paste mistake

* undo ui changes

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

* removed images from readme

* added ESP32 support

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

* added setters/getters for hsv settings

* added color correction logic

* faster algorithm for color conversion

* added save/load config to fs

* adjusted value scale

* move color functions to colors.cpp

* remove unchecked file

* Various small changes

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

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

* Option for segment count instead of stop.

* Small fixes and improvements

* Further improvements

* Enable custom CSS by default

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

* Make rotary encoder usermod runtime configurable.
2021-07-20 13:41:30 +02:00
57 changed files with 4969 additions and 3374 deletions

View File

@@ -2,6 +2,56 @@
### Builds after release 0.12.0
#### Build 2109220
- Version bump to 0.13.0-b3 "Toki"
- Added segment names (PR #2184)
- Improved Police and other effects (PR #2184)
- Reverted PR #1902 (Live color correction - will be implemented as usermod) (PR #2175)
- Added transitions for segment on/off
- Improved number of sparks/stars in Fireworks effect with low number of segments
- Fixed segment name edit pencil disappearing with request
- Fixed color transition active even if the segment is off
- Disallowed file upload with OTA lock active
- Fixed analog invert option missing (PR #2219)
#### Build 2109100
- Added an auto create segments per bus setting
- Added 15 new palettes from SR branch (PR #2134)
- Fixed segment runtime not reset on FX change via HTTP API
- Changed AsyncTCP dependency to pbolduc fork v1.2.0
#### Build 2108250
- Added Sync groups (PR #2150)
- Added JSON API over Serial support
- Live color correction (PR #1902)
#### Build 2108180
- Fixed JSON IR remote not working with codes greater than 0xFFFFFF (fixes #2135)
- Fixed transition 0 edge case
#### Build 2108170
- Added application level pong websockets reply (#2139)
- Use AsyncTCP 1.0.3 as it mitigates the flickering issue from 0.13.0-b2
- Fixed transition manually updated in preset overriden by field value
#### Build 2108050
- Fixed undesirable color transition from Orange to boot preset color on first boot
- Removed misleading Delete button on new playlist with one entry
- Updated NeoPixelBus to 2.6.7 and AsyncTCP to 1.1.1
#### Build 2107230
- Added skinning (extra custom CSS) (PR #2084)
- Added presets/config backup/restore (PR #2084)
- Added option for using length instead of Stop LED in UI (PR #2048)
- Added custom `holidays.json` holiday list (PR #2048)
#### Build 2107100
- Version bump to 0.13.0-b2 "Toki"

2
package-lock.json generated
View File

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

View File

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

View File

@@ -8,12 +8,15 @@
# Please uncomment one of the lines below to select your board(s)
# ------------------------------------------------------------------------------
# Travis CI binaries (comment this out with a ';' when building for your own board)
;default_envs = travis_esp8266, travis_esp32
# Travis CI binaries (use `platformio_override.ini` when building for your own board; see `platformio_override.ini.sample` for an example)
; default_envs = travis_esp8266, travis_esp32
# Release binaries
default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth
# Build everything
; default_envs = esp32dev, esp8285_4CH_MagicHome, esp8285_4CH_H801, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_5CH_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
# Single binaries (uncomment your board)
; default_envs = elekstube_ips
; default_envs = nodemcuv2
@@ -196,17 +199,18 @@ lib_deps =
${env.lib_deps}
# ESPAsyncTCP @ 1.2.0
ESPAsyncUDP
makuna/NeoPixelBus @ 2.6.4 # 2.6.5 and newer do not compile on ESP core < 3.0.0
makuna/NeoPixelBus @ 2.6.7 # 2.6.5/2.6.6 and newer do not compile on ESP core < 3.0.0
[esp32]
build_flags = -g
-DARDUINO_ARCH_ESP32
-DCONFIG_LITTLEFS_FOR_IDF_3_2
-D CONFIG_ASYNC_TCP_USE_WDT=0
lib_deps =
${env.lib_deps}
https://github.com/Makuna/NeoPixelBus.git # until next upstream release
AsyncTCP @ 1.0.3
makuna/NeoPixelBus @ 2.6.7
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
[esp32s2]
build_flags = -g
@@ -214,11 +218,13 @@ build_flags = -g
-DCONFIG_LITTLEFS_FOR_IDF_3_2
-DARDUINO_ARCH_ESP32S2
-DCONFIG_IDF_TARGET_ESP32S2
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DCO
lib_deps =
${env.lib_deps}
https://github.com/Makuna/NeoPixelBus.git # until next upstream release
AsyncTCP @ 1.0.3
makuna/NeoPixelBus @ 2.6.7
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
# ------------------------------------------------------------------------------
# WLED BUILDS
@@ -399,7 +405,7 @@ build_flags = ${common.build_flags_esp32}
-D TEMPERATURE_PIN=23
lib_deps = ${esp32.lib_deps}
OneWire@~2.3.5
U8g2@~2.28.11
olikraus/U8g2 @ ^2.28.8
[env:m5atom]
board = esp32dev
@@ -415,6 +421,13 @@ board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=1
lib_deps = ${esp8266.lib_deps}
[env:sp511e]
board = esp_wroom_02
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
# travis test board configurations
# ------------------------------------------------------------------------------
@@ -482,4 +495,6 @@ build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_D
-D SPI_FREQUENCY=40000000
-D USER_SETUP_LOADED
monitor_filters = esp32_exception_decoder
lib_deps = ${esp32.lib_deps} TFT_eSPI
lib_deps =
${esp32.lib_deps}
TFT_eSPI @ ^2.3.70

View File

@@ -12,9 +12,9 @@ board = esp01_1m
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
lib_deps = ${esp8266.lib_deps}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266}
lib_deps = ${esp8266.lib_deps}
; *********************************************************************
; *** Use custom settings from file my_config.h
-DWLED_USE_MY_CONFIG
@@ -44,4 +44,4 @@ lib_deps = ${esp8266.lib_deps}
; configure the settings in the UI as follows (hard):
; for the Magic Home LED Controller use PWM pins 5,12,13,15
; for the H801 controller use PINs 15,13,12,14 (W2 = 04)
; for the BW-LT11 controller use PINs 12,4,14,5
; for the BW-LT11 controller use PINs 12,4,14,5

View File

@@ -4,11 +4,12 @@
<a href="https://raw.githubusercontent.com/Aircoookie/WLED/master/LICENSE"><img src="https://img.shields.io/github/license/Aircoookie/wled?color=blue&style=flat-square"></a>
<a href="https://wled.discourse.group"><img src="https://img.shields.io/discourse/topics?colorB=blue&label=forum&server=https%3A%2F%2Fwled.discourse.group%2F&style=flat-square"></a>
<a href="https://discord.gg/KuqP7NE"><img src="https://img.shields.io/discord/473448917040758787.svg?colorB=blue&label=discord&style=flat-square"></a>
<a href="https://github.com/Aircoookie/WLED/wiki"><img src="https://img.shields.io/badge/quick_start-wiki-blue.svg?style=flat-square"></a>
<a href="https://kno.wled.ge"><img src="https://img.shields.io/badge/quick_start-wiki-blue.svg?style=flat-square"></a>
<a href="https://github.com/Aircoookie/WLED-App"><img src="https://img.shields.io/badge/app-wled-blue.svg?style=flat-square"></a>
<a href="https://gitpod.io/#https://github.com/Aircoookie/WLED"><img src="https://img.shields.io/badge/Gitpod-ready--to--code-blue?style=flat-square&logo=gitpod"></a>
</p>
# Welcome to my project WLED! ✨
A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102!

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

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

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

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

View File

@@ -306,22 +306,26 @@ class Animated_Staircase : public Usermod {
public:
void setup() {
// standardize invalid pin numbers to -1
if (topPIRorTriggerPin < 0) topPIRorTriggerPin = -1;
if (topEchoPin < 0) topEchoPin = -1;
if (bottomPIRorTriggerPin < 0) bottomPIRorTriggerPin = -1;
if (bottomEchoPin < 0) bottomEchoPin = -1;
// allocate pins
if (topPIRorTriggerPin >= 0) {
if (!pinManager.allocatePin(topPIRorTriggerPin,useUSSensorTop))
topPIRorTriggerPin = -1;
}
if (topEchoPin >= 0) {
if (!pinManager.allocatePin(topEchoPin,false))
topEchoPin = -1;
}
if (bottomPIRorTriggerPin >= 0) {
if (!pinManager.allocatePin(bottomPIRorTriggerPin,useUSSensorBottom))
bottomPIRorTriggerPin = -1;
}
if (bottomEchoPin >= 0) {
if (!pinManager.allocatePin(bottomEchoPin,false))
bottomEchoPin = -1;
PinManagerPinType pins[4] = {
{ topPIRorTriggerPin, useUSSensorTop },
{ topEchoPin, false },
{ bottomPIRorTriggerPin, useUSSensorBottom },
{ bottomEchoPin, false },
};
// NOTE: this *WILL* return TRUE if all the pins are set to -1.
// this is *BY DESIGN*.
if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_AnimatedStaircase)) {
topPIRorTriggerPin = -1;
topEchoPin = -1;
bottomPIRorTriggerPin = -1;
bottomEchoPin = -1;
enabled = false;
}
enable(enabled);
initDone = true;
@@ -480,10 +484,10 @@ class Animated_Staircase : public Usermod {
(oldBottomAPin != bottomPIRorTriggerPin) ||
(oldBottomBPin != bottomEchoPin)) {
changed = true;
pinManager.deallocatePin(oldTopAPin);
pinManager.deallocatePin(oldTopBPin);
pinManager.deallocatePin(oldBottomAPin);
pinManager.deallocatePin(oldBottomBPin);
pinManager.deallocatePin(oldTopAPin, PinOwner::UM_AnimatedStaircase);
pinManager.deallocatePin(oldTopBPin, PinOwner::UM_AnimatedStaircase);
pinManager.deallocatePin(oldBottomAPin, PinOwner::UM_AnimatedStaircase);
pinManager.deallocatePin(oldBottomBPin, PinOwner::UM_AnimatedStaircase);
}
if (changed) setup();
}

View File

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

View File

@@ -1,398 +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;
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[];
/**
* 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 (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()
{
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (!pinManager.allocatePin(PIRsensorPin,false)) {
PIRsensorPin = -1; // allocation failed
enabled = false;
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
} else {
// PIR Sensor mode INPUT_PULLUP
pinMode(PIRsensorPin, INPUT_PULLUP);
if (enabled) {
sensorPinState = digitalRead(PIRsensorPin);
}
}
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 10x/s
if (millis() - lastLoop < 100 || 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;
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;
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);
if (pinManager.allocatePin(PIRsensorPin,false)) {
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 true;
}
/**
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId()
{
return USERMOD_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";
#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

@@ -115,14 +115,19 @@ class UsermodTemperature : public Usermod {
// config says we are enabled
DEBUG_PRINTLN(F("Allocating temperature pin..."));
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin)) {
if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) {
oneWire = new OneWire(temperaturePin);
if (!oneWire->reset())
if (!oneWire->reset()) {
sensorFound = false; // resetting 1-Wire bus yielded an error
else
while ((sensorFound=findSensor()) && retries--) delay(25); // try to find sensor
} else {
while ((sensorFound=findSensor()) && retries--) {
delay(25); // try to find sensor
}
}
} else {
if (temperaturePin >= 0) DEBUG_PRINTLN(F("Temperature pin allocation failed."));
if (temperaturePin >= 0) {
DEBUG_PRINTLN(F("Temperature pin allocation failed."));
}
temperaturePin = -1; // allocation failed
sensorFound = false;
}
@@ -273,7 +278,7 @@ class UsermodTemperature : public Usermod {
DEBUG_PRINTLN(F("Re-init temperature."));
// deallocate pin and release memory
delete oneWire;
pinManager.deallocatePin(temperaturePin);
pinManager.deallocatePin(temperaturePin, PinOwner::UM_Temperature);
temperaturePin = newTemperaturePin;
// initialise
setup();

View File

@@ -0,0 +1,55 @@
# :battery: Battery status/level Usermod :battery:
This Usermod allows you to monitor the battery level of your battery powered project.
You can see the battery level in the `info modal` right under the `estimated current`.
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)
## Installation
define `USERMOD_BATTERY_STATUS_BASIC` in `my_config.h`
### Define Your Options
* `USERMOD_BATTERY_STATUS_BASIC` - define this (in `my_config.h`) to have this user mod included wled00\usermods_list.cpp
* `USERMOD_BATTERY_MEASUREMENT_PIN` - defaults to A0 on esp8266 and GPIO32 on esp32
* `USERMOD_BATTERY_MEASUREMENT_INTERVAL` - the frequency to check the battery, defaults to 30 seconds
* `USERMOD_BATTERY_MIN_VOLTAGE` - minimum voltage of the Battery used, default is 2.6 (18650 battery standard)
* `USERMOD_BATTERY_MAX_VOLTAGE` - maximum voltage of the Battery used, default is 4.2 (18650 battery standard)
All parameters can be configured at runtime using Usermods settings page.
## Important :warning:
* Make sure you know your battery specification ! not every battery is the same !
* Example:
| Your battery specification table | | Options you can define |
| :-------------------------------- |:--------------- | :---------------------------- |
| Capacity | 3500mAh 12,5 Wh | |
| Minimum capacity | 3350mAh 11,9 Wh | |
| Rated voltage | 3.6V - 3.7V | |
| **Charging end voltage** | **4,2V ± 0,05** | `USERMOD_BATTERY_MAX_VOLTAGE` |
| **Discharge voltage** | **2,5V** | `USERMOD_BATTERY_MIN_VOLTAGE` |
| Max. discharge current (constant) | 10A (10000mA) | |
| max. charging current | 1.7A (1700mA) | |
| ... | ... | ... |
| .. | .. | .. |
Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.6V - 3.7V](https://www.akkuteile.de/lithium-ionen-akkus/18650/molicel/molicel-inr18650-m35a-3500mah-10a-lithium-ionen-akku-3-6v-3-7v_100833)
## Useful Links
* https://lazyzero.de/elektronik/esp8266/wemos_d1_mini_a0/start
* https://arduinodiy.wordpress.com/2016/12/25/monitoring-lipo-battery-voltage-with-wemos-d1-minibattery-shield-and-thingspeak/
## Change Log
2021-08-15
* changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries
* Updated readme, added specification table
2021-08-10
* Created

View File

@@ -0,0 +1,346 @@
#pragma once
#include "wled.h"
// pin defaults
// for the esp32 it is best to use the ADC1: GPIO32 - GPIO39
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html
#ifndef USERMOD_BATTERY_MEASUREMENT_PIN
#ifdef ARDUINO_ARCH_ESP32
#define USERMOD_BATTERY_MEASUREMENT_PIN 32
#else //ESP8266 boards
#define USERMOD_BATTERY_MEASUREMENT_PIN A0
#endif
#endif
// esp32 has a 12bit adc resolution
// esp8266 only 10bit
#ifndef USERMOD_BATTERY_ADC_PRECISION
#ifdef ARDUINO_ARCH_ESP32
// 12 bits
#define USERMOD_BATTERY_ADC_PRECISION 4095.0
#else
// 10 bits
#define USERMOD_BATTERY_ADC_PRECISION 1024.0
#endif
#endif
// the frequency to check the battery, 1 minute
#ifndef USERMOD_BATTERY_MEASUREMENT_INTERVAL
#define USERMOD_BATTERY_MEASUREMENT_INTERVAL 30000
#endif
// default for 18650 battery
// https://batterybro.com/blogs/18650-wholesale-battery-reviews/18852515-when-to-recycle-18650-batteries-and-how-to-start-a-collection-center-in-your-vape-shop
// Discharge voltage: 2.5 volt + .1 for personal safety
#ifndef USERMOD_BATTERY_MIN_VOLTAGE
#define USERMOD_BATTERY_MIN_VOLTAGE 2.6
#endif
#ifndef USERMOD_BATTERY_MAX_VOLTAGE
#define USERMOD_BATTERY_MAX_VOLTAGE 4.2
#endif
class UsermodBatteryBasic : public Usermod
{
private:
// battery pin can be defined in my_config.h
int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN;
// how often to read the battery voltage
unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL;
unsigned long lastTime = 0;
// battery min. voltage
float minBatteryVoltage = USERMOD_BATTERY_MIN_VOLTAGE;
// battery max. voltage
float maxBatteryVoltage = USERMOD_BATTERY_MAX_VOLTAGE;
// 0 - 1024 for esp8266 (10-bit resolution)
// 0 - 4095 for esp32 (Default is 12-bit resolution)
float adcPrecision = USERMOD_BATTERY_ADC_PRECISION;
// raw analog reading
float rawValue = 0.0;
// calculated voltage
float voltage = 0.0;
// mapped battery level based on voltage
long batteryLevel = 0;
bool initDone = false;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _readInterval[];
// custom map function
// https://forum.arduino.cc/t/floating-point-using-map-function/348113/2
double mapf(double x, double in_min, double in_max, double out_min, double out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
public:
//Functions called by WLED
/*
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup()
{
#ifdef ARDUINO_ARCH_ESP32
DEBUG_PRINTLN(F("Allocating battery pin..."));
if (batteryPin >= 0 && pinManager.allocatePin(batteryPin, false))
{
DEBUG_PRINTLN(F("Battery pin allocation succeeded."));
} else {
if (batteryPin >= 0) DEBUG_PRINTLN(F("Battery pin allocation failed."));
batteryPin = -1; // allocation failed
}
#else //ESP8266 boards have only one analog input pin A0
pinMode(batteryPin, INPUT);
#endif
initDone = true;
}
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
//Serial.println("Connected to WiFi!");
}
/*
* loop() is called continuously. Here you can check for events, read sensors, etc.
*
*/
void loop()
{
if(strip.isUpdating()) return;
unsigned long now = millis();
// check the battery level every USERMOD_BATTERY_MEASUREMENT_INTERVAL (ms)
if (now - lastTime >= readingInterval) {
// read battery raw input
rawValue = analogRead(batteryPin);
// calculate the voltage
voltage = (rawValue / adcPrecision) * maxBatteryVoltage ;
// 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);
lastTime = now;
}
}
/*
* 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 battery = user.createNestedArray("Battery level");
battery.add(batteryLevel);
battery.add(F(" %"));
}
/*
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
/*
void addToJsonState(JsonObject& root)
{
}
*/
/*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
/*
void readFromJsonState(JsonObject& root)
{
}
*/
/*
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
* If you want to force saving the current state, use serializeConfig() in your loop().
*
* CAUTION: serializeConfig() will initiate a filesystem write operation.
* It might cause the LEDs to stutter and will cause flash wear if called too often.
* Use it sparingly and always in the loop, never in network callbacks!
*
* addToConfig() will make your settings editable through the Usermod Settings page automatically.
*
* Usermod Settings Overview:
* - Numeric values are treated as floats in the browser.
* - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float
* before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and
* doubles are not supported, numbers will be rounded to the nearest float value when being parsed.
* The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38.
* - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a
* C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod.
* Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type
* used in the Usermod when reading the value from ArduinoJson.
* - Pin values can be treated differently from an integer value by using the key name "pin"
* - "pin" can contain a single or array of integer values
* - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins
* - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin)
* - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used
*
* See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings
*
* If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work.
* You will have to add the setting to the HTML, xml.cpp and set.cpp manually.
* See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED
*
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
*/
void addToConfig(JsonObject& root)
{
// created JSON object:
/*
{
"Battery-Level": {
"pin": "A0", <--- only when using esp32 boards
"minBatteryVoltage": 2.6,
"maxBatteryVoltage": 4.2,
"read-interval-ms": 30000
}
}
*/
JsonObject battery = root.createNestedObject(FPSTR(_name)); // usermodname
#ifdef ARDUINO_ARCH_ESP32
battery["pin"] = batteryPin; // usermodparam
#endif
battery["minBatteryVoltage"] = minBatteryVoltage; // usermodparam
battery["maxBatteryVoltage"] = maxBatteryVoltage; // usermodparam
battery[FPSTR(_readInterval)] = readingInterval;
DEBUG_PRINTLN(F("Battery config saved."));
}
/*
* readFromConfig() can be used to read back the custom settings you added with addToConfig().
* This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page)
*
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*
* Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings)
*
* getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present
* The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them
*
* This function is guaranteed to be called on boot, but could also be called every time settings are updated
*/
bool readFromConfig(JsonObject& root)
{
// looking for JSON object:
/*
{
"BatteryLevel": {
"pin": "A0", <--- only when using esp32 boards
"minBatteryVoltage": 2.6,
"maxBatteryVoltage": 4.2,
"read-interval-ms": 30000
}
}
*/
#ifdef ARDUINO_ARCH_ESP32
int8_t newBatteryPin = batteryPin;
#endif
JsonObject battery = root[FPSTR(_name)];
if (battery.isNull())
{
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
#ifdef ARDUINO_ARCH_ESP32
newBatteryPin = battery["pin"] | newBatteryPin;
#endif
minBatteryVoltage = battery["minBatteryVoltage"] | minBatteryVoltage;
//minBatteryVoltage = min(12.0f, (int)readingInterval);
maxBatteryVoltage = battery["maxBatteryVoltage"] | maxBatteryVoltage;
//maxBatteryVoltage = min(14.4f, max(3.3f,(int)readingInterval));
readingInterval = battery["read-interval-ms"] | readingInterval;
readingInterval = max(3000, (int)readingInterval); // minimum repetition is >5000ms (5s)
DEBUG_PRINT(FPSTR(_name));
#ifdef ARDUINO_ARCH_ESP32
if (!initDone)
{
// first run: reading from cfg.json
newBatteryPin = batteryPin;
DEBUG_PRINTLN(F(" config loaded."));
}
else
{
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing paramters from settings page
if (newBatteryPin != batteryPin)
{
// deallocate pin
pinManager.deallocatePin(batteryPin);
batteryPin = newBatteryPin;
// initialise
setup();
}
}
#endif
return !battery[FPSTR(_readInterval)].isNull();
}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId()
{
return USERMOD_ID_BATTERY_STATUS_BASIC;
}
};
// strings to reduce flash memory usage (used more than twice)
const char UsermodBatteryBasic::_name[] PROGMEM = "Battery-level";
const char UsermodBatteryBasic::_readInterval[] PROGMEM = "read-interval-ms";

View File

@@ -258,7 +258,7 @@ class MultiRelay : public Usermod {
// pins retrieved from cfg.json (readFromConfig()) prior to running setup()
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin<0) continue;
if (!pinManager.allocatePin(_relay[i].pin,true)) {
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
_relay[i].pin = -1; // allocation failed
} else {
switchRelay(i, _relay[i].state = (bool)bri);
@@ -380,12 +380,14 @@ class MultiRelay : public Usermod {
// deallocate all pins 1st
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
if (oldPin[i]>=0) {
pinManager.deallocatePin(oldPin[i]);
pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay);
}
// allocate new pins
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin,true)) {
if (!_relay[i].external) switchRelay(i, _relay[i].state = (bool)bri);
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) {
if (!_relay[i].external) {
switchRelay(i, _relay[i].state = (bool)bri);
}
} else {
_relay[i].pin = -1;
}

View File

@@ -13,7 +13,7 @@ class RgbRotaryEncoderUsermod : public Usermod
BusDigital *ledBus;
/*
* Green - eb - Q4 - 32
* Red - ea - Q1 - 15
* Red - ea - Q1 - 15
* Black - sw - Q2 - 12
*/
ESPRotary *rotaryEncoder;
@@ -39,13 +39,10 @@ class RgbRotaryEncoderUsermod : public Usermod
void initRotaryEncoder()
{
if (!pinManager.allocatePin(eaIo, false)) {
PinManagerPinType pins[2] = { { eaIo, false }, { ebIo, false } };
if (!pinManager.allocateMultiplePins(pins, 2, UM_RGBRotaryEncoder)) {
eaIo = -1;
}
if (!pinManager.allocatePin(ebIo, false)) {
ebIo = -1;
}
if (eaIo == -1 || ebIo == -1) {
cleanup();
return;
}
@@ -111,10 +108,12 @@ class RgbRotaryEncoderUsermod : public Usermod
{
// Only deallocate pins if we allocated them ;)
if (eaIo != -1) {
pinManager.deallocatePin(eaIo);
pinManager.deallocatePin(eaIo, PinOwner::UM_RGBRotaryEncoder);
eaIo = -1;
}
if (ebIo != -1) {
pinManager.deallocatePin(ebIo);
pinManager.deallocatePin(ebIo, PinOwner::UM_RGBRotaryEncoder);
ebIo = -1;
}
delete rotaryEncoder;
@@ -304,8 +303,8 @@ class RgbRotaryEncoderUsermod : public Usermod
}
if (eaIo != oldEaIo || ebIo != oldEbIo || stepsPerClick != oldStepsPerClick || incrementPerClick != oldIncrementPerClick) {
pinManager.deallocatePin(oldEaIo);
pinManager.deallocatePin(oldEbIo);
pinManager.deallocatePin(oldEaIo, PinOwner::UM_RGBRotaryEncoder);
pinManager.deallocatePin(oldEbIo, PinOwner::UM_RGBRotaryEncoder);
delete rotaryEncoder;
initRotaryEncoder();

View File

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

View File

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

View File

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

View File

@@ -388,41 +388,12 @@ uint16_t WS2812FX::mode_rainbow_cycle(void) {
}
/*
* theater chase function
*/
uint16_t WS2812FX::theater_chase(uint32_t color1, uint32_t color2, bool do_palette) {
byte gap = 2 + ((255 - SEGMENT.intensity) >> 5);
uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*2;
uint32_t it = now / cycleTime;
if (it != SEGENV.step) //new color
{
SEGENV.aux0 = (SEGENV.aux0 +1) % gap;
SEGENV.step = it;
}
for(uint16_t i = 0; i < SEGLEN; i++) {
if((i % gap) == SEGENV.aux0) {
if (do_palette)
{
setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0));
} else {
setPixelColor(i, color1);
}
} else {
setPixelColor(i, color2);
}
}
return FRAMETIME;
}
/*
* Theatre-style crawling lights.
* Inspired by the Adafruit examples.
*/
uint16_t WS2812FX::mode_theater_chase(void) {
return theater_chase(SEGCOLOR(0), SEGCOLOR(1), true);
return running(SEGCOLOR(0), SEGCOLOR(1), true);
}
@@ -431,7 +402,7 @@ uint16_t WS2812FX::mode_theater_chase(void) {
* Inspired by the Adafruit examples.
*/
uint16_t WS2812FX::mode_theater_chase_rainbow(void) {
return theater_chase(color_wheel(SEGENV.step), SEGCOLOR(1), false);
return running(color_wheel(SEGENV.step), SEGCOLOR(1), true);
}
@@ -976,29 +947,27 @@ uint16_t WS2812FX::mode_chase_flash_random(void) {
/*
* Alternating pixels running function.
*/
uint16_t WS2812FX::running(uint32_t color1, uint32_t color2) {
uint8_t pxw = 1 + (SEGMENT.intensity >> 5);
uint32_t cycleTime = 35 + (255 - SEGMENT.speed);
uint16_t WS2812FX::running(uint32_t color1, uint32_t color2, bool theatre) {
uint8_t width = (theatre ? 3 : 1) + (SEGMENT.intensity >> 4); // window
uint32_t cycleTime = 50 + (255 - SEGMENT.speed);
uint32_t it = now / cycleTime;
if (SEGMENT.speed == 0) it = 0;
bool usePalette = color1 == SEGCOLOR(0);
for(uint16_t i = 0; i < SEGLEN; i++) {
if((i + SEGENV.aux0) % (pxw*2) < pxw) {
if (color1 == SEGCOLOR(0))
{
setPixelColor(SEGLEN -i -1, color_from_palette(SEGLEN -i -1, true, PALETTE_SOLID_WRAP, 0));
} else
{
setPixelColor(SEGLEN -i -1, color1);
}
uint32_t col = color2;
if (usePalette) color1 = color_from_palette(i, true, PALETTE_SOLID_WRAP, 0);
if (theatre) {
if ((i % width) == SEGENV.aux0) col = color1;
} else {
setPixelColor(SEGLEN -i -1, color2);
int8_t pos = (i % (width<<1));
if ((pos < SEGENV.aux0-width) || ((pos >= SEGENV.aux0) && (pos < SEGENV.aux0+width))) col = color1;
}
setPixelColor(i,col);
}
if (it != SEGENV.step )
{
SEGENV.aux0 = (SEGENV.aux0 +1) % (pxw*2);
SEGENV.aux0 = (SEGENV.aux0 +1) % (theatre ? width : (width<<1));
SEGENV.step = it;
}
return FRAMETIME;
@@ -1247,44 +1216,19 @@ uint16_t WS2812FX::mode_loading(void) {
//American Police Light with all LEDs Red and Blue
uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, bool all)
uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, uint16_t width)
{
uint16_t counter = now * ((SEGMENT.speed >> 2) +1);
uint16_t idexR = (counter * SEGLEN) >> 16;
if (idexR >= SEGLEN) idexR = 0;
uint16_t topindex = SEGLEN >> 1;
uint16_t idexB = (idexR > topindex) ? idexR - topindex : idexR + topindex;
if (SEGENV.call == 0) SEGENV.aux0 = idexR;
if (idexB >= SEGLEN) idexB = 0; //otherwise overflow on odd number of LEDs
if (all) { //different algo, ensuring immediate fill
if (idexB > idexR) {
fill(color2);
for (uint16_t i = idexR; i < idexB; i++) setPixelColor(i, color1);
} else {
fill(color1);
for (uint16_t i = idexB; i < idexR; i++) setPixelColor(i, color2);
}
} else { //regular dot-only mode
uint8_t size = 1 + (SEGMENT.intensity >> 3);
if (size > SEGLEN/2) size = 1+ SEGLEN/2;
for (uint8_t i=0; i <= size; i++) {
setPixelColor(idexR+i, color1);
setPixelColor(idexB+i, color2);
}
if (SEGENV.aux0 != idexR) {
uint8_t gap = (SEGENV.aux0 < idexR)? idexR - SEGENV.aux0:SEGLEN - SEGENV.aux0 + idexR;
for (uint8_t i = 0; i <= gap ; i++) {
if ((idexR - i) < 0) idexR = SEGLEN-1 + i;
if ((idexB - i) < 0) idexB = SEGLEN-1 + i;
setPixelColor(idexR-i, color1);
setPixelColor(idexB-i, color2);
}
SEGENV.aux0 = idexR;
}
}
uint16_t delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster
uint32_t it = now / map(SEGMENT.speed, 0, 255, delay<<4, delay);
uint16_t offset = it % SEGLEN;
if (!width) width = 1;
for (uint16_t i = 0; i < width; i++) {
uint16_t indexR = (offset + i) % SEGLEN;
uint16_t indexB = (offset + i + (SEGLEN>>1)) % SEGLEN;
setPixelColor(indexR, color1);
setPixelColor(indexB, color2);
}
return FRAMETIME;
}
@@ -1292,7 +1236,7 @@ uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2, bool all)
//American Police Light with all LEDs Red and Blue
uint16_t WS2812FX::mode_police_all()
{
return police_base(RED, BLUE, true);
return police_base(RED, BLUE, (SEGLEN>>1));
}
@@ -1300,15 +1244,15 @@ uint16_t WS2812FX::mode_police_all()
uint16_t WS2812FX::mode_police()
{
fill(SEGCOLOR(1));
return police_base(RED, BLUE, false);
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()
{
return police_base(SEGCOLOR(0), SEGCOLOR(1), true);
fill(SEGCOLOR(2));
return police_base(SEGCOLOR(0), SEGCOLOR(1), ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip
}
@@ -1318,7 +1262,7 @@ uint16_t WS2812FX::mode_two_dots()
fill(SEGCOLOR(2));
uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1);
return police_base(SEGCOLOR(0), color2, false);
return police_base(SEGCOLOR(0), color2, ((SEGLEN*(SEGMENT.intensity+1))>>9)); // max width is half the strip
}
@@ -1326,21 +1270,20 @@ uint16_t WS2812FX::mode_two_dots()
* Tricolor chase function
*/
uint16_t WS2812FX::tricolor_chase(uint32_t color1, uint32_t color2) {
uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*2;
uint32_t it = now / cycleTime;
uint8_t width = (1 + SEGMENT.intensity/32) * 3; //value of 1-8 for each colour
uint8_t index = it % width;
uint32_t cycleTime = 50 + ((255 - SEGMENT.speed)<<1);
uint32_t it = now / cycleTime; // iterator
uint8_t width = (1 + (SEGMENT.intensity>>4)); // value of 1-16 for each colour
uint8_t index = it % (width*3);
for(uint16_t i = 0; i < SEGLEN; i++, index++) {
if(index > width-1) index = 0;
for (uint16_t i = 0; i < SEGLEN; i++, index++) {
if (index > (width*3)-1) index = 0;
uint32_t color = color1;
if(index > width*2/3-1) color = color_from_palette(i, true, PALETTE_SOLID_WRAP, 1);
else if(index > width/3-1) color = color2;
if (index > (width<<1)-1) color = color_from_palette(i, true, PALETTE_SOLID_WRAP, 1);
else if (index > width-1) color = color2;
setPixelColor(SEGLEN - i -1, color);
}
return FRAMETIME;
}
@@ -1548,7 +1491,7 @@ uint16_t WS2812FX::mode_random_chase(void)
return FRAMETIME;
}
//7 bytes
typedef struct Oscillator {
int16_t pos;
int8_t size;
@@ -1780,7 +1723,7 @@ uint16_t WS2812FX::mode_fire_2012()
// Step 1. Cool down every cell a little
for (uint16_t i = 0; i < SEGLEN; i++) {
uint8_t temp = qsub8(heat[i], random8(0, (((20 + SEGMENT.speed /3) * 10) / SEGLEN) + 2));
heat[i] = (temp==0 && i<ignition) ? 2 : temp; // prevent ignition area from becoming black
heat[i] = (temp==0 && i<ignition) ? 16 : temp; // prevent ignition area from becoming black
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
@@ -1994,7 +1937,7 @@ uint16_t WS2812FX::mode_colortwinkle()
if (fadeUp) {
CRGB incrementalColor = fastled_col;
incrementalColor.nscale8_video( fadeUpAmount);
incrementalColor.nscale8_video(fadeUpAmount);
fastled_col += incrementalColor;
if (fastled_col.red == 255 || fastled_col.green == 255 || fastled_col.blue == 255) {
@@ -2002,24 +1945,21 @@ uint16_t WS2812FX::mode_colortwinkle()
}
setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue);
if (col_to_crgb(getPixelColor(i)) == prev) //fix "stuck" pixels
{
if (col_to_crgb(getPixelColor(i)) == prev) { //fix "stuck" pixels
fastled_col += fastled_col;
setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue);
}
} else {
fastled_col.nscale8( 255 - fadeDownAmount);
fastled_col.nscale8(255 - fadeDownAmount);
setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue);
}
}
for (uint16_t j = 0; j <= SEGLEN / 50; j++)
{
for (uint16_t j = 0; j <= SEGLEN / 50; j++) {
if (random8() <= SEGMENT.intensity) {
for (uint8_t times = 0; times < 5; times++) //attempt to spawn a new pixel 5 times
{
for (uint8_t times = 0; times < 5; times++) { //attempt to spawn a new pixel 5 times
int i = random16(SEGLEN);
if(getPixelColor(i) == 0) {
if (getPixelColor(i) == 0) {
fastled_col = ColorFromPalette(currentPalette, random8(), 64, NOBLEND);
uint16_t index = i >> 3;
uint8_t bitNum = i & 0x07;
@@ -2170,10 +2110,14 @@ typedef struct Ripple {
uint16_t pos;
} ripple;
#ifdef ESP8266
#define MAX_RIPPLES 56
#else
#define MAX_RIPPLES 100
#endif
uint16_t WS2812FX::ripple_base(bool rainbow)
{
uint16_t maxRipples = 1 + (SEGLEN >> 2);
if (maxRipples > 100) maxRipples = 100;
uint16_t maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 18 segment ESP8266
uint16_t dataSize = sizeof(ripple) * maxRipples;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
@@ -2241,6 +2185,7 @@ uint16_t WS2812FX::ripple_base(bool rainbow)
}
return FRAMETIME;
}
#undef MAX_RIPPLES
uint16_t WS2812FX::mode_ripple(void) {
return ripple_base(false);
@@ -2541,7 +2486,6 @@ uint16_t WS2812FX::mode_spots_fade()
//each needs 12 bytes
//Spark type is used for popcorn and 1D fireworks
typedef struct Ball {
unsigned long lastBounceTime;
float impactVelocity;
@@ -2651,7 +2595,7 @@ uint16_t WS2812FX::mode_sinelon_dual(void) {
}
uint16_t WS2812FX::mode_sinelon_rainbow(void) {
return sinelon_base(true, true);
return sinelon_base(false, true);
}
@@ -2685,7 +2629,7 @@ typedef struct Spark {
*/
uint16_t WS2812FX::mode_popcorn(void) {
//allocate segment data
uint16_t maxNumPopcorn = 24;
uint16_t maxNumPopcorn = 21; // max 21 on 16 segment ESP8266
uint16_t dataSize = sizeof(spark) * maxNumPopcorn;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
@@ -2744,7 +2688,7 @@ uint16_t WS2812FX::candle(bool multi)
if (multi)
{
//allocate segment data
uint16_t dataSize = (SEGLEN -1) *3;
uint16_t dataSize = (SEGLEN -1) *3; //max. 1365 pixels (ESP8266)
if (!SEGENV.allocateData(dataSize)) return candle(false); //allocation failed
}
@@ -2831,9 +2775,12 @@ uint16_t WS2812FX::mode_candle_multi()
/ based on the video: https://www.reddit.com/r/arduino/comments/c3sd46/i_made_this_fireworks_effect_for_my_led_strips/
/ Speed sets frequency of new starbursts, intensity is the intensity of the burst
*/
#define STARBURST_MAX_FRAG 12
//each needs 64 byte
#ifdef ESP8266
#define STARBURST_MAX_FRAG 8 //52 bytes / star
#else
#define STARBURST_MAX_FRAG 10 //60 bytes / star
#endif
//each needs 20+STARBURST_MAX_FRAG*4 bytes
typedef struct particle {
CRGB color;
uint32_t birth =0;
@@ -2844,8 +2791,14 @@ typedef struct particle {
} star;
uint16_t WS2812FX::mode_starburst(void) {
uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640
uint8_t segs = getActiveSegmentsNum();
if (segs <= (MAX_NUM_SEGMENTS /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs
if (segs <= (MAX_NUM_SEGMENTS /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs
uint16_t maxStars = maxData / sizeof(star); //ESP8266: max. 4/9/19 stars/seg, ESP32: max. 10/21/42 stars/seg
uint8_t numStars = 1 + (SEGLEN >> 3);
if (numStars > 15) numStars = 15;
if (numStars > maxStars) numStars = maxStars;
uint16_t dataSize = sizeof(star) * numStars;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
@@ -2946,21 +2899,31 @@ uint16_t WS2812FX::mode_starburst(void) {
}
return FRAMETIME;
}
#undef STARBURST_MAX_FRAG
#undef STARBURST_MAX_STARS
/*
* Exploding fireworks effect
* adapted from: http://www.anirama.com/1000leds/1d-fireworks/
*/
uint16_t WS2812FX::mode_exploding_fireworks(void)
{
//allocate segment data
uint16_t numSparks = 2 + (SEGLEN >> 1);
if (numSparks > 80) numSparks = 80;
uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640
uint8_t segs = getActiveSegmentsNum();
if (segs <= (MAX_NUM_SEGMENTS /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs
if (segs <= (MAX_NUM_SEGMENTS /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs
int maxSparks = maxData / sizeof(spark); //ESP8266: max. 21/42/85 sparks/seg, ESP32: max. 53/106/213 sparks/seg
uint16_t numSparks = min(2 + (SEGLEN >> 1), maxSparks);
uint16_t dataSize = sizeof(spark) * numSparks;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
if (dataSize != SEGENV.aux1) { //reset to flare if sparks were reallocated
SEGENV.aux0 = 0;
SEGENV.aux1 = dataSize;
}
fill(BLACK);
bool actuallyReverse = SEGMENT.getOption(SEG_OPTION_REVERSED);
@@ -3052,7 +3015,7 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
SEGENV.aux0--;
if (SEGENV.aux0 < 4) {
SEGENV.aux0 = 0; //back to flare
SEGENV.step = (SEGMENT.intensity > random8()); //decide firing side
SEGENV.step = actuallyReverse ^ (SEGMENT.intensity > random8()); //decide firing side
}
}
@@ -3060,6 +3023,7 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
return FRAMETIME;
}
#undef MAX_SPARKS
/*
@@ -3069,7 +3033,7 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
uint16_t WS2812FX::mode_drip(void)
{
//allocate segment data
uint16_t numDrops = 4;
uint8_t numDrops = 4;
uint16_t dataSize = sizeof(spark) * numDrops;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
@@ -3077,7 +3041,7 @@ uint16_t WS2812FX::mode_drip(void)
Spark* drops = reinterpret_cast<Spark*>(SEGENV.data);
numDrops = 1 + (SEGMENT.intensity >> 6);
numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3
float gravity = -0.0005 - (SEGMENT.speed/50000.0);
gravity *= SEGLEN;
@@ -3107,13 +3071,13 @@ uint16_t WS2812FX::mode_drip(void)
if (drops[j].pos > 0) { // fall until end of segment
drops[j].pos += drops[j].vel;
if (drops[j].pos < 0) drops[j].pos = 0;
drops[j].vel += gravity;
drops[j].vel += gravity; // gravity is negative
for (uint16_t i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets
uint16_t pos = constrain(uint16_t(drops[j].pos) +i, 0, SEGLEN-1); //this is BAD, returns a pos >= SEGLEN occasionally
setPixelColor(pos,color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling
}
if (drops[j].colIndex > 2) { // during bounce, some water is on the floor
setPixelColor(0,color_blend(SEGCOLOR(0),BLACK,drops[j].col));
}
@@ -3142,6 +3106,7 @@ uint16_t WS2812FX::mode_drip(void)
* Tetris or Stacking (falling bricks) Effect
* by Blaz Kristan (https://github.com/blazoncek, https://blaz.at/home)
*/
//12 bytes
typedef struct Tetris {
float pos;
float speed;
@@ -3163,8 +3128,8 @@ uint16_t WS2812FX::mode_tetrix(void) {
}
if (SEGENV.step == 0) { //init
drop->speed = 0.0238 * (SEGMENT.speed ? (SEGMENT.speed>>3)+1 : random8(6,40)); // set speed
drop->pos = SEGLEN-1; // start at end of segment
drop->speed = 0.0238 * (SEGMENT.speed ? (SEGMENT.speed>>2)+1 : random8(6,64)); // set speed
drop->pos = SEGLEN; // start at end of segment (no need to subtract 1)
drop->col = color_from_palette(random8(0,15)<<4,false,false,0); // limit color choices so there is enough HUE gap
SEGENV.step = 1; // drop state (0 init, 1 forming, 2 falling)
SEGENV.aux0 = (SEGMENT.intensity ? (SEGMENT.intensity>>5)+1 : random8(1,5)) * (1+(SEGLEN>>6)); // size of brick
@@ -3196,13 +3161,17 @@ uint16_t WS2812FX::mode_tetrix(void) {
/ adapted from https://github.com/atuline/FastLED-Demos/blob/master/plasma/plasma.ino
*/
uint16_t WS2812FX::mode_plasma(void) {
uint8_t thisPhase = beatsin8(6,-64,64); // Setting phase change for a couple of waves.
uint8_t thatPhase = beatsin8(7,-64,64);
// initialize phases on start
if (SEGENV.call == 0) {
SEGENV.aux0 = random8(0,2); // add a bit of randomness
}
uint8_t thisPhase = beatsin8(6+SEGENV.aux0,-64,64);
uint8_t thatPhase = beatsin8(7+SEGENV.aux0,-64,64);
for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set color & brightness based on a wave as follows:
uint8_t colorIndex = cubicwave8(((i*(1+ 3*(SEGMENT.speed >> 5)))+(thisPhase)) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change.
+ cos8(((i*(1+ 2*(SEGMENT.speed >> 5)))+(thatPhase)) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish.
uint8_t thisBright = qsub8(colorIndex, beatsin8(6,0, (255 - SEGMENT.intensity)|0x01 ));
uint8_t colorIndex = cubicwave8((i*(2+ 3*(SEGMENT.speed >> 5))+thisPhase) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change.
+ cos8((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish.
uint8_t thisBright = qsub8(colorIndex, beatsin8(7,0, (128 - (SEGMENT.intensity>>1))));
CRGB color = ColorFromPalette(currentPalette, colorIndex, thisBright, LINEARBLEND);
setPixelColor(i, color.red, color.green, color.blue);
}
@@ -3531,7 +3500,7 @@ uint16_t WS2812FX::mode_noisepal(void) { // S
uint16_t scale = 15 + (SEGMENT.intensity >> 2); //default was 30
//#define scale 30
uint16_t dataSize = sizeof(CRGBPalette16) * 2; //allocate space for 2 Palettes
uint16_t dataSize = sizeof(CRGBPalette16) * 2; //allocate space for 2 Palettes (2 * 16 * 3 = 96 bytes)
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
CRGBPalette16* palettes = reinterpret_cast<CRGBPalette16*>(SEGENV.data);
@@ -3644,7 +3613,7 @@ uint16_t WS2812FX::mode_chunchun(void)
return FRAMETIME;
}
//13 bytes
typedef struct Spotlight {
float speed;
uint8_t colorIdx;
@@ -3661,6 +3630,11 @@ typedef struct Spotlight {
#define SPOT_TYPE_3X_DOT 4
#define SPOT_TYPE_4X_DOT 5
#define SPOT_TYPES_COUNT 6
#ifdef ESP8266
#define SPOT_MAX_COUNT 17 //Number of simultaneous waves
#else
#define SPOT_MAX_COUNT 49 //Number of simultaneous waves
#endif
/*
* Spotlights moving back and forth that cast dancing shadows.
@@ -3671,7 +3645,7 @@ typedef struct Spotlight {
*/
uint16_t WS2812FX::mode_dancing_shadows(void)
{
uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, 50);
uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 18 segment ESP8266
bool initialize = SEGENV.aux0 != numSpotlights;
SEGENV.aux0 = numSpotlights;
@@ -3810,7 +3784,7 @@ uint16_t WS2812FX::mode_washing_machine(void) {
Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e
*/
uint16_t WS2812FX::mode_blends(void) {
uint16_t dataSize = sizeof(uint32_t) * SEGLEN;
uint16_t dataSize = sizeof(uint32_t) * SEGLEN; // max segment length of 56 pixels on 18 segment ESP8266
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data);
uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128);
@@ -3825,6 +3799,11 @@ uint16_t WS2812FX::mode_blends(void) {
return FRAMETIME;
}
/*
TV Simulator
Modified and adapted to WLED by Def3nder, based on "Fake TV Light for Engineers" by Phillip Burgess https://learn.adafruit.com/fake-tv-light-for-engineers/arduino-sketch
*/
//43 bytes
typedef struct TvSim {
uint32_t totalTime = 0;
uint32_t fadeTime = 0;
@@ -3845,11 +3824,6 @@ typedef struct TvSim {
uint16_t pb = 0;
} tvSim;
/*
TV Simulator
Modified and adapted to WLED by Def3nder, based on "Fake TV Light for Engineers" by Phillip Burgess https://learn.adafruit.com/fake-tv-light-for-engineers/arduino-sketch
*/
uint16_t WS2812FX::mode_tv_simulator(void) {
uint16_t nr, ng, nb, r, g, b, i, hue;
uint8_t sat, bri, j;
@@ -3957,10 +3931,15 @@ uint16_t WS2812FX::mode_tv_simulator(void) {
*/
//CONFIG
#define W_MAX_COUNT 20 //Number of simultaneous waves
#ifdef ESP8266
#define W_MAX_COUNT 9 //Number of simultaneous waves
#else
#define W_MAX_COUNT 20 //Number of simultaneous waves
#endif
#define W_MAX_SPEED 6 //Higher number, higher speed
#define W_WIDTH_FACTOR 6 //Higher number, smaller waves
//24 bytes
class AuroraWave {
private:
uint16_t ttl;
@@ -4055,10 +4034,10 @@ uint16_t WS2812FX::mode_aurora(void) {
if(SEGENV.aux0 != SEGMENT.intensity || SEGENV.call == 0) {
//Intensity slider changed or first call
SEGENV.aux1 = ((float)SEGMENT.intensity / 255) * W_MAX_COUNT;
SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT);
SEGENV.aux0 = SEGMENT.intensity;
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) {
if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 18 segment ESP8266
return mode_static(); //allocation failed
}

View File

@@ -24,8 +24,6 @@
Modified for WLED
*/
#include "wled.h"
#ifndef WS2812FX_h
#define WS2812FX_h
@@ -55,19 +53,23 @@
/* each segment uses 52 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
#ifdef ESP8266
#define MAX_NUM_SEGMENTS 12
#define MAX_NUM_SEGMENTS 16
/* How many color transitions can run at once */
#define MAX_NUM_TRANSITIONS 8
/* How much data bytes all segments combined may allocate */
#define MAX_SEGMENT_DATA 2048
#define MAX_SEGMENT_DATA 4096
#else
#ifndef MAX_NUM_SEGMENTS
#define MAX_NUM_SEGMENTS 16
#endif
#define MAX_NUM_TRANSITIONS 16
#define MAX_SEGMENT_DATA 8192
#ifndef MAX_NUM_SEGMENTS
#define MAX_NUM_SEGMENTS 32
#endif
#define MAX_NUM_TRANSITIONS 24
#define MAX_SEGMENT_DATA 20480
#endif
/* How much data bytes each segment should max allocate to leave enough space for other segments,
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS)
#define LED_SKIP_AMOUNT 1
#define MIN_SHOW_DELAY 15
@@ -245,7 +247,7 @@ class WS2812FX {
// segment parameters
public:
typedef struct Segment { // 25 (28 in memory?) bytes
typedef struct Segment { // 29 (32 in memory?) bytes
uint16_t start;
uint16_t stop; //segment invalid if stop == 0
uint16_t offset;
@@ -257,6 +259,7 @@ class WS2812FX {
uint8_t grouping, spacing;
uint8_t opacity;
uint32_t colors[NUM_COLORS];
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;
@@ -275,40 +278,42 @@ class WS2812FX {
}*/
void setOption(uint8_t n, bool val, uint8_t segn = 255)
{
//bool prevOn = false;
//if (n == SEG_OPTION_ON) prevOn = getOption(SEG_OPTION_ON);
bool prevOn = false;
if (n == SEG_OPTION_ON) {
prevOn = getOption(SEG_OPTION_ON);
if (!val && prevOn) { //fade off
ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0);
}
}
if (val) {
options |= 0x01 << n;
} else
{
options &= ~(0x01 << n);
}
//transitions on segment on/off don't work correctly at this point
/*if (n == SEG_OPTION_ON && segn < MAX_NUM_SEGMENTS && getOption(SEG_OPTION_ON) != prevOn) {
if (getOption(SEG_OPTION_ON)) {
ColorTransition::startTransition(0, colors[0], instance->_transitionDur, segn, 0);
} else {
ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0);
}
}*/
if (n == SEG_OPTION_ON && val && !prevOn) { //fade on
ColorTransition::startTransition(0, colors[0], instance->_transitionDur, segn, 0);
}
}
bool getOption(uint8_t n)
{
return ((options >> n) & 0x01);
}
bool isSelected()
inline bool isSelected()
{
return getOption(0);
}
bool isActive()
inline bool isActive()
{
return stop > start;
}
uint16_t length()
inline uint16_t length()
{
return stop - start;
}
uint16_t groupLength()
inline uint16_t groupLength()
{
return grouping + spacing;
}
@@ -345,17 +350,23 @@ class WS2812FX {
// segment runtime parameters
typedef struct Segment_runtime { // 28 bytes
unsigned long next_time;
uint32_t step;
uint32_t call;
uint16_t aux0;
uint16_t aux1;
unsigned long next_time; // millis() of next update
uint32_t step; // custom "step" var
uint32_t call; // call counter
uint16_t aux0; // custom var
uint16_t aux1; // custom var
byte* data = nullptr;
bool allocateData(uint16_t len){
if (data && _dataLen == len) return true; //already allocated
deallocateData();
if (WS2812FX::instance->_usedSegmentData + len > MAX_SEGMENT_DATA) return false; //not enough memory
data = new (std::nothrow) byte[len];
// if possible use SPI RAM on ESP32
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
if (psramFound())
data = (byte*) ps_malloc(len);
else
#endif
data = (byte*) malloc(len);
if (!data) return false; //allocation failed
WS2812FX::instance->_usedSegmentData += len;
_dataLen = len;
@@ -363,7 +374,7 @@ class WS2812FX {
return true;
}
void deallocateData(){
delete[] data;
free(data);
data = nullptr;
WS2812FX::instance->_usedSegmentData -= _dataLen;
_dataLen = 0;
@@ -389,7 +400,7 @@ class WS2812FX {
* the internal segment state should be reset.
* Call resetIfRequired before calling the next effect function.
*/
void reset() { _requiresReset = true; }
inline void reset() { _requiresReset = true; }
private:
uint16_t _dataLen = 0;
bool _requiresReset = false;
@@ -404,6 +415,7 @@ class WS2812FX {
static void startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot) {
if (segn >= MAX_NUM_SEGMENTS || slot >= NUM_COLORS || dur == 0) return;
if (instance->_brightness == 0) return; //do not need transitions if master bri is off
if (!instance->_segments[segn].getOption(SEG_OPTION_ON)) return; //not if segment is off either
uint8_t tIndex = 0xFF; //none found
uint16_t tProgression = 0;
uint8_t s = segn + (slot << 6); //merge slot and segment into one byte
@@ -432,7 +444,8 @@ class WS2812FX {
ColorTransition& t = instance->transitions[tIndex];
if (t.segment == s) //this is an active transition on the same segment+color
{
t.briOld = t.currentBri();
bool wasTurningOff = (oldBri == 0);
t.briOld = t.currentBri(wasTurningOff);
t.colorOld = t.currentColor(oldCol);
} else {
t.briOld = oldBri;
@@ -464,10 +477,11 @@ class WS2812FX {
uint32_t currentColor(uint32_t colorNew) {
return instance->color_blend(colorOld, colorNew, progress(true), true);
}
uint8_t currentBri() {
uint8_t currentBri(bool turningOff = false) {
uint8_t 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;
uint32_t prog = progress() + 1;
return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16;
}
@@ -622,11 +636,12 @@ class WS2812FX {
trigger(void),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0),
resetSegments(),
populateDefaultSegments(),
setPixelColor(uint16_t n, uint32_t c),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
show(void),
setColorOrder(uint8_t co),
setPixelSegment(uint8_t n);
setPixelSegment(uint8_t n),
deserializeMap(uint8_t n=0);
bool
isRgbw = false,
@@ -644,34 +659,26 @@ class WS2812FX {
paletteFade = 0,
paletteBlend = 0,
milliampsPerLed = 55,
// getStripType(uint8_t strip=0),
// setStripType(uint8_t type, uint8_t strip=0),
getBrightness(void),
getMode(void),
getSpeed(void),
getModeCount(void),
getPaletteCount(void),
getMaxSegments(void),
getActiveSegmentsNum(void),
//getFirstSelectedSegment(void),
getMainSegmentId(void),
getColorOrder(void),
gamma8(uint8_t),
gamma8_cal(uint8_t, float),
sin_gap(uint16_t),
get_random_wheel_index(uint8_t);
int8_t
// setStripPin(uint8_t strip, int8_t pin),
// getStripPin(uint8_t strip=0),
// setStripPinClk(uint8_t strip, int8_t pin),
// getStripPinClk(uint8_t strip=0),
tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec);
uint16_t
ablMilliampsMax,
currentMilliamps,
// setStripLen(uint8_t strip, uint16_t len),
// getStripLen(uint8_t strip=0),
triwave16(uint16_t),
getFps();
@@ -849,7 +856,6 @@ class WS2812FX {
color_wipe(bool, bool),
dynamic(bool),
scan(bool),
theater_chase(uint32_t, uint32_t, bool),
running_base(bool,bool),
larson_scanner(bool),
sinelon_base(bool,bool),
@@ -857,8 +863,8 @@ class WS2812FX {
chase(uint32_t, uint32_t, uint32_t, bool),
gradient_base(bool),
ripple_base(bool),
police_base(uint32_t, uint32_t, bool),
running(uint32_t, uint32_t),
police_base(uint32_t, uint32_t, uint16_t),
running(uint32_t, uint32_t, bool theatre=false),
tricolor_chase(uint32_t, uint32_t),
twinklefox_base(bool),
spots_base(uint16_t),
@@ -869,8 +875,7 @@ class WS2812FX {
void
blendPixelColor(uint16_t n, uint32_t color, uint8_t blend),
startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot),
deserializeMap(void);
startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot);
uint16_t* customMappingTable = nullptr;
uint16_t customMappingSize = 0;
@@ -921,7 +926,9 @@ const char JSON_palette_names[] PROGMEM = R"=====([
"Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64",
"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn",
"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura",
"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2"
"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf",
"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide",
"Candy2"
])=====";
#endif

View File

@@ -23,7 +23,7 @@
Modified heavily for WLED
*/
#include "wled.h"
#include "FX.h"
#include "palettes.h"
@@ -40,7 +40,7 @@
another example. Switches direction every 5 LEDs.
{"map":[
0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14,
19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]
19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]}
*/
//factory defaults LED setup
@@ -98,24 +98,25 @@ void WS2812FX::finalizeInit(uint16_t countPixels)
setBrightness(_brightness);
//TODO make sure segments are only refreshed when bus config actually changed (new settings page)
//make one segment per bus
uint8_t s = 0;
for (uint8_t i = 0; i < busses.getNumBusses(); i++) {
Bus* b = busses.getBus(i);
segStarts[s] = b->getStart();
segStops[s] = segStarts[s] + b->getLength();
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--;
//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++;
}
s++;
#ifdef ESP8266
if ((!IS_DIGITAL(b->getType()) || IS_2PIN(b->getType()))) continue;
@@ -126,9 +127,24 @@ void WS2812FX::finalizeInit(uint16_t countPixels)
#endif
}
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) {
_segments[i].start = segStarts[i];
_segments[i].stop = segStops [i];
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);
}
}
}
}
@@ -197,14 +213,13 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) {
int16_t realIndex = iGroup;
if (IS_REVERSE) {
if (IS_MIRROR) {
realIndex = (SEGMENT.length() -1) / 2 - iGroup; //only need to index half the pixels
realIndex = (SEGMENT.length() - 1) / 2 - iGroup; //only need to index half the pixels
} else {
realIndex = SEGMENT.length() - iGroup - 1;
realIndex = (SEGMENT.length() - 1) - iGroup;
}
}
realIndex += SEGMENT.start;
return realIndex;
}
@@ -225,7 +240,6 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
}
if (SEGLEN) {//from segment
//color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments)
if (_bri_t < 255) {
r = scale8(r, _bri_t);
@@ -235,12 +249,12 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
}
uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
bool reversed = IS_REVERSE;
/* 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++) {
int indexSet = realIndex + (reversed ? -j : j);
uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j);
if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) {
if (IS_MIRROR) { //set the corresponding mirrored pixel
uint16_t indexMir = SEGMENT.stop - indexSet + SEGMENT.start - 1;
@@ -252,8 +266,8 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
busses.setPixelColor(indexMir, col);
}
/* offset/phase */
indexSet += SEGMENT.offset;
if (indexSet >= SEGMENT.stop) indexSet -= len;
indexSet += SEGMENT.offset;
if (indexSet >= SEGMENT.stop) indexSet -= len;
if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet];
busses.setPixelColor(indexSet, col);
@@ -261,7 +275,6 @@ 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);
}
@@ -527,6 +540,15 @@ uint8_t WS2812FX::getMainSegmentId(void) {
return 0;
}
uint8_t WS2812FX::getActiveSegmentsNum(void) {
uint8_t c = 0;
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].isActive()) c++;
}
return c;
}
uint32_t WS2812FX::getColor(void) {
return _segments[getMainSegmentId()].colors[0];
}
@@ -542,6 +564,7 @@ uint32_t WS2812FX::getPixelColor(uint16_t i)
}
if (i < customMappingSize) i = customMappingTable[i];
if (i >= _length) return 0;
return busses.getPixelColor(i);
}
@@ -563,15 +586,6 @@ uint32_t WS2812FX::getLastShow(void) {
return _lastShow;
}
//TODO these need to be on a per-strip basis
uint8_t WS2812FX::getColorOrder(void) {
return COL_ORDER_GRB;
}
void WS2812FX::setColorOrder(uint8_t co) {
//bus->SetColorOrder(co);
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) {
if (n >= MAX_NUM_SEGMENTS) return;
Segment& seg = _segments[n];
@@ -582,7 +596,11 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
if (seg.stop) setRange(seg.start, seg.stop -1, 0); //turn old segment range off
if (i2 <= i1) //disable segment
{
seg.stop = 0;
seg.stop = 0;
if (seg.name) {
delete[] seg.name;
seg.name = nullptr;
}
if (n == mainSegment) //if main segment is deleted, set first active as main segment
{
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
@@ -607,6 +625,7 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
}
void WS2812FX::resetSegments() {
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) if (_segments[i].name) delete _segments[i].name;
mainSegment = 0;
memset(_segments, 0, sizeof(_segments));
//memset(_segment_runtimes, 0, sizeof(_segment_runtimes));
@@ -635,6 +654,25 @@ 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;
}
}
//After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply)
void WS2812FX::setPixelSegment(uint8_t n)
{
@@ -1012,18 +1050,34 @@ uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8
//load custom mapping table from JSON file
void WS2812FX::deserializeMap(void) {
if (!WLED_FS.exists("/ledmap.json")) return;
void WS2812FX::deserializeMap(uint8_t n) {
char fileName[32];
strcpy_P(fileName, PSTR("/ledmap"));
if (n) sprintf(fileName +7, "%d", n);
strcat(fileName, ".json");
bool isFile = WLED_FS.exists(fileName);
if (!isFile) {
// erase custom mapping if selecting nonexistent ledmap.json (n==0)
if (!n && customMappingTable != nullptr) {
customMappingSize = 0;
delete[] customMappingTable;
customMappingTable = nullptr;
}
return;
}
DynamicJsonDocument doc(JSON_BUFFER_SIZE); // full sized buffer for larger maps
DEBUG_PRINT(F("Reading LED map from "));
DEBUG_PRINTLN(fileName);
DEBUG_PRINTLN(F("Reading LED map from /ledmap.json..."));
if (!readObjectFromFile("/ledmap.json", nullptr, &doc)) return; //if file does not exist just exit
if (!readObjectFromFile(fileName, nullptr, &doc)) return; //if file does not exist just exit
// erase old custom ledmap
if (customMappingTable != nullptr) {
customMappingSize = 0;
delete[] customMappingTable;
customMappingTable = nullptr;
customMappingSize = 0;
}
JsonArray map = doc[F("map")];

View File

@@ -119,10 +119,10 @@ class BusDigital : public Bus {
public:
BusDigital(BusConfig &bc, uint8_t nr) : Bus(bc.type, bc.start) {
if (!IS_DIGITAL(bc.type) || !bc.count) return;
if (!pinManager.allocatePin(bc.pins[0])) return;
if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return;
_pins[0] = bc.pins[0];
if (IS_2PIN(bc.type)) {
if (!pinManager.allocatePin(bc.pins[1])) {
if (!pinManager.allocatePin(bc.pins[1], true, PinOwner::BusDigital)) {
cleanup(); return;
}
_pins[1] = bc.pins[1];
@@ -206,8 +206,8 @@ class BusDigital : public Bus {
_iType = I_NONE;
_valid = false;
_busPtr = nullptr;
pinManager.deallocatePin(_pins[0]);
pinManager.deallocatePin(_pins[1]);
pinManager.deallocatePin(_pins[1], PinOwner::BusDigital);
pinManager.deallocatePin(_pins[0], PinOwner::BusDigital);
}
~BusDigital() {
@@ -242,7 +242,7 @@ class BusPwm : public Bus {
for (uint8_t i = 0; i < numPins; i++) {
uint8_t currentPin = bc.pins[i];
if (!pinManager.allocatePin(currentPin)) {
if (!pinManager.allocatePin(currentPin, true, PinOwner::BusPwm)) {
deallocatePins(); return;
}
_pins[i] = currentPin; // store only after allocatePin() succeeds
@@ -334,7 +334,7 @@ class BusPwm : public Bus {
#else
if (_ledcStart < 16) ledcDetachPin(_pins[i]);
#endif
pinManager.deallocatePin(_pins[i]);
pinManager.deallocatePin(_pins[i], PinOwner::BusPwm);
}
#ifdef ARDUINO_ARCH_ESP32
pinManager.deallocateLedc(_ledcStart, numPins);
@@ -457,4 +457,4 @@ class BusManager {
uint8_t numBusses = 0;
Bus* busses[WLED_MAX_BUSSES];
};
#endif
#endif

View File

@@ -18,6 +18,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
//long vid = doc[F("vid")]; // 2010020
#ifdef WLED_USE_ETHERNET
JsonObject ethernet = doc[F("eth")];
CJSON(ethernetType, ethernet["type"]);
// NOTE: Ethernet configuration takes priority over other use of pins
WLED::instance().initEthernet();
#endif
JsonObject id = doc["id"];
getStringFromJson(cmDNS, id[F("mdns")], 33);
getStringFromJson(serverDescription, id[F("name")], 33);
@@ -53,10 +60,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(apBehavior, ap[F("behav")]);
#ifdef WLED_USE_ETHERNET
JsonObject ethernet = doc[F("eth")];
CJSON(ethernetType, ethernet["type"]);
#endif
/*
JsonArray ap_ip = ap["ip"];
@@ -71,7 +74,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject hw = doc[F("hw")];
// initialize LED pins and lengths prior to other HW
// initialize LED pins and lengths prior to other HW (except for ethernet)
JsonObject hw_led = hw[F("led")];
CJSON(ledCount, hw_led[F("total")]);
@@ -103,7 +106,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint16_t length = elm[F("len")] | 1;
uint8_t colorOrder = (int)elm[F("order")];
uint8_t skipFirst = elm[F("skip")];
uint16_t start = elm[F("start")] | 0;
uint16_t start = elm["start"] | 0;
uint8_t ledType = elm["type"] | TYPE_WS2812_RGB;
bool reversed = elm["rev"];
@@ -130,7 +133,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
for (JsonObject btn : hw_btn_ins) {
CJSON(buttonType[s], btn["type"]);
int8_t pin = btn["pin"][0] | -1;
if (pin > -1 && pinManager.allocatePin(pin,false)) {
if (pin > -1 && pinManager.allocatePin(pin, false, PinOwner::Button)) {
btnPin[s] = pin;
pinMode(btnPin[s], INPUT_PULLUP);
} else {
@@ -155,7 +158,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (fromFS) {
// relies upon only being called once with fromFS == true, which is currently true.
uint8_t s = 0;
if (pinManager.allocatePin(btnPin[0],false)) { // initialized to #define value BTNPIN, or zero if not defined(!)
if (pinManager.allocatePin(btnPin[0], false, PinOwner::Button)) { // initialized to #define value BTNPIN, or zero if not defined(!)
++s; // do not clear default button if allocated successfully
}
for (; s<WLED_MAX_BUTTONS; s++) {
@@ -172,7 +175,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
if (hw_ir_pin > -2) {
if (pinManager.allocatePin(hw_ir_pin,false)) {
if (pinManager.allocatePin(hw_ir_pin, false, PinOwner::IR)) {
irPin = hw_ir_pin;
} else {
irPin = -1;
@@ -183,7 +186,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject relay = hw[F("relay")];
int hw_relay_pin = relay["pin"] | -2;
if (hw_relay_pin > -2) {
if (pinManager.allocatePin(hw_relay_pin,true)) {
if (pinManager.allocatePin(hw_relay_pin,true, PinOwner::Relay)) {
rlyPin = hw_relay_pin;
pinMode(rlyPin, OUTPUT);
} else {
@@ -199,6 +202,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject light = doc[F("light")];
CJSON(briMultiplier, light[F("scale-bri")]);
CJSON(strip.paletteBlend, light[F("pal-mode")]);
CJSON(autoSegments, light[F("aseg")]);
float light_gc_bri = light["gc"]["bri"];
float light_gc_col = light["gc"]["col"]; // 2.8
@@ -237,6 +241,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(receiveNotificationBrightness, if_sync_recv["bri"]);
CJSON(receiveNotificationColor, if_sync_recv["col"]);
CJSON(receiveNotificationEffects, if_sync_recv["fx"]);
CJSON(receiveGroups, if_sync_recv["grp"]);
//! following line might be a problem if called after boot
receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects);
@@ -249,6 +254,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(notifyHue, if_sync_send["hue"]);
CJSON(notifyMacro, if_sync_send["macro"]);
CJSON(notifyTwice, if_sync_send[F("twice")]);
CJSON(syncGroups, if_sync_send["grp"]);
JsonObject if_nodes = interfaces["nodes"];
CJSON(nodeListEnabled, if_nodes[F("list")]);
@@ -392,7 +398,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject dmx = doc["dmx"];
CJSON(DMXChannels, dmx[F("chan")]);
CJSON(DMXGap,dmx[F("gap")]);
CJSON(DMXStart, dmx[F("start")]);
CJSON(DMXStart, dmx["start"]);
CJSON(DMXStartLED,dmx[F("start-led")]);
JsonArray dmx_fixmap = dmx[F("fixmap")];
@@ -433,6 +439,8 @@ void deserializeConfigFromFS() {
return;
}
// NOTE: This routine deserializes *and* applies the configuration
// Therefore, must also initialize ethernet from this function
deserializeConfig(doc.as<JsonObject>(), true);
}
@@ -508,7 +516,7 @@ void serializeConfig() {
Bus *bus = busses.getBus(s);
if (!bus || bus->getLength()==0) break;
JsonObject ins = hw_led_ins.createNestedObject();
ins[F("start")] = bus->getStart();
ins["start"] = bus->getStart();
ins[F("len")] = bus->getLength();
JsonArray ins_pin = ins.createNestedArray("pin");
uint8_t pins[5];
@@ -554,6 +562,7 @@ void serializeConfig() {
JsonObject light = doc.createNestedObject(F("light"));
light[F("scale-bri")] = briMultiplier;
light[F("pal-mode")] = strip.paletteBlend;
light[F("aseg")] = autoSegments;
JsonObject light_gc = light.createNestedObject("gc");
light_gc["bri"] = (strip.gammaCorrectBri) ? 2.8 : 1.0;
@@ -585,6 +594,7 @@ void serializeConfig() {
if_sync_recv["bri"] = receiveNotificationBrightness;
if_sync_recv["col"] = receiveNotificationColor;
if_sync_recv["fx"] = receiveNotificationEffects;
if_sync_recv["grp"] = receiveGroups;
JsonObject if_sync_send = if_sync.createNestedObject("send");
if_sync_send[F("dir")] = notifyDirect;
@@ -593,6 +603,7 @@ void serializeConfig() {
if_sync_send["hue"] = notifyHue;
if_sync_send["macro"] = notifyMacro;
if_sync_send[F("twice")] = notifyTwice;
if_sync_send["grp"] = syncGroups;
JsonObject if_nodes = interfaces.createNestedObject("nodes");
if_nodes[F("list")] = nodeListEnabled;
@@ -608,6 +619,7 @@ void serializeConfig() {
if_live_dmx[F("seqskip")] = e131SkipOutOfSequence;
if_live_dmx[F("addr")] = DMXAddress;
if_live_dmx[F("mode")] = DMXMode;
if_live[F("timeout")] = realtimeTimeoutMs / 100;
if_live[F("maxbri")] = arlsForceMaxBri;
if_live[F("no-gc")] = arlsDisableGammaCorrection;
@@ -707,7 +719,7 @@ void serializeConfig() {
JsonObject dmx = doc.createNestedObject("dmx");
dmx[F("chan")] = DMXChannels;
dmx[F("gap")] = DMXGap;
dmx[F("start")] = DMXStart;
dmx["start"] = DMXStart;
dmx[F("start-led")] = DMXStartLED;
JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap"));

View File

@@ -58,6 +58,7 @@
#define USERMOD_ID_RTC 15 //Usermod "usermod_rtc.h"
#define USERMOD_ID_ELEKSTUBE_IPS 16 //Usermod "usermod_elekstube_ips.h"
#define USERMOD_ID_SN_PHOTORESISTOR 17 //Usermod "usermod_sn_photoresistor.h"
#define USERMOD_ID_BATTERY_STATUS_BASIC 18 //Usermod "usermod_v2_battery_status_basic.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@@ -214,6 +215,7 @@
#define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?)
#define ERR_FS_QUOTA 11 // The FS is full or the maximum file size is reached
#define ERR_FS_PLOAD 12 // It was attempted to load a preset that does not exist
#define ERR_FS_IRLOAD 13 // It was attempted to load an IR JSON cmd, but the "ir.json" file does not exist
#define ERR_FS_GENERAL 19 // A general unspecified filesystem error occured
#define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented)
#define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented)
@@ -290,7 +292,7 @@
#ifdef ESP8266
#define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards
#else
#define LEDPIN 16 // alligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards
#define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards
#endif
#endif

File diff suppressed because one or more lines are too long

View File

@@ -156,7 +156,7 @@
<p>Transition: <input id="tt" class="noslide" type="number" min="0" max="65.5" step="0.1" value="0.7">s</p>
</div>
<div id="Favorites" class="tabcontent">
<div id="Presets" class="tabcontent">
<div id="putil">
</div>
@@ -173,7 +173,7 @@
<button class="tablinks" onclick="openTab(0)"><i class="icons">&#xe2b3;</i><p class="tab-label">Colors</p></button>
<button class="tablinks" onclick="openTab(1)"><i class="icons">&#xe23d;</i><p class="tab-label">Effects</p></button>
<button class="tablinks" onclick="openTab(2)"><i class="icons">&#xe34b;</i><p class="tab-label">Segments</p></button>
<button class="tablinks" onclick="openTab(3)"><i class="icons">&#xe04c;</i><p class="tab-label">Favorites</p></button>
<button class="tablinks" onclick="openTab(3)"><i class="icons">&#xe04c;</i><p class="tab-label">Presets</p></button>
</div>
<div id="connind"></div>

View File

@@ -26,8 +26,16 @@ var ws;
var fxlist = d.getElementById('fxlist'), pallist = d.getElementById('pallist');
var cfg = {
theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}},
comp :{colors:{picker: true, rgb: false, quick: true, hex: false}, labels:true, pcmbot:false, pid:true}
comp :{colors:{picker: true, rgb: false, quick: true, hex: false},
labels:true, pcmbot:false, pid:true, seglen:false, css:true, hdays:false}
};
var hol = [
[0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas
[0,2,17,1,"https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day
[2022,3,17,2,"https://aircoookie.github.io/easter.png"],
[2023,3,9,2,"https://aircoookie.github.io/easter.png"],
[2024,2,31,2,"https://aircoookie.github.io/easter.png"]
];
var cpick = new iro.ColorPicker("#picker", {
width: 260,
@@ -158,13 +166,6 @@ function loadBg(iUrl) {
img.src = iUrl;
if (iUrl == "") {
var today = new Date();
var hol = [
[0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas
[0,2,17,1,"https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day
[2022,3,17,2,"https://aircoookie.github.io/easter.png"],
[2023,3,9,2,"https://aircoookie.github.io/easter.png"],
[2024,2,31,2,"https://aircoookie.github.io/easter.png"]
];
for (var i=0; i<hol.length; i++) {
var yr = hol[i][0]==0 ? today.getFullYear() : hol[i][0];
var hs = new Date(yr,hol[i][1],hol[i][2]);
@@ -182,6 +183,21 @@ function loadBg(iUrl) {
});
}
function loadSkinCSS(cId)
{
if (!d.getElementById(cId)) // check if element exists
{
var h = document.getElementsByTagName('head')[0];
var l = document.createElement('link');
l.id = cId;
l.rel = 'stylesheet';
l.type = 'text/css';
l.href = (loc?`http://${locip}`:'.') + '/skin.css';
l.media = 'all';
h.appendChild(l);
}
}
function onLoad() {
if (window.location.protocol == "file:") {
loc = true;
@@ -198,7 +214,27 @@ function onLoad() {
resetPUtil();
applyCfg();
loadBg(cfg.theme.bg.url);
if (cfg.comp.hdays) { //load custom holiday list
fetch((loc?`http://${locip}`:'.') + "/holidays.json", { // may be loaded from external source
method: 'get'
})
.then(res => {
//if (!res.ok) showErrorToast();
return res.json();
})
.then(json => {
if (Array.isArray(json)) hol = json;
//TODO: do some parsing first
})
.catch(function (error) {
console.log("holidays.json does not contain array of holidays. Defaults loaded.");
})
.finally(function(){
loadBg(cfg.theme.bg.url);
});
} else
loadBg(cfg.theme.bg.url);
if (cfg.comp.css) loadSkinCSS('skinCss');
var cd = d.getElementById('csl').children;
for (var i = 0; i < cd.length; i++) {
@@ -211,7 +247,7 @@ function onLoad() {
setColor(1);
});
pmtLS = localStorage.getItem('wledPmt');
setTimeout(function(){requestJson(null, false);}, 25);
setTimeout(function(){requestJson(null, false);}, 50);
d.addEventListener("visibilitychange", handleVisibilityChange, false);
size();
d.getElementById("cv").style.opacity=0;
@@ -542,11 +578,13 @@ function populateSegments(s)
<input type="checkbox" id="seg${i}sel" onchange="selSeg(${i})" ${inst.sel ? "checked":""}>
<span class="checkmark schk"></span>
</label>
<div class="segname" onclick="selSegEx(${i})">
Segment ${i}
<div class="segname">
<div class="segntxt" onclick="selSegEx(${i})">${inst.n ? inst.n : "Segment "+i}</div>
<i class="icons edit-icon ${expanded[i] ? "expanded":""}" id="seg${i}nedit" onclick="tglSegn(${i})">&#xe2c6;</i>
</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}">
<input type="text" class="ptxt stxt noslide" id="seg${i}t" autocomplete="off" maxlength=32 value="${inst.n?inst.n:""}" placeholder="Enter name..."/>
<div class="sbs">
<i class="icons e-icon pwr ${powered[i] ? "act":""}" id="seg${i}pwr" onclick="setSegPwr(${i})">&#xe08f;</i>
<div class="sliderwrap il sws">
@@ -557,12 +595,12 @@ function populateSegments(s)
<table class="infot">
<tr>
<td class="segtd">Start LED</td>
<td class="segtd">Stop LED</td>
<td class="segtd">${cfg.comp.seglen?"Length":"Stop LED"}</td>
<td class="segtd">Offset</td>
</tr>
<tr>
<td class="segtd"><input class="noslide segn" id="seg${i}s" type="number" min="0" max="${ledCount-1}" value="${inst.start}" oninput="updateLen(${i})"></td>
<td class="segtd"><input class="noslide segn" id="seg${i}e" type="number" min="0" max="${ledCount}" value="${inst.stop}" oninput="updateLen(${i})"></td>
<td class="segtd"><input class="noslide segn" id="seg${i}e" type="number" min="0" max="${ledCount-(cfg.comp.seglen?inst.start:0)}" value="${inst.stop-(cfg.comp.seglen?inst.start:0)}" oninput="updateLen(${i})"></td>
<td class="segtd"><input class="noslide segn" id="seg${i}of" type="number" value="${inst.of}" oninput="updateLen(${i})"></td>
</tr>
</table>
@@ -613,7 +651,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-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++) {
@@ -659,15 +697,14 @@ function populatePalettes(palettes)
});
var html = `<div class="searchbar"><input type="text" class="search" placeholder="Search" oninput="search(this)" />
<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++) {
let previewCss = genPalPrevCss(palettes[i].id);
html += generateListItemHtml(
'palette',
palettes[i].id,
palettes[i].name,
'setPalette',
`<div class="lstIprev" style="${previewCss}"></div>`,
`<div class="lstIprev" style="${genPalPrevCss(palettes[i].id)}"></div>`,
palettes[i].class,
);
}
@@ -693,7 +730,6 @@ function genPalPrevCss(id)
return;
}
var paletteData = palettesData[id];
var previewCss = "";
if (!paletteData) {
return 'display: none';
@@ -855,7 +891,7 @@ function updateLen(s)
if (!d.getElementById(`seg${s}s`)) return;
var start = parseInt(d.getElementById(`seg${s}s`).value);
var stop = parseInt(d.getElementById(`seg${s}e`).value);
var len = stop - start;
var len = stop - (cfg.comp.seglen?0:start);
var out = "(delete)";
if (len > 1) {
out = `${len} LEDs`;
@@ -986,7 +1022,7 @@ function readState(s,command=false) {
if (isRgbw) whites[e] = parseInt(i.col[e][3]);
selectSlot(csel);
}
d.getElementById('sliderSpeed').value = whites[csel];
d.getElementById('sliderW').value = whites[csel];
d.getElementById('sliderSpeed').value = i.sx;
d.getElementById('sliderIntensity').value = i.ix;
@@ -1063,7 +1099,7 @@ function requestJson(command, rinfo = true) {
command.v = true; //get complete API response
command.time = Math.floor(Date.now() / 1000);
var t = d.getElementById('tt');
if (t.validity.valid) {
if (t.validity.valid && command.transition===undefined) {
var tn = parseInt(t.value*10);
if (tn != tr) command.transition = tn;
}
@@ -1218,23 +1254,25 @@ function toggleNodes() {
function makeSeg() {
var ns = 0;
if (lowestUnused > 0) {
var pend = d.getElementById(`seg${lowestUnused -1}e`).value;
var pend = parseInt(d.getElementById(`seg${lowestUnused -1}e`).value,10) + (cfg.comp.seglen?parseInt(d.getElementById(`seg${lowestUnused -1}s`).value,10):0);
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">Stop 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}" value="${ledCount}" 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>
@@ -1277,7 +1315,7 @@ function refreshPlE(p) {
}
plEDiv.innerHTML = content;
var dels = plEDiv.getElementsByClassName("btn-pl-del");
if (dels.length < 2 && p > 0) dels[0].style.display = "none";
if (dels.length < 2) dels[0].style.display = "none";
var sels = d.getElementById(`seg${p+100}`).getElementsByClassName("sel");
for (var i of sels) {
@@ -1297,7 +1335,7 @@ function addPl(p,i) {
}
function delPl(p,i) {
if (plJson[p].ps.length < 2) {if (p == 0) resetPUtil(); return;}
if (plJson[p].ps.length < 2) return;
plJson[p].ps.splice(i,1);
plJson[p].dur.splice(i,1);
plJson[p].transition.splice(i,1);
@@ -1371,7 +1409,7 @@ function makeP(i,pl) {
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="stxt noslide" maxlength=2 value="${qlName(i)}" id="p${i}ql" autocomplete="off"/></div>
<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">
@@ -1448,6 +1486,12 @@ function tglCs(i){
d.getElementById(`p${i}o2`).style.display = !pss? "block" : "none";
}
function tglSegn(s)
{
d.getElementById(`seg${s}t`).style.display =
(window.getComputedStyle(d.getElementById(`seg${s}t`)).display === "none") ? "inline":"none";
}
function selSegEx(s)
{
var obj = {"seg":[]};
@@ -1464,10 +1508,11 @@ function selSeg(s){
}
function setSeg(s){
var name = d.getElementById(`seg${s}t`).value;
var start = parseInt(d.getElementById(`seg${s}s`).value);
var stop = parseInt(d.getElementById(`seg${s}e`).value);
if (stop <= start) {delSeg(s); return;}
var obj = {"seg": {"id": s, "start": start, "stop": stop}};
var obj = {"seg": {"id": s, "n": name, "start": start, "stop": (cfg.comp.seglen?start:0)+stop}};
if (d.getElementById(`seg${s}grp`))
{
var grp = parseInt(d.getElementById(`seg${s}grp`).value);
@@ -1921,7 +1966,10 @@ function expand(i,a)
if (!a) expanded[i] = !expanded[i];
d.getElementById('seg' +i).style.display = (expanded[i]) ? "block":"none";
d.getElementById('sege' +i).style.transform = (expanded[i]) ? "rotate(180deg)":"rotate(0deg)";
if (i < 100) return; //no preset, we are done
if (i < 100) {
d.getElementById(`seg${i}nedit`).style.display = (expanded[i]) ? "inline":"none";
return; //no preset, we are done
}
var p = i-100;
d.getElementById(`p${p}o`).style.background = (expanded[i] || p != currentPreset)?"var(--c-2)":"var(--c-6)";

View File

@@ -18,6 +18,16 @@
function off(n){
d.getElementsByName(n)[0].value = -1;
}
var timeout;
function showToast(text, error = false)
{
var x = gId("toast");
x.innerHTML = text;
x.className = error ? "error":"show";
clearTimeout(timeout);
x.style.animation = 'none';
timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900);
}
function bLimits(b,p,m) {
maxB = b; maxM = m; maxPB = p;
}
@@ -130,7 +140,9 @@
}
}
if (type == 30 || type == 31 || (type > 40 && type < 46 && type != 43)) isRGBW = true;
gId("dig"+n).style.display = (type > 31 && type < 48) ? "none":"inline";
gId("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:";
}
}
@@ -204,6 +216,7 @@
s2 += "A is enough)<br>";
gId('psu').innerHTML = s;
gId('psu2').innerHTML = isWS2815 ? "" : s2;
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
}
function lastEnd(i) {
if (i<1) return 0;
@@ -259,11 +272,10 @@ Color Order:
<span id="p4d${i}"></span><input type="number" class="xs" name="L4${i}" min="0" max="33" onchange="UI()"/>
<br>
<span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" min="0" max="8191" value="${lastEnd(i)}" required />&nbsp;
<div id="dig${i}" style="display:inline">
Count: <input type="number" name="LC${i}" min="0" max="${maxPB}" value="1" required oninput="UI()" /><br>
Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
&nbsp;Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}"><br>
</div>
<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>
<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>
</div>`;
f.insertAdjacentHTML("beforeend", cn);
}
@@ -293,6 +305,17 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
c += `</select>`;
c += `<span style="cursor: pointer;" onclick="off('${bt}')">&nbsp;&#215;</span><br>`;
gId("btns").innerHTML = c;
}
function uploadFile(name) {
var req = new XMLHttpRequest();
req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)});
req.addEventListener('error', function(e){showToast(e.stack,true);});
req.open("POST", "/upload");
var formData = new FormData();
formData.append("data", d.Sf.data.files[0], name);
req.send(formData);
d.Sf.data.value = '';
return false;
}
function GetV()
{
@@ -347,10 +370,12 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
<div id="ledwarning" style="color: orange; display: none;">
&#9888; You might run into stability or lag issues.<br>
Use less than <span id="wreason">800 LEDs per pin</span> for the best experience!<br>
</div><hr style="width:260px">
</div>
Make a segment for each output: <input type="checkbox" name="MS"> <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">
IR pin: <input type="number" class="xs" min="-1" max="40" name="IR" onchange="UI()">&nbsp;<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>
@@ -361,6 +386,8 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
<option value=7>9-key red</option>
<option value=8>JSON remote</option>
</select><span style="cursor: pointer;" onclick="off('IR')">&nbsp;&#215;</span><br>
<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>
<hr style="width:260px">
@@ -404,7 +431,7 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
<option value=4>Legacy</option>
</select>
<br></span><hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>

View File

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

View File

@@ -1,23 +1,86 @@
<!DOCTYPE html>
<html lang="en"><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>Sync Settings</title>
<script>var d=document;
function gId(s)
{
return d.getElementById(s);
}
function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#sync-settings");}function B(){window.open("/settings","_self");}
function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.DA.value == 1) d.Sf.DA.value = 0; if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;}
else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} }
function SP(){var p = d.Sf.DI.value; d.getElementById("xp").style.display = (p > 0)?"none":"block"; if (p > 0) d.Sf.EP.value = p;}
function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 5568; break; case 6454: d.Sf.DI.value = 6454; break; case 4048: d.Sf.DI.value = 4048; break; }; SP();}
function FC()
{
for(j=0;j<8;j++)
{
gId("G"+(j+1)).checked=gId("GS").value>>j&1;
gId("R"+(j+1)).checked=gId("GR").value>>j&1;
}
}
function GC()
{
var a=0, b=0;
var m=1;
for(j=0;j<8;j++)
{
a+=gId("G"+(j+1)).checked*m;
b+=gId("R"+(j+1)).checked*m;
m*=2;
}
gId("GS").value=a;
gId("GR").value=b;
}
function SP(){var p = d.Sf.DI.value; gId("xp").style.display = (p > 0)?"none":"block"; if (p > 0) d.Sf.EP.value = p;}
function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 5568; break; case 6454: d.Sf.DI.value = 6454; break; case 4048: d.Sf.DI.value = 4048; break; }; SP();FC();}
function S(){GetV();SetVal();}
function GetV(){var d=document;}
</script>
<style>@import url("style.css");</style></head>
<body onload="S()">
<form id="form_s" name="Sf" method="post">
<form id="form_s" name="Sf" method="post" onsubmit="GC()">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
<h2>Sync setup</h2>
<h3>WLED Broadcast</h3>
UDP Port: <input name="UP" type="number" min="1" max="65535" class="d5" required><br>
2nd Port: <input name="U2" type="number" min="1" max="65535" class="d5" required><br>
2nd Port: <input name="U2" type="number" min="1" max="65535" class="d5" required><br><br>
<input name="GS" id="GS" type="number" style="display: none;"> <!-- hidden inputs for bitwise group checkboxes -->
<input name="GR" id="GR" type="number" style="display: none;">
<table style="margin: 0 auto;">
<tr>
<td>Sync groups</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
</tr>
<tr>
<td>Send:</td>
<td><input type="checkbox" id="G1" name="G1"></td>
<td><input type="checkbox" id="G2" name="G2"></td>
<td><input type="checkbox" id="G3" name="G3"></td>
<td><input type="checkbox" id="G4" name="G4"></td>
<td><input type="checkbox" id="G5" name="G5"></td>
<td><input type="checkbox" id="G6" name="G6"></td>
<td><input type="checkbox" id="G7" name="G7"></td>
<td><input type="checkbox" id="G8" name="G8"></td>
</tr>
<tr>
<td>Receive:</td>
<td><input type="checkbox" id="R1" name="R1"></td>
<td><input type="checkbox" id="R2" name="R2"></td>
<td><input type="checkbox" id="R3" name="R3"></td>
<td><input type="checkbox" id="R4" name="R4"></td>
<td><input type="checkbox" id="R5" name="R5"></td>
<td><input type="checkbox" id="R6" name="R6"></td>
<td><input type="checkbox" id="R7" name="R7"></td>
<td><input type="checkbox" id="R8" name="R8"></td>
</tr>
</table><br>
Receive: <input type="checkbox" name="RB">Brightness, <input type="checkbox" name="RC">Color, and <input type="checkbox" name="RX">Effects<br>
Send notifications on direct change: <input type="checkbox" name="SD"><br>
Send notifications on button press or IR: <input type="checkbox" name="SB"><br>
@@ -28,7 +91,7 @@ Send notifications twice: <input type="checkbox" name="S2"><br>
<i>Reboot required to apply changes. </i>
<h3>Instance List</h3>
Enable instance list: <input type="checkbox" name="NL"><br>
Make this instance discoverable: <input type="checkbox" name="NB"><br>
Make this instance discoverable: <input type="checkbox" name="NB">
<h3>Realtime</h3>
Receive UDP realtime: <input type="checkbox" name="RD"><br><br>
<i>Network DMX input</i><br>

View File

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

View File

@@ -9,16 +9,20 @@ body {
hr {
border-color: #666;
}
button {
button, .btn {
background: #333;
color: #fff;
font-family: Verdana, sans-serif;
border: 0.3ch solid #333;
display: inline-block;
font-size: 20px;
margin: 8px;
margin-top: 12px;
margin: 12px 8px 8px;
padding: 1px 6px;
cursor: pointer;
text-decoration: none;
}
.lnk {
border: 0;
}
.helpB {
text-align: left;
@@ -42,16 +46,16 @@ input[type="number"].xl {
width: 85px;
}
input[type="number"].l {
width: 60px;
width: 63px;
}
input[type="number"].m {
width: 55px;
width: 56px;
}
input[type="number"].s {
width: 42px;
width: 49px;
}
input[type="number"].xs {
width: 35px;
width: 42px;
}
input[type="checkbox"] {
transform: scale(1.5);
@@ -69,3 +73,32 @@ td {
.d5 {
width: 4.5em !important;
}
#toast {
opacity: 0;
background-color: #444;
border-radius: 5px;
bottom: 64px;
color: #fff;
font-size: 17px;
padding: 16px;
pointer-events: none;
position: fixed;
text-align: center;
z-index: 5;
transform: translateX(-50%%); /* %% because of AsyncWebServer */
max-width: 90%%; /* %% because of AsyncWebServer */
left: 50%%; /* %% because of AsyncWebServer */
}
#toast.show {
opacity: 1;
background-color: #264;
animation: fadein 0.5s, fadein 0.5s 2.5s reverse;
}
#toast.error {
opacity: 1;
background-color: #b21;
animation: fadein 0.5s;
}

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -119,7 +119,7 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len)
const char* apikey = root[0][F("success")][F("username")];
if (apikey != nullptr && strlen(apikey) < sizeof(hueApiKey))
{
strcpy(hueApiKey, apikey);
strlcpy(hueApiKey, apikey, sizeof(hueApiKey));
hueAuthRequired = false;
hueNewKey = true;
}

View File

@@ -71,9 +71,11 @@ void decBrightness()
// apply preset or fallback to a effect and palette if it doesn't exist
void presetFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID)
{
byte prevError = errorFlag;
if (!applyPreset(presetID, CALL_MODE_BUTTON)) {
effectCurrent = effectID;
effectPalette = paletteID;
errorFlag = prevError; //clear error 12 from non-existent preset
}
}
@@ -161,7 +163,11 @@ void decodeIR(uint32_t code)
}
lastValidCode = 0; irTimesRepeated = 0;
if (decodeIRCustom(code)) return;
if (code > 0xFFFFFF) return; //invalid code
if (irEnabled == 8) { // any remote configurable with ir.json file
decodeIRJson(code);
return;
}
if (code > 0xFFFFFF) return; //invalid code
switch (irEnabled) {
case 1:
if (code > 0xF80000) {
@@ -178,7 +184,7 @@ void decodeIR(uint32_t code)
// "VOL +" controls effect, "VOL -" controls colour/palette, "MUTE"
// sets bright plain white
case 7: decodeIR9(code); break;
case 8: decodeIRJson(code); break; // any remote configurable with ir.json file
//case 8: return; // ir.json file, handled above switch statement
default: return;
}
@@ -568,53 +574,57 @@ void decodeIRJson(uint32_t code)
sprintf(objKey, "\"0x%X\":", code);
errorFlag = readObjectFromFile("/ir.json", objKey, &irDoc) ? ERR_NONE : ERR_FS_PLOAD;
readObjectFromFile("/ir.json", objKey, &irDoc);
fdo = irDoc.as<JsonObject>();
lastValidCode = 0;
if (!errorFlag)
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
return;
}
cmd = fdo["cmd"]; //string
cmdStr = String(cmd);
jsonCmdObj = fdo["cmd"]; //object
if (!cmdStr.isEmpty())
{
cmd = fdo["cmd"];
cmdStr = String(cmd);
jsonCmdObj = fdo["cmd"];
if (!cmdStr.isEmpty())
{
if (cmdStr.startsWith("!")) {
// call limited set of C functions
if (cmdStr.startsWith(F("!incBri"))) {
lastValidCode = code;
incBrightness();
} else if (cmdStr.startsWith(F("!decBri"))) {
lastValidCode = code;
decBrightness();
} else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback
uint8_t p1 = fdo["PL"] ? fdo["PL"] : 1;
uint8_t p2 = fdo["FX"] ? fdo["FX"] : random8(100);
uint8_t p3 = fdo["FP"] ? fdo["FP"] : 0;
presetFallback(p1, p2, p3);
}
} else {
// HTTP API command
if (cmdStr.indexOf("~") || fdo["rpt"])
{
// repeatable action
lastValidCode = code;
}
if (effectCurrent == 0 && cmdStr.indexOf("FP=") > -1) {
// setting palette but it wont show because effect is solid
effectCurrent = FX_MODE_GRADIENT;
}
if (!cmdStr.startsWith("win&")) {
cmdStr = "win&" + cmdStr;
}
handleSet(nullptr, cmdStr, false);
}
} 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;
}
if (cmdStr.startsWith("!")) {
// call limited set of C functions
if (cmdStr.startsWith(F("!incBri"))) {
lastValidCode = code;
incBrightness();
} else if (cmdStr.startsWith(F("!decBri"))) {
lastValidCode = code;
decBrightness();
} else if (cmdStr.startsWith(F("!presetF"))) { //!presetFallback
uint8_t p1 = fdo["PL"] ? fdo["PL"] : 1;
uint8_t p2 = fdo["FX"] ? fdo["FX"] : random8(MODE_COUNT);
uint8_t p3 = fdo["FP"] ? fdo["FP"] : 0;
presetFallback(p1, p2, p3);
}
} else {
// HTTP API command
if (cmdStr.indexOf("~") || fdo["rpt"])
{
// repeatable action
lastValidCode = code;
}
if (effectCurrent == 0 && cmdStr.indexOf("FP=") > -1) {
// setting palette but it wont show because effect is solid
effectCurrent = FX_MODE_GRADIENT;
}
if (!cmdStr.startsWith("win&")) {
cmdStr = "win&" + cmdStr;
}
handleSet(nullptr, cmdStr, false);
}
} 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;
}
}

View File

@@ -15,14 +15,39 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
//WS2812FX::Segment prev;
//prev = seg; //make a backup so we can tell if something changed
uint16_t start = elem[F("start")] | seg.start;
uint16_t start = elem["start"] | seg.start;
int stop = elem["stop"] | -1;
if (stop < 0) {
uint16_t len = elem[F("len")];
stop = (len > 0) ? start + len : seg.stop;
}
uint16_t grp = elem[F("grp")] | seg.grouping;
if (elem["n"]) {
// name field exists
if (seg.name) { //clear old name
delete[] seg.name;
seg.name = nullptr;
}
const char * name = elem["n"].as<const char*>();
size_t len = 0;
if (name != nullptr) len = strlen(name);
if (len > 0 && len < 33) {
seg.name = new char[len+1];
if (seg.name) strlcpy(seg.name, name, 33);
} else {
// but is empty (already deleted above)
elem.remove("n");
}
} else if (start != seg.start || stop != seg.stop) {
// clearing or setting segment without name field
if (seg.name) {
delete[] seg.name;
seg.name = nullptr;
}
}
uint16_t grp = elem["grp"] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing;
strip.setSegment(id, start, stop, grp, spc);
@@ -45,7 +70,9 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
seg.setOption(SEG_OPTION_ON, 1, id);
}
seg.setOption(SEG_OPTION_ON, elem["on"] | seg.getOption(SEG_OPTION_ON), 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);
JsonArray colarr = elem["col"];
if (!colarr.isNull())
@@ -328,17 +355,19 @@ void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool fo
{
root["id"] = id;
if (segmentBounds) {
root[F("start")] = seg.start;
root["start"] = seg.start;
root["stop"] = seg.stop;
}
if (!forPreset) root[F("len")] = seg.stop - seg.start;
root[F("grp")] = seg.grouping;
root["grp"] = seg.grouping;
root[F("spc")] = seg.spacing;
root[F("of")] = seg.offset;
root["on"] = seg.getOption(SEG_OPTION_ON);
byte segbri = seg.opacity;
root["bri"] = (segbri) ? segbri : 255;
if (segmentBounds && seg.name != nullptr) root["n"] = reinterpret_cast<const char *>(seg.name); //not good practice, but decreases required JSON buffer
char colstr[70]; colstr[0] = '['; colstr[1] = '\0'; //max len 68 (5 chan, all 255)
for (uint8_t i = 0; i < 3; i++)
@@ -655,37 +684,37 @@ void serializePalettes(JsonObject root, AsyncWebServerRequest* request)
curPalette.add("r");
break;
case 2: //primary color only
curPalette.add(F("c1"));
curPalette.add("c1");
break;
case 3: //primary + secondary
curPalette.add(F("c1"));
curPalette.add(F("c1"));
curPalette.add(F("c2"));
curPalette.add(F("c2"));
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c2");
curPalette.add("c2");
break;
case 4: //primary + secondary + tertiary
curPalette.add(F("c3"));
curPalette.add(F("c2"));
curPalette.add(F("c1"));
curPalette.add("c3");
curPalette.add("c2");
curPalette.add("c1");
break;
case 5: {//primary + secondary (+tert if not off), more distinct
curPalette.add(F("c1"));
curPalette.add(F("c1"));
curPalette.add(F("c1"));
curPalette.add(F("c1"));
curPalette.add(F("c1"));
curPalette.add(F("c2"));
curPalette.add(F("c2"));
curPalette.add(F("c2"));
curPalette.add(F("c2"));
curPalette.add(F("c2"));
curPalette.add(F("c3"));
curPalette.add(F("c3"));
curPalette.add(F("c3"));
curPalette.add(F("c3"));
curPalette.add(F("c3"));
curPalette.add(F("c1"));
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c1");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c2");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c3");
curPalette.add("c1");
break;}
case 6: //Party colors
setPaletteColors(curPalette, PartyColors_p);

View File

@@ -46,10 +46,6 @@ byte scaledBri(byte in)
void setAllLeds() {
if (!realtimeMode || !arlsForceMaxBri)
{
strip.setBrightness(scaledBri(briT));
}
if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY)
{
colorRGBtoRGBW(col);
@@ -61,6 +57,10 @@ void setAllLeds() {
{
col[3] = 0; colSec[3] = 0;
}
if (!realtimeMode || !arlsForceMaxBri)
{
strip.setBrightness(scaledBri(briT));
}
}
@@ -142,7 +142,6 @@ void colorUpdated(int callMode)
}
if (briT == 0)
{
//setLedsStandard(true); //do not color transition if starting from off!
if (callMode != CALL_MODE_NOTIFICATION) resetTimebase(); //effect start from beginning
}

View File

@@ -26,21 +26,21 @@ void onMqttConnect(bool sessionPresent)
char subuf[38];
if (mqttDeviceTopic[0] != 0) {
strcpy(subuf, mqttDeviceTopic);
strlcpy(subuf, mqttDeviceTopic, 33);
mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0);
strcpy(subuf, mqttDeviceTopic);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/api"));
mqtt->subscribe(subuf, 0);
}
if (mqttGroupTopic[0] != 0) {
strcpy(subuf, mqttGroupTopic);
strlcpy(subuf, mqttGroupTopic, 33);
mqtt->subscribe(subuf, 0);
strcat_P(subuf, PSTR("/col"));
mqtt->subscribe(subuf, 0);
strcpy(subuf, mqttGroupTopic);
strlcpy(subuf, mqttGroupTopic, 33);
strcat_P(subuf, PSTR("/api"));
mqtt->subscribe(subuf, 0);
}
@@ -122,22 +122,22 @@ void publishMqtt()
char subuf[38];
sprintf_P(s, PSTR("%u"), bri);
strcpy(subuf, mqttDeviceTopic);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/g"));
mqtt->publish(subuf, 0, true, s);
sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2]));
strcpy(subuf, mqttDeviceTopic);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/c"));
mqtt->publish(subuf, 0, true, s);
strcpy(subuf, mqttDeviceTopic);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/status"));
mqtt->publish(subuf, 0, true, "online");
char apires[1024];
XML_response(nullptr, apires);
strcpy(subuf, mqttDeviceTopic);
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/v"));
mqtt->publish(subuf, 0, false, apires);
}
@@ -167,7 +167,7 @@ bool initMqtt()
mqtt->setClientId(mqttClientID);
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
strcpy(mqttStatusTopic, mqttDeviceTopic);
strlcpy(mqttStatusTopic, mqttDeviceTopic, 33);
strcat_P(mqttStatusTopic, PSTR("/status"));
mqtt->setWill(mqttStatusTopic, 0, true, "offline");
mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME);

View File

@@ -359,7 +359,7 @@ void _drawOverlayCronixie()
}
#else // WLED_DISABLE_CRONIXIE
byte getSameCodeLength(char code, int index, char const cronixieDisplay[]) {}
byte getSameCodeLength(char code, int index, char const cronixieDisplay[]) { return 0; }
void setCronixie() {}
void _overlayCronixie() {}
void _drawOverlayCronixie() {}

View File

@@ -13,7 +13,7 @@
#ifndef PalettesWLED_h
#define PalettesWLED_h
#define GRADIENT_PALETTE_COUNT 43
#define GRADIENT_PALETTE_COUNT 58
const byte ib_jul01_gp[] PROGMEM = {
0, 194, 1, 1,
@@ -631,7 +631,7 @@ const byte temperature_gp[] PROGMEM = {
240, 80, 3, 3,
255, 80, 3, 3};
const byte Aurora2[] PROGMEM = {
const byte Aurora2_gp[] PROGMEM = {
0, 17, 177, 13, //Greenish
64, 121, 242, 5, //Greenish
128, 25, 173, 121, //Turquoise
@@ -639,6 +639,213 @@ const byte temperature_gp[] PROGMEM = {
255, 171, 101, 221 //Purple
};
// Gradient palette "bhw1_01_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_01.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Size: 12 bytes of program space.
const byte retro_clown_gp[] PROGMEM = {
0, 227,101, 3,
117, 194, 18, 19,
255, 92, 8,192};
// Gradient palette "bhw1_04_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_04.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Size: 20 bytes of program space.
const byte candy_gp[] PROGMEM = {
0, 229,227, 1,
15, 227,101, 3,
142, 40, 1, 80,
198, 17, 1, 79,
255, 0, 0, 45};
// Gradient palette "bhw1_05_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_05.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Size: 8 bytes of program space.
const byte toxy_reaf_gp[] PROGMEM = {
0, 1,221, 53,
255, 73, 3,178};
// Gradient palette "bhw1_06_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_06.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Size: 16 bytes of program space.
const byte fairy_reaf_gp[] PROGMEM = {
0, 184, 1,128,
160, 1,193,182,
219, 153,227,190,
255, 255,255,255};
// Gradient palette "bhw1_14_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_14.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Size: 36 bytes of program space.
const byte semi_blue_gp[] PROGMEM = {
0, 0, 0, 0,
12, 1, 1, 3,
53, 8, 1, 22,
80, 4, 6, 89,
119, 2, 25,216,
145, 7, 10, 99,
186, 15, 2, 31,
233, 2, 1, 5,
255, 0, 0, 0};
// Gradient palette "bhw1_three_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_three.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Size: 32 bytes of program space.
const byte pink_candy_gp[] PROGMEM = {
0, 255,255,255,
45, 7, 12,255,
112, 227, 1,127,
112, 227, 1,127,
140, 255,255,255,
155, 227, 1,127,
196, 45, 1, 99,
255, 255,255,255};
// Gradient palette "bhw1_w00t_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_w00t.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Size: 16 bytes of program space.
const byte red_reaf_gp[] PROGMEM = {
0, 3, 13, 43,
104, 78,141,240,
188, 255, 0, 0,
255, 28, 1, 1};
// Gradient palette "bhw2_23_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_23.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Red & Flash in SR
// Size: 28 bytes of program space.
const byte aqua_flash_gp[] PROGMEM = {
0, 0, 0, 0,
66, 57,227,233,
96, 255,255, 8,
124, 255,255,255,
153, 255,255, 8,
188, 57,227,233,
255, 0, 0, 0};
// Gradient palette "bhw2_xc_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_xc.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// YBlue in SR
// Size: 28 bytes of program space.
const byte yelblu_hot_gp[] PROGMEM = {
0, 4, 2, 9,
58, 16, 0, 47,
122, 24, 0, 16,
158, 144, 9, 1,
183, 179, 45, 1,
219, 220,114, 2,
255, 234,237, 1};
// Gradient palette "bhw2_45_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_45.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Size: 24 bytes of program space.
const byte lite_light_gp[] PROGMEM = {
0, 0, 0, 0,
9, 1, 1, 1,
40, 5, 5, 6,
66, 5, 5, 6,
101, 10, 1, 12,
255, 0, 0, 0};
// Gradient palette "bhw2_22_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_22.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Pink Plasma in SR
// Size: 20 bytes of program space.
const byte red_flash_gp[] PROGMEM = {
0, 0, 0, 0,
99, 227, 1, 1,
130, 249,199, 95,
155, 227, 1, 1,
255, 0, 0, 0};
// Gradient palette "bhw3_40_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw3/tn/bhw3_40.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Size: 32 bytes of program space.
const byte blink_red_gp[] PROGMEM = {
0, 1, 1, 1,
43, 4, 1, 11,
76, 10, 1, 3,
109, 161, 4, 29,
127, 255, 86,123,
165, 125, 16,160,
204, 35, 13,223,
255, 18, 2, 18};
// Gradient palette "bhw3_52_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw3/tn/bhw3_52.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Yellow2Blue in SR
// Size: 28 bytes of program space.
const byte red_shift_gp[] PROGMEM = {
0, 31, 1, 27,
45, 34, 1, 16,
99, 137, 5, 9,
132, 213,128, 10,
175, 199, 22, 1,
201, 199, 9, 6,
255, 1, 0, 1};
// Gradient palette "bhw4_097_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw4/tn/bhw4_097.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Yellow2Red in SR
// Size: 44 bytes of program space.
const byte red_tide_gp[] PROGMEM = {
0, 247, 5, 0,
28, 255, 67, 1,
43, 234, 88, 11,
58, 234,176, 51,
84, 229, 28, 1,
114, 113, 12, 1,
140, 255,225, 44,
168, 113, 12, 1,
196, 244,209, 88,
216, 255, 28, 1,
255, 53, 1, 1};
// Gradient palette "bhw4_017_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw4/tn/bhw4_017.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
// Size: 40 bytes of program space.
const byte candy2_gp[] PROGMEM = {
0, 39, 33, 34,
25, 4, 6, 15,
48, 49, 29, 22,
73, 224,173, 1,
89, 177, 35, 5,
130, 4, 6, 15,
163, 255,114, 6,
186, 224,173, 1,
211, 39, 33, 34,
255, 1, 1, 1};
// Single array of defined cpt-city color palettes.
// This will let us programmatically choose one based on
// a number, rather than having to activate each explicitly
@@ -686,7 +893,22 @@ const byte* const gGradientPalettes[] PROGMEM = {
C9_2_gp, //52-39 C9 2
C9_new_gp, //53-40 C9 New
temperature_gp, //54-41 Temperature
Aurora2 //55-42 Aurora 2
Aurora2_gp, //55-42 Aurora 2
retro_clown_gp, //56-43 Retro Clown
candy_gp, //57-44 Candy
toxy_reaf_gp, //58-45 Toxy Reaf
fairy_reaf_gp, //59-46 Fairy Reaf
semi_blue_gp, //60-47 Semi Blue
pink_candy_gp, //61-48 Pink Candy
red_reaf_gp, //62-49 Red Reaf
aqua_flash_gp, //63-50 Aqua Flash
yelblu_hot_gp, //64-51 Yelblu Hot
lite_light_gp, //65-52 Lite Light
red_flash_gp, //66-53 Red Flash
blink_red_gp, //67-54 Blink Red
red_shift_gp, //68-55 Red Shift
red_tide_gp, //69-56 Red Tide
candy2_gp //70-57 Candy2
};
#endif

View File

@@ -1,35 +1,110 @@
#include "pin_manager.h"
#include "wled.h"
void PinManagerClass::deallocatePin(byte gpio)
static void DebugPrintOwnerTag(PinOwner tag)
{
if (!isPinOk(gpio, false)) return;
uint32_t q = static_cast<uint8_t>(tag);
if (q) {
DEBUG_PRINTF("0x%02x (%d)", q, q);
} else {
DEBUG_PRINT(F("(no owner)"));
}
}
/// Actual allocation/deallocation routines
bool PinManagerClass::deallocatePin(byte gpio, PinOwner tag)
{
if (gpio == 0xFF) return true; // explicitly allow clients to free -1 as a no-op
if (!isPinOk(gpio, false)) return false; // but return false for any other invalid pin
// if a non-zero ownerTag, only allow de-allocation if the owner's tag is provided
if ((ownerTag[gpio] != PinOwner::None) && (ownerTag[gpio] != tag)) {
DEBUG_PRINT(F("PIN DEALLOC: IO "));
DEBUG_PRINT(gpio);
DEBUG_PRINT(F(" allocated by "));
DebugPrintOwnerTag(ownerTag[gpio]);
DEBUG_PRINT(F(", but attempted de-allocation by "));
DebugPrintOwnerTag(tag);
return false;
}
byte by = gpio >> 3;
byte bi = gpio - 8*by;
bitWrite(pinAlloc[by], bi, false);
ownerTag[gpio] = PinOwner::None;
return true;
}
bool PinManagerClass::allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag)
{
bool shouldFail = false;
// first verify the pins are OK and not already allocated
for (int i = 0; i < arrayElementCount; i++) {
byte gpio = mptArray[i].pin;
if (gpio == 0xFF) {
// explicit support for io -1 as a no-op (no allocation of pin),
// as this can greatly simplify configuration arrays
continue;
}
if (!isPinOk(gpio, mptArray[i].isOutput)) {
DEBUG_PRINT(F("PIN ALLOC: Invalid pin attempted to be allocated: "));
DEBUG_PRINT(gpio);
DEBUG_PRINTLN(F(""));
shouldFail = true;
}
if (isPinAllocated(gpio)) {
DEBUG_PRINT(F("PIN ALLOC: FAIL: IO "));
DEBUG_PRINT(gpio);
DEBUG_PRINT(F(" already allocated by "));
DebugPrintOwnerTag(ownerTag[gpio]);
DEBUG_PRINTLN(F(""));
shouldFail = true;
}
}
if (shouldFail) {
return false;
}
bool PinManagerClass::allocatePin(byte gpio, bool output)
// all pins are available .. track each one
for (int i = 0; i < arrayElementCount; i++) {
byte gpio = mptArray[i].pin;
if (gpio == 0xFF) {
// allow callers to include -1 value as non-requested pin
// as this can greatly simplify configuration arrays
continue;
}
byte by = gpio >> 3;
byte bi = gpio - 8*by;
bitWrite(pinAlloc[by], bi, true);
ownerTag[gpio] = tag;
}
return true;
}
bool PinManagerClass::allocatePin(byte gpio, bool output, PinOwner tag)
{
if (!isPinOk(gpio, output)) return false;
if (isPinAllocated(gpio)) {
DEBUG_PRINT(F("Attempted duplicate allocation of pin "));
DEBUG_PRINTLN(gpio);
DEBUG_PRINT(F("PIN ALLOC: Pin "));
DEBUG_PRINT(gpio);
DEBUG_PRINT(F(" already allocated by "));
DebugPrintOwnerTag(ownerTag[gpio]);
DEBUG_PRINTLN(F(""));
return false;
}
byte by = gpio >> 3;
byte bi = gpio - 8*by;
bitWrite(pinAlloc[by], bi, true);
ownerTag[gpio] = tag;
return true;
}
bool PinManagerClass::isPinAllocated(byte gpio)
// if tag is set to PinOwner::None, checks for ANY owner of the pin.
// if tag is set to any other value, checks if that tag is the current owner of the pin.
bool PinManagerClass::isPinAllocated(byte gpio, PinOwner tag)
{
if (!isPinOk(gpio, false)) return true;
if ((tag != PinOwner::None) && (ownerTag[gpio] != tag)) return false;
byte by = gpio >> 3;
byte bi = gpio - 8*by;
return bitRead(pinAlloc[by], bi);
@@ -46,7 +121,7 @@ bool PinManagerClass::isPinOk(byte gpio, bool output)
if (gpio < 34) return true;
if (gpio < 40 && !output) return true; //34-39 input only
#endif
return false;
}
@@ -88,4 +163,4 @@ void PinManagerClass::deallocateLedc(byte pos, byte channels)
}
#endif
PinManagerClass pinManager = PinManagerClass();
PinManagerClass pinManager = PinManagerClass();

View File

@@ -4,21 +4,93 @@
* Registers pins so there is no attempt for two interfaces to use the same pin
*/
#include <Arduino.h>
#include "const.h" // for USERMOD_* values
typedef struct PinManagerPinType {
int8_t pin;
uint8_t isOutput;
} managed_pin_type;
/*
* Allows PinManager to "lock" an allocation to a specific
* owner, so someone else doesn't accidentally de-allocate
* a pin it hasn't allocated. Also enhances debugging.
*
* RAM Cost:
* 17 bytes on ESP8266
* 40 bytes on ESP32
*/
enum struct PinOwner : uint8_t {
None = 0, // default == legacy == unspecified owner
// High bit is set for all built-in pin owners
// StatusLED -- THIS SHOULD NEVER BE ALLOCATED -- see handleStatusLED()
Ethernet = 0x81,
BusDigital = 0x82,
BusDigital2 = 0x83,
BusPwm = 0x84, // 'BusP' == PWM output using BusPwm
Button = 0x85, // 'Butn' == button from configuration
IR = 0x86, // 'IR' == IR receiver pin from configuration
Relay = 0x87, // 'Rly' == Relay pin from configuration
SPI_RAM = 0x88, // 'SpiR' == SPI RAM
DebugOut = 0x89, // 'Dbg' == debug output always IO1
DMX = 0x8A, // 'DMX' == hard-coded to IO2
// 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
UM_PIR = USERMOD_ID_PIRSWITCH, // 0x05 // Usermod "usermod_PIR_sensor_switch.h"
// #define USERMOD_ID_IMU // 0x06 // Usermod "usermod_mpu6050_imu.h" -- Uses "standard" I2C pins ... TODO -- enable shared I2C bus use
UM_FourLineDisplay = USERMOD_ID_FOUR_LINE_DISP, // 0x07 // Usermod "usermod_v2_four_line_display.h
UM_RotaryEncoderUI = USERMOD_ID_ROTARY_ENC_UI, // 0x08 // Usermod "usermod_v2_rotary_encoder_ui.h"
// #define USERMOD_ID_AUTO_SAVE // 0x09 // Usermod "usermod_v2_auto_save.h" -- Does not allocate pins
// #define USERMOD_ID_DHT // 0x0A // Usermod "usermod_dht.h" -- Statically allocates pins, not compatible with pinManager?
// #define USERMOD_ID_MODE_SORT // 0x0B // Usermod "usermod_v2_mode_sort.h" -- Does not allocate pins
// #define USERMOD_ID_VL53L0X // 0x0C // Usermod "usermod_vl53l0x_gestures.h" -- Uses "standard" I2C pins ... TODO -- enable shared I2C bus use
UM_MultiRelay = USERMOD_ID_MULTI_RELAY, // 0x0D // Usermod "usermod_multi_relay.h"
UM_AnimatedStaircase = USERMOD_ID_ANIMATED_STAIRCASE, // 0x0E // Usermod "Animated_Staircase.h"
// #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
};
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");
class PinManagerClass {
private:
#ifdef ESP8266
uint8_t pinAlloc[3] = {0x00, 0x00, 0x00}; //24bit, 1 bit per pin, we use first 17bits
PinOwner ownerTag[17] = { PinOwner::None };
#else
uint8_t pinAlloc[5] = {0x00, 0x00, 0x00, 0x00, 0x00}; //40bit, 1 bit per pin, we use all bits
uint8_t ledcAlloc[2] = {0x00, 0x00}; //16 LEDC channels
PinOwner ownerTag[40] = { PinOwner::None };
#endif
public:
void deallocatePin(byte gpio);
bool allocatePin(byte gpio, bool output = true);
bool isPinAllocated(byte gpio);
// De-allocates a single pin
bool deallocatePin(byte gpio, PinOwner tag);
// Allocates a single pin, with an owner tag.
// De-allocation requires the same owner tag (or override)
bool allocatePin(byte gpio, bool output, PinOwner tag);
// Allocates all the pins, or allocates none of the pins, with owner tag.
// Provided to simplify error condition handling in clients
// using more than one pin, such as I2C, SPI, rotary encoders,
// ethernet, etc..
bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag );
#if !defined(ESP8266) // ESP8266 compiler doesn't understand deprecated attribute
[[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]]
#endif
inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); }
#if !defined(ESP8266) // ESP8266 compiler doesn't understand deprecated attribute
[[deprecated("Replaced by three-parameter deallocatePin(gpio, output, ownerTag), for improved debugging")]]
#endif
inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); }
bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None);
bool isPinOk(byte gpio, bool output = true);
#ifdef ARDUINO_ARCH_ESP32
byte allocateLedc(byte channels);
void deallocateLedc(byte pos, byte channels);
@@ -26,4 +98,4 @@ class PinManagerClass {
};
extern PinManagerClass pinManager;
#endif
#endif

View File

@@ -54,6 +54,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
#ifdef WLED_USE_ETHERNET
ethernetType = request->arg(F("ETH")).toInt();
WLED::instance().initEthernet();
#endif
char k[3]; k[2] = 0;
@@ -77,17 +78,25 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
{
int t = 0;
if (rlyPin>=0 && pinManager.isPinAllocated(rlyPin)) pinManager.deallocatePin(rlyPin);
if (irPin>=0 && pinManager.isPinAllocated(irPin)) pinManager.deallocatePin(irPin);
for (uint8_t s=0; s<WLED_MAX_BUTTONS; s++)
if (btnPin[s]>=0 && pinManager.isPinAllocated(btnPin[s]))
pinManager.deallocatePin(btnPin[s]);
if (rlyPin>=0 && pinManager.isPinAllocated(rlyPin, PinOwner::Relay)) {
pinManager.deallocatePin(rlyPin, PinOwner::Relay);
}
if (irPin>=0 && pinManager.isPinAllocated(irPin, PinOwner::IR)) {
pinManager.deallocatePin(irPin, PinOwner::IR);
}
for (uint8_t s=0; s<WLED_MAX_BUTTONS; s++) {
if (btnPin[s]>=0 && pinManager.isPinAllocated(btnPin[s], PinOwner::Button)) {
pinManager.deallocatePin(btnPin[s], PinOwner::Button);
}
}
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"));
for (uint8_t s = 0; s < WLED_MAX_BUSSES; s++) {
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
@@ -127,7 +136,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// upate other pins
int hw_ir_pin = request->arg(F("IR")).toInt();
if (pinManager.allocatePin(hw_ir_pin,false)) {
if (pinManager.allocatePin(hw_ir_pin,false, PinOwner::IR)) {
irPin = hw_ir_pin;
} else {
irPin = -1;
@@ -135,7 +144,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
irEnabled = request->arg(F("IT")).toInt();
int hw_rly_pin = request->arg(F("RL")).toInt();
if (pinManager.allocatePin(hw_rly_pin,true)) {
if (pinManager.allocatePin(hw_rly_pin,true, PinOwner::Relay)) {
rlyPin = hw_rly_pin;
} else {
rlyPin = -1;
@@ -146,7 +155,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char bt[4] = "BT"; bt[2] = 48+i; bt[3] = 0; // button pin
char be[4] = "BE"; be[2] = 48+i; be[3] = 0; // button type
int hw_btn_pin = request->arg(bt).toInt();
if (pinManager.allocatePin(hw_btn_pin,false)) {
if (pinManager.allocatePin(hw_btn_pin,false,PinOwner::Button)) {
btnPin[i] = hw_btn_pin;
pinMode(btnPin[i], INPUT_PULLUP);
buttonType[i] = request->arg(be).toInt();
@@ -202,6 +211,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (t > 0) udpPort = t;
t = request->arg(F("U2")).toInt();
if (t > 0) udpPort2 = t;
syncGroups = request->arg(F("GS")).toInt();
receiveGroups = request->arg(F("GR")).toInt();
receiveNotificationBrightness = request->hasArg(F("RB"));
receiveNotificationColor = request->hasArg(F("RC"));
receiveNotificationEffects = request->hasArg(F("RX"));
@@ -312,7 +325,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
analogClockSecondsTrail = request->hasArg(F("OS"));
#ifndef WLED_DISABLE_CRONIXIE
strcpy(cronixieDisplay,request->arg(F("CX")).c_str());
strlcpy(cronixieDisplay,request->arg(F("CX")).c_str(),7);
cronixieBacklight = request->hasArg(F("CB"));
#endif
countdownMode = request->hasArg(F("CE"));
@@ -717,7 +730,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
}
//set effect parameters
if (updateVal(&req, "FX=", &effectCurrent, 0, strip.getModeCount()-1)) unloadPlaylist();
if (updateVal(&req, "FX=", &effectCurrent, 0, strip.getModeCount()-1) && request != nullptr) unloadPlaylist(); //unload playlist if changing FX using web request
updateVal(&req, "SX=", &effectSpeed);
updateVal(&req, "IX=", &effectIntensity);
updateVal(&req, "FP=", &effectPalette, 0, strip.getPaletteCount()-1);
@@ -879,7 +892,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
WS2812FX::Segment& seg = strip.getSegment(i);
if (!seg.isSelected()) continue;
if (effectCurrent != prevEffect) {
seg.mode = effectCurrent;
strip.setMode(i, effectCurrent);
effectChanged = true;
}
if (effectSpeed != prevSpeed) {

View File

@@ -2,14 +2,18 @@
IPAddress NetworkClass::localIP()
{
IPAddress localIP;
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
if (ETH.localIP()[0] != 0) {
return ETH.localIP();
localIP = ETH.localIP();
if (localIP[0] != 0) {
return localIP;
}
#endif
if (WiFi.localIP()[0] != 0) {
return WiFi.localIP();
localIP = WiFi.localIP();
if (localIP[0] != 0) {
return localIP;
}
return INADDR_NONE;
}

View File

@@ -4,13 +4,14 @@
* UDP sync notifier / Realtime / Hyperion / TPM2.NET
*/
#define WLEDPACKETSIZE 36
#define WLEDPACKETSIZE 37
#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
void notify(byte callMode, bool followUp)
{
if (!udpConnected) return;
if (!syncGroups) return;
switch (callMode)
{
case CALL_MODE_INIT: return;
@@ -39,7 +40,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
udpOut[11] = 8;
//9: supports sync groups, 37 byte packet
udpOut[11] = 9;
udpOut[12] = colSec[0];
udpOut[13] = colSec[1];
udpOut[14] = colSec[2];
@@ -72,6 +74,9 @@ void notify(byte callMode, bool followUp)
uint16_t ms = tm.ms;
udpOut[34] = (ms >> 8) & 0xFF;
udpOut[35] = (ms >> 0) & 0xFF;
//sync groups
udpOut[36] = syncGroups;
IPAddress broadcastIp;
broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP());
@@ -115,6 +120,8 @@ void sendTPM2Ack() {
void handleNotifications()
{
IPAddress localIP;
//send second notification if enabled
if(udpConnected && notificationTwoRequired && millis()-notificationSentTime > 250){
notify(notificationSentCallMode,true);
@@ -161,7 +168,6 @@ void handleNotifications()
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;
}
strip.show();
@@ -171,9 +177,10 @@ void handleNotifications()
if (!(receiveNotifications || receiveDirect)) return;
localIP = Network.localIP();
//notifier and UDP realtime
if (!packetSize || packetSize > UDP_IN_MAXSIZE) return;
if (!isSupp && notifierUdp.remoteIP() == Network.localIP()) return; //don't process broadcasts we send ourselves
if (!isSupp && notifierUdp.remoteIP() == localIP) return; //don't process broadcasts we send ourselves
uint8_t udpIn[packetSize +1];
uint16_t len;
@@ -182,7 +189,7 @@ void handleNotifications()
// WLED nodes info notifications
if (isSupp && udpIn[0] == 255 && udpIn[1] == 1 && len >= 40) {
if (!nodeListEnabled || notifier2Udp.remoteIP() == Network.localIP()) return;
if (!nodeListEnabled || notifier2Udp.remoteIP() == localIP) return;
uint8_t unit = udpIn[39];
NodesMap::iterator it = Nodes.find(unit);
@@ -220,6 +227,12 @@ void handleNotifications()
//compatibilityVersionByte:
byte version = udpIn[11];
// if we are not part of any sync group ignore message
if (version < 9 || version > 199) {
// legacy senders are treated as if sending in sync group 1 only
if (!(receiveGroups & 0x01)) return;
} else if (!(receiveGroups & udpIn[36])) return;
bool someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects);
//apply colors from notification
@@ -264,7 +277,7 @@ void handleNotifications()
}
//adjust system time, but only if sender is more accurate than self
if (version > 7)
if (version > 7 && version < 200)
{
Toki::Time tm;
tm.sec = (udpIn[30] << 24) | (udpIn[31] << 16) | (udpIn[32] << 8) | (udpIn[33]);
@@ -385,7 +398,7 @@ void handleNotifications()
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 >= ledCount) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
}
@@ -394,7 +407,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 >= ledCount) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++;
}

View File

@@ -11,6 +11,10 @@
*/
//#include "../usermods/EXAMPLE_v2/usermod_v2_example.h"
#ifdef USERMOD_BATTERY_STATUS_BASIC
#include "../usermods/battery_status_basic/usermod_v2_battery_status_basic.h"
#endif
#ifdef USERMOD_DALLASTEMPERATURE
#include "../usermods/Temperature/usermod_temperature.h"
#endif
@@ -91,6 +95,10 @@ void registerUsermods()
*/
//usermods.add(new MyExampleUsermod());
#ifdef USERMOD_BATTERY_STATUS_BASIC
usermods.add(new UsermodBatteryBasic());
#endif
#ifdef USERMOD_DALLASTEMPERATURE
usermods.add(new UsermodTemperature());
#endif

View File

@@ -105,6 +105,11 @@ void WiFiEvent(WiFiEvent_t event)
break;
case SYSTEM_EVENT_ETH_DISCONNECTED:
DEBUG_PRINT(F("ETH Disconnected"));
// This doesn't really affect ethernet per se,
// as it's only configured once. Rather, it
// may be necessary to reconnect the WiFi when
// ethernet disconnects, as a way to provide
// alternative access to the device.
forceReconnect = true;
break;
#endif
@@ -275,20 +280,21 @@ void WLED::setup()
registerUsermods();
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
if (psramFound()) {
pinManager.allocatePin(16); // GPIO16 reserved for SPI RAM
pinManager.allocatePin(17); // GPIO17 reserved for SPI RAM
}
if (psramFound()) {
// GPIO16/GPIO17 reserved for SPI RAM
managed_pin_type pins[2] = { {16, true}, {17, true} };
pinManager.allocateMultiplePins(pins, 2, PinOwner::SPI_RAM);
}
#endif
//DEBUG_PRINT(F("LEDs inited. heap usage ~"));
//DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap());
#ifdef WLED_DEBUG
pinManager.allocatePin(1,true); // GPIO1 reserved for debug output
pinManager.allocatePin(1, true, PinOwner::DebugOut); // GPIO1 reserved for debug output
#endif
#ifdef WLED_USE_DMX //reserve GPIO2 as hardcoded DMX pin
pinManager.allocatePin(2);
pinManager.allocatePin(2, true, PinOwner::DMX);
#endif
for (uint8_t i=1; i<WLED_MAX_BUTTONS; i++) btnPin[i] = -1;
@@ -310,7 +316,11 @@ void WLED::setup()
deserializeConfigFromFS();
#if STATUSLED
if (!pinManager.isPinAllocated(STATUSLED)) pinMode(STATUSLED, OUTPUT);
if (!pinManager.isPinAllocated(STATUSLED)) {
// NOTE: Special case: The status LED should *NOT* be allocated.
// See comments in handleStatusLed().
pinMode(STATUSLED, OUTPUT);
}
#endif
DEBUG_PRINTLN(F("Initializing strip"));
@@ -436,72 +446,97 @@ void WLED::initAP(bool resetAP)
apActive = true;
}
bool WLED::initEthernet()
{
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
static bool successfullyConfiguredEthernet = false;
if (successfullyConfiguredEthernet) {
// DEBUG_PRINTLN(F("initE: ETH already successfully configured, ignoring"));
return false;
}
if (ethernetType == WLED_ETH_NONE) {
return false;
}
if (ethernetType >= WLED_NUM_ETH_TYPES) {
DEBUG_PRINT(F("initE: Ignoring attempt for invalid ethernetType ")); DEBUG_PRINTLN(ethernetType);
return false;
}
DEBUG_PRINT(F("initE: Attempting ETH config: ")); DEBUG_PRINTLN(ethernetType);
// Ethernet initialization should only succeed once -- else reboot required
ethernet_settings es = ethernetBoards[ethernetType];
managed_pin_type pinsToAllocate[10] = {
// first six pins are non-configurable
esp32_nonconfigurable_ethernet_pins[0],
esp32_nonconfigurable_ethernet_pins[1],
esp32_nonconfigurable_ethernet_pins[2],
esp32_nonconfigurable_ethernet_pins[3],
esp32_nonconfigurable_ethernet_pins[4],
esp32_nonconfigurable_ethernet_pins[5],
{ (int8_t)es.eth_mdc, true }, // [6] = MDC is output and mandatory
{ (int8_t)es.eth_mdio, true }, // [7] = MDIO is bidirectional and mandatory
{ (int8_t)es.eth_power, true }, // [8] = optional pin, not all boards use
{ ((int8_t)0xFE), false }, // [9] = replaced with eth_clk_mode, mandatory
};
// update the clock pin....
if (es.eth_clk_mode == ETH_CLOCK_GPIO0_IN) {
pinsToAllocate[9].pin = 0;
pinsToAllocate[9].isOutput = false;
} else if (es.eth_clk_mode == ETH_CLOCK_GPIO0_OUT) {
pinsToAllocate[9].pin = 0;
pinsToAllocate[9].isOutput = true;
} else if (es.eth_clk_mode == ETH_CLOCK_GPIO16_OUT) {
pinsToAllocate[9].pin = 16;
pinsToAllocate[9].isOutput = true;
} else if (es.eth_clk_mode == ETH_CLOCK_GPIO17_OUT) {
pinsToAllocate[9].pin = 17;
pinsToAllocate[9].isOutput = true;
} else {
DEBUG_PRINT(F("initE: Failing due to invalid eth_clk_mode ("));
DEBUG_PRINT(es.eth_clk_mode);
DEBUG_PRINTLN(F(")"));
return false;
}
if (!pinManager.allocateMultiplePins(pinsToAllocate, 10, PinOwner::Ethernet)) {
DEBUG_PRINTLN(F("initE: Failed to allocate ethernet pins"));
return false;
}
if (!ETH.begin(
(uint8_t) es.eth_address,
(int) es.eth_power,
(int) es.eth_mdc,
(int) es.eth_mdio,
(eth_phy_type_t) es.eth_type,
(eth_clock_mode_t) es.eth_clk_mode
)) {
DEBUG_PRINTLN(F("initC: ETH.begin() failed"));
// de-allocate the allocated pins
for (managed_pin_type mpt : pinsToAllocate) {
pinManager.deallocatePin(mpt.pin, PinOwner::Ethernet);
}
return false;
}
successfullyConfiguredEthernet = true;
DEBUG_PRINTLN(F("initC: *** Ethernet successfully configured! ***"));
return true;
#else
return false; // Ethernet not enabled for build
#endif
}
void WLED::initConnection()
{
#ifdef WLED_ENABLE_WEBSOCKETS
ws.onEvent(wsEvent);
#endif
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
// Only initialize ethernet board if not NONE
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
ethernet_settings es = ethernetBoards[ethernetType];
// Use PinManager to ensure pins are available for
// ethernet AND to prevent other uses of these pins.
bool s = true;
byte pinsAllocated[4] { 255, 255, 255, 255 };
if (s && (s = pinManager.allocatePin((byte)es.eth_power))) {
pinsAllocated[0] = (byte)es.eth_power;
}
if (s && (s = pinManager.allocatePin((byte)es.eth_mdc))) {
pinsAllocated[1] = (byte)es.eth_mdc;
}
if (s && (s = pinManager.allocatePin((byte)es.eth_mdio))) {
pinsAllocated[2] = (byte)es.eth_mdio;
}
switch(es.eth_clk_mode) {
case ETH_CLOCK_GPIO0_IN:
s = pinManager.allocatePin(0, false);
pinsAllocated[3] = 0;
break;
case ETH_CLOCK_GPIO0_OUT:
s = pinManager.allocatePin(0);
pinsAllocated[3] = 0;
break;
case ETH_CLOCK_GPIO16_OUT:
s = pinManager.allocatePin(16);
pinsAllocated[3] = 16;
break;
case ETH_CLOCK_GPIO17_OUT:
s = pinManager.allocatePin(17);
pinsAllocated[3] = 17;
break;
default:
s = false;
break;
}
if (s) {
s = ETH.begin(
(uint8_t) es.eth_address,
(int) es.eth_power,
(int) es.eth_mdc,
(int) es.eth_mdio,
(eth_phy_type_t) es.eth_type,
(eth_clock_mode_t) es.eth_clk_mode
);
}
if (!s) {
DEBUG_PRINTLN(F("Ethernet init failed"));
// de-allocate only those pins allocated before the failure
for (byte p : pinsAllocated) {
pinManager.deallocatePin(p);
}
}
}
#endif
WiFi.disconnect(true); // close old connections
#ifdef ESP8266
@@ -509,7 +544,7 @@ void WLED::initConnection()
#endif
if (staticIP[0] != 0 && staticGateway[0] != 0) {
WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8));
WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(1, 1, 1, 1));
} else {
WiFi.config(0U, 0U, 0U);
}
@@ -556,13 +591,14 @@ void WLED::initConnection()
void WLED::initInterfaces()
{
IPAddress ipAddress = Network.localIP();
DEBUG_PRINTLN(F("Init STA interfaces"));
#ifndef WLED_DISABLE_HUESYNC
if (hueIP[0] == 0) {
hueIP[0] = Network.localIP()[0];
hueIP[1] = Network.localIP()[1];
hueIP[2] = Network.localIP()[2];
hueIP[0] = ipAddress[0];
hueIP[1] = ipAddress[1];
hueIP[2] = ipAddress[2];
}
#endif
@@ -692,14 +728,25 @@ void WLED::handleConnection()
}
}
// If status LED pin is allocated for other uses, does nothing
// else blink at 1Hz when WLED_CONNECTED is false (no WiFi, ?? no Ethernet ??)
// else blink at 2Hz when MQTT is enabled but not connected
// else turn the status LED off
void WLED::handleStatusLED()
{
#if STATUSLED
if (pinManager.isPinAllocated(STATUSLED)) return; //lower priority if something else uses the same pin
static unsigned long ledStatusLastMillis = 0;
static unsigned short ledStatusType = 0; // current status type - corresponds to number of blinks per second
static bool ledStatusState = 0; // the current LED state
if (pinManager.isPinAllocated(STATUSLED)) {
return; //lower priority if something else uses the same pin
}
ledStatusType = WLED_CONNECTED ? 0 : 2;
if (mqttEnabled && ledStatusType != 2) // Wi-Fi takes presendence over MQTT
if (mqttEnabled && ledStatusType != 2) { // Wi-Fi takes precendence over MQTT
ledStatusType = WLED_MQTT_CONNECTED ? 0 : 4;
}
if (ledStatusType) {
if (millis() - ledStatusLastMillis >= (1000/ledStatusType)) {
ledStatusLastMillis = millis();
@@ -715,4 +762,4 @@ void WLED::handleStatusLED()
}
#endif
}
}

View File

@@ -3,12 +3,12 @@
/*
Main sketch, global variable declarations
@title WLED project sketch
@version 0.13.0-b2
@version 0.13.0-b3
@author Christian Schwinne
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2107100
#define VERSION 2109220
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -267,6 +267,11 @@ WLED_GLOBAL uint16_t ledCount _INIT(DEFAULT_LED_COUNT); // overcurrent prevent
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
//if true, a segment per bus will be created on boot and LED settings save
//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 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
WLED_GLOBAL byte briS _INIT(128); // default brightness
@@ -295,6 +300,8 @@ WLED_GLOBAL uint16_t udpPort _INIT(21324); // WLED notifier default port
WLED_GLOBAL uint16_t udpPort2 _INIT(65506); // WLED notifier supplemental port
WLED_GLOBAL uint16_t udpRgbPort _INIT(19446); // Hyperion port
WLED_GLOBAL uint8_t syncGroups _INIT(0x01); // sync groups this instance syncs (bit mapped)
WLED_GLOBAL uint8_t receiveGroups _INIT(0x01); // sync receive groups this instance belongs to (bit mapped)
WLED_GLOBAL bool receiveNotificationBrightness _INIT(true); // apply brightness from incoming notifications
WLED_GLOBAL bool receiveNotificationColor _INIT(true); // apply color
WLED_GLOBAL bool receiveNotificationEffects _INIT(true); // apply effects setup
@@ -591,12 +598,6 @@ WLED_GLOBAL bool doInitBusses _INIT(false);
// Usermod manager
WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager());
// Status LED
#if STATUSLED
WLED_GLOBAL unsigned long ledStatusLastMillis _INIT(0);
WLED_GLOBAL unsigned short ledStatusType _INIT(0); // current status type - corresponds to number of blinks per second
WLED_GLOBAL bool ledStatusState _INIT(0); // the current LED state
#endif
// enable additional debug output
#ifdef WLED_DEBUG
@@ -660,6 +661,7 @@ public:
void beginStrip();
void handleConnection();
bool initEthernet(); // result is informational
void initAP(bool resetAP = false);
void initConnection();
void initInterfaces();

View File

@@ -2,7 +2,32 @@
#define WLED_ETHERNET_H
#ifdef WLED_USE_ETHERNET
// settings for various ethernet boards
#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)
// eth_mdc is an output GPIO pin used to provide the clock for the management data
// eth_mdio is an input/output GPIO pin used to transfer management data
// eth_type is the physical ethernet module's type (ETH_PHY_LAN8720, ETH_PHY_TLK110)
// eth_clk_mode defines the GPIO pin and GPIO mode for the clock signal
// However, there are really only four configurable options on ESP32:
// ETH_CLOCK_GPIO0_IN == External oscillator, clock input via GPIO0
// ETH_CLOCK_GPIO0_OUT == ESP32 provides 50MHz clock output via GPIO0
// ETH_CLOCK_GPIO16_OUT == ESP32 provides 50MHz clock output via GPIO16
// ETH_CLOCK_GPIO17_OUT == ESP32 provides 50MHz clock output via GPIO17
typedef struct EthernetSettings {
uint8_t eth_address;
int eth_power;
@@ -18,18 +43,9 @@ ethernet_settings ethernetBoards[] = {
},
// WT32-EHT01
// Please note, from my testing only these pins work for LED outputs:
// IO2, IO4, IO12, IO14, IO15
// Pins IO34 through IO39 are input-only on ESP32, so
// the following exposed pins won't work to drive WLEDs
// IO35(*), IO36, IO39
// The following pins are also exposed via the headers,
// and likely can be used as general purpose IO:
// IO05(*), IO32 (CFG), IO33 (485_EN), IO33 (TXD)
//
// (*) silkscreen on board revision v1.2 may be wrong:
// IO5 silkscreen on v1.2 says IO35
// IO35 silkscreen on v1.2 says RXD
// (*) 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
{
1, // eth_address,
16, // eth_power,

View File

@@ -21,6 +21,8 @@ enum class AdaState {
void handleSerial()
{
if (pinManager.isPinAllocated(3)) return;
#ifdef WLED_ENABLE_ADALIGHT
static auto state = AdaState::Header_A;
static uint16_t count = 0;
@@ -32,13 +34,35 @@ void handleSerial()
while (Serial.available() > 0)
{
yield();
byte next = Serial.read();
byte next = Serial.peek();
switch (state) {
case AdaState::Header_A:
if (next == 'A') state = AdaState::Header_d;
else if (next == 0xC9) { //TPM2 start byte
state = AdaState::TPM2_Header_Type;
}
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;
}
//only send response if TX pin is unused for other purposes
if (verboseResponse && !pinManager.isPinAllocated(1)) {
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
JsonObject state = doc.createNestedObject("state");
serializeState(state);
JsonObject info = doc.createNestedObject("info");
serializeInfo(info);
serializeJson(doc, Serial);
}
}
break;
case AdaState::Header_d:
if (next == 'd') state = AdaState::Header_a;
@@ -98,6 +122,7 @@ void handleSerial()
}
break;
}
Serial.read(); //discard the byte
}
#endif
}

View File

@@ -15,6 +15,26 @@ bool isIp(String str) {
return true;
}
void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){
if (otaLock) {
if (final) request->send(500, "text/plain", F("Please unlock OTA in security settings!"));
return;
}
if(!index){
request->_tempFile = WLED_FS.open(filename, "w");
DEBUG_PRINT("Uploading ");
DEBUG_PRINTLN(filename);
if (filename == "/presets.json") presetsModifiedTime = toki.second();
}
if (len) {
request->_tempFile.write(data,len);
}
if(final){
request->_tempFile.close();
request->send(200, "text/plain", F("File Uploaded!"));
}
}
bool captivePortal(AsyncWebServerRequest *request)
{
if (ON_STA_FILTER(request)) return false; //only serve captive in AP mode
@@ -95,7 +115,12 @@ void initServer()
const String& url = request->url();
isConfig = url.indexOf("cfg") > -1;
if (!isConfig) {
fileDoc = &jsonBuffer;
#ifdef WLED_DEBUG
DEBUG_PRINTLN(F("Serialized HTTP"));
serializeJson(root,Serial);
DEBUG_PRINTLN();
#endif
fileDoc = &jsonBuffer; // used for applying presets (presets.cpp)
verboseResponse = deserializeState(root);
fileDoc = nullptr;
} else {
@@ -136,7 +161,12 @@ void initServer()
server.on("/teapot", HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 418, F("418. I'm a teapot."), F("(Tangible Embedded Advanced Project Of Twinkling)"), 254);
});
server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request) {},
[](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data,
size_t len, bool final) {handleUpload(request, filename, index, data, len, final);}
);
//if OTA is allowed
if (!otaLock){
#ifdef WLED_ENABLE_FS_EDITOR
@@ -197,15 +227,15 @@ void initServer()
}
#ifdef WLED_ENABLE_DMX
server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", PAGE_dmxmap , dmxProcessor);
});
#else
server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 501, "Not implemented", F("DMX support is not enabled in this build."), 254);
});
#endif
#ifdef WLED_ENABLE_DMX
server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", PAGE_dmxmap , dmxProcessor);
});
#else
server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 501, "Not implemented", F("DMX support is not enabled in this build."), 254);
});
#endif
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
if (captivePortal(request)) return;
serveIndexOrWelcome(request);
@@ -261,7 +291,13 @@ bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest* request)
void setStaticContentCacheHeaders(AsyncWebServerResponse *response)
{
#ifndef WLED_DEBUG
//this header name is misleading, "no-cache" will not disable cache,
//it just revalidates on every load using the "If-None-Match" header with the last ETag value
response->addHeader(F("Cache-Control"),"no-cache");
#else
response->addHeader(F("Cache-Control"),"no-store,max-age=0"); // prevent caching if debug build
#endif
response->addHeader(F("ETag"), String(VERSION));
}
@@ -275,7 +311,6 @@ void serveIndex(AsyncWebServerRequest* request)
response->addHeader(F("Content-Encoding"),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
}

View File

@@ -26,6 +26,12 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
//the whole message is in a single frame and we got all of its data (max. 1450byte)
if(info->opcode == WS_TEXT)
{
if (len > 0 && len < 10 && data[0] == 'p') {
//application layer ping/pong heartbeat.
//client-side socket layer ping packets are unresponded (investigate)
client->text(F("pong"));
return;
}
bool verboseResponse = false;
{ //scope JsonDocument so it releases its buffer
DynamicJsonDocument jsonBuffer(JSON_BUFFER_SIZE);

View File

@@ -322,6 +322,7 @@ void getSettingsJS(byte subPage, char* dest)
oappend(";");
sappend('v',SET_F("LC"),ledCount);
sappend('c',SET_F("MS"),autoSegments);
for (uint8_t s=0; s < busses.getNumBusses(); s++) {
Bus* bus = busses.getBus(s);
@@ -396,6 +397,9 @@ void getSettingsJS(byte subPage, char* dest)
{
sappend('v',SET_F("UP"),udpPort);
sappend('v',SET_F("U2"),udpPort2);
sappend('v',SET_F("GS"),syncGroups);
sappend('v',SET_F("GR"),receiveGroups);
sappend('c',SET_F("RB"),receiveNotificationBrightness);
sappend('c',SET_F("RC"),receiveNotificationColor);
sappend('c',SET_F("RX"),receiveNotificationEffects);