Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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 TX16 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 TX16 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.
The create
function loads the file loadable.lua
in the folder /WIDGETS/<widget name>/, and calls it immediately as described in the previous section. 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 closure of loadable.lua
, therefore they do not need to be added to the widget
table, as is commonly done.
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.
This section provides more specifics on the EdgeTX Lua implementation. Here you will find syntax rules for interface tables and functions.
Lua is a small but powerful language. This section will explain a few of the most important concepts.
Lua was chosen for OpenTX, and hence also EdgeTX, because it is a small language designed to be highly extensible by libraries written in C and C++, so it can be integrated into other systems like EdgeTX. It is also relatively efficient, both in terms of memory and CPU usage, and hence well suited for the radios.
In addition to the provided libraries, Lua has a very elegant mechanism for loading new Lua code modules during run-time. A lot of the elegance comes from the way that the loading mechanism meshes with another concept supported by Lua: first class functions with closures.
Computer science pioneer Christopher Strachey introduced the concept of functions as first-class objects in his paper F from 1967. What it means is that functions can be treated as other variables: as arguments passed in function calls, as results returned from function calls, and a function identifier can be re-assigned to another chunk of function code, just like a variable ca be assigned to a new value.
In Lua, a function declaration is really "syntactic sugar" for assigning a variable to the chunk of code that is called when the function is invoked, i.e.
local function f(x) return x + 1 end
is the same as
local f = function(x) return x + 1 end
You can even add functions to Lua tables, i.e.
t = { f = f }
will add the above function to the table t
, as a field that is also called f
. Does that look familiar to the return statement required at the end of a Lua script?
Yes indeed, because a script is really an anonymous function that returns a list of functions to the system. The function declarations assign variables to chunks of function code, these variables are added to the list returned at the end of the script, and the system then calls the functions periodically to run the script. So the script itself is run one time initially, and subsequently the functions returned by the last statement are called periodically.
Another important concept that goes with first-class functions, is closures. This is the environment of the function with the variable names that the function can see. Please consider the following function counter
that returns another function:
The function is returned directly without being assigned to a variable name. The closure of the function returned is the body of the function counter
, containing both the arguments start
and step
, and the local variable x
. So if c1 = counter(1, 1)
then c1()
will return 1, 2, 3, ... when called repeatedly, and if c2 = counter(2, 3)
then c2()
will return 2, 5, 8, ...
Likewise, the local variables that you declare outside the functions of your script can be used by all of the functions in your script, and they persist between function calls, but they are not visible to other scripts.
Please consider this function:
if event == EVT_VIRTUAL_ENTER or event == EVT_VIRTUAL_EXIT then
can be replaced by
if match(event, EVT_VIRTUAL_ENTER, EVT_VIRTUAL_EXIT) then
You can also use ...
directly as a comma separated list of values, e.g. local a, b, c = ...
will assign the three variables to the three first arguments following x
, or nil
if none are given.
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 .
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 = 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.
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)
.
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, older versions of OpenTX disabled the function lcd.RGB
when the screen is not available for drawing, so it could 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 color constants instead of calling lcd.RGB
during initialization, and this is not going to work with EdgeTX, because of the different binary format. But in the current version of OpenTX this issue has been fixed, so you can replace the hard coded color constants with lcd.RGB
values.
Lua makes it easy to load and unload code modules on the fly, to save memory or to provide program extensions.
The 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 are a little trickier, as you can register multiple instances of the same widget script, and all of these instances run within the same Lua closure. Therefore, local variables declared outside any functions in a widget script are shared among all of the instances of that script. But each call to the create(...)
function returns a new widget
list to the system. And since this list is unique to each instance, you can add private instance variables to it.
It takes an argument x
and a list ...
A vararg list is just like a list of arguments separated by commas, and you can call the function with any number of arguments greater than 1. Inside the function, you can also treat ...
like a comma separated list of variables, e.g. in the above function, ...
is converted to a table by {...}
just like you could construct a table by e.g. {a, b, c}
. The table is iterated with the ipairs
function to look for an element matching the first argument x
. So e.g. the following statement
The helpful, both if you want to learn more about Lua, and if you want to search for answers to specific questions.
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 . 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!!
must have an 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.
always returns a RGB color. This can be used to "save" an indexed color before you change it.
obviously returns a RGB color.
Closes file
. Note that files are automatically closed when their handles are garbage collected, but that takes an unpredictable amount of time to happen.
This function opens a file, in the mode specified in the string mode
. It returns a new file handle, or, in case of errors, nil plus an error message.
The mode
string can be any of the following:
"r
": read mode (the default)
"w
": write mode
"a
": append mode
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.
This 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. 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 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 a global function that loads the library and the standard functions needed for a widget.
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 that can be edited, and timers.
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(event, touchState)
to draw the screen background in full screen mode. The GUI elements are drawn afterwards on top.
A GUI (or another table with a function run(event, touchState
). When this is set, the GUI will first be drawn, and then it will call prompt.run(event, touchState)
instead of running itself. That way, the prompt
can implement a modal prompt window.
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.
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
.
elements.blink
will make non-button elements blink
elements.invers
will make non-button elements draw in inversed colors.
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, events are passed to callBack(self, event, touchState)
. Thereby, the call back function can use events to edit the number, e.g. sliding a finger up and down can increase and decrease the value. You can look in the LibGUI widget's loadable file for an example of this.
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. This function returns a table with each of the menu's line elements.
visibleCount
is the number of visible menu items.
items
is a table with the menu item texts.
When a menu item is tapped, it calls callBack(self)
. Each menu element has a field self.idx
giving the index in the menu, and this can be used by callBack
to see which menu item was selected.
Notice that the menu's width is decided by the item texts and the font flags, and the height is decided by visibleCount
and the font flags.
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.
Lua Standard Libraries | Included |
---|---|
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 , 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 where name
can be any of the valid source names listed . 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 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, 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
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)
. Notice that a blank sticky switch STICKY(---, ---)
is ON when the model is first loaded. If you want a sticky switch to be OFF by default then use the value of the switch itself as V1
, i.e. L01 = STICKY(L01, ---)
will be off by default.
The following table gives an overview of the Lua API functions that can be used to exchange data with the EdgeTX model setup.
Color | Default value | Used for |
---|---|---|
You can find the meta-index of a particular switch source with , 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 as a true
/false
value.
Function | Description |
---|
package
-
coroutine
-
table
since OpenTX 2.3.8
(color LCD radios only)
since OpenTX 2.1.0 (with limitations)
os
-
string
since OpenTX 2.1.7
bit32
since OpenTX 2.1.0
math
since OpenTX 2.0.0
debug
-
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.
| Gets the meta-index of a value source with |
| Gets the current value of a source, where |
| Returns the current flight mode number and name. Do not confuse with |
| Read the value of a global variable between -1024 and 1024. You can use the above to get the current flioght mode for |
| Sets the value of a global variable. |
| 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. |
| Get the value of a logical switch. |
| Set the value of a |
| Get the meta-index of a switch source with |
| Get the name of a switch source. |
| Get the value of a switch source. |
| Get a list of physical switches on the radio. Can be used to make a selection list. |
| 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. |
| Set the value of a shared memory variable. |
| Set the timer to |
| Read the timer. |