Robotics Tutorial 3 (C#) - Creating Reusable Orchestration Services

Glossary Item Box

Microsoft Robotics Developer Studio Send feedback on this topic

Robotics Tutorial 3 (C#) - Creating Reusable Orchestration Services

Microsoft Robotics Developer Studio (RDS) provides a re-usable design for writing services. This design allows you to write a service once to a common hardware specification and then use that service across a variety of hardware robotic platforms.

Figure 1

Figure 1 - Simple wander service

This tutorial teaches you how to create a service that partners with abstract, base definitions of hardware services. Then based on a configuration file (a manifest), that service binds at runtime to a specific implementation of these hardware services. The tutorial implements a very basic wandering behavior:

  • If the front bumper is pressed, rotate randomly, then apply equal motor power to both wheels using negative values (reverse motors)
  • If the rear bumper is pressed, rotate randomly, then apply equal motor power to both wheels using positive values

This very primitive behavior will result in the wheeled robot bumping and turning into things. For a more intelligent (and more advanced orchestration service) please review Robotics Tutorial 5 (C#) - Using Advanced Services and the Explorer Service.

This tutorial is provided in the C# language. You can find the project files for this tutorial at the following location under the Microsoft Robotics Developer Studio installation folder:

Samples\RoboticsTutorials\Tutorial3\CSharp

This tutorial teaches you how to:

  • Use Partnerships on Abstract Services.
  • Use the Contact Sensor as a Bumper to Trigger Behavior.
  • Issue Drive Commands to Turn and Move.
  • Use a Hardware Manifest with Robotics Tutorial 3.

See Also:

  • Getting Started
  • Try It Out

Prerequisites

Hardware

You need a robot with microcontroller and a contact sensor. The sensor can also be distance detection devices (such as sonar and infrared sensors) that provide a simple binary signal when a particular threshold is detected. Configure your contact sensors so that one is at the front and the other is at the rear.

This tutorial also requires two motors in a two-wheeled differential/skid drive configuration.

Connect the sensors and motors to your robotic platform following the normal conventions for the hardware you are using.

To determine if support is included in RDS for your robot and to setup your hardware, see Setting Up Your Hardware. You may be able to apply this tutorial for other robots that provide similar services (or create your own services by performing the Service Tutorials included in RDS). Setting up Your Hardware may also provide you with any recommended guidelines for setting up your PC to communicate with your robot.

Software

This tutorial is designed for use with Microsoft Visual C#. You can use:

  • Microsoft Visual C# Express Edition.
  • Microsoft Visual Studio Standard, Professional, or Team Edition.

You will also need Microsoft Internet Explorer or another conventional web browser.

Getting Started

We will not create a service for this tutorial. Instead, we walk through an existing service installed in samples\RoboticsTutorials\Tutorial3\CSharp.

Start the development environment and load RoboticsTutorial3.csproj file. If you are using Microsoft Visual Studio, you should see the following in the Solution Explorer of your VS window. Notice that this service uses common definitions defined in RoboticsCommon.Proxy.dll. This DLL exists as a project reference.

Figure 2

Figure 2 - Make sure RoboticsCommon.Proxy.dll is referenced in the service project.

Open the RoboticsTutorial3.cs file.

Step 1: Use Partnerships on Abstract Services

At the top of the RoboticsTutorial3.cs, the following using statements have been added.

using contactsensor = Microsoft.Robotics.Services.ContactSensor.Proxy;
using drive = Microsoft.Robotics.Services.Drive.Proxy;
using motor = Microsoft.Robotics.Services.Motor.Proxy;

The following partnerships are already added in the service class definition:

[Partner("Bumper",
    Contract = contactsensor.Contract.Identifier,
    CreationPolicy = PartnerCreationPolicy.UseExisting)]
contactsensor.ContactSensorArrayOperations _contactSensorPort = new contactsensor.ContactSensorArrayOperations();

[Partner("Drive",
    Contract = drive.Contract.Identifier,
    CreationPolicy = PartnerCreationPolicy.UseExisting)]
drive.DriveOperations _drivePort = new drive.DriveOperations();

One difference from previous partnership declarations is the PartnerCreationPolicy value:

CreationPolicy = PartnerCreationPolicy UseExisting

The ContactSensorArray is an abstract service definition. There is no default service implementation for it. Instead, it relies on a specific service implementation being started (like the Lego NXT Bumper service) that implements the contact sensor contract as an alternate contract. When a service implements an alternate contract, it will be found twice in the service instance directory, first by its own contract, and the second instance "posing" as the alternate contract. Many of the sample services provided as part of RDS implement alternate contracts.

Step 2: Use the Contact Sensor as a Bumper to Trigger Behavior

To start the wandering behavior, one of our bumpers must be pressed. To receive bumper notifications, subscribe to the contact sensor port and supply a notification target port.

The following declares the notification target port for bumper.

// target port for bumper notifications
contactsensor.ContactSensorArrayOperations _contactNotificationPort = new contactsensor.ContactSensorArrayOperations();

Define SubscribeToBumperSensors() and call it from the Start():

/// <summary>
/// Service Start
/// </summary>
protected override void Start()
{
    // Listen on the main port for requests and call the appropriate handler.
    // Publish the service to the local Node Directory
    base.Start();

    SubscribeToBumperSensors();
}

/// <summary>
/// Subscribe to sensors and send notifications
/// to BumperNotificationHandler
/// </summary>
void SubscribeToBumperSensors()
{
    _contactSensorPort.Subscribe(_contactNotificationPort);

    // Attach handler to update notification on bumpers
    Activate(Arbiter.Receive<contactsensor.Update>(true, _contactNotificationPort, BumperNotificationHandler));
}

The SubscribeToBumperSensors() method issues a Subscribe message to the service bound to our _contactSensorPort. We then activate a handler on the notification port. The handler triggers one of two routines, depending on which bumper was pressed. If the front bumper is pressed, the handler executes the TurnAndGoBackwards method. If the rear bumper is pressed it executes TurnAndGoForward.

/// <summary>
/// Handler for Contact Sensor Notifications
/// </summary>
/// <param name="updateNotification"></param>
void BumperNotificationHandler(contactsensor.Update updateNotification)
{
    // Since we are writing a generic wander service we dont really know
    // which bumper is front, side, rear etc. We expect a navigation service to be tuned
    // to a robot platform or read configuration through its initial state.
    // here, we just assume the bumpers are named intuitively so we search by name

    contactsensor.ContactSensor s = updateNotification.Body;
    if (!s.Pressed)
        return;

    if (!string.IsNullOrEmpty(s.Name) &amp;&amp;
        s.Name.ToLowerInvariant().Contains("front"))
    {
        SpawnIterator<double>(-1,BackUpTurnAndMove);
        return;
    }

    if (!string.IsNullOrEmpty(s.Name) &amp;&amp;
        s.Name.ToLowerInvariant().Contains("rear"))
    {
        SpawnIterator<double>(1,BackUpTurnAndMove);
        return;
    }
}

Step 3: Issue Drive Commands to Turn and Move

Now examine one of the methods we triggered when the bumpers were pressed. The BackUpTurnAndMove method does the following steps as its name implies:

  1. Reverse direction and move for 1500ms.
  2. Apply equal magnitude, but opposite polarity, power to the two motors in each wheel of the drive service.
    This rotates the robot.
  3. Apply equal power to both motors for a random period of time to move the robot.
Random _randomGen = new Random();

/// <summary>
/// Implements a simple sequential state machine that makes the robot wander
/// </summary>
/// <param name="polarity">Use 1 for going forward, -1 for going backwards</param>
/// <returns></returns>
IEnumerator<ITask> BackUpTurnAndMove(double polarity)
{
    // First backup a little.
    yield return Arbiter.Receive(false,
        StartMove(0.4*polarity),
        delegate(bool result) { });

    // wait
    yield return Arbiter.Receive(false, TimeoutPort(1500), delegate(DateTime t) { });

    // now Turn
    yield return Arbiter.Receive(false,
        StartTurn(),
        delegate(bool result) { });

    // wait
    yield return Arbiter.Receive(false, TimeoutPort(1500), delegate(DateTime t) { });

    // now reverse direction and keep moving straight
    yield return Arbiter.Receive(false,
        StartMove(_randomGen.NextDouble()*polarity),
        delegate(bool result) { });

    // done
    yield break;
}

Port<bool> StartTurn()
{
    Port<bool> result = new Port<bool>();
    // start a turn
    SpawnIterator<Port<bool>>(result, RandomTurn);
    return result;
}

Port<bool> StartMove(double powerLevel)
{
    Port<bool> result = new Port<bool>();
    // start movement
    SpawnIterator<Port<bool>, double>(result, powerLevel, MoveStraight);
    return result;
}

IEnumerator<ITask> RandomTurn(Port<bool> done)
{
    // we turn by issuing motor commands, using reverse polarity for left and right
    // We could just issue a Rotate command but since its a higher level function
    // we cant assume (yet) all our implementations of differential drives support it
    double randomPower = _randomGen.NextDouble();
    drive.SetDrivePowerRequest setPower = new drive.SetDrivePowerRequest(randomPower, -randomPower);

    bool success = false;
    yield return
        Arbiter.Choice(
            _drivePort.SetDrivePower(setPower),
            delegate(DefaultUpdateResponseType rsp) { success = true; },
            delegate(W3C.Soap.Fault failure)
            {
                // report error but report done anyway. we will attempt
                // to do the next step in wander behavior even if turn failed
                LogError("Failed setting drive power");
            });

    done.Post(success);
    yield break;
}

IEnumerator<ITask> MoveStraight(Port<bool> done, double powerLevel)
{
    drive.SetDrivePowerRequest setPower = new drive.SetDrivePowerRequest(powerLevel, powerLevel);

    yield return
        Arbiter.Choice(
        _drivePort.SetDrivePower(setPower),
        delegate(DefaultUpdateResponseType success) { done.Post(true); },
        delegate(W3C.Soap.Fault failure)
        {
            // report error but report done anyway. we will attempt
            // to do the next step in wander behavior even if turn failed
            LogError("Failed setting drive power");
            done.Post(false);
        });
}

Step 4: Use a Hardware Manifest with Robotics Tutorial 3

The tutorial service cannot run if it is started by itself. It depends on actual implementations of the drive, motor, and contact sensor abstract services. The partners we declared require an existing instance of these services to be running.

The simplest way to bind the service partner to your hardware is to start an additional manifest which contains the service contract(s) for your hardware. The following line shows you how to modify the command line arguments to include an additional manifest for the LEGO NXT Tribot to the debug settings supplied by DssNewService. To find the contracts for hardware supported for this tutorial, check the \Samples\Config\ directory in your RDS installation directory and look for the manifests which end with .manifest.xml and then find the one which corresponds with your supported robot. Modify your project settings under the Debug tab and change the Command Line Arguments setting to reference the proper manifest for your hardware.

For example if you want to run the LEGO.NXT.TriBot manifest with this service, the Command Line Arguments should look like this:

/p:50000 /t:50001 /m:"samples\config\LEGO.NXT.TriBot.manifest.xml" /m:"samples\config\RoboticsTutorial3.manifest.xml"

Try It Out

Compile the service from Visual Studio (Menu > Build > Build Solution) or compile from from the DSS Command Prompt using the following:

msbuild -v:m "samples\RoboticsTutorials\Tutorial3\CSharp\RoboticsTutorial3.csproj"

After you compile the project, you can run it from the DSS Command Prompt. If you have supported robotics hardware, replace the robot manifest below with your hardware manifest.
If you don't have a hardware platform we support, use the simulation manifest as follows:

dsshost /p:50000 /t:50001 /m:"samples\config\RoboticsTutorial3.manifest.xml" /m:"samples\config\MobileRobots.P3DX.Simulation.manifest.xml"

Now, actuate the bumper on your robot. You should see your robot start moving around randomly.

Bb483052.hs-note(en-us,MSDN.10).gif

If you are using the simulation manifest, you can use the virtual camera as an entity that can press the bumper on the simulated robot. First, change your camera settings with Alt-P (Menu / Physics) / Settings / [x] Enable rigid body for default camera / [ok].

This produces a small sphere in front of the camera. Now press F2 twice to switch to the physics view and move the sphere until you touch the robot bumpers. However, be careful because it is easy to push the robot very hard and send it flying off into the distance!

Bb483052.hs-note(en-us,MSDN.10).gif

Refer to Setting Up Your Hardware to configure the hardware for your robot.

Summary

In this tutorial, you learned how to:

  • Use Partnerships on Abstract Services.
  • Use the Contact Sensor as a Bumper to Trigger Behavior.
  • Issue Drive Commands to Turn and Move.
  • Use a Hardware Manifest with Robotics Tutorial 3.

 

 

© 2012 Microsoft Corporation. All Rights Reserved.