Current section
Files
Jump to
Current section
Files
lib/slave.ex
defmodule Modbus.Tcp.Slave do
@moduledoc false
import Supervisor.Spec
alias Modbus.Model.Shared
alias Modbus.Tcp
def start_link(params, opts \\ []) do
Agent.start_link(fn -> init(params) end, opts)
end
def stop(pid) do
Agent.stop(pid)
end
#comply with formward id
def id(pid) do
Agent.get(pid, fn %{ip: ip, port: port, name: name} -> {:ok, %{ip: ip, port: port, name: name}} end)
end
def state(pid) do
Agent.get(pid, fn state -> state end)
end
defp init(params) do
model = Keyword.fetch!(params, :model)
{:ok, shared} = Shared.start_link([model: model])
remote = Keyword.get(params, :remote, false)
ip = if remote, do: {0,0,0,0}, else: {127,0,0,1}
{:ok, listener} = :gen_tcp.listen(0, [:binary, ip: ip,
packet: :raw, active: false])
{:ok, {ip, port}} = :inet.sockname(listener)
name = Keyword.get(params, :name, name(ip, port))
spec = worker(__MODULE__, [], restart: :temporary, function: :start_child)
{:ok, sup} = Supervisor.start_link([spec], strategy: :simple_one_for_one)
accept = spawn_link(fn -> accept(listener, sup, shared) end)
%{ip: ip, port: port, name: name, shared: shared, sup: sup, accept: accept, listener: listener}
end
defp name(ip, port) do
ips = :inet_parse.ntoa(ip)
mod = Atom.to_string(__MODULE__)
"#{mod}:#{ips}:#{port}"
end
defp accept(listener, sup, model) do
{:ok, socket} = :gen_tcp.accept(listener)
{:ok, pid} = Supervisor.start_child(sup, [socket, model])
:ok = :gen_tcp.controlling_process(socket, pid)
send pid, :go
accept(listener, sup, model)
end
def start_child(socket, shared) do
{:ok, spawn_link(fn ->
receive do
:go ->
loop(socket, shared)
end
end)}
end
defp loop(socket, shared) do
{:ok, data} = :gen_tcp.recv(socket, 0)
{cmd, transid} = Tcp.parse_req(data)
{:ok, values} = Shared.apply(shared, cmd)
resp = Tcp.pack_res(cmd, values, transid)
:ok = :gen_tcp.send(socket, resp)
loop(socket, shared)
end
end