From 49c021a7c6363ce6e7948e38a6f02553c9db4eaa Mon Sep 17 00:00:00 2001 From: Athavan Kanapuli Date: Thu, 30 Jan 2025 13:45:18 +0100 Subject: [PATCH] Create new node_attributes.go --- opcua_plugin/browse.go | 154 --------------------------- opcua_plugin/node_attributes.go | 178 ++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 154 deletions(-) create mode 100644 opcua_plugin/node_attributes.go diff --git a/opcua_plugin/browse.go b/opcua_plugin/browse.go index daec8d54..b15860ae 100644 --- a/opcua_plugin/browse.go +++ b/opcua_plugin/browse.go @@ -66,160 +66,6 @@ func Browse(ctx context.Context, n NodeBrowser, path string, level int, logger L browse(ctx, n, path, level, logger, parentNodeId, nodeChan, errChan, wg, opcuaBrowserChan, visited) } -// AttributeHandler defines how to handle different attribute statuses and values -type AttributeHandler struct { - onOK func(value *ua.Variant) error - onNotReadable func() - onInvalidAttr bool // whether to ignore invalid attribute errors - requiresValue bool // whether a nil value is acceptable - affectsNodeClass bool // whether errors should mark node as Object -} - -// handleAttributeStatus processes an attribute's status and value according to defined handlers -func handleAttributeStatus( - attr *ua.DataValue, - def *NodeDef, - path string, - logger Logger, - handler AttributeHandler, -) error { - switch err := attr.Status; { - case errors.Is(err, ua.StatusOK): - if attr.Value == nil && handler.requiresValue { - return fmt.Errorf("attribute value is nil") - } - if handler.onOK != nil && attr.Value != nil { - if err := handler.onOK(attr.Value); err != nil { - return err - } - } - case errors.Is(err, ua.StatusBadSecurityModeInsufficient): - return errors.New("insufficient security mode") - case errors.Is(err, ua.StatusBadAttributeIDInvalid): - if !handler.onInvalidAttr { - return fmt.Errorf("invalid attribute ID") - } - // ignore if handler allows invalid attributes - case errors.Is(err, ua.StatusBadNotReadable): - if handler.affectsNodeClass { - def.NodeClass = ua.NodeClassObject - } - if handler.onNotReadable != nil { - handler.onNotReadable() - } - logger.Warnf("Access denied for node: %s, continuing...\n", path) - default: - return err - } - return nil -} - -// processNodeAttributes processes all attributes for a node -// This function is used to process the attributes of a node and set the NodeDef struct -func processNodeAttributes(attrs []*ua.DataValue, def *NodeDef, path string, logger Logger) error { - // NodeClass (attrs[0]) - nodeClassHandler := AttributeHandler{ - onOK: func(value *ua.Variant) error { - def.NodeClass = ua.NodeClass(value.Int()) - return nil - }, - requiresValue: true, - affectsNodeClass: true, - } - if err := handleAttributeStatus(attrs[0], def, path, logger, nodeClassHandler); err != nil { - return err - } - - // BrowseName (attrs[1]) - browseNameHandler := AttributeHandler{ - onOK: func(value *ua.Variant) error { - def.BrowseName = value.String() - return nil - }, - requiresValue: true, - } - if err := handleAttributeStatus(attrs[1], def, path, logger, browseNameHandler); err != nil { - return err - } - - // Description (attrs[2]) - descriptionHandler := AttributeHandler{ - onOK: func(value *ua.Variant) error { - if value != nil { - def.Description = value.String() - } else { - def.Description = "" - } - return nil - }, - onInvalidAttr: true, - affectsNodeClass: true, - } - if err := handleAttributeStatus(attrs[2], def, path, logger, descriptionHandler); err != nil { - return err - } - - // AccessLevel (attrs[3]) - accessLevelHandler := AttributeHandler{ - onOK: func(value *ua.Variant) error { - def.AccessLevel = ua.AccessLevelType(value.Int()) - return nil - }, - onInvalidAttr: true, - } - if err := handleAttributeStatus(attrs[3], def, path, logger, accessLevelHandler); err != nil { - return err - } - - // Check AccessLevel None - if def.AccessLevel == ua.AccessLevelTypeNone { - logger.Warnf("Node %s has AccessLevel None, marking as Object\n", path) - def.NodeClass = ua.NodeClassObject - } - - // DataType (attrs[4]) - dataTypeHandler := AttributeHandler{ - onOK: func(value *ua.Variant) error { - if value == nil { - logger.Debugf("ignoring node: %s as its datatype is nil...\n", path) - return fmt.Errorf("datatype is nil") - } - def.DataType = getDataTypeString(value.NodeID().IntID()) - return nil - }, - onInvalidAttr: true, - affectsNodeClass: true, - } - if err := handleAttributeStatus(attrs[4], def, path, logger, dataTypeHandler); err != nil { - return err - } - - return nil -} - -// getDataTypeString maps OPC UA data type IDs to Go type strings -func getDataTypeString(typeID uint32) string { - dataTypeMap := map[uint32]string{ - id.DateTime: "time.Time", - id.Boolean: "bool", - id.SByte: "int8", - id.Int16: "int16", - id.Int32: "int32", - id.Byte: "byte", - id.UInt16: "uint16", - id.UInt32: "uint32", - id.UtcTime: "time.Time", - id.String: "string", - id.Float: "float32", - id.Double: "float64", - } - - if dtype, ok := dataTypeMap[typeID]; ok { - return dtype - } - return fmt.Sprintf("ns=%d;i=%d", 0, typeID) -} - // browse recursively explores OPC UA nodes to build a comprehensive list of NodeDefs. // // The `browse` function is essential for discovering the structure and details of OPC UA nodes. diff --git a/opcua_plugin/node_attributes.go b/opcua_plugin/node_attributes.go new file mode 100644 index 00000000..b1fe6c0b --- /dev/null +++ b/opcua_plugin/node_attributes.go @@ -0,0 +1,178 @@ +package opcua_plugin + +import ( + "errors" + "fmt" + + "github.com/gopcua/opcua/id" + "github.com/gopcua/opcua/ua" +) + +// AttributeHandler defines how to handle different attribute statuses and values +type AttributeHandler struct { + onOK func(value *ua.Variant) error + onNotReadable func() + onInvalidAttr bool // whether to ignore invalid attribute errors + requiresValue bool // whether a nil value is acceptable + affectsNodeClass bool // whether errors should mark node as Object +} + +// handleAttributeStatus processes an attribute's status and value according to defined handlers +func handleAttributeStatus( + attr *ua.DataValue, + def *NodeDef, + path string, + logger Logger, + handler AttributeHandler, +) error { + // Early validation for nil value when required + if attr.Status == ua.StatusOK && attr.Value == nil && handler.requiresValue { + return fmt.Errorf("attribute value is nil") + } + + switch attr.Status { + case ua.StatusOK: + return handleOKStatus(attr.Value, handler) + + case ua.StatusBadSecurityModeInsufficient: + return errors.New("insufficient security mode") + + case ua.StatusBadAttributeIDInvalid: + if handler.onInvalidAttr { + return nil // Skip invalid attributes if allowed + } + return fmt.Errorf("invalid attribute ID") + + case ua.StatusBadNotReadable: + handleNotReadable(def, handler, path, logger) + return nil + + default: + return attr.Status + } +} + +// handleOKStatus processes successful attribute reads +func handleOKStatus(value *ua.Variant, handler AttributeHandler) error { + if handler.onOK != nil && value != nil { + return handler.onOK(value) + } + return nil +} + +// handleNotReadable processes non-readable attributes +func handleNotReadable(def *NodeDef, handler AttributeHandler, path string, logger Logger) { + if handler.affectsNodeClass { + def.NodeClass = ua.NodeClassObject + } + if handler.onNotReadable != nil { + handler.onNotReadable() + } + logger.Warnf("Access denied for node: %s, continuing...\n", path) +} + +// processNodeAttributes processes all attributes for a node +// This function is used to process the attributes of a node and set the NodeDef struct +func processNodeAttributes(attrs []*ua.DataValue, def *NodeDef, path string, logger Logger) error { + // NodeClass (attrs[0]) + nodeClassHandler := AttributeHandler{ + onOK: func(value *ua.Variant) error { + def.NodeClass = ua.NodeClass(value.Int()) + return nil + }, + requiresValue: true, + affectsNodeClass: true, + } + if err := handleAttributeStatus(attrs[0], def, path, logger, nodeClassHandler); err != nil { + return err + } + + // BrowseName (attrs[1]) + browseNameHandler := AttributeHandler{ + onOK: func(value *ua.Variant) error { + def.BrowseName = value.String() + return nil + }, + requiresValue: true, + } + if err := handleAttributeStatus(attrs[1], def, path, logger, browseNameHandler); err != nil { + return err + } + + // Description (attrs[2]) + descriptionHandler := AttributeHandler{ + onOK: func(value *ua.Variant) error { + if value != nil { + def.Description = value.String() + } else { + def.Description = "" + } + return nil + }, + onInvalidAttr: true, + affectsNodeClass: true, + } + if err := handleAttributeStatus(attrs[2], def, path, logger, descriptionHandler); err != nil { + return err + } + + // AccessLevel (attrs[3]) + accessLevelHandler := AttributeHandler{ + onOK: func(value *ua.Variant) error { + def.AccessLevel = ua.AccessLevelType(value.Int()) + return nil + }, + onInvalidAttr: true, + } + if err := handleAttributeStatus(attrs[3], def, path, logger, accessLevelHandler); err != nil { + return err + } + + // Check AccessLevel None + if def.AccessLevel == ua.AccessLevelTypeNone { + logger.Warnf("Node %s has AccessLevel None, marking as Object\n", path) + def.NodeClass = ua.NodeClassObject + } + + // DataType (attrs[4]) + dataTypeHandler := AttributeHandler{ + onOK: func(value *ua.Variant) error { + if value == nil { + logger.Debugf("ignoring node: %s as its datatype is nil...\n", path) + return fmt.Errorf("datatype is nil") + } + def.DataType = getDataTypeString(value.NodeID().IntID()) + return nil + }, + onInvalidAttr: true, + affectsNodeClass: true, + } + if err := handleAttributeStatus(attrs[4], def, path, logger, dataTypeHandler); err != nil { + return err + } + + return nil +} + +// getDataTypeString maps OPC UA data type IDs to Go type strings +func getDataTypeString(typeID uint32) string { + dataTypeMap := map[uint32]string{ + id.DateTime: "time.Time", + id.Boolean: "bool", + id.SByte: "int8", + id.Int16: "int16", + id.Int32: "int32", + id.Byte: "byte", + id.UInt16: "uint16", + id.UInt32: "uint32", + id.UtcTime: "time.Time", + id.String: "string", + id.Float: "float32", + id.Double: "float64", + } + + if dtype, ok := dataTypeMap[typeID]; ok { + return dtype + } + return fmt.Sprintf("ns=%d;i=%d", 0, typeID) +}