summaryrefslogtreecommitdiffstats
path: root/src
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
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')
-rw-r--r--src/plugins/canbus/canbus.pro2
-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
-rw-r--r--src/serialbus/doc/src/passthrucan.qdoc155
-rw-r--r--src/serialbus/doc/src/qtcanbus-backends.qdoc4
12 files changed, 1578 insertions, 1 deletions
diff --git a/src/plugins/canbus/canbus.pro b/src/plugins/canbus/canbus.pro
index 37db8a3..0eddffc 100644
--- a/src/plugins/canbus/canbus.pro
+++ b/src/plugins/canbus/canbus.pro
@@ -7,6 +7,6 @@ qtConfig(socketcan) {
}
qtConfig(library) {
- SUBDIRS += peakcan tinycan
+ SUBDIRS += passthrucan peakcan tinycan
win32:SUBDIRS += systeccan vectorcan
}
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"
+}
diff --git a/src/serialbus/doc/src/passthrucan.qdoc b/src/serialbus/doc/src/passthrucan.qdoc
new file mode 100644
index 0000000..2f29926
--- /dev/null
+++ b/src/serialbus/doc/src/passthrucan.qdoc
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Ford Motor Company.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: http://www.gnu.org/copyleft/fdl.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+/*!
+ \page qtserialbus-passthrucan-overview.html
+ \title Using PassThruCAN Plugin
+
+ \brief Overview of how to use the J2534 Pass-Thru CAN plugin.
+
+ The Pass-Thru CAN plugin accesses CAN adapters via the SAE J2534 Pass-Thru API.
+ SAE J2534 is a standard for accessing vehicle busses from an x86 Windows PC.
+ Although the API is specified only for 32-bit Windows, some vendors also provide
+ implementations for 64-bit Windows and other operating systems such as Linux.
+
+ \section1 PassThruCAN usage
+
+ To use PassThruCAN, the corresponding vendor drivers for the CAN adapter must
+ be installed. The vendor must also provide an implementation of the J2534 API
+ by way of a shared library. Currently, only version 04.04 of the Pass-Thru API
+ is supported.
+
+ When using an x64 build of Qt, this plugin only works if the CAN device vendor
+ also provides a 64-bit version of the J2534 Pass-Thru interface library. If the
+ vendor provides only a 32-bit J2534 interface, a 32-bit build of Qt is required
+ to make use of it.
+
+ For automatic device discovery, the vendor software must also list and describe
+ the available adapters in the Windows registry. On systems other than Windows,
+ automatic discovery is currently not supported.
+
+ \section1 Creating CAN Bus Devices
+
+ At first it is necessary to check that QCanBus provides the desired plugin:
+
+ \code
+ if (QCanBus::instance()->plugins().contains(QStringLiteral("passthrucan"))) {
+ // plugin available
+ }
+ \endcode
+
+ Where \e passthrucan is the plugin name.
+
+ On Windows, automatic device discovery should be used to list the available
+ CAN adapters accessible via the Pass-Thru API:
+
+ \code
+ const auto adapters = QCanBus::instance()->
+ availableDevices(QStringLiteral("passthrucan"));
+ for (const QCanBusDeviceInfo &info : adapters) {
+ // List available adapter in the user interface.
+ uiListBox->addItem(info.name());
+ }
+ \endcode
+
+ On other operating systems, the list of discovered adapters will be empty.
+ Instead, the full path to the vendor-provided J2534 interface library
+ should be provided in lieu of the device name:
+
+ \code
+ QCanBusDevice *device = QCanBus::instance()->createDevice(
+ QStringLiteral("passthrucan"), QStringLiteral("/path/to/libj2534-vendor.so"));
+ \endcode
+
+ For special needs, it is also possible to pass a vendor-specific device
+ name argument when opening the Pass-Thru adapter:
+
+ \code
+ QCanBusDevice *device = QCanBus::instance()->createDevice(
+ QStringLiteral("passthrucan"), info.name() + QChar::fromLatin1('%') + deviceName);
+ \endcode
+
+ All operations on the Pass-Thru CAN bus device are executed asynchronously,
+ including connect and disconnect. In order to be notified when the device
+ is ready for reading and writing CAN frames, connect to the
+ \l {QCanBusDevice::}{stateChanged(QCanBusDevice::CanBusDeviceState state)}
+ signal:
+
+ \code
+ connect(device, &QCanBusDevice::stateChanged,
+ this, &MyClass::canStateChanged);
+ device->connectDevice();
+ \endcode
+
+ \l {QCanBusDevice::}{state()} will return \l {QCanBusDevice::}{ConnectedState}
+ once the CAN adapter has been successfully connected to. The device is then
+ open for writing and reading CAN frames:
+
+ \code
+ QCanBusFrame frame;
+ frame.setFrameId(8);
+ frame.setPayload(QByteArray("\xA3\x6E\x74\x9C", 4));
+ device->writeFrame(frame);
+ \endcode
+
+ The reading can be done using the \l {QCanBusDevice::}{readFrame()} method. The
+ \l {QCanBusDevice::}{framesReceived()} signal is emitted when at least one new frame
+ is available for reading:
+
+ \code
+ QCanBusFrame frame = device->readFrame();
+ \endcode
+
+ The Pass-Thru CAN plugin supports the following configuration options
+ controllable via \l {QCanBusDevice::}{setConfigurationParameter()}:
+
+ \table
+ \header
+ \li Configuration parameter key
+ \li Description
+ \row
+ \li QCanBusDevice::LoopbackKey
+ \li When enabled, if a CAN frame is transmitted on the CAN bus, a local
+ echo of this frame will be received by the CAN adapter. The echo
+ frames are marked with QCanBusFrame::hasLocalEcho(). By default,
+ loopback mode is disabled.
+ \row
+ \li QCanBusDevice::RawFilterKey
+ \li This option allows setting up filters for incoming CAN bus messages.
+ If provided, the value should be a \l {QList<QCanBusDevice::Filter>}.
+ Only data frame ID filters are supported. By default, data frames
+ with any ID are accepted.
+ \row
+ \li QCanBusDevice::BitRateKey
+ \li The bit rate of the CAN bus as an unsigned integer, in bit/s. The
+ default bit rate is 500000 (500 kbit/s). Setting the bit rate after
+ the device has already been connected may trigger an implicit
+ reinitialization of the CAN interface.
+ \endtable
+
+ The Pass-Thru CAN plugin supports extended frame format (29-bit IDs), but not
+ flexible data-rate (CAN FD).
+ */
diff --git a/src/serialbus/doc/src/qtcanbus-backends.qdoc b/src/serialbus/doc/src/qtcanbus-backends.qdoc
index e18adf6..1c4cd74 100644
--- a/src/serialbus/doc/src/qtcanbus-backends.qdoc
+++ b/src/serialbus/doc/src/qtcanbus-backends.qdoc
@@ -61,6 +61,10 @@
\li \l {Using SocketCAN Plugin}{SocketCAN} (\c socketcan)
\li CAN bus plugin using Linux sockets and open source drivers.
\row
+ \li CAN via SAE J2534 Pass-Thru
+ \li \l {Using PassThruCAN Plugin}{PassThruCAN} (\c passthrucan)
+ \li CAN bus plugin using the SAE J2534 Pass-Thru interface.
+ \row
\li SYS TEC electronic
\li \l {Using SystecCAN Backend}{SystecCAN} (\c systeccan)
\li CAN bus backend using the SYS TEC CAN adapters.