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

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

trunk: Merged in qt 4.6.1 sources.

  • Property svn:eol-style set to native
File size: 32.0 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 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
102namespace 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. */
112struct 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
124TrkMessage::TrkMessage(byte c, byte t, TrkCallback cb) :
125 code(c),
126 token(t),
127 callback(cb)
128{
129}
130
131} // namespace trk
132
133Q_DECLARE_METATYPE(trk::TrkMessage)
134Q_DECLARE_METATYPE(trk::TrkResult)
135
136namespace 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
148class TrkWriteQueue
149{
150 Q_DISABLE_COPY(TrkWriteQueue)
151public:
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
185private:
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
196TrkWriteQueue::TrkWriteQueue() :
197 m_trkWriteToken(0),
198 m_trkWriteBusy(false)
199{
200}
201
202byte TrkWriteQueue::nextTrkWriteToken()
203{
204 ++m_trkWriteToken;
205 if (m_trkWriteToken == 0)
206 ++m_trkWriteToken;
207 return m_trkWriteToken;
208}
209
210void 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
221TrkWriteQueue::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
239void 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
249void 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
267void 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
293void 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
306struct 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
319DeviceContext::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
337class WriterThread : public QThread {
338 Q_OBJECT
339 Q_DISABLE_COPY(WriterThread)
340public:
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
353signals:
354 void error(const QString &);
355 void internalNoopMessageDequeued(const trk::TrkMessage&);
356
357public slots:
358 bool trkWriteRawMessage(const TrkMessage &msg);
359 void terminate();
360 void tryWrite();
361
362private slots:
363 void invokeNoopMessage(const trk::TrkMessage &);
364
365private:
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
377WriterThread::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
387void WriterThread::run()
388{
389 while (writePendingMessage() == 0) ;
390}
391
392int 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
437void WriterThread::invokeNoopMessage(const trk::TrkMessage &msg)
438{
439 TrkWriteQueue::invokeNoopMessage(msg);
440}
441
442void 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
452static inline QString msgTerminated(int size)
453{
454 return QString::fromLatin1("Terminated with %1 bytes pending.").arg(size);
455}
456
457// Interruptible synchronous write function.
458static 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
500bool 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
519bool 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
531void WriterThread::tryWrite()
532{
533 m_waitCondition.wakeAll();
534}
535
536void 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
545void 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.
554void 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
569class ReaderThreadBase : public QThread {
570 Q_OBJECT
571 Q_DISABLE_COPY(ReaderThreadBase)
572public:
573
574signals:
575 void messageReceived(const trk::TrkResult &result, const QByteArray &rawData);
576
577protected:
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
584private:
585 void readMessages();
586
587 QByteArray m_trkReadBuffer;
588};
589
590ReaderThreadBase::ReaderThreadBase(const QSharedPointer<DeviceContext> &context) :
591 m_context(context)
592{
593 static const int trkResultMetaId = qRegisterMetaType<trk::TrkResult>();
594 Q_UNUSED(trkResultMetaId)
595}
596
597void ReaderThreadBase::processData(const QByteArray &a)
598{
599 m_trkReadBuffer += a;
600 readMessages();
601}
602
603void ReaderThreadBase::processData(char c)
604{
605 m_trkReadBuffer += c;
606 if (m_trkReadBuffer.size() > 1)
607 readMessages();
608}
609
610void 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
628class WinReaderThread : public ReaderThreadBase {
629 Q_OBJECT
630 Q_DISABLE_COPY(WinReaderThread)
631public:
632 explicit WinReaderThread(const QSharedPointer<DeviceContext> &context);
633 ~WinReaderThread();
634
635 virtual void run();
636
637signals:
638 void error(const QString &);
639
640public slots:
641 void terminate();
642
643private:
644 enum Handles { FileHandle, TerminateEventHandle, HandleCount };
645
646 inline int tryRead();
647
648 HANDLE m_handles[HandleCount];
649};
650
651WinReaderThread::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
658WinReaderThread::~WinReaderThread()
659{
660 CloseHandle(m_handles[TerminateEventHandle]);
661}
662
663// Return 0 to continue or error code
664int 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
712void WinReaderThread::run()
713{
714 m_handles[FileHandle] = m_context->readOverlapped.hEvent;
715 while ( tryRead() == 0) ;
716}
717
718void WinReaderThread::terminate()
719{
720 SetEvent(m_handles[TerminateEventHandle]);
721 wait();
722}
723
724typedef 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
738static 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
743class UnixReaderThread : public ReaderThreadBase {
744 Q_OBJECT
745 Q_DISABLE_COPY(UnixReaderThread)
746public:
747 explicit UnixReaderThread(const QSharedPointer<DeviceContext> &context);
748 ~UnixReaderThread();
749
750 virtual void run();
751
752signals:
753 void error(const QString &);
754
755public slots:
756 void terminate();
757
758private:
759 inline int tryRead();
760
761 int m_terminatePipeFileDescriptors[2];
762};
763
764UnixReaderThread::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
773UnixReaderThread::~UnixReaderThread()
774{
775 close(m_terminatePipeFileDescriptors[0]);
776 close(m_terminatePipeFileDescriptors[1]);
777}
778
779int 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
827void UnixReaderThread::run()
828{
829 // Read loop
830 while (tryRead() == 0)
831 ;
832}
833
834void 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
842typedef UnixReaderThread ReaderThread;
843
844#endif
845
846///////////////////////////////////////////////////////////////////////
847//
848// TrkDevicePrivate
849//
850///////////////////////////////////////////////////////////////////////
851
852struct 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
871TrkDevicePrivate::TrkDevicePrivate() :
872 deviceContext(new DeviceContext),
873 verbose(0)
874{
875}
876
877///////////////////////////////////////////////////////////////////////
878//
879// TrkDevice
880//
881///////////////////////////////////////////////////////////////////////
882
883TrkDevice::TrkDevice(QObject *parent) :
884 QObject(parent),
885 d(new TrkDevicePrivate)
886{}
887
888TrkDevice::~TrkDevice()
889{
890 close();
891 delete d;
892}
893
894bool 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
966void 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
987bool 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
996QString TrkDevice::errorString() const
997{
998 return d->errorString;
999}
1000
1001bool TrkDevice::serialFrame() const
1002{
1003 return d->deviceContext->serialFrame;
1004}
1005
1006void TrkDevice::setSerialFrame(bool f)
1007{
1008 d->deviceContext->serialFrame = f;
1009}
1010
1011int TrkDevice::verbose() const
1012{
1013 return d->verbose;
1014}
1015
1016void TrkDevice::setVerbose(int b)
1017{
1018 d->verbose = b;
1019}
1020
1021void 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
1029void TrkDevice::emitError(const QString &s)
1030{
1031 d->errorString = s;
1032 qWarning("%s\n", qPrintable(s));
1033 emit error(s);
1034}
1035
1036void 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
1046void TrkDevice::sendTrkInitialPing()
1047{
1048 if (!d->writerThread.isNull())
1049 d->writerThread->queueTrkInitialPing();
1050}
1051
1052bool 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
1064void 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"
Note: See TracBrowser for help on using the repository browser.