-- TNS|ExpressLRS|TNE ---- ######################################################################### ---- # # ---- # Copyright (C) OpenTX # -----# # ---- # 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. # ---- # # ---- ######################################################################### local deviceId = 0xEE local handsetId = 0xEF local deviceName = "" local lineIndex = 1 local pageOffset = 0 local edit = nil local charIndex = 1 local fieldPopup local fieldTimeout = 0 local loadQ = {} local fieldChunk = 0 local fieldData = {} local fields = {} local devices = {} local goodBadPkt = "?/??? ?" local elrsFlags = 0 local elrsFlagsInfo = "" local fields_count = 0 local backButtonId = 2 local exitButtonId = 3 local devicesRefreshTimeout = 50 local folderAccess = nil local commandRunningIndicator = 1 local expectChunksRemain = -1 local deviceIsELRS_TX = nil local linkstatTimeout = 100 local titleShowWarn = nil local titleShowWarnTimeout = 100 local exitscript = 0 local COL2 local maxLineIndex local textXoffset local textYoffset local textSize local byteToStr local function allocateFields() fields = {} for i=1, fields_count + 2 + #devices do fields[i] = { } end backButtonId = fields_count + 2 + #devices fields[backButtonId] = {name="----BACK----", parent = 255, type=14} if folderAccess ~= nil then fields[backButtonId].parent = folderAccess end exitButtonId = backButtonId + 1 fields[exitButtonId] = {id = exitButtonId, name="----EXIT----", type=17} end local function reloadAllField() fieldChunk = 0 fieldData = {} -- loadQ is actually a stack loadQ = {} for fieldId = fields_count, 1, -1 do loadQ[#loadQ+1] = fieldId end end local function getField(line) local counter = 1 for i = 1, #fields do local field = fields[i] if folderAccess == field.parent and not field.hidden then if counter < line then counter = counter + 1 else return field end end end end local function constrain(x, low, high) if x < low then return low elseif x > high then return high end return x end -- Change display attribute to current field local function incrField(step) local field = getField(lineIndex) local min, max = 0, 0 if ((field.type <= 5) or (field.type == 8)) then min = field.min or 0 max = field.max or 0 step = field.step * step elseif field.type == 9 then min = 0 max = #field.values - 1 end field.value = constrain(field.value + step, min, max) end -- Select the next or previous editable field local function selectField(step) local newLineIndex = lineIndex local field repeat newLineIndex = newLineIndex + step if newLineIndex <= 0 then newLineIndex = #fields elseif newLineIndex == 1 + #fields then newLineIndex = 1 pageOffset = 0 end field = getField(newLineIndex) until newLineIndex == lineIndex or (field and field.name) lineIndex = newLineIndex if lineIndex > maxLineIndex + pageOffset then pageOffset = lineIndex - maxLineIndex elseif lineIndex <= pageOffset then pageOffset = lineIndex - 1 end end local function fieldGetSelectOpts(data, offset, last) if last then while data[offset] ~= 0 do offset = offset + 1 end return last, offset + 1 end -- Split a table of byte values (string) with ; separator into a table local r = {} local opt = '' local b = data[offset] while b ~= 0 do if b == 59 then -- ';' r[#r+1] = opt opt = '' else opt = opt .. byteToStr(b) end offset = offset + 1 b = data[offset] end r[#r+1] = opt opt = nil return r, offset + 1, collectgarbage("collect") end local function fieldGetString(data, offset, last) if last then return last, offset + #last + 1 end local result = "" while data[offset] ~= 0 do result = result .. byteToStr(data[offset]) offset = offset + 1 end return result, offset + 1, collectgarbage("collect") end local function getDevice(name) for i=1, #devices do if devices[i].name == name then return devices[i] end end end local function fieldGetValue(data, offset, size) local result = 0 for i=0, size-1 do result = bit32.lshift(result, 8) + data[offset + i] end return result end local function fieldUnsignedLoad(field, data, offset, size) field.value = fieldGetValue(data, offset, size) field.min = fieldGetValue(data, offset+size, size) field.max = fieldGetValue(data, offset+2*size, size) --field.default = fieldGetValue(data, offset+3*size, size) field.unit = fieldGetString(data, offset+4*size, field.unit) field.step = 1 end local function fieldUnsignedToSigned(field, size) local bandval = bit32.lshift(0x80, (size-1)*8) field.value = field.value - bit32.band(field.value, bandval) * 2 field.min = field.min - bit32.band(field.min, bandval) * 2 field.max = field.max - bit32.band(field.max, bandval) * 2 --field.default = field.default - bit32.band(field.default, bandval) * 2 end local function fieldSignedLoad(field, data, offset, size) fieldUnsignedLoad(field, data, offset, size) fieldUnsignedToSigned(field, size) end local function fieldIntSave(index, value, size) local frame = { deviceId, handsetId, index } for i=size-1, 0, -1 do frame[#frame + 1] = (bit32.rshift(value, 8*i) % 256) end crossfireTelemetryPush(0x2D, frame) end local function fieldUnsignedSave(field, size) local value = field.value fieldIntSave(field.id, value, size) end local function fieldSignedSave(field, size) local value = field.value if value < 0 then value = bit32.lshift(0x100, (size-1)*8) + value end fieldIntSave(field.id, value, size) end local function fieldIntDisplay(field, y, attr) lcd.drawText(COL2, y, field.value .. field.unit, attr) end -- UINT8 local function fieldUint8Load(field, data, offset) fieldUnsignedLoad(field, data, offset, 1) end local function fieldUint8Save(field) fieldUnsignedSave(field, 1) end -- INT8 local function fieldInt8Load(field, data, offset) fieldSignedLoad(field, data, offset, 1) end local function fieldInt8Save(field) fieldSignedSave(field, 1) end -- UINT16 local function fieldUint16Load(field, data, offset) fieldUnsignedLoad(field, data, offset, 2) end local function fieldUint16Save(field) fieldUnsignedSave(field, 2) end -- INT16 local function fieldInt16Load(field, data, offset) fieldSignedLoad(field, data, offset, 2) end local function fieldInt16Save(field) fieldSignedSave(field, 2) end -- TEXT SELECTION local function fieldTextSelectionLoad(field, data, offset) field.values, offset = fieldGetSelectOpts(data, offset, field.nc == nil and field.values) field.value = data[offset] -- min max and default (offset+1 to 3) are not used on selections -- units never uses cache field.unit = fieldGetString(data, offset+4) field.nc = nil -- use cache next time end local function fieldTextSelectionSave(field) crossfireTelemetryPush(0x2D, { deviceId, handsetId, field.id, field.value }) end local function fieldTextSelectionDisplay_color(field, y, attr) local val = field.values[field.value+1] or "ERR" lcd.drawText(COL2, y, val, attr) local strPix = lcd.sizeText and lcd.sizeText(val) or (10 * #val) lcd.drawText(COL2 + strPix, y, field.unit, 0) end local function fieldTextSelectionDisplay_bw(field, y, attr) lcd.drawText(COL2, y, field.values[field.value+1] or "ERR", attr) lcd.drawText(lcd.getLastPos(), y, field.unit, 0) end -- STRING local function fieldStringLoad(field, data, offset) field.value, offset = fieldGetString(data, offset) if #data >= offset then field.maxlen = data[offset] end end local function fieldStringDisplay(field, y, attr) lcd.drawText(COL2, y, field.value, attr) end local function fieldFolderOpen(field) folderAccess = field.id local backFld = fields[backButtonId] -- Store the lineIndex and pageOffset to return to in the backFld backFld.li = lineIndex backFld.po = pageOffset backFld.parent = folderAccess lineIndex = 1 pageOffset = 0 end local function fieldFolderDeviceOpen(field) crossfireTelemetryPush(0x28, { 0x00, 0xEA }) --broadcast with standard handset ID to get all node respond correctly return fieldFolderOpen(field) end local function fieldFolderDisplay(field,y ,attr) lcd.drawText(textXoffset, y, "> " .. field.name, bit32.bor(attr, BOLD)) end local function fieldCommandLoad(field, data, offset) field.status = data[offset] field.timeout = data[offset+1] field.info = fieldGetString(data, offset+2) if field.status == 0 then fieldPopup = nil end end local function fieldCommandSave(field) if field.status ~= nil then if field.status < 4 then field.status = 1 crossfireTelemetryPush(0x2D, { deviceId, handsetId, field.id, field.status }) fieldPopup = field fieldPopup.lastStatus = 0 commandRunningIndicator = 1 fieldTimeout = getTime() + field.timeout end end end local function fieldCommandDisplay(field, y, attr) lcd.drawText(10, y, "[" .. field.name .. "]", bit32.bor(attr, BOLD)) end local function UIbackExec() local backFld = fields[backButtonId] lineIndex = backFld.li or 1 pageOffset = backFld.po or 0 backFld.parent = 255 backFld.li = nil backFld.po = nil folderAccess = nil end local function UIexitExec() exitscript = 1 end local function changeDeviceId(devId) --change to selected device ID folderAccess = nil deviceIsELRS_TX = nil elrsFlags = 0 --if the selected device ID (target) is a TX Module, we use our Lua ID, so TX Flag that user is using our LUA if devId == 0xEE then handsetId = 0xEF else --else we would act like the legacy lua handsetId = 0xEA end deviceId = devId fields_count = 0 --set this because next target wouldn't have the same count, and this trigger to request the new count end local function fieldDeviceIdSelect(field) local device = getDevice(field.name) changeDeviceId(device.id) crossfireTelemetryPush(0x28, { 0x00, 0xEA }) end local function createDeviceFields() -- put other devices in the field list fields[fields_count + 2 + #devices] = fields[backButtonId] backButtonId = fields_count + 2 + #devices -- move back button to the end of the list, so it will always show up at the bottom. for i=1, #devices do if devices[i].id == deviceId then fields[fields_count+1+i] = {name=devices[i].name, parent = 255, type=15} else fields[fields_count+1+i] = {name=devices[i].name, parent = fields_count+1, type=15} end end end local function parseDeviceInfoMessage(data) local offset local id = data[2] local newName newName, offset = fieldGetString(data, 3) local device = getDevice(newName) if device == nil then device = { id = id, name = newName } devices[#devices + 1] = device end if deviceId == id then deviceName = newName deviceIsELRS_TX = ((fieldGetValue(data,offset,4) == 0x454C5253) and (deviceId == 0xEE)) or nil -- SerialNumber = 'E L R S' and ID is TX module local newFieldCount = data[offset+12] if newFieldCount ~= fields_count or newFieldCount == 0 then fields_count = newFieldCount allocateFields() reloadAllField() fields[fields_count+1] = {id = fields_count+1, name="Other Devices", parent = 255, type=16} -- add other devices folders if newFieldCount == 0 then -- This device has no fields so the Loading code never starts createDeviceFields() end end end end local functions = { { load=fieldUint8Load, save=fieldUint8Save, display=fieldIntDisplay }, --1 UINT8(0) { load=fieldInt8Load, save=fieldInt8Save, display=fieldIntDisplay }, --2 INT8(1) { load=fieldUint16Load, save=fieldUint16Save, display=fieldIntDisplay }, --3 UINT16(2) { load=fieldInt16Load, save=fieldInt16Save, display=fieldIntDisplay }, --4 INT16(3) nil, nil, nil, nil, nil, --9 FLOAT(8) { load=fieldTextSelectionLoad, save=fieldTextSelectionSave, display = nil }, --10 SELECT(9) { load=fieldStringLoad, save=nil, display=fieldStringDisplay }, --11 STRING(10) editing NOTIMPL { load=nil, save=fieldFolderOpen, display=fieldFolderDisplay }, --12 FOLDER(11) { load=fieldStringLoad, save=nil, display=fieldStringDisplay }, --13 INFO(12) { load=fieldCommandLoad, save=fieldCommandSave, display=fieldCommandDisplay }, --14 COMMAND(13) { load=nil, save=UIbackExec, display=fieldCommandDisplay }, --15 back(14) { load=nil, save=fieldDeviceIdSelect, display=fieldCommandDisplay }, --16 device(15) { load=nil, save=fieldFolderDeviceOpen, display=fieldFolderDisplay }, --17 deviceFOLDER(16) { load=nil, save=UIexitExec, display=fieldCommandDisplay }, --18 exit(17) } local function parseParameterInfoMessage(data) local fieldId = (fieldPopup and fieldPopup.id) or loadQ[#loadQ] if data[2] ~= deviceId or data[3] ~= fieldId then fieldData = {} fieldChunk = 0 return end local field = fields[fieldId] local chunksRemain = data[4] -- If no field or the chunksremain changed when we have data, don't continue if not field or (chunksRemain ~= expectChunksRemain and #fieldData ~= 0) then return end expectChunksRemain = chunksRemain - 1 for i=5, #data do fieldData[#fieldData + 1] = data[i] end if chunksRemain > 0 then fieldChunk = fieldChunk + 1 else loadQ[#loadQ] = nil -- Populate field from fieldData if #fieldData > 3 then local offset field.id = fieldId field.parent = (fieldData[1] ~= 0) and fieldData[1] or nil field.type = bit32.band(fieldData[2], 0x7f) field.hidden = bit32.btest(fieldData[2], 0x80) or nil field.name, offset = fieldGetString(fieldData, 3, field.name) if functions[field.type+1].load then functions[field.type+1].load(field, fieldData, offset) end if field.min == 0 then field.min = nil end if field.max == 0 then field.max = nil end end fieldChunk = 0 fieldData = {} -- Last field loaded, add the list of devices to the end if #loadQ == 0 then createDeviceFields() end end end local function parseElrsInfoMessage(data) if data[2] ~= deviceId then fieldData = {} fieldChunk = 0 return end local badPkt = data[3] local goodPkt = (data[4]*256) + data[5] local newFlags = data[6] -- If flags are changing, reset the warning timeout to display/hide message immediately if newFlags ~= elrsFlags then elrsFlags = newFlags titleShowWarnTimeout = 0 end elrsFlagsInfo = fieldGetString(data, 7) local state = (bit32.btest(elrsFlags, 1) and "C") or "-" goodBadPkt = string.format("%u/%u %s", badPkt, goodPkt, state) end local function parseElrsV1Message(data) if (data[1] ~= 0xEA) or (data[2] ~= 0xEE) then return end -- local badPkt = data[9] -- local goodPkt = (data[10]*256) + data[11] -- goodBadPkt = string.format("%u/%u X", badPkt, goodPkt) fieldPopup = {id = 0, status = 2, timeout = 0xFF, info = "ERROR: 1.x firmware"} fieldTimeout = getTime() + 0xFFFF end local function refreshNext() local command, data = crossfireTelemetryPop() if command == 0x29 then parseDeviceInfoMessage(data) elseif command == 0x2B then parseParameterInfoMessage(data) if #loadQ > 0 then fieldTimeout = 0 -- request next chunk immediately elseif fieldPopup then fieldTimeout = getTime() + fieldPopup.timeout end elseif command == 0x2D then parseElrsV1Message(data) elseif command == 0x2E then parseElrsInfoMessage(data) end local time = getTime() if fieldPopup then if time > fieldTimeout and fieldPopup.status ~= 3 then crossfireTelemetryPush(0x2D, { deviceId, handsetId, fieldPopup.id, 6 }) -- lcsQuery fieldTimeout = time + fieldPopup.timeout end elseif time > devicesRefreshTimeout and fields_count < 1 then devicesRefreshTimeout = time + 100 -- 1s crossfireTelemetryPush(0x28, { 0x00, 0xEA }) elseif time > linkstatTimeout then if not deviceIsELRS_TX and #loadQ == 0 then goodBadPkt = "" else crossfireTelemetryPush(0x2D, { deviceId, handsetId, 0x0, 0x0 }) --request linkstat end linkstatTimeout = time + 100 elseif time > fieldTimeout and fields_count ~= 0 then if #loadQ > 0 then crossfireTelemetryPush(0x2C, { deviceId, handsetId, loadQ[#loadQ], fieldChunk }) fieldTimeout = time + 50 -- 0.5s end end if time > titleShowWarnTimeout then -- if elrsFlags bit set is bit higher than bit 0 and bit 1, it is warning flags titleShowWarn = (elrsFlags > 3 and not titleShowWarn) or nil titleShowWarnTimeout = time + 100 end end local lcd_title -- holds function that is color/bw version local function lcd_title_color() lcd.clear() local EBLUE = lcd.RGB(0x43, 0x61, 0xAA) local EGREEN = lcd.RGB(0x9f, 0xc7, 0x6f) local EGREY1 = lcd.RGB(0x91, 0xb2, 0xc9) local EGREY2 = lcd.RGB(0x6f, 0x62, 0x7f) local barHeight = 30 -- Field display area (white w/ 2px green border) lcd.setColor(CUSTOM_COLOR, EGREEN) lcd.drawRectangle(0, 0, LCD_W, LCD_H, CUSTOM_COLOR) lcd.drawRectangle(1, 0, LCD_W - 2, LCD_H - 1, CUSTOM_COLOR) -- title bar lcd.drawFilledRectangle(0, 0, LCD_W, barHeight, CUSTOM_COLOR) lcd.setColor(CUSTOM_COLOR, EGREY1) lcd.drawFilledRectangle(LCD_W - textSize, 0, textSize, barHeight, CUSTOM_COLOR) lcd.setColor(CUSTOM_COLOR, EGREY2) lcd.drawRectangle(LCD_W - textSize, 0, textSize, barHeight - 1, CUSTOM_COLOR) lcd.drawRectangle(LCD_W - textSize, 1 , textSize - 1, barHeight - 2, CUSTOM_COLOR) -- left and bottom line only 1px, make it look bevelled lcd.setColor(CUSTOM_COLOR, BLACK) if titleShowWarn then lcd.drawText(textXoffset + 1, 4, elrsFlagsInfo, CUSTOM_COLOR) lcd.drawText(LCD_W - textSize - 5, 4, tostring(elrsFlags), RIGHT + BOLD + CUSTOM_COLOR) else local title = fields_count > 0 and deviceName or "Loading..." lcd.drawText(textXoffset + 1, 4, title, CUSTOM_COLOR) lcd.drawText(LCD_W - 5, 4, goodBadPkt, RIGHT + BOLD + CUSTOM_COLOR) end -- progress bar if #loadQ > 0 and fields_count > 0 then local barW = (COL2-4) * (fields_count - #loadQ) / fields_count lcd.setColor(CUSTOM_COLOR, EBLUE) lcd.drawFilledRectangle(2, 2+20, barW, barHeight-5-20, CUSTOM_COLOR) lcd.setColor(CUSTOM_COLOR, WHITE) lcd.drawFilledRectangle(2+barW, 2+20, COL2-2-barW, barHeight-5-20, CUSTOM_COLOR) end end local function lcd_title_bw() lcd.clear() -- B&W screen local barHeight = 9 if titleShowWarn then lcd.drawText(LCD_W, 1, tostring(elrsFlags), RIGHT) else lcd.drawText(LCD_W - 1, 1, goodBadPkt, RIGHT) lcd.drawLine(LCD_W - 10, 0, LCD_W - 10, barHeight-1, SOLID, INVERS) end if #loadQ > 0 and fields_count > 0 then lcd.drawFilledRectangle(COL2, 0, LCD_W, barHeight, GREY_DEFAULT) lcd.drawGauge(0, 0, COL2, barHeight, fields_count - #loadQ, fields_count, 0) else lcd.drawFilledRectangle(0, 0, LCD_W, barHeight, GREY_DEFAULT) if titleShowWarn then lcd.drawText(textXoffset, 1, elrsFlagsInfo, INVERS) else local title = fields_count > 0 and deviceName or "Loading..." lcd.drawText(textXoffset, 1, title, INVERS) end end end local function lcd_warn() lcd.drawText(textXoffset, textSize*2, "Error:") lcd.drawText(textXoffset, textSize*3, elrsFlagsInfo) lcd.drawText(LCD_W/2, textSize*5, "[OK]", BLINK + INVERS + CENTER) end local function reloadCurField() local field = getField(lineIndex) fieldTimeout = 0 fieldChunk = 0 fieldData = {} loadQ[#loadQ+1] = field.id end local function reloadRelatedFields(field) -- Reload the parent folder to update the description if field.parent then loadQ[#loadQ+1] = field.parent fields[field.parent].name = nil end -- Reload all editable fields at the same level as well as the parent item for fieldId = fields_count, 1, -1 do -- Skip this field, will be added to end local fldTest = fields[fieldId] if fieldId ~= field.id and fldTest.parent == field.parent and (fldTest.type or 99) < 11 then -- type could be nil if still loading fldTest.nc = true -- "no cache" the options loadQ[#loadQ+1] = fieldId end end -- Reload this field loadQ[#loadQ+1] = field.id -- with a short delay to allow the module EEPROM to commit fieldTimeout = getTime() + 20 end local function handleDevicePageEvent(event) if #fields == 0 then --if there is no field yet return else if fields[backButtonId].name == nil then --if back button is not assigned yet, means there is no field yet. return end end if event == EVT_VIRTUAL_EXIT then -- Cancel edit / go up a folder / reload all if edit then edit = nil reloadCurField(0) else if folderAccess == nil and #loadQ == 0 then -- only do reload if we're in the root folder and finished loading if deviceId ~= 0xEE then changeDeviceId(0xEE) --change device id clear the fields_count, therefore the next ping will do reloadAllField() else reloadAllField() end crossfireTelemetryPush(0x28, { 0x00, 0xEA }) end UIbackExec() end elseif event == EVT_VIRTUAL_ENTER then -- toggle editing/selecting current field if elrsFlags > 0x1F then elrsFlags = 0 crossfireTelemetryPush(0x2D, { deviceId, handsetId, 0x2E, 0x00 }) else local field = getField(lineIndex) if field and field.name then if field.type < 10 then edit = not edit end if not edit then if field.type < 10 then -- Editable fields reloadRelatedFields(field) elseif field.type == 13 then -- Command reloadCurField() end if functions[field.type+1].save then functions[field.type+1].save(field) end end end end elseif edit then if event == EVT_VIRTUAL_NEXT then incrField(1) elseif event == EVT_VIRTUAL_PREV then incrField(-1) end else if event == EVT_VIRTUAL_NEXT then selectField(1) elseif event == EVT_VIRTUAL_PREV then selectField(-1) end end end -- Main local function runDevicePage(event) handleDevicePageEvent(event) lcd_title() if #devices > 1 then -- show other device folder fields[fields_count+1].parent = nil end if elrsFlags > 0x1F then lcd_warn() else for y = 1, maxLineIndex+1 do local field = getField(pageOffset+y) if not field then break elseif field.name ~= nil then local attr = lineIndex == (pageOffset+y) and ((edit and BLINK or 0) + INVERS) or 0 if field.type < 11 or field.type == 12 then -- if not folder, command, or back lcd.drawText(textXoffset, y*textSize+textYoffset, field.name, 0) end if functions[field.type+1].display then functions[field.type+1].display(field, y*textSize+textYoffset, attr) end end end end end local function popupCompat(t, m, e) -- Only use 2 of 3 arguments for older platforms return popupConfirmation(t, e) end local function runPopupPage(event) if event == EVT_VIRTUAL_EXIT then crossfireTelemetryPush(0x2D, { deviceId, handsetId, fieldPopup.id, 5 }) -- lcsCancel fieldTimeout = getTime() + 200 -- 2s end local result if fieldPopup.status == 0 and fieldPopup.lastStatus ~= 0 then -- stopped popupCompat(fieldPopup.info, "Stopped!", event) reloadAllField() fieldPopup = nil elseif fieldPopup.status == 3 then -- confirmation required result = popupCompat(fieldPopup.info, "PRESS [OK] to confirm", event) fieldPopup.lastStatus = fieldPopup.status if result == "OK" then crossfireTelemetryPush(0x2D, { deviceId, handsetId, fieldPopup.id, 4 }) -- lcsConfirmed fieldTimeout = getTime() + fieldPopup.timeout -- we are expecting an immediate response fieldPopup.status = 4 elseif result == "CANCEL" then fieldPopup = nil end elseif fieldPopup.status == 2 then -- running if fieldChunk == 0 then commandRunningIndicator = (commandRunningIndicator % 4) + 1 end result = popupCompat(fieldPopup.info .. " [" .. string.sub("|/-\\", commandRunningIndicator, commandRunningIndicator) .. "]", "Press [RTN] to exit", event) fieldPopup.lastStatus = fieldPopup.status if result == "CANCEL" then crossfireTelemetryPush(0x2D, { deviceId, handsetId, fieldPopup.id, 5 }) -- lcsCancel fieldTimeout = getTime() + fieldPopup.timeout -- we are expecting an immediate response fieldPopup = nil end end end local function loadSymbolChars() -- On firmwares that have constants defined for the arrow chars, use them in place of -- the \xc0 \xc1 chars (which are OpenTX-en) if __opentx then byteToStr = function (b) -- Use the table to convert the char, else use string.char if not in the table return ({ [192] = __opentx.CHAR_UP, [193] = __opentx.CHAR_DOWN })[b] or string.char(b) end else byteToStr = string.char end end local function touch2evt(event, touchState) -- Convert swipe events to normal events Left/Right/Up/Down -> EXIT/ENTER/PREV/NEXT -- PREV/NEXT are swapped if editing -- TAP is converted to ENTER touchState = touchState or {} return (touchState.swipeLeft and EVT_VIRTUAL_EXIT) or (touchState.swipeRight and EVT_VIRTUAL_ENTER) or (touchState.swipeUp and (edit and EVT_VIRTUAL_NEXT or EVT_VIRTUAL_PREV)) or (touchState.swipeDown and (edit and EVT_VIRTUAL_PREV or EVT_VIRTUAL_NEXT)) or (event == EVT_TOUCH_TAP and EVT_VIRTUAL_ENTER) end local function setLCDvar() -- Set the title function depending on if LCD is color, and free the other function and -- set textselection unit function, use GetLastPost or sizeText if (lcd.RGB ~= nil) then lcd_title = lcd_title_color functions[10].display=fieldTextSelectionDisplay_color else lcd_title = lcd_title_bw functions[10].display=fieldTextSelectionDisplay_bw touch2evt = nil end lcd_title_color = nil lcd_title_bw = nil fieldTextSelectionDisplay_bw = nil fieldTextSelectionDisplay_color = nil -- Determine if popupConfirmation takes 3 arguments or 2 -- if pcall(popupConfirmation, "", "", EVT_VIRTUAL_EXIT) then -- major 1 is assumed to be FreedomTX local ver, radio, major = getVersion() if major ~= 1 then popupCompat = popupConfirmation end if LCD_W == 480 then COL2 = 240 maxLineIndex = 10 textXoffset = 3 textYoffset = 10 textSize = 22 --textSize is text Height elseif LCD_W == 320 then COL2 = 160 maxLineIndex = 14 textXoffset = 3 textYoffset = 10 textSize = 22 else if LCD_W == 212 then COL2 = 110 else COL2 = 70 end maxLineIndex = 6 textXoffset = 0 textYoffset = 3 textSize = 8 end loadSymbolChars() loadSymbolChars = nil end local function setMock() -- Setup fields to display if running in Simulator local _, rv = getVersion() if string.sub(rv, -5) ~= "-simu" then return end local mock = loadScript("mockup/elrsmock.lua") if mock == nil then return end fields, goodBadPkt, deviceName = mock(), "0/500 C", "ExpressLRS TX" fields_count = #fields - 1 loadQ = { fields_count } deviceIsELRS_TX = true backButtonId = #fields fields_count = fields_count + 1 exitButtonId = fields_count + 1 fields[exitButtonId] = {id = exitButtonId, name="----EXIT----", type=17} end -- Init local function init() setLCDvar() setMock() setLCDvar = nil setMock = nil end -- Main local function run(event, touchState) if event == nil then error("Cannot be run as a model script!") return 2 end event = (touch2evt and touch2evt(event, touchState)) or event if fieldPopup ~= nil then runPopupPage(event) else runDevicePage(event) end refreshNext() return exitscript end return { init=init, run=run }