
Задани на лабораторные работы. ПРК / Professional Microsoft Robotics Developer Studio
.pdf
www.it-ebooks.info
Chapter 4: Advanced Service Concepts
httpPost.ResponsePort.Post(rsp);
// Save the state now because it might have changed SaveState(_state);
}
Conversely, the HttpPostFailure routine does require some explanation. If it simply posts back a Fault (as in the code that is commented out), then XML code is displayed on the screen. Instead, it posts a Fault response but specifies an XSLT file (_faultTransform):
///<summary>
///Send Http Post Failure Response
///</summary>
private void HttpPostFailure(HttpPost httpPost, string failureReason)
{
// Create a new Fault based on the error message
Fault fault = Fault.FromCodeSubcodeReason(FaultCodes.Receiver, DsspFaultCodes.OperationFailed, failureReason);
//HttpResponseType rsp = new HttpResponseType(fault); //httpPost.ResponsePort.Post(rsp);
// Post it back but use the Fault Transform httpPost.ResponsePort.Post(new HttpResponseType(
HttpStatusCode.OK,
fault, _faultTransform
)
);
}
The Fault.xslt file isn’t covered here. It formats a standard SOAP fault (which is an XML message) and provides a couple of buttons so that users are not left at a dead end with nowhere to go. You can examine this file yourself.
Using JavaScript for Client-Side Scripting
A Web Forms approach whereby all the data on the Form is sent to the web server for validation does not result in a good user experience. It is much better to have JavaScript code in the Form to do validation prior to submitting the Form.
If you do not already know JavaScript, it might be a good investment in your job skills because it is a key component of web development. Numerous websites on the Internet offer tutorials on JavaScript, such as www.w3schools.com/js/default.asp. Because it is a C-like language, you should not have trouble learning enough to do basic validation. There are also websites that provide JavaScript code for a variety of tasks, such as www.dynamicdrive.com.
The position in the XSLT file where you place your JavaScript code is indicated quite clearly in the comments in the template. You can either enter the code directly into the XSLT file or create another embedded resource and add your own <script> tag to reference it as follows:
<script language=”javascript” type=”text/javascript” src=”URI-for-JS-file” />
203

www.it-ebooks.info
Part I: Robotics Developer Studio Fundamentals
In TeleOperation.xslt you can see the validation code, which is split across two routines. The first routine, simply called validate, is used to confirm that a value is numeric and falls within a specified range:
//Check that a value is numeric and within a specified range
//Returns a string which is either empty or an error message function validate(val, fieldname, minval, maxval)
{
var msg = “”; var num;
if (val.length == 0)
{
msg += “Please enter a value for “ + fieldname + “\n”;
}
else
{
num = parseFloat(val); if (isNaN(num))
msg += fieldname + “ must be a number\n”; else if (num < minval || num >= maxval)
msg += fieldname + “ is out of range (“ + minval + “ to “ + maxval +”)\n”;
}
return msg;
}
The validate function returns an error message if there is one, or an empty string. It is called by the checkform routine:
//Check the form before submission
//NOTE: Return true if OK to proceed, or false if there are errors function checkform(f)
{
var msg = “”;
// Accumulate all of the error messages (if there are any) msg += validate(f.DeadZoneX.value, “Dead Zone X”, 0, 900); msg += validate(f.DeadZoneY.value, “Dead Zone Y”, 0, 900);
msg += validate(f.TranslateScaleFactor.value, “Translate Scale Factor”, 0.01, 100);
msg += validate(f.RotateScaleFactor.value, “Rotate Scale Factor”, 0.01, 100); msg += validate(f.MotionSpeed.value, “Motion Speed”, 10, 1000);
msg += validate(f.DriveDistance.value, “Drive Distance”, 10, 2000); msg += validate(f.RotateAngle.value, “Rotate Angle”, 5, 360);
// If there were any errors at all, display them if (msg != “”)
{
// Pop up a dialog to tell the user what is wrong alert(msg);
return false;
}
// If we got here, then the data is OK return true;
}
204

www.it-ebooks.info
Chapter 4: Advanced Service Concepts
In addition, you need to modify the <form> tag so that the validation routine is called prior to the Form being submitted. To do this, add the onsubmit attribute to the tag:
<form name=”DssForm” method=”post” onsubmit=”return checkform(this);”>
Note the syntax used in the onsubmit attribute — you must return the Boolean value from checkform so that the Form submission can either proceed or be suppressed. In addition, note that JavaScript is case sensitive, so if you write “return CheckForm(this);”, you will receive an error when you try to submit the Form.
Validating Form data this way cannot catch every possible type of error. There might still be errors that occur only after the Form has been submitted and the service has had a chance to process it. In any case, the user can turn off JavaScript in the web browser, so you always have to check on the server side. This is just basic Web Development 101.
Using a Camera
This section discusses how to set up a webcam and display live video in a WinForm. You have already seen in Figures 4-3 and 4-4 that TeleOperation displays a separate window for the video feed.
Before you start working with a webcam, it is a good idea to test it. Some webcams don’t work with MRDS because they must support DirectX. In particular, older camera drivers might not be suitable if they support only the obsolete Video for Windows (VFW) standard.
To test the camera, plug it in and make sure that the drivers are loaded. (Read the manufacturer’s instructions for this. There might even be a test program included with the software so that you can see if the camera works.)
In Chapter 3, you learned how to start DssHost without a manifest and manually start up a Webcam service. If you need detailed instructions, refer back to Chapter 3. In summary:
1.
2.
3.
4.
5.
6.
Start up DssHost from a MRDS Command Prompt window without a manifest.
Open a web browser and browse to the Control Panel.
Locate the Webcam service in the list of services.
Click the Create button beside the Webcam service.
Browse to the Service Directory.
Click the Webcam service in the list of running services.
This displays a page that shows an image from your webcam. You can click the Start button to get a continuous video feed. If you worked through Chapter 3, then you might have already tried out your webcam in the section “Service Directory.”
If your camera requires a particular service to be running — for example, the Surveyor SRV-1 with the modified service supplied with this book — then you have to start DssHost with the appropriate manifest. However, the Swann Microcam2 plugs into a USB Video Capture Device (VCD) and is automatically detected by the standard Webcam service.
205

www.it-ebooks.info
Part I: Robotics Developer Studio Fundamentals
If you change webcams, you might encounter a problem trying to get the new one to work. In this case, try renaming (or deleting) the config file, which is samples\Config\webcam.Config.xml. This file contains the last selected webcam, so if you switch cameras, the Webcam service sometimes gets confused. This seems to be the case with the Belkin USB VCD.
You can change the settings on your webcam via the Webcam state page even while the TeleOperation service is running. TeleOperation does not provide facilities to do this. If the default resolution is not appropriate, then you have to change it manually. Once you have made the change, it is recorded in the config file and you should not have to worry about it again.
Adding a Camera to a Service
The following is a quick summary of the steps that are required to add a webcam to a new service. You should first test the camera as outlined above and make sure that it works with MRDS.
1. Set up the webcam as a partner, or write code to locate it in the service directory and connect to it dynamically.
2.If you want to use an existing WinForm to display the video images, skip to step 4.
3.Create a WinForm (see the section “Creating a WinForm”). Set up the necessary communication with the main service as explained earlier in the chapter. This Form does not need to send messages back to the main service, so the standard OnLoad and OnClosed messages (discussed above) should be sufficient.
4.Add a PictureBox to the WinForm with an appropriate size. Cameras usually have an aspect ratio of 4:3, e.g., 160 120, 320 240, etc. Set the SizeMode property to Zoom so that the image aspect ratio is preserved (unless you like stretched images).
5. Add a public property or method to the WinForm to set the bitmap in the PictureBox.
6.Add ports to your main service to communicate with the webcam and receive notifications. You should add a shutdown port as well.
7.Add a receiver to the main interleave to handle notification messages from the webcam. This receiver listens for UpdateFrame messages from the webcam.
8.Write a handler for UpdateFrame messages. This handler sends QueryFrame messages to the webcam to get the actual bitmap data, and then puts it into the PictureBox on the Form using
FormInvoke.
9.Add code to your main service to subscribe to the webcam either when you connect or in the Start method if the partnership is established in the manifest.
10. Optionally, add code to your Drop handler to unsubscribe from the webcam before shutting down. (This is best practice).
Most of the code you can just copy and paste from another service such as TeleOperation or the Dashboard.
206

www.it-ebooks.info
Chapter 4: Advanced Service Concepts
Setting Up a WinForm for the Video Feed
If you require a particular web camera for your service to operate, you can declare the generic Webcam service as a partner at the top of your main service source file. Then add a partner in the manifest, and when your service starts it should find the appropriate camera.
However, for the TeleOperation service there are two problems with this approach:
This would lead to a static definition and you would have to modify the manifest or the source code to use a different DSS node or a different camera.
The service might not start if the webcam partner cannot be found.
The TeleOperation service does not connect to a webcam until you click the Connect button. The button click event handler in DriveControl.cs posts a message to the main service requesting a connection to the host and port specified on the WinForm. The OnConnect message contains the URI of the appropriate directory service in the Service property.
The code to handle the connection request in TeleOperation.cs is in two parts. The OnConnectHandler is called first in response to the message from the DriveControl Form; then the ConnectDrive and/or ConnectWebCam routines are called to make the actual connections.
Handling Connection Requests
OnConnectHandler searches the directory on the specified remote host (it does not have to be localhost) for generic Differential Drive and Webcam services. It is possible that there are no matching services, so this must be taken into account:
///<summary>
///Connect Handler
///</summary>
///<param name=”onConnect”></param>
///<returns></returns>
IEnumerator<ITask> OnConnectHandler(OnConnect onConnect)
{
if (onConnect.Form == _driveControl)
{
string ErrorMessage = null;
// The service here is the Directory on the specified host:port UriBuilder builder = new UriBuilder(onConnect.Service); builder.Scheme = new Uri(ServiceInfo.Service).Scheme;
ds.DirectoryPort port = ServiceForwarder<ds.DirectoryPort>(builder.Uri);
ds.Get get = new ds.Get();
port.Post(get); ServiceInfoType[] list = null;
yield return Arbiter.Choice(get.ResponsePort, delegate(ds.GetResponseType response)
(continued)
207

www.it-ebooks.info
Part I: Robotics Developer Studio Fundamentals
(continued)
{
list = response.RecordList;
},
delegate(Fault fault)
{
list = new ServiceInfoType[0]; LogError(fault);
}
);
If you did not have to allow for the possibility of remote hosts, then it would not be necessary to create a ServiceForwarder for the directory service. Instead, you could just use the DirectoryPort, which is defined in the DsspServiceBase class.
A simple Get request to the directory service returns an array of ServiceInfoType records. The code extracts the host name and port number from the first record, although this is not really necessary. It also handles the situation where no services are found:
ServiceInfoType driveInfo = null;
ServiceInfoType webcamInfo = null;
try
{
if (list.Length > 0)
{
UriBuilder node = new UriBuilder(list[0].Service); node.Path = null;
string nodestring = node.Host + “:” + node.Port; LogInfo(nodestring);
}
else
{
LogError(“No services found!”); ErrorMessage = “No services found!\n”; _state.Connected = false;
}
Next, the code loops through all of the services in the list, comparing the Contract ID with the IDs for the Differential Drive service and the Webcam service. If either of these is found, the service info is remembered:
string driveUriPath = null; string webcamUriPath = null;
foreach (ServiceInfoType info in list)
{
if (driveInfo == null && info.Contract == drive.Contract.Identifier)
{
driveInfo = info;
}
if (webcamInfo == null && info.Contract == webcam.Contract.Identifier)
208

www.it-ebooks.info
Chapter 4: Advanced Service Concepts
{
webcamInfo = info;
}
if (driveInfo != null && webcamInfo != null) break;
}
}
catch (Exception ex)
{
string msg = “Service search error: “ + ex.Message; LogError(msg);
ErrorMessage += msg + “\n”; _state.Connected = false;
}
An error message is constructed based on the outcome of the search. If either or both of the services were not found, a MessageBox displays. The MessageBox is invoked via a public method in the DriveControl Form. The ShowErrorMessage method is trivial — it contains a single statement, which displays a MessageBox using the string parameter it was given. This illustrates one way to display message boxes from within a service:
if (driveInfo == null)
ErrorMessage += “No Drive service found\n”; if (webcamInfo == null)
ErrorMessage += “No WebCam service found\n”;
if (ErrorMessage != null)
{
if (_driveControl != null)
{
WinFormsServicePort.FormInvoke(
delegate()
{
_driveControl.ShowErrorMessage(ErrorMessage);
}
);
}
}
Lastly, the appropriate connect routine is called for each of the services (if found):
if (driveInfo != null) SpawnIterator<string>(driveInfo.Service, ConnectDrive);
if (webcamInfo != null) SpawnIterator<string>(webcamInfo.Service, ConnectWebCam);
// We are “connected” if either service was found if (driveInfo != null || webcamInfo != null)
_state.Connected = true;
}
}
209

www.it-ebooks.info
Part I: Robotics Developer Studio Fundamentals
Connecting a Webcam
Before looking at the ConnectWebCam routine, there are several global variables declared at the top of TeleOperation.cs that are used in this routine:
//Ports for the Web Camera webcam.WebCamOperations _webCamPort;
webcam.WebCamOperations _webCamNotify = new webcam.WebCamOperations(); Port<Shutdown> _webCamShutdown = null;
//Form to display the video in
WebCamForm _cameraForm;
//Port for the WebCam Form to communicate on WebCamFormEvents _webCamEventsPort = new WebCamFormEvents();
//Flag to indicate that the form is loaded and ready
bool _webCamFormLoaded = false;
For this code, note the following:
_webCamPort is used to send requests to the camera service.
The purpose of _webCamNotify should be obvious: It receives UpdateFrame messages from the camera. These messages do not contain the actual bitmap data — you must make a request to the camera to get the data in response to a notification. This avoids sending a lot of data around if there is a backlog.
Likewise, the purpose of _webCamShutdown is obvious, as is the _cameraForm handle for the WinForm instance.
The _webCamEventsPort is used for communication from the Webcam Form. In order to ensure that you can drive the robot using the keyboard regardless of which window has the input focus, the keyboard handling code is duplicated in the Webcam Form. It is a different port from the DriveControl Form, but it uses the same handler.
_webCamFormLoaded is an important flag. It indicates whether the Webcam Form is active or not. Due to timing issues, it is possible for frames to arrive from the camera before the Form is properly initialized, or after the user has closed down the Form. The point of this flag is to prevent access violations caused by trying to update the PictureBox when it does not exist.
The rest of the code is in the Camera region at the bottom of TeleOperation.cs.
ConnectWebCam takes the name of the service as a string. If a connection is already open to a camera, then it unsubscribes:
// Handler for connecting to WebCam IEnumerator<ITask> ConnectWebCam(string camera)
{
//ServiceInfoType info = null; Fault fault = null; SubscribeResponseType s; //String camera = Opt.Service;
// Already connected? if (_webCamPort != null)
{
210

www.it-ebooks.info
Chapter 4: Advanced Service Concepts
// Unsubscribe
if (_webCamShutdown != null)
yield return PerformShutdown(ref _webCamShutdown);
}
Next, it creates a new operations port and subscribes to the webcam:
// Create a new port
_webCamPort = ServiceForwarder<webcam.WebCamOperations>(camera);
// Subscribe to the webcam
webcam.Subscribe subscribe = new webcam.Subscribe(); subscribe.NotificationPort = _webCamNotify; subscribe.NotificationShutdownPort = _webCamShutdown;
_webCamPort.Post(subscribe);
yield return Arbiter.Choice( //_webCamPort.Subscribe(_webCamNotify), subscribe.ResponsePort, delegate(SubscribeResponseType success) { s = success; },
delegate(Fault f)
{
fault = f;
}
);
if (fault != null)
{
LogError(null, “Failed to subscribe to webcam”, fault); yield break;
}
If the subscription is successful, then the state is updated with the full URI of the Webcam service and a new Webcam View Form is created:
//Put the service URI into the state for visibility _state.WebCamService = camera;
LogInfo(“Connected WebCam to “ + camera);
//Now that we have found the service and subscribed,
//create a form to display the video
RunForm runForm = new RunForm(CreateWebCamForm);
WinFormsServicePort.Post(runForm);
yield return Arbiter.Choice( runForm.pResult, delegate(SuccessResult success) { }, delegate(Exception e)
{
fault = Fault.FromException(e);
}
(continued)
211

www.it-ebooks.info
Part I: Robotics Developer Studio Fundamentals
(continued)
);
if (fault != null)
{
LogError(null, “Failed to Create WebCam window”, fault); yield break;
}
yield break;
}
At this stage, the TeleOperation service is waiting for messages to arrive from the webcam and a new WinForm should be visible on the screen, as shown in Figures 4-3 and 4-4.
Processing Video Frames
When an UpdateFrame message arrives from the webcam, the handler must issue a QueryFrame request to get the image data. Notice that a timeout is set on the request so that the handler does not get bogged down if the Webcam service dies or is too slow responding:
// Handler for new frames from the camera
IEnumerator<ITask> WebCamUpdateFrameHandler(webcam.UpdateFrame update)
{
webcam.QueryFrameResponse frame = null; Fault fault = null;
//Don’t do anything if the form has not loaded or has been closed!
//Race conditions can arise when the form is first created, or if
//the user closes the form. These result in access violations unless
//we are careful not to execute the rest of the code.
if (!_webCamFormLoaded) yield break;
//Throw away the backlog
//This does no harm because we are throwing away notifications,
//not webcam images
Port<webcam.UpdateFrame> p = (Port<webcam.UpdateFrame>)_webCamNotify[typeof(webcam.UpdateFrame)];
if (p.ItemCount > 2)
{
Console.WriteLine(“Webcam backlog: “ + p.ItemCount); p.Clear();
}
webcam.QueryFrame query = new webcam.QueryFrame();
//Set a timeout so that this cannot wait forever query.TimeSpan = TimeSpan.FromMilliseconds(1000); _webCamPort.Post(query);
//Wait for response
yield return Arbiter.Choice( query.ResponsePort,
212