h-opc icon indicating copy to clipboard operation
h-opc copied to clipboard

Connecting to server with spaces in URL

Open Syphontwo opened this issue 6 years ago • 8 comments

I see on the version posted here is GitHub, there is a comment in h-opc/h-opc/Da/DaClient.cs which states

    /// <param name="serverUrl">The url of the server to connect to. WARNING: If server URL includes
    /// spaces (ex. "RSLinx OPC Server") then pass the server URL in to the constructor as an Opc.URL object
    /// directly instead.</param>

However the constructor:

    public DaClient(URL serverUrl)
    {
      _url = serverUrl;
    }

Is not available when using the package through visual studios NuGet Package Manager.

According to the NuGet package manager I have installed version 0.9.3 In this version, only the System.URI constructor is present. This is the latest version available via NuGet.

I was able to clone the repository, build it, and add it to a project. After this, I was able to build use it as expected. This issue appears to be related to the latest NuGet version not matching the source here on GitHub.

Syphontwo avatar Jun 01 '18 20:06 Syphontwo

I think that this is still a problem. Could you post a bit of code to show how you got around this? I'm using the latest binaries, and they still throw an exception when there's a space in the name.

Nubber99 avatar Jan 22 '20 19:01 Nubber99

In fact, the following piece of code does work and it's using the same OPC Foundation DLL's. The OPC Server does have spaces in it's name. So i's not the OPC Foundatrion DLL's. I'm connecting to a DA server, bye the way.

Public Sub MyOPC() Dim url As Opc.URL = New Opc.URL("opcda://localhost/National Instruments.Variable Engine.1") Dim server As Opc.Da.Server = Nothing Dim fact As OpcCom.Factory = New OpcCom.Factory server = New Opc.Da.Server(fact, Nothing) server.Connect(url, New Opc.ConnectData(New System.Net.NetworkCredential))

    Dim sstate As Opc.Da.ServerStatus
    sstate = server.GetStatus()
    If sstate.ServerState <> Opc.Da.serverState.running Then
        Console.Write("Server not running")
        Application.Exit()
    End If

Nubber99 avatar Jan 22 '20 19:01 Nubber99

Unfortunately, I do not have that working code on hand. I'll have to do a lot of digging to find it. I was able to locate another useful snippit though, this is an overload for the Connect method in Da/DaClinet.cs

I will continue to try and find the connection method I changed to allow spaces in the URL, but no promises. I have since left the role where I did this work and most of the code stayed with my previous employer (except some of the open source work I did, which this may have been in that folder).

     /// Connect using username and password credentials
     /// </summary>
     /// <param name="userName">the username to use with NetworkCredentials</param>
     /// <param name="password">the password to use with NetworkCredentials</param>
     public void Connect(string userName, string password)
     {
       if (Status == OpcStatus.Connected)
         return;
       _server = new OpcDa.Server(new Factory(), _url);
       _server.Connect(new ConnectData(new System.Net.NetworkCredential(userName, password)));
       var root = new DaNode(string.Empty, string.Empty);
       RootNode = root;
       AddNodeToCache(root);
     }

Syphontwo avatar Jan 23 '20 14:01 Syphontwo

Now I remember...

It isn't a code change that fixed it. I had to bring the project down from github, then manually rebuild it as part of a larger project. This forced it to grab a newer version of the NuGet repositories which allowed it to run with the space character in URLs. All the code I wrote was to add functionality that I required for my specific project, such as username and password login.

Sorry I can't offer much more help than that. Try grabbing the repository and building it yourself, bringing the NuGet packages to their latest versions. That worked over a year ago, so things might be different now...

Syphontwo avatar Jan 23 '20 14:01 Syphontwo

Thanks for the reply. I tried what you suggested, but it still doesn't work. The URL has a %20 in it (for the space) and it looks like OpcNetApi.dll doesn't know what to do with that. Looks like I'll have to try another solution. image

Nubber99 avatar Jan 23 '20 17:01 Nubber99

I found some snippits of old code that may or may not be helpful.

Looks like I stored their info as a URI in my own Server objects

        /// <summary>
        /// Full URI to access server using Hylasoft.Opc
        /// </summary>
        [XmlElement("URI")]
        public string Uri { get; set; }

I had some prep work before connecting

        /// <summary>
        /// Get a new client ready to connect, but do not actually connect it
        /// </summary>
        /// <returns>new, prepared client</returns>
        private DaClient PrepClient()
        {
            var info = ConnectionInformation.Servers.Instance.ServersList.PLCServers.FirstOrDefault((e) => e.Name == this.Name);
            return new DaClient(
                new Opc.URL(info.Uri + "/" + info.Pid)
                );
        }

I then had a whole series of functions to check for connection errors and such, but here is the method I used to connect. NOTE: this uses the username and password credential functionality that I added in a fork to the DaClient. Check my public repositories if you are interested in that.

private DaClient client;

        /// <summary>
        /// Attempt to connect to the prepared client.  Increment a counter on any failure, then rethrow internal error
        /// </summary>
        /// <returns>connection status after attempt to connect</returns>
        public ConnectionStatus Connect()
        {
            var credentials = ConnectionInformation.Servers.Instance.ServersList.PLCServers
                .FirstOrDefault((e) => e.Name == this.Name).Credentials;
            try //try to connect
            {
                this.client.Connect(credentials.Username, credentials.Password);
            }
            catch (Exception) //on Any failure, increment failed connection count and rethrow to caller
            {
                this.FailedConnectionAttempts += 1;
                if (this.FailedConnectionAttempts >= int.MaxValue - 1) //just to prevent overflow
                {
                    this.FailedConnectionAttempts = 0;
                }
                //Add error logging here for connection debugging
                throw;
            }

            return this.Status;
        }

Syphontwo avatar Jan 23 '20 20:01 Syphontwo

Just adding another post to re-iterate what I -believe- was the most important bit of info.

Store the URL as a URI object. Then, when creating a new DaClient pass the URI as a new Opc.URL using new Opc.URL(URI uri). I believe the automatic URI to string conversion that occurs when you call that constructor should allow you to get around space issue.

This is my best guess at the black magic I performed a few years ago. Like I said, this is old stuff that I only have bits and pieces of laying around.

Syphontwo avatar Jan 23 '20 20:01 Syphontwo

You cracked it!

Here's what I did: (VB)

Dim myurl As New Uri("opcda://localhost/National Instruments.Variable Engine.1") myclient = New Hylasoft.Opc.Da.DaClient(New Opc.URL(myurl.ToString)) or, in a shorter fashion: myclient = New Hylasoft.Opc.Da.DaClient(New Opc.URL("opcda://localhost/National Instruments.Variable Engine.1")) And it works!

Many thanks. This should be in the documentation.

Nubber99 avatar Jan 24 '20 15:01 Nubber99