Packages

OPCUA Erlang library

Current section

Files

Jump to
opcua src opcua_nodeset_datatypes.erl
Raw

src/opcua_nodeset_datatypes.erl

-module(opcua_nodeset_datatypes).
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% API Functions
-export([generate_schemas/1]).
%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-include_lib("kernel/include/logger.hrl").
-include("opcua.hrl").
-include("opcua_internal.hrl").
%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
generate_schemas(DataTypeNodesProplist) ->
Digraph = generate_data_types_digraph(DataTypeNodesProplist),
SortedNodes = digraph_utils:topsort(Digraph),
generate_schemas(Digraph, SortedNodes, #{}).
%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
generate_schemas(_Digraph, [], Acc) ->
maps:values(Acc);
generate_schemas(Digraph, [#opcua_node{node_id = #opcua_node_id{value = Id}} | Nodes], Acc)
when ?IS_BUILTIN_TYPE_ID(Id) ->
generate_schemas(Digraph, Nodes, Acc);
generate_schemas(Digraph, [Node | Nodes], Acc) ->
[#opcua_node{node_id = ParentNodeId}] = digraph:in_neighbours(Digraph, Node),
#opcua_node{node_id = NodeId, browse_name = BrowseName, node_class = NodeClass} = Node,
DataTypeDefinition = case NodeClass#opcua_data_type.data_type_definition of
undefined -> #{};
Map -> Map
end,
Fields = maps:get(fields, DataTypeDefinition, []),
IsUnion = maps:get(is_union, DataTypeDefinition, false),
IsOptionSet = maps:get(is_option_set, DataTypeDefinition, false),
RecordFields = [field_to_record(Field) || Field <- Fields],
WithOptions = lists:any(fun(X) -> X#opcua_field.is_optional end, RecordFields),
{RootNodeId, NewFields} = resolve_inheritance(ParentNodeId, RecordFields, Acc),
DataType = resolve_type(RootNodeId, NodeId, NewFields, IsUnion, IsOptionSet, WithOptions),
StringNodeId = #opcua_node_id{type = string, value = BrowseName},
Keys = [StringNodeId, NodeId, {0, BrowseName}, {0, NodeId#opcua_node_id.value}],
generate_schemas(Digraph, Nodes, maps:put(NodeId, {Keys, DataType}, Acc)).
field_to_record({Name, Attrs}) ->
#opcua_field{name = Name,
node_id = maps:get(data_type, Attrs, #opcua_node_id{value = 24}),
value_rank = maps:get(value_rank, Attrs, -1),
value = maps:get(value, Attrs, undefined),
is_optional = maps:get(is_optional, Attrs, false)}.
generate_data_types_digraph(DataTypeNodesProplist) ->
DataTypeNodesMap = maps:from_list(DataTypeNodesProplist),
Digraph = digraph:new([acyclic]),
fill_data_types(Digraph, DataTypeNodesMap, maps:values(DataTypeNodesMap)).
fill_data_types(Digraph, _NodesMap, []) ->
Digraph;
fill_data_types(Digraph, NodesMap, [{Node, References} | Nodes]) ->
case get_sub_type_reference(References) of
{SourceNodeId, TargetNodeId} ->
{Source, _} = maps:get(SourceNodeId, NodesMap),
{Target, _} = maps:get(TargetNodeId, NodesMap),
%% NOTE: we might add nodes multiple times here,
%% but the add operation is idempotent anyway
digraph:add_vertex(Digraph, Source),
digraph:add_vertex(Digraph, Target),
digraph:add_edge(Digraph, Source, Target);
undefined ->
digraph:add_vertex(Digraph, Node) %% we got a root node
end,
fill_data_types(Digraph, NodesMap, Nodes).
get_sub_type_reference(References) ->
SubTypeRefs = [Ref || Ref = #opcua_reference{type_id = #opcua_node_id{value = 45}} <- References],
case SubTypeRefs of
[] ->
undefined;
[#opcua_reference{source_id = SourceNodeId, target_id = TargetNodeId}] ->
{SourceNodeId, TargetNodeId}
end.
resolve_type(#opcua_node_id{value = 22}, NodeId, Fields, false, false, WithOptions) ->
#opcua_structure{node_id = NodeId, fields = Fields, with_options = WithOptions};
resolve_type(#opcua_node_id{value = 22}, NodeId, Fields, true, _IsOptionSet, _WithOptions) ->
#opcua_union{node_id = NodeId, fields = Fields};
resolve_type(#opcua_node_id{} = MaskTypeNodeId, NodeId, Fields, _IsUnion, true, _WithOptions) ->
#opcua_option_set{node_id = NodeId, mask_type = MaskTypeNodeId, fields = Fields};
resolve_type(#opcua_node_id{value = 29}, NodeId, Fields, _IsUnion, _IsOptionSet, _WithOptions) ->
#opcua_enum{node_id = NodeId, fields = Fields};
resolve_type(BuiltinNodeId = #opcua_node_id{value = Id}, NodeId, _Fields, _IsUnion, _IsOptionSet, _WithOptions)
when ?IS_BUILTIN_TYPE_ID(Id) ->
#opcua_builtin{node_id = NodeId, builtin_node_id = BuiltinNodeId}.
resolve_inheritance(ParentNodeId, Fields, DataTypes) ->
{RootNodeId, NewFields} = resolve_inheritance1(ParentNodeId, DataTypes),
{RootNodeId, NewFields ++ Fields}.
resolve_inheritance1(NodeId = #opcua_node_id{value = Id}, _DataTypes)
when Id=:=29 ->
{NodeId, []};
resolve_inheritance1(NodeId = #opcua_node_id{value = Id}, _DataTypes)
when ?IS_BUILTIN_TYPE_ID(Id) ->
{NodeId, []};
resolve_inheritance1(NodeId, DataTypes) ->
{_Keys, DataType} = maps:get(NodeId, DataTypes),
case DataType of
#opcua_structure{fields = Fields} -> {#opcua_node_id{value = 22}, Fields};
#opcua_union{fields = Fields} -> {#opcua_node_id{value = 22}, Fields};
#opcua_enum{fields = Fields} -> {#opcua_node_id{value = 29}, Fields};
#opcua_builtin{builtin_node_id = BuiltinNodeId} -> {BuiltinNodeId, []}
end.