merge changes from led_visu to v4.3

This commit is contained in:
Wizmo2
2022-11-21 19:01:19 -05:00
parent 18cc0adfb4
commit afd0da16a5
21 changed files with 1676 additions and 47 deletions

View File

@@ -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
)

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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
)

View File

@@ -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);

View File

@@ -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
)

View File

@@ -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);
}

View File

@@ -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"
)

View File

@@ -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);
}
}

Binary file not shown.

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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