/*
* Unofficial Qt Serial Port Library
*
* Copyright (c) 2010 Inbiza Systems Inc. All rights reserved.
*
* 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 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
*
* author labs@inbiza.com
*/
#include
#include "commdcbhelper.h"
/* glue for unsupported windows speeds */
#define CBR_230400 230400
#define CBR_460800 460800
#define CBR_500000 500000
#define CBR_576000 576000
#define CBR_921600 921600
namespace TNX {
/*!
Constructs a CommDCBHelper object with the given \a file handle.
*/
CommDCBHelper::CommDCBHelper(HANDLE fileHandle)
: fileHandle_(fileHandle), originalAttrs_(NULL), currentAttrs_(NULL)
{
Q_ASSERT(fileHandle_ > 0);
originalAttrs_ = new DCB();
currentAttrs_ = new DCB();
// save the current serial port attributes
// see restoreDCB()
saveDCB();
// clone the original attributes
*currentAttrs_ = *originalAttrs_;
// initialize port attributes for serial port communication
initDCB();
}
CommDCBHelper::~CommDCBHelper()
{
// It is good practice to reset a serial port back to the state in
// which you found it. This is why we saved the original DCB struct
restoreDCB();
delete originalAttrs_;
delete currentAttrs_;
}
/*!
Sets the DCB structure.
*/
bool CommDCBHelper::applyChanges(ChangeApplyTypes apptype)
{
if ( apptype == PortAttrOnlyAppTy || apptype == AllAppTy ) {
if ( !SetCommState(fileHandle_, currentAttrs_) ) {
qDebug() << QString("CommDCBHelper::applyChanges(file: %1, PortAttributes) failed: %2(Err #%3)")
.arg((quintptr)fileHandle_)
.arg(errorText(GetLastError()))
.arg(GetLastError());
return false;
}
}
// Communication Timeouts
if ( apptype == CommTimeoutsOnlyAppTy || apptype == AllAppTy ) {
if ( !SetCommTimeouts(fileHandle_, &commTimeouts_) ) {
qDebug() << QString("CommDCBHelper::applyChanges(file: %1, CommTimeouts) failed: %2(Err #%3)")
.arg((quintptr)fileHandle_)
.arg(errorText(GetLastError()))
.arg(GetLastError());
return false;
}
}
return true;
}
/*!
*/
bool CommDCBHelper::setCtrSignal(ControlSignals csig, bool value)
{
DWORD sig;
switch ( csig ) {
case CSIGNAL_RTS:
sig = value ? SETRTS : CLRRTS;
break;
case CSIGNAL_DTR:
sig = value ? SETDTR : CLRDTR;
break;
default:
qDebug() << QString("CommDCBHelper::setCtrSignal(file: %1, csig: %2) failed." \
"Given signal is read only.")
.arg((quintptr)fileHandle_)
.arg(csig);
return false;
}
if ( !EscapeCommFunction(fileHandle_, sig) ) {
qDebug() << QString("CommDCBHelper::setCtrSignal(file: %1, csig: %2) failed" \
"when fetching control signal values : %3(Err #%4)")
.arg((quintptr)fileHandle_)
.arg(csig)
.arg(errorText(GetLastError()))
.arg(GetLastError());
return false;
}
return true;
}
/*!
*/
QSerialPort::CommSignalValues CommDCBHelper::ctrSignal(ControlSignals csig) const
{
DWORD status = 0;
if ( !GetCommModemStatus(fileHandle_, &status) ) {
qDebug() << QString("CommDCBHelper::ctrSignal(file: %1, csig: %2) failed" \
"when fetching control signal values : %3(Err #%4)")
.arg((quintptr)fileHandle_)
.arg(csig)
.arg(errorText(GetLastError()))
.arg(GetLastError());
return QSerialPort::Signal_Unknown;
}
DWORD sig;
switch ( csig ) {
case CSIGNAL_CTS:
sig = MS_CTS_ON;
break;
case CSIGNAL_DSR:
sig = MS_DSR_ON;
break;
case CSIGNAL_DCD:
sig = MS_RLSD_ON;
break;
case CSIGNAL_RNG:
sig = MS_RING_ON;
break;
default:
qDebug() << QString("CommDCBHelper::ctrSignal(file: %1, csig: %2) failed." \
"Invalid signal.")
.arg((quintptr)fileHandle_)
.arg(csig);
return QSerialPort::Signal_Unknown;
}
return ((status & sig) ? QSerialPort::Signal_On : QSerialPort::Signal_Off);
}
/*!
*/
void CommDCBHelper::initDCB()
{
currentAttrs_->DCBlength = sizeof(DCB);
currentAttrs_->EvtChar = '\0';
currentAttrs_->EofChar = '\0';
currentAttrs_->ErrorChar = '\0';
currentAttrs_->XonChar = '\0';
currentAttrs_->XoffChar = '\0';
currentAttrs_->fDtrControl = DTR_CONTROL_DISABLE;
currentAttrs_->fRtsControl = RTS_CONTROL_DISABLE;
currentAttrs_->fOutxCtsFlow = 0;
currentAttrs_->fOutxDsrFlow = 0;
currentAttrs_->fDsrSensitivity = 0;
currentAttrs_->fOutX = 0;
currentAttrs_->fInX = 0;
currentAttrs_->fTXContinueOnXoff = 0;
currentAttrs_->fErrorChar = 0;
currentAttrs_->XonLim = 0;
currentAttrs_->XoffLim = 0;
currentAttrs_->fBinary = 1;
currentAttrs_->fNull = 0;
// If a communications port has been set up with a TRUE value for the fAbortOnError member
// of the setup DCB structure, the communications software will terminate all read and write
// operations on the communications port when a communications error occurs. No new read or write
// operations will be accepted until the application acknowledges the communications error by
// calling the ClearCommError function.
currentAttrs_->fAbortOnError = 0;
currentAttrs_->BaudRate = CBR_9600;
currentAttrs_->fParity = 0;
currentAttrs_->Parity = 0;
currentAttrs_->StopBits = 0;
currentAttrs_->ByteSize = 8;
// ensure the new attributes take effect immediately
applyChanges(AllAppTy);
}
/*!
*/
void CommDCBHelper::saveDCB()
{
// get the current serial port attributes
if ( !GetCommState(fileHandle_, originalAttrs_) ) {
qDebug() << QString("CommDCBHelper::saveDCB(file: %1) failed when" \
" getting original port attributes: %2(Err #%3)")
.arg((quintptr)fileHandle_)
.arg(errorText(GetLastError()))
.arg(GetLastError());
}
}
/*!
*/
bool CommDCBHelper::getCommTimeouts()
{
// get the current communication timeouts
if ( !GetCommTimeouts(fileHandle_, &commTimeouts_) ) {
qDebug() << QString("CommDCBHelper::getCommTimeouts(file: %1) failed when" \
" getting communication timeout values: %2(Err #%3)")
.arg((quintptr)fileHandle_)
.arg(errorText(GetLastError()))
.arg(GetLastError());
return false;
}
return true;
}
/*!
*/
void CommDCBHelper::restoreDCB()
{
if ( !originalAttrs_ || !SetCommState(fileHandle_, originalAttrs_) ) {
qDebug() << QString("CommDCBHelper::restoreDCB(file: %1) failed when resetting " \
"serial port attributes: %2(Err #%3)")
.arg((quintptr)fileHandle_)
.arg(errorText(GetLastError()))
.arg(GetLastError());
}
}
/*!
*/
void CommDCBHelper::setBaudRate(QPortSettings::BaudRate baudRate)
{
DWORD baud = CBR_9600;
switch ( baudRate ) {
case QPortSettings::BAUDR_110:
baud = CBR_110;
break;
case QPortSettings::BAUDR_300:
baud = CBR_300;
break;
case QPortSettings::BAUDR_600:
baud = CBR_600;
break;
case QPortSettings::BAUDR_1200:
baud = CBR_1200;
break;
case QPortSettings::BAUDR_2400:
baud = CBR_2400;
break;
case QPortSettings::BAUDR_4800:
baud = CBR_4800;
break;
case QPortSettings::BAUDR_9600:
baud = CBR_9600;
break;
case QPortSettings::BAUDR_14400:
baud = CBR_14400;
break;
case QPortSettings::BAUDR_19200:
baud = CBR_19200;
break;
case QPortSettings::BAUDR_38400:
baud = CBR_38400;
break;
case QPortSettings::BAUDR_56000:
baud = CBR_56000;
break;
case QPortSettings::BAUDR_57600:
baud = CBR_57600;
break;
case QPortSettings::BAUDR_115200:
baud = CBR_115200;
break;
case QPortSettings::BAUDR_128000:
baud = CBR_128000;
break;
case QPortSettings::BAUDR_230400:
baud = CBR_230400;
break;
case QPortSettings::BAUDR_256000:
baud = CBR_256000;
break;
case QPortSettings::BAUDR_460800:
baud = CBR_460800;
break;
case QPortSettings::BAUDR_500000:
baud = CBR_500000;
break;
case QPortSettings::BAUDR_576000:
baud = CBR_576000;
break;
case QPortSettings::BAUDR_921600:
baud = CBR_921600;
break;
default:
qWarning() << "CommDCBHelper::setBaudRate(" << baudRate << "): " \
"Unsupported baud rate";
}
currentAttrs_->BaudRate = baud;
}
/*!
*/
QPortSettings::BaudRate CommDCBHelper::baudRate() const
{
DWORD baud;
DCB dcb;
// although we store the last value of the baud rate attribute in currentAttrs_ structure,
// it is better practice to request the actual value from the OS.
if ( !GetCommState(fileHandle_, &dcb) ) {
qDebug() << QString("CommDCBHelper::baudRate(file: %1) failed when" \
" getting baud rate: %2(Err #%3)")
.arg((quintptr)fileHandle_)
.arg(errorText(GetLastError()))
.arg(GetLastError());
baud = currentAttrs_->BaudRate;
}
else {
baud = dcb.BaudRate;
}
Q_ASSERT(currentAttrs_->BaudRate == baud);
switch ( baud ) {
case CBR_110:
return QPortSettings::BAUDR_110;
case CBR_300:
return QPortSettings::BAUDR_300;
case CBR_600:
return QPortSettings::BAUDR_600;
case CBR_1200:
return QPortSettings::BAUDR_1200;
case CBR_2400:
return QPortSettings::BAUDR_2400;
case CBR_4800:
return QPortSettings::BAUDR_4800;
case CBR_9600:
return QPortSettings::BAUDR_9600;
case CBR_14400:
return QPortSettings::BAUDR_14400;
case CBR_19200:
return QPortSettings::BAUDR_19200;
case CBR_38400:
return QPortSettings::BAUDR_38400;
case CBR_56000:
return QPortSettings::BAUDR_56000;
case CBR_57600:
return QPortSettings::BAUDR_57600;
case CBR_115200:
return QPortSettings::BAUDR_115200;
case CBR_128000:
return QPortSettings::BAUDR_128000;
case CBR_230400:
return QPortSettings::BAUDR_230400;
case CBR_256000:
return QPortSettings::BAUDR_256000;
case CBR_500000:
return QPortSettings::BAUDR_500000;
case CBR_576000:
return QPortSettings::BAUDR_57600;
case CBR_921600:
return QPortSettings::BAUDR_921600;
default:
qWarning() << "CommDCBHelper::baudRate(): Unknown baud rate";
}
return QPortSettings::BAUDR_UNKNOWN;
}
/*!
*/
QPortSettings::DataBits CommDCBHelper::dataBits() const
{
DCB dcb;
BYTE dataBits;
// get the current serial port attributes
if ( !GetCommState(fileHandle_, &dcb) ) {
qDebug() << QString("CommDCBHelper::dataBits(file: %1) failed when" \
" getting original port attributes: %2(Err #%3)")
.arg((quintptr)fileHandle_)
.arg(errorText(GetLastError()))
.arg(GetLastError());
dataBits = currentAttrs_->ByteSize;
}
else {
dataBits = dcb.ByteSize;
}
Q_ASSERT(currentAttrs_->ByteSize == dataBits);
if ( dataBits == 5 )
return QPortSettings::DB_5;
else if ( dataBits == 6 )
return QPortSettings::DB_6;
else if ( dataBits == 7 )
return QPortSettings::DB_7;
else if ( dataBits == 8 )
return QPortSettings::DB_8;
else
return QPortSettings::DB_UNKNOWN;
}
/*!
*/
void CommDCBHelper::setDataBits(QPortSettings::DataBits dataBits)
{
switch( dataBits ) {
/*5 data bits*/
case QPortSettings::DB_5:
currentAttrs_->ByteSize = 5;
break;
/*6 data bits*/
case QPortSettings::DB_6:
currentAttrs_->ByteSize = 6;
break;
/*7 data bits*/
case QPortSettings::DB_7:
currentAttrs_->ByteSize = 7;
break;
/*8 data bits*/
case QPortSettings::DB_8:
currentAttrs_->ByteSize = 8;
break;
default:
currentAttrs_->ByteSize = 8;
qWarning() << "CommDCBHelper::setDataBits(enum[" << dataBits << "]): Unsupported data bits";
}
}
/*!
*/
QPortSettings::Parity CommDCBHelper::parity() const
{
DCB dcb;
BYTE parity;
DWORD fParity;
// get the current serial port attributes
if ( !GetCommState(fileHandle_, &dcb) ) {
qDebug() << QString("CommDCBHelper::parity(file: %1) failed when" \
" getting original port attributes: %2(Err #%3)")
.arg((quintptr)fileHandle_)
.arg(errorText(GetLastError()))
.arg(GetLastError());
fParity = currentAttrs_->fParity;
parity = currentAttrs_->Parity;
}
else {
fParity = dcb.fParity;
parity = dcb.Parity;
}
// For some reason windows keep changing fParity value even Parity field is set
// something other than 0 (no parity)
//Q_ASSERT(currentAttrs_->fParity == fParity && currentAttrs_->Parity == parity);
//if ( fParity != 1 ) {
// return QPortSettings::PAR_NONE;
//}
//else {
if ( parity == 0 )
return QPortSettings::PAR_NONE;
else if ( parity == 1 )
return QPortSettings::PAR_ODD;
else if ( parity == 2 )
return QPortSettings::PAR_EVEN;
else if ( parity == 3 )
return QPortSettings::PAR_MARK;
else if ( parity == 4 )
return QPortSettings::PAR_SPACE;
else
return QPortSettings::PAR_UNKNOWN;
//}
}
/*!
*/
void CommDCBHelper::setParity(QPortSettings::Parity parity)
{
switch ( parity ) {
/*no parity*/
case QPortSettings::PAR_NONE:
currentAttrs_->fParity = 0;
currentAttrs_->Parity = 0;
break;
/*odd parity*/
case QPortSettings::PAR_ODD:
currentAttrs_->fParity = 1;
currentAttrs_->Parity = 1;
break;
/*even parity*/
case QPortSettings::PAR_EVEN:
currentAttrs_->fParity = 1;
currentAttrs_->Parity = 2;
break;
/*mark parity*/
case QPortSettings::PAR_MARK:
currentAttrs_->fParity = 1;
currentAttrs_->Parity = 3;
break;
/*space parity*/
case QPortSettings::PAR_SPACE:
currentAttrs_->fParity = 1;
currentAttrs_->Parity = 4;
break;
default:
currentAttrs_->fParity = 0;
currentAttrs_->Parity = 0;
qWarning() << "CommDCBHelper::setParity(enum[" << parity << "]): Unsupported parity";
}
}
/*!
*/
QPortSettings::StopBits CommDCBHelper::stopBits() const
{
DCB dcb;
BYTE stopBits;
// get the current serial port attributes
if ( !GetCommState(fileHandle_, &dcb) ) {
qDebug() << QString("CommDCBHelper::stopBits(file: %1) failed when" \
" getting original port attributes: %2(Err #%3)")
.arg((quintptr)fileHandle_)
.arg(errorText(GetLastError()))
.arg(GetLastError());
stopBits = currentAttrs_->StopBits;
}
else {
stopBits = dcb.StopBits;
}
Q_ASSERT(currentAttrs_->StopBits == stopBits);
if ( stopBits == 0 )
return QPortSettings::STOP_1;
else if ( stopBits == 1 )
return QPortSettings::STOP_1_5;
else if ( stopBits == 2 )
return QPortSettings::STOP_2;
else
return QPortSettings::STOP_UNKNOWN;
}
/*!
*/
void CommDCBHelper::setStopBits(QPortSettings::StopBits stopBits)
{
switch( stopBits ) {
/*one stop bit*/
case QPortSettings::STOP_1:
currentAttrs_->StopBits = 0;
break;
/*one and half bit*/
case QPortSettings::STOP_1_5:
currentAttrs_->StopBits = 1;
break;
/*two stop bits*/
case QPortSettings::STOP_2:
currentAttrs_->StopBits = 2;
break;
default:
currentAttrs_->StopBits = 0;
qWarning() << "CommDCBHelper::setStopBits(enum[" << stopBits << "]): Unsupported stop bits";
}
}
/*!
@return FLOW_UNKNOWN in error case
*/
QPortSettings::FlowControl CommDCBHelper::flowControl() const
{
DCB dcb;
// get the current serial port attributes.
if ( !GetCommState(fileHandle_, &dcb) ) {
qWarning() << QString("CommDCBHelper::flowControl(file: %1) failed when" \
" getting original port attributes: %2(Err #%3)")
.arg((quintptr)fileHandle_)
.arg(errorText(GetLastError()))
.arg(GetLastError());
return QPortSettings::FLOW_UNKNOWN;
}
if ( !dcb.fOutxCtsFlow && dcb.fRtsControl == RTS_CONTROL_DISABLE ) {
if ( dcb.fInX && dcb.fOutX )
return QPortSettings::FLOW_XONXOFF;
else if ( !dcb.fInX && !dcb.fOutX )
return QPortSettings::FLOW_OFF;
else
return QPortSettings::FLOW_UNKNOWN;
}
else if ( dcb.fOutxCtsFlow && dcb.fRtsControl == RTS_CONTROL_HANDSHAKE &&
!dcb.fInX && !dcb.fOutX ) {
return QPortSettings::FLOW_HARDWARE;
}
else {
return QPortSettings::FLOW_UNKNOWN;
}
}
/*!
*/
void CommDCBHelper::setFlowControl(QPortSettings::FlowControl flow)
{
switch( flow ) {
/*no flow control*/
case QPortSettings::FLOW_OFF:
currentAttrs_->fOutxCtsFlow = false;
currentAttrs_->fRtsControl = RTS_CONTROL_DISABLE;
currentAttrs_->fInX = false;
currentAttrs_->fOutX = false;
break;
/*software (XON/XOFF) flow control*/
case QPortSettings::FLOW_XONXOFF:
currentAttrs_->fOutxCtsFlow = false;
currentAttrs_->fRtsControl = RTS_CONTROL_DISABLE;
currentAttrs_->fInX = true;
currentAttrs_->fOutX = true;
break;
case QPortSettings::FLOW_HARDWARE:
currentAttrs_->fOutxCtsFlow = true;
currentAttrs_->fRtsControl = RTS_CONTROL_HANDSHAKE;
currentAttrs_->fInX = false;
currentAttrs_->fOutX = false;
break;
default:
currentAttrs_->fOutxCtsFlow = false;
currentAttrs_->fRtsControl = RTS_CONTROL_DISABLE;
currentAttrs_->fInX = false;
currentAttrs_->fOutX = false;
qWarning("CommDCBHelper::setFlowControl(%i): Unsupported FlowType", flow);
}
}
//
// ReadIntervalTimeout
// Maximum time allowed to elapse between the arrival of two characters on the communications
// line, in milliseconds. During a ReadFile operation, the time period begins when the first
// character is received. If the interval between the arrival of any two characters
// exceeds this amount, the ReadFile operation is completed and any buffered data is returned.
// A value of zero indicates that interval time-outs are not used. A value of MAXDWORD,
// combined with zero values for both the ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier
// parameters, specifies that the read operation is to return immediately with the characters that
// have already been received, even if no characters have been received.
//
// ReadTotalTimeoutMultiplier
// Multiplier used to calculate the total time-out period for read operations,
// in milliseconds. For each read operation, this value is multiplied by the requested
// number of bytes to be read.
//
// ReadTotalTimeoutConstant
// Constant used to calculate the total time-out period for read operations, in milliseconds.
// For each read operation, this value is added to the product of the ReadTotalTimeoutMultiplier
// parameter and the requested number of bytes. A value of zero for both the ReadTotalTimeoutMultiplier
// and ReadTotalTimeoutConstant members indicates that total time-outs are not used for read operations.
//
// WriteTotalTimeoutMultiplier
// Multiplier used to calculate the total time-out period for write operations, in milliseconds.
// For each write operation, this value is multiplied by the number of bytes to be written.
//
// WriteTotalTimeoutConstant
// Constant used to calculate the total time-out period for write operations, in milliseconds.
// For each write operation, this value is added to the product of the WriteTotalTimeoutMultiplier
// member and the number of bytes to be written. A value of zero for both the WriteTotalTimeoutMultiplier
// and WriteTotalTimeoutConstant parameters indicates that total time-outs are not used for write operations.
//
bool CommDCBHelper::commTimeouts(CommTimeouts &commtimeouts)
{
// get the current serial port attributes
if ( !getCommTimeouts() )
return false;
commtimeouts.Win32ReadIntervalTimeout = commTimeouts_.ReadIntervalTimeout;
commtimeouts.Win32ReadTotalTimeoutConstant = commTimeouts_.ReadTotalTimeoutConstant;
commtimeouts.Win32ReadTotalTimeoutMultiplier = commTimeouts_.ReadTotalTimeoutMultiplier;
commtimeouts.Win32WriteTotalTimeoutConstant = commTimeouts_.WriteTotalTimeoutConstant;
commtimeouts.Win32WriteTotalTimeoutMultiplier = commTimeouts_.WriteTotalTimeoutMultiplier;
return true;
}
void CommDCBHelper::setCommTimeouts(const CommTimeouts commtimeouts)
{
commTimeouts_.ReadIntervalTimeout = commtimeouts.Win32ReadIntervalTimeout;
commTimeouts_.ReadTotalTimeoutConstant = commtimeouts.Win32ReadTotalTimeoutConstant;
commTimeouts_.ReadTotalTimeoutMultiplier = commtimeouts.Win32ReadTotalTimeoutMultiplier;
commTimeouts_.WriteTotalTimeoutConstant = commtimeouts.Win32WriteTotalTimeoutConstant;
commTimeouts_.WriteTotalTimeoutMultiplier = commtimeouts.Win32WriteTotalTimeoutMultiplier;
}
/*!
*/
QString CommDCBHelper::errorText(DWORD err)
{
unsigned short *pMsg = 0;
// if character set is Ascii, pMsg should be defined as char *string = 0
// and QString::fromLocal8Bit(pMsg) should be used instead.
if ( FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
0,
err,
MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
(LPTSTR)&pMsg,
0,
NULL) == 0 ) {
return "There is no message associated with that error code.";
}
else {
QString errMsg = QString::fromUtf16(pMsg);
LocalFree(pMsg); // free memory allocated by the OS
return errMsg;
}
}
} // namespace