aboutsummaryrefslogtreecommitdiff
path: root/smartpen.cc
diff options
context:
space:
mode:
Diffstat (limited to 'smartpen.cc')
-rw-r--r--smartpen.cc424
1 files changed, 424 insertions, 0 deletions
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 <QtCore/QDateTime>
+#include <QtCore/QDebug>
+#include <QtCore/QtEndian>
+#include <usb.h>
+#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<const uint8_t*>(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::ChangeReport> Smartpen::getChangeList(const QDateTime &from)
+{
+ QList<ChangeReport> 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<const quint8*>(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<Smartpen*>(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<const char*>(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<quint16*>(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";
+ }
+}