|
| About
Use Cases |
Back
to Index |
|
| You
are looking at the Psyclone™
Use Cases file,
which technically is part of the Psyclone Tutorial suite. To use
it you must be familiar with certain concepts and have experience
with certain things, as listed in the section Prerequisits.
This set of use case examples is designed to familiarize you
with the Psyclone feature set and some important concepts through
explicit examples in the use of Psyclone and the construction
of systmes using it. You should walk away from this set of tutorials
with the ability to easily, effectively and comfortably design
and build mixed-language, highly configurable intelligent distributed
environments. |
Connecting
2 programs via the network
and sending messages between them |
Back
to Index |
|
|
Option 1: Use Corelibrary
If you really only need to connect 2 pieces of software
on 2 computers, and you don't expect to expand your system in
the future, and one or both are written in C++, you should consider
using CoreLibrary.
Option 2: Use Psyclone
This is a scalable and robust method — you can easliy
add more computers, and you get message and module debugging (psyProbe).
You connect two programs together via Psyclone by wrapping the
programs in AIRPlugs
and use these to connect via a whiteboard
in Psyclone. To do so you should take the following tutorials
(before looking at this one):
- How
to start up Psyclone
- Writing external modules in Java
/ C++
When running a time-dependent
system across a network you may want to synchronize the clocks
on the computers, otherwise the various post
and receive
timestamps on OpenAIR
messages will not be comparable between computers. Synchronization
can be done using the Network Time Protocol NTP
daemon; it is compatible with virtually all platforms.
The system we are about to describe will aptly demonstrate how
two programs written in two different languages can communicate
across a network. (The files for this examples should have been
included with this Psyclone distribution.) The system does the
following:
One program will assign a task
A second program will complete the task
TaskAssigner will connect to Psyclone, trigger itself once (this
is allowed as indicated by the attribute allowselftriggering="yes"),
and post a task assignment message with the parameterized blurb
Do this. Worker will be triggered on task assignment, do the task,
and post a task completion message with the parameterized blurb
I have finished.
TaskAssigner will be triggered on this task completion, and
proceed to assign another task, and will continue in this fashion.
TaskAssigner can be shutdown to discontinue task assignment and
restarted to continue task assignment.
The example makes use of the parameter
mechanism in Psyclone. The task assignment program, TaskAssigner,
is written in Java. The module specification, which we will put
into a psySpec, looks like this:
<module name="TaskAssigner"
allowselftriggering="yes">
<executable />
<parameter name="blurb"
type="String" value="Do
this."/>
<spec>
<context name="Psyclone.System.Ready">
<phase id="Assign
task">
<triggers
from="WB1">
<trigger
after="500" type="TaskAssigner.Ready"/>
<trigger
after="500" type="Output.Task.Complete"/>
</triggers>
<posts>
<post
to="WB1" type="Output.Task.Assign"
/>
</posts>
</phase>
</context>
</spec>
</module>
(Remember, you do not
need the <context> and <phase> tags if you are building
simple systems such as this example; they are only included
here for clarity.)
TaskAssigner.java, which will use AIR to connect to Psyclone,
will be constructed like this:
public class TaskAssigner {
private JavaAIRPlug plug; // the connection to Psyclone
private Message triggerMessage; // reusable incoming
message
private Message msg; // reusable ougoing message
/**
* This module waits for messages and responds.
* It could run some code after each message, depending on the
phase.
* This has been simulated with the System.out.println() calls.
*/
public TaskAssigner(String plugName, String host,
int port) {
System.out.println(plugName +" "+ host
+" "+ port);
plug = new JavaAIRPlug(plugName, host,
port);
if (!plug.init()) {
System.out.println("Couldn't
connect to Psyclone...");
return;
} else {
System.out.println("Connected
to Psyclone");
}
String blurb = plug.getParameterString("blurb");
plug.postMessage("WB1", "TaskAssigner.Ready",
"");
while (true) {
if ((triggerMessage = plug.waitForNewMessage(50))
!= null) {
System.out.println(triggerMessage.getContent());
// Task assignment processing
should occur here
msg = new Message(, ,,
blurb, );
plug.addOutputMessage(msg);
plug.sendOutputMessages();
}
}
}
}
The task completion program, Worker, will be written in C++.
The module specification will look like this:
<module name="Worker"
allowselftriggering="no">
<executable />
<parameter name="blurb"
type="String" value="I
have finished."/>
<spec>
<context name="Psyclone.System.Ready">
<phase id="Complete
task">
<triggers from="WB1">
<trigger
after="500" type="Output.Task.Assign"/>
</triggers>
<posts>
<post to="WB1"
type="Output.Task.Complete" />
</posts>
</phase>
</context>
</spec>
</module>
The code for this module is nearly identical to the TaskAssigner:
Message* triggerMessage;
JTime lastTime;
CppAIRPlug* plug = new CppAIRPlug(name, host, port);
if (!plug->init()) {
printf("Couldn't connect to Psyclone..."); fflush(stdout);
return 0;
} else {
printf("Connected to Psyclone");
}
JString blurb = plug->getParameterString("blurb");
while (true) {
if ((triggerMessage = tv->waitForNewMessage(50))
!= NULL) {
printf("%s\n", (char *) triggerMessage->getContent());
// Worker's work should be performed
here
Message* msg = new Message("", "", "",
blurb, "");
plug->addOutputMessage(msg);
plug->sendOutputMessages();
} else {
if (!tv->isServerAvailable())
printf("x");
else
printf(".");
fflush(stdout);
count++;
if (count == 10) {
printf("\r \r");
fflush(stdout);
count = 0;
}
}
}
}
In your favorite text editor, include the module specifications
above into a psySpec and save the file as ‘twoWorkers.xml.
Remember to include a whiteboard spec, and in your spec include
one whiteboard, and name the whiteboard WB1, like this:
<whiteboard name="WB1"
maxcount="10000">
<description>This
is Whiteboard 1</description>
</whiteboard>
At the command line, run Psyclone with the name of the spec
file above using the command:
>./psyclone spec=twoWorkers.xml
For this exercise, Worker should run on a machine other than
the one that Psyclone is running on. After the Worker has been
compiled and linked in the C++ development environment of your
choice, simply move the Worker executable to a different machine,
and run the executable as:
>./worker psyclone=[machine]:10000
name=Worker
where [machine] is the machine name or IP address of the machine
that Psyclone is running on. Worker will connect and just wait
until it is assigned a task by TaskAssigner.
For this exercise, TaskAssigner should run on a machine other
than the one that Psyclone and Worker are running on. If the TaskAssigner
has been successfully compiled in a Java environment, move the
class file to a different machine, and run the TaskAssigner as:
>java -cp .;JavaOpenAIR.jar TaskAssigner
psyclone=[machine]:10000 name=TaskAssigner
if you are operating in Windows, and
>./java -cp ./:JavaOpenAIR.jar
TaskAssigner psyclone=[machine]:10000 name=TaskAssigner
if you are operating in a Unix-based OS.
When running a time-dependent
system across a network you may want to synchronize the clocks
on the computers, otherwise the various post
and receive
timestamps on OpenAIR
messages will not be comparable between computers. Synchronization
can be done using the Network Time Protocol NTP
daemon; it is compatible with virtually all platforms.
|
Connecting
many programs via the network
and sending messages between them |
Back
to Index |
|
| If you need to connect three
or more programs together Psyclone is likely to be the best way
to do so. This is because Psyclone provides:
- Easy incremental system creation
- Increased debugging support during development
- Flexible rewiring of data flow between
components
- Central management of distributed system
- ... and more
If you need to connect multiple programs (that is, more than 2)
via the network you might want to look at one or more of the following
tutorials:
- How to start
up Psyclone
- Running the provided external Java
examples / C++ examples
- Creating a publish-subscribe
system
When running a time-dependent
system across a network you may want to synchronize the clocks
on the computers, otherwise the various post
and receive
timestamps on OpenAIR
messages will not be comparable between computers. Synchronization
can be done using the Network Time Protocol NTP
daemon; it is compatible with virtually all platforms.
|
| Connecting
programs across firewalls and routers |
Back
to Index |
|
| Psyclone,
and its AIRPlugs, have built-in support that will automatically
detect and route network traffic in even the most complicated networks.
To read about how to start up Psyclone, go here.
To
read about connecting programs via the network here.
If you wish to hook up programs as modules all you need to do is
to make sure that they each can connect to Psyclone on its main
port, which is usually port 10000. If all the modules in the system
can connect to Psyclone on this port, your system will work, even
if Psyclone cannot open a new connection back to the module (because
of a firewall for example).
In all but the largest most complicated systems there will be no
noticeable delay in the networking when routing via firewalls and
routers. Only if you have multiple computers on each side of the
router or firewall will you notice a difference, as all communication
between modules on the inside and the outside of the system then
will be routed via the main Psyclone computer. However, in this
case you should consider asking your system administrator to allow
more liberal communication through your firewall.
When running a time-dependent
system across a network you may want to synchronize the clocks
on the computers, otherwise the various post
and receive
timestamps on OpenAIR
messages will not be comparable between computers. Synchronization
can be done using the Network Time Protocol NTP
daemon; it is compatible with virtually all platforms.
|
| Sending
binary data in a message |
Back
to Index |
|
| Psyclone
has built-in support for binary content in messages. You would normally
use the object slot in a message for this content, just as you do
when sending other non-text information, such as dictionaries or
collections. If this object has binary content it will automatically
be attached as a binary attachment to the message when transmitted
across the network.
In both internal and external C++ and external Java modules you
can add raw binary data to a message using the DataSample object.
An example for C++ could be:
int len = 5322;
char* data = new char[len];
memset(data, 0, len);
Message* msg = new Message("", "", "");
DataSample* sample = new DataSample();
sample->giveData(data, len);
msg->setObject(sample);
In Java, you do almost the same:
int len = 5322;
byte[] data = new byte[len];
for (int n=0; n<len; n++)
data[n] = 0;
Message msg = new Message("", "", "");
DataSample sample = new DataSample();
sample.data = data;
msg.object = sample;
Now you can send your message as normal.
You can create your own objects to carry binary information. In
Java you need to inherit from either the DataSample object or from
the BaseObject – please see the DataSample object to see what
you need. In C++ you need to obtain the version of the CoreLibary
source code which matches the version of the AIR Plug or the SDK
you are using and then implement a third party object. See the CoreLibrary
homepage for more information on how to do this. |
| Creating
a publish-subscribe system |
Back
to Index |
About
pub-sub |
| You can create a simple publish-subscribe
system in Psyclone by making two modules, one that posts a message
type and another that subscribes to that message. For this you use
an intermediate message handler called a whiteboard.
If you are only planning on connecting two programs, and not to
expand your system beyond that, you need not read any further. To
see how to connect two programs over a network, go here.
In Psyclone simple pub-sub system can very quickly (and safely)
grow to a large pub-sub system: Once you have defined at least one
message type to be posted any module can subscribe to that message.
The best approach to take is to define all your modules in a psySpec.
For example, a module that posts a message of type A.B.C
might look like this:
<module name="MyModule1">
<description>This
module bootstraps the dominos by setting their root context.</description>
<spec>
<context name="Psyclone.System.Ready">
<phase
name="Look for System Created">
<posts>
<post
to="Whiteboard.1" type="A.B.C"
/>
</posts>
</phase>
</context>
</spec>
</module>
According to this specification, this module will post message
of type A.B.C when Psyclone is started
up and the system is created (Psyclone posts Psyclone.System.Ready
which starts our module). Any modules that want to subscribe to
the A.B.C message type would be
specified like this:
<module name="MyModule2">
<description>This
module bootstraps the dominos by setting their root context.</description>
<spec>
<context name="Psyclone.System.Ready">
<phase
name="Look for alphabtic messages">
<triggers
from="Whiteboard.1">
<trigger
type="A.B.C"/>
</triggers>
<posts>
<post
to="Whiteboard.1" type="D.E.F"
/>
</posts>
</phase>
</context>
</spec>
</module>
This module is triggered by MyModule1's
posting of A.B.C and when it posts
messages it will post a message of type D.E.F.
Once defined as XML in the psySpec, you need to make sure that
the modules actually exist as executables. (One important thing
we are leaving out in the above example is that the two modules
have no crank,
or method to run when they get triggered.) To see how to run modules
that post and receive messages using cranks, follow the tutorial
on connecting
two modules via the network, or look at the tutorial
on how to run the provided examples (internal
C++, external
C++, Java).
More info on how to construct psySpecs is given here.
The big benefit about pub-sub systems is the relative ease with
which the "wiring" between modules can be changed: To
route a message A.B.C to MyModule4 instead of MyModule2 simply unregister
MyModule2 and register MyModule4 for the message type A.B.C. Registrations
can be specified up front in the psySpec or they can be transmitted
by the module directly to Psyclone when the module has started up
(and can be overwritten at any time). The registration tells Psyclone
which message types the module is interested in, among other things.
It can also tell Psyclone which contexts the module wants to be
active in and what kinds of messages the module will be posting.
|
Using
contexts & phases
in a hypothetical rocket launch system |
Back
to Index |
About
contexts |
| Contexts are used for managing
the global state of a system. Here we will take the example of starting
up and shutting down a system.
The example we will use is that of a rocket. This hypothetical
rocket has three main modes that the system can be in: Launch, mid-flight
and re-entry. Then there are 30 software modules total that control
the rocket, but not all of them need to be involved in all three
modes. We will assume that 10 of them need to keep running at all
tiems, another 10 are only needed during launch and the final 10
are only needed during re-entry. To design this system we will use
the root-context Rocket.
A root context is the context
at the beginning, or "root", of the context list. For
example, in the context tree First.Second.Third,
the root context is the First context, because
each context, going from left to right, is more specific than
the one before. First.Second.Third is called
a context tree because it represents a hierarchy
where the second, third, etc. context listed can be replaced by
other contexts, making the context hierarchy into a tree-like
structure. Hierarchical contexts are explained in the manual.
There will be three branches on the root context to represent
the three stages of the rocket's lifespan which are Launch, Flight
and ReEntry. So we have the following possible context trees:
Rocket
Rocket.Launch
Rocket.Flight
Rocket.ReEntry
Since contexts are global states, and failure is one of those
system states that the whole system should know about, it would
also be good to represent this for the rocket:
Rocket.Fail
However, the meaning of Rocket.Fail is rather unspecific (which
part of the Rocket has failed?), so it would be better to create
an additional fail branch for each of the different stages:
Rocket.Launch
Rocket.Launch.Fail
Rocket.Flight
Rocket.Flight.Fail
Rocket.ReEntry
Rocket.ReEntry.Fail
But who decides when each context should be posted? Some of the
software modules in the Rocket are specifically designed to monitor
the status of everything and these modules will in turn decide when
and if to post any of the contexts, including the Fail contexts.
For example, one module could be monitoring specifically whether
any part of the system fails during launch. If that happens the
module would post Rocket.Lauch.Fail.
Now let's define a module whose job it is to manage these contexts.
It monitors the state of the system and makes sure that each context
is posted when appropriate. Let's call this module ContextPoster.
Initially, when the system is started up, Psyclone will automatically
post Psyclone.System.Ready, indicating that Psyclone is indeed started
and ready to run our system. This would mean that we can launch
the rocket. We use Psyclone.System.Ready as context and trigger
for the ContextPoster module.
Since contexts are posted like
any other message, context postings can be used as triggers for
any module. To post a context, use the message type Psyclone.Context:X,
where the X is replaced with the name of the context you wish
to post (make sure you don't forget the colon). This can be experimented
with in the dominosPsySpec.xml.
To make things simple, let's say that the rocket always launches
as soon as possible, so when ContextPoster is triggered it will
set the global context to Rocket.Launch by posting a context message
of type Rocket.Launch (Psyclone.Context:Rocket.Launch).
We will make Rocket the context for the 10 modules
which should be active all the time. This means that even when Rocket.Launch,
Rocket.Flight and Rocket.ReEntry are posted, these modules will
still be in context.
Child-contexts (branches) do
not invalidate parent-contexts, they only make them more specific.
For example, a biological system might have the context Alive.Awake,
which specifies that the system is alive and awake. Should the
system switch to being asleep the appropriate module would post
Alive.Sleeping. One could then also have an intermediate
stage, Alive.Sleepy, which helps manage the whole
transition from the Awake state to the Sleeping
state. During these context transitions the system is continuously
alive — the root context is always valid.
We will make Rocket.Launch the context for all modules that have
to be running during launch. ContextPoster will monitor the modules
and the rocket's own position in space, and when it's been launched
successfully it will post the context Rocket.Flight. When Rocket.Flight
is posted the context Rocket.Launch is no longer valid. Therefore,
all modules with Rocket.Launch as context will go out of context
-- they will no longer be able to receive triggers (and theoretically
therefore not take up any CPU).
We will make Rocket.ReEntry the context for all modules that are
only supposed to be running during re-entry. These will be out of
context during flight and launch because the context Rocket.ReEntry
cannot be active while Rocket.Launch and Rocket.Flight are active.
Now, something could of course fail in the system at any time. We
will design ContextPoster such that it will post the context Rocket.Flight.Fail
if this happens during flight. We will further create a new module
that is designed for such conditions. Its context is Rocket.Flight.Fail.
This module, called FlightFailurePlanner, will determine when a
parachute should be deployed. Further, its job is to decide when
it's safe to initiate re-entry, at which point it would post Rocket.ReEntry.
This will activate all the modules needed for the re-entry condition,
i.e. all modules whose context is Rocket.ReEntry. |
|