Skip to content

Commit

Permalink
🧹 improve opc ua resource pack
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-rock committed May 22, 2023
1 parent 75d64f5 commit ffc07a1
Show file tree
Hide file tree
Showing 10 changed files with 794 additions and 123 deletions.
76 changes: 76 additions & 0 deletions resources/packs/opcua/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# OPC UA Resource Pack

Query all nodes:

```coffeescript
# gather all available namespaces
opcua.namespaces { * }
opcua.namespaces: [
0: {
id: 0
name: "http://opcfoundation.org/UA/"
}
1: {
id: 1
name: "urn:open62541.server.application"
}
]

# gather root node
cnquery> opcua.root
opcua.root: opcua.node id="i=84" name="Root"


# gather all nodes
cnquery> opcua.nodes { name namespace.name }

# gather node with a specific id
cnquery> opcua.nodes.where (id == "i=2253")
opcua.nodes.where: [
0: opcua.node id="i=2253" name="Server"
]

# gather details about the server
cnquery> opcua.server { * }
opcua.server: {
buildInfo: {
BuildDate: "2023-05-21T21:03:43.817369Z"
BuildNumber: "May 20 2023 15:51:32"
ManufacturerName: "open62541"
ProductName: "open62541 OPC UA Server"
ProductURI: "http://open62541.org"
SoftwareVersion: "1.3.5-994-g5d73f0cc5"
}
node: opcua.node id="i=2253" name="Server"
currentTime: 2023-05-22 08:28:30.625932 +0000 UTC
state: "ServerStateRunning"
startTime: 2023-05-21 21:03:43.834304 +0000 UTC
}
```

## Example Servers

*Open62541*

The [Open62541](https://github.com/open62541/open62541) includes may examples for building an Open62541 server.


*Azure IoT Edge*

Azure has an example for [OPC PLC server](https://github.com/Azure-Samples/iot-edge-opc-plc) available that you can quickly start locally:

```bash
# run service with no security configuration
docker run --rm -it -p 50000:50000 -p 8080:8080 --name opcplc mcr.microsoft.com/iotedge/opc-plc:latest --pn=50000 --autoaccept --sph --sn=5 --sr=10 --st=uint --fn=5 --fr=1 --ft=uint --gn=5 --ut --dca
```

## UI

*Simple OPC-UA GUI client*

This is a very simple [client](https://github.com/FreeOpcUa/opcua-client-gui) that allows you to browse the OPC UA data:

```bash
pip3 install opcua-client
opcua-client
```
2 changes: 1 addition & 1 deletion resources/packs/opcua/info/opcua.lr.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"resources":{"opcua":{"id":"opcua","name":"opcua","fields":{"namespaces":{"name":"namespaces","type":"\u0019\u0007"},"root":{"name":"root","type":"\u001bopcua.node"}},"title":"OPC UA"},"opcua.namespace":{"id":"opcua.namespace","name":"opcua.namespace","fields":{"id":{"name":"id","type":"\u0005","is_mandatory":true},"name":{"name":"name","type":"\u0007","is_mandatory":true}},"title":"OPC UA Namespace"},"opcua.node":{"id":"opcua.node","name":"opcua.node","fields":{"accessLevel":{"name":"accessLevel","type":"\u0007","is_mandatory":true},"class":{"name":"class","type":"\u0007","is_mandatory":true},"components":{"name":"components","type":"\u0019\u001bopcua.node"},"dataType":{"name":"dataType","type":"\u0007","is_mandatory":true},"description":{"name":"description","type":"\u0007","is_mandatory":true},"id":{"name":"id","type":"\u0007","is_mandatory":true},"max":{"name":"max","type":"\u0007","is_mandatory":true},"min":{"name":"min","type":"\u0007","is_mandatory":true},"name":{"name":"name","type":"\u0007","is_mandatory":true},"organizes":{"name":"organizes","type":"\u0019\u001bopcua.node"},"properties":{"name":"properties","type":"\u0019\u001bopcua.node"},"unit":{"name":"unit","type":"\u0007","is_mandatory":true},"writeable":{"name":"writeable","type":"\u0004","is_mandatory":true}},"title":"OPC UA Node","defaults":"id name"}}}
{"resources":{"opcua":{"id":"opcua","name":"opcua","fields":{"namespaces":{"name":"namespaces","type":"\u0019\u001bopcua.namespace","title":"Namespaces"},"nodes":{"name":"nodes","type":"\u0019\u001bopcua.node","title":"List of all nodes"},"root":{"name":"root","type":"\u001bopcua.node","title":"Root node"}},"title":"OPC UA"},"opcua.namespace":{"id":"opcua.namespace","name":"opcua.namespace","fields":{"id":{"name":"id","type":"\u0005","is_mandatory":true,"title":"Namespace ID"},"name":{"name":"name","type":"\u0007","is_mandatory":true,"title":"Namespace Name"}},"title":"OPC UA Namespace"},"opcua.node":{"id":"opcua.node","name":"opcua.node","fields":{"accessLevel":{"name":"accessLevel","type":"\u0007","is_mandatory":true},"class":{"name":"class","type":"\u0007","is_mandatory":true},"components":{"name":"components","type":"\u0019\u001bopcua.node"},"dataType":{"name":"dataType","type":"\u0007","is_mandatory":true},"description":{"name":"description","type":"\u0007","is_mandatory":true},"id":{"name":"id","type":"\u0007","is_mandatory":true},"max":{"name":"max","type":"\u0007","is_mandatory":true},"min":{"name":"min","type":"\u0007","is_mandatory":true},"name":{"name":"name","type":"\u0007","is_mandatory":true},"namespace":{"name":"namespace","type":"\u001bopcua.namespace"},"organizes":{"name":"organizes","type":"\u0019\u001bopcua.node"},"properties":{"name":"properties","type":"\u0019\u001bopcua.node"},"unit":{"name":"unit","type":"\u0007","is_mandatory":true},"writeable":{"name":"writeable","type":"\u0004","is_mandatory":true}},"title":"OPC UA Node","defaults":"id name"},"opcua.server":{"id":"opcua.server","name":"opcua.server","fields":{"buildInfo":{"name":"buildInfo","type":"\n","is_mandatory":true},"currentTime":{"name":"currentTime","type":"\t","is_mandatory":true},"node":{"name":"node","type":"\u001bopcua.node","is_mandatory":true},"startTime":{"name":"startTime","type":"\t","is_mandatory":true},"state":{"name":"state","type":"\u0007","is_mandatory":true}},"title":"Server Object"}}}
2 changes: 1 addition & 1 deletion resources/packs/opcua/info/opcua.lr.manifest.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"resources":{"opcua":{"fields":{"namespaces":{},"root":{}},"min_mondoo_version":"latest"},"opcua.namespace":{"fields":{"id":{},"name":{}},"min_mondoo_version":"latest"},"opcua.node":{"fields":{"accessLevel":{},"class":{},"components":{},"dataType":{},"description":{},"id":{},"max":{},"min":{},"name":{},"namespace":{},"nodeid":{},"organizes":{},"properties":{},"unit":{},"writeable":{}},"min_mondoo_version":"latest"}}}
{"resources":{"opcua":{"fields":{"namespaces":{},"nodes":{},"root":{}},"min_mondoo_version":"latest"},"opcua.namespace":{"fields":{"id":{},"name":{}},"min_mondoo_version":"latest"},"opcua.node":{"fields":{"accessLevel":{},"class":{},"components":{},"dataType":{},"description":{},"id":{},"max":{},"min":{},"name":{},"namespace":{},"nodeid":{},"organizes":{},"properties":{},"unit":{},"writeable":{}},"min_mondoo_version":"latest"},"opcua.server":{"fields":{"buildInfo":{},"currentTime":{},"node":{},"startTime":{},"state":{}},"min_mondoo_version":"latest"}}}
42 changes: 42 additions & 0 deletions resources/packs/opcua/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package opcua

import (
"go.mondoo.com/cnquery/resources"
"strconv"
)

func (o *mqlOpcuaNamespace) id() (string, error) {
id, err := o.Id()
if err != nil {
return "", err
}
s := strconv.FormatInt(id, 10)
return "opcua.namespace/" + s, nil
}

// https://reference.opcfoundation.org/DI/v102/docs/11.2
func (o *mqlOpcua) GetNamespaces() ([]interface{}, error) {
op, err := opcuaProvider(o.MotorRuntime.Motor.Provider)
if err != nil {
return nil, err
}
client := op.Client()

namespaces := client.Namespaces()
resList := []interface{}{}
for i := range namespaces {
res, err := newMqlOpcuaNamespaceResource(o.MotorRuntime, int64(i), namespaces[i])
if err != nil {
return nil, err
}
resList = append(resList, res)
}
return resList, nil
}

func newMqlOpcuaNamespaceResource(runtime *resources.Runtime, id int64, name string) (interface{}, error) {
return runtime.CreateResource("opcua.namespace",
"id", id,
"name", name,
)
}
161 changes: 158 additions & 3 deletions resources/packs/opcua/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package opcua

import (
"context"
"fmt"
"github.com/gopcua/opcua"
"github.com/gopcua/opcua/errors"
"github.com/gopcua/opcua/id"
"github.com/gopcua/opcua/ua"
"go.mondoo.com/cnquery/resources"
)

type NodeDef struct {
type nodeMeta struct {
NodeID *ua.NodeID
NodeClass ua.NodeClass
BrowseName string
Expand All @@ -25,13 +28,13 @@ type NodeDef struct {
Properties []*opcua.Node
}

func fetchNodeInfo(ctx context.Context, n *opcua.Node) (*NodeDef, error) {
func fetchNodeInfo(ctx context.Context, n *opcua.Node) (*nodeMeta, error) {
attrs, err := n.AttributesWithContext(ctx, ua.AttributeIDNodeClass, ua.AttributeIDBrowseName, ua.AttributeIDDescription, ua.AttributeIDAccessLevel, ua.AttributeIDDataType)
if err != nil {
return nil, err
}

var def = NodeDef{
var def = nodeMeta{
NodeID: n.ID,
}

Expand Down Expand Up @@ -137,3 +140,155 @@ func fetchNodeInfo(ctx context.Context, n *opcua.Node) (*NodeDef, error) {

return &def, nil
}

func newMqlOpcuaNodeResource(runtime *resources.Runtime, ndef *nodeMeta) (interface{}, error) {
res, err := runtime.CreateResource("opcua.node",
"id", ndef.NodeID.String(),
"name", ndef.BrowseName,
"class", ndef.NodeClass.String(),
"description", ndef.Description,
"writeable", ndef.Writable,
"dataType", ndef.DataType,
"min", ndef.Min,
"max", ndef.Max,
"unit", ndef.Unit,
"accessLevel", ndef.AccessLevel.String(),
)
if err != nil {
return nil, err
}
res.MqlResource().Cache.Store("_object", &resources.CacheEntry{
Data: ndef,
})
return res, nil
}

func (o *mqlOpcuaNode) id() (string, error) {
id, err := o.Id()
if err != nil {
return "", err
}
return "opcua.node/" + id, nil
}

func (o *mqlOpcuaNode) GetNamespace() (interface{}, error) {
res, ok := o.Cache.Load("_object")
if !ok {
return nil, errors.New("could not fetch properties")
}

if res.Error != nil {
return nil, res.Error
}
nodeDef, ok := res.Data.(*nodeMeta)
if !ok {
return nil, fmt.Errorf("\"opcua\" failed to cast field \"node\" to the right type: %#v", res)
}

obj, err := o.MotorRuntime.CreateResource("opcua")
if err != nil {
return nil, err
}
mqlOpcua := obj.(Opcua)

namespaces, err := mqlOpcua.Namespaces()
if err != nil {
return nil, err
}

entry := namespaces[nodeDef.NodeID.Namespace()]
return entry, nil
}

func (o *mqlOpcuaNode) GetProperties() ([]interface{}, error) {
res, ok := o.Cache.Load("_object")
if !ok {
return nil, errors.New("could not fetch properties")
}
if res.Error != nil {
return nil, res.Error
}
nodeDef, ok := res.Data.(*nodeMeta)
if !ok {
return nil, fmt.Errorf("\"opcua\" failed to cast field \"node\" to the right type: %#v", res)
}

ctx := context.Background()
results := []interface{}{}
for i := range nodeDef.Properties {
def := nodeDef.Properties[i]
n, err := fetchNodeInfo(ctx, def)
if err != nil {
return nil, err
}
r, err := newMqlOpcuaNodeResource(o.MotorRuntime, n)
if err != nil {
return nil, err
}
results = append(results, r)
}

return results, nil
}

func (o *mqlOpcuaNode) GetComponents() ([]interface{}, error) {
res, ok := o.Cache.Load("_object")
if !ok {
return nil, errors.New("could not fetch properties")
}
if res.Error != nil {
return nil, res.Error
}
nodeDef, ok := res.Data.(*nodeMeta)
if !ok {
return nil, fmt.Errorf("\"opcua\" failed to cast field \"node\" to the right type: %#v", res)
}

ctx := context.Background()
results := []interface{}{}
for i := range nodeDef.Components {
def := nodeDef.Components[i]
n, err := fetchNodeInfo(ctx, def)
if err != nil {
return nil, err
}
r, err := newMqlOpcuaNodeResource(o.MotorRuntime, n)
if err != nil {
return nil, err
}
results = append(results, r)
}

return results, nil
}

func (o *mqlOpcuaNode) GetOrganizes() ([]interface{}, error) {
res, ok := o.Cache.Load("_object")
if !ok {
return nil, errors.New("could not fetch properties")
}
if res.Error != nil {
return nil, res.Error
}
nodeDef, ok := res.Data.(*nodeMeta)
if !ok {
return nil, fmt.Errorf("\"opcua\" failed to cast field \"node\" to the right type: %#v", res)
}

ctx := context.Background()
results := []interface{}{}
for i := range nodeDef.Organizes {
def := nodeDef.Organizes[i]
n, err := fetchNodeInfo(ctx, def)
if err != nil {
return nil, err
}
r, err := newMqlOpcuaNodeResource(o.MotorRuntime, n)
if err != nil {
return nil, err
}
results = append(results, r)
}

return results, nil
}
Loading

0 comments on commit ffc07a1

Please sign in to comment.