Задани на лабораторные работы. ПРК / Professional Microsoft Robotics Developer Studio
.pdf
www.it-ebooks.info
Chapter 4: Advanced Service Concepts
Setting up these receivers ahead of time has no adverse effects. If no messages are ever sent to a particular port, then a receiver is wasted, but it does no harm to the service.
The handlers for the game controller are set up separately once a subscription is successfully established, as discussed in the next section.
Subscribing and Notifying
A common requirement in robotics is to “listen” for updates to sensor information. In MRDS you do this by subscribing to a service. The service then sends you notification messages, either at regular intervals or only when something changes.
The alternative to subscribing is to poll the sensors by periodically sending Get requests to the service that controls the robot (probably the “Brick” service). This is an inefficient approach because you might find that nothing has changed, especially if you are polling faster than new data arrives.
To Poll or Not to Poll
It is important to understand that at some level all sensor updates are the result of polling because you do not have a direct connection to the devices attached to the robot (unless the PC is mounted on the robot, and even then it has to go through some sort of hardware interface). Even if the robot sends periodic sensor updates automatically, the onboard firmware in the robot is still polling based on a timer.
If you let another service get the data and send you notifications, then it must poll in the background without you knowing about it. The advantage is that you don’t have to bother with a timer and continual Get requests that might not produce any new information. However, polling is still occurring behind the scenes.
Whether you should poll, and how often, should be based on the importance and usage of the sensor information. For example, reading switches on a robot to determine option settings is usually done during startup. After that, there is no need to poll them.
When you are using Bluetooth, a rough rule of thumb is not to poll any more frequently than once every 50 milliseconds. That’s 20 times a second, and it should be fast enough for most applications. Of course, this assumes that your robot can keep up.
To summarize, whenever you want to get data, you have two options:
Send a request to the robot and wait for the data to be sent back.
Let another service get the data and tell you when it is available.
The remainder of this section discusses the second option — using subscriptions, i.e., letting somebody else do the polling for you.
163
www.it-ebooks.info
Part I: Robotics Developer Studio Fundamentals
Subscribing to State Changes
Subscribing to a partner service is quite simple. Partners that offer a subscription service must implement the Subscribe operation, so you simply send a Subscribe message. This is one of the fundamental message types defined in the DSSP service model.
The steps to subscribe to a partner service are:
1.
2.
3.
Set up a partnership, which can be done declaratively or dynamically in the code.
Send a Subscribe message with appropriate parameters.
Set up receivers and the corresponding handlers for notification messages.
Each of these steps is explained in more detail in the rest of this section using the Game Controller service as an example.
Consider the following code fragment from the TeleOperation service, which establishes a partnership with a game controller. As usual, you declare the partnership using an attribute and create a port to access the Game Controller service. (Remember that game is an alias declared in one of the using statements above).
[Partner(“GameController”, Contract = game.Contract.Identifier, CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate)]
game.GameControllerOperations _gameControllerPort = new game.GameControllerOperations();
game.GameControllerOperations _gameControllerNotify = new game.GameControllerOperations();
This code is usually added at the top of your service class. It establishes a partnership that must always exist (because the policy is UseExistingOrCreate), although if there are no game controllers then the partner service is not very useful. Notice here that a second port is also created to receive notification messages — _gameControllerPort is for sending and _gameControllerNotify is for receiving. Later in the code you can subscribe to the Game Controller service by posting a Subscribe message. You also need to set up a receiver to handle the incoming notification messages.
The following two statements basically show what is required to subscribe to the Game Controller service. However, the actual code is a little more sophisticated. (You can see the full subscription process in the SubscribeToGameController method shown later.)
_gameControllerPortPort.Subscribe(_gameControllerNotify); Activate(Arbiter.Receive<game.Replace>(true, _gameControllerNotify,
GameReplaceHandler));
This is the absolute minimum amount of code required to set up a subscription, and it is not well written — you should check for a successful subscription by looking at the response message from the Subscribe; and you should not create an entirely separate receiver because it does not participate in the main interleave. Look at SubscribeToGameController below.
Referring back to the partner declaration in the code again, there is a third port:
Port<Shutdown> _gameControllerShutdown = new Port<Shutdown>();
164
www.it-ebooks.info
Chapter 4: Advanced Service Concepts
This port is used for unsubscribing, but it is optional. (Unsubscribing is covered in the next section.) If you want to be able to unsubscribe, then you must specify a valid shutdown port in the NotificationShutdownPort property of the Subscribe message. To unsubscribe later, you send a Shutdown message to this shutdown port, hence the need for yet another port.
Lastly, the Game Controller service can send three types of notifications, not just one, and you need to set up receivers for each of them.
Taking all of this into account, the subscription code can now be rewritten in a more robust way:
private IEnumerator<ITask> SubscribeToGameController()
{
bool success = false;
LogInfo(“Subscribing to Game Controller”);
// Create a subscription message to subscribe to the Game
Controller service
game.Subscribe msg = new game.Subscribe(); msg.NotificationPort = _gameControllerNotify;
//Specify a Shutdown port so we can unsubscribe later msg.NotificationShutdownPort = _gameControllerShutdown;
//Post the message
_gameControllerPort.Post(msg); // Wait for a response
yield return Arbiter.Choice( msg.ResponsePort,
delegate(SubscribeResponseType response) { success = true; }, delegate(Fault fault) { LogError(fault); success = false; }
); |
|
if (!success) |
|
yield break; |
// Subscription failed |
//Add receivers to the main interleave for each of the possible
//notification messages from the game controller.
//If there are no game controllers, then there will be no messages! MainPortInterleave.CombineWith(new Interleave(
new ExclusiveReceiverGroup(), new ConcurrentReceiverGroup
(
Arbiter.ReceiveWithIterator<game.Replace>(true, _gameControllerNotify, GameReplaceHandler),
Arbiter.ReceiveWithIterator<game.UpdateAxes>(true, _gameControllerNotify, GameUpdateAxesHandler),
Arbiter.ReceiveWithIterator<game.UpdateButtons>(true, _gameControllerNotify, GameUpdateButtonsHandler)
)
));
LogInfo(“Game Controller subscription successful”);
}
165
www.it-ebooks.info
Part I: Robotics Developer Studio Fundamentals
As a matter of interest, a subscription to the Game Controller service always succeeds regardless of whether you have a gamepad or joystick connected to your PC. However, if you try to enumerate
the game controllers, you will not find any. TeleOperation doesn’t enumerate the controllers because the Game Controller service automatically selects the first controller (if there is one). The TeleOperation service does not give you any way to select a game controller if you have more than one.
Three receivers are set up here and merged with the Concurrent receiver group of the main interleave. (You need to write three iterator handlers for each of the different types of notification messages).
Most services just send a Replace message as a notification, but it is also possible to define additional messages that send subsets of the service state. These are based on the Update message type. For example, the game controller can send changes to the button states separately from the axes (movement of the joystick). However, the settings of the buttons are also included in a Replace message (which contains the whole of the game controller state).
In addition to a Subscribe operation, some services offer a ReliableSubscribe operation. With a normal subscription, if the receiving service dies or is dropped, the sender just continues to send notifications. With a reliable subscription, however, the sender stops sending notifications if the receiver becomes unreachable. The subscription is added to a suspended list, and every so often an attempt is made to ping the receiver. If the receiver comes back to life (perhaps it was temporarily overloaded), then notification messages resume.
Because of this additional feature, there is a parameter called suspensionInterval that can be specified using ReliableSubscribe. You won’t always need reliable subscriptions, so don’t just use it because it is there. The following example shows how to use ReliableSubscribe:
// Subscribe to the drive _driveShutdown = new Port<Shutdown>();
drive.ReliableSubscribe subscribe = new drive.ReliableSubscribe( new ReliableSubscribeRequestType(10)
);
subscribe.NotificationPort = _driveNotify; subscribe.NotificationShutdownPort = _driveShutdown;
_drivePort.Post(subscribe);
yield return Arbiter.Choice( subscribe.ResponsePort, delegate(SubscribeResponseType response)
{
LogInfo(“Subscribed to “ + service);
},
delegate(Fault fault)
{
_driveShutdown = null; LogError(fault);
}
);
166
www.it-ebooks.info
Chapter 4: Advanced Service Concepts
Unsubscribing from State Change Notifications
It is always a good idea to unsubscribe from your partners in your service’s Drop handler. Otherwise, your service might not be able to shut down cleanly. In addition, if you are dynamically connecting to services and disconnecting again, then you need to be able to unsubscribe. (Otherwise, you might have the strange situation where you are no longer talking to a particular service but it is still talking to you! There is no actual connection between services, just messages traveling back and forth.)
The following code fragment unsubscribes from the Webcam service (if one is in use):
// Already connected? if (_webCamPort != null)
{
// Unsubscribe
if (_webCamShutdown != null)
yield return PerformShutdown(ref _webCamShutdown);
}
Because unsubscribing is done from several places in the code, a function is defined in the TeleOperation service to do this:
Choice PerformShutdown(ref Port<Shutdown> port)
{
Shutdown shutdown = new Shutdown(); port.Post(shutdown);
port = null;
return Arbiter.Choice( shutdown.ResultPort, delegate(SuccessResult success) { }, delegate(Exception e)
{
LogError(e);
}
);
}
That’s all there is to unsubscribing — just send a Shutdown message to the port you supplied when you subscribed. Of course, you should wait for the response to ensure that the unsubscribe has completed.
Building in Support for Subscriptions and Notifications
The TeleOperation service has no need to handle subscriptions from other partners, so an example is required from elsewhere in the book code. The following code snippets are from BSBumper.cs, which is in Chapter 14. It implements the “bumpers” for the Boe-Bot, and consists of two infrared sensors (which only register on and off) and two “whiskers.”
167
www.it-ebooks.info
Part I: Robotics Developer Studio Fundamentals
Microsoft provides a Subscription Manager Service as part of MRDS. This makes it easy to handle subscriptions because you don’t have to keep track of all the services that have subscribed to your service or worry about how to send notification messages to all of them. The following discussion outlines the steps that a service must follow in order to accept subscriptions:
1. You need a using statement to simplify access to the Subscription Manager:
using submgr = Microsoft.Dss.Services.SubscriptionManager;
2.
3.
Add a Subscription Manager partner at the top of your service class:
[Partner(“SubMgr”, Contract=submgr.Contract.Identifier, CreationPolicy=PartnerCreationPolicy.CreateAlways, Optional=false)]
private submgr.SubscriptionManagerPort _subMgrPort = new submgr.SubscriptionManagerPort();
You need a handler for Subscribe messages:
///<summary>
///Subscribe Handler
///</summary>
///<param name=”subscribe”></param>
///<returns></returns> [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public virtual void SubscribeHandler(bumper.Subscribe subscribe)
{
base.SubscribeHelper(_subMgrPort, subscribe.Body, subscribe.ResponsePort);
The SubscribeHelper method takes care of the subscription process for you. Any number of other services can subscribe to your service, but you do not need to keep track of them.
4. It is a good idea at this stage to immediately send a notification message to the new subscriber. This initializes its state. Otherwise, it might have to wait a while before the first notification message.
foreach (bumper.ContactSensor bumper in _state.Sensors)
{
SendNotification<bumper.Update>(_subMgrPort, subscribe.Body.Subscriber,
new bumper.Update(bumper));
}
}
This overload of SendNotification specifies a particular subscriber, rather than sending to all subscribers. This example is a little complicated because the individual bumpers in the contact sensor array are sent one at a time. In general, you would send the entire state using a Replace message.
5. In order for the SubscribeHandler to be called, you must add the Subscribe type to your main operations port. The message type in this case is bumper.Subscribe because this service implements the generic Contact Sensor Array service. When you implement a generic service, you use the operations that are defined in the generic contract. Consequently, the Subscribe
168
www.it-ebooks.info
Chapter 4: Advanced Service Concepts
message type is already declared. If you look carefully at the top of the service class declaration, you will see that it implements an alternate contract:
[Contract(Contract.Identifier)]
[AlternateContract(bumper.Contract.Identifier)] [DisplayName(“Boe-Bot Generic Contact Sensor”)]
[Description(“Provides access to the Parallax BASIC Stamp 2 Boe-Bot
infrared sensor used as a bumper.\n(Uses Generic Contact Sensors contract.)”)]
public class BumperService : DsspServiceBase
Generic contracts are discussed toward the end of this chapter in “Inheriting from Abstract Services.”
6. You can optionally define a ReliableSubscribe handler as well:
///<summary>
///ReliableSubscribe Handler
///</summary>
///<param name=”subscribe”></param>
///<returns></returns> [ServiceHandler(ServiceHandlerBehavior.Exclusive)] public virtual void ReliableSubscribeHandler(
bumper.ReliableSubscribe subscribe)
{
base.SubscribeHelper(_subMgrPort, subscribe.Body,
subscribe.ResponsePort);
foreach (bumper.ContactSensor bumper in _state.Sensors)
{
SendNotification<bumper.Update>(_subMgrPort,
subscribe.Body.Subscriber, new bumper.Update(bumper));
}
}
Although this looks identical to the Subscribe handler, note that the message type is different and the SubscribeHelper acts accordingly. Because the code looks the same at a quick glance, it is easy to overlook the difference between the two operations.
The preceding steps cover the process for supporting subscriptions in the Bumper service. However, the updates to the sensor information have to come from somewhere. The Bumper service subscribes to the BASICStamp2 “Brick” service (the Boe-Bot’s brain) for SensorsChanged messages. There is a partner declaration at the top of the code, and a method called SubscribeToBasicStamp2 does the subscribing. The process is similar to the game controller described earlier, so it is not repeated here.
When a notification arrives from the Boe-Bot brick, the Bumper service checks whether any of the sensors have changed since the last update; if so, it issues a notification to all of its subscribers. The last step in the SensorsChangedHander is as follows:
if (changed) this.SendNotification<bumper.Update>(_subMgrPort,
new bumper.Update(bumper));
That’s it. The Subscription Manager sends a bumper.Update message to all the subscribers (or does nothing if no other services have subscribed).
169
www.it-ebooks.info
Part I: Robotics Developer Studio Fundamentals
Make sure that you don’t flood your partners with notification messages. Always check incoming data to see if anything has changed. If there are no changes, don’t send a notification! This is particularly important for the Game Controller service, for example. Imagine that you let go of the joystick and it springs back to the (0,0) position. If the Game Controller service kept sending updates with axis values of (0,0), then you would not be able to use the buttons on the TeleOperation Form because the game controller would continually override them and stop the robot. Therefore, the game controller only sends notifications when you move the joystick.
If you have implemented a Replace message handler in your service, then you must modify it to send new state information to all of the subscribers using SendNotification. Replace messages are discussed in Chapter 3. The code is not shown here, but you can look at it in Visual Studio.
If your service state is quite large, and especially if it can be broken into logical subgroupings, then consider having more than one type of notification message. This Boe-Bot example is trivial, but it is conceivable that there could be a message type for only the IR sensors and a different message type for just the whiskers. A subscriber might choose to listen only to the IR data, and ignore messages about the whiskers. In fact, the updated firmware from Parallax for use with a SpinStamp microcontroller stops the motors whenever a whisker is pressed. The MRDS service has no say
in the matter because this happens aboard the robot.
User Interfaces
Although a primary objective of the robotics field is to create autonomous robots, almost all robots have to interact with humans. Therefore, user interfaces are an essential element of the equation. You have two different approaches available to you for creating user interfaces for MRDS services:
Windows Forms (WinForms)
Web Forms
Which approach you take depends a lot on the amount of user interaction that is required. In general, more complex or frequently used interfaces are best written using Windows Forms. However, a Windows Form will only be visible on the local computer that is running the DSS node. If you want to allow users to make changes to service parameters remotely, then you have to use a Web Form. You could write yet another service that displays a WinForm and run it as a client on another computer to talk to the main service, but this is getting ridiculous — where do you draw the line? In any case, this would require users to have MRDS installed on their computer instead of just a web browser.
In the example for this chapter, both types of interface are used. However, as you will see when you use it, the TeleOperation service would probably be easier to use if the option settings were in a Windows Form, rather than a Web Form. For comparison, the Dashboard service, also included with this chapter, uses a Windows Form for option settings.
In terms of “best practice,” it is a good idea to implement a web page to display the service state, i.e., a HttpGet operation using an XSLT transform. This makes the state information much easier to read, and it looks more professional. Whether you decide to implement a Web Form, i.e., support for the HttpPost operation, to allow users to update fields in the state is a different issue. Once you have worked through the next few sections you will be able to make an informed decision based on your users’ needs and your own programming skills.
170
www.it-ebooks.info
Chapter 4: Advanced Service Concepts
Using Windows Forms
This section assumes that you are familiar with Windows Forms (or WinForms, for short) in the same way that it is assumed you are already a C# programmer.
The MRDS Robotics Tutorial 4 (Drive-By-Wire) uses a simple form with four buttons to control a robot. The TeleOperation service in this chapter is much more sophisticated than this. However, you should read Robotics Tutorial 4 in conjunction with this section of the book.
If you plan to use a Windows Form on a CF (Compact Framework) device, e.g. a PDA, then you should read Chapter 16. There are some considerations that are specific to the CF environment. The details are omitted here in order to keep the discussion as simple as possible at this stage. A slimmed-down version of TeleOperation, called Drive-By-Wire, is provided with the code for this chapter, which includes a CF version (not discussed here).
To see how the WinForms work, follow these steps:
1.
2.
3.
Start the TeleOperation service in the debugger. It takes a little while because the default manifest is set up to start the simulator.
Select localhost as the node name and 50001 as the port number. (These values are stored in the config file, so they should already appear in the window.)
Click the Connect button. You should see another window appear with the view from the robot’s camera, as shown in Figure 4-4. You can move the two windows around independently. The service was deliberately designed to use a second WinForm for the camera view so that you can still use TeleOperation when the robot has no camera.
Figure 4-4
If you don’t see the simulated camera view in the webcam window, make sure that you have installed the V1.5 Refresh.
4. You can drive the robot around using the arrow buttons on the main form, the arrow keys on the keyboard, a gamepad, or a joystick. If you close the WebCam View window, you can reopen it by clicking the Connect button again. If you close the TeleOperation window, then the service should shut down and take the DSS node with it. However, the DSS node sometimes doesn’t shut down for reasons that are not apparent.
171
www.it-ebooks.info
Part I: Robotics Developer Studio Fundamentals
How WinForms Work under MRDS
A quick overview of how Windows Forms work with DSS services is appropriate at this point. In a normal Windows Forms application, all of the relevant code is often included directly in the form. However, for use with MRDS, “control” of the service is done in the main service implementation code and not the form.
Windows Forms operate off the main Win32 message queue for the application. They handle events such as MouseMove, ButtonClick, KeyDown, and so on. These events need to be sent back to the main DSS service (possibly after some pre-processing, or perhaps not at all if they only affect the internal state of the form).
Therefore, a port is created by the main service to allow the WinForm to send messages back to the main service. The message types have to be defined in the same way as they would for any service. In this sense, the WinForm acts something like an internal partner service, but it does not have a Proxy.
When it is necessary to execute some code in the context of the WinForm, the main service has to use an approach that is similar to PlatformInvoke for calling unmanaged code. This is done by sending a FormInvoke message to the WinFormsServicePort specifying a delegate to execute.
Windows Forms are in a sense “legacy” code. (Eventually, WinForms might disappear and be replaced by the new Windows Presentation Foundation, WPF. However, as of V1.5 of MRDS the WPF is not supported.) WinForms run in a Single-Threaded Apartment model and do not fit nicely into the multithreaded model of the CCR. Because WinForms have thread affinity — i.e., they store state information into the thread’s local store — they require special treatment. Therefore, Microsoft defined a WinFormsServicePort in the DsspServiceBase class that is used to control Windows Forms.
Following is a summary of the steps for adding a WinForm to your service (steps 2–4 are similar to setting up a new service):
1.
2.
3.
4.
5.
6.
7.
Create a new Form in your service project.
Define the request messages that the Form can send and a PortSet containing these message types. These messages usually correspond to each of the event handlers in the Form. You can place these message classes in the Form source file if they are declared as public, or add them to the ServiceTypes.cs file for the service.
Define a port in the main service (using the Form’s PortSet) to receive the messages from the Form.
Write handlers for each of the Form message types and add appropriate receivers to the main interleave.
Modify the Form’s constructor to accept a port as a parameter and create a variable to store it in.
Edit the Form source code to add appropriate public properties and methods to enable information to be passed back to the Form from the main service.
Update the Start method of the service to post a RunForm message to the WinFormsServicePort. This creates a new instance of the Form. The Form creation code should pass the Form port to the Form constructor. Save the handle (pointer) to the new Form instance so that you can access it later.
172
