Newer
Older
/*=====================================================================
======================================================================*/
/**
* @file
* @brief Cross-platform support for serial ports
*
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#include <QTimer>
#include <QDebug>
#include <qserialport.h>
#include <qserialportinfo.h>
SerialLink::SerialLink(QString portname, int baudRate, bool hardwareFlowControl, bool parity,
int dataBits, int stopBits) :
inDataIndex(0),
outDataIndex(0),
Michael Carpenter
committed
m_stopp(false),
Michael Carpenter
committed
m_reqReset(false)
// Initialize our arrays manually, cause C++<03 is dumb.
for (int i = 0; i < buffer_size; ++i)
{
inDataWriteAmounts[i] = 0;
inDataWriteTimes[i] = 0;
outDataWriteAmounts[i] = 0;
outDataWriteTimes[i] = 0;
}
// Get the name of the current port in use.
m_portName = portname.trimmed();
if (m_portName == "" && getCurrentPorts().size() > 0)
m_portName = m_ports.first().trimmed();
if (hardwareFlowControl)
{
qDebug() << "create SerialLink " << portname << baudRate << hardwareFlowControl
<< parity << dataBits << stopBits;
qDebug() << "m_portName " << m_portName;
LinkManager::instance()->add(this);
Michael Carpenter
committed
void SerialLink::requestReset()
{
QMutexLocker locker(&this->m_stoppMutex);
m_reqReset = true;
}
QList<QString> SerialLink::getCurrentPorts()
QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
if( portList.count() == 0){
qDebug() << "No Ports Found" << m_ports;
}
foreach (const QSerialPortInfo &info, portList)
// qDebug() << "PortName : " << info.portName()
// << "Description : " << info.description();
// qDebug() << "Manufacturer: " << info.manufacturer();
m_ports.append(info.portName());
void SerialLink::loadSettings()
{
// Load defaults from settings
QSettings settings(QGC::ORG_NAME, QGC::APPNAME);
settings.sync();
if (settings.contains("SERIALLINK_COMM_PORT"))
{
m_portName = settings.value("SERIALLINK_COMM_PORT").toString();
m_baud = settings.value("SERIALLINK_COMM_BAUD").toInt();
m_parity = settings.value("SERIALLINK_COMM_PARITY").toInt();
m_stopBits = settings.value("SERIALLINK_COMM_STOPBITS").toInt();
m_dataBits = settings.value("SERIALLINK_COMM_DATABITS").toInt();
m_flowControl = settings.value("SERIALLINK_COMM_FLOW_CONTROL").toInt();
}
}
void SerialLink::writeSettings()
{
// Store settings
QSettings settings(QGC::ORG_NAME, QGC::APPNAME);
settings.setValue("SERIALLINK_COMM_PORT", getPortName());
settings.setValue("SERIALLINK_COMM_BAUD", getBaudRateType());
settings.setValue("SERIALLINK_COMM_PARITY", getParityType());
settings.setValue("SERIALLINK_COMM_STOPBITS", getStopBits());
settings.setValue("SERIALLINK_COMM_DATABITS", getDataBits());
settings.setValue("SERIALLINK_COMM_FLOW_CONTROL", getFlowType());
settings.sync();
}
void SerialLink::run()
{
if (!hardwareConnect()) {
Michael Carpenter
committed
//Need to error out here.
emit communicationError(getName(),"Error connecting: " + m_port->errorString());
disconnect(); // This tidies up and sends the necessary signals
emit communicationError(tr("Serial Port %1").arg(getPortName()), tr("Cannot read / write data - check physical USB and cable connections."));
Michael Carpenter
committed
return;
Michael Carpenter
committed
}
qint64 msecs = QDateTime::currentMSecsSinceEpoch();
qint64 initialmsecs = QDateTime::currentMSecsSinceEpoch();
Michael Carpenter
committed
quint64 bytes = 0;
bool triedreset = false;
bool triedDTR = false;
QMutexLocker locker(&this->m_stoppMutex);
if(m_stopp) {
m_stopp = false;
break; // exit the thread
}
if (m_reqReset) {
m_reqReset = false;
emit communicationUpdate(getName(),"Reset requested via DTR signal");
m_port->setDataTerminalReady(true);
msleep(250);
m_port->setDataTerminalReady(false);
}
// If there are too many errors on this link, disconnect.
qDebug() << "linkErrorCount too high: disconnecting!";
emit communicationUpdate(getName(), tr("Disconnecting on too many link errors"));
// Write all our buffered data out the serial port.
if (m_transmitBuffer.count() > 0) {
m_writeMutex.lock();
int numWritten = m_port->write(m_transmitBuffer);
Lorenz Meier
committed
bool txSuccess = m_port->waitForBytesWritten(5);
if (!txSuccess || (numWritten != m_transmitBuffer.count())) {
linkErrorCount++;
qDebug() << "TX Error! wrote" << numWritten << ", asked for " << m_transmitBuffer.count() << "bytes";
// Since we were successful, reset out error counter.
// Now that we transmit all of the data in the transmit buffer, flush it.
m_transmitBuffer = m_transmitBuffer.remove(0, numWritten);
m_writeMutex.unlock();
// Log this written data for this timestep. If this value ends up being 0 due to
// write() failing, that's what we want as well.
QMutexLocker statsLocker(&m_statisticsMutex);
WriteDataStatsBuffer(outDataWriteAmounts, outDataWriteTimes, &outDataIndex, numWritten, QDateTime::currentMSecsSinceEpoch());
//wait n msecs for data to be ready
//[TODO][BB] lower to SerialLink::poll_interval?
m_dataMutex.lock();
bool success = m_port->waitForReadyRead(10);
QByteArray readData = m_port->readAll();
while (m_port->waitForReadyRead(10))
readData += m_port->readAll();
m_dataMutex.unlock();
if (readData.length() > 0) {
emit bytesReceived(this, readData);
// Log this data reception for this timestep
QMutexLocker statsLocker(&m_statisticsMutex);
WriteDataStatsBuffer(inDataWriteAmounts, inDataWriteTimes, &inDataIndex, readData.length(), QDateTime::currentMSecsSinceEpoch());
// Track the total amount of data read.
m_dataMutex.unlock();
if (bytes != m_bytesRead) { // i.e things are good and data is being read.
Michael Carpenter
committed
msecs = QDateTime::currentMSecsSinceEpoch();
}
else {
if (QDateTime::currentMSecsSinceEpoch() - msecs > timeout) {
Michael Carpenter
committed
//It's been 10 seconds since the last data came in. Reset and try again
msecs = QDateTime::currentMSecsSinceEpoch();
if (msecs - initialmsecs > 25000) {
//After initial 25 seconds, timeouts are increased to 30 seconds.
//This prevents temporary silences from things like calibration commands
//from screwing things up. In all reality, timeouts should be enabled/disabled
//for events like that on a case by case basis.
//TODO ^^
timeout = 30000;
}
if (!triedDTR && triedreset) {
Michael Carpenter
committed
triedDTR = true;
emit communicationUpdate(getName(),"No data to receive on COM port. Attempting to reset via DTR signal");
Michael Carpenter
committed
qDebug() << "No data!!! Attempting reset via DTR.";
m_port->setDataTerminalReady(true);
msleep(250);
m_port->setDataTerminalReady(false);
Michael Carpenter
committed
}
else if (!triedreset) {
Michael Carpenter
committed
qDebug() << "No data!!! Attempting reset via reboot command.";
emit communicationUpdate(getName(),"No data to receive on COM port. Assuming possible terminal mode, attempting to reset via \"reboot\" command");
Michael Carpenter
committed
triedreset = true;
}
else {
emit communicationUpdate(getName(),"No data to receive on COM port....");
Michael Carpenter
committed
qDebug() << "No data!!!";
}
}
}
} // end of forever
if (m_port) { // [TODO][BB] Not sure we need to close the port here
qDebug() << "Closing Port #"<< __LINE__ << m_port->portName();
m_port->close();
delete m_port;
m_port = NULL;
emit disconnected();
emit connected(false);
void SerialLink::WriteDataStatsBuffer(quint64 *bytesBuffer, qint64 *timeBuffer, int *writeIndex, quint64 bytes, qint64 time)
{
int i = *writeIndex;
// Now write into the buffer, if there's no room, we just overwrite the first data point.
bytesBuffer[i] = bytes;
timeBuffer[i] = time;
// Increment and wrap the write index
++i;
if (i == buffer_size)
{
i = 0;
}
*writeIndex = i;
}
void SerialLink::writeBytes(const char* data, qint64 size)
{
QByteArray byteArray(data, size);
m_writeMutex.lock();
m_transmitBuffer.append(byteArray);
m_writeMutex.unlock();
} else {
disconnect();
// Error occured
emit communicationError(getName(), tr("Could not send data - link %1 is disconnected!").arg(getName()));
}
}
/**
* @brief Read a number of bytes from the interface.
*
* @param data Pointer to the data byte array to write the bytes to
* @param maxLength The maximum number of bytes to write
**/
void SerialLink::readBytes()
{
const qint64 maxLength = 2048;
char data[maxLength];
m_dataMutex.lock();
/* Read as much data in buffer as possible without overflow */
if(maxLength < numBytes) numBytes = maxLength;
QByteArray b(data, numBytes);
emit bytesReceived(this, b);
}
m_dataMutex.unlock();
}
}
/**
* @brief Get the number of bytes to read.
*
* @return The number of bytes to read
**/
qint64 SerialLink::bytesAvailable()
{
return 0;
}
}
/**
* @brief Disconnect the connection.
*
* @return True if connection has been disconnected, false if connection couldn't be disconnected.
**/
bool SerialLink::disconnect()
{
qDebug() << "disconnect";
if (m_port)
qDebug() << m_port->portName();
qDebug() << "running so disconnect" << m_port->portName();
QMutexLocker locker(&m_stoppMutex);
m_stopp = true;
wait(); // This will terminate the thread and close the serial port
emit disconnected(); // [TODO] There are signals from QSerialPort we should use
emit connected(false);
return true;
}
m_transmitBuffer.clear(); //clear the output buffer to avoid sending garbage at next connect
qDebug() << "already disconnected";
return true;
}
/**
* @brief Connect the connection.
*
* @return True if connection has been established, false if connection couldn't be established.
**/
bool SerialLink::connect()
{
QMutexLocker locker(&this->m_stoppMutex);
}
/**
* @brief This function is called indirectly by the connect() call.
*
* The connect() function starts the thread and indirectly calls this method.
*
* @return True if the connection could be established, false otherwise
* @see connect() For the right function to establish the connection.
**/
bool SerialLink::hardwareConnect()
{
qDebug() << "SerialLink:" << QString::number((long)this, 16) << "closing port";
m_port->close();
delete m_port;
m_port = NULL;
}
qDebug() << "SerialLink: hardwareConnect to " << m_portName;
m_port = new QSerialPort(m_portName);
if (m_port == NULL) {
emit communicationUpdate(getName(),"Error opening port: " + m_port->errorString());
return false; // couldn't create serial port.
Michael Carpenter
committed
}
QObject::connect(m_port,SIGNAL(aboutToClose()),this,SIGNAL(disconnected()));
QObject::connect(m_port, SIGNAL(error(SerialLinkPortError_t)),
this, SLOT(linkError(SerialLinkPortError_t)));
// port->setCommTimeouts(QSerialPort::CtScheme_NonBlockingRead);
if (!m_port->open(QIODevice::ReadWrite)) {
emit communicationUpdate(getName(),"Error opening port: " + m_port->errorString());
m_port->close();
return false; // couldn't open serial port
Michael Carpenter
committed
}
// Need to configure the port
m_port->setBaudRate(m_baud);
m_port->setDataBits(static_cast<QSerialPort::DataBits>(m_dataBits));
m_port->setFlowControl(static_cast<QSerialPort::FlowControl>(m_flowControl));
m_port->setStopBits(static_cast<QSerialPort::StopBits>(m_stopBits));
m_port->setParity(static_cast<QSerialPort::Parity>(m_parity));
qDebug() << "CONNECTING LINK: " << __FILE__ << __LINE__ << "with settings" << m_port->portName()
<< getBaudRate() << getDataBits() << getParityType() << getStopBits();
writeSettings();
void SerialLink::linkError(SerialLinkPortError_t error)
{
qDebug() << error;
}
/**
* @brief Check if connection is active.
*
* @return True if link is connected, false otherwise.
**/
bool SerialLink::isConnected() const
{
if (m_port) {
bool isConnected = m_port->isOpen();
// qDebug() << "SerialLink #" << __LINE__ << ":"<< m_port->portName()
// << " isConnected =" << QString::number(isConnected);
// qDebug() << "SerialLink #" << __LINE__ << ":" << m_portName
// << " isConnected = NULL";
int SerialLink::getId() const
QString SerialLink::getName() const
return m_portName;
Bryant Mairs
committed
/**
* This function maps baud rate constants to numerical equivalents.
* It relies on the mapping given in qportsettings.h from the QSerialPort library.
*/
qint64 SerialLink::getConnectionSpeed() const
{
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
int baudRate;
if (m_port) {
baudRate = m_port->baudRate();
} else {
baudRate = m_baud;
}
qint64 dataRate;
switch (baudRate)
{
case QSerialPort::Baud1200:
dataRate = 1200;
break;
case QSerialPort::Baud2400:
dataRate = 2400;
break;
case QSerialPort::Baud4800:
dataRate = 4800;
break;
case QSerialPort::Baud9600:
dataRate = 9600;
break;
case QSerialPort::Baud19200:
dataRate = 19200;
break;
case QSerialPort::Baud38400:
dataRate = 38400;
break;
case QSerialPort::Baud57600:
dataRate = 57600;
break;
case QSerialPort::Baud115200:
dataRate = 115200;
break;
// Otherwise do nothing.
case QSerialPort::UnknownBaud:
default:
dataRate = -1;
break;
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
qint64 SerialLink::getCurrentOutDataRate() const
{
const qint64 now = QDateTime::currentMSecsSinceEpoch();
// Limit the time we calculate to the recent past
const qint64 cutoff = now - stats_timespan;
// Grab the mutex for working with the stats variables
QMutexLocker statsLocker(&m_statisticsMutex);
// Now iterate through the buffer of all received data packets adding up all values
// within now and our cutof.
int index = outDataIndex;
qint64 totalBytes = 0;
qint64 totalTime = 0;
qint64 lastTime = 0;
int size = buffer_size;
while (size-- > 0)
{
// If this data is within our cutoff time, include it in our calculations.
// This also accounts for when the buffer is empty and filled with 0-times.
if (outDataWriteTimes[index] > cutoff && lastTime > 0) {
// Track the total time, using the previous time as our timeperiod.
totalTime += outDataWriteTimes[index] - lastTime;
totalBytes += outDataWriteAmounts[index];
}
// Track the last time sample for doing timespan calculations
lastTime = outDataWriteTimes[index];
// Increment and wrap the index if necessary.
if (++index == buffer_size)
{
index = 0;
}
}
// Return the final calculated value in bits / s, converted from bytes/ms.
qint64 dataRate = (totalTime != 0)?(qint64)((float)totalBytes * 8.0f / ((float)totalTime / 1000.0f)):0;
// Finally return our calculated data rate.
return dataRate;
}
qint64 SerialLink::getCurrentInDataRate() const
{
const qint64 now = QDateTime::currentMSecsSinceEpoch();
// Limit the time we calculate to the recent past
const qint64 cutoff = now - stats_timespan;
// Grab the mutex for working with the stats variables
QMutexLocker statsLocker(&m_statisticsMutex);
// Now iterate through the buffer of all received data packets adding up all values
// within now and our cutof.
int index = inDataIndex;
qint64 totalBytes = 0;
qint64 totalTime = 0;
qint64 lastTime = 0;
int size = buffer_size;
while (size-- > 0)
{
// If this data is within our cutoff time, include it in our calculations.
// This also accounts for when the buffer is empty and filled with 0-times.
if (inDataWriteTimes[index] > cutoff && lastTime > 0) {
// Track the total time, using the previous time as our timeperiod.
totalTime += inDataWriteTimes[index] - lastTime;
totalBytes += inDataWriteAmounts[index];
}
// Track the last time sample for doing timespan calculations
lastTime = inDataWriteTimes[index];
// Increment and wrap the index if necessary.
if (++index == buffer_size)
{
index = 0;
}
}
// Return the final calculated value in bits / s, converted from bytes/ms.
qint64 dataRate = (totalTime != 0)?(qint64)((float)totalBytes * 8.0f / ((float)totalTime / 1000.0f)):0;
// Finally return our calculated data rate.
return dataRate;
}
QString SerialLink::getPortName() const
{
// We should replace the accessors below with one to get the QSerialPort
int SerialLink::getBaudRate() const
{
return getConnectionSpeed();
int SerialLink::getBaudRateType() const
{
int baudRate;
if (m_port) {
baudRate = m_port->baudRate();
} else {
baudRate = m_baud;
}
return baudRate;
int SerialLink::getFlowType() const
{
int flowControl;
if (m_port) {
flowControl = m_port->flowControl();
} else {
flowControl = m_flowControl;
}
return flowControl;
int SerialLink::getParityType() const
{
int parity;
if (m_port) {
parity = m_port->parity();
} else {
parity = m_parity;
}
return parity;
int SerialLink::getDataBitsType() const
{
int dataBits;
if (m_port) {
dataBits = m_port->dataBits();
} else {
dataBits = m_dataBits;
}
return dataBits;
int SerialLink::getStopBitsType() const
{
int stopBits;
if (m_port) {
stopBits = m_port->stopBits();
} else {
stopBits = m_stopBits;
}
return stopBits;
int SerialLink::getDataBits() const
{
int ret;
int dataBits;
if (m_port) {
dataBits = m_port->dataBits();
} else {
dataBits = m_dataBits;
}
switch (dataBits) {
case QSerialPort::Data5:
ret = 5;
break;
ret = 6;
break;
ret = 7;
break;
ret = 8;
break;
default:
break;
}
return ret;
}
int SerialLink::getStopBits() const
{
int stopBits;
if (m_port) {
stopBits = m_port->stopBits();
} else {
stopBits = m_stopBits;
}
ret = 2;
break;
default:
ret = -1;
break;
}
return ret;
}
qDebug() << "current portName " << m_portName;
qDebug() << "setPortName to " << portName;
bool accepted = false;
if ((portName != m_portName)
&& (portName.trimmed().length() > 0)) {
m_portName = portName.trimmed();
if(m_port)
m_port->setPortName(portName);
emit nameChanged(m_portName); // [TODO] maybe we can eliminate this
}
bool SerialLink::setBaudRateType(int rateIndex)
{
Q_ASSERT_X(m_port != NULL, "setBaudRateType", "m_port is NULL");
// These minimum and maximum baud rates were based on those enumerated in qserialport.h
const int minBaud = (int)QSerialPort::Baud1200;
const int maxBaud = (int)QSerialPort::Baud115200;
if (m_port && (rateIndex >= minBaud && rateIndex <= maxBaud))
result = m_port->setBaudRate(static_cast<QSerialPort::BaudRate>(rateIndex));
emit updateLink(this);
return result;
bool SerialLink::setBaudRateString(const QString& rate)
{
bool ok;
int intrate = rate.toInt(&ok);
if (!ok) return false;
return setBaudRate(intrate);
}
bool accepted = false;
if (rate != m_baud) {
m_baud = rate;
accepted = true;
if (m_port)
accepted = m_port->setBaudRate(rate);
bool SerialLink::setFlowType(int flow)
{
bool accepted = false;
if (flow != m_flowControl) {
m_flowControl = static_cast<QSerialPort::FlowControl>(flow);
accepted = true;
if (m_port)
accepted = m_port->setFlowControl(static_cast<QSerialPort::FlowControl>(flow));
bool SerialLink::setParityType(int parity)
{
bool accepted = false;
if (parity != m_parity) {
m_parity = static_cast<QSerialPort::Parity>(parity);
accepted = true;
if (m_port) {
switch (parity) {
case QSerialPort::NoParity:
accepted = m_port->setParity(QSerialPort::NoParity);
break;
case 1: // Odd Parity setting for backwards compatibilty
accepted = m_port->setParity(QSerialPort::OddParity);
break;
case QSerialPort::EvenParity:
accepted = m_port->setParity(QSerialPort::EvenParity);
break;
case QSerialPort::OddParity:
accepted = m_port->setParity(QSerialPort::OddParity);
break;
default:
// If none of the above cases matches, there must be an error
accepted = false;
break;
}
bool SerialLink::setDataBits(int dataBits)
{
bool accepted = false;
if (dataBits != m_dataBits) {
m_dataBits = static_cast<QSerialPort::DataBits>(dataBits);
accepted = true;
if (m_port)
accepted = m_port->setDataBits(static_cast<QSerialPort::DataBits>(dataBits));
bool SerialLink::setStopBits(int stopBits)
{
// Note 3 is OneAndAHalf stopbits.
bool accepted = false;
if (stopBits != m_stopBits) {
m_stopBits = static_cast<QSerialPort::StopBits>(stopBits);
accepted = true;
if (m_port)
accepted = m_port->setStopBits(static_cast<QSerialPort::StopBits>(stopBits));
bool SerialLink::setDataBitsType(int dataBits)
{
bool accepted = false;
if (dataBits != m_dataBits) {
m_dataBits = static_cast<QSerialPort::DataBits>(dataBits);
accepted = true;
if (m_port)
accepted = m_port->setDataBits(static_cast<QSerialPort::DataBits>(dataBits));
}
return accepted;
}
bool SerialLink::setStopBitsType(int stopBits)
{
bool accepted = false;
if (stopBits != m_stopBits) {
m_stopBits = static_cast<QSerialPort::StopBits>(stopBits);
accepted = true;
if (m_port)
accepted = m_port->setStopBits(static_cast<QSerialPort::StopBits>(stopBits));
}
return accepted;
}