1 ---- #########################################################################
3 ---- # Copyright (C) OpenTX #
5 ---- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html #
7 ---- # This program is free software; you can redistribute it and/or modify #
8 ---- # it under the terms of the GNU General Public License version 2 as #
9 ---- # published by the Free Software Foundation. #
11 ---- # This program is distributed in the hope that it will be useful #
12 ---- # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 ---- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 ---- # GNU General Public License for more details. #
16 ---- #########################################################################
24 local fieldTimeout
= 0
30 local function getField(line
)
33 local field
= fields
[i
]
34 if not field
.hidden
then
35 if counter
< line
then
44 local function initLineIndex()
47 local field
= getField(i
)
48 if field
and field
.type ~= 11 and field
.type ~= 12 and field
.name
~= nil then
55 -- Change display attribute to current field
56 local function incrField(step
)
57 local field
= getField(lineIndex
)
58 if field
.type == 10 then
60 if charIndex
<= #field
.value
then
61 byte
= string.byte(field
.value
, charIndex
) + step
65 elseif byte
> 122 then
68 if charIndex
<= #field
.value
then
69 field
.value
= string.sub(field
.value
, 1, charIndex
-1) .. string.char(byte
) .. string.sub(field
.value
, charIndex
+1)
71 field
.value
= field
.value
.. string.char(byte
)
75 if field
.type <= 5 then
78 step
= field
.step
* step
79 elseif field
.type == 9 then
81 max = #field
.values
- 1
83 if (step
< 0 and field
.value
> min) or (step
> 0 and field
.value
< max) then
84 field
.value
= field
.value
+ step
89 -- Select the next or previous editable field
90 local function selectField(step
)
91 local newLineIndex
= lineIndex
94 newLineIndex
= newLineIndex
+ step
95 if newLineIndex
== 0 then
96 newLineIndex
= #fields
97 elseif newLineIndex
== 1 + #fields
then
101 field
= getField(newLineIndex
)
102 until newLineIndex
== lineIndex
or (field
and field
.type ~= 11 and field
.name
)
103 lineIndex
= newLineIndex
104 if lineIndex
> 7 + pageOffset
then
105 pageOffset
= lineIndex
- 7
106 elseif lineIndex
<= pageOffset
then
107 pageOffset
= lineIndex
- 1
111 local function split(str
)
114 for s
in string.gmatch(str
, "([^;]+)") do
121 local function fieldGetString(data
, offset
)
123 while data
[offset
] ~= 0 do
124 result
= result
.. string.char(data
[offset
])
128 return result
, offset
131 local function parseDeviceInfoMessage(data
)
134 deviceName
, offset
= fieldGetString(data
, 3)
135 local fields_count
= data
[offset
+12]
136 for i
=1, fields_count
do
137 fields
[i
] = { name
=nil }
141 local function fieldGetValue(data
, offset
, size
)
144 result
= bit32
.lshift(result
, 8) + data
[offset
+ i
]
149 local function fieldUnsignedLoad(field
, data
, offset
, size
)
150 field
.value
= fieldGetValue(data
, offset
, size
)
151 field
.min = fieldGetValue(data
, offset
+size
, size
)
152 field
.max = fieldGetValue(data
, offset
+2*size
, size
)
153 field
.default
= fieldGetValue(data
, offset
+3*size
, size
)
154 field
.unit
, offset
= fieldGetString(data
, offset
+4*size
)
158 local function fieldUnsignedToSigned(field
, size
)
159 local bandval
= bit32
.lshift(0x80, (size
-1)*8)
160 field
.value
= field
.value
- bit32
.band(field
.value
, bandval
) * 2
161 field
.min = field
.min - bit32
.band(field
.min, bandval
) * 2
162 field
.max = field
.max - bit32
.band(field
.max, bandval
) * 2
163 field
.default
= field
.default
- bit32
.band(field
.default
, bandval
) * 2
166 local function fieldSignedLoad(field
, data
, offset
, size
)
167 fieldUnsignedLoad(field
, data
, offset
, size
)
168 fieldUnsignedToSigned(field
, size
)
171 local function fieldIntSave(index
, value
, size
)
172 local frame
= { deviceId
, 0xEA, index
}
173 for i
=size
-1, 0, -1 do
174 frame
[#frame
+ 1] = (bit32
.rshift(value
, 8*i
) % 256)
176 crossfireTelemetryPush(0x2D, frame
)
179 local function fieldUnsignedSave(field
, size
)
180 local value
= field
.value
181 fieldIntSave(field
.id
, value
, size
)
184 local function fieldSignedSave(field
, size
)
185 local value
= field
.value
187 value
= bit32
.lshift(0x100, (size
-1)*8) + value
189 fieldIntSave(field
.id
, value
, size
)
192 local function fieldIntDisplay(field
, y
, attr
)
193 lcd
.drawNumber(89, y
, field
.value
, LEFT
+ attr
)
194 lcd
.drawText(lcd
.getLastPos(), y
, field
.unit
, attr
)
198 local function fieldUint8Load(field
, data
, offset
)
199 fieldUnsignedLoad(field
, data
, offset
, 1)
202 local function fieldUint8Save(field
)
203 fieldUnsignedSave(field
, 1)
207 local function fieldInt8Load(field
, data
, offset
)
208 fieldSignedLoad(field
, data
, offset
, 1)
211 local function fieldInt8Save(field
)
212 fieldSignedSave(field
, 1)
216 local function fieldUint16Load(field
, data
, offset
)
217 fieldUnsignedLoad(field
, data
, offset
, 2)
220 local function fieldUint16Save(field
)
221 fieldUnsignedSave(field
, 2)
225 local function fieldInt16Load(field
, data
, offset
)
226 fieldSignedLoad(field
, data
, offset
, 2)
229 local function fieldInt16Save(field
)
230 fieldSignedSave(field
, 2)
234 local function fieldFloatLoad(field
, data
, offset
)
235 field
.value
= fieldGetValue(data
, offset
, 4)
236 field
.min = fieldGetValue(data
, offset
+4, 4)
237 field
.max = fieldGetValue(data
, offset
+8, 4)
238 field
.default
= fieldGetValue(data
, offset
+12, 4)
239 fieldUnsignedToSigned(field
, 4)
240 field
.prec
= data
[offset
+16]
241 if field
.prec
> 2 then
244 field
.step
= fieldGetValue(data
, offset
+17, 4)
245 field
.unit
, offset
= fieldGetString(data
, offset
+21)
248 local function fieldFloatDisplay(field
, y
, attr
)
250 if field
.prec
== 1 then
251 attrnum
= LEFT
+ attr
+ PREC1
252 elseif field
.prec
== 2 then
253 attrnum
= LEFT
+ attr
+ PREC2
255 attrnum
= LEFT
+ attr
257 lcd
.drawNumber(89, y
, field
.value
, attrnum
)
258 lcd
.drawText(lcd
.getLastPos(), y
, field
.unit
, attr
)
261 local function fieldFloatSave(field
)
262 fieldUnsignedSave(field
, 4)
266 local function fieldTextSelectionLoad(field
, data
, offset
)
268 values
, offset
= fieldGetString(data
, offset
)
270 field
.values
= split(values
)
272 field
.value
= data
[offset
]
273 field
.min = data
[offset
+1]
274 field
.max = data
[offset
+2]
275 field
.default
= data
[offset
+3]
276 field
.unit
, offset
= fieldGetString(data
, offset
+4)
279 local function fieldTextSelectionSave(field
)
280 crossfireTelemetryPush(0x2D, { deviceId
, 0xEA, field
.id
, field
.value
})
283 local function fieldTextSelectionDisplay(field
, y
, attr
)
284 lcd
.drawText(89, y
, field
.values
[field
.value
+1], attr
)
285 lcd
.drawText(lcd
.getLastPos(), y
, field
.unit
, attr
)
289 local function fieldStringLoad(field
, data
, offset
)
290 field
.value
, offset
= fieldGetString(data
, offset
)
291 if #data
>= offset
then
292 field
.maxlen
= data
[offset
]
296 local function fieldStringSave(field
)
297 local frame
= { deviceId
, 0xEA, field
.id
}
298 for i
=1, string.len(field
.value
) do
299 frame
[#frame
+ 1] = string.byte(field
.value
, i
)
301 frame
[#frame
+ 1] = 0
302 crossfireTelemetryPush(0x2D, frame
)
305 local function fieldStringDisplay(field
, y
, attr
)
306 if edit
== true and attr
then
307 lcd
.drawText(89, y
, field
.value
, FIXEDWIDTH
)
308 lcd
.drawText(83+6*charIndex
, y
, string.sub(field
.value
, charIndex
, charIndex
), FIXEDWIDTH
+ attr
)
310 lcd
.drawText(89, y
, field
.value
, attr
)
314 local function fieldCommandLoad(field
, data
, offset
)
315 field
.status
= data
[offset
]
316 field
.timeout
= data
[offset
+1]
317 field
.info
, offset
= fieldGetString(data
, offset
+2)
318 if field
.status
< 2 or field
.status
> 3 then
323 local function fieldCommandSave(field
)
324 if field
.status
== 0 then
326 crossfireTelemetryPush(0x2D, { deviceId
, 0xEA, field
.id
, field
.status
})
328 fieldTimeout
= getTime() + field
.timeout
332 local function fieldCommandDisplay(field
, y
, attr
)
333 lcd
.drawText(0, y
, field
.name
, attr
)
334 if field
.info
~= "" then
335 lcd
.drawText(89, y
, "[" .. field
.info
.. "]")
340 { load
=fieldUint8Load
, save
=fieldUint8Save
, display
=fieldIntDisplay
},
341 { load
=fieldInt8Load
, save
=fieldInt8Save
, display
=fieldIntDisplay
},
342 { load
=fieldUint16Load
, save
=fieldUint16Save
, display
=fieldIntDisplay
},
343 { load
=fieldInt16Load
, save
=fieldInt16Save
, display
=fieldIntDisplay
},
348 { load
=fieldFloatLoad
, save
=fieldFloatSave
, display
=fieldFloatDisplay
},
349 { load
=fieldTextSelectionLoad
, save
=fieldTextSelectionSave
, display
=fieldTextSelectionDisplay
},
350 { load
=fieldStringLoad
, save
=fieldStringSave
, display
=fieldStringDisplay
},
352 { load
=fieldStringLoad
, save
=fieldStringSave
, display
=fieldStringDisplay
},
353 { load
=fieldCommandLoad
, save
=fieldCommandSave
, display
=fieldCommandDisplay
},
356 local function parseParameterInfoMessage(data
)
357 if data
[2] ~= deviceId
or data
[3] ~= fieldId
then
362 local field
= fields
[fieldId
]
363 local chunks
= data
[4]
365 fieldData
[#fieldData
+ 1] = data
[i
]
368 fieldChunk
= fieldChunk
+ 1
372 field
.parent
= fieldData
[1]
373 field
.type = fieldData
[2] % 128
374 field
.hidden
= (bit32
.rshift(fieldData
[2], 7) == 1)
375 local name
, i
= fieldGetString(fieldData
, 3)
378 local parent
= field
.parent
380 indent
= indent
.. " "
381 parent
= fields
[parent
].parent
383 field
.name
= indent
.. name
385 if functions
[field
.type+1] then
386 functions
[field
.type+1].load(field
, fieldData
, i
)
388 if not fieldPopup
then
389 if lineIndex
== 0 and field
.hidden
~= true and field
.type and field
.type ~= 11 and field
.type ~= 12 then
392 fieldId
= 1 + (fieldId
% #fields
)
398 local function refreshNext()
399 local command
, data
= crossfireTelemetryPop()
400 if command
== nil then
401 local time
= getTime()
403 if time
> fieldTimeout
then
404 local frame
= { deviceId
, 0xEA, fieldPopup
.id
}
405 crossfireTelemetryPush(0x2D, frame
)
406 fieldTimeout
= time
+ fieldPopup
.timeout
408 elseif time
> fieldTimeout
and not edit
then
409 crossfireTelemetryPush(0x2C, { deviceId
, 0xEA, fieldId
, fieldChunk
})
410 fieldTimeout
= time
+ 200 -- 2s
412 elseif command
== 0x29 then
413 parseDeviceInfoMessage(data
)
414 elseif command
== 0x2B then
415 parseParameterInfoMessage(data
)
421 local function runDevicePage(event
)
422 if event
== EVT_EXIT_BREAK
then -- exit script
425 local field
= getField(lineIndex
)
426 fieldTimeout
= getTime() + 200 -- 2s
427 fieldId
, fieldChunk
= field
.id
, 0
429 functions
[field
.type+1].save(field
)
431 return "crossfire.lua"
433 elseif event
== EVT_ROT_BREAK
then -- toggle editing/selecting current field
434 local field
= getField(lineIndex
)
436 if field
.type == 10 then
437 if edit
== false then
441 charIndex
= charIndex
+ 1
443 elseif field
.type < 11 then
446 if edit
== false then
447 fieldTimeout
= getTime() + 200 -- 2s
448 fieldId
, fieldChunk
= field
.id
, 0
450 functions
[field
.type+1].save(field
)
454 if event
== EVT_ROT_RIGHT
then
456 elseif event
== EVT_ROT_LEFT
then
460 if event
== EVT_ROT_RIGHT
then
462 elseif event
== EVT_ROT_LEFT
then
468 lcd
.drawScreenTitle(deviceName
, 0, 0)
470 local field
= getField(pageOffset
+y
)
473 elseif field
.name
== nil then
474 lcd
.drawText(0, 1+8*y
, "...")
476 local attr
= lineIndex
== (pageOffset
+y
) and ((edit
== true and BLINK
or 0) + INVERS
) or 0
477 lcd
.drawText(0, 1+8*y
, field
.name
)
478 if functions
[field
.type+1] then
479 functions
[field
.type+1].display(field
, 1+8*y
, attr
)
487 local function runPopupPage(event
)
489 if fieldPopup
.status
== 3 then
490 result
= popupConfirmation(fieldPopup
.info
, event
)
492 result
= popupWarning(fieldPopup
.info
, event
)
494 if result
== "OK" then
495 crossfireTelemetryPush(0x2D, { deviceId
, 0xEA, fieldPopup
.id
, 4 })
496 elseif result
== "CANCEL" then
497 crossfireTelemetryPush(0x2D, { deviceId
, 0xEA, fieldPopup
.id
, 5 })
503 local function init()
504 lineIndex
, edit
= 0, false
508 local function run(event
)
510 error("Cannot be run as a model script!")
515 if fieldPopup
~= nil then
516 result
= runPopupPage(event
)
518 result
= runDevicePage(event
)
526 return { init
=init
, run
=run
}