mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-06 11:36:59 +03:00
merge changes from led_visu to v4.3
This commit is contained in:
11
components/led_strip/CMakeLists.txt
Normal file
11
components/led_strip/CMakeLists.txt
Normal 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
|
||||
)
|
||||
202
components/led_strip/LICENSE
Normal file
202
components/led_strip/LICENSE
Normal 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.
|
||||
407
components/led_strip/led_strip.c
Normal file
407
components/led_strip/led_strip.c
Normal 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;
|
||||
}
|
||||
96
components/led_strip/led_strip.h
Normal file
96
components/led_strip/led_strip.h
Normal 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
|
||||
363
components/led_strip/led_vu.c
Normal file
363
components/led_strip/led_vu.c
Normal 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);
|
||||
}
|
||||
|
||||
31
components/led_strip/led_vu.h
Normal file
31
components/led_strip/led_vu.h
Normal 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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -115,3 +121,5 @@ 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);
|
||||
const ledvu_struct_t * config_ledvu_get();
|
||||
esp_err_t config_ledvu_set(ledvu_struct_t * rotary);
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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,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"
|
||||
)
|
||||
|
||||
@@ -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.
@@ -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 %]">
|
||||
[% "PLUGIN_SQUEEZEESP32_LED_DATA_X" | string %] 
|
||||
<input type="text" class="stdedit" name="pref_led_data_x" id="led_data_x" value="0" size="2">
|
||||
[% "PLUGIN_SQUEEZEESP32_LED_DATA_CMD" | string %] 
|
||||
<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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
plugin/SqueezeESP32/RgbLed.pm
Normal file
188
plugin/SqueezeESP32/RgbLed.pm
Normal 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;
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user