Bluetooth read write stream

Topics: Bluetooth - Microsoft
Nov 26, 2012 at 8:25 AM

Hi,

I have to establish communication between the desktop application and the mobile device.

The communication takes place in this way:
The application sends requests to the device that responds (question-answer, question-answer, etc.).

I set up my project using callback functions to read and send the data from a stream.

The problem is that not always get answer from ReadCallBack.

//Imposed the callback function in the connect
m_BT.Connect(device.DeviceAddress, device.InstalledServices[0]);

m_stream = m_BT.GetStream();

m_stream.BeginRead(m_btInput, 0, m_btInput.Length, new AsyncCallback(readCallback), m_stream);

 //--

//readCallback

private void readCallback(IAsyncResult result)
        {
            if (m_stream != null && m_stream.CanRead)
            {
                System.Net.Sockets.NetworkStream stream = (System.Net.Sockets.NetworkStream)result.AsyncState;
                try
                {
                    int len = stream.EndRead(result);
                    //string sValue = Encoding.ASCII.GetString(m_btInput, 0, len);

                    if (OnDataReceived != null)
                        OnDataReceived(m_btInput, len);
                   
                    m_stream.BeginRead(m_btInput, 0, m_btInput.Length, new AsyncCallback(readCallback), m_stream);
                }
                catch (Exception e)
                {
                    MessageBox.Show("readCallback\n" + e.Message);
                }
            }
        }

//--

//send command

public void WriteData(byte[] data, int iLength)
        {
            if (m_stream != null && m_stream.CanWrite)
            {
                try
                {
                    if (data != null && m_stream != null && !bBeginWrite)
                    {
                        m_stream.BeginWrite(data, 0, iLength, new AsyncCallback(writeCallback), m_stream);

                        bBeginWrite = true;
                    }
                    else
                    {
                    }
                }
                catch (Exception e)
                {
                    MessageBox.Show("WriteData\n" + e.Message);
                }

            }
        }

//--

 

//write callback

private void writeCallback(IAsyncResult result)
        {
            if (m_stream != null)
            {
                try
                {
                    if (bBeginWrite)
                    {
                        bBeginWrite = false;
                        m_stream.EndWrite(result);
                    }
                }
                catch (Exception e)
                {
                    MessageBox.Show("writeCallback\n" + e.Message);
                }
            }
        }

//--

Can you Help me?

Best regards

Alberto

Developer
Nov 26, 2012 at 7:58 PM

Do you get some response callbacks? Or is the read callback simply never called?

 

You re-use m_btInput in the next BeginRead when it has also been passed to the callback. What's to stop new data being written to it when the callback is still accessing it? You may want to clone the respective buffer to pass to the callback. e.g.

// untested, uncompiled, etc, etc.
var copyBuf = new byte[len];
Array.Copy((m_btInput, 0, copyBuf, 0, len); //check this!!!
OnDataReceived(copyBuf, len);

 

I don't like:  :-)

m_BT.Connect(device.DeviceAddress, device.InstalledServices[0]);

You are better to specify the UUID of the service you want to speak to and not rely on the content of some list of services -- what if the order of the list changes.

Also are you sure you are connecting to the correct service? Most services one connects to with BluetoothClient will NOT appear in InstalledServices -- which contains services Windows itself is talking to.

Nov 27, 2012 at 4:38 AM
Dear alanjmcf,

Thanks for your Help!
I will try your code.
So, for service how can I check right service?

Best regards

Alberto
Nov 27, 2012 at 6:45 AM

Dear alanjmcf,

I tried your code in readcallback.

I think I have a problem in write callback, because when I send a write command sometimes write callback is never call.

My communication start with send command (WriteData) to Bluetooth device. The Bluetooth device should reply with a string in readcallback.

In my OnDatareceive event I check end of trasmission, after that I can send another command. The communication principle is like the serial communication.

This is write data:

public void WriteData(byte[] data, int iLength)

{

if (m_stream != null && m_stream.CanWrite)

{

try

{

if (data != null && m_stream != null && !bBeginWrite)

{

m_stream.BeginWrite(data, 0, iLength, new AsyncCallback(writeCallback), m_stream);

bBeginWrite = true;

}

else

{

}

}

catch (Exception e)

{

MessageBox.Show("WriteData\n" + e.Message);

}

}

}

And this is write call back:

private void writeCallback(IAsyncResult result)

{

if (m_stream != null)

{

try

{

if (bBeginWrite)

{

bBeginWrite = false;

m_stream.EndWrite(result);

}

}

catch (Exception e)

{

MessageBox.Show("writeCallback\n" + e.Message);

}

}

}

I use m_Stream that is assigned at connect:

m_btInput = new byte[1024];

m_BT.Connect(device.DeviceAddress, SPP_GUID);

m_stream = m_BT.GetStream();

m_stream.BeginRead(m_btInput, 0, m_btInput.Length, new AsyncCallback(readCallback), m_stream);

Best regards

Alberto

Nov 29, 2012 at 7:23 PM
I need help

Thanks

Alberto
Nov 30, 2012 at 10:44 PM

here is my windows mobile stream handler class. it does all your connects, disconnects, reads and writes. this class would need small mods to use the full framework waithandles instead of my custom pinoke class

so, put in some #defines to cross compile it...

waitHandle.WaitAny() isn't native to CF.NET !!!! stupid....

 

Imports System.IO
Imports System.Net.Sockets
Imports System.Threading
Imports InTheHand.Net
Imports InTheHand.Net.Sockets
Imports System.Text

Namespace Cat.CatHardwareInterface

    ''' <summary>
    ''' This class handles the raw serial communication to the MAT hardware.
    ''' This class also handles the connection and disconnection of the hardware.
    ''' </summary>
    ''' <remarks></remarks>
    Public Class BluetoothStreamHandler
        Implements IDisposable

#Region "Events"
        Public Delegate Sub ConnectionStatusEventHandler(ByVal sender As Object, ByVal e As CatHardwareConnectionStatusEventArgs)
        Public Delegate Sub ResponseDataReceivedEventHandler(ByVal sender As Object, ByVal e As ResponeDataReceivedEventArgs)

        ''' <summary>
        ''' This event is raised upon every successful response packet (string) which
        ''' is delimited terminated by a vbCrLf sequence
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="e"></param>
        ''' <remarks></remarks>
        Public Event ResponseDataReceived As ResponseDataReceivedEventHandler
        ''' <summary>
        ''' This event indicates the connection status of the serial handler. This event represents the
        ''' true current connection state to the end-point device.
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="e"></param>
        ''' <remarks></remarks>
        Public Event ConnectionStatus As ConnectionStatusEventHandler
#End Region

        Private logger As NLog.Logger = NLog.LogManager.GetLogger("CatMatSerialHandler")

        Private Enum connectionSource
            eNone
            eStream
            ePort
            eBluetooth
        End Enum

        Private ReadOnly _pineCode As String = ""
        Private ReadOnly _portName As String = ""
        Private ReadOnly _inputStream As IO.Stream = Nothing
        Private ReadOnly _bluetoothDeviceAddress As InTheHand.Net.BluetoothAddress = Nothing
        Private ReadOnly _bluetoothSyncLock As Object = Nothing
        Private ReadOnly _source As connectionSource = connectionSource.eNone
        Private ReadOnly _readTimeout As Integer = 90000

        Private WithEvents _serialPortHandle As IO.Ports.SerialPort
        Private WithEvents _endpointStream As IO.Stream

        Private WithEvents _bluetoothClient As BluetoothClient

        Private _connected As Boolean = False
        Private _writeWaitHandle As New ManualResetWaitEvent(True)


        ''' <summary>
        ''' Initialises the Serial Handler to read from a serial port, by name
        ''' </summary>
        ''' <param name="port"></param>
        ''' <remarks></remarks>
        Sub New(ByVal port As String)
            _source = connectionSource.ePort
            _portName = port

            logger.Log(NLog.LogLevel.Trace, "New CatMatSerialHandler to serial port name:{0}", port)

            InitiateSerialPort(port)
        End Sub

        ''' <summary>
        ''' Initialises the Serial Handler to read from a defined stream. This removes the ability of this
        ''' class to handle reconnections
        ''' </summary>
        ''' <param name="stream"></param>
        ''' <remarks></remarks>
        Sub New(ByVal stream As IO.Stream)
            _source = connectionSource.eStream
            _inputStream = stream

            logger.Log(NLog.LogLevel.Trace, "New CatMatSerialHandler to IO.Stream")

            InitialiseStream(stream)
        End Sub

        ''' <summary>
        ''' Initialises the serial handler to read from a bluetooth stream
        ''' </summary>
        ''' <param name="DeviceAddress"></param>
        ''' <param name="syncLock"></param>
        ''' <remarks></remarks>
        Sub New(ByVal DeviceAddress As BluetoothAddress, ByVal [syncLock] As Object, ByVal pinecode As String)
            _source = connectionSource.eBluetooth
            _bluetoothDeviceAddress = DeviceAddress
            _bluetoothSyncLock = [syncLock]
            _pineCode = pinecode

            logger.Log(NLog.LogLevel.Trace, "New CatMatSerialHandler to Bluetooth address: {0}", DeviceAddress.ToString)

            '            InitiateBluetooth(_bluetoothDeviceAddress, 10000, _pineCode)
        End Sub

        ''' <summary>
        ''' Initialise with an active client connection
        ''' </summary>
        ''' <param name="client"></param>
        ''' <remarks></remarks>
        Sub New(ByVal client As BluetoothClient, ByVal pincode As String, ByVal [synclock] As Object)
            _source = connectionSource.eBluetooth
            _bluetoothClient = client
            _bluetoothDeviceAddress = client.RemoteEndPoint.Address
            If [synclock] Is Nothing Then
                _bluetoothSyncLock = New Object
            Else
                _bluetoothSyncLock = [synclock]
            End If
            _pineCode = pincode
            client.SetPin(_pineCode)
            'client.LingerState.Enabled = False

            logger.Log(NLog.LogLevel.Trace, "New CatMatSerialHandler to Bluetooth Client name:{0} address:{1}", client.RemoteMachineName, client.RemoteEndPoint.Address.ToString)

            ' Connect the streams
            Connect()
        End Sub

#Region "Properties"
        ''' <summary>
        ''' Gets the RSSI value for the connected device
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property RSSI() As Integer
            Get
                Dim info As New BluetoothDeviceInfo(_bluetoothDeviceAddress)

                If info.Connected Then
                    Dim rs As Integer = info.Rssi
                    If rs > -128 And rs < 128 Then
                        Return rs
                    Else
                        Return 0
                    End If
                Else
                    Return 0
                End If
            End Get
        End Property

        ''' <summary>
        ''' Returns the current connection status
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Connected() As Boolean
            Get
                Return _connected
            End Get
            Set(ByVal value As Boolean)
                _connected = value
            End Set
        End Property

        Private _enabled As Boolean = False

        ''' <summary>
        ''' Gets or Sets if this connection should be disabled or not
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Enabled() As Boolean
            Get
                Return _enabled
            End Get
            Set(ByVal value As Boolean)
                _enabled = value
            End Set
        End Property

#End Region

#Region "Private Functions"

        ''' <summary>
        ''' This function connects the SerialHandler to the end-point device.
        ''' </summary>
        ''' <exception cref="CatHardwareInterfaceConnectionException">Thrown if a connection is not possible</exception>
        ''' <param name="Timeout"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Connect(Optional ByVal Timeout As Integer = -1) As Boolean
            Try
                Select Case _source
                    Case connectionSource.eBluetooth
                        Connected = ConnectBluetooth(Timeout)
                    Case connectionSource.ePort
                        Connected = ConnectSerialPort(Timeout)
                    Case connectionSource.eStream
                        Connected = ConnectStream(Timeout)
                    Case Else
                        Connected = False
                End Select
            Catch ex As InvalidOperationException
                Connected = False
            Catch ex As CatHardwareInterfaceConnectionException
                Connected = False
            Finally
                ' Startup the threads if possible
                If Connected Then
                    ' Log the connection success
                    StartStreamThreads()
                    ' Raise event for connection status
                    RaiseEvent ConnectionStatus(Me, New CatHardwareConnectionStatusEventArgs(eCatConnectionStatus.Disconnected, eCatConnectionStatus.Connected, _connected, "Connected"))
                End If
            End Try

            Return Connected
        End Function

        ''' <summary>
        ''' This routines attempts to make a bluetooth connection in another thread
        ''' </summary>
        ''' <param name="params"></param>
        ''' <remarks></remarks>
        Private Sub CallConnectBluetooth(ByVal params As Object)
            Dim wtHandle As ManualResetWaitEvent = CType(params, ManualResetWaitEvent)

            Thread.CurrentThread.Name = "BT Connection Thread"

            Try
                If _bluetoothClient IsNot Nothing OrElse InitiateBluetooth(_bluetoothDeviceAddress, 0, _pineCode) Then
                    If Not _bluetoothClient.Connected Then
                        ' Connect to the bluetooth device
                        _bluetoothClient.Connect(_bluetoothDeviceAddress, Bluetooth.BluetoothService.SerialPort)

                        ' Flag the waiting process that we're good to go
                        wtHandle.Set()
                    End If

                End If
            Catch ex As Exception
                ' Do nothing about this

            Finally
                ' Clean up the handle
                wtHandle.Dispose()

            End Try
        End Sub

        ''' <summary>
        ''' Connect to the bluetooth device
        ''' </summary>
        ''' <exception cref="CatHardwareInterfaceConnectionException">Thrown if unable to connect</exception>
        ''' <param name="Timeout"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function ConnectBluetooth(Optional ByVal Timeout As Integer = -1) As Boolean
            Dim handle As New ManualResetWaitEvent(False)

            Monitor.Enter(_bluetoothSyncLock)

            ' Run the connection to bluetooth
            ThreadPool.QueueUserWorkItem(AddressOf CallConnectBluetooth, handle)

            ' Wait for the connection to occur
            If handle.WaitOne(Timeout) Then
                If _bluetoothClient.Connected Then
                    _endpointStream = _bluetoothClient.GetStream

                    Monitor.Exit(_bluetoothSyncLock)

                    ' We're all good!
                    Return True
                End If
            Else
                ' Timeout occured, just dispose the client and let it all clear own
                Try
                    _bluetoothClient.Dispose()
                Catch ex As Exception
                    ' Do nothing
                Finally
                    _bluetoothClient = Nothing
                End Try

            End If

            Monitor.Exit(_bluetoothSyncLock)

            Return False
        End Function

        ''' <summary>
        ''' Connect to the serial port. This will open the serial port, as it has already
        ''' been initialised.
        ''' </summary>
        ''' <param name="Timeout"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function ConnectSerialPort(Optional ByVal Timeout As Integer = -1) As Boolean
            If Not _serialPortHandle Is Nothing AndAlso Not _serialPortHandle.IsOpen Then
                Try
                    ' Open the serial port
                    _serialPortHandle.Open()
                    ' Save the stream handle
                    _endpointStream = _serialPortHandle.BaseStream

                    logger.Log(NLog.LogLevel.Trace, "Serial port successfuly opened")

                    Return True
                Catch ex As Exception
                    Throw New CatHardwareInterfaceConnectionException("Unable to connect to serial port", ex)
                End Try
            End If

            Return False
        End Function

        ''' <summary>
        ''' Connect the inputStream as the IO.Stream to read/write from
        ''' </summary>
        ''' <param name="Timeout"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function ConnectStream(Optional ByVal Timeout As Integer = -1) As Boolean
            _endpointStream = _inputStream

            Return _inputStream.CanRead
        End Function

        ''' <summary>
        ''' Close the current connection. Closes the underlying IO.stream for the serial handler, and all associated threads
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Disconnect(Optional ByVal Reason As String = "No Reason") As Boolean
            If Connected Then
                Try
                    ' First things first.. get to disconnected
                    Connected = False

                    Select Case _source
                        Case connectionSource.eBluetooth
                            ' Close down the connected stream
                            _endpointStream.Close()
                            _bluetoothClient.Close()
                            '_bluetoothClient.Dispose()
                            _bluetoothClient = Nothing
                        Case connectionSource.ePort
                            If Not _serialPortHandle Is Nothing AndAlso Not _serialPortHandle.IsOpen Then
                                ' Close the serial port
                                _serialPortHandle.Close()
                            End If
                        Case connectionSource.eStream
                            ' Do nothing with this one.. fairly transparent to open/closes apart from internal workings.
                    End Select
                Catch ex As Exception
                    logger.LogException(NLog.LogLevel.Error, "Exception raised on disconnection", ex)
                Finally
                    logger.Log(NLog.LogLevel.Trace, "CatMatSerialHandler closed with reson:'{0}'", Reason)

                    ' Clear all the privates
                    _endpointStream = Nothing

                    ' Stop all the pending threads
                    StopStreamThreads()

                    RaiseEvent ConnectionStatus(Me, New CatHardwareConnectionStatusEventArgs(eCatConnectionStatus.Connected, eCatConnectionStatus.Disconnected, _connected, "Disconnected"))
                End Try
            Else
                logger.Log(NLog.LogLevel.Trace, "Disconnection called on already disconnected serial handler, reason: '{0}'", Reason)
            End If

            Return True
        End Function

        ''' <summary>
        ''' Initiates the serial port with all correct settings
        ''' </summary>
        ''' <param name="SerialPort"></param>
        ''' <remarks></remarks>
        Private Function InitiateSerialPort(ByVal SerialPort As String) As Boolean
            Try
                _serialPortHandle = New System.IO.Ports.SerialPort(SerialPort, 57600, IO.Ports.Parity.None, 8, IO.Ports.StopBits.One)
                ' Initialise the end of line character to carage return
                _serialPortHandle.NewLine = Chr(13)
                _serialPortHandle.Encoding = System.Text.Encoding.GetEncoding("iso-8859-1")
                ' Open the port
                Return True
            Catch ex As UnauthorizedAccessException
                _serialPortHandle.Dispose()
                _serialPortHandle = Nothing
            End Try

            Return False
        End Function

        ''' <summary>
        ''' Initialise the stream as the input mechanism
        ''' </summary>
        ''' <param name="stream"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function InitialiseStream(ByVal stream As IO.Stream) As Boolean
            _endpointStream = stream

            Return True
        End Function

        ''' <summary>
        ''' Initialise Bluetooth as the input mechanism
        ''' </summary>
        ''' <param name="Address"></param>
        ''' <param name="Timeout"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function InitiateBluetooth(ByVal Address As BluetoothAddress, ByVal Timeout As Integer, ByVal pincode As String) As Boolean
            Dim pairSuccess As Boolean = False

            If _bluetoothClient Is Nothing Then
                _bluetoothClient = New BluetoothClient()

                ' Set the pin here
                '_bluetoothClient.SetPin(pincode)

                '' Attempt a pair request multiple times until successful
                Try
                    ' Just try and remove it... simple :)
                    Try
                        Bluetooth.BluetoothSecurity.RemoveDevice(_bluetoothDeviceAddress)
                    Catch ex As Exception

                    End Try

                    'Bluetooth.BluetoothSecurity.SetPin(_bluetoothDeviceAddress, _pineCode)

                    pairSuccess = Bluetooth.BluetoothSecurity.PairRequest(_bluetoothDeviceAddress, _pineCode)

                    'Dim pairAttempts As Integer = 0

                    'While Not pairSuccess And pairAttempts < 3

                    '    If Bluetooth.BluetoothSecurity.PairRequest(_bluetoothDeviceAddress, _pineCode) Then
                    '        pairSuccess = True
                    '    Else
                    '        pairSuccess = False
                    '    End If

                    '    pairAttempts += 1
                    'End While

                Catch ex As Exception

                End Try

            End If

            Return pairSuccess
        End Function

#Region "Read/Write Callbacks"

        Private _receiveWaitHandle As AutoResetWaitEvent
        Private _streamErrorWaitHandle As ManualResetWaitEvent
        Private _receiveContinueWaitHandle As AutoResetWaitEvent
        Private _streamThreadsShutdown As ManualResetWaitEvent
        Private _receiveLock As New Object
        Private _receiveBuffer(5000) As Byte
        Private _receiveBufferOffset As Integer = 0
        Private _receiveString As New StringBuilder
        Private _streamReadThreadRunning As Boolean = False
        Private _streamReadThreadRunningHandle As ManualResetWaitEvent
        Private _streamWriteResult As IAsyncResult

        Private Sub StartStreamThreads()
            If Not _streamReadThreadRunning Then
                ' Start the Stream Read Thread
                _receiveWaitHandle = New AutoResetWaitEvent(False)
                _streamErrorWaitHandle = New ManualResetWaitEvent(False)
                _receiveContinueWaitHandle = New AutoResetWaitEvent(False)
                _streamThreadsShutdown = New ManualResetWaitEvent(False)
                _streamReadThreadRunningHandle = New ManualResetWaitEvent(False)
                _streamReadThreadRunning = True
                ' Start the thread
                ThreadPool.QueueUserWorkItem(AddressOf StreamReadThread)
            End If
        End Sub

        Private Sub StopStreamThreads()
            If _streamThreadsShutdown IsNot Nothing Then
                ' Set all the threads to shutdown
                _streamThreadsShutdown.Set()

                Try
                    _streamReadThreadRunningHandle.WaitOne(3000)
                Catch ex As Exception
                    ' Catch all exception from this thread execution stoppage
                    ' Nlog the results
                Finally
                    _streamReadThreadRunningHandle.Dispose()
                    _streamReadThreadRunningHandle = Nothing
                End Try
            End If
        End Sub

        Private Sub StreamReadThread()
            Dim result As IAsyncResult
            Dim finished As Boolean = False
            Dim searching As Boolean = True

            logger.Log(NLog.LogLevel.Trace, "Stream Read Thread Started")

            Thread.CurrentThread.Name = Me.GetType.Name & ":Stream Read Thread"

            _streamReadThreadRunningHandle.Reset()

            Try

                ' Attempt to read a packet
                If Not _endpointStream Is Nothing AndAlso _endpointStream.CanRead Then
                    result = _endpointStream.BeginRead(_receiveBuffer, 0, _receiveBuffer.Length, New AsyncCallback(AddressOf StreamReadCallBack), _endpointStream)

                    While Not finished
                        Select Case EventWaitHandle2.WaitAny(New EventWaitHandle2() {_streamThreadsShutdown, _receiveWaitHandle, _streamErrorWaitHandle}, _readTimeout)
                            Case 0 ' Read error wait handle OR Shutdown request
                                ' Clean up any open connections
                                ' Cause the exit of all read threads
                                ' Inform people that this thread is clearly not running any more

                                ' Set finished flag
                                finished = True
                                OnConnectionStatus(eCatConnectionStatus.Disconnected, False, "Shutdown request received")

                            Case 1 ' Read data procesed wait handle
                                Dim buffer As String
                                SyncLock _receiveLock
                                    buffer = _receiveString.ToString()
                                End SyncLock

                                Dim count As Integer = 0

                                While buffer.Contains(vbCrLf)
                                    ' Find the index of the vbCrLf
                                    Dim length As Integer = buffer.IndexOf(vbCrLf) + 2
                                    ' Strip the vbCrLf
                                    Dim response As String = buffer.Substring(0, length - 2)

                                    ' Write the response to log
                                    'logger.Log(NLog.LogLevel.Debug, "Stream Read: '{0}'", response)

                                    ' Increment the count of characters removed
                                    count += length

                                    ' Raise the event for data recieved
                                    OnResponseData(response)

                                    ' Remove the response from the receive buffer
                                    If length >= buffer.Length Then
                                        buffer = ""
                                    Else
                                        buffer = buffer.Substring(length)
                                    End If
                                End While

                                ' Remove all the processed data
                                If count Then
                                    SyncLock _receiveLock
                                        _receiveString.Remove(0, count)
                                    End SyncLock
                                End If

                            Case 2
                                ' Clean up any open connections
                                ' Cause the exit of all read threads
                                ' Inform people that this thread is clearly not running any more

                                ' Set finished flag
                                finished = True
                                OnConnectionStatus(eCatConnectionStatus.Disconnected, False, "Read error occured")

                            Case EventWaitHandle2.WaitTimeout ' No data for an extended period
                                ' Tell the waiting process that this connection is most probably dead
                                OnConnectionStatus(eCatConnectionStatus.ConnectedExtendedIdle, True, "Long Read Idle Time")

                        End Select
                    End While
                End If
            Finally
                ' We're no longer connected by a long shot
                Connected = False

                ' Set the not running handle, this is for the shutdown request
                _streamReadThreadRunning = False

                ' If we came from a shutdown request, then we need to clear down by responding
                ' with the runningHandle.Set()
                ' Otherwise we need to dispose these handles
                If _streamThreadsShutdown.WaitOne(0) Then
                    _streamReadThreadRunningHandle.Set()
                Else
                    _streamReadThreadRunningHandle.Dispose()
                    _streamReadThreadRunningHandle = Nothing
                End If

                ' Clean up all the wait handles
                _receiveWaitHandle.Dispose()
                _streamErrorWaitHandle.Dispose()
                _receiveContinueWaitHandle.Dispose()
                _streamThreadsShutdown.Dispose()
                _receiveWaitHandle = Nothing
                _streamErrorWaitHandle = Nothing
                _receiveContinueWaitHandle = Nothing
                _streamThreadsShutdown = Nothing

                logger.Log(NLog.LogLevel.Trace, "Stream Read Thread Finished")
            End Try

        End Sub

        ''' <summary>
        ''' Raises the connection status event
        ''' </summary>
        ''' <param name="mode"></param>
        ''' <param name="connected"></param>
        ''' <param name="message"></param>
        ''' <remarks></remarks>
        Protected Sub OnConnectionStatus(ByVal mode As eCatConnectionStatus, ByVal connected As Boolean, ByVal message As String)
            RaiseEvent ConnectionStatus(Me, New CatHardwareConnectionStatusEventArgs( _
                    If(mode = eCatConnectionStatus.Disconnected, _
                       eCatConnectionStatus.Connected, _
                       eCatConnectionStatus.Disconnected), mode, connected, message))
        End Sub

        ''' <summary>
        ''' Raises the ResponseData event
        ''' </summary>
        ''' <param name="response"></param>
        ''' <remarks></remarks>
        Protected Sub OnResponseData(ByVal response As String)
            RaiseEvent ResponseDataReceived(Me, New ResponeDataReceivedEventArgs(response))
        End Sub

        ''' <summary>
        ''' Read stream Async Callback
        ''' </summary>
        ''' <param name="result"></param>
        ''' <remarks></remarks>
        Private Sub StreamReadCallBack(ByVal result As IAsyncResult)
            Dim stream As IO.Stream = CType(result.AsyncState, IO.Stream)

            Try
                Dim bytesRead As Integer

                ' If stream has been closed due to an error, we'll have an excpetion here
                bytesRead = stream.EndRead(result)

                ' Append to the buffer
                If bytesRead > 0 Then
                    SyncLock _receiveLock
                        ' Increment the offset in the buffer
                        _receiveBufferOffset += bytesRead

                        _receiveString.Append(New System.Text.UTF8Encoding().GetString(_receiveBuffer, 0, bytesRead))
                    End SyncLock

                    ' Signal data received
                    If _receiveWaitHandle IsNot Nothing Then
                        _receiveWaitHandle.Set()

                        ' Start a new async read
                        _endpointStream.BeginRead(_receiveBuffer, 0, _receiveBuffer.Length, New AsyncCallback(AddressOf StreamReadCallBack), _endpointStream)
                    End If
                Else
                    logger.Log(NLog.LogLevel.Trace, "Zero bytes returned from blocking read")
                    Throw New IOException("Zero bytes returned from blocking read")
                End If

            Catch ex As ObjectDisposedException
                logger.Debug("StreamReadCallBack: Finished")
                ' Set the stream error event
                If _streamErrorWaitHandle IsNot Nothing Then
                    Me._streamErrorWaitHandle.Set()
                End If

            Catch ex As IOException
                logger.Debug("StreamReadCallBack: Finished")
                ' Set the stream error event
                If _streamErrorWaitHandle IsNot Nothing Then
                    Me._streamErrorWaitHandle.Set()
                End If

            Catch ex As InvalidOperationException
                logger.Debug("StreamReadCallBack: Finished")
                ' Set the stream error event
                If _streamErrorWaitHandle IsNot Nothing Then
                    Me._streamErrorWaitHandle.Set()
                End If

            End Try

        End Sub

        Private Sub StreamWriteCallBack(ByVal result As IAsyncResult)
            Dim stream As IO.Stream = CType(result.AsyncState, IO.Stream)

            Try
                stream.EndWrite(result)
            Catch ex As Exception
                logger.ErrorException("Error writing to stream", ex)
            End Try
        End Sub

#End Region

#End Region

#Region "Publics"
        Public Function Write(ByVal data As String) As Boolean
            Dim encText As New System.Text.UTF8Encoding
            Dim bytes() As Byte = encText.GetBytes(data & vbCr)

            If data.Length Then
                Try
                    If Not _endpointStream Is Nothing AndAlso _endpointStream.CanWrite Then
                        ' Start the Aysnc Write process
                        _endpointStream.BeginWrite(bytes, 0, bytes.Length, New AsyncCallback(AddressOf StreamWriteCallBack), _endpointStream)

                        ' logger.Debug("Stream Write: " & data)

                        Return True
                    Else
                        logger.Error("IO.Stream Cannot Write")
                        If _streamErrorWaitHandle IsNot Nothing Then
                            Me._streamErrorWaitHandle.Set()
                        End If

                    End If
                Catch ex As ObjectDisposedException
                    logger.ErrorException("IO.Stream is disposed", ex)
                    If _streamErrorWaitHandle IsNot Nothing Then
                        Me._streamErrorWaitHandle.Set()
                    End If

                Catch ex As IOException
                    logger.FatalException("IO.Stream produced an unexpected error!", ex)
                    If _streamErrorWaitHandle IsNot Nothing Then
                        Me._streamErrorWaitHandle.Set()
                    End If

                End Try
            End If

            Return False
        End Function
#End Region

#Region "IDisposable Support"
        Private disposedValue As Boolean ' To detect redundant calls

        ' IDisposable
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not Me.disposedValue Then
                If disposing Then
                    ' Disconnect and close all threads.
                    Me.Disconnect("CatMatSerialHandler being Disposed")
                    ' Kill off the serial port handler
                    If Not _serialPortHandle Is Nothing Then
                        _serialPortHandle.Dispose()
                    End If
                End If

            End If
            Me.disposedValue = True
        End Sub

        ' This code added by Visual Basic to correctly implement the disposable pattern.
        Public Sub Dispose() Implements IDisposable.Dispose
            ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region

    End Class
End Namespace

 

here is my waithandle class.. its also at pinvoke.com

Imports System.Runtime.InteropServices
Imports System.Threading
Imports System.Runtime
Imports System.Linq

Namespace System.Threading

    Public Class AutoResetWaitEvent
        Inherits EventWaitHandle2

        Sub New(ByVal initialState As Boolean)
            MyBase.New(initialState, EventResetMode.AutoReset)
        End Sub

        Sub New(ByVal initialState As Boolean, ByVal name As String)
            MyBase.New(initialState, EventResetMode.AutoReset, name)
        End Sub
    End Class

    Public Class ManualResetWaitEvent
        Inherits EventWaitHandle2
        Implements IDisposable

        Sub New(ByVal initialState As Boolean)
            MyBase.New(initialState, EventResetMode.ManualReset)
        End Sub

        Sub New(ByVal initialState As Boolean, ByVal name As String)
            MyBase.New(initialState, EventResetMode.ManualReset, name)
        End Sub
    End Class

    Public Class EventWaitHandle2
        Inherits WaitHandle
        Implements IDisposable

        Protected Shared _logger As NLog.Logger = NLog.LogManager.GetLogger("EventWaitHandle")
        Protected Shared _openCount As Integer = 0

        Public Shared Function OpenExisting(ByVal name As String) As System.Threading.EventWaitHandle2
            Return New EventWaitHandle2(Win32Api.OpenEvent(Win32Api.EVENT_ALL_ACCESS, False, name))
        End Function

        Public Function Reset() As Boolean
            Return Win32Api.EventModify(Me.Handle, Win32Api.EventFlags.EVENT_RESET)
        End Function

        Public Function [Set]() As Boolean
            Return Win32Api.EventModify(Me.Handle, Win32Api.EventFlags.EVENT_SET)
        End Function

        Public Sub New(ByVal initialState As Boolean, ByVal mode As System.Threading.EventResetMode)
            Me.New(initialState, mode, Nothing)
        End Sub

        Public Sub New(ByVal initialState As Boolean, ByVal mode As System.Threading.EventResetMode, ByVal name As String)
            Me.New(Win32Api.CreateEvent(IntPtr.Zero, mode = EventResetMode.ManualReset, initialState, name))
            If (InteropServices.Marshal.GetLastWin32Error() <> Win32Api.ERROR_ALREADY_EXISTS) Then
                _openCount += 1
            End If
        End Sub

        Public Sub New(ByVal initialState As Boolean, ByVal mode As System.Threading.EventResetMode, ByVal name As String, <OutAttribute()> ByRef createdNew As Boolean)
            Dim h As IntPtr = Win32Api.CreateEvent(IntPtr.Zero, mode = EventResetMode.ManualReset, initialState, name)
            If h.Equals(IntPtr.Zero) Then Throw New ApplicationException("Cannot create " & name)
            createdNew = (InteropServices.Marshal.GetLastWin32Error() = Win32Api.ERROR_ALREADY_EXISTS)
            Me.Handle = h
            If createdNew Then
                _openCount += 1
            End If
        End Sub

        Public Overloads Overrides Function WaitOne() As Boolean
            Return WaitOne(-1, False)
        End Function

        Public Overloads Function WaitOne(ByVal millisecondsTimeout As Int32) As Boolean
            Return (Win32Api.WaitForSingleObject(Me.Handle, millisecondsTimeout) <> Win32Api.WAIT_TIMEOUT)
        End Function

        Public Overloads Function WaitOne(ByVal aTs As TimeSpan) As Boolean
            Return (Win32Api.WaitForSingleObject(Me.Handle, aTs.Milliseconds) <> Win32Api.WAIT_TIMEOUT)
        End Function

        Public Overloads Function WaitOne(ByVal millisecondsTimeout As Int32, ByVal notForCEApplications As Boolean) As Boolean
            Return (Win32Api.WaitForSingleObject(Me.Handle, millisecondsTimeout) <> Win32Api.WAIT_TIMEOUT)
        End Function

        Public Overloads Function WaitOne(ByVal aTs As TimeSpan, ByVal notForCEApplications As Boolean) As Boolean
            Return (Win32Api.WaitForSingleObject(Me.Handle, aTs.Milliseconds) <> Win32Api.WAIT_TIMEOUT)
        End Function

        Public Overloads Shared Function WaitAny(ByVal waitHandles As EventWaitHandle2(), ByVal millisecondsTimeout As Int32) As Integer
            Dim handlePointers(waitHandles.Length - 1) As IntPtr
            For i As Integer = 0 To waitHandles.Length - 1
                handlePointers(i) = waitHandles(i).Handle
            Next

            If handlePointers.Any(Function(ptr) ptr = IntPtr.Zero Or ptr = New IntPtr(-1)) Then
                _logger.Fatal("Null Handle Recieved")
                'Throw New ApplicationException("Null Handle recieved")

                Return -1
            Else
                ' Perform the waiting
                Dim watch As New Stopwatch()
                watch.Start()
                Dim val As Integer = Win32Api.WaitForMultipleObjects(handlePointers.Length, handlePointers, False, millisecondsTimeout)
                watch.Stop()
                If val = -1 Then
                    _logger.Fatal("WaitAny() Error {0}", InteropServices.Marshal.GetLastWin32Error())
                    Throw New ApplicationException("Error on WaitAny")
                End If

                Return val
            End If
        End Function

        ''' <summary>
        ''' Closes the class
        ''' </summary>
        ''' <remarks></remarks>
        Public Overloads Sub Dispose()
            Me.dispose(True)
            GC.SuppressFinalize(Me)
        End Sub

        Private disposedValue As Boolean = False ' To detect redundant calls

        ''' <summary>
        ''' This handles the explicit closing of the event handle
        ''' </summary>
        ''' <param name="explicitDisposing"></param>
        ''' <remarks></remarks>
        Protected Overrides Sub Dispose(ByVal explicitDisposing As Boolean)
            If Not disposedValue Then
                If explicitDisposing Then
                    Close()
                End If

                ' Save the disposed status
                disposedValue = True
            End If
        End Sub

        Public Const WaitTimeout As Integer = Win32Api.WAIT_TIMEOUT

        Public Overrides Sub Close()
            If Me.Handle = IntPtr.Zero Or Me.Handle = New IntPtr(-1) Then
                Throw New ApplicationException("Closing an already closed Wait Handle")
            Else
                Win32Api.CloseHandle(Me.Handle)
                Me.Handle = New IntPtr(-1)
                _openCount -= 1
            End If

        End Sub

        Private Sub New(ByVal aHandle As IntPtr)
            MyBase.New()
            If aHandle.Equals(IntPtr.Zero) Then Throw New ApplicationException("CreateEvent failed")
            Me.Handle = aHandle
        End Sub

        Protected Overrides Sub Finalize()
            If Me.Handle <> IntPtr.Zero And Me.Handle <> New IntPtr(-1) Then
                Win32Api.CloseHandle(Me.Handle)
            End If

        End Sub
#Region "PInvokes"
        Private Class Win32Api
            Public Const WAIT_TIMEOUT As Int32 = &H102          '258
            Public Const EVENT_ALL_ACCESS As Int32 = &H3
            Public Const ERROR_ALREADY_EXISTS As Int32 = 183

            Public Enum EventFlags
                EVENT_RESET = 2
                EVENT_SET = 3
            End Enum
            <DllImportAttribute("coredll.dll", SetLastError:=True)> _
            Public Shared Function EventModify(ByVal hEvent As IntPtr, ByVal ef As EventFlags) As Boolean
            End Function
            <DllImportAttribute("coredll.dll", SetLastError:=True)> _
            Public Shared Function WaitForSingleObject(ByVal hHandle As IntPtr, ByVal dwMilliseconds As Int32) As Int32
            End Function
            <DllImportAttribute("coredll.dll", SetLastError:=True)> _
            Public Shared Function CreateEvent(ByVal lpEventAttributes As IntPtr, ByVal bManualReset As Boolean, ByVal bInitialState As Boolean, ByVal lpName As String) As IntPtr
            End Function
            <DllImportAttribute("coredll.dll", SetLastError:=True)> _
            Public Shared Function OpenEvent(ByVal dwDesiredAccess As Int32, ByVal bInheritHandle As Boolean, ByVal lpName As String) As IntPtr
            End Function
            <DllImportAttribute("coredll.dll", SetLastError:=True)> _
            Public Shared Function CloseHandle(ByVal hObject As IntPtr) As Boolean
            End Function
            <DllImportAttribute("coredll.dll", SetLastError:=True)> _
            Public Shared Function WaitForMultipleObjects(ByVal count As Integer, ByVal handle() As IntPtr, ByVal waitAll As Boolean, ByVal milliseconds As Integer) As Integer
            End Function
        End Class
#End Region
    End Class

    Public Enum EventResetMode
        AutoReset = 0
        ManualReset = 1
    End Enum
End Namespace

my usage of Linq may require .NET3.5+

Developer
Dec 2, 2012 at 7:25 PM

What's your platform? Where are you running your code, on the PC? What's the stack Microsoft, Widcomm, BlueSoleil etc?

I've seen cases where the necessary command termination isn't be sent e.g. the target is Hayes-compatible and code isn't sending the necessary '\r' terminator. That would cause the remote device to not respond -- but the write callback _would_ be called.

Dec 3, 2012 at 5:35 AM

Hi,

I use Visual Studio 2008. The application is .Net framework 3.5.

The Write CallBack is called but after few send commands is no longer.

If you have an e xample for write and read stream is better for me.

Best regards

Alberto Piccinin

Developer
Dec 3, 2012 at 7:41 PM

Where are you running your code, on the PC?

What's the stack Microsoft, Widcomm, BlueSoleil etc?

 

There's a few things I'd consider doing.

1. Write a very simple application that tests the communications with the remote device and doesn't have all the features of the main application. In particular it is very very very difficult to debug with Begin/End reads and writes. Use the normal blocking read and write methods in that app.

2. Add logging to my app to show exactly what each read and write is receiving/sending.

3. Write automatic tests for my code. I write test for all my comms code to ensure I'm sending what I think I am etc. e.g.

byte[] expectedSends = { ... };
byte[] responses = { ... };
var peer = new FakeStream(responses);
...run the code to test...
Assert.AreEqual(expectedSends, peer.StoredSends);

And get that to complete without fault.

Dec 4, 2012 at 5:00 AM

Hi,

my application run under Windows 7 64 bit.

Best regards

Alberto Piccinin