From 5cb277888995edecfafd83fed4cf2bd510052a4b Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 12 Apr 2020 00:45:32 +0200 Subject: port to qt5, libusb 1.0, and openobex 1.7 --- smartpen.cc | 269 +++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 204 insertions(+), 65 deletions(-) (limited to 'smartpen.cc') diff --git a/smartpen.cc b/smartpen.cc index b03f0e5..1282e33 100644 --- a/smartpen.cc +++ b/smartpen.cc @@ -18,29 +18,25 @@ #include #include +#include +#include #include #include -#include +#include #include "xmlutils.h" #include "smartpen.h" -#define PEN_EPOCH (1289335960000LL) // This is probably not correct -#define PEN_MTU 900 -#define PEN_TIMEOUT_SECONDS 30 +#define PEN_EPOCH (1289335960000LL) +#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 +54,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 +72,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); @@ -117,7 +107,7 @@ QString Smartpen::getPenName() { QString name = getParameter(PenName); if (name.isEmpty()) { - return name; + return name; // Empty string if unknown name } QByteArray hex = QByteArray::fromHex(name.mid(2).toLatin1()); @@ -163,7 +153,7 @@ QList 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"); @@ -271,6 +261,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 +326,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 +349,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 +382,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 +391,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 +425,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 +467,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 +483,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 +506,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(hdata.bs), hlen); + _inBuf.append(reinterpret_cast(hdata.bs), hlen); } } break; @@ -450,6 +519,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(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 +617,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 +636,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"; - } -} -- cgit v1.2.3