Step 1: Choose a Modbus Server
Servers abound: other PLCs, touchscreens, temperature controllers, VFDs, DAQs, other Pis; nearly anything on the network these days has modbus functionality built in. It is, for better or worse, the least common denominator in device communication. We wrote a bit about this topic previously.
Here, we’ll demonstrate communication with a LabJack DAQ. They offer very reasonably priced hardware with some great functionality. We’ve used them for many years, built LabVIEW applications for them, and put them to test in industrial data acquisition environments. Bang for buck, you just can’t beat them.
Incredibly, outside of the well-written LJUD driver (which our LabVIEW applications are built on), each of their devices also acts as a modbus server for all available data. All you need is a map of registers and which values they correspond to, which they provide here.
Map out the values
With the modbus register map, all we need to do is decide what we want to read, choose the appropriate registers, read them, and make sure we interpret them properly into meaningful values.
We’ve got a couple LabJacks laying around. For this example, we’ll use our trusty UE9, which has plenty of IO for what we’re doing here. It’s on our LAN here at the shop, and we’ve set it up to take a static IP.
First, we’ll make sure that we’re talking properly to view the Analog Input (AIN) data. We’ll use AIN0-3 (why and where we’re probing is discussed below). Each value comes in as a float, which occupies four bytes. Each modbus register is a two-byte word, so each AIN will require two modbus registers. This means we can read two registers each from registers 1, 3, 5, and 7 for the four analog inputs. Note that the modbus maps provided by LabJack start at 0, but all modbus data layers start at 1. So all values are offset by one.
We pull up our basic modbus TCP viewer page to view devices. As we discussed previously, we use some ajax and wsgi to pass data back and forth between our browser and our server. In this case, the user initiates a request in the webpage by pressing a ‘data refresh’ button, which passes the register, clientIP, and length of the request to the server via ajax. A wsgi script on the server, in the case ‘wsgiactions.wsgi’, is run with the request information. It calls pymodbus to read our data from our LabJack, and happily returns it to our browser, as shown below:
You can watch all of this magic happen using Firebug, which is how we diagnose all of our web behavior. See at the bottom how we log the data object as it comes from our wsgi script, and can examine the object structure and the DOM as we like. You can even execute js/jQuery on the fly. It’s seriously like magic.
So we’re getting values, and they appear to be legitimate. Next, we need to make a decision about how to format them. These are four-byte float values, which need to be converted into something that resembles, you know, a number. We can do this in the web page itself, or we can process them as we put them into a database that stores current values and datalogs. We’ll show both, just for fun. Inside the browser, we’ll add an option to select formatting. It will force us to build some formatting and byte-switching functions that we should probably have around anyway.
First, we borrow this beautifully simple piece of code from here. It takes an array of bytes and converts it into a float, given a few other parameters that determine the type of conversion. It’s simple enough to plug and play, but flexible enough to allow conversion of 32 and 64 bit floats and varying endianness. First, we simply take our two 16-bit words and break them into bytes:
var byte2 = somedata.response.values[i] % 256 var byte1 = (somedata.response.values[i] - byte2)/256 var byte4 = somedata.response.values[i+1] % 256 var byte3 = (somedata.response.values[i+1] - byte4)/256 var bytes = [byte1, byte2, byte3, byte4]
Then we feed our bytes into our decoder, and out pop values:
var myfloat = decodeFloat(bytes ,1, 8, 23, -126, 127, false)
We’d also like to truncate the displayed values a little bit to make them easier to read. At best, the UE9 will give us 16-bit resolution over the +/-5V range, which comes out to 0.15mV. This means it’s totally safe to give ourselves 5 digits of precision without losing any information. And, while we’re at it, let’s add a widget to the panel to let us set this on the fly, should we want to fix it. We’ll also add a drop-down to allow selection of our float formatting. We add it in, an d presto: human-readable values! We have connected AIN0 to Vs and AIN1 to GND on the LabJack, so we know where our numbers should be. Everything looks pretty good.
In python, our life is easier. Everything we need is built into standard packages, namely the
struct package. First, we break the words into bytes as we did previously:
byte2 = somedata.response.values[i] % 256 byte1 = (somedata.response.values[i] - byte2)/256 byte4 = somedata.response.values[i+1] % 256 byte3 = (somedata.response.values[i+1] - byte4)/256
Next, we convert to characters that are string hexadecimal representations of the bytes:
byte1hex = hex(byte1) byte2hex = hex(byte2) byte3hex = hex(byte3) byte4hex = hex(byte4) hexstring = byte1hex + byte2hex + byte3hex + byte4hex
Finally, we use the struct package to convert a string representation, specifying that we are big-endian (>) and float (f):
import struct myfloat = struct.unpack('>f',hexstring)
We’ll use this later when we build a general purpose importer for our database values. Easy peasy!
Read and log, please
Now we don’t want to have to keep pressing our data refresh button, so we’re going to set up our Modbus values as inputs for our CuPID. Besides our mouse finger getting tired, we want to datalog, and potentially use these values to trigger external actions, such as outputs, emails, etc.
We define all of the poll and read configuration through database tables, in a database aptly named ‘controldata.db’. A python daemon handles various system maintenance, data hygiene functions, web session management, and most important, our input/output polling and control algorithms. How this works is worth an article in itself, and it’s on the way. For now, suffice it to say that when we create an interface and define IO as below, our CuPID will periodically read and log values to our IO and log databases.
Each time the daemon cycles through and reads IO, it does so by interface. Each type of interface is handled depending on type. For example, 1Wire interfaces have their own routine, as do SPI, I2C, and so on. For the modbus interface type, if the interface is enabled, the ‘modbustcp’ table is searched for those bound to its interface id and processes them as dictated by the table entry.
Defining an interface
Our interfaces are defined in the ‘interfaces’ table, shown here in phpliteadmin:
and here in our interfaces screen:
You can see that all the defining parameters are present for our various interfaces. In the case of Modbus TCP/IP, we define the IP here and then individual registers in a separate table, aptly named modbustcp.
The modbustcp table
Each modbustcp table entry specifies the unique interface ID of the modbus interface (created above), and are hence bound to this interface. In this way, if we change the client ID or basic read parameters, we can do it once in the interface table, rather than in a dozen places. It also enables us to take devices offline in one fell swoop. It has loads of benefits.
In the ‘modbustcp’ table, we specify the read memory locations and size, how to interpret the data, and a number of other options. In light of our work with float conversion above, we add a ‘format’ field, to which we can add our float specification. We add a piece to our IO read handler (see processMBinterface section here), and we’re set. We also add a catch-all ‘options’ field to which we can add any other number of flags and options, in object notation. So, for example, if we want to scale our values and set precision before the values are entered into the database, we can specify:
Our python control scripts can use a function to parse this into a dictionary and do with it as we see fit. More on this later. Now we just need to add and name our modbustcp entries for our analog input entries. We can do this from our ioedit page:
A little clicky-clicky, and we have four inputs named for the four analog inputs. To get them to show up in our list of inputs, we hit the refresh button. Remember, what shows up in the lists of inputs and outputs are what are actually generated when IO are read by the daemon. Notice that they have values in the right-hand column. Our first analog input (shown below) has a value shown in the bubble. To give them handy names, we edit them in place:
Now, when we make new connections, we rename them and they’ll pop up with the names we’ve given them. It’s otherwise quite inconvenient to have to remember what “AIN0” or “MBTCP1_400001_2R” mean. The colloquial names we make are stored in a special table called “ioinfo”, and key an ID to a text name. The IDs are automatically created in such a way as to make them unique, and the labels are stored even after the IO is removed. If you add it back in again, the label will still be there.
See the Data
Now, we want to see the data. First, we verify that it’s being recorded as we wish. We won’t get into the details (we’ll do that elsewhere), but default parameters dictate that each input that is read is logged with the same regularity it’s read. We can specify how much is retained. The default here is 100 points. Either using phpliteadmin or our ‘dataviewer’ page, we can see they indeed exist, and we select them to be viewed using the toggles:
Now, we pop over to our datalogviewer to see them in plot form. Using the plot controls at left, we select our newly-created short names and refresh the plot. Like magic, they pop up with our names:
Next, on to a practical example!
The above makes use of the open source libraries available on github here:
Explanation and installation here: