Added Emesary, AN/SPN-46 ACLS, and a Multiplayer Bridge
Emesary is a simple and efficient class based interobject communcation system to allow decoupled disparate parts of a system to function together without knowing about each. It allows decoupling and removal of dependencies by using notifications to cause actions or to query values. Emesary is all about decoupling and removing dependecies, and improving the structure of code. Using Emesary you can more easily define the what rather than the how. By using what is essential an event driven system it is easy to add or remove modules, and also for extra modules to be inserted that the rest of the aircraft knows nothing about (e.g. FGCamera or the Walker). see: http://chateau-logic.com/content/emesary-nasal-implementation-flightgear The AN/SPN-46 is an ACLS implementation using Emesary. ACLS is the Navy's version of ILS. --------------------------------------------------------------------------- There is also support for transmitting messages over MP which allows models to communicate with each other in a decoupled method using notifications. What happens is that bridged messags simply arrive at the Receive method of recipients in other models on other running instances without the need to do anything special.
This commit is contained in:
commit
f171e41632
8 changed files with 1581 additions and 43 deletions
358
Aircraft/Generic/an_spn_46.nas
Normal file
358
Aircraft/Generic/an_spn_46.nas
Normal file
|
@ -0,0 +1,358 @@
|
|||
#---------------------------------------------------------------------------
|
||||
#
|
||||
# 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 ???
|
||||
}
|
||||
};
|
||||
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)
|
||||
{
|
||||
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.NotificationType == "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;
|
||||
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;
|
||||
},
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -10,6 +10,7 @@ apart from most of the very excellent textures, remain. -->
|
|||
|
||||
<nasal>
|
||||
<load>
|
||||
<![CDATA[
|
||||
print("LOAD Vinson ", cmdarg().getPath());
|
||||
|
||||
var fg_root = getprop("/sim/fg-root");
|
||||
|
@ -69,62 +70,76 @@ apart from most of the very excellent textures, remain. -->
|
|||
# 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 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 <=1 and whip_pos >=0 and turn_old == 0){
|
||||
whip_pos += 0.001;
|
||||
var whip_pos = pos_node.getValue();
|
||||
if (whip_pos <=1 and whip_pos >=0 and turn_old == 0){
|
||||
whip_pos += 0.001;
|
||||
|
||||
if (whip_pos > 1)
|
||||
whip_pos = 1;
|
||||
if (whip_pos > 1)
|
||||
whip_pos = 1;
|
||||
|
||||
pos_node.setValue(whip_pos);
|
||||
settimer(move_whips,0);
|
||||
} elsif (whip_pos <=1 and whip_pos >=0 and turn_old == 1) {
|
||||
whip_pos -= 0.001;
|
||||
pos_node.setValue(whip_pos);
|
||||
settimer(move_whips,0);
|
||||
} elsif (whip_pos <=1 and whip_pos >=0 and turn_old == 1) {
|
||||
whip_pos -= 0.001;
|
||||
|
||||
if (whip_pos < 0)
|
||||
whip_pos = 0;
|
||||
if (whip_pos < 0)
|
||||
whip_pos = 0;
|
||||
|
||||
pos_node.setValue(whip_pos);
|
||||
settimer(move_whips,0);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
pos_node.setValue(whip_pos);
|
||||
settimer(move_whips,0);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
########
|
||||
#start the main loop
|
||||
update();
|
||||
|
||||
########
|
||||
#start the main loop
|
||||
update();
|
||||
|
||||
</load>
|
||||
########
|
||||
# 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("Vinson", 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);
|
||||
]]>
|
||||
</load>
|
||||
</nasal>
|
||||
|
||||
<unload>
|
||||
#print("UNLOAD Vinson ", cmdarg().getPath());
|
||||
<![CDATA[
|
||||
#print("UNLOAD Vinson ", cmdarg().getPath());
|
||||
]]>
|
||||
</unload>
|
||||
|
||||
<!-- crew to be added later
|
||||
|
|
346
Nasal/emesary.nas
Normal file
346
Nasal/emesary.nas
Normal file
|
@ -0,0 +1,346 @@
|
|||
#---------------------------------------------------------------------------
|
||||
#
|
||||
# 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("Emesary: Recipient list for ",me.Ident,"(",me.UniqueId,")");
|
||||
for (var idx = 0; idx < size(me.Recipients); idx += 1)
|
||||
print("Emesary: Recipient[",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.
|
||||
# NotificationType : Notification 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.NotificationType = _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("Emesary 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");
|
||||
|
||||
#
|
||||
#
|
||||
# This is basically a base64 like encode except we just use alphanumerics which gives us a base62 encode.
|
||||
var BinaryAsciiTransfer =
|
||||
{
|
||||
alphabet : "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
encodeInt : func(num)
|
||||
{
|
||||
if (num == 0)
|
||||
return substr(BinaryAsciiTransfer.alphabet,0,1);
|
||||
|
||||
var arr="";
|
||||
var _base = size(BinaryAsciiTransfer.alphabet);
|
||||
while(num > 0)
|
||||
{
|
||||
var rem = math.mod(num,_base);
|
||||
num = int(num / _base);
|
||||
arr =substr(BinaryAsciiTransfer.alphabet, rem,1) ~ arr;
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
decodeInt : func(v)
|
||||
{
|
||||
var _base = size(BinaryAsciiTransfer.alphabet);
|
||||
var power = size(v) - 1;
|
||||
var num = 0;
|
||||
|
||||
var idx = 0;
|
||||
for(var idx=0; idx < size(v); idx += 1)
|
||||
{
|
||||
var c = substr(v,idx,1);
|
||||
var cc = find(c,BinaryAsciiTransfer.alphabet);
|
||||
if (cc < 0)
|
||||
return nil;
|
||||
num += int(cc * math.exp(math.ln(_base) * power));
|
||||
|
||||
power -= 1;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
};
|
||||
|
||||
var TransferCoord =
|
||||
{
|
||||
# 28 bits = 268435456 (268 435 456)
|
||||
# to transfer lat lon (360 degree range) 268435456/360=745654
|
||||
# we could use different factors for lat lon due to the differing range, however
|
||||
# this will be fine.
|
||||
encode : func(v)
|
||||
{
|
||||
return BinaryAsciiTransfer.encodeInt( (v.lat()+90)*745654) ~ "." ~
|
||||
BinaryAsciiTransfer.encodeInt((v.lon()+180)*745654) ~ "." ~
|
||||
BinaryAsciiTransfer.encodeInt((v.alt()+1400)*100);
|
||||
}
|
||||
,
|
||||
decode : func(v)
|
||||
{
|
||||
var parts = split(".", v);
|
||||
var lat = (BinaryAsciiTransfer.decodeInt(parts[0]) / 745654)-90;
|
||||
var lon = (BinaryAsciiTransfer.decodeInt(parts[1]) / 745654)-180;
|
||||
var alt = (BinaryAsciiTransfer.decodeInt(parts[2]) / 100)-1400;
|
||||
return geo.Coord.new().set_latlon(lat, lon).set_alt(alt);
|
||||
}
|
||||
};
|
||||
|
||||
var TransferString =
|
||||
{
|
||||
#
|
||||
# just to pack a valid range and keep the lower and very upper control codes for seperators
|
||||
# that way we don't need to do anything special to encode the string.
|
||||
getalphanumericchar : func(v)
|
||||
{
|
||||
if (find(v,"-./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_abcdefghijklmnopqrstuvwxyz") > 0)
|
||||
return v;
|
||||
return nil;
|
||||
},
|
||||
encode : func(v)
|
||||
{
|
||||
var l = size(v);
|
||||
if (l > 16)
|
||||
l = 16;
|
||||
var rv = BinaryAsciiTransfer.encodeInt(l);
|
||||
|
||||
for(var ii = 0; ii < l; ii = ii + 1)
|
||||
{
|
||||
ev = TransferString.getalphanumericchar(substr(v,ii,1));
|
||||
if (ev != nil)
|
||||
rv = rv ~ ev;
|
||||
}
|
||||
return rv;
|
||||
},
|
||||
decode : func(v)
|
||||
{
|
||||
var l = BinaryAsciiTransfer.decodeInt(v);
|
||||
var rv = substr(v,1,l-1);
|
||||
return rv;
|
||||
}
|
||||
};
|
||||
|
||||
var TransferByte =
|
||||
{
|
||||
encode : func(v)
|
||||
{
|
||||
return BinaryAsciiTransfer.encodeInt(v);
|
||||
},
|
||||
decode : func(v)
|
||||
{
|
||||
return BinaryAsciiTransfer.decodeInt(v);
|
||||
}
|
||||
};
|
||||
|
||||
var TransferInt =
|
||||
{
|
||||
encode : func(v)
|
||||
{
|
||||
return BinaryAsciiTransfer.encodeInt(v);
|
||||
},
|
||||
decode : func(v)
|
||||
{
|
||||
return BinaryAsciiTransfer.decodeInt(v);
|
||||
}
|
||||
};
|
||||
|
||||
var TransferFixedDouble =
|
||||
{
|
||||
encode : func(v, precision=4)
|
||||
{
|
||||
return BinaryAsciiTransfer.encodeInt(v*math.exp(math.ln(10) * precision));
|
||||
},
|
||||
decode : func(v, precision=4)
|
||||
{
|
||||
return BinaryAsciiTransfer.decodeInt(v)/math.exp(math.ln(10) * precision);
|
||||
}
|
||||
};
|
372
Nasal/emesary_mp_bridge.nas
Normal file
372
Nasal/emesary_mp_bridge.nas
Normal file
|
@ -0,0 +1,372 @@
|
|||
#---------------------------------------------------------------------------
|
||||
#
|
||||
# Title : EMESARY multiplayer bridge
|
||||
#
|
||||
# File Type : Implementation File
|
||||
#
|
||||
# Description : Bridges selected emesary notifications over MP
|
||||
# : 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
|
||||
#
|
||||
# Author : Richard Harrison (richard@zaretto.com)
|
||||
#
|
||||
# Creation Date : 04 April 2016
|
||||
#
|
||||
# Version : 4.8
|
||||
#
|
||||
# Copyright © 2016 Richard Harrison Released under GPL V2
|
||||
#
|
||||
#---------------------------------------------------------------------------*/
|
||||
|
||||
# Example of connecting an incoming and outgoing bridge (should reside inside an aircraft nasal file)
|
||||
#
|
||||
# var routedNotifications = [notifications.TacticalNotification.new(nil)];
|
||||
# var incomingBridge = emesary_mp_bridge.IncomingMPBridge.startMPBridge(routedNotifications);
|
||||
# var outgoingBridge = emesary_mp_bridge.OutgoingMPBridge.new("F-15mp",routedNotifications);
|
||||
#------------------------------------------------------------------
|
||||
#
|
||||
# NOTES: Aircraft do not need to have both an incoming and outgoing bridge, but it is usual.
|
||||
# Only the notifications specified will be routed via the bridge.
|
||||
# Once routed a message will by default not be re-rerouted again by the outgoing bridge.
|
||||
# Transmit frequency and message lifetime may need to be tuned.
|
||||
# IsDistinct messages must be absolute and self contained as a later message will
|
||||
# supercede any earlier ones in the outgoing queue (possibly prior to receipt)
|
||||
# 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)
|
||||
# - There is an extra ! at the start of the message that is used to indicate protocol version.
|
||||
# - ; is used to seperate serialzied elemnts of the notification
|
||||
|
||||
# General Notes
|
||||
#----------------------------------------------------------------------
|
||||
# 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 EmesaryMPBridgeDefaultPropertyIndex=19;
|
||||
|
||||
var OutgoingMPBridge =
|
||||
{
|
||||
SeperatorChar : "!",
|
||||
StartMessageIndex : 11,
|
||||
DefaultMessageLifetime : 10,
|
||||
MPStringMaxLen: 50,
|
||||
new: func(_ident, _notifications_to_bridge=nil, _mpidx=19, _root="", _transmitter=nil)
|
||||
{
|
||||
if (_transmitter == nil)
|
||||
_transmitter = emesary.GlobalTransmitter;
|
||||
|
||||
print("OutgoingMPBridge created for "~_ident," mp=",_mpidx);
|
||||
var new_class = emesary.Recipient.new("OutgoingMPBridge "~_ident);
|
||||
|
||||
new_class.MessageIndex = OutgoingMPBridge.StartMessageIndex;
|
||||
# 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;
|
||||
|
||||
foreach(var n ; new_class.NotificationsToBridge)
|
||||
print(" bridge --> ",n.NotificationType);
|
||||
|
||||
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.TransmitFrequencySeconds = 1;
|
||||
|
||||
new_class.TransmitTimer =
|
||||
maketimer(6, func
|
||||
{
|
||||
if(new_class.TransmitterActive)
|
||||
new_class.Transmit();
|
||||
|
||||
new_class.TransmitTimer.restart(new_class.TransmitFrequencySeconds);
|
||||
});
|
||||
|
||||
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.NotificationType," ",notification.Ident);
|
||||
for (var idx = 0; idx < size(me.NotificationsToBridge); idx += 1)
|
||||
{
|
||||
if(me.NotificationsToBridge[idx].NotificationType == notification.NotificationType)
|
||||
{
|
||||
me.MessageIndex += 1;
|
||||
notification.MessageExpiryTime = systime()+me.MessageLifeTime;
|
||||
notification.BridgeMessageId = me.MessageIndex;
|
||||
notification.BridgeMessageNotificationTypeId = idx;
|
||||
#
|
||||
# The message key is a composite of the type and ident to allow for multiple senders
|
||||
# of the same message type.
|
||||
notification.BridgeMessageNotificationTypeKey = notification.NotificationType~"."~notification.Ident;
|
||||
#print("Received ",notification.BridgeMessageNotificationTypeKey," 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].BridgeMessageNotificationTypeKey == notification.BridgeMessageNotificationTypeKey)
|
||||
{
|
||||
#print("Update ",me.OutgoingList[idx].BridgeMessageNotificationTypeKey);
|
||||
me.OutgoingList[idx]= notification;
|
||||
me.TransmitterActive = size(me.OutgoingList);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#print("Added ",notification.BridgeMessageNotificationTypeKey);
|
||||
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;
|
||||
}
|
||||
# !idx!typ!encv
|
||||
sect = sprintf("%s%s%s%s%s%s",
|
||||
OutgoingMPBridge.SeperatorChar, emesary.BinaryAsciiTransfer.encodeInt(notification.BridgeMessageId),
|
||||
OutgoingMPBridge.SeperatorChar, emesary.BinaryAsciiTransfer.encodeInt(notification.BridgeMessageNotificationTypeId),
|
||||
OutgoingMPBridge.SeperatorChar, 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);
|
||||
};
|
||||
new_class.TransmitTimer.restart(new_class.TransmitFrequencySeconds);
|
||||
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," mp=",_mpidx);
|
||||
|
||||
var new_class = emesary.Transmitter.new("IncominggMPBridge "~_ident);
|
||||
|
||||
new_class.IncomingMessageIndex = OutgoingMPBridge.StartMessageIndex;
|
||||
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 = OutgoingMPBridge.DefaultMessageLifetime; # 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.CallsignPath = _root~"callsign";
|
||||
setlistener(me.MpVariable, func(v)
|
||||
{
|
||||
me.ProcessIncoming(v.getValue());
|
||||
});
|
||||
};
|
||||
new_class.GetCallsign = func
|
||||
{
|
||||
return getprop(me.CallsignPath);
|
||||
};
|
||||
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(OutgoingMPBridge.SeperatorChar, encoded_val);
|
||||
if (size(encoded_notification) < 4)
|
||||
print("Error: emesary.IncomingBridge.ProcessIncoming bad msg ",encoded_val);
|
||||
else
|
||||
{
|
||||
var msg_idx = emesary.BinaryAsciiTransfer.decodeInt(encoded_notification[1]);
|
||||
var msg_type_id = emesary.BinaryAsciiTransfer.decodeInt(encoded_notification[2]);
|
||||
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[3];
|
||||
# print("received idx=",msg_idx," ",msg_type_id,":",msg.NotificationType);
|
||||
if (msg_idx > me.IncomingMessageIndex)
|
||||
{
|
||||
# raise notification
|
||||
var bridged_notification = msg; #emesary.Notification.new(msg.NotificationType,"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];
|
||||
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.GetCallsign();
|
||||
me.Transmitter.NotifyAll(bridged_notification);
|
||||
me.IncomingMessageIndex = msg_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach(var n; new_class.NotificationsToBridge)
|
||||
{
|
||||
print("IncomingBridge: ",n.NotificationType);
|
||||
}
|
||||
return new_class;
|
||||
},
|
||||
#
|
||||
# Each multiplayer object will have its own incoming bridge. This is necessary to allow message ID
|
||||
# tracking (as the bridge knows which messages have been already processed)
|
||||
# Whenever a client connects over MP a new bridge is instantiated
|
||||
startMPBridge : func(notification_list)
|
||||
{
|
||||
var incomingBridgeList = {};
|
||||
|
||||
#
|
||||
# Create bridge whenever a client connects
|
||||
#
|
||||
setlistener("/ai/models/model-added", func(v)
|
||||
{
|
||||
# Model added will be eg: /ai[0]/models[0]/multiplayer[0]
|
||||
var path = v.getValue();
|
||||
|
||||
# Ensure we only handle multiplayer elements
|
||||
if (find("/multiplayer",path) > 0)
|
||||
{
|
||||
var callsign = getprop(path~"/callsign");
|
||||
print("Creating Emesary MPBridge for ",path);
|
||||
if (callsign == "" or callsign == nil)
|
||||
callsign = path;
|
||||
|
||||
var incomingBridge = emesary_mp_bridge.IncomingMPBridge.new(path, notification_list);
|
||||
|
||||
incomingBridge.Connect(path~"/");
|
||||
incomingBridgeList[path] = incomingBridge;
|
||||
}
|
||||
});
|
||||
|
||||
#
|
||||
# when a client disconnects remove the associated bridge.
|
||||
#
|
||||
setlistener("/ai/models/model-removed", func(v){
|
||||
var path = v.getValue();
|
||||
var bridge = incomingBridgeList[path];
|
||||
if (bridge != nil)
|
||||
{
|
||||
# print("Bridge removed for ",v.getValue());
|
||||
bridge.Remove();
|
||||
incomingBridgeList[path]=nil;
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
371
Nasal/notifications.nas
Normal file
371
Nasal/notifications.nas
Normal file
|
@ -0,0 +1,371 @@
|
|||
#---------------------------------------------------------------------------
|
||||
#
|
||||
# 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 GeoEventNotification =
|
||||
{
|
||||
new: func(_ident="none", _name="", _kind=0, _secondary_kind=0)
|
||||
{
|
||||
var new_class = emesary.Notification.new("GeoEventNotification", _ident);
|
||||
|
||||
new_class.Kind = _kind;
|
||||
new_class.Name = _name;
|
||||
new_class.SecondaryKind = _secondary_kind;
|
||||
new_class.Position = geo.aircraft_position();
|
||||
|
||||
new_class.Heading = getprop("/orientation/heading");
|
||||
new_class.u_fps = getprop("/velocities/uBody-fps");
|
||||
new_class.v_fps = getprop("/velocities/vBody-fps");
|
||||
new_class.w_fps = getprop("/velocities/wBody-fps");
|
||||
new_class.IsDistinct = 0;
|
||||
new_class.Callsign = nil; # populated automatically by the incoming bridge when routed
|
||||
|
||||
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.TransferString.encode(new_class.Name);},
|
||||
setValue:func(v){new_class.Name=emesary.TransferString.decode(v);},
|
||||
},
|
||||
{
|
||||
getValue:func{return emesary.TransferByte.encode(new_class.Kind);},
|
||||
setValue:func(v){new_class.Kind=emesary.TransferByte.decode(v);},
|
||||
},
|
||||
{
|
||||
getValue:func{return emesary.TransferByte.encode(new_class.SecondaryKind);},
|
||||
setValue:func(v){new_class.SecondaryKind=emesary.TransferByte.decode(v);},
|
||||
},
|
||||
{
|
||||
getValue:func{return emesary.TransferFixedDouble.encode(new_class.u_fps);},
|
||||
setValue:func(v){new_class.u_fps=emesary.TransferFixedDouble.decode(v);},
|
||||
},
|
||||
{
|
||||
getValue:func{return emesary.TransferFixedDouble.encode(new_class.v_fps);},
|
||||
setValue:func(v){new_class.v_fps=emesary.TransferFixedDouble.decode(v);},
|
||||
},
|
||||
{
|
||||
getValue:func{return emesary.TransferFixedDouble.encode(new_class.w_fps);},
|
||||
setValue:func(v){new_class.w_fps=emesary.TransferFixedDouble.decode(v);},
|
||||
},
|
||||
];
|
||||
};
|
||||
return new_class;
|
||||
},
|
||||
};
|
||||
#
|
||||
# Defined kinds:
|
||||
# 1 - Created
|
||||
# 2 - Moved
|
||||
# 3 - Deleted
|
||||
# 4 -
|
||||
# ----
|
||||
# Secondary kind (8 bits)
|
||||
# using the first 4 bits as the classification and the second 4 bits as the sub-classification
|
||||
#-----------
|
||||
# Type 0000 : Cargo
|
||||
# 0 0000 0000 - Vehicle
|
||||
# 1 0000 0001 - Person
|
||||
# 2 0000 0010 - 10 kg Item
|
||||
# 3 0000 0011 - 20 kg Item
|
||||
# 4 0000 0100 - 30 kg Item
|
||||
# 5 0000 0101 - 40 kg Item
|
||||
# 6 0000 0110 - 50 kg Item
|
||||
# 7 0000 0111 - 100 kg Item
|
||||
# 8 0000 1000 - 200 kg Item
|
||||
# 9 0000 1001 - 500 kg Item
|
||||
# 10 0000 1010 - 1000 kg Item
|
||||
# 11 0000 1011 - Chaff
|
||||
# 12 0000 1100 - Flares
|
||||
# 13 0000 1101 - Water (fire fighting)
|
||||
# 14 0000 1110 -
|
||||
# 15 0000 1111 - Morris Marina
|
||||
#--------
|
||||
# Type 0001 : Self propelled
|
||||
# 16 0001 0000 - X-2
|
||||
# 17 0001 0001 - X-15
|
||||
# 18 0001 0010 - X-24
|
||||
# 19 0001 0011 -
|
||||
# 20 0001 0100 -
|
||||
# 21 0001 0101 -
|
||||
# 22 0001 0110 -
|
||||
# 23 0001 0111 -
|
||||
# 24 0001 1000 -
|
||||
# 25 0001 1001 -
|
||||
# 26 0001 1010 -
|
||||
# 27 0001 1011 -
|
||||
# 28 0001 1100 -
|
||||
# 29 0001 1101 -
|
||||
# 30 0001 1110 -
|
||||
# 31 0001 1111 -
|
||||
#--------
|
||||
# Type 0010 : Aircraft Damage (e.g space shuttle re-entry or during launch)
|
||||
# 32 0010 0000 - Engine 1
|
||||
# 33 0010 0001 - Engine 2
|
||||
# 34 0010 0010 - Engine 3
|
||||
# 35 0010 0011 - Engine 4
|
||||
# 36 0010 0100 - Engine 5
|
||||
# 37 0010 0101 - Engine 6
|
||||
# 38 0010 0110 - Engine 7
|
||||
# 39 0010 0111 - Engine 8
|
||||
# 40 0010 1000 - Vertical Tail Right
|
||||
# 41 0010 1001 - Left Wing
|
||||
# 42 0010 1010 - Right Wing
|
||||
# 43 0010 1011 - Horizontal Tail Left
|
||||
# 44 0010 1100 - Horizontal Tail Right
|
||||
# 45 0010 1101 - Fuselage Front
|
||||
# 46 0010 1110 - Fuselage Center
|
||||
# 47 0010 1111 - Fuselage Back
|
||||
#--------
|
||||
# Type 0011 : External stores
|
||||
# 48 0011 0000 - Drop Tank 1
|
||||
# 49 0011 0001 - Drop Tank 2
|
||||
# 50 0011 0010 - Drop Tank 3
|
||||
# 51 0011 0011 - Drop Tank 4
|
||||
# 52 0011 0100 -
|
||||
# 53 0011 0101 -
|
||||
# 54 0011 0110 -
|
||||
# 55 0011 0111 -
|
||||
# 56 0011 1000 -
|
||||
# 57 0011 1001 -
|
||||
# 58 0011 1010 -
|
||||
# 59 0011 1011 -
|
||||
# 60 0011 1100 -
|
||||
# 61 0011 1101 -
|
||||
# 62 0011 1110 -
|
||||
# 63 0011 1111 -
|
||||
#--------
|
||||
# Type 0100 :
|
||||
# 64 0100 0000 -
|
||||
# 65 0100 0001 -
|
||||
# 66 0100 0010 -
|
||||
# 67 0100 0011 -
|
||||
# 68 0100 0100 -
|
||||
# 69 0100 0101 -
|
||||
# 70 0100 0110 -
|
||||
# 71 0100 0111 -
|
||||
# 72 0100 1000 -
|
||||
# 73 0100 1001 -
|
||||
# 74 0100 1010 -
|
||||
# 75 0100 1011 -
|
||||
# 76 0100 1100 -
|
||||
# 77 0100 1101 -
|
||||
# 78 0100 1110 -
|
||||
# 79 0100 1111 -
|
||||
#--------
|
||||
# Type 0101 : Models/Geometry items
|
||||
# 80 0101 0000 - Aim91x.ac
|
||||
# 81 0101 0001 - Bomb-500lbs-MC/bomb-500lbs-mc.ac
|
||||
# 82 0101 0010 - Clemenceau/tracteur.ac
|
||||
# 83 0101 0011 - Crater/crater.ac
|
||||
# 84 0101 0100 - Ensign.ac
|
||||
# 85 0101 0101 - Nimitz/Models/phalanx.ac
|
||||
# 86 0101 0110 - Nimitz/Models/phalanx.xml
|
||||
# 87 0101 0111 - Nimitz/Models/sea-sparrow.ac
|
||||
# 88 0101 1000 - Nimitz/Models/sea-sparrow.xml
|
||||
# 89 0101 1001 - RP-3/RP-3.ac
|
||||
# 90 0101 1010 - RP-3/crater.ac
|
||||
# 91 0101 1011 - container_carrier.ac
|
||||
# 92 0101 1100 - droptank_300_gal.ac
|
||||
# 93 0101 1101 - flare.ac
|
||||
# 94 0101 1110 - load.ac
|
||||
# 95 0101 1111 - mk82.ac
|
||||
#--------
|
||||
# Type 0110 : Models/Geometry items
|
||||
# 96 0110 0000 - puff.ac
|
||||
# 97 0110 0001 - rocket.ac
|
||||
# 98 0110 0010 - tracer.ac
|
||||
# 99 0110 0011 - tracer2.ac
|
||||
# 100 0110 0100 -
|
||||
# 101 0110 0101 -
|
||||
# 102 0110 0110 -
|
||||
# 103 0110 0111 -
|
||||
# 104 0110 1000 -
|
||||
# 105 0110 1001 -
|
||||
# 106 0110 1010 -
|
||||
# 107 0110 1011 -
|
||||
# 108 0110 1100 -
|
||||
# 109 0110 1101 -
|
||||
# 110 0110 1110 -
|
||||
# 111 0110 1111 -
|
||||
#--------
|
||||
# Type 0111 : Models/Geometry items
|
||||
# 112 0111 0000 -
|
||||
# 113 0111 0001 -
|
||||
# 114 0111 0010 -
|
||||
# 115 0111 0011 -
|
||||
# 116 0111 0100 -
|
||||
# 117 0111 0101 -
|
||||
# 118 0111 0110 -
|
||||
# 119 0111 0111 -
|
||||
# 120 0111 1000 -
|
||||
# 121 0111 1001 -
|
||||
# 122 0111 1010 -
|
||||
# 123 0111 1011 -
|
||||
# 124 0111 1100 -
|
||||
# 125 0111 1101 -
|
||||
# 126 0111 1110 -
|
||||
# 127 0111 1111 -
|
||||
#--------
|
||||
# Type 1000 : Models/Geometry items
|
||||
# 128 1000 0000 -
|
||||
# 129 1000 0001 -
|
||||
# 130 1000 0010 -
|
||||
# 131 1000 0011 -
|
||||
# 132 1000 0100 -
|
||||
# 133 1000 0101 -
|
||||
# 134 1000 0110 -
|
||||
# 135 1000 0111 -
|
||||
# 136 1000 1000 -
|
||||
# 137 1000 1001 -
|
||||
# 138 1000 1010 -
|
||||
# 139 1000 1011 -
|
||||
# 140 1000 1100 -
|
||||
# 141 1000 1101 -
|
||||
# 142 1000 1110 -
|
||||
# 143 1000 1111 -
|
||||
#--------
|
||||
# Type 1001 :
|
||||
# 144 1001 0000 -
|
||||
# 145 1001 0001 -
|
||||
# 146 1001 0010 -
|
||||
# 147 1001 0011 -
|
||||
# 148 1001 0100 -
|
||||
# 149 1001 0101 -
|
||||
# 150 1001 0110 -
|
||||
# 151 1001 0111 -
|
||||
# 152 1001 1000 -
|
||||
# 153 1001 1001 -
|
||||
# 154 1001 1010 -
|
||||
# 155 1001 1011 -
|
||||
# 156 1001 1100 -
|
||||
# 157 1001 1101 -
|
||||
# 158 1001 1110 -
|
||||
# 159 1001 1111 -
|
||||
#--------
|
||||
# Type 1010 :
|
||||
# 160 1010 0000 -
|
||||
# 161 1010 0001 -
|
||||
# 162 1010 0010 -
|
||||
# 163 1010 0011 -
|
||||
# 164 1010 0100 -
|
||||
# 165 1010 0101 -
|
||||
# 166 1010 0110 -
|
||||
# 167 1010 0111 -
|
||||
# 168 1010 1000 -
|
||||
# 169 1010 1001 -
|
||||
# 170 1010 1010 -
|
||||
# 171 1010 1011 -
|
||||
# 172 1010 1100 -
|
||||
# 173 1010 1101 -
|
||||
# 174 1010 1110 -
|
||||
# 175 1010 1111 -
|
||||
#--------
|
||||
# Type 1011 :
|
||||
# 176 1011 0000 -
|
||||
# 177 1011 0001 -
|
||||
# 178 1011 0010 -
|
||||
# 179 1011 0011 -
|
||||
# 180 1011 0100 -
|
||||
# 181 1011 0101 -
|
||||
# 182 1011 0110 -
|
||||
# 183 1011 0111 -
|
||||
# 184 1011 1000 -
|
||||
# 185 1011 1001 -
|
||||
# 186 1011 1010 -
|
||||
# 187 1011 1011 -
|
||||
# 188 1011 1100 -
|
||||
# 189 1011 1101 -
|
||||
# 190 1011 1110 -
|
||||
# 191 1011 1111 -
|
||||
#--------
|
||||
# Type 1100 :
|
||||
# 192 1100 0000 -
|
||||
# 193 1100 0001 -
|
||||
# 194 1100 0010 -
|
||||
# 195 1100 0011 -
|
||||
# 196 1100 0100 -
|
||||
# 197 1100 0101 -
|
||||
# 198 1100 0110 -
|
||||
# 199 1100 0111 -
|
||||
# 200 1100 1000 -
|
||||
# 201 1100 1001 -
|
||||
# 202 1100 1010 -
|
||||
# 203 1100 1011 -
|
||||
# 204 1100 1100 -
|
||||
# 205 1100 1101 -
|
||||
# 206 1100 1110 -
|
||||
# 207 1100 1111 -
|
||||
#--------
|
||||
# Type 1101 :
|
||||
# 208 1101 0000 -
|
||||
# 209 1101 0001 -
|
||||
# 210 1101 0010 -
|
||||
# 211 1101 0011 -
|
||||
# 212 1101 0100 -
|
||||
# 213 1101 0101 -
|
||||
# 214 1101 0110 -
|
||||
# 215 1101 0111 -
|
||||
# 216 1101 1000 -
|
||||
# 217 1101 1001 -
|
||||
# 218 1101 1010 -
|
||||
# 219 1101 1011 -
|
||||
# 220 1101 1100 -
|
||||
# 221 1101 1101 -
|
||||
# 222 1101 1110 -
|
||||
# 223 1101 1111 -
|
||||
#--------
|
||||
# Type 1110 :
|
||||
# 224 1110 0000 -
|
||||
# 225 1110 0001 -
|
||||
# 226 1110 0010 -
|
||||
# 227 1110 0011 -
|
||||
# 228 1110 0100 -
|
||||
# 229 1110 0101 -
|
||||
# 230 1110 0110 -
|
||||
# 231 1110 0111 -
|
||||
# 232 1110 1000 -
|
||||
# 233 1110 1001 -
|
||||
# 234 1110 1010 -
|
||||
# 235 1110 1011 -
|
||||
# 236 1110 1100 -
|
||||
# 237 1110 1101 -
|
||||
# 238 1110 1110 -
|
||||
# 239 1110 1111 -
|
||||
#--------
|
||||
# Type 1111 :
|
||||
# 240 1111 0000 -
|
||||
# 241 1111 0001 -
|
||||
# 242 1111 0010 -
|
||||
# 243 1111 0011 -
|
||||
# 244 1111 0100 -
|
||||
# 245 1111 0101 -
|
||||
# 246 1111 0110 -
|
||||
# 247 1111 0111 -
|
||||
# 248 1111 1000 -
|
||||
# 249 1111 1001 -
|
||||
# 250 1111 1010 -
|
||||
# 251 1111 1011 -
|
||||
# 252 1111 1100 -
|
||||
# 253 1111 1101 -
|
||||
# 254 1111 1110 -
|
||||
# 255 1111 1111 -
|
Loading…
Add table
Reference in a new issue