1
0
Fork 0
fgdata/Nasal/FailureMgr/public.nas
Anton Gomez Alvedro c108f3b988 Bugfixes and improvements to the Failure Manager
- Fix: runtime exception in remove_failure_mode()
  - Fix: keep failure & trigger status on teleport.
  - Fix: allow random failures from the gui to be enabled/disabled multiple times.
  - Fix: mcbf/mtbf are set to zero when they fire, so they can be reactivated from the gui.
  - Fix: string casts of several trigger types had syntax errors.
  - Usability: screen messages related to failures now use positive logic:
         "condition 100%" instead of "failure level 0%"
  - Performance: Time triggers now use internal timers, instead of requiring being polled.
  - Reviewed Trigger interface for more rational usage. reset() is replaced by arm()/disarm()
  - Added a subscription interface to listen to FailureMgr events.
  - Added an internal log buffer to keep a record of relevant events and present them to gui elements.
  - Several usability improvements to the FailureMgr Nasal API.
2014-12-21 12:39:52 +01:00

332 lines
9.3 KiB
Text

# 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,
};