makes GPIO_PIN_RST optional for the sx1276
[ExpressLRS.git] / src / lua / elrsV2.lua
blob0800a881237e41ce92273ca181ac70f203a7270c
1 -- TNS|ExpressLRS|TNE
2 ---- #########################################################################
3 ---- # #
4 ---- # Copyright (C) OpenTX #
5 -----# #
6 ---- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html #
7 ---- # #
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. #
11 ---- # #
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. #
16 ---- # #
17 ---- #########################################################################
18 local deviceId = 0xEE
19 local handsetId = 0xEF
20 local deviceName = ""
21 local lineIndex = 1
22 local pageOffset = 0
23 local edit = nil
24 local charIndex = 1
25 local fieldPopup
26 local fieldTimeout = 0
27 local fieldId = 1
28 local fieldChunk = 0
29 local fieldData = {}
30 local fields = {}
31 local devices = {}
32 local goodBadPkt = "?/??? ?"
33 local elrsFlags = 0
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
48 local COL2
49 local maxLineIndex
50 local textXoffset
51 local textYoffset
52 local textSize
54 local function allocateFields()
55 fields = {}
56 for i=1, fields_count + 2 + #devices do
57 fields[i] = { }
58 end
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
63 end
64 end
66 local function reloadAllField()
67 allParamsLoaded = 0
68 fieldId, fieldChunk = 1, 0
69 fieldData = {}
70 end
72 local function getField(line)
73 local counter = 1
74 for i = 1, #fields do
75 local field = fields[i]
76 if folderAccess == field.parent and not field.hidden then
77 if counter < line then
78 counter = counter + 1
79 else
80 return field
81 end
82 end
83 end
84 end
86 local function constrain(x, low, high)
87 if x < low then
88 return low
89 elseif x > high then
90 return high
91 end
92 return x
93 end
95 -- Change display attribute to current field
96 local function incrField(step)
97 local field = getField(lineIndex)
98 if field.type == 10 then
99 local byte = 32
100 if charIndex <= #field.value then
101 byte = string.byte(field.value, charIndex) + step
103 if byte < 32 then
104 byte = 32
105 elseif byte > 122 then
106 byte = 122
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)
110 else
111 field.value = field.value .. string.char(byte)
113 else
114 local min, max = 0, 0
115 if ((field.type <= 5) or (field.type == 8)) then
116 min = field.min or 0
117 max = field.max or 0
118 step = field.step * step
119 elseif field.type == 9 then
120 min = 0
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
130 local field
131 repeat
132 newLineIndex = newLineIndex + step
133 if newLineIndex <= 0 then
134 newLineIndex = #fields
135 elseif newLineIndex == 1 + #fields then
136 newLineIndex = 1
137 pageOffset = 0
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
151 offset = offset + 1
153 return last, offset + 1
156 local function fieldGetSelectOpts(data, offset, last)
157 if last then
158 return fieldStrFF(data, offset, last)
161 -- Split a table of byte values (string) with ; separator into a table
162 local r = {}
163 local opt = ''
164 local b = data[offset]
165 while b ~= 0 do
166 if b == 59 then -- ';'
167 r[#r+1] = opt
168 opt = ''
169 else
170 opt = opt .. string.char(b)
172 offset = offset + 1
173 b = data[offset]
176 r[#r+1] = opt
177 return r, offset + 1
180 local function fieldGetString(data, offset, last)
181 if last then
182 return fieldStrFF(data, offset, last)
185 local result = ""
186 while data[offset] ~= 0 do
187 result = result .. string.char(data[offset])
188 offset = offset + 1
191 return result, offset + 1
194 local function getDevice(name)
195 for i=1, #devices do
196 if devices[i].name == name then
197 return devices[i]
202 local function fieldGetValue(data, offset, size)
203 local result = 0
204 for i=0, size-1 do
205 result = bit32.lshift(result, 8) + data[offset + i]
207 return result
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)
216 field.step = 1
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
247 if value < 0 then
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)
257 -- UINT8
258 local function fieldUint8Load(field, data, offset)
259 fieldUnsignedLoad(field, data, offset, 1)
262 local function fieldUint8Save(field)
263 fieldUnsignedSave(field, 1)
266 -- INT8
267 local function fieldInt8Load(field, data, offset)
268 fieldSignedLoad(field, data, offset, 1)
271 local function fieldInt8Save(field)
272 fieldSignedSave(field, 1)
275 -- UINT16
276 local function fieldUint16Load(field, data, offset)
277 fieldUnsignedLoad(field, data, offset, 2)
280 local function fieldUint16Save(field)
281 fieldUnsignedSave(field, 2)
284 -- INT16
285 local function fieldInt16Load(field, data, offset)
286 fieldSignedLoad(field, data, offset, 2)
289 local function fieldInt16Save(field)
290 fieldSignedSave(field, 2)
293 -- FLOAT
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
302 field.prec = 3
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)
322 -- TEXT SELECTION
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)
338 -- STRING
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)
359 else
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
372 lineIndex = 1
373 pageOffset = 0
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
390 fieldPopup = nil
394 local function fieldCommandSave(field)
395 if field.status < 4 then
396 field.status = 1
397 crossfireTelemetryPush(0x2D, { deviceId, handsetId, field.id, field.status })
398 fieldPopup = field
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
414 backFld.parent = 255
415 backFld.li = nil
416 backFld.po = nil
417 folderAccess = nil
420 local function changeDeviceId(devId) --change to selected device ID
421 folderAccess = nil
422 deviceIsELRS_TX = nil
423 elrsFlags = 0
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
426 handsetId = 0xEF
427 else --else we would act like the legacy lua
428 handsetId = 0xEA
430 deviceId = devId
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.
443 for i=1, #devices do
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}
446 else
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)
453 local offset
454 local id = data[2]
455 local devicesName
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]
466 reloadAllField()
467 if newFieldCount ~= fields_count or newFieldCount == 0 then
468 fields_count = newFieldCount
469 allocateFields()
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
472 allParamsLoaded = 1
473 fieldId = 1
474 createDeviceFields()
480 local functions = {
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)
485 nil,
486 nil,
487 nil,
488 nil,
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
502 fieldData = {}
503 fieldChunk = 0
504 return
506 if #fieldData == 0 then
507 expectedChunks = -1
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
515 for i=5, #data do
516 fieldData[#fieldData + 1] = data[i]
518 if chunks > 0 then
519 fieldChunk = fieldChunk + 1
520 statusComplete = 0
521 else
522 fieldChunk = 0
523 if #fieldData < 4 then -- short packet, invalid
524 fieldData = {}
525 return -- no data extraction
528 field.id = fieldId
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
532 local offset
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
535 fieldData = {}
536 return -- no data extraction
539 field.parent = parent
540 field.type = type
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
551 allParamsLoaded = 1
552 fieldId = 1
553 createDeviceFields()
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
559 else
560 fieldTimeout = getTime() + fieldPopup.timeout
562 statusComplete = 1
563 fieldData = {}
567 local function parseElrsInfoMessage(data)
568 if data[2] ~= deviceId then
569 fieldData = {}
570 fieldChunk = 0
571 return
574 local badPkt = data[3]
575 local goodPkt = (data[4]*256) + data[5]
576 elrsFlags = data[6]
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()
597 if fieldPopup then
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
607 goodBadPkt = ""
608 -- enable both line below to do what the legacy lua is doing which is reloading all params in an interval
609 -- reloadAllField()
610 -- linkstatTimeout = time + 300 --reload all param every 3s if not elrs
611 else
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()
631 lcd.clear()
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)
637 local barHeight = 30
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)
643 -- title bar
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)
654 else
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)
659 -- progress bar
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()
670 lcd.clear()
671 -- B&W screen
672 local barHeight = 9
673 if titleShowWarn then
674 lcd.drawText(LCD_W, 1, tostring(elrsFlags), RIGHT)
675 else
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)
683 else
684 lcd.drawFilledRectangle(0, 0, LCD_W, barHeight, GREY_DEFAULT)
685 if titleShowWarn then
686 lcd.drawText(textXoffset, 1, elrsFlagsInfo, INVERS)
687 else
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
701 return
702 else
703 if fields[backButtonId].name == nil then --if back button is not assigned yet, means there is no field yet.
704 return
708 if event == EVT_VIRTUAL_EXIT then -- exit script
709 if edit then -- reload the field
710 edit = nil
711 local field = getField(lineIndex)
712 fieldTimeout = getTime() + 200 -- 2s
713 fieldId, fieldChunk = field.id, 0
714 fieldData = {}
715 crossfireTelemetryPush(0x2C, { deviceId, handsetId, fieldId, fieldChunk })
716 else
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()
720 else
721 reloadAllField()
723 crossfireTelemetryPush(0x28, { 0x00, 0xEA })
725 UIbackExec()
727 elseif event == EVT_VIRTUAL_ENTER then -- toggle editing/selecting current field
728 if elrsFlags > 0x1F then
729 elrsFlags = 0
730 crossfireTelemetryPush(0x2D, { deviceId, handsetId, 0x2E, 0x00 })
731 else
732 local field = getField(lineIndex)
733 if field and field.name then
734 if field.type == 10 then
735 if not edit then
736 edit = true
737 charIndex = 1
738 else
739 charIndex = charIndex + 1
741 elseif field.type < 11 then
742 edit = not edit
744 if not edit 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
751 fieldData = {}
753 functions[field.type+1].save(field)
757 elseif edit then
758 if event == EVT_VIRTUAL_NEXT then
759 incrField(1)
760 elseif event == EVT_VIRTUAL_PREV then
761 incrField(-1)
763 else
764 if event == EVT_VIRTUAL_NEXT then
765 selectField(1)
766 elseif event == EVT_VIRTUAL_PREV then
767 selectField(-1)
772 -- Main
773 local function runDevicePage(event)
774 handleDevicePageEvent(event)
776 lcd_title()
778 if #devices > 1 then -- show other device folder
779 fields[fields_count+1].parent = nil
781 if elrsFlags > 0x1F then
782 lcd_warn()
783 else
784 for y = 1, maxLineIndex+1 do
785 local field = getField(pageOffset+y)
786 if not field then
787 break
788 elseif field.name ~= nil then
789 local attr = lineIndex == (pageOffset+y)
790 and ((edit and BLINK or 0) + INVERS)
791 or 0
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
814 local result
815 if fieldPopup.status == 0 and fieldPopup.lastStatus ~= 0 then -- stopped
816 popupCompat(fieldPopup.info, "Stopped!", event)
817 reloadAllField()
818 fieldPopup = nil
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
827 fieldPopup = nil
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
838 fieldPopup = nil
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
847 lcd_title_bw = 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()
852 if major ~= 1 then
853 popupCompat = popupConfirmation
855 if LCD_W == 480 then
856 COL2 = 240
857 maxLineIndex = 10
858 textXoffset = 3
859 textYoffset = 10
860 textSize = 22 --textSize is text Height
861 elseif LCD_W == 320 then
862 COL2 = 160
863 maxLineIndex = 14
864 textXoffset = 3
865 textYoffset = 10
866 textSize = 22
867 else
868 if LCD_W == 212 then
869 COL2 = 110
870 else
871 COL2 = 70
873 maxLineIndex = 6
874 textXoffset = 0
875 textYoffset = 3
876 textSize = 8
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
888 fieldId = #fields
889 deviceIsELRS_TX = true
890 allParamsLoaded = 1
891 backButtonId = #fields
894 -- Init
895 local function init()
896 setLCDvar()
897 setMock()
898 setLCDvar = nil
899 setMock = nil
902 -- Main
903 local function run(event)
904 if event == nil then
905 error("Cannot be run as a model script!")
906 return 2
909 if fieldPopup ~= nil then
910 runPopupPage(event)
911 else
912 runDevicePage(event)
915 refreshNext()
917 return 0
920 return { init=init, run=run }