/* * Authors (alphabetical order) * - Andre Bernet * - Andreas Weitl * - Bertrand Songis * - Bryan J. Rentoul (Gruvin) * - Cameron Weeks * - Erez Raviv * - Gabriel Birkus * - Jean-Pierre Parisy * - Karl Szmutny * - Michael Blandford * - Michal Hlavinka * - Pat Mackenzie * - Philip Moss * - Rob Thomson * - Romolo Manfredini * - Thomas Husterer * * opentx is based on code named * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, * er9x by Erez Raviv: http://code.google.com/p/er9x/, * and the original (and ongoing) project by * Thomas Husterer, th9x: http://code.google.com/p/th9x/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include "opentx.h" #include "bin_allocator.h" #include "lua/lua_api.h" #if defined(LUA_COMPILER) && defined(SIMU) #include #include #endif #define PERMANENT_SCRIPTS_MAX_INSTRUCTIONS (10000/100) #define MANUAL_SCRIPTS_MAX_INSTRUCTIONS (20000/100) #define SET_LUA_INSTRUCTIONS_COUNT(x) (instructionsPercent=0, lua_sethook(L, hook, LUA_MASKCOUNT, x)) #define LUA_WARNING_INFO_LEN 64 lua_State *L = NULL; uint8_t luaState = 0; uint8_t luaScriptsCount = 0; ScriptInternalData scriptInternalData[MAX_SCRIPTS] = { { SCRIPT_NOFILE, 0 } }; ScriptInputsOutputs scriptInputsOutputs[MAX_SCRIPTS] = { {0} }; ScriptInternalData standaloneScript = { SCRIPT_NOFILE, 0 }; uint16_t maxLuaInterval = 0; uint16_t maxLuaDuration = 0; bool luaLcdAllowed; int instructionsPercent = 0; char lua_warning_info[LUA_WARNING_INFO_LEN+1]; struct our_longjmp * global_lj = 0; /* custom panic handler */ static int custom_lua_atpanic(lua_State *lua) { TRACE("PANIC: unprotected error in call to Lua API (%s)\n", lua_tostring(L, -1)); if (global_lj) { longjmp(global_lj->b, 1); /* will never return */ } return 0; } void hook(lua_State* L, lua_Debug *ar) { instructionsPercent++; if (instructionsPercent > 100) { // From now on, as soon as a line is executed, error // keep erroring until you're script reaches the top lua_sethook(L, hook, LUA_MASKLINE, 0); luaL_error(L, ""); } } int luaGetInputs(ScriptInputsOutputs & sid) { if (!lua_istable(L, -1)) return -1; sid.inputsCount = 0; for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { luaL_checktype(L, -2, LUA_TNUMBER); // key is number luaL_checktype(L, -1, LUA_TTABLE); // value is table if (sid.inputsCounttop - 1), luaDumpWriter, &D, 1); lua_unlock(L); TRACE("Saved Lua bytecode to file %s", bytecodeName); } } UNPROTECT_LUA(); f_close(&D); } #endif int luaLoad(const char *filename, ScriptInternalData & sid, ScriptInputsOutputs * sio=NULL) { int init = 0; sid.instructions = 0; sid.state = SCRIPT_OK; #if 0 // not needed, we just called luaInit luaFree(sid); #endif if (luaState == INTERPRETER_PANIC) { return SCRIPT_PANIC; } #if defined(LUA_COMPILER) && defined(SIMU) luaCompileAndSave(filename); #endif SET_LUA_INSTRUCTIONS_COUNT(MANUAL_SCRIPTS_MAX_INSTRUCTIONS); PROTECT_LUA() { if (luaL_loadfile(L, filename) == 0 && lua_pcall(L, 0, 1, 0) == 0 && lua_istable(L, -1)) { luaL_checktype(L, -1, LUA_TTABLE); for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { const char *key = lua_tostring(L, -2); if (!strcmp(key, "init")) { init = luaL_ref(L, LUA_REGISTRYINDEX); lua_pushnil(L); } else if (!strcmp(key, "run")) { sid.run = luaL_ref(L, LUA_REGISTRYINDEX); lua_pushnil(L); } else if (!strcmp(key, "background")) { sid.background = luaL_ref(L, LUA_REGISTRYINDEX); lua_pushnil(L); } else if (sio && !strcmp(key, "input")) { luaGetInputs(*sio); } else if (sio && !strcmp(key, "output")) { luaGetOutputs(*sio); } } if (init) { lua_rawgeti(L, LUA_REGISTRYINDEX, init); if (lua_pcall(L, 0, 0, 0) != 0) { TRACE("Error in script %s init: %s", filename, lua_tostring(L, -1)); sid.state = SCRIPT_SYNTAX_ERROR; } luaL_unref(L, LUA_REGISTRYINDEX, init); lua_gc(L, LUA_GCCOLLECT, 0); } } else { TRACE("Error in script %s: %s", filename, lua_tostring(L, -1)); sid.state = SCRIPT_SYNTAX_ERROR; } } else { luaDisable(); return SCRIPT_PANIC; } UNPROTECT_LUA(); if (sid.state != SCRIPT_OK) { luaFree(sid); } return sid.state; } bool luaLoadMixScript(uint8_t index) { ScriptData & sd = g_model.scriptsData[index]; if (ZEXIST(sd.file)) { ScriptInternalData & sid = scriptInternalData[luaScriptsCount++]; ScriptInputsOutputs * sio = &scriptInputsOutputs[index]; sid.reference = SCRIPT_MIX_FIRST+index; sid.state = SCRIPT_NOFILE; char filename[sizeof(SCRIPTS_MIXES_PATH)+sizeof(sd.file)+sizeof(SCRIPTS_EXT)] = SCRIPTS_MIXES_PATH "/"; strncpy(filename+sizeof(SCRIPTS_MIXES_PATH), sd.file, sizeof(sd.file)); filename[sizeof(SCRIPTS_MIXES_PATH)+sizeof(sd.file)] = '\0'; strcat(filename+sizeof(SCRIPTS_MIXES_PATH), SCRIPTS_EXT); if (luaLoad(filename, sid, sio) == SCRIPT_PANIC) { return false; } } return true; } bool luaLoadFunctionScript(uint8_t index) { CustomFunctionData & fn = g_model.customFn[index]; if (fn.func == FUNC_PLAY_SCRIPT && ZEXIST(fn.play.name)) { if (luaScriptsCount < MAX_SCRIPTS) { ScriptInternalData & sid = scriptInternalData[luaScriptsCount++]; sid.reference = SCRIPT_FUNC_FIRST+index; sid.state = SCRIPT_NOFILE; char filename[sizeof(SCRIPTS_FUNCS_PATH)+sizeof(fn.play.name)+sizeof(SCRIPTS_EXT)] = SCRIPTS_FUNCS_PATH "/"; strncpy(filename+sizeof(SCRIPTS_FUNCS_PATH), fn.play.name, sizeof(fn.play.name)); filename[sizeof(SCRIPTS_FUNCS_PATH)+sizeof(fn.play.name)] = '\0'; strcat(filename+sizeof(SCRIPTS_FUNCS_PATH), SCRIPTS_EXT); if (luaLoad(filename, sid) == SCRIPT_PANIC) { return false; } } else { POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS); return false; } } return true; } bool luaLoadTelemetryScript(uint8_t index) { TelemetryScreenType screenType = TELEMETRY_SCREEN_TYPE(index); if (screenType == TELEMETRY_SCREEN_TYPE_SCRIPT) { TelemetryScriptData & script = g_model.frsky.screens[index].script; if (ZEXIST(script.file)) { if (luaScriptsCount < MAX_SCRIPTS) { ScriptInternalData & sid = scriptInternalData[luaScriptsCount++]; sid.reference = SCRIPT_TELEMETRY_FIRST+index; sid.state = SCRIPT_NOFILE; char filename[sizeof(SCRIPTS_TELEM_PATH)+sizeof(script.file)+sizeof(SCRIPTS_EXT)] = SCRIPTS_TELEM_PATH "/"; strncpy(filename+sizeof(SCRIPTS_TELEM_PATH), script.file, sizeof(script.file)); filename[sizeof(SCRIPTS_TELEM_PATH)+sizeof(script.file)] = '\0'; strcat(filename+sizeof(SCRIPTS_TELEM_PATH), SCRIPTS_EXT); if (luaLoad(filename, sid) == SCRIPT_PANIC) { return false; } } else { POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS); return false; } } } return true; } uint8_t isTelemetryScriptAvailable(uint8_t index) { for (int i=0; i 100) { TRACE("Script killed"); standaloneScript.state = SCRIPT_KILLED; luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; } else if (lua_isstring(L, -1)) { char nextScript[_MAX_LFN+1]; strncpy(nextScript, lua_tostring(L, -1), _MAX_LFN); nextScript[_MAX_LFN] = '\0'; luaExec(nextScript); } else { TRACE("Script run function returned unexpected value"); standaloneScript.state = SCRIPT_SYNTAX_ERROR; luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; } } else { int scriptResult = lua_tointeger(L, -1); lua_pop(L, 1); /* pop returned value */ if (scriptResult != 0) { TRACE("Script finished with status %d", scriptResult); standaloneScript.state = SCRIPT_NOFILE; luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; return; } else if (luaDisplayStatistics) { #if defined(COLORLCD) #else lcdDrawSolidHorizontalLine(0, 7*FH-1, lcdLastPos+6, ERASE); lcd_puts(0, 7*FH, "GV Use: "); lcd_outdezAtt(lcdLastPos, 7*FH, luaGetMemUsed(), LEFT); lcd_putc(lcdLastPos, 7*FH, 'b'); lcdDrawSolidHorizontalLine(0, 7*FH-2, lcdLastPos+6, FORCE); lcdDrawVerticalLine(lcdLastPos+6, 7*FH-2, FH+2, SOLID, FORCE); #endif } } } else { TRACE("Script error: %s", lua_tostring(L, -1)); standaloneScript.state = (instructionsPercent > 100 ? SCRIPT_KILLED : SCRIPT_SYNTAX_ERROR); luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; } if (standaloneScript.state != SCRIPT_OK) { luaError(standaloneScript.state); luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; } if (evt == EVT_KEY_LONG(KEY_EXIT)) { TRACE("Script force exit"); killEvents(evt); standaloneScript.state = SCRIPT_NOFILE; luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; } else if (evt == EVT_KEY_LONG(KEY_MENU)) { killEvents(evt); luaDisplayStatistics = !luaDisplayStatistics; } } } bool luaDoOneRunPermanentScript(uint8_t evt, int i, uint32_t scriptType) { ScriptInternalData & sid = scriptInternalData[i]; if (sid.state != SCRIPT_OK) return false; SET_LUA_INSTRUCTIONS_COUNT(PERMANENT_SCRIPTS_MAX_INSTRUCTIONS); int inputsCount = 0; #if defined(SIMU) || defined(DEBUG) const char *filename; #endif ScriptInputsOutputs * sio = NULL; #if SCRIPT_MIX_FIRST > 0 if ((scriptType & RUN_MIX_SCRIPT) && (sid.reference >= SCRIPT_MIX_FIRST && sid.reference <= SCRIPT_MIX_LAST)) { #else if ((scriptType & RUN_MIX_SCRIPT) && (sid.reference <= SCRIPT_MIX_LAST)) { #endif ScriptData & sd = g_model.scriptsData[sid.reference-SCRIPT_MIX_FIRST]; sio = &scriptInputsOutputs[sid.reference-SCRIPT_MIX_FIRST]; inputsCount = sio->inputsCount; #if defined(SIMU) || defined(DEBUG) filename = sd.file; #endif lua_rawgeti(L, LUA_REGISTRYINDEX, sid.run); for (int j=0; jinputsCount; j++) { if (sio->inputs[j].type == 1) luaGetValueAndPush((uint8_t)sd.inputs[j]); else lua_pushinteger(L, sd.inputs[j] + sio->inputs[j].def); } } else if ((scriptType & RUN_FUNC_SCRIPT) && (sid.reference >= SCRIPT_FUNC_FIRST && sid.reference <= SCRIPT_FUNC_LAST)) { CustomFunctionData & fn = g_model.customFn[sid.reference-SCRIPT_FUNC_FIRST]; if (!getSwitch(fn.swtch)) return false; #if defined(SIMU) || defined(DEBUG) filename = fn.play.name; #endif lua_rawgeti(L, LUA_REGISTRYINDEX, sid.run); } else { #if defined(SIMU) || defined(DEBUG) TelemetryScriptData & script = g_model.frsky.screens[sid.reference-SCRIPT_TELEMETRY_FIRST].script; filename = script.file; #endif if ((scriptType & RUN_TELEM_FG_SCRIPT) && #if defined(PCBFLAMENCO) (g_menuStack[0]==menuMainView && sid.reference==SCRIPT_TELEMETRY_FIRST+g_eeGeneral.view-VIEW_TELEM1)) { #else (g_menuStack[0]==menuTelemetryFrsky && sid.reference==SCRIPT_TELEMETRY_FIRST+s_frsky_view)) { #endif lua_rawgeti(L, LUA_REGISTRYINDEX, sid.run); lua_pushinteger(L, evt); inputsCount = 1; } else if ((scriptType & RUN_TELEM_BG_SCRIPT) && (sid.background)) { lua_rawgeti(L, LUA_REGISTRYINDEX, sid.background); } else { return false; } } if (lua_pcall(L, inputsCount, sio ? sio->outputsCount : 0, 0) == 0) { if (sio) { for (int j=sio->outputsCount-1; j>=0; j--) { if (!lua_isnumber(L, -1)) { sid.state = (instructionsPercent > 100 ? SCRIPT_KILLED : SCRIPT_SYNTAX_ERROR); TRACE("Script %8s disabled", filename); break; } sio->outputs[j].value = lua_tointeger(L, -1); lua_pop(L, 1); } } } else { if (instructionsPercent > 100) { TRACE("Script %8s killed", filename); sid.state = SCRIPT_KILLED; } else { TRACE("Script %8s error: %s", filename, lua_tostring(L, -1)); sid.state = SCRIPT_SYNTAX_ERROR; } } if (sid.state != SCRIPT_OK) { luaFree(sid); } else { if (instructionsPercent > sid.instructions) { sid.instructions = instructionsPercent; } } return true; } void luaDoGc() { if (L) { PROTECT_LUA() { lua_gc(L, LUA_GCCOLLECT, 0); #if defined(SIMU) || defined(DEBUG) static int lastgc = 0; int gc = luaGetMemUsed(); if (gc != lastgc) { lastgc = gc; TRACE("GC Use: %dbytes", gc); } #endif } else { // we disable Lua for the rest of the session luaDisable(); } UNPROTECT_LUA(); } } bool luaTask(uint8_t evt, uint8_t scriptType, bool allowLcdUsage) { if (luaState == INTERPRETER_PANIC) return false; luaLcdAllowed = allowLcdUsage; bool scriptWasRun = false; // we run either standalone script or permanent scripts if (luaState & INTERPRETER_RUNNING_STANDALONE_SCRIPT) { // run standalone script if ((scriptType & RUN_STNDAL_SCRIPT) == 0) return false; PROTECT_LUA() { luaDoOneRunStandalone(evt); scriptWasRun = true; } else { luaDisable(); return false; } UNPROTECT_LUA(); } else { // run permanent scripts if (luaState & INTERPRETER_RELOAD_PERMANENT_SCRIPTS) { luaState = 0; luaInit(); if (luaState == INTERPRETER_PANIC) return false; luaLoadPermanentScripts(); if (luaState == INTERPRETER_PANIC) return false; } for (int i=0; i