2 ---- #########################################################################
4 ---- # Copyright (C) OpenTX #
6 ---- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html #
8 ---- # This program is free software; you can redistribute it and/or modify #
9 ---- # it under the terms of the GNU General Public License version 2 as #
10 ---- # published by the Free Software Foundation. #
12 ---- # This program is distributed in the hope that it will be useful #
13 ---- # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 ---- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 ---- # GNU General Public License for more details. #
17 ---- #########################################################################
19 local handsetId
= 0xEF
26 local fieldTimeout
= 0
32 local goodBadPkt
= "?/??? ?"
34 local elrsFlagsInfo
= ""
35 local fields_count
= 0
36 local backButtonId
= 2
37 local exitButtonId
= 3
38 local devicesRefreshTimeout
= 50
39 local folderAccess
= nil
40 local commandRunningIndicator
= 1
41 local expectChunksRemain
= -1
42 local deviceIsELRS_TX
= nil
43 local linkstatTimeout
= 100
44 local titleShowWarn
= nil
45 local titleShowWarnTimeout
= 100
55 local function allocateFields()
57 for i
=1, fields_count
+ 2 + #devices
do
60 backButtonId
= fields_count
+ 2 + #devices
61 fields
[backButtonId
] = {name
="----BACK----", parent
= 255, type=14}
62 if folderAccess
~= nil then
63 fields
[backButtonId
].parent
= folderAccess
65 exitButtonId
= backButtonId
+ 1
66 fields
[exitButtonId
] = {id
= exitButtonId
, name
="----EXIT----", type=17}
69 local function reloadAllField()
72 -- loadQ is actually a stack
74 for fieldId
= fields_count
, 1, -1 do
75 loadQ
[#loadQ
+1] = fieldId
79 local function getField(line
)
82 local field
= fields
[i
]
83 if folderAccess
== field
.parent
and not field
.hidden
then
84 if counter
< line
then
93 local function constrain(x
, low
, high
)
102 -- Change display attribute to current field
103 local function incrField(step
)
104 local field
= getField(lineIndex
)
105 local min, max = 0, 0
106 if ((field
.type <= 5) or (field
.type == 8)) then
109 step
= field
.step
* step
110 elseif field
.type == 9 then
112 max = #field
.values
- 1
114 field
.value
= constrain(field
.value
+ step
, min, max)
117 -- Select the next or previous editable field
118 local function selectField(step
)
119 local newLineIndex
= lineIndex
122 newLineIndex
= newLineIndex
+ step
123 if newLineIndex
<= 0 then
124 newLineIndex
= #fields
125 elseif newLineIndex
== 1 + #fields
then
129 field
= getField(newLineIndex
)
130 until newLineIndex
== lineIndex
or (field
and field
.name
)
131 lineIndex
= newLineIndex
132 if lineIndex
> maxLineIndex
+ pageOffset
then
133 pageOffset
= lineIndex
- maxLineIndex
134 elseif lineIndex
<= pageOffset
then
135 pageOffset
= lineIndex
- 1
139 local function fieldGetSelectOpts(data
, offset
, last
)
141 while data
[offset
] ~= 0 do
144 return last
, offset
+ 1
147 -- Split a table of byte values (string) with ; separator into a table
150 local b
= data
[offset
]
152 if b
== 59 then -- ';'
156 opt
= opt
.. byteToStr(b
)
164 return r
, offset
+ 1, collectgarbage("collect")
167 local function fieldGetString(data
, offset
, last
)
169 return last
, offset
+ #last
+ 1
173 while data
[offset
] ~= 0 do
174 result
= result
.. byteToStr(data
[offset
])
178 return result
, offset
+ 1, collectgarbage("collect")
181 local function getDevice(name
)
183 if devices
[i
].name
== name
then
189 local function fieldGetValue(data
, offset
, size
)
192 result
= bit32
.lshift(result
, 8) + data
[offset
+ i
]
197 local function fieldUnsignedLoad(field
, data
, offset
, size
)
198 field
.value
= fieldGetValue(data
, offset
, size
)
199 field
.min = fieldGetValue(data
, offset
+size
, size
)
200 field
.max = fieldGetValue(data
, offset
+2*size
, size
)
201 --field.default = fieldGetValue(data, offset+3*size, size)
202 field
.unit
= fieldGetString(data
, offset
+4*size
, field
.unit
)
206 local function fieldUnsignedToSigned(field
, size
)
207 local bandval
= bit32
.lshift(0x80, (size
-1)*8)
208 field
.value
= field
.value
- bit32
.band(field
.value
, bandval
) * 2
209 field
.min = field
.min - bit32
.band(field
.min, bandval
) * 2
210 field
.max = field
.max - bit32
.band(field
.max, bandval
) * 2
211 --field.default = field.default - bit32.band(field.default, bandval) * 2
214 local function fieldSignedLoad(field
, data
, offset
, size
)
215 fieldUnsignedLoad(field
, data
, offset
, size
)
216 fieldUnsignedToSigned(field
, size
)
219 local function fieldIntSave(index
, value
, size
)
220 local frame
= { deviceId
, handsetId
, index
}
221 for i
=size
-1, 0, -1 do
222 frame
[#frame
+ 1] = (bit32
.rshift(value
, 8*i
) % 256)
224 crossfireTelemetryPush(0x2D, frame
)
227 local function fieldUnsignedSave(field
, size
)
228 local value
= field
.value
229 fieldIntSave(field
.id
, value
, size
)
232 local function fieldSignedSave(field
, size
)
233 local value
= field
.value
235 value
= bit32
.lshift(0x100, (size
-1)*8) + value
237 fieldIntSave(field
.id
, value
, size
)
240 local function fieldIntDisplay(field
, y
, attr
)
241 lcd
.drawText(COL2
, y
, field
.value
.. field
.unit
, attr
)
245 local function fieldUint8Load(field
, data
, offset
)
246 fieldUnsignedLoad(field
, data
, offset
, 1)
249 local function fieldUint8Save(field
)
250 fieldUnsignedSave(field
, 1)
254 local function fieldInt8Load(field
, data
, offset
)
255 fieldSignedLoad(field
, data
, offset
, 1)
258 local function fieldInt8Save(field
)
259 fieldSignedSave(field
, 1)
263 local function fieldUint16Load(field
, data
, offset
)
264 fieldUnsignedLoad(field
, data
, offset
, 2)
267 local function fieldUint16Save(field
)
268 fieldUnsignedSave(field
, 2)
272 local function fieldInt16Load(field
, data
, offset
)
273 fieldSignedLoad(field
, data
, offset
, 2)
276 local function fieldInt16Save(field
)
277 fieldSignedSave(field
, 2)
281 local function fieldTextSelectionLoad(field
, data
, offset
)
282 field
.values
, offset
= fieldGetSelectOpts(data
, offset
, field
.nc
== nil and field
.values
)
283 field
.value
= data
[offset
]
284 -- min max and default (offset+1 to 3) are not used on selections
285 -- units never uses cache
286 field
.unit
= fieldGetString(data
, offset
+4)
287 field
.nc
= nil -- use cache next time
290 local function fieldTextSelectionSave(field
)
291 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, field
.id
, field
.value
})
294 local function fieldTextSelectionDisplay_color(field
, y
, attr
)
295 local val
= field
.values
[field
.value
+1] or "ERR"
296 lcd
.drawText(COL2
, y
, val
, attr
)
297 local strPix
= lcd
.sizeText
and lcd
.sizeText(val
) or (10 * #val
)
298 lcd
.drawText(COL2
+ strPix
, y
, field
.unit
, 0)
301 local function fieldTextSelectionDisplay_bw(field
, y
, attr
)
302 lcd
.drawText(COL2
, y
, field
.values
[field
.value
+1] or "ERR", attr
)
303 lcd
.drawText(lcd
.getLastPos(), y
, field
.unit
, 0)
307 local function fieldStringLoad(field
, data
, offset
)
308 field
.value
, offset
= fieldGetString(data
, offset
)
309 if #data
>= offset
then
310 field
.maxlen
= data
[offset
]
314 local function fieldStringDisplay(field
, y
, attr
)
315 lcd
.drawText(COL2
, y
, field
.value
, attr
)
318 local function fieldFolderOpen(field
)
319 folderAccess
= field
.id
320 local backFld
= fields
[backButtonId
]
321 -- Store the lineIndex and pageOffset to return to in the backFld
322 backFld
.li
= lineIndex
323 backFld
.po
= pageOffset
324 backFld
.parent
= folderAccess
330 local function fieldFolderDeviceOpen(field
)
331 crossfireTelemetryPush(0x28, { 0x00, 0xEA }) --broadcast with standard handset ID to get all node respond correctly
332 return fieldFolderOpen(field
)
335 local function fieldFolderDisplay(field
,y
,attr
)
336 lcd
.drawText(textXoffset
, y
, "> " .. field
.name
, bit32
.bor(attr
, BOLD
))
339 local function fieldCommandLoad(field
, data
, offset
)
340 field
.status
= data
[offset
]
341 field
.timeout
= data
[offset
+1]
342 field
.info
= fieldGetString(data
, offset
+2)
343 if field
.status
== 0 then
348 local function fieldCommandSave(field
)
349 if field
.status
~= nil then
350 if field
.status
< 4 then
352 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, field
.id
, field
.status
})
354 fieldPopup
.lastStatus
= 0
355 commandRunningIndicator
= 1
356 fieldTimeout
= getTime() + field
.timeout
361 local function fieldCommandDisplay(field
, y
, attr
)
362 lcd
.drawText(10, y
, "[" .. field
.name
.. "]", bit32
.bor(attr
, BOLD
))
365 local function UIbackExec()
366 local backFld
= fields
[backButtonId
]
367 lineIndex
= backFld
.li
or 1
368 pageOffset
= backFld
.po
or 0
376 local function UIexitExec()
380 local function changeDeviceId(devId
) --change to selected device ID
382 deviceIsELRS_TX
= nil
384 --if the selected device ID (target) is a TX Module, we use our Lua ID, so TX Flag that user is using our LUA
385 if devId
== 0xEE then
387 else --else we would act like the legacy lua
391 fields_count
= 0 --set this because next target wouldn't have the same count, and this trigger to request the new count
394 local function fieldDeviceIdSelect(field
)
395 local device
= getDevice(field
.name
)
396 changeDeviceId(device
.id
)
397 crossfireTelemetryPush(0x28, { 0x00, 0xEA })
400 local function createDeviceFields() -- put other devices in the field list
401 fields
[fields_count
+ 2 + #devices
] = fields
[backButtonId
]
402 backButtonId
= fields_count
+ 2 + #devices
-- move back button to the end of the list, so it will always show up at the bottom.
404 if devices
[i
].id
== deviceId
then
405 fields
[fields_count
+1+i
] = {name
=devices
[i
].name
, parent
= 255, type=15}
407 fields
[fields_count
+1+i
] = {name
=devices
[i
].name
, parent
= fields_count
+1, type=15}
412 local function parseDeviceInfoMessage(data
)
416 newName
, offset
= fieldGetString(data
, 3)
417 local device
= getDevice(newName
)
418 if device
== nil then
419 device
= { id
= id
, name
= newName
}
420 devices
[#devices
+ 1] = device
422 if deviceId
== id
then
424 deviceIsELRS_TX
= ((fieldGetValue(data
,offset
,4) == 0x454C5253) and (deviceId
== 0xEE)) or nil -- SerialNumber = 'E L R S' and ID is TX module
425 local newFieldCount
= data
[offset
+12]
426 if newFieldCount
~= fields_count
or newFieldCount
== 0 then
427 fields_count
= newFieldCount
430 fields
[fields_count
+1] = {id
= fields_count
+1, name
="Other Devices", parent
= 255, type=16} -- add other devices folders
431 if newFieldCount
== 0 then
432 -- This device has no fields so the Loading code never starts
440 { load
=fieldUint8Load
, save
=fieldUint8Save
, display
=fieldIntDisplay
}, --1 UINT8(0)
441 { load
=fieldInt8Load
, save
=fieldInt8Save
, display
=fieldIntDisplay
}, --2 INT8(1)
442 { load
=fieldUint16Load
, save
=fieldUint16Save
, display
=fieldIntDisplay
}, --3 UINT16(2)
443 { load
=fieldInt16Load
, save
=fieldInt16Save
, display
=fieldIntDisplay
}, --4 INT16(3)
449 { load
=fieldTextSelectionLoad
, save
=fieldTextSelectionSave
, display
= nil }, --10 SELECT(9)
450 { load
=fieldStringLoad
, save
=nil, display
=fieldStringDisplay
}, --11 STRING(10) editing NOTIMPL
451 { load
=nil, save
=fieldFolderOpen
, display
=fieldFolderDisplay
}, --12 FOLDER(11)
452 { load
=fieldStringLoad
, save
=nil, display
=fieldStringDisplay
}, --13 INFO(12)
453 { load
=fieldCommandLoad
, save
=fieldCommandSave
, display
=fieldCommandDisplay
}, --14 COMMAND(13)
454 { load
=nil, save
=UIbackExec
, display
=fieldCommandDisplay
}, --15 back(14)
455 { load
=nil, save
=fieldDeviceIdSelect
, display
=fieldCommandDisplay
}, --16 device(15)
456 { load
=nil, save
=fieldFolderDeviceOpen
, display
=fieldFolderDisplay
}, --17 deviceFOLDER(16)
457 { load
=nil, save
=UIexitExec
, display
=fieldCommandDisplay
}, --18 exit(17)
460 local function parseParameterInfoMessage(data
)
461 local fieldId
= (fieldPopup
and fieldPopup
.id
) or loadQ
[#loadQ
]
462 if data
[2] ~= deviceId
or data
[3] ~= fieldId
then
467 local field
= fields
[fieldId
]
468 local chunksRemain
= data
[4]
469 -- If no field or the chunksremain changed when we have data, don't continue
470 if not field
or (chunksRemain
~= expectChunksRemain
and #fieldData
~= 0) then
473 expectChunksRemain
= chunksRemain
- 1
475 fieldData
[#fieldData
+ 1] = data
[i
]
477 if chunksRemain
> 0 then
478 fieldChunk
= fieldChunk
+ 1
481 -- Populate field from fieldData
482 if #fieldData
> 3 then
485 field
.parent
= (fieldData
[1] ~= 0) and fieldData
[1] or nil
486 field
.type = bit32
.band(fieldData
[2], 0x7f)
487 field
.hidden
= bit32
.btest(fieldData
[2], 0x80) or nil
488 field
.name
, offset
= fieldGetString(fieldData
, 3, field
.name
)
489 if functions
[field
.type+1].load
then
490 functions
[field
.type+1].load(field
, fieldData
, offset
)
492 if field
.min == 0 then field
.min = nil end
493 if field
.max == 0 then field
.max = nil end
499 -- Last field loaded, add the list of devices to the end
504 -- Return value is if the screen should be updated
505 -- If deviceId is TX module, then the Bad/Good drives the update; for other
506 -- devices update each new item. and always update when the queue empties
507 return deviceId
~= 0xEE or #loadQ
== 0
511 local function parseElrsInfoMessage(data
)
512 if data
[2] ~= deviceId
then
518 local badPkt
= data
[3]
519 local goodPkt
= (data
[4]*256) + data
[5]
520 local newFlags
= data
[6]
521 -- If flags are changing, reset the warning timeout to display/hide message immediately
522 if newFlags
~= elrsFlags
then
524 titleShowWarnTimeout
= 0
526 elrsFlagsInfo
= fieldGetString(data
, 7)
528 local state
= (bit32
.btest(elrsFlags
, 1) and "C") or "-"
529 goodBadPkt
= string.format("%u/%u %s", badPkt
, goodPkt
, state
)
532 local function parseElrsV1Message(data
)
533 if (data
[1] ~= 0xEA) or (data
[2] ~= 0xEE) then
537 -- local badPkt = data[9]
538 -- local goodPkt = (data[10]*256) + data[11]
539 -- goodBadPkt = string.format("%u/%u X", badPkt, goodPkt)
540 fieldPopup
= {id
= 0, status
= 2, timeout
= 0xFF, info
= "ERROR: 1.x firmware"}
541 fieldTimeout
= getTime() + 0xFFFF
544 local function refreshNext()
545 local command
, data
, forceRedraw
547 command
, data
= crossfireTelemetryPop()
548 if command
== 0x29 then
549 parseDeviceInfoMessage(data
)
550 elseif command
== 0x2B then
551 if parseParameterInfoMessage(data
) then
555 fieldTimeout
= 0 -- request next chunk immediately
556 elseif fieldPopup
then
557 fieldTimeout
= getTime() + fieldPopup
.timeout
559 elseif command
== 0x2D then
560 parseElrsV1Message(data
)
561 elseif command
== 0x2E then
562 parseElrsInfoMessage(data
)
567 local time
= getTime()
569 if time
> fieldTimeout
and fieldPopup
.status
~= 3 then
570 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, fieldPopup
.id
, 6 }) -- lcsQuery
571 fieldTimeout
= time
+ fieldPopup
.timeout
573 elseif time
> devicesRefreshTimeout
and fields_count
< 1 then
574 forceRedraw
= true -- handles initial screen draw
575 devicesRefreshTimeout
= time
+ 100 -- 1s
576 crossfireTelemetryPush(0x28, { 0x00, 0xEA })
577 elseif time
> linkstatTimeout
then
578 if not deviceIsELRS_TX
and #loadQ
== 0 then
581 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, 0x0, 0x0 }) --request linkstat
583 linkstatTimeout
= time
+ 100
584 elseif time
> fieldTimeout
and fields_count
~= 0 then
586 crossfireTelemetryPush(0x2C, { deviceId
, handsetId
, loadQ
[#loadQ
], fieldChunk
})
587 fieldTimeout
= time
+ 50 -- 0.5s
591 if time
> titleShowWarnTimeout
then
592 -- if elrsFlags bit set is bit higher than bit 0 and bit 1, it is warning flags
593 titleShowWarn
= (elrsFlags
> 3 and not titleShowWarn
) or nil
594 titleShowWarnTimeout
= time
+ 100
601 local lcd_title
-- holds function that is color/bw version
602 local function lcd_title_color()
605 local EBLUE
= lcd
.RGB(0x43, 0x61, 0xAA)
606 local EGREEN
= lcd
.RGB(0x9f, 0xc7, 0x6f)
607 local EGREY1
= lcd
.RGB(0x91, 0xb2, 0xc9)
608 local EGREY2
= lcd
.RGB(0x6f, 0x62, 0x7f)
611 -- Field display area (white w/ 2px green border)
612 lcd
.setColor(CUSTOM_COLOR
, EGREEN
)
613 lcd
.drawRectangle(0, 0, LCD_W
, LCD_H
, CUSTOM_COLOR
)
614 lcd
.drawRectangle(1, 0, LCD_W
- 2, LCD_H
- 1, CUSTOM_COLOR
)
616 lcd
.drawFilledRectangle(0, 0, LCD_W
, barHeight
, CUSTOM_COLOR
)
617 lcd
.setColor(CUSTOM_COLOR
, EGREY1
)
618 lcd
.drawFilledRectangle(LCD_W
- textSize
, 0, textSize
, barHeight
, CUSTOM_COLOR
)
619 lcd
.setColor(CUSTOM_COLOR
, EGREY2
)
620 lcd
.drawRectangle(LCD_W
- textSize
, 0, textSize
, barHeight
- 1, CUSTOM_COLOR
)
621 lcd
.drawRectangle(LCD_W
- textSize
, 1 , textSize
- 1, barHeight
- 2, CUSTOM_COLOR
) -- left and bottom line only 1px, make it look bevelled
622 lcd
.setColor(CUSTOM_COLOR
, BLACK
)
623 if titleShowWarn
then
624 lcd
.drawText(textXoffset
+ 1, 4, elrsFlagsInfo
, CUSTOM_COLOR
)
626 local title
= fields_count
> 0 and deviceName
or "Loading..."
627 lcd
.drawText(textXoffset
+ 1, 4, title
, CUSTOM_COLOR
)
628 lcd
.drawText(LCD_W
- 5, 4, goodBadPkt
, RIGHT
+ BOLD
+ CUSTOM_COLOR
)
631 if #loadQ
> 0 and fields_count
> 0 then
632 local barW
= (COL2
-4) * (fields_count
- #loadQ
) / fields_count
633 lcd
.setColor(CUSTOM_COLOR
, EBLUE
)
634 lcd
.drawFilledRectangle(2, 2+20, barW
, barHeight
-5-20, CUSTOM_COLOR
)
635 lcd
.setColor(CUSTOM_COLOR
, WHITE
)
636 lcd
.drawFilledRectangle(2+barW
, 2+20, COL2
-2-barW
, barHeight
-5-20, CUSTOM_COLOR
)
640 local function lcd_title_bw()
644 if not titleShowWarn
then
645 lcd
.drawText(LCD_W
- 1, 1, goodBadPkt
, RIGHT
)
646 lcd
.drawLine(LCD_W
- 10, 0, LCD_W
- 10, barHeight
-1, SOLID
, INVERS
)
649 if #loadQ
> 0 and fields_count
> 0 then
650 lcd
.drawFilledRectangle(COL2
, 0, LCD_W
, barHeight
, GREY_DEFAULT
)
651 lcd
.drawGauge(0, 0, COL2
, barHeight
, fields_count
- #loadQ
, fields_count
, 0)
653 lcd
.drawFilledRectangle(0, 0, LCD_W
, barHeight
, GREY_DEFAULT
)
654 if titleShowWarn
then
655 lcd
.drawText(textXoffset
, 1, elrsFlagsInfo
, INVERS
)
657 local title
= fields_count
> 0 and deviceName
or "Loading..."
658 lcd
.drawText(textXoffset
, 1, title
, INVERS
)
663 local function lcd_warn()
664 lcd
.drawText(textXoffset
, textSize
*2, "Error:")
665 lcd
.drawText(textXoffset
, textSize
*3, elrsFlagsInfo
)
666 lcd
.drawText(LCD_W
/2, textSize
*5, "[OK]", BLINK
+ INVERS
+ CENTER
)
669 local function reloadCurField()
670 local field
= getField(lineIndex
)
674 loadQ
[#loadQ
+1] = field
.id
677 local function reloadRelatedFields(field
)
678 -- Reload the parent folder to update the description
680 loadQ
[#loadQ
+1] = field
.parent
681 fields
[field
.parent
].name
= nil
684 -- Reload all editable fields at the same level as well as the parent item
685 for fieldId
= fields_count
, 1, -1 do
686 -- Skip this field, will be added to end
687 local fldTest
= fields
[fieldId
]
688 if fieldId
~= field
.id
689 and fldTest
.parent
== field
.parent
690 and (fldTest
.type or 99) < 11 then -- type could be nil if still loading
691 fldTest
.nc
= true -- "no cache" the options
692 loadQ
[#loadQ
+1] = fieldId
697 loadQ
[#loadQ
+1] = field
.id
698 -- with a short delay to allow the module EEPROM to commit
699 fieldTimeout
= getTime() + 20
702 local function handleDevicePageEvent(event
)
703 if #fields
== 0 then --if there is no field yet
706 if fields
[backButtonId
].name
== nil then --if back button is not assigned yet, means there is no field yet.
711 if event
== EVT_VIRTUAL_EXIT
then -- Cancel edit / go up a folder / reload all
716 if folderAccess
== nil and #loadQ
== 0 then -- only do reload if we're in the root folder and finished loading
717 if deviceId
~= 0xEE then
718 changeDeviceId(0xEE) --change device id clear the fields_count, therefore the next ping will do reloadAllField()
722 crossfireTelemetryPush(0x28, { 0x00, 0xEA })
726 elseif event
== EVT_VIRTUAL_ENTER
then -- toggle editing/selecting current field
727 if elrsFlags
> 0x1F then
729 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, 0x2E, 0x00 })
731 local field
= getField(lineIndex
)
732 if field
and field
.name
then
733 if field
.type < 10 then
737 if field
.type < 10 then
739 reloadRelatedFields(field
)
740 elseif field
.type == 13 then
744 if functions
[field
.type+1].save
then
745 functions
[field
.type+1].save(field
)
751 if event
== EVT_VIRTUAL_NEXT
then
753 elseif event
== EVT_VIRTUAL_PREV
then
757 if event
== EVT_VIRTUAL_NEXT
then
759 elseif event
== EVT_VIRTUAL_PREV
then
766 local function runDevicePage(event
)
767 handleDevicePageEvent(event
)
771 if #devices
> 1 then -- show other device folder
772 fields
[fields_count
+1].parent
= nil
774 if elrsFlags
> 0x1F then
777 for y
= 1, maxLineIndex
+1 do
778 local field
= getField(pageOffset
+y
)
781 elseif field
.name
~= nil then
782 local attr
= lineIndex
== (pageOffset
+y
)
783 and ((edit
and BLINK
or 0) + INVERS
)
785 if field
.type < 11 or field
.type == 12 then -- if not folder, command, or back
786 lcd
.drawText(textXoffset
, y
*textSize
+textYoffset
, field
.name
, 0)
788 if functions
[field
.type+1].display
then
789 functions
[field
.type+1].display(field
, y
*textSize
+textYoffset
, attr
)
796 local function popupCompat(t
, m
, e
)
797 -- Only use 2 of 3 arguments for older platforms
798 return popupConfirmation(t
, e
)
801 local function runPopupPage(event
)
802 if event
== EVT_VIRTUAL_EXIT
then
803 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, fieldPopup
.id
, 5 }) -- lcsCancel
804 fieldTimeout
= getTime() + 200 -- 2s
808 if fieldPopup
.status
== 0 and fieldPopup
.lastStatus
~= 0 then -- stopped
809 popupCompat(fieldPopup
.info
, "Stopped!", event
)
812 elseif fieldPopup
.status
== 3 then -- confirmation required
813 result
= popupCompat(fieldPopup
.info
, "PRESS [OK] to confirm", event
)
814 fieldPopup
.lastStatus
= fieldPopup
.status
815 if result
== "OK" then
816 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, fieldPopup
.id
, 4 }) -- lcsConfirmed
817 fieldTimeout
= getTime() + fieldPopup
.timeout
-- we are expecting an immediate response
818 fieldPopup
.status
= 4
819 elseif result
== "CANCEL" then
822 elseif fieldPopup
.status
== 2 then -- running
823 if fieldChunk
== 0 then
824 commandRunningIndicator
= (commandRunningIndicator
% 4) + 1
826 result
= popupCompat(fieldPopup
.info
.. " [" .. string.sub("|/-\\", commandRunningIndicator
, commandRunningIndicator
) .. "]", "Press [RTN] to exit", event
)
827 fieldPopup
.lastStatus
= fieldPopup
.status
828 if result
== "CANCEL" then
829 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, fieldPopup
.id
, 5 }) -- lcsCancel
830 fieldTimeout
= getTime() + fieldPopup
.timeout
-- we are expecting an immediate response
836 local function loadSymbolChars()
837 -- On firmwares that have constants defined for the arrow chars, use them in place of
838 -- the \xc0 \xc1 chars (which are OpenTX-en)
840 byteToStr
= function (b
)
841 -- Use the table to convert the char, else use string.char if not in the table
843 [192] = __opentx
.CHAR_UP
,
844 [193] = __opentx
.CHAR_DOWN
845 })[b
] or string.char(b
)
848 byteToStr
= string.char
852 local function touch2evt(event
, touchState
)
853 -- Convert swipe events to normal events Left/Right/Up/Down -> EXIT/ENTER/PREV/NEXT
854 -- PREV/NEXT are swapped if editing
855 -- TAP is converted to ENTER
856 touchState
= touchState
or {}
857 return (touchState
.swipeLeft
and EVT_VIRTUAL_EXIT
)
858 or (touchState
.swipeRight
and EVT_VIRTUAL_ENTER
)
859 or (touchState
.swipeUp
and (edit
and EVT_VIRTUAL_NEXT
or EVT_VIRTUAL_PREV
))
860 or (touchState
.swipeDown
and (edit
and EVT_VIRTUAL_PREV
or EVT_VIRTUAL_NEXT
))
861 or (event
== EVT_TOUCH_TAP
and EVT_VIRTUAL_ENTER
)
864 local function setLCDvar()
865 -- Set the title function depending on if LCD is color, and free the other function and
866 -- set textselection unit function, use GetLastPost or sizeText
867 if (lcd
.RGB
~= nil) then
868 lcd_title
= lcd_title_color
869 functions
[10].display
=fieldTextSelectionDisplay_color
871 lcd_title
= lcd_title_bw
872 functions
[10].display
=fieldTextSelectionDisplay_bw
875 lcd_title_color
= nil
877 fieldTextSelectionDisplay_bw
= nil
878 fieldTextSelectionDisplay_color
= nil
879 -- Determine if popupConfirmation takes 3 arguments or 2
880 -- if pcall(popupConfirmation, "", "", EVT_VIRTUAL_EXIT) then
881 -- major 1 is assumed to be FreedomTX
882 local ver
, radio
, major
= getVersion()
884 popupCompat
= popupConfirmation
891 textSize
= 22 --textSize is text Height
892 elseif LCD_W
== 320 then
910 loadSymbolChars
= nil
913 local function setMock()
914 -- Setup fields to display if running in Simulator
915 local _
, rv
= getVersion()
916 if string.sub(rv
, -5) ~= "-simu" then return end
917 local mock
= loadScript("mockup/elrsmock.lua")
918 if mock
== nil then return end
919 fields
, goodBadPkt
, deviceName
= mock(), "0/500 C", "ExpressLRS TX"
920 fields_count
= #fields
- 1
921 loadQ
= { fields_count
}
922 deviceIsELRS_TX
= true
923 backButtonId
= #fields
925 fields_count
= fields_count
+ 1
926 exitButtonId
= fields_count
+ 1
927 fields
[exitButtonId
] = {id
= exitButtonId
, name
="----EXIT----", type=17}
931 local function init()
939 local function run(event
, touchState
)
941 error("Cannot be run as a model script!")
945 local forceRedraw
= refreshNext()
947 event
= (touch2evt
and touch2evt(event
, touchState
)) or event
948 if fieldPopup
~= nil then
950 elseif event
~= 0 or forceRedraw
or edit
then
957 return { init
=init
, run
=run
}