/*
PIP - Platform Independent Primitives
COM
Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
*/
#include "piserial.h"
#include "piconfig.h"
#include "pidir.h"
#include "piincludes_p.h"
#include "pipropertystorage.h"
#include "pitime.h"
#include "pitranslator.h"
#include "piwaitevent_p.h"
#include
#if defined(MICRO_PIP)
# define PISERIAL_NO_PINS
#endif
#if defined(PISERIAL_NO_PINS) || defined(WINDOWS)
# 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
#endif
#ifdef WINDOWS
# ifndef INITGUID
# define INITGUID
# include
# undef INITGUID
# else
# include
# endif
// clang-format off
# include
# include
# include
# include
# include
# include
// clang-format on
# 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
# include
# include
# 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
#ifdef LINUX
# include
#endif
//! \class PISerial piserial.h
//! \details
//! \~english \section PISerial_sec0 Synopsis
//! \~russian \section PISerial_sec0 Краткий обзор
//! \~english
//! 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.
//!
//! \~russian
//! Этот класс предоставляет доступ к последовательному порту, например, COM-порт.
//!
//! \~english \section PISerial_sec1 FullPath
//! \~russian \section PISerial_sec1 Строка полного описания
//! \~english
//! Since version 1.16.0 you can use as \a path \a PISerial::DeviceInfo::id() USB identifier
//! for USB devices.
//!
//! \~russian
//! Начиная с версии 1.16.0 можно в качестве \a path использовать \a PISerial::DeviceInfo::id()
//! USB идентификатор для USB устройств.
//!
//! \~\code
//! PISerial * s = new PISerial("0403:6001");
//! PIIODevice * d = PIIODevice::createFromFullPath("ser://0403:6001:115200");
//! \endcode
//!
REGISTER_DEVICE(PISerial)
PRIVATE_DEFINITION_START(PISerial)
PIWaitEvent event;
#ifdef WINDOWS
PIWaitEvent event_write;
DCB desc, sdesc;
HANDLE hCom = nullptr;
DWORD readed = 0, mask = 0;
OVERLAPPED overlap, overlap_write;
#else
termios desc, sdesc;
uint readed = 0;
#endif
PRIVATE_DEFINITION_END(PISerial)
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();
}
PISerial::PISerial(const PIString & device_, PISerial::Speed speed_, PIFlags params_)
: PIIODevice(device_, ReadWrite) {
construct();
setPath(device_);
setSpeed(speed_);
setParameters(params_);
}
PISerial::~PISerial() {
stopAndWait();
close();
PRIVATE->event.destroy();
#ifdef WINDOWS
PRIVATE->event_write.destroy();
#endif
}
void PISerial::construct() {
sending = false;
// setPriority(piHigh);
setParameters(0);
setSpeed(S115200);
setDataBitsCount(8);
}
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::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");
}
//! \~\details
//! \~english
//! If enabled, sends a continuous stream of zero bits.
//! Returns if state changed successfully.
//! \note The serial port has to be open before using this method
//!
//! \~russian
//! Если включено, отсылается непрерывный поток нулей.
//! Возвращает успешна ли смена состояния.
//! \note Порт должен быть открыт перед использованием этого метода
bool PISerial::setBreak(bool enabled) {
if (fd < 0) {
piCoutObj << "sendBreak error: \"" << path() << "\" is not opened!";
return false;
}
#ifdef WINDOWS
if (enabled) {
if (!SetCommBreak(PRIVATE->hCom)) {
piCoutObj << "setBreak error: " << errorString();
return false;
} else {
return true;
}
} else {
if (!ClearCommBreak(PRIVATE->hCom)) {
piCoutObj << "setBreak error: " << errorString();
return false;
} else {
return true;
}
}
#else
if (ioctl(fd, enabled ? TIOCSBRK : TIOCCBRK) < 0) {
piCoutObj << "setBreak error: " << errorString();
return false;
} else {
return true;
}
#endif
return false;
}
bool PISerial::setBit(int bit, bool on, const PIString & bname) {
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;
# endif
#endif
piCoutObj << "setBit" << bname << " doesn`t implemented, sorry :-(";
return false;
}
bool PISerial::isBit(int bit, const PIString & bname) const {
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;
# endif
#endif
piCoutObj << "isBit" << bname << " doesn`t implemented, sorry :-(";
return false;
}
void PISerial::flush() {
#ifndef WINDOWS
if (fd != -1) tcflush(fd, TCIOFLUSH);
#endif
}
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;
#ifdef WINDOWS
case S14400: return B14400;
#endif
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;
}
#ifdef WINDOWS
piCoutObj << "Warning: Custom speed %1"_tr("PISerial").arg((int)speed);
return (int)speed;
#else
piCoutObj << "Warning: Unknown speed %1, using 115200"_tr("PISerial").arg((int)speed);
return B115200;
#endif
}
//! \details
//! \~english
//! Read to pointer "read_to" no more than "max_size" and no longer
//! than "timeout_ms" milliseconds.\n
//! If "timeout_ms" < 0 function will be wait forever until "max_size" will be readed.\n
//! If "size" <= 0 function immediate returns \b false.\n
//! For read data with unknown size use function \a readData().
//! \returns If readed bytes count = "max_size"
//!
//! \~russian
//! Читает в указатель "read_to" не более "max_size" байт и не дольше
//! чем "timeout_ms" миллисекунд.\n
//! Если "timeout_ms" < 0 метод ожидает бесконечно, пока не будет прочитано "max_size" байт.\n
//! Если "size" <= 0, то метод немедленно возвращает \b false.\n
//! Для чтения данных неизвестного размера используется метод \a readData().
//! \returns Если количество прочитанных байт = "max_size"
//!
//! \sa \a readString(), \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.) {
bool br = setOption(BlockingRead, false);
all = readDevice(data, 1);
tm_.reset();
while (all < size && tm_.elapsed_m() < timeout_ms) {
ret = readDevice(&((uchar *)data)[all], size - all);
if (ret > 0)
all += ret;
else
piMinSleep();
}
setOption(BlockingRead, br);
received(data, all);
return (all == size);
} else {
bool br = setOption(BlockingRead, true);
all = readDevice(data, 1);
while (all < size) {
ret = readDevice(&((uchar *)data)[all], size - all);
if (ret > 0) all += ret;
}
setOption(BlockingRead, br);
received(data, all);
return (all == size);
}
return false;
}
//! \details
//! \~english
//! Read all or no more than "size" bytes and no longer than "timeout_ms" milliseconds.\n
//! If "timeout_ms" < 0 function will be wait forever until "max_size" will be readed.\n
//! 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.
//! \returns If readed bytes count = "max_size"
//!
//! \~russian
//! Читает всё или не более "size" байт и не дольше чем "timeout_ms" миллисекунд.\n
//! Если "timeout_ms" < 0 метод ожидает бесконечно, пока не будет прочитано "max_size" байт.\n
//! Если "size" <= 0, то читает всё в течении "timeout_ms" миллисекунд.\n
//! Если "size" <= 0 и "timeout_ms" <= 0, то метод немедленно возвращает пустую строку.\n
//! Этот метод аналогичен \a readData(), но возвращает строку.
//! \returns Если количество прочитанных байт = "max_size"
//!
//! \sa \a readData()
PIString PISerial::readString(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.) {
bool br = setOption(BlockingRead, false);
tm_.reset();
if (size <= 0) {
while (tm_.elapsed_m() < timeout_ms) {
ret = readDevice(td, 1024);
if (ret <= 0)
piMinSleep();
else
str += PIString((char *)td, ret);
}
} else {
while (all < size && tm_.elapsed_m() < timeout_ms) {
ret = readDevice(td, size - all);
if (ret <= 0)
piMinSleep();
else {
str += PIString((char *)td, ret);
all += ret;
}
}
}
setOption(BlockingRead, br);
} else {
bool br = setOption(BlockingRead, true);
all = readDevice(td, 1);
str += PIString((char *)td, all);
while (all < size) {
ret = readDevice(td, size - all);
if (ret <= 0)
piMinSleep();
else {
str += PIString((char *)td, ret);
all += ret;
}
}
setOption(BlockingRead, br);
}
received(str.data(), str.size_s());
return str;
}
//! \details
//! \~english
//! Read all or no more than "size" bytes and no longer than "timeout_ms" milliseconds.\n
//! If "timeout_ms" < 0 function will be wait forever until "max_size" will be readed.\n
//! 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 readString() but returns data as byte array.
//! \returns If readed bytes count = "max_size"
//!
//! \~russian
//! Читает всё или не более "size" байт и не дольше чем "timeout_ms" миллисекунд.\n
//! Если "timeout_ms" < 0 метод ожидает бесконечно, пока не будет прочитано "max_size" байт.\n
//! Если "size" <= 0, то читает всё в течении "timeout_ms" миллисекунд.\n
//! Если "size" <= 0 и "timeout_ms" <= 0, то метод немедленно возвращает пустую строку.\n
//! Этот метод аналогичен \a readString(), но возвращает массив байт.
//! \returns Если количество прочитанных байт = "max_size"
//!
//! \sa \a readString()
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.) {
bool br = setOption(BlockingRead, false);
tm_.reset();
if (size <= 0) {
while (tm_.elapsed_m() < timeout_ms) {
ret = readDevice(td, 1024);
if (ret <= 0)
piMinSleep();
else
str.append(td, ret);
}
} else {
while (all < size && tm_.elapsed_m() < timeout_ms) {
ret = readDevice(td, size - all);
if (ret <= 0)
piMinSleep();
else {
str.append(td, ret);
all += ret;
}
}
}
setOption(BlockingRead, br);
} else {
bool br = setOption(BlockingRead, true);
all = readDevice(td, 1);
str.append(td, all);
while (all < size) {
ret = readDevice(td, size - all);
if (ret <= 0)
piMinSleep();
else {
str.append(td, ret);
all += ret;
}
}
setOption(BlockingRead, br);
}
received(str.data(), str.size_s());
return str;
}
bool PISerial::send(const void * data, int size) {
int ret = 0;
int wsz = 0;
do {
ret = write(&(((uchar *)data)[wsz]), size - wsz);
if (ret > 0) wsz += ret;
// piCout << ret << wsz;
else
return false;
} while (wsz < size);
return (wsz == size);
}
void PISerial::interrupt() {
// piCoutObj << "interrupt";
PRIVATE->event.interrupt();
#ifdef WINDOWS
PRIVATE->event_write.interrupt();
#endif
}
bool PISerial::openDevice() {
PIString p = path();
// piCout << "ser open" << p;
PIString pl = p.toLowerCase().removeAll(' ');
if (!pl.startsWith("/") && !pl.startsWith("com")) {
p.clear();
PIVector devs = availableDevicesInfo();
for (const auto & d: devs) {
if (d.id() == pl) {
p = d.path;
break;
}
}
if (p.isEmpty()) {
piCoutObj << "Unable to find device \"%1\""_tr("PISerial").arg(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 = "//./" + p;
PRIVATE->hCom = CreateFileA(wp.dataAscii(), ds, sm, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);
if (PRIVATE->hCom == INVALID_HANDLE_VALUE) {
piCoutObj << "Unable to open \"%1\": %2"_tr("PISerial").arg(p).arg(errorString());
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;
}
fd = ::open(p.data(), O_NOCTTY | om);
if (fd == -1) {
piCoutObj << "Unable to open \"%1\": %2"_tr("PISerial").arg(p).arg(errorString());
return false;
}
tcgetattr(fd, &PRIVATE->desc);
PRIVATE->sdesc = PRIVATE->desc;
// piCoutObj << "Initialized " << p;
#endif
applySettings();
PRIVATE->event.create();
#ifdef WINDOWS
PRIVATE->event_write.create();
#endif
return true;
}
bool PISerial::closeDevice() {
if (isThreadedRead() && !isThreadedReadStopping()) {
stopThreadedRead();
}
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;
}
PRIVATE->event.destroy();
#ifdef WINDOWS
PRIVATE->event_write.destroy();
#endif
return true;
}
void PISerial::applySettings() {
#ifdef WINDOWS
if (fd == -1) return;
setTimeouts();
GetCommMask(PRIVATE->hCom, &PRIVATE->mask);
SetCommMask(PRIVATE->hCom, EV_RXCHAR);
GetCommState(PRIVATE->hCom, &PRIVATE->sdesc);
// piCoutObj << PRIVATE->sdesc.fBinary << PRIVATE->sdesc.fAbortOnError << PRIVATE->sdesc.fDsrSensitivity << PRIVATE->sdesc.fDtrControl
// << PRIVATE->sdesc.fDummy2 << PRIVATE->sdesc.fErrorChar;
PRIVATE->desc = PRIVATE->sdesc;
PRIVATE->desc.DCBlength = sizeof(PRIVATE->desc);
PRIVATE->desc.BaudRate = convertSpeed(outSpeed());
PRIVATE->desc.fDtrControl = DTR_CONTROL_ENABLE;
if (dataBitsCount() >= 5 && dataBitsCount() <= 8)
PRIVATE->desc.ByteSize = dataBitsCount();
else
PRIVATE->desc.ByteSize = 8;
PIFlags 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 \"%1\""_tr("PISerial").arg(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 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);
setTimeouts();
if (tcsetattr(fd, TCSANOW, &PRIVATE->desc) < 0) {
piCoutObj << "Can`t set attributes for \"%1\""_tr("PISerial").arg(path());
return;
}
#endif
}
void PISerial::setTimeouts() {
#ifdef WINDOWS
COMMTIMEOUTS times;
if (isOptionSet(BlockingRead)) {
times.ReadIntervalTimeout = MAXDWORD;
times.ReadTotalTimeoutConstant = vtime;
times.ReadTotalTimeoutMultiplier = MAXDWORD;
} else {
times.ReadIntervalTimeout = MAXDWORD;
times.ReadTotalTimeoutConstant = 0;
times.ReadTotalTimeoutMultiplier = 0;
}
times.WriteTotalTimeoutConstant = isOptionSet(BlockingWrite) ? 0 : 1;
times.WriteTotalTimeoutMultiplier = 0;
if (SetCommTimeouts(PRIVATE->hCom, ×) == -1) piCoutObj << "Unable to set timeouts for \"" << path() << "\"";
#else
fcntl(fd, F_SETFL, isOptionSet(BlockingRead) ? 0 : O_NONBLOCK);
#endif
}
//! \details
//! \~english
//! Read to pointer "read_to" no more than "max_size".
//! If \a PIIODevice::BlockingRead option set this function
//! will be wait at least one byte.
//! \returns Readed bytes count, -1 for error
//!
//! \~russian
//! Читает в указатель "read_to" не более "max_size" байт.
//! Если установлена опция \a PIIODevice::BlockingRead,
//! то этот метод ожидает хотя бы одного байта.
//! \returns Количество прочитанных байт, -1 при ошибке
//!
//! \~\sa \a readData(), \a readString()
ssize_t PISerial::readDevice(void * read_to, ssize_t max_size) {
#ifdef WINDOWS
if (!canRead()) return -1;
if (sending) return -1;
// piCoutObj << "read ..." << PRIVATE->hCom << max_size;
DWORD mask = 0;
if (GetCommMask(PRIVATE->hCom, &mask) == FALSE) {
piCoutObj << "Read error: %1"_tr("PISerial").arg(errorString());
stop();
close();
return 0;
}
memset(&(PRIVATE->overlap), 0, sizeof(PRIVATE->overlap));
PRIVATE->overlap.hEvent = PRIVATE->event.getEvent();
PRIVATE->readed = 0;
ReadFile(PRIVATE->hCom, read_to, max_size, NULL, &(PRIVATE->overlap));
DWORD err = GetLastError();
// piCoutObj << "read" << err;
if (err == ERROR_BAD_COMMAND || err == ERROR_ACCESS_DENIED) {
piCoutObj << "Read error: %1"_tr("PISerial").arg(errorString());
stop();
close();
return 0;
}
// piCout << "wait ...";
if (PRIVATE->event.wait()) {
GetOverlappedResult(PRIVATE->hCom, &(PRIVATE->overlap), &(PRIVATE->readed), FALSE);
} else
return -1;
// piCoutObj << "read" << (PRIVATE->readed) << errorString();
return PRIVATE->readed;
#else
if (!canRead()) return -1;
if (isOptionSet(PIIODevice::BlockingRead)) {
if (!PRIVATE->event.wait(fd)) return -1;
}
ssize_t ret = ::read(fd, read_to, max_size);
if (ret < 0) {
int err = errno;
if (err == EBADF || err == EFAULT || err == EINVAL || err == EIO) {
stopThreadedRead();
close();
return 0;
}
}
return ret;
#endif
}
ssize_t PISerial::writeDevice(const void * data, ssize_t max_size) {
if (fd == -1 || !canWrite()) {
// piCoutObj << "Can`t write to uninitialized COM";
return -1;
}
#ifdef WINDOWS
DWORD wrote(0);
// piCoutObj << "send ..." << max_size;// << ": " << PIString((char*)data, max_size);
sending = true;
memset(&(PRIVATE->overlap_write), 0, sizeof(PRIVATE->overlap_write));
PRIVATE->overlap_write.hEvent = PRIVATE->event_write.getEvent();
WriteFile(PRIVATE->hCom, data, max_size, NULL, &(PRIVATE->overlap_write));
if (PRIVATE->event_write.wait()) {
GetOverlappedResult(PRIVATE->hCom, &(PRIVATE->overlap_write), &wrote, FALSE);
}
sending = false;
// piCoutObj << "send ok" << wrote;// << " bytes in " << path();
#else
ssize_t wrote;
wrote = ::write(fd, data, max_size);
if (isOptionSet(BlockingWrite)) tcdrain(fd);
#endif
return (ssize_t)wrote;
// piCoutObj << "Error while sending";
}
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::constructFullPathDevice() const {
PIString ret;
ret += path() + ":" + PIString::fromNumber(int(inSpeed())) + ":" + PIString::fromNumber(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::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) {
case 0: setProperty("path", p); break;
case 1:
setProperty("outSpeed", p.toInt());
setProperty("inSpeed", p.toInt());
break;
case 2: setProperty("dataBitsCount", p.toInt()); break;
case 3:
p = p.left(1).toLowerCase();
if (p != "n") setParameter(ParityControl);
if (p == "o") setParameter(ParityOdd);
break;
case 4:
if (p.toInt() == 2) setParameter(TwoStopBits);
break;
}
}
applySettings();
}
PIPropertyStorage PISerial::constructVariantDevice() const {
PIPropertyStorage ret;
ret.addProperty("path", path());
PIVariantTypes::Enum e;
PIVector as = availableSpeeds();
for (const auto s: as) {
e << PIVariantTypes::Enumerator(s, PIString::fromNumber(s));
}
e.selectValue((int)inSpeed());
ret.addProperty("speed", e);
e = PIVariantTypes::Enum();
for (int i = 5; i <= 8; ++i) {
e << PIVariantTypes::Enumerator(i, PIString::fromNumber(i));
}
e.selectValue(dataBitsCount());
ret.addProperty("dataBits", e);
e = PIVariantTypes::Enum();
e << "None"
<< "Odd"
<< "Even";
if (parameters()[ParityControl]) {
if (parameters()[ParityOdd])
e.selectValue(1);
else
e.selectValue(2);
} else
e.selectValue(0);
ret.addProperty("parity", e);
e = PIVariantTypes::Enum();
for (int i = 1; i <= 2; ++i) {
e << PIVariantTypes::Enumerator(i, PIString::fromNumber(i));
}
e.selectValue(parameters()[TwoStopBits] ? 2 : 1);
ret.addProperty("stopBits", e);
return ret;
}
void PISerial::configureFromVariantDevice(const PIPropertyStorage & d) {
setPath(d.propertyValueByName("path").toString());
setSpeed((Speed)d.propertyValueByName("speed").toEnum().selectedValue());
setDataBitsCount(d.propertyValueByName("dataBits").toEnum().selectedValue());
PIVariantTypes::Enum e = d.propertyValueByName("parity").toEnum();
setParameter(ParityControl, e.selectedValue() > 0);
setParameter(ParityOdd, e.selectedValue() == 1);
setParameter(TwoStopBits, d.propertyValueByName("stopBits").toEnum().selectedValue() == 2);
}
PIVector PISerial::availableSpeeds() {
PIVector spds;
spds << 50 << 75 << 110 << 300 << 600 << 1200 << 2400 << 4800 << 9600 <<
#ifdef WINDOWS
14400 <<
#endif
19200 << 38400 << 57600 << 115200 << 230400 << 460800 << 500000 << 576000 << 921600 << 1000000 << 1152000 << 1500000 << 2000000
<< 2500000 << 3000000 << 3500000 << 4000000;
return spds;
}
PIStringList PISerial::availableDevices(bool test) {
PIVector devs = availableDevicesInfo(test);
PIStringList ret;
for (const auto & d: devs)
ret << d.path;
return ret;
}
#ifdef WINDOWS
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
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
PIStringList prefixes;
# ifdef QNX
prefixes << "ser";
# else
prefixes << "ttyS"
<< "ttyO"
<< "ttyUSB"
<< "ttyACM"
<< "ttyGS"
<< "ttyMI"
<< "ttymxc"
<< "ttyAMA"
<< "rfcomm"
<< "ircomm";
# ifdef FREE_BSD
prefixes << "cu";
# endif
# ifdef MAC_OS
prefixes.clear();
prefixes << "cu."
<< "tty.";
# endif
PIFile file_prefixes("/proc/tty/drivers", PIIODevice::ReadOnly);
if (file_prefixes.open()) {
PIString fc = PIString::fromAscii(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();
# ifdef LINUX
char linkbuf[1024];
# endif
for (const auto & e: de) { // TODO changes in FileInfo
for (const auto & p: prefixes) {
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::fromAscii(_f.readAll()).trim().toInt(16);
if (_f.open(fpath + "idProduct", PIIODevice::ReadOnly)) di.pID = PIString::fromAscii(_f.readAll()).trim().toInt(16);
if (_f.open(fpath + "product", PIIODevice::ReadOnly)) di.description = PIString::fromUTF8(_f.readAll()).trim();
if (_f.open(fpath + "manufacturer", PIIODevice::ReadOnly))
di.manufacturer = PIString::fromUTF8(_f.readAll()).trim();
if (di.pID > 0) break;
}
}
# endif
ret << di;
}
}
}
# endif
#endif
if (test) {
for (int i = 0; i < ret.size_s(); ++i) {
#ifdef WINDOWS
void * hComm = CreateFileA(ret[i].path.dataAscii(),
GENERIC_READ,
FILE_SHARE_READ,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
0);
if (hComm == INVALID_HANDLE_VALUE) {
#else
int fd = ::open(ret[i].path.dataAscii(), O_NOCTTY | O_RDONLY);
if (fd == -1) {
#endif
ret.remove(i);
--i;
continue;
}
bool rok = true;
#ifndef WINDOWS
int void_ = 0;
fcntl(fd, F_SETFL, O_NONBLOCK);
if (::read(fd, &void_, 1) == -1) rok = errno != EIO;
#endif
if (!rok) {
ret.remove(i);
--i;
continue;
}
#ifdef WINDOWS
CloseHandle(hComm);
#else
::close(fd);
#endif
}
}
return ret;
}
void PISerial::optionsChanged() {
if (isOpened()) setTimeouts();
}
void PISerial::threadedReadBufferSizeChanged() {
if (!isOpened()) return;
#if defined(LINUX)
serial_struct ss;
ioctl(fd, TIOCGSERIAL, &ss);
// piCoutObj << "b" << ss.xmit_fifo_size;
ss.xmit_fifo_size = piMaxi(threadedReadBufferSize(), 4096);
ioctl(fd, TIOCSSERIAL, &ss);
// piCoutObj << "a" << ss.xmit_fifo_size;
#endif
}