diff --git a/radio/src/Makefile b/radio/src/Makefile index a0737e9ae..0a5c7bb47 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -1024,7 +1024,7 @@ ifeq ($(PCB), TARANIS) endif CPPDEFS += -DLUA INCDIRS += $(LUADIR) - CPPSRC += lua_api.cpp + CPPSRC += lua/interface.cpp lua/api_general.cpp lua/api_lcd.cpp lua/api_model.cpp LUASRC = $(LUADIR)/lapi.c $(LUADIR)/lcode.c $(LUADIR)/lctype.c $(LUADIR)/ldebug.c $(LUADIR)/ldo.c $(LUADIR)/ldump.c $(LUADIR)/lfunc.c $(LUADIR)/lgc.c $(LUADIR)/llex.c $(LUADIR)/lmem.c \ $(LUADIR)/lobject.c $(LUADIR)/lopcodes.c $(LUADIR)/lparser.c $(LUADIR)/lstate.c $(LUADIR)/lstring.c $(LUADIR)/ltable.c $(LUADIR)/lrotable.c $(LUADIR)/ltm.c $(LUADIR)/lundump.c $(LUADIR)/lvm.c $(LUADIR)/lzio.c \ $(LUADIR)/lbaselib.c $(LUADIR)/linit.c $(LUADIR)/lmathlib.c $(LUADIR)/lbitlib.c $(LUADIR)/loadlib.c $(LUADIR)/lauxlib.c $(LUADIR)/ltablib.c $(LUADIR)/lcorolib.c $(LUADIR)/liolib.c diff --git a/radio/src/lua/api_general.cpp b/radio/src/lua/api_general.cpp new file mode 100644 index 000000000..bcc6d1fc0 --- /dev/null +++ b/radio/src/lua/api_general.cpp @@ -0,0 +1,743 @@ +/* + * 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 "stamp-opentx.h" +#include "lua/lua_api.h" + +#include "lua_exports.inc" // this line must be after lua headers + +#if defined(PCBTARANIS) && defined(REV9E) + #define RADIO "taranisx9e" +#elif defined(PCBTARANIS) && defined(REVPLUS) + #define RADIO "taranisplus" +#elif defined(PCBTARANIS) + #define RADIO "taranis" +#else +#error "Unknown board" +#endif + +#if defined(SIMU) + #define RADIO_VERSION RADIO"-simu" +#else + #define RADIO_VERSION RADIO +#endif + +#define FIND_FIELD_DESC 0x01 + +struct LuaField { + uint16_t id; + char desc[50]; +}; + +/*luadoc +@function getVersion() + +Returns OpenTX version + +@retval string OpenTX version (ie "2.1.5") + +@retval list (available since OpenTX 2.1.7) returns two values: + * `string` OpenTX version (ie "2.1.5") + * `string` radio version: `taranisx9e`, `taranisplus` or `taranis`. +If running in simulator the "-simu" is added + +@status current Introduced in 2.0.0, expanded in 2.1.7 + +### Example + +This example also runs in OpenTX versions where the radio version was not available: + +```lua +local function run(event) + local ver, radio = getVersion() + print("version: "..ver) + if radio then print ("radio: "..radio) end + return 1 +end + +return { run=run } +``` +Output of above script in simulator: +``` +version: 2.1.7 +radio: taranis-simu +Script finished with status 1 +``` +*/ +static int luaGetVersion(lua_State *L) +{ + lua_pushstring(L, VERS_STR); + lua_pushstring(L, RADIO_VERSION); + return 2; +} + +/*luadoc +@function getTime() + +Returns the time since the radio was started in multiple of 10ms + +@retval number Number of 10ms ticks since the radio was started Example: +run time: 12.54 seconds, return value: 1254 + +@status current Introduced in 2.0.0 +*/ +static int luaGetTime(lua_State *L) +{ + lua_pushunsigned(L, get_tmr10ms()); + return 1; +} + +static void luaPushDateTime(lua_State *L, uint32_t year, uint32_t mon, uint32_t day, + uint32_t hour, uint32_t min, uint32_t sec) +{ + lua_createtable(L, 0, 6); + lua_pushtableinteger(L, "year", year); + lua_pushtableinteger(L, "mon", mon); + lua_pushtableinteger(L, "day", day); + lua_pushtableinteger(L, "hour", hour); + lua_pushtableinteger(L, "min", min); + lua_pushtableinteger(L, "sec", sec); +} + +/*luadoc +@function getDateTime() + +Returns current system date and time that is kept by the RTC unit + +@retval table current date and time, table elements: + * `year` year + * `mon` month + * `day` day of month + * `hour` hours + * `min` minutes + * `sec` seconds +*/ +static int luaGetDateTime(lua_State *L) +{ + struct gtm utm; + gettime(&utm); + luaPushDateTime(L, utm.tm_year + 1900, utm.tm_mon + 1, utm.tm_mday, utm.tm_hour, utm.tm_min, utm.tm_sec); + return 1; +} + +static void luaPushLatLon(TelemetrySensor & telemetrySensor, TelemetryItem & telemetryItem) +/* result is lua table containing members ["lat"] and ["lon"] as lua_Number (doubles) in decimal degrees */ +{ + lua_Number lat = 0.0; + lua_Number lon = 0.0; + uint32_t gpsLat = 0; + uint32_t gpsLon = 0; + + telemetryItem.gps.extractLatitudeLongitude(&gpsLat, &gpsLon); /* close, but not the format we want */ + lat = gpsLat / 1000000.0; + if (telemetryItem.gps.latitudeNS == 'S') lat = -lat; + lon = gpsLon / 1000000.0; + if (telemetryItem.gps.longitudeEW == 'W') lon = -lon; + + lua_createtable(L, 0, 2); + lua_pushtablenumber(L, "lat", lat); + lua_pushtablenumber(L, "lon", lon); +} + +static void luaPushTelemetryDateTime(TelemetrySensor & telemetrySensor, TelemetryItem & telemetryItem) +{ + luaPushDateTime(L, telemetryItem.datetime.year + 2000, telemetryItem.datetime.month, telemetryItem.datetime.day, + telemetryItem.datetime.hour, telemetryItem.datetime.min, telemetryItem.datetime.sec); +} + +static void luaPushCells(TelemetrySensor & telemetrySensor, TelemetryItem & telemetryItem) +{ + if (telemetryItem.cells.count == 0) + lua_pushinteger(L, (int)0); // returns zero if no cells + else { + lua_createtable(L, telemetryItem.cells.count, 0); + for (int i = 0; i < telemetryItem.cells.count; i++) { + lua_pushnumber(L, i + 1); + lua_pushnumber(L, telemetryItem.cells.values[i].value / 100.0); + lua_settable(L, -3); + } + } +} + +void luaGetValueAndPush(int src) +{ + getvalue_t value = getValue(src); // ignored for GPS, DATETIME, and CELLS + + if (src >= MIXSRC_FIRST_TELEM && src <= MIXSRC_LAST_TELEM) { + src = (src-MIXSRC_FIRST_TELEM) / 3; + // telemetry values + if (TELEMETRY_STREAMING() && telemetryItems[src].isAvailable()) { + TelemetrySensor & telemetrySensor = g_model.telemetrySensors[src]; + switch (telemetrySensor.unit) { + case UNIT_GPS: + luaPushLatLon(telemetrySensor, telemetryItems[src]); + break; + case UNIT_DATETIME: + luaPushTelemetryDateTime(telemetrySensor, telemetryItems[src]); + break; + case UNIT_CELLS: + luaPushCells(telemetrySensor, telemetryItems[src]); + break; + default: + if (telemetrySensor.prec > 0) + lua_pushnumber(L, float(value)/telemetrySensor.getPrecDivisor()); + else + lua_pushinteger(L, value); + break; + } + } + else { + // telemetry not working, return zero for telemetry sources + lua_pushinteger(L, (int)0); + } + } + else if (src == MIXSRC_TX_VOLTAGE) { + lua_pushnumber(L, float(value)/10.0); + } + else { + lua_pushinteger(L, value); + } +} + +/** + Return field data for a given field name +*/ +bool luaFindFieldByName(const char * name, LuaField & field, unsigned int flags=0) +{ + // TODO better search method (binary lookup) + for (unsigned int n=0; n + * - 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 "lua/lua_api.h" + +/*luadoc +@function lcd.lock() + +@notice This function has no effect in OpenTX 2.1 +*/ +static int luaLcdLock(lua_State *L) +{ + // disabled in opentx 2.1 + // TODO: remove this function completely in opentx 2.2 + return 0; +} + +/*luadoc +@function lcd.clear() + +Erases all contents of LCD. + +@notice This function only works in stand-alone and telemetry scripts. +*/ +static int luaLcdClear(lua_State *L) +{ + if (luaLcdAllowed) lcd_clear(); + return 0; +} + +/*luadoc +@function lcd.drawPoint(x, y) + +Draws a single pixel on LCD + +@param x (positive number) x position, starts from 0 in top left corner. + +@param y (positive number) y position, starts from 0 in top left corner and goes down. + +@notice Drawing on an existing black pixel produces white pixel (TODO check this!) +*/ +static int luaLcdDrawPoint(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + lcd_plot(x, y); + return 0; +} + +/*luadoc +@function lcd.drawLine(x1, y1, x2, y2, pattern, flags) + +Draws a straight line on LCD + +@param x1,y1 (positive numbers) starting coordinate + +@param x2,y2 (positive numbers) end coordinate + +@param pattern TODO + +@param flags TODO + +@notice If the start or the end of the line is outside the LCD dimensions, then the +whole line will not be drawn (starting from OpenTX 2.1.5) +*/ +static int luaLcdDrawLine(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x1 = luaL_checkinteger(L, 1); + int y1 = luaL_checkinteger(L, 2); + int x2 = luaL_checkinteger(L, 3); + int y2 = luaL_checkinteger(L, 4); + int pat = luaL_checkinteger(L, 5); + int flags = luaL_checkinteger(L, 6); + lcd_line(x1, y1, x2, y2, pat, flags); + return 0; +} + +static int luaLcdGetLastPos(lua_State *L) +{ + lua_pushinteger(L, lcdLastPos); + return 1; +} + +static int luaLcdDrawText(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + const char * s = luaL_checkstring(L, 3); + unsigned int att = luaL_optunsigned(L, 4, 0); + lcd_putsAtt(x, y, s, att); + return 0; +} + +static int luaLcdDrawTimer(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int seconds = luaL_checkinteger(L, 3); + unsigned int att = luaL_optunsigned(L, 4, 0); + putsTimer(x, y, seconds, att|LEFT, att); + return 0; +} + +static int luaLcdDrawNumber(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + float val = luaL_checknumber(L, 3); + unsigned int att = luaL_optunsigned(L, 4, 0); + int n; + if ((att & PREC2) == PREC2) + n = val * 100; + else if ((att & PREC1) == PREC1) + n = val * 10; + else + n = val; + lcd_outdezAtt(x, y, n, att); + return 0; +} + +static int luaLcdDrawChannel(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int channel = -1; + if (lua_isnumber(L, 3)) { + channel = luaL_checkinteger(L, 3); + } + else { + const char * what = luaL_checkstring(L, 3); + LuaField field; + bool found = luaFindFieldByName(what, field); + if (found) { + channel = field.id; + } + } + unsigned int att = luaL_optunsigned(L, 4, 0); + getvalue_t value = getValue(channel); + putsTelemetryChannelValue(x, y, (channel-MIXSRC_FIRST_TELEM)/3, value, att); + return 0; +} + +static int luaLcdDrawSwitch(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int s = luaL_checkinteger(L, 3); + unsigned int att = luaL_optunsigned(L, 4, 0); + putsSwitches(x, y, s, att); + return 0; +} + +static int luaLcdDrawSource(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int s = luaL_checkinteger(L, 3); + unsigned int att = luaL_optunsigned(L, 4, 0); + putsMixerSource(x, y, s, att); + return 0; +} + +static int luaLcdDrawPixmap(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + const char * filename = luaL_checkstring(L, 3); + uint8_t bitmap[BITMAP_BUFFER_SIZE(LCD_W/2, LCD_H)]; // width max is LCD_W/2 pixels for saving stack and avoid a malloc here + const pm_char * error = bmpLoad(bitmap, filename, LCD_W/2, LCD_H); + if (!error) { + lcd_bmp(x, y, bitmap); + } + return 0; +} + +static int luaLcdDrawRectangle(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int w = luaL_checkinteger(L, 3); + int h = luaL_checkinteger(L, 4); + unsigned int flags = luaL_optunsigned(L, 5, 0); + lcd_rect(x, y, w, h, 0xff, flags); + return 0; +} + +static int luaLcdDrawFilledRectangle(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int w = luaL_checkinteger(L, 3); + int h = luaL_checkinteger(L, 4); + unsigned int flags = luaL_optunsigned(L, 5, 0); + drawFilledRect(x, y, w, h, SOLID, flags); + return 0; +} + +static int luaLcdDrawGauge(lua_State *L) +{ + if (!luaLcdAllowed) return 0; + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int w = luaL_checkinteger(L, 3); + int h = luaL_checkinteger(L, 4); + int num = luaL_checkinteger(L, 5); + int den = luaL_checkinteger(L, 6); + // int flags = luaL_checkinteger(L, 7); + lcd_rect(x, y, w, h); + uint8_t len = limit((uint8_t)1, uint8_t(w*num/den), uint8_t(w)); + for (int i=1; i= count) { + // TODO error + } + if (flags & BLINK) { + drawFilledRect(x, y, w-9, count*9+2, SOLID, ERASE); + lcd_rect(x, y, w-9, count*9+2); + for (int i=0; i + * - 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 "lua/lua_api.h" +#include "timers.h" + +static int luaModelGetInfo(lua_State *L) +{ + lua_newtable(L); + lua_pushtablezstring(L, "name", g_model.header.name); + lua_pushtablenzstring(L, "bitmap", g_model.header.bitmap); + return 1; +} + +static int luaModelSetInfo(lua_State *L) +{ + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + luaL_checktype(L, -2, LUA_TSTRING); // key is string + const char * key = luaL_checkstring(L, -2); + if (!strcmp(key, "name")) { + const char * name = luaL_checkstring(L, -1); + str2zchar(g_model.header.name, name, sizeof(g_model.header.name)); + memcpy(modelHeaders[g_eeGeneral.currModel].name, g_model.header.name, sizeof(g_model.header.name)); + } + else if (!strcmp(key, "bitmap")) { + const char * name = luaL_checkstring(L, -1); + strncpy(g_model.header.bitmap, name, sizeof(g_model.header.bitmap)); + } + } + eeDirty(EE_MODEL); + return 0; +} + +static int luaModelGetModule(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + if (idx < NUM_MODULES) { + ModuleData & module = g_model.moduleData[idx]; + lua_newtable(L); + lua_pushtableinteger(L, "rfProtocol", module.rfProtocol); + lua_pushtableinteger(L, "modelId", g_model.header.modelId[idx]); + lua_pushtableinteger(L, "firstChannel", module.channelsStart); + lua_pushtableinteger(L, "channelsCount", module.channelsCount + 8); + } + else { + lua_pushnil(L); + } + return 1; +} + +static int luaModelSetModule(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + + if (idx < NUM_MODULES) { + ModuleData & module = g_model.moduleData[idx]; + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + luaL_checktype(L, -2, LUA_TSTRING); // key is string + const char * key = luaL_checkstring(L, -2); + if (!strcmp(key, "rfProtocol")) { + module.rfProtocol = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "modelId")) { + g_model.header.modelId[idx] = modelHeaders[g_eeGeneral.currModel].modelId[idx] = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "firstChannel")) { + module.channelsStart = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "channelsCount")) { + module.channelsCount = luaL_checkinteger(L, -1) - 8; + } + } + eeDirty(EE_MODEL); + } + return 0; +} + +static int luaModelGetTimer(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + if (idx < MAX_TIMERS) { + TimerData & timer = g_model.timers[idx]; + lua_newtable(L); + lua_pushtableinteger(L, "mode", timer.mode); + lua_pushtableinteger(L, "start", timer.start); + lua_pushtableinteger(L, "value", timersStates[idx].val); + lua_pushtableinteger(L, "countdownBeep", timer.countdownBeep); + lua_pushtableboolean(L, "minuteBeep", timer.minuteBeep); + lua_pushtableinteger(L, "persistent", timer.persistent); + } + else { + lua_pushnil(L); + } + return 1; +} + +static int luaModelSetTimer(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + + if (idx < MAX_TIMERS) { + TimerData & timer = g_model.timers[idx]; + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + luaL_checktype(L, -2, LUA_TSTRING); // key is string + const char * key = luaL_checkstring(L, -2); + if (!strcmp(key, "mode")) { + timer.mode = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "start")) { + timer.start = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "value")) { + timersStates[idx].val = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "countdownBeep")) { + timer.countdownBeep = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "minuteBeep")) { + timer.minuteBeep = lua_toboolean(L, -1); + } + else if (!strcmp(key, "persistent")) { + timer.persistent = luaL_checkinteger(L, -1); + } + } + eeDirty(EE_MODEL); + } + return 0; +} + +static int luaModelResetTimer(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + if (idx < MAX_TIMERS) { + timerReset(idx); + } + return 0; +} + +static unsigned int getFirstInput(unsigned int chn) +{ + for (unsigned int i=0; isrcRaw || expo->chn >= chn) { + return i; + } + } + return 0; +} + +static unsigned int getInputsCountFromFirst(unsigned int chn, unsigned int first) +{ + unsigned int count = 0; + for (unsigned int i=first; isrcRaw || expo->chn!=chn) break; + count++; + } + return count; +} + +static unsigned int getInputsCount(unsigned int chn) +{ + return getInputsCountFromFirst(chn, getFirstInput(chn)); +} + +/*luadoc +@function model.getInputsCount(input) + +Returns number of lines for given input + +@param input (unsigned number) input number (use 0 for Input1) + +@retval number number of configured lines for given input + +@status current Introduced in 2.0.0 +*/ +static int luaModelGetInputsCount(lua_State *L) +{ + unsigned int chn = luaL_checkunsigned(L, 1); + int count = getInputsCount(chn); + lua_pushinteger(L, count); + return 1; +} + +/*luadoc +@function model.getInput(input, line) + +Returns input data for given input and line number + +@param input (unsigned number) input number (use 0 for Input1) + +@param line (unsigned number) input line (use 0 for first line) + +@retval nil requested input or line does not exist + +@retval table input data: + * `name` (string) input line name + * `source` (number) input source index + * `weight` (number) input weight + * `offset` (number) input offset + * `switch` (number) input switch index + +@status current Introduced in 2.0.0, `switch` added in TODO +*/ +static int luaModelGetInput(lua_State *L) +{ + unsigned int chn = luaL_checkunsigned(L, 1); + unsigned int idx = luaL_checkunsigned(L, 2); + unsigned int first = getFirstInput(chn); + unsigned int count = getInputsCountFromFirst(chn, first); + if (idx < count) { + ExpoData * expo = expoAddress(first+idx); + lua_newtable(L); + lua_pushtablezstring(L, "name", expo->name); + lua_pushtableinteger(L, "source", expo->srcRaw); + lua_pushtableinteger(L, "weight", expo->weight); + lua_pushtableinteger(L, "offset", expo->offset); + lua_pushtableinteger(L, "switch", expo->swtch); + } + else { + lua_pushnil(L); + } + return 1; +} + +/*luadoc +@function model.insertInput(input, line, value) + +Inserts an Input at specified line + +@param input (unsigned number) input number (use 0 for Input1) + +@param line (unsigned number) input line (use 0 for first line) + +@param value (table) input data, see model.getInput() + +@status current Introduced in 2.0.0, `switch` added in TODO +*/ +static int luaModelInsertInput(lua_State *L) +{ + unsigned int chn = luaL_checkunsigned(L, 1); + unsigned int idx = luaL_checkunsigned(L, 2); + + unsigned int first = getFirstInput(chn); + unsigned int count = getInputsCountFromFirst(chn, first); + + if (chnname, name, sizeof(expo->name)); + } + else if (!strcmp(key, "source")) { + expo->srcRaw = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "weight")) { + expo->weight = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "offset")) { + expo->offset = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "switch")) { + expo->swtch = luaL_checkinteger(L, -1); + } + } + } + + return 0; +} + +/*luadoc +@function model.deleteInput(input, line) + +Delete line from specified input + +@param input (unsigned number) input number (use 0 for Input1) + +@param line (unsigned number) input line (use 0 for first line) + +@status current Introduced in 2.0.0 +*/ +static int luaModelDeleteInput(lua_State *L) +{ + unsigned int chn = luaL_checkunsigned(L, 1); + unsigned int idx = luaL_checkunsigned(L, 2); + + int first = getFirstInput(chn); + unsigned int count = getInputsCountFromFirst(chn, first); + + if (idx < count) { + deleteExpoMix(1, first+idx); + } + + return 0; +} + +/*luadoc +@function model.deleteInputs() + +Delete all Inputs + +@status current Introduced in 2.0.0 +*/ +static int luaModelDeleteInputs(lua_State *L) +{ + clearInputs(); + return 0; +} + +/*luadoc +@function model.defaultInputs() + +Set all inputs to defaults + +@status current Introduced in 2.0.0 +*/ +static int luaModelDefaultInputs(lua_State *L) +{ + defaultInputs(); + return 0; +} + +static unsigned int getFirstMix(unsigned int chn) +{ + for (unsigned int i=0; isrcRaw || mix->destCh>=chn) { + return i; + } + } + return 0; +} + +static unsigned int getMixesCountFromFirst(unsigned int chn, unsigned int first) +{ + unsigned int count = 0; + for (unsigned int i=first; isrcRaw || mix->destCh!=chn) break; + count++; + } + return count; +} + +static unsigned int getMixesCount(unsigned int chn) +{ + return getMixesCountFromFirst(chn, getFirstMix(chn)); +} + +static int luaModelGetMixesCount(lua_State *L) +{ + unsigned int chn = luaL_checkunsigned(L, 1); + unsigned int count = getMixesCount(chn); + lua_pushinteger(L, count); + return 1; +} + +static int luaModelGetMix(lua_State *L) +{ + unsigned int chn = luaL_checkunsigned(L, 1); + unsigned int idx = luaL_checkunsigned(L, 2); + unsigned int first = getFirstMix(chn); + unsigned int count = getMixesCountFromFirst(chn, first); + if (idx < count) { + MixData * mix = mixAddress(first+idx); + lua_newtable(L); + lua_pushtablezstring(L, "name", mix->name); + lua_pushtableinteger(L, "source", mix->srcRaw); + lua_pushtableinteger(L, "weight", mix->weight); + lua_pushtableinteger(L, "offset", mix->offset); + lua_pushtableinteger(L, "switch", mix->swtch); + lua_pushtableinteger(L, "curveType", mix->curve.type); + lua_pushtableinteger(L, "curveValue", mix->curve.value); + lua_pushtableinteger(L, "multiplex", mix->mltpx); + lua_pushtableinteger(L, "flightModes", mix->flightModes); + lua_pushtableboolean(L, "carryTrim", mix->carryTrim); + lua_pushtableinteger(L, "mixWarn", mix->mixWarn); + lua_pushtableinteger(L, "delayUp", mix->delayUp); + lua_pushtableinteger(L, "delayDown", mix->delayDown); + lua_pushtableinteger(L, "speedUp", mix->speedUp); + lua_pushtableinteger(L, "speedDown", mix->speedDown); + } + else { + lua_pushnil(L); + } + return 1; +} + +static int luaModelInsertMix(lua_State *L) +{ + unsigned int chn = luaL_checkunsigned(L, 1); + unsigned int idx = luaL_checkunsigned(L, 2); + + unsigned int first = getFirstMix(chn); + unsigned int count = getMixesCountFromFirst(chn, first); + + if (chnname, name, sizeof(mix->name)); + } + else if (!strcmp(key, "source")) { + mix->srcRaw = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "weight")) { + mix->weight = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "offset")) { + mix->offset = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "switch")) { + mix->swtch = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "curveType")) { + mix->curve.type = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "curveValue")) { + mix->curve.value = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "multiplex")) { + mix->mltpx = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "flightModes")) { + mix->flightModes = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "carryTrim")) { + mix->carryTrim = lua_toboolean(L, -1); + } + else if (!strcmp(key, "mixWarn")) { + mix->mixWarn = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "delayUp")) { + mix->delayUp = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "delayDown")) { + mix->delayDown = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "speedUp")) { + mix->speedUp = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "speedDown")) { + mix->speedDown = luaL_checkinteger(L, -1); + } + } + } + + return 0; +} + +static int luaModelDeleteMix(lua_State *L) +{ + unsigned int chn = luaL_checkunsigned(L, 1); + unsigned int idx = luaL_checkunsigned(L, 2); + + unsigned int first = getFirstMix(chn); + unsigned int count = getMixesCountFromFirst(chn, first); + + if (idx < count) { + deleteExpoMix(0, first+idx); + } + + return 0; +} + +static int luaModelDeleteMixes(lua_State *L) +{ + memset(g_model.mixData, 0, sizeof(g_model.mixData)); + return 0; +} + +static int luaModelGetLogicalSwitch(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + if (idx < NUM_LOGICAL_SWITCH) { + LogicalSwitchData * sw = lswAddress(idx); + lua_newtable(L); + lua_pushtableinteger(L, "func", sw->func); + lua_pushtableinteger(L, "v1", sw->v1); + lua_pushtableinteger(L, "v2", sw->v2); + lua_pushtableinteger(L, "v3", sw->v3); + lua_pushtableinteger(L, "and", sw->andsw); + lua_pushtableinteger(L, "delay", sw->delay); + lua_pushtableinteger(L, "duration", sw->duration); + } + else { + lua_pushnil(L); + } + return 1; +} + +static int luaModelSetLogicalSwitch(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + if (idx < NUM_LOGICAL_SWITCH) { + LogicalSwitchData * sw = lswAddress(idx); + memclear(sw, sizeof(LogicalSwitchData)); + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + luaL_checktype(L, -2, LUA_TSTRING); // key is string + const char * key = luaL_checkstring(L, -2); + if (!strcmp(key, "func")) { + sw->func = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "v1")) { + sw->v1 = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "v2")) { + sw->v2 = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "v3")) { + sw->v3 = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "and")) { + sw->andsw = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "delay")) { + sw->delay = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "duration")) { + sw->duration = luaL_checkinteger(L, -1); + } + } + eeDirty(EE_MODEL); + } + + return 0; +} + +static int luaModelGetCurve(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + if (idx < MAX_CURVES) { + CurveInfo & curveInfo = g_model.curves[idx]; + lua_newtable(L); + lua_pushtablezstring(L, "name", g_model.curveNames[idx]); + lua_pushtableinteger(L, "type", curveInfo.type); + lua_pushtableboolean(L, "smooth", curveInfo.smooth); + lua_pushtableinteger(L, "points", curveInfo.points+5); + lua_pushstring(L, "y"); + lua_newtable(L); + int8_t * point = curveAddress(idx); + for (int i=0; iplay.name); + } + else { + lua_pushtableinteger(L, "value", cfn->all.val); + lua_pushtableinteger(L, "mode", cfn->all.mode); + lua_pushtableinteger(L, "param", cfn->all.param); + } + lua_pushtableinteger(L, "active", CFN_ACTIVE(cfn)); + } + else { + lua_pushnil(L); + } + return 1; +} + +static int luaModelSetCustomFunction(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + if (idx < NUM_CFN) { + CustomFunctionData * cfn = &g_model.customFn[idx]; + memclear(cfn, sizeof(CustomFunctionData)); + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + luaL_checktype(L, -2, LUA_TSTRING); // key is string + const char * key = luaL_checkstring(L, -2); + if (!strcmp(key, "switch")) { + CFN_SWITCH(cfn) = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "func")) { + CFN_FUNC(cfn) = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "name")) { + const char * name = luaL_checkstring(L, -1); + strncpy(cfn->play.name, name, sizeof(cfn->play.name)); + } + else if (!strcmp(key, "value")) { + cfn->all.val = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "mode")) { + cfn->all.mode = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "param")) { + cfn->all.param = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "active")) { + CFN_ACTIVE(cfn) = luaL_checkinteger(L, -1); + } + } + eeDirty(EE_MODEL); + } + + return 0; +} + +static int luaModelGetOutput(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + if (idx < NUM_CHNOUT) { + LimitData * limit = limitAddress(idx); + lua_newtable(L); + lua_pushtablezstring(L, "name", limit->name); + lua_pushtableinteger(L, "min", limit->min-1000); + lua_pushtableinteger(L, "max", limit->max+1000); + lua_pushtableinteger(L, "offset", limit->offset); + lua_pushtableinteger(L, "ppmCenter", limit->ppmCenter); + lua_pushtableinteger(L, "symetrical", limit->symetrical); + lua_pushtableinteger(L, "revert", limit->revert); + if (limit->curve) + lua_pushtableinteger(L, "curve", limit->curve-1); + else + lua_pushtablenil(L, "curve"); + } + else { + lua_pushnil(L); + } + return 1; +} + +/*luadoc +@function model.setOutput(index, value) + +Sets current global variable value. See also model.getGlobalVariable() + +@param index zero based output index, use 0 for CH1, 31 for CH32 + +@param value new value for output. The `value` is a table with following items: + * `name` name of the output (channel) + * `min` negative limit + * `max` positive limit + * `offset` subtrim value + * `ppmCenter` ppm center value + * `symetrical` 1 for symmetric limits, 0 for normal + * `revert` 1 for inverted output, 0 for normal + * `curve` curve reference (zero based index, 0 means Curve 1) +*/ +static int luaModelSetOutput(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + if (idx < NUM_CHNOUT) { + LimitData * limit = limitAddress(idx); + luaL_checktype(L, -1, LUA_TTABLE); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + luaL_checktype(L, -2, LUA_TSTRING); // key is string + const char * key = luaL_checkstring(L, -2); + if (!strcmp(key, "name")) { + const char * name = luaL_checkstring(L, -1); + str2zchar(limit->name, name, sizeof(limit->name)); + } + else if (!strcmp(key, "min")) { + limit->min = luaL_checkinteger(L, -1)+1000; + } + else if (!strcmp(key, "max")) { + limit->max = luaL_checkinteger(L, -1)-1000; + } + else if (!strcmp(key, "offset")) { + limit->offset = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "ppmCenter")) { + limit->ppmCenter = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "symetrical")) { + limit->symetrical = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "revert")) { + limit->revert = luaL_checkinteger(L, -1); + } + else if (!strcmp(key, "curve")) { + if (lua_isnil(L, -1)) + limit->curve = 0; + else + limit->curve = luaL_checkinteger(L, -1) + 1; + } + } + eeDirty(EE_MODEL); + } + + return 0; +} + +/*luadoc +@function model.getGlobalVariable(index [, phase]) + +Returns current global variable value. +See also model.setGlobalVariable() + +@notice a simple warning or notice + +@param index zero based global variable index, use 0 for GV1, 8 for GV9 + +@param phase zero based phase index, use 0 for Phase 1, 5 for Phase 6 + +@retval nil requested global variable does not exist + +@retval number current value of global variable + +Example: + +```lua + -- get GV3 (index = 2) from flight phase 1 (phase = 0) + val = model.getGlobalVariable(2, 0) +``` +*/ +static int luaModelGetGlobalVariable(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + unsigned int phase = luaL_checkunsigned(L, 2); + if (phase < MAX_FLIGHT_MODES && idx < MAX_GVARS) + lua_pushinteger(L, g_model.flightModeData[phase].gvars[idx]); + else + lua_pushnil(L); + return 1; +} + +/*luadoc +@function model.setGlobalVariable(index, phase, value) + +Sets current global variable value. See also model.getGlobalVariable() + +@param index zero based global variable index, use 0 for GV1, 8 for GV9 + +@param phase zero based phase index, use 0 for Phase 1, 5 for Phase 6 + +@param value new value for global variable. Permitted range is +from -1024 to 1024. + + +@notice Global variable can only store integer values, +any floating point value is converted (todo check how) into integer value. +*/ +static int luaModelSetGlobalVariable(lua_State *L) +{ + unsigned int idx = luaL_checkunsigned(L, 1); + unsigned int phase = luaL_checkunsigned(L, 2); + int value = luaL_checkinteger(L, 3); + if (phase < MAX_FLIGHT_MODES && idx < MAX_GVARS && value >= -GVAR_MAX && value <= GVAR_MAX) { + g_model.flightModeData[phase].gvars[idx] = value; + eeDirty(EE_MODEL); + } + return 0; +} + +const luaL_Reg modelLib[] = { + { "getInfo", luaModelGetInfo }, + { "setInfo", luaModelSetInfo }, + { "getModule", luaModelGetModule }, + { "setModule", luaModelSetModule }, + { "getTimer", luaModelGetTimer }, + { "setTimer", luaModelSetTimer }, + { "resetTimer", luaModelResetTimer }, + { "getInputsCount", luaModelGetInputsCount }, + { "getInput", luaModelGetInput }, + { "insertInput", luaModelInsertInput }, + { "deleteInput", luaModelDeleteInput }, + { "deleteInputs", luaModelDeleteInputs }, + { "defaultInputs", luaModelDefaultInputs }, + { "getMixesCount", luaModelGetMixesCount }, + { "getMix", luaModelGetMix }, + { "insertMix", luaModelInsertMix }, + { "deleteMix", luaModelDeleteMix }, + { "deleteMixes", luaModelDeleteMixes }, + { "getLogicalSwitch", luaModelGetLogicalSwitch }, + { "setLogicalSwitch", luaModelSetLogicalSwitch }, + { "getCustomFunction", luaModelGetCustomFunction }, + { "setCustomFunction", luaModelSetCustomFunction }, + { "getCurve", luaModelGetCurve }, + { "getOutput", luaModelGetOutput }, + { "setOutput", luaModelSetOutput }, + { "getGlobalVariable", luaModelGetGlobalVariable }, + { "setGlobalVariable", luaModelSetGlobalVariable }, + { NULL, NULL } /* sentinel */ +}; diff --git a/radio/src/lua/interface.cpp b/radio/src/lua/interface.cpp new file mode 100644 index 000000000..b8be613f8 --- /dev/null +++ b/radio/src/lua/interface.cpp @@ -0,0 +1,734 @@ +/* + * 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 "stamp-opentx.h" +#include "bin_allocator.h" +#include "lua/lua_api.h" + +#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; +static 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.inputsCount 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) { + lcd_hline(0, 7*FH-1, lcdLastPos+FW, ERASE); + lcd_puts(0, 7*FH, "GV Use: "); + lcd_outdezAtt(lcdLastPos, 7*FH, luaGetMemUsed(), LEFT); + lcd_putc(lcdLastPos, 7*FH, 'b'); + lcd_hline(0, 7*FH-2, lcdLastPos+FW, FORCE); + lcd_vlineStip(lcdLastPos+FW, 7*FH-2, FH+2, SOLID, FORCE); + } + } + } + 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) && + (g_menuStack[0]==menuTelemetryFrsky && sid.reference==SCRIPT_TELEMETRY_FIRST+s_frsky_view)) { + 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 + #include + #include + #include +#if !defined(SIMU) +} +#endif + + extern lua_State *L; + extern bool luaLcdAllowed; + + #define lua_registernumber(L, n, i) (lua_pushnumber(L, (i)), lua_setglobal(L, (n))) + #define lua_registerint(L, n, i) (lua_pushinteger(L, (i)), lua_setglobal(L, (n))) + #define lua_pushtablenil(L, k) (lua_pushstring(L, (k)), lua_pushnil(L), lua_settable(L, -3)) + #define lua_pushtableboolean(L, k, v) (lua_pushstring(L, (k)), lua_pushboolean(L, (v)), lua_settable(L, -3)) + #define lua_pushtableinteger(L, k, v) (lua_pushstring(L, (k)), lua_pushinteger(L, (v)), lua_settable(L, -3)) + #define lua_pushtablenumber(L, k, v) (lua_pushstring(L, (k)), lua_pushnumber(L, (v)), lua_settable(L, -3)) + #define lua_pushtablestring(L, k, v) (lua_pushstring(L, (k)), lua_pushstring(L, (v)), lua_settable(L, -3)) + #define lua_pushtablenzstring(L, k, v) { char tmp[sizeof(v)+1]; strncpy(tmp, (v), sizeof(v)); tmp[sizeof(v)] = '\0'; lua_pushstring(L, (k)); lua_pushstring(L, tmp); lua_settable(L, -3); } + #define lua_pushtablezstring(L, k, v) { char tmp[sizeof(v)+1]; zchar2str(tmp, (v), sizeof(v)); lua_pushstring(L, (k)); lua_pushstring(L, tmp); lua_settable(L, -3); } + #define lua_registerlib(L, name, tab) (luaL_newmetatable(L, name), luaL_setfuncs(L, tab, 0), lua_setglobal(L, name)) + + #define RUN_MIX_SCRIPT (1 << 0) + #define RUN_FUNC_SCRIPT (1 << 1) + #define RUN_TELEM_BG_SCRIPT (1 << 2) + #define RUN_TELEM_FG_SCRIPT (1 << 3) + #define RUN_STNDAL_SCRIPT (1 << 4) + struct ScriptInput { const char *name; uint8_t type; @@ -98,11 +122,11 @@ void luaExec(const char * filename); void luaError(uint8_t error, bool acknowledge=true); int luaGetMemUsed(); + void luaGetValueAndPush(int src); #define luaGetCpuUsed(idx) scriptInternalData[idx].instructions uint8_t isTelemetryScriptAvailable(uint8_t index); #define LUA_LOAD_MODEL_SCRIPTS() luaState |= INTERPRETER_RELOAD_PERMANENT_SCRIPTS #define LUA_LOAD_MODEL_SCRIPT(idx) luaState |= INTERPRETER_RELOAD_PERMANENT_SCRIPTS - #define LUA_STANDALONE_SCRIPT_RUNNING() (luaState == INTERPRETER_RUNNING_STANDALONE_SCRIPT) // Lua PROTECT/UNPROTECT #include struct our_longjmp { @@ -119,10 +143,11 @@ extern uint16_t maxLuaInterval; extern uint16_t maxLuaDuration; -#else // #if defined(LUA) - #define LUA_LOAD_MODEL_SCRIPTS() - #define LUA_LOAD_MODEL_SCRIPT(idx) - #define LUA_STANDALONE_SCRIPT_RUNNING() (0) -#endif -#endif // #ifndef luaapi_h +#else // #if defined(LUA) + + #define LUA_LOAD_MODEL_SCRIPTS() + +#endif // #if defined(LUA) + +#endif // #ifndef lua_api_h \ No newline at end of file diff --git a/radio/src/lua_api.cpp b/radio/src/lua_api.cpp deleted file mode 100644 index d61371267..000000000 --- a/radio/src/lua_api.cpp +++ /dev/null @@ -1,2586 +0,0 @@ -/* - * 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 "stamp-opentx.h" -#include "bin_allocator.h" -#include "timers.h" - -#if !defined(SIMU) -extern "C" { -#endif - #include - #include - #include - #include -#if !defined(SIMU) -} -#endif - -#include "lua_exports.inc" // this line must be after lua headers - -#define lua_registernumber(L, n, i) (lua_pushnumber(L, (i)), lua_setglobal(L, (n))) -#define lua_registerint(L, n, i) (lua_pushinteger(L, (i)), lua_setglobal(L, (n))) -#define lua_pushtablenil(L, k) (lua_pushstring(L, (k)), lua_pushnil(L), lua_settable(L, -3)) -#define lua_pushtableboolean(L, k, v) (lua_pushstring(L, (k)), lua_pushboolean(L, (v)), lua_settable(L, -3)) -#define lua_pushtableinteger(L, k, v) (lua_pushstring(L, (k)), lua_pushinteger(L, (v)), lua_settable(L, -3)) -#define lua_pushtablenumber(L, k, v) (lua_pushstring(L, (k)), lua_pushnumber(L, (v)), lua_settable(L, -3)) -#define lua_pushtablestring(L, k, v) (lua_pushstring(L, (k)), lua_pushstring(L, (v)), lua_settable(L, -3)) -#define lua_pushtablenzstring(L, k, v) { char tmp[sizeof(v)+1]; strncpy(tmp, (v), sizeof(v)); tmp[sizeof(v)] = '\0'; lua_pushstring(L, (k)); lua_pushstring(L, tmp); lua_settable(L, -3); } -#define lua_pushtablezstring(L, k, v) { char tmp[sizeof(v)+1]; zchar2str(tmp, (v), sizeof(v)); lua_pushstring(L, (k)); lua_pushstring(L, tmp); lua_settable(L, -3); } -#define lua_registerlib(L, name, tab) (luaL_newmetatable(L, name), luaL_setfuncs(L, tab, 0), lua_setglobal(L, name)) - -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; - -#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)) - -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; -} - -static int instructionsPercent = 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, ""); - } -} - -#if defined(PCBTARANIS) && defined(REV9E) - #define RADIO "taranisx9e" -#elif defined(PCBTARANIS) && defined(REVPLUS) - #define RADIO "taranisplus" -#elif defined(PCBTARANIS) - #define RADIO "taranis" -#else -#error "Unknown board" -#endif - -#if defined(SIMU) - #define RADIO_VERSION RADIO"-simu" -#else - #define RADIO_VERSION RADIO -#endif - -/*luadoc -@function getVersion() - -Returns OpenTX version - -@retval string OpenTX version (ie "2.1.5") - -@retval list (available since OpenTX 2.1.7) returns two values: - * `string` OpenTX version (ie "2.1.5") - * `string` radio version: `taranisx9e`, `taranisplus` or `taranis`. -If running in simulator the "-simu" is added - -@status current Introduced in 2.0.0, expanded in 2.1.7 - -### Example - -This example also runs in OpenTX versions where the radio version was not available: - -```lua -local function run(event) - local ver, radio = getVersion() - print("version: "..ver) - if radio then print ("radio: "..radio) end - return 1 -end - -return { run=run } -``` -Output of above script in simulator: -``` -version: 2.1.7 -radio: taranis-simu -Script finished with status 1 -``` -*/ -static int luaGetVersion(lua_State *L) -{ - lua_pushstring(L, VERS_STR); - lua_pushstring(L, RADIO_VERSION); - return 2; -} - -/*luadoc -@function getTime() - -Returns the time since the radio was started in multiple of 10ms - -@retval number Number of 10ms ticks since the radio was started Example: -run time: 12.54 seconds, return value: 1254 - -@status current Introduced in 2.0.0 -*/ -static int luaGetTime(lua_State *L) -{ - lua_pushunsigned(L, get_tmr10ms()); - return 1; -} - -static void luaPushDateTime(lua_State *L, uint32_t year, uint32_t mon, uint32_t day, - uint32_t hour, uint32_t min, uint32_t sec) -{ - lua_createtable(L, 0, 6); - lua_pushtableinteger(L, "year", year); - lua_pushtableinteger(L, "mon", mon); - lua_pushtableinteger(L, "day", day); - lua_pushtableinteger(L, "hour", hour); - lua_pushtableinteger(L, "min", min); - lua_pushtableinteger(L, "sec", sec); -} - -/*luadoc -@function getDateTime() - -Returns current system date and time that is kept by the RTC unit - -@retval table current date and time, table elements: - * `year` year - * `mon` month - * `day` day of month - * `hour` hours - * `min` minutes - * `sec` seconds -*/ -static int luaGetDateTime(lua_State *L) -{ - struct gtm utm; - gettime(&utm); - luaPushDateTime(L, utm.tm_year + 1900, utm.tm_mon + 1, utm.tm_mday, utm.tm_hour, utm.tm_min, utm.tm_sec); - return 1; -} - -static void luaPushLatLon(TelemetrySensor & telemetrySensor, TelemetryItem & telemetryItem) -/* result is lua table containing members ["lat"] and ["lon"] as lua_Number (doubles) in decimal degrees */ -{ - lua_Number lat = 0.0; - lua_Number lon = 0.0; - uint32_t gpsLat = 0; - uint32_t gpsLon = 0; - - telemetryItem.gps.extractLatitudeLongitude(&gpsLat, &gpsLon); /* close, but not the format we want */ - lat = gpsLat / 1000000.0; - if (telemetryItem.gps.latitudeNS == 'S') lat = -lat; - lon = gpsLon / 1000000.0; - if (telemetryItem.gps.longitudeEW == 'W') lon = -lon; - - lua_createtable(L, 0, 2); - lua_pushtablenumber(L, "lat", lat); - lua_pushtablenumber(L, "lon", lon); -} - -static void luaPushTelemetryDateTime(TelemetrySensor & telemetrySensor, TelemetryItem & telemetryItem) -{ - luaPushDateTime(L, telemetryItem.datetime.year + 2000, telemetryItem.datetime.month, telemetryItem.datetime.day, - telemetryItem.datetime.hour, telemetryItem.datetime.min, telemetryItem.datetime.sec); -} - -static void luaPushCells(TelemetrySensor & telemetrySensor, TelemetryItem & telemetryItem) -{ - if (telemetryItem.cells.count == 0) - lua_pushinteger(L, (int)0); // returns zero if no cells - else { - lua_createtable(L, telemetryItem.cells.count, 0); - for (int i = 0; i < telemetryItem.cells.count; i++) { - lua_pushnumber(L, i + 1); - lua_pushnumber(L, telemetryItem.cells.values[i].value / 100.0); - lua_settable(L, -3); - } - } -} - -static void luaGetValueAndPush(int src) -{ - getvalue_t value = getValue(src); // ignored for GPS, DATETIME, and CELLS - - if (src >= MIXSRC_FIRST_TELEM && src <= MIXSRC_LAST_TELEM) { - src = (src-MIXSRC_FIRST_TELEM) / 3; - // telemetry values - if (TELEMETRY_STREAMING() && telemetryItems[src].isAvailable()) { - TelemetrySensor & telemetrySensor = g_model.telemetrySensors[src]; - switch (telemetrySensor.unit) { - case UNIT_GPS: - luaPushLatLon(telemetrySensor, telemetryItems[src]); - break; - case UNIT_DATETIME: - luaPushTelemetryDateTime(telemetrySensor, telemetryItems[src]); - break; - case UNIT_CELLS: - luaPushCells(telemetrySensor, telemetryItems[src]); - break; - default: - if (telemetrySensor.prec > 0) - lua_pushnumber(L, float(value)/telemetrySensor.getPrecDivisor()); - else - lua_pushinteger(L, value); - break; - } - } - else { - // telemetry not working, return zero for telemetry sources - lua_pushinteger(L, (int)0); - } - } - else if (src == MIXSRC_TX_VOLTAGE) { - lua_pushnumber(L, float(value)/10.0); - } - else { - lua_pushinteger(L, value); - } -} - -struct LuaField { - uint16_t id; - char desc[50]; -}; - -#define FIND_FIELD_DESC 0x01 - -/** - Return field data for a given field name -*/ -bool luaFindFieldByName(const char * name, LuaField & field, unsigned int flags=0) -{ - // TODO better search method (binary lookup) - for (unsigned int n=0; n= count) { - // TODO error - } - if (flags & BLINK) { - drawFilledRect(x, y, w-9, count*9+2, SOLID, ERASE); - lcd_rect(x, y, w-9, count*9+2); - for (int i=0; isrcRaw || expo->chn >= chn) { - return i; - } - } - return 0; -} - -static unsigned int getInputsCountFromFirst(unsigned int chn, unsigned int first) -{ - unsigned int count = 0; - for (unsigned int i=first; isrcRaw || expo->chn!=chn) break; - count++; - } - return count; -} - -static unsigned int getInputsCount(unsigned int chn) -{ - return getInputsCountFromFirst(chn, getFirstInput(chn)); -} - -/*luadoc -@function model.getInputsCount(input) - -Returns number of lines for given input - -@param input (unsigned number) input number (use 0 for Input1) - -@retval number number of configured lines for given input - -@status current Introduced in 2.0.0 -*/ -static int luaModelGetInputsCount(lua_State *L) -{ - unsigned int chn = luaL_checkunsigned(L, 1); - int count = getInputsCount(chn); - lua_pushinteger(L, count); - return 1; -} - -/*luadoc -@function model.getInput(input, line) - -Returns input data for given input and line number - -@param input (unsigned number) input number (use 0 for Input1) - -@param line (unsigned number) input line (use 0 for first line) - -@retval nil requested input or line does not exist - -@retval table input data: - * `name` (string) input line name - * `source` (number) input source index - * `weight` (number) input weight - * `offset` (number) input offset - * `switch` (number) input switch index - -@status current Introduced in 2.0.0, `switch` added in TODO -*/ -static int luaModelGetInput(lua_State *L) -{ - unsigned int chn = luaL_checkunsigned(L, 1); - unsigned int idx = luaL_checkunsigned(L, 2); - unsigned int first = getFirstInput(chn); - unsigned int count = getInputsCountFromFirst(chn, first); - if (idx < count) { - ExpoData * expo = expoAddress(first+idx); - lua_newtable(L); - lua_pushtablezstring(L, "name", expo->name); - lua_pushtableinteger(L, "source", expo->srcRaw); - lua_pushtableinteger(L, "weight", expo->weight); - lua_pushtableinteger(L, "offset", expo->offset); - lua_pushtableinteger(L, "switch", expo->swtch); - } - else { - lua_pushnil(L); - } - return 1; -} - -/*luadoc -@function model.insertInput(input, line, value) - -Inserts an Input at specified line - -@param input (unsigned number) input number (use 0 for Input1) - -@param line (unsigned number) input line (use 0 for first line) - -@param value (table) input data, see model.getInput() - -@status current Introduced in 2.0.0, `switch` added in TODO -*/ -static int luaModelInsertInput(lua_State *L) -{ - unsigned int chn = luaL_checkunsigned(L, 1); - unsigned int idx = luaL_checkunsigned(L, 2); - - unsigned int first = getFirstInput(chn); - unsigned int count = getInputsCountFromFirst(chn, first); - - if (chnname, name, sizeof(expo->name)); - } - else if (!strcmp(key, "source")) { - expo->srcRaw = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "weight")) { - expo->weight = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "offset")) { - expo->offset = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "switch")) { - expo->swtch = luaL_checkinteger(L, -1); - } - } - } - - return 0; -} - -/*luadoc -@function model.deleteInput(input, line) - -Delete line from specified input - -@param input (unsigned number) input number (use 0 for Input1) - -@param line (unsigned number) input line (use 0 for first line) - -@status current Introduced in 2.0.0 -*/ -static int luaModelDeleteInput(lua_State *L) -{ - unsigned int chn = luaL_checkunsigned(L, 1); - unsigned int idx = luaL_checkunsigned(L, 2); - - int first = getFirstInput(chn); - unsigned int count = getInputsCountFromFirst(chn, first); - - if (idx < count) { - deleteExpoMix(1, first+idx); - } - - return 0; -} - -/*luadoc -@function model.deleteInputs() - -Delete all Inputs - -@status current Introduced in 2.0.0 -*/ -static int luaModelDeleteInputs(lua_State *L) -{ - clearInputs(); - return 0; -} - -/*luadoc -@function model.defaultInputs() - -Set all inputs to defaults - -@status current Introduced in 2.0.0 -*/ -static int luaModelDefaultInputs(lua_State *L) -{ - defaultInputs(); - return 0; -} - -static unsigned int getFirstMix(unsigned int chn) -{ - for (unsigned int i=0; isrcRaw || mix->destCh>=chn) { - return i; - } - } - return 0; -} - -static unsigned int getMixesCountFromFirst(unsigned int chn, unsigned int first) -{ - unsigned int count = 0; - for (unsigned int i=first; isrcRaw || mix->destCh!=chn) break; - count++; - } - return count; -} - -static unsigned int getMixesCount(unsigned int chn) -{ - return getMixesCountFromFirst(chn, getFirstMix(chn)); -} - -static int luaModelGetMixesCount(lua_State *L) -{ - unsigned int chn = luaL_checkunsigned(L, 1); - unsigned int count = getMixesCount(chn); - lua_pushinteger(L, count); - return 1; -} - -static int luaModelGetMix(lua_State *L) -{ - unsigned int chn = luaL_checkunsigned(L, 1); - unsigned int idx = luaL_checkunsigned(L, 2); - unsigned int first = getFirstMix(chn); - unsigned int count = getMixesCountFromFirst(chn, first); - if (idx < count) { - MixData * mix = mixAddress(first+idx); - lua_newtable(L); - lua_pushtablezstring(L, "name", mix->name); - lua_pushtableinteger(L, "source", mix->srcRaw); - lua_pushtableinteger(L, "weight", mix->weight); - lua_pushtableinteger(L, "offset", mix->offset); - lua_pushtableinteger(L, "switch", mix->swtch); - lua_pushtableinteger(L, "curveType", mix->curve.type); - lua_pushtableinteger(L, "curveValue", mix->curve.value); - lua_pushtableinteger(L, "multiplex", mix->mltpx); - lua_pushtableinteger(L, "flightModes", mix->flightModes); - lua_pushtableboolean(L, "carryTrim", mix->carryTrim); - lua_pushtableinteger(L, "mixWarn", mix->mixWarn); - lua_pushtableinteger(L, "delayUp", mix->delayUp); - lua_pushtableinteger(L, "delayDown", mix->delayDown); - lua_pushtableinteger(L, "speedUp", mix->speedUp); - lua_pushtableinteger(L, "speedDown", mix->speedDown); - } - else { - lua_pushnil(L); - } - return 1; -} - -static int luaModelInsertMix(lua_State *L) -{ - unsigned int chn = luaL_checkunsigned(L, 1); - unsigned int idx = luaL_checkunsigned(L, 2); - - unsigned int first = getFirstMix(chn); - unsigned int count = getMixesCountFromFirst(chn, first); - - if (chnname, name, sizeof(mix->name)); - } - else if (!strcmp(key, "source")) { - mix->srcRaw = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "weight")) { - mix->weight = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "offset")) { - mix->offset = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "switch")) { - mix->swtch = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "curveType")) { - mix->curve.type = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "curveValue")) { - mix->curve.value = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "multiplex")) { - mix->mltpx = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "flightModes")) { - mix->flightModes = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "carryTrim")) { - mix->carryTrim = lua_toboolean(L, -1); - } - else if (!strcmp(key, "mixWarn")) { - mix->mixWarn = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "delayUp")) { - mix->delayUp = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "delayDown")) { - mix->delayDown = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "speedUp")) { - mix->speedUp = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "speedDown")) { - mix->speedDown = luaL_checkinteger(L, -1); - } - } - } - - return 0; -} - -static int luaModelDeleteMix(lua_State *L) -{ - unsigned int chn = luaL_checkunsigned(L, 1); - unsigned int idx = luaL_checkunsigned(L, 2); - - unsigned int first = getFirstMix(chn); - unsigned int count = getMixesCountFromFirst(chn, first); - - if (idx < count) { - deleteExpoMix(0, first+idx); - } - - return 0; -} - -static int luaModelDeleteMixes(lua_State *L) -{ - memset(g_model.mixData, 0, sizeof(g_model.mixData)); - return 0; -} - -static int luaModelGetLogicalSwitch(lua_State *L) -{ - unsigned int idx = luaL_checkunsigned(L, 1); - if (idx < NUM_LOGICAL_SWITCH) { - LogicalSwitchData * sw = lswAddress(idx); - lua_newtable(L); - lua_pushtableinteger(L, "func", sw->func); - lua_pushtableinteger(L, "v1", sw->v1); - lua_pushtableinteger(L, "v2", sw->v2); - lua_pushtableinteger(L, "v3", sw->v3); - lua_pushtableinteger(L, "and", sw->andsw); - lua_pushtableinteger(L, "delay", sw->delay); - lua_pushtableinteger(L, "duration", sw->duration); - } - else { - lua_pushnil(L); - } - return 1; -} - -static int luaModelSetLogicalSwitch(lua_State *L) -{ - unsigned int idx = luaL_checkunsigned(L, 1); - if (idx < NUM_LOGICAL_SWITCH) { - LogicalSwitchData * sw = lswAddress(idx); - memclear(sw, sizeof(LogicalSwitchData)); - luaL_checktype(L, -1, LUA_TTABLE); - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - luaL_checktype(L, -2, LUA_TSTRING); // key is string - const char * key = luaL_checkstring(L, -2); - if (!strcmp(key, "func")) { - sw->func = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "v1")) { - sw->v1 = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "v2")) { - sw->v2 = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "v3")) { - sw->v3 = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "and")) { - sw->andsw = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "delay")) { - sw->delay = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "duration")) { - sw->duration = luaL_checkinteger(L, -1); - } - } - eeDirty(EE_MODEL); - } - - return 0; -} - -static int luaModelGetCurve(lua_State *L) -{ - unsigned int idx = luaL_checkunsigned(L, 1); - if (idx < MAX_CURVES) { - CurveInfo & curveInfo = g_model.curves[idx]; - lua_newtable(L); - lua_pushtablezstring(L, "name", g_model.curveNames[idx]); - lua_pushtableinteger(L, "type", curveInfo.type); - lua_pushtableboolean(L, "smooth", curveInfo.smooth); - lua_pushtableinteger(L, "points", curveInfo.points+5); - lua_pushstring(L, "y"); - lua_newtable(L); - int8_t * point = curveAddress(idx); - for (int i=0; iplay.name); - } - else { - lua_pushtableinteger(L, "value", cfn->all.val); - lua_pushtableinteger(L, "mode", cfn->all.mode); - lua_pushtableinteger(L, "param", cfn->all.param); - } - lua_pushtableinteger(L, "active", CFN_ACTIVE(cfn)); - } - else { - lua_pushnil(L); - } - return 1; -} - -static int luaModelSetCustomFunction(lua_State *L) -{ - unsigned int idx = luaL_checkunsigned(L, 1); - if (idx < NUM_CFN) { - CustomFunctionData * cfn = &g_model.customFn[idx]; - memclear(cfn, sizeof(CustomFunctionData)); - luaL_checktype(L, -1, LUA_TTABLE); - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - luaL_checktype(L, -2, LUA_TSTRING); // key is string - const char * key = luaL_checkstring(L, -2); - if (!strcmp(key, "switch")) { - CFN_SWITCH(cfn) = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "func")) { - CFN_FUNC(cfn) = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "name")) { - const char * name = luaL_checkstring(L, -1); - strncpy(cfn->play.name, name, sizeof(cfn->play.name)); - } - else if (!strcmp(key, "value")) { - cfn->all.val = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "mode")) { - cfn->all.mode = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "param")) { - cfn->all.param = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "active")) { - CFN_ACTIVE(cfn) = luaL_checkinteger(L, -1); - } - } - eeDirty(EE_MODEL); - } - - return 0; -} - -static int luaModelGetOutput(lua_State *L) -{ - unsigned int idx = luaL_checkunsigned(L, 1); - if (idx < NUM_CHNOUT) { - LimitData * limit = limitAddress(idx); - lua_newtable(L); - lua_pushtablezstring(L, "name", limit->name); - lua_pushtableinteger(L, "min", limit->min-1000); - lua_pushtableinteger(L, "max", limit->max+1000); - lua_pushtableinteger(L, "offset", limit->offset); - lua_pushtableinteger(L, "ppmCenter", limit->ppmCenter); - lua_pushtableinteger(L, "symetrical", limit->symetrical); - lua_pushtableinteger(L, "revert", limit->revert); - if (limit->curve) - lua_pushtableinteger(L, "curve", limit->curve-1); - else - lua_pushtablenil(L, "curve"); - } - else { - lua_pushnil(L); - } - return 1; -} - -/*luadoc -@function model.setOutput(index, value) - -Sets current global variable value. See also model.getGlobalVariable() - -@param index zero based output index, use 0 for CH1, 31 for CH32 - -@param value new value for output. The `value` is a table with following items: - * `name` name of the output (channel) - * `min` negative limit - * `max` positive limit - * `offset` subtrim value - * `ppmCenter` ppm center value - * `symetrical` 1 for symmetric limits, 0 for normal - * `revert` 1 for inverted output, 0 for normal - * `curve` curve reference (zero based index, 0 means Curve 1) -*/ -static int luaModelSetOutput(lua_State *L) -{ - unsigned int idx = luaL_checkunsigned(L, 1); - if (idx < NUM_CHNOUT) { - LimitData * limit = limitAddress(idx); - luaL_checktype(L, -1, LUA_TTABLE); - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - luaL_checktype(L, -2, LUA_TSTRING); // key is string - const char * key = luaL_checkstring(L, -2); - if (!strcmp(key, "name")) { - const char * name = luaL_checkstring(L, -1); - str2zchar(limit->name, name, sizeof(limit->name)); - } - else if (!strcmp(key, "min")) { - limit->min = luaL_checkinteger(L, -1)+1000; - } - else if (!strcmp(key, "max")) { - limit->max = luaL_checkinteger(L, -1)-1000; - } - else if (!strcmp(key, "offset")) { - limit->offset = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "ppmCenter")) { - limit->ppmCenter = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "symetrical")) { - limit->symetrical = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "revert")) { - limit->revert = luaL_checkinteger(L, -1); - } - else if (!strcmp(key, "curve")) { - if (lua_isnil(L, -1)) - limit->curve = 0; - else - limit->curve = luaL_checkinteger(L, -1) + 1; - } - } - eeDirty(EE_MODEL); - } - - return 0; -} - -/*luadoc -@function model.getGlobalVariable(index [, phase]) - -Returns current global variable value. -See also model.setGlobalVariable() - -@notice a simple warning or notice - -@param index zero based global variable index, use 0 for GV1, 8 for GV9 - -@param phase zero based phase index, use 0 for Phase 1, 5 for Phase 6 - -@retval nil requested global variable does not exist - -@retval number current value of global variable - -Example: - -```lua - -- get GV3 (index = 2) from flight phase 1 (phase = 0) - val = model.getGlobalVariable(2, 0) -``` -*/ -static int luaModelGetGlobalVariable(lua_State *L) -{ - unsigned int idx = luaL_checkunsigned(L, 1); - unsigned int phase = luaL_checkunsigned(L, 2); - if (phase < MAX_FLIGHT_MODES && idx < MAX_GVARS) - lua_pushinteger(L, g_model.flightModeData[phase].gvars[idx]); - else - lua_pushnil(L); - return 1; -} - -/*luadoc -@function model.setGlobalVariable(index, phase, value) - -Sets current global variable value. See also model.getGlobalVariable() - -@param index zero based global variable index, use 0 for GV1, 8 for GV9 - -@param phase zero based phase index, use 0 for Phase 1, 5 for Phase 6 - -@param value new value for global variable. Permitted range is -from -1024 to 1024. - - -@notice Global variable can only store integer values, -any floating point value is converted (todo check how) into integer value. -*/ -static int luaModelSetGlobalVariable(lua_State *L) -{ - unsigned int idx = luaL_checkunsigned(L, 1); - unsigned int phase = luaL_checkunsigned(L, 2); - int value = luaL_checkinteger(L, 3); - if (phase < MAX_FLIGHT_MODES && idx < MAX_GVARS && value >= -GVAR_MAX && value <= GVAR_MAX) { - g_model.flightModeData[phase].gvars[idx] = value; - eeDirty(EE_MODEL); - } - return 0; -} - -/*luadoc -@function popupInput(title, event, input, min, max) - -Raises a popup on screen that allows uses input - -@param title (string) text to display - -@param event (number) the event variable that is passed in from the -Run function (key pressed) - -@param input (number) value that can be adjusted by the +/­- keys - -@param min (number) min value that input can reach (by pressing the -­ key) - -@param max (number) max value that input can reach - -@retval number result of the input adjustment - -@retval "OK" user pushed ENT key - -@retval "CANCEL" user pushed EXIT key - -@notice Use only from stand-alone and telemetry scripts. - -@status current Introduced in 2.0.0 -*/ -static int luaPopupInput(lua_State *L) -{ - uint8_t event = luaL_checkinteger(L, 2); - s_warning_input_value = luaL_checkinteger(L, 3); - s_warning_input_min = luaL_checkinteger(L, 4); - s_warning_input_max = luaL_checkinteger(L, 5); - s_warning = luaL_checkstring(L, 1); - s_warning_type = WARNING_TYPE_INPUT; - displayWarning(event); - if (s_warning_result) { - s_warning_result = 0; - lua_pushstring(L, "OK"); - } - else if (!s_warning) { - lua_pushstring(L, "CANCEL"); - } - else { - lua_pushinteger(L, s_warning_input_value); - } - s_warning = NULL; - return 1; -} - -/*luadoc -@function defaultStick(channel) - -Get stick that is assigned to a channel. See Default Channel Order in General Settings. - -@param channel (number) channel number (0 means CH1) - -@retval number Stick assigned to this channel (from 0 to 3) - -@status current Introduced in 2.0.0 -*/ -static int luaDefaultStick(lua_State *L) -{ - uint8_t channel = luaL_checkinteger(L, 1); - lua_pushinteger(L, channel_order(channel+1)-1); - return 1; -} - -/*luadoc -@function defaultChannel(stick) - -Get channel assigned to stick. See Default Channel Order in General Settings - -@param stick (number) stick number (from 0 to 3) - -@retval number channel assigned to this stick (from 0 to 3) - -@retval nil stick not found - -@status current Introduced in 2.0.0 -*/ -static int luaDefaultChannel(lua_State *L) -{ - uint8_t stick = luaL_checkinteger(L, 1); - for (int i=1; i<=4; i++) { - int tmp = channel_order(i) - 1; - if (tmp == stick) { - lua_pushinteger(L, i-1); - return 1; - } - } - lua_pushnil(L); - return 1; -} - -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.inputsCount 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) { - lcd_hline(0, 7*FH-1, lcdLastPos+FW, ERASE); - lcd_puts(0, 7*FH, "GV Use: "); - lcd_outdezAtt(lcdLastPos, 7*FH, luaGetMemUsed(), LEFT); - lcd_putc(lcdLastPos, 7*FH, 'b'); - lcd_hline(0, 7*FH-2, lcdLastPos+FW, FORCE); - lcd_vlineStip(lcdLastPos+FW, 7*FH-2, FH+2, SOLID, FORCE); - } - } - } - 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) && - (g_menuStack[0]==menuTelemetryFrsky && sid.reference==SCRIPT_TELEMETRY_FIRST+s_frsky_view)) { - 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 -#include -#include - extern const char * zchar2string(const char * zstring, int size); #define EXPECT_ZSTREQ(c_string, z_string) EXPECT_STREQ(c_string, zchar2string(z_string, sizeof(z_string))) extern void luaInit();