makes GPIO_PIN_RST optional for the sx1276
[ExpressLRS.git] / src / lua / ELRS.lua
blobc160bf91f2c9c887440a78b95c723b4ef384313f
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 else
272 lcd.drawText(lOffset, (radio_data.yOffset*5), "Connecting...", INVERS + BLINK)
276 local function increase(_menu)
277 local item = _menu
278 if item.modify then
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)
290 local item = _menu
291 if item.modify then
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 -- ################################################
302 --[[
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
309 period.
311 ]]--
313 function GetIndexOf(t,val)
314 for k,v in ipairs(t) do
315 if v == val then
316 return k
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
334 return value
335 else
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
351 gotFirstResp = true
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])
360 if data[8] == 6 then
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
366 else
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]
386 needResp = false
390 local function init_func()
391 loadViewFunctions()
394 local function bg_func(event)
397 --[[
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.
402 Basic strategy:
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
406 redraw the display
408 ]]--
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)
428 refreshLCD()
429 return retval
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
436 -- exit script
437 return 2
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
443 decrease(menu)
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
450 increase(menu)
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
458 local value = 0
459 if 0 < item.selected and item.selected <= #item.values then
460 value = item.values[item.selected]
461 else
462 type = 0
464 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, type, value})
465 NewReqTime = getTime()
466 needResp = true
467 menu.modify = false
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
470 menu.modify = true
471 elseif item.func ~= nil then
472 item.exec = true
473 elseif item.action == 'exit' then
474 -- exit script
475 return 2
478 elseif menu.modify and (event == EVT_VIRTUAL_EXIT or
479 event == EVT_EXIT_BREAK or
480 event == EVT_RTN_FIRST) then
481 menu.modify = false
482 crossfireTelemetryPush(0x2D, {0xEE, 0xEA, 0x00, 0x00}) -- refresh data
483 NewReqTime = getTime()
484 needResp = true
487 refreshLCD()
489 return 0
492 --return {run = run_func, background = bg_func, init = init_func}
493 return {run = run_func, init = init_func}