tools/runonphone/trk/trkdevice.cpp
branchRCL_3
changeset 4 3b1da2848fc7
child 5 d3bac044e0f0
equal deleted inserted replaced
3:41300fa6a67c 4:3b1da2848fc7
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the tools applications of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "trkdevice.h"
       
    43 #include "trkutils.h"
       
    44 
       
    45 #include <QtCore/QString>
       
    46 #include <QtCore/QDebug>
       
    47 #include <QtCore/QQueue>
       
    48 #include <QtCore/QHash>
       
    49 #include <QtCore/QMap>
       
    50 #include <QtCore/QThread>
       
    51 #include <QtCore/QMutex>
       
    52 #include <QtCore/QWaitCondition>
       
    53 #include <QtCore/QSharedPointer>
       
    54 #include <QtCore/QMetaType>
       
    55 
       
    56 #ifdef Q_OS_WIN
       
    57 #  include <windows.h>
       
    58 #else
       
    59 #  include <QtCore/QFile>
       
    60 
       
    61 #  include <stdio.h>
       
    62 #  include <sys/ioctl.h>
       
    63 #  include <sys/types.h>
       
    64 #  include <termios.h>
       
    65 #  include <errno.h>
       
    66 #  include <string.h>
       
    67 #  include <unistd.h>
       
    68 /* Required headers for select() according to POSIX.1-2001 */
       
    69 #  include <sys/select.h>
       
    70 /* Required headers for select() according to earlier standards:
       
    71        #include <sys/time.h>
       
    72        #include <sys/types.h>
       
    73        #include <unistd.h>
       
    74 */
       
    75 #endif
       
    76 
       
    77 #ifdef Q_OS_WIN
       
    78 
       
    79 // Format windows error from GetLastError() value:
       
    80 // TODO: Use the one provided by the utils lib.
       
    81 QString winErrorMessage(unsigned long error)
       
    82 {
       
    83     QString rc = QString::fromLatin1("#%1: ").arg(error);
       
    84     ushort *lpMsgBuf;
       
    85 
       
    86     const int len = FormatMessage(
       
    87             FORMAT_MESSAGE_ALLOCATE_BUFFER
       
    88                 | FORMAT_MESSAGE_FROM_SYSTEM
       
    89                 | FORMAT_MESSAGE_IGNORE_INSERTS,
       
    90             NULL, error, 0, (LPTSTR)&lpMsgBuf, 0, NULL);
       
    91     if (len) {
       
    92         rc = QString::fromUtf16(lpMsgBuf, len);
       
    93         LocalFree(lpMsgBuf);
       
    94     } else {
       
    95         rc += QString::fromLatin1("<unknown error>");
       
    96     }
       
    97     return rc;
       
    98 }
       
    99 
       
   100 #endif
       
   101 
       
   102 namespace trk {
       
   103 
       
   104 ///////////////////////////////////////////////////////////////////////
       
   105 //
       
   106 // TrkMessage
       
   107 //
       
   108 ///////////////////////////////////////////////////////////////////////
       
   109 
       
   110 /* A message to be send to TRK, triggering a callback on receipt
       
   111  * of the answer. */
       
   112 struct TrkMessage
       
   113 {
       
   114     explicit TrkMessage(byte code = 0u, byte token = 0u,
       
   115                         TrkCallback callback = TrkCallback());
       
   116 
       
   117     byte code;
       
   118     byte token;
       
   119     QByteArray data;
       
   120     QVariant cookie;
       
   121     TrkCallback callback;
       
   122 };
       
   123 
       
   124 TrkMessage::TrkMessage(byte c, byte t, TrkCallback cb) :
       
   125     code(c),
       
   126     token(t),
       
   127     callback(cb)
       
   128 {
       
   129 }
       
   130 
       
   131 } // namespace trk
       
   132 
       
   133 Q_DECLARE_METATYPE(trk::TrkMessage)
       
   134 Q_DECLARE_METATYPE(trk::TrkResult)
       
   135 
       
   136 namespace trk {
       
   137 
       
   138 ///////////////////////////////////////////////////////////////////////
       
   139 //
       
   140 // TrkWriteQueue: Mixin class that manages a write queue of Trk messages.
       
   141 // pendingMessage()/notifyWriteResult() should be called from a worked/timer
       
   142 // that writes the messages. The class does not take precautions for multithreading.
       
   143 // A no-op message is simply taken off the queue. The calling class
       
   144 // can use the helper invokeNoopMessage() to trigger its callback.
       
   145 //
       
   146 ///////////////////////////////////////////////////////////////////////
       
   147 
       
   148 class TrkWriteQueue
       
   149 {    
       
   150     Q_DISABLE_COPY(TrkWriteQueue)
       
   151 public:
       
   152     explicit TrkWriteQueue();
       
   153 
       
   154     // Enqueue messages.
       
   155     void queueTrkMessage(byte code, TrkCallback callback,
       
   156                         const QByteArray &data, const QVariant &cookie);
       
   157     void queueTrkInitialPing();
       
   158 
       
   159     // Call this from the device read notification with the results.
       
   160     void slotHandleResult(const TrkResult &result, QMutex *mutex = 0);
       
   161 
       
   162     // pendingMessage() can be called periodically in a timer to retrieve
       
   163     // the pending messages to be sent.
       
   164     enum PendingMessageResult {
       
   165         NoMessage,               // No message in queue.
       
   166         PendingMessage,          /* There is a queued message. The calling class
       
   167                                   * can write it out and use notifyWriteResult()
       
   168                                   * to notify about the result. */
       
   169         NoopMessageDequeued      // A no-op message has been dequeued. see invokeNoopMessage().
       
   170     };
       
   171 
       
   172     PendingMessageResult pendingMessage(TrkMessage *message);
       
   173     // Notify the queue about the success of the write operation
       
   174     // after taking the pendingMessage off.
       
   175     enum WriteResult {
       
   176         WriteOk,
       
   177         WriteFailedDiscard, // Discard failed message
       
   178         WriteFailedKeep,    // Keep failed message
       
   179     };
       
   180     void notifyWriteResult(WriteResult ok);
       
   181 
       
   182     // Helper function that invokes the callback of a no-op message
       
   183     static void invokeNoopMessage(trk::TrkMessage);
       
   184 
       
   185 private:
       
   186     typedef QMap<byte, TrkMessage> TokenMessageMap;
       
   187 
       
   188     byte nextTrkWriteToken();
       
   189 
       
   190     byte m_trkWriteToken;
       
   191     QQueue<TrkMessage> m_trkWriteQueue;
       
   192     TokenMessageMap m_writtenTrkMessages;
       
   193     bool m_trkWriteBusy;
       
   194 };
       
   195 
       
   196 TrkWriteQueue::TrkWriteQueue() :
       
   197     m_trkWriteToken(0),
       
   198     m_trkWriteBusy(false)
       
   199 {
       
   200 }
       
   201 
       
   202 byte TrkWriteQueue::nextTrkWriteToken()
       
   203 {
       
   204     ++m_trkWriteToken;
       
   205     if (m_trkWriteToken == 0)
       
   206         ++m_trkWriteToken;
       
   207     return m_trkWriteToken;
       
   208 }
       
   209 
       
   210 void TrkWriteQueue::queueTrkMessage(byte code, TrkCallback callback,
       
   211     const QByteArray &data, const QVariant &cookie)
       
   212 {
       
   213     const byte token = code == TRK_WRITE_QUEUE_NOOP_CODE ?
       
   214                                 byte(0) : nextTrkWriteToken();
       
   215     TrkMessage msg(code, token, callback);
       
   216     msg.data = data;
       
   217     msg.cookie = cookie;
       
   218     m_trkWriteQueue.append(msg);
       
   219 }
       
   220 
       
   221 TrkWriteQueue::PendingMessageResult TrkWriteQueue::pendingMessage(TrkMessage *message)
       
   222 {
       
   223     // Invoked from timer, try to flush out message queue
       
   224     if (m_trkWriteBusy || m_trkWriteQueue.isEmpty())
       
   225         return NoMessage;
       
   226     // Handle the noop message, just invoke CB in slot (ower thread)
       
   227     if (m_trkWriteQueue.front().code == TRK_WRITE_QUEUE_NOOP_CODE) {
       
   228         *message = m_trkWriteQueue.dequeue();
       
   229         return NoopMessageDequeued;
       
   230     }
       
   231     // Insert into map fir answers (as reading threads might get an
       
   232     // answer before notifyWriteResult(true)) is called.
       
   233     *message = m_trkWriteQueue.front();
       
   234     m_writtenTrkMessages.insert(message->token, *message);
       
   235     m_trkWriteBusy = true;
       
   236     return PendingMessage;
       
   237 }
       
   238 
       
   239 void TrkWriteQueue::invokeNoopMessage(trk::TrkMessage noopMessage)
       
   240 {
       
   241     TrkResult result;
       
   242     result.code = noopMessage.code;
       
   243     result.token = noopMessage.token;
       
   244     result.data = noopMessage.data;
       
   245     result.cookie = noopMessage.cookie;
       
   246     noopMessage.callback(result);
       
   247 }
       
   248 
       
   249 void TrkWriteQueue::notifyWriteResult(WriteResult wr)
       
   250 {
       
   251     // On success, dequeue message and await result
       
   252     const byte token = m_trkWriteQueue.front().token;
       
   253     switch (wr) {
       
   254     case WriteOk:
       
   255         m_trkWriteQueue.dequeue();
       
   256         break;
       
   257     case WriteFailedKeep:
       
   258     case WriteFailedDiscard:
       
   259         m_writtenTrkMessages.remove(token);
       
   260         m_trkWriteBusy = false;
       
   261         if (wr == WriteFailedDiscard)
       
   262             m_trkWriteQueue.dequeue();
       
   263         break;
       
   264     }
       
   265 }
       
   266 
       
   267 void TrkWriteQueue::slotHandleResult(const TrkResult &result, QMutex *mutex)
       
   268 {
       
   269     // Find which request the message belongs to and invoke callback
       
   270     // if ACK or on NAK if desired.
       
   271     if (mutex)
       
   272         mutex->lock();
       
   273     m_trkWriteBusy = false;
       
   274     const TokenMessageMap::iterator it = m_writtenTrkMessages.find(result.token);
       
   275     if (it == m_writtenTrkMessages.end()) {
       
   276         if (mutex)
       
   277             mutex->unlock();
       
   278         return;
       
   279     }
       
   280     TrkCallback callback = it.value().callback;
       
   281     const QVariant cookie = it.value().cookie;
       
   282     m_writtenTrkMessages.erase(it);
       
   283     if (mutex)
       
   284         mutex->unlock();
       
   285     // Invoke callback
       
   286     if (callback) {
       
   287         TrkResult result1 = result;
       
   288         result1.cookie = cookie;
       
   289         callback(result1);
       
   290     }
       
   291 }
       
   292 
       
   293 void TrkWriteQueue::queueTrkInitialPing()
       
   294 {
       
   295     // Ping, reset sequence count
       
   296     m_trkWriteToken = 0;
       
   297     m_trkWriteQueue.append(TrkMessage(TrkPing, 0));
       
   298 }
       
   299 
       
   300 ///////////////////////////////////////////////////////////////////////
       
   301 //
       
   302 // DeviceContext to be shared between threads
       
   303 //
       
   304 ///////////////////////////////////////////////////////////////////////
       
   305 
       
   306 struct DeviceContext {
       
   307     DeviceContext();
       
   308 #ifdef Q_OS_WIN
       
   309     HANDLE device;
       
   310     OVERLAPPED readOverlapped;
       
   311     OVERLAPPED writeOverlapped;
       
   312 #else
       
   313     QFile file;
       
   314 #endif
       
   315     bool serialFrame;
       
   316     QMutex mutex;
       
   317 };
       
   318 
       
   319 DeviceContext::DeviceContext() :
       
   320 #ifdef Q_OS_WIN
       
   321     device(INVALID_HANDLE_VALUE),
       
   322 #endif
       
   323     serialFrame(true)
       
   324 {
       
   325 }
       
   326 
       
   327 ///////////////////////////////////////////////////////////////////////
       
   328 //
       
   329 // TrkWriterThread: A thread operating a TrkWriteQueue.
       
   330 // with exception of the handling of the  TRK_WRITE_QUEUE_NOOP_CODE
       
   331 // synchronization message. The invocation of the callback is then
       
   332 // done by the thread owning the TrkWriteQueue, while pendingMessage() is called
       
   333 // from another thread. This happens via a Qt::BlockingQueuedConnection.
       
   334 
       
   335 ///////////////////////////////////////////////////////////////////////
       
   336 
       
   337 class WriterThread : public QThread {
       
   338     Q_OBJECT
       
   339     Q_DISABLE_COPY(WriterThread)
       
   340 public:            
       
   341     explicit WriterThread(const QSharedPointer<DeviceContext> &context);
       
   342 
       
   343     // Enqueue messages.
       
   344     void queueTrkMessage(byte code, TrkCallback callback,
       
   345                         const QByteArray &data, const QVariant &cookie);
       
   346     void queueTrkInitialPing();
       
   347 
       
   348     // Call this from the device read notification with the results.
       
   349     void slotHandleResult(const TrkResult &result);
       
   350 
       
   351     virtual void run();
       
   352 
       
   353 signals:
       
   354     void error(const QString &);
       
   355     void internalNoopMessageDequeued(const trk::TrkMessage&);
       
   356 
       
   357 public slots:
       
   358     bool trkWriteRawMessage(const TrkMessage &msg);
       
   359     void terminate();
       
   360     void tryWrite();
       
   361 
       
   362 private slots:
       
   363     void invokeNoopMessage(const trk::TrkMessage &);
       
   364 
       
   365 private:    
       
   366     bool write(const QByteArray &data, QString *errorMessage);
       
   367     inline int writePendingMessage();
       
   368 
       
   369     const QSharedPointer<DeviceContext> m_context;
       
   370     QMutex m_dataMutex;
       
   371     QMutex m_waitMutex;
       
   372     QWaitCondition m_waitCondition;
       
   373     TrkWriteQueue m_queue;
       
   374     bool m_terminate;
       
   375 };
       
   376 
       
   377 WriterThread::WriterThread(const QSharedPointer<DeviceContext> &context) :
       
   378     m_context(context),
       
   379     m_terminate(false)
       
   380 {
       
   381     static const int trkMessageMetaId = qRegisterMetaType<trk::TrkMessage>();
       
   382     Q_UNUSED(trkMessageMetaId)
       
   383     connect(this, SIGNAL(internalNoopMessageDequeued(trk::TrkMessage)),
       
   384             this, SLOT(invokeNoopMessage(trk::TrkMessage)), Qt::BlockingQueuedConnection);
       
   385 }
       
   386 
       
   387 void WriterThread::run()
       
   388 {
       
   389     while (writePendingMessage() == 0) ;
       
   390 }
       
   391 
       
   392 int WriterThread::writePendingMessage()
       
   393 {
       
   394     enum { MaxAttempts = 100, RetryIntervalMS = 200 };
       
   395 
       
   396     // Wait. Use a timeout in case something is already queued before we
       
   397     // start up or some weird hanging exit condition
       
   398     m_waitMutex.lock();
       
   399     m_waitCondition.wait(&m_waitMutex, 100);
       
   400     m_waitMutex.unlock();
       
   401     if (m_terminate)
       
   402         return 1;
       
   403     // Send off message
       
   404     m_dataMutex.lock();
       
   405     TrkMessage message;
       
   406     const TrkWriteQueue::PendingMessageResult pr = m_queue.pendingMessage(&message);
       
   407     m_dataMutex.unlock();
       
   408     switch (pr) {
       
   409     case TrkWriteQueue::NoMessage:
       
   410         break;
       
   411     case TrkWriteQueue::PendingMessage: {
       
   412             // Untested: try to re-send a few times
       
   413             bool success = false;
       
   414             for (int r = 0; !success && (r < MaxAttempts); r++) {
       
   415                 success = trkWriteRawMessage(message);
       
   416                 if (!success) {
       
   417                     emit error(QString::fromLatin1("Write failure, attempt %1 of %2.").arg(r).arg(int(MaxAttempts)));
       
   418                     if (m_terminate)
       
   419                         return 1;
       
   420                     QThread::msleep(RetryIntervalMS);
       
   421                 }
       
   422             }
       
   423             // Notify queue. If still failed, give up.
       
   424             m_dataMutex.lock();
       
   425             m_queue.notifyWriteResult(success ? TrkWriteQueue::WriteOk : TrkWriteQueue::WriteFailedDiscard);
       
   426             m_dataMutex.unlock();
       
   427         }
       
   428         break;
       
   429     case TrkWriteQueue::NoopMessageDequeued:
       
   430         // Sync with thread that owns us via a blocking signal
       
   431         emit internalNoopMessageDequeued(message);
       
   432         break;
       
   433     } // switch
       
   434     return 0;
       
   435 }
       
   436 
       
   437 void WriterThread::invokeNoopMessage(const trk::TrkMessage &msg)
       
   438 {
       
   439     TrkWriteQueue::invokeNoopMessage(msg);
       
   440 }
       
   441 
       
   442 void WriterThread::terminate()
       
   443 {
       
   444     m_terminate = true;
       
   445     m_waitCondition.wakeAll();
       
   446     wait();
       
   447     m_terminate = false;
       
   448 }
       
   449 
       
   450 #ifdef Q_OS_WIN
       
   451 
       
   452 static inline QString msgTerminated(int size)
       
   453 {
       
   454     return QString::fromLatin1("Terminated with %1 bytes pending.").arg(size);
       
   455 }
       
   456 
       
   457 // Interruptible synchronous write function.
       
   458 static inline bool overlappedSyncWrite(HANDLE file,
       
   459                                        const bool &terminateFlag,
       
   460                                        const char *data,
       
   461                                        DWORD size, DWORD *charsWritten,
       
   462                                        OVERLAPPED *overlapped,
       
   463                                        QString *errorMessage)
       
   464 {
       
   465     if (WriteFile(file, data, size, charsWritten, overlapped))
       
   466         return true;
       
   467     const DWORD writeError = GetLastError();
       
   468     if (writeError != ERROR_IO_PENDING) {
       
   469         *errorMessage = QString::fromLatin1("WriteFile failed: %1").arg(winErrorMessage(writeError));
       
   470         return false;
       
   471     }
       
   472     // Wait for written or thread terminated
       
   473     const DWORD timeoutMS = 200;
       
   474     const unsigned maxAttempts = 20;
       
   475     DWORD wr = WaitForSingleObject(overlapped->hEvent, timeoutMS);
       
   476     for (unsigned n = 0; wr == WAIT_TIMEOUT && n < maxAttempts && !terminateFlag;
       
   477          wr = WaitForSingleObject(overlapped->hEvent, timeoutMS), n++);
       
   478     if (terminateFlag) {
       
   479         *errorMessage = msgTerminated(size);
       
   480         return false;
       
   481     }
       
   482     switch (wr) {
       
   483     case WAIT_OBJECT_0:
       
   484         break;
       
   485     case WAIT_TIMEOUT:
       
   486         *errorMessage = QString::fromLatin1("Write timed out.");
       
   487         return false;
       
   488     default:
       
   489         *errorMessage = QString::fromLatin1("Error while waiting for WriteFile results: %1").arg(winErrorMessage(GetLastError()));
       
   490         return false;
       
   491     }
       
   492     if (!GetOverlappedResult(file, overlapped, charsWritten, TRUE)) {
       
   493         *errorMessage = QString::fromLatin1("Error writing %1 bytes: %2").arg(size).arg(winErrorMessage(GetLastError()));
       
   494         return false;
       
   495     }
       
   496     return true;
       
   497 }
       
   498 #endif
       
   499 
       
   500 bool WriterThread::write(const QByteArray &data, QString *errorMessage)
       
   501 {
       
   502     QMutexLocker locker(&m_context->mutex);
       
   503 #ifdef Q_OS_WIN
       
   504     DWORD charsWritten;
       
   505     if (!overlappedSyncWrite(m_context->device, m_terminate, data.data(), data.size(), &charsWritten, &m_context->writeOverlapped, errorMessage)) {
       
   506         return false;
       
   507     }
       
   508     FlushFileBuffers(m_context->device);
       
   509     return true;
       
   510 #else
       
   511     if (m_context->file.write(data) == -1 || !m_context->file.flush()) {
       
   512         *errorMessage = QString::fromLatin1("Cannot write: %1").arg(m_context->file.errorString());
       
   513         return false;
       
   514     }
       
   515     return  true;
       
   516 #endif
       
   517 }
       
   518 
       
   519 bool WriterThread::trkWriteRawMessage(const TrkMessage &msg)
       
   520 {
       
   521     const QByteArray ba = frameMessage(msg.code, msg.token, msg.data, m_context->serialFrame);
       
   522     QString errorMessage;
       
   523     const bool rc = write(ba, &errorMessage);
       
   524     if (!rc) {
       
   525         qWarning("%s\n", qPrintable(errorMessage));
       
   526         emit error(errorMessage);
       
   527     }
       
   528     return rc;
       
   529 }
       
   530 
       
   531 void WriterThread::tryWrite()
       
   532 {
       
   533     m_waitCondition.wakeAll();
       
   534 }
       
   535 
       
   536 void WriterThread::queueTrkMessage(byte code, TrkCallback callback,
       
   537                                    const QByteArray &data, const QVariant &cookie)
       
   538 {
       
   539     m_dataMutex.lock();
       
   540     m_queue.queueTrkMessage(code, callback, data, cookie);
       
   541     m_dataMutex.unlock();
       
   542     tryWrite();
       
   543 }
       
   544 
       
   545 void WriterThread::queueTrkInitialPing()
       
   546 {
       
   547     m_dataMutex.lock();
       
   548     m_queue.queueTrkInitialPing();
       
   549     m_dataMutex.unlock();
       
   550     tryWrite();
       
   551 }
       
   552 
       
   553 // Call this from the device read notification with the results.
       
   554 void WriterThread::slotHandleResult(const TrkResult &result)
       
   555 {
       
   556     m_queue.slotHandleResult(result, &m_dataMutex);
       
   557     tryWrite(); // Have messages been enqueued in-between?
       
   558 }
       
   559 
       
   560 ///////////////////////////////////////////////////////////////////////
       
   561 //
       
   562 // ReaderThreadBase: Base class for a thread that reads data from
       
   563 // the device, decodes the messages and emit signals for the messages.
       
   564 // A Qt::BlockingQueuedConnection should be used for the message signal
       
   565 // to ensure messages are processed in the correct sequence.
       
   566 //
       
   567 ///////////////////////////////////////////////////////////////////////
       
   568 
       
   569 class ReaderThreadBase : public QThread {
       
   570     Q_OBJECT
       
   571     Q_DISABLE_COPY(ReaderThreadBase)
       
   572 public:
       
   573 
       
   574 signals:
       
   575     void messageReceived(const trk::TrkResult &result, const QByteArray &rawData);
       
   576 
       
   577 protected:
       
   578     explicit ReaderThreadBase(const QSharedPointer<DeviceContext> &context);
       
   579     void processData(const QByteArray &a);
       
   580     void processData(char c);
       
   581 
       
   582     const QSharedPointer<DeviceContext> m_context;
       
   583 
       
   584 private:
       
   585     void readMessages();
       
   586 
       
   587     QByteArray m_trkReadBuffer;
       
   588 };
       
   589 
       
   590 ReaderThreadBase::ReaderThreadBase(const QSharedPointer<DeviceContext> &context) :
       
   591     m_context(context)
       
   592 {
       
   593     static const int trkResultMetaId = qRegisterMetaType<trk::TrkResult>();
       
   594     Q_UNUSED(trkResultMetaId)
       
   595 }
       
   596 
       
   597 void ReaderThreadBase::processData(const QByteArray &a)
       
   598 {
       
   599     m_trkReadBuffer += a;
       
   600     readMessages();
       
   601 }
       
   602 
       
   603 void ReaderThreadBase::processData(char c)
       
   604 {
       
   605     m_trkReadBuffer += c;
       
   606     if (m_trkReadBuffer.size() > 1)
       
   607         readMessages();
       
   608 }
       
   609 
       
   610 void ReaderThreadBase::readMessages()
       
   611 {
       
   612     TrkResult r;
       
   613     QByteArray rawData;
       
   614     while (extractResult(&m_trkReadBuffer, m_context->serialFrame, &r, &rawData)) {
       
   615         emit messageReceived(r, rawData);
       
   616     }
       
   617 }
       
   618 
       
   619 #ifdef Q_OS_WIN
       
   620 ///////////////////////////////////////////////////////////////////////
       
   621 //
       
   622 // WinReaderThread: A thread reading from the device using Windows API.
       
   623 // Waits on an overlapped I/O handle and an event that tells the thread to
       
   624 // terminate.
       
   625 //
       
   626 ///////////////////////////////////////////////////////////////////////
       
   627 
       
   628 class WinReaderThread : public ReaderThreadBase {
       
   629     Q_OBJECT
       
   630     Q_DISABLE_COPY(WinReaderThread)
       
   631 public:
       
   632     explicit WinReaderThread(const QSharedPointer<DeviceContext> &context);
       
   633     ~WinReaderThread();
       
   634 
       
   635     virtual void run();
       
   636 
       
   637 signals:
       
   638     void error(const QString &);
       
   639 
       
   640 public slots:
       
   641     void terminate();
       
   642 
       
   643 private:
       
   644     enum Handles { FileHandle, TerminateEventHandle, HandleCount };
       
   645 
       
   646     inline int tryRead();
       
   647 
       
   648     HANDLE m_handles[HandleCount];
       
   649 };
       
   650 
       
   651 WinReaderThread::WinReaderThread(const QSharedPointer<DeviceContext> &context) :
       
   652     ReaderThreadBase(context)
       
   653 {
       
   654     m_handles[FileHandle] = NULL;
       
   655     m_handles[TerminateEventHandle] = CreateEvent(NULL, FALSE, FALSE, NULL);
       
   656 }
       
   657 
       
   658 WinReaderThread::~WinReaderThread()
       
   659 {
       
   660     CloseHandle(m_handles[TerminateEventHandle]);
       
   661 }
       
   662 
       
   663 // Return 0 to continue or error code
       
   664 int WinReaderThread::tryRead()
       
   665 {
       
   666     enum { BufSize = 1024 };
       
   667     char buffer[BufSize];
       
   668     // Check if there are already bytes waiting. If not, wait for first byte
       
   669     COMSTAT comStat;
       
   670     if (!ClearCommError(m_context->device, NULL, &comStat)){
       
   671         emit error(QString::fromLatin1("ClearCommError failed: %1").arg(winErrorMessage(GetLastError())));
       
   672         return -7;
       
   673     }    
       
   674     const DWORD bytesToRead = qMax(DWORD(1), qMin(comStat.cbInQue, DWORD(BufSize)));
       
   675     // Trigger read
       
   676     DWORD bytesRead = 0;
       
   677     if (ReadFile(m_context->device, &buffer, bytesToRead, &bytesRead, &m_context->readOverlapped)) {
       
   678         if (bytesRead == 1) {
       
   679             processData(buffer[0]);
       
   680         } else {
       
   681             processData(QByteArray(buffer, bytesRead));
       
   682         }
       
   683         return 0;
       
   684     }
       
   685     const DWORD readError = GetLastError();
       
   686     if (readError != ERROR_IO_PENDING) {
       
   687         emit error(QString::fromLatin1("Read error: %1").arg(winErrorMessage(readError)));
       
   688         return -1;
       
   689     }    
       
   690     // Wait for either termination or data
       
   691     const DWORD wr = WaitForMultipleObjects(HandleCount, m_handles, false, INFINITE);
       
   692     if (wr == WAIT_FAILED) {
       
   693         emit error(QString::fromLatin1("Wait failed: %1").arg(winErrorMessage(GetLastError())));
       
   694         return -2;
       
   695     }
       
   696     if (wr - WAIT_OBJECT_0 == TerminateEventHandle) {
       
   697         return 1; // Terminate
       
   698     }
       
   699     // Check data
       
   700     if (!GetOverlappedResult(m_context->device, &m_context->readOverlapped, &bytesRead, true)) {
       
   701         emit error(QString::fromLatin1("GetOverlappedResult failed: %1").arg(winErrorMessage(GetLastError())));
       
   702         return -3;
       
   703     }
       
   704     if (bytesRead == 1) {
       
   705         processData(buffer[0]);
       
   706     } else {
       
   707         processData(QByteArray(buffer, bytesRead));
       
   708     }
       
   709     return 0;
       
   710 }
       
   711 
       
   712 void WinReaderThread::run()
       
   713 {
       
   714     m_handles[FileHandle] = m_context->readOverlapped.hEvent;
       
   715     while ( tryRead() == 0) ;
       
   716 }
       
   717 
       
   718 void WinReaderThread::terminate()
       
   719 {
       
   720     SetEvent(m_handles[TerminateEventHandle]);
       
   721     wait();
       
   722 }
       
   723 
       
   724 typedef WinReaderThread ReaderThread;
       
   725 
       
   726 #else
       
   727 
       
   728 ///////////////////////////////////////////////////////////////////////
       
   729 //
       
   730 // UnixReaderThread: A thread reading from the device.
       
   731 // Uses select() to wait and a special ioctl() to find out the number
       
   732 // of bytes queued. For clean termination, the self-pipe trick is used.
       
   733 // The class maintains a pipe, on whose read end the select waits besides
       
   734 // the device file handle. To terminate, a byte is written to the pipe.
       
   735 //
       
   736 ///////////////////////////////////////////////////////////////////////
       
   737 
       
   738 static inline QString msgUnixCallFailedErrno(const char *func, int errorNumber)
       
   739 {
       
   740     return QString::fromLatin1("Call to %1() failed: %2").arg(QLatin1String(func), QString::fromLocal8Bit(strerror(errorNumber)));
       
   741 }
       
   742 
       
   743 class UnixReaderThread : public ReaderThreadBase {
       
   744     Q_OBJECT
       
   745     Q_DISABLE_COPY(UnixReaderThread)
       
   746 public:
       
   747     explicit UnixReaderThread(const QSharedPointer<DeviceContext> &context);
       
   748     ~UnixReaderThread();
       
   749 
       
   750     virtual void run();
       
   751 
       
   752 signals:
       
   753     void error(const QString &);
       
   754 
       
   755 public slots:
       
   756     void terminate();
       
   757 
       
   758 private:
       
   759     inline int tryRead();
       
   760 
       
   761     int m_terminatePipeFileDescriptors[2];
       
   762 };
       
   763 
       
   764 UnixReaderThread::UnixReaderThread(const QSharedPointer<DeviceContext> &context) : 
       
   765     ReaderThreadBase(context)
       
   766 {
       
   767     m_terminatePipeFileDescriptors[0] = m_terminatePipeFileDescriptors[1] = -1;
       
   768     // Set up pipes for termination. Should not fail
       
   769     if (pipe(m_terminatePipeFileDescriptors) < 0)
       
   770         qWarning("%s\n", qPrintable(msgUnixCallFailedErrno("pipe", errno)));
       
   771 }
       
   772 
       
   773 UnixReaderThread::~UnixReaderThread()
       
   774 {
       
   775     close(m_terminatePipeFileDescriptors[0]);
       
   776     close(m_terminatePipeFileDescriptors[1]);
       
   777 }
       
   778 
       
   779 int UnixReaderThread::tryRead()
       
   780 {
       
   781     fd_set readSet, tempReadSet, tempExceptionSet;
       
   782     struct timeval timeOut;
       
   783     const int fileDescriptor = m_context->file.handle();
       
   784     FD_ZERO(&readSet);
       
   785     FD_SET(fileDescriptor, &readSet);
       
   786     FD_SET(m_terminatePipeFileDescriptors[0], &readSet);
       
   787     const int maxFileDescriptor = qMax(m_terminatePipeFileDescriptors[0], fileDescriptor);
       
   788     int result = 0;
       
   789     do {
       
   790         memcpy(&tempReadSet, &readSet, sizeof(fd_set));
       
   791         memcpy(&tempExceptionSet, &readSet, sizeof(fd_set));
       
   792         timeOut.tv_sec = 1;
       
   793         timeOut.tv_usec = 0;
       
   794         result = select(maxFileDescriptor + 1, &tempReadSet, NULL, &tempExceptionSet, &timeOut);
       
   795     } while ( result < 0 && errno == EINTR );
       
   796     // Timeout?
       
   797     if (result == 0)
       
   798         return 0;
       
   799    // Something wrong?
       
   800     if (result < 0) {
       
   801         emit error(msgUnixCallFailedErrno("select", errno));
       
   802         return -1;
       
   803     }
       
   804     // Did the exception set trigger on the device?
       
   805     if (FD_ISSET(fileDescriptor,&tempExceptionSet)) {
       
   806         emit error(QLatin1String("An Exception occurred on the device."));
       
   807         return -2;
       
   808     }
       
   809     // Check termination pipe.
       
   810     if (FD_ISSET(m_terminatePipeFileDescriptors[0], &tempReadSet)
       
   811         || FD_ISSET(m_terminatePipeFileDescriptors[0], &tempExceptionSet))
       
   812         return 1;
       
   813 
       
   814     // determine number of pending bytes and read
       
   815     int numBytes;
       
   816     if (ioctl(fileDescriptor, FIONREAD, &numBytes) < 0) {
       
   817         emit error(msgUnixCallFailedErrno("ioctl", errno));
       
   818         return -1;
       
   819     }
       
   820     m_context->mutex.lock();
       
   821     const QByteArray data = m_context->file.read(numBytes);
       
   822     m_context->mutex.unlock();
       
   823     processData(data);
       
   824     return 0;
       
   825 }
       
   826 
       
   827 void UnixReaderThread::run()
       
   828 {
       
   829     // Read loop
       
   830     while (tryRead() == 0)
       
   831         ;
       
   832 }
       
   833 
       
   834 void UnixReaderThread::terminate()
       
   835 {
       
   836     // Trigger select() by writing to the pipe
       
   837     char c = 0;
       
   838     write(m_terminatePipeFileDescriptors[1], &c, 1);
       
   839     wait();
       
   840 }
       
   841 
       
   842 typedef UnixReaderThread ReaderThread;
       
   843 
       
   844 #endif
       
   845 
       
   846 ///////////////////////////////////////////////////////////////////////
       
   847 //
       
   848 // TrkDevicePrivate
       
   849 //
       
   850 ///////////////////////////////////////////////////////////////////////
       
   851 
       
   852 struct TrkDevicePrivate
       
   853 {
       
   854     TrkDevicePrivate();
       
   855 
       
   856     QSharedPointer<DeviceContext> deviceContext;
       
   857     QSharedPointer<WriterThread> writerThread;
       
   858     QSharedPointer<ReaderThread> readerThread;
       
   859 
       
   860     QByteArray trkReadBuffer;
       
   861     int verbose;
       
   862     QString errorString;
       
   863 };
       
   864 
       
   865 ///////////////////////////////////////////////////////////////////////
       
   866 //
       
   867 // TrkDevice
       
   868 //
       
   869 ///////////////////////////////////////////////////////////////////////
       
   870 
       
   871 TrkDevicePrivate::TrkDevicePrivate() :
       
   872     deviceContext(new DeviceContext),
       
   873     verbose(0)
       
   874 {
       
   875 }
       
   876 
       
   877 ///////////////////////////////////////////////////////////////////////
       
   878 //
       
   879 // TrkDevice
       
   880 //
       
   881 ///////////////////////////////////////////////////////////////////////
       
   882 
       
   883 TrkDevice::TrkDevice(QObject *parent) :
       
   884     QObject(parent),
       
   885     d(new TrkDevicePrivate)
       
   886 {}
       
   887 
       
   888 TrkDevice::~TrkDevice()
       
   889 {
       
   890     close();
       
   891     delete d;
       
   892 }
       
   893 
       
   894 bool TrkDevice::open(const QString &port, QString *errorMessage)
       
   895 {
       
   896     if (d->verbose)
       
   897         qDebug() << "Opening" << port << "is open: " << isOpen() << " serialFrame=" << serialFrame();
       
   898     close();
       
   899 #ifdef Q_OS_WIN
       
   900     d->deviceContext->device = CreateFile(port.toStdWString().c_str(),
       
   901                            GENERIC_READ | GENERIC_WRITE,
       
   902                            0,
       
   903                            NULL,
       
   904                            OPEN_EXISTING,
       
   905                            FILE_ATTRIBUTE_NORMAL|FILE_FLAG_NO_BUFFERING|FILE_FLAG_OVERLAPPED,
       
   906                            NULL);
       
   907 
       
   908     if (INVALID_HANDLE_VALUE == d->deviceContext->device) {
       
   909         *errorMessage = QString::fromLatin1("Could not open device '%1': %2").arg(port, winErrorMessage(GetLastError()));
       
   910         return false;
       
   911     }
       
   912     memset(&d->deviceContext->readOverlapped, 0, sizeof(OVERLAPPED));
       
   913     d->deviceContext->readOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
       
   914     memset(&d->deviceContext->writeOverlapped, 0, sizeof(OVERLAPPED));
       
   915     d->deviceContext->writeOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
       
   916     if (d->deviceContext->readOverlapped.hEvent == NULL || d->deviceContext->writeOverlapped.hEvent == NULL) {
       
   917         *errorMessage = QString::fromLatin1("Failed to create events: %1").arg(winErrorMessage(GetLastError()));
       
   918         return false;
       
   919     }
       
   920 #else
       
   921     d->deviceContext->file.setFileName(port);
       
   922     if (!d->deviceContext->file.open(QIODevice::ReadWrite|QIODevice::Unbuffered)) {
       
   923         *errorMessage = QString::fromLatin1("Cannot open %1: %2").arg(port, d->deviceContext->file.errorString());
       
   924         return false;
       
   925     }
       
   926 
       
   927     struct termios termInfo;
       
   928     if (tcgetattr(d->deviceContext->file.handle(), &termInfo) < 0) {
       
   929         *errorMessage = QString::fromLatin1("Unable to retrieve terminal settings: %1 %2").arg(errno).arg(QString::fromAscii(strerror(errno)));
       
   930         return false;
       
   931     }
       
   932     // Turn off terminal echo as not get messages back, among other things
       
   933     termInfo.c_cflag |= CREAD|CLOCAL;
       
   934     termInfo.c_lflag &= (~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG));
       
   935     termInfo.c_iflag &= (~(INPCK|IGNPAR|PARMRK|ISTRIP|ICRNL|IXANY));
       
   936     termInfo.c_oflag &= (~OPOST);
       
   937     termInfo.c_cc[VMIN]  = 0;
       
   938     termInfo.c_cc[VINTR] = _POSIX_VDISABLE;
       
   939     termInfo.c_cc[VQUIT] = _POSIX_VDISABLE;
       
   940     termInfo.c_cc[VSTART] = _POSIX_VDISABLE;
       
   941     termInfo.c_cc[VSTOP] = _POSIX_VDISABLE;
       
   942     termInfo.c_cc[VSUSP] = _POSIX_VDISABLE;
       
   943     if (tcsetattr(d->deviceContext->file.handle(), TCSAFLUSH, &termInfo) < 0) {
       
   944         *errorMessage = QString::fromLatin1("Unable to apply terminal settings: %1 %2").arg(errno).arg(QString::fromAscii(strerror(errno)));
       
   945         return false;
       
   946     }
       
   947 #endif
       
   948     d->readerThread = QSharedPointer<ReaderThread>(new ReaderThread(d->deviceContext));
       
   949     connect(d->readerThread.data(), SIGNAL(error(QString)), this, SLOT(emitError(QString)),
       
   950             Qt::QueuedConnection);
       
   951     connect(d->readerThread.data(), SIGNAL(messageReceived(trk::TrkResult,QByteArray)),
       
   952             this, SLOT(slotMessageReceived(trk::TrkResult,QByteArray)),
       
   953             Qt::QueuedConnection);
       
   954     d->readerThread->start();
       
   955 
       
   956     d->writerThread = QSharedPointer<WriterThread>(new WriterThread(d->deviceContext));
       
   957     connect(d->writerThread.data(), SIGNAL(error(QString)), this, SLOT(emitError(QString)),
       
   958             Qt::QueuedConnection);    
       
   959     d->writerThread->start();    
       
   960 
       
   961     if (d->verbose)
       
   962         qDebug() << "Opened" << port;
       
   963     return true;
       
   964 }
       
   965 
       
   966 void TrkDevice::close()
       
   967 {
       
   968     if (!isOpen())
       
   969         return;
       
   970     if (d->readerThread)
       
   971         d->readerThread->terminate();
       
   972     if (d->writerThread)
       
   973         d->writerThread->terminate();
       
   974 #ifdef Q_OS_WIN
       
   975     CloseHandle(d->deviceContext->device);
       
   976     d->deviceContext->device = INVALID_HANDLE_VALUE;
       
   977     CloseHandle(d->deviceContext->readOverlapped.hEvent);
       
   978     CloseHandle(d->deviceContext->writeOverlapped.hEvent);
       
   979     d->deviceContext->readOverlapped.hEvent = d->deviceContext->writeOverlapped.hEvent = NULL;
       
   980 #else
       
   981     d->deviceContext->file.close();
       
   982 #endif
       
   983     if (d->verbose)
       
   984         emitLogMessage("Close");
       
   985 }
       
   986 
       
   987 bool TrkDevice::isOpen() const
       
   988 {
       
   989 #ifdef Q_OS_WIN
       
   990     return d->deviceContext->device != INVALID_HANDLE_VALUE;
       
   991 #else
       
   992     return d->deviceContext->file.isOpen();
       
   993 #endif
       
   994 }
       
   995 
       
   996 QString TrkDevice::errorString() const
       
   997 {
       
   998     return d->errorString;
       
   999 }
       
  1000 
       
  1001 bool TrkDevice::serialFrame() const
       
  1002 {
       
  1003     return d->deviceContext->serialFrame;
       
  1004 }
       
  1005 
       
  1006 void TrkDevice::setSerialFrame(bool f)
       
  1007 {
       
  1008     d->deviceContext->serialFrame = f;
       
  1009 }
       
  1010 
       
  1011 int TrkDevice::verbose() const
       
  1012 {
       
  1013     return d->verbose;
       
  1014 }
       
  1015 
       
  1016 void TrkDevice::setVerbose(int b)
       
  1017 {
       
  1018     d->verbose = b;
       
  1019 }
       
  1020 
       
  1021 void TrkDevice::slotMessageReceived(const trk::TrkResult &result, const QByteArray &rawData)
       
  1022 {
       
  1023     d->writerThread->slotHandleResult(result);
       
  1024     emit messageReceived(result);    
       
  1025     if (!rawData.isEmpty())
       
  1026         emit rawDataReceived(rawData);
       
  1027 }
       
  1028 
       
  1029 void TrkDevice::emitError(const QString &s)
       
  1030 {
       
  1031     d->errorString = s;
       
  1032     qWarning("%s\n", qPrintable(s));
       
  1033     emit error(s);
       
  1034 }
       
  1035 
       
  1036 void TrkDevice::sendTrkMessage(byte code, TrkCallback callback,
       
  1037      const QByteArray &data, const QVariant &cookie)
       
  1038 {
       
  1039     if (!d->writerThread.isNull()) {
       
  1040         if (d->verbose > 1)
       
  1041             qDebug() << "Sending " << code << data.toHex();
       
  1042         d->writerThread->queueTrkMessage(code, callback, data, cookie);
       
  1043     }
       
  1044 }
       
  1045 
       
  1046 void TrkDevice::sendTrkInitialPing()
       
  1047 {
       
  1048     if (!d->writerThread.isNull())
       
  1049         d->writerThread->queueTrkInitialPing();
       
  1050 }
       
  1051 
       
  1052 bool TrkDevice::sendTrkAck(byte token)
       
  1053 {
       
  1054     if (d->writerThread.isNull())
       
  1055         return false;
       
  1056     // The acknowledgement must not be queued!
       
  1057     TrkMessage msg(0x80, token);
       
  1058     msg.token = token;
       
  1059     msg.data.append('\0');
       
  1060     return d->writerThread->trkWriteRawMessage(msg);
       
  1061     // 01 90 00 07 7e 80 01 00 7d 5e 7e
       
  1062 }
       
  1063 
       
  1064 void TrkDevice::emitLogMessage(const QString &msg)
       
  1065 {
       
  1066     if (d->verbose)
       
  1067         qDebug("%s\n", qPrintable(msg));
       
  1068     emit logMessage(msg);
       
  1069 }
       
  1070 
       
  1071 } // namespace trk
       
  1072 
       
  1073 #include "trkdevice.moc"