Commit d7ca39da authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Symbian/CODA: Add infrastructure for serial communication.

Add USB protocol and chunking.
parent c4e49138
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "tcftrkdevice.h" #include "tcftrkdevice.h"
#include "json.h" #include "json.h"
#include "trkutils.h"
#include <QtNetwork/QAbstractSocket> #include <QtNetwork/QAbstractSocket>
#include <QtCore/QDebug> #include <QtCore/QDebug>
...@@ -40,7 +41,62 @@ ...@@ -40,7 +41,62 @@
enum { debug = 0 }; 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 { namespace tcftrk {
// ------------- TcfTrkCommandError // ------------- TcfTrkCommandError
...@@ -272,11 +328,12 @@ struct TcfTrkDevicePrivate { ...@@ -272,11 +328,12 @@ struct TcfTrkDevicePrivate {
TokenWrittenMessageMap m_writtenMessages; TokenWrittenMessageMap m_writtenMessages;
QVector<QByteArray> m_registerNames; QVector<QByteArray> m_registerNames;
QVector<QByteArray> m_fakeGetMRegisterValues; QVector<QByteArray> m_fakeGetMRegisterValues;
bool m_serialFrame;
}; };
TcfTrkDevicePrivate::TcfTrkDevicePrivate() : TcfTrkDevicePrivate::TcfTrkDevicePrivate() :
m_messageTerminator(messageTerminatorC), m_messageTerminator(wlanMessageTerminatorC),
m_verbose(0), m_token(0) m_verbose(0), m_token(0), m_serialFrame(false)
{ {
} }
...@@ -380,7 +437,12 @@ void TcfTrkDevice::slotDeviceSocketStateChanged() ...@@ -380,7 +437,12 @@ void TcfTrkDevice::slotDeviceSocketStateChanged()
static inline QString debugMessage(QByteArray message, const char *prefix = 0) 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); const QString messageS = QString::fromLatin1(message);
return prefix ? return prefix ?
(QLatin1String(prefix) + messageS) : messageS; (QLatin1String(prefix) + messageS) : messageS;
...@@ -388,30 +450,101 @@ static inline QString debugMessage(QByteArray message, const char *prefix = 0) ...@@ -388,30 +450,101 @@ static inline QString debugMessage(QByteArray message, const char *prefix = 0)
void TcfTrkDevice::slotDeviceReadyRead() 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. // Take complete message off front of readbuffer.
do { do {
const int messageEndPos = d->m_readBuffer.indexOf(d->m_messageTerminator); const int messageEndPos = d->m_readBuffer.indexOf(d->m_messageTerminator);
if (messageEndPos == -1) if (messageEndPos == -1)
break; break;
if (messageEndPos == 0) { if (messageEndPos == 0) {
// TCF TRK 4.0.5 emits empty messages on errors. // TCF TRK 4.0.5 emits empty messages on errors.
emitLogMessage(QString::fromLatin1("An empty TCF TRK message has been received.")); emitLogMessage(QString::fromLatin1("An empty TCF TRK message has been received."));
} else { } else {
const QByteArray message = d->m_readBuffer.left(messageEndPos); processMessage(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)));
}
} }
d->m_readBuffer.remove(0, messageEndPos + d->m_messageTerminator.size()); d->m_readBuffer.remove(0, messageEndPos + d->m_messageTerminator.size());
} while (!d->m_readBuffer.isEmpty()); } 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 // Split \0-terminated message into tokens, skipping the initial type character
...@@ -551,8 +684,6 @@ int TcfTrkDevice::parseTcfCommandReply(char type, const QVector<QByteArray> &tok ...@@ -551,8 +684,6 @@ int TcfTrkDevice::parseTcfCommandReply(char type, const QVector<QByteArray> &tok
return 0; return 0;
} }
static const char locatorAnswerC[] = "E\0Locator\0Hello\0[\"Locator\"]";
int TcfTrkDevice::parseTcfEvent(const QVector<QByteArray> &tokens) int TcfTrkDevice::parseTcfEvent(const QVector<QByteArray> &tokens)
{ {
// Event: Ignore the periodical heartbeat event, answer 'Hello', // Event: Ignore the periodical heartbeat event, answer 'Hello',
...@@ -572,9 +703,10 @@ int TcfTrkDevice::parseTcfEvent(const QVector<QByteArray> &tokens) ...@@ -572,9 +703,10 @@ int TcfTrkDevice::parseTcfEvent(const QVector<QByteArray> &tokens)
// Parse known events, emit signals // Parse known events, emit signals
QScopedPointer<TcfTrkEvent> knownEvent(TcfTrkEvent::parseEvent(service, tokens.at(1), values)); QScopedPointer<TcfTrkEvent> knownEvent(TcfTrkEvent::parseEvent(service, tokens.at(1), values));
if (!knownEvent.isNull()) { if (!knownEvent.isNull()) {
// Answer hello event. // Answer hello event (WLAN)
if (knownEvent->type() == TcfTrkEvent::LocatorHello) if (knownEvent->type() == TcfTrkEvent::LocatorHello)
writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC))); if (!d->m_serialFrame)
writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC)));
emit tcfEvent(*knownEvent); emit tcfEvent(*knownEvent);
} }
emit genericTcfEvent(service, tokens.at(1), values); emit genericTcfEvent(service, tokens.at(1), values);
...@@ -600,6 +732,16 @@ unsigned TcfTrkDevice::verbose() const ...@@ -600,6 +732,16 @@ unsigned TcfTrkDevice::verbose() const
return d->m_verbose; 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) void TcfTrkDevice::setVerbose(unsigned v)
{ {
d->m_verbose = v; d->m_verbose = v;
...@@ -625,6 +767,17 @@ bool TcfTrkDevice::checkOpen() ...@@ -625,6 +767,17 @@ bool TcfTrkDevice::checkOpen()
return true; 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, void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const char *command,
const char *commandParameters, // may contain '\0' const char *commandParameters, // may contain '\0'
int commandParametersLength, int commandParametersLength,
...@@ -673,18 +826,29 @@ void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const cha ...@@ -673,18 +826,29 @@ void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const cha
} }
// Enclose in message frame and write. // Enclose in message frame and write.
void TcfTrkDevice::writeMessage(QByteArray data) void TcfTrkDevice::writeMessage(QByteArray data, bool ensureTerminating0)
{ {
if (!checkOpen()) if (!checkOpen())
return; 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) if (d->m_verbose)
emitLogMessage(debugMessage(data, "TCF <-")); emitLogMessage(debugMessage(data, "TCF <-"));
// Ensure \0-termination which easily gets lost in QByteArray CT. // Ensure \0-termination which easily gets lost in QByteArray CT.
if (!data.endsWith('\0')) if (ensureTerminating0 && !data.endsWith('\0'))
data.append('\0'); data.append('\0');
data += d->m_messageTerminator; if (d->m_serialFrame) {
data = encodeUsbSerialMessage(data);
} else {
data += d->m_messageTerminator;
}
if (debug > 1) if (debug > 1)
qDebug("Writing:\n%s", qPrintable(formatData(data))); qDebug("Writing:\n%s", qPrintable(formatData(data)));
......
...@@ -132,11 +132,20 @@ http://dev.eclipse.org/svnroot/dsdp/org.eclipse.tm.tcf/trunk/docs/TCF%20Services ...@@ -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 * single commands. As soon as 'Registers::getm' is natively supported, all code
* related to 'FakeRegisterGetm' should be removed. The workaround requires that * related to 'FakeRegisterGetm' should be removed. The workaround requires that
* the register name is known. * 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 class SYMBIANUTILS_EXPORT TcfTrkDevice : public QObject
{ {
Q_PROPERTY(unsigned verbose READ verbose WRITE setVerbose) Q_PROPERTY(unsigned verbose READ verbose WRITE setVerbose)
Q_PROPERTY(bool serialFrame READ serialFrame WRITE setSerialFrame)
Q_OBJECT Q_OBJECT
public: public:
// Flags for FileSystem:open // Flags for FileSystem:open
...@@ -163,6 +172,8 @@ public: ...@@ -163,6 +172,8 @@ public:
virtual ~TcfTrkDevice(); virtual ~TcfTrkDevice();
unsigned verbose() const; unsigned verbose() const;
bool serialFrame() const;
void setSerialFrame(bool);
// Mapping of register names to indices for multi-requests. // Mapping of register names to indices for multi-requests.
// Register names can be retrieved via 'Registers:getChildren' (requires // Register names can be retrieved via 'Registers:getChildren' (requires
...@@ -174,6 +185,9 @@ public: ...@@ -174,6 +185,9 @@ public:
IODevicePtr takeDevice(); IODevicePtr takeDevice();
void setDevice(const IODevicePtr &dp); 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'). // Send with parameters from string (which may contain '\0').
void sendTcfTrkMessage(MessageType mt, Services service, void sendTcfTrkMessage(MessageType mt, Services service,
const char *command, const char *command,
...@@ -338,6 +352,7 @@ public: ...@@ -338,6 +352,7 @@ public:
signals: signals:
void genericTcfEvent(int service, const QByteArray &name, const QVector<tcftrk::JsonValue> &value); void genericTcfEvent(int service, const QByteArray &name, const QVector<tcftrk::JsonValue> &value);
void tcfEvent(const tcftrk::TcfTrkEvent &knownEvent); void tcfEvent(const tcftrk::TcfTrkEvent &knownEvent);
void serialPong(const QString &codaVersion);
void logMessage(const QString &); void logMessage(const QString &);
void error(const QString &); void error(const QString &);
...@@ -351,11 +366,15 @@ private slots: ...@@ -351,11 +366,15 @@ private slots:
void slotDeviceReadyRead(); void slotDeviceReadyRead();
private: private:
void deviceReadyReadSerial();
void deviceReadyReadWLAN();
bool checkOpen(); bool checkOpen();
void checkSendQueue(); void checkSendQueue();
void writeMessage(QByteArray data); void writeMessage(QByteArray data, bool ensureTerminating0 = true);
void emitLogMessage(const QString &); 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 parseTcfCommandReply(char type, const QVector<QByteArray> &tokens);
int parseTcfEvent(const QVector<QByteArray> &tokens); int parseTcfEvent(const QVector<QByteArray> &tokens);
// Send with parameters from string (which may contain '\0'). // Send with parameters from string (which may contain '\0').
......
DEFINES += SYMBIANUTILS_INCLUDE_PRI DEFINES += SYMBIANUTILS_INCLUDE_PRI
include(../../../qtcreator.pri)
include(../../../src/shared/symbianutils/symbianutils.pri) include(../../../src/shared/symbianutils/symbianutils.pri)
# include(../../../src/libs/3rdparty/qextserialport/qextserialport.pri)
QT += core gui network QT += core gui network
TARGET = codaclient TARGET = codaclient
......
...@@ -29,6 +29,10 @@ ...@@ -29,6 +29,10 @@
#include "codaclientapplication.h" #include "codaclientapplication.h"
#ifdef HAS_SERIALPORT
# include <qextserialport/qextserialport.h>
#endif
#include "tcftrkdevice.h" #include "tcftrkdevice.h"
#include <QtNetwork/QTcpSocket> #include <QtNetwork/QTcpSocket>
#include <QtCore/QFile> #include <QtCore/QFile>
...@@ -255,31 +259,27 @@ bool CodaClientApplication::start() ...@@ -255,31 +259,27 @@ bool CodaClientApplication::start()
switch (m_mode) { switch (m_mode) {
case Launch: { case Launch: {
const QString args = m_launchArgs.join(QString(QLatin1Char(' '))); 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), m_launchUID, qPrintable(m_launchBinary),
qPrintable(args), qPrintable(m_address), m_port, qPrintable(args), m_launchDebug);
m_launchDebug);
} }
break; break;
case Install: case Install:
std::printf("Installing '%s' to '%s' on %s:%hu\n", std::printf("Installing '%s' to '%s'\n",
qPrintable(m_installSisFile), qPrintable(m_installTargetDrive), qPrintable(m_installSisFile), qPrintable(m_installTargetDrive));
qPrintable(m_address), m_port);
break; break;
case Put: 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_putLocalFile), qPrintable(m_putRemoteFile),
qPrintable(m_address), m_port, m_putChunkSize / 1024); m_putChunkSize / 1024);
break; break;
case Stat: case Stat:
std::printf("Retrieving attributes of '%s' from %s:%hu\n", std::printf("Retrieving attributes of '%s'\n", qPrintable(m_statRemoteFile));
qPrintable(m_statRemoteFile), qPrintable(m_address), m_port);
break; break;
case Invalid: case Invalid:
break; break;
} }
// Start connection // Start connection
const QSharedPointer<QTcpSocket> tcfTrkSocket(new QTcpSocket);
m_trkDevice.reset(new tcftrk::TcfTrkDevice); m_trkDevice.reset(new tcftrk::TcfTrkDevice);
m_trkDevice->setVerbose(m_verbose); m_trkDevice->setVerbose(m_verbose);
connect(m_trkDevice.data(), SIGNAL(error(QString)), connect(m_trkDevice.data(), SIGNAL(error(QString)),
...@@ -288,9 +288,48 @@ bool CodaClientApplication::start() ...@@ -288,9 +288,48 @@ bool CodaClientApplication::start()
this, SLOT(slotTrkLogMessage(QString))); this, SLOT(slotTrkLogMessage(QString)));
connect(m_trkDevice.data(), SIGNAL(tcfEvent(tcftrk::TcfTrkEvent)), connect(m_trkDevice.data(), SIGNAL(tcfEvent(tcftrk::TcfTrkEvent)),
this, SLOT(slotTcftrkEvent(tcftrk::TcfTrkEvent))); this, SLOT(slotTcftrkEvent(tcftrk::TcfTrkEvent)));
m_trkDevice->setDevice(tcfTrkSocket); if (m_address.startsWith(QLatin1String("/dev"))
tcfTrkSocket->connectToHost(m_address, m_port); || m_address.startsWith(QLatin1String("com"), Qt::CaseInsensitive)
std::printf("Connecting...\n"); || 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; return true;
} }