Commit d7ca39da authored by Friedemann Kleint's avatar Friedemann Kleint

Symbian/CODA: Add infrastructure for serial communication.

Add USB protocol and chunking.
parent c4e49138
......@@ -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)));
......
......@@ -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').
......
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
......
......@@ -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);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment