diff --git a/.gitignore b/.gitignore index 6a9856f3..971ffc61 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build/ dist/ __pycache__/ .vscode/ +.idea/ diff --git a/README.md b/README.md index 680136c0..f4258ecb 100644 --- a/README.md +++ b/README.md @@ -172,8 +172,8 @@ The current set of supported services is only for the high-level client. | Service Set | Service | Supported | Notes | |-----------------------------|-------------------------------|-----------|--------------| -| Discovery Service Set | FindServers | | | -| | FindServersOnNetwork | | | +| Discovery Service Set | FindServers | Yes | | +| | FindServersOnNetwork | Yes | | | | GetEndpoints | Yes | | | | RegisterServer | | | | | RegisterServer2 | | | diff --git a/client.go b/client.go index 17cadd21..5204cb0f 100644 --- a/client.go +++ b/client.go @@ -27,6 +27,44 @@ import ( "github.com/gopcua/opcua/uasc" ) +// FindServers returns the Servers known to a Server or Discovery Server. +func FindServers(ctx context.Context, endpoint string, opts ...Option) ([]*ua.ApplicationDescription, error) { + opts = append(opts, AutoReconnect(false)) + c := NewClient(endpoint, opts...) + if err := c.Dial(ctx); err != nil { + return nil, err + } + defer func(c *Client, ctx context.Context) { + if err := c.Close(ctx); err != nil { + // TODO(sruehl): log error + } + }(c, ctx) + res, err := c.FindServers(ctx) + if err != nil { + return nil, err + } + return res.Servers, nil +} + +// FindServersOnNetwork returns the Servers known to a Server or Discovery Server. Unlike FindServers, this Service is only implemented by Discovery Servers. +func FindServersOnNetwork(ctx context.Context, endpoint string, opts ...Option) ([]*ua.ServerOnNetwork, error) { + opts = append(opts, AutoReconnect(false)) + c := NewClient(endpoint, opts...) + if err := c.Dial(ctx); err != nil { + return nil, err + } + defer func(c *Client, ctx context.Context) { + if err := c.Close(ctx); err != nil { + // TODO(sruehl): log error + } + }(c, ctx) + res, err := c.FindServersOnNetwork(ctx) + if err != nil { + return nil, err + } + return res.Servers, nil +} + // GetEndpoints returns the available endpoint descriptions for the server. func GetEndpoints(ctx context.Context, endpoint string, opts ...Option) ([]*ua.EndpointDescription, error) { opts = append(opts, AutoReconnect(false)) @@ -936,6 +974,32 @@ func (c *Client) Node(id *ua.NodeID) *Node { return &Node{ID: id, c: c} } +// FindServers finds the servers available at an endpoint +func (c *Client) FindServers(ctx context.Context) (*ua.FindServersResponse, error) { + stats.Client().Add("FindServers", 1) + + req := &ua.FindServersRequest{ + EndpointURL: c.endpointURL, + } + var res *ua.FindServersResponse + err := c.Send(ctx, req, func(v interface{}) error { + return safeAssign(v, &res) + }) + return res, err +} + +// FindServersOnNetwork finds the servers available at an endpoint +func (c *Client) FindServersOnNetwork(ctx context.Context) (*ua.FindServersOnNetworkResponse, error) { + stats.Client().Add("FindServers", 1) + + req := &ua.FindServersOnNetworkRequest{} + var res *ua.FindServersOnNetworkResponse + err := c.Send(ctx, req, func(v interface{}) error { + return safeAssign(v, &res) + }) + return res, err +} + // GetEndpoints returns the list of available endpoints of the server. func (c *Client) GetEndpoints(ctx context.Context) (*ua.GetEndpointsResponse, error) { stats.Client().Add("GetEndpoints", 1) diff --git a/examples/discovery/discovery.go b/examples/discovery/discovery.go new file mode 100644 index 00000000..65f2f220 --- /dev/null +++ b/examples/discovery/discovery.go @@ -0,0 +1,53 @@ +// Copyright 2018-2020 opcua authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +package main + +import ( + "context" + "flag" + "fmt" + "github.com/gopcua/opcua" + "github.com/gopcua/opcua/debug" + "log" +) + +func main() { + endpoint := flag.String("endpoint", "opc.tcp://localhost:4840", "OPC UA Endpoint URL") + flag.BoolVar(&debug.Enable, "debug", false, "enable debug logging") + flag.Parse() + log.SetFlags(0) + + ctx := context.Background() + + log.Println("Finding servers on network") + serversOnNetwork, err := opcua.FindServersOnNetwork(ctx, *endpoint) + if err == nil { + for i, server := range serversOnNetwork { + fmt.Printf("%d Server on network:\n", i) + fmt.Printf(" -- RecordID: %v\n", server.RecordID) + fmt.Printf(" -- ServerName: %v\n", server.ServerName) + fmt.Printf(" -- DiscoveryURL: %v\n", server.DiscoveryURL) + fmt.Printf(" -- ServerCapabilities: %v\n", server.ServerCapabilities) + } + } else { + log.Printf("Error calling find servers on network: %v", err) + } + + log.Println("Finding servers") + servers, err := opcua.FindServers(ctx, *endpoint) + if err != nil { + log.Fatal(err) + } + for i, server := range servers { + fmt.Printf("%dth Server:\n", i+1) + fmt.Printf(" -- ApplicationURI: %v\n", server.ApplicationURI) + fmt.Printf(" -- ProductURI: %v\n", server.ProductURI) + fmt.Printf(" -- ApplicationName: %v\n", server.ApplicationName) + fmt.Printf(" -- ApplicationType: %v\n", server.ApplicationType) + fmt.Printf(" -- GatewayServerURI: %v\n", server.GatewayServerURI) + fmt.Printf(" -- DiscoveryProfileURI: %v\n", server.DiscoveryProfileURI) + fmt.Printf(" -- DiscoveryURLs: %v\n", server.DiscoveryURLs) + } +}