Hold the knob to open a six-row menu: save/remove the playing camera as a favourite (12 NVS slots), play or forget favourites, choose the source (random insecam or favourites), time per camera, listing, screen and ring brightness, caption, and reboot into Wi-Fi setup. Settings live in NVS and override the Kconfig first-boot defaults. Camera URLs are stored with their COUNTER placeholder so favourites get a fresh cache-buster on every visit.
135 lines
4.8 KiB
C
135 lines
4.8 KiB
C
#include "leds.h"
|
|
#include "board.h"
|
|
#include "sdkconfig.h"
|
|
#include "driver/spi_master.h"
|
|
#include "led_strip.h"
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include <math.h>
|
|
#include <string.h>
|
|
|
|
static const char *TAG = "leds";
|
|
#define MAX_LEDS 16
|
|
#define TICK_MS 25
|
|
|
|
static led_strip_handle_t strip; // WS2812 path
|
|
static spi_device_handle_t apa; // APA102 path
|
|
static uint8_t apa_frame[4 + MAX_LEDS * 4 + 4];
|
|
|
|
static volatile led_mode_t mode = LED_OFF;
|
|
static volatile uint32_t colour = 0;
|
|
static volatile int countdown = 1000;
|
|
static volatile uint8_t brightness = CONFIG_WIDGET_LED_BRIGHTNESS;
|
|
|
|
static void push(const uint8_t (*rgb)[3], int n)
|
|
{
|
|
if (board->led_ws2812) {
|
|
for (int i = 0; i < n; i++)
|
|
led_strip_set_pixel(strip, i, rgb[i][0] * brightness / 255, rgb[i][1] * brightness / 255,
|
|
rgb[i][2] * brightness / 255);
|
|
led_strip_refresh(strip);
|
|
} else {
|
|
// APA102: 32-bit zero start frame, per-LED 0b111xxxxx global brightness + B G R, ones end frame.
|
|
int gb = brightness * 31 / 255;
|
|
if (gb < 1 && brightness) gb = 1;
|
|
uint8_t *p = apa_frame;
|
|
memset(p, 0, 4); p += 4;
|
|
for (int i = 0; i < n; i++) {
|
|
*p++ = 0xE0 | gb; *p++ = rgb[i][2]; *p++ = rgb[i][1]; *p++ = rgb[i][0];
|
|
}
|
|
memset(p, 0xFF, 4); p += 4;
|
|
spi_transaction_t t = {.length = (p - apa_frame) * 8, .tx_buffer = apa_frame};
|
|
spi_device_transmit(apa, &t);
|
|
}
|
|
}
|
|
|
|
static void scale(uint8_t out[3], uint32_t rgb, int permille)
|
|
{
|
|
if (permille < 0) permille = 0;
|
|
if (permille > 1000) permille = 1000;
|
|
out[0] = ((rgb >> 16) & 0xff) * permille / 1000;
|
|
out[1] = ((rgb >> 8) & 0xff) * permille / 1000;
|
|
out[2] = (rgb & 0xff) * permille / 1000;
|
|
}
|
|
|
|
static void led_task(void *arg)
|
|
{
|
|
uint8_t rgb[MAX_LEDS][3];
|
|
const int n = board->led_count;
|
|
while (1) {
|
|
int64_t ms = esp_timer_get_time() / 1000;
|
|
led_mode_t m = mode;
|
|
uint32_t c = colour;
|
|
memset(rgb, 0, sizeof rgb);
|
|
switch (m) {
|
|
case LED_OFF:
|
|
break;
|
|
case LED_SOLID:
|
|
for (int i = 0; i < n; i++) scale(rgb[i], c, 1000);
|
|
break;
|
|
case LED_BLINK:
|
|
if ((ms / 250) & 1) for (int i = 0; i < n; i++) scale(rgb[i], c, 1000);
|
|
break;
|
|
case LED_BREATHE: {
|
|
float phase = (ms % 3000) / 3000.0f * 2 * (float)M_PI;
|
|
int level = 60 + (int)(470 * (1 - cosf(phase))); // 60..1000
|
|
for (int i = 0; i < n; i++) scale(rgb[i], c, level);
|
|
break;
|
|
}
|
|
case LED_SPIN: {
|
|
int head = (ms / 70) % n;
|
|
for (int i = 0; i < n; i++) {
|
|
int d = (head - i + n) % n;
|
|
int level = d == 0 ? 1000 : d == 1 ? 350 : d == 2 ? 100 : 20;
|
|
scale(rgb[i], c, level);
|
|
}
|
|
break;
|
|
}
|
|
case LED_COUNTDOWN: {
|
|
int lit = countdown * n; // in thousandths of an LED
|
|
for (int i = 0; i < n; i++) {
|
|
int level = lit - i * 1000;
|
|
if (level > 1000) level = 1000;
|
|
if (level < 40) level = 40; // faint base glow keeps the ring visible
|
|
scale(rgb[i], c, level);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
push(rgb, n);
|
|
vTaskDelay(pdMS_TO_TICKS(TICK_MS));
|
|
}
|
|
}
|
|
|
|
void leds_init(void)
|
|
{
|
|
if (board->led_ws2812) {
|
|
led_strip_config_t cfg = {
|
|
.strip_gpio_num = board->led_data, .max_leds = board->led_count,
|
|
.led_model = LED_MODEL_WS2812, .color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
|
|
};
|
|
led_strip_rmt_config_t rmt = {.clk_src = RMT_CLK_SRC_DEFAULT, .resolution_hz = 10 * 1000 * 1000};
|
|
ESP_ERROR_CHECK(led_strip_new_rmt_device(&cfg, &rmt, &strip));
|
|
led_strip_clear(strip);
|
|
} else {
|
|
spi_bus_config_t bus = {
|
|
.mosi_io_num = board->led_data, .sclk_io_num = board->led_clk,
|
|
.miso_io_num = -1, .quadwp_io_num = -1, .quadhd_io_num = -1,
|
|
.max_transfer_sz = sizeof apa_frame,
|
|
};
|
|
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &bus, SPI_DMA_DISABLED));
|
|
spi_device_interface_config_t dev = {
|
|
.clock_speed_hz = 4 * 1000 * 1000, .mode = 0, .spics_io_num = -1, .queue_size = 1,
|
|
};
|
|
ESP_ERROR_CHECK(spi_bus_add_device(SPI3_HOST, &dev, &apa));
|
|
}
|
|
ESP_LOGI(TAG, "%d x %s", board->led_count, board->led_ws2812 ? "WS2812" : "APA102");
|
|
xTaskCreatePinnedToCore(led_task, "leds", 3072, NULL, 2, NULL, 0);
|
|
}
|
|
|
|
void leds_set_mode(led_mode_t m, uint32_t rgb) { colour = rgb; mode = m; }
|
|
void leds_set_countdown(int permille) { countdown = permille; }
|
|
void leds_set_brightness(uint8_t b) { brightness = b; }
|