-
Notifications
You must be signed in to change notification settings - Fork 0
2.2 Level II : read & show telemetry
Now, I hope it’s getting really interesting for you.
Building on the very first widget, I want to expand its functionality to display two sensor values instead of static text.
The layout should no longer be "hardcoded" for specific displays.
The final result should look like this:

This introduces a new level of difficulty with topics such as:
- How to read telemetry (or, more generally, "values from a source")
- How to handle different displays/transmitter types
- How to refresh the screen
Each of these points may initially seem trivial, but they are essential for all widgets.
In the past, there were many examples of how easily one can fall into pitfalls with these topics, leading to strange widget behavior or significantly reduced transmitter performance.
I will first address each of these points separately before diving into the "complete widget."
Displaying telemetry values individually is certainly one of the main use cases for Lua widgets.
Here’s a few pages of explanation on the topic.
In general, Ethos Lua doesn’t distinguish whether you want to "query" telemetry sensors, physical switches, analog inputs, trims, logical switches, or anything else.
Lua treats these in an "object-oriented" way as a "source" (the object) with properties (attributes).
- Properties can be names, but also values.
- When querying a telemetry value, two steps are necessary:
- Step 1: Define the source ONCE in the entire script (by assigning it to a variable).
- Step 2: Query the source for its value whenever needed.
Important: Each source should be defined only once throughout the entire runtime!
But what is the correct method?
We remember that possible sources include analog inputs, switches, sensors, etc.
In the guide, you’ll find the corresponding entry under system.

Don’t be confused by the many examples.
The first example is actually quite poor and should never be used that way, as a logical switch of the same name could exist, leading to ambiguity about which source is returned.
Typically, you specify the name-category or "member" pair.
The member indicates where in the category list the source lies.
In the base section, you can get a good overview of the available categories.
- Our desired value, RSSI, is generated by the receiver as a telemetry sensor, and thus belongs to the
CATEGORY_TELEMETRY_SENSORcategory. - The transmitter voltage is "internal" and comes from
CATEGORY_SYSTEM.
Thus, I define the following two sources:
srcValue1 = system.getSource({category=CATEGORY_TELEMETRY_SENSOR, name="RSSI"})
srcValue2 = system.getSource({category=CATEGORY_SYSTEM, member=SYSTEM_MAIN_VOLTAGE})(Example code to follow later in the widget)
Where do I place the definition within the widget?
Remember: Sources should only be defined once.
If it’s clear at program startup which sources will be used, it’s best to use the create handler for this, as it is only called once when the widget starts.
If you dynamically determine which sources to query during runtime, you can check in the wakeup handler whether the source variable is already defined (not nil) and assign the source only once.
By the way, nil is Lua terminology meaning "nothing" or "empty."
A simple declaration like local test = nil is possible. The variable will be assigned a value later but already "exists" in the local address space/namespace.
One of the most common mistakes is defining the source in the wakeup handler on every run (or at least multiple times) and then querying it.
local srcRSSI =system.getSource(„RSSI“)
local rssiValue = srcRSSI:value()Or even more elegantly, all in one line:
local rssiValue = system.getSource(„RSSI“):value()One reason you might see something like this is that many oTx users have switched to Ethos and may think they can replicate the query style they’re used to.
In openTx, it was possible to query a simple telemetry value in one line, for example, using getValue(source).
This might quickly lead someone to pack source definition and value querying into one line.
Ethos is not openTx and indeed works differently in some areas.
Here, the object-oriented approach in Ethos becomes clear:
- You define the object once (in this case, a sensor).
- Then you query the desired attribute, such as the value, using
value().
An attribute can also be the name, and you’d get the name back with source:name(), and so on.
Often, an argument (value) can be included within the parentheses, which "writes" the corresponding value to the attribute.
This is implemented uniformly and cross-functionally in Lua.
If you assign a source to a variable multiple times (worst-case in every wakeup call), Lua has to locate this source in its "index," which costs a fair amount of CPU time.
Furthermore, it can happen that the "old" memory area of the "previous" source isn’t immediately freed up but only after several dozen loops.
So with each wakeup loop, the available memory becomes less until finally the "cleanup crew," or the so-called "garbage collector," clears the memory.
In the first small widget, we hardcoded variables for layout parameters, such as x/y coordinates for placement, and had to activate certain ones depending on the display.
How can we support different display resolutions or even different widget sizes without changing the code?
Several tools are available for this.
A relatively simple way is to query the widget size (x & y in pixels) and set positions in relation to it, for example, at 0.2 times the widget width.
The method for this is lcd.getWindowSize(), which returns the widget’s width and height.
The size can already be queried in the create handler.
local w, h = lcd.getWindowSize()w = width h = height
A line like this: ```lua Lcd.drawText( w*0.1, h*0.2 , „TestText“) ```
would always set the text in relation to the widget size.
Some widgets are intended to be displayed in Fullscreen mode, taking up the entire screen.
In such cases, using the lcd.getWindowSize() method makes sense. However, you can also indirectly query the display by checking the transmitter type.
For this, the system.getVersion() method exists.
This method returns a table with various elements.
The "board" element specifically returns a string representing the transmitter hardware, such as "X20", "X18", "X12".
A small function to query the transmitter hardware could look like this:
local DISP_X20 <const> = 1
local DISP_X18 <const> = 2
local DISP_HORUS <const> = 3
function evaluate_display()
local detectSys = system.getVersion()
if detectSys.board == "X12" or detectSys.board == "X10EXPRESS" then return(DISP_HORUS) end
if detectSys.board == "X18S" or detectSys.board == "X18" then return(DISP_X18) end
return(DISP_X20)
endFirst, I define constants with "self-explanatory names."
A function retrieves the board type and returns the corresponding constant.
Now, I can create a multidimensional table, for example. The table could store positions for different objects, like text, values, or images, depending on screen (or widget) sizes.
local masterLayout = {
{hLine=46, offset=2, tabTxt=180, tabValue=290}, -- x20
{hLine=28, offset=0, tabTxt=100, tabValue= 80}, -- x18
{hLine=20, offset=1, tabTxt=100, tabValue= 80}, -- horus
}Here, I define a table named "masterLayout" and store different values for line height (hLine), y-offsets for positions (offset), and the horizontal positions for text and values (tabTxt, tabValue) for different display types.
The way tables are defined might not be immediately obvious to beginners, so here’s a brief explanation of Lua tables:
Let’s take a small detour to explore tables in Lua.
I assume at least a basic understanding of what a multidimensional table is.
When discussing multidimensional tables, the terms "matrix" or "array" are often used.
Most people are familiar with programs like Excel, which are used for editing two-dimensional tables (rows and columns).
A table has multiple entries in each dimension.
- A specific element in a two-dimensional Ethos table could be accessed as follows:
value = table[row][column]For three dimensions (and so on), it would look like this:
value = table[row][column][indexDimension3]However, any element of a table cannot simply be assigned a value!
For a one-dimensional table (a "list"), the table is initialized like this, after which values can be assigned:
local array = {}
array[1] = 15
array[2] = 17For a two-dimensional table, multiple initializations are necessary, for example, for a table with 3 "rows" and 2 "columns":
local array = {}
for i= 1,3 do
array[i] ={}
array[i][1] = i*10
array[i][2] = i*10+1
endAnother way to directly initialize would be through nesting:
local array = {
{10,11},
{20,21},
{30,31}
}The result would be the same; the specific use case dictates the approach.
In my opinion, it’s also worth considering that you’ll want to be able to read and understand your code long after it was initially written, so personal preferences can also play a role.
Now it’s getting really interesting:
In Lua, tables are "associative arrays." Clear as day, right?
This means that indices don’t just have to be numbers, as shown above; they can also be text labels.
local array = {}
array[„height“] = 12 -- height
array[„width“] = 40 –- width
print(„Height:“, array[„height“])The array element doesn’t get a numerical index but is identified by a text label:
You can achieve this with even less code by assigning the label and its values directly during initialization:
local array = {
height = 12,
width = 40
}
print(„Höhe:“, array[„height“])Now, let’s make this multidimensional, with two "rows," each assigning different height and width values:
local array = {
{height= 12, width = 40},
{height= 20, width = 60},
}
print(„height 1:“, array[1][„height“])
print(„height 2:“, array[2][„height“])
Why this?
This can significantly improve code readability, especially when using the concise syntax where the element label is separated by a dot.
In the first example above, you could modify the print statement to get the same result as follows:
print(„height:“, array.height )Somehow better, right?
Remember "my" layout definition?
local masterLayout = {
{hLine=46, offset=2, tabTxt=180, tabValue=290}, -- x20
{hLine=28, offset=0, tabTxt=100, tabValue= 80}, -- x18
{hLine=20, offset=1, tabTxt=100, tabValue= 80}, -- horus
}What value do you think this function outputs?
print( masterLayout[DISP_X20].offset ) Would you be able to "see" as quickly what’s hidden in the middle of the code if the array was generated purely with numerical indices, like this?
print( masterLayout[1][2] ) Finally, here are two links from the official Lua documentation that also explain these concepts: