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