Current section

Files

Jump to
modbus lib slave.ex
Raw

lib/slave.ex

defmodule Modbus.Tcp.Slave do
use GenServer
@moduledoc false
alias Modbus.Model.Shared
alias Modbus.Tcp
def start_link(opts) do
ip = Keyword.get(opts, :ip, {127, 0, 0, 1})
port = Keyword.get(opts, :port, 0)
model = Keyword.fetch!(opts, :model)
GenServer.start_link(__MODULE__, {ip, port, model})
end
def init({ip, port, model}) do
{:ok, shared} = Shared.start_link(model)
opts = [:binary, ip: ip, packet: :raw, active: false]
case :gen_tcp.listen(port, opts) do
{:ok, listener} ->
spawn_link(fn -> accept(listener, shared) end)
{:ok, {ip, port}} = :inet.sockname(listener)
{:ok,
%{
ip: ip,
port: port,
shared: shared,
listener: listener
}}
{:error, reason} ->
{:stop, reason}
end
end
def terminate(reason, %{shared: shared}) do
Agent.stop(shared, reason)
end
def stop(pid) do
# listener automatic close should
# close the accepting process which
# should close all client sockets
GenServer.stop(pid)
end
def port(pid) do
GenServer.call(pid, :port)
end
def handle_call(:port, _from, state) do
{:reply, state.port, state}
end
defp accept(listener, shared) do
case :gen_tcp.accept(listener) do
{:ok, socket} ->
spawn(fn -> client(socket, shared) end)
accept(listener, shared)
{:error, reason} ->
Process.exit(self(), reason)
end
end
def client(socket, shared) do
case :gen_tcp.recv(socket, 0) do
{:ok, data} ->
{cmd, transid} = Tcp.parse_req(data)
result = Shared.apply(shared, cmd)
case result do
:ok ->
resp = Tcp.pack_res(cmd, nil, transid)
:gen_tcp.send(socket, resp)
{:ok, values} ->
resp = Tcp.pack_res(cmd, values, transid)
:gen_tcp.send(socket, resp)
_ ->
:ignore
end
client(socket, shared)
{:error, reason} ->
Process.exit(self(), reason)
end
end
end