Merge pull request #2754 from ExpressLRS/merge-3.4.2-into-master
[ExpressLRS.git] / src / lua / ELRS.lua
blob1a72b795835d26fc4e6f78820e79c0552e19c012
1 --[[
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
9 ]] --
10 local commitSha = '??????'
11 local shaLUT = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}
12 local version = 3;
13 local gotFirstResp = false
14 local needResp = false
15 local NewReqTime = 0;
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 = {
35 selected = 1,
36 list = {'?', '?', 'v0.3', 'v0.4', 'v0.5'},
37 values = {0x01, 0x02, 0x03, 0x04, 0x05},
40 local AirRate = {
41 index = 1,
42 editable = true,
43 name = 'Pkt. Rate',
44 selected = 99,
45 list = SX127x_RATES.list,
46 values = SX127x_RATES.values,
47 rates = SX127x_RATES.rates,
48 max_allowed = #SX127x_RATES.values,
51 local TLMinterval = {
52 index = 2,
53 editable = true,
54 name = 'TLM Ratio',
55 selected = 99,
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 },
59 max_allowed = 8,
62 local MaxPower = {
63 index = 3,
64 editable = true,
65 name = 'Power',
66 selected = 99,
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},
69 max_allowed = 8,
72 local RFfreq = {
73 index = 4,
74 editable = false,
75 name = 'RF Freq',
76 selected = 99,
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},
79 max_allowed = 7,
82 local function binding(item, event)
83 if (bindmode == true) then
84 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0xFF, 0x00})
85 else
86 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0xFF, 0x01})
87 end
89 playTone(2000, 50, 0)
90 item.exec = false
91 return 0
92 end
94 local Bind = {
95 index = 5,
96 editable = false,
97 name = '[Bind]',
98 exec = false,
99 func = binding,
100 selected = 99,
101 list = {},
102 values = {},
103 max_allowed = 0,
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)
110 item.exec = false
111 return 0
114 local WebServer = {
115 index = 5,
116 editable = false,
117 name = '[Wifi Update]',
118 exec = false,
119 func = web_server_start,
120 selected = 99,
121 list = {},
122 values = {},
123 max_allowed = 0,
124 offsets = {left=65, right=0, top=5, bottom=5},
127 -- local exit_script = {
128 -- index = 7,
129 -- editable = false,
130 -- action = 'exit',
131 -- name = '[EXIT]',
132 -- selected = 99,
133 -- list = {},
134 -- values = {},
135 -- max_allowed = 0,
136 -- offsets = {left=5, right=0, top=5, bottom=5},
137 -- }
139 local menu = {
140 selected = 1,
141 modify = false,
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()
148 force_use_lua = true
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
156 StopUpdate = false
157 return 0 + INVERS
159 -- this element is currently selected
160 StopUpdate = true
161 return 0 + INVERS + BLINK
164 -- ################################################
166 local supportedRadios =
168 ["128x64"] =
170 --highRes = false,
171 textSize = SMLSIZE,
172 xOffset = 60,
173 yOffset = 8,
174 yOffset_val = 3,
175 topOffset = 1,
176 leftOffset = 1,
178 ["128x96"] =
180 --highRes = false,
181 textSize = SMLSIZE,
182 xOffset = 60,
183 yOffset = 8,
184 yOffset_val = 3,
185 topOffset = 1,
186 leftOffset = 1,
188 ["212x64"] =
190 --highRes = false,
191 textSize = SMLSIZE,
192 xOffset = 60,
193 yOffset = 8,
194 yOffset_val = 3,
195 topOffset = 1,
196 leftOffset = 1,
198 ["480x272"] =
200 --highRes = true,
201 textSize = 0,
202 xOffset = 100,
203 yOffset = 20,
204 yOffset_val = 5,
205 topOffset = 1,
206 leftOffset = 1,
208 ["320x480"] =
210 --highRes = true,
211 textSize = 0,
212 xOffset = 120,
213 yOffset = 25,
214 yOffset_val = 5,
215 topOffset = 5,
216 leftOffset = 5,
220 local radio_resolution = LCD_W.."x"..LCD_H
221 local radio_data = assert(supportedRadios[radio_resolution], radio_resolution.." not supported")
223 -- redraw the screen
224 local function refreshLCD()
226 local yOffset = radio_data.topOffset;
227 local lOffset = radio_data.leftOffset;
229 lcd.clear()
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
233 else
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)
248 else
249 local value = '?'
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)
266 else
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)
275 else
276 lcd.drawText(lOffset, (radio_data.yOffset*5), "Connecting...", INVERS + BLINK)
280 local function increase(_menu)
281 local item = _menu
282 if item.modify then
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)
294 local item = _menu
295 if item.modify then
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 -- ################################################
306 --[[
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
313 period.
315 ]]--
317 function GetIndexOf(t,val)
318 for k,v in ipairs(t) do
319 if v == val then
320 return k
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
338 return value
339 else
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
361 gotFirstResp = true
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])
370 if data[8] == 6 then
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
376 else
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]
396 needResp = false
397 elseif (command == 0x2E) and (data[1] == 0xEA) and (data[2] == 0xEE) then
398 processElrsV2(data)
402 local function init_func()
403 loadViewFunctions()
406 local function bg_func(event)
409 --[[
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.
414 Basic strategy:
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
418 redraw the display
420 ]]--
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)
440 refreshLCD()
441 return retval
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
448 -- exit script
449 return 2
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
455 decrease(menu)
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
462 increase(menu)
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
470 local value = 0
471 if 0 < item.selected and item.selected <= #item.values then
472 value = item.values[item.selected]
473 else
474 type = 0
476 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, type, value})
477 NewReqTime = getTime()
478 needResp = true
479 menu.modify = false
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
482 menu.modify = true
483 elseif item.func ~= nil then
484 item.exec = true
485 elseif item.action == 'exit' then
486 -- exit script
487 return 2
490 elseif menu.modify and (event == EVT_VIRTUAL_EXIT or
491 event == EVT_EXIT_BREAK or
492 event == EVT_RTN_FIRST) then
493 menu.modify = false
494 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0x00, 0x00}) -- refresh data
495 NewReqTime = getTime()
496 needResp = true
499 refreshLCD()
501 return 0
504 --return {run = run_func, background = bg_func, init = init_func}
505 return {run = run_func, init = init_func}