Current section
Files
Jump to
Current section
Files
test/test_helper.exs
ExUnit.start()
defmodule Modbus.Tcp.Slave do
import Supervisor.Spec
alias Modbus.Model
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
def id(pid) do
Agent.get(pid, fn %{ip: ip, port: port, model: model} -> {:ok, %{ip: ip, port: port, model: model}} end)
end
def state(pid) do
Agent.get(pid, fn state -> state end)
end
defp init(params) do
model = Keyword.fetch!(params, :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)
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, model) end)
%{ip: ip, port: port, model: model, sup: sup, accept: accept, listener: listener}
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, model) do
{:ok, spawn_link(fn ->
receive do
:go ->
loop(socket, model)
end
end)}
end
defp loop(socket, model) do
{:ok, data} = :gen_tcp.recv(socket, 0)
{cmd, transid} = Tcp.parse_req(data)
{model2, values} = Model.apply(model, cmd)
resp = Tcp.pack_res(cmd, values, transid)
:ok = :gen_tcp.send(socket, resp)
loop(socket, model2)
end
end
defmodule TestHelper do
use ExUnit.Case
alias Modbus.Request
alias Modbus.Response
alias Modbus.Model
alias Modbus.Rtu
alias Modbus.Tcp
alias Modbus.Tcp.Master
alias Modbus.Tcp.Slave
def pp1(cmd, req, res, val, state) do
assert req == Request.pack(cmd)
assert cmd == Request.parse(req)
assert {state, val} == Model.apply(state, cmd)
assert res == Response.pack(cmd, val)
assert val == Response.parse(cmd, res)
#length predition
assert byte_size(res) == Response.length(cmd)
assert byte_size(req) == Request.length(cmd)
#rtu
rtu_req = Rtu.pack_req(cmd)
assert cmd == Rtu.parse_req(rtu_req)
rtu_res = Rtu.pack_res(cmd, val)
assert val == Rtu.parse_res(cmd, rtu_res)
assert byte_size(rtu_res) == Rtu.res_len(cmd)
assert byte_size(rtu_req) == Rtu.req_len(cmd)
#tcp
tcp_req = Tcp.pack_req(cmd, 1)
assert {cmd, 1} == Tcp.parse_req(tcp_req)
tcp_res = Tcp.pack_res(cmd, val, 1)
assert val == Tcp.parse_res(cmd, tcp_res, 1)
assert byte_size(tcp_res) == Tcp.res_len(cmd)
assert byte_size(tcp_req) == Tcp.req_len(cmd)
#master
{:ok, slave_pid} = Slave.start_link([model: state])
{:ok, %{port: port}} = Slave.id(slave_pid)
{:ok, master_pid} = Master.start_link([port: port, ip: {127,0,0,1}])
assert {:ok, val} == Master.exec(master_pid, cmd)
end
def pp2(cmd, req, res, state0, state1) do
assert req == Request.pack(cmd)
assert cmd == Request.parse(req)
assert {state1, nil} == Model.apply(state0, cmd)
assert res == Response.pack(cmd, nil)
assert nil == Response.parse(cmd, res)
#length predition
assert byte_size(res) == Response.length(cmd)
#rtu
rtu_req = Rtu.pack_req(cmd)
assert cmd == Rtu.parse_req(rtu_req)
rtu_res = Rtu.pack_res(cmd, nil)
assert nil == Rtu.parse_res(cmd, rtu_res)
assert byte_size(rtu_res) == Rtu.res_len(cmd)
#tcp
tcp_req = Tcp.pack_req(cmd, 1)
assert {cmd, 1} == Tcp.parse_req(tcp_req)
tcp_res = Tcp.pack_res(cmd, nil, 1)
assert nil == Tcp.parse_res(cmd, tcp_res, 1)
assert byte_size(tcp_res) == Tcp.res_len(cmd)
#master
{:ok, slave_pid} = Slave.start_link([model: state0])
{:ok, %{port: port}} = Slave.id(slave_pid)
{:ok, master_pid} = Master.start_link([port: port, ip: {127,0,0,1}])
assert :ok == Master.exec(master_pid, cmd)
end
end