|
| Getting Started with
Psyclone |
Back to Index |
|
| To learn the basic
tenets of Psyclone jargon, go here.
In
the psyclone directory you will
find the Psyclone executable for your operating system ("psyclone"
for OS X and Linux; "psyclone.exe" for Windows).
To
run Psyclone, open a console window and cd into the psyclone
directory.
The
psyclone
executable is run by the following command: psyclone
Example:
>./psyclone
This
should start Psyclone, giving some printout of its acitivities
in the console.
NOTE:
If you get the following message:
./psyclone: Permission denied.
you
do not have execution permission in this directory. Execute
the command:
>chmod a+rwx *
at
the command line, and then try running Psyclone again.
You
can specify a setup file to use at the command line, called
a psySpec, to
initialize the system. The psySpec is read in by doing:
psyclone spec="[psySpecName]"
Example:
>./psyclone spec="roundRobinPsySpec.xml"
The roundRobinPsySpec.xml is a simple example psySpec (should
be included with your copy of Psyclone). To see what each of
its parts mean, see the roundRobinPsySpec
tutorial below.
Psyclone
will print out an error if (a) it cannot find the specified
psySpec. It will also print out errors if (b) the port specified
is in use. Troubleshooting.
To
quit Psyclone at the command
line press CTRL+C.
If CTRL+C at the command line doesn't work, pressing CTRL+C
three times will perform a hard exit.
RELATED:
constructing
a psySpec
There
is a third parameter you can specify, which will help you understand
what is going on at run-time with Psyclone. It is called verbose,
and it is used in the following way:
psyclone
port=[port] spec="[psySpecName]"
verbose=[1,2,3,4]
Example:
>./psyclone port=20000
spec="testPsySpec.xml" verbose=4
Related:
command
line parameters verbose=
spec=
If
verbose is specified, Psyclone will print run-time messages
to the console while running.
To
see the status of Psyclone at run time, use psyProbe,
the Web-based monitoring tool.
Run
the example
posting and receiving modules to see a demonstration
of how the posting and registration works in Psyclone.
To
quit Psyclone from psyProbe
you can use the link the bottom of the main psyProbe page.
To quit Psyclone at the command
line press CTRL-C.
If CTRL-C at the command line doesn't make Psyclone shut down
nicely, pressing CTRL-C three times will perform a hard exit.
|
|
Using
psyProbe
|
Back to Index |
|
| psyProbe
is the web-based monitoring interface for Psyclone. With it you can view the
internals of Psyclone at run-time. Before you can use psyProbe you have
to run
Psyclone.
Use
psyProbe by typing the following into the URL box on your browser:
http://[machine]:[psyclonePort]
where [psyclonePort] is the
port
that Psyclone was started up with, or assigned to psyProbe in
the psySpec (the default is 10000 if nothing
is specified), and [machine]
is either the IP address for the machine that Psyclone is running
on, or its name, or the word "localhost".
For example, if
your machine's name is "matrix", and Psyclone is running on port 10000,
you would type http://matrix:10000 into your browser. You
can also type http://localhost:10000
Notice: You may have to use
http:// in the path since
most browsers do not default to this protocol by themselves. More
specifically, when you use psyProbe you
are not using port 80 on the computer (the default port for http
transfer); the browser may not know to default to the http:// protocol
for any other port than port 80.
Posting Messages
Go to
the Post Message page by clicking on the post_message_page_::
link on the main psyProbe page (if you have Psyclone currently running
you can go there now by clicking here).
To post a message to a Psyclone Whiteboard you need to specify
three things, at a minimum: Who the message is from, which Whiteboard(s)
will receive the message, and what type the message is. You
specify the sender in the first panel (the one called from).
The to on the page contains a list of the Whiteboards
— check off one or more Whiteboards. The third
panel on the page, post_message_::,
allows you to manually key in the message type, and the message
content, if you have any. When this is done, you can post your
message by pressing the 'post' button at the bottom.
In
addition to this, you can make any module receive a 'cc'
on your message. This is done in the from panel. A 'cc'd
module will receive (i.e. it will be woken up by) the message
no matter whether it registered for its type or not.
Whiteboards
Whiteboards can carry discrete messages and continuous
streams. The two differ in that messages are like email: You
receive a message and that it is that, unless you write back
that is. Streams are like a phone call: You get a ring and you
pick up, and there is something more coming. The whole thing
continues until you hang up. During the phone call you know
that what comes through is audio (as opposed to video or a fax).
Therefore, when you use psyProbe to view the content of the
whiteboards, the stream samples do not show up in the same way
as messages do, as they reside inside whichever streams they
belong to Whiteboard. PsyProbe lists messages and streams separately,
and you need to look at the streams to see your samples, whether
they are video, audio, or something else.
Samples in streams normally come in rapid succession (10s
or 100s each second). Usually one module post to a stream and
one or more will be subscribers. As a result, each datasample
doesn't have a named type, they all automatically live inside
a stream, which lives in a whiteboard. Receivers of the streams
can perform time-based searches inside the stream and don't
only have to receive all the samples in turn.
Using
Whiteboard Filters
The Whiteboard
page allows you to filter the messages shown on the messages page. If
you are using the recommended
dot-delimited message name format (e.g. My.Example.Message.Type,
Input.hearing.voice), you can construct filters using a star (*) as a
wildcard.
For example,
let's say that the Whiteboard contains the following 5 message types:
Namespace.Input.Message.One
Namespace.Output.Message.Alpha
Namespace2.Output.Message.Omega
Namespace3.My.Input.Message.One
Namespace3.My.Output.Info.Two
The filter
pattern *Namespace.* will result in only the first
and second messages types showing on the page. The pattern Namespace3.My.Input.*
will block all messages except the fourth in the list. The pattern *.Message.*
will, in this example, show all messages except the last one.
This mechanism
is very useful for making sure that messages that should be posted have
been posted. The filtering is not limited to dot-delimitation; you can
specify for example *M* and all
messagetypes above above will be allowed; *essag*
to exclude the last one.
The
content filter allows you to filter on any content of the messages. If
you use this feature only messages which contain a particular string in
their content will be listed in the list. (Content refers
to the payload of messages.) The input
field in the content filter allows you to
input any ASCII string for this purpose. For example, *clone* will filter out all
messages except those which contain the string "clone" somewhere in the
content.
The
amount of content shown in the table can also be set with the
radio buttons none, tiny,
full. Selecting "none" will hide the content
of the messages; selecting tiny will show the first line of
the content (or a summary of the content, if Psyclone knows
how to parse it); "full" will show the full content of all messages
below each message in the table.
Using psyProbe With Satellites
If you want to use PsyProbe
to see WBs running in the Satellite,
you must put a copy of the full HTML tree (from the Psyclone
distribution) next to the Psyclone Satellite Server in the directory
where you run the Psyclone -satelliteserver command.
|
| Creating and
running your first Psyclone system |
|
Back to Index |
|
|
Whether you are a novice or expert in modeling
complex systems the best place to start will probably be the examples
provided with the Psyclone distribution (see the index).
The path of least resistance (can be taken in any
order, except the first two):
Getting started
with Psyclone
Concepts: Whiteboards,
Modules, Messages, Streams
Running the provided external
Java examples
Running the provided external
C++ examples
Running the provided
internal C++ examples
Running the provided roundRobinPsySpec
example
Running the provided dominoPsySpec
example
There are several things that need to be
considered when creating the first system using Psyclone. Firstly,
Psyclone uses a few new
concepts that you may want to familiarize yourself with.
Secondly, you will want to look at the message and routing protocol
that Psyclone uses, called OpenAIR.
Thirdly you probably should look at the examples provided with the
distribution.
One of the complicating things about real-time systems with independent
elements is the way that causality works in them —
when each element can operate independent of the others some
causal chain of events that the designer wanted to happen may
not happen because one or more events in the system don't always
happen at the right time. Systems which are highly time-dependent,
and especially systems which are intended to interact with the
real world, will most likely need to be tuned and tested before
they operate as intended. Yet there is no other way to model
many of the highly complex systems we see in the world. Here
is what you can do:
- The Constructionist Design Methodology
provides some principles to help with this and related problems.
Mindmakers.org has a
section
on this methodology.
- Go through as many tutorials
on Psyclone as you can before starting to design your own
system — be prepared to have to redo some of
your design.
- Use your own carefully designed message/stream
type ontology or a third-party ontology.
- Desing your system to be resilient
to temporal variation is events.
|
| Running the
provided external Java examples |
|
Back to Index |
|
|
Java examples are
included in the JavaAIRPlug download. The
examples included are JavaPoster.java
and JavaReader.java.
The JavaPoster simply posts a message of
type My.Test.Message.Type
to whiteboard WB1
when you press the return key. The JavaReader is an example
of a module that registers with whiteboard WB1
for that same message type, and simply prints it to the console.
To
run (on Linux and Mac OS X), do:
java -classpath ./:JavaOpenAIR.jar JavaReader psyclone=[psyclone
host]:[psyclone
port]
where
[psyclone host] is either the IP
address of your machine or the word localhost, and
[psyclone port] is the port that you started
Psyclone up with.
Example command (on Linux, with Psyclone running on port 10000):
>java -classpath ./:JavaOpenAIR.jar
JavaReader psyclone=localhost:10000
If you have problems with Java you may want to recompile the
files (see below).
Notice
that on Windows you will
be using a slightly different syntax for paths:
java -classpath .;JavaOpenAIR.jar
JavaReader psyclone=[psyclone host]:[psyclone port]
i.e. use semicolon instead of colon.
If
you are using Cygwin
on Windows you will be
using yet another syntax for paths:
java -classpath .;JavaOpenAIR.jar
JavaReader [psyclone host] [psyclone port]
i.e. use semicolon instead of colon, and for paths, use backslash
"\" instead of forward slash "/".
Now
you need to go to another console window, and in the same JavaExamples
directory, do:
Example (Linux):
>java -classpath ./:JavaOpenAIR.jar
JavaPoster psyclone=localhost:10000
Pressing
Enter on the JavaPoster
you should see the JavaReader
print out that it received it, on the command line.
If
you get a message of the effect 'java.lang.OutOfMemoryError',
you may need to provide a command-line parameter to ensure
sufficient memory allocation for Java.
Use
the following command to run Java:
>java -Xincgc -Xms50M -Xmx200M -classpath ./:JavaOpenAIR.jar
JavaReader psyclone=localhost:10000
The
first parameter is incremental garbage collection, the second
is the startup memory size and the third is the maximum. In
this example, an out of memory error would not occur until
Java has used up 200MB of memory.
To
compile the Java examples
you need to include the JavaOpenAIR
libraries. To
compile the Java examples you will need the need to download
and use the Sun
Java SDK (choose
the one that is appropriate to your platform).
javac -classpath JavaOpenAIR.jar
JavaReader.java
javac -classpath JavaOpenAIR.jar
JavaPoster.java
To
compile on Linux and
Mac OS X, do:
Example:
>javac -classpath ./:JavaOpenAIR.jar
JavaPoster.java
and:
>javac -classpath
./:JavaOpenAIR.jar JavaReader.java
Both
should return without error (let
us know if you have problems). Use the above commands to
run them. |
| External Java
module example in detail |
|
Back to Index |
|
|
To read about external modules in general, go here.
To write an external module in Java, you will need
the Java AIR Plug. (If JavaAIRPlug was not included with this
distribution you can download the plug by going to http://www.cmlabs.com/psyclone/download/,
and selecting the platform that you are developing your external module
on).
We will assume that you start your external Java
module manually. To start modules automatically, see the tutorial on Automatically
starting modules.
To write an external Java module, you will need
to create a source file bearing the name of your module. We will create
a module call Reader, with source Reader.java.
Initially in your source file you will always
need to import the AIR package that come with the plug:
package com.cmlabs.air; After creating a class definition and creating
a few instance variable:
public class Reader {
private JavaAIRPlug plug; // the connection to Psyclone
private Message triggerMessage; // reusable incoming message
private Message msg; // reusable ougoing message
you will create a 3-arg constructor:
public Reader(String
plugName, String host, int port) {
In the constructor, instantiate the module
plug = new
JavaAIRPlug(plugName, host, port);
and use the plug call init() to connect to
Psyclone:
if (!plug.init()) {
System.out.println("Could not connect to
the Server on " + host + on port " + port + "...");
System.exit(0);
}
Next, the important work that the module needs to
do can now be written. This sample code simply waits for a trigger
message (of message type that is specified in the psySpec for this
module) by using the plug call waitForNewMessage(long
ms), and prints information about the trigger message when
the module is woken up. Additionally, a new basic message is create,
given to the plug as a message to post using the plug call addOutputMessage(), and then
post a message (of message type that is specified in the psySpec for
this module) using the call sendOutputMessages():
while (true) {
if ((triggerMessage =
plug.waitForNewMessage(50)) != null) {
System.out.println(triggerMessage.postedTime.printTime()
+ ": Reader
received
message " + triggerMessage.type + " from " +
triggerMessage.from);
msg = new Message("", "", "");
plug.addOutputMessage(msg);
plug.sendOutputMessages();
} else {
if (!tv.isServerAvailable())
System.out.printf("Psyclone is available
but the module
has not been awoken ");
else
System.out.printf ("Psyclone is not
available");
}
}
}
Finally, you will need a main() method in the class in
order to startup the module. The main() method will generally comprise
of code that interprets some of the command-line parameters that would
be important, such as the host and port that Psyclone is running on.
Some sample code that can be used in the external module to do this
would be:
public static void
main(String[] args) {
String usage = "Reader usage: Reader
[psyclone=[psycloneHost][:psyclonePort]]
[name=Reader] (eg \"Reader psyclone=:27000\")";
// default host and port
String host = "localhost";
int port = 10000;
String name = "Reader";
String arg;
for (int n = 0; n < args.length; n++) {
arg = args[n];
if (arg.startsWith("psyclone=")) {
String str
= arg.substring(9);
if
(str.length() > 0) {
int
pos = str.indexOf(":");
if
(pos < 0)
host
= str;
else
if (pos > 0)
host
= str.substring(0, pos);
port
= Utils.str2int(str.substring(pos + 1));
}
} else if (arg.startsWith("name=")) {
name =
arg.substring(5);
}
}
Reader reader = new Reader(name, host,
port);
}
|
| Running
the provided external c++ example |
|
Back to Index |
|
|
This external C++
example is included in the CppAIRPlug download. The
example inlcudes two programs called CppPoster
and CppReader. The CppPoster
simply posts a message of
type My.Test.Message.Type to
whiteboard WB1 when you press the return key.
The CppReader
is an example of a module that registers with whiteboard
WB1 for that same message type, and simply prints it
to the console. This examples parallels the example
of external modules written in Java.
Before you run the code you
will need to compile it. Refer to the QUICKSTART file for the
external C++ example that is provided with the Psyclone distribution.
To
run the C++ examples you will have to have a Whiteboard
called WB1 already running in Psyclone. The simplest way to
do this is to just start up Psyclone:
./psyclone
and
then, in the directory where the C++ examples are, give the
command:
> CppReader psyclone=[psyclone
host]:[any port number]
The
colon : has to be there if you give a port number; you don't
need to specify psyclone=localhost:10000 at all if you want
to use the default of localhost:10000.
and in another
console window, do:
> CppPoster psyclone=[psyclone
host]:[any port number]
in
this order, each in its own console window, where [psyclone
host] is the IP address of the computer running Psyclone,
the name of the computer running Psyclone (or the word "localhost",
if you're running the example on the same machine as Psyclone).
The Reader will register with whiteboard WB1 for a message of
type Test.Message.Type. CppPoster will post a message of type
Test.Message.Type once every second.
When
CppPoster posts a message it goes to WB1, which then sends it
to the registered CppReader module. If you look at WB1 in psyProbe
you can see the message entered every time foor both events.
To run psyProbe, start a Web browser and type into it:
http://localhost:10000
This should give you the front page of psyProbe. (If you get
something else, make sure you have included the html directory
in the same directory as Psyclone.) Here you can probe around
to see what is happening to your messages and your modules.
Compiling
the C++ Examples
To compile the C++ examples for Linux or Mac
OS X, use the Make file
included with the examples. For Windows you need the Visual
Studio 6 (.NET project files can be made available).
Related: How
to run the SDK |
| External C++
module example in detail |
|
Back to Index |
|
|
To read about external modules in general, go here.
To write an external module in C++, you will need
the CppAIRPlug. (If CppAIRPlug was not included with this distribution
you can download the plug by going to http://www.cmlabs.com/psyclone/download/,
and selecting the platform that you are developing your external module
on).
We will run this external module manually. (The
tutorial on Automatically starting modules
explains how to do this automatically.)
To write an external C++ module, you will need a
source and header file bearing the name of your module. The Reader
example with the Psyclone distribution is Reader.cpp
and Reader.h.
The header file must contain at least the
following, using Reader as the example:
#include "Communicator.h"
#include "Component.h"
#include "CppAIRPlug.h"
class Reader : public CppAIRPlug
{
public:
Reader(JString name, JString host, int
port);
virtual ~Reader();
};
The source files must first and foremost include
the header:
#include "Reader.h"
The source file must also have a constructor and
destructor:
CppReader::CppReader(JString
name, JString host, int port) :
CppAIRPlug(name, host, port) {}
CppReader::~CppReader() {}
The source file must have a main() function in order to
startup the module:
int main(int argc, char*
argv[])
The main()
function will generally first contain some code that interprets some of
the command-line parameters that would be important, such as the host
and port that Psyclone is running on. Some sample code that can be used
in the external module to do this would be:
JString host =
"localhost";
int port = 10000;
JString allArgs;
for (int ii=0; ii<argc; ii++) {
allArgs += JString(argv[ii]) + " ";
}
allArgs.trim();
Dictionary argDict = allArgs.splitCommandLine();
JString arg;
JString val;
for (int n=1; n<argDict.getCount(); n++) {
arg = argDict.getKey(n);
val = argDict.get(n);
if (arg.equalsIgnoreCase("psyclone")) {
Collection
col = val.trim().split(":");
if
(col.get(0).length() > 0)
host =
col.get(0);
if
(col.get(1).length() > 0)
port =
col.get(1).toInt();
}
}
The module should then be instantiated:
CppReader* tv = new
CppReader("CppReader", host, port);
and use the plug call init()
to connect to Psyclone:
if (!tv->init()) {
printf("Could not connect to Psyclone on
'%s' port %d, exiting...\n\n", (char*) host, port);
return 0;
}
Since you are running the module manually, you
may now test for the availability of Psyclone and, if available, start
the module, otherwise, delete the module and exit from the program:
if
(tv->isServerAvailable()) {
tv->start();
} else {
printf("Could not connect to Psyclone on
'%s' port %d,
exiting...\n\n",
(char*) host, port);
delete(tv);
exit(0);
}
Finally, the important work that the module needs
to do can now be written. This sample code simply waits for a trigger
message (of message type that is specified in the psySpec for this
module) by using the plug call waitForNewMessage(long
ms), and prints information about the trigger message when
the module is woken up. Additionally, a new basic message is create,
given to the plug as a message to post using the plug call addOutputMessage(), and then
post a message (of message type that is specified in the psySpec for
this module) using the call sendOutputMessages():
Message* triggerMessage;
Message* msg
int count = 0;
while (true) {
if ((triggerMessage =
tv->waitForNewMessage(100)) != NULL) {
printf("\r%s:
CppReader received message %s from %s...\n", (char*)
triggerMessage->getTimestamp().printTimeMS(),
(char*) triggerMessage->getType(),
(char*)
triggerMessage->getFrom());
msg = new
Message("", "", "");
plug->addOutputMessage(msg);
plug->sendOutputMessages();
fflush(stdout);
} else {
if
(!tv->isServerAvailable())
printf("Psyclone
is available but the module has not been awoken");
else
printf("Psyclone
is not available");
}
}
return 0;
|
| Running
the provided internal c++ examples |
|
Back to Index |
|
|
Internal modules are designed to run inside
Psyclone for maximum performance and efficiency. They are similar to
external modules, but somewhat simpler to design, implement and
maintain.
A library
contains crank functions,
which can be as complex as the designer wants. Cranks share the same
interface definition, but must have different names. Any number of
libraries can be loaded by Psyclone at the same time.
When an
Internal Module is specified in the psySpec
file, it may include a crank section. The crank section can list one or
more cranks to be executed. These then reference crank methods in the
externally loaded library. To take an example, a crank 'mycrank',
contained in an external library called 'mylib', can be specified in
the psySpec file as
<crank name="mylib::mycrank"
/>
Thust, to include a crank function from a third-party
library in an internal module you will
need to create a psySpec
and define the module
using the specific crank function as a crank. For example, to
define a module which uses the crank function testCrank1
located inside the example library called cm,
you would specify the module like this:
<module
name="MyModule"
type="internal" >
<description>This
module is my first module</description>
<trigger after="100"
type="Psyclone.System.Ready"/>
<crank name="cm::testCrank1"
/>
<post to="WB1"
type="Some.Message.Type"
/>
</module>
This means that
when the Internal Module that contains this crank specification
gets awoken with
a trigger, the triggering message and any retrieved messages
will be delivered as input to the crank. In return the Internal
Module expects zero or more output messages, that is, the crank
may post something as a result of having been awakened.
To compile the C++ examples for Linux or Mac
OS X, use the Make file
included with the examples. For Windows you need the Visual
Studio 6 (.NET project files can be made available). To start
up the project in Visual Studio, double-click on the mycmdll.sln
file. Then build the solution. This will compile and build the
library file cm.dll.
|
| Running
the provided roundRobinPsySpec example |
Back
to Index |
|
|
If
you are new to Psyclone this is a good place to start.
A
psySpec is an XML file which is used to initialize things.
The psySpec dictates for example which whiteboards and which
modules to start up, as well as some other things.
The
roundRobinPsySpec.xml
psySpec file is included with Psyclone and reproduced here in
full, for convenience. It
should take you about 5-10 minutes to go through this example.
The
file itself looks like this:
<psySpec name="Demonstration
of Whiteboards, Modules and Messages" version="1.2">
<global>
<title>Round-robin psySpec</title>
<description>
This demo shows basic usage of Whiteboards
Modules and Messages.
</description>
</global>
<whiteboard
name="Whiteboard.1"
maxcount="15000">
<description>Stores all messages
from the three round-robin modules.</description>
</whiteboard>
<!-- ####################
ROUND ROBIN MODULES #################### -->
<module
name="RoundRobin-1">
<description>
This
module gets initially triggered by Psyclone.System.Ready,
which
is posted by Psyclone when it's ready,
and
after that it is triggered by Trigger3
which
is posted by the RoundRobin-3 module.
</description>
<triggers
from="Whiteboard.1">
<trigger
after="100" type="Psyclone.System.Ready"/>
<trigger
after="500" type="Trigger3"/>
</triggers>
<post
to="Whiteboard.1"
type="Trigger1" />
</module>
<module
name="RoundRobin-2">
<description>This module will
trigger off RoundRobin-1's posting.</description>
<trigger
from="Whiteboard.1"
after="500"
type="Trigger1"/>
<post to="Whiteboard.1"
type="Trigger2"
/>
</module>
<module
name="RoundRobin-3">
<description>This module will
trigger off RoundRobin-2's posting.</description>
<trigger from="Whiteboard.1"
after="500"
type="Trigger2"/>
<post to="Whiteboard.1"
type="Trigger3"
/>
</module>
</psySpec>
When Psyclone starts up it can be told to read in this psySpec.
Go
to the specification
of running Psyclone; use the roundRobinPsySpec.xml as the
value for the 'spec' parameter.
READ MORE:
running Psyclone
If the psySpec specifies internal
modules, Psyclone also spawns these. In the roundRobinPsySpec.xml
file we have defined three internal modules. These modules are
so that they only use functionality that is built into Psyclone:
receiving and posting messages.
By
default, Psyclone always spawns (creates) the whiteboard
AIRCentral. This is because Psyclone is OpenAIR
compliant. In the case of the roundRobinPsySpec.xml
we also create a second whiteboard, whiteboard.1.
When roundRobinPsySpec starts up the modules start
to post messages, each in turn. This is because they have been
rigged in the psySpec to trigger each other in a loop: module
RoundRobin-1 posts a message type which RoundRobin-2 has subscribed
to, and so on; RoundRobin-1 also subscribes to a message type
that is posted by RoundRobin-3. Each of the triggers are outfitted
with a delay, the after= parameter. This means that Psyclone
will wait for the specified number of milliseconds before waking
the module up with that trigger.
READ
MORE: psySpec
internal module example writing
a psySpec running the dominosPsySpec
|
| Running
the provided dominosPsySpec example |
Back to Index |
|
|
If
you are new to Psyclone you might first want to look at Running
the provided roundRobinPsySpec example. If
you follow most of the links provided down one level of detail,
it should take you about 10 minutes to go through all points.
A good way to proceed is to first go through all the steps,
and then go back and look further at some of the links provided.
1. Psyclone
Startup
When Psyclone starts up it reads in the psySpec,
an XML file which is used to initialize. The psySpec dictates
for example which whiteboards and which modules to start up,
as well as some other things.
The dominosPsySpec.xml
file is included with Psyclone. Go to the specification of running Psyclone;
use the dominosPsySpec.xml as the value for the 'spec' parameter.
READ MORE: running
Psyclone
2. Spawning
Internal Modules
If the psySpec specifies internal
modules, Psyclone also spawns these. In the dominosPsySpec.xml
file we have defined a few internal modules. These modules have
very simple functionality — so simple in fact that
they only use functionality that is built into Psyclone, that
of receiving and posting messages.
By default,
Psyclone always spawns (creates) the whiteboards
AIRCentral, WB1 and WB2. This is because Psyclone is OpenAIR
compliant. In the case of the
dominosPsySpec.xml it also creates whiteboard.1
and whiteboard.2.
When dominosPsySpec starts up
it creates eight modules. Three of these will continue to post messages
after the program starts up. This is because they have been rigged in
the psySpec to trigger each other in a loop.
READ MORE: psySpec
internal module example writing
a psySpec
3. Contexts
Contexts can be used to
manage the activity of modules in the system. They are a convenient way
to make sure that sets of modules be active
during certain states and not others. Even
though contexts have certain features of their own that make them
special, in some ways they can be thought of as global variables. Contexts are defined hierarchically;
in a typical system specific modules are used to post contexts and
manage transition between. Their syntax is period-delimited.
Once Psyclone is running, go
to a browser and start up psyProbe, the
web-based interface to Psyclone. This will enable you to open another
page, called contexts+phases page,
listed at the top of the main psyProbe page.
Once you have the contexts+phases page loaded, take
a look at the following modules listed there: Domino-1,
Domino-2, Domino-3,
Domino-40,
Domino-50 and Domino-60.
These modules are internal modules. Domino-1, 2 and 3 are set up to trigger
each other in a cycle, as are Domino-40, 50 and 60,
using the after= parameter of the trigger
for each module.
Currently, as you can see in the list of contexts
at the top of the page, the contexts System.Ready
and Dominos.Chain-1 are active. Now, using
the post
context button, you can switch the Dominos context to Dominos.Chain-2
(select this context from the drop-down, and click on post
context button.) When you post Dominos.Chain-1 the
Dominos.Chain-2 context becomes invalid. (However, if you post
a context called Dominos.Chain-1.hello the Dominos.Chain-1 will
still be valid — the latter subsumes the former.)
Now you should see Domino-1,
2 and 3 go out of context,
while the modules Domino-40, 50
and 60 are in context. You can further verify
that this is true by going to the whiteboard pages
(click on the whiteboard's name on the main psyProbe page, i.e.
Whiteboard.1), and see that only System.Report
messages are posted there. If you look at Whiteboard.2,
however, you'll see that the domino chain 2 has started —
there are now plenty of messages on Whiteboard.2.
READ
MORE: using
contexts
4. Phases
Every
module has one or more phases. Phases work
like states in a sequencer: A module with
only one phase has only one state while active; a module with two or
more phases cycles through the phases. Typically, a phase is changed
immediately after a module has posted (this can be specified in the
psySpec definition for the module by setting the post parameter
phasechange="yes"). A module can also simply post an explicit phase
change.
Phases can be grouped under contexts.
Phases belonging to the same context get activated one after another,
like a sequencer. For every phase there may be different triggers; when
the module is in a particular phase, any message types listed as
triggers for that phase will cause the module to get a wakeup if they
appear on the Whiteboard.
The phases are listed in the psySpec (example).
When a module has two or more contexts (each with one or more phases),
and the context gets switched, the first phase in that context is
activated.
|
| Creating
external modules in Java |
|
Back to Index |
|
To read about external modules in general, go here.
To write an external module in Java, you will
need the JavaAIRPlug or JavaOpenAIR. (JavaOpenAIR can be downloaded
from http://www.mindmakers.org/openair/download/).
External modules are designed to run outside
Psyclone in separate executables. They are very similar to internal
modules and they can be written in other languages, such as Java.
Any module has two distinct functionalities: message handling
and message processing. Like internal modules, external modules
only have to worry about the message processing (called a crank),
because all the message handling (receiving, organizing, posting,
etc.) is done automatically.
To create an external module in Java one normally
would use an AIR Plug. This is a Java object which can be instantiated
inside any Java program and the plug is used for all communication with
Psyclone.
This is an example of a simple Java program and
how to use the plug:
import com.cmlabs.air.*;
public class MyModule {
public static void main(String[] args) {
// while (poster.isConnected()) {
JavaAIRPlug plug = new JavaAIRPlug("MyModule", "localhost", 10000);
if (!plug.init()) {
System.out.println("Could not connect to Psyclone");
System.exit(0);
}
Message triggerMessage;
Message msg;
while (true) {
if ((triggerMessage = plug.waitForNewMessage(3000)) == null)
{
// ... processing ... //
msg = new Message("", "", "My.First.Output");
plug.addOutputMessage(msg);
plug.sendOutputMessages();
}
else {
// ... do something else for a while ... //
}
}
}
}
This program assumes that Psyclone is running on
the local computer on the standard Psyclone port 10000.
For Psyclone to use the module, you will need to tell Psyclone
about it. You do this via an XML definition like this:
<module name="MyFirstModule"
type="external" >
<context name="Psyclone.System.Ready">
<phase name="Default">
<trigger from="AIRCentral"
type="My.First.Type" />
<post to="AIRCentral"
type="My.First.Output" />
</phase>
</context>
</module>
This specification can be put into the central psySpec configuration
file and read in by Psyclone at startup. Alternatively it can
be sent to Psyclone at runtime, in the conten of a message;
the message type must be Psyclone.System.Create.Module.
(Destroy modules by sending a message type Psyclone.System.Destroy.Module,
with the name of the module as content, encapsulated in the
<module> tag but without
further content.)
Unless you specify a command that will automatically start
up your module (using the <executable>
tag) you will need to start up your external module manually
after you start up Psyclone.
Note that you need to use the <executable />
tag to tell Psyclone that this is an external module. Otherwise,
Psyclone will create it as an internal module. In a later tutorial you
can see how to fill in the <executable> tag to automatically
start up your module.
Now Psyclone knows that when any message with
type My.First.Type is posted to the Whiteboard AIRCentral, your module
with the name MyFirstModule should be woken up with this message.
In most cases you probably want to do a bit more
in your module. The Plug object is a communication device, which you
can use to get the triggering message and any other messages which may
have been delivered if you asked for it in the psySpec using the
<retrieve> tag (see below). If you post messages using
the Plug the messages are automatically sent to the correct
destinations.
When this empty message is posted, the :from
and the :to fields are automatically filled in
and a copy is sent to every destination Whiteboard defined in
the psySpec, each with the correct type filled in. For example,
if the psySpec had defined two post destinations:
<posts>
<post to="AIRCentral"
type="My.Second.Type"
/>
<post to="AIRCentral"
type="My.Third.Type"
/>
</posts>
the AirCentral whiteboard would receive two
messages, one with type My.Second.Type and one with My.Third.Type.
If, however, the function had created a message
like this:
msg = new Message("",
"", "My.Second.Type");
only one message would be sent out, as only the
first post matches the type. Likewise, had the message been created
with
msg = new Message("",
"", "My.Other.Type");
no message would be sent out.
If you wish to bypass the post section manually,
you can always do:
msg = new Message("",
"MyOtherWB", "My.Other.Type");
in which case only one message would be sent to
the MyOtherWB Whiteboard.
When you in the psySpec specify which message
types will trigger your module you may wish to retrieve additional
messages from the same or other Whiteboards to be delivered along side
the triggering message:
...
<triggers>
<trigger from="AIRCentral"
type="My.First.Type"
/>
</triggers>
<retrieves>
<retrieve from="BBX"
type="Input.Sens.UniM.Hear"/>
<retrieve from="BBX"
type="Input.Person.Found.True">
<latest>10</latest>
<lastmsec>200</lastmsec>
</retrieve>
</retrieves>
...
In your code, you can read these additional
messages, if any, by asking the Messenger object:
// Get the current
number of retrieved messages
int getRetrievedMessageCount();
// Get a specific retrieved message
Message getRetrievedMessage(int pos);
// Get the full collection of the retrieved messages
ObjectCollection getAllRetrievedMessages();
Should you want to manually retrieve messages
from the crank without specifying this in the psySpec, you can ask the
Messenger object:
// Retrieve additional
messages from a source
ObjectCollection retrieveMessages(String retrieveFrom, String
retrieveSpecXML);
where retrieveSpecXML uses the same format as you
would have used in the psySpec including the surrounding <retrieves>…</retrieves>.
You do not have to register cranks for external
modules, but if you do it is a good way of telling your module what to
do with a message. If we add a crank in the psySpec like this:
<module name="MyFirstModule"
type="external">
<context name="Psyclone.System.Ready">
<phase name="Default">
<triggers>
<trigger from="AIRCentral" type="My.First.Type" />
</triggers>
<crank name="MyFirstModuleCrank" />
<posts>
<post to="WB1" type="My.First.Output" />
<post to="WB2" type="My.Second.Output" />
</posts>
</phase>
</context>
</module>
we can now slightly extend our code to ask for
the name of the crank after being woken up by the trigger message:
import com.cmlabs.air.*;
public class MyModule {
public static void main(String[] args) {
// while (poster.isConnected()) {
JavaAIRPlug plug = new JavaAIRPlug("MyModule", "localhost", 10000);
if (!plug.init()) {
System.out.println("Could not connect to Psyclone");
System.exit(0);
}
Message triggerMessage;
Message msg;
while (true) {
if ((triggerMessage = plug.waitForNewMessage(3000)) == null)
{
if
(plug.getCrankName().equals("MyFirstModuleCrank")) {
// ... processing ... //
msg = new Message("", "",
"My.First.Output");
plug.addOutputMessage(msg);
}
else if
(plug.getCrankName().equals("MySecondModuleCrank")) {
// ... processing ... //
msg = new Message("", "",
"My.Second.Output");
plug.addOutputMessage(msg);
}
plug.sendOutputMessages();
}
else {
// ... do something else for a while ... //
}
}
}
}
Note that the module will now post its output to
Whiteboard WB1 if the crank is MyFirstModuleCrank, and to Whiteboard
WB2 if the crank is equal to MySecondModuleCrank.
After receiving a wakeup message a module can ask:
getTriggerAlias()
which will return the alias of the trigger
message, or the message type if <trigger> does not have
an alias. By default, the name parameter will be the type if not
specified.
When posting you can use:
postOutputMessage(Message*
msg)
and
postOutputMessage(Message*
msg, JString destination)
The former is shorthand for first adding to output
and then sending output. The
second allows you to post to a named alias <post alias="xyz">
— so if you use postOutputMessage(Message*
msg, "xyz") the post entry with the xyz alias will be
chosen. Alternatively, you can specify the type in a <post>
the same way:
postOutputMessage
(Message* msg, "my.type")
If you merely wish to post a message to all
whiteboards:
msg = new Message("",
"", "")
addoutputmessage(msg)
sendoutputmessages()
You can call one method which will do all this:
postOutputMessage(new
Message())
You can set the priority of a message by doing
msg.setPriority(3.3)
where 3.3 is a floating-point number.
|
| Creating external
modules in C++ |
|
Back to Index |
|
|
To read about external modules in general, go here.
To write an external module in C++, you will need
the CppAIRPlug. (If CppAIRPlug was not included with this distribution
you can download the plug by going to http://www.cmlabs.com/psyclone/download/,
and selecting the platform that you are developing your external module
on).
External modules are designed to run outside
Psyclone in separate executables. They are very similar to internal
modules and they can be written in other languages, such as Java.
Any module has two distinct functionalities,
namely message handling and message processing. Like internal modules,
external modules only have to worry about the message processing (or
crank, as we call it), because all the message handling (receiving,
organizing, posting, etc.) is done automatically.
To create an external module in C++ one normally
would use an AIRPlug. This is a C++ object which can be instantiated
inside any C++ program and the plug is used for all communication with
Psyclone.
This is an example of a simple C++ program and
how to use the plug:
int main(int argc, char*
argv[]) {
CppAIRPlug plug = new
CppAIRPlug("MyFirstModule", "localhost", 10000);
if (!plug->init()) {
printf("Could
not connect to Psyclone\n\n");
return -1;
}
Message* triggerMessage;
Message* msg;
while (true) {
if
((triggerMessage = plug->waitForNewMessage(3000)) == NULL) {
// ...
processing ... //
msg = new
Message("", "", "");
plug->addOutputMessage(msg);
plug->sendOutputMessages();
}
else {
// ... do
something else for a while ... //
}
}
return 0;
}
This program assumes that Psyclone is running on
the local computer on the standard Psyclone port 10000.
For Psyclone to use the module, you will need to tell Psyclone
about it. You do this via an XML definition like this:
<module name="MyFirstModule"
type="external" >
<executable/>
<description></description>
<context name="Psyclone.System.Ready">
<phase
name="Default">
<triggers>
<trigger
from="AIRCentral"
type="My.First.Type"
/>
</triggers>
<posts>
<post
to="AIRCentral" type="My.First.Output" />
</posts>
</phase>
</context>
</module>
This specification can be put into the central psySpec configuration
file and read in by Psyclone at startup. Alternatively it can
be sent to Psyclone at runtime, in the conten of a message;
the message type must be Psyclone.System.Create.Module.
(Destroy modules by sending a message type Psyclone.System.Destroy.Module,
with the name of the module as content, encapsulated in the
<module> tag but without
further content.)
Unless you specify a command that will automatically start
up your module (using the <executable>
tag) you will need to start up your external module manually
after you start up Psyclone.
Note that you need to use the <executable />
tag to tell Psyclone that this is an external module. Otherwise,
Psyclone will create it as an internal module. In a later tutorial you
can see how to fill in the <executable>
tag to automatically start up your module.
Now Psyclone knows that when any message with
type My.First.Type is posted to the Whiteboard AIRCentral,
your module with the name MyFirstModule should be woken up with this
message.
In most cases you probably want to do a bit more
in your module. The Plug object is a communication device, which you
can use to get the triggering message and any other messages which may
have been delivered if you asked for it in the psySpec using the <retrieve> tag
(see below). If you post messages using the Plug the messages are
automatically sent to the correct destinations.
When this empty message is posted, the :from
and the :to fields are automatically filled in
and a copy is sent to every destination Whiteboard defined in
the psySpec, each with the correct type filled in. For example,
if the psySpec had defined two post destinations
<posts>
<post to="AIRCentral" type="My.Second.Type" />
<post to="AIRCentral" type="My.Third.Type" />
</posts>
the AirCentral whiteboard would receive two
messages, one with type My.Second.Type and one with My.Third.Type.
If, however, the function had created a message
like this
msg = new Message("",
"", "My.Second.Type");
only one message would be sent out, as only the
first post matches the type. Likewise, had the message been created with
msg = new Message("",
"", "My.Other.Type");
no message would be sent out.
If you wish to bypass the post section manually,
you can always do
msg = new Message("",
"MyOtherWB", "My.Other.Type");
in which case only one message would be sent to
the MyOtherWB Whiteboard.
When you specify in the psySpec which message
types will trigger your module you may wish to retrieve additional
messages from the same or other Whiteboards to be delivered along side
the triggering message:
...
<triggers>
<trigger from="AIRCentral" type="My.First.Type" />
</triggers>
<retrieves>
<retrieve from="BBX" type="Input.Sens.UniM.Hear"/>
<retrieve from="BBX" type="Input.Person.Found.True">
<latest>10</latest>
<lastmsec>200</lastmsec>
</retrieve>
</retrieves>
...
In your code, you can read these additional
messages, if any, by asking the Messenger object:
// Get the current
number of retrieved messages
int getRetrievedMessageCount();
// Get a specific retrieved message
Message* getRetrievedMessage(int pos);
// Get the full collection of the retrieved messages
ObjectCollection* getAllRetrievedMessages();
Should you want to manually retrieve messages
from the crank without specifying this in the psySpec, you can ask the
Messenger object:
// Retrieve additional
messages from a source
ObjectCollection* retrieveMessages(const JString& retrieveFrom,
const JString& retrieveSpecXML);
where retrieveSpecXML uses the same format as you would have
used in the psySpec including the surrounding <retrieves>…
</retrieves>.
You do not have to register cranks for external
modules, but if you do it is a good way of telling your module what to
do with a message. If we add a crank in the psySpec like this:
<module name="MyFirstModule"
type="external">
<description></description>
<context name="Psyclone.System.Ready">
<phase
name="Default">
<triggers>
<trigger
from="AIRCentral"
type="My.First.Type"
/>
</triggers>
<crank
name="MyFirstModuleCrank"
/>
<posts>
<post
to="WB1" type="My.First.Output" />
<post
to="WB2" type="My.Second.Output" />
</posts>
</phase>
</context>
</module>
we can now slightly extend our code to ask for
the name of the crank after being woken up by the trigger message:
while (true) {
if ((triggerMessage =
plug->waitForNewMessage(3000)) == NULL) {
//
... processing ... //
if
(plug->getCrankName().equals("MyFirstModuleCrank")) {
//
... processing ... //
msg
= new Message("", "", "My.First.Output");
plug->addOutputMessage(msg);
}
else
if (plug->getCrankName().equals("MySecondModuleCrank")) {
//
... processing ... //
msg
= new Message("", "", "My.Second.Output");
plug->addOutputMessage(msg);
}
plug->sendOutputMessages();
}
else {
//
... do something else for a while ... //
}
}
Note that the module will now post its output to
Whiteboard WB1 if
the crank is MyFirstModuleCrank,
and to whiteboard WB2
if the crank is equal to MySecondModuleCrank.
|
| Creating internal
c++ modules |
|
Back to Index |
|
|
Internal modules are designed to run inside
Psyclone for maximum performance and efficiency. They are similar to
external modules, but simpler to design, implement and maintain. (However, they do have limitations
related to dynamically loaded libraries.)
Internal C++ modules are developed using the Psyclone SDK, which
is provided with PsyclonePro distributions.
Any module has two distinct functionalities, namely message reception/handling
and subsequent processing — also called a crank.
Internal modules only have to worry about the crank, because
all the message handling (receiving, organizing, posting, etc.)
is done automatically.
To create an internal module one has to define a
crank which will process incoming messages and generate output
messages.
This is an example of the crank function for an
internal module, which just prints Hello World to the screen:
int
MyFirstModuleCrank(Messenger* messenger) {
printf("Hello World");
return 0;
}
All you need to do is put this into the cranks
file in the Psyclone SDK (cranks.cpp
and cranks.h in the
default library called cm),
recompile and now you have an internal module.
For Psyclone to use the module, you will need to tell Psyclone
about it. You do this via an XML definition like this:
<module name="MyFirstModule"
type="internal"
>
<description></description>
<context name="Psyclone.System.Ready">
<phase name="Default">
<triggers>
<trigger from="WB1"
type="My.First.Type"
/>
</triggers>
<crank name="cm::MyFirstModuleCrank"
/>
<posts>
<post to="WB1"
type="My.Second.Type"
/>
</posts>
</phase>
</context>
</module>
This specification can be put into the central psySpec configuration
file and read in by Psyclone at startup. Alternatively it can
be sent to Psyclone at runtime, in the conten of a message;
the message type must be Psyclone.System.Create.Module.
(Destroy modules by sending a message type Psyclone.System.Destroy.Module,
with the name of the module as content, encapsulated in the
<module> tag but without
further content.)
Now Psyclone knows that when any message with type My.First.Type
is posted to the Whiteboard WB1, your module with the name MyFirstModule
should be woken up with this message. This will in turn automatically
call the MyFirstModuleCrank
function in the library cm you created above and hence print
"Hello World" to the console.
In most cases you probably want to do a bit more
in your module. The Messenger object which is passed to the function is
a communication device, which you can use to get the triggering message
and any other messages which may have been delivered if you asked for
it in the psySpec using the <retrieve> tag (see below).
If you post messages using the Messenger the messages are automatically
sent to the correct destinations.
Here is an example of a slightly more advanced
version of the crank function above:
int MyFirstModuleCrank (Messenger*
messenger) {
if (messenger == NULL)
return -1;
Message* triggerMessage;
Message* msg;
while (messenger->shouldContinueRunning()) {
if
(messenger->waitForNewMessage(50)) {
if ( (triggerMessage = messenger->getTriggerMessage()) != NULL) {
// Do something with the trigger message...
msg = new Message("", "", "");
messenger->addOutputMessage(msg);
messenger->sendOutputMessages();
}
}
}
return 0;
}
Now the module sends out one message each time it
is woken up. You may notice that instead of returning, the module
chooses to stay inside the function until the Messenger says that it
should not continue running any longer.
When this empty message is posted, the :from and
the :to fields
are automatically filled in and a copy is sent to every destination
Whiteboard defined in the psySpec, each with the correct type
filled in. For example, if the psySpec had defined two post
destinations
<posts>
<post to="WB1" type="My.Second.Type" />
<post to="WB1" type="My.Third.Type" />
</posts>
the WB1 Whiteboard would receive two messages, one
with type My.Second.Type and one with My.Third.Type.
If, however, the function had created a message
like this
msg
= new Message("", "", "My.Second.Type");
only one message would be sent out, as only the
first post matches the type. Likewise, had the message been created
with
msg
= new Message("", "", "My.Other.Type");
no message would be sent out.
If you wish to bypass the post section manually,
you can always do
msg
= new Message("", "MyOtherWB", "My.Other.Type");
in which case only one message would be sent to
the MyOtherWB Whiteboard.
When you in the psySpec specify which message
types will trigger your module you may wish to retrieve additional
messages from the same or other Whiteboards to be delivered along side
the triggering message:
...
<triggers>
<trigger
from="WB1" type="My.First.Type" />
</triggers>
<retrieves>
<retrieve
from="BBX" type="Input.Sens.UniM.Hear"/>
<retrieve
from="BBX" type="Input.Person.Found.True">
<latest>10</latest>
<lastmsec>200</lastmsec>
</retrieve>
</retrieves>
<crank name="cm::MyFirstModuleCrank" />
...
In your crank code, you can read these additional
messages, if any, by asking the Messenger object:
// Get the current number of retrieved messages
int
getRetrievedMessageCount();
// Get a
specific retrieved message
Message*
getRetrievedMessage(int pos);
// Get the
full collection of the retrieved messages
ObjectCollection* getAllRetrievedMessages();
Should you want to manually retrieve messages
from the crank without specifying this in the psySpec, you can ask the
Messenger object:
// Retrieve additional messages from a source
ObjectCollection* retrieveMessages(const
JString& retrieveFrom,
const JString& retrieveSpecXML);
where retrieveSpecXML uses the same format as you
would have used in the psySpec.
Note:
The nature of dynamically loaded libraries where they are loaded
and unloaded periodically means that static and global variables
loose their meaning. When creating internal crank functions
do not use global or static variables in your code. Furthermore,
on some UNIX systems such as Linux you will get errors when
trying to load a library with static or global variables, usually
complaining about an undefined symbol: __dso_handle.
If you see this, go through your code and remove all global
and static variables.
|
| Creating
Your Own CoreLibrary Objects |
|
Back
to Index |
|
|
All CoreLibrary
objects share a vast set of basic functionalities, and if you
wish to create your own objects it would be to your advantage
to create them as part of the CoreLibrary
using the Third Party Extension mechanism. The most important
benefit from this is that all CoreLibrary
objects are automatically converted from and to XML when sent
across the network. An example could be:
RELATED:
tutorial on SourceForge CoreLibrary
on SourceForge
|
|