/*
    PIP - Platform Independent Primitives
    COM
    Copyright (C) 2016  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 <http://www.gnu.org/licenses/>.
*/

#include "piserial.h"
#include "piconfig.h"
#include "pidir.h"
#include <errno.h>
#ifdef WINDOWS
#  include <winreg.h>
#  define TIOCM_LE   1
#  define TIOCM_DTR  4
#  define TIOCM_RTS  7
#  define TIOCM_CTS  8
#  define TIOCM_ST   3
#  define TIOCM_SR   2
#  define TIOCM_CAR  1
#  define TIOCM_RNG  9
#  define TIOCM_DSR  6
#  define B50       50
#  define B75       75
#  define B110      110
#  define B300      300
#  define B600      600
#  define B1200     1200
#  define B2400     2400
#  define B4800     4800
#  define B9600     9600
#  define B14400    14400
#  define B19200    19200
#  define B38400    38400
#  define B57600    57600
#  define B115200   115200
#  define B230400   230400
#  define B460800   460800
#  define B500000   500000
#  define B576000   576000
#  define B921600   921600
#  define B1000000  1000000
#  define B1152000  1152000
#  define B1500000  1500000
#  define B2000000  2000000
#  define B2500000  2500000
#  define B3000000  3000000
#  define B3500000  3500000
#  define B4000000  4000000
#else
#  include <termios.h>
#  include <fcntl.h>
#  include <sys/ioctl.h>
#  ifndef B50
#    define B50       0000001
#  endif
#  ifndef B75
#    define B75       0000002
#  endif
#  ifndef B230400
#    define B230400   0010003
#  endif
#  ifndef B460800
#    define B460800   0010004
#  endif
#  ifndef B500000
#    define B500000   0010005
#  endif
#  ifndef B576000
#    define B576000   0010006
#  endif
#  ifndef B921600
#    define B921600   0010007
#  endif
#  ifndef B1000000
#    define B1000000  0010010
#  endif
#  ifndef B1152000
#    define B1152000  0010011
#  endif
#  ifndef B1500000
#    define B1500000  0010012
#  endif
#  ifndef B2000000
#    define B2000000  0010013
#  endif
#  ifndef B2500000
#    define B2500000  0010014
#  endif
#  ifndef B3000000
#    define B3000000  0010015
#  endif
#  ifndef B3500000
#    define B3500000  0010016
#  endif
#  ifndef B4000000
#    define B4000000  0010017
#  endif
#endif
#ifndef CRTSCTS
#  define CRTSCTS 020000000000
#endif


/*! \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);


PRIVATE_DEFINITION_START(PISerial)
#ifdef WINDOWS
	DCB desc, sdesc;
	void * hCom;
	DWORD readed, mask;
#else
	termios desc, sdesc;
	uint readed;
#endif
PRIVATE_DEFINITION_END(PISerial)


PISerial::PISerial(): PIIODevice("", ReadWrite) {
	_init();
}


PISerial::PISerial(const PIString & device_, PISerial::Speed speed_, PIFlags<PISerial::Parameters> params_): PIIODevice(device_, ReadWrite) {
	_init();
	setPath(device_);
	setSpeed(speed_);
	setParameters(params_);
}


PISerial::~PISerial() {
	piMonitor.serials--;
}


void PISerial::_init() {
#ifdef WINDOWS
	PRIVATE->hCom = 0;
#endif
	fd = -1;
	piMonitor.serials++;
	setPriority(piHigh);
	block_read = block_write = true;
	vtime = 1;
	setParameters(0);
	setSpeed(S115200);
	setDataBitsCount(8);
	//init();
}


void PISerial::setParameter(PISerial::Parameters parameter, bool on) {
	PIFlags<Parameters> cp = (PIFlags<Parameters>)(property("parameters").toInt());
	cp.setFlag(parameter, on);
	setParameters(cp);
}


bool PISerial::isParameterSet(PISerial::Parameters parameter) const {
	PIFlags<Parameters> cp = (PIFlags<Parameters>)(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::setLE(bool on) {return setBit(TIOCM_LE, on, "LE");}
bool PISerial::setDTR(bool on) {return setBit(TIOCM_DTR, on, "DTR");}
bool PISerial::setRTS(bool on) {return setBit(TIOCM_RTS, on, "RTS");}
bool PISerial::setCTS(bool on) {return setBit(TIOCM_CTS, on, "CTS");}
bool PISerial::setST(bool on) {return setBit(TIOCM_ST, on, "ST");}
bool PISerial::setSR(bool on) {return setBit(TIOCM_SR, on, "SR");}
bool PISerial::setCAR(bool on) {return setBit(TIOCM_CAR, on, "CAR");}
bool PISerial::setRNG(bool on) {return setBit(TIOCM_RNG, on, "RNG");}
bool PISerial::setDSR(bool on) {return setBit(TIOCM_DSR, on, "DSR");}

bool PISerial::isLE() const {return isBit(TIOCM_LE, "LE");}
bool PISerial::isDTR() const {return isBit(TIOCM_DTR, "DTR");}
bool PISerial::isRTS() const {return isBit(TIOCM_RTS, "RTS");}
bool PISerial::isCTS() const {return isBit(TIOCM_CTS, "CTS");}
bool PISerial::isST() const {return isBit(TIOCM_ST, "ST");}
bool PISerial::isSR() const {return isBit(TIOCM_SR, "SR");}
bool PISerial::isCAR() const {return isBit(TIOCM_CAR, "CAR");}
bool PISerial::isRNG() const {return isBit(TIOCM_RNG, "RNG");}
bool PISerial::isDSR() const {return isBit(TIOCM_DSR, "DSR");}


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
}


void PISerial::flush() {
#ifndef WINDOWS
	if (fd != -1) tcflush(fd, TCIOFLUSH);
#endif
}


bool PISerial::closeDevice() {
	if (!isInitialized()) return true;
	if (isRunning()) {
		stop();
		PIThread::terminate();
	}
	if (fd != -1) {
#ifdef WINDOWS
		SetCommState(PRIVATE->hCom, &PRIVATE->sdesc);
		SetCommMask(PRIVATE->hCom, PRIVATE->mask);
		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;
	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 S230400: return B230400;
	case S460800: return B460800;
	case S500000: return B500000;
	case S576000: return B576000;
	case S921600: return B921600;
	case S1000000: return B1000000;
	case S1152000: return B1152000;
	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);
		tm_.reset();
		while (all < size && tm_.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);
		tm_.reset();
		if (size <= 0) {
			while (tm_.elapsed_m() < timeout_ms) {
				ret = read(td, 1024);
				if (ret <= 0) msleep(1);
				else str << PIString((char*)td, ret);
			}
		} else {
			while (all < size && tm_.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);
		tm_.reset();
		if (size <= 0) {
			while (tm_.elapsed_m() < timeout_ms) {
				ret = read(td, 1024);
				if (ret <= 0) msleep(1);
				else str.append(td, ret);
			}
		} else {
			while (all < size && tm_.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();
	PRIVATE->hCom = CreateFileA(wp.data(), ds, sm, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
	if (PRIVATE->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, &PRIVATE->desc);
	PRIVATE->sdesc = PRIVATE->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(PRIVATE->hCom, &times) == -1)
		piCoutObj << "Unable to set timeouts for \"" << path() << "\"";
	GetCommMask(PRIVATE->hCom, &PRIVATE->mask);
	SetCommMask(PRIVATE->hCom, EV_RXCHAR);
	GetCommState(PRIVATE->hCom, &PRIVATE->sdesc);
	PRIVATE->desc = PRIVATE->sdesc;
	PRIVATE->desc.DCBlength = sizeof(PRIVATE->desc);
	PRIVATE->desc.BaudRate = convertSpeed(outSpeed());
	if (dataBitsCount() >= 5 && dataBitsCount() <= 8)
		PRIVATE->desc.ByteSize = dataBitsCount();
	else
		PRIVATE->desc.ByteSize = 8;
	PIFlags<Parameters> params = parameters();
	if (params[PISerial::ParityControl]) {
		PRIVATE->desc.fParity = 1;
		PRIVATE->desc.Parity = params[PISerial::ParityOdd] ? 1 : 2;
	}
	PRIVATE->desc.StopBits = params[PISerial::TwoStopBits] ? TWOSTOPBITS : ONESTOPBIT;
	if (SetCommState(PRIVATE->hCom, &PRIVATE->desc) == -1) {
		piCoutObj << "Unable to set comm state for \"" << path() << "\"";
		return;
	}
#else
	if (fd == -1) return;
	tcgetattr(fd, &PRIVATE->desc);
	PRIVATE->desc.c_oflag = PRIVATE->desc.c_lflag = PRIVATE->desc.c_cflag = 0;
	PRIVATE->desc.c_iflag = IGNBRK;
	PRIVATE->desc.c_cflag = CLOCAL | HUPCL;
	switch (dataBitsCount()) {
	case 5: PRIVATE->desc.c_cflag |= (CSIZE & CS5); break;
	case 6: PRIVATE->desc.c_cflag |= (CSIZE & CS6); break;
	case 7: PRIVATE->desc.c_cflag |= (CSIZE & CS7); break;
	case 8: default: PRIVATE->desc.c_cflag |= (CSIZE & CS8); break;
	};
	if (isReadable()) PRIVATE->desc.c_cflag |= CREAD;
	PIFlags<Parameters> params = parameters();
	if (params[PISerial::TwoStopBits]) PRIVATE->desc.c_cflag |= CSTOPB;
	if (params[PISerial::ParityControl]) {
		PRIVATE->desc.c_iflag |= INPCK;
		PRIVATE->desc.c_cflag |= PARENB;
		if (params[PISerial::ParityOdd]) PRIVATE->desc.c_cflag |= PARODD;
	}
	PRIVATE->desc.c_cc[VMIN] = 1;
	PRIVATE->desc.c_cc[VTIME] = vtime;

	cfsetispeed(&PRIVATE->desc, convertSpeed(inSpeed()));
	cfsetospeed(&PRIVATE->desc, convertSpeed(outSpeed()));

	tcflush(fd, TCIOFLUSH);
	fcntl(fd, F_SETFL, block_read ? 0 : O_NONBLOCK);

	if(tcsetattr(fd, TCSANOW, &PRIVATE->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(PRIVATE->hCom, &times);
#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(PRIVATE->hCom, 0, 0);
		ReadFile(PRIVATE->hCom, read_to, max_size, &PRIVATE->readed, 0);
		return PRIVATE->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(PRIVATE->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<PIString>("device", device(), em, ep));
	setSpeed((PISerial::Speed)(readDeviceSetting<int>("speed", (int)outSpeed(), em, ep)));
	setDataBitsCount(readDeviceSetting<int>("dataBitsCount", dataBitsCount(), em, ep));
	setParameter(PISerial::ParityControl, readDeviceSetting<bool>("parityControl", isParameterSet(PISerial::ParityControl), em, ep));
	setParameter(PISerial::ParityOdd, readDeviceSetting<bool>("parityOdd", isParameterSet(PISerial::ParityOdd), em, ep));
	setParameter(PISerial::TwoStopBits, readDeviceSetting<bool>("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<int> PISerial::availableSpeeds() {
	PIVector<int> 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<PIFile::FileInfo> de = dir.entries();
	piForeachC (PIFile::FileInfo & e, de) { // TODO changes in FileInfo
		piForeachC (PIString & p, prefixes) {
			if (e.name().left(p.size_s()) != p) continue;
			dl << e.path;
		}
	}
#  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, &times);
			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;
}
