Search code examples
c#opc-uaopcunified-automation-sdk

OPC UA Foundation SDK .net OnSimpleWriteValue method return byte[] on complex data


I'm trying to read values written from an opc client to the server via the OnSimpleWriteValue event. As regards "basic" values (such as int, float, string,...) there are no problems, but when I have to read "complex" values, therefore with dedicated IEncodable classes, the story changes. When writing, the server encodes the values correctly, while when reading, the value returns to me in the form of an array of bytes

enter image description here

Below the codes I am using

public static void HandleProperty<T, P>(this T parent, ISystemContext systemContext, P property, string propertyStateName)
            where T : NodeState
            where P : PropertyChanged
        {
            BaseVariableState variableState = (BaseVariableState)parent.FindChildBySymbolicName(systemContext, propertyStateName);
            if (variableState == null) return;

            //Handle value changed on service
            property.ChangedByService += (obj, pn, v)
                => OnPropertyChangedByService(systemContext, variableState, v);

            //Handle value changed on OPC
            variableState.OnSimpleWriteValue += (ISystemContext ctx, NodeState n, ref object v)
                => OnPropertyChangedByOpc(ctx, n, property, v);

            //Init value
            UpdateValue(variableState, systemContext, property.GetValue());
        }
public static void UpdateValue<T>(this T variableState, ISystemContext systemContext, object value)
            where T : BaseVariableState
        {
            var type = value.GetType();

            //Convert IList to IEncodeable[]
            if (value is IList valueList)
            {
                var valueArray = Array.CreateInstance(type.GenericTypeArguments[0], valueList.Count);
                valueList.CopyTo(valueArray, 0);
                value = valueArray;
            }

            variableState.Value = value;
            variableState.UpdateChangeMasks(NodeStateChangeMasks.Value);
            variableState.ClearChangeMasks(systemContext, false); // notifies any monitored items that the value has changed.
        }
private static ServiceResult OnPropertyChangedByOpc<P>(ISystemContext ctx, NodeState n, P property, object value)
            where P : PropertyChanged
        {
            var test = n.FindChild(ctx, property.Name);
            if (value is ExtensionObject extObj) value = extObj.DecodeBinaryValue();
            if (value is ExtensionObject[] extArr)
            {
                var type = property.GetType().GenericTypeArguments[0];
                IList tempList = Array.CreateInstance(type, extArr.Length);
                for (var index = 0; index < extArr.Length; index++)
                {
                    var valueBytes = extArr[index];
                    var ext = valueBytes.DecodeBinaryValue(); //Here's I am trying to Decode, value is byte[]
                    tempList[index] = ext;
                }

                value = tempList;
            }

            property.SetValue(value);
            return ServiceResult.Good;
        }

private static object DecodeBinaryValue(this ExtensionObject extensionObject)
        {
            using var decoder = new BinaryDecoder(extensionObject.Body as byte[], ServiceMessageContext.GlobalContext);
            var decodeValue = decoder.ReadExtensionObject(null); //TODO - Fix length array error
            return decodeValue;
        }

What I tried

  • Search for online sample & documentation

What I expect

  • Convert byte[] to proper class or at least receive the value already decoded

Solution

  • I think this is a work around, but after some spent time I found a solution. I modified the DecodeBinaryValue using the ReadEncodable method

    private static object DecodeBinaryValue(this ExtensionObject extensionObject, Type type)
    {
        using var decoder = new BinaryDecoder(extensionObject.Body as byte[], ServiceMessageContext.GlobalContext);
        var decodeValue = decoder.ReadEncodeable("", type);
        return decodeValue;
    }
    

    On method handler OnPropertyChangedByOpc, I have to modified the code too because the object returned it's not cast correctly

    private static ServiceResult OnPropertyChangedByOpc<P>(ISystemContext ctx, NodeState n, P property, object value)
        where P : PropertyChanged
    {
    
        if (value is ExtensionObject extObj)
        {
            var type = property.GetType().GenericTypeArguments[0];
            value = extObj.DecodeBinaryValue(type);
        }
    
        if (value is ExtensionObject[] extArr)
        {
            var listType = property.GetType().GenericTypeArguments[0];
            var type = listType.GenericTypeArguments[0];
            List<object> tempList = new List<object>();
            for (var index = 0; index < extArr.Length; index++)
            {
                var valueBytes = extArr[index];
                var ext = valueBytes.DecodeBinaryValue(type);
                //Here, after decode the value, create a temp list object and
                //re-cast all single value as expected then save on a new cast
                //istance list and save the value as usual.
                //If you dont do this, method will return cast conversion error
                tempList.Add(Convert.ChangeType(ext, type));
            }
    
            var castValue = Activator.CreateInstance(listType) as IList;
            foreach (var item in tempList) castValue.Add(item);
            value = castValue;
        }
    
        property.SetValue(value);
        return ServiceResult.Good;
    }