From a69e97943539a8abc4d2762638c169dc19c88516 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 7 Jun 2015 21:22:45 +0200 Subject: initial import --- smartpen.cc | 424 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 smartpen.cc (limited to 'smartpen.cc') diff --git a/smartpen.cc b/smartpen.cc new file mode 100644 index 0000000..6df3fd1 --- /dev/null +++ b/smartpen.cc @@ -0,0 +1,424 @@ +#include +#include +#include +#include +#include "xmlutils.h" +#include "smartpen.h" + +#define PEN_EPOCH (1289335960000LL) +#define PEN_MTU 900 +#define PEN_TIMEOUT_SECONDS 10 + +#define INVALID_CID 0xFFFFFFFFU + +/* Terrible hack comes now: */ +struct obex_usb_intf_transport_t { + struct obex_usb_intf_transport_t *prev, *next; /* Next and previous interfaces in the list */ + struct usb_device *device; /* USB device that has the interface */ +}; + +Smartpen::Smartpen(QObject *parent) : + QObject(parent), _obex(0), _connId(INVALID_CID) +{ +} + +Smartpen::~Smartpen() +{ + if (_connId != INVALID_CID || _obex) { + disconnectFromPen(); + } +} + +bool Smartpen::isConnected() const +{ + return _obex && _connId != INVALID_CID; +} + +QByteArray Smartpen::getObject(const QString &name) +{ + obex_object_t *obj = OBEX_ObjectNew(_obex, OBEX_CMD_GET); + Q_ASSERT(obj); + + addConnHeader(obj); + + obex_headerdata_t hd; + QByteArray encodedName = encodeUtf16(name); + hd.bs = reinterpret_cast(encodedName.constData()); + if (OBEX_ObjectAddHeader(_obex, obj, OBEX_HDR_NAME, hd, encodedName.size(), 0) < 0) { + qCritical("Could not add name header"); + OBEX_ObjectDelete(_obex, obj); + return QByteArray(); + } + + qDebug() << "Getting object" << name; + + if (OBEX_Request(_obex, obj) < 0) { + qWarning() << "Get object request failed"; + return QByteArray(); + } + + QDateTime start = QDateTime::currentDateTimeUtc(); + QDateTime now; + do { + OBEX_HandleInput(_obex, PEN_TIMEOUT_SECONDS); + now = QDateTime::currentDateTimeUtc(); + } while (_inBuf.isEmpty() && start.secsTo(now) < PEN_TIMEOUT_SECONDS); + + if (_inBuf.isEmpty()) { + qWarning() << "Did not receive any data in" << start.secsTo(now) << "seconds"; + } + + QByteArray result; + qSwap(_inBuf, result); + + return result; +} + +QString Smartpen::getParameter(Parameters parameter) +{ + QString objectName = QString("ppdata?key=pp%1").arg(int(parameter), 4); + QByteArray data = getObject(objectName); + QXmlStreamReader r(data); + + advanceToFirstChildElement(r, "xml"); + advanceToFirstChildElement(r, "parameter"); + + if (!r.atEnd()) { + QXmlStreamAttributes attrs = r.attributes(); + return attrs.value("value").toString(); + } + + return QString(); +} + +QString Smartpen::getPenName() +{ + QString name = getParameter(PenName); + if (name.isEmpty()) { + return name; + } + + QByteArray hex = QByteArray::fromHex(name.mid(2).toLatin1()); + return QString::fromUtf8(hex); +} + +QVariantMap Smartpen::getPenInfo() +{ + QVariantMap result; + QByteArray data = getObject("peninfo"); + QXmlStreamReader r(data); + + advanceToFirstChildElement(r, "xml"); + advanceToFirstChildElement(r, "peninfo"); + + if (!r.atEnd()) { + Q_ASSERT(r.isStartElement() && r.name() == "peninfo"); + QString penId = r.attributes().value("penid").toString(); + result["penid"] = penId; + result["penserial"] = toPenSerial(penId.mid(2).toULongLong(0, 16)); + + while (r.readNextStartElement()) { + if (r.name() == "battery") { + result["battery"] = r.attributes().value("level").toString(); + r.skipCurrentElement(); + } else if (r.name() == "time") { + result["time"] = r.attributes().value("absolute").toString(); + r.skipCurrentElement(); + } else { + r.skipCurrentElement(); + } + } + } else { + qWarning() << "Could not parse peninfo XML"; + } + + return result; +} + +QList Smartpen::getChangeList(const QDateTime &from) +{ + QList result; + QByteArray data = getObject(QString("changelist?start_time=%1").arg(toPenTime(from))); + QXmlStreamReader r(data); + + advanceToFirstChildElement(r, "xml"); + advanceToFirstChildElement(r, "changelist"); + + if (!r.atEnd()) { + Q_ASSERT(r.isStartElement() && r.name() == "changelist"); + + while (r.readNextStartElement()) { + if (r.name() == "lsp") { + QXmlStreamAttributes attrs = r.attributes(); + ChangeReport report; + if (attrs.hasAttribute("guid")) { + report.guid = attrs.value("guid").toString(); + report.title = attrs.value("title").toString(); + result.append(report); + } + r.skipCurrentElement(); + } else { + r.skipCurrentElement(); + } + } + + } else { + qWarning() << "Could not parse changelist XML"; + } + + return result; +} + +QByteArray Smartpen::getLspData(const QString &name, const QDateTime &from) +{ + return getObject(QString("lspdata?name=%1&start_time=%2").arg(name).arg(toPenTime(from))); +} + +qint64 Smartpen::toPenTime(const QDateTime &dt) +{ + if (dt.isValid()) { + return dt.toMSecsSinceEpoch() - PEN_EPOCH; + } else { + return 0; + } +} + +QDateTime Smartpen::fromPenTime(qint64 t) +{ + if (t) { + return QDateTime::fromMSecsSinceEpoch(t + PEN_EPOCH).toLocalTime(); + } else { + return QDateTime(); + } +} + +QString Smartpen::toPenSerial(quint64 id) +{ + QString serial; + serial.reserve(3 + 1 + 3 + 1 + 3 + 1 + 2 + 1); + + serial.append(toPenSerialSegment(id >> 32, 3)); + serial.append('-'); + serial.append(toPenSerialSegment(id, 6).mid(0, 3)); + serial.append('-'); + serial.append(toPenSerialSegment(id, 6).mid(3, 3)); + serial.append('-'); + serial.append(toPenSerialSegment(id % 0x36D, 2)); + + return serial; +} + +bool Smartpen::connectToPen(const Address &addr) +{ + if (_obex) { + qWarning() << "Already connected"; + return false; + } + + _obex = OBEX_Init(OBEX_TRANS_USB, obexEventCb, 0); + Q_ASSERT(_obex); + + OBEX_SetUserData(_obex, this); + OBEX_SetTransportMTU(_obex, PEN_MTU, PEN_MTU); + + obex_interface_t *interfaces, *ls_interface = 0; + int count = OBEX_FindInterfaces(_obex, &interfaces); + for (int i = 0; i < count; i++) { + if (interfaces[i].usb.intf->device->bus->location == addr.first && + interfaces[i].usb.intf->device->devnum == addr.second) { + ls_interface = &interfaces[i]; + } + } + + if (!ls_interface) { + qWarning() << "Could not find Lightscribe interface on device:" << addr; + return false; + } + + usb_dev_handle *handle = usb_open(ls_interface->usb.intf->device); + if (handle) { + qDebug() << "resetting usb device"; + usb_reset(handle); + usb_close(handle); + } else { + qWarning() << "could not open usb device for resetting"; + } + + qDebug() << "connecting to" << ls_interface->usb.product; + + if (OBEX_InterfaceConnect(_obex, ls_interface) < 0) { + qWarning() << "Could not connect to Livescribe interface"; + return false; + } + + static const char * livescribe_service = "LivescribeService"; + obex_object_t *object = OBEX_ObjectNew(_obex, OBEX_CMD_CONNECT); + obex_headerdata_t hd; + int hd_len; + + Q_ASSERT(object); + + hd.bs = reinterpret_cast(livescribe_service); + hd_len = strlen(livescribe_service) + 1; + if (OBEX_ObjectAddHeader(_obex, object, OBEX_HDR_TARGET, hd, hd_len, 0) < 0) { + qWarning() << "Failed to add Target header"; + OBEX_ObjectDelete(_obex, object); + return false; + } + + if (OBEX_Request(_obex, object) < 0) { + qWarning() << "Failed to make connection request"; + OBEX_ObjectDelete(_obex, object); + return false; + } + + qDebug() << "Connection in progress"; + + OBEX_HandleInput(_obex, PEN_TIMEOUT_SECONDS); + + return _connId != INVALID_CID; +} + +void Smartpen::disconnectFromPen() +{ + if (_connId != INVALID_CID) { + if (_obex) { + obex_object_t *object = OBEX_ObjectNew(_obex, OBEX_CMD_DISCONNECT); + Q_ASSERT(object); + addConnHeader(object); + OBEX_Request(_obex, object); + OBEX_HandleInput(_obex, PEN_TIMEOUT_SECONDS); + } + _connId = INVALID_CID; + } + if (_obex) { + OBEX_Cleanup(_obex); + _obex = 0; + } + _inBuf.clear(); +} + +void Smartpen::obexEventCb(obex_t *handle, obex_object_t *obj, + int mode, int event, int obex_cmd, int obex_rsp) +{ + Smartpen *smartpen = static_cast(OBEX_GetUserData(handle)); + Q_UNUSED(mode); + smartpen->handleObexEvent(obj, event, obex_cmd, obex_rsp); +} + +void Smartpen::handleObexEvent(obex_object_t *object, + int event, int obex_cmd, int obex_rsp) +{ + + switch (event) { + case OBEX_EV_PROGRESS: + if (obex_cmd == OBEX_CMD_GET) { + // It seems that the pen wants us to add this header on every continue response + addConnHeader(object); + } + break; + case OBEX_EV_REQDONE: + qDebug() << "event reqdone cmd=" << obex_cmd << " rsp=" << OBEX_ResponseToString(obex_rsp); + handleObexRequestDone(object, obex_cmd, obex_rsp); + break; + case OBEX_EV_LINKERR: + qWarning() << "link error cmd=" << obex_cmd; + emit error(); + break; + default: + qDebug() << "event" << event << obex_cmd << obex_rsp; + break; + } +} + +void Smartpen::handleObexRequestDone(obex_object_t *object, int obex_cmd, int obex_rsp) +{ + quint8 header_id; + obex_headerdata_t hdata; + quint32 hlen; + + switch (obex_cmd & ~OBEX_FINAL) { + case OBEX_CMD_CONNECT: + switch (obex_rsp) { + case OBEX_RSP_SUCCESS: + while (OBEX_ObjectGetNextHeader(_obex, object, &header_id, &hdata, &hlen)) { + if (header_id == OBEX_HDR_CONNECTION) { + Q_ASSERT(_connId == INVALID_CID); + _connId = hdata.bq4; + qDebug() << "Connection established, id:" << _connId; + } + } + break; + default: + qWarning() << "Failed connection request:" << OBEX_ResponseToString(obex_rsp); + emit error(); + break; + } + break; + case OBEX_CMD_DISCONNECT: + switch (obex_rsp) { + case OBEX_RSP_SUCCESS: + qDebug() << "Disconnected succesfully"; + _connId = INVALID_CID; + break; + default: + qWarning() << "Failed disconnection request:" << OBEX_ResponseToString(obex_rsp); + _connId = INVALID_CID; + break; + } + + break; + case OBEX_CMD_GET: + switch (obex_rsp) { + case OBEX_RSP_SUCCESS: + qDebug() << "GET request succesful"; + while (OBEX_ObjectGetNextHeader(_obex, object, &header_id, &hdata, &hlen)) { + if (header_id == OBEX_HDR_BODY || header_id == OBEX_HDR_BODY_END) { + _inBuf = QByteArray(reinterpret_cast(hdata.bs), hlen); + } + } + break; + default: + qWarning() << "Failed GET request:" << OBEX_ResponseToString(obex_rsp); + break; + } + + break; + } +} + +QString Smartpen::toPenSerialSegment(quint32 id, int len) +{ + static const char chars[] = "ABCDEFGHJKMNPQRSTUWXYZ23456789"; + static const unsigned int num_chars = sizeof(chars) - 1; + + QString segment(len, Qt::Uninitialized); + + for (int i = 0; i < len; i++) { + segment[len - (i + 1)] = chars[id % num_chars]; + id /= num_chars; + } + + return segment; +} + +QByteArray Smartpen::encodeUtf16(const QString &s) +{ + const int size = s.size(); + QByteArray data((size + 1) * sizeof(quint16), Qt::Uninitialized); + quint16 *p = reinterpret_cast(data.data()); + for (int i = 0; i < size; i++) { + p[i] = qToBigEndian(s.at(i).unicode()); + } + p[size] = 0; + return data; +} + +void Smartpen::addConnHeader(obex_object_t *obj) const +{ + obex_headerdata_t hd; + hd.bq4 = _connId; + if (OBEX_ObjectAddHeader(_obex, obj, OBEX_HDR_CONNECTION, hd, sizeof(hd.bq4), 0) < 0) { + qCritical() << "Could not add connection header"; + } +} -- cgit v1.2.3