From 96fe1761562db2413aba66fa047f4df205b49d8e Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Thu, 30 Apr 2026 02:58:59 +0200 Subject: [PATCH 1/4] Add PARLIO, multi-segments, power relay, custom boards --- .github/workflows/build.yml | 3 + 3RD_PARTY_LICENSES | 53 +- data/css/settings.css | 31 + data/gpio.js | 139 +++- data/settings.html | 156 +++- include/config.h | 37 +- include/led_bridge/double_buffer.h | 88 +++ include/led_bridge/espressif_bridge.h | 237 ------ include/led_bridge/fastled_bridge.h | 188 ----- include/led_bridge/led_bridge.h | 7 +- .../led_bridge/multi_esp32_led_strip_bridge.h | 501 +++++++++++++ include/led_bridge/neopixelbus_bridge.h | 52 +- include/led_bridge/parlio_bridge.h | 604 ++++++++++++++++ .../led_bridge/picolada/pico/neopixel.pio.h | 81 +++ .../picolada/pico/neopixel_ws2812b.pio.h | 81 +++ .../led_bridge/picolada/pico2/neopixel.pio.h | 81 +++ .../picolada/pico2/neopixel_ws2812b.pio.h | 81 +++ include/led_bridge/picolada/picolada.h | 675 ++++++++++++++++++ include/led_bridge/picolada_bridge.h | 248 +++++++ include/leds.h | 8 +- include/volatile_state.h | 3 + platformio.ini | 229 ++++-- src/config.cpp | 67 ++ src/leds.cpp | 67 +- src/storage.cpp | 45 +- src/udp_receiver.cpp | 14 +- src/utils.cpp | 8 + src/volatile_state.cpp | 28 + src/web_server.cpp | 57 +- version | 2 +- 30 files changed, 3243 insertions(+), 628 deletions(-) create mode 100644 include/led_bridge/double_buffer.h delete mode 100644 include/led_bridge/espressif_bridge.h delete mode 100644 include/led_bridge/fastled_bridge.h create mode 100644 include/led_bridge/multi_esp32_led_strip_bridge.h create mode 100644 include/led_bridge/parlio_bridge.h create mode 100644 include/led_bridge/picolada/pico/neopixel.pio.h create mode 100644 include/led_bridge/picolada/pico/neopixel_ws2812b.pio.h create mode 100644 include/led_bridge/picolada/pico2/neopixel.pio.h create mode 100644 include/led_bridge/picolada/pico2/neopixel_ws2812b.pio.h create mode 100644 include/led_bridge/picolada/picolada.h create mode 100644 include/led_bridge/picolada_bridge.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4fa5175..277aa7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,6 +26,9 @@ jobs: - group: ClassicESP boards: "esp32 esp32s2 esp32-eth01 esp8266" cache_extra: "" + - group: CustomBoards + boards: "esp32-GLEDOPTO_GL_C_616WL esp32-GLEDOPTO_GL_C_615WL esp32-DOMRAEM_WLE_ADM esp32-IOTORERO_ETHERNET" + cache_extra: "" - group: Pico boards: "pico pico2" cache_extra: "" diff --git a/3RD_PARTY_LICENSES b/3RD_PARTY_LICENSES index 1af254f..3fa15a8 100644 --- a/3RD_PARTY_LICENSES +++ b/3RD_PARTY_LICENSES @@ -1,10 +1,36 @@ +============================================== + HyperSerialPico +============================================== + +MIT License + +Copyright (c) 2023-2026 awawa-dev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + ============================================== HyperSerialEsp8266 ============================================== MIT License -Copyright (c) 2020-2025 awawa-dev +Copyright (c) 2020-2026 awawa-dev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -379,31 +405,6 @@ apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. -============================================== - fastled/FastLED -============================================== - -The MIT License (MIT) - -Copyright (c) 2013 FastLED - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ============================================== picocss/pico ============================================== diff --git a/data/css/settings.css b/data/css/settings.css index 84ed4e8..cac46ae 100644 --- a/data/css/settings.css +++ b/data/css/settings.css @@ -132,3 +132,34 @@ details summary[role="button"]:focus { color: #111111; font-weight: bold; } + +.segmentElement { + border: 1px solid #405000; + padding: 1rem; + margin-bottom: 1rem; + border-radius: var(--pico-border-radius); +} + +.delSegmentBtn { + width: auto; + padding: 0.25rem 1rem; + margin-bottom: 0; + background-color: #EAB308 !important; + border-color: #EAB308 !important; + color: #111111 !important; +} + +.addSegmentBtn { + display: none; + margin-top: 1rem; + background-color: #2e7d32 !important; + border-color: #2e7d32 !important; + color: #fff !important; +} + +.powerRelay { + border: 1px solid #000090; + padding: 1rem; + margin-bottom: 1rem; + border-radius: var(--pico-border-radius); +} diff --git a/data/gpio.js b/data/gpio.js index c430160..a2aea53 100644 --- a/data/gpio.js +++ b/data/gpio.js @@ -4,27 +4,29 @@ function setupPinValidator() { "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: {13:14} }, - "ESP32": { gpio: null, spi: {23:18} }, - "ESP32-S2": { gpio: null, spi: {35:36} }, + "ESP32": { gpio: [2,4,5,12,13,14,15,16,17,18,19,21,23,25,26,27,32,33], spi: {23:18} }, + "ESP32-S2": { gpio: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,21,33,34,35,36,37,38,39,40,41,42,45], 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} } + "RP2350": { gpio: null, spi: {19:18} }, + "esp32-GLEDOPTO_GL_C_616WL": { gpio: [16, 2, 12, 14], spi: {16:2} }, + "esp32-GLEDOPTO_GL_C_615WL": { gpio: [16, 2], spi: {16:2} }, + "esp32-DOMRAEM_WLE_ADM": { gpio: [ 16, 2, 17, 18], spi: {16:2} }, + "esp32-IOTORERO_ETHERNET": { gpio: [ 5, 16, 4, 12], spi: {5:16} } }; const arch = (typeof cfgDeviceArchitecture !== 'undefined') ? cfgDeviceArchitecture : ""; - const els = { type: document.getElementById('ledType'), clkLabel: document.getElementById('clockPinLabel') }; + const els = { type: document.getElementById('ledType'), addSegBtn: document.getElementById('addSegmentBtn'), segContainer: document.getElementById('segmentContainer') }; - if (!els.type || !els.clkLabel) { - console.warn("LED Validator: Missing required DOM elements (ledType/clockPinLabel)"); + if (!els.type || !els.addSegBtn || !els.segContainer) { + console.warn("LED Validator: Missing required DOM elements (ledType/addSegBtn/segContainer)"); return; } - els.clkLabel.setAttribute('aria-live', 'polite'); - - const setField = (name, opts) => { - const old = document.getElementsByName(name)[0]; + const setField = (wrapper, name, opts) => { + const old = wrapper.querySelector(`select[name="${name}"]`); if (!old) return null; const isSel = (opts != null); @@ -49,26 +51,123 @@ function setupPinValidator() { } old.replaceWith(el); - el.addEventListener('change', updateUI); if (wasFocused) el.focus(); return el; }; - function updateUI() { + function setupSegments() { const isSpi = els.type.value == "2"; const cfg = hardwareLimits[arch]; + const basePins = cfg ? (isSpi ? Object.keys(cfg.spi).map(Number) : cfg.gpio) : null; + const allUsedPins = cfgSegments.map(s => parseInt(s.data, 10)); - let validPins = cfg ? (isSpi ? Object.keys(cfg.spi).map(Number) : cfg.gpio) : null; - const dataPinEditor = setField('dataPin', validPins); + els.segContainer.innerHTML = ''; - const autoClk = (cfg && cfg.spi) ? (cfg.spi[dataPinEditor.value] ?? null) : null; - const clockPinEditor = setField('clockPin', ((autoClk !== null) ? [autoClk] : null)); + cfgSegments.some((segment, i) => { + const wrapper = document.createElement('div'); + wrapper.id = `segment_${i}`; + wrapper.className = 'segmentElement'; + + wrapper.innerHTML = ` +
+ + + ${isSpi ? ` + + ` : ''} + + ${(cfgSegmentSupported && !isSpi && (i > 0)) ? ` + + ` : ''} +
+ + ${(i > 0) ? ` +
+ +
+ ` : ''} + `; + + els.segContainer.appendChild(wrapper); + + const availablePins = (i > 0 && (arch == "RP2040" || arch == "RP2350")) ? [ Math.min((parseInt(cfgSegments[0].data, 10) + i), 22) || i ] : + (basePins ? basePins.filter(p => !allUsedPins.includes(p) || p === parseInt(segment.data, 10)) : null); + const dataPinEditor = setField(wrapper, `dataPin${i}`, availablePins); + cfgSegments[i].data = dataPinEditor?.value || cfgSegments[i].data; + dataPinEditor.onchange = () => { + cfgSegments[i].data = parseInt(dataPinEditor.value, 10); + setupSegments(); + }; - els.clkLabel.style.display = isSpi ? 'block' : 'none'; - clockPinEditor.disabled = !isSpi; + if (cfgSegmentSupported && !isSpi) { + const startIndex = wrapper.querySelector(`input[name="startIndex${i}"]`); + if (startIndex) { + startIndex.oninput = (e) => cfgSegments[i].startIndex = parseInt(e.target.value, 10) || 0; + } + } + + if (isSpi) { + const autoClk = (cfg && cfg.spi && dataPinEditor.value != null) ? (cfg.spi[dataPinEditor.value] ?? null) : null; + const clockPinEditor = setField(wrapper, `clockPin${i}`, ((autoClk !== null) ? [autoClk] : null)); + cfgSegments[i].clock = clockPinEditor?.value || cfgSegments[i].clock; + clockPinEditor.onchange = () => cfgSegments[i].clock = parseInt(clockPinEditor.value, 10); + } + + return !cfgSegmentSupported || isSpi; + }); + + if (cfgSegmentSupported) { + const delButtons = els.segContainer.querySelectorAll('.delSegmentBtn'); + delButtons.forEach(btn => { + btn.onclick = () => { + const idx = parseInt(btn.getAttribute('data-index'), 10); + cfgSegments.splice(idx, 1); + setupSegments(); + }; + }); + } + + if (cfgSegmentSupported && !isSpi) { + els.addSegBtn.style.display = 'block'; + els.addSegBtn.onclick = () => { + if (cfgSegments.length >= cfgSegmentSupported) return; + + const currentUsed = cfgSegments.map(s => parseInt(s.data, 10)); + const freePin = basePins ? basePins.find(p => !currentUsed.includes(p)) : 0; + + const numLeds = parseInt(document.querySelector('input[name="numLeds"]')?.value, 10) || -1; + let nextStart = 0; + if (cfgSegments.length > 0 && numLeds > 0) { + nextStart = Math.floor((cfgSegments.at(-1).startIndex + numLeds) / 2); + } + + cfgSegments.push({ + data: (freePin !== undefined) ? freePin : 0, + clock: 0, + startIndex: nextStart + }); + setupSegments(); + }; + } + else { + els.addSegBtn.style.display = 'none'; + els.addSegBtn.onclick = null; + } } - els.type.onchange = updateUI; - updateUI(); + els.type.onchange = setupSegments; + setupSegments(); }; diff --git a/data/settings.html b/data/settings.html index 2a79ab7..c175f24 100644 --- a/data/settings.html +++ b/data/settings.html @@ -59,17 +59,24 @@

Settings

- - - - + +
+
+ + + +
+ + +