Automation Direct Modbus Addressing in Ignition
Recently we were tasked to pull in data from an Automation Direct Protos X Analog Input Terminal and map it to tags in an Ignition SCADA system using Modbus TCP.
Normally we would set up the Modbus TCP device on the gateway, create the tags, add the addresses and be done with it. Maybe we would have to toggle “Zero-Based Addressing” or “Reverse Word Order” if it wasn’t working right away, but the number of options there is limited for most applications.
The sticking point with the Protos X Module is they use a different method than most Modbus Devices as shown here:
The image above shows the Protos X module sending 16 bits as we would expect. But, the data we want is only contained in the 12 bits from 3 to 14. Bits 0-2—along with bit 15—are not in use.
If we use the standard Modbus register mapping, our values will not line up with reality. The other limiting factor is that instead of a 16 bit value, we only have 12 bits. The scaling will be based on a value from 0 to 4095. Not a huge issue, but we’ll have less overall resolution than we would normally expect.
Bitwise Operations to the Rescue
Your first instinct might be to see what data comes across by simulating a known value into the device and seeing what you get on the output. If you try this, you will find the values do increase and decrease as your signal increases and decreases, but none of the numbers will make any sense. This is because Ignition is expecting 16 bits, so whatever values come across in bits 0-2 will affect the value you are reading once the data gets to Ignition.
Luckily for us, our Computer Science ancestors who built all of this in the first place gave us a convenient set of tools known as Bitwise Operations to handle these types of scenarios.
Time to Go Undercover with Bitmask Operations in Python
You could read in the integer value, break it into individual bits, cut out the ones you don’t need, then convert it back to an integer. But, this is a decent amount of code to write, and can introduce some troubleshooting time. Instead, if you think about “how would someone have done this in the 80s” you will find there are Bitmask operations bit into Python that will simplify this whole exercise into a couple of lines of code.
First we’ll show you the code, then we’ll talk about what it is doing:
Our final engineering unit value will be in the range of 0-1000 so we set those up as constants. Assuming we are testing the code in the script console in Ignition, we will set up a value to simulate the raw value from the Protos X unit. When we move this into production, we will set it up to read from a tag.
We next, set up a bitmask using the VALUE_MASK variable. Essentially the bitmask acts as a filter for whatever value we compare it against, and it will only “use” bits that have a value of 1 in the bitmask. In this case we have a 0, followed by 12 1s then 3 0s. The code will ignore anything in bits 15, 0, 1, and 2, and only allow bits 3-14 to be used when we apply the bitmask operation to our value.
To apply the bitmask filter, we use the bitwise & operator with the rawPLCValue and VALUE_Mask variables which will return the bits from rawPLCValue that have a corresponding 1 bit in the VALUE_MASK variable. We will also shift the result of this operation up by 3 bits using the >> operator which will move the result from Bits 3-14 to Bits 0-11.
Once we have the bits 3-14 moved to bits 0-11, we can treat it like any other normal integer value.
Finally, we will apply a scaling operation to the value to scale it from 0-4095 to 0-1000.
Move it to Production with UDTs
To make this useful across the project, we will create a UDT with two tags. One is an OPC tag that will read from the Protos X using Modbus TCP as normal. The second tag is a memory tag where we will store the result of the operation above. We will set up a tag change event script on the OPC tag, put the code above into it, and write the scaledValue result to the second tag.
You’ll notice here we pulled the bitwise operations out into an Ignition Project script. This is useful in case we need to use this code elsewhere in the project, or if we need to modify it at some point in the future.
Wrapping Up
This is obviously not the most common task you will run into in an Ignition implementation. Most hardware uses the standard protocols as written to avoid having to go into this level of detail. In cases where this is what is available, you might need to jump through some hoops to get the data you want, however as far as communication conversions go this one is pretty easy.
Updated - 6/16/2022