Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Задани на лабораторные работы. ПРК / Professional Microsoft Robotics Developer Studio

.pdf
Скачиваний:
126
Добавлен:
20.04.2015
Размер:
16.82 Mб
Скачать

www.it-ebooks.info

Chapter 17: Writing New Hardware Services

An XSLT file is included with the Generic Brick service and you can use it to format the state information for display on a web page. However, it is not possible to incorporate an XSLT file into a Proxy DLL,

so you have to copy the file into your own solution when you implement a brick service. This is discussed later in the section “Adding Embedded Resources.”

Communications statistics, CommsStats, are also optional. It is a good idea to think about instrumenting your services early in the design. For example, you can verify that your service is polling by watching the counters in a web browser. You can also check the error rate.

BrickCapabilities is a bit field that uses an enum in GenericBrickTypes.cs. This enum has the [Flags] attribute so that its values can be combined using a logical OR and it is also marked as a [DataContract] to ensure that it is copied to the Proxy.

LEDs and Switches in the state provide redundant information (because the devices are also in the actuators and sensors), but they are easier to understand. They are both binary bitmasks and are declared as integers. The LEDs and Switches can therefore handle up to 32 devices each — far more I/O pins than are available on any hobby robot currently on the market. Note that this does not imply a maximum of 64 devices because there can be other types of devices as well.

The mapping from I/O pins to LEDs and/or Switches is under the control of the Brick service. For example, there could be two LEDs attached to PortA pins 3 and 5 (out of 8) and two more on PortB pins 0 and 1 (out of 8). These might be presented as four LEDs numbered from 0 to 3. The corresponding hardware identifiers might be 3, 5, 8, and 9 due to the concatenation of the ports to form a sequence of 16 devices.

Lastly, the DrivePower class is a convenient way to pass around the left and right motor power settings in messages.

Devices

The Sensors and Actuators are deliberately kept as separate lists in the state. This makes it easier to send sensor updates and it reduces the time that would be wasted searching through the entire list for a particular device when you know that the device is a sensor or an actuator.

Sensors and Actuators are represented as lists of type Device. A Device instance contains a complete description of the device, including its name, type, function, and location on the robot. This class is a key component of the Generic Brick contract:

///<summary>

///Class for all Devices (Analog/Digital, Input/Output)

///</summary>

///NOTE: A Device can be Analog or Digital. Therefore, there are properties

///for both types of values. This is wasteful, but it makes it easier if the

///device can be reconfigured.

[DataContract]

[DataMemberConstructor]

[Description(“Basic details about a Device a.k.a. Pin”)] public class Device

{

// Basic device information private string _name;

private int _hardwareIdentifier;

private DeviceTypes _type;

(continued)

743

www.it-ebooks.info

Part IV: Robotics Hardware

(continued)

private DeviceFunctions _function; private Location _location;

//Additional fields to help manage the device

//These should NOT be changed by external services! private int _port;

private int _pin;

//Current value properties

private DateTime _timeStamp; private bool _state; private double _value; private double _minValue; private double _maxValue;

///<summary>

///Device Name

///</summary> [DataMember]

[Description(“User-friendly Name for the Device”)] public string Name

{

get { return _name; } set { _name = value; }

}

///<summary>

///Unique Hardware Identifier

///</summary>

[DataMember]

[Description(“A unique, immutable identifier for this particular device”)] public int HardwareIdentifer

{

get { return _hardwareIdentifier; } set { _hardwareIdentifier = value; }

}

///<summary>

///Device Type

///</summary> [DataMember]

[Description(“Type of the Device (See DeviceTypes enum)”)] public DeviceTypes Type

{

get { return _type; }

set { _type = (DeviceTypes)value; }

}

///<summary>

///Device Function

///</summary>

744

www.it-ebooks.info

Chapter 17: Writing New Hardware Services

[DataMember]

[Description(“Function of the Device (See DeviceFunctions enum)”)] public DeviceFunctions Function

{

get { return _function; }

set { _function = (DeviceFunctions)value; }

}

///<summary>

///Location of the Device on the Robot

///</summary>

[DataMember]

[Description(“Location of the Device (See Location enum)”)] public Location Location

{

get { return _location; }

set { _location = (Location)value; }

}

///<summary>

///Port on the PIC or MCU

///</summary> [DataMember]

[Description(“The physical Port the Device is attached to”)] public int Port

{

get { return _port; } set { _port = value; }

}

///<summary>

///Pin on the PIC or MCU

///</summary>

[DataMember]

[Description(“The Pin the Device is attached to (within a Port)”)] public int Pin

{

get { return _pin; } set { _pin = value; }

}

///<summary>

///Time Stamp for last update

///</summary>

[DataMember]

[Description(“Time Stamp for last update that took place”)] public DateTime TimeStamp

{

get { return _timeStamp; } set { _timeStamp = value; }

}

(continued)

745

www.it-ebooks.info

Part IV: Robotics Hardware

(continued)

///<summary>

///Binary State of the Device

///</summary>

[DataMember]

[Description(“Indicates if the Device state is currently high/low, on/off, etc.”)]

public bool State

{

get { return _state; } set { _state = value; }

}

///<summary>

///Current Value

///</summary> [DataMember]

[Description(“Current Device value”)] public double Value

{

get { return _value; } set { _value = value; }

}

///<summary>

///Minimum Value

///</summary>

[DataMember] [Description(“Minimum Value”)] public double MinValue

{

get { return _minValue; } set { _minValue = value; }

}

///<summary>

///Maximum Value

///</summary> [DataMember]

[Description(“Maximum Value”)] public double MaxValue

{

get { return _maxValue; } set { _maxValue = value; }

}

}

Notice that there is a [DataMemberConstructor] attribute on the Device class. This causes DssProxy to create a constructor for this class, which is important in terms of defining the devices that make up the robot.

746

www.it-ebooks.info

Chapter 17: Writing New Hardware Services

Note the following points about device properties:

Every device must have a unique HardwareIdentifier (for a given brick implementation). Although the Port and Pin fields also identify the device, they are intended for internal use within the brick code.

The current state of a device is stored in either the State field (a bool) or the Value field (a double), depending on whether it is a digital or analog device. Purists might be offended by this mixing of data types, but it is convenient to be able to pass lists of devices around without regard to what types they are.

There are fields for MinValue and MaxValue that apply only to analog devices. For consistency, implementations should store 0 and 1 in these fields for a digital device, and set the Value to 1 if the State is true or 0 if the State is false. The reverse also applies.

Device Enums

The DeviceTypes enum is used to define whether the device is an input (sensor) or output (actuator):

[DataContract]

 

 

[Flags]

 

 

public enum DeviceTypes : short

 

{

 

 

Other

= 0x0001,

// None of the ones below, or unspecified

Reserved

= 0x0002,

// Reserved = DO NOT USE THIS DEVICE

Reconfigurable

= 0x0004,

// Can be one of several types

DigitalIn

= 0x0008,

// Only Digital Input

DigitalOut

= 0x0010,

// Only Digital Output

DigitalInOut

= 0x0020,

// Can be EITHER Input or Output

(but no other type)

 

 

 

 

// NOTE: This is NOT a logical OR of

Digital In/Out

 

 

AnalogIn

= 0x0040,

// ADC

AnalogOut

= 0x0080,

// DAC

CommsIn

= 0x0100,

// RS-232 Rx Data, or Rx Data for

Bluetooth, etc.

 

 

CommsOut

= 0x0200

// RS-232 Tx Data, or Tx Data for

Bluetooth, etc.

 

 

}

 

 

There might be other types of devices defined in the future, but this is enough for now. Notice that types can be logically combined (because of the [Flags] attribute), so you can mark a device as Reserved if you don’t want other services to access it. Of course, you could also leave it out of the device list so that it is invisible outside the Brick service.

Many microcontrollers enable you to reconfigure I/O pins to be either an input or an output. Some can switch between digital and analog as well. This is not easily accommodated, so in the state a device can only be an input or an output, and only digital or analog. Besides, you don’t want naïve programmers inadvertently changing an analog-to-digital converter (ADC) input to a digital output. The consequences for the hardware might be disastrous.

747

www.it-ebooks.info

Part IV: Robotics Hardware

There are enums for several other device properties as well, including DeviceFunction and Location, but they are not all listed here. Look at them in Visual Studio to see what values they have.

Brick Operations

Having defined the GenericBrickState, you can now move on to the operations that you want to use to manipulate the state. Most operations require either a Request or a Response data type, or in some cases both.

All of the message data types and the operations themselves (which are PortSets) are defined in the GenericBrickTypes.cs file. Rather than list them all here, the main operations port is provided and the operations are discussed briefly:

///<summary>

///GenericBrick Main Operations Port

///</summary>

///<remarks>Keep the number of operations to 20 or less</remarks> [ServicePort()]

public class GenericBrickOperations : PortSet

{

public GenericBrickOperations() : base( typeof(DsspDefaultLookup), typeof(DsspDefaultDrop), typeof(Get),

typeof(HttpGet),

typeof(HttpPost),

 

 

typeof(Replace),

//

Replaces entire state

typeof(Subscribe),

//

Subscribe to changes

typeof(ConfigureBrick),

//

Changes Brick Configuration

typeof(ConfigureDevices),

//

Changes Devices (if possible)

typeof(UpdateSensors),

//

Selective update / notification

typeof(PlayTone),

//

Play a Tone

typeof(SetLEDs),

//

Turn LEDs on/off

typeof(GetSwitches),

//

Read all switches

typeof(GetSensors),

//

Get list of Digital and Analog Inputs

typeof(SetActuators),

//

Set list of Digital and Analog Outputs

typeof(QueryDrivePower),

//

Query current Drive Power

typeof(drive.SetDrivePower),//

Update power to motors

typeof(drive.DriveDistance),//

Drive a specified distance

typeof(drive.RotateDegrees) //

Rotate a specified number of degrees

)

{

}

The first half dozen operations you should recognize as standard DSSP operations. There is some redundancy in the rest of the operations. However, this provides flexibility in how they are used:

ConfigureBrick enables some limited changes to the brick such as the PollingInterval. With the current design, there is no way to set the SerialPort before the service attempts to connect, so you must edit the config file.

748

www.it-ebooks.info

Chapter 17: Writing New Hardware Services

The ConfigureDevices operation is a “placeholder” for a future version, when it will be possible to dynamically reconfigure devices (only those marked as Reconfigurable).

UpdateSensors is intended for notifications to subscribers, but it might also be used internally for different parts of the brick code to update the central state. Note that because the design is based around lists of devices, it is possible to do selective updates and even update just a single device by supplying a device list in the update message.

Several robot services that already exist have a PlayTone operation, or something similar. Lights and sounds are a crucial part of making robotics entertaining for young people. Even the simplest robot should have a buzzer or beeper so that it can indicate its displeasure or that it is in distress.

SetLEDs and GetSwitches use binary bitmasks, and these operations flow directly from the definition of the state as discussed previously.

GetSensors and SetActuators both use device lists, so they can be selective.

The last four operations are related to the robot’s drive system. Notice that the last three,

SetDrivePower, DriveDistance, and RotateDegrees, are actually based on the generic Two-Wheel Differential Drive contract. This is an interesting use of one generic contract inside another.

By defining these operations here and exposing the state of the motors, it is possible to build a drive service that is quite compact. In fact, with some more work, it might even be possible to incorporate the generic differential drive into the brick contract and still remain compatible.

In the code, you can also see some additional methods defined on the main operations port. The declaration of the PortSet using typeof instead of generics is important for making the contract compatible with the CF environment. However, you lose some of the strong type-checking that generics provide. To overcome this, several overloads of the Post method are defined. In addition, implicit operators for extracting a particular port from the PortSet are defined. Examples of both of these are shown here for the Get operation:

///<summary>

///Post(Get)

///</summary>

///<param name=”item”></param>

///<returns></returns>

public void Post(Get item) { base.PostUnknownType(item); }

///<summary>

///Implicit Operator for Port of Get

///</summary>

///<param name=”portSet”></param>

///<returns></returns>

public static implicit operator Port<Get>(GenericBrickOperations portSet)

{

if (portSet == null) return null; return (Port<Get>)portSet[typeof(Get)];

}

749

www.it-ebooks.info

Part IV: Robotics Hardware

These particular methods are created automatically by DssProxy and do not need to be added to the code explicitly. However, you might still see remnants of this code in services written for earlier versions of MRDS.

It is also common practice to define helper methods to enable you to post messages by calling a method with appropriate parameters that would construct the message, populate it, and then post it. However, DssProxy also does this for you now in most cases.

Additional Components

In case you have not realized yet, a Proxy DLL only contains data contracts — no code or embedded resources. (It does have some helper methods, but they are created by DssProxy and are not under your direct control.)

As you have seen, the Device class is a core component of the generic brick. It would be handy to have some helper or utility methods for performing various tasks on Devices and Lists of Devices. However, DssProxy does not allow methods to be transferred to the Proxy DLL, only data type definitions.

To finish off the service, an XSLT file enables the state to be displayed in a user-friendly format.

Creating a Helper DLL

To create a helper DLL, you make a new DLL (Class Library) in Visual Studio. Don’t select one of the Robotics templates because you don’t want to generate a Proxy for this new DLL — it is not a service.

The best way to keep everything together is to add a new project to the existing solution. The generic brick solution included with this chapter has a second project called GenericBrickUtil. (It is common practice to use the term “utility” functions, hence the abbreviation “util,” but it is also common to call them “helper” functions.)

Once you have created a new project, you need to modify the properties as follows:

On the Build tab, change the output directory to be the bin folder under MRDS.

On the Reference Paths tab, add the bin folder under MRDS. You might also want to add the

.NET 2.0 Framework and maybe bin\cf. Look in an existing service solution to see what paths are required.

On the Signing tab, click the box that says “Sign the assembly” and then browse to the key file mrisamples.snk, which should be in the samples folder under MRDS.

If you do not sign your DLL, then it won’t work with MRDS. When you compile, you will get the following error:

Assembly generation failed -- Referenced assembly ‘GenericBrickUtil’ does not have a strong name

750

www.it-ebooks.info

Chapter 17: Writing New Hardware Services

Signing DLLs

Signing DLLs serves two purposes:

It ensures security of the DLL so it cannot be modified or compromised.

It enables developers to uniquely identify their code, i.e., it determines ownership — a signed assembly can be traced back to the owner/developer.

To sign a DLL, you need a strong key file. You can create your own strong key file if you want. On the Signing tab, select <New...> from the drop-down list and enter a filename. You can optionally provide a password on the file for additional security.

However, all of the MRDS samples use mrisamples.snk, which is supplied with

MRDS. Unless you have a good reason not to do so, you should use this file.

If you create your own key file, you have to distribute it with your source code if you expect other people to modify your services. If you don’t supply the key file, there can be problems with version dependencies when users recompile services that have Specific Version set to True on references.

Note that if you want to distribute your services commercially, you need to control who can recompile them. In this case, you create your own strong key file but do not distribute it outside your organization. By keeping the key file a “trade secret,” nobody else will be able to modify the services that you ship or impersonate them.

You need to add references to the standard MRDS DLLs:

Ccr.Core

DssBase

DssRuntime

As usual, set Copy Local and Specific Version to False in the properties of the references.

Which of these DLLs you need depends on what you intend to do in your helper functions. You might be able to get away without some of them. Conversely, you might need to add more DLLs, such as

RoboticsCommon.Proxy.

Add a reference to the generic brick proxy, as well as a using statement:

using brick = ProMRDS.Robotics.GenericBrick.Proxy;

751

www.it-ebooks.info

Part IV: Robotics Hardware

Now you can add a class to hold the helper functions. Most functions can be declared static because they will be operating on data supplied in their parameters and have no need for private storage. For example, here are a couple of methods from GenericBrickUtil.cs:

///<summary>

///Utility Functions for Generic Brick

///</summary>

public static class Util

{

///<summary>

///Find a Device by Hardware Id

///</summary>

///<param name=”list”></param>

///<param name=”id”></param>

///<returns>Index if found or -1 if not</returns>

public static int FindDeviceById(List<brick.Device> list, int id)

{

for (int i = 0; i < list.Count; i++)

{

if (list[i].HardwareIdentifer == id) return i;

}

// Not found! return (-1);

}

///<summary>

///Is this an Analog Device?

///</summary>

///<param name=”dev”></param>

///<returns>True if Analog, False if not</returns> public static bool IsAnalogDevice(brick.Device dev)

{

if (((dev.Type & brick.DeviceTypes.AnalogIn) != 0) || ((dev.Type & brick.DeviceTypes.AnalogOut) != 0)) return true;

else

return false;

}

...

These methods are fairly self-explanatory. There are several more methods in the helper DLL, but they don’t need to be covered here. You can look at them in Visual Studio.

To use these methods in a service implementation, you just add a reference to the DLL, just as you would for any other DLL. A using statement helps to make the methods easy to use:

using util = ProMRDS.Robotics.GenericBrickUtil.Util;

752