Packages

OPCUA Erlang library

Current section

Files

Jump to
opcua src opcua_codec.erl
Raw

src/opcua_codec.erl

-module(opcua_codec).
%% TODO %%
%%
%% - Add or validate support for variable sized option sets
%% - Add or validate the support of data types with subtype as fields;
%% e.g. a structure data type with a field that could be specified as any
%% subtype of sthe defined type. See https://reference.opcfoundation.org/Core/Part3/v105/docs/8.49
%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-include_lib("kernel/include/logger.hrl").
-include("opcua.hrl").
-include("opcua_internal.hrl").
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% API Functions
-export([pack_variant/3]).
-export([builtin_type_name/1]).
-export([builtin_type_id/1]).
%% Schema resolver
-export([resolve/3]).
-export([resolve_enum/2]).
-export([resolve_option_set/2]).
%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec pack_variant(opcua_space:state(), opcua:node_spec(), term()) -> opcua:variant().
pack_variant(_Space, #opcua_node_id{ns = 0, type = numeric, value = Num}, Value)
when ?IS_BUILTIN_TYPE_ID(Num) ->
#opcua_variant{type = builtin_type_name(Num), value = Value};
pack_variant(_Space, #opcua_node_id{ns = 0, type = string, value = Name}, Value)
when ?IS_BUILTIN_TYPE_NAME(Name) ->
#opcua_variant{type = Name, value = Value};
pack_variant(Space, #opcua_node_id{} = NodeId, Value) ->
case opcua_space:schema(Space, NodeId) of
undefined -> throw({bad_encoding_error, {schema_not_found, NodeId}});
#opcua_enum{fields = Fields} ->
%TODO: Remove code duplication with opcua_codec_binary
[Idx] = [I || #opcua_field{name = N, value = I} <- Fields, Value =:= N],
#opcua_variant{type = int32, value = Idx};
#opcua_option_set{fields = Fields} ->
%TODO: Remove code duplication with opcua_codec_binary
%TODO: Add support for variable option set size
ChosenFields = [Field || Field = #opcua_field{name = Name} <- Fields,
lists:member(Name, Value)],
Int = lists:foldl(fun(X, Acc) ->
Acc bxor (1 bsl X#opcua_field.value)
end, 0, ChosenFields),
#opcua_variant{type = uint32, value = Int};
_Other ->
ExtObj = #opcua_extension_object{type_id = NodeId, encoding = byte_string, body = Value},
#opcua_variant{type = extension_object, value = ExtObj}
end;
pack_variant(Space, NodeSpec, Value) ->
pack_variant(Space, opcua_node:id(NodeSpec), Value).
-spec resolve(opcua_space:state(), opcua:node_spec(), integer()) -> term().
resolve(Space, TypeId, Value) ->
case opcua_space:schema(Space, TypeId) of
undefined -> throw({schema_not_found, TypeId});
#opcua_enum{} = Schema -> resolve_enum(Schema, Value);
#opcua_option_set{} = Schema -> resolve_option_set(Schema, Value);
#opcua_structure{} -> resolve_structure(Space, TypeId, Value);
_Schema -> throw(not_implemented)
end.
resolve_structure(Space, RootType, #opcua_extension_object{type_id = SubType, body = Data}) ->
case opcua_space:is_subtype(Space, SubType, RootType) of
false -> throw({invalid_subtype, SubType, RootType});
true -> Data
end.
resolve_enum(#opcua_enum{node_id = TypeId, fields = Fields}, Value) ->
case [F || F = #opcua_field{value = V} <- Fields, Value =:= V] of
[Field] -> Field#opcua_field.name;
[] -> throw({bad_enum_value, TypeId, Value})
end.
resolve_option_set(#opcua_option_set{fields = Fields}, Value) ->
FieldNames = lists:foldl(fun(X, Acc) ->
case (Value bsr X#opcua_field.value) rem 2 of
0 -> Acc;
1 -> [X#opcua_field.name | Acc]
end
end, [], Fields),
lists:reverse(FieldNames).
builtin_type_name( 1) -> boolean;
builtin_type_name( 2) -> sbyte;
builtin_type_name( 3) -> byte;
builtin_type_name( 4) -> int16;
builtin_type_name( 5) -> uint16;
builtin_type_name( 6) -> int32;
builtin_type_name( 7) -> uint32;
builtin_type_name( 8) -> int64;
builtin_type_name( 9) -> uint64;
builtin_type_name(10) -> float;
builtin_type_name(11) -> double;
builtin_type_name(12) -> string;
builtin_type_name(13) -> date_time;
builtin_type_name(14) -> guid;
builtin_type_name(15) -> byte_string;
builtin_type_name(16) -> xml;
builtin_type_name(17) -> node_id;
builtin_type_name(18) -> expanded_node_id;
builtin_type_name(19) -> status_code;
builtin_type_name(20) -> qualified_name;
builtin_type_name(21) -> localized_text;
builtin_type_name(22) -> extension_object;
builtin_type_name(23) -> data_value;
builtin_type_name(24) -> variant;
builtin_type_name(25) -> diagnostic_info;
builtin_type_name(26) -> byte_string;
builtin_type_name(27) -> byte_string;
builtin_type_name(28) -> byte_string;
builtin_type_name(29) -> byte_string;
builtin_type_name(30) -> byte_string;
builtin_type_name(31) -> byte_string.
builtin_type_id(boolean) -> 1;
builtin_type_id(sbyte) -> 2;
builtin_type_id(byte) -> 3;
builtin_type_id(int16) -> 4;
builtin_type_id(uint16) -> 5;
builtin_type_id(int32) -> 6;
builtin_type_id(uint32) -> 7;
builtin_type_id(int64) -> 8;
builtin_type_id(uint64) -> 9;
builtin_type_id(float) -> 10;
builtin_type_id(double) -> 11;
builtin_type_id(string) -> 12;
builtin_type_id(date_time) -> 13;
builtin_type_id(guid) -> 14;
builtin_type_id(byte_string) -> 15;
builtin_type_id(xml) -> 16;
builtin_type_id(node_id) -> 17;
builtin_type_id(expanded_node_id) -> 18;
builtin_type_id(status_code) -> 19;
builtin_type_id(qualified_name) -> 20;
builtin_type_id(localized_text) -> 21;
builtin_type_id(extension_object) -> 22;
builtin_type_id(data_value) -> 23;
builtin_type_id(variant) -> 24;
builtin_type_id(diagnostic_info) -> 25.