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. */
27 #include "bin_allocator.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;
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;
48 uint8_t instructionsPercent
= 0;
49 char lua_warning_info
[LUA_WARNING_INFO_LEN
+1];
50 struct our_longjmp
* global_lj
= 0;
52 uint32_t luaExtraMemoryUsage
= 0;
55 #if defined(LUA_ALLOCATOR_TRACER)
57 LuaMemTracer lsScriptsTrace
;
60 extern LuaMemTracer lsWidgetsTrace
;
61 #define GET_TRACER(L) (L == lsScripts) ? &lsScriptsTrace : &lsWidgetsTrace
63 #define GET_TRACER(L) &lsScriptsTrace
66 void *tracer_alloc(void * ud
, void * ptr
, size_t osize
, size_t nsize
)
68 LuaMemTracer
* tracer
= (LuaMemTracer
*)ud
;
71 // TRACE("Lua alloc %u", nsize - osize);
72 tracer
->alloc
+= nsize
- osize
;
75 // TRACE("Lua free %u", osize - nsize);
76 tracer
->free
+= osize
- nsize
;
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));
93 longjmp(global_lj
->b
, 1);
94 /* will never return */
99 void luaHook(lua_State
* L
, lua_Debug
*ar
)
101 if (ar
->event
== LUA_HOOKCOUNT
) {
102 instructionsPercent
++;
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) {
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");
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
;
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
);
146 lua_sethook(L
, luaHook
, LUA_MASKCOUNT
, count
);
150 int luaGetInputs(lua_State
* L
, ScriptInputsOutputs
& sid
)
152 if (!lua_istable(L
, -1))
155 memclear(sid
.inputs
, sizeof(sid
.inputs
));
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
) {
163 ScriptInput
* si
= &sid
.inputs
[sid
.inputsCount
];
164 for (lua_pushnil(L
); lua_next(L
, -2) && field
<5; lua_pop(L
, 1), field
++) {
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);
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
) {
178 if (si
->type
== INPUT_TYPE_VALUE
) {
183 si
->max
= MIXSRC_LAST_TELEM
;
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);
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);
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);
216 int luaGetOutputs(lua_State
* L
, ScriptInputsOutputs
& sid
)
218 if (!lua_istable(L
, -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);
235 POPUP_WARNING("Lua disabled!");
236 luaState
= INTERPRETER_PANIC
;
239 void luaClose(lua_State
** L
)
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");
252 #endif // #if defined(LUA_ALLOCATOR_TRACER)
255 // we can only disable Lua for the rest of the session
256 if (*L
== lsScripts
) luaDisable();
263 void luaRegisterLibraries(lua_State
* L
)
266 #if defined(COLORLCD)
267 registerBitmapClass(L
);
271 #define GC_REPORT_TRESHOLD (2*1024)
273 void luaDoGc(lua_State
* L
, bool full
)
278 lua_gc(L
, LUA_GCCOLLECT
, 0);
281 lua_gc(L
, LUA_GCSTEP
, 10);
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
) {
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
) {
298 TRACE("GC Use Widgets: %u bytes + Extra %u", gc
, luaExtraMemoryUsage
);
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;
315 void luaFree(lua_State
* L
, ScriptInternalData
& sid
)
319 luaL_unref(L
, LUA_REGISTRYINDEX
, sid
.run
);
322 if (sid
.background
) {
323 luaL_unref(L
, LUA_REGISTRYINDEX
, sid
.background
);
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
)
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)
357 static void luaDumpState(lua_State
* L
, const char * filename
, const FILINFO
* finfo
, int stripDebug
)
360 if (f_open(&D
, filename
, FA_WRITE
| FA_CREATE_ALWAYS
) == FR_OK
) {
362 luaU_dump(L
, getproto(L
->top
- 1), luaDumpWriter
, &D
, stripDebug
);
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
);
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).
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").
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
) {
407 } else if (filename
== nullptr) {
408 return SCRIPT_NOFILE
;
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)
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
);
437 if (fnamelen
> sizeof(filenameFull
) - sizeof(SCRIPT_BIN_EXT
)) {
438 TRACE_ERROR("luaLoadScriptFileToState(%s, %s): Error loading script: filename buffer overflow.\n", filename
, lmode
);
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
455 scriptNeedsCompile
= true;
457 else if (frLuaC
== FR_OK
&& frLuaS
!= FR_OK
) {
458 // only binary version exists
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
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
;
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")) {
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));
528 if (lstatus
== LUA_OK
) {
533 TRACE_ERROR("luaLoadScriptFileToState(%s, %s): Error loading script: %s\n", filename
, lmode
, lua_tostring(L
, -1));
534 if (lstatus
== LUA_ERRFILE
) {
537 else if (lstatus
== LUA_ERRSYNTAX
) {
538 ret
= SCRIPT_SYNTAX_ERROR
;
540 else { // LUA_ERRMEM or LUA_ERRGCMM
548 static int luaLoad(lua_State
* L
, const char * filename
, ScriptInternalData
& sid
, ScriptInputsOutputs
* sio
=nullptr)
553 sid
.instructions
= 0;
554 sid
.state
= SCRIPT_OK
;
556 if (luaState
== INTERPRETER_PANIC
) {
560 luaSetInstructionsLimit(L
, MANUAL_SCRIPTS_MAX_INSTRUCTIONS
);
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
);
571 else if (!strcmp(key
, "run")) {
572 sid
.run
= luaL_ref(L
, LUA_REGISTRYINDEX
);
575 else if (!strcmp(key
, "background")) {
576 sid
.background
= luaL_ref(L
, LUA_REGISTRYINDEX
);
579 else if (sio
&& !strcmp(key
, "input")) {
580 luaGetInputs(L
, *sio
);
582 else if (sio
&& !strcmp(key
, "output")) {
583 luaGetOutputs(L
, *sio
);
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
;
608 if (sid
.state
!= SCRIPT_OK
) {
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
) {
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
];
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
) {
662 POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS
);
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
) {
690 POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS
);
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
) {
707 return SCRIPT_NOFILE
;
710 void luaLoadPermanentScripts()
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
)) {
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
)) {
730 #if defined(PCBTARANIS)
731 // Load custom telemetry scripts
732 for (int i
=0; i
<MAX_TELEMETRY_SCREENS
; i
++) {
733 if (!luaLoadTelemetryScript(i
)) {
740 void displayLuaError(const char * title
)
742 #if !defined(COLORLCD)
743 drawMessageBox(title
);
745 if (lua_warning_info
[0]) {
746 char * split
= strstr(lua_warning_info
, ": ");
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
);
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
;
771 case SCRIPT_SYNTAX_ERROR
:
772 errorTitle
= STR_SCRIPT_SYNTAX_ERROR
;
775 errorTitle
= STR_SCRIPT_KILLED
;
778 errorTitle
= STR_SCRIPT_PANIC
;
781 errorTitle
= STR_SCRIPT_ERROR
;
785 const char * msg
= lua_tostring(L
, -1);
788 if (!strncmp(msg
, ".", 2)) msg
+= 1;
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';
795 lua_warning_info
[0] = '\0';
799 warningText
= errorTitle
;
800 warningType
= WARNING_TYPE_INFO
;
801 popupFunc
= displayAcknowledgeLuaError
;
804 displayLuaError(errorTitle
);
808 void luaExec(const char * filename
)
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
;
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';
848 TRACE("Script run function returned unexpected value");
849 standaloneScript
.state
= SCRIPT_SYNTAX_ERROR
;
850 luaState
= INTERPRETER_RELOAD_PERMANENT_SCRIPTS
;
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
;
862 else if (luaDisplayStatistics
) {
863 #if defined(COLORLCD)
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
);
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");
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
)) {
896 luaDisplayStatistics
= !luaDisplayStatistics
;
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
);
914 #if defined(SIMU) || defined(DEBUG)
915 const char *filename
;
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
)) {
921 if ((scriptType
& RUN_MIX_SCRIPT
) && (sid
.reference
<= SCRIPT_MIX_LAST
)) {
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)
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
);
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
;
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
);
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
;
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
);
960 else if ((scriptType
& RUN_TELEM_BG_SCRIPT
) && (sid
.background
)) {
961 lua_rawgeti(lsScripts
, LUA_REGISTRYINDEX
, sid
.background
);
971 if (lua_pcall(lsScripts
, inputsCount
, sio
? sio
->outputsCount
: 0, 0) == 0) {
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
);
979 sio
->outputs
[j
].value
= lua_tointeger(lsScripts
, -1);
980 lua_pop(lsScripts
, 1);
985 if (instructionsPercent
> 100) {
986 TRACE("Script %8s killed", filename
);
987 sid
.state
= SCRIPT_KILLED
;
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
);
999 if (instructionsPercent
> sid
.instructions
) {
1000 sid
.instructions
= instructionsPercent
;
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;
1017 luaDoOneRunStandalone(evt
);
1018 scriptWasRun
= true;
1027 // run permanent scripts
1028 if (luaState
& INTERPRETER_RELOAD_PERMANENT_SCRIPTS
) {
1031 if (luaState
== INTERPRETER_PANIC
) return false;
1032 luaLoadPermanentScripts();
1033 if (luaState
== INTERPRETER_PANIC
) return false;
1036 for (int i
=0; i
<luaScriptsCount
; i
++) {
1038 scriptWasRun
|= luaDoOneRunPermanentScript(evt
, i
, scriptType
);
1045 //todo gc step between scripts
1048 luaDoGc(lsScripts
, false);
1049 #if defined(COLORLCD)
1050 luaDoGc(lsWidgets
, false);
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
;
1063 if (totalMemUsed
> LUA_MEM_MAX
) {
1064 TRACE("checkLuaMemoryUsage(): max limit reached (%u), killing Lua", totalMemUsed
);
1065 // disable Lua scripts
1066 luaClose(&lsScripts
);
1068 #if defined(COLORLCD)
1070 luaClose(&lsWidgets
);
1076 uint32_t luaGetMemUsed(lua_State
* L
)
1078 return L
? (lua_gc(L
, LUA_GCCOUNT
, 0) << 10) + lua_gc(L
, LUA_GCCOUNTB
, 0) : 0;
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
1095 lsScripts
= lua_newstate(l_alloc
, nullptr); //we use Lua default allocator
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);
1105 // protect libs and constants registration
1107 luaRegisterLibraries(lsScripts
);
1110 // if we got panic during registration
1111 // we disable Lua for this session
1115 TRACE("lsScripts %p", lsScripts
);
1118 /* log error and return */
1124 bool readToolName(char * toolName
, const char * filename
)
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
);
1140 const char * tns
= "TNS|";
1141 auto * start
= std::search(buffer
, buffer
+ sizeof(buffer
), tns
, tns
+ 4);
1142 if (start
>= buffer
+ sizeof(buffer
))
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
)
1152 uint8_t len
= end
- start
;
1153 if (len
> RADIO_TOOL_NAME_MAXLEN
)
1156 strncpy(toolName
, start
, len
);
1157 memclear(toolName
+ len
, RADIO_TOOL_NAME_MAXLEN
+ 1 - len
);
1162 bool isRadioScriptTool(const char * filename
)
1164 const char * ext
= getFileExtension(filename
);
1165 return ext
&& !strcasecmp(ext
, SCRIPT_EXT
);