1
0
Fork 0

Failure Management Framework (1st milestone)

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.
This commit is contained in:
Anton Gomez Alvedro 2014-05-13 20:31:35 +02:00 committed by Philosopher
parent 8b16a71d50
commit 5df7a784e5
8 changed files with 1188 additions and 285 deletions

View file

@ -0,0 +1,210 @@
# Compatibility failure modes
#
# Loads FailureMgr with the failure modes that where previously hardcoded,
# emulating former behavior and allowing backward compatibility.
#
# Copyright (C) 2014 Anton Gomez Alvedro
# Based on previous work by Stuart Buchanan, Erobo & John Denker
#
# 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.
io.include("Aircraft/Generic/Systems/failures.nas");
MTBF = 0;
MCBF = 1;
SERV = 0;
JAM = 1;
ENG = 2;
var compat_modes = [
# Instruments
{ id: "instrumentation/adf", type: MTBF, failure: SERV, desc: "ADF" },
{ id: "instrumentation/dme", type: MTBF, failure: SERV, desc: "DME" },
{ id: "instrumentation/airspeed-indicator", type: MTBF, failure: SERV, desc: "ASI" },
{ id: "instrumentation/altimeter", type: MTBF, failure: SERV, desc: "Altimeter" },
{ id: "instrumentation/attitude-indicator", type: MTBF, failure: SERV, desc: "Attitude Indicator" },
{ id: "instrumentation/heading-indicator", type: MTBF, failure: SERV, desc: "Heading Indicator" },
{ id: "instrumentation/magnetic-compass", type: MTBF, failure: SERV, desc: "Magnetic Compass" },
{ id: "instrumentation/nav/gs", type: MTBF, failure: SERV, desc: "Nav 1 Glideslope" },
{ id: "instrumentation/nav/cdi", type: MTBF, failure: SERV, desc: "Nav 1 CDI" },
{ id: "instrumentation/nav[1]/gs", type: MTBF, failure: SERV, desc: "Nav 2 Glideslope" },
{ id: "instrumentation/nav[1]/cdi", type: MTBF, failure: SERV, desc: "Nav 2 CDI" },
{ id: "instrumentation/slip-skid-ball", type: MTBF, failure: SERV, desc: "Slip/Skid Ball" },
{ id: "instrumentation/turn-indicator", type: MTBF, failure: SERV, desc: "Turn Indicator" },
{ id: "instrumentation/vertical-speed-indicator", type: MTBF, failure: SERV, desc: "VSI" },
# Systems
{ id: "systems/electrical", type: MTBF, failure: SERV, desc: "Electrical system" },
{ id: "systems/pitot", type: MTBF, failure: SERV, desc: "Pitot system" },
{ id: "systems/static", type: MTBF, failure: SERV, desc: "Static system" },
{ id: "systems/vacuum", type: MTBF, failure: SERV, desc: "Vacuum system" },
# Controls
{ id: "controls/flight/aileron", type: MTBF, failure: JAM, desc: "Aileron" },
{ id: "controls/flight/elevator", type: MTBF, failure: JAM, desc: "Elevator" },
{ id: "controls/flight/rudder", type: MTBF, failure: JAM, desc: "Rudder" },
{ id: "controls/flight/flaps", type: MCBF, failure: JAM, desc: "Flaps" },
{ id: "controls/flight/speedbrake", type: MCBF, failure: JAM, desc: "Speed Brake" },
{ id: "controls/gear", type: MCBF, failure: SERV, desc: "Gear", prop: "/gear", mcbf_prop: "/controls/gear/gear-down" }
];
##
# Handles the old failures.nas property tree interface,
# sending the appropriate commands to the new FailureMgr.
var compat_listener = func(prop) {
var new_trigger = func {
if (name == "mtbf") {
MtbfTrigger.new(value);
}
else {
var control = id;
forindex(var i; compat_modes) {
var mode = compat_modes[i];
if (mode.id == id and contains(compat_modes[i], "mcbf_prop")) {
control = mode.mcbf_prop;
break;
}
}
McbfTrigger.new(control, value);
}
};
var name = prop.getName();
var value = prop.getValue();
var id = string.replace(io.dirname(prop.getPath()), FailureMgr.proproot, "");
id = string.trim(id, 0, func(c) c == `/`);
if (name == "serviceable") {
FailureMgr.set_failure_level(id, 1 - value);
return;
}
if (name == "failure-level") {
setprop(io.dirname(prop.getPath()) ~ "/serviceable", value ? 0 : 1);
return;
}
# mtbf and mcbf parameter handling
var trigger = FailureMgr.get_trigger(id);
if (value == 0) {
trigger != nil and FailureMgr.set_trigger(id, nil);
return;
}
if (trigger == nil) {
FailureMgr.set_trigger(id, new_trigger());
}
else {
trigger.set_param(name, value);
trigger.reset();
}
}
##
# Called from the ramdom-failures dialog to set the global MCBF parameter
var apply_global_mcbf = func(value) {
foreach (var mode; compat_modes) {
mode.type != MCBF and continue;
setprop(FailureMgr.proproot ~ mode.id ~ "/mcbf", value);
}
}
##
# Called from the ramdom-failures dialog to set the global MTBF parameter
var apply_global_mtbf = func(value) {
foreach (var mode; compat_modes) {
mode.type != MTBF and continue;
setprop(FailureMgr.proproot ~ mode.id ~ "/mtbf", value);
}
}
##
# Discover aircraft engines dynamically and add a failure mode to the
# compat_modes table for each engine.
var populate_engine_data = func {
var engines = props.globals.getNode("/engines");
var engine_id = 0;
foreach (var e; engines.getChildren("engine")) {
var starter = e.getChild("starter");
var running = e.getChild("running");
(starter != nil and starter != "" and starter.getType() != "NONE")
or (running != nil and running != "" and running.getType() != "NONE")
or continue;
var id = "engines/engine";
if (engine_id > 0)
id = id ~ "[" ~ engine_id ~ "]";
var entry = {
id: id,
desc: "Engine " ~ (engine_id + 1),
type: MTBF,
failure: ENG
};
append(compat_modes, entry);
engine_id += 1;
}
}
##
# Subscribes all failure modes that the old failures.nas module did,
# and recreates the same property tree interface (more or less).
var compat_setup = func {
removelistener(lsnr);
populate_engine_data();
foreach (var m; compat_modes) {
var control_prop = contains(m, "prop") ? m.prop : m.id;
FailureMgr.add_failure_mode(
id: m.id,
description: m.desc,
actuator: if (m.failure == SERV) set_unserviceable(control_prop)
elsif (m.failure == JAM) set_readonly(control_prop)
else fail_engine(io.basename(control_prop)));
# Recreate the prop tree interface
var prop = FailureMgr.proproot ~ m.id;
var n = props.globals.initNode(prop ~ "/serviceable", 1, "BOOL");
setlistener(n, compat_listener, 0, 0);
setlistener(prop ~ "/failure-level", compat_listener, 0, 0);
var trigger_type = (m.type == MTBF) ? "/mtbf" : "/mcbf";
setprop(prop ~ trigger_type, 0);
setlistener(prop ~ trigger_type, compat_listener, 0, 0);
}
}
var lsnr = setlistener("sim/signals/fdm-initialized", compat_setup);

View file

@ -0,0 +1,363 @@
# Failure simulation library
#
# Collection of generic Triggers and FailureActuators for programming the
# FailureMgr Nasal module.
#
# Copyright (C) 2014 Anton Gomez Alvedro
# Based on previous work by Stuart Buchanan, Erobo & John Denker
#
# 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.
#
# Functions for generating FailureActuators
# ------------------------------------------
##
# Returns an actuator object that will set the serviceable property at
# the given node to zero when the level of failure is > 0.
var set_unserviceable = func(path) {
var prop = path ~ "/serviceable";
return {
parents: [FailureMgr.FailureActuator],
set_failure_level: func(level) setprop(prop, level > 0 ? 0 : 1),
get_failure_level: func { getprop(prop) ? 0 : 1 }
}
}
##
# Returns an actuator object that will make the given property read only.
# This prevents any other system from updating it, and effectively jamming
# whatever it is that is controlling.
var set_readonly = func(property) {
return {
parents: [FailureMgr.FailureActuator],
set_failure_level: func(level) {
var pnode = props.globals.getNode(property);
pnode.setAttribute("writable", level > 0 ? 0 : 1);
},
get_failure_level: func {
var pnode = props.globals.getNode(property);
pnode.getAttribute("writable") ? 0 : 1;
}
}
}
##
# Returns an an actuator object the manipulates engine controls (magnetos &
# cutoff) to simulate an engine failure. Sets these properties to read only
# while the system is failed.
var fail_engine = func(engine) {
return {
parents: [FailureMgr.FailureActuator],
level: 0,
magnetos: props.globals.getNode("/controls/engines/" ~ engine ~ "/magnetos", 1),
cutoff: props.globals.getNode("/controls/engines/" ~ engine ~ "/cutoff", 1),
get_failure_level: func me.level,
set_failure_level: func(level) {
if (level) {
# Switch off the engine, and disable writing to it.
me.magnetos.setValue(0);
me.magnetos.setAttribute("writable", 0);
me.cutoff.setValue(1);
me.cutoff.setAttribute("writable", 0);
}
else {
# Enable the properties, but don't set the magnetos, as they may
# be off for a reason.
me.magnetos.setAttribute("writable", 1);
me.cutoff.setAttribute("writable", 1);
me.cutoff.setValue(0);
}
me.level = level;
}
}
}
#
# Triggers
# ---------
##
# Returns a random number from a Normal distribution with given mean and
# standard deviation.
var norm_rand = func(mean, std) {
var r = -2 * math.ln(1 - rand());
var a = 2 * math.pi * (1 - rand());
return mean + (math.sqrt(r) * math.sin(a) * std);
};
##
# Trigger object that will fire when aircraft altitude is between
# min and max, both specified in feet. One of min or max may be nil for
# expressing "altitude > x" or "altitude < x" conditions.
var AltitudeTrigger = {
parents: [FailureMgr.Trigger],
requires_polling: 1,
new: func(min, max) {
min != nil or max != nil or
die("AltitudeTrigger.new: either min or max must be specified");
var m = FailureMgr.Trigger.new();
m.parents = [AltitudeTrigger];
m.params["min-altitude-ft"] = min;
m.params["max-altitude-ft"] = max;
m._altitude_prop = "/position/altitude-ft";
return m;
},
to_str: func {
# TODO: Handle min or max == nil
sprintf("Altitude between %d and %d ft",
int(me.params["min-altitude-ft"]), int(me.params["max-altitude-ft"]))
},
update: func {
var alt = getprop(me._altitude_prop);
var min = me.params["min-altitude-ft"];
var max = me.params["max-altitude-ft"];
me.fired = min != nil ? min < alt : 1;
me.fired = max != nil ? me.fired and alt < max : me.fired;
}
};
##
# Trigger object that fires when the aircraft's position is within a certain
# distance of a given waypoint.
var WaypointTrigger = {
parents: [FailureMgr.Trigger],
requires_polling: 1,
new: func(lat, lon, distance) {
var wp = geo.Coord.new();
wp.set_latlon(lat, lon);
var m = FailureMgr.Trigger.new();
m.parents = [WaypointTrigger];
m.params["latitude-deg"] = lat;
m.params["longitude-deg"] = lon;
m.params["distance-nm"] = distance;
m.waypoint = wp;
return m;
},
reset: func {
call(FailureMgr.Trigger.reset, [], me);
me.waypoint.set_latlon(me.params["latitude-deg"],
me.params["longitude-deg"]);
},
to_str: func {
sprintf("Within %.2f miles of %s", me.params["distance-nm"],
geo.format(me.waypoint.lat, me.waypoint.lon));
},
update: func {
var d = geo.aircraft_position().distance_to(me.waypoint) * M2NM;
me.fired = d < me.params["distance-nm"];
}
};
##
# Trigger object that will fire on average after the specified time.
var MtbfTrigger = {
parents: [FailureMgr.Trigger],
# TODO: make this trigger async
requires_polling: 1,
new: func(mtbf) {
var m = FailureMgr.Trigger.new();
m.parents = [MtbfTrigger];
m.params["mtbf"] = mtbf;
m.fire_time = 0;
m._time_prop = "/sim/time/elapsed-sec";
return m;
},
reset: func {
call(FailureMgr.Trigger.reset, [], me);
# TODO: use an elapsed time prop that accounts for speed-up and pause
var std = math.sqrt(me.params["mtbf"] / 10 - 1);
me.fire_time = getprop(me._time_prop)
+ norm_rand(me.params["mtbf"], std);
},
to_str: func {
sprintf("Mean time between failures: %f.1 mins", me.params["mtbf"] / 60);
},
update: func {
me.fired = getprop(me._time_prop) > me.fire_time;
}
};
##
# Trigger object that will fire exactly after the given timeout.
var TimeoutTrigger = {
parents: [FailureMgr.Trigger],
# TODO: make this trigger async
requires_polling: 1,
new: func(timeout) {
var m = FailureMgr.Trigger.new();
m.parents = [TimeoutTrigger];
m.params["timeout-sec"] = timeout;
fire_time = 0;
return m;
},
reset: func {
call(FailureMgr.Trigger.reset, [], me);
# TODO: use an elapsed time prop that accounts for speed-up and pause
me.fire_time = getprop("/sim/time/elapsed-sec")
+ me.params["timeout-sec"];
},
to_str: func {
sprintf("Fixed delay: %d minutes", me.params["timeout-sec"] / 60);
},
update: func {
me.fired = getprop("/sim/time/elapsed-sec") > me.fire_time;
}
};
##
# Simple approach to count usage cycles for a given property. Every time
# the propery variation changes in direction, we count half a cycle.
# If the property represents aileron angular position, for example, this
# would count roughly the number of times the aileron has been actuated.
var CycleCounter = {
new: func(property, on_update = nil) {
return {
parents: [CycleCounter],
cycles: 0,
_property: property,
_on_update: on_update,
_prev_value: getprop(property),
_prev_delta: 0,
_lsnr: nil
};
},
enable: func {
if (me._lsnr == nil)
me._lsnr = setlistener(me._property, func (p) me._on_prop_change(p), 0, 0);
},
disable: func {
if (me._lsnr != nil) removelistener(me._lsnr);
},
reset: func {
me.cycles = 0;
me._prev_value = getprop(me._property);
me._prev_delta = 0;
},
_on_prop_change: func(prop) {
# TODO: Implement a filter for avoiding spureous values.
var value = prop.getValue();
var delta = value - me._prev_value;
if (delta == 0) return;
if (delta * me._prev_delta < 0) {
# Property variation has changed direction
me.cycles += 0.5;
if (me._on_update != nil) me._on_update(me.cycles);
}
me._prev_delta = delta;
me._prev_value = value;
}
};
##
# Trigger object that will fire on average after a property has gone through
# mcbf (mean cycles between failures) cycles.
var McbfTrigger = {
parents: [FailureMgr.Trigger],
requires_polling: 0,
new: func(property, mcbf) {
var m = FailureMgr.Trigger.new();
m.parents = [McbfTrigger];
m.params["mcbf"] = mcbf;
m.counter = CycleCounter.new(property, func(c) call(m._on_cycle, [c], m));
m.activation_cycles = 0;
m.enabled = 0;
return m;
},
enable: func {
me.counter.enable();
me.enabled = 1;
},
disable: func {
me.counter.disable();
me.enabled = 0;
},
reset: func {
call(FailureMgr.Trigger.reset, [], me);
me.counter.reset();
me.activation_cycles =
norm_rand(me.params["mcbf"], math.sqrt(me.params["mcbf"] / 10));
me.enabled and me.counter.enable();
},
to_str: func {
sprintf("Mean cycles between failures: %.2f", me.params["mcbf"]);
},
_on_cycle: func(cycles) {
if (!me.fired and cycles > me.activation_cycles) {
# TODO: Why this doesn't work?
# me.counter.disable();
me.fired = 1;
me.on_fire();
}
}
};

View file

@ -0,0 +1,327 @@
# 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);

229
Nasal/FailureMgr/public.nas Normal file
View file

@ -0,0 +1,229 @@
# 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/";
##
# 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));
}
##
# 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);
}
##
# 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 = {
# 1 for pollable triggers, 0 for async triggers.
requires_polling: 0,
new: func {
return {
parents: [Trigger],
params: {},
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
};
},
##
# Enables/disables the trigger. While a trigger is disabled, any timer
# or listener that could potentially own shall be disabled.
enable: func,
disable: func,
##
# 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) {
contains(me.params, param) or
die("Trigger.set_param: undefined param: " ~ param);
me._path != nil or
die("Trigger.set_param: Unbound trigger");
setprop(sprintf("%s/%s",me._path, param), value);
},
##
# Reload trigger parameters and reset internal state, i.e. start from
# scratch. If the trigger was fired, the trigger is set to not fired.
reset: func {
me._path or die("Trigger.reset: unbound trigger");
foreach (var p; keys(me.params))
me.params[p] = getprop(sprintf("%s/%s", me._path, p));
me.fired = 0;
me._path != nil and setprop(me._path ~ "/reset", 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) {
me._path == nil or
die("Trigger.bind(): attempt to bind an already bound trigger");
me._path = path;
props.globals.getNode(path) != nil or props.globals.initNode(path);
props.globals.getNode(path).setValues(me.params);
var reset_prop = path ~ "/reset";
props.globals.initNode(reset_prop, 0, "BOOL");
setlistener(reset_prop, func me.reset(), 0, 0);
},
##
# Removes this trigger's interface from the property tree.
unbind: func {
props.globals.getNode(me._path ~ "/reset").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,
};

View file

@ -1,230 +0,0 @@
# failures.nas a manager for failing systems based on MTBF/MCBF
# Time between MTBF checks
var dt = 10;
# Root property for failure information
var failure_root = "/sim/failure-manager";
# Enumerations
var type = { MTBF : 1, MCBF: 2 };
var fail = { SERVICEABLE : 1, JAM : 2, ENGINE: 3};
# This hash contains a mapping from property entry to a failure object
# containing the following members:
# type: MTBF|MCBF Mean Time Between Failures/Mean Cycle Between Failures
# desc: <description> Description of property for screen output
# failure: SERVICEABLE Property has a "serviceable" child that can be set to false
# failure: JAM Property is failed by marking as Read-Only
# failure: ENGINE Special case for engines, where a variety of properties are set.
# failure: <prop> Property is failed by setting another property to false
var breakHash = {
"/instrumentation/adf" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "ADF" },
"/instrumentation/dme" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "DME" },
"/instrumentation/airspeed-indicator" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "ASI" },
"/instrumentation/altimeter" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Altimeter" },
"/instrumentation/attitude-indicator" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Attitude Indicator" },
"/instrumentation/heading-indicator" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Heading Indicator" },
"/instrumentation/magnetic-compass" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Magnetic Compass" },
"/instrumentation/nav[0]/gs" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Nav 1 Glideslope" },
"/instrumentation/nav[0]/cdi" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Nav 1 CDI" },
"/instrumentation/nav[1]/gs" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Nav 2 Glideslope" },
"/instrumentation/nav[1]/cdi" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Nav 2 CDI" },
"/instrumentation/slip-skid-ball" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Slip/Skid Ball" },
"/instrumentation/turn-indicator" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Turn Indicator" },
"/instrumentation/vertical-speed-indicator" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "VSI" },
"/systems/electrical" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Electrical system" },
"/systems/pitot" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Pitot system" },
"/systems/static" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Static system" },
"/systems/vacuum" : { type: type.MTBF, failure: fail.SERVICEABLE, desc: "Vacuum system" },
"/controls/gear/gear-down" : { type: type.MCBF, failure: "/gear/serviceable", desc: "Gear" },
"/controls/flight/aileron" : { type: type.MTBF, failure: fail.JAM, desc: "Aileron" },
"/controls/flight/elevator" : { type: type.MTBF, failure: fail.JAM, desc: "Elevator" },
"/controls/flight/rudder" : { type: type.MTBF, failure: fail.JAM, desc: "Rudder" },
"/controls/flight/flaps" : { type: type.MCBF, failure: fail.JAM, desc: "Flaps" },
"/controls/flight/speedbrake" : { type: type.MCBF, failure: fail.JAM, desc: "Speed Brake" }
};
# Return the failure entry for a given property
var getFailure = func (prop) {
var o = breakHash[prop];
if (o.failure == fail.SERVICEABLE) {
return prop ~ "/serviceable";
} elsif (o.failure == fail.ENGINE) {
return failure_root ~ prop ~ "/serviceable";
} elsif (o.failure == fail.JAM) {
return failure_root ~ prop ~ "/serviceable";
} else {
return o.failure;
}
}
# Fail a given property, either using a serviceable flag, or by jamming the property
var failProp = func(prop) {
var o = breakHash[prop];
var p = getFailure(prop);
if (getprop(p) == 1) {
setprop(p, 0);
# We always print to the console
print(getprop("/sim/time/gmt-string") ~ " : " ~ o.desc ~ " failed");
if (getprop(failure_root ~ "/display-on-screen")) {
# Display message to the screen in red
screen.log.write(o.desc ~ " failed", 1.0, 0.0, 0.0);
}
}
}
# Unfail a given property, used for resetting a failure state.
var unfailProp = func(prop)
{
var p = getFailure(prop);
setprop(p, 1);
}
# Unfail all the failed properties
var unfail = func {
foreach(var prop; keys(breakHash)) {
unfailProp(prop);
}
}
# Listener to jam a property. Note that the property to jam is
# encoded within the property name
var jamListener = func(p) {
var jamprop = string.replace(p.getParent().getPath(), failure_root, "");
#jamprop = string.replace(jamprop, "/serviceable", "");
var prop = props.globals.getNode(jamprop);
if (p.getValue()) {
prop.setAttribute("writable", 1);
} else {
prop.setAttribute("writable", 0);
}
}
# Listener for an engine property. Note that the engine to set is
# encoded within the property name. We set both the magnetos and
# cutoff to handle different engine models.
var engineListener = func(p) {
var e = string.replace(p.getParent().getPath(), failure_root, "");
var prop = props.globals.getNode(e);
if (p.getValue()) {
# Enable the properties, but don't set the magnetos, as they may
# be off for a reason.
var magnetos = props.globals.getNode("/controls/" ~ e ~ "/magnetos", 1);
var cutoff = props.globals.getNode("/controls/" ~ e ~ "/cutoff", 1);
magnetos.setAttribute("writable", 1);
cutoff.setAttribute("writable", 1);
cutoff.setValue(0);
} else {
# Switch off the engine, and disable writing to it.
var magnetos = props.globals.getNode("/controls/" ~ e ~ "/magnetos", 1);
var cutoff = props.globals.getNode("/controls/" ~ e ~ "/cutoff", 1);
magnetos.setValue(0);
cutoff.setValue(1);
magnetos.setAttribute("writable", 0);
cutoff.setAttribute("writable", 0);
}
}
# Perform a MCBF check against a failure property.
var checkMCBF = func(prop) {
var mcbf = getprop(failure_root ~ prop.getPath() ~ "/mcbf");
# mcbf == mean cycles between failures
# hence 2*mcbf is the number of _half-cycles_ between failures,
# which is relevant because we do this check on each half-cycle:
if ((mcbf > 0) and !int(2 * mcbf * rand())) {
# Get the property information.
failProp(prop.getPath());
}
}
# Timer based loop to check MTBF properties
var checkMTBF = func {
foreach(var prop; keys(breakHash)) {
var o = breakHash[prop];
if (o.type == type.MTBF) {
var mtbf = getprop(failure_root ~ prop ~ "/mtbf");
if (mtbf and !int(rand() * mtbf / dt)) {
failProp(prop);
}
}
}
settimer(checkMTBF, dt);
}
# Function to set all MTBF failures to a give value. Mainly for testing.
var setAllMTBF = func(mtbf) {
foreach(var prop; keys(breakHash)) {
var o = breakHash[prop];
if (o.type == type.MTBF) {
setprop(failure_root ~ prop ~ "/mtbf", mtbf);
}
}
}
# Function to set all MCBF failures to a give value. Mainly for testing.
var setAllMCBF = func(mcbf) {
foreach(var prop; keys(breakHash)) {
var o = breakHash[prop];
if (o.type == type.MCBF) {
setprop(failure_root ~ prop ~ "/mcbf", mcbf);
}
}
}
# Initialization, called once Nasal and the FDM are loaded properly.
var fdm_init_listener = _setlistener("/sim/signals/fdm-initialized", func {
removelistener(fdm_init_listener); # uninstall, so we're only called once
srand();
# Engines are added dynamically because there may be an arbitrary number
var i = 1;
foreach (var e; props.globals.getNode("/engines").getChildren("engine")) {
breakHash[e.getPath()] = { type: type.MTBF, failure: fail.ENGINE, desc : "Engine " ~ i };
i = i+1;
}
# Set up serviceable, MCBF and MTBF properties.
foreach(var prop; keys(breakHash)) {
var o = breakHash[prop];
var t = "/mcbf";
if (o.type == type.MTBF) {
t = "/mtbf";
}
# Set up the MTBF/MCFB properties to 0. Note that they are in a separate
# subtree, as there's no guarantee that the property isn't a leaf.
props.globals.initNode(failure_root ~ prop ~ t, 0);
if (o.failure == fail.SERVICEABLE) {
# If the property has a serviceable property, set it if appropriate.
props.globals.initNode(prop ~ "/serviceable", 1, "BOOL");
} elsif (o.failure == fail.JAM) {
# In the JAM case, we actually have a dummy serviceable property for the GUI.
props.globals.initNode(failure_root ~ prop ~ "/serviceable", 1, "BOOL");
setlistener(failure_root ~ prop ~ "/serviceable", jamListener);
} elsif (o.failure == fail.ENGINE) {
# In the JAM case, we actually have a dummy serviceable property for the GUI.
props.globals.initNode(failure_root ~ prop ~ "/serviceable", 1, "BOOL");
setlistener(failure_root ~ prop ~ "/serviceable", engineListener);
} else {
# If the serviceable property is actually defined, check it is set.
props.globals.initNode(o.failure, 1, "BOOL");
}
if (o.type == type.MCBF) {
# Set up listener for MCBF properties, only when the value changes.
setlistener(prop, checkMCBF, 0, 0);
}
}
# Start checking for failures.
checkMTBF();
});

View file

@ -80,7 +80,7 @@
<checkbox>
<row>1</row>
<col>1</col>
<property>/instrumentation/nav[0]/cdi/serviceable</property>
<property>/sim/failure-manager/instrumentation/nav[0]/cdi/serviceable</property>
</checkbox>
<input>
@ -101,7 +101,7 @@
<checkbox>
<row>1</row>
<col>5</col>
<property>/instrumentation/nav[1]/cdi/serviceable</property>
<property>/sim/failure-manager/instrumentation/nav[1]/cdi/serviceable</property>
</checkbox>
<input>
@ -122,7 +122,7 @@
<checkbox>
<row>2</row>
<col>1</col>
<property>/instrumentation/nav[0]/gs/serviceable</property>
<property>/sim/failure-manager/instrumentation/nav[0]/gs/serviceable</property>
</checkbox>
<input>
@ -141,7 +141,7 @@
<checkbox>
<row>2</row>
<col>5</col>
<property>/instrumentation/nav[1]/gs/serviceable</property>
<property>/sim/failure-manager/instrumentation/nav[1]/gs/serviceable</property>
</checkbox>
<input>
@ -160,7 +160,7 @@
<checkbox>
<row>3</row>
<col>1</col>
<property>/instrumentation/dme/serviceable</property>
<property>/sim/failure-manager/instrumentation/dme/serviceable</property>
</checkbox>
<input>
@ -179,7 +179,7 @@
<checkbox>
<row>3</row>
<col>5</col>
<property>/instrumentation/adf/serviceable</property>
<property>/sim/failure-manager/instrumentation/adf/serviceable</property>
</checkbox>
<input>
@ -198,7 +198,7 @@
<checkbox>
<row>4</row>
<col>1</col>
<property>/instrumentation/airspeed-indicator/serviceable</property>
<property>/sim/failure-manager/instrumentation/airspeed-indicator/serviceable</property>
</checkbox>
<input>
@ -217,7 +217,7 @@
<checkbox>
<row>4</row>
<col>5</col>
<property>/instrumentation/attitude-indicator/serviceable</property>
<property>/sim/failure-manager/instrumentation/attitude-indicator/serviceable</property>
</checkbox>
<input>
@ -236,7 +236,7 @@
<checkbox>
<row>5</row>
<col>1</col>
<property>/instrumentation/altimeter/serviceable</property>
<property>/sim/failure-manager/instrumentation/altimeter/serviceable</property>
</checkbox>
<input>
@ -255,7 +255,7 @@
<checkbox>
<row>5</row>
<col>5</col>
<property>/instrumentation/turn-indicator/serviceable</property>
<property>/sim/failure-manager/instrumentation/turn-indicator/serviceable</property>
</checkbox>
<input>
@ -274,7 +274,7 @@
<checkbox>
<row>6</row>
<col>1</col>
<property>/instrumentation/slip-skid-ball/serviceable</property>
<property>/sim/failure-manager/instrumentation/slip-skid-ball/serviceable</property>
</checkbox>
<input>
@ -293,7 +293,7 @@
<checkbox>
<row>6</row>
<col>5</col>
<property>/instrumentation/heading-indicator/serviceable</property>
<property>/sim/failure-manager/instrumentation/heading-indicator/serviceable</property>
</checkbox>
<input>
@ -312,7 +312,7 @@
<checkbox>
<row>7</row>
<col>1</col>
<property>/instrumentation/vertical-speed-indicator/serviceable</property>
<property>/sim/failure-manager/instrumentation/vertical-speed-indicator/serviceable</property>
</checkbox>
<input>
@ -331,7 +331,7 @@
<checkbox>
<row>7</row>
<col>5</col>
<property>/instrumentation/magnetic-compass/serviceable</property>
<property>/sim/failure-manager/instrumentation/magnetic-compass/serviceable</property>
</checkbox>
<input>
@ -380,7 +380,7 @@
</button>
<button>
<legend>Reset</legend>
<legend>Refresh</legend>
<equal>true</equal>
<binding>
<command>dialog-update</command>

View file

@ -326,8 +326,8 @@
<binding>
<command>nasal</command>
<script>
failures.setAllMTBF(getprop("/sim/failure-manager/global-mtbf"));
failures.setAllMCBF(getprop("/sim/failure-manager/global-mcbf"));
compat_failure_modes.apply_global_mtbf(getprop("/sim/failure-manager/global-mtbf"));
compat_failure_modes.apply_global_mcbf(getprop("/sim/failure-manager/global-mcbf"));
</script>
</binding>
<binding>
@ -344,8 +344,8 @@
<binding>
<command>nasal</command>
<script>
failures.setAllMTBF(getprop("/sim/failure-manager/global-mtbf"));
failures.setAllMCBF(getprop("/sim/failure-manager/global-mcbf"));
compat_failure_modes.apply_global_mtbf(getprop("/sim/failure-manager/global-mtbf"));
compat_failure_modes.apply_global_mcbf(getprop("/sim/failure-manager/global-mcbf"));
</script>
</binding>
</button>

View file

@ -30,40 +30,44 @@
i += 1;
foreach (var e; engines.getChildren("engine")) {
if (((e.getChild("starter") != nil) and (e.getChild("starter") != "")) or
((e.getChild("running") != nil) and (e.getChild("running") != "")) ) {
row = row + 1;
var starter = e.getChild("starter");
var running = e.getChild("running");
# Set up the label
target = group.getNode("text[" ~ i ~ "]", 1);
props.copy(group.getNode("text-template"), target);
target.getNode("row").setValue(row);
(starter != nil and starter != "" and starter.getType() != "NONE")
or (running != nil and running != "" and running.getType() != "NONE")
or continue;
if (size(engines.getChildren("engine")) == 1) {
target.getNode("label").setValue("Engine");
} else {
# Engines are indexed from 1 in the GUI.
target.getNode("label").setValue("Engine " ~ (engine + 1));
}
row = row + 1;
# Now the checkbox
target = group.getNode("checkbox[" ~ i ~ "]", 1);
props.copy(group.getChild("checkbox-template"), target);
target.getNode("row").setValue(row);
# Set up the label
target = group.getNode("text[" ~ i ~ "]", 1);
props.copy(group.getNode("text-template"), target);
target.getNode("row").setValue(row);
var failure = "/sim/failure-manager/engines/engine[" ~ engine ~ "]/serviceable";
target.getNode("property").setValue(failure);
# Finally the MTBF
target = group.getNode("input[" ~ i ~ "]", 1);
props.copy(group.getChild("input-template"), target);
target.getNode("row").setValue(row);
i += 1;
var mtbf = "/sim/failure-manager/engines/engine[" ~ engine ~ "]/mtbf";
target.getNode("property").setValue(mtbf);
engine += 1;
if (size(engines.getChildren("engine")) == 1) {
target.getNode("label").setValue("Engine");
} else {
# Engines are indexed from 1 in the GUI.
target.getNode("label").setValue("Engine " ~ (engine + 1));
}
# Now the checkbox
target = group.getNode("checkbox[" ~ i ~ "]", 1);
props.copy(group.getChild("checkbox-template"), target);
target.getNode("row").setValue(row);
var failure = "/sim/failure-manager/engines/engine[" ~ engine ~ "]/serviceable";
target.getNode("property").setValue(failure);
# Finally the MTBF
target = group.getNode("input[" ~ i ~ "]", 1);
props.copy(group.getChild("input-template"), target);
target.getNode("row").setValue(row);
i += 1;
var mtbf = "/sim/failure-manager/engines/engine[" ~ engine ~ "]/mtbf";
target.getNode("property").setValue(mtbf);
engine += 1;
}
@ -132,7 +136,7 @@
<checkbox>
<row>1</row>
<col>1</col>
<property>/systems/vacuum/serviceable</property>
<property>/sim/failure-manager/systems/vacuum/serviceable</property>
</checkbox>
<input>
@ -151,7 +155,7 @@
<checkbox>
<row>2</row>
<col>1</col>
<property>/systems/static/serviceable</property>
<property>/sim/failure-manager/systems/static/serviceable</property>
</checkbox>
<input>
@ -170,7 +174,7 @@
<checkbox>
<row>3</row>
<col>1</col>
<property>/systems/pitot/serviceable</property>
<property>/sim/failure-manager/systems/pitot/serviceable</property>
</checkbox>
<input>
@ -189,7 +193,7 @@
<checkbox>
<row>4</row>
<col>1</col>
<property>/systems/electrical/serviceable</property>
<property>/sim/failure-manager/systems/electrical/serviceable</property>
</checkbox>
<input>
@ -287,13 +291,13 @@
<checkbox>
<row>1</row>
<col>1</col>
<property>/gear/serviceable</property>
<property>/sim/failure-manager/controls/gear/serviceable</property>
</checkbox>
<input>
<row>1</row>
<col>2</col>
<property>/sim/failure-manager/controls/gear/gear-down/mcbf</property>
<property>/sim/failure-manager/controls/gear/mcbf</property>
</input>
<text>
@ -402,7 +406,7 @@
</button>
<button>
<legend>Reset</legend>
<legend>Refresh</legend>
<equal>true</equal>
<binding>
<command>dialog-update</command>