VB.Net Com Port reading - vb.net

I am working on a project in which I have to make a GUI to connect with a controller via COM Port and retrieve the incoming data on the GUI. My controller accepts asynchronous commands in which the controller answers right away and synchronous commands in which they need some time until the answer arrives. I have created the GUI in Visual Studio VB.net.
My problem is when I send a mix of commands (synchronous and asynchronous) in a loop I cant read the answer of the 1st loop because i suppose it hasn't arrived yet and continues to the next loop.
My commands endet with a semicolon character (;) at the end and so is also the answer of each command with a semicolon at the end.
As example i send ?TR; and i am get the answer TR=1;
I thought a good way to watch if the whole answer of the send packet with commands is completed is to create a function that adds the received semicolons each time the Port_DataReceive method fires.
Here is my code of the Data_received method in which I am counting the received semicolons when arrives
Private instring As String = ""
Private Sub Port_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
While CType(sender, SerialPort).BytesToRead > 0
instring += CType(sender, SerialPort).ReadExisting
' MessageBox.Show(instring)
End While
Dim instrings() As String = instring.Split(New String() {";"}, StringSplitOptions.None)
instring = instring.Substring(instring.LastIndexOf(";") + 1).Trim
Dim i As Integer = 0
Do While (i < (instrings.Length - 1))
Dispatcher.Invoke(Sub()
Try
Try
CommDataTable.Rows(SemicolonIn)(1) = (instrings(i) + ";")
Catch ex As System.Exception
inError = (inError + 1)
End Try
Catch ex As Exception
End Try
End Sub)
i = (i + 1)
SemicolonIn = SemicolonIn + 1
Loop
End Sub
And here is my problem when i click a button event to send the loop of commands and check if the whole message was received
Private Sub btnSendFile_Click(sender As Object, e As RoutedEventArgs)
'loop
For i = 0 To 5
Port.Write("TGS;PW0;") 'TGS is asychronous and need some time till the ansew arrives
' i am sending 2 semicolons
'wait until the controller answers with the 2 semicolons -- here is my problem
MessageBox.Show("The complete message" + (i.ToString) + "was received!")
Next
End Sub

Related

Unsure on proper use of Serial Port Data Received Event

I'm working on a VSTO add-in for Excel 2013 in VB.NET that will help me interface with an instrument via a serial connection. I currently have the COM connection set up correctly and it will allow me to send and receive one command at a time. I'd like to set it up so that I can push one button and have it collect two separate readings in different worksheet cells. Using the code below, the tools work great to collect a single reading, but when I enable the code to send a second command to the instrument the Data Received event stops working entirely until I send another single read command. I know that the instrument received and processed the second command, but it never appears in excel. Could anyone help with a way to modify this code?
Private Sub mySerialPort_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
'Handles serial port data received events
UpdateFormDeligate1 = New UpdateFormDeligate(AddressOf UpdateDisplay)
Dim n As Integer = mySerialPort.BytesToRead 'find number of bytes in buff
comBuffer = New Byte(n - 1) {} 're-dimension storage buffer (n - 1)
mySerialPort.Read(comBuffer, 0, n) 'read data from the buffer
comBuffer2 = mySerialPort.ReadTo(vbCr)
Me.Invoke(UpdateFormDeligate1) 'call the deligate
mySerialPort.Close()
End Sub
Private Sub Invoke(updateFormDeligate1 As UpdateFormDeligate)
lblReading.Label = processReading() 'write to a Current Reading lable on the ribbon
Dim myApp As Excel.Application = Globals.ThisAddIn.Application
Dim currentCell = myApp.ActiveCell
currentCell.Value = processReading() 'write data in the excel active cell
Try
advanceCell()
Catch ex As Exception
System.Windows.Forms.MessageBox.Show(ex.Message)
End Try
If measureNo = 2 Then 'this case is selected when I want to read 2 measurements with a single button push
cmdSent = 2
sendCommand(measureCmd)
End If
End Sub
Private Sub UpdateDisplay()
End Sub
Note that I did not include my sendCommand sub because this is a simple .write command to the instrument that appears to be working correctly in all cases. I'd much appreciate any help anyone could provide as I'm pretty new to using data received events.
OK, I tried to isolate only the relevant the part of the script that was having an issue and I created a completely new toolbar for testing. Below is the full code for this new toolbar that contains one connect/measure button and a label that displays the status/result. I tried to comment the code to make it readable, hopefully this helps.
This new toolbar does appear to be working correctly. I'm still a little unsure on my correct usage of the DataReceived event handler in conjunction with the Invoke method (which Visual Studio slightly changed for use with Excel2013). Could anyone please provide comment as to whether I'm still using these events in an unclear way and provide a suggestion on how I may make it better?
Thanks again in advance for any help. I really appreciate it.
Imports Microsoft.Office.Tools.Ribbon
Imports System.IO.Ports
Public Class Measure2x_COM
Dim mySerialPort As New SerialPort
Dim CMD As String = "M" & vbCr 'statement telling instrument to measure
Dim measureNo As Integer = 0 'counts the number of measure commands sent to the instrument
Private Delegate Sub UpdateFormDeligate()
Private UpdateFormDeligate1 As UpdateFormDeligate
Dim sngReading As Single 'this is the reading received from the instrument as a single data type
Private Sub setupConnectCOM()
'Open COM and send measure command - this part works correctly
'first, check if serial port is open
If mySerialPort.IsOpen Then 'send measure command
mySerialPort.Write(CMD) 'the instrument will generally take 15.1 sec to perform a measurement before sending the result back
Else
'if serial port is not open, set it up, then open, then send command
'Setup COM --this part works correctly
With mySerialPort
.PortName = "COM3"
.BaudRate = 1200
.DataBits = 7
.Parity = Parity.None
.StopBits = StopBits.Two
.Handshake = Handshake.None
.ReadTimeout = 16000
End With
Try
mySerialPort.Open()
Catch ex As Exception
System.Windows.Forms.MessageBox.Show(ex.Message)
Exit Sub 'exit sub if the connection fails
End Try
Threading.Thread.Sleep(200) 'wait 0.2 sec for port to open
mySerialPort.Write(CMD) 'send measure command after serial port is open
End If
measureNo = 1
lblResult.Label = "Measuring"
End Sub
Private Sub Measure2x_COM_Load(ByVal sender As System.Object, ByVal e As RibbonUIEventArgs) Handles MyBase.Load
AddHandler mySerialPort.DataReceived, AddressOf mySerialPort_DataReceived
End Sub
Private Sub mySerialPort_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
'Handles serial port data received events
UpdateFormDeligate1 = New UpdateFormDeligate(AddressOf UpdateDisplay)
'Read data as it comes back from serial port
'I had to do this in two steps because it, for some reason needs to read
'the +/- symbol as a Byte, then needs to read the ASCII measurement number
'the third part concatenates the data and converts it to a single type
'part 1 - read +/- symbol
Dim comBuffer As Byte()
Dim n As Integer = mySerialPort.BytesToRead 'find number of bytes in buff
comBuffer = New Byte(n - 1) {} 're-dimension storage buffer (n - 1)
mySerialPort.Read(comBuffer, 0, n) 'read data from the buffer
'part 2 - read ASCII measurement number
Dim comBuffer2 As String
comBuffer2 = mySerialPort.ReadTo(vbCr)
'part 3 - concatenate read data and convert to single type
Dim txtReading As String = Nothing
txtReading = System.Text.ASCIIEncoding.ASCII.GetString(comBuffer) & CStr(CInt(comBuffer2) / 10)
sngReading = CSng(txtReading)
'Call the update form deligate
'Visual Studio slightly changed this from the example on Microsoft's website that used a Windows Form
'I tried the code in a windows form and I get the same results
Me.Invoke(UpdateFormDeligate1) 'call the deligate
End Sub
Private Sub Invoke(updateFormDeligate1 As UpdateFormDeligate)
lblResult.Label = sngReading 'set the Result label in the ribbon to equal the received data value
'now place the data received in the active cell in the worksheet
Dim myApp As Excel.Application = Globals.ThisAddIn.Application
Dim currentCell = myApp.ActiveCell
currentCell.Value = sngReading
'advance cell to the next cell
Dim newCell = currentCell
newCell = myApp.ActiveCell.Offset(1, 0)
newCell.Select()
currentCell = newCell
'check if this was the first reading from the instrument
'if it was the first reading, then send a second read command
If measureNo = 1 Then
measureNo = 2 'make sure to change measurement number to 2 to avoid infinite loop
mySerialPort.Write(CMD) 'send command to measure to instrument
End If
End Sub
'the usage of this section changed from the Microsoft Windows Form example
'in function, the mySerialPort_DataREceived(), Invoke(), and UpdateDisplay() functions do appear to be
'working with the same results and same hangups
Private Sub UpdateDisplay()
End Sub
Private Sub btnMeasure_Click(sender As Object, e As RibbonControlEventArgs) Handles btnMeasure.Click
setupConnectCOM() 'connect to COM and send first measure command
End Sub
End Class

OutOfMemory Exception when Control.invoke

I use a working thread to pump up messages , and use a event handler on UI thread to call Control.Invoke to show them on the RichTextBox.
Now found it would generate exception after 12 hours continuous running on target machine.
Log showed that:
System.OutOfMemoryException when System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
And here's part of my codes in Main Form:
Dim __lastMessage As String = ""
Private Sub historyMessageHandler(ByVal sender As messageHandler, ByVal e As String) Handles messengerReference.MessagePoped
'prevent invoking after form closed
If (Me.IsDisposed) Then
Exit Sub
End If
Dim __thisMessage As String = e
If (__lastMessage <> __thisMessage) Then
'---------------------------
' Once this message is not equal to last mesage , post on the history panel
'---------------------------
Me.Invoke(Sub()
RichTextBoxHistory.Text += (e & vbCrLf)
'-------------------------------------------
' Discard oldest message if count reached to prevent buffer overload
'-------------------------------------------
If (RichTextBoxHistory.Lines.Length > 64) Then
RichTextBoxHistory.Text = String.Join(vbCrLf,RichTextBoxHistory.Lines, 1, RichTextBoxHistory.Lines.Count - 1)
End If
'----------------------
' keep scoll on bottom
'---------------------
textBox.SelectionStart = textBox.Text.Length
textBox.ScrollToCaret()
End Sub)
Else
'-----------------------
'redundant message found , no need to show
'-----------------------
End If
__lastMessage = __thisMessage 'memorize last message
End Sub
Until now i considered two possible issues and did some tests:
The way to remove first line on TextBox is not appropriate.
There's some side-effect about Control.Invoke i don't know before.
For the first one , i tried to raise message event once a line per 1ms in my laptop, after 24 hours testing , it still works , no exception.
For the second reason , i read How to avoid leaking handles when invoking in UI from System.Threading.Timer? , and use perfmon.exe to monitor if handle leaks , it seems works fine so far, unused handles was collected by GC for sure.
Out of ideas now , is there other issue i missed?
Found a defect on this command:
RichTextBoxHistory.Text =
String.Join(vbCrLf,RichTextBoxHistory.Lines, 1,
RichTextBoxHistory.Lines.Count - 1)
If there's multi line message coming via argument e, it would delete only the very beginning single line per event raised , so that the heap usage of RichTextBox may keep growing on this situation , of course there's possible to occurs OutOfMemory in the end.
In this post, found the useful alternative calling to keeps fixed lines on RichTextBox, it can solve this problem.
RichTextBoxHistory.Lines = RichTextBoxHistory.Lines.Skip(RichTextBoxHistory.Lines.Length - 64).ToArray()

How to get Output of a Command Prompt Window line by line in Visual Basic?

I am trying to get a command line output line by line till the end of the output but I am not able to do so. I am using it in my Form and this code executes on click of a button.
Can you tell me whats wrong with my code?
Dim proc As ProcessStartInfo = New ProcessStartInfo("cmd.exe")
Dim pr As Process
proc.CreateNoWindow = True
proc.UseShellExecute = False
proc.RedirectStandardInput = True
proc.RedirectStandardOutput = True
pr = Process.Start(proc)
pr.StandardInput.WriteLine("cd C:\sdk\platform-tools\")
pr.StandardInput.WriteLine("adb help")
Dim helpArray(20) as String
For i as Integer 1 To 7
helpArray(i) = pr.StandardOutput.ReadLine()
Next
pr.StandardOutput.Close()
The program stops responding when this code is executed.
I've done some research. adb help writes output into STDERR. So you need something like:
Dim proc As ProcessStartInfo = New ProcessStartInfo("cmd.exe")
Dim pr As Process
proc.CreateNoWindow = True
proc.UseShellExecute = False
proc.RedirectStandardInput = True
proc.RedirectStandardOutput = True
pr = Process.Start(proc)
pr.StandardInput.WriteLine("C:\sdk\platform-tools")
pr.StandardInput.WriteLine("adb help 2>&1")
pr.StandardInput.Close()
Console.WriteLine(pr.StandardOutput.ReadToEnd())
pr.StandardOutput.Close()
to catch it.
You need no 2>&1 if you call ipconfig, for example.
Do not interate over the output and do not read it! Normally you don't know how long the output (same goes for error output too) would be, so you need to prepare for an unknown length. Since you are telling the Process class, that you want to handle the standard output and the standard error by yourself, you also need to bind to the events, in this case:
OutputDataReceived
ErrorDataReceived
or to block the current process and read the complete output at once like #Dmitry Kurilo does in his answer. I find the first approach better because I do not need to wait for the process to end to see it's output. The MSDN documentation of the ProcessStartInfo.RedirectstandardError property gives a good explanation of the different possibilities with a lot of examples.
If you want to take a specific line, there are a lot of possibilities. One would be to store each output (line) in the delegate and use it later, using a List(Of String) and output the specific line when the process is done (= all output lines are present).
A possible solution could look like this:
' store error output lines
dim lines = new List(of String)
dim executable = "c:\temp\android\sdk\platform-tools\adb.exe"
dim arguments = " help"
dim process = new Process()
process.StartInfo = createStartInfo(executable, arguments)
process.EnableRaisingEvents = true
addhandler process.Exited, Sub (ByVal sender As Object, ByVal e As System.EventArgs)
Console.WriteLine(process.ExitTime)
Console.WriteLine(". Processing done.")
' output line n when output is ready (= all lines are present)
Console.WriteLine(lines(4))
end sub
' catch standard output
addhandler process.OutputDataReceived, Sub (ByVal sender As Object, ByVal e As System.Diagnostics.DataReceivedEventArgs)
if (not String.IsNullOrEmpty(e.Data))
Console.WriteLine(String.Format("{0}> {1}", DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss") ,e.Data))
end if
end sub
' catch errors
addhandler process.ErrorDataReceived, Sub (ByVal sender As Object, ByVal e As System.Diagnostics.DataReceivedEventArgs)
'Console.WriteLine(String.Format("! {0}", e.Data))
' add every output line to the list of strings
lines.Add(e.Data)
end sub
' start process
dim result = process.Start()
' and wait for output
process.BeginOutputReadLine()
' and wait for errors :-)
process.BeginErrorReadLine()
private function createStartInfo(byval executable as String, byval arguments as String) as ProcessStartInfo
dim processStartInfo = new ProcessStartInfo(executable, arguments)
processStartInfo.WorkingDirectory = Path.GetDirectoryName(executable)
' we want to read standard output
processStartInfo.RedirectStandardOutput = true
' we want to read the standard error
processStartInfo.RedirectStandardError = true
processStartInfo.UseShellExecute = false
processStartInfo.ErrorDialog = false
processStartInfo.CreateNoWindow = true
return processStartInfo
end function
Now even if the adb writes to the error output, you will be able to see it. It will also be complete.
The output in this case looks like this:
14.10.2014 12:49:10
. Processing done.
-e - directs command to the only running emulator.
Another possibility would be to put everything into one string and after the process has finished split the single string on line endings (CRLF \r\n) and you will gain the lines you want to filter.

VB.Net Sockets Invoke

I am using vb.net 2010 and I have created a program that uses sockets to transfer data between our windows server and a unix server. The code was originally from a Microsoft sample project hence my little understanding of it.
Everything was fine until I had the idea of changing the program into a service. The Invoke command is not accessable from a service. I think I understand why but more importantly how do I get around it or fix it?
' need to call Invoke before can update UI elements
Dim args As Object() = {command, data}
Invoke(_processInStream, args)
Someone please help I am desperate to finish this program so I can move on :)
Below is the rest of the class, there is a server socket class too but I didnt want to complicate things?
Public Class srvMain
' start the InStream code to receive data control.Invoke callback, used to process the socket notification event on the GUI's thread
Delegate Sub ProcessSocketCommandHandler(ByVal command As NotifyCommandIn, ByVal data As Object)
Dim _processInStream As ProcessSocketCommandHandler
' network communication
Dim WithEvents _serverPRC As New ServerSocket
Dim _encryptDataIn() As Byte
Dim myConn As SqlConnection
Dim _strsql As String = String.Empty
Protected Overrides Sub OnStart(ByVal args() As String)
' watch for filesystem changes in 'FTP Files' folder
Watch()
' hookup Invoke callback
_processInStream = New ProcessSocketCommandHandler(AddressOf ProcessSocketCommandIn)
' listen for Ultimate sending signatures
_serverPRC.Start(My.Settings.listen_port_prc)
myConn = New SqlConnection(My.Settings.Mill_SQL_Connect)
End Sub
Protected Overrides Sub OnStop()
' Add code here to perform any tear-down necessary to stop your service.
End Sub
' this is where we will break the data down into arrays
Private Sub processDataIn(ByVal data As Object)
Try
If data Is Nothing Then
Throw New Exception("Stream empty!")
End If
Dim encdata As String
' decode to string and perform split(multi chars not supported)
encdata = Encoding.Default.GetString(data)
_strsql = encdata
myConn.Open()
Dim commPrice As New SqlCommand(_strsql, myConn)
Dim resPrice As SqlDataReader = commPrice.ExecuteReader
'********************************THIS MUST BE DYNAMIC FOR MORE THAN ONE NATIONAL
If resPrice.Read = True And resPrice("ats" & "_price") IsNot DBNull.Value Then
'If resPrice("ats" & "_price") Is DBNull.Value Then
' cannot find price so error
'natPrice = ""
'natAllow = 2
'End If
natPrice = resPrice("ats" & "_price")
natAllow = resPrice("ats" & "_allow")
Else
' cannot find price so error
natPrice = ""
natAllow = 2
End If
myConn.Close()
' substring not found therefore must be a pricing query
'MsgBox("string: " & encdata.ToString)
'natPrice = "9.99"
Catch ex As Exception
ErrHandle("4", "Process Error: " + ex.Message + ex.Data.ToString)
Finally
myConn.Close() ' dont forget to close!
End Try
End Sub
'========================
'= ServerSocket methods =
'========================
' received a socket notification for receiving from Ultimate
Private Sub ProcessSocketCommandIn(ByVal command As NotifyCommandIn, ByVal data As Object)
' holds the status message for the command
Dim status As String = ""
Select Case command
Case NotifyCommandIn.Listen
'status = String.Format("Listening for server on {0} ...", CStr(data))
status = "Waiting..."
Case NotifyCommandIn.Connected
'status = "Connected to Ultimate" ' + CStr(data)
status = "Receiving..."
Case NotifyCommandIn.Disconnected
status = "Waiting..." ' disconnected from Ultimate now ready...
Case NotifyCommandIn.ReceivedData
' store the encrypted data then process
processDataIn(data)
End Select
End Sub
' called from socket object when a network event occurs.
Private Sub NotifyCallbackIn(ByVal command As NotifyCommandIn, ByVal data As Object) Handles _serverPRC.Notify
' need to call Invoke before can update UI elements
Dim args As Object() = {command, data}
Invoke(_processInStream, args)
End Sub
End Class
Any help is appreciated
Many thanks
Invoke is a member of System.Windows.Forms.Form, and it is used to make sure that a certain method is invoked on the UI thread. This is a necessity in case the method in question touches UI controls.
In this case it looks like you simply can call the method directly, i.e.
instead of
Dim args As Object() = {command, data}
Invoke(_processInStream, args)
you can simply write
ProcessSocketCommandIn(command, data)
Also, in this case you can get rid of the _processInStream delegate instance.

Don't get any data from socket when data should be there, and no exceptions raised, connection is open. Using DataAvailable to wait for data

I have a problem reading data from an RFID-reader. I connect to the reader by tcp and wait for DataAvailable to be true, then reading the data until I got a end of data character. Then I just go back and waiting for a new DataAvailable. This is done in a own thread for the function.
There seems to be some kind of timeout, If I don't got any data in a couple of minutes, then it just sits there in the do/loop waiting for DataAvailable. I hold the card to the RFID reader, it beeps, but there is no data available. I don't get any exceptions, and the information says that the clientsocket is still connected. Are there anything more I can check?
If I put the card to the reader in a minute-interval it seems as this will never occur. So 2-3 minutes idle:ing seems to do this.
Here are my code to read data from the socket, I have taken away some irrelevant code:
Sub test(ByVal ip As String, ByVal port As Integer)
' this sub is meant to run forever
Try
Dim clientSocket As New System.Net.Sockets.TcpClient()
clientSocket.Connect(ip, port)
Using serverStream As NetworkStream = clientSocket.GetStream()
Do 'loop forever or until error occur
'Every new dataentry starts here
Dim inStream(0) As Byte
Dim returndata As String = ""
Do 'loop forever
Do Until serverStream.DataAvailable 'loop until data exists to read
If clientSocket.Connected = False Then
'this will never happen.
'but if there are more than 5 minutes between data then
'it never got data again as if no data was sent.
Exit Sub
End If
Application.DoEvents()
Loop
'there is data to read, read first byte and
serverStream.Read(inStream, 0, 1)
If inStream(0) = 13 Then
'got end of data
'exit loop if reading chr 13.
returndata &= System.Text.Encoding.ASCII.GetString(inStream)
Exit Do
End If
Loop
GotData(returndata)
Loop
End Using
Catch ex As Exception
' handle error
Finally
'close connection if open
End Try
End Sub
I found out that socket.Connected only reports the status for when the last command was runned on the connection.
So in the Do Until serverStream.DataAvailable loop I used this trick to check if the connection was closed instead:
Dim test As Boolean = clientSocket.Client.Poll(10, System.Net.Sockets.SelectMode.SelectRead)
If test = True And serverStream.DataAvailable = False Then
'restart connection
End If
So now finally got control over what is happening and know that its because of the client connection is closed that I dont got any data.
So, then I figured, now that I know that the connection is closed, how do I prevent it? That was easier, just send data to the tcpip-server every 10 second and it will hold it open.
The result that works for me (this is not the production code, just an example of the solution):
Dim s As Date = Now
Do Until serverStream.DataAvailable
Dim r As Boolean = clientSocket.Client.Poll(10, System.Net.Sockets.SelectMode.SelectRead)
If DateDiff(DateInterval.Second, s, Now) > 10 Then
Dim dta(0) As Byte
dta(0) = 0
clientSocket.Client.Send(dta)
s = Now
End If
If r = True And serverStream.DataAvailable = False Then
'restart sub
Exit Sub
End If
loop
So now it doesnt even close and need to restart every x minutes.
My problems are solved and Im a happy coder. ;)