diff --git a/.github/workflows/usermods.yml b/.github/workflows/usermods.yml new file mode 100644 index 0000000000..a1cd8fbb79 --- /dev/null +++ b/.github/workflows/usermods.yml @@ -0,0 +1,71 @@ +name: Usermod CI + +on: + push: + paths: + - usermods/** + - .github/workflows/usermods.yml + +jobs: + + get_usermod_envs: + name: Gather Usermods + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + - name: Install PlatformIO + run: pip install -r requirements.txt + - name: Get default environments + id: envs + run: | + echo "usermods=$(find usermods/ -name library.json | xargs dirname | xargs -n 1 basename | jq -R | grep -v PWM_fan | grep -v BME68X_v2| grep -v pixels_dice_tray | jq --slurp -c)" >> $GITHUB_OUTPUT + outputs: + usermods: ${{ steps.envs.outputs.usermods }} + + + build: + name: Build Enviornments + runs-on: ubuntu-latest + needs: get_usermod_envs + strategy: + fail-fast: false + matrix: + usermod: ${{ fromJSON(needs.get_usermod_envs.outputs.usermods) }} + environment: [usermods_esp32, usermods_esp32c3, usermods_esp32s2, usermods_esp32s3] + steps: + - uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + - run: npm ci + - name: Cache PlatformIO + uses: actions/cache@v4 + with: + path: | + ~/.platformio/.cache + ~/.buildcache + build_output + 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 + with: + python-version: '3.12' + cache: 'pip' + - name: Install PlatformIO + run: pip install -r requirements.txt + - name: Add usermods environment + run: | + cp -v usermods/platformio_override.usermods.ini platformio_override.ini + echo >> platformio_override.ini + echo "custom_usermods = ${{ matrix.usermod }}" >> platformio_override.ini + cat platformio_override.ini + + - name: Build firmware + run: pio run -e ${{ matrix.environment }} diff --git a/boards/lilygo-t7-s3.json b/boards/lilygo-t7-s3.json new file mode 100644 index 0000000000..4bf071fc7e --- /dev/null +++ b/boards/lilygo-t7-s3.json @@ -0,0 +1,47 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DARDUINO_TTGO_T7_S3", + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + [ + "0X303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "LILYGO T3-S3", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.aliexpress.us/item/3256804591247074.html", + "vendor": "LILYGO" +} \ No newline at end of file diff --git a/pio-scripts/load_usermods.py b/pio-scripts/load_usermods.py index 146cb1f870..38a08401e6 100644 --- a/pio-scripts/load_usermods.py +++ b/pio-scripts/load_usermods.py @@ -77,17 +77,18 @@ def wrapped_ConfigureProjectLibBuilder(xenv): for dep in result.depbuilders: cached_add_includes(dep, processed_deps, extra_include_dirs) + wled_deps = [dep for dep in result.depbuilders if is_wled_module(dep)] + broken_usermods = [] - for dep in result.depbuilders: - if is_wled_module(dep): - # Add the wled folder to the include path - dep.env.PrependUnique(CPPPATH=str(wled_dir)) - # Add WLED's own dependencies - for dir in extra_include_dirs: - dep.env.PrependUnique(CPPPATH=str(dir)) - # Enforce that libArchive is not set; we must link them directly to the executable - if dep.lib_archive: - broken_usermods.append(dep) + for dep in wled_deps: + # Add the wled folder to the include path + dep.env.PrependUnique(CPPPATH=str(wled_dir)) + # Add WLED's own dependencies + for dir in extra_include_dirs: + dep.env.PrependUnique(CPPPATH=str(dir)) + # Enforce that libArchive is not set; we must link them directly to the executable + if dep.lib_archive: + broken_usermods.append(dep) if broken_usermods: broken_usermods = [usermod.name for usermod in broken_usermods] @@ -97,6 +98,9 @@ def wrapped_ConfigureProjectLibBuilder(xenv): err=True) Exit(1) + # Save the depbuilders list for later validation + xenv.Replace(WLED_MODULES=wled_deps) + return result # Apply the wrapper diff --git a/pio-scripts/validate_modules.py b/pio-scripts/validate_modules.py index f8c9a599d9..d63b609ac8 100644 --- a/pio-scripts/validate_modules.py +++ b/pio-scripts/validate_modules.py @@ -53,20 +53,8 @@ def validate_map_file(source, target, env): secho(f"ERROR: Map file not found: {map_file_path}", fg="red", err=True) Exit(1) - # Identify the WLED module source directories - module_lib_builders = [builder for builder in env.GetLibBuilders() if is_wled_module(env, builder)] - - if env.GetProjectOption("custom_usermods","") == "*": - # All usermods build; filter non-platform-OK modules - module_lib_builders = [builder for builder in module_lib_builders if env.IsCompatibleLibBuilder(builder)] - else: - incompatible_builders = [builder for builder in module_lib_builders if not env.IsCompatibleLibBuilder(builder)] - if incompatible_builders: - secho( - f"ERROR: Modules {[b.name for b in incompatible_builders]} are not compatible with this platform!", - fg="red", - err=True) - Exit(1) + # Identify the WLED module builders, set by load_usermods.py + module_lib_builders = env['WLED_MODULES'] # Extract the values we care about modules = {Path(builder.build_dir).name: builder.name for builder in module_lib_builders} diff --git a/platformio_override.ini b/platformio_override.ini new file mode 100644 index 0000000000..98b84f2cc1 --- /dev/null +++ b/platformio_override.ini @@ -0,0 +1,88 @@ +# Example PlatformIO Project Configuration Override +# ------------------------------------------------------------------------------ +# Copy to platformio_override.ini to activate overrides +# ------------------------------------------------------------------------------ +# Please visit documentation: https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = esp32dev_hub75, adafruit_matrixportal_esp32s3, esp32S3_PSRAM_HUB75, esp32dev_hub75_forum_pinout + +[env:esp32dev_hub75] +board = esp32dev +upload_speed = 921600 +platform = ${esp32_idf_V4.platform} +platform_packages = +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} + -D WLED_RELEASE_NAME=\"ESP32_hub75\" + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + ; -D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins + ; -D WLED_DEBUG +lib_deps = ${esp32_idf_V4.lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#3.0.11 + +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.default_partitions} +board_build.flash_mode = dio + +[env:esp32dev_hub75_forum_pinout] +extends = env:esp32dev_hub75 +build_flags = ${common.build_flags} + -D WLED_RELEASE_NAME=\"ESP32_hub75_forum_pinout\" + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins +; -D WLED_DEBUG + + +[env:adafruit_matrixportal_esp32s3] +; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75 +board = lolin_s3_mini +platform = ${esp32s3.platform} +platform_packages = +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\" + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -DLOLIN_WIFI_FIX ; seems to work much better with this + -D WLED_WATCHDOG_TIMEOUT=0 + ${esp32.AR_build_flags} + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips + -D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3 + +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix + +board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = qio +monitor_filters = esp32_exception_decoder + +[env:esp32S3_PSRAM_HUB75] +;; MOONHUB HUB75 adapter board +board = lilygo-t7-s3 +platform = ${esp32s3.platform} +platform_packages = +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"esp32S3_16MB_PSRAM_HUB75\" + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -DLOLIN_WIFI_FIX ; seems to work much better with this + -D WLED_WATCHDOG_TIMEOUT=0 + ${esp32.AR_build_flags} + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips + -D MOONHUB_S3_PINOUT ;; HUB75 pinout + +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix + +board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = qio +monitor_filters = esp32_exception_decoder + diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index ee2b177143..ede622b8c0 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -181,7 +181,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; ; enable IR by setting remote type ; -D IRTYPE=0 # 0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote -; +; ; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues) ; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue # needed only for classic ESP32 rev.1 ; @@ -526,6 +526,48 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOU -D USER_SETUP_LOADED monitor_filters = esp32_exception_decoder + +[env:esp32dev_hub75] +board = esp32dev +upload_speed = 921600 +platform = ${esp32_idf_V4.platform} +extends = esp32dev_V4 +build_flags = ${common.build_flags} + -D WLED_RELEASE_NAME=\"ESP32_hub75\" + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + ; -D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins + ; -D WLED_DEBUG +lib_deps = ${esp32_idf_V4.lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#3.0.11 +board_build.partitions = ${esp32.default_partitions} +board_build.flash_mode = dio + + +[env:adafruit_matrixportal_esp32s3] +; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75 +board = lolin_s3_mini +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\" + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -DLOLIN_WIFI_FIX ; seems to work much better with this + -D WLED_WATCHDOG_TIMEOUT=0 + ${esp32.AR_build_flags} + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips + -D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3 + +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix + +board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = qio +monitor_filters = esp32_exception_decoder # ------------------------------------------------------------------------------ # Usermod examples # ------------------------------------------------------------------------------ @@ -536,3 +578,86 @@ extends = env:esp32dev build_flags = ${env:esp32dev.build_flags} -D USERMOD_RF433 lib_deps = ${env:esp32dev.lib_deps} sui77/rc-switch @ 2.6.4 + +# ------------------------------------------------------------------------------ +# Hub75 examples +# ------------------------------------------------------------------------------ + +[env:esp32dev_hub75] +board = esp32dev +upload_speed = 921600 +platform = ${esp32_idf_V4.platform} +platform_packages = +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} + -D WLED_RELEASE_NAME=ESP32_hub75 + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + ; -D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins + ; -D WLED_DEBUG +lib_deps = ${esp32_idf_V4.lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#3.0.11 + +monitor_filters = esp32_exception_decoder +board_build.partitions = ${esp32.default_partitions} +board_build.flash_mode = dio + + +[env:adafruit_matrixportal_esp32s3] +; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75 +board = lolin_s3_mini +platform = ${esp32s3.platform} +platform_packages = +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -DLOLIN_WIFI_FIX ; seems to work much better with this + -D WLED_WATCHDOG_TIMEOUT=0 + ${esp32.AR_build_flags} + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips + -D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3 + +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix + +board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = qio +monitor_filters = esp32_exception_decoder + +[env:esp32S3_PSRAM_HUB75] +;; MOONHUB HUB75 adapter board +board = lilygo-t7-s3 +platform = ${esp32s3.platform} +platform_packages = +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"esp32S3_16MB_PSRAM_HUB75\" + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -DLOLIN_WIFI_FIX ; seems to work much better with this + -D WLED_WATCHDOG_TIMEOUT=0 + ${esp32.AR_build_flags} + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips + -D MOONHUB_S3_PINOUT ;; HUB75 pinout + +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix + +board_build.partitions = ${esp32.default_partitions} +board_build.f_flash = 80000000L +board_build.flash_mode = qio +monitor_filters = esp32_exception_decoder + +[env:esp32dev_hub75_forum_pinout] +extends = env:esp32dev_hub75 +build_flags = ${common.build_flags} + -D WLED_RELEASE_NAME=\"ESP32_hub75_forum_pinout\" + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins +; -D WLED_DEBUG \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7a393786d0..fd9806ac23 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,7 +50,7 @@ tabulate==0.9.0 # via platformio typing-extensions==4.12.2 # via anyio -urllib3==2.3.0 +urllib3==2.5.0 # via requests uvicorn==0.34.0 # via platformio diff --git a/usermods/LD2410_v2/readme.md b/usermods/LD2410_v2/readme.md index ea85ab8204..25b1cbbcc3 100644 --- a/usermods/LD2410_v2/readme.md +++ b/usermods/LD2410_v2/readme.md @@ -18,7 +18,7 @@ To enable, compile with `LD2140` in `custom_usermods` (e.g. in `platformio_overr ```ini [env:usermod_USERMOD_LD2410_esp32dev] extends = env:esp32dev -custom_usermods = ${env:esp32dev.custom_usermods} LD2140 +custom_usermods = ${env:esp32dev.custom_usermods} LD2140_v2 ``` ### Configuration Options diff --git a/usermods/Si7021_MQTT_HA/library.json b/usermods/Si7021_MQTT_HA/library.json index e3d7635e35..36a930ca58 100644 --- a/usermods/Si7021_MQTT_HA/library.json +++ b/usermods/Si7021_MQTT_HA/library.json @@ -3,6 +3,8 @@ "build": { "libArchive": false }, "dependencies": { "finitespace/BME280":"3.0.0", - "adafruit/Adafruit Si7021 Library" : "1.5.3" + "adafruit/Adafruit Si7021 Library" : "1.5.3", + "SPI":"*", + "adafruit/Adafruit BusIO": "1.17.1" } } \ No newline at end of file diff --git a/usermods/Si7021_MQTT_HA/readme.md b/usermods/Si7021_MQTT_HA/readme.md index 99a240f7dd..b8ee06a73d 100644 --- a/usermods/Si7021_MQTT_HA/readme.md +++ b/usermods/Si7021_MQTT_HA/readme.md @@ -49,18 +49,8 @@ SDA_PIN = 4; ## Software -Add to `build_flags` in platformio.ini: +Add `Si7021_MQTT_HA` to custom_usermods -``` - -D USERMOD_SI7021_MQTT_HA -``` - -Add to `lib_deps` in platformio.ini: - -``` - adafruit/Adafruit Si7021 Library @ 1.4.0 - BME280@~3.0.0 -``` # Credits diff --git a/usermods/buzzer/buzzer.cpp b/usermods/buzzer/buzzer.cpp index 60db08abcb..9d62fc20c8 100644 --- a/usermods/buzzer/buzzer.cpp +++ b/usermods/buzzer/buzzer.cpp @@ -5,16 +5,17 @@ #define USERMOD_ID_BUZZER 900 #ifndef USERMOD_BUZZER_PIN +#ifdef GPIO_NUM_32 #define USERMOD_BUZZER_PIN GPIO_NUM_32 +#else +#define USERMOD_BUZZER_PIN 21 +#endif #endif /* * Usermods allow you to add own functionality to WLED more easily * See: https://github.com/wled-dev/WLED/wiki/Add-own-functionality * - * Using a usermod: - * 1. Copy the usermod into the sketch folder (same folder as wled00.ino) - * 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp */ class BuzzerUsermod : public Usermod { diff --git a/usermods/platformio_override.usermods.ini b/usermods/platformio_override.usermods.ini new file mode 100644 index 0000000000..84e56bcd16 --- /dev/null +++ b/usermods/platformio_override.usermods.ini @@ -0,0 +1,31 @@ +[platformio] +default_envs = usermods_esp32, usermods_esp32c3, usermods_esp32s2, usermods_esp32s3 + +[env:usermods_esp32] +extends = env:esp32dev_V4 +custom_usermods = ${usermods.custom_usermods} +board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat + + +[env:usermods_esp32c3] +extends = env:esp32c3dev +board = esp32-c3-devkitm-1 +custom_usermods = ${usermods.custom_usermods} +board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat + + +[env:usermods_esp32s2] +extends = env:lolin_s2_mini +custom_usermods = ${usermods.custom_usermods} +board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat + + +[env:usermods_esp32s3] +extends = env:esp32s3dev_16MB_opi +custom_usermods = ${usermods.custom_usermods} +board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat + + + +[usermods] +# Added in CI \ No newline at end of file diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp index 1514e2307a..6be3a92640 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp @@ -54,7 +54,11 @@ class RgbRotaryEncoderUsermod : public Usermod void initLedBus() { - byte _pins[5] = {(byte)ledIo, 255, 255, 255, 255}; + // Initialize all pins to the sentinel value first… + byte _pins[OUTPUT_MAX_PINS]; + std::fill(std::begin(_pins), std::end(_pins), 255); + // …then set only the LED pin + _pins[0] = static_cast(ledIo); BusConfig busCfg = BusConfig(TYPE_WS2812_RGB, _pins, 0, numLeds, COL_ORDER_GRB, false, 0); ledBus = new BusDigital(busCfg, WLED_MAX_BUSSES - 1); diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 4a364ea654..8bc6d8a1be 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -6091,9 +6091,7 @@ uint16_t mode_2Dscrollingtext(void) { } char text[WLED_MAX_SEGNAME_LEN+1] = {'\0'}; - if (SEGMENT.name) for (size_t i=0,j=0; i31 && SEGMENT.name[i]<128) text[j++] = SEGMENT.name[i]; - const bool zero = strchr(text, '0') != nullptr; - + size_t result_pos = 0; char sec[5]; int AmPmHour = hour(localTime); bool isitAM = true; @@ -6105,27 +6103,62 @@ uint16_t mode_2Dscrollingtext(void) { sprintf_P(sec, PSTR(":%02d"), second(localTime)); } - if (!strlen(text)) { // fallback if empty segment name: display date and time + size_t len = 0; + if (SEGMENT.name) len = strlen(SEGMENT.name); // note: SEGMENT.name is limited to WLED_MAX_SEGNAME_LEN + if (len == 0) { // fallback if empty segment name: display date and time sprintf_P(text, PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); } else { - if (text[0] == '#') for (auto &c : text) c = std::toupper(c); - if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, zero?PSTR("%02d.%02d.%04d"):PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); - else if (!strncmp_P(text,PSTR("#DDMM"),5)) sprintf_P(text, zero?PSTR("%02d.%02d") :PSTR("%d.%d"), day(localTime), month(localTime)); - else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, zero?PSTR("%02d/%02d") :PSTR("%d/%d"), month(localTime), day(localTime)); - else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, zero?PSTR("%02d:%02d%s") :PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); - else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d") :PSTR("%d:%02d"), AmPmHour, minute(localTime)); - else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf (text, zero? ("%02d") : ("%d"), AmPmHour); - else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf (text, zero? ("%02d") : ("%d"), minute(localTime)); - else if (!strncmp_P(text,PSTR("#SS"),3)) sprintf (text, ("%02d") , second(localTime)); - else if (!strncmp_P(text,PSTR("#DD"),3)) sprintf (text, zero? ("%02d") : ("%d"), day(localTime)); - else if (!strncmp_P(text,PSTR("#DAY"),4)) sprintf (text, ("%s") , dayShortStr(day(localTime))); - else if (!strncmp_P(text,PSTR("#DDDD"),5)) sprintf (text, ("%s") , dayStr(day(localTime))); - else if (!strncmp_P(text,PSTR("#DAYL"),5)) sprintf (text, ("%s") , dayStr(day(localTime))); - else if (!strncmp_P(text,PSTR("#MO"),3)) sprintf (text, zero? ("%02d") : ("%d"), month(localTime)); - else if (!strncmp_P(text,PSTR("#MON"),4)) sprintf (text, ("%s") , monthShortStr(month(localTime))); - else if (!strncmp_P(text,PSTR("#MMMM"),5)) sprintf (text, ("%s") , monthStr(month(localTime))); - else if (!strncmp_P(text,PSTR("#YY"),3)) sprintf (text, ("%02d") , year(localTime)%100); - else if (!strncmp_P(text,PSTR("#YYYY"),5)) sprintf_P(text, zero?PSTR("%04d") : ("%d"), year(localTime)); + size_t i = 0; + while (i < len) { + if (SEGMENT.name[i] == '#') { + char token[7]; // copy up to 6 chars + null terminator + bool zero = false; // a 0 suffix means display leading zeros + size_t j = 0; + while (j < 6 && i + j < len) { + token[j] = std::toupper(SEGMENT.name[i + j]); + if(token[j] == '0') + zero = true; // 0 suffix found. Note: there is an edge case where a '0' could be part of a trailing text and not the token, handling it is not worth the effort + j++; + } + token[j] = '\0'; + int advance = 5; // number of chars to advance in 'text' after processing the token + + // Process token + char temp[32]; + if (!strncmp_P(token,PSTR("#DATE"),5)) sprintf_P(temp, zero?PSTR("%02d.%02d.%04d"):PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); + else if (!strncmp_P(token,PSTR("#DDMM"),5)) sprintf_P(temp, zero?PSTR("%02d.%02d") :PSTR("%d.%d"), day(localTime), month(localTime)); + else if (!strncmp_P(token,PSTR("#MMDD"),5)) sprintf_P(temp, zero?PSTR("%02d/%02d") :PSTR("%d/%d"), month(localTime), day(localTime)); + else if (!strncmp_P(token,PSTR("#TIME"),5)) sprintf_P(temp, zero?PSTR("%02d:%02d%s") :PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); + else if (!strncmp_P(token,PSTR("#HHMM"),5)) sprintf_P(temp, zero?PSTR("%02d:%02d") :PSTR("%d:%02d"), AmPmHour, minute(localTime)); + else if (!strncmp_P(token,PSTR("#YYYY"),5)) sprintf_P(temp, PSTR("%04d") , year(localTime)); + else if (!strncmp_P(token,PSTR("#MONL"),5)) sprintf (temp, ("%s") , monthStr(month(localTime))); + else if (!strncmp_P(token,PSTR("#DDDD"),5)) sprintf (temp, ("%s") , dayStr(weekday(localTime))); + else if (!strncmp_P(token,PSTR("#YY"),3)) { sprintf (temp, ("%02d") , year(localTime)%100); advance = 3; } + else if (!strncmp_P(token,PSTR("#HH"),3)) { sprintf (temp, zero? ("%02d") : ("%d"), AmPmHour); advance = 3; } + else if (!strncmp_P(token,PSTR("#MM"),3)) { sprintf (temp, zero? ("%02d") : ("%d"), minute(localTime)); advance = 3; } + else if (!strncmp_P(token,PSTR("#SS"),3)) { sprintf (temp, zero? ("%02d") : ("%d"), second(localTime)); advance = 3; } + else if (!strncmp_P(token,PSTR("#MON"),4)) { sprintf (temp, ("%s") , monthShortStr(month(localTime))); advance = 4; } + else if (!strncmp_P(token,PSTR("#MO"),3)) { sprintf (temp, zero? ("%02d") : ("%d"), month(localTime)); advance = 3; } + else if (!strncmp_P(token,PSTR("#DAY"),4)) { sprintf (temp, ("%s") , dayShortStr(weekday(localTime))); advance = 4; } + else if (!strncmp_P(token,PSTR("#DD"),3)) { sprintf (temp, zero? ("%02d") : ("%d"), day(localTime)); advance = 3; } + else { temp[0] = '#'; temp[1] = '\0'; zero = false; advance = 1; } // Unknown token, just copy the # + + if(zero) advance++; // skip the '0' suffix + size_t temp_len = strlen(temp); + if (result_pos + temp_len < WLED_MAX_SEGNAME_LEN) { + strcpy(text + result_pos, temp); + result_pos += temp_len; + } + + i += advance; + } + else { + if (result_pos < WLED_MAX_SEGNAME_LEN) { + text[result_pos++] = SEGMENT.name[i++]; // no token, just copy char + } else + break; // buffer full + } + } } const int numberOfLetters = strlen(text); diff --git a/wled00/FX.h b/wled00/FX.h index 1b9bbaac3a..82213179bd 100755 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -763,7 +763,7 @@ class Segment { inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2 = CRGB::Black, int8_t rotate = 0) const { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline inline void fill_solid(CRGB c) const { fill(RGBW32(c.r,c.g,c.b,0)); } #else - inline constexpr bool is2D() const { return false; } + inline bool is2D() const { return false; } inline void setPixelColorXY(int x, int y, uint32_t c) const { setPixelColor(x, c); } inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) const { setPixelColor(int(x), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColor(x, RGBW32(r,g,b,w)); } @@ -802,7 +802,7 @@ class Segment { friend class WS2812FX; }; -// main "strip" class (104 bytes) +// main "strip" class (108 bytes) class WS2812FX { typedef uint16_t (*mode_ptr)(); // pointer to mode function typedef void (*show_callback)(); // pre show callback @@ -829,6 +829,7 @@ class WS2812FX { cctFromRgb(false), // true private variables _pixels(nullptr), + _pixelCCT(nullptr), _suspend(false), _brightness(DEFAULT_BRIGHTNESS), _length(DEFAULT_LED_COUNT), @@ -857,6 +858,7 @@ class WS2812FX { ~WS2812FX() { d_free(_pixels); + d_free(_pixelCCT); // just in case d_free(customMappingTable); _mode.clear(); _modeData.clear(); @@ -1004,6 +1006,7 @@ class WS2812FX { private: uint32_t *_pixels; + uint8_t *_pixelCCT; std::vector _segments; volatile bool _suspend; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7d5158100c..6c10482040 100755 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -678,11 +678,9 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const // check if this is a virtual strip #ifndef WLED_DISABLE_2D vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) - i &= 0xFFFF; //truncate vstrip index - if (i >= vL) return; // if pixel would still fall out of segment just exit - #else - return; #endif + i &= 0xFFFF; // truncate vstrip index. note: vStrip index is 1 even in 1D, still need to truncate + if (i >= vL) return; // if pixel would still fall out of segment just exit } #ifndef WLED_DISABLE_2D @@ -1226,11 +1224,6 @@ void WS2812FX::service() { unsigned frameDelay = FRAMETIME; if (!seg.freeze) { //only run effect function if not frozen - int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) - // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio - // when cctFromRgb is true we implicitly calculate WW and CW from RGB values - if (cctFromRgb) BusManager::setSegmentCCT(-1); - else BusManager::setSegmentCCT(seg.currentCCT(), correctWB); // Effect blending uint16_t prog = seg.progress(); seg.beginDraw(prog); // set up parameters for get/setPixelColor() (will also blend colors and palette if blend style is FADE) @@ -1251,7 +1244,6 @@ void WS2812FX::service() { Segment::modeBlend(false); // unset semaphore } if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition - BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } seg.next_time = nowUp + frameDelay; @@ -1326,6 +1318,7 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { const unsigned progress = topSegment.progress(); const unsigned progInv = 0xFFFFU - progress; uint8_t opacity = topSegment.currentBri(); // returns transitioned opacity for style FADE + uint8_t cct = topSegment.currentCCT(); Segment::setClippingRect(0, 0); // disable clipping by default @@ -1398,6 +1391,7 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { const int baseY = topSegment.startY + y; size_t indx = XY(baseX, baseY); // absolute address on strip _pixels[indx] = color_blend(_pixels[indx], blend(c, _pixels[indx]), o); + if (_pixelCCT) _pixelCCT[indx] = cct; // Apply mirroring if (topSegment.mirror || topSegment.mirror_y) { const int mirrorX = topSegment.start + width - x - 1; @@ -1408,6 +1402,11 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { if (topSegment.mirror) _pixels[idxMX] = color_blend(_pixels[idxMX], blend(c, _pixels[idxMX]), o); if (topSegment.mirror_y) _pixels[idxMY] = color_blend(_pixels[idxMY], blend(c, _pixels[idxMY]), o); if (topSegment.mirror && topSegment.mirror_y) _pixels[idxMM] = color_blend(_pixels[idxMM], blend(c, _pixels[idxMM]), o); + if (_pixelCCT) { + if (topSegment.mirror) _pixelCCT[idxMX] = cct; + if (topSegment.mirror_y) _pixelCCT[idxMY] = cct; + if (topSegment.mirror && topSegment.mirror_y) _pixelCCT[idxMM] = cct; + } } }; @@ -1479,10 +1478,12 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { indxM += topSegment.offset; // offset/phase if (indxM >= topSegment.stop) indxM -= length; // wrap _pixels[indxM] = color_blend(_pixels[indxM], blend(c, _pixels[indxM]), o); + if (_pixelCCT) _pixelCCT[indxM] = cct; } indx += topSegment.offset; // offset/phase if (indx >= topSegment.stop) indx -= length; // wrap _pixels[indx] = color_blend(_pixels[indx], blend(c, _pixels[indx]), o); + if (_pixelCCT) _pixelCCT[indx] = cct; }; // if we blend using "push" style we need to "shift" canvas to left/right/ @@ -1590,6 +1591,13 @@ void WS2812FX::show() { size_t diff = showNow - _lastShow; size_t totalLen = getLengthTotal(); + // WARNING: as WLED doesn't handle CCT on pixel level but on Segment level instead + // we need to keep track of each pixel's CCT when blending segments (if CCT is present) + // and then set appropriate CCT from that pixel during paint (see below). + if ((hasCCTBus() || correctWB) && !cctFromRgb) + _pixelCCT = static_cast(d_malloc(totalLen * sizeof(uint8_t))); // allocate CCT buffer if necessary + if (_pixelCCT) memset(_pixelCCT, 127, totalLen); // set neutral (50:50) CCT + if (realtimeMode == REALTIME_MODE_INACTIVE || useMainSegmentOnly || realtimeOverride > REALTIME_OVERRIDE_NONE) { // clear frame buffer for (size_t i = 0; i < totalLen; i++) _pixels[i] = BLACK; // memset(_pixels, 0, sizeof(uint32_t) * getLengthTotal()); @@ -1607,8 +1615,22 @@ void WS2812FX::show() { uint8_t newBri = estimateCurrentAndLimitBri(_brightness, _pixels); if (newBri != _brightness) BusManager::setBrightness(newBri); - // paint actuall pixels - for (size_t i = 0; i < totalLen; i++) BusManager::setPixelColor(getMappedPixelIndex(i), realtimeMode && arlsDisableGammaCorrection ? _pixels[i] : gamma32(_pixels[i])); + // paint actual pixels + int oldCCT = Bus::getCCT(); // store original CCT value (since it is global) + // when cctFromRgb is true we implicitly calculate WW and CW from RGB values (cct==-1) + if (cctFromRgb) BusManager::setSegmentCCT(-1); + for (size_t i = 0; i < totalLen; i++) { + // when correctWB is true setSegmentCCT() will convert CCT into K with which we can then + // correct/adjust RGB value according to desired CCT value, it will still affect actual WW/CW ratio + if (_pixelCCT) { // cctFromRgb already exluded at allocation + if (i == 0 || _pixelCCT[i-1] != _pixelCCT[i]) BusManager::setSegmentCCT(_pixelCCT[i], correctWB); + } + BusManager::setPixelColor(getMappedPixelIndex(i), realtimeMode && arlsDisableGammaCorrection ? _pixels[i] : gamma32(_pixels[i])); + } + Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments + + d_free(_pixelCCT); + _pixelCCT = nullptr; // some buses send asynchronously and this method will return before // all of the data has been sent. diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 56e5947959..6dfbd88fac 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -25,9 +25,38 @@ #include "bus_wrapper.h" #include +// functions to get/set bits in an array - based on functions created by Brandon for GOL +// toDo : make this a class that's completely defined in a header file +bool getBitFromArray(const uint8_t* byteArray, size_t position) { // get bit value + size_t byteIndex = position / 8; + unsigned bitIndex = position % 8; + uint8_t byteValue = byteArray[byteIndex]; + return (byteValue >> bitIndex) & 1; +} extern bool cctICused; extern bool useParallelI2S; +void setBitInArray(uint8_t* byteArray, size_t position, bool value) { // set bit - with error handling for nullptr + //if (byteArray == nullptr) return; + size_t byteIndex = position / 8; + unsigned bitIndex = position % 8; + if (value) + byteArray[byteIndex] |= (1 << bitIndex); + else + byteArray[byteIndex] &= ~(1 << bitIndex); +} + +size_t getBitArrayBytes(size_t num_bits) { // number of bytes needed for an array with num_bits bits + return (num_bits + 7) / 8; +} + +void setBitArray(uint8_t* byteArray, size_t numBits, bool value) { // set all bits to same value + if (byteArray == nullptr) return; + size_t len = getBitArrayBytes(numBits); + if (value) memset(byteArray, 0xFF, len); + else memset(byteArray, 0x00, len); +} + //colors.cpp uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); @@ -545,7 +574,7 @@ void BusPwm::show() { unsigned duty = (_data[i] * pwmBri) / 255; unsigned deadTime = 0; - if (_type == TYPE_ANALOG_2CH && Bus::getCCTBlend() == 0) { + if (_type == TYPE_ANALOG_2CH && Bus::_cctBlend == 0) { // add dead time between signals (when using dithering, two full 8bit pulses are required) deadTime = (1+dithering) << bitShift; // we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap @@ -745,6 +774,350 @@ void BusNetwork::cleanup() { _valid = false; } +// *************************************************************************** + +#ifdef WLED_ENABLE_HUB75MATRIX +#warning "HUB75 driver enabled (experimental)" +#ifdef ESP8266 +#error ESP8266 does not support HUB75 +#endif + +BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { + size_t lastHeap = ESP.getFreeHeap(); + _valid = false; + _hasRgb = true; + _hasWhite = false; + + mxconfig.double_buff = false; // Use our own memory-optimised buffer rather than the driver's own double-buffer + + // 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 + + // mxconfig.latch_blanking = 3; + // mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // experimental - 5MHZ should be enugh, but colours looks slightly better at 10MHz + //mxconfig.min_refresh_rate = 90; + //mxconfig.min_refresh_rate = 120; + mxconfig.clkphase = bc.reversed; + + virtualDisp = nullptr; + + if (bc.type == TYPE_HUB75MATRIX_HS) { + mxconfig.mx_width = min((u_int8_t) 64, bc.pins[0]); + mxconfig.mx_height = min((u_int8_t) 64, bc.pins[1]); + if(bc.pins[2] > 1 && bc.pins[3] > 0 && bc.pins[4]) { + virtualDisp = new VirtualMatrixPanel((*display), bc.pins[3], bc.pins[4], mxconfig.mx_width, mxconfig.mx_height, CHAIN_BOTTOM_LEFT_UP); + } + } else if (bc.type == TYPE_HUB75MATRIX_QS) { + mxconfig.mx_width = min((u_int8_t) 64, bc.pins[0]) * 2; + mxconfig.mx_height = min((u_int8_t) 64, bc.pins[1]) / 2; + virtualDisp = new VirtualMatrixPanel((*display), 1, 1, bc.pins[0], bc.pins[1]); + virtualDisp->setRotation(0); + switch(bc.pins[1]) { + case 16: + virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_16PX_HIGH); + break; + case 32: + virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH); + break; + case 64: + virtualDisp->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH); + break; + default: + DEBUGBUS_PRINTLN("Unsupported height"); + return; + } + } else { + DEBUGBUS_PRINTLN("Unknown type"); + return; + } + +#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2)// classic esp32, or esp32-s2: reduce bitdepth for large panels + if (mxconfig.mx_height >= 64) { + if (mxconfig.chain_length * mxconfig.mx_width > 192) mxconfig.setPixelColorDepthBits(3); + else if (mxconfig.chain_length * mxconfig.mx_width > 64) mxconfig.setPixelColorDepthBits(4); + else mxconfig.setPixelColorDepthBits(8); + } else mxconfig.setPixelColorDepthBits(8); +#endif + + mxconfig.chain_length = max((u_int8_t) 1, min(bc.pins[2], (u_int8_t) 4)); // prevent bad data preventing boot due to low memory + + if(mxconfig.mx_height >= 64 && (mxconfig.chain_length > 1)) { + DEBUGBUS_PRINTLN("WARNING, only single panel can be used of 64 pixel boards due to memory"); + mxconfig.chain_length = 1; + } + + +// 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}; + +#if defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 + + // https://www.adafruit.com/product/5778 + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - Matrix Portal S3 config"); + mxconfig.gpio = { 42, 41, 40, 38, 39, 37, 45, 36, 48, 35, 21, 47, 14, 2 }; + +#elif defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)// ESP32-S3 with PSRAM + +#if defined(MOONHUB_S3_PINOUT) + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - T7 S3 with PSRAM, MOONHUB 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 = { 1, 5, 6, 7, 13, 9, 16, 48, 47, 21, 38, 8, 4, 18 }; + +#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}; + mxconfig.gpio = {1, 2, 42, 41, 40, 39, 45, 48, 47, 21, 38, 8, 3, 18}; +#endif +#elif defined(ESP32_FORUM_PINOUT) // Common format for boards designed for SmartMatrix + + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - ESP32_FORUM_PINOUT"); +/* + ESP32 with SmartMatrix's default pinout - ESP32_FORUM_PINOUT + https://github.com/pixelmatix/SmartMatrix/blob/teensylc/src/MatrixHardware_ESP32_V0.h + Can use a board like https://github.com/rorosaurus/esp32-hub75-driver +*/ + + mxconfig.gpio = { 2, 15, 4, 16, 27, 17, 5, 18, 19, 21, 12, 26, 25, 22 }; + +#else + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - Default pins"); + /* + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA?tab=readme-ov-file + + Boards + + https://esp32trinity.com/ + https://www.electrodragon.com/product/rgb-matrix-panel-drive-interface-board-for-esp32-dma/ + + */ + mxconfig.gpio = { 25, 26, 27, 14, 12, 13, 23, 19, 5, 17, 18, 4, 15, 16 }; + +#endif + + int8_t pins[PIN_COUNT]; + memcpy(pins, &mxconfig.gpio, sizeof(mxconfig.gpio)); + if (!PinManager::allocateMultiplePins(pins, PIN_COUNT, PinOwner::HUB75, true)) { + DEBUGBUS_PRINTLN("Failed to allocate pins for HUB75"); + return; + } + + if(bc.colorOrder == COL_ORDER_RGB) { + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA = Default color order (RGB)"); + } else if(bc.colorOrder == COL_ORDER_BGR) { + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA = color order BGR"); + int8_t tmpPin; + tmpPin = mxconfig.gpio.r1; + mxconfig.gpio.r1 = mxconfig.gpio.b1; + mxconfig.gpio.b1 = tmpPin; + tmpPin = mxconfig.gpio.r2; + mxconfig.gpio.r2 = mxconfig.gpio.b2; + mxconfig.gpio.b2 = tmpPin; + } + else { + DEBUGBUS_PRINTF("MatrixPanel_I2S_DMA = unsupported color order %u\n", bc.colorOrder); + } + + DEBUGBUS_PRINTF("MatrixPanel_I2S_DMA config - %ux%u length: %u\n", mxconfig.mx_width, mxconfig.mx_height, mxconfig.chain_length); + DEBUGBUS_PRINTF("R1_PIN=%u, G1_PIN=%u, B1_PIN=%u, R2_PIN=%u, G2_PIN=%u, B2_PIN=%u, A_PIN=%u, B_PIN=%u, C_PIN=%u, D_PIN=%u, E_PIN=%u, LAT_PIN=%u, OE_PIN=%u, CLK_PIN=%u\n", + mxconfig.gpio.r1, mxconfig.gpio.g1, mxconfig.gpio.b1, mxconfig.gpio.r2, mxconfig.gpio.g2, mxconfig.gpio.b2, + mxconfig.gpio.a, mxconfig.gpio.b, mxconfig.gpio.c, mxconfig.gpio.d, mxconfig.gpio.e, mxconfig.gpio.lat, mxconfig.gpio.oe, mxconfig.gpio.clk); + + // OK, now we can create our matrix object + display = new MatrixPanel_I2S_DMA(mxconfig); + if (display == nullptr) { + DEBUGBUS_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! driver allocation failed ***********"); + DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap()); + return; + } + + this->_len = (display->width() * display->height()); + DEBUGBUS_PRINTF("Length: %u\n", _len); + if(this->_len >= MAX_LEDS) { + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA Too many LEDS - playing safe"); + return; + } + + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA created"); + // let's adjust default brightness + display->setBrightness8(25); // range is 0-255, 0 - 0%, 255 - 100% + + delay(24); // experimental + DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap()); + // Allocate memory and start DMA display + if( not display->begin() ) { + DEBUGBUS_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********"); + DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap()); + return; + } + else { + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA begin ok"); + DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap()); + delay(18); // experiment - give the driver a moment (~ one full frame @ 60hz) to settle + _valid = true; + display->clearScreen(); // initially clear the screen buffer + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA clear ok"); + + if (_ledBuffer) free(_ledBuffer); // should not happen + if (_ledsDirty) free(_ledsDirty); // should not happen + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA allocate memory"); + _ledsDirty = (byte*) malloc(getBitArrayBytes(_len)); // create LEDs dirty bits + DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA allocate memory ok"); + + if (_ledsDirty == nullptr) { + display->stopDMAoutput(); + delete display; display = nullptr; + _valid = false; + DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA not started - not enough memory for dirty bits!")); + DEBUGBUS_PRINT(F("heap usage: ")); DEBUGBUS_PRINTLN(lastHeap - ESP.getFreeHeap()); + return; // fail is we cannot get memory for the buffer + } + setBitArray(_ledsDirty, _len, false); // reset dirty bits + + if (mxconfig.double_buff == false) { + _ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) + } + } + + + if (_valid) { + _panelWidth = virtualDisp ? virtualDisp->width() : display->width(); // cache width - it will never change + } + + DEBUGBUS_PRINT(F("MatrixPanel_I2S_DMA ")); + DEBUGBUS_PRINTF("%sstarted, width=%u, %u pixels.\n", _valid? "":"not ", _panelWidth, _len); + + if (mxconfig.double_buff == true) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA driver native double-buffering enabled.")); + if (_ledBuffer != nullptr) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA LEDS buffer enabled.")); + if (_ledsDirty != nullptr) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA LEDS dirty bit optimization enabled.")); + if ((_ledBuffer != nullptr) || (_ledsDirty != nullptr)) { + DEBUGBUS_PRINT(F("MatrixPanel_I2S_DMA LEDS buffer uses ")); + DEBUGBUS_PRINT((_ledBuffer? _len*sizeof(CRGB) :0) + (_ledsDirty? getBitArrayBytes(_len) :0)); + DEBUGBUS_PRINTLN(F(" bytes.")); + } +} + +void __attribute__((hot)) BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c) { + if (!_valid || pix >= _len) return; + // if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + + if (_ledBuffer) { + CRGB fastled_col = CRGB(c); + if (_ledBuffer[pix] != fastled_col) { + _ledBuffer[pix] = fastled_col; + setBitInArray(_ledsDirty, pix, true); // flag pixel as "dirty" + } + } + else { + if ((c == IS_BLACK) && (getBitFromArray(_ledsDirty, pix) == false)) return; // ignore black if pixel is already black + setBitInArray(_ledsDirty, pix, c != IS_BLACK); // dirty = true means "color is not BLACK" + + #ifndef NO_CIE1931 + c = unGamma24(c); // to use the driver linear brightness feature, we first need to undo WLED gamma correction + #endif + uint8_t r = R(c); + uint8_t g = G(c); + uint8_t b = B(c); + + if(virtualDisp != nullptr) { + int x = pix % _panelWidth; + int y = pix / _panelWidth; + virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + } else { + int x = pix % _panelWidth; + int y = pix / _panelWidth; + display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + } + } +} + +uint32_t BusHub75Matrix::getPixelColor(unsigned pix) const { + if (!_valid || pix >= _len) return IS_BLACK; + if (_ledBuffer) + return uint32_t(_ledBuffer[pix].scale8(_bri)) & 0x00FFFFFF; // scale8() is needed to mimic NeoPixelBus, which returns scaled-down colours + else + return getBitFromArray(_ledsDirty, pix) ? IS_DARKGREY: IS_BLACK; // just a hack - we only know if the pixel is black or not +} + +void BusHub75Matrix::setBrightness(uint8_t b) { + _bri = b; + display->setBrightness(_bri); +} + +void BusHub75Matrix::show(void) { + if (!_valid) return; + display->setBrightness(_bri); + + if (_ledBuffer) { + // write out buffered LEDs + bool isVirtualDisp = (virtualDisp != nullptr); + unsigned height = isVirtualDisp ? virtualDisp->height() : display->height(); + unsigned width = _panelWidth; + + //while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker. + + size_t pix = 0; // running pixel index + for (int y=0; ydrawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + else display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b); + } + pix ++; + } + setBitArray(_ledsDirty, _len, false); // buffer shown - reset all dirty bits + } + + if(mxconfig.double_buff) { + display->flipDMABuffer(); // Show the back buffer, set current output buffer to the back (i.e. no longer being sent to LED panels) + // while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker. + display->clearScreen(); // Now clear the back-buffer + setBitArray(_ledsDirty, _len, false); // dislay buffer is blank - reset all dirty bits + } +} + +void BusHub75Matrix::cleanup() { + if (display && _valid) display->stopDMAoutput(); // terminate DMA driver (display goes black) + _valid = false; + _panelWidth = 0; + deallocatePins(); + DEBUGBUS_PRINTLN("HUB75 output ended."); + + //if (virtualDisp != nullptr) delete virtualDisp; // warning: deleting object of polymorphic class type 'VirtualMatrixPanel' which has non-virtual destructor might cause undefined behavior + delete display; + display = nullptr; + virtualDisp = nullptr; + if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr; + if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr; +} + +void BusHub75Matrix::deallocatePins() { + uint8_t pins[PIN_COUNT]; + memcpy(pins, &mxconfig.gpio, sizeof(mxconfig.gpio)); + PinManager::deallocateMultiplePins(pins, PIN_COUNT, PinOwner::HUB75); +} + +std::vector BusHub75Matrix::getLEDTypes() { + return { + {TYPE_HUB75MATRIX_HS, "H", PSTR("HUB75 (Half Scan)")}, + {TYPE_HUB75MATRIX_QS, "H", PSTR("HUB75 (Quarter Scan)")}, + }; +} + +size_t BusHub75Matrix::getPins(uint8_t* pinArray) const { + pinArray[0] = mxconfig.mx_width; + pinArray[1] = mxconfig.mx_height; + pinArray[2] = mxconfig.chain_length; + return 3; +} + +#endif +// *************************************************************************** //utility to get the approx. memory usage of a given BusConfig size_t BusConfig::memUsage(unsigned nr) const { @@ -801,6 +1174,10 @@ int BusManager::add(const BusConfig &bc) { if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) return -1; if (Bus::isVirtual(bc.type)) { busses.push_back(make_unique(bc)); +#ifdef WLED_ENABLE_HUB75MATRIX + } else if (Bus::isHub75(bc.type)) { + busses.push_back(make_unique(bc)); +#endif } else if (Bus::isDigital(bc.type)) { busses.push_back(make_unique(bc, Bus::is2Pin(bc.type) ? twoPin : digital)); } else if (Bus::isOnOff(bc.type)) { @@ -832,6 +1209,10 @@ String BusManager::getLEDTypesJSONString() { json += LEDTypesToJson(BusPwm::getLEDTypes()); json += LEDTypesToJson(BusNetwork::getLEDTypes()); //json += LEDTypesToJson(BusVirtual::getLEDTypes()); + #ifdef WLED_ENABLE_HUB75MATRIX + json += LEDTypesToJson(BusHub75Matrix::getLEDTypes()); + #endif + json.setCharAt(json.length()-1, ']'); // replace last comma with bracket return json; } @@ -972,7 +1353,7 @@ bool PolyBus::_useParallelI2S = false; // Bus static member definition int16_t Bus::_cct = -1; -uint8_t Bus::_cctBlend = 0; +uint8_t Bus::_cctBlend = 0; // 0 - 127 uint8_t Bus::_gAWM = 255; uint16_t BusDigital::_milliAmpsTotal = 0; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 064b600a66..937bf2636e 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -2,6 +2,13 @@ #ifndef BusManager_h #define BusManager_h +#ifdef WLED_ENABLE_HUB75MATRIX + +#include +#include +#include + +#endif /* * Class for addressing various light types */ @@ -181,6 +188,7 @@ class Bus { static constexpr bool isOnOff(uint8_t type) { return (type == TYPE_ONOFF); } static constexpr bool isPWM(uint8_t type) { return (type >= TYPE_ANALOG_MIN && type <= TYPE_ANALOG_MAX); } static constexpr bool isVirtual(uint8_t type) { return (type >= TYPE_VIRTUAL_MIN && type <= TYPE_VIRTUAL_MAX); } + static constexpr bool isHub75(uint8_t type) { return (type >= TYPE_HUB75MATRIX_MIN && type <= TYPE_HUB75MATRIX_MAX); } static constexpr bool is16bit(uint8_t type) { return type == TYPE_UCS8903 || type == TYPE_UCS8904 || type == TYPE_SM16825; } static constexpr bool mustRefresh(uint8_t type) { return type == TYPE_TM1814; } static constexpr int numPWMPins(uint8_t type) { return (type - 40); } @@ -189,9 +197,9 @@ class Bus { static inline void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } static inline uint8_t getGlobalAWMode() { return _gAWM; } static inline void setCCT(int16_t cct) { _cct = cct; } - static inline uint8_t getCCTBlend() { return _cctBlend; } - static inline void setCCTBlend(uint8_t b) { - _cctBlend = (std::min((int)b,100) * 127) / 100; + static inline uint8_t getCCTBlend() { return (_cctBlend * 100 + 64) / 127; } // returns 0-100, 100% = 127. +64 for rounding + static inline void setCCTBlend(uint8_t b) { // input is 0-100 + _cctBlend = (std::min((int)b,100) * 127 + 50) / 100; // +50 for rounding, b=100% -> 127 //compile-time limiter for hardware that can't power both white channels at max #ifdef WLED_MAX_CCT_BLEND if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; @@ -353,6 +361,37 @@ class BusNetwork : public Bus { uint8_t *_data; }; +#ifdef WLED_ENABLE_HUB75MATRIX +class BusHub75Matrix : public Bus { + public: + BusHub75Matrix(const BusConfig &bc); + [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override; + [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override; + void show() override; + void setBrightness(uint8_t b) override; + size_t getPins(uint8_t* pinArray = nullptr) const override; + void deallocatePins(); + void cleanup(); + + ~BusHub75Matrix() { + cleanup(); + } + + static std::vector getLEDTypes(void); + + private: + MatrixPanel_I2S_DMA *display = nullptr; + VirtualMatrixPanel *virtualDisp = nullptr; + HUB75_I2S_CFG mxconfig; + unsigned _panelWidth = 0; + CRGB *_ledBuffer = nullptr; + byte *_ledsDirty = nullptr; + // workaround for missing constants on include path for non-MM + uint32_t IS_BLACK = 0x000000; + uint32_t IS_DARKGREY = 0x333333; + const int PIN_COUNT = 14; +}; +#endif //temporary struct for passing bus configuration to bus struct BusConfig { @@ -364,7 +403,7 @@ struct BusConfig { uint8_t skipAmount; bool refreshReq; uint8_t autoWhite; - uint8_t pins[5] = {255, 255, 255, 255, 255}; + uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255}; uint16_t frequency; uint8_t milliAmpsPerLed; uint16_t milliAmpsMax; @@ -382,7 +421,7 @@ struct BusConfig { { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) - size_t nPins = Bus::getNumberOfPins(type); + size_t nPins = OUTPUT_MAX_PINS; for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; DEBUGBUS_PRINTF_P(PSTR("Bus: Config (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"), (int)start, (int)(start+len), diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 577aaeb826..74f7c28e1b 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -862,12 +862,12 @@ class PolyBus { // I2S1 bus or paralell buses #ifndef CONFIG_IDF_TARGET_ESP32C3 case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, col); else (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_32_I2_400_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, col); else (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; - case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); else (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; case I_32_I2_2805_5: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; @@ -1423,7 +1423,7 @@ class PolyBus { return I_8266_U0_SM16825_5 + offset; } #else //ESP32 - uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S0 (used by Audioreactive), 2 = I2S1 + uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S1 [I2S0 is used by Audioreactive] #if defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32-S2 only has 4 RMT channels if (_useParallelI2S) { diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 6d5698f426..9e34694f8c 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -207,7 +207,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { int s = 0; // bus iterator for (JsonObject elm : ins) { if (s >= WLED_MAX_BUSSES) break; // only counts physical buses - uint8_t pins[5] = {255, 255, 255, 255, 255}; + uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255}; JsonArray pinArr = elm["pin"]; if (pinArr.size() == 0) continue; //pins[0] = pinArr[0]; @@ -740,6 +740,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(aOtaEnabled, ota[F("aota")]); #endif getStringFromJson(otaPass, pwd, 33); //normally not present due to security + CJSON(otaSameSubnet, ota[F("same-subnet")]); } #ifdef WLED_ENABLE_DMX @@ -774,7 +775,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { static const char s_cfg_json[] PROGMEM = "/cfg.json"; -void deserializeConfigFromFS() { +bool deserializeConfigFromFS() { [[maybe_unused]] bool success = deserializeConfigSec(); #ifdef WLED_ADD_EEPROM_SUPPORT if (!success) { //if file does not exist, try reading from EEPROM @@ -782,7 +783,7 @@ void deserializeConfigFromFS() { } #endif - if (!requestJSONBufferLock(1)) return; + if (!requestJSONBufferLock(1)) return false; DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); @@ -794,7 +795,7 @@ void deserializeConfigFromFS() { bool needsSave = deserializeConfig(root, true); releaseJSONBufferLock(); - if (needsSave) serializeConfigToFS(); // usermods required new parameters + return needsSave; } void serializeConfigToFS() { @@ -1218,6 +1219,7 @@ void serializeConfig(JsonObject root) { #ifndef WLED_DISABLE_OTA ota[F("aota")] = aOtaEnabled; #endif + ota[F("same-subnet")] = otaSameSubnet; #ifdef WLED_ENABLE_DMX JsonObject dmx = root.createNestedObject("dmx"); diff --git a/wled00/const.h b/wled00/const.h index c19843c422..cca6d4630b 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -316,6 +316,12 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define TYPE_P9813 53 #define TYPE_LPD6803 54 #define TYPE_2PIN_MAX 63 + +#define TYPE_HUB75MATRIX_MIN 64 +#define TYPE_HUB75MATRIX_HS 65 +#define TYPE_HUB75MATRIX_QS 66 +#define TYPE_HUB75MATRIX_MAX 71 + //Network types (master broadcast) (80-95) #define TYPE_VIRTUAL_MIN 80 #define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 66bc2f000b..a01cfc8d49 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -18,6 +18,7 @@ function isD2P(t) { return gT(t).t === "2P"; } // is digital 2 pin type function isNet(t) { return gT(t).t === "N"; } // is network type function isVir(t) { return gT(t).t === "V" || isNet(t); } // is virtual type + function isHub75(t){ return gT(t).t === "H"; } // is HUB75 type function hasRGB(t) { return !!(gT(t).c & 0x01); } // has RGB function hasW(t) { return !!(gT(t).c & 0x02); } // has white channel function hasCCT(t) { return !!(gT(t).c & 0x04); } // is white CCT enabled @@ -60,6 +61,9 @@ let nm = LC.name.substring(0,2); let n = LC.name.substring(2); let t = parseInt(d.Sf["LT"+n].value, 10); // LED type SELECT + if(isHub75(t)) { + return; + } // ignore IP address if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") { if (isNet(t)) return; @@ -234,12 +238,15 @@ case 'V': // virtual/non-GPIO based p0d = "Config:" break; + case 'H': // HUB75 + p0d = "Panel size (width x height), Panel count:" + break; } gId("p0d"+n).innerText = p0d; gId("p1d"+n).innerText = p1d; gId("off"+n).innerText = off; // secondary pins show/hide (type string length is equivalent to number of pins used; except for network and on/off) - let pins = Math.max(gT(t).t.length,1) + 3*isNet(t); // fixes network pins to 4 + let pins = Math.max(gT(t).t.length,1) + 3*isNet(t) + 2*isHub75(t); // fixes network pins to 4 for (let p=1; p<5; p++) { var LK = d.Sf["L"+p+n]; if (!LK) continue; @@ -269,13 +276,13 @@ } gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory gRGBW |= hasW(t); // RGBW checkbox - gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM + gId("co"+n).style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide color order for PWM gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping - gId("dig"+n+"c").style.display = (isAna(t)) ? "none":"inline"; // hide count for analog + gId("dig"+n+"c").style.display = (isAna(t) || isHub75(t)) ? "none":"inline"; // hide count for analog gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual - gId("dig"+n+"s").style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide skip 1st for virtual & analog + gId("dig"+n+"s").style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide skip 1st for virtual & analog gId("dig"+n+"f").style.display = (isDig(t) || (isPWM(t) && maxL>2048)) ? "inline":"none"; // hide refresh (PWM hijacks reffresh for dithering on ESP32) gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off) @@ -315,7 +322,13 @@ } // do we have led pins for digital leds if (nm=="L0" || nm=="L1") { - d.Sf["LC"+n].max = maxPB; // update max led count value + if (!isHub75(t)) { + d.Sf["LC"+n].max = maxPB; // update max led count value + } + else { + d.Sf["LC"+n].min = undefined; + d.Sf["LC"+n].max = undefined; + } } // ignore IP address (stored in pins for virtual busses) if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") { @@ -329,6 +342,20 @@ LC.min = -1; } } + if (isHub75(t) && (nm=="L0" || nm=="L1")) { + // Matrix width and height + LC.max = 128; + LC.min = 16; + LC.style.color="#fff"; + return; // do not check conflicts + } + else if (isHub75(t) && nm=="L2") { + // Chain length aka Panel Count + LC.max = 4; + LC.min = 1; + LC.style.color="#fff"; + return; // do not check conflicts + } // check for pin conflicts & color fields if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4") if (LC.value!="" && LC.value!="-1") { diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index 2db798cf4d..7f46270495 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -57,6 +57,9 @@

Security & Update setup

Software Update


Enable ArduinoOTA:
+ Only allow update from same network/WiFi:
+ ⚠ If you are using multiple VLANs (i.e. IoT or guest network) either set PIN or disable this option.
+ Disabling this option will make your device less secure.


Backup & Restore

⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.
diff --git a/wled00/data/update.htm b/wled00/data/update.htm index 96ba821e87..8b39b1ccef 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -3,9 +3,20 @@ WLED Update +