/**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: http://www.qt-project.org/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "trkdevice.h" #include "trkutils.h" #include "trkutils_p.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN # include #else # include # include # include # include # include # include # include # include /* Required headers for select() according to POSIX.1-2001 */ # include /* Required headers for select() according to earlier standards: #include #include #include */ #endif #ifdef Q_OS_WIN // Format windows error from GetLastError() value: // TODO: Use the one provided by the utils lib. QString winErrorMessage(unsigned long error) { QString rc = QString::fromLatin1("#%1: ").arg(error); ushort *lpMsgBuf; const int len = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, 0, (LPTSTR)&lpMsgBuf, 0, NULL); if (len) { rc = QString::fromUtf16(lpMsgBuf, len); LocalFree(lpMsgBuf); } else { rc += QString::fromLatin1(""); } return rc; } #endif enum { verboseTrk = 0 }; static inline QString msgAccessingClosedDevice(const QString &msg) { return QString::fromLatin1("Error: Attempt to access device '%1', which is closed.").arg(msg); } namespace trk { /////////////////////////////////////////////////////////////////////// // // TrkMessage // /////////////////////////////////////////////////////////////////////// /* A message to be send to TRK, triggering a callback on receipt * of the answer. */ struct TrkMessage { explicit TrkMessage(byte code = 0u, byte token = 0u, TrkCallback callback = TrkCallback()); byte code; byte token; QByteArray data; QVariant cookie; TrkCallback callback; }; TrkMessage::TrkMessage(byte c, byte t, TrkCallback cb) : code(c), token(t), callback(cb) { } QDebug operator<<(QDebug d, const TrkMessage &msg) { return d << "Message: Code: " << msg.code << " Token: " << msg.token << " " << msg.data.toHex(); } } // namespace trk Q_DECLARE_METATYPE(trk::TrkMessage) Q_DECLARE_METATYPE(trk::TrkResult) namespace trk { /////////////////////////////////////////////////////////////////////// // // TrkWriteQueue: Mixin class that manages a write queue of Trk messages. // pendingMessage()/notifyWriteResult() should be called from a worked/timer // that writes the messages. The class does not take precautions for multithreading. // A no-op message is simply taken off the queue. The calling class // can use the helper invokeNoopMessage() to trigger its callback. // /////////////////////////////////////////////////////////////////////// class TrkWriteQueue { Q_DISABLE_COPY(TrkWriteQueue) public: explicit TrkWriteQueue(); void clear(); // Enqueue messages. void queueTrkMessage(byte code, TrkCallback callback, const QByteArray &data, const QVariant &cookie); void queueTrkInitialPing(); // Call this from the device read notification with the results. void slotHandleResult(const TrkResult &result, QMutex *mutex = 0); // pendingMessage() can be called periodically in a timer to retrieve // the pending messages to be sent. enum PendingMessageResult { NoMessage, // No message in queue. PendingMessage, /* There is a queued message. The calling class * can write it out and use notifyWriteResult() * to notify about the result. */ NoopMessageDequeued // A no-op message has been dequeued. see invokeNoopMessage(). }; PendingMessageResult pendingMessage(TrkMessage *message); // Notify the queue about the success of the write operation // after taking the pendingMessage off. enum WriteResult { WriteOk, WriteFailedDiscard, // Discard failed message WriteFailedKeep, // Keep failed message }; void notifyWriteResult(WriteResult ok); // Helper function that invokes the callback of a no-op message static void invokeNoopMessage(trk::TrkMessage); private: typedef QMap TokenMessageMap; byte nextTrkWriteToken(); byte m_trkWriteToken; QQueue m_trkWriteQueue; TokenMessageMap m_writtenTrkMessages; bool m_trkWriteBusy; }; TrkWriteQueue::TrkWriteQueue() : m_trkWriteToken(0), m_trkWriteBusy(false) { } void TrkWriteQueue::clear() { m_trkWriteToken = 0; m_trkWriteBusy = false; m_trkWriteQueue.clear(); const int discarded = m_writtenTrkMessages.size(); m_writtenTrkMessages.clear(); if (verboseTrk) qDebug() << "TrkWriteQueue::clear: discarded " << discarded; } byte TrkWriteQueue::nextTrkWriteToken() { ++m_trkWriteToken; if (m_trkWriteToken == 0) ++m_trkWriteToken; if (verboseTrk) qDebug() << "nextTrkWriteToken:" << m_trkWriteToken; return m_trkWriteToken; } void TrkWriteQueue::queueTrkMessage(byte code, TrkCallback callback, const QByteArray &data, const QVariant &cookie) { const byte token = code == TRK_WRITE_QUEUE_NOOP_CODE ? byte(0) : nextTrkWriteToken(); TrkMessage msg(code, token, callback); msg.data = data; msg.cookie = cookie; m_trkWriteQueue.append(msg); } TrkWriteQueue::PendingMessageResult TrkWriteQueue::pendingMessage(TrkMessage *message) { // Invoked from timer, try to flush out message queue if (m_trkWriteBusy || m_trkWriteQueue.isEmpty()) return NoMessage; // Handle the noop message, just invoke CB in slot (ower thread) if (m_trkWriteQueue.front().code == TRK_WRITE_QUEUE_NOOP_CODE) { *message = m_trkWriteQueue.dequeue(); return NoopMessageDequeued; } // Insert into map fir answers (as reading threads might get an // answer before notifyWriteResult(true)) is called. *message = m_trkWriteQueue.front(); m_writtenTrkMessages.insert(message->token, *message); m_trkWriteBusy = true; return PendingMessage; } void TrkWriteQueue::invokeNoopMessage(trk::TrkMessage noopMessage) { TrkResult result; result.code = noopMessage.code; result.token = noopMessage.token; result.data = noopMessage.data; result.cookie = noopMessage.cookie; noopMessage.callback(result); } void TrkWriteQueue::notifyWriteResult(WriteResult wr) { // On success, dequeue message and await result const byte token = m_trkWriteQueue.front().token; switch (wr) { case WriteOk: m_trkWriteQueue.dequeue(); break; case WriteFailedKeep: case WriteFailedDiscard: m_writtenTrkMessages.remove(token); m_trkWriteBusy = false; if (wr == WriteFailedDiscard) m_trkWriteQueue.dequeue(); break; } } void TrkWriteQueue::slotHandleResult(const TrkResult &result, QMutex *mutex) { // Find which request the message belongs to and invoke callback // if ACK or on NAK if desired. if (mutex) mutex->lock(); m_trkWriteBusy = false; const TokenMessageMap::iterator it = m_writtenTrkMessages.find(result.token); if (it == m_writtenTrkMessages.end()) { if (mutex) mutex->unlock(); return; } TrkCallback callback = it.value().callback; const QVariant cookie = it.value().cookie; m_writtenTrkMessages.erase(it); if (mutex) mutex->unlock(); // Invoke callback if (callback) { TrkResult result1 = result; result1.cookie = cookie; callback(result1); } } void TrkWriteQueue::queueTrkInitialPing() { // Ping, reset sequence count m_trkWriteToken = 0; m_trkWriteQueue.append(TrkMessage(TrkPing, 0)); } /////////////////////////////////////////////////////////////////////// // // DeviceContext to be shared between threads // /////////////////////////////////////////////////////////////////////// struct DeviceContext { DeviceContext(); #ifdef Q_OS_WIN HANDLE device; OVERLAPPED readOverlapped; OVERLAPPED writeOverlapped; #else QFile file; #endif bool serialFrame; QMutex mutex; }; DeviceContext::DeviceContext() : #ifdef Q_OS_WIN device(INVALID_HANDLE_VALUE), #endif serialFrame(true) { } /////////////////////////////////////////////////////////////////////// // // TrkWriterThread: A thread operating a TrkWriteQueue. // with exception of the handling of the TRK_WRITE_QUEUE_NOOP_CODE // synchronization message. The invocation of the callback is then // done by the thread owning the TrkWriteQueue, while pendingMessage() is called // from another thread. This happens via a Qt::BlockingQueuedConnection. /////////////////////////////////////////////////////////////////////// class WriterThread : public QThread { Q_OBJECT Q_DISABLE_COPY(WriterThread) public: explicit WriterThread(const QSharedPointer &context); // Enqueue messages. void queueTrkMessage(byte code, TrkCallback callback, const QByteArray &data, const QVariant &cookie); void queueTrkInitialPing(); void clearWriteQueue(); // Call this from the device read notification with the results. void slotHandleResult(const TrkResult &result); virtual void run(); signals: void error(const QString &); void internalNoopMessageDequeued(const trk::TrkMessage&); public slots: bool trkWriteRawMessage(const TrkMessage &msg); void terminate(); void tryWrite(); private slots: void invokeNoopMessage(const trk::TrkMessage &); private: bool write(const QByteArray &data, QString *errorMessage); inline int writePendingMessage(); const QSharedPointer m_context; QMutex m_dataMutex; QMutex m_waitMutex; QWaitCondition m_waitCondition; TrkWriteQueue m_queue; bool m_terminate; }; WriterThread::WriterThread(const QSharedPointer &context) : m_context(context), m_terminate(false) { static const int trkMessageMetaId = qRegisterMetaType(); Q_UNUSED(trkMessageMetaId) connect(this, SIGNAL(internalNoopMessageDequeued(trk::TrkMessage)), this, SLOT(invokeNoopMessage(trk::TrkMessage)), Qt::BlockingQueuedConnection); } void WriterThread::run() { while (writePendingMessage() == 0) ; } int WriterThread::writePendingMessage() { enum { MaxAttempts = 100, RetryIntervalMS = 200 }; // Wait. Use a timeout in case something is already queued before we // start up or some weird hanging exit condition m_waitMutex.lock(); m_waitCondition.wait(&m_waitMutex, 100); m_waitMutex.unlock(); if (m_terminate) return 1; // Send off message m_dataMutex.lock(); TrkMessage message; const TrkWriteQueue::PendingMessageResult pr = m_queue.pendingMessage(&message); m_dataMutex.unlock(); switch (pr) { case TrkWriteQueue::NoMessage: break; case TrkWriteQueue::PendingMessage: { //qDebug() << "Write pending message " << message; // Untested: try to re-send a few times bool success = false; for (int r = 0; !success && (r < MaxAttempts); r++) { success = trkWriteRawMessage(message); if (!success) { emit error(QString::fromLatin1("Write failure, attempt %1 of %2.").arg(r).arg(int(MaxAttempts))); if (m_terminate) return 1; QThread::msleep(RetryIntervalMS); } } // Notify queue. If still failed, give up. m_dataMutex.lock(); m_queue.notifyWriteResult(success ? TrkWriteQueue::WriteOk : TrkWriteQueue::WriteFailedDiscard); m_dataMutex.unlock(); } break; case TrkWriteQueue::NoopMessageDequeued: // Sync with thread that owns us via a blocking signal if (verboseTrk) qDebug() << "Noop message dequeued" << message; emit internalNoopMessageDequeued(message); break; } // switch return 0; } void WriterThread::invokeNoopMessage(const trk::TrkMessage &msg) { TrkWriteQueue::invokeNoopMessage(msg); } void WriterThread::terminate() { m_terminate = true; m_waitCondition.wakeAll(); wait(); m_terminate = false; m_queue.clear(); } #ifdef Q_OS_WIN static inline QString msgTerminated(int size) { return QString::fromLatin1("Terminated with %1 bytes pending.").arg(size); } // Interruptible synchronous write function. static inline bool overlappedSyncWrite(HANDLE file, const bool &terminateFlag, const char *data, DWORD size, DWORD *charsWritten, OVERLAPPED *overlapped, QString *errorMessage) { if (WriteFile(file, data, size, charsWritten, overlapped)) return true; const DWORD writeError = GetLastError(); if (writeError != ERROR_IO_PENDING) { *errorMessage = QString::fromLatin1("WriteFile failed: %1").arg(winErrorMessage(writeError)); return false; } // Wait for written or thread terminated const DWORD timeoutMS = 200; const unsigned maxAttempts = 20; DWORD wr = WaitForSingleObject(overlapped->hEvent, timeoutMS); for (unsigned n = 0; wr == WAIT_TIMEOUT && n < maxAttempts && !terminateFlag; wr = WaitForSingleObject(overlapped->hEvent, timeoutMS), n++); if (terminateFlag) { *errorMessage = msgTerminated(size); return false; } switch (wr) { case WAIT_OBJECT_0: break; case WAIT_TIMEOUT: *errorMessage = QString::fromLatin1("Write timed out."); return false; default: *errorMessage = QString::fromLatin1("Error while waiting for WriteFile results: %1").arg(winErrorMessage(GetLastError())); return false; } if (!GetOverlappedResult(file, overlapped, charsWritten, TRUE)) { *errorMessage = QString::fromLatin1("Error writing %1 bytes: %2").arg(size).arg(winErrorMessage(GetLastError())); return false; } return true; } #endif bool WriterThread::write(const QByteArray &data, QString *errorMessage) { if (verboseTrk) qDebug() << "Write raw data: " << stringFromArray(data).toLatin1(); QMutexLocker locker(&m_context->mutex); #ifdef Q_OS_WIN DWORD charsWritten; if (!overlappedSyncWrite(m_context->device, m_terminate, data.data(), data.size(), &charsWritten, &m_context->writeOverlapped, errorMessage)) { return false; } FlushFileBuffers(m_context->device); return true; #else if (m_context->file.write(data) == -1 || !m_context->file.flush()) { *errorMessage = QString::fromLatin1("Cannot write: %1").arg(m_context->file.errorString()); return false; } return true; #endif } bool WriterThread::trkWriteRawMessage(const TrkMessage &msg) { const QByteArray ba = frameMessage(msg.code, msg.token, msg.data, m_context->serialFrame); QString errorMessage; const bool rc = write(ba, &errorMessage); if (!rc) { qWarning("%s\n", qPrintable(errorMessage)); emit error(errorMessage); } return rc; } void WriterThread::tryWrite() { m_waitCondition.wakeAll(); } void WriterThread::queueTrkMessage(byte code, TrkCallback callback, const QByteArray &data, const QVariant &cookie) { m_dataMutex.lock(); m_queue.queueTrkMessage(code, callback, data, cookie); m_dataMutex.unlock(); tryWrite(); } void WriterThread::clearWriteQueue() { m_dataMutex.lock(); m_queue.clear(); m_dataMutex.unlock(); } void WriterThread::queueTrkInitialPing() { m_dataMutex.lock(); m_queue.queueTrkInitialPing(); m_dataMutex.unlock(); tryWrite(); } // Call this from the device read notification with the results. void WriterThread::slotHandleResult(const TrkResult &result) { m_queue.slotHandleResult(result, &m_dataMutex); tryWrite(); // Have messages been enqueued in-between? } /////////////////////////////////////////////////////////////////////// // // ReaderThreadBase: Base class for a thread that reads data from // the device, decodes the messages and emit signals for the messages. // A Qt::BlockingQueuedConnection should be used for the message signal // to ensure messages are processed in the correct sequence. // /////////////////////////////////////////////////////////////////////// class ReaderThreadBase : public QThread { Q_OBJECT Q_DISABLE_COPY(ReaderThreadBase) public: int bytesPending() const { return m_trkReadBuffer.size(); } signals: void messageReceived(const trk::TrkResult &result, const QByteArray &rawData); protected: explicit ReaderThreadBase(const QSharedPointer &context); void processData(const QByteArray &a); void processData(char c); const QSharedPointer m_context; private: void readMessages(); QByteArray m_trkReadBuffer; bool linkEstablishmentMode; }; ReaderThreadBase::ReaderThreadBase(const QSharedPointer &context) : m_context(context), linkEstablishmentMode(true) { static const int trkResultMetaId = qRegisterMetaType(); Q_UNUSED(trkResultMetaId) } void ReaderThreadBase::processData(const QByteArray &a) { m_trkReadBuffer += a; readMessages(); } void ReaderThreadBase::processData(char c) { m_trkReadBuffer += c; if (m_trkReadBuffer.size() > 1) readMessages(); } void ReaderThreadBase::readMessages() { TrkResult r; QByteArray rawData; while (extractResult(&m_trkReadBuffer, m_context->serialFrame, &r, linkEstablishmentMode, &rawData)) { emit messageReceived(r, rawData); } } #ifdef Q_OS_WIN /////////////////////////////////////////////////////////////////////// // // WinReaderThread: A thread reading from the device using Windows API. // Waits on an overlapped I/O handle and an event that tells the thread to // terminate. // /////////////////////////////////////////////////////////////////////// class WinReaderThread : public ReaderThreadBase { Q_OBJECT Q_DISABLE_COPY(WinReaderThread) public: explicit WinReaderThread(const QSharedPointer &context); ~WinReaderThread(); virtual void run(); signals: void error(const QString &); public slots: void terminate(); private: enum Handles { FileHandle, TerminateEventHandle, HandleCount }; inline int tryRead(); HANDLE m_handles[HandleCount]; }; WinReaderThread::WinReaderThread(const QSharedPointer &context) : ReaderThreadBase(context) { m_handles[FileHandle] = NULL; m_handles[TerminateEventHandle] = CreateEvent(NULL, FALSE, FALSE, NULL); } WinReaderThread::~WinReaderThread() { CloseHandle(m_handles[TerminateEventHandle]); } // Return 0 to continue or error code int WinReaderThread::tryRead() { enum { BufSize = 1024 }; char buffer[BufSize]; // Check if there are already bytes waiting. If not, wait for first byte COMSTAT comStat; if (!ClearCommError(m_context->device, NULL, &comStat)){ emit error(QString::fromLatin1("ClearCommError failed: %1").arg(winErrorMessage(GetLastError()))); return -7; } const DWORD bytesToRead = qMax(DWORD(1), qMin(comStat.cbInQue, DWORD(BufSize))); // Trigger read DWORD bytesRead = 0; if (ReadFile(m_context->device, &buffer, bytesToRead, &bytesRead, &m_context->readOverlapped)) { if (bytesRead == 1) { processData(buffer[0]); } else { processData(QByteArray(buffer, bytesRead)); } return 0; } const DWORD readError = GetLastError(); if (readError != ERROR_IO_PENDING) { emit error(QString::fromLatin1("Read error: %1").arg(winErrorMessage(readError))); return -1; } // Wait for either termination or data const DWORD wr = WaitForMultipleObjects(HandleCount, m_handles, false, INFINITE); if (wr == WAIT_FAILED) { emit error(QString::fromLatin1("Wait failed: %1").arg(winErrorMessage(GetLastError()))); return -2; } if (wr - WAIT_OBJECT_0 == TerminateEventHandle) { return 1; // Terminate } // Check data if (!GetOverlappedResult(m_context->device, &m_context->readOverlapped, &bytesRead, true)) { emit error(QString::fromLatin1("GetOverlappedResult failed: %1").arg(winErrorMessage(GetLastError()))); return -3; } if (bytesRead == 1) { processData(buffer[0]); } else { processData(QByteArray(buffer, bytesRead)); } return 0; } void WinReaderThread::run() { m_handles[FileHandle] = m_context->readOverlapped.hEvent; while ( tryRead() == 0) ; } void WinReaderThread::terminate() { SetEvent(m_handles[TerminateEventHandle]); wait(); } typedef WinReaderThread ReaderThread; #else /////////////////////////////////////////////////////////////////////// // // UnixReaderThread: A thread reading from the device. // Uses select() to wait and a special ioctl() to find out the number // of bytes queued. For clean termination, the self-pipe trick is used. // The class maintains a pipe, on whose read end the select waits besides // the device file handle. To terminate, a byte is written to the pipe. // /////////////////////////////////////////////////////////////////////// static inline QString msgUnixCallFailedErrno(const char *func, int errorNumber) { return QString::fromLatin1("Call to %1() failed: %2").arg(QLatin1String(func), QString::fromLocal8Bit(strerror(errorNumber))); } class UnixReaderThread : public ReaderThreadBase { Q_OBJECT Q_DISABLE_COPY(UnixReaderThread) public: explicit UnixReaderThread(const QSharedPointer &context); ~UnixReaderThread(); virtual void run(); signals: void error(const QString &); public slots: void terminate(); private: inline int tryRead(); int m_terminatePipeFileDescriptors[2]; }; UnixReaderThread::UnixReaderThread(const QSharedPointer &context) : ReaderThreadBase(context) { m_terminatePipeFileDescriptors[0] = m_terminatePipeFileDescriptors[1] = -1; // Set up pipes for termination. Should not fail if (pipe(m_terminatePipeFileDescriptors) < 0) qWarning("%s\n", qPrintable(msgUnixCallFailedErrno("pipe", errno))); } UnixReaderThread::~UnixReaderThread() { close(m_terminatePipeFileDescriptors[0]); close(m_terminatePipeFileDescriptors[1]); } int UnixReaderThread::tryRead() { fd_set readSet, tempReadSet, tempExceptionSet; struct timeval timeOut; const int fileDescriptor = m_context->file.handle(); FD_ZERO(&readSet); FD_SET(fileDescriptor, &readSet); FD_SET(m_terminatePipeFileDescriptors[0], &readSet); const int maxFileDescriptor = qMax(m_terminatePipeFileDescriptors[0], fileDescriptor); int result = 0; do { memcpy(&tempReadSet, &readSet, sizeof(fd_set)); memcpy(&tempExceptionSet, &readSet, sizeof(fd_set)); timeOut.tv_sec = 1; timeOut.tv_usec = 0; result = select(maxFileDescriptor + 1, &tempReadSet, NULL, &tempExceptionSet, &timeOut); } while ( result < 0 && errno == EINTR ); // Timeout? if (result == 0) return 0; // Something wrong? if (result < 0) { emit error(msgUnixCallFailedErrno("select", errno)); return -1; } // Did the exception set trigger on the device? if (FD_ISSET(fileDescriptor,&tempExceptionSet)) { emit error(QLatin1String("An Exception occurred on the device.")); return -2; } // Check termination pipe. if (FD_ISSET(m_terminatePipeFileDescriptors[0], &tempReadSet) || FD_ISSET(m_terminatePipeFileDescriptors[0], &tempExceptionSet)) return 1; // determine number of pending bytes and read int numBytes; if (ioctl(fileDescriptor, FIONREAD, &numBytes) < 0) { emit error(msgUnixCallFailedErrno("ioctl", errno)); return -1; } m_context->mutex.lock(); const QByteArray data = m_context->file.read(numBytes); m_context->mutex.unlock(); processData(data); return 0; } void UnixReaderThread::run() { // Read loop while (tryRead() == 0) ; } void UnixReaderThread::terminate() { // Trigger select() by writing to the pipe char c = 0; const int written = write(m_terminatePipeFileDescriptors[1], &c, 1); Q_UNUSED(written) wait(); } typedef UnixReaderThread ReaderThread; #endif /////////////////////////////////////////////////////////////////////// // // TrkDevicePrivate // /////////////////////////////////////////////////////////////////////// struct TrkDevicePrivate { TrkDevicePrivate(); QSharedPointer deviceContext; QScopedPointer writerThread; QScopedPointer readerThread; QByteArray trkReadBuffer; int verbose; QString errorString; QString port; }; /////////////////////////////////////////////////////////////////////// // // TrkDevice // /////////////////////////////////////////////////////////////////////// TrkDevicePrivate::TrkDevicePrivate() : deviceContext(new DeviceContext), verbose(0) { } /////////////////////////////////////////////////////////////////////// // // TrkDevice // /////////////////////////////////////////////////////////////////////// TrkDevice::TrkDevice(QObject *parent) : QObject(parent), d(new TrkDevicePrivate) {} TrkDevice::~TrkDevice() { close(); delete d; } bool TrkDevice::open(QString *errorMessage) { if (d->verbose || verboseTrk) qDebug() << "Opening" << port() << "is open: " << isOpen() << " serialFrame=" << serialFrame(); if (isOpen()) return true; if (d->port.isEmpty()) { *errorMessage = QLatin1String("Internal error: No port set on TrkDevice"); return false; } #ifdef Q_OS_WIN const QString fullPort = QLatin1String("\\\\.\\") + d->port; d->deviceContext->device = CreateFile(reinterpret_cast(fullPort.utf16()), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_NO_BUFFERING|FILE_FLAG_OVERLAPPED, NULL); if (INVALID_HANDLE_VALUE == d->deviceContext->device) { *errorMessage = QString::fromLatin1("Could not open device '%1': %2").arg(port(), winErrorMessage(GetLastError())); return false; } memset(&d->deviceContext->readOverlapped, 0, sizeof(OVERLAPPED)); d->deviceContext->readOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); memset(&d->deviceContext->writeOverlapped, 0, sizeof(OVERLAPPED)); d->deviceContext->writeOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (d->deviceContext->readOverlapped.hEvent == NULL || d->deviceContext->writeOverlapped.hEvent == NULL) { *errorMessage = QString::fromLatin1("Failed to create events: %1").arg(winErrorMessage(GetLastError())); return false; } #else d->deviceContext->file.setFileName(d->port); if (!d->deviceContext->file.open(QIODevice::ReadWrite|QIODevice::Unbuffered)) { *errorMessage = QString::fromLatin1("Cannot open %1: %2").arg(d->port, d->deviceContext->file.errorString()); return false; } struct termios termInfo; if (tcgetattr(d->deviceContext->file.handle(), &termInfo) < 0) { *errorMessage = QString::fromLatin1("Unable to retrieve terminal settings: %1 %2").arg(errno).arg(QString::fromAscii(strerror(errno))); return false; } // Turn off terminal echo as not get messages back, among other things termInfo.c_cflag |= CREAD|CLOCAL; termInfo.c_lflag &= (~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG)); termInfo.c_iflag &= (~(INPCK|IGNPAR|PARMRK|ISTRIP|ICRNL|IXANY)); termInfo.c_oflag &= (~OPOST); termInfo.c_cc[VMIN] = 0; termInfo.c_cc[VINTR] = _POSIX_VDISABLE; termInfo.c_cc[VQUIT] = _POSIX_VDISABLE; termInfo.c_cc[VSTART] = _POSIX_VDISABLE; termInfo.c_cc[VSTOP] = _POSIX_VDISABLE; termInfo.c_cc[VSUSP] = _POSIX_VDISABLE; if (tcsetattr(d->deviceContext->file.handle(), TCSAFLUSH, &termInfo) < 0) { *errorMessage = QString::fromLatin1("Unable to apply terminal settings: %1 %2").arg(errno).arg(QString::fromAscii(strerror(errno))); return false; } #endif d->readerThread.reset(new ReaderThread(d->deviceContext)); connect(d->readerThread.data(), SIGNAL(error(QString)), this, SLOT(emitError(QString)), Qt::QueuedConnection); connect(d->readerThread.data(), SIGNAL(messageReceived(trk::TrkResult,QByteArray)), this, SLOT(slotMessageReceived(trk::TrkResult,QByteArray)), Qt::QueuedConnection); d->readerThread->start(); d->writerThread.reset(new WriterThread(d->deviceContext)); connect(d->writerThread.data(), SIGNAL(error(QString)), this, SLOT(emitError(QString)), Qt::QueuedConnection); d->writerThread->start(); if (d->verbose || verboseTrk) qDebug() << "Opened" << d->port << d->readerThread.data() << d->writerThread.data(); return true; } void TrkDevice::close() { if (verboseTrk) qDebug() << "close" << d->port << " is open: " << isOpen() << " read pending " << (d->readerThread.isNull() ? 0 : d->readerThread->bytesPending()) << sender(); if (!isOpen()) return; if (d->readerThread) d->readerThread->terminate(); if (d->writerThread) d->writerThread->terminate(); #ifdef Q_OS_WIN CloseHandle(d->deviceContext->device); d->deviceContext->device = INVALID_HANDLE_VALUE; CloseHandle(d->deviceContext->readOverlapped.hEvent); CloseHandle(d->deviceContext->writeOverlapped.hEvent); d->deviceContext->readOverlapped.hEvent = d->deviceContext->writeOverlapped.hEvent = NULL; #else d->deviceContext->file.close(); #endif if (d->verbose) emitLogMessage("Close"); } bool TrkDevice::isOpen() const { #ifdef Q_OS_WIN return d->deviceContext->device != INVALID_HANDLE_VALUE; #else return d->deviceContext->file.isOpen(); #endif } QString TrkDevice::port() const { return d->port; } void TrkDevice::setPort(const QString &p) { if (verboseTrk) qDebug() << "setPort" << p; d->port = p; } QString TrkDevice::errorString() const { return d->errorString; } bool TrkDevice::serialFrame() const { return d->deviceContext->serialFrame; } void TrkDevice::setSerialFrame(bool f) { if (verboseTrk) qDebug() << "setSerialFrame" << f; d->deviceContext->serialFrame = f; } int TrkDevice::verbose() const { return d->verbose; } void TrkDevice::setVerbose(int b) { d->verbose = b; } void TrkDevice::slotMessageReceived(const trk::TrkResult &result, const QByteArray &rawData) { if (isOpen()) { // Might receive bytes after closing due to queued connections. d->writerThread->slotHandleResult(result); if (d->verbose > 1) qDebug() << "Received: " << result.toString(); emit messageReceived(result); if (!rawData.isEmpty()) emit rawDataReceived(rawData); } } void TrkDevice::emitError(const QString &s) { d->errorString = s; qWarning("%s\n", qPrintable(s)); emit error(s); } void TrkDevice::clearWriteQueue() { if (isOpen()) d->writerThread->clearWriteQueue(); } void TrkDevice::sendTrkMessage(byte code, TrkCallback callback, const QByteArray &data, const QVariant &cookie) { if (!isOpen()) { emitError(msgAccessingClosedDevice(d->port)); return; } if (!d->writerThread.isNull()) { if (d->verbose > 1) { QByteArray msg = "Sending: 0x"; msg += QByteArray::number(code, 16); msg += ": "; msg += stringFromArray(data).toLatin1(); if (cookie.isValid()) msg += " Cookie: " + cookie.toString().toLatin1(); qDebug("%s", msg.data()); } d->writerThread->queueTrkMessage(code, callback, data, cookie); } } void TrkDevice::sendTrkInitialPing() { if (!isOpen()) { emitError(msgAccessingClosedDevice(d->port)); return; } if (!d->writerThread.isNull()) d->writerThread->queueTrkInitialPing(); } bool TrkDevice::sendTrkAck(byte token) { if (!isOpen()) { emitError(msgAccessingClosedDevice(d->port)); return false; } if (d->writerThread.isNull()) return false; // The acknowledgement must not be queued! TrkMessage msg(0x80, token); msg.token = token; msg.data.append('\0'); if (verboseTrk) qDebug() << "Write synchroneous message: " << msg; return d->writerThread->trkWriteRawMessage(msg); // 01 90 00 07 7e 80 01 00 7d 5e 7e } void TrkDevice::emitLogMessage(const QString &msg) { if (d->verbose) qDebug("%s\n", qPrintable(msg)); emit logMessage(msg); } } // namespace trk #include "trkdevice.moc"