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 devicesRefreshTimeout
= 50
38 local allParamsLoaded
= 0
39 local folderAccess
= nil
40 local statusComplete
= 0
41 local commandRunningIndicator
= 1
42 local expectedChunks
= -1
43 local deviceIsELRS_TX
= nil
44 local linkstatTimeout
= 100
45 local titleShowWarn
= nil
46 local titleShowWarnTimeout
= 100
54 local function allocateFields()
56 for i
=1, fields_count
+ 2 + #devices
do
59 backButtonId
= fields_count
+ 2 + #devices
60 fields
[backButtonId
] = {id
= backButtonId
, name
="----BACK----", parent
= 255, type=14}
61 if folderAccess
~= nil then
62 fields
[backButtonId
].parent
= folderAccess
66 local function reloadAllField()
68 fieldId
, fieldChunk
= 1, 0
72 local function getField(line
)
75 local field
= fields
[i
]
76 if folderAccess
== field
.parent
and not field
.hidden
then
77 if counter
< line
then
86 local function constrain(x
, low
, high
)
95 -- Change display attribute to current field
96 local function incrField(step
)
97 local field
= getField(lineIndex
)
98 if field
.type == 10 then
100 if charIndex
<= #field
.value
then
101 byte
= string.byte(field
.value
, charIndex
) + step
105 elseif byte
> 122 then
108 if charIndex
<= #field
.value
then
109 field
.value
= string.sub(field
.value
, 1, charIndex
-1) .. string.char(byte
) .. string.sub(field
.value
, charIndex
+1)
111 field
.value
= field
.value
.. string.char(byte
)
114 local min, max = 0, 0
115 if ((field
.type <= 5) or (field
.type == 8)) then
118 step
= field
.step
* step
119 elseif field
.type == 9 then
121 max = #field
.values
- 1
123 field
.value
= constrain(field
.value
+ step
, min, max)
127 -- Select the next or previous editable field
128 local function selectField(step
)
129 local newLineIndex
= lineIndex
132 newLineIndex
= newLineIndex
+ step
133 if newLineIndex
<= 0 then
134 newLineIndex
= #fields
135 elseif newLineIndex
== 1 + #fields
then
139 field
= getField(newLineIndex
)
140 until newLineIndex
== lineIndex
or (field
and field
.name
)
141 lineIndex
= newLineIndex
142 if lineIndex
> maxLineIndex
+ pageOffset
then
143 pageOffset
= lineIndex
- maxLineIndex
144 elseif lineIndex
<= pageOffset
then
145 pageOffset
= lineIndex
- 1
149 local function fieldStrFF(data
, offset
, last
)
150 while data
[offset
] ~= 0 do
153 return last
, offset
+ 1
156 local function fieldGetSelectOpts(data
, offset
, last
)
158 return fieldStrFF(data
, offset
, last
)
161 -- Split a table of byte values (string) with ; separator into a table
164 local b
= data
[offset
]
166 if b
== 59 then -- ';'
170 opt
= opt
.. string.char(b
)
180 local function fieldGetString(data
, offset
, last
)
182 return fieldStrFF(data
, offset
, last
)
186 while data
[offset
] ~= 0 do
187 result
= result
.. string.char(data
[offset
])
191 return result
, offset
+ 1
194 local function getDevice(name
)
196 if devices
[i
].name
== name
then
202 local function fieldGetValue(data
, offset
, size
)
205 result
= bit32
.lshift(result
, 8) + data
[offset
+ i
]
210 local function fieldUnsignedLoad(field
, data
, offset
, size
)
211 field
.value
= fieldGetValue(data
, offset
, size
)
212 field
.min = fieldGetValue(data
, offset
+size
, size
)
213 field
.max = fieldGetValue(data
, offset
+2*size
, size
)
214 --field.default = fieldGetValue(data, offset+3*size, size)
215 field
.unit
= fieldGetString(data
, offset
+4*size
, field
.unit
)
219 local function fieldUnsignedToSigned(field
, size
)
220 local bandval
= bit32
.lshift(0x80, (size
-1)*8)
221 field
.value
= field
.value
- bit32
.band(field
.value
, bandval
) * 2
222 field
.min = field
.min - bit32
.band(field
.min, bandval
) * 2
223 field
.max = field
.max - bit32
.band(field
.max, bandval
) * 2
224 --field.default = field.default - bit32.band(field.default, bandval) * 2
227 local function fieldSignedLoad(field
, data
, offset
, size
)
228 fieldUnsignedLoad(field
, data
, offset
, size
)
229 fieldUnsignedToSigned(field
, size
)
232 local function fieldIntSave(index
, value
, size
)
233 local frame
= { deviceId
, handsetId
, index
}
234 for i
=size
-1, 0, -1 do
235 frame
[#frame
+ 1] = (bit32
.rshift(value
, 8*i
) % 256)
237 crossfireTelemetryPush(0x2D, frame
)
240 local function fieldUnsignedSave(field
, size
)
241 local value
= field
.value
242 fieldIntSave(field
.id
, value
, size
)
245 local function fieldSignedSave(field
, size
)
246 local value
= field
.value
248 value
= bit32
.lshift(0x100, (size
-1)*8) + value
250 fieldIntSave(field
.id
, value
, size
)
253 local function fieldIntDisplay(field
, y
, attr
)
254 lcd
.drawText(COL2
, y
, field
.value
.. field
.unit
, attr
)
258 local function fieldUint8Load(field
, data
, offset
)
259 fieldUnsignedLoad(field
, data
, offset
, 1)
262 local function fieldUint8Save(field
)
263 fieldUnsignedSave(field
, 1)
267 local function fieldInt8Load(field
, data
, offset
)
268 fieldSignedLoad(field
, data
, offset
, 1)
271 local function fieldInt8Save(field
)
272 fieldSignedSave(field
, 1)
276 local function fieldUint16Load(field
, data
, offset
)
277 fieldUnsignedLoad(field
, data
, offset
, 2)
280 local function fieldUint16Save(field
)
281 fieldUnsignedSave(field
, 2)
285 local function fieldInt16Load(field
, data
, offset
)
286 fieldSignedLoad(field
, data
, offset
, 2)
289 local function fieldInt16Save(field
)
290 fieldSignedSave(field
, 2)
294 local function fieldFloatLoad(field
, data
, offset
)
295 field
.value
= fieldGetValue(data
, offset
, 4)
296 field
.min = fieldGetValue(data
, offset
+4, 4)
297 field
.max = fieldGetValue(data
, offset
+8, 4)
298 --field.default = fieldGetValue(data, offset+12, 4)
299 fieldUnsignedToSigned(field
, 4)
300 field
.prec
= data
[offset
+16]
301 if field
.prec
> 3 then
304 field
.step
= fieldGetValue(data
, offset
+17, 4)
305 field
.unit
= fieldGetString(data
, offset
+21, field
.unit
)
308 local function formatFloat(num
, decimals
)
309 local mult
= 10^
(decimals
or 0)
310 local val
= num
/ mult
311 return string.format("%." .. decimals
.. "f", val
)
314 local function fieldFloatDisplay(field
, y
, attr
)
315 lcd
.drawText(COL2
, y
, formatFloat(field
.value
, field
.prec
) .. field
.unit
, attr
)
318 local function fieldFloatSave(field
)
319 fieldUnsignedSave(field
, 4)
323 local function fieldTextSelectionLoad(field
, data
, offset
)
324 field
.values
, offset
= fieldGetSelectOpts(data
, offset
, field
.values
)
325 field
.value
= data
[offset
]
326 -- min max and default (offset+1 to 3) are not used on selections
327 field
.unit
= fieldGetString(data
, offset
+4, field
.unit
)
330 local function fieldTextSelectionSave(field
)
331 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, field
.id
, field
.value
})
334 local function fieldTextSelectionDisplay(field
, y
, attr
)
335 lcd
.drawText(COL2
, y
, (field
.values
[field
.value
+1] or "ERR") .. field
.unit
, attr
)
339 local function fieldStringLoad(field
, data
, offset
)
340 field
.value
, offset
= fieldGetString(data
, offset
)
341 if #data
>= offset
then
342 field
.maxlen
= data
[offset
]
346 local function fieldStringSave(field
)
347 local frame
= { deviceId
, handsetId
, field
.id
}
348 for i
=1, string.len(field
.value
) do
349 frame
[#frame
+ 1] = string.byte(field
.value
, i
)
351 frame
[#frame
+ 1] = 0
352 crossfireTelemetryPush(0x2D, frame
)
355 local function fieldStringDisplay(field
, y
, attr
)
356 if edit
and attr
then
357 lcd
.drawText(COL2
, y
, field
.value
, attr
)
358 lcd
.drawText(COL2
+6*(charIndex
-1), y
, string.sub(field
.value
, charIndex
, charIndex
), attr
)
360 lcd
.drawText(COL2
, y
, field
.value
, attr
)
364 local function fieldFolderOpen(field
)
365 folderAccess
= field
.id
366 local backFld
= fields
[backButtonId
]
367 -- Store the lineIndex and pageOffset to return to in the backFld
368 backFld
.li
= lineIndex
369 backFld
.po
= pageOffset
370 backFld
.parent
= folderAccess
376 local function fieldFolderDeviceOpen(field
)
377 crossfireTelemetryPush(0x28, { 0x00, 0xEA }) --broadcast with standard handset ID to get all node respond correctly
378 return fieldFolderOpen(field
)
381 local function fieldFolderDisplay(field
,y
,attr
)
382 lcd
.drawText(textXoffset
, y
, "> " .. field
.name
, bit32
.bor(attr
, BOLD
))
385 local function fieldCommandLoad(field
, data
, offset
)
386 field
.status
= data
[offset
]
387 field
.timeout
= data
[offset
+1]
388 field
.info
= fieldGetString(data
, offset
+2)
389 if field
.status
== 0 then
394 local function fieldCommandSave(field
)
395 if field
.status
< 4 then
397 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, field
.id
, field
.status
})
399 fieldPopup
.lastStatus
= 0
400 commandRunningIndicator
= 1
401 fieldTimeout
= getTime() + field
.timeout
405 local function fieldCommandDisplay(field
, y
, attr
)
406 lcd
.drawText(10, y
, "[" .. field
.name
.. "]", bit32
.bor(attr
, BOLD
))
409 local function UIbackExec()
410 local backFld
= fields
[backButtonId
]
411 lineIndex
= backFld
.li
or 1
412 pageOffset
= backFld
.po
or 0
420 local function changeDeviceId(devId
) --change to selected device ID
422 deviceIsELRS_TX
= nil
424 --if the selected device ID (target) is a TX Module, we use our Lua ID, so TX Flag that user is using our LUA
425 if devId
== 0xEE then
427 else --else we would act like the legacy lua
431 fields_count
= 0 --set this because next target wouldn't have the same count, and this trigger to request the new count
434 local function fieldDeviceIdSelect(field
)
435 local device
= getDevice(field
.name
)
436 changeDeviceId(device
.id
)
437 crossfireTelemetryPush(0x28, { 0x00, 0xEA })
440 local function createDeviceFields() -- put other devices in the field list
441 fields
[fields_count
+ 2 + #devices
] = fields
[backButtonId
]
442 backButtonId
= fields_count
+ 2 + #devices
-- move back button to the end of the list, so it will always show up at the bottom.
444 if devices
[i
].id
== deviceId
then
445 fields
[fields_count
+1+i
] = {id
= fields_count
+1+i
, name
=devices
[i
].name
, parent
= 255, type=15}
447 fields
[fields_count
+1+i
] = {id
= fields_count
+1+i
, name
=devices
[i
].name
, parent
= fields_count
+1, type=15}
452 local function parseDeviceInfoMessage(data
)
456 devicesName
, offset
= fieldGetString(data
, 3)
457 local device
= getDevice(devicesName
)
458 if device
== nil then
459 device
= { id
= id
, name
= devicesName
}
460 devices
[#devices
+ 1] = device
462 if deviceId
== id
then
463 deviceName
= devicesName
464 deviceIsELRS_TX
= ((fieldGetValue(data
,offset
,4) == 0x454C5253) and (deviceId
== 0xEE)) or nil -- SerialNumber = 'E L R S' and ID is TX module
465 local newFieldCount
= data
[offset
+12]
467 if newFieldCount
~= fields_count
or newFieldCount
== 0 then
468 fields_count
= newFieldCount
470 fields
[fields_count
+1] = {id
= fields_count
+1, name
="Other Devices", parent
= 255, type=16} -- add other devices folders
471 if newFieldCount
== 0 then
481 { load
=fieldUint8Load
, save
=fieldUint8Save
, display
=fieldIntDisplay
}, --1 UINT8(0)
482 { load
=fieldInt8Load
, save
=fieldInt8Save
, display
=fieldIntDisplay
}, --2 INT8(1)
483 { load
=fieldUint16Load
, save
=fieldUint16Save
, display
=fieldIntDisplay
}, --3 UINT16(2)
484 { load
=fieldInt16Load
, save
=fieldInt16Save
, display
=fieldIntDisplay
}, --4 INT16(3)
489 { load
=fieldFloatLoad
, save
=fieldFloatSave
, display
=fieldFloatDisplay
}, --9 FLOAT(8)
490 { load
=fieldTextSelectionLoad
, save
=fieldTextSelectionSave
, display
=fieldTextSelectionDisplay
}, --10 SELECT(9)
491 { load
=fieldStringLoad
, save
=fieldStringSave
, display
=fieldStringDisplay
}, --11 STRING(10)
492 { load
=nil, save
=fieldFolderOpen
, display
=fieldFolderDisplay
}, --12 FOLDER(11)
493 { load
=fieldStringLoad
, save
=fieldStringSave
, display
=fieldStringDisplay
}, --13 INFO(12)
494 { load
=fieldCommandLoad
, save
=fieldCommandSave
, display
=fieldCommandDisplay
}, --14 COMMAND(13)
495 { load
=nil, save
=UIbackExec
, display
=fieldCommandDisplay
}, --15 back(14)
496 { load
=nil, save
=fieldDeviceIdSelect
, display
=fieldCommandDisplay
}, --16 device(15)
497 { load
=nil, save
=fieldFolderDeviceOpen
, display
=fieldFolderDisplay
}, --17 deviceFOLDER(16)
500 local function parseParameterInfoMessage(data
)
501 if data
[2] ~= deviceId
or data
[3] ~= fieldId
then
506 if #fieldData
== 0 then
509 local field
= fields
[fieldId
]
510 local chunks
= data
[4]
511 if not field
or (chunks
~= expectedChunks
and expectedChunks
~= -1) then
512 return -- we will ignore this and subsequent chunks till we send a new command
514 expectedChunks
= chunks
- 1
516 fieldData
[#fieldData
+ 1] = data
[i
]
519 fieldChunk
= fieldChunk
+ 1
523 if #fieldData
< 4 then -- short packet, invalid
525 return -- no data extraction
529 local parent
= (fieldData
[1] ~= 0) and fieldData
[1] or nil
530 local type = fieldData
[2] % 128
531 local hidden
= (bit32
.rshift(fieldData
[2], 7) == 1) or nil
533 if field
.name
~= nil then -- already seen this field before, so we can validate this packet is correct
534 if field
.parent
~= parent
or field
.type ~= type or field
.hidden
~= hidden
then
536 return -- no data extraction
539 field
.parent
= parent
541 field
.hidden
= hidden
542 field
.name
, offset
= fieldGetString(fieldData
, 3, field
.name
)
543 if functions
[field
.type+1].load
then
544 functions
[field
.type+1].load(field
, fieldData
, offset
)
546 if field
.min == 0 then field
.min = nil end
547 if field
.max == 0 then field
.max = nil end
549 if not fieldPopup
then
550 if fieldId
== fields_count
then
554 elseif allParamsLoaded
== 0 then
555 -- advance to the next field if doing a full load
556 fieldId
= 1 + (fieldId
% (#fields
-1))
558 fieldTimeout
= getTime() + 200
560 fieldTimeout
= getTime() + fieldPopup
.timeout
567 local function parseElrsInfoMessage(data
)
568 if data
[2] ~= deviceId
then
574 local badPkt
= data
[3]
575 local goodPkt
= (data
[4]*256) + data
[5]
577 elrsFlagsInfo
= fieldGetString(data
, 7)
579 local state
= (bit32
.btest(elrsFlags
, 1) and "C") or "-"
580 goodBadPkt
= string.format("%u/%u %s", badPkt
, goodPkt
, state
)
583 local function refreshNext()
584 local command
, data
= crossfireTelemetryPop()
585 if command
== 0x29 then
586 parseDeviceInfoMessage(data
)
587 elseif command
== 0x2B then
588 parseParameterInfoMessage(data
)
589 if allParamsLoaded
< 1 or statusComplete
== 0 then
590 fieldTimeout
= 0 -- go fast until we have complete status record
592 elseif command
== 0x2E then
593 parseElrsInfoMessage(data
)
596 local time
= getTime()
598 if time
> fieldTimeout
and fieldPopup
.status
~= 3 then
599 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, fieldPopup
.id
, 6 })
600 fieldTimeout
= time
+ fieldPopup
.timeout
602 elseif time
> devicesRefreshTimeout
and fields_count
< 1 then
603 devicesRefreshTimeout
= time
+ 100 -- 1s
604 crossfireTelemetryPush(0x28, { 0x00, 0xEA })
605 elseif time
> linkstatTimeout
then
606 if not deviceIsELRS_TX
and allParamsLoaded
== 1 then
608 -- enable both line below to do what the legacy lua is doing which is reloading all params in an interval
610 -- linkstatTimeout = time + 300 --reload all param every 3s if not elrs
612 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, 0x0, 0x0 }) --request linkstat
614 linkstatTimeout
= time
+ 100
615 elseif time
> fieldTimeout
and fields_count
~= 0 and not edit
then
616 if allParamsLoaded
< 1 or statusComplete
== 0 then
617 crossfireTelemetryPush(0x2C, { deviceId
, handsetId
, fieldId
, fieldChunk
})
618 fieldTimeout
= time
+ 50 -- 0.5s
622 if time
> titleShowWarnTimeout
then
623 -- if elrsFlags bit set is bit higher than bit 0 and bit 1, it is warning flags
624 titleShowWarn
= (elrsFlags
> 3 and not titleShowWarn
) or nil
625 titleShowWarnTimeout
= time
+ 100
629 local lcd_title
-- holds function that is color/bw version
630 local function lcd_title_color()
633 local EBLUE
= lcd
.RGB(0x43, 0x61, 0xAA)
634 local EGREEN
= lcd
.RGB(0x9f, 0xc7, 0x6f)
635 local EGREY1
= lcd
.RGB(0x91, 0xb2, 0xc9)
636 local EGREY2
= lcd
.RGB(0x6f, 0x62, 0x7f)
639 -- Field display area (white w/ 2px green border)
640 lcd
.setColor(CUSTOM_COLOR
, EGREEN
)
641 lcd
.drawRectangle(0, 0, LCD_W
, LCD_H
, CUSTOM_COLOR
)
642 lcd
.drawRectangle(1, 0, LCD_W
- 2, LCD_H
- 1, CUSTOM_COLOR
)
644 lcd
.drawFilledRectangle(0, 0, LCD_W
, barHeight
, CUSTOM_COLOR
)
645 lcd
.setColor(CUSTOM_COLOR
, EGREY1
)
646 lcd
.drawFilledRectangle(LCD_W
- textSize
, 0, textSize
, barHeight
, CUSTOM_COLOR
)
647 lcd
.setColor(CUSTOM_COLOR
, EGREY2
)
648 lcd
.drawRectangle(LCD_W
- textSize
, 0, textSize
, barHeight
- 1, CUSTOM_COLOR
)
649 lcd
.drawRectangle(LCD_W
- textSize
, 1 , textSize
- 1, barHeight
- 2, CUSTOM_COLOR
) -- left and bottom line only 1px, make it look bevelled
650 lcd
.setColor(CUSTOM_COLOR
, BLACK
)
651 if titleShowWarn
then
652 lcd
.drawText(textXoffset
+ 1, 4, elrsFlagsInfo
, CUSTOM_COLOR
)
653 lcd
.drawText(LCD_W
- textSize
- 5, 4, tostring(elrsFlags
), RIGHT
+ BOLD
+ CUSTOM_COLOR
)
655 local title
= allParamsLoaded
== 1 and deviceName
or "Loading..."
656 lcd
.drawText(textXoffset
+ 1, 4, title
, CUSTOM_COLOR
)
657 lcd
.drawText(LCD_W
- 5, 4, goodBadPkt
, RIGHT
+ BOLD
+ CUSTOM_COLOR
)
660 if allParamsLoaded
~= 1 and fields_count
> 0 then
661 local barW
= (COL2
-4)*fieldId
/fields_count
662 lcd
.setColor(CUSTOM_COLOR
, EBLUE
)
663 lcd
.drawFilledRectangle(2, 2+20, barW
, barHeight
-5-20, CUSTOM_COLOR
)
664 lcd
.setColor(CUSTOM_COLOR
, WHITE
)
665 lcd
.drawFilledRectangle(2+barW
, 2+20, COL2
-2-barW
, barHeight
-5-20, CUSTOM_COLOR
)
669 local function lcd_title_bw()
673 if titleShowWarn
then
674 lcd
.drawText(LCD_W
, 1, tostring(elrsFlags
), RIGHT
)
676 lcd
.drawText(LCD_W
- 1, 1, goodBadPkt
, RIGHT
)
677 lcd
.drawLine(LCD_W
- 10, 0, LCD_W
- 10, barHeight
-1, SOLID
, INVERS
)
680 if allParamsLoaded
~= 1 and fields_count
> 0 then
681 lcd
.drawFilledRectangle(COL2
, 0, LCD_W
, barHeight
, GREY_DEFAULT
)
682 lcd
.drawGauge(0, 0, COL2
, barHeight
, fieldId
, fields_count
, 0)
684 lcd
.drawFilledRectangle(0, 0, LCD_W
, barHeight
, GREY_DEFAULT
)
685 if titleShowWarn
then
686 lcd
.drawText(textXoffset
, 1, elrsFlagsInfo
, INVERS
)
688 local title
= allParamsLoaded
== 1 and deviceName
or "Loading..."
689 lcd
.drawText(textXoffset
, 1, title
, INVERS
)
694 local function lcd_warn()
695 lcd
.drawText(textSize
*3, textSize
*2, tostring(elrsFlags
).." : "..elrsFlagsInfo
, 0)
696 lcd
.drawText(textSize
*10, textSize
*6, "ok", BLINK
+ INVERS
)
699 local function handleDevicePageEvent(event
)
700 if #fields
== 0 then --if there is no field yet
703 if fields
[backButtonId
].name
== nil then --if back button is not assigned yet, means there is no field yet.
708 if event
== EVT_VIRTUAL_EXIT
then -- exit script
709 if edit
then -- reload the field
711 local field
= getField(lineIndex
)
712 fieldTimeout
= getTime() + 200 -- 2s
713 fieldId
, fieldChunk
= field
.id
, 0
715 crossfireTelemetryPush(0x2C, { deviceId
, handsetId
, fieldId
, fieldChunk
})
717 if folderAccess
== nil and allParamsLoaded
== 1 then -- only do reload if we're in the root folder and finished loading.
718 if deviceId
~= 0xEE then
719 changeDeviceId(0xEE) --change device id clear the fields_count, therefore the next ping will do reloadAllField()
723 crossfireTelemetryPush(0x28, { 0x00, 0xEA })
727 elseif event
== EVT_VIRTUAL_ENTER
then -- toggle editing/selecting current field
728 if elrsFlags
> 0x1F then
730 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, 0x2E, 0x00 })
732 local field
= getField(lineIndex
)
733 if field
and field
.name
then
734 if field
.type == 10 then
739 charIndex
= charIndex
+ 1
741 elseif field
.type < 11 then
745 if field
.type < 11 or field
.type == 13 then
746 -- For editable field types and commands, request this field's
747 -- data again, with a short delay to allow the module EEPROM to
748 -- commit. Do this before save() to allow save to override
749 fieldTimeout
= getTime() + 20
750 fieldId
, fieldChunk
, statusComplete
= field
.id
, 0, 0
753 functions
[field
.type+1].save(field
)
758 if event
== EVT_VIRTUAL_NEXT
then
760 elseif event
== EVT_VIRTUAL_PREV
then
764 if event
== EVT_VIRTUAL_NEXT
then
766 elseif event
== EVT_VIRTUAL_PREV
then
773 local function runDevicePage(event
)
774 handleDevicePageEvent(event
)
778 if #devices
> 1 then -- show other device folder
779 fields
[fields_count
+1].parent
= nil
781 if elrsFlags
> 0x1F then
784 for y
= 1, maxLineIndex
+1 do
785 local field
= getField(pageOffset
+y
)
788 elseif field
.name
~= nil then
789 local attr
= lineIndex
== (pageOffset
+y
)
790 and ((edit
and BLINK
or 0) + INVERS
)
792 if field
.type < 11 or field
.type == 12 then -- if not folder, command, or back
793 lcd
.drawText(textXoffset
, y
*textSize
+textYoffset
, field
.name
, 0)
795 if functions
[field
.type+1].display
then
796 functions
[field
.type+1].display(field
, y
*textSize
+textYoffset
, attr
)
803 local function popupCompat(t
, m
, e
)
804 -- Only use 2 of 3 arguments for older platforms
805 return popupConfirmation(t
, e
)
808 local function runPopupPage(event
)
809 if event
== EVT_VIRTUAL_EXIT
then -- exit script
810 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, fieldPopup
.id
, 5 })
811 fieldTimeout
= getTime() + 200 -- 2s
815 if fieldPopup
.status
== 0 and fieldPopup
.lastStatus
~= 0 then -- stopped
816 popupCompat(fieldPopup
.info
, "Stopped!", event
)
819 elseif fieldPopup
.status
== 3 then -- confirmation required
820 result
= popupCompat(fieldPopup
.info
, "PRESS [OK] to confirm", event
)
821 fieldPopup
.lastStatus
= fieldPopup
.status
822 if result
== "OK" then
823 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, fieldPopup
.id
, 4 })
824 fieldTimeout
= getTime() + fieldPopup
.timeout
-- we are expecting an immediate response
825 fieldPopup
.status
= 4
826 elseif result
== "CANCEL" then
829 elseif fieldPopup
.status
== 2 then -- running
830 if statusComplete
then
831 commandRunningIndicator
= (commandRunningIndicator
% 4) + 1
833 result
= popupCompat(fieldPopup
.info
.. " [" .. string.sub("|/-\\", commandRunningIndicator
, commandRunningIndicator
) .. "]", "Press [RTN] to exit", event
)
834 fieldPopup
.lastStatus
= fieldPopup
.status
835 if result
== "CANCEL" then
836 crossfireTelemetryPush(0x2D, { deviceId
, handsetId
, fieldPopup
.id
, 5 })
837 fieldTimeout
= getTime() + fieldPopup
.timeout
-- we are expecting an immediate response
843 local function setLCDvar()
844 -- Set the title function depending on if LCD is color, and free the other function
845 lcd_title
= (lcd
.RGB
~= nil) and lcd_title_color
or lcd_title_bw
846 lcd_title_color
= nil
848 -- Determine if popupConfirmation takes 3 arguments or 2
849 --if pcall(popupConfirmation, "", "", EVT_VIRTUAL_EXIT) then
850 -- major 1 is assumed to be FreedomTX
851 local ver
, radio
, major
= getVersion()
853 popupCompat
= popupConfirmation
860 textSize
= 22 --textSize is text Height
861 elseif LCD_W
== 320 then
880 local function setMock()
881 -- Setup fields to display if running in Simulator
882 local _
, rv
= getVersion()
883 if string.sub(rv
, -5) ~= "-simu" then return end
884 local mock
= loadScript("mockup/elrsmock.lua")
885 if mock
== nil then return end
886 fields
, goodBadPkt
, deviceName
= mock(), "0/500 C", "ExpressLRS TX"
887 fields_count
= #fields
- 1
889 deviceIsELRS_TX
= true
891 backButtonId
= #fields
895 local function init()
903 local function run(event
)
905 error("Cannot be run as a model script!")
909 if fieldPopup
~= nil then
920 return { init
=init
, run
=run
}