Short BonSAI Tutorial

Configuration

The BonSAI system must be configured, so that it knows how to contact the sensors and actuators.

<?xml version="1.0" encoding="utf-8"?>

<!--
Schema:
In order to be able to parse the configuration file, the BonSAI system needs to know the corresponding
schema. Place the schema file somewhere it can be read and enter the location here
 -->
<BonsaiConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="/vol/robocup/trunk/etc/schemas/BonsaiConfiguration.xsd">

    <!--
    General: Following order of entries must be satisfied: FactoryOptions, Sensors, Actuators
     -->

    <!--
    FactoryOptions:
    The system can work with different middlewares. In this case xcf is used and must be configured.
    Most options affect error handling and automatic reconnection behaviors for disappeared connections.
     -->
    <FactoryOptions factoryClass="de.unibi.airobots.bonsai.xcf.XcfFactory">
        <Option key="errorOnInitialSubscription">false</Option>
        <Option key="subscriberCheckInterval">30000</Option>
        <Option key="remoteServerCheckInterval">30000</Option>
    </FactoryOptions>

    <!--
    Sensor:
    A sensor receives data over the middleware. Each sensor has a unique key string which can
    be used in the Java code to identify a certain sensor configuration. 

    Attribute dataTypeClass: 
        defines the data type (ideally a BTL type) that will be received by this sensor.
    Attribute factoryClass: 
        defines the used middleware factory.
    Attribute sensorClass: 
        defines the implementation (not the interface) of the sensor inside 
        bonsai which matches the chosen middleware.

    Options: 
        Every sensor implementation can define a own set of possible options. In this case the 
        XcfBinarySlamSensor wants to know the name of the stream that publishes the slam map.
     -->
    <Sensor key="SlamSensor" dataTypeClass="de.unibi.airobots.btl.data.BinarySlamMap" 
        factoryClass="de.unibi.airobots.bonsai.xcf.XcfFactory" 
        sensorClass="de.unibi.airobots.bonsai.xcf.sensors.XcfBinarySlamSensor">
        <Options>
            <Option key="publisherName">SlamMap</Option>
        </Options>
    </Sensor>

    <!--
    The sensor class XcfBtlPublisherSensor can be used for all situations in which an arbitrary BTL type
     is published over a stream. You just have to specify the stream's name, the expected type and
     a unique key string.
     -->
    <Sensor key="PositionSensor" dataTypeClass="de.unibi.airobots.btl.data.PositionData" 
        factoryClass="de.unibi.airobots.bonsai.xcf.XcfFactory" 
        sensorClass="de.unibi.airobots.bonsai.xcf.sensors.XcfBtlPublisherSensor">
        <Options>
            <Option key="publisherName">interpolatedSlamPos</Option>
        </Options>
    </Sensor>

    <!--
    The sensor class XcfBtlMemorySensor is similar to XcfBtlPublisherSensor, but receives BTL types
     from insert and update events in an ActiveMemory. You just have to specify the memory's name, 
     the expected type and a unique key string.
     -->
    <Sensor key="GraspingSensor" dataTypeClass="de.unibi.airobots.btl.data.PointCloudGraspingList" 
        factoryClass="de.unibi.airobots.bonsai.xcf.XcfFactory" 
        sensorClass="de.unibi.airobots.bonsai.xcf.sensors.XcfBtlMemorySensor">
        <Options>
            <Option key="memoryName">ShortTerm</Option>
        </Options>
    </Sensor>

    <!--
    A sensor implementation may expect more than one option.
     -->
    <Sensor key="SayTaskSensor" dataTypeClass="de.unibi.airobots.btl.data.taskpattern.Say" 
        factoryClass="de.unibi.airobots.bonsai.xcf.XcfFactory" 
        sensorClass="de.unibi.airobots.bonsai.xcf.sensors.XcfTaskPatternSensor">
        <Options>
            <Option key="memoryName">ShortTerm</Option>
            <Option key="taskName">SAY</Option>
        </Options>
    </Sensor>

    <!--
    Actuator:
    A actuator sends data (requests, commands, actions) over the middleware. Each actuator has a 
    unique key string which can be used in the Java code to identify a certain actuator configuration. 

    Attribute factoryClass: 
        defines the used middleware factory.
    Attribute actuatorInterface: 
        defines the interface of the actuator inside bonsai which is independent
        from the chosen middleware.
    Attribute actuatorClass: 
        defines the implementation (not the interface) of the actor inside 
        bonsai which depends on the chosen middleware.

    Options: 
        Every actuator implementation can define a own set of possible options. In this case the 
        NavigationActuator wants to know the name of the server that can receive navigation commands.
     -->
    <Actuator key="NavigationActuator" factoryClass="de.unibi.airobots.bonsai.xcf.XcfFactory" 
        actuatorInterface="de.unibi.airobots.bonsai.actuators.NavigationActuator" 
        actuatorClass="de.unibi.airobots.bonsai.xcf.actuators.XcfNavigationActuator">
        <Options>
            <Option key="serverName">Sunflower</Option>
        </Options>
    </Actuator>

    <Actuator key="SpeechActuator" factoryClass="de.unibi.airobots.bonsai.xcf.XcfFactory" 
        actuatorInterface="de.unibi.airobots.bonsai.actuators.SpeechActuator" 
        actuatorClass="de.unibi.airobots.bonsai.xcf.actuators.XcfSpeechActuator">
        <Options>
            <Option key="serverName">SaySrv</Option>
        </Options>
    </Actuator>

</BonsaiConfiguration>

Using the bonsai interface in Java

package de.unibi.airobots.bonsai.examples;

import de.unibi.airobots.bonsai.actuators.*;
import de.unibi.airobots.bonsai.core.*;
import de.unibi.airobots.btl.data.*;

/**
 * This Class contains an example showing a way to control Biron with a "remote" 
 * (actually your remote is a GUI). It's containing basic operations like making
 * him move, turn or say something.
 */
public class BasicRemote {

    public static void main(String[] args) {
        try {
            new BasicRemote();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * Initialization of actuators and sensors
     */
    private Sensor<BinarySlamMap> slamSensor;
    private Sensor<PositionData> positionSensor;
    private NavigationActuator navigationActuator;
    private SpeechActuator speechActuator;

    private static final Logger LOGGER = Logger.getLogger(BasicRemote.class);

    public BasicRemote() {

        /*
         * While programming Biron one can send information to the logger. First
         * of all you need to initialize the logger and configure it.
         */
        BasicConfigurator.configure();

        /*
         * As there may be different situations in which something is logged
         * there are different logging levels. For example there is:
         * .info(message) .debug(message) .fatal(message) .error(message)
         * .warn(message) Moreover one can configure the logger and set a level
         * so that it will only show logged messages in this level.
         */
        LOGGER.info("setting up...");

        /*
         * Each actuator and sensor that is configured by the configuration xml can be requested
         * from the BonsaiManager. You just have to know the key string and the corresponding data type.
         * Concrete implementation details or middleware dependencies are completely hidden.
         */
        LOGGER.info("... BonsaiManager ...");

        BonsaiManager.getInstance().configure("/vol/robocup/trunk/etc/bonsai_config/basicremote.xml");

        LOGGER.info("... Sensors and Actuators ...");

        slamSensor = BonsaiManager.getInstance().createSensor("SlamSensor", BinarySlamMap.class);
        positionSensor = BonsaiManager.getInstance().createSensor("PositionSensor", PositionData.class);
        navigationActuator = BonsaiManager.getInstance().createActuator( "NavigationActuator", NavigationActuator.class);
        speechActuator = BonsaiManager.getInstance().createActuator("SpeechActuator", SpeechActuator.class);

        /*
         * You can pass around sensors and actuators
         */
        observeMapAndUpdateGui(slamSensor);

        LOGGER.info("... done");
    }

    public void actionPerformed(ActionEvent e) {

        try {

            /*
             * The following else ifs contain basic functions to control Biron.
             * As the interaction with Biron is often the same only one example
             * is explained.
             */

            if (e.getActionCommand().equals("Forward")) {

                /*
                 * First of all you have to create a goal for Biron. This is
                 * done by creating a NavigationGoalData object by using the
                 * CoordinateSystemConverter like below. It needs the current
                 * global position of the robot obtained from the
                 * PositionSensor, the angle and distance of the new goal. In
                 * addition to that it is possible to give a coordinate and yaw
                 * tolerance but by default these are set to zero.
                 * 
                 * Then you have to set the goal by delivering it to the
                 * navigation actuator via .setGoal(goal).
                 * 
                 * In case the navigation actuator is unavailable there is a
                 * try-catch surrounding the instructions.
                 */
                PositionData pos = positionSensor.readLast();
                NavigationGoalData g = CoordinateSystemConverter.polar2NavigationGoalData(pos, 0.0, 1.0);
                g.setYawTolerance(0.1 * Math.PI);
                navigationActuator.setGoal(g);

            } else if (e.getActionCommand().equals("Backward")) {

                PositionData pos = positionSensor.readLast();
                NavigationGoalData g = CoordinateSystemConverter.polar2NavigationGoalData(pos, Math.PI, 1.0);
                g.setYawTolerance(0.1 * Math.PI);
                navigationActuator.setGoal(g);

            } else if (e.getActionCommand().equals("Left")) {

                PositionData pos = positionSensor.readLast();
                NavigationGoalData g = CoordinateSystemConverter.polar2NavigationGoalData(pos, Math.PI / 2, 1.0);
                g.setYawTolerance(0.1 * Math.PI);
                navigationActuator.setGoal(g);

            } else if (e.getActionCommand().equals("Right")) {

                PositionData pos = positionSensor.readLast();
                NavigationGoalData g = CoordinateSystemConverter.polar2NavigationGoalData(pos, -Math.PI / 2, 1.0);
                g.setYawTolerance(0.1 * Math.PI);
                navigationActuator.setGoal(g);

            } else if (e.getActionCommand().equals("STOP")) {

                navigationActuator.manualStop();

            } else if (e.getActionCommand().equals("HELLO")) {

                speechActuator.say("Hello, would you like to answer some questions?");

            } else if (e.getActionCommand().equals("BYE")) {

                speechActuator.say("Okay, have a nice evening!");

            }

        } catch (Exception ex) {
            LOGGER.error("Error performing action", ex);
        }
    }
}