1 local toolName
= "TNS|DSM Smart RX Telemetry|TNE"
2 ---- ######################################################################### #
3 ---- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html #
5 ---- # This program is free software; you can redistribute it and/or modify #
6 ---- # it under the terms of the GNU General Public License version 2 as #
7 ---- # published by the Free Software Foundation. #
9 ---- # This program is distributed in the hope that it will be useful #
10 ---- # but WITHOUT ANY WARRANTY; without even the implied warranty of #
11 ---- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
12 ---- # GNU General Public License for more details. #
14 ---- #########################################################################
16 ------------------------------------------------------------------------------
17 -- Developer: Francisco Arzu
20 local DEBUG_ON
= false
23 local TEXT_SIZE
= 0 -- NORMAL
24 local X_COL1_HEADER
= 6
25 local X_COL1_DATA
= 60
26 local X_COL2_HEADER
= 170
27 local X_COL2_DATA
= 220
28 local Y_LINE_HEIGHT
= 20
30 local Y_DATA
= Y_HEADER
+ Y_LINE_HEIGHT
*2
32 local X_DATA_SPACE
= 5
37 local function getPage(iParam
)
38 -- get page from 0-based index
39 -- {0,1,2,3}: cyclic (1), {4,5,6,7}: tail (2)
40 local res
= (math
.floor(iParam
/4)==0) and 0 or 1
44 local function round(v
)
47 return math
.floor(v
* factor
+ 0.5) / factor
51 local function readValue(sensor
)
52 -- read from sensor, round and return
53 local v
= getValue(sensor
)
58 local function readValueById(sensor
)
59 local i
= getFieldInfo(sensor
)
60 if (i
==nil) then return nil end
62 local v
= getValue(i
.id
)
68 local function readBatValue(sensor
)
69 -- read from sensor, round and return
70 local v
= getValue(sensor
)
71 if (v
==nil) then return v
end
73 return string.format("%2.2f",v
)
76 local function readActiveParamValue(sensor
)
77 -- read and return a validated active parameter value
78 local v
= getValue(sensor
)
87 local function drawFlightLogScreen(event
)
88 -- draw labels and params on screen
89 local h
= getValue("Hold")
90 local activeParam
= h
-1 -- H
93 lcd
.drawText (X_COL1_HEADER
, Y_HEADER
, "Flight Log", TEXT_SIZE
+ INVERS
)
95 -- read and return parameters
96 local a
= getValue("FdeA")
97 local b
= getValue("FdeB")
98 local l
= getValue("FdeL")
99 local r
= getValue("FdeR")
100 local f
= getValue("FLss")
102 local titles
= {[0]="A:", "B:", "L:", "R:", "F:", "H:"}
103 local values
= {[0]=a
,b
,l
,r
,f
,h
}
105 local y
= Y_LINE_HEIGHT
+Y_DATA
107 for iParam
=0,3 do -- A,B,L,R
108 -- highlight selected parameter (rund)
109 local attr
= ((activeParam
%4)==iParam
) and INVERS
or 0
111 local x
= X_COL1_HEADER
112 local val
= titles
[iParam
]
113 lcd
.drawText (x
, y
, val
, TEXT_SIZE
)
117 x
= X_COL1_DATA
+ X_DATA_LEN
118 if (val
~=16384) then -- Active value
119 lcd
.drawText (x
, y
, val
, attr
+ TEXT_SIZE
+ RIGHT
)
122 y
= y
+ Y_LINE_HEIGHT
125 y
= Y_LINE_HEIGHT
+Y_DATA
126 for iParam
=4,5 do -- F, H
128 local x
= X_COL2_HEADER
129 local val
= titles
[iParam
]
130 lcd
.drawText (x
, y
, val
, TEXT_SIZE
+ BOLD
)
134 x
= X_COL2_DATA
+ X_DATA_LEN
135 lcd
.drawText (x
, y
, val
, TEXT_SIZE
+ RIGHT
+ BOLD
)
137 y
= y
+ Y_LINE_HEIGHT
141 y
= y
+ Y_LINE_HEIGHT
142 local bat
= readBatValue("A2") or "--"
143 lcd
.drawText (X_COL2_HEADER
, y
, "Bat:", TEXT_SIZE
)
144 lcd
.drawText (X_COL2_DATA
+ X_DATA_LEN
, y
, bat
, TEXT_SIZE
+ RIGHT
)
145 lcd
.drawText (X_COL2_DATA
+ X_DATA_LEN
+ X_DATA_SPACE
, y
, "v", TEXT_SIZE
)
155 local function Unsigned_to_SInt16(value
)
156 if value
>= 0x8000 then -- Negative value??
157 return value
- 0x10000
162 local function getDegreesValue(sensor
)
163 local i
= getFieldInfo(sensor
)
164 if (i
==nil) then return "-unk-" end
166 local v
= getValue(i
.id
)
167 if v
==nil then return "---" end
168 local vs
= Unsigned_to_SInt16(v
)
170 return string.format("%0.1f o",vs
/10)
174 local function getDecHexValue(sensor
)
175 local i
= getFieldInfo(sensor
)
176 if (i
==nil) then return "-unk-" end
178 local v
= getValue(i
.id
)
179 if v
==nil then return "---" end
180 local vs
= Unsigned_to_SInt16(v
)
182 return string.format("%d (0x%04X)",vs
,v
)
185 local as3xData
= {[0]=0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
186 local function drawAS3XSettings(event
, page
)
187 local s0500
= getDecHexValue("0500")
188 local s0502
= getDecHexValue("0502")
189 local s0504
= getDecHexValue("0504")
190 local s0506
= getDecHexValue("0506")
191 local s0508
= getDecHexValue("0508")
192 local s050B
= getDecHexValue("050B")
193 local s050D
= getDecHexValue("050D")
195 local d0500
= readValueById("0500") or 0
196 local flags
= bit32
.rshift(d0500
,8)
197 local state
= bit32
.band(d0500
,0xFF)
200 -- flags bits: Safe Envelop, ?, Angle Demand, Stab
201 if (bit32
.band(flags
,0x1)~=0) then flagsMsg
=flagsMsg
.."AS3X Stab" end
202 -- This one, only one should show
203 if (bit32
.band(flags
,0x2)~=0) then flagsMsg
=flagsMsg
..", Angle Demand"
204 elseif (bit32
.band(flags
,0x8)~=0) then flagsMsg
=flagsMsg
..", Safe Envelope"
205 elseif (bit32
.band(flags
,0x4)~=0) then flagsMsg
=flagsMsg
..", AS3X Heading" end
207 local d0502
= readValueById("0502") or 0 -- 0x?F?S
208 local fm
= bit32
.band(bit32
.rshift(d0502
,8),0xF) -- 0,1,2
210 local axis
= bit32
.band(d0502
,0xF) -- 0=Gains,1=Headings,2=Angle Limits (cointinus iterating to provide all values)
212 local d0504
= readValueById("0504") or 0
213 local d0506
= readValueById("0506") or 0
214 local d0508
= readValueById("0508") or 0
216 local d0
= bit32
.rshift(d0504
,8)
217 local d1
= bit32
.band(d0504
,0xFF)
218 local d2
= bit32
.rshift(d0506
,8)
219 local d3
= bit32
.band(d0506
,0xFF)
220 local d4
= bit32
.rshift(d0508
,8)
221 local d5
= bit32
.band(d0508
,0xFF)
223 --axis: 0=Gains+Headings (RG,PG,YG,RH,PH,YH), 1=Safe Gains (R,P,Y),2=Angle Limits(L,R,U,D)
224 --Constantly changing from 0..2 to represent different data, thats why we have to store the values
225 --in a script/global variable, and not local to the function
236 lcd
.drawText (0,0, "AS3X/SAFE Settings", TEXT_SIZE
+ INVERS
)
240 lcd
.drawText (X_COL1_HEADER
,y
, "FM: "..(fm
+1), TEXT_SIZE
)
241 lcd
.drawText (X_COL1_DATA
+X_DATA_LEN
*0.3,y
, "Flags: "..flags
, TEXT_SIZE
)
242 lcd
.drawText (X_COL2_HEADER
+X_DATA_LEN
*0.3,y
, "State: "..state
, TEXT_SIZE
)
244 y
= y
+ Y_LINE_HEIGHT
245 lcd
.drawText (X_COL1_HEADER
,y
, flagsMsg
, TEXT_SIZE
)
247 y
= y
+ Y_LINE_HEIGHT
250 lcd
.drawText (X_COL1_HEADER
+X_DATA_LEN
*0.3,y
, "AS3X Gains", TEXT_SIZE
+BOLD
)
251 lcd
.drawText (X_COL2_HEADER
+X_DATA_LEN
*0.3,y
, "AS3X Headings", TEXT_SIZE
+BOLD
)
253 y
= y
+ Y_LINE_HEIGHT
254 lcd
.drawText (X_COL1_HEADER
,y
, "Roll:", TEXT_SIZE
)
255 lcd
.drawText (X_COL1_DATA
+X_DATA_LEN
,y
, as3xData
[0], TEXT_SIZE
+ RIGHT
) -- Roll G
256 lcd
.drawText (X_COL2_DATA
+X_DATA_LEN
,y
, as3xData
[3], TEXT_SIZE
+ RIGHT
) -- Roll H
258 y
= y
+ Y_LINE_HEIGHT
259 lcd
.drawText (X_COL1_HEADER
,y
, "Pitch:", TEXT_SIZE
)
260 lcd
.drawText (X_COL1_DATA
+X_DATA_LEN
,y
,as3xData
[1], TEXT_SIZE
+ RIGHT
) -- Pitch G
261 lcd
.drawText (X_COL2_DATA
+X_DATA_LEN
,y
, as3xData
[4], TEXT_SIZE
+ RIGHT
) -- Pitch H
263 y
= y
+ Y_LINE_HEIGHT
264 lcd
.drawText (X_COL1_HEADER
,y
, "Yaw:", TEXT_SIZE
)
265 lcd
.drawText (X_COL1_DATA
+X_DATA_LEN
,y
, as3xData
[2], TEXT_SIZE
+ RIGHT
) -- Yaw G
266 lcd
.drawText (X_COL2_DATA
+X_DATA_LEN
,y
, as3xData
[5], TEXT_SIZE
+ RIGHT
) -- Yaw H
271 local x_data1
= X_COL1_DATA
+X_DATA_LEN
272 local x_data2
= X_COL2_HEADER
+X_DATA_LEN
*1.6
274 lcd
.drawText (X_COL1_HEADER
+X_DATA_LEN
*0.3,y
, "SAFE Gains", TEXT_SIZE
+BOLD
)
275 lcd
.drawText (X_COL2_HEADER
+X_DATA_LEN
*0.1,y
, "Angle Limits", TEXT_SIZE
+BOLD
)
277 y
= y
+ Y_LINE_HEIGHT
278 lcd
.drawText (X_COL1_HEADER
,y
, "Roll:", TEXT_SIZE
)
279 lcd
.drawText (x_data1
,y
, as3xData
[6], TEXT_SIZE
+ RIGHT
)
281 lcd
.drawText (X_COL2_HEADER
,y
, "Roll R:", TEXT_SIZE
)
282 lcd
.drawText (x_data2
,y
, as3xData
[12], TEXT_SIZE
+ RIGHT
)
285 y
= y
+ Y_LINE_HEIGHT
286 lcd
.drawText (X_COL1_HEADER
,y
, "Pitch:", TEXT_SIZE
)
287 lcd
.drawText (x_data1
,y
,as3xData
[7], TEXT_SIZE
+ RIGHT
)
289 lcd
.drawText (X_COL2_HEADER
,y
, "Roll L:", TEXT_SIZE
)
290 lcd
.drawText (x_data2
,y
,as3xData
[13], TEXT_SIZE
+ RIGHT
)
293 y
= y
+ Y_LINE_HEIGHT
294 lcd
.drawText (X_COL1_HEADER
,y
, "Yaw:", TEXT_SIZE
)
295 lcd
.drawText (x_data1
,y
, as3xData
[8], TEXT_SIZE
+ RIGHT
)
297 lcd
.drawText (X_COL2_HEADER
,y
, "Pitch U:", TEXT_SIZE
)
298 lcd
.drawText (x_data2
,y
, as3xData
[14], TEXT_SIZE
+ RIGHT
)
300 y
= y
+ Y_LINE_HEIGHT
301 lcd
.drawText (X_COL2_HEADER
,y
, "Pitch D:", TEXT_SIZE
)
302 lcd
.drawText (x_data2
,y
, as3xData
[15], TEXT_SIZE
+ RIGHT
)
308 "0500","0502","0504","0506","0508","050B","050D"}
311 s0500
,s0502
,s0504
,s0506
,s0508
,s050B
,s050D
}
313 y
= Y_LINE_HEIGHT
*2 + Y_HEADER
314 for iParam
=0,#titles
do -- ??
316 local x
= X_COL1_HEADER
+250
317 local val
= titles
[iParam
]
318 lcd
.drawText (x
, y
, val
, TEXT_SIZE
)
320 val
= values
[iParam
] or "--"
322 lcd
.drawText (x
, y
, val
, TEXT_SIZE
)
324 y
= y
+ Y_LINE_HEIGHT
329 local function drawAS3XSettingsP1(event
)
330 drawAS3XSettings(event
, 1)
333 local function drawAS3XSettingsP2(event
)
334 drawAS3XSettings(event
, 2)
338 local function doFloat(v
)
339 if v
==nil then return 0.0 end
341 local vs
= string.format("%1.2f",v
)
347 local ESC_Title
={[0]="","RPM:","Volts:","Motor:","Mot Out:","Throttle:","FET Temp:", "BEC V:", "BEC T:", "BEC A:"}
348 local ESC_uom
={[0]="","","V","A","%","%","C", "V","C","A"}
349 local ESC_Status
={[0]=0,0,0,0,0,0,0,0,0,0,0}
350 local ESC_Min
={[0]=0,0,0,0,0,0,0,0,0,0,0}
351 local ESC_Max
={[0]=0,0,0,0,0,0,0,0,0,0,0}
353 local function drawESCStatus(event
)
355 ESC_Status
[1] = getValue("Erpm") -- RPM
356 ESC_Status
[2] = doFloat(getValue("EVIN")) -- Volts
357 ESC_Status
[3] = doFloat(getValue("ECUR")) -- Current
358 ESC_Status
[4] = doFloat(getValue("EOUT")) -- % Output
359 ESC_Status
[5] = doFloat(getValue("ETHR")) -- Throttle % (EOUT)
360 ESC_Status
[6] = getValue("TFET") -- Temp FET
362 ESC_Status
[7] = doFloat(getValue("VBEC")) -- Volts BEC
363 ESC_Status
[8] = getValue("TBEC") -- Temp BEC
364 ESC_Status
[9] = doFloat(getValue("CBEC")) -- Current BEC
367 if (ESC_Status
~=nil) then
368 if (ESC_Min
[i
]==0) then
369 ESC_Min
[i
]=ESC_Status
[i
]
371 ESC_Min
[i
] = math
.min(ESC_Min
[i
],ESC_Status
[i
])
374 ESC_Max
[i
] = math
.max(ESC_Max
[i
],ESC_Status
[i
])
378 lcd
.drawText (0,0, "ESC", TEXT_SIZE
+INVERS
)
381 local x_data
= X_COL1_DATA
+X_DATA_LEN
*1.5
382 local x_data2
= X_COL2_DATA
+X_DATA_LEN
*0.5
383 local x_data3
= x_data2
+ X_DATA_LEN
*0.8
386 lcd
.drawText (x_data
,y
, "Status", TEXT_SIZE
+BOLD
+RIGHT
)
387 lcd
.drawText (x_data2
,y
, "Min", TEXT_SIZE
+BOLD
+RIGHT
)
388 lcd
.drawText (x_data3
,y
, "Max", TEXT_SIZE
+BOLD
+RIGHT
)
392 lcd
.drawText (X_COL1_HEADER
,y
, ESC_Title
[i
], TEXT_SIZE
+ BOLD
)
394 lcd
.drawText (x_data
,y
, ESC_Status
[i
] or "--", TEXT_SIZE
+ RIGHT
)
395 lcd
.drawText (x_data
+ X_DATA_SPACE
,y
, ESC_uom
[i
], TEXT_SIZE
)
397 lcd
.drawText (x_data2
,y
, ESC_Min
[i
] or "--", TEXT_SIZE
+ RIGHT
)
398 lcd
.drawText (x_data3
,y
, ESC_Max
[i
] or "--", TEXT_SIZE
+ RIGHT
)
399 y
= y
+ Y_LINE_HEIGHT
404 local function drawBATStatus(event
)
405 local Title
={[0]="","Bat:","Temp:","Rem :","Curr:","Used:","Imbal:","Cycles:", "RX:", "BCpT?:"}
406 local uom
={[0]="","V","C","%","mAh","mAh","mV","", "V",""}
407 local Values
={[0]=0,0,0,0,0,0,0,0,0,0,0}
408 local CellValues
={[0]=0,0,0,0,0,0,0,0,0,0,0}
412 local ESC_Volts
= getValue("EVIN") or 0 -- Volts
413 local ESC_Current
= getValue("ECUR") or 0 -- Current
415 Values
[1] = 0 -- compute later
416 Values
[2] = getValue("BTmp") -- Current (C)
417 Values
[3] = nil -- Remaining???
418 Values
[4] = getValue("BCur") -- Current (mAh)
419 Values
[5] = getValue("BUse") -- Current Used (mAh)
420 Values
[6] = getValue("CLMa") -- 0.0 (mV) Imbalance
421 Values
[7] = getValue("Cycl") -- Cycles
422 Values
[8] = readBatValue("A2") -- v
423 Values
[9] = getValue("BCpT") -- Current (mAh) ????
426 --- Total Voltange Calculation
429 CellValues
[i
] = getValue("Cel"..i
)
430 VTotal
= VTotal
+ CellValues
[i
]
433 if (VTotal
==0) then -- No Inteligent Battery,use intelligent ESC if any
435 Values
[4] = string.format("%d",ESC_Current
* 1000)
438 Values
[1] = string.format("%2.2f",VTotal
)
442 lcd
.drawText (X_COL1_HEADER
,0, "Battery Stats", TEXT_SIZE
+INVERS
)
447 local x_data
= X_COL1_DATA
+X_DATA_LEN
+X_DATA_SPACE
*3
449 lcd
.drawText (X_COL1_HEADER
, y
, Title
[i
], TEXT_SIZE
+ BOLD
)
450 lcd
.drawText (x_data
, y
, Values
[i
] or "--", TEXT_SIZE
+ RIGHT
)
451 lcd
.drawText (x_data
+X_DATA_SPACE
, y
, uom
[i
], TEXT_SIZE
)
452 y
= y
+ Y_LINE_HEIGHT
456 x_data
= X_COL2_DATA
+X_DATA_LEN
+X_DATA_SPACE
*5
458 if ((CellValues
[i
] or 0) > 0) then
459 lcd
.drawText (X_COL2_HEADER
+X_DATA_LEN
/2,y
, "Cel "..i
..":", TEXT_SIZE
+ BOLD
)
460 lcd
.drawText (x_data
,y
, string.format("%2.2f",CellValues
[i
] or 0), TEXT_SIZE
+ RIGHT
)
461 lcd
.drawText (x_data
+X_DATA_SPACE
,y
, "v", TEXT_SIZE
)
463 y
= y
+ Y_LINE_HEIGHT
466 lcd
.drawText (X_COL2_HEADER
+X_DATA_LEN
/2,0, Title
[1], TEXT_SIZE
+ INVERS
+ BOLD
)
467 lcd
.drawText (x_data
,0, string.format("%2.2f",Values
[1] or 0), TEXT_SIZE
+ INVERS
+ RIGHT
)
468 lcd
.drawText (x_data
+X_DATA_SPACE
, 0, uom
[1], TEXT_SIZE
+ INVERS
)
474 local function openTelemetryRaw(i2cId
)
475 --Init telemetry (Spectrun Telemetry Raw STR)
476 multiBuffer( 0, string.byte('S') )
477 multiBuffer( 1, string.byte('T') )
478 multiBuffer( 2, string.byte('R') )
479 multiBuffer( 3, i2cId
) -- Monitor this teemetry data
480 multiBuffer( 4, 0 ) -- Allow to get Data
483 local function closeTelemetryRaw()
484 multiBuffer(0, 0) -- Destroy the STR header
485 multiBuffer(3, 0) -- Not requesting any Telementry ID
488 local lineText
= {nil}
489 local I2C_TEXT_GEN
= 0x0C
490 local function drawTextGen(event
)
491 if (multiBuffer(0)~=string.byte('S')) then -- First time run???
492 openTelemetryRaw(I2C_TEXT_GEN
) -- I2C_ID for TEXT_GEN
496 -- Proces TEXT GEN Telementry message
497 if multiBuffer( 4 ) == I2C_TEXT_GEN
then -- Specktrum Telemetry ID of data received
498 local instanceNo
= multiBuffer( 5 )
499 local lineNo
= multiBuffer( 6 )
503 line
= line
.. string.char(multiBuffer( 7 + i
))
506 multiBuffer( 4, 0 ) -- Clear Semaphore, to notify that we fully process the current message
507 lineText
[lineNo
]=line
512 if (lineText
[0]) then
513 lcd
.drawText (X_COL1_HEADER
,0, " "..lineText
[0].." ", TEXT_SIZE
+ BOLD
+ INVERS
)
515 lcd
.drawText (X_COL1_HEADER
,0, "TextGen", TEXT_SIZE
+INVERS
)
521 if (lineText
[i
]) then
522 lcd
.drawText (X_COL1_HEADER
,y
, lineText
[i
], TEXT_SIZE
)
524 y
= y
+ Y_LINE_HEIGHT
527 if event
== EVT_VIRTUAL_EXIT
then -- Exit?? Clear menu data
534 local telPageSelected
= 0
535 local pageTitle
= {[0]="Main", "AS3X Settings", "SAFE Settings", "ESC Status", "Battery Status","TextGen","Flight Log"}
537 local function drawMainScreen(event
)
539 lcd
.drawText (X_COL1_HEADER
, Y_HEADER
, "Main Telemetry (Smart RXs)", TEXT_SIZE
+ INVERS
)
541 for iParam
=1,#pageTitle
do
542 -- highlight selected parameter
543 local attr
= (telPage
==iParam
) and INVERS
or 0
546 local y
= (iParam
)*Y_LINE_HEIGHT
+Y_DATA
549 local x
= X_COL1_HEADER
550 local val
= pageTitle
[iParam
]
551 lcd
.drawText (x
, y
, val
, attr
+ TEXT_SIZE
)
554 if event
== EVT_VIRTUAL_PREV
then
555 if (telPage
>1) then telPage
= telPage
- 1 end
556 elseif event
== EVT_VIRTUAL_NEXT
then
557 if (telPage
<#pageTitle
) then telPage
= telPage
+ 1 end
558 elseif event
== EVT_VIRTUAL_ENTER
then
559 telPageSelected
= telPage
564 local pageDraw
= {[0]=drawMainScreen
, drawAS3XSettingsP1
, drawAS3XSettingsP2
, drawESCStatus
, drawBATStatus
, drawTextGen
, drawFlightLogScreen
}
566 local function run_func(event
)
568 error("Cannot be run as a model script!")
572 -- draw specific page
573 pageDraw
[telPageSelected
](event
)
575 if event
== EVT_VIRTUAL_EXIT
then
576 if (telPageSelected
==0) then return 1 end -- on Main?? Exit Script
577 telPageSelected
= 0 -- any page, return to Main
583 local function init_func()
585 if (LCD_W
<= 128 or LCD_H
<=64) then -- Smaller Screens
598 Y_DATA
= Y_HEADER
+ Y_LINE_HEIGHT
603 return { run
=run_func
, init
=init_func
}