Search code examples
javaopc-uamilo

Eclipse Milo: How to call method which takes a customDataType as argument?


The CustomDataType the method requires:

<UADataType NodeId="ns=1;i=3010" BrowseName="1:ScanSettings">
<DisplayName>ScanSettings</DisplayName>
<References>
<Reference ReferenceType="HasEncoding">ns=1;i=5015</Reference>
<Reference ReferenceType="HasEncoding">ns=1;i=5016</Reference>
<Reference ReferenceType="HasSubtype" IsForward="false">i=22</Reference>
</References>
<Definition Name="1:ScanSettings">
<Field DataType="Duration" Name="Duration"/>
<Field DataType="Int32" Name="Cycles"/>
<Field DataType="Boolean" Name="DataAvailable"/>
<Field IsOptional="true" DataType="LocationTypeEnumeration" Name="LocationType"/>
</Definition>
</UADataType>

MethodCall:

CallMethodRequest tCallMethodRequest = new CallMethodRequest(tObjectId, tMethodId, new Variant[]{});
CallMethodResult tCallMethodResult = pOpcUaClient.call(tCallMethodRequest).get();
System.out.println(tCallMethodResult.getStatusCode());

How to call a method which requires ScanSettings? Do I need to pass the Variant-array with three Variants containing Duration, Cycles and DataAvailable?

Or

do i need to do something like this ?

EDIT:

Tried it with a ScanSettings-class and got the error:

10:08:52.655 [ua-shared-pool-2] WARN org.eclipse.milo.opcua.stack.core.serialization.OpcUaBinaryStreamEncoder - Not a built-in type: class ScanSettings

My ScanSettings-class:

public class ScanSettings {

private final double duration;
private final int cycles;
private final boolean dataAvailable;

public ScanSettings() {
    this(1000.0, 1, true);
}

public ScanSettings(double pDuration, int pCycles, boolean pDataAvailable) {
    duration = pDuration;
    cycles = pCycles;
    dataAvailable = pDataAvailable;
}

public double getDuration() {
    return duration;
}

public int getCycles() {
    return cycles;
}

public boolean isDataAvailable() {
    return dataAvailable;
}

@Override
public int hashCode() {
    return Objects.hashCode(duration);
}

@Override
public boolean equals(final Object obj) {
    return super.equals(obj);
}

@Override
public String toString() {
    return duration + " " + cycles + " " + dataAvailable;
}

public static class Codec extends GenericDataTypeCodec<ScanSettings> {

    @Override
    public Class<ScanSettings> getType() {
        return ScanSettings.class;
    }

    @Override
    public ScanSettings decode(final SerializationContext context, final UaDecoder reader) throws UaSerializationException {
        double tDuration = reader.readDouble("Duration");
        int tCycle = reader.readInt32("Cycle");
        boolean tDataAvalible = reader.readBoolean("DataAvailable");

        return new ScanSettings(tDuration, tCycle, tDataAvalible);
    }

    @Override
    public void encode(final SerializationContext context, final ScanSettings pScanSettings, final UaEncoder writer) throws UaSerializationException {
        writer.writeDouble("Duration", pScanSettings.duration);
        writer.writeInt32("Cycle", pScanSettings.cycles);
        writer.writeBoolean("DataAvailable", pScanSettings.dataAvailable);
    }
}

}

Registrating it with:

OpcUaBinaryDataTypeDictionary tOpcUaBinaryDataTypeDictionary = new OpcUaBinaryDataTypeDictionary("urn:ScanSettings");

            NodeId binaryEncodingId = new NodeId(2, "DataType.ScanSettings.BinaryEncoding");

            tOpcUaBinaryDataTypeDictionary.registerStructCodec(new ScanSettings.Codec().asBinaryCodec(), "ScanSettings", binaryEncodingId);

            OpcUaDataTypeManager.getInstance().registerTypeDictionary(tOpcUaBinaryDataTypeDictionary);

Solution

  • I think because there is no code generation support right now the best thing to do is take advantage of the fact the client already knows how to read data type dictionaries and turn them into generic Struct objects, as well as receive those Struct objects and encode them when necessary.

    Your method call would look something like this:

    Struct scanSettings = Struct.builder("ScanSettings")
        .addMember("LocationTypeSpecified", 0)
        .addMember("Reserved1", 0)
        .addMember("Duration", 3.14)
        .addMember("Cycles", 1)
        .addMember("DataAvailable", false)
        .build();
    
    ExtensionObject xo = ExtensionObject.encodeAsByteString(
        scanSettings,
        new NodeId(1, 3010),
        client.getDataTypeManager()
    );
    
    CallMethodRequest request = new CallMethodRequest(
        objectId,
        methodId,
        new Variant[]{new Variant(xo)}
    );