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
)
271 elseif version
== -1 then
272 lcd
.drawText(lOffset
, (radio_data
.yOffset
*2), "!!! VERSION MISMATCH !!!", INVERS
)
273 lcd
.drawText(lOffset
, (radio_data
.yOffset
*3), "Module is not ELRS v1", INVERS
)
274 lcd
.drawText(lOffset
, (radio_data
.yOffset
*5), "Use the elrsV2.lua", INVERS
)
276 lcd
.drawText(lOffset
, (radio_data
.yOffset
*5), "Connecting...", INVERS
+ BLINK
)
280 local function increase(_menu
)
283 item
= item
.list
[item
.selected
]
286 if item
.selected
< #item
.list
and
287 (item
.max_allowed
== nil or item
.selected
< item
.max_allowed
) then
288 item
.selected
= item
.selected
+ 1
289 --playTone(2000, 50, 0)
293 local function decrease(_menu
)
296 item
= item
.list
[item
.selected
]
298 if item
.selected
> 1 and item
.selected
<= #item
.list
then
299 item
.selected
= item
.selected
- 1
300 --playTone(2000, 50, 0)
304 -- ################################################
307 It's unclear how the telemetry push/pop system works. We don't always seem to get
308 a response to a single push event. Can multiple responses be stacked up? Do they timeout?
310 If there are multiple repsonses we typically want the newest one, so this method
311 will keep reading until it gets a nil response, discarding the older data. A maximum number
312 of reads is used to defend against the possibility of this function running for an extended
317 function GetIndexOf(t
,val
)
318 for k
,v
in ipairs(t
) do
325 local function viewTlmInterval(item
, value
)
326 -- Calculate the burst telemetry rate the same way it is defined in rx_main
327 local TELEM_MIN_LINK_INTERVAL
= 512 -- defined in rx_main, ms per link packet
328 local hz
= AirRate
.rates
[AirRate
.selected
]
329 local ratiodiv
= TLMinterval
.rates
[TLMinterval
.selected
]
330 local burst
= math
.floor(math
.floor(TELEM_MIN_LINK_INTERVAL
* hz
/ ratiodiv
) / 1000)
331 -- Reserve one slot for LINK telemetry
332 burst
= (burst
> 1) and (burst
- 1) or 1
333 -- Calculate bandwidth using packets per second and burst
334 local telemPPS
= hz
/ ratiodiv
335 local bandwidth
= math
.floor(5 * 8 * telemPPS
* burst
/ (burst
+ 1) + 0.5)
337 if ratiodiv
== 1 then
340 return string.format("%s (%dbps)", value
, bandwidth
)
344 local function loadViewFunctions()
345 TLMinterval
.view
= viewTlmInterval
348 local function processElrsV2(data
)
349 UartBadPkts
= data
[3]
350 UartGoodPkts
= (data
[4]*256) + data
[5]
351 version
= -1 -- Indicate this is ELRS a 2.0 system, wrong script
354 local function processResp()
355 local command
, data
= crossfireTelemetryPop()
356 if (data
== nil) then return end
358 if (command
== 0x2D) and (data
[1] == 0xEA) and (data
[2] == 0xEE) then
359 -- Type 0xff - "sendLuaParams"
360 if( data
[3] == 0xFF) then
363 if (#data
== 12 or force_use_lua
== true) then
364 bindmode
= bit32
.btest(0x01, data
[4]) -- bind mode active
365 wifiupdatemode
= bit32
.btest(0x02, data
[4])
366 if StopUpdate
== false then
367 TLMinterval
.selected
= GetIndexOf(TLMinterval
.values
,data
[6])
368 MaxPower
.selected
= GetIndexOf(MaxPower
.values
,data
[7])
369 tx_lua_version
.selected
= GetIndexOf(tx_lua_version
.values
,data
[12])
371 -- ISM 2400 band (SX128x)
372 AirRate
.list
= SX128x_RATES
.list
373 AirRate
.rates
= SX128x_RATES
.rates
374 AirRate
.values
= SX128x_RATES
.values
375 AirRate
.max_allowed
= #SX128x_RATES
.values
377 -- 433/868/915 (SX127x)
378 AirRate
.list
= SX127x_RATES
.list
379 AirRate
.rates
= SX127x_RATES
.rates
380 AirRate
.values
= SX127x_RATES
.values
381 AirRate
.max_allowed
= #SX127x_RATES
.values
383 RFfreq
.selected
= GetIndexOf(RFfreq
.values
,data
[8])
384 AirRate
.selected
= GetIndexOf(AirRate
.values
, data
[5])
387 UartBadPkts
= data
[9]
388 UartGoodPkts
= data
[10] * 256 + data
[11]
389 end -- if correct amount of data for version
391 -- Type 0xfe - "luaCommitPacket"
392 elseif(data
[3] == 0xFE) and #data
== 9 then
393 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]
397 elseif (command
== 0x2E) and (data
[1] == 0xEA) and (data
[2] == 0xEE) then
402 local function init_func()
406 local function bg_func(event
)
410 Called at (unspecified) intervals when the script is running and the screen is visible
412 Handles key presses and sends state changes to the tx module.
415 read any outstanding telemetry data
416 process the event, sending a telemetryPush if necessary
417 if there was no push due to events, send the void push to ensure current values are sent for next iteration
421 local function run_func(event
)
423 if (gotFirstResp
== false or commitSha
== '??????') and (getTime() > (NewReqTime
+ ReqWaitTime
)) then
424 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0x00, 0x00}) -- ping until we get a resp
425 NewReqTime
= getTime()
428 if needResp
== true and (getTime() > (NewReqTime
+ ReqWaitTime
)) then
429 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0x00, 0x00}) -- ping until we get a resp
430 NewReqTime
= getTime()
433 processResp() -- check if we have data from the module
435 local type = menu
.selected
436 local item
= menu
.list
[type]
438 if item
.exec
== true and item
.func
~= nil then
439 local retval
= item
.func(item
, event
)
444 -- now process key events
445 if event
== EVT_VIRTUAL_ENTER_LONG
or
446 event
== EVT_ENTER_LONG
or
447 event
== EVT_MENU_LONG
then
450 elseif event
== EVT_VIRTUAL_PREV
or
451 event
== EVT_VIRTUAL_PREV_REPT
or
452 event
== EVT_ROT_LEFT
or
453 --event == EVT_MINUS_BREAK or
454 event
== EVT_SLIDE_LEFT
then
457 elseif event
== EVT_VIRTUAL_NEXT
or
458 event
== EVT_VIRTUAL_NEXT_REPT
or
459 event
== EVT_ROT_RIGHT
or
460 --event == EVT_PLUS_BREAK or
461 event
== EVT_SLIDE_RIGHT
then
464 elseif event
== EVT_VIRTUAL_ENTER
or
465 event
== EVT_ENTER_BREAK
then
466 if version
~= tx_lua_version
.values
[tx_lua_version
.selected
] and force_use_lua
== false then
467 force_use_lua_enable()
468 elseif menu
.modify
then
469 -- update module when edit ready
471 if 0 < item
.selected
and item
.selected
<= #item
.values
then
472 value
= item
.values
[item
.selected
]
476 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, type, value
})
477 NewReqTime
= getTime()
480 elseif item
.editable
and 0 < item
.selected
and item
.selected
<= #item
.values
then
481 -- allow modification only if not readonly and values received from module
483 elseif item
.func
~= nil then
485 elseif item
.action
== 'exit' then
490 elseif menu
.modify
and (event
== EVT_VIRTUAL_EXIT
or
491 event
== EVT_EXIT_BREAK
or
492 event
== EVT_RTN_FIRST
) then
494 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0x00, 0x00}) -- refresh data
495 NewReqTime
= getTime()
504 --return {run = run_func, background = bg_func, init = init_func}
505 return {run
= run_func
, init
= init_func
}