none
Simple Modbus TCP/IP communications using VB 2012 (no DLL!) RRS feed

  • Question

  • My app formerly was communicating using Modbus to a PLC via its RS232 serial port (38400 bps), and I'm now trying to convert over to communicating via an Ethernet port (100mbps). 

    Is there a simple way to do this while NOT 1) spending any money buying some company's software, and 2) using a DLL?  I'd like to stay in control of the app I'm writing and not depend on anybody's DLL. 

    I've written this app in VB 2012, not C++ or C# and want to keep using VB.


    Sometimes I get tired just smiling...

    Wednesday, January 8, 2014 9:34 PM

Answers

  • Hi Dwyee,

    Where the links to the documentation not helpful enough?  I can try to give you an idea, but code for this kind of thing really needs to be written specific to the communication needed and protocols used.

    Here's a rough outline of using a TcpClient:

    Public Class Form1
        Private _Client As New System.Net.Sockets.TcpClient
    
        Private _Buffer(63) As Byte 'a data buffer to read bytes from the remote connection; arbitrarily set to 64 bytes
        Private _MessageQueue As New Queue(Of Byte) 'a collection of the received bytes which can be analyzed, looking for a complete message from the remote device
    
        Public Sub ConnectToPlc(hostNameOrAddress As String, port As Integer)
            If Not _Client.Connected Then
                _Client.Connect(hostNameOrAddress, port)
                _Client.GetStream.BeginRead(_Buffer, 0, _Buffer.Length, AddressOf ReceiveData, Nothing)
            End If
        End Sub
    
        Public Sub SendMessage(messageBytes() As Byte)
            If _Client.Connected Then
                _Client.GetStream.Write(messageBytes, 0, messageBytes.Length)
            End If
        End Sub
    
        Private Sub ReceiveData(result As IAsyncResult)
            Dim stream As System.Net.Sockets.NetworkStream
            Dim bytesRead As Integer
            stream = _Client.GetStream
            bytesRead = stream.EndRead(result)
            For i As Integer = 0 To bytesRead - 1
                _MessageQueue.Enqueue(_Buffer(i))
            Next
            stream.BeginRead(_Buffer, 0, _Buffer.Length, AddressOf ReceiveData, Nothing)
            ProcessMessageQueue()
        End Sub
    
        Private Sub ProcessMessageQueue()
            'see if bytes in queue represent a complete message result from the remote device
            'if so, empty the queue up to that point
        End Sub
    End Class
    

    The connection part should be pretty self explanatory.

    I have to assume from your posts that you are already at the point where you can represent/recognize a MODBUS message/reply as a series of bytes.  So after connecting, you can use the SendMessage() method to send a composed message to the remote endpoint.

    The receiving is the potentially tricky part.  Its best to read asynchronously from the stream and accumulate the received bytes into a message buffer until you determine that a complete message has been received.  Here is where things get application-specific.  How much complexity you need here will depend on how large and complex the transmissions are.

    If you always know that a message will be a certain number of bytes, or even within a certain tolerable limit (at least less than the size of an Ethernet frame) then you might just have one buffer and count on receiving the reply message in a single transmission every time.  But its usually best to blindly read chunks of data from the remote endpoint and then gather them up into some other contain for inspection at your leisure.

    So this shell of an example shows how to read the data into a small buffer, add it to a queue (which you could make into any appropriate collection, I use a queue to demonstrate the purpose of the object), start another read operation, and then proceed to see if you have a complete message to process yet or not.

    I know that's not a lot to go on but hopefully it will help you get started enough that you can come back with more specific questions if you run into problems.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Tuesday, January 14, 2014 11:29 PM
    Moderator

All replies

  • It seems to me if you do not want to use any .Dlls then you would need to write the program using .Dlls. Once it is working use something like "Dependency walker" in order to find what functions are used by your program for each .Dll your program accesses. And then re-write those functions in Visual Basic and change your program to incorporate them instead of referencing or importing .Dlls. If in fact all of that is possible.

    Though it seems quite the waste of time to me. Even the namespace System needs mscorlib.dll. So in Visual Studio, Reference Manager, any checked item I believe requires a .Dll.

    And I suppose a Form needs a .Dll in order to be created in designer or something.


    Please BEWARE that I have NO EXPERIENCE and NO EXPERTISE and probably onset of DEMENTIA which may affect my answers! Also, I've been told by an expert, that when you post an image it clutters up the thread and mysteriously, over time, the link to the image will somehow become "unstable" or something to that effect. :) I can only surmise that is due to Global Warming of the threads.



    Thursday, January 9, 2014 2:16 AM
  • Perhaps I should re-phrase my question...I don't want to use any external DLL (not included with Visual Studio) developed by somebody else.  I'm not sure of any DLL's future compatibility (OS and apps) unless I have its source code that can be modified to fix any problems.

    So, anybody have any ideas (or references) that they can suggest to help me?

    I have seen something about using 'System.Net.Sockets' in Visual Studio to communicate via the Ethernet port.  I still need help using it in VB.


    Sometimes I get tired just smiling...

    Thursday, January 9, 2014 3:27 PM
  • You may need an online C# to VB converter for code from links in the following thread how to communicate with multiple(200) modbus TCP/IP devices at a time? . Specifically the link for Modbus TCP class which I would guess will solve your problem.


    Please BEWARE that I have NO EXPERIENCE and NO EXPERTISE and probably onset of DEMENTIA which may affect my answers! Also, I've been told by an expert, that when you post an image it clutters up the thread and mysteriously, over time, the link to the image will somehow become "unstable" or something to that effect. :) I can only surmise that is due to Global Warming of the threads.

    Thursday, January 9, 2014 4:08 PM
  • Hmmm...still not exactly what I need.  I previously found the above 'Modbus TCP Class' that you mentioned above.  It uses 'ModbusTCP.dll', that I'm trying to avoid, since it is not a standard part of Visual Studio, and its source code is not provided to enable me to make changes/fixes.

    Any other ideas?

    I'm currently attempting to use 'System.Net.Sockets' unsuccessfully, though it seems to be the way to get to the Ethernet port.  I have routines to compose Modbus 'Read' and 'Write' requests, but I just need to send these out and receive the return messages back from the PLC via the Ethernet port.

    I have set the PLC's Ethernet port to be IP Address 169.254.104.25 (and, according to the docs, it's listening to port 502), and my PC's Ethernet IP address is 169.254.104.45.


    Sometimes I get tired just smiling...

    Thursday, January 9, 2014 8:15 PM
  • If you had looked at all those links one was for the Modbus community where maybe you can get an answer to a question or possibly find code posted that does what you want to do.


    Please BEWARE that I have NO EXPERIENCE and NO EXPERTISE and probably onset of DEMENTIA which may affect my answers! Also, I've been told by an expert, that when you post an image it clutters up the thread and mysteriously, over time, the link to the image will somehow become "unstable" or something to that effect. :) I can only surmise that is due to Global Warming of the threads.


    Thursday, January 9, 2014 8:22 PM
  • I previously found the above 'Modbus TCP Class' that you mentioned above.  It uses 'ModbusTCP.dll', that I'm trying to avoid, since it is not a standard part of Visual Studio, and its source code is not provided to enable me to make changes/fixes.

    Why are you so determined not to use the provided DLL?   While it will be possible to get to the network data stream, that's only a small part of the task, even for a passive listener.  The process of detecting the packets, constructing the messages then decoding the messages is not trivial and involves a lot of bit twiddling which is not particularly easy in VB.  If you want an active node then the problem is much more difficult.  The best way to 'stay in control' of the app would be to spend the time required in developing a new DLL on researching then validating an existing one. 

    Thursday, January 9, 2014 8:44 PM
  • Perhaps with the information in this .PDF document Technical Reference – Modbus TCP/IP and information available from this link TCP/UDP you can figure out how to send to and receive from the PLC. Or maybe not. I don't have one so I know nothing about it.

    Please BEWARE that I have NO EXPERIENCE and NO EXPERTISE and probably onset of DEMENTIA which may affect my answers! Also, I've been told by an expert, that when you post an image it clutters up the thread and mysteriously, over time, the link to the image will somehow become "unstable" or something to that effect. :) I can only surmise that is due to Global Warming of the threads.

    Thursday, January 9, 2014 9:04 PM
  • Acamar:

    I may not have made myself clear...I've already done all of the 'twiddling' needed to construct the outgoing request messages and receiving/deconstructing the incoming responses via the RS232 serial port.  I just need to start sending/receiving via the ethernet port using TCP/IP.

    So, what I really need are how to use the 'System.Net.Sockets' (and whatever else VB provides) to do this.

    I will do some more checking on the Modbus Community site, thanks Monkeyboy.


    Sometimes I get tired just smiling...

    Monday, January 13, 2014 3:44 PM
  • Hmmm...still not exactly what I need.  I previously found the above 'Modbus TCP Class' that you mentioned above.  It uses 'ModbusTCP.dll', that I'm trying to avoid, since it is not a standard part of Visual Studio, and its source code is not provided to enable me to make changes/fixes.

    Any other ideas?

    I'm currently attempting to use 'System.Net.Sockets' unsuccessfully, though it seems to be the way to get to the Ethernet port.  I have routines to compose Modbus 'Read' and 'Write' requests, but I just need to send these out and receive the return messages back from the PLC via the Ethernet port.

    I have set the PLC's Ethernet port to be IP Address 169.254.104.25 (and, according to the docs, it's listening to port 502), and my PC's Ethernet IP address is 169.254.104.45.


    Sometimes I get tired just smiling...

    You'll use the System.Net.Sockets.TcpClient to connect to your MODBUS endpoint.  It will then be up to you to implement the MODBUS protocol over the raw bytes which you can read/write from the NetworkStream returned by a call to GetStream on the TcpClient instance.

    You can use any of the generic examples of reading/writing a stream since all Tcp communication works the same way (the only MODBUS-specific part is how to interpret the byte data once you have it).


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Monday, January 13, 2014 5:16 PM
    Moderator
  • ...

    I have set the PLC's Ethernet port to be IP Address 169.254.104.25 (and, according to the docs, it's listening to port 502), and my PC's Ethernet IP address is 169.254.104.45.


    Sometimes I get tired just smiling...

    Whoop, just caught this...

    DO NOT use the 169.254.0.0 network; these are "local link" address for unconfigured adapters and cannot be routed.  Depending on your network configuration it may be that these two addresses cannot talk to each other.

    Move your devices onto a standard private network, most commonly 192.168.0.0, 10.10.0.0, or 172.16.0.0.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Monday, January 13, 2014 5:21 PM
    Moderator
  • Reed:

    Oh...good idea.  I had left the IP address of my PC to what I found when I looked at the ethernet port.  I have the rest of my internal PC network on the 192.168.1.xxx subnet, which I would like to keep clear of this PLC subnet, so I'll go ahead and set the PLC to be 192.168.2.1 and my PC's ethernet at 192.168.2.100.

    The 169.254.104.xxx settings seemed to be working ok with the code I found on:

    Hadi Scada Site

    But, as you mentioned, this subnet is for unconfigured adapters, and I want to set this up for future PLCs on this network, and to avoid any future problems.

    Good catch!

    Anybody else have any other ideas?


    Sometimes I get tired just smiling...

    Tuesday, January 14, 2014 8:10 PM
  • Hi Dwyee,

    Where the links to the documentation not helpful enough?  I can try to give you an idea, but code for this kind of thing really needs to be written specific to the communication needed and protocols used.

    Here's a rough outline of using a TcpClient:

    Public Class Form1
        Private _Client As New System.Net.Sockets.TcpClient
    
        Private _Buffer(63) As Byte 'a data buffer to read bytes from the remote connection; arbitrarily set to 64 bytes
        Private _MessageQueue As New Queue(Of Byte) 'a collection of the received bytes which can be analyzed, looking for a complete message from the remote device
    
        Public Sub ConnectToPlc(hostNameOrAddress As String, port As Integer)
            If Not _Client.Connected Then
                _Client.Connect(hostNameOrAddress, port)
                _Client.GetStream.BeginRead(_Buffer, 0, _Buffer.Length, AddressOf ReceiveData, Nothing)
            End If
        End Sub
    
        Public Sub SendMessage(messageBytes() As Byte)
            If _Client.Connected Then
                _Client.GetStream.Write(messageBytes, 0, messageBytes.Length)
            End If
        End Sub
    
        Private Sub ReceiveData(result As IAsyncResult)
            Dim stream As System.Net.Sockets.NetworkStream
            Dim bytesRead As Integer
            stream = _Client.GetStream
            bytesRead = stream.EndRead(result)
            For i As Integer = 0 To bytesRead - 1
                _MessageQueue.Enqueue(_Buffer(i))
            Next
            stream.BeginRead(_Buffer, 0, _Buffer.Length, AddressOf ReceiveData, Nothing)
            ProcessMessageQueue()
        End Sub
    
        Private Sub ProcessMessageQueue()
            'see if bytes in queue represent a complete message result from the remote device
            'if so, empty the queue up to that point
        End Sub
    End Class
    

    The connection part should be pretty self explanatory.

    I have to assume from your posts that you are already at the point where you can represent/recognize a MODBUS message/reply as a series of bytes.  So after connecting, you can use the SendMessage() method to send a composed message to the remote endpoint.

    The receiving is the potentially tricky part.  Its best to read asynchronously from the stream and accumulate the received bytes into a message buffer until you determine that a complete message has been received.  Here is where things get application-specific.  How much complexity you need here will depend on how large and complex the transmissions are.

    If you always know that a message will be a certain number of bytes, or even within a certain tolerable limit (at least less than the size of an Ethernet frame) then you might just have one buffer and count on receiving the reply message in a single transmission every time.  But its usually best to blindly read chunks of data from the remote endpoint and then gather them up into some other contain for inspection at your leisure.

    So this shell of an example shows how to read the data into a small buffer, add it to a queue (which you could make into any appropriate collection, I use a queue to demonstrate the purpose of the object), start another read operation, and then proceed to see if you have a complete message to process yet or not.

    I know that's not a lot to go on but hopefully it will help you get started enough that you can come back with more specific questions if you run into problems.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Tuesday, January 14, 2014 11:29 PM
    Moderator