From 7addae1a1e48af352845253334d3c40c40743840 Mon Sep 17 00:00:00 2001 From: "David B. Bitton" Date: Thu, 27 Aug 2026 10:59:21 -0400 Subject: [PATCH 1/2] Use decorative (bling) LED count for animations Prefer BLING_LED_STRIP_LENGTH when firmware provides it. On current builds that still report 26 (20 gimbal + 6 CFS) on TX16S Mk3 / GX15 / TX15, fall back to 20 so dual-ring math stays 10+10. Use FUNC_RGB_LED from firmware when present instead of hardcoding 25. Function-switch RGB colours stay under Radio/Model settings. --- README.md | 1 + SCRIPTS/RGBLED/rgbk.lua | 17 ++++++++++++++++- SCRIPTS/TOOLS/RGB.lua | 9 +++++---- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a2241df..97ad2bd 100644 --- a/README.md +++ b/README.md @@ -47,4 +47,5 @@ RGB LED control tool for EdgeTX 2.11+ radios. ## Notes - Requires EdgeTX 2.11 or newer. +- Animates decorative gimbal / "bling" LEDs only (20 LEDs on TX16S Mk3, GX15 and TX15). Custom function switch RGB colours stay under Radio/Model settings. - The keeper polls `/SCRIPTS/TOOLS/RGB.dat` and runs the selected mode, so no other mode scripts are needed. diff --git a/SCRIPTS/RGBLED/rgbk.lua b/SCRIPTS/RGBLED/rgbk.lua index acd617f..f9f44e0 100644 --- a/SCRIPTS/RGBLED/rgbk.lua +++ b/SCRIPTS/RGBLED/rgbk.lua @@ -15,7 +15,22 @@ -- on-screen preview matches exactly what the keeper runs on the radio. local CFG = "/SCRIPTS/TOOLS/RGB.dat" -local N = LED_STRIP_LENGTH or 0 + +-- Decorative ("bling") LEDs only. Firmware maps Lua index 0 to the first +-- gimbal/bling LED. Older builds reported LED_STRIP_LENGTH as bling+CFS +-- (26 on TX16S Mk3 / GX15 / TX15). Function-switch LEDs stay under model +-- on/off colours / setCFSLedColor() and must not be painted here. +local function decorativeLength() + if type(BLING_LED_STRIP_LENGTH) == "number" and BLING_LED_STRIP_LENGTH > 0 then + return BLING_LED_STRIP_LENGTH + end + local n = LED_STRIP_LENGTH or 0 + -- 20 bling + 6 CFS: the dual-ring radios this tool was written for. + if n == 26 then return 20 end + return n +end + +local N = decorativeLength() local HALF = math.floor(N / 2) local curSel = nil diff --git a/SCRIPTS/TOOLS/RGB.lua b/SCRIPTS/TOOLS/RGB.lua index e49e91b..b88f2ca 100644 --- a/SCRIPTS/TOOLS/RGB.lua +++ b/SCRIPTS/TOOLS/RGB.lua @@ -3,7 +3,8 @@ local LED_DIR = "/SCRIPTS/RGBLED" local CFG_FILE = "/SCRIPTS/TOOLS/RGB.dat" -- Native "RGB Led" special function: loads .lua from /SCRIPTS/RGBLED/ -local FUNC_RGB_LED = 25 +-- Prefer the firmware constant so a future enum shift does not break setup. +local RGB_LED_FUNC = (type(FUNC_RGB_LED) == "number") and FUNC_RGB_LED or 25 -- Fixed Special Function slot for the keeper script (SF64 = index 63) local SF_SLOT = 63 local KEEPER_NAME = "rgbk" @@ -156,7 +157,7 @@ end local function keeperInstalled() if type(model.getCustomFunction) ~= "function" then return false end local cur = model.getCustomFunction(SF_SLOT) - return cur ~= nil and cur.func == FUNC_RGB_LED and cur.name == KEEPER_NAME and cur.active == 1 + return cur ~= nil and cur.func == RGB_LED_FUNC and cur.name == KEEPER_NAME and cur.active == 1 end -- Install the keeper Special Function once. After it exists (and is enabled) @@ -169,7 +170,7 @@ local function ensureKeeper() return end local cur = model.getCustomFunction(SF_SLOT) - local ours = cur ~= nil and cur.func == FUNC_RGB_LED and cur.name == KEEPER_NAME + local ours = cur ~= nil and cur.func == RGB_LED_FUNC and cur.name == KEEPER_NAME if cur ~= nil and type(cur.func) == "number" and cur.func ~= 0 and not ours then lvgl.message({ title = "SF64 in use", message = "Special Function SF64 is already assigned to a different function. Free it to let the RGB LED tool use it." }) @@ -182,7 +183,7 @@ local function ensureKeeper() end model.setCustomFunction(SF_SLOT, { switch = onSwitch, - func = FUNC_RGB_LED, + func = RGB_LED_FUNC, name = KEEPER_NAME, active = 1, }) From 5d9c5ccc60086ae5c6ea4981d472d35f23ee755d Mon Sep 17 00:00:00 2001 From: "David B. Bitton" Date: Thu, 27 Aug 2026 11:49:30 -0400 Subject: [PATCH 2/2] Harden LED writes against the bling/CFS split Subtract CFS_LED_STRIP_LENGTH when firmware exposes it, and wrap setRGBLedColor so patterns cannot paint past the decorative range. The tool now reports strip length in the subtitle and skips setup when the radio has no RGB LED API. --- SCRIPTS/RGBLED/rgbk.lua | 29 ++++++++++++++++++++++++----- SCRIPTS/TOOLS/RGB.lua | 25 ++++++++++++++++++++----- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/SCRIPTS/RGBLED/rgbk.lua b/SCRIPTS/RGBLED/rgbk.lua index f9f44e0..9bce3c7 100644 --- a/SCRIPTS/RGBLED/rgbk.lua +++ b/SCRIPTS/RGBLED/rgbk.lua @@ -17,21 +17,40 @@ local CFG = "/SCRIPTS/TOOLS/RGB.dat" -- Decorative ("bling") LEDs only. Firmware maps Lua index 0 to the first --- gimbal/bling LED. Older builds reported LED_STRIP_LENGTH as bling+CFS --- (26 on TX16S Mk3 / GX15 / TX15). Function-switch LEDs stay under model --- on/off colours / setCFSLedColor() and must not be painted here. +-- gimbal/bling LED. Function-switch LEDs stay under model on/off colours / +-- setCFSLedColor() and must not be painted here. local function decorativeLength() if type(BLING_LED_STRIP_LENGTH) == "number" and BLING_LED_STRIP_LENGTH > 0 then return BLING_LED_STRIP_LENGTH end local n = LED_STRIP_LENGTH or 0 - -- 20 bling + 6 CFS: the dual-ring radios this tool was written for. + if type(CFS_LED_STRIP_LENGTH) == "number" and CFS_LED_STRIP_LENGTH > 0 and n >= CFS_LED_STRIP_LENGTH then + return n - CFS_LED_STRIP_LENGTH + end + -- Pre-constant firmware: TX16S Mk3 / GX15 / TX15 report 20 bling + 6 CFS. if n == 26 then return 20 end return n end local N = decorativeLength() -local HALF = math.floor(N / 2) +local HALF = math.max(1, math.floor(N / 2)) + +-- Bound every write to the decorative range so leftover CFS indices are never +-- touched, even if a pattern hardcodes a 10+10 gimbal layout. +local _setRGB = setRGBLedColor +local _applyRGB = applyRGBLedColors + +local function setRGBLedColor(id, r, g, b) + if type(_setRGB) ~= "function" then return false end + if type(id) ~= "number" or id < 0 or id >= N then return false end + return _setRGB(id, r, g, b) +end + +local function applyRGBLedColors() + if type(_applyRGB) == "function" then + _applyRGB() + end +end local curSel = nil local runFn = nil diff --git a/SCRIPTS/TOOLS/RGB.lua b/SCRIPTS/TOOLS/RGB.lua index b88f2ca..479b4b8 100644 --- a/SCRIPTS/TOOLS/RGB.lua +++ b/SCRIPTS/TOOLS/RGB.lua @@ -13,6 +13,7 @@ local CUSTOM_PREFIX = "custom:" local exitRequested = false local noLvgl = false +local noLeds = type(setRGBLedColor) ~= "function" or type(applyRGBLedColors) ~= "function" local selected = nil local subtitle = "" local buttons = {} @@ -229,8 +230,10 @@ end local function selectMode(name) selected = name saveSelection(name) - preview(name) - ensureKeeper() + if not noLeds then + preview(name) + ensureKeeper() + end for key, _ in pairs(buttons) do setChecked(key, key == name) end @@ -255,9 +258,15 @@ local function buildUi() local MARGIN = 8 local GAP = 6 local LABEL_H = 28 - local y = MARGIN + if noLeds then + pg:label({ x = MARGIN, y = y, w = W - 2 * MARGIN, + text = "This radio has no RGB LED strip (setRGBLedColor missing).", + color = COLOR_THEME_WARNING }) + y = y + LABEL_H + GAP + end + local offBtn = pg:button({ x = MARGIN, y = y, w = W - 2 * MARGIN, h = EH, text = "Off", color = function() return COLOR_THEME_PRIMARY3 end, @@ -273,8 +282,9 @@ local function buildUi() text = "Setup Background Script on Model", color = function() return rgbValue({ r = 30, g = 120, b = 255 }, true) end, textColor = function() return rgbValue({ r = 30, g = 120, b = 255 }, false) end, - active = function() return not keeperInstalled() end, + active = function() return not noLeds and not keeperInstalled() end, press = function() + if noLeds then return end ensureKeeper() if keeperInstalled() then lvgl.message({ title = "Background Script Installed", @@ -342,6 +352,11 @@ local function init() if ok and info and type(info) == "table" then subtitle = info.name or "" end + if type(BLING_LED_STRIP_LENGTH) == "number" and BLING_LED_STRIP_LENGTH > 0 then + subtitle = (subtitle ~= "" and (subtitle .. " · ") or "") .. tostring(BLING_LED_STRIP_LENGTH) .. " LEDs" + elseif type(LED_STRIP_LENGTH) == "number" and LED_STRIP_LENGTH > 0 then + subtitle = (subtitle ~= "" and (subtitle .. " · ") or "") .. tostring(LED_STRIP_LENGTH) .. " LEDs" + end if lvgl == nil then noLvgl = true return @@ -366,7 +381,7 @@ local function run(event, touchState) end return 0 end - if selected then + if not noLeds and selected then preview(selected) end if exitRequested then