Modbus TCP/IP Basic Python Script – Part 2

Taking my previous Modbus Python Script further I wanted to extend my Python script to read multiple Modbus Registers from my 4-Noks Modbus TCP/IP Zigbee gateway.

4-Noks Modbus CodeThe following script asks the user to provide options such as Unit ID, Function Code and Register Address which are then used to construct the appropriate Modbus TCP/IP packet to be sent to the 4-Noks gateway.

The script then finishes after displaying the contents of the requested registers, contained within these register readings could be information such as sensor readings, device addresses, serial numbers, counters or status.

In principle, there is not a massive difference between the below script and that in my previous post other than some more advanced mathematics required to calculate packet and buffer sizes.

import socket
import struct
import time

# Create a TCP/IP socket
TCP_IP = '192.168.0.107'
TCP_PORT = 502
BUFFER_SIZE = 0
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((TCP_IP, TCP_PORT))

try:
# Ask user for Modbus options
print("\nPLEASE ENTER MODBUS OPTIONS")
unitId = input(" Unit Identifier : ")
functionCode = input(" Function Code : ")
startRegister = input(" Start Register : ")
numRegister = input(" Number of Registers : ")

# Construct request packet
req = struct.pack('>3H 2B 2H', 0, 0, 6, int(unitId), int(functionCode), int(startRegister), int(numRegister))
sock.send(req)

# Calculate receipt packet buffer and structure
BUFFER_SIZE = (3*2) + (3*1) + (int(numRegister)*2)
rec = sock.recv(BUFFER_SIZE)

def setB():
global BH
BH = 'B' #1
def setH():
global BH
BH = 'H' #2

functionLookup = {
1 : setB, # Read Coils (1 byte)
2 : setB, # Read Input Discrete Registers (1 byte)
3 : setH, # Read Holding Registers (2 byte)
4 : setH  # Read Input Registers (2 byte)
}
functionLookup[int(functionCode)]()

s = struct.Struct('>3H 3B %s%s' %(numRegister, BH))
data = s.unpack(rec)

# Print out packet header
print("\nMODBUS PACKET HEADER")
print(" Transaction Identifier : %s" %data[0])
print(" Protocol Identifier : %s" %data[1])
print(" Length : %s" %data[2])
print(" Unit Identifier : %s" %data[3])
print(" Function Code : %s" %data[4])
print(" Byte Count : %s" %data[5])

# Print out register values
print("\nREGISTER VALUES")
for i in range(6, 6+int(numRegister)):
currentRegister = str((i - 6) + int(startRegister)).zfill(2)
print(" Register #%s : %s" %(currentRegister, data[i]))

# Wait a couple of seconds before disconnecting
time.sleep(2);

finally:
print('\nCLOSING SOCKET')
sock.close()

5 thoughts on “Modbus TCP/IP Basic Python Script – Part 2

  1. Oscar P. says:

    Hello James Saunders,

    So much thanks you for this post. We have used this code for our implementations in a Raspberry Pi 2 in a U project. We wrote a papers about our projects and included a appointment about your post.

    Thank again.

  2. Jeff805 says:

    Thank you for a great tutorial. Thank you for sharing.

    Unfortunately for me, I’m more familiar with C than Python. I’m hoping you can explain a couple things about the following line of your code:

    req = struct.pack(‘>3H 2B 2H’, 0, 0, 6, int(unitId), int(functionCode), int(startRegister), int(numRegister))

    I figured out that the > means big indian, but I’m not sure what the purpose of the 3H 2B 2H is. I thought this first part was to describe the length of the packet. I also thought the 6 should be 4 since there are only 4 bits that follow and not 6. Sorry if these are basics. Thank you in advance for your help.

    • James Saunders says:

      Hi Jeff,

      Thanks for your feedback. I am (still) not a Python expert but I will try to explain this struct.pack line.

      struct.pack('>3H 2B 2H', 0, 0, 6, int(unitId), int(functionCode), int(startRegister), int(numRegister))

      Also refer to https://docs.python.org/2/library/struct.html table 7.3.2:

      This is saying “Construct me a binary data packet made up of…”

      • > Big endian
      • 3H 3 unsigned shorts (3 x 2 bytes) Into this we pass ‘0, 0, 6’
      • 2B 2 unsigned ints (2 x 1 bytes) Into this we pass int(unitId) & int(functionCode)
      • 2H 2 unsigned shorts (2 x 2 bytes) Into this we pass int(startRegiser) & int(numRegister)

      So the end result is a packet 12 bytes long. The above is actually a shorthand way of writing:

      struct.pack('>1H 1H 1H 1B 1B 1H 1H', 0, 0, 6, int(unitId), int(functionCode), int(startRegister), int(numRegister))

      Which I think is easier to read, with the number of elements in the struct.pack definition matching the number of parameters following:

      • 1H = 0
      • 1H = 0
      • 1H = 6
      • 1B = int(unitId)
      • 1B = int(functionCode)
      • 1H = int(startRegister)
      • 1H = int(numRegister)

      You will see this now matches the Modbus packet definition in part 1 of this tutorial.

      Hope this helps, it too me a little while to get my head around Python’s struct also!

      • Rafael says:

        Hi James, Thank you for a great tutorial.

        I’m from brazil and I’m using your code as a base for my aplication.

        I’m trying to get a float number in one of the registers, but your code gives me only interger results, what i nedd to chage to see a float result for exemple: “0.16”?

        Thank you!

  3. keyla2018 says:

    Thank U sir for your great tutorial , it’s really helpful
    I just have a question about the script in line ’43’
    s = struct.Struct(‘>3H 3B %s%s’ %(numRegister, BH))
    I’ll be so thankful if you can explain to me what does it mean ?
    thank you in advance for your efforts 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *