From 8dafff9933a314d835556097a46b88264f11324b Mon Sep 17 00:00:00 2001
From: Richard Harrison <rjh@zaretto.com>
Date: Thu, 16 Nov 2017 02:55:07 +0100
Subject: [PATCH] MFD framework - add support for Emesary

Allow a MFD to be controlled via Emesary notifications (notifications.PFDEventNotification);

Notification needs to be constructed with the following parameters;

1 - MFD designation (text; name of MFD)
2 - MFD identity (int, id of the MFD within the model). Default 1; same as used when creating the MFD
3 - Event type (PFDEventNotification.SoftKeyPushed, notifications.PFDEventNotification.ChangeMenuText
4 - Event parameter.
  a) for PFDEventNotification.SoftKeyPushed an int identifying the button
  b) for notifications.PFDEventNotification.ChangeMenuText a hash array containing the menu ID and the new text (e.g. [{ Id: 1, Text: "NNN"}])

Method obj.PFD.RegisterWithEmesary(emesary.GlobalTransmitter) need to be called to connect the MFD to a transmitter
---
 Nasal/canvas/MFD_Generic.nas | 62 +++++++++++++++++++++++++++++++++++-
 Nasal/notifications.nas      | 62 ++++++++++++++++++++++++++++++++++--
 2 files changed, 121 insertions(+), 3 deletions(-)

diff --git a/Nasal/canvas/MFD_Generic.nas b/Nasal/canvas/MFD_Generic.nas
index 5afe46c8b..7789aec4c 100644
--- a/Nasal/canvas/MFD_Generic.nas
+++ b/Nasal/canvas/MFD_Generic.nas
@@ -137,7 +137,7 @@ var PFD_Device =
 # This does not actually create the canvas elements, or parse the SVG, that would typically be done in 
 # a higher level class that contains an instance of this class.
 # see: http://wiki.flightgear.org/Canvas_MFD_Framework
-    new : func(svg, num_menu_buttons, button_prefix, _canvas)
+    new : func(svg, num_menu_buttons, button_prefix, _canvas, designation="MFD")
     {
 		var obj = {parents : [PFD_Device] };
         obj.svg = svg;
@@ -146,6 +146,11 @@ var PFD_Device =
         obj.pages = [];
         obj.page_index = {};
         obj.buttons = setsize([], num_menu_buttons);
+        obj.transmitter = nil;
+
+        # change after creation if required
+        obj.device_id = 1; 
+        obj.designation = designation;
 
         for(var idx = 0; idx < num_menu_buttons; idx += 1)
         {
@@ -159,9 +164,64 @@ var PFD_Device =
                 obj.buttons[idx].setText(sprintf("M",idx));
             }
         }
+        obj.Recipient = nil;
         return obj;
     },
     #
+    # instead of using the direct call method this allows the use of Emesary (via a specified or default global transmitter)
+    # example to notify that a softkey has been used. The "1" in the line below is the device ID
+    # var notification = notifications.PFDEventNotification.new(me.designation, me.DeviceId, notifications.PFDEventNotification.SoftKeyPushed, me.mpcd_button_pushed);
+    # emesary.GlobalTransmitter.NotifyAll(notification);
+    # - currently supported is
+    # 1. setting menu text directly (after page has been loaded)
+    #    notifications.PFDEventNotification.new(me.designation, 1, notifications.PFDEventNotification.ChangeMenuText, [{ Id: 1, Text: "NNN"}]);
+    # 2. SoftKey selection.
+    # 
+    # the device ID must match this device ID (to allow for multiple devices).
+    RegisterWithEmesary : func(transmitter = nil){
+        if (transmitter == nil)
+          transmitter = emesary.GlobalTransmitter;
+
+        if (me.Recipient == nil){
+            me.Recipient = emesary.Recipient.new("PFD_"~me.designation);
+            var pfd_obj = me;
+            me.Recipient.Receive = func(notification)
+              {
+                  if (notification.Device_id = pfd_obj.device_id 
+                      and notification.NotificationType == notifications.PFDEventNotification.DefaultType) {
+                      if (notification.Event_Id == notifications.PFDEventNotification.SoftKeyPushed 
+                          and notification.EventParameter != nil)
+                        {
+                            pfd_obj.notifyButton(notification.EventParameter);
+                        }
+                      else if (notification.Event_Id == notifications.PFDEventNotification.ChangeMenuText
+                          and notification.EventParameter != nil)
+                        {
+                            foreach(var eventMenu; notification.EventParameter) {
+                                foreach (var mi ; pfd_obj.current_page.menus) {
+                                    if (pfd_obj.buttons[eventMenu.Id] != nil) {
+                                        pfd_obj.buttons[eventMenu.Id].setText(eventMenu.Text);
+                                    }
+                                    else
+                                      printf("PFD_device: Menu for button not found. Menu ID '%s'",mi.menu_id);
+                                }
+                            }
+                        }
+                      return emesary.Transmitter.ReceiptStatus_OK;
+                  }
+                  return emesary.Transmitter.ReceiptStatus_NotProcessed;
+              };
+            transmitter.Register(me.Recipient);
+            me.transmitter = transmitter;
+        }
+    },
+    DeRegisterWithEmesary : func(transmitter = nil){
+        # remove registration from transmitter; but keep the recipient once it is created.
+        if (me.transmitter != nil)
+          me.transmitter.DeRegister(me.Recipient);
+        me.transmitter = nil;
+    },
+    #
     # called when a button is pushed - connecting the property to this method is implemented in the outer class
     notifyButton : func(button_id)
     {
diff --git a/Nasal/notifications.nas b/Nasal/notifications.nas
index 79988265e..48dd08a7d 100644
--- a/Nasal/notifications.nas
+++ b/Nasal/notifications.nas
@@ -25,7 +25,8 @@
 var PropertySyncNotificationBase_Id = 16;
 var AircraftControlNotification_Id = 17;
 var GeoEventNotification_Id = 18;
-
+# event ID 19 reserved for armaments and stores (model defined).
+var PFDEventNotification_Id = 20;
 
 #
 # PropertySyncNotificationBase is a wrapper class for allow properties to be synchronized between
@@ -167,6 +168,8 @@ var GeoEventNotification =
         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.RemoteCallsign = ""; # associated remote callsign.
+        new_class.Flags = 0; # 8 bits for whatever.
 
         new_class.bridgeProperties = func
         {
@@ -200,6 +203,14 @@ var GeoEventNotification =
             getValue:func{return emesary.TransferFixedDouble.encode(new_class.w_fps,2,10);},
             setValue:func(v,root,pos){var dv=emesary.TransferFixedDouble.decode(v,2,10,pos);new_class.w_fps=dv.value;return dv}, 
              },
+             {
+            getValue:func{return emesary.TransferString.encode(new_class.RemoteCallsign);},
+            setValue:func(v,root,pos){var dv=emesary.TransferString.decode(v,pos);new_class.RemoteCallsign=dv.value;return dv}, 
+             },
+             {
+            getValue:func{return emesary.TransferByte.encode(new_class.Flags);},
+            setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.Flags=dv.value;return dv}, 
+             },
             ];
           };
         return new_class;
@@ -210,7 +221,7 @@ var GeoEventNotification =
 #    1 - Created
 #    2 - Moved
 #    3 - Deleted
-#    4 - 
+#    4 - Collision
 # ----
 # Secondary kind (8 bits)
 # using the first 4 bits as the classification and the second 4 bits as the sub-classification
@@ -502,3 +513,50 @@ var GeoEventNotification =
 # 253 1111 1101 - 
 # 254 1111 1110 - 
 # 255 1111 1111 - 
+
+
+#
+#
+# Use to transmit events that happen at a specific place; can be used to make 
+# models that are simulated locally (e.g. tankers) appear on other player's MP sessions.
+var PFDEventNotification = 
+{
+# new:
+# _ident - the identifier for the notification. not bridged.
+# _pfd_id - numeric identification of the PFD within the model
+# _event_id - event ID. 
+#     1       softkey pushed.
+#     2       select page by ID
+# _event_param - param related to the event ID. implementation specific.
+##
+    SoftKeyPushed : 1,
+    SelectPageById : 2,
+    ChangeMenuText : 3, #event parameter contains hash of { Id: , Text: }
+    DefaultType : "PFDEventNotification",
+
+    new: func(_ident, _device_id,_event_id,_event_parameter_id)
+    {
+        var new_class = emesary.Notification.new(PFDEventNotification.DefaultType, _ident, PFDEventNotification_Id);
+        new_class.Device_Id = _device_id;
+        new_class.Event_Id = _event_id;
+        new_class.EventParameter = _event_parameter_id;
+
+        new_class.IsDistinct = 1; # each of these events is unique and needs to be bridged
+
+        new_class.bridgeProperties = func
+        {
+            return 
+            [ 
+             {
+            getValue:func{return emesary.TransferByte.encode(new_class.Event_Id);},
+            setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.Event_Id=dv.value;return dv}, 
+             },
+             {
+            getValue:func{return emesary.TransferByte.encode(new_class.EventParameter);},
+            setValue:func(v,root,pos){var dv=emesary.TransferByte.decode(v,pos);new_class.EventParameter=dv.value;return dv}, 
+             },
+            ];
+          };
+        return new_class;
+    },
+};