summaryrefslogtreecommitdiffstats
path: root/src/plugins/canbus/passthrucan
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/canbus/passthrucan')
-rw-r--r--src/plugins/canbus/passthrucan/j2534passthru.cpp222
-rw-r--r--src/plugins/canbus/passthrucan/j2534passthru.h332
-rw-r--r--src/plugins/canbus/passthrucan/main.cpp71
-rw-r--r--src/plugins/canbus/passthrucan/passthrucan.pro21
-rw-r--r--src/plugins/canbus/passthrucan/passthrucanbackend.cpp273
-rw-r--r--src/plugins/canbus/passthrucan/passthrucanbackend.h84
-rw-r--r--src/plugins/canbus/passthrucan/passthrucanio.cpp314
-rw-r--r--src/plugins/canbus/passthrucan/passthrucanio.h98
-rw-r--r--src/plugins/canbus/passthrucan/plugin.json3
9 files changed, 1418 insertions, 0 deletions
diff --git a/src/plugins/canbus/passthrucan/j2534passthru.cpp b/src/plugins/canbus/passthrucan/j2534passthru.cpp
new file mode 100644
index 0000000..22bf996
--- /dev/null
+++ b/src/plugins/canbus/passthrucan/j2534passthru.cpp
@@ -0,0 +1,222 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Ford Motor Company.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "j2534passthru.h"
+
+#include <QtEndian>
+
+#include <cstring>
+
+namespace {
+
+enum Ioctl {
+ GetConfig = 1,
+ SetConfig = 2
+};
+
+// Template to model the structs SCONFIG_LIST, SBYTE_ARRAY etc as defined
+// in the J2534 spec.
+template <typename T>
+struct SArray
+{
+ SArray(ulong n, T *p) : num(n), ptr(p) {}
+ // On Windows x64, ulong is 32 bit wide and thus the value would normally
+ // be padded so that the pointer begins on a 64-bit boundary. It is not
+ // clear from the J2534 spec whether structs should be packed or not on
+ // x64. Most vendors still only provide a 32-bit DLL, but there is at
+ // least one x64 implementation (from Hatteland Display) out there which
+ // does not pack this struct.
+ ulong num;
+ T *ptr;
+};
+
+// Fixed-length string buffers must be at least 80 bytes according to the spec.
+// The example code in the spec document uses 256 bytes though -- let's play it
+// safe and do so, too.
+const int StringBufferSize = 256;
+
+} // anonymous namespace
+
+Q_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_PASSTHRU, "qt.canbus.plugins.passthru", QtWarningMsg)
+
+namespace J2534 {
+
+Message::Message()
+{
+ std::memset(m_data, 0, sizeof(m_data));
+}
+
+Message::Message(Protocol proto)
+ : m_protocolId (ulong(proto))
+{
+ std::memset(m_data, 0, sizeof(m_data));
+}
+
+PassThru::PassThru(const QString &libraryPath, QObject *parent)
+ : QObject(parent)
+ , m_libJ2534 (libraryPath, this)
+{
+ if (!m_libJ2534.load()
+ || !resolveApiFunction(&m_ptOpen, "PassThruOpen")
+ || !resolveApiFunction(&m_ptClose, "PassThruClose")
+ || !resolveApiFunction(&m_ptConnect, "PassThruConnect")
+ || !resolveApiFunction(&m_ptDisconnect, "PassThruDisconnect")
+ || !resolveApiFunction(&m_ptReadMsgs, "PassThruReadMsgs")
+ || !resolveApiFunction(&m_ptWriteMsgs, "PassThruWriteMsgs")
+ || !resolveApiFunction(&m_ptStartMsgFilter, "PassThruStartMsgFilter")
+ || !resolveApiFunction(&m_ptGetLastError, "PassThruGetLastError")
+ || !resolveApiFunction(&m_ptIoctl, "PassThruIoctl")) {
+
+ m_lastError = LoadFailed;
+ m_lastErrorString = m_libJ2534.errorString();
+
+ qCWarning(QT_CANBUS_PLUGINS_PASSTHRU, "%ls", qUtf16Printable(m_lastErrorString));
+ }
+}
+
+PassThru::~PassThru()
+{
+ m_libJ2534.unload();
+}
+
+PassThru::Status PassThru::open(const QByteArray &name, Handle *deviceId)
+{
+ Q_ASSERT(m_ptOpen);
+
+ const char *const devName = (name.isEmpty()) ? nullptr : name.data();
+ const long status = (*m_ptOpen)(devName, deviceId);
+ return handleResult(status);
+}
+
+PassThru::Status PassThru::close(Handle deviceId)
+{
+ Q_ASSERT(m_ptClose);
+
+ const long status = (*m_ptClose)(deviceId);
+ return handleResult(status);
+}
+
+PassThru::Status PassThru::connect(Handle deviceId, Protocol protocolId,
+ ConnectFlags flags, uint baudRate, Handle *channelId)
+{
+ Q_ASSERT(m_ptConnect);
+
+ const long status = (*m_ptConnect)(deviceId, ulong(protocolId),
+ flags, baudRate, channelId);
+ return handleResult(status);
+}
+
+PassThru::Status PassThru::disconnect(Handle channelId)
+{
+ Q_ASSERT(m_ptDisconnect);
+
+ const long status = (*m_ptDisconnect)(channelId);
+ return handleResult(status);
+}
+
+PassThru::Status PassThru::readMsgs(Handle channelId, Message *msgs,
+ ulong *numMsgs, uint timeout)
+{
+ Q_ASSERT(m_ptReadMsgs);
+
+ const long status = (*m_ptReadMsgs)(channelId, msgs, numMsgs, timeout);
+ return handleResult(status);
+}
+
+PassThru::Status PassThru::writeMsgs(Handle channelId, const Message *msgs,
+ ulong *numMsgs, uint timeout)
+{
+ Q_ASSERT(m_ptWriteMsgs);
+
+ const long status = (*m_ptWriteMsgs)(channelId, msgs, numMsgs, timeout);
+ return handleResult(status);
+}
+
+PassThru::Status PassThru::startMsgFilter(Handle channelId, FilterType filterType,
+ const Message &maskMsg, const Message &patternMsg)
+{
+ Q_ASSERT(m_ptStartMsgFilter);
+
+ // The CAN pass-thru plugin implementation does not need the filter ID.
+ Handle filterId = 0;
+
+ const long status = (*m_ptStartMsgFilter)(channelId, filterType, &maskMsg,
+ &patternMsg, nullptr, &filterId);
+ return handleResult(status);
+}
+
+PassThru::Status PassThru::setConfig(Handle channelId, const Config *params, ulong numParams)
+{
+ Q_ASSERT(m_ptIoctl);
+
+ const SArray<const Config> configList {numParams, params};
+ const long status = (*m_ptIoctl)(channelId, SetConfig, &configList, nullptr);
+ return handleResult(status);
+}
+
+PassThru::Status PassThru::clear(Handle channelId, ClearTarget target)
+{
+ Q_ASSERT(m_ptIoctl);
+
+ const long status = (*m_ptIoctl)(channelId, target, nullptr, nullptr);
+ return handleResult(status);
+}
+
+QString PassThru::lastErrorString() const
+{
+ return m_lastErrorString;
+}
+
+PassThru::Status PassThru::handleResult(long statusCode)
+{
+ if (Q_UNLIKELY(statusCode != NoError)) {
+ m_lastError = Status(statusCode);
+
+ QByteArray description (StringBufferSize, 0);
+ Q_ASSERT(m_ptGetLastError);
+ const long descStatus = (*m_ptGetLastError)(description.data());
+
+ if (Q_LIKELY(descStatus == NoError)) {
+ m_lastErrorString = QString::fromLatin1(description);
+ } else {
+ m_lastErrorString = tr("Command failed with status code %1").arg(statusCode);
+ qCWarning(QT_CANBUS_PLUGINS_PASSTHRU, "GetLastError failed with code %ld", descStatus);
+ }
+ }
+ return Status(statusCode);
+}
+
+} // namespace J2534
diff --git a/src/plugins/canbus/passthrucan/j2534passthru.h b/src/plugins/canbus/passthrucan/j2534passthru.h
new file mode 100644
index 0000000..a9b88f4
--- /dev/null
+++ b/src/plugins/canbus/passthrucan/j2534passthru.h
@@ -0,0 +1,332 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Ford Motor Company.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef PASSTHRUCAN_J2534PASSTHRU_H
+#define PASSTHRUCAN_J2534PASSTHRU_H
+
+#include <QByteArray>
+#include <QLibrary>
+#include <QLoggingCategory>
+#include <QObject>
+#include <QString>
+
+#ifdef Q_OS_WIN32
+# define J2534_API __stdcall
+#else
+# define J2534_API
+#endif
+
+Q_DECLARE_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_PASSTHRU)
+
+namespace J2534 {
+
+class Message;
+
+extern "C" {
+
+typedef long (J2534_API *PassThruOpenFunc)(const void *pName, ulong *pDeviceId);
+typedef long (J2534_API *PassThruCloseFunc)(ulong deviceId);
+typedef long (J2534_API *PassThruConnectFunc)(ulong deviceId, ulong protocolId, ulong flags,
+ ulong baudRate, ulong *pChannelId);
+typedef long (J2534_API *PassThruDisconnectFunc)(ulong channelId);
+typedef long (J2534_API *PassThruReadMsgsFunc)(ulong channelId, Message *pMsg,
+ ulong *pNumMsgs, ulong timeout);
+typedef long (J2534_API *PassThruWriteMsgsFunc)(ulong channelId, const Message *pMsg,
+ ulong *pNumMsgs, ulong timeout);
+typedef long (J2534_API *PassThruStartMsgFilterFunc)(ulong channelID, ulong filterType,
+ const Message *pMaskMsg,
+ const Message *pPatternMsg,
+ const Message *pFlowControlMsg,
+ ulong *pFilterId);
+typedef long (J2534_API *PassThruGetLastErrorFunc)(char *pErrorDescription);
+typedef long (J2534_API *PassThruIoctlFunc)(ulong channelId, ulong ioctlId,
+ const void *pInput, void *pOutput);
+} // extern "C"
+
+enum class Protocol : uint {
+ J1850VPW = 1,
+ J1850PWM,
+ ISO9141,
+ ISO14230,
+ CAN,
+ ISO15765,
+ SCIAEngine,
+ SCIATrans,
+ SCIBEngine,
+ SCIBTrans
+};
+
+class Message
+{
+public:
+ static const ulong maxSize = 4128;
+
+ enum RxStatusBit {
+ InTxMsgType = 1 << 0,
+ InStartOfMessage = 1 << 1,
+ InRxBreak = 1 << 2,
+ InTxIndication = 1 << 3,
+ InISO15765PaddingError = 1 << 4,
+ InISO15765AddrType = 1 << 7,
+ InCAN29BitID = 1 << 8
+ };
+ Q_DECLARE_FLAGS(RxStatus, RxStatusBit)
+
+ enum TxFlag {
+ OutISO15765FramePad = 1 << 6,
+ OutISO15765AddrType = 1 << 7,
+ OutCAN29BitID = 1 << 8,
+ OutWaitP3MinOnly = 1 << 9
+ };
+ Q_DECLARE_FLAGS(TxFlags, TxFlag)
+
+ Message();
+ explicit Message(Protocol proto);
+
+ Protocol protocolId() const { return Protocol(m_protocolId); }
+ void setProtocolId(Protocol proto) { m_protocolId = uint(proto); }
+
+ RxStatus rxStatus() const { return RxStatus(uint(m_rxStatus)); }
+ void setRxStatus(RxStatus status) { m_rxStatus = uint(status); }
+
+ TxFlags txFlags() const { return TxFlags(uint(m_txFlags)); }
+ void setTxFlags(TxFlags flags) { m_txFlags = uint(flags); }
+
+ ulong timestamp() const { return m_timestamp; }
+ void setTimestamp(ulong stamp) { m_timestamp = stamp; }
+
+ ulong size() const { return m_dataSize; }
+ void setSize(ulong dataSize) { m_dataSize = dataSize; }
+
+ ulong extraDataIndex() const { return m_extraDataIndex; }
+ void setExtraDataIndex(ulong index) { m_extraDataIndex = index; }
+
+ char *data() { return m_data; }
+ const char *data() const { return m_data; }
+
+private:
+ ulong m_protocolId = 0;
+ ulong m_rxStatus = 0;
+ ulong m_txFlags = 0;
+ ulong m_timestamp = 0;
+ ulong m_dataSize = 0;
+ ulong m_extraDataIndex = 0;
+ char m_data[maxSize];
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(Message::RxStatus)
+Q_DECLARE_OPERATORS_FOR_FLAGS(Message::TxFlags)
+
+class Config
+{
+public:
+ enum Parameter {
+ DataRate = 1,
+
+ Loopback = 3,
+ NodeAddress,
+ NetworkLine,
+ P1Min,
+ P1Max,
+ P2Min,
+ P2Max,
+ P3Min,
+ P3Max,
+ P4Min,
+ P4Max,
+ W1,
+ W2,
+ W3,
+ W4,
+ W5,
+ Tidle,
+ Tinil,
+ Twup,
+ Parity,
+ BitSamplePoint,
+ SyncJumpWidth,
+ W0,
+ T1Max,
+ T2Max,
+ T4Max,
+ T5Max,
+ ISO15765BS,
+ ISO15765STmin,
+ DataBits,
+ FiveBaudMod,
+ BSTx,
+ STminTx,
+ T3Max,
+ ISO15765WFTMax,
+
+ CanMixedFormat = 0x8000,
+ J1962Pins,
+
+ SWCANHSDataRate = 0x8010,
+ SWCANSpeedchangeEnable,
+ SWCANResSwitch,
+
+ ActiveChannels = 0x8020,
+ SampleRate,
+ SamplesPerReading,
+ ReadingsPerMsg,
+ AveragingMethod,
+ SampleResolution,
+ InputRangeLow,
+ InputRangeHigh
+ };
+
+ Config() : m_parameter(0), m_value(0) {}
+ explicit Config(Parameter param, ulong val = 0) : m_parameter(param), m_value(val) {}
+
+ Parameter parameter() const { return Parameter(m_parameter); }
+ ulong value() const { return m_value; }
+
+private:
+ ulong m_parameter;
+ ulong m_value;
+};
+
+/**
+ * @brief J2534 pass-through interface, version 04.04.
+ * @internal
+ * @see http://www.drewtech.com/support/passthru.html
+ */
+class PassThru : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PassThru)
+public:
+ typedef ulong Handle;
+
+ enum Status {
+ LoadFailed = -1,
+ NoError = 0,
+ NotSupported,
+ InvalidChannelID,
+ InvalidProtocolID,
+ NullParameter,
+ InvalidIoctlValue,
+ InvalidFlags,
+ Failed,
+ DeviceNotConnected,
+ Timeout,
+ InvalidMsg,
+ InvalidTimeInterval,
+ ExceededLimit,
+ InvalidMsgID,
+ DeviceInUse,
+ InvalidIoctlID,
+ BufferEmpty,
+ BufferFull,
+ BufferOverflow,
+ PinInvalid,
+ ChannelInUse,
+ MsgProtocolID,
+ InvalidFilterID,
+ NoFlowControl,
+ NotUnique,
+ InvalidBaudrate,
+ InvalidDeviceID
+ };
+
+ enum ConnectFlag {
+ CAN29BitID = 1 << 8,
+ ISO9141NoChecksum = 1 << 9,
+ CANIDBoth = 1 << 11,
+ ISO9141KLineOnly = 1 << 12
+ };
+ Q_DECLARE_FLAGS(ConnectFlags, ConnectFlag)
+
+ enum FilterType {
+ PassFilter = 1,
+ BlockFilter,
+ FlowControlFilter
+ };
+
+ enum ClearTarget {
+ TxBuffer = 7,
+ RxBuffer,
+ PeriodicMsgs,
+ MsgFilters
+ };
+
+ explicit PassThru(const QString &libraryPath, QObject *parent = nullptr);
+ virtual ~PassThru();
+
+ Status open(const QByteArray &name, Handle *deviceId);
+ Status close(Handle deviceId);
+ Status connect(Handle deviceId, Protocol protocolId, ConnectFlags flags,
+ uint baudRate, Handle *channelId);
+ Status disconnect(Handle channelId);
+ Status readMsgs(Handle channelId, Message *msgs, ulong *numMsgs, uint timeout = 0);
+ Status writeMsgs(Handle channelId, const Message *msgs, ulong *numMsgs, uint timeout = 0);
+ Status startMsgFilter(Handle channelId, FilterType filterType,
+ const Message &maskMsg, const Message &patternMsg);
+ Status setConfig(Handle channelId, const Config *params, ulong numParams = 1);
+ Status clear(Handle channelId, ClearTarget target);
+
+ Status lastError() const { return m_lastError; }
+ QString lastErrorString() const;
+
+private:
+ Status handleResult(long statusCode);
+
+ template <typename Func>
+ Func resolveApiFunction(Func *funcPtr, const char *name) {
+ *funcPtr = reinterpret_cast<Func>(m_libJ2534.resolve(name));
+ return *funcPtr;
+ }
+
+ QLibrary m_libJ2534;
+ PassThruOpenFunc m_ptOpen = nullptr;
+ PassThruCloseFunc m_ptClose = nullptr;
+ PassThruConnectFunc m_ptConnect = nullptr;
+ PassThruDisconnectFunc m_ptDisconnect = nullptr;
+ PassThruReadMsgsFunc m_ptReadMsgs = nullptr;
+ PassThruWriteMsgsFunc m_ptWriteMsgs = nullptr;
+ PassThruStartMsgFilterFunc m_ptStartMsgFilter = nullptr;
+ PassThruGetLastErrorFunc m_ptGetLastError = nullptr;
+ PassThruIoctlFunc m_ptIoctl = nullptr;
+ QString m_lastErrorString;
+ Status m_lastError = NoError;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(PassThru::ConnectFlags)
+
+} // namespace J2534
+
+#endif // PASSTHRUCAN_J2534PASSTHRU_H
diff --git a/src/plugins/canbus/passthrucan/main.cpp b/src/plugins/canbus/passthrucan/main.cpp
new file mode 100644
index 0000000..5017597
--- /dev/null
+++ b/src/plugins/canbus/passthrucan/main.cpp
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Ford Motor Company.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "passthrucanbackend.h"
+
+#include <QtSerialBus/qcanbus.h>
+#include <QtSerialBus/qcanbusdevice.h>
+#include <QtSerialBus/qcanbusfactory.h>
+
+QT_BEGIN_NAMESPACE
+
+class PassThruCanBusPlugin : public QObject, public QCanBusFactoryV2
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QCanBusFactory" FILE "plugin.json")
+ Q_INTERFACES(QCanBusFactoryV2)
+
+public:
+ PassThruCanBusPlugin()
+ {
+ qRegisterMetaType<QCanBusDevice::CanBusError>();
+ qRegisterMetaType<QVector<QCanBusFrame>>();
+ }
+
+ QList<QCanBusDeviceInfo> availableDevices(QString *) const override
+ {
+ return PassThruCanBackend::interfaces();
+ }
+
+ QCanBusDevice *createDevice(const QString &interfaceName, QString *) const override
+ {
+ return new PassThruCanBackend(interfaceName);
+ }
+};
+
+QT_END_NAMESPACE
+
+#include "main.moc"
diff --git a/src/plugins/canbus/passthrucan/passthrucan.pro b/src/plugins/canbus/passthrucan/passthrucan.pro
new file mode 100644
index 0000000..96ed090
--- /dev/null
+++ b/src/plugins/canbus/passthrucan/passthrucan.pro
@@ -0,0 +1,21 @@
+QT = serialbus
+
+TARGET = qtpassthrucanbus
+
+SOURCES += \
+ j2534passthru.cpp \
+ main.cpp \
+ passthrucanio.cpp \
+ passthrucanbackend.cpp
+
+HEADERS += \
+ j2534passthru.h \
+ passthrucanio.h \
+ passthrucanbackend.h
+
+DISTFILES = plugin.json
+
+PLUGIN_TYPE = canbus
+PLUGIN_EXTENDS = serialbus
+PLUGIN_CLASS_NAME = PassThruCanBusPlugin
+load(qt_plugin)
diff --git a/src/plugins/canbus/passthrucan/passthrucanbackend.cpp b/src/plugins/canbus/passthrucan/passthrucanbackend.cpp
new file mode 100644
index 0000000..2c18160
--- /dev/null
+++ b/src/plugins/canbus/passthrucan/passthrucanbackend.cpp
@@ -0,0 +1,273 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Ford Motor Company.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "passthrucanbackend.h"
+#include "passthrucanio.h"
+
+#include <QEventLoop>
+#include <QSettings>
+
+namespace {
+
+#ifdef Q_OS_WIN32
+
+static inline QString registryPath()
+{
+ return QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\PassThruSupport.04.04");
+}
+
+static QString canAdapterName(const QSettings &entries)
+{
+ const int supportsCan = entries.value(QStringLiteral("CAN")).toInt();
+ if (supportsCan)
+ return entries.value(QStringLiteral("Name")).toString();
+ return {};
+}
+
+static QString libraryForAdapter(const QString &adapterName)
+{
+ QString library;
+ QSettings entries (registryPath(), QSettings::NativeFormat);
+ const QStringList groups = entries.childGroups();
+
+ for (const auto &group : groups) {
+ entries.beginGroup(group);
+
+ const QString name = canAdapterName(entries);
+ if (!name.isEmpty() && (adapterName.isEmpty() ||
+ name.compare(adapterName, Qt::CaseInsensitive) == 0))
+ library = entries.value(QStringLiteral("FunctionLibrary")).toString();
+
+ entries.endGroup();
+
+ if (!library.isEmpty())
+ break;
+ }
+ return library;
+}
+
+#else // !Q_OS_WIN32
+
+static QString libraryForAdapter(const QString &adapterName)
+{
+ // Insert system-specific device name to J2534 library name mapping here.
+ // For now, allow the path to the J2534 library to be specified directly
+ // as the adapter name.
+ return adapterName;
+}
+
+#endif // !Q_OS_WIN32
+
+} // anonymous namespace
+
+PassThruCanBackend::PassThruCanBackend(const QString &name, QObject *parent)
+ : QCanBusDevice(parent)
+ , m_deviceName (name)
+ , m_canIO (new PassThruCanIO())
+{
+ m_canIO->moveToThread(&m_ioThread);
+
+ // Signals emitted by the I/O thread, to be queued.
+ connect(m_canIO, &PassThruCanIO::errorOccurred,
+ this, &PassThruCanBackend::setError);
+ connect(m_canIO, &PassThruCanIO::openFinished,
+ this, &PassThruCanBackend::ackOpenFinished);
+ connect(m_canIO, &PassThruCanIO::closeFinished,
+ this, &PassThruCanBackend::ackCloseFinished);
+ connect(m_canIO, &PassThruCanIO::messagesReceived,
+ this, &PassThruCanBackend::enqueueReceivedFrames);
+ connect(m_canIO, &PassThruCanIO::messagesSent,
+ this, &QCanBusDevice::framesWritten);
+}
+
+PassThruCanBackend::~PassThruCanBackend()
+{
+ if (state() != UnconnectedState) {
+ // If the I/O thread is still active at this point, we will have to
+ // wait for it to finish.
+ QEventLoop loop;
+ connect(&m_ioThread, &QThread::finished, &loop, &QEventLoop::quit);
+
+ if (state() != ClosingState)
+ disconnectDevice();
+
+ while (!m_ioThread.isFinished())
+ loop.exec(QEventLoop::ExcludeUserInputEvents);
+ }
+ m_canIO->deleteLater();
+}
+
+void PassThruCanBackend::setConfigurationParameter(int key, const QVariant &value)
+{
+ QCanBusDevice::setConfigurationParameter(key, value);
+
+ if (state() == ConnectedState)
+ applyConfig(key, value);
+}
+
+bool PassThruCanBackend::writeFrame(const QCanBusFrame &frame)
+{
+ if (state() != ConnectedState) {
+ setError(tr("Device is not connected"), WriteError);
+ return false;
+ }
+ if (!frame.isValid()) {
+ setError(tr("Invalid CAN bus frame"), WriteError);
+ return false;
+ }
+ if (frame.frameType() != QCanBusFrame::DataFrame) {
+ setError(tr("Unsupported CAN frame type"), WriteError);
+ return false;
+ }
+ // Push the frame directly to the write queue of the worker thread,
+ // bypassing the QCanBusDevice output queue. Despite the duplicated
+ // queue, things are cleaner this way as it avoids a reverse dependency
+ // from the worker object on the QCanBusDevice object.
+ return m_canIO->enqueueMessage(frame);
+}
+
+QString PassThruCanBackend::interpretErrorFrame(const QCanBusFrame &)
+{
+ // J2534 Pass-thru v04.04 does not seem to support error frames.
+ return {};
+}
+
+QList<QCanBusDeviceInfo> PassThruCanBackend::interfaces()
+{
+ QList<QCanBusDeviceInfo> list;
+#ifdef Q_OS_WIN32
+ QSettings entries (registryPath(), QSettings::NativeFormat);
+ const QStringList groups = entries.childGroups();
+
+ for (const auto &group : groups) {
+ entries.beginGroup(group);
+
+ const QString name = canAdapterName(entries);
+ if (!name.isEmpty())
+ list.append(createDeviceInfo(name));
+
+ entries.endGroup();
+ }
+#endif
+ return list;
+}
+
+bool PassThruCanBackend::open()
+{
+ if (Q_UNLIKELY(state() != ConnectingState)) {
+ qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Unexpected state on open");
+ return false;
+ }
+ // Support a special "adapter%subdevice" syntax to allow control of the
+ // device name passed to the J2534 library's PassThruOpen() function.
+ // If the "%subdevice" suffix is not used, the J2534 interface library
+ // will choose a default or ask the user.
+ const int splitPos = m_deviceName.indexOf(QChar::fromLatin1('%'));
+ const QString adapter = m_deviceName.left(splitPos);
+ QByteArray subDev;
+
+ if (splitPos >= 0)
+ subDev = m_deviceName.midRef(splitPos + 1).toLatin1();
+
+ const QString library = libraryForAdapter(adapter);
+ if (library.isEmpty()) {
+ setError(tr("Adapter not found: %1").arg(adapter), ConnectionError);
+ return false;
+ }
+ bool ok = false;
+ uint bitRate = configurationParameter(BitRateKey).toUInt(&ok);
+ if (!ok) {
+ bitRate = 500*1000; // default initial bit rate
+ setConfigurationParameter(BitRateKey, bitRate);
+ }
+ m_ioThread.start();
+
+ return QMetaObject::invokeMethod(m_canIO, "open", Qt::QueuedConnection,
+ Q_ARG(QString, library),
+ Q_ARG(QByteArray, subDev),
+ Q_ARG(uint, bitRate));
+}
+
+void PassThruCanBackend::close()
+{
+ if (Q_UNLIKELY(state() != ClosingState)) {
+ qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Unexpected state on close");
+ return;
+ }
+ QMetaObject::invokeMethod(m_canIO, "close", Qt::QueuedConnection);
+}
+
+void PassThruCanBackend::ackOpenFinished(bool success)
+{
+ // Do not transition to connected state if close() has been called
+ // in the meantime.
+ if (state() != ConnectingState)
+ return;
+
+ if (success) {
+ const QVariant loopback = configurationParameter(LoopbackKey);
+ if (loopback.toBool())
+ applyConfig(LoopbackKey, loopback);
+
+ QVariant filters = configurationParameter(RawFilterKey);
+ if (!filters.isValid()) {
+ // Configure default match-all filter.
+ filters = QVariant::fromValue(QList<Filter>{Filter{}});
+ setConfigurationParameter(RawFilterKey, filters);
+ }
+ applyConfig(RawFilterKey, filters);
+
+ QMetaObject::invokeMethod(m_canIO, "listen", Qt::QueuedConnection);
+
+ setState(ConnectedState);
+ } else {
+ setState(UnconnectedState);
+ }
+}
+
+void PassThruCanBackend::ackCloseFinished()
+{
+ m_ioThread.exit(0);
+ m_ioThread.wait();
+
+ setState(UnconnectedState);
+}
+
+void PassThruCanBackend::applyConfig(int key, const QVariant &value)
+{
+ QMetaObject::invokeMethod(m_canIO, "applyConfig", Qt::QueuedConnection,
+ Q_ARG(int, key), Q_ARG(QVariant, value));
+}
diff --git a/src/plugins/canbus/passthrucan/passthrucanbackend.h b/src/plugins/canbus/passthrucan/passthrucanbackend.h
new file mode 100644
index 0000000..864d1b4
--- /dev/null
+++ b/src/plugins/canbus/passthrucan/passthrucanbackend.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Ford Motor Company.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef PASSTHRUCAN_PASSTHRUCANBACKEND_H
+#define PASSTHRUCAN_PASSTHRUCANBACKEND_H
+
+#include <QtSerialBus/qcanbusdevice.h>
+#include <QtSerialBus/qcanbusframe.h>
+
+#include <QString>
+#include <QThread>
+#include <QVector>
+
+QT_BEGIN_NAMESPACE
+
+class PassThruCanIO;
+
+class PassThruCanBackend : public QCanBusDevice
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PassThruCanBackend)
+public:
+ explicit PassThruCanBackend(const QString &name, QObject *parent = nullptr);
+ virtual ~PassThruCanBackend();
+
+ void setConfigurationParameter(int key, const QVariant &value) override;
+ bool writeFrame(const QCanBusFrame &frame) override;
+ QString interpretErrorFrame(const QCanBusFrame &errorFrame) override;
+
+ static QList<QCanBusDeviceInfo> interfaces();
+
+protected:
+ bool open() override;
+ void close() override;
+
+private:
+ void ackOpenFinished(bool success);
+ void ackCloseFinished();
+ void applyConfig(int key, const QVariant &value);
+
+ QString m_deviceName;
+ QThread m_ioThread;
+ PassThruCanIO * m_canIO;
+};
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(QCanBusDevice::CanBusError)
+Q_DECLARE_METATYPE(QVector<QCanBusFrame>)
+
+#endif // PASSTHRUCAN_PASSTHRUCANBACKEND_H
diff --git a/src/plugins/canbus/passthrucan/passthrucanio.cpp b/src/plugins/canbus/passthrucan/passthrucanio.cpp
new file mode 100644
index 0000000..dc3d2c7
--- /dev/null
+++ b/src/plugins/canbus/passthrucan/passthrucanio.cpp
@@ -0,0 +1,314 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Ford Motor Company.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "passthrucanio.h"
+
+#include <QLoggingCategory>
+#include <QtEndian>
+
+#include <cstring>
+
+PassThruCanIO::PassThruCanIO(QObject *parent)
+ : QObject(parent)
+ , m_ioBuffer (8, J2534::Message(J2534::Protocol::CAN))
+{
+}
+
+PassThruCanIO::~PassThruCanIO()
+{
+}
+
+void PassThruCanIO::open(const QString &library, const QByteArray &subDev, uint bitRate)
+{
+ if (Q_UNLIKELY(m_passThru)) {
+ qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface already open");
+ emit openFinished(false);
+ return;
+ }
+ qCDebug(QT_CANBUS_PLUGINS_PASSTHRU, "Loading interface library: %ls",
+ qUtf16Printable(library));
+
+ m_passThru = new J2534::PassThru(library, this);
+ J2534::PassThru::Status openStatus = m_passThru->lastError();
+
+ if (openStatus == J2534::PassThru::NoError)
+ openStatus = m_passThru->open(subDev, &m_deviceId);
+
+ if (openStatus == J2534::PassThru::NoError
+ && m_passThru->connect(m_deviceId, J2534::Protocol::CAN,
+ J2534::PassThru::CAN29BitID | J2534::PassThru::CANIDBoth,
+ bitRate, &m_channelId) == J2534::PassThru::NoError) {
+ emit openFinished(true);
+ return;
+ }
+ emit errorOccurred(m_passThru->lastErrorString(),
+ QCanBusDevice::ConnectionError);
+
+ if (openStatus == J2534::PassThru::NoError
+ && m_passThru->close(m_deviceId) != J2534::PassThru::NoError)
+ qCWarning(QT_CANBUS_PLUGINS_PASSTHRU, "Failed to close pass-thru device");
+
+ delete m_passThru;
+ m_passThru = nullptr;
+
+ emit openFinished(false);
+}
+
+void PassThruCanIO::close()
+{
+ if (Q_LIKELY(m_passThru)) {
+ delete m_idleNotifier;
+ m_idleNotifier = nullptr;
+
+ if (m_passThru->disconnect(m_channelId) != J2534::PassThru::NoError
+ || m_passThru->close(m_deviceId) != J2534::PassThru::NoError) {
+
+ qCWarning(QT_CANBUS_PLUGINS_PASSTHRU, "Failed to close pass-thru device");
+ emit errorOccurred(m_passThru->lastErrorString(),
+ QCanBusDevice::ConnectionError);
+ }
+ delete m_passThru;
+ m_passThru = nullptr;
+ }
+ emit closeFinished();
+}
+
+void PassThruCanIO::applyConfig(int key, const QVariant &value)
+{
+ if (Q_UNLIKELY(!m_passThru)) {
+ qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface not open");
+ return;
+ }
+ bool success = true;
+
+ switch (key) {
+ case QCanBusDevice::RawFilterKey:
+ success = setMessageFilters(qvariant_cast<QList<QCanBusDevice::Filter>>(value));
+ break;
+ case QCanBusDevice::LoopbackKey:
+ success = setConfigValue(J2534::Config::Loopback, value.toBool());
+ break;
+ case QCanBusDevice::BitRateKey:
+ success = setConfigValue(J2534::Config::DataRate, value.toUInt());
+ break;
+ default:
+ emit errorOccurred(tr("Unsupported configuration key: %1").arg(key),
+ QCanBusDevice::ConfigurationError);
+ break;
+ }
+ if (!success) {
+ emit errorOccurred(tr("Configuration failed: %1").arg(m_passThru->lastErrorString()),
+ QCanBusDevice::ConfigurationError);
+ }
+}
+
+void PassThruCanIO::listen()
+{
+ if (Q_UNLIKELY(!m_passThru)) {
+ qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface not open");
+ return;
+ }
+ if (Q_UNLIKELY(m_idleNotifier)) {
+ qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Idle notifier already created");
+ return;
+ }
+ m_idleNotifier = new QTimer(this);
+ connect(m_idleNotifier, &QTimer::timeout, this, &PassThruCanIO::pollForMessages);
+
+ m_idleNotifier->start(0);
+}
+
+bool PassThruCanIO::enqueueMessage(const QCanBusFrame &frame)
+{
+ const QMutexLocker lock (&m_writeGuard);
+ m_writeQueue.append(frame);
+ return true;
+}
+
+bool PassThruCanIO::setMessageFilters(const QList<QCanBusDevice::Filter> &filters)
+{
+ if (m_passThru->clear(m_channelId, J2534::PassThru::MsgFilters) != J2534::PassThru::NoError)
+ return false;
+
+ J2534::Message pattern {J2534::Protocol::CAN};
+ pattern.setSize(4);
+ J2534::Message mask {J2534::Protocol::CAN};
+ mask.setSize(4);
+
+ for (const auto &filter : filters) {
+ if (filter.type != QCanBusFrame::DataFrame
+ && filter.type != QCanBusFrame::InvalidFrame) {
+ emit errorOccurred(tr("Configuration failed: unsupported filter type"),
+ QCanBusDevice::ConfigurationError);
+ break;
+ }
+ if (filter.format & QCanBusDevice::Filter::MatchExtendedFormat)
+ pattern.setRxStatus(J2534::Message::InCAN29BitID);
+ else
+ pattern.setRxStatus({});
+
+ if (filter.format != QCanBusDevice::Filter::MatchBaseAndExtendedFormat)
+ mask.setRxStatus(J2534::Message::InCAN29BitID);
+ else
+ mask.setRxStatus({});
+
+ qToBigEndian<quint32>(filter.frameId & filter.frameIdMask, pattern.data());
+ qToBigEndian<quint32>(filter.frameIdMask, mask.data());
+
+ if (m_passThru->startMsgFilter(m_channelId, J2534::PassThru::PassFilter,
+ mask, pattern) != J2534::PassThru::NoError)
+ return false;
+ }
+ return true;
+}
+
+bool PassThruCanIO::setConfigValue(J2534::Config::Parameter param, ulong value)
+{
+ const J2534::Config config {param, value};
+
+ return (m_passThru->setConfig(m_channelId, &config) == J2534::PassThru::NoError);
+}
+
+void PassThruCanIO::pollForMessages()
+{
+ if (Q_UNLIKELY(!m_passThru)) {
+ qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface not open");
+ return;
+ }
+ const bool writePending = writeMessages();
+ readMessages(writePending);
+}
+
+bool PassThruCanIO::writeMessages()
+{
+ ulong numMsgs = m_ioBuffer.size();
+ {
+ const QMutexLocker lock (&m_writeGuard);
+ numMsgs = qMin<ulong>(m_writeQueue.size(), numMsgs);
+
+ for (ulong i = 0; i < numMsgs; ++i) {
+ const QCanBusFrame &frame = m_writeQueue.at(i);
+ J2534::Message &msg = m_ioBuffer[i];
+
+ const QByteArray payload = frame.payload();
+ const ulong payloadSize = qMin<ulong>(payload.size(),
+ J2534::Message::maxSize - 4);
+ msg.setRxStatus({});
+ msg.setTimestamp(0);
+ msg.setSize(4 + payloadSize);
+ msg.setExtraDataIndex(0);
+
+ if (frame.hasExtendedFrameFormat())
+ msg.setTxFlags(J2534::Message::OutCAN29BitID);
+ else
+ msg.setTxFlags({});
+
+ qToBigEndian<quint32>(frame.frameId(), msg.data());
+ std::memcpy(msg.data() + 4, payload.data(), payloadSize);
+ }
+ }
+ if (numMsgs == 0)
+ return false;
+
+ const auto status = m_passThru->writeMsgs(m_channelId, m_ioBuffer.constData(),
+ &numMsgs, pollTimeout);
+ if (status == J2534::PassThru::BufferFull)
+ return false;
+
+ if (status != J2534::PassThru::NoError && status != J2534::PassThru::Timeout) {
+ emit errorOccurred(tr("Message write failed: %1").arg(m_passThru->lastErrorString()),
+ QCanBusDevice::WriteError);
+ return false;
+ }
+ if (numMsgs == 0)
+ return false;
+
+ bool morePending;
+ {
+ const QMutexLocker lock (&m_writeGuard);
+ // De-queue successfully written frames.
+ m_writeQueue.erase(m_writeQueue.begin(), m_writeQueue.begin() + numMsgs);
+ morePending = !m_writeQueue.isEmpty();
+ }
+ emit messagesSent(numMsgs);
+
+ return morePending;
+}
+
+void PassThruCanIO::readMessages(bool writePending)
+{
+ // If there are outgoing messages waiting to be written, just check
+ // for already received messages but do not block waiting for more.
+ const uint timeout = (writePending) ? 0 : pollTimeout;
+
+ ulong numMsgs = m_ioBuffer.size();
+ const auto status = m_passThru->readMsgs(m_channelId, m_ioBuffer.data(),
+ &numMsgs, timeout);
+ if (status == J2534::PassThru::BufferEmpty)
+ return;
+
+ if (status != J2534::PassThru::NoError && status != J2534::PassThru::Timeout) {
+ emit errorOccurred(tr("Message read failed: %1").arg(m_passThru->lastErrorString()),
+ QCanBusDevice::ReadError);
+ if (status != J2534::PassThru::BufferOverflow)
+ return;
+ }
+ const int numFrames = qMin<ulong>(m_ioBuffer.size(), numMsgs);
+ QVector<QCanBusFrame> frames;
+ frames.reserve(numFrames);
+
+ for (int i = 0; i < numFrames; ++i) {
+ const J2534::Message &msg = m_ioBuffer.at(i);
+ if (Q_UNLIKELY(msg.size() < 4)
+ || Q_UNLIKELY(msg.size() > J2534::Message::maxSize)) {
+ // This normally shouldn't happen, so a log message is appropriate.
+ qCWarning(QT_CANBUS_PLUGINS_PASSTHRU,
+ "Message with invalid size %lu received", msg.size());
+ continue;
+ }
+ const quint32 msgId = qFromBigEndian<quint32>(msg.data());
+ const QByteArray payload (msg.data() + 4, msg.size() - 4);
+
+ QCanBusFrame frame (msgId, payload);
+ frame.setExtendedFrameFormat((msg.rxStatus() & J2534::Message::InCAN29BitID) != 0);
+ frame.setLocalEcho((msg.rxStatus() & J2534::Message::InTxMsgType) != 0);
+ frame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(msg.timestamp()));
+
+ frames.append(std::move(frame));
+ }
+ if (!frames.isEmpty())
+ emit messagesReceived(std::move(frames));
+}
diff --git a/src/plugins/canbus/passthrucan/passthrucanio.h b/src/plugins/canbus/passthrucan/passthrucanio.h
new file mode 100644
index 0000000..071174c
--- /dev/null
+++ b/src/plugins/canbus/passthrucan/passthrucanio.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Ford Motor Company.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef PASSTHRUCAN_PASSTHRUCANIO_H
+#define PASSTHRUCAN_PASSTHRUCANIO_H
+
+#include "j2534passthru.h"
+
+#include <QtSerialBus/qcanbusdevice.h>
+#include <QtSerialBus/qcanbusframe.h>
+#include <QByteArray>
+#include <QList>
+#include <QMutex>
+#include <QObject>
+#include <QString>
+#include <QTimer>
+#include <QVariant>
+#include <QVector>
+
+QT_BEGIN_NAMESPACE
+
+class PassThruCanIO : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PassThruCanIO)
+public:
+ static const uint pollTimeout = 100; // ms
+
+ explicit PassThruCanIO(QObject *parent = nullptr);
+ virtual ~PassThruCanIO();
+
+ Q_INVOKABLE void open(const QString &library, const QByteArray &subDev, uint bitRate);
+ Q_INVOKABLE void close();
+ Q_INVOKABLE void applyConfig(int key, const QVariant &value);
+ Q_INVOKABLE void listen();
+
+ // Internally locked; safe to call directly from any thread.
+ bool enqueueMessage(const QCanBusFrame &frame);
+
+Q_SIGNALS:
+ void errorOccurred(const QString &description, QCanBusDevice::CanBusError error);
+ void messagesReceived(QVector<QCanBusFrame> frames);
+ void messagesSent(qint64 count);
+ void openFinished(bool success);
+ void closeFinished();
+
+private:
+ bool setMessageFilters(const QList<QCanBusDevice::Filter> &filters);
+ bool setConfigValue(J2534::Config::Parameter param, ulong value);
+ void pollForMessages();
+ bool writeMessages();
+ void readMessages(bool writePending);
+
+ J2534::PassThru * m_passThru = nullptr;
+ J2534::PassThru::Handle m_deviceId = 0;
+ J2534::PassThru::Handle m_channelId = 0;
+ QTimer * m_idleNotifier = nullptr;
+ QVector<J2534::Message> m_ioBuffer;
+ QMutex m_writeGuard;
+ QList<QCanBusFrame> m_writeQueue;
+};
+
+QT_END_NAMESPACE
+
+#endif // PASSTHRUCAN_PASSTHRUCANIO_H
diff --git a/src/plugins/canbus/passthrucan/plugin.json b/src/plugins/canbus/passthrucan/plugin.json
new file mode 100644
index 0000000..2026547
--- /dev/null
+++ b/src/plugins/canbus/passthrucan/plugin.json
@@ -0,0 +1,3 @@
+{
+ "Key": "passthrucan"
+}