source: trunk/tools/runonphone/trk/trkdevice.cpp@ 651

Last change on this file since 651 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

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