Homebrewed software to communicate with measuring device via Modbus TCP

jschulze

Your homebrewed software capture does not show any TCP packets. Your software is not establishing a connection and is not communicating with the MC 750 at all.

You need to rewrite your homebrewed software. The .NET library I sent a link for is one way you could do this, and is likely the easiest method, since it does not require you to know the Modbus protocol in great detail. Another popular .NET library you could use is NModbus https://github.com/NModbus/NModbus

nikoloz

Thank you sir, for the right directives, that probably change my approach to programming connection.

Am I supposed to download the .NET library from the link to my computer? And then to rewrite my software using MS VB.NET, or MS Visual C.NET?

Faithfully.

jschulze

Yes, you need to download one of the .NET Modbus libraries to your computer and include the DLL as a reference in your .NET project (note that the library may be offered in source code format instead, in which case you will need to compile the project into a DLL yourself or simply inclulde the project into your .NET project).

When using MS .NET, you can write your code in any of the following languages:
• VB.NET
• C#
• F#
• C++/CLI

The EasyModbusTCP web site has code examples in C# here:
http://easymodbustcp.net/codesampleseasymodbustcp-net

And there is a step-by-step example using EasyModbusTCP available online using VB.NET here:
https://accautomation.ca/tag/easymodbustcp/

The NModbus library includes sample code under the Samples directory when you download the library's source code:
https://github.com/NModbus/NModbus

If I may ask, what is the intended use of your homebrewed software? Is it simply an educational/learning experience or do you have a specific goal you are trying to achieve?

nikoloz

Thank you sir, without your detailed explanation I could barely find this valuable resources.

If I may ask, what is the intended use of your homebrewed software? Is it simply an educational/learning experience or do you have a specific goal you are trying to achieve?
Yes, certainly sir. In a company I work(in automation branch), there is some scada software which displays and sounds an alarm when power goes off on 6-10/0.4 kv substations. So there are discrete signals which "sound" when changing their status from 1 to 0 and 0 to 1. By the way there is a need of having telemetry(current, power, voltage etc.). MC750-760 are possible telemetry source. On 6-10/0.4 kv substations there are some kind of controllers with GPRS modem and ports which already transmit statuses(1 to 0, and 0 to 1), so I decided to try adding some timid functionality enabling of posessing some telemetry also. Please have a look at the screnshot below:

Faithfully.

nikoloz

Yes, you need to download one of the .NET Modbus libraries to your computer and include the DLL as a reference in your .NET project (note that the library may be offered in source code format instead, in which case you will need to compile the project into a DLL yourself or simply inclulde the project into your .NET project).

When using MS .NET, you can write your code in any of the following languages:
• VB.NET
• C#

Dear sir,
I have chosen the the "EasyModbusTCP.NET package v5.5" from "SourceForge". When downloaded it appeared this archive has an inclusion:
1. Easy Modbus Server Simulator (NET.Version)
2. Easy Modbus Client (.NET Version)
3. Easy Modbus library for .NET(DLL)

Can you kindly tell me, which of abovementioned I have to install?

Faithfully.

jschulze

Yes, you need to download one of the .NET Modbus libraries to your computer and include the DLL as a reference in your .NET project
Unzip the "Easy Modbus library for .NET(DLL)" and include EasyModbus.dll as a reference in your .NET project.

And there is a step-by-step example using EasyModbusTCP available online using VB.NET here:
https://accautomation.ca/tag/easymodbustcp/

nikoloz

Dear gentlemen,

I unpacked an archive EasyModbusLibrary for .NET(DLL) , and there was file EasyModbus.dll inside. Then I:

1. Copied it to folders C:\Windows\System32 and also C:\Windows\SysWOW64
2. Then I launched console as administrator
3. In the console I typed a command:
C:\Windows\System32>regsvr32 EasyModbus.dll

Afterwards I downloaded some software designated 'DLL Export Viewer' and launched it. This program enables user trace the dll's functionality. And it shown me this:

Then I hit "OK" :

Some minutes later with this software I tried to do the same with Winsock.ocx file(which I loaded much earlier to my PC) and it displayed this:

Then afterall I decided to try another version of a package(assuming there may be a .DLL with an 'entry point'), I downloaded the package from the same site, but an elder version of 5.0.
That estimated the same.
Maybe there is no problem of use dll with no entry point? How can other people use this dll when it doesn't have an entry point?

Faithfully.

jschulze

I believe the method you're attempting to use is for special OLE controls that use the DLL or OCX extension and are special controls that implement the entry point methods DllRegisterServer and DllUnregisterServer functions.
https://stackoverflow.com/questions/620858/what-does-registering-a-dll-do

The EasyModbusTCP.dll is NOT an OLE control; it is a .NET library. Therefore, you cannot register the DLL with windows. You need to open Microsoft Visual Studio, open or create a .NET project, and add the DLL to your project references, as shown in this step-by-step example here:
https://accautomation.ca/tag/easymodbustcp/

nikoloz

Good afternoon dear gentlemen!

As you kindly adviced me I carefully followed the steps defined on site you suggested me above.
First of all I created a new VB.NET project "MC GET DATA". Then I copied EasyModbus.dll and added a reference to that dll.
Please have a look at screnshots below:

2nd. I created a form similar(please have a look at the picture above). Then I added a programming code. Very similar to the code in the source. While coding there were no notifications on errors or warnings. I succesfully imported EasyModbus namespace using the statement: ' Imports EasyModbus'. All methods of the library appeared properly while typing the entire code.
3rd I intended to read and display the two adjacent holding registers 30142 and 30143 those refer to T6 data type of a Real Power P1. And therefore I have applied only to textboxes to display extracted data. And therefore I typed:
4th. I run a newbye application, input IP and port to communicate. It has thrown exceptions, please have a look how it tries to operate:

As you see I toggled between ports 502 and 10001 on the MC 750. When ports do not match an application outputs message "Communication error!" . Then ports match the application throws an exception as displayed above on the screnshots.
It looks that some unknown issue prevents registers 30142 and 30143 from being extracted to the newborn software.
Please have a look on the Wireshark capture file I attached to the post.

Faithfully.

Attachments

• 2.8 KB Views: 1

jschulze

Again, you're incorrectly trying to access the registers as Holding Registers when you need to access them as Input Registers. The 30,000 offset means they are Input Registers.

Therefore you need to change
to

However, this is not the cause of your errors. Your errors indicates a connection or timeout issue. Your Wireshark capture shows that the MC 750 is not sending a Modbus response packet (when using port 10001) to your requests. This is because you are using the wrong Unit Identifier (Slave ID). You are currently sending 1 instead of 33. You need to set the UnitIdentifier property of your ModbusClient to 33. I believe you would do this as follows:

ModbusClient.UnitIdentifier = 33

Also from your Wireshark capture, it shows that the MC 750 is rejecting the connection to port 502. Again, the MC 750 manual does state that if a connection is made to the user defined port (TCP port 10001), that all other connections (including connection to port 502) are disabled. That seems to be what's happening here. The MC 750 is rejecting connections to port 502 because you've previously made a connection to port 10001.

If you are still having issues after fixing the above, please confirm communications again using Modbus Poll. After you can successfully communicate with Modbus Poll, then try your new software.

nikoloz

Good morning dear gentlemen!

After I applied to the program code the correction changes, you've kindly suggested me to make:

ModbusClient.UnitIdentifier = 33

I run the newbye software(and a wireshark alongside), please have a look at the screnshots below:

Please have a look inside wireshark capture file I attached as an zip archive.
I left it being run connected MC750 alongside and wireshark capture on for some time.
Does it operate properly?

Yours faithfully.

Attachments

• 145.6 KB Views: 4
• 3.7 KB Views: 4

jschulze

You do have successful communications, but from your Wireshark captures, it appears that the EasyModbusTCP library uses 0-based addressing instead of 1-based.

Therefore you need to subtract 1 from the register number, to read Input Registers 142 -143 (i.e. 30142 - 30143):

You should also modify your window to only show one value and should modify your code to apply the proper decoding of the value.

I don't know visual basic, so here is a pseudo-code example, you will need to translate this in the VB.NET code
Code:
SByte exponent
Integer mantissa
Single value

exponent = Registers[1] RightShiftBy 8

mantissa = Registers[1] AND 0xFF
manitssa = mantissa LeftShiftBy 16
mantissa = manstissa OR Registers[0]

value = mantissa * (10 ^ exponent)

nikoloz

Dear sir,
Just before trying to implement the code lines you kindly suggested to add.
I will try to understand at least abstractly those transformations that need to be done on the contents of the registers in order to get data in the physical units we are accustomed to use.

SByte exponent 'Declaring variable 'exponent' that should be of SByte data type
Integer mantissa 'Declaring variable 'mantissa' that should be of Integer data type
Single value 'Declaring variable 'value' that should be of Single data type. This variable will be displayed as a phisical value

exponent = Register[1]RightShiftBy 8 ' the content of first register(30103) that stores decade exponent is shifted by 8 bits and the desired value is assigned then to variable exponent.

mantissa = Registers[1] AND 0xFF 'Can you please give me some sentences to get me through understanding this expression?
mantissa=mantissaLeftShiftBy16 'the variable 'mantissa' is shifted then by 16 bit and assigned to itself(rewrited?)
mantissa = mantissa OR Registers[0] ' this is logical function of OR, can you kindly give me some sentences to understand this expression?
value = mantissa*(10^exponent) 'mantissa is then multipled by decade exponent and that yields is assigned to 'value'

Faithfully yours.

jschulze

There is a small issue with the pseudo code example I gave you, I did not sign-extend bits 23...16 of the 32-bit register value. The code should be as follows (note the addition of the "as SByte" cast):
Code:
SByte exponent
Integer mantissa
Single value

exponent = Registers[1] RightShiftBy 8

mantissa = (Registers[1] AND 0xFF) as SByte
manitssa = mantissa LeftShiftBy 16
mantissa = manstissa OR Registers[0]

value = mantissa * (10 ^ exponent)
Let's walkthrough an example from post #33 for how to decode Real Power P1 with this code.

As a reminder, the data format for P1 (from the MC 750's User's Manual) is as as follows:

Step #1 Read the Register Values
The least-significant word of the 32-bit value is in register 30142 and the most-significant word is in 30143.

30142 = 0xD6AA
30143 = 0xFEFF
This equates to a 32-bit value 0xFEFFD6AA

Registers = ModbusClient.ReadInputRegisters(141, 2)
After this code, the Registers array variable contains the following:
Registers[0] = 0xD6AA
Registers[1] = 0xFEFF

The EasyModbusTCP library uses 0-based register addressing. So to read register 30142, we must use the ReadInputRegisters function, pass 141 for the starting register value, and read 2 registers. Registers[0] is the first register in an array variable called Registers, located at index 0 (0-based counting, as typically used in programming). Registers[1] is the second register in an array variable called Registers, located at index 1 (0-based counting, as typically used in programming).

Step #2 Extract Bits 31...24 (The Exponent)
bits # 31..24 = 0xFE (-2 decimal)

exponent = Registers[1] RightShiftBy 8
After this code, the exponent variable contains the following:
exponent = 0xFE

The value in Registers[1] is 0xFEFF. After the right shift, the value becomes 0x00FE. Assigning this value to the exponent variable, the exponent variable now contains 0xFE.

Registers[1] is the second register (30143), which is the most-significant word of the value containing bits 31...16. Right shifting by 8 moves the upper 8 bits to the lower 8 bit spot, replacing the upper 8 bits with 0.

Step #3 Extract Bits 23...0 (The Mantissa)
bits # 23..00 = 0xFFD6AA (-10582 decimal)

mantissa = (Registers[1] AND 0xFF) as SByte
After this code, the mantissa variable contains the following:
mantissa = 0xFFFFFFFF

The value in Registers[1] is 0xFEFF. ANDing this value with 0xFF results in a value of 0x00FF (255 decimal). Casting the value as a SByte results in a value of 0xFF (-1) decimal. Finally, the value automatically gets casted as an Integer data type (which will perform the sign extension) and results in a value of 0xFFFFFFFF (-1 decimal) before getting stored in the mantissa variable.

This code extracts bits 23...16 from the second register (30143). The AND operation, with a value of 0xFF, masks off the upper 8-bits of the 16-bit value, leaving only the least-significant 8 bits. Casting as a SByte data type tells the compiler to interpret the value as a 8-bit signed integer before converting the number to a 32-bit signed integer.

manitssa = mantissa LeftShiftBy 16
After this code, the mantissa variable contains the following:
mantissa = 0xFFFF0000

Before this code, the mantissa variable has a value of 0xFFFFFFFF. After this code, the value in the mantissa variable is 0xFFFF0000.

This code moves the value 0xFF in the mantissa variable from bit positions 7...0 to bit positions 23...16 (while also moving the sign-extension bits in bit positions 15...8 to bit positions 31...24) in the mantissa variable by shifting the value left by 16.

mantissa = manstissa OR Registers[0]
After this code, the mantissa variable contains the following:
mantissa = 0xFFFFD6AA

The value in Registers[0] is 0xD6AA. After this code, the value in the mantissa variable is 0xFFFFD6AA.

This is a bit-wise OR function, which performs the OR operation on each bit in the two values:
0xFFFF0000 OR 0x0000D6AA = 0xFFFFD6AA

Step #4 Calculate the Value
P1 = -10582 x 10^-2 = -105.82 W.

value = mantissa * (10 ^ exponent)
After this code, the value variable contains the following:
value = -105.82

The value in the mantissa variable is 0xFFFFD6AA (-10582 decimal). The value in the exponent variable is 0xFE (-2 decimal). After this code, the value in the value variable is -105.82.

nikoloz

Dear sir,
I have been trying to implement/rewrite in VB.NET the strings you've kindly offered me to add into a new program.
There are 2 issues in code I have to deal. The fourth string is complained of having issue 'statement completion requered'.
The sixth string is complained of having expectation of ")", but when I type it into the string the issue is not cleared.

The explanations you've provided are very helpful for me. And I am thinking on this. You shifted my mind to the new(for me) level of understanding how to handle the extracted data from input registers.
I also made some changes to the newborn software interface, the input registers contence transformation into phisical unit is supposed to run under 'Data in PU' button click event. Please have a look at a screenshot below:

Sincirely yours,
N.Qv.

jschulze

As I mentioned, I do not know Visual Basic, so the code example I provided you is NOT Visual Basic, but rather pseudo-code. You will need to translate my pseudo-code into proper Visual Basic code.

Bit Shift Operators (Left Shift, Right Shift)
https://docs.microsoft.com/en-us/do...guage-reference/operators/bit-shift-operators

Logical Operators (AND, OR, etc.)
https://docs.microsoft.com/en-us/do...reference/operators/logical-bitwise-operators

Hint: "RightShiftBy", "AND", and "as" are not valid Visual Basic operations. They should be ">>", "And", and "As", respectively.

Of course, the pseudo-code I provided is just one way of doing this. This is the way that makes sense to me. There are likely many other ways of creating code to perform this value decoding task.

jschulze

To add to my previous response, the as SByte pseudo-code for casting to a SByte is also not proper Visual Basic syntax.

I think you may need to use one of the type conversion functions here:
https://docs.microsoft.com/en-us/do...reference/functions/type-conversion-functions

Therefore I think the following pseudo-code
mantissa = (Registers[1] AND 0xFF) as SByte

Should translate to the following Visual Basic code
mantissa = CSByte(Registers(1) And 0xFF)

In fact, you probably don't even need the And 0xFF at all (since the cast should just take the least-significant byte anyway), so the line could simply be
mantissa = CSByte(Registers(1))

Again, I'm not a Visual Basic programmer, so please verify that using CSByte is the correct way of casting value types in Visual Basic.

nikoloz

Dear sir,

All errors experiencing while Data Casting seems to be cleared in favour of your meaningful suggestions!
There is some error that possibly takes its origin from referring the contence of text boxes instead of referring to values extracted directly from registers.
I added as you recomended as follows:

exponent= Registers(1) >> 8

mantissa= CSByte(Registers(1))

mantissa= mantissa << 16

mantissa= mantissa Or Registers(0)

value= mantissa*(10^exponent)

TextBox6.Text = "value"

These code lines are consolidated under Button2(Data in PU) button click event. I separated Data Casting task to other command.
Please have a look at screnshots below:

Faithfully.
N.Qv.

jschulze

You're getting a NullReferenceException: Object reference not set to an instance of an object. This means that one of the variables you're trying to access is Null (i.e. uninitialized).

There are several issues with the following 3 lines of your code
Code:
Dim Registers As Integer()
Registers(0) = TextBox4.Text
Registers(1) = TextBox5.Text
First, this line
Dim Registers As Integer()
Does not initialize the size of the Registers array, therefore indexes 0 and 1 do not exist, hence the NullReferenceException. It should be
Dim Registers(1) As Integer
See here for details on declaring arrays:
https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/arrays/

Next, the other two lines are incorrect.
Registers(0) = TextBox4.Text Registers(1) = TextBox5.Text
You are trying to assign a string to an integer. You need to convert the string to an integer. You can do this using one of several methods: Integer.Parse (equivalent to Int32.Parse), Integer.TryParse (equivalent to Int32.TryParse), Convert.ToInt32, CInt:
https://docs.microsoft.com/en-us/dotnet/api/system.int32.parse?view=net-5.0
https://docs.microsoft.com/en-us/dotnet/api/system.int32.tryparse?view=net-5.0
https://docs.microsoft.com/en-us/dotnet/api/system.convert.toint32?view=net-5.0
https://docs.microsoft.com/en-us/do...reference/functions/type-conversion-functions

Because you know the text is a valid integer, I recommend using Integer.Parse as follows
Registers(0) = Integer.Parse(TextBox4.Text) Registers(1) = Integer.Parse(TextBox5.Text)

So in summary, your three lines should look like this
Code:
Dim Registers(1) As Integer
Registers(0) = Integer.Parse(TextBox4.Text)
Registers(1) = Integer.Parse(TextBox5.Text)

nikoloz

Good afternoon dear sir!

Your suggestions on using "Parse" method applied to an application perfectly!
The compiler proceeded from the first line to the last without throwing an exceptions and displayed some data in the text box!

But when I increased the signal value(Current x Voltage = Power) applied to MC750, the content of Register(1) went below -128 and hitting "Data in PU" button led to an exception thrown immediately.
From the link you kindly passed to me I read that the CSByte function properly works, when its argument is within -128 ....... +127 range.
Range for expression argument should be: SByte.MinValue(-128) through SByte.MaxValue(127).
And therefore when contence of Register(1) for example is -26568 it throws an exception on: String 80, Column 9, Lit 9.
Please have a look at the screnshot below:

There are some other screnshots on this error experiencing to newborn application:

Faithfully yours.
N. Qv.