Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add node groups to opcua input plugin #8389

Merged
merged 21 commits into from
Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
41146ce
Remove extra toml section header in the sample config
reimda Oct 29, 2020
98ba434
Remove data type of node. It's never used in the code
reimda Nov 6, 2020
ff62dbe
Remove description of node. It's never used in the code.
reimda Nov 6, 2020
8464ba5
Uncapitalize quality field name. Update readme.
reimda Nov 6, 2020
1ac5f81
Disambiguate the two settings called 'name'. Rename the one in main c…
reimda Nov 10, 2020
2e0e7a9
Add ability to assign a different metric name to a group of nodes. G…
reimda Nov 10, 2020
6c36ee7
Remove name tag that duplicates the opc ua value's field name
reimda Nov 10, 2020
d1546f7
Expose read errors and successes as selfstats
reimda Nov 10, 2020
b4f7288
Update the readme
reimda Nov 11, 2020
79db300
Update the readme
reimda Nov 11, 2020
24112ae
revert changing name fields
reimda Nov 17, 2020
d69e9a2
revert removal of node's data_type and description fields
reimda Nov 17, 2020
6dc2ea9
revert lowercasing Quality field
reimda Nov 17, 2020
ac14f75
revert settings name changes in unit tests
reimda Nov 17, 2020
c83b6da
Add ability to set tags for a group or a node
reimda Nov 17, 2020
1d36187
make fmt
reimda Nov 17, 2020
0d869c8
fix missing tags
reimda Nov 18, 2020
6f7614f
Update duplicate check to respect metric name and metric tags
reimda Dec 2, 2020
48b6fda
Update duplicate check to respect tag values. Check that tag key and…
reimda Dec 2, 2020
ee63c41
simplify tag validation loop
reimda Dec 2, 2020
56dd521
Add documentation for node groups
reimda Dec 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions plugins/inputs/opcua/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ Plugin minimum tested version: 1.16

```toml
[[inputs.opcua]]
## Device name
# name = "localhost"
## Metric name
# metric_name = "opcua"
#
## OPC UA Endpoint URL
# endpoint = "opc.tcp://localhost:4840"
Expand Down Expand Up @@ -47,34 +47,32 @@ Plugin minimum tested version: 1.16
# password = ""
#
## Node ID configuration
## name - the variable name
## namespace - integer value 0 thru 3
## field_name - the field name
## namespace - integer value 0 thru 3
## identifier_type - s=string, i=numeric, g=guid, b=opaque
## identifier - tag as shown in opcua browser
## data_type - boolean, byte, short, int, uint, uint16, int16,
## uint32, int32, float, double, string, datetime, number
## Example:
## {name="ProductUri", namespace="0", identifier_type="i", identifier="2262", data_type="string", description="http://open62541.org"}
## {name="ProductUri", namespace="0", identifier_type="i", identifier="2262"}
nodes = [
{name="", namespace="", identifier_type="", identifier="", data_type="", description=""},
{name="", namespace="", identifier_type="", identifier="", data_type="", description=""},
{field_name="", namespace="", identifier_type="", identifier=""},
{field_name="", namespace="", identifier_type="", identifier=""},
]
```

### Example Node Configuration
An OPC UA node ID may resemble: "n=3,s=Temperature". In this example:
An OPC UA node ID may resemble: "n=3;s=Temperature". In this example:
- n=3 is indicating the `namespace` is 3
- s=Temperature is indicting that the `identifier_type` is a string and `identifier` value is 'Temperature'
- This example temperature node has a value of 79.0, which makes the `data_type` a 'float'.
- This example temperature node has a value of 79.0
To gather data from this node enter the following line into the 'nodes' property above:
```
{name="LabelName", namespace="3", identifier_type="s", identifier="Temperature", data_type="float", description="Description of node"},
{field_name="temp", namespace="3", identifier_type="s", identifier="Temperature"},
```


### Example Output

```
opcua,host=3c70aee0901e,name=Random,type=double Random=0.018158170305814902 1597820490000000000
opcua,name=temp,id=n\=3;s\=Temperature temp=79.0,quality="OK (0x0)" 1597820490000000000

```
42 changes: 16 additions & 26 deletions plugins/inputs/opcua/opcua_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

// OpcUA type
type OpcUA struct {
Name string `toml:"name"`
MetricName string `toml:"metric_name"`
reimda marked this conversation as resolved.
Show resolved Hide resolved
Endpoint string `toml:"endpoint"`
SecurityPolicy string `toml:"security_policy"`
SecurityMode string `toml:"security_mode"`
Expand Down Expand Up @@ -49,12 +49,10 @@ type OpcUA struct {

// OPCTag type
type OPCTag struct {
Name string `toml:"name"`
FieldName string `toml:"field_name"`
reimda marked this conversation as resolved.
Show resolved Hide resolved
Namespace string `toml:"namespace"`
IdentifierType string `toml:"identifier_type"`
Identifier string `toml:"identifier"`
DataType string `toml:"data_type"`
Description string `toml:"description"`
reimda marked this conversation as resolved.
Show resolved Hide resolved
}

// OPCData type
Expand All @@ -81,7 +79,6 @@ const (

const description = `Retrieve data from OPCUA devices`
const sampleConfig = `
[[inputs.opcua]]
## Device name
# name = "localhost"
#
Expand Down Expand Up @@ -166,7 +163,7 @@ func (o *OpcUA) Init() error {
}

func (o *OpcUA) validateEndpoint() error {
if o.Name == "" {
if o.MetricName == "" {
return fmt.Errorf("device name is empty")
}

Expand All @@ -184,14 +181,14 @@ func (o *OpcUA) validateEndpoint() error {
case "None", "Basic128Rsa15", "Basic256", "Basic256Sha256", "auto":
break
default:
return fmt.Errorf("invalid security type '%s' in '%s'", o.SecurityPolicy, o.Name)
return fmt.Errorf("invalid security type '%s' in '%s'", o.SecurityPolicy, o.MetricName)
}
//search security mode type
switch o.SecurityMode {
case "None", "Sign", "SignAndEncrypt", "auto":
break
default:
return fmt.Errorf("invalid security type '%s' in '%s'", o.SecurityMode, o.Name)
return fmt.Errorf("invalid security type '%s' in '%s'", o.SecurityMode, o.MetricName)
}
return nil
}
Expand All @@ -214,28 +211,21 @@ func (o *OpcUA) validateOPCTags() error {
nameEncountered := map[string]bool{}
for i, item := range o.NodeList {
//check empty name
if item.Name == "" {
return fmt.Errorf("empty name in '%s'", item.Name)
if item.FieldName == "" {
return fmt.Errorf("empty name in '%s'", item.FieldName)
}
//search name duplicate
if nameEncountered[item.Name] {
return fmt.Errorf("name '%s' is duplicated in '%s'", item.Name, item.Name)
if nameEncountered[item.FieldName] {
return fmt.Errorf("name '%s' is duplicated", item.FieldName)
} else {
nameEncountered[item.Name] = true
nameEncountered[item.FieldName] = true
}
//search identifier type
switch item.IdentifierType {
case "s", "i", "g", "b":
break
default:
return fmt.Errorf("invalid identifier type '%s' in '%s'", item.IdentifierType, item.Name)
}
// search data type
switch item.DataType {
case "boolean", "byte", "short", "int", "uint", "uint16", "int16", "uint32", "int32", "float", "double", "string", "datetime", "number":
break
default:
return fmt.Errorf("invalid data type '%s' in '%s'", item.DataType, item.Name)
return fmt.Errorf("invalid identifier type '%s' in '%s'", item.IdentifierType, item.FieldName)
}

// build nodeid
Expand Down Expand Up @@ -333,7 +323,7 @@ func (o *OpcUA) getData() error {
if d.Status != ua.StatusOK {
return fmt.Errorf("Status not OK: %v", d.Status)
}
o.NodeData[i].TagName = o.NodeList[i].Name
o.NodeData[i].TagName = o.NodeList[i].FieldName
if d.Value != nil {
o.NodeData[i].Value = d.Value.Value()
o.NodeData[i].DataType = d.Value.Type()
Expand Down Expand Up @@ -395,13 +385,13 @@ func (o *OpcUA) Gather(acc telegraf.Accumulator) error {
for i, n := range o.NodeList {
fields := make(map[string]interface{})
tags := map[string]string{
"name": n.Name,
"name": n.FieldName,
"id": BuildNodeID(n),
}

fields[o.NodeData[i].TagName] = o.NodeData[i].Value
fields["Quality"] = strings.TrimSpace(fmt.Sprint(o.NodeData[i].Quality))
acc.AddFields(o.Name, fields, tags)
fields["quality"] = strings.TrimSpace(fmt.Sprint(o.NodeData[i].Quality))
acc.AddFields(o.MetricName, fields, tags)
}
return nil
}
Expand All @@ -410,7 +400,7 @@ func (o *OpcUA) Gather(acc telegraf.Accumulator) error {
func init() {
inputs.Add("opcua", func() telegraf.Input {
return &OpcUA{
Name: "localhost",
MetricName: "opcua",
Endpoint: "opc.tcp://localhost:4840",
SecurityPolicy: "auto",
SecurityMode: "auto",
Expand Down
26 changes: 12 additions & 14 deletions plugins/inputs/opcua/opcua_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ type OPCTags struct {
Namespace string
IdentifierType string
Identifier string
DataType string
Want string
}

Expand All @@ -25,15 +24,15 @@ func TestClient1(t *testing.T) {
}

var testopctags = []OPCTags{
{"ProductName", "0", "i", "2261", "string", "open62541 OPC UA Server"},
{"ProductUri", "0", "i", "2262", "string", "http://open62541.org"},
{"ManufacturerName", "0", "i", "2263", "string", "open62541"},
{"ProductName", "0", "i", "2261", "open62541 OPC UA Server"},
{"ProductUri", "0", "i", "2262", "http://open62541.org"},
{"ManufacturerName", "0", "i", "2263", "open62541"},
}

var o OpcUA
var err error

o.Name = "testing"
o.MetricName = "testing"
o.Endpoint = "opc.tcp://opcua.rocks:4840"
o.AuthMethod = "Anonymous"
o.ConnectTimeout = config.Duration(10 * time.Second)
Expand All @@ -58,27 +57,26 @@ func TestClient1(t *testing.T) {
value := reflect.ValueOf(v.Value)
compare := fmt.Sprintf("%v", value.Interface())
if compare != testopctags[i].Want {
t.Errorf("Tag %s: Values %v for type %s does not match record", o.NodeList[i].Name, value.Interface(), types)
t.Errorf("Tag %s: Values %v for type %s does not match record", o.NodeList[i].FieldName, value.Interface(), types)
}
} else {
t.Errorf("Tag: %s has value: %v", o.NodeList[i].Name, v.Value)
t.Errorf("Tag: %s has value: %v", o.NodeList[i].FieldName, v.Value)
}
}
}

func MapOPCTag(tags OPCTags) (out OPCTag) {
out.Name = tags.Name
out.FieldName = tags.Name
out.Namespace = tags.Namespace
out.IdentifierType = tags.IdentifierType
out.Identifier = tags.Identifier
out.DataType = tags.DataType
return out
}

func TestConfig(t *testing.T) {
toml := `
[[inputs.opcua]]
name = "localhost"
metric_name = "localhost"
endpoint = "opc.tcp://localhost:4840"
connect_timeout = "10s"
request_timeout = "5s"
Expand All @@ -90,8 +88,8 @@ auth_method = "Anonymous"
username = ""
password = ""
nodes = [
{name="name", namespace="", identifier_type="", identifier="", data_type="", description=""},
{name="name2", namespace="", identifier_type="", identifier="", data_type="", description=""},
{field_name="name", namespace="1", identifier_type="s", identifier="one"},
{field_name="name2", namespace="2", identifier_type="s", identifier="two"},
]
`

Expand All @@ -105,6 +103,6 @@ nodes = [
require.True(t, ok)

require.Len(t, o.NodeList, 2)
require.Equal(t, o.NodeList[0].Name, "name")
require.Equal(t, o.NodeList[1].Name, "name2")
require.Equal(t, o.NodeList[0].FieldName, "name")
require.Equal(t, o.NodeList[1].FieldName, "name2")
}