This the last post of this sequence. A C# application and an Ada application now
communicate over Microsoft Named Pipes which was the original goal. (See Pseudo Visual Compiler using Interface
to Ada as the OFP of January 8, 2019 as well as Pseudo Visual Compiler of
Decades Ago of Dec 2018).
General Discussion
The C# application (App1) has a Windows Form to display a
partial CDU/MCDU of an aircraft. When a
key is clicked, imitating a key push on an aircraft MCDU, the key is sent to
the Ada application (App2) imitating a tiny portion of an aircraft OFP. Rather than actually pretending to imitate
the selection of new MCDU pages or performing tasks associated with Line Select
Keys (LSKs) for a particular displayed page, the Ada application just echoes
the particular keys back to App1 and other messages for LSKs and other keys
(such as PREV, UP, etc) that would require actually code to decipher.
Before this can happen the two applications must be fully connected. That is, before App2 can treat keys it has to know that there is a consumer of its response message which just happens to be located in App1. (That is, with the message delivery framework, the topic consumer components can be located in any of the applications of the configuration. In this case there are only the two applications.) Therefore, that the Register Request message that contains the components of App1 that have registered to consume messages has to have been received by App2 and its Register Response message returned to App1 informing App1 that its topic consumer components have been treated. Consequently OFP CHANGEPAGE topics will be sent by App2 to App1 since it has a component that will consume the message. (Of course, App2 could have had such a component as well. In which case the topic would have been delivered to the App2 component as well as the App1 component.)
To inform the operator that the initialization has completed, a Windows MessageBox is displayed. The operator can then indicate that it has been seen (selecting the OK button) and then proceed to click a key on the MCDU. Depending upon the speed of the PC and the like, the feedback will appear in the text box and, if applicable, the page title will change.
For debugging purposes, the Ada App2 application outputs Text_IO messages that show particular progress. If the GNAT debugger is used, this will scroll past in its console window. Otherwise, the application can be run in a DOS window and the console output piped to a file to be reviewed upon termination. Likewise, for the C# App1 application console output can be captured if desired. Since App1 uses a Windows form, the C# Console output is worthless (that is, no console window is produced) so a ConsoleOut class has been implemented to output the text to a particular file where it can be reviewed upon termination. Therefore, at startup, the operator is prompted as to whether this output should be retained along with notification of where it will be located. (This is dependent upon the Windows folder structure being the same as that used to create the application.)
Figure CDU-1
Figure Ok-Popup
App1 initially displays the partial CDU/MCDU as in Figure
CDU-1. When the two applications are
fully connected the popup MessageBox in Figure Ok-Popup is displayed to inform
the user that the key buttons can now be clicked. Figure CDU-2 then displays what the happens after the F-PLAN key is
clicked. For such a key click the OFP
KEYPUSH topic message is sent to App2 with data corresponding to the key and
App2 receives the message and responds with the OFP CHANGEPAGE topic. It either echoes the key or if not DIR, PROG, PERF, INIT, DATA, or F-PLAN (FLIGHTPLAN)
informational text to be displayed in the text box as shown in Figure CDU-3.
When the key is DIR, PROG, PERF, INIT, DATA, or F-PLAN the
page title is changed to reflect the selected page. When a different key is clicked, the returned informational text
of the Data portion of the CHANGEPAGE topic message is displayed in the text
box as shown in Figure CDU-3 where LSK R5 (the fifth key down on the right hand
side of MCDU screen) had been clicked.
Figure CDU-2
The new code of the C# application 1 and the Ada application
2 will be included with this post. The
current code of the entire project will be stored on GitHub in sw-engr/rpi.
Figure CDU-3
Problems Encountered
When communicating between C# applications no fussing was
required – I just had to follow Microsoft instructions. When communicating between Ada applications,
the two variations of the server and client pipe open methods were
necessary. For the server
Handle :=
ExecItf.CreateFile
(
FileName =>
Addr_to_LPSCSTR(Comm.Pipe(Index)(Transmit).Name'address),
DesiredAccess =>
ExecItf.GENERIC_READ or ExecItf.GENERIC_WRITE,
ShareMode => 0, -- no sharing
SecurityAttributes => null,
-- default security attributes
CreationDisposition => ExecItf.OPEN_EXISTING,
FlagsAndAttributes => 0, -- default attributes
TemplateFile =>
System.Null_Address ); -- no template file
and for the client
Handle
:=
ExecItf.CreateNamedPipe
(
Name =>
Addr_to_LPSCSTR(Comm.Pipe(Index)(Receive).Name'address),
OpenMode =>
ExecItf.PIPE_ACCESS_DUPLEX,
--16#00000003#,
PipeMode =>
ExecItf.PIPE_TYPE_MESSAGE or --16#00000004#
ExecItf.PIPE_READMODE_MESSAGE
or --2
ExecItf.PIPE_WAIT, --0
MaxInstances =>
ExecItf.PIPE_UNLIMITED_INSTANCES, --255
OutBufferSize => Message_Size,
-- output buffer size
InBufferSize =>
Message_Size, -- input buffer size
DefaultTimeOut => 0, -- client timeout in msec
SecurityAttributes => null );
-- default priority attributes
Where the \\.\pipe\MtoN and NtoM names were located in the
Comm.Pipe structures of the initial project.
(Index allowed for multiple different pairs of applications.)
However, for communication between Ada (via its C interface
in ExecItf) and C# this didn't work and I was off wild goose hunting. At the beginning either the C# application
would give an exception such as "UnauthorizedAccessException
was unhandled" with hints such as
Make sure you have sufficient privileges to
access this resource
If you are attempting to access a file, make
sure it is not ReadOnly
Another exception was "Access to the path
is denied." Or else the Ada
application would return null for the handle.
Due to the not sufficient privileges etc I got off on pursuits such as
adding security attributes and such to the C# pipe open and this led to losing sight of the goal with changes to both the C# and
Ada pipe opens. I was going about it
blind since who knows what Microsoft was doing in their System calls that
involved the named pipes and how they were creating the pipe path names.
After
chasing my tail for a few days it occurred to me that the Ada to C# interface
would be similar to C to C# (since I had been using the C interface of GNAT Ada
to interface with Windows) or C++ to C#.
So I did internet searches for named pipes connecting between C++ and C#
applications. This led to finding
Microsoft "documents" for "How to: Use Named Pipes for Network
Interprocess Communication" (C#); "C++ named-pipe server for
IPC"; "Named Pipe Client"; and "Named Pipe Server Using
Overlapped I/O" (the later three for C++).
The
C++ documents reversed the server and client CreateFile and CreateNamedPipe
opens that I had been using in Ada to Ada communications. That is, CreateNamedPipe for the server and
CreateFile for the client. Once I made
that change and remembered to add the trailing ASCII NUL to the Ada pipe name,
which early on had occurred to me as going to be needed to have a pipe name
that would look like a C nul terminated string (and which sometime during the
chase after lack of privilege etc had gotten removed), I was able to get a
connection between the two applications.
Then it was just a matter of locating problems where one or
the other of the applications lacked a needed fix (of which there weren't too
many). After enough problems had been
fixed that the KEYPUSH message was getting sent (if I delayed long enough for
the full connection to occur) and the CHANGEPAGE message was being returned, it
took about a day to figure out why the text wasn't appearing for the page title
or in the text box. That is, in the
ComOFP component I would invoke a method in Windows CDUForm to write the text
to the controls. But, although the
debugger would show that the text was changed, it wouldn't appear on the
screen.
Then it occurred to me that back in September of last year
(shown as October in the blog posts) (C# Displays Follow On for ExpenseIt Using
Access Database) had to wait in the form to await the response and then update
the controls. That is, the form would
invoke the framework interface component via calls such as
string response
= EmployeeDatabase.ModifyEmployeeData
(firstName,
lastName, department);
if (response
== "success")
where the form couldn't continue until, in the example, the
ModifyEmployeeData method of the EmployeeDatabase class returned the
response. The form controls could then
be updated. Meanwhile the
EmployeeDatabase framework interface class built the message, sent it, and
waited for the response. Upon receiving
the response, a wait and Sleep loop of the ModifyEmployeeData method detected
this and returned the response. In this
way the Windows form could update the controls in such a way that the update
would become visible after the framework had been doing other threads.
A piece of cake after that.
I just had CDUForm and ComOFP interface the same way. That is,
//
This method is to react to a key push
private void React(Key key)
{
if (ComOFP.connected) // fully connected to
remote app
{
// Send key to OFP application
Result result = new Result();
result = comOFP.TreatKey(key);
if (result.success)
{
DisplayPageTitle(result.text);
}
else
{
textBoxL2.Text = "key ignored";
}
}
} //
end React
in CDUForm where result returned whether the send of the key
had resulted in a successful response, and if so, the text contained in the
response. While ComOFP has
public
CDUForm.Result TreatKey(CDUForm.Key key)
{
responseReceived = false;
Delivery.MessageType keypush;
keypush.data = key.ToString();
Delivery.Publish(keyPushTopic, componentKey, keypush.data);
// Wait until response received
while (true)
{
if (!responseReceived)
{
Thread.Sleep(100); // millisecs
}
else
{
break; // exit loop
}
}
// Return the response
responseReceived = false;
response.success = true;
response.text = receivedChangePage;
return response;
} //
end TreatKey
to send the KEYPUSH topic and
static void AnyMessage(Delivery.MessageType message)
{
// Treat received message
ConsoleOut.WriteLine("ComOFP AnyMessage " + message.data);
receivedChangePage = message.data;
responseReceived = true;
} //
end AnyMessage
is entered from its Disburse queue when a message is
received and saves the data where it can be visible to TreatKey and sets that
the responseReceived flag to true to terminate the while loop in TreatKey. These two changes get around the Windows
refresh problem so that DisplayPageTitle is able to not only change the text of
the controls (as happened before the change) but have the new text become
visible.
I then added the popup MessageBox (Figure Ok-Popup) so that
the end of setup would be known. Thus
the pair of applications to communicate between C# and Ada is complete. And I now have two versions of the message
delivery framework that are compatible.
New Code Classes and Packages
Corrections to previously published code will not be
provided here. See GitHub for the
entire set of C# classes and Ada packages.
C# App1 classes
Program.cs to start the framework and run the CDUForm.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
namespace VisualCompiler
{
static
class Program
{
///
<summary>
///
The main entry point for the application.
///
</summary>
[STAThread]
static void Main()
{
if (System.Threading.Thread.CurrentThread.Name == null)
{
System.Threading.Thread.CurrentThread.Name =
"MainThread";
}
currentProcess = Process.GetCurrentProcess();
// Open ConsoleOut text file
ConsoleOut.Install();
Component.ApplicationId appId; // the Program.cs version
appId.name = "App 1";
// of the Program class
appId.id = 1;
// ids the first application
App.Launch(appId);
// Install the components of this application.
App1.Install();
// Now, after all the components have been installed, create the
// threads for the components to run in.
// o In addition, the
TimingScheduler thread will be created.
// o
There will be a return from Create and then the created threads
// will begin to execute. And the main procedure application
// thread will no longer be
executed -- instead, the created
// threads will be the only
threads running.
Threads.Create();
// Except, that with this framework plus Windows Forms application,
// this main procedure application thread will continue to be
// executed in the CDUForm.
CDUForm cduForm;
cduForm = new CDUForm();
Application.Run(cduForm);
} //
end Main
static private Process currentProcess;
} // end
class Program
} // end namespace
In the above, ConsoleOut class is installed and then general
App class is launched indicating that the application is App1. As provided before, this class is a common
class used with C# other applications.
After the common classes of any application are Installed, the App1
class Install is done for the classes that are unique to this application. Finally, the framework threads are created
and then the secondary CDUForm is run.
I had previously found integrating the message delivery
framework with Windows forms that the initial Form1 created by C# for a Windows
Forms Application can be ignored and new forms created by Add
Windows Forms | Windows Form used instead.
This is the case with CDUForm as renamed from the created Form2.
App1.cs
The unique App1.cs file installs the components that only
exist in this particular application.
As such it only installs the ComOFP class – the only framework user
(that is, non-framework) component of the application.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace VisualCompiler
{
static
class App1
{
//
Install the components of Application 1
static public void Install()
{
// Install into the framework each of the components
// of the application.
ComOFP.Install();
} //
end Install
} // end
class App1
} // end namespace
CDUForm.cs
This is the renamed Form2 created by Project | Add Windows
Forms | Windows Form. It displays a
pretend aircraft CDU/MCDU as illustrated by the CDU figures and created using
CDUForm.cs [Design]. The event actions
are created normally by selecting the button and renaming it as desired (LSKL1,
etc) and double clicking the buttons to get C# to insert the entry point into
the code.
private void LSKL1_Click(object
sender, EventArgs e)
{
React(Key.LSKL1);
}
etc where I added the invocation of React to have a common
method to treat all the buttons (referenced as Keys as per a physical MCDU).
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace VisualCompiler
{
public partial class CDUForm : Form
{
static private ComOFP comOFP;
static private CDUForm cduForm = new CDUForm();
// The type to be returned by ComOFP
public struct Result
{
public bool success;
public string text;
}
public enum Key
{ // the line select keys and a portion of the other
non-alphanumeric keys
LSKL1,
LSKL2,
LSKL3,
LSKL4,
LSKL5,
LSKL6,
LSKR1,
LSKR2,
LSKR3,
LSKR4,
LSKR5,
LSKR6,
DATA,
DIR,
FLIGHTPLAN,
INIT,
PERF,
PROG,
PREV,
NEXT,
UP,
DOWN
} // end enum Key
public CDUForm()
{
InitializeComponent();
comOFP = new ComOFP();
} // end constructor
public void DisplayPageTitle(string text)
{
// Display new Title if key push was to known page
if ((text == "DIR") || (text ==
"PROG") || (text == "PERF") ||
(text == "INIT") || (text ==
"DATA") || (text == "FLIGHTPLAN"))
{
DisplayLabel.Text = text;
}
// Display result in text box no matter what
textBoxL2.Text = text;
} // end DisplayPageTitle
// This method is to react to a key push
private void React(Key key)
{
ConsoleOut.WriteLine("React " + key);
if (ComOFP.connected) // fully connected to remote app
{
// Send key to OFP application
Result result = new Result();
result = comOFP.TreatKey(key);
if (result.success)
{
DisplayPageTitle(result.text);
}
else
{
textBoxL2.Text = "key ignored";
}
}
} // end React
//**********************************************************************
// Beginning of event handlers
// These event handlers all invoke the React method to
allow a common
// method to determine whether in the build table mode or
the OFP mode.
// This allows a common form to be used to interpret the
button push
// as from the visual compiler / table builder or from the
OFP using
// the created table.
private void LSKL1_Click(object sender, EventArgs e)
{
React(Key.LSKL1);
}
private void LSKL2_Click(object sender, EventArgs e)
{
React(Key.LSKL2);
}
private void LSKL3_Click(object sender, EventArgs e)
{
React(Key.LSKL3);
}
private void LSKL4_Click(object sender, EventArgs e)
{
React(Key.LSKL4);
}
private void LSKL5_Click(object sender, EventArgs e)
{
React(Key.LSKL5);
}
private void LSKL6_Click(object sender, EventArgs e)
{
React(Key.LSKL6);
}
private void LSKR1_Click(object sender, EventArgs e)
{
React(Key.LSKR1);
}
private void LSKR2_Click(object sender, EventArgs e)
{
React(Key.LSKR2);
}
private void LSKR3_Click(object sender, EventArgs e)
{
React(Key.LSKR3);
}
private void LSKR4_Click(object sender, EventArgs e)
{
React(Key.LSKR4);
}
private void LSKR5_Click(object sender, EventArgs e)
{
React(Key.LSKR5);
}
private void LSKR6_Click(object sender, EventArgs e)
{
React(Key.LSKR6);
}
private void
DIR_Click(object sender, EventArgs e)
{
React(Key.DIR);
}
private void PROG_Click(object sender, EventArgs e)
{
React(Key.PROG);
}
private void PERF_Click(object sender, EventArgs e)
{
React(Key.PERF);
}
private void INIT_Click(object sender, EventArgs e)
{
React(Key.INIT);
}
private void DATA_Click(object sender, EventArgs e)
{
React(Key.DATA);
}
private void FPLAN_Click(object sender, EventArgs e)
{
React(Key.FLIGHTPLAN);
}
private void PREV_Click(object sender, EventArgs e)
{
React(Key.PREV);
}
private void NEXT_Click(object sender, EventArgs e)
{
React(Key.NEXT);
}
private void UP_Click(object sender, EventArgs e)
{
React(Key.UP);
}
private void DOWN_Click(object sender, EventArgs e)
{
React(Key.DOWN);
}
private void DisplayLabel_Click(object sender, EventArgs e)
{
}
} // end class
} // end namespace
When a button/key is clicked the corresponding event handler
is entered. Since all the widgets are
treated the same, the React method is immediately invoked passing the
button/key's enumerated literal.
React then checks whether the two applications have
completed their initial messages to exchange Register Request messages to add
any non-framework topics that have been registered to be consumed (that is,
received and delivered to their component).
In the situation of this application, that is the OFP CHANGEPAGE topic
which gets added to the App2 Library. App2 adds the OFP KEYPUSH topic to its Library. If the two applications are fully connected,
the key is passed to ComOFP via the TreatKey method to be transmitted to App2.
The invocation of TreatKey is not completed until there is a response available as explained
above. When there is a return from
ComOFP the result is checked for success.
If true, the result.text is passed to DisplayPageTitle to update the
title if the text indicates a new page and, in any case to update the text
box. Which completes the LSKL1, etc
event handler.
ComOFP.cs
This message delivery framework interface class is invoked
by CDUForm to treat a keypush event. It
publishes the OFP KEYPUSH topic which the framework will deliver to the
consumer component and then waits for the signal (responseReceived) that the
response can be returned to the CDUForm for display.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace VisualCompiler
{
class
ComOFP
{
// This component contains only two
topics. To send the selected CDU
// key to the remote OFP app and to
receive the response.
static private Component.ParticipantKey componentKey;
static private Topic.TopicIdType changePageTopic;
static private Topic.TopicIdType keyPushTopic;
static private Disburse queue;
static private CDUForm cduForm = new CDUForm();
static public bool connected = false; // connected to remote app 2
static private bool responseReceived = false;
static private CDUForm.Result response = new CDUForm.Result();
static private string receivedChangePage = "";
static public void Install()
{
// Create Disburse queue
queue = new Disburse("ComOFP", true, // use Timer event wakeup
false, AnyMessage); // not
periodic
// Register this component
Component.RegisterResult result;
result = Component.Register
("ComOFP", 0, Threads.ComponentThreadPriority.NORMAL,
MainEntry, queue);
componentKey = result.key;
if (result.status == Component.ComponentStatus.VALID)
{
Library.AddStatus status;
changePageTopic.topic = Topic.Id.OFP;
changePageTopic.ext = Topic.Extender.CHANGEPAGE;
keyPushTopic.topic = Topic.Id.OFP;
keyPushTopic.ext = Topic.Extender.KEYPUSH;
// Register to consume the OFP CHANGEPAGE topic
status = Library.RegisterTopic
(changePageTopic, result.key,
Delivery.Distribution.CONSUMER,
MainEntry);
// Register to produce the OFP KEYPUSH topic
status = Library.RegisterTopic
(keyPushTopic, result.key,
Delivery.Distribution.PRODUCER, null);
}
} // end Install
//
Entry point
static void MainEntry()
{
while (true) // loop forever
{
if (connected)
{
// Wait for event.
string xxx = queue.queueName;
queue.EventWait();
}
// wait for remote app (remoteAppId of 2) before wait for event -
else if ((!connected) && (Remote.RegisterAcknowledged(2)))
{
var result = MessageBox.Show("Ok to use keys",
"TreatKey",
MessageBoxButtons.OK);
connected = true;
}
else
{
Thread.Sleep(100); // wait and check for connected
}
}
// end forever loop
} //
end MainEntry
static void AnyMessage(Delivery.MessageType message)
{
// Treat received message - notify TreatKey that
response received
ConsoleOut.WriteLine("ComOFP AnyMessage " + message.data);
receivedChangePage = message.data;
responseReceived = true;
} //
end AnyMessage
public CDUForm.Result TreatKey(CDUForm.Key key)
{
responseReceived = false;
Delivery.MessageType keypush;
keypush.data = key.ToString();
Delivery.Publish(keyPushTopic, componentKey, keypush.data);
// Wait until response received
while (true)
{
if (!responseReceived)
{
Thread.Sleep(100); //
millisecs
}
else
{
break; // exit loop
}
}
// Return the response to CDUForm
responseReceived = false;
response.success = true;
response.text = receivedChangePage;
return response;
} //
end TreatKey
} // end
ComOFP class
} // end namespace
ConsoleOut.cs
ConsoleOut is a replacement for the C++ Console. It is necessary in order to see activity
during execution since the normal Console in only available for Console
Applications. It uses a popup
MessageBox at startup to ask the user if text output is to be saved and to
inform the user where the disk file containing the output will be located if
the response is Yes.
For Write statements (vs WriteLine) the text is saved
awaiting a WriteLine in order to output the text all at once.
Since various threads can request text output, the output of
one thread (via various Write statements) can be superceded by that of another
thread. Therefore, provision has been
made for this possibility.
Note: The output file can't be used if already open, such as
to examine it, therefore before selecting this option other users have to be
terminated.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace VisualCompiler
{
public
class ConsoleOut
{
private
static string path = "C:\\Source\\XP3\\ConsoleOut.txt";
private static bool finishedWrite = true;
private static string partialText = "";
private static bool doOutput = false;
//
Install by creating the txt file
static public void Install()
{
MessageBoxButtons buttons = MessageBoxButtons.YesNo;
DialogResult result;
string message = "String output to replace Console is in " +
path;
string caption = "Info";
result = MessageBox.Show(message, caption, buttons);
if (result == DialogResult.Yes)
{
doOutput = true;
}
else
{
doOutput = false;
}
if (doOutput)
{
if (System.IO.File.Exists(path))
{
try
{
System.IO.File.Delete(@path);
}
catch (System.IO.IOException e)
{
Console.WriteLine(e.Message);
return;
}
}
using (System.IO.FileStream textfile = System.IO.File.Create(path))
{
}
}
} //
end Install
static public void Write(string text)
{
partialText += text;
} //
Write
static public void WriteLine(String text)
{
if (!doOutput)
{
return;
}
if ((text == "") || (text == " "))
{
if (!finishedWrite)
{
while (!finishedWrite) // wait while another thread finishes write
{
Thread.Sleep(10); // 10 milliseconds
}
}
finishedWrite = false;
using (System.IO.StreamWriter textFile =
new System.IO.StreamWriter(@path, true))
{
textFile.WriteLine(partialText);
partialText = ""; // clear for the next time
}
finishedWrite = true;
}
else
{
if (!finishedWrite)
{
while (!finishedWrite) // wait while another thread finishes write
{
Thread.Sleep(10); // 10 milliseconds
}
}
finishedWrite = false;
while (true)
{
try
{
using (System.IO.StreamWriter textFile =
new System.IO.StreamWriter(@path,
true))
{
textFile.WriteLine(text);
}
break; // exit loop
}
catch (IOException)
{
}
}
finishedWrite = true;
}
} //
end WriteLine
} // end
ConsoleOut class
} // end namespace
Ada App2 packages
Main is the procedure that is entered from the operating
system at startup. As with C# Program,
the common App procedure is launched identifying the application – in this
instance App2 – then the unique App2 application specific code in installed,
followed by the creation of the necessary threads to support the message
delivery framework components and the user components.
Main.adb
with App;
with App2;
with Itf;
with Threads;
procedure Main is
AppId :
Itf.ApplicationIdType;
begin -- Main
-- Launch
the general packages of this application
AppId.Name :=
"App 2 ";
AppId.Id := 2;
App.Launch(AppId);
-- Install
the components of this application
App2.Install;
-- Now,
after all the components have been installed, create their threads
Threads.Create;
end Main;
The application specific components are installed via, for
this application, the App2 package.
Similarly with the C# App1, there is only one non-framework (i.e., user)
component to be installed.
App2.ads
package App2 is
procedure
Install;
end App2;
App2.adb
with ComOFP;
package body App2 is
procedure
Install is
begin --
Install
ComOFP.Install;
end
Install;
end App2;
The ComOFP package is the user component that treats the
received OFP KEYPUSH message topic and responds by requesting delivery of the
OFP CHANGEPAGE response.
The visible Install procedure, registers the component with
the Component package while naming the Main callback procedure to be entered
via its thread, the Disburse package to receive wakeup events when messages are
delivered and the address of the Write function to be used by Delivery when a
message is to be queued to the instance of the Disburse package. Component will then obtain a wait handle for
the queue and return it in Result and Install will then pass it to its instance
of the Disburse package. Then the two
topics (OFP KEYPUSH and OFP CHANGEPAGE) are registered with the Library; the
first specifying that ComOFP will be the producer of the messages and that it
will be a consumer of the second topic.
When, via Delivery, DisburseWrite is invoked to write to the
particular queue, Disburse will add the message to the queue and signal that
the wait should end. This processing
will cause the thread of ComOFP to execute (note, the write took place in the
thread of the writer) and the message will be forwarded to the AnyMessage
Universal message handler of the component as specified in the declaration of
the queue. Upon entry to the AnyMessage
procedure the message data is checked and the appropriate response data created
and published. (Note: No check for what
the received topic might be is necessary since only the one consumer topic was
registered.) The framework will then
look up the consumers of the topic in the Library and, since only the one
remote consumer, cause it to be transmitted to App1 for delivery to its ComOFP
consumer.
ComOFP.ads
package ComOFP is
procedure
Install;
end ComOFP;
ComOFP.adb
with CStrings;
with Component;
with Delivery;
with Disburse;
with ExecItf;
with Itf;
with Library;
with System;
with Text_IO;
with Threads;
with Topic;
with Unchecked_Conversion;
package body ComOFP is
package
Int_IO is new Text_IO.Integer_IO( Integer );
QueueOFP :
Itf.V_Short_String_Type
:= ( Count => 7,
Data => "QComOFP " );
ComponentKey : Itf.ParticipantKeyType := Component.NullKey;
--
Component's key returned from Register
KeyPushTopic :
Topic.TopicIdType;
ChangePageTopic : Topic.TopicIdType;
procedure
AnyMessage
( Message :
in Itf.MessageType );
package
DisburseQueue
--
Instantiate disburse queue for component
is new
Disburse( QueueName => QueueOFP'Address,
Periodic => False,
Universal => AnyMessage'Address,
Forward =>
System.Null_Address );
function
DisburseWrite
-- Callback
to write message to the DisburseQueue
( Message :
in Itf.MessageType
) return
Boolean;
procedure
Main -- callback
( Topic :
in Boolean := False
);
ComOFPName
: Itf.V_Medium_String_Type
:= ( Count
=> 6,
Data => "ComOFP
" );
Result :
Component.RegisterResult;
procedure
Install is
Status :
Library.AddStatus;
use type
Component.ComponentStatus;
use type
Library.AddStatus;
function
to_Callback is new Unchecked_Conversion
( Source => System.Address,
Target => Topic.CallbackType
);
begin --
Install
Result :=
Component.Register
(
Name => ComOFPName,
Period => 0, -- not
periodic
Priority => Threads.NORMAL,
-- Requested priority of thread
Callback =>
to_Callback(Main'Address), -- Callback function of component
Queue =>
DisburseQueue.Location,
QueueWrite
=> DisburseWrite'Address );
if
Result.Status = Component.VALID then
DisburseQueue.ProvideWaitEvent( Event => Result.Event );
ComponentKey := Result.Key;
KeyPushTopic.Topic := Topic.OFP;
KeyPushTopic.Ext := Topic.KEYPUSH;
Status
:= Library.RegisterTopic( KeyPushTopic, Result.Key,
Delivery.CONSUMER,
to_Callback(Main'Address) );
if
Status /= Library.SUCCESS then
Text_IO.Put_Line( "ERROR: Register of KEYPUSH Topic failed" );
end if;
ChangePageTopic.Topic := Topic.OFP;
ChangePageTopic.Ext := Topic.CHANGEPAGE;
Status
:= Library.RegisterTopic( ChangePageTopic, Result.Key,
Delivery.PRODUCER,
to_Callback(Main'Address) );
if
Status /= Library.SUCCESS then
Text_IO.Put_Line( "ERROR: Register of CHANGEPAGE Topic failed"
);
end if;
end if;
end
Install;
-- Write to
queue
function
DisburseWrite
( Message :
in Itf.MessageType
) return
Boolean is
begin --
DisburseWrite
return
DisburseQueue.Write(Message => Message);
end
DisburseWrite;
-- Forever
loop as initiated by Threads
procedure
Main -- callback
( Topic :
in Boolean := False
) is
Success :
Boolean;
Timer_Sec
-- System
time seconds as ASCII
:
String(1..2);
System_Time
-- System
time
:
ExecItf.System_Time_Type;
begin --
Main
Text_IO.Put_Line("in ComOFP callback");
loop --
forever
Text_IO.Put_Line("ComOFP wait for event");
DisburseQueue.EventWait; -- wait for event
Text_IO.Put_Line("ComOFP after end of wait");
System_Time := ExecItf.SystemTime;
CStrings.IntegerToString(System_Time.Second, 2, False, Timer_Sec,
Success);
Text_IO.Put("ComOFP ");
Text_IO.Put_Line(Timer_Sec(1..2));
end loop;
end Main;
-- Treat
any message of the component that doesn't have its own procedure
procedure
AnyMessage
( Message :
in Itf.MessageType
) is
MessageOut : Itf.MessageType;
use type
Topic.Extender_Type;
use type
Topic.Id_Type;
begin --
AnyMessage
Text_IO.Put("Entered ComOFP AnyMessage ");
Int_IO.Put(Topic.Id_Type'pos(Message.Header.Id.Topic));
Int_IO.Put(Topic.Extender_Type'pos(Message.Header.Id.Ext));
Text_IO.Put_Line(" ");
if
Message.Header.Id.Topic = Topic.OFP and then
Message.Header.Id.Ext =
Topic.KEYPUSH
then
--
Treat the message by selecting a new page and publishing the
-- OFP
CHANGEPAGE back to App1 to change the displayed page on
-- the
pseudo CDU.
Text_IO.Put_Line(Message.Data(1..3));
MessageOut.Data := ( others => ASCII.NUL );
if
Message.Data(1..3) = "DIR" then
MessageOut.Data(1..3) := "DIR";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..4) = "PROG" then
MessageOut.Data(1..4) := "PROG";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..4) = "PERF" then
MessageOut.Data(1..4) := "PERF";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..4) = "INIT" then
MessageOut.Data(1..4)
:= "INIT";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..4) = "DATA" then
MessageOut.Data(1..4) := "DATA";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..10) = "FLIGHTPLAN" then
MessageOut.Data(1..10) := "FLIGHTPLAN";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..4) = "PREV" then
MessageOut.Data(1..13) := "Previous Page";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..4) = "NEXT" then
MessageOut.Data(1..9) := "Next Page";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..2) = "UP" then
MessageOut.Data(1..11) := "Up Selected";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..4) = "DOWN" then
MessageOut.Data(1..13) := "Down Selected";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKL1" then
MessageOut.Data(1..13) := "LineSelect L1";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKL2" then
MessageOut.Data(1..13) := "LineSelect L2";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKL3" then
MessageOut.Data(1..13) := "LineSelect L3";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKL4" then
MessageOut.Data(1..13) := "LineSelect L4";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKL5" then
MessageOut.Data(1..13) := "LineSelect L5";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKL6" then
MessageOut.Data(1..13) := "LineSelect L6";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKR1" then
MessageOut.Data(1..13) := "LineSelect R1";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKR2" then
MessageOut.Data(1..13) := "LineSelect R2";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKR3" then
MessageOut.Data(1..13) := "LineSelect R3";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKR4" then
MessageOut.Data(1..13) := "LineSelect R4";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKR5" then
MessageOut.Data(1..13) := "LineSelect R5";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
elsif
Message.Data(1..5) = "LSKR6" then
MessageOut.Data(1..13) := "LineSelect R6";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
else
Text_IO.Put_Line("ERROR: Invalid Key data");
MessageOut.Data(1..16) := "Invalid Key data";
Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
end
if;
else
Text_IO.Put_Line("ERROR: Invalid topic received by ComOFP");
end if;
Text_IO.Put_Line("Exiting ComOFP AnyMessage");
end
AnyMessage;
end ComOFP;
Sample Text Output (of App2)
Receive of Register Request message where 3 followed by 4
are the numeric values of REGISTER and REQUEST. 5 in the 16th byte is the data Size and 8 6 1 4 0 is the data
where 8 and 6 are the numeric values of OFP and CHANGEPAGE while 1,4,0 is the
component key of ComOFP of application 1 meaning that it is the 4th component
(of any kind) registered in Application 1.
Bytes
read 29Received CRC 247 188
247 188
3 4 1 0 0 2 0
0 0 0 0
6 0 5 8
6 1 4 0
DisburseBytes
Write to be done 21
Disburse
Bytes Set_Event True
Disburse
Bytes after Wait
Disburse
Bytes after Reset Event
ReceiveInterface
wait satisfied
Receive1
returned with Success
ReceiveMessage
fromServer ReceiveInterface RecdMessage 21
ParseRecdMessages
message
data size 5
CopyMessage
CopyMessage 6 5
exit
from CopyMessage
ForwardMessage
to be called
1 True
Register
Message
do
ReadFile 250 250 240
RegisterRequest
topic 6 8 6 6 8 6TopicTable after Decode
in
Write TransmitQueue 172
ReceiveInterface
wait for event
T1
Entered
Transmit AnyMessage T1
3REGISTER RESPONSE 5
#
The above shows the REGISTER RESPONSE being transmitted to
notify App1 of the receipt of the REGISTER REQUEST.
. . .
Below is the receive of the OFP KEYPUSH (8 3 in the 3rd and
4th bytes) with 3 bytes of data (68, 73, 82) which, as ASCII characters are D I
R.
Bytes read 27Received CRC
237 191
237 191 8 3 1 4
0 2 5 0
0 0 0 11
0 3 68 73 82
DisburseBytes Write to be done 19
Disburse Bytes Set_Event True
Disburse Bytes after Wait
Disburse Bytes after Reset Event
ReceiveInterface wait satisfied
Receive1 returned with Success
ReceiveMessage fromServer ReceiveInterface
RecdMessage 19
ParseRecdMessages
message data size 1 True
3
do ReadFile 250 250 240
CopyMessage
CopyMessage 11 3
exit from CopyMessage
ForwardMessage to be called
Forward Delivery Publish 8
Delivery of topic 8 1
8
Deliver the topic 0 0 2
in Write QComOFP 184
ReceiveInterface wait for event
ComOFP after end of wait
Disburse Bytes in EventWait
ComOFP 38
ComOFP wait for event
Entered ComOFP AnyMessage 8 3
DIR
in
Write TransmitQueue 172
Exiting
ComOFP AnyMessage
T1
after
ForwardMessage QComOFP 184
Entered
Transmit AnyMessage T1
8OFP CHANGEPAGE 12 DI
TransmitMessage
sending 28 bytes
Immediately
above is ComOFP AnyMessage receiving the OFP KEYPUSH (8 3) topic followed by
the results of publishing the OFP CHANGEPAGE response with the message
delivered to Transmit via its queue and Transmit sending the 28 bytes of the
message.
The
results in App1 can be seen in the figures (although for the F-PLAN key).