LUA 502 – Beginning Model Scripting.
Your instructor: Isaac Davis (Fig Newton on RCGroups)
Getting Started
All right class, now that we have the basic concept of what a lua script does and what they are capable of, let’s get the party started. There are plenty of courses and classes on programming so I won’t spend much time going over the basics of coding unless it’s something I feel needs to be brought up. I have been coding for over 30 years now and will not claim to be an expert at it. There are so many languages to learn and becoming proficient takes time. I have a couple of languages that I can say I was extremely proficient in at one time, but I don’t think that is the case anymore. So don’t worry about your skill level. We are all just getting started with this, unless you have been programming in lua, and then I have to wonder why you are spending time in this class.
In this course we are going to cover Model Scripting. Remember? There are two kinds of lua scripts: one time and modeling. We’re talking about model scripts right now.
The first thing I need to mention is that there are certain rules that you need to follow for a model script to be a “model” script. I am going to quote the model script wiki verbatim as it is important and in a big font. “Do not use Lua model scripts for controlling any aspect of your model that could cause a crash if script stops executing.” Now what does that mean exactly? It means that if you wanted to, you could write a lua script to take an input from, say a telemetry value like altitude, and use that to calculate something crazy like the throttle. The warning is saying that this is not a good idea. The main reason is that if the model script uses too much cpu or memory, it will be shut down and not run again while this model is selected. You remember this from the last class, right? So, don’t drive any servos using a value you are generating with a lua script unless you can afford to have that servo stop working at any given time.
Even though I just finished saying I wasn’t going to cover programming, I think that we need to explain the basic layout of a lua script. There are subroutines that are basic and possibly even required for your lua script to work. A subroutine is just as it sounds: A block of code that can be executed and bundled together to form a routine. The four subroutines are defined as follows: Input, Output, Init, and Run. You must define these four functions/subroutines (Definition, see above) in a return statement (Definition) at the end of your script. Since the entire lua script you are writing is basically a subroutine that is called by the main loop of the radio’s code, it needs to know what to expect. Let’s go over and explain the four parts of the script.
Input: This is how the script gets information passed to it. I will use my gps heading lua as an example. I wanted to give the user the ability to adjust some basic parameters that the script follows. I used a couple of inputs to allow this to happen. For another script out there, the input might be a channel that the user can select, or maybe a telemetry value that can be set up. Input is optional and not required.
Output: This defines the up to seven variables that the script is allowed to return. At this moment it’s strictly numbers that can be returned. But don’t let that stop you from getting clever and returning something like a 1 for true and a 0 for false. This is also optional and not required.
Init: This is where we go in and initialize the script. We set initial values for variables, and do any prep work. The key here is to note that this subroutine is run when the script is initially turned on and only the one time. It will not be run again for the life of the script. Now to avoid confusion, what I mean by life of the script is this particular time it is run. Init will be called every time you select this model or turn the radio on and this model is active. This too is optional and not required.
Run: Now here’s where we get to the meat and potatoes of the lua script. The other parts, while might be necessary for your script aren’t where the real grunt work is done. The run subroutine is it. This is the same as a main() routine in languages like java or c++. And like those other languages it can be passed input. Input is a variable or set of variables containing values. In my GPS heading script, the values that the user can set are passed to the run subroutine via input variables. You will want the input to match the input you defined above. This routine will also return any of the output you have defined above.
Writing Your First Script
So, let’s get started with a simple Hello World script. For this script we want to get a value from something like the elevator input. Let’s keep it simple and have the script output that same value, and also the negative of the value. So, that’s one input and two outputs. We don’t need an init in this case as we are keeping this simple, but we could initialize a variable if we wanted. Let’s keep it simple for now. Are you familiar with a variable’s scope? If not, we might be able to cover some of that in another class. Don’t worry about it. For now, we can keep the scope of the variables inside the run routine.
Fire up your favorite text editor. I am using gedit (https://wiki.gnome.org/Apps/Gedit) on a linux machine. It seems to format the code once you save it and it recognizes the .lua extension. Formatting makes the code look nice and helps to make it easier to follow. Once you have your editor up, you want to start out by putting in some comments. Remember, comments are lines ‘not to be acted upon’ by the script. Comment lines start with two dashes and are there only to provide informational notes concerning the program. Annotating your scripts is considered one of the biggies in scripting best practices. Any line with two initial dashes will be ignored by the lua interpreter and not run as I mentioned above.
Here’s an example:
— Hello World, Taranis style
— by {your name here} 6/29/2014
— This script will allow the user to define an input and then return
— the same input and also the inverse.
Notice that we do not have to end our comments. The end of the line denotes the end of the comment.
Now lets go ahead and define the input that we need. This will be pretty simple. We pick one input and on the screen it will say “Your Val” and allow the user to select an input. There is another type of input where we can specify a value and allow the user to pick from a range of values like 1-10, or 5-25. We won’t use that for this simple example. Take note of the formatting here. The braces are used to encapsulate the entire block of input (that’s the outer set of braces). And there is also a set of braces used to encapsulate the input you are defining (the inner set that is around our input). The format for this input also specifies that the text we want displayed is enclosed in quotes, and that we also tell it we want this to be a SOURCE type variable.
local inputs = { {“Your Val”, SOURCE } }
Now that we have the input defined, it’s time to get some output set up. In this case we are going to return two values. Formatting specifies that our output variables are enclosed in braces, and each output variable is enclosed in quotes and separated by a comma. First we define the original value, and second is the inverse. Another note here, you are only allowed four characters for the name of your output and a max of seven outputs, so plan this out wisely. In this case we will have the script return Val and Inv. Pretty simple but remember, this is only hello world..
local outputs = { “Val”, “Inv” }
So now we have an input value and some output. It’s time to get our main loop of code written. This is the part that tells the script what to do with our input and output that we defined in the previous lines.
First we need to pass the input to the run subroutine. We use that in the main statement. At this point we can give it a variable name that will be used in the routine. I am using yourval for this simple example. The function run_sub has to have a declaration statement, which is the first line of the subroutine. It starts with ‘local function’ and then has the name of the function. In this case it’s ‘run_sub’ and next it has any input variables enclosed in parenthesis ‘(yourval)’. That is a properly declared subroutine.Next I put in a comment that explains the subroutine.
Now we get down to the nitty gritty. We are defining a new variable invval, and also having it calculated on the same line. In this case we are taking the inverse of yourval by multiplying by -1. After that’s done, it’s just a matter of returning these two values, which is done with the return statement. The return command does exactly that, leaves your subroutine and passes along the variables you have specified. In this particular case, OpenTX takes the output and displays it on the screen in the custom script page for your script as you will see below.
One thing to note in your return statement is that the values returned need to be in the same order as you have defined above in the output. In this case the input value goes first and the inverse second.
local function run_sub( yourval)
— Return: We are going to get the negative of the input value and return both
local invval = yourval*-1
return yourval, invval
end
It’s just that simple right? If you answered yes, then you are unfortunately wrong. We have one last little bit of code to write. Remember above where I mentioned that you have to define the routines used in a return statement at the end of the code? Well that’s what we haven’t done yet. But not to worry, it’s pretty simple.
Let’s see, we have some input, some output, and a run subroutine.
— Return statement
return { run=run_sub, output=outputs, input=inputs}
That’s it. Now what?
Well, we need to save it somewhere. Let me take a second here to point out that it would actually be a lot safer if you were saving this as we went along. If you have been working with computers for any amount of time, you will learn quickly that it’s best to save often.
Now, since we are working on a model script, we need to place this on the SD card in the transmitter in a sub-directory called: /SCRIPTS/MIXES. If you’re wanting to just test this using OpenTX Companion, you can save it in the correct folder under your SD card image that you created on your computer. You DID create an exact image of your SD card on your computer and indicated its path in OpenTX Companion, didn’t you? I used the name helloworld.lua for this script .
Once it’s saved in the /SCRIPTS/MIXES subdir, you can then go to your model on your Taranis, or run the simulator in OpenTX Companion.
Making OpenTX Lua Friendly
OpenTX is unique in some ways in that you have the ability to custom build the firmware to included only what you want it to contain. For instance, those who don’t fly helicopopiters don’t have to include the heli screen. With Lua, it’s the same way but different. With helicopters, you specifically tell OpenTX to NOT include it in the download. With Lua, you have to tell it to ADD it to the firmware download. Once you do that, flashing OpenTX is exactly the same as how you normally do it.
Including Lua in OpenTX Download
- Start OpenTX Companion on your desktop.
- Either: Click the “Settings” icon or click Settings –> Settings in the menu bar:
- Check to see that the “Radio Profile” tab is selected and most importantly, that “OpenTX for FrSky Taranis” is selected in the Radio Type box. You should see something like the following:
- The above screen is how I have mine set. Note that I choose NOT to include helicopter information (noheli is checked), and I’ve asked to include lua (lua is checked). The other options are yours to command depending upon your preference.
- Assuming that everything is as you like it, click: “OK.”
- Ok, Heads Up! This may be new for some of you. Look at the toolbar. You’ll see an icon with a blue down-pointing arrow. On mine it’s the sixth one from the left.
- Click it. You’ll be rewarded with the following screen, indicating the version and the specific build parameters you’re about to download. How do I know that? Look just to the right of the word Firmware. See the line? In my image it says: opentx-taranis-nohili-lua-en. That translates as: OpenTX firmware for the Taranis without helicopter information, but with lua included, all in English. See? This stuff ain’t rocket science!
- When you’re satisfied that you’re getting what you want, click “OK.”
- OpenTX Companion will use the filename you just saw and want to ask to save the download in your default download location for Taranis firmware. Unless you have an overwhelming need, accept the download location.
- The download will finish, and you’ll be given a chance to flash the firmware. You’ve done this before, so you’re on your own from here!
Creating the /SCRIPTS/MIXES Folder on the SD card!
As of 06/30/2014, the current version of OpenTX is v2.0.5. It DOES NOT automatically create a place to keep your lua scripts on your SD card. If you’ve been upgrading your firmware, then you may recall that you had to create a folder called FIRMWARES to store your on-board copy of the firmware. This serves as a home for the firmware files that you download and place there if you wished to have the ability to upgrade your firmware from the TX without having to use OpenTX Companion. You must do much the same thing here, except that you need to create a directory/sub-directory pair.
If you’re into scripting at all, then you probably already know how to do this in your sleep. However, for those of us who don’t sleep well, I’ll try and provide some abbreviated procedures.
- The objective is to create a folder and a sub-folder on the SD card of your Taranis. This can be done most easily using whatever file management utility your operating system uses. In Windows, it’s Windows File Explorer. That’s what we’ll use.
- You can access the SD card either directly by removing the card from your Taranis and inserting it into a micro-SD card reader, or by turning on your TX in Boot Loader mode and plugging it into your computer via a USB cable, just like updating your firmware when using OpenTX Companion.
- Once you’ve either got the card plugged into an active card reader or connected to your computer via USB, open Windows File Explorer and navigate to your SD card. If all is cool, you’ll see two drives originating from your transmitter. One is labeled TARANIS. DO NOT TOUCH THAT ONE! If you do, very, very, VERRRYY bad things will happen to you. The other drive will be listed as a removable drive. On my computer, it’s Drive H, but your experience will vary depending upon how many devices you’ve got crammed into your machine. In this case, I’ve really fouled you up, because I’ve renamed mine TARANIS1_SD. You see, I have two Tarani, and….well, it’s a long story. Yours will probably be labeled simply Removable Drive, or something similar.
- Double-click the Removable Drive (TARANIS1_SD in this case). Note that there is no SCRIPTS folder. You have to create it.
- Right click the drive label (Removable Drive), select New à Folder, and name the folder SCRIPTS. Yes, capitalization counts!
- YEA! You’ve got the structure created and ready for use. When you create your lua scripts, save them to the MIXES folder.
- If you’re using a clone of the SD card on your computer, be sure to sync the computer and TX card contents! My preferred method is to check and make sure that everything is in the MIXES folder of the computer, then while in File Explorer to drag the MIXES folder from the computer to the transmitter. DON”T go from the transmitter to the computer!
OK, back to the ballgame.
Selecting and Running Your Lua Script
Remember, you’ve already created the lua script and saved it in the SCRIPTS/MIXES sub-directory. Now you want to assign it to a model and get it to run.
I know you already know this, but just in case I’ve confused you totally out of your mind already, to get to the model setup page that you want to go to, you:
- Turn on your transmitter
- Clear any warning screens
- Short Press (i.e.: tap) “MENU”
- Navigate to the model you wish to work with
- Short Press “PAGE” to enter that model’s Setup Screen
- Once you are at the Model page, Short Press (move forward) or Long Press (move backward) the PAGE button until you get to the Custom Scripts page.
- Now that you are at the custom scripts page, click the Enter button and you should see the helloworld script available like this. Note: I have other scripts in this folder and can choose which one I want to set up as LUA1.
- Highlight the helloworld script, press ENT, and you will be taken to the script setup page.
- Notice you have Inputs on the left hand side, and Outputs on the right hand side. Do they look familiar? They should. Now notice the outputs are 0. Believe it or not your script is running right now, but since you haven’t defined an input the values returned are 0 and -0. So scroll down to the input Your Val and click the Enter button. Press “ENT” to select it, and then press the “+” or “-” button to select an input. Let’s pick something simple like elevator.
- Hmm, the value of the output didn’t change did it? Well, what’s the current value of your elevator? If you aren’t jumping ahead here, the stick should be centered and yup, you guessed it, the value is 0. So start moving the elevator and watch your script at work.
So how about that? As you move your elevator up and down, the value is returned along with its inverse.
Here is what the entire script looks like put together. No cheating and copying this into your text editor.
— Hello World, Taranis style
— by Super Duper Student 6/29/2014
— This script will allow the user to define an input and then return
— the same input and also the inverse.
local inputs = { {“Your Val”, SOURCE } }
local outputs = { “Val”, “Inv” }
local function run_sub( yourval )
— Return: We are going to get the negative of the input value and return both
local invval = yourval*-1
return yourval, invval
end
— Return statement
return { run=run_sub, output=outputs, input=inputs}
With that I am going to call it a wrap. Your homework is going to be to reverse the values giving back the inverse as the first output and the actual value as output number two. You can also do some other math on the variable to see how it changes it up. Try adding in an output variable and returning the input value times two.
Additional Resources
Just like the last class, here are some links to help you:
- There is a fair amount of discussion on Lua scripting but you have to dig for it: http://www.rcgroups.com
- The Lua Scripting thread on RCGroups: http://www.rcgroups.com/forums/showthread.php?t=2180477
- This will be the official Lua script docs for OpenTX: https://github.com/opentx/opentx/wiki/Lua-scripting-in-OpenTX
- The Wikipedia entry on Lua programming: http://en.wikipedia.org/wiki/Lua_%28programming_language%29
- The official Lua website. Couldn’t leave this one out: http://www.lua.org/”