valiot / modbux

Elixir Modbus library for network and serial communications.
https://hexdocs.pm/modbux
MIT License
40 stars 15 forks source link

Modbux.Tcp.Client terminates when TCP response contains 2 payloads #14

Open pojiro opened 3 months ago

pojiro commented 3 months ago

Hi, thanks for your modbus library. I found an issue when I use this library. So I report.

What happens

Modbux.Tcp.Client terminates when TCP response contains 2 payloads. Following is the log,

07:10:12.450 [error] (Elixir.Modbux.Tcp) size = 5, payload_size = 17, msg = <<0x7, 0x8E, 0x0, 0x0, 0x0, 0x5, 0x0, 0x2, 0x2, 0x1, 0x80, 0x7, 0x8F, 0x0, 0x0, 0x0, 0x6, 0x0, 0x5, 0x0, 0x10, 0xFF, 0x0>>

07:10:12.450 [error] GenServer #PID<0.17883.0> terminating
** (FunctionClauseError) no function clause matching in Modbux.Response.parse/2
    (modbux 0.3.13) lib/helpers/response.ex:64: Modbux.Response.parse({:ri, 0, 0, 16}, nil)
    (modbux 0.3.13) lib/tcp/client.ex:330: Modbux.Tcp.Client.handle_info/2
    (stdlib 6.0) gen_server.erl:2173: :gen_server.try_handle_info/3
    (stdlib 6.0) gen_server.erl:2261: :gen_server.handle_msg/6
    (stdlib 6.0) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message: {:tcp, #Port<0.10199>, <<7, 142, 0, 0, 0, 5, 0, 2, 2, 1, 128, 7, 143, 0, 0, 0, 6, 0, 5, 0, 16, 255, 0>>}
** (EXIT from #PID<0.17850.0>) shell process exited with reason: an exception was raised:
    ** (FunctionClauseError) no function clause matching in Modbux.Response.parse/2
        (modbux 0.3.13) lib/helpers/response.ex:64: Modbux.Response.parse({:ri, 0, 0, 16}, nil)
        (modbux 0.3.13) lib/tcp/client.ex:330: Modbux.Tcp.Client.handle_info/2
        (stdlib 6.0) gen_server.erl:2173: :gen_server.try_handle_info/3
        (stdlib 6.0) gen_server.erl:2261: :gen_server.handle_msg/6
        (stdlib 6.0) proc_lib.erl:329: :proc_lib.init_p_do_apply/3

There are two payloads in the response.

  1. <<0x7, 0x8E, 0x0, 0x0, 0x0, 0x5, 0x0, 0x2, 0x2, 0x1, 0x80>>
  2. <<0x7, 0x8F, 0x0, 0x0, 0x0, 0x6, 0x0, 0x5, 0x0, 0x10, 0xFF, 0x0>>

But following code try to handle this response as one payload, so it fails.

https://github.com/valiot/modbux/blob/fead05e0b77e054bdb839a4777f9903999d439a1/lib/tcp/client.ex#L315


If you need any info, let me know. Thank you!

alde103 commented 3 months ago

Hi,

Can you confirm (using wireshark or similar) that you are indeed receiving both payloads in the same tcp message?

I think that the problem is the :packet option of :gen_tcp (currently :raw).

pojiro commented 3 months ago

Thanks for your quick reply. Currently the machine is located remote. But I will get it. When I get it, I will capture the log with wireshark and confirm it, then report. Thanks!

pojiro commented 3 months ago

I attached the packets log, the_packets.pcapng.zip, that I captured by wireshark.

The issue's log is following, <<0x4, 0x7B>>, <<0x4,0x7C>> corresponds to trans id 1147, 1148.

07:44:03.214 [error] (Elixir.Modbux.Tcp) size = 6, payload_size = 17, msg = <<0x4, 0x7B, 0x0, 0x0, 0x0, 0x6, 0x0, 0x5, 0x0, 0x10, 0x0, 0x0, 0x4, 0x7C, 0x0, 0x0, 0x0, 0x5, 0x0, 0x2, 0x2, 0x0, 0x0>>

07:44:03.215 [error] GenServer #PID<0.1849.0> terminating
** (FunctionClauseError) no function clause matching in Modbux.Response.parse/2
    (modbux 0.3.13) lib/helpers/response.ex:64: Modbux.Response.parse({:fc, 0, 16, 0}, nil)
    (modbux 0.3.13) lib/tcp/client.ex:330: Modbux.Tcp.Client.handle_info/2
    (stdlib 6.0) gen_server.erl:2173: :gen_server.try_handle_info/3
    (stdlib 6.0) gen_server.erl:2261: :gen_server.handle_msg/6
    (stdlib 6.0) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message: {:tcp, #Port<0.69>, <<4, 123, 0, 0, 0, 6, 0, 5, 0, 16, 0, 0, 4, 124, 0, 0, 0, 5, 0, 2, 2, 0, 0>>}

The following capture show the issue packets,

image

This response was from :gen_tcp with active: true, here

https://github.com/valiot/modbux/blob/fead05e0b77e054bdb839a4777f9903999d439a1/lib/tcp/client.ex#L315

then

https://github.com/valiot/modbux/blob/fead05e0b77e054bdb839a4777f9903999d439a1/lib/tcp/client.ex#L330

finally reached here

https://github.com/valiot/modbux/blob/fead05e0b77e054bdb839a4777f9903999d439a1/lib/tcp/tcp.ex#L34

So I guess that :gen_tcp reads several packets together at once.

alde103 commented 3 months ago

Hi, I create this branch, that expose the packet argument from :gen_tcp, this parameter (packet_format in modbux) accepts 0 | 1 | 2 | 4 | :raw (default: :raw, :raw == 0), however, it seems that the client and the server must match this parameter (https://stackoverflow.com/questions/43957164/erlang-client-server-example-using-gen-tcp-is-not-receiving-anything), so please give it a try, hope this helps.

Example:

# Starts the Client that will connect to a Server with tcp port: 2000
{:ok, cpid} = Modbux.Tcp.Client.start_link(ip: {127,0,0,1}, tcp_port: 2000, timeout: 2000, packet_format: 1)
# Connect to the Server
Modbux.Tcp.Client.connect(cpid)
# Read 1 coil at 20818 from the device 80
Modbux.Tcp.Client.request(cpid, {:rc, 0x50, 20818, 1})
# Parse the Server response
resp = Modbux.Tcp.Client.confirmation(cpid) 
# resp == {:ok, [0]}
pojiro commented 3 months ago

Hi, I read the stackoverflow link and the section of the book, Programming Erlang). I also read https://stackoverflow.com/questions/36854060/using-packet-n-in-gen-tcp-and-how-to-receive-data-in-a-c-program

In my understanding, I cannot use the option for this case. Because the target machine, modbus server, doesn't support the header length which :gen_tcps {:packet, N} option add. I think we use this option when we write :gen_tcp in client and server both with same N.

Thanks.

pojiro commented 3 months ago

@alde103 This is not an immediate issue, so it's enough if you took this as a report that I have found such a case.