Search code examples
cwindowsbluetoothhid

How to detect if a Bluetooth HID device was disconnected?


I'm using CreateFile to open an asynchronous file handle to a Bluetooth HID device on the system. The device will then start streaming data, and I use ReadFile to read data from the device. The problem is, that if the Bluetooth connection is dropped, ReadFile just keeps giving ERROR_IO_PENDING instead of reporting a failure.

I cannot rely on timeouts, because the device doesn't send any data if there is nothing to report. I do not want it to time out if the connection is still alive, but there is simply no data for a while.

Still, the Bluetooth manager (both the Windows one and the Toshiba one) do immediately notice that the connection was lost. So this information is somewhere inside the system; it's just not getting through to ReadFile.

I have available:

  • the file handle (HANDLE value) to the device,
  • the path that was used to open that handle (but I don't want to attempt to open it another time, creating a new connection...)
  • an OVERLAPPED struct used for asynchronous ReadFile.

I am not sure if this issue is Bluetooth-specific, HID-specific, or occurs with devices in general. Is there any way that I can either

  • get ReadFile to return an error when the connection was dropped, or
  • detect quickly upon a timeout from ReadFile whether the connection is still alive (it needs to be fast because ReadFile is called at least 100 times per second), or
  • solve this problem in another way I haven't thought of?

Solution

  • You are going to have to have some sort of polling to check. Unless there is a event you can attach (I'm not familiar with the driver), the simplest way is to poll your COM port by doing a ReadFile and check is dwBytesRead > 0 when you send a command. There should be some status command you can send or you can check if you can write to the port and copy those bytes written to dwBytesWrite using WriteFile, for instance, and check if that is equal to the length of bytes you are sending. For instance:

    WriteFile(bcPort, cmd, len, &dwBytesWrite, NULL);
    if (len == dwBytesWrite) {
       // Good! Return true
    } else
       // Bad! Return false
    }
    

    This is how I do it in my application. Below may seem like a bunch of boilerplate code, but I think it will help you get to the root of your problem. I first open the Comm Port in the beginning.

    I have an array of COM ports that I maintain and check to see if they are open before writing to a particular COM port. For instance, they are opened in the beginning.

    int j;
        DWORD dwBytesRead;
    
        if (systemDetect == SYS_DEMO)  
            return;
    
        if (port <= 0 || port >= MAX_PORT)
            return;
    
        if (hComm[port]) {
            ShowPortMessage(true, 20, port, "Serial port already open:");
            return;
        }
    
        wsprintf(buff, "COM%d", port);
        hComm[port] = CreateFile(buff,
                          GENERIC_READ | GENERIC_WRITE,
                          0,    //Set of bit flags that specifies how the object can be shared
                          0,    //Security Attributes
                          OPEN_EXISTING,
                          0,    //Specifies the file attributes and flags for the file
                          0);   //access to a template file
    
        if (hComm[port] != INVALID_HANDLE_VALUE) {
            if (GetCommState(hComm[port], &dcbCommPort)) {
                if(baudrate == 9600) {
                        dcbCommPort.BaudRate = CBR_9600;//current baud rate
                } else {
                   if(baudrate == 115200) {
                       dcbCommPort.BaudRate = CBR_115200;
                   }
                }
                dcbCommPort.fBinary = 1;        //binary mode, no EOF check
                dcbCommPort.fParity = 0;        //enable parity checking
                dcbCommPort.fOutxCtsFlow = 0;   //CTS output flow control
                dcbCommPort.fOutxDsrFlow = 0;   //DSR output flow control
    //           dcbCommPort.fDtrControl = 1;    //DTR flow control type
                dcbCommPort.fDtrControl = 0;    //DTR flow control type
                dcbCommPort.fDsrSensitivity = 0;//DSR sensitivity
                dcbCommPort.fTXContinueOnXoff = 0; //XOFF continues Tx
                dcbCommPort.fOutX = 0;          //XON/XOFF out flow control
                dcbCommPort.fInX = 0;           //XON/XOFF in flow control
                dcbCommPort.fErrorChar = 0;     //enable error replacement
                dcbCommPort.fNull = 0;          //enable null stripping
                //dcbCommPort.fRtsControl = 1;  //RTS flow control
                dcbCommPort.fRtsControl = 0;    //RTS flow control
                dcbCommPort.fAbortOnError = 0;  //abort reads/writes on error
                dcbCommPort.fDummy2 = 0;        //reserved
    
                dcbCommPort.XonLim = 2048;      //transmit XON threshold
                dcbCommPort.XoffLim = 512;      //transmit XOFF threshold
                dcbCommPort.ByteSize = 8;       //number of bits/byte, 4-8
                dcbCommPort.Parity = 0;         //0-4=no,odd,even,mark,space
                dcbCommPort.StopBits = 0;       //0,1,2 = 1, 1.5, 2
                dcbCommPort.XonChar = 0x11;     //Tx and Rx XON character
                dcbCommPort.XoffChar = 0x13;    //Tx and Rx XOFF character
                dcbCommPort.ErrorChar = 0;      //error replacement character
                dcbCommPort.EofChar = 0;        //end of input character
                dcbCommPort.EvtChar = 0;        //received event character
                if (!SetCommState(hComm[port], &dcbCommPort)) {
                    setBit(SystemState, SYSTEM_PORT_ERROR);
                    //ShowPortMessage(true, 21, port, "Cannot set serial port state information:");
                    if (!CloseHandle(hComm[port])) {
                        //ShowPortMessage(true, 22, port, "Cannot close serial port:");
                    }
                    hComm[port] = NULL;
                    return;
                }
            } else {
                setBit(SystemState, SYSTEM_PORT_ERROR); 
                //ShowPortMessage(true, 29, port, "Cannot get serial port state information:");
                if (!CloseHandle(hComm[port])) {
                    //ShowPortMessage(true, 22, port, "Cannot close serial port:");
                }
                hComm[port] = NULL;
                return;
            }
    
            if (!SetupComm(hComm[port], 1024*4, 1024*2)) {
                setBit(SystemState, SYSTEM_PORT_ERROR); 
                //ShowPortMessage(true, 23, port, "Cannot set serial port I/O buffer size:");
                if (!CloseHandle(hComm[port])) {
                    //ShowPortMessage(true, 22, port, "Cannot close serial port:");
                }
                hComm[port] = NULL;
                return;
            }
    
            if (GetCommTimeouts(hComm[port], &ctmoOld)) {
                memmove(&ctmoNew, &ctmoOld, sizeof(ctmoNew));
                //default setting
                ctmoNew.ReadTotalTimeoutConstant = 100;
                ctmoNew.ReadTotalTimeoutMultiplier = 0;
                ctmoNew.WriteTotalTimeoutMultiplier = 0;
                ctmoNew.WriteTotalTimeoutConstant = 0;
                if (!SetCommTimeouts(hComm[port], &ctmoNew)) {
                    setBit(SystemState, SYSTEM_PORT_ERROR); 
                    //ShowPortMessage(true, 24, port, "Cannot set serial port timeout information:");
                    if (!CloseHandle(hComm[port])) {
                        //ShowPortMessage(true, 22, port, "Cannot close serial port:");
                    }
                    hComm[port] = NULL;
                    return;
                }
            } else {
                setBit(SystemState, SYSTEM_PORT_ERROR); 
                //ShowPortMessage(true, 25, port, "Cannot get serial port timeout information:");
                if (!CloseHandle(hComm[port])) {
                    //ShowPortMessage(true, 22, port, "Cannot close serial port:");
                }
                hComm[port] = NULL;
                return;
            }
    
            for (j = 0; j < 255; j++) {
                if (!ReadFile(hComm[port], buff, sizeof(buff), &dwBytesRead, NULL)) {
                    setBit(SystemState, SYSTEM_PORT_ERROR); 
                    //ShowPortMessage(true, 26, port, "Cannot read serial port:");
                    j = 999;    //read error
                    break;
                }
    
                if (dwBytesRead == 0)   //No data in COM buffer
                    break;
    
                Sleep(10);   //Have to sleep certain time to let hardware flush buffer
            }
    
            if (j != 999) {
                setBit(pcState[port], PORT_OPEN);
            }
        } else {
            setBit(SystemState, SYSTEM_PORT_ERROR); 
            //ShowPortMessage(true, 28, port, "Cannot open serial port:");
            hComm[port] = NULL;
        }
    
    
    HANDLE TCommPorts::OpenCommPort(void) {
    
     // OPEN THE COMM PORT.
     if (hComm)
         return NULL;  // if already open, don't bother
    
     if (systemDetect == SYS_DEMO)  
        return NULL;
    
     hComm = CreateFile(port,
                 GENERIC_READ | GENERIC_WRITE,
                 0, //Set of bit flags that specifies how the object can be shared
                 0, //Security Attributes
                 OPEN_EXISTING,
                 0, //Specifies the file attributes and flags for the file
             0);//access to a template file
    
    
    // If CreateFile fails, throw an exception. CreateFile will fail if the
    // port is already open, or if the com port does not exist.
    
    // If the function fails, the return value is INVALID_HANDLE_VALUE.
    // To get extended error information, call GetLastError.
    
     if (hComm == INVALID_HANDLE_VALUE) {
    //     throw ECommError(ECommError::OPEN_ERROR);
         return INVALID_HANDLE_VALUE;
     }
    
     // GET THE DCB PROPERTIES OF THE PORT WE JUST OPENED
     if (GetCommState(hComm, &dcbCommPort)) {
        // set the properties of the port we want to use
        dcbCommPort.BaudRate = CBR_9600;// current baud rate
        //dcbCommPort.BaudRate = CBR_115200;
        dcbCommPort.fBinary = 1;        // binary mode, no EOF check
        dcbCommPort.fParity = 0;        // enable parity checking
        dcbCommPort.fOutxCtsFlow = 0;   // CTS output flow control
        dcbCommPort.fOutxDsrFlow = 0;   // DSR output flow control
        //dcbCommPort.fDtrControl = 1;  // DTR flow control type
        dcbCommPort.fDtrControl = 0;    // DTR flow control type
        dcbCommPort.fDsrSensitivity = 0;// DSR sensitivity
        dcbCommPort.fTXContinueOnXoff = 0; // XOFF continues Tx
        dcbCommPort.fOutX = 0;          // XON/XOFF out flow control
        dcbCommPort.fInX = 0;           // XON/XOFF in flow control
        dcbCommPort.fErrorChar = 0;     // enable error replacement
        dcbCommPort.fNull = 0;          // enable null stripping
        //dcbCommPort.fRtsControl = 1;  // RTS flow control
        dcbCommPort.fRtsControl = 0;    // RTS flow control
        dcbCommPort.fAbortOnError = 0;  // abort reads/writes on error
        dcbCommPort.fDummy2 = 0;        // reserved
        dcbCommPort.XonLim = 2048;      // transmit XON threshold
        dcbCommPort.XoffLim = 512;      // transmit XOFF threshold
        dcbCommPort.ByteSize = 8;       // number of bits/byte, 4-8
        dcbCommPort.Parity = 0;         // 0-4=no,odd,even,mark,space
        dcbCommPort.StopBits = 0;       // 0,1,2 = 1, 1.5, 2
        dcbCommPort.XonChar = 0x11;     // Tx and Rx XON character
        dcbCommPort.XoffChar = 0x13;    // Tx and Rx XOFF character
        dcbCommPort.ErrorChar = 0;      // error replacement character
        dcbCommPort.EofChar = 0;        // end of input character
        dcbCommPort.EvtChar = 0;        // received event character
     }
     else
     {
     // something is way wrong, close the port and return
        CloseHandle(hComm);
        throw ECommError(ECommError::GETCOMMSTATE);
     }
    
    
     // SET BAUD RATE, PARITY, WORD SIZE, AND STOP BITS TO OUR SETTINGS.
     // REMEMBERTHAT THE ARGUMENT FOR BuildCommDCB MUST BE A POINTER TO A STRING.
     // ALSO NOTE THAT BuildCommDCB() DEFAULTS TO NO HANDSHAKING.
     //    wsprintf(portSetting, "%s,%c,%c,%c", baud, parity, databits, stopbits);
    
        dcbCommPort.DCBlength = sizeof(DCB);
    //    BuildCommDCB(portSetting, &dcbCommPort);
    
        if (!SetCommState(hComm, &dcbCommPort)) {
            // something is way wrong, close the port and return
            CloseHandle(hComm);
            throw ECommError(ECommError::SETCOMMSTATE);
        }
    
        // set the intial size of the transmit and receive queues.
        // I set the receive buffer to 32k, and the transmit buffer
        // to 9k (a default).
        if (!SetupComm(hComm, 1024*32, 1024*9))
        {
            // something is hay wire, close the port and return
            CloseHandle(hComm);
            throw ECommError(ECommError::SETUPCOMM);
        }
     // SET THE COMM TIMEOUTS.
        if (GetCommTimeouts(hComm,&ctmoOld)) {
            memmove(&ctmoNew, &ctmoOld, sizeof(ctmoNew));
            //default settings
            ctmoNew.ReadTotalTimeoutConstant = 100;
            ctmoNew.ReadTotalTimeoutMultiplier = 0;
            ctmoNew.WriteTotalTimeoutMultiplier = 0;
            ctmoNew.WriteTotalTimeoutConstant = 0;
            if (!SetCommTimeouts(hComm, &ctmoNew)) {
                // something is way wrong, close the port and return
                CloseHandle(hComm);
                throw ECommError(ECommError::SETCOMMTIMEOUTS);
            }
         } else {
            CloseHandle(hComm);
            throw ECommError(ECommError::GETCOMMTIMEOUTS);
         }
    
         return hComm;
     }