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

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

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

www.it-ebooks.info

Chapter 3: Decentralized Software Services (DSS)

At the top of ServiceA.cs underneath the existing using statements, add another one:

using serviceb = Robotics.ServiceB.Proxy;

You can now recompile ServiceA with the new reference. Clearly, you have to compile ServiceB before ServiceA so that the correct reference is used.

Proxy Assemblies

When you compile a service, a post-build event is triggered after a successful compilation. This runs DssProxy, which generates a Proxy DLL. You can see the command in the Project Properties on the Build Events tab, as mentioned earlier.

Service operations are executed “over the wire” by sending messages. These messages must first be serialized, i.e., converted to XML, so that they can be sent. This is necessary because the service that you are communicating with might not be on the same computer. It is not simply a matter of passing across a pointer reference to a location in memory — this will not work across the network!

This is an important point: You can’t allocate a chunk of memory, e.g., create a new class instance, and send a pointer to it to another service. The contents of the area of memory have to be converted to XML, and then sent.

The Proxy DLL is responsible for performing the serialization and deserialization of messages. When you post a message, you pass a handle to the request message body (a pointer reference) to the Proxy and it creates an XML message from that. When a response message is returned, the Proxy converts the XML back into its binary representation and gives you a handle to a response message.

Therefore, the reference that you add to ServiceA is to the ServiceB proxy. You do not call ServiceB directly.

Running Services

Now that you have compiled the services, you can try running each of them using whatever method you normally use: press F5; click the Start Debugging icon in the toolbar; or click Debug Start Debugging.

This starts DssHost and runs the manifest that was generated for you when you created the service. The command that is executed can be found in the Project Properties on the Debug tab in the Start External Program textbox, and the command-line options are in the Command Line Arguments textbox.

The first time you run ServiceA, you should see something like what is shown in Figure 3-19. Notice that the message “ServiceA starting” is displayed but then nothing else happens. You must stop debugging or close the MRDS Command Prompt window to terminate the service.

123

www.it-ebooks.info

Part I: Robotics Developer Studio Fundamentals

Figure 3-19

Because this is a new service that has not been run before, DSS rebuilds the contract directory cache, as you can see from the message:

Rebuilding contract directory cache. This will take a few moments ...

Contract directory cache refresh complete

The contract directory cache is populated using reflection on all of the DLLs in the MRDS bin folder. It consists of two files in the store folder:

contractDirectory.state.xml

contractDirectoryCache.xml

You can open these files and have a look at them if you like, but you will never need to edit them. If you want to force the cache to be refreshed, you can delete these two files. The next time DssHost starts, it will recreate them.

Also in Figure 3-19, you can see that the Manifest Loader starts up and then loads the ServiceA manifest. When you are creating your own manifest (explained below), you might make mistakes. If you do, this is where the errors will appear.

Now add some behavior to ServiceB. Open it in Visual Studio and then open ServiceB.cs. Scroll down in ServiceB until you find the Start method. Add the following code (shown highlighted):

///<summary>

///Service Start

///</summary>

protected override void Start()

{

base.Start();

// Add service specific initialization here.

SpawnIterator(MainLoop);

}

124

www.it-ebooks.info

Chapter 3: Decentralized Software Services (DSS)

private IEnumerator<ITask> MainLoop()

{

Random r = new Random(); while (true)

{

//Generate a random number from 0-100 and write it out Console.WriteLine(“B: “ + r.Next(100));

//Wait a while

yield return Arbiter.Receive( false, TimeoutPort(1000), delegate(DateTime time) { }

);

}

}

The SpawnIterator in the Start method kicks off an infinite loop called MainLoop. ServiceB then displays a random number from 0–100 on the console, waits for a second, and then repeats forever (or until you shut it down). The point of this code is that it continually generates new random numbers that are used to simulate sensor readings.

Compile ServiceB and run it. You should see output like that in Figure 3-20.

Figure 3-20

Using the Debugger

Debugging services is just like debugging normal code — assuming that you normally debug multithreaded code! You can set breakpoints and step through code just as usual. Try it out by setting a breakpoint inside MainLoop and running ServiceB again.

Figure 3-21 shows the debugger stopped at a breakpoint. Notice in the bottom-left corner that the Threads window is visible. Select it from the menu using Debug Windows Threads. This menu

125

www.it-ebooks.info

Part I: Robotics Developer Studio Fundamentals

option is only available while the debugger is running. Two threads are assigned to the User Services Common Dispatcher. This dispatcher is set up for you by DssHost.

Figure 3-21

Be careful about where you set breakpoints. You can set breakpoints inside a delegate if you want to. If you try to single-step through the code, you might encounter strange behavior when you hit a yield return. Instead of stepping through a yield return, set a breakpoint on the statement after it and tell the debugger to continue execution.

It is important to understand that by setting up an infinite loop like this, one of the two threads created for the default dispatcher is unavailable to the CCR for scheduling service operations. In this case it is not a problem, but in general you should not tie up a thread like this, or you should specify more threads as explained in the previous chapter.

If you are calling other services, you can debug them too. Simply open the relevant source file from another project in the current Visual Studio window. Set breakpoints in this “external” source file as required, and then run the debugger.

For example, later in the chapter you will be running ServiceA with ServiceB as a partner. You can open ServiceA in Visual Studio and set breakpoints in ServiceA.cs. Then, from the menu, click File Open File and browse to ServiceB.cs and open it. You can set breakpoints inside ServiceB as well. That way, as you send messages backward and forward, you stop in the code on both sides.

126

www.it-ebooks.info

Chapter 3: Decentralized Software Services (DSS)

Defining Service State

The concept of service state implies that it should contain all of the necessary information to enable you to save the state, restart the service some time later, and, by reloading the saved state, continue running from where the service left off. For this reason, any information that you retrieve from a service must be part of the state. Service state is exposed via the Get and HttpGet operations. The Get operation is intended to be used programmatically, whereas HttpGet is for human consumption, which enables you to observe the state using a web browser.

In the simplest form, the state is displayed as XML. Therefore, you can start working with a new service without having to write a UI. If you want to present the information in an easy-to-read form, you can supply an XSLT file and even some JavaScript code for the web page.

To define a service state, follow these steps:

1. Open ServiceB in Visual Studio. (Note that this is “B,” not “A.”) Then open the source file called

ServiceBTypes.cs.

2. Scroll down through the source code to the service state, which is initially empty. Add two properties to the state, as shown here:

///<summary>

///The ServiceB State

///</summary> [DataContract()]

public class ServiceBState

{

[DataMember]

public int Interval; [DataMember]

public int SensorValue;

}

Each of these fields, Interval and SensorValue, is declared as public and is decorated with the [DataMember] attribute. (The purpose of these two fields will become apparent shortly.) These fields are part of the service contract and will be serialized (to XML) when the state is saved or sent in a message. You can, of course, have private members in the state if you want, but they are ignored during serialization.

Purists might prefer to use a property declaration like the following, rather than a public field, although this is not strictly necessary. There is, however, a subtle difference if you want to fully document your code — the [DisplayName]attribute can only be applied to a property, not a public field.

private int _interval; [DataMember]

public int Interval

{

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

}

127

www.it-ebooks.info

Part I: Robotics Developer Studio Fundamentals

3. Now that the state for ServiceB contains some fields, go back to ServiceB.cs and revise the MainLoop function as shown:

// Global flag to allow terminating the Main Loop

private bool _shutdown = false;

private IEnumerator<ITask> MainLoop()

{

Random r = new Random();

while (!_shutdown)

{

// Generate a random number from 0-100 and save it in the state

_state.SensorValue = r.Next(100);

Console.WriteLine(“B: “ + _state.SensorValue);

// Wait a while

yield return Arbiter.Receive( false,

TimeoutPort(_state.Interval),

delegate(DateTime time) { }

);

}

}

The changes are as follows:

A global flag called _shutdown has been added.

The while loop now uses _shutdown so that it can be terminated (see below).

New random values are stored in the state SensorValue property.

The timer interval is read from the state Interval property.

Later in the chapter, in the section “Dropping Services,” you will add a Drop handler, which can use _shutdown to terminate the MainLoop. For now, this variable has no effect on the operation of the service.

Storing the simulated sensor value into the state means that it can be accessed from other services using a Get operation.

Exposing the Interval property enables it to be changed without having to recompile the code. It is really a configuration parameter. There are two ways in which the interval can be changed: through the initial state (as explained in the next section) or via an update operation (covered later in the chapter).

128

www.it-ebooks.info

Chapter 3: Decentralized Software Services (DSS)

Persisting Service State

Saving state is useful for remembering the configuration of a service. MRDS provides a service called the Initial State Partner service that can be used to read a configuration file and populate your service state when the service starts up.

In the case of the Simulator, you can save the entire state of the simulation. Using this saved state, you can restart a simulation later without having to construct the environment again programmatically. You can see an example of this in the MRDS Simulation Tutorials.

Loading State from a Configuration File

To load an initial state from a config file, you need to specify where it should come from. DSS can obtain a name for a config file in two ways:

You can specify it in your code in an [InitialStatePartner] declaration.

The filename can be supplied in the manifest.

Using Initial State Partner to Load a Configuration File

To load state using a service state partner, go to the top of ServiceB.cs and locate the declaration of the state instance. Add an [InitialStatePartner]attribute as shown here:

///<summary>

///_state

///</summary>

//Set an OPTIONAL initial state partner

//NOTE: If the config file is NOT specified in the manifest, then the

//file will be created in the MRDS root directory. If it is listed in the

//manifest, then it will be created in the same directory as the manifest. [InitialStatePartner(Optional = true, ServiceUri = “ServiceB.Config.xml”)] private ServiceBState _state = new ServiceBState();

The initial state partner declaration says that the config file is optional. This is a good idea because the service won’t start if the file is required and it doesn’t exist, such as the first time you run the service.

Notice the ServiceUri parameter, which specifies the filename. This is not required, but if you omit it, you must specify the filename in the manifest. You should get into the habit of naming your manifests and config files in a consistent fashion. Then you can easily pick up all of the files associated with a particular service. This is why we have called the config file ServiceB.Config.xml. However, MRDS does not care what the filename is.

If a config file is specified in a manifest without a path, then it is assumed to be in the same directory as the manifest. This is an easy way to keep the files together. However, if the config file is specified in the service code without a path (and not listed in the manifest), then it defaults to the MRDS root directory.

129

www.it-ebooks.info

Part I: Robotics Developer Studio Fundamentals

The ServiceUri is a URI, not a directory path on your hard drive. You can, however, specify the location of a config file using the ServicePaths class, as shown by the following example from the Dashboard in Chapter 4:

private const string InitialStateUri = ServicePaths.MountPoint + @”/ProMRDS/ Config/Dashboard.Config.xml”;

//shared access to state is protected by the interleave pattern

//when we activate the handlers

[InitialStatePartner(Optional = true, ServiceUri = InitialStateUri)] StateType _state = null;

ServicePaths.MountPoint maps to “/mountpoint,” but you should not make this assumption. The mountpoint is the root directory of the MRDS installation on the machine where the DSS node is running. Directory paths are specified relative to this. The ServicePaths class contains a number of other URI prefixes that you can use. You can investigate it using the Object Browser or reflection (by typing ServicePaths in the Visual Studio editor and then right-clicking it and selecting Go To Definition).

Specifying a Configuration File in a Manifest

If you want to specify the config file in the manifest, you must add a service state partner to ServiceB. Open ServiceB.manifest.xml in Visual Studio and update it by adding the following highlighted code:

<?xml version=”1.0” ?> <Manifest

xmlns=”http://schemas.microsoft.com/xw/2004/10/manifest.html”

xmlns:dssp=”http://schemas.microsoft.com/xw/2004/10/dssp.html”

>

<CreateServiceList>

<ServiceRecordType>

<dssp:Contract>http://schemas.tempuri.org/2008/01/serviceb.html</dssp:Contract>

<dssp:PartnerList>

<dssp:Partner>

<dssp:Service>ServiceB.Config.xml</dssp:Service>

<dssp:Name>dssp:StateService</dssp:Name>

</dssp:Partner>

</dssp:PartnerList>

</ServiceRecordType>

</CreateServiceList>

</Manifest>

The first time that you run ServiceB after completing the rest of the changes in this section, it will create a config file in the Projects\ServiceB directory because this is where the manifest is located and no path is specified in the manifest.

Your code, usually in the Start method, must be prepared to handle the situation where no state is defined. This might happen, for example, the first time you run a service. In this case, the global variable (usually called _state by convention) will be null because no config file was found.

130

www.it-ebooks.info

Chapter 3: Decentralized Software Services (DSS)

Go to the Start method in ServiceB.cs and add the following code in the middle of the function to check the state:

protected override void Start()

{

base.Start();

//Add service specific initialization here. Console.WriteLine(“ServiceB starting”);

//Make sure that we have an initial state! if (_state == null)

{

_state = new ServiceBState();

}

//Sanity check the values (or initialize them if empty) if (_state.Interval <= 0)

_state.Interval = 1000;

//Save the state now

SaveState(_state);

SpawnIterator(MainLoop);

}

As well as creating a new state if there isn’t one, the code confirms that the information in the state is sensible. The value of Interval will be zero if the state has just been created. It also saves the state (config file), as explained in the next section.

Saving the State to a Config File

Saving the current state of a service is actually quite trivial — just call the SaveState helper function in DSS. However, you need to set up the name of the config file first, as explained in the previous section.

The following code from the previous code snippet saves the state each time the service runs:

// Save the state now SaveState(_state);

SaveState is just a helper function that posts a Replace message to the Mount service. Because this happens asynchronously, you cannot assume when it returns that the state has been saved, or even that it was successful! However, SaveState returns a PortSet, so you can use it in a Choice to determine whether it returns a Fault. More important, if you are saving the state in your Drop handler, then you can wait until the save has completed before finally shutting down the service. The simplest way to wait is as follows:

yield return (Choice)SaveState(_state);

This assumes that you are inside an iterator.

In V1.5, the default output directory for saved config files was changed to the MRDS root directory (if no path is specified in the Partner attribute). For this reason, it is preferable to supply the filename in the

131

www.it-ebooks.info

Part I: Robotics Developer Studio Fundamentals

manifest file. Then the file will be saved to the same directory as the manifest. Alternatively, as explained in the last section, you can specify an explicit path.

Saved state files are in XML format. In the case of ServiceB, the first time the state is saved it should look like the following:

<?xml version=”1.0” encoding=”utf-8”?>

<ServiceBState xmlns:s=”http://www.w3.org/2003/05/soap-envelope” xmlns:wsa=”http://schemas.xmlsoap.org/ws/2004/08/addressing” xmlns:d=”http://schemas.microsoft.com/xw/2004/10/dssp.html” xmlns=”http://schemas.tempuri.org/2008/01/serviceb.html”>

<Interval>1000</Interval>

<SensorValue>0</SensorValue>

</ServiceBState>

You can edit the saved state and change the Interval. The file is in the MRDS root directory and is called ServiceB.Config.xml. Open this file in Notepad and change Interval to 200. Save the file.

Rerun ServiceB. The output should appear a lot faster. What you have done is to change the configuration of ServiceB without modifying the code and recompiling. Even though ServiceB rewrites the config file every time it runs, it simply propagates what is already there.

Modifying Service State

The DSS Protocol defines several types of operations that you can use to modify the state of a service, but only two of them are commonly used in MRDS: Replace and Update.

Replace messages are not used very often, so some services do not implement this operation. It is easy to understand why if you consider that a sophisticated service has a lot of properties in the state and these properties might not be directly related to one another. For example, you would rarely want to set the sensor polling interval, the serial COM port for communications, and the power to the motors at the same time.

This is where Update comes in. The Generic Brick contract in Chapter 17 defines separate Update messages for several tasks, including setting the drive power, changing the brick configuration parameters, and turning on LEDs.

Replace

A Replace message supplies a completely new copy of the state. Because the Body of the message contains an instance of the state that is specific to this service, you must define a Replace class in your service types file (where xxx is the service name):

public class Replace : Replace<xxxState, PortSet<DefaultReplaceResponseType, Fault>>

{

public Replace()

{

}

public Replace(xxxState body)

132