diff options
Diffstat (limited to 'src/plugins/canbus/passthrucan/passthrucanbackend.cpp')
-rw-r--r-- | src/plugins/canbus/passthrucan/passthrucanbackend.cpp | 273 |
1 files changed, 273 insertions, 0 deletions
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)); +} |