diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e675e46..7a67043a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.0) cmake_policy(SET CMP0017 NEW) # need include() with .cmake project(pip) set(_PIP_MAJOR 1) -set(_PIP_MINOR 15) -set(_PIP_REVISION 1) +set(_PIP_MINOR 16) +set(_PIP_REVISION 0) set(_PIP_SUFFIX alpha) set(_PIP_COMPANY SHS) set(_PIP_DOMAIN org.SHS) @@ -358,7 +358,7 @@ if(NOT PIP_FREERTOS) if(WIN32) if(${C_COMPILER} STREQUAL "cl.exe") else() - list(APPEND LIBS_MAIN ws2_32 iphlpapi psapi) + list(APPEND LIBS_MAIN ws2_32 iphlpapi psapi cfgmgr32 SetupAPI) endif() else() list(APPEND LIBS_MAIN dl) diff --git a/main.cpp b/main.cpp index ed0d1135..e7c406b4 100644 --- a/main.cpp +++ b/main.cpp @@ -112,15 +112,17 @@ private: */ -PIKbdListener kbd; +PIKbdListener kbd(0, 0, false); + + int main(int argc, char * argv[]) { - /*PIStringList dl = PISerial::availableDevices(); - piCout << dl; - PISerial ser(dl[0]); - piCout << ser.open(PIIODevice::ReadWrite) << &ser; - */ - PIConfig conf("d:/orders/nicirt/bin/spec_core.conf", PIIODevice::ReadOnly); - piCout << conf.allTree(); + //piCout << PISerial::availableDevicesInfo(); + PIIODevice * d = PIIODevice::createFromFullPath(argv[1]); + piCout << d; + if (d) { + d->open(); + piCout << d->constructFullPath() << d->isOpened(); + } return 0; } diff --git a/src_main/io_devices/piconfig.cpp b/src_main/io_devices/piconfig.cpp index 258b79cf..d0452e4f 100755 --- a/src_main/io_devices/piconfig.cpp +++ b/src_main/io_devices/piconfig.cpp @@ -49,7 +49,7 @@ * debug = true * \endcode * - * You can use multiline values ends with " \\" + * You can use multiline values ends with " \" * \code * value = start \ #s comment * _mid \ diff --git a/src_main/io_devices/piserial.cpp b/src_main/io_devices/piserial.cpp index bf81dda2..a83691c6 100755 --- a/src_main/io_devices/piserial.cpp +++ b/src_main/io_devices/piserial.cpp @@ -23,10 +23,10 @@ #include "pipropertystorage.h" #include -#if defined(WINDOWS) || defined(FREERTOS) +#if defined(FREERTOS) # define PISERIAL_NO_PINS #endif -#ifdef PISERIAL_NO_PINS +#if defined(PISERIAL_NO_PINS) || defined(WINDOWS) # define TIOCM_LE 1 # define TIOCM_DTR 4 # define TIOCM_RTS 7 @@ -38,7 +38,19 @@ # define TIOCM_DSR 6 #endif #ifdef WINDOWS +# ifndef INITGUID +# define INITGUID +# include +# undef INITGUID +# else +# include +# endif +# include # include +# include +# include +# include +# include # define B50 50 # define B75 75 # define B110 110 @@ -130,7 +142,13 @@ * \section PISerial_sec0 Synopsis * This class provide access to serial device, e.g. COM port. It can read, * write, wait for write. There are several read and write functions. - * + * + * \section PISerial_sec0 FullPath + * Since version 1.16.0 you can use as \a path DeviceInfo::id() USB identifier. + * \code + * PISerial * s = new PISerial("0403:6001"); + * PIIODevice * d = PIIODevice::createFromFullPath("ser://0403:6001:115200"); + * \endcode * */ @@ -149,6 +167,21 @@ PRIVATE_DEFINITION_START(PISerial) PRIVATE_DEFINITION_END(PISerial) + + +PISerial::DeviceInfo::DeviceInfo() { + vID = pID = 0; +} + + +PIString PISerial::DeviceInfo::id() const { + return PIString::fromNumber(vID, 16).toLowerCase().expandLeftTo(4, '0') + ":" + + PIString::fromNumber(pID, 16).toLowerCase().expandLeftTo(4, '0'); +} + + + + PISerial::PISerial(): PIIODevice("", ReadWrite) { construct(); } @@ -255,37 +288,51 @@ bool PISerial::isDSR() const {return isBit(TIOCM_DSR, "DSR");} bool PISerial::setBit(int bit, bool on, const PIString & bname) { -#ifndef PISERIAL_NO_PINS if (fd < 0) { piCoutObj << "setBit" << bname << " error: \"" << path() << "\" is not opened!"; return false; } +#ifndef PISERIAL_NO_PINS +# ifdef WINDOWS + static int bit_map_on [] = {0, 0, 0, 0, SETDTR, 0, 0, SETRTS, 0, 0, 0}; + static int bit_map_off[] = {0, 0, 0, 0, CLRDTR, 0, 0, CLRRTS, 0, 0, 0}; + int action = (on ? bit_map_on : bit_map_off)[bit]; + if (action > 0) { + if (EscapeCommFunction(PRIVATE->hCom, action) == 0) { + piCoutObj << "setBit" << bname << " error: " << errorString(); + return false; + } + return true; + } +# else if (ioctl(fd, on ? TIOCMBIS : TIOCMBIC, &bit) < 0) { piCoutObj << "setBit" << bname << " error: " << errorString(); return false; } return true; -#else +# endif +#endif piCoutObj << "setBit" << bname << " doesn`t implemented, sorry :-("; return false; -#endif } bool PISerial::isBit(int bit, const PIString & bname) const { -#ifndef PISERIAL_NO_PINS if (fd < 0) { piCoutObj << "isBit" << bname << " error: \"" << path() << "\" is not opened!"; return false; } +#ifndef PISERIAL_NO_PINS +# ifdef WINDOWS +# else int ret = 0; if (ioctl(fd, TIOCMGET, &ret) < 0) piCoutObj << "isBit" << bname << " error: " << errorString(); return ret & bit; -#else +# endif +#endif piCoutObj << "isBit" << bname << " doesn`t implemented, sorry :-("; return false; -#endif } @@ -296,28 +343,6 @@ void PISerial::flush() { } -bool PISerial::closeDevice() { - if (isRunning() && !isStopping()) { - stop(); - PIThread::terminate(); - } - if (fd != -1) { -#ifdef WINDOWS - SetCommState(PRIVATE->hCom, &PRIVATE->sdesc); - SetCommMask(PRIVATE->hCom, PRIVATE->mask); -// piCoutObj << "close" << - CloseHandle(PRIVATE->hCom); - PRIVATE->hCom = 0; -#else - tcsetattr(fd, TCSANOW, &PRIVATE->sdesc); - ::close(fd); -#endif - fd = -1; - } - return true; -} - - int PISerial::convertSpeed(PISerial::Speed speed) { switch (speed) { case S50: return B50; @@ -509,16 +534,31 @@ bool PISerial::send(const void * data, int size) { bool PISerial::openDevice() { - //piCout << "ser open" << path(); - if (path().isEmpty()) return false; + PIString p = path(); + //piCout << "ser open" << p; + PIString pl = p.toLowerCase().removeAll(' '); + if (!pl.startsWith("/") && !pl.startsWith("com")) { + p.clear(); + PIVector devs = availableDevicesInfo(); + piForeachC (DeviceInfo & d, devs) { + if (d.id() == pl) { + p = d.path; + piBreak; + } + } + if (p.isEmpty()) { + piCoutObj << "Unable to find device \"" << pl << "\""; + } + } + if (p.isEmpty()) return false; #ifdef WINDOWS DWORD ds = 0, sm = 0; if (isReadable()) {ds |= GENERIC_READ; sm |= FILE_SHARE_READ;} if (isWriteable()) {ds |= GENERIC_WRITE; sm |= FILE_SHARE_WRITE;} - PIString wp = "//./" + path(); - PRIVATE->hCom = CreateFileA(wp.data(), ds, sm, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); + PIString wp = "//./" + p; + PRIVATE->hCom = CreateFileA(wp.dataAscii(), ds, sm, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); if (PRIVATE->hCom == INVALID_HANDLE_VALUE) { - piCoutObj << "Unable to open \"" << path() << "\""; + piCoutObj << "Unable to open \"" << p << "\""; fd = -1; return false; } @@ -530,21 +570,42 @@ bool PISerial::openDevice() { case PIIODevice::WriteOnly: om = O_WRONLY; break; case PIIODevice::ReadWrite: om = O_RDWR; break; } - //cout << "init ser " << path_ << " mode " << om << " param " << params << endl; - fd = ::open(path().data(), O_NOCTTY | om); + fd = ::open(p.data(), O_NOCTTY | om); if (fd == -1) { - piCoutObj << "Unable to open \"" << path() << "\""; + piCoutObj << "Unable to open \"" << p << "\""; return false; } tcgetattr(fd, &PRIVATE->desc); PRIVATE->sdesc = PRIVATE->desc; - //piCoutObj << "Initialized " << path_; + //piCoutObj << "Initialized " << p; #endif applySettings(); return true; } +bool PISerial::closeDevice() { + if (isRunning() && !isStopping()) { + stop(); + PIThread::terminate(); + } + if (fd != -1) { +#ifdef WINDOWS + SetCommState(PRIVATE->hCom, &PRIVATE->sdesc); + SetCommMask(PRIVATE->hCom, PRIVATE->mask); +// piCoutObj << "close" << + CloseHandle(PRIVATE->hCom); + PRIVATE->hCom = 0; +#else + tcsetattr(fd, TCSANOW, &PRIVATE->sdesc); + ::close(fd); +#endif + fd = -1; + } + return true; +} + + void PISerial::applySettings() { #ifdef WINDOWS if (fd == -1) return; @@ -718,6 +779,13 @@ PIString PISerial::constructFullPathDevice() const { void PISerial::configureFromFullPathDevice(const PIString & full_path) { PIStringList pl = full_path.split(":"); + if (pl.size_s() > 1) { + PIString _s = pl[0].toLowerCase().removeAll(' ').trim(); + if (!_s.startsWith("/") && !_s.startsWith("com")) { + pl[0] += ":" + pl[1]; + pl.remove(1); + } + } for (int i = 0; i < pl.size_s(); ++i) { PIString p(pl[i]); switch (i) { @@ -792,24 +860,134 @@ PIVector PISerial::availableSpeeds() { PIStringList PISerial::availableDevices(bool test) { - PIStringList dl; + PIVector devs = availableDevicesInfo(test); + PIStringList ret; + piForeachC (DeviceInfo & d, devs) + ret << d.path; + return ret; +} + + #ifdef WINDOWS - HKEY key = 0; +PIString devicePortName(HDEVINFO deviceInfoSet, PSP_DEVINFO_DATA deviceInfoData) { + PIString ret; + const HKEY key = SetupDiOpenDevRegKey(deviceInfoSet, deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); + if (key == INVALID_HANDLE_VALUE) + return ret; + static const wchar_t * const keyTokens[] = { + L"PortName\0", + L"PortNumber\0" + }; + static const int keys_count = sizeof(keyTokens) / sizeof(keyTokens[0]); + for (int i = 0; i < keys_count; ++i) { + DWORD dataType = 0; + DWORD bytesRequired = MAX_PATH; + PIVector outputBuffer(MAX_PATH + 1); + for (;;) { + LONG res = RegQueryValueExW(key, keyTokens[i], NULL, &dataType, (LPBYTE)outputBuffer.data(), &bytesRequired); + if (res == ERROR_MORE_DATA) { + outputBuffer.resize(bytesRequired / sizeof(wchar_t) + 2); + continue; + } else if (res == ERROR_SUCCESS) { + if (dataType == REG_SZ) + ret = PIString(outputBuffer.data()); + else if (dataType == REG_DWORD) + ret = PIStringAscii("COM") + PIString::fromNumber(*(PDWORD(&outputBuffer[0]))); + } + break; + } + if (!ret.isEmpty()) + break; + } + RegCloseKey(key); + return ret; +} + + +PIString deviceRegistryProperty(HDEVINFO deviceInfoSet, PSP_DEVINFO_DATA deviceInfoData, DWORD property) { + DWORD dataType = 0; + DWORD bytesRequired = MAX_PATH; + PIVector outputBuffer(MAX_PATH + 1); + for (;;) { + if (SetupDiGetDeviceRegistryPropertyW(deviceInfoSet, deviceInfoData, property, &dataType, (PBYTE)outputBuffer.data(), bytesRequired, &bytesRequired)) + break; + if ((GetLastError() != ERROR_INSUFFICIENT_BUFFER) || (dataType != REG_SZ && dataType != REG_EXPAND_SZ)) + return PIString(); + outputBuffer.resize(bytesRequired / sizeof(wchar_t) + 2, 0); + } + return PIString(outputBuffer.data()); +} + + +PIString deviceInstanceIdentifier(DEVINST deviceInstanceNumber) { + PIVector outputBuffer(MAX_DEVICE_ID_LEN + 1); + if (CM_Get_Device_IDW(deviceInstanceNumber, (PWCHAR)outputBuffer.data(), MAX_DEVICE_ID_LEN, 0) != CR_SUCCESS) { + return PIString(); + } + return PIString(outputBuffer.data()); +} + + +bool parseID(PIString str, PISerial::DeviceInfo & di) { + if (str.isEmpty()) return false; + int i = str.find("VID_"); + if (i > 0) di.vID = str.mid(i + 4, 4).toInt(16); + i = str.find("PID_"); + if (i > 0) di.pID = str.mid(i + 4, 4).toInt(16); + return (di.vID > 0) && (di.pID > 0); +} +#endif + + +PIVector PISerial::availableDevicesInfo(bool test) { + PIVector ret; + DeviceInfo di; +#ifdef WINDOWS + /*HKEY key = 0; RegOpenKey(HKEY_LOCAL_MACHINE, (LPCTSTR)"HARDWARE\\DEVICEMAP\\SERIALCOMM", &key); if (key != 0) { char name[1024], data[1024]; DWORD index = 0; - LONG ret = ERROR_SUCCESS; - while (ret != ERROR_NO_MORE_ITEMS) { + LONG res = ERROR_SUCCESS; + while (res != ERROR_NO_MORE_ITEMS) { memset(name, 0, 1024); memset(data, 0, 1024); DWORD name_len = 1024, data_len = 1024, type = 0; - ret = RegEnumValue(key, index, (LPTSTR)name, &name_len, NULL, &type, (uchar * )data, &data_len); - if (ret == ERROR_NO_MORE_ITEMS) break; - dl << PIString(data, data_len).trim(); + res = RegEnumValue(key, index, (LPTSTR)name, &name_len, NULL, &type, (uchar * )data, &data_len); + if (res == ERROR_NO_MORE_ITEMS) break; + di.path = PIString(data, data_len).trim(); + ret << di; index++; } RegCloseKey(key); + }*/ + static const GUID guids[] = {GUID_DEVINTERFACE_MODEM, GUID_DEVINTERFACE_COMPORT}; + static const int guids_cnt = sizeof(guids) / sizeof(GUID); + for (int i = 0; i < guids_cnt; ++i) { + const HDEVINFO dis = SetupDiGetClassDevs(&(guids[i]), NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (dis == INVALID_HANDLE_VALUE) continue; + SP_DEVINFO_DATA did; + memset(&did, 0, sizeof(did)); + did.cbSize = sizeof(did); + DWORD index = 0; + while (SetupDiEnumDeviceInfo(dis, index++, &did)) { + di = DeviceInfo(); + di.path = devicePortName(dis, &did); + if (!di.path.startsWith("COM")) continue; + di.description = deviceRegistryProperty(dis, &did, SPDRP_DEVICEDESC); + di.manufacturer = deviceRegistryProperty(dis, &did, SPDRP_MFG); + PIString id_str = deviceInstanceIdentifier(did.DevInst); + if (!parseID(id_str, di)) { + DEVINST pdev = 0; + if (CM_Get_Parent(&pdev, did.DevInst, 0) == CR_SUCCESS) { + id_str = deviceInstanceIdentifier(pdev); + parseID(id_str, di); + } + } + ret << di; + //piCout << "dev" << did.DevInst << di; + } + SetupDiDestroyDeviceInfoList(dis); } #else # ifndef ANDROID @@ -849,24 +1027,51 @@ PIStringList PISerial::availableDevices(bool test) { # endif PIDir dir("/dev"); PIVector de = dir.entries(); +# ifdef LINUX + char linkbuf[1024]; +# endif piForeachC (PIFile::FileInfo & e, de) { // TODO changes in FileInfo piForeachC (PIString & p, prefixes) { - if (e.name().startsWith(p)) - dl << e.path; + if (e.name().startsWith(p)) { + di = DeviceInfo(); + di.path = e.path; +# ifdef LINUX + ssize_t lsz = readlink(("/sys/class/tty/" + e.name()).dataAscii(), linkbuf, 1024); + if (lsz > 0) { + PIString fpath = "/sys/class/tty/" + PIString(linkbuf, lsz) + "/"; + PIFile _f; + for (int i = 0; i < 5; ++i) { + fpath += "../"; + //piCout << "try" << fpath; + if (_f.open(fpath + "idVendor", PIIODevice::ReadOnly)) + di.vID = PIString(_f.readAll()).trim().toInt(16); + if (_f.open(fpath + "idProduct", PIIODevice::ReadOnly)) + di.pID = PIString(_f.readAll()).trim().toInt(16); + if (_f.open(fpath + "product", PIIODevice::ReadOnly)) + di.description = PIString(_f.readAll()).trim(); + if (_f.open(fpath + "manufacturer", PIIODevice::ReadOnly)) + di.manufacturer = PIString(_f.readAll()).trim(); + if (di.pID > 0) + break; + } + } +# endif + ret << di; + } } } # endif #endif if (test) { - for (int i = 0; i < dl.size_s(); ++i) { + for (int i = 0; i < ret.size_s(); ++i) { #ifdef WINDOWS - void * hComm = CreateFileA(dl[i].data(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); + void * hComm = CreateFileA(ret[i].path.dataAscii(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); if (hComm == INVALID_HANDLE_VALUE) { #else - int fd = ::open(dl[i].data(), O_NOCTTY | O_RDONLY); + int fd = ::open(ret[i].path.dataAscii(), O_NOCTTY | O_RDONLY); if (fd == -1) { #endif - dl.remove(i); + ret.remove(i); --i; continue; } @@ -889,7 +1094,7 @@ PIStringList PISerial::availableDevices(bool test) { #endif if (!rok) { - dl.remove(i); + ret.remove(i); --i; continue; } @@ -900,7 +1105,7 @@ PIStringList PISerial::availableDevices(bool test) { #endif } } - return dl; + return ret; } diff --git a/src_main/io_devices/piserial.h b/src_main/io_devices/piserial.h index 685926c9..ec9b6035 100755 --- a/src_main/io_devices/piserial.h +++ b/src_main/io_devices/piserial.h @@ -71,6 +71,29 @@ public: S4000000 /*! 4000000 baud */ = 4000000 }; + //! \brief Information about serial device + struct DeviceInfo { + DeviceInfo(); + + //! \brief String representation of USB ID in format \"xxxx:xxxx\" + PIString id() const; + + //! \brief USB Vendor ID + uint vID; + + //! \brief USB Product ID + uint pID; + + //! \brief Path to device, e.g. "COM2" or "/dev/ttyUSB0" + PIString path; + + //! \brief Device description + PIString description; + + //! \brief Device manufacturer + PIString manufacturer; + }; + //! Contructs %PISerial with device name "device", speed "speed" and parameters "params" explicit PISerial(const PIString & device, PISerial::Speed speed = S115200, PIFlags params = 0); @@ -173,9 +196,12 @@ public: //! \brief Returns all available speeds for serial devices static PIVector availableSpeeds(); - //! \brief Returns all available system devices. If "test" each device will be tried to open + //! \brief Returns all available system devices path. If "test" each device will be tried to open static PIStringList availableDevices(bool test = false); + //! \brief Returns all available system devices. If "test" each device will be tried to open + static PIVector availableDevicesInfo(bool test = false); + //! \ioparams //! \{ #ifdef DOXYGEN @@ -232,4 +258,11 @@ protected: }; + +inline PICout operator <<(PICout s, const PISerial::DeviceInfo & v) { + s << v.path << " (" << v.id() << ", \"" << v.manufacturer << "\", \"" << v.description << "\")"; + return s; +} + + #endif // PISERIAL_H