OPCFoundation / UA-ModelCompiler

ModelCompiler converts XML files into C# and ANSI C
MIT License
151 stars 94 forks source link

Generated VariableValue class is missing code for updating subscribed children variables #132

Closed LaXiS96 closed 1 year ago

LaXiS96 commented 1 year ago

Hello, I am designing a model which includes nodes like StructureScalar from the reference server TestData model (that is, a structure which exposes its fields as their own DataVariables), but I am not seeing value changes on subscribed items when writing to either the structure or its children (which instead works on the reference server implementation).

My model XML defines DataType and VariableType like this (snippet):

<opc:DataType SymbolicName="EventDataType" BaseType="ua:Structure">
  <opc:Fields>
    <opc:Field Name="Request" DataType="ua:UInt16" />
  </opc:Fields>
</opc:DataType>

<opc:VariableType SymbolicName="EventVariableType" DataType="EventDataType" BaseType="ua:BaseDataVariableType" AccessLevel="ReadWrite">
  <opc:Children>
    <opc:Variable SymbolicName="Request" DataType="ua:UInt16" AccessLevel="ReadWrite" />
  </opc:Children>
</opc:VariableType>

My NodeManager extends CustomNodeManager2 to load predefined nodes from the compiled model and add variables at runtime. I am therefore adding a variable at runtime using the generated EventVariableState and EventVariableValue classes. With UaExpert, after subscribing to both the parent variable and its Request child, if I write to Request I don't see any update on the parent (but the value was updated, as confirmed by re-reading the parent outside of the subscription), and vice versa.

The only difference I noticed is that my generated VariableState class uses the OnSimpleWriteValue delegate for the Request child, which is implemented like this:

private ServiceResult OnWrite_Request(ISystemContext context, NodeState node, ref object value)
{
    lock (Lock)
    {
        m_value.Request = (ushort)Write(value);
    }

    return ServiceResult.Good;
}

while the reference server TestData classes uses:

private ServiceResult OnWrite_Int32Value(
    ISystemContext context,
    NodeState node,
    NumericRange indexRange,
    QualifiedName dataEncoding,
    ref object value,
    ref StatusCode statusCode,
    ref DateTime timestamp)
{
    lock (Lock)
    {
        UpdateChildVariableStatus(m_variable.Int32Value, ref statusCode, ref timestamp);
        m_value.Int32Value = (int)Write(value);
        UpdateParent(context, ref statusCode, ref timestamp);
    }

    return ServiceResult.Good;
}

Am I missing something? Why does my generated classes not include the Update* calls?

Thank you in advance

LaXiS96 commented 1 year ago

I searched in UA-related repositories and suspiciously found no references to those Update* methods. This makes me assume the reference server's generated code was manually modified after running UA-ModelCompiler, which is a total anti-pattern to code generation.

How am I supposed to update and recompile my model without having to modify the generated code every time? There is no object oriented way to extend those methods...

opcfoundation-org commented 1 year ago

You have a situation where 2 in memory caches need to be kept in sync (The Request in the Structure in the Parent and the value of the Request Variable). This is not the typical scenario which the autogenerated code supports. So you will need hand coded logic.

There are two ways to do this: by maintaining the value outside the CoreNodeManager2 and handing the read/write/subscribe operations explicitly or by doing something like the sample code from the TestData class.

The new discussion forum provides a venue for people looking for implementation help: https://github.com/OPCFoundation/UA-.NETStandard/discussions

LaXiS96 commented 1 year ago

Thank you for your reply. It's unfortunate that this isn't handled by ModelCompiler, it looks like the additional logic could be included in the code generation to support this use case which is relevant enough to be part of the reference implementation.

If I am understanding correctly you are suggesting to implement something similar to TestDataSystem, managing in there all reads/writes and notifications for MonitoredItems?

opcfoundation-org commented 1 year ago

Yes - the samples use classes called UnderlyingSystem to represent data that managed outside of the the NodeManager. Most real servers will want something like this for efficiency. The model of storing all your live data in the memory of the NodeManager is great for proof of concepts but does not scale that well.

LaXiS96 commented 1 year ago

Thank you, for some reason I had been mostly ignoring the Samples repository and basing my code on the reference implementation, but I now see that the included samples have pretty much all I need.