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

 

 

Getting Started with Psyclone Back to Index  

To learn the basic tenets of Psyclone jargon, go here.

In the psyclone directory you will find the Psyclone executable for your operating system ("psyclone" for OS X and Linux; "psyclone.exe" for Windows).

To run Psyclone, open a console window and cd into the psyclone directory. 

The psyclone executable is run by the following command: psyclone

 Example:
  >./psyclone

This should start Psyclone, giving some printout of its acitivities in the console. 

NOTE: If you get the following message: 
   ./psyclone: Permission denied.

you do not have execution permission in this directory. Execute the command:

  >chmod a+rwx *

at the command line, and then try running Psyclone again.

You can specify a setup file to use at the command line, called a psySpec, to initialize the system. The psySpec is read in by doing:

   psyclone spec="[psySpecName]"

 Example:
  >./psyclone spec="roundRobinPsySpec.xml"

The roundRobinPsySpec.xml is a simple example psySpec (should be included with your copy of Psyclone). To see what each of its parts mean, see the roundRobinPsySpec tutorial below.

Psyclone will print out an error if (a) it cannot find the specified psySpec. It will also print out errors if (b) the port specified is in use. Troubleshooting.

To quit Psyclone at the command line press CTRL+C.
If CTRL+C at the command line doesn't work, pressing CTRL+C three times will perform a hard exit.

RELATED: constructing a psySpec

There is a third parameter you can specify, which will help you understand what is going on at run-time with Psyclone. It is called verbose, and it is used in the following way:

 
psyclone port=[port] spec="[psySpecName]" verbose=[1,2,3,4]

 Example:
  >./psyclone port=20000 spec="testPsySpec.xml" verbose=4

Related: command line parameters verbose= spec=

If verbose is specified, Psyclone will print run-time messages to the console while running. 

To see the status of Psyclone at run time, use psyProbe, the Web-based monitoring tool.

Run the example posting and receiving modules to see a demonstration of how the posting and registration works in Psyclone.

To quit Psyclone from psyProbe you can use the link the bottom of the main psyProbe page.

To quit Psyclone at the command line press CTRL-C.

If CTRL-C at the command line doesn't make Psyclone shut down nicely, pressing CTRL-C three times will perform a hard exit.

 

Using psyProbe

Back to Index  
psyProbe is the web-based monitoring interface for Psyclone. With it you can view the internals of Psyclone at run-time. Before you can use psyProbe you have to run Psyclone.

Use psyProbe by typing the following into the URL box on your browser:

  http://
[machine]:[psyclonePort]

 


where [psyclonePort] is the port that Psyclone was started up with, or assigned to psyProbe in the psySpec (the default is 10000 if nothing is specified), and [machine] is either the IP address for the machine that Psyclone is running on, or its name, or the word "localhost".

For example, if your machine's name is "matrix", and Psyclone is running on port 10000, you would type http://matrix:10000 into your browser. You can also type http://localhost:10000

Notice: You may have to use http:// in the path since most browsers do not default to this protocol by themselves. More specifically, when you use psyProbe you are not using port 80 on the computer (the default port for http transfer); the browser may not know to default to the http:// protocol for any other port than port 80.

Posting Messages
Go to the Post Message page by clicking on the post_message_page_:: link on the main psyProbe page (if you have Psyclone currently running you can go there now by clicking here). 

To post a message to a Psyclone Whiteboard you need to specify three things, at a minimum: Who the message is from, which Whiteboard(s) will receive the message, and what type the message is. You specify the sender in the first panel (the one called from). The to on the page contains a list of the Whiteboards — check off one or more Whiteboards. The third panel on the page, post_message_::, allows you to manually key in the message type, and the message content, if you have any. When this is done, you can post your message by pressing the 'post' button at the bottom.

In addition to this, you can make any module receive a 'cc' on your message. This is done in the from panel. A 'cc'd module will receive (i.e. it will be woken up by) the message no matter whether it registered for its type or not.

Whiteboards
Whiteboards can carry discrete messages and continuous streams. The two differ in that messages are like email: You receive a message and that it is that, unless you write back that is. Streams are like a phone call: You get a ring and you pick up, and there is something more coming. The whole thing continues until you hang up. During the phone call you know that what comes through is audio (as opposed to video or a fax).

Therefore, when you use psyProbe to view the content of the whiteboards, the stream samples do not show up in the same way as messages do, as they reside inside whichever streams they belong to Whiteboard. PsyProbe lists messages and streams separately, and you need to look at the streams to see your samples, whether they are video, audio, or something else.

Samples in streams normally come in rapid succession (10s or 100s each second). Usually one module post to a stream and one or more will be subscribers. As a result, each datasample doesn't have a named type, they all automatically live inside a stream, which lives in a whiteboard. Receivers of the streams can perform time-based searches inside the stream and don't only have to receive all the samples in turn.

Using Whiteboard Filters
The Whiteboard page allows you to filter the messages shown on the messages page. If you are using the recommended dot-delimited message name format (e.g. My.Example.Message.Type, Input.hearing.voice), you can construct filters using a star (*) as a wildcard.

For example, let's say that the Whiteboard contains the following 5 message types:

  Namespace.Input.Message.One
  Namespace.Output.Message.Alpha
  Namespace2.Output.Message.Omega
  Namespace3.My.Input.Message.One
  Namespace3.My.Output.Info.Two

The filter pattern *Namespace.* will result in only the first and second messages types showing on the page. The pattern Namespace3.My.Input.* will block all messages except the fourth in the list. The pattern *.Message.* will, in this example, show all messages except the last one.

This mechanism is very useful for making sure that messages that should be posted have been posted. The filtering is not limited to dot-delimitation; you can specify for example *M* and all messagetypes above above will be allowed; *essag* to exclude the last one.

The content filter allows you to filter on any content of the messages. If you use this feature only messages which contain a particular string in their content will be listed in the list. (Content refers to the payload of messages.) The input field in the content filter allows you to input any ASCII string for this purpose. For example, *clone* will filter out all messages except those which contain the string "clone" somewhere in the content.

The amount of content shown in the table can also be set with the radio buttons none, tiny, full. Selecting "none" will hide the content of the messages; selecting tiny will show the first line of the content (or a summary of the content, if Psyclone knows how to parse it); "full" will show the full content of all messages below each message in the table.

Using psyProbe With Satellites

If you want to use PsyProbe to see WBs running in the Satellite, you must put a copy of the full HTML tree (from the Psyclone distribution) next to the Psyclone Satellite Server in the directory where you run the Psyclone -satelliteserver command.

 

Creating and running your first Psyclone system   Back to Index  

Whether you are a novice or expert in modeling complex systems the best place to start will probably be the examples provided with the Psyclone distribution (see the index).

The path of least resistance (can be taken in any order, except the first two):

Getting started with Psyclone
Concepts: Whiteboards, Modules, Messages, Streams
Running the provided external Java examples
Running the provided external C++ examples
Running the provided internal C++ examples
Running the provided roundRobinPsySpec example
Running the provided dominoPsySpec example

There are several things that need to be considered when creating the first system using Psyclone. Firstly, Psyclone uses a few new concepts that you may want to familiarize yourself with. Secondly, you will want to look at the message and routing protocol that Psyclone uses, called OpenAIR. Thirdly you probably should look at the examples provided with the distribution.

One of the complicating things about real-time systems with independent elements is the way that causality works in them — when each element can operate independent of the others some causal chain of events that the designer wanted to happen may not happen because one or more events in the system don't always happen at the right time. Systems which are highly time-dependent, and especially systems which are intended to interact with the real world, will most likely need to be tuned and tested before they operate as intended. Yet there is no other way to model many of the highly complex systems we see in the world. Here is what you can do:

  • The Constructionist Design Methodology provides some principles to help with this and related problems. Mindmakers.org has a section on this methodology.
  • Go through as many tutorials on Psyclone as you can before starting to design your own system — be prepared to have to redo some of your design.
  • Use your own carefully designed message/stream type ontology or a third-party ontology.
  • Desing your system to be resilient to temporal variation is events.

 

Running the provided external Java examples   Back to Index  

Java examples are included in the JavaAIRPlug download. The examples included are JavaPoster.java and JavaReader.java. The JavaPoster simply posts a message of type My.Test.Message.Type to whiteboard WB1 when you press the return key. The JavaReader is an example of a module that registers with whiteboard WB1 for that same message type, and simply prints it to the console.

To run (on Linux and Mac OS X), do:
  java -classpath ./:JavaOpenAIR.jar JavaReader psyclone=[psyclone host]:[psyclone port]

where [psyclone host] is either the IP address of your machine or the word localhost, and [psyclone port] is the port that you started Psyclone up with.

  Example command (on Linux, with Psyclone running on port 10000): 

  >java -classpath ./:JavaOpenAIR.jar JavaReader psyclone=localhost:10000

If you have problems with Java you may want to recompile the files (see below).

Notice that on Windows you will be using a slightly different syntax for paths: 
  java -classpath .;JavaOpenAIR.jar JavaReader psyclone=[psyclone host]:[psyclone port]

i.e. use semicolon instead of colon.

If you are using Cygwin on Windows you will be using yet another syntax for paths:
  java -classpath .;JavaOpenAIR.jar JavaReader [psyclone host] [psyclone port]

i.e. use semicolon instead of colon, and for paths, use backslash "\" instead of forward slash "/".

Now you need to go to another console window, and in the same JavaExamples directory, do:

  Example (Linux): 
  >java -classpath ./:JavaOpenAIR.jar JavaPoster psyclone=localhost:10000

Pressing Enter on the JavaPoster you should see the JavaReader print out that it received it, on the command line.

If you get a message of the effect 'java.lang.OutOfMemoryError', you may need to provide a command-line parameter to ensure sufficient memory allocation for Java. 
Use the following command to run Java:

  >java -Xincgc -Xms50M -Xmx200M -classpath ./:JavaOpenAIR.jar JavaReader psyclone=localhost:10000 

The first parameter is incremental garbage collection, the second is the startup memory size and the third is the maximum. In this example, an out of memory error would not occur until Java has used up 200MB of memory. 

To compile the Java examples you need to include the JavaOpenAIR libraries. To compile the Java examples you will need the need to download and use the Sun Java SDK (choose the one that is appropriate to your platform).

  javac -classpath JavaOpenAIR.jar JavaReader.java
  javac -classpath JavaOpenAIR.jar JavaPoster.java 

To compile on Linux and Mac OS X, do:

 Example: 
  >javac -classpath ./:JavaOpenAIR.jar JavaPoster.java

 and:
  >javac -classpath ./:JavaOpenAIR.jar JavaReader.java

Both should return without error (let us know if you have problems). Use the above commands to run them.

 

External Java module example in detail   Back to Index  

To read about external modules in general, go here.

To write an external module in Java, you will need the Java AIR Plug. (If JavaAIRPlug was not included with this distribution you can download the plug by going to http://www.cmlabs.com/psyclone/download/, and selecting the platform that you are developing your external module on).

We will assume that you start your external Java module manually. To start modules automatically, see the tutorial on Automatically starting modules.

To write an external Java module, you will need to create a source file bearing the name of your module. We will create a module call Reader, with source Reader.java.

Initially in your source file you will always need to import the AIR package that come with the plug:
package com.cmlabs.air; After creating a class definition and creating a few instance variable:

public class Reader {
private JavaAIRPlug plug; // the connection to Psyclone
private Message triggerMessage; // reusable incoming message
private Message msg; // reusable ougoing message

you will create a 3-arg constructor:

public Reader(String plugName, String host, int port) {

In the constructor, instantiate the module

plug = new JavaAIRPlug(plugName, host, port);

and use the plug call init() to connect to Psyclone:

if (!plug.init()) {
   System.out.println("Could not connect to the Server on " + host + on port " + port + "...");
   System.exit(0);
}

Next, the important work that the module needs to do can now be written. This sample code simply waits for a trigger message (of message type that is specified in the psySpec for this module) by using the plug call waitForNewMessage(long ms), and prints information about the trigger message when the module is woken up. Additionally, a new basic message is create, given to the plug as a message to post using the plug call addOutputMessage(), and then post a message (of message type that is specified in the psySpec for this module) using the call sendOutputMessages():

while (true) {
   if ((triggerMessage = plug.waitForNewMessage(50)) != null) {
   System.out.println(triggerMessage.postedTime.printTime() + ": Reader       received message " + triggerMessage.type + " from " +       triggerMessage.from);
   msg = new Message("", "", "");
   plug.addOutputMessage(msg);
   plug.sendOutputMessages();
} else {
if (!tv.isServerAvailable())
   System.out.printf("Psyclone is available but the module
   has not been awoken ");
else
   System.out.printf ("Psyclone is not available");
      }
   }
}

Finally, you will need a main() method in the class in order to startup the module. The main() method will generally comprise of code that interprets some of the command-line parameters that would be important, such as the host and port that Psyclone is running on. Some sample code that can be used in the external module to do this would be:

public static void main(String[] args) {
String usage = "Reader usage: Reader   [psyclone=[psycloneHost][:psyclonePort]] [name=Reader] (eg \"Reader   psyclone=:27000\")";
// default host and port
String host = "localhost";
int port = 10000;
String name = "Reader";
String arg;
for (int n = 0; n < args.length; n++) {
   arg = args[n];
   if (arg.startsWith("psyclone=")) {
      String str = arg.substring(9);
      if (str.length() > 0) {
         int pos = str.indexOf(":");
         if (pos < 0)
            host = str;
         else if (pos > 0)
            host = str.substring(0, pos);
         port = Utils.str2int(str.substring(pos + 1));
         }
   } else if (arg.startsWith("name=")) {
      name = arg.substring(5);
      }
   }
   Reader reader = new Reader(name, host, port);
}

 

Running the provided external c++ example   Back to Index  

This external C++ example is included in the CppAIRPlug download. The example inlcudes two programs called CppPoster and CppReader. The CppPoster simply posts a message of type My.Test.Message.Type to whiteboard WB1 when you press the return key. The CppReader is an example of a module that registers with whiteboard WB1 for that same message type, and simply prints it to the console. This examples parallels the example of external modules written in Java.

Before you run the code you will need to compile it. Refer to the QUICKSTART file for the external C++ example that is provided with the Psyclone distribution.

To run the C++ examples you will have to have a Whiteboard called WB1 already running in Psyclone. The simplest way to do this is to just start up Psyclone:

./psyclone

and then, in the directory where the C++ examples are, give the command:

> CppReader psyclone=[psyclone host]:[any port number]

The colon : has to be there if you give a port number; you don't need to specify psyclone=localhost:10000 at all if you want to use the default of localhost:10000.

and in another console window, do:

> CppPoster psyclone=[psyclone host]:[any port number]

in this order, each in its own console window, where [psyclone host] is the IP address of the computer running Psyclone, the name of the computer running Psyclone (or the word "localhost", if you're running the example on the same machine as Psyclone). The Reader will register with whiteboard WB1 for a message of type Test.Message.Type. CppPoster will post a message of type Test.Message.Type once every second.

When CppPoster posts a message it goes to WB1, which then sends it to the registered CppReader module. If you look at WB1 in psyProbe you can see the message entered every time foor both events. To run psyProbe, start a Web browser and type into it:

http://localhost:10000

This should give you the front page of psyProbe. (If you get something else, make sure you have included the html directory in the same directory as Psyclone.) Here you can probe around to see what is happening to your messages and your modules.

 

Compiling the C++ Examples

To compile the C++ examples for Linux or Mac OS X, use the Make file included with the examples. For Windows you need the Visual Studio 6 (.NET project files can be made available).

Related: How to run the SDK

 

External C++ module example in detail   Back to Index  

To read about external modules in general, go here.

To write an external module in C++, you will need the CppAIRPlug. (If CppAIRPlug was not included with this distribution you can download the plug by going to http://www.cmlabs.com/psyclone/download/, and selecting the platform that you are developing your external module on).

We will run this external module manually. (The tutorial on Automatically starting modules explains how to do this automatically.)

To write an external C++ module, you will need a source and header file bearing the name of your module. The Reader example with the Psyclone distribution is Reader.cpp and Reader.h.

The header file must contain at least the following, using Reader as the example:

#include "Communicator.h"
#include "Component.h"
#include "CppAIRPlug.h"
class Reader : public CppAIRPlug
{
   public:
   Reader(JString name, JString host, int port);
   virtual ~Reader();
};

The source files must first and foremost include the header:

#include "Reader.h"

The source file must also have a constructor and destructor:

CppReader::CppReader(JString name, JString host, int port) : CppAIRPlug(name, host, port) {}
CppReader::~CppReader() {}

The source file must have a main() function in order to startup the module:

int main(int argc, char* argv[])

The main() function will generally first contain some code that interprets some of the command-line parameters that would be important, such as the host and port that Psyclone is running on. Some sample code that can be used in the external module to do this would be:

JString host = "localhost";
int port = 10000;
JString allArgs;
for (int ii=0; ii<argc; ii++) {
   allArgs += JString(argv[ii]) + " ";
}
allArgs.trim();
Dictionary argDict = allArgs.splitCommandLine();
JString arg;
JString val;
for (int n=1; n<argDict.getCount(); n++) {
   arg = argDict.getKey(n);
   val = argDict.get(n);
   if (arg.equalsIgnoreCase("psyclone")) {
      Collection col = val.trim().split(":");
      if (col.get(0).length() > 0)
      host = col.get(0);
      if (col.get(1).length() > 0)
      port = col.get(1).toInt();
   }
}

The module should then be instantiated:

CppReader* tv = new CppReader("CppReader", host, port);

and use the plug call init() to connect to Psyclone:

if (!tv->init()) {
   printf("Could not connect to Psyclone on '%s' port %d, exiting...\n\n", (char*) host, port);
return 0;
}

Since you are running the module manually, you may now test for the availability of Psyclone and, if available, start the module, otherwise, delete the module and exit from the program:

if (tv->isServerAvailable()) {
   tv->start();
} else {
   printf("Could not connect to Psyclone on '%s' port %d,
      exiting...\n\n", (char*) host, port);
   delete(tv);
   exit(0);
}

Finally, the important work that the module needs to do can now be written. This sample code simply waits for a trigger message (of message type that is specified in the psySpec for this module) by using the plug call waitForNewMessage(long ms), and prints information about the trigger message when the module is woken up. Additionally, a new basic message is create, given to the plug as a message to post using the plug call addOutputMessage(), and then post a message (of message type that is specified in the psySpec for this module) using the call sendOutputMessages():

Message* triggerMessage;
Message* msg
int count = 0;
while (true) {
   if ((triggerMessage = tv->waitForNewMessage(100)) != NULL) {
      printf("\r%s: CppReader received message %s from %s...\n", (char*)       triggerMessage->getTimestamp().printTimeMS(), (char*) triggerMessage->getType(),       (char*) triggerMessage->getFrom());
      msg = new Message("", "", "");
      plug->addOutputMessage(msg);
      plug->sendOutputMessages();
      fflush(stdout);
   } else {
      if (!tv->isServerAvailable())
      printf("Psyclone is available but the module has not been awoken");
      else
      printf("Psyclone is not available");
      }
   }
return 0;

 

Running the provided internal c++ examples   Back to Index  

Internal modules are designed to run inside Psyclone for maximum performance and efficiency. They are similar to external modules, but somewhat simpler to design, implement and maintain.

A library contains crank functions, which can be as complex as the designer wants. Cranks share the same interface definition, but must have different names. Any number of libraries can be loaded by Psyclone at the same time.

When an Internal Module is specified in the psySpec file, it may include a crank section. The crank section can list one or more cranks to be executed. These then reference crank methods in the externally loaded library. To take an example, a crank 'mycrank', contained in an external library called 'mylib', can be specified in the psySpec file as

        <crank name="mylib::mycrank" />

Thust, to include a crank function from a third-party library in an internal module you will need to create a psySpec and define the module using the specific crank function as a crank. For example, to define a module which uses the crank function testCrank1 located inside the example library called cm, you would specify the module like this:

<module name="MyModule" type="internal" >
  <description>
This module is my first module</description>
  <trigger after="
100" type="Psyclone.System.Ready"/>
  <crank name="
cm::testCrank1" />
  <post to="
WB1" type="Some.Message.Type" />
</module>

This means that when the Internal Module that contains this crank specification gets awoken with a trigger, the triggering message and any retrieved messages will be delivered as input to the crank. In return the Internal Module expects zero or more output messages, that is, the crank may post something as a result of having been awakened.

To compile the C++ examples for Linux or Mac OS X, use the Make file included with the examples. For Windows you need the Visual Studio 6 (.NET project files can be made available). To start up the project in Visual Studio, double-click on the mycmdll.sln file. Then build the solution. This will compile and build the library file cm.dll.

 

Running the provided roundRobinPsySpec example Back to Index  

If you are new to Psyclone this is a good place to start.

A psySpec is an XML file which is used to initialize things. The psySpec dictates for example which whiteboards and which modules to start up, as well as some other things.

The roundRobinPsySpec.xml psySpec file is included with Psyclone and reproduced here in full, for convenience. It should take you about 5-10 minutes to go through this example.

The file itself looks like this:

<psySpec name="Demonstration of Whiteboards, Modules and Messages" version="1.2">

  <global>
  <title>Round-robin psySpec</title>
  <description>
    This demo shows basic usage of Whiteboards Modules and Messages.
  </description>
  </global>

  <whiteboard name="Whiteboard.1" maxcount="15000">
    <description>Stores all messages from the three round-robin modules.</description>
  </whiteboard>

<!-- #################### ROUND ROBIN MODULES #################### -->

  <module name="RoundRobin-1">
    <description>
      This module gets initially triggered by Psyclone.System.Ready,
      which is posted by Psyclone when it's ready,
      and after that it is triggered by Trigger3
      which is posted by the RoundRobin-3 module.
    </description>
    <triggers from="Whiteboard.1">
      <trigger after="100" type="Psyclone.System.Ready"/>
      <trigger after="500" type="Trigger3"/>
    </triggers>
    <post to="Whiteboard.1" type="Trigger1" />
  </module>

  <module name="RoundRobin-2">
    <description>This module will trigger off RoundRobin-1's posting.</description>
      <trigger from="Whiteboard.1" after="500" type="Trigger1"/>
      <post to="
Whiteboard.1" type="Trigger2" />
  </module>

  <module name="RoundRobin-3">
    <description>This module will trigger off RoundRobin-2's posting.</description>
    <trigger from="
Whiteboard.1" after="500" type="Trigger2"/>
    <post to="
Whiteboard.1" type="Trigger3" />
  </module>

</psySpec>

When Psyclone starts up it can be told to read in this psySpec. Go to the specification of running Psyclone; use the roundRobinPsySpec.xml as the value for the 'spec' parameter.

READ MORE: running Psyclone

If the psySpec specifies internal modules, Psyclone also spawns these. In the roundRobinPsySpec.xml file we have defined three internal modules. These modules are so that they only use functionality that is built into Psyclone: receiving and posting messages.

By default, Psyclone always spawns (creates) the whiteboard AIRCentral. This is because Psyclone is OpenAIR compliant. In the case of the roundRobinPsySpec.xml we also create a second whiteboard, whiteboard.1.

When roundRobinPsySpec starts up the modules start to post messages, each in turn. This is because they have been rigged in the psySpec to trigger each other in a loop: module RoundRobin-1 posts a message type which RoundRobin-2 has subscribed to, and so on; RoundRobin-1 also subscribes to a message type that is posted by RoundRobin-3. Each of the triggers are outfitted with a delay, the after= parameter. This means that Psyclone will wait for the specified number of milliseconds before waking the module up with that trigger.

READ MORE: psySpec internal module example writing a psySpec running the dominosPsySpec

 

Running the provided dominosPsySpec example Back to Index  

If you are new to Psyclone you might first want to look at Running the provided roundRobinPsySpec example. If you follow most of the links provided down one level of detail, it should take you about 10 minutes to go through all points. A good way to proceed is to first go through all the steps, and then go back and look further at some of the links provided.

1. Psyclone Startup
When Psyclone starts up it reads in the psySpec, an XML file which is used to initialize. The psySpec dictates for example which whiteboards and which modules to start up, as well as some other things.

The dominosPsySpec.xml file is included with Psyclone. Go to the specification of running Psyclone; use the dominosPsySpec.xml as the value for the 'spec' parameter.

READ MORE: running Psyclone

2. Spawning Internal Modules
If the psySpec specifies internal modules, Psyclone also spawns these. In the dominosPsySpec.xml file we have defined a few internal modules. These modules have very simple functionality — so simple in fact that they only use functionality that is built into Psyclone, that of receiving and posting messages.

By default, Psyclone always spawns (creates) the whiteboards AIRCentral, WB1 and WB2. This is because Psyclone is OpenAIR compliant. In the case of the dominosPsySpec.xml it also creates whiteboard.1 and whiteboard.2.

When dominosPsySpec starts up it creates eight modules. Three of these will continue to post messages after the program starts up. This is because they have been rigged in the psySpec to trigger each other in a loop.

READ MORE: psySpec internal module example writing a psySpec

3. Contexts
Contexts can be used to manage the activity of modules in the system. They are a convenient way to make sure that sets of modules be active during certain states and not others. Even though contexts have certain features of their own that make them special, in some ways they can be thought of as global variables. Contexts are defined hierarchically; in a typical system specific modules are used to post contexts and manage transition between. Their syntax is period-delimited.

Once Psyclone is running, go to a browser and start up psyProbe, the web-based interface to Psyclone. This will enable you to open another page, called contexts+phases page, listed at the top of the main psyProbe page.

Once you have the contexts+phases page loaded, take a look at the following modules listed there: Domino-1, Domino-2, Domino-3, Domino-40, Domino-50 and Domino-60. These modules are internal modules. Domino-1, 2 and 3 are set up to trigger each other in a cycle, as are Domino-40, 50 and 60, using the after= parameter of the trigger for each module.

Currently, as you can see in the list of contexts at the top of the page, the contexts System.Ready and Dominos.Chain-1 are active. Now, using the post context button, you can switch the Dominos context to Dominos.Chain-2 (select this context from the drop-down, and click on post context button.) When you post Dominos.Chain-1 the Dominos.Chain-2 context becomes invalid. (However, if you post a context called Dominos.Chain-1.hello the Dominos.Chain-1 will still be valid — the latter subsumes the former.)

Now you should see Domino-1, 2 and 3 go out of context, while the modules Domino-40, 50 and 60 are in context. You can further verify that this is true by going to the whiteboard pages (click on the whiteboard's name on the main psyProbe page, i.e. Whiteboard.1), and see that only System.Report messages are posted there. If you look at Whiteboard.2, however, you'll see that the domino chain 2 has started — there are now plenty of messages on Whiteboard.2.

READ MORE: using contexts

4. Phases
Every module has one or more phases. Phases work like states in a sequencer: A module with only one phase has only one state while active; a module with two or more phases cycles through the phases. Typically, a phase is changed immediately after a module has posted (this can be specified in the psySpec definition for the module by setting the post parameter phasechange="yes"). A module can also simply post an explicit phase change.

Phases can be grouped under contexts. Phases belonging to the same context get activated one after another, like a sequencer. For every phase there may be different triggers; when the module is in a particular phase, any message types listed as triggers for that phase will cause the module to get a wakeup if they appear on the Whiteboard.

The phases are listed in the psySpec (example).

When a module has two or more contexts (each with one or more phases), and the context gets switched, the first phase in that context is activated.

 

Creating external modules in Java   Back to Index  

To read about external modules in general, go here.

To write an external module in Java, you will need the JavaAIRPlug or JavaOpenAIR. (JavaOpenAIR can be downloaded from http://www.mindmakers.org/openair/download/).

External modules are designed to run outside Psyclone in separate executables. They are very similar to internal modules and they can be written in other languages, such as Java.

Any module has two distinct functionalities: message handling and message processing. Like internal modules, external modules only have to worry about the message processing (called a crank), because all the message handling (receiving, organizing, posting, etc.) is done automatically.

To create an external module in Java one normally would use an AIR Plug. This is a Java object which can be instantiated inside any Java program and the plug is used for all communication with Psyclone.

This is an example of a simple Java program and how to use the plug:

import com.cmlabs.air.*;
public class MyModule {
public static void main(String[] args) {
// while (poster.isConnected()) {
JavaAIRPlug plug = new JavaAIRPlug("MyModule", "localhost", 10000);
if (!plug.init()) {
  System.out.println("Could not connect to Psyclone");
  System.exit(0);
  }
Message triggerMessage;
Message msg;
    
while (true) {
  if ((triggerMessage = plug.waitForNewMessage(3000)) == null) {
    // ... processing ... //
    msg = new Message("", "", "My.First.Output");
    plug.addOutputMessage(msg);
    plug.sendOutputMessages();
    }
  else {
    // ... do something else for a while ... //
    }
  }
}
}

This program assumes that Psyclone is running on the local computer on the standard Psyclone port 10000.

For Psyclone to use the module, you will need to tell Psyclone about it. You do this via an XML definition like this:

<module name="MyFirstModule" type="external" >
<context name="Psyclone.System.Ready">
    <phase name="Default">
      <trigger from="AIRCentral" type="My.First.Type" />
      <post to="AIRCentral" type="My.First.Output" />
    </phase>
</context>
</module>

This specification can be put into the central psySpec configuration file and read in by Psyclone at startup. Alternatively it can be sent to Psyclone at runtime, in the conten of a message; the message type must be Psyclone.System.Create.Module. (Destroy modules by sending a message type Psyclone.System.Destroy.Module, with the name of the module as content, encapsulated in the <module> tag but without further content.)

Unless you specify a command that will automatically start up your module (using the <executable> tag) you will need to start up your external module manually after you start up Psyclone.

Note that you need to use the <executable /> tag to tell Psyclone that this is an external module. Otherwise, Psyclone will create it as an internal module. In a later tutorial you can see how to fill in the <executable> tag to automatically start up your module.

Now Psyclone knows that when any message with type My.First.Type is posted to the Whiteboard AIRCentral, your module with the name MyFirstModule should be woken up with this message.

In most cases you probably want to do a bit more in your module. The Plug object is a communication device, which you can use to get the triggering message and any other messages which may have been delivered if you asked for it in the psySpec using the <retrieve> tag (see below). If you post messages using the Plug the messages are automatically sent to the correct destinations.

When this empty message is posted, the :from and the :to fields are automatically filled in and a copy is sent to every destination Whiteboard defined in the psySpec, each with the correct type filled in. For example, if the psySpec had defined two post destinations:

<posts>
  <post to="AIRCentral" type="My.Second.Type" />
  <post to="AIRCentral" type="My.Third.Type" />
</posts>

the AirCentral whiteboard would receive two messages, one with type My.Second.Type and one with My.Third.Type.

If, however, the function had created a message like this:

msg = new Message("", "", "My.Second.Type");

only one message would be sent out, as only the first post matches the type. Likewise, had the message been created with

msg = new Message("", "", "My.Other.Type");

no message would be sent out.

If you wish to bypass the post section manually, you can always do:

msg = new Message("", "MyOtherWB", "My.Other.Type");

in which case only one message would be sent to the MyOtherWB Whiteboard.

When you in the psySpec specify which message types will trigger your module you may wish to retrieve additional messages from the same or other Whiteboards to be delivered along side the triggering message:

...
<triggers>
  <trigger from="AIRCentral" type="My.First.Type" />
</triggers>
<retrieves>
  <retrieve from="BBX" type="Input.Sens.UniM.Hear"/>
  <retrieve from="BBX" type="Input.Person.Found.True">
     <latest>10</latest>
     <lastmsec>200</lastmsec>
  </retrieve>
</retrieves>
...

In your code, you can read these additional messages, if any, by asking the Messenger object:

// Get the current number of retrieved messages
int getRetrievedMessageCount();
// Get a specific retrieved message
Message getRetrievedMessage(int pos);
// Get the full collection of the retrieved messages
ObjectCollection getAllRetrievedMessages();

Should you want to manually retrieve messages from the crank without specifying this in the psySpec, you can ask the Messenger object:

// Retrieve additional messages from a source
ObjectCollection retrieveMessages(String retrieveFrom, String retrieveSpecXML);

where retrieveSpecXML uses the same format as you would have used in the psySpec including the surrounding <retrieves>…</retrieves>.

You do not have to register cranks for external modules, but if you do it is a good way of telling your module what to do with a message. If we add a crank in the psySpec like this:

<module name="MyFirstModule" type="external">
  <context name="Psyclone.System.Ready">
    <phase name="Default">
      <triggers>
        <trigger from="AIRCentral" type="My.First.Type" />
      </triggers>
      <crank name="MyFirstModuleCrank" />
      <posts>
        <post to="WB1" type="My.First.Output" />
        <post to="WB2" type="My.Second.Output" />
      </posts>
    </phase>
  </context>
</module>

we can now slightly extend our code to ask for the name of the crank after being woken up by the trigger message:

import com.cmlabs.air.*;
public class MyModule {
public static void main(String[] args) {
// while (poster.isConnected()) {
JavaAIRPlug plug = new JavaAIRPlug("MyModule", "localhost", 10000);
if (!plug.init()) {
  System.out.println("Could not connect to Psyclone");
  System.exit(0);
  }
Message triggerMessage;
Message msg;
    
while (true) {
  if ((triggerMessage = plug.waitForNewMessage(3000)) == null) {
    if (plug.getCrankName().equals("MyFirstModuleCrank")) {
      // ... processing ... //
      msg = new Message("", "", "My.First.Output");
      plug.addOutputMessage(msg);
      }
    else if (plug.getCrankName().equals("MySecondModuleCrank")) {
      // ... processing ... //
      msg = new Message("", "", "My.Second.Output");
      plug.addOutputMessage(msg);
      }
    plug.sendOutputMessages();
  }
  else {
    // ... do something else for a while ... //
    }
  }
}
}

Note that the module will now post its output to Whiteboard WB1 if the crank is MyFirstModuleCrank, and to Whiteboard WB2 if the crank is equal to MySecondModuleCrank.

After receiving a wakeup message a module can ask:

getTriggerAlias()

which will return the alias of the trigger message, or the message type if <trigger> does not have an alias. By default, the name parameter will be the type if not specified.

When posting you can use:

postOutputMessage(Message* msg)

and

postOutputMessage(Message* msg, JString destination)

The former is shorthand for first adding to output and then sending output. The second allows you to post to a named alias <post alias="xyz"> — so if you use postOutputMessage(Message* msg, "xyz") the post entry with the xyz alias will be chosen. Alternatively, you can specify the type in a <post> the same way:

postOutputMessage (Message* msg, "my.type")

If you merely wish to post a message to all whiteboards:

msg = new Message("", "", "")
addoutputmessage(msg)
sendoutputmessages()

You can call one method which will do all this:

postOutputMessage(new Message())

You can set the priority of a message by doing

msg.setPriority(3.3)

where 3.3 is a floating-point number.

 

Creating external modules in C++   Back to Index  

To read about external modules in general, go here.

To write an external module in C++, you will need the CppAIRPlug. (If CppAIRPlug was not included with this distribution you can download the plug by going to http://www.cmlabs.com/psyclone/download/, and selecting the platform that you are developing your external module on).

External modules are designed to run outside Psyclone in separate executables. They are very similar to internal modules and they can be written in other languages, such as Java.

Any module has two distinct functionalities, namely message handling and message processing. Like internal modules, external modules only have to worry about the message processing (or crank, as we call it), because all the message handling (receiving, organizing, posting, etc.) is done automatically.

To create an external module in C++ one normally would use an AIRPlug. This is a C++ object which can be instantiated inside any C++ program and the plug is used for all communication with Psyclone.

This is an example of a simple C++ program and how to use the plug:

int main(int argc, char* argv[]) {
   CppAIRPlug plug = new CppAIRPlug("MyFirstModule", "localhost", 10000);
   if (!plug->init()) {
      printf("Could not connect to Psyclone\n\n");
      return -1;
      }
   Message* triggerMessage;
   Message* msg;
   while (true) {
      if ((triggerMessage = plug->waitForNewMessage(3000)) == NULL) {
      // ... processing ... //
      msg = new Message("", "", "");
      plug->addOutputMessage(msg);
      plug->sendOutputMessages();
      }
   else {
      // ... do something else for a while ... //
      }
   }
return 0;
}

This program assumes that Psyclone is running on the local computer on the standard Psyclone port 10000.

For Psyclone to use the module, you will need to tell Psyclone about it. You do this via an XML definition like this:

<module name="MyFirstModule" type="external" >
    <executable/>
    <description></description>
    <context name="Psyclone.System.Ready">
        <phase name="Default">
            <triggers>
                <trigger from="AIRCentral" type="My.First.Type" />
            </triggers>
            <posts>
                <post to="AIRCentral" type="My.First.Output" />
            </posts>
        </phase>
    </context>
</module>

This specification can be put into the central psySpec configuration file and read in by Psyclone at startup. Alternatively it can be sent to Psyclone at runtime, in the conten of a message; the message type must be Psyclone.System.Create.Module. (Destroy modules by sending a message type Psyclone.System.Destroy.Module, with the name of the module as content, encapsulated in the <module> tag but without further content.)

Unless you specify a command that will automatically start up your module (using the <executable> tag) you will need to start up your external module manually after you start up Psyclone.

Note that you need to use the <executable /> tag to tell Psyclone that this is an external module. Otherwise, Psyclone will create it as an internal module. In a later tutorial you can see how to fill in the <executable> tag to automatically start up your module.

Now Psyclone knows that when any message with type My.First.Type is posted to the Whiteboard AIRCentral, your module with the name MyFirstModule should be woken up with this message.

In most cases you probably want to do a bit more in your module. The Plug object is a communication device, which you can use to get the triggering message and any other messages which may have been delivered if you asked for it in the psySpec using the <retrieve> tag (see below). If you post messages using the Plug the messages are automatically sent to the correct destinations.

When this empty message is posted, the :from and the :to fields are automatically filled in and a copy is sent to every destination Whiteboard defined in the psySpec, each with the correct type filled in. For example, if the psySpec had defined two post destinations

<posts>
    <post to="AIRCentral" type="My.Second.Type" />
    <post to="AIRCentral" type="My.Third.Type" />
</posts>

the AirCentral whiteboard would receive two messages, one with type My.Second.Type and one with My.Third.Type.

If, however, the function had created a message like this

msg = new Message("", "", "My.Second.Type");

only one message would be sent out, as only the first post matches the type. Likewise, had the message been created with

msg = new Message("", "", "My.Other.Type");

no message would be sent out.

If you wish to bypass the post section manually, you can always do

msg = new Message("", "MyOtherWB", "My.Other.Type");

in which case only one message would be sent to the MyOtherWB Whiteboard.

When you specify in the psySpec which message types will trigger your module you may wish to retrieve additional messages from the same or other Whiteboards to be delivered along side the triggering message:

...
<triggers>
    <trigger from="AIRCentral" type="My.First.Type" />
</triggers>
<retrieves>
    <retrieve from="BBX" type="Input.Sens.UniM.Hear"/>
    <retrieve from="BBX" type="Input.Person.Found.True">
       <latest>10</latest>
       <lastmsec>200</lastmsec>
    </retrieve>
</retrieves>
...

In your code, you can read these additional messages, if any, by asking the Messenger object:

// Get the current number of retrieved messages
int getRetrievedMessageCount();
// Get a specific retrieved message
Message* getRetrievedMessage(int pos);
// Get the full collection of the retrieved messages
ObjectCollection* getAllRetrievedMessages();

Should you want to manually retrieve messages from the crank without specifying this in the psySpec, you can ask the Messenger object:

// Retrieve additional messages from a source
ObjectCollection* retrieveMessages(const JString& retrieveFrom, const JString& retrieveSpecXML);

where retrieveSpecXML uses the same format as you would have used in the psySpec including the surrounding <retrieves>… </retrieves>.

You do not have to register cranks for external modules, but if you do it is a good way of telling your module what to do with a message. If we add a crank in the psySpec like this:

<module name="MyFirstModule" type="external">
    <description></description>
    <context name="Psyclone.System.Ready">
        <phase name="Default">
            <triggers>
                <trigger from="AIRCentral" type="My.First.Type" />
            </triggers>
            <crank name="MyFirstModuleCrank" />
            <posts>
                <post to="WB1" type="My.First.Output" />
                <post to="WB2" type="My.Second.Output" />
            </posts>
        </phase>
    </context>
</module>

we can now slightly extend our code to ask for the name of the crank after being woken up by the trigger message:

while (true) {
    if ((triggerMessage = plug->waitForNewMessage(3000)) == NULL) {
        // ... processing ... //
        if (plug->getCrankName().equals("MyFirstModuleCrank")) {
              // ... processing ... //
              msg = new Message("", "", "My.First.Output");
              plug->addOutputMessage(msg);
            }
        else if (plug->getCrankName().equals("MySecondModuleCrank")) {
              // ... processing ... //
              msg = new Message("", "", "My.Second.Output");
              plug->addOutputMessage(msg);
             }
        plug->sendOutputMessages();
        }
    else {
        // ... do something else for a while ... //
    }
}

Note that the module will now post its output to Whiteboard WB1 if the crank is MyFirstModuleCrank, and to whiteboard WB2 if the crank is equal to MySecondModuleCrank.

 

Creating internal c++ modules   Back to Index  

Internal modules are designed to run inside Psyclone for maximum performance and efficiency. They are similar to external modules, but simpler to design, implement and maintain. (However, they do have limitations related to dynamically loaded libraries.)

Internal C++ modules are developed using the Psyclone SDK, which is provided with PsyclonePro distributions.

Any module has two distinct functionalities, namely message reception/handling and subsequent processing — also called a crank. Internal modules only have to worry about the crank, because all the message handling (receiving, organizing, posting, etc.) is done automatically.  

To create an internal module one has to define a crank which will process incoming messages and generate output messages.  

This is an example of the crank function for an internal module, which just prints Hello World to the screen:

int MyFirstModuleCrank(Messenger* messenger) {
   printf("Hello World");
   return 0;
}

All you need to do is put this into the cranks file in the Psyclone SDK (cranks.cpp and cranks.h in the default library called cm), recompile and now you have an internal module.

For Psyclone to use the module, you will need to tell Psyclone about it. You do this via an XML definition like this:

   <module name="MyFirstModule" type="internal" >
      <description></description>
     <context name="
Psyclone.System.Ready">
            <phase name="
Default">
               <triggers>
                  <trigger from="
WB1" type="My.First.Type" />
               </triggers>
               <crank name="
cm::MyFirstModuleCrank" />
               <posts>
                  <post to="
WB1" type="My.Second.Type" />
               </posts>
            </phase>
     </context>
   </module>

This specification can be put into the central psySpec configuration file and read in by Psyclone at startup. Alternatively it can be sent to Psyclone at runtime, in the conten of a message; the message type must be Psyclone.System.Create.Module. (Destroy modules by sending a message type Psyclone.System.Destroy.Module, with the name of the module as content, encapsulated in the <module> tag but without further content.)

Now Psyclone knows that when any message with type My.First.Type is posted to the Whiteboard WB1, your module with the name MyFirstModule should be woken up with this message. This will in turn automatically call the MyFirstModuleCrank function in the library cm you created above and hence print "Hello World" to the console.

In most cases you probably want to do a bit more in your module. The Messenger object which is passed to the function is a communication device, which you can use to get the triggering message and any other messages which may have been delivered if you asked for it in the psySpec using the <retrieve> tag (see below). If you post messages using the Messenger the messages are automatically sent to the correct destinations.

Here is an example of a slightly more advanced version of the crank function above:

   int MyFirstModuleCrank (Messenger* messenger) {
 
   if (messenger == NULL)
      return -1;
 
   Message* triggerMessage;
   Message* msg;
   while (messenger->shouldContinueRunning()) {
      if (messenger->waitForNewMessage(50)) {
         if ( (triggerMessage = messenger->getTriggerMessage()) != NULL) {
         //   Do something with the trigger message...
            msg = new Message("", "", "");
            messenger->addOutputMessage(msg);
            messenger->sendOutputMessages();
            }
         }
      }
   return 0;
   }

Now the module sends out one message each time it is woken up. You may notice that instead of returning, the module chooses to stay inside the function until the Messenger says that it should not continue running any longer.

When this empty message is posted, the :from and the :to fields are automatically filled in and a copy is sent to every destination Whiteboard defined in the psySpec, each with the correct type filled in. For example, if the psySpec had defined two post destinations

               <posts>
                  <post to="
WB1" type="My.Second.Type" />
                  <post to="
WB1" type="My.Third.Type" />
               </posts>

the WB1 Whiteboard would receive two messages, one with type My.Second.Type and one with My.Third.Type.

If, however, the function had created a message like this

       msg = new Message("", "", "My.Second.Type");

only one message would be sent out, as only the first post matches the type. Likewise, had the message been created with

       msg = new Message("", "", "My.Other.Type");

no message would be sent out.

If you wish to bypass the post section manually, you can always do

       msg = new Message("", "MyOtherWB", "My.Other.Type");

in which case only one message would be sent to the MyOtherWB Whiteboard. 

When you in the psySpec specify which message types will trigger your module you may wish to retrieve additional messages from the same or other Whiteboards to be delivered along side the triggering message:

   ...
   <triggers>
      <trigger from="
WB1" type="My.First.Type" />
   </triggers>
   <retrieves>
      <retrieve from="
BBX" type="Input.Sens.UniM.Hear"/>
      <retrieve from="
BBX" type="Input.Person.Found.True">
         <latest>
10</latest>
         <lastmsec>
200</lastmsec>
      </retrieve>
   </retrieves>
   <crank name="
cm::MyFirstModuleCrank" />
   ...

In your crank code, you can read these additional messages, if any, by asking the Messenger object:

       // Get the current number of retrieved messages
       int getRetrievedMessageCount();
       // Get a specific retrieved message
       Message* getRetrievedMessage(int pos);
       // Get the full collection of the retrieved messages
       ObjectCollection* getAllRetrievedMessages();

Should you want to manually retrieve messages from the crank without specifying this in the psySpec, you can ask the Messenger object:

   // Retrieve additional messages from a source
   ObjectCollection* retrieveMessages(const JString& retrieveFrom,
    const JString& retrieveSpecXML);

where retrieveSpecXML uses the same format as you would have used in the psySpec. 

Note: 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.

 

Creating Your Own CoreLibrary Objects   Back to Index  

All CoreLibrary objects share a vast set of basic functionalities, and if you wish to create your own objects it would be to your advantage to create them as part of the CoreLibrary using the Third Party Extension mechanism. The most important benefit from this is that all CoreLibrary objects are automatically converted from and to XML when sent across the network. An example could be:

RELATED: tutorial on SourceForge CoreLibrary on SourceForge

 

 

 
2006©Communicative Machines Inc. License