|
| Writing
a psySpec |
Back to Index |
definition_:: |
| 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.
Components can be created
and destroyed at runtime by sending a message of type Psyclone.System.Create.[component-type],
where component-type is one of {Module, Whiteboard, Stream}.
The
psySpec is read in by doing:
psyclone port=[port] spec="[psySpecName]"
Example:
>./psyclone
port=10000 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.
Port defaults to 10000 if not specified.
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 (or dynamically), 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
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 |
definition_:: |
|
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.
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,
1000)) != 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 as C++, 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.
Alternatively,
you can use the stream in Java in callback mode with
stream1.startContinuousBackgroundReceive(this)
where 'this' has to implement the MessageReceiver interface.
Every time a new sample appears on the stream, this.receiveMessage(Message
msg) will be called:
public Message receiveMessage(Message msg) {
if ((msg == null) || (msg.object == null))
return new Message(msg.to, msg.from,
"RECEIVE_ERROR");
DataSample sample = (DataSample) msg.object;
// compute
return new Message(msg.to, msg.from, "RECEIVE_ACCEPT");
}
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
Catalogs |
Back
to Index |
|
Catalogs
are components that can provide access to large amounts of data
and they have a query mechanism built in. This sections hows
how to use the built-in Catalogs FileCatalog
and Persistent Record Storage Catalog,
which are built into all versions of
Psyclone.
If you wish to create your own catalogs (examples
could be a Facial Expression Database or Google Query Catalog)
please contact us.
FileCatalog
The
FileCatalog allows a module to read and write files from a named location specified
in the PsySpec. It uses the generic Psyclone catalog functionality but is built
directly into Psyclone and therefore always available to be used by any module,
internal or external. This way you can in the
psySpec specify a directory location from which all requested files should
be read and to which all requested files will be written where the location is specified
in the psySpec and not in the code itself.
To use
this Catalog a definition is put into the
psySpec:
<catalog name="MyFileCatalog" query="Internal::FileCatalog"
location="." readonly="no"
/>
An example could be specifying one directory
for static read-only configuration files:
<catalog name="ConfigurationCatalog" query="Internal::FileCatalog" location="../conf"
readonly="yes" />
and another for output files from your application:
<catalog name="OutputCatalog" query="Internal::FileCatalog" location="../output"
readonly="no" />
The location is always relative to the current directory, i.e. the directory which
was the current working directory when Psyclone was started.
Now any module can use the catalog. Here is a concrete example for code written
for the FileCatalog in C++:
Message* msg = new Message();
msg->setType("Write");
msg->set("Filename", "MyFileName");
msg->setContent("My File Contents, bla bla ",
"");
InfoItem* answer = messenger->queryCatalog("MyFileCatalog",
msg);
if (answer == NULL)
error = "No Catalog by that name";
else if (!answer->getEntry("Status").equalsIgnoreCase("Success"))
error = "Catalog reported an error,
look at answer to see why";
else
error = "Wrote file";
delete(answer);
This will write the content "My File Contents, bla bla " to
a file called "MyFileName". Similarly, you can read a file using
the type "Read" instead of "Write".
See section Coding
Clients for Catalogs below on how
to write code in C++ and Java for using Catalogs.
Commands are set as the message type, parameters are set using the set(param,
value) function and the content to be written is added to the
content of the message.
The following commands are available for the FileCatalog:
- Command "Read"
- Param: <filename>
- Answer: answer.getEntry("Content") contains the file content
- Command "ReadBinary"
- Param: <filename>
- Answer: DataSample object in answer.object
- Command "Write"
- Param: <filename>
- Content: Text content to write (use msg->setContent(text, ""))
- Command "WriteBinary"
- Param: <filename>
- Content: DataSample object with binary data to write (use msg->setObject(sample))
Persistent
Record Storage Catalog
The Persistent Record Storage Catalog (StorageCatalog
for short) allows a module to save persistent data that can be reloaded again after
a Psyclone shutdown. It is built into Psyclone and therefore available to be used
by any module, internal or external.
To use this Catalog a definition is put into the
psySpec:
<catalog name="MyStorageCatalog"
query="Internal::StorageCatalog"
location="." index="index.xml"
/>
This example will store your Persistent Record Storage Catalog entries in the directory
"." and create or use the database index file called index.xml. By using this catalog
you can now save data in the form of InfoItem objects in the database which can
be retrieved at any time by the same or any other module, even after a Psyclone
restart.
Now any module can use the catalog. Here is a concrete example for code written
for the StorageCatalog in C++:
InfoItem* myItem = new InfoItem(); // contains the data to be stored
Message* msg = new Message();
msg->setType("StoreRecord");
msg->set("Name", "MyRecordEntry");
msg->setTime("Time", JTime());
msg->setObject(myItem);
InfoItem* answer = messenger->queryCatalog("MyStorageCatalog", msg);
if (answer == NULL)
error = "No Catalog by that name";
else if (!answer->getEntry("Status").equalsIgnoreCase("Success"))
error = "Catalog reported an error, look at answer to see why";
else
error = "Stored entry";
delete(answer);
This example stores the InfoItem object myItem in the database under the
name "MyRecordEntry". This or any other module can at any time
retrieve this data by using the command "GetRecord" on the same
catalog, even after Psyclone has restarted. If someone stores
a record with the same name, the entry is overwritten.
See section Coding
Clients for Catalogs below on how
to write code in C++ and Java for using Catalogs.
To use the StorageCatalog, use these commands:
- Command "GetRecords"
- Answer: answer.object with collection of entries (ObjectCollection
with timestamps)
- Command "GetRecord"
- Param: "Name" name of record to get
- Param: "Time" msg->setTime("Time",
time)
- Time is optional, if not provided returns the last written
record for name
- Answer: answer.object (InfoItem object)
- Command "StoreRecord"
- Param: "Name" name of record to store
- Param: "Time" msg->setTime("Time",
time)
- optional, if not provided, uses 'now' to store
record time
- Content: InfoItem object to be stored (use msg->setObject(item))
- Command "DeleteRecord"
- Param: "Name" name of record to store
- Param: "Time" msg->setTime("Time",
time)
- if provided, deletes only that time entry for named
record
- if not provided, deletes full history of named record
- Command "DeleteDatabase"
- Deletes all named records and all files from disk
- Warning: This command deletes everything!
Coding
Clients for Catalogs
The following shows examples of how to code clients to use
Catalogs:
Internal
Module (C++):
Message* msg = new Message();
msg->setType("<command>");
msg->set("<param1>", "Param1");
msg->set("<param2>", "Param2");
InfoItem* answer = messenger->queryCatalog("<Catalog>",
msg);
if (answer == NULL)
error = "No Catalog by that name";
else if (!answer->getEntry("Status").equalsIgnoreCase("Success"))
error = "Catalog reported an error,
look at answer to see why";
else
error = "Success, look at answer for
results";
delete(answer);
External
module, C++:
Message* msg = new Message();
msg->setType("<command>");
msg->set("<param1>", "Param1");
msg->set("<param2>", "Param2");
InfoItem* answer = plug->queryCatalog("<Catalog>",
msg);
if (answer == NULL)
error = "No Catalog by that name";
else if (!answer->getEntry("Status").equalsIgnoreCase("Success"))
error = "Catalog reported an error,
look at answer to see why";
else
error = "Success, look at answer for
results";
delete(answer);
External
module in Java:
Message msg = new Message();
msg.type = "<command>";
msg.set("<param1>", "Param1");
msg.set("<param2>", "Param2");
InfoItem answer = plug.queryCatalog("<Catalog>",
msg);
if (answer == null)
error = "No Catalog by that name";
else if (!answer.getEntry("Status").equalsIgnoreCase("Success"))
error = "Catalog reported an error,
look at answer to see why";
else
error = "Success, look at answer for
results";
RELATED:
psySpec |
| Using
contexts |
Back
to Index |
|
| Contexts
are used to manage a collection of modules by simultaneously
turning them on and off, and to make them behave differently
in different situations. Contexts are very helpful 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 can be context-driven in that each module can
be set up to have 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.
If no context is specified
for a module they default to belong to the context Psyclone.System.Ready.
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"
type="internal">
<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"
type="external">
<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. Phases are grouped by
context —
each set of phases belongs to a particular context.
The simplest way to think about phases is like
a sequencer. Here is an example of this idea:
<module name="Sequencer"
type="internal" allowselftriggering="yes">
<phases>
<phase name="One">
<trigger
name="Sequence.Start"
from="WB1" after="2000">
<post
name="Sequence.Step.One"
to="WB1">
</phase>
<phase name="Two">
<trigger
name="Sequence.Step.One"
from="WB1" after="2000">
<post
name="Sequence.Step.Two"
to="WB1">
</phase>
<phase name="Three">
<trigger
name="Sequence.Step.Two"
from="WB1" after="2000">
<post
name="Sequence.Step.Three"
to="WB1">
<post
name="Sequence.Start"
to="WB1">
</phase>
</phases>
</module>
In this module, which is self-triggering, each
phase will trigger the next phase. Whenever a module posts it
will switch phase (this will happen by default unless the changephase
parameter is set to false in the posted message).
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" type="external">
<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"
type="internal">
<phase name="Send
first message group">
<crank name="cm1::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="cm2::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>
where cm1 and cm2 are dynamically loaded into
Psyclone at startup. 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"
type="external">
<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, internal
and external, can have an unlimited number of parameters, which
the module itself can control, but which other modules can interact
with as well, both via a direct function call and via
(low-level) messages, as explained below. Parameters work
the same for both internal as well as external modules in all
programming languages and the getParameter()
function returns the same value for internal and external modules,
local or remote.
Parameters are defined in the psySpec:
<module name="VideoServer1.Camera1"
type="internal"
>
<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>
In this example the module VideoServer1.Camera1
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.
To set a parameter using
a message, send the following message to the module (put the
module's name in the cc field
of the message):
message type = "PARAM_SET"
message content = "myparam=myvalue"
This is a standard Psyclone lowlevel command, using the low-level
OpenAIR message API.
|
| 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);
The modules that share a Plug need to be in the same executable.
|
|