Next: Bibliography
Up: Infrastructure for an Intelligent
Previous: Analysis and Conclusions
Subsections
Documentation
RDF for Configuration
------------
| OVERVIEW |
------------
Hive configuration files allow you to do a number of things:
o Specify which shadows and agents get automatically started
o Specify communication channel parameters (baud rate, etc)
o Specify other agent or shadow configuration
o Specify a different PPM icon for a particular instance of an agent
o Specify semantic descriptions for agents and shadows
This makes it easy to create an application built on top of Hive that
just involves running Hive, and requires no manipulation or
configuration through the GUI. This is useful for demos so that you
can avoid clicking through multiple PropertySheet's and the like.
These files are stored in a single directory, be default .hive in your
home directory. A different directory can be specified on the
command line with the -configdir option. For example:
"java edu.mit.media.hive.server.Server -configdir=/home/mkgray/democonfig"
-------------------------
| STANDARD CONFIG FILES |
-------------------------
There are two default hive configuration files plus one per shadow or
agent that you want to have custom configuration for. This section
describes the two default files. The format of the per shadow/agent
config files is in the next section.
The default filename for the first is "agentConfig", but can be
specified to be something else with the -agentconfig command line
option. The default filename for the other is "shadowConfig", and can
be specified to be something else by the -shadowconfig command line
option.
Both of these files have the same format. On each line, an agent or
shadow class is specified and the name of an RDF configuration file is
listed. A line beginning with a '#' is considered a comment. Class
names must be fully qualified. For example, an agentConfig file to
start up a running graph agent with the configuration file mygraph.rdf
would look like this:
------- cut here-------
# Just start up my running graph agent
edu.mit.media.hive.agent.desktop.RunningGraphAgentImpl mygraph.rdf
------- cut here-------
If you wanted to start up multiple running graph agents, with
different configurations, you'd do something like this:
------- cut here-------
# Start up three running graph agents
#
# Start two graphs with my standard settings
edu.mit.media.hive.agent.desktop.RunningGraphAgentImpl mygraph.rdf
edu.mit.media.hive.agent.desktop.RunningGraphAgentImpl mygraph.rdf
#
# And start another with my other settings
edu.mit.media.hive.agent.desktop.RunningGraphAgentImpl myothergraph.rdf
------- cut here-------
The shadowConfig file is the same. For example, if you wanted to
start two cricket shadows, running on different serial ports:
------- cut here-------
# Start up our cricket shadows
edu.mit.media.hive.shadows.CricketShadow cricket-com1-config.rdf
edu.mit.media.hive.shadows.CricketShadow cricket-com2-config.rdf
------- cut here-------
One added important piece is that if you use a shadowConfig file,
these are THE ONLY SHADOWS that will ever be allowed to be started.
For example, if you use the above configuration and then try to start
an agent that tries to use another shadow (say, a QuickCam shadow), it
will be forbidden. So, be sure to specify all shadows you want
started. You are still free to start arbitrary agents.
-------------
| RDF FILES |
-------------
The RDF files specified in the agentConfig and shadowConfig files
determine the configuration for each individual agent or shadow.
These files are found in your hive config directory.
All Hive RDF files should contain the following at the beginning of the file:
------- cut here --------
<?xml version="1.0"?>
<RDF
xmlns='http://www.w3.org/TR/WD-rdf-syntax#'
xmlns:RDF='http://www.w3.org/TR/WD-rdf-syntax#'
xmlns:thing='http://www.media.mit.edu/hive-syntax#'>
<Description about="">
------- cut here --------
and at the end of the file:
------- cut here --------
</Description>
</RDF>
------- cut here --------
The rest of this section will describe what goes in between these. In
order to specify a configuration parameter for a agent or shadow, you
use something like the following:
<thing:config thing:iconPPMName="scale.ppm"
thing:sampleRate=".5"/>
This specifies that the iconPPMName is "scale.ppm" and that the
sampleRate is ".5".
To configure a serial port is slightly more complicated:
<thing:config thing:parameterFoo="blah">
<Description>
<thing:channel thing:baudRate="9600"/>
</Description>
</thing:config>
This sets parameterFoo to "blah", and the baud rate of the serial port
used by this shadow to 9600.
Now, how does an agent or shadow use the values specified in these
config files? By using Java Beans. For example, in the previous
example, an agent would need to implement two methods:
public String getParameterFoo();
public void setParameterFoo(String s);
and, automatically setParameterFoo would be called with the value from
the config file. This happens between agent construction and the call
to arriveAt(), so the agent will not even have a Server reference yet
when it is configured, so best practice is to store away the value
when setParameter(...) is called, and use it in arriveAt() or
doBehavior(). Configuration parameters may be String's, int's,
float's, double's, boolean's and anything else there is a
PropertyEditor for (java defines the ones listed by default).
To allow serial port or other LocalChannel configuration, you don't
need to do anything other than specify the parameters in the config
file, if you've used the standard Hive serial port and external
process support.
The configurable parameters for a serial port channel are:
Parameter Value
--------- -----
baudRate 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
serialPort 1, 2, 3, ... (note these correspond to COM1, COM2, ...
or ttyS0, ttyS1, ttyS2, ...)
The configurable parameter for a process channel is:
Parameter Value
--------- -----
processCommand a string specifying the external command to run
Additionally, AgentImpl has one default configurable parameter:
Parameter Value
--------- -----
iconPPMName A string specifying the filename of a 32x32 raw PPM
(this file should be in your hive config dir)
Finally, an RDF file may be used to provide a semantic description of
the agent or shadow. This is a substantial topic in and of itself,
and will be addressed separately, at another time.
------------
| EXAMPLES |
------------
Here is a set of sample configuration files that would work together,
with descriptions of what they do.
===== agentConfig =====
# Create a couple of agents
edu.mit.media.hive.agent.desktop.RunningGraphAgentImpl mygraph.rdf
edu.mit.media.hive.agent.thing.ScaleAgent scale.rdf
=======================
===== shadowConfig ====
# Start the shadow for the scale
edu.mit.media.hive.shadows.TranscellScale transcell.rdf
=======================
This configures the graph to update every half second, and have a
scale of .0625 (1/16) so that when hooked up to the scale it will show
a graph of ounces over time. "updateInterval" and "scaleFactor" are
parameters that the RunningGraphAgentImpl accepts.
===== mygraph.rdf =====
<?xml version="1.0"?>
<RDF
xmlns='http://www.w3.org/TR/WD-rdf-syntax#'
xmlns:RDF='http://www.w3.org/TR/WD-rdf-syntax#'
xmlns:thing='http://www.media.mit.edu/hive-syntax#'>
<Description about="">
<thing:config
thing:updateInterval="500"
thing:scaleFactor="0.0625"/>
</Description>
</RDF>
=======================
This just specifies an alternate PPM for the scale
====== scale.rdf ======
<?xml version="1.0"?>
<RDF
xmlns='http://www.w3.org/TR/WD-rdf-syntax#'
xmlns:RDF='http://www.w3.org/TR/WD-rdf-syntax#'
xmlns:thing='http://www.media.mit.edu/hive-syntax#'>
<Description about="">
<thing:config
thing:iconPPMName="scale.ppm"/>
</Description>
</RDF>
=======================
Configure the scale to be on port 1 at 9600 baud.
This file isn't actually necessary, since port 1 and
9600 baud are the defaults.
==== transcell.rdf ====
<?xml version="1.0"?>
<RDF
xmlns='http://www.w3.org/TR/WD-rdf-syntax#'
xmlns:RDF='http://www.w3.org/TR/WD-rdf-syntax#'
xmlns:thing='http://www.media.mit.edu/hive-syntax#'>
<Description about="">
<thing:config>
<Description>
<thing:channel
thing:baudRate="9600"
thing:serialPort="1"/>
</Description>
</RDF>
=======================
How to use semantic descriptions
HOWTO use semantic descriptions in Hive
---------------------------------------
INTRO
-----
Semantic descriptions in Hive are based on RDF. This means everything
is a directed labeled graph. Using the Lookup utility class or the
DescSet.select() method, you can query these graphs and find resources
that match your requirements.
SAMPLE
------
Here's a sample RDF file, describing a camera in room 468:
<?xml version="1.0"?>
<RDF
xmlns='http://www.w3.org/TR/WD-rdf-syntax#'
xmlns:RDF='http://www.w3.org/TR/WD-rdf-syntax#'
xmlns:thing='http://www.media.mit.edu/hive-syntax#'>
<Description about=""
thing:nickname="Pia Quickcam">
<thing:config thing:command="cqcam"/>
<thing:location
thing:building="E15"
thing:room="468"/>
<thing:role>
<Description>
<thing:camera thing:kind="QuickCam"/>
</Description>
</thing:role>
</Description>
</RDF>
The rdf statements this makes are:
The "nickname" of (the described object) is "Pia Quickcam"
The "config" of (the described object) is FOO
The "command" of FOO is "cqcam"
The "location" of (the described object) is BAR
The "building" of BAR is "E15"
The "room" of BAR is "468"
The "role" of (the described object) is BAZ
The "camera" of BAZ is QUUX
The "kind" of QUUX is "QuickCam"
Note, it says about="". Leave this as is, and Hive will automatically
generate the correct object associations. Putting something else in
the about field may produce strange results.
This may seem like a mess (and it is), and in general you won't need
to think about things this way to generate descriptions, or query
them, but it is useful to know when starting out.
IN GENERAL
----------
In general, to discover a new agent, you would do one of the following
two things. Say you wanted to find a camera, such as the one
described above. You might do:
Agent a = Lookup.findAgentMatch(myServer, this,
"edu.mit.media.hive.EventTranslatingAgent",
Description.HIVE+"camera");
This will give you the first agent it finds that is of syntactic type
EventTranslatingAgent and has the role of camera. The
"Description.HIVE" prefix, is the RDF name space stuff, which I explain
in the details below. If you don't care about the details, always
prefix types with it.
If instead you want to find all agents in room 468, you might do:
DescSet set = Lookup.findAgentMatch(myServer, this, null, null);
set = Lookup.require(set, Description.HIVE+"location",
Description.HIVE+"room",
"468");
These lines say, correspondingly "Give me all the agents on this
server", and "Give me all the agents whose location has a room of
468". (note, this code will give agents in room 468 of any building)
Then if you want an enumeration of the matching objects you would do:
Enumeration e= set.elements();
Or, if you want the first match, you would do:
Object o = set.elementAt(0);
If you want the number of matches, you would do:
int n = set.matches();
DETAILS
-------
So, how do you discover a particular agent? For agents, you do so by
calling queryAgents(...). For example, to get all of the
EventSendingAgent's on the local cell, you could do the "raw" query:
String[] esa = { "edu.mit.media.hive.agent.EventSendingAgent" };
DescSet mySet = myServer.queryAgents(this, esa, null);
or, more likely, to save you the trouble of creating the temporary array,
you use the utility method in Lookup:
DescSet mySet = Lookup.getAgentSet(myServer, this,
"edu.mit.media.hive.agentEventSendingAgent",
null);
queryAgents and getAgentSet each take arguments of the caller, a
string array or string corresponding to the agent syntactic type you
want, and a string array or string corresponding to the agent semantic
role you want. Either of these may be null to match any. getAgentSet
also has as a first argument the RemoteServer to query.
[Note: in all the examples from here on in, I'm not prefacing types with
Description.HIVE or any other name space prefix. This is because
it's messy looking and I don't feel like it, not because it isn't
needed]
Now you have a DescSet object which is a set of 0 or more descriptions
which can be queried and manipulated. Now, say you want to select the
agent from this set that has the nickname "Pia Quickcam". To do so,
you would do:
DescSet mySet = mySet.select(Description.HIVE+"nickname", "Pia Quickcam");
Select takes two arguments, the label of the RDF graph edge to follow,
and a second optional argument of the required value of that node. To
clarify, here's another more complicated example:
set = set.select("owner"); // This selects the owner of each item
set = set.select("birthday"); // This selects their birthdays
set = set.select("month", "July"); // This selects those whose birthday is in
// July
Each select causes the new, potentially pared down, set to be
returned. If a description in the set does not have an owner at all,
the first call will eliminate it. The second will eliminate all owners
that don't have birthdays, and the third will eliminate all owners
whose birthdays whose months are not "July". Get it?
As you may have inferred by now, there is a "context" in a set, so
that you can do structured queries like the above one. What if in the
above scenario we wanted to, after all this, select only objects in
E15. If we immediately did:
set = set.select("location");
set = set.select("building", "E15");
That would be equivalent to having said: Give me all objects which
have a owner which has a birthday which has a month equal to "July"
which has a location which has a building equal to "E15". Since a
month does not have a location, and that's not what you meant, you
have to do something else. So, you have to reset the context. You do
this by doing:
set.noContext()
Note, this has no return value, it actually changes set, rather than
return a new one. Eventually I'll clean up the API to be more
consistent, but deal for now. So, the total sequence of:
set = set.select("owner"); // This selects the owner of each item
set = set.select("birthday"); // This selects their birthdays
set = set.select("month", "July"); // This selects those whose birthday is in
// July
set.noContext();
set = set.select("location");
set = set.select("building", "E15");
This has the total effect of what was originally intended.
Additionally, DescSet has another method upContext(), which causes the
context to be moved up one level.
Now, say you want to do something with the values rather than exact
match, like get all the agents in even numbered rooms. To do that,
you would have to do the following once you got your DescSet mySet:
mySet = mySet.select("location");
mySet = mySet.select("room");
DescSet newset = new DescSet();
for(int i=0; i < mySet.matches(); i++){
if(isStringEven(mySet.getValue(i))){
Object m = mySet.elementAt(i);
// Do whatever you want with the match m, like
// add it to a Vector or whatever, or maybe put its
// description back into a new descset for more queries:
newset.addDesc(m.getDescription());
}
}
Now, what if you want to look at the description of an object that you
already have? At the moment, this requires putting it into a descset
and manipulating it as above. Say I have an Agent foo (or any object that
implements Describable), I do:
DescSet set = new DescSet();
set.addDesc(foo.getDescription())
If you have a Vector of Describables, you can just throw that in the
constructor:
Vector foo = new Vector();
// Add some describables to foo
DescSet set = new DescSet(foo);
From this point you can manipulate the set as above.
Now, what do you do if you have multiple DescSet's and you want to
mush them together? (eg, you query multiple servers, and now want to
find all objects in room 468, on all the servers you queried) You use
DescSet.merge():
DescSet everything = new DescSet();
DescSet foo = Lookup.getAgentSet(server1, this, null, null);
everything.merge(foo);
foo = Lookup.getAgentSet(server2, this, null, null);
everything.merge(foo);
foo = Lookup.getAgentSet(server3, this, null, null);
everything.merge(foo);
foo = Lookup.getAgentSet(server4, this, null, null);
everything.merge(foo);
This will cause the DescSet "everything" to contain the merged results
from all 4 queries. merge(...) will purge duplicates should the
occasion arise where you try to merge a set which contains repeats.
That is, set.merge(set) is a no-op.
The Lookup class has a number of utility functions to make doing this
sort of thing slightly easier, but this utility class is incomplete.
But, you can do anything with DescSet, if in a few more API calls.
Here's the relevant/useful methods on DescSet:
DescSet(Vector v)
Construct a DescSet from a Vector of Describable's
DescSet()
Construct a new empty DescSet
int matches()
Number of objects that match the current query
Enumeration elements()
An enumeration of the objects that match
Object firstElement()
Get the first match
Object elementAt(int i)
Get the object match at index i
Object pickOne()
Randomly select one of the matches.
String getValue(int i)
Get the value of the current query in the _description_
(ie, if you've selected the name of the owner, this will give
you the name of the owner, while elementAt() would give you
the object itself)
DescSet select(String p)
Prune the set to objects that have a parameter p in their
description and current context
DescSet select(String p, String v)
Prune the set to objects that have a parameter p with value v
in their description and current context
void merge(DescSet s)
Merge s into the current set
void addDesc(Description d)
Add a description to the set
void removeDesc(Description d)
Remove a description from the set
void upContext()
move the context "back" one
void noContext()
Reset the context
Matthew K Gray
1999-05-14