Improve multi (#7136)
[opentx.git] / radio / src / lua / interface.cpp
blob73b71da62a4c31fc30de922b7db871f628dba12e
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 /** @file Main interface layer handler for Lua API. */
23 #include <ctype.h>
24 #include <stdio.h>
25 #include <algorithm>
26 #include "opentx.h"
27 #include "bin_allocator.h"
28 #include "lua_api.h"
29 #include "sdcard.h"
31 extern "C" {
32 #include <lundump.h>
35 #define PERMANENT_SCRIPTS_MAX_INSTRUCTIONS (10000/100)
36 #define MANUAL_SCRIPTS_MAX_INSTRUCTIONS (20000/100)
37 #define LUA_WARNING_INFO_LEN 64
39 lua_State *lsScripts = nullptr;
40 uint8_t luaState = 0;
41 uint8_t luaScriptsCount = 0;
42 ScriptInternalData scriptInternalData[MAX_SCRIPTS];
43 ScriptInputsOutputs scriptInputsOutputs[MAX_SCRIPTS];
44 ScriptInternalData standaloneScript;
45 uint16_t maxLuaInterval = 0;
46 uint16_t maxLuaDuration = 0;
47 bool luaLcdAllowed;
48 uint8_t instructionsPercent = 0;
49 char lua_warning_info[LUA_WARNING_INFO_LEN+1];
50 struct our_longjmp * global_lj = 0;
51 #if defined(COLORLCD)
52 uint32_t luaExtraMemoryUsage = 0;
53 #endif
55 #if defined(LUA_ALLOCATOR_TRACER)
57 LuaMemTracer lsScriptsTrace;
59 #if defined(PCBHORUS)
60 extern LuaMemTracer lsWidgetsTrace;
61 #define GET_TRACER(L) (L == lsScripts) ? &lsScriptsTrace : &lsWidgetsTrace
62 #else
63 #define GET_TRACER(L) &lsScriptsTrace
64 #endif
66 void *tracer_alloc(void * ud, void * ptr, size_t osize, size_t nsize)
68 LuaMemTracer * tracer = (LuaMemTracer *)ud;
69 if (ptr) {
70 if (osize < nsize) {
71 // TRACE("Lua alloc %u", nsize - osize);
72 tracer->alloc += nsize - osize;
74 else {
75 // TRACE("Lua free %u", osize - nsize);
76 tracer->free += osize - nsize;
79 else {
80 // TRACE("Lua alloc %u (type %s)", nsize, osize < LUA_TOTALTAGS ? lua_typename(0, osize) : "unk");
81 tracer->alloc += nsize;
83 return l_alloc(ud, ptr, osize, nsize);
86 #endif // #if defined(LUA_ALLOCATOR_TRACER)
88 /* custom panic handler */
89 int custom_lua_atpanic(lua_State * L)
91 TRACE("PANIC: unprotected error in call to Lua API (%s)", lua_tostring(L, -1));
92 if (global_lj) {
93 longjmp(global_lj->b, 1);
94 /* will never return */
96 return 0;
99 void luaHook(lua_State * L, lua_Debug *ar)
101 if (ar->event == LUA_HOOKCOUNT) {
102 instructionsPercent++;
103 #if defined(DEBUG)
104 // Disable Lua script instructions limit in DEBUG mode,
105 // just report max value reached
106 static uint16_t max = 0;
107 if (instructionsPercent > 100) {
108 if (max + 10 < instructionsPercent) {
109 max = instructionsPercent;
110 TRACE("LUA instructionsPercent %u%%", (uint32_t)max);
113 else if (instructionsPercent < 10) {
114 max = 0;
116 #else
117 if (instructionsPercent > 100) {
118 // From now on, as soon as a line is executed, error
119 // keep erroring until you're script reaches the top
120 lua_sethook(L, luaHook, LUA_MASKLINE, 0);
121 luaL_error(L, "CPU limit");
123 #endif
125 #if defined(LUA_ALLOCATOR_TRACER)
126 else if (ar->event == LUA_HOOKLINE) {
127 lua_getinfo(L, "nSl", ar);
128 LuaMemTracer * tracer = GET_TRACER(L);
129 if (tracer->alloc || tracer->free) {
130 TRACE("LT: [+%u,-%u] %s:%d", tracer->alloc, tracer->free, tracer->script, tracer->lineno);
132 tracer->script = ar->source;
133 tracer->lineno = ar->currentline;
134 tracer->alloc = 0;
135 tracer->free = 0;
137 #endif // #if defined(LUA_ALLOCATOR_TRACER)
140 void luaSetInstructionsLimit(lua_State * L, int count)
142 instructionsPercent = 0;
143 #if defined(LUA_ALLOCATOR_TRACER)
144 lua_sethook(L, luaHook, LUA_MASKCOUNT|LUA_MASKLINE, count);
145 #else
146 lua_sethook(L, luaHook, LUA_MASKCOUNT, count);
147 #endif
150 int luaGetInputs(lua_State * L, ScriptInputsOutputs & sid)
152 if (!lua_istable(L, -1))
153 return -1;
155 memclear(sid.inputs, sizeof(sid.inputs));
156 sid.inputsCount = 0;
157 for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
158 luaL_checktype(L, -2, LUA_TNUMBER); // key is number
159 luaL_checktype(L, -1, LUA_TTABLE); // value is table
160 if (sid.inputsCount<MAX_SCRIPT_INPUTS) {
161 uint8_t field = 0;
162 int type = 0;
163 ScriptInput * si = &sid.inputs[sid.inputsCount];
164 for (lua_pushnil(L); lua_next(L, -2) && field<5; lua_pop(L, 1), field++) {
165 switch (field) {
166 case 0:
167 luaL_checktype(L, -2, LUA_TNUMBER); // key is number
168 luaL_checktype(L, -1, LUA_TSTRING); // value is string
169 si->name = lua_tostring(L, -1);
170 break;
171 case 1:
172 luaL_checktype(L, -2, LUA_TNUMBER); // key is number
173 luaL_checktype(L, -1, LUA_TNUMBER); // value is number
174 type = lua_tointeger(L, -1);
175 if (type >= INPUT_TYPE_FIRST && type <= INPUT_TYPE_LAST) {
176 si->type = type;
178 if (si->type == INPUT_TYPE_VALUE) {
179 si->min = -100;
180 si->max = 100;
182 else {
183 si->max = MIXSRC_LAST_TELEM;
185 break;
186 case 2:
187 luaL_checktype(L, -2, LUA_TNUMBER); // key is number
188 luaL_checktype(L, -1, LUA_TNUMBER); // value is number
189 if (si->type == INPUT_TYPE_VALUE) {
190 si->min = lua_tointeger(L, -1);
192 break;
193 case 3:
194 luaL_checktype(L, -2, LUA_TNUMBER); // key is number
195 luaL_checktype(L, -1, LUA_TNUMBER); // value is number
196 if (si->type == INPUT_TYPE_VALUE) {
197 si->max = lua_tointeger(L, -1);
199 break;
200 case 4:
201 luaL_checktype(L, -2, LUA_TNUMBER); // key is number
202 luaL_checktype(L, -1, LUA_TNUMBER); // value is number
203 if (si->type == INPUT_TYPE_VALUE) {
204 si->def = lua_tointeger(L, -1);
206 break;
209 sid.inputsCount++;
213 return 0;
216 int luaGetOutputs(lua_State * L, ScriptInputsOutputs & sid)
218 if (!lua_istable(L, -1))
219 return -1;
221 sid.outputsCount = 0;
222 for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
223 luaL_checktype(L, -2, LUA_TNUMBER); // key is number
224 luaL_checktype(L, -1, LUA_TSTRING); // value is string
225 if (sid.outputsCount<MAX_SCRIPT_OUTPUTS) {
226 sid.outputs[sid.outputsCount++].name = lua_tostring(L, -1);
230 return 0;
233 void luaDisable()
235 POPUP_WARNING("Lua disabled!");
236 luaState = INTERPRETER_PANIC;
239 void luaClose(lua_State ** L)
241 if (*L) {
242 PROTECT_LUA() {
243 TRACE("luaClose %p", *L);
244 lua_close(*L); // this should not panic, but we make sure anyway
245 #if defined(LUA_ALLOCATOR_TRACER)
246 LuaMemTracer * tracer = GET_TRACER(*L);
247 if (tracer->alloc || tracer->free) {
248 TRACE("LT: [+%u,-%u] luaClose(%s)", tracer->alloc, tracer->free, (*L == lsScripts) ? "scipts" : "widgets");
250 tracer->alloc = 0;
251 tracer->free = 0;
252 #endif // #if defined(LUA_ALLOCATOR_TRACER)
254 else {
255 // we can only disable Lua for the rest of the session
256 if (*L == lsScripts) luaDisable();
258 UNPROTECT_LUA();
259 *L = nullptr;
263 void luaRegisterLibraries(lua_State * L)
265 luaL_openlibs(L);
266 #if defined(COLORLCD)
267 registerBitmapClass(L);
268 #endif
271 #define GC_REPORT_TRESHOLD (2*1024)
273 void luaDoGc(lua_State * L, bool full)
275 if (L) {
276 PROTECT_LUA() {
277 if (full) {
278 lua_gc(L, LUA_GCCOLLECT, 0);
280 else {
281 lua_gc(L, LUA_GCSTEP, 10);
283 #if defined(DEBUG)
284 if (L == lsScripts) {
285 static uint32_t lastgcSctipts = 0;
286 uint32_t gc = luaGetMemUsed(L);
287 if (gc > (lastgcSctipts + GC_REPORT_TRESHOLD) || (gc + GC_REPORT_TRESHOLD) < lastgcSctipts) {
288 lastgcSctipts = gc;
289 TRACE("GC Use Scripts: %u bytes", gc);
292 #if defined(COLORLCD)
293 if (L == lsWidgets) {
294 static uint32_t lastgcWidgets = 0;
295 uint32_t gc = luaGetMemUsed(L);
296 if (gc > (lastgcWidgets + GC_REPORT_TRESHOLD) || (gc + GC_REPORT_TRESHOLD) < lastgcWidgets) {
297 lastgcWidgets = gc;
298 TRACE("GC Use Widgets: %u bytes + Extra %u", gc, luaExtraMemoryUsage);
301 #endif
302 #endif
304 else {
305 // we disable Lua for the rest of the session
306 if (L == lsScripts) luaDisable();
307 #if defined(COLORLCD)
308 if (L == lsWidgets) lsWidgets = 0;
309 #endif
311 UNPROTECT_LUA();
315 void luaFree(lua_State * L, ScriptInternalData & sid)
317 PROTECT_LUA() {
318 if (sid.run) {
319 luaL_unref(L, LUA_REGISTRYINDEX, sid.run);
320 sid.run = 0;
322 if (sid.background) {
323 luaL_unref(L, LUA_REGISTRYINDEX, sid.background);
324 sid.background = 0;
327 else {
328 luaDisable();
330 UNPROTECT_LUA();
332 luaDoGc(L, true);
335 #if defined(LUA_COMPILER)
336 /// callback for luaU_dump()
337 static int luaDumpWriter(lua_State * L, const void* p, size_t size, void* u)
339 UNUSED(L);
340 UINT written;
341 FRESULT result = f_write((FIL *)u, p, size, &written);
342 return (result != FR_OK && !written);
346 @fn luaDumpState(lua_State * L, const char * filename, const FILINFO * finfo, int stripDebug)
348 Save compiled bytecode from a given Lua stack to a file.
350 @param L The Lua stack to dump.
351 @param filename Full path and name of file to save to (typically with .luac extension).
352 @param finfo Can be NULL. If not NULL, sets timestamp of created file to match the one in finfo->fdate/ftime
353 @param stripDebug This is passed directly to luaU_dump()
354 1 = remove debug info from bytecode (smaller but errors are less informative)
355 0 = keep debug info
357 static void luaDumpState(lua_State * L, const char * filename, const FILINFO * finfo, int stripDebug)
359 FIL D;
360 if (f_open(&D, filename, FA_WRITE | FA_CREATE_ALWAYS) == FR_OK) {
361 lua_lock(L);
362 luaU_dump(L, getproto(L->top - 1), luaDumpWriter, &D, stripDebug);
363 lua_unlock(L);
364 if (f_close(&D) == FR_OK) {
365 if (finfo != nullptr)
366 f_utime(filename, finfo); // set the file mod time
367 TRACE("luaDumpState(%s): Saved bytecode to file.", filename);
369 } else
370 TRACE_ERROR("luaDumpState(%s): Error: Could not open output file.", filename);
372 #endif // LUA_COMPILER
375 @fn luaLoadScriptFileToState(lua_State * L, const char * filename, const char * mode)
377 Load a Lua script file into a given lua_State (stack). May use OpenTx's optional pre-compilation
378 feature to save memory and time during load.
380 @param L (lua_State) the Lua stack to load into.
382 @param filename (string) full path and file name of script.
384 @param mode (string) controls whether the file can be text or binary (that is, a pre-compiled file).
385 Possible values are:
386 "b" only binary.
387 "t" only text.
388 "T" (default on simulator) prefer text but load binary if that is the only version available.
389 "bt" (default on radio) either binary or text, whichever is newer (binary preferred when timestamps are equal).
390 Add "x" to avoid automatic compilation of source file to .luac version.
391 Eg: "tx", "bx", or "btx".
392 Add "c" to force compilation of source file to .luac version (even if existing version is newer than source file).
393 Eg: "tc" or "btc" (forces "t", overrides "x").
394 Add "d" to keep extra debug info in the compiled binary.
395 Eg: "td", "btd", or "tcd" (no effect with just "b" or with "x").
397 @retval (int)
398 SCRIPT_OK on success (LUA_OK)
399 SCRIPT_NOFILE if file wasn't found for specified mode or Lua could not open file (LUA_ERRFILE)
400 SCRIPT_SYNTAX_ERROR if Lua returned a syntax error during pre/de-compilation (LUA_ERRSYNTAX)
401 SCRIPT_PANIC for Lua memory errors (LUA_ERRMEM or LUA_ERRGCMM)
403 int luaLoadScriptFileToState(lua_State * L, const char * filename, const char * mode)
405 if (luaState == INTERPRETER_PANIC) {
406 return SCRIPT_PANIC;
407 } else if (filename == nullptr) {
408 return SCRIPT_NOFILE;
411 int lstatus;
412 char lmode[6] = "bt";
413 uint8_t ret = SCRIPT_NOFILE;
415 if (mode != nullptr) {
416 strncpy(lmode, mode, sizeof(lmode)-1);
417 lmode[sizeof(lmode)-1] = '\0';
420 #if defined(LUA_COMPILER)
421 uint16_t fnamelen;
422 uint8_t extlen;
423 char filenameFull[LEN_FILE_PATH_MAX + _MAX_LFN + 1] = "\0";
424 FILINFO fnoLuaS, fnoLuaC;
425 FRESULT frLuaS, frLuaC;
427 bool scriptNeedsCompile = false;
428 uint8_t loadFileType = 0; // 1=text, 2=binary
430 memset(&fnoLuaS, 0, sizeof(FILINFO));
431 memset(&fnoLuaC, 0, sizeof(FILINFO));
433 fnamelen = strlen(filename);
434 // check if file extension is already in the file name and strip it
435 getFileExtension(filename, fnamelen, 0, nullptr, &extlen);
436 fnamelen -= extlen;
437 if (fnamelen > sizeof(filenameFull) - sizeof(SCRIPT_BIN_EXT)) {
438 TRACE_ERROR("luaLoadScriptFileToState(%s, %s): Error loading script: filename buffer overflow.\n", filename, lmode);
439 return ret;
441 strncat(filenameFull, filename, fnamelen);
443 // check if binary version exists
444 strcpy(filenameFull + fnamelen, SCRIPT_BIN_EXT);
445 frLuaC = f_stat(filenameFull, &fnoLuaC);
447 // check if text version exists
448 strcpy(filenameFull + fnamelen, SCRIPT_EXT);
449 frLuaS = f_stat(filenameFull, &fnoLuaS);
451 // decide which version to load, text or binary
452 if (frLuaC != FR_OK && frLuaS == FR_OK) {
453 // only text version exists
454 loadFileType = 1;
455 scriptNeedsCompile = true;
457 else if (frLuaC == FR_OK && frLuaS != FR_OK) {
458 // only binary version exists
459 loadFileType = 2;
461 else if (frLuaS == FR_OK) {
462 // both versions exist, compare them
463 if (strchr(lmode, 'c') || (uint32_t)((fnoLuaC.fdate << 16) + fnoLuaC.ftime) < (uint32_t)((fnoLuaS.fdate << 16) + fnoLuaS.ftime)) {
464 // text version is newer than binary or forced by "c" mode flag, rebuild it
465 scriptNeedsCompile = true;
467 if (scriptNeedsCompile || !strchr(lmode, 'b')) {
468 // text version needs compilation or forced by mode
469 loadFileType = 1;
470 } else {
471 // use binary file
472 loadFileType = 2;
475 // else both versions are missing
477 // skip compilation based on mode flags? ("c" overrides "x")
478 if (scriptNeedsCompile && strchr(lmode, 'x') && !strchr(lmode, 'c')) {
479 scriptNeedsCompile = false;
482 if (loadFileType == 2) {
483 // change file extension to binary version
484 strcpy(filenameFull + fnamelen, SCRIPT_BIN_EXT);
487 // TRACE_DEBUG("luaLoadScriptFileToState(%s, %s):\n", filename, lmode);
488 // TRACE_DEBUG("\tldfile='%s'; ldtype=%u; compile=%u;\n", filenameFull, loadFileType, scriptNeedsCompile);
489 // TRACE_DEBUG("\t%-5s: %s; mtime: %04X%04X = %u/%02u/%02u %02u:%02u:%02u;\n", SCRIPT_EXT, (frLuaS == FR_OK ? "ok" : "nf"), fnoLuaS.fdate, fnoLuaS.ftime,
490 // (fnoLuaS.fdate >> 9) + 1980, (fnoLuaS.fdate >> 5) & 15, fnoLuaS.fdate & 31, fnoLuaS.ftime >> 11, (fnoLuaS.ftime >> 5) & 63, (fnoLuaS.ftime & 31) * 2);
491 // TRACE_DEBUG("\t%-5s: %s; mtime: %04X%04X = %u/%02u/%02u %02u:%02u:%02u;\n", SCRIPT_BIN_EXT, (frLuaC == FR_OK ? "ok" : "nf"), fnoLuaC.fdate, fnoLuaC.ftime,
492 // (fnoLuaC.fdate >> 9) + 1980, (fnoLuaC.fdate >> 5) & 15, fnoLuaC.fdate & 31, fnoLuaC.ftime >> 11, (fnoLuaC.ftime >> 5) & 63, (fnoLuaC.ftime & 31) * 2);
494 // final check that file exists and is allowed by mode flags
495 if (!loadFileType || (loadFileType == 1 && !strpbrk(lmode, "tTc")) || (loadFileType == 2 && !strpbrk(lmode, "bT"))) {
496 TRACE_ERROR("luaLoadScriptFileToState(%s, %s): Error loading script: file not found.\n", filename, lmode);
497 return SCRIPT_NOFILE;
500 #else // !defined(LUA_COMPILER)
502 // use passed file name as-is
503 const char *filenameFull = filename;
505 #endif
507 TRACE("luaLoadScriptFileToState(%s, %s): loading %s", filename, lmode, filenameFull);
509 // we don't pass <mode> on to loadfilex() because we want lua to load whatever file we specify, regardless of content
510 lstatus = luaL_loadfilex(L, filenameFull, nullptr);
511 #if defined(LUA_COMPILER)
512 // Check for bytecode encoding problem, eg. compiled for x64. Unfortunately Lua doesn't provide a unique error code for this. See Lua/src/lundump.c.
513 if (lstatus == LUA_ERRSYNTAX && loadFileType == 2 && frLuaS == FR_OK && strstr(lua_tostring(L, -1), "precompiled")) {
514 loadFileType = 1;
515 scriptNeedsCompile = true;
516 strcpy(filenameFull + fnamelen, SCRIPT_EXT);
517 TRACE_ERROR("luaLoadScriptFileToState(%s, %s): Error loading script: %s\n\tRetrying with %s\n", filename, lmode, lua_tostring(L, -1), filenameFull);
518 lstatus = luaL_loadfilex(L, filenameFull, nullptr);
520 if (lstatus == LUA_OK) {
521 if (scriptNeedsCompile && loadFileType == 1) {
522 strcpy(filenameFull + fnamelen, SCRIPT_BIN_EXT);
523 luaDumpState(L, filenameFull, &fnoLuaS, (strchr(lmode, 'd') ? 0 : 1));
525 ret = SCRIPT_OK;
527 #else
528 if (lstatus == LUA_OK) {
529 ret = SCRIPT_OK;
531 #endif
532 else {
533 TRACE_ERROR("luaLoadScriptFileToState(%s, %s): Error loading script: %s\n", filename, lmode, lua_tostring(L, -1));
534 if (lstatus == LUA_ERRFILE) {
535 ret = SCRIPT_NOFILE;
537 else if (lstatus == LUA_ERRSYNTAX) {
538 ret = SCRIPT_SYNTAX_ERROR;
540 else { // LUA_ERRMEM or LUA_ERRGCMM
541 ret = SCRIPT_PANIC;
545 return ret;
548 static int luaLoad(lua_State * L, const char * filename, ScriptInternalData & sid, ScriptInputsOutputs * sio=nullptr)
550 int init = 0;
551 int lstatus = 0;
553 sid.instructions = 0;
554 sid.state = SCRIPT_OK;
556 if (luaState == INTERPRETER_PANIC) {
557 return SCRIPT_PANIC;
560 luaSetInstructionsLimit(L, MANUAL_SCRIPTS_MAX_INSTRUCTIONS);
562 PROTECT_LUA() {
563 sid.state = luaLoadScriptFileToState(L, filename, LUA_SCRIPT_LOAD_MODE);
564 if (sid.state == SCRIPT_OK && (lstatus = lua_pcall(L, 0, 1, 0)) == LUA_OK && lua_istable(L, -1)) {
565 for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
566 const char * key = lua_tostring(L, -2);
567 if (!strcmp(key, "init")) {
568 init = luaL_ref(L, LUA_REGISTRYINDEX);
569 lua_pushnil(L);
571 else if (!strcmp(key, "run")) {
572 sid.run = luaL_ref(L, LUA_REGISTRYINDEX);
573 lua_pushnil(L);
575 else if (!strcmp(key, "background")) {
576 sid.background = luaL_ref(L, LUA_REGISTRYINDEX);
577 lua_pushnil(L);
579 else if (sio && !strcmp(key, "input")) {
580 luaGetInputs(L, *sio);
582 else if (sio && !strcmp(key, "output")) {
583 luaGetOutputs(L, *sio);
587 if (init) {
588 lua_rawgeti(L, LUA_REGISTRYINDEX, init);
589 if (lua_pcall(L, 0, 0, 0) != 0) {
590 TRACE_ERROR("luaLoad(%s): Error in script init(): %s\n", filename, lua_tostring(L, -1));
591 sid.state = SCRIPT_SYNTAX_ERROR;
593 luaL_unref(L, LUA_REGISTRYINDEX, init);
594 lua_gc(L, LUA_GCCOLLECT, 0);
597 else if (sid.state == SCRIPT_OK) {
598 TRACE_ERROR("luaLoad(%s): Error parsing script (%d): %s\n", filename, lstatus, lua_tostring(L, -1));
599 sid.state = SCRIPT_SYNTAX_ERROR;
602 else {
603 luaDisable();
604 return SCRIPT_PANIC;
606 UNPROTECT_LUA();
608 if (sid.state != SCRIPT_OK) {
609 luaFree(L, sid);
612 luaDoGc(L, true);
614 return sid.state;
617 bool luaLoadMixScript(uint8_t index)
619 ScriptData & sd = g_model.scriptsData[index];
621 if (ZEXIST(sd.file)) {
622 ScriptInternalData & sid = scriptInternalData[luaScriptsCount++];
623 ScriptInputsOutputs * sio = &scriptInputsOutputs[index];
624 sid.reference = SCRIPT_MIX_FIRST+index;
625 sid.state = SCRIPT_NOFILE;
626 char filename[sizeof(SCRIPTS_MIXES_PATH) + LEN_SCRIPT_FILENAME + sizeof(SCRIPT_EXT)] = SCRIPTS_MIXES_PATH "/";
627 strncpy(filename + sizeof(SCRIPTS_MIXES_PATH), sd.file, LEN_SCRIPT_FILENAME);
628 filename[sizeof(SCRIPTS_MIXES_PATH) + LEN_SCRIPT_FILENAME] = '\0';
629 strcat(filename + sizeof(SCRIPTS_MIXES_PATH), SCRIPT_EXT);
630 if (luaLoad(lsScripts, filename, sid, sio) == SCRIPT_PANIC) {
631 return false;
634 return true;
637 bool luaLoadFunctionScript(uint8_t index, uint8_t ref)
639 CustomFunctionData * fn;
641 if (ref < SCRIPT_GFUNC_FIRST)
642 fn = &g_model.customFn[index];
643 else if (!g_model.noGlobalFunctions)
644 fn = &g_eeGeneral.customFn[index];
645 else
646 return true;
648 if (fn->func == FUNC_PLAY_SCRIPT && ZEXIST(fn->play.name)) {
649 if (luaScriptsCount < MAX_SCRIPTS) {
650 ScriptInternalData & sid = scriptInternalData[luaScriptsCount++];
651 sid.reference = ref + index;
652 sid.state = SCRIPT_NOFILE;
653 char filename[sizeof(SCRIPTS_FUNCS_PATH) + LEN_FUNCTION_NAME + sizeof(SCRIPT_EXT)] = SCRIPTS_FUNCS_PATH "/";
654 strncpy(filename + sizeof(SCRIPTS_FUNCS_PATH), fn->play.name, LEN_FUNCTION_NAME);
655 filename[sizeof(SCRIPTS_FUNCS_PATH) + LEN_FUNCTION_NAME] = '\0';
656 strcat(filename + sizeof(SCRIPTS_FUNCS_PATH), SCRIPT_EXT);
657 if (luaLoad(lsScripts, filename, sid) == SCRIPT_PANIC) {
658 return false;
661 else {
662 POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS);
663 return false;
666 return true;
669 #if defined(PCBTARANIS)
670 bool luaLoadTelemetryScript(uint8_t index)
672 TelemetryScreenType screenType = TELEMETRY_SCREEN_TYPE(index);
674 if (screenType == TELEMETRY_SCREEN_TYPE_SCRIPT) {
675 TelemetryScriptData & script = g_model.screens[index].script;
676 if (ZEXIST(script.file)) {
677 if (luaScriptsCount < MAX_SCRIPTS) {
678 ScriptInternalData & sid = scriptInternalData[luaScriptsCount++];
679 sid.reference = SCRIPT_TELEMETRY_FIRST+index;
680 sid.state = SCRIPT_NOFILE;
681 char filename[sizeof(SCRIPTS_TELEM_PATH) + LEN_SCRIPT_FILENAME + sizeof(SCRIPT_EXT)] = SCRIPTS_TELEM_PATH "/";
682 strncpy(filename + sizeof(SCRIPTS_TELEM_PATH), script.file, LEN_SCRIPT_FILENAME);
683 filename[sizeof(SCRIPTS_TELEM_PATH) + LEN_SCRIPT_FILENAME] = '\0';
684 strcat(filename + sizeof(SCRIPTS_TELEM_PATH), SCRIPT_EXT);
685 if (luaLoad(lsScripts, filename, sid) == SCRIPT_PANIC) {
686 return false;
689 else {
690 POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS);
691 return false;
695 return true;
697 #endif
699 uint8_t isTelemetryScriptAvailable(uint8_t index)
701 for (int i=0; i<luaScriptsCount; i++) {
702 ScriptInternalData & sid = scriptInternalData[i];
703 if (sid.reference == SCRIPT_TELEMETRY_FIRST+index) {
704 return sid.state;
707 return SCRIPT_NOFILE;
710 void luaLoadPermanentScripts()
712 luaScriptsCount = 0;
713 memset(scriptInternalData, 0, sizeof(scriptInternalData));
714 memset(scriptInputsOutputs, 0, sizeof(scriptInputsOutputs));
716 // Load model scripts
717 for (int i=0; i<MAX_SCRIPTS; i++) {
718 if (!luaLoadMixScript(i)) {
719 return;
723 // Load custom function scripts
724 for (int i=0; i<MAX_SPECIAL_FUNCTIONS; i++) {
725 if (!luaLoadFunctionScript(i, SCRIPT_FUNC_FIRST) || !luaLoadFunctionScript(i, SCRIPT_GFUNC_FIRST)) {
726 return;
730 #if defined(PCBTARANIS)
731 // Load custom telemetry scripts
732 for (int i=0; i<MAX_TELEMETRY_SCREENS; i++) {
733 if (!luaLoadTelemetryScript(i)) {
734 return;
737 #endif
740 void displayLuaError(const char * title)
742 #if !defined(COLORLCD)
743 drawMessageBox(title);
744 #endif
745 if (lua_warning_info[0]) {
746 char * split = strstr(lua_warning_info, ": ");
747 if (split) {
748 lcdDrawSizedText(WARNING_LINE_X, WARNING_LINE_Y+FH+3, lua_warning_info, split-lua_warning_info, SMLSIZE);
749 lcdDrawSizedText(WARNING_LINE_X, WARNING_LINE_Y+2*FH+2, split+2, lua_warning_info+LUA_WARNING_INFO_LEN-split, SMLSIZE);
751 else {
752 lcdDrawSizedText(WARNING_LINE_X, WARNING_LINE_Y+FH+3, lua_warning_info, 40, SMLSIZE);
757 void displayAcknowledgeLuaError(event_t event)
759 warningResult = false;
760 displayLuaError(warningText);
761 if (event == EVT_KEY_BREAK(KEY_EXIT)) {
762 warningText = nullptr;
766 void luaError(lua_State * L, uint8_t error, bool acknowledge)
768 const char * errorTitle;
770 switch (error) {
771 case SCRIPT_SYNTAX_ERROR:
772 errorTitle = STR_SCRIPT_SYNTAX_ERROR;
773 break;
774 case SCRIPT_KILLED:
775 errorTitle = STR_SCRIPT_KILLED;
776 break;
777 case SCRIPT_PANIC:
778 errorTitle = STR_SCRIPT_PANIC;
779 break;
780 default:
781 errorTitle = STR_SCRIPT_ERROR;
782 break;
785 const char * msg = lua_tostring(L, -1);
786 if (msg) {
787 #if defined(SIMU)
788 if (!strncmp(msg, ".", 2)) msg += 1;
789 #endif
790 if (!strncmp(msg, "/SCRIPTS/", 9)) msg += 9;
791 strncpy(lua_warning_info, msg, LUA_WARNING_INFO_LEN);
792 lua_warning_info[LUA_WARNING_INFO_LEN] = '\0';
794 else {
795 lua_warning_info[0] = '\0';
798 if (acknowledge) {
799 warningText = errorTitle;
800 warningType = WARNING_TYPE_INFO;
801 popupFunc = displayAcknowledgeLuaError;
803 else {
804 displayLuaError(errorTitle);
808 void luaExec(const char * filename)
810 luaInit();
812 if (luaState != INTERPRETER_PANIC) {
813 standaloneScript.state = SCRIPT_NOFILE;
814 int result = luaLoad(lsScripts, filename, standaloneScript);
815 // TODO the same with run ...
816 if (result == SCRIPT_OK) {
817 luaState = INTERPRETER_RUNNING_STANDALONE_SCRIPT;
819 else {
820 luaError(lsScripts, result);
821 luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
826 void luaDoOneRunStandalone(event_t evt)
828 static uint8_t luaDisplayStatistics = false;
830 if (standaloneScript.state == SCRIPT_OK && standaloneScript.run) {
831 luaSetInstructionsLimit(lsScripts, MANUAL_SCRIPTS_MAX_INSTRUCTIONS);
832 lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, standaloneScript.run);
833 lua_pushunsigned(lsScripts, evt);
834 if (lua_pcall(lsScripts, 1, 1, 0) == 0) {
835 if (!lua_isnumber(lsScripts, -1)) {
836 if (instructionsPercent > 100) {
837 TRACE("Script killed");
838 standaloneScript.state = SCRIPT_KILLED;
839 luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
841 else if (lua_isstring(lsScripts, -1)) {
842 char nextScript[_MAX_LFN+1];
843 strncpy(nextScript, lua_tostring(lsScripts, -1), _MAX_LFN);
844 nextScript[_MAX_LFN] = '\0';
845 luaExec(nextScript);
847 else {
848 TRACE("Script run function returned unexpected value");
849 standaloneScript.state = SCRIPT_SYNTAX_ERROR;
850 luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
853 else {
854 int scriptResult = lua_tointeger(lsScripts, -1);
855 lua_pop(lsScripts, 1); /* pop returned value */
856 if (scriptResult != 0) {
857 TRACE("Script finished with status %d", scriptResult);
858 standaloneScript.state = SCRIPT_NOFILE;
859 luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
860 return;
862 else if (luaDisplayStatistics) {
863 #if defined(COLORLCD)
864 #else
865 lcdDrawSolidHorizontalLine(0, 7*FH-1, lcdLastRightPos+6, ERASE);
866 lcdDrawText(0, 7*FH, "GV Use: ");
867 lcdDrawNumber(lcdLastRightPos, 7*FH, luaGetMemUsed(lsScripts), LEFT);
868 lcdDrawChar(lcdLastRightPos, 7*FH, 'b');
869 lcdDrawSolidHorizontalLine(0, 7*FH-2, lcdLastRightPos+6, FORCE);
870 lcdDrawVerticalLine(lcdLastRightPos+6, 7*FH-2, FH+2, SOLID, FORCE);
871 #endif
875 else {
876 TRACE("Script error: %s", lua_tostring(lsScripts, -1));
877 standaloneScript.state = (instructionsPercent > 100 ? SCRIPT_KILLED : SCRIPT_SYNTAX_ERROR);
878 luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
881 if (standaloneScript.state != SCRIPT_OK) {
882 luaError(lsScripts, standaloneScript.state);
883 luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
886 if (evt == EVT_KEY_LONG(KEY_EXIT)) {
887 TRACE("Script force exit");
888 killEvents(evt);
889 standaloneScript.state = SCRIPT_NOFILE;
890 luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
892 #if defined(KEYS_GPIO_REG_MENU)
893 // TODO find another key and add a #define
894 else if (evt == EVT_KEY_LONG(KEY_MENU)) {
895 killEvents(evt);
896 luaDisplayStatistics = !luaDisplayStatistics;
898 #endif
900 else {
901 TRACE("Script run method missing");
902 standaloneScript.state = SCRIPT_SYNTAX_ERROR;
903 luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
907 bool luaDoOneRunPermanentScript(event_t evt, int i, uint32_t scriptType)
909 ScriptInternalData & sid = scriptInternalData[i];
910 if (sid.state != SCRIPT_OK) return false;
912 luaSetInstructionsLimit(lsScripts, PERMANENT_SCRIPTS_MAX_INSTRUCTIONS);
913 int inputsCount = 0;
914 #if defined(SIMU) || defined(DEBUG)
915 const char *filename;
916 #endif
917 ScriptInputsOutputs * sio = nullptr;
918 #if SCRIPT_MIX_FIRST > 0
919 if ((scriptType & RUN_MIX_SCRIPT) && (sid.reference >= SCRIPT_MIX_FIRST && sid.reference <= SCRIPT_MIX_LAST)) {
920 #else
921 if ((scriptType & RUN_MIX_SCRIPT) && (sid.reference <= SCRIPT_MIX_LAST)) {
922 #endif
923 ScriptData & sd = g_model.scriptsData[sid.reference-SCRIPT_MIX_FIRST];
924 sio = &scriptInputsOutputs[sid.reference-SCRIPT_MIX_FIRST];
925 inputsCount = sio->inputsCount;
926 #if defined(SIMU) || defined(DEBUG)
927 filename = sd.file;
928 #endif
929 lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run);
930 for (int j=0; j<sio->inputsCount; j++) {
931 if (sio->inputs[j].type == INPUT_TYPE_SOURCE)
932 luaGetValueAndPush(lsScripts, sd.inputs[j].source);
933 else
934 lua_pushinteger(lsScripts, sd.inputs[j].value + sio->inputs[j].def);
937 else if ((scriptType & RUN_FUNC_SCRIPT) && (sid.reference >= SCRIPT_FUNC_FIRST && sid.reference <= SCRIPT_GFUNC_LAST)) {
938 CustomFunctionData & fn = (sid.reference < SCRIPT_GFUNC_FIRST ? g_model.customFn[sid.reference-SCRIPT_FUNC_FIRST] : g_eeGeneral.customFn[sid.reference-SCRIPT_GFUNC_FIRST]);
939 #if defined(SIMU) || defined(DEBUG)
940 filename = fn.play.name;
941 #endif
942 if (getSwitch(fn.swtch))
943 lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run);
944 else if (sid.background)
945 lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.background);
946 else
947 return false;
949 else {
950 #if defined(PCBTARANIS)
951 #if defined(SIMU) || defined(DEBUG)
952 TelemetryScriptData & script = g_model.screens[sid.reference-SCRIPT_TELEMETRY_FIRST].script;
953 filename = script.file;
954 #endif
955 if ((scriptType & RUN_TELEM_FG_SCRIPT) && (menuHandlers[0]==menuViewTelemetryFrsky && sid.reference==SCRIPT_TELEMETRY_FIRST+s_frsky_view)) {
956 lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run);
957 lua_pushunsigned(lsScripts, evt);
958 inputsCount = 1;
960 else if ((scriptType & RUN_TELEM_BG_SCRIPT) && (sid.background)) {
961 lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.background);
963 else {
964 return false;
966 #else
967 return false;
968 #endif
971 if (lua_pcall(lsScripts, inputsCount, sio ? sio->outputsCount : 0, 0) == 0) {
972 if (sio) {
973 for (int j=sio->outputsCount-1; j>=0; j--) {
974 if (!lua_isnumber(lsScripts, -1)) {
975 sid.state = (instructionsPercent > 100 ? SCRIPT_KILLED : SCRIPT_SYNTAX_ERROR);
976 TRACE("Script %8s disabled", filename);
977 break;
979 sio->outputs[j].value = lua_tointeger(lsScripts, -1);
980 lua_pop(lsScripts, 1);
984 else {
985 if (instructionsPercent > 100) {
986 TRACE("Script %8s killed", filename);
987 sid.state = SCRIPT_KILLED;
989 else {
990 TRACE("Script %8s error: %s", filename, lua_tostring(lsScripts, -1));
991 sid.state = SCRIPT_SYNTAX_ERROR;
995 if (sid.state != SCRIPT_OK) {
996 luaFree(lsScripts, sid);
998 else {
999 if (instructionsPercent > sid.instructions) {
1000 sid.instructions = instructionsPercent;
1003 return true;
1006 bool luaTask(event_t evt, uint8_t scriptType, bool allowLcdUsage)
1008 if (luaState == INTERPRETER_PANIC) return false;
1009 luaLcdAllowed = allowLcdUsage;
1010 bool scriptWasRun = false;
1012 // we run either standalone script or permanent scripts
1013 if (luaState & INTERPRETER_RUNNING_STANDALONE_SCRIPT) {
1014 // run standalone script
1015 if ((scriptType & RUN_STNDAL_SCRIPT) == 0) return false;
1016 PROTECT_LUA() {
1017 luaDoOneRunStandalone(evt);
1018 scriptWasRun = true;
1020 else {
1021 luaDisable();
1022 return false;
1024 UNPROTECT_LUA();
1026 else {
1027 // run permanent scripts
1028 if (luaState & INTERPRETER_RELOAD_PERMANENT_SCRIPTS) {
1029 luaState = 0;
1030 luaInit();
1031 if (luaState == INTERPRETER_PANIC) return false;
1032 luaLoadPermanentScripts();
1033 if (luaState == INTERPRETER_PANIC) return false;
1036 for (int i=0; i<luaScriptsCount; i++) {
1037 PROTECT_LUA() {
1038 scriptWasRun |= luaDoOneRunPermanentScript(evt, i, scriptType);
1040 else {
1041 luaDisable();
1042 break;
1044 UNPROTECT_LUA();
1045 //todo gc step between scripts
1048 luaDoGc(lsScripts, false);
1049 #if defined(COLORLCD)
1050 luaDoGc(lsWidgets, false);
1051 #endif
1052 return scriptWasRun;
1055 void checkLuaMemoryUsage()
1057 #if (LUA_MEM_MAX > 0)
1058 uint32_t totalMemUsed = luaGetMemUsed(lsScripts);
1059 #if defined(COLORLCD)
1060 totalMemUsed += luaGetMemUsed(lsWidgets);
1061 totalMemUsed += luaExtraMemoryUsage;
1062 #endif
1063 if (totalMemUsed > LUA_MEM_MAX) {
1064 TRACE("checkLuaMemoryUsage(): max limit reached (%u), killing Lua", totalMemUsed);
1065 // disable Lua scripts
1066 luaClose(&lsScripts);
1067 luaDisable();
1068 #if defined(COLORLCD)
1069 // disable widgets
1070 luaClose(&lsWidgets);
1071 #endif
1073 #endif
1076 uint32_t luaGetMemUsed(lua_State * L)
1078 return L ? (lua_gc(L, LUA_GCCOUNT, 0) << 10) + lua_gc(L, LUA_GCCOUNTB, 0) : 0;
1081 void luaInit()
1083 TRACE("luaInit");
1085 luaClose(&lsScripts);
1087 if (luaState != INTERPRETER_PANIC) {
1088 #if defined(USE_BIN_ALLOCATOR)
1089 lsScripts = lua_newstate(bin_l_alloc, nullptr); //we use our own allocator!
1090 #elif defined(LUA_ALLOCATOR_TRACER)
1091 memset(&lsScriptsTrace, 0 , sizeof(lsScriptsTrace));
1092 lsScriptsTrace.script = "lua_newstate(scripts)";
1093 lsScripts = lua_newstate(tracer_alloc, &lsScriptsTrace); //we use tracer allocator
1094 #else
1095 lsScripts = lua_newstate(l_alloc, nullptr); //we use Lua default allocator
1096 #endif
1097 if (lsScripts) {
1098 // install our panic handler
1099 lua_atpanic(lsScripts, &custom_lua_atpanic);
1101 #if defined(LUA_ALLOCATOR_TRACER)
1102 lua_sethook(lsScripts, luaHook, LUA_MASKLINE, 0);
1103 #endif
1105 // protect libs and constants registration
1106 PROTECT_LUA() {
1107 luaRegisterLibraries(lsScripts);
1109 else {
1110 // if we got panic during registration
1111 // we disable Lua for this session
1112 luaDisable();
1114 UNPROTECT_LUA();
1115 TRACE("lsScripts %p", lsScripts);
1117 else {
1118 /* log error and return */
1119 luaDisable();
1124 bool readToolName(char * toolName, const char * filename)
1126 FIL file;
1127 char buffer[1024];
1128 UINT count;
1130 if (f_open(&file, filename, FA_READ) != FR_OK) {
1131 return "Error opening file";
1134 FRESULT res = f_read(&file, &buffer, sizeof(buffer), &count);
1135 f_close(&file);
1137 if (res != FR_OK)
1138 return false;
1140 const char * tns = "TNS|";
1141 auto * start = std::search(buffer, buffer + sizeof(buffer), tns, tns + 4);
1142 if (start >= buffer + sizeof(buffer))
1143 return false;
1145 start += 4;
1147 const char * tne = "|TNE";
1148 auto * end = std::search(buffer, buffer + sizeof(buffer), tne, tne + 4);
1149 if (end >= buffer + sizeof(buffer) || end <= start)
1150 return false;
1152 uint8_t len = end - start;
1153 if (len > RADIO_TOOL_NAME_MAXLEN)
1154 return false;
1156 strncpy(toolName, start, len);
1157 memclear(toolName + len, RADIO_TOOL_NAME_MAXLEN + 1 - len);
1159 return true;
1162 bool isRadioScriptTool(const char * filename)
1164 const char * ext = getFileExtension(filename);
1165 return ext && !strcasecmp(ext, SCRIPT_EXT);