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

 

 

Introduction
Back to Index


About Tutorials Back to Index

You are looking at the Psyclone™ Tutorial. To use this tutorial you must be familiar with certain concepts and have experience with certain things, as listed in the section Prerequisits.

This set of tutorials 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.

 

Prerequisits Back to Index

To work through the tutorials successfully, you should be familiar with the Java and/or C++ programming languages and XML.

It is also necessary to be familiar with the command line interface. Here are tutorials on various command lines: Linux Mac OS X Windows

If you want to develop modules in Java or C++, you must also make sure you have the AIRPlugs (for external modules) or the Psyclone SDK (for internal modules). To develop in Java, you will also need the Java SDK; the Sun Java SDK will suffice (http://java.sun.com/j2se/).

 

 

 

 


 
Ordered Lessons Back to Index

 

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.

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.

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.

Furthermore, 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 "/".

  Example command (on Linux, with Psyclone running on port 20001): 
  >java -classpath ./:JavaOpenAIR.jar JavaReader psyclone=localhost:10000

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:

 Example:
  >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++ examples   Back to Index  

The C++ examples are included in the CppAIRPlug download. The C++ examples are Poster and Reader. 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.

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 start up Psyclone without a spec specified at the command line). In the directory where the C++ examples are, give the commands:

  Reader psyclone=[psyclone host][:any port number]

Note that the : has to be there if you give a port, and that 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:

  Poster 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 Poster posts a message it goes to WB1 which then sends it to the registered Reader module. If you look at WB1 in psyProbe you can see the message entered every time foor both events. Compare the timestamps to see how long it took for the message to go across from one module to the other. 

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

To include crank functions as part of an external library when running Psyclone you will need to create a psySpec and define a 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">
  <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>

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" />

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.

 

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 in the central psySpec configuration file by defining a module entry like this:

<module name="MyFirstModule">
<executable/>
<description></description>
<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>

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">
  <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 in the central psySpec configuration file by defining a module entry like this:

<module name="MyFirstModule">
    <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>

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">
    <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.

To run the module, you will need to tell Psyclone about it. You do this in the central psySpec configuration file by defining a module entry like this:

   <module name="MyFirstModule">
      <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>

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

 

Writing a psySpec Back to Index

A simple psySpec contains initialization values for Psyclone at startup; an advanced psySpec contains the complete specification for a whole system architecture, and enables its automatic management. It can be used to specify one or more Whiteboards, the external and internal modules, as well as global parameters of the system. The dominosPsySpec.xml is a tutorial example psySpec for demonstrating the use of contexts in Psyclone.

The psySpec is read in by doing:

   psyclone port=[port] spec="[psySpecName]"

 Example:
  >./psyclone port=20001 spec="dominosPsySpec.xml"

If no psySpec is given, Psyclone will look for a file called psySpec.xml. If none is found, it will automatically create two Whiteboards by default, named WB1 and WB2.

The psySpec contains the intialization values for Psyclone at startup. It can be used to specify one or more Whiteboards, as well as the modules of the system.

Here is a simple example of a psySpec, specifying some global variables, a Whiteboard called WB1 and one module called Startup:

   <psySpec name="psySpec Example" version="1.2">  

  <whiteboard name="WB1">
     <description>
This is a basic Whiteboard</description>
  </whiteboard>

  <module name="Startup" type="internal">
        <description>Bootstrap by posting a root context upon system ready</description>
        <context name="System">
                  <phase name="Look for System Created">
                      <triggers from="any">
                             <trigger type="System.Ready" after="100" />
                      </triggers>
                      <posts>
                           <post to="WB1" type="Psyclone.Context:SoB.Alive" />
                      </posts>
                 </phase>
        </context>
   </module>

   </psySpec>

(The tags context and phase may be omitted.) This psySpec creates one Whiteboard called WB1, and one internal module called Startup. When the Psyclone Factory posts some initial System messages to all Whiteboards, Startup goes into context. Then, when the Factory posts message System.Ready, Startup gets triggered and posts the message of type Psyclone.Context:SoB.Alive. This posting is a signal to the context system in Psyclone to make the context SoB.Alive active. (If you are wondering where the crank is for this module: When you don't specify a crank Psyclone will use its default crank that simply posts upon triggering.)

To specify a module that runs external to Psyclone:

<module ... type="external">

(If type="external" is omitted and an <executable> is defined, it is assumed to be an external module.)

The <command> tag inside <executable> can hold a command to be executed automatically by Psyclone:

<executable name="myexe">
   <command>command-line command to be executed</command>
</executable>

For example:

<executable name="myexe">
   <command osmem="256">./big_java</command>
   <command osmem="64">./small_java</command>
</executable>

Further details on the use of the psySpec are listed in various sections; see the following sections.


List of psySpec tags

<version> Psyclone tracks the versions of psySpec syntax and provides startup information on backward and forward compatibility of a psySpec.

<global> This is a general section that contains global parameters for psyclone.

<psyprobe> PsyProbe is the web-based interface to Psyclone. It defaults to port 10000, so technically this example is redundant. Here you can change it.

<whiteboard> Here we have specified a whiteboard with a simple description. Whiteboards must have unique names.

<module> We have specified one module called Startup. Modules must have unique names.

<context> This module will be "in context", as it's called — i.e. listening for its triggers — during one context, namely, the System context. Since contexts are posted automatically to all Whiteboards (the message type Psyclone.Context is global), there is no need to specify which Whiteboard the context lives on.

<phase> This module only has one phase, named Look for system created. Having one phase means that the module only will always have the same triggers during the System context.

<trigger> Here we have given the module one trigger, namely, System.Ready. When the message type System.Ready gets posted, the module will be woken up with an AIR.Instruct.Wakeup message. This message will contain the message which caused the triggering, as well as the phaseSpec (the XML within the <phase> tags) for the module. Here, the parameter after states that the wakeup message should be sent 100 milliseconds after the trigger message System.Ready gets posted.

<post> This module is designed to post a context. To do this it uses the message type Psyclone.Context. The module, when woken up, will automatically post a context message with the value SoB.Alive — the actual context that gets posted. Any module that has listed SoB.Alive as their context will then be in context, ready to be woken up by any of its trigger messages, should they get posted.

RELATED: contexts running the dominoPsySpec phases wakeup

 

Creating and Using Whiteboards   Back to Index  

To specify a whiteboard in the psySpec, this is the basic syntax:

  <whiteboard name="WB1">
     <description>
This is a basic Whiteboard</description>
  </whiteboard>

This creates a whiteboard called WB1. (All whiteboards (and modules) in Psyclone must have unique names.) To limit the number of messages stored, use the max= parameter:

  <whiteboard name="WB1" maxcount="10000">

This will limit the number of messages to 10000; when message 10001 is recieved the oldest message will be permanently destroyed by the whiteboard.

Media streams can bespecified for whiteboards in the psySpec like this:

  <whiteboard name="MyWB" maxcount="15000">
      <description>My Whiteboard</description>
      <streams>
         <stream name="Input.Stream.Video.Raw" maxsize="10" />
         <stream name="Input.Stream.Video.Proc1" maxsize="10" />
         <stream name="Input.Stream.Video.Proc2" maxsize="10" />
         <stream name="Input.Stream.Video.Proc3" maxsize="10" />
      </streams>
  </whiteboard>

The MyWB whiteboard will now contain four circular media streams, each which has a max buffer size of 10 megabytes. After this limit is reached the oldest data samples are deleted. (Notice that the names of the streams have no semantic meaning to the system.)

Whiteboards can be created and destroyed dynamically by sending a message to 'Psyclone' with type

   Psyclone.System.Create.Whiteboard

Include the spec for the whiteboard in the content of the message. To destroy a whiteboard use:

   Psyclone.System.Destroy.Whiteboard

with the name of the whiteboard to be destroyed in the content of the message.

Streams can be added to a whiteboard by sending the message

   Psyclone.System.Create.Stream

to 'Psyclone' with the whiteboard spec:

  <whiteboard name="MyWB">
      <stream name="Input.Stream.Video.Proc3" maxsize="10" />
  </whiteboard>

where MyWB is a whiteboard already in existence. The maxsize= parameter sets the size of the circular stream buffer to 10 megabytes.

Streams can also be created outside whiteboards by simply putting the following in the content:

   <stream name="Input.Stream.Video.Raw" maxsize="10" />

The following will create a standalone stream in a satellite called Sat1:

   <stream name="Input.Stream.Video.Raw" maxsize="10" satellite="Sat1" /> 

 

Using Timing With High-Volume Posting

When Modules post messages faster than the receiving Whiteboards can process them, the posting may be slowed down by the Whiteboard itself. The simplest way for a Module to detect that a Whiteboard is getting too busy is to put a simple timer around the posting itself, to measure if the posting at some point starts taking longer than usual.

In C++ you can do this very easily using the JTime object:

JTime start;
// post message to Whiteboard
int dif = start.getMicroAge(); // returns microsecond time

In Java you can do this with millisecond accuracy using

long start = System.currentTimeMillis();
// post message to Whiteboard
int dif = start - System.currentTimeMillis(); // returns microsecond time

You can then notice if the posting starts taking much longer than usual and take the appropriate action.

 

RELATED: tutorial on SourceForge CoreLibrary on SourceForge

 

Using Retrieves to Retrieve Past Information Back to Index

In addition to being triggered by information that a module is subscribed for, a module can also ask to be given old information. Your module can retrieve dynamically (at runtime). The runtime method requires the module itself to decide when to do the retrieval. Alternatively, your module definition in the psySpec can include a retrieve spec (see below).

The XML for the retreiving looks like this:

  <trigger from="WB1" type="Input.Sens.MultiM.Vision.Human.Found.True"/>
  <retrieve from="WBX" type="Input.Sens.UniM.Hear.Human.Voice"/>
  <retrieve from="WBX" type="Input.Sens.MultiM.Vision.Human">
    <latest>3</latest>
    <lastmsec>8000</lastmsec>
  </retrieve>

The first retrieve asks to be sent a message of type Input.Sens.UniM.Hear.Human.Voice along with the message type Input.Sens.MultiM.Vision.Human.Found.True to which it has subscribed; the second retrieve asks to be sent a message of type Input.Sens.MultiM.Vision.Human along with that, but with the conditions that if there are more than 3 such messages in the last 8 seconds it will only return the 3 most recent of those. All conditions that are listed like that are ANDed together.

You can also group them using a retrieves tag:

<retrieves>
  <retrieve from="WB3" type="xxx"/>
  <retrieve from="WB2" type="yyy"/>
</retrieves>

You can retrieve based on several features:

  • message ID
    • Example: <retrieve id="[GUID]"/> where GUID is the unique id of a message
  • message sender
    • Ex: <retrieve sender="HumanTracker1"/>
  • message type
    • Ex: <retrieve from="WBX" type="Output.Plan"/>
  • content
    • Ex: <retrieve content="*green tomato*"/> will return any message where the phrase "green tomato" appears anywhere in the content of the message. The stars mean that anything can come before and after the two words.
  • contentmatch
    • Ex: <retrieve contentmatch="xml" content="*green tomato*"/> will work like the last example, but in addition the contentmatch="xml" will force matching to also be done on the XML itself, i.e. tag names, parameter names, etc. Notice: You cannot use the reserved XML symbols like < > / & " etc.
  • Time: aftertime, untiltime, latest, lastmsec

The last constraints, aftertime, untiltime, latest and lastmsec, are done with tags, as the example above has shown for latest and lastmsec. The others are used precisely in the same way.
aftertime means anything that has happened after the particular timestamp used;
untiltime will mean anything that happened before that timestamp.

The <retrieve> spec also allows these three additional constraints which specify retrieval relative to the posting of contexts:

<retrieve>
   <before>SoB.Alive.SoS.Awake</before>
   <during>SoB.Alive.SoS.Awake</during>
   <after>SoB.Alive.SoS.Awake</after>
<retrieve>

before means any time before this context last became active.
during means any time between this context last became active and subsequently
became inactive.
after means any time after this context last became inactive.


Specifying Retrieves in the psySpec

When using retrieves in the psySpec the above example would look like this:

<module name="HumanTracker1">
  <trigger from="WB1" type="Input.Sens.MultiM.Vision.Human.Found.True"/>
  <retrieve from="WBX" type="Input.Sens.UniM.Hear.Human.Voice"/>
  <retrieve from="WBX" type="Input.Sens.MultiM.Vision.Human">
    <latest>3</latest>
    <lastmsec>8000</lastmsec>
  </retrieve>
  . . .
</module>


Using Retrieves in Code

A module can at any time decide to retrieve old information. To do this from inside the module you can use any one of these:

ObjectCollection retrieveMessages(String xml)
ObjectCollection retrieveMessages(RetrieveSpec spec)
ObjectCollection retrieveMessages(ObjectCollection specs)

where the xml variable contains some retrieve XML specification as shown above.

The two latter methods are alternatives to raw XML. The spec variable is XML code contained in a RetrieveSpec object; the specs variable is a collection of RetrieveSpec objects contained in an ObjectCollection object.

If your module definition in the psySpec includes a retrieve spec, any messages retrieved according to that spec will be included in every wakeup message each time your module gets triggered.

When your module receives the wakeup message, to get at the retrieved messages in your code, use:

// 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();

RELATED: psySpec

 

Running modules on multiple computers   Back to Index  

You can distribute your Psyclone-centered system on as many computers as you wish. Each module will use an AIRPlug to connect to Psyclone — the connections and their maintenance is seamless, even through firewalls. In such systems it is often necessary to connect some modules to many modules, in a graph-like structure (that may change at runtime). To do so, use Psyclone's publish-subscribe mechanisms.

You can manage a distributed system manually. To manually start an external module that runs on another computer than Psyclone you just have to provide the AIRPlug with the network name or address and the port on which the main Psyclone program is running. For example, to start up your Java module on Computer2 when Psyclone is running on Computer1 on port 10000 you would do

         java MyJavaModule psyclone=Computer1:10000 name=MyModule

If you, however, want to either

  • •automatically start your external module on another computer
  • start your internal module on another computer (automatically), or
  • run a whiteboard on another computer

you can use a Psyclone Satellite. A Satellite is what we call modules and whiteboards that were started remotely, i.e. on a different computer from where Psyclone is running.

To use a Satellite you run Psyclone as a Satellite Server on the other computer or computers. Put Psyclone on the remote computer and run it with the command

       ./psyclone -satelliteserver

on the command line of that machine. If no port number is given it defaults to 10000. The Satellite Server will then start and continue to run on this computer forever, until you kill it manually or the machine is shut down. The Server will use minimal CPU cycles if it is not running any Satellites.

As long as you have defined the modules and whiteboards in the psySpec (see below), you can start and stop Psyclone as many times as you like the Satellite Server will make sure that a Satellite runs the whiteboards and modules specified to start up remotely. You can also use the same Satellite Server for more than one Satellite at the same time.

To use a Satellite you will need to define it in the psySpec global section:

<global>
   <satellites>
      <satellite name="
MySatellite" host="SatelliteOne" port="10000" />
   </satellites>

</global>

Now you can tell any whiteboard, internal and automatically started external modules, that it should in fact start up in the remote Satellite called SatelliteOne instead of starting locally. You do this using the remote parameter for the whiteboard or module in question:

   <whiteboard name="MyWB" remote="MySatellite">
      ...
   </whiteboard>
 

and  

   <module name="MyModule" remote="MySatellite">
      ...
   </module>

The new whiteboard MyWB will be created and run inside a new satellite created by the Satellite Server. A new module called MyModule will also be created - if it is internal it will run inside the satellite, if it is external it will be autostarted by the Satellite Server. So MyWB and MyModule will now be running on the remote computer SatelliteOne, and will continue to do so until this Psyclone shuts down, at which time the satellite will be terminated by the Satellite Server, including everyting in it. The Server will also shut down MyModule if it is external. The Satellite Server will then continue to run, waiting for the next Psyclone which needs its services.

Internal modules and whiteboards which run in the same satellite do communicate as quickly with each other as internal modules and whiteboards that run inside the main Psyclone. So remember when you design your system that modules which make heavy use of the same data should all use the same whiteboard or whiteboards, which again should be located either in the same satellite or the main Psyclone.

As previously mentioned, more than one Psyclone can use the same Satellite Server at the same time. The Server will keep track of which whiteboards and which modules belong to which Psyclone without conflicts by creating a separate satellite per Psyclone.  

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 Satellite Server in the directory where you run the Server command (just like for Psyclone).

 

 

Running modules on multiple operating systems   Back to Index  

You can distribute your system to run on multiple computers and multiple operating systems by by moving one or more of your modules over to an operating system supported by the supplied AIRPlugs: C++ is supported on Linux, Windows and Mac OS X; Java is supported on all Java-compatible platforms. To manage a distributed system refer to Automatically Starting up Modules and Running modules on multiple computers.

 

Creating modules that use streaming media   Back to Index  

A module can subscribe to a named media stream in a whiteboard. Media streams are specified for whiteboards in the psySpec like this:

  <whiteboard name="MyWB" maxcount="15000">
      <description>My Whiteboard</description>
      <streams>
         <stream name="Input.Stream.Video.Raw" maxsize="10" />
         <stream name="Input.Stream.Video.Proc1" maxsize="10" />
         <stream name="Input.Stream.Video.Proc2" maxsize="10" />
         <stream name="Input.Stream.Video.Proc3" maxsize="10" />
      </streams>
  </whiteboard>

The MyWB whiteboard will now contain four circular media streams, each which has a max buffer size of 10 megabytes. After this limit is reached the oldest data samples are deleted. (Notice that the names of the streams have no semantic meaning to the system.)

From a module you can access one or more media streams by asking for them inside a phase:

   <module name="MyFirstModule" type="internal" >
     <trigger from="
AIRCentral" type="My.First.Type" />
     <inputstream alias="
MyInput" source="Input.Stream.Video.Raw" />
     <outputstream alias="
MyOutput" source="Input.Stream.Video.Proc2" />
     <crank name="
cm::MyFirstModuleCrank"/>
     <post to="
AIRCentral" type="My.Second.Type" />
   </module>

This module has access to two streams and will use the aliases MyInput and MyOutput to work with them (the alias is the name used internally in the code for the particular source stream — its purpose is to decouple the media type from the internal references to that stream, to make it possible to change the type name used without recompiling the code). This is done since the psySpec may later be used to change the flow of data without having to recompile the code. (The example message types used here are for demonstration purposes only; please refer to the section on message types to learn about the proper use of dot-delimited message types.)

In an internal module you access the media stream using a MediaConnection via the messenger:

   MediaConnection* myInput = messenger->getStreamConnection("MyInput");
   if (stream == NULL)
      return -1;
   MediaConnection* myOutput = messenger->getStreamConnection("MyOutput");
   if (stream == NULL)
      return -1;

A module can access as many input and output streams as needed.

 

External Modules

The code for external C++ and Java modules is almost identical, except that you will have to wait for the plug to have the stream become available (streams are automatically read from the module's own registration (either in the psySpec or manually registration in the code), so it can take a little while before the streams are ready for use):

C++:

   MediaConnection* myInput = plug->waitForStreamConnection("MyInput", 5000);
   if (stream == NULL)
      return -1;
   MediaConnection* myOutput = plug->waitForStreamConnection("MyOutput", 5000);
   if (stream == NULL)
      return -1;  

In an external Java module you access the media stream using a MediaConnection via the plug.

Java:

   MediaConnection myInput = plug.waitForStreamConnection("MyInput", 5000);
   if (stream == null)
      return -1;
   MediaConnection myOutput = plug.waitForStreamConnection("MyOutput", 5000 );
   if (stream == null)
      return -1;

 

Using the MediaConnection object you can add new data samples to a stream, wait for new data to be added by another module, and search and retrieve data samples by timestamp.

The following example shows how a module would wait for new data, and when the data arrives process it and post the resulting output (internal and external Java and C++ modules):

DataSample* inputSample = NULL;   
DataSample* outputSample = NULL;   
JTime lasttime;   
if ((inputSample = myInput->waitForFirstSampleAfter(now, )) != NULL ) {
      lasttime = inputSample->timestamp;      
      // process the sample and create an output sample
      outputSample = new DataSample();
      if (!myOutput->addDataSample(outputSample)) {
      // error
      }
}

(For Java one merely has to replace -> with . and NULL with null, and remove the * chars)

A DataSample object is merely a placeholder for either a chunk of raw binary data, a CoreLibrary Object or one of the special objects derived from DataSample such as Bitmap and BitmapUpdate.

For more information and documentation on how to use these objects, see the online CoreLibrary documentation on Source Forge or jump directly to the CoreLibrary base Object to see which objects are available or to the DataSample object. Here you can also see how you work with MediaConnections.

 

Continuous Mode

The above will work fine if you are receiving 1-5 samples per second, but beyond that your code may become slow, because each time you wait for a new sample you send a small message to the server. So, for high volume data you should switch to Continuous Receiving. This is somewhat different for C++ and Java, but identical for internal and external C++ modules.

 

C++ Modules

C++ is capable of using an advanced local memory buffer, which means that to use continuous mode one merely has to add one line:

if (!myInput->startContinuousBackgroundReceive()) return false;

DataSample* inputSample = NULL;
DataSample* outputSample = NULL;
JTime lasttime;
if ( (inputSample = myInput->waitForFirstSampleAfter(now, 5000)) != NULL ) {
  lasttime = inputSample->timestamp;
  // process the sample and create an output sample
  outputSample = new DataSample();
  if (!myOutput->addDataSample(outputSample)) {
    // error
  }
}

The code for continuous mode is identical in C++ for internal and external modules.

In this example, after the stream is put into continuous mode, the wait command only waits for the local buffer to receive new data. However, if you stop getting the new data, eventually the buffer will run full and the oldest data deleted.

And you can stop the continuous mode using

stream->stopContinuousBackgroundReceive()

 

Java Modules

Java doesn't have the same capabilities for handling extreme data transfers, but a small local buffer is implemented to handle continuous streaming:

if (!stream1.startContinuousBackgroundReceive()) {
  System.out.println( "Error: Couldn't start stream1 continuous...");
  return false;
}
System.out.println( "Listening for new samples on the stream...");
DataSample sample;
while (shouldContinue) {
  if ( (sample = stream1.receiveContinuousSample(100)) != null) {
    Time now = new Time();
    System.out.println(now.printTime() + ": JavaStreamer: received sample
    from stream1");
    if (!stream2.addDataSample(sample)) {
      System.out.println( "Error: Couldn't output to stream2...");
     }
    else {
      System.out.println(now.printTime() + ": JavaStreamer: output
      sample to stream2");
    }
  }
}

And you can stop the continuous mode using

stream1.stopContinuousBackgroundReceive();

It is very important that you process the data as fast as they come in and that you stop the continuous mode when you are done, as the local buffer will start erasing the oldest samples if it gets too full.

For more information and documentation on how to use these objects, see the online CoreLibrary documentation on Source Forge or jump directly to the CoreLibrary base Object to see which objects are available or to the DataSample object. Here you can also see how you work with MediaConnections.

 

Automatically starting modules   Back to Index  

Internal modules running on the same computer as Psyclone are automatically started when Psyclone starts. You can also start external C++ and Java modules automatically on the same computer by using the psySpec to specify how to run them (to automatically start external and internal modules on remote computers you will need to install a Satellite Server see Running modules on multiple computers). To see how you automatically start up modules on other computers than the one on which Psyclone is running, see Creating a Distributed System.

If you are simply running your Psyclone system on one operating system and do not plan to run on other operating systems in the near future, you can specify how to run a module automatically like this:

     <module name="MyExternalModule" type="external">
      <executable name="
MyExternalProgram" consoleoutput="yes">
        
java MyJavaModuleXYZ psyclone=%host%:%port% name=%name%
      </executable>
      <spec>
         <context name="
Psyclone.System.Ready">
            <phase name="
1">
               <triggers from="
WB1">
                  <trigger type="
test.type"/>
               </triggers>
            </phase>
         </context>
      </spec>
   </module>

(The <spec> tag is optional). Notice that the percentages and tags inside them should be copied verbatim. Whenever Psyclone starts, it will run the command line specified as:

           java MyJavaModuleXYZ psyclone=%host%:%port% name=%name%

and it will allow everything which this program prints to be seen on the Psyclone console. The %host% part will always be replaced by the current Psyclone computer's network address, the %port% by the port number Psyclone is running on, and the %name% by the name of your module. If you used an AIR Plug to create your external module these will be read by your module on startup.

You should test your command line before running Psyclone by going to the directory where you will run Psyclone from and typing your line exactly as you put it in the psySpec. If your module starts, everything should work. If not, you will need to figure out why not and fix it, as Psyclone will not be able to run it either.  

If you wish to start up Psyclone first and then start your module manually you can leave out the command line information in the tag:

     <module name="MyExternalModule" type="external" >
      <executable name="
MyExternalProgram" consoleoutput="yes"> </executable>
      ...

If you plan on running Psyclone with the same psySpec on different operating systems, you may need to take into account the slight differences in how to run programs from the command line. One example is when running Java programs with multiple class paths. On Windows you separate these with semicolons and on Unix you use colons. Hence, you would specify a command line for each operating system like this:

   <executable name="MyExternalProgram" consoleoutput="yes">
      <command ostype="
Win32">java -cp a.jar;b.jar MyModule ...</command>
      <command ostype="
Linux">java -cp a.jar:b.jar MyModule ...</command>
      <command ostype="
OSX">java -cp a.jar:b.jar MyModule ...</command>
   </executable>
 

You can even use different modules on different computers by name:

   <executable name="MyExternalProgram" consoleoutput="yes">
      <command hostname="
Loki">java -cp a.jar:b.jar MyModule1 ...</command>
      <command hostname="
Odin">java -cp a.jar:b.jar MyModule2 ...</command>
   </executable>
 

or mix and match:  

   <executable name="MyExternalProgram" consoleoutput="yes">
      <command ostype="
Win32">java -cp a.jar;b.jar MyModule ...</command>
      <command ostype="
Linux">java -cp a.jar:b.jar MyModule ...</command>
      <command ostype="
OSX" hostname="Loki">java -cp a.jar:b.jar MyModule1 ...</command>
      <command ostype="
OSX" hostname="Odin">java -cp a.jar:b.jar MyModule2 ...</command>
   </executable>

This can be very useful when a team is developing a Psyclone system. One person can then have a stable version of a module, called e.g. Module1, and an experimental version of the same module, called Module2, which is only ever run on a computer called Odin.

You can match the following system parameters (and use wildcards like *):

   ostype="Win32/Linux/OSX"
   osname="Windows XP/Linux/Darwin"
   arch="i386/i486/i586/i686/athlon/powerpc/mips/alpha"
   hostname="xxx"    (computers network name)
   osmem="nnn"       (minimum amount of memory in computer in MB)
   cpuspeed="nnn"    (minimum CPU speed of computer in MHz)
   cpucount="n"      (minimum number of CPUs in computer)  

If either field is empty it is considered a match

     Example: <command ostype="OSX"> allows system with the Mac OS X operating system

All string fields are matched with wildcards

     Example: <command hostname="*fun*"> allows system with hostname="somefuncomputer.local.">  

All numerical values are matched with >=

     Example: <command osmem="256"> allows systems with 512MB, but not 128MB

So you could, for example, say

       <executable name="myexe">
            <command osmem="
256">./big_java</command>
            <command osmem="
64">./small_java</command>
      </executable>

or  

      <executable name="myexe">
            <command cpucount="
2">./heavy_java</command>
            <command cpucount="
1">./light_java</command>
      </executable>
   

 

Using contexts Back to Index

Contexts are used to manage the complexity of a large number of modules that typically are not all relevant for the operation of a system at the same time. Psyclone modules are context-driven: Each module has a specific context in which it can run. If that context is not active, the module will not be woken up with any messages it has subscribed to.

You can get some hands-on experience with contexts by looking at the dominosPsySpec example.

Contexts are hierarchically defined, forming a tree. For example, this is a context tree:

  SoB.[Alive, Dead]
  SoB.Alive.[Awake, Asleep]

In this context tree, the root is called SoB (short for state of being). The root has two branches, Alive and Dead. The branch Alive has two leafs, Awake and Asleep. (Dead has no branches.) To see how this tree is used at run-time, let's look at a hypothetical system that uses this context tree. In this hypothetical system we would have modules specifically designed to post contexts, which ever one is relevant at any time. When the system starts up the context SoB is posted by some bootstrapping module. When this happens another module (called e.g. BeingAlive.100) posts SoB.Alive if the system is considered "alive", otherwise it posts SoB.Dead (the module would presumably run some tests to see if the system actually qualifies for the "alive" label). If SoB.Alive is posted, then another module (called e.g. BeingAwake.200) posts SoB.Alive.Awake if it considers the system to be "awake" (whatever that means in this hypothetical system).

Now we can look at how modules use these contexts to specify when they should be active and when not. For example, we might have a module called LookAround.150, whose job it is to move the sensors of a hypothetical being in the direction of a sound when it receives one. This module should not be running if the being is dead or if the being is asleep. So when we choose a context for this module, we choose the context in which it should be active, which is in this case SoB.Alive.Awake.

In the psySpec this is indicated by the syntax:

  <context name="SoB.Alive.Awake">

Now, if at this point any module in the system posts the context SoB.Dead, the context SoB.Alive.Awake will not be active any more, since the branch Dead replaces the branch Alive in the context tree, and the active context will instead be SoB.Dead. At this point the module LookAround.150 will not be triggered by any message types it has subscribed to, because it needs the context SoB.Alive.Awake to be active.

Multiple roots are allowed. If you post for example the context SoB.Alive.Asleep and then post the context System.Ok, the two context trees have different roots, and will thus co-exist with no interference.

Notice that context trees are designed by you, the user. The only restrictions on them is that they be dot-delimited to designate branches.

Contexts are posted (i.e. announced to the system) by using a special message type called Psyclone.Context. In the posting section of a module in the psySpec (or, alternatively, in the XML as constructed by an external module), the type of the message should be Psyclone.Context:[context tree], where [context tree] is the dot-delimited context tree.

Example:
  Psyclone.Context:SoB.Alive.Awake

When a context is posted, the context gets "switched" only if a current branch in the tree gets replaced by a new branch. For example, if the current context is SoB.Alive.Asleep and SoB.Alive.Awake gets posted, the context will be switched. And if SoB.Alive.Asleep.REMsleep gets posted, this context will be switched on for any module which had listed this as their context. But if SoB.Alive gets posted while SoB.Alive.Asleep is active, nothing happens.

 

RELATED: rocket example running the domionsPsySpec.xml


 

Contexts are used to make a module behave differently in different situations. For example, a vision module would want to use a different algorithm if the scene is bright from if the scene is dark. Instead of managing this manually inside the code of the module, contexts allow a module to change from one type of operation to another by listening to an external signal.

The way contexts are typically used is that in each Context a module will have a separate set of inputs and outputs. By looking at currently active context and a module's spec, anyone, including the system desginer and other runtime modules, can become aware of what the module is doing.

In the module spec each context will have at least one Phase, even if not directly specified. A context can have more than one Phase, in which case the module will cycle through the phases inside this context, until the relevant system context is changed. When this happens, the module would either change to a new set of Phases inside the new context, or go out of context and become idle, if the module has no configuration for the new context.

The system as a whole will usually have a hierarchy of contexts and many of these may be active at the same time. However, a module will only have one active Phase in one of these contexts, and it should not register for more than one simultaneously active contexts.

In a module registration without context and without phases looks like this:

   <module name="MyFirstModule">
      <trigger from="
AIRCentral" type="My.First.Type" />
      <crank name="
cm::MyFirstModuleCrank">
      <post to="
AIRCentral" type="My.Second.Type" />
   </module>

This module only has one phase which belongs to the default context, Psyclone.System.Ready. This context will never be switched off until the system is shutting down. If this module tries to switch phase, it will end up in the same state as before.

If you define more than one context for a module, the module will be in one and only one (or none) of these contexts at any given time.

    <module name="Domino-60">
            <context name="Scene.Bright">
                <phase name="Send first message group">
                    <triggers from="Whiteboard.2">
                        <trigger after="1000" type="DEMO.Domino.50"/>
                    </triggers>
                    <posts to="Whiteboard.2">
                        <post type="DEMO.Domino.60" />
                        <post type="DEMO.Domino.Flip" phasechange="yes" />
                    </posts>
                </phase>
            </context>
            <context name="Scene.Dark">
                <phase id="Send second message group">
                    <triggers from="Whiteboard.2">
                        <trigger after="1000" type="DEMO.Domino.50"/>
                    </triggers>
                    <posts to="Whiteboard.2">
                        <post type="DEMO.Domino.60" phasechange="yes" />
                        <post type="DEMO.Domino.Flop" />
                    </posts>
                </phase>
            </context>
    </module>

If you specify more than one phase in each context, the module will behave as explained in the phase tutorial.

Internal modules will automatically be using the correct crank in the correct phase in the currently active context. If they go out of context they will stop running and wait to be in context again.

External modules will have to detect whether they are in context at all, and if so, which one. The main loop should be checking this, as shown here:

C++

while (true) {
  if (plug->inContext()) {
  if ((triggerMessage = plug->waitForNewMessage(3000)) == NULL) {
    // read the crank name
    crankName = plug->getCurrentCrankName();
    // read context name if you like
    context = plug->getCurrentContextName();
    // read phase name if you like
    phase = plug->getCurrentPhaseName();
    // ... process trigger ... //
    }
  }
  else {
    plug->wait(50);
  }
}

Java

while (true) {
  if (plug.inContext()) {
  if ((triggerMessage = plug.waitForNewMessage(3000)) == NULL) {
    // read the crank name
    crankName = plug.getCurrentCrankName();
    // read context name if you like
    context = plug.getCurrentContextName();
    // read phase name if you like
    phase = plug.getCurrentPhaseName();
    // ... process trigger ... //
    }
  }
  else {
    Utils.pause(50);
  }
}

 

RELATED: psySpec phases wakeup running the domionsPsySpec.xml

 

Using phases Back to Index

A module may go through two or more different states in its normal operation. Instead of managing this manually inside the code of the module, one can use phases to allow a module to change from one type of operation to the next. Each phase will have its own set of triggers, retrieves, crank and posts.

A module registration without context and without phases looks like this:

   <module name="MyFirstModule">
      <trigger from="AIRCentral" type="My.First.Type" />
      <crank name="cm::MyFirstModuleCrank">
      <post to="AIRCentral" type="My.Second.Type" />
   </module>

This module only has one (hidden) phase: If it tries to switch phase, it will end up in the same phase (state) as before.

If you specify more than one phase:

    <module name="Domino-60">
                <phase name="Send first message group">
                    <triggers from="Whiteboard.2">
                        <trigger after="1000" type="DEMO.Domino.50"/>
                    </triggers>
                    <posts to="Whiteboard.2">
                        <post type="DEMO.Domino.60" />
                        <post type="DEMO.Domino.Flip" phasechange="yes" />
                    </posts>
                </phase>
                <phase name="Send second message group">
                    <triggers from="Whiteboard.2">
                        <trigger after="1000" type="DEMO.Domino.50"/>
                    </triggers>
                    <posts to="Whiteboard.2">
                        <post type="DEMO.Domino.60" phasechange="yes" />
                        <post type="DEMO.Domino.Flop" />
                    </posts>
                </phase>

    </module>

The module will automatically switch to the next phase whenever it posts a message with the phasechange parameter set to yes.

In the above example you can see that the module will alternate between posting the message DEMO.Domino.Flip and DEMO.Domino.Flop.

 

Internal Modules

For internal modules you simply put a different crank into each phase and Psyclone will automatically switch to the new crank code when the module switches phase.

    <module name="Domino-60">

                <phase name="Send first message group">
                    <crank name="MyCrank" />
                    <triggers from="Whiteboard.2">
                        <trigger after="1000" type="DEMO.Domino.50"/>
                    </triggers>
                    <posts to="Whiteboard.2">
                        <post type="DEMO.Domino.60" />
                        <post type="DEMO.Domino.Flip" phasechange="yes" />
                    </posts>
                </phase>
                <phase name="Send second message group">
                    <crank name="MyCrank2" />
                    <triggers from="Whiteboard.2">
                        <trigger after="1000" type="DEMO.Domino.50"/>
                    </triggers>
                    <posts to="Whiteboard.2">
                        <post type="DEMO.Domino.60" phasechange="yes" />
                        <post type="DEMO.Domino.Flop" />
                    </posts>
                </phase>

    </module>

This means that for internal modules there is no code change when you want to use phases.

 

External Modules

External module will have to check themselves in the code to see which phase it is currently in. The easiest way is to use cranks just like internal modules

    <module name="Domino-60">
                <executable ... />
                <phase name="Send first message group">
                    <crank name="MyCrank" />
                    <triggers from="Whiteboard.2">
                        <trigger after="1000" type="DEMO.Domino.50"/>
                    </triggers>
                    <posts to="Whiteboard.2">
                        <post type="DEMO.Domino.60" />
                        <post type="DEMO.Domino.Flip" phasechange="yes" />
                    </posts>
                </phase>
                <phase name="Send second message group">
                    <crank name="MyCrank2" />
                    <triggers from="Whiteboard.2">
                        <trigger after="1000" type="DEMO.Domino.50"/>
                    </triggers>
                    <posts to="Whiteboard.2">
                        <post type="DEMO.Domino.60" phasechange="yes" />
                        <post type="DEMO.Domino.Flop" />
                    </posts>
                </phase>

    </module>

When you receive a wakeup message you can simply read, in your main loop, what the current crank name is and you will know which phase your module is supposed to be in (all your cranks have unique names).

 

C++

   JString crankName;
   while (true) {
      if ((triggerMessage = plug->waitForNewMessage(3000)) == NULL) {
         crankName = plug->getCurrentCrankName();
         if (crankName.equals(“MyCrank”)) {
            // ... processing ... //
            msg = new Message("", "", "");
            plug->addOutputMessage(msg);
            plug->sendOutputMessages();
         }
         else if (crankName.equals(“MyCrank2”)) {
            // ... processing ... //
            msg = new Message("", "", "");
            plug->addOutputMessage(msg);
            plug->sendOutputMessages();
         }
      }
   }

Java

      String crankName;
      while (true) {
         if ((triggerMessage = plug.waitForNewMessage(3000)) == null) {
            crankName = plug.getCurrentCrankName();
            if (crankName.equals(“MyCrank”)) {
               // ... processing ... //
               msg = new Message("", "", "My.First.Output");
               plug.addOutputMessage(msg);
               plug.sendOutputMessages();
            }
            else if (crankName.equals(“MyCrank2”)) {
               // ... processing ... //
               msg = new Message("", "", "My.First.Output");
               plug.addOutputMessage(msg);
               plug.sendOutputMessages();
            }
         }
      }

Remember, that external modules do not use the crank if it is specified in any other way than the above example.

RELATED: psySpec contexts wakeup running the domionsPsySpec.xml

 

 

Using module parameters   Back to Index  

All modules can have an unlimited number of parameters, which the module itself can control, but which other modules can interact with as well. Parameters work the same for both internal as well as external modules in all languages.

Parameters are defined in the psySpec:

   <module name="VideoServer1.Camera1">
      <description></description>
      <parameter name="
Interval" type="Integer" value="200" step="50" default="100"/>
      <parameter name="
BitmapFiles" type="String" value="../../Video/Frame###.bmp" />
      <parameter name="
Repeat" type="String" value="Yes">
         <collection>
            <entry>
Yes</entry>
            <entry>
No</entry>
         </collection>
      </parameter>
      <parameter name="
Resize" type="double" value="0.5" min="0.125" max="4.0"/>
      <parameter name="
Printlevel" type="Integer" value="0" min="0"/>
      <context name="
Psyclone.System.Ready">
            <phase name="
ReadBitmaps">
               <triggers>
                  <trigger after="
100" type="Psyclone.System.Ready"/>
                  <trigger from="
Vision" after="100" type="Command.Camera1*"/>
               </triggers>
               <crank name="gui::BitmapVideoReader"/>
               <outputstream name="Output" source="Input.Video.Camera1" />
            </phase>
      </context>
   </module>

This module defines five named parameters, two of which are strings, two are integers and one is a double (floating point). Each has an initial value and some have a default value which is set when the parameter is being reset.

Some define a step value, which will restrict the parameter from taking on values which are not an integer times the step value away from the initial value. Some specify a minimum and/or a maximum value that the parameter can have. Finally, one parameter specifies the full list of values that that parameter can ever have.

When working with ones own parameters one can use the following API, defined for the Messenger object for internal modules and the Plug object for both C++ and Java external modules.

To check that the module has a parameter by name:

       bool hasParameter(String name);

To read one of the modul's own parameters, either as a string, integer or double:

       String getParameterString(String param);
       int getParameterInteger(String param);
       double getParameterDouble(String param);

To se one of the module's own parameters, either as a string, integer or double:

       bool setParameterString(String param, String value);
       bool setParameterInteger(String param, int value);
       bool setParameterDouble(String param, double value);

To reset a parameter back to its default value:

       bool resetParameter(String param);

To tweak a parameter up or down one or more notches:

       bool increaseParameter(String param, int steps);
       bool decreaseParameter(String param, int steps);

To add another item value to an itemized parameter:

       bool addParameterItem(String param, String value);
       bool removeParameterItem(String param, String value);

When working with other modules' parameters one can use the following API, defined for the Messenger object for internal modules and the Plug object for both C++ and Java external modules.

When wishing to read another module's parameters:

       String getParameterString(String module, String param);
       int getParameterInteger(String module, String param);
       double getParameterDouble(String module, String param);

The string version will always return either the string value of the knob value ("2", "2.54", "text") or if an error occurred the string PARAM_ERROR: <reason>

The two numerical functions in case of errors return

       Java: ret = Parameter.PARAM_ERROR
       C++:  ret = PARAM_ERROR

For setting another module's parameters:

       bool setParameterString(String module, String param, String value, String notify);
       bool setParameterInteger(String module, String param, int value, String notify);
       bool setParameterDouble(String module, String param, double value, String notify);

notify is the name of the Whiteboard which should receive the notification messages. This is not needed for internal modules, as they already know which Whiteboard to post to, so the Messenger API does not include this last parameter for the functions or any of the functions below.

To tweak a parameter up or down one or more notches:

       bool parameterIncrease(String module, String param, int steps, String notify);
       bool parameterDecrease(String module, String param, int steps, String notify);

To add or remove an item from an itemized parameter:

       bool parameterAddItem(String module, String param, String value, String notify);
       bool parameterRemoveItem(String module, String param, String value, String notify);

To reset a parameter to its default value:

       bool parameterReset(String module, String param, String notify);

You can also choose to create a new parameter in your own module manually from your code at any time:

       public boolean addParameter(String paramname, String xml)

With this a module can add an additional parameter to itself using the same XML as is used in the psySpec, i.e.

       <parameter name="Frequency" type="Integer" value="1" min="0" max="100" step="5" />

Note that paramname in the code must match the name="" XML parameter name in the psySpec.

One can also ask for the full list of parameters from another module, including each parameter's specification:

       ObjectDictionary getParameterSpecs(String module)
       Parameter getParameterSpec(String module, String parameter)

The former returns a dictionary of Parameter Specs and the latter just the one Parameter Spec which matches the name. Both can return null if no relevant information is found.

Armed with this spec one can look at the various spec fields, such as parameter.name, parameter.valueType, parameter.minValue, parameter.maxValue, but also the value of the parameter at the time of the request with parameter.value.

 

Setting Message and Module Priorities   Back to Index  

There are two priorities in Psyclone, one for messages and one for modules and whiteboards. Each module has a priority setting that determines its relative priority for accessing CPU cycles — i.e. its runtime priority. Messages have a prority that determines their relative order of scheduling, both in the whiteboards and in the modules.

This dual priority mechanism allows the system to dynamically prioritize certain information paths over others, creating a hierarchy of data significance, from the absolutely critical to the supernumerary while also taking into account the computing cost of producing these.

To set this priority in the psySpec, do:

<module priority="3.0">
  ...
</module>

The message and runtime priority goes from 0.0 (highest) to 6.999... (lowst), following the OpenAIR specification:

0.0 - Highest. Reserved for I/O, network and other vital systems-related processes.
1.0 - Very High. Message scheduling; all whiteboards are at this level.
2.0 - High.
3.0 - Medium.
4.0 - Low.
5.0 - Even lower. Used for rather unimportant processes, e.g. logging.
6.0 - Lowest. Background, non-real-time and immaterial tasks.

When setting the runtime proirity of modules on this scale, Psyclone will translate the selected priority into an equivalently ranked priority of the operating system that the module is running on, maintaining the relative differences between the levels. This means that internal and external modules, as well as whiteboards, are balanced directly against other processes running on the same computer. (Psyclone is designed to make no more use of CPU cycles than necessary; its priority against other processes can be set on UNIX by doing

If no priority is specified for a module it defaults to 2.0; whiteboard priority defaults to 1.0. While the priority of whiteboards can be set, this is only recommended for unique situations. Priority 0.x is used for network activity and similar activities which are vital to normal functioning of Psyclone. (The priority of these cannot be modified).

To set the priority of whiteboards in the psySpec do:

<whiteboard priority="2.2">
  ...
</whiteboard>

While the priorities of modules and whiteboards can be set to any level, it is not recommended to put anything at the 0 (network) level as this can lead to fairly unpredictable system behaviors.

By default a message inherits the priority of the module that posts it. The module that posts a message can set its priority in the post() method.

In the code you can set the priority of a message by doing

msg.setPriority(3.3)

where 3.3 is a float. In the psySpec you can set the priority of a message by doing:

<post to="WB1" priority="2.2">My.Type.1</post>

 

 

Sharing Resources in External Modules   Back to Index  

External modules can share the network resources provided by a single Plug, to fit e.g. more socket connection per computer/Plug/Satellite than otherwise possible. To specify modules sharing resources use the parameter called <shareresources> in the psySpec:

<module name="ModuleB" type="external" shareresources="ModuleA">
...

and the function name in the Plug objects:

plug->shareResources(String name, String xml);

 

 

 

 
2006©Communicative Machines License