1
0
Fork 0

Merge branch 'next-rjh' into next-emesary-mp-bridge

This commit is contained in:
Richard Harrison 2016-04-10 00:38:48 +02:00
commit 7fa52ea4ee
9 changed files with 1154 additions and 70 deletions

View file

@ -0,0 +1,362 @@
#---------------------------------------------------------------------------
#
# Title : AN/SPN-46 Precision Approach Landing System (PALS)
#
# File Type : Implementation File
#
# Description : Representative emulation of the functions of the AN/SPN-46 using emesary
# : Where an AN/SPN-46 is required it is sufficient to instantiate a ANSPN46_System connected to the instantiator model.
# : Register with an instance of a Transmitter, and provide a Receive method, periodically send out Active messages
# : expecting ActiveResponse messages which may result in a Communication.
# : This is implemented as a set of messages that models the operation of the PALS
#
# References : http://trace.tennessee.edu/cgi/viewcontent.cgi?article=3297&context=utk_gradthes
# : http://www.navair.navy.mil/index.cfm?fuseaction=home.displayPlatform&key=E8D18768-14B6-4CF5-BAB5-12B009070CFC
# : http://www.afceaboston.com/documents/events/cnsatm2010/Briefs/4%20-%20Friday/03-Navy_Landing_Systems_Roadmap-(CDR%20Easler).pdf
#
# Author : Richard Harrison (richard@zaretto.com)
#
# Creation Date : 29 January 2016
#
# Version : 4.8
#
# Copyright © 2016 Richard Harrison Released under GPL V2
#
#---------------------------------------------------------------------------*/
#Message Reference:
#---------------------------------------------------------------------------*/
# Notification 1 ANSPN46ActiveNotification from carrier to aircraft
# Transmitted via GlobalTransmitter at 1hz
# - carrier position
# - beam start
# - beam angle
# - channel / frequency information
# - beam range / power
#
# Notification 2 - from aircraft to carrier
# - aircraft position
# - radar return size
# - aircraft altitude, heading, velocity
# - respose indicating tuned or not
#
# Notification 3 - from carrier to aircraft
# - lateral deviation
# - vertical deviation
# - LSO information / messages
# - system status
# - lateral
#
#Operation:
#---------------------
# The ANSPN64 system will send periodically send out a ANSPN46ActiveNotification
# the rest of the logic within this system related to aircraft will be handled when
# the ANSPN46ActiveResponseNotification - which itself in turn will send out a ANSPN46CommunicationNotification
#----------------------
# NOTE: to avoid garbage collection all of the notifications that are sent out are created during construction
# and simply modified prior to sending. This works because emesary is synchronous and therefore the state
# and lifetime are known.
#
# Notification(1) from carrier to any aircraft within range.
#
var ANSPN46ActiveNotification =
{
# Create a new active notification notification. Aircraft will respond to this.
# param(_anspn46_system): instance of ANSPN46_System which will send the notification
new: func(_anspn46_system)
{
var ident="none";
if (_anspn46_system != nil)
ident=_anspn46_system.Ident;
var new_class = emesary.Notification.new("ANSPN46ActiveNotification", ident);
new_class.ANSPN46_system = _anspn46_system;
new_class.Position = nil;
new_class.BeamPosition = nil;
new_class.BeamAngle = 35;
new_class.Channel = 2;
new_class.BeamRange = 35; ##nm
new_class.BeamPower = 999; ## mw ???
#
# Set notification properties from the ANSPN46_System.
new_class.set_from = func(_anspn)
{
if (_anspn != nil)
{
me.Ident = _anspn.Ident;
me.Position = _anspn.GetCarrierPosition();
me.BeamPosition = _anspn.GetTDZPosition();
me.BeamAngle = 35;
me.Channel = _anspn.GetChannel();
me.BeamRange = 35; ##nm
me.BeamPower = 999; ## mw ???
}
print("\nANSPN46ActiveNotification::set_from: ", me.Ident);
};
new_class.bridgeProperties = func
{
return
[
{
getValue:func{return emesary.TransferCoord.encode(new_class.Position);},
setValue:func(v){new_class.Position=emesary.TransferCoord.decode(v);},
},
{
getValue:func{return emesary.TransferByte.encode(new_class.Channel);},
setValue:func(v){new_class.Channel=emesary.TransferByte.decode(v);},
},
];
};
new_class.set_from(new_class.ANSPN46_system);
return new_class;
},
};
# Notification(2) - from aircraft to carrier sent in response to Notification(1) above
#
var ANSPN46ActiveResponseNotification =
{
new: func(_ident)
{
var new_class = emesary.Notification.new("ANSPN46ActiveResponseNotification", _ident);
new_class.Respond = func(_msg)
{
new_class.Position = geo.aircraft_position();
new_class.Heading = getprop("orientation/heading-deg");
new_class.RadarReturnStrength = 1; # normalised value based on RCS beam power etc.
new_class.Tuned = 0; # 0 or 1
new_class.ufps = getprop("velocities/uBody-fps");
return me;
}
return new_class;
},
};
# Notification 3 - from carrier to aircraft as a result of active response notification
# - only sent if the aircraft is set to the same channel that we are transmitting on
#
var ANSPN46CommunicationNotification =
{
new: func(_ident, _anspn46_system)
{
var new_class = emesary.Notification.new("ANSPN46CommunicationNotification", _ident);
new_class.Model = _anspn46_system.Model;
new_class.set_from = func(_ident, _msg, _anspn46_system)
{
var carrier_ara_63_position = _anspn46_system.GetCarrierPosition();
var carrier_heading = _anspn46_system.GetCarrierHeading();
var carrier_ara_63_heading = 0;
# relative offset of the course to the tdz
# according to my measurements the Nimitz class is 8.1362114 degrees (measured 178 vs carrier 200 allowing for local magvar -13.8637886)
# i.e. this value is from tuning rather than calculation
if (carrier_heading != nil)
carrier_ara_63_heading = carrier_heading.getValue() - 8.1362114;
var range = _msg.Position.distance_to(carrier_ara_63_position);
var bearing_to = _msg.Position.course_to(carrier_ara_63_position);
var deviation = bearing_to - carrier_ara_63_heading;
deviation = deviation *0.1;
me.ReturnPosition = _msg.Position;
me.ReturnBearing = getprop("orientation/heading-deg");
# the AN/SPN 46 has a 20nm range with a 3 degree beam; ref: F14-AAD-1 17.3.2
if (range < 37000 and abs(deviation) < 3)
{
var FD_TAN3DEG = math.tan(3.0 / 57.29577950560105);
var deck_height=20;
var gs_height = ((range*FD_TAN3DEG)) + deck_height;
var gs_deviation = (gs_height - _msg.Position.alt()) / 42.0;
if (gs_deviation > 1)
gs_deviation = 1;
else if (gs_deviation < -1)
gs_deviation = -1;
# if not in range message will not be transmitted.
me.InRange = 1;
# calculate the deviation from the ideal approach
me.VerticalAdjustmentCommanded = gs_deviation;
me.HorizontalAdjustmentCommanded = deviation;
me.LateralDeviation = deviation;
me.VerticalDeviation = gs_deviation;
me.Distance = range;
me.SignalQualityNorm = 1;
#
# work out the rough ETA for the 10 seconds light, and use this
# to decide whether or not to waveoff
var eta = range / (_msg.ufps / 3.281);
if(eta <= 10 and range < 800 and range > 150)
{
me.TenSeconds = 1;
if(math.abs(deviation) > 0.2 or math.abs(gs_deviation) > 0.2)
{
me.WaveOff = 1;
}
else
{
me.WaveOff = 0;
}
}
else
{
me.TenSeconds = 0;
me.WaveOff = 0;
}
me.LSOMessage = "";
me.SystemStatus = "OK"; # Wave off, error, etcc.
}
else
{
#
# No response will be sent when not in range; so ensure values are all cleared.
me.InRange = 0;
me.VerticalAdjustmentCommanded = 0;
me.HorizontalAdjustmentCommanded = 0;
me.SignalQualityNorm =0;
me.Distance = -10000000000;
me.TenSeconds = 0;
me.WaveOff = 0;
me.InRange = 0;
me.VerticalDeviation = 0;
me.LateralDeviation = 0;
me.LSOMessage = "";
me.SystemStatus = "";
}
};
return new_class;
},
};
#
# The main AN/SPN46 system implemented using emesary.
# Periodically the Update method should be called, which will
# send out a notification via the global transmitter so that aircraft within range
# can respond. This is similar to a radar transmit and return.
# There should be an instance of this class created in the nasal section of the model xml
# Once an aircraft is within range it will receive guidance that can be displayed.
# It is the responsibility of the AN/SPN system to decided whether a craft is within range.
# It is the responsibility of the aircraft receiver to indicate whether it is tuned in or not
# if it the aircraft is not tuned into the right channel the receiver (e.g. ARA-63) will not receive anything; however
# the AN/SPN system (being a radar) will still have guidance information that could be relayed over the
# comms channel or displayed on a radar display on the carrier.
#
var ANSPN46_System =
{
new: func(_ident,_model)
{
print("AN/SNP46 created for "~_ident);
var new_class = emesary.Recipient.new(_ident~".ANSPN46");
new_class.ara_63_position = geo.Coord.new();
new_class.Model = _model;
new_class.communicationNotification = ANSPN46CommunicationNotification.new(new_class.Ident, new_class);
new_class.Channel = 2;
new_class.UpdateRate = 10;
#-------------------------------------------
# Receive override:
# Iinterested in receiving ANSPN46ActiveResponseNotification. When we get
# one of these we can respond with a CommunicationNotification
new_class.Receive = func(notification)
{
if (notification.Type == "ANSPN46ActiveResponseNotification")
{
if (notification.Tuned)
{
me.communicationNotification.set_from(me.Ident, notification, me);
if(me.communicationNotification.InRange)
{
me.UpdateRate = 0.2;
emesary.GlobalTransmitter.NotifyAll(me.communicationNotification);
}
}
return emesary.Transmitter.ReceiptStatus_OK;
}
return emesary.Transmitter.ReceiptStatus_NotProcessed;
}
#
# Interface methods
#-----------------------------
# Required interface to get the current carrier position
new_class.GetCarrierPosition = func()
{
var x = me.Model.getNode("position/global-x").getValue() + 88.7713542;
var y = me.Model.getNode("position/global-y").getValue() + 18.74631309;
var z = me.Model.getNode("position/global-z").getValue() + 115.6574875;
me.ara_63_position.set_xyz(x, y, z);
return me.ara_63_position;
};
#
# Interface to get the carrier heading
new_class.GetCarrierHeading = func()
{
return me.Model.getNode("orientation/true-heading-deg");
};
#
# offset of the TDZ (wires) from the carrier centre
new_class.GetTDZPosition = func
{
return me.ara_63_position;
};
#
# radar beam angle
new_class.GetUpdateRate = func
{
return me.UpdateRate;
};
new_class.BeamAngle = func
{
return 30;
};
#
# currently transmitting channel number.
new_class.GetChannel = func
{
return me.Channel;
};
new_class.SetChannel = func(v)
{
me.Channel = v;
};
#
# main entry point. The object itself will manage the update rate - but it is
# up to the caller to use this rate
new_class.Update = func
{
# fill in properties of message
me.msg.set_from(me);
#
# manage the update rate; increase each frame until we get to 10 seconds
# this will be reset if we receive something back from the aircraft.
if (me.UpdateRate < 10)
me.UpdateRate = me.UpdateRate+1;
print("AN/SPN 46 : update msg: ",me.msg.Ident," sys.rate=",me.UpdateRate);
return emesary.GlobalTransmitter.NotifyAll(me.msg);
};
#
# create the message that will be used to notify of an active carrier. This needs to be done after the methods
# have been created as it references them. Implemented like this to reduce garbage collection
new_class.msg = ANSPN46ActiveNotification.new(new_class);
emesary.GlobalTransmitter.Register(new_class);
return new_class;
},
}

View file

@ -13,6 +13,32 @@
<PropertyList>
<path>clem-superstructure.ac</path>
<nasal>
<load>
<![CDATA[
# add AN/SPN-46 see http://chateau-logic.com/content/emesary-nasal-implementation-flightgear
var self = cmdarg();
fn_an_spn_46 = getprop("/sim/fg-root") ~ "/Aircraft/Generic/an_spn_46.nas";
io.load_nasal(fn_an_spn_46, "an_spn_46");
var anspn = an_spn_46.ANSPN46_System.new("Clemenceau", self);
anspn.SetChannel(2);
var an_spn_46_timer = maketimer(6, func {
anspn.Update();
an_spn_46_timer.restart(anspn.GetUpdateRate());
});
an_spn_46_timer.restart(6);
]]>
</load>
<unload>
<![CDATA[
an_spn_46_timer.stop();
]]>
</unload>
</nasal>
<texture-path>Textures</texture-path>
<offsets>
<z-m>0</z-m>

View file

@ -8,7 +8,32 @@ apart from most of the very excellent textures, remain. -->
<status>early-production</status>
<path>nimitz.ac</path>
<!-- crew to be added later
<nasal>
<load>
<![CDATA[
var self = cmdarg();
print("Model load Nimitz ", self.getPath());
fn_an_spn_46 = getprop("/sim/fg-root") ~ "/Aircraft/Generic/an_spn_46.nas";
io.load_nasal(fn_an_spn_46, "an_spn_46");
var anspn = an_spn_46.ANSPN46_System.new("Eisenhower", self);
anspn.SetChannel(2);
var an_spn_46_timer = maketimer(6, func {
anspn.Update();
an_spn_46_timer.restart(anspn.GetUpdateRate());
});
an_spn_46_timer.restart(6);
]]>
</load>
<unload>
<![CDATA[
an_spn_46_timer.stop();
]]>
</unload>
</nasal>
<!-- crew to be added later
<model>
<path>Models/Geometry/Nimitz/Crew/crew.xml</path>

View file

@ -8,7 +8,32 @@ apart from most of the very excellent textures, remain. -->
<status>early-production</status>
<path>nimitz.ac</path>
<!-- crew to be added later
<nasal>
<load>
<![CDATA[
var self = cmdarg();
print("Model load Nimitz ", self.getPath());
fn_an_spn_46 = getprop("/sim/fg-root") ~ "/Aircraft/Generic/an_spn_46.nas";
io.load_nasal(fn_an_spn_46, "an_spn_46");
var anspn = an_spn_46.ANSPN46_System.new("Nimitz", self);
anspn.SetChannel(2);
var an_spn_46_timer = maketimer(6, func {
anspn.Update();
an_spn_46_timer.restart(anspn.GetUpdateRate());
});
an_spn_46_timer.restart(6);
]]>
</load>
<unload>
<![CDATA[
an_spn_46_timer.stop();
]]>
</unload>
</nasal>
<!-- crew to be added later
<model>
<path>Models/Geometry/Nimitz/Crew/crew.xml</path>
</model>

View file

@ -10,91 +10,104 @@ apart from most of the very excellent textures, remain. -->
<nasal>
<load>
print("LOAD Vinson ", cmdarg().getPath());
<![CDATA[
print("LOAD Vinson ", cmdarg().getPath());
var fg_root = getprop("/sim/fg-root");
var self = cmdarg();
var control_node = self.getNode("controls/turn-to-base-course", 1);
var pos_node = self.getNode("sim/antenna-pos-norm", 1);
pos_node.setDoubleValue(0);
var turn_old = 1;
var fg_root = getprop("/sim/fg-root");
var self = cmdarg();
var control_node = self.getNode("controls/turn-to-base-course", 1);
var pos_node = self.getNode("sim/antenna-pos-norm", 1);
pos_node.setDoubleValue(0);
var turn_old = 1;
########
# properties used to handle rendering, lighting and to control the Pack-Park
# Due to a change in MPcarrier system ?
# add AN/SPN-46 see http://chateau-logic.com/content/emesary-nasal-implementation-flightgear
fn_an_spn_46 = getprop("/sim/fg-root") ~ "/Aircraft/Generic/an_spn_46.nas";
io.load_nasal(fn_an_spn_46, "an_spn_46");
var anspn = an_spn_46.ANSPN46_System.new("Eisenhower", self);
anspn.SetChannel(2);
# not using the main update as need to keep the scheduling that an_spn_46 requests.
var an_spn_46_timer = maketimer(6, func {
anspn.Update();
an_spn_46_timer.restart(anspn.GetUpdateRate());
});
an_spn_46_timer.restart(6);
########
# properties used to handle rendering, lighting and to control the Pack-Park
# Due to a change in MPcarrier system ?
var rembrandt_node = self.getNode("sim/rendering/rembrandt/enabled", 1);
var sunAngleRad_node = self.getNode("sim/time/sun-angle-rad", 1);
var deckPark_node = self.getNode("sim/current-view/deck-park",1);
var rembrandt_node = self.getNode("sim/rendering/rembrandt/enabled", 1);
var sunAngleRad_node = self.getNode("sim/time/sun-angle-rad", 1);
var deckPark_node = self.getNode("sim/current-view/deck-park",1);
########
# properties used to calculate rel wind for the bow-wave shader
########
# properties used to calculate rel wind for the bow-wave shader
var speed_Node = self.getNode("velocities/speed-kts", 1);
var hdg_Node = self.getNode("orientation/true-heading-deg", 1);
var wind_speed_Node = self.getNode("environment/rel-wind-speed-kts", 1);
wind_speed_Node.setDoubleValue(0);
var speed_Node = self.getNode("velocities/speed-kts", 1);
var hdg_Node = self.getNode("orientation/true-heading-deg", 1);
var wind_speed_Node = self.getNode("environment/rel-wind-speed-kts", 1);
wind_speed_Node.setDoubleValue(0);
########
# properties to control the E2C
########
# properties to control the E2C
var gear0_Node = self.getNode("gear/gear[0]/position-norm", 1);
gear0_Node.setDoubleValue(1);
var gear0_Node = self.getNode("gear/gear[0]/position-norm", 1);
gear0_Node.setDoubleValue(1);
var gear1_Node = self.getNode("gear/gear[1]/position-norm", 1);
gear1_Node.setDoubleValue(1);
var gear1_Node = self.getNode("gear/gear[1]/position-norm", 1);
gear1_Node.setDoubleValue(1);
var gear2_Node = self.getNode("gear/gear[2]/position-norm", 1);
gear2_Node.setDoubleValue(1);
var gear2_Node = self.getNode("gear/gear[2]/position-norm", 1);
gear2_Node.setDoubleValue(1);
var compression0_Node = self.getNode("gear/gear[0]/compression-norm", 1);
compression0_Node.setDoubleValue(0);
var compression0_Node = self.getNode("gear/gear[0]/compression-norm", 1);
compression0_Node.setDoubleValue(0);
var compression1_Node = self.getNode("gear/gear[1]/compression-norm", 1);
compression1_Node.setDoubleValue(0.9);
var compression1_Node = self.getNode("gear/gear[1]/compression-norm", 1);
compression1_Node.setDoubleValue(0.9);
var compression2_Node = self.getNode("gear/gear[2]/compression-norm", 1);
compression2_Node.setDoubleValue(0.9);
var compression2_Node = self.getNode("gear/gear[2]/compression-norm", 1);
compression2_Node.setDoubleValue(0.9);
var wingfold_Node = self.getNode("surface-positions/wing-fold-pos-norm", 1);
wingfold_Node.setDoubleValue(1.0);
var wingfold_Node = self.getNode("surface-positions/wing-fold-pos-norm", 1);
wingfold_Node.setDoubleValue(1.0);
var rpm0_Node = self.getNode("engines/engine[0]/rpm", 1);
rpm0_Node.setDoubleValue(0.15);
var rpm0_Node = self.getNode("engines/engine[0]/rpm", 1);
rpm0_Node.setDoubleValue(0.15);
var rpm1_Node = self.getNode("engines/engine[1]/rpm", 1);
rpm1_Node.setDoubleValue(0.17);
var rpm1_Node = self.getNode("engines/engine[1]/rpm", 1);
rpm1_Node.setDoubleValue(0.17);
########
# the main loop
########
# the main loop
var update = func {
var turn = control_node.getValue();
var value = wind_speed_Node.getValue();
setprop("/environment/Vinson/rel-wind-speed-kts", value);
value = speed_Node.getValue();
setprop("/environment/Vinson/spd-kt", value);
value = hdg_Node.getValue();
setprop("/environment/Vinson/hdg-deg", value);
var update = func {
var turn = control_node.getValue();
var value = wind_speed_Node.getValue();
setprop("/environment/Vinson/rel-wind-speed-kts", value);
value = speed_Node.getValue();
setprop("/environment/Vinson/spd-kt", value);
value = hdg_Node.getValue();
setprop("/environment/Vinson/hdg-deg", value);
value = getprop("/sim/rendering/rembrandt/enabled");
rembrandt_node.setBoolValue(value);
value = getprop("/sim/current-view/deck-park");
deckPark_node.setBoolValue(value);
value = getprop("/sim/time/sun-angle-rad");
sunAngleRad_node.setValue(value);
value = getprop("/sim/rendering/rembrandt/enabled");
rembrandt_node.setBoolValue(value);
value = getprop("/sim/current-view/deck-park");
deckPark_node.setBoolValue(value);
value = getprop("/sim/time/sun-angle-rad");
sunAngleRad_node.setValue(value);
if (turn_old != turn){
turn_old = turn;
move_whips();
}
if (turn_old != turn){
turn_old = turn;
move_whips();
}
settimer(update,0);
}
settimer(update,0);
}
var move_whips = func {
var whip_pos = pos_node.getValue();
if (whip_pos &lt;=1 and whip_pos &gt;=0 and turn_old == 0){
var move_whips = func {
var whip_pos = pos_node.getValue();
if (whip_pos &lt;=1 and whip_pos &gt;=0 and turn_old == 0){
whip_pos += 0.001;
if (whip_pos &gt; 1)
@ -119,13 +132,16 @@ apart from most of the very excellent textures, remain. -->
########
#start the main loop
update();
</load>
]]>
</load>
</nasal>
<unload>
#print("UNLOAD Vinson ", cmdarg().getPath());
</unload>
<![CDATA[
#print("UNLOAD Vinson ", cmdarg().getPath());
an_spn_46_timer.stop();
]]>
</unload>
<!-- crew to be added later
<model>

View file

@ -150,6 +150,7 @@ var PFD_Device =
obj.svg = svg;
obj.current_page = nil;
obj.pages = [];
obj.page_index = {};
obj.buttons = setsize([], num_menu_buttons);
for(var idx = 0; idx < num_menu_buttons; idx += 1)
@ -199,6 +200,7 @@ var PFD_Device =
{
var np = PFD_Page.new(me.svg, title, layer_id, me);
append(me.pages, np);
me.page_index[layer_id] = np;
np.setVisible(0);
return np;
},

260
Nasal/emesary.nas Normal file
View file

@ -0,0 +1,260 @@
#---------------------------------------------------------------------------
#
# Title : EMESARY inter-object communication
#
# File Type : Implementation File
#
# Description : Provides generic inter-object communication. For an object to receive a message it
# : must first register with an instance of a Transmitter, and provide a Receive method
#
# : To send a message use a Transmitter with an object. That's all there is to it.
#
# References : http://chateau-logic.com/content/emesary-nasal-implementation-flightgear
# : http://www.chateau-logic.com/content/class-based-inter-object-communication
# : http://chateau-logic.com/content/emesary-efficient-inter-object-communication-using-interfaces-and-inheritance
# : http://chateau-logic.com/content/c-wpf-application-plumbing-using-emesary
#
# Author : Richard Harrison (richard@zaretto.com)
#
# Creation Date : 29 January 2016
#
# Version : 4.8
#
# Copyright © 2016 Richard Harrison Released under GPL V2
#
#---------------------------------------------------------------------------*/
var __emesaryUniqueId = 14; # 0-15 are reserved, this way the global transmitter will be 15.
# Transmitters send notifications to all recipients that are registered.
var Transmitter =
{
ReceiptStatus_OK : 0, # Processing completed successfully
ReceiptStatus_Fail : 1, # Processing resulted in at least one failure
ReceiptStatus_Abort : 2, # Fatal error, stop processing any further recipieints of this message. Implicitly failed.
ReceiptStatus_Finished : 3, # Definitive completion - do not send message to any further recipieints
ReceiptStatus_NotProcessed : 4,# Return value when method doesn't process a message.
ReceiptStatus_Pending : 5, # Message sent with indeterminate return status as processing underway
ReceiptStatus_PendingFinished : 6,# Message definitively handled, status indeterminate. The message will not be sent any further
# create a new transmitter. shouldn't need many of these
new: func(_ident)
{
var new_class = { parents: [Transmitter]};
new_class.Recipients = [];
new_class.Ident = _ident;
__emesaryUniqueId += 1;
new_class.UniqueId = __emesaryUniqueId;
return new_class;
},
# Add a recipient to receive notifications from this transmitter
Register: func (recipient)
{
append(me.Recipients, recipient);
},
# Stops a recipient from receiving notifications from this transmitter.
DeRegister: func(todelete_recipient)
{
var out_idx = 0;
var element_deleted = 0;
for (var idx = 0; idx < size(me.Recipients); idx += 1)
{
if (me.Recipients[idx] != todelete_recipient)
{
me.Recipients[out_idx] = me.Recipients[idx];
out_idx = out_idx + 1;
}
else
element_deleted = 1;
}
if (element_deleted)
pop(me.Recipients);
},
RecipientCount: func
{
return size(me.Recipients);
},
PrintRecipients: func
{
print("Recpient list for ",me.Ident,"(",me.UniqueId,")");
for (var idx = 0; idx < size(me.Recipients); idx += 1)
print("Recpient ",idx," ",me.Recipients[idx].Ident," (",me.Recipients[idx].UniqueId,")");
},
# Notify all registered recipients. Stop when receipt status of abort or finished are received.
# The receipt status from this method will be
# - OK > message handled
# - Fail > message not handled. A status of Abort from a recipient will result in our status
# being fail as Abort means that the message was not and cannot be handled, and
# allows for usages such as access controls.
NotifyAll: func(message)
{
var return_status = Transmitter.ReceiptStatus_NotProcessed;
foreach (var recipient; me.Recipients)
{
if (recipient.RecipientActive)
{
var rstat = recipient.Receive(message);
if(rstat == Transmitter.ReceiptStatus_Fail)
{
return_status = Transmitter.ReceiptStatus_Fail;
}
elsif(rstat == Transmitter.ReceiptStatus_Pending)
{
return_status = Transmitter.ReceiptStatus_Pending;
}
elsif(rstat == Transmitter.ReceiptStatus_PendingFinished)
{
return rstat;
}
# elsif(rstat == Transmitter.ReceiptStatus_NotProcessed)
# {
# ;
# }
elsif(rstat == Transmitter.ReceiptStatus_OK)
{
if (return_status == Transmitter.ReceiptStatus_NotProcessed)
return_status = rstat;
}
elsif(rstat == Transmitter.ReceiptStatus_Abort)
{
return Transmitter.ReceiptStatus_Abort;
}
elsif(rstat == Transmitter.ReceiptStatus_Finished)
{
return Transmitter.ReceiptStatus_OK;
}
}
}
return return_status;
},
# Returns true if a return value from NotifyAll is to be considered a failure.
IsFailed: func(receiptStatus)
{
# Failed is either Fail or Abort.
# NotProcessed isn't a failure because it hasn't been processed.
if (receiptStatus == Transmitter.ReceiptStatus_Fail or receiptStatus == Transmitter.ReceiptStatus_Abort)
return 1;
return 0;
}
};
#
#
# Base class for Notifications. By convention a Notification has a type and a value.
# SubClasses can add extra properties or methods.
# Properties:
# Ident : Generic message identity. Can be an ident, or for simple messages a value that needs transmitting.
# Type : Message Type
# IsDistinct : non zero if this message supercedes previous messages of this type.
# Distinct messages are usually sent often and self contained
# (i.e. no relative state changes such as toggle value)
# Messages that indicate an event (such as after a pilot action)
# will usually be non-distinct. So an example would be gear/up down
# or ATC acknowledgements that all need to be transmitted
# The IsDistinct is important for any messages that are bridged over MP as
# only the most recently sent distinct message will be transmitted over MP
var Notification =
{
new: func(_type, _ident)
{
var new_class = { parents: [Notification]};
new_class.Ident = _ident;
new_class.Type = _type;
new_class.IsDistinct = 1;
new_class.FromIncomingBridge = 0;
new_class.Callsign = nil;
return new_class;
},
};
# Inherit or implement class with the same signatures to receive messages.
var Recipient =
{
new: func(_ident)
{
var new_class = { parents: [Recipient]};
if (_ident == nil or _ident == "")
{
_ident = id(new_class);
print("ERROR: Ident required when creating a recipient, defaulting to ",_ident);
}
Recipient.construct(_ident, new_class);
},
construct: func(_ident, new_class)
{
new_class.Ident = _ident;
new_class.RecipientActive = 1;
__emesaryUniqueId += 1;
new_class.UniqueId = __emesaryUniqueId;
new_class.Receive = func(notification)
{
# warning if required function not
print("Emesary Error: Receive function not implemented in recipient ",me.Ident);
return Transmitter.ReceiptStatus_NotProcessed;
};
return new_class;
},
};
#
# Instantiate a Global Transmitter, this is a convenience and a known starting point. Generally most classes will
# use this transmitters, however other transmitters can be created and merely use the global transmitter to discover each other
var GlobalTransmitter = Transmitter.new("GlobalTransmitter");
var TransferCoord =
{
encode : func(v)
{
return mp_broadcast.Binary.encodeCoord(v);
},
decode : func(v)
{
return mp_broadcast.Binary.decodeCoord(v);
}
};
var TransferByte =
{
encode : func(v)
{
return mp_broadcast.Binary.encodeByte(v);
},
decode : func(v)
{
return mp_broadcast.Binary.decodeByte(v);
}
};
var TransferInt =
{
encode : func(v)
{
return mp_broadcast.Binary.encodeInt(v);
},
decode : func(v)
{
return mp_broadcast.Binary.decodeInt(v);
}
};
var TransferDouble =
{
encode : func(v)
{
return mp_broadcast.Binary.encodeDouble(v);
},
decode : func(v)
{
return mp_broadcast.Binary.decodeDouble(v);
}
};

319
Nasal/emesary_mp_bridge.nas Normal file
View file

@ -0,0 +1,319 @@
#
# bridge message rules;
# - if distinct must be absolute and self contained as a later message will supercede any earlier ones in the outgoing queue.
# - use the message type and ident to identify distinct messages
# The outgoing 'port' is a multiplay/generic/string index.
# - ! is used as a seperator between the elements that are used to send the notification (typeid, sequence, notification)
# - ; is used to seperate serialzied elemnts of the notification
#
# Outgoing messages are sent in a scheduled manner, usually once per second, and each message has a lifetime (to allow for propogation to
# all clients over UDP). Clients will ignore messages that they have already received (based on the sequence id).
#
# The incoming bridge will usually be created part of the aircraft model file; it is important to understand that each AI/MP model will have an incoming bridge
# as each element in /ai/models needs its own bridge to keep up with the incoming sequence id. This scheme may not work properly as it relies on the model being
# loaded which may only happen when visible so it may be necessary to track AI models in a seperate instantiatable incoming bridge manager.
#
# The outgoing bridge would usually be created within the aircraft loading Nasal.
var OutgoingMPBridge =
{
new: func(_ident, _notifications_to_bridge=nil, _mpidx=19, _root="", _transmitter=nil)
{
if (_transmitter == nil)
_transmitter = emesary.GlobalTransmitter;
print("OutgoingMPBridge created for "~_ident);
var new_class = emesary.Recipient.new("OutgoingMPBridge "~_ident);
new_class.MessageIndex = 100;
# foreach (var notification; _notifications_to_bridge)
# new_class.NotificationsToBridge = notification.new(;
if(_notifications_to_bridge == nil)
new_class.NotificationsToBridge = [];
else
new_class.NotificationsToBridge = _notifications_to_bridge;
new_class.MPout = "";
new_class.MPidx = _mpidx;
new_class.MessageLifeTime = 10; # seconds
new_class.OutgoingList = [];
new_class.Transmitter = _transmitter;
new_class.TransmitRequired=0;
new_class.Transmitter.Register(new_class);
new_class.MpVariable = _root~"sim/multiplay/generic/string["~new_class.MPidx~"]";
new_class.TransmitterActive = 0;
new_class.TransmitTimer =
maketimer(6, func
{
if(new_class.TransmitterActive)
new_class.Transmit();
new_class.TransmitTimer.restart(1);
});
new_class.TransmitTimer.restart(1);
new_class.Delete = func
{
if (me.Transmitter != nil) {
me.Transmitter.DeRegister(me);
me.Transmitter = nil;
}
};
new_class.AddMessage = func(m)
{
append(me.NotificationsToBridge, m);
};
#-------------------------------------------
# Receive override:
new_class.Receive = func(notification)
{
if (notification.FromIncomingBridge)
return emesary.Transmitter.ReceiptStatus_NotProcessed;
#print("Receive ",notification.Type," (",notification.Ident);
for (var idx = 0; idx < size(me.NotificationsToBridge); idx += 1)
{
if(me.NotificationsToBridge[idx].Type == notification.Type)
{
me.MessageIndex += 1;
notification.MessageExpiryTime = systime()+me.MessageLifeTime;
notification.BridgeMessageId = me.MessageIndex;
notification.BridgeMessageTypeId = idx;
#
# The message key is a composite of the type and ident to allow for multiple senders
# of the same message type.
notification.BridgeMessageTypeKey = notification.Type~"."~notification.Ident;
#print("Received ",notification.BridgeMessageTypeKey," expire=",notification.MessageExpiryTime);
me.AddToOutgoing(notification);
return emesary.Transmitter.ReceiptStatus_Pending;
}
}
return emesary.Transmitter.ReceiptStatus_NotProcessed;
};
new_class.AddToOutgoing = func(notification)
{
if (notification.IsDistinct)
{
for (var idx = 0; idx < size(me.OutgoingList); idx += 1)
{
if(me.OutgoingList[idx].BridgeMessageTypeKey == notification.BridgeMessageTypeKey)
{
#print("Update ",me.OutgoingList[idx].BridgeMessageTypeKey);
me.OutgoingList[idx]= notification;
me.TransmitterActive = size(me.OutgoingList);
return;
}
}
}
#print("Added ",notification.BridgeMessageTypeKey);
append(me.OutgoingList, notification);
me.TransmitterActive = size(me.OutgoingList);
};
new_class.Transmit = func
{
var outgoing = "";
var cur_time=systime();
var out_idx = 0;
for (var idx = 0; idx < size(me.OutgoingList); idx += 1)
{
var sect = "";
var notification = me.OutgoingList[idx];
if (notification.MessageExpiryTime > cur_time)
{
var encval="";
var first_time = 1;
var eidx = 0;
foreach(var p ; notification.bridgeProperties())
{
if (encval != "")
encval = encval ~ ";";
encval = encval ~ p.getValue();
#print("Encode ",eidx,"=",encval);
eidx += 1;
}
sect = sprintf("%d!%d!%s",notification.BridgeMessageId, notification.BridgeMessageTypeId, encval);
outgoing = outgoing~sect;
me.OutgoingList[out_idx] = me.OutgoingList[idx];
# print("xmit ",idx," out=",out_idx);
out_idx += 1;
}
# else
# printf("expired ",idx,out_idx);
}
me.TransmitterActive = size(me.OutgoingList);
var del_count = size(me.OutgoingList)-out_idx;
setprop(me.MpVariable,outgoing);
# print("Set ",me.MpVariable," to ",outgoing);
# print("outgoingList : ",out_idx, " ", size(me.OutgoingList), " del count=",del_count);
for(var del_i=0; del_i < del_count; del_i += 1)
pop(me.OutgoingList);
};
return new_class;
},
};
#
#
# one of these for each model instantiated in the model XML - it will
# route messages to
var IncomingMPBridge =
{
new: func(_ident, _notifications_to_bridge=nil, _mpidx=19, _transmitter=nil)
{
if (_transmitter == nil)
_transmitter = emesary.GlobalTransmitter;
print("IncominggMPBridge created for "~_ident);
var new_class = emesary.Transmitter.new("IncominggMPBridge "~_ident);
new_class.IncomingMessageIndex = 100;
if(_notifications_to_bridge == nil)
new_class.NotificationsToBridge = [];
else
new_class.NotificationsToBridge = _notifications_to_bridge;
new_class.MPout = "";
new_class.MPidx = _mpidx;
new_class.MessageLifeTime = 10; # seconds
new_class.OutgoingList = [];
new_class.Transmitter = _transmitter;
new_class.MpVariable = "";
new_class.Connect = func(_root)
{
me.MpVariable = _root~"sim/multiplay/generic/string["~new_class.MPidx~"]";
me.Callsign = getprop(_root~"callsign");
print("bridge ",me.MpVariable, " callsign ",me.Callsign);
setlistener(me.MpVariable, func(v)
{
me.ProcessIncoming(v.getValue());
});
};
new_class.AddMessage = func(m)
{
append(me.NotificationsToBridge, m);
};
new_class.Remove = func
{
print("Emesary IncomingMPBridge Remove() ",me.Ident);
me.Transmitter.DeRegister(me);
};
#-------------------------------------------
# Receive override:
new_class.ProcessIncoming = func(encoded_val)
{
if (encoded_val == "")
return;
# print("ProcessIncoming ", encoded_val);
var encoded_notifications = split(";", encoded_val);
for (var idx = 0; idx < size(encoded_notifications); idx += 1)
{
# get the message parts
var encoded_notification = split("!", encoded_val);
if (size(encoded_notification) < 3)
print("Error: emesary.IncomingBridge.ProcessIncoming bad msg ",encoded_val);
else
{
var msg_idx = encoded_notification[0];
var msg_type_id = encoded_notification[1];
if (msg_type_id >= size(me.NotificationsToBridge))
{
print("Error: emesary.IncomingBridge.ProcessIncoming invalid type_id ",msg_type_id);
}
else
{
var msg = me.NotificationsToBridge[msg_type_id];
var msg_notify = encoded_notification[2];
print("received idx=",msg_idx," ",msg_type_id,":",msg.Type);
if(msg_idx <= me.IncomingMessageIndex)
print(" **Already processed");
else
{
# raise notification
var bridged_notification = msg; #emesary.Notification.new(msg.Type,"BridgedMessage");
# populate fields
var bridgedProperties = msg.bridgeProperties();
var encvals=split(";", msg_notify);
for (var bpi = 0; bpi < size(encvals); bpi += 1) {
if (bpi < size(bridgedProperties)) {
var bp = bridgedProperties[bpi];
print("encval ",bpi,"=",encvals[bpi]);
if (encvals[bpi] != ";" and encvals[bpi] != "") {
var bp = bridgedProperties[bpi];
bp.setValue(encvals[bpi]);
}
#else
#print("EMPTY encoded ",bpi," empty");
} else
print("Error: emesary.IncomingBridge.ProcessIncoming: supplementary encoded value at",bpi);
}
if (bridged_notification.Ident == "none")
bridged_notification.Ident = "mp-bridge";
bridged_notification.FromIncomingBridge = 1;
bridged_notification.Callsign = me.Callsign;
me.Transmitter.NotifyAll(bridged_notification);
me.IncomingMessageIndex = msg_idx;
}
}
}
}
}
foreach(var n; new_class.NotificationsToBridge)
{
print("IncomingBridge: ",n.Type);
}
return new_class;
},
startMPBridge : func(notification_list)
{
var incomingBridgeList = {};
setlistener("/ai/models/model-added", func(v)
{
#Model added /ai[0]/models[0]/multiplayer[0]
var path = v.getValue();
print("Model added ",path);
if (find("/multiplayer",path) > 0)
{
var callsign = getprop(path~"/callsign");
print("Creating Emesary MPBridge for ",callsign);
if (callsign == "" or callsign == nil)
callsign = path;
var incomingBridge = emesary_mp_bridge.IncomingMPBridge.new(callsign~"mp", notification_list, 19);
incomingBridge.Connect(path~"/");
incomingBridgeList[path] = incomingBridge;
}
});
setlistener("/ai/models/model-removed", func(v){
print("Model removed ",v.getValue());
var path = v.getValue();
var bridge = incomingBridgeList[path];
if (bridge != nil)
{
bridge.Remove();
incomingBridgeList[path]=nil;
}
});
},
};
#to setup bridges;
#io.load_nasal(getprop("/sim/fg-root") ~ "/Nasal/emesary_mp_bridge.nas");
#var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-15mp");
#outgoingBridge.AddMessage(an_spn_46.ANSPN46ActiveNotification.new("template"));
#incomingBridge = emesary_mp_bridge.IncomingMPBridge.new("F-15mp");
#incomingBridge.AddMessage(an_spn_46.ANSPN46ActiveNotification.new("template"));
#var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-15mp");
#outgoingBridge.AddMessage(an_spn_46.ANSPN46ActiveNotification.new("template"));
# var m = emesary.notifications.TacticalNotification("rr",2);
# emesary.GlobalTransmitter.NotifyAll(m);
#var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-15mp");
#outgoingBridge.AddMessage(notifications.TacticalNotification.new(nil));
# var m = emesary.notifications.TacticalNotification("rr",2);
# emesary.GlobalTransmitter.NotifyAll(m);

49
Nasal/notifications.nas Normal file
View file

@ -0,0 +1,49 @@
#---------------------------------------------------------------------------
#
# Title : EMESARY flightgear standardised notifications
#
# File Type : Implementation File
#
# Description : Messages that are applicable across all models and do not specifically relate to a single sysmte
# : - mostly needed when using the mutiplayer bridge
#
# Author : Richard Harrison (richard@zaretto.com)
#
# Creation Date : 06 April 2016
#
# Version : 4.8
#
# Copyright © 2016 Richard Harrison Released under GPL V2
#
#---------------------------------------------------------------------------*/
var TacticalNotification =
{
new: func(_ident=nil, _kind=0)
{
if(_ident==nil)
_ident="none";
var new_class = emesary.Notification.new("TacticalNotification", _ident);
new_class.Kind = _kind;
new_class.Position=geo.aircraft_position();
new_class.IsDistinct = 0;
new_class.bridgeProperties = func
{
return
[
{
getValue:func{return emesary.TransferCoord.encode(new_class.Position);},
setValue:func(v){new_class.Position=emesary.TransferCoord.decode(v);},
},
{
getValue:func{return emesary.TransferByte.encode(new_class.Kind);},
setValue:func(v){new_class.Kind=emesary.TransferByte.decode(v);},
},
];
};
return new_class;
},
};