2 Change ExpressLRS parameters
4 License https://www.gnu.org/licenses/gpl-3.0.en.html
6 Lua script for radios X7, X9, X-lite and Horus with openTx 2.2 or higher
8 Original author: AlessandroAU + Cruwaller
10 local commitSha
= '??????'
11 local shaLUT
= {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}
13 local gotFirstResp
= false
14 local needResp
= false
16 local ReqWaitTime
= 100;
17 local UartGoodPkts
= 0;
18 local UartBadPkts
= 0;
19 local StopUpdate
= false;
20 local force_use_lua
= false;
21 local bindmode
= false;
22 local wifiupdatemode
= false;
24 local SX127x_RATES
= {
25 list
= {'25Hz(-123dbm)', '50Hz(-120dbm)', '100Hz(-117dbm)', '200Hz(-112dbm)'},
26 values
= {0x06, 0x05, 0x04, 0x02},
27 rates
= { 25, 50, 100, 200 },
29 local SX128x_RATES
= {
30 list
= {'50Hz(-117dbm)', '150Hz(-112dbm)', '250Hz(-108dbm)', '500Hz(-105dbm)'},
31 values
= {0x05, 0x03, 0x01, 0x00},
32 rates
= { 50, 150, 250, 500 },
34 local tx_lua_version
= {
36 list
= {'?', '?', 'v0.3', 'v0.4', 'v0.5'},
37 values
= {0x01, 0x02, 0x03, 0x04, 0x05},
45 list
= SX127x_RATES
.list
,
46 values
= SX127x_RATES
.values
,
47 rates
= SX127x_RATES
.rates
,
48 max_allowed
= #SX127x_RATES
.values
,
56 list
= {'Off', '1:128', '1:64', '1:32', '1:16', '1:8', '1:4', '1:2'},
57 values
= {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07},
58 rates
= { 1, 128, 64, 32, 16, 8, 4, 2 },
67 list
= {'10 mW', '25 mW', '50 mW', '100 mW', '250 mW', '500 mW', '1000 mW', '2000 mW'},
68 values
= {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07},
77 list
= {'915 AU', '915 FCC', '868 EU', '433 AU', '433 EU', '2.4G ISM', '866 IN'},
78 values
= {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07},
82 local function binding(item
, event
)
83 if (bindmode
== true) then
84 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0xFF, 0x00})
86 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0xFF, 0x01})
104 offsets
= {left
=5, right
=0, top
=5, bottom
=5},
107 local function web_server_start(item
, event
)
108 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0xFE, 0x01})
109 playTone(2000, 50, 0)
117 name
= '[Wifi Update]',
119 func
= web_server_start
,
124 offsets
= {left
=65, right
=0, top
=5, bottom
=5},
127 -- local exit_script = {
136 -- offsets = {left=5, right=0, top=5, bottom=5},
142 -- Note: list indexes must match to param handling in tx_main!
143 list
= {AirRate
, TLMinterval
, MaxPower
, RFfreq
, Bind
, WebServer
},
144 --list = {AirRate, TLMinterval, MaxPower, RFfreq, WebServer, exit_script},
147 local function force_use_lua_enable()
149 playTone(2000, 50, 0)
152 -- returns flags to pass to lcd.drawText for inverted and flashing text
153 local function getFlags(element
)
154 if menu
.selected
~= element
then return 0 end
155 if menu
.selected
== element
and menu
.modify
== false then
159 -- this element is currently selected
161 return 0 + INVERS
+ BLINK
164 -- ################################################
166 local supportedRadios
=
220 local radio_resolution
= LCD_W
.."x"..LCD_H
221 local radio_data
= assert(supportedRadios
[radio_resolution
], radio_resolution
.." not supported")
224 local function refreshLCD()
226 local yOffset
= radio_data
.topOffset
;
227 local lOffset
= radio_data
.leftOffset
;
230 if wifiupdatemode
== true then --make this less hacky later
231 lcd
.drawText(lOffset
, yOffset
, "Goto http://10.0.0.1 ", INVERS
)
232 -- elseif bindmode == true then
234 lcd
.drawText(lOffset
, yOffset
, 'ExpressLRS ' .. commitSha
.. ' ' .. tostring(UartBadPkts
) .. ':' .. tostring(UartGoodPkts
), INVERS
)
237 if tx_lua_version
.values
[tx_lua_version
.selected
] == version
or force_use_lua
== true then
238 yOffset
= radio_data
.yOffset_val
239 for idx
,item
in pairs(menu
.list
) do
240 local offsets
= {left
=0, right
=0, top
=0, bottom
=0}
241 if item
.offsets
~= nil then
242 offsets
= item
.offsets
244 lOffset
= offsets
.left
+ radio_data
.leftOffset
245 local item_y
= yOffset
+ offsets
.top
+ radio_data
.yOffset
* item
.index
246 if item
.action
~= nil or item
.func
~= nil then
247 lcd
.drawText(lOffset
, item_y
, item
.name
, getFlags(idx
) + radio_data
.textSize
)
250 if 0 < item
.selected
and item
.selected
<= #item
.list
and gotFirstResp
then
251 --if 0 < item.selected and item.selected <= #item.list and item.selected <= item.max_allowed then
252 value
= item
.list
[item
.selected
]
253 -- Apply the view function to the value if present
254 if item
.view
~= nil then
255 value
= item
.view(item
, value
)
258 lcd
.drawText(lOffset
, item_y
, item
.name
, radio_data
.textSize
)
259 lcd
.drawText(radio_data
.xOffset
, item_y
, value
, getFlags(idx
) + radio_data
.textSize
)
262 elseif gotFirstResp
then
263 lcd
.drawText(lOffset
, (radio_data
.yOffset
*2), "!!! VERSION MISMATCH !!!", INVERS
)
264 if (tx_lua_version
.values
[tx_lua_version
.selected
] > version
) then
265 lcd
.drawText(lOffset
, (radio_data
.yOffset
*3), "Update ELRS.lua", INVERS
)
267 lcd
.drawText(lOffset
, (radio_data
.yOffset
*3), "Update TX module", INVERS
)
269 lcd
.drawText(lOffset
, (radio_data
.yOffset
*4), "LUA v0."..version
..", TX "..tx_lua_version
.list
[tx_lua_version
.selected
], INVERS
)
270 lcd
.drawText(lOffset
, (radio_data
.yOffset
*5), "[force use]", INVERS
+ BLINK
)
272 lcd
.drawText(lOffset
, (radio_data
.yOffset
*5), "Connecting...", INVERS
+ BLINK
)
276 local function increase(_menu
)
279 item
= item
.list
[item
.selected
]
282 if item
.selected
< #item
.list
and
283 (item
.max_allowed
== nil or item
.selected
< item
.max_allowed
) then
284 item
.selected
= item
.selected
+ 1
285 --playTone(2000, 50, 0)
289 local function decrease(_menu
)
292 item
= item
.list
[item
.selected
]
294 if item
.selected
> 1 and item
.selected
<= #item
.list
then
295 item
.selected
= item
.selected
- 1
296 --playTone(2000, 50, 0)
300 -- ################################################
303 It's unclear how the telemetry push/pop system works. We don't always seem to get
304 a response to a single push event. Can multiple responses be stacked up? Do they timeout?
306 If there are multiple repsonses we typically want the newest one, so this method
307 will keep reading until it gets a nil response, discarding the older data. A maximum number
308 of reads is used to defend against the possibility of this function running for an extended
313 function GetIndexOf(t
,val
)
314 for k
,v
in ipairs(t
) do
321 local function viewTlmInterval(item
, value
)
322 -- Calculate the burst telemetry rate the same way it is defined in rx_main
323 local TELEM_MIN_LINK_INTERVAL
= 512 -- defined in rx_main, ms per link packet
324 local hz
= AirRate
.rates
[AirRate
.selected
]
325 local ratiodiv
= TLMinterval
.rates
[TLMinterval
.selected
]
326 local burst
= math
.floor(math
.floor(TELEM_MIN_LINK_INTERVAL
* hz
/ ratiodiv
) / 1000)
327 -- Reserve one slot for LINK telemetry
328 burst
= (burst
> 1) and (burst
- 1) or 1
329 -- Calculate bandwidth using packets per second and burst
330 local telemPPS
= hz
/ ratiodiv
331 local bandwidth
= math
.floor(5 * 8 * telemPPS
* burst
/ (burst
+ 1) + 0.5)
333 if ratiodiv
== 1 then
336 return string.format("%s (%dbps)", value
, bandwidth
)
340 local function loadViewFunctions()
341 TLMinterval
.view
= viewTlmInterval
344 local function processResp()
345 local command
, data
= crossfireTelemetryPop()
346 if (data
== nil) then return end
348 if (command
== 0x2D) and (data
[1] == 0xEA) and (data
[2] == 0xEE) then
349 -- Type 0xff - "sendLuaParams"
350 if( data
[3] == 0xFF) then
353 if (#data
== 12 or force_use_lua
== true) then
354 bindmode
= bit32
.btest(0x01, data
[4]) -- bind mode active
355 wifiupdatemode
= bit32
.btest(0x02, data
[4])
356 if StopUpdate
== false then
357 TLMinterval
.selected
= GetIndexOf(TLMinterval
.values
,data
[6])
358 MaxPower
.selected
= GetIndexOf(MaxPower
.values
,data
[7])
359 tx_lua_version
.selected
= GetIndexOf(tx_lua_version
.values
,data
[12])
361 -- ISM 2400 band (SX128x)
362 AirRate
.list
= SX128x_RATES
.list
363 AirRate
.rates
= SX128x_RATES
.rates
364 AirRate
.values
= SX128x_RATES
.values
365 AirRate
.max_allowed
= #SX128x_RATES
.values
367 -- 433/868/915 (SX127x)
368 AirRate
.list
= SX127x_RATES
.list
369 AirRate
.rates
= SX127x_RATES
.rates
370 AirRate
.values
= SX127x_RATES
.values
371 AirRate
.max_allowed
= #SX127x_RATES
.values
373 RFfreq
.selected
= GetIndexOf(RFfreq
.values
,data
[8])
374 AirRate
.selected
= GetIndexOf(AirRate
.values
, data
[5])
377 UartBadPkts
= data
[9]
378 UartGoodPkts
= data
[10] * 256 + data
[11]
379 end -- if correct amount of data for version
381 -- Type 0xfe - "luaCommitPacket"
382 elseif(data
[3] == 0xFE) and #data
== 9 then
383 commitSha
= shaLUT
[data
[4]+1] .. shaLUT
[data
[5]+1] .. shaLUT
[data
[6]+1] .. shaLUT
[data
[7]+1] .. shaLUT
[data
[8]+1] .. shaLUT
[data
[9]+1]
390 local function init_func()
394 local function bg_func(event
)
398 Called at (unspecified) intervals when the script is running and the screen is visible
400 Handles key presses and sends state changes to the tx module.
403 read any outstanding telemetry data
404 process the event, sending a telemetryPush if necessary
405 if there was no push due to events, send the void push to ensure current values are sent for next iteration
409 local function run_func(event
)
411 if (gotFirstResp
== false or commitSha
== '??????') and (getTime() > (NewReqTime
+ ReqWaitTime
)) then
412 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0x00, 0x00}) -- ping until we get a resp
413 NewReqTime
= getTime()
416 if needResp
== true and (getTime() > (NewReqTime
+ ReqWaitTime
)) then
417 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0x00, 0x00}) -- ping until we get a resp
418 NewReqTime
= getTime()
421 processResp() -- check if we have data from the module
423 local type = menu
.selected
424 local item
= menu
.list
[type]
426 if item
.exec
== true and item
.func
~= nil then
427 local retval
= item
.func(item
, event
)
432 -- now process key events
433 if event
== EVT_VIRTUAL_ENTER_LONG
or
434 event
== EVT_ENTER_LONG
or
435 event
== EVT_MENU_LONG
then
438 elseif event
== EVT_VIRTUAL_PREV
or
439 event
== EVT_VIRTUAL_PREV_REPT
or
440 event
== EVT_ROT_LEFT
or
441 --event == EVT_MINUS_BREAK or
442 event
== EVT_SLIDE_LEFT
then
445 elseif event
== EVT_VIRTUAL_NEXT
or
446 event
== EVT_VIRTUAL_NEXT_REPT
or
447 event
== EVT_ROT_RIGHT
or
448 --event == EVT_PLUS_BREAK or
449 event
== EVT_SLIDE_RIGHT
then
452 elseif event
== EVT_VIRTUAL_ENTER
or
453 event
== EVT_ENTER_BREAK
then
454 if version
~= tx_lua_version
.values
[tx_lua_version
.selected
] and force_use_lua
== false then
455 force_use_lua_enable()
456 elseif menu
.modify
then
457 -- update module when edit ready
459 if 0 < item
.selected
and item
.selected
<= #item
.values
then
460 value
= item
.values
[item
.selected
]
464 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, type, value
})
465 NewReqTime
= getTime()
468 elseif item
.editable
and 0 < item
.selected
and item
.selected
<= #item
.values
then
469 -- allow modification only if not readonly and values received from module
471 elseif item
.func
~= nil then
473 elseif item
.action
== 'exit' then
478 elseif menu
.modify
and (event
== EVT_VIRTUAL_EXIT
or
479 event
== EVT_EXIT_BREAK
or
480 event
== EVT_RTN_FIRST
) then
482 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0x00, 0x00}) -- refresh data
483 NewReqTime
= getTime()
492 --return {run = run_func, background = bg_func, init = init_func}
493 return {run
= run_func
, init
= init_func
}