forked from qt-creator/qt-creator
		
	
		
			
	
	
		
			563 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			563 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|   | /**************************************************************************
 | ||
|  | ** | ||
|  | ** This file is part of Qt Creator | ||
|  | ** | ||
|  | ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). | ||
|  | ** | ||
|  | ** Contact: Nokia Corporation (qt-info@nokia.com) | ||
|  | ** | ||
|  | ** Commercial Usage | ||
|  | ** | ||
|  | ** Licensees holding valid Qt Commercial licenses may use this file in | ||
|  | ** accordance with the Qt Commercial License Agreement provided with the | ||
|  | ** Software or, alternatively, in accordance with the terms contained in | ||
|  | ** a written agreement between you and Nokia. | ||
|  | ** | ||
|  | ** GNU Lesser General Public License Usage | ||
|  | ** | ||
|  | ** Alternatively, this file may be used under the terms of the GNU Lesser | ||
|  | ** General Public License version 2.1 as published by the Free Software | ||
|  | ** Foundation and appearing in the file LICENSE.LGPL included in the | ||
|  | ** packaging of this file.  Please review the following information to | ||
|  | ** ensure the GNU Lesser General Public License version 2.1 requirements | ||
|  | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
 | ||
|  | ** | ||
|  | ** If you are unsure which license is appropriate for your use, please | ||
|  | ** contact the sales department at http://qt.nokia.com/contact.
 | ||
|  | ** | ||
|  | **************************************************************************/ | ||
|  | 
 | ||
|  | #include "trkdevicex.h"
 | ||
|  | #include "trkutils.h"
 | ||
|  | 
 | ||
|  | #include <QtCore/QString>
 | ||
|  | #include <QtCore/QDebug>
 | ||
|  | #include <QtCore/QQueue>
 | ||
|  | #include <QtCore/QHash>
 | ||
|  | #include <QtCore/QMap>
 | ||
|  | #include <QtCore/QSharedPointer>
 | ||
|  | 
 | ||
|  | #ifdef Q_OS_WIN
 | ||
|  | #  include <windows.h>
 | ||
|  | #else
 | ||
|  | #  include <QtCore/QFile>
 | ||
|  | 
 | ||
|  | #  include <stdio.h>
 | ||
|  | #  include <sys/ioctl.h>
 | ||
|  | #  include <termios.h>
 | ||
|  | #  include <errno.h>
 | ||
|  | #  include <string.h>
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | enum { TimerInterval = 100 }; | ||
|  | 
 | ||
|  | #ifdef Q_OS_WIN
 | ||
|  | 
 | ||
|  | // Format windows error from GetLastError() value: TODO: Use the one provided by the utisl lib.
 | ||
|  | QString winErrorMessage(unsigned long error) | ||
|  | { | ||
|  |     QString rc = QString::fromLatin1("#%1: ").arg(error); | ||
|  |     ushort *lpMsgBuf; | ||
|  | 
 | ||
|  |     const int len = FormatMessage( | ||
|  |             FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | ||
|  |             NULL, error, 0, (LPTSTR)&lpMsgBuf, 0, NULL); | ||
|  |     if (len) { | ||
|  |         rc = QString::fromUtf16(lpMsgBuf, len); | ||
|  |         LocalFree(lpMsgBuf); | ||
|  |     } else { | ||
|  |         rc += QString::fromLatin1("<unknown error>"); | ||
|  |     } | ||
|  |     return rc; | ||
|  | } | ||
|  | 
 | ||
|  | // Non-blocking replacement for win-api ReadFile function
 | ||
|  | BOOL WINAPI TryReadFile(HANDLE          hFile, | ||
|  |                         LPVOID          lpBuffer, | ||
|  |                         DWORD           nNumberOfBytesToRead, | ||
|  |                         LPDWORD         lpNumberOfBytesRead, | ||
|  |                         LPOVERLAPPED    lpOverlapped) | ||
|  | { | ||
|  |     COMSTAT comStat; | ||
|  |     if (!ClearCommError(hFile, NULL, &comStat)){ | ||
|  |         logMessage("ClearCommError() failed"); | ||
|  |         return FALSE; | ||
|  |     } | ||
|  |     if (comStat.cbInQue == 0) { | ||
|  |         *lpNumberOfBytesRead = 0; | ||
|  |         return FALSE; | ||
|  |     } | ||
|  |     return ReadFile(hFile, | ||
|  |                     lpBuffer, | ||
|  |                     qMin(comStat.cbInQue, nNumberOfBytesToRead), | ||
|  |                     lpNumberOfBytesRead, | ||
|  |                     lpOverlapped); | ||
|  | } | ||
|  | #endif
 | ||
|  | 
 | ||
|  | namespace trk { | ||
|  | 
 | ||
|  | ///////////////////////////////////////////////////////////////////////
 | ||
|  | //
 | ||
|  | // TrkMessage
 | ||
|  | //
 | ||
|  | ///////////////////////////////////////////////////////////////////////
 | ||
|  | 
 | ||
|  | /* A message to be send to TRK, triggering a callback on receipt
 | ||
|  |  * of the answer. */ | ||
|  | struct TrkMessage | ||
|  | { | ||
|  |     typedef TrkFunctor1<const TrkResult &> Callback; | ||
|  | 
 | ||
|  |     explicit TrkMessage(byte code = 0u, byte token = 0u, | ||
|  |                         Callback callback = Callback()); | ||
|  | 
 | ||
|  |     byte code; | ||
|  |     byte token; | ||
|  |     QByteArray data; | ||
|  |     QVariant cookie; | ||
|  |     Callback callback; | ||
|  |     bool invokeOnNAK; | ||
|  | }; | ||
|  | 
 | ||
|  | TrkMessage::TrkMessage(byte c, byte t, Callback cb) : | ||
|  |     code(c), | ||
|  |     token(t), | ||
|  |     callback(cb), | ||
|  |     invokeOnNAK(false) | ||
|  | { | ||
|  | } | ||
|  | 
 | ||
|  | ///////////////////////////////////////////////////////////////////////
 | ||
|  | //
 | ||
|  | // TrkWriteQueue
 | ||
|  | //
 | ||
|  | ///////////////////////////////////////////////////////////////////////
 | ||
|  | 
 | ||
|  | /* Mixin class that manages a write queue of Trk messages. */ | ||
|  | class TrkWriteQueue | ||
|  | { | ||
|  | public: | ||
|  |     typedef TrkDevice::Callback Callback;    | ||
|  | 
 | ||
|  |     TrkWriteQueue(); | ||
|  | 
 | ||
|  |     // Enqueue messages.
 | ||
|  |     void queueTrkMessage(byte code, Callback callback, | ||
|  |                         const QByteArray &data, const QVariant &cookie, | ||
|  |                         bool invokeOnNAK); | ||
|  |     void queueTrkInitialPing(); | ||
|  | 
 | ||
|  |     // Call this from the device read notification with the results.
 | ||
|  |     void slotHandleResult(const TrkResult &result); | ||
|  | 
 | ||
|  |     // This can be called periodically in a timer to retrieve
 | ||
|  |     // the pending messages to be sent.
 | ||
|  |     bool pendingMessage(TrkMessage *message); | ||
|  |     // Notify the queue about the success of the write operation
 | ||
|  |     // after taking the pendingMessage off.
 | ||
|  |     void notifyWriteResult(bool ok); | ||
|  | 
 | ||
|  | private: | ||
|  |     typedef QMap<byte, TrkMessage> TokenMessageMap; | ||
|  | 
 | ||
|  |     byte nextTrkWriteToken(); | ||
|  | 
 | ||
|  |     byte trkWriteToken; | ||
|  |     QQueue<TrkMessage> trkWriteQueue; | ||
|  |     TokenMessageMap writtenTrkMessages; | ||
|  |     bool trkWriteBusy; | ||
|  | }; | ||
|  | 
 | ||
|  | TrkWriteQueue::TrkWriteQueue() : | ||
|  |     trkWriteToken(0), | ||
|  |     trkWriteBusy(false) | ||
|  | { | ||
|  | } | ||
|  | 
 | ||
|  | byte TrkWriteQueue::nextTrkWriteToken() | ||
|  | { | ||
|  |     ++trkWriteToken; | ||
|  |     if (trkWriteToken == 0) | ||
|  |         ++trkWriteToken; | ||
|  |     return trkWriteToken; | ||
|  | } | ||
|  | 
 | ||
|  | void TrkWriteQueue::queueTrkMessage(byte code, Callback callback, | ||
|  |     const QByteArray &data, const QVariant &cookie, bool invokeOnNAK) | ||
|  | { | ||
|  |     const byte token = code == TRK_WRITE_QUEUE_NOOP_CODE ? | ||
|  |                                 byte(0) : nextTrkWriteToken(); | ||
|  |     TrkMessage msg(code, token, callback); | ||
|  |     msg.data = data; | ||
|  |     msg.cookie = cookie; | ||
|  |     msg.invokeOnNAK = invokeOnNAK; | ||
|  |     trkWriteQueue.append(msg); | ||
|  | } | ||
|  | 
 | ||
|  | bool TrkWriteQueue::pendingMessage(TrkMessage *message) | ||
|  | { | ||
|  |     // Invoked from timer, try to flush out message queue
 | ||
|  |     if (trkWriteBusy || trkWriteQueue.isEmpty()) | ||
|  |         return false; | ||
|  |     // Handle the noop message, just invoke CB
 | ||
|  |     if (trkWriteQueue.front().code == TRK_WRITE_QUEUE_NOOP_CODE) { | ||
|  |         TrkMessage noopMessage = trkWriteQueue.dequeue(); | ||
|  |         if (noopMessage.callback) { | ||
|  |             TrkResult result; | ||
|  |             result.code = noopMessage.code; | ||
|  |             result.token = noopMessage.token; | ||
|  |             result.data = noopMessage.data; | ||
|  |             result.cookie = noopMessage.cookie; | ||
|  |             noopMessage.callback(result); | ||
|  |         } | ||
|  |     } | ||
|  |     // Check again for real messages
 | ||
|  |     if (trkWriteQueue.isEmpty()) | ||
|  |         return false; | ||
|  |     if (message) | ||
|  |         *message = trkWriteQueue.front(); | ||
|  |     return true; | ||
|  | } | ||
|  | 
 | ||
|  | void TrkWriteQueue::notifyWriteResult(bool ok) | ||
|  | { | ||
|  |     // On success, dequeue message and await result
 | ||
|  |     if (ok) {         | ||
|  |         TrkMessage firstMsg = trkWriteQueue.dequeue(); | ||
|  |         writtenTrkMessages.insert(firstMsg.token, firstMsg); | ||
|  |         trkWriteBusy = true; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void TrkWriteQueue::slotHandleResult(const TrkResult &result) | ||
|  | { | ||
|  |     trkWriteBusy = false; | ||
|  |     if (result.code != TrkNotifyAck && result.code != TrkNotifyNak) | ||
|  |         return; | ||
|  |     // Find which request the message belongs to and invoke callback
 | ||
|  |     // if ACK or on NAK if desired.
 | ||
|  |     const TokenMessageMap::iterator it = writtenTrkMessages.find(result.token); | ||
|  |     if (it == writtenTrkMessages.end()) | ||
|  |         return; | ||
|  |     const bool invokeCB = it.value().callback | ||
|  |                           && (result.code == TrkNotifyAck || it.value().invokeOnNAK); | ||
|  | 
 | ||
|  |     if (invokeCB) { | ||
|  |         TrkResult result1 = result; | ||
|  |         result1.cookie = it.value().cookie; | ||
|  |         it.value().callback(result1); | ||
|  |     } | ||
|  |     writtenTrkMessages.erase(it); | ||
|  | } | ||
|  | 
 | ||
|  | void TrkWriteQueue::queueTrkInitialPing() | ||
|  | { | ||
|  |     // Ping, reset sequence count
 | ||
|  |     trkWriteQueue.append(TrkMessage(0, 0)); | ||
|  | } | ||
|  | 
 | ||
|  | struct TrkDevicePrivate { | ||
|  |     TrkDevicePrivate(); | ||
|  | #ifdef Q_OS_WIN
 | ||
|  |     HANDLE hdevice; | ||
|  | #else
 | ||
|  |     QFile file; | ||
|  | #endif
 | ||
|  | 
 | ||
|  |     QByteArray trkReadBuffer; | ||
|  |     bool trkWriteBusy; | ||
|  |     int timerId; | ||
|  |     bool serialFrame; | ||
|  |     bool verbose; | ||
|  |     QString errorString; | ||
|  | }; | ||
|  | 
 | ||
|  | ///////////////////////////////////////////////////////////////////////
 | ||
|  | //
 | ||
|  | // TrkDevice
 | ||
|  | //
 | ||
|  | ///////////////////////////////////////////////////////////////////////
 | ||
|  | 
 | ||
|  | TrkDevicePrivate::TrkDevicePrivate() : | ||
|  | #ifdef Q_OS_WIN
 | ||
|  |     hdevice(INVALID_HANDLE_VALUE), | ||
|  | #endif
 | ||
|  |     trkWriteBusy(false), | ||
|  |     timerId(-1), | ||
|  |     serialFrame(true), | ||
|  |     verbose(false) | ||
|  | { | ||
|  | } | ||
|  | 
 | ||
|  | ///////////////////////////////////////////////////////////////////////
 | ||
|  | //
 | ||
|  | // TrkDevice
 | ||
|  | //
 | ||
|  | ///////////////////////////////////////////////////////////////////////
 | ||
|  | 
 | ||
|  | TrkDevice::TrkDevice(QObject *parent) : | ||
|  |     QObject(parent), | ||
|  |     d(new TrkDevicePrivate), | ||
|  |     qd(new TrkWriteQueue) | ||
|  | { | ||
|  |     connect(this, SIGNAL(messageReceived(trk::TrkResult)), | ||
|  |         this, SLOT(slotHandleResult(trk::TrkResult))); | ||
|  | } | ||
|  | 
 | ||
|  | bool TrkDevice::open(const QString &port, QString *errorMessage) | ||
|  | { | ||
|  |     close(); | ||
|  | #ifdef Q_OS_WIN
 | ||
|  |     d->hdevice = CreateFile(port.toStdWString().c_str(), | ||
|  |                            GENERIC_READ | GENERIC_WRITE, | ||
|  |                            0, | ||
|  |                            NULL, | ||
|  |                            OPEN_EXISTING, | ||
|  |                            FILE_ATTRIBUTE_NORMAL, | ||
|  |                            NULL); | ||
|  | 
 | ||
|  |     if (INVALID_HANDLE_VALUE == d->hdevice) { | ||
|  |         *errorMessage = QString::fromLatin1("Could not open device '%1': %2").arg(port, winErrorMessage(GetLastError())); | ||
|  |         return false; | ||
|  |     } | ||
|  |     d->timerId = startTimer(TimerInterval); | ||
|  |     return true; | ||
|  | #else
 | ||
|  |     d->file.setFileName(port); | ||
|  |     if (!d->file.open(QIODevice::ReadWrite|QIODevice::Unbuffered)) { | ||
|  |         *errorMessage = QString::fromLatin1("Cannot open %1: %2").arg(port, d->file.errorString()); | ||
|  |         return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     struct termios termInfo; | ||
|  |     if (tcgetattr(d->file.handle(), &termInfo) < 0) { | ||
|  |         *errorMessage = QString::fromLatin1("Unable to retrieve terminal settings: %1 %2").arg(errno).arg(QString::fromAscii(strerror(errno))); | ||
|  |         return false; | ||
|  |     } | ||
|  |     // Turn off terminal echo as not get messages back, among other things
 | ||
|  |     termInfo.c_cflag |= CREAD|CLOCAL; | ||
|  |     termInfo.c_lflag &= (~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG)); | ||
|  |     termInfo.c_iflag &= (~(INPCK|IGNPAR|PARMRK|ISTRIP|ICRNL|IXANY)); | ||
|  |     termInfo.c_oflag &= (~OPOST); | ||
|  |     termInfo.c_cc[VMIN]  = 0; | ||
|  |     termInfo.c_cc[VINTR] = _POSIX_VDISABLE; | ||
|  |     termInfo.c_cc[VQUIT] = _POSIX_VDISABLE; | ||
|  |     termInfo.c_cc[VSTART] = _POSIX_VDISABLE; | ||
|  |     termInfo.c_cc[VSTOP] = _POSIX_VDISABLE; | ||
|  |     termInfo.c_cc[VSUSP] = _POSIX_VDISABLE; | ||
|  |     if (tcsetattr(d->file.handle(), TCSAFLUSH, &termInfo) < 0) { | ||
|  |         *errorMessage = QString::fromLatin1("Unable to apply terminal settings: %1 %2").arg(errno).arg(QString::fromAscii(strerror(errno))); | ||
|  |         return false; | ||
|  |     } | ||
|  |     d->timerId = startTimer(TimerInterval); | ||
|  |     return true; | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | TrkDevice::~TrkDevice() | ||
|  | { | ||
|  |     close(); | ||
|  |     delete d; | ||
|  |     delete qd; | ||
|  | } | ||
|  | 
 | ||
|  | void TrkDevice::close() | ||
|  | { | ||
|  |     if (!isOpen()) | ||
|  |         return; | ||
|  |     if (d->timerId != -1) { | ||
|  |         killTimer(d->timerId); | ||
|  |         d->timerId = -1; | ||
|  |     } | ||
|  | #ifdef Q_OS_WIN
 | ||
|  |     CloseHandle(d->hdevice); | ||
|  |     d->hdevice = INVALID_HANDLE_VALUE; | ||
|  | #else
 | ||
|  |     d->file.close(); | ||
|  | #endif
 | ||
|  |     if (verbose()) | ||
|  |         logMessage("Close"); | ||
|  | } | ||
|  | 
 | ||
|  | bool TrkDevice::isOpen() const | ||
|  | { | ||
|  | #ifdef Q_OS_WIN
 | ||
|  |     return d->hdevice != INVALID_HANDLE_VALUE; | ||
|  | #else
 | ||
|  |     return d->file.isOpen(); | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | QString TrkDevice::errorString() const | ||
|  | { | ||
|  |     return d->errorString; | ||
|  | } | ||
|  | 
 | ||
|  | bool TrkDevice::serialFrame() const | ||
|  | { | ||
|  |     return d->serialFrame; | ||
|  | } | ||
|  | 
 | ||
|  | void TrkDevice::setSerialFrame(bool f) | ||
|  | { | ||
|  |     d->serialFrame = f; | ||
|  | } | ||
|  | 
 | ||
|  | bool TrkDevice::verbose() const | ||
|  | { | ||
|  |     return true || d->verbose; | ||
|  | } | ||
|  | 
 | ||
|  | void TrkDevice::setVerbose(bool b) | ||
|  | { | ||
|  |     d->verbose = b; | ||
|  | } | ||
|  | 
 | ||
|  | bool TrkDevice::write(const QByteArray &data, QString *errorMessage) | ||
|  | { | ||
|  | #ifdef Q_OS_WIN
 | ||
|  |     DWORD charsWritten; | ||
|  |     if (!WriteFile(d->hdevice, data.data(), data.size(), &charsWritten, NULL)) { | ||
|  |         *errorMessage = QString::fromLatin1("Error writing data: %1").arg(winErrorMessage(GetLastError())); | ||
|  |         return false; | ||
|  |     } | ||
|  |     FlushFileBuffers(d->hdevice); | ||
|  |     return true; | ||
|  | #else
 | ||
|  |     if (d->file.write(data) == -1 || !d->file.flush()) { | ||
|  |         *errorMessage = QString::fromLatin1("Cannot write: %1").arg(d->file.errorString()); | ||
|  |         return false; | ||
|  |     } | ||
|  |     return  true; | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | #ifndef Q_OS_WIN
 | ||
|  | static inline int bytesAvailable(int fileNo) | ||
|  | { | ||
|  |     int numBytes; | ||
|  |     const int rc = ioctl(fileNo, FIONREAD, &numBytes); | ||
|  |     if (rc < 0) | ||
|  |         numBytes=0; | ||
|  |     return numBytes; | ||
|  | } | ||
|  | #endif
 | ||
|  | 
 | ||
|  | void TrkDevice::tryTrkRead() | ||
|  | { | ||
|  | #ifdef Q_OS_WIN
 | ||
|  |     const DWORD BUFFERSIZE = 1024; | ||
|  |     char buffer[BUFFERSIZE]; | ||
|  |     DWORD charsRead; | ||
|  |     DWORD totalCharsRead = 0; | ||
|  | 
 | ||
|  |     while (TryReadFile(d->hdevice, buffer, BUFFERSIZE, &charsRead, NULL)) { | ||
|  |         totalCharsRead += charsRead; | ||
|  |         d->trkReadBuffer.append(buffer, charsRead); | ||
|  |         if (isValidTrkResult(d->trkReadBuffer, d->serialFrame)) | ||
|  |             break; | ||
|  |     } | ||
|  |     if (verbose() && totalCharsRead) | ||
|  |         logMessage("Read" + d->trkReadBuffer.toHex()); | ||
|  |     if (!totalCharsRead) | ||
|  |         return; | ||
|  |     const ushort len = isValidTrkResult(d->trkReadBuffer, d->serialFrame); | ||
|  |     if (!len) { | ||
|  |         const QString msg = QString::fromLatin1("Partial message: %1").arg(stringFromArray(d->trkReadBuffer)); | ||
|  |         emitError(msg); | ||
|  |         return; | ||
|  |     } | ||
|  | #else
 | ||
|  |     const int size = bytesAvailable(d->file.handle()); | ||
|  |     if (!size) | ||
|  |         return; | ||
|  |     const QByteArray data = d->file.read(size); | ||
|  |     if (verbose()) | ||
|  |         logMessage("trk: <- " + stringFromArray(data)); | ||
|  |     d->trkReadBuffer.append(data); | ||
|  |     const ushort len = isValidTrkResult(d->trkReadBuffer, d->serialFrame); | ||
|  |     if (!len) { | ||
|  |         if (d->trkReadBuffer.size() > 10) { | ||
|  |             const QString msg = QString::fromLatin1("Unable to extract message from '%1' '%2'"). | ||
|  |                              arg(QLatin1String(d->trkReadBuffer.toHex())).arg(QString::fromAscii(d->trkReadBuffer)); | ||
|  |             emitError(msg); | ||
|  |         } | ||
|  |         return; | ||
|  |     } | ||
|  | #endif // Q_OS_WIN
 | ||
|  |     TrkResult r; | ||
|  |     QByteArray rawData; | ||
|  |     while (extractResult(&d->trkReadBuffer, d->serialFrame, &r, &rawData)) { | ||
|  |         //if (verbose())
 | ||
|  |         //    logMessage("Read TrkResult " + r.data.toHex());
 | ||
|  |         emit messageReceived(r); | ||
|  |         if (!rawData.isEmpty()) | ||
|  |             emit rawDataReceived(rawData); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void TrkDevice::timerEvent(QTimerEvent *) | ||
|  | { | ||
|  |     tryTrkWrite(); | ||
|  |     tryTrkRead(); | ||
|  | } | ||
|  | 
 | ||
|  | void TrkDevice::emitError(const QString &s) | ||
|  | { | ||
|  |     d->errorString = s; | ||
|  |     qWarning("%s\n", qPrintable(s)); | ||
|  |     emit error(s); | ||
|  | } | ||
|  | 
 | ||
|  | void TrkDevice::sendTrkMessage(byte code, Callback callback, | ||
|  |      const QByteArray &data, const QVariant &cookie, bool invokeOnNAK) | ||
|  | { | ||
|  |     qd->queueTrkMessage(code, callback, data, cookie, invokeOnNAK); | ||
|  | } | ||
|  | 
 | ||
|  | void TrkDevice::sendTrkInitialPing() | ||
|  | { | ||
|  |     qd->queueTrkInitialPing(); | ||
|  | } | ||
|  | 
 | ||
|  | bool TrkDevice::sendTrkAck(byte token) | ||
|  | { | ||
|  |     // The acknowledgement must not be queued!
 | ||
|  |     TrkMessage msg(0x80, token); | ||
|  |     msg.token = token; | ||
|  |     msg.data.append('\0'); | ||
|  |     return trkWriteRawMessage(msg); | ||
|  |     // 01 90 00 07 7e 80 01 00 7d 5e 7e
 | ||
|  | } | ||
|  | 
 | ||
|  | void TrkDevice::tryTrkWrite() | ||
|  | { | ||
|  |     TrkMessage message; | ||
|  |     if (!qd->pendingMessage(&message)) | ||
|  |         return; | ||
|  |     const bool success = trkWriteRawMessage(message); | ||
|  |     qd->notifyWriteResult(success); | ||
|  | } | ||
|  | 
 | ||
|  | bool TrkDevice::trkWriteRawMessage(const TrkMessage &msg) | ||
|  | { | ||
|  |     const QByteArray ba = frameMessage(msg.code, msg.token, msg.data, serialFrame()); | ||
|  |     if (verbose()) | ||
|  |          logMessage("trk: -> " + stringFromArray(ba)); | ||
|  |     QString errorMessage; | ||
|  |     const bool rc = write(ba, &errorMessage); | ||
|  |     if (!rc) | ||
|  |         emitError(errorMessage); | ||
|  |     return rc; | ||
|  | } | ||
|  | 
 | ||
|  | void TrkDevice::slotHandleResult(const TrkResult &result) | ||
|  | { | ||
|  |     qd->slotHandleResult(result); | ||
|  | } | ||
|  | 
 | ||
|  | } // namespace trk
 | ||
|  | 
 |