From 8e4072aa37c226d102577e19db8cc71c3180bd40 Mon Sep 17 00:00:00 2001 From: egonl Date: Sun, 1 Feb 2026 00:09:39 +0100 Subject: [PATCH 01/14] Added RF2 widget --- src/SCRIPTS/RF2/COMPILE/compile.lua | 6 +- src/SCRIPTS/RF2/tool.lua | 2 + src/SCRIPTS/RF2/ui_lvgl_runner.lua | 17 +++-- src/WIDGETS/rftool/main.lua | 109 ++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 src/WIDGETS/rftool/main.lua diff --git a/src/SCRIPTS/RF2/COMPILE/compile.lua b/src/SCRIPTS/RF2/COMPILE/compile.lua index 20bfa604..7b6785c2 100644 --- a/src/SCRIPTS/RF2/COMPILE/compile.lua +++ b/src/SCRIPTS/RF2/COMPILE/compile.lua @@ -1,5 +1,5 @@ local i = 1 -local scripts = assert(loadScript("COMPILE/scripts.lua")) +local scripts = assert(loadScript("/SCRIPTS/RF2/COMPILE/scripts.lua")) collectgarbage() local function deleteOrTruncateFile(filepath) @@ -33,10 +33,10 @@ local function compile() assert(loadScript(script, 'cd')) -- The 'd' flags gets removed in by minimize.lua return 0 end - local file = io.open("COMPILE/scripts_compiled.lua", 'w') + local file = io.open("/SCRIPTS/RF2/COMPILE/scripts_compiled.lua", 'w') io.write(file, "return true") io.close(file) - assert(loadScript("COMPILE/scripts_compiled.lua", 'c')) + assert(loadScript("/SCRIPTS/RF2/COMPILE/scripts_compiled.lua", 'c')) return 1 end diff --git a/src/SCRIPTS/RF2/tool.lua b/src/SCRIPTS/RF2/tool.lua index bfd2e404..697ce67f 100644 --- a/src/SCRIPTS/RF2/tool.lua +++ b/src/SCRIPTS/RF2/tool.lua @@ -28,6 +28,8 @@ if scriptsCompiled then run = rf2.executeScript("ui_lcd") end --rf2.showMemoryUsage("ui loaded") + + rf2.isTool = true else run = assert(loadScript("COMPILE/compile.lua"))() collectgarbage() diff --git a/src/SCRIPTS/RF2/ui_lvgl_runner.lua b/src/SCRIPTS/RF2/ui_lvgl_runner.lua index d6f4d72b..57fb7f14 100644 --- a/src/SCRIPTS/RF2/ui_lvgl_runner.lua +++ b/src/SCRIPTS/RF2/ui_lvgl_runner.lua @@ -4,8 +4,8 @@ local IsInitialized = false local InitTask local IgnoreNextKeyEvent = false -local function run(event, touchState) - ui.update() +local function run(event, touchState, noUi) + if not noUi then ui.update() end if not IsInitialized then rf2.mspQueue.maxRetries = -1 -- retry indefinitely @@ -17,8 +17,10 @@ local function run(event, touchState) end InitTask = nil ui.clearWaitMessage() - ui.loadMainMenu() - ui.showMainMenu() + if not noUi then + ui.loadMainMenu() + ui.showMainMenu() + end IsInitialized = true end @@ -40,7 +42,7 @@ local function run(event, touchState) if event == 0x20D or event == EVT_VIRTUAL_PREV_PAGE or event == EVT_VIRTUAL_NEXT_PAGE then -- For some reason the tool gets all key events twice, so we need to ignore the second one. if not IgnoreNextKeyEvent then - IgnoreNextKeyEvent = true + if rf2.isTool then IgnoreNextKeyEvent = true end if event == 0x20D then -- SYS break ui.showPopupMenu() elseif event == EVT_VIRTUAL_PREV_PAGE then @@ -66,6 +68,9 @@ rf2.setWaitMessage = ui.setWaitMessage rf2.clearWaitMessage = ui.clearWaitMessage rf2.settingsSaved = ui.saveSettingsToEeprom rf2.onPageReady = ui.onPageReady - +rf2.showMainMenu = function() + ui.loadMainMenu() + ui.showMainMenu() +end -- Return the run function to be called by the RF2 tool return run diff --git a/src/WIDGETS/rftool/main.lua b/src/WIDGETS/rftool/main.lua new file mode 100644 index 00000000..463138fe --- /dev/null +++ b/src/WIDGETS/rftool/main.lua @@ -0,0 +1,109 @@ +local name = "rftool" +local versionString = "v0.1.0" +local options = {} +local compile = nil +local run = nil +local useLvgl = false + +if lvgl == nil then + return { + name = name, + options = { }, + create = (function() end), + refresh = function() + lcd.drawText(10, 10, "LVGL support required", COLOR_THEME_WARNING) + end, + } +end + +local function create(zone, options) + print("RF2: create called") + + local scriptsCompiled = assert(loadScript("/SCRIPTS/RF2/COMPILE/scripts_compiled.lua"))() + + if scriptsCompiled then + --print("RF2: Before rf2.lua: ", collectgarbage("count") * 1024) + assert(loadScript("/SCRIPTS/RF2/rf2.lua"))() + --rf2.showMemoryUsage("rf2 loaded") + rf2.radio = rf2.executeScript("radios").msp + --rf2.showMemoryUsage("radios loaded") + rf2.mspQueue = rf2.executeScript("MSP/mspQueue") + --rf2.showMemoryUsage("MSP queue loaded") + rf2.mspQueue.maxRetries = 3 + rf2.mspHelper = rf2.executeScript("MSP/mspHelper") + --rf2.showMemoryUsage("MSP helper loaded") + + local canUseLvgl = rf2.executeScript("F/canUseLvgl")() + if canUseLvgl then + local settings = rf2.loadSettings() + if settings["useLvgl"] == nil or settings["useLvgl"] == 1 then useLvgl = true end + end + + if useLvgl then + print("Using LVGL UI") + run = rf2.executeScript("ui_lvgl_runner") + else + print("Using LCD UI") + run = rf2.executeScript("ui_lcd") + end + --rf2.showMemoryUsage("ui loaded") + else + compile = assert(loadScript("/SCRIPTS/RF2/COMPILE/compile.lua"))() + collectgarbage() + end + + -- rf2.print("rftool:create") + local widget = {zone = zone, options = options } + return widget +end + +local function showWidget(widget) + lvgl.clear(); + lvgl.build({ + { + type = "box", flexFlow = lvgl.FLOW_COLUMN, children = + { + { type = "label", text = "Rotorflight", w = widget.zone.x, align = CENTER }, + } + } + }); +end + +-- Update function (called when options change) +local function update(widget, options) + rf2.print("update called") + widget.options = options + + if (lvgl.isFullScreen() or lvgl.isAppMode()) then + rf2.showMainMenu() + else + showWidget(widget) + end +end + +local function refresh(widget, event, touchState) + --rf2.print("refresh called") + + if compile ~= nil then + local result = compile() + if result == 1 then + compile = nil + end + return + end + + local noUi = not(lvgl.isFullScreen() or lvgl.isAppMode()) + if run ~= nil then + run(event, touchState, noUi) + end +end + +local function background(widget) + --rf2.print("background called") +end + +local function layout(widget) + --rf2.print("layout called") +end + +return { useLvgl = true, name = name, options = options, create = create, update = update, refresh = refresh, background = background, layout = layout } From d7bf58ee2eac91c0d34dc99a0d2c6da6f4cd0ac1 Mon Sep 17 00:00:00 2001 From: egonl Date: Sun, 15 Feb 2026 11:36:13 +0100 Subject: [PATCH 02/14] Introduced widget.state --- src/WIDGETS/rftool/main.lua | 89 ++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/src/WIDGETS/rftool/main.lua b/src/WIDGETS/rftool/main.lua index 463138fe..9662cea1 100644 --- a/src/WIDGETS/rftool/main.lua +++ b/src/WIDGETS/rftool/main.lua @@ -4,6 +4,7 @@ local options = {} local compile = nil local run = nil local useLvgl = false +local timeCreated = nil if lvgl == nil then return { @@ -19,44 +20,52 @@ end local function create(zone, options) print("RF2: create called") - local scriptsCompiled = assert(loadScript("/SCRIPTS/RF2/COMPILE/scripts_compiled.lua"))() + local widget = { + zone = zone, + options = options, + } - if scriptsCompiled then - --print("RF2: Before rf2.lua: ", collectgarbage("count") * 1024) - assert(loadScript("/SCRIPTS/RF2/rf2.lua"))() - --rf2.showMemoryUsage("rf2 loaded") - rf2.radio = rf2.executeScript("radios").msp - --rf2.showMemoryUsage("radios loaded") - rf2.mspQueue = rf2.executeScript("MSP/mspQueue") - --rf2.showMemoryUsage("MSP queue loaded") - rf2.mspQueue.maxRetries = 3 - rf2.mspHelper = rf2.executeScript("MSP/mspHelper") - --rf2.showMemoryUsage("MSP helper loaded") - - local canUseLvgl = rf2.executeScript("F/canUseLvgl")() - if canUseLvgl then - local settings = rf2.loadSettings() - if settings["useLvgl"] == nil or settings["useLvgl"] == 1 then useLvgl = true end - end + timeCreated = getTime() - if useLvgl then - print("Using LVGL UI") - run = rf2.executeScript("ui_lvgl_runner") - else - print("Using LCD UI") - run = rf2.executeScript("ui_lcd") - end - --rf2.showMemoryUsage("ui loaded") + local scriptsCompiled = assert(loadScript("/SCRIPTS/RF2/COMPILE/scripts_compiled.lua"))() + if scriptsCompiled then + widget.state = "loading" else compile = assert(loadScript("/SCRIPTS/RF2/COMPILE/compile.lua"))() - collectgarbage() + widget.state = "compiling" end - -- rf2.print("rftool:create") - local widget = {zone = zone, options = options } return widget end +local function loadScripts(widget) + --print("RF2: Before rf2.lua: ", collectgarbage("count") * 1024) + assert(loadScript("/SCRIPTS/RF2/rf2.lua"))() + --rf2.showMemoryUsage("rf2 loaded") + rf2.radio = rf2.executeScript("radios") + --rf2.showMemoryUsage("radios loaded") + rf2.mspQueue = rf2.executeScript("MSP/mspQueue") + --rf2.showMemoryUsage("MSP queue loaded") + rf2.mspQueue.maxRetries = 3 + rf2.mspHelper = rf2.executeScript("MSP/mspHelper") + --rf2.showMemoryUsage("MSP helper loaded") + + local canUseLvgl = rf2.executeScript("F/canUseLvgl")() + if canUseLvgl then + local settings = rf2.loadSettings() + if settings["useLvgl"] == nil or settings["useLvgl"] == 1 then useLvgl = true end + end + + if useLvgl then + print("Using LVGL UI") + run = rf2.executeScript("ui_lvgl_runner") + else + print("Using LCD UI") + run = rf2.executeScript("ui_lcd") + end + --rf2.showMemoryUsage("ui loaded") +end + local function showWidget(widget) lvgl.clear(); lvgl.build({ @@ -71,7 +80,7 @@ end -- Update function (called when options change) local function update(widget, options) - rf2.print("update called") + --rf2.print("update called") widget.options = options if (lvgl.isFullScreen() or lvgl.isAppMode()) then @@ -82,14 +91,20 @@ local function update(widget, options) end local function refresh(widget, event, touchState) - --rf2.print("refresh called") - - if compile ~= nil then - local result = compile() - if result == 1 then - compile = nil + -- print("refresh called") + + if widget.state == "compiling" then + if compile ~= nil then + local result = compile() + if result == 1 then + compile = nil + widget.state = "loading" + end + return end - return + elseif widget.state == "loading" and (getTime() - timeCreated) / 100 > 5 then -- bootgrace timeout + loadScripts(widget) + widget.state = "ready" end local noUi = not(lvgl.isFullScreen() or lvgl.isAppMode()) From e037e9719093321c2d9d1403c681c76317d86578 Mon Sep 17 00:00:00 2001 From: egonl Date: Tue, 17 Feb 2026 21:04:14 +0100 Subject: [PATCH 03/14] Added Model Name widget --- src/WIDGETS/modelname/main.lua | 85 ++++++++++++++++++++++++++++++++++ src/WIDGETS/rftool/main.lua | 55 +++++++++++++++++++--- 2 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 src/WIDGETS/modelname/main.lua diff --git a/src/WIDGETS/modelname/main.lua b/src/WIDGETS/modelname/main.lua new file mode 100644 index 00000000..209a7313 --- /dev/null +++ b/src/WIDGETS/modelname/main.lua @@ -0,0 +1,85 @@ +local name = "Model Name" +local versionString = "v0.1.0" +-- local mspName = nil + +if lvgl == nil then + return { + name = name, + options = { }, + create = (function() end), + refresh = function() + lcd.drawText(10, 10, "LVGL support required", COLOR_THEME_WARNING) + end, + } +end + +local function getModelName() + local modelName = rf2 and rf2.model and rf2.model.name or nil + + -- if not mspName and rf2 then + -- rf2.useApi("mspName").getModelName(function(page, name) mspName = name end, self) + -- end + -- local modelName = mspName + + if not modelName then + modelName = model.getInfo().name + end + return modelName or "Unknown" +end + +local function create(zone, options) + print("modelname: create called") + + local widget = { + zone = zone, + options = options, + } + + return widget +end + +local function showWidget(widget) + lvgl.clear(); + lvgl.build({ + { + type = "box", flexFlow = lvgl.FLOW_COLUMN, children = + { + { type = "label", text = function() return getModelName() end, w = widget.zone.x, align = CENTER }, + } + } + }); +end + +-- Update function (called when options change) +local function update(widget, options) + --print("modelname: update called") + widget.options = options + showWidget(widget) +end + +local function refresh(widget, event, touchState) + --print("modelname: refresh called") + local modelName = getModelName() + --print(modelName) + + if not rf2 then return end + if not widget.registered then + widget.ping = function(w) rf2.print("Ping!!!") end + rf2.registerWidget(widget) + widget.registered = true + end + + if rf2 and rf2.widgetIsAlivePing then rf2.widgetIsAlivePing(widget) end +end + +local function background(widget) + --print("modelname: background called") + if rf2 and rf2.widgetIsAlivePing then rf2.widgetIsAlivePing(widget) end +end + +local function translate(widget) + print("modelname: translate called") +end + +-- See https://github.com/EdgeTX/edgetx/blob/main/radio/src/lua/widgets.cpp +return { useLvgl = true, name = name, options = {}, create = create, update = update, refresh = refresh, background = background, translate = translate } diff --git a/src/WIDGETS/rftool/main.lua b/src/WIDGETS/rftool/main.lua index 9662cea1..3c14844a 100644 --- a/src/WIDGETS/rftool/main.lua +++ b/src/WIDGETS/rftool/main.lua @@ -1,6 +1,5 @@ local name = "rftool" local versionString = "v0.1.0" -local options = {} local compile = nil local run = nil local useLvgl = false @@ -17,6 +16,39 @@ if lvgl == nil then } end +local rfWidgets = {} +local function registerWidget(widget) + table.insert(rfWidgets, widget) +end + +local function widgetIsAlivePing(widget) + for k, v in pairs(rfWidgets) do + if v == widget then + print("Received ping from widget, updating lastPing time") + v.lastPing = getTime() + return + end + end +end + +local timeLastPing = nil +local function ping() + if timeLastPing ~= nil and (getTime() - timeLastPing) / 100 < 1 then + return + end + + timeLastPing = getTime() + for k, v in pairs(rfWidgets) do + if v.lastPing ~= nil and (getTime() - v.lastPing) / 100 > 5 then + -- widget is considered dead, remove it from the list + print("Widget considered dead, removing it") + table.remove(rfWidgets, k) + elseif v.ping then + local status, err = pcall(v.ping, v) + end + end +end + local function create(zone, options) print("RF2: create called") @@ -90,9 +122,16 @@ local function update(widget, options) end end +-- local lastHelloTime = nil + local function refresh(widget, event, touchState) -- print("refresh called") + -- if lastHelloTime == nil or (getTime() - lastHelloTime) / 100 > 1 then + -- print("rftool says hello!") + -- lastHelloTime = getTime() + -- end + if widget.state == "compiling" then if compile ~= nil then local result = compile() @@ -105,20 +144,24 @@ local function refresh(widget, event, touchState) elseif widget.state == "loading" and (getTime() - timeCreated) / 100 > 5 then -- bootgrace timeout loadScripts(widget) widget.state = "ready" + + rf2.model = { name = "test" } + rf2.print(rf2 and rf2.shared and rf2.shared.modelName or "Unknown") + + rf2.registerWidget = registerWidget + rf2.widgetIsAlivePing = widgetIsAlivePing end local noUi = not(lvgl.isFullScreen() or lvgl.isAppMode()) if run ~= nil then run(event, touchState, noUi) end + + ping() end local function background(widget) --rf2.print("background called") end -local function layout(widget) - --rf2.print("layout called") -end - -return { useLvgl = true, name = name, options = options, create = create, update = update, refresh = refresh, background = background, layout = layout } +return { useLvgl = true, name = name, options = {}, create = create, update = update, refresh = refresh, background = background } From 68cd2716ccff053ecb3ee78bb2351b7017bdbd88 Mon Sep 17 00:00:00 2001 From: egonl Date: Tue, 17 Feb 2026 23:48:56 +0100 Subject: [PATCH 04/14] Introduced app.lua --- src/SCRIPTS/RF2/background.lua | 5 + src/WIDGETS/RfModelName/app.lua | 58 +++++++++++ src/WIDGETS/RfModelName/main.lua | 42 ++++++++ src/WIDGETS/modelname/main.lua | 85 ----------------- src/WIDGETS/rftool/app.lua | 121 +++++++++++++++++++++++ src/WIDGETS/rftool/main.lua | 159 ++++--------------------------- 6 files changed, 243 insertions(+), 227 deletions(-) create mode 100644 src/WIDGETS/RfModelName/app.lua create mode 100644 src/WIDGETS/RfModelName/main.lua delete mode 100644 src/WIDGETS/modelname/main.lua create mode 100644 src/WIDGETS/rftool/app.lua diff --git a/src/SCRIPTS/RF2/background.lua b/src/SCRIPTS/RF2/background.lua index 21eb4268..112c3600 100644 --- a/src/SCRIPTS/RF2/background.lua +++ b/src/SCRIPTS/RF2/background.lua @@ -11,7 +11,12 @@ end local hasSensor = rf2.executeScript("F/hasSensor") +--local lastHelloTime = nil local function run() + -- if lastHelloTime == nil or rf2.clock() - lastHelloTime > 1 then + -- rf2.print("Background says hello!") + -- lastHelloTime = rf2.clock() + -- end if rf2.runningInSimulator then modelIsConnected = true elseif isInitialized and customTelemetryTask and not hasSensor("*Cnt") then diff --git a/src/WIDGETS/RfModelName/app.lua b/src/WIDGETS/RfModelName/app.lua new file mode 100644 index 00000000..569bc45b --- /dev/null +++ b/src/WIDGETS/RfModelName/app.lua @@ -0,0 +1,58 @@ +local zone, options = ... + +local w = { + zone = zone, + options = options +} + +local function getModelName() + local modelName = rf2 and rf2.model and rf2.model.name or nil + + -- if not mspName and rf2 then + -- rf2.useApi("mspName").getModelName(function(page, name) mspName = name end, self) + -- end + -- local modelName = mspName + + if not modelName then + modelName = model.getInfo().name + end + return modelName or "Unknown" +end + +local function showWidget(widget) + lvgl.clear(); + lvgl.build({ + { + type = "box", flexFlow = lvgl.FLOW_COLUMN, children = + { + { type = "label", text = function() return getModelName() end, w = widget.zone.x, align = CENTER }, + } + } + }); +end + +w.update = function(widget, options) + widget.options = options + showWidget(widget) +end + +w.background = function(widget) + if rf2 and rf2.widgetIsAlivePing then rf2.widgetIsAlivePing(widget) end +end + +w.refresh = function(widget, event, touchState) + widget.background(widget) + + local modelName = getModelName() + + if not rf2 then return end + --print(modelName) + + if not widget.registered then + widget.ping = function(w) rf2.print("Ping!!!") end + rf2.registerWidget(widget) + widget.registered = true + end +end + +return w diff --git a/src/WIDGETS/RfModelName/main.lua b/src/WIDGETS/RfModelName/main.lua new file mode 100644 index 00000000..b04ad475 --- /dev/null +++ b/src/WIDGETS/RfModelName/main.lua @@ -0,0 +1,42 @@ +local name = "RF Model Name" +local versionString = "v0.1.0" + +if lvgl == nil then + return { + name = name, + options = { }, + create = function() end, + refresh = function() + lcd.drawText(10, 10, "LVGL support required", COLOR_THEME_WARNING) + end, + } +end + +local function create(zone, options) + --print("RfModelName: create called") + local widget = loadScript("/WIDGETS/RfModelName/app.lua")(zone, options) + return widget +end + +local function update(widget, options) + --print("RfModelName: update called") + if widget and widget.update then widget.update(widget, options) end +end + +local function refresh(widget, event, touchState) + --print("RfModelName: refresh called") + if widget and widget.refresh then widget.refresh(widget, event, touchState) end +end + +local function background(widget) + --print("RfModelName: background called") + if widget and widget.background then widget.background(widget) end +end + +local function translate(widget) + --print("RfModelName: translate called") + if widget and widget.translate then widget.translate(widget) end +end + +-- See https://github.com/EdgeTX/edgetx/blob/main/radio/src/lua/widgets.cpp +return { useLvgl = true, name = name, options = {}, create = create, update = update, refresh = refresh, background = background, translate = translate } diff --git a/src/WIDGETS/modelname/main.lua b/src/WIDGETS/modelname/main.lua deleted file mode 100644 index 209a7313..00000000 --- a/src/WIDGETS/modelname/main.lua +++ /dev/null @@ -1,85 +0,0 @@ -local name = "Model Name" -local versionString = "v0.1.0" --- local mspName = nil - -if lvgl == nil then - return { - name = name, - options = { }, - create = (function() end), - refresh = function() - lcd.drawText(10, 10, "LVGL support required", COLOR_THEME_WARNING) - end, - } -end - -local function getModelName() - local modelName = rf2 and rf2.model and rf2.model.name or nil - - -- if not mspName and rf2 then - -- rf2.useApi("mspName").getModelName(function(page, name) mspName = name end, self) - -- end - -- local modelName = mspName - - if not modelName then - modelName = model.getInfo().name - end - return modelName or "Unknown" -end - -local function create(zone, options) - print("modelname: create called") - - local widget = { - zone = zone, - options = options, - } - - return widget -end - -local function showWidget(widget) - lvgl.clear(); - lvgl.build({ - { - type = "box", flexFlow = lvgl.FLOW_COLUMN, children = - { - { type = "label", text = function() return getModelName() end, w = widget.zone.x, align = CENTER }, - } - } - }); -end - --- Update function (called when options change) -local function update(widget, options) - --print("modelname: update called") - widget.options = options - showWidget(widget) -end - -local function refresh(widget, event, touchState) - --print("modelname: refresh called") - local modelName = getModelName() - --print(modelName) - - if not rf2 then return end - if not widget.registered then - widget.ping = function(w) rf2.print("Ping!!!") end - rf2.registerWidget(widget) - widget.registered = true - end - - if rf2 and rf2.widgetIsAlivePing then rf2.widgetIsAlivePing(widget) end -end - -local function background(widget) - --print("modelname: background called") - if rf2 and rf2.widgetIsAlivePing then rf2.widgetIsAlivePing(widget) end -end - -local function translate(widget) - print("modelname: translate called") -end - --- See https://github.com/EdgeTX/edgetx/blob/main/radio/src/lua/widgets.cpp -return { useLvgl = true, name = name, options = {}, create = create, update = update, refresh = refresh, background = background, translate = translate } diff --git a/src/WIDGETS/rftool/app.lua b/src/WIDGETS/rftool/app.lua new file mode 100644 index 00000000..866ea3e0 --- /dev/null +++ b/src/WIDGETS/rftool/app.lua @@ -0,0 +1,121 @@ +local zone, options = ... + +local w = { + zone = zone, + options = options, +} + +local scriptsCompiled = assert(loadScript("/SCRIPTS/RF2/COMPILE/scripts_compiled.lua"))() +if scriptsCompiled then + w.state = "loading" +else + w.state = "compiling" +end + +local compile = nil +local run = nil +local timeCreated = getTime() + +local rfWidgets = {} +local function registerWidget(widget) + table.insert(rfWidgets, widget) +end + +local function widgetIsAlivePing(widget) + for k, v in pairs(rfWidgets) do + if v == widget then + print("Received ping from widget, updating lastPing time") + v.lastPing = getTime() + return + end + end +end + +local timeLastPing = nil +local function ping() + if timeLastPing ~= nil and (getTime() - timeLastPing) / 100 < 1 then + return + end + + timeLastPing = getTime() + for k, v in pairs(rfWidgets) do + if v.lastPing ~= nil and (getTime() - v.lastPing) / 100 > 5 then + -- widget is considered dead, remove it from the list + print("Widget considered dead, removing it") + table.remove(rfWidgets, k) + elseif v.ping then + local status, err = pcall(v.ping, v) + end + end +end + +local function loadScripts(widget) + --print("RF2: Before rf2.lua: ", collectgarbage("count") * 1024) + assert(loadScript("/SCRIPTS/RF2/rf2.lua"))() + --rf2.showMemoryUsage("rf2 loaded") + rf2.radio = rf2.executeScript("radios") + --rf2.showMemoryUsage("radios loaded") + rf2.mspQueue = rf2.executeScript("MSP/mspQueue") + --rf2.showMemoryUsage("MSP queue loaded") + rf2.mspQueue.maxRetries = 3 + rf2.mspHelper = rf2.executeScript("MSP/mspHelper") + --rf2.showMemoryUsage("MSP helper loaded") + + run = rf2.executeScript("ui_lvgl_runner") + --run = rf2.executeScript("ui_lcd") + --rf2.showMemoryUsage("ui loaded") +end + +local function showWidget(widget) + lvgl.clear(); + lvgl.build({ + { + type = "box", flexFlow = lvgl.FLOW_COLUMN, children = + { + { type = "label", text = "Rotorflight", w = widget.zone.x, align = CENTER }, + } + } + }); +end + +w.update = function(widget, options) + widget.options = options + + if (lvgl.isFullScreen() or lvgl.isAppMode()) then + rf2.showMainMenu() + else + showWidget(widget) + end +end + +w.refresh = function(widget, event, touchState) + if widget.state == "compiling" then + compile = compile or assert(loadScript("/SCRIPTS/RF2/COMPILE/compile.lua"))() + if compile() == 1 then + compile = nil + widget.state = "loading" + end + return + elseif widget.state == "loading" and (getTime() - timeCreated) / 100 > 5 then -- bootgrace timeout + loadScripts(widget) + widget.state = "ready" + + rf2.model = { name = "test" } + rf2.print(rf2 and rf2.shared and rf2.shared.modelName or "Unknown") + + rf2.registerWidget = registerWidget + rf2.widgetIsAlivePing = widgetIsAlivePing + end + + local noUi = not(lvgl.isFullScreen() or lvgl.isAppMode()) + if run ~= nil then + run(event, touchState, noUi) + end + + ping() +end + +w.background = function(widget) +end + +return w diff --git a/src/WIDGETS/rftool/main.lua b/src/WIDGETS/rftool/main.lua index 3c14844a..f10f9e56 100644 --- a/src/WIDGETS/rftool/main.lua +++ b/src/WIDGETS/rftool/main.lua @@ -1,167 +1,42 @@ -local name = "rftool" +local name = "RF Tool" local versionString = "v0.1.0" -local compile = nil -local run = nil -local useLvgl = false -local timeCreated = nil if lvgl == nil then return { name = name, options = { }, - create = (function() end), + create = function() end, refresh = function() lcd.drawText(10, 10, "LVGL support required", COLOR_THEME_WARNING) end, } end -local rfWidgets = {} -local function registerWidget(widget) - table.insert(rfWidgets, widget) -end - -local function widgetIsAlivePing(widget) - for k, v in pairs(rfWidgets) do - if v == widget then - print("Received ping from widget, updating lastPing time") - v.lastPing = getTime() - return - end - end -end - -local timeLastPing = nil -local function ping() - if timeLastPing ~= nil and (getTime() - timeLastPing) / 100 < 1 then - return - end - - timeLastPing = getTime() - for k, v in pairs(rfWidgets) do - if v.lastPing ~= nil and (getTime() - v.lastPing) / 100 > 5 then - -- widget is considered dead, remove it from the list - print("Widget considered dead, removing it") - table.remove(rfWidgets, k) - elseif v.ping then - local status, err = pcall(v.ping, v) - end - end -end - local function create(zone, options) - print("RF2: create called") - - local widget = { - zone = zone, - options = options, - } - - timeCreated = getTime() - - local scriptsCompiled = assert(loadScript("/SCRIPTS/RF2/COMPILE/scripts_compiled.lua"))() - if scriptsCompiled then - widget.state = "loading" - else - compile = assert(loadScript("/SCRIPTS/RF2/COMPILE/compile.lua"))() - widget.state = "compiling" - end - + --print("RfTool: create called") + local widget = loadScript("/WIDGETS/RfTool/app.lua")(zone, options) return widget end -local function loadScripts(widget) - --print("RF2: Before rf2.lua: ", collectgarbage("count") * 1024) - assert(loadScript("/SCRIPTS/RF2/rf2.lua"))() - --rf2.showMemoryUsage("rf2 loaded") - rf2.radio = rf2.executeScript("radios") - --rf2.showMemoryUsage("radios loaded") - rf2.mspQueue = rf2.executeScript("MSP/mspQueue") - --rf2.showMemoryUsage("MSP queue loaded") - rf2.mspQueue.maxRetries = 3 - rf2.mspHelper = rf2.executeScript("MSP/mspHelper") - --rf2.showMemoryUsage("MSP helper loaded") - - local canUseLvgl = rf2.executeScript("F/canUseLvgl")() - if canUseLvgl then - local settings = rf2.loadSettings() - if settings["useLvgl"] == nil or settings["useLvgl"] == 1 then useLvgl = true end - end - - if useLvgl then - print("Using LVGL UI") - run = rf2.executeScript("ui_lvgl_runner") - else - print("Using LCD UI") - run = rf2.executeScript("ui_lcd") - end - --rf2.showMemoryUsage("ui loaded") -end - -local function showWidget(widget) - lvgl.clear(); - lvgl.build({ - { - type = "box", flexFlow = lvgl.FLOW_COLUMN, children = - { - { type = "label", text = "Rotorflight", w = widget.zone.x, align = CENTER }, - } - } - }); -end - --- Update function (called when options change) local function update(widget, options) - --rf2.print("update called") - widget.options = options - - if (lvgl.isFullScreen() or lvgl.isAppMode()) then - rf2.showMainMenu() - else - showWidget(widget) - end + --print("RfTool: update called") + if widget and widget.update then widget.update(widget, options) end end --- local lastHelloTime = nil - local function refresh(widget, event, touchState) - -- print("refresh called") - - -- if lastHelloTime == nil or (getTime() - lastHelloTime) / 100 > 1 then - -- print("rftool says hello!") - -- lastHelloTime = getTime() - -- end - - if widget.state == "compiling" then - if compile ~= nil then - local result = compile() - if result == 1 then - compile = nil - widget.state = "loading" - end - return - end - elseif widget.state == "loading" and (getTime() - timeCreated) / 100 > 5 then -- bootgrace timeout - loadScripts(widget) - widget.state = "ready" - - rf2.model = { name = "test" } - rf2.print(rf2 and rf2.shared and rf2.shared.modelName or "Unknown") - - rf2.registerWidget = registerWidget - rf2.widgetIsAlivePing = widgetIsAlivePing - end - - local noUi = not(lvgl.isFullScreen() or lvgl.isAppMode()) - if run ~= nil then - run(event, touchState, noUi) - end - - ping() + --print("RfTool: refresh called") + if widget and widget.refresh then widget.refresh(widget, event, touchState) end end local function background(widget) - --rf2.print("background called") + --print("RfTool: background called") + if widget and widget.background then widget.background(widget) end +end + +local function translate(widget) + --print("RfTool: translate called") + if widget and widget.translate then widget.translate(widget) end end -return { useLvgl = true, name = name, options = {}, create = create, update = update, refresh = refresh, background = background } +-- See https://github.com/EdgeTX/edgetx/blob/main/radio/src/lua/widgets.cpp +return { useLvgl = true, name = name, options = {}, create = create, update = update, refresh = refresh, background = background, translate = translate } From 4c5701eb02e9495bb9ea307ca64a8a466b87deb0 Mon Sep 17 00:00:00 2001 From: egonl Date: Wed, 18 Feb 2026 12:34:43 +0100 Subject: [PATCH 05/14] Added backgroundTask --- src/WIDGETS/RfModelName/app.lua | 6 ++++- src/WIDGETS/RfModelName/main.lua | 22 ++++++++++++------ src/WIDGETS/rftool/app.lua | 39 +++++++++++++++++++++----------- src/WIDGETS/rftool/main.lua | 22 ++++++++++++------ 4 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/WIDGETS/RfModelName/app.lua b/src/WIDGETS/RfModelName/app.lua index 569bc45b..2ce509d8 100644 --- a/src/WIDGETS/RfModelName/app.lua +++ b/src/WIDGETS/RfModelName/app.lua @@ -36,8 +36,12 @@ w.update = function(widget, options) showWidget(widget) end +local timeLastPing = nil w.background = function(widget) - if rf2 and rf2.widgetIsAlivePing then rf2.widgetIsAlivePing(widget) end + if rf2 and rf2.widgetIsAlivePing and (timeLastPing == nil or (getTime() - timeLastPing) / 100 >= 1) then + rf2.widgetIsAlivePing(widget) + timeLastPing = getTime() + end end w.refresh = function(widget, event, touchState) diff --git a/src/WIDGETS/RfModelName/main.lua b/src/WIDGETS/RfModelName/main.lua index b04ad475..07f831e9 100644 --- a/src/WIDGETS/RfModelName/main.lua +++ b/src/WIDGETS/RfModelName/main.lua @@ -1,5 +1,5 @@ +-- Keep main.lua as lightweight as possible, since main.lua gets loaded for **all** widgets at boot time. Even if a widget isn't used by a particular model. local name = "RF Model Name" -local versionString = "v0.1.0" if lvgl == nil then return { @@ -33,10 +33,18 @@ local function background(widget) if widget and widget.background then widget.background(widget) end end -local function translate(widget) - --print("RfModelName: translate called") - if widget and widget.translate then widget.translate(widget) end -end +-- local function translate(widget) +-- --print("RfModelName: translate called") +-- if widget and widget.translate then widget.translate(widget) end +-- end --- See https://github.com/EdgeTX/edgetx/blob/main/radio/src/lua/widgets.cpp -return { useLvgl = true, name = name, options = {}, create = create, update = update, refresh = refresh, background = background, translate = translate } +return { + useLvgl = true, + name = name, + options = {}, + create = create, + update = update, + refresh = refresh, + background = background, + --translate = translate +} diff --git a/src/WIDGETS/rftool/app.lua b/src/WIDGETS/rftool/app.lua index 866ea3e0..66ece968 100644 --- a/src/WIDGETS/rftool/app.lua +++ b/src/WIDGETS/rftool/app.lua @@ -12,8 +12,10 @@ else w.state = "compiling" end -local compile = nil -local run = nil +local compileTask = nil +local uiTask = nil +local backgroundTask = nil + local timeCreated = getTime() local rfWidgets = {} @@ -61,9 +63,12 @@ local function loadScripts(widget) rf2.mspHelper = rf2.executeScript("MSP/mspHelper") --rf2.showMemoryUsage("MSP helper loaded") - run = rf2.executeScript("ui_lvgl_runner") - --run = rf2.executeScript("ui_lcd") + uiTask = rf2.executeScript("ui_lvgl_runner") + --uiTask = rf2.executeScript("ui_lcd") --rf2.showMemoryUsage("ui loaded") + + backgroundTask = rf2.executeScript("background") + --rf2.showMemoryUsage("background loaded") end local function showWidget(widget) @@ -88,11 +93,21 @@ w.update = function(widget, options) end end +w.background = function(widget) + if widget.state ~= "ready" then return end + + ping() + + if backgroundTask ~= nil then + backgroundTask(widget) + end +end + w.refresh = function(widget, event, touchState) if widget.state == "compiling" then - compile = compile or assert(loadScript("/SCRIPTS/RF2/COMPILE/compile.lua"))() - if compile() == 1 then - compile = nil + compileTask = compileTask or assert(loadScript("/SCRIPTS/RF2/COMPILE/compile.lua"))() + if compileTask() == 1 then + compileTask = nil widget.state = "loading" end return @@ -104,18 +119,16 @@ w.refresh = function(widget, event, touchState) rf2.print(rf2 and rf2.shared and rf2.shared.modelName or "Unknown") rf2.registerWidget = registerWidget - rf2.widgetIsAlivePing = widgetIsAlivePing + rf2.widgetIsAlivePing = widgetIsAlivePing -- TODO: replace ping with destroy + unregisterWidget once this gets implemented in the EdgeTX widget interface, see https://github.com/EdgeTX/edgetx/issues/7104 end local noUi = not(lvgl.isFullScreen() or lvgl.isAppMode()) - if run ~= nil then - run(event, touchState, noUi) + if uiTask ~= nil then + uiTask(event, touchState, noUi) end - ping() + w.background(widget) end -w.background = function(widget) -end return w diff --git a/src/WIDGETS/rftool/main.lua b/src/WIDGETS/rftool/main.lua index f10f9e56..e33a6b49 100644 --- a/src/WIDGETS/rftool/main.lua +++ b/src/WIDGETS/rftool/main.lua @@ -1,5 +1,5 @@ +-- Keep main.lua as lightweight as possible, since main.lua gets loaded for **all** widgets at boot time. Even if a widget isn't used by a particular model. local name = "RF Tool" -local versionString = "v0.1.0" if lvgl == nil then return { @@ -33,10 +33,18 @@ local function background(widget) if widget and widget.background then widget.background(widget) end end -local function translate(widget) - --print("RfTool: translate called") - if widget and widget.translate then widget.translate(widget) end -end +-- local function translate(widget) +-- --print("RfTool: translate called") +-- if widget and widget.translate then widget.translate(widget) end +-- end --- See https://github.com/EdgeTX/edgetx/blob/main/radio/src/lua/widgets.cpp -return { useLvgl = true, name = name, options = {}, create = create, update = update, refresh = refresh, background = background, translate = translate } +return { + useLvgl = true, + name = name, + options = {}, + create = create, + update = update, + refresh = refresh, + background = background, + --translate = translate +} From 66a7152ac11f8e767526e5291c772ef44ba45849 Mon Sep 17 00:00:00 2001 From: egonl Date: Wed, 18 Feb 2026 23:55:53 +0100 Subject: [PATCH 06/14] Added pacman tone --- src/SCRIPTS/RF2/MSP/common.lua | 2 +- src/SCRIPTS/RF2/background_init.lua | 7 ++++++- src/WIDGETS/RfModelName/app.lua | 1 + src/WIDGETS/rftool/app.lua | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/SCRIPTS/RF2/MSP/common.lua b/src/SCRIPTS/RF2/MSP/common.lua index 553373f4..89c4b434 100644 --- a/src/SCRIPTS/RF2/MSP/common.lua +++ b/src/SCRIPTS/RF2/MSP/common.lua @@ -127,7 +127,7 @@ end local function mspPollReply() local startTime = rf2.clock() - while (rf2.clock() - startTime < 0.05) do + while (rf2.clock() - startTime < 0.01) do local mspData = mspPoll() if mspData ~= nil and mspReceivedReply(mspData) then mspLastReq = 0 diff --git a/src/SCRIPTS/RF2/background_init.lua b/src/SCRIPTS/RF2/background_init.lua index 59f4e154..fa800e41 100644 --- a/src/SCRIPTS/RF2/background_init.lua +++ b/src/SCRIPTS/RF2/background_init.lua @@ -158,7 +158,12 @@ local function initializeQueue() if autoSetName then setModelName(rf2.modelName) end - playTone(1600, 300, 0, PLAY_BACKGROUND) + playTone(523, 120, 0, PLAY_NOW) -- C5 + playTone(659, 120, 0) -- E5 + playTone(784, 120, 0) -- G5 + playTone(1047, 120, 0) -- C6 + playTone(784, 120, 0) -- G5 + playTone(1047, 200, 0) -- C6 --rf2.print("RTC set") rf2.mspQueue.maxRetries = 3 initializationDone = true diff --git a/src/WIDGETS/RfModelName/app.lua b/src/WIDGETS/RfModelName/app.lua index 2ce509d8..ce7a6ae2 100644 --- a/src/WIDGETS/RfModelName/app.lua +++ b/src/WIDGETS/RfModelName/app.lua @@ -26,6 +26,7 @@ local function showWidget(widget) type = "box", flexFlow = lvgl.FLOW_COLUMN, children = { { type = "label", text = function() return getModelName() end, w = widget.zone.x, align = CENTER }, + { type = "label", text = function() return tostring(getValue("Vbat")) end, w = widget.zone.x, align = CENTER }, } } }); diff --git a/src/WIDGETS/rftool/app.lua b/src/WIDGETS/rftool/app.lua index 66ece968..18ffafc7 100644 --- a/src/WIDGETS/rftool/app.lua +++ b/src/WIDGETS/rftool/app.lua @@ -78,6 +78,7 @@ local function showWidget(widget) type = "box", flexFlow = lvgl.FLOW_COLUMN, children = { { type = "label", text = "Rotorflight", w = widget.zone.x, align = CENTER }, + { type = "label", text = function() return tostring(getValue("RxBt")) end, w = widget.zone.x, align = CENTER }, } } }); From 3b6ccf5babb3ec0707d2ea11535f58ae741283ab Mon Sep 17 00:00:00 2001 From: egonl Date: Thu, 19 Feb 2026 01:11:16 +0100 Subject: [PATCH 07/14] Background now updates widget state --- src/SCRIPTS/RF2/background.lua | 27 ++++++++++++++++++++------- src/WIDGETS/RfModelName/app.lua | 9 +++------ src/WIDGETS/rftool/app.lua | 13 ++++++++----- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/SCRIPTS/RF2/background.lua b/src/SCRIPTS/RF2/background.lua index 112c3600..b0b10f53 100644 --- a/src/SCRIPTS/RF2/background.lua +++ b/src/SCRIPTS/RF2/background.lua @@ -11,15 +11,19 @@ end local hasSensor = rf2.executeScript("F/hasSensor") +local function setState(widget, state) + if widget == nil then return end + widget:setState(state) +end + --local lastHelloTime = nil -local function run() +local function run(widget) -- if lastHelloTime == nil or rf2.clock() - lastHelloTime > 1 then -- rf2.print("Background says hello!") -- lastHelloTime = rf2.clock() -- end - if rf2.runningInSimulator then - modelIsConnected = true - elseif isInitialized and customTelemetryTask and not hasSensor("*Cnt") then + + if isInitialized and customTelemetryTask and not hasSensor("*Cnt") then isInitialized = false -- user probably deleted all sensors on TX elseif getRSSI() > 0 then lastTimeRssi = rf2.clock() @@ -32,10 +36,11 @@ local function run() if lastTimeRssi and rf2.clock() - lastTimeRssi < 5 then -- Do not re-initialise if the RSSI is 0 for less than 5 seconds. -- This is also a work-around for https://github.com/ExpressLRS/ExpressLRS/issues/3207 (AUX channel bug in ELRS TX < 3.5.5) + setState(widget, "sensor lost") return end - rf2.executeScript("F/pilotConfigReset")() if modelIsConnected then + rf2.executeScript("F/pilotConfigReset")() if initTask then initTask.reset() initTask = nil @@ -56,6 +61,11 @@ local function run() local initTaskResult = initTask.run(modelIsConnected) if not initTaskResult.isInitialized then --rf2.print("Not initialized yet") + if getRSSI() == 0 then + setState(widget, "disconnected") + else + setState(widget, "initializing") + end return end if initTaskResult.crsfCustomTelemetryEnabled then @@ -67,6 +77,7 @@ local function run() end initTask = nil isInitialized = true + setState(widget, "connected") end if getRSSI() == 0 and not rf2.runningInSimulator then @@ -83,8 +94,10 @@ local function run() end end -local function runProtected() - local status, err = pcall(run) +-- widget is optional and will be provided by the RfTool widget. +-- If the background script runs as a special function, widget will be nil. +local function runProtected(widget) + local status, err = pcall(run, widget) --[NIR if not status then rf2.print(err) end --]] diff --git a/src/WIDGETS/RfModelName/app.lua b/src/WIDGETS/RfModelName/app.lua index ce7a6ae2..6f5f4a4c 100644 --- a/src/WIDGETS/RfModelName/app.lua +++ b/src/WIDGETS/RfModelName/app.lua @@ -1,3 +1,4 @@ +-- RfModelName widget local zone, options = ... local w = { @@ -6,16 +7,12 @@ local w = { } local function getModelName() - local modelName = rf2 and rf2.model and rf2.model.name or nil - - -- if not mspName and rf2 then - -- rf2.useApi("mspName").getModelName(function(page, name) mspName = name end, self) - -- end - -- local modelName = mspName + local modelName = rf2 and rf2.modelName or nil if not modelName then modelName = model.getInfo().name end + return modelName or "Unknown" end diff --git a/src/WIDGETS/rftool/app.lua b/src/WIDGETS/rftool/app.lua index 18ffafc7..8dde1d83 100644 --- a/src/WIDGETS/rftool/app.lua +++ b/src/WIDGETS/rftool/app.lua @@ -1,8 +1,13 @@ +-- RfTool widget local zone, options = ... local w = { zone = zone, options = options, + setState = function(self, state) + if self.state == state then return end + self.state = state + end } local scriptsCompiled = assert(loadScript("/SCRIPTS/RF2/COMPILE/scripts_compiled.lua"))() @@ -78,6 +83,7 @@ local function showWidget(widget) type = "box", flexFlow = lvgl.FLOW_COLUMN, children = { { type = "label", text = "Rotorflight", w = widget.zone.x, align = CENTER }, + { type = "label", text = function() return widget.state end, w = widget.zone.x, align = CENTER }, { type = "label", text = function() return tostring(getValue("RxBt")) end, w = widget.zone.x, align = CENTER }, } } @@ -95,7 +101,7 @@ w.update = function(widget, options) end w.background = function(widget) - if widget.state ~= "ready" then return end + if not backgroundTask then return end ping() @@ -116,11 +122,8 @@ w.refresh = function(widget, event, touchState) loadScripts(widget) widget.state = "ready" - rf2.model = { name = "test" } - rf2.print(rf2 and rf2.shared and rf2.shared.modelName or "Unknown") - rf2.registerWidget = registerWidget - rf2.widgetIsAlivePing = widgetIsAlivePing -- TODO: replace ping with destroy + unregisterWidget once this gets implemented in the EdgeTX widget interface, see https://github.com/EdgeTX/edgetx/issues/7104 + rf2.widgetIsAlivePing = widgetIsAlivePing -- TODO: replace ping with destroy + unregisterWidget once destroy gets implemented in the EdgeTX widget interface, see https://github.com/EdgeTX/edgetx/issues/7104 end local noUi = not(lvgl.isFullScreen() or lvgl.isAppMode()) From 059115e84b4d75837c3c4bd3913d7ee42eb0cf8c Mon Sep 17 00:00:00 2001 From: egonl Date: Thu, 19 Feb 2026 22:22:03 +0100 Subject: [PATCH 08/14] Improved connected tone --- src/SCRIPTS/RF2/background.lua | 2 +- src/SCRIPTS/RF2/background_init.lua | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/SCRIPTS/RF2/background.lua b/src/SCRIPTS/RF2/background.lua index b0b10f53..315251fc 100644 --- a/src/SCRIPTS/RF2/background.lua +++ b/src/SCRIPTS/RF2/background.lua @@ -36,7 +36,7 @@ local function run(widget) if lastTimeRssi and rf2.clock() - lastTimeRssi < 5 then -- Do not re-initialise if the RSSI is 0 for less than 5 seconds. -- This is also a work-around for https://github.com/ExpressLRS/ExpressLRS/issues/3207 (AUX channel bug in ELRS TX < 3.5.5) - setState(widget, "sensor lost") + -- setState(widget, "telemetry lost") -- also needs telemetry recoverede/connected return end if modelIsConnected then diff --git a/src/SCRIPTS/RF2/background_init.lua b/src/SCRIPTS/RF2/background_init.lua index fa800e41..32ea6234 100644 --- a/src/SCRIPTS/RF2/background_init.lua +++ b/src/SCRIPTS/RF2/background_init.lua @@ -158,11 +158,9 @@ local function initializeQueue() if autoSetName then setModelName(rf2.modelName) end - playTone(523, 120, 0, PLAY_NOW) -- C5 - playTone(659, 120, 0) -- E5 - playTone(784, 120, 0) -- G5 - playTone(1047, 120, 0) -- C6 - playTone(784, 120, 0) -- G5 + playTone(523, 200, 0, PLAY_NOW) -- C5 + playTone(659, 200, 0) -- E5 + playTone(784, 200, 0) -- G5 playTone(1047, 200, 0) -- C6 --rf2.print("RTC set") rf2.mspQueue.maxRetries = 3 From 08d8ec744e4e9cf852941e9693509852c5b0c867 Mon Sep 17 00:00:00 2001 From: egonl Date: Thu, 19 Feb 2026 23:16:11 +0100 Subject: [PATCH 09/14] Added options --- src/SCRIPTS/RF2/LVGL/page.lua | 7 ++++++- src/WIDGETS/rftool/app.lua | 13 +++++++++++-- src/WIDGETS/rftool/main.lua | 8 +++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/SCRIPTS/RF2/LVGL/page.lua b/src/SCRIPTS/RF2/LVGL/page.lua index 2b12dbea..518fdacf 100644 --- a/src/SCRIPTS/RF2/LVGL/page.lua +++ b/src/SCRIPTS/RF2/LVGL/page.lua @@ -162,7 +162,12 @@ local function show(page) { type = "page", title = "Rotorflight " .. rf2.luaVersion, - subtitle = page.title, + subtitle = function() + if rf2.widget and rf2.widget.options and rf2.widget.options.sourceName then + return page.title .. " - " .. getValue(rf2.widget.options.sourceName) .. rf2.widget.options.Suffix + end + return page.title + end, icon = rf2.baseDir .. "rf2.png", back = function() if page.back then diff --git a/src/WIDGETS/rftool/app.lua b/src/WIDGETS/rftool/app.lua index 8dde1d83..281d41bc 100644 --- a/src/WIDGETS/rftool/app.lua +++ b/src/WIDGETS/rftool/app.lua @@ -47,7 +47,7 @@ local function ping() timeLastPing = getTime() for k, v in pairs(rfWidgets) do if v.lastPing ~= nil and (getTime() - v.lastPing) / 100 > 5 then - -- widget is considered dead, remove it from the list + -- previously registered widget is considered dead, remove it from the list print("Widget considered dead, removing it") table.remove(rfWidgets, k) elseif v.ping then @@ -74,6 +74,8 @@ local function loadScripts(widget) backgroundTask = rf2.executeScript("background") --rf2.showMemoryUsage("background loaded") + + rf2.widget = widget end local function showWidget(widget) @@ -92,8 +94,15 @@ end w.update = function(widget, options) widget.options = options + if options and options.Source and getFieldInfo then + local fieldInfo = getFieldInfo(options.Source) + if fieldInfo then + widget.options.sourceName = fieldInfo.name + print("RF2: source name: ", widget.options.sourceName) + end + end - if (lvgl.isFullScreen() or lvgl.isAppMode()) then + if (lvgl.isFullScreen() or lvgl.isAppMode()) and widget.state == "connected" then rf2.showMainMenu() else showWidget(widget) diff --git a/src/WIDGETS/rftool/main.lua b/src/WIDGETS/rftool/main.lua index e33a6b49..7c3409f9 100644 --- a/src/WIDGETS/rftool/main.lua +++ b/src/WIDGETS/rftool/main.lua @@ -1,5 +1,11 @@ -- Keep main.lua as lightweight as possible, since main.lua gets loaded for **all** widgets at boot time. Even if a widget isn't used by a particular model. local name = "RF Tool" +local options = { + { "Source", SOURCE, "Vcel" }, + { "Text Color", COLOR, COLOR_THEME_PRIMARY1 }, + { "Suffix", STRING, "" }, + { "Show Min/Max", BOOL, 1 } +} if lvgl == nil then return { @@ -41,7 +47,7 @@ end return { useLvgl = true, name = name, - options = {}, + options = options, create = create, update = update, refresh = refresh, From 6bf9cd7f72aeb856e1f6fd23252af8215028af40 Mon Sep 17 00:00:00 2001 From: egonl Date: Thu, 19 Feb 2026 23:45:42 +0100 Subject: [PATCH 10/14] Added model name to RfTool --- src/SCRIPTS/RF2/LVGL/page.lua | 4 ++-- src/WIDGETS/rftool/app.lua | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/SCRIPTS/RF2/LVGL/page.lua b/src/SCRIPTS/RF2/LVGL/page.lua index 518fdacf..66949de3 100644 --- a/src/SCRIPTS/RF2/LVGL/page.lua +++ b/src/SCRIPTS/RF2/LVGL/page.lua @@ -163,8 +163,8 @@ local function show(page) type = "page", title = "Rotorflight " .. rf2.luaVersion, subtitle = function() - if rf2.widget and rf2.widget.options and rf2.widget.options.sourceName then - return page.title .. " - " .. getValue(rf2.widget.options.sourceName) .. rf2.widget.options.Suffix + if rf2.widget and rf2.widget.options then + return page.title .. " - " .. rf2.widget.options:getText() end return page.title end, diff --git a/src/WIDGETS/rftool/app.lua b/src/WIDGETS/rftool/app.lua index 281d41bc..e8db300a 100644 --- a/src/WIDGETS/rftool/app.lua +++ b/src/WIDGETS/rftool/app.lua @@ -7,6 +7,9 @@ local w = { setState = function(self, state) if self.state == state then return end self.state = state + if state == "disconnected" then + rf2.modelName = nil + end end } @@ -17,6 +20,10 @@ else w.state = "compiling" end +w.options.getText = function(options) + return options.sourceName .. ": " .. tostring(getValue(options.sourceName)) .. options.Suffix +end + local compileTask = nil local uiTask = nil local backgroundTask = nil @@ -78,15 +85,25 @@ local function loadScripts(widget) rf2.widget = widget end +local function getModelName() + local modelName = rf2 and rf2.modelName or nil + + if not modelName then + modelName = model.getInfo().name + end + + return modelName or "Unknown" +end + local function showWidget(widget) lvgl.clear(); lvgl.build({ { type = "box", flexFlow = lvgl.FLOW_COLUMN, children = { - { type = "label", text = "Rotorflight", w = widget.zone.x, align = CENTER }, + { type = "label", text = function() return getModelName() end, w = widget.zone.x, font = DBLSIZE, align = CENTER }, + { type = "label", text = function() return rf2 and rf2.widget.options:getText() or "" end, w = widget.zone.x, align = CENTER }, { type = "label", text = function() return widget.state end, w = widget.zone.x, align = CENTER }, - { type = "label", text = function() return tostring(getValue("RxBt")) end, w = widget.zone.x, align = CENTER }, } } }); From 7adc67100e1ff2b523675bbace7467afa3d01911 Mon Sep 17 00:00:00 2001 From: egonl Date: Fri, 20 Feb 2026 22:56:39 +0100 Subject: [PATCH 11/14] Renamed tools --- src/WIDGETS/{RfModelName => RfStatus}/app.lua | 0 src/WIDGETS/{RfModelName => RfStatus}/main.lua | 2 +- src/WIDGETS/{rftool => RfTool2}/app.lua | 0 src/WIDGETS/{rftool => RfTool2}/main.lua | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename src/WIDGETS/{RfModelName => RfStatus}/app.lua (100%) rename src/WIDGETS/{RfModelName => RfStatus}/main.lua (98%) rename src/WIDGETS/{rftool => RfTool2}/app.lua (100%) rename src/WIDGETS/{rftool => RfTool2}/main.lua (100%) diff --git a/src/WIDGETS/RfModelName/app.lua b/src/WIDGETS/RfStatus/app.lua similarity index 100% rename from src/WIDGETS/RfModelName/app.lua rename to src/WIDGETS/RfStatus/app.lua diff --git a/src/WIDGETS/RfModelName/main.lua b/src/WIDGETS/RfStatus/main.lua similarity index 98% rename from src/WIDGETS/RfModelName/main.lua rename to src/WIDGETS/RfStatus/main.lua index 07f831e9..da7113e1 100644 --- a/src/WIDGETS/RfModelName/main.lua +++ b/src/WIDGETS/RfStatus/main.lua @@ -1,5 +1,5 @@ -- Keep main.lua as lightweight as possible, since main.lua gets loaded for **all** widgets at boot time. Even if a widget isn't used by a particular model. -local name = "RF Model Name" +local name = "RF Status" if lvgl == nil then return { diff --git a/src/WIDGETS/rftool/app.lua b/src/WIDGETS/RfTool2/app.lua similarity index 100% rename from src/WIDGETS/rftool/app.lua rename to src/WIDGETS/RfTool2/app.lua diff --git a/src/WIDGETS/rftool/main.lua b/src/WIDGETS/RfTool2/main.lua similarity index 100% rename from src/WIDGETS/rftool/main.lua rename to src/WIDGETS/RfTool2/main.lua From 1086f22b0cd7658902ceaf86affcb982ec5bc097 Mon Sep 17 00:00:00 2001 From: egonl Date: Fri, 20 Feb 2026 22:57:03 +0100 Subject: [PATCH 12/14] Renamed RfTool --- src/WIDGETS/{RfTool2 => RfTool}/app.lua | 0 src/WIDGETS/{RfTool2 => RfTool}/main.lua | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/WIDGETS/{RfTool2 => RfTool}/app.lua (100%) rename src/WIDGETS/{RfTool2 => RfTool}/main.lua (100%) diff --git a/src/WIDGETS/RfTool2/app.lua b/src/WIDGETS/RfTool/app.lua similarity index 100% rename from src/WIDGETS/RfTool2/app.lua rename to src/WIDGETS/RfTool/app.lua diff --git a/src/WIDGETS/RfTool2/main.lua b/src/WIDGETS/RfTool/main.lua similarity index 100% rename from src/WIDGETS/RfTool2/main.lua rename to src/WIDGETS/RfTool/main.lua From 51710a999f7db4eddb8e0ee1897889d428c0a49c Mon Sep 17 00:00:00 2001 From: egonl Date: Sat, 21 Feb 2026 00:15:30 +0100 Subject: [PATCH 13/14] Added pub/sub support for state events --- src/WIDGETS/RfStatus/app.lua | 5 +++- src/WIDGETS/RfStatus/main.lua | 2 +- src/WIDGETS/RfTool/app.lua | 48 ++++++++++++++++++++--------------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/WIDGETS/RfStatus/app.lua b/src/WIDGETS/RfStatus/app.lua index 6f5f4a4c..c94dbdae 100644 --- a/src/WIDGETS/RfStatus/app.lua +++ b/src/WIDGETS/RfStatus/app.lua @@ -42,6 +42,10 @@ w.background = function(widget) end end +w.onStateChanged = function(w, newState) + rf2.print("RfStatus - got new state: %s", newState) +end + w.refresh = function(widget, event, touchState) widget.background(widget) @@ -51,7 +55,6 @@ w.refresh = function(widget, event, touchState) --print(modelName) if not widget.registered then - widget.ping = function(w) rf2.print("Ping!!!") end rf2.registerWidget(widget) widget.registered = true end diff --git a/src/WIDGETS/RfStatus/main.lua b/src/WIDGETS/RfStatus/main.lua index da7113e1..24061652 100644 --- a/src/WIDGETS/RfStatus/main.lua +++ b/src/WIDGETS/RfStatus/main.lua @@ -14,7 +14,7 @@ end local function create(zone, options) --print("RfModelName: create called") - local widget = loadScript("/WIDGETS/RfModelName/app.lua")(zone, options) + local widget = loadScript("/WIDGETS/RfStatus/app.lua")(zone, options) return widget end diff --git a/src/WIDGETS/RfTool/app.lua b/src/WIDGETS/RfTool/app.lua index e8db300a..f25ee699 100644 --- a/src/WIDGETS/RfTool/app.lua +++ b/src/WIDGETS/RfTool/app.lua @@ -1,16 +1,11 @@ -- RfTool widget local zone, options = ... +local previousArmState = 0 + local w = { zone = zone, - options = options, - setState = function(self, state) - if self.state == state then return end - self.state = state - if state == "disconnected" then - rf2.modelName = nil - end - end + options = options } local scriptsCompiled = assert(loadScript("/SCRIPTS/RF2/COMPILE/scripts_compiled.lua"))() @@ -45,24 +40,29 @@ local function widgetIsAlivePing(widget) end end -local timeLastPing = nil -local function ping() - if timeLastPing ~= nil and (getTime() - timeLastPing) / 100 < 1 then - return - end - - timeLastPing = getTime() +local function publishStateChangedEvent(newState) for k, v in pairs(rfWidgets) do if v.lastPing ~= nil and (getTime() - v.lastPing) / 100 > 5 then -- previously registered widget is considered dead, remove it from the list print("Widget considered dead, removing it") table.remove(rfWidgets, k) - elseif v.ping then - local status, err = pcall(v.ping, v) + elseif v.onStateChanged then + local status, err = pcall(v.onStateChanged, v, newState) end end end +w.setState = function(self, state) + -- This function will also be called from the background task + if self.state == state then return end + self.state = state + if state == "disconnected" then + rf2.modelName = nil + previousArmState = 0 + end + publishStateChangedEvent(self.state) +end + local function loadScripts(widget) --print("RF2: Before rf2.lua: ", collectgarbage("count") * 1024) assert(loadScript("/SCRIPTS/RF2/rf2.lua"))() @@ -126,10 +126,18 @@ w.update = function(widget, options) end end -w.background = function(widget) - if not backgroundTask then return end +local function setArmState(widget) + if not getValue then return end + local armState = getValue("ARM") + if armState ~= previousArmState then + previousArmState = armState + local state = bit32.btest(armState, 1) and "armed" or "disarmed" + widget:setState(state) + end +end - ping() +w.background = function(widget) + setArmState(widget) if backgroundTask ~= nil then backgroundTask(widget) From 014fa12c2fcb0aa95b7a4cb80f2dede8d8648961 Mon Sep 17 00:00:00 2001 From: egonl Date: Sat, 21 Feb 2026 00:18:00 +0100 Subject: [PATCH 14/14] Improved documentation --- src/WIDGETS/RfStatus/app.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/WIDGETS/RfStatus/app.lua b/src/WIDGETS/RfStatus/app.lua index c94dbdae..571c71e8 100644 --- a/src/WIDGETS/RfStatus/app.lua +++ b/src/WIDGETS/RfStatus/app.lua @@ -42,7 +42,8 @@ w.background = function(widget) end end -w.onStateChanged = function(w, newState) +w.onStateChanged = function(w, newState) + -- Possible states: "connected", "disconnected", "armed", "disarmed" rf2.print("RfStatus - got new state: %s", newState) end