Compare commits

..

57 Commits

Author SHA1 Message Date
cschwinne
420f858d9b Release of WLED v0.13.2 2022-08-15 02:08:48 +02:00
Blaž Kristan
902c11d074 Merge pull request #2657 from poelzi/watchdog
Enable ESP watchdog by default
2022-07-22 09:26:46 +02:00
Blaž Kristan
38330b735c Merge pull request #2723 from albarlow/BME280-Enhancements
BME280 Usermod Enhancements
2022-07-21 18:42:19 +02:00
Blaz Kristan
bda3c4ab7a Minor optimisations. 2022-07-21 18:38:07 +02:00
Blaz Kristan
d8d01ac353 Merge branch 'BME280-Enhancements' of https://github.com/albarlow/WLED into BME280-Enhancements 2022-07-21 18:37:17 +02:00
dependabot[bot]
51d935f419 Bump terser from 4.8.0 to 4.8.1 (#2726)
Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* changed variable names to avoid conflicts with SparkFunDMX library

* board version checks

* minor changes to DMX

* fix brightness when no shutter DMX channel is set

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

adds new features and options for wizlights usermod

* Update wizlights.h

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

* Update wizlights.h

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

* Update wizlights.h

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

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

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

* added some sensors to publish

* typo

* added dependency

* mqtt si7021_* names + don't  retain

* timer to 60 s

* some changes to HA auto discovery

* added config entries (no function yet)

* renaming

* made configs work

* added getId()

* refactoring + wrong mqtt topics fixed

* retain HA auto discovery

* do not spam serial console on each sensor update

* added readme

* add update interval info

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

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

* - fix wording issue for "six"
-

* - add some more comments

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

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

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

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

* removed unnecessary code

* try/catch for parent WS

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

* Fix inclusion in usermods list

Co-authored-by: Christian Schwinne <dev.aircoookie@gmail.com>
2022-03-23 16:20:18 +01:00
cschwinne
a517f0df1d Add ESP32-ETHERNET-KIT-VE type 2022-03-20 22:12:18 +01:00
cschwinne
9c9854b6bf Fixed sunrise/set calculation 2022-03-19 19:27:32 +01:00
cschwinne
d280e16723 Fixed /json/cfg unable to set busses (fixes #2589) 2022-03-19 14:21:14 +01:00
cschwinne
b93a9cb8bc FIxed liveview 2022-03-18 14:24:10 +01:00
cschwinne
8601052179 Fixed liveview buffer over-write (fixes #2586 )
(with odd LED counts > 256)
2022-03-18 14:08:37 +01:00
cschwinne
eaa20ff4bf Add handleOverlayDraw() to example v2 usermod 2022-03-16 19:32:11 +01:00
Blaž Kristan
e4c6e4bc48 Merge pull request #2584 from tonyn0/master
DDP in AP mode.
2022-03-16 17:31:09 +01:00
Blaž Kristan
c52597205e Merge branch 'master' into master 2022-03-16 17:29:12 +01:00
tonyn0
c73033c0b4 udp.cpp update
added ap check for ddp in L657
2022-03-16 11:00:29 -05:00
cschwinne
522e752582 Add ability to skip up to 255 LEDs (#2342) 2022-03-16 01:45:07 +01:00
SpeakingOfBrad
854ed8cfa9 ABL milliamps no longer hardcoded to 850 at runtime (#2581) 2022-03-16 00:35:49 +01:00
SpeakingOfBrad
4642205768 Optionally set led strip color order at runtime. (#2582) 2022-03-16 00:17:49 +01:00
André Klitzing
40dbfbe092 Update IRremoteESP8266 to 2.8.2 (#2579)
This fixes some C3 issues
2022-03-16 00:16:49 +01:00
68 changed files with 5383 additions and 2531 deletions

View File

@@ -1,5 +1,36 @@
## WLED changelog
### WLED release 0.13.2
#### Build 2208140
- Version bump to v0.13.2 "Toki"
- Added option to receive live data on the main segment only (PR #2601)
- Enable ESP watchdog by default (PR #2657)
- Fixed race condition when saving bus config
- Better potentiometer filtering (PR #2693)
- More suitable DMX libraries (PR #2652)
- Fixed outgoing serial TPM2 message length (PR #2628)
- Fixed next universe overflow and Art-Net DMX start address (PR #2607)
- Fixed relative segment brightness (PR #2665)
### Builds between releases 0.13.1 and 0.13.2
#### Build 2203191
- Fixed sunrise/set calculation (once again)
#### Build 2203190
- Fixed `/json/cfg` unable to set busses (#2589)
- Fixed Peek with odd LED counts > 255 (#2586)
#### Build 2203160
- Version bump to v0.13.2-a0 "Toki"
- Add ability to skip up to 255 LEDs
- Dependency version bumps
### WLED release 0.13.1
#### Build 2203150

14
package-lock.json generated
View File

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

View File

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

View File

@@ -161,7 +161,7 @@ upload_speed = 115200
lib_compat_mode = strict
lib_deps =
fastled/FastLED @ 3.5.0
IRremoteESP8266 @ 2.8.1
IRremoteESP8266 @ 2.8.2
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.4
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
#TFT_eSPI
@@ -199,9 +199,9 @@ build_flags =
lib_deps =
${env.lib_deps}
#https://github.com/lorol/LITTLEFS.git
# ESPAsyncTCP @ 1.2.0
ESPAsyncTCP @ 1.2.2
ESPAsyncUDP
makuna/NeoPixelBus @ 2.6.7 # 2.6.5/2.6.6 and newer do not compile on ESP core < 3.0.0
makuna/NeoPixelBus @ 2.6.9
[esp32]
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip
@@ -221,7 +221,7 @@ default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
lib_deps =
${env.lib_deps}
https://github.com/lorol/LITTLEFS.git
makuna/NeoPixelBus @ 2.6.7
makuna/NeoPixelBus @ 2.6.9
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
[esp32s2]
@@ -234,7 +234,7 @@ build_flags = -g
lib_deps =
${env.lib_deps}
makuna/NeoPixelBus @ 2.6.7
makuna/NeoPixelBus @ 2.6.9
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
[esp32c3]
@@ -247,7 +247,7 @@ build_flags = -g
lib_deps =
${env.lib_deps}
makuna/NeoPixelBus @ 2.6.7
makuna/NeoPixelBus @ 2.6.9
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
# ------------------------------------------------------------------------------
@@ -481,6 +481,15 @@ board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags_esp8266} -D WLED_USE_IC_CCT -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
[env:MY9291]
board = esp01_1m
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -D USERMOD_MY9291
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
# travis test board configurations
# ------------------------------------------------------------------------------

View File

@@ -57,7 +57,10 @@ build_flags = ${common.build_flags_esp8266}
; -D DEFAULT_LED_COUNT=30
;
; set milliampere limit when using ESP pin to power leds
; -D ABL_MILLIAMPS_DEFAULT =850
; -D ABL_MILLIAMPS_DEFAULT=850
;
; enable IR by setting remote type
; -D IRTYPE=0 //0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote
; -D IRTYPE=0 ;0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote
;
; set default color order of your led strip
; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB

View File

@@ -8,7 +8,7 @@ aiofiles==0.6.0
# via platformio
ajsonrpc==1.1.0
# via platformio
bottle==0.12.19
bottle==0.12.20
# via platformio
certifi==2020.12.5
# via requests

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -46,6 +46,11 @@
#define MAX(a,b) ((a)>(b)?(a):(b))
#endif
//color mangling macros
#ifndef RGBW32
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
#endif
/* Not used in all effects yet */
#define WLED_FPS 42
#define FRAMETIME_FIXED (1000/WLED_FPS)
@@ -71,7 +76,6 @@
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS)
#define LED_SKIP_AMOUNT 1
#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
#define NUM_COLORS 3 /* number of colors per segment */
@@ -609,7 +613,7 @@ class WS2812FX {
_brightness = DEFAULT_BRIGHTNESS;
currentPalette = CRGBPalette16(CRGB::Black);
targetPalette = CloudColors_p;
ablMilliampsMax = 850;
ablMilliampsMax = ABL_MILLIAMPS_DEFAULT;
currentMilliamps = 0;
timebase = 0;
resetSegments();
@@ -638,12 +642,13 @@ class WS2812FX {
resetSegments(),
makeAutoSegments(bool forceReset = false),
fixInvalidSegments(),
setPixelColor(uint16_t n, uint32_t c),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
show(void),
setTargetFps(uint8_t fps),
deserializeMap(uint8_t n=0);
inline void setPixelColor(uint16_t n, uint32_t c) {setPixelColor(n, byte(c>>16), byte(c>>8), byte(c), byte(c>>24));}
bool
gammaCorrectBri = false,
gammaCorrectCol = true,
@@ -911,7 +916,6 @@ class WS2812FX {
friend class ColorTransition;
uint16_t
realPixelIndex(uint16_t i),
transitionProgress(uint8_t tNr);
public:

View File

@@ -60,6 +60,11 @@
#define DEFAULT_LED_TYPE TYPE_WS2812_RGB
#endif
#ifndef DEFAULT_LED_COLOR_ORDER
#define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB
#endif
#if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES
#error "Max segments must be at least max number of busses!"
#endif
@@ -77,6 +82,7 @@ void WS2812FX::finalizeInit(void)
//if busses failed to load, add default (fresh install, FS issue, ...)
if (busses.getNumBusses() == 0) {
DEBUG_PRINTLN(F("No busses, init default"));
const uint8_t defDataPins[] = {DATA_PINS};
const uint16_t defCounts[] = {PIXEL_COUNTS};
const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0]));
@@ -87,7 +93,7 @@ void WS2812FX::finalizeInit(void)
uint16_t start = prevLen;
uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
prevLen += count;
BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, COL_ORDER_GRB);
BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER);
busses.add(defCfg);
}
}
@@ -125,6 +131,8 @@ void WS2812FX::service() {
for(uint8_t i=0; i < MAX_NUM_SEGMENTS; i++)
{
//if (realtimeMode && useMainSegmentOnly && i == getMainSegmentId()) continue;
_segment_index = i;
// reset the segment runtime data if needed, called before isActive to ensure deleted
@@ -179,34 +187,11 @@ void WS2812FX::service() {
_triggered = false;
}
void IRAM_ATTR WS2812FX::setPixelColor(uint16_t n, uint32_t c) {
setPixelColor(n, R(c), G(c), B(c), W(c));
}
//used to map from segment index to physical pixel, taking into account grouping, offsets, reverse and mirroring
uint16_t IRAM_ATTR WS2812FX::realPixelIndex(uint16_t i) {
int16_t iGroup = i * SEGMENT.groupLength();
/* reverse just an individual segment */
int16_t realIndex = iGroup;
if (IS_REVERSE) {
if (IS_MIRROR) {
realIndex = (SEGMENT.length() - 1) / 2 - iGroup; //only need to index half the pixels
} else {
realIndex = (SEGMENT.length() - 1) - iGroup;
}
}
realIndex += SEGMENT.start;
return realIndex;
}
void IRAM_ATTR WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
{
if (SEGLEN) {//from segment
uint16_t realIndex = realPixelIndex(i);
uint16_t len = SEGMENT.length();
uint8_t segIdx;
if (SEGLEN) { // SEGLEN!=0 -> from segment/FX
//color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments)
if (_bri_t < 255) {
r = scale8(r, _bri_t);
@@ -214,30 +199,48 @@ void IRAM_ATTR WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte
b = scale8(b, _bri_t);
w = scale8(w, _bri_t);
}
segIdx = _segment_index;
} else // from live/realtime
segIdx = _mainSegment;
if (SEGLEN || (realtimeMode && useMainSegmentOnly)) {
uint32_t col = RGBW32(r, g, b, w);
uint16_t len = _segments[segIdx].length();
/* Set all the pixels in the group */
for (uint16_t j = 0; j < SEGMENT.grouping; j++) {
uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j);
if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) {
if (IS_MIRROR) { //set the corresponding mirrored pixel
uint16_t indexMir = SEGMENT.stop - indexSet + SEGMENT.start - 1;
/* offset/phase */
indexMir += SEGMENT.offset;
if (indexMir >= SEGMENT.stop) indexMir -= len;
// get physical pixel address (taking into account start, grouping, spacing [and offset])
i = i * _segments[segIdx].groupLength();
if (_segments[segIdx].options & REVERSE) { // is segment reversed?
if (_segments[segIdx].options & MIRROR) { // is segment mirrored?
i = (len - 1) / 2 - i; //only need to index half the pixels
} else {
i = (len - 1) - i;
}
}
i += _segments[segIdx].start;
// set all the pixels in the group
for (uint16_t j = 0; j < _segments[segIdx].grouping; j++) {
uint16_t indexSet = i + ((_segments[segIdx].options & REVERSE) ? -j : j);
if (indexSet >= _segments[segIdx].start && indexSet < _segments[segIdx].stop) {
if (_segments[segIdx].options & MIRROR) { //set the corresponding mirrored pixel
uint16_t indexMir = _segments[segIdx].stop - indexSet + _segments[segIdx].start - 1;
indexMir += _segments[segIdx].offset; // offset/phase
if (indexMir >= _segments[segIdx].stop) indexMir -= len;
if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir];
busses.setPixelColor(indexMir, col);
}
/* offset/phase */
indexSet += SEGMENT.offset;
if (indexSet >= SEGMENT.stop) indexSet -= len;
indexSet += _segments[segIdx].offset; // offset/phase
if (indexSet >= _segments[segIdx].stop) indexSet -= len;
if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet];
busses.setPixelColor(indexSet, col);
}
}
} else { //live data, etc.
} else {
if (i < customMappingSize) i = customMappingTable[i];
busses.setPixelColor(i, RGBW32(r, g, b, w));
}
@@ -502,7 +505,13 @@ uint8_t WS2812FX::getActiveSegmentsNum(void) {
uint32_t WS2812FX::getPixelColor(uint16_t i)
{
i = realPixelIndex(i);
// get physical pixel
i = i * SEGMENT.groupLength();;
if (IS_REVERSE) {
if (IS_MIRROR) i = (SEGMENT.length() - 1) / 2 - i; //only need to index half the pixels
else i = (SEGMENT.length() - 1) - i;
}
i += SEGMENT.start;
if (SEGLEN) {
/* offset/phase */
@@ -517,7 +526,7 @@ uint32_t WS2812FX::getPixelColor(uint16_t i)
}
WS2812FX::Segment& WS2812FX::getSegment(uint8_t id) {
if (id >= MAX_NUM_SEGMENTS) return _segments[0];
if (id >= MAX_NUM_SEGMENTS) return _segments[getMainSegmentId()];
return _segments[id];
}
@@ -1256,4 +1265,4 @@ WS2812FX* WS2812FX::instance = nullptr;
//Bus static member definition, would belong in bus_manager.cpp
int16_t Bus::_cct = -1;
uint8_t Bus::_cctBlend = 0;
uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL;
uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL;

View File

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

View File

@@ -131,21 +131,42 @@ void handleSwitch(uint8_t b)
}
}
#define ANALOG_BTN_READ_CYCLE 250 // min time between two analog reading cycles
#define STRIP_WAIT_TIME 6 // max wait time in case of strip.isUpdating()
#define POT_SMOOTHING 0.25f // smoothing factor for raw potentiometer readings
#define POT_SENSITIVITY 4 // changes below this amount are noise (POT scratching, or ADC noise)
void handleAnalog(uint8_t b)
{
static uint8_t oldRead[WLED_MAX_BUTTONS];
static uint8_t oldRead[WLED_MAX_BUTTONS] = {0};
static float filteredReading[WLED_MAX_BUTTONS] = {0.0f};
uint16_t rawReading; // raw value from analogRead, scaled to 12bit
#ifdef ESP8266
uint16_t aRead = analogRead(A0) >> 2; // convert 10bit read to 8bit
rawReading = analogRead(A0) << 2; // convert 10bit read to 12bit
#else
uint16_t aRead = analogRead(btnPin[b]) >> 4; // convert 12bit read to 8bit
rawReading = analogRead(btnPin[b]); // collect at full 12bit resolution
#endif
yield(); // keep WiFi task running - analog read may take several millis on ESP8266
filteredReading[b] += POT_SMOOTHING * ((float(rawReading) / 16.0f) - filteredReading[b]); // filter raw input, and scale to [0..255]
uint16_t aRead = max(min(int(filteredReading[b]), 255), 0); // squash into 8bit
if(aRead <= POT_SENSITIVITY) aRead = 0; // make sure that 0 and 255 are used
if(aRead >= 255-POT_SENSITIVITY) aRead = 255;
if (buttonType[b] == BTN_TYPE_ANALOG_INVERTED) aRead = 255 - aRead;
// remove noise & reduce frequency of UI updates
aRead &= 0xFC;
if (abs(int(aRead) - int(oldRead[b])) <= POT_SENSITIVITY) return; // no significant change in reading
// Unomment the next lines if you still see flickering related to potentiometer
// This waits until strip finishes updating (why: strip was not updating at the start of handleButton() but may have started during analogRead()?)
//unsigned long wait_started = millis();
//while(strip.isUpdating() && (millis() - wait_started < STRIP_WAIT_TIME)) {
// delay(1);
//}
//if (strip.isUpdating()) return; // give up
if (oldRead[b] == aRead) return; // no change in reading
oldRead[b] = aRead;
// if no macro for "short press" and "long press" is defined use brightness control
@@ -168,6 +189,7 @@ void handleAnalog(uint8_t b)
} else if (macroDoublePress[b] == 247) {
// selected palette
effectPalette = map(aRead, 0, 252, 0, strip.getPaletteCount()-1);
effectPalette = constrain(effectPalette, 0, strip.getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result
} else if (macroDoublePress[b] == 200) {
// primary color, hue, full saturation
colorHStoRGB(aRead*256,255,col);
@@ -196,6 +218,8 @@ void handleButton()
static unsigned long lastRead = 0UL;
bool analog = false;
if (strip.isUpdating()) return; // don't interfere with strip updates. Our button will still be there in 1ms (next cycle)
for (uint8_t b=0; b<WLED_MAX_BUTTONS; b++) {
#ifdef ESP8266
if ((btnPin[b]<0 && !(buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED)) || buttonType[b] == BTN_TYPE_NONE) continue;
@@ -205,7 +229,7 @@ void handleButton()
if (usermods.handleButton(b)) continue; // did usermod handle buttons
if ((buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) && millis() - lastRead > 250) { // button is not a button but a potentiometer
if ((buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) && millis() - lastRead > ANALOG_BTN_READ_CYCLE) { // button is not a button but a potentiometer
analog = true;
handleAnalog(b); continue;
}

View File

@@ -95,6 +95,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t s = 0; // bus iterator
if (fromFS) busses.removeAll(); // can't safely manipulate busses directly in network callback
uint32_t mem = 0;
bool busesChanged = false;
for (JsonObject elm : ins) {
if (s >= WLED_MAX_BUSSES) break;
uint8_t pins[5] = {255, 255, 255, 255, 255};
@@ -116,7 +117,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
bool reversed = elm["rev"];
bool refresh = elm["ref"] | false;
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
s++;
if (fromFS) {
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
mem += BusManager::memUsage(bc);
@@ -124,9 +124,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
} else {
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
doInitBusses = true;
busesChanged = true;
}
s++;
}
doInitBusses = busesChanged;
// finalization done in beginStrip()
}
if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus
@@ -291,6 +293,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject if_live = interfaces["live"];
CJSON(receiveDirect, if_live["en"]);
CJSON(useMainSegmentOnly, if_live[F("mso")]);
CJSON(e131Port, if_live["port"]); // 5568
if (e131Port == DDP_DEFAULT_PORT) e131Port = E131_DEFAULT_PORT; // prevent double DDP port allocation
CJSON(e131Multicast, if_live[F("mc")]);
@@ -455,7 +458,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
if (fromFS) return needsSave;
// if from /json/cfg
doReboot = doc[F("rb")] | doReboot;
if (doInitBusses) return false; // no save needed, will do after bus init in wled.cpp loop
return (doc["sv"] | true);
}
@@ -702,6 +707,7 @@ void serializeConfig() {
JsonObject if_live = interfaces.createNestedObject("live");
if_live["en"] = receiveDirect;
if_live[F("mso")] = useMainSegmentOnly;
if_live["port"] = e131Port;
if_live[F("mc")] = e131Multicast;

View File

@@ -72,6 +72,11 @@
#define USERMOD_ID_QUINLED_AN_PENTA 23 //Usermod "quinled-an-penta.h"
#define USERMOD_ID_SSDR 24 //Usermod "usermod_v2_seven_segment_display_reloaded.h"
#define USERMOD_ID_CRONIXIE 25 //Usermod "usermod_cronixie.h"
#define USERMOD_ID_WIZLIGHTS 26 //Usermod "wizlights.h"
#define USERMOD_ID_WORDCLOCK 27 //Usermod "usermod_v2_word_clock.h"
#define USERMOD_ID_MY9291 28 //Usermod "usermod_MY9291.h"
#define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h"
#define USERMOD_ID_BME280 30 //Usermod "usermod_bme280.h
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@@ -192,7 +197,7 @@
#define BTN_TYPE_ANALOG_INVERTED 8
//Ethernet board types
#define WLED_NUM_ETH_TYPES 7
#define WLED_NUM_ETH_TYPES 8
#define WLED_ETH_NONE 0
#define WLED_ETH_WT32_ETH01 1
@@ -292,7 +297,13 @@
#endif
#endif
#define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit
#ifndef ABL_MILLIAMPS_DEFAULT
#define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit
#else
#if ABL_MILLIAMPS_DEFAULT < 250 // make sure value is at least 250
#define ABL_MILLIAMPS_DEFAULT 250
#endif
#endif
// PWM settings
#ifndef WLED_PWM_FREQ

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -864,6 +864,19 @@ input[type=number]::-webkit-outer-spin-button {
right: 8px;
}
.frz {
left: 36px;
position: absolute;
top: 0px;
cursor: pointer;
padding: 8px;
}
/* TODO expanded does not seem to apply to .frz, what is this for? */
.expanded .frz {
display: none;
}
.check, .radio {
display: inline-block;
position: relative;

View File

@@ -137,7 +137,7 @@
<div id="Effects" class="tabcontent">
<p class="labels">Effect speed</p>
<div class="staytop">
<i class="icons slider-icon" style="cursor: pointer;" onclick="tglFreeze()">&#xe325;</i>
<i class="icons slider-icon" style="cursor: pointer;" title="Freeze" onclick="tglFreeze()">&#xe325;</i>
<div class="sliderwrap il">
<input id="sliderSpeed" class="noslide" onchange="setSpeed()" oninput="updateTrail(this)" max="255" min="0" type="range" value="128" />
<output class="sliderbubble hidden"></output>

View File

@@ -566,6 +566,7 @@ function populateInfo(i)
function populateSegments(s)
{
var cn = "";
let li = lastinfo;
segCount = 0; lowestUnused = 0; lSeg = 0;
for (var y = 0; y < (s.seg||[]).length; y++)
@@ -584,6 +585,7 @@ function populateSegments(s)
<input type="checkbox" id="seg${i}sel" onchange="selSeg(${i})" ${inst.sel ? "checked":""}>
<span class="checkmark schk"></span>
</label>
<i class="icons e-icon frz" id="seg${i}frz" onclick="event.preventDefault();tglFreeze(${i});" style="display:${inst.frz?"inline":"none"}">&#x${li.live && li.liveseg==i?'e410':'e325'};</i>
<div class="segname">
<div class="segntxt" onclick="selSegEx(${i})">${inst.n ? inst.n : "Segment "+i}</div>
<i class="icons edit-icon ${expanded[i] ? "expanded":""}" id="seg${i}nedit" onclick="tglSegn(${i})">&#xe2c6;</i>
@@ -964,7 +966,7 @@ function updateUI()
function displayRover(i,s)
{
d.getElementById('rover').style.transform = (i.live && s.lor == 0) ? "translateY(0px)":"translateY(100%)";
d.getElementById('rover').style.transform = (i.live && s.lor == 0 && i.liveseg<0) ? "translateY(0px)":"translateY(100%)";
var sour = i.lip ? i.lip:""; if (sour.length > 2) sour = " from " + sour;
d.getElementById('lv').innerHTML = `WLED is receiving live ${i.lm} data${sour}`;
d.getElementById('roverstar').style.display = (i.live && s.lor) ? "block":"none";
@@ -1643,7 +1645,11 @@ function setSegBri(s){
function tglFreeze(s=null)
{
var obj = {"seg": {"frz": "t"}}; // toggle
if (s!==null) obj.id = s;
if (s!==null) {
obj.seg.id = s;
// if live segment, enter live override (which also unfreezes)
if (lastinfo && s==lastinfo.liveseg && lastinfo.live) obj = {"lor":1};
}
requestJson(obj);
}

View File

@@ -45,7 +45,10 @@
}
}
var ws = top.window.ws;
var ws;
try {
ws = top.window.ws;
} catch (e) {}
if (ws && ws.readyState === WebSocket.OPEN) {
console.info("Peek uses top WS");
ws.send("{'lv':true}");

View File

@@ -96,9 +96,11 @@
UI();
}
//returns mem usage
function getMem(t, len, p0) {
function getMem(t, n) {
let len = parseInt(d.getElementsByName("LC"+n)[0].value);
len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too
if (t < 32) {
if (maxM < 10000 && p0==3) { //8266 DMA uses 5x the mem
if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem
if (t > 29) return len*20; //RGBW
return len*15;
} else if (maxM >= 10000) //ESP32 RMT uses double buffer?
@@ -133,7 +135,7 @@
gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : "";
var LK = d.getElementsByName("L1"+n)[0]; // clock pin
memu += getMem(t, d.getElementsByName("LC"+n)[0].value, d.getElementsByName("L0"+n)[0].value); // calc memory
memu += getMem(t, n); // calc memory
// enumerate pins
for (p=1; p<5; p++) {
@@ -324,7 +326,7 @@ ${i+1}:
<span id="p3d${i}"></span><input type="number" name="L3${i}" min="0" max="33" class="xs" onchange="UI()"/>
<span id="p4d${i}"></span><input type="number" name="L4${i}" min="0" max="33" class="xs" onchange="UI()"/>
<div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div>
<div id="dig${i}s" style="display:inline"><br>Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}"></div>
<div id="dig${i}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${i}" min="0" max="255" value="0" oninput="UI()"></div>
<div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}"></div>
</div>`;
f.insertAdjacentHTML("beforeend", cn);
@@ -466,7 +468,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
d.getElementsByName("LS"+i)[0].value = v.start;
d.getElementsByName("LC"+i)[0].value = v.len;
d.getElementsByName("CO"+i)[0].value = v.order;
d.getElementsByName("SL"+i)[0].checked = v.skip;
d.getElementsByName("SL"+i)[0].value = v.skip;
d.getElementsByName("RF"+i)[0].checked = v.ref;
d.getElementsByName("CV"+i)[0].checked = v.rev;
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -206,6 +206,7 @@ bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte
void notify(byte callMode, bool followUp=false);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false);
void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC);
void exitRealtime();
void handleNotifications();
void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w);
void refreshNodeList();

View File

@@ -42,7 +42,7 @@ function B(){window.history.back()}function U(){document.getElementById("uf").st
.bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none}
</style></head><body><h2>WLED Software Update</h2><form method="POST"
action="/update" id="uf" enctype="multipart/form-data" onsubmit="U()">
Installed version: 0.13.1<br>Download the latest binary: <a
Installed version: 0.13.2<br>Download the latest binary: <a
href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img
src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square">
</a><br><input type="file" class="bt" name="update" required><br><input
@@ -85,7 +85,7 @@ charset="utf-8"><meta name="theme-color" content="#222222"><title>
WLED Live Preview</title><style>
body{margin:0}#canv{background:#000;filter:brightness(175%);width:100%;height:100%;position:absolute}
</style></head><body><div id="canv"><script>
function updatePreview(e){var n="linear-gradient(90deg,",o=e.length;for(i=2;i<o;i+=3)n+=`rgb(${e[i]},${e[i+1]},${e[i+2]})`,i<o-3&&(n+=",");n+=")",document.getElementById("canv").style.background=n}function getLiveJson(e){try{if("[object ArrayBuffer]"===toString.call(e.data)){let e=new Uint8Array(event.data);if(76!=e[0])return;updatePreview(e)}}catch(e){console.error("Peek WS error:",e)}}var ws=top.window.ws;ws&&ws.readyState===WebSocket.OPEN?(console.info("Peek uses top WS"),ws.send("{'lv':true}")):(console.info("Peek WS opening"),(ws=new WebSocket(("https:"==window.location.protocol?"wss":"ws")+"://"+document.location.host+"/ws")).onopen=function(){console.info("Peek WS open"),ws.send("{'lv':true}")}),ws.binaryType="arraybuffer",ws.addEventListener("message",getLiveJson)
function updatePreview(e){var n="linear-gradient(90deg,",t=e.length;for(i=2;i<t;i+=3)n+=`rgb(${e[i]},${e[i+1]},${e[i+2]})`,i<t-3&&(n+=",");n+=")",document.getElementById("canv").style.background=n}function getLiveJson(e){try{if("[object ArrayBuffer]"===toString.call(e.data)){let e=new Uint8Array(event.data);if(76!=e[0])return;updatePreview(e)}}catch(e){console.error("Peek WS error:",e)}}var ws;try{ws=top.window.ws}catch(e){}ws&&ws.readyState===WebSocket.OPEN?(console.info("Peek uses top WS"),ws.send("{'lv':true}")):(console.info("Peek WS opening"),(ws=new WebSocket(("https:"==window.location.protocol?"wss":"ws")+"://"+document.location.host+"/ws")).onopen=function(){console.info("Peek WS open"),ws.send("{'lv':true}")}),ws.binaryType="arraybuffer",ws.addEventListener("message",getLiveJson)
</script></body></html>)=====";

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -95,7 +95,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
if (stop > start && of > len -1) of = len -1;
strip.setSegment(id, start, stop, grp, spc, of);
byte segbri = 0;
byte segbri = seg.opacity;
if (getVal(elem["bri"], &segbri)) {
if (segbri > 0) seg.setOpacity(segbri, id);
seg.setOption(SEG_OPTION_ON, segbri, id);
@@ -228,8 +228,8 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
}
strip.setPixelSegment(oldSegId);
strip.trigger();
} else if (!elem["frz"] && iarr.isNull()) { //return to regular effect
seg.setOption(SEG_OPTION_FREEZE, false);
// } else if (!elem["frz"] && iarr.isNull()) { //return to regular effect
// seg.setOption(SEG_OPTION_FREEZE, false);
}
// send UDP if not in preset and something changed that is not just selection
//if (!presetId && (seg.differs(prev) & 0x7F)) stateChanged = true;
@@ -243,6 +243,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
{
bool stateResponse = root[F("v")] | false;
bool onBefore = bri;
getVal(root["bri"], &bri);
bool on = root["on"] | (bri > 0);
@@ -250,6 +251,15 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (root["on"].is<const char*>() && root["on"].as<const char*>()[0] == 't') toggleOnOff();
if (bri && !onBefore) { // unfreeze all segments when turning on
for (uint8_t s=0; s < strip.getMaxSegments(); s++) {
strip.getSegment(s).setOption(SEG_OPTION_FREEZE, false, s);
}
if (realtimeMode && !realtimeOverride && useMainSegmentOnly) { // keep live segment frozen if live
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, true, strip.getMainSegmentId());
}
}
int tr = -1;
if (!presetId || currentPlaylist < 0) { //do not apply transition time from preset if playlist active, as it would override playlist transition times
tr = root[F("transition")] | -1;
@@ -275,8 +285,8 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
JsonObject nl = root["nl"];
nightlightActive = nl["on"] | nightlightActive;
nightlightDelayMins = nl["dur"] | nightlightDelayMins;
nightlightMode = nl["mode"] | nightlightMode;
nightlightDelayMins = nl["dur"] | nightlightDelayMins;
nightlightMode = nl["mode"] | nightlightMode;
nightlightTargetBri = nl[F("tbri")] | nightlightTargetBri;
JsonObject udpn = root["udpn"];
@@ -292,22 +302,23 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
doReboot = root[F("rb")] | doReboot;
strip.setMainSegmentId(root[F("mainseg")] | strip.getMainSegmentId()); // must be before realtimeLock() if "live"
realtimeOverride = root[F("lor")] | realtimeOverride;
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
bool liveEnabled = false;
if (root.containsKey("live")) {
bool lv = root["live"];
if (lv) {
transitionDelayTemp = 0;
jsonTransitionOnce = true;
liveEnabled = true; // triggers realtimeLock() below
realtimeLock(65000);
}
else realtimeTimeout = 0; //cancel realtime mode immediately
if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, !realtimeOverride, strip.getMainSegmentId());
}
strip.setMainSegmentId(root[F("mainseg")] | strip.getMainSegmentId());
if (root.containsKey("live")) {
if (root["live"].as<bool>()) {
transitionDelayTemp = 0;
jsonTransitionOnce = true;
realtimeLock(65000);
} else {
exitRealtime();
}
}
int it = 0;
JsonVariant segVar = root["seg"];
@@ -381,7 +392,6 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
}
stateUpdated(callMode);
if (liveEnabled) realtimeTimeout = UINT32_MAX; // force indefinite timeout if this request contained {"live":true}
return stateResponse;
}
@@ -536,6 +546,8 @@ void serializeInfo(JsonObject root)
root[F("name")] = serverDescription;
root[F("udpport")] = udpPort;
root["live"] = (bool)realtimeMode;
root[F("liveseg")] = useMainSegmentOnly ? strip.getMainSegmentId() : -1; // if using main segment only for live
//root[F("mso")] = useMainSegmentOnly; // using main segment only for live
switch (realtimeMode) {
case REALTIME_MODE_INACTIVE: root["lm"] = ""; break;

View File

@@ -100,7 +100,6 @@ void stateUpdated(byte callMode) {
setValuesFromFirstSelectedSeg();
if (bri != briOld || stateChanged) {
if (realtimeTimeout == UINT32_MAX) realtimeTimeout = 0;
if (stateChanged) currentPreset = 0; //something changed, so we are no longer in the preset
if (callMode != CALL_MODE_NOTIFICATION && callMode != CALL_MODE_NO_NOTIFY) notify(callMode);

View File

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

View File

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

View File

@@ -90,6 +90,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
Bus::setAutoWhiteMode(strip.autoWhiteMode);
strip.setTargetFps(request->arg(F("FR")).toInt());
bool busesChanged = false;
for (uint8_t s = 0; s < WLED_MAX_BUSSES; s++) {
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
@@ -97,7 +98,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char lt[4] = "LT"; lt[2] = 48+s; lt[3] = 0; //strip type
char ls[4] = "LS"; ls[2] = 48+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = 48+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip 1st LED
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip first N LEDs
char rf[4] = "RF"; rf[2] = 48+s; rf[3] = 0; //refresh required
if (!request->hasArg(lp)) {
DEBUG_PRINTLN(F("No data.")); break;
@@ -109,7 +110,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
type = request->arg(lt).toInt();
type |= request->hasArg(rf) << 7; // off refresh override
skip = request->hasArg(sl) ? LED_SKIP_AMOUNT : 0;
skip = request->arg(sl).toInt();
colorOrder = request->arg(co).toInt();
start = (request->hasArg(ls)) ? request->arg(ls).toInt() : t;
if (request->hasArg(lc) && request->arg(lc).toInt() > 0) {
@@ -121,8 +122,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// actual finalization is done in WLED::loop() (removing old busses and adding new)
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder, request->hasArg(cv), skip);
doInitBusses = true;
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
ColorOrderMap com = {};
for (uint8_t s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) {
@@ -198,6 +200,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (t >= 0 && t < 4) strip.paletteBlend = t;
t = request->arg(F("BF")).toInt();
if (t > 0) briMultiplier = t;
doInitBusses = busesChanged;
}
//UI
@@ -237,6 +241,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
nodeBroadcastEnabled = request->hasArg(F("NB"));
receiveDirect = request->hasArg(F("RD"));
useMainSegmentOnly = request->hasArg(F("MO"));
e131SkipOutOfSequence = request->hasArg(F("ES"));
e131Multicast = request->hasArg(F("EM"));
t = request->arg(F("EP")).toInt();
@@ -933,6 +938,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (pos > 0) {
realtimeOverride = getNumVal(&req, pos);
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, !realtimeOverride, strip.getMainSegmentId());
}
}
pos = req.indexOf(F("RB"));

View File

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

View File

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

View File

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

View File

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

View File

@@ -140,28 +140,53 @@ void notify(byte callMode, bool followUp)
void realtimeLock(uint32_t timeoutMs, byte md)
{
if (!realtimeMode && !realtimeOverride){
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 0; i < totalLen; i++)
{
strip.setPixelColor(i,0,0,0,0);
if (!realtimeMode && !realtimeOverride) {
uint16_t stop, start;
if (useMainSegmentOnly) {
WS2812FX::Segment& mainseg = strip.getMainSegment();
start = mainseg.start;
stop = mainseg.stop;
mainseg.setOption(SEG_OPTION_FREEZE, true, strip.getMainSegmentId());
} else {
start = 0;
stop = strip.getLengthTotal();
}
// clear strip/segment
for (uint16_t i = start; i < stop; i++) strip.setPixelColor(i,0,0,0,0);
// if WLED was off and using main segment only, freeze non-main segments so they stay off
if (useMainSegmentOnly && bri == 0) {
for (uint8_t s=0; s < strip.getMaxSegments(); s++) {
strip.getSegment(s).setOption(SEG_OPTION_FREEZE, true, s);
}
}
}
// if strip is off (bri==0) and not already in RTM
if (briT == 0 && !realtimeMode && !realtimeOverride) {
strip.setBrightness(scaledBri(briLast), true);
}
if (realtimeTimeout != UINT32_MAX) {
realtimeTimeout = millis() + timeoutMs;
if (timeoutMs == 255001 || timeoutMs == 65000) realtimeTimeout = UINT32_MAX;
}
// if strip is off (bri==0) and not already in RTM
if (briT == 0 && !realtimeMode) {
strip.setBrightness(scaledBri(briLast), true);
realtimeTimeout = (timeoutMs == 255001 || timeoutMs == 65000) ? UINT32_MAX : millis() + timeoutMs;
}
realtimeMode = md;
if (arlsForceMaxBri && !realtimeOverride) strip.setBrightness(scaledBri(255), true);
if (realtimeOverride) return;
if (arlsForceMaxBri) strip.setBrightness(scaledBri(255), true);
if (briT > 0 && md == REALTIME_MODE_GENERIC) strip.show();
}
void exitRealtime() {
if (!realtimeMode) return;
if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE;
strip.setBrightness(scaledBri(bri));
realtimeTimeout = 0; // cancel realtime mode immediately
realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately
realtimeIP[0] = 0;
if (useMainSegmentOnly) { // unfreeze live segment again
strip.getMainSegment().setOption(SEG_OPTION_FREEZE, false, strip.getMainSegmentId());
}
}
#define TMP2NET_OUT_PORT 65442
@@ -189,13 +214,7 @@ void handleNotifications()
}
//unlock strip when realtime UDP times out
if (realtimeMode && millis() > realtimeTimeout)
{
if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE;
strip.setBrightness(scaledBri(bri));
realtimeMode = REALTIME_MODE_INACTIVE;
realtimeIP[0] = 0;
}
if (realtimeMode && millis() > realtimeTimeout) exitRealtime();
//receive UDP notifications
if (!udpConnected) return;
@@ -654,7 +673,7 @@ void sendSysInfoUDP()
uint8_t sequenceNumber = 0; // this needs to be shared across all outputs
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW) {
if (!interfacesInited || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address
if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap
WiFiUDP ddpUdp;

View File

@@ -112,6 +112,22 @@
#include "../usermods/quinled-an-penta/quinled-an-penta.h"
#endif
#ifdef USERMOD_WIZLIGHTS
#include "../usermods/wizlights/wizlights.h"
#endif
#ifdef USERMOD_WORDCLOCK
#include "../usermods/usermod_v2_word_clock/usermod_v2_word_clock.h"
#endif
#ifdef USERMOD_MY9291
#include "../usermods/MY9291/usermode_MY9291.h"
#endif
#ifdef USERMOD_SI7021_MQTT_HA
#include "../usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h"
#endif
void registerUsermods()
{
/*
@@ -211,4 +227,20 @@ void registerUsermods()
#ifdef QUINLED_AN_PENTA
usermods.add(new QuinLEDAnPentaUsermod());
#endif
#ifdef USERMOD_WIZLIGHTS
usermods.add(new WizLightsUsermod());
#endif
#ifdef USERMOD_WORDCLOCK
usermods.add(new WordClockUsermod());
#endif
#ifdef USERMOD_MY9291
usermods.add(new MY9291Usermod());
#endif
#ifdef USERMOD_SI7021_MQTT_HA
usermods.add(new Si7021_MQTT_HA());
#endif
}

View File

@@ -28,7 +28,7 @@ void WLED::reset()
yield(); // enough time to send response to client
}
applyBri();
DEBUG_PRINTLN(F("MODULE RESET"));
DEBUG_PRINTLN(F("WLED RESET"));
ESP.restart();
}
@@ -148,33 +148,38 @@ void WLED::loop()
yield();
handleIO();
handleIR();
#ifndef WLED_DISABLE_ALEXA
handleAlexa();
#endif
yield();
if (doReboot)
if (doReboot && !doInitBusses) // if busses have to be inited & saved, wait until next iteration
reset();
if (doCloseFile) {
closeFile();
yield();
}
if (!realtimeMode || realtimeOverride) // block stuff if WARLS/Adalight is enabled
if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) // block stuff if WARLS/Adalight is enabled
{
if (apActive)
dnsServer.processNextRequest();
#ifndef WLED_DISABLE_OTA
if (WLED_CONNECTED && aOtaEnabled)
ArduinoOTA.handle();
#endif
if (apActive) dnsServer.processNextRequest();
#ifndef WLED_DISABLE_OTA
if (WLED_CONNECTED && aOtaEnabled) ArduinoOTA.handle();
#endif
handleNightlight();
handlePlaylist();
yield();
#ifndef WLED_DISABLE_HUESYNC
handleHue();
#ifndef WLED_DISABLE_BLYNK
yield();
#endif
#ifndef WLED_DISABLE_BLYNK
handleBlynk();
#endif
yield();
#endif
yield();
@@ -271,6 +276,44 @@ void WLED::loop()
loops++;
#endif // WLED_DEBUG
toki.resetTick();
#if WLED_WATCHDOG_TIMEOUT > 0
// we finished our mainloop, reset the watchdog timer
#ifdef ARDUINO_ARCH_ESP32
esp_task_wdt_reset();
#else
ESP.wdtFeed();
#endif
#endif
}
void WLED::enableWatchdog() {
#if WLED_WATCHDOG_TIMEOUT > 0
#ifdef ARDUINO_ARCH_ESP32
esp_err_t watchdog = esp_task_wdt_init(WLED_WATCHDOG_TIMEOUT, true);
DEBUG_PRINT(F("Watchdog enabled: "));
if (watchdog == ESP_OK) {
DEBUG_PRINTLN(F("OK"));
} else {
DEBUG_PRINTLN(watchdog);
return;
}
esp_task_wdt_add(NULL);
#else
ESP.wdtEnable(WLED_WATCHDOG_TIMEOUT * 1000);
#endif
#endif
}
void WLED::disableWatchdog() {
#if WLED_WATCHDOG_TIMEOUT > 0
DEBUG_PRINTLN(F("Watchdog: disabled"));
#ifdef ARDUINO_ARCH_ESP32
esp_task_wdt_delete(NULL);
#else
ESP.wdtDisable();
#endif
#endif
}
void WLED::setup()
@@ -297,6 +340,8 @@ void WLED::setup()
DEBUG_PRINT(F("heap "));
DEBUG_PRINTLN(ESP.getFreeHeap());
enableWatchdog();
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
if (psramFound()) {
// GPIO16/GPIO17 reserved for SPI RAM
@@ -359,8 +404,8 @@ void WLED::setup()
#endif
#ifdef WLED_ENABLE_ADALIGHT
//Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused
//Serial TX (Debug, Improv, Serial JSON) only possible if GPIO1 unused
//Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused
//Serial TX (Debug, Improv, Serial JSON) only possible if GPIO1 unused
if (!pinManager.isPinAllocated(3) && !pinManager.isPinAllocated(1)) {
Serial.println(F("Ada"));
}
@@ -384,7 +429,9 @@ void WLED::setup()
sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6);
}
#ifdef WLED_ENABLE_ADALIGHT
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
#endif
strip.service();
@@ -394,8 +441,13 @@ void WLED::setup()
#ifdef ESP8266
wifi_set_sleep_type(NONE_SLEEP_T);
#endif
WLED::instance().disableWatchdog();
DEBUG_PRINTLN(F("Start ArduinoOTA"));
});
ArduinoOTA.onError([](ota_error_t error) {
// reenable watchdog on failed update
WLED::instance().enableWatchdog();
});
if (strlen(cmDNS) > 0)
ArduinoOTA.setHostname(cmDNS);
}
@@ -404,7 +456,10 @@ void WLED::setup()
initDMX();
#endif
#ifdef WLED_ENABLE_ADALIGHT
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
#endif
// HTTP server page init
initServer();

View File

@@ -3,12 +3,12 @@
/*
Main sketch, global variable declarations
@title WLED project sketch
@version 0.13.1
@version 0.13.2
@author Christian Schwinne
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2203150
#define VERSION 2208140
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -49,6 +49,12 @@
// filesystem specific debugging
//#define WLED_DEBUG_FS
#ifndef WLED_WATCHDOG_TIMEOUT
// 3 seconds should be enough to detect a lockup
// define WLED_WATCHDOG_TIMEOUT=0 to disable watchdog
#define WLED_WATCHDOG_TIMEOUT 3
#endif
//optionally disable brownout detector on ESP32.
//This is generally a terrible idea, but improves boot success on boards with a 3.3v regulator + cap setup that can't provide 400mA peaks
//#define WLED_DISABLE_BROWNOUT_DET
@@ -78,6 +84,7 @@
#else
#include <LittleFS.h>
#endif
#include "esp_task_wdt.h"
#endif
#include "src/dependencies/network/Network.h"
@@ -111,7 +118,11 @@
#endif
#ifdef WLED_ENABLE_DMX
#ifdef ESP8266
#include "src/dependencies/dmx/ESPDMX.h"
#else //ESP32
#include "src/dependencies/dmx/SparkFunDMX.h"
#endif
#endif
#include "src/dependencies/e131/ESPAsyncE131.h"
@@ -347,7 +358,11 @@ WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if
WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black
#ifdef WLED_ENABLE_DMX
WLED_GLOBAL DMXESPSerial dmx;
#ifdef ESP8266
WLED_GLOBAL DMXESPSerial dmx;
#else //ESP32
WLED_GLOBAL SparkFunDMX dmx;
#endif
WLED_GLOBAL uint16_t e131ProxyUniverse _INIT(0); // output this E1.31 (sACN) / ArtNet universe via MAX485 (0 = disabled)
#endif
WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes)
@@ -537,6 +552,7 @@ WLED_GLOBAL IPAddress realtimeIP _INIT_N(((0, 0, 0, 0)));
WLED_GLOBAL unsigned long realtimeTimeout _INIT(0);
WLED_GLOBAL uint8_t tpmPacketCount _INIT(0);
WLED_GLOBAL uint16_t tpmPayloadFrameSize _INIT(0);
WLED_GLOBAL bool useMainSegmentOnly _INIT(false);
// mqtt
WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0);
@@ -695,5 +711,7 @@ public:
void initConnection();
void initInterfaces();
void handleStatusLED();
void enableWatchdog();
void disableWatchdog();
};
#endif // WLED_H

View File

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

View File

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

View File

@@ -86,8 +86,8 @@ void handleSerial()
Serial.write(0xC9); Serial.write(0xDA);
uint16_t used = strip.getLengthTotal();
uint16_t len = used*3;
Serial.write((len << 8) & 0xFF);
Serial.write( len & 0xFF);
Serial.write(highByte(len));
Serial.write(lowByte(len));
for (uint16_t i=0; i < used; i++) {
uint32_t c = strip.getPixelColor(i);
Serial.write(qadd8(W(c), R(c))); //R, add white channel to RGB channels as a simple RGBW -> RGB map

View File

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

View File

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

View File

@@ -409,7 +409,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',co,bus->getColorOrder());
sappend('v',ls,bus->getStart());
sappend('c',cv,bus->reversed);
sappend('c',sl,bus->skippedLeds());
sappend('v',sl,bus->skippedLeds());
sappend('c',rf,bus->isOffRefreshRequired());
}
sappend('v',SET_F("MA"),strip.ablMilliampsMax);
@@ -493,6 +493,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',SET_F("NB"),nodeBroadcastEnabled);
sappend('c',SET_F("RD"),receiveDirect);
sappend('c',SET_F("MO"),useMainSegmentOnly);
sappend('v',SET_F("EP"),e131Port);
sappend('c',SET_F("ES"),e131SkipOutOfSequence);
sappend('c',SET_F("EM"),e131Multicast);