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