// Copyright (C) 2017 Ford Motor Company. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "passthrucanbackend.h" #include "passthrucanio.h" #include #include 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(ConfigurationKey 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 PassThruCanBackend::interfaces() { QList 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(QStringLiteral("passthrucan"), name, false, false)); entries.endGroup(); } #endif return list; } QCanBusDeviceInfo PassThruCanBackend::deviceInfo() const { return createDeviceInfo(QStringLiteral("passthrucan"), m_deviceName, false, false); } 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 = QStringView{m_deviceName}.mid(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, [this, library, subDev, bitRate] { m_canIO->open(library, subDev, bitRate); }, Qt::QueuedConnection); } void PassThruCanBackend::close() { if (Q_UNLIKELY(state() != ClosingState)) { qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Unexpected state on close"); return; } QMetaObject::invokeMethod(m_canIO, &PassThruCanIO::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{}}); setConfigurationParameter(RawFilterKey, filters); } applyConfig(RawFilterKey, filters); QMetaObject::invokeMethod(m_canIO, &PassThruCanIO::listen, Qt::QueuedConnection); setState(ConnectedState); } else { setState(UnconnectedState); } } void PassThruCanBackend::ackCloseFinished() { m_ioThread.exit(0); m_ioThread.wait(); setState(UnconnectedState); } void PassThruCanBackend::applyConfig(QCanBusDevice::ConfigurationKey key, const QVariant &value) { QMetaObject::invokeMethod(m_canIO, [this, key, value] { m_canIO->applyConfig(key, value); }, Qt::QueuedConnection); }