blob: 2c181605667c844c4f8d5264f58e5d8ec6087fb7 [file] [log] [blame]
/****************************************************************************
**
** 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));
}