# Failure Manager public interface # # Copyright (C) 2014 Anton Gomez Alvedro # # 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. var proproot = "sim/failure-manager/"; ## # Nasal modules can subscribe to FailureMgr events. # Each event has an independent dispatcher so modules can subscribe only to # the events they are interested in. This also simplifies processing at client # side by being able to subscibe different callbacks to different events. # # Example: # # var handle = FailureMgr.events["trigger-fired"].subscribe(my_callback); var events = {}; # Event: trigger-fired # Format: { mode_id: <failure mode id>, trigger: <trigger that fired> } events["trigger-fired"] = globals.events.EventDispatcher.new(); ## # Encodes a pair "category" and "failure_mode" into a "mode_id". # # These just have the simple form "category/mode", and are used to refer to # failure modes throughout the FailureMgr API and to create a path within the # sim/failure-manager property tree for the failure mode. # # examples of categories: # structural, instrumentation, controls, sensors, etc... # # examples of failure modes: # altimeter, pitot, left-tire, landing-light, etc... var get_id = func(category, failure_mode) { return sprintf("%s/%s", string.normpath(category), failure_mode); } ## # Returns a vector containing: [category, failure_mode] var split_id = func(mode_id) { return [string.normpath(io.dirname(mode_id)), io.basename(mode_id)]; } ## # Subscribe a new failure mode to the system. # # id: Unique identifier for this failure mode. # eg: "engine/carburetor-ice" # # description: Short text description, suitable for printing to the user. # eg: "Ice in the carburetor" # # actuator: Object implementing the FailureActuator interface. # Used by the failure manager to apply a certain level of # failure to the failure mode. var add_failure_mode = func(id, description, actuator) { _failmgr.add_failure_mode( FailureMode.new(id, description, actuator)); } ## # Returns a vector with all failure modes in the system. # Each vector entry is a hash with the following keys: # { id, description } var get_failure_modes = func() { _failmgr.get_failure_modes(); } ## # Remove a failure mode from the system. # id: FailureMode id string, e.g. "systems/pitot" var remove_failure_mode = func(id) { _failmgr.remove_failure_mode(id); } ## # Removes all failure modes from the failure manager. var remove_all = func { _failmgr.remove_all(); } ## # Attaches a trigger to the given failure mode. Discards the current trigger # if any. # # mode_id: FailureMode id string, e.g. "systems/pitot" # trigger: Trigger object or nil. Nil will just detach the current trigger var set_trigger = func(mode_id, trigger) { _failmgr.set_trigger(mode_id, trigger); } ## # Returns the trigger object attached to the given failure mode. # mode_id: FailureMode id string, e.g. "systems/pitot" var get_trigger = func(mode_id) { _failmgr.get_trigger(mode_id); } ## # Applies a certain level of failure to this failure mode. # # mode_id: Failure mode id string. # level: Floating point number in the range [0, 1] # Zero represents no failure and one means total failure. var set_failure_level = func(mode_id, level) { setprop(proproot ~ mode_id ~ "/failure-level", level); } ## # Returns the current failure level for the given failure mode. # mode_id: Failure mode id string. var get_failure_level = func(mode_id) { getprop(proproot ~ mode_id ~ "/failure-level"); } ## # Restores all failure modes to level = 0 var repair_all = func { _failmgr.repair_all(); } ## # Returns a vector of timestamped failure manager events, such as the # messages shown in the console when there are changes to the failure conditions. # # Each entry in the vector has the following format: # { time: <time stamp>, message: <event description> } var get_log_buffer = func { _failmgr.logbuf.get_buffer(); } ## # Allows applications to disable the failure manager and restore it later on. # While disabled, no failure modes will be activated from the failure manager. var enable = func setprop(proproot ~ "enabled", 1); var disable = func setprop(proproot ~ "enabled", 0); ## # Encapsulates a condition that when met, will make the failure manager to # apply a certain level of failure to the failure mode it is bound to. # # Two types of triggers are supported: pollable and asynchronous. # # Pollable triggers require periodic check for trigger conditions. For example, # an altitude trigger will need to poll current altitude until the fire # condition is reached. # # Asynchronous trigger do not require periodic updates. They can detect # the firing condition by themselves by using timers or listeners. # Async triggers must call the inherited method on_fire() to let the Failure # Manager know about the fired condition. # # See Aircraft/Generic/Systems/failures.nas for concrete examples of triggers. var Trigger = { type: nil, # 1 for pollable triggers, 0 for async triggers. requires_polling: 0, enabled: 0, new: func { return { parents: [Trigger], params: {}, armed: 0, fired: 0, ## # Async triggers shall call the on_fire() callback when their fire # conditions are met to notify the failure manager. on_fire: func 0, _path: nil }; }, ## # Forces a check of the firing conditions. Returns 1 if the trigger fired, # 0 otherwise. update: func 0, ## # Returns a printable string describing the trigger condition. to_str: func "undefined trigger", ## # Modify a trigger parameter. Parameters will take effect after the next # call to reset() set_param: func(param, value) { assert(me._path != nil, "Trigger.set_param: unbound trigger"); contains(me.params, param) or die("Trigger.set_param: undefined param: " ~ param); setprop(sprintf("%s/%s",me._path, param), value); }, ## # Load trigger parameters and reset internal state. Once armed, the trigger # will fire as soon as the right conditions are met. It can be called after # the trigger fires to rearm it again. # # The "armed" condition survives enable/disable calls. arm: func { assert(me._path != nil, "Trigger.arm: unbound trigger"); setprop(me._path ~ "/armed", 1); }, _arm: func { foreach (var p; keys(me.params)) me.params[p] = getprop(sprintf("%s/%s", me._path, p)); me.fired = 0; me.armed = 1; }, ## # Deactivate the trigger. The trigger will not fire until rearmed again. disarm: func { assert(me._path != nil, "Trigger.disarm: unbound trigger"); setprop(me._path ~ "/armed", 0); }, _disarm: func { me.armed = 0; }, ## # Enables/disables the trigger. While a trigger is disabled, any timer # or listener that could potentially own shall be disabled. # # The FailureMgr calls these methods when the entire system is # enabled/disabled. By keeping enabled/disabled state separated from # armed/disarmed allows the FailureMgr to keep its configuration while # disabled, i.e. those triggers that where armed when the system was # disabled will resume when the system is enabled again. # # The FailureMgr disables itself during a teleport. enable: func { me.enabled = 1 }, disable: func { me.enabled = 0 }, ## # Creates an interface for the trigger in the property tree. # Every parameter in the params hash will be exposed, in addition to # a path/reset property for resetting the trigger from the prop tree. bind: func(path) { assert(me._path == nil, "Trigger.bind: trigger already bound"); me._path = path; props.globals.getNode(path) != nil or props.globals.initNode(path); props.globals.getNode(path).setValues(me.params); var prop = path ~ "/armed"; props.globals.initNode(prop, 0, "BOOL"); setlistener(prop, func(p) { p.getValue() ? me._arm() : me._disarm() }, 0, 1); }, ## # Removes this trigger's interface from the property tree. unbind: func { props.globals.getNode(me._path ~ "/armed").remove(); foreach (var p; keys(me.params)) props.globals.getNode(me._path ~ "/" ~ p).remove(); me._path = nil; } }; ## # FailureActuators encapsulate the actions required for activating the actual # failure simulation. # # Traditionally this action was just manipulating a "serviceable" property # somewhere, but the FailureActuator gives you more flexibility, allowing you # to touch several properties at once or call other Nasal scripts, for example. # # See Aircraft/Generic/Systems/failure.nas and # Aircraft/Generic/Systems/compat_failures.nas for some examples of actuators. var FailureActuator = { ## # Called from the failure manager to activate a certain level of failure. # level: Target level of failure [0 to 1]. set_failure_level: func(level) 0, ## # Returns the level of failure that is currently being simulated. get_failure_level: func 0, };