#include "Serial.h" #ifdef WIN32 #include typedef DWORD REGSAM; #ifdef USE_SETUPAPI #include #endif NAMESPACE_UPP dword Serial::stdBauds[] = { 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 128000, 256000 }; int Serial::stdBaudsCount = sizeof ( Serial::stdBauds ) / sizeof ( dword ); // constructor Serial::Serial() { isError = false; errCode = Ok; fd = INVALID_HANDLE_VALUE; } Serial::Serial ( String const &port, unsigned long speed, byte parity, byte bits, byte stopBits ) { isError = false; errCode = Ok; fd = INVALID_HANDLE_VALUE; Open ( port, speed, parity, bits, stopBits ); } // destructor Serial::~Serial() { Close(); } // open the port bool Serial::Open ( String const &port, dword speed, byte parity, byte bits, byte stopBits ) { // open the device fd = CreateFile ( "\\\\.\\" + port, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0 ); if ( fd == INVALID_HANDLE_VALUE ) { isError = true; errCode = DeviceError; return false; } DCB dcb; char dcbSz[50]; COMMTIMEOUTS cmt; // clear dcb and set length FillMemory ( &dcb, sizeof ( dcb ), 0 ); dcb.DCBlength = sizeof ( dcb ); // check baud is valid if ( GetStandardBaudRates().Find ( speed ) < 0 ) { Close(); isError = true; errCode = InvalidSpeed; return false; } // check parity */ char cParity; switch ( parity ) { case ParityNone : cParity = 'n'; break; case ParityEven : cParity = 'e'; break; case ParityOdd : cParity = 'o'; break; case ParityMark : case ParitySpace : default: { Close(); isError = true; errCode = InvalidParity; return false; } } // check data bits if ( bits < 5 || bits > 8 ) { Close(); isError = true; errCode = InvalidSize; return false; } // check stop bits if ( stopBits < 1 || stopBits > 2 ) { Close(); isError = true; errCode = InvalidStopBits; return false; } // build dcb sprintf_s ( dcbSz, 49, "%d,%c,%d,%d", speed, cParity, bits, stopBits ); if ( !BuildCommDCB ( dcbSz, &dcb ) ) { Close(); isError = true; errCode = DeviceError; return false; } /* dcb.BaudRate = speed; if(stopBits == 1) dcb.StopBits = ONESTOPBIT; else dcb.StopBits = TWOSTOPBITS; switch(parity) { case ParityNone: dcb.Parity = PARITY_NONE; break; case ParityEven: dcb.Parity = PARITY_EVEN; break; case ParityOdd: dcb.Parity = PARITY_ODD; break; default: break; } switch(bits) { case 5: dcb.ByteSize = DATABITS_5; break; case 7: dcb.ByteSize = DATABITS_7; break; case 8: dcb.ByteSize = DATABITS_8; break; default: break; } dcb.fDtrControl = 0; dcb.fRtsControl = 0; */ // set dcb to serial port */ if ( !SetCommState ( fd, &dcb ) ) { Close(); isError = true; errCode = DeviceError; return false; } // set input and output bufffer sizes if ( !SetupComm ( fd, 1024, 1024 ) ) { Close(); isError = true; errCode = DeviceError; return false; } // set timeouts to 0 -- non blocking cmt.ReadIntervalTimeout = MAXDWORD; cmt.ReadTotalTimeoutMultiplier = 0; cmt.ReadTotalTimeoutConstant = 0; cmt.WriteTotalTimeoutMultiplier = 0; cmt.WriteTotalTimeoutConstant = MAXDWORD; if ( !SetCommTimeouts ( fd, &cmt ) ) { Close(); isError = true; errCode = DeviceError; return false; } return true; } // close the port void Serial::Close ( void ) { if ( fd == INVALID_HANDLE_VALUE ) return; // cancel pending i/o CancelIo ( fd ); // close the device CloseHandle ( fd ); fd = INVALID_HANDLE_VALUE; } // control DTR and RTS lines bool Serial::SetDTR(bool on) { if(on) EscapeCommFunction(hComPort, SETDTR); else EscapeCommFunction(hComPort, CLRDTR); return true; } bool Serial::SetRTS(bool on) { if(on) EscapeCommFunction(hComPort, SETRTS); else EscapeCommFunction(hComPort, CLRRTS); return true; } // flush data bool Serial::FlushInput ( void ) { return PurgeComm ( fd, PURGE_RXCLEAR ); } bool Serial::FlushOutput ( void ) { return PurgeComm ( fd, PURGE_TXCLEAR ); } bool Serial::FlushAll ( void ) { return PurgeComm ( fd, PURGE_RXCLEAR ) & PurgeComm ( fd, PURGE_TXCLEAR ); } // check if data is available on serial port int Serial::Avail(void) { dword errors; COMSTAT stat; if(!ClearCommError(fd, &errors, &stat)) return 0; return stat.cbInQue; ); // read a single byte, block 'timeout' milliseconds bool Serial::Read ( byte &c, uint32_t timeout ) { char buf[1]; unsigned long count = 0; uint32_t tim = ( uint32_t ) msecs() + timeout; for ( ;; ) { count = 0; ReadFile ( fd, buf, 1, &count, NULL ); if ( !count ) { if ( ( uint32_t ) msecs() > tim ) return false; continue; } c = ( byte ) buf[0]; return true; } } bool Serial::Read ( char &c, uint32_t timeout ) { char buf[1]; unsigned long count = 0; uint32_t tim = ( uint32_t ) msecs() + timeout; for ( ;; ) { count = 0; ReadFile ( fd, buf, 1, &count, NULL ); if ( !count ) { if ( ( uint32_t ) msecs() > tim ) return false; continue; } c = buf[0]; return true; } } // write a single byte bool Serial::Write ( char c, uint32_t timeout ) { unsigned long count = 0; if ( !timeout ) { WriteFile ( fd, &c, 1, &count, NULL ); return count == 1; } else { uint32_t tim = msecs() + timeout; for ( ;; ) { WriteFile ( fd, &c, 1, &count, NULL ); if ( count == 1 ) return true; if ( ( uint32_t ) msecs() > tim ) break; } return false; } } // read data, requested amount, blocks 'timeout' milliseconds // if reqSize == 0 just read all available data, waiting for 'timeout' if != 0 String Serial::Read ( size_t reqSize, uint32_t timeout ) { char buf[1001]; String res; unsigned long count = 0; unsigned long n; uint32_t tim = ( uint32_t ) msecs() + timeout; if ( reqSize ) { n = min ( reqSize, ( size_t ) 1000 ); while ( reqSize ) { count = 0; ReadFile ( fd, buf, n, &count, NULL ); if ( !count ) { if ( ( uint32_t ) msecs() > tim ) break; continue; } tim = ( uint32_t ) msecs() + timeout; if ( count ) { reqSize -= count; res.Cat ( buf, count ); } n = min ( reqSize, ( size_t ) 1000 ); } } else { for ( ;; ) { count = 0; ReadFile ( fd, buf, 1000, &count, NULL ); if ( !count ) { if ( ( uint32_t ) msecs() > tim ) break; continue; } tim = ( uint32_t ) msecs() + timeout; if ( count ) res.Cat ( buf, count ); } } return res; } // writes data bool Serial::Write ( String const &data, uint32_t timeout ) { unsigned long count = 0; int dLen = data.GetCount(); if ( !timeout ) { WriteFile ( fd, ~data, dLen, &count, NULL ); return count == dLen; } uint32_t tim = msecs() + timeout; const char *dPos = ~data; for ( ;; ) { WriteFile ( fd, dPos, dLen, &count, NULL ); if ( count == dLen ) return true; if ( count > 0 ) { dPos += count; dLen -= count; continue; } if ( ( uint32_t ) msecs() >= tim ) break; } return false; } // check if opened bool Serial::IsOpened ( void ) const { return fd != INVALID_HANDLE_VALUE; } /////////////////////////////////////////////////////////////////////////////////////////////////////// #ifdef USE_SETUPAPI // ENUMERATE PORTS USING SETUPAPI #ifndef GUID_DEVINTERFACE_COMPORT DEFINE_GUID ( GUID_DEVINTERFACE_COMPORT, 0x86E0D1E0L, 0x8089, 0x11D0, 0x9C, 0xE4, 0x08, 0x00, 0x3E, 0x30, 0x1F, 0x73 ); #endif typedef HKEY ( __stdcall SETUPDIOPENDEVREGKEY ) ( HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM ); typedef BOOL ( __stdcall SETUPDICLASSGUIDSFROMNAME ) ( LPCTSTR, LPGUID, DWORD, PDWORD ); typedef BOOL ( __stdcall SETUPDIDESTROYDEVICEINFOLIST ) ( HDEVINFO ); typedef BOOL ( __stdcall SETUPDIENUMDEVICEINFO ) ( HDEVINFO, DWORD, PSP_DEVINFO_DATA ); typedef HDEVINFO ( __stdcall SETUPDIGETCLASSDEVS ) ( LPGUID, LPCTSTR, HWND, DWORD ); typedef BOOL ( __stdcall SETUPDIGETDEVICEREGISTRYPROPERTY ) ( HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD ); class CAutoHModule { public: CAutoHModule() : m_hModule ( NULL ), m_dwError ( ERROR_SUCCESS ) {} explicit CAutoHModule ( HMODULE hModule ) : m_hModule ( hModule ), m_dwError ( GetLastError() ) {} explicit CAutoHModule ( HMODULE hModule, DWORD dwError ) : m_hModule ( hModule ), m_dwError ( dwError ) {} ~CAutoHModule() { if ( m_hModule != NULL ) { FreeLibrary ( m_hModule ); m_hModule = NULL; } SetLastError ( m_dwError ); } operator HMODULE() { return m_hModule; } HMODULE m_hModule; DWORD m_dwError; }; static HMODULE LoadLibraryFromSystem32 ( LPCTSTR lpFileName ) { //Get the Windows System32 directory TCHAR szFullPath[_MAX_PATH]; szFullPath[0] = _T ( '\0' ); if ( ::GetSystemDirectory ( szFullPath, _countof ( szFullPath ) ) == 0 ) return NULL; //Setup the full path and delegate to LoadLibrary #pragma warning(suppress: 6102) //There is a bug with the SAL annotation of GetSystemDirectory in the Windows 8.1 SDK _tcscat_s ( szFullPath, _countof ( szFullPath ), _T ( "\\" ) ); _tcscat_s ( szFullPath, _countof ( szFullPath ), lpFileName ); return LoadLibrary ( szFullPath ); } static BOOL RegQueryValueString ( HKEY kKey, LPCTSTR lpValueName, LPTSTR& pszValue ) { //Initialize the output parameter pszValue = NULL; //First query for the size of the registry value DWORD dwType = 0; DWORD dwDataSize = 0; LONG nError = RegQueryValueEx ( kKey, lpValueName, NULL, &dwType, NULL, &dwDataSize ); if ( nError != ERROR_SUCCESS ) { SetLastError ( nError ); return FALSE; } //Ensure the value is a string if ( dwType != REG_SZ ) { SetLastError ( ERROR_INVALID_DATA ); return FALSE; } //Allocate enough bytes for the return value DWORD dwAllocatedSize = dwDataSize + sizeof ( TCHAR ); //+sizeof(TCHAR) is to allow us to NULL terminate the data if it is not null terminated in the registry pszValue = reinterpret_cast ( LocalAlloc ( LMEM_FIXED, dwAllocatedSize ) ); if ( pszValue == NULL ) return FALSE; //Recall RegQueryValueEx to return the data pszValue[0] = _T ( '\0' ); DWORD dwReturnedSize = dwAllocatedSize; nError = RegQueryValueEx ( kKey, lpValueName, NULL, &dwType, reinterpret_cast ( pszValue ), &dwReturnedSize ); if ( nError != ERROR_SUCCESS ) { LocalFree ( pszValue ); pszValue = NULL; SetLastError ( nError ); return FALSE; } //Handle the case where the data just returned is the same size as the allocated size. This could occur where the data //has been updated in the registry with a non null terminator between the two calls to ReqQueryValueEx above. Rather than //return a potentially non-null terminated block of data, just fail the method call if ( dwReturnedSize >= dwAllocatedSize ) { SetLastError ( ERROR_INVALID_DATA ); return FALSE; } //NULL terminate the data if it was not returned NULL terminated because it is not stored null terminated in the registry if ( pszValue[dwReturnedSize/sizeof ( TCHAR ) - 1] != _T ( '\0' ) ) pszValue[dwReturnedSize/sizeof ( TCHAR ) ] = _T ( '\0' ); return TRUE; } static BOOL IsNumeric ( LPCSTR pszString, BOOL bIgnoreColon ) { size_t nLen = strlen ( pszString ); if ( nLen == 0 ) return FALSE; //What will be the return value from this function (assume the best) BOOL bNumeric = TRUE; for ( size_t i = 0; i < nLen && bNumeric; i++ ) { bNumeric = ( isdigit ( static_cast ( pszString[i] ) ) != 0 ); if ( bIgnoreColon && ( pszString[i] == ':' ) ) bNumeric = TRUE; } return bNumeric; } static BOOL IsNumeric ( LPCWSTR pszString, BOOL bIgnoreColon ) { size_t nLen = wcslen ( pszString ); if ( nLen == 0 ) return FALSE; //What will be the return value from this function (assume the best) BOOL bNumeric = TRUE; for ( size_t i = 0; i < nLen && bNumeric; i++ ) { bNumeric = ( iswdigit ( pszString[i] ) != 0 ); if ( bIgnoreColon && ( pszString[i] == L':' ) ) bNumeric = TRUE; } return bNumeric; } static BOOL QueryRegistryPortName ( HKEY hDeviceKey, int& nPort ) { //What will be the return value from the method (assume the worst) BOOL bAdded = FALSE; //Read in the name of the port LPTSTR pszPortName = NULL; if ( RegQueryValueString ( hDeviceKey, _T ( "PortName" ), pszPortName ) ) { //If it looks like "COMX" then //add it to the array which will be returned size_t nLen = _tcslen ( pszPortName ); if ( nLen > 3 ) { if ( ( _tcsnicmp ( pszPortName, _T ( "COM" ), 3 ) == 0 ) && IsNumeric ( ( pszPortName + 3 ), FALSE ) ) { //Work out the port number nPort = _ttoi ( pszPortName + 3 ); bAdded = TRUE; } } LocalFree ( pszPortName ); } return bAdded; } // get a list of all serial ports ArrayMap Serial::GetSerialPorts ( void ) { ArrayMap res; //Get the various function pointers we require from setupapi.dll CAutoHModule setupAPI ( LoadLibraryFromSystem32 ( _T ( "SETUPAPI.DLL" ) ) ); if ( setupAPI == NULL ) return res; SETUPDIOPENDEVREGKEY* lpfnSETUPDIOPENDEVREGKEY = reinterpret_cast ( GetProcAddress ( setupAPI, "SetupDiOpenDevRegKey" ) ); /* #if defined _UNICODE */ SETUPDIGETCLASSDEVS* lpfnSETUPDIGETCLASSDEVS = reinterpret_cast ( GetProcAddress ( setupAPI, "SetupDiGetClassDevsW" ) ); SETUPDIGETDEVICEREGISTRYPROPERTY* lpfnSETUPDIGETDEVICEREGISTRYPROPERTY = reinterpret_cast ( GetProcAddress ( setupAPI, "SetupDiGetDeviceRegistryPropertyW" ) ); /* #else SETUPDIGETCLASSDEVS* lpfnSETUPDIGETCLASSDEVS = reinterpret_cast(GetProcAddress(setupAPI, "SetupDiGetClassDevsA")); SETUPDIGETDEVICEREGISTRYPROPERTY* lpfnSETUPDIGETDEVICEREGISTRYPROPERTY = reinterpret_cast(GetProcAddress(setupAPI, "SetupDiGetDeviceRegistryPropertyA")); #endif */ SETUPDIDESTROYDEVICEINFOLIST* lpfnSETUPDIDESTROYDEVICEINFOLIST = reinterpret_cast ( GetProcAddress ( setupAPI, "SetupDiDestroyDeviceInfoList" ) ); SETUPDIENUMDEVICEINFO* lpfnSETUPDIENUMDEVICEINFO = reinterpret_cast ( GetProcAddress ( setupAPI, "SetupDiEnumDeviceInfo" ) ); if ( ( lpfnSETUPDIOPENDEVREGKEY == NULL ) || ( lpfnSETUPDIDESTROYDEVICEINFOLIST == NULL ) || ( lpfnSETUPDIENUMDEVICEINFO == NULL ) || ( lpfnSETUPDIGETCLASSDEVS == NULL ) || ( lpfnSETUPDIGETDEVICEREGISTRYPROPERTY == NULL ) ) { //Set the error to report setupAPI.m_dwError = ERROR_CALL_NOT_IMPLEMENTED; return res; } //Now create a "device information set" which is required to enumerate all the ports GUID guid = GUID_DEVINTERFACE_COMPORT; HDEVINFO hDevInfoSet = lpfnSETUPDIGETCLASSDEVS ( &guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE ); if ( hDevInfoSet == INVALID_HANDLE_VALUE ) { //Set the error to report setupAPI.m_dwError = GetLastError(); return res; } //Finally do the enumeration BOOL bMoreItems = TRUE; int nIndex = 0; SP_DEVINFO_DATA devInfo; while ( bMoreItems ) { //Enumerate the current device devInfo.cbSize = sizeof ( SP_DEVINFO_DATA ); bMoreItems = lpfnSETUPDIENUMDEVICEINFO ( hDevInfoSet, nIndex, &devInfo ); if ( bMoreItems ) { //Did we find a serial port for this device BOOL bAdded = FALSE; String port, friendly; //Get the registry key which stores the ports settings HKEY hDeviceKey = lpfnSETUPDIOPENDEVREGKEY ( hDevInfoSet, &devInfo, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE ); if ( hDeviceKey != INVALID_HANDLE_VALUE ) { int nPort = 0; if ( QueryRegistryPortName ( hDeviceKey, nPort ) ) { port = Format ( "COM%d", nPort ); bAdded = TRUE; } //Close the key now that we are finished with it RegCloseKey ( hDeviceKey ); } //If the port was a serial port, then also try to get its friendly name if ( bAdded ) { TCHAR szFriendlyName[1024]; szFriendlyName[0] = _T ( '\0' ); DWORD dwSize = sizeof ( szFriendlyName ); DWORD dwType = 0; if ( lpfnSETUPDIGETDEVICEREGISTRYPROPERTY ( hDevInfoSet, &devInfo, SPDRP_DEVICEDESC, &dwType, reinterpret_cast ( szFriendlyName ), dwSize, &dwSize ) && ( dwType == REG_SZ ) ) friendly = szFriendlyName; else friendly = ""; res.Add ( port, friendly ); } } ++nIndex; } //Free up the "device information set" now that we are finished with it lpfnSETUPDIDESTROYDEVICEINFOLIST ( hDevInfoSet ); return res; } #else // ENUMERATE PORTS BY REGISTI ENTRIES // get a list of all serial ports ArrayMap Serial::GetSerialPorts ( void ) { ArrayMap res; HKEY hSERIALCOMM; if ( RegOpenKeyEx ( HKEY_LOCAL_MACHINE, _T ( "HARDWARE\\DEVICEMAP\\SERIALCOMM" ), 0, KEY_QUERY_VALUE, &hSERIALCOMM ) == ERROR_SUCCESS ) { //Get the max value name and max value lengths DWORD dwMaxValueNameLen; DWORD dwMaxValueLen; DWORD dwQueryInfo = RegQueryInfoKey ( hSERIALCOMM, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dwMaxValueNameLen, &dwMaxValueLen, NULL, NULL ); if ( dwQueryInfo == ERROR_SUCCESS ) { DWORD dwMaxValueNameSizeInChars = dwMaxValueNameLen + 1; //Include space for the NULL terminator DWORD dwMaxValueNameSizeInBytes = dwMaxValueNameSizeInChars * sizeof ( TCHAR ); DWORD dwMaxValueDataSizeInChars = dwMaxValueLen / sizeof ( TCHAR ) + 1; //Include space for the NULL terminator DWORD dwMaxValueDataSizeInBytes = dwMaxValueDataSizeInChars * sizeof ( TCHAR ); //Allocate some space for the value name and value data TCHAR *valueName = (TCHAR *)malloc(dwMaxValueNameSizeInBytes + 1); TCHAR *valueData = (TCHAR *)malloc(dwMaxValueDataSizeInChars + 1); //Enumerate all the values underneath HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM DWORD dwIndex = 0; DWORD dwType; DWORD dwValueNameSize = dwMaxValueNameSizeInChars; DWORD dwDataSize = dwMaxValueDataSizeInBytes; memset (valueName, 0, dwMaxValueNameSizeInBytes ); memset (valueData, 0, dwMaxValueDataSizeInBytes ); TCHAR* szValueName = valueName; BYTE* byValue = (BYTE *)valueData; LONG nEnum = RegEnumValue (hSERIALCOMM, dwIndex, szValueName, &dwValueNameSize, NULL, &dwType, byValue, &dwDataSize ); while ( nEnum == ERROR_SUCCESS ) { //If the value is of the correct type, then add it to the array if ( dwType == REG_SZ ) { TCHAR* szPort = reinterpret_cast ( byValue ); res.Add(szPort, szPort); } //Prepare for the next time around dwValueNameSize = dwMaxValueNameSizeInChars; dwDataSize = dwMaxValueDataSizeInBytes; memset (valueName, 0, dwMaxValueNameSizeInBytes ); memset (valueData, 0, dwMaxValueDataSizeInBytes ); ++dwIndex; nEnum = RegEnumValue (hSERIALCOMM, dwIndex, szValueName, &dwValueNameSize, NULL, &dwType, byValue, &dwDataSize ); } free(valueName); free(valueData); } //Close the registry key now that we are finished with it RegCloseKey ( hSERIALCOMM ); if ( dwQueryInfo != ERROR_SUCCESS ) SetLastError ( dwQueryInfo ); } return res; } #endif END_UPP_NAMESPACE #endif