Introduction
A .NET user control that allows connects to an Omega DP41 over the ethernet using the Modbus over TCP protocol.
The meter can be set to poll on any interval between 1 and 20 seconds.
Background
I had a hard time getting a newly purchased DP41 communicating on Modbus over TCP-IP. Having no experience with Modbus didn't help. After getting the Omega meter set up exactly correct (both from the meter menu and the meter's html config page), I was able to return some values, but never exactly the right ones. Finally I was able to decode the info in the Omega documentation to the data I was getting back. With the help of some other open source code, it was pretty easy to make a user control that looks and works great.
Critical parts of this effort were provided by 2 other open source projects:
http://www.codeproject.com/KB/IP/Modbus_TCP_class.aspx and http://www.openwinforms.com/digital_display_control_net.html.
Big thanks to all the developers that worked on those projects.
I highly suggest going through all of the Omega Documentation - I found the following documents to be critical:
INF-B Serial Communication Option,
INF-B ModBus Communication Option,
the DP41-B User's Guide as well as the DB41-B-E1 User's Guide. A little pleasure reading to get you started.
Using the Code
This code is designed to provide a very basic Modbus TCP connection to an Omega DP41-B-E1 (Ethernet server option).
There are some excellent open source components that were used to build this control.
The apperance of the control is based on the digital display control available from OpenWinForms here:
http://www.openwinforms.com/digital_display_control_net.html.
The only real changes to the initial code was to enable negative signs and leading zeros so the apperance of the
actual Omega unit could be more accurately aped. Ghosted leading zeros make it look good.
The modbus component is based on the Modbus TCP class submitted by Stephan Stricker (http://www.codeproject.com/KB/IP/Modbus_TCP_class.aspx)
The only change to that class that I can recall was to change the default address for the Modbus slave from 0 to 1.
The OmegaDP41ModbusTCPMeter is a user control that can be added to a form (as seen in the ControlDemo project). Set the
startup project to be the ControlDemo project to see how it works. My apologies for mixing VB.NET and C#, but all of the code I needed to use was in C#, and my bosses prefer that we stick with VB (which I'm more comfortable in anyway).
This code was the end result of nearly 2 days of banging my head against the wall trying to communicate with the Omega unit using modbus over TCP-IP. The fact that every single open source or free modbus software would return an error or some bad values when I tried to get a value from the Omega unit is most likely because Omega uses some custom 2-1/2 byte encoding for the meter reading values (and other values) when they are returned via modbus.
It would have been helpful if the first words out of the Omega customer service rep was something like 'Most standard Modbus software won't read our values - We don't send back a 16 or 32 bit number, we send back a 20 bit number with a 4 bit formatting section'. It would have been more clear what I was in for. That said, most all the info is in the documentation if you look hard enough.
This control is mostly done, and I don't plan to update the code here unless there are required bugfixes. Additional options would be nice (reading the setpoints from the meter, automatically changing the display color based on the meter set points). These are left as an exercise for the reader (and please feel free to revise the code on CodeProject if you make some nice additions like that).
To configure your DB 41 meter correctly, first set it up in its own menus as follows (as provided by Omega tech support)
iSeries Settings under CNFG > COMM
Baud 9600
Parity No
Data 8 bit
Stop 1 bit
Modbus Yes
LF No
Echo Yes
Stan RS-232
Mode Cmd
Sepr CR
Address 0001
Then log in to your Omega meter's HTTP page and modify the Configuration page so that the following settings are set:
Device type connection: iNFB (Update) Configuration; Login with 12345678;
Serial Communication:
Baud Rate 9600; Data Bits 8; Parity None; Stop Bits 1; Flow Control None; Transceiver RS-232; Modbus/TCP enable
End Character 0D; Forward End Char disable; Timeout 0 msec; serial port password disable.
Terminal Server - There are changes in this section that are required:
TCP/UDP TCP; Server Type slave; Number of Connections 5; Local Port 00502; Connection Ctrl not used; Device No. 1; Connection Timeout 01000 msecs
Remote Access disable, Remote IP address 0.0.0.0, Remote port 02000
Points of Interest
Omega uses some unique encoding of values when sent via the Modbus protocol over TCIP. I would have thought that there would be no problems with accuracy given the custom encoding method used by Omega, but I do see some rounding error on the last decimal on my thermocouple readings. I have verified that the value returned can sometimes be .1 degree off that displayed on the meter.
Private Function GetOmegaValueFromByteArray(ByVal byteArray As Byte()) As String
Dim sngMultiplier As Single = 1
Dim sFormatString As String = String.Empty
' If the byte array isn't 4 elements wide, we've got a problem.
If byteArray.Length <> 4 Then Return "999999"
' This procedure used to use an Array.Reverse on the byteArray, but for some reason this wasn't
' Thread-safe and it would magically un-reversed by the BeginInvoke command. I don't understand why.
' In any case, it is just as easy to just pick apart the byte array one index at a time.
' From what I have seen, byte 0 is always empty (set to 0).
' Byte 1 is the one that contains both some Omega formatting and possibly some actual values in it.
Dim byteOmegaFormatting As Byte = byteArray(1)
' Bytes 2 and 3 store most of the Omega encoded value. There is an extra 1/2 byte of information in
' byte 1, but we'll get to that later. Here, we extract the value from bytes 2 and 3 and reverse them as we put them
' into a buffer array.
Dim bytesMeterValue As Byte() = {byteArray(3), byteArray(2)}
' Combine bytes 3 and 2 into a 16-bit unsigned integer, and then place that value into a 32 bit integer variable.
Dim iValue As UInt32 = BitConverter.ToUInt16(bytesMeterValue, 0)
' We have to extract the meaningful data from the 1st nibble of the data at byteArray(1) - Without the OmegaFormatting portion.
If CBool(byteOmegaFormatting And 1) Then iValue += CType(2 ^ 16, UInt32)
If CBool(byteOmegaFormatting And 2) Then iValue += CType(2 ^ 17, UInt32)
If CBool(byteOmegaFormatting And 4) Then iValue += CType(2 ^ 18, UInt32)
If CBool(byteOmegaFormatting And 8) Then iValue += CType(2 ^ 19, UInt32)
' Then we extract the 2nd nibble of the Omega formatting byte and put them in a string.
' There is probably a more elegant way to do this; Feel free to suggest one. This way is very clear, tho.
Dim sOmegaFormattingStringArray() As String = {"0", "0", "0", "0"}
If CBool(byteOmegaFormatting And 16) Then sOmegaFormattingStringArray(3) = "1"
If CBool(byteOmegaFormatting And 32) Then sOmegaFormattingStringArray(2) = "1"
If CBool(byteOmegaFormatting And 64) Then sOmegaFormattingStringArray(1) = "1"
If CBool(byteOmegaFormatting And 128) Then sOmegaFormattingStringArray(0) = "1"
' Combine the 4 extracted bits into a single string against which we'll perform a select case.
Dim sOmegaFormattingString As String = sOmegaFormattingStringArray(0) & sOmegaFormattingStringArray(1) & sOmegaFormattingStringArray(2) & sOmegaFormattingStringArray(3)
' Set the formatting to match as specified by the Omega documentation.
' Specifically, INFB_SCO.pdf (INF-B Serial Communication Option) section 10.28, table 10.26.
' That is correct - You need to reference the Serial Communication Option manual to get
' necessary information for your Ethernet Server Option equipment.
Select Case sOmegaFormattingString
Case "0001"
sngMultiplier = 1
sFormatString = "0"
Case "0010"
sngMultiplier = 0.1
sFormatString = ".0"
Case "0011"
sngMultiplier = 0.01
sFormatString = ".00"
Case "0100"
sngMultiplier = 0.001
sFormatString = ".000"
Case "0101"
sngMultiplier = 0.0001
sFormatString = ".0000"
Case "0110"
sngMultiplier = 0.00001
sFormatString = ".00000"
Case "1001"
sngMultiplier = -1
sFormatString = "0"
Case "1010"
sngMultiplier = -0.1
sFormatString = ".0"
Case "1011"
sngMultiplier = -0.01
sFormatString = ".00"
Case "1100"
sngMultiplier = -0.001
sFormatString = ".000"
Case "1101"
sngMultiplier = -0.0001
sFormatString = ".0000"
Case "1110"
sngMultiplier = -0.00001
sFormatString = ".00000"
End Select
' Set the class level variable msngMeterReading, which is refereced by the MeterReading readonly property.
msngMeterReading = CSng(iValue * sngMultiplier)
' Add the leading zeros because the meter will ghost them in, and that makes it look super cool.
Dim sFormattedValue = "000000000" & msngMeterReading.ToString(sFormatString)
' Decimal places don't count against the number of characters to show in the meter.
' The physical meter has 6 characters, so we mimic that here.
If sFormattedValue.Contains(".") Then
sFormattedValue = sFormattedValue.Substring(sFormattedValue.Length - 7)
Else
sFormattedValue = sFormattedValue.Substring(sFormattedValue.Length - 6)
End If
Return sFormattedValue
End Function
I have also seen the meter freeze on a single temperature while continuing to behave normally over the Modbus interface. This is pretty much a worst case scenario when trying to log temperatures, so be aware of that possibility. I think that using the latest meter and ethernet board settings listed here (updated as of 08/05/11) address that problem. That said, to be safe, whatever application this control is used in should have an alarm if the temperature doesn't change at all.
History
08/03/2011 - Released version to Code Project.
08/05/2011 - Corrected meter settings so both ethernet board and meter use RS-232 setting, and Serial Connection Timeout was set to 0.