Your CuPID as a PLC: Modbus TCP Client

The PLC Pi as a Communicator

As we discussed in a previous post, one of the most importantĀ  functions of a PLC is as a communication swiss army knife. A given control system might have devices that speak half a dozen or more languages on as many interfaces that need to be read and written. If the central control device cannot interface with a device, different hardware must be specified to resolve compatibility. Obviously, the fewer languages your PLC speaks, the fewer options you have when it comes to selecting instrumentation and acquisition hardware.

The Modbus Protocol

One of the most common formats in use in the industry is Modbus, which has been around since 1979. It’s a simple and somewhat archaic protocol, but one that you’ll find everywhere as a least common denominator for inter-device communication. While it’s quite limited in structure, the fact that it is well-defined makes it an easy drop-in format that’s easy to configure.

We won’t get into the nitty gritty of frame and bit by bit message format, but the protocol has the following properties:

  • A Modbus transaction has two participants: a Master (Client) and Slave (Server)
  • Members on a multi-drop Modbus network are identified by a Node number (address), and in the case of Modbus TCP/IP, an IP Address. In the case of TCP/IP, the node number is unnecessary except in unusual cases where the Slave acts as a gateway for multiple devices.
  • Data exist in a Modbus slave in banks of data locations referred to as Registers
  • Data transactions consist of a Function Code, which specifies a read or write transaction, as well as which bank of registers to act on, a specific register number, and any data required to complete the request
  • There is no way to encode metadata, except in adhoc systems. Datamaps must be defined outside of the protocol itself. Most devices will have a map that says “data A is in register X, in format Y”
Registers and Function Codes
Name Registers Datatype Mode Read FC Write FC
Coils 000001-065535 bit RW 1 5
Discrete Inputs 100001-165535 bit R 2 -
Input Registers 300001-065535 bit R 4 -
Holding Registers 400001-165535 bit RW 3 6

The Physical Layer

The above defines the communication format, i.e. where the bytes and bits go. It does not define, however, how that information is physically communicated. Typically, Modbus is employed in serial formats such as RS232, RS485, and over TCP/IP. The first two are what might be considered legacy, in particular RS232, but they are still in widespread usage in industry today. TCP/IP is the go-to format for most networks today, and what new designs are built around, at least as far as Modbus is concerned. In general, where the overhead of the TCP/IP layer is not a killer for transfer efficiency or power considerations, it’s a good way to go, if for no other reason that so many network infrastructures are built on it.

Although it is possible to interface the Pi with devices on RS485 and RS232, using level-shifting transceivers depending on the voltage levels of the host devices, what we will cover here is Modbus TCP/IP, for the fact that it is the most widely used moving forward, and that the standard is so well defined. Plus, we’ve got some great toys laying around that talk it, which make great demos!

How does Pi talk Modbus?

There are many options available for speaking modbus onĀ  a linux box. We’ll demonstrate a Python implementation, for the reasons that it is the default language for Pi projects and because it’s what we write all of our server-side code in here. We use a nice little library called Pymodbus, which is quite easy to use. On top of it, we build in some error-handling and user-friendliness. You can find that here. Thumbing down a bit, you can find the function below, which is the workhorse for our MB TCP/IP reads:

def readMBcodedaddresses(clientIP,address,length=1)

The function takes a server IP, register, and read length, chooses the appropriate function code, and returns the read value(s). Some basic coded error functions are included.

>>> import netfun
>>> netfun.readMBcodedaddresses('',400001,2)
ReadRegisterResponse (2)
{'message': 'status ok', 'values': [16544, 20890], 'statuscode': 0}

We’re working on an analogous write routine.

One thing to keep in mind, however, is that each transaction has an overhead involved with establishing the connection. If you are carrying out multiple reads, it’s much more efficient to intelligently group register reads into blocks. We’ve written a young piece of code that will do this for you, given you have a datamap that has the correct structure for it. The main function is datamaptoblockreads, and interprets an sqlite table of reads, groups them into blocks, and parses the data. This ends up saving loads of time for large read operations. We’ll cover this later.

What to do with it?

So we’ve got some server-side python that reads clients on the network. What do we do with it? Well, a few things:

  • Read data from LAN/WAN host devices
  • Log and database it
  • Expose it on a web server
  • Accept and process write requests

We’ll explore the topics above in a miniseries:


The above makes use of the open source libraries available on github here:

Explanation and installation here:


2 thoughts on “Your CuPID as a PLC: Modbus TCP Client”

Leave a Reply

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