/*===================================================================== ======================================================================*/ /** * @file * @brief Cross-platform support for serial ports * * @author Lorenz Meier * */ #include #include #include #include #include #include #include "SerialLink.h" #include "LinkManager.h" #include "QGC.h" #include SerialLink::SerialLink(QString portname, int baudRate, bool hardwareFlowControl, bool parity, int dataBits, int stopBits) : m_bytesRead(0), m_port(NULL), m_ports(new QVector()), m_stopp(false), m_reqReset(false) { // Setup settings m_portName = portname.trimmed(); if (m_portName == "" && getCurrentPorts()->size() > 0) { m_portName = m_ports->first().trimmed(); } // Set unique ID and add link to the list of links m_id = getNextLinkId(); m_baud = baudRate; if (hardwareFlowControl) { m_flowControl = QSerialPort::HardwareControl; } else { m_flowControl = QSerialPort::NoFlowControl; } if (parity) { m_parity = QSerialPort::EvenParity; } else { m_parity = QSerialPort::NoParity; } m_dataBits = dataBits; m_stopBits = stopBits; // Set the port name if (m_portName == "") { m_name = tr("Serial Link ") + QString::number(getId()); } else { m_name = portname.trimmed(); } loadSettings(); } void SerialLink::requestReset() { QMutexLocker locker(&this->m_stoppMutex); m_reqReset = true; } SerialLink::~SerialLink() { disconnect(); if(m_port) delete m_port; m_port = NULL; if (m_ports) delete m_ports; m_ports = NULL; } QVector* SerialLink::getCurrentPorts() { Q_ASSERT_X(m_ports != NULL, "getCurrentPorts", "m_ports is NULL"); m_ports->clear(); // Example use QSerialPortInfo foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) { // qDebug() << "PortName : " << info.portName() // << "Description : " << info.description(); // qDebug() << "Manufacturer: " << info.manufacturer(); m_ports->append(info.portName()); } return m_ports; } void SerialLink::loadSettings() { // Load defaults from settings QSettings settings(QGC::COMPANYNAME, 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::COMPANYNAME, 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(); } /** * @brief Runs the thread * **/ void SerialLink::run() { // Initialize the connection if (!hardwareConnect()) { //Need to error out here. emit communicationError(getName(),"Error connecting: " + m_port->errorString()); return; } // Qt way to make clear what a while(1) loop does qint64 msecs = QDateTime::currentMSecsSinceEpoch(); qint64 initialmsecs = QDateTime::currentMSecsSinceEpoch(); quint64 bytes = 0; bool triedreset = false; bool triedDTR = false; qint64 timeout = 5000; forever { { QMutexLocker locker(&this->m_stoppMutex); if(this->m_stopp) { this->m_stopp = false; break; } if (m_reqReset) { this->m_reqReset = false; communicationUpdate(getName(),"Reset requested via DTR signal"); m_port->setDataTerminalReady(true); this->msleep(250); m_port->setDataTerminalReady(false); } } // Check if new bytes have arrived, if yes, emit the notification signal checkForBytes(); if (bytes != m_bytesRead) { bytes = m_bytesRead; msecs = QDateTime::currentMSecsSinceEpoch(); } else { if (QDateTime::currentMSecsSinceEpoch() - msecs > timeout) { //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) { triedDTR = true; 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); this->msleep(250); m_port->setDataTerminalReady(false); } else if (!triedreset) { qDebug() << "No data!!! Attempting reset via reboot command."; 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 { communicationUpdate(getName(),"No data to receive on COM port...."); qDebug() << "No data!!!"; } } } /* Serial data isn't arriving that fast normally, this saves the thread * from consuming too much processing time */ MG::SLEEP::msleep(SerialLink::poll_interval); } // end forever loop if (m_port) { m_port->close(); delete m_port; m_port = NULL; } } void SerialLink::checkForBytes() { /* Check if bytes are available */ if(m_port && m_port->isOpen() && m_port->isWritable()) { m_dataMutex.lock(); qint64 available = m_port->bytesAvailable(); m_dataMutex.unlock(); if(available > 0) { readBytes(); m_bytesRead += available; } else if (available < 0) { /* Error, close port */ m_port->close(); emit disconnected(); emit connected(false); emit communicationError(this->getName(), tr("Could not read data - link %1 is disconnected!").arg(this->getName())); } } // else // { // emit disconnected(); // emit connected(false); // } } void SerialLink::writeBytes(const char* data, qint64 size) { if(m_port && m_port->isOpen()) { qDebug() << "writeBytes" << m_portName << "attempting to tx " << size << "bytes."; int b = m_port->write(data, size); if (b > 0) { Q_ASSERT_X(b = size, "writeBytes", "failed to write all bytes"); qDebug() << "writeBytes " << m_portName << "tx'd" << b << "bytes:"; // Increase write counter m_bitsSentTotal += size * 8; QByteArray* byteArray = new QByteArray(data,size); qDebug() << byteArray->toHex(); delete byteArray; } 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() { m_dataMutex.lock(); if(m_port && m_port->isOpen()) { const qint64 maxLength = 2048; char data[maxLength]; qint64 numBytes = m_port->bytesAvailable(); //qDebug() << "numBytes: " << numBytes; if(numBytes > 0) { /* Read as much data in buffer as possible without overflow */ if(maxLength < numBytes) numBytes = maxLength; m_port->read(data, numBytes); QByteArray b(data, numBytes); emit bytesReceived(this, b); //qDebug() << "SerialLink::readBytes()" << std::hex << data; // int i; // for (i=0; ibytesAvailable(); } else { 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" << m_port->portName(); Q_ASSERT_X(m_port != NULL, "disconnect", "m_port is NULL"); if (isRunning()) { { 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; } else { m_port->close(); return true; } } /** * @brief Connect the connection. * * @return True if connection has been established, false if connection couldn't be established. **/ bool SerialLink::connect() { if (isRunning()) disconnect(); { QMutexLocker locker(&this->m_stoppMutex); m_stopp = false; } start(LowPriority); return true; } /** * @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() { if(m_port) { 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. } QObject::connect(m_port,SIGNAL(aboutToClose()),this,SIGNAL(disconnected())); // port->setCommTimeouts(QSerialPort::CtScheme_NonBlockingRead); m_connectionStartTime = MG::TIME::getGroundTimeNow(); 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 } emit communicationUpdate(getName(),"Opened port!"); // Need to configure the port m_port->setBaudRate(m_baud); m_port->setDataBits(static_cast(m_dataBits)); m_port->setFlowControl(static_cast(m_flowControl)); m_port->setStopBits(static_cast(m_stopBits)); m_port->setParity(static_cast(m_parity)); emit connected(); emit connected(true); qDebug() << "CONNECTING LINK: " << __FILE__ << __LINE__ << "with settings" << m_port->portName() << getBaudRate() << getDataBits() << getParityType() << getStopBits(); writeSettings(); return true; // successful connection } /** * @brief Check if connection is active. * * @return True if link is connected, false otherwise. **/ bool SerialLink::isConnected() { if (m_port) { bool isConnected = m_port->isOpen(); qDebug() << "SerialLink #" << __LINE__ << ":"<< m_port->portName() << " isConnected =" << QString::number(isConnected); return isConnected; } else { qDebug() << "SerialLink #" << __LINE__ << ":" << m_portName << " isConnected = NULL"; return false; } } int SerialLink::getId() { return m_id; } QString SerialLink::getName() { return m_name; } /** * This function maps baud rate constants to numerical equivalents. * It relies on the mapping given in qportsettings.h from the QSerialPort library. */ qint64 SerialLink::getNominalDataRate() { 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; } return dataRate; } qint64 SerialLink::getTotalUpstream() { m_statisticsMutex.lock(); return m_bitsSentTotal / ((MG::TIME::getGroundTimeNow() - m_connectionStartTime) / 1000); m_statisticsMutex.unlock(); } qint64 SerialLink::getCurrentUpstream() { return 0; // TODO } qint64 SerialLink::getMaxUpstream() { return 0; // TODO } qint64 SerialLink::getBitsSent() { return m_bitsSentTotal; } qint64 SerialLink::getBitsReceived() { return m_bitsReceivedTotal; } qint64 SerialLink::getTotalDownstream() { m_statisticsMutex.lock(); return m_bitsReceivedTotal / ((MG::TIME::getGroundTimeNow() - m_connectionStartTime) / 1000); m_statisticsMutex.unlock(); } qint64 SerialLink::getCurrentDownstream() { return 0; // TODO } qint64 SerialLink::getMaxDownstream() { return 0; // TODO } bool SerialLink::isFullDuplex() { /* Serial connections are always half duplex */ return false; } int SerialLink::getLinkQuality() { /* This feature is not supported with this interface */ return -1; } QString SerialLink::getPortName() { return m_portName; } // We should replace the accessors below with one to get the QSerialPort int SerialLink::getBaudRate() { return getNominalDataRate(); } int SerialLink::getBaudRateType() { int baudRate; if (m_port) { baudRate = m_port->baudRate(); } else { baudRate = m_baud; } return baudRate; } int SerialLink::getFlowType() { int flowControl; if (m_port) { flowControl = m_port->flowControl(); } else { flowControl = m_flowControl; } return flowControl; } int SerialLink::getParityType() { int parity; if (m_port) { parity = m_port->parity(); } else { parity = m_parity; } return parity; } int SerialLink::getDataBitsType() { int dataBits; if (m_port) { dataBits = m_port->dataBits(); } else { dataBits = m_dataBits; } return dataBits; } int SerialLink::getStopBitsType() { int stopBits; if (m_port) { stopBits = m_port->stopBits(); } else { stopBits = m_stopBits; } return stopBits; } int SerialLink::getDataBits() { int ret; int dataBits; if (m_port) { dataBits = m_port->dataBits(); } else { dataBits = m_dataBits; } switch (dataBits) { case QSerialPort::Data5: ret = 5; break; case QSerialPort::Data6: ret = 6; break; case QSerialPort::Data7: ret = 7; break; case QSerialPort::Data8: ret = 8; break; default: ret = -1; break; } return ret; } int SerialLink::getStopBits() { int stopBits; if (m_port) { stopBits = m_port->stopBits(); } else { stopBits = m_stopBits; } int ret = -1; switch (stopBits) { case QSerialPort::OneStop: ret = 1; break; case QSerialPort::TwoStop: ret = 2; break; default: ret = -1; break; } return ret; } bool SerialLink::setPortName(QString portName) { qDebug() << "current portName " << m_portName; qDebug() << "setPortName to " << portName; bool accepted = false; if ((portName != m_portName) && (portName.trimmed().length() > 0)) { m_portName = portName.trimmed(); m_name = tr("serial port ") + portName.trimmed(); // [TODO] Do we need this? if(m_port) m_port->setPortName(portName); emit nameChanged(m_name); // [TODO] maybe we can eliminate this return accepted; } return false; } 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)) { return m_port->setBaudRate(static_cast(rateIndex)); } return false; } bool SerialLink::setBaudRateString(const QString& rate) { bool ok; int intrate = rate.toInt(&ok); if (!ok) return false; return setBaudRate(intrate); } bool SerialLink::setBaudRate(int rate) { bool accepted = false; if (rate != m_baud) { m_baud = rate; accepted = true; if (m_port) accepted = m_port->setBaudRate(rate); } return accepted; } bool SerialLink::setFlowType(int flow) { bool accepted = false; if (flow != m_flowControl) { m_flowControl = static_cast(flow); accepted = true; if (m_port) accepted = m_port->setFlowControl(static_cast(flow)); } return accepted; } bool SerialLink::setParityType(int parity) { bool accepted = false; if (parity != m_parity) { m_parity = static_cast(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; } } } return accepted; } bool SerialLink::setDataBits(int dataBits) { bool accepted = false; if (dataBits != m_dataBits) { m_dataBits = static_cast(dataBits); accepted = true; if (m_port) accepted = m_port->setDataBits(static_cast(dataBits)); } return accepted; } bool SerialLink::setStopBits(int stopBits) { // Note 3 is OneAndAHalf stopbits. bool accepted = false; if (stopBits != m_stopBits) { m_stopBits = static_cast(stopBits); accepted = true; if (m_port) accepted = m_port->setStopBits(static_cast(stopBits)); } return accepted; } bool SerialLink::setDataBitsType(int dataBits) { bool accepted = false; if (dataBits != m_dataBits) { m_dataBits = static_cast(dataBits); accepted = true; if (m_port) accepted = m_port->setDataBits(static_cast(dataBits)); } return accepted; } bool SerialLink::setStopBitsType(int stopBits) { bool accepted = false; if (stopBits != m_stopBits) { m_stopBits = static_cast(stopBits); accepted = true; if (m_port) accepted = m_port->setStopBits(static_cast(stopBits)); } return accepted; }