none
Timer and threading RRS feed

  • Question

  • I created a small application to take measurements with an instrument conneted to the RS-232 port.

    As you can see from the screen capture below, all I do is to take a measurement in response to the Measure button being clicked. That part works great. But I never know how long the measurement will take? That depends on the amount of light being measured. Low lights require more time than bright lights, anywhere from 10 secondes to 8 minutes.

    As you can see, I have a label called "Time". Would it be possible to "update" this label while the measurement is going on? So the user does not have the impression that the application is hung or unresponsive?

    Saturday, December 14, 2019 4:09 PM

Answers

  • Leslie and Peter and Tommy,

    Thanks to ALL your kind help and patience, I think I begin to see the light at the end of the tunnel. This is the UI of my new test program:

    The time shows up in seconds, at the bottom of the window.
    This is exactly what I had in mind, nothing more complicated.
    The idea was not to have the program appear "hung" or "crashed" while the instrument is making a measurement.

    This is the code I have so far (thanks to all the clues you guys generously gave me throughout this conversation!):

    ' Serial Port Interfacing With VB.net 2010 Express Edition
    'Copyright (C) 2010 Richard Myrick T. Arellaga
    
    Imports System
    Imports System.ComponentModel
    Imports System.Threading
    Imports System.IO.Ports
    Public Class Form1
        Dim startTime As Date = DateTime.Now()
        Dim WithEvents myPort2 As New IO.Ports.SerialPort("COM5") With {
                .BaudRate = 9600,
                .DataBits = 8,
                .StopBits = IO.Ports.StopBits.One,
                .Parity = IO.Ports.Parity.None,
                .Handshake = IO.Ports.Handshake.RequestToSend,
                .RtsEnable = True,
                .DtrEnable = True,
                .ReadTimeout = 1000000, ' 1,000,000 milisecondes = 1,000 secondes
                .WriteTimeout = 10000
                }
    
        Delegate Sub SetTextCallback(ByVal [text] As String) 'Added to prevent threading errors during receiving of data
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            lblRS232PortStatus.Text = "COM5 close"
            btnCloseRS232.Enabled = False 'Initially Disconnect Button is Disabled
        End Sub
    
        Private Sub OpenRS232_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOpenRS232.Click
            Try
                myPort2.Open()
                If myPort2.IsOpen Then
                    lblRS232PortStatus.Text = "COM5 Open"
                End If
            Catch ex As Exception
                lblRS232PortStatus.Text = "COM5 Problem:" & ex.ToString()
            End Try
    
            btnOpenRS232.Enabled = False
            btnCloseRS232.Enabled = True
        End Sub
    
        Private Sub CloseRS232_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCloseRS232.Click
            myPort2.Close()
            btnOpenRS232.Enabled = True
            btnCloseRS232.Enabled = False
            lblRS232PortStatus.Text = "COM5 close"
        End Sub
    
        Private Sub SendCommand_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSendCommand.Click
    
            tbPR705response.Text = ""
    
            If Not myPort2.IsOpen Then
                myPort2.Open()
                lblRS232PortStatus.Text = "COM5 Open"
            End If
    
            startTime = DateTime.Now()
    
            Timer1.Start()
            myPort2.WriteLine(tbPR705Command.Text)
    
        End Sub
    
        Private Sub myPort2_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles myPort2.DataReceived
            'Automatically called every time data is received at the serialPort
            ReceivedText(myPort2.ReadExisting())
            Timer1.Stop()
        End Sub
        Private Sub ReceivedText(ByVal [text] As String)
    
            'compares the ID of the creating Thread to the ID of the calling Thread
            If Me.tbPR705response.InvokeRequired Then
                Dim x As New SetTextCallback(AddressOf ReceivedText)
                Me.Invoke(x, New Object() {(text)})
            Else
                Me.tbPR705response.Text &= [text]
            End If
        End Sub
    
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    
            Dim diff As TimeSpan = DateTime.Now - startTime
            lblTime.Text = diff.ToString("mm\:ss\:ff") & " seconds"
    
        End Sub
    End Class
    
    
    
    I don't mind leaving the fact where I got part of this code, which I extensively modified and adapted, from  https://www.codeproject.com/questions/183627/how-to-receive-data-from-serial-port-rs232-in-vb-n

    In fairness, I don't understand the role played by all the instructions in this code but sufficiently that I can move along. I better understand what a delegate is, the idea of threads, which were unknown to me, before I embarked on this project.

    Hope I can pick you guys's brains in the future :-)


    • Marked as answer by roger.breton Monday, December 16, 2019 1:26 AM
    • Edited by roger.breton Monday, December 16, 2019 1:26 AM
    Monday, December 16, 2019 1:26 AM

All replies

  • Hi

    As you did not show any code, here is my interpretation of one possible solution.

    This emulates your data collection start with a click on Button1 and emulates data collection completed with second click on Button1. In your case, I would assume you have a certain point in your code where you can insert a call to StartCollection and when data collection is comped, a call to FinishCollection. In this example, I use a Button, but in your case, the Button clicks would be replaced by those calls toStartCollection and FinishCollection.

    Here is code for a stand alone example

    ' Example with Form1, Label1
    ' Button1 and Timer1
    Option Strict On
    Option Explicit On
    Public Class Form1
        ' for counter Label display
        Dim counter As Integer = 1
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Timer1.Interval = 1000 ' one second
            Button1.Text = "START"
        End Sub
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Select Case Button1.Text
                Case "START"
                    StartCollection()
                Case Else
                    FinishCollection()
            End Select
        End Sub
        Sub StartCollection()
            ' emulate data collection START
            Timer1.Enabled = True
            counter = 1
            Button1.Text = "STOP"
            Label1.Text = "0"
        End Sub
        Sub FinishCollection()
            ' emulate data collection STOP
            Button1.Text = "START"
            Timer1.Enabled = False
        End Sub
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
            Label1.Text = counter.ToString
            counter += 1
        End Sub
    End Class
    When I posted this reply, the code was in a code block but appeared as plain text!



    Regards Les, Livingston, Scotland



    • Proposed as answer by tommytwotrain Saturday, December 14, 2019 5:11 PM
    • Edited by leshay Saturday, December 14, 2019 5:33 PM
    Saturday, December 14, 2019 4:53 PM
  • Here is a sample similar to what Leshay has shown. A list is made and basically just redraw the chart each time you have new data.

    This uses the chart control and the windows forms timer. You might have some timing from your com port etc?

    Imports System.Windows.Forms.DataVisualization.Charting
    Public Class Form3
        Private WithEvents timer1 As New System.Windows.Forms.Timer With {.Interval = 100}
        Private DataPoints As New List(Of Point)
        Private Rand1 As New Random
        Private LastX As Integer
    
        Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Text = "Sample Chart"
            Button1.Text = "Start"
    
            With Chart1.ChartAreas(0)
                .AxisX.Title = "X"
                .AxisX.MajorGrid.LineColor = Color.LightBlue
                .AxisX.Minimum = 0
                .AxisY.Title = "Y"
                .AxisY.MajorGrid.LineColor = Color.LightGray
                .AxisY.Minimum = 0
    
                .BackColor = Color.FloralWhite
                .BackSecondaryColor = Color.White
                .BackGradientStyle = GradientStyle.HorizontalCenter
                .BorderColor = Color.Blue
                .BorderDashStyle = ChartDashStyle.Solid
                .BorderWidth = 1
                .ShadowOffset = 2
            End With
    
            DrawChart()
    
        End Sub
    
        Private Sub DrawChart()
            Chart1.Series.Clear()
            Chart1.Series.Add("Y = f(x)")
            Chart1.Series(0).ChartType = DataVisualization.Charting.SeriesChartType.Line
            Chart1.Series(0).BorderWidth = 2
            Chart1.Series(0).IsVisibleInLegend = False
    
            For i = 0 To DataPoints.Count - 1
                Chart1.Series(0).Points.AddXY(DataPoints(i).X, DataPoints(i).Y)
            Next
    
        End Sub
    
        Private Sub timer1_Tick(sender As Object, e As EventArgs) Handles timer1.Tick
            LastX += Rand1.Next(0, 10)
            Dim y As Integer = 20 + (10 * Math.Sin(LastX / 20))
            DataPoints.Add(New Point(LastX, y))
            DrawChart()
        End Sub
    
        Private Sub Button1_MouseDown(sender As Object, e As MouseEventArgs) Handles Button1.MouseDown
            'start 
            Button1.Text = "Stop"
            DataPoints.Clear()
            LastX = 0
            timer1.Start()
        End Sub
    
        Private Sub Button1_MouseUp(sender As Object, e As MouseEventArgs) Handles Button1.MouseUp
            'stop 
            Button1.Text = "Start"
            timer1.Stop()
        End Sub
    End Class

    Saturday, December 14, 2019 5:11 PM
  • Hi,
    try this little demo:

    Imports System.Threading
    
    Public Class Form1
      Private WithEvents btnStart As New Button With {.Text = "Start", .Dock = DockStyle.Top, .Margin = New Padding(5)}
      Private WithEvents btnStop As New Button With {.Text = "Stop", .Dock = DockStyle.Top, .Margin = New Padding(5)}
      Private pan As New Panel With {.Dock = DockStyle.Fill, .Margin = New Padding(5)}
      Private lbl As New Label With {.Text = "stopped", .Dock = DockStyle.Bottom, .Margin = New Padding(5)}
      Private Sub Form54_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.Controls.AddRange(New Control() {pan, lbl, btnStop, btnStart})
      End Sub
    
      Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
        StartDrawing()
      End Sub
    
      Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
        StopTimer()
      End Sub
    
      Private Async Sub StartDrawing()
        StartTimer()
        Dim gr = pan.CreateGraphics
        gr.Clear(Color.White)
        For i = 1 To Me.Width
          gr.DrawLine(Pens.Blue, New Point(i, CType((pan.Height - 2) / 2 * (1 + Math.Sin(i / 10)), Integer)),
                            New Point(i + 1, CType((pan.Height - 2) / 2 * (1 + Math.Sin(i / 10 + 0.1)), Integer)))
          Await Task.Delay(50)
          If t Is Nothing Then Exit Sub
        Next
        StopTimer()
      End Sub
    
      Private t As Timer = Nothing
    
      Private Sub StartTimer()
        If t IsNot Nothing Then Exit Sub
        Dim startTime = Now
        t = New Timer(Sub(state As Object)
                        lbl.Invoke(Sub()
                                     lbl.Text = (Now - startTime).TotalSeconds.ToString
                                   End Sub)
                      End Sub, t, 0, 100)
      End Sub
    
      Private Sub StopTimer()
        t.Dispose()
        t = Nothing
        lbl.Text = "stopped"
      End Sub
    
    End Class


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Saturday, December 14, 2019 5:14 PM
  • Thank you so much Leslie.

    I pasted your code in a new WinForm application and, the way you have it, it "works", I can see the label "incremeting" as each second passes. *BUT* if I place my code into StartCollection then the counter stops incrementing for the duration of the measurement. Here is your code amended with the code I current use:

    Option Strict On
    Option Explicit On

    Imports System.IO.Ports

    Public Class Form1

        Dim myPort As New IO.Ports.SerialPort("COM5") With {
                .BaudRate = 9600,
                .DataBits = 8,
                .StopBits = IO.Ports.StopBits.One,
                .Parity = IO.Ports.Parity.None,
                .Handshake = IO.Ports.Handshake.RequestToSend,
                .RtsEnable = True,
                .DtrEnable = True,
                .ReadTimeout = 1000000,
                .WriteTimeout = 10000
                }

        ' for counter Label display
        Dim counter As Integer = 1
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Timer1.Interval = 1000 ' one second
            Button1.Text = "START"
        End Sub
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Select Case Button1.Text
                Case "START"
                    StartCollection()
                Case Else
                    FinishCollection()
            End Select
        End Sub
        Sub StartCollection()

            ' emulate data collection START
            Timer1.Enabled = True
            counter = 1
            Button1.Text = "STOP"
            Label1.Text = "0"

            ' Roger Breton Code to communicate with the instrument through the RS-232 port
            Dim Incoming As String

            Try
                myPort.Open()

                myPort.WriteLine("PR705")
                Incoming = myPort.ReadLine()    ' Incoming = " REMOTE MODE" & vbCr

                If Incoming.Contains("REMOTE MODE") Then
                    myPort.WriteLine("M1")
                    Incoming = myPort.ReadLine()
                    FinishCollection()
                End If

            Catch ex As Exception
                MessageBox.Show("Serial Port Error : " & ex.ToString())
            Finally
                myPort.Close()
            End Try

        End Sub
        Sub FinishCollection()
            ' emulate data collection STOP
            Button1.Text = "START"
            Timer1.Enabled = False
        End Sub
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
            Label1.Text = counter.ToString
            counter += 1
        End Sub
    End Class

    Saturday, December 14, 2019 6:39 PM
  • Hi Tommy, I tried your code and it works BUT #1, I don't see a label for displaying the time? #2 I can't say whether the time is going to run concurrently with the animation of the chart?
    Saturday, December 14, 2019 6:52 PM
  • Hallo Peter -- wie get es dir!

    I tried your code and ich habe eine fehler when I try to run (F5 key):

    Error BC30516 Overload resolution failed because no accessible 'New' accepts this number of arguments.

    Die fehler is here:

    In case you want to know, here is the exact code I paste in VisualStudio 2019:

    Public Class Form1

        Private WithEvents btnStart As New Button With {.Text = "Start", .Dock = DockStyle.Top, .Margin = New Padding(5)}
        Private WithEvents btnStop As New Button With {.Text = "Stop", .Dock = DockStyle.Top, .Margin = New Padding(5)}
        Private pan As New Panel With {.Dock = DockStyle.Fill, .Margin = New Padding(5)}
        Private lbl As New Label With {.Text = "stopped", .Dock = DockStyle.Bottom, .Margin = New Padding(5)}
        Private Sub Form54_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Me.Controls.AddRange(New Control() {pan, lbl, btnStop, btnStart})
        End Sub

        Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
            StartDrawing()
        End Sub

        Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
            StopTimer()
        End Sub

        Private Async Sub StartDrawing()
            StartTimer()
            Dim gr = pan.CreateGraphics
            gr.Clear(Color.White)
            For i = 1 To Me.Width
                gr.DrawLine(Pens.Blue, New Point(i, CType((pan.Height - 2) / 2 * (1 + Math.Sin(i / 10)), Integer)),
                            New Point(i + 1, CType((pan.Height - 2) / 2 * (1 + Math.Sin(i / 10 + 0.1)), Integer)))
                Await Task.Delay(50)
                If t Is Nothing Then Exit Sub
            Next
            StopTimer()
        End Sub

        Private t As Timer = Nothing

        Private Sub StartTimer()
            If t IsNot Nothing Then Exit Sub
            Dim startTime = Now
            t = New Timer(Sub(state As Object)
                              lbl.Invoke(Sub()
                                             lbl.Text = (Now - startTime).TotalSeconds.ToString
                                         End Sub)
                          End Sub, t, 0, 100)
        End Sub

        Private Sub StopTimer()
            t.Dispose()
            t = Nothing
            lbl.Text = "stopped"
        End Sub
    End Class


    • Edited by roger.breton Saturday, December 14, 2019 7:02 PM Forgot to take out the last two lines
    Saturday, December 14, 2019 6:58 PM
  • Peter,

    I checked the Timer class on :

    https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer?view=netframework-4.8#constructors

    And tried to create a new Timer object in my code and got the following:

    Is it possible you have two (zwei) nested class declaration ? :

    t = New Timer(Sub(state As Object)
                        lbl.Invoke(Sub()
                                     lbl.Text = (Now - startTime).TotalSeconds.ToString
                                   End Sub)
                      End Sub, t, 0, 100)

    I get the idea of the Invoke statement which is used to access UI controls from another thread? 

    Saturday, December 14, 2019 7:11 PM
  • Hi

    I do not have a serial port here to test with, but here is some code that uses a BackGroundWorker to do the 'Timing' (no Timer component needed,)

    Can you try this in place of the code I previously posted and see if it helps.

    I saw from your own code that the reading of serial port is tying up the thread and this*might*  work.

    ' Example with Form1, Label1
    ' Button1 and BackGroundWorker1
    Option Strict On
    Option Explicit On
    Public Class Form1
    	Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    		Button1.Text = "START"
    		BackgroundWorker1.WorkerSupportsCancellation = True
    	End Sub
    	Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    		Select Case Button1.Text
    			Case "START"
    				StartCollection()
    			Case Else
    				FinishCollection()
    		End Select
    	End Sub
    	Sub StartCollection()
    		' emulate data collection START
    		Button1.Text = "STOP"
    		BackgroundWorker1.RunWorkerAsync()
    	End Sub
    	Sub FinishCollection()
    		' emulate data collection STOP
    		Button1.Text = "START"
    		BackgroundWorker1.CancelAsync()
    	End Sub
    
    	Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    
    		Dim sw As New Stopwatch
    		sw.Start()
    		Do
    			Try
    				Invoke(Sub() Label1.Text = CInt(sw.ElapsedMilliseconds / 1000).ToString)
    			Catch ex As ObjectDisposedException
    				' used if BGW finds itself in
    				' a state of 'disposed'
    				Exit Do
    			End Try
    		Loop Until BackgroundWorker1.CancellationPending
    		sw.Stop()
    	End Sub
    End Class


    Regards Les, Livingston, Scotland


    • Edited by leshay Saturday, December 14, 2019 7:37 PM
    Saturday, December 14, 2019 7:35 PM
  • Hi Tommy, I tried your code and it works BUT #1, I don't see a label for displaying the time? #2 I can't say whether the time is going to run concurrently with the animation of the chart?

    roger,

    Those are features that need to be added if this is the basic procedure you want. Does what you want.

    We dont know enough about exactly how you may want it, if you know at this point. So those are details that need to be incorporated based on your desires and skill level. You can see the basic flow of the things shown... ??

    The x axis can show time if that is how you want to save the data. Then the time you see in the chart axis is the time you take the readings. You save the time and the reading and plot that. Maybe make a class if you know those.

    If you want a label shown with the time then you can just label1.text = time reading when the data is saved and the chart updated etc.

    Did you draw the chart you show in your image or is that the chart control?

    Saturday, December 14, 2019 7:57 PM
  • Leslie, I tried your new code, integrated with my "RS-232 code" and did not get the counter to increment, as expected. This is the "new revised code" :

    Option Strict On
    Option Explicit On

    Imports System.ComponentModel
    Imports System.IO.Ports

    Public Class Form1

        Dim myPort As New IO.Ports.SerialPort("COM5") With {
                .BaudRate = 9600,
                .DataBits = 8,
                .StopBits = IO.Ports.StopBits.One,
                .Parity = IO.Ports.Parity.None,
                .Handshake = IO.Ports.Handshake.RequestToSend,
                .RtsEnable = True,
                .DtrEnable = True,
                .ReadTimeout = 1000000,
                .WriteTimeout = 10000
                }

        ' for counter Label display
        Dim counter As Integer = 1
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Button1.Text = "START"
            BackgroundWorker1.WorkerSupportsCancellation = True
        End Sub
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Select Case Button1.Text
                Case "START"
                    StartCollection()
                Case Else
                    FinishCollection()
            End Select
        End Sub
        Sub StartCollection()

            Button1.Text = "STOP"
            BackgroundWorker1.RunWorkerAsync()

            ' Roger Breton Code to communicate with the instrument through the RS-232 port
            Dim Incoming As String

            Try
                myPort.Open()

                myPort.WriteLine("PR705")
                Incoming = myPort.ReadLine()    ' Incoming = " REMOTE MODE" & vbCr

                If Incoming.Contains("REMOTE MODE") Then
                    myPort.WriteLine("M1")
                    Incoming = myPort.ReadLine()
                End If

            Catch ex As Exception
                MessageBox.Show("Serial Port Error : " & ex.ToString())
            Finally
                myPort.Close()
            End Try

            FinishCollection()

        End Sub
        Sub FinishCollection()
            ' emulate data collection STOP
            Button1.Text = "START"
            BackgroundWorker1.CancelAsync()
        End Sub
        Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

            Dim sw As New Stopwatch
            sw.Start()
            Do
                Try
                    Invoke(Sub() Label1.Text = CInt(sw.ElapsedMilliseconds / 1000).ToString)
                Catch ex As ObjectDisposedException
                    ' used if BGW finds itself in
                    ' a state of 'disposed'
                    Exit Do
                End Try
            Loop Until BackgroundWorker1.CancellationPending
            sw.Stop()
        End Sub
    End Class

    And this is three screen captures that show what is going on when I execute the code:

    Saturday, December 14, 2019 8:02 PM
  • Tommy,

    Yes, this is the code used to generate the chart in my image:

                Me.GraphLUT.ChartAreas.Clear()      
                Me.GraphLUT.ChartAreas.Add("Base")
                'GraphLUT.ChartAreas.Titles.Clear()
                GraphLUT.Legends.Clear()
                GraphLUT.Series.Clear()
                GraphLUT.Annotations.Clear()
                GraphLUT.Legends.Clear()

                With Me.GraphLUT.ChartAreas("Base")
                    .BorderColor = Color.Black
                    .BackColor = Color.LightGray
                    .AxisX.Minimum = 380
                    .AxisX.Maximum = 730
                    .AxisX.Interval = 10
                    .AxisY.Minimum = 0
                    .AxisY.Maximum = MaxSpectralValue
                    .AxisY.Interval = MaxSpectralValue / 10
                    .AxisY.Title = "Watts/sr/m2"
                    .AxisX.Title = "Wavelength (2nm resolution)"
                End With

                Me.GraphLUT.Series.Clear()
                Me.GraphLUT.Series.Add("CIE D50")

                With Me.GraphLUT.Series("CIE D50")
                    .ChartType = DataVisualization.Charting.SeriesChartType.Line
                    .Color = Color.Black  ' Couleur du trait
                    .BorderWidth = 2
                    .MarkerColor = Color.Black
                    .MarkerSize = 5
                    .MarkerBorderWidth = 3
                    .MarkerBorderColor = Color.White
                End With

                For i = 1 To 201
                    GraphLUT.Series("CIE D50").Points.AddXY(nmValue(i), SpectralValue(i))
                Next i

    The only thing I'm not showing in this code is the "CIE D50" array.

    The Chart shows the data returned from the instrument. There are 201 measurements accross the visible spectrum. The use of the label to show the time "elapsed" in the interface AS THE MEASUREMENT PROGRESSES is completely optional. I show, at the end of the measurement, the time elapsed in milliseconds and seconds, if you noticed on my screen capture?

    I don't need to show the "time" on the graph. The role of the "time" label is just to show "progress" to the user as "time passes". I would like it to display, in seconds, 01, then 02, then 03, then 04, than 05 and so on, and stop when the measurement is done.

    What do you think?

    Saturday, December 14, 2019 8:13 PM
  • Hi Roger,
    in my demo I use the Thread.Threading.Timer class. Without Imports System.Threading you can use this code:

    Public Class Form1

    Private WithEvents btnStart As New Button With {.Text = "Start", .Dock = DockStyle.Top, .Margin = New Padding(5)} Private WithEvents btnStop As New Button With {.Text = "Stop", .Dock = DockStyle.Top, .Margin = New Padding(5)} Private pan As New Panel With {.Dock = DockStyle.Fill, .Margin = New Padding(5)} Private lbl As New Label With {.Text = "stopped", .Dock = DockStyle.Bottom, .Margin = New Padding(5)}

    Private Sub Form54_Load(sender As Object, e As EventArgs) Handles MyBase.Load Me.Controls.AddRange(New Control() {pan, lbl, btnStop, btnStart}) End Sub Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click StartDrawing() End Sub Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click StopTimer() End Sub Private Async Sub StartDrawing() StartTimer() Dim gr = pan.CreateGraphics gr.Clear(Color.White) For i = 1 To Me.Width gr.DrawLine(Pens.Blue, New Point(i, CType((pan.Height - 2) / 2 * (1 + Math.Sin(i / 10)), Integer)), New Point(i + 1, CType((pan.Height - 2) / 2 * (1 + Math.Sin(i / 10 + 0.1)), Integer))) Await Task.Delay(50) If t Is Nothing Then Exit Sub Next StopTimer() End Sub Private t As System.Threading.Timer = Nothing Private Sub StartTimer() If t IsNot Nothing Then Exit Sub Dim startTime = Now t = New System.Threading.Timer(Sub(state As Object) lbl.Invoke(Sub() lbl.Text = (Now - startTime).TotalSeconds.ToString End Sub) End Sub, Nothing, 0, 100) End Sub Private Sub StopTimer() t.Dispose() t = Nothing lbl.Text = "stopped" End Sub End Class



    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks


    Saturday, December 14, 2019 8:54 PM
  • Hi

    OK, try this one ( complete code ).

    Obviously, I can't test, but have tried to emulate a SP in/out. In your case, maybe if you alter the code to your actual Serial Port then it could conceivably work 😊

    ' Example with Form1, Label1
    ' Button1, BackGroundWorker1,
    ' ListBox1 and ListBox2
    Option Strict On
    Option Explicit On
    Public Class Form1
    	Dim myPort As New MyPort
    	Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    		Button1.Text = "START"
    		BackgroundWorker1.WorkerSupportsCancellation = True
    	End Sub
    	Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    		Select Case Button1.Text
    			Case "START"
    				StartCollection()
    			Case Else
    				FinishCollection()
    		End Select
    	End Sub
    	Sub StartCollection()
    		' emulate data collection START
    		Button1.Text = "STOP"
    		BackgroundWorker1.RunWorkerAsync()
    	End Sub
    	Sub FinishCollection()
    		' emulate data collection STOP
    		Button1.Text = "START"
    		BackgroundWorker1.CancelAsync()
    	End Sub
    
    	Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    		Dim Incoming As String
    		Dim sw As New Stopwatch
    		sw.Start()
    		Invoke(Sub() ListBox1.Items.Add(myPort.Open()))
    		Do
    			Try
    				Invoke(Sub() ListBox1.Items.Add(myPort.WriteLine("PR705")))
    				Incoming = myPort.ReadLine()
    
    				If Incoming.Contains("REMOTE MODE") Then
    					Invoke(Sub() ListBox1.Items.Add(myPort.WriteLine("M1")))
    					Incoming = myPort.ReadLine()
    				End If
    
    				Invoke(Sub() ListBox2.Items.Add(Incoming))
    
    				Invoke(Sub() Label1.Text = CInt(sw.ElapsedMilliseconds / 1000).ToString)
    			Catch ex As ObjectDisposedException
    				' used if BGW finds itself in
    				' a state of 'disposed'
    				Exit Do
    			Catch ex As Exception
    				MessageBox.Show("Serial Port Error : " & ex.ToString())
    				Exit Do
    			End Try
    		Loop Until BackgroundWorker1.CancellationPending
    		sw.Stop()
    		Invoke(Sub() ListBox1.Items.Add(myPort.Close()))
    	End Sub
    End Class
    
    Class MyPort
    	
    	Function WriteLine(s As String) As String
    		' emulate string sent to Serial Port
    		Return "Written: " & s
    	End Function
    	Function ReadLine() As String
    		' emulate string sent from Serial Port
    		Dim r As New Random
    		Dim lst As New List(Of String)
    		lst.AddRange({"REMOTE MODE", "ABC", "DEF"})
    		Threading.Thread.Sleep(300)
    		Return lst(r.Next(0, lst.Count))
    	End Function
    	Function Open() As String
    		Return "Port Open"
    	End Function
    	Function Close() As String
    		Return "Port Closed"
    	End Function
    End Class


    Regards Les, Livingston, Scotland




    • Edited by leshay Saturday, December 14, 2019 10:09 PM
    Saturday, December 14, 2019 8:56 PM
  • Tommy,

    Yes, this is the code used to generate the chart in my image:

    The Chart shows the data returned from the instrument. There are 201 measurements accross the visible spectrum. The use of the label to show the time "elapsed" in the interface AS THE MEASUREMENT PROGRESSES is completely optional. I show, at the end of the measurement, the time elapsed in milliseconds and seconds, if you noticed on my screen capture?

    I don't need to show the "time" on the graph. The role of the "time" label is just to show "progress" to the user as "time passes". I would like it to display, in seconds, 01, then 02, then 03, then 04, than 05 and so on, and stop when the measurement is done.

    What do you think?

    Well first off I think les's background worker will let you control a label with the time.

    As far as the chart goes, 

      Incoming = myPort.ReadLine()   

    I dont really understand what your data is I should be quiet. Do you just get one big line of data for eight mins? There must be more? Perhaps one cant update the the chart if the data is not in a usable way. Or not worth it.

    It is just an idea... show the chart updating as it is being made... to show the passage of time and something is working. Or even if an eight minute chart looks bad the first 30 secs then cancel etc.


    Saturday, December 14, 2019 9:36 PM
  • Hi

    Just noticed that you have set a very high read timeout on the Serial Port.

    This is the BLOCKING point. An alternative would be to set a short timeout, trap the exception and deal with it (even a do nothing) This example (UNTESTED) , just displays an increment each TimeOut.

    I have tried to re-code one of the earlier examples. Please try it and see what's what 😊

    The idea is that the port will be checked for incoming data 10 times per second.

    *

    From MS Docs:

    By default, the ReadLine method will block until a line is received. If this behavior is undesirable, set the ReadTimeout property to any non-zero value to force the ReadLine method to throw a TimeoutException if a line is not available on the port.

    *

    ' UNTESTED
    
    Option Strict On
    Option Explicit On
    Imports System.IO.Ports
    Public Class Form1
      Dim myPort As New SerialPort("COM5") With {
              .BaudRate = 9600,
              .DataBits = 8,
              .StopBits = StopBits.One,
              .Parity = Parity.None,
              .Handshake = Handshake.RequestToSend,
              .RtsEnable = True,
              .DtrEnable = True,
              .ReadTimeout = 100,
              .WriteTimeout = 100
              }
      ' count TimeOuts
      Dim TimeOuts As Integer = 0
      ' for counter Label display
      Dim counter As Integer = 1
      Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Timer1.Interval = 1000 ' one second
        Button1.Text = "START"
      End Sub
      Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Select Case Button1.Text
          Case "START"
            StartCollection()
          Case Else
            FinishCollection()
        End Select
      End Sub
      Sub StartCollection()
        Timer1.Enabled = True
        counter = 1
        Button1.Text = "STOP"
        Label1.Text = "0"
        Dim Incoming As String
        Try
          myPort.Open()
          myPort.WriteLine("PR705")
          Incoming = myPort.ReadLine()
          If Incoming.Contains("REMOTE MODE") Then
            myPort.WriteLine("M1")
            Incoming = myPort.ReadLine()
            FinishCollection()
          End If
        Catch ex As TimeoutException
          TimeOuts += 1
          Label1.Text = TimeOuts.ToString
        Catch ex As Exception
          MessageBox.Show("Serial Port Error : " & ex.ToString())
        Finally
          myPort.Close()
        End Try
      End Sub
      Sub FinishCollection()
        Button1.Text = "START"
        Timer1.Enabled = False
      End Sub
      Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        Label1.Text = counter.ToString
        counter += 1
      End Sub
    End Class


    Regards Les, Livingston, Scotland

    Saturday, December 14, 2019 11:50 PM
  • Peter, the addition of System.Threading is making all the difference.

    I can see that the counter incrementing at the bottom of the window as the graph is being drawn.

    I will see if I can understand enough of your code to use it in my application and report!!!

    1000 Mit freundlich grüssen (I don't know where the "B" deutche ist on meine keyboard...) 

    / Roger 

    Sunday, December 15, 2019 1:42 AM
  • Leslie, I copied and pasted your code into a new Project and, as you can see from the following screen capture, the "type" of myPort seems to be an issue:

    Any suggestion?

    Sunday, December 15, 2019 1:56 AM
  • Leslie, I copied and pasted your code into a new Project and, as you can see from the following screen capture, the "type" of myPort seems to be an issue:

    Any suggestion?

    Hi

    Did you copy all of the code I posted? There is a Class at the bottom called myPort which I was using to simulate a Serial Port.

    However, here is a later piece of code where I explain about Timeouts. Perhaps it is the better option to try.


    Regards Les, Livingston, Scotland


    • Edited by leshay Sunday, December 15, 2019 2:09 AM
    Sunday, December 15, 2019 2:09 AM
  • Tommy, I would like Leslie's background worker to do the trick.

    Here's how the data "works" -- please don't be quiet. I've asked for help, it's only natural that I don't give you half anwers. The way it works is, I send commands to the serial port and the device sends data back in response to each commands.

    Typical command is "M1", meaning "Take a measurement". Then, if there were no errors, the device sends measurement data on the serial port for me to grab "one line at a time". When I issue a "ReadLine" command, I get the data in the "Incoming" string variable. It's up to me at that point to decode the data. For example, here's the content of the Incoming variable in reponse to the M1 command:

    ' Incoming = " 0000,111,1.951e+001,0.4383,0.4110" & vbCr 

    You see? It's very easy at that point. All I have to do is to "Split" the string into an array using the "," as the delimiter.

    Most data can be retrieved in one "read" but some data, like the "spectral data" which contains 201 data points have to be retrived in a Loop, one line of data at the time. That is the data I use to plot the graph with.

    The "time" the instrument takes to read the source of light I aim it at (btw, here's what the instrument/device looks like, it looks like a simple video camera, with a lens in the front and an eyepiece in the back, except, it's not "filiming" but taking "light" in):

    It's a neat instrument. Once I collected all the 201 data points I'm ready to assemble the chart for display, I don't think that task can be splitted for the sale of updating a timer in the interface. Thanks for the suggestion, though.

    The thing is, I have idea in advance, how long the instrument will need in terms of time to fully analyze the incoming light.

    Sunday, December 15, 2019 2:09 AM
  • Leslie, I set this "abnormally" high TimeOut value because of the variability of the time the instrument takes to complete a measurement cycle. I will think about your suggestion??? There maybe "light" at the end of the tunnel, after all. But know that if I did not use a superhigh TimeOut value, the instrument throws an error when it is finally done with the measurement and it tries to send the data down on the RS-232 line? When that happens, I get a big "Beep" and some undocumented message on the back of the instrument, on its 4 line LCD display, and then the instrument restarts on its own. Not very good. The high TimeOut value allows plenty of time, more than 8 minutes (?), to allow reading the darkest signals, very black, very dim, think a monitor RGB value = 0,0,0 -- practicaly no light whatsoever. 

    When I first got the instrument, I was driving it with HyperTerminal and I did not have to specify any TimeOut values? All I did specify was "Hardware" handshake like this :

    That's all. As you can see, there is no interface to specify TimeOuts here?

    Whereas in VisualBasic, I *have* to specify a TimeOut value.

    Sunday, December 15, 2019 2:19 AM
  • Leslie, re: ReadTimeOut value

    You suggest to use a 100 millisecond value or 1/10 of a second whereas my ReadTimeOut values is one million milliseconds or 1,000 seconds:

    ReadTimeout = 1000000

    For the instrument to complete an 8 minutes measurement cycle requires 16.66 minutes which is double its maximum time but I *did* run into cases where the instrument took more than 13 minutes to complete a measurement (can't remember what I was aiming the instrument at, at that point?) but that explains the One million milliseconds value.

    Sunday, December 15, 2019 2:27 AM
  • Leslie, 

    Before I try your latest code suggestion, with a 100 millisecond TimeOut settings, allow to ask, by what logic will the code "recover" or "reattempt" to read the serial port, once it gets to the Exception? Forgive my poor programming knowledge. Essentially, this is the gist of the code :

    Try
          myPort.Open()
          myPort.WriteLine("PR705")
          Incoming = myPort.ReadLine()
          If Incoming.Contains("REMOTE MODE") Then
            myPort.WriteLine("M1")
            Incoming = myPort.ReadLine()
            FinishCollection()
          End If
        Catch ex As TimeoutException
          TimeOuts += 1
          Label1.Text = TimeOuts.ToString
        Catch ex As Exception
          MessageBox.Show("Serial Port Error : " & ex.ToString())
        Finally
          myPort.Close()
        End Try

    So, the code is wrapped in a Try/Catch block. After sending "M1", an attempt is made to "Read Incoming", at which point, it will Time Out for sure, then the code will go the Catch as TimeOutException code. Then TimeOuts will be incremented. Then, perfect time to update Label1.Text. But then, what happens next? The code will exit the Try/Catch block? Without re-attempting another "Read"? 

    I guess I ought to wrap to use a second, inner Try/Catch block, just for the Read, and wrap that in a "Do/While" loop? To loop until it finally gets data out of the port? That part is not clear to me.

    Sunday, December 15, 2019 2:35 AM
  • Hi

    The issue is mine, I am getting a bit mixed up with the various code blocks and I think I have added code to one when I should have added it to another version. As I can't test anything my end, I failed to catch that one.#I have found what looks to be a valuable description of the use of the Serial Port. Have a look HERE.

    I have tried to edit the code he shown there to suit your example. As far as I can test, the Timer code counter is working. You can test the Serial Port code at your end.

    BTW: I am trying to track versions - I have labelled this one as '10'

    ' THIS IS CODE BLOCK 10
    ' Form1 with Multiline TextBox1
    ' Label1, Button1 and Timer1
    Imports System.IO.Ports
    Public Class Form1
      Public Delegate Sub StringSubPointer(ByVal Buffer As String)
      Dim WithEvents myPort As New SerialPort("COM5") With {
                .BaudRate = 9600,
                .DataBits = 8,
                .StopBits = StopBits.One,
                .Parity = Parity.None,
                .Handshake = Handshake.RequestToSend,
                .RtsEnable = True,
                .DtrEnable = True,
                .ReadTimeout = 2000,
                .WriteTimeout = 2000,
                .NewLine = vbLf
                }
      Dim sw As New Stopwatch
      Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' open port once at beginning
        Try
          myPort.Open()
        Catch ex As Exception
          MessageBox.Show(ex.Message)
        End Try
        Size = New Size(700, 500)
        With TextBox1
          .Multiline = True
          .Size = New Size(ClientSize.Width - 15, ClientSize.Height - 60)
          .Location = New Point(8, 8)
        End With
        With Label1
          .Location = New Point(8, TextBox1.Bottom + 16)
          .Text = "Counter"
        End With
        With Button1
          .Text = "START"
        End With
        With Timer1
          .Enabled = False
          .Interval = 200
        End With
      End Sub
      Private Sub MyFormClosing(ByVal sender As Object, ByVal e As ComponentModel.CancelEventArgs) Handles MyBase.Closing
        ' close once at end
        If myPort.IsOpen Then myPort.Close()
      End Sub
      Private Sub Receiver(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs) Handles myPort.DataReceived
        Me.BeginInvoke(New StringSubPointer(AddressOf Display), myPort.ReadLine)
      End Sub
      Private Sub Display(ByVal Buffer As String)
        TextBox1.AppendText(Buffer)
      End Sub
      Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Select Case Button1.Text
          Case "START"
            Button1.Text = "STOP"
            sw.Start()
            Timer1.Enabled = True
          Case Else
            Button1.Text = "START"
            Timer1.Enabled = False
            sw.Reset()
        End Select
      End Sub
      Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        Label1.Text = (sw.ElapsedMilliseconds \ 1000).ToString
      End Sub
    End Class


    Regards Les, Livingston, Scotland



    • Edited by leshay Sunday, December 15, 2019 3:40 AM
    Sunday, December 15, 2019 3:36 AM
  • Hi,
    I think the main problem that has to be solved is the asynchronous receiving of the data from the COM port. Then the surface is not blocked. The asynchronously received data must then be transferred to the controls in the user interface across threads. You can work asynchronously either with Begin... or with separate thread of the thread or task class.

    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Sunday, December 15, 2019 6:45 AM
  • Tommy, I would like Leslie's background worker to do the trick.

    Here's how the data "works" -- please don't be quiet. I've asked for help, it's only natural that I don't give you half anwers. The way it works is, I send commands to the serial port and the device sends data back in response to each commands.

    Typical command is "M1", meaning "Take a measurement". Then, if there were no errors, the device sends measurement data on the serial port for me to grab "one line at a time". When I issue a "ReadLine" command, I get the data in the "Incoming" string variable. It's up to me at that point to decode the data. For example, here's the content of the Incoming variable in reponse to the M1 command:

    ' Incoming = " 0000,111,1.951e+001,0.4383,0.4110" & vbCr 

    You see? It's very easy at that point. All I have to do is to "Split" the string into an array using the "," as the delimiter.

    Most data can be retrieved in one "read" but some data, like the "spectral data" which contains 201 data points have to be retrived in a Loop, one line of data at the time. That is the data I use to plot the graph with.

    The "time" the instrument takes to read the source of light I aim it at (btw, here's what the instrument/device looks like, it looks like a simple video camera, with a lens in the front and an eyepiece in the back, except, it's not "filiming" but taking "light" in):

    It's a neat instrument. Once I collected all the 201 data points I'm ready to assemble the chart for display, I don't think that task can be splitted for the sale of updating a timer in the interface. Thanks for the suggestion, though.

    The thing is, I have idea in advance, how long the instrument will need in terms of time to fully analyze the incoming light.

    LOL!

    Yes I think maybe there are two different things being discussed maybe more.

    "I would like Leslie's background worker to do the trick."

    For what? Showing the, lets call it elapsed, time on the screen? Ok. That is easy. Its starting and stopping it that is hard. You need to stop when the test stops. But the elapsed time display is a separate thing that has nothing to do with the actual com port data acquisition (other than starting and stopping the same). Rather the elapsed time display is just a clock or number image put on the screen gui at some interval while the test runs independently.

    If a background worker (bgw) is used for the time display the bgw updates the clock value at some timer interval like half sec by sharing the time value with the main application gui thread. Then the main thread puts the value on the screen.

    Again all that is happening is a timer running in the background worker displaying a number value on the screen gui until something tells it to stop. You can also do it with a system interval timer like I showed in my chart example. However I updated the whole chart each time interval. You only wanted to update the current time in a label on the screen when you started the question.


    "The way it works is, I send commands to the serial port and the device sends data back in response to each commands."

    Yes. So that is something else. That is reading the data from the device across the com port and saving it in memory on the computer.

    It depends on how your device works. I think to read the com port one makes a data buffer and then keep requesting data from the serial port until you receive the done signal. Each time you get data you have to figure what it is. Then decide what to do. So at 1:22:44 am maybe ticks 10000023l400 even the data starts and then you check every X milliseconds and reguest more data and if there read it. So you get chunks of data at unknown intervals until done. Then you have 200 data points to draw on a graph on the gui. But it depends on exactly how YOUR device does it.

    It sounds like you have been using some other software or driver to do this until now???

    What is this:

                For i = 1 To 201
                    GraphLUT.Series("CIE D50").Points.AddXY(nmValue(i), SpectralValue(i))
                Next I

    How do you get the array SpectralValue? That is where you can control the time interval. Where you SpectralValue(n) = XXX now you can timeLabel.text = TTT or etc.

    And or you can draw the partial data you have collected in the buffer as you get it at time 4 mins draw the buffer... again at 5 mins... until ... done.

    So one can do that with an interval timer or a separate thread. If you use threading then you will have to keep the gui display code on the main app thread and the read data code on a second thread.

    If you use a system interval timer you dont have cross threading problems. That is like the example I showed. If you use a real second thread or async like les and Peter describe you need to deal with cross threading.

    How does your device work? Depends on the type of data. A thermostat just sends one reading back when you ask for it. Your spectral test has some internal device method to get values from your CCD or whatever you spectrum is done with and now you have 200 points to read not just one ie a CCD with 20 x 10 pixels. Correct?

    So how does that spectrum device work? That is how the data comes to you. Does it read the 200 pixels once and done? Or does it read them 100000 times and send you an average?? Or do you ask for 100 readings of all 200 pixels? Or does the device do that? I dont know but that is how your will get your data from the com port... ie the way the device sends it. So look there for how to code that receiving ie the device maker should have sample code.

    I know enough about astronomy and serial ports to know it is done but I have never done it I just see these threads now and then. So I am not really helping just confusing things??

    Now, you already have the whole thing working correct? You can run a test and get the data and make a graph already correct? So you just need to add in the time display or what ever it can be correct? Whether its a simple label with the time or a chart with the data or both. Thats the original question correct?

    In order to answer that you have to show the forum how you collect the data now. Correct? Or is that also the question?

    :)

    PS that looks like my old Sony super 8 video camera. What is it on a telescope?

    I used to do astrophotography back in the stone age of real (gas hypered) film. Many nights sitting in a mountian snowbank in the middle of the night with my own guiding recticle that I made from a pin and my own RA/DEC gear box and joystick that I made from a furnace flap control and radio shack parts ... then developing the negs myself in my own light proof closet and finally running it to the camera shop for prints...

    Got something! Joy!


    Sunday, December 15, 2019 9:32 AM
  • Leslie, shame on me, I missed the "myPort" class -- sorry, my bad. This code is working perfectly. I have to study the "logic" now... THANK YOU!

    Sunday, December 15, 2019 1:48 PM
  • GUYS! While you were busy helping me find a solution, I continue my research and came accross a different approach. *BEFORE* I continue using one of your code suggestions, I realize one of the potential "source" of my problem is the way I read the Serial port and I have to thank Leslie to point me in this direction -- all the codes suggestions, so far, are (sind) flawless, and I thank you from the bottom of my heart to help me so generously -- I owe you guy more than a beer :-)

    It seems there are three approaches to reading data from serial port:

    1. One line at a time (ReadLine)
    2. One char at a time (Readchar)
    3. Using an "Event" (AddHandler mySerialPort.DataReceived, AddressOf DataReceivedHandler)

    I'm sorry I did not understand the three approaches before. The third approach has its advantages and disadvantages, and from what I read, so far, some find it unreliable and will not use it. Still, this a "solution" to the "polling" question I had yesterday, in that, what the "event" does is to "listen" to characters coming on the serial port and raising an event when data is received. To do this, the way I understand it, it needs to use a separate thread and therefore, there is a need to use "delegate" to work around the "transfer" of data to the "GUI" or the main thread? Which I think, with Peter's help, I can work around. But (aber) I think, for my needs, I will start experiment with the ReadChar method instead, in a While/Loop, repeatedly polling the port for "incoming characters". The ReadChar method should not take long to execute and therefore I should have control back to update a timer in the interface. We will see how that works. 

    If this works, that would be a minimal change to my code and provide a satisfactory solution. If this does not work (arbeit) then I will look at other solutions, perhaps the DataReceived event.

    Thank YOU all (vielen danke) so much for your kind and patient help!! XX

    Sunday, December 15, 2019 2:05 PM
  • I experimented with :

        CharReceived = myPort.ReadChar()

    And that takes the *same* time as 

        Incoming= myPort.ReadLine()

    The application still remains unresponsive while waiting for a response from the serial port -- no change, no improvement. So I need to consider other solutions... Peter ist richtig, ich bin "stuck" mit die asynchronous nature of the COM port... I will look at his latest code carefully... and report :-)

    Sunday, December 15, 2019 3:06 PM
  • I am experimenting with DataReceived Event. 

    This is the code I am using (put together from code I found on a French web sit

    --------------------------------------------

    Public Class Form1
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

            Dim myPort As New IO.Ports.SerialPort("COM5") With {
                .BaudRate = 9600,
                .DataBits = 8,
                .StopBits = IO.Ports.StopBits.One,
                .Parity = IO.Ports.Parity.None,
                .Handshake = IO.Ports.Handshake.RequestToSend,
                .RtsEnable = True,
                .DtrEnable = True,
                .ReadTimeout = 1000000, ' 1,000,000 milisecondes = 1,000 secondes
                .WriteTimeout = 10000
                }

            myPort.Open()

            myPort.WriteLine("PR705")
            Dim Incoming As String = myPort.ReadLine()    ' Incoming = " REMOTE MODE" & vbCr

            myPort.WriteLine("M1")

        End Sub

        Private Delegate Sub _Display_ASCII(ByVal data As String)

        Private Sub myPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles myPort.DataReceived
            Dim nb_bytes As Integer = myPort.BytesToRead ' recover nb of bytes in buffer
            Dim trame(nb_bytes - 1) As Byte ' array to read in bytes

            myPort.Read(trame, 0, nb_bytes) 'read the port

            Dim data As String = System.Text.Encoding.ASCII.GetString(trame) ' recover data in ASCII format
            TextBox1.Text = data

            'The DataReceived event being in a thread different from Form1, 
            ' wer have to call a delegate to display the result on screen
            If Me.InvokeRequired Then
                Me.Invoke(New _Display_ASCII(AddressOf Display_ASCII), data)
            End If
        End Sub

        Private Sub Display_ASCII(ByVal data As String)
            Dim byte_received = data
            TextBox1.Text = byte_received 'display data in Textbox1
        End Sub

    End Class

    --------------------------------------------

    Problem is that the myPort_DataReceived event does not fire?
    I'm probably on the wrong track...

    Sunday, December 15, 2019 4:28 PM
  • Hi

    You would need to declare the myPort WithEvents  - see the code in the last post I made about 6 or 7 posts before this one. (where I show code very similar to your code here.)


    Regards Les, Livingston, Scotland

    Sunday, December 15, 2019 4:38 PM
  • Leslie and Peter and Tommy,

    Thanks to ALL your kind help and patience, I think I begin to see the light at the end of the tunnel. This is the UI of my new test program:

    The time shows up in seconds, at the bottom of the window.
    This is exactly what I had in mind, nothing more complicated.
    The idea was not to have the program appear "hung" or "crashed" while the instrument is making a measurement.

    This is the code I have so far (thanks to all the clues you guys generously gave me throughout this conversation!):

    ' Serial Port Interfacing With VB.net 2010 Express Edition
    'Copyright (C) 2010 Richard Myrick T. Arellaga
    
    Imports System
    Imports System.ComponentModel
    Imports System.Threading
    Imports System.IO.Ports
    Public Class Form1
        Dim startTime As Date = DateTime.Now()
        Dim WithEvents myPort2 As New IO.Ports.SerialPort("COM5") With {
                .BaudRate = 9600,
                .DataBits = 8,
                .StopBits = IO.Ports.StopBits.One,
                .Parity = IO.Ports.Parity.None,
                .Handshake = IO.Ports.Handshake.RequestToSend,
                .RtsEnable = True,
                .DtrEnable = True,
                .ReadTimeout = 1000000, ' 1,000,000 milisecondes = 1,000 secondes
                .WriteTimeout = 10000
                }
    
        Delegate Sub SetTextCallback(ByVal [text] As String) 'Added to prevent threading errors during receiving of data
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            lblRS232PortStatus.Text = "COM5 close"
            btnCloseRS232.Enabled = False 'Initially Disconnect Button is Disabled
        End Sub
    
        Private Sub OpenRS232_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOpenRS232.Click
            Try
                myPort2.Open()
                If myPort2.IsOpen Then
                    lblRS232PortStatus.Text = "COM5 Open"
                End If
            Catch ex As Exception
                lblRS232PortStatus.Text = "COM5 Problem:" & ex.ToString()
            End Try
    
            btnOpenRS232.Enabled = False
            btnCloseRS232.Enabled = True
        End Sub
    
        Private Sub CloseRS232_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCloseRS232.Click
            myPort2.Close()
            btnOpenRS232.Enabled = True
            btnCloseRS232.Enabled = False
            lblRS232PortStatus.Text = "COM5 close"
        End Sub
    
        Private Sub SendCommand_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSendCommand.Click
    
            tbPR705response.Text = ""
    
            If Not myPort2.IsOpen Then
                myPort2.Open()
                lblRS232PortStatus.Text = "COM5 Open"
            End If
    
            startTime = DateTime.Now()
    
            Timer1.Start()
            myPort2.WriteLine(tbPR705Command.Text)
    
        End Sub
    
        Private Sub myPort2_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles myPort2.DataReceived
            'Automatically called every time data is received at the serialPort
            ReceivedText(myPort2.ReadExisting())
            Timer1.Stop()
        End Sub
        Private Sub ReceivedText(ByVal [text] As String)
    
            'compares the ID of the creating Thread to the ID of the calling Thread
            If Me.tbPR705response.InvokeRequired Then
                Dim x As New SetTextCallback(AddressOf ReceivedText)
                Me.Invoke(x, New Object() {(text)})
            Else
                Me.tbPR705response.Text &= [text]
            End If
        End Sub
    
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    
            Dim diff As TimeSpan = DateTime.Now - startTime
            lblTime.Text = diff.ToString("mm\:ss\:ff") & " seconds"
    
        End Sub
    End Class
    
    
    
    I don't mind leaving the fact where I got part of this code, which I extensively modified and adapted, from  https://www.codeproject.com/questions/183627/how-to-receive-data-from-serial-port-rs232-in-vb-n

    In fairness, I don't understand the role played by all the instructions in this code but sufficiently that I can move along. I better understand what a delegate is, the idea of threads, which were unknown to me, before I embarked on this project.

    Hope I can pick you guys's brains in the future :-)


    • Marked as answer by roger.breton Monday, December 16, 2019 1:26 AM
    • Edited by roger.breton Monday, December 16, 2019 1:26 AM
    Monday, December 16, 2019 1:26 AM
  • The general idea of the following code is  to queue up the received data and process it on another thread.  I have had a lot of luck with this general pattern.

    Imports System.IO.Ports
    
    Public Class Form1
    
        Private WithEvents TheSP As New IO.Ports.SerialPort
        Private HaveData As New Threading.AutoResetEvent(False)
        Private InData As New Concurrent.ConcurrentQueue(Of List(Of Byte))
        Private ProcDataTask As Task
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
            ProcDataTask = Task.Run(Sub() ProcData()) 'start background thread to process data
        End Sub
    
        Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
            RichTextBox1.Clear()
            OpenSP()
        End Sub
    
        Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
            Try
                If TheSP.IsOpen Then
                    TheSP.Close()
                End If
                TheSP.Dispose()
            Catch ex As Exception
                Stop
            End Try
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Send()
        End Sub
    
        Private Sub Send()
            Try
                If Not TheSP.IsOpen Then
                    OpenSP()
                End If
                TheSP.WriteLine("01234567890Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
            Catch ex As Exception
    
            End Try
        End Sub
    
        Private Sub _DataReceived(sender As Object,
                                             e As IO.Ports.SerialDataReceivedEventArgs) Handles TheSP.DataReceived
            'do as little as possible in the handler
            Dim readBuf(TheSP.BytesToRead - 1) As Byte 'buffer for bytes
            Try
                Dim br As Integer = TheSP.Read(readBuf, 0, readBuf.Length) 'read bytes
                'queue up the data received
                InData.Enqueue(readBuf.Take(br).ToList)
                HaveData.Set() 'signal data to process
                '
                If br <> readBuf.Length Then
                    'todo Didn't read all available
                End If
            Catch ex As Exception
                'todo
            End Try
        End Sub
    
        Private Sub ProcData()
            'process the data
            Dim AllDataQ As New List(Of Byte)
            Do
                HaveData.WaitOne()
                'Debug.WriteLine("")
                'process the queue
                Dim OneBuf As List(Of Byte)
                While Not InData.IsEmpty
                    If InData.TryDequeue(OneBuf) Then 'get some data
                        AllDataQ.AddRange(OneBuf)
                        'Debug.WriteLine(OneBuf.Count)
                    Else
                        HaveData.WaitOne(1)
                    End If
                End While
    
                '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                ' at this point the protocol of the device is
                '  enforced.  The sample just empties the buffer
                '  show each received byte as hex.
                '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                If AllDataQ.Count > 0 Then
                    Dim sb As New System.Text.StringBuilder
                    sb.AppendLine()
                    For Each b As Byte In AllDataQ
                        sb.Append(b.ToString("X2"))
                    Next
                    sb.AppendLine()
                    AllDataQ.Clear()
                    '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                    'UI Stuff
                    Me.Invoke(Sub()
                                  RichTextBox1.AppendText(sb.ToString)
                                  RichTextBox1.ScrollToCaret()
                              End Sub)
                    '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                End If
            Loop
        End Sub
    
        Private Sub OpenSP()
            Dim t As Task
            t = Task.Run(Sub()
                             If Not TheSP.IsOpen Then
                                 'Modify settings as needed <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                 TheSP.PortName = "COM1"
                                 TheSP.BaudRate = 115200 '  19200 '  
                                 TheSP.DataBits = 8
                                 TheSP.Parity = Parity.None
                                 TheSP.StopBits = StopBits.One
    
                                 'other settings as needed - consider speed and max size to determine settings  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                 TheSP.ReceivedBytesThreshold = 1 'one is the default, recommend no change unless absolutely needed
                                 TheSP.ReadTimeout = 1000 'default is infinite if not set
                                 TheSP.WriteTimeout = 1000 'default is infinite if not set
                                 TheSP.ReadBufferSize = 1024 * 4 'Windows-created input buffer 4096 is default, change if needed
                                 TheSP.WriteBufferSize = 1024 * 2  'Windows-created output buffer 2048 is default, change if needed
    
                                 'this setting is informational only.  the code only reads bytes  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                 'if the device is sending strings recommend setting this to match encoding device is using  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                 'if the application is writing strings this must match devices encoding                     <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                 TheSP.Encoding = System.Text.Encoding.GetEncoding(28591) 'default is 7 bit ascii, this is an 8 bit flavor of asciii
                                 Try
                                     TheSP.Open()
                                     'some devices require the following
                                     TheSP.DtrEnable = True 'only after Open. may cause multiple PinChanged events
                                 Catch ex As Exception
                                     Debug.WriteLine(ex.Message)
                                 End Try
                                 'SerialPortOpen - port open
                             End If
                         End Sub)
        End Sub
    End Class
    


    Search Documentation

    SerialPort Info

    Multics - An OS ahead of its time.

     "Those who use Application.DoEvents have no idea what it does

        and those who know what it does never use it."    former MSDN User JohnWein

    Monday, December 16, 2019 4:35 PM
  • Wow! Thanks 'dbasnett'. This is quite extensive. Let me see if I can "digest" your code... :-)
    Tuesday, December 17, 2019 2:44 PM