I have written a number of little scripts for my home automation projects, mostly in C++, Perl or Lua, however to date I have avoided writing any projects in Python. It would seem that Python is currently the latest fashion for Raspberry Pi and home automation projects so I thought I had better have a go at writing my own.
For my first Python project, I decided I wanted to write a simple script which could communicate with my 4-Noks Modbus TCP/IP Zigbee gateway and plug.
Before I started writing any Python code, I needed to understand a little more about the MODBUS protocol (and also brush up on my TCP/IP knowledge). I found the RTA Automation website extremely useful, and it’s superb tutorials covered pretty much everything I needed to understand the Modbus protocol. In particular I needed to understand about Modbus registers and the packet structures.
I shan’t try and explain everything about Modbus registers (see the RTA website for this) but I will attempt to summarise… In Modbus there are upto 4 areas of memory called:
- Discrete Inputs (read-only boolean)
- Coils (read-write boolean)
- Input Registers (read-only integer)
- Holding Registers (read-write integer)
These 4 areas of memory are split into a number of addressed slots called registers used to store variables in order to control or set functionality of a Modbus device, as well as sensor readings, addresses, counters and device statuses. By changing the values within these registers you can control the devices behaviour (e.g switching a particular coil register to 1 (high) may switch a plug on). Or by reading a certain input register may provide you with the devices current status (e.g may contain power draw value).
MODBUS TCP/IP Packet Structure
The next thing I needed to know was the structure for Modbus packets. Thankfully they are quite simple, made up of a small header containing information such as length, destination unit identifier and function code, followed by the data:
- byte 0 : Transaction identifier – copied by server – usually 0
- byte 1 : Transaction identifier – copied by server – usually 0
- byte 2 : Protocol identifier = 0
- byte 3 : Protocol identifier = 0
- byte 4 : Length field (upper byte) = 0 (since all messages are smaller than 256)
- byte 5 : Length field (lower byte) = number of bytes following
- byte 6 : Unit identifier (previously ‘slave address’)
- byte 7 : MODBUS function code
- byte 8+ : Data as needed
Byte 7 above contains the ‘Function Code’, these codes instruct the destination on whether the requester is asking to read or write to one of the 4 Modbus registers described previously:
- 1 : Read coils
- 2 : Read input discretes
- 3 : Read multiple registers
- 4 : Read input registers
- 5 : Write coil
- 6 : Write single register
- 7 : Read exception status
- 16: Write multiple registers
The following is an example Modbus packet:
00, 00, 00, 00, 00, 06, 10, 05, 00, 01, FF, 00
- 00 : Transaction ID
- 00 : Transaction ID
- 00 : Protocol ID
- 00 : Protocol ID
- 06 : The following packet contains 6 bytes.
- 10 : Destination unit 16 (Hex 10).
- 05 : Write a single coil (Function Code 5).
- 00 :
- 01 : Set coil address #1.
- FF : Value High (On).
Simple Modbus Python Code
Armed with the above knowledge the following is my first little Python script which sends Modbus TCP/IP commands to my 4-Noks plug to switch it on and off:
import socket import struct import time # Create a TCP/IP socket TCP_IP = '192.168.0.107' TCP_PORT = 502 BUFFER_SIZE = 39 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((TCP_IP, TCP_PORT)) try: # Switch Plug On then Off unitId = 16 # Plug Socket functionCode = 5 # Write coil print("\nSwitching Plug ON...") coilId = 1 req = struct.pack('12B', 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, int(unitId), int(functionCode), 0x00, int(coilId), 0xff, 0x00) sock.send(req) print("TX: (%s)" %req) rec = sock.recv(BUFFER_SIZE) print("RX: (%s)" %rec) time.sleep(2) print("\nSwitching Plug OFF...") coilId = 2 req = struct.pack('12B', 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, int(unitId), int(functionCode), 0x00, int(coilId), 0xff, 0x00) sock.send(req) print("TX: (%s)" %req) rec = sock.recv(BUFFER_SIZE) print("RX: (%s)" %rec) time.sleep(2) finally: print('\nCLOSING SOCKET') sock.close()
In the second post I further the above Python script to read multiple registers.
12 thoughts on “Modbus TCP/IP Basic Python Script – Part 1”
The script is good and helpful but I have 1 question, as I can see you have 2 sequence, can you repeat the whole sequence again without exiting the socket.
Thanks for this script James. I have a question, can you explain me the struct pack()? I don´t understand the each field of this struct. Thanks.
Thanks for your comment.
Each of the fields in the structure are detailed in the MODBUS TCP/IP Packet Structure section above.
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, int(unitId), int(functionCode), 0x00, int(coilId), 0xff, 0x00byte 0 : Transaction identifier – copied by server – usually 0byte 1 : Transaction identifier – copied by server – usually 0byte 2 : Protocol identifier = 0byte 3 : Protocol identifier = 0byte 4 : Length field (upper byte) = 0 (since all messages are smaller than 256)byte 5 : Length field (lower byte) = number of bytes followingbyte 6 : Unit identifier (previously ‘slave address’)byte 7 : MODBUS function codebyte 8-11 : Data (in the above case request to ‘switch plug on’)
As it happens I was only last night converting the above to Arduino C code, which once I get it working I will publish on this site.
smartofthehome thanks for your rapid answer.
I want to write an information in a record of a PLC. The record is in the direction of memory 40005. Can you explain to me how modify the field DATA to write in this direction of memory?
Thanks very much James.
smartofthehome whats is the meaning of ’12B’ ??
i12ropea smartofthehome 12B means the following structure will be 12 Bytes long.
I have a question, can you explain me the struct pack()? I don´t understand the each field of this struct. by the way, thanks for the script. 🙂
If for example I wanted to send a floating type over modbus, how would the code above be changed? I guess what I am trying to what register would you send it to.
My name is Kinley and I am from Bhutan. I went through your articles and they are really good.
Do you uave a similar python scripts for communicating with Modbus RTU??
Thanks in advance.
Sorry, I don’t have any other code examples.
I can not make it work. I typed your code save it and run it with thonny python editor and it says connection refused… how can I solve this.
I need to read the data from the eth port of my hitachi plc. Only read the status of the variables, dont need to write.
The section title reads: Simple Modbus Perl Code.
But the script is written in Python. 😉