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

 


Writing a psySpec Back to Index

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

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

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.

 

 

 

 
2006©Communicative Machines Inc. License