Compare commits

..

41 Commits

Author SHA1 Message Date
Frank Möhle 939c254636 Merge branch 'main' into hub75_4scan_bugfixes 2026-06-05 00:28:42 +02:00
Frank Möhle 3a5026886f Disable auto_apply_labels in coderabbit configuration
has produced too many random-looking labels..
2026-06-05 00:26:20 +02:00
Frank Möhle 1dbceed9dc Update recommendations for synchronization methods
added std::atomic
2026-06-04 23:50:53 +02:00
Frank Möhle be264fb210 Update verification instructions for AI-generated code
limit "existence" check to preprocessor macros etc.
If a global variable or function does not exist, this would lead to a compilation error.
2026-06-04 16:42:17 +02:00
Frank Möhle 53cbb154cb Merge branch 'main' into hub75_4scan_bugfixes 2026-06-04 15:05:47 +02:00
Frank Möhle 7bd387444d Merge pull request #5665 from wled/hub75_gamma_hotfix
HUB75 hotfix: prevents double-applying color corrections (WLED gamma + HUB75 internal CIE correction), by disabling driver-internal color corrections.
2026-06-04 15:03:25 +02:00
Frank 174ff022cb HUB75 hotfix: prevent double-applying gamma correction
disables the driver-internal CIE color correction.

partial solution for #5664
2026-06-04 14:53:20 +02:00
Frank 32412c9aa1 HUB75 tiny bugfix
ensure that BusHub75Matrix::getPixelColor() returns RGBW format, not RGBA.

Currently busses.getPixelColor() is not used by the WLED core.
2026-06-04 13:16:57 +02:00
Frank 22fead8f38 comment correction 2026-06-03 22:45:24 +02:00
Frank 1ee4c617f2 prevent 8-bit wrap-around on 128x64 4-scan panels
change local variables to unsigned (=32bit)
2026-06-03 22:23:10 +02:00
Frank c530e13bdc bugfixes: 4-scan HUB75 driver setup
* prevent panels going flatter each time that cfg.json is saved
* correct VirtualMatrixPanel setup: provide real panel dimensions
* only set chainType when chain length > 1
* use a chaintype that does not flip the display upside-down
2026-06-03 22:08:00 +02:00
Frank f1a99ce741 allow HUB75 panel width up to 128px
align with settings page that also allows up to 64x128 per panel
2026-06-03 22:04:15 +02:00
Frank 834a90278b add SEENGREAT_V2 pin-out (S3)
https://seengreat.com/wiki/186
2026-06-03 22:02:32 +02:00
Will Tatam bd0ebf90c0 Merge pull request #5657 from Will-wastelander/main
Adding support for Waveshare ESP32-S3-RGB-Matrix
2026-06-02 19:52:36 +01:00
Damian Schneider 0a1a7fc26f update version name to Kagayaki 2026-05-31 20:36:49 +02:00
Dimitry 659f698144 Refactor ADS1115 usermod loop logic and update documentation (#5655) 2026-06-01 06:29:21 +02:00
Frank bf948fadb5 platformio: allow building with espressif framework (instead of tasmota)
* add necessary platform and platform_packages entries (commented out)
* compatibility patch for ADC_ATTEN_DB_11
2026-05-31 22:19:07 +02:00
Copilot d884a3e692 Pin andelf/nightly-release tospecific commit SHA instead of @main branch (#5386)
The nightly release GitHub Action is pinned from the mutable main branch to a specific immutable commit SHA

we should update the pin when v2 comes out.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
Co-authored-by: Frank Möhle <91616163+softhack007@users.noreply.github.com>
2026-05-30 23:38:04 +02:00
Frank 82c4ce580e move new buildenv into HUB75 section 2026-05-30 22:06:00 +02:00
Frank bb495f7879 buildenv bugfix, better release_name
* un-set previous RELEASE_NAME before setting a new one
* align REALEASE_NAME with naming scheme used by other HUB75 builds
2026-05-30 22:02:24 +02:00
Will 6bd90abac7 Clean up build flags in platformio.ini
Removed unused build unflags.
2026-05-30 12:37:41 -07:00
Will d6482e99bb Remove upload settings from waveshare_esp32s3_32MB_hub75
Removed upload speed and port settings for the waveshare_esp32s3_32MB_hub75 environment.
2026-05-30 12:20:02 -07:00
Will c399d712c9 Remove unused build flags from platformio.ini
Removed flags for fast functions and CIE1931 from build configuration.
2026-05-30 12:19:10 -07:00
Will fd162cee43 Modify build flags in platformio.ini for ESP32-S3
Updated build flags for the waveshare_esp32s3_32MB_hub75 environment, removing some flags and adding new ones.
2026-05-30 08:28:34 -07:00
Frank Möhle 43278a5d0f Update frequency details for usermod loop()
Clarified frequency of calls for usermod loop() based on system load.
2026-05-30 13:04:20 +02:00
Will bd8a16a6f3 Update upload port and build flags in platformio.ini 2026-05-29 21:14:18 -07:00
Will 73898ddf5d Add new environment for Waveshare ESP32-S3 hub75
https://www.waveshare.com/esp32-s3-rgb-matrix.htm
https://docs.waveshare.com/ESP32-S3-RGB-Matrix
2026-05-29 21:00:20 -07:00
Will cdf1f80a81 Add support for Waveshare S3 Matrix Driver board
https://www.waveshare.com/esp32-s3-rgb-matrix.htm
https://docs.waveshare.com/ESP32-S3-RGB-Matrix
2026-05-29 20:55:36 -07:00
Frank Möhle 1825ce7fec Remove duplicate sentence in secure code instructions
Removed redundant text regarding destination capacity and length safety.
2026-05-30 02:35:56 +02:00
Frank Möhle b4747a827b Restructure usermod documentation for AI, and clarify details 2026-05-30 02:26:59 +02:00
Frank e2708be4a9 bugfix
don't modify tpmPayloadFrameSize, its a global value.
2026-05-30 01:21:13 +02:00
Frank 79bfc8b223 harden UDP packet parsing with bounds checks
some offsets are calculated from the incoming packet (untrusted ingress) - make sure that these do not lead to out-of-bounds array indices.
2026-05-30 01:15:07 +02:00
Frank f98e19c965 more robustness for improv - gracefully handle broken input 2026-05-30 00:16:07 +02:00
Frank Möhle 5d00dd0394 Update caveats and pitfalls in cpp.instructions.md
WLED-MM backport: Clarify operator precedence in C/C++.
2026-05-29 20:26:34 +02:00
Frank Möhle beaa5001b6 small update 2026-05-29 19:54:52 +02:00
Frank Möhle 33fe82c207 Modify verification step for numerical accuracy
small clarifications:
* not only applicable to fixed-point
* added sentence to explain why this rule is needed.
2026-05-29 19:53:52 +02:00
Frank Möhle 36fa42d7ea Update auth checks for '/reset' endpoint clarification
Clarified the handling of the '/reset' endpoint in the auth checks section - not security critical because it will not alter any user data.
2026-05-29 18:30:15 +02:00
Jordan W. Cobb 555d0cfbf7 animated staircase: support for inverted logic PIR pins (#5300)
* Added support for inverting logic of PIR pins
* Fixed indentations and removed whitespace
* Added newline to EOF
2026-05-29 16:54:23 +02:00
Frank Möhle 090ab59a9c HUB75: use FM6124 driver for 4-scan panels, remove 64x64 limitation if board has PSRAM (partial solution for #5628) (#5647)
* HUB75: use FM6124 driver for 4-scan panels
* Remove 1-panel limit if the board has PSRAM
2026-05-29 16:48:00 +02:00
kaibae19 c0a90ea470 fix(FX): restore palette wrap in color_wheel() (regression since 0.15.x) (#5646)
color_wheel() historically called color_from_palette() with moving=true, so
under the default palette blend mode (paletteBlend=0, "wrap if moving") the
palette's end->start seam was interpolated. Commit ee9ac947 changed that call
to moving=false, dropping the wrap; effects that scroll a palette (e.g. the
Palette effect with Retro Clown) then shows the unblended seam travelling
visibly along the strip light-by-light.

Restore the original moving=true (per maintainer review). This fixes the seam
for the Palette effect and every other color_wheel() caller under the default
blend mode, without adding any new function parameters.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 16:39:42 +02:00
Benjam Welker 7a37579ef8 fix button label regression (#5636)
* fix button label regression, also add switch notation
2026-05-29 16:32:27 +02:00
20 changed files with 215 additions and 87 deletions
+3 -3
View File
@@ -17,7 +17,7 @@ language: en-US
reviews:
# generic review setting, see https://docs.coderabbit.ai/reference/configuration#reference
auto_apply_labels: true
auto_apply_labels: false
# abort_on_close: false
high_level_summary: true
review_status: true
@@ -48,11 +48,11 @@ reviews:
NOT to effect functions in FX.cpp, which have diverse contributor styles.
When reviewing PRs labeled "AI" or when source code appears to be AI-generated, perform these additional checks:
1. VERIFY all referenced constants, flags, and functions exist by searching the codebase - do not trust the AI's claims about what exists.
1. VERIFY all referenced preprocessor macros, constants and flags exist by searching the codebase - do not trust the AI's claims about what exists.
2. CHECK for reinvention: search for existing functions/patterns that already solve the same problem.
3. CHECK for singleton data (defined but never used) and for dead/disabled code, and suggest to remove them.
4. VERIFY comments match code behavior - AI frequently generates plausible but incorrect comments.
5. VERIFY numerical stability / accuracy of fixed-point arithmetic.
5. VERIFY numerical stability / accuracy of arithmetic expressions. AI is often wrong when it comes to math and numbers.
6. CHECK for implied but weakly justified assumptions - like usermod loop() call frequency - and ask for clarification.
7. FLAG changes that appear unrelated: deleted comments, unnecessary re-formatting or re-factoring, and modifications in files that seem unrelated to the PR description.
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
# Exclude issues that were closed without resolution from changelog
excludeLabels: 'stale,wontfix,duplicate,invalid,external,question,use-as-is,not_planned'
- name: Update Nightly Release
uses: andelf/nightly-release@main
uses: andelf/nightly-release@5834076edc55cc05975561c9722043f072ac5c26
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
+12 -9
View File
@@ -163,7 +163,7 @@ Background Info:
- Use FreeRTOS mutexes, semaphores or queues when true concurrent access from multiple FreeRTOS tasks is possible, and race-conditions can lead to unexpected behaviour.
- **Avoid `portENTER_CRITICAL()` / `portEXIT_CRITICAL()`**, as these functions stall the complete system and may cause LEDs flickering. Prefer FreeRTOS mutexes, semaphores or queues.
- **Important**: Not every shared resource needs a mutex. Some synchronization is guaranteed by the overall control flow, for example when function calls are sequenced within the same loop iteration.
- Consider RAII as an alternative to mutexes or semaphores.
- Consider using `std::atomic` or RAII scoped guards as alternatives to mutexes, semaphores or queues.
## Web UI Code Style (wled00/data/)
@@ -198,10 +198,10 @@ REGISTER_USERMOD(myUsermod);
refer to detailed examples in `usermods/EXAMPLE/`, `usermods/user_fx/` and [in the user documentation for custom features](https://kno.wled.ge/advanced/custom-features/).
### Usermod `loop()`
- Called once per main loop iteration. Usermods should simply `return` when `!enabled`.
- Frequency of calls varies with system load - up to 2000 times/sec with few LEDs and little background activity, down to 1-3 times/sec during FS activity or during high workload from effects and other usermods.
- Activate via `custom_usermods = ` in platformio build config. The `usermod_v2_` prefix or `_v2` suffix can be omitted.
- Base new usermods on `usermods/EXAMPLE/` (never edit the example directly)
- Store repeated strings as `static const char[] PROGMEM`
- Add usermod IDs to `wled00/const.h` **only when a unique ID is required** (see below)
### Usermod IDs
@@ -213,10 +213,13 @@ A unique ID (registered in `wled00/const.h` and overriding `getId()`) is **only
If none of the above apply, the usermod may omit `getId()` (or return the default `USERMOD_ID_UNSPECIFIED`) and does **not** need an entry in `const.h`.
- Add usermod IDs to `wled00/const.h` **only when a unique ID is required** (see above)
- Activate via `custom_usermods` in platformio build config
- Base new usermods on `usermods/EXAMPLE/` (never edit the example directly)
- Store repeated strings as `static const char[] PROGMEM`
### Usermod `loop()`
- Called once per main loop iteration. Usermods should simply `return` when `!enabled`.
- Frequency of calls varies with system load:
* up to 2000 times/sec with few LEDs and little background activity,
* between 20 and 300 times/second during high workload from effects and other usermods,
* (worst case) down to 1-3 times/sec during FS activity or when serving lots of network API requests.
## CI/CD
+2
View File
@@ -515,6 +515,8 @@ void myTask(void*) {
- **LittleFS filenames**: File paths passed to `file.open()` must not exceed 255 bytes (`LFS_NAME_MAX`). Validate constructed paths (e.g., `/ledmap_` + segment name + `.json`) stay within this limit (assume standard configurations, like WLED_MAX_SEGNAME_LEN = 64).
- In C/C++, additive operators (`+`, `-`) have HIGHER precedence than shift operators (`<<`, `>>`). Therefore `x - edge0 << 8` correctly parses as `(x - edge0) << 8`. Do NOT flag this pattern as a precedence bug. When reviewing WLED fixed-point code or any C/C++ shift expressions, verify against cppreference before claiming precedence issues with mixed `-`/`+` and `<<`/`>>` expressions.
- **Float-to-unsigned conversion is undefined behavior when the value is out of range.** Converting a negative `float` directly to an unsigned integer type (`uint8_t`, `uint16_t`, …) is UB per the C++ standard — the Xtensa (ESP32) toolchain may silently wrap, but RISC-V (ESP32-C3/C5/C6/P4) can produce different results due to clamping. Cast through a signed integer first:
```cpp
// Undefined behavior — avoid:
+1 -1
View File
@@ -68,7 +68,6 @@ De-prioritize unless explicitly introduced by a PR:
- Prefer bounded alternatives for string operations (`strnlen`, `strncmp`, `strncpy`, `strlcpy`, `snprintf`).
- Treat a finding against FW1 as **suggestion** only when the operation is provably bounded
and both the destination capacity and copied/compared length are known safe.
and both the destination capacity and copied/compared length are known safe.
### FW2: Format-string injection
- **Severity**: CRITICAL
@@ -87,6 +86,7 @@ De-prioritize unless explicitly introduced by a PR:
### FW5: Missing auth checks on state-changing endpoints (where auth is feasible)
- **Severity**: CRITICAL
- HTTP/JSON and other control paths that support auth must enforce configured auth policy.
- Do not flag the HTTP endpoint `/reset` as state-changing. This endpoint triggers a reboot, causing a short interruption without loss of user data.
- Do not flag standards-based UDP multicast/broadcast paths solely for lacking authentication when authentication is not defined in the protocol specification.
### FW6: Fail-open behavior after parse or allocation errors
+26
View File
@@ -298,8 +298,18 @@ AR_lib_deps = ;; for pre-usermod-library platformio_override compatibility
;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly.
;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio.
;; tasmota platform (default)
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8
platform_packages =
;; espressif platform (optional - needs 300KB extra flash)
;;; arduino-esp32 2.0.17 + esp-idf 4.4.7
;; platform = espressif32@ ~6.13.0
;; platform_packages =
;;; arduino-esp32 2.0.14 + esp-idf 4.4.6
;; platform = espressif32@ ~6.6.0
;; platform_packages = platformio/framework-arduinoespressif32 @ 3.20014.231204
build_unflags = ${common.build_unflags}
build_flags = -g
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
@@ -720,6 +730,7 @@ board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigge
;; Core HUB75 flags - common to every HUB75 build
build_flags =
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D NO_CIE1931 ;; disable driver-internal gamma correction
-D WLED_DEBUG_BUS
-D LED_TYPES=TYPE_HUB75MATRIX_HS
; -D WLED_DEBUG
@@ -779,6 +790,21 @@ lib_deps = ${esp32s3.lib_deps}
${hub75.lib_deps}
;; board_build.partitions = tools/partitions-8MB_spiffs-tinyuf2.csv ;; supports adafruit UF2 bootloader
[env:waveshare_esp32s3_32MB_hub75]
;; Waveshare ESP32-S3-RGB-Matrix (memory_type: opi_opi); see https://docs.waveshare.com/ESP32-S3-RGB-Matrix
extends = env:esp32S3_wroom2_32MB
monitor_filters = esp32_exception_decoder
build_unflags = ${env:esp32S3_wroom2_32MB.build_unflags}
-D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2_32MB\" ;; need to un-set the relese name before setting a new one
build_flags = ${common.build_flags} ${esp32s3.build_flags} ${hub75.build_flags} ${hub75.s3_build_flags} ${hub75.i2s_disable_flags}
-D WLED_RELEASE_NAME=\"ESP32-S3_Waveshare_HUB75\"
-D WAVESHARE_S3_PINOUT
; -D WLED_USE_SD_SPI
; -D SD_PRINT_HOME_DIR
; -D WLED_DEBUG
lib_deps = ${esp32s3.lib_deps}
${hub75.lib_deps}
[env:esp32s3dev_16MB_opi_hub75]
;; MOONHUB HUB75 adapter board (lilygo T7-S3 with 16MB flash and octal PSRAM)
extends = env:esp32s3dev_8MB_opi
+12 -11
View File
@@ -28,18 +28,19 @@ class ADS1115Usermod : public Usermod {
}
void loop() {
if (isEnabled && millis() - lastTime > loopInterval) {
lastTime = millis();
if (!isEnabled || strip.isUpdating() || millis() - lastTime <= loopInterval)
return;
// If we don't have new data, skip this iteration.
if (!ads.conversionComplete()) {
return;
}
lastTime = millis();
updateResult();
moveToNextChannel();
startReading();
// If we don't have new data, skip this iteration.
if (!ads.conversionComplete()) {
return;
}
updateResult();
moveToNextChannel();
startReading();
}
void addToJsonInfo(JsonObject& root)
@@ -69,6 +70,8 @@ class ADS1115Usermod : public Usermod {
{
JsonObject top = root.createNestedObject(F("ADC ADS1115"));
top[F("Loop Interval")] = loopInterval;
for (uint8_t i = 0; i < channelsCount; i++) {
ChannelSettings* settingsPtr = &(channelSettings[i]);
JsonObject channel = top.createNestedObject(settingsPtr->settingName);
@@ -79,8 +82,6 @@ class ADS1115Usermod : public Usermod {
channel[F("Offset")] = settingsPtr->offset;
channel[F("Decimals")] = settingsPtr->decimals;
}
top[F("Loop Interval")] = loopInterval;
}
bool readFromConfig(JsonObject& root)
+2 -2
View File
@@ -2,7 +2,7 @@
"name": "ADS1115_v2",
"build": { "libArchive": false },
"dependencies": {
"Adafruit BusIO": "https://github.com/adafruit/Adafruit_BusIO#1.13.2",
"Adafruit ADS1X15": "https://github.com/adafruit/Adafruit_ADS1X15#2.4.0"
"Adafruit BusIO": "https://github.com/adafruit/Adafruit_BusIO#1.17.4",
"Adafruit ADS1X15": "https://github.com/adafruit/Adafruit_ADS1X15#2.6.2"
}
}
+24 -5
View File
@@ -1,10 +1,29 @@
# ADS1115 16-Bit ADC with four inputs
# ADS1115 Usermod
This usermod will read from an ADS1115 ADC. The voltages are displayed in the Info section of the web UI.
Reads values from an ADS1115 16-bit ADC and exposes them in the `Info` tab.
Configuration is performed via the Usermod menu. There are no parameters to set in code!
## Features
- Reads values from an ADS1115 over I2C.
- Supports 8 ADS1115 input modes:
- 4 single-ended inputs (`AIN0` to `AIN3`)
- 4 differential pairs (`AIN0-AIN1`, `AIN0-AIN3`, `AIN1-AIN3`, `AIN2-AIN3`)
- Per-channel configuration in the Usermod settings:
- Enable/disable
- Display name
- Units
- Multiplier and offset
- Decimal precision
- Configurable measurement loop interval.
- Publishes configured channel values to the `Info` tab.
## Installation
## Compatibility
- Requires an ADS1115 module connected via I2C.
- Works on targets with I2C support.
- Default ADC gain is `1x` (input range `+/-4.096V`).
Add 'ADS1115' to `custom_usermods` in your platformio environment.
## Installation
- Add `ADS1115` to `custom_usermods` in your `platformio.ini` (or `platformio_override.ini`).
## Author
- Dima Zhemkov [@dima-zhemkov](https://github.com/dima-zhemkov)
@@ -25,6 +25,8 @@ class Animated_Staircase : public Usermod {
unsigned int topMaxDist = 50; // default maximum measured distance in cm, top
unsigned int bottomMaxDist = 50; // default maximum measured distance in cm, bottom
bool togglePower = false; // toggle power on/off with staircase on/off
bool topAPinInvert = false; // invert output of top sensor
bool bottomAPinInvert = false; // invert output of bottom sensor
/* runtime variables */
bool initDone = false;
@@ -91,6 +93,8 @@ class Animated_Staircase : public Usermod {
static const char _topEchoCm[];
static const char _bottomEchoCm[];
static const char _togglePower[];
static const char _topPIRorTrigger_pin_invert[];
static const char _bottomPIRorTrigger_pin_invert[];
void publishMqtt(bool bottom, const char* state) {
#ifndef WLED_DISABLE_MQTT
@@ -156,6 +160,12 @@ class Animated_Staircase : public Usermod {
return pulseIn(echoPin, HIGH, maxTimeUs) > 0;
}
bool readPIRPin(int8_t pin, bool invert) {
if (pin < 0) return false;
bool v = digitalRead(pin);
return invert ? !v : v;
}
bool checkSensors() {
bool sensorChanged = false;
@@ -164,15 +174,15 @@ class Animated_Staircase : public Usermod {
bottomSensorRead = bottomSensorWrite ||
(!useUSSensorBottom ?
(bottomPIRorTriggerPin<0 ? false : digitalRead(bottomPIRorTriggerPin)) :
(bottomPIRorTriggerPin<0 ? false : readPIRPin(bottomPIRorTriggerPin, bottomAPinInvert)) :
ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxDist*59) // cm to us
);
topSensorRead = topSensorWrite ||
(!useUSSensorTop ?
(topPIRorTriggerPin<0 ? false : digitalRead(topPIRorTriggerPin)) :
(topPIRorTriggerPin<0 ? false : readPIRPin(topPIRorTriggerPin, topAPinInvert)) :
ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxDist*59) // cm to us
);
if (bottomSensorRead != bottomSensorState) {
bottomSensorState = bottomSensorRead; // change previous state
sensorChanged = true;
@@ -439,18 +449,20 @@ class Animated_Staircase : public Usermod {
if (staircase.isNull()) {
staircase = root.createNestedObject(FPSTR(_name));
}
staircase[FPSTR(_enabled)] = enabled;
staircase[FPSTR(_segmentDelay)] = segment_delay_ms;
staircase[FPSTR(_onTime)] = on_time_ms / 1000;
staircase[FPSTR(_useTopUltrasoundSensor)] = useUSSensorTop;
staircase[FPSTR(_topPIRorTrigger_pin)] = topPIRorTriggerPin;
staircase[FPSTR(_topEcho_pin)] = useUSSensorTop ? topEchoPin : -1;
staircase[FPSTR(_useBottomUltrasoundSensor)] = useUSSensorBottom;
staircase[FPSTR(_bottomPIRorTrigger_pin)] = bottomPIRorTriggerPin;
staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1;
staircase[FPSTR(_topEchoCm)] = topMaxDist;
staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist;
staircase[FPSTR(_togglePower)] = togglePower;
staircase[FPSTR(_enabled)] = enabled;
staircase[FPSTR(_segmentDelay)] = segment_delay_ms;
staircase[FPSTR(_onTime)] = on_time_ms / 1000;
staircase[FPSTR(_useTopUltrasoundSensor)] = useUSSensorTop;
staircase[FPSTR(_topPIRorTrigger_pin)] = topPIRorTriggerPin;
staircase[FPSTR(_topEcho_pin)] = useUSSensorTop ? topEchoPin : -1;
staircase[FPSTR(_useBottomUltrasoundSensor)] = useUSSensorBottom;
staircase[FPSTR(_bottomPIRorTrigger_pin)] = bottomPIRorTriggerPin;
staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1;
staircase[FPSTR(_topEchoCm)] = topMaxDist;
staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist;
staircase[FPSTR(_togglePower)] = togglePower;
staircase[FPSTR(_topPIRorTrigger_pin_invert)] = topAPinInvert;
staircase[FPSTR(_bottomPIRorTrigger_pin_invert)] = bottomAPinInvert;
DEBUG_PRINTLN(F("Staircase config saved."));
}
@@ -462,11 +474,13 @@ class Animated_Staircase : public Usermod {
bool readFromConfig(JsonObject& root) {
bool oldUseUSSensorTop = useUSSensorTop;
bool oldUseUSSensorBottom = useUSSensorBottom;
bool oldTopAPinInvert = topAPinInvert;
bool oldBottomAPinInvert = bottomAPinInvert;
int8_t oldTopAPin = topPIRorTriggerPin;
int8_t oldTopBPin = topEchoPin;
int8_t oldBottomAPin = bottomPIRorTriggerPin;
int8_t oldBottomBPin = bottomEchoPin;
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
@@ -485,10 +499,12 @@ class Animated_Staircase : public Usermod {
useUSSensorTop = top[FPSTR(_useTopUltrasoundSensor)] | useUSSensorTop;
topPIRorTriggerPin = top[FPSTR(_topPIRorTrigger_pin)] | topPIRorTriggerPin;
topEchoPin = top[FPSTR(_topEcho_pin)] | topEchoPin;
topAPinInvert = top[FPSTR(_topPIRorTrigger_pin_invert)] | topAPinInvert;
useUSSensorBottom = top[FPSTR(_useBottomUltrasoundSensor)] | useUSSensorBottom;
bottomPIRorTriggerPin = top[FPSTR(_bottomPIRorTrigger_pin)] | bottomPIRorTriggerPin;
bottomEchoPin = top[FPSTR(_bottomEcho_pin)] | bottomEchoPin;
bottomAPinInvert = top[FPSTR(_bottomPIRorTrigger_pin_invert)] | bottomAPinInvert;
topMaxDist = top[FPSTR(_topEchoCm)] | topMaxDist;
topMaxDist = min(150,max(30,(int)topMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)
@@ -554,14 +570,15 @@ const char Animated_Staircase::_segmentDelay[] PROGMEM = "segment-d
const char Animated_Staircase::_onTime[] PROGMEM = "on-time-s";
const char Animated_Staircase::_useTopUltrasoundSensor[] PROGMEM = "useTopUltrasoundSensor";
const char Animated_Staircase::_topPIRorTrigger_pin[] PROGMEM = "topPIRorTrigger_pin";
const char Animated_Staircase::_topPIRorTrigger_pin_invert[] PROGMEM = "topPIRorTrigger_pin_invert";
const char Animated_Staircase::_topEcho_pin[] PROGMEM = "topEcho_pin";
const char Animated_Staircase::_useBottomUltrasoundSensor[] PROGMEM = "useBottomUltrasoundSensor";
const char Animated_Staircase::_bottomPIRorTrigger_pin[] PROGMEM = "bottomPIRorTrigger_pin";
const char Animated_Staircase::_bottomPIRorTrigger_pin_invert[] PROGMEM = "bottomPIRorTrigger_pin_invert";
const char Animated_Staircase::_bottomEcho_pin[] PROGMEM = "bottomEcho_pin";
const char Animated_Staircase::_topEchoCm[] PROGMEM = "top-dist-cm";
const char Animated_Staircase::_bottomEchoCm[] PROGMEM = "bottom-dist-cm";
const char Animated_Staircase::_togglePower[] PROGMEM = "toggle-on-off";
static Animated_Staircase animated_staircase;
REGISTER_USERMOD(animated_staircase);
+1 -1
View File
@@ -1117,7 +1117,7 @@ void Segment::blur(uint8_t blur_amount, bool smear) const {
* Rotates the color in HSV space, where pos is H. (0=0deg, 256=360deg)
*/
uint32_t Segment::color_wheel(uint8_t pos) const {
if (palette) return color_from_palette(pos, false, false, 0); // only wrap if "always wrap" is set
if (palette) return color_from_palette(pos, false, true, 0); // color_wheel is a continuous (moving) wheel, so wrap end->start (restores pre-0.16 behaviour)
uint8_t w = W(getCurrentColor(0));
CRGBW rgb;
rgb = CHSV32(static_cast<uint16_t>(pos << 8), 255, 255);
+38 -20
View File
@@ -802,10 +802,11 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
_hasRgb = true;
_hasWhite = false;
virtualDisp = nullptr; // todo: this should be solved properly, can cause memory leak (if omitted here, nothing seems to work)
_isVirtual = false;
// aliases for easier reading
uint8_t panelWidth = bc.pins[0];
uint8_t panelHeight = bc.pins[1];
uint8_t chainLength = bc.pins[2];
unsigned panelWidth = bc.pins[0];
unsigned panelHeight = bc.pins[1];
unsigned chainLength = bc.pins[2];
_rows = bc.pins[3];
_cols = bc.pins[4];
@@ -813,6 +814,7 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
// mxconfig.driver = HUB75_I2S_CFG::ICN2038S; // experimental - use specific shift register driver
// mxconfig.driver = HUB75_I2S_CFG::FM6124; // try this driver in case you panel stays dark, or when colors look too pastel
// Other possible shiftreg drivers: HUB75_I2S_CFG::FM6126A, HUB75_I2S_CFG::ICN2038S, HUB75_I2S_CFG::MBI5124, HUB75_I2S_CFG::DP3246
// mxconfig.latch_blanking = 3;
// mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // experimental - 5MHZ should be enugh, but colours looks slightly better at 10MHz
@@ -821,20 +823,26 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
mxconfig.clkphase = bc.reversed;
// allow chain length up to 4, limit to prevent bad data from preventing boot due to low memory
mxconfig.chain_length = max((uint8_t) 1, min(chainLength, (uint8_t) 4));
mxconfig.chain_length = max(1U, min(chainLength, 4U));
if (mxconfig.mx_height >= 64 && (mxconfig.chain_length > 1)) {
DEBUGBUS_PRINTLN(F("WARNING, only single panel can be used of 64 pixel boards due to memory"));
mxconfig.chain_length = 1;
#if defined(BOARD_HAS_PSRAM) // limitation to one panel only applies to boards without PSRAM
if (!psramFound() || ESP.getPsramSize() == 0) // PSRAM sanity check
#endif
{
DEBUGBUS_PRINTLN(F("WARNING, only single panel can be used of 64 pixel boards due to memory"));
mxconfig.chain_length = 1;
}
}
if (bc.type == TYPE_HUB75MATRIX_HS) {
mxconfig.mx_width = min((uint8_t) 64, panelWidth); // TODO: UI limit is 128, this limits to 64
mxconfig.mx_height = min((uint8_t) 64, panelHeight);
mxconfig.mx_width = min(128U, panelWidth); // UI limit is 128
mxconfig.mx_height = min(64U, panelHeight);
} else if (bc.type == TYPE_HUB75MATRIX_QS) {
_isVirtual = true;
mxconfig.mx_width = min((uint8_t) 64, panelWidth) * 2;
mxconfig.mx_height = min((uint8_t) 64, panelHeight) / 2;
mxconfig.mx_width = min(128U, panelWidth) * 2;
mxconfig.mx_height = min(64U, panelHeight) / 2;
mxconfig.driver = HUB75_I2S_CFG::FM6124; // use FM6124 for "outdoor" 4-scan panels - workaround until we can make the driver user-configurable
} else {
DEBUGBUS_PRINTLN("Unknown type");
return;
@@ -874,6 +882,18 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
mxconfig.gpio = { 1, 5, 6, 7, 13, 9, 16, 48, 47, 21, 38, 8, 4, 18 };
#elif defined(WAVESHARE_S3_PINOUT)
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - Waveshare S3 with PSRAM, Waveshare pinout");
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
mxconfig.gpio = {4, 5, 6, 7, 15, 16, 18, 8, 3, 42, 9, 40, 2, 41};
#elif defined(SEENGREAT_V2_PINOUT)
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - S3 devKit-C with PSRAM, SEENGREAT_V2 pinout");
mxconfig.gpio = { 18, 8, 17, // R1_PIN, G1_PIN, B1_PIN,
16, 1, 15, // R2_PIN, G2_PIN, B2_PIN,
7, 48, 6, 47, 2, // A_PIN, B_PIN, C_PIN, D_PIN, E_PIN,
21, 4, 5 }; //LAT_PIN, OE_PIN,CLK_PIN
#else
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - S3 with PSRAM");
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
@@ -990,25 +1010,23 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
}
setBitArray(_ledsDirty, _len, false); // reset dirty bits
if (mxconfig.double_buff == false) {
// create LEDs buffer (initialized to BLACK), prefer DRAM if enough heap is available (faster in case global _pixels buffer is in PSRAM as not both will fit the cache)
_ledBuffer = static_cast<CRGB*>(allocate_buffer(_len * sizeof(CRGB), BFRALLOC_PREFER_DRAM | BFRALLOC_CLEAR));
}
// create LEDs buffer (initialized to BLACK), prefer DRAM if enough heap is available (faster in case global _pixels buffer is in PSRAM as not both will fit the cache)
_ledBuffer = static_cast<CRGB*>(allocate_buffer(_len * sizeof(CRGB), BFRALLOC_PREFER_DRAM | BFRALLOC_CLEAR));
}
PANEL_CHAIN_TYPE chainType = CHAIN_NONE; // default for quarter-scan panels that do not use chaining
// chained panels with cols and rows define need the virtual display driver, so do quarter-scan panels
if (chainLength > 1 && (_rows > 1 || _cols > 1) || bc.type == TYPE_HUB75MATRIX_QS) {
_isVirtual = true;
chainType = CHAIN_BOTTOM_LEFT_UP; // TODO: is there any need to support other chaining types?
DEBUGBUS_PRINTF_P(PSTR("Using virtual matrix: %ux%u panels of %ux%u pixels\n"), _cols, _rows, mxconfig.mx_width, mxconfig.mx_height);
if (chainLength > 1 && (_rows > 1 || _cols > 1)) chainType = CHAIN_TOP_RIGHT_DOWN; // we need to use a _DOWN chainType, otherwise the display is upside-down
DEBUGBUS_PRINTF_P(PSTR("Using virtual matrix: %ux%u panels of %ux%u pixels\n"), _cols, _rows, mxconfig.mx_width/2, mxconfig.mx_height*2);
}
else {
_isVirtual = false;
}
if (_isVirtual) {
virtualDisp = new VirtualMatrixPanel((*display), _rows, _cols, mxconfig.mx_width, mxconfig.mx_height, chainType);
virtualDisp = new VirtualMatrixPanel((*display), _rows, _cols, mxconfig.mx_width/2, mxconfig.mx_height*2, chainType);
virtualDisp->setRotation(0);
if (bc.type == TYPE_HUB75MATRIX_QS) {
switch(panelHeight) {
@@ -1079,7 +1097,7 @@ void IRAM_ATTR BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c) {
uint32_t BusHub75Matrix::getPixelColor(unsigned pix) const {
if (!_valid) return IS_BLACK; // note: no need to check pix >= _len as that is checked in containsPixel()
if (_ledBuffer)
return uint32_t(_ledBuffer[pix]);
return uint32_t(_ledBuffer[pix]) & 0x00FFFFFF; // FastLED 32bit is RGBA, we need RGBW
else
return getBitFromArray(_ledsDirty, pix) ? IS_DARKGREY: IS_BLACK; // just a hack - we only know if the pixel is black or not
}
@@ -1144,8 +1162,8 @@ std::vector<LEDType> BusHub75Matrix::getLEDTypes() {
size_t BusHub75Matrix::getPins(uint8_t* pinArray) const {
if (pinArray) {
pinArray[0] = mxconfig.mx_width;
pinArray[1] = mxconfig.mx_height;
pinArray[0] = _isVirtual ? mxconfig.mx_width /2 : mxconfig.mx_width;
pinArray[1] = _isVirtual ? mxconfig.mx_height *2 : mxconfig.mx_height;
pinArray[2] = mxconfig.chain_length;
pinArray[3] = _rows;
pinArray[4] = _cols;
+5 -5
View File
@@ -252,18 +252,18 @@
var buttonBlock = document.createElement('div');
buttonBlock.className = 'bb';
buttonBlock.innerHTML = `
<div class="bh">Button ${i}</div>
<div class="bh">Button (switch) ${i}</div>
<div class="bs">
<div class="ba">
<label>Push/Switch</label>
<label>Short (on → off)</label>
<select name="MP${b}" class="s" required>${presetOpts}</select>
</div>
<div class="ba">
<label>Short (on→off)</label>
<label>Long (off → on)</label>
<select name="ML${b}" class="s" required>${presetOpts}</select>
</div>
<div class="ba">
<label>Long (off→on)</label>
<label>Double press (n/a)</label>
<select name="MD${b}" class="s" required>${presetOpts}</select>
</div>
</div>
@@ -372,7 +372,7 @@
Alexa On/Off Preset: <input name="A0" class="m" type="number" min="0" max="250" required> <input name="A1" class="m" type="number" min="0" max="250" required><br>
</div>
<div class="sec">
<h3>Button Action Presets</h3>
<h3>Button (switch) Action Presets</h3>
<div id="macros"></div>
<a href="https://kno.wled.ge/features/macros/#analog-button" target="_blank">Analog Button setup</a>
</div>
+5
View File
@@ -414,6 +414,11 @@ namespace UsermodManager {
// Register usermods by building a static list via a linker section
#define REGISTER_USERMOD(x) DYNARRAY_MEMBER(Usermod*, usermods, um_##x, 1) = &x
//usermod.cpp
void userSetup();
void userConnected();
void userLoop();
//util.cpp
#ifdef ESP8266
#define HW_RND_REGISTER RANDOM_REG32
+6 -7
View File
@@ -116,8 +116,8 @@ void handleImprovPacket() {
return;
}
} else if (packetByte > 9) { //RPC data
rpcData[packetByte - 10] = next;
if (packetByte > 137) return; //prevent buffer overflow
rpcData[packetByte - 10] = next;
}
}
}
@@ -238,8 +238,8 @@ void handleImprovWifiScan() {
bool isOpen = WiFi.encryptionType(i) == WIFI_AUTH_OPEN;
#endif
char ssidStr[33];
strcpy(ssidStr, WiFi.SSID(i).c_str());
char ssidStr[33] = {'\0'};
strlcpy(ssidStr, WiFi.SSID(i).c_str(), sizeof(ssidStr));
const char *str[3] = {ssidStr, rssiStr, isOpen ? "NO":"YES"};
sendImprovRPCResult(ImprovRPCType::Request_Scan, 3, str);
}
@@ -258,14 +258,13 @@ static void parseWiFiCommand(char* rpcData) {
unsigned ssidLen = rpcData[1];
if (ssidLen > len -1 || ssidLen > 32) return;
memset(multiWiFi[0].clientSSID, 0, 32);
memset(multiWiFi[0].clientSSID, 0, sizeof(multiWiFi[0].clientSSID));
memcpy(multiWiFi[0].clientSSID, rpcData+2, ssidLen);
memset(multiWiFi[0].clientPass, 0, 64);
memset(multiWiFi[0].clientPass, 0, sizeof(multiWiFi[0].clientPass));
if (len > ssidLen +1) {
unsigned passLen = rpcData[2+ssidLen];
memset(multiWiFi[0].clientPass, 0, 64);
memcpy(multiWiFi[0].clientPass, rpcData+3+ssidLen, passLen);
memcpy(multiWiFi[0].clientPass, rpcData+3+ssidLen, min(size_t(passLen), sizeof(multiWiFi[0].clientPass)));
}
sendImprovStateResponse(0x03); //provisioning
+6 -3
View File
@@ -6,7 +6,7 @@
#define UDP_SEG_SIZE 36
#define SEG_OFFSET (41)
#define WLEDPACKETSIZE (41+(WS2812FX::getMaxSegments()*UDP_SEG_SIZE)+0)
static constexpr size_t WLEDPACKETSIZE = 41+(WS2812FX::getMaxSegments()*UDP_SEG_SIZE); // make sure this is known at compile-time
#define UDP_IN_MAXSIZE 1472
#define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times
@@ -268,6 +268,7 @@ static void parseNotifyPacket(const uint8_t *udpIn) {
size_t inactiveSegs = 0;
for (size_t i = 0; i < numSrcSegs && i < WS2812FX::getMaxSegments(); i++) {
unsigned ofs = 41 + i*udpIn[40]; //start of segment offset byte
if (ofs + 36 > UDP_IN_MAXSIZE) break; // avoid reading outside of array
unsigned id = udpIn[0 +ofs];
DEBUG_PRINTF_P(PSTR("UDP segment received: %u\n"), id);
if (id > strip.getSegmentsNum()) break;
@@ -499,7 +500,7 @@ void handleNotifications()
packetSize = rgbUdp.parsePacket();
if (packetSize) {
if (!receiveDirect) return;
if (packetSize > UDP_IN_MAXSIZE || packetSize < 3) return;
if (packetSize > UDP_IN_MAXSIZE || packetSize < 3) return; // packetSize must not exceed buffersize (UDP_IN_MAXSIZE)
realtimeIP = rgbUdp.remoteIP();
DEBUG_PRINTLN(rgbUdp.remoteIP());
uint8_t lbuf[packetSize];
@@ -587,7 +588,9 @@ void handleNotifications()
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
unsigned totalLen = strip.getLengthTotal();
for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
// Clamp to prevent buffer overread: loop accesses up to udpIn[tpmPayloadFrameSize + 5]
size_t currentPayloadFrameSize = (packetSize >= 5) ? min(tpmPayloadFrameSize, uint16_t(packetSize - 5)) : 0;
for (size_t i = 6; i < currentPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
}
if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
+29
View File
@@ -0,0 +1,29 @@
#include "wled.h"
/*
* This v1 usermod file allows you to add own functionality to WLED more easily
* See: https://github.com/wled-dev/WLED/wiki/Add-own-functionality
* EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h)
* If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE)
*
* Consider the v2 usermod API if you need a more advanced feature set!
*/
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
//gets called once at boot. Do all initialization that doesn't depend on network here
void userSetup()
{
}
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
void userConnected()
{
}
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
void userLoop()
{
}
+3
View File
@@ -1296,6 +1296,9 @@ String computeSHA1(const String& input) {
#ifdef ESP32
#include "esp_adc_cal.h"
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,4,7) // backwards compatibility patch
#define ADC_ATTEN_DB_12 ADC_ATTEN_DB_11
#endif
String generateDeviceFingerprint() {
uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint
esp_chip_info_t chip_info;
+3
View File
@@ -78,6 +78,7 @@ void WLED::loop()
#ifdef WLED_DEBUG
unsigned long usermodMillis = millis();
#endif
userLoop();
UsermodManager::loop();
#ifdef WLED_DEBUG
usermodMillis = millis() - usermodMillis;
@@ -496,6 +497,7 @@ void WLED::setup()
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
DEBUG_PRINTLN(F("Usermods setup"));
userSetup();
UsermodManager::setup();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
@@ -978,6 +980,7 @@ void WLED::handleConnection()
if (improvActive > 1) sendImprovIPRPCResult(ImprovRPCType::Command_Wifi);
}
initInterfaces();
userConnected();
UsermodManager::connected();
lastMqttReconnectAttempt = 0; // force immediate update
+1 -1
View File
@@ -274,7 +274,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#define STRINGIFY(X) #X
#define TOSTRING(X) STRINGIFY(X)
#define WLED_CODENAME "Niji"
#define WLED_CODENAME "Kagayaki"
// AP and OTA default passwords (for maximum security change them!)
WLED_GLOBAL char apPass[65] _INIT(WLED_AP_PASS);