diff --git a/components/led_strip/CMakeLists.txt b/components/led_strip/CMakeLists.txt new file mode 100644 index 00000000..c7e9f3f0 --- /dev/null +++ b/components/led_strip/CMakeLists.txt @@ -0,0 +1,11 @@ + +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES platform_config tools esp_common + PRIV_REQUIRES services freertos driver +) + +set_source_files_properties(led_strip.c + PROPERTIES COMPILE_FLAGS + -Wno-format-overflow +) diff --git a/components/led_strip/LICENSE b/components/led_strip/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/components/led_strip/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + 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/components/led_strip/led_strip.c b/components/led_strip/led_strip.c new file mode 100644 index 00000000..92b1ba3d --- /dev/null +++ b/components/led_strip/led_strip.c @@ -0,0 +1,407 @@ +/* ---------------------------------------------------------------------------- + File: led_strip.c + Author(s): Lucas Bruder + Date Created: 11/23/2016 + Last modified: 11/26/2016 + + Updated: C. Rohs - The update thread now + only runs when signalled. The double buffer code was modified to copy on show + instead of the ping pong buffer that destroyed the buffers contents. + + The current code is not thread safe, but is more performant, and the thread + safety does not matter the was it is currently used. + + Description: LED Library for driving various led strips on ESP32. + + This library uses double buffering to display the LEDs. + ------------------------------------------------------------------------- */ + +#include "led_strip.h" +#include "freertos/task.h" + +#include + +#define LED_STRIP_TASK_SIZE (1024) +#define LED_STRIP_TASK_PRIORITY (configMAX_PRIORITIES - 1) + +#define LED_STRIP_REFRESH_PERIOD_MS (30U) // TODO: add as parameter to led_strip_init + +#define LED_STRIP_NUM_RMT_ITEMS_PER_LED (24U) // Assumes 24 bit color for each led + +// RMT Clock source is @ 80 MHz. Dividing it by 8 gives us 10 MHz frequency, or 100ns period. +#define LED_STRIP_RMT_CLK_DIV (8) + +/**************************** + WS2812 Timing + ****************************/ +#define LED_STRIP_RMT_TICKS_BIT_1_HIGH_WS2812 9 // 900ns (900ns +/- 150ns per datasheet) +#define LED_STRIP_RMT_TICKS_BIT_1_LOW_WS2812 3 // 300ns (350ns +/- 150ns per datasheet) +#define LED_STRIP_RMT_TICKS_BIT_0_HIGH_WS2812 3 // 300ns (350ns +/- 150ns per datasheet) +#define LED_STRIP_RMT_TICKS_BIT_0_LOW_WS2812 9 // 900ns (900ns +/- 150ns per datasheet) + +/**************************** + SK6812 Timing + ****************************/ +#define LED_STRIP_RMT_TICKS_BIT_1_HIGH_SK6812 6 +#define LED_STRIP_RMT_TICKS_BIT_1_LOW_SK6812 6 +#define LED_STRIP_RMT_TICKS_BIT_0_HIGH_SK6812 3 +#define LED_STRIP_RMT_TICKS_BIT_0_LOW_SK6812 9 + +/**************************** + APA106 Timing + ****************************/ +#define LED_STRIP_RMT_TICKS_BIT_1_HIGH_APA106 14 // 1.36us +/- 150ns per datasheet +#define LED_STRIP_RMT_TICKS_BIT_1_LOW_APA106 3 // 350ns +/- 150ns per datasheet +#define LED_STRIP_RMT_TICKS_BIT_0_HIGH_APA106 3 // 350ns +/- 150ns per datasheet +#define LED_STRIP_RMT_TICKS_BIT_0_LOW_APA106 14 // 1.36us +/- 150ns per datasheet + +// Function pointer for generating waveforms based on different LED drivers +typedef void (*led_fill_rmt_items_fn)(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length); + +static inline void led_strip_fill_item_level(rmt_item32_t* item, int high_ticks, int low_ticks) +{ + item->level0 = 1; + item->duration0 = high_ticks; + item->level1 = 0; + item->duration1 = low_ticks; +} + +static inline void led_strip_rmt_bit_1_sk6812(rmt_item32_t* item) +{ + led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_1_HIGH_SK6812, LED_STRIP_RMT_TICKS_BIT_1_LOW_SK6812); +} + +static inline void led_strip_rmt_bit_0_sk6812(rmt_item32_t* item) +{ + led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_0_HIGH_SK6812, LED_STRIP_RMT_TICKS_BIT_0_LOW_SK6812); +} + +static void led_strip_fill_rmt_items_sk6812(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length) +{ + uint32_t rmt_items_index = 0; + for (uint32_t led_index = 0; led_index < led_strip_length; led_index++) { + struct led_color_t led_color = led_strip_buf[led_index]; + + for (uint8_t bit = 8; bit != 0; bit--) { + uint8_t bit_set = (led_color.green >> (bit - 1)) & 1; + if(bit_set) { + led_strip_rmt_bit_1_sk6812(&(rmt_items[rmt_items_index])); + } else { + led_strip_rmt_bit_0_sk6812(&(rmt_items[rmt_items_index])); + } + rmt_items_index++; + } + for (uint8_t bit = 8; bit != 0; bit--) { + uint8_t bit_set = (led_color.red >> (bit - 1)) & 1; + if(bit_set) { + led_strip_rmt_bit_1_sk6812(&(rmt_items[rmt_items_index])); + } else { + led_strip_rmt_bit_0_sk6812(&(rmt_items[rmt_items_index])); + } + rmt_items_index++; + } + for (uint8_t bit = 8; bit != 0; bit--) { + uint8_t bit_set = (led_color.blue >> (bit - 1)) & 1; + if(bit_set) { + led_strip_rmt_bit_1_sk6812(&(rmt_items[rmt_items_index])); + } else { + led_strip_rmt_bit_0_sk6812(&(rmt_items[rmt_items_index])); + } + rmt_items_index++; + } + } +} + +static inline void led_strip_rmt_bit_1_ws2812(rmt_item32_t* item) +{ + led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_1_HIGH_WS2812, LED_STRIP_RMT_TICKS_BIT_1_LOW_WS2812); +} + +static inline void led_strip_rmt_bit_0_ws2812(rmt_item32_t* item) +{ + led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_0_HIGH_WS2812, LED_STRIP_RMT_TICKS_BIT_0_LOW_WS2812); +} + +static void led_strip_fill_rmt_items_ws2812(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length) +{ + uint32_t rmt_items_index = 0; + for (uint32_t led_index = 0; led_index < led_strip_length; led_index++) { + struct led_color_t led_color = led_strip_buf[led_index]; + + for (uint8_t bit = 8; bit != 0; bit--) { + uint8_t bit_set = (led_color.green >> (bit - 1)) & 1; + if(bit_set) { + led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index])); + } else { + led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index])); + } + rmt_items_index++; + } + for (uint8_t bit = 8; bit != 0; bit--) { + uint8_t bit_set = (led_color.red >> (bit - 1)) & 1; + if(bit_set) { + led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index])); + } else { + led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index])); + } + rmt_items_index++; + } + for (uint8_t bit = 8; bit != 0; bit--) { + uint8_t bit_set = (led_color.blue >> (bit - 1)) & 1; + if(bit_set) { + led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index])); + } else { + led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index])); + } + rmt_items_index++; + } + } +} + +static inline void led_strip_rmt_bit_1_apa106(rmt_item32_t* item) +{ + led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_1_HIGH_APA106, LED_STRIP_RMT_TICKS_BIT_1_LOW_APA106); +} + +static inline void led_strip_rmt_bit_0_apa106(rmt_item32_t* item) +{ + led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_0_HIGH_APA106, LED_STRIP_RMT_TICKS_BIT_0_LOW_APA106); +} + +static void led_strip_fill_rmt_items_apa106(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length) +{ + uint32_t rmt_items_index = 0; + for (uint32_t led_index = 0; led_index < led_strip_length; led_index++) { + struct led_color_t led_color = led_strip_buf[led_index]; + + for (uint8_t bit = 8; bit != 0; bit--) { + uint8_t bit_set = (led_color.red >> (bit - 1)) & 1; + if(bit_set) { + led_strip_rmt_bit_1_apa106(&(rmt_items[rmt_items_index])); + } else { + led_strip_rmt_bit_0_apa106(&(rmt_items[rmt_items_index])); + } + rmt_items_index++; + } + for (uint8_t bit = 8; bit != 0; bit--) { + uint8_t bit_set = (led_color.green >> (bit - 1)) & 1; + if(bit_set) { + led_strip_rmt_bit_1_apa106(&(rmt_items[rmt_items_index])); + } else { + led_strip_rmt_bit_0_apa106(&(rmt_items[rmt_items_index])); + } + rmt_items_index++; + } + for (uint8_t bit = 8; bit != 0; bit--) { + uint8_t bit_set = (led_color.blue >> (bit - 1)) & 1; + if(bit_set) { + led_strip_rmt_bit_1_apa106(&(rmt_items[rmt_items_index])); + } else { + led_strip_rmt_bit_0_apa106(&(rmt_items[rmt_items_index])); + } + rmt_items_index++; + } + } +} + +static void led_strip_task(void *arg) +{ + struct led_strip_t *led_strip = (struct led_strip_t *)arg; + led_fill_rmt_items_fn led_make_waveform = NULL; + + size_t num_items_malloc = (LED_STRIP_NUM_RMT_ITEMS_PER_LED * led_strip->led_strip_length); + rmt_item32_t *rmt_items = (rmt_item32_t*) malloc(sizeof(rmt_item32_t) * num_items_malloc); + if (!rmt_items) { + vTaskDelete(NULL); + } + + switch (led_strip->rgb_led_type) { + case RGB_LED_TYPE_WS2812: + led_make_waveform = led_strip_fill_rmt_items_ws2812; + break; + + case RGB_LED_TYPE_SK6812: + led_make_waveform = led_strip_fill_rmt_items_sk6812; + break; + + case RGB_LED_TYPE_APA106: + led_make_waveform = led_strip_fill_rmt_items_apa106; + break; + + default: + // Will avoid keeping it point to NULL + led_make_waveform = led_strip_fill_rmt_items_ws2812; + break; + }; + + for(;;) { + rmt_wait_tx_done(led_strip->rmt_channel, portMAX_DELAY); + vTaskDelay(LED_STRIP_REFRESH_PERIOD_MS / portTICK_PERIOD_MS); + + xSemaphoreTake(led_strip->access_semaphore, portMAX_DELAY); + + led_make_waveform(led_strip->led_strip_working, + rmt_items, + led_strip->led_strip_length); + rmt_write_items(led_strip->rmt_channel, + rmt_items, + num_items_malloc, + false); + } + + if (rmt_items) { + free(rmt_items); + } + vTaskDelete(NULL); +} + +static bool led_strip_init_rmt(struct led_strip_t *led_strip) +{ + rmt_config_t rmt_cfg = { + .rmt_mode = RMT_MODE_TX, + .channel = led_strip->rmt_channel, + .clk_div = LED_STRIP_RMT_CLK_DIV, + .gpio_num = led_strip->gpio, + .mem_block_num = 1, + .tx_config = { + .loop_en = false, + .carrier_freq_hz = 100, // Not used, but has to be set to avoid divide by 0 err + .carrier_duty_percent = 50, + .carrier_level = RMT_CARRIER_LEVEL_LOW, + .carrier_en = false, + .idle_level = RMT_IDLE_LEVEL_LOW, + .idle_output_en = true, + } + }; + + esp_err_t cfg_ok = rmt_config(&rmt_cfg); + if (cfg_ok != ESP_OK) { + return false; + } + esp_err_t install_ok = rmt_driver_install(rmt_cfg.channel, 0, 0); + if (install_ok != ESP_OK) { + return false; + } + + return true; +} + +bool led_strip_init(struct led_strip_t *led_strip) +{ + TaskHandle_t led_strip_task_handle; + + if ((led_strip == NULL) || + (led_strip->rmt_channel >= RMT_CHANNEL_MAX) || + (led_strip->gpio > GPIO_NUM_33) || + (led_strip->led_strip_working == NULL) || + (led_strip->led_strip_showing == NULL) || + (led_strip->led_strip_length == 0) || + (led_strip->access_semaphore == NULL)) { + return false; + } + + if(led_strip->led_strip_working == led_strip->led_strip_showing) { + return false; + } + + memset(led_strip->led_strip_working, 0, sizeof(struct led_color_t) * led_strip->led_strip_length); + memset(led_strip->led_strip_showing, 0, sizeof(struct led_color_t) * led_strip->led_strip_length); + + bool init_rmt = led_strip_init_rmt(led_strip); + if (!init_rmt) { + return false; + } + + xSemaphoreGive(led_strip->access_semaphore); + BaseType_t task_created = xTaskCreate(led_strip_task, + "led_strip_task", + LED_STRIP_TASK_SIZE, + led_strip, + LED_STRIP_TASK_PRIORITY, + &led_strip_task_handle); + + if (!task_created) { + return false; + } + + return true; +} + +bool led_strip_set_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color) +{ + bool set_led_success = true; + + if ((!led_strip) || (!color) || (pixel_num > led_strip->led_strip_length)) { + return false; + } + + led_strip->led_strip_working[pixel_num] = *color; + + return set_led_success; +} + +bool led_strip_set_pixel_rgb(struct led_strip_t *led_strip, uint32_t pixel_num, uint8_t red, uint8_t green, uint8_t blue) +{ + bool set_led_success = true; + + if ((!led_strip) || (pixel_num > led_strip->led_strip_length)) { + return false; + } + + led_strip->led_strip_working[pixel_num].red = red; + led_strip->led_strip_working[pixel_num].green = green; + led_strip->led_strip_working[pixel_num].blue = blue; + + return set_led_success; +} + +bool led_strip_get_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color) +{ + bool get_success = true; + + if ((!led_strip) || + (pixel_num > led_strip->led_strip_length) || + (!color)) { + color = NULL; + return false; + } + + *color = led_strip->led_strip_working[pixel_num]; + + return get_success; +} + +/** + * Updates the led buffer to be shown + */ +bool led_strip_show(struct led_strip_t *led_strip) +{ + bool success = true; + + if (!led_strip) { + return false; + } + /* copy the current buffer for display */ + memcpy(led_strip->led_strip_showing,led_strip->led_strip_working, sizeof(struct led_color_t) * led_strip->led_strip_length); + + xSemaphoreGive(led_strip->access_semaphore); + + return success; +} + +/** + * Clears the LED strip + */ +bool led_strip_clear(struct led_strip_t *led_strip) +{ + bool success = true; + if (!led_strip) { + return false; + } + + memset(led_strip->led_strip_working, + 0, + sizeof(struct led_color_t) * led_strip->led_strip_length); + + return success; +} diff --git a/components/led_strip/led_strip.h b/components/led_strip/led_strip.h new file mode 100644 index 00000000..0809a27f --- /dev/null +++ b/components/led_strip/led_strip.h @@ -0,0 +1,96 @@ +/* --------------------------------------------------------------------------- + File: led_strip.h + Author(s): Lucas Bruder + Date Created: 11/23/2016 + Last modified: 11/26/2016 + + Description: + This library can drive led strips through the RMT module on the ESP32. + ------------------------------------------------------------------------ */ + +#ifndef LED_STRIP_H +#define LED_STRIP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "freertos/FreeRTOS.h" + +#include + +enum rgb_led_type_t { + RGB_LED_TYPE_WS2812 = 0, + RGB_LED_TYPE_SK6812 = 1, + RGB_LED_TYPE_APA106 = 2, + + RGB_LED_TYPE_MAX, +}; + +/** + * RGB LED colors + */ +struct led_color_t { + uint8_t red; + uint8_t green; + uint8_t blue; +}; + +struct led_strip_t { + const enum rgb_led_type_t rgb_led_type; + uint32_t led_strip_length; + + // RMT peripheral settings + rmt_channel_t rmt_channel; + + /* + * Interrupt table is located in soc.h + * As of 11/27/16, reccomended interrupts are: + * 9, 12, 13, 17, 18, 19, 20, 21 or 23 + * Ensure that the same interrupt number isn't used twice + * across all libraries + */ + int rmt_interrupt_num; + + gpio_num_t gpio; // Must be less than GPIO_NUM_33 + + struct led_color_t *led_strip_working; + struct led_color_t *led_strip_showing; + + SemaphoreHandle_t access_semaphore; +}; + +bool led_strip_init(struct led_strip_t *led_strip); + +/** + * Sets the pixel at pixel_num to color. + */ +bool led_strip_set_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color); +bool led_strip_set_pixel_rgb(struct led_strip_t *led_strip, uint32_t pixel_num, uint8_t red, uint8_t green, uint8_t blue); +/** + * Get the pixel color at pixel_num for the led strip that is currently being shown! + * NOTE: If you call set_pixel_color then get_pixel_color for the same pixel_num, you will not + * get back the same pixel value. This gets you the color of the pixel currently being shown, not the one + * being updated + * + * If there is an invalid argument, color will point to NULL and this function will return false. + */ +bool led_strip_get_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color); + +/** + * Updates the led buffer to be shown using double buffering. + */ +bool led_strip_show(struct led_strip_t *led_strip); + +/** + * Clears the LED strip. + */ +bool led_strip_clear(struct led_strip_t *led_strip); + +#ifdef __cplusplus +} +#endif + +#endif // LED_STRIP_H diff --git a/components/led_strip/led_vu.c b/components/led_strip/led_vu.c new file mode 100644 index 00000000..49ba26a1 --- /dev/null +++ b/components/led_strip/led_vu.c @@ -0,0 +1,363 @@ +/* + * Control of LED strip within squeezelite-esp32 + * + * (c) Wizmo 2021 + * + * Loosely based on code by + * Chuck Rohs 2020, chuck@zethus.ca + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + * + * ToDo: + * Driver does support other led device. Maybe look at supporting in future. + * The VU refresh rate has been decreaced (100->75) to optimize animation of spin dial. Could make + * configurable like text scrolling (or use the same value) + * Look at reserving a status led within the effects. (may require nvs setting for center or end position) + * Artwork function, but not released as very buggy and not really practical + */ + +#include +#include +#include "esp_log.h" + +#include "led_strip.h" +#include "platform_config.h" +#include "led_vu.h" + +static const char *TAG = "led_vu"; + +#define LED_VU_STACK_SIZE (3*1024) +#define LED_VU_RMT_INTR_NUM 20U + +#define LED_VU_PEAK_HOLD 6U + +#define LED_VU_DEFAULT_GPIO 22 +#define LED_VU_DEFAULT_LENGTH 19 +#define LED_VU_MAX_LENGTH 255 + +#define max(a,b) (((a) > (b)) ? (a) : (b)) + +struct led_strip_t* led_display = NULL; +static struct led_strip_t led_strip_config = { + .rgb_led_type = RGB_LED_TYPE_WS2812, + .rmt_channel = RMT_CHANNEL_1, + .rmt_interrupt_num = LED_VU_RMT_INTR_NUM, + .gpio = GPIO_NUM_22, +}; + +static struct { + int gpio; + int length; + int vu_length; + int vu_start_l; + int vu_start_r; + int vu_odd; +} strip; + +static int led_addr(int pos ) { + if (pos < 0) return pos + strip.length; + if (pos >= strip.length) return pos - strip.length; + return pos; +} + +/**************************************************************************************** + * Initialize the led vu strip if configured. + * + */ +void led_vu_init() +{ + char* p; + char* config = config_alloc_get_str("led_vu_config", NULL, "N/A"); + + // Initialize led VU strip + char* drivername = strcasestr(config, "WS2812"); + + if ((p = strcasestr(config, "length")) != NULL) { + strip.length = atoi(strchr(p, '=') + 1); + } // else 0 + if ((p = strcasestr(config, "gpio")) != NULL) { + strip.gpio = atoi(strchr(p, '=') + 1); + } else { + strip.gpio = LED_VU_DEFAULT_GPIO; + } + // check for valid configuration + if (!drivername || !strip.gpio) { + ESP_LOGI(TAG, "led_vu configuration invalid"); + goto done; + } + + if (strip.length > LED_VU_MAX_LENGTH) strip.length = LED_VU_MAX_LENGTH; + // initialize vu settings + //strip.vu_length = (strip.length % 2) ? strip.length / 2 : (strip.length - 1) / 2; + strip.vu_length = (strip.length - 1) / 2; + strip.vu_start_l = strip.vu_length; + strip.vu_start_r = strip.vu_start_l + 1; + strip.vu_odd = strip.length - 1; + + // create driver configuration + led_strip_config.access_semaphore = xSemaphoreCreateBinary(); + led_strip_config.led_strip_length = strip.length; + led_strip_config.led_strip_working = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT); + led_strip_config.led_strip_showing = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT); + led_strip_config.gpio = strip.gpio; + + // initialize driver + bool led_init_ok = led_strip_init(&led_strip_config); + if (led_init_ok) { + led_display = &led_strip_config; + ESP_LOGI(TAG, "led_vu using gpio:%d length:%d", strip.gpio, strip.length); + } else { + ESP_LOGE(TAG, "led_vu init failed"); + goto done; + } + + // reserver max memory for remote management systems + rmt_set_mem_block_num(RMT_CHANNEL_1, 7); + + led_vu_clear(led_display); + + done: + free(config); + return; + } + +inline bool inRange(double x, double y, double z) { + return (x > y && x < z); +} + +/**************************************************************************************** + * Returns the led strip length + */ +uint16_t led_vu_string_length() { + if (!led_display) return 0; + return (uint16_t)strip.length; +} + +/**************************************************************************************** + * Turns all LEDs off (Black) + */ +void led_vu_clear() { + if (!led_display) return; + led_strip_clear(led_display); + + led_strip_show(led_display); +} + +/**************************************************************************************** + * Sets all LEDs to one color + * r = red (0-255), g = green (0-255), b - blue (0-255) + * note - all colors are adjusted for brightness + */ +void led_vu_color_all(uint8_t r, uint8_t g, uint8_t b) { + if (!led_display) return; + + struct led_color_t color_on = {.red = r, .green = g, .blue = b}; + + for (int i = 0 ; i < strip.length ; i ++){ + led_strip_set_pixel_color(led_display, i, &color_on); + } + + led_strip_show(led_display); +} + +/**************************************************************************************** + * Sets LEDs based on a data packet consiting of rgb data + * offset - starting LED, + * length - number of leds (3x rgb bytes) + * data - array of rgb values in multiples of 3 bytes + */ +void led_vu_data(uint8_t* data, uint16_t offset, uint16_t length) { + if (!led_display) return; + + uint8_t* p = (uint8_t*) data; + for (int i = 0; i < length; i++) { + led_strip_set_pixel_rgb(led_display, i+offset, *p, *(p+1), *(p+2)); + p+=3; + } + + led_strip_show(led_display); +} + +/**************************************************************************************** + * Progress bar display + * data - array of gain values(0-100) + * offset - starting position + * length - size of array + */ +void led_vu_spectrum(uint8_t* data, int bright, int length, int style) { + if (!led_display) return; + uint8_t gain,r,g,b; + int width = strip.length / length; + int pos = 0; + uint8_t* p = (uint8_t*) data; + for (int i=0; i LED_VU_MAX-step) ? LED_VU_MAX : g + step; + r = (r < step) ? 0 : r - step; + if (r == 0) b = step; + } else if (r == 0) { + b = (b > LED_VU_MAX-step) ? LED_VU_MAX : b + step; + g = (g < step) ? 0 : g- step; + if (g == 0) r = step; + } else { + r = (r > LED_VU_MAX-step) ? LED_VU_MAX : r + step; + b = (b < step) ? 0 : b - step; + if (r == 0) b = step; + } + + uint8_t rp = r * gain / LED_VU_MAX; + uint8_t gp = g * gain / LED_VU_MAX; + uint8_t bp = b * gain / LED_VU_MAX; + + // set led color_ + led_strip_set_pixel_rgb(led_display, led_pos, rp, gp, bp); + if (comet) { + led_strip_set_pixel_rgb(led_display, led_addr(led_pos-1), rp/2, gp/2, bp/2); + led_strip_set_pixel_rgb(led_display, led_addr(led_pos-2), rp/4, gp/4, bp/4); + led_strip_set_pixel_rgb(led_display, led_addr(led_pos-3), rp/8, gp/8, bp/8); + led_strip_set_pixel_rgb(led_display, led_addr(led_pos-4), 0, 0, 0); + } + + // next led + led_pos = led_addr(++led_pos); + + led_strip_show(led_display); +} + +/**************************************************************************************** + * VU meter display + * vu_l - left response (0-100), vu_r - right response (0-100) + * comet - alternate display mode + */ +void led_vu_display(int vu_l, int vu_r, int bright, bool comet) { + static int peak_l = 0; + static int peak_r = 0; + static int decay_l = 0; + static int decay_r = 0; + if (!led_display) return; + + + + // scale vu samples to length + vu_l = vu_l * strip.vu_length / bright; + vu_r = vu_r * strip.vu_length / bright; + + // calculate hold peaks + if (peak_l > vu_l) { + if (decay_l-- < 0) { + decay_l = LED_VU_PEAK_HOLD; + peak_l--; + } + } else { + peak_l = vu_l; + decay_l = LED_VU_PEAK_HOLD; + } + if (peak_r > vu_r) { + if (decay_r-- < 0) { + decay_r = LED_VU_PEAK_HOLD; + peak_r--; + } + } else { + peak_r = vu_r; + decay_r = LED_VU_PEAK_HOLD; + } + + // turn off all leds + led_strip_clear(led_display); + + // set the led bar values + uint8_t step = bright / (strip.vu_length-1); + if (step < 1) step = 1; // dor low brightness or larger strips + uint8_t g = bright * 2 / 3; // more red at top + uint8_t r = 0; + int shift = 0; + for (int i = 0; i < strip.vu_length; i++) { + // set left + if (i == peak_l) { + led_strip_set_pixel_rgb(led_display, strip.vu_start_l - i, r, g, bright); + } else if (i <= vu_l) { + shift = vu_l - i; + if (comet) + led_strip_set_pixel_rgb(led_display, strip.vu_start_l - i, r>>shift, g>>shift, 0); + else + led_strip_set_pixel_rgb(led_display, strip.vu_start_l - i, r, g, 0); + } + // set right + if (i == peak_r) { + led_strip_set_pixel_rgb(led_display, strip.vu_start_r + i, r, g, bright); + } else if (i <= vu_r) { + shift = vu_r - i; + if (comet) + led_strip_set_pixel_rgb(led_display, strip.vu_start_r + i, r>>shift, g>>shift, 0); + else + led_strip_set_pixel_rgb(led_display, strip.vu_start_r + i, r, g, 0); + } + // adjust colors (with limit checks) + r = (r > bright-step) ? bright : r + step; + g = (g < step) ? 0 : g - step; + } + + led_strip_show(led_display); +} + diff --git a/components/led_strip/led_vu.h b/components/led_strip/led_vu.h new file mode 100644 index 00000000..f6c26f12 --- /dev/null +++ b/components/led_strip/led_vu.h @@ -0,0 +1,31 @@ +/* + * Control of LED strip within squeezelite-esp32 + * + * (c) Wizmo 2021 + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + * + */ + +#include + +#define LED_VU_MAX 255U +#define LED_VU_BRIGHT 20U + +#define led_vu_color_red(B) led_vu_color_all(B, 0, 0) +#define led_vu_color_green(B) led_vu_color_all(0, B, 0) +#define led_vu_color_blue(B) led_vu_color_all(0, 0, B) +#define led_vu_color_yellow(B) led_vu_color_all(B/2, B/2, 0) + +extern struct led_strip_t* led_display; + +uint16_t led_vu_string_length(); +void led_vu_progress_bar(int pct, int bright); +void led_vu_display(int vu_l, int vu_r, int bright, bool comet); +void led_vu_spin_dial(int gain, int rate, bool comet); +void led_vu_spectrum(uint8_t* data, int bright, int length, int style); +void led_vu_color_all(uint8_t r, uint8_t g, uint8_t b); +void led_vu_data(uint8_t* data, uint16_t offset, uint16_t length); +void led_vu_clear(); + diff --git a/components/platform_console/cmd_config.c b/components/platform_console/cmd_config.c index f25bc919..19d78e32 100644 --- a/components/platform_console/cmd_config.c +++ b/components/platform_console/cmd_config.c @@ -29,6 +29,7 @@ const char * desc_spdif= "SPDIF Options"; const char * desc_audio= "General Audio Options"; const char * desc_bt_source= "Bluetooth Audio Output Options"; const char * desc_rotary= "Rotary Control"; +const char * desc_ledvu= "Led Strip Options"; extern const struct adac_s *dac_set[]; @@ -108,6 +109,15 @@ static struct { struct arg_end * end; } rotary_args; //config_rotary_get + +static struct { + struct arg_str * type; + struct arg_int * length; + struct arg_int * gpio; + struct arg_lit * clear; + struct arg_end * end; +} ledvu_args; + static struct{ struct arg_str *sink_name; struct arg_str *pin_code; @@ -635,6 +645,54 @@ static int do_cspot_config(int argc, char **argv){ FREE_AND_NULL(buf); return nerrors; } + + +static int do_ledvu_cmd(int argc, char **argv){ + ledvu_struct_t ledvu={ .type = "WS2812", .gpio = -1, .length = 0}; + esp_err_t err=ESP_OK; + int nerrors = arg_parse(argc, argv,(void **)&ledvu_args); + if (ledvu_args.clear->count) { + cmd_send_messaging(argv[0],MESSAGING_WARNING,"ledvu config cleared\n"); + config_set_value(NVS_TYPE_STR, "led_vu_config", ""); + return 0; + } + + char *buf = NULL; + size_t buf_size = 0; + FILE *f = open_memstream(&buf, &buf_size); + if (f == NULL) { + cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n"); + return 1; + } + if(nerrors >0){ + arg_print_errors(f,ledvu_args.end,desc_ledvu); + return 1; + } + + nerrors+=is_output_gpio(ledvu_args.gpio, f, &ledvu.gpio, true); + + if(ledvu_args.length->count==0 || ledvu_args.length->ival[0]<1 || ledvu_args.length->ival[0]>255){ + fprintf(f,"error: strip length must be greater than 0 and no more than 255\n"); + nerrors++; + } + else { + ledvu.length = ledvu_args.length->count>0?ledvu_args.length->ival[0]:0; + } + + if(!nerrors ){ + fprintf(f,"Storing ledvu parameters.\n"); + nerrors+=(config_ledvu_set(&ledvu )!=ESP_OK); + } + if(!nerrors ){ + fprintf(f,"Done.\n"); + } + fflush (f); + cmd_send_messaging(argv[0],nerrors>0?MESSAGING_ERROR:MESSAGING_INFO,"%s", buf); + fclose(f); + FREE_AND_NULL(buf); + return (nerrors==0 && err==ESP_OK)?0:1; +} + static int do_i2s_cmd(int argc, char **argv) { i2s_platform_config_t i2s_dac_pin = { @@ -842,6 +900,24 @@ cJSON * rotary_cb(){ } return values; } + +cJSON * ledvu_cb(){ + cJSON * values = cJSON_CreateObject(); + const ledvu_struct_t *ledvu= config_ledvu_get(); + + if(GPIO_IS_VALID_GPIO(ledvu->gpio) && ledvu->gpio>=0 && ledvu->length > 0){ + cJSON_AddNumberToObject(values,"gpio",ledvu->gpio); + cJSON_AddNumberToObject(values,"length",ledvu->length); + } + if(strlen(ledvu->type)>0){ + cJSON_AddStringToObject(values,"type",ledvu->type); + } + else { + cJSON_AddStringToObject(values,"type","WS2812"); + } + return values; +} + cJSON * audio_cb(){ cJSON * values = cJSON_CreateObject(); char * p = config_alloc_get_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0); @@ -1252,6 +1328,24 @@ static void register_rotary_config(void){ ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); } +static void register_ledvu_config(void){ + ledvu_args.type = arg_str1(NULL,"type","|WS2812","Led type (supports one rgb strip to display built in effects and allow remote control through 'dmx' messaging)"); + ledvu_args.length = arg_int1(NULL,"length","<1..255>","Strip length (1-255 supported)"); + ledvu_args.gpio = arg_int1(NULL,"gpio","gpio","Data pin"); + ledvu_args.clear = arg_lit0(NULL, "clear", "Clear configuration"); + ledvu_args.end = arg_end(4); + + const esp_console_cmd_t cmd = { + .command = CFG_TYPE_HW("ledvu"), + .help = desc_ledvu, + .hint = NULL, + .func = &do_ledvu_cmd, + .argtable = &ledvu_args + }; + cmd_to_json_with_cb(&cmd,&ledvu_cb); + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} + static void register_audio_config(void){ audio_args.jack_behavior = arg_str0("j", "jack_behavior","Headphones|Subwoofer","On supported DAC, determines the audio jack behavior. Selecting headphones will cause the external amp to be muted on insert, while selecting Subwoofer will keep the amp active all the time."); audio_args.end = arg_end(6); @@ -1340,5 +1434,6 @@ void register_config_cmd(void){ register_spdif_config(); } register_rotary_config(); + register_ledvu_config(); } diff --git a/components/services/accessors.c b/components/services/accessors.c index 8bc4b18d..61113dc0 100644 --- a/components/services/accessors.c +++ b/components/services/accessors.c @@ -316,6 +316,30 @@ esp_err_t config_rotary_set(rotary_struct_t * config){ return err; } +/**************************************************************************************** + * + */ +esp_err_t config_ledvu_set(ledvu_struct_t * config){ + int buffer_size=512; + esp_err_t err=ESP_OK; + char * config_buffer=calloc(buffer_size,1); + char * config_buffer2=calloc(buffer_size,1); + if(config_buffer && config_buffer2) { + snprintf(config_buffer,buffer_size,"%s,length=%i,gpio=%i",config->type, config->length, config->gpio); + log_send_messaging(MESSAGING_INFO,"Updating ledvu configuration to %s",config_buffer); + err = config_set_value(NVS_TYPE_STR, "led_vu_config", config_buffer); + if(err!=ESP_OK){ + log_send_messaging(MESSAGING_ERROR,"Error: %s",esp_err_to_name(err)); + } + } + else { + err = ESP_ERR_NO_MEM; + } + FREE_AND_NULL(config_buffer); + FREE_AND_NULL(config_buffer2); + return err; +} + /**************************************************************************************** * */ @@ -722,6 +746,24 @@ const rotary_struct_t * config_rotary_get() { return &rotary; } +/**************************************************************************************** + * + */ +const ledvu_struct_t * config_ledvu_get() { + + static ledvu_struct_t ledvu={ .type = "WS2812", .gpio = -1, .length = 0}; + char *config = config_alloc_get_default(NVS_TYPE_STR, "led_vu_config", NULL, 0); + if (config && *config) { + char *p; + + // ToDo: Add code for future support of alternate led types + if ((p = strcasestr(config, "gpio")) != NULL) ledvu.gpio = atoi(strchr(p, '=') + 1); + if ((p = strcasestr(config, "length")) != NULL) ledvu.length = atoi(strchr(p, '=') + 1); + free(config); + } + return &ledvu; +} + /**************************************************************************************** * */ @@ -925,6 +967,17 @@ cJSON * get_Rotary_GPIO(cJSON * list){ return llist; } +/**************************************************************************************** + * + */ +cJSON * get_ledvu_GPIO(cJSON * list){ + cJSON * llist = list?list:cJSON_CreateArray(); + + const ledvu_struct_t *ledvu= config_ledvu_get(); + add_gpio_for_value(llist,"gpio",ledvu->gpio, "led_vu", false); + return llist; +} + /**************************************************************************************** * */ @@ -1130,6 +1183,7 @@ cJSON * get_gpio_list(bool refresh) { gpio_list=get_SPI_GPIO(gpio_list); gpio_list=get_I2C_GPIO(gpio_list); gpio_list=get_DAC_GPIO(gpio_list); + gpio_list=get_ledvu_GPIO(gpio_list); gpio_list=get_psram_gpio_list(gpio_list); gpio_list=get_eth_GPIO(gpio_list); return gpio_list; diff --git a/components/services/accessors.h b/components/services/accessors.h index d7c9c640..959af575 100644 --- a/components/services/accessors.h +++ b/components/services/accessors.h @@ -84,6 +84,12 @@ typedef struct { int timer; } rotary_struct_t; +typedef struct { + char type[16]; + int length; + int gpio; +} ledvu_struct_t; + typedef struct { bool fixed; char * name; @@ -114,4 +120,6 @@ cJSON * get_gpio_list(bool refresh); bool is_dac_config_locked(); bool are_statistics_enabled(); const rotary_struct_t * config_rotary_get(); -esp_err_t config_rotary_set(rotary_struct_t * rotary); \ No newline at end of file +esp_err_t config_rotary_set(rotary_struct_t * rotary); +const ledvu_struct_t * config_ledvu_get(); +esp_err_t config_ledvu_set(ledvu_struct_t * rotary); \ No newline at end of file diff --git a/components/squeezelite-ota/CMakeLists.txt b/components/squeezelite-ota/CMakeLists.txt index cf75eab9..e7fefbaa 100644 --- a/components/squeezelite-ota/CMakeLists.txt +++ b/components/squeezelite-ota/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register(SRC_DIRS . INCLUDE_DIRS . REQUIRES app_update esp_https_ota - PRIV_REQUIRES console tools display services platform_config spi_flash vfs console freertos platform_console + PRIV_REQUIRES console tools display led_strip services platform_config spi_flash vfs console freertos platform_console ) diff --git a/components/squeezelite-ota/squeezelite-ota.c b/components/squeezelite-ota/squeezelite-ota.c index d98f4867..488e3cf0 100644 --- a/components/squeezelite-ota/squeezelite-ota.c +++ b/components/squeezelite-ota/squeezelite-ota.c @@ -157,24 +157,26 @@ static progress_t * loc_displayer_get_progress_dft(){ } static void loc_displayer_progressbar(uint8_t pct){ static progress_t * progress_coordinates; - if(!display){ - return; - } - if(!progress_coordinates) progress_coordinates = loc_displayer_get_progress_dft(); - int filler_x=progress_coordinates->filler.x1+(int)((float)progress_coordinates->filler.width*(float)pct/(float)100); + if(display) { + if(!progress_coordinates) progress_coordinates = loc_displayer_get_progress_dft(); + int filler_x=progress_coordinates->filler.x1+(int)((float)progress_coordinates->filler.width*(float)pct/(float)100); - ESP_LOGD(TAG,"Drawing %d,%d,%d,%d",progress_coordinates->border.x1,progress_coordinates->border.y1,progress_coordinates->border.x2,progress_coordinates->border.y2); - GDS_DrawBox(display,progress_coordinates->border.x1,progress_coordinates->border.y1,progress_coordinates->border.x2,progress_coordinates->border.y2,GDS_COLOR_WHITE,false); - ESP_LOGD(TAG,"Drawing %d,%d,%d,%d",progress_coordinates->filler.x1,progress_coordinates->filler.y1,filler_x,progress_coordinates->filler.y2); - if(filler_x > progress_coordinates->filler.x1){ - GDS_DrawBox(display,progress_coordinates->filler.x1,progress_coordinates->filler.y1,filler_x,progress_coordinates->filler.y2,GDS_COLOR_WHITE,true); + ESP_LOGD(TAG,"Drawing %d,%d,%d,%d",progress_coordinates->border.x1,progress_coordinates->border.y1,progress_coordinates->border.x2,progress_coordinates->border.y2); + GDS_DrawBox(display,progress_coordinates->border.x1,progress_coordinates->border.y1,progress_coordinates->border.x2,progress_coordinates->border.y2,GDS_COLOR_WHITE,false); + ESP_LOGD(TAG,"Drawing %d,%d,%d,%d",progress_coordinates->filler.x1,progress_coordinates->filler.y1,filler_x,progress_coordinates->filler.y2); + if(filler_x > progress_coordinates->filler.x1){ + GDS_DrawBox(display,progress_coordinates->filler.x1,progress_coordinates->filler.y1,filler_x,progress_coordinates->filler.y2,GDS_COLOR_WHITE,true); + } + else { + // Clear the inner box + GDS_DrawBox(display,progress_coordinates->filler.x1,progress_coordinates->filler.y1,progress_coordinates->filler.x2,progress_coordinates->filler.y2,GDS_COLOR_BLACK,true); + } + ESP_LOGD(TAG,"Updating Display"); + GDS_Update(display); } - else { - // Clear the inner box - GDS_DrawBox(display,progress_coordinates->filler.x1,progress_coordinates->filler.y1,progress_coordinates->filler.x2,progress_coordinates->filler.y2,GDS_COLOR_BLACK,true); + if (led_display) { + led_vu_progress_bar(pct, LED_VU_BRIGHT); } - ESP_LOGD(TAG,"Updating Display"); - GDS_Update(display); } void sendMessaging(messaging_types type,const char * fmt, ...){ va_list args; @@ -452,6 +454,10 @@ void ota_task_cleanup(const char * message, ...){ va_start(args, message); sendMessaging(MESSAGING_ERROR,message, args); va_end(args); + + if (led_display) led_vu_color_red(LED_VU_BRIGHT); + } else { + if (led_display) led_vu_color_green(LED_VU_BRIGHT); } FREE_RESET(ota_status->ota_write_data); FREE_RESET(ota_status->bin_data); diff --git a/components/squeezelite/CMakeLists.txt b/components/squeezelite/CMakeLists.txt index 5c1f0375..0505c672 100644 --- a/components/squeezelite/CMakeLists.txt +++ b/components/squeezelite/CMakeLists.txt @@ -13,6 +13,7 @@ idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978 display tools audio + led_strip EMBED_FILES vu_s.data arrow.data ) diff --git a/components/squeezelite/displayer.c b/components/squeezelite/displayer.c index 22949bdb..c06ca2c6 100644 --- a/components/squeezelite/displayer.c +++ b/components/squeezelite/displayer.c @@ -16,6 +16,7 @@ #include "gds_text.h" #include "gds_draw.h" #include "gds_image.h" +#include "led_vu.h" #pragma pack(push, 1) @@ -107,13 +108,20 @@ struct visu_packet { }; }; +struct ledv_packet { + char opcode[4]; + u8_t which; + u8_t style; + u8_t bright; +}; + struct ANIC_header { char opcode[4]; u32_t length; u8_t mode; }; -struct dmxt_packet { +struct ledd_packet { char opcode[4]; u16_t x; u16_t length; @@ -206,7 +214,7 @@ static EXT_RAM_ATTR struct { static EXT_RAM_ATTR struct { int mode; - int max; + int n, style, max; u16_t config; struct bar_s bars[MAX_BARS] ; } led_visu; @@ -247,11 +255,10 @@ static void grfs_handler(u8_t *data, int len); static void grfg_handler(u8_t *data, int len); static void grfa_handler(u8_t *data, int len); static void visu_handler(u8_t *data, int len); -static void dmxt_handler(u8_t *data, int len); +static void ledv_handler(u8_t *data, int len); +static void ledd_handler(u8_t *data, int len); static void displayer_task(void* arg); -void *led_display; - /* scrolling undocumented information grfs B: screen number @@ -349,8 +356,7 @@ bool sb_displayer_init(void) { } if (led_display) { - // PLACEHOLDER to init config - led_visu.mode = VISU_VUMETER; + led_visu.config = led_vu_string_length(); } // inform LMS of our screen/led dimensions @@ -428,10 +434,11 @@ static void sendSETD(u16_t width, u16_t height, u16_t led_config) { pkt_header.id = 0xfe; // id 0xfe is width S:P:Squeezebox2 pkt_header.length = htonl(sizeof(pkt_header) + 6 - 8); - LOG_INFO("sending dimension %ux%u", width, height); + LOG_INFO("sending dimension display:%ux%u led_config:%u", width, height, led_config); width = htons(width); height = htons(height); + led_config = htons(led_config); LOCK_P; send_packet((uint8_t *) &pkt_header, sizeof(pkt_header)); @@ -481,8 +488,10 @@ static bool handler(u8_t *data, int len){ grfa_handler(data, len); } else if (!strncmp((char*) data, "visu", 4)) { visu_handler(data, len); - } else if (!strncmp((char*) data, "dmxt", 4)) { - dmxt_handler(data, len); + } else if (!strncmp((char*) data, "ledv", 4)) { + ledv_handler(data, len); + } else if (!strncmp((char*) data, "ledd", 4)) { + ledd_handler(data, len); } else { res = false; } @@ -1074,23 +1083,40 @@ static void displayer_update(void) { } // actualize led_vu - if (led_visu.mode) { - // PLACEHOLDER to handle led_display. you need potentially scaling of spectrum (X and Y) - // and scaling of levels (Y) and then call the + if (led_display && led_visu.mode) { + // scale to correct rgb brightness + if (led_visu.mode == VISU_VUMETER) vu_scale(led_visu.bars, led_visu.max, meters.levels); + else spectrum_scale(led_visu.n, led_visu.bars, led_visu.max, meters.samples); + + // run built in visualizer effects + if (led_visu.mode == VISU_VUMETER) { + led_vu_display(led_visu.bars[0].current, led_visu.bars[1].current, led_visu.max, led_visu.style); + } else if (led_visu.mode == VISU_SPECTRUM) { + uint8_t* led_data = malloc(led_visu.n); + uint8_t* p = (uint8_t*) led_data; + for (int i = 0; i < led_visu.n; i++) { + *p = led_visu.bars[i].current; + p++; + } + led_vu_spectrum(led_data, led_visu.max, led_visu.n, led_visu.style); + free(led_data); + } else if (led_visu.mode == VISU_WAVEFORM) { + led_vu_spin_dial(led_visu.bars[1].current, led_visu.bars[(led_visu.n/2)+1].current * 50 / led_visu.max , led_visu.style); + } } } /**************************************************************************************** * Calculate spectrum spread */ -static void spectrum_limits(int min, int n, int pos) { +static void spectrum_limits(struct bar_s *bars, int min, int n, int pos, float spectrum_scale) { if (n / 2) { - int step = ((DISPLAY_BW - min) * visu.spectrum_scale) / (n/2); - visu.bars[pos].limit = min + step; - for (int i = 1; i < n/2; i++) visu.bars[pos+i].limit = visu.bars[pos+i-1].limit + step; - spectrum_limits(visu.bars[pos + n/2 - 1].limit, n - n/2, pos + n/2); + int step = ((DISPLAY_BW - min) * spectrum_scale) / (n/2); + bars[pos].limit = min + step; + for (int i = 1; i < n/2; i++) bars[pos+i].limit = bars[pos+i-1].limit + step; + spectrum_limits(bars, bars[pos + n/2 - 1].limit, n - n/2, pos + n/2, spectrum_scale); } else { - visu.bars[pos].limit = DISPLAY_BW; + bars[pos].limit = DISPLAY_BW; } } @@ -1103,7 +1129,7 @@ static void visu_fit(int bars, int width, int height) { visu.n = bars ? bars : MAX_BARS; visu.max = height - 1; if (visu.spectrum_scale <= 0 || visu.spectrum_scale > 0.5) visu.spectrum_scale = 0.5; - spectrum_limits(0, visu.n, 0); + spectrum_limits(visu.bars, 0, visu.n, 0, visu.spectrum_scale); } else { visu.n = 2; visu.max = (visu.style ? VU_COUNT : height) - 1; @@ -1236,11 +1262,50 @@ static void visu_handler( u8_t *data, int len) { } /**************************************************************************************** - * Dmx style packet handler + * Led_visu packet handler + */ +static void ledv_handler( u8_t *data, int len) { + struct ledv_packet *pkt = (struct ledv_packet*) data; + + LOG_DEBUG("led_visu %u with parameters", pkt->which); + + xSemaphoreTake(displayer.mutex, portMAX_DELAY); + led_visu.mode = pkt->which; + led_visu.style = pkt->style; + led_visu.max = pkt->bright; + + led_vu_clear(); + if (led_visu.mode) { + if (led_visu.mode == VISU_SPECTRUM) { + led_visu.n = (led_visu.config < MAX_BARS) ? led_visu.config : MAX_BARS; + spectrum_limits(led_visu.bars, 0, led_visu.n, 0, 0.25); + } else if (led_visu.mode == VISU_WAVEFORM) { + led_visu.n = 6; + spectrum_limits(led_visu.bars, 0, led_visu.n, 0, 0.25); + } + + displayer.wake = 1; // wake up + + // reset bars maximum + for (int i = led_visu.n; --i >= 0;) led_visu.bars[i].max = 0; + + LOG_INFO("LED Visualizer mode %u with bars:%u max:%u style:%d", led_visu.mode, led_visu.n, led_visu.max, led_visu.style); + } else { + LOG_INFO("Stopping led visualizer"); + } + + xSemaphoreGive(displayer.mutex); + + // resume displayer task + vTaskResume(displayer.task); +} + +/**************************************************************************************** + * Led_data dmx style packet handler * ToDo: make packet match dmx protocol format */ -static void dmxt_handler( u8_t *data, int len) { - struct dmxt_packet *pkt = (struct dmxt_packet*) data; +static void ledd_handler( u8_t *data, int len) { + struct ledd_packet *pkt = (struct ledd_packet*) data; uint16_t offset = htons(pkt->x); uint16_t length = htons(pkt->length); @@ -1248,8 +1313,9 @@ static void dmxt_handler( u8_t *data, int len) { xSemaphoreTake(displayer.mutex, portMAX_DELAY); - // PLACEHOLDER - //led_vu_data(data + sizeof(struct dmxt_packet), offset, length); + led_vu_data(data + sizeof(struct ledd_packet), offset, length); + + displayer.wake = 1000; // wait a little while xSemaphoreGive(displayer.mutex); } diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index f98926be..80054ede 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register(SRC_DIRS . - PRIV_REQUIRES _override esp_common wifi-manager pthread squeezelite-ota platform_console telnet display targets + PRIV_REQUIRES _override esp_common wifi-manager pthread squeezelite-ota platform_console telnet display targets led_strip EMBED_FILES ../server_certs/github.pem LDFRAGMENTS "linker.lf" ) diff --git a/main/esp_app_main.c b/main/esp_app_main.c index 0343e332..fa829e02 100644 --- a/main/esp_app_main.c +++ b/main/esp_app_main.c @@ -42,6 +42,7 @@ #include "gds_draw.h" #include "gds_text.h" #include "gds_font.h" +#include "led_vu.h" #include "display.h" #include "accessors.h" #include "cmd_system.h" @@ -73,6 +74,7 @@ extern const uint8_t server_cert_pem_end[] asm("_binary_github_pem_end"); // as an exception _init function don't need include extern void services_init(void); extern void display_init(char *welcome); +extern void led_vu_init(void); extern void target_init(char *target); const char * str_or_unknown(const char * str) { return (str?str:unknown_string_placeholder); } const char * str_or_null(const char * str) { return (str?str:null_string_placeholder); } @@ -368,6 +370,7 @@ void register_default_nvs(){ register_default_string_val("ethtmout","8"); register_default_string_val("dhcp_tmout","8"); register_default_string_val("target", CONFIG_TARGET); + register_default_string_val("led_vu_config", ""); #ifdef CONFIG_CSPOT_SINK register_default_string_val("enable_cspot", STR(CONFIG_CSPOT_SINK)); register_default_string_val("cspot_config", ""); @@ -467,10 +470,18 @@ void app_main() target_init(target); free(target); } - if(is_recovery_running && display){ - GDS_ClearExt(display, true); - GDS_SetFont(display, &Font_line_2 ); - GDS_TextPos(display, GDS_FONT_DEFAULT, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "RECOVERY"); + ESP_LOGI(TAG,"Initializing led_vu"); + led_vu_init(); + + if(is_recovery_running) { + if (display) { + GDS_ClearExt(display, true); + GDS_SetFont(display, &Font_line_2 ); + GDS_TextPos(display, GDS_FONT_DEFAULT, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "RECOVERY"); + } + if(led_display) { + led_vu_color_yellow(LED_VU_BRIGHT); + } } diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index cc52e727..79b7f39b 100644 Binary files a/plugin/SqueezeESP32.zip and b/plugin/SqueezeESP32.zip differ diff --git a/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html b/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html index f5e683d5..0ddf2fd6 100644 --- a/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html +++ b/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html @@ -106,6 +106,34 @@
[% END %] + [% IF prefs.pref_led_config %] + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_LED_CONFIG" desc="PLUGIN_SQUEEZEESP32_LED_CONFIG_DESC" %] + + + [% prefs.pref_led_config %] + [% END %] + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_LED_VISUALIZER" desc="PLUGIN_SQUEEZEESP32_LED_VISUALIZER_DESC" %] + + [% END %] + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_LED_BRIGHTNESS" desc="PLUGIN_SQUEEZEESP32_LED_BRIGHTNESS_DESC" %] + + [% END %] + + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_LED_DATA" desc="PLUGIN_SQUEEZEESP32_LED_DATA_DESC" %] +   + [% "PLUGIN_SQUEEZEESP32_LED_DATA_X" | string %]  + + [% "PLUGIN_SQUEEZEESP32_LED_DATA_CMD" | string %]  + + [% END %] +
+ [% END %] + [% IF pref_equalizer %] [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_EQUALIZER" desc="" %]
[% "PLUGIN_SQUEEZEESP32_EQUALIZER_SAVE" | string %]
diff --git a/plugin/SqueezeESP32/Player.pm b/plugin/SqueezeESP32/Player.pm index cbc32d3b..00dd167d 100644 --- a/plugin/SqueezeESP32/Player.pm +++ b/plugin/SqueezeESP32/Player.pm @@ -10,6 +10,7 @@ use Slim::Utils::Log; use Slim::Utils::Prefs; use Plugins::SqueezeESP32::FirmwareHelper; +use Plugins::SqueezeESP32::RgbLed; my $sprefs = preferences('server'); my $prefs = preferences('plugin.squeezeesp32'); @@ -63,6 +64,10 @@ sub maxTreble { 20 } sub minTreble { -13 } sub maxBass { 20 } sub minBass { -13 } +sub hasLED { + my $client = shift; + return $prefs->client($client)->get('led_config') || 0; +} sub init { my $client = shift; @@ -98,6 +103,7 @@ sub init { $client->SUPER::init(@_); Plugins::SqueezeESP32::FirmwareHelper::init($client); + Plugins::SqueezeESP32::RgbLed::init($client); main::INFOLOG && $log->is_info && $log->info("SqueezeESP player connected: " . $client->id); } @@ -110,6 +116,9 @@ sub initPrefs { $prefs->client($client)->init( { equalizer => [(0) x 10], artwork => undef, + led_config => 0, + led_visualizer => 0, + led_brightness => 20, } ); $prefs->setValidate({ @@ -169,6 +178,9 @@ sub playerSettingsFrame { main::INFOLOG && $log->is_info && $log->info("Setting player $value" . "x" . "$height for ", $client->name); } + my $led_config = (unpack('Cnnn',$$data_ref))[3]; + $prefs->client($client)->set('led_config', $led_config); + main::INFOLOG && $log->is_info && $led_config && $log->info("Setting led length $led_config for ", $client->name); } $client->SUPER::playerSettingsFrame($data_ref); diff --git a/plugin/SqueezeESP32/PlayerSettings.pm b/plugin/SqueezeESP32/PlayerSettings.pm index 4265f335..3c1298ae 100644 --- a/plugin/SqueezeESP32/PlayerSettings.pm +++ b/plugin/SqueezeESP32/PlayerSettings.pm @@ -7,6 +7,7 @@ use List::Util qw(first min max); use Slim::Utils::Log; use Slim::Utils::Prefs; +use Slim::Utils::Strings qw(string cstring); my $sprefs = preferences('server'); my $prefs = preferences('plugin.squeezeesp32'); @@ -33,6 +34,7 @@ sub prefs { my ($class, $client) = @_; my @prefs; push @prefs, qw(width small_VU) if $client->displayWidth; + push @prefs, qw(led_config led_visualizer led_brightness);# if $client->hasLED; return ($prefs->client($client), @prefs); } @@ -86,6 +88,12 @@ sub handler { $cprefs->set('equalizer', $equalizer); $client->update_tones($equalizer); } + + if ($client->hasLED) { + $cprefs->set('led_visualizer', $paramRef->{'pref_led_visualizer'} || 0); + $cprefs->set('led_brightness', $paramRef->{'pref_led_brightness'} || 20); + Plugins::SqueezeESP32::RgbLed::updateLED($client); + } } if ($client->displayWidth) { @@ -95,6 +103,10 @@ sub handler { $paramRef->{'pref_artwork'} = $cprefs->get('artwork'); } + if ($client->hasLED) { + $paramRef->{'ledVisualModes'} = Plugins::SqueezeESP32::RgbLed::ledVisualModeOptions($client); + } + $paramRef->{'pref_equalizer'} = $cprefs->get('equalizer') if $client->can('depth') && $client->depth == 16; $paramRef->{'player_ip'} = $client->ip; diff --git a/plugin/SqueezeESP32/RgbLed.pm b/plugin/SqueezeESP32/RgbLed.pm new file mode 100644 index 00000000..99167516 --- /dev/null +++ b/plugin/SqueezeESP32/RgbLed.pm @@ -0,0 +1,188 @@ +package Plugins::SqueezeESP32::RgbLed; + +=head1 NAME + +Plugins::SqueezeESP32::RgbLed + +=head1 DESCRIPTION + +L + +=cut + +use strict; +use Slim::Utils::Strings qw(string cstring); + +use Slim::Utils::Log; +use Slim::Utils::Prefs; +use Plugins::SqueezeESP32::Player + +my $log = logger('player.RgbLed'); + +my $prefs = preferences('plugin.squeezeesp32'); +my $log = logger('plugin.squeezeesp32'); + +sub init { + Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'pause', 'resume', 'stop', 'clear'] ]); + + # register led visualizer comands to allow independant update and command line controls. + Slim::Control::Request::addDispatch([ 'dmx', '_data', '_xoff'], [1, 0, 0, \&sendDMX]); + Slim::Control::Request::addDispatch([ 'led_visual', '_mode', '_bright'], [1, 0, 0, \&setLEDVisu]); +} + +my $VISUALIZER_NONE = 0; +my $VISUALIZER_VUMETER = 1; +my $VISUALIZER_SPECTRUM_ANALYZER = 2; +my $VISUALIZER_WAVEFORM = 3; +my @ledvisualizers = ( + { desc => ['BLANK'], + params => [$VISUALIZER_NONE], + }, + { desc => ['VISUALIZER_ANALOG_VUMETER'], + params => [$VISUALIZER_VUMETER, 0], + }, + { desc => ['VISUALIZER_DIGITAL_VUMETER'], + params => [$VISUALIZER_VUMETER, 1], + }, + { desc => ['VISUALIZER_SPECTRUM_ANALYZER'], + params => [$VISUALIZER_SPECTRUM_ANALYZER, 0], + }, + { desc => ['VISUALIZER_SPECTRUM_ANALYZER','2'], + params => [$VISUALIZER_SPECTRUM_ANALYZER, 1], + }, + { desc => ['PLUGIN_SQUEEZEESP32_WAVEFORM'], + params => [$VISUALIZER_WAVEFORM, 0], + }, + { desc => ['PLUGIN_SQUEEZEESP32_WAVEFORM','2'], + params => [$VISUALIZER_WAVEFORM, 1], + }, +); + +my $nledvisualizers = $#ledvisualizers; + +sub ledVisualizerModes { + return \@ledvisualizers; +} + +sub ledVisualizerNModes { + return $nledvisualizers; +} + +sub updateLED { + my $client = shift; + my $cprefs = $prefs->client($client); + + my $visu = $cprefs->get('led_visualizer') || 0; + my $bright = $cprefs->get('led_brightness') || 20; + + $visu = 0 if ($visu < 0 || $visu > ledVisualizerNModes || !(Slim::Player::Source::playmode($client) eq 'play')); + my $modes = ledVisualizerModes; + my $params = $modes->[$visu]{'params'}; + my $data = pack('CCC', $params->[0], $params->[1], $bright); + main::INFOLOG && $log->is_debug && $log->info("Sending visu mode $visu ", $client->name); + + $client->sendFrame( ledv => \$data ); +} + +sub ledVisualParams { + my $client = shift; + + my $visu = $prefs->client($client)->get('led_visualizer') || 0; + + return $ledvisualizers[$visu]{params}; +} + +sub ledVisualModeOptions { + my $client = shift; + + my $display = { + '-1' => ' ' + }; + + my $modes = ledVisualizerModes; + my $nmodes = ledVisualizerNModes; + + for (my $i = 0; $i <= $nmodes; $i++) { + + my $desc = $modes->[$i]{'desc'}; + + for (my $j = 0; $j < scalar @$desc; $j++) { + + $display->{$i} .= ' ' if ($j > 0); + $display->{$i} .= string(@{$desc}[$j]) || @{$desc}[$j]; + } + } + + return $display; +} + +sub sendDMX { + my $request = shift; + + # check this is the correct command. + if ($request->isNotCommand([['dmx']])) { + $request->setStatusBadDispatch(); + return; + } + + # get our parameters + my $client = $request->client(); + + my $count = 0; + my $outData; + my @values = split(',', $request->getParam('_data') || ''); + foreach my $val (@values) { + $outData .= pack ( 'C', $val); + $count++; + } + $count /= 3; + + my $data = pack('nn', $request->getParam('_xoff') || 0, $count ) . $outData; + + # changed from dmxt to ledd (matches 'ledc' for tricolor led in receiver player) + $client->sendFrame( ledd => \$data ); +} + +sub setLEDVisu { + my $request = shift; + + # check this is the correct command. + if ($request->isNotCommand([['led_visual']])) { + $request->setStatusBadDispatch(); + return; + } + + my $client = $request->client(); + return if (!$client->hasLED); + + my $cprefs = $prefs->client($client); + + my $visu = $cprefs->get('led_visualizer') || 0; + my $mode = $request->getParam('_mode') || -1; + if ($mode == -1) { + $visu+=1; + } else { + $visu = $mode; + } + $visu = 0 if ($visu < 0 || $visu > ledVisualizerNModes); + $cprefs->set('led_visualizer', $visu); + + my $bright = $request->getParam('_bright') || -1; + if ($bright >= 0 && $bright < 256) { + $cprefs->set('led_brightness', $bright); + } + + updateLED($client); +} + +sub onNotification { + my $request = shift; + my $client = $request->client || return; + + foreach my $player ($client->syncGroupActiveMembers) { + next unless $player->isa('Plugins::SqueezeESP32::Player'); + updateLED($player) if $player->hasLED; + } +} + +1; diff --git a/plugin/SqueezeESP32/strings.txt b/plugin/SqueezeESP32/strings.txt index bd9daf29..78e3d1b8 100644 --- a/plugin/SqueezeESP32/strings.txt +++ b/plugin/SqueezeESP32/strings.txt @@ -106,6 +106,44 @@ PLUGIN_SQUEEZEESP32_ARTWORK_X PLUGIN_SQUEEZEESP32_ARTWORK_Y EN Y +PLUGIN_SQUEEZEESP32_LED_CONFIG + EN Led RGB Strip + +PLUGIN_SQUEEZEESP32_LED_CONFIG_DESC + EN Length of the Led strip reported by the player + +PLUGIN_SQUEEZEESP32_LED_VISUALIZER + EN Led Visualizer + +PLUGIN_SQUEEZEESP32_LED_VISUALIZER_DESC + EN Select Led Visualizer from the built in effects + +PLUGIN_SQUEEZEESP32_LED_BRIGHTNESS + EN Led Brghtness + +PLUGIN_SQUEEZEESP32_LED_BRIGHTNESS_DESC + EN Sets the brightness of the Led Visualizer effects + +PLUGIN_SQUEEZEESP32_LED_DATA + EN Led Test + +PLUGIN_SQUEEZEESP32_LED_DATA_DESC + EN Sends custom RGB data to the Led Strip. + EN
Enter R,G,B values (comma delimited). Repeat RGB for multiple Led sequences. Use the Offset to specifiy the first LED of the sequence. + EN
Use the Set button the transmit. + +PLUGIN_SQUEEZEESP32_LED_DATA_SEND + EN Set + +PLUGIN_SQUEEZEESP32_LED_DATA_X + EN Offset + +PLUGIN_SQUEEZEESP32_LED_DATA_CMD + EN Command + +PLUGIN_SQUEEZEESP32_WAVEFORM + EN Waveform Visualizer + PLUGIN_SQUEEZEESP32_EQUALIZER DE Grafischer Equalizer EN Graphic equalizer