Does not receive data immediately by using readByte() in serial port - vb.net

I try to send command to a device and read data back via serial port, however it gives me a weird behavior. I have to sendData() a couple of times(most twice) to get the data. I do not want to use writeLine and I do not write a loop in my code (I have figured out already). My schema works like this. I set ReceivedByteThreshold to 1, so that each time the it receives the data from the device, the ReceivedData event handler is fired. I read one byte at a time. When it read CR, whose ascii is 13, I display the data on the label, but I have to send data (*idn?) more than once(sometimes can be done in one time but not consistent). See the pic for reference. I am not sure why it happens. Thank you in advance
Private Sub DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
Dim ret As Integer
ret = SerialPort.ReadByte()
If ((ret <> 13) And (ret <> 10)) Then
str &= System.Convert.ToChar(ret)
End If
If ret = 13 Then
ret = SerialPort.ReadByte()
If ret = 10 Then
Me.Label2.Text = str
str = ""
End If
End If
End Sub
Sub SendData(ByVal data As String)
Try
SerialPort.Write(data)
Catch ex As Exception
MsgBox("fail to send data")
End Try
End Sub
[update]
I changed my approach. I just used a Do loop but the result is still not consistent. I chose not to use timer any more but just use ReceiveData Event Handler.
Private Sub DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
Dim ret As Integer
Do
ret = SerialPort.ReadByte()
str &= System.Convert.ToChar(ret)
If ret = 13 Then
ret = SerialPort.ReadByte()
If ret = 10 Then
Exit Do
End If
End If
Loop
Me.Label2.Text = str
str = ""
End Sub
I use Docklight to test my code, you can tell sometimes it sends twice in a row before get the data back. I am not if it has race condition issue involved, thank.

Related

Serial Communication stops working when loop is started

I have problem with serial data communication if I started a loop it is not updating the data from my weighing scale. I can't figure out how to continue the communication as well as to run the loop. The logic of my code will be in the loop so I could check the value from my integer and compare it to the data from serial data (Weighing Scale)
Private Sub conWeight_DataReceived(sender As System.Object, e As System.IO.Ports.SerialDataReceivedEventArgs) Handles conWeight.DataReceived
receivedText(conWeight.ReadExisting())
End Sub
Private Sub receivedText(ByVal [text] As String)
If Me.lblWeight.InvokeRequired Then
Dim x As New SetTextCallback(AddressOf receivedText)
Me.Invoke(x, New Object() {(text)})
Else
Dim reverseString As String = [text]
Dim revString As String = StrReverse(reverseString)
Dim clean As String
clean = revString.Replace("=", "")
Me.lblWeight.Text = clean 'append text
End If
End Sub
'this is part with loop
If binWeight = 0 Then
targetweight = CInt(txtbSilo1.Text) + CInt(txtbSilo2.Text) + CInt(txtbSilo3.Text) + CInt(txtbSilo4.Text)
If CInt(txtbSilo1.Text) > 0 Then
currentWeight = CInt(txtbSilo1.Text)
frmAutomaticControl.conControl.Open()
frmAutomaticControl.conControl.Write("motr1")
frmAutomaticControl.conControl.Close()
MsgBox("check")
Do
If binWeight >= currentWeight Then
frmAutomaticControl.conControl.Open()
frmAutomaticControl.conControl.Write("moto1")
frmAutomaticControl.conControl.Close()
Exit Do
End If
Loop
Else
End If
BunifuFlatButton1.Enabled = True
Else
MsgBox("Empty The Bin")
End If
just a couple of ideas.
1. Throw that part of the code into a background worker.
2. Cheese it and throw in an application.doevents.
3. Create a global variable that'll capture the output of each iteration of your loop that'll then feed it where it needs to go.
Just one line of code is all you need. In your loop put this above everything else.
Application.DoEvents()

Streamreader not reading all lines

I am working on a little tool that allows the selection of a single file. Where it will calculate the SHA2 hash and shows it in a simple GUI then takes the value and checks if that hash is listed in a blacklist text file. If it is listed then it will flag it as dirty, and if not it will pass it as clean.
But after hitting Google for hours on end and sifting through many online sources I decided let's just ask for advise and help.
That said while my program does work I seem to run into a problem, since no matter what I do ,it only reads the first line of my "blacklist" and refuses to read the whole list or to actually go line by line to see if there is a match.
No matter if I got 100 or 1 SHA2 hash in it.
So example if I were to have 5 files which I add to the so called blacklist. By pre-calculating their SHA2 value. Then no matter what my little tool will only flag one file which is blacklisted as a match.
Yet the moment I use the reset button and I select a different (also blacklisted) file, it passes it as clean while its not. As far as I can tell it is always the first SHA2 hash it seems to flag and ignoring the others. I personally think the program does not even check beyond the first hash.
Now the blacklist file is made up very simple.
*example:
1afde1cbccd2ab36f90973cb985072a01ebdc64d8fdba6a895c855d90f925043
2afde1cbccd2ab36f90973cb985072a01ebdc64d8fdba6a895c855d90f925043
3afde1cbccd2ab36f90973cb985072a01ebdc64d8fdba6a895c855d90f925043
4afde1cbccd2ab36f90973cb985072a01ebdc64d8fdba6a895c855d90f925043
....and so on.
So as you can see these fake example hashes are listed without any details.
Now my program is suppose to calculate the hash from a selected file.
Example:
somefile.exe (or any extension)
Its 5KB in size and its SHA2 value would be:
3afde1cbccd2ab36f90973cb985072a01ebdc64d8fdba6a895c855d90f925043
Well as you can see I took the third hash from the example list right?
Now if I select somefile.exe for scanning then it will pass it as clean. While its blacklisted. So if I move this hash to the first position. Then my little program does correctly flag it.
So long story short I assume that something is horrible wrong with my code, even though it seems to be working.
Anyway this is what I got so far:
Imports System.IO
Imports System.Security
Imports System.Security.Cryptography
Imports MetroFramework.Forms
Public Class Fsmain
Function SHA256_SIG(ByVal file_name As String)
Return SHA256_engine("SHA-256", file_name)
End Function
Function SHA256_engine(ByRef hash_type As String, ByRef file_name As String)
Dim SIG
SIG = SHA256.Create()
Dim hashValue() As Byte
Dim filestream As FileStream = File.OpenRead(file_name)
filestream.Position = 0
hashValue = SIG.ComputeHash(filestream)
Dim hash_hex = PrintByteArray(hashValue)
Stream.Null.Close()
Return hash_hex
End Function
Public Function PrintByteArray(ByRef array() As Byte)
Dim hex_value As String = ""
Dim i As Integer
For i = 0 To array.Length - 1
hex_value += array(i).ToString("x2")
Next i
Return hex_value.ToLower
End Function
Private Sub Browsebutton_Click(sender As Object, e As EventArgs) Handles Browsebutton.Click
If SampleFetch.ShowDialog = DialogResult.OK Then
Dim path As String = SampleFetch.FileName
Selectfile.Text = path
Dim Sample As String
Sample = SHA256_SIG(path)
SignatureREF.Text = SHA256_SIG(path)
Using f As System.IO.FileStream = System.IO.File.OpenRead("blacklist.txt")
Using s As System.IO.StreamReader = New System.IO.StreamReader(f)
While Not s.EndOfStream
Dim line As String = s.ReadLine()
If (line = Sample) Then
Result.Visible = True
SignatureREF.Visible = True
Result.Text = "Dirty"
Resetme.Visible = True
RemoveMAL.Visible = True
Else
Result.Visible = True
SignatureREF.Visible = True
Result.Text = "Clean"
Resetme.Visible = True
RemoveMAL.Visible = False
End If
End While
End Using
End Using
End If
End Sub
Private Sub Fsmain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Result.Visible = False
SignatureREF.Visible = False
Resetme.Visible = False
RemoveMAL.Visible = False
End Sub
Private Sub Resetme_Click(sender As Object, e As EventArgs) Handles Resetme.Click
Selectfile.Text = Nothing
SignatureREF.Text = Nothing
Result.Visible = False
SignatureREF.Visible = False
Resetme.Visible = False
RemoveMAL.Visible = False
End Sub
Private Sub RemoveMAL_Click(sender As Object, e As EventArgs) Handles RemoveMAL.Click
Dim ask As MsgBoxResult = MsgBox("Would you like to remove the Dirty file?", MsgBoxStyle.YesNo, MessageBoxIcon.None)
If ask = MsgBoxResult.Yes Then
System.IO.File.Delete(Selectfile.Text$)
Else
MsgBox("You sure you want to keep this file?")
Dim filepath As String = IO.Path.Combine("c:\Dirty\", "Dirty.txt")
Using sw As New StreamWriter(filepath)
sw.WriteLine(" " & DateTime.Now)
sw.WriteLine(" " & Selectfile.Text)
sw.WriteLine(" " & SignatureREF.Text)
sw.WriteLine(" " & Result.Text)
sw.WriteLine("-------------------")
sw.Close()
End Using
End If
End Sub
End Class
So if any of you guys can have a look at it and point out errors, or even can come up with a fix that would be great.
The simplest thing you can do to make your procedure working, is testing whether a defined condition is verified. Terminate the test if that condition is met.
Using a boolean variable, report the result of the test and take action accordingly.
The Using statement takes care of disposing the StreamReader.
You could modify you procedure this way:
Private Sub Browsebutton_Click(sender As Object, e As EventArgs) Handles Browsebutton.Click
If SampleFetch.ShowDialog <> DialogResult.OK Then Exit Sub
Dim sample As String = SHA256_SIG(SampleFetch.FileName)
SignatureREF.Text = sample
Dim isDirty As Boolean = False
Using reader As StreamReader = New StreamReader("blacklist.txt", True)
Dim line As String = String.Empty
While reader.Peek() > 0
line = reader.ReadLine()
If line = sample Then
isDirty = True
Exit While
End If
End While
End Using
If isDirty Then
'(...)
RemoveMAL.Visible = True
Result.Text = "Dirty"
Else
'(...)
RemoveMAL.Visible = False
Result.Text = "Clean"
End If
End Sub
If you have a String and you want to test whether it matches a line of a text file then you can use this simple one-liner:
If IO.File.ReadLines(filePath).Contains(myString) Then

Speeding up "Old" string parsing methodes using PLINQ or Async/Await or TPL, etc

As (hopefully) all developers, I've developed over the years and am now at the point that OOP and functional programming is a daily costs. Recently I've bumped into and "old" (2 years) DLL of my that reads in a TCP stream, drops the incoming string (which end with a line feed) into a ConcurrentQueue(Of String). Then a second Backgroundworker loops over the ConcurrentQueue(Of String) and dequeue's the string and parses it.
Depending on the request command i send to the TCP server i gat get one line or several hunders of line. Also when nothing is asked the TCP server send's nothing. It is completely event driven.
With the new and efficient Technics of PLINQ, Async/Await and TPL i am determine to make this faster/more efficient. Currently i'm brainstorming on how to do this and am asking you to think with me or give some good advice.
So let's dig deeper into what i have now:
It al starts with two Backgroundworkers, One for Reading and One for Parsing.
The Reading Backgroundworker:
This backgroundworker job is to read the incomming data and make sure to keep reading until a Linefeed is passed. Then it cuts into a string and drops it into a ConcurrentQueue(Of String). The code looks like this:
Private Sub bgwReceive_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgwTCP_Receive.DoWork
Dim strRXData As String = String.Empty
Dim intLfpos As Integer = 0
Try
Do Until bgwTCP_Receive.CancellationPending
'while the client is connected, continue to wait for and read data
While _Client.Connected AndAlso _ClientStream.CanRead
Dim buffer(_Client.ReceiveBufferSize - 1) As Byte
Dim read As Integer = _ClientStream.Read(buffer, 0, buffer.Length)
'Put the new data in the queue
If read > 0 Then
'Add incomming data to the buffer string
strRXData &= Text.Encoding.ASCII.GetString(buffer, 0, read)
'Replace "0 padding"
strRXData.Replace(Chr(0), String.Empty)
'Now check if we have a Linefeed in our buffer string
While strRXData <> String.Empty
'Get the position of the first linefeed
intLfpos = strRXData.IndexOf(vbLf)
'Now check if we find something.
If intLfpos < 0 Then Exit While
'There is a line feed, it is time to add it to the incomming data queue
IncommingData.Enqueue(strRXData.Substring(0, intLfpos - 1))
'Now remove the command from the buffer string
strRXData = strRXData.Substring(intLfpos + 1, strRXData.Length - intLfpos - 1)
End While
End If
'Check if we have to stop
If bgwTCP_Receive.CancellationPending Then e.Cancel = True : Exit Do
Threading.Thread.Sleep(1)
End While
Threading.Thread.Sleep(100)
Loop
e.Cancel = True
Catch op As OperationCanceledException
Throw New Exception("TCP Reading cancelled")
Catch se As SocketException
Throw New Exception("Socket unknown error. Native error code: " & se.NativeErrorCode)
Catch IOex As IOException
Throw New Exception("Socket timeout while receiving data from [" & TCP_ServerIP.ToString & "]")
End Try
End Sub
The Parsing Backgroundworker: This backgroundworker job is to parse the strings in the ConcurrentQueue(Of String). In this backgroundworker there is a big Select case that looks at the first word in the string. This determines what to do.
The received protocol is very simple but unfortunately it hasn't a sollid structure like JSON. A string would look like this: IND PARM1:"Hello world!" PARM2:1.4.8 \CrLf
As you can see it is very simple and plain. The IND indicate the Command, like VER is for VERSION string. IND is always 3 chars long. Then comes the parameters. The parameter name is always 4 chars long. If it is between double quotes it is a string, else its something like a double/integer/IP address (X.X.X.X). Keep in mind that original it is a string. In my parser i parse it to the object properties. The code looks like this:
Private Sub bgwProcessQueue_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgwProcessQueue.DoWork
Dim strNewLine As String = String.Empty
Dim strSubLine As String = String.Empty
Try
'Loop until program has stopped
Do Until bgwProcessQueue.CancellationPending
'Only process when something is in the queue
If TCP_Connection.IncommingData.TryDequeue(strNewLine) Then
'If Backgroundworker has to cancel than cancel
If bgwProcessQueue.CancellationPending Then Exit Do
Select Case True
Case strNewLine.StartsWith("VER") 'Example: VER PRM1:1.1 PRM2:"Hi there" PRM3:1.1.1 PRM4:8/0 PRM5:8
'First check if all the needed values are there, if not resend the command
If strNewLine.Contains("PRM1:") AndAlso strNewLine.Contains("PRM2:") AndAlso strNewLine.Contains("PRM3:") AndAlso strNewLine.Contains("PRM4:") AndAlso strNewLine.Contains("PRM5:") Then
'Get versions and devicename
Me.Param1 = GetSubstring(strNewLine, "PRM1:", " ")
Me.Param2 = GetSubstring(strNewLine, "PRM2:", " ")
Me.Param3 = GetSubstring(strNewLine, "PRM3:", " ")
Me.Param4 = GetSubstring(strNewLine, "PRM4:", " ")
Me.Param5 = GetSubstring(strNewLine, "PRM5:", " ")
Else
'Commando was invalid, resend the command
Log.Message(LogLevel.Warning, "VER Messages was incompleet, resending VER command")
SendData("VER")
End If
Case strNewLine.StartsWith("ERROR")
Log.Message(LogLevel.Warning, strNewLine)
End Select
End If
'Clear any old data
strNewLine = String.Empty
'Sleep
Threading.Thread.Sleep(1)
Loop
e.Cancel = True
Catch ex As Exception
Log.Exception(ex)
End Try
End Sub
Private Function GetSubstring(text As String, StartValue As String, EndValue As String) As String
Try
'If we can't find the Start value we can't do anything
If Not text.Contains(StartValue) Then Return String.Empty
'Find the index of the Start and End value
intStartValueIndex = text.IndexOf(StartValue) + StartValue.Length
intEndValueIndex = text.IndexOf(EndValue, intStartValueIndex)
'If no Endvalue index was found then get the end of the string
If intEndValueIndex < intStartValueIndex Then intEndValueIndex = text.Length
'Return the substring en remove quetes
Return text.Substring(intStartValueIndex, intEndValueIndex - intStartValueIndex).Replace("""", "")
Catch ex As Exception
Log.Exception(ex)
Return String.Empty
End Try
End Function
Hopefully all above is understandable. I was thinking of looping through the ConcurrentQueue(Of String) with a Parrallel.ForEach Wich passes the Dequeued string into a sub that does the parsing. At the reading side, my inspiration is empty at the moment...

Change label text in foreach loop

i want to update a label while a foreach-loop.
The problem is: the program waits until the loop is done and then updates the label.
Is it possible to update the label during the foreach-loop?
Code:
Dim count as Integer = 0
For Each sFile as String in Files
'ftp-code here, works well
count = count+1
progressbar1.value = count
label1.text = "File " & count & " of 10 uploaded."
next
Thanks in advance
Label is not updated because UI thread is blocked while executing your foreach loop.
You can use async-await approach
Private Async Sub Button_Click(sender As Object, e As EventArgs)
Dim count as Integer = 0
For Each sFile as String in Files
'ftp-code here, works well
count = count+1
progressbar1.value = count
label1.text = "File " & count & " of 10 uploaded."
Await Task.Delay(100)
Next
End Sub
Because you will work with Ftp connections, which is perfect candidate for using async-await.
The Await line will release UI thread which will update label with new value, and continue from that line after 100 milliseconds.
If you will use asynchronous code for ftp connection , then you don't need Task.Delay
You've already accepted an answer but just as an alternative a BackgroundWorker can also be used for something like this. In my case the FTP to get the original files happens very quickly so this snippet from the DoWork event is for downloading those files to a printer.
Dim cnt As Integer = docs.Count
Dim i As Integer = 1
For Each d As String In docs
bgwTest.ReportProgress(BGW_State.S2_UpdStat, "Downloading file " & i.ToString & " of " & cnt.ToString)
Dim fs As New IO.FileStream(My.Application.Info.DirectoryPath & "\labels\" & d, IO.FileMode.Open)
Dim br As New IO.BinaryReader(fs)
Dim bytes() As Byte = br.ReadBytes(CInt(br.BaseStream.Length))
br.Close()
fs.Close()
For x = 0 To numPorts - 1
If Port(x).IsOpen = True Then
Port(x).Write(bytes, 0, bytes.Length)
End If
Next
If bytes.Length > 2400 Then
'these sleeps are because it is only 1-way comm to printer so I have no idea when printer is ready for next file
System.Threading.Thread.Sleep(20000)
Else
System.Threading.Thread.Sleep(5000)
End If
i = i + 1
Next
In the ReportProgress event... (of course, you need to set WorkerReportsProgress property to True)
Private Sub bgwTest_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles bgwTest.ProgressChanged
Select Case e.ProgressPercentage
'BGW_State is just a simple enum for the state,
'which determines which UI controls I need to use.
'Clearly I copy/pasted from a program that had 15 "states" :)
Case BGW_State.S2_UpdStat
Dim s As String = CType(e.UserState, String)
lblStatus.Text = s
lblStatus.Refresh()
Case BGW_State.S15_ShowMessage
Dim s As String = CType(e.UserState, String)
MessageBox.Show(s)
End Select
End Sub
Is it not enough to use Application.DoEvents()? This clears the build up and you should be able to see the text fields being updated very quickly.

Read and Write from Host to Slave through Serial Port in vb.net, Unable to stop program, break loop, without quitting

I have written code to read and write to a serial port. I need it to indefinitely loop, I have been unable to figure a way to place a button to stop the loop, it will stop if there are errors.
I have searched the internet and have tried people's suggestions, but yet to get one actually work for me.
I am also unsure if the display data is updating frequently enough. This is my first time to use shapes.
I am still new to all this.
'this code has no provision to stop voluntarily, apart from quitting the program.
Imports System.IO.Ports
Class form1
'==CONTROL CHARACTERS- as per spec==
'==start and stop values==
Dim STX As Byte = &H2
Dim ETX As Byte = &H3
'==Read==
Dim read As String = "R"
'==Acknowledgment==
Dim ACK As Byte = &H6
'==class and address==
Dim DeviceClass As String = "E"
Dim DeviceAddress As String = "1"
'==Host Commane==
Dim hostCommand As String
'==STX E 1 R REG1 REG0 ETX==
'==Command to read==
Dim readSlave As String
'==STX E 1 ACK REG1 REG0 D1 D0 ETX==
'==array of register values==
Dim REG = New String() {"22", "23", "2F", "30"}
'==set and open port==NB not using get portname- COM1 to be used as dedicated port as spec==
Private Sub btnStartReset_Click(sender As Object, e As EventArgs) Handles btnStartReset.Click
If SerialPort1.IsOpen = False Then
'==Open and set COM1 as host==
Try
'==Set COM1 as portname==
SerialPort1.PortName = "COM1"
'==Port settings==
SerialPort1.BaudRate = 9600
SerialPort1.Parity = Parity.None
SerialPort1.StopBits = StopBits.One
SerialPort1.DataBits = 8
SerialPort1.ReadTimeout = 100
'==Open port==
SerialPort1.Open()
rtbCom1.Text = "COM1 Ready"
tmrPoll.Start()
Catch ex As Exception
rtbCom1.Text = "open error " & ex.Message
End Try
End If
End Sub
Private Sub tmrPoll_Tick(sender As Object, e As EventArgs) Handles tmrPoll.Tick
'==timeout error counter==
Dim i As Integer = 0
'==Prevent unnecessary timeout errors/allow time lag for port to open==
Do While SerialPort1.IsOpen = True
'==Loop through Register==
For Each register In REG
'==STX E 1 R REG1 REG0 ETX==
hostCommand = (STX & DeviceClass & DeviceAddress & read & register & ETX)
Try
'==Loop Host Commands for Register==
SerialPort1.WriteLine(hostCommand)
Catch ex As Exception
rtbCom1.Text = "Write Error: " & ex.Message
End Try
Try
'==readline to separate data==
readSlave = SerialPort1.ReadLine()
'==display data in GUI==
lst1.Items.Add(readSlave)
'==Get Register Value==
'==STX E 1 ACK REG1 REG0 D1 D0 ETX==
'==2-E-1-6-R-R-D-D-3==
'==Get the Data Value for Individual Register==
Dim reg = readSlave.Substring(4, 2)
'==convert data to integer, so data can be displayed graphically==
Dim D1 = CInt(readSlave.Substring(6, 1))
Dim D0 = CInt(readSlave.Substring(7, 1))
'==Display received substring values==
Select Case reg
Case Is = "22"
'list box until advised.
lst1.Items.Add(reg & D1 & D0)
Case Is = "23"
'==display data as shape==
'==0-100==
shpTemp.Width = (D1 + D0)
Case Is = "2F"
'==0-5==
shpAmp.Width = (D1 + D0) * 20
Case Is = "30"
'==0-40==
shpVolt.Width = (D1 + D0) * 2.5
End Select
Catch ex As Exception
rtbCom1.Text = "Read error: " & ex.Message
i += 1
End Try
If i > 2 Then
rtbCom1.Text = "Operation Aborted: 3 timeout errors."
'==Stop program if 3 timeout errors- as spec/closed port==
SerialPort1.Close()
rtbCom1.Text = "port closed - Operation Aborted: 3 timeout errors."
shpAmp.Width = 1
shpTemp.Width = 1
shpVolt.Width = 1
tmrPoll.Stop()
Exit Do
End If
Next
Loop
End Sub
End Class
With WinForm projects, the UI is single-threaded. The message loop (which processes incoming messages from the OS, such as button clicks) runs on the same thread as the UI event handlers, such as your tmrPoll_Tick event handler method. Therefore, until your event handler from one UI event exits, the message loop will not process the next OS message. Since that is the case, if you sit in an infinite loop in tmrPoll_Tick, it will completely lock up the UI because it will block the message loop from processing any more messages.
For that reason, as a rule, in WinForm projects, you should never create an infinite or long-running loop which runs in a UI event handler. You need to either redesign your code so it is more event-driven (doing one piece of work at a time in a recurring event), or you need to run the loop in a separate thread so that it doesn't block the UI thread. If you want to do it with a separate thread, a popular option is to use the BackgroundWorker component, which you will find in your form-designer tool box.