1
0
Fork 0
fgdata/Aircraft/Generic/DualControl/dual-control-tools.nas

645 lines
19 KiB
Text
Raw Normal View History

###############################################################################
##
## Nasal module for dual control over the multiplayer network.
##
## Copyright (C) 2007 - 2010 Anders Gidenstam (anders(at)gidenstam.org)
## This file is licensed under the GPL license version 2 or later.
##
###############################################################################
## MP properties
var lat_mpp = "position/latitude-deg";
var lon_mpp = "position/longitude-deg";
var alt_mpp = "position/altitude-ft";
var heading_mpp = "orientation/true-heading-deg";
var pitch_mpp = "orientation/pitch-deg";
var roll_mpp = "orientation/roll-deg";
# Import components from the mp_broadcast module.
var Binary = mp_broadcast.Binary;
var MessageChannel = mp_broadcast.MessageChannel;
###############################################################################
# Utility classes
############################################################
# Translate a property into another.
# Factor and offsets are only used for numeric values.
# src - source : property node
# dest - destination : property node
# factor - : double
# offset - : double
var Translator = {};
Translator.new = func (src = nil, dest = nil, factor = 1, offset = 0) {
var obj = { parents : [Translator],
src : src,
dest : dest,
factor : factor,
offset : offset };
if (obj.src == nil or obj.dest == nil) {
print("Translator[");
print(" ", debug.string(obj.src));
print(" ", debug.string(obj.dest));
print("]");
fail();
}
return obj;
}
Translator.update = func () {
var v = me.src.getValue();
if (is_num(v)) {
me.dest.setValue(me.factor * v + me.offset);
} else {
if (typeof(v) == "scalar")
me.dest.setValue(v);
}
}
############################################################
# Detects flanks on two insignals encoded in a property.
# - positive signal up/down flank
# - negative signal up/down flank
# n - source : property node
# on_positive_flank - action : func (v)
# on_negative_flank - action : func (v)
var EdgeTrigger = {};
EdgeTrigger.new = func (n, on_positive_flank, on_negative_flank) {
var obj = { parents : [EdgeTrigger],
old : 0,
node : n,
pos_flank : on_positive_flank,
neg_flank : on_negative_flank };
if (obj.node == nil) {
print("EdgeTrigger[");
print(" ", debug.string(obj.node));
print("]");
fail();
}
return obj;
}
EdgeTrigger.update = func {
# NOTE: float MP properties get interpolated.
# This detector relies on that steady state is reached between
# flanks.
var val = me.node.getValue();
if (!is_num(val)) return;
if (me.old == 1) {
if (val < me.old) {
me.pos_flank(0);
}
} elsif (me.old == 0) {
if (val > me.old) {
me.pos_flank(1);
} elsif (val < me.old) {
me.neg_flank(1);
}
} elsif (me.old == -1) {
if (val > me.old) {
me.neg_flank(0);
}
}
me.old = val;
}
############################################################
# StableTrigger: Triggers an action when a MPP property
# becomes stable (i.e. doesn't change for
# MIN_STABLE seconds).
# src - MP prop : property node
# action - action to take when the value becomes stable : [func(v)]
# An action is triggered when value has stabilized.
var StableTrigger = {};
StableTrigger.new = func (src, action) {
var obj = { parents : [StableTrigger],
src : src,
action : action,
old : 0,
stable_since : 0,
wait : 0,
MIN_STABLE : 0.01 };
# Error checking.
var bad = (obj.src == nil) or (action = nil);
if (bad) {
print("StableTrigger[");
print(" ", debug.string(obj.src));
print(" ", debug.string(obj.action));
print("]");
fail();
}
return obj;
}
StableTrigger.update = func () {
var v = me.src.getValue();
if (!is_num(v)) return;
var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
if ((me.old == v) and
((t - me.stable_since) > me.MIN_STABLE) and (me.wait == 1)) {
# Trigger action.
me.action(v);
me.wait = 0;
} elsif (me.old == v) {
# Wait. This is either before the signal is stable or after the action.
} else {
me.stable_since = t;
me.wait = 1;
me.old = me.src.getValue();
}
}
############################################################
# Selects the most recent value of two properties.
# src1 - : property node
# src2 - : property node
# dest - : property node
# threshold - : double
var MostRecentSelector = {};
MostRecentSelector.new = func (src1, src2, dest, threshold) {
var obj = { parents : [MostRecentSelector],
old1 : 0,
old2 : 0,
src1 : src1,
src2 : src2,
dest : dest,
thres : threshold };
if (obj.src1 == nil or obj.src2 == nil or obj.dest == nil) {
print("MostRecentSelector[");
print(" ", debug.string(obj.src1));
print(" ", debug.string(obj.src2));
print(" ", debug.string(obj.dest));
print("]");
}
return obj;
}
MostRecentSelector.update = func {
var v1 = me.src1.getValue();
var v2 = me.src2.getValue();
if (!is_num(v1) and !is_num(v2)) return;
elsif (!is_num(v1)) me.dest.setValue(v2);
elsif (!is_num(v2)) me.dest.setValue(v1);
else {
if (abs (v2 - me.old2) > me.thres) {
me.old2 = v2;
me.dest.setValue(me.old2);
}
if (abs (v1 - me.old1) > me.thres) {
me.old1 = v1;
me.dest.setValue(me.old1);
}
}
}
############################################################
# Adds two input properties.
# src1 - : property node
# src2 - : property node
# dest - : property node
var Adder = {};
Adder.new = func (src1, src2, dest) {
var obj = { parents : [DeltaAccumulator],
src1 : src1,
src2 : src2,
dest : dest };
if (obj.src1 == nil or obj.src2 == nil or obj.dest == nil) {
print("Adder[");
print(" ", debug.string(obj.src1));
print(" ", debug.string(obj.src2));
print(" ", debug.string(obj.dest));
print("]");
fail();
}
return obj;
}
Adder.update = func () {
var v1 = me.src1.getValue();
var v2 = me.src2.getValue();
if (!is_num(v1) or !is_num(v2)) return;
me.dest.setValue(v1 + v2);
}
############################################################
# Adds the delta of src to dest.
# src - : property node
# dest - : property node
var DeltaAdder = {};
DeltaAdder.new = func (src, dest) {
var obj = { parents : [DeltaAdder],
old : 0,
src : src,
dest : dest };
if (obj.src == nil or obj.dest == nil) {
print("DeltaAdder[", debug.string(obj.src), ", ",
debug.string(obj.dest), "]");
fail();
}
return obj;
}
DeltaAdder.update = func () {
var v = me.src.getValue();
if (!is_num(v)) return;
me.dest.setValue((v - me.old) + me.dest.getValue());
me.old = v;
}
############################################################
# Switch encoder: Encodes upto 32 boolean properties in one
# int property.
# inputs - list of property nodes
# dest - where the bitmask is stored : property node
var SwitchEncoder = {};
SwitchEncoder.new = func (inputs, dest) {
var obj = { parents : [SwitchEncoder],
inputs : inputs,
dest : dest };
# Error checking.
var bad = (obj.dest == nil);
foreach (var i; inputs) {
if (i == nil) { bad = 1; }
}
if (bad) {
print("SwitchEncoder[");
foreach (var i; inputs) {
print(" ", debug.string(i));
}
print(" ", debug.string(obj.dest));
print("]");
fail();
}
return obj;
}
SwitchEncoder.update = func () {
var v = 0;
var b = 1;
forindex (var i; me.inputs) {
if (me.inputs[i].getBoolValue()) {
v = v + b;
}
b *= 2;
}
me.dest.setIntValue(v);
}
############################################################
# Switch decoder: Decodes a bitmask in an int property.
# src - : property node
# actions - list of actions : [func(b)]
# Actions are triggered when their input bit change.
# Due to interpolation the decoder needs to wait for a
# stable input value.
var SwitchDecoder = {};
SwitchDecoder.new = func (src, actions) {
var obj = { parents : [SwitchDecoder],
wait : 0,
old : 0,
old_stable : 0,
stable_since : 0,
reset : 1,
src : src,
actions : actions,
MIN_STABLE : 0.1 };
# Error checking.
var bad = (obj.src == nil);
foreach (var a; obj.actions) {
if (a == nil) { bad = 1; }
}
if (bad) {
print("SwitchDecoder[");
print(" ", debug.string(obj.src));
foreach (var a; obj.actions) {
print(" ", debug.string(a));
}
print("]");
fail();
}
return obj;
}
SwitchDecoder.update = func () {
var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
var v = me.src.getValue();
if (!is_num(v)) return;
if ((me.old == v) and ((t - me.stable_since) > me.MIN_STABLE) and
(me.wait == 1)) {
var ov = me.old_stable;
# Use this to improve.
#<cptf> here's the boring version: var bittest = func(u, b) { while (b) { u = int(u / 2); b -= 1; } u != int(u / 2) * 2; }
forindex (var i; me.actions) {
var m = math.mod(v, 2);
var om = math.mod(ov, 2);
if ((m != om or me.reset)) { me.actions[i](m?1:0); }
v = (v - m)/2;
ov = (ov - om)/2;
}
me.old_stable = me.src.getValue();
me.wait = 0;
me.reset = 0;
} elsif (me.old == v) {
# Wait. This is either before the bitmask is stable or after
# it has been processed.
} else {
me.stable_since = t;
me.wait = 1;
me.old = me.src.getValue();
}
}
############################################################
# Time division multiplexing encoder: Transmits a list of
# properties over a MP enabled string property.
# inputs - input properties : [property node]
# dest - MP string prop : property node
# Note: TDM can have high latency so it is best used for
# non-time critical properties.
var TDMEncoder = {};
TDMEncoder.new = func (inputs, dest) {
var obj = { parents : [TDMEncoder],
inputs : inputs,
channel : MessageChannel.new(dest,
func (msg) {
print("This should not happen!");
}),
MIN_INT : 0.25,
last_time : 0,
next_item : 0,
old : [] };
# Error checking.
var bad = (dest == nil) or (obj.channel == nil);
foreach (var i; inputs) {
if (i == nil) { bad = 1; }
}
if (bad) {
print("TDMEncoder[");
foreach (var i; inputs) {
print(" ", debug.string(i));
}
print(" ", debug.string(dest));
print("]");
}
setsize(obj.old, size(obj.inputs));
return obj;
}
TDMEncoder.update = func () {
var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
if (t > me.last_time + me.MIN_INT) {
var n = size(me.inputs);
while (1) {
var v = me.inputs[me.next_item].getValue();
if ((n <= 0) or (me.old[me.next_item] != v)) {
# Set the MP properties to send the next item.
me.channel.send(Binary.encodeByte(me.next_item) ~
Binary.encodeDouble(v));
me.old[me.next_item] = v;
me.last_time = t;
me.next_item += 1;
if (me.next_item >= size(me.inputs)) { me.next_item = 0; }
return;
} else {
# Search for changed property.
n -= 1;
me.next_item += 1;
if (me.next_item >= size(me.inputs)) { me.next_item = 0; }
}
}
}
}
############################################################
# Time division multiplexing decoder: Receives a list of
# properties over a MP enabled string property.
# src - MP string prop : property node
# actions - list of actions : [func(v)]
# An action is triggered when its value is received.
# Note: TDM can have high latency so it is best used for
# non-time critical properties.
var TDMDecoder = {};
TDMDecoder.new = func (src, actions) {
var obj = { parents : [TDMDecoder],
actions : actions };
obj.channel = MessageChannel.new(src,
func (msg) {
obj.process(msg);
});
# Error checking.
var bad = (src == nil) or (obj.channel == nil);
foreach (var a; actions) {
if (a == nil) { bad = 1; }
}
if (bad) {
print("TDMDecoder[");
print(" ", debug.string(src));
foreach (var a; actions) {
print(" ", debug.string(a));
}
print("]");
fail();
}
return obj;
}
TDMDecoder.process = func (msg) {
var v1 = Binary.decodeByte(msg);
var v2 = Binary.decodeDouble(substr(msg, 1));
# Trigger action.
me.actions[v1](v2);
}
TDMDecoder.update = func {
me.channel.update();
}
###############################################################################
# Internal utility functions
var is_num = func (v) {
return num(v) != nil;
}
# fail causes a Nasal runtime error so we get a backtrace.
var fail = func {
error_detected_in_calling_context();
}
###############################################################################
###############################################################################
# Copilot selection dialog.
#
# Usage: dual_control_tools.copilot_dialog.show(<copilot type string>);
#
var COPILOT_DLG = 0;
var copilot_dialog = {};
############################################################
copilot_dialog.init = func (copilot_type, x = nil, y = nil) {
me.x = x;
me.y = y;
me.bg = [0, 0, 0, 0.3]; # background color
me.fg = [[1.0, 1.0, 1.0, 1.0]];
#
# "private"
if (contains(aircraft_dual_control, "copilot_view")) {
me.title = "Pilot selection";
} else {
me.title = "Copilot selection";
}
me.basenode = props.globals.getNode("/sim/remote", 1);
me.dialog = nil;
me.namenode = props.Node.new({"dialog-name" : me.title });
me.listeners = [];
me.copilot_type = copilot_type;
}
############################################################
copilot_dialog.create = func {
if (me.dialog != nil)
me.close();
me.dialog = gui.Widget.new();
me.dialog.set("name", me.title);
if (me.x != nil)
me.dialog.set("x", me.x);
if (me.y != nil)
me.dialog.set("y", me.y);
me.dialog.set("layout", "vbox");
me.dialog.set("default-padding", 0);
var titlebar = me.dialog.addChild("group");
titlebar.set("layout", "hbox");
titlebar.addChild("empty").set("stretch", 1);
if (contains(aircraft_dual_control, "copilot_view")) {
titlebar.addChild("text").set("label", "Pilots online");
} else {
titlebar.addChild("text").set("label", "Copilots online");
}
var w = titlebar.addChild("button");
w.set("pref-width", 16);
w.set("pref-height", 16);
w.set("legend", "");
w.set("default", 0);
w.set("key", "esc");
w.setBinding("nasal", "dual_control_tools.copilot_dialog.destroy(); ");
w.setBinding("dialog-close");
me.dialog.addChild("hrule");
var content = me.dialog.addChild("group");
content.set("layout", "vbox");
content.set("halign", "center");
content.set("default-padding", 5);
# Generate the dialog contents.
me.players = me.find_copilot_players();
var i = 0;
var tmpbase = me.basenode.getNode("dialog", 1);
var selected = me.basenode.getNode("pilot-callsign").getValue();
foreach (var p; me.players) {
var tmp = tmpbase.getNode("b[" ~ i ~ "]", 1);
tmp.setBoolValue(streq(selected, p));
var w = content.addChild("checkbox");
w.node.setValues({"label" : p,
"halign" : "left",
"property" : tmp.getPath()});
w.setBinding
("nasal",
"dual_control_tools.copilot_dialog.select_action(" ~ i ~ ");");
i = i + 1;
}
me.dialog.addChild("hrule");
# Display the dialog.
fgcommand("dialog-new", me.dialog.prop());
fgcommand("dialog-show", me.namenode);
}
############################################################
copilot_dialog.close = func {
fgcommand("dialog-close", me.namenode);
}
############################################################
copilot_dialog.destroy = func {
COPILOT_DLG = 0;
me.close();
foreach(var l; me.listeners)
removelistener(l);
delete(gui.dialog, "\"" ~ me.title ~ "\"");
}
############################################################
copilot_dialog.show = func (copilot_type) {
# print("Showing MPCopilots dialog!");
if (!COPILOT_DLG) {
COPILOT_DLG = int(getprop("/sim/time/elapsed-sec"));
me.init(copilot_type);
me.create();
me._update_(COPILOT_DLG);
}
}
############################################################
copilot_dialog._redraw_ = func {
if (me.dialog != nil) {
me.close();
me.create();
}
}
############################################################
copilot_dialog._update_ = func (id) {
if (COPILOT_DLG != id) return;
me._redraw_();
settimer(func { me._update_(id); }, 4.1);
}
############################################################
copilot_dialog.select_action = func (n) {
var selected = me.basenode.getNode("pilot-callsign").getValue();
var bs = me.basenode.getNode("dialog").getChildren();
# Assumption: There are two true b:s or none. The one not matching selected
# is the new selection.
var i = 0;
me.basenode.getNode("pilot-callsign").setValue("");
foreach (var b; bs) {
if (!b.getValue() and (i == n)) {
b.setValue(1);
me.basenode.getNode("pilot-callsign").setValue(me.players[i]);
} else {
b.setValue(0);
}
i = i + 1;
}
dual_control.main.reset();
me._redraw_();
}
############################################################
# Return a list containing all nearby copilot players of the right type.
copilot_dialog.find_copilot_players = func {
var mpplayers =
props.globals.getNode("/ai/models").getChildren("multiplayer");
var res = [];
foreach (var pilot; mpplayers) {
if ((pilot.getNode("valid") != nil) and
(pilot.getNode("valid").getValue()) and
(pilot.getNode("sim/model/path") != nil)) {
var type = pilot.getNode("sim/model/path").getValue();
if (type == me.copilot_type) {
append(res, pilot.getNode("callsign").getValue());
}
}
}
# debug.dump(res);
return res;
}
###############################################################################