Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
The input table defines what values are available as input(s) to mix scripts. There are two forms of input table entries.
SOURCE syntax
SOURCE inputs provide the current value of a selected OpenTX variable. The source must set by the user when the mix script is configured. Source can be any value OpenTX knows about (inputs, channels, telemetry values, switches, custom functions,...). Note: typical range is -1024 thru +1024. Simply divide the input value by 10.24 to interpret as a percentage from -100% to +100%.
VALUE syntax
VALUE inputs provide a constant value that is set by the user when the mix script is configured.
name - maximum length of 8 characters
min - minimum value of -128
max - maximum value of 127
default - must be within the valid range specified
Maximum of 6 inputs per script (warning : was 8 in 2.1)
If defined, init function is called right after the script is loaded from SD card and begins execution. Init is called only once before the run function is called for the first time.
Input Parameters:
none
Return values:
none
This section provides more specifics on the EdgeTX Lua implementation. Here you will find syntax rules for interface tables and functions.
Lua makes it easy to load and unload code modules on the fly, to save memory or to provide program extensions.
The loadScript(<file>)
function will load a script from a the file and return a function that is the body of the script, as described in the previous section. So you could have the following Lua script file saved on the SD card:
You can load and use the above file with the following code:
So here we put together what we learned in the previous section. The body of the script is an anonymous function returned by loadScript
and stored in the variable chunk
. It returns the function f
when it is called. The local variable c
in the script is assigned to the first vararg passed to the call. Since a new closure is created every time we call chunk
, f1
and f2
have different closures with different values of c
.
The advanced topics section covers file I/O, data sharing, and debugging techniques
An EdgeTX model has several data components that can be exchanged with Lua scripts. This section gives an overview.
In general terms, there are two different types of data that can be exchanged with EdgeTX:
Values, which generally fall on a scale between -1024 and 1024, also sometimes reported as -100% to 100%, e.g. for mixer values and channel outputs.
Switches, which are either true
or false.
A specific data source type, e.g. Global Variables, can be indexed directly with an index that starts with 0 for the first one. This can be a little confusing, as e.g. GV1 for FM0 is read with model.getGlobalVariable(0, 0)
, because GV1 is the first global variable, and FM0 is the first flight mode. But as long as you remember that all direct indices start with 0 for the first one, you should be fine!
Global Variables is a value data source that can return a value between -1024 and 1024. Examples of value sources are:
Global Variables
Sticks, sliders and knobs
Trim values
Input lines
Channel outputs
Trainer channel inputs
Telemetry sensors
There is a way to access a value of any such type with a meta-index. You use the function getFieldInfo(name)
where name
can be any of the valid source names listed here. It returns a table with a description of the value source, including the field id,
which is the meta-index.
You can use id
with the function getValue(id)
to read the current value. You can also use name
directly, but this is less efficient, as EdgeTX does a linear search for the name every time it is called. Therefore, the procedure of first extracting the meta-index from getFieldInfo(name).id
, and then using that, is recommended.
For switches, getLogicalSwitchValue(index)
uses a direct index to read this specific type of switch sources, again using the index
0 for LS1 etc. But there are also several types of switch sources, such as:
Logical switches
Switch positions, testing if a physical switch is in a particular position, e.g. SA↑
.
Trim buttons
Transmitter and telemetry activity
You can find the meta-index of a particular switch source with getSwitchIndex(name)
, where name
is exactly the name that you see in the radio menus where you can select a switch source.
You can read the current value of a switch source with getSwitchValue(index)
as a true
/false
value.
It can be very confusing that some source can be read both as a value and as a switch. As an example, elevator trim can be read as the current value of the trim:
Or it can be read as the current value of the trim button position:
These are really two different things, as the elevator trim is an internal value stored by the radio, which is changed with the trim button, and the button is a physical button.
But it can also be a more direct comparison, e.g. the 3-position switch B, which can be read as a value source with:
Or the current position of switch B can be read with:
Hopefully, these examples illustrated the differences between values and switches, and showed how these can be retrieved in a Lua script.
You can also send data the other way: from Lua to the EdgeTX model setup.
To send a value, use a global variable: model.setGlobalVariable(index, fm, value)
. If you use the default GV setting, where all other flight modes use the value of FM0, then you can use 0 for fm
.
To send a switch, setup a STICKY
type logical switch, and then use setStickySwitch(index, true/false)
.
The following table gives an overview of the Lua API functions that can be used to exchange data with the EdgeTX model setup.
This section will give some technical details for radios with a color screen.
An argument with drawing flags can be given to the various functions that draw on the LCD screen. The lower half of the flags (bits 1-16) are the flag attributes shown below, and the upper half of the flags (bits 17-32) are a color value.
Not all of the flags below should be directly manipulated from Lua, but those that are meant to be set directly by Lua, are accessible by the Lua flag constants.
Since the flags are bits, you can add them up to combine, as long as you only add each flag one time. If you add the same flag twice, then it will add up to the next bit over, e.g. INVERS + INVERS = VCENTER
. If you want to add a flag to a value where it may already be set, then use flags = bit32.bor(flags, INVERS)
.
RGB_FLAG decides how the color value is encoded into the upper half (bits 17-32).
If RGB_FLAG = 0, then an index into a color table is stored. This color table holds a default color (index 0), the theme colors, and CUSTOM_COLOR. The entries in the color table can be changed with the function lcd.setColor
. The advantage of this system is that the color changes everywhere that this indexed color is used, and this is how different color themes are created. Notice that changing the theme colors affects the entire user interface of your radio!!
If RGB_FLAG = 1, then a 16-bit RGB565 color is stored. This is used directly by the system to draw a color on the screen.
You should not change RGB_FLAG explicitly; this is handled automatically by the various functions and Lua constants. But you should be aware of the following.
lcd.setColor
must have an indexed color as its first argument, because this will be the index of the color in the table being changed. Giving another color, e.g. ORANGE, as the first argument will result in nothing.
If no color is given to the flags with a drawing function, RGB_FLAGS = 0 and the color index = 0. Therefore, the default color is stored in the color table under this index, and you can change the default color with lcd.setColor(0, color)
.
lcd.getColor
always returns a RGB color. This can be used to "save" an indexed color before you change it.
lcd.RGB
obviously returns a RGB color.
OpenTX only supports indexed colors in drawing functions, so you must first call lcd.setColor
to change e.g. CUSTOM_COLOR, and then call the LCD drawing function with that indexed color. In EdgeTX, you can use either type of color for drawing functions, so you are no longer forced to constantly call lcd.setColor
. You can also store any color in local variables, and then use these when drawing, thus effectively creating your own color theme.
In OpenTX, lcd.RGB
returns a 16 bit RGB565 value, but in EdgeTX it returns a 32 bit flags value with the 16 bit RGB565 value in the upper half (bits 17-32). Therefore, colors in EdgeTX are not binary compatible with colors in OpenTX. But if you use the functions lcd.RGB
, lcd.setColor
, and lcd.getColor
, then your code should work the same way in EdgeTX as in OpenTX.
Unfortunately, OpenTX has disabled the function lcd.RGB
when the screen is not available for drawing, so it can only be called successfully from the refresh
function in widgets and from the run
function in One-Time scripts. Therefore, some existing widget scripts set up colors with hard coded constants instead of calling lcd.RGB
during initialization, and this is not going to work with EdgeTX, because of the different binary format.
A pull request has been submitted to OpenTX, allowing lcd.RGB
to work also during widget script initialization, and hopefully, it will be merged into OpenTX 2.3.15. If that happens, then the obvious way to solve the problem is to use lcd.RGB
values instead of hard coded color constants. But in the meantime, the following RGB function can be used for setting up colors in a way that works for both EdgeTX and OpenTX.
This functions calls lcd.RGB
, and if it gets a nil value (because we are running a widget script under OpenTX, and it is not called from the refresh
function) then it creates the 16-bit RGB565 value that OpenTX wants.
Outputs are only used in mix scripts. The output table defines only name(s), the actual values are determined by the script's run function.
Example:
Output name is limited to four characters.
A maximum of 6 outputs are supported
Number Format Outputs are 16 bit signed integers when they leave Lua script and are then divided by 10.24 to produce output value in percent:
Script Return Value | Mix Value in OpenTX |
---|---|
The return statment is the last statement in an OpenTX Lua script. It defines the input/output table values and functions used to run the script.
Parameters init, input and output are optional. If a script doesn't use them, they can be omitted from return statement.
Example without init and output:
This chapter will show you some ways that large script projects can be fitted into the limited memory of our radios.
Regarding memory, the situation is a bit different for the radios with black/white or grey scale screens and telemetry scripts, and the radios with color screens and widget scripts. The telemetry script radios only have 128-192 KB RAM memory - that is very small! The widget script radios have 8 MB RAM memory. But the way that widgets are designed means that all widget scripts present on the SD card will be loaded into memory, whether or not they are actually used. Therefore, different strategies should be applied to save memory for the two different types of radios and scripts.
Radios with black/white or grey scale screens and telemetry scripts such as e.g. FrSky Taranis, Xlite, Jumper T12 and Radiomaster TX12 have extremely small RAM memories, and therefore it may be necessary to divide up your script into smaller loadable modules.
The following simple example demonstrates how different screens can be loaded on demand, and how shared data can be stored in a table.
The table shared
contains data that is shared between the main telemetry script and the loadable screens. Notice that the functions shared.changeScreen
and shared.run
are also shared this way.
Code is loaded by shared.changeScreen
with the loadScript
function, which returns the loadable script as a chunk of code. The code is executed with shared
as the argument, and the loadable script adds a new run
function to the shared
table. shared.run
is called by run
in the main script.
Radios with color screens and widget scripts such as e.g. FrSky Horus, Jumper T16 and Radiomaster TX16S have fairly large RAM memories, but since all widget scripts present on the SD card are always loaded into memory, they could run out of memory if many big widget scripts are present on the card - even if they are not being used by the selected model. Therefore, large widget scripts should be divided into a small main script and a large loadable part. One way to accomplish this is the following.
Obviously, the bulk of the widget's code goes in loadable.lua
, and is only loaded if the widgets is in fact being used. Therefore, if the widget is not used, only the small amount of code in main.lua
is loaded into the radio's memory.
For an example of a widget that uses the above design pattern, please have a look at the EventDemo widget that is included on the SD card with EdgeTX for color screen radios.
Function | Description |
---|---|
The create
function loads the file loadable.lua
in the folder /WIDGETS/<widget name>/, and calls it immediately as described in . It passes zone
and options
as arguments to loadable.lua
. This scripts adds the functions refresh
, update
and (optionally) background
to the widget
table:
zone
and options
are stored in the of loadable.lua
, therefore they do not need to be added to the widget
table, as is commonly done.
getFieldInfo(name)
Gets the meta-index of a value source with name
.
getValue(index)
Gets the current value of a source, where index
is from the above.
getFlightMode()
Returns the current flight mode number and name. Do not confuse with model.getFlightMode
, which is a completely different function!
model.getGlobalVariable( index, fm)
Read the value of a global variable between -1024 and 1024. You can use the above to get the current flioght mode for fm
, or you can setup the GV to always use the value from FM0.
model.setGlobalVariable( index, fm, value)
Sets the value of a global variable.
setTelemetryValue(id, subID, instance, value, ...)
This function can send data from Lua to a telemetry sensor. If it is running, then the sensor will show up with Discover Sensors. This can be used to log values generated by Lua.
getLogicalSwitchValue( index)
Get the value of a logical switch.
setStickySwitch(id, value)
Set the value of a STICKY
type logical switch.
getSwitchIndex(name)
Get the meta-index of a switch source with name
.
getSwitchName(index)
Get the name of a switch source.
getSwitchValue(index)
Get the value of a switch source.
getPhysicalSwitches()
Get a list of physical switches on the radio. Can be used to make a selection list.
getShmVar(index)
Get the value of a shared memory variable. These are little "mail boxes" provided as a way to send data between Lua widgets and other Lua scripts like Mixer and Function scripts.
setShmVar(index, value)
Set the value of a shared memory variable.
model.setTimer(timer, value)
Set the timer to value
.
model.getTimer(timer)
Read the timer.
0
0%
996
97.2%
1024
100%
-1024
-100%
It is always good practice to check your code on syntax before even testing it. There are several good LUA editors on the market, some of them for free. The ZeroBrane (https://studio.zerobrane.com/) suite is quite powerful, and very simple to use. In the rest of this article we will assume you use ZeroBrane, but the same techniques can be used in any powerful code editor.
You can set ZeroBrane to use the Scripts directory of your simulated transmitter SDCard image as a default directory, and it will show you all the files in a nice navigation tree.
If you open a LUA file, you will already have some markup in your screen, indicating errors or important info. In ZeroBrane for instance, a not declared variable will always get underlined, so that you are made aware you forgot to declare it, or you redeclared it by accident afterwards again.
The editor will have an "execute code" option, that will try to run the code on it's own interpreter (code processing engine). If there are any syntax errors, it will not be able to execute the code, and inform you about the errors. A common error in LUA is using a single equal sign (=) in a condition in an 'if' statement, whereas in LUA that should be a double equal sign (==). The interpreter will inform you about such an error ocurring, and mention the line where you made the error.
Since the OpenTX LUA environment has some own functions, like lcd.drawText(), the interpreter will 'complain' it cannot call an unspecified function, but it will check the entire syntax nonetheless.
In zerobrane, if you tried to run the code, it will first save it if it could be interpreted correctly. A common workflow would be:
do some code corrections / additions
try to run the code in the editor
if the code gets compiled, the edited LUA file gets saved automatically
run the code in the transmitter simulator
check for the desired functionality
restart this cycle
In the later versions of the companion software, a LUA debug screen is available. So once you start your just syntaxically verified and saved LUA script, you can follow some of it's output and actions in the debug screen. It will tell you where and in what line an eventual crash occured.
If you made some code changes, chances are that you have to do a whole sequence of key-clicks and actions on the transmitter simulator in order to retest the same script after those changes.
You can substatially reduce the effort of all this by using a script loader in your transmitter. This is nothing more then a function that will load and run your code. If you press the enter button, it will unload the current code, and ask if you want to run the code again. So, with just two clicks, you can unload the running code and reload your updated code. On the Taranis simulator, you can also reload the LUA scripts environment with just a buttonclick.
An example of such a script is found under the notes. You can adapt it for other types of scripts of course.
This script loader will load the file /SCRIPTS/TELEM/telem1.lua, run it, and wait for an Enter Break event. Once received, it will unload the code and wait for a next Enter Break event.
Normally one uses getValue() function with the source/filed name like so:
This works and is recommended method for portability. But if a particular script needs to get the value of certain field a lot, then it is faster to use this syntax:
Why is this method faster? With the function getFieldInfo(name) we get the numerical id
of the wanted filed. The function has to find the requested value by its name in the table of all available sources. That search takes some time.
When we use this syntax the search is only done once. In comparison in the first example the search must be performed every time getValue("bar")
is called.
So when the getValue(my_id)
is called the search can be skipped and the requested value if fetched directly.
Of course there is a trade-of, the second example uses little more memory (for variable my_id
).
The run function is the function that is periodically called for the lifetime of script execution. Syntax of the run function is different between mix scripts and telemetry scripts.
Input parameters:
zero or more input values, their names are arbitrary, their meaning and order is defined by the input table. (see Input Table Syntax)
Return values:
none - if output table is empty (i.e. script has no output)
values
or -
comma separated list of output values, their order and meaning is defined by the output table. (see Output Table Syntax)
Input parameters:
The key-event parameter indicates which transmitter button has been pressed (see Key Events)
Return values:
A non-zero return value will halt the script
This section will discuss how interactive scripts receiving user inputs via key and touch events can be created.
The two Lua widgets EventDemo and LibGUI are provided on the SD card content for color screen radios. EventDemo is just a small widget showing off how key, and especially touch events, can be used. LibGUI contains a globally available library, and a widget showing how the library can be used by other widgets. This section will discuss these two widgets for color screen radios, but generally, what is stated about key events here also applies to the run
function in Telemetry and One-Time scripts.
This widget uses the design pattern for saving memory by loadable files discussed in the previous section, so all of the action takes place in the file loadable.lua. The following code listing is an outline of the refresh
function with comments explaining what is going on.
LibGUI was created to support the SoarETX widgets, but it was made separate so everyone can use it, if they so desire. Since it is all Lua, the code is visible for everyone to study and use as is, or modify for their own use (as long as they comply with the terms of the Gnu Public license, of course).
It is a widget that comes with a global library. Since all widgets are loaded, whether or not they are being used, global functions declared in the body of a widget script will always be available in any widget script in the create
and refresh
functions. It is not necessary to setup the widget to use the library, and the only purpose of the widget is to show how LibGUI
can be used to create widget apps.
The library is implemented in the loadable file /WIDGETS/LibGUI/libgui.lua. The widget that demonstrates how to use the library is implemented in the loadable file /WIDGETS/LibGUI/loadable.lua. The file /WIDGETS/LibGUI/main.lua contains the standard functions needed for a widget and handles the loading of the two other files.
The global function loadGUI()
returns a new libGUI
object. This object can be used to create new GUI
objects, which are used to create screens with elements like buttons, menus, labels, numbers etc.
libGUI
has the following properties controlling general settings for the library.
These are default drawing flags to be applied if no flags are given at creation of screen elements.
Note: these flags should not contain color values, as colors are added by the following.
This is a table of the colors used for drawing the GUI elements. These are all theme colors by default, but they can be changed for the libGUI
instance without changing the theme colors anywhere else.
A function f()
to draw the zone of the screen in widget mode. It takes no arguments.
This is a small utility function that returns true
if the first argument x
matches any of the following arguments. It is useful for comparing values of event
, e.g. if we want to test if the user pressed either of the following buttons, we can use:libGUI.match(event, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT, EVT_VIRTUAL_MENU)
This is the main function that creates a new GUI
object. If an application has different screens, then a GUI
object is created for each screen.
A function f()
to draw the screen background in full screen mode. The GUI elements are drawn afterwards on top.
Redraws the screen and processes key and touch events. It can directly replace a widget's refresh
function, or it can be called by refresh
.
Sets a function f(event, touchState)
to handle an event. If no GUI element is being edited, then this can trap events before they are passed to the GUI, e.g. to press EXIT to go back to the previous screen. If f
is nil
, then the event handler is removed.
If the function returns another event value, then it is passed to the active screen element (i.e. the handler can modify certain events).
It cannot trap the events EVT_VIRTUAL_PREV
, EVT_VIRTUAL_NEXT
as these events are used for navigation. If elements are being edited, then this also takes precedence over the event handler.
This function can be used to show a modal prompt window, or trap events (alternative to setEventHandler
). guiPrompt
is a GUI
or another Lua table with a function run(event, touchState
).
When it is set, the main GUI will first be drawn, and then it will call guiPrompt.run(event, touchState)
instead of its own onEvent
function.
Dismisses the prompt.
The screen elements are drawn in the order that they are added to the GUI, and touch events are sent to the first element that covers the touched point of the screen. GUI elements therefore should never overlap.
There are some common properties that can be set for all or most of the GUI elements.
element.disabled = true
prevents the element from taking focus and receiving events, and disabled buttons are greyed out.
element.hidden = true
in addition to the above, the element is not drawn.
element.title
can be changed for elements with a title.
element.value
can be changed for elements with a value.
element.flags
drawing flags for the element's text. If no flags were given at element creation, it defaults to GUI.flags
.
The various screen elements are added to the GUI with the functions described below. The functions all add the element to the GUI and returns a reference so the element subsequently can be accessed by the client.
Add a button to the GUI.
When tapped, it calls callBack(self)
so a call back function can tell which button activated it.
Add a toggle button to the GUI.
The value
is either true
or false
.
When tapped, it calls callBack(self)
so a call back function can tell which toggle button activated it, and what self.value
is.
Add an editable number to the GUI.
The value
can be either a number or text. By setting the value to a text, it can be shown that the number is not present e.g. by "- -" or similar.
When tapped, the number will go to edit mode. In edit mode, and changeValue(d, self)
is called if the increase/decrease buttons are pressed, or a finger is slided up or down from the number. The parameter d
is the number of "ticks" by which the number is being changed, and changeValue
must return the new value for the number. In a simple case, the new value would just be self.value + d
, but it could also select new values from a table etc.
Add a timer to the GUI.
If no value
is present, then the model timer tmr
will be shown. If value
is a number, then it indicates the time in seconds, and it will be shown as MM:SS. The value
can also be text, e.g. "- - : - -" to show that the timer is disabled.
When tapped, the timer will go to edit mode, as described above for number.
Add a text label to the GUI.
The label does not respond to any events, but its title
and flags
can be changed.
Add a scrollable menu to the GUI.
items
is a table with the menu item texts.
When a menu item is tapped, it calls callBack(self)
and self.selected
gets the index of the selected item.
Add a field with values that can be selected on a drop down menu.
items
is a table with the items that can be selected from.
selected
is the index of the initially selected item.
When an item has been selected, it calls callBack(self)
. The index of the selected item is given by self.selected
.
Adds a horizontal slider that starts at (x, y)
and has the width w
.
value
is the initial value, and min - max
is the interval of values that can be selected with step size delta
.
When the value is changed, callBack(self)
is called, and the value is given by self.value
.
The same as the above, just vertical.
This can be used to create your own custom GUI elements. self
is a table containing the element. You must define the functions self.draw(focused)
and self.onEvent(event, touchState)
. There is a function self.drawFocus(color)
defined that can draw a border around the element (use if focused == true).
Create a nested sub-GUI. This can be used to group GUI elements which will be offset by (x, y)
relative to the parent GUI
.
OpenTX considers all function, mix, and telemetry scripts to be 'permanent' scripts that share the same runtime environment. They are typically loaded at power up or when a new model is selected. However, they are also reinitialized when a script is added or removed during model editing.
Any variable or function not declared local is implicitly global. Care must be taken to avoid unintentional global declarations, and ensure that the globals you intentionally declare have unique names to avoid conflicts with scripts written by others.
This example consists of three scripts
count-dn.lua - this is a mix script than can be run stand alone to announce time remaining based on a user-defined switch and duration. It updates two global variables (gCountUp and gCountDown). It also creates output values (ctup and ctdn) which are for demonstration purposes only.
count-up.lua - this is an optional function script which will do count up announcements based on harded coded values.
shocount.lua - this is an optional telemetry script which simply shows the current values of the gCountUp and gCountDown variables.
count-dn.lua
copy to /SCRIPTS/MIXES
configure on the transmitter CUSTOM SCRIPT page
suggested switch = "SA"
suggested mins = 3
suggested sw_high = 0
screen image:
count-up.lua
copy to /SCRIPTS/FUNCTIONS
configure on the transmitter SPECIAL FUNCTIONS page
suggested switch SA(down)
screen image:
shocount.lua
copy to /SCRIPTS/TELEMETRY
configure on the transmitter TELEMETRY page
screen image:
Color | Default value | Used for |
---|---|---|
primary1
COLOR_THEME_PRIMARY1
Text on dropDown menu
primary2
COLOR_THEME_PRIMARY2
Text on buttons and numbers/timers being edited
primary3
COLOR_THEME_PRIMARY3
Text on labels, menus, and numbers/timers
focus
COLOR_THEME_FOCUS
Background on buttons and numbers/timers being edited.
edit
COLOR_THEME_EDIT
Background when a value is being edited.
active
COLOR_THEME_ACTIVE
Background on active toggle buttons and the border around selected elements.