You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Apache Milo客户端动态发现并填充OPC UA自定义结构体写入服务器

Apache Milo动态处理OPC UA自定义结构体写入方案优化

问题背景

需要通过Apache Milo客户端向OPC UA服务器写入自定义结构体,要求运行时动态发现所有数据类型,无预先定义任何自定义类型信息,需基于结构体的字段名称和数据类型解析JSON、填充结构体并完成写入。已通过DataTypeDictionarySessionInitializer实现连接时自动获取数据类型定义,但当前填充结构体的方式存在诸多问题。

现有方案的痛点

  1. 依赖反射访问私有字段:通过反射获取AbstractCodec的私有fields字段,依赖Milo内部实现细节,后续版本升级可能失效
  2. 类型判断兼容性差:通过类型名字符串判断字段类型,不同服务器实现可能返回不同格式的类型名,导致匹配失败
  3. 功能覆盖不全:缺少对更多基础类型、嵌套结构体、数组等复杂类型的支持

优化方案

1. 用官方API替代反射获取字段信息

Milo的GenericStructCodec提供了getDefinition()方法,可直接获取StructuredDataTypeDefinition对象,包含所有字段的名称、数据类型NodeId等合法公开信息,完全无需反射:

// 获取结构体Codec
final GenericStructCodec dataTypeCodec = 
    (GenericStructCodec) client.getDynamicSerializationContext().getDataTypeManager().getCodec(dataTypeNodeId);
// 获取合法公开的结构体定义
StructuredDataTypeDefinition definition = dataTypeCodec.getDefinition();

// 遍历所有字段
for (StructuredDataTypeDefinition.Field field : definition.getFields()) {
    String fieldName = field.getName();
    NodeId fieldDataTypeId = field.getDataTypeId();
    // 后续处理字段逻辑
}

2. 基于NodeId判断数据类型,替代字符串匹配

OPC UA的所有基础类型都有固定的标准NodeId(可通过Identifiers类直接引用),对比NodeId的方式完全兼容所有服务器实现,不存在字符串匹配的歧义:

// 示例:判断基础类型
if (fieldDataTypeId.equals(Identifiers.UInt32)) {
    builder.addMember(fieldName, UInteger.valueOf(jsonNode.toString()));
} else if (fieldDataTypeId.equals(Identifiers.String)) {
    builder.addMember(fieldName, jsonNode.toString());
} else if (fieldDataTypeId.equals(Identifiers.Boolean)) {
    builder.addMember(fieldName, Boolean.parseBoolean(jsonNode.toString()));
}

3. 递归处理嵌套结构体

编写工具方法递归处理嵌套的自定义结构体,自动识别字段类型并递归填充:

private static ExtensionObject buildExtensionObjectFromJson(OpcUaClient client, Object jsonNode, NodeId dataTypeNodeId) throws Exception {
    DataTypeCodec codec = client.getDynamicSerializationContext().getDataTypeManager().getCodec(dataTypeNodeId);
    
    if (codec instanceof GenericStructCodec) {
        GenericStructCodec structCodec = (GenericStructCodec) codec;
        StructuredDataTypeDefinition definition = structCodec.getDefinition();
        Struct.Builder builder = Struct.builder(definition.getName());
        
        for (StructuredDataTypeDefinition.Field field : definition.getFields()) {
            String fieldName = field.getName();
            NodeId fieldTypeId = field.getDataTypeId();
            Object fieldJson = ((Map<?, ?>) jsonNode).get(fieldName);
            
            if (fieldJson == null) continue;
            
            // 基础类型处理
            if (fieldTypeId.equals(Identifiers.UInt32)) {
                builder.addMember(fieldName, UInteger.valueOf(fieldJson.toString()));
            } else if (fieldTypeId.equals(Identifiers.String)) {
                builder.addMember(fieldName, fieldJson.toString());
            } else if (fieldTypeId.equals(Identifiers.Boolean)) {
                builder.addMember(fieldName, Boolean.parseBoolean(fieldJson.toString()));
            } 
            // 枚举类型处理
            else if (client.getDynamicSerializationContext().getDataTypeManager().getCodec(fieldTypeId) instanceof GenericEnumCodec) {
                builder.addMember(fieldName, Integer.valueOf(fieldJson.toString()));
            } 
            // 嵌套结构体递归处理
            else if (client.getDynamicSerializationContext().getDataTypeManager().getCodec(fieldTypeId) instanceof GenericStructCodec) {
                ExtensionObject nested = buildExtensionObjectFromJson(client, fieldJson, fieldTypeId);
                builder.addMember(fieldName, nested);
            }
            // 可继续扩展数组、Int32、Double等其他类型
        }
        
        return ExtensionObject.encode(
            client.getDynamicSerializationContext(),
            builder.build(),
            dataTypeNodeId,
            OpcUaDefaultBinaryEncoding.getInstance()
        );
    }
    return null;
}

4. 扩展支持数组类型

通过field.getValueRank()判断字段是否为数组,再批量转换JSON数组元素为对应OPC UA类型:

// 判断是否为数组类型(valueRank >= 0表示数组)
if (field.getValueRank() >= 0) {
    List<?> jsonArray = (List<?>) fieldJson;
    if (fieldTypeId.equals(Identifiers.UInt32)) {
        UInteger[] uintArray = jsonArray.stream()
            .map(item -> UInteger.valueOf(item.toString()))
            .toArray(UInteger[]::new);
        builder.addMember(fieldName, uintArray);
    }
    // 其他数组类型(如String[]、Boolean[])同理扩展
}

总结

  • 抛弃反射依赖,使用Milo官方公开API获取字段定义,保证版本兼容性
  • 基于标准NodeId判断数据类型,彻底解决字符串匹配的兼容性问题
  • 实现递归逻辑支持嵌套结构体,覆盖复杂类型场景
  • 可灵活扩展支持更多基础类型、数组等OPC UA数据类型

内容的提问来源于stack exchange,提问作者Daniel Krüger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 02:24:52