Shouldn't copyright be there too ? (#5200)
[opentx.git] / radio / sdcard / taranis-x9 / CROSSFIRE / device.lua
blobfd60fd85cabbee404b34a91e31711daa765cbcba
1 ---- #########################################################################
2 ---- # #
3 ---- # Copyright (C) OpenTX #
4 -----# #
5 ---- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html #
6 ---- # #
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. #
10 ---- # #
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. #
15 ---- # #
16 ---- #########################################################################
17 local deviceId = 0
18 local deviceName = ""
19 local lineIndex = 0
20 local pageOffset = 0
21 local edit = false
22 local charIndex = 1
23 local fieldPopup
24 local fieldTimeout = 0
25 local fieldId = 1
26 local fieldChunk = 0
27 local fieldData = {}
28 local fields = {}
30 local function getField(line)
31 local counter = 1
32 for i = 1, #fields do
33 local field = fields[i]
34 if not field.hidden then
35 if counter < line then
36 counter = counter + 1
37 else
38 return field
39 end
40 end
41 end
42 end
44 local function initLineIndex()
45 lineIndex = 0
46 for i = 1, #fields do
47 local field = getField(i)
48 if field and field.type ~= 11 and field.type ~= 12 and field.name ~= nil then
49 lineIndex = i
50 break
51 end
52 end
53 end
55 -- Change display attribute to current field
56 local function incrField(step)
57 local field = getField(lineIndex)
58 if field.type == 10 then
59 local byte = 32
60 if charIndex <= #field.value then
61 byte = string.byte(field.value, charIndex) + step
62 end
63 if byte < 32 then
64 byte = 32
65 elseif byte > 122 then
66 byte = 122
67 end
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)
70 else
71 field.value = field.value .. string.char(byte)
72 end
73 else
74 local min, max = 0, 0
75 if field.type <= 5 then
76 min = field.min
77 max = field.max
78 step = field.step * step
79 elseif field.type == 9 then
80 min = 0
81 max = #field.values - 1
82 end
83 if (step < 0 and field.value > min) or (step > 0 and field.value < max) then
84 field.value = field.value + step
85 end
86 end
87 end
89 -- Select the next or previous editable field
90 local function selectField(step)
91 local newLineIndex = lineIndex
92 local field
93 repeat
94 newLineIndex = newLineIndex + step
95 if newLineIndex == 0 then
96 newLineIndex = #fields
97 elseif newLineIndex == 1 + #fields then
98 newLineIndex = 1
99 pageOffset = 0
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)
112 local t = {}
113 local i = 1
114 for s in string.gmatch(str, "([^;]+)") do
115 t[i] = s
116 i = i + 1
118 return t
121 local function fieldGetString(data, offset)
122 local result = ""
123 while data[offset] ~= 0 do
124 result = result .. string.char(data[offset])
125 offset = offset + 1
127 offset = offset + 1
128 return result, offset
131 local function parseDeviceInfoMessage(data)
132 local offset
133 deviceId = data[2]
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)
142 local result = 0
143 for i=0, size-1 do
144 result = bit32.lshift(result, 8) + data[offset + i]
146 return result
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)
155 field.step = 1
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
186 if value < 0 then
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(140, y, field.value, LEFT + attr)
194 lcd.drawText(lcd.getLastPos(), y, field.unit, attr)
197 -- UINT8
198 local function fieldUint8Load(field, data, offset)
199 fieldUnsignedLoad(field, data, offset, 1)
202 local function fieldUint8Save(field)
203 fieldUnsignedSave(field, 1)
206 -- INT8
207 local function fieldInt8Load(field, data, offset)
208 fieldSignedLoad(field, data, offset, 1)
211 local function fieldInt8Save(field)
212 fieldSignedSave(field, 1)
215 -- UINT16
216 local function fieldUint16Load(field, data, offset)
217 fieldUnsignedLoad(field, data, offset, 2)
220 local function fieldUint16Save(field)
221 fieldUnsignedSave(field, 2)
224 -- INT16
225 local function fieldInt16Load(field, data, offset)
226 fieldSignedLoad(field, data, offset, 2)
229 local function fieldInt16Save(field)
230 fieldSignedSave(field, 2)
233 -- FLOAT
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
242 field.prec = 2
244 field.step = fieldGetValue(data, offset+17, 4)
245 field.unit, offset = fieldGetString(data, offset+21)
248 local function fieldFloatDisplay(field, y, attr)
249 local attrnum
250 if field.prec == 1 then
251 attrnum = LEFT + attr + PREC1
252 elseif field.prec == 2 then
253 attrnum = LEFT + attr + PREC2
254 else
255 attrnum = LEFT + attr
257 lcd.drawNumber(140, y, field.value, attrnum)
258 lcd.drawText(lcd.getLastPos(), y, field.unit, attr)
261 local function fieldFloatSave(field)
262 fieldUnsignedSave(field, 4)
265 -- TEXT SELECTION
266 local function fieldTextSelectionLoad(field, data, offset)
267 local values
268 values, offset = fieldGetString(data, offset)
269 if values ~= "" then
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(140, y, field.values[field.value+1], attr)
285 lcd.drawText(lcd.getLastPos(), y, field.unit, attr)
288 -- STRING
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(140, y, field.value, FIXEDWIDTH)
308 lcd.drawText(134+6*charIndex, y, string.sub(field.value, charIndex, charIndex), FIXEDWIDTH + attr)
309 else
310 lcd.drawText(140, 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
319 fieldPopup = nil
323 local function fieldCommandSave(field)
324 if field.status == 0 then
325 field.status = 1
326 crossfireTelemetryPush(0x2D, { deviceId, 0xEA, field.id, field.status })
327 fieldPopup = field
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(140, y, "[" .. field.info .. "]")
339 local functions = {
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 },
344 nil,
345 nil,
346 nil,
347 nil,
348 { load=fieldFloatLoad, save=fieldFloatSave, display=fieldFloatDisplay },
349 { load=fieldTextSelectionLoad, save=fieldTextSelectionSave, display=fieldTextSelectionDisplay },
350 { load=fieldStringLoad, save=fieldStringSave, display=fieldStringDisplay },
351 nil,
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
358 fieldData = {}
359 fieldChunk = 0
360 return
362 local field = fields[fieldId]
363 local chunks = data[4]
364 for i=5, #data do
365 fieldData[#fieldData + 1] = data[i]
367 if chunks > 0 then
368 fieldChunk = fieldChunk + 1
369 else
370 fieldChunk = 0
371 field.id = fieldId
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)
376 if name ~= "" then
377 local indent = ""
378 local parent = field.parent
379 while parent ~= 0 do
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
390 initLineIndex()
392 fieldId = 1 + (fieldId % #fields)
394 fieldData = {}
398 local function refreshNext()
399 local command, data = crossfireTelemetryPop()
400 if command == nil then
401 local time = getTime()
402 if fieldPopup then
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)
416 fieldTimeout = 0
420 -- Main
421 local function runDevicePage(event)
422 if event == EVT_EXIT_BREAK then -- exit script
423 if edit == true then
424 edit = false
425 local field = getField(lineIndex)
426 fieldTimeout = getTime() + 200 -- 2s
427 fieldId, fieldChunk = field.id, 0
428 fieldData = {}
429 functions[field.type+1].save(field)
430 else
431 return "crossfire.lua"
433 elseif event == EVT_ENTER_BREAK then -- toggle editing/selecting current field
434 local field = getField(lineIndex)
435 if field.name then
436 if field.type == 10 then
437 if edit == false then
438 edit = true
439 charIndex = 1
440 else
441 charIndex = charIndex + 1
443 elseif field.type < 11 then
444 edit = not edit
446 if edit == false then
447 fieldTimeout = getTime() + 200 -- 2s
448 fieldId, fieldChunk = field.id, 0
449 fieldData = {}
450 functions[field.type+1].save(field)
453 elseif edit then
454 if event == EVT_PLUS_FIRST or event == EVT_PLUS_REPT then
455 incrField(1)
456 elseif event == EVT_MINUS_FIRST or event == EVT_MINUS_REPT then
457 incrField(-1)
459 else
460 if event == EVT_MINUS_FIRST then
461 selectField(1)
462 elseif event == EVT_PLUS_FIRST then
463 selectField(-1)
467 lcd.clear()
468 lcd.drawScreenTitle(deviceName, 0, 0)
469 for y = 1, 7 do
470 local field = getField(pageOffset+y)
471 if not field then
472 break
473 elseif field.name == nil then
474 lcd.drawText(0, 1+8*y, "...")
475 else
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)
484 return 0
487 local function runPopupPage(event)
488 local result
489 if fieldPopup.status == 3 then
490 result = popupConfirmation(fieldPopup.info, event)
491 else
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 })
499 return 0
502 -- Init
503 local function init()
504 lineIndex, edit = 0, false
507 -- Main
508 local function run(event)
509 if event == nil then
510 error("Cannot be run as a model script!")
511 return 2
514 local result
515 if fieldPopup ~= nil then
516 result = runPopupPage(event)
517 else
518 result = runDevicePage(event)
521 refreshNext()
523 return result
526 return { init=init, run=run }