Hello everyone,
first of all, thanks to all those involved in FG development, all your work has meant so much to me!
I have enjoyed the A320 family a lot recently and my idea of an, at least very simplified, home cockpit came to my mind again. It's just so much nicer to press a physical button rather than clicking with my mouse
I know this is some kind of a lifetime project, but fully motivated I did some initial "feasibility studies" on this.
I took a look on how the buttons and lights are handled in the code (I use the A320-family that can be installed from FG 2020.3.13's launcher btw.), e.g. for the overhead panel.
From my understanding, the "lightened buttons" are done using animation/select XML tags. I have not found an explicit verification on this topic in the docs, but the code shows quite clearly that using multiple selects on one element "AND"s the necessary conditions. This is used in the code to light up a switch if it is actually triggered *and* powered. So you can find e.g.:
- Code: Select all
<animation>
<type>select</type>
...
<object-name>BlueElecPump1F</object-name>
...
<condition>
<greater-than-equals>
<property>systems/electrical/bus/ac-ess-shed</property>
<value>110</value>
</greater-than-equals>
</condition>
</animation>
Which says: for "Blue Electric Pump Fail light to light up, ac-ess-shed must be >110V".
You will also find:
- Code: Select all
<animation>
<type>select</type>
<object-name>BlueElecPump1F</object-name>
<condition>
<or>
<property>systems/hydraulic/lights/blue-elec-fault</property>
<property>controls/switches/annun-test</property>
</or>
</condition>
</animation>
Saying: "Light it up only if there is a 'blue elec fault' or if 'annun-test' is pressed". My experience in the FG A320 cockpit says: The power state AND one of the conditions here must be met
That means, there is a lot of the lighting-logic coded in the "model view XML" (what's the actual name of this?). While, for a home cockpit, the PropertyTree can be accessed quite easily, I'm at least not aware of a way to access these view states externally. Therefore I could read out the affected input values (
systems/electrical/bus/ac-ess-shed,
systems/hydraulic/lights/blue-elec-fault and
controls/switches/annun-test in this case), but I would have to re-write the complete logic behind it again.
Furthermore there is not only the "showing the lights", but also the actions when a button is pressed. In my example, this is quite easy:
- Code: Select all
<animation>
<name>Blue Elec Pump pick</name>
<type>pick</type>
<object-name>BlueElecPump1</object-name>
<object-name>BlueElecPump2</object-name>
<object-name>BlueElecPump1F</object-name>
<object-name>BlueElecPump2O</object-name>
<action>
<button>0</button>
<repeatable>false</repeatable>
<binding>
<command>property-toggle</command>
<property>controls/hydraulic/switches/blue-elec</property>
</binding>
<binding>
<command>nasal</command>
<script>libraries.pushbutton();</script>
</binding>
</action>
</animation>
When one of the elements (BlueElecPump1/2 and the corresponding OFF and FAULT) are clicked, toggle the '....switches/blue-elec' property and run the pushbutton(); nasal function (I think this only generates the click sound). The property-toggle could be done quite simply from an external program.
Anyway, we have a lot more complexity for some buttons. E.g. see the APU starter:
- Code: Select all
<animation>
<name>APU starter pick</name>
<type>pick</type>
<object-name>...</object-name>
<action>
<button>0</button>
<repeatable>false</repeatable>
<binding>
<condition>
<or>
<greater-than-equals>
<property>systems/electrical/bus/dc-bat</property>
<value>25</value>
</greater-than-equals>
</or>
</condition>
<command>nasal</command>
<script>
if (systems.APUNodes.Controls.master.getBoolValue()) {
systems.APUController.APU.startCommand();
}
</script>
</binding>...
What I see here is: If the battery DC voltage is >=25V and "systems.APUNodes.master" run the APU.startCommand(). I don't see any way to do what is done here from an external script accessing the PropertyTree without writing a more-or-less clone of the FG aircraft's nasal code in an external application.
So re-writing code and have the more or less same functionality implemented multiple times is definitely not the best attempt. So my idea was, to put all what's going on into PropertyTree nodes:
- The "light up" state of any switch (or: the LEDs of any switch)
- The "pressed" state of any button
And put all the logic behind it into some nasal script. In my example this would result in the following code:
The "animation/select" tag in the XML file:
- Code: Select all
<animation>
<type>select</type>
<object-name>BlueElecPump1F</object-name>
<condition><property>/paneliotest/view/BlueElecPump1F</property></condition>
</animation>
(i.e. the "Blue Elec Pump Fault" is shown, when /paneliotest/view/BlueElecPumpt1F property is true)
The "animation/pick" tag in the XML file:
- Code: Select all
<animation>
<type>pick</type>
<name>Blue Elec Pump pick</name>
<object-name>BlueElecPump1</object-name>
<object-name>BlueElecPump2</object-name>
<object-name>BlueElecPump1F</object-name>
<object-name>BlueElecPump2O</object-name>
<action>
<repeatable>false</repeatable>
<button>0</button>
<binding>
<command>property-assign</command>
<property>/paneliotest/trigger/BlueElecPump_b0</property>
<value>1</value>
</binding>
<mod-up><binding>
<command>property-assign</command>
<property>/paneliotest/trigger/BlueElecPump_b0</property>
<value>0</value>
</binding></mod-up>
</action>
</animation>
(i.e. while "Blue Elec Pump Button" is pressed, set /paneliotest/trigger/BlueElecPump_b0 to 1, on release, set it to 0)
In my nasal script, there would be some listeners to be added. For the properties triggering the view state:
- Code: Select all
setlistener("systems/electrical/bus/ac-ess-shed", setView_BlueElecPump1F);
setlistener("systems/hydraulic/lights/blue-elec-fault", setView_BlueElecPump1F);
setlistener("controls/switches/annun-test", setView_BlueElecPump1F);
... and for the button pressing:
- Code: Select all
setlistener("/paneliotest/trigger/BlueElecPump_b0", setTrigger_BlueElecPump_b0);
... and of course, these functions need to be implemented:
- Code: Select all
var setView_BlueElecPump1F = func{
if ((getprop("systems/electrical/bus/ac-ess-shed") >= 110) and ((getprop("systems/hydraulic/lights/blue-elec-fault")) or (getprop("controls/switches/annun-test")))) {
setprop("/paneliotest/view/BlueElecPump1F", 1);
} else {
setprop("/paneliotest/view/BlueElecPump1F", 0);
}
}
...
var setTrigger_BlueElecPump_b0 = func {
if (getprop("/paneliotest/trigger/BlueElecPump_b0")) {
if (getprop("controls/hydraulic/switches/blue-elec")) {
setprop("controls/hydraulic/switches/blue-elec", 0);
} else {
setprop("controls/hydraulic/switches/blue-elec", 1);
}
libraries.pushbutton();
}
}
In case of the APU starter, the nasal function for the trigger would look like this:
- Code: Select all
var setTrigger_APUStartBtn_b0 = func {
if (getprop("/paneliotest/trigger/APUStartBtn_b0")) {
if( (((getprop("systems/electrical/bus/dc-bat") >= 25))) ) {
if (systems.APUNodes.Controls.master.getBoolValue()) {
systems.APUController.APU.startCommand();
}}
libraries.pushbutton();
}
}
The nice thing about this is: Any button pressing can be triggered using the PropertyTree (just set it to true if pushed and false if released), any "light" state can also be accessed and read out. The complete logic is within the (new) nasal script and only needs to be implemented (and maintained) once.
Re-writing all this to generate the code would be a major PITA. And I'm a lazy person. So for my feasibility test I wrote some quick&dirty python test script to save some manual work. You can give it one or multiple XML input files from your models directory and it will try to create new code for selects and pick animations (only pick at this point, anything else gets more complex), generate one (or multiple) nasal files and new XML files. By replacing the original XML files by the generated ones and including the nasal code the affected buttons will be handled as mentioned above.
If you're interested, you can find the script here:
https://kirschfx.de/xml-nas-codegen-1.0.zipThe produced XML is, unfortunately, a bit messy without any indenting. The nasal tries to match the A320-family's coding standards a little for readability.
Command line argument "-h" will show a help.
You can select between producing one large nasal file from all input XML (standard) or with -s create one .nas file per XML file.
Animation/Pick tags with multiple actions are skipped (i.e. remain unchanged) normally, but with -m parameter, they are included and functions are generated for each action. This is, because multi-action picks usually implement some sort of rotating switch, where the triggering model I used might not be ideal.
If you use the "-d" argument, any pressing/releasing a button action will be printed out on your console.
I tried the script and its results with my A320-family's OHpanel.xml and a320.flightdeck.xml and (to my suprise
) it generally still seems to work. It does not "find" every button (might be, because some of them are not "pick"s), it does still include some elements where the model is not perfect (i.e. the engine cutoff switches) and there is definitely an issue with buttons that have covers, but from a quick first test the plane does not seem to be broken completely. If you're interested and you don't want to run the python script above, for your convenience you can find the resulting .xml/.nas files here:
https://kirschfx.de/fga320-test.zipSo, with so many words being written, I can finally come to my questions
- Does this approach make sense at all or is there a much simpler way that I have missed?
- And if so: Would this "model" be something that might be worth implementing into the A320's official sources? I know, there would still be a lot of manual fitting to do - it's more whether the general concept makes sense and does not have any unwanted side effects (I think of performance at least)
If you made it up to here: Thanks for reading and I'm glad to hear your comments on this.
Best wishes,
Oli