mirror of
https://github.com/mainflux/mainflux.git
synced 2025-05-12 19:29:30 +08:00

* NOISSUE- Add OPC-UA adapter Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * NOISSUE - Add opc-adapter PoC, docker and vendor Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Convert OPC messages to SenML Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Add gopcua package Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * lora-adapter typo Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Add OPC Reader Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Typo fix Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Typo fix Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Update copyright headers Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Fix reviews Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Fix reviews Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Add opc config Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Add all opc envars in the config Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Config typo Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Add route map Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Use opcua package instead of opc Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Fix OPCUA typo Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Rm MQTT sub Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Move interefaces to root Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Fix revieews and typo Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Update Gopkg.toml Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Add all envars into .env Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
272 lines
7.5 KiB
Go
272 lines
7.5 KiB
Go
// Copyright 2018-2019 opcua authors. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
package opcua
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gopcua/opcua/id"
|
|
"github.com/gopcua/opcua/ua"
|
|
)
|
|
|
|
// Node is a high-level object to interact with a node in the
|
|
// address space. It provides common convenience functions to
|
|
// access and manipulate the common attributes of a node.
|
|
type Node struct {
|
|
// ID is the node id of the node.
|
|
ID *ua.NodeID
|
|
|
|
c *Client
|
|
}
|
|
|
|
func (n *Node) String() string {
|
|
return n.ID.String()
|
|
}
|
|
|
|
// NodeClass returns the node class attribute.
|
|
func (n *Node) NodeClass() (ua.NodeClass, error) {
|
|
v, err := n.Attribute(ua.AttributeIDNodeClass)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return ua.NodeClass(v.Int()), nil
|
|
}
|
|
|
|
// BrowseName returns the browse name of the node.
|
|
func (n *Node) BrowseName() (*ua.QualifiedName, error) {
|
|
v, err := n.Attribute(ua.AttributeIDBrowseName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return v.Value().(*ua.QualifiedName), nil
|
|
}
|
|
|
|
// Description returns the description of the node.
|
|
func (n *Node) Description() (*ua.LocalizedText, error) {
|
|
v, err := n.Attribute(ua.AttributeIDDescription)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return v.Value().(*ua.LocalizedText), nil
|
|
}
|
|
|
|
// DisplayName returns the display name of the node.
|
|
func (n *Node) DisplayName() (*ua.LocalizedText, error) {
|
|
v, err := n.Attribute(ua.AttributeIDDisplayName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return v.Value().(*ua.LocalizedText), nil
|
|
}
|
|
|
|
// AccessLevel returns the access level of the node.
|
|
// The returned value is a mask where multiple values can be
|
|
// set, e.g. read and write.
|
|
func (n *Node) AccessLevel() (ua.AccessLevelType, error) {
|
|
v, err := n.Attribute(ua.AttributeIDAccessLevel)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return ua.AccessLevelType(v.Value().(uint8)), nil
|
|
}
|
|
|
|
// HasAccessLevel returns true if all bits from mask are
|
|
// set in the access level mask of the node.
|
|
func (n *Node) HasAccessLevel(mask ua.AccessLevelType) (bool, error) {
|
|
v, err := n.AccessLevel()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return (v & mask) == mask, nil
|
|
}
|
|
|
|
// UserAccessLevel returns the access level of the node.
|
|
func (n *Node) UserAccessLevel() (ua.AccessLevelType, error) {
|
|
v, err := n.Attribute(ua.AttributeIDUserAccessLevel)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return ua.AccessLevelType(v.Value().(uint8)), nil
|
|
}
|
|
|
|
// HasUserAccessLevel returns true if all bits from mask are
|
|
// set in the user access level mask of the node.
|
|
func (n *Node) HasUserAccessLevel(mask ua.AccessLevelType) (bool, error) {
|
|
v, err := n.UserAccessLevel()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return (v & mask) == mask, nil
|
|
}
|
|
|
|
// Value returns the value of the node.
|
|
func (n *Node) Value() (*ua.Variant, error) {
|
|
return n.Attribute(ua.AttributeIDValue)
|
|
}
|
|
|
|
// Attribute returns the attribute of the node. with the given id.
|
|
func (n *Node) Attribute(attrID ua.AttributeID) (*ua.Variant, error) {
|
|
rv := &ua.ReadValueID{NodeID: n.ID, AttributeID: attrID}
|
|
req := &ua.ReadRequest{NodesToRead: []*ua.ReadValueID{rv}}
|
|
res, err := n.c.Read(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(res.Results) == 0 {
|
|
// #188: we return StatusBadUnexpectedError because it is unclear, under what
|
|
// circumstances the server would return no error and no results in the response
|
|
return nil, ua.StatusBadUnexpectedError
|
|
}
|
|
value := res.Results[0].Value
|
|
if res.Results[0].Status != ua.StatusOK {
|
|
return value, res.Results[0].Status
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func (n *Node) Attributes(attrID ...ua.AttributeID) ([]*ua.DataValue, error) {
|
|
req := &ua.ReadRequest{}
|
|
for _, id := range attrID {
|
|
rv := &ua.ReadValueID{NodeID: n.ID, AttributeID: id}
|
|
req.NodesToRead = append(req.NodesToRead, rv)
|
|
}
|
|
res, err := n.c.Read(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res.Results, nil
|
|
}
|
|
|
|
func (n *Node) Children(refs uint32, mask ua.NodeClass) ([]*Node, error) {
|
|
if refs == 0 {
|
|
refs = id.HierarchicalReferences
|
|
}
|
|
return n.ReferencedNodes(refs, ua.BrowseDirectionForward, mask, true)
|
|
}
|
|
|
|
func (n *Node) ReferencedNodes(refs uint32, dir ua.BrowseDirection, mask ua.NodeClass, includeSubtypes bool) ([]*Node, error) {
|
|
if refs == 0 {
|
|
refs = id.References
|
|
}
|
|
var nodes []*Node
|
|
res, err := n.References(refs, dir, mask, includeSubtypes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, r := range res {
|
|
nodes = append(nodes, n.c.Node(r.NodeID.NodeID))
|
|
}
|
|
return nodes, nil
|
|
}
|
|
|
|
// References returns all references for the node.
|
|
// todo(fs): this is not complete since it only returns the
|
|
// todo(fs): top-level reference at this point.
|
|
func (n *Node) References(refType uint32, dir ua.BrowseDirection, mask ua.NodeClass, includeSubtypes bool) ([]*ua.ReferenceDescription, error) {
|
|
if refType == 0 {
|
|
refType = id.References
|
|
}
|
|
if mask == 0 {
|
|
mask = ua.NodeClassAll
|
|
}
|
|
|
|
desc := &ua.BrowseDescription{
|
|
NodeID: n.ID,
|
|
BrowseDirection: dir,
|
|
ReferenceTypeID: ua.NewNumericNodeID(0, refType),
|
|
IncludeSubtypes: includeSubtypes,
|
|
NodeClassMask: uint32(mask),
|
|
ResultMask: uint32(ua.BrowseResultMaskAll),
|
|
}
|
|
|
|
req := &ua.BrowseRequest{
|
|
View: &ua.ViewDescription{
|
|
ViewID: ua.NewTwoByteNodeID(0),
|
|
Timestamp: time.Now(),
|
|
},
|
|
RequestedMaxReferencesPerNode: 0,
|
|
NodesToBrowse: []*ua.BrowseDescription{desc},
|
|
}
|
|
|
|
resp, err := n.c.Browse(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return n.browseNext(resp.Results)
|
|
}
|
|
|
|
func (n *Node) browseNext(results []*ua.BrowseResult) ([]*ua.ReferenceDescription, error) {
|
|
refs := results[0].References
|
|
for len(results[0].ContinuationPoint) > 0 {
|
|
req := &ua.BrowseNextRequest{
|
|
ContinuationPoints: [][]byte{results[0].ContinuationPoint},
|
|
ReleaseContinuationPoints: false,
|
|
}
|
|
resp, err := n.c.BrowseNext(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results = resp.Results
|
|
refs = append(refs, results[0].References...)
|
|
}
|
|
return refs, nil
|
|
}
|
|
|
|
// TranslateBrowsePathsToNodeIDs translates an array of browseName segments to NodeIDs.
|
|
func (n *Node) TranslateBrowsePathsToNodeIDs(pathNames []*ua.QualifiedName) (*ua.NodeID, error) {
|
|
req := ua.TranslateBrowsePathsToNodeIDsRequest{
|
|
BrowsePaths: []*ua.BrowsePath{
|
|
{
|
|
StartingNode: n.ID,
|
|
RelativePath: &ua.RelativePath{
|
|
Elements: []*ua.RelativePathElement{},
|
|
},
|
|
},
|
|
}}
|
|
|
|
for _, name := range pathNames {
|
|
req.BrowsePaths[0].RelativePath.Elements = append(req.BrowsePaths[0].RelativePath.Elements,
|
|
&ua.RelativePathElement{ReferenceTypeID: ua.NewTwoByteNodeID(id.HierarchicalReferences),
|
|
IsInverse: false,
|
|
IncludeSubtypes: true,
|
|
TargetName: name,
|
|
},
|
|
)
|
|
}
|
|
|
|
var nodeID *ua.NodeID
|
|
err := n.c.Send(&req, func(i interface{}) error {
|
|
if resp, ok := i.(*ua.TranslateBrowsePathsToNodeIDsResponse); ok {
|
|
if len(resp.Results) == 0 {
|
|
return ua.StatusBadUnexpectedError
|
|
}
|
|
|
|
if resp.Results[0].StatusCode != ua.StatusOK {
|
|
return resp.Results[0].StatusCode
|
|
}
|
|
|
|
if len(resp.Results[0].Targets) == 0 {
|
|
return ua.StatusBadUnexpectedError
|
|
}
|
|
nodeID = resp.Results[0].Targets[0].TargetID.NodeID
|
|
return nil
|
|
}
|
|
return ua.StatusBadUnexpectedError
|
|
})
|
|
return nodeID, err
|
|
}
|
|
|
|
// TranslateBrowsePathInNamespaceToNodeID translates a browseName to a NodeID within the same namespace.
|
|
func (n *Node) TranslateBrowsePathInNamespaceToNodeID(ns uint16, browsePath string) (*ua.NodeID, error) {
|
|
segments := strings.Split(browsePath, ".")
|
|
var names []*ua.QualifiedName
|
|
for _, segment := range segments {
|
|
qn := &ua.QualifiedName{NamespaceIndex: ns, Name: segment}
|
|
names = append(names, qn)
|
|
}
|
|
return n.TranslateBrowsePathsToNodeIDs(names)
|
|
}
|