fix: Accept non-standard OPC UA OK status by implementing a configurable workaround (#10384)
Thanks!
This commit is contained in:
parent
48b981bd4e
commit
515715fee3
|
|
@ -89,6 +89,11 @@ Plugin minimum tested version: 1.16
|
|||
# {name="", namespace="", identifier_type="", identifier=""},
|
||||
# {name="", namespace="", identifier_type="", identifier=""},
|
||||
#]
|
||||
|
||||
## Enable workarounds required by some devices to work correctly
|
||||
# [inputs.opcua.workarounds]
|
||||
## Set additional valid status codes, StatusOK (0x0) is always considered valid
|
||||
# additional_valid_status_codes = ["0xC0"]
|
||||
```
|
||||
|
||||
## Node Configuration
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -18,23 +19,28 @@ import (
|
|||
"github.com/influxdata/telegraf/selfstat"
|
||||
)
|
||||
|
||||
type OpcuaWorkarounds struct {
|
||||
AdditionalValidStatusCodes []string `toml:"additional_valid_status_codes"`
|
||||
}
|
||||
|
||||
// OpcUA type
|
||||
type OpcUA struct {
|
||||
MetricName string `toml:"name"`
|
||||
Endpoint string `toml:"endpoint"`
|
||||
SecurityPolicy string `toml:"security_policy"`
|
||||
SecurityMode string `toml:"security_mode"`
|
||||
Certificate string `toml:"certificate"`
|
||||
PrivateKey string `toml:"private_key"`
|
||||
Username string `toml:"username"`
|
||||
Password string `toml:"password"`
|
||||
Timestamp string `toml:"timestamp"`
|
||||
AuthMethod string `toml:"auth_method"`
|
||||
ConnectTimeout config.Duration `toml:"connect_timeout"`
|
||||
RequestTimeout config.Duration `toml:"request_timeout"`
|
||||
RootNodes []NodeSettings `toml:"nodes"`
|
||||
Groups []GroupSettings `toml:"group"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
MetricName string `toml:"name"`
|
||||
Endpoint string `toml:"endpoint"`
|
||||
SecurityPolicy string `toml:"security_policy"`
|
||||
SecurityMode string `toml:"security_mode"`
|
||||
Certificate string `toml:"certificate"`
|
||||
PrivateKey string `toml:"private_key"`
|
||||
Username string `toml:"username"`
|
||||
Password string `toml:"password"`
|
||||
Timestamp string `toml:"timestamp"`
|
||||
AuthMethod string `toml:"auth_method"`
|
||||
ConnectTimeout config.Duration `toml:"connect_timeout"`
|
||||
RequestTimeout config.Duration `toml:"request_timeout"`
|
||||
RootNodes []NodeSettings `toml:"nodes"`
|
||||
Groups []GroupSettings `toml:"group"`
|
||||
Workarounds OpcuaWorkarounds `toml:"workarounds"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
|
||||
nodes []Node
|
||||
nodeData []OPCData
|
||||
|
|
@ -50,6 +56,7 @@ type OpcUA struct {
|
|||
client *opcua.Client
|
||||
req *ua.ReadRequest
|
||||
opts []opcua.Option
|
||||
codes []ua.StatusCode
|
||||
}
|
||||
|
||||
type NodeSettings struct {
|
||||
|
|
@ -180,6 +187,11 @@ const sampleConfig = `
|
|||
# {name="", namespace="", identifier_type="", identifier=""},
|
||||
# {name="", namespace="", identifier_type="", identifier=""},
|
||||
#]
|
||||
|
||||
## Enable workarounds required by some devices to work correctly
|
||||
# [inputs.opcua.workarounds]
|
||||
## Set additional valid status codes, StatusOK (0x0) is always considered valid
|
||||
# additional_valid_status_codes = ["0xC0"]
|
||||
`
|
||||
|
||||
// Description will appear directly above the plugin definition in the config file
|
||||
|
|
@ -216,6 +228,11 @@ func (o *OpcUA) Init() error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = o.setupWorkarounds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"endpoint": o.Endpoint,
|
||||
}
|
||||
|
|
@ -480,6 +497,28 @@ func (o *OpcUA) setupOptions() error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (o *OpcUA) setupWorkarounds() error {
|
||||
if len(o.Workarounds.AdditionalValidStatusCodes) != 0 {
|
||||
for _, c := range o.Workarounds.AdditionalValidStatusCodes {
|
||||
val, err := strconv.ParseInt(c, 0, 32) // setting 32 bits to allow for safe conversion
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.codes = append(o.codes, ua.StatusCode(uint32(val)))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpcUA) checkStatusCode(code ua.StatusCode) bool {
|
||||
for _, val := range o.codes {
|
||||
if val == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *OpcUA) getData() error {
|
||||
resp, err := o.client.Read(o.req)
|
||||
if err != nil {
|
||||
|
|
@ -489,7 +528,7 @@ func (o *OpcUA) getData() error {
|
|||
o.ReadSuccess.Incr(1)
|
||||
for i, d := range resp.Results {
|
||||
o.nodeData[i].Quality = d.Status
|
||||
if d.Status != ua.StatusOK {
|
||||
if !o.checkStatusCode(d.Status) {
|
||||
o.Log.Errorf("status not OK for node %v: %v", o.nodes[i].tag.FieldName, d.Status)
|
||||
continue
|
||||
}
|
||||
|
|
@ -553,7 +592,7 @@ func (o *OpcUA) Gather(acc telegraf.Accumulator) error {
|
|||
}
|
||||
|
||||
for i, n := range o.nodes {
|
||||
if o.nodeData[i].Quality == ua.StatusOK {
|
||||
if o.checkStatusCode(o.nodeData[i].Quality) {
|
||||
fields := make(map[string]interface{})
|
||||
tags := map[string]string{
|
||||
"id": n.idStr,
|
||||
|
|
@ -593,6 +632,7 @@ func init() {
|
|||
Certificate: "/etc/telegraf/cert.pem",
|
||||
PrivateKey: "/etc/telegraf/key.pem",
|
||||
AuthMethod: "Anonymous",
|
||||
codes: []ua.StatusCode{ua.StatusOK},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gopcua/opcua/ua"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
|
@ -43,6 +44,7 @@ func TestClient1Integration(t *testing.T) {
|
|||
o.RequestTimeout = config.Duration(1 * time.Second)
|
||||
o.SecurityPolicy = "None"
|
||||
o.SecurityMode = "None"
|
||||
o.codes = []ua.StatusCode{ua.StatusOK}
|
||||
o.Log = testutil.Logger{}
|
||||
for _, tags := range testopctags {
|
||||
o.RootNodes = append(o.RootNodes, MapOPCTag(tags))
|
||||
|
|
@ -108,6 +110,9 @@ namespace = "0"
|
|||
identifier_type = "i"
|
||||
tags = [["tag1", "val1"], ["tag2", "val2"]]
|
||||
nodes = [{name="name4", identifier="4000", tags=[["tag1", "override"]]}]
|
||||
|
||||
[inputs.opcua.workarounds]
|
||||
additional_valid_status_codes = ["0xC0"]
|
||||
`
|
||||
|
||||
c := config.NewConfig()
|
||||
|
|
@ -132,6 +137,9 @@ nodes = [{name="name4", identifier="4000", tags=[["tag1", "override"]]}]
|
|||
require.Len(t, o.nodes, 4)
|
||||
require.Len(t, o.nodes[2].metricTags, 3)
|
||||
require.Len(t, o.nodes[3].metricTags, 2)
|
||||
|
||||
require.Len(t, o.Workarounds.AdditionalValidStatusCodes, 1)
|
||||
require.Equal(t, o.Workarounds.AdditionalValidStatusCodes[0], "0xC0")
|
||||
}
|
||||
|
||||
func TestTagsSliceToMap(t *testing.T) {
|
||||
|
|
@ -260,3 +268,24 @@ func TestValidateOPCTags(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupWorkarounds(t *testing.T) {
|
||||
var o OpcUA
|
||||
o.codes = []ua.StatusCode{ua.StatusOK}
|
||||
|
||||
o.Workarounds.AdditionalValidStatusCodes = []string{"0xC0", "0x00AA0000"}
|
||||
|
||||
err := o.setupWorkarounds()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, o.codes, 3)
|
||||
require.Equal(t, o.codes[0], ua.StatusCode(0))
|
||||
require.Equal(t, o.codes[1], ua.StatusCode(192))
|
||||
require.Equal(t, o.codes[2], ua.StatusCode(11141120))
|
||||
}
|
||||
|
||||
func TestCheckStatusCode(t *testing.T) {
|
||||
var o OpcUA
|
||||
o.codes = []ua.StatusCode{ua.StatusCode(0), ua.StatusCode(192), ua.StatusCode(11141120)}
|
||||
require.Equal(t, o.checkStatusCode(ua.StatusCode(192)), true)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue