Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile

Microsoft Robotics Studio - A pad service

4.81/5 (10 votes)
7 Sep 20073 min read 1   410  
Implement a pad service using CCR and DSS
Screenshot - screen_shot.jpg

Introduction

In this article you will see how to create a Microsoft Robotics (from now MR) service to use a game pad. This article is not a CCR or a DSS tutorial, and a good knowledge in these technologies will be very useful. In this article I assume you know what messages, queues and services are.

The base idea is quite simple: the service will poll the gamepad device every x milliseconds. The gamepad device will be queried using managed direct input (.NET version) and will return values from -1 to 1. So, for example the left upper corner will be (-1,1) and the right lower corner will be (1,-1).

Screenshot - pad_axis.jpg

No timers or threads will be used to implement polling. I will use only messages and queues and the CCR Classes.

Background

Few words about hardware (the pad). I've used a Logitech Dual Action Gamepad (USB). This is a cheap and good device for gaming, precise enough to control an hobbyist robot. I never experienced Direct Input so I was forced, because of my poor knowledge, to "hard code" numeric values inside the code. I assume that these values are strictly related to my hardware, so, if you have a different gamepad change them accordingly.

The next version of this service will have configuration support using XML files.

In order to compile this solution, you must have the Microsoft.DirectX.DirectInput.dll file. You can find it in Directx SDK.

The project

To create an empty service, you have two ways: start from a blank solution or start using DssNewService.exe. I will show you the easy way: open MR command line and cd to samples directory, then launch DssNewService.exe /s:Pad This will create a new project which contains all the needed files in order to have a VS2005 solution with a DSS service. The project also has post build actions to generate Proxy classes.

The other files created are Manifest.xml and the PadTypes.cs files. PadTypes contains all the messages this service will use. There you define the "actions" your service will respond to.

Open the solution and add reference to Microsoft.DirectX.DirectInput.dll. Add:

C#
using Microsoft.DirectX.DirectInput;

on Pad.cs. You are now able to use Managed Direct Input.

The service is split into three logical sections:

  • Direct Input Init
  • Direct Input Polling
  • Message Notifications

Direct Input Init

C#
//private class variable
private Device _gamepad = null;

...

//Init direct input
DeviceList devlist = Manager.GetDevices(DeviceClass.GameControl, 
                    EnumDevicesFlags.AttachedOnly);
foreach (DeviceInstance inst in devlist)
{
    Guid g = inst.InstanceGuid;
    _gamepad = new Device(g);
    _gamepad.SetCooperativeLevel(_form.Handle,
        CooperativeLevelFlags.Background |
        CooperativeLevelFlags.NonExclusive);
}
if (_gamepad != null)
{
    _gamepad.SetDataFormat(DeviceDataFormat.Joystick);
    _gamepad.Acquire();
}

Now we can use _gamepad to query the pad status to search for Sticks values or buttons status. The _form is used only to get a "Handle" to init DirectInput. This form could be use to provide graphical feedback of the pad.

ATTENTION! If I start debugging (F5) in VS2005, I get a "Loader Lock" warning. This is done by Managed debugging assistants (MDAs). So you can disable assistants as the article describes or you run and attach to process later for debugging (CTRL + ALT + P).

Direct Input Polling

Pad polling is split in two "blocks". The first one is based on a "timer": on every tick a gamepad read is performed and data is immediately sent to a queue. An Arbiter is activated on the queue: an anonymous delegate process messages and eventually notifies new "events" to the main port.

The timer is achieved using this snippet (freely taken from robotics examples).

C#
//the timer port 
private Port<DateTime> _timerPort = null;

...

_timerPort = new Port<DateTime>();

...

_timerPort.Post(DateTime.Now);
Activate(Arbiter.Receive(true, _timerPort,
    delegate(DateTime sign)
    {
        Activate(//every 75 ms ...
            Arbiter.Receive(false, TimeoutPort(75),
               delegate(DateTime time)
               {
                   //poll the gamepad device and enqueue the data read
                   if (_gamepad != null)
                   {
                        // ... poll the pad
                       JoystickState jstate = default(JoystickState);
                       _gamepad.Poll();
                       jstate = _gamepad.CurrentJoystickState;
                       _joyReads.Post(jstate);
                   }
                   _timerPort.Post(time);
               }
            )
        );
    }
));

Another arbiter is active on _joyReads:

C#
Activate(
    Arbiter.Receive<JoystickState>(true, _joyReads,
        delegate(JoystickState jsta)
        {
            //jsta analysis in order to send external notifications
        })
);

That's it. Pad polling is almost complete. The commented code reads current sticks (L and R) values and normalize them in range (-1,1). Inside the omitted code, the interested "service" actions performed are similar to this snippet.

C#
RightStickUpdate actR = new RightStickUpdate();

actR.Body.Stick.StickValueX = _state.RightStick.StickValueX;
actR.Body.Stick.StickValueY = _state.RightStick.StickValueY;

LogInfo("RightStickUpdate " + actR.Body.Stick.StickValueX, 
                        actR.Body.Stick.StickValueY);
_mainPort.Post(actR);

RightStickUpdate is a class defined in PadType.cs file.

C#
public class RightStickUpdate : 
    Update<StickUpdateResponseRight, PortSet<DefaultUpdateResponseType, Fault>>
{
    private StickStatus _stick;

    [DataMember]
    public StickStatus StickStatus
    {
        get { return _stick; }
        set { _stick = value; }
    }

    public RightStickUpdate()
        : base(new StickUpdateResponseRight())
    {
        _stick = base.Body.Stick;
    }
}

The last step is inside the messages handler methods, for example:

C#
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public IEnumerator<ITask> RightStickUpdateHandler(RightStickUpdate rsUpd)
{
    rsUpd.ResponsePort.Post(DefaultUpdateResponseType.Instance);

    //notify it
    SendNotification(_submgrPort, rsUpd);

    yield break;
}

Post on response port and send notification. The service flow is now complete. Inside the solution other messages are defined to fully handle sticks and buttons.

GUI

A GUI will be developed soon to give feedback on pad state. To show a form "inside" a service, use this snippet (actually commented in the source files). The PadInfoForm is totally empty.

C#
using Microsoft.Ccr.Adapters.WinForms;

...

//Create Form
PadInfoForm _form = new PadInfoForm();


WinFormsServicePort.Post(new RunForm(
    delegate()
    {
        return _form;
    }
));

The next improvement will be a GUI. If you have comments and suggestions, please post!

History

  • September 2007: First release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here