I have never written my own Modbus Communications, is there a resource that I could read to understand how to do this.
Start simple and work your way up.
Look up client-server architecture, and find a "hello world" example in an easy scripting language like Python or Javascript, or even C.
Make sure you understand what is going on both in the client (connect) and in the server (bind/accept).
The WSL (Windows System for Linux) was an add-on to Windows 10 that allows easy experimentation with command-line apps.
Then read the technical documentation about Modbus at modbus.org. The documents are quite readable; don't get swamped with the details, just remember that 90% of most applications is bookkeeping i.e. keeping track of the bits, whether those are discrete bits or bits grouped as INTs or DINTs or REALs. The Modbus protocol is almost completely based on 16-bit MSByte-first unsigned integers. Pay particular attention to the byte order of multi-byte entities (addresses, lengths, etc.).
Then write a Modbus TCP Client/Server pair in something like Python; that's a fairly small step from the hello world example once you understand the protocol (see previous protocol).
As with anything, break it into bite-size tasks:
- build a message into a stream of byte data, be those data "hello" or a Modbus request.
- write a TCP server that binds an [IP,port] pair (IP will be localhost or 0.0.0.0) and accepts connections
- write a TCP client that connects to an [IP,port], and prove it works by having the server above print "accepted connection" to the terminal in response
- modify the TCP server to read the accepted connection
- modify the TCP client to write data to the connection, and prove it works by having the server above print "received data" to the terminal.
- modify the TCP server to, after receiving the client data, to send response data (e.g. the current time plus "OK," back to the client over the connection
- modify the TCP client to read the response from the server, and prove it works by having the client print "received response YYYY-mm-ddTHH:MM:SSOK."
- By this point, you have more or less duplicated the hello world examples.
- modify the TCP client to create a Modbus protocol message (ADU = additional (modbus unit, usually 1 or 0) address (1 byte) + PDU (1 to 253 bytes); Modbus TCP needs no CRC; PDU = function code (1 byte) _[1 to 252 bytes)) as data, and print it to standard output, but converted to hexadecimal so you can read it.
- modify the TCP server to parse a Modbus protocol message; don't worry about fully parsing the PDU beyond the function code yet.
- modify the TCP client to send that Modbus protocol message to the server connection, and prove it works by having the server print the additional address and the function code (first byte) of the PDU.
- modify the TCP server to parse the PDU for one type of function code e.g. 3 (0x03) Read Holding Registers, so it will see the byte 3, parse the next two bytes as an MSByte-first offset into the server's "holding registers," and the next two bytes as an MSByte-first quantity of registers requested.
- modify the TCP client to generate a Read Holding Registers Modbus protocol request and send same to the TCP server, and prove it works by having the server print out the details of the request (additional address, function code, offset to starting address, request quantity).
- modify the TCP server the generate data for a response by taking the addresses requested (e.g. modbus address offset = 16 and quantity = 3 => 16, 17, 18), subtracting each from 65535 (so 65519, 65518, 65517) and converting those to a Modbus response (additional address, function code, 2-byte byte count, register data), and sending that message (add'l address, 0x03, 6, 65519, 65518, 65517) as a response to the client
- modify the TCP client to read back those data, and prove it works by parsing those data and printing the results to standard output.
Each one of those steps is typically, and often far, less than a couple dozen lines of code. Extra points if you break each step into its own method, to make it easy for the main program to choose between methods later (e.g. call parse_read_holding(method) if function code is 3; call parse_read_discrete_inputs(method) if function code is 2; call retrieve_holding_registers(method) if the parse_read_holding(method) found a valid address offset and quantity; etc.).
What's next is up to you: convert that from Python (or whatever) to a PLC socket interface; add other function codes; etc. Also: good writers borrow; great writers steal; but make sure you understand what you are stealing.
It's all bookkeeping. As Jouni Ryno wrote: "It's just zeros and ones, it cannot be hard"