Robotics Tutorial 4 (C#) - Drive-By-Wire

Glossary Item Box

Microsoft Robotics Developer Studio Send feedback on this topic

Robotics Tutorial 4 (C#) - Drive-By-Wire

Robotics Developer Studio 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 - Robotics Tutorial 4

This tutorial teaches you how to create a service that partners with abstract, base definitions of hardware services. Based on a configuration file (a manifest), your service binds at runtime to a specific implementation of these services. The tutorial displays a Windows user interface that allows basic user control of a robot's movement.

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\Tutorial4\CSharp

This tutorial teaches you how to:

  • Define the Service Operations.
  • Start the Form.
  • Call the Service from the Form.
  • Run the Service.

See Also:

  • Getting Started

Prerequisites

Hardware

You need a robot with microcontroller and a contact sensor. The sensor can also be distance detection devices (like sonars or 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 Robotics Developer Studio 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 Robotics Developer Studio). 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\Tutorial4\CSharp.

Start the development environment and load RoboticsTutorial4.csproj file. If you are using Visual Studio, you should see the following in the Solution Explorer of your VS window:

Figure 2

Figure 2 - RoboticsTutorial4 project files

Open the RoboticsTutorial4.cs file.

Step 1: Define the Service Operations

RoboticsTutorial4Types.cs defines the operations the service supports. The class RoboticsTutorial4Operations defines five new operations in addition to those that were generated by DssNewService.

[ServicePort]
public class RoboticsTutorial4Operations : PortSet<
    DsspDefaultLookup,
    DsspDefaultDrop,
    Get,
    Replace,
    Stop,
    Forward,
    Backward,
    TurnLeft,
    TurnRight>
{
}

Each of these operations has class definition for the operation and a class definition for the message body type. These are the definitions for the Start operation, found lower down in the RoboticsTutorial4Types.cs file.

Message type and request type for the Stop operation:

public class Stop : Submit<StopRequest, PortSet<DefaultSubmitResponseType, Fault>>
{
    public Stop()
        : base(new StopRequest())
    {
    }
}

[DataContract]
public class StopRequest { }

This defines an Operation class Stop, derived from the generic type Submit<TBody, TResponse>, having a body type StopRequest. In the same way that Stop is defined, the file also contains definitions for Forward, Backward, TurnLeft and TurnRight. The service responds to these operations by sending commands to a drive partner.

Each of these operations has a matching handler in the main service implementation class, RoboticsTutorial4, in the file RoboticsTutorial4.cs. The handler for the Forward operation is ForwardHandler.

ForwardHandler

If the motor is not enabled, ForwardHandler calls EnableMotor. EnableMotor sends a message to the drive to enable the motor.

To make the robot move forward, ForwardHandler constructs a SetDrivePowerRequest and posts it to _drivePort using the SetDrivePower helper function.

[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public virtual IEnumerator<ITask> ForwardHandler(Forward forward)
{
    if (!_state.MotorEnabled)
    {
        yield return EnableMotor();
    }

    // This sample sets the power to 75%.
    // Depending on your robotic hardware,
    // you may wish to change these values.
    drive.SetDrivePowerRequest request = new drive.SetDrivePowerRequest();
    request.LeftWheelPower = 0.75;
    request.RightWheelPower = 0.75;

    yield return Arbiter.Choice(
        _drivePort.SetDrivePower(request),
        delegate(DefaultUpdateResponseType response) { },
        delegate(Fault fault)
        {
            LogError(null, "Unable to drive forwards", fault);
        }
    );
}

The field, _drivePort is the operations port of a partner using the abstract drive service definition. This is described in detail in Robotics Tutorial 3 (C#) - Creating Reusable Orchestration Services.

Handlers for the other four messages are defined in a similar way.

EnableMotor

The handlers for Forward, Backward, LeftTurn and RightTurn call the EnableMotor utility method to enable the motor if the service state has the MotorEnabled property set to false.

This method sends an EnableDrive update message to the drive service partner. By returning the type Choice, the callers can return the result of this function, using yield return, to wait until a response is received for this message.

private Choice EnableMotor()
{
    drive.EnableDriveRequest request = new drive.EnableDriveRequest();
    request.Enable = true;

    return Arbiter.Choice(
        _drivePort.EnableDrive(request),
        delegate(DefaultUpdateResponseType response) { },
        delegate(Fault fault)
        {
            LogError(null, "Unable to enable motor", fault);
        }
    );
}

NotifyDriveUpdate Handler

This service subscribes to the drive service partner so that it can keep track of whether the motors are enabled. On some robots, the motors can be disabled to preserve batteries and to ensure that the robot doesn't move unexpectedly. In the Start method, the subscription is started and the NotifyDriveUpdate method is activated to handle Update notifications from the drive service:

_drivePort.Subscribe(_driveNotify);
Activate(Arbiter.Receive<drive.Update>(true, _driveNotify, NotifyDriveUpdate));

The NotifyDriveUpdate message copies the IsEnabled property from the body of the update message into the MotorEnabled property of a new StateType variable which is then posted to this service in a Replace message. The StateType class is defined in RoboticsTutorial4Types.cs:

private void NotifyDriveUpdate(drive.Update update)
{
    RoboticsTutorial4State state = new RoboticsTutorial4State();
    state.MotorEnabled = update.Body.IsEnabled;

    _mainPort.Post(new Replace(state));
}

Step 2: Start the Form

WinForms requires that a form is used only on the thread on which it was started. To ensure that the service can track which thread a form was started on, and to route invocations correctly to that form, the service sends messages to WinFormsServicePort. This is an internal port defined on the DsspServiceBase class from which the service implementation class is defined.

To display a form you must post a RunForm message. Post the FormInvoke message to call code that directs a control on the form. The Start method on the service implementation class demonstrates how to call these.

protected override void Start()
{
    base.Start();

    WinFormsServicePort.Post(new RunForm(StartForm));

    _drivePort.Subscribe(_driveNotify);
    Activate(Arbiter.Receive<drive.Update>(true, _driveNotify, NotifyDriveUpdate));
}

The RunForm message takes, as a single parameter to its constructor, a delegate that is called to create a form. That delegate returns a System.Windows.Forms.Form object. At its simplest, this object instantiates the form and returns the newly created object. In the code below, the title of the form is set to the path of the drive service being used. This demonstrates how to use the FormInvoke message.

private System.Windows.Forms.Form StartForm()
{
    RoboticsTutorial4Form form = new RoboticsTutorial4Form(_mainPort);

    Invoke(delegate()
        {
            PartnerType partner = FindPartner("Drive");
            Uri uri = new Uri(partner.Service);
            form.Text = string.Format(
                Resources.Culture,
                Resources.Title,
                uri.AbsolutePath
            );
        }
    );

    return form;
}

This helper method sends a FormInvoke message to WinFormsServicePort. This ensures that the code that modifies the form is executed on the correct thread.

private void Invoke(System.Windows.Forms.MethodInvoker mi)
{
    WinFormsServicePort.Post(new FormInvoke(mi));
}

Step 3: Call the Service from the Form

In Step 2 above, the form was created as part of service startup. When the form was created, the _mainPort field was passed as a parameter to the form constructor. That gives the form the ability to send messages to the service. The form has 5 buttons that correspond to the five operations defined in Step 1.

Figure 3

Figure 3 - Robotics Tutorial 4 Direction Dialog

When a button is pressed, the event is handled by posting the appropriate operation to the main port of the service. This is the event handler function for the Stop button.

private void btnStop_Click(object sender, EventArgs e)
{
    _mainPort.Post(new Stop());
}

Similar methods exist for the other buttons.

When the form is closed, the service is no longer able to perform its primary function. The form code sends a Drop to the service port when you close the form.

protected override void OnClosed(EventArgs e)
{
    _mainPort.Post(new DsspDefaultDrop(DropRequestType.Instance));

    base.OnClosed(e);
}

Step 4: Run the Service

This service, like the service in Robotics Tutorial 3 (C#) - Creating Reusable Orchestration Services, uses the abstract drive contract. This allows the contract to be used to drive any service that implements this contract.

In the Samples\Config directory, there are several files with the extension .manifest.xml. As described in Robotics Tutorial 3 (C#) - Creating Reusable Orchestration Services, these files contain a list of services to run when the DSS node is started and, optionally, for each service configuration information. To start this tutorial using the simulated drive use the following at the command line:

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

Summary

In this tutorial, you learned how to:

  • Define the Service Operations.
  • Start the Form.
  • Call the Service from the Form.
  • Run the Service.

 

 

© 2012 Microsoft Corporation. All Rights Reserved.