diff --git a/lib/modbuzz/pdu.ex b/lib/modbuzz/pdu.ex index 4c2f4f0..ca26a11 100644 --- a/lib/modbuzz/pdu.ex +++ b/lib/modbuzz/pdu.ex @@ -9,16 +9,7 @@ defmodule Modbuzz.PDU do def encode_request(struct), do: {:ok, Modbuzz.PDU.Protocol.encode(struct)} def encode_response(struct), do: {:ok, Modbuzz.PDU.Protocol.encode(struct)} - for {modbus_function_code, modbus_function} <- [ - {0x01, ReadCoils}, - {0x02, ReadDiscreteInputs}, - {0x03, ReadHoldingRegisters}, - {0x04, ReadInputRegisters}, - {0x05, WriteSingleCoil}, - {0x06, WriteSingleRegister}, - {0x0F, WriteMultipleCoils}, - {0x10, WriteMultipleRegisters} - ] do + for {modbus_function_code, modbus_function} <- Modbuzz.MixProject.pdu_seed() do req_module = Module.concat([Modbuzz.PDU, modbus_function, Req]) res_module = Module.concat([Modbuzz.PDU, modbus_function, Res]) err_module = Module.concat([Modbuzz.PDU, modbus_function, Err]) diff --git a/lib/modbuzz/pdu/diagnostics.ex b/lib/modbuzz/pdu/diagnostics.ex new file mode 100644 index 0000000..cb65c74 --- /dev/null +++ b/lib/modbuzz/pdu/diagnostics.ex @@ -0,0 +1,110 @@ +defmodule Modbuzz.PDU.Diagnostics do + @moduledoc false + + defmodule Req do + @moduledoc Modbuzz.PDU.Helper.module_one_line_doc(__MODULE__) + + @type t :: %__MODULE__{ + sub_function: 0x0000..0xFFFF, + data: 0x0000..0xFFFF + } + + defstruct [:sub_function, :data] + + defimpl Modbuzz.PDU.Protocol do + @function_code 0x08 + + @doc """ + iex> req = %Modbuzz.PDU.Diagnostics.Req{ + ...> sub_function: 0x0000, + ...> data: 0xA537 + ...> } + iex> Modbuzz.PDU.Protocol.encode(req) + <<#{@function_code}, 0x0000::16, 0xA537::16>> + """ + def encode(struct) do + <<@function_code, struct.sub_function::16, struct.data::16>> + end + + @doc """ + iex> req = %Modbuzz.PDU.Diagnostics.Req{} + iex> Modbuzz.PDU.Protocol.decode(req, <<#{@function_code}, 0x0000::16, 0xA537::16>>) + %Modbuzz.PDU.Diagnostics.Req{sub_function: 0x0000, data: 0xA537} + """ + def decode(struct, <<@function_code, sub_function::16, data::16>>) do + %{struct | sub_function: sub_function, data: data} + end + end + end + + defmodule Res do + @moduledoc Modbuzz.PDU.Helper.module_one_line_doc(__MODULE__) + + @type t :: %__MODULE__{ + sub_function: 0x0000..0xFFFF, + data: 0x0000..0xFFFF + } + + defstruct [:sub_function, :data] + + defimpl Modbuzz.PDU.Protocol do + @function_code 0x08 + + @doc """ + iex> res = %Modbuzz.PDU.Diagnostics.Res{ + ...> sub_function: 0x0000, + ...> data: 0xA537 + ...> } + iex> Modbuzz.PDU.Protocol.encode(res) + <<#{@function_code}, 0x0000::16, 0xA537::16>> + """ + def encode(struct) do + <<@function_code, struct.sub_function::16, struct.data::16>> + end + + @doc """ + iex> res = %Modbuzz.PDU.Diagnostics.Res{} + iex> Modbuzz.PDU.Protocol.decode(res, <<#{@function_code}, 0x0000::16, 0xA537::16>>) + %Modbuzz.PDU.Diagnostics.Res{ + sub_function: 0x0000, + data: 0xA537 + } + """ + def decode(struct, <<@function_code, sub_function::16, data::16>>) do + %{struct | sub_function: sub_function, data: data} + end + end + end + + defmodule Err do + @moduledoc Modbuzz.PDU.Helper.module_one_line_doc(__MODULE__) + + @type t :: %__MODULE__{ + exception_code: 0x01 | 0x03 | 0x04 + } + + defstruct [:exception_code] + + defimpl Modbuzz.PDU.Protocol do + @error_code 0x08 + 0x80 + + @doc """ + iex> err = %Modbuzz.PDU.Diagnostics.Err{exception_code: 0x01} + iex> Modbuzz.PDU.Protocol.encode(err) + <<#{@error_code}, 0x01>> + """ + def encode(struct) do + <<@error_code, struct.exception_code>> + end + + @doc """ + iex> err = %Modbuzz.PDU.Diagnostics.Err{} + iex> Modbuzz.PDU.Protocol.decode(err, <<#{@error_code}, 0x01>>) + %Modbuzz.PDU.Diagnostics.Err{exception_code: 0x01} + """ + def decode(struct, <<@error_code, exception_code>>) do + %{struct | exception_code: exception_code} + end + end + end +end diff --git a/lib/modbuzz/tcp/client.ex b/lib/modbuzz/tcp/client.ex index 880c31b..fdd0fbb 100644 --- a/lib/modbuzz/tcp/client.ex +++ b/lib/modbuzz/tcp/client.ex @@ -3,7 +3,7 @@ defmodule Modbuzz.TCP.Client do use GenServer - require Logger + alias Modbuzz.TCP.Log defmodule Transaction do @moduledoc false @@ -125,11 +125,11 @@ defmodule Modbuzz.TCP.Client do def handle_continue(:connect, %{socket: nil} = state) do case gen_tcp_connect(state) do {:ok, socket} -> - Logger.debug("#{__MODULE__}: :connect succeeded.") + Log.debug(":connect succeeded", state) {:noreply, %{state | socket: socket}} {:error, reason} -> - Logger.error("#{__MODULE__}: :connect failed, the reason is #{inspect(reason)}.") + Log.error(":connect failed", reason, state) {:noreply, state, {:continue, :connect}} end end @@ -154,17 +154,17 @@ defmodule Modbuzz.TCP.Client do {:noreply, %{state | socket: socket}} else {:connect, {:error, reason} = error} -> - Logger.error("#{__MODULE__}: :recall connect failed, the reason is #{inspect(reason)}.") + Log.error(":recall connect failed", reason, state) GenServer.reply(from, error) {:noreply, state, {:continue, :connect}} {:send, {:error, reason} = error} -> - Logger.error("#{__MODULE__}: :recall send failed, the reason is #{inspect(reason)}.") + Log.error(":recall send failed", reason, state) GenServer.reply(from, error) {:noreply, state, {:continue, :connect}} {:recv, {:error, reason} = error} -> - Logger.error("#{__MODULE__}: :recall recv failed, the reason is #{inspect(reason)}.") + Log.error(":recall recv failed", reason, state) GenServer.reply(from, error) {:noreply, state, {:continue, :connect}} end @@ -195,11 +195,11 @@ defmodule Modbuzz.TCP.Client do {:noreply, %{state | socket: socket, transactions: transactions}} else {:connect, {:error, reason}} -> - Logger.error("#{__MODULE__}: :recast connect failed, the reason is #{inspect(reason)}.") + Log.error(":recast connect failed", reason, state) {:noreply, state, {:continue, :connect}} {:send, {:error, reason}} -> - Logger.error("#{__MODULE__}: :recast send failed, the reason is #{inspect(reason)}.") + Log.error(":recast send failed", reason, state) {:noreply, state, {:continue, :connect}} end end @@ -223,18 +223,14 @@ defmodule Modbuzz.TCP.Client do {:reply, res_tuple, state} else {:send, {:error, reason}} -> - Logger.warning( - "#{__MODULE__}: :call send failed, the reason is #{inspect(reason)}, :recall." - ) + Log.warning(":call send failed, :recall", reason, state) :ok = transport.close(socket) state = %{state | socket: nil} {:noreply, state, {:continue, {:recall, unit_id, request, timeout, from}}} {:recv, {:error, reason}} -> - Logger.warning( - "#{__MODULE__}: :call recv failed, the reason is #{inspect(reason)}, :recall." - ) + Log.warning(":call recv failed, :recall", reason, state) :ok = transport.close(socket) state = %{state | socket: nil} @@ -273,9 +269,7 @@ defmodule Modbuzz.TCP.Client do {:noreply, %{state | transactions: transactions}} {:error, reason} -> - Logger.warning( - "#{__MODULE__}: :cast send failed, the reason is #{inspect(reason)}, :recast." - ) + Log.warning(":cast send failed, :recast", reason, state) :ok = transport.close(socket) state = %{state | socket: nil} @@ -316,7 +310,7 @@ defmodule Modbuzz.TCP.Client do def handle_info({:tcp_closed, socket}, %{socket: socket, active: true} = state) do %{transport: transport, transactions: transactions} = state - Logger.warning("#{__MODULE__}: transport closed.") + Log.warning("transport closed.", nil, state) :ok = transport.close(socket) state = %{state | socket: nil} @@ -329,7 +323,7 @@ defmodule Modbuzz.TCP.Client do {:noreply, state, {:continue, :connect}} [{_transaction_id, transaction}] -> - Logger.debug("#{__MODULE__}: sent successfully but RST ACK received, :recast.") + Log.debug("sent successfully but RST ACK received, :recast", state) {:noreply, state, {:continue, {:recast, transaction.unit_id, transaction.request, transaction.from_pid}}} @@ -338,7 +332,7 @@ defmodule Modbuzz.TCP.Client do def handle_info({:tcp_error, socket, reason}, %{socket: socket, active: true} = state) do %{transport: transport} = state - Logger.error("#{__MODULE__}: transport error, the reason is #{inspect(reason)}.") + Log.error("transport error", reason, state) :ok = transport.close(socket) {:noreply, %{state | socket: nil}, {:continue, :connect}} end diff --git a/lib/modbuzz/tcp/log.ex b/lib/modbuzz/tcp/log.ex new file mode 100644 index 0000000..50fff10 --- /dev/null +++ b/lib/modbuzz/tcp/log.ex @@ -0,0 +1,33 @@ +defmodule Modbuzz.TCP.Log do + @moduledoc false + + require Logger + + def debug(what_happend) do + Logger.debug(what_happend) + end + + def debug(what_happend, state) do + Logger.debug(what_happend <> where(state)) + end + + def error(what_happend) do + Logger.error(what_happend) + end + + def error(what_happend, reason, state) do + Logger.error(what_happend <> why(reason) <> where(state)) + end + + def warning(what_happend, reason, state) do + Logger.warning(what_happend <> why(reason) <> where(state)) + end + + defp why(nil), do: "" + defp why(reason), do: ", the reason is #{inspect(reason)}" + + defp where(state) do + %{address: address, port: port} = state + " (address: #{inspect(address)}, port: #{inspect(port)})" + end +end diff --git a/lib/modbuzz/tcp/server.ex b/lib/modbuzz/tcp/server.ex index 66bae35..2514909 100644 --- a/lib/modbuzz/tcp/server.ex +++ b/lib/modbuzz/tcp/server.ex @@ -3,7 +3,7 @@ defmodule Modbuzz.TCP.Server do use GenServer - require Logger + alias Modbuzz.TCP.Log @doc false @spec start_link(keyword()) :: GenServer.on_start() @@ -40,7 +40,7 @@ defmodule Modbuzz.TCP.Server do {:noreply, %{state | listen_socket: socket}, {:continue, :accept}} {:error, reason} -> - Logger.error("#{__MODULE__}: :listen failed, the reason is #{inspect(reason)}.") + Log.error(":listen failed", reason, state) {:noreply, state, {:continue, :listen}} end end @@ -65,7 +65,7 @@ defmodule Modbuzz.TCP.Server do ) {:error, reason} -> - Logger.error("#{__MODULE__}: #{inspect(reason)}") + Log.error(":accept failed", reason, state) end {:noreply, state, {:continue, :accept}} diff --git a/lib/modbuzz/tcp/server/socket_handler.ex b/lib/modbuzz/tcp/server/socket_handler.ex index a884b46..bc6718d 100644 --- a/lib/modbuzz/tcp/server/socket_handler.ex +++ b/lib/modbuzz/tcp/server/socket_handler.ex @@ -3,7 +3,7 @@ defmodule Modbuzz.TCP.Server.SocketHandler do use GenServer, restart: :temporary - require Logger + alias Modbuzz.TCP.Log def start_link(args) do GenServer.start_link(__MODULE__, args) @@ -11,6 +11,8 @@ defmodule Modbuzz.TCP.Server.SocketHandler do def init(args) do transport = Keyword.fetch!(args, :transport) + address = Keyword.fetch!(args, :address) + port = Keyword.fetch!(args, :port) socket = Keyword.fetch!(args, :socket) data_source = Keyword.fetch!(args, :data_source) timeout = Keyword.get(args, :timeout, 5000) @@ -18,6 +20,8 @@ defmodule Modbuzz.TCP.Server.SocketHandler do {:ok, %{ transport: transport, + address: address, + port: port, socket: socket, data_source: data_source, timeout: timeout @@ -52,7 +56,7 @@ defmodule Modbuzz.TCP.Server.SocketHandler do {:stop, reason, state} {:error, reason} -> - Logger.error("#{__MODULE__}: #{inspect(reason)}") + Log.error(":recv failed", reason, state) :ok = transport.close(socket) {:stop, reason, state} end @@ -66,7 +70,7 @@ defmodule Modbuzz.TCP.Server.SocketHandler do end catch :exit, {:noproc, mfa} -> - Logger.error("#{__MODULE__}: `#{data_source}` not found. (mfa is #{inspect(mfa)})") + Log.error("`#{data_source}` not found. (mfa is #{inspect(mfa)})") Modbuzz.PDU.to_error(request) end end diff --git a/lib/modbuzz/tcp/server/socket_handler_supervisor.ex b/lib/modbuzz/tcp/server/socket_handler_supervisor.ex index fc51758..8c9780a 100644 --- a/lib/modbuzz/tcp/server/socket_handler_supervisor.ex +++ b/lib/modbuzz/tcp/server/socket_handler_supervisor.ex @@ -13,9 +13,9 @@ defmodule Modbuzz.TCP.Server.SocketHandlerSupervisor do {Modbuzz.TCP.Server.SocketHandler, [ transport: transport, - socket: socket, address: address, port: port, + socket: socket, data_source: data_source ]} ) diff --git a/mix.exs b/mix.exs index c170baa..fa46e5d 100644 --- a/mix.exs +++ b/mix.exs @@ -29,6 +29,19 @@ defmodule Modbuzz.MixProject do ] end + def pdu_seed() do + [ + {0x01, ReadCoils}, + {0x02, ReadDiscreteInputs}, + {0x03, ReadHoldingRegisters}, + {0x04, ReadInputRegisters}, + {0x05, WriteSingleCoil}, + {0x06, WriteSingleRegister}, + {0x0F, WriteMultipleCoils}, + {0x10, WriteMultipleRegisters} + ] + end + # Run "mix help deps" to learn about dependencies. defp deps do [ @@ -75,9 +88,10 @@ defmodule Modbuzz.MixProject do docs: [ main: "readme", extras: ["README.md"], - nest_modules_by_prefix: [ - Modbuzz.PDU - ] + nest_modules_by_prefix: + for {_modbus_function_code, modbus_function} <- pdu_seed() do + Module.concat([Modbuzz.PDU, modbus_function]) + end ] ] end diff --git a/test/modbuzz/pdu_test.exs b/test/modbuzz/pdu_test.exs index 4ffee4c..a775548 100644 --- a/test/modbuzz/pdu_test.exs +++ b/test/modbuzz/pdu_test.exs @@ -2,16 +2,7 @@ defmodule Modbuzz.PDUTest do use ExUnit.Case for type <- [Req, Res, Err], - modbus_function <- [ - ReadCoils, - ReadDiscreteInputs, - ReadHoldingRegisters, - ReadInputRegisters, - WriteSingleCoil, - WriteSingleRegister, - WriteMultipleCoils, - WriteMultipleRegisters - ] do + {_modbus_function_code, modbus_function} <- Modbuzz.MixProject.pdu_seed() do doctest Module.concat([Modbuzz.PDU.Protocol.Modbuzz.PDU, modbus_function, type]) end end