github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/integration/OPCClient/opcclient.go (about)

     1  package opcclient
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"os"
    11  	"os/signal"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/gopcua/opcua"
    16  	"github.com/gopcua/opcua/id"
    17  	"github.com/gopcua/opcua/ua"
    18  	"github.com/mdaxf/iac/logger"
    19  )
    20  
    21  type OPCConfig struct {
    22  	OPCClients []OPCClient `json:"opcclients"`
    23  }
    24  
    25  type OPCClient struct {
    26  	Client    *opcua.Client
    27  	Endpoint  string        `json:"endpoint"`
    28  	Host      string        `json:"host"`
    29  	Name      string        `json:"name"`
    30  	Namespace uint16        `json:"namespace"`
    31  	CertFile  string        `json:"certFile"`
    32  	KeyFile   string        `json:"keyFile"`
    33  	Timeout   time.Duration `json:"timeout"`
    34  	Nodes     map[string]*opcua.Node
    35  	SubGroups []SubGroup `json:"subgroups"`
    36  	iLog      logger.Log
    37  }
    38  
    39  type SubGroup struct {
    40  	TriggerTags []string                    `json:"triggerTags"`
    41  	ReportTags  []string                    `json:"reportTags"`
    42  	Trigger     func(string, *ua.DataValue) `json:"trigger"`
    43  	Report      func(string, *ua.DataValue) `json:"report"`
    44  }
    45  
    46  func Initialize(configurations OPCClient) {
    47  	// Initialize the OPC UA client
    48  
    49  	iLog := logger.Log{ModuleName: logger.Framework, User: "System", ControllerName: "OPCClient"}
    50  
    51  	iLog.Debug(fmt.Sprintf(("Create OPCClient with configuration : %s"), logger.ConvertJson(configurations)))
    52  
    53  	opcclient := &OPCClient{
    54  		Endpoint:  configurations.Endpoint,
    55  		Namespace: configurations.Namespace,
    56  		CertFile:  configurations.CertFile,
    57  		KeyFile:   configurations.KeyFile,
    58  		Timeout:   configurations.Timeout * time.Second,
    59  		SubGroups: configurations.SubGroups,
    60  		iLog:      iLog,
    61  	}
    62  
    63  	cancel := opcclient.CreateClient()
    64  	opcclient.Connect()
    65  
    66  	defer cancel()
    67  	defer opcclient.Disconnect()
    68  	//	defer opcclient.Client.CloseWithContext(context.Background())
    69  
    70  	go func() {
    71  
    72  		for _, subgroup := range opcclient.SubGroups {
    73  
    74  			callbackfunc := func(tag string, v *ua.DataValue) {
    75  				fmt.Printf("Tag: %v, Value: %v\n", tag, v.Value)
    76  			}
    77  			opcclient.Subscribe(subgroup.TriggerTags, callbackfunc)
    78  
    79  		}
    80  	}()
    81  	// Wait for termination signal to gracefully shut down the client
    82  
    83  	terminate := make(chan os.Signal, 1)
    84  	signal.Notify(terminate, os.Interrupt, syscall.SIGTERM)
    85  	<-terminate
    86  }
    87  
    88  func (c *OPCClient) CreateClient() context.CancelFunc {
    89  	// OPC UA server configuration
    90  	var (
    91  		endpoint = flag.String("endpoint", c.Endpoint, "OPC UA Endpoint URL")
    92  		policy   = flag.String("policy", "", "Security policy: None, Basic128Rsa15, Basic256, Basic256Sha256. Default: auto")
    93  		mode     = flag.String("mode", "", "Security mode: None, Sign, SignAndEncrypt. Default: auto")
    94  		certFile = flag.String("cert", "", "Path to cert.pem. Required for security mode/policy != None")
    95  		keyFile  = flag.String("key", "", "Path to private key.pem. Required for security mode/policy != None")
    96  	)
    97  	ctx, cancel := context.WithCancel(context.Background())
    98  
    99  	endpoints, err := opcua.GetEndpoints(ctx, *endpoint)
   100  	if err != nil {
   101  		c.iLog.Critical(fmt.Sprintf("error: %v", err))
   102  	}
   103  
   104  	ep := opcua.SelectEndpoint(endpoints, *policy, ua.MessageSecurityModeFromString(*mode))
   105  	if ep == nil {
   106  		c.iLog.Critical("Failed to find suitable endpoint")
   107  	}
   108  	c.iLog.Debug(fmt.Sprintf("Using endpoint: %v", ep.EndpointURL))
   109  	c.iLog.Debug(fmt.Sprintf("%s,%s", ep.SecurityPolicyURI, ep.SecurityMode))
   110  	opts := []opcua.Option{
   111  		opcua.SecurityPolicy(*policy),
   112  		opcua.SecurityModeString(*mode),
   113  		opcua.CertificateFile(*certFile),
   114  		opcua.PrivateKeyFile(*keyFile),
   115  		opcua.AuthAnonymous(),
   116  		opcua.SecurityFromEndpoint(ep, ua.UserTokenTypeAnonymous),
   117  	}
   118  
   119  	opcclient, err := opcua.NewClient(ep.EndpointURL, opts...)
   120  
   121  	if err != nil {
   122  		c.iLog.Critical(fmt.Sprintf("Failed to create client: %s", err))
   123  	}
   124  
   125  	c.Client = opcclient
   126  
   127  	return cancel
   128  }
   129  
   130  func (c *OPCClient) BrowseEndpoints() (*ua.GetEndpointsResponse, error) {
   131  	// Browse the endpoint by the server
   132  	c.iLog.Debug(fmt.Sprintf("Browsing endpoints from server"))
   133  	endpoints, err := c.Client.GetEndpoints(context.Background())
   134  	if err != nil {
   135  		c.iLog.Critical(fmt.Sprintf("Failed to get endpoints: %s", err))
   136  		return nil, err
   137  	}
   138  	c.iLog.Debug(fmt.Sprintf("Endpoints: %v", endpoints))
   139  
   140  	return endpoints, nil
   141  }
   142  func (c *OPCClient) Connect() {
   143  
   144  	c.iLog.Debug(fmt.Sprintf("Connectting to OPC Server: %s", c.Endpoint))
   145  	//	defer client.Close()
   146  
   147  	// Connect to the OPC UA server
   148  	err := c.Client.Connect(context.Background())
   149  	if err != nil {
   150  		c.iLog.Critical(fmt.Sprintf("Failed to connect to OPC UA server: %s", err))
   151  	}
   152  	c.iLog.Debug(fmt.Sprintf("Connected to OPC Server: %s", c.Endpoint))
   153  }
   154  
   155  func (c *OPCClient) browseTags(nodeID string) ([]Tag, error) {
   156  	// Browse the node
   157  	c.iLog.Debug(fmt.Sprintf("Browsing nodes (tags) from server"))
   158  	result, err := c.Client.Browse(context.Background(), &ua.BrowseRequest{
   159  		NodesToBrowse: []*ua.BrowseDescription{
   160  			&ua.BrowseDescription{
   161  				NodeID:          ua.MustParseNodeID(nodeID),
   162  				ReferenceTypeID: ua.NewNumericNodeID(0, 0),
   163  				IncludeSubtypes: true,
   164  				ResultMask:      uint32(ua.BrowseResultMaskAll),
   165  			},
   166  		},
   167  	})
   168  	if err != nil {
   169  		c.iLog.Error(fmt.Sprintf("Failed to browse nodes: %s", err))
   170  		return nil, err
   171  	}
   172  
   173  	c.iLog.Debug(fmt.Sprintf("Browse result: %v", result))
   174  
   175  	var tags []Tag
   176  
   177  	// Process the browse result
   178  	for _, res := range result.Results {
   179  		for _, ref := range res.References {
   180  			// Extract the tag name and address from the reference
   181  			tagName := ref.DisplayName.Text
   182  			tagAddress := ref.NodeID.String()
   183  
   184  			// Create a new tag object
   185  			tag := Tag{
   186  				Name:    tagName,
   187  				Address: tagAddress,
   188  			}
   189  
   190  			// Add the tag to the list
   191  			tags = append(tags, tag)
   192  		}
   193  	}
   194  	c.iLog.Debug(fmt.Sprintf("Tags: %v", tags))
   195  	return tags, nil
   196  }
   197  
   198  func (c *OPCClient) readTagValue(nodeID string) (interface{}, error) {
   199  	// Read the node value
   200  	c.iLog.Debug(fmt.Sprintf("Reading node value: %s", nodeID))
   201  	opcnodeid := flag.String("node", "", nodeID)
   202  
   203  	id, err := ua.ParseNodeID(*opcnodeid)
   204  	if err != nil {
   205  		c.iLog.Critical(fmt.Sprintf("invalid node id: %v", err))
   206  	}
   207  
   208  	req := &ua.ReadRequest{
   209  		MaxAge: 2000,
   210  		NodesToRead: []*ua.ReadValueID{
   211  			{NodeID: id},
   212  		},
   213  		TimestampsToReturn: ua.TimestampsToReturnBoth,
   214  	}
   215  
   216  	var resp *ua.ReadResponse
   217  
   218  	for {
   219  		resp, err = c.Client.Read(context.Background(), req) //.ReadWithContext(context.Background(), req)
   220  		if err == nil {
   221  			break
   222  		}
   223  
   224  		// Following switch contains known errors that can be retried by the user.
   225  		// Best practice is to do it on read operations.
   226  		switch {
   227  		case err == io.EOF && c.Client.State() != opcua.Closed:
   228  			// has to be retried unless user closed the connection
   229  			time.After(1 * time.Second)
   230  			continue
   231  
   232  		case errors.Is(err, ua.StatusBadSessionIDInvalid):
   233  			// Session is not activated has to be retried. Session will be recreated internally.
   234  			time.After(1 * time.Second)
   235  			continue
   236  
   237  		case errors.Is(err, ua.StatusBadSessionNotActivated):
   238  			// Session is invalid has to be retried. Session will be recreated internally.
   239  			time.After(1 * time.Second)
   240  			continue
   241  
   242  		case errors.Is(err, ua.StatusBadSecureChannelIDInvalid):
   243  			// secure channel will be recreated internally.
   244  			time.After(1 * time.Second)
   245  			continue
   246  
   247  		default:
   248  			c.iLog.Critical(fmt.Sprintf("Read failed: %s", err))
   249  		}
   250  	}
   251  
   252  	if resp != nil && resp.Results[0].Status != ua.StatusOK {
   253  		c.iLog.Critical(fmt.Sprintf("Status not OK: %v", resp.Results[0].Status))
   254  	}
   255  
   256  	return resp.Results[0].Value.Value(), nil
   257  }
   258  
   259  func (c *OPCClient) writeTagValue(nodeID string, value string) (ua.StatusCode, error) {
   260  
   261  	opcnodeid := flag.String("node", "", nodeID)
   262  	opcvalue := flag.String("value", "", value)
   263  
   264  	// Create a variant from the value
   265  	c.iLog.Debug(fmt.Sprintf("Writing node value: %s, %v", nodeID, value))
   266  	id, err := ua.ParseNodeID(*opcnodeid)
   267  	if err != nil {
   268  		c.iLog.Critical(fmt.Sprintf("invalid node id: %v", err))
   269  	}
   270  
   271  	v, err := ua.NewVariant(*opcvalue)
   272  	if err != nil {
   273  		c.iLog.Critical(fmt.Sprintf("invalid value: %v", err))
   274  	}
   275  
   276  	if err != nil {
   277  		c.iLog.Error(fmt.Sprintf("Failed to create variant: %s", err))
   278  		return ua.StatusBad, err
   279  	}
   280  
   281  	// Write the node value
   282  	req := &ua.WriteRequest{
   283  		NodesToWrite: []*ua.WriteValue{
   284  			{
   285  				NodeID:      id,
   286  				AttributeID: ua.AttributeIDValue,
   287  				Value: &ua.DataValue{
   288  					EncodingMask: ua.DataValueValue,
   289  					Value:        v,
   290  				},
   291  			},
   292  		},
   293  	}
   294  
   295  	resp, err := c.Client.Write(context.Background(), req) //.WriteWithContext(context.Background(), req)
   296  	if err != nil {
   297  		c.iLog.Critical(fmt.Sprintf("Write failed: %s", err))
   298  		return ua.StatusBad, err
   299  	}
   300  
   301  	c.iLog.Debug(fmt.Sprintf("Node value written with result: %v", resp.Results[0]))
   302  	return resp.Results[0], nil
   303  }
   304  
   305  func (c *OPCClient) Subscribe(triggerTags []string, callback func(string, *ua.DataValue)) {
   306  	notifyCh := make(chan *opcua.PublishNotificationData, 1000)
   307  	ctx := context.Background()
   308  	interval := flag.Duration("interval", opcua.DefaultSubscriptionInterval, "subscription interval")
   309  	sub, err := c.Client.Subscribe(ctx, &opcua.SubscriptionParameters{ //).SubscribeWithContext
   310  		Interval: *interval,
   311  	}, notifyCh)
   312  	if err != nil {
   313  		c.iLog.Critical(fmt.Sprintf("Failed to create subscription: %s", err))
   314  	}
   315  	defer sub.Cancel(ctx)
   316  
   317  	event := flag.Bool("event", false, "subscribe to node event changes (Default: node value changes)")
   318  
   319  	for _, tag := range triggerTags {
   320  		// Create a monitored item for the tag
   321  		nodeID := flag.String("node", tag, "node id to subscribe to")
   322  		//nodeID := ua.NewStringNodeID(2, tag)
   323  		id, err := ua.ParseNodeID(*nodeID)
   324  
   325  		if err != nil {
   326  			c.iLog.Critical(fmt.Sprintf("invalid node id: %v", err))
   327  		}
   328  
   329  		var miCreateRequest *ua.MonitoredItemCreateRequest
   330  		var eventFieldNames []string
   331  
   332  		if *event {
   333  			miCreateRequest, eventFieldNames = eventRequest(id)
   334  		} else {
   335  			miCreateRequest = valueRequest(id)
   336  		}
   337  		res, err := sub.Monitor(context.Background(), ua.TimestampsToReturnBoth, miCreateRequest)
   338  		if err != nil || res.Results[0].StatusCode != ua.StatusOK {
   339  			c.iLog.Error(fmt.Sprintf("Failed to create monitored item for tag: %v", err))
   340  		}
   341  		// read from subscription's notification channel until ctx is cancelled
   342  		for {
   343  			select {
   344  			case <-ctx.Done():
   345  				return
   346  			case res := <-notifyCh:
   347  				if res.Error != nil {
   348  					c.iLog.Error(fmt.Sprintf("there is error: %v", res.Error))
   349  					continue
   350  				}
   351  
   352  				switch x := res.Value.(type) {
   353  				case *ua.DataChangeNotification:
   354  					for _, item := range x.MonitoredItems {
   355  						data := item.Value.Value.Value()
   356  						c.iLog.Debug(fmt.Sprintf("MonitoredItem with client handle %v = %v", item.ClientHandle, data))
   357  					}
   358  
   359  				case *ua.EventNotificationList:
   360  					for _, item := range x.Events {
   361  						c.iLog.Debug(fmt.Sprintf("Event for client handle: %v\n", item.ClientHandle))
   362  						for i, field := range item.EventFields {
   363  							c.iLog.Debug(fmt.Sprintf("%v: %v of Type: %T", eventFieldNames[i], field.Value(), field.Value()))
   364  						}
   365  
   366  					}
   367  
   368  				default:
   369  					c.iLog.Debug(fmt.Sprintf("what's this publish result? %T", res.Value))
   370  				}
   371  			}
   372  		}
   373  
   374  	}
   375  }
   376  func valueRequest(nodeID *ua.NodeID) *ua.MonitoredItemCreateRequest {
   377  	handle := uint32(42)
   378  	return opcua.NewMonitoredItemCreateRequestWithDefaults(nodeID, ua.AttributeIDValue, handle)
   379  }
   380  
   381  func eventRequest(nodeID *ua.NodeID) (*ua.MonitoredItemCreateRequest, []string) {
   382  	fieldNames := []string{"EventId", "EventType", "Severity", "Time", "Message"}
   383  	selects := make([]*ua.SimpleAttributeOperand, len(fieldNames))
   384  
   385  	for i, name := range fieldNames {
   386  		selects[i] = &ua.SimpleAttributeOperand{
   387  			TypeDefinitionID: ua.NewNumericNodeID(0, id.BaseEventType),
   388  			BrowsePath:       []*ua.QualifiedName{{NamespaceIndex: 0, Name: name}},
   389  			AttributeID:      ua.AttributeIDValue,
   390  		}
   391  	}
   392  
   393  	wheres := &ua.ContentFilter{
   394  		Elements: []*ua.ContentFilterElement{
   395  			{
   396  				FilterOperator: ua.FilterOperatorGreaterThanOrEqual,
   397  				FilterOperands: []*ua.ExtensionObject{
   398  					{
   399  						EncodingMask: 1,
   400  						TypeID: &ua.ExpandedNodeID{
   401  							NodeID: ua.NewNumericNodeID(0, id.SimpleAttributeOperand_Encoding_DefaultBinary),
   402  						},
   403  						Value: ua.SimpleAttributeOperand{
   404  							TypeDefinitionID: ua.NewNumericNodeID(0, id.BaseEventType),
   405  							BrowsePath:       []*ua.QualifiedName{{NamespaceIndex: 0, Name: "Severity"}},
   406  							AttributeID:      ua.AttributeIDValue,
   407  						},
   408  					},
   409  					{
   410  						EncodingMask: 1,
   411  						TypeID: &ua.ExpandedNodeID{
   412  							NodeID: ua.NewNumericNodeID(0, id.LiteralOperand_Encoding_DefaultBinary),
   413  						},
   414  						Value: ua.LiteralOperand{
   415  							Value: ua.MustVariant(uint16(0)),
   416  						},
   417  					},
   418  				},
   419  			},
   420  		},
   421  	}
   422  
   423  	filter := ua.EventFilter{
   424  		SelectClauses: selects,
   425  		WhereClause:   wheres,
   426  	}
   427  
   428  	filterExtObj := ua.ExtensionObject{
   429  		EncodingMask: ua.ExtensionObjectBinary,
   430  		TypeID: &ua.ExpandedNodeID{
   431  			NodeID: ua.NewNumericNodeID(0, id.EventFilter_Encoding_DefaultBinary),
   432  		},
   433  		Value: filter,
   434  	}
   435  
   436  	handle := uint32(42)
   437  	req := &ua.MonitoredItemCreateRequest{
   438  		ItemToMonitor: &ua.ReadValueID{
   439  			NodeID:       nodeID,
   440  			AttributeID:  ua.AttributeIDEventNotifier,
   441  			DataEncoding: &ua.QualifiedName{},
   442  		},
   443  		MonitoringMode: ua.MonitoringModeReporting,
   444  		RequestedParameters: &ua.MonitoringParameters{
   445  			ClientHandle:     handle,
   446  			DiscardOldest:    true,
   447  			Filter:           &filterExtObj,
   448  			QueueSize:        10,
   449  			SamplingInterval: 1.0,
   450  		},
   451  	}
   452  
   453  	return req, fieldNames
   454  }
   455  
   456  func (c *OPCClient) createSubscriptionGroup(triggerTags []string, callback func(string, *ua.DataValue)) (*opcua.Subscription, error) {
   457  
   458  	notifyCh := make(chan *opcua.PublishNotificationData)
   459  
   460  	sub, err := c.Client.Subscribe(context.Background(), &opcua.SubscriptionParameters{
   461  		Interval: opcua.DefaultSubscriptionInterval,
   462  	}, notifyCh)
   463  
   464  	// 		RequestedPublishingInterval: 1000,
   465  	//RequestedMaxKeepAliveCount:  10,
   466  	//RequestedLifetimeCount:      100,
   467  	//PublishingEnabled:           true,
   468  	if err != nil {
   469  		c.iLog.Critical(fmt.Sprintf("Failed to create subscription: %s", err))
   470  	}
   471  	/*
   472  		for _, tag := range triggerTags {
   473  			// Create a monitored item for the tag
   474  			nodeID := ua.NewStringNodeID(2, tag)
   475  
   476  			item, err := sub.MonitorValue(nodeID, ua.AttributeIDValue, func(v *ua.DataValue) {
   477  				// Trigger action for tag1
   478  				callback(tag, v)
   479  				c.iLog.Debug(fmt.Sprintf("Trigger action for tag1: %v", v.Value))
   480  			})
   481  			if err != nil {
   482  				c.iLog.Error(fmt.Sprintf("Failed to create monitored item for tag1: %v", err))
   483  			}
   484  
   485  		}
   486  	*/
   487  	var nodeID = flag.String("node", "", "node id to subscribe to")
   488  	var event = flag.Bool("event", false, "subscribe to node event changes (Default: node value changes)")
   489  
   490  	id, err := ua.ParseNodeID(*nodeID)
   491  	if err != nil {
   492  		log.Fatal(err)
   493  	}
   494  
   495  	var miCreateRequest *ua.MonitoredItemCreateRequest
   496  	var eventFieldNames []string
   497  	if *event {
   498  		miCreateRequest, eventFieldNames = eventRequest(id)
   499  	} else {
   500  		miCreateRequest = valueRequest(id)
   501  	}
   502  	res, err := sub.Monitor(context.Background(), ua.TimestampsToReturnBoth, miCreateRequest)
   503  	if err != nil || res.Results[0].StatusCode != ua.StatusOK {
   504  		log.Fatal(err)
   505  	}
   506  
   507  	// read from subscription's notification channel until ctx is cancelled
   508  	for {
   509  		select {
   510  		case <-context.Background().Done():
   511  			return sub, nil
   512  		case res := <-notifyCh:
   513  			if res.Error != nil {
   514  				log.Print(res.Error)
   515  				continue
   516  			}
   517  
   518  			switch x := res.Value.(type) {
   519  			case *ua.DataChangeNotification:
   520  				for _, item := range x.MonitoredItems {
   521  					data := item.Value.Value.Value()
   522  					log.Printf("MonitoredItem with client handle %v = %v", item.ClientHandle, data)
   523  				}
   524  
   525  			case *ua.EventNotificationList:
   526  				for _, item := range x.Events {
   527  					log.Printf("Event for client handle: %v\n", item.ClientHandle)
   528  					for i, field := range item.EventFields {
   529  						log.Printf("%v: %v of Type: %T", eventFieldNames[i], field.Value(), field.Value())
   530  					}
   531  					log.Println()
   532  				}
   533  
   534  			default:
   535  				log.Printf("what's this publish result? %T", res.Value)
   536  			}
   537  		}
   538  	}
   539  	return sub, nil
   540  }
   541  
   542  func (c *OPCClient) monitorSubscribeGroup(group []*opcua.Subscription) {
   543  	filter := flag.String("filter", "timestamp", "DataFilter: status, value, timestamp.")
   544  	// Start the subscription
   545  	for _, sub := range group {
   546  
   547  		// need to change
   548  
   549  		triggerNodeID := flag.String("trigger", "", "node id to trigger with")
   550  		reportNodeID := flag.String("report", "", "node id value to report on trigger")
   551  		triggeringNode, err := ua.ParseNodeID(*triggerNodeID)
   552  		if err != nil {
   553  			c.iLog.Critical(fmt.Sprintf("There are error:%v", err))
   554  		}
   555  
   556  		triggeredNode, err := ua.ParseNodeID(*reportNodeID)
   557  		if err != nil {
   558  			c.iLog.Critical(fmt.Sprintf("There are error:%v", err))
   559  		}
   560  
   561  		miCreateRequests := []*ua.MonitoredItemCreateRequest{
   562  			opcua.NewMonitoredItemCreateRequestWithDefaults(triggeringNode, ua.AttributeIDValue, 42),
   563  			{
   564  				ItemToMonitor: &ua.ReadValueID{
   565  					NodeID:       triggeredNode,
   566  					AttributeID:  ua.AttributeIDValue,
   567  					DataEncoding: &ua.QualifiedName{},
   568  				},
   569  				MonitoringMode: ua.MonitoringModeSampling,
   570  				RequestedParameters: &ua.MonitoringParameters{
   571  					ClientHandle:     43,
   572  					DiscardOldest:    true,
   573  					Filter:           c.getFilter(*filter),
   574  					QueueSize:        10,
   575  					SamplingInterval: 0.0,
   576  				},
   577  			},
   578  		}
   579  
   580  		sub.Monitor(context.Background(), ua.TimestampsToReturnBoth, miCreateRequests...)
   581  	}
   582  }
   583  
   584  func (c *OPCClient) getFilter(filterStr string) *ua.ExtensionObject {
   585  
   586  	var filter ua.DataChangeFilter
   587  	switch filterStr {
   588  	case "status":
   589  		filter = ua.DataChangeFilter{Trigger: ua.DataChangeTriggerStatus}
   590  	case "value":
   591  		filter = ua.DataChangeFilter{Trigger: ua.DataChangeTriggerStatusValue}
   592  	case "timestamp":
   593  		filter = ua.DataChangeFilter{Trigger: ua.DataChangeTriggerStatusValueTimestamp}
   594  	default:
   595  		log.Fatalf("Unable to match to a valid filter type: %v\nShould be status, value, or timestamp", filterStr)
   596  	}
   597  
   598  	return &ua.ExtensionObject{
   599  		EncodingMask: ua.ExtensionObjectBinary,
   600  		TypeID: &ua.ExpandedNodeID{
   601  			NodeID: ua.NewNumericNodeID(0, id.DataChangeFilter_Encoding_DefaultBinary),
   602  		},
   603  		Value: filter,
   604  	}
   605  }
   606  
   607  func (c *OPCClient) Disconnect() {
   608  	// Disconnect from the OPC UA server
   609  	c.Client.Close(context.Background())
   610  	c.iLog.Debug(fmt.Sprintf("Disconnected from OPC Server: %s", c.Endpoint))
   611  }
   612  
   613  type Tag struct {
   614  	Name    string
   615  	Address string
   616  }