Browse Source

merge changes from led_visu to v4.3

Wizmo2 2 years ago
parent
commit
afd0da16a5

+ 11 - 0
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
+)

+ 202 - 0
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.

+ 407 - 0
components/led_strip/led_strip.c

@@ -0,0 +1,407 @@
+/*  ----------------------------------------------------------------------------
+    File: led_strip.c
+    Author(s):  Lucas Bruder <LBruder@me.com>
+    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 <string.h>
+
+#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;
+}

+ 96 - 0
components/led_strip/led_strip.h

@@ -0,0 +1,96 @@
+/*  ---------------------------------------------------------------------------
+    File: led_strip.h
+    Author(s):  Lucas Bruder <LBruder@me.com>
+    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 <driver/rmt.h>
+#include <driver/gpio.h>
+#include "freertos/FreeRTOS.h"
+
+#include <stddef.h>
+
+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

+ 363 - 0
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 <ctype.h>
+#include <math.h>
+#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<length; i++) {
+		gain = *p;
+        r = gain*gain/bright;
+        if (!style) {
+            g = 0;
+            b = gain;
+        } else {
+            g = r;
+            r = 0;
+            b = gain * (bright-gain)/bright;
+        }
+        for (int j=0; j<width; j++) {
+            led_strip_set_pixel_rgb(led_display, pos, r, g, b);
+            pos++;
+        }
+        p++;
+    }
+    
+    led_strip_show(led_display);
+ }
+
+/****************************************************************************************
+ * Progress bar display
+ * pct - percentage complete (0-100)
+ */
+void led_vu_progress_bar(int pct, int bright) {
+    if (!led_display) return;
+
+    // define colors
+    struct led_color_t color_on   = {.red = bright, .green = 0, .blue = 0};
+    struct led_color_t color_off = {.red = 0, .green = bright, .blue = 0};
+
+    // calcuate led position
+    int led_lit = strip.length * pct / 100;
+
+    // set colors
+    for (int i = 0; i < strip.length; i++) {
+        led_strip_set_pixel_color(led_display, i, (i < led_lit) ? &color_off : &color_on);
+    }
+
+    led_strip_show(led_display);
+}
+
+/****************************************************************************************
+ * Spin dial display
+ * gain - brightness (0-100), rate - color change speed (0-100) 
+ * comet - alternate display mode
+ */
+void led_vu_spin_dial(int gain, int rate, bool comet) 
+{
+    if (!led_display) return;
+
+    static int led_pos = 0;
+    static uint8_t r = 0;
+    static uint8_t g = 0;
+    static uint8_t b = 0;
+    
+    // calculate next color
+    uint8_t step = rate / 2; // controls color change speed
+    if (r == 0 && g == 0 && b == 0) {
+        r = LED_VU_MAX; g = step; 
+    } else if (b == 0) {
+        g = (g > 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);
+}
+

+ 31 - 0
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 <ctype.h>
+
+#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();
+

+ 95 - 0
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","<none>|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();
 }
 

+ 54 - 0
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;

+ 9 - 1
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);
+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);

+ 1 - 1
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 
 					  )

+ 22 - 16
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);
-
-	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);
+	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);
+		}
+		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);

+ 1 - 0
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
 )
 

+ 91 - 25
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);
 }	

+ 1 - 1
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"
                     	)

+ 15 - 4
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);
+		}
 	}
 
 

BIN
plugin/SqueezeESP32.zip


+ 28 - 0
plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html

@@ -106,6 +106,34 @@
 		<hr>
 	[% END %]
 
+	[% IF prefs.pref_led_config %]
+		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_LED_CONFIG" desc="PLUGIN_SQUEEZEESP32_LED_CONFIG_DESC" %]
+			<!--<input type="text" readonly class="stdedit" name="pref_led_config" id="led_config" value="[% prefs.pref_led_config %]" size="3">-->
+			<input type="hidden" name="pref_led_config" value="[% prefs.pref_led_config %]">
+			[% prefs.pref_led_config %]
+		[% END %]
+		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_LED_VISUALIZER" desc="PLUGIN_SQUEEZEESP32_LED_VISUALIZER_DESC" %]
+			<select class="stdedit" name="pref_led_visualizer" id="led_visualizer">
+				[% num = 1 %][% last = 1 %]
+				[% FOREACH option = ledVisualModes.keys.nsort %]
+					<option [% IF prefs.pref_led_visualizer == option %]selected [% ELSIF num == 1 && option == '-1' %]selected [% END %]value="[% option %]">[% ledVisualModes.$option %]</option>
+				[%- END -%]
+			</select>
+		[% END %]
+		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_LED_BRIGHTNESS" desc="PLUGIN_SQUEEZEESP32_LED_BRIGHTNESS_DESC" %]
+			<input type="text" class="stdedit sliderInput_0_255" name="pref_led_brightness" id="led_brightness" value="[% prefs.pref_led_brightness %]" size="2">
+		[% END %]
+		
+		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_LED_DATA" desc="PLUGIN_SQUEEZEESP32_LED_DATA_DESC" %]
+			<input type="button" name="led_data_send" onclick="SqueezeJS.Controller.request({params: ['[% playerid %]', ['dmx', document.getElementById('led_data_cmd').value, document.getElementById('led_data_x').value]] });" value="[% "PLUGIN_SQUEEZEESP32_LED_DATA_SEND" | string %]">&nbsp;
+			[% "PLUGIN_SQUEEZEESP32_LED_DATA_X" | string %]&nbsp
+			<input type="text" class="stdedit" name="pref_led_data_x" id="led_data_x" value="0" size="2">
+			[% "PLUGIN_SQUEEZEESP32_LED_DATA_CMD" | string %]&nbsp
+			<input type="text" class="stdedit" name="pref_led_data_cmd" id="led_data_cmd" value="80,0,0,0,80,0,0,0,80" size="50">
+		[% END %]
+		<hr>
+	[% END %]
+	
 	[% IF pref_equalizer %]
 		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_EQUALIZER" desc="" %]
 			<div>[% "PLUGIN_SQUEEZEESP32_EQUALIZER_SAVE" | string %]</div>

+ 12 - 0
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);

+ 12 - 0
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;
 

+ 188 - 0
plugin/SqueezeESP32/RgbLed.pm

@@ -0,0 +1,188 @@
+package Plugins::SqueezeESP32::RgbLed;
+
+=head1 NAME
+
+Plugins::SqueezeESP32::RgbLed
+
+=head1 DESCRIPTION
+
+L<Plugins::SqueezeESP32::RgbLed>
+
+=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;

+ 38 - 0
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	<br>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	<br>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