1/****************************************************************************
2**
3** Copyright (C) 2017 Ford Motor Company.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtSerialBus module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "passthrucanbackend.h"
38#include "passthrucanio.h"
39
40#include <QEventLoop>
41#include <QSettings>
42
43namespace {
44
45#ifdef Q_OS_WIN32
46
47static inline QString registryPath()
48{
49 return QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\PassThruSupport.04.04");
50}
51
52static QString canAdapterName(const QSettings &entries)
53{
54 const int supportsCan = entries.value(QStringLiteral("CAN")).toInt();
55 if (supportsCan)
56 return entries.value(QStringLiteral("Name")).toString();
57 return {};
58}
59
60static QString libraryForAdapter(const QString &adapterName)
61{
62 QString library;
63 QSettings entries (registryPath(), QSettings::NativeFormat);
64 const QStringList groups = entries.childGroups();
65
66 for (const auto &group : groups) {
67 entries.beginGroup(group);
68
69 const QString name = canAdapterName(entries);
70 if (!name.isEmpty() && (adapterName.isEmpty() ||
71 name.compare(adapterName, Qt::CaseInsensitive) == 0))
72 library = entries.value(QStringLiteral("FunctionLibrary")).toString();
73
74 entries.endGroup();
75
76 if (!library.isEmpty())
77 break;
78 }
79 return library;
80}
81
82#else // !Q_OS_WIN32
83
84static QString libraryForAdapter(const QString &adapterName)
85{
86 // Insert system-specific device name to J2534 library name mapping here.
87 // For now, allow the path to the J2534 library to be specified directly
88 // as the adapter name.
89 return adapterName;
90}
91
92#endif // !Q_OS_WIN32
93
94} // anonymous namespace
95
96PassThruCanBackend::PassThruCanBackend(const QString &name, QObject *parent)
97 : QCanBusDevice(parent)
98 , m_deviceName (name)
99 , m_canIO (new PassThruCanIO())
100{
101 m_canIO->moveToThread(thread: &m_ioThread);
102
103 // Signals emitted by the I/O thread, to be queued.
104 connect(sender: m_canIO, signal: &PassThruCanIO::errorOccurred,
105 receiver: this, slot: &PassThruCanBackend::setError);
106 connect(sender: m_canIO, signal: &PassThruCanIO::openFinished,
107 receiver: this, slot: &PassThruCanBackend::ackOpenFinished);
108 connect(sender: m_canIO, signal: &PassThruCanIO::closeFinished,
109 receiver: this, slot: &PassThruCanBackend::ackCloseFinished);
110 connect(sender: m_canIO, signal: &PassThruCanIO::messagesReceived,
111 receiver: this, slot: &PassThruCanBackend::enqueueReceivedFrames);
112 connect(sender: m_canIO, signal: &PassThruCanIO::messagesSent,
113 receiver: this, slot: &QCanBusDevice::framesWritten);
114}
115
116PassThruCanBackend::~PassThruCanBackend()
117{
118 if (state() != UnconnectedState) {
119 // If the I/O thread is still active at this point, we will have to
120 // wait for it to finish.
121 QEventLoop loop;
122 connect(sender: &m_ioThread, signal: &QThread::finished, receiver: &loop, slot: &QEventLoop::quit);
123
124 if (state() != ClosingState)
125 disconnectDevice();
126
127 while (!m_ioThread.isFinished())
128 loop.exec(flags: QEventLoop::ExcludeUserInputEvents);
129 }
130 m_canIO->deleteLater();
131}
132
133void PassThruCanBackend::setConfigurationParameter(int key, const QVariant &value)
134{
135 QCanBusDevice::setConfigurationParameter(key, value);
136
137 if (state() == ConnectedState)
138 applyConfig(key, value);
139}
140
141bool PassThruCanBackend::writeFrame(const QCanBusFrame &frame)
142{
143 if (state() != ConnectedState) {
144 setError(errorText: tr(s: "Device is not connected"), WriteError);
145 return false;
146 }
147 if (!frame.isValid()) {
148 setError(errorText: tr(s: "Invalid CAN bus frame"), WriteError);
149 return false;
150 }
151 if (frame.frameType() != QCanBusFrame::DataFrame) {
152 setError(errorText: tr(s: "Unsupported CAN frame type"), WriteError);
153 return false;
154 }
155 // Push the frame directly to the write queue of the worker thread,
156 // bypassing the QCanBusDevice output queue. Despite the duplicated
157 // queue, things are cleaner this way as it avoids a reverse dependency
158 // from the worker object on the QCanBusDevice object.
159 return m_canIO->enqueueMessage(frame);
160}
161
162QString PassThruCanBackend::interpretErrorFrame(const QCanBusFrame &)
163{
164 // J2534 Pass-thru v04.04 does not seem to support error frames.
165 return {};
166}
167
168QList<QCanBusDeviceInfo> PassThruCanBackend::interfaces()
169{
170 QList<QCanBusDeviceInfo> list;
171#ifdef Q_OS_WIN32
172 QSettings entries (registryPath(), QSettings::NativeFormat);
173 const QStringList groups = entries.childGroups();
174
175 for (const auto &group : groups) {
176 entries.beginGroup(group);
177
178 const QString name = canAdapterName(entries);
179 if (!name.isEmpty())
180 list.append(createDeviceInfo(name));
181
182 entries.endGroup();
183 }
184#endif
185 return list;
186}
187
188bool PassThruCanBackend::open()
189{
190 if (Q_UNLIKELY(state() != ConnectingState)) {
191 qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Unexpected state on open");
192 return false;
193 }
194 // Support a special "adapter%subdevice" syntax to allow control of the
195 // device name passed to the J2534 library's PassThruOpen() function.
196 // If the "%subdevice" suffix is not used, the J2534 interface library
197 // will choose a default or ask the user.
198 const int splitPos = m_deviceName.indexOf(c: QChar::fromLatin1(c: '%'));
199 const QString adapter = m_deviceName.left(n: splitPos);
200 QByteArray subDev;
201
202 if (splitPos >= 0)
203 subDev = m_deviceName.midRef(position: splitPos + 1).toLatin1();
204
205 const QString library = libraryForAdapter(adapterName: adapter);
206 if (library.isEmpty()) {
207 setError(errorText: tr(s: "Adapter not found: %1").arg(a: adapter), ConnectionError);
208 return false;
209 }
210 bool ok = false;
211 uint bitRate = configurationParameter(key: BitRateKey).toUInt(ok: &ok);
212 if (!ok) {
213 bitRate = 500*1000; // default initial bit rate
214 setConfigurationParameter(key: BitRateKey, value: bitRate);
215 }
216 m_ioThread.start();
217
218 return QMetaObject::invokeMethod(obj: m_canIO, member: "open", type: Qt::QueuedConnection,
219 Q_ARG(QString, library),
220 Q_ARG(QByteArray, subDev),
221 Q_ARG(uint, bitRate));
222}
223
224void PassThruCanBackend::close()
225{
226 if (Q_UNLIKELY(state() != ClosingState)) {
227 qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Unexpected state on close");
228 return;
229 }
230 QMetaObject::invokeMethod(obj: m_canIO, member: "close", type: Qt::QueuedConnection);
231}
232
233void PassThruCanBackend::ackOpenFinished(bool success)
234{
235 // Do not transition to connected state if close() has been called
236 // in the meantime.
237 if (state() != ConnectingState)
238 return;
239
240 if (success) {
241 const QVariant loopback = configurationParameter(key: LoopbackKey);
242 if (loopback.toBool())
243 applyConfig(key: LoopbackKey, value: loopback);
244
245 QVariant filters = configurationParameter(key: RawFilterKey);
246 if (!filters.isValid()) {
247 // Configure default match-all filter.
248 filters = QVariant::fromValue(value: QList<Filter>{Filter{}});
249 setConfigurationParameter(key: RawFilterKey, value: filters);
250 }
251 applyConfig(key: RawFilterKey, value: filters);
252
253 QMetaObject::invokeMethod(obj: m_canIO, member: "listen", type: Qt::QueuedConnection);
254
255 setState(ConnectedState);
256 } else {
257 setState(UnconnectedState);
258 }
259}
260
261void PassThruCanBackend::ackCloseFinished()
262{
263 m_ioThread.exit(retcode: 0);
264 m_ioThread.wait();
265
266 setState(UnconnectedState);
267}
268
269void PassThruCanBackend::applyConfig(int key, const QVariant &value)
270{
271 QMetaObject::invokeMethod(obj: m_canIO, member: "applyConfig", type: Qt::QueuedConnection,
272 Q_ARG(int, key), Q_ARG(QVariant, value));
273}
274

source code of qtserialbus/src/plugins/canbus/passthrucan/passthrucanbackend.cpp