Skip to content

2.2 Level II : read & show telemetry

Udo edited this page Nov 20, 2024 · 17 revisions

1. The Task:

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:
image



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."



2. One of the Most Important Lua Topics: Sensor & Source Values

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).

Key Points:

  • Properties can be names, but also values.
  • When querying a telemetry value, two steps are necessary:
    1. Step 1: Define the source ONCE in the entire script (by assigning it to a variable).
    2. Step 2: Query the source for its value whenever needed.



Defining a Source or Sensor:

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.

image

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.



Proper Method:

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_SENSOR category.
  • 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)

and now, which handler?

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.



The Typical "Source Error"

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.



Why is this wrong? Why should the source only be defined once in Ethos Lua?

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.




3. Working with Different Displays & Resolutions

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.


1. Querying Widget Size

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.


Code Example

    local w, h = lcd.getWindowSize()

w = width h = height

A line like this:

   Lcd.drawText( w*0.1,  h*0.2   , „TestText“)

would always set the text in relation to the widget size.



2. Querying Transmitter Type (Display Type)

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)
end

First, 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:


Tables (Arrays) in Lua

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!

Clone this wiki locally