defmodule OpcUA.Common do @moduledoc """ This module covers common functions for Client & Server behavior. """ alias OpcUA.QualifiedName alias OpcUA.{ExpandedNodeId, NodeId, QualifiedName} defmacro __using__(opts) do quote location: :keep, bind_quoted: [opts: opts] do use GenServer, opts require Logger @c_timeout 5000 @mix_env Mix.env() defmodule State do @moduledoc false # port: C port process # controlling_process: parent process defstruct port: nil, controlling_process: nil end # Write nodes Attributes functions @doc """ Change the browse name of a node in the server. """ @spec write_node_browse_name(GenServer.server(), %NodeId{}, %QualifiedName{}) :: :ok | {:error, binary()} | {:error, :einval} def write_node_browse_name(pid, %NodeId{} = node_id, browse_name) do GenServer.call(pid, {:write, {:browse_name, node_id, browse_name}}) end @doc """ Change the display name attribute of a node in the server. """ @spec write_node_display_name(GenServer.server(), %NodeId{}, binary(), binary()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_display_name(pid, %NodeId{} = node_id, locale, name) do GenServer.call(pid, {:write, {:display_name, node_id, {locale, name}}}) end @doc """ Change description attribute of a node in the server. """ @spec write_node_description(GenServer.server(), %NodeId{}, binary(), binary()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_description(pid, %NodeId{} = node_id, locale, description) do GenServer.call(pid, {:write, {:description, node_id, {locale, description}}}) end # TODO: friendlier write_mask params @doc """ Change 'Write Mask' attribute of a node in the server. """ @spec write_node_write_mask(GenServer.server(), %NodeId{}, integer()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_write_mask(pid, %NodeId{} = node_id, write_mask) do GenServer.call(pid, {:write, {:write_mask, node_id, write_mask}}) end @doc """ Change 'Is Abstract' attribute of a node in the server. """ @spec write_node_is_abstract(GenServer.server(), %NodeId{}, boolean()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_is_abstract(pid, %NodeId{} = node_id, is_abstract?) do GenServer.call(pid, {:write, {:is_abstract, node_id, is_abstract?}}) end @doc """ Change 'Inverse name' attribute of a node in the server. """ @spec write_node_inverse_name(GenServer.server(), %NodeId{}, binary(), binary()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_inverse_name(pid, %NodeId{} = node_id, locale, inverse_name) do GenServer.call(pid, {:write, {:inverse_name, node_id, {locale, inverse_name}}}) end @doc """ Change 'data_type' attribute of a node in the server. """ @spec write_node_data_type(GenServer.server(), %NodeId{}, %NodeId{}) :: :ok | {:error, binary()} | {:error, :einval} def write_node_data_type(pid, %NodeId{} = node_id, %NodeId{} = data_type_node_id) do GenServer.call(pid, {:write, {:data_type, node_id, data_type_node_id}}) end @doc """ Change 'Value rank' of a node in the server. This attribute indicates whether the value attribute of the variable is an array and how many dimensions the array has. It may have the following values: value_rank >= 1: the value is an array with the specified number of dimensions value_rank = 0: the value is an array with one or more dimensions value_rank = -1: the value is a scalar value_rank = -2: the value can be a scalar or an array with any number of dimensions value_rank = -3: the value can be a scalar or a one dimensional array """ @spec write_node_value_rank(GenServer.server(), %NodeId{}, integer()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_value_rank(pid, %NodeId{} = node_id, value_rank) when value_rank >= -3 and is_integer(value_rank) do GenServer.call(pid, {:write, {:value_rank, node_id, value_rank}}) end @doc """ Change 'Array Dimensions' of a node in the server. """ @spec write_node_array_dimensions(GenServer.server(), %NodeId{}, list()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_array_dimensions(pid, %NodeId{} = node_id, array_dimensions) when is_list(array_dimensions) do GenServer.call(pid, {:write, {:array_dimensions, node_id, array_dimensions}}) end @doc """ Change 'Access level' of a node in the server. """ @spec write_node_access_level(GenServer.server(), %NodeId{}, integer()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_access_level(pid, %NodeId{} = node_id, access_level) do GenServer.call(pid, {:write, {:access_level, node_id, access_level}}) end @doc """ Change 'Minimum Sampling Interval level' of a node in the server. """ @spec write_node_minimum_sampling_interval(GenServer.server(), %NodeId{}, integer()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_minimum_sampling_interval( pid, %NodeId{} = node_id, minimum_sampling_interval ) do GenServer.call( pid, {:write, {:minimum_sampling_interval, node_id, minimum_sampling_interval}} ) end @doc """ Change 'Historizing' attribute of a node in the server. """ @spec write_node_historizing(GenServer.server(), %NodeId{}, boolean()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_historizing(pid, %NodeId{} = node_id, historizing?) do GenServer.call(pid, {:write, {:historizing, node_id, historizing?}}) end @doc """ Change 'Executable' attribute of a node in the server. """ @spec write_node_executable(GenServer.server(), %NodeId{}, boolean()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_executable(pid, %NodeId{} = node_id, executable?) do GenServer.call(pid, {:write, {:executable, node_id, executable?}}) end @doc """ Change 'event_notifier' attribute of a node in the server. """ @spec write_node_event_notifier(GenServer.server(), %NodeId{}, integer()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_event_notifier(pid, %NodeId{} = node_id, event_notifier) when is_integer(event_notifier) do GenServer.call(pid, {:write, {:event_notifier, node_id, event_notifier}}) end @doc """ Change 'Value' attribute of a node in the server. """ @spec write_node_value(GenServer.server(), %NodeId{}, integer(), term()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_value(pid, %NodeId{} = node_id, data_type, value, index \\ 0) do GenServer.call(pid, {:write, {:value, node_id, {data_type, value, index}}}) end @doc """ Creates a blank 'value array' attribute of a node in the server. Note: the array must match with 'value_rank' and 'array_dimensions' attribute. """ @spec write_node_blank_array(GenServer.server(), %NodeId{}, integer(), list()) :: :ok | {:error, binary()} | {:error, :einval} def write_node_blank_array(pid, %NodeId{} = node_id, data_type, array_dimensions) when is_integer(data_type) and is_list(array_dimensions) do GenServer.call(pid, {:write, {:array, node_id, {data_type, array_dimensions}}}) end # Read nodes Attributes function @doc """ Reads the node_id attribute of a node in the server. """ @spec read_node_node_id(GenServer.server(), %NodeId{}) :: {:ok, %NodeId{}} | {:error, binary()} | {:error, :einval} def read_node_node_id(pid, %NodeId{} = node_id) do GenServer.call(pid, {:read, {:node_id, node_id}}) end @doc """ Reads the node_class attribute of a node in the server. """ @spec read_node_node_class(GenServer.server(), %NodeId{}) :: {:ok, %NodeId{}} | {:error, binary()} | {:error, :einval} def read_node_node_class(pid, %NodeId{} = node_id) do GenServer.call(pid, {:read, {:node_class, node_id}}) end @doc """ Reads the browse name attribute of a node in the server. """ @spec read_node_browse_name(GenServer.server(), %NodeId{}) :: {:ok, %QualifiedName{}} | {:error, binary()} | {:error, :einval} def read_node_browse_name(pid, %NodeId{} = node_id) do GenServer.call(pid, {:read, {:browse_name, node_id}}) end @doc """ Reads the display name attribute of a node in the server. """ @spec read_node_display_name(GenServer.server(), %NodeId{}) :: {:ok, {binary(), binary()}} | {:error, binary()} | {:error, :einval} def read_node_display_name(pid, %NodeId{} = node_id) do GenServer.call(pid, {:read, {:display_name, node_id}}) end @doc """ Reads description attribute of a node in the server. """ @spec read_node_description(GenServer.server(), %NodeId{}) :: {:ok, {binary(), binary()}} | {:error, binary()} | {:error, :einval} def read_node_description(pid, node_id) do GenServer.call(pid, {:read, {:description, node_id}}) end @doc """ Reads 'Is Abstract' attribute of a node in the server. """ @spec read_node_is_abstract(GenServer.server(), %NodeId{}) :: {:ok, boolean()} | {:error, binary()} | {:error, :einval} def read_node_is_abstract(pid, node_id) do GenServer.call(pid, {:read, {:is_abstract, node_id}}) end @doc """ Reads 'Symmetric' attribute of a node in the server. """ @spec read_node_symmetric(GenServer.server(), %NodeId{}) :: {:ok, boolean()} | {:error, binary()} | {:error, :einval} def read_node_symmetric(pid, node_id) do GenServer.call(pid, {:read, {:symmetric, node_id}}) end # TODO: friendlier write_mask params @doc """ Reads 'Write Mask' attribute of a node in the server. """ @spec read_node_write_mask(GenServer.server(), %NodeId{}) :: {:ok, integer()} | {:error, binary()} | {:error, :einval} def read_node_write_mask(pid, node_id) do GenServer.call(pid, {:read, {:write_mask, node_id}}) end @doc """ Reads 'data_type' attribute of a node in the server. """ @spec read_node_data_type(GenServer.server(), %NodeId{}) :: {:ok, %NodeId{}} | {:error, binary()} | {:error, :einval} def read_node_data_type(pid, node_id) do GenServer.call(pid, {:read, {:data_type, node_id}}) end @doc """ Reads 'Inverse name' attribute of a node in the server. """ @spec read_node_inverse_name(GenServer.server(), %NodeId{}) :: {:ok, {binary(), binary()}} | {:error, binary()} | {:error, :einval} def read_node_inverse_name(pid, node_id) do GenServer.call(pid, {:read, {:inverse_name, node_id}}) end @doc """ Reads 'contains_no_loops' attribute of a node in the server. """ @spec read_node_contains_no_loops(GenServer.server(), %NodeId{}) :: {:ok, {binary(), binary()}} | {:error, binary()} | {:error, :einval} def read_node_contains_no_loops(pid, node_id) do GenServer.call(pid, {:read, {:contains_no_loops, node_id}}) end @doc """ Reads 'Value Rank' of a node in the server. """ @spec read_node_value_rank(GenServer.server(), %NodeId{}) :: {:ok, integer()} | {:error, binary()} | {:error, :einval} def read_node_value_rank(pid, node_id) do GenServer.call(pid, {:read, {:value_rank, node_id}}) end @doc """ Reads 'array_dimensions' of a node in the server. """ @spec read_node_array_dimensions(GenServer.server(), %NodeId{}) :: {:ok, list()} | {:error, binary()} | {:error, :einval} def read_node_array_dimensions(pid, node_id) do GenServer.call(pid, {:read, {:array_dimensions, node_id}}) end @doc """ Reads 'Access level' of a node in the server. """ @spec read_node_access_level(GenServer.server(), %NodeId{}) :: {:ok, integer()} | {:error, binary()} | {:error, :einval} def read_node_access_level(pid, node_id) do GenServer.call(pid, {:read, {:access_level, node_id}}) end @doc """ Reads 'Minimum Sampling Interval level' of a node in the server. """ @spec read_node_minimum_sampling_interval(GenServer.server(), %NodeId{}) :: {:ok, integer()} | {:error, binary()} | {:error, :einval} def read_node_minimum_sampling_interval(pid, node_id) do GenServer.call(pid, {:read, {:minimum_sampling_interval, node_id}}) end @doc """ Reads 'Historizing' attribute of a node in the server. """ @spec read_node_historizing(GenServer.server(), %NodeId{}) :: {:ok, boolean()} | {:error, binary()} | {:error, :einval} def read_node_historizing(pid, node_id) do GenServer.call(pid, {:read, {:historizing, node_id}}) end @doc """ Reads 'Executable' attribute of a node in the server. """ @spec read_node_executable(GenServer.server(), %NodeId{}) :: {:ok, boolean()} | {:error, binary()} | {:error, :einval} def read_node_executable(pid, node_id) do GenServer.call(pid, {:read, {:executable, node_id}}) end @doc """ Reads 'event_notifier' attribute of a node in the server. """ @spec read_node_event_notifier(GenServer.server(), %NodeId{}) :: {:ok, term()} | {:error, binary()} | {:error, :einval} def read_node_event_notifier(pid, node_id) do GenServer.call(pid, {:read, {:event_notifier, node_id}}) end @doc """ Reads 'value' attribute of a node in the server. Note: If the value is an array you can search a scalar using `index` parameter. """ @spec read_node_value(GenServer.server(), %NodeId{}, integer()) :: {:ok, term()} | {:error, binary()} | {:error, :einval} def read_node_value(pid, node_id, index \\ 0) do if(@mix_env != :test) do GenServer.call(pid, {:read, {:value, {node_id, index}}}) else # Valgrind GenServer.call(pid, {:read, {:value, {node_id, index}}}, :infinity) end end @doc """ Reads 'value' attribute of a node in the server. Note: If the value is an array you can search a scalar using `index` parameter. """ @spec read_node_value_by_index(GenServer.server(), %NodeId{}, integer()) :: {:ok, term()} | {:error, binary()} | {:error, :einval} def read_node_value_by_index(pid, node_id, index \\ 0) do if(@mix_env != :test) do GenServer.call(pid, {:read, {:value_by_index, {node_id, index}}}) else # Valgrind GenServer.call(pid, {:read, {:value_by_index, {node_id, index}}}, :infinity) end end @doc """ Reads 'Value' attribute (matching data type) of a node in the server. """ @spec read_node_value_by_data_type(GenServer.server(), %NodeId{}, integer()) :: {:ok, term()} | {:error, binary()} | {:error, :einval} def read_node_value_by_data_type(pid, node_id, data_type) when is_integer(data_type) do GenServer.call(pid, {:read, {:value_by_data_type, {node_id, data_type}}}) end # Write nodes Attributes handlers def handle_call({:write, {:browse_name, node_id, browse_name}}, caller_info, state) do c_args = {to_c(node_id), to_c(browse_name)} call_port(state, :write_node_browse_name, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:display_name, node_id, {locale, name}}}, caller_info, state) when is_binary(locale) and is_binary(name) do c_args = {to_c(node_id), locale, name} call_port(state, :write_node_display_name, caller_info, c_args) {:noreply, state} end def handle_call( {:write, {:description, node_id, {locale, description}}}, caller_info, state ) when is_binary(locale) and is_binary(description) do c_args = {to_c(node_id), locale, description} call_port(state, :write_node_description, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:write_mask, node_id, write_mask}}, caller_info, state) when is_integer(write_mask) do c_args = {to_c(node_id), write_mask} call_port(state, :write_node_write_mask, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:is_abstract, node_id, is_abstract?}}, caller_info, state) when is_boolean(is_abstract?) do c_args = {to_c(node_id), is_abstract?} call_port(state, :write_node_is_abstract, caller_info, c_args) {:noreply, state} end def handle_call( {:write, {:inverse_name, node_id, {locale, inverse_name}}}, caller_info, state ) when is_binary(locale) and is_binary(inverse_name) do c_args = {to_c(node_id), locale, inverse_name} call_port(state, :write_node_inverse_name, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:data_type, node_id, data_type_node_id}}, caller_info, state) do c_args = {to_c(node_id), to_c(data_type_node_id)} call_port(state, :write_node_data_type, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:value_rank, node_id, value_rank}}, caller_info, state) when is_integer(value_rank) do c_args = {to_c(node_id), value_rank} call_port(state, :write_node_value_rank, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:array_dimensions, node_id, array_dimensions}}, caller_info, state) when is_list(array_dimensions) do with true <- all_must_be(:integer, array_dimensions) do c_args = {to_c(node_id), length(array_dimensions), List.to_tuple(array_dimensions)} call_port(state, :write_node_array_dimensions, caller_info, c_args) {:noreply, state} else _ -> {:reply, {:error, :einval}, state} end end def handle_call({:write, {:access_level, node_id, access_level}}, caller_info, state) when is_integer(access_level) do c_args = {to_c(node_id), access_level} call_port(state, :write_node_access_level, caller_info, c_args) {:noreply, state} end def handle_call( {:write, {:minimum_sampling_interval, node_id, minimum_sampling_interval}}, caller_info, state ) when is_float(minimum_sampling_interval) do c_args = {to_c(node_id), minimum_sampling_interval} call_port(state, :write_node_minimum_sampling_interval, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:historizing, node_id, historizing?}}, caller_info, state) when is_boolean(historizing?) do c_args = {to_c(node_id), historizing?} call_port(state, :write_node_historizing, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:executable, node_id, executable?}}, caller_info, state) when is_boolean(executable?) do c_args = {to_c(node_id), executable?} call_port(state, :write_node_executable, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:event_notifier, node_id, event_notifier}}, caller_info, state) when is_integer(event_notifier) do c_args = {to_c(node_id), event_notifier} call_port(state, :write_node_event_notifier, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:value, node_id, {data_type, raw_value}}}, caller_info, state) do c_args = {to_c(node_id), data_type, 0, value_to_c(data_type, raw_value)} call_port(state, :write_node_value, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:value, node_id, {data_type, raw_value, index}}}, caller_info, state) do c_args = {to_c(node_id), data_type, index, value_to_c(data_type, raw_value)} call_port(state, :write_node_value, caller_info, c_args) {:noreply, state} end def handle_call({:write, {:array, node_id, {data_type, array_dimensions}}}, caller_info, state) when is_integer(data_type) and is_list(array_dimensions) do with true <- all_must_be(:integer, array_dimensions), array_raw_size <- get_array_raw_size(array_dimensions) do #{node_id, data_type, } c_args = {to_c(node_id), data_type, length(array_dimensions), array_raw_size, List.to_tuple(array_dimensions)} call_port(state, :write_node_blank_array, caller_info, c_args) {:noreply, state} else _ -> {:reply, {:error, :einval}, state} end end # Read nodes Attributes handlers def handle_call({:read, {:node_id, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_node_id, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:browse_name, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_browse_name, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:node_class, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_node_class, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:display_name, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_display_name, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:description, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_description, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:is_abstract, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_is_abstract, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:symmetric, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_symmetric, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:write_mask, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_write_mask, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:data_type, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_data_type, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:inverse_name, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_inverse_name, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:contains_no_loops, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_contains_no_loops, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:value_rank, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_value_rank, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:array_dimensions, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_array_dimensions, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:access_level, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_access_level, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:minimum_sampling_interval, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_minimum_sampling_interval, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:historizing, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_historizing, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:executable, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_executable, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:event_notifier, node_id}}, caller_info, state) do c_args = to_c(node_id) call_port(state, :read_node_event_notifier, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:value, {node_id, index}}}, caller_info, state) do c_args = {to_c(node_id), index} call_port(state, :read_node_value, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:value_by_index, {node_id, index}}}, caller_info, state) do c_args = {to_c(node_id), index} call_port(state, :read_node_value_by_index, caller_info, c_args) {:noreply, state} end def handle_call({:read, {:value_by_data_type, {node_id, data_type}}}, caller_info, state) do c_args = {to_c(node_id), data_type} call_port(state, :read_node_value_by_data_type, caller_info, c_args) {:noreply, state} end # Catch all handlers def handle_info({_port, {:data, <>}}, state) do state = c_response |> :erlang.binary_to_term() |> handle_c_response(state) {:noreply, state} end # Write nodes Attributes C handlers defp handle_c_response({:write_node_browse_name, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_display_name, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_description, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_write_mask, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_is_abstract, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_inverse_name, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_data_type, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_value_rank, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_array_dimensions, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_access_level, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response( {:write_node_minimum_sampling_interval, caller_metadata, data}, state ) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_historizing, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_executable, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_event_notifier, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_value, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:write_node_blank_array, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end # Read nodes Attributes C handlers defp handle_c_response({:read_node_node_id, caller_metadata, node_id_response}, state) do response = parse_node_id(node_id_response) GenServer.reply(caller_metadata, response) state end defp handle_c_response({:read_node_node_class, caller_metadata, data}, state) do response = charlist_to_string(data) GenServer.reply(caller_metadata, response) state end defp handle_c_response( {:read_node_browse_name, caller_metadata, browse_name_response}, state ) do response = parse_browse_name(browse_name_response) GenServer.reply(caller_metadata, response) state end defp handle_c_response({:read_node_display_name, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_description, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_write_mask, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_is_abstract, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_symmetric, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_inverse_name, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_data_type, caller_metadata, data_type_response}, state) do response = parse_data_type(data_type_response) GenServer.reply(caller_metadata, response) state end defp handle_c_response({:read_node_value_rank, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_array_dimensions, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_access_level, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_minimum_sampling_interval, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_contains_no_loops, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_historizing, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_executable, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_event_notifier, caller_metadata, data}, state) do GenServer.reply(caller_metadata, data) state end defp handle_c_response({:read_node_value, caller_metadata, value_response}, state) do response = parse_value(value_response) GenServer.reply(caller_metadata, response) state end defp handle_c_response({:read_node_value_by_index, caller_metadata, value_response}, state) do response = parse_value(value_response) GenServer.reply(caller_metadata, response) state end defp handle_c_response( {:read_node_value_by_data_type, caller_metadata, value_response}, state ) do response = parse_value(value_response) GenServer.reply(caller_metadata, response) state end defp use_valgrind?(), do: System.get_env("USE_VALGRIND", "false") defp open_port(executable, "false") do Port.open({:spawn_executable, to_charlist(executable)}, [ {:args, []}, {:packet, 2}, :use_stdio, :binary, :exit_status ]) end defp open_port(executable, valgrind_env) do Logger.warn("(#{__MODULE__}) Valgrind Activated: #{inspect valgrind_env}") Port.open({:spawn_executable, to_charlist("/usr/bin/valgrind.bin")}, [ {:args, [ "-q", "--undef-value-errors=no", "--leak-check=full", "--show-leak-kinds=all", #"--verbose", #"--track-origins=yes", executable ]}, {:packet, 2}, :use_stdio, :binary, :exit_status ]) end defp call_port(state, command, caller, arguments) do msg = {command, caller, arguments} send(state.port, {self(), {:command, :erlang.term_to_binary(msg)}}) end defp charlist_to_string({:ok, charlist}), do: {:ok, to_string(charlist)} defp charlist_to_string(error_response), do: error_response defp to_c(%NodeId{ns_index: ns_index, identifier_type: id_type, identifier: identifier}), do: {id_type, ns_index, identifier} defp to_c(%QualifiedName{ns_index: ns_index, name: name}), do: {ns_index, name} defp to_c(_invalid_struct), do: raise("Invalid Data type") # For NodeId, QualifiedName. defp value_to_c(data_type, value) when data_type in [16, 17, 19], do: to_c(value) # SEMANTICCHANGESTRUCTUREDATATYPE defp value_to_c(data_type, {arg1, arg2}) when data_type == 25, do: {to_c(arg1), to_c(arg2)} defp value_to_c(_data_type, value), do: value defp parse_browse_name({:ok, {ns_index, name}}), do: {:ok, QualifiedName.new(ns_index: ns_index, name: name)} defp parse_browse_name(response), do: response defp parse_node_id({:ok, {ns_index, type, name}}), do: {:ok, NodeId.new(ns_index: ns_index, identifier_type: type, identifier: name)} defp parse_node_id(response), do: response defp parse_data_type({:ok, {ns_index, type, name}}), do: {:ok, NodeId.new(ns_index: ns_index, identifier_type: type, identifier: name)} defp parse_data_type(response), do: response defp parse_value({:ok, {ns_index, type, name, name_space_uri, server_index}}), do: {:ok, ExpandedNodeId.new( node_id: NodeId.new(ns_index: ns_index, identifier_type: type, identifier: name), name_space_uri: name_space_uri, server_index: server_index )} defp parse_value({:ok, {ns_index, type, name}}), do: {:ok, NodeId.new(ns_index: ns_index, identifier_type: type, identifier: name)} defp parse_value({:ok, {{ns_index1, type1, name1}, {ns_index2, type2, name2}}}), do: { :ok, { NodeId.new(ns_index: ns_index1, identifier_type: type1, identifier: name1), NodeId.new(ns_index: ns_index2, identifier_type: type2, identifier: name2) } } defp parse_value({:ok, {ns_index, name}}) when is_integer(ns_index), do: {:ok, QualifiedName.new(ns_index: ns_index, name: name)} defp parse_value({:ok, array}) when is_list(array), do: {:ok, Enum.map(array, fn(data) -> parse_c_value(data) end)} defp parse_value(response), do: response defp parse_c_value({ns_index, type, name, name_space_uri, server_index}), do: ExpandedNodeId.new( node_id: NodeId.new(ns_index: ns_index, identifier_type: type, identifier: name), name_space_uri: name_space_uri, server_index: server_index ) defp parse_c_value({ns_index, type, name}), do: NodeId.new(ns_index: ns_index, identifier_type: type, identifier: name) defp parse_c_value({{ns_index1, type1, name1}, {ns_index2, type2, name2}}), do: { NodeId.new(ns_index: ns_index1, identifier_type: type1, identifier: name1), NodeId.new(ns_index: ns_index2, identifier_type: type2, identifier: name2) } defp parse_c_value({ns_index, name}) when is_integer(ns_index), do: QualifiedName.new(ns_index: ns_index, name: name) defp parse_c_value(array) when is_list(array), do: Enum.map(array, fn(data) -> parse_c_value(data) end) defp parse_c_value(response), do: response @doc false def set_ld_library_path(priv_dir) do System.get_env("LD_LIBRARY_PATH", "") |> String.contains?(priv_dir) |> write_ld_library_path(priv_dir) end defp write_ld_library_path(false, priv_dir) do ld_dirs = System.get_env("LD_LIBRARY_PATH", "") |> Path.join(":") |> Path.join(priv_dir) |> Path.join("/lib") System.put_env("LD_LIBRARY_PATH", ld_dirs) priv_dir end defp all_must_be(:integer, list), do: Enum.all?(list, fn x -> is_integer(x) end) defp get_array_raw_size(array_dimensions), do: Enum.reduce(array_dimensions, 1, fn(x, acc) -> x * acc end) defp write_ld_library_path(true, priv_dir), do: priv_dir defp get_binary_data(binary) when is_binary(binary), do: binary defp get_binary_data(function) when is_function(function), do: function.() defp get_binary_data(_non_binary_or_function), do: nil end end end