2022-10-25 22:06:08 +08:00
|
|
|
package opcua
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
2022-11-09 03:04:12 +08:00
|
|
|
|
2022-10-25 22:06:08 +08:00
|
|
|
"github.com/gopcua/opcua/ua"
|
2022-11-09 03:04:12 +08:00
|
|
|
|
2022-10-25 22:06:08 +08:00
|
|
|
"github.com/influxdata/telegraf"
|
|
|
|
|
"github.com/influxdata/telegraf/plugins/common/opcua"
|
|
|
|
|
"github.com/influxdata/telegraf/plugins/common/opcua/input"
|
|
|
|
|
"github.com/influxdata/telegraf/selfstat"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type ReadClientWorkarounds struct {
|
|
|
|
|
UseUnregisteredReads bool `toml:"use_unregistered_reads"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ReadClientConfig struct {
|
|
|
|
|
ReadClientWorkarounds ReadClientWorkarounds `toml:"request_workarounds"`
|
|
|
|
|
input.InputClientConfig
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ReadClient Requests the current values from the required nodes when gather is called.
|
|
|
|
|
type ReadClient struct {
|
|
|
|
|
*input.OpcUAInputClient
|
|
|
|
|
|
|
|
|
|
ReadSuccess selfstat.Stat
|
|
|
|
|
ReadError selfstat.Stat
|
|
|
|
|
Workarounds ReadClientWorkarounds
|
|
|
|
|
|
|
|
|
|
// internal values
|
2024-04-22 21:25:32 +08:00
|
|
|
reqIDs []*ua.ReadValueID
|
|
|
|
|
ctx context.Context
|
2022-10-25 22:06:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (rc *ReadClientConfig) CreateReadClient(log telegraf.Logger) (*ReadClient, error) {
|
|
|
|
|
inputClient, err := rc.InputClientConfig.CreateInputClient(log)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tags := map[string]string{
|
|
|
|
|
"endpoint": inputClient.Config.OpcUAClientConfig.Endpoint,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &ReadClient{
|
|
|
|
|
OpcUAInputClient: inputClient,
|
|
|
|
|
ReadSuccess: selfstat.Register("opcua", "read_success", tags),
|
|
|
|
|
ReadError: selfstat.Register("opcua", "read_error", tags),
|
|
|
|
|
Workarounds: rc.ReadClientWorkarounds,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (o *ReadClient) Connect() error {
|
2024-02-10 03:12:03 +08:00
|
|
|
o.ctx = context.Background()
|
|
|
|
|
|
|
|
|
|
if err := o.OpcUAClient.Connect(o.ctx); err != nil {
|
2023-08-08 04:48:47 +08:00
|
|
|
return fmt.Errorf("connect failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure we setup the node-ids correctly after reconnect
|
|
|
|
|
// as the server might be restarted and IDs changed
|
|
|
|
|
if err := o.OpcUAInputClient.InitNodeIDs(); err != nil {
|
|
|
|
|
return fmt.Errorf("initializing node IDs failed: %w", err)
|
2022-10-25 22:06:08 +08:00
|
|
|
}
|
|
|
|
|
|
2024-04-22 21:25:32 +08:00
|
|
|
o.reqIDs = make([]*ua.ReadValueID, 0, len(o.NodeIDs))
|
2022-10-25 22:06:08 +08:00
|
|
|
if o.Workarounds.UseUnregisteredReads {
|
2022-12-12 22:05:33 +08:00
|
|
|
for _, nid := range o.NodeIDs {
|
2024-04-22 21:25:32 +08:00
|
|
|
o.reqIDs = append(o.reqIDs, &ua.ReadValueID{NodeID: nid})
|
2022-10-25 22:06:08 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
2024-02-10 03:12:03 +08:00
|
|
|
regResp, err := o.Client.RegisterNodes(o.ctx, &ua.RegisterNodesRequest{
|
2022-10-25 22:06:08 +08:00
|
|
|
NodesToRegister: o.NodeIDs,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
2023-08-08 04:48:47 +08:00
|
|
|
return fmt.Errorf("registering nodes failed: %w", err)
|
2022-10-25 22:06:08 +08:00
|
|
|
}
|
|
|
|
|
|
2022-12-12 22:05:33 +08:00
|
|
|
for _, v := range regResp.RegisteredNodeIDs {
|
2024-04-22 21:25:32 +08:00
|
|
|
o.reqIDs = append(o.reqIDs, &ua.ReadValueID{NodeID: v})
|
2022-10-25 22:06:08 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-08 04:48:47 +08:00
|
|
|
if err := o.read(); err != nil {
|
2023-03-02 05:19:38 +08:00
|
|
|
return fmt.Errorf("get data failed: %w", err)
|
2022-10-25 22:06:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (o *ReadClient) ensureConnected() error {
|
2023-07-01 02:49:26 +08:00
|
|
|
if o.State() == opcua.Disconnected {
|
|
|
|
|
return o.Connect()
|
2022-10-25 22:06:08 +08:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (o *ReadClient) CurrentValues() ([]telegraf.Metric, error) {
|
2023-07-01 02:49:26 +08:00
|
|
|
if err := o.ensureConnected(); err != nil {
|
2022-10-25 22:06:08 +08:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-01 02:49:26 +08:00
|
|
|
if state := o.State(); state != opcua.Connected {
|
|
|
|
|
return nil, fmt.Errorf("not connected, in state %q", state)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := o.read(); err != nil {
|
2022-10-25 22:06:08 +08:00
|
|
|
// We do not return the disconnect error, as this would mask the
|
|
|
|
|
// original problem, but we do log it
|
2023-07-01 02:49:26 +08:00
|
|
|
if derr := o.Disconnect(context.Background()); derr != nil {
|
|
|
|
|
o.Log.Debug("Error while disconnecting: ", derr)
|
2022-10-25 22:06:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
metrics := make([]telegraf.Metric, 0, len(o.NodeMetricMapping))
|
|
|
|
|
// Parse the resulting data into metrics
|
|
|
|
|
for i := range o.NodeIDs {
|
|
|
|
|
if !o.StatusCodeOK(o.LastReceivedData[i].Quality) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
metrics = append(metrics, o.MetricForNode(i))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return metrics, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (o *ReadClient) read() error {
|
2024-04-22 21:25:32 +08:00
|
|
|
req := &ua.ReadRequest{
|
|
|
|
|
MaxAge: 2000,
|
|
|
|
|
TimestampsToReturn: ua.TimestampsToReturnBoth,
|
|
|
|
|
NodesToRead: o.reqIDs,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp, err := o.Client.Read(o.ctx, req)
|
2022-10-25 22:06:08 +08:00
|
|
|
if err != nil {
|
|
|
|
|
o.ReadError.Incr(1)
|
2023-03-02 05:19:38 +08:00
|
|
|
return fmt.Errorf("RegisterNodes Read failed: %w", err)
|
2022-10-25 22:06:08 +08:00
|
|
|
}
|
|
|
|
|
o.ReadSuccess.Incr(1)
|
|
|
|
|
for i, d := range resp.Results {
|
|
|
|
|
o.UpdateNodeValue(i, d)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|