More help required with receiving serial data

Vader

Well-Known Member
Licensed User
Longtime User
Ok, so I am successfully sending serial data, as shown here.

Now, the problem is that I can't receive data. well, to be exact, now that I have ported the remaining code over from my Windows Library, I can't receive data any more (yes, I was receiving it previously).

My Windows Library worked by polling the serial port when I wanted to, not by using events. With AsyncStreams, this isn't possible - you apparently get an event raised when data is received.

I don't get these events now (where I did previously).

Here is my code to receive the data:

B4X:
Private Sub Stream_NewData(Buffer() As Byte)
   Private NewData As String = BytesToString(Buffer, 0, Buffer.Length, "ISO8859_1")
   Private i As Int = 0
   
   Log("Received string:" & NewData)
   
   For i = 0 To NewData.Length-1
      Queue.Add(NewData.SubString2(i,i))
   Next   
End Sub

The Stream is initialised here:
B4X:
Private Sub InitialiseStream As Boolean
   Try
      Stream.Initialize(Port.InputStream, Port.OutputStream, "Stream")
      Log("Stream initialised")
      Return True
   Catch
      Log("** Error initialising stream")
      Return False
   End Try
   
End Sub


'Queue' is a list that I can check (or poll) when I want to. I do it in the ReadFromSerialPort() Subroutine.
B4X:
Private Sub ReadFromSerialPort(IgnoreNULL As Boolean) As Int
   Private ReceivedByte As Int = -1
   
   Do While (ReceivedByte = 0 AND IgnoreNULL = True) OR ReceivedByte = -1
      If Queue.Size > 0 Then
         ReceivedByte = Queue.Get(0)
         Queue.RemoveAt(0)         
      End If
      DoEvents
   Loop
   
   Return ReceivedByte
   
End Sub

Port, Stream and Queue are defined in Class_Globals, and initialised elsewhere.
B4X:
Sub Class_Globals
    Public Port As Serial
    Public BluetoothConnected As Boolean = False
    Public PairedMACAddress As String = ""
    Public Stream As AsyncStreams
    Private Queue As List
    Private PartNumber As String = ""
    Private Faults As List

It's kind of like the app is too wound-up to give time over to receive the data. And yes, I do use Doevents() and a Wait() Sub.

Can anyone see anything that I am doing wrong?
 

mc73

Well-Known Member
Licensed User
Longtime User
The substring2 is not used properly here:
B4X:
Queue.Add(NewData.SubString2(i,i))
. The second index should be i+1,
B4X:
Queue.Add(NewData.SubString2(i,i+1))
If you can log correctly the receivedString,then your problem is simply in the above line.
 
Upvote 0

Vader

Well-Known Member
Licensed User
Longtime User
Thanks very much for answering.

Unfortunately, that is not the problem.

Before your change, and after, the Log output is as follows:

B4X:
Installing file.
** Activity (main) Pause, UserClosed = false **
PackageAdded: package:vader.twdtrial
** Activity (main) Create, isFirst = true **
STATE loaded
Serial port Initialised
Retrieved the following pairing: 00:12:06:01:53:65
Bluetooth enabled
Bluetooth connected
Serial port Initialised
** Activity (main) Resume **
Stream initialised
Sending Command bytes....
-----------Sent->255
-----------Sent->255
-----------Sent->239
Waiting for 3 byte(s).  IgnoreNULL=false
** Activity (main) Resume **

I do not receive any data.
 
Upvote 0

mc73

Well-Known Member
Licensed User
Longtime User
Tried bypassing the wait and readFromSerial subs? I only ask cause I don't know they're exact structure and can be pausing your module? I would instead enable and disable a timer, in order to poll. Just a thought.
 
Upvote 0

Vader

Well-Known Member
Licensed User
Longtime User
Tried bypassing the wait and readFromSerial subs? I only ask cause I don't know they're exact structure and can be pausing your module? I would instead enable and disable a timer, in order to poll. Just a thought.

Thanks. I will look into it.

(It will be difficult though as the code is by it's nature, very procedural, not event driven).
 
Upvote 0

mc73

Well-Known Member
Licensed User
Longtime User
It's obvious you want to avoid timers. I respect your reasons, still, here's an example of using a timer in your case. Perhaps it helps:
B4X:
Sub Globals
    Dim tmr As Timer 
    Dim IgnoreNull As Boolean
End Sub
Private Sub ReadFromSerialPort
   IgnoreNull=true 
tmr.Initialize ("tmr",1)
    tmr.Enabled =True
End Sub

Sub tmr_tick
    Private ReceivedByte As Int = -1
    If Queue.Size > 0 Then
       ReceivedByte = Queue.Get(0)
       Queue.RemoveAt(0)            
       If Not((ReceivedByte=0 AND IgnoreNull = True) OR ReceivedByte = -1) Then        
            'tmr.Enabled =False:'stop the timer if you really want a single byte
            heresTheByte(ReceivedByte)
        End If
    End If
End Sub

Sub heresTheByte(ReceivedByte As Int)
Log(ReceivedByte):'do what you want with this byte. When finished, set tmr.enabled=false
'In your other events you could exit them if tmr.enabled=true
End Sub
 
Upvote 0

Vader

Well-Known Member
Licensed User
Longtime User
Thanks very much for this. It doesn't work fully (yet), but it may just be enough to get me to the next stage.
 
Upvote 0

mc73

Well-Known Member
Licensed User
Longtime User
I may be missing something, however why do you need a timer? This code should move to AStream_NewData event.

This was actually my thought too! But then I thought that somehow, a reading after the whole transaction, was needed. The problem with such reading, would be that when we begin to read the list, new data may arrive at the same time, altering the list. This is why I set a timer.

In reality, in astream_newData we can filter the unwanted bytes (defined by us) and proceed, without even setting up a list. I just don't know how we handle data in this particular app; What we're doing with them upon or after receiving.
 
Upvote 0

Vader

Well-Known Member
Licensed User
Longtime User
Ok, so here is what the app does...

1. Establish Bluetooth connection

2. Initiate communication with car ECU:
Send 3 bytes (0xFF,0xFF,0xEF)
Wait for 3 bytes (0x0,0x0,0x10)

3. Read ECU part number:
Send 2 bytes (0x5A, 0xD0)
Wait for 22 bytes, sampling 7 from the middle to form the part number

4. Read Sensor data:
Send 2 bytes (0x5A,XX), where XX dictates the sensor to read

[In this app I want to read data continuously]
--

When enough data has been read, you cancel the data stream by sending 0x30.

It is possible to read multiple sensors at once by composing a "sentence" from 0x5A, XX, 0x5A, YY, 0x5A, ZZ
In this mode, the ECU responds with sensor data in the same order you have requested. ie XX then YY then ZZ.

The data from the ECU is not delimited, nor bound by any field characters.

So you see that I only want to read data after I have initiated the data transfer. Because the ECU continues to send data after the initial byte or two, I don't want to necessarily be interrupted with data I don't want to read.
I also want to read data when I want as it is essentially a conversation. If I just respond to all data received, I won't know where in the conversation I am.

I hope that helps.

Dave
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
You need to store the "state" in a global variable.

This is how I would have implement this:
B4X:
Sub Process_Globals
 Dim CurrentState As Int
 Dim STATE_IGNORE = 0, STATE_WAIT_FOR_3 = 1, STATE_WAIT_FOR_22 = 2 As Int
 Dim Buffer As List : Buffer.Initialize
End Sub

Sub AStream_NewData (Data() As Byte)
 Select CurrentState
  Case STATE_WAIT_FOR_3
    Buffer.AddAll(Data)
    If Buffer.Length >= 3 Then
      ...
    End If
 ...
 End Select
End Sub

When you change the current state you should clear the buffer.
 
Upvote 0

Vader

Well-Known Member
Licensed User
Longtime User
Thanks Erel.

I am about to make major structural changes to it to support responding to Events.

Of course this will involve creating a basic state machine so I know what to do with any data I receive.

I will do this and let you know.

Dave
 
Upvote 0
Top