diff --git a/radio/src/CMakeLists.txt b/radio/src/CMakeLists.txt index 00c1188be7a..be9af93e302 100644 --- a/radio/src/CMakeLists.txt +++ b/radio/src/CMakeLists.txt @@ -146,6 +146,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) @@ -158,7 +159,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) @@ -615,7 +616,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/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/boards/hw_defs/gx15.json b/radio/src/boards/hw_defs/gx15.json index 9500564bf20..ea489708edf 100644 --- a/radio/src/boards/hw_defs/gx15.json +++ b/radio/src/boards/hw_defs/gx15.json @@ -464,6 +464,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 04d214a4594..7b31d781175 100644 --- a/radio/src/boards/hw_defs/st16.json +++ b/radio/src/boards/hw_defs/st16.json @@ -425,6 +425,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 85fa800d51e..19a991260bf 100644 --- a/radio/src/boards/hw_defs/tx15.json +++ b/radio/src/boards/hw_defs/tx15.json @@ -360,6 +360,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 1fb8f881546..cb127765b86 100644 --- a/radio/src/boards/hw_defs/tx16smk3.json +++ b/radio/src/boards/hw_defs/tx16smk3.json @@ -474,6 +474,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/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..bd4a40e4cb5 100644 --- a/radio/src/lua/api_general.cpp +++ b/radio/src/lua/api_general.cpp @@ -3014,17 +3014,26 @@ 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 +Set the color of a LED, the change is visible after applyRGBLedColors() -@param rvalue: interger, value of red channel +@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 gvalue: interger, value of green channel +@param rvalue: integer, value of red channel -@param bvalue: interger, value of blue channel +@param gvalue: integer, value of green channel -@retval: true if LED index is valid, false otherwise +@param bvalue: integer, value of blue channel + +@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 */ @@ -3051,12 +3060,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 @@ -3134,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 @@ -3307,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 ) @@ -3404,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 @@ -3464,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/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..99b4bd68e1b 100644 --- a/radio/src/tests/lua.cpp +++ b/radio/src/tests/lua.cpp @@ -622,4 +622,65 @@ 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"); +} + +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 dc2b602469f..6dba42e9c6d 100644 --- a/radio/util/hw_defs/hal_settings.jinja +++ b/radio/util/hw_defs/hal_settings.jinja @@ -87,7 +87,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 01929d6bbb4..e51f38e66c2 100644 --- a/radio/util/hw_defs/models.py +++ b/radio/util/hw_defs/models.py @@ -227,10 +227,33 @@ class Touch(BaseModel): gt911_rotation_mode: Optional[int] = None exti: Optional[List[EXTI]] = 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 @@ -253,6 +276,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 diff --git a/tools/hwdef_schema.json b/tools/hwdef_schema.json index 7ccf37a677c..b919473ebed 100644 --- a/tools/hwdef_schema.json +++ b/tools/hwdef_schema.json @@ -296,7 +296,28 @@ "cfs_led_strip_length": { "type": "integer", "minimum": 0 }, "cfs_leds_per_switch": { "type": "integer", "minimum": 0 }, "bling_led_strip_start": { "type": "integer", "minimum": 0 }, - "bling_led_strip_length": { "type": "integer", "minimum": 0 } + "bling_led_strip_length": { "type": "integer", "minimum": 0 }, + "bling_groups": { + "type": "array", + "items": { "$ref": "#/$defs/led_group" } + } + } + }, + + "led_group": { + "type": "object", + "additionalProperties": false, + "required": ["name", "first", "count"], + "dependentRequired": { + "start_angle": ["direction"], + "direction": ["start_angle"] + }, + "properties": { + "name": { "type": "string" }, + "first": { "type": "integer", "minimum": 0 }, + "count": { "type": "integer", "minimum": 1 }, + "start_angle": { "type": "integer" }, + "direction": { "enum": [-1, 1] } } },