locked
Remote DSS Node communication hack. Why does this work? RRS feed

  • Question

  • Hi,

    I have successfully achieved communication between two DSS nodes. But, I had to code a "hack" to get remote communications to work and I am not sure why I needed to. I was hoping someone could explain it or at least tell me how I should change my code to not need this hack.

    My environment is:

    Hardware Controller <--RS232--> DssHost Brick Service <--TCP/IP--> DSS Runtime hosted in a WPF app.

    Currently both the DssHost Controller (port 50000/1) and the WPF hosted Dss Runtime (port 60000/1) are on the same machine sharing the same C:\Microsoft Robotics Studio (1.5)\bin directory.

    I have my brick service running on the DssHost Controller and this is used by the WPF application to query, via DssEnvironment.ServiceForwarder, the hardware controller state.

    In the script to start the remote DssHost I specify:

    /p:50000 /t:50001 /h:remotePC /m:"C:\Microsoft Robotics Studio (1.5)\manifests\MyBrickService.manifest.xml"

    In the WPF application I initialize the DssEnvironment as shown in the new Hosting Tutorials:

    DssEnvironment.Initialize(60000, 60001,
                    LayoutPaths.RootDir + LayoutPaths.SampleDir + @"config\TextToSpeech.manifest.xml");

    The only local service this app uses is the TextToSpeech service. All other service requests are made on the remote Dss node.

    This is where the "hack" comes in. In order to communicate sucessfully with the "remote" Dss node I perform a Get on the local Dss node for the Control Panel. This causes the local node to rebuild its contract directory cache. It is only after this has been done that remote service requests succeed. Without this hack I get "Failed outbound message" errors.

    Am I doing something wrong here?

    Any help much appreciated.

    Cheers
    Reece
    Sunday, July 15, 2007 6:06 AM

Answers

  • Hi the error you get indicates serialization problems which are most likely due to issues relating to a proxy/transform. Using Dbgview.exe or some utility that gives you debug console info, you should see serialization failures.

     

    If you dont start a node with a manifest, then we have no idea what types to load. So if you try to send messages to a remote service, we might have not locally loaded the proxy types for that service. THis never happens if you use DssHost, since services that talk to other services, link with ther proxies, and the runtime does everything you need under the covers.

     

    DssEnvironment however, while giving you interop and flexibility, its late very late bound and has this issue: Since it does not force you to write a service to talk to a service (which has to link with the proxy DLL of the remote service, thus its types get loaded), its runtime does not have the types available.

     

    Ways around this:

     

    1) Issue a QueryServiceContract, using the contract of your brick service, on the "local" node. This will force the types to load, and just those types

    2) In your dssEnvironment node, the local node, start a simple/stub service that has as a partner the remote brick service. Start the stub using DssEnvironment INitialize(pass a simple manifest)

     

    1) should be about 5 lines of code. An example:

     

    Code Snippet
               
    using cd = Microsoft.Dss.Services.ContractDirectory.Proxy;
    .....
    ....
                cd.ContractResults results = null;
                cd.QueryRequest body = new cd.QueryRequest();
                body.DssContract = brickService.Contract.Identifier;
                body.IncludeReferencedContracts = true;
                body.LoadTransforms = bode.LoadProxies = true;
                body.ResultTypes = cd.ResultTypes.AssemblyLocations | cd.ResultTypes.Compact | cd.ResultTypes.Simple;
                body.RequiredResults = cd.RequiredResults.Any;
                cd.QueryServiceContract query = new cd.QueryServiceContract(body);
                query.TimeSpan = DsspOperation.DefaultLongTimeSpan;
                _contractDir.Post(query);
                yield return Arbiter.Choice(
                    query.ResponsePort,
                    delegate (cd.ContractResults res) {results = res;},
                    delegate (Fault f) {start.ResultPort.Post(new Exception("fault"));}
                );

     

    Now, moving forward, DSS should do all this for you, when using DssEnvironment. I will open a bug. Until then, you can create a little helper that does this given a contract, and returns an ITask (which you can simply yield too) or a response port.

     

    g

    Sunday, July 15, 2007 6:30 AM

All replies

  • Hi the error you get indicates serialization problems which are most likely due to issues relating to a proxy/transform. Using Dbgview.exe or some utility that gives you debug console info, you should see serialization failures.

     

    If you dont start a node with a manifest, then we have no idea what types to load. So if you try to send messages to a remote service, we might have not locally loaded the proxy types for that service. THis never happens if you use DssHost, since services that talk to other services, link with ther proxies, and the runtime does everything you need under the covers.

     

    DssEnvironment however, while giving you interop and flexibility, its late very late bound and has this issue: Since it does not force you to write a service to talk to a service (which has to link with the proxy DLL of the remote service, thus its types get loaded), its runtime does not have the types available.

     

    Ways around this:

     

    1) Issue a QueryServiceContract, using the contract of your brick service, on the "local" node. This will force the types to load, and just those types

    2) In your dssEnvironment node, the local node, start a simple/stub service that has as a partner the remote brick service. Start the stub using DssEnvironment INitialize(pass a simple manifest)

     

    1) should be about 5 lines of code. An example:

     

    Code Snippet
               
    using cd = Microsoft.Dss.Services.ContractDirectory.Proxy;
    .....
    ....
                cd.ContractResults results = null;
                cd.QueryRequest body = new cd.QueryRequest();
                body.DssContract = brickService.Contract.Identifier;
                body.IncludeReferencedContracts = true;
                body.LoadTransforms = bode.LoadProxies = true;
                body.ResultTypes = cd.ResultTypes.AssemblyLocations | cd.ResultTypes.Compact | cd.ResultTypes.Simple;
                body.RequiredResults = cd.RequiredResults.Any;
                cd.QueryServiceContract query = new cd.QueryServiceContract(body);
                query.TimeSpan = DsspOperation.DefaultLongTimeSpan;
                _contractDir.Post(query);
                yield return Arbiter.Choice(
                    query.ResponsePort,
                    delegate (cd.ContractResults res) {results = res;},
                    delegate (Fault f) {start.ResultPort.Post(new Exception("fault"));}
                );

     

    Now, moving forward, DSS should do all this for you, when using DssEnvironment. I will open a bug. Until then, you can create a little helper that does this given a contract, and returns an ITask (which you can simply yield too) or a response port.

     

    g

    Sunday, July 15, 2007 6:30 AM
  • Hi George,


    Thanks very much for your answer.


    I know I should know the answer to this question...but...can you please show me how you define the _contractDir port set? I can't figure out what operation class provides the QueryServiceContract operation.


    Your help is appreciated.


    Cheers
    Reece

    Sunday, July 15, 2007 7:57 AM
  • Okay...I found it!
     
    Code Snippet

    cd.ContractDirectoryPort _contractDir = DssEnvironment.ServiceForwarder<cd.ContractDirectoryPort>(new Uri("dssp.tcp://localPC:60001/contractdirectory"));



    Thanks for your help. This worked great :-)

    Cheers
    Reece
    Sunday, July 15, 2007 8:08 AM
  • note my recent edit: you should specify Load Proxies , LoadTransforms in the request to the contract directory, to true...
    Monday, July 16, 2007 5:06 AM
  • Also, you can get access to know core services by doing this:

     

    ServiceForwarder<T> (base.Environment.ServiceUriTable[contractIdentifier])

     

    basically we have a dictionary with URIs, keyed of their contract ids, a spart of your service environment

     

    Monday, July 16, 2007 5:08 AM
  • Hi George,

    Thank you for the update. I have made the edits and all is working great.

    Cheers
    Reece
    Tuesday, July 17, 2007 3:57 AM
  • Hi ReeceR

    I’m trying to do what you done but I can’t make it work, I have a sample code of my application can you please help me.

     

    Many thanks

     

    I have a simple service (SimpleService) which is hosted by dssHost in p:50000 t:50001 in the local machine and in the second application I'm hosting a DSS node with DssEnvironment and I am passing Simple Service manifest, so I have a instance of SimpleService on each node, so the proxy for this service should be loaded on each node:

     

    Code Block

            static int Main()

            {

                int _status = 1;

    DssEnvironment.Initialize(40000, 40001,

    @"C:\Documents and Settings\matt.majedi\My Documents\Visual Studio 2005\Projects\Robotic\SampleServices\SimpleService\SimpleService\SimpleService.manifest.xml");

                Arbiter.Activate(DssEnvironment.TaskQueue, Arbiter.FromIteratorHandler(RunTask));

                DssEnvironment.WaitForShutdown();

                return _status;

            }

            static IEnumerator<ITask> RunTask()

            {

                //rebuild contract directory

                cd.ContractDirectoryPort _contractDir = DssEnvironment.ServiceForwarder<cd.ContractDirectoryPort>(new Uri("dssp.tcp://tic-test-matt:40001/controlpanel"));

                yield return Arbiter.Choice(

                     _contractDir.Get(),

                     delegate(cd.State res) {System.Windows.Forms.MessageBox.Show("State Come"); },

                     delegate(Fault f) { System.Windows.Forms.MessageBox.Show(f.Reason[0].Value); }

                 );

     

                ss.SimpleOperations ssPort = DssEnvironment.ServiceForwarder<ss.SimpleOperations>(new Uri("dssp.tcp://tic-test-matt:50001/simpleservice"));

     

                //yield break;

                string s = "No result";

                yield return Arbiter.Choice(

                     ssPort.Get(),

                     delegate(ss.SimpleState res) {

                         s = "Name is: " + res.Name;

                     },

                     delegate(Fault f) {

                         s = "Remote fault " + f.Reason[0].Value;

                     }

                 );

                System.Windows.Forms.MessageBox.Show(s);

            }

     

     

    My application always stock in yield return, not even Fault happen. and If I try to use service forwarder to access http://tic-test-matt:50001/simpleservice I will have  "Failed outbound message" .

    Any help would be much appreciated.

     

     

    Thursday, November 15, 2007 3:29 PM
  • I'm really sorry, but I sort it out. I just disable authentication on my first service on: http://localhost:50000/security/manager

     

    Thanks

    Thursday, November 15, 2007 5:07 PM
  • Hi Reece, Hi George

    I'm trying to reproduce Scenario of Reece,

    Node S (server) starting a node on /p:50030 /t:50031 without manifest.
    Application A starting internal node C (client) on /p:50010, /t:50011 , connects to service directory of node S, search for Math service. If service does not exists, its starts, using constructor service.
    Than application creates a port to the math service on S, and than tries to send SqrtRequest,

    Code Snippet          

    yield return Arbiter.Choice(

    mathPort.Sqrt(new math.SqrtRequest() { A = 4 }),

              delegate(math.Response response) {
                        DssEnvironment.LogInfo("Result = " + response.Result.ToString());

              },

              delegate(Fault f)

    DssEnvironment.LogError("Math failed:" + f.ToString());
    }

    );

              

                   

    which fails with 

    Proxy type not found for outbound request or response. Make sure you have compiled a proxy and transform dll for the target service.Outbound type:Microsoft.Robotics.Services.Sample.Math.Proxy.SqrtRequest
    fault.
    Seems to be exactly the problem with not registred proxy types.
    I've tried the trick with QueryByContract

    Code Snippet
    using cd = Microsoft.Dss.Services.ContractDirectory.Proxy;
    using math = Microsoft.Robotics.Services.Sample.Math.Proxy;

         W3C.Soap.Fault Failure = null;


    ...

    // force local load of proxy dll


                    ServiceInfoType cdsit = null;
                    yield return Arbiter.Choice(DssEnvironment.DirectoryQuery(cd.Contract.Identifier),
                        (ServiceInfoType sit) => { cdsit = sit; },
                        (W3C.Soap.Fault failure) => { Failure = failure; }
                        );
                    if (Failure != null) yield break;

                    var contractDir =
                        DssEnvironment.ServiceForwarder<cd.ContractDirectoryPort>(new Uri(cdsit.Service));

                    var cdqbcr = new cd.QueryByContractRequest() {
                        Contract = math.Contract.Identifier,
                        LoadProxies = true,
                        IncludeReferencedContracts = true,
                        IncludeRunningServices = true,
                        LoadTransforms = true,
                        SearchAlternateContracts = true
                    };

                    cd.QueryByContract cdqbc = new cd.QueryByContract(cdqbcr);
                    cdqbc.TimeSpan = DsspOperation.DefaultLongTimeSpan;

                    contractDir.Post(cdqbc);

                    cd.ContractResults results = null;


                    yield return Arbiter.Choice(
                        cdqbc.ResponsePort,
                        delegate(cd.ContractResults res) {
                            results = res;
                        },
                        delegate(Fault failure) {
                            Failure = failure;
                            DssEnvironment.LogError(failure.ToString());
                        }
                    );

                  

    I get no fault here, but still one on SqrtRequest.
    The trick with ControlPanel.Get does not work either.

    I tried, both July CTP and final 2008 Expression.

    Complete project is here
    http://www.lunohod.com/Transfer/RemoteDss.zip

    What does I do wrong?
     
    Thank you!
    Roman
    Monday, December 1, 2008 9:08 AM
  • Hi All

    I found out that my application didnt't run because been started from bin\Debug direcory. Visual studio copies proxy assemblies to the directory, and code loads them from there, so they are not the same as the ones in MRDS\bin.

    If I start the build application directly with command line from MRDS\bin directory everething works fine.

    Thanks for your attention!

    Roman
    Monday, December 1, 2008 11:55 PM