Myro 3.0 Developer Manual

From IPRE Wiki
Jump to: navigation, search

Introduction

This manual is a work in progress!

This manual attempts to:

  1. Survey the underlying architecture of Myro, which is comprised of a set of base classes and utilities for creating Microsoft Robotics Developer Studio (MSRDS) services, an adapter layer providing synchronized, object-oriented access to MSRDS services, and a high-level end-user API.
  2. Describe how each layer of the architecture works, and how Myro communicates information and error handling between layers, so that you can extend or program for Myro.
  3. Provide tutorials for programming using the Myro architecture, including supporting new robots and extending the end-user API.

Architecture Survey

Myro consists of three layers, as shown below and described as follows:

Service Layer    The service layer consists of MSRDS services that communicate with robot hardware, or with a robot in simulation. These services run asynchronously, and are potentially distributed across multiple computers. To make developing and accessing services simple and consistent, we developed a new generic service type, called Vector. The services distributed with Myro are implemented as Vector services, but Vector services can also automatically publish Drive, AnalogSensor, AnalogArraySensor, and Contact contracts.

Adapter Layer    The adapter layer provides synchronous, object-oriented access to MSRDS services. In general, there is a one-to-one mapping between service types and adapter types, and also a one-to-one mapping between the requests supported by a service, and the methods provided by an adapter to make those requests. In addition to making requests, such as setting and retrieving parts of services’ states, adapters also convert Faults into Exceptions, making error handling somewhat transparent.

API Layer    The API layer is the part of Myro that is visible to the end-user. The API layer is high-level, and provides simple, uniform access to robot sensors and actuators, in addition to performing additional logic to build more complicated functions (such as functions that execute timed movements, play music, etc.). The functions in this end-user API make calls to adapters in the adapter layer to actually interface with the hardware, instead of dealing directly with MSRDS services.

Myro3Architecture.gif

Myro Components and Communication

To completely understand this section, you should have some understanding of how DSS services work, and how they communicate.

Concept of Adapters

MSRDS by itself employs a message-passing paradigm to transmit requests for sensor status or control actuators, whose responses arrive asynchronously. For a developer or student to communicate with hardware or MSRDS services directly requires a good deal of understanding about how software and hardware communication works.

Because Myro is targeted for introductory-level students, we have designed it to hide the complexity of robot communication from students. The primary way we hide the asynchronous nature of this communication is through adapters, which provide simple object-oriented calls to retrieve sensor values and control actuators, and appear to the end-user to operate synchronously.

As you should know, each MSRDS service operates under one or more contracts, each of which identify a specific set of operations supported by the service. Each operation is initiated by the receipt of a particular request object type, after which the service asynchronously responds by sending one of a set of response object types, which normally either indicate success, provide the requested information, or indicate failure. For instance, a drive contract may indicate that a service supports operations such as setting motor speeds, and reading encoder values.

Each adapter type provides access to services that operate under at least one of a certain set of generic contracts. For example, a Vector adapter provides access to any service that operates with a Vector contract. (The Vector service, however, can subscribe to Analog, AnalogArray, and Contact services, providing compatibility with the standard MSRDS generic contracts).

Each instance of an adapter connects to a single MSRDS service, acting as a proxy for that service. For example, suppose a robot has two Vector services. Suppose one Vector service contains a length-two vector with the values of the robot’s two IR sensors, and another Vector service contains a length-three vector with the values of the three light sensors. For this robot, Myro will create two Vector adapter instances at runtime, one connecting to the IR service, and the other connecting to the light service. A developer then accesses the particular adapter instance corresponding to the sensor he or she wants to read.

Adapters and communication with services

For the moment, assume that an adapter already knows which MSRDS service it should connect to. When a developer calls an adapter method, such as “get” or “set”, the adapter formulates a corresponding request for the service, sends it, waits for the reply, and then returns the reply. If an error occurs, indicated by a DSS Fault, the adapter method instead throws an exception (Myro allows exceptions thrown in a service to be passed through a Fault, and re-thrown by adapter methods).

Let’s take a look at the communication between a Vector service and a Vector adapter. A vector service can respond to, for instance, a “GetByIndex” request, shown below:

[DataContract]
[DataMemberConstructor]
public class GetByIndexRequestType
{
    [DataMember]
    public int Index { get; set; }
    public GetByIndexRequestType()
    {
        Index = 0;
    }
}
public class GetByIndex : Get<GetByIndexRequestType,
                              DsspResponsePort<GetElementResponseType>>
{
    public GetByIndex() : base() { }
    public GetByIndex(GetByIndexRequestType body) : base(body) { }
    public GetByIndex(GetByIndexRequestType body,
                       DsspResponsePort<GetElementResponseType> responsePort)
        : base(body, responsePort) { }
}

The actual implementation of “GetByIndex”, in the Vector service, just retrieves a vector element:

[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public IEnumerator<ITask> GetByIndexHandler(GetByIndex get)
{
    try
    {
        GetElementResponseType response = new GetElementResponseType()
        {
            Value = _state.Get(get.Body.Index),
            Timestamp = _state.Timestamp
        };
        …
        get.ResponsePort.Post(response);
   }
   catch (Exception e)
   {
       get.ResponsePort.Post(RSUtils.FaultOfException(e));
   }
   yield break;
}

Simple, right? There is something important going on, though. Note that lines that retrieve the state are in a try block. If an exception occurs, for instance because the element index is out-of-bounds, this handler wraps the exception in a Fault using RSUtils.FaultOfException(e), and sends it off as the response.

Now take a look at the adapter method for “GetByIndex” on the adapter side:

public double Get(int index)
{
    try
    {
        var resp = RecieveSync<vector.GetElementResponseType>(opPort.GetByIndex(index));
        return resp.Value;
    }
    catch (ArgumentOutOfRangeException e)
    {
        throw new AdapterArgumentException(Strings.IndexOutOfBounds(index));
    }
}

public static T RecieveSync<T>(PortSet<T, Fault> port)
{
    T ret = default(T);
    Fault error = null;
            
    ManualResetEvent signal = new ManualResetEvent(false);
    Arbiter.Activate(DssEnvironment.TaskQueue,
        Arbiter.Choice<T, Fault>(
            port,
            delegate(T state)
            {
                ret = state;
                signal.Set();
            },
            delegate(Fault failure)
            {
                error = failure;
                signal.Set();
            }));
    signal.WaitOne();

    if (fault != null)
        throw ExceptionOfFault(fault);
    else
        return ret;
}

The ReceiveSync<T>(PortSet<T,Fault>) method waits for a response on a port of type <T> or <Fault>. If an object of type T is received, the method returns the object of type </tt>T</tt>. If a Fault is received, the method throws an exception generated by ExceptionOfFault(Fault) (if the Fault does not actually encapsulate an exception, ExceptionOfFault instead returns a FaultReceivedException, which encapsulates the Fault).

This method, ReceiveSync, is used in Get(int) (and most other adapter methods). In Get(int), we enclose the ReceiveSync call in a try block, and we can then check for any exceptions we know could be thrown (such as an ArgumentOutOfRangeException when the requested index is out-of-bounds). Any other, unexpected, exceptions will propagate out of Get.

Summary

Let’s recap: We’ve seen an adapter method make a simple query to a service. Myro provides a ReceiveSync<T>(PortSet<T,Fault>) method that makes writing adapter methods fairly easy, as it takes care of waiting for a response. We also saw how Myro makes exception passing somewhat transparent, providing a FaultOfException(Exception) method that can be used as a catch-all for errors occuring in a service handler, and an ExceptionOfFault(Fault) method that either retrieves an exception from a Fault, or generates an exception from the fault. Finally, ReceiveSync automatically uses ExceptionOfFault when a Fault is generated.

How Myro finds services and connects adapters

To the user, services appear to have simple names, such as "drive" or "bumpers". These simple names map to MSRDS services as: localhost/{name}.

Myro keeps a cache of services that it knows about, in the class Myro.Adapters.AdaperBank. When the API requests an adapter for a service by simple name, AdapterBank returns a token (an instance of AdapterSpec) representing that service. AdapterBank keeps a cache of these tokens: If a service has been previously requested, Myro will return an existing token, but if a service has not been previously requested, Myro will return a new token.

Returning a token is instantaneous - Myro will not try to connect to a service to return a token. When an adapter is requested from the token, using the AdapterSpec.Adapter property, the AdapterSpec token will try to connect to the service if it is not already connected. If it cannot connect, it will throw either a NoAdapterFoundException, or a ServiceNotAvailableException. Because the API only deals with the token, it does not have to worry about services starting up or not existing at all. Every access to a non-existent service will throw an exception, but when the service starts, access will start to work.

When the Adapter property of an AdapterSpec token is accessed, it tries to connect an adapter to a service if it is not already connected. It starts by querying the URI dssp.tcp://localhost/{name}, where {name} is the simple name for the service. In MSRDS, services may use multiple contracts, available at child URI's. AdapterSpec searches through all of the contracts for the service, for a contract compatible with one of the adapter factories in the AdapterFactories property.

You can add new adapter types to Myro by extending the AdapterFactory class to create a new adapter factory, which should contain a list of contracts the adapter can connect to, and a Create method, which should return an instance of a class that implements IAdapter. Myro will always call Create with a ServiceInfoType for a service exposing one of your adapter's compatible contracts.

The Vector service

We saw how Myro facilitates communication with services with synchronous receives and exception passing. Now we’ll take a closer look at the Vector service, which provides a contract for a very flexible generic set of operations, and additionally, a base class that makes implementing new services using this contract extremely easy. The Vector service is located in IPRE\Services\Generic\Vector. The request and response types, as well as the state, are defined in VectorTypes.cs, and the handlers are implemented in Vector.cs.

As we described briefly earlier, the Vector service supports an array of floating-point numbers, each with an optional associated string key. Keys are mapped to vector element indices in the order they appear in the Keys list, i.e. the 4th key maps to the 4th value. The association of keys with positions in the vector is fairly flexible, however. If there are fewer keys than values, the last values will not be accessible by key, but will still be accessible by index. Conversely, if there are more keys than values, the last keys will be unused, and any queries made using these keys will throw a KeyNotFoundExceptions. The list of keys can also be empty, in which case the “-ByKey” operations will always fail. If any keys in the list are empty strings, "", the indices of those keys will not be accessible by key. Finally, the number of vector elements (i.e. the vector length) can change at runtime, in which case all of the preceding rules will be reapplied (including reassigning keys that were previously unused due to the vector being shorter).

The Vector service specifies the operations in the following table. Most operations have their own request type, but share response types. For example, “GetByIndex” and “GetByKey” both respond with a GetElementResponseType, which contains the floating-point element value and the latest modification timestamp.

Request Fuctionality
DsspDefaultLookup, DsspDefaultDrop Common DSS operations
HttpGet, Get, Replace Retrieve or set the entire state, comprised of Values, Keys, Timestamp, and the index cache.
GetByIndex Get a single element at a given position in the vector (zero-based index), and the latest modification timestamp. Throws ArgumentOutOfRangeException.
GetByKey Get a single element with the given string key, and the latest modification timestamp. Throws KeyNotFoundException.
GetAllElements Retrieve all the vector values at once, in order, and the latest modification timestamp.
SetByIndex Modify a single element at a given position in the vector (zero-based index), and the latest modification timestamp. Throws ArgumentOutOfRangeException.
SetByKey Modify a single element with the given string key, and set the latest modification timestamp. Throws KeyNotFoundException.
SetAllElements Modify all the vector values at once, in order, and set the latest modification timestamp.

The state type, VectorState, keeps a cache of key-index pairs, which accelerates lookups by key. Because of this cache, however, you should not modify VectorState properties directly. Instead, use the public accessor and modifier methods of VectorState. (Of course, you could modify some without causing any problems, but to keep things consistent, we provide public methods for all types of modifications).

Vector also supports subscribers, and notifies them whenever the state is modified in any way.

(This feature not yet implemented) The Vector service will in the future be able to expose some of the standard MSRDS generic services: AnalogSensor, AnalogSensorArray, Contact, and Drive. If you are using orchestration services that expect one of these generic contracts, or want to use Vector outside of Myro, Vector will be able to automatically operate with one or more of these generic contracts.

The Vector service does not interface with any hardware by itself; it only stores data, and allows it to be modified and retrieved. The service implementation class, VectorService, is easy to extend, however, making it possible to write new services that interface with hardware, by overriding one or two callback methods. Also, VectorService can subscribe to services exposing AnalogSensor, AnalogSensorArray, and Contact contracts, and automatically update its state to match theirs’, while in the process assigning element keys such as “left” and “right”. Extending VectorService is the easiest way to create new services and support new robots, and Section 4.2, “Extending Vector to creating a new service” provides a tutorial on creating new services by extending VectorService.

In order to use Myro with standard pre-made MSRDS sensor contracts, such as AnalogSensor, AnalogSensorArray, and Contact, an instance of the Vector service can subscribe to one or more services exposing these contracts, and build its state from the elements of each of these other sensors. In the process, it assigns element keys to each sensor. For instance, if a set of robot services contains two separate AnalogSensor services connected to left and right IR sensors, a Vector service can represent both as a two-element vector, with element keys “left”, and “right”. This “auto-subscribe” functionality is enabled by specifying partners for a Vector service with the partner name pattern “auto_key1_key2_key3…”. The vector will be build by looking at each partner name in order, and reserving a vector element for each key name in the partner name. In the partner name “auto_front_back”, for instance, two elements will be reserved, with element keys “front” and “back”. Additional partners will append to the vector. The partners should be connected to existing services as usual by partner name, service URI, or contract. Details on leveraging this functionality to use pre-made services with Myro is documented in Section 4.1, “Using pre-made robot services with Myro”.

MSRDS services supported by Myro

Although Myro is easy to extend to support new services, the following service types are supported "out of the box":

Service Details
Drive Differential drive, supports setting power, but not speed or distance. High-level commands allow driving for an amount of time.
AnalogSensor Fully supported through Vector service, including assigning a string key.
AnalogSensorArray Fully supported through Vector service, including assigning string keys.
Contact (array) Fully supported through Vector service. Can use string keys provided by Contact service, or assign new string keys.
WebCam Supported, but current image type GUIDs are for the Fluke WebCam service. Some slight modification is needed to work with other image type GUIDs.
Speech Created for Myro. Text-to-speech service. Adapter stub present, but service not implemented.
Vector Created for Myro. Variable-length array of floating-point numbers, with string keys for each element. Automatically subscribes to other services to build array from subsets of other services' arrays. Can be extended to create actuator services.

Myro new robot tutorials

This set of tutorials walks you through everything you need to do to use a new robot with Myro. If you haven’t done so already, please read Section 3.4, “How Myro finds MSRDS services” (it’s short) before continuing, which explains how Myro finds services at runtime. If your robot has pre-made MSRDS services, all you have to do is configure Myro to use them: Tutorial 4.1, “Using pre-made robot services with Myro”. If your robot does not have pre-made MSRDS services, you will have to do some coding, but fortunately, we’ve taken care of most of the work for you: Tutorial 4.2, “Extending Vector to creating a new service”. You can also use a mixture of pre-made services and newly-written services by simply adding the appropriate manifest file entries. Myro is configured entirely through a DSS manifest file, and the tutorials will explain that each sensor or actuator in Myro is one or more services in the manifest file.

Using pre-made robot services with Myro

Myro only can connect directly to Vector, Drive, Speech, and WebCam services. The latter three are standard MSRDS generic contracts, and are provided by most pre-made services. Vector, on the other hand, was created by the Myro team, but you can easily configure Vector services to automatically subscribe to AnalogSensor, AnalogSensorArray, and Contact services. Furthermore, Vector can automatically build a state vector from multiple subsets of arrays from other services, while additionally assigning a string key to each element (such as “left” or “right”). This tutorial will explain how to get Myro working with pre-made robot services by creating a new manifest file, using the Microsoft DSS Manifest Editor.

Add basic services

Most sets of robot services consist of a “base”, or “brick” service, which communicates directly with the hardware. Individual sensor and actuator services then communicate with this base service. Let’s configure Myro for an iRobot Create. Start the Microsoft DSS Manifest Editor, you should see an empty window:

M3DImage003.jpg


Scroll down and find “iRobot® Create / Roomba”, this is the Create’s base service. Drag it into the manifest area:

M3DImage005.jpg


There are two important pieces of information the manifest editor gives us at this point. As a subentry to the service we just added, we can see a partner definition, named “irobotstream”. This partner definition also appears in the Properties panel, under “Partners”. The subentry contains the text “UsePartnerListEntry, optional”, which tells us that this partner is optional. It turns out in this case that if we do not specify this partner service, the iRobot service will create an instance of it automatically. Note, that when a partner service will be created if not specified, it is more common to see “UseExistingOrCreate”, in which case if the partner is not specified, the DSS system will create it automatically.

The other important piece of information is that it is possible to specify an initial state for the Create/Roomba service, as indicated by the text and buttons in the Partners section of the Properties panel. Many robot base services require an initial state to know how to connect to the robot, and the Create is not an exception. Click the “Create Initial State” button, and fill in the “Name” field (pick a name), and the SerialPort field:

M3DImage007.jpg


The default values are ok for the other fields.

Now add the “iRobot® Generic Drive” and “iRobot® Generic Contact Sensors”. Because we only have one instance of the Create/Roomba service, and because we see the text “UseExisting” next to the partners, we don’t need to specify partners, the DSS service will find them by contract at runtime. We do need to change one thing, though: Myro finds services by path, and treats the drive service specially, so it has to have a particular location. Change the Service Path for the drive service to “drive”:

M3Dimage009.jpg


Myro will not connect directly to the contact sensors, we have to set up a Vector service as an intermediary. Drag “Vector” from the Services list into the Manifest window. We see a list of optional partners the Vector service can subscribe to. By default, there are two of each contract type on the list, but if you ever need more, you can simply add additional partners by right-clicking Vector, and clicking “Add Partnership”. We have enough for now though. Tell Vector to automatically subscribe to the contact sensors by dragging their service onto “Auto_Contact_1” (the manifest editor will only let you drag onto the right partner type):

M3DImage011.jpg


Next, we have to give our Vector service a service path so that we know how to access it in Myro. The convention in Myro is to name the bumpers “bumpers”, so fill that in for the service path for the Vector service (not for the Auto_Contact_1 partner!):

M3DImage013.jpg


Finally, we have to create an initial state for this Vector service that will tell it to subscribe to the partner. Click the “Create Initial State” button, then add an “AutoSubset”. Type the name of the partner (“Auto_Contact_1”) in the “PartnerName” field, but just leave the other fields at their default values:

M3DImage015.jpg


Also, save the manifest in the Myro config directory (you have to save it inside the MSRDS installation tree!), by default in “C:\Program Files\Myro 3.0\Myro\config”.

Congratulations! You should be able to select the new robot in the robot chooser and drive the robot now. Use the Configuration Editor (Tools -> Configuration Editor) to give the new robot an icon. Also, in the control panel, we can see the status of all the contact sensors on the Create, and Myro even fills in the names of each sensor for us (unfortunately they are very crowded, next we’ll do something about that):

M3DImage017.jpg


Configure subsets

One problem with our configuration at this point, is that there are so many contact sensors we can’t even read the names, not to mention the names are long and will be cumbersome to access in code. Point your browser to http://localhost:50000/bumpers to see what the names actually are:

M3DImage019.jpg


Let’s organize the sensors. We’ll put the bumper switches and cliff detectors in a “bumpers” service, and the wheel drop and stall sensors in a “stall” service. Go back to the initial state for the “bumpers” service in the DSS Manifest Editor. In the AutoSubset entry, make “StartIndex=0” and “Count=3”. This will capture the bumpers and wall detector. Add a new AutoSubset entry by clicking the large '+' to the right of the AutoSubsets entry. Make “PartnerName=Auto_Contact_1”, “StartIndex=5”, and “Count=2”. This will add another subset of the same Contacts service, capturing two of the cliff detectors:

M3DImage023.jpg


Running Myro at this point, we see that the number of “bumpers” is more manageable:

M3DImage025.jpg


The names are still a little too long for our purpose. They will be accessed in code as get(“bumpers”, “Front Left Bumper”), for instance. The Keys field of an AutoSubset lets us rename the elements, or give them names if the service does not provide them (Analog sensors and arrays do not provide names, for instance). Keys are assigned in order with the elements in each subset, but any blank or missing keys will keep their service-provided value if possible (Note that the “Wall” sensor will keep its service-provided name here). Give the elements new names:

M3DImage027.jpg


Now we’re really cooking. The names are short and will be easy to access in code:

M3DImage029.jpg


Another sensor service

The last thing to do is add a new service for the additional sensors that the “bumper” service is ignoring. The procedure is to that of adding the first Vector service. Drag a new instance of the Vector service into the manifest, and also drag the “iRobot® Generic Contact Sensors” to its “Auto_Contact_1” partner. This time, give the Vector service the service path “stall”. Add a new AutoSubset, and set the StartIndex and Count so that it will cover the wheel drop and stall sensors. Also give the sensor elements shorter names:

M3DImage031.jpg


Now we see both services logically grouping the sensors of the Create. Not a moment too soon, either, as the robot is about to drive off a cliff!

M3DImage033.jpg


Summary

To recap, Myro can connect directly to Drive, Speech, and WebCam services. To be able to find these services, set the Service Path. Myro treats the Drive service specially, and it must be called “drive”. To use AnalogSensor, AnalogSensorArray, and Contact services, create one or more Vector services. Each Vector service represents one sensor or actuator group in Myro. Set the Service Path of each Vector service to control the Myro name, for example, “bumpers”. Each Vector service can then update itself automatically from one or more other services, when you add those services as partners, then create AutoSubset entries in the Vector service’s initial state. Each AutoSubset entry contains the partner name of one of those services, and optionally, the subset of that service’s array to use, and also optionally, new names for each array element. You can save time by using a pre-made manifest file as well. If your new robot comes with one, you can just make the necessary service path changes, and add any necessary Vector services.

Extending Vector to create a new service

While Vector can automatically subscribe to other services as described in the previous tutorial, it also can serve as a base class for new services. The VectorService base class includes callbacks that make implementing a new service very easy.

Let’s look at an example of a tone generator service implemented by extending VectorService, shown below. The steps to create an actuator service, as visible in this example, are:

  1. Extend the Myro.Services.Generic.Vector class.
  2. The main contract should be a new, specific contract, as in [Contract(Contract.Identifier)].
  3. The alternate contract should be the Vector contract, as in [AlternateContract(vector.Contract.Identifier)].
  4. Create a default initial state, that will set the number of elements in the vector, and assign keys to each element. You do not have to do this, and can resize the vector or change keys at any time, but then consumers of your service will have to be robust to these changes. Example:
    _state = new vector.VectorState(
        new List<double> { 0.0, 0.0, 0.0 },
        new List<string> { "tone1", "tone2", "duration" },
        DateTime.Now);
  5. Optionally override SetCallback to take action for actuators (throw exceptions for invalid requests).
  6. Optionally override GetCallback to modify state before it is retrieved (throw exceptions for invalid requests).
using brick = Myro.Services.Scribbler.ScribblerBase.Proxy;
using vector = Myro.Services.Generic.Vector;

namespace Myro.Services.Scribbler.ToneGenerator
{
    public static class Contract
    {
        public const string Identifier = "http://www.roboteducation.org/schemas/2008/06/scribblertonegenerator.html";
    }

    /// <summary>
    /// The Tone Generator Service
    /// </summary>
    [DisplayName("Scribbler Tone Generator")]
    [Description("The Scribbler ToneGenerator Service")]
    [Contract(Contract.Identifier)]
    [AlternateContract(vector.Contract.Identifier)] //implementing the generic contract
    public class ScribblerToneGenerator : vector.VectorService
    {
        /// <summary>
        /// Robot base partner
        /// </summary>
        [Partner("ScribblerBase", Contract = brick.Contract.Identifier,
            CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate, Optional = false)]
        private brick.ScribblerOperations _scribblerPort = new brick.ScribblerOperations();

        /// <summary>
        /// Constructor
        /// </summary>
        public ScribblerToneGenerator(DsspServiceCreationPort creationPort)
            : base(creationPort)
        {
            _state = new vector.VectorState(
                new List<double> { 0.0, 0.0, 0.0 },
                new List<string> { "tone1", "tone2", "duration" },
                DateTime.Now);
        }

        /// <summary>
        /// Actuator callback
        /// </summary>
        protected override void SetCallback(Myro.Services.Generic.Vector.SetRequestInfo request)
        {
            brick.PlayToneBody play = new brick.PlayToneBody()
            {
                Frequency1 = (int)Math.Round(_state.Values[0]),
                Frequency2 = (int)Math.Round(_state.Values[1]),
                Duration = (int)Math.Round(_state.Values[2])
            };
            if (play.Frequency1 < 0 || play.Frequency2 < 0 || play.Duration < 0)
                throw new ArgumentOutOfRangeException();
            else
                Activate(Arbiter.Choice(_scribblerPort.PlayTone(play),
                    delegate(DefaultUpdateResponseType success) { },
                    delegate(Fault failure) { LogError("Fault playing tone", failure); }));
        }
    }
}

Behind the scenes

Below is the definition of the “GetByIndex” handler in the Vector service. It makes a call to GetCallback(GetRequestInfo), and then responds to the client and posts a subscriber notification. If GetCallback throws an exception, however, the handler wraps the exception in a Fault, using RSUtils.FaultOfException(e), and does not notify subscribers.

[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public IEnumerator<ITask> GetByKeyHandler(GetByKey get)
{
    try
    {
        GetElementResponseType response = new GetElementResponseType()
        {
            Value = _state.Get(get.Body.Key),
            Timestamp = _state.Timestamp
        };
        GetCallback(new GetElementRequestInfo()
        {
            RequestType = RequestType.ByKey,
            Index = _state.indexCache[get.Body.Key],
            Key = get.Body.Key
        });
        get.ResponsePort.Post(response);
    }
    catch (Exception e)
    {
        get.ResponsePort.Post(RSUtils.FaultOfException(e));
    }
    yield break;
}

protected virtual void GetCallback(GetRequestInfo request)
{
}

GetCallback by default does not do anything, but if you override it, you can modify the state before vector elements are returned to the client. The GetRequestInfo class is either an instance of GetElementRequestInfo, or GetAllRequestInfo, and contains information about the actual client request. Taking a look at the source code and definitions of GetRequestInfo and SetRequestInfo in Vector.cs should make clear what information is available to the callback methods.

The callback for actuators – SetCallback(SetRequestInfo) – works similarly. Here is the override of SetCallback for the Scribbler tone generator:

protected override void SetCallback(Myro.Services.Generic.Vector.SetRequestInfo request)
{
    playTone();
}

private void playTone()
{
    brick.PlayToneBody play = new brick.PlayToneBody()
    {
        Frequency1 = (int)Math.Round(_state.Values[0]),
        Frequency2 = (int)Math.Round(_state.Values[1]),
        Duration = (int)Math.Round(_state.Values[2])
    };
    if (play.Frequency1 < 0 || play.Frequency2 < 0 || play.Duration < 0)
        throw new ArgumentOutOfRangeException();
    else
        RSUtils.RecieveSync<DefaultUpdateResponseType>(_scribblerPort.PlayTone(play));
}

As you can see, in this case, the callback makes a request to the Scribbler base service to actually play the tone, and also does some additional validity checking on the actuator values, throwing an ArgumentOutOfRangeException if they are out of bounds. If this exception is thrown, the base service would wrap this exception in a Fault, and send it to the client.

Remember: The state type, VectorState, keeps a cache of key-index pairs, which accelerates lookups by key. Because of this cache, however, you should not modify VectorState properties directly. Instead, use the public accessor and modifier methods of VectorState.

When implementing a sensor service, a good practice is to update the service state asynchronously (either periodically or via a subscription), and then not actually take any action in GetCallback. This practice is in contrast to actually making a hardware or service request in GetCallback. Instruction for doing this falls into the realm of the Service Tutorials, distributed with MSRDS, as this is not specific to Myro or the Vector service. The Scribbler services, located in IPRE\Services\Scribbler are implemented using subscriptions, though, so they provide examples of how to update the service state asynchronously.

Summary

To recap, this tutorial has explained the steps you can take to extend the Vector service, and create new sensor and actuator services. Actually updating the state is outside the scope of this tutorial, but remember: use the state accessor and modifier methods.

NOTES: One problem with the current callback implementation is that if there is a delay in the callbacks, such as waiting for an operation to complete, a service handler thread will be suspended for each waiting operation. The CCR in fact allows us to give up the thread for other operations while waiting for an operation. In the near future, the implementation of these callback methods will change to allow this.