How to Add a New Component to the EQUIP Component Toolkit
Chris Greenhalgh, 2004-06-03. v.3 2005-04-28
Building ECT
- Check out EQUIP4J from CVS
(:pserver:anonymous@dumas.mrl.nott.ac.uk:/mrl/src/cvsroot path
/Equator/equip4j
- Set up your environment for Java and ANT (set JAVA_HOME,
ANT_HOME, add java and ant bin dirs to PATH).
- Build the infrastructure - cd Equator/equip4j/infrastructure; ant
You might want to jump ahead to Running ECT with your component
to check that it is working.
Adding a Component
See also ECT_Component_Guidelines.html
- Make a new directory for your component under
Equator/equip4j/infrastructure/src/equip/ect/components
- Write you Component in that directory as a Java Bean with full
support for PropertyChangeListeners (consider using
java.beans.PropertyChangeSupport, and copying the relevant delegation
code from one of the other components; insert firePropertyChange calls
in all property setters). Apparently Vetoable properties do not work in
ECT at present (2004-06-03). You can add as many other supported
classes in that directory as you need.
- If your component has an idle and a running state then it is
probably safest to:
- name all idle-state configuration properties "config..."
- name the (boolean?!) property to start/run the component
"configured"
- name all run-state properties not starting with "config..."
- The persistence mechanisms (and users) will then know that they
should set all the config... properties first, then set configured,
then any other properties/use it
- Create a java.beans.BeanInfo class for your bean; its class name must be the same as your component
bean with 'BeanInfo' appended.
- Edit the ant build file,
Equator/equip4j/infrastructure/build.xml; copy the target for an
existing component (e.g. the Chat component), and change the names of
the target, jar and path to match your new component, e.g.
<!-- FOO COMPONENT TARGET -->
<target name="foo" depends="init, ectCore">
<!-- compile and jar the component -->
<antcall target="buildcomponent">
<param name="compdir" value="foo"/>
<param name="compclass" value="Foo"/>
</antcall>
<!-- dependencies...
copy jars to ${dist-common}, jars with javax classes to ${dist-common-ext},
DLLs to ${dist-common} -->
</target>
- Also add you new target to the depends part of the all target at
the
bottom of the file (comma separated list).
- Build again: cd Equator/equip4j/infrastructure; ant. It should
all compile :-)
Running ECT with your
component
Try it out - using the webstart version - double click in "ect.jnlp" in
the webstart directory, or the "runWebstart.bat" script. See ECT_Webstart_User_Guide.html
for more details.
Alternatively, use the individual scripts in
Equator/equip4j/infrastructure/install. If using scripts on a single
machine then you will need to:
- run the batch file 'runEquipDataSpace.bat' - to start a
dataspace, by default on port 9123 (equip://localhost:9123) and
multicast discoverable using group 'equip.ect.default'.
- run the batch file 'runExporterGUI.bat' - to start a Java
container. This should show your new component JAR along with the other
component jars. At present you will need to activate these individually
- select your component jar and click 'export capabilities'.
- run the batch file 'runGraphEditor.bat' - to start a
graphical component editor. This has two main windows, 'Capability
Browser' and 'Graph Component Editor'.
However you got started, you will now need to:
- In the Component Browser window expand the
'Capability Root' node and to find per-container sub-node(s); expand
one of these to reveal the Capability for your
new Component class.
- Right click on this Capability and select "Create Request" from
the popup menu. This should create a
ComponentRequest in the dataspace, which the container will see and
instantiate a new instance of your component class. Check the
container's console for diagnostic output from your component if
nothing happens ("Help" menu, "Show Console").
- (You can delete the ComponentRequest by expanding your
component's Capability node to reveal the created ComponentRequest
sub-node, right clicking on it and selecting "Delete request"
from the popup menu.)
- In the Graph Component Editor window you should see a rounded box
representing your component in the upper "Components" panel.
- Select this and then drag it down onto the lower "Editor" panel.
This will now reveal
your component's properties. The values should change to reflect the
underlying component (if they do not then you have probably not
supported
the PropertyChangeListener correctly).
- Try setting a property of your component - right click on the
property and select "Set value" from the popup menu; type a new value
into the dialog and press "Set". The editor will ask the component to
set that property to the specified value.
- Make some more components...
- To connect two component properties together, in the Graph
Component Editor's Editor panel click and drag from the source (origin)
property to the destination (target) property. This will create a
PropertyLinkRequest
which the destination property's container should implement, calling
the destination property's setter when the source property's value
changes.
- If you open the editor's optional "Property Link Browser" window
("View" menu) and select a linked property in the Editor view then an
entries should appear in the in/out table to show the other end(s) of
any links
applying to that property.
- You can delete a link by right clicking on it in the Editor panel
and selecting "Delete", or by selecting it in
the Property Link Browser table and choosing 'Delete selected links'
from the popup menu there.
More Advanced...
Making components with sub-components
Normal ("top-level") components are instantiated in response to
explicit requests. On some situations components should be instantiated
dynamically by the system, e.g. components which exclusively proxy
hardware devices (e.g. camera, smart it, phidget) should be
instantiated when the correponding hardware is detected. The
recommended way to do this is via sub-components...
- Create a component bean with bean info in the normal way to
represent a factory or manager (a single instance of this will be
requested to bootstrap the process - it will create the dynamic
sub-components).
- Give it a readonly property 'children' with a public getter,
the type of which is a 1D array of the sub-component type (e.g. if the
subcomponent class is 'MySubComponent' then the getter signature would
be "public MySubComponent[] getChildren()"). If there are no children
then a zero sized array (not null) must be returned.
- Fire the property change on 'children' whenever a child
component is added or removed (and update 'children' accordingly).
- When stopped ("public void stop()"), the manager component
should tidy up and destroy/stop all child components.
- Create a sub-component bean but WITHOUT bean info (otherwise the
current component loaded will get confused). Consequently, you must
stick to java bean idiom, e.g. all public getX/setX methods will be
exposed as properties.
- The sub-component class must implement Serializable.
- The sub-component will be instantiated by the manager
component, and may accept arguments from it, e.g. internal references
to interface objects.
- The sub-component should expose bean properties appropriate for
the device (of whatever), and fire its own property change events.
- Each sub-component should have a String-type readonly property
called "persistentChild", the value of which is unique to this
particular sub-component (within the scope of other sub-components of
this manager). Without this, or if it is not consistent between
re-creations of the subcomponent then persitent links will not work
to/from this sub-component.
See for example the subcomponenttest and smartitfactory components.
Making components persistent
The dataspace will persist Component Requests and Property Link
Requests. The container will also persist a components visible property
values, and try to re-set them when it is created. The configuration
manager allows components to be recreated in a similar way. Whereever
possible it is recommended that you make use of this form of
persistence, i.e. make the component's visible and settable properties
sufficient to recreate the component. See ECT_Component_Guidelines.html
for additional guidance on component life-cycle and configuration
property naming.
However, if this is not possible for some reason (e.g. much too much
state to expose as properties) then it is down to individual components
to persist their
own property values and/or internal state. The way to do this
at present is:
- Your component bean should implement "equip.ect.Persistable", and
read/write its state from/to the specified file when requested. The
file format is down to the bean itself. Standard Java serialisation can
be used, but also consider persistance across changes to the component
class.
At present the container persists all components at a regular interval.
At some point this should be extended with consideration to (a) not
persisting too often (b) components being able to request persisting
(e.g. after a configuration change).
Note that components that persist themselve will not currently be
recreated correctly by the configuration manager, since it does not
have access to these container- and component-specific persistence
files.
Making components remotely configurable
Components can be remotely configured in two main ways:
- By exposing input properties for configuration, which can be set
from other component properties via links. Advantage: configuration is
visible in the dataspace, and can be modified within the component
system. Disadvantage: relatively slow and restricted to strings and
simple types that have a coercion from string supported in ect.
- By exposing an embedded configuration web server, exposing the
URL to this server via a property. E.g. see the httpconfigtextvalue
component.
These should normally be combined, so that web server configuration is
reflected in properties and vice versa.
The visionframework beans illustrate a short-cut of exposing a single
configuration property which may be parsed to extract more complex
configuration. The disadvantage of this is a more complex configuration
language, and relatively help/feedback in using it.
At some point another ServiceUI approach could also be considered.
Making components that directly invoke methods on each other
This is currently only supported for components within the same JVM.
The component providing the methods to be called must provide a
readonly property whose value is an object which is NOT Serializable
and which implements the interface in question. The component which
will be calling these methods provides a readwrite property of the same
type.
When the user makes Property Link Request FROM the interface provider
TO the interface caller then a string key will be passed via the
dataspace, but a (weak) reference to the actual underlying Java object
will be set as the property value (provided, as noted, it is in the
same JVM). The link destination component will then be able to invoke
methods on the interface implemented by the source component.
See for example the visionframework, which pass video frames in this
way (via a callback handler to allow sources to split).