mt12/SCRIPTS/TOOLS/elrsV3.lua
2025-06-26 07:54:22 -04:00

945 lines
28 KiB
Lua
Executable file

-- 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 }