1
0
Fork 0
fgdata/Nasal/FailureMgr/private.nas

328 lines
8.4 KiB
Text
Raw Normal View History

# Failure Manager implementation
#
# Monitors trigger conditions periodically and fires failure modes when those
# conditions are met. It also provides a central access point for publishing
# failure modes to the user interface and the property tree.
#
# 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.
##
# Represents one way things can go wrong, for example "a blown tire".
var FailureMode = {
##
# 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.
new: func(id, description, actuator) {
return {
parents: [FailureMode],
id: id,
description: description,
actuator: actuator,
_path: nil
};
},
##
# Applies a certain level of failure to this failure mode.
# level: Floating point number in the range [0, 1] zero being no failure
# and 1 total failure.
set_failure_level: func(level) {
me._path != nil or
die("FailureMode.set_failure_level: Unbound failure mode");
setprop(me._path ~ me.id ~ "/failure-level", level);
},
##
# Internal version that actually does the job.
_set_failure_level: func(level) {
me.actuator.set_failure_level(level);
me._log_failure(sprintf("%s failure level %d%%",
me.description, level*100));
},
##
# Returns the level of failure currently being simulated.
get_failure_level: func me.actuator.get_failure_level(),
##
# Creates an interface for this failure mode in the property tree at the
# given location. Currently the interface is just:
#
# path/failure-level (double, rw)
bind: func(path) {
me._path == nil or die("FailureMode.bind: mode already bound");
var prop = path ~ me.id ~ "/failure-level";
props.globals.initNode(prop, me.actuator.get_failure_level(), "DOUBLE");
setlistener(prop, func (p) me._set_failure_level(p.getValue()), 0, 0);
me._path = path;
},
##
# Remove bound properties from the property tree.
unbind: func {
me._path != nil and props.globals.getNode(me._path ~ me.id).remove();
me._path = nil;
},
##
# Send a message to the logging facilities, currently the screen and
# the console.
_log_failure: func(message) {
print(getprop("/sim/time/gmt-string") ~ " : " ~ message);
if (getprop(proproot ~ "/display-on-screen"))
screen.log.write(message, 1.0, 0.0, 0.0);
},
};
##
# Implements the FailureMgr functionality.
#
# It is wrapped into an object to leave the door open to several evolution
# approaches, for example moving the implementation down to the C++ engine,
# or supporting several independent instances of the failure manager.
# Additionally, it also serves to isolate low level implementation details
# into its own namespace.
var _failmgr = {
timer: nil,
update_period: 10, # 0.1 Hz
failure_modes: {},
pollable_trigger_count: 0,
init: func {
me.timer = maketimer(me.update_period, func me._update());
setlistener("sim/signals/reinit", func me._on_reinit());
props.globals.initNode(proproot ~ "display-on-screen", 1, "BOOL");
props.globals.initNode(proproot ~ "enabled", 1, "BOOL");
setlistener(proproot ~ "enabled",
func (n) { n.getValue() ? me._enable() : me._disable() });
},
##
# Subscribe a new failure mode to the system.
# mode: FailureMode object.
add_failure_mode: func(mode) {
contains(me.failure_modes, mode.id) and
die("add_failure_mode: failure mode already exists: " ~ id);
me.failure_modes[mode.id] = { mode: mode, trigger: nil };
mode.bind(proproot);
},
##
# Remove a failure mode from the system.
# id: FailureMode id string, e.g. "systems/pitot"
remove_failure_mode: func(id) {
contains(me.failure_modes, id) or
die("remove_failure_mode: failure mode does not exist: " ~ mode_id);
var trigger = me.failure_modes[id].trigger;
if (trigger != nil)
me._discard_trigger(trigger);
me.failure_modes[id].unbind();
props.globals.getNode(proproot ~ id).remove();
delete(me.failure_modes, id);
},
##
# Removes all failure modes from the system.
remove_all: func {
foreach(var id; keys(me.failure_modes))
me.remove_failure_mode(id);
},
##
# Attach 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.
set_trigger: func(mode_id, trigger) {
contains(me.failure_modes, mode_id) or
die("set_trigger: failure mode does not exist: " ~ mode_id);
var mode = me.failure_modes[mode_id];
if (mode.trigger != nil)
me._discard_trigger(mode.trigger);
mode.trigger = trigger;
if (trigger == nil) return;
trigger.bind(proproot ~ mode_id);
trigger.on_fire = func me.on_trigger_activated(trigger);
trigger.reset();
if (trigger.requires_polling) {
me.pollable_trigger_count += 1;
if (me.enabled() and !me.timer.isRunning)
me.timer.start();
}
trigger.enable();
},
##
# Returns the trigger object attached to the given failure mode.
# mode_id: FailureMode id string, e.g. "systems/pitot"
get_trigger: func(mode_id) {
contains(me.failure_modes, mode_id) or
die("get_trigger: failure mode does not exist: " ~ mode_id);
return me.failure_modes[mode_id].trigger;
},
##
# Observer interface. Called from asynchronous triggers when they fire.
# trigger: Reference to the calling trigger.
on_trigger_activated: func(trigger) {
var found = 0;
foreach (var id; keys(me.failure_modes)) {
if (me.failure_modes[id].trigger == trigger) {
me.failure_modes[id].mode.set_failure_level(1);
found = 1;
break;
}
}
found or die("FailureMgr.on_trigger_activated: trigger not found");
},
##
# Enable the failure manager.
_enable: func {
foreach(var id; keys(me.failure_modes)) {
var trigger = me.failure_modes[id].trigger;
trigger != nil and trigger.enable();
}
if (me.pollable_trigger_count > 0)
me.timer.start();
},
##
# Suspends failure manager activity. Pollable triggers will not be updated
# and all triggers will be disabled.
_disable: func {
me.timer.stop();
foreach(var id; keys(me.failure_modes)) {
var trigger = me.failure_modes[id].trigger;
trigger != nil and trigger.disable();
}
},
##
# Returns enabled status.
enabled: func {
getprop(proproot ~ "enabled");
},
##
# Poll loop. Updates pollable triggers and applies a failure level
# when they fire.
_update: func {
foreach (var id; keys(me.failure_modes)) {
var failure = me.failure_modes[id];
if (failure.trigger != nil and !failure.trigger.fired) {
var level = failure.trigger.update();
if (level > 0 and level != failure.mode.get_failure_level())
failure.mode.set_failure_level(level);
}
}
},
##
# Detaches a trigger from the system.
_discard_trigger: func(trigger) {
trigger.disable();
trigger.unbind();
if (trigger.requires_polling) {
me.pollable_trigger_count -= 1;
me.pollable_trigger_count == 0 and me.timer.stop();
}
},
##
# Reinit listener. Sets all failure modes to "working fine".
_on_reinit: func {
foreach (var id; keys(me.failure_modes)) {
var failure = me.failure_modes[id];
failure.mode.set_failure_level(0);
if (failure.trigger != nil) {
me._discard_trigger(failure.trigger);
failure.trigger = nil;
}
}
}
};
##
# Module initialization
var _init = func {
removelistener(lsnr);
_failmgr.init();
# Load legacy failure modes for backwards compatibility
io.load_nasal(getprop("/sim/fg-root") ~
"/Aircraft/Generic/Systems/compat_failure_modes.nas");
}
var lsnr = setlistener("/nasal/FailureMgr/loaded", _init);