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
+1 -1
View File
@@ -27,7 +27,7 @@ extern "C" {
/* USER CODE BEGIN EC */ /* USER CODE BEGIN EC */
#define FW_VERSION_MAJOR 1 #define FW_VERSION_MAJOR 1
#define FW_VERSION_MINOR 0 #define FW_VERSION_MINOR 0
#define FW_VERSION_PATCH 28 #define FW_VERSION_PATCH 31
/* USER CODE END EC */ /* USER CODE END EC */
/* Exported macro ------------------------------------------------------------*/ /* Exported macro ------------------------------------------------------------*/
+3 -1
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "main.h" #include "main.h"
#include "serial_control.h"
#pragma pack(push, 1) #pragma pack(push, 1)
@@ -40,6 +41,7 @@ typedef struct{
void LED_Task(); void LED_Task();
void LED_Write(); void LED_Write();
void LED_Init(); void LED_Init();
void LED_SetColor(RGB_Cycle_t *color); void LED_SetColor(const RGB_Cycle_t *color);
void RGB_SetColor(RGB_t *color); void RGB_SetColor(RGB_t *color);
void WS2812_SetColor(RGB_t *color); void WS2812_SetColor(RGB_t *color);
uint8_t LED_ApplyLightingConfig(const LightingConfigPacket_t *config);
+22
View File
@@ -22,6 +22,7 @@
// Сервисные команды // Сервисные команды
#define CMD_SET_CONFIG 0xB0 #define CMD_SET_CONFIG 0xB0
#define CMD_SET_LIGHTING 0xB1
// Перезагрузка для входа в бутлоадер // Перезагрузка для входа в бутлоадер
#define CMD_DEVICE_RESET 0xB5 #define CMD_DEVICE_RESET 0xB5
@@ -147,6 +148,27 @@ typedef struct __attribute__((packed)) {
} ConfigBlock_t; } ConfigBlock_t;
typedef struct __attribute__((packed)) {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t brightness;
uint8_t effect;
uint8_t speed;
uint8_t fadeTo;
} LightingRolePacket_t;
typedef struct __attribute__((packed)) {
uint8_t mode;
uint8_t flags;
uint8_t previewSec;
uint8_t reserved;
LightingRolePacket_t roles[5];
} LightingConfigPacket_t;
_Static_assert(sizeof(LightingRolePacket_t) == 7, "Lighting role packet must be 7 bytes");
_Static_assert(sizeof(LightingConfigPacket_t) == 39, "Lighting config packet must be 39 bytes");
// Предварительное объявление структуры протокола // Предварительное объявление структуры протокола
typedef struct SerialControl_t SerialControl_t; typedef struct SerialControl_t SerialControl_t;
+193 -104
View File
@@ -18,16 +18,16 @@ static RGB_t ws2812_led1 = { .R = 0, .G = 0, .B = 0 };
RGB_State_t LED_State; RGB_State_t LED_State;
RGB_Cycle_t LED_Cycle; RGB_Cycle_t LED_Cycle;
RGB_Cycle_t color_estop = { static const RGB_Cycle_t color_estop = {
.Color1 = { .R = 250, .G = 0, .B = 0 }, .Color1 = { .R = 250, .G = 0, .B = 0 },
.Color2 = { .R = 0, .G = 0, .B = 0 }, .Color2 = { .R = 0, .G = 0, .B = 0 },
.Tr = 10, .Tr = 40,
.Th = 5, .Th = 10,
.Tf = 10, .Tf = 40,
.Tl = 5, .Tl = 10,
}; };
RGB_Cycle_t color_unlock = { static const RGB_Cycle_t color_unlock = {
.Color1 = { .R = 255, .G = 0, .B = 0 }, .Color1 = { .R = 255, .G = 0, .B = 0 },
.Color2 = { .R = 0, .G = 0, .B = 0 }, .Color2 = { .R = 0, .G = 0, .B = 0 },
.Tr = 10, .Tr = 10,
@@ -36,138 +36,219 @@ RGB_Cycle_t color_unlock = {
.Tl = 10, .Tl = 10,
}; };
RGB_Cycle_t color_unknown = { /* Same init shimmer as AC22/tkzu22: hue=85, sat breathes 30↔250 (not fade-to-black). */
.Color1 = { .R = 64, .G = 0, .B = 0 }, static uint8_t lighting_init_active;
.Color2 = { .R = 64, .G = 0, .B = 0 },
.Tr = 50, 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;
if (sat == 0U) {
out->R = out->G = out->B = val;
return;
}
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:
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, .Th = 10,
.Tf = 50, .Tf = (uint8_t)((100 - role->speed) < 5 ? 5 : (100 - role->speed)),
.Tl = 0, .Tl = fade_to_black ? 10 : 0,
}; };
RGB_Cycle_t color_light = { if (role->effect == 0) {
.Color1 = { .R = 0, .G = 255, .B = 0 }, cycle.Color2 = cycle.Color1;
.Color2 = { .R = 0, .G = 255, .B = 0 }, } else {
.Tr = 50, cycle.Color2.R = (uint8_t)((uint16_t)cycle.Color1.R * (100 - role->fadeTo) / 100);
.Th = 10, cycle.Color2.G = (uint8_t)((uint16_t)cycle.Color1.G * (100 - role->fadeTo) / 100);
.Tf = 50, cycle.Color2.B = (uint8_t)((uint16_t)cycle.Color1.B * (100 - role->fadeTo) / 100);
.Tl = 0, }
}; return cycle;
}
RGB_Cycle_t color_disabled = { static uint8_t LED_IsLightingConfigValid(const LightingConfigPacket_t *config)
.Color1 = { .R = 250, .G = 0, .B = 0 }, {
.Color2 = { .R = 32, .G = 0, .B = 0 }, if (config == NULL || config->mode > 1 || (config->flags & 0xFE) != 0 ||
.Tr = 50, config->reserved != 0 || ((config->flags & 0x01) && config->previewSec == 0) ||
.Th = 10, (!(config->flags & 0x01) && config->previewSec != 0)) {
.Tf = 50, return 0;
.Tl = 0, }
};
RGB_Cycle_t color_unplugged = { for (uint8_t i = 0; i < 5; i++) {
.Color1 = { .R = 0, .G = 128, .B = 0 }, const LightingRolePacket_t *role = &config->roles[i];
.Color2 = { .R = 0, .G = 128, .B = 0 }, if (role->brightness > 100 || role->effect > 1 || role->speed > 100 || role->fadeTo > 100) {
.Tr = 50, return 0;
.Th = 10, }
.Tf = 50, }
.Tl = 0, return 1;
}; }
RGB_Cycle_t color_preparing = { uint8_t LED_ApplyLightingConfig(const LightingConfigPacket_t *config)
.Color1 = { .R = 0, .G = 0, .B = 255 }, {
.Color2 = { .R = 0, .G = 0, .B = 0 }, if (!LED_IsLightingConfigValid(config)) {
.Tr = 10, return 0;
.Th = 10, }
.Tf = 10,
.Tl = 10,
};
RGB_Cycle_t color_charging = { if (config->flags & 0x01) {
.Color1 = { .R = 0, .G = 255, .B = 0 }, memcpy(&preview_lighting, config, sizeof(preview_lighting));
.Color2 = { .R = 0, .G = 32, .B = 0 }, preview_active = 1;
.Tr = 50, preview_expires_at = HAL_GetTick() + ((uint32_t)config->previewSec * 1000U);
.Th = 10, } else {
.Tf = 50, memcpy(&committed_lighting, config, sizeof(committed_lighting));
.Tl = 0, preview_active = 0;
}; }
return 1;
}
RGB_Cycle_t color_finished = { void LED_Write(void)
.Color1 = { .R = 255, .G = 255, .B = 255 }, {
.Color2 = { .R = 255, .G = 255, .B = 255 }, const LightingConfigPacket_t *lighting = &committed_lighting;
.Tr = 50, RGB_Cycle_t cycle;
.Th = 10,
.Tf = 50,
.Tl = 0,
};
RGB_Cycle_t color_error = { lighting_init_active = 0;
.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.connControl == CMD_STOP) {
if(CONN.chargingError != CONN_NO_ERROR){ LED_SetColor(&color_estop);
LED_SetColor(&color_error); 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; return;
} }
if (CONN.connControl == CMD_FORCE_UNLOCK) { if (CONN.connControl == CMD_FORCE_UNLOCK) {
LED_SetColor(&color_unlock); LED_SetColor(&color_unlock);
return; return;
} }
if(CONN.connControl == CMD_STOP){ if (CONN.connState == Unknown) {
LED_SetColor(&color_estop); lighting_init_active = 1;
return; return;
} }
switch (CONN.connState) { switch (CONN.connState) {
case Unknown:
LED_SetColor(&color_unknown);
break;
case Unplugged: case Unplugged:
LED_SetColor(&color_unplugged); cycle = LED_CycleFromRole(&lighting->roles[0]);
break;
case Disabled:
LED_SetColor(&color_error);
break; break;
case Preparing: case Preparing:
LED_SetColor(&color_preparing);
break;
case AuthRequired: case AuthRequired:
LED_SetColor(&color_preparing); case Replugging:
cycle = LED_CycleFromRole(&lighting->roles[1]);
break; break;
case WaitingForEnergy: case WaitingForEnergy:
LED_SetColor(&color_charging);
break;
case ChargingPausedEV: case ChargingPausedEV:
LED_SetColor(&color_charging);
break;
case ChargingPausedEVSE: case ChargingPausedEVSE:
LED_SetColor(&color_charging);
break;
case Charging: case Charging:
LED_SetColor(&color_charging); cycle = LED_CycleFromRole(&lighting->roles[2]);
break; break;
case AuthTimeout: case AuthTimeout:
LED_SetColor(&color_finished);
break;
case Finished: case Finished:
LED_SetColor(&color_finished);
break;
case FinishedEVSE: case FinishedEVSE:
LED_SetColor(&color_finished);
break;
case FinishedEV: case FinishedEV:
LED_SetColor(&color_finished); cycle = LED_CycleFromRole(&lighting->roles[3]);
break;
case Replugging:
LED_SetColor(&color_preparing);
break; break;
default: default:
LED_SetColor(&color_unknown); lighting_init_active = 1;
break; return;
} }
LED_SetColor(&cycle);
} }
void interpolateColors(RGB_t* color1, RGB_t* color2, uint16_t a, uint16_t b, RGB_t *result) { 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); 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)); memcpy(&LED_Cycle, color, sizeof(RGB_Cycle_t));
} }
@@ -349,6 +430,15 @@ void LED_Task(){
static uint32_t led_tick; static uint32_t led_tick;
if((HAL_GetTick() - led_tick) > 20){ if((HAL_GetTick() - led_tick) > 20){
led_tick = HAL_GetTick(); 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++; LED_State.tick++;
switch(LED_State.state){ switch(LED_State.state){
case LED_RISING: case LED_RISING:
@@ -386,7 +476,6 @@ void LED_Task(){
default: default:
LED_State.state = LED_RISING; LED_State.state = LED_RISING;
} }
LED_Ws2812ErrorBlinkUpdate();
RGB_SetColor(&LED_State.color); RGB_SetColor(&LED_State.color);
WS2812_SetColor(&LED_State.color); WS2812_SetColor(&LED_State.color);
} }
+9
View File
@@ -5,6 +5,7 @@
#include "debug.h" #include "debug.h"
#include "fire_alarm.h" #include "fire_alarm.h"
#include "psu_control.h" #include "psu_control.h"
#include "rgb_controller.h"
#include "usart.h" #include "usart.h"
#include <string.h> #include <string.h>
@@ -58,6 +59,14 @@ void SC_CommandHandler(ReceivedCommand_t* cmd) {
} }
response_code = RESP_FAILED; response_code = RESP_FAILED;
break; 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: case CMD_SET_POWER_LIMIT:
if (cmd->argument_length == 1) { if (cmd->argument_length == 1) {
PSU0.power_limit = ((uint8_t*)cmd->argument)[0] * 1000; PSU0.power_limit = ((uint8_t*)cmd->argument)[0] * 1000;
Binary file not shown.
Binary file not shown.
+3714 -3625
View File
File diff suppressed because it is too large Load Diff
+3713 -3625
View File
File diff suppressed because it is too large Load Diff