diff --git a/src/SCRIPTS/RF2/COMPILE/compile.lua b/src/SCRIPTS/RF2/COMPILE/compile.lua index 20bfa60..7b6785c 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/LVGL/page.lua b/src/SCRIPTS/RF2/LVGL/page.lua index 2b12dbe..66949de 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 then + return page.title .. " - " .. rf2.widget.options:getText() + end + return page.title + end, icon = rf2.baseDir .. "rf2.png", back = function() if page.back then diff --git a/src/SCRIPTS/RF2/MSP/common.lua b/src/SCRIPTS/RF2/MSP/common.lua index 553373f..89c4b43 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.lua b/src/SCRIPTS/RF2/background.lua index 21eb426..315251f 100644 --- a/src/SCRIPTS/RF2/background.lua +++ b/src/SCRIPTS/RF2/background.lua @@ -11,10 +11,19 @@ end local hasSensor = rf2.executeScript("F/hasSensor") -local function run() - if rf2.runningInSimulator then - modelIsConnected = true - elseif isInitialized and customTelemetryTask and not hasSensor("*Cnt") then +local function setState(widget, state) + if widget == nil then return end + widget:setState(state) +end + +--local lastHelloTime = nil +local function run(widget) + -- if lastHelloTime == nil or rf2.clock() - lastHelloTime > 1 then + -- rf2.print("Background says hello!") + -- lastHelloTime = rf2.clock() + -- end + + 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() @@ -27,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, "telemetry lost") -- also needs telemetry recoverede/connected return end - rf2.executeScript("F/pilotConfigReset")() if modelIsConnected then + rf2.executeScript("F/pilotConfigReset")() if initTask then initTask.reset() initTask = nil @@ -51,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 @@ -62,6 +77,7 @@ local function run() end initTask = nil isInitialized = true + setState(widget, "connected") end if getRSSI() == 0 and not rf2.runningInSimulator then @@ -78,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/SCRIPTS/RF2/background_init.lua b/src/SCRIPTS/RF2/background_init.lua index 59f4e15..32ea623 100644 --- a/src/SCRIPTS/RF2/background_init.lua +++ b/src/SCRIPTS/RF2/background_init.lua @@ -158,7 +158,10 @@ local function initializeQueue() if autoSetName then setModelName(rf2.modelName) end - playTone(1600, 300, 0, PLAY_BACKGROUND) + 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 initializationDone = true diff --git a/src/SCRIPTS/RF2/tool.lua b/src/SCRIPTS/RF2/tool.lua index bfd2e40..697ce67 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 d6f4d72..57fb7f1 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/RfStatus/app.lua b/src/WIDGETS/RfStatus/app.lua new file mode 100644 index 0000000..571c71e --- /dev/null +++ b/src/WIDGETS/RfStatus/app.lua @@ -0,0 +1,64 @@ +-- RfModelName widget +local zone, options = ... + +local w = { + zone = zone, + options = options +} + +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 = 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 }, + } + } + }); +end + +w.update = function(widget, options) + widget.options = options + showWidget(widget) +end + +local timeLastPing = nil +w.background = function(widget) + if rf2 and rf2.widgetIsAlivePing and (timeLastPing == nil or (getTime() - timeLastPing) / 100 >= 1) then + rf2.widgetIsAlivePing(widget) + timeLastPing = getTime() + end +end + +w.onStateChanged = function(w, newState) + -- Possible states: "connected", "disconnected", "armed", "disarmed" + rf2.print("RfStatus - got new state: %s", newState) +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 + rf2.registerWidget(widget) + widget.registered = true + end +end + +return w diff --git a/src/WIDGETS/RfStatus/main.lua b/src/WIDGETS/RfStatus/main.lua new file mode 100644 index 0000000..2406165 --- /dev/null +++ b/src/WIDGETS/RfStatus/main.lua @@ -0,0 +1,50 @@ +-- 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 Status" + +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/RfStatus/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 + +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 0000000..f25ee69 --- /dev/null +++ b/src/WIDGETS/RfTool/app.lua @@ -0,0 +1,172 @@ +-- RfTool widget +local zone, options = ... + +local previousArmState = 0 + +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 + +w.options.getText = function(options) + return options.sourceName .. ": " .. tostring(getValue(options.sourceName)) .. options.Suffix +end + +local compileTask = nil +local uiTask = nil +local backgroundTask = 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 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.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"))() + --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") + + uiTask = rf2.executeScript("ui_lvgl_runner") + --uiTask = rf2.executeScript("ui_lcd") + --rf2.showMemoryUsage("ui loaded") + + backgroundTask = rf2.executeScript("background") + --rf2.showMemoryUsage("background loaded") + + 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 = 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 }, + } + } + }); +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()) and widget.state == "connected" then + rf2.showMainMenu() + else + showWidget(widget) + end +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 + +w.background = function(widget) + setArmState(widget) + + if backgroundTask ~= nil then + backgroundTask(widget) + end +end + +w.refresh = function(widget, event, touchState) + if widget.state == "compiling" then + compileTask = compileTask or assert(loadScript("/SCRIPTS/RF2/COMPILE/compile.lua"))() + if compileTask() == 1 then + compileTask = nil + widget.state = "loading" + end + return + elseif widget.state == "loading" and (getTime() - timeCreated) / 100 > 5 then -- bootgrace timeout + loadScripts(widget) + widget.state = "ready" + + rf2.registerWidget = registerWidget + 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()) + if uiTask ~= nil then + uiTask(event, touchState, noUi) + end + + w.background(widget) +end + + +return w diff --git a/src/WIDGETS/RfTool/main.lua b/src/WIDGETS/RfTool/main.lua new file mode 100644 index 0000000..7c3409f --- /dev/null +++ b/src/WIDGETS/RfTool/main.lua @@ -0,0 +1,56 @@ +-- 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 { + 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("RfTool: create called") + local widget = loadScript("/WIDGETS/RfTool/app.lua")(zone, options) + return widget +end + +local function update(widget, options) + --print("RfTool: update called") + if widget and widget.update then widget.update(widget, options) end +end + +local function refresh(widget, event, touchState) + --print("RfTool: refresh called") + if widget and widget.refresh then widget.refresh(widget, event, touchState) end +end + +local function background(widget) + --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 = options, + create = create, + update = update, + refresh = refresh, + background = background, + --translate = translate +}