diff --git a/.gitignore b/.gitignore index c7642effc..2f102171c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,6 @@ sdkconfig.board.*.dev .caveman.json .ai/mcp +.clinerules/ __pycache__ \ No newline at end of file diff --git a/Devices/lilygo-tembed-cc1101-plus/CMakeLists.txt b/Devices/lilygo-tembed-cc1101-plus/CMakeLists.txt new file mode 100644 index 000000000..b2d9c4492 --- /dev/null +++ b/Devices/lilygo-tembed-cc1101-plus/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB_RECURSE SOURCE_FILES source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + REQUIRES TactilityKernel Tactility +) diff --git a/Devices/lilygo-tembed-cc1101-plus/LICENSE-Apache-2.0.md b/Devices/lilygo-tembed-cc1101-plus/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Devices/lilygo-tembed-cc1101-plus/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License 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. + diff --git a/Devices/lilygo-tembed-cc1101-plus/device.properties b/Devices/lilygo-tembed-cc1101-plus/device.properties new file mode 100644 index 000000000..0c46c00e6 --- /dev/null +++ b/Devices/lilygo-tembed-cc1101-plus/device.properties @@ -0,0 +1,28 @@ +general.vendor=LilyGO +general.name=T-Embed CC1101 Plus + +apps.launcherAppId=tactility.launcher + +hardware.target=ESP32S3 +hardware.flashSize=16MB +hardware.spiRam=true +hardware.spiRamMode=OCT +hardware.spiRamSpeed=80M +hardware.spiRamXipDisabled=true +hardware.tinyUsbMsc=true +hardware.esptoolFlashFreq=80M +hardware.bluetooth=true + +storage.userDataLocation=SD + +display.size=1.9" +display.shape=rectangle +display.dpi=191 + +lvgl.colorDepth=16 +lvgl.fontSize=16 +lvgl.uiDensity=compact + +sdkconfig.CONFIG_CODEC_DUMMY_SUPPORT=y + + diff --git a/Devices/lilygo-tembed-cc1101-plus/lilygo,tembed-cc1101-plus.dts b/Devices/lilygo-tembed-cc1101-plus/lilygo,tembed-cc1101-plus.dts new file mode 100644 index 000000000..30cc27b51 --- /dev/null +++ b/Devices/lilygo-tembed-cc1101-plus/lilygo,tembed-cc1101-plus.dts @@ -0,0 +1,186 @@ +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +// Reference: +// https://wiki.lilygo.cc/products/t-embed-series/t-embed-cc1101/ +// https://github.com/Xinyuan-LilyGO/T-Embed-CC1101 +// https://lilygo.cc/products/t-embed-cc1101-plus +/ { + compatible = "root"; + model = "T-Embed CC1101 Plus"; + + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + clk-src = ; + channels = ; + }; + + battery-sense { + compatible = "battery-sense"; + io-channel = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2110>; + }; + + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + }; + + ble0 { + compatible = "espressif,esp32-ble"; + }; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <49>; + }; + + // Required to enable peripherals like BQ25896 + peripheral-power-hog { + compatible = "gpio-hog"; + pin = <&gpio0 15 GPIO_FLAG_NONE>; + mode = ; + }; + + // Speaker I2S (MAX98357A amplifier) + i2s0 { + compatible = "espressif,esp32-i2s"; + port = ; + pin-bclk = <&gpio0 46 GPIO_FLAG_NONE>; + pin-ws = <&gpio0 40 GPIO_FLAG_NONE>; + pin-data-out = <&gpio0 7 GPIO_FLAG_NONE>; + pin-data-in = <&gpio0 42 GPIO_FLAG_NONE>; + pin-mclk = <&gpio0 39 GPIO_FLAG_NONE>; + }; + + // MAX98357A class-D speaker amplifier. No I2C. + speaker0 { + compatible = "maxim,max98357a"; + i2s = <&i2s0>; + }; + + mic0 { + compatible = "generic,spm1423"; + i2s = <&i2s0>; + channels = <1>; + }; + + // WS2812 RGB LED Ring + // TODO - Sync on/off with DisplayIdle? + ledstrip0 { + compatible = "espressif,esp32-led-strip"; + pin-data = <&gpio0 14 GPIO_FLAG_NONE>; + led-count = <8>; + }; + + i2c_internal: i2c0 { + compatible = "espressif,esp32-i2c-master"; + port = ; + clock-frequency = <100000>; + pin-sda = <&gpio0 8 GPIO_FLAG_NONE>; + pin-scl = <&gpio0 18 GPIO_FLAG_NONE>; + + bq27220 { + compatible = "ti,bq27220"; + reg = <0x55>; + }; + + bq25896 { + compatible = "ti,bq25896"; + reg = <0x6B>; + }; + + // TODO - Add PN532 NFC - IRQ GPIO 17, RES GPIO 45, address 0x24 - driver needed + }; + + display_backlight_pwm { + compatible = "espressif,esp32-pwm-ledc"; + pin = <&gpio0 21 GPIO_FLAG_NONE>; + period-ns = <33333>; + ledc-timer = <0>; + ledc-channel = <0>; + }; + + display_backlight { + compatible = "pwm-backlight"; + // Off by default so dispaly power-on won't show the screen from before the last power loss. + // The display backlight is turned on during the boot process. + status = "disabled"; + pwm = <&display_backlight_pwm>; + }; + + spi0 { + compatible = "espressif,esp32-spi"; + host = ; + cs-gpios = <&gpio0 41 GPIO_FLAG_NONE>, // Display + <&gpio0 13 GPIO_FLAG_NONE>, // SD card + <&gpio0 12 GPIO_FLAG_NONE>, // Radio - CC1101 + <&gpio0 44 GPIO_FLAG_NONE>; // Radio - nRF24L01 + pin-mosi = <&gpio0 9 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 10 GPIO_FLAG_NONE>; + pin-sclk = <&gpio0 11 GPIO_FLAG_NONE>; + + display@0 { + compatible = "sitronix,st7789"; + horizontal-resolution = <320>; + vertical-resolution = <170>; + gap-x = <35>; + swap-xy; + mirror-y; + invert-color; + pixel-clock-hz = <80000000>; + pin-dc = <&gpio0 16 GPIO_FLAG_NONE>; + pin-reset = <&gpio0 40 GPIO_FLAG_NONE>; + backlight = <&display_backlight>; + }; + + sdcard@1 { + compatible = "espressif,esp32-sdspi"; + status = "disabled"; + frequency-khz = <20000>; + }; + + // TODO - Add CC1101 Radio - CS GPIO 12, SCK GPIO 11, MOSI GPIO 9, MISO GPIO 10, IO2 GPIO 38, IO0 GPIO 3, SW1 GPIO 47, SW0 GPIO 48 - driver needed + // TODO - Add nRF24L01 Radio - CS GPIO 44, CE GPIO 43, IQR -1/NC, SCK GPIO 11, MOSI GPIO 9, MISO GPIO 10 - driver needed + }; + + encoder { + compatible = "tactility,gpio-encoder"; + pin-a = <&gpio0 5 GPIO_FLAG_NONE>; + pin-b = <&gpio0 4 GPIO_FLAG_NONE>; + pin-enter = <&gpio0 0 GPIO_FLAG_NONE>; + pulses-per-detent = <2>; + pending-capacity = <8>; + }; + + usbdevice0 { + compatible = "espressif,esp32-usbdevice"; + + usbdevicemsc0 { + compatible = "espressif,esp32-usbdevice-msc"; + }; + }; +}; diff --git a/Devices/lilygo-tembed-cc1101-plus/module.yaml b/Devices/lilygo-tembed-cc1101-plus/module.yaml new file mode 100644 index 000000000..00e9ad69b --- /dev/null +++ b/Devices/lilygo-tembed-cc1101-plus/module.yaml @@ -0,0 +1,11 @@ +dependencies: + - Platforms/platform-esp32 + - Drivers/bq27220-module + - Drivers/bq25896-module + - Drivers/gpio-encoder-module + - Drivers/st7789-module + - Drivers/dummy-i2s-amp-module + - Drivers/pdm-mic-module + - Drivers/audio-stream-module + - Drivers/esp32-led-strip-module +dts: lilygo,tembed-cc1101-plus.dts diff --git a/Devices/lilygo-tembed-cc1101-plus/source/module.cpp b/Devices/lilygo-tembed-cc1101-plus/source/module.cpp new file mode 100644 index 000000000..97e9b00c1 --- /dev/null +++ b/Devices/lilygo-tembed-cc1101-plus/source/module.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include +#include + +constexpr auto* TAG = "tembed-cc1101-plus"; + +extern "C" { + +static void on_boot_completed(SystemEvent* event, void* context) { + (void)event; + (void)context; + Device* device = nullptr; + if (device_get_first_by_type(&LED_STRIP_TYPE, &device) != ERROR_NONE) { + LOG_W(TAG, "No LED strip found during boot restore"); + return; + } + + const auto settings = tt::settings::ledstrip::loadOrGetDefault(device->name); + const error_t result = tt::settings::ledstrip::apply(device, settings); + if (result != ERROR_NONE) { + LOG_E(TAG, "Failed to restore LED strip %s: %d", device->name, result); + } else { + LOG_I(TAG, "Restored LED strip %s", device->name); + } + device_put(device); +} + +static error_t start() { + return system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed, nullptr); +} + +static error_t stop() { + return system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed); +} + +Module lilygo_tembed_cc1101_plus_module = { + .name = "lilygo-tembed-cc1101-plus", + .start = start, + .stop = stop, + .drivers = nullptr, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Drivers/esp32-led-strip-module/CMakeLists.txt b/Drivers/esp32-led-strip-module/CMakeLists.txt new file mode 100644 index 000000000..8e6a6d6eb --- /dev/null +++ b/Drivers/esp32-led-strip-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(esp32-led-strip-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 esp_driver_rmt driver +) \ No newline at end of file diff --git a/Drivers/esp32-led-strip-module/LICENSE-Apache-2.0.md b/Drivers/esp32-led-strip-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/esp32-led-strip-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License 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. + diff --git a/Drivers/esp32-led-strip-module/README.md b/Drivers/esp32-led-strip-module/README.md new file mode 100644 index 000000000..c5479dbef --- /dev/null +++ b/Drivers/esp32-led-strip-module/README.md @@ -0,0 +1,88 @@ +# esp32-led-strip-module + +WS2812-compatible RGB LED strip driver for Tactility. It uses ESP-IDF's RMT peripheral to transmit pixel data and provides per-pixel and range color control, brightness adjustment, and gamma-corrected output. + +## Devicetree Configuration + +Declare the strip with the compatible string `"espressif,esp32-led-strip"`. `pin-data` references the GPIO device and pin used for the data signal; `led-count` is the number of LEDs on the strip. + +```dts +ws2812 { + compatible = "espressif,esp32-led-strip"; + pin-data = <&gpio0 14 GPIO_FLAG_NONE>; + led-count = <8>; +}; +``` + +The driver reads these properties into `LedStripConfig`. + +## API + +Include ``. The kernel API operates on a `struct Device *` and returns `error_t`; successful calls return `ERROR_NONE`. + +| Function | Description | +|---|---| +| `led_strip_set_single_led_color` | Set one LED using global brightness | +| `led_strip_set_single_led_color_and_brightness` | Set one LED with an explicit brightness | +| `led_strip_set_led_range_color` | Set a range from an array of `LedRgb` values using global brightness | +| `led_strip_set_led_range_color_and_brightness` | Set a range from an array with explicit brightness | +| `led_strip_fill_led_range_color` | Fill a range using global brightness | +| `led_strip_fill_led_range_color_and_brightness` | Fill a range with explicit brightness | +| `led_strip_set_brightness` | Set global brightness | +| `led_strip_show` | Transmit buffered pixel values to the strip | +| `led_strip_clear` | Turn off all LEDs | +| `led_strip_get_length` | Get the configured LED count | + +LED indices and range offsets are zero-based. Color channels in `struct LedRgb` range from 0 to 255. Global and per-operation brightness also range from 0 (off) to 255 (full); the default global brightness is 255. Call `led_strip_show()` after setting colors to transmit the updated buffer. `led_strip_clear()` clears and refreshes the strip. + +## Usage Example + +```c +#include + +void led_demo(struct Device *strip) { + uint16_t length = 0; + if (led_strip_get_length(strip, &length) != ERROR_NONE || length == 0) { + return; + } + + led_strip_set_brightness(strip, 128); // About 50% brightness + led_strip_fill_led_range_color(strip, 0, length, LED_RGB_COLOR_GREEN); + led_strip_show(strip); + + led_strip_set_single_led_color(strip, 0, LED_RGB_COLOR_RED); + led_strip_show(strip); + + led_strip_clear(strip); +} +``` + +## Using Literal RGB Values + +Color macros are optional. Initialize `struct LedRgb` directly when a color is calculated or loaded at runtime. This example assumes the strip has at least four LEDs. + +```c +#include + +void show_custom_colors(struct Device *strip) { + const struct LedRgb colors[] = { + { .r = 255, .g = 32, .b = 0 }, + { .r = 0, .g = 180, .b = 64 }, + { .r = 24, .g = 48, .b = 255 }, + { .r = 200, .g = 80, .b = 160 } + }; + + led_strip_set_led_range_color_and_brightness(strip, 0, 4, colors, 192); + led_strip_show(strip); +} +``` + +## Color Presets + +The kernel header also provides `LED_RGB_COLOR_*` macros such as `LED_RGB_COLOR_RED`, `LED_RGB_COLOR_GREEN`, and `LED_RGB_COLOR_WHITE`, plus `LED_COLOR_RGB_CUSTOM(r, g, b)` for a custom color. These expand to `struct LedRgb` values. + +## Implementation Notes + +- The driver configures the ESP-IDF RMT backend for WS2812 RGB output in GRB component order. +- An 8-bit gamma correction lookup table is applied after brightness scaling. +- The devicetree compatible string and driver registration are `"espressif,esp32-led-strip"`; the kernel device type is `LED_STRIP_TYPE`. diff --git a/Drivers/esp32-led-strip-module/bindings/espressif,esp32-led-strip.yaml b/Drivers/esp32-led-strip-module/bindings/espressif,esp32-led-strip.yaml new file mode 100644 index 000000000..79b98f912 --- /dev/null +++ b/Drivers/esp32-led-strip-module/bindings/espressif,esp32-led-strip.yaml @@ -0,0 +1,13 @@ +description: RGB LED strip. ESP-IDF's RMT peripheral to transmit pixel data to drive WS2812-compatible LEDs. + +compatible: "espressif,esp32-led-strip" + +properties: + pin-data: + type: phandles + required: true + description: GPIO pin for strip data output + led-count: + type: int + required: true + description: Number of LEDs in the strip diff --git a/Drivers/esp32-led-strip-module/include/bindings/esp32_led_strip.h b/Drivers/esp32-led-strip-module/include/bindings/esp32_led_strip.h new file mode 100644 index 000000000..b3be8cbc5 --- /dev/null +++ b/Drivers/esp32-led-strip-module/include/bindings/esp32_led_strip.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(esp32_led_strip, struct LedStripConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/esp32-led-strip-module/include/drivers/esp32_led_strip.h b/Drivers/esp32-led-strip-module/include/drivers/esp32_led_strip.h new file mode 100644 index 000000000..e4d0d3a81 --- /dev/null +++ b/Drivers/esp32-led-strip-module/include/drivers/esp32_led_strip.h @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct LedStripConfig { + /** Data Pin */ + struct GpioPinSpec pin_data; + + /** LED Count */ + uint16_t led_count; +}; + +/** + * @brief Sets the color of a single LED on the strip with global brightness. + * @param[in] device the LED strip device + * @param[in] index index of the LED in the strip (0-based) + * @param[in] color the color to set + * @retval ERROR_NONE when the operation was successful + */ +error_t esp32_led_strip_set_single_led_color(struct Device* device, size_t index, const struct LedRgb color); + +/** + * @brief Sets the color of a single LED on the strip with an explicit brightness value. + * @param[in] device the LED strip device + * @param[in] index index of the LED in the strip (0-based) + * @param[in] color the color to set + * @param[in] brightness 0-255 + * @retval ERROR_NONE when the operation was successful + */ +error_t esp32_led_strip_set_single_led_color_and_brightness(struct Device* device, size_t index, const struct LedRgb color, uint8_t brightness); + +/** + * @brief Sets colors for a range of LEDs on the strip using the global brightness. + * @param[in] device the LED strip device + * @param[in] first starting LED index (0-based) + * @param[in] count number of LEDs to set + * @param[in] color array of colors to set + * @retval ERROR_NONE when the operation was successful + */ +error_t esp32_led_strip_set_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb* color); + +/** + * @brief Sets colors for a range of LEDs on the strip with an explicit brightness value. + * @param[in] device the LED strip device + * @param[in] first starting LED index (0-based) + * @param[in] count number of LEDs to set + * @param[in] color array of colors to set + * @param[in] brightness 0-255 + * @retval ERROR_NONE when the operation was successful + */ +error_t esp32_led_strip_set_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb* color, uint8_t brightness); + +/** + * @brief Fills a range of LEDs with a single color using the global brightness. + * @param[in] device the LED strip device + * @param[in] first starting LED index (0-based) + * @param[in] count number of LEDs to fill + * @param[in] color the color to fill with + * @retval ERROR_NONE when the operation was successful + */ +error_t esp32_led_strip_fill_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb color); + +/** + * @brief Fills a range of LEDs with a single color and an explicit brightness value. + * @param[in] device the LED strip device + * @param[in] first starting LED index (0-based) + * @param[in] count number of LEDs to fill + * @param[in] color the color to fill with + * @param[in] brightness 0-255 + * @retval ERROR_NONE when the operation was successful + */ +error_t esp32_led_strip_fill_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb color, uint8_t brightness); + +/** + * @brief Sets global brightness for the LED strip. + * @param[in] device the LED strip device + * @param[in] brightness 0-255 + * @retval ERROR_NONE when the operation was successful + */ +error_t esp32_led_strip_set_brightness(struct Device* device, uint8_t brightness); + +/** + * @brief Refreshes the strip to update the displayed colors. + * @param[in] device the LED strip device + * @retval ERROR_NONE when the operation was successful + */ +error_t esp32_led_strip_show(struct Device* device); + +/** + * @brief Clears the strip to black. + * @param[in] device the LED strip device + * @retval ERROR_NONE when the operation was successful + */ +error_t esp32_led_strip_clear(struct Device* device); + +/** + * @brief Gets the length of a strip. + * @param[in] device the LED strip device + * @param[out] out_length receives the strip length + * @retval ERROR_NONE when the operation was successful + * @retval ERROR_INVALID_ARGUMENT if out of range + */ +error_t esp32_led_strip_get_length(struct Device* device, uint16_t* out_length); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/esp32-led-strip-module/include/esp32_led_strip_module.h b/Drivers/esp32-led-strip-module/include/esp32_led_strip_module.h new file mode 100644 index 000000000..ad33cc90d --- /dev/null +++ b/Drivers/esp32-led-strip-module/include/esp32_led_strip_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module esp32_led_strip_module; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Drivers/esp32-led-strip-module/include/esp32_led_strip_rmt.h b/Drivers/esp32-led-strip-module/include/esp32_led_strip_rmt.h new file mode 100644 index 000000000..e0a600ec1 --- /dev/null +++ b/Drivers/esp32-led-strip-module/include/esp32_led_strip_rmt.h @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "esp32_led_strip_types.h" +#include "esp_idf_version.h" +#include "driver/rmt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LED Strip RMT specific configuration + */ +typedef struct { + rmt_clock_source_t clk_src; /*!< RMT clock source */ + uint32_t resolution_hz; /*!< RMT tick resolution, if set to zero, a default resolution (10MHz) will be applied */ + size_t mem_block_symbols; /*!< How many RMT symbols can one RMT channel hold at one time. Set to 0 will fallback to use the default size. */ + /*!< Extra RMT specific driver flags */ + struct led_strip_rmt_extra_config { + uint32_t with_dma: 1; /*!< Use DMA to transmit data */ + } flags; /*!< Extra driver flags */ +} led_strip_rmt_config_t; + +/** + * @brief Create LED strip based on RMT TX channel + * + * @param led_config LED strip configuration + * @param rmt_config RMT specific configuration + * @param ret_strip Returned LED strip handle + * @return + * - ESP_OK: create LED strip handle successfully + * - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument + * - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory + * - ESP_FAIL: create LED strip handle failed because some other error + */ +esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip); + +/** + * @brief Set RGB for a specific pixel + * + * @param strip: LED strip + * @param index: index of pixel to set + * @param red: red part of color + * @param green: green part of color + * @param blue: blue part of color + * + * @return + * - ESP_OK: Set RGB for a specific pixel successfully + * - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters + * - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred + */ +esp_err_t led_strip_rmt_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); + +/** + * @brief Refresh memory colors to LEDs + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Refresh successfully + * - ESP_FAIL: Refresh failed because some other error occurred + * + * @note: + * After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip. + */ +esp_err_t led_strip_rmt_refresh(led_strip_handle_t strip); + +/** + * @brief Clear LED strip (turn off all LEDs) + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Clear LEDs successfully + * - ESP_FAIL: Clear LEDs failed because some other error occurred + */ +esp_err_t led_strip_rmt_clear(led_strip_handle_t strip); + +/** + * @brief Free LED strip resources + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Free resources successfully + * - ESP_FAIL: Free resources failed because error occurred + */ +esp_err_t led_strip_rmt_del(led_strip_handle_t strip); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/esp32-led-strip-module/include/esp32_led_strip_rmt_encoder.h b/Drivers/esp32-led-strip-module/include/esp32_led_strip_rmt_encoder.h new file mode 100644 index 000000000..9e62615f0 --- /dev/null +++ b/Drivers/esp32-led-strip-module/include/esp32_led_strip_rmt_encoder.h @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "driver/rmt_encoder.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of led strip encoder configuration + */ +typedef struct { + uint32_t resolution; /*!< Encoder resolution, in Hz */ + led_model_t led_model; /*!< LED model */ +} led_strip_encoder_config_t; + +/** + * @brief Create RMT encoder for encoding LED strip pixels into RMT symbols + * + * @param[in] config Encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_ERR_INVALID_ARG for any invalid arguments + * - ESP_ERR_NO_MEM out of memory when creating led strip encoder + * - ESP_OK if creating encoder successfully + */ +esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/esp32-led-strip-module/include/esp32_led_strip_types.h b/Drivers/esp32-led-strip-module/include/esp32_led_strip_types.h new file mode 100644 index 000000000..1c61f40a8 --- /dev/null +++ b/Drivers/esp32-led-strip-module/include/esp32_led_strip_types.h @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of LED strip handle + */ +typedef struct led_strip_t led_strip_t; +typedef led_strip_t *led_strip_handle_t; + +/** + * @brief LED strip model + * @note Different led model may have different timing parameters, so we need to distinguish them. + */ +typedef enum { + LED_MODEL_WS2812, /*!< LED strip model: WS2812 */ + LED_MODEL_SK6812, /*!< LED strip model: SK6812 */ + LED_MODEL_WS2811, /*!< LED strip model: WS2811 */ + LED_MODEL_WS2816, /*!< LED strip model: WS2816 */ + LED_MODEL_INVALID /*!< Invalid LED strip model */ +} led_model_t; + +/** + * @brief LED color component format + * @note The format is used to specify the order of color components in each pixel, also the number of color components. + */ +typedef union { + struct format_layout { + uint32_t r_pos: 2; /*!< Position of the red channel in the color order: 0~3 */ + uint32_t g_pos: 2; /*!< Position of the green channel in the color order: 0~3 */ + uint32_t b_pos: 2; /*!< Position of the blue channel in the color order: 0~3 */ + uint32_t w_pos: 2; /*!< Position of the white channel in the color order: 0~3 */ + uint32_t reserved: 19; /*!< Reserved */ + uint32_t bytes_per_color: 2; /*!< Bytes per color component: 1 or 2. If set to 0, it will fallback to 1 */ + uint32_t num_components: 3; /*!< Number of color components per pixel: 3 or 4. If set to 0, it will fallback to 3 */ + } format; /*!< Format layout */ + uint32_t format_id; /*!< Format ID */ +} led_color_component_format_t; + +/// Helper macros to set the color component format +#define LED_STRIP_COLOR_COMPONENT_FMT_GRB (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 3}} +#define LED_STRIP_COLOR_COMPONENT_FMT_GRB_16 (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 3}} +#define LED_STRIP_COLOR_COMPONENT_FMT_GRBW (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 4}} +#define LED_STRIP_COLOR_COMPONENT_FMT_GRBW_16 (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 4}} +#define LED_STRIP_COLOR_COMPONENT_FMT_RGB (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 3}} +#define LED_STRIP_COLOR_COMPONENT_FMT_RGB_16 (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 3}} +#define LED_STRIP_COLOR_COMPONENT_FMT_RGBW (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 4}} +#define LED_STRIP_COLOR_COMPONENT_FMT_RGBW_16 (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 4}} + +/** + * @brief LED Strip common configurations + * The common configurations are not specific to any backend peripheral. + */ +typedef struct { + int strip_gpio_num; /*!< GPIO number that used by LED strip */ + uint32_t max_leds; /*!< Maximum number of LEDs that can be controlled in a single strip */ + led_model_t led_model; /*!< Specifies the LED strip model (e.g., WS2812, SK6812) */ + led_color_component_format_t color_component_format; /*!< Specifies the order of color components in each pixel. + Use helper macros like `LED_STRIP_COLOR_COMPONENT_FMT_GRB` to set the format */ + /*!< LED strip extra driver flags */ + struct led_strip_extra_flags { + uint32_t invert_out: 1; /*!< Invert output signal */ + } flags; /*!< Extra driver flags */ +} led_strip_config_t; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/esp32-led-strip-module/module.yaml b/Drivers/esp32-led-strip-module/module.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/esp32-led-strip-module/module.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/esp32-led-strip-module/source/esp32_led_strip.cpp b/Drivers/esp32-led-strip-module/source/esp32_led_strip.cpp new file mode 100644 index 000000000..e7f82bbb2 --- /dev/null +++ b/Drivers/esp32-led-strip-module/source/esp32_led_strip.cpp @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +constexpr auto* TAG = "Esp32LedStrip"; + +#define GET_CONFIG(device) (static_cast((device)->config)) +#define GET_INTERNAL(device) (static_cast(device_get_driver_data(device))) + +struct LedStripInternal { + led_strip_handle_t strip; + GpioPinSpec data_pin; + uint16_t led_count; + uint8_t global_brightness; +}; + +// Gamma correction lookup table for 8-bit brightness/color scaling +static const uint8_t gamma8[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, + 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, + 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, + 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, 30, + 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, 41, 42, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, + 76, 77, 78, 79, 81, 82, 83, 84, 86, 87, 88, 90, 91, 92, 94, 95, + 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117, 119, + 121, 122, 124, 125, 127, 129, 130, 132, 134, 135, 137, 139, 141, 142, 144, 146, + 148, 150, 151, 153, 155, 157, 159, 161, 163, 165, 166, 168, 170, 172, 174, 176, + 178, 180, 182, 184, 186, 188, 191, 193, 195, 197, 199, 201, 204, 206, 208, 210, + 213, 215, 217, 220, 222, 224, 227, 229, 231, 234, 236, 239, 241, 244, 246, 249, + 251, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 +}; + +static error_t create_strip(const LedStripConfig* source, led_strip_handle_t* out_strip) { + led_strip_config_t strip_config_internal = { + .strip_gpio_num = static_cast(source->pin_data.pin), + .max_leds = source->led_count, + .led_model = LED_MODEL_WS2812, + .color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB, + .flags = { + .invert_out = false, + } + }; + + led_strip_rmt_config_t rmt_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10 * 1000 * 1000, + .mem_block_symbols = 0, + .flags = { + .with_dma = 0, + } + }; + + led_strip_handle_t strip = nullptr; + esp_err_t err = led_strip_new_rmt_device(&strip_config_internal, &rmt_config, &strip); + if (err != ESP_OK) { + return ERROR_RESOURCE; + } + + *out_strip = strip; + return ERROR_NONE; +} + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(std::calloc(1, sizeof(LedStripInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + internal->data_pin = static_cast(config->pin_data); + internal->led_count = config->led_count; + internal->global_brightness = 255; + + led_strip_handle_t strip = nullptr; + if (create_strip(config, &strip) != ERROR_NONE) { + std::free(internal); + return ERROR_RESOURCE; + } + + internal->strip = strip; + + device_set_driver_data(device, internal); + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = GET_INTERNAL(device); + + led_strip_rmt_del(internal->strip); + + std::free(internal); + device_set_driver_data(device, nullptr); + return ERROR_NONE; +} + +static error_t apply_single(Device* device, size_t index, const LedRgb& color, uint8_t brightness) { + auto* internal = GET_INTERNAL(device); + + if (index >= internal->led_count) return ERROR_INVALID_ARGUMENT; + + uint32_t sr = (color.r * brightness) / 255; + uint32_t sg = (color.g * brightness) / 255; + uint32_t sb = (color.b * brightness) / 255; + + esp_err_t err = led_strip_rmt_set_pixel(internal->strip, index, gamma8[sr], gamma8[sg], gamma8[sb]); + if (err != ESP_OK) { + return ERROR_RESOURCE; + } + return ERROR_NONE; +} + +static error_t apply_range(Device* device, size_t first, size_t count, const LedRgb* colors, uint8_t brightness) { + auto* internal = GET_INTERNAL(device); + + if (first > internal->led_count) return ERROR_INVALID_ARGUMENT; + if (count > internal->led_count - first) return ERROR_INVALID_ARGUMENT; + if (colors == nullptr) return ERROR_INVALID_ARGUMENT; + + for (size_t i = 0; i < count; i++) { + uint32_t sr = (colors[i].r * brightness) / 255; + uint32_t sg = (colors[i].g * brightness) / 255; + uint32_t sb = (colors[i].b * brightness) / 255; + + esp_err_t err = led_strip_rmt_set_pixel(internal->strip, first + i, gamma8[sr], gamma8[sg], gamma8[sb]); + if (err != ESP_OK) { + return ERROR_RESOURCE; + } + } + return ERROR_NONE; +} + +static error_t apply_fill(Device* device, size_t first, size_t count, const LedRgb& color, uint8_t brightness) +{ + auto* internal = GET_INTERNAL(device); + if (first > internal->led_count) return ERROR_INVALID_ARGUMENT; + if (count > internal->led_count - first) return ERROR_INVALID_ARGUMENT; + + for (size_t i = 0; i < count; i++) { + uint32_t sr = (color.r * brightness) / 255; + uint32_t sg = (color.g * brightness) / 255; + uint32_t sb = (color.b * brightness) / 255; + + esp_err_t err = led_strip_rmt_set_pixel(internal->strip, first + i, gamma8[sr], gamma8[sg], gamma8[sb]); + if (err != ESP_OK) { + return ERROR_RESOURCE; + } + } + return ERROR_NONE; +} + +extern "C" { + +error_t esp32_led_strip_set_single_led_color(Device* device, size_t index, const LedRgb color) { + auto* internal = GET_INTERNAL(device); + return apply_single(device, index, color, internal->global_brightness); +} + +error_t esp32_led_strip_set_single_led_color_and_brightness(Device* device, size_t index, const LedRgb color, uint8_t brightness) { + return apply_single(device, index, color, brightness); +} + +error_t esp32_led_strip_set_led_range_color(Device* device, size_t first, size_t count, const LedRgb* color) { + auto* internal = GET_INTERNAL(device); + return apply_range(device, first, count, color, internal->global_brightness); +} + +error_t esp32_led_strip_set_led_range_color_and_brightness(Device* device, size_t first, size_t count, const LedRgb* color, uint8_t brightness) { + return apply_range(device, first, count, color, brightness); +} + +error_t esp32_led_strip_fill_led_range_color(Device* device, size_t first, size_t count, const LedRgb color) { + auto* internal = GET_INTERNAL(device); + return apply_fill(device, first, count, color, internal->global_brightness); +} + +error_t esp32_led_strip_fill_led_range_color_and_brightness(Device* device, size_t first, size_t count, const LedRgb color, uint8_t brightness) { + return apply_fill(device, first, count, color, brightness); +} + +error_t esp32_led_strip_set_brightness(Device* device, uint8_t brightness) { + auto* internal = GET_INTERNAL(device); + internal->global_brightness = brightness; + return ERROR_NONE; +} + +error_t esp32_led_strip_show(Device* device) { + auto* internal = GET_INTERNAL(device); + if (internal->strip != nullptr) { + esp_err_t err = led_strip_rmt_refresh(internal->strip); + if (err != ESP_OK) { + return ERROR_RESOURCE; + } + } + return ERROR_NONE; +} + +error_t esp32_led_strip_clear(Device* device) { + auto* internal = GET_INTERNAL(device); + if (internal->strip != nullptr) { + esp_err_t err = led_strip_rmt_clear(internal->strip); + if (err != ESP_OK) { + return ERROR_RESOURCE; + } + } + return ERROR_NONE; +} + +error_t esp32_led_strip_get_length(Device* device, uint16_t* out_length) { + auto* internal = GET_INTERNAL(device); + *out_length = internal->led_count; + return ERROR_NONE; +} + +static constexpr LedStripApi LED_STRIP_API = { + .set_single_led_color = esp32_led_strip_set_single_led_color, + .set_single_led_color_and_brightness = esp32_led_strip_set_single_led_color_and_brightness, + .set_led_range_color = esp32_led_strip_set_led_range_color, + .set_led_range_color_and_brightness = esp32_led_strip_set_led_range_color_and_brightness, + .fill_led_range_color = esp32_led_strip_fill_led_range_color, + .fill_led_range_color_and_brightness = esp32_led_strip_fill_led_range_color_and_brightness, + .set_brightness = esp32_led_strip_set_brightness, + .show = esp32_led_strip_show, + .clear = esp32_led_strip_clear, + .get_length = esp32_led_strip_get_length, +}; + +extern Module esp32_led_strip_module; + +Driver esp32_led_strip_driver = { + .name = "esp32_led_strip", + .compatible = (const char*[]) { "espressif,esp32-led-strip", nullptr }, + .start_device = start, + .stop_device = stop, + .probe = nullptr, + .api = &LED_STRIP_API, + .device_type = &LED_STRIP_TYPE, + .owner = &esp32_led_strip_module, + .internal = nullptr, +}; + +} // extern "C" diff --git a/Drivers/esp32-led-strip-module/source/esp32_led_strip_rmt_dev.c b/Drivers/esp32-led-strip-module/source/esp32_led_strip_rmt_dev.c new file mode 100644 index 000000000..70decdad6 --- /dev/null +++ b/Drivers/esp32-led-strip-module/source/esp32_led_strip_rmt_dev.c @@ -0,0 +1,170 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "esp_log.h" +#include "esp_check.h" +#include "driver/rmt_tx.h" +#include "drivers/esp32_led_strip.h" +#include "esp32_led_strip_rmt_encoder.h" +#include "esp32_led_strip_rmt.h" + +#define LED_STRIP_RMT_DEFAULT_RESOLUTION 10000000 // 10MHz resolution +#define LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE 4 +// the memory size of each RMT channel, in words (4 bytes) +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 +#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64 +#else +#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48 +#endif + +static const char *TAG = "led_strip_rmt"; + +typedef struct led_strip_t { + rmt_channel_handle_t rmt_chan; + rmt_encoder_handle_t strip_encoder; + uint32_t strip_len; + uint8_t bytes_per_pixel; + led_color_component_format_t component_fmt; + uint8_t pixel_buf[]; +} led_strip_rmt_obj; + +esp_err_t led_strip_rmt_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) +{ + led_strip_rmt_obj *rmt_strip = strip; + ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs"); + + struct format_layout format = rmt_strip->component_fmt.format; + uint32_t start = index * rmt_strip->bytes_per_pixel; + uint8_t *pixel_buf = rmt_strip->pixel_buf; + uint8_t pos_bytes = format.bytes_per_color; + + for (uint8_t i = 0; i < format.bytes_per_color; i++) { + uint8_t color_shift = 8 * (format.bytes_per_color - 1 - i); + pixel_buf[start + format.r_pos * pos_bytes + i] = (red >> color_shift) & 0xFF; + pixel_buf[start + format.g_pos * pos_bytes + i] = (green >> color_shift) & 0xFF; + pixel_buf[start + format.b_pos * pos_bytes + i] = (blue >> color_shift) & 0xFF; + if (format.num_components > 3) { + pixel_buf[start + format.w_pos * pos_bytes + i] = 0; + } + } + return ESP_OK; +} + +esp_err_t led_strip_rmt_refresh(led_strip_handle_t strip) +{ + led_strip_rmt_obj *rmt_strip = strip; + rmt_transmit_config_t tx_conf = { + .loop_count = 0, + }; + + ESP_RETURN_ON_ERROR(rmt_enable(rmt_strip->rmt_chan), TAG, "enable RMT channel failed"); + ESP_RETURN_ON_ERROR(rmt_transmit(rmt_strip->rmt_chan, rmt_strip->strip_encoder, rmt_strip->pixel_buf, + rmt_strip->strip_len * rmt_strip->bytes_per_pixel, &tx_conf), TAG, "transmit pixels by RMT failed"); + ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(rmt_strip->rmt_chan, -1), TAG, "flush RMT channel failed"); + ESP_RETURN_ON_ERROR(rmt_disable(rmt_strip->rmt_chan), TAG, "disable RMT channel failed"); + return ESP_OK; +} + +esp_err_t led_strip_rmt_clear(led_strip_handle_t strip) +{ + led_strip_rmt_obj *rmt_strip = strip; + // Write zero to turn off all leds + memset(rmt_strip->pixel_buf, 0, rmt_strip->strip_len * rmt_strip->bytes_per_pixel); + return led_strip_rmt_refresh(strip); +} + +esp_err_t led_strip_rmt_del(led_strip_handle_t strip) +{ + led_strip_rmt_obj *rmt_strip = strip; + ESP_RETURN_ON_ERROR(rmt_del_channel(rmt_strip->rmt_chan), TAG, "delete RMT channel failed"); + ESP_RETURN_ON_ERROR(rmt_del_encoder(rmt_strip->strip_encoder), TAG, "delete strip encoder failed"); + free(rmt_strip); + return ESP_OK; +} + +esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip) +{ + led_strip_rmt_obj *rmt_strip = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(led_config && rmt_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + led_color_component_format_t component_fmt = led_config->color_component_format; + // If R/G/B order is not specified, set default GRB order as fallback + if (component_fmt.format_id == 0) { + component_fmt = LED_STRIP_COLOR_COMPONENT_FMT_GRB; + } + if (led_config->led_model == LED_MODEL_WS2816) { + component_fmt.format.bytes_per_color = 2; + } + if (component_fmt.format.bytes_per_color == 0) { + component_fmt.format.bytes_per_color = 1; + } + // check the validation of the color component format + uint8_t mask = 0; + if (component_fmt.format.num_components == 3) { + mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos); + // Check for invalid values + ESP_RETURN_ON_FALSE(mask == 0x07, ESP_ERR_INVALID_ARG, TAG, "invalid order argument"); + } else if (component_fmt.format.num_components == 4) { + mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos) | BIT(component_fmt.format.w_pos); + // Check for invalid values + ESP_RETURN_ON_FALSE(mask == 0x0F, ESP_ERR_INVALID_ARG, TAG, "invalid order argument"); + } else { + ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid number of color components: %d", component_fmt.format.num_components); + } + uint8_t bytes_per_pixel = component_fmt.format.num_components; + if (component_fmt.format.bytes_per_color > 1) { + bytes_per_pixel *= component_fmt.format.bytes_per_color; + } + rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel); + ESP_GOTO_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt strip"); + uint32_t resolution = rmt_config->resolution_hz ? rmt_config->resolution_hz : LED_STRIP_RMT_DEFAULT_RESOLUTION; + + // for backward compatibility, if the user does not set the clk_src, use the default value + rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT; + if (rmt_config->clk_src) { + clk_src = rmt_config->clk_src; + } + size_t mem_block_symbols = LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS; + // override the default value if the user sets it + if (rmt_config->mem_block_symbols) { + mem_block_symbols = rmt_config->mem_block_symbols; + } + rmt_tx_channel_config_t rmt_chan_config = { + .clk_src = clk_src, + .gpio_num = led_config->strip_gpio_num, + .mem_block_symbols = mem_block_symbols, + .resolution_hz = resolution, + .trans_queue_depth = LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE, + .flags.with_dma = rmt_config->flags.with_dma, + .flags.invert_out = led_config->flags.invert_out, + }; + ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed"); + + led_strip_encoder_config_t strip_encoder_conf = { + .resolution = resolution, + .led_model = led_config->led_model + }; + ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed"); + + rmt_strip->component_fmt = component_fmt; + rmt_strip->bytes_per_pixel = bytes_per_pixel; + rmt_strip->strip_len = led_config->max_leds; + *ret_strip = rmt_strip; + return ESP_OK; +err: + if (rmt_strip) { + if (rmt_strip->rmt_chan) { + rmt_del_channel(rmt_strip->rmt_chan); + } + if (rmt_strip->strip_encoder) { + rmt_del_encoder(rmt_strip->strip_encoder); + } + free(rmt_strip); + } + return ret; +} diff --git a/Drivers/esp32-led-strip-module/source/esp32_led_strip_rmt_encoder.c b/Drivers/esp32-led-strip-module/source/esp32_led_strip_rmt_encoder.c new file mode 100644 index 000000000..b96186847 --- /dev/null +++ b/Drivers/esp32-led-strip-module/source/esp32_led_strip_rmt_encoder.c @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "esp_idf_version.h" +#include "esp_check.h" +#include "esp_attr.h" +#include "esp32_led_strip_rmt_encoder.h" + +static const char *TAG = "led_rmt_encoder"; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) +#if CONFIG_RMT_ISR_IRAM_SAFE +#define RMT_ENCODER_FUNC_ATTR IRAM_ATTR +#else +#define RMT_ENCODER_FUNC_ATTR +#endif // CONFIG_RMT_ISR_IRAM_SAFE +#endif // ESP_IDF_VERSION + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *copy_encoder; + int state; + rmt_symbol_word_t reset_code; +} rmt_led_strip_encoder_t; + +RMT_ENCODER_FUNC_ATTR +static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder; + rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder; + rmt_encode_state_t session_state = 0; + rmt_encode_state_t state = 0; + size_t encoded_symbols = 0; + switch (led_encoder->state) { + case 0: // send RGB data + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 1; // switch to next state when current encoding session finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + // fall-through + case 1: // send reset code + encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code, + sizeof(led_encoder->reset_code), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 0; // back to the initial encoding session + state |= RMT_ENCODING_COMPLETE; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_del_encoder(led_encoder->bytes_encoder); + rmt_del_encoder(led_encoder->copy_encoder); + free(led_encoder); + return ESP_OK; +} + +RMT_ENCODER_FUNC_ATTR +static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_reset(led_encoder->bytes_encoder); + rmt_encoder_reset(led_encoder->copy_encoder); + led_encoder->state = 0; + return ESP_OK; +} + +esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_led_strip_encoder_t *led_encoder = NULL; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(config->led_model < LED_MODEL_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led model"); + led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t)); + ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder"); + led_encoder->base.encode = rmt_encode_led_strip; + led_encoder->base.del = rmt_del_led_strip_encoder; + led_encoder->base.reset = rmt_led_strip_encoder_reset; + rmt_bytes_encoder_config_t bytes_encoder_config; + uint32_t reset_ticks = config->resolution / 1000000 * 280 / 2; // reset code duration defaults to 280us to accommodate WS2812B-V5 + if (config->led_model == LED_MODEL_SK6812) { + bytes_encoder_config = (rmt_bytes_encoder_config_t) { + .bit0 = { + .level0 = 1, + .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us + .level1 = 0, + .duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us + }, + .bit1 = { + .level0 = 1, + .duration0 = 0.6 * config->resolution / 1000000, // T1H=0.6us + .level1 = 0, + .duration1 = 0.6 * config->resolution / 1000000, // T1L=0.6us + }, + .flags.msb_first = 1 // SK6812 transfer bit order: G7...G0R7...R0B7...B0(W7...W0) + }; + } else if (config->led_model == LED_MODEL_WS2812) { + // different led strip might have its own timing requirements, following parameter is for WS2812 + bytes_encoder_config = (rmt_bytes_encoder_config_t) { + .bit0 = { + .level0 = 1, + .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us + .level1 = 0, + .duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us + }, + .bit1 = { + .level0 = 1, + .duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us + .level1 = 0, + .duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us + }, + .flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0 + }; + } else if (config->led_model == LED_MODEL_WS2811) { + // different led strip might have its own timing requirements, following parameter is for WS2811 + bytes_encoder_config = (rmt_bytes_encoder_config_t) { + .bit0 = { + .level0 = 1, + .duration0 = 0.5 * config->resolution / 1000000., // T0H=0.5us + .level1 = 0, + .duration1 = 2.0 * config->resolution / 1000000., // T0L=2.0us + }, + .bit1 = { + .level0 = 1, + .duration0 = 1.2 * config->resolution / 1000000., // T1H=1.2us + .level1 = 0, + .duration1 = 1.3 * config->resolution / 1000000., // T1L=1.3us + }, + .flags.msb_first = 1 + }; + reset_ticks = config->resolution / 1000000 * 50 / 2; // divide by 2... signal is sent twice + } else if (config->led_model == LED_MODEL_WS2816) { + // different led strip might have its own timing requirements, following parameter is for WS2816 + bytes_encoder_config = (rmt_bytes_encoder_config_t) { + .bit0 = { + .level0 = 1, + .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us + .level1 = 0, + .duration1 = 0.95 * config->resolution / 1000000, // T0L=0.95us + }, + .bit1 = { + .level0 = 1, + .duration0 = 0.75 * config->resolution / 1000000, // T1H=0.75us + .level1 = 0, + .duration1 = 0.5 * config->resolution / 1000000, // T1L=0.5us + }, + .flags.msb_first = 1 + }; + } else { + assert(false); + } + ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed"); + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed"); + + led_encoder->reset_code = (rmt_symbol_word_t) { + .level0 = 0, + .duration0 = reset_ticks, + .level1 = 0, + .duration1 = reset_ticks, + }; + *ret_encoder = &led_encoder->base; + return ESP_OK; +err: + if (led_encoder) { + if (led_encoder->bytes_encoder) { + rmt_del_encoder(led_encoder->bytes_encoder); + } + if (led_encoder->copy_encoder) { + rmt_del_encoder(led_encoder->copy_encoder); + } + free(led_encoder); + } + return ret; +} diff --git a/Drivers/esp32-led-strip-module/source/module.cpp b/Drivers/esp32-led-strip-module/source/module.cpp new file mode 100644 index 000000000..05204f4ae --- /dev/null +++ b/Drivers/esp32-led-strip-module/source/module.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +extern "C" { + +extern Driver esp32_led_strip_driver; + +static Driver* const esp32_led_strip_drivers[] = { + &esp32_led_strip_driver, + nullptr +}; + +Module esp32_led_strip_module = { + .name = "esp32_led_strip", + .start = nullptr, + .stop = nullptr, + .drivers = esp32_led_strip_drivers, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Modules/lvgl-module/assets/generate-all.py b/Modules/lvgl-module/assets/generate-all.py index 3a67c271c..6b19da426 100644 --- a/Modules/lvgl-module/assets/generate-all.py +++ b/Modules/lvgl-module/assets/generate-all.py @@ -114,6 +114,7 @@ def generate_icon_names(codepoint_map: dict, codepoint_names: list, variable_nam "terminal", "logo_dev", "camera", + "lightstrip", ] # Get more from https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded diff --git a/Modules/lvgl-module/include/lvgl/icons/shared.h b/Modules/lvgl-module/include/lvgl/icons/shared.h index d2fcbd451..7c745beff 100644 --- a/Modules/lvgl-module/include/lvgl/icons/shared.h +++ b/Modules/lvgl-module/include/lvgl/icons/shared.h @@ -48,3 +48,4 @@ #define LVGL_ICON_SHARED_TERMINAL "\xEE\xAE\x8E" #define LVGL_ICON_SHARED_LOGO_DEV "\xEE\xAB\x96" #define LVGL_ICON_SHARED_CAMERA "\xEE\x8E\xAF" +#define LVGL_ICON_SHARED_LIGHTSTRIP "\xF3\xBF\xBD\xB5" diff --git a/Modules/lvgl-module/source-fonts/material_symbols_shared_12.c b/Modules/lvgl-module/source-fonts/material_symbols_shared_12.c index 8c0c11f52..0ca7af0fa 100644 --- a/Modules/lvgl-module/source-fonts/material_symbols_shared_12.c +++ b/Modules/lvgl-module/source-fonts/material_symbols_shared_12.c @@ -1,7 +1,7 @@ /******************************************************************************* * Size: 12 px * Bpp: 2 - * Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF --format lvgl -o ../source-fonts/material_symbols_shared_12.c --force-fast-kern-format + * Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_12.c --force-fast-kern-format ******************************************************************************/ #ifdef LV_LVGL_H_INCLUDE_SIMPLE @@ -258,7 +258,13 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { 0x0, 0x80, 0x0, 0x9, 0x0, 0x70, 0x91, 0xcd, 0x9, 0xd, 0xc0, 0x90, 0xac, 0x4, 0x6, 0xd0, 0x0, 0x97, 0x0, 0x1c, 0x2c, 0x7, 0x0, 0x7f, - 0x80 + 0x80, + + /* U+FFF75 "󿽵" */ + 0xff, 0xff, 0xec, 0x41, 0x13, 0xd5, 0x55, 0x7a, + 0xaf, 0xef, 0xb, 0xd1, 0x3b, 0x81, 0x2e, 0xd5, + 0x7f, 0x5e, 0xaa, 0xab, 0xc4, 0x11, 0x3b, 0xff, + 0xff }; @@ -313,7 +319,8 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { {.bitmap_index = 883, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2}, {.bitmap_index = 899, .adv_w = 192, .box_w = 12, .box_h = 8, .ofs_x = 0, .ofs_y = 2}, {.bitmap_index = 923, .adv_w = 192, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = 1}, - {.bitmap_index = 946, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1} + {.bitmap_index = 946, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1}, + {.bitmap_index = 971, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1} }; /*--------------------- @@ -335,6 +342,10 @@ static const lv_font_fmt_txt_cmap_t cmaps[] = { .range_start = 57669, .range_length = 6019, .glyph_id_start = 1, .unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 46, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + }, + { + .range_start = 1048437, .range_length = 1, .glyph_id_start = 47, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY } }; @@ -359,7 +370,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = { .cmaps = cmaps, .kern_dsc = NULL, .kern_scale = 0, - .cmap_num = 1, + .cmap_num = 2, .bpp = 2, .kern_classes = 0, .bitmap_format = 0, diff --git a/Modules/lvgl-module/source-fonts/material_symbols_shared_16.c b/Modules/lvgl-module/source-fonts/material_symbols_shared_16.c index dad2feff7..63d932133 100644 --- a/Modules/lvgl-module/source-fonts/material_symbols_shared_16.c +++ b/Modules/lvgl-module/source-fonts/material_symbols_shared_16.c @@ -1,7 +1,7 @@ /******************************************************************************* * Size: 16 px * Bpp: 2 - * Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF --format lvgl -o ../source-fonts/material_symbols_shared_16.c --force-fast-kern-format + * Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_16.c --force-fast-kern-format ******************************************************************************/ #ifdef LV_LVGL_H_INCLUDE_SIMPLE @@ -361,7 +361,16 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { 0xca, 0x0, 0x80, 0xc, 0xa0, 0x0, 0x0, 0xc7, 0x0, 0x0, 0x2c, 0x38, 0x0, 0x3, 0x40, 0xe0, 0x1, 0xe0, 0x3, 0xea, 0xf8, 0x0, 0x6, 0xf8, - 0x0 + 0x0, + + /* U+FFF75 "󿽵" */ + 0x55, 0x55, 0x55, 0xf, 0xff, 0xff, 0xfc, 0xd1, + 0x4, 0x4, 0xde, 0x55, 0x55, 0x5d, 0xff, 0xff, + 0xff, 0xd0, 0x7, 0xf4, 0xd, 0x1b, 0xe0, 0x21, + 0xd7, 0x90, 0x82, 0xf8, 0xe1, 0x7, 0xf8, 0xf, + 0xff, 0xff, 0xfd, 0xe5, 0x55, 0x55, 0xdd, 0x20, + 0x80, 0x8d, 0xb5, 0x55, 0x55, 0xd2, 0xff, 0xff, + 0xfd }; @@ -416,7 +425,8 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { {.bitmap_index = 1630, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2}, {.bitmap_index = 1666, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2}, {.bitmap_index = 1714, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1}, - {.bitmap_index = 1756, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1} + {.bitmap_index = 1756, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1}, + {.bitmap_index = 1805, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1} }; /*--------------------- @@ -438,6 +448,10 @@ static const lv_font_fmt_txt_cmap_t cmaps[] = { .range_start = 57669, .range_length = 6019, .glyph_id_start = 1, .unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 46, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + }, + { + .range_start = 1048437, .range_length = 1, .glyph_id_start = 47, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY } }; @@ -462,7 +476,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = { .cmaps = cmaps, .kern_dsc = NULL, .kern_scale = 0, - .cmap_num = 1, + .cmap_num = 2, .bpp = 2, .kern_classes = 0, .bitmap_format = 0, diff --git a/Modules/lvgl-module/source-fonts/material_symbols_shared_20.c b/Modules/lvgl-module/source-fonts/material_symbols_shared_20.c index 11eb44f8f..d7525abc6 100644 --- a/Modules/lvgl-module/source-fonts/material_symbols_shared_20.c +++ b/Modules/lvgl-module/source-fonts/material_symbols_shared_20.c @@ -1,7 +1,7 @@ /******************************************************************************* * Size: 20 px * Bpp: 2 - * Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF --format lvgl -o ../source-fonts/material_symbols_shared_20.c --force-fast-kern-format + * Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_20.c --force-fast-kern-format ******************************************************************************/ #ifdef LV_LVGL_H_INCLUDE_SIMPLE @@ -480,6 +480,19 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { 0x0, 0x7, 0x80, 0xf4, 0x0, 0x0, 0xf0, 0x3, 0xe0, 0x0, 0x7c, 0x0, 0xb, 0xea, 0xbf, 0x0, 0x0, 0x1b, 0xff, 0x40, 0x0, 0x0, 0x0, 0x0, + 0x0, + + /* U+FFF75 "󿽵" */ + 0x2a, 0xaa, 0xaa, 0xa9, 0x3, 0xff, 0xff, 0xff, + 0xfc, 0x38, 0x0, 0x0, 0x1, 0xd3, 0x89, 0x7, + 0x3, 0xe, 0x38, 0x0, 0x0, 0x0, 0xe3, 0xff, + 0xff, 0xff, 0xfe, 0x2a, 0xab, 0xff, 0xab, 0xe0, + 0x1, 0xff, 0x44, 0xe, 0x6, 0xfe, 0x0, 0x82, + 0xd2, 0xf9, 0x2, 0x6, 0xf8, 0x38, 0x30, 0x1b, + 0xf8, 0x3, 0xea, 0xaf, 0xfe, 0xa9, 0x3f, 0xff, + 0xff, 0xff, 0xe3, 0x80, 0x0, 0x1, 0xe, 0x38, + 0x90, 0x20, 0x20, 0xe3, 0xea, 0xaa, 0xaa, 0xae, + 0x1f, 0xff, 0xff, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0 }; @@ -535,7 +548,8 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { {.bitmap_index = 2555, .adv_w = 320, .box_w = 14, .box_h = 14, .ofs_x = 3, .ofs_y = 3}, {.bitmap_index = 2604, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3}, {.bitmap_index = 2674, .adv_w = 320, .box_w = 16, .box_h = 18, .ofs_x = 2, .ofs_y = 1}, - {.bitmap_index = 2746, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1} + {.bitmap_index = 2746, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1}, + {.bitmap_index = 2827, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1} }; /*--------------------- @@ -557,6 +571,10 @@ static const lv_font_fmt_txt_cmap_t cmaps[] = { .range_start = 57669, .range_length = 6019, .glyph_id_start = 1, .unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 46, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + }, + { + .range_start = 1048437, .range_length = 1, .glyph_id_start = 47, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY } }; @@ -581,7 +599,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = { .cmaps = cmaps, .kern_dsc = NULL, .kern_scale = 0, - .cmap_num = 1, + .cmap_num = 2, .bpp = 2, .kern_classes = 0, .bitmap_format = 0, diff --git a/Modules/lvgl-module/source-fonts/material_symbols_shared_24.c b/Modules/lvgl-module/source-fonts/material_symbols_shared_24.c index 18ca9baeb..d7c544e2e 100644 --- a/Modules/lvgl-module/source-fonts/material_symbols_shared_24.c +++ b/Modules/lvgl-module/source-fonts/material_symbols_shared_24.c @@ -1,7 +1,7 @@ /******************************************************************************* * Size: 24 px * Bpp: 2 - * Opts: --no-compress --no-prefilter --bpp 2 --size 24 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF --format lvgl -o ../source-fonts/material_symbols_shared_24.c --force-fast-kern-format + * Opts: --no-compress --no-prefilter --bpp 2 --size 24 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_24.c --force-fast-kern-format ******************************************************************************/ #ifdef LV_LVGL_H_INCLUDE_SIMPLE @@ -582,7 +582,22 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { 0x0, 0x0, 0x7c, 0x1f, 0x0, 0x0, 0x0, 0xf4, 0xb, 0xd0, 0x0, 0x7, 0xe0, 0x2, 0xfd, 0x0, 0x7f, 0x80, 0x0, 0x7f, 0xff, 0xfd, 0x0, 0x0, - 0x6, 0xff, 0x90, 0x0 + 0x6, 0xff, 0x90, 0x0, + + /* U+FFF75 "󿽵" */ + 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0xf0, 0x0, 0x0, 0x0, 0xf, 0xf0, + 0xd0, 0x28, 0x7, 0xf, 0xf0, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x0, 0x2, 0xff, 0x80, 0xf, + 0x0, 0x7f, 0xf4, 0x28, 0xf, 0xb, 0xfe, 0x0, + 0x0, 0x2f, 0x7f, 0x90, 0x28, 0x7, 0xfd, 0xf8, + 0x34, 0x1, 0xbf, 0xe0, 0xf0, 0x10, 0x6f, 0xf9, + 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0xf, + 0xf0, 0xd0, 0x28, 0x7, 0xf, 0xf0, 0x0, 0x0, + 0x0, 0xf, 0xbf, 0xff, 0xff, 0xff, 0xff, 0x2f, + 0xff, 0xff, 0xff, 0xff }; @@ -637,7 +652,8 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { {.bitmap_index = 3288, .adv_w = 384, .box_w = 16, .box_h = 16, .ofs_x = 4, .ofs_y = 4}, {.bitmap_index = 3352, .adv_w = 384, .box_w = 22, .box_h = 16, .ofs_x = 1, .ofs_y = 4}, {.bitmap_index = 3440, .adv_w = 384, .box_w = 18, .box_h = 20, .ofs_x = 3, .ofs_y = 2}, - {.bitmap_index = 3530, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2} + {.bitmap_index = 3530, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2}, + {.bitmap_index = 3630, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2} }; /*--------------------- @@ -659,6 +675,10 @@ static const lv_font_fmt_txt_cmap_t cmaps[] = { .range_start = 57669, .range_length = 6019, .glyph_id_start = 1, .unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 46, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + }, + { + .range_start = 1048437, .range_length = 1, .glyph_id_start = 47, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY } }; @@ -683,7 +703,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = { .cmaps = cmaps, .kern_dsc = NULL, .kern_scale = 0, - .cmap_num = 1, + .cmap_num = 2, .bpp = 2, .kern_classes = 0, .bitmap_format = 0, diff --git a/Modules/lvgl-module/source-fonts/material_symbols_shared_32.c b/Modules/lvgl-module/source-fonts/material_symbols_shared_32.c index 90fd2664b..013b37479 100644 --- a/Modules/lvgl-module/source-fonts/material_symbols_shared_32.c +++ b/Modules/lvgl-module/source-fonts/material_symbols_shared_32.c @@ -1,7 +1,7 @@ /******************************************************************************* * Size: 32 px * Bpp: 2 - * Opts: --no-compress --no-prefilter --bpp 2 --size 32 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF --format lvgl -o ../source-fonts/material_symbols_shared_32.c --force-fast-kern-format + * Opts: --no-compress --no-prefilter --bpp 2 --size 32 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_32.c --force-fast-kern-format ******************************************************************************/ #ifdef LV_LVGL_H_INCLUDE_SIMPLE @@ -980,6 +980,33 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { 0x0, 0xb, 0xff, 0xaa, 0xbf, 0xf4, 0x0, 0x0, 0x1, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x0, 0x1b, 0xff, 0xe4, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + + /* U+FFF75 "󿽵" */ + 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x40, 0x3f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3e, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xbd, 0x3e, 0x7, 0x0, 0x1c, + 0x0, 0x70, 0x3e, 0x3e, 0xb, 0x0, 0x2c, 0x0, + 0x70, 0x3e, 0x3e, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3e, 0x3f, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xbe, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x0, 0x0, + 0x6, 0xff, 0xf8, 0x0, 0x7e, 0x0, 0x0, 0xbf, + 0xfe, 0x42, 0x40, 0x3e, 0x0, 0x1f, 0xff, 0xd0, + 0x3, 0x80, 0x3e, 0x2, 0xff, 0xf8, 0x1c, 0x0, + 0x1, 0xfd, 0xf, 0xfe, 0x40, 0x2c, 0x0, 0x6f, + 0xf8, 0x3f, 0xe0, 0xd0, 0x0, 0xb, 0xff, 0xe0, + 0x3e, 0x1, 0xd0, 0x2, 0xff, 0xfd, 0x0, 0x3f, + 0x0, 0x0, 0x7f, 0xff, 0x80, 0x0, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xbe, 0x3e, 0x1, 0x0, 0x4, 0x0, + 0x10, 0x3e, 0x3e, 0xb, 0x0, 0x2c, 0x0, 0xb4, + 0x3e, 0x3e, 0x1, 0x0, 0x4, 0x0, 0x10, 0x3e, + 0x3f, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xbe, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; @@ -1035,7 +1062,8 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { {.bitmap_index = 6130, .adv_w = 512, .box_w = 22, .box_h = 22, .ofs_x = 5, .ofs_y = 5}, {.bitmap_index = 6251, .adv_w = 512, .box_w = 30, .box_h = 22, .ofs_x = 1, .ofs_y = 5}, {.bitmap_index = 6416, .adv_w = 512, .box_w = 25, .box_h = 28, .ofs_x = 4, .ofs_y = 2}, - {.bitmap_index = 6591, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2} + {.bitmap_index = 6591, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2}, + {.bitmap_index = 6787, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2} }; /*--------------------- @@ -1057,6 +1085,10 @@ static const lv_font_fmt_txt_cmap_t cmaps[] = { .range_start = 57669, .range_length = 6019, .glyph_id_start = 1, .unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 46, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + }, + { + .range_start = 1048437, .range_length = 1, .glyph_id_start = 47, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY } }; @@ -1081,7 +1113,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = { .cmaps = cmaps, .kern_dsc = NULL, .kern_scale = 0, - .cmap_num = 1, + .cmap_num = 2, .bpp = 2, .kern_classes = 0, .bitmap_format = 0, diff --git a/Modules/lvgl-module/source/widgets/toolbar.cpp b/Modules/lvgl-module/source/widgets/toolbar.cpp index 5a223a83c..cac2e1167 100644 --- a/Modules/lvgl-module/source/widgets/toolbar.cpp +++ b/Modules/lvgl-module/source/widgets/toolbar.cpp @@ -59,6 +59,7 @@ typedef struct { lv_obj_t* close_button_image; lv_obj_t* action_container; uint8_t action_count; + lv_event_cb_t nav_action_callback; } Toolbar; static void toolbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj); @@ -105,6 +106,7 @@ lv_obj_t* lvgl_toolbar_create(lv_obj_t* parent, const char* title) { lv_obj_set_height(obj, toolbar_height); auto* toolbar = reinterpret_cast(obj); + toolbar->nav_action_callback = nullptr; lv_obj_set_width(obj, LV_PCT(100)); lv_obj_set_style_pad_all(obj, 0, LV_STATE_DEFAULT); lv_obj_set_style_pad_column(obj, 0, LV_STATE_DEFAULT); @@ -176,7 +178,13 @@ void lvgl_toolbar_set_title(lv_obj_t* obj, const char* title) { void lvgl_toolbar_set_nav_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* user_data) { auto* toolbar = reinterpret_cast(obj); - lv_obj_add_event_cb(toolbar->close_button, callback, LV_EVENT_SHORT_CLICKED, user_data); + if (toolbar->nav_action_callback != nullptr) { + lv_obj_remove_event_cb(toolbar->close_button, toolbar->nav_action_callback); + } + toolbar->nav_action_callback = callback; + if (callback != nullptr) { + lv_obj_add_event_cb(toolbar->close_button, callback, LV_EVENT_SHORT_CLICKED, user_data); + } lv_image_set_src(toolbar->close_button_image, icon); // e.g. LV_SYMBOL_CLOSE } diff --git a/Tactility/Include/Tactility/settings/LedStripSettings.h b/Tactility/Include/Tactility/settings/LedStripSettings.h new file mode 100644 index 000000000..035e0eb35 --- /dev/null +++ b/Tactility/Include/Tactility/settings/LedStripSettings.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace tt::settings::ledstrip { + +enum class Pattern : uint8_t { + Solid, + Alternating, + Gradient, +}; + +struct LedStripSettings { + bool enabled = true; + Pattern pattern = Pattern::Alternating; + uint8_t brightness = 77; + uint8_t primaryPreset = 6; + uint8_t secondaryPreset = 7; + struct LedRgb primaryCustom { 255, 165, 0 }; //Orange + struct LedRgb secondaryCustom { 0, 0, 255 }; //Blue +}; + +bool load(const char* deviceName, LedStripSettings& settings); + +LedStripSettings loadOrGetDefault(const char* deviceName); + +LedStripSettings getDefault(); + +bool save(const char* deviceName, const LedStripSettings& settings); + +error_t apply(struct Device* device, const LedStripSettings& settings); + +} \ No newline at end of file diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index ae52fadf2..e6bf9e71a 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -91,6 +91,7 @@ extern "C" Module tactility_audio_module; #include #include +#include #include #include #include @@ -176,6 +177,7 @@ namespace app { namespace imageviewer { extern const ::AppManifest manifest; } namespace inputdialog { extern const ::AppManifest manifest; } namespace launcher { extern const ::AppManifest manifest; } + namespace ledstripsettings { extern const ::AppManifest manifest; } namespace localesettings { extern const ::AppManifest manifest; } namespace notes { extern const ::AppManifest manifest; } namespace power { extern const ::AppManifest manifest; } @@ -237,6 +239,9 @@ static void registerInternalApps() { if (device_exists_of_type(&DISPLAY_TYPE)) { app_manager_add(&app::display::manifest); } + if (device_exists_of_type(&LED_STRIP_TYPE)) { + app_manager_add(&app::ledstripsettings::manifest); + } app_manager_add(&app::files::manifest); app_manager_add(&app::fileselection::manifest); app_manager_add(&app::i2cscanner::manifest); diff --git a/Tactility/Source/app/ledstripsettings/LedStripSettings.cpp b/Tactility/Source/app/ledstripsettings/LedStripSettings.cpp new file mode 100644 index 000000000..9f3bbb308 --- /dev/null +++ b/Tactility/Source/app/ledstripsettings/LedStripSettings.cpp @@ -0,0 +1,474 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace tt::app::ledstripsettings { + +extern const ::AppManifest manifest; + +constexpr auto* TAG = "LedStripSettings"; +constexpr uint8_t CUSTOM_PRESET = 12; +constexpr const char* COLOR_NAMES[] = { + "White", "Green", "Red", "Blue", + "Yellow", "Orange", "Cyan", "Magenta", + "Purple", "Pink", "Teal", "Black" +}; + +namespace { + +struct Context { + uint32_t appInstanceId = 0; + Device* device = nullptr; + settings::ledstrip::LedStripSettings settings; + bool updated = false; + enum class Page : uint8_t { Main, Choices, CustomColor } page = Page::Main; + enum class ChoiceKind : uint8_t { None, Pattern, PrimaryColor, SecondaryColor } choiceKind = ChoiceKind::None; + bool editingSecondary = false; + lv_obj_t* toolbar = nullptr; + lv_obj_t* mainPanel = nullptr; + lv_obj_t* choicePanel = nullptr; + lv_obj_t* choiceList = nullptr; + lv_obj_t* customPanel = nullptr; + lv_obj_t* returnFocus = nullptr; + lv_obj_t* modeValue = nullptr; + lv_obj_t* primaryValue = nullptr; + lv_obj_t* secondaryRow = nullptr; + lv_obj_t* secondaryValue = nullptr; + lv_obj_t* primaryEditor = nullptr; + lv_obj_t* secondaryEditor = nullptr; + lv_obj_t* primarySliderBoxes[3] = {}; + lv_obj_t* secondarySliderBoxes[3] = {}; + lv_obj_t* brightnessSlider = nullptr; + lv_obj_t* brightnessValue = nullptr; +}; + +const char* patternName(settings::ledstrip::Pattern pattern) { + switch (pattern) { + case settings::ledstrip::Pattern::Solid: return "Solid"; + case settings::ledstrip::Pattern::Alternating: return "Alternating"; + case settings::ledstrip::Pattern::Gradient: return "Gradient"; + } + return "Solid"; +} + +const char* presetName(uint8_t preset) { + if (preset == CUSTOM_PRESET) return "Custom"; + return COLOR_NAMES[preset < CUSTOM_PRESET ? preset : 0]; +} + +void applyAndLog(Context& ctx) { + const error_t result = settings::ledstrip::apply(ctx.device, ctx.settings); + if (result != ERROR_NONE) { + LOG_W(TAG, "Failed to apply LED strip settings: %d", result); + } +} + +void updateVisibility(Context& ctx) { + const bool hasSecondary = ctx.settings.pattern != settings::ledstrip::Pattern::Solid; + if (hasSecondary) { + lv_obj_remove_flag(ctx.secondaryRow, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(ctx.secondaryRow, LV_OBJ_FLAG_HIDDEN); + } +} + +void onNavigationPressed(lv_event_t* event); + +void returnToMain(Context& ctx) { + ctx.page = Context::Page::Main; + ctx.choiceKind = Context::ChoiceKind::None; + lv_obj_add_flag(ctx.choicePanel, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ctx.customPanel, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(ctx.mainPanel, LV_OBJ_FLAG_HIDDEN); + lv_obj_update_layout(ctx.mainPanel); + lvgl_toolbar_set_title(ctx.toolbar, "LED Strip"); + lvgl_toolbar_set_nav_action(ctx.toolbar, LV_SYMBOL_CLOSE, onNavigationPressed, &ctx); + if (ctx.returnFocus != nullptr) { + lv_group_focus_obj(ctx.returnFocus); + } +} + +void onNavigationPressed(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + if (ctx->page != Context::Page::Main) { + returnToMain(*ctx); + } else { + app_event_emit_close(ctx->appInstanceId); + } +} + +void openCustomEditor(Context& ctx, bool secondary) { + ctx.page = Context::Page::CustomColor; + ctx.choiceKind = Context::ChoiceKind::None; + ctx.editingSecondary = secondary; + lv_obj_add_flag(ctx.choicePanel, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ctx.mainPanel, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(ctx.customPanel, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(secondary ? ctx.primaryEditor : ctx.secondaryEditor, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(secondary ? ctx.secondaryEditor : ctx.primaryEditor, LV_OBJ_FLAG_HIDDEN); + lvgl_toolbar_set_title(ctx.toolbar, secondary ? "Custom second color" : "Custom color"); + lvgl_toolbar_set_nav_action(ctx.toolbar, LV_SYMBOL_LEFT, onNavigationPressed, &ctx); + lv_obj_update_layout(ctx.customPanel); + auto* sliderBox = secondary ? ctx.secondarySliderBoxes[0] : ctx.primarySliderBoxes[0]; + lv_group_focus_obj(sliderBox); +} + +size_t choiceCount(Context::ChoiceKind kind) { + return kind == Context::ChoiceKind::Pattern ? 3 : CUSTOM_PRESET + 1; +} + +uint32_t currentChoice(const Context& ctx) { + switch (ctx.choiceKind) { + case Context::ChoiceKind::Pattern: return static_cast(ctx.settings.pattern); + case Context::ChoiceKind::PrimaryColor: return ctx.settings.primaryPreset; + case Context::ChoiceKind::SecondaryColor: return ctx.settings.secondaryPreset; + case Context::ChoiceKind::None: return 0; + } + return 0; +} + +const char* choiceTitle(Context::ChoiceKind kind) { + switch (kind) { + case Context::ChoiceKind::Pattern: return "Choose mode"; + case Context::ChoiceKind::PrimaryColor: return "Choose color"; + case Context::ChoiceKind::SecondaryColor: return "Choose second color"; + case Context::ChoiceKind::None: return "LED Strip"; + } + return "LED Strip"; +} + +const char* choiceName(Context::ChoiceKind kind, size_t index) { + if (kind == Context::ChoiceKind::Pattern) { + return patternName(static_cast(index)); + } + return index == CUSTOM_PRESET ? "Custom" : COLOR_NAMES[index]; +} + +void onChoiceSelected(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + const size_t selected = lv_obj_get_index(static_cast(lv_event_get_target(event))); + if (selected >= choiceCount(ctx->choiceKind)) return; + + const auto choiceKind = ctx->choiceKind; + switch (ctx->choiceKind) { + case Context::ChoiceKind::Pattern: + ctx->settings.pattern = static_cast(selected); + break; + case Context::ChoiceKind::PrimaryColor: + ctx->settings.primaryPreset = static_cast(selected); + break; + case Context::ChoiceKind::SecondaryColor: + ctx->settings.secondaryPreset = static_cast(selected); + break; + case Context::ChoiceKind::None: + return; + } + + ctx->updated = true; + lv_label_set_text(ctx->modeValue, patternName(ctx->settings.pattern)); + lv_label_set_text(ctx->primaryValue, presetName(ctx->settings.primaryPreset)); + lv_label_set_text(ctx->secondaryValue, presetName(ctx->settings.secondaryPreset)); + updateVisibility(*ctx); + if (selected == CUSTOM_PRESET && choiceKind != Context::ChoiceKind::Pattern) { + openCustomEditor(*ctx, choiceKind == Context::ChoiceKind::SecondaryColor); + applyAndLog(*ctx); + return; + } + returnToMain(*ctx); + applyAndLog(*ctx); +} + +void openChoice(Context& ctx, Context::ChoiceKind kind, lv_obj_t* returnFocus) { + ctx.page = Context::Page::Choices; + ctx.choiceKind = kind; + ctx.returnFocus = returnFocus; + lv_obj_clean(ctx.choiceList); + + const size_t selected = currentChoice(ctx); + lv_obj_t* selectedButton = nullptr; + for (size_t index = 0; index < choiceCount(kind); index++) { + auto* button = lv_list_add_button(ctx.choiceList, index == selected ? LV_SYMBOL_OK : nullptr, choiceName(kind, index)); + lv_obj_add_event_cb(button, onChoiceSelected, LV_EVENT_SHORT_CLICKED, &ctx); + if (index == selected) selectedButton = button; + } + + lvgl_toolbar_set_title(ctx.toolbar, choiceTitle(kind)); + lvgl_toolbar_set_nav_action(ctx.toolbar, LV_SYMBOL_LEFT, onNavigationPressed, &ctx); + lv_obj_add_flag(ctx.mainPanel, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(ctx.choicePanel, LV_OBJ_FLAG_HIDDEN); + if (selectedButton != nullptr) { + lv_group_focus_obj(selectedButton); + } +} + +void onOutputChanged(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + auto* sw = static_cast(lv_event_get_target(event)); + ctx->settings.enabled = lv_obj_has_state(sw, LV_STATE_CHECKED); + ctx->updated = true; + applyAndLog(*ctx); +} + +void onPatternPressed(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + openChoice(*ctx, Context::ChoiceKind::Pattern, static_cast(lv_event_get_target(event))); +} + +void onPrimaryColorPressed(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + openChoice(*ctx, Context::ChoiceKind::PrimaryColor, static_cast(lv_event_get_target(event))); +} + +void onSecondaryColorPressed(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + openChoice(*ctx, Context::ChoiceKind::SecondaryColor, static_cast(lv_event_get_target(event))); +} + +void setChannel(LedRgb& color, size_t channel, uint8_t value) { + switch (channel) { + case 0: color.r = value; break; + case 1: color.g = value; break; + case 2: color.b = value; break; + default: break; + } +} + +void onColorSliderChanged(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + auto* target = static_cast(lv_event_get_target(event)); + const uint8_t value = static_cast(lvgl_sliderbox_get_value(target)); + + for (size_t channel = 0; channel < 3; channel++) { + if (target == ctx->primarySliderBoxes[channel]) { + setChannel(ctx->settings.primaryCustom, channel, value); + ctx->updated = true; + applyAndLog(*ctx); + return; + } + if (target == ctx->secondarySliderBoxes[channel]) { + setChannel(ctx->settings.secondaryCustom, channel, value); + ctx->updated = true; + applyAndLog(*ctx); + return; + } + } +} + +void onBrightnessChanged(lv_event_t* event) { + auto* ctx = static_cast(lv_event_get_user_data(event)); + const auto value = static_cast(lvgl_sliderbox_get_value(static_cast(lv_event_get_target(event)))); + ctx->settings.brightness = value; + lv_label_set_text_fmt(ctx->brightnessValue, "%u%%", (static_cast(value) * 100 + 127) / 255); + ctx->updated = true; + applyAndLog(*ctx); +} + +lv_obj_t* createRow(lv_obj_t* parent, const char* title) { + auto* row = lv_obj_create(parent); + lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(row, 2, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(row, 0, LV_STATE_DEFAULT); + auto* label = lv_label_create(row); + lv_label_set_text(label, title); + lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0); + return row; +} + +void createRgbEditor(lv_obj_t* parent, const char* title, const LedRgb& color, lv_obj_t** sliderBoxes, Context* ctx, bool primary) { + auto* editor = lv_obj_create(parent); + lv_obj_set_width(editor, LV_PCT(100)); + lv_obj_set_flex_flow(editor, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(editor, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(editor, 0, LV_STATE_DEFAULT); + auto* heading = lv_label_create(editor); + lv_label_set_text(heading, title); + + constexpr const char* CHANNELS[] = { "R", "G", "B" }; + const uint8_t channels[] = { color.r, color.g, color.b }; + for (size_t i = 0; i < 3; i++) { + auto* row = lv_obj_create(editor); + lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(row, 1, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(row, 0, LV_STATE_DEFAULT); + auto* label = lv_label_create(row); + lv_label_set_text(label, CHANNELS[i]); + lv_obj_set_width(label, LV_PCT(12)); + lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0); + sliderBoxes[i] = lvgl_sliderbox_create(row, 0, UINT8_MAX, 8, channels[i]); + lv_obj_set_width(sliderBoxes[i], LV_PCT(84)); + lv_obj_align(sliderBoxes[i], LV_ALIGN_RIGHT_MID, 0, 0); + lvgl_sliderbox_add_value_changed_cb(sliderBoxes[i], onColorSliderChanged, ctx); + } + if (primary) { + ctx->primaryEditor = editor; + } else { + ctx->secondaryEditor = editor; + } +} + +void createWidgets(lv_obj_t* parent, void* userData) { + auto* ctx = static_cast(userData); + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); + + ctx->toolbar = lvgl_toolbar_create(parent, "LED Strip"); + lvgl_toolbar_set_nav_action(ctx->toolbar, LV_SYMBOL_CLOSE, onNavigationPressed, ctx); + + ctx->mainPanel = lv_obj_create(parent); + lv_obj_set_width(ctx->mainPanel, LV_PCT(100)); + lv_obj_set_flex_grow(ctx->mainPanel, 1); + lv_obj_set_flex_flow(ctx->mainPanel, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_row(ctx->mainPanel, 2, LV_STATE_DEFAULT); + lv_obj_add_flag(ctx->mainPanel, LV_OBJ_FLAG_SCROLLABLE); + + ctx->customPanel = lv_obj_create(parent); + lv_obj_set_width(ctx->customPanel, LV_PCT(100)); + lv_obj_set_flex_grow(ctx->customPanel, 1); + lv_obj_set_flex_flow(ctx->customPanel, LV_FLEX_FLOW_COLUMN); + lv_obj_add_flag(ctx->customPanel, LV_OBJ_FLAG_HIDDEN); + + auto* outputRow = createRow(ctx->mainPanel, "Output"); + auto* outputSwitch = lv_switch_create(outputRow); + if (ctx->settings.enabled) lv_obj_add_state(outputSwitch, LV_STATE_CHECKED); + lv_obj_align(outputSwitch, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(outputSwitch, onOutputChanged, LV_EVENT_VALUE_CHANGED, ctx); + + auto* modeRow = createRow(ctx->mainPanel, "Mode"); + auto* modeButton = lv_button_create(modeRow); + lv_obj_set_width(modeButton, LV_PCT(62)); + lv_obj_align(modeButton, LV_ALIGN_RIGHT_MID, 0, 0); + ctx->modeValue = lv_label_create(modeButton); + lv_label_set_text(ctx->modeValue, patternName(ctx->settings.pattern)); + lv_obj_center(ctx->modeValue); + lv_obj_add_event_cb(modeButton, onPatternPressed, LV_EVENT_SHORT_CLICKED, ctx); + + auto* primaryRow = createRow(ctx->mainPanel, "Color"); + auto* primaryButton = lv_button_create(primaryRow); + lv_obj_set_width(primaryButton, LV_PCT(62)); + lv_obj_align(primaryButton, LV_ALIGN_RIGHT_MID, 0, 0); + ctx->primaryValue = lv_label_create(primaryButton); + lv_label_set_text(ctx->primaryValue, presetName(ctx->settings.primaryPreset)); + lv_obj_center(ctx->primaryValue); + lv_obj_add_event_cb(primaryButton, onPrimaryColorPressed, LV_EVENT_SHORT_CLICKED, ctx); + + createRgbEditor(ctx->customPanel, "Red / Green / Blue", ctx->settings.primaryCustom, ctx->primarySliderBoxes, ctx, true); + lv_obj_add_flag(ctx->primaryEditor, LV_OBJ_FLAG_HIDDEN); + + ctx->secondaryRow = createRow(ctx->mainPanel, "Second color"); + auto* secondaryButton = lv_button_create(ctx->secondaryRow); + lv_obj_set_width(secondaryButton, LV_PCT(62)); + lv_obj_align(secondaryButton, LV_ALIGN_RIGHT_MID, 0, 0); + ctx->secondaryValue = lv_label_create(secondaryButton); + lv_label_set_text(ctx->secondaryValue, presetName(ctx->settings.secondaryPreset)); + lv_obj_center(ctx->secondaryValue); + lv_obj_add_event_cb(secondaryButton, onSecondaryColorPressed, LV_EVENT_SHORT_CLICKED, ctx); + + createRgbEditor(ctx->customPanel, "Red / Green / Blue", ctx->settings.secondaryCustom, ctx->secondarySliderBoxes, ctx, false); + lv_obj_add_flag(ctx->secondaryEditor, LV_OBJ_FLAG_HIDDEN); + + auto* brightnessRow = createRow(ctx->mainPanel, "Brightness"); + ctx->brightnessValue = lv_label_create(brightnessRow); + lv_label_set_text_fmt(ctx->brightnessValue, "%u%%", (static_cast(ctx->settings.brightness) * 100 + 127) / 255); + lv_obj_align(ctx->brightnessValue, LV_ALIGN_RIGHT_MID, 0, 0); + ctx->brightnessSlider = lvgl_sliderbox_create(ctx->mainPanel, 0, UINT8_MAX, 17, ctx->settings.brightness); + lv_obj_set_width(ctx->brightnessSlider, LV_PCT(100)); + lvgl_sliderbox_add_value_changed_cb(ctx->brightnessSlider, onBrightnessChanged, ctx); + + ctx->choicePanel = lv_obj_create(parent); + lv_obj_set_width(ctx->choicePanel, LV_PCT(100)); + lv_obj_set_flex_grow(ctx->choicePanel, 1); + lv_obj_set_flex_flow(ctx->choicePanel, LV_FLEX_FLOW_COLUMN); + lv_obj_add_flag(ctx->choicePanel, LV_OBJ_FLAG_HIDDEN); + ctx->choiceList = lv_list_create(ctx->choicePanel); + lv_obj_set_width(ctx->choiceList, LV_PCT(100)); + lv_obj_set_flex_grow(ctx->choiceList, 1); + + updateVisibility(*ctx); +} + +void persistIfUpdated(const Context& ctx) { + if (!ctx.updated) return; + const auto settingsToSave = ctx.settings; + const std::string deviceName = ctx.device->name; + getMainDispatcher().dispatch([settingsToSave, deviceName] { + if (!settings::ledstrip::save(deviceName.c_str(), settingsToSave)) { + LOG_W(TAG, "Failed to save LED strip settings"); + } + }); +} + +int32_t appMain(int argc, char* argv[]) { + Device* device = nullptr; + if (device_get_first_by_type(&LED_STRIP_TYPE, &device) != ERROR_NONE) { + return 0; + } + + Context ctx {}; + ctx.appInstanceId = app_scheduler_current_app_id(); + ctx.device = device; + ctx.settings = settings::ledstrip::loadOrGetDefault(device->name); + uint16_t ledCount = 0; + if (led_strip_get_length(device, &ledCount) != ERROR_NONE || ledCount == 0) { + device_put(device); + return 0; + } + applyAndLog(ctx); + + TaskEventGroup eventGroup {}; + task_event_group_construct(&eventGroup); + AppEventSubscription subscription {}; + if (app_event_subscribe(&subscription, &eventGroup) != ERROR_NONE) { + task_event_group_destruct(&eventGroup); + device_put(device); + return 0; + } + + const WindowId window = window_manager_create(ctx.appInstanceId, createWidgets, &ctx); + bool shouldClose = false; + while (!shouldClose) { + task_event_group_wait_any(&eventGroup, nullptr, portMAX_DELAY); + AppEvent event {}; + while (app_event_poll(&subscription, &event) == ERROR_NONE) { + if (event.type == APP_EVENT_CLOSE) { + persistIfUpdated(ctx); + shouldClose = true; + break; + } + } + } + + window_manager_remove(window); + app_event_unsubscribe(&subscription); + task_event_group_destruct(&eventGroup); + device_put(device); + return 0; +} + +} + +extern const ::AppManifest manifest = { + .id = "tactility.ledstripsettings", + .name = "LED Strip", + .category = APP_CATEGORY_SETTINGS, + .location = { APP_LOCATION_MEMORY, reinterpret_cast(appMain) }, + .flags = 0, + .stack = {} +}; + +} \ No newline at end of file diff --git a/Tactility/Source/app/settings/Settings.cpp b/Tactility/Source/app/settings/Settings.cpp index 94ffc6ca4..ccda269e2 100644 --- a/Tactility/Source/app/settings/Settings.cpp +++ b/Tactility/Source/app/settings/Settings.cpp @@ -43,6 +43,7 @@ constexpr IconEntry ICONS[] = { {"tactility.gpssettings", LVGL_ICON_SHARED_NAVIGATION}, {"tactility.grovesettings", LVGL_ICON_SHARED_CABLE}, {"tactility.keyboardsettings", LVGL_ICON_SHARED_KEYBOARD_ALT}, + {"tactility.ledstripsettings", LVGL_ICON_SHARED_LIGHTSTRIP}, {"tactility.localesettings", LVGL_ICON_SHARED_LANGUAGE}, {"tactility.power", LVGL_ICON_SHARED_POWER_SETTINGS_NEW}, {"tactility.timedatesettings", LVGL_ICON_SHARED_CALENDAR_MONTH}, diff --git a/Tactility/Source/settings/LedStripSettings.cpp b/Tactility/Source/settings/LedStripSettings.cpp new file mode 100644 index 000000000..86d98c85e --- /dev/null +++ b/Tactility/Source/settings/LedStripSettings.cpp @@ -0,0 +1,194 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace tt::settings::ledstrip { + +namespace { + +struct ColorPreset { + const char* name; + LedRgb color; +}; + +const ColorPreset COLOR_PRESETS[] = { + {"White", LED_RGB_COLOR_WHITE}, + {"Green", LED_RGB_COLOR_GREEN}, + {"Red", LED_RGB_COLOR_RED}, + {"Blue", LED_RGB_COLOR_BLUE}, + {"Yellow", LED_RGB_COLOR_YELLOW}, + {"Orange", LED_RGB_COLOR_ORANGE}, + {"Cyan", LED_RGB_COLOR_CYAN}, + {"Magenta", LED_RGB_COLOR_MAGENTA}, + {"Purple", LED_RGB_COLOR_PURPLE}, + {"Pink", LED_RGB_COLOR_PINK}, + {"Teal", LED_RGB_COLOR_TEAL}, + {"Black", LED_RGB_COLOR_BLACK}, +}; + +LedRgb getColor(uint8_t preset, const LedRgb& custom) { + if (preset == 12) return custom; + return COLOR_PRESETS[std::min(preset, std::size(COLOR_PRESETS) - 1)].color; +} + +uint8_t interpolate(uint8_t first, uint8_t second, size_t position, size_t count) { + if (count <= 1) return first; + const size_t denominator = count - 1; + return static_cast((first * (denominator - position) + second * position) / denominator); +} + +} + +static std::string getSettingsFilePath(const char* deviceName) { + char path[256]; + const auto fileName = std::string(deviceName) + ".properties"; + if (app_paths_get_user_data_path("tactility.ledstripsettings", fileName.c_str(), path, sizeof(path)) != ERROR_NONE) { + return ""; + } + return path; +} + +static bool readNumber(const std::map& properties, const char* key, unsigned maximum, unsigned& value) { + const auto it = properties.find(key); + if (it == properties.end()) { + return false; + } + + unsigned parsed = 0; + const auto result = std::from_chars(it->second.data(), it->second.data() + it->second.size(), parsed); + if (result.ec != std::errc() || result.ptr != it->second.data() + it->second.size() || parsed > maximum) { + return false; + } + + value = parsed; + return true; +} + +static bool readBool(const std::map& properties, const char* key, bool& value) { + const auto it = properties.find(key); + if (it == properties.end()) { + return false; + } + if (it->second == "1" || it->second == "true") { + value = true; + return true; + } + if (it->second == "0" || it->second == "false") { + value = false; + return true; + } + return false; +} + +LedStripSettings getDefault() { + return {}; +} + +bool load(const char* deviceName, LedStripSettings& settings) { + const auto path = getSettingsFilePath(deviceName); + if (!file::isFile(path)) { + return false; + } + + std::map properties; + if (!file::loadPropertiesFile(path, properties)) { + return false; + } + + auto loaded = getDefault(); + unsigned value = 0; + readBool(properties, "enabled", loaded.enabled); + if (readNumber(properties, "pattern", static_cast(Pattern::Gradient), value)) { + loaded.pattern = static_cast(value); + } + + if (readNumber(properties, "brightness", UINT8_MAX, value)) loaded.brightness = static_cast(value); + if (readNumber(properties, "primaryPreset", 12, value)) loaded.primaryPreset = static_cast(value); + if (readNumber(properties, "secondaryPreset", 12, value)) loaded.secondaryPreset = static_cast(value); + + if (readNumber(properties, "primaryRed", UINT8_MAX, value)) loaded.primaryCustom.r = static_cast(value); + if (readNumber(properties, "primaryGreen", UINT8_MAX, value)) loaded.primaryCustom.g = static_cast(value); + if (readNumber(properties, "primaryBlue", UINT8_MAX, value)) loaded.primaryCustom.b = static_cast(value); + if (readNumber(properties, "secondaryRed", UINT8_MAX, value)) loaded.secondaryCustom.r = static_cast(value); + if (readNumber(properties, "secondaryGreen", UINT8_MAX, value)) loaded.secondaryCustom.g = static_cast(value); + if (readNumber(properties, "secondaryBlue", UINT8_MAX, value)) loaded.secondaryCustom.b = static_cast(value); + + settings = loaded; + return true; +} + +LedStripSettings loadOrGetDefault(const char* deviceName) { + auto settings = getDefault(); + load(deviceName, settings); + return settings; +} + +bool save(const char* deviceName, const LedStripSettings& settings) { + std::map properties; + properties["enabled"] = settings.enabled ? "1" : "0"; + properties["pattern"] = std::to_string(static_cast(settings.pattern)); + properties["brightness"] = std::to_string(settings.brightness); + properties["primaryPreset"] = std::to_string(settings.primaryPreset); + properties["secondaryPreset"] = std::to_string(settings.secondaryPreset); + properties["primaryRed"] = std::to_string(settings.primaryCustom.r); + properties["primaryGreen"] = std::to_string(settings.primaryCustom.g); + properties["primaryBlue"] = std::to_string(settings.primaryCustom.b); + properties["secondaryRed"] = std::to_string(settings.secondaryCustom.r); + properties["secondaryGreen"] = std::to_string(settings.secondaryCustom.g); + properties["secondaryBlue"] = std::to_string(settings.secondaryCustom.b); + + auto settings_path = getSettingsFilePath(deviceName); + if (!file::findOrCreateParentDirectory(settings_path, 0755)) { + return false; + } + + return file::savePropertiesFile(settings_path, properties); +} + +error_t apply(struct Device* device, const LedStripSettings& settings) { + if (!settings.enabled) { + return led_strip_clear(device); + } + + uint16_t ledCount = 0; + auto result = led_strip_get_length(device, &ledCount); + if (result != ERROR_NONE) return result; + if (ledCount == 0) return ERROR_INVALID_ARGUMENT; + + result = led_strip_set_brightness(device, settings.brightness); + if (result != ERROR_NONE) return result; + + const LedRgb primary = getColor(settings.primaryPreset, settings.primaryCustom); + const LedRgb secondary = getColor(settings.secondaryPreset, settings.secondaryCustom); + if (settings.pattern == Pattern::Solid) { + result = led_strip_fill_led_range_color(device, 0, ledCount, primary); + if (result != ERROR_NONE) return result; + } else { + for (size_t index = 0; index < ledCount; index++) { + LedRgb color = primary; + if (settings.pattern == Pattern::Alternating) { + color = (index % 2 == 0) ? primary : secondary; + } else if (settings.pattern == Pattern::Gradient) { + color = { + interpolate(primary.r, secondary.r, index, ledCount), + interpolate(primary.g, secondary.g, index, ledCount), + interpolate(primary.b, secondary.b, index, ledCount), + }; + } + result = led_strip_set_single_led_color(device, index, color); + if (result != ERROR_NONE) return result; + } + } + return led_strip_show(device); +} + +} \ No newline at end of file diff --git a/TactilityKernel/include/tactility/drivers/led_strip.h b/TactilityKernel/include/tactility/drivers/led_strip.h new file mode 100644 index 000000000..17d0bc261 --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/led_strip.h @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief An RGB color value. + */ +struct LedRgb { + uint8_t r; + uint8_t g; + uint8_t b; +}; + +#define LED_RGB_COLOR_WHITE ((struct LedRgb){255, 255, 255}) +#define LED_RGB_COLOR_GREEN ((struct LedRgb){0, 255, 0}) +#define LED_RGB_COLOR_RED ((struct LedRgb){255, 0, 0}) +#define LED_RGB_COLOR_BLUE ((struct LedRgb){0, 0, 255}) +#define LED_RGB_COLOR_YELLOW ((struct LedRgb){255, 255, 0}) +#define LED_RGB_COLOR_ORANGE ((struct LedRgb){255, 165, 0}) +#define LED_RGB_COLOR_CYAN ((struct LedRgb){0, 255, 255}) +#define LED_RGB_COLOR_MAGENTA ((struct LedRgb){255, 0, 255}) +#define LED_RGB_COLOR_PURPLE ((struct LedRgb){191, 0, 255}) +#define LED_RGB_COLOR_PINK ((struct LedRgb){255, 192, 203}) +#define LED_RGB_COLOR_TEAL ((struct LedRgb){0, 128, 128}) +#define LED_RGB_COLOR_BLACK ((struct LedRgb){0, 0, 0}) + +#define LED_COLOR_RGB_CUSTOM(r, g, b) ((struct LedRgb){(r), (g), (b)}) + +struct LedStripApi { + error_t (*set_single_led_color)(struct Device* device, size_t index, const struct LedRgb color); + error_t (*set_single_led_color_and_brightness)(struct Device* device, size_t index, const struct LedRgb color, uint8_t brightness); + + error_t (*set_led_range_color)(struct Device* device, size_t first, size_t count, const struct LedRgb* color); + error_t (*set_led_range_color_and_brightness)(struct Device* device, size_t first, size_t count, const struct LedRgb* color, uint8_t brightness); + + error_t (*fill_led_range_color)(struct Device* device, size_t first, size_t count, const struct LedRgb color); + error_t (*fill_led_range_color_and_brightness)(struct Device* device, size_t first, size_t count, const struct LedRgb color, uint8_t brightness); + + error_t (*set_brightness)(struct Device* device, uint8_t brightness); + + error_t (*show)(struct Device* device); + + error_t (*clear)(struct Device* device); + + error_t (*get_length)(struct Device* device, uint16_t* out_length); +}; + +extern const struct DeviceType LED_STRIP_TYPE; + +/** + * @brief Sets the color of a single LED using the global brightness. + */ +error_t led_strip_set_single_led_color(struct Device* device, size_t index, const struct LedRgb color); + +/** + * @brief Sets the color of a single LED with an explicit brightness value. + */ +error_t led_strip_set_single_led_color_and_brightness(struct Device* device, size_t index, const struct LedRgb color, uint8_t brightness); + +/** + * @brief Sets colors for a range of LEDs using the global brightness. + */ +error_t led_strip_set_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb* color); + +/** + * @brief Sets colors for a range of LEDs with an explicit brightness value. + */ +error_t led_strip_set_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb* color, uint8_t brightness); + +/** + * @brief Fills a range of LEDs with a single color using the global brightness. + */ +error_t led_strip_fill_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb color); + +/** + * @brief Fills a range of LEDs with a single color and an explicit brightness value. + */ +error_t led_strip_fill_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb color, uint8_t brightness); + +/** + * @brief Set global brightness for the LED strip. + */ +error_t led_strip_set_brightness(struct Device* device, uint8_t brightness); + +/** + * @brief Refreshes the strip to update the displayed colors. + */ +error_t led_strip_show(struct Device* device); + +/** + * @brief Clears the strip to black. + */ +error_t led_strip_clear(struct Device* device); + +/** + * @brief Get the LED strip length. + */ +error_t led_strip_get_length(struct Device* device, uint16_t* out_length); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/TactilityKernel/source/drivers/led_strip.cpp b/TactilityKernel/source/drivers/led_strip.cpp new file mode 100644 index 000000000..f9f450ba3 --- /dev/null +++ b/TactilityKernel/source/drivers/led_strip.cpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include + +#define TAG "LedStrip" + +#define LED_STRIP_DRIVER_API(driver) ((struct LedStripApi*)driver->api) + +extern "C" { + +error_t led_strip_set_single_led_color(struct Device* device, size_t index, const struct LedRgb color) { + const auto* driver = device_get_driver(device); + return LED_STRIP_DRIVER_API(driver)->set_single_led_color(device, index, color); +} + +error_t led_strip_set_single_led_color_and_brightness(struct Device* device, size_t index, const struct LedRgb color, uint8_t brightness) { + const auto* driver = device_get_driver(device); + return LED_STRIP_DRIVER_API(driver)->set_single_led_color_and_brightness(device, index, color, brightness); +} + +error_t led_strip_set_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb* color) { + const auto* driver = device_get_driver(device); + return LED_STRIP_DRIVER_API(driver)->set_led_range_color(device, first, count, color); +} + +error_t led_strip_set_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb* color, uint8_t brightness) { + const auto* driver = device_get_driver(device); + return LED_STRIP_DRIVER_API(driver)->set_led_range_color_and_brightness(device, first, count, color, brightness); +} + +error_t led_strip_fill_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb color) { + const auto* driver = device_get_driver(device); + return LED_STRIP_DRIVER_API(driver)->fill_led_range_color(device, first, count, color); +} + +error_t led_strip_fill_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb color, uint8_t brightness) { + const auto* driver = device_get_driver(device); + return LED_STRIP_DRIVER_API(driver)->fill_led_range_color_and_brightness(device, first, count, color, brightness); +} + +error_t led_strip_set_brightness(struct Device* device, uint8_t brightness) { + const auto* driver = device_get_driver(device); + return LED_STRIP_DRIVER_API(driver)->set_brightness(device, brightness); +} + +error_t led_strip_show(struct Device* device) { + const auto* driver = device_get_driver(device); + return LED_STRIP_DRIVER_API(driver)->show(device); +} + +error_t led_strip_clear(struct Device* device) { + const auto* driver = device_get_driver(device); + return LED_STRIP_DRIVER_API(driver)->clear(device); +} + +error_t led_strip_get_length(struct Device* device, uint16_t* out_length) { + const auto* driver = device_get_driver(device); + return LED_STRIP_DRIVER_API(driver)->get_length(device, out_length); +} + +const struct DeviceType LED_STRIP_TYPE { + .name = "led_strip" +}; + +} diff --git a/TactilityKernel/source/symbols.c b/TactilityKernel/source/symbols.c index e769c4b26..3ea20c0a4 100644 --- a/TactilityKernel/source/symbols.c +++ b/TactilityKernel/source/symbols.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -257,6 +258,18 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { // drivers/keyboard DEFINE_MODULE_SYMBOL(keyboard_read_key), DEFINE_MODULE_SYMBOL(KEYBOARD_TYPE), + // drivers/led_strip + DEFINE_MODULE_SYMBOL(led_strip_set_single_led_color), + DEFINE_MODULE_SYMBOL(led_strip_set_single_led_color_and_brightness), + DEFINE_MODULE_SYMBOL(led_strip_set_led_range_color), + DEFINE_MODULE_SYMBOL(led_strip_set_led_range_color_and_brightness), + DEFINE_MODULE_SYMBOL(led_strip_fill_led_range_color), + DEFINE_MODULE_SYMBOL(led_strip_fill_led_range_color_and_brightness), + DEFINE_MODULE_SYMBOL(led_strip_set_brightness), + DEFINE_MODULE_SYMBOL(led_strip_show), + DEFINE_MODULE_SYMBOL(led_strip_clear), + DEFINE_MODULE_SYMBOL(led_strip_get_length), + DEFINE_MODULE_SYMBOL(LED_STRIP_TYPE), // drivers/paths DEFINE_MODULE_SYMBOL(paths_get_data_path), DEFINE_MODULE_SYMBOL(paths_get_temp_path),