All pages
Powered by GitBook
1 of 4

Loading...

Loading...

Loading...

Loading...

Advanced Topics

The advanced topics section covers file I/O, data sharing, and debugging techniques

Debugging techniques

Debugging your code before testing

A good editor is key

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.

Checking if the code can be compiled

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.

Ready to run the code

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

The lua debug viewer

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.

Using a script loader

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.

Notes

Script Loader

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.

Speed/memory optimizaton tricks

Faster getValue()

Normally one uses 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 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")

check for the desired functionality

  • restart this cycle

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

    local foo = getValue("bar")
    local my_id = getFieldInfo("bar").id    -- here we get the numerical id of the filed "bar"
    
    local function run_a_lot()
      local my_value = getValue(my_id)      -- exactly the same effect as local my_value = getValue("bar"), but faster
    end
    getValue()
    getFieldInfo(name)
    local fileToLoad="/SCRIPTS/TELEM/telem1.lua"
    local active = true
     
    local thisPage={}
    local page={}
     
    local function clearTable(t)
      if type(t)=="table" then
        for i,v in pairs(t) do
          if type(v) == "table" then
            clearTable(v)
          end
          t[i] = nil
        end
      end
      collectgarbage()
      return t 
    end
    
    thisPage.init=function(...)
      if active then
        page=dofile(fileToLoad)
        page.init(...)
      end
      return true
    end
     
    thisPage.background=function(...)
      if active then
        page.background(...)
      end
      return true
    end
     
    thisPage.run=function(...)
      if active then
        page.run(...)
        active= not (...==EVT_ENTER_BREAK)
      else
        lcd.drawText( 15, 2, fileToLoad, 0 )
        lcd.drawText( 15, 20, "disabled", 0 )
        lcd.drawText( 15, 40,"press enter-button to activate",0)
        clearTable(page)
        active= (...==EVT_ENTER_BREAK)
        thisPage.init()
      end
      return not (...==EVT_MENU_BREAK)  
    end
     
    return thisPage

    Lua data sharing across scripts

    Overview:

    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.

    Lua scoping rules:

    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.

    Example:

    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.

    Installation:

    • count-dn.lua

      • copy to /SCRIPTS/MIXES

      • configure on the transmitter CUSTOM SCRIPT page

        • suggested switch = "SA"

    Script sources:

    count-dn.lua

    count-up.lua

    shocount.lua

    suggested mins = 3

  • suggested sw_high = 0

  • screen image:

    count-dn.lua mix script
  • 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:

      shocount.lua function script
  • -- these globals can be referenced in function and telemetry scripts
    gCountUp = 0
    gCountDown = 0
    
    local target
    local running = false
    local complete = false
    local announcements = { 720, 660, 600, 540, 480, 420, 360, 300, 240, 180, 120, 105, 90, 75, 60, 55, 50, 45, 40, 35, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    local annIndex -- index into the announcements table (1 based)
    local minUnit -- used by playNumber() for unit announcement
    
    local input =
        {
            { "switch", SOURCE},        -- switch used to activate count down
            { "mins", VALUE, 1, 12, 2 },    -- minutes to count down
            { "sw_high", VALUE, 0, 1, 1 } -- 0 = active when low, otherwise active when high
        }
    
    local output = {"ctup", "ctdn" }     
    
    local function init()
      local version = getVersion()
      if version < "2.1" then
        minUnit = 16  -- unit for minutes in OpenTX 2.0
      elseif version < "2.2" then
        minUnit = 23  -- unit for minutes in OpenTX 2.1
      else
        minUnit = 25  -- unit for minutes in OpenTX 2.2
      end
    end
    
    local function countdownIsRunning(switch, sw_high)
      -- evaluate switch - return true if we should be counting down
      if (sw_high > 0) then
        return (switch > -1000)
      else
        return (switch < 1000)
      end
    end
    
    local function run(switch, mins, sw_high)
      local timenow = getTime() -- 10ms tick count
      local minutes
      local seconds
    
      if (not countdownIsRunning(switch, sw_high)) then
          running = false
          complete = false
          return 0, 0 -- ***** NOTE: early exit *****
      end
    
      if (complete) then
        return 0, 0 -- must reset the switch before we go again
      end
    
      if (not running) then
        running = true
        target = timenow + ((mins * 60) * 100)
        annIndex = 1
      end
    
      gCountDown = math.floor(((target - timenow) / 100) + .7) --  + is adj. to for announcement lag
      gCountUp = (mins * 60) - gCountDown
    
      while gCountDown < announcements[annIndex] do
        annIndex = annIndex + 1 -- catch up
      end
    
      if gCountDown == announcements[annIndex] then
        minutes = math.floor(gCountDown / 60)
        seconds = gCountDown % 60
        if minutes > 0 then
          playNumber(minutes, minUnit, 0)
        end
        if seconds > 0 then
          playNumber(seconds, 0, 0)
        end
        annIndex = annIndex + 1
      end
    
      if gCountDown <= 0 then
        playNumber(0,0,0)
        running = false
        gCountDown = 0
        complete = true
      end
    
      return gCountUp * 10.24, gCountDown * 10.24
    end
    
    return { input=input, output=output, init=init, run=run }
    gCountUp = 0
    
    local min = 5
    local max = 30
    local last = 0
    local announcements = { 5, 10, 15, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29 }
    local annIndex = 1
    
    local function run(e)
      if not (gCountUp == last) then
        last = gCountUp
        for key, value in pairs(announcements) do
            if value == last then
              playNumber(last, 0, 0)
            end
        end    
      end
    end
    
    return{run=run}
    -- these globals can be referenced in mix, function, and telemetry scripts
    gCountUp = 0
    gCountDown = 0
    
    local function run(e)
      lcd.clear()
      lcd.drawText(1,1,"OpenTx Lua Data Sharing",0)
    
      lcd.drawText(1,11,"gCountUp:", 0)
      lcd.drawText(lcd.getLastPos()+2,11,gCountUp,0)
      lcd.drawText(1, 21, "gCountDown:", 0)
      lcd.drawText(lcd.getLastPos()+2,21,gCountDown,0)
    end
    
    return{run=run}
    count-up.lua function script