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) :
Lorenz Meier
committed
type(""),
m_is_cdc(true),
Michael Carpenter
committed
m_stopp(false),
Michael Carpenter
committed
m_reqReset(false)
Lorenz Meier
committed
// We're doing it wrong - because the Qt folks got the API wrong:
// http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/
moveToThread(this);
// 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();
Lorenz Meier
committed
checkIfCDC();
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();
Lorenz Meier
committed
checkIfCDC();
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();
}
Lorenz Meier
committed
void SerialLink::checkIfCDC()
{
Lorenz Meier
committed
QString description = "X";
foreach (QSerialPortInfo info,QSerialPortInfo::availablePorts())
{
if (m_portName == info.portName())
{
description = info.description();
break;
}
}
if (description.toLower().contains("mega") && description.contains("2560"))
{
type = "apm";
Lorenz Meier
committed
m_is_cdc = false;
Lorenz Meier
committed
qDebug() << "Attempting connection to an APM, with description:" << description;
}
else if (description.toLower().contains("px4"))
{
type = "px4";
Lorenz Meier
committed
m_is_cdc = true;
Lorenz Meier
committed
qDebug() << "Attempting connection to a PX4 unit with description:" << description;
}
else
{
type = "other";
Lorenz Meier
committed
m_is_cdc = false;
Lorenz Meier
committed
qDebug() << "Attempting connection to something unknown with description:" << description;
}
Lorenz Meier
committed
}
/**
* @brief Runs the thread
*
**/
void SerialLink::run()
{
checkIfCDC();
Lorenz Meier
committed
Lorenz Meier
committed
if (!hardwareConnect(type)) {
Michael Carpenter
committed
//Need to error out here.
Lorenz Meier
committed
QString err("Could not create port.");
if (m_port) {
err = m_port->errorString();
}
emit communicationError(getName(),"Error connecting: " + err);
// disconnect(); // This tidies up and sends the necessary signals
Michael Carpenter
committed
return;
Michael Carpenter
committed
}
qint64 msecs = QDateTime::currentMSecsSinceEpoch();
qint64 initialmsecs = QDateTime::currentMSecsSinceEpoch();
Michael Carpenter
committed
quint64 bytes = 0;
Lorenz Meier
committed
// 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.
Bryant Mairs
committed
QMutexLocker dataRateLocker(&dataRateMutex);
logDataRateToBuffer(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
Bryant Mairs
committed
QMutexLocker dataRateLocker(&dataRateMutex);
logDataRateToBuffer(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;
}
Lorenz Meier
committed
// if (!triedDTR && triedreset) {
// triedDTR = true;
// emit communicationUpdate(getName(),"No data to receive on COM port. Attempting to reset via DTR signal");
// qDebug() << "No data!!! Attempting reset via DTR.";
// m_port->setDataTerminalReady(true);
// msleep(250);
// m_port->setDataTerminalReady(false);
// }
// else if (!triedreset) {
// 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");
// m_port->write("reboot\r\n",8);
// triedreset = true;
// }
// else {
// emit communicationUpdate(getName(),"No data to receive on COM port....");
// qDebug() << "No data!!!";
// }
Michael Carpenter
committed
}
}
} // 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::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();
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.
**/
Lorenz Meier
committed
bool SerialLink::hardwareConnect(QString &type)
{
Lorenz Meier
committed
if (m_port) {
qDebug() << "SerialLink:" << QString::number((long)this, 16) << "closing port";
m_port->close();
Lorenz Meier
committed
QGC::SLEEP::usleep(50000);
}
Lorenz Meier
committed
qDebug() << "SerialLink: hardwareConnect to " << m_portName;
m_port = new QSerialPort(m_portName);
if (!m_port) {
emit communicationUpdate(getName(),"Error opening port: " + m_portName);
Michael Carpenter
committed
}
QObject::connect(m_port,SIGNAL(aboutToClose()),this,SIGNAL(disconnected()));
QObject::connect(m_port, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(linkError(QSerialPort::SerialPortError)));
Lorenz Meier
committed
checkIfCDC();
Lorenz Meier
committed
// 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
}
Lorenz Meier
committed
// Need to configure the port
// NOTE: THE PORT NEEDS TO BE OPEN!
if (!m_is_cdc) {
qDebug() << "Configuring 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));
}
Lorenz Meier
committed
emit communicationUpdate(getName(),"Opened port!");
Lorenz Meier
committed
qDebug() << "CONNECTING LINK: " << __FILE__ << __LINE__ << "type:" << type << "with settings" << m_port->portName()
<< getBaudRate() << getDataBits() << getParityType() << getStopBits();
writeSettings();
void SerialLink::linkError(QSerialPort::SerialPortError error)
// You can use the following qDebug output as needed during development. Make sure to comment it back out
// when you are done. The reason for this is that this signal is very noisy. For example if you try to
// connect to a PixHawk before it is ready to accept the connection it will output a continuous stream
// of errors until the Pixhawk responds.
//qDebug() << "SerialLink::linkError" << 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
{
Lorenz Meier
committed
if (m_port && !m_is_cdc) {
548
549
550
551
552
553
554
555
556
557
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
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;
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
{
Lorenz Meier
committed
if (m_port && !m_is_cdc) {
baudRate = m_port->baudRate();
} else {
baudRate = m_baud;
}
return baudRate;
int SerialLink::getFlowType() const
{
Lorenz Meier
committed
Lorenz Meier
committed
if (m_port && !m_is_cdc) {
flowControl = m_port->flowControl();
} else {
flowControl = m_flowControl;
}
return flowControl;
int SerialLink::getParityType() const
{
Lorenz Meier
committed
Lorenz Meier
committed
if (m_port && !m_is_cdc) {
parity = m_port->parity();
} else {
parity = m_parity;
}
return parity;
int SerialLink::getDataBitsType() const
{
Lorenz Meier
committed
Lorenz Meier
committed
if (m_port && !m_is_cdc) {
dataBits = m_port->dataBits();
} else {
dataBits = m_dataBits;
}
return dataBits;
int SerialLink::getStopBitsType() const
{
Lorenz Meier
committed
Lorenz Meier
committed
if (m_port && !m_is_cdc) {
stopBits = m_port->stopBits();
} else {
stopBits = m_stopBits;
}
return stopBits;
int SerialLink::getDataBits() const
{
Lorenz Meier
committed
Lorenz Meier
committed
if (m_port && !m_is_cdc) {
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
{
Lorenz Meier
committed
Lorenz Meier
committed
if (m_port && !m_is_cdc) {
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;
Lorenz Meier
committed
bool accepted = true;
if ((portName != m_portName)
&& (portName.trimmed().length() > 0)) {
m_portName = portName.trimmed();
Lorenz Meier
committed
checkIfCDC();
emit nameChanged(m_portName); // [TODO] maybe we can eliminate this
}
bool SerialLink::setBaudRateType(int rateIndex)
{
Lorenz Meier
committed
Lorenz Meier
committed
// 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;
Lorenz Meier
committed
if ((rateIndex >= minBaud && rateIndex <= maxBaud))
Lorenz Meier
committed
if (!m_is_cdc && m_port)
{
result = m_port->setBaudRate(static_cast<QSerialPort::BaudRate>(rateIndex));
emit updateLink(this);
} else {
m_baud = (int)rateIndex;
result = true;
}
} else {
result = false;
Lorenz Meier
committed
return result;
bool SerialLink::setBaudRateString(const QString& rate)
{
bool ok;
int intrate = rate.toInt(&ok);
if (!ok) return false;
return setBaudRate(intrate);
}
Lorenz Meier
committed
bool accepted = false;
if (rate != m_baud) {
m_baud = rate;
accepted = true;
Lorenz Meier
committed
if (m_port && !m_is_cdc) {
Lorenz Meier
committed
}
bool SerialLink::setFlowType(int flow)
{
Lorenz Meier
committed
bool accepted = false;
if (flow != m_flowControl) {
m_flowControl = static_cast<QSerialPort::FlowControl>(flow);
accepted = true;
Lorenz Meier
committed
if (m_port && !m_is_cdc)
accepted = m_port->setFlowControl(static_cast<QSerialPort::FlowControl>(flow));
bool SerialLink::setParityType(int parity)
{
Lorenz Meier
committed
bool accepted = false;
if (parity != m_parity) {
m_parity = static_cast<QSerialPort::Parity>(parity);
accepted = true;
Lorenz Meier
committed
if (m_port && !m_is_cdc) {
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)
{
Lorenz Meier
committed
qDebug("SET DATA BITS");
bool accepted = false;
if (dataBits != m_dataBits) {
m_dataBits = static_cast<QSerialPort::DataBits>(dataBits);
accepted = true;
Lorenz Meier
committed
if (m_port && !m_is_cdc)
accepted = m_port->setDataBits(static_cast<QSerialPort::DataBits>(dataBits));
bool SerialLink::setStopBits(int stopBits)
{
Lorenz Meier
committed
// Note 3 is OneAndAHalf stopbits.
bool accepted = false;
if (stopBits != m_stopBits) {
m_stopBits = static_cast<QSerialPort::StopBits>(stopBits);
accepted = true;
Lorenz Meier
committed
if (m_port && !m_is_cdc)
accepted = m_port->setStopBits(static_cast<QSerialPort::StopBits>(stopBits));
bool SerialLink::setDataBitsType(int dataBits)
{
Lorenz Meier
committed
bool accepted = false;
if (dataBits != m_dataBits) {
m_dataBits = static_cast<QSerialPort::DataBits>(dataBits);
accepted = true;
Lorenz Meier
committed
if (m_port && !m_is_cdc)
accepted = m_port->setDataBits(static_cast<QSerialPort::DataBits>(dataBits));
}
return accepted;
}
bool SerialLink::setStopBitsType(int stopBits)
{
Lorenz Meier
committed
bool accepted = false;
if (stopBits != m_stopBits) {
m_stopBits = static_cast<QSerialPort::StopBits>(stopBits);
accepted = true;
Lorenz Meier
committed
if (m_port && !m_is_cdc)
accepted = m_port->setStopBits(static_cast<QSerialPort::StopBits>(stopBits));
}
return accepted;
}