Commit c589054f authored by nickolay.kovalev's avatar nickolay.kovalev
Browse files

stateless library & README

parent 5d8dde8b
# erlopen62541
Erlang port for OPC UA Client functionality based on
[open62541](https://github.com/open62541/open62541) project.
# Compilation
As prerequisite [open62541](https://github.com/open62541/open62541) framework should be installed and
simple example of C++ client/server could be built.
```bash
$ rebar3 clean && rebar3 compile
```
# Basic usage
After successfull compilation shell could be used for easy checking.
NOTE: As this is client functionality, valid and accessible OPC UA server or OPC UA server emulator
is required for completing this step.
Initial version of port has been tested with Prosys OPC Siluation Server.
```bash
$ rebar3 shell
```
```erlang
1> % open port
1> f(P), P = erlopen62541:open_port(<<"/home/<user>/erlopen62541/priv/">>).
#Port<0.10>
2> % connecting to server
2> erlopen62541:connect(P, <<"opc.tcp://192.168.1.33:53530/OPCUA/SimulationServer">>).
ok
3> % reading system namespace values
3> erlopen62541:read_node(P, 0, 2258).
{datetime,<<"25/8/2020 6:21:5.751">>}
4> % reading user namespace values
erlopen62541:read_node(P, 3, 1001).
{integer,23}
```
\ No newline at end of file
%%------------------------------------------------------------------------------
%% @doc
%% Port wrapper for open62541 client
%% Stateless port wrapper for open62541 client
%% @end
%% Copyright (c) 2020,
%% Copyright (c) Zenit&SSE, 2020
%%------------------------------------------------------------------------------
-module(erlopen62541).
-behavior(gen_server).
%% General API
-export([
start_link/0,
open_port/1,
connect/2,
disconnect/1,
get_value/3,
stop/1
]).
% OTP
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
read_node/3
]).
-include("erlopen62541.hrl").
......@@ -33,58 +20,74 @@
-define(PORT_OPTS, [{packet, 4}, binary, exit_status, use_stdio]).
-define(PORT_SYNC_MS, 3000).
-type opc_ua_connection() :: port().
-export_type([opc_ua_connection/0]).
-type opc_ua_op_result() ::
ok |
nok |
{error, Reason :: any()} |
{datetime, Date :: binary()} |
{int, Integer :: integer()} |
{float, Float :: float()} |
{string, String :: binary()} |
{boolean, Bool :: boolean()} |
{exit, Status :: any()}.
-export_type([opc_ua_op_result/0]).
%%==============================================================================
%% General API
%%==============================================================================
start_link() ->
gen_server:start_link(?MODULE, [], []).
%% @doc Opens erlang port either binary location to C++ port
%%------------------------------------------------------------------------------
-spec open_port(Location :: binary()) -> opc_ua_connection().
%%------------------------------------------------------------------------------
open_port(Location) when is_binary(Location) ->
LocationStr = erlang:binary_to_list(Location),
erlang:open_port({spawn_executable, LocationStr ++ ?PORT_NAME}, ?PORT_OPTS).
stop(Pid) ->
gen_server:call(Pid, stop).
%% @doc Connects to given server
%%------------------------------------------------------------------------------
-spec connect(
Connection :: opc_ua_connection(),
ServerName :: binary()
) -> opc_ua_op_result().
%%------------------------------------------------------------------------------
connect(Pid, ServerName) when is_binary(ServerName) ->
gen_server:call(Pid, {connect, ServerName}).
connect(Connection, ServerName) when is_binary(ServerName) ->
ConnectCmd = <<(?CONNECT)/binary, ServerName/binary>>,
sync_read(Connection, ConnectCmd).
disconnect(Pid) ->
gen_server:call(Pid, disconnect).
%% @doc Disconnects to given server
%%------------------------------------------------------------------------------
-spec disconnect(Connection :: opc_ua_connection())
-> ok.
%%------------------------------------------------------------------------------
get_value(Pid, Namespace, NodeId) ->
gen_server:call(Pid, {get_value, Namespace, NodeId}).
disconnect(Connection) ->
sync_read(Connection, ?DISCONNECT),
true = erlang:port_close(Connection),
ok.
init([]) ->
Port = erlang:open_port({spawn_executable, "/home/nick/erlopen62541/priv/" ++ ?PORT_NAME}, ?PORT_OPTS),
{ok, #{port => Port}}.
%% @doc read value of OPC UA node for given connection
%%------------------------------------------------------------------------------
-spec read_node(
Connection :: opc_ua_connection(),
Namespace :: integer(),
NodeId :: integer()
) -> opc_ua_op_result().
%%------------------------------------------------------------------------------
%calls for management
handle_call(stop, _, State) ->
{stop, normal, State};
handle_call(disconnect, _, #{port := Port} = State) ->
{reply, sync_read(Port, ?DISCONNECT), State};
handle_call({connect, ServerName}, _, #{port := Port} = State) ->
ConnectCmd = <<(?CONNECT)/binary, ServerName/binary>>,
{reply, sync_read(Port, ConnectCmd), State};
handle_call({get_value, Namespace, NodeId}, _, #{port := Port} = State) ->
read_node(Connection, Namespace, NodeId) when is_integer(Namespace), is_integer(NodeId) ->
NamespaceBin = erlang:integer_to_binary(Namespace),
NodeIdBin = erlang:integer_to_binary(NodeId),
GetValue = <<(?READ_NODE)/binary, NamespaceBin/binary, $|, NodeIdBin/binary>>,
{reply, sync_read(Port, GetValue), State};
handle_call(Msg, From, State) ->
io:format(?MODULE_STRING ++ ": What a call heck ~p in state: ~p from ~p", [Msg, From]),
{reply, ok, State}.
handle_cast(Msg, State) ->
io:format(?MODULE_STRING ++ ": What a cast heck ~p in state: ~p", [Msg]),
{noreply, State}.
sync_read(Connection, GetValue).
handle_info(Msg, State) ->
io:format(?MODULE_STRING ++ ": What a info heck ~p in state: ~p", [Msg]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_, State, _) ->
{ok, State}.
%%==============================================================================
%% Helpers
%%==============================================================================
sync_read(Port, Data) when is_binary(Data) ->
Port ! {self(), {command, Data}},
......@@ -124,6 +127,6 @@ convert_to_float(Value) when is_binary(Value) ->
Int ->
erlang:float(Int)
catch _:_:_ ->
bad_value
{bad_value, Value}
end
end.
\ No newline at end of file
end.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment