Add SET_LIGHTING (0xB1) and AC22-style init LED shimmer.

Mutable status palette over serial; Unknown uses HSV saturation pulse instead of dim-red/fade-to-black.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
raduet
2026-07-30 17:12:09 +02:00
co-authored by Cursor
parent 940e3f4a3d
commit b98207da4c
9 changed files with 7690 additions and 7391 deletions
+228 -139
View File
@@ -18,16 +18,16 @@ static RGB_t ws2812_led1 = { .R = 0, .G = 0, .B = 0 };
RGB_State_t LED_State;
RGB_Cycle_t LED_Cycle;
RGB_Cycle_t color_estop = {
.Color1 = { .R = 250, .G = 0, .B = 0 },
.Color2 = { .R = 0, .G = 0, .B = 0 },
.Tr = 10,
.Th = 5,
.Tf = 10,
.Tl = 5,
};
static const RGB_Cycle_t color_estop = {
.Color1 = { .R = 250, .G = 0, .B = 0 },
.Color2 = { .R = 0, .G = 0, .B = 0 },
.Tr = 40,
.Th = 10,
.Tf = 40,
.Tl = 10,
};
RGB_Cycle_t color_unlock = {
static const RGB_Cycle_t color_unlock = {
.Color1 = { .R = 255, .G = 0, .B = 0 },
.Color2 = { .R = 0, .G = 0, .B = 0 },
.Tr = 10,
@@ -36,139 +36,220 @@ RGB_Cycle_t color_unlock = {
.Tl = 10,
};
RGB_Cycle_t color_unknown = {
.Color1 = { .R = 64, .G = 0, .B = 0 },
.Color2 = { .R = 64, .G = 0, .B = 0 },
.Tr = 50,
.Th = 10,
.Tf = 50,
.Tl = 0,
};
/* Same init shimmer as AC22/tkzu22: hue=85, sat breathes 30↔250 (not fade-to-black). */
static uint8_t lighting_init_active;
RGB_Cycle_t color_light = {
.Color1 = { .R = 0, .G = 255, .B = 0 },
.Color2 = { .R = 0, .G = 255, .B = 0 },
.Tr = 50,
.Th = 10,
.Tf = 50,
.Tl = 0,
};
static void LED_HsvToRgb(uint8_t hue, uint8_t sat, uint8_t val, RGB_t *out)
{
uint8_t region;
uint8_t remainder;
uint8_t p;
uint8_t q;
uint8_t t;
RGB_Cycle_t color_disabled = {
.Color1 = { .R = 250, .G = 0, .B = 0 },
.Color2 = { .R = 32, .G = 0, .B = 0 },
.Tr = 50,
.Th = 10,
.Tf = 50,
.Tl = 0,
};
RGB_Cycle_t color_unplugged = {
.Color1 = { .R = 0, .G = 128, .B = 0 },
.Color2 = { .R = 0, .G = 128, .B = 0 },
.Tr = 50,
.Th = 10,
.Tf = 50,
.Tl = 0,
};
RGB_Cycle_t color_preparing = {
.Color1 = { .R = 0, .G = 0, .B = 255 },
.Color2 = { .R = 0, .G = 0, .B = 0 },
.Tr = 10,
.Th = 10,
.Tf = 10,
.Tl = 10,
};
RGB_Cycle_t color_charging = {
.Color1 = { .R = 0, .G = 255, .B = 0 },
.Color2 = { .R = 0, .G = 32, .B = 0 },
.Tr = 50,
.Th = 10,
.Tf = 50,
.Tl = 0,
};
RGB_Cycle_t color_finished = {
.Color1 = { .R = 255, .G = 255, .B = 255 },
.Color2 = { .R = 255, .G = 255, .B = 255 },
.Tr = 50,
.Th = 10,
.Tf = 50,
.Tl = 0,
};
RGB_Cycle_t color_error = {
.Color1 = { .R = 255, .G = 0, .B = 0 },
.Color2 = { .R = 32, .G = 0, .B = 0 },
.Tr = 50,
.Th = 10,
.Tf = 50,
.Tl = 0,
};
void LED_Write(){
if(CONN.chargingError != CONN_NO_ERROR){
LED_SetColor(&color_error);
return;
if (sat == 0U) {
out->R = out->G = out->B = val;
return;
}
if(CONN.connControl == CMD_FORCE_UNLOCK){
LED_SetColor(&color_unlock);
return;
}
if(CONN.connControl == CMD_STOP){
LED_SetColor(&color_estop);
return;
}
switch(CONN.connState){
case Unknown:
LED_SetColor(&color_unknown);
break;
case Unplugged:
LED_SetColor(&color_unplugged);
break;
case Disabled:
LED_SetColor(&color_error);
break;
case Preparing:
LED_SetColor(&color_preparing);
break;
case AuthRequired:
LED_SetColor(&color_preparing);
break;
case WaitingForEnergy:
LED_SetColor(&color_charging);
break;
case ChargingPausedEV:
LED_SetColor(&color_charging);
break;
case ChargingPausedEVSE:
LED_SetColor(&color_charging);
break;
case Charging:
LED_SetColor(&color_charging);
break;
case AuthTimeout:
LED_SetColor(&color_finished);
break;
case Finished:
LED_SetColor(&color_finished);
break;
case FinishedEVSE:
LED_SetColor(&color_finished);
break;
case FinishedEV:
LED_SetColor(&color_finished);
break;
case Replugging:
LED_SetColor(&color_preparing);
break;
region = (uint8_t)(hue / 43U);
remainder = (uint8_t)((hue - (uint8_t)(region * 43U)) * 6U);
p = (uint8_t)((val * (255U - sat)) >> 8);
q = (uint8_t)((val * (255U - ((sat * remainder) >> 8))) >> 8);
t = (uint8_t)((val * (255U - ((sat * (255U - remainder)) >> 8))) >> 8);
switch (region) {
case 0:
out->R = val;
out->G = t;
out->B = p;
break;
case 1:
out->R = q;
out->G = val;
out->B = p;
break;
case 2:
out->R = p;
out->G = val;
out->B = t;
break;
case 3:
out->R = p;
out->G = q;
out->B = val;
break;
case 4:
out->R = t;
out->G = p;
out->B = val;
break;
default:
LED_SetColor(&color_unknown);
break;
out->R = val;
out->G = p;
out->B = q;
break;
}
}
}
/** AC22-compatible init «перелив»: fixed green hue, saturation pulse. */
static void LED_RenderInitShimmer(void)
{
static uint8_t sat = 250U;
static uint8_t rising = 0U;
const uint8_t hue = 85U;
const uint8_t val = 245U;
if (sat >= 250U) {
rising = 0U;
}
if (sat <= 30U) {
rising = 1U;
}
if (rising) {
sat = (sat <= 246U) ? (uint8_t)(sat + 4U) : 250U;
} else {
sat = (sat >= 34U) ? (uint8_t)(sat - 4U) : 30U;
}
LED_HsvToRgb(hue, sat, val, &LED_State.color);
}
static LightingConfigPacket_t committed_lighting = {
.mode = 0,
.roles = {
{ 0, 128, 0, 100, 0, 40, 0 },
{ 0, 0, 255, 100, 1, 55, 100 },
{ 0, 255, 0, 100, 1, 40, 87 },
{ 255, 255, 255, 100, 0, 40, 0 },
{ 255, 0, 0, 100, 1, 50, 87 },
},
};
static LightingConfigPacket_t preview_lighting;
static uint32_t preview_expires_at;
static uint8_t preview_active;
static RGB_Cycle_t LED_CycleFromRole(const LightingRolePacket_t *role)
{
const uint8_t fade_to_black = role->fadeTo >= 95;
RGB_Cycle_t cycle = {
.Color1 = {
.R = (uint8_t)((uint16_t)role->r * role->brightness / 100),
.G = (uint8_t)((uint16_t)role->g * role->brightness / 100),
.B = (uint8_t)((uint16_t)role->b * role->brightness / 100),
},
.Tr = (uint8_t)((100 - role->speed) < 5 ? 5 : (100 - role->speed)),
.Th = 10,
.Tf = (uint8_t)((100 - role->speed) < 5 ? 5 : (100 - role->speed)),
.Tl = fade_to_black ? 10 : 0,
};
if (role->effect == 0) {
cycle.Color2 = cycle.Color1;
} else {
cycle.Color2.R = (uint8_t)((uint16_t)cycle.Color1.R * (100 - role->fadeTo) / 100);
cycle.Color2.G = (uint8_t)((uint16_t)cycle.Color1.G * (100 - role->fadeTo) / 100);
cycle.Color2.B = (uint8_t)((uint16_t)cycle.Color1.B * (100 - role->fadeTo) / 100);
}
return cycle;
}
static uint8_t LED_IsLightingConfigValid(const LightingConfigPacket_t *config)
{
if (config == NULL || config->mode > 1 || (config->flags & 0xFE) != 0 ||
config->reserved != 0 || ((config->flags & 0x01) && config->previewSec == 0) ||
(!(config->flags & 0x01) && config->previewSec != 0)) {
return 0;
}
for (uint8_t i = 0; i < 5; i++) {
const LightingRolePacket_t *role = &config->roles[i];
if (role->brightness > 100 || role->effect > 1 || role->speed > 100 || role->fadeTo > 100) {
return 0;
}
}
return 1;
}
uint8_t LED_ApplyLightingConfig(const LightingConfigPacket_t *config)
{
if (!LED_IsLightingConfigValid(config)) {
return 0;
}
if (config->flags & 0x01) {
memcpy(&preview_lighting, config, sizeof(preview_lighting));
preview_active = 1;
preview_expires_at = HAL_GetTick() + ((uint32_t)config->previewSec * 1000U);
} else {
memcpy(&committed_lighting, config, sizeof(committed_lighting));
preview_active = 0;
}
return 1;
}
void LED_Write(void)
{
const LightingConfigPacket_t *lighting = &committed_lighting;
RGB_Cycle_t cycle;
lighting_init_active = 0;
if (CONN.connControl == CMD_STOP) {
LED_SetColor(&color_estop);
return;
}
if (CONN.chargingError != CONN_NO_ERROR || CONN.connState == Disabled) {
cycle = LED_CycleFromRole(&lighting->roles[4]);
LED_SetColor(&cycle);
return;
}
if (preview_active && (int32_t)(HAL_GetTick() - preview_expires_at) >= 0) {
preview_active = 0;
}
if (preview_active) {
lighting = &preview_lighting;
}
if (lighting->mode == 1) {
RGB_Cycle_t off = { 0 };
LED_SetColor(&off);
return;
}
if (CONN.connControl == CMD_FORCE_UNLOCK) {
LED_SetColor(&color_unlock);
return;
}
if (CONN.connState == Unknown) {
lighting_init_active = 1;
return;
}
switch (CONN.connState) {
case Unplugged:
cycle = LED_CycleFromRole(&lighting->roles[0]);
break;
case Preparing:
case AuthRequired:
case Replugging:
cycle = LED_CycleFromRole(&lighting->roles[1]);
break;
case WaitingForEnergy:
case ChargingPausedEV:
case ChargingPausedEVSE:
case Charging:
cycle = LED_CycleFromRole(&lighting->roles[2]);
break;
case AuthTimeout:
case Finished:
case FinishedEVSE:
case FinishedEV:
cycle = LED_CycleFromRole(&lighting->roles[3]);
break;
default:
lighting_init_active = 1;
return;
}
LED_SetColor(&cycle);
}
void interpolateColors(RGB_t* color1, RGB_t* color2, uint16_t a, uint16_t b, RGB_t *result) {
@@ -331,7 +412,7 @@ void WS2812_SetColor(RGB_t *color){
ws2812_update(&led0, &led1);
}
void LED_SetColor(RGB_Cycle_t *color){
void LED_SetColor(const RGB_Cycle_t *color){
memcpy(&LED_Cycle, color, sizeof(RGB_Cycle_t));
}
@@ -349,6 +430,15 @@ void LED_Task(){
static uint32_t led_tick;
if((HAL_GetTick() - led_tick) > 20){
led_tick = HAL_GetTick();
LED_Ws2812ErrorBlinkUpdate();
if (lighting_init_active) {
LED_RenderInitShimmer();
RGB_SetColor(&LED_State.color);
WS2812_SetColor(&LED_State.color);
return;
}
LED_State.tick++;
switch(LED_State.state){
case LED_RISING:
@@ -386,7 +476,6 @@ void LED_Task(){
default:
LED_State.state = LED_RISING;
}
LED_Ws2812ErrorBlinkUpdate();
RGB_SetColor(&LED_State.color);
WS2812_SetColor(&LED_State.color);
}
+9
View File
@@ -5,6 +5,7 @@
#include "debug.h"
#include "fire_alarm.h"
#include "psu_control.h"
#include "rgb_controller.h"
#include "usart.h"
#include <string.h>
@@ -58,6 +59,14 @@ void SC_CommandHandler(ReceivedCommand_t* cmd) {
}
response_code = RESP_FAILED;
break;
case CMD_SET_LIGHTING:
if (cmd->argument_length == sizeof(LightingConfigPacket_t) &&
LED_ApplyLightingConfig((const LightingConfigPacket_t *)cmd->argument)) {
response_code = RESP_SUCCESS;
break;
}
response_code = RESP_FAILED;
break;
case CMD_SET_POWER_LIMIT:
if (cmd->argument_length == 1) {
PSU0.power_limit = ((uint8_t*)cmd->argument)[0] * 1000;