mirror of
https://github.com/wled/WLED.git
synced 2025-08-23 17:49:23 +00:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
420f858d9b | ||
![]() |
902c11d074 | ||
![]() |
38330b735c | ||
![]() |
bda3c4ab7a | ||
![]() |
d8d01ac353 | ||
![]() |
51d935f419 | ||
![]() |
c96f83b076 | ||
![]() |
7308f5993c | ||
![]() |
22ac12dc36 | ||
![]() |
866296fefd | ||
![]() |
9d574397bc | ||
![]() |
bee48dae7e | ||
![]() |
e12f7b67e5 | ||
![]() |
a8908238d5 | ||
![]() |
fd4c0e795a | ||
![]() |
c79eb43347 | ||
![]() |
860e74bffa | ||
![]() |
ed374684a6 | ||
![]() |
169a46c38c | ||
![]() |
1dbea434a3 | ||
![]() |
0dd12cf0a6 | ||
![]() |
19c8b4fe2d | ||
![]() |
26fa38d052 | ||
![]() |
db8e1dec3e | ||
![]() |
213e3e998a | ||
![]() |
bef9c68f81 | ||
![]() |
099d2fd03d | ||
![]() |
23d39e5366 | ||
![]() |
1a513c7bbf | ||
![]() |
d1f76042e1 | ||
![]() |
9cd8acab43 | ||
![]() |
8b79a9708b | ||
![]() |
e362b3b6aa | ||
![]() |
d2ced93e58 | ||
![]() |
958cd35e21 | ||
![]() |
46eae410c3 | ||
![]() |
73a9e1c316 | ||
![]() |
03862d4b6c | ||
![]() |
ae90aa4ccc | ||
![]() |
b583def913 | ||
![]() |
dd85da053f | ||
![]() |
6079effae3 | ||
![]() |
8d2fe315db | ||
![]() |
22c3ac5be3 | ||
![]() |
a517f0df1d | ||
![]() |
9c9854b6bf | ||
![]() |
d280e16723 | ||
![]() |
b93a9cb8bc | ||
![]() |
8601052179 | ||
![]() |
eaa20ff4bf | ||
![]() |
e4c6e4bc48 | ||
![]() |
c52597205e | ||
![]() |
c73033c0b4 | ||
![]() |
522e752582 | ||
![]() |
854ed8cfa9 | ||
![]() |
4642205768 | ||
![]() |
40dbfbe092 |
31
CHANGELOG.md
31
CHANGELOG.md
@@ -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
14
package-lock.json
generated
@@ -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",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "0.13.1",
|
||||
"version": "0.13.2",
|
||||
"description": "Tools for WLED project",
|
||||
"main": "tools/cdata.js",
|
||||
"directories": {
|
||||
|
@@ -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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
@@ -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;
|
||||
}
|
||||
};
|
@@ -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
321
usermods/MY9291/MY92xx.h
Normal 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 pulse,at 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
|
45
usermods/MY9291/usermode_MY9291.h
Normal file
45
usermods/MY9291/usermode_MY9291.h
Normal 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;
|
||||
}
|
||||
};
|
@@ -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
|
||||
|
||||
|
@@ -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;
|
||||
|
69
usermods/Si7021_MQTT_HA/readme.md
Normal file
69
usermods/Si7021_MQTT_HA/readme.md
Normal 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
|
236
usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h
Normal file
236
usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h
Normal 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";
|
@@ -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.
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
26
usermods/usermod_v2_word_clock/readme.md
Normal file
26
usermods/usermod_v2_word_clock/readme.md
Normal 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
|
427
usermods/usermod_v2_word_clock/usermod_v2_word_clock.h
Normal file
427
usermods/usermod_v2_word_clock/usermod_v2_word_clock.h
Normal 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!
|
||||
};
|
35
usermods/wizlights/readme.md
Normal file
35
usermods/wizlights/readme.md
Normal 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.
|
154
usermods/wizlights/wizlights.h
Normal file
154
usermods/wizlights/wizlights.h
Normal 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;}
|
||||
};
|
12
wled00/FX.h
12
wled00/FX.h
@@ -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:
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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
|
||||
|
7
wled00/data/icons-ui/Read Me.txt
Normal file
7
wled00/data/icons-ui/Read Me.txt
Normal 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.
|
152
wled00/data/icons-ui/demo-files/demo.css
Normal file
152
wled00/data/icons-ui/demo-files/demo.css
Normal 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;
|
||||
}
|
||||
|
30
wled00/data/icons-ui/demo-files/demo.js
Normal file
30
wled00/data/icons-ui/demo-files/demo.js
Normal 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();
|
||||
}());
|
360
wled00/data/icons-ui/demo.html
Normal file
360
wled00/data/icons-ui/demo.html
Normal 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: 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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">
|
||||
</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>
|
BIN
wled00/data/icons-ui/fonts/wled122.woff
Normal file
BIN
wled00/data/icons-ui/fonts/wled122.woff
Normal file
Binary file not shown.
BIN
wled00/data/icons-ui/fonts/wled122.woff2
Normal file
BIN
wled00/data/icons-ui/fonts/wled122.woff2
Normal file
Binary file not shown.
1
wled00/data/icons-ui/selection.json
Normal file
1
wled00/data/icons-ui/selection.json
Normal file
File diff suppressed because one or more lines are too long
96
wled00/data/icons-ui/style.css
Normal file
96
wled00/data/icons-ui/style.css
Normal 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";
|
||||
}
|
@@ -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;
|
||||
|
@@ -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()"></i>
|
||||
<i class="icons slider-icon" style="cursor: pointer;" title="Freeze" onclick="tglFreeze()"></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>
|
||||
|
@@ -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})"></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);
|
||||
}
|
||||
|
||||
|
@@ -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}");
|
||||
|
@@ -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;
|
||||
});
|
||||
|
@@ -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();">
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
@@ -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
4437
wled00/html_ui.h
4437
wled00/html_ui.h
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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");
|
||||
|
||||
|
@@ -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"));
|
||||
|
@@ -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
|
55
wled00/src/dependencies/dmx/LICENSE.md
Normal file
55
wled00/src/dependencies/dmx/LICENSE.md
Normal 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.
|
160
wled00/src/dependencies/dmx/SparkFunDMX.cpp
Normal file
160
wled00/src/dependencies/dmx/SparkFunDMX.cpp
Normal 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
|
38
wled00/src/dependencies/dmx/SparkFunDMX.h
Normal file
38
wled00/src/dependencies/dmx/SparkFunDMX.h
Normal 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
|
@@ -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;
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user