Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ include/web_resources.h
release/
managed_components/
sdkconfig*
ccache.exe
.ccache/
CMakeLists.txt
dependencies.lock
7 changes: 7 additions & 0 deletions data/css/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
16 changes: 9 additions & 7 deletions data/gpio.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
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-S3": { gpio: [1,2,4,5,6,7,8,10,16,17,18,48], spi: {11:12} }, // GPIO48 = built-in WS2812B
"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: {5:4} }, // 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: {11:7} },
"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-S2": { gpio: null, spi: {35:36} },
"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} }
};

const arch = (typeof cfgDeviceArchitecture !== 'undefined') ? cfgDeviceArchitecture : "";
Expand Down
52 changes: 52 additions & 0 deletions data/network.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
function promptSwitchToAP() {
const dialog = document.createElement('dialog');
dialog.innerHTML = `
<article>
<h3>⚠️ Switch to AP Mode</h3>
<p>This will reset Hyperk WiFi password and switch to AP after reboot. Are you sure?</p>
<footer>
<button class="secondary outline" id="ap-cancel">Cancel</button>
<button class="contrast" id="ap-confirm">Yes, proceed</button>
</footer>
</article>
`;
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);
}
}
11 changes: 10 additions & 1 deletion data/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ <h5>White channel calibration</h5>
</div>
</details>

<details>
<details ontoggle="loadSubScript('network', 'setupNetwork')">
<summary role="button" class="outline secondary">Network</summary>
<div>
<label>Device Name (MDNS)
Expand All @@ -122,6 +122,9 @@ <h5>White channel calibration</h5>
<input type="text" name="extraMdnsTag" maxlength="15">
</label>
</div>
<div style="margin-top: 1rem;">
<button type="button" id="btn-switch-ap">Switch to AP</button>
</div>
</details>

<details id="ota_page" style="display: none;" ontoggle="loadSubScript('ota', null)">
Expand Down Expand Up @@ -192,6 +195,7 @@ <h3>⚠️ OTA Firmware Flash Procedure</h3>
let cfgDeviceArchitecture = "";
let cfgBoardArchitecture = ""
let cfgDeviceVersion = "";
let cfgSSID = "";

const loadedScripts = {};
function loadSubScript(scriptName, initFunName) {
Expand Down Expand Up @@ -245,6 +249,11 @@ <h3>⚠️ OTA Firmware Flash Procedure</h3>
const c = await res.json();
const l = c.config;

const SSIDField = l["ssid"];
if (SSIDField !== undefined){
cfgSSID = SSIDField;
}

const archField = l["architecture"];
if (archField !== undefined){
cfgDeviceArchitecture = archField;
Expand Down
8 changes: 6 additions & 2 deletions include/leds.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
#include <vector>
#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();
void checkDeleyedRender();
void checkDelayedRender();
void renderLed(bool isNewFrame);
void synchronizeLedsToVolatileStateBeforeDeleyedRender();
void synchronizeLedsToVolatileStateBeforeDelayedRender();

template<bool applyBrightness>
void setLed(int index, uint8_t r, uint8_t g, uint8_t b);
Expand Down
1 change: 1 addition & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
36 changes: 36 additions & 0 deletions scripts/enable_ccache.py
Original file line number Diff line number Diff line change
@@ -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))
Loading