/* * 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) * Artwork function, but not released as very buggy and not really practical */ #include #include #include "esp_log.h" #include "globdefs.h" #include "monitor.h" #include "led_strip.h" #include "platform_config.h" #include "services.h" #include "led_vu.h" static const char *TAG = "led_vu"; static void (*battery_handler_chain)(float value, int cells); static void battery_svc(float value, int cells); static int battery_status = 0; #define LED_VU_STACK_SIZE (3*1024) #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 LED_VU_STATUS_GREEN 75 #define LED_VU_STATUS_RED 25 #define max(a,b) (((a) > (b)) ? (a) : (b)) struct led_strip_t* led_display = NULL; static EXT_RAM_ATTR struct led_strip_t led_strip_config; static EXT_RAM_ATTR struct { int gpio; int length; int vu_length; int vu_start_l; int vu_start_r; int vu_status; int vu_scale; } strip; static int led_addr(int pos ) { if (pos < 0) return pos + strip.length; if (pos >= strip.length) return pos - strip.length; return pos; } static void battery_svc(float value, int cells) { battery_status = battery_level_svc(); ESP_LOGI(TAG, "Called for battery service with volt:%f cells:%d status:%d", value, cells, battery_status); if (battery_handler_chain) battery_handler_chain(value, cells); } /**************************************************************************************** * Suspend. * */ static void led_vu_sleep(void) { led_vu_clear(led_display); } /**************************************************************************************** * Initialize the led vu strip if configured. * */ void led_vu_init() { char* config = config_alloc_get_str("led_vu_config", NULL, "N/A"); PARSE_PARAM(config, "length",'=', strip.length); PARSE_PARAM(config, "gpio",'=', strip.gpio); // check for valid configuration if (!strip.gpio) { ESP_LOGI(TAG, "led_vu configuration invalid"); goto done; } strip.vu_scale = 100; PARSE_PARAM(config, "scale",'=',strip.vu_scale); battery_handler_chain = battery_handler_svc; battery_handler_svc = battery_svc; battery_status = battery_level_svc(); if (strip.length > LED_VU_MAX_LENGTH) strip.length = LED_VU_MAX_LENGTH; // initialize vu meter settings if (strip.length < 10) { // single bar for small strips strip.vu_length = strip.length; strip.vu_start_l = 0; strip.vu_start_r = strip.vu_start_l; strip.vu_status = 0; } else { strip.vu_length = (strip.length - 1) / 2; strip.vu_start_l = (strip.length % 2) ? strip.vu_length -1 : strip.vu_length; strip.vu_start_r = strip.vu_length + 1; strip.vu_status = strip.vu_length; } ESP_LOGI(TAG, "vu meter using length:%d left:%d right:%d status:%d scale:%d", strip.vu_length, strip.vu_start_l, strip.vu_start_r, strip.vu_status, strip.vu_scale); // create driver configuration led_strip_config.rgb_led_type = RGB_LED_TYPE_WS2812; 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; led_strip_config.rmt_channel = RMT_NEXT_TX_CHANNEL(); // 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 on channel:%d", strip.gpio, strip.length, led_strip_config.rmt_channel); } else { ESP_LOGE(TAG, "led_vu init failed"); goto done; } // reserver max memory for remote management systems rmt_set_mem_block_num(led_strip_config.rmt_channel, 7); services_sleep_setsuspend(led_vu_sleep); 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; } /**************************************************************************************** * Returns a user defined scale (percent) */ uint16_t led_vu_scale() { if (!led_display) return 0; return (uint16_t)strip.vu_scale; } /**************************************************************************************** * Turns all LEDs off (Black) */ void led_vu_clear() { if (!led_display) return; led_strip_clear(led_display); led_strip_show(led_display); } /**************************************************************************************** * Sets all LEDs to one color * r = red (0-255), g = green (0-255), b - blue (0-255) * note - all colors are adjusted for brightness */ void led_vu_color_all(uint8_t r, uint8_t g, uint8_t b) { if (!led_display) return; struct led_color_t color_on = {.red = r, .green = g, .blue = b}; for (int i = 0 ; i < strip.length ; i ++){ led_strip_set_pixel_color(led_display, i, &color_on); } led_strip_show(led_display); } /**************************************************************************************** * Sets LEDs based on a data packet consiting of rgb data * offset - starting LED, * length - number of leds (3x rgb bytes) * data - array of rgb values in multiples of 3 bytes */ void led_vu_data(uint8_t* data, uint16_t offset, uint16_t length) { if (!led_display) return; uint8_t* p = (uint8_t*) data; for (int i = 0; i < length; i++) { led_strip_set_pixel_rgb(led_display, i+offset, *p, *(p+1), *(p+2)); p+=3; } led_strip_show(led_display); } /**************************************************************************************** * Progress bar display * data - array of gain values(0-100) * offset - starting position * length - size of array */ void led_vu_spectrum(uint8_t* data, int bright, int length, int style) { if (!led_display) return; uint8_t gain,r,g,b; int width = strip.length / length; int pos = 0; uint8_t* p = (uint8_t*) data; for (int i=0; i LED_VU_MAX-step) ? LED_VU_MAX : g + step; r = (r < step) ? 0 : r - step; if (r == 0) b = step; } else if (r == 0) { b = (b > LED_VU_MAX-step) ? LED_VU_MAX : b + step; g = (g < step) ? 0 : g- step; if (g == 0) r = step; } else { r = (r > LED_VU_MAX-step) ? LED_VU_MAX : r + step; b = (b < step) ? 0 : b - step; if (r == 0) b = step; } uint8_t rp = r * gain / LED_VU_MAX; uint8_t gp = g * gain / LED_VU_MAX; uint8_t bp = b * gain / LED_VU_MAX; // set led color speed++; if (comet) { led_strip_clear(led_display); 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); } for (int i = 0; i < speed; i++) { led_strip_set_pixel_rgb(led_display, led_pos, rp, gp, bp); 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; // single bar if (strip.vu_start_l == strip.vu_start_r) { vu_r = (vu_l + vu_r) / 2; vu_l = 0; } // 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; } // show battery status if (battery_status > LED_VU_STATUS_GREEN) led_strip_set_pixel_rgb(led_display, strip.vu_status, 0, bright, 0); else if (battery_status > LED_VU_STATUS_RED) led_strip_set_pixel_rgb(led_display, strip.vu_status, bright/2, bright/2, 0); else if (battery_status > 0) led_strip_set_pixel_rgb(led_display, strip.vu_status, bright, 0, 0); led_strip_show(led_display); }