################################################################################ # # 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 -set.xml: # # # ... other scripts here # # Aircraft/Generic/autochecklist.nas # # # # 2. In the -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: # # # # # ... more checklists here # # # 0 # 1 # # # 9 # # # # If you are using -tags in your checklist definition, you need # to add one 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: # # # # # nasal # # # # # 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 complete # Running checklists, please wait ... # Some checks failed. # 10 # 3 # # # # 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(); });