333 lines
10 KiB
Text
333 lines
10 KiB
Text
################################################################################
|
|
#
|
|
# Automated Checklists
|
|
#
|
|
# Copyright (c) 2015, Richard Senior
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
# MA 02110-1301, USA.
|
|
#
|
|
################################################################################
|
|
#
|
|
# This script runs the bindings in a sequence of checklists. Typically used
|
|
# to implement autostart or shutdown but can run any checklist sequence at any
|
|
# time. Checklist execution runs each binding where the condition is not yet
|
|
# satisfied and waits until the condition becomes true before moving on. If
|
|
# the condition does not become true within a timeout period, the checklist
|
|
# execution fails.
|
|
#
|
|
# Can also run in no wait mode where items are run in order without waiting
|
|
# for the previous condition to become true. Checklists never timeout or fail
|
|
# in no wait mode but some conditions may remain false.
|
|
#
|
|
# Typical usage:
|
|
#
|
|
# 1. Add this script to <Aircraft>-set.xml:
|
|
#
|
|
# <nasal>
|
|
# ... other scripts here
|
|
# <autochecklist>
|
|
# <file>Aircraft/Generic/autochecklist.nas</file>
|
|
# </autochecklist>
|
|
# </nasal>
|
|
#
|
|
# 2. In the <Aircraft>-set.xml, define a list of checklist indexes to run
|
|
# as a named sequence.
|
|
#
|
|
# For example, to run checklists with indexes 0 and 1 for startup and
|
|
# checklist 9 for shutdown:
|
|
#
|
|
# <checklists>
|
|
# <checklist include="Checklists/before-starting-engines.xml"/>
|
|
# <checklist include="Checklists/start-engines.xml"/>
|
|
# ... more checklists here
|
|
# <checklist include="Checklists/parking.xml"/>
|
|
# <startup>
|
|
# <index n="0">0</index> <!-- Before starting engines -->
|
|
# <index n="1">1</index> <!-- Start engines -->
|
|
# </startup>
|
|
# <shutdown>
|
|
# <index n="0">9</index> <!-- Parking -->
|
|
# </startup>
|
|
# </checklists>
|
|
#
|
|
# If you are using <group>-tags in your checklist definition, you need
|
|
# to add one <group-index> per sequence.
|
|
#
|
|
# 3. Define a menu item that calls the complete_checklists function with
|
|
# the name of the checklist sequence you would like to run.
|
|
#
|
|
# For example:
|
|
#
|
|
# <item>
|
|
# <label>Autostart</label>
|
|
# <binding>
|
|
# <command>nasal</command>
|
|
# <script>autochecklist.complete_checklists("startup");</script>
|
|
# </binding>
|
|
# </item>
|
|
#
|
|
# For a no wait checklist execution (which will never fail), pass zero (0)
|
|
# as the second argument:
|
|
#
|
|
# autochecklist.complete_checklists(sequence:"startup", wait:0);
|
|
#
|
|
# 4. Optionally, configure automated execution using the following properties.
|
|
# See comments within the autochecklist_init function for a description.
|
|
#
|
|
# <checklists>
|
|
# ...
|
|
# <auto>
|
|
# <completed-message>Checklists complete</completed-message>
|
|
# <startup-message>Running checklists, please wait ...</startup-message>
|
|
# <timeout-message>Some checks failed.</timeout-message>
|
|
# <timeout-sec>10</timeout-sec>
|
|
# <wait-sec>3</wait-sec>
|
|
# <auto>
|
|
# </checklists>
|
|
#
|
|
# Note that messages are not displayed for no wait execution.
|
|
#
|
|
################################################################################
|
|
|
|
var checklists = nil;
|
|
var auto = nil;
|
|
var active = nil;
|
|
var expedited = nil;
|
|
var timeout_sec = nil;
|
|
var timeout_start = nil;
|
|
var wait_sec = nil;
|
|
var completed_message = nil;
|
|
var startup_message = nil;
|
|
var timeout_message = nil;
|
|
|
|
var autochecklist_init = func()
|
|
{
|
|
# Root property tree path for checklists
|
|
#
|
|
checklists = props.globals.getNode("sim/checklists");
|
|
|
|
# Root property tree path for auto checklists
|
|
#
|
|
auto = checklists.initNode("auto");
|
|
|
|
# Flag to indicate that checklists are being completed automatically.
|
|
# This can be used in Nasal bindings in the checklists to suppress a
|
|
# binding when automated execution is in progress, e.g. for checklist
|
|
# items that display dialogs.
|
|
#
|
|
active = auto.initNode("active", 0, "BOOL");
|
|
|
|
# Flag to indicate that checklist execution is expedited, i.e. there
|
|
# is no wait time between items. Typically indicates an in-air start. Note
|
|
# that the expedited flag does not imply automated checklists are active.
|
|
#
|
|
expedited = auto.initNode("expedited", 0, "BOOL");
|
|
|
|
# Timeout for completion of a checklist item. If the previous condition
|
|
# is still not satisifed after this timeout, the checklist fails.
|
|
#
|
|
timeout_sec = auto.initNode("timeout-sec", 10, "INT");
|
|
timeout_start = auto.initNode("timeout-start", 0.0, "DOUBLE");
|
|
|
|
# If the previous checklist item is not complete, the process waits
|
|
# this number of seconds before trying again.
|
|
#
|
|
wait_sec = auto.initNode("wait-sec", 3, "INT");
|
|
|
|
# Messages
|
|
#
|
|
completed_message = auto.initNode("completed-message",
|
|
"Checklists complete."
|
|
);
|
|
|
|
startup_message = auto.initNode("startup-message",
|
|
"Running checklists, please wait ..."
|
|
);
|
|
|
|
timeout_message = auto.initNode("timeout-message",
|
|
"Some checks failed. Try completing checklist manually."
|
|
);
|
|
}
|
|
|
|
################################################################################
|
|
|
|
# Announces a message to the pilot
|
|
#
|
|
# @param message: the message to display
|
|
#
|
|
var announce = func(message)
|
|
{
|
|
setprop("sim/messages/copilot", message);
|
|
logprint(3, message);
|
|
}
|
|
|
|
# Resets the timestamp used for checking timeouts
|
|
#
|
|
var reset_timeout = func()
|
|
{
|
|
timeout_start.setValue(0.0);
|
|
}
|
|
|
|
# Return true if the timeout period has been exceeded
|
|
#
|
|
var timed_out = func()
|
|
{
|
|
var elapsed = getprop("sim/time/elapsed-sec") - timeout_start.getValue();
|
|
return timeout_start.getValue() and elapsed > timeout_sec.getValue();
|
|
}
|
|
|
|
# Waits for the completion of an item, setting a timestamp for timeouts
|
|
#
|
|
# @param node: the node containing the list of checklist indexes to run
|
|
# @param from: the checklist item node from which to start
|
|
#
|
|
var wait_for_completion = func(node, from)
|
|
{
|
|
var t = maketimer(wait_sec.getValue(), func {
|
|
complete(node, 1, from);
|
|
});
|
|
t.singleShot = 1;
|
|
t.start();
|
|
|
|
if (!timeout_start.getValue()) {
|
|
timeout_start.setValue(getprop("sim/time/elapsed-sec"));
|
|
}
|
|
}
|
|
|
|
# Automatically complete a set of checklists defined by a series of indexes
|
|
# listed under the node argument. Not intended to be called from outside this
|
|
# script.
|
|
#
|
|
# @param node: the node containing the list of checklist indexes to run
|
|
# @param wait: whether to wait for the preceding binding to complete
|
|
# @param from: the checklist item node from which to start, default nil
|
|
#
|
|
var complete = func(node, wait, from = nil)
|
|
{
|
|
var previous_condition = nil;
|
|
var skipping = from != nil;
|
|
|
|
if( node.getChild("group-index") != nil ){
|
|
var checklists_node = checklists.getChild("group", node.getChild("group-index").getIntValue());
|
|
} else {
|
|
var checklists_node = checklists;
|
|
}
|
|
|
|
foreach (var index; node.getChildren("index")) {
|
|
var checklist = checklists_node.getChild("checklist", index.getValue());
|
|
if( size( checklist.getChildren("page") ) > 0 ){
|
|
foreach( var page; checklist.getChildren("page") ){
|
|
foreach (var item; page.getChildren("item")) {
|
|
var condition = item.getNode("condition");
|
|
if (skipping) {
|
|
if (!item.equals(from)) {
|
|
previous_condition = condition;
|
|
continue;
|
|
}
|
|
skipping = 0;
|
|
}
|
|
if (wait) {
|
|
if (props.condition(previous_condition)) {
|
|
reset_timeout();
|
|
} else {
|
|
if (timed_out()) {
|
|
var title = checklist.getNode("title").getValue();
|
|
announce(title~": "~timeout_message.getValue());
|
|
} else {
|
|
wait_for_completion(node: node, from: item);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (!props.condition(condition)) {
|
|
foreach (var binding; item.getChildren("binding")) {
|
|
active.setValue(1);
|
|
props.runBinding(binding);
|
|
active.setValue(0);
|
|
}
|
|
}
|
|
previous_condition = condition;
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
foreach (var item; checklist.getChildren("item")) {
|
|
var condition = item.getNode("condition");
|
|
if (skipping) {
|
|
if (!item.equals(from)) {
|
|
previous_condition = condition;
|
|
continue;
|
|
}
|
|
skipping = 0;
|
|
}
|
|
if (wait) {
|
|
if (props.condition(previous_condition)) {
|
|
reset_timeout();
|
|
} else {
|
|
if (timed_out()) {
|
|
var title = checklist.getNode("title").getValue();
|
|
announce(title~": "~timeout_message.getValue());
|
|
} else {
|
|
wait_for_completion(node: node, from: item);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (!props.condition(condition)) {
|
|
foreach (var binding; item.getChildren("binding")) {
|
|
active.setValue(1);
|
|
props.runBinding(binding);
|
|
active.setValue(0);
|
|
}
|
|
}
|
|
previous_condition = condition;
|
|
}
|
|
}
|
|
|
|
}
|
|
if (wait) {
|
|
announce(completed_message.getValue());
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
|
|
# Complete a checklist sequence, typically called from a Nasal binding in a
|
|
# menu, e.g. Autostart, but could be assigned to a controller button or even
|
|
# called from a listener.
|
|
#
|
|
# @param sequence: the name of the checklist sequence to run
|
|
# @param wait: whether to wait for the preceding binding, default 1 (true)
|
|
#
|
|
var complete_checklists = func(sequence, wait = 1)
|
|
{
|
|
var node = checklists.getNode(sequence);
|
|
if (node != nil) {
|
|
if (wait) {
|
|
announce(startup_message.getValue());
|
|
}
|
|
expedited.setValue(!wait);
|
|
complete(node, wait);
|
|
} else {
|
|
announce("Could not find checklist sequence called '"~sequence~"'");
|
|
}
|
|
}
|
|
|
|
setlistener("/sim/signals/fdm-initialized", func() {
|
|
autochecklist_init();
|
|
});
|
|
|