USE CASES
PART 4 of 4

Back to Index An index to the main sections below is stored in index.html
that should be part of the distribution that includes this file

 

 

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):

  1. How to start up Psyclone
  2. 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:

  1. How to start up Psyclone
  2. Running the provided external Java examples / C++ examples
  3. 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

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

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.

 

 

 

Troubleshooting
- Make sure you are in the correct directory for your operating system.

- Make sure you have included all command line parameters needed.

- Make sure your operating system knows the path to the directory where the Psyclone executable is located. You may need to provide the whole path on the command line. 

- If you are having problems running the Java examples on your computer, you may have to update your Java virtual machine. Alternatively, you can try re-compiling the examples using the above instructions.

- The nature of dynamically loaded libraries where they are loaded and unloaded periodically means that static and global variables loose their meaning. When creating internal crank functions do not use global or static variables in your code. Furthermore, on some UNIX systems such as Linux you will get errors when trying to load a library with static or global variables, usually complaining about an undefined symbol: __dso_handle. If you see this, go through your code and remove all global and static variables.

Let us know if you experience persistent problems.