#include "thread.hpp" #include "compat/sleep.hpp" #include #include #include #include #include void hatire_thread::sendcmd_impl(const QByteArray &cmd) { #ifndef HATIRE_DEBUG_LOGFILE QByteArray Msg; if (cmd.length() > 0) { if (com_port.isOpen()) { QString logMess; logMess.append("SEND '"); logMess.append(cmd); logMess.append("'"); Log(logMess); com_port.write(cmd); if (!com_port.waitForBytesWritten(1000)) { emit serial_debug_info_str(tr("Timeout during writing command")); } else { Msg.append("\r\n"); Msg.append("SEND '"); Msg.append(cmd); Msg.append("'\r\n"); } #if 0 // WaitForReadyRead isn't working well and there are some reports of it being a win32 issue. We can live without it anyway if ( !com_port.waitForReadyRead(1000)) { emit serial_debug_info("TimeOut in response to CMD") ; } else { emit serial_debug_info(Msg); } #else emit serial_debug_info(Msg); #endif } else { emit serial_debug_info_str(tr("COM port not open")); } } #endif } struct Diag final : public QFile { Diag() { setFileName(QCoreApplication::applicationDirPath() + "/HATDiagnostics.txt"); } }; void hatire_thread::Log(const QString& message) { // Drop out immediately if logging is off. Yes, there is still some overhead because of passing strings around for no reason. // that's unfortunate and I'll monitor the impact and see if it needs a more involved fix. if (!s.EnableLogging) return; Diag flDiagnostics; if (flDiagnostics.open(QIODevice::ReadWrite | QIODevice::Append)) { QTextStream out(&flDiagnostics); QString milliSeconds; milliSeconds = QString("%1").arg(QTime::currentTime().msec(), 3, 10, QChar('0')); // We have a file out << QTime::currentTime().toString() << "." << milliSeconds << ": " << message << "\r\n"; flDiagnostics.close(); } } void hatire_thread::start() { QThread::start(); } hatire_thread::~hatire_thread() { quit(); wait(); } hatire_thread::hatire_thread() { data_read.reserve(65536); com_port.setReadBufferSize(2048); connect(this, &QThread::finished, this, &hatire_thread::teardown_serial, Qt::DirectConnection); connect(this, &hatire_thread::init_serial_port, this, &hatire_thread::init_serial_port_impl, Qt::QueuedConnection); connect(this, &hatire_thread::serial_info, this, &hatire_thread::serial_info_impl, Qt::QueuedConnection); connect(this, &hatire_thread::sendcmd, this, &hatire_thread::sendcmd_impl, Qt::QueuedConnection); connect(this, &hatire_thread::sendcmd_str, this, &hatire_thread::sendcmd_str_impl, Qt::QueuedConnection); //com_port.moveToThread(this); #ifdef HATIRE_DEBUG_LOGFILE read_timer.moveToThread(this); #endif connect(&com_port, &serial_t::readyRead, this, &hatire_thread::on_serial_read, Qt::DirectConnection); } void hatire_thread::teardown_serial() { if (isRunning() && com_port.isOpen()) { QByteArray msg; Log("Tracker shut down"); com_port.write(to_latin1(s.CmdStop)); if (!com_port.waitForBytesWritten(1000)) { emit serial_debug_info("TimeOut in writing CMD"); } else { msg.append("\r\n"); msg.append("SEND '"); msg.append(s.CmdStop); msg.append("'\r\n"); } emit serial_debug_info(msg); disconnect(&com_port, SIGNAL(readyRead()), nullptr, nullptr); com_port.close(); } } void hatire_thread::run() { #ifdef HATIRE_DEBUG_LOGFILE com_port.setFileName(HATIRE_DEBUG_LOGFILE); com_port.open(QIODevice::ReadOnly); read_timer.start(10); connect(&read_timer, &QTimer::timeout, this, &hatire_thread::on_serial_read, Qt::DirectConnection); #endif (void) exec(); #ifdef HATIRE_DEBUG_LOGFILE read_timer.stop(); #endif } void hatire_thread::serial_debug_info_str(const QString& str) { serial_debug_info(str.toUtf8()); } serial_result hatire_thread::init_serial_port_impl() { #ifndef HATIRE_DEBUG_LOGFILE Log(tr("Setting serial port name")); com_port.setPortName(s.QSerialPortName); Log(tr("Opening serial port")); if (com_port.open(QIODevice::ReadWrite)) { Log(tr("Port Open")); if ( com_port.setBaudRate((QSerialPort::BaudRate)s.pBaudRate) && com_port.setDataBits((QSerialPort::DataBits)s.pDataBits) && com_port.setParity((QSerialPort::Parity)s.pParity) && com_port.setStopBits((QSerialPort::StopBits)s.pStopBits) && com_port.setFlowControl((QSerialPort::FlowControl)s.pFlowControl) && com_port.clear(QSerialPort::AllDirections) && com_port.setDataErrorPolicy(QSerialPort::IgnorePolicy) ) { Log(tr("Port Parameters set")); qDebug() << QTime::currentTime() << " HAT OPEN on" << com_port.portName() << com_port.baudRate() << com_port.dataBits() << com_port.parity() << com_port.stopBits() << com_port.flowControl(); if (com_port.flowControl() == QSerialPort::HardwareControl) { // Raise DTR Log(tr("Raising DTR")); if (!com_port.setDataTerminalReady(true)) Log("Couldn't set DTR"); // Raise RTS/CTS Log(tr("Raising RTS")); if (!com_port.setRequestToSend(true)) Log("Couldn't set RTS"); } // Wait init arduino sequence for (int i = 1; i <= s.DelayInit; i+=50) { if (com_port.waitForReadyRead(50)) break; } Log(tr("Waiting on init")); qDebug() << QTime::currentTime() << " HAT send INIT "; sendcmd_str(s.CmdInit); // Wait init MPU sequence for (int i = 1; i <= s.DelayStart; i+=50) { if (com_port.waitForReadyRead(50)) break; } // Send START cmd to IMU qDebug() << QTime::currentTime() << " HAT send START "; sendcmd_str(s.CmdStart); // Wait start MPU sequence for (int i = 1; i <=s.DelaySeq; i+=50) { if (com_port.waitForReadyRead(50)) break; } Log(tr("Port setup, waiting for HAT frames to process")); qDebug() << QTime::currentTime() << " HAT wait MPU "; return serial_result(); } else { return serial_result(result_error, com_port.errorString()); } } else return serial_result(result_open_error, com_port.errorString()); #else return serial_result(); #endif } // Info SerialPort void hatire_thread::serial_info_impl() { #ifndef HATIRE_DEBUG_LOGFILE QByteArray msg; if (com_port.isOpen()) { msg.append("\r\n"); msg.append(com_port.portName()); msg.append("\r\n"); msg.append("BAUDRATE :"); msg.append(QString::number(com_port.baudRate())); msg.append("\r\n"); msg.append("DataBits :"); msg.append(QString::number(com_port.dataBits())); msg.append("\r\n"); msg.append("Parity :"); switch (com_port.parity()) { case 0: msg.append("No parity"); break; case 2: msg.append("Even parity"); break; case 3: msg.append("Odd parity"); break; case 4: msg.append("Space parity"); break; case 5: msg.append("Mark parity"); break; default: msg.append("Unknown parity"); break; } msg.append("\r\n"); msg.append("Stop Bits :"); switch (com_port.stopBits()) { case 1: msg.append("1 stop bit."); break; case 2: msg.append("2 stop bits."); break; case 3: msg.append("1.5 stop bits."); break; default: msg.append("Unknown number of stop bit."); break; } msg.append("\r\n"); msg.append("Flow Control :"); switch (com_port.flowControl()) { case 0: msg.append("No flow control"); break; case 1: msg.append("Hardware flow control (RTS/CTS)"); break; case 2: msg.append("Software flow control (XON/XOFF)"); break; default: msg.append("Unknown flow control"); break; } emit serial_debug_info(msg); } #endif } void hatire_thread::on_serial_read() { static char buf[256]; int sz; #if !defined HATIRE_DEBUG_LOGFILE bool error = false, empty = false; #endif { QMutexLocker lck(&data_mtx); #ifndef HATIRE_DEBUG_LOGFILE sz = com_port.read(buf, sizeof(buf)); error |= sz < 0; empty |= sz == 0; #else const int sz = com_port.read(buf, sizeof(buf)); if (sz <= 0 && read_timer.isActive()) { if (sz < 0) qDebug() << "hatire: debug file read error" << com_port.errorString(); qDebug() << "eof"; read_timer.stop(); return; } #endif } #if !defined HATIRE_DEBUG_LOGFILE if (error || com_port.error() != QSerialPort::NoError) { once_only(qDebug() << "hatire serial: error num" << com_port.error() << "num2" << error); com_port.clearError(); // XXX must test it } else if (empty) once_only(qDebug() << "hatire serial: empty"); else goto ok; #endif goto fail; ok: data_read.append(buf, sz); using namespace time_units; stat.input(prog1(timer.elapsed().count(), timer.start())); if (throttle_timer.elapsed_seconds() >= 1) { throttle_timer.start(); qDebug() << "hatire stat:" << "avg" << stat.avg() << "stddev" << stat.stddev(); } if (!s.serial_bug_workaround) return; fail: // qt can fire QSerialPort::readyRead() needlessly, causing a busy loop. // see https://github.com/opentrack/opentrack/issues/327#issuecomment-207941003 once_only(qDebug() << "hatire: sleeping due to error, pinout:" << int(com_port.pinoutSignals())); { constexpr int hz = 90; constexpr int ms = 1000/hz; portable::sleep(ms); } } QByteArray& hatire_thread::send_data_read_nolock() { return data_read; }