From 7d735cd4cfb9d289f23cfb124480f416642c6e74 Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Tue, 3 Mar 2026 00:47:52 +0100 Subject: [PATCH 01/16] Fix RGBW management for FastLED (ESP32-C2,C3,C5,C6,S3) --- .github/workflows/build.yml | 2 +- include/leds.h | 4 +- readme.md | 2 +- src/leds.cpp | 135 +++++++++++++++++++++--------------- src/manager.cpp | 4 +- src/udp_receiver.cpp | 36 +++------- version | 2 +- 7 files changed, 96 insertions(+), 89 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c89b737..ef4a73d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: include: - group: ModernESP_C2 boards: "esp32c2" - cache_extra: ".pio" + cache_extra: "" - group: ModernESP_C3_C5_C6 boards: "esp32c3 esp32c5 esp32c6" cache_extra: "" diff --git a/include/leds.h b/include/leds.h index da9c17f..ed8804b 100644 --- a/include/leds.h +++ b/include/leds.h @@ -7,9 +7,9 @@ namespace Leds { void applyLedConfig(); int getLedsNumber(); - void checkDeleyedRender(); + void checkDelayedRender(); void renderLed(bool isNewFrame); - void synchronizeLedsToVolatileStateBeforeDeleyedRender(); + void synchronizeLedsToVolatileStateBeforeDelayedRender(); template void setLed(int index, uint8_t r, uint8_t g, uint8_t b); diff --git a/readme.md b/readme.md index e2d93e8..69b6b20 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # Hyperk -Hyperk is a minimalist, high-performance uni-platform WiFi LED driver for ESP8266, ESP32 (S2, S3, C3, C6), Raspberry Pi Pico W (RP2040, RP2350). Designed as a lightweight and streamlined component that avoids unnecessary complexity, it delivers low‑latency performance and integrates smoothly with platforms such as HyperHDR, while offering essential home‑automation capabilities through a clean, modern codebase. +Hyperk is a minimalist, high-performance uni-platform WiFi LED driver for ESP8266, ESP32 (S2, S3, C2, C3, C5, C6), Raspberry Pi Pico W (RP2040, RP2350). Designed as a lightweight and streamlined component that avoids unnecessary complexity, it delivers low‑latency performance and integrates smoothly with platforms such as HyperHDR, while offering essential home‑automation capabilities through a clean, modern codebase. ## Installation diff --git a/src/leds.cpp b/src/leds.cpp index 491b3d0..aea23c1 100644 --- a/src/leds.cpp +++ b/src/leds.cpp @@ -87,6 +87,7 @@ namespace Leds{ volatile bool delayedRender = false; + uint16_t briPlus = 256; int getLedsNumber() { @@ -140,7 +141,7 @@ namespace Leds{ return true; } - void synchronizeLedsToVolatileStateBeforeDeleyedRender() + void synchronizeLedsToVolatileStateBeforeDelayedRender() { if (delayedRender || !canRender()) return; @@ -150,9 +151,7 @@ namespace Leds{ { updated = true; Log::debug("Updating brightness to: ", Volatile::state.brightness); - #ifdef USE_FASTLED - FastLED.setBrightness(Volatile::state.brightness); - #endif + briPlus = Volatile::state.brightness + 1; } if (Volatile::clearUpdatedPowerOnState()) @@ -174,7 +173,13 @@ namespace Leds{ auto b = (Volatile::state.on) ? Volatile::state.staticColor.blue : 0; #ifdef USE_FASTLED - fill_solid(leds, getLedsNumber(), CRGB(r, g, b)); + for(int i = 0; i < getLedsNumber(); i++) { + if (Volatile::state.brightness != 255) + setLed(i, r, g, b); + else + setLed(i, r, g, b); + } + FastLED.show(); #else if (dotstar == nullptr && neopixel == nullptr && neopixelRgbw == nullptr) @@ -209,6 +214,9 @@ namespace Leds{ #ifdef USE_FASTLED if (FastLED.count()) { + if (cfgLedType == LedType::SK6812) { + setParamsAndPrepareCalibration(calGain, calRed, calGreen, calBlue); + } return; } @@ -243,10 +251,9 @@ namespace Leds{ if (fastLedsType == LedType::SK6812) { + setParamsAndPrepareCalibration(calGain, calRed, calGreen, calBlue); virtualLedsNumber = (fastLedsNumber * 4 + 2) / 3; - const size_t bytesToAllocate = virtualLedsNumber * 3; - leds = reinterpret_cast(new uint8_t[bytesToAllocate]); - memset(leds, 0, bytesToAllocate); + leds = new CRGB[virtualLedsNumber]; } else { @@ -257,42 +264,42 @@ namespace Leds{ { switch (cfgLedDataPin) { #if !defined(CONFIG_IDF_TARGET_ESP32S3) - case 0: FastLED.addLeds(leds, virtualLedsNumber); break; + case 0: FastLED.addLeds(leds, virtualLedsNumber); break; #endif - case 1: FastLED.addLeds(leds, virtualLedsNumber); break; - case 2: FastLED.addLeds(leds, virtualLedsNumber); break; + case 1: FastLED.addLeds(leds, virtualLedsNumber); break; + case 2: FastLED.addLeds(leds, virtualLedsNumber); break; #if !defined(CONFIG_IDF_TARGET_ESP32S3) - case 3: FastLED.addLeds(leds, virtualLedsNumber); break; + case 3: FastLED.addLeds(leds, virtualLedsNumber); break; #endif - case 4: FastLED.addLeds(leds, virtualLedsNumber); break; - case 5: FastLED.addLeds(leds, virtualLedsNumber); break; - case 6: FastLED.addLeds(leds, virtualLedsNumber); break; - case 7: FastLED.addLeds(leds, virtualLedsNumber); break; + case 4: FastLED.addLeds(leds, virtualLedsNumber); break; + case 5: FastLED.addLeds(leds, virtualLedsNumber); break; + case 6: FastLED.addLeds(leds, virtualLedsNumber); break; + case 7: FastLED.addLeds(leds, virtualLedsNumber); break; #if !defined(CONFIG_IDF_TARGET_ESP32C2) - case 8: FastLED.addLeds(leds, virtualLedsNumber); break; + case 8: FastLED.addLeds(leds, virtualLedsNumber); break; #endif - case 10: FastLED.addLeds(leds, virtualLedsNumber); break; + case 10: FastLED.addLeds(leds, virtualLedsNumber); break; #if defined(CONFIG_IDF_TARGET_ESP32C6) - case 15: FastLED.addLeds(leds, virtualLedsNumber); break; - case 18: FastLED.addLeds(leds, virtualLedsNumber); break; - case 19: FastLED.addLeds(leds, virtualLedsNumber); break; - case 20: FastLED.addLeds(leds, virtualLedsNumber); break; - case 21: FastLED.addLeds(leds, virtualLedsNumber); break; - case 22: FastLED.addLeds(leds, virtualLedsNumber); break; + case 15: FastLED.addLeds(leds, virtualLedsNumber); break; + case 18: FastLED.addLeds(leds, virtualLedsNumber); break; + case 19: FastLED.addLeds(leds, virtualLedsNumber); break; + case 20: FastLED.addLeds(leds, virtualLedsNumber); break; + case 21: FastLED.addLeds(leds, virtualLedsNumber); break; + case 22: FastLED.addLeds(leds, virtualLedsNumber); break; #elif defined(CONFIG_IDF_TARGET_ESP32S3) - case 16: FastLED.addLeds(leds, virtualLedsNumber); break; - case 17: FastLED.addLeds(leds, virtualLedsNumber); break; - case 18: FastLED.addLeds(leds, virtualLedsNumber); break; - case 48: FastLED.addLeds(leds, virtualLedsNumber); break; + case 16: FastLED.addLeds(leds, virtualLedsNumber); break; + case 17: FastLED.addLeds(leds, virtualLedsNumber); break; + case 18: FastLED.addLeds(leds, virtualLedsNumber); break; + case 48: FastLED.addLeds(leds, virtualLedsNumber); break; #elif defined(CONFIG_IDF_TARGET_ESP32C3) - case 20: FastLED.addLeds(leds, virtualLedsNumber); break; - case 21: FastLED.addLeds(leds, virtualLedsNumber); break; + case 20: FastLED.addLeds(leds, virtualLedsNumber); break; + case 21: FastLED.addLeds(leds, virtualLedsNumber); break; #elif defined(CONFIG_IDF_TARGET_ESP32C5) - case 11: FastLED.addLeds(leds, virtualLedsNumber); break; - case 27: FastLED.addLeds(leds, virtualLedsNumber); break; + case 11: FastLED.addLeds(leds, virtualLedsNumber); break; + case 27: FastLED.addLeds(leds, virtualLedsNumber); break; #endif default: - FastLED.addLeds(leds, virtualLedsNumber); + FastLED.addLeds(leds, virtualLedsNumber); break; } } @@ -300,20 +307,21 @@ namespace Leds{ { // SPI (APA102 / SK9822) switch (cfgLedDataPin) { #if defined(CONFIG_IDF_TARGET_ESP32S3) - case 11: FastLED.addLeds(leds, virtualLedsNumber); break; + case 11: FastLED.addLeds(leds, virtualLedsNumber); break; #elif defined(CONFIG_IDF_TARGET_ESP32C3) - case 7: FastLED.addLeds(leds, virtualLedsNumber); break; + case 7: FastLED.addLeds(leds, virtualLedsNumber); break; #elif defined(CONFIG_IDF_TARGET_ESP32C6) - case 6: FastLED.addLeds(leds, virtualLedsNumber); break; + case 6: FastLED.addLeds(leds, virtualLedsNumber); break; #endif default: Log::debug("!!! FATAL ERROR: Invalid LED Data Pin. Must use Hardware SPI pins. !!!"); - FastLED.addLeds(leds, virtualLedsNumber); + FastLED.addLeds(leds, virtualLedsNumber); } } - FastLED.setBrightness(Volatile::state.brightness); + FastLED.setBrightness(255); + #else if (cfgLedType == LedType::WS2812 || cfgLedType == LedType::SK6812) { // clockless @@ -352,24 +360,36 @@ namespace Leds{ Volatile::updateStaticColor(cfg.led.r, cfg.led.g, cfg.led.b); } + inline uint8_t scaleBri(uint8_t v) + { + return (static_cast(v) * briPlus) >> 8; + } + template void setLed(int index, uint8_t r, uint8_t g, uint8_t b) { #ifdef USE_FASTLED + if constexpr (applyBrightness) { + r = scaleBri(r); + g = scaleBri(g); + b = scaleBri(b); + } + if (fastLedsType == LedType::SK6812) { if (index >= fastLedsNumber) return; const RgbwColor calibrated = rgb2rgbw(r, g, b); uint16_t i = index * 4; - reinterpret_cast(leds)[i] = calibrated.G; - reinterpret_cast(leds)[i + 1] = calibrated.R; - reinterpret_cast(leds)[i + 2] = calibrated.B; - reinterpret_cast(leds)[i + 3] = calibrated.W; + auto raw = reinterpret_cast(leds); + raw[i] = calibrated.G; + raw[i + 1] = calibrated.R; + raw[i + 2] = calibrated.B; + raw[i + 3] = calibrated.W; } else { if (index >= fastLedsNumber) return; - leds[index] = CRGB(r, g, b); + leds[index] = CRGB(g, r, b); } #else if (dotstar != nullptr) @@ -397,19 +417,27 @@ namespace Leds{ void setLedW(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { #ifdef USE_FASTLED + if constexpr (applyBrightness) { + r = scaleBri(r); + g = scaleBri(g); + b = scaleBri(b); + w = scaleBri(w); + } + if (fastLedsType == LedType::SK6812) { if (index >= fastLedsNumber) return; uint16_t i = index * 4; - reinterpret_cast(leds)[i] = g; - reinterpret_cast(leds)[i + 1] = r; - reinterpret_cast(leds)[i + 2] = b; - reinterpret_cast(leds)[i + 3] = w; + auto raw = reinterpret_cast(leds); + raw[i] = g; + raw[i + 1] = r; + raw[i + 2] = b; + raw[i + 3] = w; } else { if (index >= fastLedsNumber) return; - leds[index] = CRGB(r, g, b); + leds[index] = CRGB(g, r, b); } #else if (dotstar != nullptr) @@ -433,7 +461,7 @@ namespace Leds{ #endif } - void checkDeleyedRender() + void checkDelayedRender() { if (delayedRender) { @@ -491,10 +519,7 @@ namespace Leds{ } template void setLed(int, uint8_t, uint8_t, uint8_t); - template void setLedW(int, uint8_t, uint8_t, uint8_t, uint8_t); - - #ifndef USE_FASTLED - template void setLed(int, uint8_t, uint8_t, uint8_t); - template void setLedW(int, uint8_t, uint8_t, uint8_t, uint8_t); - #endif + template void setLedW(int, uint8_t, uint8_t, uint8_t, uint8_t); + template void setLed(int, uint8_t, uint8_t, uint8_t); + template void setLedW(int, uint8_t, uint8_t, uint8_t, uint8_t); } \ No newline at end of file diff --git a/src/manager.cpp b/src/manager.cpp index a34adb7..2c99351 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -61,8 +61,8 @@ void managerLoop() { Volatile::checkStreamTimeout(); - Leds::synchronizeLedsToVolatileStateBeforeDeleyedRender(); - Leds::checkDeleyedRender(); + Leds::synchronizeLedsToVolatileStateBeforeDelayedRender(); + Leds::checkDelayedRender(); if (_pendingReboot) { diff --git a/src/udp_receiver.cpp b/src/udp_receiver.cpp index 88808ec..933f9ec 100644 --- a/src/udp_receiver.cpp +++ b/src/udp_receiver.cpp @@ -59,15 +59,9 @@ void handleDDP(WiFiUDP& udp) { return; } - #ifdef USE_FASTLED - const bool brightnessControl = false; - auto setPixel = Leds::setLed; - auto setPixelW = Leds::setLedW; - #else - const bool brightnessControl = (Volatile::state.brightness != 255); - auto setPixel = brightnessControl ? Leds::setLed : Leds::setLed; - auto setPixelW = brightnessControl ? Leds::setLedW : Leds::setLedW; - #endif + const bool brightnessControl = (Volatile::state.brightness != 255); + auto setPixel = brightnessControl ? Leds::setLed : Leds::setLed; + auto setPixelW = brightnessControl ? Leds::setLedW : Leds::setLedW; uint8_t buffer[packetSize]; uint8_t* endBuffer = &(buffer[0]) + udp.read(buffer, packetSize); @@ -134,15 +128,9 @@ void handleRealTime(WiFiUDP& udp) { return; } - #ifdef USE_FASTLED - const bool brightnessControl = false; - auto setPixel = Leds::setLed; - auto setPixelW = Leds::setLedW; - #else - const bool brightnessControl = (Volatile::state.brightness != 255); - auto setPixel = brightnessControl ? Leds::setLed : Leds::setLed; - auto setPixelW = brightnessControl ? Leds::setLedW : Leds::setLedW; - #endif + const bool brightnessControl = (Volatile::state.brightness != 255); + auto setPixel = brightnessControl ? Leds::setLed : Leds::setLed; + auto setPixelW = brightnessControl ? Leds::setLedW : Leds::setLedW; uint8_t buffer[packetSize]; uint8_t* endBuffer = &(buffer[0]) + udp.read(buffer, packetSize); @@ -198,15 +186,9 @@ void handleRAW(WiFiUDP& udp) return; } - #ifdef USE_FASTLED - const bool brightnessControl = false; - auto setPixel = Leds::setLed; - auto setPixelW = Leds::setLedW; - #else - const bool brightnessControl = (Volatile::state.brightness != 255); - auto setPixel = brightnessControl ? Leds::setLed : Leds::setLed; - auto setPixelW = brightnessControl ? Leds::setLedW : Leds::setLedW; - #endif + const bool brightnessControl = (Volatile::state.brightness != 255); + auto setPixel = brightnessControl ? Leds::setLed : Leds::setLed; + auto setPixelW = brightnessControl ? Leds::setLedW : Leds::setLedW; uint8_t buffer[packetSize]; uint8_t* endBuffer = &(buffer[0]) + udp.read(buffer, packetSize); diff --git a/version b/version index e9c21cd..0b81e50 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.0.2-alpha.6 +0.0.2-alpha.7 From 8ef453b657f6b20b250aae4a9159ab359cbf3e12 Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:55:40 +0100 Subject: [PATCH 02/16] Optimize build --- .github/workflows/build.yml | 9 ++++++--- .gitignore | 2 ++ platformio.ini | 1 + scripts/enable_ccache.py | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 scripts/enable_ccache.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef4a73d..4fa5175 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: include: - group: ModernESP_C2 boards: "esp32c2" - cache_extra: "" + cache_extra: ".pio" - group: ModernESP_C3_C5_C6 boards: "esp32c3 esp32c5 esp32c6" cache_extra: "" @@ -45,13 +45,16 @@ jobs: path: | ~/.platformio/packages ~/.platformio/platforms + .ccache ${{ matrix.cache_extra }} key: pio-${{ matrix.group }}-${{ hashFiles('platformio.ini') }} restore-keys: | pio-${{ matrix.group }}- - - name: Install PlatformIO + esptool + - name: Install PlatformIO + esptool + ccache run: | + sudo apt update + sudo apt install ccache python -m pip install --upgrade pip pip install --upgrade platformio esptool @@ -62,7 +65,7 @@ jobs: for env in $ENVS; do CMD="$CMD -e $env" done - eval "$CMD" || (sleep 25 && echo "🔄 Retry for ${{ matrix.group }} (network?)" && eval "$CMD") + eval "$CMD" - name: "Upload Artifacts - ${{ matrix.group }}" uses: actions/upload-artifact@v7 diff --git a/.gitignore b/.gitignore index b5273f7..77619c8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ include/web_resources.h release/ managed_components/ sdkconfig* +ccache.exe +.ccache/ diff --git a/platformio.ini b/platformio.ini index ec84a2a..7ed5b00 100644 --- a/platformio.ini +++ b/platformio.ini @@ -4,6 +4,7 @@ framework = arduino monitor_speed = 115200 board_build.filesystem = littlefs extra_scripts = + pre:scripts/enable_ccache.py pre:scripts/get_version.py pre:scripts/web_embedder.py post:scripts/collect_firmware.py diff --git a/scripts/enable_ccache.py b/scripts/enable_ccache.py new file mode 100644 index 0000000..c31fdc5 --- /dev/null +++ b/scripts/enable_ccache.py @@ -0,0 +1,36 @@ +import os +import sys + +Import("env") + +pio_env = env.get("PIOENV", "default") +ccache_dir = os.path.join(env["PROJECT_DIR"], ".ccache", pio_env) +os.makedirs(ccache_dir, exist_ok=True) + +os.environ["CCACHE_DIR"] = ccache_dir +env.Append(ENV={"CCACHE_DIR": ccache_dir}) + +if sys.platform.startswith("win32"): + local = os.path.join(env["PROJECT_DIR"], "ccache.exe") + ccache_cmd = f'"{local}"' if os.path.exists(local) else "ccache" +else: + ccache_cmd = "ccache" + +print(f"\n[ccache] === {pio_env.upper()} ===") +print(f"[ccache] DIR: {ccache_dir}") +print(f"[ccache] CMD: {ccache_cmd}") + +for var in ("CCCOM", "CXXCOM", "ASCOM"): + if var in env: + old = env[var] + env[var] = f"{ccache_cmd} {old}" + print(f"[ccache] Wrapped {var} → {env[var]}") + +os.environ.update({ + "CCACHE_SLOPPINESS": "pch_defines,time_macros,include_file_mtime,include_file_ctime,file_macro,locale", + "CCACHE_COMPRESS": "1", + "CCACHE_COMPRESSLEVEL": "6", + "CCACHE_MAXSIZE": "200M", + "CCACHE_BASEDIR": env["PROJECT_DIR"] +}) +env.Append(ENV=dict(os.environ)) From 18dcf27df47c4d338805d4c72f0b324673eb2fef Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:12:13 +0100 Subject: [PATCH 03/16] Simplified the rendering path --- src/leds.cpp | 92 ++++++++++++++++++---------------------------------- 1 file changed, 32 insertions(+), 60 deletions(-) diff --git a/src/leds.cpp b/src/leds.cpp index aea23c1..1e6bf6d 100644 --- a/src/leds.cpp +++ b/src/leds.cpp @@ -39,31 +39,31 @@ #include #if defined(CONFIG_IDF_TARGET_ESP32C6) - typedef NeoPixelBus DotStar; + typedef NeoPixelBus DotStar; typedef NeoPixelBus NeoPixel; typedef NeoPixelBus NeoPixelRgbw; #elif defined(CONFIG_IDF_TARGET_ESP32C3) - typedef NeoPixelBus DotStar; + typedef NeoPixelBus DotStar; typedef NeoPixelBus NeoPixel; typedef NeoPixelBus NeoPixelRgbw; #elif defined(CONFIG_IDF_TARGET_ESP32S3) - typedef NeoPixelBus DotStar; + typedef NeoPixelBus DotStar; typedef NeoPixelBus NeoPixel; typedef NeoPixelBus NeoPixelRgbw; #elif defined(CONFIG_IDF_TARGET_ESP32S2) - typedef NeoPixelBus DotStar; + typedef NeoPixelBus DotStar; typedef NeoPixelBus NeoPixel; typedef NeoPixelBus NeoPixelRgbw; #elif defined(ARDUINO_ARCH_ESP32) - typedef NeoPixelBus DotStar; + typedef NeoPixelBus DotStar; typedef NeoPixelBus NeoPixel; typedef NeoPixelBus NeoPixelRgbw; #elif defined(ARDUINO_ARCH_ESP8266) - typedef NeoPixelBus DotStar; + typedef NeoPixelBus DotStar; typedef NeoPixelBus NeoPixel; typedef NeoPixelBus NeoPixelRgbw; #else // Raspberry Pi Pico - typedef NeoPixelBus DotStar; + typedef NeoPixelBus DotStar; typedef NeoPixelBus NeoPixel; typedef NeoPixelBus NeoPixelRgbw; #endif @@ -114,7 +114,7 @@ namespace Leds{ return; if (dotstar != nullptr) - {dotstar->ClearTo(RgbwColor(0, 0, 0, 31)); dotstar->Show();} + {dotstar->ClearTo(RgbColor(0, 0, 0)); dotstar->Show();} else if (neopixel != nullptr) {neopixel->ClearTo(RgbColor(0, 0, 0)); neopixel->Show();} else if (neopixelRgbw != nullptr) @@ -171,39 +171,15 @@ namespace Leds{ auto r = (Volatile::state.on) ? Volatile::state.staticColor.red : 0; auto g = (Volatile::state.on) ? Volatile::state.staticColor.green : 0; auto b = (Volatile::state.on) ? Volatile::state.staticColor.blue : 0; + + for(int i = 0; i < getLedsNumber(); i++) { + if (Volatile::state.brightness != 255) + setLed(i, r, g, b); + else + setLed(i, r, g, b); + } - #ifdef USE_FASTLED - for(int i = 0; i < getLedsNumber(); i++) { - if (Volatile::state.brightness != 255) - setLed(i, r, g, b); - else - setLed(i, r, g, b); - } - - FastLED.show(); - #else - if (dotstar == nullptr && neopixel == nullptr && neopixelRgbw == nullptr) - return; - - if (dotstar != nullptr) { - RgbwColor col(r, g, b, 31); - col = col.Dim(Volatile::state.brightness); - dotstar->ClearTo(col); - dotstar->Show(); - } - else if (neopixel != nullptr) { - RgbColor col(r, g, b); - col = col.Dim(Volatile::state.brightness); - neopixel->ClearTo(col); - neopixel->Show(); - } - else if (neopixelRgbw != nullptr) { - RgbwColor col(r, g, b, 0); - col = col.Dim(Volatile::state.brightness); - neopixelRgbw->ClearTo(col); - neopixelRgbw->Show(); - } - #endif + renderLed(true); } } @@ -368,13 +344,13 @@ namespace Leds{ template void setLed(int index, uint8_t r, uint8_t g, uint8_t b) { + if constexpr (applyBrightness) { + r = scaleBri(r); + g = scaleBri(g); + b = scaleBri(b); + } #ifdef USE_FASTLED - if constexpr (applyBrightness) { - r = scaleBri(r); - g = scaleBri(g); - b = scaleBri(b); - } - + if (fastLedsType == LedType::SK6812) { if (index >= fastLedsNumber) return; @@ -394,20 +370,17 @@ namespace Leds{ #else if (dotstar != nullptr) { - RgbwColor col(r, g, b, 31); - if constexpr (applyBrightness) col = col.Dim(Volatile::state.brightness); + RgbColor col(r, g, b); dotstar->SetPixelColor(index, col); } else if (neopixel != nullptr) { RgbColor col(r, g, b); - if constexpr (applyBrightness) col = col.Dim(Volatile::state.brightness); neopixel->SetPixelColor(index, col); } else if (neopixelRgbw != nullptr) { RgbwColor col = rgb2rgbw(r, g, b); - if constexpr (applyBrightness) col = col.Dim(Volatile::state.brightness); neopixelRgbw->SetPixelColor(index, col); } #endif @@ -416,13 +389,15 @@ namespace Leds{ template void setLedW(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + if constexpr (applyBrightness) { + r = scaleBri(r); + g = scaleBri(g); + b = scaleBri(b); + w = scaleBri(w); + } + #ifdef USE_FASTLED - if constexpr (applyBrightness) { - r = scaleBri(r); - g = scaleBri(g); - b = scaleBri(b); - w = scaleBri(w); - } + if (fastLedsType == LedType::SK6812) { @@ -442,20 +417,17 @@ namespace Leds{ #else if (dotstar != nullptr) { - RgbwColor col(r, g, b, min(w, (uint8_t)31)); - if constexpr (applyBrightness) col = col.Dim(Volatile::state.brightness); + RgbColor col(r, g, b); dotstar->SetPixelColor(index, col); } else if (neopixel != nullptr) { RgbColor col(r, g, b); - if constexpr (applyBrightness) col = col.Dim(Volatile::state.brightness); neopixel->SetPixelColor(index, col); } else if (neopixelRgbw != nullptr) { RgbwColor col(r, g, b, w); - if constexpr (applyBrightness) col = col.Dim(Volatile::state.brightness); neopixelRgbw->SetPixelColor(index, col); } #endif From ff285fc0aa09987cf80399012b6ea5d2a9d6c2d7 Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 01:24:13 +0100 Subject: [PATCH 04/16] Fix SPI leds on ESP32/ESP32-S2/ESP8266 --- src/leds.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/leds.cpp b/src/leds.cpp index 1e6bf6d..5cb41d8 100644 --- a/src/leds.cpp +++ b/src/leds.cpp @@ -315,11 +315,7 @@ namespace Leds{ } else { // SPI (APA102 / SK9822) - #if defined(ARDUINO_ARCH_ESP32) - dotstar = new DotStar(cfgLedNumLeds, cfgLedDataPin, cfgLedClockPin); //23, 18 - #else - dotstar = new DotStar(cfgLedNumLeds, cfgLedDataPin, cfgLedClockPin); //19, 18 - #endif + dotstar = new DotStar(cfgLedNumLeds, cfgLedClockPin, cfgLedDataPin); dotstar->Begin(); } #endif From 084ef38ccb5d55a7e91d851acf5112b50c25efd6 Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 01:38:08 +0100 Subject: [PATCH 05/16] Updated SPI valid GPIO for ESP32-S2 --- data/gpio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/gpio.js b/data/gpio.js index e0e85f7..5542b8a 100644 --- a/data/gpio.js +++ b/data/gpio.js @@ -5,7 +5,7 @@ function setupPinValidator() { "ESP32-C3": { gpio: [0,1,2,3,4,5,6,7,8,10,20,21], spi: {7:6} }, // GPIO08 = built-in WS2812B "ESP8266": { gpio: [2], spi: {19:18} }, "ESP32": { gpio: null, spi: {23:18} }, - "ESP32-S2": { gpio: null, spi: {11:7} }, + "ESP32-S2": { gpio: null, spi: {35:36} }, "ESP32-ETH01": { gpio: [2,4,14], spi: {4:14} }, "ESP32-C2": { gpio: [0,1,2,3,4,5,6,7,10], spi: {7:6} }, "ESP32-C5": { gpio: [0,1,2,3,4,5,6,7,8,10,11,27], spi: {7:6} } // GPIO27 = built-in WS2812B From 94a917c3d2fd9756910d8a6866c834516d89ec7f Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 02:39:54 +0100 Subject: [PATCH 06/16] Change methods for ESP32 --- src/leds.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/leds.cpp b/src/leds.cpp index 5cb41d8..89f8573 100644 --- a/src/leds.cpp +++ b/src/leds.cpp @@ -56,8 +56,8 @@ typedef NeoPixelBus NeoPixelRgbw; #elif defined(ARDUINO_ARCH_ESP32) typedef NeoPixelBus DotStar; - typedef NeoPixelBus NeoPixel; - typedef NeoPixelBus NeoPixelRgbw; + typedef NeoPixelBus NeoPixel; + typedef NeoPixelBus NeoPixelRgbw; #elif defined(ARDUINO_ARCH_ESP8266) typedef NeoPixelBus DotStar; typedef NeoPixelBus NeoPixel; From 43411709465eb817fab95da1d4ca65822a0723d8 Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 03:05:57 +0100 Subject: [PATCH 07/16] Fix default SPI GPIO on esp8266 --- data/gpio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/gpio.js b/data/gpio.js index 5542b8a..700d693 100644 --- a/data/gpio.js +++ b/data/gpio.js @@ -3,7 +3,7 @@ function setupPinValidator() { "ESP32-C6": { gpio: [0,1,2,3,4,5,6,7,8,10,15,18,19,20,21,22], spi: {6:5} }, "ESP32-S3": { gpio: [1,2,4,5,6,7,8,10,16,17,18,48], spi: {11:12} }, // GPIO48 = built-in WS2812B "ESP32-C3": { gpio: [0,1,2,3,4,5,6,7,8,10,20,21], spi: {7:6} }, // GPIO08 = built-in WS2812B - "ESP8266": { gpio: [2], spi: {19:18} }, + "ESP8266": { gpio: [2], spi: {13:14} }, "ESP32": { gpio: null, spi: {23:18} }, "ESP32-S2": { gpio: null, spi: {35:36} }, "ESP32-ETH01": { gpio: [2,4,14], spi: {4:14} }, From 0cb1486f9e2c60fcc5b0b261ac31297dde3fc1a4 Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 17:44:45 +0100 Subject: [PATCH 08/16] Set Pico SPI default GPIO --- data/gpio.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/gpio.js b/data/gpio.js index 700d693..f2fa66d 100644 --- a/data/gpio.js +++ b/data/gpio.js @@ -8,7 +8,9 @@ function setupPinValidator() { "ESP32-S2": { gpio: null, spi: {35:36} }, "ESP32-ETH01": { gpio: [2,4,14], spi: {4:14} }, "ESP32-C2": { gpio: [0,1,2,3,4,5,6,7,10], spi: {7:6} }, - "ESP32-C5": { gpio: [0,1,2,3,4,5,6,7,8,10,11,27], spi: {7:6} } // GPIO27 = built-in WS2812B + "ESP32-C5": { gpio: [0,1,2,3,4,5,6,7,8,10,11,27], spi: {7:6} }, // GPIO27 = built-in WS2812B + "RP2040": { gpio: null, spi: {19:18} }, + "RP2350": { gpio: null, spi: {19:18} } }; const arch = (typeof cfgDeviceArchitecture !== 'undefined') ? cfgDeviceArchitecture : ""; From 3d05285642db93bc2ed8bdd5ac27281c39db09ee Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:09:36 +0100 Subject: [PATCH 09/16] Require restart on Pico boards --- include/leds.h | 4 ++++ src/leds.cpp | 9 +++++++-- src/web_server.cpp | 8 ++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/include/leds.h b/include/leds.h index ed8804b..b18fad3 100644 --- a/include/leds.h +++ b/include/leds.h @@ -4,6 +4,10 @@ #include #include "config.h" +#if !(defined(USE_FASTLED) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350)) + #define LEDS_NOT_REQUIRE_RESTART +#endif + namespace Leds { void applyLedConfig(); int getLedsNumber(); diff --git a/src/leds.cpp b/src/leds.cpp index 89f8573..095f772 100644 --- a/src/leds.cpp +++ b/src/leds.cpp @@ -86,6 +86,7 @@ ////////////////////////////////////////////////////////////////////////////////////////////////// namespace Leds{ + bool ledDriverInitialized = false; volatile bool delayedRender = false; uint16_t briPlus = 256; @@ -187,15 +188,17 @@ namespace Leds{ uint8_t calGain, uint8_t calRed, uint8_t calGreen, uint8_t calBlue) { clearAll(); - #ifdef USE_FASTLED - if (FastLED.count()) + #ifndef LEDS_NOT_REQUIRE_RESTART + if (ledDriverInitialized) { if (cfgLedType == LedType::SK6812) { setParamsAndPrepareCalibration(calGain, calRed, calGreen, calBlue); } return; } + #endif + #ifdef USE_FASTLED if (leds != nullptr) { delete[] leds; @@ -321,6 +324,8 @@ namespace Leds{ #endif clearAll(); + + ledDriverInitialized = true; } void applyLedConfig() diff --git a/src/web_server.cpp b/src/web_server.cpp index c71a203..3996a7a 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -165,7 +165,7 @@ void setupWebServer(AsyncWebServer& server) { uint8_t t = request->getParam("type", true)->value().toInt(); if (t != (uint8_t)cfg.led.type) { - #ifdef USE_FASTLED + #ifndef LEDS_NOT_REQUIRE_RESTART needsRestart = true; #endif @@ -176,7 +176,7 @@ void setupWebServer(AsyncWebServer& server) { uint8_t p = request->getParam("dataPin", true)->value().toInt(); if (p != cfg.led.dataPin) { - #ifdef USE_FASTLED + #ifndef LEDS_NOT_REQUIRE_RESTART needsRestart = true; #endif @@ -187,7 +187,7 @@ void setupWebServer(AsyncWebServer& server) { uint8_t p = request->getParam("clockPin", true)->value().toInt(); if (p != cfg.led.clockPin) { - #ifdef USE_FASTLED + #ifndef LEDS_NOT_REQUIRE_RESTART needsRestart = true; #endif @@ -198,7 +198,7 @@ void setupWebServer(AsyncWebServer& server) { uint16_t n = request->getParam("numLeds", true)->value().toInt(); if (n != cfg.led.numLeds && n <= MAX_LEDS) { - #ifdef USE_FASTLED + #ifndef LEDS_NOT_REQUIRE_RESTART needsRestart = true; #endif From 09c960fb055c06a2a64fb8414d026b284dd33300 Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:44:40 +0100 Subject: [PATCH 10/16] Fix SPI LEDs for ESP32-ETH01 --- data/gpio.js | 4 ++-- src/leds.cpp | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/data/gpio.js b/data/gpio.js index f2fa66d..4a20d8a 100644 --- a/data/gpio.js +++ b/data/gpio.js @@ -6,8 +6,8 @@ function setupPinValidator() { "ESP8266": { gpio: [2], spi: {13:14} }, "ESP32": { gpio: null, spi: {23:18} }, "ESP32-S2": { gpio: null, spi: {35:36} }, - "ESP32-ETH01": { gpio: [2,4,14], spi: {4:14} }, - "ESP32-C2": { gpio: [0,1,2,3,4,5,6,7,10], spi: {7:6} }, + "ESP32-ETH01": { gpio: [2,4], spi: {2:4} }, + "ESP32-C2": { gpio: [0,1,2,3,4,5,6,7,10], spi: {7:6} }, "ESP32-C5": { gpio: [0,1,2,3,4,5,6,7,8,10,11,27], spi: {7:6} }, // GPIO27 = built-in WS2812B "RP2040": { gpio: null, spi: {19:18} }, "RP2350": { gpio: null, spi: {19:18} } diff --git a/src/leds.cpp b/src/leds.cpp index 095f772..2407636 100644 --- a/src/leds.cpp +++ b/src/leds.cpp @@ -55,7 +55,11 @@ typedef NeoPixelBus NeoPixel; typedef NeoPixelBus NeoPixelRgbw; #elif defined(ARDUINO_ARCH_ESP32) - typedef NeoPixelBus DotStar; + #if ETH_PHY_TYPE == ETH_PHY_LAN8720 + typedef NeoPixelBus DotStar; + #else + typedef NeoPixelBus DotStar; + #endif typedef NeoPixelBus NeoPixel; typedef NeoPixelBus NeoPixelRgbw; #elif defined(ARDUINO_ARCH_ESP8266) From b191d22af68757adc86fe01aa5e3e7e2c0a16549 Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 21:14:55 +0100 Subject: [PATCH 11/16] Fix ETH_PHY_TYPE detection --- src/leds.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leds.cpp b/src/leds.cpp index 2407636..3380a45 100644 --- a/src/leds.cpp +++ b/src/leds.cpp @@ -55,7 +55,7 @@ typedef NeoPixelBus NeoPixel; typedef NeoPixelBus NeoPixelRgbw; #elif defined(ARDUINO_ARCH_ESP32) - #if ETH_PHY_TYPE == ETH_PHY_LAN8720 + #if defined(ETH_PHY_TYPE) && (ETH_PHY_TYPE == ETH_PHY_LAN8720) typedef NeoPixelBus DotStar; #else typedef NeoPixelBus DotStar; From 9e18ebe04773d8a9f92e1333fe8206e924e3bd4d Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 21:28:24 +0100 Subject: [PATCH 12/16] Change default SPI GPIO for C6 --- data/gpio.js | 2 +- src/leds.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/gpio.js b/data/gpio.js index 4a20d8a..47e2db7 100644 --- a/data/gpio.js +++ b/data/gpio.js @@ -1,6 +1,6 @@ function setupPinValidator() { const hardwareLimits = { - "ESP32-C6": { gpio: [0,1,2,3,4,5,6,7,8,10,15,18,19,20,21,22], spi: {6:5} }, + "ESP32-C6": { gpio: [0,1,2,3,4,5,6,7,8,10,15,18,19,20,21,22], spi: {5:4} }, "ESP32-S3": { gpio: [1,2,4,5,6,7,8,10,16,17,18,48], spi: {11:12} }, // GPIO48 = built-in WS2812B "ESP32-C3": { gpio: [0,1,2,3,4,5,6,7,8,10,20,21], spi: {7:6} }, // GPIO08 = built-in WS2812B "ESP8266": { gpio: [2], spi: {13:14} }, diff --git a/src/leds.cpp b/src/leds.cpp index 3380a45..953e883 100644 --- a/src/leds.cpp +++ b/src/leds.cpp @@ -294,7 +294,7 @@ namespace Leds{ #elif defined(CONFIG_IDF_TARGET_ESP32C3) case 7: FastLED.addLeds(leds, virtualLedsNumber); break; #elif defined(CONFIG_IDF_TARGET_ESP32C6) - case 6: FastLED.addLeds(leds, virtualLedsNumber); break; + case 5: FastLED.addLeds(leds, virtualLedsNumber); break; #endif default: From 2adc205ca97b5ca53182ba7a903d0715cf1200b5 Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:10:02 +0100 Subject: [PATCH 13/16] Add FastLED support for SPI on ESP32-C2/C5 --- src/leds.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leds.cpp b/src/leds.cpp index 953e883..04b73ae 100644 --- a/src/leds.cpp +++ b/src/leds.cpp @@ -291,7 +291,7 @@ namespace Leds{ switch (cfgLedDataPin) { #if defined(CONFIG_IDF_TARGET_ESP32S3) case 11: FastLED.addLeds(leds, virtualLedsNumber); break; - #elif defined(CONFIG_IDF_TARGET_ESP32C3) + #elif defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) case 7: FastLED.addLeds(leds, virtualLedsNumber); break; #elif defined(CONFIG_IDF_TARGET_ESP32C6) case 5: FastLED.addLeds(leds, virtualLedsNumber); break; From 5fb01a42b35835adb0d92e5f4744f5696a2784d6 Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:00:58 +0100 Subject: [PATCH 14/16] Add button to reset SSID/Password and switch to AP --- .gitignore | 2 ++ data/css/settings.css | 7 ++++++ data/network.js | 52 +++++++++++++++++++++++++++++++++++++++++++ data/settings.html | 11 ++++++++- src/web_server.cpp | 18 +++++++++++++++ 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 data/network.js diff --git a/.gitignore b/.gitignore index 77619c8..47f9315 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ managed_components/ sdkconfig* ccache.exe .ccache/ +CMakeLists.txt +dependencies.lock diff --git a/data/css/settings.css b/data/css/settings.css index 8ee7812..84ed4e8 100644 --- a/data/css/settings.css +++ b/data/css/settings.css @@ -125,3 +125,10 @@ details summary[role="button"]:focus { opacity: 0.6; transition: opacity 0.2s ease; } + +#btn-switch-ap { + background-color: #eab308; + border-color: #eab308; + color: #111111; + font-weight: bold; +} diff --git a/data/network.js b/data/network.js new file mode 100644 index 0000000..c99b7be --- /dev/null +++ b/data/network.js @@ -0,0 +1,52 @@ +function promptSwitchToAP() { + const dialog = document.createElement('dialog'); + dialog.innerHTML = ` +
+

⚠️ Switch to AP Mode

+

This will reset Hyperk WiFi password and switch to AP after reboot. Are you sure?

+
+ + +
+
+ `; + document.body.appendChild(dialog); + dialog.showModal(); + + dialog.querySelector('#ap-cancel').addEventListener('click', () => { + dialog.close(); + dialog.remove(); + }); + + dialog.querySelector('#ap-confirm').addEventListener('click', async (e) => { + const btn = e.target; + btn.setAttribute('aria-busy', 'true'); + + try { + const params = new URLSearchParams(); + params.append('reset_wifi', cfgSSID); + + const res = await fetch('/save_config', { + method: 'POST', + body: params + }); + + if (res.ok) { + const data = await res.json(); + showToast(data.status === 'reboot'); + } + } catch (err) { + alert("Error!"); + } finally { + dialog.close(); + dialog.remove(); + } + }); +} + +function setupNetwork() { + const apBtn = document.getElementById('btn-switch-ap'); + if (apBtn) { + apBtn.addEventListener('click', promptSwitchToAP); + } +} diff --git a/data/settings.html b/data/settings.html index 5261cf1..2a79ab7 100644 --- a/data/settings.html +++ b/data/settings.html @@ -110,7 +110,7 @@
White channel calibration
-
+
Network
+
+ +