/* PIP - Platform Independent Primitives COM Copyright (C) 2014 Ivan Pelipenko peri4ko@gmail.com, Bychkov Andrey wapmobil@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "piserial.h" #include "piconfig.h" #include "pidir.h" /*! \class PISerial * \brief Serial device * * \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. * * */ REGISTER_DEVICE(PISerial); PISerial::PISerial(): PIIODevice("", ReadWrite) { _init(); } PISerial::PISerial(const PIString & device_, PISerial::Speed speed_, PIFlags params_): PIIODevice(device_, ReadWrite) { _init(); setPath(device_); setSpeed(speed_); setParameters(params_); } PISerial::~PISerial() { piMonitor.serials--; } void PISerial::_init() { fd = -1; piMonitor.serials++; setPriority(piHigh); block_read = true; vtime = 1; #ifdef WINDOWS block_write = true; hCom = 0; #endif setParameters(0); setSpeed(S115200); setDataBitsCount(8); //init(); } void PISerial::setParameter(PISerial::Parameters parameter, bool on) { PIFlags cp = (PIFlags)(property("parameters").toInt()); cp.setFlag(parameter, on); setParameters(cp); } bool PISerial::isParameterSet(PISerial::Parameters parameter) const { PIFlags cp = (PIFlags)(property("parameters").toInt()); return cp[parameter]; } bool PISerial::setPin(int number, bool on) { switch (number) { case 1: return setCAR(on); break; case 2: return setSR(on); break; case 3: return setST(on); break; case 4: return setDTR(on); break; case 5: piCoutObj << "Pin number 5 is ground"; return false; case 6: return setDSR(on); break; case 7: return setRTS(on); break; case 8: return setCTS(on); break; case 9: return setRNG(on); break; default: piCoutObj << "Pin number " << number << " doesn`t exists!"; return false; } return false; } bool PISerial::isPin(int number) const { switch (number) { case 1: return isCAR(); break; case 2: return isSR(); break; case 3: return isST(); break; case 4: return isDTR(); break; case 5: return false; case 6: return isDSR(); break; case 7: return isRTS(); break; case 8: return isCTS(); break; case 9: return isRNG(); break; default: piCoutObj << "Pin number " << number << " doesn`t exists!"; return false; } return false; } bool PISerial::setBit(int bit, bool on, const PIString & bname) { #ifndef WINDOWS if (fd < 0) { piCoutObj << "setBit" << bname << " error: \"" << path() << "\" is not opened!"; return false; } if (ioctl(fd, on ? TIOCMBIS : TIOCMBIC, &bit) < 0) { piCoutObj << "setBit" << bname << " error: " << errorString(); return false; } return true; #else piCoutObj << "setBit" << bname << " doesn`t implemented on Windows, sorry :-("; return false; #endif } bool PISerial::isBit(int bit, const PIString & bname) const { #ifndef WINDOWS if (fd < 0) { piCoutObj << "isBit" << bname << " error: \"" << path() << "\" is not opened!"; return false; } int ret = 0; if (ioctl(fd, TIOCMGET, &ret) < 0) piCoutObj << "isBit" << bname << " error: " << errorString(); return ret & bit; #else piCoutObj << "isBit" << bname << " doesn`t implemented on Windows, sorry :-("; return false; #endif } bool PISerial::closeDevice() { if (!isInitialized()) return true; if (isRunning()) { stop(); PIThread::terminate(); } if (fd != -1) { #ifdef WINDOWS SetCommState(hCom, &sdesc); SetCommMask(hCom, mask); CloseHandle(hCom); hCom = 0; #else tcsetattr(fd, TCSANOW, &sdesc); ::close(fd); #endif fd = -1; } return true; } int PISerial::convertSpeed(PISerial::Speed speed) { switch (speed) { case S50: return B50; case S75: return B75; case S110: return B110; case S300: return B300; case S600: return B600; case S1200: return B1200; case S2400: return B2400; case S4800: return B4800; case S9600: return B9600; case S19200: return B19200; case S38400: return B38400; case S57600: return B57600; case S115200: return B115200; case S1500000: return B1500000; case S2000000: return B2000000; case S2500000: return B2500000; case S3000000: return B3000000; case S3500000: return B3500000; case S4000000: return B4000000; default: break; } return B115200; } /** \brief Advanced read function * \details Read to pointer "read_to" no more than "max_size" and no longer * than "timeout_ms" milliseconds. If "timeout_ms" < 0 function will be * wait forever until "max_size" will be readed. If size <= 0 function * immediate returns \b false. For read data with unknown size use function * \a readData(). * \returns \b True if readed bytes count = "max_size", else \b false * \sa \a readData() */ bool PISerial::read(void * data, int size, double timeout_ms) { if (data == 0 || size <= 0) return false; int ret, all = 0; if (timeout_ms > 0.) { setReadIsBlocking(false); all = read(data, 1); timer.reset(); while (all < size && timer.elapsed_m() < timeout_ms) { ret = read(&((uchar * )data)[all], size - all); if (ret > 0) all += ret; else msleep(1); } received(data, all); return (all == size); } else { setReadIsBlocking(true); all = read(data, 1); while (all < size) { ret = read(&((uchar * )data)[all], size - all); if (ret > 0) all += ret; } received(data, all); return (all == size); } return false; } /** \brief Advanced read function * \details Read all or no more than "size" and no longer than * "timeout_ms" milliseconds. If "timeout_ms" < 0 function will be * wait forever until "size" will be readed. If "size" <= 0 * function will be read all until "timeout_ms" elaped. \n If size <= 0 * and "timeout_ms" <= 0 function immediate returns empty string. * \n This function similar to \a readData() but returns data as string. * \sa \a readData() */ PIString PISerial::read(int size, double timeout_ms) { PIString str; if (size <= 0 && timeout_ms <= 0.) return str; int ret, all = 0; uchar td[1024]; if (timeout_ms > 0.) { setReadIsBlocking(false); timer.reset(); if (size <= 0) { while (timer.elapsed_m() < timeout_ms) { ret = read(td, 1024); if (ret <= 0) msleep(1); else str << PIString((char*)td, ret); } } else { while (all < size && timer.elapsed_m() < timeout_ms) { ret = read(td, size - all); if (ret <= 0) msleep(1); else { str << PIString((char*)td, ret); all += ret; } } } } else { setReadIsBlocking(true); all = read(td, 1); str << PIString((char*)td, all); while (all < size) { ret = read(td, size - all); if (ret <= 0) msleep(1); else { str << PIString((char*)td, ret); all += ret; } } } received(str.data(), str.size_s()); return str; } /** \brief Advanced read function * \details Read all or no more than "size" and no longer than * "timeout_ms" milliseconds. If "timeout_ms" < 0 function will be * wait forever until "size" will be readed. If "size" <= 0 * function will be read all until "timeout_ms" elaped. \n If size <= 0 * and "timeout_ms" <= 0 function immediate returns empty byte array. * \n This function similar to \a read() but returns data as byte array. * \sa \a read() */ PIByteArray PISerial::readData(int size, double timeout_ms) { PIByteArray str; if (size <= 0 && timeout_ms <= 0.) return str; int ret, all = 0; uchar td[1024]; if (timeout_ms > 0.) { setReadIsBlocking(false); timer.reset(); if (size <= 0) { while (timer.elapsed_m() < timeout_ms) { ret = read(td, 1024); if (ret <= 0) msleep(1); else str.append(td, ret); } } else { while (all < size && timer.elapsed_m() < timeout_ms) { ret = read(td, size - all); if (ret <= 0) msleep(1); else { str.append(td, ret); all += ret; } } } } else { setReadIsBlocking(true); all = read(td, 1); str.append(td, all); while (all < size) { ret = read(td, size - all); if (ret <= 0) msleep(1); else { str.append(td, ret); all += ret; } } } received(str.data(), str.size_s()); return str; } bool PISerial::openDevice() { //piCout << "ser open" << path(); if (path().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(); hCom = CreateFileA(wp.data(), ds, sm, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); if (hCom == INVALID_HANDLE_VALUE) { piCoutObj << "Unable to open \"" << path() << "\""; fd = -1; return false; } fd = 0; #else int om = 0; switch (mode()) { case PIIODevice::ReadOnly: om = O_RDONLY; break; 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); if (fd == -1) { piCoutObj << "Unable to open \"" << path() << "\""; return false; } tcgetattr(fd, &desc); sdesc = desc; //piCoutObj << "Initialized " << path_; #endif applySettings(); return true; } void PISerial::applySettings() { #ifdef WINDOWS if (fd == -1) return; COMMTIMEOUTS times; times.ReadIntervalTimeout = block_read ? vtime : MAXDWORD; times.ReadTotalTimeoutConstant = block_read ? 0 : 1; times.ReadTotalTimeoutMultiplier = block_read ? 0 : MAXDWORD; times.WriteTotalTimeoutConstant = 0; times.WriteTotalTimeoutMultiplier = block_write ? 0 : 1; if (SetCommTimeouts(hCom, ×) == -1) piCoutObj << "Unable to set timeouts for \"" << path() << "\""; GetCommMask(hCom, &mask); SetCommMask(hCom, EV_RXCHAR); GetCommState(hCom, &sdesc); desc = sdesc; desc.DCBlength = sizeof(desc); desc.BaudRate = convertSpeed(outSpeed()); if (dataBitsCount() >= 5 && dataBitsCount() <= 8) desc.ByteSize = dataBitsCount(); else desc.ByteSize = 8; PIFlags params = parameters(); if (params[PISerial::ParityControl]) { desc.fParity = 1; desc.Parity = params[PISerial::ParityOdd] ? 1 : 2; } desc.StopBits = params[PISerial::TwoStopBits] ? TWOSTOPBITS : ONESTOPBIT; if (SetCommState(hCom, &desc) == -1) { piCoutObj << "Unable to set comm state for \"" << path() << "\""; return; } #else if (fd == -1) return; tcgetattr(fd, &desc); desc.c_oflag = desc.c_lflag = desc.c_cflag = 0; desc.c_iflag = IGNBRK; desc.c_cflag = CLOCAL | HUPCL; switch (dataBitsCount()) { case 5: desc.c_cflag |= (CSIZE & CS5); break; case 6: desc.c_cflag |= (CSIZE & CS6); break; case 7: desc.c_cflag |= (CSIZE & CS7); break; case 8: default: desc.c_cflag |= (CSIZE & CS8); break; }; if (isReadable()) desc.c_cflag |= CREAD; PIFlags params = parameters(); if (params[PISerial::TwoStopBits]) desc.c_cflag |= CSTOPB; if (params[PISerial::ParityControl]) { desc.c_iflag |= INPCK; desc.c_cflag |= PARENB; if (params[PISerial::ParityOdd]) desc.c_cflag |= PARODD; } desc.c_cc[VMIN] = 1; desc.c_cc[VTIME] = vtime; cfsetispeed(&desc, convertSpeed(inSpeed())); cfsetospeed(&desc, convertSpeed(outSpeed())); tcflush(fd, TCIOFLUSH); fcntl(fd, F_SETFL, block_read ? 0 : O_NONBLOCK); if(tcsetattr(fd, TCSANOW, &desc) < 0) { piCoutObj << "Can`t set attributes for \"" << path() << "\""; return; } #endif } void PISerial::setReadIsBlocking(bool yes) { block_read = yes; #ifdef WINDOWS COMMTIMEOUTS times; times.ReadIntervalTimeout = block_read ? vtime : MAXDWORD; times.ReadTotalTimeoutConstant = block_read ? 0 : 1; times.ReadTotalTimeoutMultiplier = block_read ? 0 : MAXDWORD; times.WriteTotalTimeoutConstant = 0; times.WriteTotalTimeoutMultiplier = block_write ? 0 : 1; if (isOpened()) SetCommTimeouts(hCom, ×); #else if (isOpened()) fcntl(fd, F_SETFL, yes ? 0 : O_NONBLOCK); #endif } /** \brief Basic read function * \details Read to pointer "read_to" no more than "max_size". If read is * set to blocking this function will be wait at least one byte. * \returns Readed bytes count * \sa \a readData() */ int PISerial::read(void * read_to, int max_size) { #ifdef WINDOWS if (!canRead()) return -1; WaitCommEvent(hCom, 0, 0); ReadFile(hCom, read_to, max_size, &readed, 0); return readed; #else if (!canRead()) return -1; return ::read(fd, read_to, max_size); #endif } int PISerial::write(const void * data, int max_size, bool wait) { //piCoutObj << "send " << max_size << ": " << PIString((char*)data, max_size); if (fd == -1 || !canWrite()) { //piCoutObj << "Can`t write to uninitialized COM"; return -1; } #ifdef WINDOWS if (block_write != wait) { block_write = wait; setReadIsBlocking(block_read); } DWORD wrote; WriteFile(hCom, data, max_size, &wrote, 0); #else int wrote; wrote = ::write(fd, data, max_size); if (wait) tcdrain(fd); #endif return (int)wrote; //piCoutObj << "Error while sending"; //piCoutObj << "Wrote " << wrote << " bytes in " << path_; } bool PISerial::configureDevice(const void * e_main, const void * e_parent) { PIConfig::Entry * em = (PIConfig::Entry * )e_main; PIConfig::Entry * ep = (PIConfig::Entry * )e_parent; setDevice(readDeviceSetting("device", device(), em, ep)); setSpeed((PISerial::Speed)(readDeviceSetting("speed", (int)outSpeed(), em, ep))); setDataBitsCount(readDeviceSetting("dataBitsCount", dataBitsCount(), em, ep)); setParameter(PISerial::ParityControl, readDeviceSetting("parityControl", isParameterSet(PISerial::ParityControl), em, ep)); setParameter(PISerial::ParityOdd, readDeviceSetting("parityOdd", isParameterSet(PISerial::ParityOdd), em, ep)); setParameter(PISerial::TwoStopBits, readDeviceSetting("twoStopBits", isParameterSet(PISerial::TwoStopBits), em, ep)); return true; } PIString PISerial::constructFullPath() const { PIString ret(fullPathPrefix() + "://"); ret << path() << ":" << int(inSpeed()) << ":" << dataBitsCount(); if (parameters()[ParityControl]) { if (parameters()[ParityOdd]) ret << ":O"; else ret << ":E"; } else ret << ":N"; if (parameters()[TwoStopBits]) ret << ":2"; else ret << ":1"; return ret; } void PISerial::configureFromFullPath(const PIString & full_path) { PIStringList pl = full_path.split(":"); for (int i = 0; i < pl.size_s(); ++i) { PIString p(pl[i]); switch (i) { case 0: setPath(p); break; case 1: setSpeed((Speed)(p.toInt())); break; case 2: setDataBitsCount(p.toInt()); break; case 3: p = p.toLowerCase(); if (p != "n") setParameter(ParityControl); if (p == "o") setParameter(ParityOdd); break; case 4: if (p.toInt() == 2) setParameter(TwoStopBits); break; } } } PIVector PISerial::availableSpeeds() { PIVector spds; spds << 50 << 75 << 110 << 300 << 600 << 1200 << 2400 << 4800 << 9600 << 19200 << 38400 << 57600 << 115200 << 1500000 << 2000000 << 2500000 << 3000000 << 3500000 << 4000000; return spds; } PIStringList PISerial::availableDevices(bool test) { PIStringList dl; #ifdef WINDOWS HKEY key = 0; RegOpenKey(HKEY_LOCAL_MACHINE, (LPCTSTR)"HARDWARE\\DEVICEMAP\\SERIALCOMM", &key); if (key != 0) { char name[1024], data[1024]; DWORD name_len = 1024, data_len = 1024, type = 0, index = 0; LONG ret; while ((ret = RegEnumValue(key, index, (LPTSTR)name, &name_len, NULL, &type, (uchar * )data, &data_len)) != ERROR_NO_MORE_ITEMS) { dl << PIString(data); index++; } RegCloseKey(key); } #else # ifndef ANDROID PIStringList prefixes; # ifdef QNX prefixes << "ser"; # else prefixes << "ttyS" << "ttyO" << "ttyUSB" << "ttyACM" << "ttyGS" << "ttyMI" << "ttymxc" << "ttyAMA" << "rfcomm" << "ircomm"; # ifdef FREE_BSD prefixes << "cu"; # endif PIFile file_prefixes("/proc/tty/drivers", PIIODevice::ReadOnly); if (file_prefixes.open()) { PIString fc = file_prefixes.readAll(true), line, cpref; PIStringList words; file_prefixes.close(); while (!fc.isEmpty()) { words.clear(); line = fc.takeLine(); if (line.isEmpty()) break; while (!line.isEmpty()) words << line.takeWord(); if (words.size_s() < 2) break; if (words.back() != "serial") continue; cpref = words[1]; int li = cpref.findLast("/"); if (li > 0) cpref.cutLeft(li + 1); prefixes << cpref; } prefixes.removeDuplicates(); } # endif PIDir dir("/dev"); PIVector de = dir.entries(); piForeachC (PIDir::DirEntry & e, de) { piForeachC (PIString & p, prefixes) { if (e.name.left(p.size_s()) != p) continue; dl << "/dev/" + e.name; } } # endif #endif if (test) { for (int i = 0; i < dl.size_s(); ++i) { #ifdef WINDOWS void * hCom = CreateFileA(dl[i].data(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); if (hCom == INVALID_HANDLE_VALUE) { #else int fd = ::open(dl[i].data(), O_NOCTTY | O_RDONLY); if (fd == -1) { #endif dl.remove(i); --i; continue; } int void_ = 0; bool rok = true; #ifdef WINDOWS /*COMMTIMEOUTS times; times.ReadIntervalTimeout = MAXDWORD; times.ReadTotalTimeoutConstant = 0; times.ReadTotalTimeoutMultiplier = 0; times.WriteTotalTimeoutConstant = 1; times.WriteTotalTimeoutMultiplier = 0; SetCommTimeouts(hCom, ×); if (ReadFile(hCom, &void_, 1, &readed_, 0) == 0) rok = GetLastError() == ;*/ #else fcntl(fd, F_SETFL, O_NONBLOCK); if (::read(fd, &void_, 1) == -1) rok = errno != EIO; #endif if (!rok) { dl.remove(i); --i; continue; } #ifdef WINDOWS CloseHandle(hCom); #else ::close(fd); #endif } } return dl; }