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

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

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

www.it-ebooks.info

Chapter 4: Advanced Service Concepts

delegate(webcam.QueryFrameResponse success)

{

frame = success;

}, delegate(Fault f)

{

fault = f;

}

);

if (fault != null)

{

LogError(null, “Failed to get frame from camera”, fault); yield break;

}

Once a response is received successfully, the data (which is a raw array of bytes) can be turned into a Bitmap and inserted into the PictureBox on the Form:

// Create a bitmap from the webcam response and display it Bitmap bmp = MakeBitmap(frame.Size.Width, frame.Size.Height,

frame.Frame);

// Display the image in the WinForm SpawnIterator<Bitmap>(bmp, DisplayImage);

yield break;

}

The code for creating a Bitmap from a byte array is general and can be used anywhere that you need to do this type of conversion. Note that the image dimensions must be supplied separately because a byte array does not contain this information:

Bitmap MakeBitmap(int width, int height, byte[] imageData)

{

//NOTE: This code implicitly assumes that the width is a multiple

//of four bytes because Bitmaps have to be longword aligned.

//We really should look at bmp.Stride to see if there is any padding.

//However, the width and height come from the webcam and most cameras

//have resolutions that are multiples of four.

Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);

BitmapData data = bmp.LockBits(

new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb

);

Marshal.Copy(imageData, 0, data.Scan0, imageData.Length);

bmp.UnlockBits(data);

return bmp;

}

213

www.it-ebooks.info

Part I: Robotics Developer Studio Fundamentals

Finally, the new Bitmap image is copied into the PictureBox using a FormInvoke. This code explicitly creates a FormInvoke message, and then posts it:

// Display an image in the WebCam Form IEnumerator<ITask> DisplayImage(Bitmap bmp)

{

Fault fault = null;

// Insurance in case the form was closed if (!_webCamFormLoaded)

yield break;

FormInvoke setImage = new FormInvoke( delegate()

{

if (_webCamFormLoaded) _cameraForm.CameraImage = bmp;

}

);

WinFormsServicePort.Post(setImage);

yield return Arbiter.Choice( setImage.ResultPort, delegate(EmptyValue success) { }, delegate(Exception e)

{

fault = Fault.FromException(e);

}

);

if (fault != null)

{

LogError(null, “Unable to set camera image on form”, fault);

}

else

{

// LogInfo(“New camera frame”);

}

yield break;

}

There are only a couple of lines of code in the FormInvoke delegate. First, a test is done to make sure that the Webcam Form is still valid. Then the Bitmap is assigned to a public property in the Form.

The code inside the Webcam Form that handles the Bitmap is as follows:

private Bitmap _cameraImage;

public Bitmap CameraImage

{

get { return _cameraImage; } set

{

214

www.it-ebooks.info

Chapter 4: Advanced Service Concepts

_cameraImage = value;

Image old = picCamera.Image; picCamera.Image = value;

//Dispose of the old bitmap to save memory

//(It will be garbage collected eventually, but this is faster) if (old != null)

{

old.Dispose();

}

}

}

As well as keeping a private copy of the Bitmap, the code assigns it to the PictureBox and then disposes of the previous Bitmap to save memory.

That completes the processing of a camera frame. The result is that you see images updating continuously on the screen, unless the Webcam service has a problem.

Inheriting from Abstract Services

There has been a fair amount of discussion on the MRDS Forum about object-oriented concepts and inheritance. The design of DSS does not really allow for traditional inheritance, but DSS has a form of inheritance through generic contracts.

Generic contracts have been mentioned several times previously. The concept is quite simple: You define a service state, a set of message types, and an operations port. (You can also include enum data types as part of the data contract). Note that there is no executable code included in a generic contract.

Developers can implement generic contracts for different brands and models of robots so that they all have a common interface. This enables applications like the Dashboard and TeleOperation to work on a variety of robots. In fact, the applications do not even know what type of robot they are talking to because all robots look the same from an API point of view.

When a developer implements a generic contract, the new service is like a device driver in an operating system — it hides the details about how to control a physical device and presents a “virtual” device to the operating system that accepts a standard set of commands. Generic contracts are covered in the MRDS Service Tutorials 8 and 9. You should read these tutorials for further information.

You are more likely to implement a generic contract than you are to define one, so the next section covers implementing contracts; building generic contracts is covered in the following section.

Implementing a Generic Service

In the ProMRDS\Chapter16 folder is a service called StingerPWMDrive. This example implements the Generic Differential Drive service for the Stinger robot. The original services from RoboticsConnection for the Stinger did not support the generic interface, which meant that it would not work with the TeleOperation service.

215

www.it-ebooks.info

Part I: Robotics Developer Studio Fundamentals

StingerPWMDrive is a “wrapper” that translates generic drive operations into requests to the Serializer Services. (The Serializer is the on-board brains of a Stinger robot). By using StingerPWMDrive instead of the drive service supplied by RoboticsConnection, the TeleOperation service can treat a Stinger like other types of robots because StingerPWMDrive has a generic interface. In fact, TeleOperation is not even aware that it is talking to a Stinger. Without this service, you cannot control the Stinger using TeleOperation.

The StingerPWMDrive service uses the Pulse Width Modulation (PWM) interface on the Stinger, not the Proportional, Integral, and Derivative (PID) interface. Therefore, it does not make use of the wheel encoders. The DriveDistance and RotateDegrees operations just use a timer, which is not very accurate. However, it is still a useful example.

To create a new service based on a generic contract, you use DssNewService with the /alt parameter to specify the alternate contract. You must also include the /i parameter to specify which assembly to look at for the alternate service. (The bold text in the following code indicates what you type.)

C:\Microsoft Robotics Studio (1.5)\ProMRDS\Chapter16>dssnewservice

/service:”StingerPWMDrive” /namespace:”ProMRDS.Robotics.Stinger.PWMDrive” /year:”2008” /month:”01” /alt:”http://schemas.microsoft.com/robotics/2006/05/drive.html” /i:”..\..\bin\RoboticsCommon.dll”

The new service has the [AlternateContract] attribute with the contract identifier of the generic service:

///<summary>

///Provides access to a differential drive (that coordinates two motors that function together).

///</summary>

[DisplayName(“Stinger Generic Differential Drive”)] [Description(“Provides access to the Stinger Drive\n(Uses the Generic

Differential Drive contract)\n(Partners with Stinger ‘brick’)”)] [Contract(Contract.Identifier)] [AlternateContract(“http://schemas.microsoft.com/robotics/2006/05/drive.html”)] public class StingerPWMDriveService : DsspServiceBase

{

Because it is implementing an existing service, the main port and the state both use data types from the generic service:

///<summary>

///Main Port

///</summary>

///<remarks>Note: The main port is an instance of the Generic Differential Drive Operations Port</remarks>

[ServicePort(“/stingerpwmdrive”, AllowMultipleInstances=false)] private drive.DriveOperations _mainPort = new drive.DriveOperations();

///<summary>

///Stinger PWMDrive Service State

216

www.it-ebooks.info

Chapter 4: Advanced Service Concepts

///</summary>

///<remarks>Note: The State is an instance of the Generic Differential Drive State</remarks>

[InitialStatePartner(Optional=true, ServiceUri=”Stinger.PWMDrive.Config.xml”)]

private drive.DriveDifferentialTwoWheelState _state = new drive.DriveDifferentialTwoWheelState();

Stubs are created for all of the operations in the generic contract, but they throw exceptions saying that they are not implemented, as shown in this example:

///<summary>

///HttpPost Handler

///</summary>

///<param name=”submit”></param>

///<returns></returns> [ServiceHandler(ServiceHandlerBehavior.Concurrent)]

public virtual IEnumerator<ITask> HttpPostHandler(dssphttp.HttpPost submit)

{

//TODO: Implement Submit operations here.

throw new NotImplementedException(“TODO: Implement Submit operations

here.”);

}

The StingerPWMDriveTypes.cs file only contains the contract identifier. There is no need to define a main operations port or request types because these are all in the generic contract.

However, if you want to extend the state or add more operations, then you can subclass your state or PortSet off the generic ones. It is therefore possible to add more fields to the state, or more operations to the PortSet. The Microsoft Tutorials refer to this as “extending” a generic contract.

What if you find yourself writing very similar services, e.g., one for simulation and one for real hardware, and you want to avoid duplicating code? The simplest approach is to use a Helper DLL or service.

If you abstract the common routines and place them into a separate DLL, then you avoid the maintenance nightmare of keeping two copies of the code in sync. Whether you choose to use a DLL with a conventional library interface or a service is up to you, although your decision should be guided by the need to implement queuing, which ports are very good at.

Building Virtual Services

Generic contracts are intended to be used across a range of different hardware. The most common example is the Generic Differential Drive contract. If you are building services for a single robot and have no plans to support other robots, you probably don’t need to create any generic contracts. However, you should try to use existing generic contracts in this case.

The source files for most of the generic contracts in MRDS are located under the MRDS root directory in the folder samples\Common. These are good examples of how to create generic services. You should become familiar with these generic services and try to use them whenever possible, rather than define your own.

217

www.it-ebooks.info

Part I: Robotics Developer Studio Fundamentals

Creating a generic contract is very easy, assuming that you have already planned what data you need in the state and the types of operations you want to perform. Note that Chapter 17 walks through the steps for creating a new Generic Brick contract, but they are outlined here for completeness.

To create a generic contract, follow these steps:

1.

2.

Create a new service, such as MyGenericContract. There are no special requirements for creating the new service. Note that there is no need to make any changes to the Contract class that is automatically generated when you create a new service.

Open AssemblyInfo.cs and modify the ServiceDeclaration attribute. The

ServiceDeclaration should initially contain the following:

[assembly: ServiceDeclaration(DssServiceDeclaration.ServiceBehavior)]

This indicates an implementation of a service. However, this new service is a generic service so it has no implementation. Change this declaration to the following:

[assembly: ServiceDeclaration(DssServiceDeclaration.DataContract)]

This revised declaration says that the service contains only a DataContract, i.e., it is generic.

If you want to reduce the number of DLLs in your applications, you might choose to have generic services and service implementations in the same solution. If so, you can modify the ServiceDeclaration so that both types of service can coexist in the same DLL:

[assembly: ServiceDeclaration(DssServiceDeclaration.DataContract | DssServiceDeclaration.ServiceBehavior)]

If you combine both types of services in a single DLL, then you must use different namespaces for each of the services.

3.

4.

5.

6.

Remove the service implementation source file, MyGenericContract.cs, from the solution and delete the file. A generic contract, as its name implies, is simply a data contract; therefore, it does not contain any executable code, i.e., there is no actual implementation of the service.

Update the state in MyGenericContractTypes.cs. Defining the generic service state is no different from a normal service. You use the [DataContract], [DataMember], and [DataMemberConstructor] attributes in the same way as usual.

If you want, you can split this file into two parts and create MyGenericContractState.cs. This is not required but it might be easier for users of your contract to understand. Many of the standard MRDS generic contracts are organized this way.

Define all the necessary data types for your generic service operations in the file MyGenericContractTypes.cs. This process is identical to how you define operations for a normal service, as explained in Chapter 3.

Add a main operations port that lists all of the operations. The main operations port is similar to a normal service.

218

www.it-ebooks.info

Chapter 4: Advanced Service Concepts

If you plan to use the generic service to create services to run under the .NET Compact Framework (CF), then you have to be careful about how you declare the operations port. In particular, if there are more than eight data types in the PortSet declaration, then you must use typeof. Refer to Chapter 16 for more information on CF services.

Generic services do not explicitly include a version number. (It is possible to specify a version number in the Assembly Information, but this is not used for locating services.) You can change the year and month in the contract identifier if you make substantial changes to a generic contract. However, existing services cannot use the new generic contract without being modified and recompiled. Therefore, try to design your generic services carefully to avoid possible changes in the future.

The MRDS Service Tutorial 9 discusses how to extend existing generic contracts. The procedure is straightforward, so it is not discussed here.

More on Debugging

Debugging is discussed briefly in Chapter 3. This section provides some more tips on how to debug services under MRDS.

Read the Documentation First

It sounds obvious, but read the documentation. Also read all of the messages that are displayed in the Console window, and the Debug and Trace messages in a web browser. Often the answer is right under your nose. Even if the problem is not clear, you can use part of an error message to search the MRDS Discussion Forum or even Google it.

The MRDS Discussion Forum contains a wealth of information. You might not get an immediate answer if you post a question there, but in general you will find the information you need, often from an expert or one of the people on the MRDS Development Team.

As with all forums, make sure you can clearly define your problem, and if possible narrow it down to a small code snippet. (You can mark code when you post it to the Discussion Forum so that it doesn’t end up with smiley faces all through it). You are more likely to get a response if your posting demonstrates that you have made an effort to solve the problem yourself and you list the things you have already tried. If you post a question like “Why doesn’t this code work?” followed by 100 lines of code, or you ask a question that has been discussed several times before, then you might not get an answer.

Use the Visual Studio Debugger

The obvious way to debug a service is using the Visual Studio Debugger! What might not be so obvious is that you can actually set breakpoints inside multiple services so you can see the effect of messages bouncing back and forth. All you have to do is open the relevant source code files from the other projects and set breakpoints. (This assumes that you have the source code and that the current assemblies in the bin folder were compiled from those sources).

The debugger is not always the solution to finding bugs. Some bugs occur due to subtle timing issues, and as soon as you stop in the debugger you change the timing. In addition, because the environment is

219

www.it-ebooks.info

Part I: Robotics Developer Studio Fundamentals

multi-threaded, stopping at a breakpoint might leave other code running, with the result that messages pile up in a port.

Finally, remember that there is a Threads window in the Debugger. This might help you to identify what is happening.

Examine the State of a Node and Services

You have already seen how to examine the state of a service, even if it is displayed in XML. This works across the network, so you can examine services on remote hosts.

Don’t be afraid to add debugging information to your state. The values of key variables, especially counters such as the number of packets sent and the number of packet errors, can be invaluable for verifying that the service is operating correctly or locating the source of errors. Instrumenting your service in this way is a good idea. You can always use conditional compilation to remove these variables later.

Traditional Debugging Techniques

Of course, if you really want to, you can resort to the classic debugging technique called “debug print statements” whereby you place Console.WriteLine statements at strategic places in your code. However, this is the lazy approach.

You should be aware that Console.WriteLine is quite slow, and it can actually hold up your services, resulting in behavior that might not be typical. You should not use it in time-critical code, inside loops, or in handlers that are called frequently. However, it is useful during initialization to indicate progress and for catastrophic errors that force the service to close down.

Using Trace Level and Logging

You might be familiar with the .NET Debug and Trace classes in System.Diagnostics that can be used to conditionally output messages. The DsspServiceBase class has a set of built-in methods that you can use to log information. In addition to the general-purpose Log method, specific methods are associated with each of the trace levels. These methods also output to the Debug and Trace Messages page shown in Chapter 3.

The methods and their associated trace levels are shown in the following table:

Method

Trace Level

 

 

LogError

1

LogWarning

2

LogInfo

3

LogVerbose

4

 

 

220

www.it-ebooks.info

Chapter 4: Advanced Service Concepts

Tracing is a feature of .NET, so the trace levels are set in the .NET application configuration file for DssHost, which is bin\dsshost.exe.config. Enabling tracing at one level also enables all trace messages at a lower level. The default trace levels should be appropriate. If you are interested in changing the tracing behavior, read the Visual Studio help on the subject.

Each of these methods has several overloads. You can use the Object Browser to look at their definitions. Alternatively, type LogInfo into a Visual Studio source window, place the cursor over it, and press F1. This invokes the online help for Visual Studio, but since the V1.5 Refresh, the MRDS Class Reference is also available via this method, so you will see a description of LogInfo. However, most of the Class Reference is automatically generated and it might not give you much more information than the Object Browser.

The primary advantage of using tracing is that it can be enabled or disabled without recompiling the code.

Where to Go from Here

The TeleOperation service covers a wide variety of tasks that a service can perform, but it is by no means complete. You can use it as a starting point for building your own applications, or continue to improve it.

Another service called Drive-By-Wire is included with the code for the book. It is an abbreviated version of TeleOperation designed to run on a PDA. For example, it works with the Boe-Bot if you have a PDA with built-in Bluetooth. However, it won’t work with a LEGO NXT on a Dell Axim 50v because the NXT requires a baud rate of 115200, and Bluetooth on the Axim will not run that fast.

Here are a few more enhancements for the TeleOperation service that you might consider:

Add controls to one of the WinForms (and the necessary code) to change the camera resolution, or select a different camera. Remember to add this information to the state so it is written into the config file. On startup, set the resolution based on the config file.

Add a drop-down list to select the game controller. (This is already implemented in the Dashboard service, so you can cheat and look in there).

Allow the Webcam View window to be resized, and adjust the size of the PictureBox so that the aspect ratio is maintained. You could send a message to the main service to change the camera resolution to try to match the window size.

Add some color or blob tracking code so the robot can follow an object. A sample Blob Tracker service is included with MRDS — look in samples\Misc\BlobTracker.

Incorporate some vision processing using the MRDS services from RoboRealm (www.roborealm.com).

The possibilities are endless! Let your imagination run free, and remember that you can use the simulator if you don’t have real hardware.

221

www.it-ebooks.info

Part I: Robotics Developer Studio Fundamentals

Summary

Many services do not require a user interface. Those that do have two options: Windows Forms or Web Forms (using XSLT to format the state information). Both of these were covered in detail in this chapter. This chapter has also shown you how to use a web camera as part of the TeleOperation service for remotely driving a robot.

This is the end of the introductory part of the book. It has covered all the basics of MRDS, and by now you should be comfortable writing your own services.

The next part of the book discusses using the MRDS simulator. It offers a great environment for testing services without a real robot. This is a significant benefit if you don’t have a lot of money. It also means that if you make a mistake and crash your robot, it doesn’t matter.

222