summaryrefslogtreecommitdiffstats
path: root/src/plugins/canbus/passthrucan
diff options
context:
space:
mode:
authorDaniel Elstner <daniel.elstner.ford@kdab.com>2017-11-29 14:33:18 +0100
committerDaniel Elstner <daniel.elstner@kdab.com>2017-11-30 14:02:27 +0000
commit4ec837324894a13c00e7f1a9a13e79ebdc4f4ba5 (patch)
treec00fab668ce059e3db54a9417998bcb5c81d6bc3 /src/plugins/canbus/passthrucan
parente0a745d241822193f877a801151ff3f12a4967f9 (diff)
Add J2534 Pass-Thru CAN plugin
[ChangeLog] Add J2534 Pass-Thru CAN plugin SAE J2534 is a standard for communication between a computer and a vehicle. The "passthrucan" plugin adds support for communicating with a vehicle's CAN bus via the generic J2534 Pass-Thru API. The J2534 Pass-Thru API v04.04 is only specified for 32-bit Windows, but implementations for 64-bit Windows and other platforms do exist. Automatic device discovery is based on the Windows registry and thus supported on Windows only. However, this plugin alternatively allows users to specify the path to a J2534 vendor library to load. Any CAN interface adapters that ship with a compliant J2534 Pass-Thru interface library (v04.04) for the target platform should work with this plugin. It should be noted though that driver support for x64 is as of yet rare, thus a 32-bit Qt build may be required to make use of the passthrucan plugin. Change-Id: I55280d455828d9cf0ad03690224b924b39c17cf7 Reviewed-by: André Hartmann <aha_1980@gmx.de> Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
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"
+}