Recent

Author Topic: SnapModbus  (Read 5527 times)

ojz0r

  • Jr. Member
  • **
  • Posts: 71
Re: SnapModbus
« Reply #15 on: June 06, 2025, 04:01:47 pm »
Can you show what has been sent when you recieved the errors as well?
I like big endians and i can not lie.

avra

  • Hero Member
  • *****
  • Posts: 2547
    • Additional info
Re: SnapModbus
« Reply #16 on: June 11, 2025, 02:09:27 pm »
See how many telegrams are rejected by SnapMODBUS?
Your discrete/coil telegrams are accepted, your register telegrams are not accepted (functions 03 and 04).
Check if address needs to be adjusted by +-1 offset.

This is what DUMP looks like when WinBroker requests first 8 input registers from a WinDevice ("Increase Fill All" used to populate register values) and gets a good response:
Quote
[Request to Device 1]
00 0A 00 00 00 06 01 04 00 00 00 08               ............
[Confirmation from Device 1]
00 0A 00 00 00 13 01 04 10 00 00 00 01 00 02 00   ................
03 00 04 00 05 00 06 00 07                        .........

Compare that to what your PLC MODBUS master requests from your Delphi MODBUS slave and you will figure out what is wrong.
If you can not dump your MODBUS communication from your Delphi app, then use Wireshark or some other communication sniffer.

You are misinterpreting the log:

This shows in log when WinBroker asks for 8 input registers starting from address 1:
Code: Pascal  [Select][+][-]
  1. 2025-06-11 15:13:21 - [127.0.0.1] 0x04 - Read Input Registers - Addr : 0x0000, Amount : 8 --> OK

And this shows in log when WinBroker asks for 8 input registers starting from address 100:
Code: Pascal  [Select][+][-]
  1. 2025-06-11 15:20:02 - [127.0.0.1] 0x04 - Read Input Registers - Addr : 0x0063, Amount : 8 --> OK

Note that Addr is 0x0000 when starting address is 1, and 0x0063 when it is 100. Besides that, your requested Amount for registers is always 0, which is not good. Investigate that. Also check if endianes is the same on both sides.
« Last Edit: June 11, 2025, 05:18:42 pm by avra »
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

bobby100

  • Sr. Member
  • ****
  • Posts: 291
    • Malzilla
Re: SnapModbus
« Reply #17 on: June 11, 2025, 07:03:11 pm »
Hi avra,

I've lost you at some point in your comment - the logs in my screenshot are not from WinBroker, but from WinDevice.
I did check the offset. Priva is using zero-based addressing (first address is zero). In my tests, I didn't use the address zero at all. I've gone with the address 1 as the first address, which would be the address 2 for some other products.
As for the endianess - I would be happy to get anything, but I didn't got to this point at all.

I am not using frames in Priva PLC (read/write more than one register in one request) - I am accessing single registers/coils.
As for the Priva PLC - didn't have any problems with it, and I am using the S10 controller from Priva for the last 7 years. I am pretty sure the Modbus implementation from Priva is OK.

==========boring story follows, read if you don't have anything better to do==========
To get back to the root of my problem - since a couple of months, I am also programming Loytec PLCs. The Modbus driver in Loytec PLC is using LREAL (long real) for interfacing to the programming. Modbus registers are 16-bit but LREAL is 64-bit - so, I wanted to inspect what does Loytec really sends over Modbus RTU.
As I don't have a Loytec PLC at home, I wanted to prepare a Modbus RTU slave app and test it with Priva PLC which I do have at home.
As the SnapMODBUS WinDevice didn't work with Priva Modbus RTU, I've tested it over TCP, and got the same results - some of the functions are rejected over and over again. Checked the Priva PLC with my app based on Delphi-Modbus in TCP mode - everything is working well. It is not an app written in Delphi, but Lazarus app using the package delphi-modbus.
Long story short - Loytec's Modbus RTU was OK in the end. I got it to communicate with Daikin cooling devices. My concern about LREAL in Loytec's Modbus driver got another turn - the conversion from INT/WORD/DWORD to LREAL (and vice versa) from the Loytec base-libs isn't working at all, so I wrote my own low-level functions for this. Daikin has some crazy modbus implementation where you need to do bit-manipulation, and there is no bit-manipulation on LREAL
============================================================

Can it be that the SnapMODBUS does not handle access to single registers well?
The WinDevice demo is built to accept every legit request and send some answer back.
It complains about illegal address 0x00, which isn't something requested from my Priva PLC.

I'll do some more tests when I get some free time for it.
In the meantime, I've found some abandoned code on sf.net - pascal-modbus from Yuri Lychakov. It contains Modbus RTU slave (using Synapse). Probably, I will try this first, and then get back to SnapMODBUS if the code from Yuri doesn't do the job.

ojz0r

  • Jr. Member
  • **
  • Posts: 71
Re: SnapModbus
« Reply #18 on: June 11, 2025, 09:45:58 pm »
I wrote this small CLI app to try modbus tcp. Its a client reliant on ssockets.
Tested on Linux against a codesys modbus tcp server.
Code: Pascal  [Select][+][-]
  1. Program PasModCli;
  2. {$mode objfpc}{$H+}
  3. uses
  4.   sysutils,
  5.   ssockets;
  6.  
  7. type TMBHeader = packed record
  8.   MsgID: word;
  9.   ProtocolIdent: word;
  10.   Len: word;
  11.   UnitIdent: byte;
  12. end;
  13.  
  14. type TMBRequest = packed record
  15.   FunCode: byte;
  16.   StartAdr: word;
  17.   Amount: word;
  18. end;
  19.  
  20. type TMBResponse = packed record
  21.   FunCode: byte;
  22.   StartAdr: word;
  23.   Data: array of byte;
  24. end;
  25.  
  26. const
  27.   Server = '192.168.1.30';
  28. //  Server = '127.0.0.1';
  29. //  Port = 502;
  30.   Port = 4100;
  31.   FC1  = 1;  // Read coils
  32.   FC2  = 2;  // Read discrete inputs
  33.   FC3  = 3;  // Read registers
  34.   FC4  = 4;  // Read input registers
  35.   FC5  = 5;  // Write single coil
  36.   FC6  = 6;  // Write single register
  37.   FC15 = 15; // Write multiple coils
  38.   FC16 = 16; // Write multiple registers
  39.   FC69 = 69; // Shutdown command
  40.  
  41. var
  42.   i: integer;
  43.   sent: string = '';
  44.   rec: String = '';
  45.   MBHeader: TMBHeader;
  46.   MBRequest: TMBRequest;
  47.   MBMsg: array of byte;
  48.   Buffer: array of byte;
  49.   Count: longint;
  50.   s: string = '0';
  51.   looper: integer = 1;
  52.   ValueAmount: integer;
  53.   tempdata: word;
  54.   j: integer = 1;
  55.   ReqData: array of byte;
  56.   BitCount: integer = 0;
  57.   byter: byte = 0;
  58.  
  59. begin
  60.   writeln('Welcome to  PasModCli (Pascal Modbus Client/Command line interface)');
  61.   with MBRequest do begin
  62.     writeln;
  63.     writeln('Enter Function Code (FC):');
  64.     writeln('FC1  = 1:  Read coils');
  65.     writeln('FC2  = 2:  Read discrete inputs');
  66.     writeln('FC3  = 3:  Read registers');
  67.     writeln('FC4  = 4:  Read input registers');
  68.     writeln('FC5  = 5:  Write single coil');
  69.     writeln('FC6  = 6:  Write single register');
  70.     writeln('FC15 = 15: Write multiple coils');
  71.     writeln('FC16 = 16: Write multiple registers');
  72.     writeln('FC69 = 69: Shutdown command');
  73.     readln(s);
  74.     FunCode := strtoint(s);
  75.     case FunCode of
  76.       1..6, 15..16, 69:;
  77.       else begin
  78.         writeln('Function Code not supported!');
  79.         writeln('Exiting');
  80.         FunCode := 0;
  81.       end;
  82.     end;
  83.     if (FunCode <> 0) and (FunCode <> 69) then begin
  84.       writeln;
  85.       writeln('Enter register number:');
  86.       readln(s);
  87.       StartAdr := swap(word(strtoint(s)));
  88.       writeln;
  89.       case FunCode of
  90.         1..4,69: writeln('Enter amount to read');
  91.         5..6: writeln('Enter value to write');
  92.         15..16: writeln('Enter amount to write');
  93.       end;
  94.       readln(s);
  95.       if (FunCode = 5) and (s <> '0') then begin
  96.         Amount := $00FF;
  97.       end else begin
  98.         Amount := swap(word(strtoint(s)));
  99.       end;
  100.       ValueAmount := strtoint(s);
  101.       if FunCode = 16 then begin
  102.         setlength(ReqData, ValueAmount*2 + 1);
  103.         ReqData[0] := ValueAmount*2;
  104.         repeat
  105.           writeln('Enter value number ' + inttostr(looper) + '/' + inttostr(ValueAmount));
  106.           readln(s);
  107.           tempdata := swap(word(strtoint(s)));
  108.           move(tempdata, ReqData[j], 2);
  109.           inc(j,2);
  110.           inc(looper);
  111.         until looper = ValueAmount+1;
  112.       end;
  113.       if FunCode = 15 then begin
  114.         if (ValueAmount mod 8) > 0 then begin
  115.           setlength(ReqData, round(ValueAmount/8) + 2);
  116.           ReqData[0] := round(ValueAmount/8) + 1;
  117.         end else begin
  118.           setlength(ReqData, round(ValueAmount/8) + 1);
  119.           ReqData[0] := round(ValueAmount/8);
  120.         end;
  121.         repeat
  122.           writeln('Enter value number ' + inttostr(looper) + '/' + inttostr(ValueAmount));
  123.           readln(s);
  124.           byter := strtoint(s);
  125.            ReqData[j] := ReqData[j] or (byter shl BitCount);
  126.           inc(looper);
  127.           inc(BitCount);
  128.           if (looper mod 9) = 0 then begin
  129.             inc(j);
  130.             BitCount := 0;
  131.           end;
  132.         until looper = ValueAmount+1;
  133.       end;
  134.     end;
  135.   end;
  136.   with MBHeader do begin
  137.     MsgID := swap(1);
  138.     ProtocolIdent := swap(0);  //0 for ModbusTCP
  139.     Len :=  swap(word(1 + sizeof(MBRequest) + length(ReqData)));
  140.     UnitIdent := 255;
  141.   end;
  142.   setlength(MBMsg, sizeof(MBHeader) + sizeof(MBRequest) + length(ReqData));
  143.   move(MBHeader, MBMsg[0], sizeof(MBHeader));
  144.   move(MBRequest, MBMsg[sizeof(MBHeader)], sizeof(MBRequest));
  145.   if length(ReqData) >0 then begin
  146.     move(ReqData[0], MBMsg[sizeof(MBHeader)+sizeof(MBRequest)], length(ReqData));
  147.  end;
  148.  
  149.   if MBRequest.FunCode <> 0 then begin
  150.     with TInetSocket.Create(Server, Port, StrToIntDef(ParamStr(1),0)) do begin
  151.       Write(MBMsg[0], Length(MBMsg));
  152.       SetLength(Buffer, 260);
  153.       Count := Read(Buffer[0], length(Buffer));
  154.       SetLength(Buffer, Count);
  155.       Free;
  156.     end;
  157.     for i := low(MBMsg) to high(MBMsg) do begin
  158.       sent += inttostr(MBMsg[i]) + ' ';
  159.     end;
  160.     for i := low(Buffer) to high(Buffer) do begin
  161.       rec += inttostr(Buffer[i]) + ' ';
  162.     end;
  163.     writeln;
  164.     writeln('sent: ', sent);
  165.     writeln(' rec: ', rec);
  166.   end;
  167. end.
  168.  

(Please dont judge, im a hobby programmer   O:-))
I like big endians and i can not lie.

bobby100

  • Sr. Member
  • ****
  • Posts: 291
    • Malzilla
Re: SnapModbus
« Reply #19 on: June 15, 2025, 05:34:54 pm »
So, I've got some time for SnapModbus again, and here are the top and bottom logs side by side. Left is the raw data and right is the log message.
Quote
[Indication from 0.0.0.0]                                    
64 02 00 06 00 01 50 3E                        d.....P.
[Response to 0.0.0.0]
64 02 01 00 BF 44                                 d....D        2025-06-15 17:15:08 - 0x02 - Read Discrete Inputs - Addr : 0x0006, Amount : 1  --> OK

[Indication from 0.0.0.0]
64 03 00 03 00 01 7D FF                        d.......
[Response to 0.0.0.0]
64 83 01 90 EF                                      d....           2025-06-15 17:15:09 - 0x03 - Read Holding Registers - Addr : 0x0000, Amount : 0  --> ERR 0x01 Illegal Function

[Indication from 0.0.0.0]
64 03 00 07 00 01 3C 3E                        d.......
[Response to 0.0.0.0]
64 83 01 90 EF                                      d....           2025-06-15 17:15:09 - 0x03 - Read Holding Registers - Addr : 0x0000, Amount : 0  --> ERR 0x01 Illegal Function

[Indication from 0.0.0.0]
64 04 00 04 00 01 79 FE                        d.....y.
[Response to 0.0.0.0]
64 84 01 92 DF                                      d....           2025-06-15 17:15:09 - 0x04 - Read Input Registers - Addr : 0x0000, Amount : 0  --> ERR 0x01 Illegal Function

[Indication from 0.0.0.0]
64 04 00 08 00 01 B9 FD                        d.......
[Response to 0.0.0.0]
64 84 01 92 DF                                      d....           2025-06-15 17:15:10 - 0x04 - Read Input Registers - Addr : 0x0000, Amount : 0  --> ERR 0x01 Illegal Function

[Indication from 0.0.0.0]
64 01 00 05 00 01 E4 3E                        d.......
[Response to 0.0.0.0]
64 01 01 00 4F 44                                  d...OD         2025-06-15 17:15:10 - 0x01 - Read Coils - Addr : 0x0005, Amount : 1  --> OK

The first package got a valid response. I've highlighted the function code and the address.
The second package was rejected. As you can see, the address isn't zero (orange highlight), but the error message says it is zero.
I didn't highlight the rest of the log.

avra

  • Hero Member
  • *****
  • Posts: 2547
    • Additional info
Re: SnapModbus
« Reply #20 on: June 17, 2025, 03:02:46 am »
When I send RTU over TCP message (plain TCP adds 7 bytes header seen in 1st screenshot)
Code: [Select]
64 04 00 08 00 01 B9 FDI get
Code: [Select]
64 04 02 00 00 F5 38which is correct, and log looks like this:
Code: [Select]
2025-06-17 02:53:40 - [127.0.0.1] 0x04 - Read Input Registers - Addr : 0x0008, Amount : 1  --> OKso WinDevice and WinBroker work fine on my side even for a single register (note that your double takes 8 bytes which is 4 registers, so make sure that endianes match).

Code: [Select]
64 84 01 92 DF84 in your response means error. Double check configurations on all sides. If you choose RTU over TCP then it must be on both sides. Be aware that MODBUS TCP and MODBUS RTU OVER TCP are not the same. Also try with firewall switched off.

Besides WinBroker I also sent messages using 3rd party Modbus Poll tool and the result is the same.
« Last Edit: June 17, 2025, 01:14:51 pm by avra »
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

bobby100

  • Sr. Member
  • ****
  • Posts: 291
    • Malzilla
Re: SnapModbus
« Reply #21 on: June 19, 2025, 11:37:00 pm »
Got it.
I've mentioned that I've modified the sources to meet my needs, and the following line messed the things up:
Code: Pascal  [Select][+][-]
  1. Device.SetParam(par_BaseAddressZero, integer(Settings.BaseAddressZero));
What I've expected is to have the register addresses counted from zero or from one, according to this setting.
What I didn't expect is, that this parameter will mess with the amount of registers to be read/written.
So, when the PLC asked for the amount of one register, this parameter translated it to the amount of zero registers requested.

It is a shame that I can't contact the author to report the bug. The discussions on source forge seems to be dead (author not answering in the discussions) and there is no contact on the website.

Thanks for all the help, guys.

Btw. one question that bothers me.
In SnapMBcommon.pas, the number of registers are limited as:
Code: Pascal  [Select][+][-]
  1. regs_amount = 32768;
According to modbus specs, this should be 65536.
Who got it wrong?

Thaddy

  • Hero Member
  • *****
  • Posts: 17477
  • Ceterum censeo Trumpum esse delendum (Tnx Charlie)
Re: SnapModbus
« Reply #22 on: June 20, 2025, 03:18:55 pm »
Not modbus....SnapModbus seems to use a signed SmallInt while it is specified a Word. (a.k.a. UInt16)
« Last Edit: June 20, 2025, 03:52:57 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

bobby100

  • Sr. Member
  • ****
  • Posts: 291
    • Malzilla
Re: SnapModbus
« Reply #23 on: June 21, 2025, 12:36:45 pm »
Any idea how to get this working on Linux?
I have compiled the library (make install) and the make copied it to /usr/lib/
How do I set the linker to find the necessary files?
Code: Pascal  [Select][+][-]
  1. Warning: linker: /usr/bin/ld: -lsnapmb kann nicht gefunden werden: Datei oder Verzeichnis nicht gefunden
(Lazarus ignores the language setting - the error messages in log window are in default OS language)


btw. I got delphi-modbus package to install in Lazarus 4.0 on Linux Mint.

Thaddy

  • Hero Member
  • *****
  • Posts: 17477
  • Ceterum censeo Trumpum esse delendum (Tnx Charlie)
Re: SnapModbus
« Reply #24 on: June 21, 2025, 05:00:03 pm »
-Fu/the/path/to/your/library/units
-Fl/the/patch/to/o/or/so/files

Anyway, compiling delphi-modbus with the huge bug/mistake of using signed integers is not good enough and you or the author should fix that first. I'd recommend to simply fork it, fix it, call it laz-modbus.
The bug suggests it is really old delphi code.(D2 or older) It must be seen by the docter.
« Last Edit: June 21, 2025, 05:06:24 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

bobby100

  • Sr. Member
  • ****
  • Posts: 291
    • Malzilla
Re: SnapModbus
« Reply #25 on: June 21, 2025, 11:09:58 pm »
Got the SnapModbus running on Linux  :D

 

TinyPortal © 2005-2018