Merge branch '0_15' into serg

This commit is contained in:
Woody 2024-03-19 17:49:56 +01:00
commit c6ff45f959
No known key found for this signature in database
GPG Key ID: 9872D7F5072789B2
128 changed files with 4352 additions and 1740 deletions

View File

@ -45,7 +45,7 @@ jobs:
~/.platformio/.cache
~/.buildcache
build_output
key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**') }}
key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }}
restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-
- name: Set up Python
uses: actions/setup-python@v5
@ -86,7 +86,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'

View File

@ -1,5 +1,55 @@
## WLED changelog
#### Build 2403190
- limit max PWM frequency (fix incorrect PWM resolution)
- Segment UI bugfix
- Updated AsyncWebServer (by @wlillmmiles)
- Simpler boot preset (fix for #3806)
- Effect: Fix for 2D Drift animation (#3816 by @BaptisteHudyma)
- Effect: Add twin option to 2D Drift
- MQTT cleanup
- DDP: Support sources that don't push (#3833 by @willmmiles)
- Usermod: Tetris AI usermod (#3711 by @muebau)
#### Build 2403171
- merge 0.14.2 changes into 0.15
#### Build 2403070
- Add additional segment options when controlling over e1.31 (#3616 by @demophoon)
- LockedJsonResponse: Release early if possible (#3760 by @willmmiles)
- Update setup-node and cache usermods in wled-ci.yml (#3737 by @WoodyLetsCode)
- Fix preset sorting (#3790 by @WoodyLetsCode)
- compile time button configuration #3792
- remove IR config if not compiled
- additional string optimisations
- Better low brightness level PWM handling (fixes #2767, #2868)
#### Build 2402290
- Multiple analog button fix for #3549
- Preset caching on chips with PSRAM (credit @akaricchi)
- Fixing stairway usermod and adding buildflags (by @lost-hope)
- ESP-NOW packet modification
- JSON buffer lock error messages / Reduce wait time for lock to 100ms
- Reduce string RAM usage for ESP8266
- Fixing a potential array bounds violation in ESPDMX
- Move timezone table to PROGMEM (#3766 by @willmmiles)
- Reposition upload warning message. (fixes #3778)
- ABL display fix & optimisation
- Add virtual Art-Net RGBW option (#3783 by @shammy642)
#### Build 2402090
- Added new Ethernet controller RGB2Go Tetra (duplicate of ESP3DEUXQuattro)
- Usermod: httpPullLightControl (#3560 by @roelbroersma)
- DMX: S2 & C3 support via modified ESPDMX
- Bugfix: prevent cleaning of JSON buffer after a failed lock attempt (BufferGuard)
- Product/Brand override (API & AP SSID) (#3750 by @moustachauve)
#### Build 2402060
- WLED version 0.15.0-b1
- Harmonic Random Cycle palette (#3729 by @dedehai)
- Multi PIR sensor usermod (added support for attaching multiple PIR sensors)
- Removed obsolete (and nonfunctional) usermods
#### Build 2309120 till build 2402010
- WLED version 0.15.0-a0
- Multi-WiFi support. Add up to 3 (or more via cusom compile) WiFis to connect to
@ -84,6 +134,53 @@
- send UDP/WS on segment change
- pop_back() when removing last segment
#### Build 2403170
- WLED 0.14.2 release
#### Build 2403110
- Beta WLED 0.14.2-b2
- New AsyncWebServer (improved performance and reduced memory use)
- New builds for ESP8266 with 160MHz CPU clock
- Fixing stairway usermod and adding buildflags (#3758 by @lost-hope)
- Fixing a potential array bounds violation in ESPDMX
- Reduced RAM usage (moved strings and TZ data (by @willmmiles) to PROGMEM)
- LockedJsonResponse: Release early if possible (by @willmmiles)
#### Build 2402120
- Beta WLED 0.14.2-b1
- Possible fix for #3589 & partial fix for #3605
- Prevent JSON buffer clear after failed lock attempt
- Multiple analog button fix for #3549
- UM Audioreactive: add two compiler options (#3732 by @wled-install)
- Fix for #3693
#### Build 2401141
- Official release of WLED 0.14.1
- Fix for #3566, #3665, #3672
- Sorting of palettes in custom palette editor (#3674 by @WoodyLetsCode)
#### Build 2401060
- Version bump: 0.14.1-b3
- Global JSON buffer guarding (#3648 by @willmmiles, resolves #3641, #3312, #3367, #3637, #3646, #3447)
- Fix for #3632
- Custom palette editor mobile UI enhancement (#3617 by @imeszaros)
- changelog update
#### Build 2312290
- Fix for #3622, #3613, #3609
- Various tweaks and fixes
- changelog update
#### Build 2312230
- Version bump: 0.14.1-b2
- Fix for Pixel Magic button
- Fix for #2922 (option to force WiFi PHY mode to G on ESP8266)
- Fix for #3601, #3400 (incorrect sunrise/sunset, #3612 by @softhack007)
#### Build 2312180
- Bugfixes (#3593, #3490, #3573, #3517, #3561, #3555, #3541, #3536, #3515, #3522, #3533, #3508)
- Various other internal cleanups and optimisations
#### Build 2311160
- Version bump: 0.14.1-b1
- Bugfixes (#3526, #3502, #3496, #3484, #3487, #3445, #3466, #3296, #3382, #3312)
@ -516,7 +613,7 @@
- Added application level pong websockets reply (#2139)
- Use AsyncTCP 1.0.3 as it mitigates the flickering issue from 0.13.0-b2
- Fixed transition manually updated in preset overriden by field value
- Fixed transition manually updated in preset overridden by field value
#### Build 2108050
@ -1045,7 +1142,7 @@
#### Build 2011040
- Inversed Rain direction (fixes #1147)
- Inverted Rain direction (fixes #1147)
#### Build 2011010
@ -1256,7 +1353,7 @@
- Added module info page to web UI
- Added realtime override functionality to web UI
- Added individial segment power and brightness to web UI
- Added individual segment power and brightness to web UI
- Added feature to one-click select single segment only by tapping segment name
- Removed palette jumping to default if color is changed

View File

@ -2,6 +2,20 @@
Here are a few suggestions to make it easier for you to contribute!
### Describe your PR
Please add a description of your proposed code changes. It does not need to be an exhaustive essay, however a PR with no description or just a few words might not get accepted, simply because very basic information is missing.
A good description helps us to review and understand your proposed changes. For example, you could say a few words about
* what you try to achieve (new feature, fixing a bug, refactoring, security enhancements, etc.)
* how your code works (short technical summary - focus on important aspects that might not be obvious when reading the code)
* testing you performed, known limitations, open ends you possibly could not solve.
* any areas where you like to get help from an experienced maintainer (yes WLED has become big 😉)
### Target branch for pull requests
Please make all PRs against the `0_15` branch.
### Code style
When in doubt, it is easiest to replicate the code style you find in the files you want to edit :)
@ -73,6 +87,6 @@ Good:
<!-- This is an HTML comment -->
```
There is no set character limit for a comment within a line,
though as a rule of thumb you should wrap your comment if it exceeds the width of your editor window.
There is no hard character limit for a comment within a line,
though as a rule of thumb consider wrapping after 120 characters.
Inline comments are OK if they describe that line only and are not exceedingly wide.

573
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -10,7 +10,7 @@
# ------------------------------------------------------------------------------
# CI/release binaries
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32_wrover
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32_wrover
src_dir = ./wled00
data_dir = ./wled00/data
@ -144,11 +144,11 @@ lib_deps =
fastled/FastLED @ 3.6.0
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.7.5
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.1.0
# for I2C interface
;Wire
# ESP-NOW library (includes mandatory QuickDebug library)
;gmag11/QuickESPNow @ 0.6.2
# ESP-NOW library
;gmag11/QuickESPNow @ ~0.7.0
https://github.com/blazoncek/QuickESPNow.git#optional-debug
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
#TFT_eSPI
@ -172,7 +172,7 @@ lib_deps =
# SHT85
;robtillaart/SHT85@~0.3.3
# Audioreactive usermod
;https://github.com/kosme/arduinoFFT#develop @ ^1.9.2
;kosme/arduinoFFT @ 2.0.0
extra_scripts = ${scripts_defaults.extra_scripts}
@ -222,8 +222,8 @@ lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
${env.lib_deps}
# additional build flags for audioreactive
AR_build_flags = -D USERMOD_AUDIOREACTIVE -D UM_AUDIOREACTIVE_USE_NEW_FFT
AR_lib_deps = https://github.com/kosme/arduinoFFT#develop @ ^1.9.2
AR_build_flags = -D USERMOD_AUDIOREACTIVE
AR_lib_deps = kosme/arduinoFFT @ 2.0.0
[esp32_idf_V4]
;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
@ -314,6 +314,11 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 #-DWLED
lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder
[env:nodemcuv2_160]
extends = env:nodemcuv2
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D
[env:esp8266_2m]
board = esp_wroom_02
platform = ${common.platform_wled_default}
@ -323,6 +328,11 @@ build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m_160]
extends = env:esp8266_2m
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02_160
[env:esp01_1m_full]
board = esp01_1m
platform = ${common.platform_wled_default}
@ -333,6 +343,12 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_D
; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM
lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full_160]
extends = env:esp01_1m_full
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA
; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM
[env:esp32dev]
board = esp32dev
platform = ${esp32.platform}

View File

@ -26,7 +26,7 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control
- Up to 250 user presets to save and load colors/effects easily, supports cycling through them.
- Presets can be used to automatically execute API calls
- Nightlight function (gradually dims down)
- Full OTA software updatability (HTTP + ArduinoOTA), password protectable
- Full OTA software updateability (HTTP + ArduinoOTA), password protectable
- Configurable analog clock (Cronixie, 7-segment and EleksTube IPS clock support via usermods)
- Configurable Auto Brightness limit for safe operation
- Filesystem-based config for easier backup of presets and settings

View File

@ -52,7 +52,7 @@ starlette==0.23.1
# via platformio
tabulate==0.9.0
# via platformio
urllib3==1.26.15
urllib3==1.26.18
# via requests
uvicorn==0.20.0
# via platformio

View File

@ -30,14 +30,12 @@ const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h"
// \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset
const wledBanner = `
\t\x1b[34m## ## ## ######## ########
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m## ## ## ## ###### ## ##
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m ### ### ######## ######## ########
\t\t\x1b[36mbuild script for web UI
\t\x1b[34m ## ## ## ###### ######
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m## ## ## ## ###### ## ##
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m ## ## ###### ###### ######
\t\t\x1b[36m build script for web UI
\x1b[0m`;
const singleHeader = `/*

View File

@ -133,7 +133,7 @@ class Animated_Staircase : public Usermod {
* received within this time, an object is detected
* and the function will return true.
*
* The speed of sound is 343 meters per second at 20 degress Celcius.
* The speed of sound is 343 meters per second at 20 degrees Celsius.
* Since the sound has to travel back and forth, the detection
* distance for the sensor in cm is (0.0343 * maxTimeUs) / 2.
*
@ -259,7 +259,7 @@ class Animated_Staircase : public Usermod {
}
}
// send sesnor values to JSON API
// send sensor values to JSON API
void writeSensorsToJson(JsonObject& staircase) {
staircase[F("top-sensor")] = topSensorRead;
staircase[F("bottom-sensor")] = bottomSensorRead;
@ -309,7 +309,7 @@ class Animated_Staircase : public Usermod {
seg.setOption(SEG_OPTION_ON, true);
}
strip.trigger(); // force strip update
stateChanged = true; // inform external dvices/UI of change
stateChanged = true; // inform external devices/UI of change
colorUpdated(CALL_MODE_DIRECT_CHANGE);
DEBUG_PRINTLN(F("Animated Staircase disabled."));
}
@ -492,7 +492,7 @@ class Animated_Staircase : public Usermod {
bottomEchoPin = top[FPSTR(_bottomEcho_pin)] | bottomEchoPin;
topMaxDist = top[FPSTR(_topEchoCm)] | topMaxDist;
topMaxDist = min(150,max(30,(int)topMaxDist)); // max distnace ~1.5m (a lag of 9ms may be expected)
topMaxDist = min(150,max(30,(int)topMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)
bottomMaxDist = top[FPSTR(_bottomEchoCm)] | bottomMaxDist;
bottomMaxDist = min(150,max(30,(int)bottomMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)

View File

@ -11,7 +11,7 @@ The Animated Staircase can be controlled by the WLED API. Change settings such a
speed, on/off time and distance by sending an HTTP request, see below.
## WLED integration
To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://github.com/Aircoookie/WLED/wiki/Compiling-WLED).
To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/).
Before compiling, you have to make the following modifications:
@ -38,7 +38,7 @@ Maximum distance for ultrasonic sensor can be configured as the time needed for
You _may_ need to use 10k pull-down resistors on the selected PIR pins, depending on the sensor.
## WLED configuration
1. In the WLED UI, confgure a segment for each step. The lowest step of the stairs is the
1. In the WLED UI, configure a segment for each step. The lowest step of the stairs is the
lowest segment id.
2. Save your segments into a preset.
3. Ideally, add the preset in the config > LED setup menu to the "apply
@ -91,7 +91,7 @@ To enable the usermod again, use `"enabled":true`.
Alternatively you can use _Usermod_ Settings page where you can change other parameters as well.
### Changing animation parameters and detection range of the ultrasonic HC-SR04 sensor
Using _Usermod_ Settings page you can define different usermod parameters, includng sensor pins, delay between segment activation etc.
Using _Usermod_ Settings page you can define different usermod parameters, including sensor pins, delay between segment activation etc.
When an ultrasonic sensor is enabled you can enter maximum detection distance in centimeters separately for top and bottom sensors.

View File

@ -9,7 +9,7 @@ The luminance is displayed in both the Info section of the web UI, as well as pu
- This must be added under `lib_deps` in your `platformio.ini` (or `platformio_override.ini`).
- Data is published over MQTT - make sure you've enabled the MQTT sync interface.
## Compiliation
## Compilation
To enable, compile with `USERMOD_BH1750` defined (e.g. in `platformio_override.ini`)
```ini

View File

@ -25,7 +25,7 @@
#define USERMOD_BH1750_FIRST_MEASUREMENT_AT 10000
#endif
// only report if differance grater than offset value
// only report if difference grater than offset value
#ifndef USERMOD_BH1750_OFFSET_VALUE
#define USERMOD_BH1750_OFFSET_VALUE 1
#endif
@ -86,7 +86,7 @@ private:
StaticJsonDocument<600> doc;
doc[F("name")] = String(serverDescription) + F(" ") + name;
doc[F("name")] = String(serverDescription) + " " + name;
doc[F("state_topic")] = topic;
doc[F("unique_id")] = String(mqttClientID) + name;
if (unitOfMeasurement != "")
@ -98,8 +98,8 @@ private:
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("manufacturer")] = F(WLED_BRAND);
device[F("model")] = F(WLED_PRODUCT_NAME);
device[F("sw_version")] = versionString;
String temp;

View File

@ -31,7 +31,7 @@ private:
// set the default pins based on the architecture, these get overridden by Usermod menu settings
#ifdef ESP8266
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
//uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8
#endif
bool initDone = false;
@ -78,7 +78,7 @@ private:
static const char _name[];
static const char _enabled[];
// Read the BME280/BMP280 Sensor (which one runs depends on whether Celsius or Farenheit being set in Usermod Menu)
// Read the BME280/BMP280 Sensor (which one runs depends on whether Celsius or Fahrenheit being set in Usermod Menu)
void UpdateBME280Data(int SensorType)
{
float _temperature, _humidity, _pressure;
@ -160,8 +160,8 @@ private:
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("manufacturer")] = F(WLED_BRAND);
device[F("model")] = F(WLED_PRODUCT_NAME);
device[F("sw_version")] = versionString;
String temp;

View File

@ -19,7 +19,7 @@ If you have an ESP32 board, connect the positive side of the battery to ADC1 (GP
- 💯 Displays current battery voltage
- 🚥 Displays battery level
- 🚫 Auto-off with configurable Threshold
- 🚨 Low power indicator with many configuration posibilities
- 🚨 Low power indicator with many configuration possibilities
## 🎈 Installation
@ -41,7 +41,7 @@ define `USERMOD_BATTERY` in `wled00/my_config.h`
| `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | battery check interval. defaults to 30 seconds |
| `USERMOD_BATTERY_MIN_VOLTAGE` | v | minimum battery voltage. default is 2.6 (18650 battery standard) |
| `USERMOD_BATTERY_MAX_VOLTAGE` | v | maximum battery voltage. default is 4.2 (18650 battery standard) |
| `USERMOD_BATTERY_TOTAL_CAPACITY` | mAh | the capacity of all cells in parralel sumed up |
| `USERMOD_BATTERY_TOTAL_CAPACITY` | mAh | the capacity of all cells in parallel summed up |
| `USERMOD_BATTERY_CALIBRATION` | | offset / calibration number, fine tune the measured voltage by the microcontroller |
| Auto-Off | --- | --- |
| `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | enables auto-off |

View File

@ -114,7 +114,7 @@ class UsermodCronixie : public Usermod {
//W Week of Month | WW Week of Year
//D Day of Week | DD Day Of Month | DDD Day Of Year
DEBUG_PRINT("cset ");
DEBUG_PRINT(F("cset "));
DEBUG_PRINTLN(cronixieDisplay);
for (int i = 0; i < 6; i++)
@ -160,7 +160,7 @@ class UsermodCronixie : public Usermod {
//case 'v': break; //user var1
}
}
DEBUG_PRINT("result ");
DEBUG_PRINT(F("result "));
for (int i = 0; i < 5; i++)
{
DEBUG_PRINT((int)dP[i]);

View File

@ -49,7 +49,7 @@
#endif
// how many seconds after boot to take first measurement, 90 seconds
// 90 gives enough time to OTA update firmware if this crashses
// 90 gives enough time to OTA update firmware if this crashes
#ifndef USERMOD_DHT_FIRST_MEASUREMENT_AT
#define USERMOD_DHT_FIRST_MEASUREMENT_AT 90000
#endif

View File

@ -46,7 +46,7 @@ class MyExampleUsermod : public Usermod {
static const char _enabled[];
// any private methods should go here (non-inline methosd should be defined out of class)
// any private methods should go here (non-inline method should be defined out of class)
void publishMqtt(const char* state, bool retain = false); // example for publishing MQTT message

View File

@ -15,23 +15,23 @@ OneWire oneWire(13);
DallasTemperature sensor(&oneWire);
long temptimer = millis();
long lastMeasure = 0;
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit
// If display does not work or looks corrupted check the
// constructor reference:
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
// or check the gallery:
// https://github.com/olikraus/u8g2/wiki/gallery
// --> First choise of cheap I2C OLED 128X32 0.91"
// --> First choice of cheap I2C OLED 128X32 0.91"
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3"
// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3"
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
// gets called once at boot. Do all initialization that doesn't depend on
// network here
void userSetup() {
sensor.begin(); //Start Dallas temperature sensor
u8x8.begin();
//u8x8.setFlipMode(1); //Uncoment if using WLED Wemos shield
//u8x8.setFlipMode(1); //Un-comment if using WLED Wemos shield
u8x8.setPowerSave(0);
u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
u8x8.setFont(u8x8_font_chroma48medium8_r);
@ -71,7 +71,7 @@ void userLoop() {
if (mqtt != nullptr)
{
sensor.requestTemperatures();
//Gets prefered temperature scale based on selection in definitions section
//Gets preferred temperature scale based on selection in definitions section
#ifdef Celsius
float board_temperature = sensor.getTempCByIndex(0);
#else
@ -138,11 +138,11 @@ void userLoop() {
// First row with Wifi name
u8x8.setCursor(1, 0);
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
// Print `~` char to indicate that SSID is longer, than owr dicplay
// Print `~` char to indicate that SSID is longer than our display
if (knownSsid.length() > u8x8.getCols())
u8x8.print("~");
// Second row with IP or Psssword
// Second row with IP or Password
u8x8.setCursor(1, 1);
// Print password in AP mode and if led is OFF.
if (apActive && bri == 0)

View File

@ -10,7 +10,7 @@
void UpdateBME280Data();
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit
BME280I2C bme; // Default : forced mode, standby time = 1000 ms
// Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off,
@ -20,14 +20,14 @@ 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
// uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8
#endif
//The SCL and SDA pins are defined here.
//ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21
#define U8X8_PIN_SCL SCL_PIN
#define U8X8_PIN_SDA SDA_PIN
//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8
//#define U8X8_PIN_RESET RST_PIN // Un-comment for Heltec WiFi-Kit-8
// If display does not work or looks corrupted check the
// constructor reference:
@ -36,9 +36,9 @@ uint8_t SDA_PIN = 4;
// https://github.com/olikraus/u8g2/wiki/gallery
// --> First choise of cheap I2C OLED 128X32 0.91"
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3"
// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3"
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91"
// --> Third choice of Heltec WiFi-Kit-8 OLED 128X32 0.91"
//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8
// gets called once at boot. Do all initialization that doesn't depend on network here
@ -181,11 +181,11 @@ void userLoop() {
// First row with Wifi name
u8x8.setCursor(1, 0);
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
// Print `~` char to indicate that SSID is longer, than owr dicplay
// Print `~` char to indicate that SSID is longer than our display
if (knownSsid.length() > u8x8.getCols())
u8x8.print("~");
// Second row with IP or Psssword
// Second row with IP or Password
u8x8.setCursor(1, 1);
// Print password in AP mode and if led is OFF.
if (apActive && bri == 0)

View File

@ -2,7 +2,7 @@
**Attention: This usermod compiles only for ESP8266**
This usermod-v2 modification performs a ping request to a local IP address every 60 seconds. This ensures WLED net services remain accessible in some problematic WLAN environments.
This usermod-v2 modification performs a ping request to a local IP address every 60 seconds. This ensures WLED net services remain accessible in some problematic WiFi environments.
The modification works with static or DHCP IP address configuration.
@ -24,7 +24,7 @@ The usermod supports the following state changes:
| JSON key | Value range | Description |
|-------------|------------------|---------------------------------|
| PingDelayMs | 5000 to 18000000 | Deactivdate/activate the sensor |
| PingDelayMs | 5000 to 18000000 | Deactivate/activate the sensor |
Changes also persist after a reboot.

View File

@ -15,7 +15,7 @@ private:
static const char _enabled[];
static const char _loopInterval[];
// any private methods should go here (non-inline methosd should be defined out of class)
// any private methods should go here (non-inline method should be defined out of class)
void publishMqtt(const char *state, bool retain = false); // example for publishing MQTT message
public:

View File

@ -0,0 +1,26 @@
# LDR_Dusk_Dawn_v2
This usermod will obtain readings from a Light Dependent Resistor (LDR) and will turn on/off specific presets based on those readings. This is useful for exterior lighting situations where you want the lights to only be on when it is dark out.
# Installation
Add "-D USERMOD_LDR_DUSK_DAWN" to your platformio.ini [common] build_flags and build.
Example:
```
[common]
build_flags =
-D USERMOD_LDR_DUSK_DAWN # Enable LDR Dusk Dawn Usermod
```
# Usermod Settings
Setting | Description | Default
--- | --- | ---
Enabled | Enable/Disable the LDR functionality. | Disabled
LDR Pin | The analog capable pin your LDR is connected to. | 34
Threshold Minutes | The number of minutes of consistent readings above/below the on/off threshold before the LED state will change. | 5
Threshold | The analog read value threshold from the LDR. Readings lower than this number will count towards changing the LED state to off. You can see the current LDR reading by going into the info section when LDR functionality is enabled. | 1000
On Preset | The WLED preset to be used for the LED on state. | 1
Off Preset | The WLED preset to be used for the LED off state. | 2
## Author
[@jeffwdh](https://github.com/jeffwdh)
jeffwdh@tarball.ca

View File

@ -0,0 +1,153 @@
#pragma once
#include "wled.h"
#ifndef ARDUINO_ARCH_ESP32
// 8266 does not support analogRead on user selectable pins
#error only ESP32 is supported by usermod LDR_DUSK_DAWN
#endif
class LDR_Dusk_Dawn_v2 : public Usermod {
private:
// Defaults
bool ldrEnabled = false;
int ldrPin = 34; //A2 on Adafruit Huzzah32
int ldrThresholdMinutes = 5; // How many minutes of readings above/below threshold until it switches LED state
int ldrThreshold = 1000; // Readings higher than this number will turn off LED.
int ldrOnPreset = 1; // Default "On" Preset
int ldrOffPreset = 2; // Default "Off" Preset
// Variables
bool initDone = false;
bool ldrEnabledPreviously = false; // Was LDR enabled for the previous check? First check is always no.
int ldrOffCount; // Number of readings above the threshold
int ldrOnCount; // Number of readings below the threshold
int ldrReading = 0; // Last LDR reading
int ldrLEDState; // Current LED on/off state
unsigned long lastMillis = 0;
static const char _name[];
public:
void setup() {
// register ldrPin
if ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)) {
if(!pinManager.allocatePin(ldrPin, false, PinOwner::UM_LDR_DUSK_DAWN)) ldrEnabled = false; // pin already in use -> disable usermod
else pinMode(ldrPin, INPUT); // alloc success -> configure pin for input
} else ldrEnabled = false; // invalid pin -> disable usermod
initDone = true;
}
void loop() {
// Only update every 10 seconds
if (millis() - lastMillis > 10000) {
if ( (ldrEnabled == true)
&& (ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0) ) { // make sure that pin is valid for analogread()
// Default state is off
if (ldrEnabledPreviously == false) {
applyPreset(ldrOffPreset);
ldrEnabledPreviously = true;
ldrLEDState = 0;
}
// Get LDR reading and increment counter by number of seconds since last read
ldrReading = analogRead(ldrPin);
if (ldrReading <= ldrThreshold) {
ldrOnCount = ldrOnCount + 10;
ldrOffCount = 0;
} else {
ldrOffCount = ldrOffCount + 10;
ldrOnCount = 0;
}
if (ldrOnCount >= (ldrThresholdMinutes * 60)) {
ldrOnCount = 0;
// If LEDs were previously off, turn on
if (ldrLEDState == 0) {
applyPreset(ldrOnPreset);
ldrLEDState = 1;
}
}
if (ldrOffCount >= (ldrThresholdMinutes * 60)) {
ldrOffCount = 0;
// If LEDs were previously on, turn off
if (ldrLEDState == 1) {
applyPreset(ldrOffPreset);
ldrLEDState = 0;
}
}
} else {
// LDR is disabled, reset variables to default
ldrReading = 0;
ldrOnCount = 0;
ldrOffCount = 0;
ldrLEDState = 0;
ldrEnabledPreviously = false;
}
lastMillis = millis();
}
}
void addToConfig(JsonObject& root) {
JsonObject top = root.createNestedObject(FPSTR(_name));
top["Enabled"] = ldrEnabled;
top["LDR Pin"] = ldrPin;
top["Threshold Minutes"] = ldrThresholdMinutes;
top["Threshold"] = ldrThreshold;
top["On Preset"] = ldrOnPreset;
top["Off Preset"] = ldrOffPreset;
}
bool readFromConfig(JsonObject& root) {
int8_t oldLdrPin = ldrPin;
JsonObject top = root[FPSTR(_name)];
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top["Enabled"], ldrEnabled);
configComplete &= getJsonValue(top["LDR Pin"], ldrPin);
configComplete &= getJsonValue(top["Threshold Minutes"], ldrThresholdMinutes);
configComplete &= getJsonValue(top["Threshold"], ldrThreshold);
configComplete &= getJsonValue(top["On Preset"], ldrOnPreset);
configComplete &= getJsonValue(top["Off Preset"], ldrOffPreset);
if (initDone && (ldrPin != oldLdrPin)) {
// pin changed - un-register previous pin, register new pin
if (oldLdrPin >= 0) pinManager.deallocatePin(oldLdrPin, PinOwner::UM_LDR_DUSK_DAWN);
setup(); // setup new pin
}
return configComplete;
}
void addToJsonInfo(JsonObject& root) {
// If "u" object does not exist yet we need to create it
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
JsonArray LDR_Enabled = user.createNestedArray("LDR dusk/dawn enabled");
LDR_Enabled.add(ldrEnabled);
if (!ldrEnabled) return; // do not add more if usermod is disabled
JsonArray LDR_Reading = user.createNestedArray("LDR reading");
LDR_Reading.add(ldrReading);
JsonArray LDR_State = user.createNestedArray("LDR turned LEDs on");
LDR_State.add(bool(ldrLEDState));
// Optional debug information:
//JsonArray LDR_On_Count = user.createNestedArray("LDR on count");
//LDR_On_Count.add(ldrOnCount);
//JsonArray LDR_Off_Count = user.createNestedArray("LDR off count");
//LDR_Off_Count.add(ldrOffCount);
//bool pinValid = ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0));
//if (pinManager.getPinOwner(ldrPin) != PinOwner::UM_LDR_DUSK_DAWN) pinValid = false;
//JsonArray LDR_valid = user.createNestedArray(F("LDR pin"));
//LDR_valid.add(ldrPin);
//LDR_valid.add(pinValid ? F(" OK"): F(" invalid"));
}
uint16_t getId() {
return USERMOD_ID_LDR_DUSK_DAWN;
}
};
const char LDR_Dusk_Dawn_v2::_name[] PROGMEM = "LDR_Dusk_Dawn_v2";

View File

@ -23,7 +23,7 @@ You can also use usermod's off timer instead of sensor's. In such case rotate th
## Usermod installation
**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionaly `-D PIR_SENSOR_PIN=16` to override default pin. You can also change the default off time by adding `-D PIR_SENSOR_OFF_SEC=30`.
**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionally `-D PIR_SENSOR_PIN=16` to override default pin. You can also change the default off time by adding `-D PIR_SENSOR_OFF_SEC=30`.
## API to enable/disable the PIR sensor from outside. For example from another usermod:
@ -31,7 +31,7 @@ To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` an
When the PIR sensor state changes an MQTT message is broadcasted with topic `wled/deviceMAC/motion` and message `on` or `off`.
Usermod can also be configured to send just the MQTT message but not change WLED state using settings page as well as responding to motion only at night
(assuming NTP and lattitude/longitude are set to determine sunrise/sunset times).
(assuming NTP and latitude/longitude are set to determine sunrise/sunset times).
### There are two options to get access to the usermod instance:
@ -75,6 +75,9 @@ Usermod can be configured via the Usermods settings page.
* `mqtt-only` - send only MQTT messages, do not interact with WLED
* `off-only` - only trigger presets or turn WLED on/off if WLED is not already on (displaying effect)
* `notifications` - enable or disable sending notifications to other WLED instances using Sync button
* `HA-discovery` - enable automatic discovery in Home Assistant
* `override` - override PIR input when WLED state is changed using UI
* `domoticz-idx` - Domoticz virtual switch ID (used with MQTT `domoticz/in`)
Have fun - @gegu & @blazoncek
@ -85,9 +88,16 @@ Have fun - @gegu & @blazoncek
2021-11
* Added information about dynamic configuration options
* Added option to temporary enable/disble usermod from WLED UI (Info dialog)
* Added option to temporary enable/disable usermod from WLED UI (Info dialog)
2022-11
* Added compile time option for off timer.
* Added Home Assistant autodiscovery MQTT broadcast.
* Updated info on compiling.
2023-??
* Override option
* Domoticz virtual switch ID (used with MQTT `domoticz/in`)
2024-02
* Added compile time option to expand number of PIR sensors (they are logically ORed) `-D PIR_SENSOR_MAX_SENSORS=3`

View File

@ -15,6 +15,9 @@
#define PIR_SENSOR_OFF_SEC 600
#endif
#ifndef PIR_SENSOR_MAX_SENSORS
#define PIR_SENSOR_MAX_SENSORS 1
#endif
/*
* This usermod handles PIR sensor states.
@ -50,13 +53,13 @@ private:
volatile unsigned long offTimerStart = 0; // off timer start time
volatile bool PIRtriggered = false; // did PIR trigger?
bool sensorPinState = LOW; // current PIR sensor pin state
bool initDone = false; // status of initialization
unsigned long lastLoop = 0;
bool initDone = false; // status of initialization
unsigned long lastLoop = 0;
bool sensorPinState[PIR_SENSOR_MAX_SENSORS] = {LOW}; // current PIR sensor pin state
// configurable parameters
bool enabled = true; // PIR sensor enabled
int8_t PIRsensorPin = PIR_SENSOR_PIN; // PIR sensor pin
int8_t PIRsensorPin[PIR_SENSOR_MAX_SENSORS] = {PIR_SENSOR_PIN}; // PIR sensor pin
uint32_t m_switchOffDelay = PIR_SENSOR_OFF_SEC*1000; // delay before switch off after the sensor state goes LOW (10min)
uint8_t m_onPreset = 0; // on preset
uint8_t m_offPreset = 0; // off preset
@ -101,7 +104,7 @@ private:
/**
* Read and update PIR sensor state.
* Initilize/reset switch off timer
* Initialize/reset switch off timer
*/
bool updatePIRsensorState();
@ -309,8 +312,8 @@ void PIRsensorSwitch::publishHomeAssistantAutodiscovery()
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
device[F("name")] = serverDescription;
device[F("ids")] = String(F("wled-sensor-")) + mqttClientID;
device[F("mf")] = "WLED";
device[F("mdl")] = F("FOSS");
device[F("mf")] = F(WLED_BRAND);
device[F("mdl")] = F(WLED_PRODUCT_NAME);
device[F("sw")] = versionString;
sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid);
@ -325,21 +328,29 @@ void PIRsensorSwitch::publishHomeAssistantAutodiscovery()
bool PIRsensorSwitch::updatePIRsensorState()
{
bool pinState = digitalRead(PIRsensorPin);
if (pinState != sensorPinState) {
sensorPinState = pinState; // change previous state
bool stateChanged = false;
bool allOff = true;
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) {
if (PIRsensorPin[i] < 0) continue;
if (sensorPinState == HIGH) {
offTimerStart = 0;
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
} else {
// start switch off timer
offTimerStart = millis();
bool pinState = digitalRead(PIRsensorPin[i]);
if (pinState != sensorPinState[i]) {
sensorPinState[i] = pinState; // change previous state
stateChanged = true;
if (sensorPinState[i] == HIGH) {
offTimerStart = 0;
allOff = false;
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
}
}
publishMqtt(sensorPinState == HIGH);
return true;
}
return false;
if (stateChanged) {
publishMqtt(!allOff);
// start switch off timer
if (allOff) offTimerStart = millis();
}
return stateChanged;
}
bool PIRsensorSwitch::handleOffTimer()
@ -356,18 +367,21 @@ bool PIRsensorSwitch::handleOffTimer()
void PIRsensorSwitch::setup()
{
if (enabled) {
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) {
sensorPinState[i] = LOW;
if (PIRsensorPin[i] < 0) continue;
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
// PIR Sensor mode INPUT_PULLUP
pinMode(PIRsensorPin, INPUT_PULLUP);
sensorPinState = digitalRead(PIRsensorPin);
if (pinManager.allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) {
// PIR Sensor mode INPUT_PULLDOWN
#ifdef ESP8266
pinMode(PIRsensorPin[i], PIRsensorPin[i]==16 ? INPUT_PULLDOWN_16 : INPUT_PULLUP); // ESP8266 has INPUT_PULLDOWN on GPIO16 only
#else
pinMode(PIRsensorPin[i], INPUT_PULLDOWN);
#endif
sensorPinState[i] = digitalRead(PIRsensorPin[i]);
} else {
if (PIRsensorPin >= 0) {
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
}
PIRsensorPin = -1; // allocation failed
enabled = false;
DEBUG_PRINT(F("PIRSensorSwitch pin ")); DEBUG_PRINTLN(i); DEBUG_PRINTLN(F(" allocation failed."));
PIRsensorPin[i] = -1; // allocation failed
}
}
initDone = true;
@ -382,8 +396,8 @@ void PIRsensorSwitch::onMqttConnect(bool sessionPresent)
void PIRsensorSwitch::loop()
{
// only check sensors 4x/s
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
// only check sensors 5x/s
if (!enabled || millis() - lastLoop < 200) return;
lastLoop = millis();
if (!updatePIRsensorState()) {
@ -396,37 +410,35 @@ void PIRsensorSwitch::addToJsonInfo(JsonObject &root)
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
bool state = LOW;
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++)
if (PIRsensorPin[i] >= 0) state |= sensorPinState[i];
JsonArray infoArr = user.createNestedArray(FPSTR(_name));
String uiDomString;
if (enabled) {
if (offTimerStart > 0)
{
if (offTimerStart > 0) {
uiDomString = "";
unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000;
if (offSeconds >= 3600)
{
if (offSeconds >= 3600) {
uiDomString += (offSeconds / 3600);
uiDomString += F("h ");
offSeconds %= 3600;
}
if (offSeconds >= 60)
{
if (offSeconds >= 60) {
uiDomString += (offSeconds / 60);
offSeconds %= 60;
}
else if (uiDomString.length() > 0)
{
} else if (uiDomString.length() > 0) {
uiDomString += 0;
}
if (uiDomString.length() > 0)
{
if (uiDomString.length() > 0) {
uiDomString += F("min ");
}
uiDomString += (offSeconds);
infoArr.add(uiDomString + F("s"));
} else {
infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
infoArr.add(state ? F("sensor on") : F("inactive"));
}
} else {
infoArr.add(F("disabled"));
@ -446,9 +458,11 @@ void PIRsensorSwitch::addToJsonInfo(JsonObject &root)
uiDomString += F("</button>");
infoArr.add(uiDomString);
JsonObject sensor = root[F("sensor")];
if (sensor.isNull()) sensor = root.createNestedObject(F("sensor"));
sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false;
if (enabled) {
JsonObject sensor = root[F("sensor")];
if (sensor.isNull()) sensor = root.createNestedObject(F("sensor"));
sensor[F("motion")] = state || offTimerStart>0 ? true : false;
}
}
void PIRsensorSwitch::onStateChange(uint8_t mode) {
@ -478,7 +492,8 @@ void PIRsensorSwitch::addToConfig(JsonObject &root)
JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled;
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
top["pin"] = PIRsensorPin;
JsonArray pinArray = top.createNestedArray("pin");
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) pinArray.add(PIRsensorPin[i]);
top[FPSTR(_onPreset)] = m_onPreset;
top[FPSTR(_offPreset)] = m_offPreset;
top[FPSTR(_nightTime)] = m_nightTimeOnly;
@ -494,12 +509,20 @@ void PIRsensorSwitch::appendConfigData()
{
oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field
oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) {
char str[128];
sprintf_P(str, PSTR("addInfo('PIRsensorSwitch:pin[]',%d,'','#%d');"), i, i);
oappend(str);
}
}
bool PIRsensorSwitch::readFromConfig(JsonObject &root)
{
bool oldEnabled = enabled;
int8_t oldPin = PIRsensorPin;
int8_t oldPin[PIR_SENSOR_MAX_SENSORS];
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) {
oldPin[i] = PIRsensorPin[i];
PIRsensorPin[i] = -1;
}
DEBUG_PRINT(FPSTR(_name));
JsonObject top = root[FPSTR(_name)];
@ -508,7 +531,13 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root)
return false;
}
PIRsensorPin = top["pin"] | PIRsensorPin;
JsonArray pins = top["pin"];
if (!pins.isNull()) {
for (size_t i = 0; i < PIR_SENSOR_MAX_SENSORS; i++)
if (i < pins.size()) PIRsensorPin[i] = pins[i] | PIRsensorPin[i];
} else {
PIRsensorPin[0] = top["pin"] | oldPin[0];
}
enabled = top[FPSTR(_enabled)] | enabled;
@ -530,26 +559,11 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root)
// reading config prior to setup()
DEBUG_PRINTLN(F(" config loaded."));
} else {
if (oldPin != PIRsensorPin || oldEnabled != enabled) {
// check if pin is OK
if (oldPin != PIRsensorPin && oldPin >= 0) {
// if we are changing pin in settings page
// deallocate old pin
pinManager.deallocatePin(oldPin, PinOwner::UM_PIR);
if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
pinMode(PIRsensorPin, INPUT_PULLUP);
} else {
// allocation failed
PIRsensorPin = -1;
enabled = false;
}
}
if (enabled) {
sensorPinState = digitalRead(PIRsensorPin);
}
}
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++)
if (oldPin[i] >= 0) pinManager.deallocatePin(oldPin[i], PinOwner::UM_PIR);
setup();
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_domoticzIDX)].isNull();
return !(pins.isNull() || pins.size() != PIR_SENSOR_MAX_SENSORS);
}

View File

@ -5,7 +5,7 @@ v2 Usermod to to control PWM fan with RPM feedback and temperature control
This usermod requires the Dallas Temperature usermod to obtain temperature information. If it's not available, the fan will run at 100% speed.
If the fan does not have _tachometer_ (RPM) output you can set the _tachometer-pin_ to -1 to disable that feature.
You can also set the thershold temperature at which fan runs at lowest speed. If the measured temperature is 3°C greater than the threshold temperature, the fan will run at 100%.
You can also set the threshold temperature at which fan runs at lowest speed. If the measured temperature is 3°C greater than the threshold temperature, the fan will run at 100%.
If the _tachometer_ is supported, the current speed (in RPM) will be displayed on the WLED Info page.
@ -22,7 +22,7 @@ This includes:
* PWM output pin (can be configured at compile time `-D PWM_PIN=xx`)
* tachometer input pin (can be configured at compile time `-D TACHO_PIN=xx`)
* sampling frequency in seconds
* threshold temperature in degees C
* threshold temperature in degrees Celsius
_NOTE:_ You may also need to tweak Dallas Temperature usermod sampling frequency to match PWM fan sampling frequency.

View File

@ -43,12 +43,12 @@ void handleRelay()
digitalWrite(PIN_UP_RELAY, LOW);
upActiveBefore = true;
upStartTime = millis();
DEBUG_PRINTLN("UPA");
DEBUG_PRINTLN(F("UPA"));
}
if (millis()- upStartTime > PIN_ON_TIME)
{
upActive = false;
DEBUG_PRINTLN("UPN");
DEBUG_PRINTLN(F("UPN"));
}
} else if (upActiveBefore)
{

View File

@ -30,7 +30,7 @@
#define USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE 10000.0f
#endif
// only report if differance grater than offset value
// only report if difference grater than offset value
#ifndef USERMOD_SN_PHOTORESISTOR_OFFSET_VALUE
#define USERMOD_SN_PHOTORESISTOR_OFFSET_VALUE 5
#endif
@ -119,7 +119,7 @@ public:
}
else
{
DEBUG_PRINTLN("Missing MQTT connection. Not publishing data");
DEBUG_PRINTLN(F("Missing MQTT connection. Not publishing data"));
}
}
#endif

View File

@ -93,8 +93,8 @@ class Si7021_MQTT_HA : public Usermod
JsonObject device = doc.createNestedObject("device"); // attach the sensor to the same device
device["name"] = String(serverDescription);
device["model"] = "WLED";
device["manufacturer"] = "Aircoookie";
device["model"] = F(WLED_PRODUCT_NAME);
device["manufacturer"] = F(WLED_BRAND);
device["identifiers"] = String("wled-") + String(serverDescription);
device["sw_version"] = VERSION;

View File

@ -3,7 +3,7 @@
* This file allows you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
* EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h)
* bytes 2400+ are currently ununsed, but might be used for future wled features
* bytes 2400+ are currently unused, but might be used for future wled features
*/
/*
@ -144,7 +144,7 @@ void userLoop() {
// First row with Wifi name
tft.setCursor(1, 1);
tft.print(knownSsid.substring(0, tftcharwidth > 1 ? tftcharwidth - 1 : 0));
// Print `~` char to indicate that SSID is longer, than our dicplay
// Print `~` char to indicate that SSID is longer than our display
if (knownSsid.length() > tftcharwidth)
tft.print("~");

View File

@ -18,7 +18,7 @@ Copy the example `platformio_override.ini` to the root directory. This file sho
* `USERMOD_DALLASTEMPERATURE` - enables this user mod wled00/usermods_list.cpp
* `USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL` - number of milliseconds between measurements, defaults to 60000 ms (60s)
All parameters can be configured at runtime via the Usermods settings page, including pin, temperature in degrees Celsius or Farenheit and measurement interval.
All parameters can be configured at runtime via the Usermods settings page, including pin, temperature in degrees Celsius or Fahrenheit and measurement interval.
## Project link

View File

@ -57,7 +57,10 @@ class UsermodTemperature : public Usermod {
static const char _parasite[];
static const char _parasitePin[];
static const char _domoticzIDX[];
static const char _sensor[];
static const char _temperature[];
static const char _Temperature[];
//Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013
float readDallas();
void requestTemperatures();
@ -110,9 +113,9 @@ float UsermodTemperature::readDallas() {
#ifdef WLED_DEBUG
if (OneWire::crc8(data,8) != data[8]) {
DEBUG_PRINTLN(F("CRC error reading temperature."));
for (byte i=0; i < 9; i++) DEBUG_PRINTF("0x%02X ", data[i]);
for (byte i=0; i < 9; i++) DEBUG_PRINTF_P(PSTR("0x%02X "), data[i]);
DEBUG_PRINT(F(" => "));
DEBUG_PRINTF("0x%02X\n", OneWire::crc8(data,8));
DEBUG_PRINTF_P(PSTR("0x%02X\n"), OneWire::crc8(data,8));
}
#endif
switch(sensorFound) {
@ -149,7 +152,7 @@ void UsermodTemperature::readTemperature() {
temperature = readDallas();
lastMeasurement = millis();
waitingForConversion = false;
//DEBUG_PRINTF("Read temperature %2.1f.\n", temperature); // does not work properly on 8266
//DEBUG_PRINTF_P(PSTR("Read temperature %2.1f.\n"), temperature); // does not work properly on 8266
DEBUG_PRINT(F("Read temperature "));
DEBUG_PRINTLN(temperature);
}
@ -171,7 +174,7 @@ bool UsermodTemperature::findSensor() {
case 0x42: // DS28EA00
DEBUG_PRINTLN(F("Sensor found."));
sensorFound = deviceAddress[0];
DEBUG_PRINTF("0x%02X\n", sensorFound);
DEBUG_PRINTF_P(PSTR("0x%02X\n"), sensorFound);
return true;
}
}
@ -191,9 +194,9 @@ void UsermodTemperature::publishHomeAssistantAutodiscovery() {
sprintf_P(buf, PSTR("%s Temperature"), serverDescription);
json[F("name")] = buf;
strcpy(buf, mqttDeviceTopic);
strcat_P(buf, PSTR("/temperature"));
strcat_P(buf, _Temperature);
json[F("state_topic")] = buf;
json[F("device_class")] = F("temperature");
json[F("device_class")] = FPSTR(_temperature);
json[F("unique_id")] = escapedMac.c_str();
json[F("unit_of_measurement")] = F("°C");
payload_size = serializeJson(json, json_str);
@ -272,7 +275,7 @@ void UsermodTemperature::loop() {
// dont publish super low temperature as the graph will get messed up
// the DallasTemperature library returns -127C or -196.6F when problem
// reading the sensor
strcat_P(subuf, PSTR("/temperature"));
strcat_P(subuf, _Temperature);
mqtt->publish(subuf, 0, false, String(getTemperatureC()).c_str());
strcat_P(subuf, PSTR("_f"));
mqtt->publish(subuf, 0, false, String(getTemperatureF()).c_str());
@ -335,9 +338,9 @@ void UsermodTemperature::addToJsonInfo(JsonObject& root) {
temp.add(getTemperature());
temp.add(getTemperatureUnit());
JsonObject sensor = root[F("sensor")];
if (sensor.isNull()) sensor = root.createNestedObject(F("sensor"));
temp = sensor.createNestedArray(F("temperature"));
JsonObject sensor = root[FPSTR(_sensor)];
if (sensor.isNull()) sensor = root.createNestedObject(FPSTR(_sensor));
temp = sensor.createNestedArray(FPSTR(_temperature));
temp.add(getTemperature());
temp.add(getTemperatureUnit());
}
@ -367,7 +370,7 @@ void UsermodTemperature::addToConfig(JsonObject &root) {
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = enabled;
top["pin"] = temperaturePin; // usermodparam
top["degC"] = degC; // usermodparam
top[F("degC")] = degC; // usermodparam
top[FPSTR(_readInterval)] = readingInterval / 1000;
top[FPSTR(_parasite)] = parasite;
top[FPSTR(_parasitePin)] = parasitePin;
@ -393,7 +396,7 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) {
enabled = top[FPSTR(_enabled)] | enabled;
newTemperaturePin = top["pin"] | newTemperaturePin;
degC = top["degC"] | degC;
degC = top[F("degC")] | degC;
readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000;
readingInterval = min(120,max(10,(int)readingInterval)) * 1000; // convert to ms
parasite = top[FPSTR(_parasite)] | parasite;
@ -444,3 +447,6 @@ const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s";
const char UsermodTemperature::_parasite[] PROGMEM = "parasite-pwr";
const char UsermodTemperature::_parasitePin[] PROGMEM = "parasite-pwr-pin";
const char UsermodTemperature::_domoticzIDX[] PROGMEM = "domoticz-idx";
const char UsermodTemperature::_sensor[] PROGMEM = "sensor";
const char UsermodTemperature::_temperature[] PROGMEM = "temperature";
const char UsermodTemperature::_Temperature[] PROGMEM = "/temperature";

View File

@ -0,0 +1,117 @@
/******************************************************************************
* @file : gridbw.h
* @brief : contains the tetris grid as binary so black and white version
******************************************************************************
* @attention
*
* Copyright (c) muebau 2023
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __GRIDBW_H__
#define __GRIDBW_H__
#include <iterator>
#include <vector>
#include "pieces.h"
using namespace std;
class GridBW
{
private:
public:
uint8_t width;
uint8_t height;
std::vector<uint32_t> pixels;
GridBW(uint8_t width, uint8_t height):
width(width),
height(height),
pixels(height)
{
if (width > 32)
{
throw std::invalid_argument("maximal width is 32");
}
}
void placePiece(Piece* piece, uint8_t x, uint8_t y)
{
for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++)
{
pixels[y + (row - (4 - piece->getRotation().height))] |= piece->getGridRow(x, row, width);
}
}
void erasePiece(Piece* piece, uint8_t x, uint8_t y)
{
for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++)
{
pixels[y + (row - (4 - piece->getRotation().height))] &= ~piece->getGridRow(x, row, width);
}
}
bool noCollision(Piece* piece, uint8_t x, uint8_t y)
{
//if it touches a wall it is a collision
if (x > (this->width - piece->getRotation().width) || y > this->height - piece->getRotation().height)
{
return false;
}
for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++)
{
if (piece->getGridRow(x, row, width) & pixels[y + (row - (4 - piece->getRotation().height))])
{
return false;
}
}
return true;
}
void findLandingPosition(Piece* piece)
{
// move down until the piece bumps into some occupied pixels or the 'wall'
while (noCollision(piece, piece->x, piece->landingY))
{
piece->landingY++;
}
//at this point the positon is 'in the wall' or 'over some occupied pixel'
//so the previous position was the last correct one (clamped to 0 as minimum).
piece->landingY = piece->landingY > 0 ? piece->landingY - 1 : 0;
}
void cleanupFullLines()
{
uint8_t offset = 0;
//from "height - 1" to "0", so from bottom row to top
for (uint8_t row = height; row-- > 0; )
{
//full line?
if (isLineFull(row))
{
offset++;
pixels[row] = 0x0;
continue;
}
if (offset > 0)
{
pixels[row + offset] = pixels[row];
pixels[row] = 0x0;
}
}
}
bool isLineFull(uint8_t y)
{
return pixels[y] == (uint32_t)((1 << width) - 1);
}
};
#endif /* __GRIDBW_H__ */

View File

@ -0,0 +1,132 @@
/******************************************************************************
* @file : gridcolor.h
* @brief : contains the tetris grid as 8bit indexed color version
******************************************************************************
* @attention
*
* Copyright (c) muebau 2023
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __GRIDCOLOR_H__
#define __GRIDCOLOR_H__
#include <stdint.h>
#include <stdbool.h>
#include <vector>
#include "gridbw.h"
#include "gridcolor.h"
using namespace std;
class GridColor
{
private:
public:
uint8_t width;
uint8_t height;
GridBW gridBW;
std::vector<uint8_t> pixels;
GridColor(uint8_t width, uint8_t height):
width(width),
height(height),
gridBW(width, height),
pixels(width* height)
{}
void clear()
{
for (uint8_t y = 0; y < height; y++)
{
gridBW.pixels[y] = 0x0;
for (int8_t x = 0; x < width; x++)
{
*getPixel(x, y) = 0;
}
}
}
void placePiece(Piece* piece, uint8_t x, uint8_t y)
{
for (uint8_t pieceY = 0; pieceY < piece->getRotation().height; pieceY++)
{
for (uint8_t pieceX = 0; pieceX < piece->getRotation().width; pieceX++)
{
if (piece->getPixel(pieceX, pieceY))
{
*getPixel(x + pieceX, y + pieceY) = piece->pieceData->colorIndex;
}
}
}
}
void erasePiece(Piece* piece, uint8_t x, uint8_t y)
{
for (uint8_t pieceY = 0; pieceY < piece->getRotation().height; pieceY++)
{
for (uint8_t pieceX = 0; pieceX < piece->getRotation().width; pieceX++)
{
if (piece->getPixel(pieceX, pieceY))
{
*getPixel(x + pieceX, y + pieceY) = 0;
}
}
}
}
void cleanupFullLines()
{
uint8_t offset = 0;
//from "height - 1" to "0", so from bottom row to top
for (uint8_t y = height; y-- > 0; )
{
if (gridBW.isLineFull(y))
{
offset++;
for (uint8_t x = 0; x < width; x++)
{
pixels[y * width + x] = 0;
}
continue;
}
if (offset > 0)
{
if (gridBW.pixels[y])
{
for (uint8_t x = 0; x < width; x++)
{
pixels[(y + offset) * width + x] = pixels[y * width + x];
pixels[y * width + x] = 0;
}
}
}
}
gridBW.cleanupFullLines();
}
uint8_t* getPixel(uint8_t x, uint8_t y)
{
return &pixels[y * width + x];
}
void sync()
{
for (uint8_t y = 0; y < height; y++)
{
gridBW.pixels[y] = 0x0;
for (int8_t x = 0; x < width; x++)
{
gridBW.pixels[y] <<= 1;
if (*getPixel(x, y) != 0)
{
gridBW.pixels[y] |= 0x1;
}
}
}
}
};
#endif /* __GRIDCOLOR_H__ */

View File

@ -0,0 +1,184 @@
/******************************************************************************
* @file : pieces.h
* @brief : contains the tetris pieces with their colors indecies
******************************************************************************
* @attention
*
* Copyright (c) muebau 2022
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __PIECES_H__
#define __PIECES_H__
#include <stdint.h>
#include <stdbool.h>
#include <bitset>
#include <cstddef>
#include <cassert>
#include <iostream>
#define numPieces 7
struct PieceRotation
{
uint8_t width;
uint8_t height;
uint16_t rows;
};
struct PieceData
{
uint8_t rotCount;
uint8_t colorIndex;
PieceRotation rotations[4];
};
PieceData piecesData[numPieces] = {
// I
{
2,
1,
{
{ 1, 4, 0b0001000100010001},
{ 4, 1, 0b0000000000001111}
}
},
// O
{
1,
2,
{
{ 2, 2, 0b0000000000110011}
}
},
// Z
{
2,
3,
{
{ 3, 2, 0b0000000001100011},
{ 2, 3, 0b0000000100110010}
}
},
// S
{
2,
4,
{
{ 3, 2, 0b0000000000110110},
{ 2, 3, 0b0000001000110001}
}
},
// L
{
4,
5,
{
{ 2, 3, 0b0000001000100011},
{ 3, 2, 0b0000000001110100},
{ 2, 3, 0b0000001100010001},
{ 3, 2, 0b0000000000010111}
}
},
// J
{
4,
6,
{
{ 2, 3, 0b0000000100010011},
{ 3, 2, 0b0000000001000111},
{ 2, 3, 0b0000001100100010},
{ 3, 2, 0b0000000001110001}
}
},
// T
{
4,
7,
{
{ 3, 2, 0b0000000001110010},
{ 2, 3, 0b0000000100110001},
{ 3, 2, 0b0000000000100111},
{ 2, 3, 0b0000001000110010}
}
},
};
class Piece
{
private:
public:
uint8_t x;
uint8_t y;
PieceData* pieceData;
uint8_t rotation;
uint8_t landingY;
Piece(uint8_t pieceIndex = 0):
x(0),
y(0),
rotation(0),
landingY(0)
{
this->pieceData = &piecesData[pieceIndex];
}
void reset()
{
this->rotation = 0;
this->x = 0;
this->y = 0;
this->landingY = 0;
}
uint32_t getGridRow(uint8_t x, uint8_t y, uint8_t width)
{
if (x < width)
{
//shift the row with the "top-left" position to the "x" position
auto shiftx = (width - 1) - x;
auto topleftx = (getRotation().width - 1);
auto finalShift = shiftx - topleftx;
auto row = getRow(y);
auto finalResult = row << finalShift;
return finalResult;
}
return 0xffffffff;
}
uint8_t getRow(uint8_t y)
{
if (y < 4)
{
return (getRotation().rows >> (12 - (4 * y))) & 0xf;
}
return 0xf;
}
bool getPixel(uint8_t x, uint8_t y)
{
if(x > getRotation().width - 1 || y > getRotation().height - 1 )
{
return false;
}
if (x < 4 && y < 4)
{
return (getRow((4 - getRotation().height) + y) >> (3 - ((4 - getRotation().width) + x))) & 0x1;
}
return false;
}
PieceRotation getRotation()
{
return this->pieceData->rotations[rotation];
}
};
#endif /* __PIECES_H__ */

View File

@ -0,0 +1,64 @@
/******************************************************************************
* @file : rating.h
* @brief : contains the tetris rating of a grid
******************************************************************************
* @attention
*
* Copyright (c) muebau 2022
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __RATING_H__
#define __RATING_H__
#include <stdint.h>
#include <float.h>
#include <stdbool.h>
#include <math.h>
#include <vector>
#include "rating.h"
using namespace std;
class Rating
{
private:
public:
uint8_t minHeight;
uint8_t maxHeight;
uint16_t holes;
uint8_t fullLines;
uint16_t bumpiness;
uint16_t aggregatedHeight;
double score;
uint8_t width;
std::vector<uint8_t> lineHights;
Rating(uint8_t width):
width(width),
lineHights(width)
{
reset();
}
void reset()
{
this->minHeight = 0;
this->maxHeight = 0;
for (uint8_t line = 0; line < this->width; line++)
{
this->lineHights[line] = 0;
}
this->holes = 0;
this->fullLines = 0;
this->bumpiness = 0;
this->aggregatedHeight = 0;
this->score = -DBL_MAX;
}
};
#endif /* __RATING_H__ */

View File

@ -0,0 +1,33 @@
# Tetris AI effect usermod
This usermod brings you a effect brings a self playing Tetris game. The mod needs version 0.14 or above as it is based on matrix support. The effect was tested on an ESP32 with a WS2812B 16x16 matrix.
Version 1.0
## Installation
Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'.
## Usage
It is best to set the background color to black, the border color to light grey and the game over color (foreground) to dark grey.
### Sliders and boxes
#### Sliders
* speed: speed the game plays
* look ahead: how many pieces is the AI allowed to know the next pieces (0 - 2)
* intelligence: how good the AI will play
* Rotate color: make the colors shift (rotate) every few cicles
* Mistakes free: how many good moves between mistakes (if activated)
#### Checkboxes
* show next: if true a space of 5 pixels from the right is used to show the next pieces. The whole segment is used for the grid otherwise.
* show border: if true an additional column of 1 pixel is used to draw a border between the grid and the next pieces
* mistakes: if true the worst instead of the best move is choosen every few moves (read above)
## Best results
If the speed is set to be a little bit faster than a good human could play with maximal intelligence and very few mistakes it makes people furious/happy at a party.

View File

@ -0,0 +1,302 @@
/******************************************************************************
* @file : ai.h
* @brief : contains the heuristic
******************************************************************************
* @attention
*
* Copyright (c) muebau 2023
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __AI_H__
#define __AI_H__
#include "gridbw.h"
#include "rating.h"
using namespace std;
class TetrisAI
{
private:
public:
double aHeight;
double fullLines;
double holes;
double bumpiness;
bool findWorstMove = false;
uint8_t countOnes(uint32_t vector)
{
uint8_t count = 0;
while (vector)
{
vector &= (vector - 1);
count++;
}
return count;
}
void updateRating(GridBW grid, Rating* rating)
{
rating->minHeight = 0;
rating->maxHeight = 0;
rating->holes = 0;
rating->fullLines = 0;
rating->bumpiness = 0;
rating->aggregatedHeight = 0;
fill(rating->lineHights.begin(), rating->lineHights.end(), 0);
uint32_t columnvector = 0x0;
uint32_t lastcolumnvector = 0x0;
for (uint8_t row = 0; row < grid.height; row++)
{
columnvector |= grid.pixels[row];
//first (highest) column makes it
if (rating->maxHeight == 0 && columnvector)
{
rating->maxHeight = grid.height - row;
}
//if column vector is full we found the minimal height (or it stays zero)
if (rating->minHeight == 0 && (columnvector == (uint32_t)((1 << grid.width) - 1)))
{
rating->minHeight = grid.height - row;
}
//line full if all ones in mask :-)
if (grid.isLineFull(row))
{
rating->fullLines++;
}
//holes are basically a XOR with the "full" columns
rating->holes += countOnes(columnvector ^ grid.pixels[row]);
//calculate the difference (XOR) between the current column vector and the last one
uint32_t columnDelta = columnvector ^ lastcolumnvector;
//process every new column
uint8_t index = 0;
while (columnDelta)
{
//if this is a new column
if (columnDelta & 0x1)
{
//update hight of this column
rating->lineHights[(grid.width - 1) - index] = grid.height - row;
// update aggregatedHeight
rating->aggregatedHeight += grid.height - row;
}
index++;
columnDelta >>= 1;
}
lastcolumnvector = columnvector;
}
//compare every two columns to get the difference and add them up
for (uint8_t column = 1; column < grid.width; column++)
{
rating->bumpiness += abs(rating->lineHights[column - 1] - rating->lineHights[column]);
}
rating->score = (aHeight * (rating->aggregatedHeight)) + (fullLines * (rating->fullLines)) + (holes * (rating->holes)) + (bumpiness * (rating->bumpiness));
}
TetrisAI(): TetrisAI(-0.510066, 0.760666, -0.35663, -0.184483)
{}
TetrisAI(double aHeight, double fullLines, double holes, double bumpiness):
aHeight(aHeight),
fullLines(fullLines),
holes(holes),
bumpiness(bumpiness)
{}
void findBestMove(GridBW grid, Piece *piece)
{
vector<Piece> pieces = {*piece};
findBestMove(grid, &pieces);
*piece = pieces[0];
}
void findBestMove(GridBW grid, std::vector<Piece> *pieces)
{
findBestMove(grid, pieces->begin(), pieces->end());
}
void findBestMove(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end)
{
Rating bestRating(grid.width);
findBestMove(grid, start, end, &bestRating);
}
void findBestMove(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
{
grid.cleanupFullLines();
Rating curRating(grid.width);
Rating deeperRating(grid.width);
Piece piece = *start;
// for every rotation of the piece
for (piece.rotation = 0; piece.rotation < piece.pieceData->rotCount; piece.rotation++)
{
// put piece to top left corner
piece.x = 0;
piece.y = 0;
//test for every column
for (piece.x = 0; piece.x <= grid.width - piece.getRotation().width; piece.x++)
{
//todo optimise by the use of the previous grids height
piece.landingY = 0;
//will set landingY to final position
grid.findLandingPosition(&piece);
// draw piece
grid.placePiece(&piece, piece.x, piece.landingY);
if(start == end - 1)
{
//at the deepest level
updateRating(grid, &curRating);
}
else
{
//go deeper to take another piece into account
findBestMove(grid, start + 1, end, &deeperRating);
curRating = deeperRating;
}
// eraese piece
grid.erasePiece(&piece, piece.x, piece.landingY);
if(findWorstMove)
{
//init rating for worst
if(bestRating->score == -DBL_MAX)
{
bestRating->score = DBL_MAX;
}
// update if we found a worse one
if (bestRating->score > curRating.score)
{
*bestRating = curRating;
(*start) = piece;
}
}
else
{
// update if we found a better one
if (bestRating->score < curRating.score)
{
*bestRating = curRating;
(*start) = piece;
}
}
}
}
}
bool findBestMoveNonBlocking(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
{
//vector with pieces
//for every piece
//for every
switch (expression)
{
case INIT:
break;
default:
break;
}
}
bool findBestMoveNonBlocking(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
{
//INIT
grid.cleanupFullLines();
Rating curRating(grid.width);
Rating deeperRating(grid.width);
Piece piece = *start;
// for every rotation of the piece
piece.rotation = 0;
//HANDLE
while (piece.rotation < piece.pieceData->rotCount)
{
// put piece to top left corner
piece.x = 0;
piece.y = 0;
//test for every column
piece.x = 0;
while (piece.x <= grid.width - piece.getRotation().width)
{
//todo optimise by the use of the previous grids height
piece.landingY = 0;
//will set landingY to final position
grid.findLandingPosition(&piece);
// draw piece
grid.placePiece(&piece, piece.x, piece.landingY);
if(start == end - 1)
{
//at the deepest level
updateRating(grid, &curRating);
}
else
{
//go deeper to take another piece into account
findBestMove(grid, start + 1, end, &deeperRating);
curRating = deeperRating;
}
// eraese piece
grid.erasePiece(&piece, piece.x, piece.landingY);
if(findWorstMove)
{
//init rating for worst
if(bestRating->score == -DBL_MAX)
{
bestRating->score = DBL_MAX;
}
// update if we found a worse one
if (bestRating->score > curRating.score)
{
*bestRating = curRating;
(*start) = piece;
}
}
else
{
// update if we found a better one
if (bestRating->score < curRating.score)
{
*bestRating = curRating;
(*start) = piece;
}
}
piece.x++;
}
piece.rotation++;
}
//EXIT
return true;
}
};
#endif /* __AI_H__ */

View File

@ -0,0 +1,150 @@
/******************************************************************************
* @file : tetrisaigame.h
* @brief : main tetris functions
******************************************************************************
* @attention
*
* Copyright (c) muebau 2022
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __TETRISAIGAME_H__
#define __TETRISAIGAME_H__
#include <stdint.h>
#include <stdbool.h>
#include <vector>
#include "pieces.h"
#include "gridcolor.h"
#include "tetrisbag.h"
#include "tetrisai.h"
using namespace std;
class TetrisAIGame
{
private:
bool animateFallOfPiece(Piece* piece, bool skip)
{
if (skip || piece->y >= piece->landingY)
{
piece->y = piece->landingY;
grid.gridBW.placePiece(piece, piece->x, piece->landingY);
grid.placePiece(piece, piece->x, piece->y);
return false;
}
else
{
// eraese last drawing
grid.erasePiece(piece, piece->x, piece->y);
//move piece down
piece->y++;
// draw piece
grid.placePiece(piece, piece->x, piece->y);
return true;
}
}
public:
uint8_t width;
uint8_t height;
uint8_t nLookAhead;
TetrisBag bag;
GridColor grid;
TetrisAI ai;
Piece curPiece;
PieceData* piecesData;
enum States { INIT, TEST_GAME_OVER, GET_NEXT_PIECE, FIND_BEST_MOVE, ANIMATE_MOVE, ANIMATE_GAME_OVER } state = INIT;
TetrisAIGame(uint8_t width, uint8_t height, uint8_t nLookAhead, PieceData* piecesData, uint8_t nPieces):
width(width),
height(height),
nLookAhead(nLookAhead),
bag(nPieces, 1, nLookAhead),
grid(width, height + 4),
ai(),
piecesData(piecesData)
{
}
void nextPiece()
{
grid.cleanupFullLines();
bag.queuePiece();
}
void findBestMove()
{
ai.findBestMove(grid.gridBW, &bag.piecesQueue);
}
bool animateFall(bool skip)
{
return animateFallOfPiece(&(bag.piecesQueue[0]), skip);
}
bool isGameOver()
{
//if there is something in the 4 lines of the hidden area the game is over
return grid.gridBW.pixels[0] || grid.gridBW.pixels[1] || grid.gridBW.pixels[2] || grid.gridBW.pixels[3];
}
void poll()
{
switch (state)
{
case INIT:
reset();
state = TEST_GAME_OVER;
break;
case TEST_GAME_OVER:
if (isGameOver())
{
state = ANIMATE_GAME_OVER;
}
else
{
state = GET_NEXT_PIECE;
}
break;
case GET_NEXT_PIECE:
nextPiece();
state = FIND_BEST_MOVE;
break;
case FIND_BEST_MOVE:
findBestMove();
state = ANIMATE_MOVE;
break;
case ANIMATE_MOVE:
if (!animateFall(false))
{
state = TEST_GAME_OVER;
}
break;
case ANIMATE_GAME_OVER:
static auto curPixel = grid.pixels.size();
grid.pixels[curPixel] = 254;
if (curPixel == 0)
{
state = INIT;
curPixel = grid.pixels.size();
}
curPixel--;
break;
}
}
void reset()
{
grid.clear();
bag.init();
}
};
#endif /* __TETRISAIGAME_H__ */

View File

@ -0,0 +1,100 @@
/******************************************************************************
* @file : tetrisbag.h
* @brief : the tetris implementation of a random piece generator
******************************************************************************
* @attention
*
* Copyright (c) muebau 2022
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __TETRISBAG_H__
#define __TETRISBAG_H__
#include <stdint.h>
#include <vector>
#include <algorithm>
#include "tetrisbag.h"
class TetrisBag
{
private:
public:
uint8_t nPieces;
uint8_t nBagLength;
uint8_t bagIdx;
std::vector<uint8_t> bag;
std::vector<Piece> piecesQueue;
TetrisBag(uint8_t nPieces, uint8_t nBagLength, uint8_t queueLength):
nPieces(nPieces),
nBagLength(nBagLength),
bag(nPieces * nBagLength),
piecesQueue(queueLength)
{
init();
}
void init()
{
//will shuffle the bag at first use
bagIdx = nPieces - 1;
for (uint8_t bagIndex = 0; bagIndex < nPieces * nBagLength; bagIndex++)
{
bag[bagIndex] = bagIndex % nPieces;
}
//will init the queue
for (uint8_t index = 0; index < piecesQueue.size(); index++)
{
queuePiece();
}
}
void shuffleBag()
{
uint8_t temp;
uint8_t swapIdx;
for (int index = nPieces - 1; index > 0; index--)
{
//get candidate to swap
swapIdx = rand() % index;
//swap it!
temp = bag[swapIdx];
bag[swapIdx] = bag[index];
bag[index] = temp;
}
}
Piece getNextPiece()
{
bagIdx++;
if (bagIdx >= nPieces)
{
shuffleBag();
bagIdx = 0;
}
return Piece(bag[bagIdx]);
}
void queuePiece()
{
//move vector to left
std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end());
piecesQueue[piecesQueue.size() - 1] = getNextPiece();
}
void queuePiece(uint8_t idx)
{
//move vector to left
std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end());
piecesQueue[piecesQueue.size() - 1] = Piece(idx % nPieces);
}
};
#endif /* __TETRISBAG_H__ */

View File

@ -0,0 +1,222 @@
#pragma once
#include "wled.h"
#include "FX.h"
#include "fcn_declare.h"
#include "tetrisaigame.h"
// By: muebau
typedef struct TetrisAI_data
{
unsigned long lastTime = 0;
TetrisAIGame tetris;
uint8_t intelligence;
uint8_t rotate;
bool showNext;
bool showBorder;
uint8_t colorOffset;
uint8_t colorInc;
uint8_t mistaceCountdown;
} tetrisai_data;
void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
{
SEGMENT.fill(SEGCOLOR(1));
//GRID
for (auto index_y = 4; index_y < tetris->grid.height; index_y++)
{
for (auto index_x = 0; index_x < tetris->grid.width; index_x++)
{
CRGB color;
if (*tetris->grid.getPixel(index_x, index_y) == 0)
{
//BG color
color = SEGCOLOR(1);
}
//game over animation
else if(*tetris->grid.getPixel(index_x, index_y) == 254)
{
//use fg
color = SEGCOLOR(0);
}
else
{
//spread the color over the whole palette
uint8_t colorIndex = *tetris->grid.getPixel(index_x, index_y) * 32;
colorIndex += tetrisai_data->colorOffset;
color = ColorFromPalette(SEGPALETTE, colorIndex, 255, NOBLEND);
}
SEGMENT.setPixelColorXY(index_x, index_y - 4, color);
}
}
tetrisai_data->colorOffset += tetrisai_data->colorInc;
//NEXT PIECE AREA
if (tetrisai_data->showNext)
{
//BORDER
if (tetrisai_data->showBorder)
{
//draw a line 6 pixels from right with the border color
for (auto index_y = 0; index_y < SEGMENT.virtualHeight(); index_y++)
{
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() - 6, index_y, SEGCOLOR(2));
}
}
//NEXT PIECE
int piecesOffsetX = SEGMENT.virtualWidth() - 4;
int piecesOffsetY = 1;
for (uint8_t nextPieceIdx = 1; nextPieceIdx < tetris->nLookAhead; nextPieceIdx++)
{
uint8_t pieceNbrOffsetY = (nextPieceIdx - 1) * 5;
Piece piece(tetris->bag.piecesQueue[nextPieceIdx]);
for (uint8_t pieceY = 0; pieceY < piece.getRotation().height; pieceY++)
{
for (uint8_t pieceX = 0; pieceX < piece.getRotation().width; pieceX++)
{
if (piece.getPixel(pieceX, pieceY))
{
uint8_t colIdx = ((piece.pieceData->colorIndex * 32) + tetrisai_data->colorOffset);
SEGMENT.setPixelColorXY(piecesOffsetX + pieceX, piecesOffsetY + pieceNbrOffsetY + pieceY, ColorFromPalette(SEGPALETTE, colIdx, 255, NOBLEND));
}
}
}
}
}
}
////////////////////////////
// 2D Tetris AI //
////////////////////////////
uint16_t mode_2DTetrisAI()
{
if (!strip.isMatrix || !SEGENV.allocateData(sizeof(tetrisai_data)))
{
// not a 2D set-up
SEGMENT.fill(SEGCOLOR(0));
return 350;
}
TetrisAI_data* tetrisai_data = reinterpret_cast<TetrisAI_data*>(SEGENV.data);
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
//range 0 - 1024ms => 1024/255 ~ 4
uint16_t msDelayMove = 1024 - (4 * SEGMENT.speed);
int16_t msDelayGameOver = msDelayMove / 4;
//range 0 - 2 (not including current)
uint8_t nLookAhead = SEGMENT.intensity ? (SEGMENT.intensity >> 7) + 2 : 1;
//range 0 - 16
tetrisai_data->colorInc = SEGMENT.custom2 >> 4;
if (!tetrisai_data->tetris || (tetrisai_data->tetris.nLookAhead != nLookAhead
|| tetrisai_data->showNext != SEGMENT.check1
|| tetrisai_data->showBorder != SEGMENT.check2
)
)
{
tetrisai_data->showNext = SEGMENT.check1;
tetrisai_data->showBorder = SEGMENT.check2;
//not more than 32 as this is the limit of this implementation
uint8_t gridWidth = cols < 32 ? cols : 32;
uint8_t gridHeight = rows;
// do we need space for the 'next' section?
if (tetrisai_data->showNext)
{
// make space for the piece and one pixel of space
gridWidth = gridWidth - 5;
// do we need space for a border?
if (tetrisai_data->showBorder)
{
gridWidth = gridWidth - 1;
}
}
tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces);
SEGMENT.fill(SEGCOLOR(1));
}
if (tetrisai_data->intelligence != SEGMENT.custom1)
{
tetrisai_data->intelligence = SEGMENT.custom1;
double dui = 0.2 - (0.2 * (tetrisai_data->intelligence / 255.0));
tetrisai_data->tetris.ai.aHeight = -0.510066 + dui;
tetrisai_data->tetris.ai.fullLines = 0.760666 - dui;
tetrisai_data->tetris.ai.holes = -0.35663 + dui;
tetrisai_data->tetris.ai.bumpiness = -0.184483 + dui;
}
if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE)
{
if (millis() - tetrisai_data->lastTime > msDelayMove)
{
drawGrid(&tetrisai_data->tetris, tetrisai_data);
tetrisai_data->lastTime = millis();
tetrisai_data->tetris.poll();
}
}
else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_GAME_OVER)
{
if (millis() - tetrisai_data->lastTime > msDelayGameOver)
{
drawGrid(&tetrisai_data->tetris, tetrisai_data);
tetrisai_data->lastTime = millis();
tetrisai_data->tetris.poll();
}
}
else if (tetrisai_data->tetris.state == TetrisAIGame::FIND_BEST_MOVE)
{
if (SEGMENT.check3)
{
if(tetrisai_data->mistaceCountdown == 0)
{
tetrisai_data->tetris.ai.findWorstMove = true;
tetrisai_data->tetris.poll();
tetrisai_data->tetris.ai.findWorstMove = false;
tetrisai_data->mistaceCountdown = SEGMENT.custom3;
}
tetrisai_data->mistaceCountdown--;
}
tetrisai_data->tetris.poll();
}
else
{
tetrisai_data->tetris.poll();
}
return FRAMETIME;
} // mode_2DTetrisAI()
static const char _data_FX_MODE_2DTETRISAI[] PROGMEM = "Tetris AI@!,Look ahead,Intelligence,Rotate color,Mistake free,Show next,Border,Mistakes;Game Over,!,Border;!;2;sx=127,ix=64,c1=255,c2=0,c3=31,o1=1,o2=1,o3=0,pal=11";
class TetrisAIUsermod : public Usermod
{
private:
public:
void setup()
{
strip.addEffect(255, &mode_2DTetrisAI, _data_FX_MODE_2DTETRISAI);
}
void loop()
{
}
uint16_t getId()
{
return USERMOD_ID_TETRISAI;
}
};

View File

@ -34,30 +34,30 @@ uint8_t DALLAS_PIN =23;
uint8_t SCL_PIN = 5;
uint8_t SDA_PIN = 4;
uint8_t DALLAS_PIN =13;
// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
// uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8
#endif
//The SCL and SDA pins are defined here.
//ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21
#define U8X8_PIN_SCL SCL_PIN
#define U8X8_PIN_SDA SDA_PIN
//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8
//#define U8X8_PIN_RESET RST_PIN // Un-comment for Heltec WiFi-Kit-8
// Dallas sensor reading timer
long temptimer = millis();
long lastMeasure = 0;
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit
// If display does not work or looks corrupted check the
// constructor reference:
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
// or check the gallery:
// https://github.com/olikraus/u8g2/wiki/gallery
// --> First choise of cheap I2C OLED 128X32 0.91"
// --> First choice of cheap I2C OLED 128X32 0.91"
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3"
// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3"
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91"
// --> Third choice of Heltec WiFi-Kit-8 OLED 128X32 0.91"
//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8
// gets called once at boot. Do all initialization that doesn't depend on network here
void userSetup() {
@ -97,7 +97,7 @@ void userLoop() {
//----> Dallas temperature sensor MQTT publishing
temptimer = millis();
// Timer to publishe new temperature every 60 seconds
// Timer to publish new temperature every 60 seconds
if (temptimer - lastMeasure > 60000)
{
lastMeasure = temptimer;
@ -106,7 +106,7 @@ void userLoop() {
if (mqtt != nullptr)
{
// Serial.println(Dallas(DALLAS_PIN,0));
//Gets prefered temperature scale based on selection in definitions section
//Gets preferred temperature scale based on selection in definitions section
#ifdef Celsius
int16_t board_temperature = Dallas(DALLAS_PIN,0);
#else
@ -173,11 +173,11 @@ void userLoop() {
// First row with Wifi name
u8x8.setCursor(1, 0);
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
// Print `~` char to indicate that SSID is longer, than owr dicplay
// Print `~` char to indicate that SSID is longer than our display
if (knownSsid.length() > u8x8.getCols())
u8x8.print("~");
// Second row with IP or Psssword
// Second row with IP or Password
u8x8.setCursor(1, 1);
// Print password in AP mode and if led is OFF.
if (apActive && bri == 0)

View File

@ -6,7 +6,7 @@
void UpdateBME280Data();
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit
BME280I2C bme; // Default : forced mode, standby time = 1000 ms
// Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off,
@ -16,25 +16,25 @@ 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
// uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8
#endif
//The SCL and SDA pins are defined here.
//ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21
#define U8X8_PIN_SCL SCL_PIN
#define U8X8_PIN_SDA SDA_PIN
//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8
//#define U8X8_PIN_RESET RST_PIN // Un-comment for Heltec WiFi-Kit-8
// If display does not work or looks corrupted check the
// constructor reference:
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
// or check the gallery:
// https://github.com/olikraus/u8g2/wiki/gallery
// --> First choise of cheap I2C OLED 128X32 0.91"
// --> First choice of cheap I2C OLED 128X32 0.91"
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3"
// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3"
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91"
// --> Third choice of Heltec WiFi-Kit-8 OLED 128X32 0.91"
//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8
// gets called once at boot. Do all initialization that doesn't depend on network here
@ -179,11 +179,11 @@ void userLoop() {
// First row with Wifi name
u8x8.setCursor(1, 0);
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
// Print `~` char to indicate that SSID is longer, than owr dicplay
// Print `~` char to indicate that SSID is longer, than our display
if (knownSsid.length() > u8x8.getCols())
u8x8.print("~");
// Second row with IP or Psssword
// Second row with IP or Password
u8x8.setCursor(1, 1);
// Print password in AP mode and if led is OFF.
if (apActive && bri == 0)

View File

@ -4,6 +4,10 @@
#include <driver/i2s.h>
#include <driver/adc.h>
#ifdef WLED_ENABLE_DMX
#error This audio reactive usermod is not compatible with DMX Out.
#endif
#ifndef ARDUINO_ARCH_ESP32
#error This audio reactive usermod does not support the ESP8266.
#endif
@ -83,7 +87,7 @@ static bool limiterOn = true;
static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec
static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec
// user settable options for FFTResult scaling
static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized sqare root
static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root
//
// AGC presets
@ -118,9 +122,9 @@ static float sampleAgc = 0.0f; // Smoothed AGC sample
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay()
static uint8_t maxVol = 31; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated)
static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated)
static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData
static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData
static unsigned long timeOfPeak = 0; // time of last sample peak detection.
static void detectSamplePeak(void); // peak detection function (needs scaled FFT reasults in vReal[])
static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[])
static void autoResetPeak(void); // peak auto-reset function
@ -179,31 +183,20 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT resul
// These are the input and output vectors. Input vectors receive computed results from FFT.
static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins
static float vImag[samplesFFT] = {0.0f}; // imaginary parts
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
static float windowWeighingFactors[samplesFFT] = {0.0f};
#endif
// Create FFT object
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2
// these options actually cause slow-downs on all esp32 processors, don't use them.
// #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32
// #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32
// Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt()
#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32
#define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83
#else
// around 40% slower on -S2
// lib_deps += https://github.com/blazoncek/arduinoFFT.git
#endif
// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2
// these options actually cause slow-downs on all esp32 processors, don't use them.
// #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32
// #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32
// Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt()
#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32
#define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83
#include <arduinoFFT.h>
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
static ArduinoFFT<float> FFT = ArduinoFFT<float>( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors);
#else
static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE);
#endif
// Helper functions
@ -212,7 +205,7 @@ static float mapf(float x, float in_min, float in_max, float out_min, float out_
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
// compute average of several FFT resut bins
// compute average of several FFT result bins
static float fftAddAvg(int from, int to) {
float result = 0.0f;
for (int i = from; i <= to; i++) {
@ -284,28 +277,13 @@ void FFTcode(void * parameter)
#endif
// run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2)
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
FFT.dcRemoval(); // remove DC offset
FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy
//FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection
FFT.compute( FFTDirection::Forward ); // Compute FFT
FFT.complexToMagnitude(); // Compute magnitudes
#else
FFT.DCRemoval(); // let FFT lib remove DC component, so we don't need to care about this in getSamples()
//FFT.Windowing( FFT_WIN_TYP_HAMMING, FFT_FORWARD ); // Weigh data - standard Hamming window
//FFT.Windowing( FFT_WIN_TYP_BLACKMAN, FFT_FORWARD ); // Blackman window - better side freq rejection
//FFT.Windowing( FFT_WIN_TYP_BLACKMAN_HARRIS, FFT_FORWARD );// Blackman-Harris - excellent sideband rejection
FFT.Windowing( FFT_WIN_TYP_FLT_TOP, FFT_FORWARD ); // Flat Top Window - better amplitude accuracy
FFT.Compute( FFT_FORWARD ); // Compute FFT
FFT.ComplexToMagnitude(); // Compute magnitudes
#endif
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant
#else
FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant
#endif
FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant
FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
#if defined(WLED_DEBUG) || defined(SR_DEBUG)
@ -330,7 +308,7 @@ void FFTcode(void * parameter)
*
* Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap.
* Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255.
* Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then detetermine the bins.
* Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then determine the bins.
* End frequency = Start frequency * multiplier ^ 16
* Multiplier = (End frequency/ Start frequency) ^ 1/16
* Multiplier = 1.320367784
@ -389,7 +367,7 @@ void FFTcode(void * parameter)
}
}
// post-processing of frequency channels (pink noise adjustment, AGC, smooting, scaling)
// post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling)
postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS);
#if defined(WLED_DEBUG) || defined(SR_DEBUG)
@ -436,7 +414,7 @@ static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // p
// FIR lowpass, to remove high frequency noise
float highFilteredSample;
if (i < (numSamples-1)) highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*sampleBuffer[i+1]; // smooth out spikes
else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // spcial handling for last sample in array
else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // special handling for last sample in array
last_vals[1] = last_vals[0];
last_vals[0] = sampleBuffer[i];
sampleBuffer[i] = highFilteredSample;
@ -640,7 +618,7 @@ class AudioReactive : public Usermod {
// variables used by getSample() and agcAvg()
int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed
double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controler.
double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controller.
double micLev = 0.0; // Used to convert returned value to have '0' as minimum. A leveller
float expAdjF = 0.0f; // Used for exponential filter.
float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC.
@ -662,6 +640,9 @@ class AudioReactive : public Usermod {
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
static const char _config[];
static const char _dynamics[];
static const char _frequency[];
static const char _inputLvl[];
static const char _analogmic[];
static const char _digitalmic[];
@ -763,13 +744,13 @@ class AudioReactive : public Usermod {
* 2. we use two setpoints, one at ~60%, and one at ~80% of the maximum signal
* 3. the amplification depends on signal level:
* a) normal zone - very slow adjustment
* b) emergency zome (<10% or >90%) - very fast adjustment
* b) emergency zone (<10% or >90%) - very fast adjustment
*/
void agcAvg(unsigned long the_time)
{
const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function
float lastMultAgc = multAgc; // last muliplier used
float lastMultAgc = multAgc; // last multiplier used
float multAgcTemp = multAgc; // new multiplier
float tmpAgc = sampleReal * multAgc; // what-if amplified signal
@ -809,13 +790,13 @@ class AudioReactive : public Usermod {
if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping
&& (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max)
control_integrated += control_error * 0.002 * 0.25; // 2ms = intgration time; 0.25 for damping
control_integrated += control_error * 0.002 * 0.25; // 2ms = integration time; 0.25 for damping
else
control_integrated *= 0.9; // spin down that beasty integrator
// apply PI Control
tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain
if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower emergy zone
if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower energy zone
multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error;
multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated;
} else { // "normal zone"
@ -823,7 +804,7 @@ class AudioReactive : public Usermod {
multAgcTemp += agcFollowSlow[AGC_preset] * agcControlKi[AGC_preset] * control_integrated;
}
// limit amplification again - PI controler sometimes "overshoots"
// limit amplification again - PI controller sometimes "overshoots"
//multAgcTemp = constrain(multAgcTemp, 0.015625f, 32.0f); // 1/64 < multAgcTemp < 32
if (multAgcTemp > 32.0f) multAgcTemp = 32.0f;
if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f;
@ -853,7 +834,7 @@ class AudioReactive : public Usermod {
void getSample()
{
float sampleAdj; // Gain adjusted sample value
float tmpSample; // An interim sample variable used for calculatioins.
float tmpSample; // An interim sample variable used for calculations.
const float weighting = 0.2f; // Exponential filter weighting. Will be adjustable in a future release.
const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function
@ -1275,16 +1256,16 @@ class AudioReactive : public Usermod {
{
#ifdef WLED_DEBUG
if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled"
DEBUG_PRINTLN("[AR userLoop] realtime mode active - audio processing suspended.");
DEBUG_PRINTF( " RealtimeMode = %d; RealtimeOverride = %d\n", int(realtimeMode), int(realtimeOverride));
DEBUG_PRINTLN(F("[AR userLoop] realtime mode active - audio processing suspended."));
DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride));
}
#endif
disableSoundProcessing = true;
} else {
#ifdef WLED_DEBUG
if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled"
DEBUG_PRINTLN("[AR userLoop] realtime mode ended - audio processing resumed.");
DEBUG_PRINTF( " RealtimeMode = %d; RealtimeOverride = %d\n", int(realtimeMode), int(realtimeOverride));
DEBUG_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed."));
DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride));
}
#endif
if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping
@ -1308,7 +1289,7 @@ class AudioReactive : public Usermod {
// complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second.
// softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS
//if ((userloopDelay > 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) {
//DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay);
// DEBUG_PRINTF_P(PSTR("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n"), userloopDelay);
//}
#endif
@ -1525,7 +1506,7 @@ class AudioReactive : public Usermod {
} else {
// Analog or I2S digital input
if (audioSource && (audioSource->isInitialized())) {
// audio source sucessfully configured
// audio source successfully configured
if (audioSource->getType() == AudioSource::Type_I2SAdc) {
infoArr.add(F("ADC analog"));
} else {
@ -1703,29 +1684,29 @@ class AudioReactive : public Usermod {
#endif
JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic));
dmic[F("type")] = dmType;
dmic["type"] = dmType;
JsonArray pinArray = dmic.createNestedArray("pin");
pinArray.add(i2ssdPin);
pinArray.add(i2swsPin);
pinArray.add(i2sckPin);
pinArray.add(mclkPin);
JsonObject cfg = top.createNestedObject("config");
JsonObject cfg = top.createNestedObject(FPSTR(_config));
cfg[F("squelch")] = soundSquelch;
cfg[F("gain")] = sampleGain;
cfg[F("AGC")] = soundAgc;
JsonObject dynLim = top.createNestedObject("dynamics");
JsonObject dynLim = top.createNestedObject(FPSTR(_dynamics));
dynLim[F("limiter")] = limiterOn;
dynLim[F("rise")] = attackTime;
dynLim[F("fall")] = decayTime;
JsonObject freqScale = top.createNestedObject("frequency");
JsonObject freqScale = top.createNestedObject(FPSTR(_frequency));
freqScale[F("scale")] = FFTScalingMode;
JsonObject sync = top.createNestedObject("sync");
sync[F("port")] = audioSyncPort;
sync[F("mode")] = audioSyncEnabled;
sync["port"] = audioSyncPort;
sync["mode"] = audioSyncEnabled;
}
@ -1773,18 +1754,18 @@ class AudioReactive : public Usermod {
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin);
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin);
configComplete &= getJsonValue(top["config"][F("squelch")], soundSquelch);
configComplete &= getJsonValue(top["config"][F("gain")], sampleGain);
configComplete &= getJsonValue(top["config"][F("AGC")], soundAgc);
configComplete &= getJsonValue(top[FPSTR(_config)][F("squelch")], soundSquelch);
configComplete &= getJsonValue(top[FPSTR(_config)][F("gain")], sampleGain);
configComplete &= getJsonValue(top[FPSTR(_config)][F("AGC")], soundAgc);
configComplete &= getJsonValue(top["dynamics"][F("limiter")], limiterOn);
configComplete &= getJsonValue(top["dynamics"][F("rise")], attackTime);
configComplete &= getJsonValue(top["dynamics"][F("fall")], decayTime);
configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("limiter")], limiterOn);
configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("rise")], attackTime);
configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("fall")], decayTime);
configComplete &= getJsonValue(top["frequency"][F("scale")], FFTScalingMode);
configComplete &= getJsonValue(top[FPSTR(_frequency)][F("scale")], FFTScalingMode);
configComplete &= getJsonValue(top["sync"][F("port")], audioSyncPort);
configComplete &= getJsonValue(top["sync"][F("mode")], audioSyncEnabled);
configComplete &= getJsonValue(top["sync"]["port"], audioSyncPort);
configComplete &= getJsonValue(top["sync"]["mode"], audioSyncEnabled);
if (initDone) {
// add/remove custom/audioreactive palettes
@ -1955,6 +1936,9 @@ void AudioReactive::fillAudioPalettes() {
// strings to reduce flash memory usage (used more than twice)
const char AudioReactive::_name[] PROGMEM = "AudioReactive";
const char AudioReactive::_enabled[] PROGMEM = "enabled";
const char AudioReactive::_config[] PROGMEM = "config";
const char AudioReactive::_dynamics[] PROGMEM = "dynamics";
const char AudioReactive::_frequency[] PROGMEM = "frequency";
const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel";
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
const char AudioReactive::_analogmic[] PROGMEM = "analogmic";

View File

@ -44,7 +44,7 @@
// benefit: analog mic inputs will be sampled contiously -> better response times and less "glitches"
// WARNING: this option WILL lock-up your device in case that any other analogRead() operation is performed;
// for example if you want to read "analog buttons"
//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continously sample analog ADC microphone. WARNING will cause analogRead() lock-up
//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continuously sample analog ADC microphone. WARNING will cause analogRead() lock-up
// data type requested from the I2S driver - currently we always use 32bit
//#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible
@ -192,7 +192,7 @@ class I2SSource : public AudioSource {
}
virtual void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE, int8_t mclkPin = I2S_PIN_NO_CHANGE) {
DEBUGSR_PRINTLN("I2SSource:: initialize().");
DEBUGSR_PRINTLN(F("I2SSource:: initialize()."));
if (i2swsPin != I2S_PIN_NO_CHANGE && i2ssdPin != I2S_PIN_NO_CHANGE) {
if (!pinManager.allocatePin(i2swsPin, true, PinOwner::UM_Audioreactive) ||
!pinManager.allocatePin(i2ssdPin, false, PinOwner::UM_Audioreactive)) { // #206
@ -378,7 +378,7 @@ class I2SSource : public AudioSource {
};
/* ES7243 Microphone
This is an I2S microphone that requires ininitialization over
This is an I2S microphone that requires initialization over
I2C before I2S data can be received
*/
class ES7243 : public I2SSource {
@ -413,7 +413,7 @@ public:
};
void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) {
DEBUGSR_PRINTLN("ES7243:: initialize();");
DEBUGSR_PRINTLN(F("ES7243:: initialize();"));
if ((i2sckPin < 0) || (mclkPin < 0)) {
DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin);
return;
@ -429,8 +429,8 @@ public:
}
};
/* ES8388 Sound Modude
This is an I2S sound processing unit that requires ininitialization over
/* ES8388 Sound Module
This is an I2S sound processing unit that requires initialization over
I2C before I2S data can be received.
*/
class ES8388Source : public I2SSource {
@ -475,7 +475,7 @@ class ES8388Source : public I2SSource {
// The mics *and* line-in are BOTH connected to LIN2/RIN2 on the AudioKit
// so there's no way to completely eliminate the mics. It's also hella noisy.
// Line-in works OK on the AudioKit, generally speaking, as the mics really need
// amplification to be noticable in a quiet room. If you're in a very loud room,
// amplification to be noticeable in a quiet room. If you're in a very loud room,
// the mics on the AudioKit WILL pick up sound even in line-in mode.
// TL;DR: Don't use the AudioKit for anything, use the LyraT.
//
@ -529,7 +529,7 @@ class ES8388Source : public I2SSource {
};
void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) {
DEBUGSR_PRINTLN("ES8388Source:: initialize();");
DEBUGSR_PRINTLN(F("ES8388Source:: initialize();"));
if ((i2sckPin < 0) || (mclkPin < 0)) {
DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin);
return;
@ -587,7 +587,7 @@ class I2SAdcSource : public I2SSource {
AudioSourceType getType(void) {return(Type_I2SAdc);}
void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {
DEBUGSR_PRINTLN("I2SAdcSource:: initialize().");
DEBUGSR_PRINTLN(F("I2SAdcSource:: initialize()."));
_myADCchannel = 0x0F;
if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) {
DEBUGSR_PRINTF("failed to allocate GPIO for audio analog input: %d\n", audioPin);
@ -759,7 +759,7 @@ class SPH0654 : public I2SSource {
{}
void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE) {
DEBUGSR_PRINTLN("SPH0654:: initialize();");
DEBUGSR_PRINTLN(F("SPH0654:: initialize();"));
I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin);
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
// these registers are only existing in "classic" ESP32

View File

@ -1,6 +1,6 @@
# Audioreactive usermod
Enabless controlling LEDs via audio input. Audio source can be a microphone or analog-in (AUX) using an appropriate adapter.
Enables controlling LEDs via audio input. Audio source can be a microphone or analog-in (AUX) using an appropriate adapter.
Supported microphones range from analog (MAX4466, MAX9814, ...) to digital (INMP441, ICS-43434, ...).
Does audio processing and provides data structure that specially written effects can use.
@ -19,7 +19,7 @@ This usermod is an evolution of [SR-WLED](https://github.com/atuline/WLED), and
## Supported MCUs
This audioreactive usermod works best on "classic ESP32" (dual core), and on ESP32-S3 which also has dual core and hardware floating point support.
It will compile succesfully for ESP32-S2 and ESP32-C3, however might not work well, as other WLED functions will become slow. Audio processing requires a lot of computing power, which can be problematic on smaller MCUs like -S2 and -C3.
It will compile successfully for ESP32-S2 and ESP32-C3, however might not work well, as other WLED functions will become slow. Audio processing requires a lot of computing power, which can be problematic on smaller MCUs like -S2 and -C3.
Analog audio is only possible on "classic" ESP32, but not on other MCUs like ESP32-S3.
@ -35,7 +35,7 @@ Customised _arduinoFFT_ library for use with this usermod can be found at https:
### using latest (develop) _arduinoFFT_ library
Alternatively, you can use the latest arduinoFFT development version.
ArduinoFFT `develop` library is slightly more accurate, and slighly faster than our customised library, however also needs additional 2kB RAM.
ArduinoFFT `develop` library is slightly more accurate, and slightly faster than our customised library, however also needs additional 2kB RAM.
* `build_flags` = `-D USERMOD_AUDIOREACTIVE` `-D UM_AUDIOREACTIVE_USE_NEW_FFT`
* `lib_deps`= `https://github.com/kosme/arduinoFFT#develop @ 1.9.2`
@ -68,7 +68,7 @@ You can use the following additional flags in your `build_flags`
* `-D SR_GAIN=x` : Default "gain" setting (60)
* `-D I2S_USE_RIGHT_CHANNEL`: Use RIGHT instead of LEFT channel (not recommended unless you strictly need this).
* `-D I2S_USE_16BIT_SAMPLES`: Use 16bit instead of 32bit for internal sample buffers. Reduces sampling quality, but frees some RAM ressources (not recommended unless you absolutely need this).
* `-D I2S_GRAB_ADC1_COMPLETELY`: Experimental: continously sample analog ADC microphone. Only effective on ESP32. WARNING this _will_ cause conflicts(lock-up) with any analogRead() call.
* `-D I2S_GRAB_ADC1_COMPLETELY`: Experimental: continuously sample analog ADC microphone. Only effective on ESP32. WARNING this _will_ cause conflicts(lock-up) with any analogRead() call.
* `-D MIC_LOGGER` : (debugging) Logs samples from the microphone to serial USB. Use with serial plotter (Arduino IDE)
* `-D SR_DEBUG` : (debugging) Additional error diagnostics and debug info on serial USB.

View File

@ -174,9 +174,9 @@ class BobLightUsermod : public Usermod {
#if WLED_DEBUG
DEBUG_PRINTLN(F("Fill light data: "));
DEBUG_PRINTF(" lights %d\n", numLights);
DEBUG_PRINTF_P(PSTR(" lights %d\n"), numLights);
for (int i=0; i<numLights; i++) {
DEBUG_PRINTF(" light %s scan %2.1f %2.1f %2.1f %2.1f\n", lights[i].lightname, lights[i].vscan[0], lights[i].vscan[1], lights[i].hscan[0], lights[i].hscan[1]);
DEBUG_PRINTF_P(PSTR(" light %s scan %2.1f %2.1f %2.1f %2.1f\n"), lights[i].lightname, lights[i].vscan[0], lights[i].vscan[1], lights[i].hscan[0], lights[i].hscan[1]);
}
#endif
}
@ -191,7 +191,7 @@ class BobLightUsermod : public Usermod {
uint16_t totalLights = bottom + left + top + right;
if ( totalLights > strip.getLengthTotal() ) {
DEBUG_PRINTLN(F("BobLight: Too many lights."));
DEBUG_PRINTF("%d+%d+%d+%d>%d\n", bottom, left, top, right, strip.getLengthTotal());
DEBUG_PRINTF_P(PSTR("%d+%d+%d+%d>%d\n"), bottom, left, top, right, strip.getLengthTotal());
totalLights = strip.getLengthTotal();
top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f);
left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f);
@ -318,7 +318,7 @@ class BobLightUsermod : public Usermod {
void addToConfig(JsonObject& root) override {
JsonObject umData = root.createNestedObject(FPSTR(_name));
umData[FPSTR(_enabled)] = enabled;
umData[F("port")] = bobPort;
umData[ "port" ] = bobPort;
umData[F("top")] = top;
umData[F("bottom")] = bottom;
umData[F("left")] = left;
@ -334,7 +334,7 @@ class BobLightUsermod : public Usermod {
configComplete &= getJsonValue(umData[FPSTR(_enabled)], en);
enable(en);
configComplete &= getJsonValue(umData[F("port")], bobPort);
configComplete &= getJsonValue(umData[ "port" ], bobPort);
configComplete &= getJsonValue(umData[F("bottom")], bottom, 16);
configComplete &= getJsonValue(umData[F("top")], top, 16);
configComplete &= getJsonValue(umData[F("left")], left, 9);
@ -392,7 +392,7 @@ void BobLightUsermod::pollBob() {
//get data from the client
while (bobClient.available()) {
String input = bobClient.readStringUntil('\n');
// DEBUG_PRINT("Client: "); DEBUG_PRINTLN(input); // may be to stressful on Serial
// DEBUG_PRINT(F("Client: ")); DEBUG_PRINTLN(input); // may be to stressful on Serial
if (input.startsWith(F("hello"))) {
DEBUG_PRINTLN(F("hello"));
bobClient.print(F("hello\n"));

View File

@ -233,7 +233,7 @@ class MPU6050Driver : public Usermod {
* Use it to initialize network interfaces
*/
void connected() {
//DEBUG_PRINTLN("Connected to WiFi!");
//DEBUG_PRINTLN(F("Connected to WiFi!"));
}

View File

@ -50,5 +50,5 @@ This usermod listens on `[mqttDeviceTopic]/switch/0/set` (where 0 is replaced wi
Feedback about the current state is provided at `[mqttDeviceTopic]/switch/0/state`.
### Home Assistant auto-discovery
Auto-discovery information is automatically published and you shoudn't have to do anything to register the switches in Home Assistant.
Auto-discovery information is automatically published and you shouldn't have to do anything to register the switches in Home Assistant.

View File

@ -2,7 +2,7 @@
This usermod-v2 modification allows the connection of multiple relays, each with individual delay and on/off mode.
Usermod supports PCF8574 I2C port expander to reduce GPIO use.
PCF8574 supports 8 outputs and each output corresponds to a relay in WLED (relay 0 = port 0, etc). I you are using more than 8 relays with multiple PCF8574 make sure their addresses are set conscutively (e.g. 0x20 and 0x21). You can set address of first expander in settings.
PCF8574 supports 8 outputs and each output corresponds to a relay in WLED (relay 0 = port 0, etc). I you are using more than 8 relays with multiple PCF8574 make sure their addresses are set in sequence (e.g. 0x20 and 0x21). You can set address of first expander in settings.
(**NOTE:** Will require Wire library and global I2C pins defined.)
## HTTP API

View File

@ -104,6 +104,9 @@ class MultiRelay : public Usermod {
static const char _HAautodiscovery[];
static const char _pcf8574[];
static const char _pcfAddress[];
static const char _switch[];
static const char _toggle[];
static const char _Command[];
void handleOffTimer();
void InitHtmlAPIHandle();
@ -220,7 +223,7 @@ class MultiRelay : public Usermod {
};
// class implementetion
// class implementation
void MultiRelay::publishMqtt(int relay) {
#ifndef WLED_DISABLE_MQTT
@ -261,8 +264,8 @@ void MultiRelay::handleOffTimer() {
void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer
DEBUG_PRINTLN(F("Relays: Initialize HTML API"));
server.on("/relays", HTTP_GET, [this](AsyncWebServerRequest *request) {
DEBUG_PRINTLN("Relays: HTML API");
server.on(SET_F("/relays"), HTTP_GET, [this](AsyncWebServerRequest *request) {
DEBUG_PRINTLN(F("Relays: HTML API"));
String janswer;
String error = "";
//int params = request->params();
@ -271,9 +274,9 @@ void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsync
if (getActiveRelayCount()) {
// Commands
if(request->hasParam("switch")) {
if (request->hasParam(FPSTR(_switch))) {
/**** Switch ****/
AsyncWebParameter* p = request->getParam("switch");
AsyncWebParameter* p = request->getParam(FPSTR(_switch));
// Get Values
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
int value = getValue(p->value(), ',', i);
@ -284,9 +287,9 @@ void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsync
if (_relay[i].external) switchRelay(i, (bool)value);
}
}
} else if(request->hasParam("toggle")) {
} else if (request->hasParam(FPSTR(_toggle))) {
/**** Toggle ****/
AsyncWebParameter* p = request->getParam("toggle");
AsyncWebParameter* p = request->getParam(FPSTR(_toggle));
// Get Values
for (int i=0;i<MULTI_RELAY_MAX_RELAYS;i++) {
int value = getValue(p->value(), ',', i);
@ -314,7 +317,7 @@ void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsync
janswer += error;
janswer += F("\",");
janswer += F("\"SW Version\":\"");
janswer += String(GEOGABVERSION);
janswer += String(F(GEOGABVERSION));
janswer += F("\"}");
request->send(200, "application/json", janswer);
});
@ -388,7 +391,7 @@ void MultiRelay::switchRelay(uint8_t relay, bool mode) {
if (relay>=MULTI_RELAY_MAX_RELAYS || _relay[relay].pin<0) return;
_relay[relay].state = mode;
if (usePcf8574 && _relay[relay].pin >= 100) {
// we need to send all ouputs at the same time
// we need to send all outputs at the same time
uint8_t state = 0;
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].pin < 100) continue;
@ -420,7 +423,7 @@ uint8_t MultiRelay::getActiveRelayCount() {
* topic should look like: /relay/X/command; where X is relay number, 0 based
*/
bool MultiRelay::onMqttMessage(char* topic, char* payload) {
if (strlen(topic) > 8 && strncmp_P(topic, PSTR("/relay/"), 7) == 0 && strncmp_P(topic+8, PSTR("/command"), 8) == 0) {
if (strlen(topic) > 8 && strncmp_P(topic, PSTR("/relay/"), 7) == 0 && strncmp_P(topic+8, _Command, 8) == 0) {
uint8_t relay = strtoul(topic+7, NULL, 10);
if (relay<MULTI_RELAY_MAX_RELAYS) {
String action = payload;
@ -430,7 +433,7 @@ bool MultiRelay::onMqttMessage(char* topic, char* payload) {
} else if (action == "off") {
if (_relay[relay].external) switchRelay(relay, false);
return true;
} else if (action == "toggle") {
} else if (action == FPSTR(_toggle)) {
if (_relay[relay].external) toggleRelay(relay);
return true;
}
@ -470,7 +473,7 @@ void MultiRelay::publishHomeAssistantAutodiscovery() {
sprintf_P(buf, PSTR("%s/relay/%d"), mqttDeviceTopic, i); //max length: 33 + 7 + 3 = 43
json["~"] = buf;
strcat_P(buf, PSTR("/command"));
strcat_P(buf, _Command);
mqtt->subscribe(buf, 0);
json[F("stat_t")] = "~";
@ -675,8 +678,8 @@ void MultiRelay::addToJsonInfo(JsonObject &root) {
uiDomString += F(",on:");
uiDomString += _relay[i].state ? "false" : "true";
uiDomString += F("}});\">");
uiDomString += F("<i class=\"icons");
uiDomString += _relay[i].state ? F(" on") : F(" off");
uiDomString += F("<i class=\"icons ");
uiDomString += _relay[i].state ? "on" : "off";
uiDomString += F("\">&#xe08f;</i></button>");
infoArr.add(uiDomString);
}
@ -699,11 +702,11 @@ void MultiRelay::addToJsonState(JsonObject &root) {
if (_relay[i].pin < 0) continue;
JsonObject relay = rel_arr.createNestedObject();
relay[FPSTR(_relay_str)] = i;
relay[F("state")] = _relay[i].state;
relay["state"] = _relay[i].state;
}
#else
multiRelay[FPSTR(_relay_str)] = 0;
multiRelay[F("state")] = _relay[0].state;
multiRelay["state"] = _relay[0].state;
#endif
}
@ -836,3 +839,6 @@ const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec";
const char MultiRelay::_HAautodiscovery[] PROGMEM = "HA-autodiscovery";
const char MultiRelay::_pcf8574[] PROGMEM = "use-PCF8574";
const char MultiRelay::_pcfAddress[] PROGMEM = "PCF8574-address";
const char MultiRelay::_switch[] PROGMEM = "switch";
const char MultiRelay::_toggle[] PROGMEM = "toggle";
const char MultiRelay::_Command[] PROGMEM = "/command";

View File

@ -2,7 +2,7 @@
The (un)official usermod to get the best out of the QuinLED-An-Penta (https://quinled.info/quinled-an-penta/), e.g. using the OLED and the SHT30 temperature/humidity sensor.
## Requirements
* "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2
* "u8g2" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2
* "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85
## Usermod installation

View File

@ -85,8 +85,8 @@ private:
JsonObject device = doc.createNestedObject("device"); // attach the sensor to the same device
device["identifiers"] = String("wled-sensor-") + mqttClientID;
device["manufacturer"] = "Aircoookie";
device["model"] = "WLED";
device["manufacturer"] = F(WLED_BRAND);
device["model"] = F(WLED_PRODUCT_NAME);
device["sw_version"] = VERSION;
device["name"] = mqttClientID;

View File

@ -17,7 +17,7 @@ The number of individual LEDs per segment. 7 segments per digit.
#### perPeriod -- ssLEDPerPeriod
The number of individual LEDs per period. A ':' (colon) has two periods.
#### startIdx -- ssStartLED
Index of the LED the display starts at. Enabless a seven segment display to be in the middle of a string.
Index of the LED the display starts at. Enables a seven segment display to be in the middle of a string.
#### timeEnable -- ssTimeEnabled
When true, when displayMask is configured for a time output and no message is set, the time will be displayed.
#### scrollSpd -- ssScrollSpeed

View File

@ -409,7 +409,7 @@ public:
if (mqttGroupTopic[0] != 0)
{
//subcribe for sevenseg messages on the group topic
//subscribe for sevenseg messages on the group topic
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_sevenSeg);
mqtt->subscribe(subBuffer, 2);
}
@ -417,7 +417,7 @@ public:
bool onMqttMessage(char *topic, char *payload)
{
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
//If topic beings with sevenSeg cut it off, otherwise not our message.
size_t topicPrefixLen = strlen_P(PSTR("/sevenSeg/"));
if (strncmp_P(topic, PSTR("/sevenSeg/"), topicPrefixLen) == 0)
topic += topicPrefixLen;

View File

@ -480,14 +480,14 @@ public:
if (mqttGroupTopic[0] != 0)
{
//subcribe for sevenseg messages on the group topic
//subscribe for sevenseg messages on the group topic
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_name);
mqtt->subscribe(subBuffer, 2);
}
}
bool onMqttMessage(char *topic, char *payload) {
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
//If topic begins with sevenSeg cut it off, otherwise not our message.
size_t topicPrefixLen = strlen_P(PSTR("/wledSS/"));
if (strncmp_P(topic, PSTR("/wledSS/"), topicPrefixLen) == 0) {
topic += topicPrefixLen;

View File

@ -290,7 +290,7 @@ void ShtUsermod::loop()
/**
* Whenever MQTT is connected, publish HA autodiscovery topics.
*
* Is only donce once.
* Is only done once.
*
* @see Usermod::onMqttConnect()
* @see UsermodManager::onMqttConnect()

View File

@ -1,5 +1,13 @@
### Stairway lighting
# Stairway lighting
## Install
Add the buildflag `-D USERMOD_STAIRCASE_WIPE` to your enviroment to activate it.
### Configuration
`-D STAIRCASE_WIPE_OFF`
<br>Have the LEDs wipe off instead of fading out
## Description
Quick usermod to accomplish something similar to [this video](https://www.youtube.com/watch?v=NHkju5ncC4A).
This usermod enables you to add a lightstrip alongside or on the steps of a staircase.

View File

@ -19,10 +19,12 @@ class StairwayWipeUsermod : public Usermod {
unsigned long timeStaticStart = 0;
uint16_t previousUserVar0 = 0;
//moved to buildflag
//comment this out if you want the turn off effect to be just fading out instead of reverse wipe
#define STAIRCASE_WIPE_OFF
//#define STAIRCASE_WIPE_OFF
public:
void setup() {
}
void loop() {
//userVar0 (U0 in HTTP API):
//has to be set to 1 if movement is detected on the PIR that is the same side of the staircase as the ESP8266
@ -84,7 +86,7 @@ class StairwayWipeUsermod : public Usermod {
uint16_t getId()
{
return USERMOD_ID_EXAMPLE;
return USERMOD_ID_STAIRWAY_WIPE;
}

View File

@ -23,7 +23,7 @@ private:
unsigned char Enc_B;
unsigned char Enc_A_prev = 0;
// private class memebers configurable by Usermod Settings (defaults set inside readFromConfig())
// private class members configurable by Usermod Settings (defaults set inside readFromConfig())
int8_t pins[3]; // pins[0] = DT from encoder, pins[1] = CLK from encoder, pins[2] = CLK from encoder (optional)
int fadeAmount; // how many points to fade the Neopixel with each step
@ -162,7 +162,7 @@ public:
* - configComplete is used to return false if any value is missing, not just if the main object is missing
* - The defaults are loaded every time readFromConfig() is run, not just once after boot
*
* This ensures that missing values are added to the config, with their default values, in the rare but plauible cases of:
* This ensures that missing values are added to the config, with their default values, in the rare but plausible cases of:
* - a single value being missing at boot, e.g. if the Usermod was upgraded and a new setting was added
* - a single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
*

View File

@ -0,0 +1,110 @@
# usermod_v2_HttpPullLightControl
The `usermod_v2_HttpPullLightControl` is a custom user module for WLED that enables remote control over the lighting state and color through HTTP requests. It periodically polls a specified URL to obtain a JSON response containing instructions for controlling individual lights.
## Features
* Configure the URL endpoint (only support HTTP for now, no HTTPS) and polling interval via the WLED user interface.
* All options from the JSON API are supported (since v0.0.3). See: https://kno.wled.ge/interfaces/json-api/
* The ability to control the brightness of all lights and the state (on/off) and color of individual lights remotely.
* Start or stop an effect and when you run the same effect when its's already running, it won't restart.
* The ability to control all these settings per segment.
* Remotely turn on/off relays, change segments or presets.
* Unique ID generation based on the device's MAC address and a configurable salt value, appended to the request URL for identification.
## Configuration
* Enable the `usermod_v2_HttpPullLightControl` via the WLED user interface.
* Specify the URL endpoint and polling interval.
## JSON Format and examples
* The module sends a GET request to the configured URL, appending a unique identifier as a query parameter: `https://www.example.com/mycustompage.php?id=xxxxxxxx` where xxxxxxx is a 40 character long SHA1 hash of the MAC address combined with a given salt.
* Response Format (since v0.0.3) it is eactly the same as the WLED JSON API, see: https://kno.wled.ge/interfaces/json-api/
After getting the URL (it can be a static file like static.json or a mylogic.php which gives a dynamic response), the response is read and parsed to WLED.
* An example of a response to set the individual lights: 0 to RED, 12 to Green and 14 to BLUE. Remember that is will SET lights, you might want to set all the others to black.
`{
"seg":
{
"i": [
0, "FF0000",
12, "00FF00",
14, "0000FF"
]
}
}`
* Another example setting the first 10 LEDs to RED, LED 40 to a PURPLE (using RGB values) and all LEDs in between OFF (black color)
`{
"seg":
{
"i": [
0,10, "FF0000",
10,40, "00FF00",
40, [0,100,100]
]
}
}`
* Or first set all lights to black (off), then the LED5 to color RED:
`{
"seg":
{
"i": [
0,40, "000000",
5, "FF0000"
]
}
}`
* Or use the following example to start an effect, but first we UNFREEZE (frz=false) the segment because it was frozen by individual light control in the previous examples (28=Chase effect, Speed=180m Intensity=128). The three color slots are the slots you see under the color wheel and used by the effect. RED, Black, White in this case.
`{
"seg":
{
"frz": false,
"fx": 28,
"sx": 200,
"ix": 128,
"col": [
"FF0000",
"000000",
"FFFFFF"
]
}
}`
## Installation
1. Add `usermod_v2_HttpPullLightControl` to your WLED project following the instructions provided in the WLED documentation.
2. Compile by setting the build_flag: -D USERMOD_HTTP_PULL_LIGHT_CONTROL and upload to your ESP32/ESP8266!
3. There are several compile options which you can put in your platformio.ini or platformio_override.ini:
- -DUSERMOD_HTTP_PULL_LIGHT_CONTROL ;To Enable the usermod
- -DHTTP_PULL_LIGHT_CONTROL_URL="\"http://mydomain.com/json-response.php\"" ; The URL which will be requested all the time to set the lights/effects
- -DHTTP_PULL_LIGHT_CONTROL_SALT="\"my_very-S3cret_C0de\"" ; A secret SALT which will help by making the ID more safe
- -DHTTP_PULL_LIGHT_CONTROL_INTERVAL=30 ; The interval at which the URL is requested in seconds
- -DHTTP_PULL_LIGHT_CONTROL_HIDE_SALT ; Do you want to Hide the SALT in the User Interface? If yes, Set this flag. Note that the salt can now only be set via the above -DHTTP_PULL_LIGHT_CONTROL_SALT= setting
- -DWLED_AP_SSID="\"Christmas Card\"" ; These flags are not just for my Usermod but you probably want to set them
- -DWLED_AP_PASS="\"christmas\""
- -DWLED_OTA_PASS="\"otapw-secret\""
- -DMDNS_NAME="\"christmascard\""
- -DSERVERNAME="\"CHRISTMASCARD\""
- -D ABL_MILLIAMPS_DEFAULT=450
- -D DEFAULT_LED_COUNT=60 ; For a LED Ring of 60 LEDs
- -D BTNPIN=41 ; The M5Stack Atom S3 Lite has a button on GPIO41
- -D LEDPIN=2 ; The M5Stack Atom S3 Lite has a Grove connector on the front, we use this GPIO2
- -D STATUSLED=35 ; The M5Stack Atom S3 Lite has a Multi-Color LED on GPIO35, although I didnt managed to control it
- -D IRPIN=4 ; The M5Stack Atom S3 Lite has a IR LED on GPIO4
- -D DEBUG=1 ; Set these DEBUG flags ONLY if you want to debug and read out Serial (using Visual Studio Code - Serial Monitor)
- -DDEBUG_LEVEL=5
- -DWLED_DEBUG
## Use Case: Interactive Christmas Cards
Imagine distributing interactive Christmas cards embedded with a tiny ESP32 and a string of 20 LEDs to 20 friends. When a friend powers on their card, it connects to their Wi-Fi network and starts polling your server via the `usermod_v2_HttpPullLightControl`. (Tip: Let them scan a QR code to connect to the WLED WiFi, from there they configure their own WiFi).
Your server keeps track of how many cards are active at any given time. If all 20 cards are active, your server instructs each card to light up all of its LEDs. However, if only 4 cards are active, your server instructs each card to light up only 4 LEDs. This creates a real-time interactive experience, symbolizing the collective spirit of the holiday season. Each lit LED represents a friend who's thinking about the others, and the visual feedback creates a sense of connection among the group, despite the physical distance.
This setup demonstrates a unique way to blend traditional holiday sentiments with modern technology, offering an engaging and memorable experience.

View File

@ -0,0 +1,319 @@
#include "usermod_v2_HttpPullLightControl.h"
// add more strings here to reduce flash memory usage
const char HttpPullLightControl::_name[] PROGMEM = "HttpPullLightControl";
const char HttpPullLightControl::_enabled[] PROGMEM = "Enable";
void HttpPullLightControl::setup() {
//Serial.begin(115200);
// Print version number
DEBUG_PRINT(F("HttpPullLightControl version: "));
DEBUG_PRINTLN(HTTP_PULL_LIGHT_CONTROL_VERSION);
// Start a nice chase so we know its booting and searching for its first http pull.
DEBUG_PRINTLN(F("Starting a nice chase so we now it is booting."));
Segment& seg = strip.getMainSegment();
seg.setMode(28); // Set to chase
seg.speed = 200;
seg.intensity = 255;
seg.setPalette(128);
seg.setColor(0, 5263440);
seg.setColor(1, 0);
seg.setColor(2, 4605510);
// Go on with generating a unique ID and splitting the URL into parts
uniqueId = generateUniqueId(); // Cache the unique ID
DEBUG_PRINT(F("UniqueId calculated: "));
DEBUG_PRINTLN(uniqueId);
parseUrl();
DEBUG_PRINTLN(F("HttpPullLightControl successfully setup"));
}
// This is the main loop function, from here we check the URL and handle the response.
// Effects or individual lights are set as a result from this.
void HttpPullLightControl::loop() {
if (!enabled || offMode) return; // Do nothing when not enabled or powered off
if (millis() - lastCheck >= checkInterval * 1000) {
DEBUG_PRINTLN(F("Calling checkUrl function"));
checkUrl();
lastCheck = millis();
}
}
// Generate a unique ID based on the MAC address and a SALT
String HttpPullLightControl::generateUniqueId() {
uint8_t mac[6];
WiFi.macAddress(mac);
char macStr[18];
sprintf(macStr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// Set the MAC Address to a string and make it UPPERcase
String macString = String(macStr);
macString.toUpperCase();
DEBUG_PRINT(F("WiFi MAC address is: "));
DEBUG_PRINTLN(macString);
DEBUG_PRINT(F("Salt is: "));
DEBUG_PRINTLN(salt);
String input = macString + salt;
#ifdef ESP8266
// For ESP8266 we use the Hash.h library which is built into the ESP8266 Core
return sha1(input);
#endif
#ifdef ESP32
// For ESP32 we use the mbedtls library which is built into the ESP32 core
int status = 0;
unsigned char shaResult[20]; // SHA1 produces a hash of 20 bytes (which is 40 HEX characters)
mbedtls_sha1_context ctx;
mbedtls_sha1_init(&ctx);
status = mbedtls_sha1_starts_ret(&ctx);
if (status != 0) {
DEBUG_PRINTLN(F("Error starting SHA1 checksum calculation"));
}
status = mbedtls_sha1_update_ret(&ctx, reinterpret_cast<const unsigned char*>(input.c_str()), input.length());
if (status != 0) {
DEBUG_PRINTLN(F("Error feeding update buffer into ongoing SHA1 checksum calculation"));
}
status = mbedtls_sha1_finish_ret(&ctx, shaResult);
if (status != 0) {
DEBUG_PRINTLN(F("Error finishing SHA1 checksum calculation"));
}
mbedtls_sha1_free(&ctx);
// Convert the Hash to a hexadecimal string
char buf[41];
for (int i = 0; i < 20; i++) {
sprintf(&buf[i*2], "%02x", shaResult[i]);
}
return String(buf);
#endif
}
// This function is called when the user updates the Sald and so we need to re-calculate the unique ID
void HttpPullLightControl::updateSalt(String newSalt) {
DEBUG_PRINTLN(F("Salt updated"));
this->salt = newSalt;
uniqueId = generateUniqueId();
DEBUG_PRINT(F("New UniqueId is: "));
DEBUG_PRINTLN(uniqueId);
}
// The function is used to separate the URL in a host part and a path part
void HttpPullLightControl::parseUrl() {
int firstSlash = url.indexOf('/', 7); // Skip http(s)://
host = url.substring(7, firstSlash);
path = url.substring(firstSlash);
}
// This function is called by WLED when the USERMOD config is read
bool HttpPullLightControl::readFromConfig(JsonObject& root) {
// Attempt to retrieve the nested object for this usermod
JsonObject top = root[FPSTR(_name)];
bool configComplete = !top.isNull(); // check if the object exists
// Retrieve the values using the getJsonValue function for better error handling
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled, enabled); // default value=enabled
configComplete &= getJsonValue(top["checkInterval"], checkInterval, checkInterval); // default value=60
#ifndef HTTP_PULL_LIGHT_CONTROL_HIDE_URL
configComplete &= getJsonValue(top["url"], url, url); // default value="http://example.com"
#endif
#ifndef HTTP_PULL_LIGHT_CONTROL_HIDE_SALT
configComplete &= getJsonValue(top["salt"], salt, salt); // default value=your_salt_here
#endif
return configComplete;
}
// This function is called by WLED when the USERMOD config is saved in the frontend
void HttpPullLightControl::addToConfig(JsonObject& root) {
// Create a nested object for this usermod
JsonObject top = root.createNestedObject(FPSTR(_name));
// Write the configuration parameters to the nested object
top[FPSTR(_enabled)] = enabled;
if (enabled==false)
// To make it a bit more user-friendly, we unfreeze the main segment after disabling the module. Because individual light control (like for a christmas card) might have been done.
strip.getMainSegment().freeze=false;
top["checkInterval"] = checkInterval;
#ifndef HTTP_PULL_LIGHT_CONTROL_HIDE_URL
top["url"] = url;
#endif
#ifndef HTTP_PULL_LIGHT_CONTROL_HIDE_SALT
top["salt"] = salt;
updateSalt(salt); // Update the UniqueID
#endif
parseUrl(); // Re-parse the URL, maybe path and host is changed
}
// Do the http request here. Note that we can not do https requests with the AsyncTCP library
// We do everything Asynchronous, so all callbacks are defined here
void HttpPullLightControl::checkUrl() {
// Extra Inactivity check to see if AsyncCLient hangs
if (client != nullptr && ( millis() - lastActivityTime > inactivityTimeout ) ) {
DEBUG_PRINTLN(F("Inactivity detected, deleting client."));
delete client;
client = nullptr;
}
if (client != nullptr && client->connected()) {
DEBUG_PRINTLN(F("We are still connected, do nothing"));
// Do nothing, Client is still connected
return;
}
if (client != nullptr) {
// Delete previous client instance if exists, just to prevent any memory leaks
DEBUG_PRINTLN(F("Delete previous instances"));
delete client;
client = nullptr;
}
DEBUG_PRINTLN(F("Creating new AsyncClient instance."));
client = new AsyncClient();
if(client) {
client->onData([](void *arg, AsyncClient *c, void *data, size_t len) {
DEBUG_PRINTLN(F("Data received."));
// Cast arg back to the usermod class instance
HttpPullLightControl *instance = (HttpPullLightControl *)arg;
instance->lastActivityTime = millis(); // Update lastactivity time when data is received
// Convertert to Safe-String
char *strData = new char[len + 1];
strncpy(strData, (char*)data, len);
strData[len] = '\0';
String responseData = String(strData);
//String responseData = String((char *)data);
// Make sure its zero-terminated String
//responseData[len] = '\0';
delete[] strData; // Do not forget to remove this one
instance->handleResponse(responseData);
}, this);
client->onDisconnect([](void *arg, AsyncClient *c) {
DEBUG_PRINTLN(F("Disconnected."));
//Set the class-own client pointer to nullptr if its the current client
HttpPullLightControl *instance = static_cast<HttpPullLightControl*>(arg);
if (instance->client == c) {
delete instance->client; // Delete the client instance
instance->client = nullptr;
}
}, this);
client->onTimeout([](void *arg, AsyncClient *c, uint32_t time) {
DEBUG_PRINTLN(F("Timeout"));
//Set the class-own client pointer to nullptr if its the current client
HttpPullLightControl *instance = static_cast<HttpPullLightControl*>(arg);
if (instance->client == c) {
delete instance->client; // Delete the client instance
instance->client = nullptr;
}
}, this);
client->onError([](void *arg, AsyncClient *c, int8_t error) {
DEBUG_PRINTLN("Connection error occurred!");
DEBUG_PRINT("Error code: ");
DEBUG_PRINTLN(error);
//Set the class-own client pointer to nullptr if its the current client
HttpPullLightControl *instance = static_cast<HttpPullLightControl*>(arg);
if (instance->client == c) {
delete instance->client;
instance->client = nullptr;
}
// Do not remove client here, it is maintained by AsyncClient
}, this);
client->onConnect([](void *arg, AsyncClient *c) {
// Cast arg back to the usermod class instance
HttpPullLightControl *instance = (HttpPullLightControl *)arg;
instance->onClientConnect(c); // Call a method on the instance when the client connects
}, this);
client->setAckTimeout(ackTimeout); // Just some safety measures because we do not want any memory fillup
client->setRxTimeout(rxTimeout);
DEBUG_PRINT(F("Connecting to: "));
DEBUG_PRINT(host);
DEBUG_PRINT(F(" via port "));
DEBUG_PRINTLN((url.startsWith("https")) ? 443 : 80);
// Update lastActivityTime just before sending the request
lastActivityTime = millis();
//Try to connect
if (!client->connect(host.c_str(), (url.startsWith("https")) ? 443 : 80)) {
DEBUG_PRINTLN(F("Failed to initiate connection."));
// Connection failed, so cleanup
delete client;
client = nullptr;
} else {
// Connection successfull, wait for callbacks to go on.
DEBUG_PRINTLN(F("Connection initiated, awaiting response..."));
}
} else {
DEBUG_PRINTLN(F("Failed to create AsyncClient instance."));
}
}
// This function is called from the checkUrl function when the connection is establised
// We request the data here
void HttpPullLightControl::onClientConnect(AsyncClient *c) {
DEBUG_PRINT(F("Client connected: "));
DEBUG_PRINTLN(c->connected() ? F("Yes") : F("No"));
if (c->connected()) {
String request = "GET " + path + (path.indexOf('?') > 0 ? "&id=" : "?id=") + uniqueId + " HTTP/1.1\r\n"
"Host: " + host + "\r\n"
"Connection: close\r\n"
"Accept: application/json\r\n"
"Accept-Encoding: identity\r\n" // No compression
"User-Agent: ESP32 HTTP Client\r\n\r\n"; // Optional: User-Agent and end with a double rnrn !
DEBUG_PRINT(request.c_str());
auto bytesSent = c->write(request.c_str());
if (bytesSent == 0) {
// Connection could not be made
DEBUG_PRINT(F("Failed to send HTTP request."));
} else {
DEBUG_PRINT(F("Request sent successfully, bytes sent: "));
DEBUG_PRINTLN(bytesSent );
}
}
}
// This function is called when we receive data after connecting and doing our request
// It parses the JSON data to WLED
void HttpPullLightControl::handleResponse(String& responseStr) {
DEBUG_PRINTLN(F("Received response for handleResponse."));
// Get a Bufferlock, we can not use doc
if (!requestJSONBufferLock(myLockId)) {
DEBUG_PRINT(F("ERROR: Can not request JSON Buffer Lock, number: "));
DEBUG_PRINTLN(myLockId);
releaseJSONBufferLock(); // Just release in any case, maybe there was already a buffer lock
return;
}
// Search for two linebreaks between headers and content
int bodyPos = responseStr.indexOf("\r\n\r\n");
if (bodyPos > 0) {
String jsonStr = responseStr.substring(bodyPos + 4); // +4 Skip the two CRLFs
jsonStr.trim();
DEBUG_PRINTLN("Response: ");
DEBUG_PRINTLN(jsonStr);
// Check for valid JSON, otherwise we brick the program runtime
if (jsonStr[0] == '{' || jsonStr[0] == '[') {
// Attempt to deserialize the JSON response
DeserializationError error = deserializeJson(doc, jsonStr);
if (error == DeserializationError::Ok) {
// Get JSON object from th doc
JsonObject obj = doc.as<JsonObject>();
// Parse the object throuhg deserializeState (use CALL_MODE_NO_NOTIFY or OR CALL_MODE_DIRECT_CHANGE)
deserializeState(obj, CALL_MODE_NO_NOTIFY);
} else {
// If there is an error in deserialization, exit the function
DEBUG_PRINT(F("DeserializationError: "));
DEBUG_PRINTLN(error.c_str());
}
} else {
DEBUG_PRINTLN(F("Invalid JSON response"));
}
} else {
DEBUG_PRINTLN(F("No body found in the response"));
}
// Release the BufferLock again
releaseJSONBufferLock();
}

View File

@ -0,0 +1,104 @@
#pragma once
/*
* Usermod: HttpPullLightControl
* Versie: 0.0.4
* Repository: https://github.com/roelbroersma/WLED-usermodv2_HttpPullLightControl
* Author: Roel Broersma
* Website: https://www.roelbroersma.nl
* Github author: github.com/roelbroersma
* Description: This usermod for WLED will request a given URL to know which effects
* or individual lights it should turn on/off. So you can remote control a WLED
* installation without having access to it (if no port forward, vpn or public IP is available).
* Use Case: Create a WLED 'Ring of Thought' christmas card. Sent a LED ring with 60 LEDs to 60 friends.
* When they turn it on and put it at their WiFi, it will contact your server. Now you can reply with a given
* number of lights that should turn on. Each light is a friend who did contact your server in the past 5 minutes.
* So on each of your friends LED rings, the number of lights will be the number of friends who have it turned on.
* Features: It sends a unique ID (has of MAC and salt) to the URL, so you can define each client without a need to map their IP address.
* Tested: Tested on WLED v0.14 with ESP32-S3 (M5Stack Atom S3 Lite), but should also workd for other ESPs and ESP8266.
*/
#include "wled.h"
// Use the following for SHA1 computation of our HASH, unfortunatelly PlatformIO doesnt recognize Hash.h while its already in the Core.
// We use Hash.h for ESP8266 (in the core) and mbedtls/sha256.h for ESP32 (in the core).
#ifdef ESP8266
#include <Hash.h>
#endif
#ifdef ESP32
#include "mbedtls/sha1.h"
#endif
#define HTTP_PULL_LIGHT_CONTROL_VERSION "0.0.4"
class HttpPullLightControl : public Usermod {
private:
static const char _name[];
static const char _enabled[];
static const char _salt[];
static const char _url[];
bool enabled = true;
#ifdef HTTP_PULL_LIGHT_CONTROL_INTERVAL
uint16_t checkInterval = HTTP_PULL_LIGHT_CONTROL_INTERVAL;
#else
uint16_t checkInterval = 60; // Default interval of 1 minute
#endif
#ifdef HTTP_PULL_LIGHT_CONTROL_URL
String url = HTTP_PULL_LIGHT_CONTROL_URL;
#else
String url = "http://example.org/example.php"; // Default-URL (http only!), can also be url with IP address in it. HttpS urls are not supported (yet) because of AsyncTCP library
#endif
#ifdef HTTP_PULL_LIGHT_CONTROL_SALT
String salt = HTTP_PULL_LIGHT_CONTROL_SALT;
#else
String salt = "1just_a_very-secret_salt2"; // Salt for generating a unique ID when requesting the URL (in this way you can give different answers based on the WLED device who does the request)
#endif
// NOTE THAT THERE IS ALSO A #ifdef HTTP_PULL_LIGHT_CONTROL_HIDE_URL and a HTTP_PULL_LIGHT_CONTROL_HIDE_SALT IF YOU DO NOT WANT TO SHOW THE OPTIONS IN THE USERMOD SETTINGS
// Define constants
static const uint8_t myLockId = USERMOD_ID_HTTP_PULL_LIGHT_CONTROL ; // Used for the requestJSONBufferLock(id) function
static const int16_t ackTimeout = 9000; // ACK timeout in milliseconds when doing the URL request
static const uint16_t rxTimeout = 9000; // RX timeout in milliseconds when doing the URL request
static const unsigned long FNV_offset_basis = 2166136261;
static const unsigned long FNV_prime = 16777619;
static const unsigned long inactivityTimeout = 30000; // When the AsyncClient is inactive (hanging) for this many milliseconds, we kill it
unsigned long lastCheck = 0; // Timestamp of last check
unsigned long lastActivityTime = 0; // Time of last activity of AsyncClient
String host; // Host extracted from the URL
String path; // Path extracted from the URL
String uniqueId; // Cached unique ID
AsyncClient *client = nullptr; // Used very often, beware of closing and freeing
String generateUniqueId();
void parseUrl();
void updateSalt(String newSalt); // Update the salt value and recalculate the unique ID
void checkUrl(); // Check the specified URL for light control instructions
void handleResponse(String& response);
void onClientConnect(AsyncClient *c);
public:
void setup();
void loop();
bool readFromConfig(JsonObject& root);
void addToConfig(JsonObject& root);
uint16_t getId() { return USERMOD_ID_HTTP_PULL_LIGHT_CONTROL; }
inline void enable(bool enable) { enabled = enable; } // Enable or Disable the usermod
inline bool isEnabled() { return enabled; } // Get usermod enabled or disabled state
virtual ~HttpPullLightControl() {
// Remove the cached client if needed
if (client) {
client->onDisconnect(nullptr);
client->onError(nullptr);
client->onTimeout(nullptr);
client->onData(nullptr);
client->onConnect(nullptr);
// Now it is safe to delete the client.
delete client; // This is safe even if client is nullptr.
client = nullptr;
}
}
};

View File

@ -101,7 +101,7 @@ class AutoSaveUsermod : public Usermod {
// network here
void setup() {
#ifdef USERMOD_FOUR_LINE_DISPLAY
// This Usermod has enhanced funcionality if
// This Usermod has enhanced functionality if
// FourLineDisplayUsermod is available.
display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
#endif
@ -148,7 +148,7 @@ class AutoSaveUsermod : public Usermod {
if (autoSaveAfter && now > autoSaveAfter) {
autoSaveAfter = 0;
// Time to auto save. You may have some flickry?
// Time to auto save. You may have some flickery?
saveSettings();
displayOverlay();
}

View File

@ -6,7 +6,7 @@
#include "4LD_wled_fonts.c"
#ifndef FLD_ESP32_NO_THREADS
#define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!!
#define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behaviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!!
#endif
//
@ -243,7 +243,7 @@ class FourLineDisplayUsermod : public Usermod {
*/
void setMarkLine(byte newMarkLineNum, byte newMarkColNum);
//Draw the arrow for the current setting beiong changed
//Draw the arrow for the current setting being changed
void drawArrow();
//Display the current effect or palette (desiredEntry)
@ -793,7 +793,7 @@ void FourLineDisplayUsermod::setMarkLine(byte newMarkLineNum, byte newMarkColNum
markColNum = newMarkColNum;
}
//Draw the arrow for the current setting beiong changed
//Draw the arrow for the current setting being changed
void FourLineDisplayUsermod::drawArrow() {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis();
@ -1066,7 +1066,7 @@ void FourLineDisplayUsermod::networkOverlay(const char* line1, long showHowLong)
bool FourLineDisplayUsermod::handleButton(uint8_t b) {
yield();
if (!enabled
|| b // butto 0 only
|| b // button 0 only
|| buttonType[b] == BTN_TYPE_SWITCH
|| buttonType[b] == BTN_TYPE_NONE
|| buttonType[b] == BTN_TYPE_RESERVED

View File

@ -10,7 +10,7 @@ curl --location --request GET 'http://[]/printer/objects/query?virtual_sdcard=pr
## Usage
Compile the source with the buildflag `-D USERMOD_KLIPPER_PERCENTAGE` added.
You can also use the WLBD bot in the Discord by simply extending an exsisting build enviroment:
You can also use the WLBD bot in the Discord by simply extending an existing build environment:
```
[env:esp32klipper]
extends = env:esp32dev
@ -23,7 +23,7 @@ build_flags = ${common.build_flags_esp32} -D USERMOD_KLIPPER_PERCENTAGE
Checkbox to enable or disable the overlay
### Klipper IP:
IP adress of your Klipper instance you want to poll. ESP has to be restarted after change
IP address of your Klipper instance you want to poll. ESP has to be restarted after change
### Direction :
0 = normal

View File

@ -6,7 +6,7 @@ class klipper_percentage : public Usermod
{
private:
unsigned long lastTime = 0;
String ip = "192.168.25.207";
String ip = F("0.0.0.0");
WiFiClient wifiClient;
char errorMessage[100] = "";
int printPercent = 0;
@ -30,7 +30,7 @@ private:
{
// Send HTTP request
client.println(F("GET /printer/objects/query?virtual_sdcard=progress HTTP/1.0"));
client.println("Host: " + ip);
client.print(F("Host: ")); client.println(ip);
client.println(F("Connection: close"));
if (client.println() == 0)
{
@ -41,7 +41,7 @@ private:
// Check HTTP status
char status[32] = {0};
client.readBytesUntil('\r', status, sizeof(status));
if (strcmp(status, "HTTP/1.1 200 OK") != 0)
if (strcmp_P(status, PSTR("HTTP/1.1 200 OK")) != 0)
{
strcat(errorMessage, PSTR("Unexpected response: "));
strcat(errorMessage, status);
@ -79,18 +79,18 @@ public:
httpGet(wifiClient, errorMessage);
if (strcmp(errorMessage, "") == 0)
{
PSRAMDynamicJsonDocument klipperDoc(4096); // in practive about 2673
PSRAMDynamicJsonDocument klipperDoc(4096); // in practice about 2673
DeserializationError error = deserializeJson(klipperDoc, wifiClient);
if (error)
{
strcat(errorMessage, PSTR("deserializeJson() failed: "));
strcat(errorMessage, error.c_str());
}
printPercent = (int)(klipperDoc["result"]["status"]["virtual_sdcard"]["progress"].as<float>() * 100);
printPercent = (int)(klipperDoc[F("result")][F("status")][F("virtual_sdcard")][F("progress")].as<float>() * 100);
DEBUG_PRINT("Percent: ");
DEBUG_PRINTLN((int)(klipperDoc["result"]["status"]["virtual_sdcard"]["progress"].as<float>() * 100));
DEBUG_PRINT("LEDs: ");
DEBUG_PRINT(F("Percent: "));
DEBUG_PRINTLN((int)(klipperDoc[F("result")][F("status")][F("virtual_sdcard")][F("progress")].as<float>() * 100));
DEBUG_PRINT(F("LEDs: "));
DEBUG_PRINTLN(direction == 2 ? (strip.getLengthTotal() / 2) * printPercent / 100 : strip.getLengthTotal() * printPercent / 100);
}
else
@ -106,10 +106,10 @@ public:
void addToConfig(JsonObject &root)
{
JsonObject top = root.createNestedObject("Klipper Printing Percentage");
top["Enabled"] = enabled;
top["Klipper IP"] = ip;
top["Direction"] = direction;
JsonObject top = root.createNestedObject(F("Klipper Printing Percentage"));
top[F("Enabled")] = enabled;
top[F("Klipper IP")] = ip;
top[F("Direction")] = direction;
}
bool readFromConfig(JsonObject &root)
@ -117,12 +117,12 @@ public:
// 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["Klipper Printing Percentage"];
JsonObject top = root[F("Klipper Printing Percentage")];
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top["Klipper IP"], ip);
configComplete &= getJsonValue(top["Enabled"], enabled);
configComplete &= getJsonValue(top["Direction"], direction);
configComplete &= getJsonValue(top[F("Klipper IP")], ip);
configComplete &= getJsonValue(top[F("Enabled")], enabled);
configComplete &= getJsonValue(top[F("Direction")], direction);
return configComplete;
}

View File

@ -7,4 +7,4 @@ Contains a modification to use WLED in combination with the Ping Pong Ball LED C
To install this Usermod, you instruct PlatformIO to compile the Project with the USERMOD_PING_PONG_CLOCK flag.
WLED then automatically provides you with various settings on the Usermod Page.
Note: Depending on the size of your clock, you may have to update the led indices for the indivdual numbers and the base indices.
Note: Depending on the size of your clock, you may have to update the led indices for the individual numbers and the base indices.

View File

@ -18,15 +18,15 @@ private:
// ---- Variables for correct LED numbering below, edit only if your clock is built different ----
int baseH = 43; // Adress for the one place of the hours
int baseHH = 7; // Adress for the tens place of the hours
int baseM = 133; // Adress for the one place of the minutes
int baseMM = 97; // Adress for the tens place of the minutes
int colon1 = 79; // Adress for the first colon led
int colon2 = 80; // Adress for the second colon led
int baseH = 43; // Address for the one place of the hours
int baseHH = 7; // Address for the tens place of the hours
int baseM = 133; // Address for the one place of the minutes
int baseMM = 97; // Address for the tens place of the minutes
int colon1 = 79; // Address for the first colon led
int colon2 = 80; // Address for the second colon led
// Matrix for the illumination of the numbers
// Note: These only define the increments of the base adress. e.g. to define the second Minute you have to add the baseMM to every led position
// Note: These only define the increments of the base address. e.g. to define the second Minute you have to add the baseMM to every led position
const int numbers[10][10] =
{
{ 0, 1, 4, 6, 13, 15, 18, 19, -1, -1 }, // 0: null

View File

@ -4,7 +4,7 @@
//
// Inspired by the original v2 usermods
// * usermod_v2_rotaty_encoder_ui
// * usermod_v2_rotary_encoder_ui
//
// v2 usermod that provides a rotary encoder-based UI.
//
@ -99,7 +99,7 @@ static int re_qstringCmp(const void *ap, const void *bp) {
// Lowercase
bVal -= 32;
}
// Relly we shouldn't ever get to '\0'
// Really we shouldn't ever get to '\0'
if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') {
// We're done. one is a substring of the other
// or something happenend and the quote didn't stop us.
@ -596,7 +596,7 @@ void RotaryEncoderUIUsermod::loop()
bool changedState = false;
char lineBuffer[64];
do {
// finde new state
// find new state
switch (newState) {
case 0: strcpy_P(lineBuffer, PSTR("Brightness")); changedState = true; break;
case 1: if (!extractModeSlider(effectCurrent, 0, lineBuffer, 63)) newState++; else changedState = true; break; // speed

View File

@ -8,7 +8,7 @@ active: enable/disable usermod
diplayItIs: enable/disable display of "Es ist" on the clock
ledOffset: number of LEDs before the wordclock LEDs
### Update for alternatative wiring pattern
### Update for alternative wiring pattern
Based on this fantastic work I added an alternative wiring pattern.
The original used a long wire to connect DO to DI, from one line to the next line.

View File

@ -7,8 +7,8 @@
* 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:
* The visualisation is described in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr").
* There are 2 parameters to change the behaviour:
*
* active: enable/disable usermod
* diplayItIs: enable/disable display of "Es ist" on the clock.

View File

@ -66,7 +66,7 @@ class WireguardUsermod : public Usermod {
void addToConfig(JsonObject& root) {
JsonObject top = root.createNestedObject(F("WireGuard"));
top[F("host")] = endpoint_address;
top[F("port")] = endpoint_port;
top["port"] = endpoint_port;
top["ip"] = local_ip.toString();
top["psk"] = preshared_key;
top[F("pem")] = private_key;
@ -77,7 +77,7 @@ class WireguardUsermod : public Usermod {
bool readFromConfig(JsonObject& root) {
JsonObject top = root[F("WireGuard")];
if (top[F("host")].isNull() || top[F("port")].isNull() || top["ip"].isNull() || top[F("pem")].isNull() || top[F("pub")].isNull() || top[F("tz")].isNull()) {
if (top[F("host")].isNull() || top["port"].isNull() || top["ip"].isNull() || top[F("pem")].isNull() || top[F("pub")].isNull() || top[F("tz")].isNull()) {
is_enabled = false;
return false;
} else {

View File

@ -1,6 +1,6 @@
# Controlling Wiz lights
Enabless controlling [WiZ](https://www.wizconnected.com/en/consumer/) lights that are part of the same network as the WLED controller.
Enables controlling [WiZ](https://www.wizconnected.com/en/consumer/) lights that are part of the same network as the WLED controller.
The mod takes the colors from the first few pixels and sends them to the lights.
@ -8,7 +8,7 @@ The mod takes the colors from the first few pixels and sends them to the lights.
- Interval (ms)
- How frequently to update the WiZ lights, in milliseconds.
- Setting it too low may causse the ESP to become unresponsive.
- Setting it too low may cause the ESP to become unresponsive.
- Send Delay (ms)
- An optional millisecond delay after updating each WiZ light.
- Can help smooth out effects when using a large number of WiZ lights

View File

@ -51,7 +51,7 @@ uint16_t triwave16(uint16_t in) {
* Generates a tristate square wave w/ attac & decay
* @param x input value 0-255
* @param pulsewidth 0-127
* @param attdec attac & decay, max. pulsewidth / 2
* @param attdec attack & decay, max. pulsewidth / 2
* @returns signed waveform value
*/
int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) {
@ -1239,7 +1239,7 @@ uint16_t mode_fireworks() {
if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, col);
else SEGMENT.setPixelColor(index, col);
SEGENV.aux1 = SEGENV.aux0; // old spark
SEGENV.aux0 = index; // remember where spark occured
SEGENV.aux0 = index; // remember where spark occurred
}
}
return FRAMETIME;
@ -1272,8 +1272,8 @@ uint16_t mode_rain() {
SEGENV.aux0++; // increase spark index
SEGENV.aux1++;
}
if (SEGENV.aux0 == 0) SEGENV.aux0 = UINT16_MAX; // reset previous spark positiom
if (SEGENV.aux1 == 0) SEGENV.aux0 = UINT16_MAX; // reset previous spark positiom
if (SEGENV.aux0 == 0) SEGENV.aux0 = UINT16_MAX; // reset previous spark position
if (SEGENV.aux1 == 0) SEGENV.aux0 = UINT16_MAX; // reset previous spark position
if (SEGENV.aux0 >= width*height) SEGENV.aux0 = 0; // ignore
if (SEGENV.aux1 >= width*height) SEGENV.aux1 = 0;
}
@ -3777,7 +3777,7 @@ uint16_t mode_tetrix(void) {
}
if (drop->step == 0) { // init brick
// speed calcualtion: a single brick should reach bottom of strip in X seconds
// speed calculation: a single brick should reach bottom of strip in X seconds
// if the speed is set to 1 this should take 5s and at 255 it should take 0.25s
// as this is dependant on SEGLEN it should be taken into account and the fact that effect runs every FRAMETIME s
int speed = SEGMENT.speed ? SEGMENT.speed : random8(1,255);
@ -3860,7 +3860,7 @@ static const char _data_FX_MODE_PLASMA[] PROGMEM = "Plasma@Phase,!;!;!";
/*
* Percentage display
* Intesity values from 0-100 turn on the leds.
* Intensity values from 0-100 turn on the leds.
*/
uint16_t mode_percent(void) {
@ -3913,7 +3913,7 @@ static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@,% of fill,,,,One c
/*
* Modulates the brightness similar to a heartbeat
* (unimplemented?) tries to draw an ECG aproximation on a 2D matrix
* (unimplemented?) tries to draw an ECG approximation on a 2D matrix
*/
uint16_t mode_heartbeat(void) {
uint8_t bpm = 40 + (SEGMENT.speed >> 3);
@ -4593,7 +4593,7 @@ uint16_t mode_tv_simulator(void) {
// how much time is elapsed ?
tvSimulator->elapsed = strip.now - tvSimulator->startTime;
// fade from prev volor to next color
// fade from prev color to next color
if (tvSimulator->elapsed < tvSimulator->fadeTime) {
r = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pr, nr);
g = map(tvSimulator->elapsed, 0, tvSimulator->fadeTime, tvSimulator->pg, ng);
@ -5011,21 +5011,25 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
const uint16_t colsCenter = (cols>>1) + (cols%2);
const uint16_t rowsCenter = (rows>>1) + (rows%2);
SEGMENT.fadeToBlackBy(128);
const uint16_t maxDim = MAX(cols, rows)/2;
unsigned long t = strip.now / (32 - (SEGMENT.speed>>3));
unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup
for (float i = 1; i < maxDim; i += 0.25) {
for (float i = 1.0f; i < maxDim; i += 0.25f) {
float angle = radians(t * (maxDim - i));
uint16_t myX = (cols>>1) + (uint16_t)(sin_t(angle) * i) + (cols%2);
uint16_t myY = (rows>>1) + (uint16_t)(cos_t(angle) * i) + (rows%2);
SEGMENT.setPixelColorXY(myX, myY, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND));
int16_t mySin = sin_t(angle) * i;
int16_t myCos = cos_t(angle) * i;
SEGMENT.setPixelColorXY(colsCenter + mySin, rowsCenter + myCos, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND));
if (SEGMENT.check1) SEGMENT.setPixelColorXY(colsCenter + myCos, rowsCenter + mySin, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND));
}
SEGMENT.blur(SEGMENT.intensity>>3);
return FRAMETIME;
} // mode_2DDrift()
static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount;;!;2";
static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount,,,,Twin;;!;2";
//////////////////////////
@ -6198,8 +6202,9 @@ uint16_t mode_2Ddriftrose(void) {
SEGMENT.fadeToBlackBy(32+(SEGMENT.speed>>3));
for (size_t i = 1; i < 37; i++) {
uint32_t x = (CX + (sin_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f;
uint32_t y = (CY + (cos_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f;
float angle = radians(i * 10);
uint32_t x = (CX + (sin_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f;
uint32_t y = (CY + (cos_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f;
SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255));
}
SEGMENT.blur((SEGMENT.intensity>>4)+1);
@ -6498,7 +6503,7 @@ uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline.
SEGMENT.fade_out(251); // 30%
float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f;
segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivty" upscaling
segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivity" upscaling
float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 32, 0, (float)SEGLEN/2.0f); // map to pixels available in current segment
uint16_t tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing.
@ -6550,7 +6555,7 @@ uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew
SEGMENT.fade_out(253); // 50%
float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f;
segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivty" upscaling
segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivity" upscaling
float mySampleAvg = mapf(segmentSampleAvg*2.0, 0.0f, 32.0f, 0.0f, (float)SEGLEN/2.0f); // map to pixels availeable in current segment
int tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing.
@ -6599,7 +6604,7 @@ uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline.
SEGMENT.fade_out(249); // 25%
float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0;
segmentSampleAvg *= 0.25; // divide by 4, to compensate for later "sensitivty" upscaling
segmentSampleAvg *= 0.25; // divide by 4, to compensate for later "sensitivity" upscaling
float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 64, 0, (SEGLEN-1)); // map to pixels availeable in current segment
int tempsamp = constrain(mySampleAvg,0,SEGLEN-1); // Keep the sample from overflowing.
@ -6699,7 +6704,7 @@ uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline.
SEGMENT.fade_out(SEGMENT.speed);
float tmpSound2 = volumeSmth * (float)SEGMENT.intensity / 256.0; // Too sensitive.
tmpSound2 *= (float)SEGMENT.intensity / 128.0; // Reduce sensitity/length.
tmpSound2 *= (float)SEGMENT.intensity / 128.0; // Reduce sensitivity/length.
int maxLen = mapf(tmpSound2, 0, 127, 0, SEGLEN/2);
if (maxLen >SEGLEN/2) maxLen = SEGLEN/2;
@ -7110,7 +7115,7 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch
if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1;
// MajorPeak holds the freq. value which is most abundant in the last sample.
// With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz
// With our sampling rate of 10240Hz we have a usable freq range from roughly 80Hz to 10240/2 Hz
// we will treat everything with less than 65Hz as 0
if (FFT_MajorPeak < 80) {
@ -7131,7 +7136,7 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch
return FRAMETIME;
} // mode_freqmatrix()
static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound effect,Low bin,High bin,Sensivity;;;1f;m12=3,si=0"; // Corner, Beatsin
static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Speed,Sound effect,Low bin,High bin,Sensitivity;;;1f;m12=3,si=0"; // Corner, Beatsin
//////////////////////
@ -7215,7 +7220,7 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun
if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1.0f;
// MajorPeak holds the freq. value which is most abundant in the last sample.
// With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz
// With our sampling rate of 10240Hz we have a usable freq range from roughly 80Hz to 10240/2 Hz
// we will treat everything with less than 65Hz as 0
if (FFT_MajorPeak < 80) {
@ -7262,7 +7267,7 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline.
SEGMENT.fade_out(250);
float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f;
segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivty" upscaling
segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivity" upscaling
float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0,32, 0, (float)SEGLEN/2.0f); // map to pixels availeable in current segment
int tempsamp = constrain(mySampleAvg,0,SEGLEN/2); // Keep the sample from overflowing.
@ -7290,7 +7295,7 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline.
return FRAMETIME;
} // mode_gravfreq()
static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sensivity;!,!;!;1f;ix=128,m12=0,si=0"; // Pixels, Beatsin
static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sensitivity;!,!;!;1f;ix=128,m12=0,si=0"; // Pixels, Beatsin
//////////////////////
@ -7882,7 +7887,7 @@ static const char _data_FX_MODE_2DWAVINGCELL[] PROGMEM = "Waving Cell@!,,Amplitu
static const char _data_RESERVED[] PROGMEM = "RSVD";
// add (or replace reserved) effect mode and data into vector
// use id==255 to find unallocatd gaps (with "Reserved" data string)
// use id==255 to find unallocated gaps (with "Reserved" data string)
// if vector size() is smaller than id (single) data is appended at the end (regardless of id)
void WS2812FX::addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name) {
if (id == 255) { // find empty slot

View File

@ -420,7 +420,8 @@ typedef struct Segment {
// perhaps this should be per segment, not static
static CRGBPalette16 _randomPalette; // actual random palette
static CRGBPalette16 _newRandomPalette; // target random palette
static unsigned long _lastPaletteChange; // last random palette change time in millis()
static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000
static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF
#ifndef WLED_DISABLE_MODE_BLEND
static bool _modeBlend; // mode/effect blending semaphore
#endif
@ -436,7 +437,7 @@ typedef struct Segment {
uint8_t _briT; // temporary brightness
uint8_t _cctT; // temporary CCT
CRGBPalette16 _palT; // temporary palette
uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 belnds possible)
uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 blends possible)
unsigned long _start; // must accommodate millis()
uint16_t _dur;
Transition(uint16_t dur=750)

View File

@ -133,7 +133,7 @@ void WS2812FX::setUpMatrix() {
DEBUG_PRINT(F("Matrix ledmap:"));
for (unsigned i=0; i<customMappingSize; i++) {
if (!(i%Segment::maxWidth)) DEBUG_PRINTLN();
DEBUG_PRINTF("%4d,", customMappingTable[i]);
DEBUG_PRINTF_P(PSTR("%4d,"), customMappingTable[i]);
}
DEBUG_PRINTLN();
#endif

View File

@ -77,9 +77,10 @@ uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for t
uint16_t Segment::maxWidth = DEFAULT_LED_COUNT;
uint16_t Segment::maxHeight = 1;
CRGBPalette16 Segment::_randomPalette = CRGBPalette16(DEFAULT_COLOR);
CRGBPalette16 Segment::_newRandomPalette = CRGBPalette16(DEFAULT_COLOR);
unsigned long Segment::_lastPaletteChange = 0; // perhaps it should be per segment
CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment
uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only)
#ifndef WLED_DISABLE_MODE_BLEND
bool Segment::_modeBlend = false;
@ -87,7 +88,7 @@ bool Segment::_modeBlend = false;
// copy constructor
Segment::Segment(const Segment &orig) {
//DEBUG_PRINTF("-- Copy segment constructor: %p -> %p\n", &orig, this);
//DEBUG_PRINTF_P(PSTR("-- Copy segment constructor: %p -> %p\n"), &orig, this);
memcpy((void*)this, (void*)&orig, sizeof(Segment));
_t = nullptr; // copied segment cannot be in transition
name = nullptr;
@ -99,7 +100,7 @@ Segment::Segment(const Segment &orig) {
// move constructor
Segment::Segment(Segment &&orig) noexcept {
//DEBUG_PRINTF("-- Move segment constructor: %p -> %p\n", &orig, this);
//DEBUG_PRINTF_P(PSTR("-- Move segment constructor: %p -> %p\n"), &orig, this);
memcpy((void*)this, (void*)&orig, sizeof(Segment));
orig._t = nullptr; // old segment cannot be in transition any more
orig.name = nullptr;
@ -109,7 +110,7 @@ Segment::Segment(Segment &&orig) noexcept {
// copy assignment
Segment& Segment::operator= (const Segment &orig) {
//DEBUG_PRINTF("-- Copying segment: %p -> %p\n", &orig, this);
//DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
// clean destination
if (name) { delete[] name; name = nullptr; }
@ -129,7 +130,7 @@ Segment& Segment::operator= (const Segment &orig) {
// move assignment
Segment& Segment::operator= (Segment &&orig) noexcept {
//DEBUG_PRINTF("-- Moving segment: %p -> %p\n", &orig, this);
//DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
if (name) { delete[] name; name = nullptr; } // free old name
stopTransition();
@ -150,12 +151,12 @@ bool IRAM_ATTR Segment::allocateData(size_t len) {
if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation
return true;
}
//DEBUG_PRINTF("-- Allocating data (%d): %p\n", len, this);
//DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n", len, this);
deallocateData(); // if the old buffer was smaller release it first
if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) {
// not enough memory
DEBUG_PRINT(F("!!! Effect RAM depleted: "));
DEBUG_PRINTF("%d/%d !!!\n", len, Segment::getUsedSegmentData());
DEBUG_PRINTF_P(PSTR("%d/%d !!!\n"), len, Segment::getUsedSegmentData());
errorFlag = ERR_NORAM;
return false;
}
@ -163,21 +164,21 @@ bool IRAM_ATTR Segment::allocateData(size_t len) {
data = (byte*)calloc(len, sizeof(byte));
if (!data) { DEBUG_PRINTLN(F("!!! Allocation failed. !!!")); return false; } // allocation failed
Segment::addUsedSegmentData(len);
//DEBUG_PRINTF("--- Allocated data (%p): %d/%d -> %p\n", this, len, Segment::getUsedSegmentData(), data);
//DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data);
_dataLen = len;
return true;
}
void IRAM_ATTR Segment::deallocateData() {
if (!data) { _dataLen = 0; return; }
//DEBUG_PRINTF("--- Released data (%p): %d/%d -> %p\n", this, _dataLen, Segment::getUsedSegmentData(), data);
//DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data);
if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer
free(data);
} else {
DEBUG_PRINT(F("---- Released data "));
DEBUG_PRINTF("(%p): ", this);
DEBUG_PRINTF_P(PSTR("(%p): "), this);
DEBUG_PRINT(F("inconsistent UsedSegmentData "));
DEBUG_PRINTF("(%d/%d)", _dataLen, Segment::getUsedSegmentData());
DEBUG_PRINTF_P(PSTR("(%d/%d)"), _dataLen, Segment::getUsedSegmentData());
DEBUG_PRINTLN(F(", cowardly refusing to free nothing."));
}
data = nullptr;
@ -194,7 +195,7 @@ void IRAM_ATTR Segment::deallocateData() {
*/
void Segment::resetIfRequired() {
if (!reset) return;
//DEBUG_PRINTF("-- Segment reset: %p\n", this);
//DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this);
if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData())
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
reset = false;
@ -220,20 +221,9 @@ CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint
switch (pal) {
case 0: //default palette. Exceptions for specific effects above
targetPalette = PartyColors_p; break;
case 1: {//periodically replace palette with a random one
unsigned long timeSinceLastChange = millis() - _lastPaletteChange;
if (timeSinceLastChange > randomPaletteChangeTime * 1000U) {
_randomPalette = _newRandomPalette;
_newRandomPalette = CRGBPalette16(
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)));
_lastPaletteChange = millis();
handleRandomPalette(); // do a 1st pass of blend
}
targetPalette = _randomPalette;
break;}
case 1: //randomly generated palette
targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette()
break;
case 2: {//primary color only
CRGB prim = gamma32(colors[0]);
targetPalette = CRGBPalette16(prim); break;}
@ -246,7 +236,7 @@ CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint
CRGB sec = gamma32(colors[1]);
CRGB ter = gamma32(colors[2]);
targetPalette = CRGBPalette16(ter,sec,prim); break;}
case 5: {//primary + secondary (+tert if not off), more distinct
case 5: {//primary + secondary (+tertiary if not off), more distinct
CRGB prim = gamma32(colors[0]);
CRGB sec = gamma32(colors[1]);
if (colors[2]) {
@ -285,7 +275,7 @@ CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint
void Segment::startTransition(uint16_t dur) {
if (dur == 0) {
if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransisiton()
if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransition()
return;
}
if (isInTransition()) return; // already in transition no need to store anything
@ -294,7 +284,7 @@ void Segment::startTransition(uint16_t dur) {
_t = new Transition(dur); // no previous transition running
if (!_t) return; // failed to allocate data
//DEBUG_PRINTF("-- Started transition: %p (%p)\n", this, _t);
//DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t);
loadPalette(_t->_palT, palette);
_t->_briT = on ? opacity : 0;
_t->_cctT = cct;
@ -307,7 +297,7 @@ void Segment::startTransition(uint16_t dur) {
if (_dataLen > 0 && data) {
_t->_segT._dataT = (byte *)malloc(_dataLen);
if (_t->_segT._dataT) {
//DEBUG_PRINTF("-- Allocated duplicate data (%d) for %p: %p\n", _dataLen, this, _t->_segT._dataT);
//DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT);
memcpy(_t->_segT._dataT, data, _dataLen);
_t->_segT._dataLenT = _dataLen;
}
@ -322,10 +312,10 @@ void Segment::startTransition(uint16_t dur) {
void Segment::stopTransition() {
if (isInTransition()) {
//DEBUG_PRINTF("-- Stopping transition: %p\n", this);
//DEBUG_PRINTF_P(PSTR("-- Stopping transition: %p\n"), this);
#ifndef WLED_DISABLE_MODE_BLEND
if (_t->_segT._dataT && _t->_segT._dataLenT > 0) {
//DEBUG_PRINTF("-- Released duplicate data (%d) for %p: %p\n", _t->_segT._dataLenT, this, _t->_segT._dataT);
//DEBUG_PRINTF_P(PSTR("-- Released duplicate data (%d) for %p: %p\n"), _t->_segT._dataLenT, this, _t->_segT._dataT);
free(_t->_segT._dataT);
_t->_segT._dataT = nullptr;
_t->_segT._dataLenT = 0;
@ -352,7 +342,7 @@ uint16_t IRAM_ATTR Segment::progress() {
#ifndef WLED_DISABLE_MODE_BLEND
void Segment::swapSegenv(tmpsegd_t &tmpSeg) {
//DEBUG_PRINTF("-- Saving temp seg: %p->(%p) [%d->%p]\n", this, &tmpSeg, _dataLen, data);
//DEBUG_PRINTF_P(PSTR("-- Saving temp seg: %p->(%p) [%d->%p]\n"), this, &tmpSeg, _dataLen, data);
tmpSeg._optionsT = options;
for (size_t i=0; i<NUM_COLORS; i++) tmpSeg._colorT[i] = colors[i];
tmpSeg._speedT = speed;
@ -391,14 +381,14 @@ void Segment::swapSegenv(tmpsegd_t &tmpSeg) {
}
void Segment::restoreSegenv(tmpsegd_t &tmpSeg) {
//DEBUG_PRINTF("-- Restoring temp seg: %p->(%p) [%d->%p]\n", &tmpSeg, this, _dataLen, data);
//DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data);
if (_t && &(_t->_segT) != &tmpSeg) {
// update possibly changed variables to keep old effect running correctly
_t->_segT._aux0T = aux0;
_t->_segT._aux1T = aux1;
_t->_segT._stepT = step;
_t->_segT._callT = call;
//if (_t->_segT._dataT != data) DEBUG_PRINTF("--- data re-allocated: (%p) %p -> %p\n", this, _t->_segT._dataT, data);
//if (_t->_segT._dataT != data) DEBUG_PRINTF_P(PSTR("--- data re-allocated: (%p) %p -> %p\n"), this, _t->_segT._dataT, data);
_t->_segT._dataT = data;
_t->_segT._dataLenT = _dataLen;
}
@ -462,10 +452,22 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u
return targetPalette;
}
// relies on WS2812FX::service() to call it max every 8ms or more (MIN_SHOW_DELAY)
// relies on WS2812FX::service() to call it for each frame
void Segment::handleRandomPalette() {
// just do a blend; if the palettes are identical it will just compare 48 bytes (same as _randomPalette == _newRandomPalette)
// this will slowly blend _newRandomPalette into _randomPalette every 15ms or 8ms (depending on MIN_SHOW_DELAY)
// is it time to generate a new palette?
if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) {
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = millis()/1000U;
_lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately
}
// if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls)
if (strip.paletteFade) {
// assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less)
// in reality there need to be 255 blends to fully blend two entirely different palettes
if ((millis() & 0xFFFF) - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update
_lastPaletteBlend = millis();
}
nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48);
}
@ -1226,7 +1228,7 @@ uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) {
}
void WS2812FX::show(void) {
// avoid race condition, caputre _callback value
// avoid race condition, capture _callback value
show_callback callback = _callback;
if (callback) callback();
@ -1253,7 +1255,7 @@ bool WS2812FX::isUpdating() {
/**
* Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough.
* Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accurary varies
* Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies
*/
uint16_t WS2812FX::getFps() {
if (millis() - _lastShow > 2000) return 0;
@ -1585,12 +1587,12 @@ void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) {
void WS2812FX::printSize() {
size_t size = 0;
for (const Segment &seg : _segments) size += seg.getSize();
DEBUG_PRINTF("Segments: %d -> %uB\n", _segments.size(), size);
DEBUG_PRINTF("Modes: %d*%d=%uB\n", sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr)));
DEBUG_PRINTF("Data: %d*%d=%uB\n", sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *)));
DEBUG_PRINTF("Map: %d*%d=%uB\n", sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t));
DEBUG_PRINTF_P(PSTR("Segments: %d -> %uB\n"), _segments.size(), size);
DEBUG_PRINTF_P(PSTR("Modes: %d*%d=%uB\n"), sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr)));
DEBUG_PRINTF_P(PSTR("Data: %d*%d=%uB\n"), sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *)));
DEBUG_PRINTF_P(PSTR("Map: %d*%d=%uB\n"), sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t));
size = getLengthTotal();
if (useGlobalLedBuffer) DEBUG_PRINTF("Buffer: %d*%u=%uB\n", sizeof(CRGB), size, size*sizeof(CRGB));
if (useGlobalLedBuffer) DEBUG_PRINTF_P(PSTR("Buffer: %d*%u=%uB\n"), sizeof(CRGB), size, size*sizeof(CRGB));
}
#endif
@ -1619,7 +1621,7 @@ void WS2812FX::loadCustomPalettes() {
tcp[ j ] = (uint8_t) pal[ i ].as<int>(); // index
colorFromHexString(rgbw, pal[i+1].as<const char *>()); // will catch non-string entires
for (size_t c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component
DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3]));
DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3]));
}
} else {
size_t palSize = MIN(pal.size(), 72);
@ -1629,7 +1631,7 @@ void WS2812FX::loadCustomPalettes() {
tcp[i+1] = gamma8((uint8_t) pal[i+1].as<int>()); // R
tcp[i+2] = gamma8((uint8_t) pal[i+2].as<int>()); // G
tcp[i+3] = gamma8((uint8_t) pal[i+3].as<int>()); // B
DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3]));
DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3]));
}
}
customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp));

61
wled00/bus_manager.cpp Executable file → Normal file
View File

@ -12,7 +12,6 @@
//colors.cpp
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb);
void colorRGBtoRGBW(byte* rgb);
//udp.cpp
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false);
@ -32,10 +31,12 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte
#define DEBUG_PRINT(x) DEBUGOUT.print(x)
#define DEBUG_PRINTLN(x) DEBUGOUT.println(x)
#define DEBUG_PRINTF(x...) DEBUGOUT.printf(x)
#define DEBUG_PRINTF_P(x...) DEBUGOUT.printf_P(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINTF(x...)
#define DEBUG_PRINTF_P(x...)
#endif
//color mangling macros
@ -375,13 +376,22 @@ BusPwm::BusPwm(BusConfig &bc)
_frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ;
#ifdef ESP8266
analogWriteRange(255); //same range as one RGB channel
// duty cycle resolution (_depth) can be extracted from this formula: 1MHz > _frequency * 2^_depth
if (_frequency > 1760) _depth = 8;
else if (_frequency > 880) _depth = 9;
else _depth = 10; // WLED_PWM_FREQ <= 880Hz
analogWriteRange((1<<_depth)-1);
analogWriteFreq(_frequency);
#else
_ledcStart = pinManager.allocateLedc(numPins);
if (_ledcStart == 255) { //no more free LEDC channels
deallocatePins(); return;
}
// duty cycle resolution (_depth) can be extracted from this formula: 80MHz > _frequency * 2^_depth
if (_frequency > 78124) _depth = 9;
else if (_frequency > 39062) _depth = 10;
else if (_frequency > 19531) _depth = 11;
else _depth = 12; // WLED_PWM_FREQ <= 19531Hz
#endif
for (unsigned i = 0; i < numPins; i++) {
@ -393,7 +403,7 @@ BusPwm::BusPwm(BusConfig &bc)
#ifdef ESP8266
pinMode(_pins[i], OUTPUT);
#else
ledcSetup(_ledcStart + i, _frequency, 8);
ledcSetup(_ledcStart + i, _frequency, _depth);
ledcAttachPin(_pins[i], _ledcStart + i);
#endif
}
@ -460,12 +470,49 @@ uint32_t BusPwm::getPixelColor(uint16_t pix) {
return RGBW32(_data[0], _data[1], _data[2], _data[3]);
}
#ifndef ESP8266
static const uint16_t cieLUT[256] = {
0, 2, 4, 5, 7, 9, 11, 13, 15, 16,
18, 20, 22, 24, 26, 27, 29, 31, 33, 35,
34, 36, 37, 39, 41, 43, 45, 47, 49, 52,
54, 56, 59, 61, 64, 67, 69, 72, 75, 78,
81, 84, 87, 90, 94, 97, 100, 104, 108, 111,
115, 119, 123, 127, 131, 136, 140, 144, 149, 154,
158, 163, 168, 173, 178, 183, 189, 194, 200, 205,
211, 217, 223, 229, 235, 241, 247, 254, 261, 267,
274, 281, 288, 295, 302, 310, 317, 325, 333, 341,
349, 357, 365, 373, 382, 391, 399, 408, 417, 426,
436, 445, 455, 464, 474, 484, 494, 505, 515, 526,
536, 547, 558, 569, 580, 592, 603, 615, 627, 639,
651, 663, 676, 689, 701, 714, 727, 741, 754, 768,
781, 795, 809, 824, 838, 853, 867, 882, 897, 913,
928, 943, 959, 975, 991, 1008, 1024, 1041, 1058, 1075,
1092, 1109, 1127, 1144, 1162, 1180, 1199, 1217, 1236, 1255,
1274, 1293, 1312, 1332, 1352, 1372, 1392, 1412, 1433, 1454,
1475, 1496, 1517, 1539, 1561, 1583, 1605, 1628, 1650, 1673,
1696, 1719, 1743, 1767, 1791, 1815, 1839, 1864, 1888, 1913,
1939, 1964, 1990, 2016, 2042, 2068, 2095, 2121, 2148, 2176,
2203, 2231, 2259, 2287, 2315, 2344, 2373, 2402, 2431, 2461,
2491, 2521, 2551, 2581, 2612, 2643, 2675, 2706, 2738, 2770,
2802, 2835, 2867, 2900, 2934, 2967, 3001, 3035, 3069, 3104,
3138, 3174, 3209, 3244, 3280, 3316, 3353, 3389, 3426, 3463,
3501, 3539, 3576, 3615, 3653, 3692, 3731, 3770, 3810, 3850,
3890, 3930, 3971, 4012, 4053, 4095
};
#endif
void BusPwm::show() {
if (!_valid) return;
uint8_t numPins = NUM_PWM_PINS(_type);
unsigned maxBri = (1<<_depth) - 1;
#ifdef ESP8266
unsigned pwmBri = (unsigned)(roundf(powf((float)_bri / 255.0f, 1.7f) * (float)maxBri + 0.5f)); // using gamma 1.7 to extrapolate PWM duty cycle
#else
unsigned pwmBri = cieLUT[_bri] >> (12 - _depth); // use CIE LUT
#endif
for (unsigned i = 0; i < numPins; i++) {
uint8_t scaled = (_data[i] * _bri) / 255;
if (_reversed) scaled = 255 - scaled;
unsigned scaled = (_data[i] * pwmBri) / 255;
if (_reversed) scaled = maxBri - scaled;
#ifdef ESP8266
analogWrite(_pins[i], scaled);
#else
@ -552,6 +599,10 @@ BusNetwork::BusNetwork(BusConfig &bc)
_rgbw = false;
_UDPtype = 2;
break;
case TYPE_NET_ARTNET_RGBW:
_rgbw = true;
_UDPtype = 2;
break;
case TYPE_NET_E131_RGB:
_rgbw = false;
_UDPtype = 1;

View File

@ -242,7 +242,7 @@ class BusDigital : public Bus {
uint8_t* chan = (uint8_t*) &c;
for (uint_fast8_t i=0; i<4; i++) {
uint_fast16_t val = chan[i];
chan[i] = ((val << 8) + restoreBri) / (restoreBri + 1); //adding _bri slighly improves recovery / stops degradation on re-scale
chan[i] = ((val << 8) + restoreBri) / (restoreBri + 1); //adding _bri slightly improves recovery / stops degradation on re-scale
}
}
return c;
@ -268,6 +268,7 @@ class BusPwm : public Bus {
#ifdef ARDUINO_ARCH_ESP32
uint8_t _ledcStart;
#endif
uint8_t _depth;
uint16_t _frequency;
void deallocatePins();

View File

@ -74,17 +74,17 @@
#define I_32_RN_NEO_3 21
#define I_32_I0_NEO_3 22
#define I_32_I1_NEO_3 23
#define I_32_BB_NEO_3 24 // bitbangging on ESP32 not recommended
#define I_32_BB_NEO_3 24 // bitbanging on ESP32 not recommended
//RGBW
#define I_32_RN_NEO_4 25
#define I_32_I0_NEO_4 26
#define I_32_I1_NEO_4 27
#define I_32_BB_NEO_4 28 // bitbangging on ESP32 not recommended
#define I_32_BB_NEO_4 28 // bitbanging on ESP32 not recommended
//400Kbps
#define I_32_RN_400_3 29
#define I_32_I0_400_3 30
#define I_32_I1_400_3 31
#define I_32_BB_400_3 32 // bitbangging on ESP32 not recommended
#define I_32_BB_400_3 32 // bitbanging on ESP32 not recommended
//TM1814 (RGBW)
#define I_32_RN_TM1_4 33
#define I_32_I0_TM1_4 34

View File

@ -21,7 +21,6 @@ void shortPressAction(uint8_t b)
case 1: ++effectCurrent %= strip.getModeCount(); stateChanged = true; colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
}
@ -43,7 +42,6 @@ void longPressAction(uint8_t b)
case 1: bri += 8; stateUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action
}
} else {
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
}
@ -65,7 +63,6 @@ void doublePressAction(uint8_t b)
case 1: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET);
}
@ -168,8 +165,6 @@ void handleAnalog(uint8_t b)
#endif
yield(); // keep WiFi task running - analog read may take several millis on ESP8266
DEBUG_PRINT(F("Analog: Raw read = ")); DEBUG_PRINTLN(rawReading);
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
@ -180,7 +175,8 @@ void handleAnalog(uint8_t b)
// remove noise & reduce frequency of UI updates
if (abs(int(aRead) - int(oldRead[b])) <= POT_SENSITIVITY) return; // no significant change in reading
DEBUG_PRINT(F("Analog: Filtered read = ")); DEBUG_PRINTLN(aRead);
DEBUG_PRINT(F("Analog: Raw = ")); DEBUG_PRINT(rawReading);
DEBUG_PRINT(F(" Filtered = ")); DEBUG_PRINTLN(aRead);
// 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()?)
@ -188,7 +184,6 @@ void handleAnalog(uint8_t b)
//while(strip.isUpdating() && (millis() - wait_started < STRIP_WAIT_TIME)) {
// delay(1);
//}
//if (strip.isUpdating()) return; // give up
oldRead[b] = aRead;
@ -240,11 +235,11 @@ void handleAnalog(uint8_t b)
void handleButton()
{
static unsigned long lastRead = 0UL;
static unsigned long lastAnalogRead = 0UL;
static unsigned long lastRun = 0UL;
unsigned long now = millis();
if (strip.isUpdating() && (now - lastRun < 400)) return; // don't interfere with strip update (unless strip is updating continuously, e.g. very long strips)
if (strip.isUpdating() && (now - lastRun < ANALOG_BTN_READ_CYCLE+1)) return; // don't interfere with strip update (unless strip is updating continuously, e.g. very long strips)
lastRun = now;
for (uint8_t b=0; b<WLED_MAX_BUTTONS; b++) {
@ -257,9 +252,8 @@ void handleButton()
if (usermods.handleButton(b)) continue; // did usermod handle buttons
if (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { // button is not a button but a potentiometer
if (now - lastRead > ANALOG_BTN_READ_CYCLE) {
if (now - lastAnalogRead > ANALOG_BTN_READ_CYCLE) {
handleAnalog(b);
lastRead = now;
}
continue;
}
@ -339,6 +333,9 @@ void handleButton()
shortPressAction(b);
}
}
if (now - lastAnalogRead > ANALOG_BTN_READ_CYCLE) {
lastAnalogRead = now;
}
}
// If enabled, RMT idle level is set to HIGH when off
@ -388,7 +385,7 @@ void handleIO()
if (!offMode) {
#ifdef ESP8266
// turn off built-in LED if strip is turned off
// this will break digital bus so will need to be reinitialised on On
// this will break digital bus so will need to be re-initialised on On
PinOwner ledPinOwner = pinManager.getPinOwner(LED_BUILTIN);
if (!strip.isOffRefreshRequired() && (ledPinOwner == PinOwner::None || ledPinOwner == PinOwner::BusDigital)) {
pinMode(LED_BUILTIN, OUTPUT);
@ -405,4 +402,4 @@ void handleIO()
}
offMode = true;
}
}
}

57
wled00/cfg.cpp Executable file → Normal file
View File

@ -180,7 +180,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t ledType = elm["type"] | TYPE_WS2812_RGB;
bool reversed = elm["rev"];
bool refresh = elm["ref"] | false;
uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully)
uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM
uint8_t AWmode = elm[F("rgbwm")] | RGBW_MODE_MANUAL_ONLY;
uint8_t maPerLed = elm[F("ledma")] | 55;
uint16_t maMax = elm[F("maxpwr")] | (ablMilliampsMax * length) / total; // rough (incorrect?) per strip ABL calculation when no config exists
@ -286,13 +286,22 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
// new install/missing configuration (button 0 has defaults)
if (fromFS) {
// relies upon only being called once with fromFS == true, which is currently true.
uint8_t s = 0;
if (pinManager.allocatePin(btnPin[0], false, PinOwner::Button)) { // initialized to #define value BTNPIN, or zero if not defined(!)
++s; // do not clear default button if allocated successfully
}
for (; s<WLED_MAX_BUTTONS; s++) {
btnPin[s] = -1;
buttonType[s] = BTN_TYPE_NONE;
for (size_t s = 0; s < WLED_MAX_BUTTONS; s++) {
if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !pinManager.allocatePin(btnPin[s], false, PinOwner::Button)) {
btnPin[s] = -1;
buttonType[s] = BTN_TYPE_NONE;
}
if (btnPin[s] >= 0) {
if (disablePullUp) {
pinMode(btnPin[s], INPUT);
} else {
#ifdef ESP32
pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else
pinMode(btnPin[s], INPUT_PULLUP);
#endif
}
}
macroButton[s] = 0;
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
@ -302,6 +311,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(touchThreshold,btn_obj[F("tt")]);
CJSON(buttonPublishMqtt,btn_obj["mqtt"]);
#ifndef WLED_DISABLE_INFRARED
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
if (hw_ir_pin > -2) {
pinManager.deallocatePin(irPin, PinOwner::IR);
@ -312,6 +322,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
}
CJSON(irEnabled, hw["ir"]["type"]);
#endif
CJSON(irApplyToAllSelected, hw["ir"]["sel"]);
JsonObject relay = hw[F("relay")];
@ -339,7 +350,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } };
if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) {
#ifdef ESP32
if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initilised (Wire.begin() called prior)
if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initialised (Wire.begin() called prior)
else Wire.begin();
#else
Wire.begin(i2c_sda, i2c_scl);
@ -395,6 +406,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
strip.setTransition(fadeTransition ? transitionDelayDefault : 0);
CJSON(strip.paletteFade, light_tr["pal"]);
CJSON(randomPaletteChangeTime, light_tr[F("rpc")]);
CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]);
JsonObject light_nl = light["nl"];
CJSON(nightlightMode, light_nl["mode"]);
@ -420,7 +432,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(useESPNowSync, if_sync[F("espnow")]);
#endif
JsonObject if_sync_recv = if_sync["recv"];
JsonObject if_sync_recv = if_sync[F("recv")];
CJSON(receiveNotificationBrightness, if_sync_recv["bri"]);
CJSON(receiveNotificationColor, if_sync_recv["col"]);
CJSON(receiveNotificationEffects, if_sync_recv["fx"]);
@ -428,7 +440,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(receiveSegmentOptions, if_sync_recv["seg"]);
CJSON(receiveSegmentBounds, if_sync_recv["sb"]);
JsonObject if_sync_send = if_sync["send"];
JsonObject if_sync_send = if_sync[F("send")];
CJSON(sendNotifications, if_sync_send["en"]);
sendNotificationsRT = sendNotifications;
CJSON(notifyDirect, if_sync_send[F("dir")]);
@ -615,6 +627,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
return (doc["sv"] | true);
}
static const char s_cfg_json[] PROGMEM = "/cfg.json";
void deserializeConfigFromFS() {
bool success = deserializeConfigSec();
if (!success) { //if file does not exist, try reading from EEPROM
@ -628,7 +643,7 @@ void deserializeConfigFromFS() {
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
success = readObjectFromFile("/cfg.json", nullptr, pDoc);
success = readObjectFromFile(s_cfg_json, nullptr, pDoc);
if (!success) { // if file does not exist, optionally try reading from EEPROM and then save defaults to FS
releaseJSONBufferLock();
#ifdef WLED_ADD_EEPROM_SUPPORT
@ -834,8 +849,10 @@ void serializeConfig() {
hw_btn["mqtt"] = buttonPublishMqtt;
JsonObject hw_ir = hw.createNestedObject("ir");
#ifndef WLED_DISABLE_INFRARED
hw_ir["pin"] = irPin;
hw_ir["type"] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled )
#endif
hw_ir["sel"] = irApplyToAllSelected;
JsonObject hw_relay = hw.createNestedObject(F("relay"));
@ -872,6 +889,7 @@ void serializeConfig() {
light_tr["dur"] = transitionDelayDefault / 100;
light_tr["pal"] = strip.paletteFade;
light_tr[F("rpc")] = randomPaletteChangeTime;
light_tr[F("hrp")] = useHarmonicRandomPalette;
JsonObject light_nl = light.createNestedObject("nl");
light_nl["mode"] = nightlightMode;
@ -894,7 +912,7 @@ void serializeConfig() {
if_sync[F("espnow")] = useESPNowSync;
#endif
JsonObject if_sync_recv = if_sync.createNestedObject("recv");
JsonObject if_sync_recv = if_sync.createNestedObject(F("recv"));
if_sync_recv["bri"] = receiveNotificationBrightness;
if_sync_recv["col"] = receiveNotificationColor;
if_sync_recv["fx"] = receiveNotificationEffects;
@ -902,7 +920,7 @@ void serializeConfig() {
if_sync_recv["seg"] = receiveSegmentOptions;
if_sync_recv["sb"] = receiveSegmentBounds;
JsonObject if_sync_send = if_sync.createNestedObject("send");
JsonObject if_sync_send = if_sync.createNestedObject(F("send"));
if_sync_send["en"] = sendNotifications;
if_sync_send[F("dir")] = notifyDirect;
if_sync_send["btn"] = notifyButton;
@ -967,7 +985,7 @@ void serializeConfig() {
if_hue["id"] = huePollLightId;
if_hue[F("iv")] = huePollIntervalMs / 100;
JsonObject if_hue_recv = if_hue.createNestedObject("recv");
JsonObject if_hue_recv = if_hue.createNestedObject(F("recv"));
if_hue_recv["on"] = hueApplyOnOff;
if_hue_recv["bri"] = hueApplyBri;
if_hue_recv["col"] = hueApplyColor;
@ -1050,7 +1068,7 @@ void serializeConfig() {
JsonObject usermods_settings = root.createNestedObject("um");
usermods.addToConfig(usermods_settings);
File f = WLED_FS.open("/cfg.json", "w");
File f = WLED_FS.open(FPSTR(s_cfg_json), "w");
if (f) serializeJson(root, f);
f.close();
releaseJSONBufferLock();
@ -1058,13 +1076,16 @@ void serializeConfig() {
doSerializeConfig = false;
}
static const char s_wsec_json[] PROGMEM = "/wsec.json";
//settings in /wsec.json, not accessible via webserver, for passwords and tokens
bool deserializeConfigSec() {
DEBUG_PRINTLN(F("Reading settings from /wsec.json..."));
if (!requestJSONBufferLock(3)) return false;
bool success = readObjectFromFile("/wsec.json", nullptr, pDoc);
bool success = readObjectFromFile(s_wsec_json, nullptr, pDoc);
if (!success) {
releaseJSONBufferLock();
return false;
@ -1147,7 +1168,7 @@ void serializeConfigSec() {
ota[F("lock-wifi")] = wifiLock;
ota[F("aota")] = aOtaEnabled;
File f = WLED_FS.open("/wsec.json", "w");
File f = WLED_FS.open(FPSTR(s_wsec_json), "w");
if (f) serializeJson(root, f);
f.close();
releaseJSONBufferLock();

View File

@ -91,6 +91,115 @@ void setRandomColor(byte* rgb)
colorHStoRGB(lastRandomIndex*256,255,rgb);
}
/*
* generates a random palette based on harmonic color theory
* takes a base palette as the input, it will choose one color of the base palette and keep it
*/
CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
{
CHSV palettecolors[4]; //array of colors for the new palette
uint8_t keepcolorposition = random8(4); //color position of current random palette to keep
palettecolors[keepcolorposition] = rgb2hsv_approximate(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette
palettecolors[keepcolorposition].hue += random8(10)-5; // +/- 5 randomness of base color
//generate 4 saturation and brightness value numbers
//only one saturation is allowed to be below 200 creating mostly vibrant colors
//only one brightness value number is allowed below 200, creating mostly bright palettes
for (int i = 0; i < 3; i++) { //generate three high values
palettecolors[i].saturation = random8(200,255);
palettecolors[i].value = random8(220,255);
}
//allow one to be lower
palettecolors[3].saturation = random8(20,255);
palettecolors[3].value = random8(80,255);
//shuffle the arrays
for (int i = 3; i > 0; i--) {
std::swap(palettecolors[i].saturation, palettecolors[random8(i + 1)].saturation);
std::swap(palettecolors[i].value, palettecolors[random8(i + 1)].value);
}
//now generate three new hues based off of the hue of the chosen current color
uint8_t basehue = palettecolors[keepcolorposition].hue;
uint8_t harmonics[3]; //hues that are harmonic but still a little random
uint8_t type = random8(5); //choose a harmony type
switch (type) {
case 0: // analogous
harmonics[0] = basehue + random8(30, 50);
harmonics[1] = basehue + random8(10, 30);
harmonics[2] = basehue - random8(10, 30);
break;
case 1: // triadic
harmonics[0] = basehue + 113 + random8(15);
harmonics[1] = basehue + 233 + random8(15);
harmonics[2] = basehue -7 + random8(15);
break;
case 2: // split-complementary
harmonics[0] = basehue + 145 + random8(10);
harmonics[1] = basehue + 205 + random8(10);
harmonics[2] = basehue - 5 + random8(10);
break;
case 3: // square
harmonics[0] = basehue + 85 + random8(10);
harmonics[1] = basehue + 175 + random8(10);
harmonics[2] = basehue + 265 + random8(10);
break;
case 4: // tetradic
harmonics[0] = basehue + 80 + random8(20);
harmonics[1] = basehue + 170 + random8(20);
harmonics[2] = basehue + random8(30)-15;
break;
}
if (random8() < 128) {
//50:50 chance of shuffling hues or keep the color order
for (int i = 2; i > 0; i--) {
std::swap(harmonics[i], harmonics[random8(i + 1)]);
}
}
//now set the hues
int j = 0;
for (int i = 0; i < 4; i++) {
if (i==keepcolorposition) continue; //skip the base color
palettecolors[i].hue = harmonics[j];
j++;
}
bool makepastelpalette = false;
if (random8() < 25) { //~10% chance of desaturated 'pastel' colors
makepastelpalette = true;
}
//apply saturation & gamma correction
CRGB RGBpalettecolors[4];
for (int i = 0; i < 4; i++) {
if (makepastelpalette && palettecolors[i].saturation > 180) {
palettecolors[i].saturation -= 160; //desaturate all four colors
}
RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB
RGBpalettecolors[i] = gamma32(((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU); //strip alpha from CRGB
}
return CRGBPalette16(RGBpalettecolors[0],
RGBpalettecolors[1],
RGBpalettecolors[2],
RGBpalettecolors[3]);
}
CRGBPalette16 generateRandomPalette(void) //generate fully random palette
{
return CRGBPalette16(CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)));
}
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb
{
float h = ((float)hue)/65535.0f;

View File

@ -7,9 +7,26 @@
#define GRADIENT_PALETTE_COUNT 58
// You can define custom product info from build flags.
// This is useful to allow API consumer to identify what type of WLED version
// they are interacting with. Be aware that changing this might cause some third
// party API consumers to consider this as a non-WLED device since the values
// returned by the API and by MQTT will no longer be default. However, most
// third party only uses mDNS to validate, so this is generally fine to change.
// For example, Home Assistant will still work fine even with this value changed.
// Use like this:
// -D WLED_BRAND="\"Custom Brand\""
// -D WLED_PRODUCT_NAME="\"Custom Product\""
#ifndef WLED_BRAND
#define WLED_BRAND "WLED"
#endif
#ifndef WLED_PRODUCT_NAME
#define WLED_PRODUCT_NAME "FOSS"
#endif
//Defaults
#define DEFAULT_CLIENT_SSID "Your_Network"
#define DEFAULT_AP_SSID "WLED-AP"
#define DEFAULT_AP_SSID WLED_BRAND "-AP"
#define DEFAULT_AP_PASS "wled1234"
#define DEFAULT_OTA_PASS "wledota"
#define DEFAULT_MDNS_NAME "x"
@ -76,6 +93,11 @@
#else
#define WLED_MAX_BUTTONS 4
#endif
#else
#if WLED_MAX_BUTTONS < 2
#undef WLED_MAX_BUTTONS
#define WLED_MAX_BUTTONS 2
#endif
#endif
#ifdef ESP8266
@ -154,7 +176,11 @@
#define USERMOD_ID_KLIPPER 40 //Usermod Klipper percentage
#define USERMOD_ID_WIREGUARD 41 //Usermod "wireguard.h"
#define USERMOD_ID_INTERNAL_TEMPERATURE 42 //Usermod "usermod_internal_temperature.h"
#define USERMOD_ID_ANIMARTRIX 44 //Usermod "usermod_v2_animartrix.h"
#define USERMOD_ID_LDR_DUSK_DAWN 43 //Usermod "usermod_LDR_Dusk_Dawn_v2.h"
#define USERMOD_ID_STAIRWAY_WIPE 44 //Usermod "stairway-wipe-usermod-v2.h"
#define USERMOD_ID_ANIMARTRIX 45 //Usermod "usermod_v2_animartrix.h"
#define USERMOD_ID_HTTP_PULL_LIGHT_CONTROL 46 //usermod "usermod_v2_HttpPullLightControl.h"
#define USERMOD_ID_TETRISAI 47 //Usermod "usermod_v2_tetris.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@ -215,8 +241,8 @@
#define DMX_MODE_MULTIPLE_RGB 4 //every LED is addressed with its own RGB (ledCount * 3 channels)
#define DMX_MODE_MULTIPLE_DRGB 5 //every LED is addressed with its own RGB and share a master dimmer (ledCount * 3 + 1 channels)
#define DMX_MODE_MULTIPLE_RGBW 6 //every LED is addressed with its own RGBW (ledCount * 4 channels)
#define DMX_MODE_EFFECT_SEGMENT 8 //trigger standalone effects of WLED (15 channels per segement)
#define DMX_MODE_EFFECT_SEGMENT_W 9 //trigger standalone effects of WLED (18 channels per segement)
#define DMX_MODE_EFFECT_SEGMENT 8 //trigger standalone effects of WLED (15 channels per segment)
#define DMX_MODE_EFFECT_SEGMENT_W 9 //trigger standalone effects of WLED (18 channels per segment)
#define DMX_MODE_PRESET 10 //apply presets (1 channel)
//Light capability byte (unused) 0bRCCCTTTT
@ -265,6 +291,7 @@
#define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus, unused)
#define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus, unused)
#define TYPE_NET_DDP_RGBW 88 //network DDP RGBW bus (master broadcast bus)
#define TYPE_NET_ARTNET_RGBW 89 //network ArtNet RGB bus (master broadcast bus, unused)
#define IS_TYPE_VALID(t) ((t) > 15 && (t) < 128)
#define IS_DIGITAL(t) (((t) > 15 && (t) < 40) || ((t) > 47 && (t) < 64)) //digital are 16-39 and 48-63
@ -341,7 +368,7 @@
#define SEG_DIFFERS_OPT 0x02 // all segment options except: selected, reset & transitional
#define SEG_DIFFERS_COL 0x04 // colors
#define SEG_DIFFERS_FX 0x08 // effect/mode parameters
#define SEG_DIFFERS_BOUNDS 0x10 // segment start/stop ounds
#define SEG_DIFFERS_BOUNDS 0x10 // segment start/stop bounds
#define SEG_DIFFERS_GSO 0x20 // grouping, spacing & offset
#define SEG_DIFFERS_SEL 0x80 // selected
@ -366,7 +393,7 @@
#define ERR_FS_PLOAD 12 // It was attempted to load a preset that does not exist
#define ERR_FS_IRLOAD 13 // It was attempted to load an IR JSON cmd, but the "ir.json" file does not exist
#define ERR_FS_RMLOAD 14 // It was attempted to load an remote JSON cmd, but the "remote.json" file does not exist
#define ERR_FS_GENERAL 19 // A general unspecified filesystem error occured
#define ERR_FS_GENERAL 19 // A general unspecified filesystem error occurred
#define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented)
#define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented)
#define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented)
@ -395,7 +422,8 @@
#define SUBPAGE_JS 254
#define SUBPAGE_WELCOME 255
#define NTP_PACKET_SIZE 48
#define NTP_PACKET_SIZE 48 // size of NTP receive buffer
#define NTP_MIN_PACKET_SIZE 48 // min expected size - NTP v4 allows for "extended information" appended to the standard fields
//maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses
#ifndef MAX_LEDS
@ -426,7 +454,7 @@
#ifdef ESP8266
#define SETTINGS_STACK_BUF_SIZE 2048
#else
#define SETTINGS_STACK_BUF_SIZE 3608 // warning: quite a large value for stack
#define SETTINGS_STACK_BUF_SIZE 3840 // warning: quite a large value for stack (640 * WLED_MAX_USERMODS)
#endif
#ifdef WLED_USE_ETHERNET
@ -483,8 +511,8 @@
//this is merely a default now and can be changed at runtime
#ifndef LEDPIN
#if defined(ESP8266) || (defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)) || defined(CONFIG_IDF_TARGET_ESP32C3)
#define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards
#if defined(ESP8266) || (defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ARDUINO_ESP32_PICO)
#define LEDPIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, and on boards where GPIO16 is not available
#else
#define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards
#endif
@ -508,7 +536,7 @@
#define PIN_TIMEOUT 900000 // time in ms after which the PIN will be required again, 15 minutes
// HW_PIN_SCL & HW_PIN_SDA are used for information in usermods settings page and usermods themselves
// which GPIO pins are actually used in a hardwarea layout (controller board)
// which GPIO pins are actually used in a hardware layout (controller board)
#if defined(I2CSCLPIN) && !defined(HW_PIN_SCL)
#define HW_PIN_SCL I2CSCLPIN
#endif
@ -531,7 +559,7 @@
#endif
// HW_PIN_SCLKSPI & HW_PIN_MOSISPI & HW_PIN_MISOSPI are used for information in usermods settings page and usermods themselves
// which GPIO pins are actually used in a hardwarea layout (controller board)
// which GPIO pins are actually used in a hardware layout (controller board)
#if defined(SPISCLKPIN) && !defined(HW_PIN_CLOCKSPI)
#define HW_PIN_CLOCKSPI SPISCLKPIN
#endif

View File

@ -506,7 +506,7 @@
console.log('Error: ', e); console.log(' Status: ', this.status);
//Show some error notification for some time
setTimeout(()=>{
//Remove it when time has pased
//Remove it when time has passed
}, 1000);
});
req.open("POST", "/upload");
@ -554,7 +554,7 @@
paletteArray.push({"palette":[0,70,70,70,255,70,70,70]});
}
//Get static palettes from localStorage and do some magic to reformat them into the same format as the pallete JSONs
//Get static palettes from localStorage and do some magic to reformat them into the same format as the palette JSONs
//This code excludes any objects with "non valid integer colors", i.e. r, c1, c2, c3 and such
//This code also fixes potentially broken palettes which doesn't end on 255
//The code finally also removes any representations of the custom palettes, since we read them from file

View File

@ -346,10 +346,14 @@ button {
-webkit-overflow-scrolling: touch;
}
#Segments, #Presets, #Effects, #Colors {
font-size: 19px;
padding: 4px 0 0;
}
#segutil, #segutil2, #segcont, #putil, #pcont, #pql, #fx, #palw,
.fnd {
max-width: 280px;
font-size: 19px;
}
#putil, #segutil, #segutil2 {
@ -361,7 +365,7 @@ button {
padding-top: 12px;
}
#fx, #pql, #segcont, #pcont, #sliders, #picker, #qcs-w, #hexw, #pall, #ledmap,
#fx, #pql, #segcont, #pcont, #sliders, #qcs-w, #hexw, #pall, #ledmap,
.slider, .filter, .option, .segname, .pname, .fnd {
margin: 0 auto;
}
@ -371,15 +375,10 @@ button {
}
/* Quick load magin for simplified UI */
.simplified #pql {
.simplified #pql, .simplified #palw, .simplified #fx {
margin-bottom: 8px;
}
/* Button margin for simplified UI */
.simplified #fx .btn, .simplified #palw .btn {
margin-top: 0;
}
.smooth { transition: transform calc(var(--f, 1)*.5s) ease-out }
.tab-label {
@ -624,12 +623,10 @@ button {
padding-bottom: 8px;
}
#info .btn {
.infobtn {
margin: 5px;
}
#info table .btn, #nodes table .btn {
margin: 0;
}
#info div, #nodes div {
max-width: 490px;
margin: 0 auto;
@ -784,14 +781,14 @@ input[type=range]::-moz-range-thumb {
}
#picker {
margin-top: 8px !important;
margin: 4px auto 0 !important;
max-width: max-content;
}
/* buttons */
.btn {
padding: 8px;
margin: 10px 4px;
/*margin: 10px 4px;*/
width: 230px;
font-size: 19px;
color: var(--c-d);
@ -837,14 +834,14 @@ input[type=range]::-moz-range-thumb {
text-overflow: clip;
}
.btn-xs {
margin: 2px 0 0 0;
}
#putil .btn-xs {
margin: 0;
}
#info .btn-xs {
border: 1px solid var(--c-4);
}
#btns .btn-xs {
margin: 0 4px;
}
#putil .btn-s {
width: 135px;
@ -867,7 +864,7 @@ a.btn {
display: block;
white-space: nowrap;
text-align: center;
padding: 8px 32px;
padding: 9px 32px 7px 24px;
position: relative;
box-sizing: border-box;
line-height: 24px;
@ -922,9 +919,6 @@ select {
#tt {
text-align: center;
}
.cl {
background-color: #000;
}
select.sel-p, select.sel-pl, select.sel-ple {
margin: 5px 0;
width: 100%;
@ -1071,27 +1065,24 @@ textarea {
.newseg {
cursor: default;
}
/*
.ic {
padding: 6px 0 0 0;
}
.xxs {
*/
/* color selector */
#csl button {
width: 44px;
height: 44px;
margin: 5px;
border: 2px solid var(--c-d) !important;
background-color: #000;
}
.xxs-w {
/* selected color selector */
#csl .sl {
margin: 2px;
width: 50px;
height: 50px;
}
#csl .xxs {
border: 2px solid var(--c-d) !important;
}
#csl .xxs-w {
border-width: 5px !important;
}
@ -1296,13 +1287,10 @@ TD .checkmark, TD .radiomark {
position: -webkit-sticky;
position: sticky;
border-radius: 21px;
margin: 13px auto 0;
margin: 0 auto 12px;
min-height: 40px;
border: 1px solid var(--c-2);
}
#segutil .lstI {
margin-top: 0;
width: 100%;
}
/* Simplify segments */
@ -1403,7 +1391,7 @@ dialog {
width: 100%;
box-sizing: border-box;
padding: 8px 40px 8px 44px;
margin: 5px auto 0;
margin: 4px auto 12px;
text-align: left;
border-radius: 21px;
background: var(--c-2);
@ -1421,6 +1409,13 @@ dialog {
background-color: var(--c-3);
}
#fxFind.fnd input[type="text"] {
margin-bottom: 0;
}
#fxFind {
margin-bottom: 12px;
}
/* segment & preset inner/expanded content */
.segin,
.presin {

View File

@ -115,9 +115,9 @@
<div class="qcs" onclick="pC('rnd');" title="Random" style="background:linear-gradient(to right, red, orange, yellow, green, blue, purple);transform: translateY(-11px);">R</div>
</div>
<div id="csl">
<button id="csl0" title="Select slot" class="btn xxs cl" onclick="selectSlot(0);" data-r="0" data-g="0" data-b="0" data-w="0">1</button>
<button id="csl1" title="Select slot" class="btn xxs cl" onclick="selectSlot(1);" data-r="0" data-g="0" data-b="0" data-w="0">2</button>
<button id="csl2" title="Select slot" class="btn xxs cl" onclick="selectSlot(2);" data-r="0" data-g="0" data-b="0" data-w="0">3</button>
<button id="csl0" title="Select slot" class="btn" onclick="selectSlot(0);" data-r="0" data-g="0" data-b="0" data-w="0">1</button>
<button id="csl1" title="Select slot" class="btn" onclick="selectSlot(1);" data-r="0" data-g="0" data-b="0" data-w="0">2</button>
<button id="csl2" title="Select slot" class="btn" onclick="selectSlot(2);" data-r="0" data-g="0" data-b="0" data-w="0">3</button>
</div>
<p class="labels h" id="cslLabel"></p>
<div id="hexw">
@ -130,7 +130,7 @@
<button class="btn btn-xs" title="Add custom palette" type="button" onclick="window.location.href=getURL('/cpal.htm')"><i class="icons btn-icon">&#xe18a;</i></button>
<button class="btn btn-xs" title="Remove custom palette" type="button" id="rmPal" onclick="palettesData=null;localStorage.removeItem('wledPalx');requestJson({rmcpal:true});setTimeout(loadPalettes,250,loadPalettesData);"><i class="icons btn-icon">&#xe037;</i></button>
</div>
<p class="labels" id="pall"><i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i> Color palette</p>
<p class="labels hd" id="pall"><i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i> Color palette</p>
<div id="palw" class="il">
<div class="staytop fnd">
<input type="text" placeholder="Search" oninput="search(this,'pallist')" onfocus="search(this,'pallist')" />
@ -256,6 +256,7 @@
</div>
<div id="Segments" class="tabcontent">
<p class="labels hd" id="segLabel">Segments</p>
<div id="segcont">
Loading...
</div>
@ -308,7 +309,7 @@
<button class="btn infobtn" id="resetbtn" onclick="cnfReset()">Reboot WLED</button>
</div>
<br>
<span class="h">Made with <span id="heart">&#10084;&#xFE0E;</span> by <a href="https://github.com/Aircoookie/" target="_blank">Aircoookie</a> and the <a href="https://wled.discourse.group/" target="_blank">WLED community</a></span>
<span class="h">Made with&#32;<span id="heart">&#10084;&#xFE0E;</span>&#32;by&#32;<a href="https://github.com/Aircoookie/" target="_blank">Aircoookie</a>&#32;and the&#32;<a href="https://wled.discourse.group/" target="_blank">WLED community</a></span>
</div>
<div id="nodes" class="modal">

View File

@ -88,7 +88,6 @@ function setCSL(cs)
function applyCfg()
{
cTheme(cfg.theme.base === "light");
gId("Colors").style.paddingTop = cfg.comp.colors.picker ? "0" : "28px";
var bg = cfg.theme.color.bg;
if (bg) sCol('--c-1', bg);
var l = cfg.comp.labels;
@ -278,25 +277,17 @@ function onLoad()
pmtLS = localStorage.getItem('wledPmt');
// Load initial data
// Once we figure out why ESP8266 sometimes corrupts JSON responses if they are made in quick succession
// we can remove all setTimeout() throttling
loadPalettes(()=>{
setTimeout(()=>{ // ESP8266 can't handle quick requests
// fill effect extra data array
loadFXData(()=>{
setTimeout(()=>{ // ESP8266 can't handle quick requests
// load and populate effects
loadFX(()=>{
setTimeout(()=>{ // ESP8266 can't handle quick requests
loadPalettesData(()=>{
requestJson();// will load presets and create WS
if (cfg.comp.css) setTimeout(()=>{loadSkinCSS('skinCss')},100);
});
},50);
});
},50);
// fill effect extra data array
loadFXData(()=>{
// load and populate effects
loadFX(()=>{
loadPalettesData(()=>{
requestJson();// will load presets and create WS
if (cfg.comp.css) setTimeout(()=>{loadSkinCSS('skinCss')},50);
});
});
},50);
});
});
resetUtil();
@ -627,7 +618,7 @@ function populatePresets(fromls)
cn += `<div class="pres lstI" id="p${i}o">`;
if (cfg.comp.pid) cn += `<div class="pid">${i}</div>`;
cn += `<div class="pname lstIname" onclick="setPreset(${i})">${isPlaylist(i)?"<i class='icons btn-icon'>&#xe139;</i>":""}${pName(i)}
cn += `<div class="pname lstIname" onclick="setPreset(${i})">${i==lastinfo.leds.bootps?"<i class='icons btn-icon'>&#xe410;</i>":""}${isPlaylist(i)?"<i class='icons btn-icon'>&#xe139;</i>":""}${pName(i)}
<i class="icons edit-icon flr" id="p${i}nedit" onclick="tglSegn(${i+100})">&#xe2c6;</i></div>
<i class="icons e-icon flr" id="sege${i+100}" onclick="expand(${i+100})">&#xe395;</i>
<div class="presin lstIcontent" id="seg${i+100}"></div>
@ -809,13 +800,13 @@ function populateSegments(s)
`<span class="checkmark"></span>`+
`</label>`+
`<div class="segname ${smpl}" onclick="selSegEx(${i})">`+
`<i class="icons e-icon frz" id="seg${i}frz" onclick="event.preventDefault();tglFreeze(${i});">&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};</i>`+
`<i class="icons e-icon frz" id="seg${i}frz" title="(un)Freeze" onclick="event.preventDefault();tglFreeze(${i});">&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};</i>`+
(inst.n ? inst.n : "Segment "+i) +
`<div class="pop hide" onclick="event.preventDefault();event.stopPropagation();">`+
`<i class="icons g-icon" style="color:${cG};" onclick="this.nextElementSibling.classList.toggle('hide');">&#x278${String.fromCharCode(inst.set+"A".charCodeAt(0))};</i>`+
`<i class="icons g-icon" title="Set group" style="color:${cG};" onclick="this.nextElementSibling.classList.toggle('hide');">&#x278${String.fromCharCode(inst.set+"A".charCodeAt(0))};</i>`+
`<div class="pop-c hide"><span style="color:var(--c-f);" onclick="setGrp(${i},0);">&#x278A;</span><span style="color:var(--c-r);" onclick="setGrp(${i},1);">&#x278B;</span><span style="color:var(--c-g);" onclick="setGrp(${i},2);">&#x278C;</span><span style="color:var(--c-l);" onclick="setGrp(${i},3);">&#x278D;</span></div>`+
`</div> `+
`<i class="icons edit-icon flr ${smpl}" id="seg${i}nedit" onclick="tglSegn(${i})">&#xe2c6;</i>`+
`<i class="icons edit-icon flr ${smpl}" id="seg${i}nedit" title="Edit" onclick="tglSegn(${i})">&#xe2c6;</i>`+
`</div>`+
`<i class="icons e-icon flr ${smpl}" id="sege${i}" onclick="expand(${i})">&#xe395;</i>`+
(cfg.comp.segpwr ? segp : '') +
@ -846,7 +837,7 @@ function populateSegments(s)
`<tr>`+
`<td><input class="segn" id="seg${i}grp" type="number" min="1" max="255" value="${inst.grp}" oninput="updateLen(${i})" onkeydown="segEnter(${i})"></td>`+
`<td><input class="segn" id="seg${i}spc" type="number" min="0" max="255" value="${inst.spc}" oninput="updateLen(${i})" onkeydown="segEnter(${i})"></td>`+
`<td><button class="btn btn-xs" onclick="setSeg(${i})"><i class="icons btn-icon" id="segc${i}">&#xe390;</i></button></td>`+
`<td><button class="btn btn-xs" title="Update" onclick="setSeg(${i})"><i class="icons btn-icon" id="segc${i}">&#xe390;</i></button></td>`+
`</tr>`+
`</table>`+
`<div class="h bp" id="seg${i}len"></div>`+
@ -878,10 +869,11 @@ function populateSegments(s)
updateLen(i);
updateTrail(gId(`seg${i}bri`));
gId(`segr${i}`).classList.add("hide");
//if (i<lSeg) gId(`segd${i}`).classList.add("hide"); // hide delete button for all but last
if (!gId(`seg${i}sel`).checked && gId('selall')) gId('selall').checked = false; // uncheck if at least one is unselected.
}
if (segCount < 2) {
gId(`segd${lSeg}`).classList.add("hide");
gId(`segd${lSeg}`).classList.add("hide"); // hide delete if only one segment
if (parseInt(gId("seg0bri").value)==255) gId(`segp0`).classList.add("hide");
// hide segment controls if there is only one segment in simplified UI
if (simplifiedUI) gId("segcont").classList.add("hide");
@ -898,6 +890,7 @@ function populateSegments(s)
} else {
gId("ledmap").classList.add('hide');
}
tooltip("#Segments");
}
function populateEffects()
@ -1448,7 +1441,7 @@ function readState(s,command=false)
if (s.seg[i].sel) {
if (sellvl < 2) selc = i; // get first selected segment
sellvl = 2;
var lc = lastinfo.leds.seglc[s.seg[i].id];
var lc = lastinfo.leds.seglc[i];
hasRGB |= !!(lc & 0x01);
hasWhite |= !!(lc & 0x02);
hasCCT |= !!(lc & 0x04);
@ -1458,7 +1451,7 @@ function readState(s,command=false)
}
var i=s.seg[selc];
if (sellvl == 1) {
var lc = lastinfo.leds.seglc[i.id];
var lc = lastinfo.leds.seglc[selc];
hasRGB = !!(lc & 0x01);
hasWhite = !!(lc & 0x02);
hasCCT = !!(lc & 0x04);
@ -1534,9 +1527,9 @@ function readState(s,command=false)
// - For AC effects (id<128) 2 sliders and 3 colors and the palette will be shown
// - For SR effects (id>128) 5 sliders and 3 colors and the palette will be shown
// If effective (@)
// - a ; seperates slider controls (left) from color controls (middle) and palette control (right)
// - a ; separates slider controls (left) from color controls (middle) and palette control (right)
// - if left, middle or right is empty no controls are shown
// - a , seperates slider controls (max 5) or color controls (max 3). Palette has only one value
// - a , separates slider controls (max 5) or color controls (max 3). Palette has only one value
// - a ! means that the default is used.
// - For sliders: Effect speeds, Effect intensity, Custom 1, Custom 2, Custom 3
// - For colors: Fx color, Background color, Custom
@ -1560,12 +1553,12 @@ function setEffectParameters(idx)
// set html slider items on/off
let sliders = d.querySelectorAll("#sliders .sliderwrap");
sliders.forEach((slider, i)=>{
let text = slider.getAttribute("tooltip");
let text = slider.getAttribute("title");
if ((!controlDefined && i<((idx<128)?2:nSliders)) || (slOnOff.length>i && slOnOff[i]!="")) {
if (slOnOff.length>i && slOnOff[i]!="!") text = slOnOff[i];
// restore overwritten default tooltips
if (i<2 && slOnOff[i]==="!") text = i==0 ? "Effect speed" : "Effect intensity";
slider.setAttribute("tooltip", text);
slider.setAttribute("title", text);
slider.parentElement.classList.remove('hide');
} else
slider.parentElement.classList.add('hide');
@ -1575,10 +1568,10 @@ function setEffectParameters(idx)
gId('fxopt').classList.remove('fade');
let checks = d.querySelectorAll("#sliders .ochkl");
checks.forEach((check, i)=>{
let text = check.getAttribute("tooltip");
let text = check.getAttribute("title");
if (5+i<slOnOff.length && slOnOff[5+i]!=='') {
if (slOnOff.length>5+i && slOnOff[5+i]!="!") text = slOnOff[5+i];
check.setAttribute("tooltip", text);
check.setAttribute("title", text);
check.classList.remove('hide');
} else
check.classList.add('hide');
@ -1827,17 +1820,16 @@ function toggleNodes()
function makeSeg()
{
var ns = 0, ct = 0;
var ns = 0, ct = isM ? mw : ledCount;
var lu = lowestUnused;
let li = lastinfo;
if (lu > 0) {
let xend = parseInt(gId(`seg${lu -1}e`).value,10) + (cfg.comp.seglen?parseInt(gId(`seg${lu -1}s`).value,10):0);
if (isM) {
ns = 0;
ct = mw;
} else {
if (xend < ledCount) ns = xend;
ct = ledCount-(cfg.comp.seglen?ns:0)
ct -= cfg.comp.seglen?ns:0;
}
}
gId('segutil').scrollIntoView({
@ -1876,7 +1868,7 @@ function resetUtil(off=false)
+ '<label class="check schkl"><input type="checkbox" id="selall" onchange="selSegAll(this)"><span class="checkmark"></span></label>'
+ `<div class="segname" ${off?'':'onclick="makeSeg()"'}><i class="icons btn-icon">&#xe18a;</i>Add segment</div>`
+ '<div class="pop hide" onclick="event.stopPropagation();">'
+ `<i class="icons g-icon" onclick="this.nextElementSibling.classList.toggle('hide');">&#xE34B;</i>`
+ `<i class="icons g-icon" title="Select group" onclick="this.nextElementSibling.classList.toggle('hide');">&#xE34B;</i>`
+ '<div class="pop-c hide"><span style="color:var(--c-f);" onclick="selGrp(0);">&#x278A;</span><span style="color:var(--c-r);" onclick="selGrp(1);">&#x278B;</span><span style="color:var(--c-g);" onclick="selGrp(2);">&#x278C;</span><span style="color:var(--c-l);" onclick="selGrp(3);">&#x278D;</span></div>'
+ '</div></div>';
}
@ -1967,6 +1959,7 @@ function plR(p)
function makeP(i,pl)
{
var content = "";
const bps = lastinfo.leds.bootps;
if (pl) {
if (i===0) plJson[0] = {
ps: [1],
@ -2032,6 +2025,11 @@ ${makePlSel(plJson[i].end?plJson[i].end:0, true)}
</div>
<div class="po2" id="p${i}o2">API command<br><textarea class="apitxt" id="p${i}api"></textarea></div>
<div class="po1" id="p${i}o1">${content}</div>
<label class="check revchkl">
<span class="lstIname">Apply at boot</span>
<input type="checkbox" id="p${i}bps" ${i==bps?"checked":""}>
<span class="checkmark"></span>
</label>
<div class="c m6">Save to ID <input id="p${i}id" type="number" oninput="checkUsed(${i})" max=250 min=1 value=${(i>0)?i:getLowestUnusedP()}></div>
<div class="c">
<button class="btn btn-p" onclick="saveP(${i},${pl})"><i class="icons btn-icon">&#xe390;</i>Save</button>
@ -2453,8 +2451,9 @@ function saveP(i,pl)
if (gId(`p${i}lmp`) && gId(`p${i}lmp`).value!=="") obj.ledmap = parseInt(gId(`p${i}lmp`).value);
}
}
obj.psave = pI; obj.n = pN;
if (gId(`p${i}bps`).checked) obj.bootps = pI;
obj.psave = pI;
obj.n = pN;
var pQN = gId(`p${i}ql`).value;
if (pQN.length > 0) obj.ql = pQN;
@ -2514,8 +2513,8 @@ function selectSlot(b)
{
csel = b;
var cd = gId('csl').children;
for (let i of cd) i.classList.remove('xxs-w');
cd[b].classList.add('xxs-w');
for (let i of cd) i.classList.remove('sl');
cd[b].classList.add('sl');
setPicker(rgbStr(cd[b].dataset));
// force slider update on initial load (picker "color:change" not fired if black)
if (cpick.color.value == 0) updatePSliders();
@ -2803,6 +2802,12 @@ function search(field, listId = null) {
const search = field.value !== '';
// restore default preset sorting if no search term is entered
if (listId === 'pcont' && !search) {
populatePresets();
return;
}
// clear filter if searching in fxlist
if (listId === 'fxlist' && search) {
gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { e.checked = false; });
@ -3071,9 +3076,9 @@ function mergeDeep(target, ...sources)
return mergeDeep(target, ...sources);
}
function tooltip()
function tooltip(cont=null)
{
const elements = d.querySelectorAll("[title]");
const elements = d.querySelectorAll((cont?cont+" ":"")+"[title]");
elements.forEach((element)=>{
element.addEventListener("mouseover", ()=>{
// save title

View File

@ -54,7 +54,7 @@
let mW = leds[2]; // matrix width
let mH = leds[3]; // matrix height
let pPL = Math.min(c.width / mW, c.height / mH); // pixels per LED (width of circle)
let lOf = Math.floor((c.width - pPL*mW)/2); //left offeset (to center matrix)
let lOf = Math.floor((c.width - pPL*mW)/2); //left offset (to center matrix)
var i = 4;
for (y=0.5;y<mH;y++) for (x=0.5; x<mW; x++) {
ctx.fillStyle = `rgb(${leds[i]},${leds[i+1]},${leds[i+2]})`;

View File

@ -69,7 +69,7 @@ function getPixelRGBValues(base64Image) {
let sizeY = szY.value;
if (color != accentColor || sizeX < 1 || sizeY < 1){
//image will not be rezised Set desitred size to original size
//image will not be resized Set desired size to original size
sizeX = image.width;
sizeY = image.height;
//failsafe for not generating huge images automatically
@ -153,7 +153,7 @@ function getPixelRGBValues(base64Image) {
let curentColorIndex = 0
let commandArray = [];
//For evry pixel in the LED array
//For every pixel in the LED array
for (let i = 0; i < maxi; i++) {
let pixel = ledRGBValues[i];
let r = pixel[0];

View File

@ -81,7 +81,7 @@
<button type=submit id="b" onclick="window.location=getURL('/')">Back</button>
<button type="submit" onclick="window.location=getURL('/settings/wifi')">WiFi Setup</button>
<button type="submit" onclick="window.location=getURL('/settings/leds')">LED Preferences</button>
<button id="2dbtn" style="display:none;" type="submit" onclick="window.location=getURL('/settings/2D')">2D Configuration</button>
<button id="2dbtn" type="submit" onclick="window.location=getURL('/settings/2D')">2D Configuration</button>
<button type="submit" onclick="window.location=getURL('/settings/ui')">User Interface</button>
<button id="dmxbtn" style="display:none;" type="submit" onclick="window.location=getURL('/settings/dmx')">DMX Output</button>
<button type="submit" onclick="window.location=getURL('/settings/sync')">Sync Interfaces</button>

View File

@ -123,10 +123,10 @@
gId('abl').style.display = (en) ? 'inline':'none';
gId('psu2').style.display = (en) ? 'inline':'none';
if (!en) d.Sf.PPL.checked = false;
enPPL();
UI();
}
function enPPL()
// enable per port limiter and calculate current
function enPPL(sDI=0)
{
const abl = d.Sf.ABL.checked;
const ppl = d.Sf.PPL.checked;
@ -139,9 +139,11 @@
d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,n)=>{
gId("PSU"+n).style.display = ppl ? "inline" : "none";
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
const c = parseInt(d.Sf["LC"+n].value); //get LED count
i.min = ppl && !(isVir(t) || isAna(t)) ? 250 : 0;
if (!abl) i.value = 0;
if (!abl || isVir(t) || isAna(t)) i.value = 0;
else if (ppl) sumMA += parseInt(i.value,10);
else if (sDI) i.value = Math.round(parseInt(d.Sf.MA.value,10)*c/sDI);
});
if (ppl) d.Sf.MA.value = sumMA; // populate UI ABL value if PPL used
}
@ -201,10 +203,9 @@
function UI(change=false)
{
let isRGBW = false, gRGBW = false, memu = 0;
let sumMA = 0, busMA = 0;
let busMA = 0;
let sLC = 0, sPC = 0, sDI = 0, maxLC = 0;
const ablEN = d.Sf.ABL.checked;
const pplEN = d.Sf.PPL.checked;
// enable/disable LED fields
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{
@ -253,9 +254,6 @@
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog
//gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description
if (ablEN && pplEN && !(isVir(t) || isAna(t))) {
sumMA += parseInt(d.Sf["MA"+n].value); // summarize PPL ABL limit (fields)
}
});
// display global white channel overrides
gId("wc").style.display = (gRGBW) ? 'inline':'none';
@ -279,7 +277,6 @@
if (s+c > sLC) sLC = s+c; //update total count
if (c > maxLC) maxLC = c; //max per output
if (!isVir(t)) sPC += c; //virtual out busses do not count towards physical LEDs
//if (!(isVir(t) || isPWM(t))) sDI += c;
if (!(isVir(t) || isAna(t))) {
sDI += c; // summarize digital LED count
let maPL = parseInt(d.Sf["LA"+n].value);
@ -327,18 +324,9 @@
else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff";
}
});
// distribute ABL current if not using PPL, otherwise sumMA contains summarized ABL limit
d.Sf.querySelectorAll("#mLC input[name^=LC]").forEach((s,n)=>{
let c = parseInt(s.value,10); //get LED count
let t = parseInt(d.Sf["LT"+n].value); //get LED type
if (!ablEN || isVir(t) || isAna(t)) {
// virtual and analog LEDs have no limiter
d.Sf["MA"+n].value = 0;
return;
}
if (!pplEN) d.Sf["MA"+n].value = Math.round(parseInt(d.Sf.MA.value,10)*c/sDI);
});
if (pplEN) d.Sf.MA.value = sumMA; // update global ABL if using PPL
// distribute ABL current if not using PPL
enPPL(sDI);
// update total led count
gId("lc").textContent = sLC;
gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)";
@ -389,7 +377,7 @@
${i+1}:
<select name="LT${i}" onchange="UI(true)">${i>=maxB ? '' :
'<option value="22" selected>WS281x</option>\
<option value="30">SK6812 RGBW</option>\
<option value="30">SK6812/WS2814 RGBW</option>\
<option value="31">TM1814</option>\
<option value="24">400kHz</option>\
<option value="25">TM1829</option>\
@ -413,6 +401,7 @@ ${i+1}:
<!--option value="81">E1.31 RGB (network)</option-->
<option value="82">Art-Net RGB (network)</option>
<option value="88">DDP RGBW (network)</option>
<option value="89">Art-Net RGBW (network)</option>
</select><br>
<div id="abl${i}">
mA/LED: <select name="LAsel${i}" onchange="enLA(this,${i});UI();">
@ -461,7 +450,6 @@ mA/LED: <select name="LAsel${i}" onchange="enLA(this,${i});UI();">
gId("-").style.display = (i>0) ? "inline":"none";
if (!init) {
enPPL();
UI();
}
}
@ -776,7 +764,7 @@ Swap: <select id="xw${i}" name="XW${i}">
Keep at &lt;1A if poweing LEDs directly from the ESP 5V pin!<br>
Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.<br></i>
<div id="psuMA">Maximum PSU Current: <input name="MA" type="number" class="xl" min="250" max="65000" oninput="UI()" required> mA<br></div>
Use per-output limiter: <input type="checkbox" name="PPL" onchange="enPPL()"><br>
Use per-output limiter: <input type="checkbox" name="PPL" onchange="UI()"><br>
<div id="ppldis" style="display:none;">
<i>Make sure you enter correct values in each LED output.<br>
If using multiple outputs with only one PSU, distribute its power proportionally amongst ouputs.</i><br>
@ -848,6 +836,7 @@ Swap: <select id="xw${i}" name="XW${i}">
Palette transitions: <input type="checkbox" name="PF"><br>
</span>
<i>Random Cycle</i> Palette Time: <input name="TP" type="number" class="m" min="1" max="255"> s<br>
Use harmonic <i>Random Cycle</i> Palette: <input type="checkbox" name="TH"><br>
<h3>Timed light</h3>
Default Duration: <input name="TL" type="number" class="m" min="1" max="255" required> min<br>
Default Target brightness: <input name="TB" type="number" class="m" min="0" max="255" required><br>

View File

@ -124,20 +124,20 @@
Enable ArduinoOTA: <input type="checkbox" name="AO">
<hr>
<h3>Backup & Restore</h3>
<div class="warn">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.</div>
For security reasons, passwords are not backed up.
<a class="btn lnk" id="bckcfg" href="/presets.json" download="presets">Backup presets</a><br>
<div>Restore presets<br><input type="file" name="data" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data,'/presets.json');">Upload</button><br></div><br>
<a class="btn lnk" id="bckpresets" href="/cfg.json" download="cfg">Backup configuration</a><br>
<div>Restore configuration<br><input type="file" name="data2" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data2,'/cfg.json');">Upload</button><br></div>
<div class="warn">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
Incorrect configuration may require a factory reset or re-flashing of your ESP.</div>
For security reasons, passwords are not backed up.
<hr>
<h3>About</h3>
<a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a> version ##VERSION##<!-- Autoreplaced from package.json --><br><br>
<a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a>&#32;version ##VERSION##<!-- Autoreplaced from package.json --><br><br>
<a href="https://github.com/Aircoookie/WLED/wiki/Contributors-and-credits" target="_blank">Contributors, dependencies and special thanks</a><br>
A huge thank you to everyone who helped me create WLED!<br><br>
(c) 2016-2023 Christian Schwinne <br>
<i>Licensed under the <a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">MIT license</a></i><br><br>
(c) 2016-2024 Christian Schwinne <br>
<i>Licensed under the&#32;<a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">MIT license</a></i><br><br>
Server message: <span class="sip"> Response error! </span><hr>
<div id="toast"></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>

View File

@ -183,8 +183,8 @@
<option value="6">US-MST/MDT</option>
<option value="7">US-AZ</option>
<option value="8">US-PST/PDT</option>
<option value="9">CST(AWST)</option>
<option value="10">JST(KST)</option>
<option value="9">CST (AWST, PHST)</option>
<option value="10">JST (KST)</option>
<option value="11">AEST/AEDT</option>
<option value="12">NZST/NZDT</option>
<option value="13">North Korea</option>

View File

@ -227,10 +227,10 @@
} else if (typeof(fld) === "number") sel.classList.add("pin"); // a hack to add a class
let arr = d.getElementsByName(um);
let idx = arr[0].type==="hidden"?1:0; // ignore hidden field
if (arr.length > 2) {
if (arr.length > 1+idx) {
// we have array of values (usually pins)
for (let i of arr) {
if (i.type === "number") break;
if (i.nodeName === "INPUT" && i.type === "number") break;
idx++;
}
}

View File

@ -33,7 +33,7 @@
).reduce(
// Filter out duplicate SSIDs. Since it is sorted by signal
// strength, the strongest signal will be kept in the
// order it orginally appeared in the array.
// order it as originally appeared in the array.
(unique, other) => {
if(!unique.some(obj => obj.ssid === other.ssid)) {
unique.push(other);
@ -244,7 +244,7 @@ Static subnet mask:<br>
<option value="9">ABC! WLED V43 & compatible</option>
<option value="2">ESP32-POE</option>
<option value="11">ESP32-POE-WROVER</option>
<option value="6">ESP32Deux</option>
<option value="6">ESP32Deux/RGB2Go Tetra</option>
<option value="7">KIT-VE</option>
<option value="8">QuinLED-Dig-Octa & T-ETH-POE</option>
<option value="4">QuinLED-ESP32</option>

View File

@ -69,14 +69,10 @@ void handleDMX()
}
void initDMX() {
#ifdef ESP8266
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2)
dmx.init(512); // initialize with bus length
#else
dmx.initWrite(512); // initialize with bus length
#endif
}
#else
void handleDMX() {}
void initDMX() {}
#endif

View File

@ -11,6 +11,7 @@
//DDP protocol support, called by handleE131Packet
//handles RGB data only
void handleDDPPacket(e131_packet_t* p) {
static bool ddpSeenPush = false; // have we seen a push yet?
int lastPushSeq = e131LastSequenceNumber[0];
//reject late packets belonging to previous frame (assuming 4 packets max. before push)
@ -34,6 +35,7 @@ void handleDDPPacket(e131_packet_t* p) {
uint16_t c = 0;
if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later
if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) {
@ -44,7 +46,8 @@ void handleDDPPacket(e131_packet_t* p) {
}
bool push = p->flags & DDP_PUSH_FLAG;
if (push) {
ddpSeenPush |= push;
if (!ddpSeenPush || push) { // if we've never seen a push, or this is one, render display
e131NewData = true;
byte sn = p->sequenceNum & 0xF;
if (sn) e131LastSequenceNumber[0] = sn;
@ -184,7 +187,6 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
// only apply preset if not in playlist, or playlist changed
(currentPlaylist < 0 || dmxValPreset != currentPlaylist)) {
presetCycCurr = dmxValPreset;
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(dmxValPreset, CALL_MODE_NOTIFICATION);
}
@ -225,11 +227,16 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3];
if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]);
uint8_t segOption = (uint8_t)floor(e131_data[dataOffset+5]/64.0);
if (segOption == 0 && (seg.mirror || seg.reverse )) {seg.setOption(SEG_OPTION_MIRROR, false); seg.setOption(SEG_OPTION_REVERSED, false);}
if (segOption == 1 && (seg.mirror || !seg.reverse)) {seg.setOption(SEG_OPTION_MIRROR, false); seg.setOption(SEG_OPTION_REVERSED, true);}
if (segOption == 2 && (!seg.mirror || seg.reverse )) {seg.setOption(SEG_OPTION_MIRROR, true); seg.setOption(SEG_OPTION_REVERSED, false);}
if (segOption == 3 && (!seg.mirror || !seg.reverse)) {seg.setOption(SEG_OPTION_MIRROR, true); seg.setOption(SEG_OPTION_REVERSED, true);}
if ((e131_data[dataOffset+5] & 0b00000010) != seg.reverse_y) { seg.setOption(SEG_OPTION_REVERSED_Y, e131_data[dataOffset+5] & 0b00000010); }
if ((e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.setOption(SEG_OPTION_MIRROR_Y, e131_data[dataOffset+5] & 0b00000100); }
if ((e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.setOption(SEG_OPTION_TRANSPOSED, e131_data[dataOffset+5] & 0b00001000); }
if ((e131_data[dataOffset+5] & 0b00110000) / 8 != seg.map1D2D) {
seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) / 8;
}
// To maintain backwards compatibility with prior e1.31 values, reverse is fixed to mask 0x01000000
if ((e131_data[dataOffset+5] & 0b01000000) != seg.reverse) { seg.setOption(SEG_OPTION_REVERSED, e131_data[dataOffset+5] & 0b01000000); }
// To maintain backwards compatibility with prior e1.31 values, mirror is fixed to mask 0x10000000
if ((e131_data[dataOffset+5] & 0b10000000) != seg.mirror) { seg.setOption(SEG_OPTION_MIRROR, e131_data[dataOffset+5] & 0b10000000); }
uint32_t colors[3];
byte whites[3] = {0,0,0};

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