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.