aboutsummaryrefslogtreecommitdiff
path: root/smartpen.cc
diff options
context:
space:
mode:
Diffstat (limited to 'smartpen.cc')
-rw-r--r--smartpen.cc344
1 files changed, 254 insertions, 90 deletions
diff --git a/smartpen.cc b/smartpen.cc
index 79e9a01..12e2ac2 100644
--- a/smartpen.cc
+++ b/smartpen.cc
@@ -16,31 +16,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <QtCore/QDateTime>
+#include <QtCore/QDataStream>
#include <QtCore/QDebug>
+#include <QtCore/QDeadlineTimer>
+#include <QtCore/QThread>
#include <QtCore/QStringList>
#include <QtCore/QtEndian>
-#include <usb.h>
+#include <libusb-1.0/libusb.h>
#include "xmlutils.h"
#include "smartpen.h"
-#define PEN_EPOCH (1289335960000LL) // This is probably not correct
-#define PEN_MTU 900
-#define PEN_TIMEOUT_SECONDS 10
+#define PEN_MTU OBEX_MAXIMUM_MTU
+#define PEN_TIMEOUT_SECONDS 5
#define INVALID_CID 0xFFFFFFFFU
static const char pen_serial_chars[] = "ABCDEFGHJKMNPQRSTUWXYZ23456789";
static const unsigned int pen_serial_num_chars = sizeof(pen_serial_chars) - 1;
-/* 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)
+ QObject(parent), _obex(0), _connId(INVALID_CID), _reqComplete(false), _continueReceived(0)
{
}
@@ -58,6 +53,10 @@ bool Smartpen::isConnected() const
QByteArray Smartpen::getObject(const QString &name)
{
+ qDebug() << "Getting object" << name;
+
+ prepareRequest();
+
obex_object_t *obj = OBEX_ObjectNew(_obex, OBEX_CMD_GET);
Q_ASSERT(obj);
@@ -72,23 +71,13 @@ QByteArray Smartpen::getObject(const QString &name)
return QByteArray();
}
- qDebug() << "Getting object" << name;
-
if (OBEX_Request(_obex, obj) < 0) {
qWarning() << "Get object request failed";
+ OBEX_ObjectDelete(_obex, obj);
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";
- }
+ waitForRequestComplete(PEN_TIMEOUT_SECONDS);
QByteArray result;
qSwap(_inBuf, result);
@@ -96,9 +85,9 @@ QByteArray Smartpen::getObject(const QString &name)
return result;
}
-QString Smartpen::getParameter(Parameters parameter)
+QByteArray Smartpen::getParameter(Parameter parameter)
{
- QString objectName = QString("ppdata?key=pp%1").arg(int(parameter), 4);
+ QString objectName = QString("ppdata?key=pp%1").arg(uint(parameter), 4, 16, QChar('0'));
QByteArray data = getObject(objectName);
QXmlStreamReader r(data);
@@ -107,21 +96,56 @@ QString Smartpen::getParameter(Parameters parameter)
if (!r.atEnd()) {
QXmlStreamAttributes attrs = r.attributes();
- return attrs.value("value").toString();
+ QString value = attrs.value("value").toString();
+ if (!value.isEmpty()) {
+ if (value.startsWith("0x")) {
+ return QByteArray::fromHex(value.mid(2).toLatin1());
+ } else {
+ qWarning() << "Unknown parameter return format: " << value;
+ }
+ }
}
- return QString();
+ return QByteArray();
}
-QString Smartpen::getPenName()
+Smartpen::PenId Smartpen::getPenId()
{
- QString name = getParameter(PenName);
- if (name.isEmpty()) {
- return name;
+ PenId id;
+ QByteArray value = getParameter(Parameter::Id);
+ if (value.isEmpty() || value.size() != 1 + sizeof(Smartpen::PenId)) {
+ qWarning() << "got invalid value for pen id: " << value.toHex();
+ return 0;
}
+ QDataStream ds(value);
+ ds.setByteOrder(QDataStream::BigEndian);
+ ds.skipRawData(1); // Unclear what first byte is
+ ds >> id;
+ return id;
+}
+
+QString Smartpen::getPenSerial()
+{
+ return toPenSerial(getPenId());
+}
- QByteArray hex = QByteArray::fromHex(name.mid(2).toLatin1());
- return QString::fromUtf8(hex);
+QString Smartpen::getPenName()
+{
+ return QString::fromUtf8(getParameter(Parameter::Name));
+}
+
+Smartpen::PenTime Smartpen::getPenTime(Parameter parameter)
+{
+ PenTime time;
+ QByteArray value = getParameter(parameter);
+ if (value.isEmpty() || value.size() != sizeof(Smartpen::PenTime)) {
+ qWarning() << "got invalid value for pen time: " << value.toHex();
+ return 0;
+ }
+ QDataStream ds(value);
+ ds.setByteOrder(QDataStream::LittleEndian);
+ ds >> time;
+ return time;
}
QVariantMap Smartpen::getPenInfo()
@@ -130,6 +154,8 @@ QVariantMap Smartpen::getPenInfo()
QByteArray data = getObject("peninfo");
QXmlStreamReader r(data);
+ qDebug() << "PenInfo: " << QString::fromLatin1(data);
+
advanceToFirstChildElement(r, "xml");
advanceToFirstChildElement(r, "peninfo");
@@ -163,7 +189,7 @@ QList<Smartpen::ChangeReport> Smartpen::getChangeList(PenTime from)
QByteArray data = getObject(QString("changelist?start_time=%1").arg(from));
QXmlStreamReader r(data);
- qDebug() << QString::fromAscii(data);
+ qDebug() << "changelist:" << QString::fromLatin1(data);
advanceToFirstChildElement(r, "xml");
advanceToFirstChildElement(r, "changelist");
@@ -224,22 +250,11 @@ QByteArray Smartpen::getPaperReplay(PenTime from)
return getObject(QString("lspdata?name=com.livescribe.paperreplay.PaperReplay&start_time=%1&returnVersion=0.3&remoteCaller=WIN_LD_200").arg(from));
}
-qint64 Smartpen::toPenTime(const QDateTime &dt)
+QDateTime Smartpen::fromPenTime(PenTime userTime, PenTime penTime)
{
- 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();
- }
+ QDateTime dt = QDateTime::fromMSecsSinceEpoch(penTime + userTime, Qt::UTC);
+ dt.setTimeSpec(Qt::LocalTime); // userTime is actually in LocalTime, so override tz conversion
+ return dt;
}
QString Smartpen::toPenSerial(quint64 id)
@@ -271,6 +286,64 @@ quint64 Smartpen::toPenId(const QString &serial)
return id;
}
+bool Smartpen::reset(const Address &addr)
+{
+ libusb_context *ctx = 0;
+ libusb_device **devlist = 0;
+ libusb_device *dev = 0;
+ libusb_device_handle *handle = 0;
+ ssize_t ndevs;
+ int err = 0;
+
+ err = libusb_init(&ctx);
+ if (err != 0) {
+ qWarning() << "libusb_init failed:" << err;
+ goto err0;
+ }
+
+ ndevs = libusb_get_device_list(ctx, &devlist);
+ if (ndevs < 0) {
+ qWarning() << "libusb_get_device_list failed:" << err;
+ goto err1;
+ }
+
+ for (ssize_t i = 0; i < ndevs; ++i) {
+ if (libusb_get_bus_number(devlist[i]) == addr.first &&
+ libusb_get_device_address(devlist[i]) == addr.second) {
+ dev = devlist[i];
+ }
+ }
+
+ if (!dev) {
+ qWarning() << "could not find device in libusb";
+ err = -ENODEV;
+ goto err2;
+ }
+
+ err = libusb_open(dev, &handle);
+ if (err != 0) {
+ qWarning() << "libusb_open failed:" << err;
+ goto err2;
+ }
+
+ err = libusb_reset_device(handle);
+ if (err != 0) {
+ qWarning() << "libusb_reset_device failed: " << err;
+ goto err3;
+ }
+
+ qDebug() << "USB device resetted";
+
+err3:
+ libusb_close(handle);
+err2:
+ libusb_free_device_list(devlist, 1);
+err1:
+ libusb_exit(ctx);
+err0:
+ return err == 0;
+}
+
bool Smartpen::connectToPen(const Address &addr)
{
if (_obex) {
@@ -278,18 +351,21 @@ bool Smartpen::connectToPen(const Address &addr)
return false;
}
- _obex = OBEX_Init(OBEX_TRANS_USB, obexEventCb, 0);
+ _connId = INVALID_CID;
+
+ _obex = OBEX_Init(OBEX_TRANS_USB, obexEventCb, OBEX_FL_CLOEXEC);
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);
+ obex_interface_t *ls_interface = 0;
+ int count = OBEX_EnumerateInterfaces(_obex);
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];
+ obex_interface_t *intf = OBEX_GetInterfaceByIndex(_obex, i);
+ if (intf->usb.bus_number == addr.first &&
+ intf->usb.device_address == addr.second) {
+ ls_interface = intf;
}
}
@@ -298,22 +374,16 @@ bool Smartpen::connectToPen(const Address &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;
- qDebug() << "connecting to" << ls_interface->usb.product;
-
- if (OBEX_InterfaceConnect(_obex, ls_interface) < 0) {
- qWarning() << "Could not connect to Livescribe interface";
+ int err = OBEX_InterfaceConnect(_obex, ls_interface) ;
+ if (err < 0) {
+ qWarning() << "Could not connect to Livescribe interface" << strerror(-err);
return false;
}
+ prepareRequest();
+
static const char * livescribe_service = "LivescribeService";
obex_object_t *object = OBEX_ObjectNew(_obex, OBEX_CMD_CONNECT);
obex_headerdata_t hd;
@@ -337,7 +407,7 @@ bool Smartpen::connectToPen(const Address &addr)
qDebug() << "Connection in progress";
- OBEX_HandleInput(_obex, PEN_TIMEOUT_SECONDS);
+ waitForRequestComplete(PEN_TIMEOUT_SECONDS);
return _connId != INVALID_CID;
}
@@ -346,19 +416,27 @@ void Smartpen::disconnectFromPen()
{
if (_connId != INVALID_CID) {
if (_obex) {
+ OBEX_CancelRequest(_obex, 0);
+
+ prepareRequest();
+
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);
+ if (OBEX_Request(_obex, object) == 0) {
+ qDebug() << "Send disconnect";
+ waitForRequestComplete(PEN_TIMEOUT_SECONDS);
+ }
}
_connId = INVALID_CID;
}
if (_obex) {
+ OBEX_TransportDisconnect(_obex);
OBEX_Cleanup(_obex);
_obex = 0;
}
_inBuf.clear();
+ qDebug() << "Disconnected";
}
void Smartpen::obexEventCb(obex_t *handle, obex_object_t *obj,
@@ -372,24 +450,38 @@ void Smartpen::obexEventCb(obex_t *handle, obex_object_t *obj,
void Smartpen::handleObexEvent(obex_object_t *object,
int event, int obex_cmd, int obex_rsp)
{
+ // Special flag used for temporarily ignoring the synthetic events generated during OBEX_CancelRequest
+ static bool aborting_continue = false;
+ if (aborting_continue) return;
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();
+ qWarning() << "link error cmd=" << obex_cmd << " rsp=" << OBEX_ResponseToString(obex_rsp);
+ emit linkError("Link error");
+ break;
+ case OBEX_EV_PARSEERR:
+ qWarning() << "parse error cmd=" << obex_cmd << " rsp=" << OBEX_ResponseToString(obex_rsp);
+ emit linkError("Protocol error");
+ break;
+ case OBEX_EV_CONTINUE:
+ // The standard "Continue" messages sent by OpenObex "forget" to include the ConnectionID, confusing the pen
+ // We're going to therefore implement the "continue" state machine on our own
+ handleObexContinue(object, obex_cmd);
+
+ // Cancel OpenObex's Continue message, avoiding the spurious events that generates
+ Q_ASSERT(!aborting_continue);
+ aborting_continue = true;
+ OBEX_CancelRequest(_obex, 0);
+ aborting_continue = false;
break;
default:
- qDebug() << "event" << event << obex_cmd << obex_rsp;
+ qDebug() << "event unknown=" << event << " cmd=" << obex_cmd << " rsp=" << OBEX_ResponseToString(obex_rsp);
break;
}
}
@@ -400,6 +492,8 @@ void Smartpen::handleObexRequestDone(obex_object_t *object, int obex_cmd, int ob
obex_headerdata_t hdata;
quint32 hlen;
+ _reqComplete = true;
+
switch (obex_cmd & ~OBEX_FINAL) {
case OBEX_CMD_CONNECT:
switch (obex_rsp) {
@@ -414,7 +508,7 @@ void Smartpen::handleObexRequestDone(obex_object_t *object, int obex_cmd, int ob
break;
default:
qWarning() << "Failed connection request:" << OBEX_ResponseToString(obex_rsp);
- emit error();
+ emit linkError("OBEX connection error");
break;
}
break;
@@ -437,7 +531,7 @@ void Smartpen::handleObexRequestDone(obex_object_t *object, int obex_cmd, int ob
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);
+ _inBuf.append(reinterpret_cast<const char*>(hdata.bs), hlen);
}
}
break;
@@ -450,6 +544,85 @@ void Smartpen::handleObexRequestDone(obex_object_t *object, int obex_cmd, int ob
}
}
+void Smartpen::handleObexContinue(obex_object_t *object, int obex_cmd)
+{
+ const uint8_t *data;
+ int len;
+
+ _continueReceived++;
+
+ switch (obex_cmd & ~OBEX_FINAL) {
+ case OBEX_CMD_GET:
+ len = OBEX_ObjectReadStream(_obex, object, &data);
+ if (len > 0) {
+ _inBuf.append(reinterpret_cast<const char*>(data), len);
+ }
+ break;
+ }
+}
+
+void Smartpen::prepareRequest()
+{
+ _reqComplete = false;
+ _continueReceived = 0;
+ _inBuf.clear();
+}
+
+bool Smartpen::waitForRequestComplete(int timeout)
+{
+ QDeadlineTimer timer(timeout * 1000UL);
+ timer.setTimerType(Qt::CoarseTimer);
+
+ int cmd = OBEX_ObjectGetCommand(_obex, NULL);
+
+ do {
+ if (OBEX_HandleInput(_obex, timer.remainingTime() / 1000) < 0) {
+ qWarning() << "OBEX_HandleInput failed";
+ break;
+ }
+ if (_continueReceived) {
+ sendContinue(cmd);
+ _continueReceived--;
+
+ // Reset timeout
+ timer.setRemainingTime(timeout * 1000UL);
+ }
+ } while (!_reqComplete && !timer.hasExpired());
+
+ if (!_reqComplete) {
+ qWarning() << "Did not complete request in" << timeout << "seconds";
+ emit linkError("Timeout");
+ return false;
+ }
+
+ return true;
+}
+
+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), OBEX_FL_FIT_ONE_PACKET) < 0) {
+ qCritical() << "Could not add connection header";
+ }
+}
+
+bool Smartpen::sendContinue(int obex_cmd)
+{
+ obex_object_t *obj = OBEX_ObjectNew(_obex, obex_cmd);
+ Q_ASSERT(obj);
+
+ addConnHeader(obj);
+
+ if (OBEX_Request(_obex, obj) < 0) {
+ qWarning() << "Send continue failed";
+ OBEX_ObjectDelete(_obex, obj);
+ return false;
+ }
+
+ return true;
+}
+
QString Smartpen::toPenSerialSegment(quint32 id, int len)
{
@@ -469,7 +642,7 @@ quint32 Smartpen::fromPenSerialSegment(const QString &s)
quint32 id = 0;
for (int i = 0; i < len; i++) {
- uint val = qFind(&pen_serial_chars[0], &pen_serial_chars[pen_serial_num_chars], s[i]) - &pen_serial_chars[0];
+ uint val = std::find(&pen_serial_chars[0], &pen_serial_chars[pen_serial_num_chars], s[i]) - &pen_serial_chars[0];
if (val >= pen_serial_num_chars) return 0;
id = val + id * pen_serial_num_chars;
}
@@ -488,12 +661,3 @@ QByteArray Smartpen::encodeUtf16(const QString &s)
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";
- }
-}