none
Best way to send multiple messages with SerialPort ? RRS feed

  • Question

  • I'm trying to send multiply messages which can be send with diffrent amount of delay. The data of the messages will be contained in listview and amount of unique messages are not limited. For exmaple if user wrote in listview control 2 messages like: 

    https://i.imgur.com/5TV0Jbp.jpg

    It means 1 message should be send every 100ms and another every 10ms. For doing this i tried to use timer event. WHen the button clicked i enable timer. 

        Private Sub Button10_Click(sender As Object, e As EventArgs) Handles Button10.Click
                Timer3.Enabled = True
        End Sub

    What i tried to do in timer's sub, its create 2 vars for each of the message which will count with timer's tick. 

        Private Sub Timer3_Tick(sender As Object, e As EventArgs) Handles Timer3.Tick
            Timer3.Enabled = False
            Dim i As Byte
            counter = counter + 1
            counter1 = counter1 + 1

    Btw timer's interval is 1ms. So 1 tick  = 1ms, Then i looping through listview to find an existing rows and after then checking  its delay number. 

    Private Sub Timer3_Tick(sender As Object, e As EventArgs) Handles Timer3.Tick
    
                If ListView2.Items(i).SubItems(4).Text <> "off" ThenListView2.Items(i).SubItems(4).Text


    Then i want to check "Does counter or counter1 equals one of the listview row's delay

     
       If counter = ListView2.Items(i).SubItems(4).Text  Or counter1 = ListView2.Items(i).SubItems(4).Text  Then

    Then goes parsing proccess and send to the COM:

               If counter = ListView2.Items(i).SubItems(4).Text / 10 Or counter1 = ListView2.Items(i).SubItems(4).Text / 10 Then
                        ' For ccnc = 0 To timervar
                        Dim dlc_case As String = ListView2.Items(i).SubItems(1).Text
                        Dim data_to_send As String = ListView2.Items(i).SubItems(2).Text.Replace(" ", "")
                        Dim extra_id_zero As String
                        Dim a = Convert.ToByte(ListView2.Items(i).SubItems(0).Text, 16)
                        Dim b As New BitArray(BitConverter.GetBytes(a))
                        Dim c As New BitArray(8)
                        Dim chksum(8) As Byte
                        Dim sum As Integer = 0
                        Dim bytes() As Byte
    
    
    
                        c.Set(0, b(0))
                        c.Set(1, b(1))
                        c.Set(2, b(2))
                        c.Set(3, b(3))
                        Select Case dlc_case
                            Case "2"
                                c.Set(4, 0)
                                c.Set(5, 0)
                            Case "4"
                                c.Set(4, 0)
                                c.Set(5, 1)
                            Case "8"
                                c.Set(4, 1)
                                c.Set(5, 1)
                        End Select
                        Select Case ListView2.FocusedItem.SubItems(1).Text
                            Case "2"
                                data_to_send = data_to_send & "000000000000"
                                chksum(0) = Convert.ToByte(data_to_send.Substring(0, 2), 16)
                                chksum(1) = Convert.ToByte(data_to_send.Substring(2, 2), 16)
                                bytes = {chksum(0), chksum(1)}
                            Case "4"
                                data_to_send = data_to_send & "00000000"
                                chksum(0) = Convert.ToByte(data_to_send.Substring(0, 2), 16)
                                chksum(1) = Convert.ToByte(data_to_send.Substring(2, 2), 16)
                                chksum(2) = Convert.ToByte(data_to_send.Substring(4, 2), 16)
                                chksum(3) = Convert.ToByte(data_to_send.Substring(6, 2), 16)
                                bytes = {chksum(0), chksum(1), chksum(2), chksum(3)}
                            Case "8"
                                chksum(0) = Convert.ToByte(data_to_send.Substring(0, 2), 16)
                                chksum(1) = Convert.ToByte(data_to_send.Substring(2, 2), 16)
                                chksum(2) = Convert.ToByte(data_to_send.Substring(4, 2), 16)
                                chksum(3) = Convert.ToByte(data_to_send.Substring(6, 2), 16)
                                chksum(4) = Convert.ToByte(data_to_send.Substring(8, 2), 16)
                                chksum(5) = Convert.ToByte(data_to_send.Substring(10, 2), 16)
                                chksum(6) = Convert.ToByte(data_to_send.Substring(12, 2), 16)
                                chksum(7) = Convert.ToByte(data_to_send.Substring(14, 2), 16)
                                bytes = {chksum(0), chksum(1), chksum(2), chksum(3), chksum(4), chksum(5), chksum(6), chksum(7)}
                        End Select
    
                        For yy As Integer = 0 To bytes.Length - 1
                            sum += bytes(yy)
                        Next
                        Dim overflow = sum \ 256
                        Dim modulo = sum Mod 256
                        sum = Not (CByte((overflow + modulo) Mod 256))
                        c.Set(6, b(0) Xor b(1) Xor b(2) Xor c(4))
                        c.Set(7, Not (b(1) Xor b(3) Xor c(4) Xor c(5)))
                        Dim c2(7) As Byte
                        c.CopyTo(c2, 0)
    
                        Dim d = BitConverter.ToUInt32(c2, 0)
                        extra_id_zero = Hex(d.ToString)
                        If extra_id_zero.Length = 1 Then extra_id_zero = "0" & extra_id_zero
                        If ListView2.Items(i).SubItems(5).Text.Length = 1 Then ListView2.Items(i).SubItems(5).Text = "0" & ListView2.Items(i).SubItems(5).Text
                        GetValueFromlv2Row = "434D44310" & ListView2.Items(i).SubItems(0).Text & dlc_case & data_to_send & ListView2.Items(i).SubItems(5).Text
                        ' test it with some bytes, with some optional spaces in the string
                        Dim myBytes = MyStringConversions.StringToByteArray(GetValueFromlv2Row)
                        spObj.Write(myBytes, 0, myBytes.Length)
                        ' confirm it outputs "Hello."
                        spObj.Write(Microsoft.VisualBasic.ControlChars.Cr)
                        ' Threading.Thread.Sleep(10)

    Then at the end of the sub i turn  vars to 0 and enable timer: 

                If counter = ListView2.Items(i).SubItems(4).Text / 10 Then counter = 0
                        If counter1 = ListView2.Items(i).SubItems(4).Text / 10 Then counter1 = 0
                        'Threading.Thread.Sleep(timervar) : Application.DoEvents()
                        ' Next
                    End If
                End If
                ' End If
            Next
            Timer3.Enabled = True
    SO the problem is that timer sending only lowest delay value. Im guessing what its happening: its probably because of timer too busy with lowest delay thats why it cant send highest one. Any another solutions  to attempt this kind of task? Or maybe i can fix this one? Thanks a lot, any answer appreciated.   

    THe whole code:

        Private Sub Timer3_Tick(sender As Object, e As EventArgs) Handles Timer3.Tick
            Timer3.Enabled = False
            Dim i As Byte
            counter = counter + 1
            counter1 = counter1 + 1
            '  RichTextBox1.Text += counter.ToString
            For i = 0 To ListView2.Items.Count - 1
                If ListView2.Items(i).SubItems(4).Text <> "off" Then
                    '  timervar = Timer3.Interval / ListView2.Items(i).SubItems(4).Text
                    RichTextBox1.Text += counter.ToString
                    If counter = ListView2.Items(i).SubItems(4).Text / 10 Or counter1 = ListView2.Items(i).SubItems(4).Text / 10 Then
                        ' For ccnc = 0 To timervar
                        Dim dlc_case As String = ListView2.Items(i).SubItems(1).Text
                        Dim data_to_send As String = ListView2.Items(i).SubItems(2).Text.Replace(" ", "")
                        Dim extra_id_zero As String
                        Dim a = Convert.ToByte(ListView2.Items(i).SubItems(0).Text, 16)
                        Dim b As New BitArray(BitConverter.GetBytes(a))
                        Dim c As New BitArray(8)
                        Dim chksum(8) As Byte
                        Dim sum As Integer = 0
                        Dim bytes() As Byte
    
    
    
                        c.Set(0, b(0))
                        c.Set(1, b(1))
                        c.Set(2, b(2))
                        c.Set(3, b(3))
                        Select Case dlc_case
                            Case "2"
                                c.Set(4, 0)
                                c.Set(5, 0)
                            Case "4"
                                c.Set(4, 0)
                                c.Set(5, 1)
                            Case "8"
                                c.Set(4, 1)
                                c.Set(5, 1)
                        End Select
                        Select Case ListView2.FocusedItem.SubItems(1).Text
                            Case "2"
                                data_to_send = data_to_send & "000000000000"
                                chksum(0) = Convert.ToByte(data_to_send.Substring(0, 2), 16)
                                chksum(1) = Convert.ToByte(data_to_send.Substring(2, 2), 16)
                                bytes = {chksum(0), chksum(1)}
                            Case "4"
                                data_to_send = data_to_send & "00000000"
                                chksum(0) = Convert.ToByte(data_to_send.Substring(0, 2), 16)
                                chksum(1) = Convert.ToByte(data_to_send.Substring(2, 2), 16)
                                chksum(2) = Convert.ToByte(data_to_send.Substring(4, 2), 16)
                                chksum(3) = Convert.ToByte(data_to_send.Substring(6, 2), 16)
                                bytes = {chksum(0), chksum(1), chksum(2), chksum(3)}
                            Case "8"
                                chksum(0) = Convert.ToByte(data_to_send.Substring(0, 2), 16)
                                chksum(1) = Convert.ToByte(data_to_send.Substring(2, 2), 16)
                                chksum(2) = Convert.ToByte(data_to_send.Substring(4, 2), 16)
                                chksum(3) = Convert.ToByte(data_to_send.Substring(6, 2), 16)
                                chksum(4) = Convert.ToByte(data_to_send.Substring(8, 2), 16)
                                chksum(5) = Convert.ToByte(data_to_send.Substring(10, 2), 16)
                                chksum(6) = Convert.ToByte(data_to_send.Substring(12, 2), 16)
                                chksum(7) = Convert.ToByte(data_to_send.Substring(14, 2), 16)
                                bytes = {chksum(0), chksum(1), chksum(2), chksum(3), chksum(4), chksum(5), chksum(6), chksum(7)}
                        End Select
    
                        For yy As Integer = 0 To bytes.Length - 1
                            sum += bytes(yy)
                        Next
                        Dim overflow = sum \ 256
                        Dim modulo = sum Mod 256
                        sum = Not (CByte((overflow + modulo) Mod 256))
                        c.Set(6, b(0) Xor b(1) Xor b(2) Xor c(4))
                        c.Set(7, Not (b(1) Xor b(3) Xor c(4) Xor c(5)))
                        Dim c2(7) As Byte
                        c.CopyTo(c2, 0)
    
                        Dim d = BitConverter.ToUInt32(c2, 0)
                        extra_id_zero = Hex(d.ToString)
                        If extra_id_zero.Length = 1 Then extra_id_zero = "0" & extra_id_zero
                        If ListView2.Items(i).SubItems(5).Text.Length = 1 Then ListView2.Items(i).SubItems(5).Text = "0" & ListView2.Items(i).SubItems(5).Text
                        GetValueFromlv2Row = "434D44310" & ListView2.Items(i).SubItems(0).Text & dlc_case & data_to_send & ListView2.Items(i).SubItems(5).Text
                        ' test it with some bytes, with some optional spaces in the string
                        Dim myBytes = MyStringConversions.StringToByteArray(GetValueFromlv2Row)
                        spObj.Write(myBytes, 0, myBytes.Length)
                        ' confirm it outputs "Hello."
                        spObj.Write(Microsoft.VisualBasic.ControlChars.Cr)
                        ' Threading.Thread.Sleep(10)
                        If counter = ListView2.Items(i).SubItems(4).Text / 10 Then counter = 0
                        If counter1 = ListView2.Items(i).SubItems(4).Text / 10 Then counter1 = 0
                        'Threading.Thread.Sleep(timervar) : Application.DoEvents()
                        ' Next
                    End If
                End If
                ' End If
            Next
            Timer3.Enabled = True
        End Sub




    • Edited by Raffy_Raff Thursday, January 11, 2018 6:32 AM
    Thursday, January 11, 2018 6:28 AM

All replies

  • SO the problem is that timer sending only lowest delay value. Im guessing what its happening: its probably because of timer too busy with lowest delay thats why it cant send highest one.

    You can't use a timer tick time of 1ms.  About 15ms is the best you will get, but if there is any significant processing in the timer tick event (such as iterating through a list) or if the user is running any other appications that are competing for CPU time then even that will not be achieved.

    Iterating over a ListView will be much slower than iterating over a data collection, such as a List(Of custom objects.   You can use the ListView to display the messages, but you should be using a list of custom objects as the actual message information.  Parsing the string (for instance, to find "Off") is very slow - that should be a flag property in a custom message object. Extracting subitems from a ListViewItem and converting from text to numeric will be much slower than accessing a numeric property in a custom object.

    Thursday, January 11, 2018 7:50 AM
  • Everything Acamar stated is correct and you should review his comments closely.  You'll definitely want to come up with cleaner byte-conversion code for performance and you may have an issue with the way you are managing the bits in a byte; for example this: "data_to_send = data_to_send & "000000000000"" looks like you maybe attempting to bitmask a number but that's not what the code is doing... its just making one long string with a bunch of zeros at the end.  You probably want the data_to_send to be an integer, the mask to be &HC000, and the operator to be "AND" (not &).  You need an integral data type (Byte, Short, Integer, Long, etc) to perform a bitwise operation, not a string.  The bitmask needs to be in Hex, not binary and you have to include the 1's - I'm guessing you wanted a binary mask of "1100 0000 0000 0000" which would be C000 in hex.  Finally bitwise operations use the keywords AND, OR, XOR, etc.  The "&" operator is a string concatenation operator.

    Once you've worked out those design issues with your logic calculations, you can refactor the code into a much easier to follow (and develop) object model similar to the following.  Since you asked for "The best way to...", the answer is going to include some kind of object model to encapsulate your data and logic operations.  The following is an example of one way you might do this.

    We'll need two model classes to go along with the Form code.  First is a "Clock" class to handle the timing execution component of our model requirement.  This is a simple class which just tracks execution time for us.

    'Create a "Clock" class used to track the execution time of the
    'transmission loop
    Public Class Clock
        'Track the total execution time over the clock's lifespan
        Public ReadOnly Property ElapsedTime As Double
        'Track the execution time of the last clock update
        Public ReadOnly Property LastUpdateTime As Double
    
        'Use a StopWatch to keep track of execution time
        Private timer As New Stopwatch
    
        'Allow the clock to be reset to zero
        Public Sub Reset()
            timer.Stop()
            timer.Reset()
            _ElapsedTime = 0.0
            _LastUpdateTime = 0.0
        End Sub
    
        'Update the clock once per iteration of the transmission loop
        'to keep track of loop time and monitor the delay for each transmission
        Public Sub Update()
            timer.Stop()
            _LastUpdateTime = timer.Elapsed.TotalSeconds
            _ElapsedTime += _LastUpdateTime
            timer.Restart()
        End Sub
    End Class

    Next we need a class to hold all of the information about a periodic transmission.  This is the primary "business logic" object of our model.

    'Create a class to hold all of the information about a transmission,
    'to monitor the retry interval and wait delay, and to send the actual data
    'when it is time to do so
    Public Class PeriodicTransmission
        'Create read-only properties for all of the value set by the user
        'in the Form when adding a new transmission
        Public ReadOnly Property Checksum As Integer
        Public ReadOnly Property Count As Integer
        Public ReadOnly Property DataToTransmit As IEnumerable(Of Byte)
        Public ReadOnly Property Dlc As Integer
        Public ReadOnly Property Id As Integer
        '(this property can be read/write in case you want to modify the interval)
        Public Property TransmissionInterval As TimeSpan
    
        'Allow the ListViewItem instance used in the UI to be associated with the transmission
        Public ReadOnly Property ListViewItem As ListViewItem
        '(this property isn't strictly necessary but lets you disable a transmission if desired)
        Public Property TransmissionEnabled As Boolean
    
        'internally track the remaining delay before resending the transmission
        Private delayRemaining As Double
    
        'Capture and store all data relating to a transmission
        Public Sub New(itemId As Integer, itemDlc As Integer, interval As TimeSpan, data As IEnumerable(Of Byte), calculatedChecksum As Integer, lvi As ListViewItem)
            Id = itemId
            Dlc = itemDlc
            TransmissionInterval = interval
            TransmissionEnabled = True
            Checksum = calculatedChecksum
            DataToTransmit = data
            ListViewItem = lvi
            delayRemaining = TransmissionInterval.TotalSeconds
        End Sub
    
        'Allow the count to be reset so that it is tracked seperately for each SendAll operation
        'You can modify this logic to suit your needs
        Public Sub Reset()
            'reset the count
            _Count = 0
            'update the UI display
            ListViewItem.SubItems(3).Text = _Count.ToString
        End Sub
    
        'Write the data to the provided serial port
        Private Sub SendData(serialPort As IO.Ports.SerialPort)
            serialPort.Write(DataToTransmit.ToArray, 0, DataToTransmit.Count)
            serialPort.Write(ControlChars.Cr)
        End Sub
    
        'Call update on every iteration of the SendAll loop.  The method will track the
        'delay time to periodically send the data at the desired interval
        Public Sub Update(clock As Clock, serialPort As IO.Ports.SerialPort)
            'reduce the remaining delay by the last update time
            delayRemaining -= clock.LastUpdateTime
            'if the delay has run out, execute the transmission
            If delayRemaining <= 0.0 Then
                'reset the delay
                delayRemaining = TransmissionInterval.TotalSeconds
                'send the data
                SendData(serialPort)
                'update the send count
                _Count += 1
                'update the UI display
                ListViewItem.SubItems(3).Text = _Count.ToString
            End If
        End Sub
    End Class

    With these support classes in place we can write some fairly simple code in Form1 to create, track and execute instances of the model object.

    Note that the example does not include the parsing of UI control values or the calculation of the checksum value.  Those are left as ToDo's for you to complete.  The example also only stubs methods for hex-to-byte conversion so you'll need to replace those method calls with calls to your own functionality.

    This example is meant purely to demonstrate program design, not specific business logic design.

    Public Class Form1
        'Create Clock variable instance to track execution time of transmission operation
        Private clock As New Clock
        'Create list of transmission instances to send
        Private transmissions As New List(Of PeriodicTransmission)
        'Create a flag to control the repeat transmission loop
        Private continueSendLoop As Boolean
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    
        End Sub
    
        'When the user clicks the "Add" button, parse the Form input data, add a
        'new ListViewItem to the ListView and a new PeriodicTransmission to the
        'transmissions list
        Private Sub AddButton_Click(sender As Object, e As EventArgs) Handles AddButton.Click
            Dim checksumValue As Integer
            Dim dataBytes As New List(Of Byte)
            Dim newItemId As Integer
            Dim newItemDlc As Integer
            Dim newItemInverval As TimeSpan
    
            'ToDo:  Parse databytes, id, dlc and interval from Form input values
            '       and assign to variables.
    
            'ToDo:  Calculate checksum value and assign to variable.
    
            'Create and configure ListViewItem instance
            Dim lvi As New ListViewItem
            lvi.SubItems(0).Text = newItemId.ToString
            lvi.SubItems(1).Text = newItemDlc.ToString
            lvi.SubItems(2).Text = BytesToHex(dataBytes)
            lvi.SubItems(3).Text = "0"
            lvi.SubItems(4).Text = newItemInverval.ToString
    
            'Create a new PeriodicTransmission instance
            Dim tx As New PeriodicTransmission(
                              newItemId, newItemDlc, newItemInverval,
                              dataBytes, checksumValue, lvi)
    
            'Create a reverse association between the transmission and the ListViewItem by
            'setting the ListViewItem tag property value to the PeriodicTransmission instance
            lvi.Tag = tx
    
            'Add the transmission instance to the list of transmissions
            transmissions.Add(tx)
            'ToDo:  Add the ListViewItem to the ListView
        End Sub
    
        'Make the event handler for the "Send All" button an async method so that it can
        'execute repeatedly without making the UI busy.
        Private Async Sub SendAllButton_Click(sender As Object, e As EventArgs) Handles SendAllButton.Click
            'disable the button to prevent reentry while executing the loop
            SendAllButton.Enabled = False
            'flag the loop to execute
            continueSendLoop = True
            'reset the clock
            clock.Reset()
            'reset each transmission's count
            For Each t In transmissions
                t.Reset()
            Next
            'begin the SendAll loop
            Do
                'update the clock on each iteration of the loop
                clock.Update()
                'iterate over the transmission collection in reverse for safety
                For i = transmissions.Count - 1 To 0 Step -1
                    'update each transmission
                    transmissions(i).Update(clock, SerialPort1)
                Next
                'wait 1ms to provide async functionality
                Await Task.Delay(1)
            Loop While continueSendLoop
            'when the loop exits, reenable the button so that another operation can be started
            SendAllButton.Enabled = True
        End Sub
    
        'Clear the running flag to cause the SendAll loop to exit when the current
        'transmission cycle ends
        Private Sub StopAllButton_Click(sender As Object, e As EventArgs) Handles StopAllButton.Click
            'flag the loop to end execution
            continueSendLoop = False
        End Sub
    
        'Stubs of conversion methods
        Private Function BytesToHex(bytes As IEnumerable(Of Byte)) As String
            'return hex string from byte data
        End Function
    
        Private Function HexToBytes(hex As String) As IEnumerable(Of Byte)
            'return byte data from hex string
        End Function
    End Class

    So this code parses user supplied info from the textboxes and other input controls into PeriodicTransmission object instances and creates a related ListViewItem for display in the UI.  It then runs an async loop which monitors the delay time on each transmission object and sends it according to the specified internal.

    This should give you a solid foundation to work from.


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

    Thursday, January 11, 2018 2:55 PM
    Moderator