Files
Juli d92a2e53cc On-device menu, persistent settings and 12 favourite slots
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.
2026-09-17 00:57:02 +02:00

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