From 0c57525d66cf65408487e4e036faaaf7eacc2857 Mon Sep 17 00:00:00 2001 From: Peter Feerick <5500713+pfeerick@users.noreply.github.com> Date: Mon, 5 Oct 2026 04:39:59 +0000 Subject: [PATCH 1/2] fix(lua): custom switch LEDs mapped wrongly on PA01 setRGBLedColor() assumed the custom switch LEDs follow each other in switch order from CFS_LED_STRIP_START. PA01 maps them through ledMapping = {4, 6, 0, 2}, so a script checked whether one switch was set to NONE but then wrote the LEDs of another, configured, switch. fsGetLedRGB() made the same assumption, so the custom switch diagnostics page showed the wrong colours on PA01, and getFSLedState() ignored the strip offset altogether. Add a weak fsLedFirstIndex() hook giving the first strip LED of a custom switch, and use it for every custom switch LED access: fsLedRGB(), fsGetLedRGB(), getFSLedState() and setRGBLedColor(). PA01 overrides it with its mapping. The generic fsLedRGB() now handles CFS_LEDS_PER_SWITCH, so ST16's copy of the driver is removed. Also check the switchGetSwitchFromCustomIdx() result before using it, and fix the setRGBLedColor() parameter names in the luadoc. Co-Authored-By: Claude Opus 5.5 --- radio/src/boards/generic_stm32/led_driver.cpp | 21 ++++++++- radio/src/hal/rgbleds.cpp | 2 +- radio/src/lua/api_general.cpp | 16 ++++--- radio/src/switches.h | 1 + radio/src/targets/pa01/led_driver.cpp | 11 +++-- radio/src/targets/simu/led_driver.cpp | 19 +++++++- radio/src/targets/st16/CMakeLists.txt | 1 - radio/src/targets/st16/led_driver.cpp | 45 ------------------- radio/src/tests/lua.cpp | 39 ++++++++++++++++ 9 files changed, 94 insertions(+), 61 deletions(-) delete mode 100644 radio/src/targets/st16/led_driver.cpp diff --git a/radio/src/boards/generic_stm32/led_driver.cpp b/radio/src/boards/generic_stm32/led_driver.cpp index d2596efe4ba..ed782692124 100644 --- a/radio/src/boards/generic_stm32/led_driver.cpp +++ b/radio/src/boards/generic_stm32/led_driver.cpp @@ -68,9 +68,26 @@ __weak void ledInit() } #if defined(FUNCTION_SWITCHES_RGB_LEDS) +#if !defined(CFS_LED_STRIP_START) + #define CFS_LED_STRIP_START 0 +#endif +#if !defined(CFS_LEDS_PER_SWITCH) + #define CFS_LEDS_PER_SWITCH 1 +#endif + +// First strip LED of a custom switch, boards with a non-linear layout +// override this +__weak uint8_t fsLedFirstIndex(uint8_t index) +{ + return CFS_LED_STRIP_START + index * CFS_LEDS_PER_SWITCH; +} + __weak void fsLedRGB(uint8_t index, uint32_t color) { - rgbSetLedColor(index, GET_RED(color), GET_GREEN(color), GET_BLUE(color)); + uint8_t led = fsLedFirstIndex(index); + for (uint8_t i = 0; i < CFS_LEDS_PER_SWITCH; i++) { + rgbSetLedColor(led + i, GET_RED(color), GET_GREEN(color), GET_BLUE(color)); + } rgbLedColorApply(); } @@ -145,6 +162,6 @@ __weak void ledBlue() #if defined(FUNCTION_SWITCHES_RGB_LEDS) __weak uint32_t fsGetLedRGB(uint8_t index) { - return rgbGetLedColor(index + CFS_LED_STRIP_START); + return rgbGetLedColor(fsLedFirstIndex(index)); } #endif diff --git a/radio/src/hal/rgbleds.cpp b/radio/src/hal/rgbleds.cpp index a2268bff43d..13ae26ce94e 100644 --- a/radio/src/hal/rgbleds.cpp +++ b/radio/src/hal/rgbleds.cpp @@ -77,7 +77,7 @@ void setFSLedON(uint8_t index) { bool getFSLedState(uint8_t index) { uint8_t cfsIdx = switchGetCustomSwitchIdx(index); - return rgbGetLedColor(cfsIdx) == g_model.getSwitchOnColor(index).getColor(); + return fsGetLedRGB(cfsIdx) == g_model.getSwitchOnColor(index).getColor(); } uint32_t getFSLedRGBColor(uint8_t index) diff --git a/radio/src/lua/api_general.cpp b/radio/src/lua/api_general.cpp index 7a57797403c..1c5daabe704 100644 --- a/radio/src/lua/api_general.cpp +++ b/radio/src/lua/api_general.cpp @@ -3014,15 +3014,15 @@ static int luaGetTrainerStatus(lua_State * L) #if (BLING_LED_STRIP_LENGTH > 0) || (CFS_LED_STRIP_LENGTH > 0) /*luadoc -@function setRGBLedColor(id, rvalue, bvalue, cvalue) +@function setRGBLedColor(id, rvalue, gvalue, bvalue) @param id: integer identifying a led in the led chain -@param rvalue: interger, value of red channel +@param rvalue: integer, value of red channel -@param gvalue: interger, value of green channel +@param gvalue: integer, value of green channel -@param bvalue: interger, value of blue channel +@param bvalue: integer, value of blue channel @retval: true if LED index is valid, false otherwise @@ -3051,12 +3051,14 @@ static int luaSetRgbLedColor(lua_State * L) } id -= BLING_LED_STRIP_LENGTH; #endif - uint8_t swIdx = switchGetSwitchFromCustomIdx(id / CFS_LEDS_PER_SWITCH); - if (g_model.getSwitchType(swIdx) != SWITCH_NONE) { + uint8_t cfsIdx = id / CFS_LEDS_PER_SWITCH; + uint8_t swIdx = switchGetSwitchFromCustomIdx(cfsIdx); + if (swIdx >= switchGetMaxSwitches() || + g_model.getSwitchType(swIdx) != SWITCH_NONE) { lua_pushboolean(L, false); return 1; } - rgbSetLedColor(id + CFS_LED_STRIP_START, r, g, b); + rgbSetLedColor(fsLedFirstIndex(cfsIdx) + id % CFS_LEDS_PER_SWITCH, r, g, b); #else rgbSetLedColor(id + BLING_LED_STRIP_START, r, g, b); #endif diff --git a/radio/src/switches.h b/radio/src/switches.h index d29a4cff5be..4c1d830f0da 100644 --- a/radio/src/switches.h +++ b/radio/src/switches.h @@ -103,6 +103,7 @@ void fsLedOn(uint8_t index); bool fsLedState(uint8_t index); void fsLedRGB(uint8_t index, uint32_t color); uint32_t fsGetLedRGB(uint8_t index); +uint8_t fsLedFirstIndex(uint8_t index); uint8_t getRGBColorIndex(uint32_t color); #endif diff --git a/radio/src/targets/pa01/led_driver.cpp b/radio/src/targets/pa01/led_driver.cpp index 449d6e437ae..77ee3e94815 100644 --- a/radio/src/targets/pa01/led_driver.cpp +++ b/radio/src/targets/pa01/led_driver.cpp @@ -31,13 +31,18 @@ #define GET_BLUE(color) (((color) & 0xF8)) // used to map switch number to led number in the rgbled chain -uint8_t ledMapping[] = {4, 6, 0, 2}; +static const uint8_t ledMapping[] = {4, 6, 0, 2}; + +uint8_t fsLedFirstIndex(uint8_t index) +{ + return ledMapping[index]; +} void fsLedRGB(uint8_t index, uint32_t color) { - rgbSetLedColor(ledMapping[index], GET_RED(color), + rgbSetLedColor(fsLedFirstIndex(index), GET_RED(color), GET_GREEN(color),GET_BLUE(color)); - rgbSetLedColor(ledMapping[index]+1, GET_RED(color), + rgbSetLedColor(fsLedFirstIndex(index)+1, GET_RED(color), GET_GREEN(color),GET_BLUE(color)); rgbLedColorApply(); } diff --git a/radio/src/targets/simu/led_driver.cpp b/radio/src/targets/simu/led_driver.cpp index 7433c0829ba..f92ccb3c1dc 100644 --- a/radio/src/targets/simu/led_driver.cpp +++ b/radio/src/targets/simu/led_driver.cpp @@ -77,14 +77,29 @@ uint32_t rgbGetLedColor(uint8_t led) return (pixel[1] << 16) + (pixel[0] << 8) + pixel[2]; } +#if !defined(CFS_LED_STRIP_START) + #define CFS_LED_STRIP_START 0 +#endif +#if !defined(CFS_LEDS_PER_SWITCH) + #define CFS_LEDS_PER_SWITCH 1 +#endif + +uint8_t fsLedFirstIndex(uint8_t index) +{ + return CFS_LED_STRIP_START + index * CFS_LEDS_PER_SWITCH; +} + void fsLedRGB(uint8_t idx, uint32_t color) { - rgbSetLedColor(idx, (color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF); + uint8_t led = fsLedFirstIndex(idx); + for (uint8_t i = 0; i < CFS_LEDS_PER_SWITCH; i++) { + rgbSetLedColor(led + i, (color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF); + } } uint32_t fsGetLedRGB(uint8_t index) { - return rgbGetLedColor(index); + return rgbGetLedColor(fsLedFirstIndex(index)); } void fsLedOn(uint8_t idx) diff --git a/radio/src/targets/st16/CMakeLists.txt b/radio/src/targets/st16/CMakeLists.txt index 9d0a56ab2eb..6d20ed5c734 100644 --- a/radio/src/targets/st16/CMakeLists.txt +++ b/radio/src/targets/st16/CMakeLists.txt @@ -149,7 +149,6 @@ set(CMSIS_SRC ${TARGET_SRC_DIR}/system_clock.c) # Firmware board library add_library(board OBJECT EXCLUDE_FROM_ALL ${BOARD_COMMON_SRC} - ${TARGET_SRC_DIR}/led_driver.cpp ${TARGET_SRC_DIR}/touch_driver.cpp ${TARGET_SRC_DIR}/battery_driver.cpp targets/common/arm/stm32/heartbeat_driver.cpp diff --git a/radio/src/targets/st16/led_driver.cpp b/radio/src/targets/st16/led_driver.cpp deleted file mode 100644 index ffdee2600d2..00000000000 --- a/radio/src/targets/st16/led_driver.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) EdgeTX - * - * Based on code named - * opentx - https://github.com/opentx/opentx - * th9x - http://code.google.com/p/th9x - * er9x - http://code.google.com/p/er9x - * gruvin9x - http://code.google.com/p/gruvin9x - * - * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include "hal/gpio.h" -#include "hal/rgbleds.h" -#include "stm32_gpio.h" -#include "boards/generic_stm32/rgb_leds.h" -#include "board.h" - -#define GET_RED(color) (((color) & 0xFF0000) >>16) -#define GET_GREEN(color) (((color) & 0x00FF00) >> 8) -#define GET_BLUE(color) ((color) & 0x0000FF) - - -void fsLedRGB(uint8_t index, uint32_t color) -{ - index = (index * 2) + CFS_LED_STRIP_START; - rgbSetLedColor(index, GET_RED(color), GET_GREEN(color),GET_BLUE(color)); - rgbSetLedColor(index+1, GET_RED(color), GET_GREEN(color),GET_BLUE(color)); - rgbLedColorApply(); -} - -uint32_t fsGetLedRGB(uint8_t index) -{ - return rgbGetLedColor((index * 2) + CFS_LED_STRIP_START); -} - diff --git a/radio/src/tests/lua.cpp b/radio/src/tests/lua.cpp index c78837f9e2c..6b5e467888e 100644 --- a/radio/src/tests/lua.cpp +++ b/radio/src/tests/lua.cpp @@ -622,4 +622,43 @@ TEST(Lua, testUserDataEmbeddedNul) luaExecStr(userdata_nul_tst); } +#if defined(LED_STRIP_LENGTH) +#include "boards/generic_stm32/rgb_leds.h" + +#if !defined(BLING_LED_STRIP_LENGTH) + #define BLING_LED_STRIP_START 0 + #define BLING_LED_STRIP_LENGTH 0 +#endif +#if !defined(CFS_LED_STRIP_LENGTH) + #define CFS_LED_STRIP_LENGTH 0 +#endif + +TEST(Lua, RGBLedIndexes) +{ + MODEL_RESET(); + +#if BLING_LED_STRIP_LENGTH > 0 + luaExecStr("if not setRGBLedColor(0, 4, 5, 6) then error('bling') end"); + EXPECT_EQ(0x040506u, rgbGetLedColor(BLING_LED_STRIP_START)); +#endif + +#if CFS_LED_STRIP_LENGTH > 0 + // Lua index of the first LED of the first custom switch + const std::string cfs = std::to_string(BLING_LED_STRIP_LENGTH); + uint8_t sw = switchGetSwitchFromCustomIdx(0); + ASSERT_LT(sw, switchGetMaxSwitches()); + + g_model.setSwitchType(sw, SWITCH_NONE); + luaExecStr(("if not setRGBLedColor(" + cfs + ", 1, 2, 3) then error('unused cfs') end").c_str()); + EXPECT_EQ(0x010203u, fsGetLedRGB(0)); + + g_model.setSwitchType(sw, SWITCH_TOGGLE); + luaExecStr(("if setRGBLedColor(" + cfs + ", 7, 8, 9) then error('used cfs') end").c_str()); + EXPECT_EQ(0x010203u, fsGetLedRGB(0)); +#endif + + luaExecStr("if setRGBLedColor(LED_STRIP_LENGTH, 1, 2, 3) then error('out of range') end"); +} +#endif + #endif // #if defined(LUA) From 40d32794411723bc76043325c14b7df59fb1e6ad Mon Sep 17 00:00:00 2001 From: Peter Feerick <5500713+pfeerick@users.noreply.github.com> Date: Mon, 5 Oct 2026 04:44:24 +0000 Subject: [PATCH 2/2] feat(lua): describe the RGB LED layout to scripts LED_STRIP_LENGTH counts the decorative LEDs followed by the custom switch LEDs, which scripts can only set while that switch is set to NONE. Scripts had no way to tell the two ranges apart, or to find where the gimbal ring LEDs are without a hard-coded per-radio table. Without changing the meaning of LED_STRIP_LENGTH or setRGBLedColor(), add: - BLING_LED_STRIP_LENGTH, the number of decorative LEDs - FUNC_RGB_LED, missing from the Lua constants until now - getRGBLedInfo(), returning the strip length, the decorative and custom switch LED ranges, and named groups of decorative LEDs with their ring geometry The groups come from a new optional leds.bling_groups list in the hw_defs JSON, generated into lua_leds.inc, and are populated for the TX15, TX16S Mk3, GX15 and ST16 gimbal rings. Co-Authored-By: Claude Opus 5.5 --- radio/src/CMakeLists.txt | 5 +- radio/src/boards/hw_defs/gx15.json | 4 ++ radio/src/boards/hw_defs/st16.json | 4 ++ radio/src/boards/hw_defs/tx15.json | 4 ++ radio/src/boards/hw_defs/tx16smk3.json | 4 ++ radio/src/lua/api_general.cpp | 83 +++++++++++++++++++++++++- radio/src/tests/lua.cpp | 22 +++++++ radio/util/hw_defs/hal_settings.jinja | 2 +- radio/util/hw_defs/lua_leds.jinja | 12 ++++ radio/util/hw_defs/models.py | 34 +++++++++++ 10 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 radio/util/hw_defs/lua_leds.jinja diff --git a/radio/src/CMakeLists.txt b/radio/src/CMakeLists.txt index f61088f389c..ab5f8a18de8 100644 --- a/radio/src/CMakeLists.txt +++ b/radio/src/CMakeLists.txt @@ -144,6 +144,7 @@ AddHWGenTarget(${HW_DESC_JSON} yaml_inputs yaml_inputs.inc) AddHWGenTarget(${HW_DESC_JSON} lua_inputs lua_inputs.inc) AddHWGenTarget(${HW_DESC_JSON} lua_mixsrc lua_mixsrc.inc) AddHWGenTarget(${HW_DESC_JSON} lua_keys lua_keys.inc) +AddHWGenTarget(${HW_DESC_JSON} lua_leds lua_leds.inc) AddHWGenTarget(${HW_DESC_JSON} stm32_keys stm32_keys.inc) AddHWGenTarget(${HW_DESC_JSON} stm32_switches stm32_switches.inc) @@ -156,7 +157,7 @@ AddHWGenTarget(${HW_DESC_JSON} simu_switches simu_switches.inc) AddHWGenTarget(${HW_DESC_JSON} hal_keys_lock hal_keys_lock.h) AddHWGenTarget(${HW_DESC_JSON} hal_usb_descriptor usb_descriptor.h) -add_dependencies(board hal_settings hal_pwm_sticks hal_keys hal_keys_lock hal_adc_inputs stm32_keys stm32_switches stm32_adc_inputs stm32_pwm_inputs yaml_inputs lua_inputs lua_mixsrc lua_keys) +add_dependencies(board hal_settings hal_pwm_sticks hal_keys hal_keys_lock hal_adc_inputs stm32_keys stm32_switches stm32_adc_inputs stm32_pwm_inputs yaml_inputs lua_inputs lua_mixsrc lua_keys lua_leds) add_dependencies(board_bl hal_settings hal_pwm_sticks hal_keys stm32_keys) @@ -612,7 +613,7 @@ if(NATIVE_BUILD) ) add_dependencies(radiolib_native ${RADIO_DEPENDENCIES} hal_settings hal_pwm_sticks hal_keys hal_keys_lock hal_adc_inputs - yaml_inputs lua_inputs lua_mixsrc lua_keys) + yaml_inputs lua_inputs lua_mixsrc lua_keys lua_leds) set_property(TARGET radiolib_native PROPERTY POSITION_INDEPENDENT_CODE ON) add_subdirectory(targets/simu) diff --git a/radio/src/boards/hw_defs/gx15.json b/radio/src/boards/hw_defs/gx15.json index 6b16dcbfa72..b432ff10bae 100644 --- a/radio/src/boards/hw_defs/gx15.json +++ b/radio/src/boards/hw_defs/gx15.json @@ -453,6 +453,10 @@ "led_strip_length": 26, "bling_led_strip_start": 6, "bling_led_strip_length": 20, + "bling_groups": [ + { "name": "gimbal_right", "first": 0, "count": 10, "start_angle": 190, "direction": -1 }, + { "name": "gimbal_left", "first": 10, "count": 10, "start_angle": 350, "direction": 1 } + ], "cfs_led_strip_start": 0, "cfs_led_strip_length": 6, "cfs_leds_per_switch": 1, diff --git a/radio/src/boards/hw_defs/st16.json b/radio/src/boards/hw_defs/st16.json index 77a2785233c..cf3ba679613 100644 --- a/radio/src/boards/hw_defs/st16.json +++ b/radio/src/boards/hw_defs/st16.json @@ -408,6 +408,10 @@ "led_strip_length": 24, "bling_led_strip_start": 0, "bling_led_strip_length": 12, + "bling_groups": [ + { "name": "gimbal_right", "first": 6, "count": 6, "start_angle": 30, "direction": -1 }, + { "name": "gimbal_left", "first": 0, "count": 6, "start_angle": 330, "direction": -1 } + ], "cfs_led_strip_start": 12, "cfs_led_strip_length": 12, "cfs_leds_per_switch": 2, diff --git a/radio/src/boards/hw_defs/tx15.json b/radio/src/boards/hw_defs/tx15.json index b051f6d5e51..aeb9d558a7f 100644 --- a/radio/src/boards/hw_defs/tx15.json +++ b/radio/src/boards/hw_defs/tx15.json @@ -349,6 +349,10 @@ "led_strip_length": 26, "bling_led_strip_start": 6, "bling_led_strip_length": 20, + "bling_groups": [ + { "name": "gimbal_right", "first": 0, "count": 10, "start_angle": 190, "direction": -1 }, + { "name": "gimbal_left", "first": 10, "count": 10, "start_angle": 10, "direction": -1 } + ], "cfs_led_strip_start": 0, "cfs_led_strip_length": 6, "cfs_leds_per_switch": 1, diff --git a/radio/src/boards/hw_defs/tx16smk3.json b/radio/src/boards/hw_defs/tx16smk3.json index 7eff9231a38..2fbf6329a63 100644 --- a/radio/src/boards/hw_defs/tx16smk3.json +++ b/radio/src/boards/hw_defs/tx16smk3.json @@ -463,6 +463,10 @@ "led_strip_length": 26, "bling_led_strip_start": 6, "bling_led_strip_length": 20, + "bling_groups": [ + { "name": "gimbal_right", "first": 0, "count": 10, "start_angle": 190, "direction": -1 }, + { "name": "gimbal_left", "first": 10, "count": 10, "start_angle": 350, "direction": 1 } + ], "cfs_led_strip_start": 0, "cfs_led_strip_length": 6, "cfs_leds_per_switch": 1, diff --git a/radio/src/lua/api_general.cpp b/radio/src/lua/api_general.cpp index 1c5daabe704..bd4a40e4cb5 100644 --- a/radio/src/lua/api_general.cpp +++ b/radio/src/lua/api_general.cpp @@ -3016,7 +3016,15 @@ static int luaGetTrainerStatus(lua_State * L) /*luadoc @function setRGBLedColor(id, rvalue, gvalue, bvalue) -@param id: integer identifying a led in the led chain +Set the color of a LED, the change is visible after applyRGBLedColors() + +@param id: integer LED index, 0 to LED_STRIP_LENGTH - 1. + Indexes 0 to BLING_LED_STRIP_LENGTH - 1 are the decorative ('bling') LEDs. + The remaining indexes are the custom switch LEDs, which can only be set + while that custom switch is set to NONE (use setCFSLedColor() for the + others). Scripts that only want the decorative LEDs, and still run on older + firmware, can loop to `(BLING_LED_STRIP_LENGTH or LED_STRIP_LENGTH) - 1`. + See getRGBLedInfo() for how the LEDs are grouped. @param rvalue: integer, value of red channel @@ -3024,7 +3032,8 @@ static int luaGetTrainerStatus(lua_State * L) @param bvalue: integer, value of blue channel -@retval: true if LED index is valid, false otherwise +@retval: true if the LED was set, false if the index is out of range or the + LED belongs to a custom switch that is in use @status current Introduced in 2.10 */ @@ -3136,6 +3145,73 @@ static int luaApplyRGBLedColors(lua_State * L) rgbLedColorApply(); return 1; } + +struct LuaLedGroup { + const char* name; + uint8_t first; + uint8_t count; + int16_t startAngle; + int8_t direction; // 0 when the group is not a ring +}; + +#include "lua_leds.inc" + +/*luadoc +@function getRGBLedInfo() + +Describe the LEDs that setRGBLedColor() can address + +@retval table with the following fields: + * `length` (number) total number of LEDs, same as LED_STRIP_LENGTH + * `bling` (number) number of decorative LEDs, same as BLING_LED_STRIP_LENGTH. + They use indexes 0 to `bling` - 1 + * `cfs` (table) custom switch LEDs, only present if the radio has them: + * `first` (number) index of the first custom switch LED + * `count` (number) number of custom switch LEDs + * `perSwitch` (number) number of LEDs for each custom switch + * `groups` (table) groups of decorative LEDs indexed by name + (e.g. `gimbal_left`, `gimbal_right`), empty if none are known. Each group has: + * `first` (number) index of the first LED of the group + * `count` (number) number of LEDs in the group + * `startAngle` (number) only for an evenly spaced ring, angle of the + first LED in degrees (0 = right, 90 = up) + * `direction` (number) only for an evenly spaced ring, 1 if the following + LEDs go counter clockwise, -1 if they go clockwise + +@status current Introduced in 3.0.0 +*/ +static int luaGetRGBLedInfo(lua_State * L) +{ + lua_newtable(L); + lua_pushtableinteger(L, "length", BLING_LED_STRIP_LENGTH + CFS_LED_STRIP_LENGTH); + lua_pushtableinteger(L, "bling", BLING_LED_STRIP_LENGTH); + +#if CFS_LED_STRIP_LENGTH > 0 + lua_pushstring(L, "cfs"); + lua_newtable(L); + lua_pushtableinteger(L, "first", BLING_LED_STRIP_LENGTH); + lua_pushtableinteger(L, "count", CFS_LED_STRIP_LENGTH); + lua_pushtableinteger(L, "perSwitch", CFS_LEDS_PER_SWITCH); + lua_settable(L, -3); +#endif + + lua_pushstring(L, "groups"); + lua_newtable(L); + for (const LuaLedGroup* group = luaLedGroups; group->name; group++) { + lua_pushstring(L, group->name); + lua_newtable(L); + lua_pushtableinteger(L, "first", group->first); + lua_pushtableinteger(L, "count", group->count); + if (group->direction) { + lua_pushtableinteger(L, "startAngle", group->startAngle); + lua_pushtableinteger(L, "direction", group->direction); + } + lua_settable(L, -3); + } + lua_settable(L, -3); + + return 1; +} #endif @@ -3309,6 +3385,7 @@ LROT_BEGIN(etxlib, NULL, 0) #if (BLING_LED_STRIP_LENGTH > 0) || (CFS_LED_STRIP_LENGTH > 0) LROT_FUNCENTRY( setRGBLedColor, luaSetRgbLedColor ) LROT_FUNCENTRY( applyRGBLedColors, luaApplyRGBLedColors ) + LROT_FUNCENTRY( getRGBLedInfo, luaGetRGBLedInfo ) #endif #if (CFS_LED_STRIP_LENGTH > 0) LROT_FUNCENTRY( setCFSLedColor, luaSetCFSLedColor ) @@ -3406,6 +3483,7 @@ LROT_BEGIN(etxcst, NULL, 0) LROT_NUMENTRY( FUNC_BACKLIGHT, FUNC_BACKLIGHT ) LROT_NUMENTRY( FUNC_SCREENSHOT, FUNC_SCREENSHOT ) LROT_NUMENTRY( FUNC_RACING_MODE, FUNC_RACING_MODE ) + LROT_NUMENTRY( FUNC_RGB_LED, FUNC_RGB_LED ) #if defined(FUNCTION_SWITCHES) LROT_NUMENTRY( FUNC_PUSH_CUST_SWITCH, FUNC_PUSH_CUST_SWITCH ) #endif @@ -3466,6 +3544,7 @@ LROT_BEGIN(etxcst, NULL, 0) LROT_NUMENTRY( TIMEHOUR, TIMEHOUR ) #if (BLING_LED_STRIP_LENGTH > 0) || (CFS_LED_STRIP_LENGTH > 0) LROT_NUMENTRY( LED_STRIP_LENGTH, BLING_LED_STRIP_LENGTH + CFS_LED_STRIP_LENGTH ) + LROT_NUMENTRY( BLING_LED_STRIP_LENGTH, BLING_LED_STRIP_LENGTH ) #endif LROT_NUMENTRY( UNIT_RAW, UNIT_RAW ) LROT_NUMENTRY( UNIT_VOLTS, UNIT_VOLTS ) diff --git a/radio/src/tests/lua.cpp b/radio/src/tests/lua.cpp index 6b5e467888e..99b4bd68e1b 100644 --- a/radio/src/tests/lua.cpp +++ b/radio/src/tests/lua.cpp @@ -659,6 +659,28 @@ TEST(Lua, RGBLedIndexes) luaExecStr("if setRGBLedColor(LED_STRIP_LENGTH, 1, 2, 3) then error('out of range') end"); } + +TEST(Lua, RGBLedInfo) +{ + luaExecStr("if FUNC_RGB_LED == nil then error('FUNC_RGB_LED') end"); + luaExecStr("info = getRGBLedInfo()"); + luaExecStr("if info.length ~= LED_STRIP_LENGTH then error('length') end"); + luaExecStr("if info.bling ~= BLING_LED_STRIP_LENGTH then error('bling') end"); + luaExecStr( + "for name, g in pairs(info.groups) do\n" + " if g.first < 0 or g.count <= 0 or g.first + g.count > info.bling then\n" + " error(name)\n" + " end\n" + "end"); +#if CFS_LED_STRIP_LENGTH > 0 + luaExecStr( + "if info.cfs.first ~= info.bling or\n" + " info.cfs.first + info.cfs.count ~= info.length or\n" + " info.cfs.perSwitch < 1 then error('cfs') end"); +#else + luaExecStr("if info.cfs ~= nil then error('cfs') end"); +#endif +} #endif #endif // #if defined(LUA) diff --git a/radio/util/hw_defs/hal_settings.jinja b/radio/util/hw_defs/hal_settings.jinja index 9ef47ed1fa0..0ed03710964 100644 --- a/radio/util/hw_defs/hal_settings.jinja +++ b/radio/util/hw_defs/hal_settings.jinja @@ -60,7 +60,7 @@ // LEDS {% for name, val in leds %} -{% if val is not none %} +{% if val is not none and (val is string or val is not iterable) %} {% if val is sameas true or val is sameas false %} #define {{ name | upper }} {{ val | tojson }} {% elif name.endswith('_irqn') %} diff --git a/radio/util/hw_defs/lua_leds.jinja b/radio/util/hw_defs/lua_leds.jinja new file mode 100644 index 00000000000..6d5f3aee441 --- /dev/null +++ b/radio/util/hw_defs/lua_leds.jinja @@ -0,0 +1,12 @@ +// +// WARNING: DO NOT EDIT THIS FILE +// This file has been generated from the target's JSON hardware description +// + +{% set groups = (leds.bling_groups if leds else none) or [] %} +static const LuaLedGroup luaLedGroups[] = { +{% for group in groups %} + { "{{ group.name }}", {{ group.first }}, {{ group.count }}, {{ group.start_angle if group.start_angle is not none else -1 }}, {{ group.direction or 0 }} }, +{% endfor %} + { nullptr, 0, 0, -1, 0 } +}; diff --git a/radio/util/hw_defs/models.py b/radio/util/hw_defs/models.py index 03169c95af5..c4b1da9c68f 100644 --- a/radio/util/hw_defs/models.py +++ b/radio/util/hw_defs/models.py @@ -204,10 +204,32 @@ class Display(BaseModel): ltdc_irq_prio: Optional[int] = None dma_screen_irq_prio: Optional[int] = None +class LedGroup(BaseModel): + # e.g. 'gimbal_left', exposed to Lua by getRGBLedInfo() + name: str + # first LED, as a Lua setRGBLedColor() index (0 = first bling LED) + first: int + count: int + # evenly spaced ring: angle of the first LED in degrees (0 = right, + # 90 = up), following LEDs go counter clockwise (1) or clockwise (-1) + start_angle: Optional[int] = None + direction: Optional[Literal[-1, 1]] = None + + @model_validator(mode="after") + def check_ring(self: "LedGroup") -> "LedGroup": + if (self.start_angle is None) != (self.direction is None): + raise PydanticCustomError( + "LedGroupError", + "LED group needs both 'start_angle' and 'direction', or neither", + ) + return self + + class LEDS(BaseModel): led_strip_length: Optional[int] = None bling_led_strip_start: Optional[int] = None bling_led_strip_length: Optional[int] = None + bling_groups: Optional[List[LedGroup]] = None cfs_led_strip_start: Optional[int] = None cfs_led_strip_length: Optional[int] = None cfs_leds_per_switch: Optional[int] = None @@ -230,6 +252,18 @@ class LEDS(BaseModel): led_green_gpio: Optional[str] = None led_blue_gpio: Optional[str] = None + @model_validator(mode="after") + def check_bling_groups(self: "LEDS") -> "LEDS": + for group in self.bling_groups or []: + if group.first < 0 or group.count <= 0 or \ + group.first + group.count > (self.bling_led_strip_length or 0): + raise PydanticCustomError( + "LedGroupError", + "LED group '{name}' is outside the bling LEDs", + {"name": group.name}, + ) + return self + class IMU(BaseModel): imu_i2c_bus: Optional[str] = None imu_int_gpio: Optional[str] = None