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