Hi fellow wiki editors!

To help newly registered users get more familiar with the wiki (and maybe older users too) there is now a {{Welcome to the wiki}} template. Have a look at it and feel free to add it to new users discussion pages (and perhaps your own).

I have tried to keep the template short, but meaningful. /Johan G


Jump to: navigation, search

Writing Joystick Code: Part 3

1,864 bytes added, 10:04, 5 December 2015
no edit summary
{{JoystickScripting Navigation}}
== Special snippets ==
To toggle the Parking brake, use this. Entire code-block shown. Non-repeatable button. (You have to specifically set <repeatable> to true if you want repeatable. It is assumed to be false.)<syntaxhighlight lang="xml">
<button n="1">
<desc>Toggle parking break</desc>
This method can be used to toggle any property that is on/off. Use Debug - Browse Internal Properties to find suitable candidates.
Also see getProp, setProp and setAll below.
This method can be used to toggle any property that is on/off. Use Debug - Browse Internal Properties to find suitable candidates. Also see getProp, setProp and setAll below. Remember, it ''toggles'' the property, you can't use it to set the property into a specific state.
To make condition work with mixture on a lever (axis). This is pertinent to turboprops.
props.setAll("controls/engines/engine", "condition", getprop("controls/engines/engine/mixture"))
To make condition work with mixture on a non-repeatable buttonlever (axis). This is pertinent to turboprops. Change Make mixture richer NR <![CDATA[ if(getprop(pre style="controls/engines/engine/mixture") < 1 ) {white-space: pre-wrap; setprop("controls/engines/engine/mixture", getprop ("controls/engines/engine/mixture") + 0.05)white-space: -moz-pre-wrap; props.setAll(white-space: -pre-wrap;white-space: -o-pre-wrap; word-wrap: break-word"> controls/engines/engine", "condition", getprop.mixtureAxis("controls/engines/engine/mixture")) } ]]</pre> to Make mixture leaner NR <![CDATA[ if(getprop(pre style="controls/engines/engine/mixture") > 0 ) {white-space: pre-wrap; setprop("controls/engines/engine/mixture", getprop("controls/engines/engine/mixture") white-space: -moz- 0.05)pre-wrap; props.setAll("controls/engines/engine", "condition", getprop("controls/engines/engine/mixture"))white-space: -pre-wrap; }white-space: -o-pre-wrap; ]]word-wrap: break-word"> Make mixture richer R <![CDATA[ if(getprop("controls/engines/engine/mixture") < 1 ) { setprop.mixtureAxis("controls/engines/engine/mixture", getprop ("controls/engines/engine/mixture") + 0.001); props.setAll("controls/engines/engine", "condition", getprop("controls/engines/engine/mixture")) } ]]> Make mixture leaner R <![CDATA[ if(getprop("controls/engines/engine/mixture") pre> 0 ) { setprop("controls/engines/engine/mixture", getprop("controls/engines/engine/mixture") - 0.001) props.setAll("controls/engines/engine", "condition", getprop("controls/engines/engine/mixture")) } ]]>----------------------------------------------------------------------------
This is an alternative method to adjust a property
<step type="double">-2.0</step>
This will continously continuously reduce /sim/current-view/goal-pitch-offset-deg in steps of 2 while the button is held in.---------------------------------------------------------------------------------------------------------------------------------------------------------- 
=== if ... then ... else ===
Sometimes you only want to do something if a certain condition is true (or false.) In this case you use an if clause. The general form in most programming languages is if (condition) then action endif
We write : '''if''' '''('''condition''')''' '''then as {, and ''' action '''endif as }.'''
In Nasal, we write "then" as "{" and "endif" as "}". Each "action" (or statement) is terminated by a semicolon: <syntaxhighlight lang="nasal">if (condition){ # the opening curly brace means THEN action;} # the closing curly brace means ENDIF, i.e. end of this block</syntaxhighlight> Actions can be arbitrary Nasal expressions, including function calls and other conditional blocks. ==== An example ====Lets say that if x is less than 5 we want to add 2 to it. We write:<syntaxhighlight lang="nasal"> if (x < 5) { x = x + 2 ; }</syntaxhighlight>
The more readable way of writing it is
<syntaxhighlight lang="nasal">if (x < 5) { x = x + 2; }</syntaxhighlight> And we can even omit the braces if there's only one action:<syntaxhighlight lang="nasal">if (x < 5) # no opening brace... x = x + 2; # ...so the if statement ends at the first semicolon</syntaxhighlight>
If we also want to add 1 to y if the condition is true, we write
<syntaxhighlight lang="nasal">if (x < 5) { x = x + 2; y + = y + 1; }</syntaxhighlight>
A common way to abbreivate such simple expression, is using shorthand operators like: * +=* -=* *=* /= For example: <syntaxhighlight lang="nasal">if (x < 5) { x += 2; # means x = x + 2 y += 1; # means y = y + 1}</syntaxhighlight>  Now lets pretend that we still want to increase x by 2 if it less than 5, and if it is 5 or more we want to add 1 to it. We use else. It looks like this <syntaxhighlight lang="nasal">if (x < 5) { x = x + 2; } else { x = x + 1; }</syntaxhighlight>
Now lets say we have the following rules:
* If x < 5 add 2 to it * If x < 10 add 1 to it * If x is 10 or more add 7 to it. In this case elseif becomes useful.
In English our code would be
* If (x < 5) then (x = x + 2) else if (x < 10) then (x = x + 1). * If none of these are true then (x = x + 7). * End of if clause.
We test for x < 5 first. If it fails we test for x < 10. If x , 5 succeeds we do (x = x + 2) and then move on past the end of if clause. So we can test for x < 5 and x < 10 in this way.
Writing our code properly we get
<syntaxhighlight lang="nasal">if (x < 5) { x = x + 2; } elseif elsif (x < 10) { x = x + 1; } else { x = x + 7; }</syntaxhighlight>
You can use more than one elseifelsif, but it gets messy. If you have a number of tests then it is might be better to test for each case individually. Easier to understand each condition separately and much less chance of an error. Our above example would become <syntaxhighlight lang="nasal">if (x < 5) { x = x + 2; } if (x >= 5) and (x < 10) { x = x + 1; } if (x >= 10) { x = x + 7; }</syntaxhighlight>
Because each if is tested we need to have the (x >= 5) otherwise if x = 1 both the first and second if conditions would be satisfied.
But the advantage is that we are forced to write out each condition exactly as it should be tested. Easier to understand and easier to maintain.
==== Another example ====
Lets say that you want something to happen only if the gear is up. But the only property you can read is Gear-DownGearDown. Then you write <syntaxhighlight lang="nasal">if (!Gear-DownGearDown) { action; }</syntaxhighlight>
The ! means not. So it translates as: If the gear is not down perform action.
Beacuse we use < and > we will need to enclose it all with CDATA.
See the discussion of variables below for an example of the use of if.
=== getProp, setProp and setAll ===
To use a property you need its value. The best way to find out the path to a property is with Debug - Browse Internal Proprties.
Let us say we want to the current value of elevator-trim. From the menu select Debug and then Browse Internal Properties. Use the scroll-bar to go up and down the list and look at the options. One of them is controls. This is a good place to start, the elevator-trim is a control. Click on controls and inspect the new list of options.
One of them is flight. Good choice, the elevator-trim controls how the aircraft flies. Click on flight. Look at the list of variables displayed.
You will see elevator-trim = '0' (double) (It might not say 0.) The (double) means that it is a double-precision floating point number. This means that it can change in small amounts and has a decimal point with numbers after the decimal point. Make a note that it is a floating point number.
Look at the top of the dialog-box and you will see that it says /controls/flight. We can see that the variable is elevator-trim, so make a note that the path to the variable is /controls/flight/elevator-trim.
You can close the dialog-box.
Having got the current value we probably want to change it. To do that we use setProp. Lets pretend that the current value is 0.175 and we want to make it 0.200. We use
setProp("/controls/flight/elevator-trim", 0.200)
Elevator trim is a double (floating point) number. The range of values will be -1.000 through 0 to +1.000.
If the value is an integer (int) the range of values is ... -3, -2, -1, 0, 1, +2, +3 ... This doesn't mean that -3 is a suitable value for that specific property, it just means that you can pass -3 as a value without crashing the system.
Sometimes the value is a boolen (bool). This means that it is either true or false. Then you would use
You can also use 1 instead of "true" and 0 instead of "false". If you use getProp, you will get 0 and 1 as your value, not "true" and "false."
In multi-engined aircraft you can use throttleAxis() when you are adjusting the throttle with an axis. This will do all engines. But if you are using a button you need to set the throttle for all engines at the same time. To do this you need props.setAll(). It is a waste of time to try to take the throttles past maximum, so you first get the throttle setting, check if it is less than maximum, and if it is, increase it a bit. You end up with
if(getprop("controls/engines/engine/throttle") < 1 ) {
You need CDATa CDATA because of the less-than sign.
You can use this for throttle, propeller, mixture and condition.
BTW Here the props in props.setAll is short for property, not propellers.
If you are happy with a 1 second display, you can leave out the Time-to-display (and the comma before it.
The String-to-display can get hairy. To start off, choose one of your buttons (not the hat.) Let's say it is button number 3. Just before </script> add this line:
gui.popupTip(sprintf("Button 3 pressed"),3)
Don't forget to add a ; at the end of the previous line.
Delete the line and the ; from your xml file.
This was not very useful in itself. What we normally want to display is some property. A good mexample example is when adjusting a trim with a button - you have no idea where you are. You can use the HUD, but when you are transitioning to straight and level flight, it is much easier to remember a number from the last time, than the position of the trim on the HUD.
We are going to display the value of elevator-trim. See getProp, setProp and setAll above to learn how to get the current value.
Now we want to display it. For a start we will have "Elevator-trim: " followed by the value that we got. But it is a number ,so we need to tell sprintf that there is a number coming, so we change our string to "Elevator-trim: %". We still need to tell sprintf that it is a floating point number, so we use "Elevator-trim: %f". But we would like it accurate to 3 decimal places, so we use "Elevator-trim" %.3f". Now we will have "Elevator-trim: %.3f", getProp("/controls/flight/elevator-trim").
The line of code ends up as
Add the line to aileron-trim and rudder-trim (if you have it), not forgetting to change all occurrences of elevator to aileron and rudder.
We can make the display for elevator-trim a bit better. The elevator-trim control is usually a largish wheel that you turn. In the C172 it has a range of 9 turns. -4.5 to +4.5. Let's adjust our code to be similar. Our getProp returns -1.0 to +1.0, so if we multiply it by 4.5 we will get -4.5 to + 4.5. We will only need 2 decimal places, so the code becomes
gui.popupTip(sprintf("Elevator-trim: %.2f", 4.5 * getprop("/controls/flight/elevator-trim")))
interpolate(property-to-adjust, target-value, time-to-get-there-in-seconds)
An example of its use would be in using a non-repeatable button to adjust aileron-trim. You could make repetitive pushes of the button, but that is not satisfactory. Much easier to use interpolate. You want the aileron trim to go from center to max (1.0) in 15 seconds. the code is
interpolate("controls/flight/aileron-trim", 1, 15)
You give the button a tap and in 15 seconds time the aileron-trim is at 1.0.
But that was starting at 0. If the trim is at 0.5 when you start, you want it to get to max in half the time, 7.5 seconds. The trick is to get the current aileron-trim, subtract it from the max, thus giving you the change still needed, and use that to factor the time-to-get-there. This gives you
interpolate("controls/flight/aileron-trim", 1, (1 - getProp("controls/flight/aileron-trim") * 15)
All good and well, but if we are really using a non-repeatable button to adjust aileron-trim, we want to be able to stop the adjustment when it gets to a suitable value. The secret is to use <mod-up> to stop the action. To stop the action we use interpolate again, but this time we set the target to the current value, and the time-to-get-there as 0.
interpolate("controls/flight/aileron-trim", getProp("controls/flight/aileron-trim"), 0)
Now, when the button is pressed, interpolation starts. Just once, because it is a non-repeatable button. When we are happy with the aileron-trim we release the button and interpolation stops.
=== Variables ===
Lets say we want our buttons to behave in different ways, depending on whether the aircraft is on the ground, aircraft is in the air, aircraft is an helicopter. To do this we have a variable called mode, which has 3 values
mode = 1 Aircraft on ground
mode = 2 Aircraft in air
Now, each time a button is pressed, we get the value of mode and use if to decide which action the button must carry out.
Of course, we need some way to set the value of mode, say one of the buttons. Non-repeatable. Each time the button is clicked mode goes up 1 in value. If it is 3, then clicking takes it round to 1 again.
There are two ways of defining variables.
'''Method 1'''
<nowiki> <data>
<mode type="int">1</mode> # Initialise mode to 1
var self = cmdarg().getParent();
var data = self.getNode("data");
var mode = data.getNode("mode");
get_mode = func { mode.getValue() ; } ]]> </script>
var m # Used whenever mode is accessed </nowiki>
This goes right at the top of your xml file, just after </name>.
The code for the button (non-repeatable)that changes mode looks like this
var m = get_mode();
m = m + 1;
if (m == 4) {
When a button is pressed, the code looks like this
var m = get_mode();
if (m == 1) {
You have to have at <nowiki>--prop:</nowiki> in the command line.
You can't type <nowiki>--prop:</nowiki> in the comments section at the top of your file, because of the consecutive minus signs. You have to type &amp;minusminusprop: and say that "minus;&amp;minus;prop: " must be substituted with "-"
Whichever method you use, choose a better name than ''mode'' for your variable. It is too common a word and may well one day be used somewhere in the system files. In the xml file for the Saitek Yoke, where I use a variable to control the assignments of the levers on the quadrant, I call the variable ''SaitekMultiMode''. Unlikely to be used by the FG programmers, or anybody else.
=== perIndexAxisHandler ===
-----------------------------------------------------------------------------This only works with ''real'' axes.
You have a quadrant with 3 levers. Normally they are assigned to controls.throttleAxis(), controls.PropellerAxis() and controls.mixtureAxis().
Now let's say you are flying an aircraft with 2 propeller-engines, it has mixture control, but not propeller-feathering (or you are prepared to use buttons for this).
Now you want the first 2 levers to control the throttles of each engine independently,and the last to do the mixture of both. In this case you would
leave lever 3 assigned to controls.mixtureAxis(), but would use perIndexAxisHandler for the first 2.
It works like this: controls.perIndexAxisHandler(function, engine-number).
peraxisindexhandlerThe values for function are 0 Throttle 1 Mixture 2 Propeller
Engine-----------------------------------------------------------------------------number goes from 0 to 9.
So in our example, lever 1 would be assigned
and lever 2
Go back to See [[Writing Joystick Codehttp: Part 2]//forum.flightgear.org/viewtopic.php?f=24&t=17851 this post]for an example of using this.Go forward  You can assign a n axis to more than one engine. Let's say you have 4 engines. You only have 3 levers on the quadrant. You want thefirst lever to do the port-outer engine throttle , lever 3 to do the starboard-outer engine throttle, and the middle lever to do the two inboard engine throttles.So you need to assign the middle lever a list of engines.Remembering that engines are numbered from 0 upwards for furthest left towards the right, the lever assignments from left to right are controls.perIndexAxisHandler(0,0) controls.perIndexAxisHandler(0,[1, 2]) controls.perIndexAxisHandler(0,3)Notice the list of engine numbers between [Writing Joystick Code: Part 4]and ]for the middle lever.
Any complaints/suggestions/questions/kudos can be posted [http://forum.flightgear.org/forums/viewtopic.php?f=24&t=17892 here].

Navigation menu