5df7a784e5
Replaces existing Nasal/failures.nas script with a programmable failure manager. The failure manager allows dynammic creation and removal of failure modes, on demand activation and a flexible set of triggers. The public interface can be found in Nasal/FailureMgr/public.nas Aircraft/Generic/Systems/failures.nas provides a library of triggers and failure actuators ready to use for programming the failure manager. A compatibility layer is included under Aircraft/Generic/Systems/compat_failure_modes.nas. This compatibility layer is currently loaded on startup and programs the FailureMgr to emulate the former behavior (same set of failure modes and compatible interface through the property tree). This first milestone is only intended to replace the failure management engine underneeth with minimum visible changes, and hopefully no aircraft breakages. Future milestones will build upon this to add a Canvas based procedural GUI and example integration on aircrafts.
327 lines
8.4 KiB
Text
327 lines
8.4 KiB
Text
# 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);
|