source: trunk/tools/runonphone/symbianutils/trkdevice.cpp@ 769

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

trunk: Merged in qt 4.6.3 sources from branches/vendor/nokia/qt.

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