diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4916c83..d5e2088 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,11 +20,14 @@ jobs: - group: ModernESP_C3_C5_C6 boards: "esp32c3 esp32c5 esp32c6" cache_extra: "" + - group: ModernESP_S2 + boards: "esp32s2" + cache_extra: "" - group: ModernESP_S3 boards: "esp32s3" cache_extra: "" - group: ClassicESP - boards: "esp32 esp32s2 esp32-eth01 esp8266" + boards: "esp32 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" diff --git a/.gitignore b/.gitignore index 47f9315..61d3510 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,8 @@ .vscode/settings.json src/idf_component.yml src/idf_component.yml.orig -include/web_resources.h +src/backend/ +export_backend/ release/ managed_components/ sdkconfig* diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 875b052..69f7dc9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,9 +2,9 @@ "version": "2.0.0", "tasks": [ { - "label": "Build ESP Core 2.x", + "label": "Build ModernESP_C2", "type": "shell", - "command": "pio run -e esp32 -e esp32s2 -e esp32-eth01 -e esp8266", + "command": "pio run -e esp32c2", "problemMatcher": [], "presentation": { "reveal": "always", "panel": "shared" }, "options": { @@ -15,9 +15,9 @@ } }, { - "label": "Build ESP Core 3.x", + "label": "Build ModernESP_C3_C5_C6", "type": "shell", - "command": "pio run -e esp32c3 -e esp32s3 -e esp32c6 -e esp32c2 -e esp32c5", + "command": "pio run -e esp32c3 -e esp32c5 -e esp32c6", "problemMatcher": [], "presentation": { "reveal": "always", "panel": "shared" }, "options": { @@ -28,7 +28,59 @@ } }, { - "label": "Build RP2040", + "label": "Build ModernESP_S2", + "type": "shell", + "command": "pio run -e esp32s2", + "problemMatcher": [], + "presentation": { "reveal": "always", "panel": "shared" }, + "options": { + "env": { + "PATH": "${userHome}/.platformio/penv/Scripts;${userHome}/.platformio/penv;${env:PATH}", + "PYTHONPATH": "" + } + } + }, + { + "label": "Build ModernESP_S3", + "type": "shell", + "command": "pio run -e esp32s3", + "problemMatcher": [], + "presentation": { "reveal": "always", "panel": "shared" }, + "options": { + "env": { + "PATH": "${userHome}/.platformio/penv/Scripts;${userHome}/.platformio/penv;${env:PATH}", + "PYTHONPATH": "" + } + } + }, + { + "label": "Build ClassicESP", + "type": "shell", + "command": "pio run -e esp32 -e esp32-eth01 -e esp8266", + "problemMatcher": [], + "presentation": { "reveal": "always", "panel": "shared" }, + "options": { + "env": { + "PATH": "${userHome}/.platformio/penv/Scripts;${userHome}/.platformio/penv;${env:PATH}", + "PYTHONPATH": "" + } + } + }, + { + "label": "Build CustomBoards", + "type": "shell", + "command": "pio run -e esp32-GLEDOPTO_GL_C_616WL -e esp32-GLEDOPTO_GL_C_615WL -e esp32-DOMRAEM_WLE_ADM -e esp32-IOTORERO_ETHERNET", + "problemMatcher": [], + "presentation": { "reveal": "always", "panel": "shared" }, + "options": { + "env": { + "PATH": "${userHome}/.platformio/penv/Scripts;${userHome}/.platformio/penv;${env:PATH}", + "PYTHONPATH": "" + } + } + }, + { + "label": "Build Pico", "type": "shell", "command": "pio run -e pico -e pico2", "problemMatcher": [], @@ -43,9 +95,13 @@ { "label": "Build ALL", "dependsOn": [ - "Build ESP Core 3.x", - "Build ESP Core 2.x", - "Build RP2040" + "Build ClassicESP", + "Build ModernESP_C2", + "Build ModernESP_C3_C5_C6", + "Build ModernESP_S2", + "Build ModernESP_S3", + "Build CustomBoards", + "Build Pico" ], "dependsOrder": "sequence", "group": { @@ -55,4 +111,4 @@ "presentation": { "reveal": "always", "panel": "shared" } } ] -} +} \ No newline at end of file diff --git a/3RD_PARTY_LICENSES b/3RD_PARTY_LICENSES index 3fa15a8..d0a3cfc 100644 --- a/3RD_PARTY_LICENSES +++ b/3RD_PARTY_LICENSES @@ -1,421 +1,125 @@ -============================================== - HyperSerialPico -============================================== +# Third-Party Licenses -MIT License +This document contains a list of third-party software components, libraries, and frameworks used in the **Hyperk** application ecosystem, along with their respective licenses. -Copyright (c) 2023-2026 awawa-dev +For the license governing the main open-source application code, please refer to the `LICENSE` file located in the root directory of the repository. -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. +## Summary of Dependencies -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. +| Component / Library | License | Author / Maintainer | Used in Environments | +| :--- | :--- | :--- | :--- | +| **Hyperk Backend (`libbackend.a`)** | Proprietary | awawa-dev | All | +| **HyperSerialEsp8266** | MIT License | awawa-dev | ESP8266 | +| **HyperSerialESP32** | MIT License | awawa-dev | ESP32 | +| **HyperSerialPico** | MIT License | awawa-dev | Raspberry Pi Pico (W) | +| **ESPAsyncWebServer** | Apache License 2.0 | ESP32Async / Me-No-Dev | All | +| **ArduinoJson** | MIT License | Benoit Blanchon | All | +| **Embedded Template Library (etl)** | MIT License | John Wellbelove | All | +| **esp-led-strip-async** (Fork) | Apache License 2.0 | awawa-dev / Espressif Systems | ESP32 (Modern-ESP) | +| **Pico.css** | MIT License | Pico.css Contributors | All | +| **Arduino Core for ESP32** | LGPL v2.1 | Espressif Systems / pioarduino | ESP32 | +| **Arduino Core for ESP8266** | LGPL v2.1 | ESP8266 Community | ESP8266 | +| **Arduino Core for RP2040/RP2350** | LGPL v2.1 | Earle F. Philhower, III | Raspberry Pi Pico (W) | -============================================== - HyperSerialEsp8266 -============================================== +--- -MIT License +## Detailed License Information -Copyright (c) 2020-2026 awawa-dev +### 1. Hyperk Backend (`libbackend.a`) -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: +* **License:** Proprietary +* **Copyright:** Copyright (c) 2026 awawa-dev +* **Link:** https://github.com/awawa-dev/Hyperk-Backend -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +> **Notice:** The precompiled library `libbackend.a` contains proprietary software distributed in binary form only. Usage, redistribution, and integration are governed by the license provided with the Hyperk Backend distribution. -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. +### 2. HyperSerialEsp8266 -============================================== - ESP32Async/ESPAsyncWebServer -============================================== +* **License:** MIT License +* **Copyright:** Copyright (c) 2026 awawa-dev +* **Link:** https://github.com/awawa-dev/HyperSerialEsp8266 - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 +### 3. HyperSerialESP32 - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. +* **License:** MIT License +* **Copyright:** Copyright (c) 2026 awawa-dev +* **Link:** https://github.com/awawa-dev/HyperSerialESP32 +### 4. HyperSerialPico - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. - -============================================== - bblanchon/ArduinoJson -============================================== - -The MIT License (MIT) ---------------------- - -Copyright © 2014-2025, Benoit BLANCHON - -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: +* **License:** MIT License +* **Copyright:** Copyright (c) 2026 awawa-dev +* **Link:** https://github.com/awawa-dev/HyperSerialPico -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. +### 5. ESPAsyncWebServer -============================================== - makuna/NeoPixelBus -============================================== +* **License:** Apache License 2.0 +* **Copyright:** Copyright (c) Me-No-Dev and contributors +* **Link:** https://github.com/ESP32Async/ESPAsyncWebServer -GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 +### 6. ArduinoJson - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. +* **License:** MIT License +* **Copyright:** Copyright (c) Benoit Blanchon +* **Link:** https://github.com/bblanchon/ArduinoJson +### 7. Embedded Template Library (etl) - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. +* **License:** MIT License +* **Copyright:** Copyright (c) John Wellbelove +* **Link:** https://github.com/ETLCPP/etl - 0. Additional Definitions. +### 8. esp-led-strip-async (Asynchronous Fork) - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. +* **License:** Apache License 2.0 +* **Copyright:** Copyright (c) Espressif Systems and contributors +* **Additional Modifications:** Copyright (c) 2026 awawa-dev +* **Link:** https://github.com/awawa-dev/esp-led-strip-async - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. +> This component is based on Espressif's original implementation and contains modifications maintained by awawa-dev. - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". +### 9. Pico.css - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. +* **License:** MIT License +* **Copyright:** Copyright (c) Pico.css Contributors +* **Link:** https://github.com/picocss/pico - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. +### 10. Arduino Frameworks & Toolchains - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. +* **License:** GNU Lesser General Public License v2.1 (LGPL v2.1) and Apache License 2.0 (ESP-IDF components) +* **Copyright:** Respective authors and contributors +* **Links:** + * **ESP32 Core:** https://github.com/espressif/arduino-esp32 + * **ESP32 PlatformIO Fork:** https://github.com/pioarduino/platform-espressif32 + * **ESP8266 Core:** https://github.com/esp8266/Arduino + * **RP2040/RP2350 Core:** https://github.com/earlephilhower/arduino-pico - 2. Conveying Modified Versions. +--- - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. +## License Notices - 4. Combined Works. +The software components listed above are distributed under their respective licenses. - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: +Copies of the applicable license texts should be included with Hyperk distributions where required by the original license terms. - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. +--- - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. +## MIT License (Summary) - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. - -============================================== - picocss/pico -============================================== - -MIT License - -Copyright (c) 2019-2024 Pico - -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: +Permission is hereby granted, free of charge, to any person obtaining a copy of the 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. +--- + +## Apache License 2.0 (Summary) + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + +A copy of the License may be obtained at: + +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/data/calibration.js b/data/calibration.js deleted file mode 100644 index d7de61d..0000000 --- a/data/calibration.js +++ /dev/null @@ -1,32 +0,0 @@ -function setCalibration(gain, r, g, b) { - const fields = { 'calGain': gain, 'calRed': r, 'calGreen': g, 'calBlue': b }; - - for (const [name, value] of Object.entries(fields)) { - const el = document.querySelector(`input[name="${name}"]`); - if (el) { - el.value = value; - } - } -} - -function toggleCalibration() { - const ledTypeSelect = document.getElementById('ledType'); - const calSection = document.getElementById('whiteCalibration'); - - calSection.style.display = (ledTypeSelect.value === "1") ? "block" : "none"; -} - -function setupCalibration(){ - const ledTypeSelect = document.getElementById('ledType'); - ledTypeSelect.addEventListener('change', toggleCalibration); - - document.getElementById('cal-cold')?.addEventListener('click', () => { - setCalibration(255, 160, 160, 160); - }); - - document.getElementById('cal-neutral')?.addEventListener('click', () => { - setCalibration(255, 176, 176, 112); - }); - - toggleCalibration(); -}; diff --git a/data/css/settings.css b/data/css/settings.css deleted file mode 100644 index cac46ae..0000000 --- a/data/css/settings.css +++ /dev/null @@ -1,165 +0,0 @@ -option[value="CUSTOM"] { - font-weight: bold; -} - -.form-error-msg { - color: var(--pico-color-danger, red); - display: block; - margin-top: 0.25rem; - font-size: 0.875rem; -} - -#custom_ssid_wrapper input.error-active { - border-color: var(--pico-color-danger, red) !important; - box-shadow: 0 0 0 0.125rem rgba(255,0,0,0.15); -} - -#custom_ssid_wrapper input.error-active:focus { - box-shadow: 0 0 0 0.25rem rgba(255,0,0,0.25); -} - -.form-error-msg ~ input:focus { - box-shadow: 0 0 0 0.25rem rgba(255,0,0,0.25); -} - -#toast { - position: fixed; - top: 20px; - right: 20px; - left: 20px; - width: auto; - max-width: 380px; - margin-left: auto; - - z-index: 2000; - padding: 0 !important; - border: 1px solid #43a047 !important; - overflow: hidden; - background: white; - opacity: 0; - visibility: hidden; - transition: opacity 0.3s ease-in-out, visibility 0.3s; -} - -#toast.show { - opacity: 1; - visibility: visible !important; -} - -#toast header { - background-color: #43a047 !important; - color: white !important; - padding: 10px 15px !important; - margin: 0 !important; - border: none !important; - border-radius: 0 !important; -} - -.toast-body { - padding: 20px 15px; - display: flex; - align-items: center; - gap: 15px; - color: #333; /* Ciemny tekst na białym tle */ -} - -.check-icon { - color: #43a047; - font-size: 1.5rem; - font-weight: bold; -} - -input::-webkit-outer-spin-button, -input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -input[type=number] { - -moz-appearance: textfield; -} - -hr.section-divider { - display: block; - height: 1px; - border: none; - margin-top: 0; - margin-bottom: 20px; - opacity: 1; - background: linear-gradient( - 90deg, - transparent, - #444 12%, - #444 88%, - transparent - ); -} - -details > div { - padding-top: 1rem; -} - -details summary[role="button"] { - background-color: #6a2525; - border-color: #7a2525; - color: #F0F1F3; -} - -details summary[role="button"]:hover, -details summary[role="button"]:focus { - background-color: #7a2525; - border-color: #8a2525; - color: #F5F5F8 -} - -.ota-footer { - text-align: right; - margin-top: 1.2rem; - padding-bottom: 0.5rem; -} - -.ota-footer a { - color: var(--pico-muted-color); - text-decoration: none; - font-size: 0.85rem; - opacity: 0.6; - transition: opacity 0.2s ease; -} - -#btn-switch-ap { - background-color: #eab308; - border-color: #eab308; - 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/css/style.css b/data/css/style.css deleted file mode 100644 index 3a1fd47..0000000 --- a/data/css/style.css +++ /dev/null @@ -1,15 +0,0 @@ -.logo-h { - display: inline-block; - width: 24px; - height: 24px; - background: url("/img/hyperk.png") no-repeat center; - background-size: contain; - vertical-align: middle; -} - -.yperk { - color: #E78080; -} -.perk { - color: #E70000; -} diff --git a/data/gpio.js b/data/gpio.js deleted file mode 100644 index a2aea53..0000000 --- a/data/gpio.js +++ /dev/null @@ -1,173 +0,0 @@ -function setupPinValidator() { - const hardwareLimits = { - "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: {13:14} }, - "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} }, - "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'), addSegBtn: document.getElementById('addSegmentBtn'), segContainer: document.getElementById('segmentContainer') }; - - if (!els.type || !els.addSegBtn || !els.segContainer) { - console.warn("LED Validator: Missing required DOM elements (ledType/addSegBtn/segContainer)"); - return; - } - - const setField = (wrapper, name, opts) => { - const old = wrapper.querySelector(`select[name="${name}"]`); - if (!old) return null; - - const isSel = (opts != null); - const signature = "sig_" + JSON.stringify(opts); - - if ((old.tagName === 'SELECT') === isSel && old.dataset.sig === signature) return old; - - const wasFocused = (document.activeElement === old); - - const el = isSel ? document.createElement('select') : Object.assign(document.createElement('input'), { - type: 'number', min: 0, max: (arch.includes('8266') ? 16 : 48), step: '1' - }); - - el.name = name; el.id = old.id; el.required = true; - el.dataset.sig = signature; - - if (isSel) { - opts.forEach(p => el.add(new Option(`GPIO ${p}`, p))); - el.value = opts.includes(parseInt(old.value)) ? old.value : opts[0]; - } else { - el.value = old.value || 0; - } - - old.replaceWith(el); - - if (wasFocused) el.focus(); - return el; - }; - - 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)); - - els.segContainer.innerHTML = ''; - - 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(); - }; - - 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 = setupSegments; - setupSegments(); -}; diff --git a/data/index.html b/data/index.html deleted file mode 100644 index f56d188..0000000 --- a/data/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - Hyperk - - - - - - - -
-

Welcome to Hyperk Controller

-

Use the menu above to control LEDs, change settings or view system information.

-
- - \ No newline at end of file diff --git a/data/network.js b/data/network.js deleted file mode 100644 index c99b7be..0000000 --- a/data/network.js +++ /dev/null @@ -1,52 +0,0 @@ -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/ota.js b/data/ota.js deleted file mode 100644 index 9341add..0000000 --- a/data/ota.js +++ /dev/null @@ -1,216 +0,0 @@ -// ota.js -let otaFirmwareUrl = ""; -let otaSelectedFirmware = ""; -let localV = ""; -let remoteV = ""; -let updateCase = null; -let isUpdating = false; -let resolveConfirm; - -function customConfirm(msg) { - const modal = document.getElementById('confirm-modal'); - document.getElementById('confirm-msg').innerText = msg; - modal.showModal(); - - return new Promise((resolve) => { - resolveConfirm = resolve; - }); -} - -window.onbeforeunload = function() { - if (isUpdating) return "Update in progress. Do not close this page!"; -}; - -function closeConfirm(result) { - const modal = document.getElementById('confirm-modal'); - modal.close(); - resolveConfirm(result); -} - -async function checkFirmwareUpdates() { - const statusArea = document.getElementById('ota_status_area'); - const statusText = document.getElementById('ota_status_text'); - const installBtn = document.getElementById('install_update_btn'); - const progress = document.getElementById('ota_progress'); - const channel = document.getElementById('ota_channel').value; - - if (!cfgBoardArchitecture) { - statusText.innerText = "❌ Error: Unknown build architecture. Cannot check for updates."; - return; - } - - statusArea.style.display = 'block'; - progress.style.display = 'none'; - statusText.innerText = "Fetching releases..."; - installBtn.style.display = 'none'; - - let releases; - try { - let res = await fetch("https://hyperk-github-releases-api-proxy.hyperhdr.workers.dev/"); - if (!res.ok) throw new Error(`Worker status: ${res.status}`); - console.log("✅ Data from Worker Proxy"); - releases = await res.json(); - } - catch (err) { - console.warn("⚠️ Worker failed, falling back to GitHub API...", err); - try { - let res = await fetch("https://api.github.com/repos/awawa-dev/Hyperk/releases"); - if (!res.ok) throw new Error(`GitHub API status: ${res.status}`); - releases = await res.json(); - } catch (e) { - statusText.innerText = "❌ Failed to fetch releases."; - return; - } - } - - const allowPreRelease = (channel === 'testing'); - const validReleases = releases.filter(r => allowPreRelease || !r.prerelease); - - if (validReleases.length === 0) { - statusText.innerText = "No suitable releases found on this channel."; - return; - } - - const latest = validReleases[0]; - - let archSuffix = cfgBoardArchitecture.toLowerCase(); - - const expectedSuffix = `_${archSuffix}.bin`; - const asset = latest.assets.find(a => a.name.startsWith("OTA_") && a.name.endsWith(expectedSuffix)); - - if (!asset) { - statusText.innerHTML = `❌ No firmware for ${cfgDeviceArchitecture} (${archSuffix}) in release ${latest.tag_name}.
Looking for: *${expectedSuffix}`; - return; - } else { - otaSelectedFirmware = asset.name; - console.log(`ℹ️ Using firmware: ${otaSelectedFirmware}`); - } - - function compareVersions(v1, v2) { - if (v1 === v2 || !v1 || !v2 || v1 === "0.0.0" || v2 === "0.0.0") return 0; - - const parse = v => { - const [ver, suf] = v.split('-', 2); - return { n: ver.split('.').map(Number), s: suf || "" }; - }; - - const d1 = parse(v1), d2 = parse(v2); - - for (let i = 0; i < 3; i++) { - const n1 = d1.n[i] || 0, n2 = d2.n[i] || 0; - if (n1 !== n2) return n1 > n2 ? 1 : -1; - } - - if (!d1.s || !d2.s) return d1.s.localeCompare(d2.s) * -1; - - return d1.s.localeCompare(d2.s, undefined, { numeric: true }); - } - - localV = typeof cfgDeviceVersion !== 'undefined' ? cfgDeviceVersion : "0.0.0"; - remoteV = latest.tag_name; - updateCase = compareVersions(localV, remoteV); - - if (updateCase === 0) { - statusText.innerHTML = `✔️ Up to date (${remoteV})`; - installBtn.style.display = 'none'; - statusArea.style.borderColor = "var(--pico-form-element-border-color)"; // Powrót do standardu - return; - } - - statusArea.style.display = 'block'; - statusArea.style.borderColor = "#eab308"; - - installBtn.style.display = 'block'; - installBtn.style.backgroundColor = "#eab308"; - installBtn.style.borderColor = "#eab308"; - installBtn.style.color = "#111"; - - if (updateCase > 0) { - statusText.innerHTML = `⚠️ Available: ${remoteV}
Warning: This is a downgrade!`; - installBtn.innerText = "Downgrade Firmware"; - } else { - statusText.innerHTML = `🆕 New update available:
${remoteV}`; - installBtn.innerText = "Install Update"; - } - - const proxyBase = "https://hyperhdr-github-proxy.hyperhdr.workers.dev/?url="; - otaFirmwareUrl = proxyBase + encodeURIComponent(asset.browser_download_url); -}; - -async function startOtaUpdate() { - if (!otaFirmwareUrl || !localV || !remoteV || updateCase === null) return; - - const confirmed = await customConfirm(`Are you sure to ${(updateCase === 1) ? "downgrade" : "upgrade"} Hyperk ${localV} to: ${remoteV}?`); - if (!confirmed) return; - - isUpdating = true; - - const statusText = document.getElementById('ota_status_text'); - const progress = document.getElementById('ota_progress'); - const installBtn = document.getElementById('install_update_btn'); - const checkBtn = document.getElementById('check_update_btn'); - const saveConfigBtn = document.querySelector('form[action="/save_config"] button[type="submit"]'); - - if (saveConfigBtn) saveConfigBtn.disabled = true; - - installBtn.style.display = 'none'; - checkBtn.disabled = true; - progress.style.display = 'block'; - progress.value = 0; - statusText.innerText = "Downloading firmware..."; - - try { - const res = await fetch(otaFirmwareUrl); - if (!res.ok) throw new Error(`Proxy error: ${res.status} ${res.statusText}`); - const blob = await res.blob(); - console.log(`ℹ️ Firmware size: ${blob.size} "bytes`); - - statusText.innerText = "Uploading to device... DO NOT REBOOT."; - - const formData = new FormData(); - formData.append("update", blob, "firmware.bin"); - - const xhr = new XMLHttpRequest(); - xhr.open("POST", "/ota", true); - xhr.setRequestHeader("hyperk-ota-firmware-size", blob.size); - xhr.setRequestHeader("hyperk-ota-firmware-name", otaSelectedFirmware); - - xhr.upload.onprogress = (e) => { - if (e.lengthComputable) { - const percent = Math.round((e.loaded / e.total) * 100); - progress.value = percent; - statusText.innerText = `Flashing: ${percent}%`; - } - }; - - xhr.onload = () => { - isUpdating = false; - if (xhr.status === 200) { - statusText.innerText = "✅ Update successful! Rebooting..."; - showToast(true); - } else { - statusText.innerText = `❌ Flash failed: ${xhr.responseText || xhr.statusText}`; - progress.style.display = 'none'; - } - checkBtn.disabled = false; - if (saveConfigBtn) saveConfigBtn.disabled = false; - }; - - xhr.onerror = () => { - isUpdating = false; - statusText.innerText = "❌ Network error during upload. Device might have rebooted unexpectedly."; - progress.style.display = 'none'; - checkBtn.disabled = false; - if (saveConfigBtn) saveConfigBtn.disabled = false; - }; - - xhr.send(formData); - - } catch (err) { - isUpdating = false; - statusText.innerText = `❌ Error: ${err.message}`; - progress.style.display = 'none'; - checkBtn.disabled = false; - if (saveConfigBtn) saveConfigBtn.disabled = false; - } -}; diff --git a/data/settings.html b/data/settings.html deleted file mode 100644 index c175f24..0000000 --- a/data/settings.html +++ /dev/null @@ -1,449 +0,0 @@ - - - - - - Settings - Hyperk - - - - - - - - - -
-

Settings

- - -
- -
- - -
-
- LED Hardware -
- - - - -
-
- - - -
- - -
- - -
-
- -
- Light Preset -
- - - - - -
-
- -
- Network -
- -
-
- -
-
- -
-
- - - - -
-
- - -
-

⚠️ OTA Firmware Flash Procedure

-

Are you sure you want to change version?

-
- - -
-
-
- - - \ No newline at end of file diff --git a/data/stats.html b/data/stats.html deleted file mode 100644 index f1860bc..0000000 --- a/data/stats.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - Stats - Hyperk - - - - - - - - -
-

System Statistics

-
- - - - - - - - - - - - - -
VersionLoading...
Device NameLoading...
IP AddressLoading...
Signal StrengthLoading...
UptimeLoading...
Free HeapLoading...
ArchitectureLoading...
LED outputLoading...
Skipped framesLoading...
- -

Data updates automatically every 5 seconds.

-
- - - - \ No newline at end of file diff --git a/data/wifi.js b/data/wifi.js deleted file mode 100644 index 02c1fcb..0000000 --- a/data/wifi.js +++ /dev/null @@ -1,103 +0,0 @@ -document.querySelector('form[action="/save_wifi"]').addEventListener('submit', e => { - const select = document.getElementById('ssid_select'); - const customInput = document.getElementById('ssid_custom'); - - if (select.value === 'CUSTOM' && !customInput.value.trim()) { - e.preventDefault(); - - let msg = document.getElementById('wifi-error-msg'); - if (!msg) { - msg = document.createElement('small'); - msg.id = 'wifi-error-msg'; - msg.className = 'form-error-msg'; - const container = document.getElementById('custom_ssid_wrapper'); - if (container) { - container.appendChild(msg); - } - } - msg.textContent = 'Custom SSID is required'; - customInput.classList.add('error-active'); - customInput.focus(); - - setTimeout(() => { - if (msg) msg.textContent = ''; - customInput.classList.remove('error-active'); - }, 5000); - - return; - } - - const f = e.target, btn = f.querySelector('button'); - btn.setAttribute('aria-busy', 'true'); - btn.disabled = true; -}); - -async function scanWifi() { - const select = document.getElementById('ssid_select'); - const customDiv = document.getElementById('custom_ssid_wrapper'); - - try { - const res = await fetch('/api/wifi_scan?t=' + Date.now()); - - if (res.status === 200) { - const nets = await res.json(); - - let html = ''; - - if (Array.isArray(nets)){ - nets.sort((a, b) => b.rssi - a.rssi).forEach(n => { - const q = n.rssi > -50 ? 'excellent' : - n.rssi > -60 ? 'very good' : - n.rssi > -70 ? 'good' : - n.rssi > -80 ? 'fair' : - 'poor'; - html += ``; - }); - } - - html += ''; - - const prev = select.value; - select.innerHTML = html; - - if (prev) select.value = prev; - - scanInterval = 12000; - } - else if (res.status === 202) { - if (select.options[0]) { - select.options[0].text = "Scanning..."; - } - scanInterval = 4000; - } - } - catch (e) { - if (select.options[0]) { - select.options[0].text = "Connection lost"; - } - } - - setTimeout(scanWifi, scanInterval); -}; - - -function setupWifi() { - const select = document.getElementById('ssid_select'); - const customDiv = document.getElementById('custom_ssid_wrapper'); - const customInput = document.getElementById('ssid_custom'); - - select.addEventListener('change', () => { - if (select.value === 'CUSTOM') { - customDiv.style.display = 'block'; - setTimeout(() => customInput.focus(), 100); - customInput.required = true; - } - else { - customDiv.style.display = 'none'; - customInput.required = false; - customInput.value = ''; - } - }); - - scanWifi(); -}; diff --git a/effects/Solid/effect.h b/effects/Solid/effect.h new file mode 100644 index 0000000..f7f5ed2 --- /dev/null +++ b/effects/Solid/effect.h @@ -0,0 +1,27 @@ +#pragma once +#include "effects.h" +#include + +/** +* @brief Always use EFFECT_NAMESPACE for your custom effect, the effect's name is it's folder name infact +*/ +namespace EFFECT_NAMESPACE { + /** + * @brief Performs one cycle of the effect. + * @return false if the effect is finished, true otherwise. + */ + inline bool run(const EffectConfig& effectConfig, EffectSetup &effectSetup, EffectRun& effectRun) { + // If it's the first call, just do the setup and skip the rest + if (!effectRun.deltaMicroseconds) { + effectSetup.runIntervalMicroseconds = 250000; // 4Hz, every 250000 microseconds = 0.25 second + effectSetup.useGamma = false; + return true; + } + + // process the effect + for(int i = 0; i < effectConfig.numLeds; i++){ + effectRun.setLedColorRgb(i, effectRun.currentStaticColor.red, effectRun.currentStaticColor.green, effectRun.currentStaticColor.blue); + } + return true; + } +}; diff --git a/include/calibration.h b/include/calibration.h deleted file mode 100644 index e990c15..0000000 --- a/include/calibration.h +++ /dev/null @@ -1,11 +0,0 @@ -// File: include/calibration.h -#pragma once - -struct ColorRgbw { - uint8_t R, G, B, W; -}; - -void setParamsAndPrepareCalibration(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue); -void deleteCalibration(); -ColorRgbw rgb2rgbw(uint8_t r, uint8_t g, uint8_t b); - diff --git a/include/config.h b/include/config.h deleted file mode 100644 index 8656ed7..0000000 --- a/include/config.h +++ /dev/null @@ -1,81 +0,0 @@ -// File: include/config.h -#pragma once -#include -#include -#include -#include "logger.h" - -#define CONFIG_FILE "/config.json" - -#ifndef POWER_RELAY_GPIO - #define POWER_RELAY_GPIO -1 -#endif - -#ifdef POWER_RELAY_INVERT - #define POWER_RELAY_INVERT_BOOL true -#else - #define POWER_RELAY_INVERT_BOOL false -#endif - -enum class LedType : uint8_t { - WS2812 = 0, - SK6812 = 1, - APA102 = 2 -}; - -struct LedConfig { - struct Segment { - uint8_t data; - uint8_t clock; - uint16_t startIndex; - - bool operator==(const Segment& other) const { - return data == other.data && clock == other.clock && startIndex == other.startIndex; - } - - bool operator!=(const Segment& other) const { - return !(*this == other); - } - }; - - struct Relay { - int8_t gpio; - bool inverted; - }; - - LedType type = LedType::WS2812; - std::vector segments = {{2, 4, 0}}; - Relay relay = {POWER_RELAY_GPIO, POWER_RELAY_INVERT_BOOL}; - uint16_t numLeds = 16; - uint8_t brightness = 255; - uint8_t r = 196, g = 32, b = 8; - uint8_t effect = 0; - - struct Calibration { - uint8_t gain = 0xFF; - uint8_t red = 0xA0; - uint8_t green = 0xA0; - uint8_t blue = 0xA0; - } calibration; - - void deserializeSegments(const JsonArray& jsonArray); - bool deserializeSegments(const String& rawValues); - void serializeSegments(JsonArray& jsonArray) const; -}; - -struct AppConfig { - struct WifiConfig { - String ssid; - String password; - } wifi; - LedConfig led; - String deviceName = "hyperk"; - String extraMdnsTag = "wled"; -}; - -namespace Config { - extern const AppConfig& cfg; - - bool loadConfig(); - bool saveConfig(const AppConfig &cfg); -}; diff --git a/include/effects.h b/include/effects.h new file mode 100644 index 0000000..cb0dcb9 --- /dev/null +++ b/include/effects.h @@ -0,0 +1,94 @@ +#pragma once +#include +#include +#include +#include +#include "etl/vector.h" + +struct EffectConfig{ + /** + * @brief RGB or RGBW led strip. + */ + bool isRgbw; + + /** + * @brief Total number of LEDs. + */ + uint16_t numLeds; + + /** + * @brief Last segment contains numLeds value. Useful for segment [begin_current, begin_next) segment iteration. + */ + etl::vector segmentStarts; +}; + +struct EffectSetup { + /** + * @brief Use gamma 2.2 or not for the effect. Take into the account during next invoke. + */ + bool useGamma; + + /** + * @brief Delay before next invoke in microseconds. 1s = 1000 * 1000 microseconds. + */ + unsigned long runIntervalMicroseconds; + + using UserDataDeleterFn = void(*)(void*); + /** + * @brief General purpose user data container (POD). Can store information between Effect::run invokes. + */ + std::unique_ptr userData{nullptr, [](void*){}}; + + /** + * @brief Assign general purpose user data container (POD). The container is automaticly deleted alongside the effect. + */ + template + void assign(T* rawPointerToUserData) { + static_assert(std::is_standard_layout::value, "ERROR: user data structure MUST be a POD (Standard Layout) type!"); + static_assert(std::is_trivially_destructible::value, "ERROR: user data structure cannot have complex destructors (e.g., no std::string inside)!"); + userData = std::unique_ptr(rawPointerToUserData, [](void* p) { delete static_cast(p); }); + } + + /** + * @brief Optional effect starting params. + */ + std::unique_ptr optionalEffectStartingParams; +}; + +struct EffectRun { + /** + * @brief Time (microseconds) since the last time called or 0 when first time invoked, 1s = 1000 * 1000 microseconds. + */ + unsigned long deltaMicroseconds; + + using setLedColorRgbFunc = void(*)(uint16_t, uint8_t, uint8_t, uint8_t); + /** + * @brief Use this to set LEDs to selected color (index, red, greeb, blue). On RGBW LED strips the white channel is calculated using calibration settings. + */ + setLedColorRgbFunc setLedColorRgb; + using setLedColorRgbWFunc = void(*)(uint16_t, uint8_t, uint8_t, uint8_t, uint8_t); + /** + * @brief Use this to set LEDs to selected color (index, red, greeb, blue, white) + */ + setLedColorRgbWFunc setLedColorRgbW; + using getLedColorRgbWFunc = void(*)(uint16_t, uint8_t&, uint8_t&, uint8_t&, uint8_t&); + /** + * @brief Use this to get selected LEDs color (index, red, greeb, blue, white) + */ + getLedColorRgbWFunc getLedColorRgbW; + + /** + * @brief Current static color. + */ + struct { + uint8_t red, green, blue; + } currentStaticColor; +}; + +//////////////////////////////////////////////////////////////////////////////////////////// + +namespace EffectUtils{ + void fast_hsv2rgb(uint8_t h, uint8_t s, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b); + void fast_rgb2hsv(uint8_t r, uint8_t g, uint8_t b, uint8_t *h, uint8_t *s, uint8_t *v); +}; + diff --git a/include/esp32/tusb_config.h b/include/esp32/tusb_config.h new file mode 100644 index 0000000..15e34c5 --- /dev/null +++ b/include/esp32/tusb_config.h @@ -0,0 +1,234 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org), + * Additions Copyright (c) 2020, Espressif Systems (Shanghai) PTE LTD + * + * 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. + * + */ + +#pragma once +#include "tusb_option.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* */ +/* KCONFIG */ +/* */ + +#ifndef CONFIG_TINYUSB_CDC_ENABLED +# define CONFIG_TINYUSB_CDC_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_MSC_ENABLED +# define CONFIG_TINYUSB_MSC_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_HID_ENABLED +# define CONFIG_TINYUSB_HID_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_MIDI_ENABLED +# define CONFIG_TINYUSB_MIDI_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_AUDIO_ENABLED +# define CONFIG_TINYUSB_AUDIO_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_VIDEO_ENABLED +# define CONFIG_TINYUSB_VIDEO_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_CUSTOM_CLASS_ENABLED +# define CONFIG_TINYUSB_CUSTOM_CLASS_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_DFU_RT_ENABLED +# define CONFIG_TINYUSB_DFU_RT_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_DFU_ENABLED +# define CONFIG_TINYUSB_DFU_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_VENDOR_ENABLED +# define CONFIG_TINYUSB_VENDOR_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_NCM_ENABLED +# define CONFIG_TINYUSB_NCM_ENABLED 0 +#endif + +#if CONFIG_TINYUSB_ENABLED +# define CFG_TUD_ENABLED 1 +#endif + +/* */ +/* COMMON CONFIGURATION */ +/* */ +#ifndef CFG_TUSB_MCU +#define CFG_TUSB_MCU OPT_MCU_ESP32S2 +#endif +#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE +#define CFG_TUSB_OS OPT_OS_FREERTOS +#define BOARD_TUD_RHPORT 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +# define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#if CONFIG_IDF_TARGET_ESP32P4 +# define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(64) +#else +# define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) +#endif +#endif + +#if CONFIG_IDF_TARGET_ESP32P4 +#define CFG_TUD_MAX_SPEED OPT_MODE_HIGH_SPEED +#else +#define CFG_TUD_MAX_SPEED OPT_MODE_FULL_SPEED +#endif + +#define BOARD_TUD_MAX_SPEED CFG_TUD_MAX_SPEED + +/* */ +/* DEVICE CONFIGURATION */ +/* */ + +#define CFG_TUD_MAINTASK_SIZE 2048 +#define CFG_TUD_ENDPOINT_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_ENDPOINT0_SIZE 64 + +// Enabled Drivers +#ifdef CONFIG_TINYUSB_CDC_MAX_PORTS +#define CFG_TUD_CDC CONFIG_TINYUSB_CDC_MAX_PORTS +#else +#define CFG_TUD_CDC 0 +#endif +#define CFG_TUD_MSC CONFIG_TINYUSB_MSC_ENABLED +#define CFG_TUD_HID CONFIG_TINYUSB_HID_ENABLED +#define CFG_TUD_MIDI 0 +#define CFG_TUD_AUDIO 0 +#define CFG_TUD_VIDEO 0 +#define CFG_TUD_CUSTOM_CLASS 0 +#define CFG_TUD_DFU_RUNTIME 0 +#define CFG_TUD_DFU 0 +#define CFG_TUD_VENDOR 0 +#define CFG_TUD_NCM 0 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE 512 +#define CFG_TUD_CDC_TX_BUFSIZE 64 + +// MSC Buffer size of Device Mass storage: +#define CFG_TUD_MSC_BUFSIZE 512 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_BUFSIZE CONFIG_TINYUSB_HID_BUFSIZE + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE CONFIG_TINYUSB_MIDI_RX_BUFSIZE +#define CFG_TUD_MIDI_TX_BUFSIZE CONFIG_TINYUSB_MIDI_TX_BUFSIZE + +// The number of video streaming interfaces and endpoint size +#define CFG_TUD_VIDEO_STREAMING CONFIG_TINYUSB_VIDEO_STREAMING_IFS +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE CONFIG_TINYUSB_VIDEO_STREAMING_BUFSIZE + +// DFU buffer size +#define CFG_TUD_DFU_XFER_BUFSIZE CONFIG_TINYUSB_DFU_BUFSIZE + +// VENDOR FIFO size of TX and RX +#define CFG_TUD_VENDOR_RX_BUFSIZE CONFIG_TINYUSB_VENDOR_RX_BUFSIZE +#define CFG_TUD_VENDOR_TX_BUFSIZE CONFIG_TINYUSB_VENDOR_TX_BUFSIZE + +// AUDIO configuration +#define CFG_TUD_AUDIO_ENABLE_EP_IN 1 // TX +#define CFG_TUD_AUDIO_ENABLE_EP_OUT 1 // RX +#define CFG_TUD_AUDIO_MAX_N_CHANNELS 2 // Stereo +#define CFG_TUD_AUDIO_MAX_N_BYTES_PER_SAMPLE 4 // 24bit/32bit + +#if CONFIG_IDF_TARGET_ESP32P4 +#define CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP 1 // Allow volume controlled by on-baord button (UAC2) +#define CFG_TUD_AUDIO_MAX_SAMPLE_RATE 192000 +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX TUD_AUDIO_EP_SIZE(true, CFG_TUD_AUDIO_MAX_SAMPLE_RATE, CFG_TUD_AUDIO_MAX_N_BYTES_PER_SAMPLE, CFG_TUD_AUDIO_MAX_N_CHANNELS) +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ (32 * CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX) +#else +#define CFG_TUD_AUDIO_MAX_SAMPLE_RATE 96000 +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX TUD_AUDIO_EP_SIZE(false, CFG_TUD_AUDIO_MAX_SAMPLE_RATE, CFG_TUD_AUDIO_MAX_N_BYTES_PER_SAMPLE, CFG_TUD_AUDIO_MAX_N_CHANNELS) +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ (4 * CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX) +#endif + +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ + +/* */ +/* HOST CONFIGURATION */ +/* */ + +#define CFG_TUH_ENABLED 0 +#define CFG_TUSB_RHPORT1_MODE OPT_MODE_HOST +#define BOARD_TUH_RHPORT 1 +#define BOARD_TUH_MAX_SPEED CFG_TUD_MAX_SPEED +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +#define CFG_TUH_HUB 2 // number of supported hubs +#define CFG_TUH_CDC 1 // CDC ACM +#define CFG_TUH_CDC_FTDI 1 // FTDI Serial. FTDI is not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC_CP210X 1 // CP210x Serial. CP210X is not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC_CH34X 1 // CH340 or CH341 Serial. CH34X is not part of CDC class, only to re-use CDC driver API +/* Max simultaneous HID interfaces (TinyUSB hid_host.c: _hidh_itf[CFG_TUH_HID]). Value 1 allows only one iface stack-wide. */ +#define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX) // typical keyboard + mouse device can have 3-4 HID interfaces +#define CFG_TUH_MSC 1 +#define CFG_TUH_MIDI 1 // MIDI host driver; midi_host.c is already compiled, this enables it +//#define CFG_TUH_VENDOR 3 + +#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1) + +//------------- HID -------------// +#define CFG_TUH_HID_EPIN_BUFSIZE 64 +#define CFG_TUH_HID_EPOUT_BUFSIZE 64 + +//------------- CDC -------------// +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM { 115200, CDC_LINE_CODING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#ifdef __cplusplus +} +#endif + +#if !CFG_TUD_CDC + #error CFG_TUD_CDC_IS_ZERO +#endif +#if CFG_TUH_ENABLED + #error HOST_ON +#endif diff --git a/include/led_bridge/double_buffer.h b/include/led_bridge/double_buffer.h deleted file mode 100644 index a635ca6..0000000 --- a/include/led_bridge/double_buffer.h +++ /dev/null @@ -1,88 +0,0 @@ -/* double_buffer.h -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ -#include -#include "logger.h" - -class DoubleBuffer { -public: - typedef uint32_t color_cell_t; - - virtual ~DoubleBuffer() { - releaseMemory(); - } - - void releaseMemory() - { - if (_backBuffer) { - heap_caps_free(_backBuffer); - _backBuffer = nullptr; - } - _ledsNumber = 0; - } - - bool init(int ledsNumber) - { - releaseMemory(); - - _ledsNumber = ledsNumber; - _backBuffer = (color_cell_t*) heap_caps_malloc(_ledsNumber * sizeof(color_cell_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); - - bool result = (_backBuffer != nullptr); - - if (!result) { - Log::debug("Cannot alloc double buffer memory"); - } - - return result; - } - - inline void getPixel(int index, uint8_t& r, uint8_t& g, uint8_t& b, uint8_t& w) const { - uint32_t val = _backBuffer[index]; - - b = val & 0xFF; - g = (val >> 8) & 0xFF; - r = (val >> 16) & 0xFF; - w = (val >> 24) & 0xFF; - } - - inline void setPixel(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { - _backBuffer[index] = ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; - } - -private: - int _ledsNumber = 0; - color_cell_t* _backBuffer = nullptr; -}; - -template -struct InternalBuffer { -}; - -template<> -struct InternalBuffer { - DoubleBuffer doubleBuffer; -}; diff --git a/include/led_bridge/multi_esp32_led_strip_bridge.h b/include/led_bridge/multi_esp32_led_strip_bridge.h deleted file mode 100644 index 89742af..0000000 --- a/include/led_bridge/multi_esp32_led_strip_bridge.h +++ /dev/null @@ -1,501 +0,0 @@ -/* multi_esp32_led_strip_bridge.h -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#pragma once - -#include "led_strip.h" -#include "driver/spi_master.h" -#include "led_bridge.h" -#include "double_buffer.h" - -#if !defined(CONFIG_IDF_TARGET_ESP32C2) - #define BOARD_HAS_RMT_SUPPORT -#endif - -#if defined(SOC_SPI_PERIPH_NUM) && (SOC_SPI_PERIPH_NUM <= 2) - #define SPI3_HOST SPI2_HOST -#endif - -template -struct multi_esp32_led_strip_bridge : public led_bridge, InternalBuffer -{ - struct ExtSegments{ - LedConfig::Segment segment; - led_strip_handle_t handle = NULL; - int ledCount = 0; - }; - std::vector _segments; - - uint16_t _totalLedsNumber = 0; - LedType _ledsType = LedType::WS2812; - - struct { - const spi_host_device_t SELECTED_SPI_HOST = SPI2_HOST; - spi_device_handle_t spi_handle = nullptr; - size_t spi_buffer_size = 0; - uint8_t* spi_led_buffer = nullptr; - } cfgSpi; - - int getLedsNumber() override - { - return _totalLedsNumber; - } - - void clearAll() override - { - if (_segments.size() || cfgSpi.spi_handle != nullptr) - { - for (int i = 0; !canRender() && i < 200; i++) { - Log::debug("-"); - delay(1); - } - - for (int i = 0; i < _totalLedsNumber; i++) { - setLedRgb(i, 0 ,0, 0); - } - executeRenderLed(); - - for (int i = 0; !canRender() && i < 200; i++) { - Log::debug("+"); - delay(1); - } - - Log::debug("leds cleared"); - } - } - - bool canRender() override - { - if (cfgSpi.spi_handle == nullptr) - for(const auto& segment : _segments) - if (!led_strip_is_rendering_done(segment.handle)) { - return false; - } - - return true; - } - - int segmentSupported() override - { - return hardwareInfo.maxSegments(); - } - - bool supportsDoubleBuffering() override { - if constexpr(DOUBLEBUFFER_SUPPORT){ - return true; - } - else { - return false; - } - } - - void executeRenderLed() override - { - if (_segments.size()) - { - if constexpr (DOUBLEBUFFER_SUPPORT) { - for(int i = 0; i < _totalLedsNumber; i++) { - uint8_t r, g, b, w; - this->doubleBuffer.getPixel(i, r, g, b, w); - internalSetLedRgbw(i, r, g, b, w); - } - } - - for(auto& segment : _segments) { - if (segment.handle){ - if (led_strip_refresh(segment.handle) != ESP_OK) { - Log::debug("led_strip_refresh failed"); - } - } - } - } - else if (cfgSpi.spi_handle) - { - spi_transaction_t t; - memset(&t, 0, sizeof(t)); - - t.length = cfgSpi.spi_buffer_size * 8; - t.tx_buffer = cfgSpi.spi_led_buffer; - - spi_device_transmit(cfgSpi.spi_handle, &t); - } - } - - void releaseDriverResources() override - { - delay(50); - - for(auto& segment : _segments) - if (segment.handle) - { - led_strip_del(segment.handle); - segment.handle = nullptr; - } - _segments.clear(); - - if (cfgSpi.spi_handle) { - spi_bus_remove_device(cfgSpi.spi_handle); - spi_bus_free(cfgSpi.SELECTED_SPI_HOST); - cfgSpi.spi_handle = nullptr; - } - - if (cfgSpi.spi_led_buffer) { - heap_caps_free(cfgSpi.spi_led_buffer); - cfgSpi.spi_led_buffer = nullptr; - cfgSpi.spi_buffer_size = 0; - } - - if constexpr (DOUBLEBUFFER_SUPPORT) { - this->doubleBuffer.releaseMemory(); - } - - delay(50); - } - - void initializeLedDriver(LedType cfgLedType, uint16_t cfgLedNumLeds, const std::vector& cfgSegments, - uint8_t calGain, uint8_t calRed, uint8_t calGreen, uint8_t calBlue) override - { - if (cfgSegments.size() < 0) return; - - if constexpr (DOUBLEBUFFER_SUPPORT) { - if (!this->doubleBuffer.init(cfgLedNumLeds)) return; - Log::debug("Enabled support for double buffering"); - } - - _totalLedsNumber = cfgLedNumLeds; - _ledsType = cfgLedType; - - if (_ledsType == LedType::SK6812) - { - setParamsAndPrepareCalibration(calGain, calRed, calGreen, calBlue); - } - - if (_ledsType == LedType::WS2812 || _ledsType == LedType::SK6812) - { - bool error = false; - SegmentCapabilities dynamicResources; - bool hasDmaLeft = hardwareInfo.rmt_has_dma; - for(int i = 0; i < cfgSegments.size() && !error; i++) { - const auto& seg = cfgSegments[i]; - - typename multi_esp32_led_strip_bridge::SegmentCapabilities::Channels channel = dynamicResources.getFree(); - - if (channel != SegmentCapabilities::Channels::NONE) { - int nextIndex = (i + 1 < cfgSegments.size()) ? cfgSegments[i + 1].startIndex : cfgLedNumLeds; - - led_strip_config_t strip_config = { - .strip_gpio_num = seg.data, - .max_leds = static_cast(std::max(nextIndex - seg.startIndex, 0)), - .led_model = (_ledsType == LedType::SK6812) ? LED_MODEL_SK6812 : LED_MODEL_WS2812, - .color_component_format = (_ledsType == LedType::SK6812) ? LED_STRIP_COLOR_COMPONENT_FMT_GRBW : LED_STRIP_COLOR_COMPONENT_FMT_GRB, - .flags = { - .invert_out = false, - } - }; - - led_strip_handle_t led_strip_handle = NULL; - - if (channel == SegmentCapabilities::Channels::SPI) { - led_strip_spi_config_t spi_config = { - .clk_src = SPI_CLK_SRC_DEFAULT, - .spi_bus = ((i == 0) ? SPI2_HOST : SPI3_HOST), - .flags = { - .with_dma = true, - } - }; - - if (led_strip_new_spi_device(&strip_config, &spi_config, &led_strip_handle) != ESP_OK) { - led_strip_handle = NULL; - Log::debug("led_strip_new_spi_device failed for interface:", spi_config.spi_bus); - } - } - #if defined(BOARD_HAS_RMT_SUPPORT) - else if (channel == SegmentCapabilities::Channels::RMT) { - led_strip_rmt_config_t rmt_config = { - .clk_src = RMT_CLK_SRC_DEFAULT, - .resolution_hz = 10 * 1000 * 1000, - .mem_block_symbols = hardwareInfo.getRmtSize(cfgSegments.size(), hasDmaLeft), - .flags = { - .with_dma = hasDmaLeft, - } - }; - - hasDmaLeft = false; - - if (led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_handle) != ESP_OK) { - led_strip_handle = NULL; - Log::debug("led_strip_new_rmt_device failed"); - } - } - #endif - - if (led_strip_handle == NULL) { - error = true; - break; - } - else { - _segments.push_back({seg, led_strip_handle, static_cast(strip_config.max_leds)}); - Log::debug("Created Neopixel segment for ", strip_config.max_leds, " LEDS at: ", seg.startIndex, ", GPIO: ", seg.data, - ", driver: ", ((channel == SegmentCapabilities::Channels::RMT) ? "RMT" : "SPI" )); - } - } - else { - Log::debug("Cannot segment at: ",i ,". Out of free hardware resources."); - } - } - - if (error) { - releaseDriverResources(); - } - } - else - { // SPI (APA102 / SK9822) - const auto& segment = cfgSegments.front(); - uint8_t cfgLedDataPin = segment.data; - uint8_t cfgLedClockPin = segment.clock; - - cfgSpi.spi_buffer_size = 4 + (cfgLedNumLeds * 4) + ((cfgLedNumLeds / 16) + 1); - - spi_bus_config_t buscfg = { - .mosi_io_num = cfgLedDataPin, - .miso_io_num = -1, - .sclk_io_num = cfgLedClockPin, - .quadwp_io_num = -1, - .quadhd_io_num = -1, - .max_transfer_sz = static_cast(cfgSpi.spi_buffer_size) - }; - - spi_device_interface_config_t devcfg = { - .mode = 0, - .clock_speed_hz = 10 * 1000 * 1000, - .spics_io_num = -1, - .queue_size = 7, - }; - - if (spi_bus_initialize(cfgSpi.SELECTED_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO) == ESP_OK) { - if (spi_bus_add_device(cfgSpi.SELECTED_SPI_HOST, &devcfg, &cfgSpi.spi_handle) == ESP_OK) { - - cfgSpi.spi_led_buffer = (uint8_t*)heap_caps_malloc(cfgSpi.spi_buffer_size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); - - if (cfgSpi.spi_led_buffer != nullptr) - { - memset(cfgSpi.spi_led_buffer, 0, cfgSpi.spi_buffer_size); - for (size_t i = 4 + (cfgLedNumLeds * 4); i < cfgSpi.spi_buffer_size; i++) { - cfgSpi.spi_led_buffer[i] = 0xFF; - } - Log::debug("Created SPI segment, GPIO: ", cfgLedDataPin, ", CLOCK: ", cfgLedClockPin); - } - else { - Log::debug("SPI: heap_caps_malloc failed"); - } - } - else { - Log::debug("SPI: spi_bus_add_device failed"); - } - } - else { - Log::debug("SPI: spi_bus_initialize failed"); - } - - if (cfgSpi.spi_led_buffer == nullptr) { - releaseDriverResources(); - } - } - } - - inline led_strip_handle_t findHandle(int& index) const { - if (index >= 0 && index < _totalLedsNumber){ - for(const auto& seg : _segments) - if (index < seg.ledCount) { - return seg.handle; - } - else { - index -= seg.ledCount; - } - } - - return nullptr; - } - - void setLedRgb(int index, uint8_t r, uint8_t g, uint8_t b) override { - if constexpr (DOUBLEBUFFER_SUPPORT) if (cfgSpi.spi_handle == nullptr) { - uint8_t w = 0; - if (_ledsType == LedType::SK6812) - { - const ColorRgbw calibrated = rgb2rgbw(r, g, b); - r = calibrated.R; g = calibrated.G; b = calibrated.B; w = calibrated.W; - } - this->doubleBuffer.setPixel(index, r, g, b, w); - return; - } - internalSetLedRgb(index, r, g, b); - } - - void setLedRgbw(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) override { - if constexpr (DOUBLEBUFFER_SUPPORT) if (cfgSpi.spi_handle == nullptr) { - this->doubleBuffer.setPixel(index, r, g, b, w); - return; - } - internalSetLedRgbw(index, r, g, b, w); - } - - inline void internalSetLedRgb(int index, uint8_t r, uint8_t g, uint8_t b) - { - if (_ledsType == LedType::SK6812) - { - if (auto handle = findHandle(index); handle) { - const ColorRgbw calibrated = rgb2rgbw(r, g, b); - led_strip_set_pixel_rgbw(handle, index, calibrated.R, calibrated.G, calibrated.B, calibrated.W); - } - } - else if (_ledsType == LedType::WS2812) - { - if (auto handle = findHandle(index); handle) { - led_strip_set_pixel(handle, index, r, g, b); - } - } - else if (_ledsType == LedType::APA102) - { - if (index >= _totalLedsNumber || cfgSpi.spi_led_buffer == nullptr) return; - - int offset = 4 + (index * 4); - cfgSpi.spi_led_buffer[offset] = 0xFF; - cfgSpi.spi_led_buffer[offset + 1] = b; - cfgSpi.spi_led_buffer[offset + 2] = g; - cfgSpi.spi_led_buffer[offset + 3] = r; - } - } - - inline void internalSetLedRgbw(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) - { - if (_ledsType == LedType::SK6812) - { - if (auto handle = findHandle(index); handle) { - led_strip_set_pixel_rgbw(handle, index, r, g, b, w); - } - } - else if (_ledsType == LedType::WS2812) - { - if (auto handle = findHandle(index); handle) { - led_strip_set_pixel(handle, index, r, g, b); - } - } - else if (_ledsType == LedType::APA102) - { - if (index >= _totalLedsNumber || cfgSpi.spi_led_buffer == nullptr) return; - - int offset = 4 + (index * 4); - cfgSpi.spi_led_buffer[offset] = 0xFF; - cfgSpi.spi_led_buffer[offset + 1] = b; - cfgSpi.spi_led_buffer[offset + 2] = g; - cfgSpi.spi_led_buffer[offset + 3] = r; - } - } - - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - struct SegmentCapabilities { - int rmt; - bool rmt_has_dma; - int rmt_mem_block_symbols; - int rmt_mem_block_symbols_aligment; - int spi; - - enum Channels { - RMT, SPI, NONE - }; - - SegmentCapabilities() { - rmt = 0; - rmt_has_dma = false; - rmt_mem_block_symbols = 0; - rmt_mem_block_symbols_aligment = 64; - spi = 0; - - #if defined(CONFIG_IDF_TARGET_ESP32S3) - rmt = 2; - spi = 2; - rmt_has_dma = true; - rmt_mem_block_symbols = 192; - rmt_mem_block_symbols_aligment = 48; - #elif defined(CONFIG_IDF_TARGET_ESP32S2) - rmt = 2; - spi = 2; - rmt_mem_block_symbols = 256; - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - rmt = 1; - spi = 1; - rmt_mem_block_symbols = 96; - rmt_mem_block_symbols_aligment = 48; - #elif defined(CONFIG_IDF_TARGET_ESP32C2) - rmt = 0; - spi = 1; - #elif defined(CONFIG_IDF_TARGET_ESP32) - rmt = 4; - spi = 2; - rmt_mem_block_symbols = 512; - #endif - }; - - int maxSegments() const { - return rmt + spi; - }; - - size_t getRmtSize(int segments, bool hasDmaLeft) const { - if (hasDmaLeft) { - auto result = 1024; - Log::debug("Consumed ", result," DMA memory for new RMT channel"); - return static_cast(result); - } - else { - segments -= spi + ((!rmt_has_dma) ? 0 : 1); - - auto result = ((!rmt_has_dma) ? rmt_mem_block_symbols : (rmt_mem_block_symbols - rmt_mem_block_symbols_aligment)) / std::max(segments, 1); - - result = (result / rmt_mem_block_symbols_aligment) * rmt_mem_block_symbols_aligment; - - Log::debug("Consumed ", result," symbols from RMT memory pool for new RMT channel. RMT has ", rmt_mem_block_symbols, " symbols"); - return static_cast(result); - } - }; - - Channels getFree() { - if (spi > 0 ) { - spi--; - return Channels::SPI; - } - else if (rmt > 0 ) { - rmt--; - return Channels::RMT; - } - return Channels::NONE; - } - - }; - - const SegmentCapabilities hardwareInfo; -}; \ No newline at end of file diff --git a/include/led_bridge/neopixelbus_bridge.h b/include/led_bridge/neopixelbus_bridge.h deleted file mode 100644 index ddc1ba6..0000000 --- a/include/led_bridge/neopixelbus_bridge.h +++ /dev/null @@ -1,233 +0,0 @@ -/* neopixelbus_bridge.h -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#pragma once -#include -#include "led_bridge.h" - -#if defined(CONFIG_IDF_TARGET_ESP32C6) - typedef NeoPixelBus DotStar; - typedef NeoPixelBus NeoPixel; - typedef NeoPixelBus NeoPixelRgbw; -#elif defined(CONFIG_IDF_TARGET_ESP32C3) - typedef NeoPixelBus DotStar; - typedef NeoPixelBus NeoPixel; - typedef NeoPixelBus NeoPixelRgbw; -#elif defined(CONFIG_IDF_TARGET_ESP32S3) - typedef NeoPixelBus DotStar; - typedef NeoPixelBus NeoPixel; - typedef NeoPixelBus NeoPixelRgbw; -#elif defined(CONFIG_IDF_TARGET_ESP32S2) - typedef NeoPixelBus DotStar; - typedef NeoPixelBus NeoPixel; - typedef NeoPixelBus NeoPixelRgbw; -#elif defined(ARDUINO_ARCH_ESP32) - #if defined(ETH_PHY_TYPE) && (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) - typedef NeoPixelBus DotStar; - typedef NeoPixelBus NeoPixel; - typedef NeoPixelBus NeoPixelRgbw; -#else // Raspberry Pi Pico - typedef NeoPixelBus DotStar; - typedef NeoPixelBus NeoPixel; - typedef NeoPixelBus NeoPixelRgbw; -#endif - -struct neopixelbus_bridge : public led_bridge -{ - DotStar* dotstar = nullptr; - NeoPixel* neopixel = nullptr; - NeoPixelRgbw* neopixelRgbw = nullptr; - - bool restartRequired() override { - #if defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) - return true; - #endif - return false; - } - - int getLedsNumber() override - { - if (dotstar != nullptr) - return dotstar->PixelCount(); - else if (neopixel != nullptr) - return neopixel->PixelCount(); - else if (neopixelRgbw != nullptr) - return neopixelRgbw->PixelCount(); - - return 0; - } - - void clearAll() override - { - if (dotstar == nullptr && neopixel == nullptr && neopixelRgbw == nullptr) - return; - - for (int i = 0; !canRender() && i < 200; i++) { - Log::debug("-"); - delay(1); - } - - if (dotstar != nullptr) - {dotstar->ClearTo(RgbColor(0, 0, 0)); dotstar->Show();} - else if (neopixel != nullptr) - {neopixel->ClearTo(RgbColor(0, 0, 0)); neopixel->Show();} - else if (neopixelRgbw != nullptr) - {neopixelRgbw->ClearTo(RgbwColor(0, 0, 0, 0)); neopixelRgbw->Show();} - - for (int i = 0; !canRender() && i < 200; i++) { - Log::debug("+"); - delay(1); - } - - Log::debug("leds cleared"); - } - - bool canRender() override - { - if (dotstar != nullptr) - { - return dotstar->CanShow(); - } - else if (neopixel != nullptr) - { - return neopixel->CanShow(); - } - else if (neopixelRgbw != nullptr) - { - return neopixelRgbw->CanShow(); - } - return true; - } - - int segmentSupported() override - { - return 0; - } - - bool supportsDoubleBuffering() override { - return true; - } - - void executeRenderLed() override - { - if (dotstar != nullptr) - { - dotstar->Show(); - } - else if (neopixel != nullptr) - { - neopixel->Show(); - } - else if (neopixelRgbw != nullptr) - { - neopixelRgbw->Show(); - } - } - - void releaseDriverResources() override - { - delay(50); - if (dotstar != nullptr) {delete dotstar; dotstar = nullptr;} - if (neopixel != nullptr) {delete neopixel; neopixel = nullptr;} - if (neopixelRgbw != nullptr) {delete neopixelRgbw; neopixelRgbw = nullptr;} - delay(100); - } - - void initializeLedDriver(LedType cfgLedType, uint16_t cfgLedNumLeds, const std::vector& cfgSegments, - uint8_t calGain, uint8_t calRed, uint8_t calGreen, uint8_t calBlue) override - { - if (cfgSegments.size() < 0) return; - const auto& segment = cfgSegments.front(); - uint8_t cfgLedDataPin = segment.data; - uint8_t cfgLedClockPin = segment.clock; - - if (cfgLedType == LedType::WS2812 || cfgLedType == LedType::SK6812) - { // clockless - switch (cfgLedType) - { - case LedType::SK6812: - setParamsAndPrepareCalibration(calGain, calRed, calGreen, calBlue); - neopixelRgbw = new NeoPixelRgbw(cfgLedNumLeds, cfgLedDataPin); - neopixelRgbw->Begin(); - break; - default: - neopixel = new NeoPixel(cfgLedNumLeds, cfgLedDataPin); - neopixel->Begin(); - } - } - else - { // SPI (APA102 / SK9822) - dotstar = new DotStar(cfgLedNumLeds, cfgLedClockPin, cfgLedDataPin); - dotstar->Begin(); - } - } - - inline void setLedRgb(int index, uint8_t r, uint8_t g, uint8_t b) override - { - if (dotstar != nullptr) - { - RgbColor col(r, g, b); - dotstar->SetPixelColor(index, col); - } - else if (neopixel != nullptr) - { - RgbColor col(r, g, b); - neopixel->SetPixelColor(index, col); - } - else if (neopixelRgbw != nullptr) - { - ColorRgbw col = rgb2rgbw(r, g, b); - neopixelRgbw->SetPixelColor(index, RgbwColor(col.R, col.G, col.B, col.W)); - } - } - - inline void setLedRgbw(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) override - { - if (dotstar != nullptr) - { - RgbColor col(r, g, b); - dotstar->SetPixelColor(index, col); - } - else if (neopixel != nullptr) - { - RgbColor col(r, g, b); - neopixel->SetPixelColor(index, col); - } - else if (neopixelRgbw != nullptr) - { - RgbwColor col(r, g, b, w); - neopixelRgbw->SetPixelColor(index, col); - } - } -}; \ No newline at end of file diff --git a/include/led_bridge/parlio_bridge.h b/include/led_bridge/parlio_bridge.h deleted file mode 100644 index a59ddaa..0000000 --- a/include/led_bridge/parlio_bridge.h +++ /dev/null @@ -1,604 +0,0 @@ -/* parlio_bridge.h -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#pragma once - -#include "esp_log.h" -#include "esp_check.h" -#include "esp_cache.h" -#include "driver/parlio_tx.h" -#include "esp_heap_caps.h" -#include "led_strip.h" -#include "driver/spi_master.h" -#include "led_bridge.h" -#include "double_buffer.h" - -template -struct parlio_bridge : public led_bridge, InternalBuffer -{ - struct ExtSegments{ - LedConfig::Segment segment; - int ledCount = 0; - }; - std::vector _segments; - - uint16_t _totalLedsNumber = 0; - LedType _ledsType = LedType::WS2812; - - struct { - const spi_host_device_t SELECTED_SPI_HOST = SPI2_HOST; - spi_device_handle_t spi_handle = nullptr; - size_t spi_buffer_size = 0; - uint8_t* spi_led_buffer = nullptr; - } cfgSpi; - - led_strip_handle_t led_strip_handle = nullptr; - - int getLedsNumber() override - { - return _totalLedsNumber; - } - - void clearAll() override - { - if (_segments.size() || cfgSpi.spi_handle != nullptr || led_strip_handle != nullptr) - { - for (int i = 0; !canRender() && i < 200; i++) { - Log::debug("-"); - delay(1); - } - - for (int i = 0; i < _totalLedsNumber; i++) { - setLedRgb(i, 0 ,0, 0); - } - executeRenderLed(); - - for (int i = 0; !canRender() && i < 200; i++) { - Log::debug("+"); - delay(1); - } - - Log::debug("leds cleared"); - } - } - - bool canRender() override - { - if (led_strip_handle != nullptr) { - return led_strip_is_rendering_done(led_strip_handle); - } - else if (_segments.size()) - { - return parlio_neopixel_can_render(); - } - - return true; - } - - int segmentSupported() override - { - return 8; - } - - bool supportsDoubleBuffering() override { - if constexpr(DOUBLEBUFFER_SUPPORT){ - return true; - } - else { - return false; - } - } - - void executeRenderLed() override - { - if (_segments.size() || led_strip_handle != nullptr) - { - if constexpr (DOUBLEBUFFER_SUPPORT) { - for(int i = 0; i < _totalLedsNumber; i++) { - uint8_t r, g, b, w; - this->doubleBuffer.getPixel(i, r, g, b, w); - internalSetLedRgbw(i, r, g, b, w); - } - } - if (led_strip_handle != nullptr) { - led_strip_refresh(led_strip_handle); - } - else { - parlio_neopixel_show(); - } - } - else if (cfgSpi.spi_handle) - { - spi_transaction_t t; - memset(&t, 0, sizeof(t)); - - t.length = cfgSpi.spi_buffer_size * 8; - t.tx_buffer = cfgSpi.spi_led_buffer; - - spi_device_transmit(cfgSpi.spi_handle, &t); - } - } - - void releaseDriverResources() override - { - delay(50); - - parlio_neopixel_deinit(); - - _segments.clear(); - - if (led_strip_handle != nullptr) { - led_strip_del(led_strip_handle); - led_strip_handle = nullptr; - } - - if (cfgSpi.spi_handle) { - spi_bus_remove_device(cfgSpi.spi_handle); - spi_bus_free(cfgSpi.SELECTED_SPI_HOST); - cfgSpi.spi_handle = nullptr; - } - - if (cfgSpi.spi_led_buffer) { - heap_caps_free(cfgSpi.spi_led_buffer); - cfgSpi.spi_led_buffer = nullptr; - cfgSpi.spi_buffer_size = 0; - } - - if constexpr (DOUBLEBUFFER_SUPPORT) { - this->doubleBuffer.releaseMemory(); - } - - delay(50); - } - - void initializeLedDriver(LedType cfgLedType, uint16_t cfgLedNumLeds, const std::vector& cfgSegments, - uint8_t calGain, uint8_t calRed, uint8_t calGreen, uint8_t calBlue) override - { - if (cfgSegments.size() < 0) return; - - if constexpr (DOUBLEBUFFER_SUPPORT) { - if (!this->doubleBuffer.init(cfgLedNumLeds)) return; - Log::debug("Enabled support for double buffering"); - } - - _totalLedsNumber = cfgLedNumLeds; - _ledsType = cfgLedType; - - if (_ledsType == LedType::SK6812) - { - setParamsAndPrepareCalibration(calGain, calRed, calGreen, calBlue); - } - - if ((_ledsType == LedType::WS2812 || _ledsType == LedType::SK6812) && cfgSegments.size() == 1) - { - led_strip_config_t strip_config = { - .strip_gpio_num = cfgSegments.front().data, - .max_leds = static_cast(cfgLedNumLeds), - .led_model = (_ledsType == LedType::SK6812) ? LED_MODEL_SK6812 : LED_MODEL_WS2812, - .color_component_format = (_ledsType == LedType::SK6812) ? LED_STRIP_COLOR_COMPONENT_FMT_GRBW : LED_STRIP_COLOR_COMPONENT_FMT_GRB, - .flags = { - .invert_out = false, - } - }; - - led_strip_spi_config_t spi_config = { - .clk_src = SPI_CLK_SRC_DEFAULT, - .spi_bus = SPI2_HOST, - .flags = { - .with_dma = true, - } - }; - - if (led_strip_new_spi_device(&strip_config, &spi_config, &led_strip_handle) != ESP_OK) { - led_strip_handle = NULL; - Log::debug("led_strip_new_spi_device failed for interface:", spi_config.spi_bus); - } - else { - Log::debug("Created Neopixel(SPI) segment for ", strip_config.max_leds, " LEDS, GPIO: ", strip_config.strip_gpio_num); - } - } - else if (_ledsType == LedType::WS2812 || _ledsType == LedType::SK6812) - { - uint16_t max_segment_length = 0; - uint8_t parlio_data_width = 1; - int gpio_nums[8] = {-1, -1, -1, -1, -1, -1, -1, -1}; - - for (size_t i = 0; i < cfgSegments.size(); ++i) { - uint16_t current_start = cfgSegments[i].startIndex; - uint16_t next_start = (i + 1 < cfgSegments.size()) ? cfgSegments[i + 1].startIndex : cfgLedNumLeds; - uint16_t segment_length = next_start - current_start; - - max_segment_length = std::max(max_segment_length, segment_length); - - if (i < 8) { - gpio_nums[i] = cfgSegments[i].data; - } - } - - size_t num_segments = cfgSegments.size(); - if (num_segments == 1) { parlio_data_width = 1; } - else if (num_segments == 2) { parlio_data_width = 2; } - else if (num_segments <= 4) { parlio_data_width = 4; } - else if (num_segments <= 8) { parlio_data_width = 8; } - - if (parlio_neopixel_init(parlio_data_width, gpio_nums, max_segment_length, (_ledsType == LedType::SK6812))) { - for(int i = 0; i < cfgSegments.size(); i++) { - const auto& seg = cfgSegments[i]; - int nextIndex = (i + 1 < cfgSegments.size()) ? cfgSegments[i + 1].startIndex : cfgLedNumLeds; - int max_leds = static_cast(std::max(nextIndex - seg.startIndex, 0)); - - _segments.push_back({seg, static_cast(max_leds)}); - Log::debug("Created PARLIO Neopixel segment for ", max_leds, " LEDS at: ", seg.startIndex, ", GPIO: ", seg.data); - } - } - else { - releaseDriverResources(); - } - } - else - { // SPI (APA102 / SK9822) - const auto& segment = cfgSegments.front(); - uint8_t cfgLedDataPin = segment.data; - uint8_t cfgLedClockPin = segment.clock; - - cfgSpi.spi_buffer_size = 4 + (cfgLedNumLeds * 4) + ((cfgLedNumLeds / 16) + 1); - - spi_bus_config_t buscfg = { - .mosi_io_num = cfgLedDataPin, - .miso_io_num = -1, - .sclk_io_num = cfgLedClockPin, - .quadwp_io_num = -1, - .quadhd_io_num = -1, - .max_transfer_sz = static_cast(cfgSpi.spi_buffer_size) - }; - - spi_device_interface_config_t devcfg = { - .mode = 0, - .clock_speed_hz = 10 * 1000 * 1000, - .spics_io_num = -1, - .queue_size = 7, - }; - - if (spi_bus_initialize(cfgSpi.SELECTED_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO) == ESP_OK) { - if (spi_bus_add_device(cfgSpi.SELECTED_SPI_HOST, &devcfg, &cfgSpi.spi_handle) == ESP_OK) { - - cfgSpi.spi_led_buffer = (uint8_t*)heap_caps_malloc(cfgSpi.spi_buffer_size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); - - if (cfgSpi.spi_led_buffer != nullptr) - { - memset(cfgSpi.spi_led_buffer, 0, cfgSpi.spi_buffer_size); - for (size_t i = 4 + (cfgLedNumLeds * 4); i < cfgSpi.spi_buffer_size; i++) { - cfgSpi.spi_led_buffer[i] = 0xFF; - } - Log::debug("Created SPI segment, GPIO: ", cfgLedDataPin, ", CLOCK: ", cfgLedClockPin); - } - else { - Log::debug("SPI: heap_caps_malloc failed"); - } - } - else { - Log::debug("SPI: spi_bus_add_device failed"); - } - } - else { - Log::debug("SPI: spi_bus_initialize failed"); - } - - if (cfgSpi.spi_led_buffer == nullptr) { - releaseDriverResources(); - } - } - } - - inline std::pair findHandle(int& index) const { - if (index >= 0 && index < _totalLedsNumber){ - for(int i = 0; i < _segments.size(); i++){ - const auto& seg = _segments[i]; - if (index < seg.ledCount) { - return {i, index}; - } - else { - index -= seg.ledCount; - } - } - } - return {-1, -1}; - } - - void setLedRgb(int index, uint8_t r, uint8_t g, uint8_t b) override { - if constexpr (DOUBLEBUFFER_SUPPORT) if (cfgSpi.spi_handle == nullptr) { - uint8_t w = 0; - if (_ledsType == LedType::SK6812) - { - const ColorRgbw calibrated = rgb2rgbw(r, g, b); - r = calibrated.R; g = calibrated.G; b = calibrated.B; w = calibrated.W; - } - this->doubleBuffer.setPixel(index, r, g, b, w); - return; - } - internalSetLedRgb(index, r, g, b); - } - - void setLedRgbw(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) override { - if constexpr (DOUBLEBUFFER_SUPPORT) if (cfgSpi.spi_handle == nullptr) { - this->doubleBuffer.setPixel(index, r, g, b, w); - return; - } - internalSetLedRgbw(index, r, g, b, w); - } - - inline void internalSetLedRgb(int index, uint8_t r, uint8_t g, uint8_t b) - { - if (_ledsType == LedType::SK6812) - { - const ColorRgbw calibrated = rgb2rgbw(r, g, b); - - if (led_strip_handle != nullptr) { - led_strip_set_pixel_rgbw(led_strip_handle, index, calibrated.R, calibrated.G, calibrated.B, calibrated.W); - } - else if (auto handle = findHandle(index); handle.first >= 0) { - parlio_neopixel_setPixel(handle.first, handle.second, calibrated.R, calibrated.G, calibrated.B, calibrated.W); - } - } - else if (_ledsType == LedType::WS2812) - { - if (led_strip_handle != nullptr) { - led_strip_set_pixel(led_strip_handle, index, r, g, b); - } - else if (auto handle = findHandle(index); handle.first >= 0) { - parlio_neopixel_setPixel(handle.first, handle.second, r, g, b, 0); - } - } - else if (_ledsType == LedType::APA102) - { - if (index >= _totalLedsNumber || cfgSpi.spi_led_buffer == nullptr) return; - - int offset = 4 + (index * 4); - cfgSpi.spi_led_buffer[offset] = 0xFF; - cfgSpi.spi_led_buffer[offset + 1] = b; - cfgSpi.spi_led_buffer[offset + 2] = g; - cfgSpi.spi_led_buffer[offset + 3] = r; - } - } - - inline void internalSetLedRgbw(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) - { - if (_ledsType == LedType::SK6812) - { - if (led_strip_handle != nullptr) { - led_strip_set_pixel_rgbw(led_strip_handle, index, r, g, b, w); - } - else if (auto handle = findHandle(index); handle.first >= 0) { - parlio_neopixel_setPixel(handle.first, handle.second, r, g, b, w); - } - } - else if (_ledsType == LedType::WS2812) - { - if (led_strip_handle != nullptr) { - led_strip_set_pixel(led_strip_handle, index, r, g, b); - } - else if (auto handle = findHandle(index); handle.first >= 0) { - parlio_neopixel_setPixel(handle.first, handle.second, r, g, b, 0); - } - } - else if (_ledsType == LedType::APA102) - { - if (index >= _totalLedsNumber || cfgSpi.spi_led_buffer == nullptr) return; - - int offset = 4 + (index * 4); - cfgSpi.spi_led_buffer[offset] = 0xFF; - cfgSpi.spi_led_buffer[offset + 1] = b; - cfgSpi.spi_led_buffer[offset + 2] = g; - cfgSpi.spi_led_buffer[offset + 3] = r; - } - } - - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - typedef struct { - parlio_tx_unit_handle_t tx_unit; - uint8_t *dma_buffer; - size_t buffer_size; - uint16_t num_leds; - uint8_t data_width; - bool is_rgbw; - uint16_t ticks_per_led; - volatile bool is_transfering; - int64_t next_frame_allowed_at; - } parlio_led_strip_t; - - inline static parlio_led_strip_t strip = {}; - - static inline void IRAM_ATTR parlio_neopixel_setPixel(uint8_t pin_index, uint16_t led_index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) - { - if (led_index >= strip.num_leds || pin_index >= strip.data_width) return; - - uint32_t color = 0; - uint8_t bits = strip.is_rgbw ? 32 : 24; - - if (strip.is_rgbw) { - color = (g << 24) | (r << 16) | (b << 8) | w; - } else { - color = (g << 16) | (r << 8) | b; - } - - uint32_t tick_base = led_index * strip.ticks_per_led; - - for (int i = 0; i < bits; i++) { - uint8_t bit_val = (color >> (bits - 1 - i)) & 1; - - for (int tick_offset = 0; tick_offset < 3; tick_offset++) { - uint32_t current_tick = tick_base + (i * 3) + tick_offset; - uint32_t global_bit_idx = (current_tick * strip.data_width) + pin_index; - - uint32_t byte_idx = global_bit_idx / 8; - uint8_t bit_mask = 1 << (global_bit_idx % 8); - - bool pin_state = (tick_offset == 0) || (tick_offset == 1 && bit_val); - - if (pin_state) { - strip.dma_buffer[byte_idx] |= bit_mask; - } else { - strip.dma_buffer[byte_idx] &= ~bit_mask; - } - } - } - } - - static bool IRAM_ATTR parlio_tx_finish_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx) { - strip.next_frame_allowed_at = esp_timer_get_time() + 300; - strip.is_transfering = false; - return false; - } - - bool parlio_neopixel_can_render() { - if (strip.is_transfering || strip.next_frame_allowed_at > esp_timer_get_time()) - return false; - - strip.next_frame_allowed_at = 0; - return true; - } - - bool parlio_neopixel_init(uint8_t data_width, const int *gpio_nums, uint16_t num_leds, bool is_rgbw) - { - if (data_width != 1 && data_width != 2 && data_width != 4 && data_width != 8) { - Log::debug("parlio_neopixel_init: unsupported data_width. must be 1, 2, 4 or 8"); - return false; - } - - strip.num_leds = num_leds; - strip.is_rgbw = is_rgbw; - strip.data_width = data_width; - - strip.is_transfering = false; - strip.next_frame_allowed_at = 0; - - strip.ticks_per_led = is_rgbw ? (32 * 3) : (24 * 3); - - uint32_t total_ticks = strip.num_leds * strip.ticks_per_led; - - strip.buffer_size = (total_ticks * data_width + 7) / 8; - strip.buffer_size = (strip.buffer_size + 31) & ~31; - - parlio_tx_unit_config_t tx_config = { - .clk_src = PARLIO_CLK_SRC_DEFAULT, - .clk_in_gpio_num = GPIO_NUM_NC, - .output_clk_freq_hz = 2500000, - .data_width = data_width, - .clk_out_gpio_num = gpio_num_t::GPIO_NUM_NC, - .valid_gpio_num = gpio_num_t::GPIO_NUM_NC, - .trans_queue_depth = 4, - .max_transfer_size = strip.buffer_size, - .sample_edge = PARLIO_SAMPLE_EDGE_POS, - .bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB, - }; - - for (int i = 0; i < PARLIO_TX_UNIT_MAX_DATA_WIDTH; i++) { - tx_config.data_gpio_nums[i] = (i < data_width) ? (gpio_num_t)gpio_nums[i] : gpio_num_t::GPIO_NUM_NC; - } - - bool initOk = false; - if (parlio_new_tx_unit(&tx_config, &strip.tx_unit) != ESP_OK) { - Log::debug("parlio_neopixel_init: parlio_new_tx_unit failed"); - } - else - { - strip.dma_buffer = (uint8_t*)heap_caps_aligned_alloc(32, strip.buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_32BIT); - - if (strip.dma_buffer == NULL) { - Log::debug("parlio_neopixel_init: failed to allocate DMA buffer!"); - } - else { - memset(strip.dma_buffer, 0, strip.buffer_size); - - parlio_tx_event_callbacks_t cbs = {}; - cbs.on_trans_done = parlio_tx_finish_callback; - if (parlio_tx_unit_register_event_callbacks(strip.tx_unit, &cbs, NULL) != ESP_OK) { - Log::debug("parlio_neopixel_init: parlio_tx_unit_register_event_callbacks failed"); - } - else { - if (parlio_tx_unit_enable(strip.tx_unit) != ESP_OK) { - Log::debug("parlio_neopixel_init: parlio_tx_unit_enable failed"); - } - else { - initOk = true; - Log::debug("PARLIO Neopixel init @ ", tx_config.output_clk_freq_hz,"Hz. Outputs: ", data_width, ", RGBW: ", is_rgbw, ", Buffer: ", strip.buffer_size," bytes"); - } - } - } - } - - if (!initOk) { - parlio_neopixel_deinit(); - } - - return initOk; - } - - bool parlio_neopixel_show() - { - if (strip.tx_unit == NULL) return false; - - parlio_transmit_config_t trans_config = { - .idle_value = 0x00, - }; - - size_t total_bits = (strip.num_leds * strip.ticks_per_led) * strip.data_width; - - strip.is_transfering = true; - strip.next_frame_allowed_at = INT64_MAX; - - if (auto res = parlio_tx_unit_transmit(strip.tx_unit, strip.dma_buffer, total_bits, &trans_config); res != ESP_OK) { - strip.is_transfering = false; - strip.next_frame_allowed_at = 0; - - Log::debug("parlio_tx_unit_transmit failed: ", res); - return false; - } - - return true; - } - - void parlio_neopixel_deinit() - { - if (strip.tx_unit) { - parlio_tx_unit_disable(strip.tx_unit); - - parlio_del_tx_unit(strip.tx_unit); - strip.tx_unit = NULL; - Log::debug("PARLIO deinitialized: tx_unit"); - } - if (strip.dma_buffer) { - free(strip.dma_buffer); - strip.dma_buffer = NULL; - Log::debug("PARLIO deinitialized: dma_buffer"); - } - - strip = {}; - Log::debug("PARLIO deinitialized: finished"); - } -}; \ No newline at end of file diff --git a/include/led_bridge/picolada/pico/neopixel.pio.h b/include/led_bridge/picolada/pico/neopixel.pio.h deleted file mode 100644 index 4ad4eaa..0000000 --- a/include/led_bridge/picolada/pico/neopixel.pio.h +++ /dev/null @@ -1,81 +0,0 @@ -// ---------------------------------------------------------------- // -// This file is autogenerated by pioasm version 2.2.0; do not edit! // -// ---------------------------------------------------------------- // - -#pragma once - -#if !PICO_NO_HARDWARE -#include "hardware/pio.h" -#endif - -// -------- // -// neopixel // -// -------- // - -#define neopixel_wrap_target 0 -#define neopixel_wrap 3 -#define neopixel_pio_version 0 - -static const uint16_t neopixel_program_instructions[] = { - // .wrap_target - 0x6421, // 0: out x, 1 side 0 [4] - 0x1223, // 1: jmp !x, 3 side 1 [2] - 0x1300, // 2: jmp 0 side 1 [3] - 0xa342, // 3: nop side 0 [3] - // .wrap -}; - -#if !PICO_NO_HARDWARE -static const struct pio_program neopixel_program = { - .instructions = neopixel_program_instructions, - .length = 4, - .origin = -1, - .pio_version = neopixel_pio_version, -#if PICO_PIO_VERSION > 0 - .used_gpio_ranges = 0x0 -#endif -}; - -static inline pio_sm_config neopixel_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + neopixel_wrap_target, offset + neopixel_wrap); - sm_config_set_sideset(&c, 1, false, false); - return c; -} -#endif - -// ----------------- // -// neopixel_parallel // -// ----------------- // - -#define neopixel_parallel_wrap_target 0 -#define neopixel_parallel_wrap 3 -#define neopixel_parallel_pio_version 0 - -static const uint16_t neopixel_parallel_program_instructions[] = { - // .wrap_target - 0x6028, // 0: out x, 8 - 0xa20b, // 1: mov pins, ~null [2] - 0xa301, // 2: mov pins, x [3] - 0xa303, // 3: mov pins, null [3] - // .wrap -}; - -#if !PICO_NO_HARDWARE -static const struct pio_program neopixel_parallel_program = { - .instructions = neopixel_parallel_program_instructions, - .length = 4, - .origin = -1, - .pio_version = neopixel_parallel_pio_version, -#if PICO_PIO_VERSION > 0 - .used_gpio_ranges = 0x0 -#endif -}; - -static inline pio_sm_config neopixel_parallel_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + neopixel_parallel_wrap_target, offset + neopixel_parallel_wrap); - return c; -} -#endif - diff --git a/include/led_bridge/picolada/pico/neopixel_ws2812b.pio.h b/include/led_bridge/picolada/pico/neopixel_ws2812b.pio.h deleted file mode 100644 index bdd4a31..0000000 --- a/include/led_bridge/picolada/pico/neopixel_ws2812b.pio.h +++ /dev/null @@ -1,81 +0,0 @@ -// ---------------------------------------------------------------- // -// This file is autogenerated by pioasm version 2.2.0; do not edit! // -// ---------------------------------------------------------------- // - -#pragma once - -#if !PICO_NO_HARDWARE -#include "hardware/pio.h" -#endif - -// ---------------- // -// neopixel_ws2812b // -// ---------------- // - -#define neopixel_ws2812b_wrap_target 0 -#define neopixel_ws2812b_wrap 3 -#define neopixel_ws2812b_pio_version 0 - -static const uint16_t neopixel_ws2812b_program_instructions[] = { - // .wrap_target - 0x6321, // 0: out x, 1 side 0 [3] - 0x1223, // 1: jmp !x, 3 side 1 [2] - 0x1400, // 2: jmp 0 side 1 [4] - 0xa442, // 3: nop side 0 [4] - // .wrap -}; - -#if !PICO_NO_HARDWARE -static const struct pio_program neopixel_ws2812b_program = { - .instructions = neopixel_ws2812b_program_instructions, - .length = 4, - .origin = -1, - .pio_version = neopixel_ws2812b_pio_version, -#if PICO_PIO_VERSION > 0 - .used_gpio_ranges = 0x0 -#endif -}; - -static inline pio_sm_config neopixel_ws2812b_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + neopixel_ws2812b_wrap_target, offset + neopixel_ws2812b_wrap); - sm_config_set_sideset(&c, 1, false, false); - return c; -} -#endif - -// ------------------------- // -// neopixel_ws2812b_parallel // -// ------------------------- // - -#define neopixel_ws2812b_parallel_wrap_target 0 -#define neopixel_ws2812b_parallel_wrap 3 -#define neopixel_ws2812b_parallel_pio_version 0 - -static const uint16_t neopixel_ws2812b_parallel_program_instructions[] = { - // .wrap_target - 0x6028, // 0: out x, 8 - 0xa20b, // 1: mov pins, ~null [2] - 0xa401, // 2: mov pins, x [4] - 0xa203, // 3: mov pins, null [2] - // .wrap -}; - -#if !PICO_NO_HARDWARE -static const struct pio_program neopixel_ws2812b_parallel_program = { - .instructions = neopixel_ws2812b_parallel_program_instructions, - .length = 4, - .origin = -1, - .pio_version = neopixel_ws2812b_parallel_pio_version, -#if PICO_PIO_VERSION > 0 - .used_gpio_ranges = 0x0 -#endif -}; - -static inline pio_sm_config neopixel_ws2812b_parallel_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + neopixel_ws2812b_parallel_wrap_target, offset + neopixel_ws2812b_parallel_wrap); - return c; -} -#endif - diff --git a/include/led_bridge/picolada/pico2/neopixel.pio.h b/include/led_bridge/picolada/pico2/neopixel.pio.h deleted file mode 100644 index 90ea46f..0000000 --- a/include/led_bridge/picolada/pico2/neopixel.pio.h +++ /dev/null @@ -1,81 +0,0 @@ -// ---------------------------------------------------------------- // -// This file is autogenerated by pioasm version 2.2.0; do not edit! // -// ---------------------------------------------------------------- // - -#pragma once - -#if !PICO_NO_HARDWARE -#include "hardware/pio.h" -#endif - -// -------- // -// neopixel // -// -------- // - -#define neopixel_wrap_target 0 -#define neopixel_wrap 3 -#define neopixel_pio_version 1 - -static const uint16_t neopixel_program_instructions[] = { - // .wrap_target - 0x6421, // 0: out x, 1 side 0 [4] - 0x1223, // 1: jmp !x, 3 side 1 [2] - 0x1300, // 2: jmp 0 side 1 [3] - 0xa342, // 3: nop side 0 [3] - // .wrap -}; - -#if !PICO_NO_HARDWARE -static const struct pio_program neopixel_program = { - .instructions = neopixel_program_instructions, - .length = 4, - .origin = -1, - .pio_version = neopixel_pio_version, -#if PICO_PIO_VERSION > 0 - .used_gpio_ranges = 0x0 -#endif -}; - -static inline pio_sm_config neopixel_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + neopixel_wrap_target, offset + neopixel_wrap); - sm_config_set_sideset(&c, 1, false, false); - return c; -} -#endif - -// ----------------- // -// neopixel_parallel // -// ----------------- // - -#define neopixel_parallel_wrap_target 0 -#define neopixel_parallel_wrap 3 -#define neopixel_parallel_pio_version 1 - -static const uint16_t neopixel_parallel_program_instructions[] = { - // .wrap_target - 0x6028, // 0: out x, 8 - 0xa20b, // 1: mov pins, ~null [2] - 0xa301, // 2: mov pins, x [3] - 0xa303, // 3: mov pins, null [3] - // .wrap -}; - -#if !PICO_NO_HARDWARE -static const struct pio_program neopixel_parallel_program = { - .instructions = neopixel_parallel_program_instructions, - .length = 4, - .origin = -1, - .pio_version = neopixel_parallel_pio_version, -#if PICO_PIO_VERSION > 0 - .used_gpio_ranges = 0x0 -#endif -}; - -static inline pio_sm_config neopixel_parallel_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + neopixel_parallel_wrap_target, offset + neopixel_parallel_wrap); - return c; -} -#endif - diff --git a/include/led_bridge/picolada/pico2/neopixel_ws2812b.pio.h b/include/led_bridge/picolada/pico2/neopixel_ws2812b.pio.h deleted file mode 100644 index 505ec07..0000000 --- a/include/led_bridge/picolada/pico2/neopixel_ws2812b.pio.h +++ /dev/null @@ -1,81 +0,0 @@ -// ---------------------------------------------------------------- // -// This file is autogenerated by pioasm version 2.2.0; do not edit! // -// ---------------------------------------------------------------- // - -#pragma once - -#if !PICO_NO_HARDWARE -#include "hardware/pio.h" -#endif - -// ---------------- // -// neopixel_ws2812b // -// ---------------- // - -#define neopixel_ws2812b_wrap_target 0 -#define neopixel_ws2812b_wrap 3 -#define neopixel_ws2812b_pio_version 1 - -static const uint16_t neopixel_ws2812b_program_instructions[] = { - // .wrap_target - 0x6321, // 0: out x, 1 side 0 [3] - 0x1223, // 1: jmp !x, 3 side 1 [2] - 0x1400, // 2: jmp 0 side 1 [4] - 0xa442, // 3: nop side 0 [4] - // .wrap -}; - -#if !PICO_NO_HARDWARE -static const struct pio_program neopixel_ws2812b_program = { - .instructions = neopixel_ws2812b_program_instructions, - .length = 4, - .origin = -1, - .pio_version = neopixel_ws2812b_pio_version, -#if PICO_PIO_VERSION > 0 - .used_gpio_ranges = 0x0 -#endif -}; - -static inline pio_sm_config neopixel_ws2812b_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + neopixel_ws2812b_wrap_target, offset + neopixel_ws2812b_wrap); - sm_config_set_sideset(&c, 1, false, false); - return c; -} -#endif - -// ------------------------- // -// neopixel_ws2812b_parallel // -// ------------------------- // - -#define neopixel_ws2812b_parallel_wrap_target 0 -#define neopixel_ws2812b_parallel_wrap 3 -#define neopixel_ws2812b_parallel_pio_version 1 - -static const uint16_t neopixel_ws2812b_parallel_program_instructions[] = { - // .wrap_target - 0x6028, // 0: out x, 8 - 0xa20b, // 1: mov pins, ~null [2] - 0xa401, // 2: mov pins, x [4] - 0xa203, // 3: mov pins, null [2] - // .wrap -}; - -#if !PICO_NO_HARDWARE -static const struct pio_program neopixel_ws2812b_parallel_program = { - .instructions = neopixel_ws2812b_parallel_program_instructions, - .length = 4, - .origin = -1, - .pio_version = neopixel_ws2812b_parallel_pio_version, -#if PICO_PIO_VERSION > 0 - .used_gpio_ranges = 0x0 -#endif -}; - -static inline pio_sm_config neopixel_ws2812b_parallel_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + neopixel_ws2812b_parallel_wrap_target, offset + neopixel_ws2812b_parallel_wrap); - return c; -} -#endif - diff --git a/include/led_bridge/picolada/picolada.h b/include/led_bridge/picolada/picolada.h deleted file mode 100644 index 3e92162..0000000 --- a/include/led_bridge/picolada/picolada.h +++ /dev/null @@ -1,675 +0,0 @@ -#pragma once - -/* leds.h -* -* MIT License -* -* Copyright (c) 2023-2026 awawa-dev -* -* https://github.com/awawa-dev/HyperSerialPico -* -* 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. - */ - -/* - HyperSerialPico led (aka PicoLada) library features: - - neopixel (rgb: ws2812b, ws2813..., rgbw: sk6812b) and dotstar (rgb: apa102, hd107, sk9822...) led strip support - - single and up to 8 lines parallel (neopixel) mode - - DMA - - PIO neopixel hardware processing - - using LUT tables for preparing PIO DMA parallel buffer - - SPI dotstar hardware support - - non-blocking rendering (check isReady if it's finished) - - Usage for sk6812 rgbw single lane: - ledStrip1 = new sk6812(ledsNumber, DATA_PIN); - ledStrip1->SetPixel(index, ColorGrbw(255)); - ledStrip1->renderSingleLane(); - - Usage for ws2812 rgb single lane: - ledStrip1 = new ws2812(ledsNumber, DATA_PIN); - ledStrip1->SetPixel(index, ColorGrb32(255)); - ledStrip1->renderSingleLane(); - - Usage for sk6812 rgbw multi lanes: - ledStrip1 = new sk6812p(ledsNumber, DATA_PIN); // using DATA_PIN output - ledStrip2 = new sk6812p(ledsNumber, DATA_PIN); // using DATA_PIN + 1 output - ledStrip1->SetPixel(index, ColorGrbw(255)); - ledStrip2->SetPixel(index, ColorGrbw(255)); - ledStrip1->renderAllLanes(); // renders ledStrip1 and ledStrip2 simoultaneusly - - Usage for ws2812 rgb multi lanes: - ledStrip1 = new ws2812p(ledsNumber, DATA_PIN); // using DATA_PIN output - ledStrip2 = new ws2812p(ledsNumber, DATA_PIN); // using DATA_PIN + 1 output - ledStrip1->SetPixel(index, ColorGrb(255)); - ledStrip2->SetPixel(index, ColorGrb(255)); - ledStrip1->renderAllLanes(); // renders ledStrip1 and ledStrip2 simoultaneusly - - Usage for dotstar rgb single line: - ledStrip1 = new apa102(ledsNumber, DATA_PIN, CLOCK_PIN); - ledStrip1->SetPixel(index, ColorDotstartBgr(255)); - ledStrip1->renderSingleLane(); -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct __attribute__((packed)) ColorGrb32 -{ - uint8_t notUsed; - uint8_t B; - uint8_t R; - uint8_t G; - - ColorGrb32(uint8_t gray) : - R(gray), G(gray), B(gray) - { - }; - - ColorGrb32() : R(0), G(0), B(0) - { - }; - - static bool isAlignedTo24() - { - return true; - }; -}; - -struct __attribute__((packed)) ColorGrb -{ - uint8_t B; - uint8_t R; - uint8_t G; - - - ColorGrb(uint8_t gray) : - R(gray), G(gray), B(gray) - { - }; - - ColorGrb() : R(0), G(0), B(0) - { - }; -}; - -struct __attribute__((packed)) ColorGrbw -{ - uint8_t W; - uint8_t B; - uint8_t R; - uint8_t G; - - ColorGrbw(uint8_t gray) : - R(gray), G(gray), B(gray), W(gray) - { - }; - - ColorGrbw() : R(0), G(0), B(0), W(0) - { - }; - - static bool isAlignedTo24() - { - return false; - }; -}; - -struct __attribute__((packed)) ColorDotstartBgr -{ - uint8_t Brightness; - uint8_t B; - uint8_t G; - uint8_t R; - - ColorDotstartBgr(uint8_t gray) : - R(gray), G(gray), B(gray), Brightness(gray | 0b11100000) - { - }; - - ColorDotstartBgr() : R(0), G(0), B(0), Brightness(0xff) - { - }; -}; - -struct __attribute__((packed)) ColorRgb -{ - uint8_t R; - uint8_t G; - uint8_t B; - - ColorRgb(uint8_t gray) : - R(gray), G(gray), B(gray) - { - }; - - ColorRgb() : R(0), G(0), B(0) - { - }; -}; - -class LedDriver -{ - protected: - - int ledsNumber; - int pin; - int clockPin; - int dmaSize; - uint8_t* buffer; - uint8_t* dma; - - public: - - LedDriver(int _ledsNumber, int _pin, int _dmaSize): LedDriver(_ledsNumber, _pin, 0, _dmaSize) - { - - } - - LedDriver(int _ledsNumber, int _pin, int _clockPin, int _dmaSize) - { - LedDriverDmaReceiver = this; - ledsNumber = _ledsNumber; - pin = _pin; - clockPin = _clockPin; - dmaSize = _dmaSize; - if (dmaSize % 4) - dmaSize += (4 - (_dmaSize % 4)); - buffer = reinterpret_cast(calloc(dmaSize, 1)); - dma = reinterpret_cast(calloc(dmaSize, 1)); - } - - virtual ~LedDriver() - { - free(buffer); - free(dma); - if (LedDriverDmaReceiver == this) - LedDriverDmaReceiver = nullptr; - } - - static LedDriver* LedDriverDmaReceiver; -}; - -LedDriver* LedDriver::LedDriverDmaReceiver = nullptr; - -class DmaClient -{ - protected: - - PIO selectedPIO; - uint stateIndex; - - static uint PICO_DMA_CHANNEL; - static volatile uint64_t lastRenderTime; - static volatile bool isDmaBusy; - - - DmaClient() - { - PICO_DMA_CHANNEL = dma_claim_unused_channel(true); - isDmaBusy = false; - lastRenderTime = 0; - }; - - virtual ~DmaClient() - { - for(int i = 0; i < 100 && isDmaBusy; i++) - busy_wait_us(500); - - dma_channel_abort(PICO_DMA_CHANNEL); - dma_channel_set_irq0_enabled(PICO_DMA_CHANNEL, false); - irq_remove_handler(DMA_IRQ_0, dmaFinishReceiver); - - dma_channel_unclaim(PICO_DMA_CHANNEL); - }; - - void dmaConfigure() - { - selectedPIO = pio0; - int sm = pio_claim_unused_sm(selectedPIO, false); - if (sm < 0) { - selectedPIO = pio1; - sm = pio_claim_unused_sm(selectedPIO, true); - } - stateIndex = (uint)sm; - }; - - void initDmaPio(uint dataLenDword32) - { - dma_channel_config dmaConfig = dma_channel_get_default_config(PICO_DMA_CHANNEL); - channel_config_set_dreq(&dmaConfig, pio_get_dreq(selectedPIO, stateIndex, true)); - channel_config_set_transfer_data_size(&dmaConfig, DMA_SIZE_32); - channel_config_set_read_increment(&dmaConfig, true); - dma_channel_configure(PICO_DMA_CHANNEL, &dmaConfig, &selectedPIO->txf[stateIndex], NULL, dataLenDword32, false); - - assignDmaIrq(); - }; - - void initDmaSpi(spi_inst_t* _spi, uint dataLenByte8) - { - dma_channel_config dmaConfig = dma_channel_get_default_config(PICO_DMA_CHANNEL); - channel_config_set_transfer_data_size(&dmaConfig, DMA_SIZE_8); - channel_config_set_dreq(&dmaConfig, spi_get_dreq(_spi, true)); - dma_channel_configure(PICO_DMA_CHANNEL, &dmaConfig,&spi_get_hw(_spi)->dr, NULL, dataLenByte8, false); - - assignDmaIrq(); - }; - - void assignDmaIrq() - { - irq_add_shared_handler(DMA_IRQ_0, dmaFinishReceiver, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); - dma_channel_set_irq0_enabled(PICO_DMA_CHANNEL, true); - irq_set_enabled(DMA_IRQ_0, true); - }; - - public: - - bool isReadyBlocking() - { - int wait = 2000; - while(isDmaBusy && wait-- > 0) - busy_wait_us(50); - - return !isDmaBusy; - } - - bool isReady() - { - return !isDmaBusy; - } - - static void dmaFinishReceiver() - { - if (dma_hw->ints0 & (1u<ints0 = (1u<= 1) - { - pioProgram = (timingType == NeopixelSubtype::ws2812b) ? &neopixel_ws2812b_parallel_program : &neopixel_parallel_program; - programAddress = pio_add_program(selectedPIO, pioProgram); - - for(uint i=_pin; i<_pin + lanes; i++){ - pio_gpio_init(selectedPIO, i); - } - - smConfig = (timingType == NeopixelSubtype::ws2812b) ? - neopixel_ws2812b_parallel_program_get_default_config(programAddress) : neopixel_parallel_program_get_default_config(programAddress); - - sm_config_set_out_pins(&smConfig, _pin, lanes); - } - else - { - pioProgram = (timingType == NeopixelSubtype::ws2812b) ? &neopixel_ws2812b_program : &neopixel_program; - programAddress = pio_add_program(selectedPIO, pioProgram); - - pio_gpio_init(selectedPIO, _pin); - - smConfig = (timingType == NeopixelSubtype::ws2812b) ? - neopixel_ws2812b_program_get_default_config(programAddress) : neopixel_program_get_default_config(programAddress); - - sm_config_set_sideset_pins(&smConfig, _pin); - } - - pio_sm_set_consecutive_pindirs(selectedPIO, stateIndex, _pin, std::max(lanes, 1), true); - sm_config_set_out_shift(&smConfig, false, true, (alignTo24) ? 24: 32); - sm_config_set_fifo_join(&smConfig, PIO_FIFO_JOIN_TX); - float div = clock_get_hz(clk_sys) / (800000 * 12); - sm_config_set_clkdiv(&smConfig, div); - pio_sm_init(selectedPIO, stateIndex, programAddress, &smConfig); - pio_sm_set_enabled(selectedPIO, stateIndex, true); - - initDmaPio(dmaSize / 4); - } - - virtual ~Neopixel() - { - pio_sm_set_enabled(selectedPIO, stateIndex, false); - pio_sm_clear_fifos(selectedPIO, stateIndex); - pio_sm_unclaim(selectedPIO, stateIndex); - - pio_remove_program(selectedPIO, pioProgram, programAddress); - } - - uint8_t* getBufferMemory() - { - return buffer; - } - - protected: - - void renderDma(bool resetBuffer) - { - if (isDmaBusy) - return; - - isDmaBusy = true; - - uint64_t currentTime = time_us_64(); - if (currentTime < resetTime + lastRenderTime) - busy_wait_us(std::min(resetTime + lastRenderTime - currentTime, resetTime)); - - memcpy(dma, buffer, dmaSize); - - dma_channel_set_read_addr(PICO_DMA_CHANNEL, dma, true); - - if (resetBuffer) - memset(buffer, 0, dmaSize); - } -}; - -template -class NeopixelType : public Neopixel -{ - public: - - NeopixelType(int _ledsNumber, int _pin) : - Neopixel(_type, 0, RESET_TIME, _ledsNumber, _pin, _ledsNumber * sizeof(colorData), colorData::isAlignedTo24()) - { - } - - void SetPixel(int index, colorData color) - { - if (index >= ledsNumber) - return; - - *(reinterpret_cast(buffer)+index) = color; - } - - void renderSingleLane() - { - renderDma(false); - } -}; - - -class NeopixelParallel -{ - static Neopixel *muxer; - static int instances; - - protected: - static int maxLeds; - const uint8_t myLaneMask; - static uint8_t* buffer; - - public: - - NeopixelParallel(NeopixelSubtype _type, size_t pixelSize, uint64_t _resetTime, int _ledsNumber, int _pin): - myLaneMask(1 << (instances++)) - { - maxLeds = std::max(maxLeds, _ledsNumber); - - delete muxer; - muxer = new Neopixel(_type, instances, _resetTime, maxLeds, _pin, maxLeds * 8 * pixelSize ); - buffer = muxer->getBufferMemory(); - } - - virtual ~NeopixelParallel() - { - if (instances > 0) - instances--; - - if (instances == 0) - { - delete muxer; - muxer = nullptr; - buffer = nullptr; - maxLeds = 0; - } - } - - bool isReadyBlocking() - { - return muxer->isReadyBlocking(); - } - - bool isReady() - { - return muxer->isReady(); - } - - void renderAllLanes() - { - muxer->renderDma(true); - } -}; - -template -class NeopixelParallelType : public NeopixelParallel -{ - uint32_t lut[16]; - - public: - - NeopixelParallelType(int _ledsNumber, int _basePinForLanes) : - NeopixelParallel(_type, sizeof(colorData), RESET_TIME, _ledsNumber, _basePinForLanes) - { - for (uint8_t a = 0; a < 16; a++) - { - uint8_t* target = reinterpret_cast(&(lut[a])); - for (uint8_t b = 0; b < 4; b++) - *(target++) = (uint8_t) ((a & (0b00000001 << b)) ? myLaneMask : 0); - } - } - - void SetPixel(int index, colorData color) - { - if (index >= maxLeds) - return; - - uint8_t* source = reinterpret_cast(&color); - uint32_t* target = reinterpret_cast(&(buffer[(index + 1) * 8 * sizeof(colorData)])); - - for(int i = 0; i < sizeof(colorData); i++) - { - *(--target) |= lut[ *(source) & 0b00001111]; - *(--target) |= lut[ *(source++) >> 4]; - } - } -}; - -class Dotstar : public LedDriver, public DmaClient -{ - uint64_t resetTime; - - friend class NeopixelParallel; - - public: - Dotstar(uint64_t _resetTime, int _ledsNumber, spi_inst_t* _spi, uint32_t _datapin, uint32_t _clockpin, int _dmaSize): - LedDriver(_ledsNumber, _datapin, _clockpin, _dmaSize) - { - dmaConfigure(); - resetTime = _resetTime; - - spi_init(_spi, 10000000); - gpio_set_function(_clockpin, GPIO_FUNC_SPI); - gpio_set_function(_datapin, GPIO_FUNC_SPI); - bi_decl(bi_4pins_with_func(PICO_DEFAULT_SPI_RX_PIN, _datapin, _clockpin, PICO_DEFAULT_SPI_CSN_PIN, GPIO_FUNC_SPI)); - - initDmaSpi(_spi, _dmaSize); - } - - uint8_t* getBufferMemory() - { - return buffer; - } - - protected: - - void renderDma() - { - if (isDmaBusy) - return; - - isDmaBusy = true; - - uint64_t currentTime = time_us_64(); - if (currentTime < resetTime + lastRenderTime) - busy_wait_us(std::min(resetTime + lastRenderTime - currentTime, resetTime)); - - memcpy(dma, buffer, dmaSize); - - dma_channel_set_read_addr(PICO_DMA_CHANNEL, dma, true); - } -}; - -template -class DotstarType : public Dotstar -{ - public: - - DotstarType(int _ledsNumber, spi_inst_t* _spi, int _dataPin, int _clockPin) : - Dotstar(RESET_TIME, _ledsNumber, _spi, _dataPin, _clockPin, (_ledsNumber + 2) * sizeof(colorData)) - { - } - - void SetPixel(int index, colorData color) - { - if (index >= ledsNumber) - return; - - *(reinterpret_cast(buffer)+index+1) = color; - } - - void renderSingleLane() - { - memset(buffer,0 ,4); - *(reinterpret_cast(buffer)+ledsNumber+1) = colorData(0xff); - renderDma(); - } -}; - -class Ws2801 : public LedDriver, public DmaClient -{ - uint64_t resetTime; - - public: - Ws2801(uint64_t _resetTime, int _ledsNumber, spi_inst_t* _spi, uint32_t _datapin, uint32_t _clockpin, int _dmaSize): - LedDriver(_ledsNumber, _datapin, _clockpin, _dmaSize) - { - dmaConfigure(); - resetTime = _resetTime; - - spi_init(_spi, 1000000); - gpio_set_function(_clockpin, GPIO_FUNC_SPI); - gpio_set_function(_datapin, GPIO_FUNC_SPI); - bi_decl(bi_4pins_with_func(PICO_DEFAULT_SPI_RX_PIN, _datapin, _clockpin, PICO_DEFAULT_SPI_CSN_PIN, GPIO_FUNC_SPI)); - - initDmaSpi(_spi, _dmaSize); - } - - uint8_t* getBufferMemory() - { - return buffer; - } - - protected: - - void renderDma() - { - if (isDmaBusy) - return; - - isDmaBusy = true; - - uint64_t currentTime = time_us_64(); - if (currentTime < resetTime + lastRenderTime) - busy_wait_us(std::min(resetTime + lastRenderTime - currentTime, resetTime)); - - memcpy(dma, buffer, dmaSize); - - dma_channel_set_read_addr(PICO_DMA_CHANNEL, dma, true); - } -}; - -template -class Ws2801Type : public Ws2801 -{ - public: - - Ws2801Type(int _ledsNumber, spi_inst_t* _spi, int _dataPin, int _clockPin) : - Ws2801(RESET_TIME, _ledsNumber, _spi, _dataPin, _clockPin, _ledsNumber * sizeof(colorData)) - { - } - - void SetPixel(int index, colorData color) - { - if (index >= ledsNumber) - return; - - *(reinterpret_cast(buffer)+index) = color; - } - - void renderSingleLane() - { - renderDma(); - } -}; - -Neopixel* NeopixelParallel::muxer = nullptr; -uint8_t* NeopixelParallel::buffer = nullptr; -int NeopixelParallel::instances = 0; -int NeopixelParallel::maxLeds = 0; -uint DmaClient::PICO_DMA_CHANNEL = 0; -volatile uint64_t DmaClient::lastRenderTime = 0; -volatile bool DmaClient::isDmaBusy = false; - - -// API classes -typedef NeopixelType ws2812; -typedef NeopixelType sk6812; -typedef NeopixelParallelType ws2812p; -typedef NeopixelParallelType sk6812p; -typedef DotstarType<100, ColorDotstartBgr> apa102; -typedef Ws2801Type<500, ColorRgb> ws2801; diff --git a/include/led_bridge/picolada_bridge.h b/include/led_bridge/picolada_bridge.h deleted file mode 100644 index cbc7390..0000000 --- a/include/led_bridge/picolada_bridge.h +++ /dev/null @@ -1,248 +0,0 @@ -/* picolada_bridge.h -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#pragma once - -#include "picolada/picolada.h" -#include "led_bridge.h" - -struct picolada_bridge : public led_bridge -{ - struct ExtSegments{ - LedConfig::Segment segment; - sk6812* singleSk6812 = nullptr; - ws2812* singleWs2812 = nullptr; - sk6812p* multiSk6812 = nullptr; - ws2812p* multiWs2812 = nullptr; - int ledCount = 0; - }; - std::vector _segments; - - uint16_t _totalLedsNumber = 0; - LedType _ledsType = LedType::WS2812; - - apa102* singleApa102 = nullptr; - - int getLedsNumber() override - { - return _totalLedsNumber; - } - - void clearAll() override - { - if (_segments.size() || singleApa102 != nullptr) - { - for (int i = 0; !canRender() && i < 200; i++) { - Log::debug("-"); - delay(1); - } - - for (int i = 0; i < _totalLedsNumber; i++) { - setLedRgb(i, 0 ,0, 0); - } - executeRenderLed(); - - for (int i = 0; !canRender() && i < 200; i++) { - Log::debug("+"); - delay(1); - } - - Log::debug("leds cleared"); - } - } - - bool canRender() override - { - if (_segments.size()) - { - auto& seg = _segments.front(); - - if ((seg).singleSk6812 != nullptr) { return (seg).singleSk6812->isReady(); } - else if ((seg).singleWs2812 != nullptr) { return (seg).singleWs2812->isReady(); } - else if ((seg).multiSk6812 != nullptr) { return (seg).multiSk6812->isReady(); } - else if ((seg).multiWs2812 != nullptr) { return (seg).multiWs2812->isReady(); } - } - else if (singleApa102) - { - return singleApa102->isReady(); - } - return true; - } - - int segmentSupported() override - { - return 8; - } - - bool supportsDoubleBuffering() override { - return true; - } - - void executeRenderLed() override - { - if (_segments.size()) - { - auto& seg = _segments.front(); - - if ((seg).singleSk6812 != nullptr) { (seg).singleSk6812->renderSingleLane(); } - else if ((seg).singleWs2812 != nullptr) { (seg).singleWs2812->renderSingleLane(); } - else if ((seg).multiSk6812 != nullptr) { (seg).multiSk6812->renderAllLanes(); } - else if ((seg).multiWs2812 != nullptr) { (seg).multiWs2812->renderAllLanes(); } - } - else if (singleApa102) - { - singleApa102->renderSingleLane(); - } - } - - void releaseDriverResources() override - { - delay(50); - - for(auto& seg : _segments){ - delete seg.singleSk6812; seg.singleSk6812 = nullptr; - delete seg.singleWs2812; seg.singleWs2812 = nullptr; - delete seg.multiSk6812; seg.multiSk6812 = nullptr; - delete seg.multiWs2812; seg.multiWs2812 = nullptr; - } - _segments.clear(); - - delete singleApa102; singleApa102 = nullptr; - - delay(50); - } - - void initializeLedDriver(LedType cfgLedType, uint16_t cfgLedNumLeds, const std::vector& cfgSegments, - uint8_t calGain, uint8_t calRed, uint8_t calGreen, uint8_t calBlue) override - { - if (cfgSegments.size() < 0) return; - - _totalLedsNumber = cfgLedNumLeds; - _ledsType = cfgLedType; - - if (_ledsType == LedType::SK6812) - { - setParamsAndPrepareCalibration(calGain, calRed, calGreen, calBlue); - } - - if (_ledsType == LedType::WS2812 || _ledsType == LedType::SK6812) - { - bool single = (cfgSegments.size() == 1); - bool error = false; - for(int i = 0; i < cfgSegments.size() && i < 8 && !error; i++) { - const auto& seg = cfgSegments[i]; - - int nextIndex = (i + 1 < cfgSegments.size()) ? cfgSegments[i + 1].startIndex : cfgLedNumLeds; - ExtSegments newSeg{}; - - newSeg.segment = seg; - newSeg.ledCount = std::max(nextIndex - seg.startIndex, 0); - - if (_ledsType == LedType::SK6812) { - if (single) - newSeg.singleSk6812 = new sk6812(newSeg.ledCount, seg.data); - else - newSeg.multiSk6812 = new sk6812p(newSeg.ledCount, cfgSegments[0].data); - } - else { - if (single) - newSeg.singleWs2812 = new ws2812(newSeg.ledCount, seg.data); - else - newSeg.multiWs2812 = new ws2812p(newSeg.ledCount, cfgSegments[0].data); - } - - _segments.push_back(newSeg); - - Log::debug("Created Neopixel segment for ", newSeg.ledCount, " LEDS at: ", seg.startIndex, ", GPIO: ", ((single) ? seg.data : cfgSegments[0].data + i), - ", driver: ", (newSeg.singleSk6812 != nullptr || newSeg.multiSk6812 != nullptr) ? "SK6812" : "WS2812B" ); - - } - } - else - { // SPI (APA102 / SK9822) - auto& segment = cfgSegments.front(); - - singleApa102 = new apa102(cfgLedNumLeds, spi0, segment.data, segment.clock); - - Log::debug("Created SPI segment for ", cfgLedNumLeds, ", DATA: ", segment.data, ", CLOCK: ", segment.clock ); - } - } - - inline void setLedRgb(int index, uint8_t r, uint8_t g, uint8_t b) override - { - if (_ledsType == LedType::SK6812) - { - const ColorRgbw calibrated = rgb2rgbw(r, g, b); - setLedRgbw(index, calibrated.R, calibrated.G, calibrated.B, calibrated.W); - } - else if (_ledsType == LedType::WS2812) - { - for(const auto& seg : _segments) { - if (index < seg.ledCount) { - if ((seg).singleWs2812 != nullptr) { ColorGrb32 c; c.R = r; c.G = g; c.B = b; (seg).singleWs2812->SetPixel(index,c); } - else if ((seg).multiWs2812 != nullptr) { ColorGrb c; c.R = r; c.G = g; c.B = b; (seg).multiWs2812->SetPixel(index,c); } - return; - } - else { - index -= seg.ledCount; - } - } - } - else if (singleApa102) { - ColorDotstartBgr c; - c.R = r; c.G = g; c.B = b; - singleApa102->SetPixel(index, c); - } - } - - inline void setLedRgbw(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) override - { - if (_ledsType == LedType::SK6812) - { - ColorGrbw c; - c.R = r; c.G = g; c.B = b, c.W = w; - for(const auto& seg : _segments) { - if (index < seg.ledCount) { - if ((seg).singleSk6812 != nullptr) { (seg).singleSk6812->SetPixel(index,c); } - else if ((seg).multiSk6812 != nullptr) { (seg).multiSk6812->SetPixel(index,c); } - return; - } - else { - index -= seg.ledCount; - } - } - } - else if (_ledsType == LedType::WS2812) - { - setLedRgb(index, r, g, b); - } - else if (_ledsType == LedType::APA102) - { - setLedRgb(index, r, g, b); - } - } -}; \ No newline at end of file diff --git a/include/leds.h b/include/leds.h deleted file mode 100644 index 04b29a6..0000000 --- a/include/leds.h +++ /dev/null @@ -1,24 +0,0 @@ -// File: include/leds.h -#pragma once -#include -#include -#include "config.h" - -namespace Leds { - bool supportsDoubleBuffering(); - void tryWaitForRenderer(); - void applyLedConfig(); - bool restartRequired(); - int getLedsNumber(); - int segmentSupported(); - void checkDelayedRender(); - void renderLed(bool isNewFrame); - void synchronizeLedsToVolatileStateBeforeDelayedRender(); - - template - void setLed(int index, uint8_t r, uint8_t g, uint8_t b); - - template - void setLedW(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w); -}; - diff --git a/include/logger.h b/include/logger.h deleted file mode 100644 index f75088a..0000000 --- a/include/logger.h +++ /dev/null @@ -1,20 +0,0 @@ -// File: include/logger.h -#pragma once -#include - -namespace Log { - template - void inline debug(Args... args) { - #ifdef DEBUG_LOG - (Serial.print(args), ...); - Serial.println(); - #endif - } - void inline raw(const char* message, char* buffer, int len) { - #ifdef DEBUG_LOG - Serial.print(message); - Serial.write(buffer,len); - Serial.println(); - #endif - } -} \ No newline at end of file diff --git a/include/main.h b/include/main.h index 878badb..6bdbaec 100644 --- a/include/main.h +++ b/include/main.h @@ -1,4 +1,62 @@ // File: include/main.h #pragma once -bool isAPMode(); \ No newline at end of file + +bool isAPMode(); + +namespace WebServerProvider{ + void setupWebServer(); +}; + +namespace Mdns{ + void startMDNS(); +}; + +namespace Log { + #ifdef ENABLE_DEBUG + template + void inline SERIAL_LOG(Args... args) { + (Serial.print(args), ...); + Serial.println(); + } + void inline raw(const char* message, char* buffer, int len) { + Serial.print(message); + Serial.write(buffer,len); + Serial.println(); + } + #else + static inline void noop() {} + #define SERIAL_LOG(...) noop() + #endif +} + +namespace Manager { + void scheduleApplyConfig(); + void scheduleReboot(uint32_t delay_ms); + void cancelScheduledReboot(); + void processEvents(); +}; + +namespace UdpReceiver { + void handleDDP(WiFiUDP& udp); + void handleRealTime(WiFiUDP& udp); + void handleRAW(WiFiUDP& udp); +}; + +namespace Config { + bool loadConfig(); + unsigned int getSeriaPortSpeed(); + bool getWifiCredentials(const char*& ssid, const char*& pass); +}; + +namespace SerialPort { + void init(unsigned int speed); + uint32_t getFreeSerialPortStack(); + #if defined(SERIAL_PORT_LITE) + void processEventsFromLoop(); + #endif +}; + +namespace Leds { + void applyLedConfig(); +}; diff --git a/include/manager.h b/include/manager.h deleted file mode 100644 index 3156535..0000000 --- a/include/manager.h +++ /dev/null @@ -1,17 +0,0 @@ -// File: include/manager.h - -#pragma once - -struct Stats{ - volatile uint16_t renderedFrames = 0; - volatile uint16_t skippedFrames = 0; - uint32_t currentTag = 0; - uint16_t lastIntervalRenderedFrames = 0; - uint16_t lastIntervalSkippedFrames = 0; -}; - -extern Stats stats; - -void managerScheduleApplyConfig(); -void managerScheduleReboot(uint32_t delay_ms); -void managerLoop(); \ No newline at end of file diff --git a/include/mdns_service.h b/include/mdns_service.h deleted file mode 100644 index 73defd2..0000000 --- a/include/mdns_service.h +++ /dev/null @@ -1,9 +0,0 @@ -// File: include/mdns.h -#pragma once - -namespace Mdns{ - String sanitizeMdnsService(String s); - String getDeviceShortMacAddress(); - void startMDNS(); - void endMDNS(); -}; diff --git a/include/ota.h b/include/ota.h deleted file mode 100644 index 5b25068..0000000 --- a/include/ota.h +++ /dev/null @@ -1,7 +0,0 @@ -// File: include/ota.h - -#pragma once - -#include - -void otaUpdateHandler(AsyncWebServer &server); \ No newline at end of file diff --git a/include/pico/tusb_config.h b/include/pico/tusb_config.h new file mode 100644 index 0000000..f39dcce --- /dev/null +++ b/include/pico/tusb_config.h @@ -0,0 +1,91 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUSB_MCU + #define CFG_TUSB_MCU OPT_MCU_RP2040 +#endif + +#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE +#define CFG_TUSB_OS OPT_OS_PICO + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_HID (2) +#define CFG_TUD_CDC (1) +#define CFG_TUD_MSC (1) +#define CFG_TUD_MIDI (0) +#define CFG_TUD_VENDOR (0) + +#define CFG_TUD_CDC_RX_BUFSIZE (4096) +#define CFG_TUD_CDC_TX_BUFSIZE (256) + +#define CFG_TUD_MSC_EP_BUFSIZE (64) + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE (64) + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/include/storage.h b/include/storage.h deleted file mode 100644 index fd6c18c..0000000 --- a/include/storage.h +++ /dev/null @@ -1,9 +0,0 @@ -// File: include/storage.h - -#pragma once -#include "config.h" - -namespace Storage { - bool loadConfig(AppConfig& cfg); - bool saveConfig(const AppConfig& cfg); -}; diff --git a/include/udp_receiver.h b/include/udp_receiver.h deleted file mode 100644 index ff23be3..0000000 --- a/include/udp_receiver.h +++ /dev/null @@ -1,6 +0,0 @@ -// File: include/udp_receiver.h - -#pragma once -void handleDDP(WiFiUDP& udp); -void handleRealTime(WiFiUDP& udp); -void handleRAW(WiFiUDP& udp); \ No newline at end of file diff --git a/include/uni_json_api.h b/include/uni_json_api.h deleted file mode 100644 index 1d8ab70..0000000 --- a/include/uni_json_api.h +++ /dev/null @@ -1,7 +0,0 @@ -// File: include/uni_api.h -#pragma once - -#include -#include - -void setupUniApiJsonHandler(AsyncWebServer &server); diff --git a/include/uni_json_api_oss.h b/include/uni_json_api_oss.h new file mode 100644 index 0000000..e05e2de --- /dev/null +++ b/include/uni_json_api_oss.h @@ -0,0 +1,25 @@ +// File: include/uni_json_api_oss.h +#pragma once + +#include + +class IHttpRequest; +class IHttpResponse; + +namespace UniJsonApi { + void uniConfigJsonResponse(IHttpRequest* request, IHttpResponse* response); + void fillUniConfigJsonResponse(IHttpResponse* response, char* b); +} + +#define TPL_BODY \ + R"raw({"state":{)raw" \ + R"raw("on":#####,"bri":####,"mainseg":0,"lor":0,)raw" \ + R"raw("seg":[{"id":0,"fx":0,"pal":0,"sel":true,"cct":0,"o1":false,"o2":false,"o3":false,"si":0,"m12":0,"bri":255,"start":0,"stop":######,"on":#####,"col":[[####,####,####],[0,0,0],[0,0,0]]}],)raw" \ + R"raw("nl":{"on":false,"dur":60,"mode":1,"tbri":0},)raw" \ + R"raw("udpn":{"send":false,"recv":false}},)raw" \ + R"raw("info":{)raw" \ + R"raw("ver":"0.15.3","vid":2508020,"cn":"######################","name":"###############","mac":"############","arch":"###########","uptime":############,"live":#####,"freeheap":########,)raw" \ + R"raw("leds":{"count":######,"maxseg":1,"lc":1,"seglc":[1],"cct":0,"wv":0,"maxpwr":0,"rgbw":#####},)raw" \ + R"raw("wifi":{"rssi":######,"signal":####,"channel":###},)raw" \ + R"raw("fs":{"u":16,"t":61,"pmt":0}},)raw" \ + R"raw("effects":["Solid"],"palettes":["Default"]})raw" diff --git a/include/utils.h b/include/utils.h deleted file mode 100644 index 32e33a0..0000000 --- a/include/utils.h +++ /dev/null @@ -1,6 +0,0 @@ -// File: include/utils.h - -#pragma once -void rebootDevice(); -String getDeviceArch(); -int getFreeHeap(); \ No newline at end of file diff --git a/include/volatile_state.h b/include/volatile_state.h deleted file mode 100644 index c07752a..0000000 --- a/include/volatile_state.h +++ /dev/null @@ -1,35 +0,0 @@ -// File: include/config.h -#pragma once -#include - -struct VolatileState { - bool on = false; - bool live = false; - bool relayEnabled = false; - struct StaticColor - { - uint8_t red = 0, green = 0, blue = 0; - } staticColor; - uint8_t effect = 0; - uint8_t brightness = 255; - unsigned long streamTimeout = 0; -}; - -namespace Volatile{ - extern const VolatileState& state; - - static constexpr unsigned long defaultStreamTimeout = 6500; - - void updateBrightness(uint8_t brightness); - void updatePowerOn(bool on); - void updateStaticColor(uint8_t red, uint8_t green, uint8_t blue); - void updateStreamTimeout(unsigned long timeout = defaultStreamTimeout); - - void checkStreamTimeout(); - - bool clearUpdatedBrightnessState(); - bool clearUpdatedPowerOnState(); - bool clearUpdatedStaticColorState(); - - void setRelay(bool enable); -}; diff --git a/include/web_server.h b/include/web_server.h deleted file mode 100644 index dc070ee..0000000 --- a/include/web_server.h +++ /dev/null @@ -1,7 +0,0 @@ -// File: include/web_server.h - -#pragma once -#include - -void setupWebServer(AsyncWebServer& server); -String templateProcessor(const String& var); \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 501303d..970f86f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -7,19 +7,24 @@ extra_scripts = pre:scripts/enable_ccache.py pre:scripts/get_version.py pre:scripts/web_embedder.py + pre:scripts/build_effects.py + pre:scripts/smart_backend.py + post:scripts/stats.py post:scripts/collect_firmware.py lib_deps = esp32async/ESPAsyncWebServer @ 3.9.6 bblanchon/ArduinoJson @ 7.4.0 + etlcpp/Embedded Template Library @ 20.44.2 build_flags = -std=gnu++17 -DAPP_NAME=\"Hyperk\" -DPIO_ENV_NAME=\"${PIOENV}\" -DMAX_LEDS=2000 - ; -DDEBUG_LOG ; enable debug log to serial port - ; -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 + -DCORE_DEBUG_LEVEL=0 + -DLOG_LOCAL_LEVEL=0 + ; -DENABLE_DEBUG ; enable debug log to serial port build_unflags = -std=gnu++11 @@ -28,35 +33,27 @@ build_unflags = ; ESP32 and S2, S3, C2, C3, C5, C6 (SDK > 5.x) ; ========================================================= +[end:esp32-enable-cdc] +build_flags = -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 + [env:modern-esp] -platform = https://github.com/pioarduino/platform-espressif32.git#55.03.38-1 +platform = https://github.com/pioarduino/platform-espressif32.git#55.03.39 board_build.partitions = partitions_4mb.csv board_build.flash_mode = dio framework = arduino +build_flags = + ${env.build_flags} + -DCONFIG_ASYNC_TCP_STACK_SIZE=4096 + -DUSE_ASYNC_WEBSERWER=1 + lib_ignore = RPAsyncTCP ESPAsyncTCP ;---------------------------------------------------------- -[env:espressif-esp32-driver] -extends = env:modern-esp - -lib_deps = - ${env:modern-esp.lib_deps} - led_strip=https://components.espressif.com/api/downloads/?object_type=component&object_id=6628801c-aa0f-4af3-9a34-c8edcc5d2aa3 - -build_flags = - ${env:modern-esp.build_flags} - -DUSE_ESPRESSIF_LED_STRIP - -I.pio/libdeps/$PIOENV/led_strip/include - -I.pio/libdeps/$PIOENV/led_strip/src - -I.pio/libdeps/$PIOENV/led_strip/interface - -;---------------------------------------------------------- - [env:multi-esp32-led-strip] extends = env:modern-esp @@ -71,20 +68,6 @@ build_flags = -I.pio/libdeps/$PIOENV/led_strip/src -I.pio/libdeps/$PIOENV/led_strip/interface -;---------------------------------------------------------- - -[env:parlio-esp32-driver] -extends = env:multi-esp32-led-strip - -lib_deps = - ${env:multi-esp32-led-strip.lib_deps} - -build_flags = - ${env:multi-esp32-led-strip.build_flags} - -DUSE_PARLIO_LED_STRIP - -build_unflags = -DUSE_MULTI_ESP32_LED_STRIP - ; ===== ; ESP32 ; ===== @@ -117,11 +100,16 @@ build_flags = [env:esp32s2] extends = env:multi-esp32-led-strip board = esp32-s2-saola-1 +lib_deps = + ${env:multi-esp32-led-strip.lib_deps} + https://github.com/hathach/tinyusb.git#0.20.0 build_flags = ${env:multi-esp32-led-strip.build_flags} - -DMAX_LEDS=1500 + ${end:esp32-enable-cdc.build_flags} + -DUSE_SYNC_WEBSERWER=1 + "-DCFG_TUSB_CONFIG_FILE=\"${PROJECT_DIR}/include/esp32/tusb_config.h\"" -build_unflags = -DMAX_LEDS=2000 +build_unflags = -DARDUINO_USB_MODE=1 -DUSE_ASYNC_WEBSERWER=1 ; ======== ; ESP32-C3 @@ -130,6 +118,9 @@ build_unflags = -DMAX_LEDS=2000 [env:esp32c3] extends = env:multi-esp32-led-strip board = esp32-c3-devkitm-1 +build_flags = + ${env:multi-esp32-led-strip.build_flags} + ${env:esp32-enable-cdc.build_flags} ; ======== ; ESP32-S3 @@ -138,6 +129,9 @@ board = esp32-c3-devkitm-1 [env:esp32s3] extends = env:multi-esp32-led-strip board = esp32-s3-devkitc-1 +build_flags = + ${env:multi-esp32-led-strip.build_flags} + ${env:esp32-enable-cdc.build_flags} ; ======== ; ESP32-C2 @@ -155,16 +149,22 @@ build_flags = ; ======== [env:esp32c6] -extends = env:parlio-esp32-driver +extends = env:multi-esp32-led-strip board = esp32-c6-devkitc-1 +build_flags = + ${env:multi-esp32-led-strip.build_flags} + ${env:esp32-enable-cdc.build_flags} ; ======== ; ESP32-C5 ; ======== [env:esp32c5] -extends = env:parlio-esp32-driver +extends = env:multi-esp32-led-strip board = esp32-c5-devkitc-1 +build_flags = + ${env:multi-esp32-led-strip.build_flags} + ${env:esp32-enable-cdc.build_flags} ; ================================================== ; Custom boards @@ -179,7 +179,7 @@ build_flags = -DETH_PHY_ADDR=1 -DETH_PHY_MDC=23 -DETH_CLK_MODE=ETH_CLOCK_GPIO0_IN - -DGLEDOPTO_GL_C_616WL -DPOWER_RELAY_GPIO=18 -DPOWER_RELAY_INVERT -DETH_PHY_MDIO=33 -DETH_PHY_POWER=5 + -DGLEDOPTO_GL_C_616WL -DPOWER_RELAY_GPIO=18 -DETH_PHY_MDIO=33 -DETH_PHY_POWER=5 [env:esp32-GLEDOPTO_GL_C_615WL] extends = env:esp32 @@ -211,17 +211,15 @@ board = nodemcuv2 board_build.ldscript = eagle.flash.4m1m.ld lib_deps = + SPI ${env.lib_deps} - makuna/NeoPixelBus @ 2.8.4 - -build_unflags = -DMAX_LEDS=2000 build_flags = ${env.build_flags} -DARDUINO_ARCH_ESP8266 - -DUSE_NEOPIXELBUS + -DUSE_ASYNC_WEBSERWER=1 -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY - -DMAX_LEDS=1200 + -DSERIAL_PORT_LITE ; ================================================== ; RP2040/RP2350 (Raspberry Pi Pico W) @@ -235,7 +233,11 @@ board_build.filesystem_size = 1.0m build_flags = ${env.build_flags} -DPICO_STACK_SIZE=8192 + -DUSE_ASYNC_WEBSERWER=1 -DUSE_PICOLADA + -DPIO_FRAMEWORK_ARDUINO_ENABLE_FREERTOS + -DUSE_TINYUSB + "-DCFG_TUSB_CONFIG_FILE=\"${PROJECT_DIR}/include/pico/tusb_config.h\"" ; ====== ; RP2040 @@ -244,9 +246,6 @@ build_flags = [env:pico] extends = env:pico-common board = rpipicow -build_flags = - ${env:pico-common.build_flags} - -Iinclude/led_bridge/picolada/pico ; ====== ; RP2350 @@ -255,6 +254,3 @@ build_flags = [env:pico2] extends = env:pico-common board = rpipico2w -build_flags = - ${env:pico-common.build_flags} - -Iinclude/led_bridge/picolada/pico2 diff --git a/data/img/hyperk.png b/resources/img/hyperk.png similarity index 100% rename from data/img/hyperk.png rename to resources/img/hyperk.png diff --git a/scripts/build_effects.py b/scripts/build_effects.py new file mode 100644 index 0000000..7cdb368 --- /dev/null +++ b/scripts/build_effects.py @@ -0,0 +1,127 @@ +import os +import json +import re +import shutil +Import("env") + +effects_src_dir = env.subst("$PROJECT_DIR/effects") +template_src_file = env.subst("$PROJECT_DIR/include/uni_json_api_oss.h") + +build_dir = env.subst("$BUILD_DIR") +gen_dir = os.path.join(build_dir, "generated_files") +gen_effects_dir = os.path.join(gen_dir, "effects") +output_factory_file = os.path.join(gen_dir, "effects_factory.h") + +def clean_generated_dir(): + if os.path.exists(gen_effects_dir): + shutil.rmtree(gen_effects_dir) + os.makedirs(gen_effects_dir, exist_ok=True) + +def make_safe_name(folder_name): + safe = re.sub(r'[^a-zA-Z0-9]', '_', folder_name) + if safe[0].isdigit(): + safe = "E_" + safe + return safe[:20] + +def generate_system(): + if not os.path.exists(effects_src_dir): + print(f"[\033[91mERROR\033[0m] Directory {effects_src_dir} not found!") + return + + clean_generated_dir() + + effects_json_list = [] + registry_includes = [] + registry_entries = [] + + folders = [f for f in os.listdir(effects_src_dir) if os.path.isdir(os.path.join(effects_src_dir, f))] + if "Solid" not in folders: + print("[\033[91mERROR\033[0m] Required effect 'Solid' is missing in src/effects!") + env.Exit(1) + + folders.sort(key=lambda name: (name != "Solid", name)) + + for folder_name in folders: + src_folder_path = os.path.join(effects_src_dir, folder_name) + + if not os.path.isdir(src_folder_path): + continue + + safe_name = make_safe_name(folder_name) + class_name = f"Effect_{safe_name}" + + src_effect_h = os.path.join(src_folder_path, "effect.h") + if not os.path.exists(src_effect_h): + print(f"[\033[93mWARNING\033[0m] No effect.h found in {folder_name}. Skipping.") + continue + + effects_json_list.append(safe_name) + + dest_folder_path = os.path.join(gen_effects_dir, safe_name) + os.makedirs(dest_folder_path, exist_ok=True) + dest_effect_h = os.path.join(dest_folder_path, "effect.h") + + with open(src_effect_h, "r", encoding="utf-8") as f: + content = f.read() + + new_content = re.sub(r'\bEFFECT_NAMESPACE\b', class_name, content) + + with open(dest_effect_h, "w", encoding="utf-8") as f: + f.write(new_content) + + registry_includes.append(f'#include "effects/{safe_name}/effect.h"') + registry_entries.append(f' {{"{safe_name}", &{class_name}::run }}') + + print(f"[\033[92mSUCCESS\033[0m] Processed effect: {folder_name} -> {class_name}") + + # ========================================== + json_str = json.dumps(effects_json_list) + + tpl_macro_content = "" + if os.path.exists(template_src_file): + with open(template_src_file, "r", encoding="utf-8") as f: + tpl_content = f.read() + + match = re.search(r'#define\s+TPL_BODY\b.*?}\)raw"', tpl_content, flags=re.DOTALL) + + if match: + extracted_macro = match.group(0) + extracted_macro = re.sub(r'"effects":\[.*?\]', f'"effects":{json_str}', extracted_macro, flags=re.DOTALL) + extracted_macro = re.sub(r'#define\s+TPL_BODY\b', '#define TPL_BODY_WITH_EFFECTS', extracted_macro) + tpl_macro_content = extracted_macro + else: + print(f"[\033[91mERROR\033[0m] Could not find TPL_BODY definition in {template_src_file}!") + else: + print(f"[\033[93mWARNING\033[0m] {template_src_file} not found. Skipping JSON injection.") + + + cpp_factory_code = f"""#pragma once +#include +#include +#include +#include "effects.h" + +{tpl_macro_content} + +{chr(10).join(registry_includes)} + +using EffectRunFn = bool (*)(const EffectConfig& effectConfig, EffectSetup &effectSetup, EffectRun& effectRun); +using EffectPair = std::pair; + +constexpr std::size_t NUM_EFFECTS = {len(effects_json_list)}; + +inline const std::array& getEffectsRegistry() {{ + static const std::array registry = {{{{ +{chr(10).join(registry_entries)} + }}}}; + return registry; +}} +""" + with open(output_factory_file, "w", encoding="utf-8") as f: + f.write(cpp_factory_code) + + print(f"[\033[92mSUCCESS\033[0m] Generated {output_factory_file}") + print(f"[\033[96mINFO\033[0m] Injected effects JSON: {json_str}") + +# Start +generate_system() diff --git a/scripts/collect_firmware.py b/scripts/collect_firmware.py index 091e540..f75e611 100644 --- a/scripts/collect_firmware.py +++ b/scripts/collect_firmware.py @@ -7,9 +7,9 @@ Import("env") def run_merge_bin(mcu, factory_path, boot_addr, bootloader, partitions, source_path, env): - cmd_name = "merge_bin" - f_mode_flag = "--flash_mode" - f_size_flag = "--flash_size" + cmd_name = "merge-bin" + f_mode_flag = "--flash-mode" + f_size_flag = "--flash-size" cmd = [ env.subst("$PYTHONEXE"), "-m", "esptool", diff --git a/scripts/smart_backend.py b/scripts/smart_backend.py new file mode 100644 index 0000000..b0a66f1 --- /dev/null +++ b/scripts/smart_backend.py @@ -0,0 +1,102 @@ +import os +import sys +from functools import partial +import urllib.request +import urllib.error +from pathlib import Path +Import("env") + +pioenv = env.subst("$PIOENV") +project_dir = env.subst("$PROJECT_DIR") +build_dir = env.subst("$BUILD_DIR") + +version_file = os.path.join(project_dir, "version") +if not os.path.exists(version_file): + print("\033[91mERROR: 'version' file is missing in the root directory!\033[0m") + Exit(1) + +with open(version_file, "r") as f: + app_version = f.read().strip() + +backend_src_dir = os.path.join(project_dir, "src", "backend") +libbackend_name = f"libBackend_{pioenv}.a" + +# ========================================== +# MAIN SCRIPT DECISION LOGIC +# ========================================== + +if os.path.exists(backend_src_dir): + # ------------------------------------------ + # DEV MODE + # ------------------------------------------ + print(f"--- DEV Mode (Author) | Version: {app_version} | Env: {pioenv} ---") + + env.Append(CCFLAGS=["-DIS_DEV_BUILD"]) + env.Append(CXXFLAGS=["-fno-exceptions", "-fno-rtti"]) + + secret_include_dir = os.path.join(backend_src_dir, "include") + env.Append(SRC_BUILD_FLAGS=["-Isrc/backend/include"]) + + sys.path.append(os.path.join(os.getcwd(), backend_src_dir, "scripts")) + sys.pycache_prefix = os.path.join( build_dir, "pycache") + import create_backend_lib + libBackendInvoker = partial(create_backend_lib.generate_secure_library, pioenv=pioenv, project_dir=project_dir, + build_dir=build_dir, app_version=app_version, libbackend_name=libbackend_name) + env.AddPostAction("buildprog", libBackendInvoker) + +else: + # ------------------------------------------ + # USER MODE + # ------------------------------------------ + print(f"--- USER Mode (Open Source) | Version: {app_version} | Env: {pioenv} ---") + + env.Append(CXXFLAGS=["-fno-exceptions", "-fno-rtti"]) + + cache_dir = os.path.join(project_dir, "export_backend", "precompiled", app_version, pioenv) + os.makedirs(cache_dir, exist_ok=True) + local_lib_path = os.path.join(cache_dir, libbackend_name) + + if not os.path.exists(local_lib_path): + url = f"https://github.com/awawa-dev/Hyperk-Backend/releases/download/{app_version}/{libbackend_name}" + print(f"-> Downloading required backend v{app_version} from GitHub...") + + try: + with urllib.request.urlopen(url, timeout=15) as response, open(local_lib_path, 'wb') as out_file: + out_file.write(response.read()) + print(f"[\033[92mSUCCESS\033[0m] Successfully downloaded and cached.") + except urllib.error.HTTPError as e: + print(f"\033[91mERROR: Cannot find library (HTTP Code {e.code}).\033[0m") + print(f"Probably version '{app_version}' for environment '{pioenv}' has not been released yet.") + print(f"Checked URL: {url}") + Exit(1) + except Exception as e: + print(f"\033[91mCONNECTION ERROR: Make sure you have internet access. ({e})\033[0m") + Exit(1) + else: + print(f"-> Using precompiled backend v{app_version} from cache.") + + env.Append(LIBS=[env.File(local_lib_path)]) + +#////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +if env["PIOENV"] == "esp32s2": + p = Path(env.PioPlatform().get_package_dir("framework-arduinoespressif32")) / "cores" / "esp32" / "esp32-hal-tinyusb.h" + print("Patching:", p) + s = p.read_text(encoding="utf-8") + old = "#include \"tusb_config.h\"" + new = "#if !defined(CFG_TUSB_CONFIG_FILE) || !defined(CONFIG_IDF_TARGET_ESP32S2)\n#include /*patched*/ \"tusb_config.h\"\n#endif" + if old in s and new not in s: + p.write_text(s.replace(old, new), encoding="utf-8") + print("Patched:", p.name) + elif new not in s: + raise RuntimeError(f"Patch failed: expected pattern not found in {p}") + + p = Path(env.PioPlatform().get_package_dir("framework-arduinoespressif32")) / "cores" / "esp32" / "esp32-hal-tinyusb.c" + print("Patching:", p) + s = p.read_text("utf-8") + old = 'xTaskCreate(usb_device_task, "usbd", 4096, NULL, configMAX_PRIORITIES - 1, NULL);' + new = '#if !defined(CONFIG_IDF_TARGET_ESP32S2)\nxTaskCreate(usb_device_task, "usbd", 4096, NULL, configMAX_PRIORITIES - 1, NULL)/*patched*/;\n#else\nxTaskCreate(usb_device_task, "usbd", CFG_TUD_MAINTASK_SIZE, NULL, configMAX_PRIORITIES - 1, NULL)/*patched*/;\n#endif' + if old in s: + p.write_text(s.replace(old, new), "utf-8") + print("Patched:", p.name) + elif new not in s: + raise RuntimeError(f"Patch failed: expected pattern not found in {p}") diff --git a/scripts/stats.py b/scripts/stats.py new file mode 100644 index 0000000..c610792 --- /dev/null +++ b/scripts/stats.py @@ -0,0 +1,41 @@ +import os +import subprocess +from os.path import exists +from pathlib import Path + +Import("env") +def dump_symbols(target, source, env): + elf = os.path.join( + env.subst("$BUILD_DIR"), + env.subst("${PROGNAME}.elf") + ) + + cc = Path(env.subst("$CC")) + nm = cc.with_name(cc.name.replace("-gcc", "-nm")) + + print("ELF:", elf) + print("NM :", nm) + + result = subprocess.run( + [nm, "-SC", "--size-sort", elf], + capture_output=True, + text=True + ) + + print("\n>>> BIG RAM SYMBOLS") + + count = 0 + + for line in reversed(result.stdout.splitlines()): + parts = line.split() + + if len(parts) < 4: + continue + + if parts[2] in ("B", "b", "D", "d"): + print(line) + count += 1 + + if count >= 50: + break +env.AddPostAction("buildprog", dump_symbols) \ No newline at end of file diff --git a/scripts/web_embedder.py b/scripts/web_embedder.py index 445faeb..2b924b5 100644 --- a/scripts/web_embedder.py +++ b/scripts/web_embedder.py @@ -1,13 +1,10 @@ import os import gzip import urllib.parse +from pathlib import Path Import("env") -def embed_web_files(): - data_dir = "data" - # Use the project's include directory defined by PlatformIO - output_file = os.path.join(env.get("PROJECT_INCLUDE_DIR"), "web_resources.h") - +def embed_web_files(data_dir, output_file, sufix=""): if not os.path.exists(data_dir): print(f"[WebEmbedder] Error: {data_dir} directory not found!") return @@ -27,13 +24,38 @@ def embed_web_files(): print(f"[WebEmbedder] Generating {output_file} (version = {app_version})...") files_data = [] + + Path(output_file).parent.mkdir(parents=True, exist_ok=True) with open(output_file, "w", encoding="utf-8") as f: # File header and guards f.write("// Generated file - do not edit\n") - f.write("#ifndef WEB_RESOURCES_H\n#define WEB_RESOURCES_H\n\n#include \n\n") + f.write(f"#ifndef WEB_RESOURCES{sufix}_H\n#define WEB_RESOURCES{sufix}_H\n\n#include \n\n") + + # === combine CSS do hyperk.css === + css_dir = os.path.join(data_dir, "css") + combined_css = b"" + + if not sufix: + combined_css += f'@import url("/css/hyperk_OSS.css?v={app_version}");\n'.encode('utf-8') + + if os.path.exists(css_dir): + css_files_paths = [] + + for c_root, _, c_files in os.walk(css_dir): + for c_file in c_files: + if c_file.endswith(".css"): + css_files_paths.append(os.path.join(c_root, c_file)) + + css_files_paths.sort() + + for css_path in css_files_paths: + with open(css_path, "rb") as f_css: + combined_css += f_css.read() + b"\n" # 1. Generate byte arrays for each file + currentPageIndex = 0 + pageHomeIndex = -1 for root, dirs, files in os.walk(data_dir): for file in files: full_path = os.path.join(root, file) @@ -70,34 +92,52 @@ def embed_web_files(): raw_data = content.encode("utf-8") # Compress data with Gzip - compressed = gzip.compress(raw_data) - f.write(f"const uint8_t PAGE_{var_name}[] PROGMEM = {{ ") + compressed = b"" + if ext == ".css": + if combined_css: + compressed = gzip.compress(combined_css) + var_name = var_name.replace(file.upper().replace(".", "_"), "HYPERK_CSS") + rel_path = rel_path.replace(file, f"hyperk{sufix}.css") + combined_css = b"" + else: + continue + else: + compressed = gzip.compress(raw_data) + + f.write(f"const char PAGE{sufix}_{var_name}[] PROGMEM = {{ ") f.write(", ".join([f"0x{b:02x}" for b in compressed])) f.write(f" }};\n") - f.write(f"const uint32_t PAGE_{var_name}_LEN = {len(compressed)};\n\n") - processor = "" - if file == "stats.html": - processor = file + f.write(f"const uint32_t PAGE{sufix}_{var_name}_LEN = {len(compressed)};\n\n") files_data.append({ "url": "/" + rel_path, - "var": f"PAGE_{var_name}", - "len": f"PAGE_{var_name}_LEN", + "var": f"PAGE{sufix}_{var_name}", + "len": f"PAGE{sufix}_{var_name}_LEN", "mime": mime }) + if file == "index.html": + pageHomeIndex = currentPageIndex + currentPageIndex = currentPageIndex + 1 # 2. Define the Resource structure and the lookup table - f.write("struct WebResource {\n const char* url;\n const uint8_t* data;\n uint32_t len;\n const char* mime;\n};\n\n") + f.write(f"struct WebResource {{\n const char* url;\n PGM_P data;\n uint32_t len;\n const char* mime;\n}};\n\n") - f.write(f"const WebResource webResources[] PROGMEM = {{\n") + f.write(f"{'extern ' if sufix else ''}const WebResource webResources{sufix}[] = {{\n") for file in files_data: f.write(f" {{ \"{file['url']}\", {file['var']}, {file['len']}, \"{file['mime']}\" }},\n") f.write("};\n\n") total_count = len(files_data) - f.write(f"const uint16_t webResourcesCount = {total_count};\n\n") + f.write(f"{'extern ' if sufix else ''}const uint16_t webResourcesCount{sufix} = {total_count};\n\n") + + if pageHomeIndex != -1: + f.write(f"{'extern ' if sufix else ''}const uint16_t pageHomeIndex{sufix} = {pageHomeIndex};\n\n") + f.write("#endif\n") print(f"[WebEmbedder] Successfully created {output_file}") # Execute the embedding process immediately when the script is loaded by PlatformIO -embed_web_files() \ No newline at end of file +generated_include_dir = os.path.join(env.subst("$BUILD_DIR"), "generated_files") +embed_web_files(os.path.join(env.get("PROJECT_SRC_DIR"), "backend", "data"), os.path.join(generated_include_dir, "web_resources.h")) +embed_web_files(os.path.join(env.get("PROJECT_DIR"), "data"), os.path.join(generated_include_dir, "web_resources_OSS.h"), "_OSS") +env.Append(CPPPATH=[generated_include_dir]) diff --git a/src/calibration.cpp b/src/calibration.cpp deleted file mode 100644 index 7564ae8..0000000 --- a/src/calibration.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* calibration.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#if defined(USE_NEOPIXELBUS) - #include -#endif - -#include "leds.h" -#include "calibration.h" - -/* Following code comes from HyperSerialEsp8266 and HyperSerialESP32 as I invented this calibration procedure for RGBW LEDs -* -* HyperSerialEsp8266/HyperSerialESP32 -* MIT License -* -* 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 -* 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. - */ - -#define ROUND_DIVIDE(numer, denom) (((numer) + (denom) / 2) / (denom)) - -namespace -{ - struct ChannelCorrection - { - uint8_t white[256]; - uint8_t red[256]; - uint8_t green[256]; - uint8_t blue[256]; - }; - - // calibration parameters - uint8_t gain = 0xFF; - uint8_t red = 0xA0; - uint8_t green = 0xA0; - uint8_t blue = 0xA0; - - ChannelCorrection* _data = nullptr; - #define channelCorrection (*_data) -} - -void printCalibration() -{ - Log::debug("RGBW => Gain: ",gain ,"/255, red: ",red ,", green: ",green ,", blue: ", blue); -} - -void deleteCalibration() -{ - if (_data != nullptr) - { - delete _data; - _data = nullptr; - } -} - -void prepareCalibration() -{ - if (_data == nullptr) - { - _data = new ChannelCorrection(); - } - - // prepare LUT calibration table, cold white is much better than "neutral" white - for (uint32_t i = 0; i < 256; i++) - { - // color calibration - uint32_t _gain = gain * i; // adjust gain - uint32_t _red = red * i; // adjust red - uint32_t _green = green * i; // adjust green - uint32_t _blue = blue * i; // adjust blue - - channelCorrection.white[i] = (uint8_t)min(ROUND_DIVIDE(_gain, 0xFF), (uint32_t)0xFF); - channelCorrection.red[i] = (uint8_t)min(ROUND_DIVIDE(_red, 0xFF), (uint32_t)0xFF); - channelCorrection.green[i] = (uint8_t)min(ROUND_DIVIDE(_green,0xFF), (uint32_t)0xFF); - channelCorrection.blue[i] = (uint8_t)min(ROUND_DIVIDE(_blue, 0xFF), (uint32_t)0xFF); - } -} - -void setParamsAndPrepareCalibration(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue) -{ - if (gain != _gain || red != _red || green != _green || blue != _blue || _data == nullptr) - { - gain = _gain; - red = _red; - green = _green; - blue = _blue; - prepareCalibration(); - printCalibration(); - } -} - -ColorRgbw rgb2rgbw(uint8_t r, uint8_t g, uint8_t b) -{ - ColorRgbw color; - - color.W = min(channelCorrection.red[r], - min(channelCorrection.green[g], - channelCorrection.blue[b])); - color.R = r - channelCorrection.red[color.W]; - color.G = g - channelCorrection.green[color.W]; - color.B = b - channelCorrection.blue[color.W]; - color.W = channelCorrection.white[color.W]; - return color; -} diff --git a/src/config.cpp b/src/config.cpp deleted file mode 100644 index cc2f491..0000000 --- a/src/config.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* config.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#include "config.h" -#include "storage.h" -#include "volatile_state.h" - -#include - -namespace Config { - AppConfig internalCfg; - const AppConfig& cfg = internalCfg; - - bool loadConfig() { - return Storage::loadConfig(internalCfg); - } - - bool saveConfig(const AppConfig& cfg) { - internalCfg = cfg; - return Storage::saveConfig(internalCfg); - } -}; - -void LedConfig::deserializeSegments(const JsonArray& jsonArray) { - segments.clear(); - - for (JsonVariant value : jsonArray) { - Segment seg; - - seg.data = value["data"] | 2; - seg.clock = value["clock"] | 4; - seg.startIndex = value["startIndex"] | 0; - - Log::debug("Segments restored. Data: ", seg.data, ", clock: ", seg.clock, ", start: ", seg.startIndex); - - segments.push_back(seg); - } -}; - -bool LedConfig::deserializeSegments(const String& rawValues) { - std::vector newSegments; - const char* p = rawValues.c_str(); - char* end; - long buffer[3]; - uint8_t count = 0; - - while (*p != '\0') { - buffer[count++] = strtol(p, &end, 10); - if (count == 3) { - newSegments.push_back({ - static_cast(std::clamp(buffer[0], 0l, 64l)), - static_cast(std::clamp(buffer[1], 0l, 64l)), - static_cast(std::clamp(buffer[2], 0l, 2048l)) - }); - count = 0; - } - - p = end; - - if (*p == ',') { - p++; - } - else { - break; - } - }; - - bool changed = (segments != newSegments) && newSegments.size() > 0; - - Log::debug("Segments changed: ", changed, ", values: ", rawValues, ", detected segments: ", newSegments.size()); - - if (changed) { - segments = std::move(newSegments); - } - - return changed; -}; - -void LedConfig::serializeSegments(JsonArray& jsonArray) const { - for (const auto& seg : segments) { - JsonObject obj = jsonArray.add(); - - obj["data"] = seg.data; - obj["clock"] = seg.clock; - obj["startIndex"] = seg.startIndex; - } -}; \ No newline at end of file diff --git a/src/effects.cpp b/src/effects.cpp new file mode 100644 index 0000000..bd448a0 --- /dev/null +++ b/src/effects.cpp @@ -0,0 +1,59 @@ +#include "effects.h" + +namespace EffectUtils{ + void fast_hsv2rgb(uint8_t h, uint8_t s, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b) { + if (s == 0) { + *r = *g = *b = v; + return; + } + + uint8_t region = h / 43; + uint8_t remainder = (h - region * 43) * 6; + + uint8_t p = (uint16_t)v * (255 - s) >> 8; + uint8_t q = (uint16_t)v * (255 - ((uint16_t)s * remainder >> 8)) >> 8; + uint8_t t = (uint16_t)v * (255 - ((uint16_t)s * (255 - remainder) >> 8)) >> 8; + + switch (region) { + case 0: *r = v; *g = t; *b = p; break; + case 1: *r = q; *g = v; *b = p; break; + case 2: *r = p; *g = v; *b = t; break; + case 3: *r = p; *g = q; *b = v; break; + case 4: *r = t; *g = p; *b = v; break; + default: *r = v; *g = p; *b = q; break; + } + } + + void fast_rgb2hsv(uint8_t r, uint8_t g, uint8_t b, uint8_t *h, uint8_t *s, uint8_t *v) { + uint8_t rgbMin = r < g ? (r < b ? r : b) : (g < b ? g : b); + uint8_t rgbMax = r > g ? (r > b ? r : b) : (g > b ? g : b); + + *v = rgbMax; + + if (*v == 0) { + *h = *s = 0; + return; + } + + uint8_t delta = rgbMax - rgbMin; + + if (delta == 0) { + *h = 0; + *s = 0; + return; + } + + *s = (uint16_t)255 * delta / rgbMax; + + int16_t hue; + if (rgbMax == r) + hue = 43 * (int16_t)(g - b) / delta; + else if (rgbMax == g) + hue = 85 + 43 * (int16_t)(b - r) / delta; + else + hue = 171 + 43 * (int16_t)(r - g) / delta; + + if (hue < 0) hue += 256; + *h = (uint8_t)hue; + } +}; diff --git a/src/leds.cpp b/src/leds.cpp deleted file mode 100644 index 9b22113..0000000 --- a/src/leds.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/* leds.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#include "leds.h" -#include "config.h" -#include "storage.h" -#include "manager.h" -#include "calibration.h" -#include "volatile_state.h" - -////////////////////////////////////////////////////////////////////////////////////////////////// -#if defined(USE_PICOLADA) - #include "led_bridge/picolada_bridge.h" - namespace Leds{ picolada_bridge renderer; } -#elif defined(USE_MULTI_ESP32_LED_STRIP) - #include "led_bridge/multi_esp32_led_strip_bridge.h" - namespace Leds{ - #if defined(CONFIG_IDF_TARGET_ESP32C2) - multi_esp32_led_strip_bridge renderer; - #else - multi_esp32_led_strip_bridge renderer; - #endif - } -#elif defined(USE_FASTLED) - #include "led_bridge/fastled_bridge.h" - namespace Leds{ fastled_bridge renderer; } -#elif defined(USE_PARLIO_LED_STRIP) - #include "led_bridge/parlio_bridge.h" - namespace Leds{ parlio_bridge renderer; } -#elif defined(USE_ESPRESSIF_LED_STRIP) - #include "led_bridge/espressif_bridge.h" - namespace Leds{ espressif_bridge renderer; } -#else - #include "led_bridge/neopixelbus_bridge.h" - namespace Leds{ neopixelbus_bridge renderer; } -#endif - -namespace Leds{ - bool ledDriverInitialized = false; - volatile bool delayedRender = false; - uint16_t briPlus = 256; - - bool restartRequired() - { - return renderer.restartRequired(); - } - - int getLedsNumber() - { - return renderer.getLedsNumber(); - } - - int segmentSupported() - { - return renderer.segmentSupported(); - } - - bool supportsDoubleBuffering() - { - return renderer.supportsDoubleBuffering(); - } - - void tryWaitForRenderer() - { - const int max_waiting = 80; - int wait = 0; - for (wait = 0; !renderer.canRender() && wait < max_waiting; wait++) { - delay(1); - } - - if (wait > 0) { - Log::debug("Had to wait for LED renderer: ", wait); - } - } - - void synchronizeLedsToVolatileStateBeforeDelayedRender() - { - if (delayedRender || !renderer.canRender()) - return; - - bool updated = false; - if (Volatile::clearUpdatedBrightnessState()) - { - updated = true; - Log::debug("Updating brightness to: ", Volatile::state.brightness); - briPlus = Volatile::state.brightness + 1; - } - - if (Volatile::clearUpdatedPowerOnState()) - { - updated = true; - Log::debug("Updating power on to: ", Volatile::state.on); - } - - if (Volatile::clearUpdatedStaticColorState()) - { - updated = true; - Log::debug("Updating static color to: {", Volatile::state.staticColor.red, ", ", Volatile::state.staticColor.green, ", ", Volatile::state.staticColor.blue, "}"); - } - - if (updated) - { - tryWaitForRenderer(); - - 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; - - Volatile::setRelay(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); - } - - renderLed(true); - } - } - - void initLEDs(LedType cfgLedType, uint16_t cfgLedNumLeds, const std::vector& cfgSegments, - uint8_t calGain, uint8_t calRed, uint8_t calGreen, uint8_t calBlue) { - renderer.clearAll(); - - if (renderer.restartRequired()){ - if (ledDriverInitialized) - { - if (cfgLedType == LedType::SK6812) { - setParamsAndPrepareCalibration(calGain, calRed, calGreen, calBlue); - } - return; - } - } - - renderer.releaseDriverResources(); - - if (cfgLedType != LedType::SK6812) - { - deleteCalibration(); - } - - delayedRender = false; - - tryWaitForRenderer(); - - // LED controller setup - renderer.initializeLedDriver(cfgLedType, cfgLedNumLeds, cfgSegments, calGain, calRed, calGreen, calBlue); - - renderer.clearAll(); - - ledDriverInitialized = true; - } - - void applyLedConfig() - { - const AppConfig& cfg = Config::cfg; - - Volatile::setRelay(cfg.led.r || cfg.led.g || cfg.led.b); - initLEDs(cfg.led.type, cfg.led.numLeds, cfg.led.segments, cfg.led.calibration.gain, cfg.led.calibration.red, cfg.led.calibration.green, cfg.led.calibration.blue); - Volatile::updateBrightness(cfg.led.brightness); - Volatile::updatePowerOn(cfg.led.r || cfg.led.g || cfg.led.b); - 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) - { - if constexpr (applyBrightness) { - r = scaleBri(r); - g = scaleBri(g); - b = scaleBri(b); - } - - renderer.setLedRgb(index, r, g, b); - } - - 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); - } - - renderer.setLedRgbw(index, r, g, b, w); - } - - void checkDelayedRender() - { - if (delayedRender) - { - renderLed(false); - } - } - - void queueRender(bool isNewFrame) - { - if (isNewFrame && delayedRender) - { - stats.skippedFrames = stats.skippedFrames + 1; - } - delayedRender = true; - } - - void renderLed(bool isNewFrame) - { - if (!renderer.canRender()) - { - queueRender(isNewFrame); - } - else - { - renderer.executeRenderLed(); - delayedRender = false; - stats.renderedFrames = stats.renderedFrames + 1; - } - } - - template void setLed(int, uint8_t, uint8_t, uint8_t); - 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/main.cpp b/src/main.cpp index b5faad1..e6a5636 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,47 +30,60 @@ #if defined(ARDUINO_ARCH_ESP8266) #include #include + #include #elif defined(ARDUINO_ARCH_ESP32) #include #include #ifdef WEBSERVER_USE_ETHERNET #include #endif + #include #elif defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) #include #include + #include +#endif + +#ifdef USE_ASYNC_WEBSERWER + #include +#elif defined(USE_SYNC_WEBSERWER) + #include +#else + #include #endif -#include #include #include #include -#include "config.h" -#include "storage.h" -#include "mdns_service.h" -#include "web_server.h" -#include "udp_receiver.h" -#include "leds.h" -#include "manager.h" +#include "main.h" +#include "web_resources_OSS.h" namespace { DNSServer dnsServer; - AsyncWebServer server(80); WiFiUDP udpDDP, udpRealTime, udpRAW; bool inAPMode = false; - bool hasEthernet = false; -} - -void startAP() { - inAPMode = true; - WiFi.mode(WIFI_AP); - WiFi.softAP(APP_NAME "-Setup"); - delay(200); - IPAddress IP = WiFi.softAPIP(); - dnsServer.setErrorReplyCode(DNSReplyCode::NoError); - dnsServer.start(53, "*", IP); - Log::debug("Captive Portal Ready at: ", IP); + bool hasEthernet = false; + bool scheduledApRestart = false; + + void startAP(bool scheduleAutomaticRestartToReconnect) { + constexpr uint16_t DNS_PORT = 53; + inAPMode = true; + WiFi.mode(WIFI_AP); + WiFi.softAP(APP_NAME "-Setup"); + delay(200); + IPAddress IP = WiFi.softAPIP(); + dnsServer.setErrorReplyCode(DNSReplyCode::NoError); + dnsServer.start(DNS_PORT, "*", IP); + Log::SERIAL_LOG("Captive Portal Ready at: ", IP); + + if (scheduleAutomaticRestartToReconnect) { + constexpr auto restartTime = 3 * 60000; + Log::SERIAL_LOG("Scheduling automatic restart in ", (restartTime / 1000), " seconds to reconnect to the configured WiFi network"); + Manager::scheduleReboot(restartTime); + scheduledApRestart = true; + } + } } bool isAPMode() { @@ -78,25 +91,24 @@ bool isAPMode() { } void setup() { - Serial.begin(115200); - delay(500); - - #ifdef DEBUG_LOG - delay(8000); - #endif - #if defined(ARDUINO_ARCH_ESP32) LittleFS.begin(true); #else if (!LittleFS.begin()) { - Log::debug("FS Mount failed, formatting..."); + Log::SERIAL_LOG("FS Mount failed, formatting..."); LittleFS.format(); LittleFS.begin(); } #endif + #ifdef ENABLE_DEBUG + delay(8000); + #endif + Config::loadConfig(); + SerialPort::init(Config::getSeriaPortSpeed()); + Leds::applyLedConfig(); #ifdef WEBSERVER_USE_ETHERNET @@ -105,30 +117,30 @@ void setup() { unsigned long timeout = millis() + 8000; while (millis() < timeout) { if (auto localIp = ETH.localIP(); localIp != IPAddress(0, 0, 0, 0)) { - Log::debug("Ethernet Connected → ", localIp.toString()); + Log::SERIAL_LOG("Ethernet Connected → ", localIp.toString()); hasEthernet = true; break; } if (millis() > (timeout - 5000) && !ETH.linkUp()) { - Log::debug("The cable is disconnected. Give up waiting for ethernet connection."); + Log::SERIAL_LOG("The cable is disconnected. Give up waiting for ethernet connection."); break; } delay(500); - Log::debug("."); + Log::SERIAL_LOG("."); } if (!hasEthernet) { - Log::debug("Starting WiFi Fallback..."); + Log::SERIAL_LOG("Starting WiFi Fallback..."); } - #endif - - const AppConfig& cfg = Config::cfg; + #endif // WiFi connection with fallback if (!hasEthernet){ - if (cfg.wifi.ssid.length() > 0) + const char *ssid = nullptr, *pass = nullptr; + bool hasWifiConfig = Config::getWifiCredentials(ssid, pass); + if (hasWifiConfig) { - WiFi.begin(cfg.wifi.ssid.c_str(), cfg.wifi.password.c_str()); + WiFi.begin(ssid, pass); uint32_t timeout = millis() + 12000; while (WiFi.status() != WL_CONNECTED && millis() < timeout) { @@ -138,46 +150,66 @@ void setup() { if (WiFi.status() != WL_CONNECTED) { - startAP(); + startAP(hasWifiConfig); } else { - Log::debug("Connected → ", WiFi.localIP()); + Log::SERIAL_LOG("Connected → ", WiFi.localIP()); } } - Mdns::startMDNS(); - setupWebServer(server); - server.begin(); + if (!inAPMode) { + Mdns::startMDNS(); + } + + WebServerProvider::setupWebServer(); + + Log::SERIAL_LOG("HTTP Server started"); - Log::debug("HTTP Server started"); + if (!inAPMode) { + udpDDP.begin(4048); + Log::SERIAL_LOG("UDP DDP listener started on port 4048"); + udpRealTime.begin(21324); + Log::SERIAL_LOG("UDP RealTime listener started on port 21324"); + udpRAW.begin(5568); + Log::SERIAL_LOG("Raw RGB color stream listener started on port 5568"); + } - udpDDP.begin(4048); - Log::debug("UDP DDP listener started on port 4048"); - udpRealTime.begin(21324); - Log::debug("UDP RealTime listener started on port 21324"); - udpRAW.begin(5568); - Log::debug("Raw RGB color stream listener started on port 5568"); + (void)Update; } void loop() -{ - handleDDP(udpDDP); - handleRealTime(udpRealTime); - handleRAW(udpRAW); - managerLoop(); +{ + #if defined(SERIAL_PORT_LITE) + SerialPort::processEventsFromLoop(); + #endif + + if (!inAPMode) { + UdpReceiver::handleDDP(udpDDP); + UdpReceiver::handleRealTime(udpRealTime); + UdpReceiver::handleRAW(udpRAW); + } + + Manager::processEvents(); // external libraries if (inAPMode) { dnsServer.processNextRequest(); + if (scheduledApRestart && WiFi.softAPgetStationNum()){ + scheduledApRestart = false; + Log::SERIAL_LOG("Client has connected to Hyperk AP. Cancelling scheduled restart."); + Manager::cancelScheduledReboot(); + } } #if !defined(ARDUINO_ARCH_ESP32) - MDNS.update(); + if (!inAPMode) { + MDNS.update(); + } #endif - #if defined(DEBUG_LOG) && (defined(ESP32) || defined(ESP8266)) + #if defined(ENABLE_DEBUG) && (defined(ESP32) || defined(ESP8266)) static uint32_t lastAlive = 0; if (millis() - lastAlive > 1000) { lastAlive = millis(); @@ -189,7 +221,10 @@ void loop() log1 = ESP.getMaxFreeBlockSize(); log2 = ESP.getHeapFragmentation(); #endif - Serial.printf("[MEM_REPORT %lu] Heap: %6u | Largest block: %6u | Min free/frag: %6u\n", millis(), ESP.getFreeHeap(), log1, log2); + if (auto serialRes = SerialPort::getFreeSerialPortStack(); serialRes) + Log::SERIAL_LOG("[MEM_REPORT ", millis(), "] Heap: ", ESP.getFreeHeap()," | Largest block: ", log1," | Min free/frag: ", log2," | SerialTaskMem: ", serialRes); + else + Log::SERIAL_LOG("[MEM_REPORT ", millis(), "] Heap: ", ESP.getFreeHeap()," | Largest block: ", log1," | Min free/frag: ", log2); } #endif } \ No newline at end of file diff --git a/src/manager.cpp b/src/manager.cpp deleted file mode 100644 index 2c99351..0000000 --- a/src/manager.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* manager.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#include -#include "utils.h" -#include "config.h" -#include "leds.h" -#include "manager.h" -#include "mdns_service.h" -#include "volatile_state.h" - -Stats stats; - -namespace -{ - volatile bool _pendingApplyConfig = false; - volatile bool _pendingReboot = false; - unsigned long _rebootTime = 0; -} - -void managerScheduleApplyConfig() -{ - _pendingApplyConfig = true; -} - -void managerScheduleReboot(uint32_t delay_ms) -{ - _rebootTime = millis() + delay_ms; - _pendingReboot = true; - - Log::debug("--- REBOOT SCHEDULED ---"); - Log::debug("Current millis: ", millis()); - Log::debug("Reboot target : ", _rebootTime); -} - -void managerLoop() -{ - Volatile::checkStreamTimeout(); - - Leds::synchronizeLedsToVolatileStateBeforeDelayedRender(); - Leds::checkDelayedRender(); - - if (_pendingReboot) - { - if (millis() >= _rebootTime) { - rebootDevice(); - } - else { - Mdns::endMDNS(); - } - } - - if (_pendingApplyConfig && !_pendingReboot) - { - _pendingApplyConfig = false; - Leds::applyLedConfig(); - } - - uint32_t currentTag = millis() / 1000; - if (currentTag != stats.currentTag) - { - stats.lastIntervalRenderedFrames = stats.renderedFrames; - stats.lastIntervalSkippedFrames = stats.skippedFrames; - stats.renderedFrames = 0; - stats.skippedFrames = 0; - stats.currentTag = currentTag; - } -} \ No newline at end of file diff --git a/src/mdns_service.cpp b/src/mdns_service.cpp deleted file mode 100644 index 9ef335f..0000000 --- a/src/mdns_service.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* mdns_service.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#include - -#if defined(ARDUINO_ARCH_ESP8266) - #include - #include -#elif defined(ARDUINO_ARCH_ESP32) - #include - #include -#else - #include - #include -#endif - -#include "config.h" -#include "mdns_service.h" -#include "utils.h" - - namespace Mdns{ - bool mdnsInitialized = false; - String deviceShortMacAddress = ""; - - String sanitizeMdnsService(String s) { - s.toLowerCase(); - - char buf[16] = {0}; - int j = 0; - bool lastDash = false; - - for (unsigned int i = 0; i < s.length() && j < 15; i++) { - char c = s[i]; - if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { - buf[j++] = c; - lastDash = false; - } else if (!lastDash && j > 0) { - buf[j++] = '-'; - lastDash = true; - } - } - - if (lastDash) j--; - buf[j] = '\0'; - - - if (j > 0) - { - Log::debug("Validated MDNS service name: ", buf); - return String(buf); - } - else - { - Log::debug("MDNS service name is invalid: ", s); - return String(""); - } - } - - String getDeviceShortMacAddress() { - - if (deviceShortMacAddress.isEmpty()) - { - deviceShortMacAddress = WiFi.macAddress(); - deviceShortMacAddress.replace(":", ""); - deviceShortMacAddress.toLowerCase(); - } - - return deviceShortMacAddress; - } - - - void startMDNS() - { - const AppConfig& cfg = Config::cfg; - - endMDNS(); - - if (cfg.deviceName.isEmpty()) { - Log::debug("Skipping mDNS initialization: device name is empty"); - return; - } - - if (mdnsInitialized = MDNS.begin(cfg.deviceName.c_str()); mdnsInitialized) - { - String macId = getDeviceShortMacAddress(); - - // 1. Extra mdns tag - if (cfg.extraMdnsTag.length() > 0) - { - auto extraMDNS = sanitizeMdnsService(cfg.extraMdnsTag); - MDNS.addService(extraMDNS, "tcp", 80); - MDNS.addServiceTxt(extraMDNS, "tcp", "id", macId.c_str()); - String fullModelName = String(APP_NAME) + "-" + String(APP_VERSION); - MDNS.addServiceTxt(extraMDNS, "tcp", "mdl", fullModelName.c_str()); - MDNS.addServiceTxt(extraMDNS, "tcp", "mac", WiFi.macAddress().c_str()); - MDNS.addServiceTxt(extraMDNS, "tcp", "src", "udp"); - } - - // 2. Hyperk services - auto ourMDNS = sanitizeMdnsService(APP_NAME); - MDNS.addService(ourMDNS, "tcp", 80); - MDNS.addServiceTxt(ourMDNS, "tcp", "ver", APP_VERSION); - MDNS.addServiceTxt(ourMDNS, "tcp", "id", macId.c_str()); - - MDNS.addService("ddp", "udp", 4048); - MDNS.addService("realtime", "udp", 21324); - MDNS.addService("raw", "udp", 5568); - - Log::debug("mDNS: ", cfg.deviceName, ".local (", APP_NAME, " v", APP_VERSION, ")"); - } - } - - void endMDNS() { - if (mdnsInitialized) { - mdnsInitialized = false; - MDNS.end(); - } - } - -} diff --git a/src/ota.cpp b/src/ota.cpp deleted file mode 100644 index ea22dc5..0000000 --- a/src/ota.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* ota.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#include -#if defined(ESP32) - #include -#elif defined(ESP8266) - #include -#elif defined(ARDUINO_ARCH_RP2040) - #include -#endif -#include "ota.h" -#include "logger.h" -#include "manager.h" -#include "utils.h" - -namespace { - bool customValidationFailed = false; -} - -void otaUpdateHandler(AsyncWebServer &server) { - server.on("/ota", HTTP_POST, [](AsyncWebServerRequest *request) { - bool error = Update.hasError() || customValidationFailed; - AsyncWebServerResponse *response = request->beginResponse(error ? 500 : 200, "text/plain", error ? "FAIL" : "OK"); - response->addHeader("Connection", "close"); - request->send(response); - - if (!error) { - Log::debug("OTA Update successful. Rebooting..."); - managerScheduleReboot(1000); - } - }, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - if (!index) { - Log::debug("OTA Update Start: ", filename); - - customValidationFailed = false; - - if (request->hasHeader("hyperk-ota-firmware-name")) { - String firmwareName = request->getHeader("hyperk-ota-firmware-name")->value(); - firmwareName.toLowerCase(); - String expectedPlatform = String(PIO_ENV_NAME) + "."; - Log::debug("OTA firmware name: ", firmwareName, ", searching for: ", expectedPlatform, " tag"); - if (firmwareName.indexOf(expectedPlatform) < 0) { - #ifdef ARDUINO_ARCH_ESP32 - Update.abort(); - #endif - customValidationFailed = true; - Log::debug("OTA incorrect firmware version"); - return; - } - } else { - #ifdef ARDUINO_ARCH_ESP32 - Update.abort(); - #endif - customValidationFailed = true; - Log::debug("OTA missing hyperk-ota-firmware-name header"); - return; - } - -#if defined(ESP8266) - Update.runAsync(true); - size_t maxSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; -#elif defined(ARDUINO_ARCH_RP2040) - size_t maxSpace = 0; -#elif defined(ARDUINO_ARCH_ESP32) - size_t maxSpace = UPDATE_SIZE_UNKNOWN; -#endif - if (request->hasHeader("hyperk-ota-firmware-size")) { - maxSpace = request->getHeader("hyperk-ota-firmware-size")->value().toInt(); - Log::debug("OTA expected firmware size: ", maxSpace); - } else { - #ifdef ARDUINO_ARCH_ESP32 - Update.abort(); - #endif - customValidationFailed = true; - Log::debug("OTA missing hyperk-ota-firmware-size header"); - return; - } - - if (!Update.begin(maxSpace, U_FLASH)) { - Update.printError(Serial); - } - } - - if (!Update.hasError() && !customValidationFailed) { - if (Update.write(data, len) != len) { - Update.printError(Serial); - } - } - - if (final) { - if (Update.end()) { - Log::debug("OTA Update Success: ", index + len, " bytes"); - } else { - Update.printError(Serial); - } - } - }); -} \ No newline at end of file diff --git a/src/storage.cpp b/src/storage.cpp deleted file mode 100644 index 2fe2fa8..0000000 --- a/src/storage.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* storage.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#include "storage.h" -#include - -namespace Storage { - - bool loadConfig(AppConfig& cfg) { - if (!LittleFS.exists(CONFIG_FILE)) { - Log::debug("No config file → creating default"); - return saveConfig(cfg); - } - - File file = LittleFS.open(CONFIG_FILE, "r"); - JsonDocument doc; - DeserializationError err = deserializeJson(doc, file); - file.close(); - - if (err) { - LittleFS.format(); - LittleFS.begin(); - Log::debug("JSON parse error → creating default"); - return saveConfig(cfg); - } - - cfg.wifi.ssid = doc["wifi"]["ssid"] | ""; - cfg.wifi.password = doc["wifi"]["password"] | ""; - cfg.deviceName = doc["deviceName"] | APP_NAME; - cfg.extraMdnsTag = doc["extraMdnsTag"] | "wled"; - - cfg.led.type = static_cast(doc["led"]["type"] | static_cast(LedType::WS2812)); - - if (JsonArray jsonSegments = doc["segments"].as(); !jsonSegments.isNull() && jsonSegments.size() > 0) { - cfg.led.deserializeSegments(jsonSegments); - } - else { - Log::debug("Cannot find segments"); - LedConfig::Segment seg; - seg.data = doc["led"]["dataPin"] | 2; - seg.clock = doc["led"]["clockPin"] | 4; - seg.startIndex = 0; - cfg.led.segments.clear(); - cfg.led.segments.push_back(seg); - } - - JsonObject jsonRelay = doc["relay"].as(); - cfg.led.relay.gpio = jsonRelay["relay-gpio"] | POWER_RELAY_GPIO; - cfg.led.relay.inverted = jsonRelay["relay-inverted"] | POWER_RELAY_INVERT_BOOL; - - cfg.led.numLeds = doc["led"]["numLeds"] | 16; - cfg.led.brightness = doc["led"]["brightness"] | 255; - cfg.led.r = doc["led"]["r"] | 196; - cfg.led.g = doc["led"]["g"] | 32; - cfg.led.b = doc["led"]["b"] | 8; - cfg.led.effect = doc["led"]["effect"] | 0; - - cfg.led.calibration.gain = doc["calibration"]["gain"] | 0xFF; - cfg.led.calibration.red = doc["calibration"]["red"] | 0xA0; - cfg.led.calibration.green = doc["calibration"]["green"] | 0xA0; - cfg.led.calibration.blue = doc["calibration"]["blue"] | 0xA0; - - return true; - } - - bool saveConfig(const AppConfig& cfg) { - JsonDocument doc; - - doc["wifi"]["ssid"] = cfg.wifi.ssid; - doc["wifi"]["password"] = cfg.wifi.password; - doc["deviceName"] = cfg.deviceName; - doc["extraMdnsTag"] = cfg.extraMdnsTag; - - doc["led"]["type"] = static_cast(cfg.led.type); - - JsonArray segArray = doc["segments"].to(); - cfg.led.serializeSegments(segArray); - - JsonObject jsonRelay = doc["relay"].to(); - jsonRelay["relay-gpio"] = cfg.led.relay.gpio; - jsonRelay["relay-inverted"] = cfg.led.relay.inverted; - - doc["led"]["numLeds"] = cfg.led.numLeds; - doc["led"]["brightness"] = cfg.led.brightness; - doc["led"]["r"] = cfg.led.r; - doc["led"]["g"] = cfg.led.g; - doc["led"]["b"] = cfg.led.b; - doc["led"]["effect"] = cfg.led.effect; - - doc["calibration"]["gain"] = cfg.led.calibration.gain; - doc["calibration"]["red"] = cfg.led.calibration.red; - doc["calibration"]["green"] = cfg.led.calibration.green; - doc["calibration"]["blue"] = cfg.led.calibration.blue; - - File file = LittleFS.open(CONFIG_FILE, "w"); - if (!file) return false; - serializeJson(doc, file); - file.flush(); - file.close(); - - #ifdef DEBUG_LOG - String output; - serializeJson(doc, output); - Log::debug("Saving config: ", output); - #endif - - return true; - } -}; \ No newline at end of file diff --git a/src/udp_receiver.cpp b/src/udp_receiver.cpp deleted file mode 100644 index f057058..0000000 --- a/src/udp_receiver.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/* udp_receiver.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#include -#include -#include "udp_receiver.h" -#include "config.h" -#include "leds.h" -#include "volatile_state.h" - -namespace { - struct DDPHeader { - uint8_t flags; - uint8_t reserved; - uint8_t type; - uint8_t channel; - uint32_t offset; - uint16_t len; - } __attribute__((packed)); - - constexpr auto MTU_SIZE = 1500; -} - -void clearBufferUDP(WiFiUDP &udp, int howMuch) { - while (howMuch-- > 0 ) { - udp.read(); - } -} - -void handleDDP(WiFiUDP& udp) { - int packetSize = udp.parsePacket(); - if (packetSize < 5 || packetSize > MTU_SIZE) - { - clearBufferUDP(udp, packetSize); - return; - } - - if (!Leds::supportsDoubleBuffering()) { - Leds::tryWaitForRenderer(); - } - - 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); - DDPHeader* hdr = reinterpret_cast(&(buffer[0])); - auto rgb = &(buffer[0]) + sizeof(DDPHeader); - - if ((hdr->flags & 0xc0) != 0x40) return; - - if (hdr->flags & 0x02) { - hdr->flags = 0x40 | 0x04; - hdr->type = 0x10; - - char info[128]; - int infoLen = snprintf(info, sizeof(info), - "1;%d;%s;%s;%s;%d;4048", - 1, APP_NAME, "PicoESP", APP_VERSION, Leds::getLedsNumber()); - - hdr->len = __builtin_bswap16(infoLen); - - udp.beginPacket(udp.remoteIP(), udp.remotePort()); - udp.write((uint8_t*)hdr, sizeof(DDPHeader)); - udp.write((uint8_t*)info, infoLen); - udp.endPacket(); - return; - } - - uint32_t channelOffset = __builtin_bswap32(hdr->offset); - uint16_t dataLen = __builtin_bswap16(hdr->len); - int expectedSize = sizeof(DDPHeader) + dataLen + ((hdr->flags & 0x10) ? 4 : 0); - - if (packetSize < expectedSize) - return; - - if (hdr->flags & 0x10) { - rgb += 4; - } - - const int bytesPerLed = ((hdr->type & 0x38) == 0x18 || hdr->type == 0x03) ? 4 : 3; - const int offset = channelOffset / bytesPerLed; - const int maxLedNumber = Leds::getLedsNumber(); - - if (bytesPerLed == 4) - { - for (int i = offset; rgb + 3 < endBuffer && i < maxLedNumber; rgb += 4) - setPixelW(i++, rgb[0], rgb[1], rgb[2], rgb[3]); - } - else - { - for (int i = offset; rgb + 2 < endBuffer && i < maxLedNumber; rgb += 3) - setPixel(i++, rgb[0], rgb[1], rgb[2]); - } - - if (hdr->flags & 0x01) { - Volatile::updateStreamTimeout(); - Leds::renderLed(true); - } -} - -void handleRealTime(WiFiUDP& udp) { - int packetSize = udp.parsePacket(); - if (packetSize < 5 || packetSize >= MTU_SIZE) - { - clearBufferUDP(udp, packetSize); - return; - } - - if (!Leds::supportsDoubleBuffering()) { - Leds::tryWaitForRenderer(); - } - - 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); - - const uint8_t protocolType = buffer[0]; - const int maxLedNumber = Leds::getLedsNumber(); - const uint8_t* rgb = nullptr; - - // 0x01 = WARLS, 0x02 = DNRGB, 0x04 = DRGB (indexed), 0x03 = DRGBW support (RGBW) - if (protocolType == 0x02) { - rgb = &(buffer[2]); - for (int i = 0; rgb + 2 < endBuffer && i < maxLedNumber; rgb += 3) { - setPixel(i++, rgb[0], rgb[1], rgb[2]); - } - } - else if (protocolType == 0x04) { - uint16_t offset = 0; - - // offset detection: pocket size including 2 header + optional 2 offset % 3 == 1, guuarded by packetSize >= 4 - if (packetSize % 3 == 1) - { - offset = (buffer[2] << 8) | buffer[3]; - rgb = &(buffer[4]); - } - else - { - rgb = &(buffer[2]); - } - - for (int i = offset; rgb + 2 < endBuffer && i < maxLedNumber; rgb += 3) { - setPixel(i++, rgb[0], rgb[1], rgb[2]); - } - } - else if (protocolType == 0x03) { // DRGBW support (r, g, b, w...) - rgb = &(buffer[2]); - for (int i = 0; rgb + 3 < endBuffer && i < maxLedNumber; rgb += 4) { - setPixelW(i++, rgb[0], rgb[1], rgb[2], rgb[3]); - } - } - else - return; - - Volatile::updateStreamTimeout(); - Leds::renderLed(true); -} - -void handleRAW(WiFiUDP& udp) -{ - int packetSize = udp.parsePacket(); - if (packetSize < 3 || packetSize >= MTU_SIZE) - { - clearBufferUDP(udp, packetSize); - return; - } - - if (!Leds::supportsDoubleBuffering()) { - Leds::tryWaitForRenderer(); - } - - const bool brightnessControl = (Volatile::state.brightness != 255); - auto setPixel = brightnessControl ? Leds::setLed : Leds::setLed; - [[maybe_unused]] auto setPixelW = brightnessControl ? Leds::setLedW : Leds::setLedW; - - uint8_t buffer[packetSize]; - uint8_t* endBuffer = &(buffer[0]) + udp.read(buffer, packetSize); - - const int maxLedNumber = Leds::getLedsNumber(); - const uint8_t* rgb = &(buffer[0]); - - for (int i = 0; rgb + 2 < endBuffer && i < maxLedNumber; rgb += 3) { - setPixel(i++, rgb[0], rgb[1], rgb[2]); - } - - Volatile::updateStreamTimeout(); - Leds::renderLed(true); -} diff --git a/src/uni_json_api.cpp b/src/uni_json_api.cpp deleted file mode 100644 index e337d58..0000000 --- a/src/uni_json_api.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include "uni_json_api.h" -#include "config.h" -#include "utils.h" -#include "leds.h" -#include "volatile_state.h" -#include "mdns_service.h" - -#include - -#if defined(ESP8266) - #include -#else - #include -#endif - -#define TPL_BODY \ - R"raw({"state":{)raw" \ - R"raw("on":#####,"bri":####,"mainseg":0,"lor":0,)raw" \ - R"raw("seg":[{"id":0,"fx":0,"pal":0,"sel":true,"cct":0,"o1":false,"o2":false,"o3":false,"si":0,"m12":0,"bri":255,"start":0,"stop":######,"on":#####,"col":[[####,####,####],[0,0,0],[0,0,0]]}],)raw" \ - R"raw("nl":{"on":false,"dur":60,"mode":1,"tbri":0},)raw" \ - R"raw("udpn":{"send":false,"recv":false}},)raw" \ - R"raw("info":{)raw" \ - R"raw("ver":"0.15.3","vid":2508020,"cn":"######################","name":"###############","mac":"############","arch":"###########","uptime":############,"live":#####,"freeheap":########,)raw" \ - R"raw("leds":{"count":######,"maxseg":1,"lc":1,"seglc":[1],"cct":0,"wv":0,"maxpwr":0,"rgbw":#####},)raw" \ - R"raw("wifi":{"rssi":######,"signal":####,"channel":###},)raw" \ - R"raw("fs":{"u":16,"t":61,"pmt":0}},)raw" \ - R"raw("effects":["Solid"],"palettes":["Default"]})raw" - -static const char JSON_TPL[] PROGMEM = TPL_BODY; -static constexpr char TPL_CONST[] = TPL_BODY; - -constexpr int getTokenPos(const char* target, int start_at = 0) { - int i = start_at; - while (TPL_CONST[i] != '\0') { - bool found = true; int j = 0; - while (target[j] != '\0') { - if (TPL_CONST[i + j] != target[j]) { found = false; break; } - j++; - } - if (found) return i + j; - i++; - } - return -1; -} - -static constexpr int P_ON = getTokenPos("\"on\":"); -static constexpr int P_BRI = getTokenPos("\"bri\":"); -static constexpr int P_SEGON = getTokenPos("\"on\":", P_ON); -static constexpr int P_STOP = getTokenPos("\"stop\":"); -static constexpr int P_R = getTokenPos("\"col\":[["); -static constexpr int P_G = P_R + 5; -static constexpr int P_B = P_G + 5; -static constexpr int P_CNAME = getTokenPos("\"cn\":\""); -static constexpr int P_NAME = getTokenPos("\"name\":\""); -static constexpr int P_MAC = getTokenPos("\"mac\":\""); -static constexpr int P_ARCH = getTokenPos("\"arch\":\""); -static constexpr int P_UP = getTokenPos("\"uptime\":"); -static constexpr int P_CNT = getTokenPos("\"count\":", P_UP); -static constexpr int P_RSSI = getTokenPos("\"rssi\":"); -static constexpr int P_SIG = getTokenPos("\"signal\":"); -static constexpr int P_CH = getTokenPos("\"channel\":"); -static constexpr int P_RGBW = getTokenPos("\"rgbw\":"); -static constexpr int P_LIVE = getTokenPos("\"live\":"); -static constexpr int P_FREEH = getTokenPos("\"freeheap\":"); - -void fWrite(char* b, int p, int l, int32_t vv) { - auto bufferEnd = b + p + l; - auto [lastChar, ec] = std::to_chars(b + p, bufferEnd, vv); - memset(lastChar, ' ', bufferEnd - lastChar); -} - -void sWrite(char* jsonTemplate, int fieldIndex, unsigned int freeSpace, const char* input, bool quote = true) { - unsigned int copyLen = std::min(strlen(input), freeSpace); - char* target = jsonTemplate + fieldIndex; - - memcpy(target, input, copyLen); - if (quote) { - target[copyLen++] = '"'; - freeSpace++; - } - memset(&target[copyLen], ' ', freeSpace - copyLen); -} - -void bWrite(char* jsonTemplate, int fieldIndex, bool value) { - const char* input = (value) ? "true" : "false"; - sWrite(jsonTemplate, fieldIndex, 5, input, false); -} - -void uniConfigJsonResponse(AsyncWebServerRequest *request); - -void setupUniApiJsonHandler(AsyncWebServer &server) { - server.on("/presets.json", HTTP_GET, [](AsyncWebServerRequest *request){ - - Log::debug("-------- /presets.json ---------"); - Log::debug("{\"0\":{\"n\":\"Default\"}}"); - - request->send(200, "application/json", "{\"0\":{\"n\":\"Default\"}}"); - }); - - server.on("/json/state", HTTP_POST | HTTP_PUT, [](AsyncWebServerRequest *request) {},NULL, - [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { - - Log::debug("-------- /json/state ---------"); - Log::debug("--- Body starts ---"); - Log::raw("Inbound JSON: ", reinterpret_cast(data), len); - Log::debug("--- Body ends ---"); - - JsonDocument doc; - deserializeJson(doc, data, len); - - if (doc["bri"].is()) - { - Volatile::updateBrightness(doc["bri"]); - Log::debug("[UNIAPI] BRI: ", Volatile::state.brightness); - } - else - { - doc["bri"] = Volatile::state.brightness; - } - - if (doc["seg"].is()) - { - if (JsonArray sg = doc["seg"]; sg.size() > 0 && sg[0].is()) - { - JsonObject seg = sg[0]; - - if (seg["on"].is()) - { - Volatile::updatePowerOn(seg["on"]); - - seg["id"] = 0; - seg["bri"] = 255; - seg["start"] = 0; - seg["stop"] = Leds::getLedsNumber(); - - Log::debug("[UNIAPI] SEG ON: ", Volatile::state.on); - } - - if (seg["col"].is()) - { - if (JsonArray colors = seg["col"]; colors.size() > 0 && colors[0].is()) { - if (JsonArray singleColor = colors[0]; singleColor.size() > 2){ - Volatile::updateStaticColor(singleColor[0], singleColor[1], singleColor[2]); - Log::debug("[UNIAPI] COLOR: {", Volatile::state.staticColor.red, ", ", Volatile::state.staticColor.green, ", ", Volatile::state.staticColor.blue, "}"); - } - } - } - } - } - - if (doc["on"].is()) - { - Volatile::updatePowerOn(doc["on"]); - - JsonArray segArray = doc["seg"].is() ? doc["seg"].as() : doc["seg"].to(); - JsonObject seg = segArray.size() > 0 ? segArray[0] : segArray.add(); - seg["id"] = 0; - seg["bri"] = 255; - seg["start"] = 0; - seg["stop"] = Leds::getLedsNumber(); - seg["on"] = doc["on"]; - - Log::debug("[UNIAPI] ON: ", Volatile::state.on); - } - - - JsonObject udpn = doc["udpn"].to(); - udpn["send"] = false; - udpn["recv"] = false; - JsonObject nl = doc["nl"].to(); - nl["on"] = false; - doc["lor"] = 0; - - String response; - serializeJson(doc, response); - request->send(200, "application/json", response); - - Log::debug("Outbound response JSON: ", response.c_str()); - }); - - server.on("/json", HTTP_GET, [](AsyncWebServerRequest *request){ - Log::debug("-------- /json ---------"); - uniConfigJsonResponse(request); - }); -} - -void uniConfigJsonResponse(AsyncWebServerRequest *request) -{ - const AppConfig& cfg = Config::cfg; - char b[sizeof(JSON_TPL)]; - memcpy_P(b, JSON_TPL, sizeof(JSON_TPL)); - - // Current state - bWrite(b, P_ON, Volatile::state.on); - bWrite(b, P_SEGON, Volatile::state.on); - - // Brightness/LED count - int ledsCount = Leds::getLedsNumber(); - fWrite(b, P_BRI, 4, Volatile::state.brightness); - fWrite(b, P_STOP, 6, ledsCount); - fWrite(b, P_CNT, 6, ledsCount); - - // Current colors - fWrite(b, P_R, 4, Volatile::state.staticColor.red); - fWrite(b, P_G, 4, Volatile::state.staticColor.green); - fWrite(b, P_B, 4, Volatile::state.staticColor.blue); - - // Live - bWrite(b, P_LIVE, Volatile::state.live); - - // RGBW capabilities - bWrite(b, P_RGBW, (cfg.led.type == LedType::SK6812)); - - // Device Name - sWrite(b, P_CNAME, 22, APP_NAME "_" APP_VERSION); - sWrite(b, P_NAME, 15, cfg.deviceName.c_str()); - - // MAC - sWrite(b, P_MAC, 12, Mdns::getDeviceShortMacAddress().c_str()); - - // Architecture - sWrite(b, P_ARCH, 11, getDeviceArch().c_str()); - - #if defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) - uint32_t freeHeap = rp2040.getFreeHeap(); - #else - uint32_t freeHeap = ESP.getFreeHeap(); - #endif - fWrite(b, P_FREEH, 8, freeHeap); - - fWrite(b, P_UP, 12, millis() / 1000); - - int32_t rssiVal = WiFi.RSSI(); - fWrite(b, P_SIG, 4, (rssiVal <= -100) ? 0 : (rssiVal >= -50) ? 100 - : 2 * (rssiVal + 100)); - fWrite(b, P_CH, 3, WiFi.channel()); - - // RSSI - fWrite(b, P_RSSI, 6, rssiVal); - - Log::raw("Outbound JSON: ", b, sizeof(b)); - - request->send(200, "application/json", b); -} diff --git a/include/led_bridge/led_bridge.h b/src/uni_json_api_oss.cpp similarity index 59% rename from include/led_bridge/led_bridge.h rename to src/uni_json_api_oss.cpp index e05f064..14b1159 100644 --- a/include/led_bridge/led_bridge.h +++ b/src/uni_json_api_oss.cpp @@ -1,4 +1,4 @@ -/* led_bridge.h +/* uni_json_api_oss.cpp * * MIT License * @@ -24,23 +24,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +#include +#include "uni_json_api_oss.h" +#include "effects_factory.h" -#pragma once -#include "config.h" +#if defined(ESP8266) + #include +#else + #include +#endif -struct led_bridge -{ - virtual bool supportsDoubleBuffering() = 0; - virtual bool restartRequired() {return false;}; - virtual int getLedsNumber() = 0; - virtual void clearAll() = 0; - virtual bool canRender() = 0; - virtual int segmentSupported() = 0; - virtual void executeRenderLed() = 0; - virtual void releaseDriverResources() = 0; - virtual void initializeLedDriver(LedType cfgLedType, uint16_t cfgLedNumLeds, const std::vector& cfgSegments, - uint8_t calGain, uint8_t calRed, uint8_t calGreen, uint8_t calBlue) = 0; +static const char JSON_TPL[] PROGMEM = TPL_BODY_WITH_EFFECTS; - virtual inline void setLedRgb(int index, uint8_t r, uint8_t g, uint8_t b) = 0; - virtual inline void setLedRgbw(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) = 0; -}; \ No newline at end of file +void UniJsonApi::uniConfigJsonResponse(IHttpRequest*, IHttpResponse* response) +{ + char b[sizeof(JSON_TPL)]; + memcpy_P(b, JSON_TPL, sizeof(JSON_TPL)); + UniJsonApi::fillUniConfigJsonResponse(response, b); +} diff --git a/src/utils.cpp b/src/utils.cpp deleted file mode 100644 index 2a9f9d7..0000000 --- a/src/utils.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* utils.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#include -#include - -#if defined(ARDUINO_ARCH_ESP8266) - #include -#else - #include -#endif - -#if defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) - #include "hardware/watchdog.h" -#endif - -#include "utils.h" -#include "logger.h" - -void rebootDevice() { - Log::debug("[UTILS] Critical: Hyperk is rebooting system..."); - - WiFi.disconnect(true); - Log::debug("[UTILS] WiFi is closed."); - - LittleFS.end(); - Log::debug("[UTILS] File System unmounted."); - - Log::debug("[UTILS] Executing Hardware Reset..."); - delay(200); - - #if defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) - watchdog_reboot(0, 0, 0); - watchdog_enable(1, 1); - #else - ESP.restart(); - #endif - - while (true) { - Serial.print("."); - delay(1); - } -} - -String getDeviceArch() -{ - String modelName; - - #if defined(ARDUINO_ARCH_RP2350) || (defined(ARDUINO_ARCH_RP2040) && defined(__ARM_ARCH_8M_MAIN__)) - modelName = F("RP2350"); - #elif defined(ARDUINO_ARCH_RP2040) - modelName = F("RP2040"); - #elif defined(GLEDOPTO_GL_C_616WL) - modelName = F("esp32-GLEDOPTO_GL_C_616WL"); - #elif defined(GLEDOPTO_GL_C_615WL) - modelName = F("esp32-GLEDOPTO_GL_C_615WL"); - #elif defined(DOMRAEM_WLE_ADM) - modelName = F("esp32-DOMRAEM_WLE_ADM"); - #elif defined(IOTORERO_ETHERNET) - modelName = F("esp32-IOTORERO_ETHERNET"); - #elif defined(ARDUINO_ARCH_ESP32) && defined(WEBSERVER_USE_ETHERNET) - modelName = F("ESP32-ETH01"); - #elif defined(CONFIG_IDF_TARGET_ESP32) - modelName = F("ESP32"); - #elif defined(CONFIG_IDF_TARGET_ESP32S2) - modelName = F("ESP32-S2"); - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - modelName = F("ESP32-S3"); - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - modelName = F("ESP32-C3"); - #elif defined(CONFIG_IDF_TARGET_ESP32C6) - modelName = F("ESP32-C6"); - #elif defined(CONFIG_IDF_TARGET_ESP32C2) - modelName = F("ESP32-C2"); - #elif defined(CONFIG_IDF_TARGET_ESP32C5) - modelName = F("ESP32-C5"); - #elif defined(ESP8266) - modelName = F("ESP8266"); - #else - modelName = F("generic"); - #endif - - return modelName; -} - -int getFreeHeap() -{ - #if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) - return ESP.getFreeHeap(); - #else - return rp2040.getFreeHeap(); - #endif -} \ No newline at end of file diff --git a/src/volatile_state.cpp b/src/volatile_state.cpp deleted file mode 100644 index 3e1db68..0000000 --- a/src/volatile_state.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* volatile_state.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ -#include "volatile_state.h" -#include "config.h" - -namespace Volatile{ - static VolatileState internalState; - const VolatileState& state = internalState; - - bool updatedBrightness = false; - bool updatedPowerOn = false; - bool updatedStaticColor = false; - - void updateBrightness(uint8_t brightness){ - internalState.brightness = brightness; - updatedBrightness = true; - }; - - void updatePowerOn(bool on){ - internalState.on = on; - updatedPowerOn = true; - - updateStreamTimeout(0); - }; - - void updateStaticColor(uint8_t red, uint8_t green, uint8_t blue){ - internalState.staticColor.red = red; - internalState.staticColor.green = green; - internalState.staticColor.blue = blue; - updatedStaticColor = true; - - updateStreamTimeout(0); - }; - - void updateStreamTimeout(unsigned long timeout){ - internalState.streamTimeout = (timeout) ? timeout + millis() : 0; - internalState.live = (timeout); - if (internalState.live) { - setRelay(true); - } - }; - - void checkStreamTimeout(){ - if (internalState.streamTimeout > 0 && internalState.streamTimeout < millis()) - { - updatePowerOn(false); - - internalState.streamTimeout = 0; - internalState.live = false; - } - }; - - bool clearUpdatedBrightnessState(){ - bool ret = updatedBrightness; - updatedBrightness = false; - return ret; - }; - - bool clearUpdatedPowerOnState(){ - bool ret = updatedPowerOn; - updatedPowerOn = false; - return ret; - }; - - bool clearUpdatedStaticColorState(){ - bool ret = updatedStaticColor; - updatedStaticColor = false; - return ret; - }; - - void setRelay(bool enable) { - if (Config::cfg.led.relay.gpio < 0) - return; - - if (internalState.relayEnabled != enable) { - const auto enableState = (Config::cfg.led.relay.inverted) ? LOW : HIGH; - const auto disableState = (Config::cfg.led.relay.inverted) ? HIGH : LOW; - - internalState.relayEnabled = enable; - - pinMode(Config::cfg.led.relay.gpio, OUTPUT); - - if (internalState.relayEnabled) { - digitalWrite(Config::cfg.led.relay.gpio, enableState); - delay(20); - Log::debug("Power relay is enabled. GPIO: ", Config::cfg.led.relay.gpio, ", level: ", enableState, ((Config::cfg.led.relay.inverted) ? " (inverted)" : "")); - } - else { - digitalWrite(Config::cfg.led.relay.gpio, disableState); - Log::debug("Power relay is disabled. GPIO: ", Config::cfg.led.relay.gpio, ", level: ", disableState, ((Config::cfg.led.relay.inverted) ? " (inverted)" : "")); - } - } - } -} - diff --git a/src/web_server.cpp b/src/web_server.cpp deleted file mode 100644 index be85842..0000000 --- a/src/web_server.cpp +++ /dev/null @@ -1,417 +0,0 @@ -/* web_server.cpp -* -* MIT License -* -* Copyright (c) 2026 awawa-dev -* -* Project homesite: https://github.com/awawa-dev/Hyperk -* -* 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. -*/ - -#include -#include -#if defined(ESP8266) - #include -#else - #include -#endif - -#if defined(ARDUINO_ARCH_ESP32) && defined(WEBSERVER_USE_ETHERNET) - #include -#endif - -#include "web_server.h" -#include "main.h" -#include "config.h" -#include "utils.h" -#include "leds.h" -#include "manager.h" -#include "mdns_service.h" -#include "web_resources.h" -#include "uni_json_api.h" -#include "ota.h" - -namespace { - constexpr auto mime_application_json = "application/json"; - constexpr auto mime_text_html = "text/html"; - constexpr auto mime_text_plain = "text/plain"; -} - -void setupStaticHandlers(AsyncWebServer& server); - -void setupWebServer(AsyncWebServer& server) { - - if (isAPMode()){ - server.on("/fwlink", HTTP_GET, [](AsyncWebServerRequest *request){ request->redirect("/"); }); - server.on("/check_generate_204", HTTP_GET, [](AsyncWebServerRequest *request){ request->redirect("/"); }); - server.on("/generate_204", HTTP_ANY, [](AsyncWebServerRequest *request){ request->redirect("/"); }); - } else { - setupUniApiJsonHandler(server); - otaUpdateHandler(server); - } - - // Scan WiFi - server.on("/api/wifi_scan", HTTP_GET, [](AsyncWebServerRequest *request) { - if (!isAPMode()) - { - request->send(202, mime_application_json, "{\"status\":\"unsupported\"}"); - return; - } - - int n = WiFi.scanComplete(); - Log::debug("[WiFi Scan] Status: ", n); - - if (n == -1) { // scan in progress - Log::debug("[WiFi Scan] Still scanning..."); - request->send(202, mime_application_json, "{\"status\":\"scanning\"}"); - } - else if (n == -2 || (n == 0)) { // restart scan - Log::debug("[WiFi Scan] Starting new async scan..."); - WiFi.scanNetworks(true); - request->send(202, mime_application_json, "{\"status\":\"started\"}"); - } - else if (n > 0) { // scan is completed - Log::debug("[WiFi Scan] Found ", n, " networks. Sending to client."); - - AsyncResponseStream *response = request->beginResponseStream(mime_application_json); - JsonDocument doc; - JsonArray array = doc.to(); - - for (int i = 0; i < n; ++i) { - String ssid = WiFi.SSID(i); - if (ssid.length() > 0) { - JsonObject net = array.add(); - net["ssid"] = ssid; - net["rssi"] = WiFi.RSSI(i); - } - } - - serializeJson(doc, *response); - request->send(response); - - WiFi.scanDelete(); - } - else { - request->send(202, mime_application_json, "{\"status\":\"no_networks\"}"); - } - }); - - // Save WiFi - server.on("/save_wifi", HTTP_POST, [](AsyncWebServerRequest *request) { - if (request->hasParam("ssid", true) && request->hasParam("pass", true)) { - if (!isAPMode()) - { - request->send(200, mime_text_html,"Unsupported"); - return; - } - - AppConfig cfg = Config::cfg; - - cfg.wifi.ssid = request->getParam("ssid", true)->value(); - cfg.wifi.ssid.trim(); - if (cfg.wifi.ssid == "CUSTOM") { - if (request->hasParam("ssid_custom", true)) { - cfg.wifi.ssid = request->getParam("ssid_custom", true)->value(); - cfg.wifi.ssid.trim(); - } else { - cfg.wifi.ssid = ""; - } - } - cfg.wifi.password = request->getParam("pass", true)->value(); - Config::saveConfig(cfg); - - AsyncWebServerResponse *response = request->beginResponse(200, mime_text_html, - "" - "" - "

WiFi Saved

" - "

Switch your phone to your:

" + cfg.wifi.ssid + "

" - "

network and use address:

" - "

" + cfg.deviceName + ".local

" - "

Alternatively check router for new IP.

" - "

" - "If connection fails, device will switch back to WiFi AP in 12s.

" - ""); - - response->addHeader(F("Connection"), F("close")); - request->send(response); - Log::debug("WiFi Config saved. Rebooting in 2s..."); - managerScheduleReboot(1000); - } - }); - - // Save settings - server.on("/save_config", HTTP_POST, [](AsyncWebServerRequest *request) { - AppConfig cfg = Config::cfg; - bool needsRestart = false; - - if (request->hasParam("reset_wifi", true)) { - String ssid = request->getParam("reset_wifi", true)->value(); - - if (cfg.wifi.ssid.equalsIgnoreCase(ssid)) { - cfg.wifi.ssid = ""; - cfg.wifi.password = ""; - Config::saveConfig(cfg); - managerScheduleReboot(1000); - request->send(200, mime_application_json, "{\"status\":\"reboot\"}"); - } else { - request->send(403, mime_application_json, "{\"status\":\"error\"}"); - } - - return; - } - - if (request->hasParam("type", true)) { - uint8_t t = request->getParam("type", true)->value().toInt(); - if (t != (uint8_t)cfg.led.type) - { - if (Leds::restartRequired()){ - needsRestart = true; - } - - cfg.led.type = (LedType)t; - } - } - if (request->hasParam("segments", true)) { - if (cfg.led.deserializeSegments(request->getParam("segments", true)->value())) - { - if (Leds::restartRequired()){ - needsRestart = true; - } - } - } - if (request->hasParam("numLeds", true)) { - uint16_t n = request->getParam("numLeds", true)->value().toInt(); - if (n != cfg.led.numLeds && n <= MAX_LEDS) - { - if (Leds::restartRequired()){ - needsRestart = true; - } - - cfg.led.numLeds = n; - } - } - if (request->hasParam("relay-gpio", true)) { - int8_t relGpio = static_cast(request->getParam("relay-gpio", true)->value().toInt()); - bool inv = (request->hasParam("relay-inverted", true)); - if ( relGpio != cfg.led.relay.gpio || inv != cfg.led.relay.inverted) - { - needsRestart = true; - cfg.led.relay.gpio = relGpio; - cfg.led.relay.inverted = inv; - Log::debug("Set relay-gpio to: ", cfg.led.relay.gpio, ", inverted: ", cfg.led.relay.inverted); - } - } - - // live updates - if (request->hasParam("brightness", true)) cfg.led.brightness = constrain(request->getParam("brightness", true)->value().toInt(), 1, 255); - if (request->hasParam("r", true)) cfg.led.r = constrain(request->getParam("r", true)->value().toInt(), 0, 255); - if (request->hasParam("g", true)) cfg.led.g = constrain(request->getParam("g", true)->value().toInt(), 0, 255); - if (request->hasParam("b", true)) cfg.led.b = constrain(request->getParam("b", true)->value().toInt(), 0, 255); - if (request->hasParam("effect", true)) cfg.led.effect = request->getParam("effect", true)->value().toInt(); - - if (request->hasParam("calGain", true)) cfg.led.calibration.gain = constrain(request->getParam("calGain", true)->value().toInt(), 0, 255); - if (request->hasParam("calRed", true)) cfg.led.calibration.red = constrain(request->getParam("calRed", true)->value().toInt(), 0, 255); - if (request->hasParam("calGreen", true)) cfg.led.calibration.green = constrain(request->getParam("calGreen", true)->value().toInt(), 0, 255); - if (request->hasParam("calBlue", true)) cfg.led.calibration.blue = constrain(request->getParam("calBlue", true)->value().toInt(), 0, 255); - - const bool standaloneApMode = isAPMode(); - - if (request->hasParam("deviceName", true)) { - auto rawValue = request->getParam("deviceName", true)->value(); - rawValue.trim(); - const auto safeVal = Mdns::sanitizeMdnsService(rawValue); - const bool valChanged = (!cfg.deviceName.equalsIgnoreCase(safeVal)) || (rawValue != cfg.deviceName); - if (!standaloneApMode){ - needsRestart = needsRestart || valChanged; - } - if (valChanged) { - cfg.deviceName = safeVal; - } - } - - if (request->hasParam("extraMdnsTag", true)) { - auto rawValue = request->getParam("extraMdnsTag", true)->value(); - rawValue.trim(); - const auto safeVal = Mdns::sanitizeMdnsService(rawValue); - const bool valChanged = (!cfg.extraMdnsTag.equalsIgnoreCase(safeVal)) || (rawValue != cfg.extraMdnsTag); - if (!standaloneApMode) { - needsRestart = needsRestart || valChanged; - } - if (valChanged) { - cfg.extraMdnsTag = safeVal; - } - } - - Config::saveConfig(cfg); - - if (needsRestart) { - request->send(200, mime_application_json, "{\"status\":\"reboot\"}"); - Log::debug("Configuration saved. Rebooting in 2s..."); - managerScheduleReboot(2000); - } - else { - managerScheduleApplyConfig(); - request->send(200, mime_application_json, "{\"status\":\"ok\"}"); - } - }); - - // Current config - server.on("/api/get_current_config", HTTP_GET, [](AsyncWebServerRequest *request) { - AsyncResponseStream *response = request->beginResponseStream(mime_application_json); - const AppConfig& cfg = Config::cfg; - JsonDocument doc; - JsonObject led = doc["config"].to(); - led["apMode"] = isAPMode(); - led["architecture"] = getDeviceArch(); - led["board"] = String(PIO_ENV_NAME); - led["version"] = APP_VERSION; - - led["type"] = (int)cfg.led.type; - - JsonArray segArray = led["segments"].to(); - cfg.led.serializeSegments(segArray); - led["segmentSupported"] = Leds::segmentSupported(); - - led["relay-gpio"] = cfg.led.relay.gpio; - led["relay-inverted"] = cfg.led.relay.inverted; - - led["numLeds"] = cfg.led.numLeds; - led["calGain"] = cfg.led.calibration.gain; - led["calRed"] = cfg.led.calibration.red; - led["calGreen"] = cfg.led.calibration.green; - led["calBlue"] = cfg.led.calibration.blue; - - - led["brightness"] = cfg.led.brightness; - led["r"] = cfg.led.r; - led["g"] = cfg.led.g; - led["b"] = cfg.led.b; - led["effect"] = cfg.led.effect; - - led["deviceName"] = cfg.deviceName; - led["extraMdnsTag"] = cfg.extraMdnsTag; - - led["ssid"] = cfg.wifi.ssid; - - serializeJson(doc, *response); - request->send(response); - }); - - // Internal stats API - server.on("/api/stats", HTTP_GET, [](AsyncWebServerRequest *request) { - AsyncResponseStream *response = request->beginResponseStream(mime_application_json); - const AppConfig& cfg = Config::cfg; - JsonDocument doc; - - doc["VERSION"] = APP_VERSION; - doc["DEVICE"] = cfg.deviceName; - #if defined(ARDUINO_ARCH_ESP32) && defined(WEBSERVER_USE_ETHERNET) - if (auto eth = ETH.localIP(), wifi = WiFi.localIP(); eth != IPAddress(0,0,0,0) && wifi != IPAddress(0,0,0,0)) { - doc["IP"] = eth.toString() + " " + wifi.toString(); - } else { - doc["IP"] = (eth != IPAddress(0,0,0,0)) ? eth.toString() : wifi.toString(); - } - #else - doc["IP"] = WiFi.localIP().toString(); - #endif - doc["RSSI"] = String(WiFi.RSSI()) + " dBm"; - doc["UPTIME"] = String(millis() / 1000); - doc["HEAP"] = String(getFreeHeap() / 1024) + " kB"; - doc["ARCH"] = getDeviceArch(); - doc["FPS"] = stats.lastIntervalRenderedFrames; - doc["SKIPPED"] = stats.lastIntervalSkippedFrames; - - serializeJson(doc, *response); - request->send(response); - }); - - // Not found and captivity mode - server.onNotFound([](AsyncWebServerRequest *request) { - if (isAPMode() && request->method() == HTTP_GET) { - Log::debug("404 Not Found: ", request->url()); - request->redirect("/"); - } - else { - #ifdef DEBUG_LOG - String method = request->methodToString(); - String contentType = request->contentType(); - if (contentType.length() == 0) { - contentType = "unknown"; - } - Log::debug("404 Not Found: " + request->url() + " | Method: " + method + " | Content-Type: " + contentType); - #endif - request->send(404, mime_text_plain, "Not found"); - } - }); - - setupStaticHandlers(server); -} - -void sendEmbeddedFile(AsyncWebServerRequest *request, const uint8_t* content, uint32_t len, const char* contentType) -{ - AsyncWebServerResponse *response = request->beginResponse(200, contentType, content, len); - - if (response != nullptr) - { - response->addHeader(F("Content-Encoding"), F("gzip")); - response->addHeader(F("Connection"), F("close")); - if (strcmp(contentType, "application/javascript") == 0 || strcmp(contentType, "text/css") == 0 || strcmp(contentType, "image/png") == 0) - { - #ifdef DEBUG_LOG - response->addHeader(F("Cache-Control"), F("no-store, no-cache, must-revalidate, max-age=0")); - #else - response->addHeader(F("Cache-Control"), F("public, max-age=31536000, immutable")); - #endif - } - request->send(response); - } -} - -// resources are generated by web_embedder.py -void setupStaticHandlers(AsyncWebServer& server) -{ - for (uint16_t i = 0; i < webResourcesCount; i++) - { - const char* currentUrl; - #if defined(ESP8266) - currentUrl = (const char*)pgm_read_ptr(&(webResources[i].url)); - #else - currentUrl = webResources[i].url; - #endif - - auto handler = [i](AsyncWebServerRequest *request) - { - WebResource res; - #if defined(ESP8266) - memcpy_P(&res, &webResources[i], sizeof(WebResource)); - #else - res = webResources[i]; - #endif - sendEmbeddedFile(request, res.data, res.len, res.mime); - }; - - server.on(currentUrl, HTTP_GET, handler); - - if (strcmp(currentUrl, "/index.html") == 0) - { - server.on("/", HTTP_GET, handler); - } - } -} \ No newline at end of file diff --git a/version b/version index 81340c7..2995b01 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.0.4 +0.0.5-alpha.1