diff --git a/src/shared/symbianutils/tcftrkdevice.cpp b/src/shared/symbianutils/tcftrkdevice.cpp index 202fb18a687a162e3150b4af644fbf2b06a37419..17d0fd8ee379e4537bd95109b492e3e23aeb94c5 100644 --- a/src/shared/symbianutils/tcftrkdevice.cpp +++ b/src/shared/symbianutils/tcftrkdevice.cpp @@ -29,6 +29,7 @@ #include "tcftrkdevice.h" #include "json.h" +#include "trkutils.h" #include <QtNetwork/QAbstractSocket> #include <QtCore/QDebug> @@ -40,7 +41,62 @@ enum { debug = 0 }; -static const char messageTerminatorC[] = "\003\001"; +static const char wlanMessageTerminatorC[] = "\003\001"; + +// Serial Ping: 0xfc,0x1f +static const char serialPingC[] = "\xfc\x1f"; +// Serial Pong: 0xfc,0xf1, followed by version info +static const char serialPongC[] = "\xfc\xf1"; + +static const char locatorAnswerC[] = "E\0Locator\0Hello\0[\"Locator\"]"; + +static const unsigned serialChunkLength = 0x400; // 1K max USB router +static const int maxSerialMessageLength = 0x10000; // give chunking scheme + +// Create USB router frame +static inline void encodeSerialFrame(const QByteArray &data, QByteArray *target) +{ + target->append(char(0x01)); + target->append(char(0x92)); // CODA serial message ID + appendShort(target, ushort(data.size()), trk::BigEndian); + target->append(data); +} + +// Split in chunks of 1K according to CODA protocol chunking +static inline QByteArray encodeUsbSerialMessage(const QByteArray &dataIn) +{ + static const int chunkSize = serialChunkLength - 2; // 2 Header bytes + const int size = dataIn.size(); + QByteArray frame; + // Do we need to split? + if (size < chunkSize) { // Nope, all happy. + frame.reserve(size + 4); + encodeSerialFrame(dataIn, &frame); + return frame; + } + // Split. + unsigned chunkCount = size / chunkSize; + if (size % chunkSize) + chunkCount++; + if (debug) + qDebug("Serial: Splitting message of %d bytes into %u chunks of %d", size, chunkCount, chunkSize); + + frame.reserve((4 + serialChunkLength) * chunkCount); + int pos = 0; + for (unsigned c = chunkCount - 1; pos < size ; c--) { + QByteArray chunk; // chunk with long message start/continuation code + chunk.reserve(serialChunkLength); + chunk.append(pos ? char(0) : char(0xfe)); + chunk.append(char(static_cast<unsigned char>(c))); // Avoid any signedness issues. + const int chunkEnd = qMin(pos + chunkSize, size); + chunk.append(dataIn.mid(pos, chunkEnd - pos)); + encodeSerialFrame(chunk, &frame); + pos = chunkEnd; + } + if (debug > 1) + qDebug("Serial chunked:\n%s", qPrintable(tcftrk::formatData(frame))); + return frame; +} namespace tcftrk { // ------------- TcfTrkCommandError @@ -272,11 +328,12 @@ struct TcfTrkDevicePrivate { TokenWrittenMessageMap m_writtenMessages; QVector<QByteArray> m_registerNames; QVector<QByteArray> m_fakeGetMRegisterValues; + bool m_serialFrame; }; TcfTrkDevicePrivate::TcfTrkDevicePrivate() : - m_messageTerminator(messageTerminatorC), - m_verbose(0), m_token(0) + m_messageTerminator(wlanMessageTerminatorC), + m_verbose(0), m_token(0), m_serialFrame(false) { } @@ -380,7 +437,12 @@ void TcfTrkDevice::slotDeviceSocketStateChanged() static inline QString debugMessage(QByteArray message, const char *prefix = 0) { - message.replace('\0', '|'); + const bool isBinary = !message.isEmpty() && message.at(0) < 0; + if (isBinary) { + message = message.toHex(); // Some serial special message + } else { + message.replace('\0', '|'); + } const QString messageS = QString::fromLatin1(message); return prefix ? (QLatin1String(prefix) + messageS) : messageS; @@ -388,30 +450,101 @@ static inline QString debugMessage(QByteArray message, const char *prefix = 0) void TcfTrkDevice::slotDeviceReadyRead() { - d->m_readBuffer += d->m_device->readAll(); + const QByteArray newData = d->m_device->readAll(); + d->m_readBuffer += newData; + if (debug) + qDebug("ReadBuffer: %s", qPrintable(trk::stringFromArray(newData))); + if (d->m_serialFrame) { + deviceReadyReadSerial(); + } else { + deviceReadyReadWLAN(); + } +} + +// Find a serial header in input stream '0x1', '0x92', 'lenH', 'lenL' +// and return message position and size. +static inline QPair<int, int> findSerialHeader(const QByteArray &in) +{ + const int size = in.size(); + const char header1 = 0x1; + const char header2 = char(0x92); + // Header should in theory always be at beginning of + // buffer. Warn if there are bogus data in-between. + for (int pos = 0; pos < size; ) { + if (pos + 4 < size && in.at(pos) == header1 && in.at(pos + 1) == header2) { + const int length = trk::extractShort(in.constData() + 2); + return QPair<int, int>(pos + 4, length); + } + // Find next + pos = in.indexOf(header1, pos + 1); + qWarning("Bogus data received on serial line: %s\n" + "Frame Header at: %d", qPrintable(trk::stringFromArray(in)), pos); + if (pos < 0) + break; + } + return QPair<int, int>(-1, -1); +} + +void TcfTrkDevice::deviceReadyReadSerial() +{ + do { + // Extract message (pos,len) + const QPair<int, int> messagePos = findSerialHeader(d->m_readBuffer); + if (messagePos.first < 0) + break; + // Do we have the complete message? + const int messageEnd = messagePos.first + messagePos.second; + if (messageEnd > d->m_readBuffer.size()) + break; + const QByteArray message = d->m_readBuffer.mid(messagePos.first, messagePos.second); + // Is thing a ping/pong response + if (debug > 1) + qDebug("Serial message: at %d (%d bytes) of %d: %s", + messagePos.first, messagePos.second, d->m_readBuffer.size(), + qPrintable(trk::stringFromArray(message))); + if (message.startsWith(serialPongC)) { + const QString version = QString::fromLatin1(message.mid(sizeof(serialPongC) - 1)); + emitLogMessage(QString::fromLatin1("Serial connection from '%1'").arg(version)); + emit serialPong(version); + // Answer with locator. + writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC))); + } else { + processMessage(message); + } + d->m_readBuffer.remove(0, messageEnd); + } while (d->m_readBuffer.isEmpty()); + checkSendQueue(); // Send off further messages +} + +void TcfTrkDevice::deviceReadyReadWLAN() +{ // Take complete message off front of readbuffer. do { - const int messageEndPos = d->m_readBuffer.indexOf(d->m_messageTerminator); + const int messageEndPos = d->m_readBuffer.indexOf(d->m_messageTerminator); if (messageEndPos == -1) break; if (messageEndPos == 0) { // TCF TRK 4.0.5 emits empty messages on errors. emitLogMessage(QString::fromLatin1("An empty TCF TRK message has been received.")); } else { - const QByteArray message = d->m_readBuffer.left(messageEndPos); - if (debug) - qDebug("Read %d bytes:\n%s", message.size(), qPrintable(formatData(message))); - if (const int errorCode = parseMessage(message)) { - emitLogMessage(QString::fromLatin1("Parse error %1 : %2"). - arg(errorCode).arg(debugMessage(message))); - if (debug) - qDebug("Parse error %d for %d bytes:\n%s", errorCode, - message.size(), qPrintable(formatData(message))); - } + processMessage(d->m_readBuffer.left(messageEndPos)); } d->m_readBuffer.remove(0, messageEndPos + d->m_messageTerminator.size()); } while (!d->m_readBuffer.isEmpty()); - checkSendQueue(); // Send off further message + checkSendQueue(); // Send off further messages +} + +void TcfTrkDevice::processMessage(const QByteArray &message) +{ + if (debug) + qDebug("Read %d bytes:\n%s", message.size(), qPrintable(formatData(message))); + if (const int errorCode = parseMessage(message)) { + emitLogMessage(QString::fromLatin1("Parse error %1 : %2"). + arg(errorCode).arg(debugMessage(message))); + if (debug) + qDebug("Parse error %d for %d bytes:\n%s", errorCode, + message.size(), qPrintable(formatData(message))); + } } // Split \0-terminated message into tokens, skipping the initial type character @@ -551,8 +684,6 @@ int TcfTrkDevice::parseTcfCommandReply(char type, const QVector<QByteArray> &tok return 0; } -static const char locatorAnswerC[] = "E\0Locator\0Hello\0[\"Locator\"]"; - int TcfTrkDevice::parseTcfEvent(const QVector<QByteArray> &tokens) { // Event: Ignore the periodical heartbeat event, answer 'Hello', @@ -572,9 +703,10 @@ int TcfTrkDevice::parseTcfEvent(const QVector<QByteArray> &tokens) // Parse known events, emit signals QScopedPointer<TcfTrkEvent> knownEvent(TcfTrkEvent::parseEvent(service, tokens.at(1), values)); if (!knownEvent.isNull()) { - // Answer hello event. + // Answer hello event (WLAN) if (knownEvent->type() == TcfTrkEvent::LocatorHello) - writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC))); + if (!d->m_serialFrame) + writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC))); emit tcfEvent(*knownEvent); } emit genericTcfEvent(service, tokens.at(1), values); @@ -600,6 +732,16 @@ unsigned TcfTrkDevice::verbose() const return d->m_verbose; } +bool TcfTrkDevice::serialFrame() const +{ + return d->m_serialFrame; +} + +void TcfTrkDevice::setSerialFrame(bool s) +{ + d->m_serialFrame = s; +} + void TcfTrkDevice::setVerbose(unsigned v) { d->m_verbose = v; @@ -625,6 +767,17 @@ bool TcfTrkDevice::checkOpen() return true; } +void TcfTrkDevice::sendSerialPing() +{ + if (!checkOpen()) + return; + + setSerialFrame(true); + writeMessage(QByteArray(serialPingC, qstrlen(serialPingC)), false); + if (d->m_verbose) + emitLogMessage(QLatin1String("Ping...")); +} + void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const char *command, const char *commandParameters, // may contain '\0' int commandParametersLength, @@ -673,18 +826,29 @@ void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const cha } // Enclose in message frame and write. -void TcfTrkDevice::writeMessage(QByteArray data) +void TcfTrkDevice::writeMessage(QByteArray data, bool ensureTerminating0) { if (!checkOpen()) return; + if (d->m_serialFrame && data.size() > maxSerialMessageLength) { + qCritical("Attempt to send large message (%d bytes) exceeding the " + "limit of %d bytes over serial channel. Skipping.", + data.size(), maxSerialMessageLength); + return; + } + if (d->m_verbose) emitLogMessage(debugMessage(data, "TCF <-")); // Ensure \0-termination which easily gets lost in QByteArray CT. - if (!data.endsWith('\0')) + if (ensureTerminating0 && !data.endsWith('\0')) data.append('\0'); - data += d->m_messageTerminator; + if (d->m_serialFrame) { + data = encodeUsbSerialMessage(data); + } else { + data += d->m_messageTerminator; + } if (debug > 1) qDebug("Writing:\n%s", qPrintable(formatData(data))); diff --git a/src/shared/symbianutils/tcftrkdevice.h b/src/shared/symbianutils/tcftrkdevice.h index d8cfa14f453f00bc77fa44658c9d876c0bd2a0d9..4ab5fbbda01784c2c1fee28fd907c6244f2d6e38 100644 --- a/src/shared/symbianutils/tcftrkdevice.h +++ b/src/shared/symbianutils/tcftrkdevice.h @@ -132,11 +132,20 @@ http://dev.eclipse.org/svnroot/dsdp/org.eclipse.tm.tcf/trunk/docs/TCF%20Services * single commands. As soon as 'Registers::getm' is natively supported, all code * related to 'FakeRegisterGetm' should be removed. The workaround requires that * the register name is known. -*/ + * CODA notes: + * - Commands are accepted only after receiving the Locator Hello event + * - Serial communication initiation sequence: + * Send serial ping from host sendSerialPing() -> receive pong response with + * version information -> Send Locator Hello Event -> Receive Locator Hello Event + * -> Commands are accepted. + * - WLAN communication initiation sequence: + * Receive Locator Hello Event from CODA -> Commands are accepted. + */ class SYMBIANUTILS_EXPORT TcfTrkDevice : public QObject { Q_PROPERTY(unsigned verbose READ verbose WRITE setVerbose) + Q_PROPERTY(bool serialFrame READ serialFrame WRITE setSerialFrame) Q_OBJECT public: // Flags for FileSystem:open @@ -163,6 +172,8 @@ public: virtual ~TcfTrkDevice(); unsigned verbose() const; + bool serialFrame() const; + void setSerialFrame(bool); // Mapping of register names to indices for multi-requests. // Register names can be retrieved via 'Registers:getChildren' (requires @@ -174,6 +185,9 @@ public: IODevicePtr takeDevice(); void setDevice(const IODevicePtr &dp); + // Serial Only: Initiate communication. Will emit serialPong() signal with version. + void sendSerialPing(); + // Send with parameters from string (which may contain '\0'). void sendTcfTrkMessage(MessageType mt, Services service, const char *command, @@ -338,6 +352,7 @@ public: signals: void genericTcfEvent(int service, const QByteArray &name, const QVector<tcftrk::JsonValue> &value); void tcfEvent(const tcftrk::TcfTrkEvent &knownEvent); + void serialPong(const QString &codaVersion); void logMessage(const QString &); void error(const QString &); @@ -351,11 +366,15 @@ private slots: void slotDeviceReadyRead(); private: + void deviceReadyReadSerial(); + void deviceReadyReadWLAN(); + bool checkOpen(); void checkSendQueue(); - void writeMessage(QByteArray data); + void writeMessage(QByteArray data, bool ensureTerminating0 = true); void emitLogMessage(const QString &); - int parseMessage(const QByteArray &); + inline int parseMessage(const QByteArray &); + void processMessage(const QByteArray &message); int parseTcfCommandReply(char type, const QVector<QByteArray> &tokens); int parseTcfEvent(const QVector<QByteArray> &tokens); // Send with parameters from string (which may contain '\0'). diff --git a/tests/tools/codaclient/codaclient.pro b/tests/tools/codaclient/codaclient.pro index 0744400300aa9ba6b8b40975cae559b2349fb2d2..3c579b4342dbae01dbb276ade84936e8c39a9ad3 100644 --- a/tests/tools/codaclient/codaclient.pro +++ b/tests/tools/codaclient/codaclient.pro @@ -1,5 +1,8 @@ DEFINES += SYMBIANUTILS_INCLUDE_PRI + +include(../../../qtcreator.pri) include(../../../src/shared/symbianutils/symbianutils.pri) +# include(../../../src/libs/3rdparty/qextserialport/qextserialport.pri) QT += core gui network TARGET = codaclient diff --git a/tests/tools/codaclient/codaclientapplication.cpp b/tests/tools/codaclient/codaclientapplication.cpp index 7b81df976a03ac1e5dbaea09288b39a9098f357f..aa12dd010a97ec62304f080f90356e06c59d2df7 100644 --- a/tests/tools/codaclient/codaclientapplication.cpp +++ b/tests/tools/codaclient/codaclientapplication.cpp @@ -29,6 +29,10 @@ #include "codaclientapplication.h" +#ifdef HAS_SERIALPORT +# include <qextserialport/qextserialport.h> +#endif + #include "tcftrkdevice.h" #include <QtNetwork/QTcpSocket> #include <QtCore/QFile> @@ -255,31 +259,27 @@ bool CodaClientApplication::start() switch (m_mode) { case Launch: { const QString args = m_launchArgs.join(QString(QLatin1Char(' '))); - std::printf("Launching 0x%x '%s '%s' on %s:%hu (debug: %d)\n", + std::printf("Launching 0x%x '%s '%s' (debug: %d)\n", m_launchUID, qPrintable(m_launchBinary), - qPrintable(args), qPrintable(m_address), m_port, - m_launchDebug); + qPrintable(args), m_launchDebug); } break; case Install: - std::printf("Installing '%s' to '%s' on %s:%hu\n", - qPrintable(m_installSisFile), qPrintable(m_installTargetDrive), - qPrintable(m_address), m_port); + std::printf("Installing '%s' to '%s'\n", + qPrintable(m_installSisFile), qPrintable(m_installTargetDrive)); break; case Put: - std::printf("Copying '%s' to '%s' on %s:%hu in chunks of %lluKB\n", + std::printf("Copying '%s' to '%s' in chunks of %lluKB\n", qPrintable(m_putLocalFile), qPrintable(m_putRemoteFile), - qPrintable(m_address), m_port, m_putChunkSize / 1024); + m_putChunkSize / 1024); break; case Stat: - std::printf("Retrieving attributes of '%s' from %s:%hu\n", - qPrintable(m_statRemoteFile), qPrintable(m_address), m_port); + std::printf("Retrieving attributes of '%s'\n", qPrintable(m_statRemoteFile)); break; case Invalid: break; } // Start connection - const QSharedPointer<QTcpSocket> tcfTrkSocket(new QTcpSocket); m_trkDevice.reset(new tcftrk::TcfTrkDevice); m_trkDevice->setVerbose(m_verbose); connect(m_trkDevice.data(), SIGNAL(error(QString)), @@ -288,9 +288,48 @@ bool CodaClientApplication::start() this, SLOT(slotTrkLogMessage(QString))); connect(m_trkDevice.data(), SIGNAL(tcfEvent(tcftrk::TcfTrkEvent)), this, SLOT(slotTcftrkEvent(tcftrk::TcfTrkEvent))); - m_trkDevice->setDevice(tcfTrkSocket); - tcfTrkSocket->connectToHost(m_address, m_port); - std::printf("Connecting...\n"); + if (m_address.startsWith(QLatin1String("/dev")) + || m_address.startsWith(QLatin1String("com"), Qt::CaseInsensitive) + || m_address.startsWith(QLatin1Char('\\'))) { +#ifdef HAS_SERIALPORT + // Serial +#ifdef Q_OS_WIN + const QString fullPort = QextSerialPort::fullPortNameWin(m_address); +#else + const QString fullPort = m_address; +#endif + const QSharedPointer<QextSerialPort> + serialPort(new QextSerialPort(fullPort, QextSerialPort::EventDriven)); + std::printf("Opening port %s...\n", qPrintable(fullPort)); + + // Magic USB serial parameters + serialPort->setTimeout(2000); + serialPort->setBaudRate(BAUD115200); + serialPort->setFlowControl(FLOW_OFF); + serialPort->setParity(PAR_NONE); + serialPort->setDataBits(DATA_8); + serialPort->setStopBits(STOP_1); + + m_trkDevice->setSerialFrame(true); + m_trkDevice->setDevice(serialPort); // Grab all data from start + if (!serialPort->open(QIODevice::ReadWrite|QIODevice::Unbuffered)) { + std::fprintf(stderr, "Cannot open port: %s", qPrintable(serialPort->errorString())); + return false; + } + // Initiate communication + m_trkDevice->sendSerialPing(); + serialPort->flush(); +#else + std::fprintf(stderr, "Not implemented\n"); + return false; +#endif + } else { + // TCP/IP + const QSharedPointer<QTcpSocket> tcfTrkSocket(new QTcpSocket); + m_trkDevice->setDevice(tcfTrkSocket); + tcfTrkSocket->connectToHost(m_address, m_port); + std::printf("Connecting to %s:%hu...\n", qPrintable(m_address), m_port); + } return true; } @@ -399,8 +438,7 @@ void CodaClientApplication::handleFileSystemFStat(const tcftrk::TcfTrkCommandRes } else { std::fprintf(stderr, "FStat failed: %s\n", qPrintable(result.toString())); } - m_trkDevice->sendFileSystemCloseCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleFileSystemClose), - m_remoteFileHandle); + closeRemoteFile(); } void CodaClientApplication::handleFileSystemClose(const tcftrk::TcfTrkCommandResult &result) @@ -490,12 +528,14 @@ void CodaClientApplication::doExit(int ex) if (!m_trkDevice.isNull()) { const QSharedPointer<QIODevice> dev = m_trkDevice->device(); - if (QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(dev.data())) { - if (socket->state() == QAbstractSocket::ConnectedState) - socket->disconnectFromHost(); - } else { - if (dev->isOpen()) - dev->close(); + if (!dev.isNull()) { + if (QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(dev.data())) { + if (socket->state() == QAbstractSocket::ConnectedState) + socket->disconnectFromHost(); + } else { + if (dev->isOpen()) + dev->close(); + } } } std::printf("Exiting (%d)\n", ex);