La séptima vida

...o el gato así lo espera/teme

Device::Modbus - Using Perl to Communicate with Industrial Devices

Modbus is an industrial communication protocol. It is implemented by many industrial devices such as servo motors, temperature controllers, force monitors, and Programmable Logic Controllers. Device::Modbus is a set of Perl modules that should allow for writing both Modbus clients and servers, and it is my latest project.

This article introduces serving Modbus requests from a very high point of view.

How it all started

Near the end of 2014, I had a problem. I was working with a PLC I did not know and with a servo-motor that was also new to me. They would not understand each other and I could not hear what they were saying. In other words, I lacked a tool to inspect the RS485 bus.


So, naturally, I went to the CPAN looking for a Perl module that would let me connect to the PLC and read from its output. There is Protocol::Modbus, which helps you write Modbus clients, but I did not find any server or any tool that would allow me to inspect the binary stream coming out the PLC. For the record, you can go straight with Device::SerialPort if, like me, you are dealing with Modbus RTU over the serial port. This method will not tell you what the binary code represents, though.


I then moved to Python. There, I did find a very nice package, Modbus-Tk, that would let me write a toy server and simulate the servo-motor so that I could test my code on the PLC. It allows writing clients too, so in a short time I was sending commands to the servo-motor. I modified the server code in Modbus-Tk to simply display the actual requests sent by the PLC to the servo-motor, and connected my computer in between. And life was good, except it was not written in Perl.


Objectives for Device::Modbus

There are basically three applications that would be nice to have: clients, servers, and spies. Spies would show and interpret messages between devices that speak Modbus. You would see the type of request that was sent, its parameters, and the response from the other device.


Several Modbus clients exist already in Perl. Besides Protocol::Modbus, I found some code over in GitHub. Clients are nice because they let you question the PLC or command many different devices.


But probably the most interesting application is a server.


The first task to implement for a Modbus server is its ability to parse Modbus messages; this step is needed for the spy too. Once the request is parsed, the following question is how to react to client requests. With Modbus, a client can send requests to read or write a certain number of memory addresses in the remote server. Modbus-Tk builds a set of arrays to represent memory locations and then reads from or assigns values to these arrays. But that is not enough. The server should be able to query a database, to log information, or to post data to a web service. Imagine the possibilities.


Before describing the server, let's discuss how Modbus works. From there we will jump to the addressing model, and then I shall describe how Device::Modbus::Server implements the mapping from requests to actions that respond.


A brief tour of the Modbus protocol

Devices which work as Modbus servers (or slaves, as they are also called) expose one or more units. Units are the more general entities that you can address using Modbus, and they are identified using a simple number.

Often, a device is seen as a unit. In Modbus RTU, the variant that works through the serial port, you normally assign a unit number to each device in the RS485 bus. In Modbus TCP, which works over Ethernet, the IP address fulfills the same addressing function and thus it is often not necessary to define a unit number for each device, although the protocol uses a default unit number that is generally discarded.


Data within a unit is represented as belonging to one of four types:

  • Discrete inputs

  • Coils (discrete outputs)

  • Input registers

  • Holding registers

Coils are so named after the actual wire coils in mechanical relays. As their names imply, discrete inputs and outputs are bit-addressable; registers are 16-bit words. Inputs are read-only, while it is possible to read and write outputs and holding registers.

Now, these bits or words, they have an address. Requests allow you to read or write a bit or a word at a given address, or a set of bits or words starting at a given address. What's more, there is a function that will let you read and write registers in one step.

Finally, a single Modbus server might implement several “units”. This concept is useful, for example, to implement Modbus TCP to RTU gateways where a complete bus of serial devices are reachable through a single IP address.

This figure should summarize the above explanation:

Modbus proposes a simple addressing model and four different types of data

In the example, we could request unit 3 to return the values of its input registers from addresses 26 to 28 to learn the length, position in x and position in y of an imaginary instrument.

In addition to this, the Modbus specification states that devices are free to implement this model as it better fits. It is entirely possible to have registers overlap bit-addressable zones to gain the ability to query a specific bit of a word, for example. Or a device could have only input registers and nothing else. And this leads us to the model actually implemented by Device::Modbus::Server.

Device::Modbus::Server

Within a Device::Modbus::Server, it is the unit definition which is interesting. Units are sub-classes of Device::Modbus::Unit, and they contain address handlers. Let's use a picture to save a thousand words:

Device::Modbus::Server routes requests to address handlers

An address handler is in fact an object which encapsulates a piece of code. This code will be executed if the server gets a request that matches some criteria. So, the idea is to route client requests to these address handlers, pretty much like web frameworks route HTTP requests to route handlers.

To execute an address handler, a request must match its memory zone, its defined address space, the specified quantity of data the address handler can work with, and whether it is for reading or writing.

In the image, unit number 3 has three address handlers. The first will listen for requests for reading discrete outputs (coils) in addresses 2, 3 or 5. It will return only one bit, and it will execute the method called read_coordinates of the given unit object. To reach this address handler, we could send a request for reading the coil at address 3 (or 2 or 5) of unit 3.

As it is shown in the example, addresses and quantities can be expressed in a quite flexible way, and the first address matching a request will be executed.