1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 Andre Hartmann <aha_1980@gmx.de> |
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 "virtualcanbackend.h" |
38 | |
39 | #include <QtCore/qdatetime.h> |
40 | #include <QtCore/qloggingcategory.h> |
41 | #include <QtCore/qregularexpression.h> |
42 | |
43 | #include <QtNetwork/qtcpserver.h> |
44 | #include <QtNetwork/qtcpsocket.h> |
45 | |
46 | QT_BEGIN_NAMESPACE |
47 | |
48 | Q_DECLARE_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_VIRTUALCAN) |
49 | |
50 | enum { |
51 | ServerDefaultTcpPort = 35468, |
52 | VirtualChannels = 2 |
53 | }; |
54 | |
55 | static const char RemoteRequestFlag = 'R'; |
56 | static const char ExtendedFormatFlag = 'X'; |
57 | static const char FlexibleDataRateFlag = 'F'; |
58 | static const char BitRateSwitchFlag = 'B'; |
59 | static const char ErrorStateFlag = 'E'; |
60 | static const char LocalEchoFlag = 'L'; |
61 | |
62 | VirtualCanServer::VirtualCanServer(QObject *parent) |
63 | : QObject(parent) |
64 | { |
65 | qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] constructed." , this); |
66 | } |
67 | |
68 | VirtualCanServer::~VirtualCanServer() |
69 | { |
70 | qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] destructed." , this); |
71 | } |
72 | |
73 | void VirtualCanServer::start(quint16 port) |
74 | { |
75 | // If there is already a server object, return immediately |
76 | if (m_server) { |
77 | qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] is already running." , this); |
78 | return; |
79 | } |
80 | |
81 | // Otherwise try to start a new server. If there is already |
82 | // another server listen on the specified port, give up. |
83 | m_server = new QTcpServer(this); |
84 | if (!m_server->listen(address: QHostAddress::LocalHost, port)) { |
85 | qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, |
86 | "Server [%p] could not be started, port %d is already in use." , this, port); |
87 | m_server->deleteLater(); |
88 | m_server = nullptr; |
89 | return; |
90 | } |
91 | |
92 | // Server successfully started |
93 | connect(sender: m_server, signal: &QTcpServer::newConnection, receiver: this, slot: &VirtualCanServer::connected); |
94 | qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, |
95 | "Server [%p] started and listening on port %d." , this, port); |
96 | return; |
97 | } |
98 | |
99 | void VirtualCanServer::connected() |
100 | { |
101 | while (m_server->hasPendingConnections()) { |
102 | qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] client connected." , this); |
103 | QTcpSocket *next = m_server->nextPendingConnection(); |
104 | m_serverSockets.append(t: next); |
105 | connect(sender: next, signal: &QIODevice::readyRead, receiver: this, slot: &VirtualCanServer::readyRead); |
106 | connect(sender: next, signal: &QTcpSocket::disconnected, receiver: this, slot: &VirtualCanServer::disconnected); |
107 | } |
108 | } |
109 | |
110 | void VirtualCanServer::disconnected() |
111 | { |
112 | qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] client disconnected." , this); |
113 | |
114 | auto socket = qobject_cast<QTcpSocket *>(object: sender()); |
115 | Q_ASSERT(socket); |
116 | |
117 | m_serverSockets.removeOne(t: socket); |
118 | socket->deleteLater(); |
119 | } |
120 | |
121 | void VirtualCanServer::readyRead() |
122 | { |
123 | auto readSocket = qobject_cast<QTcpSocket *>(object: sender()); |
124 | Q_ASSERT(readSocket); |
125 | |
126 | while (readSocket->canReadLine()) { |
127 | const QByteArray command = readSocket->readLine().trimmed(); |
128 | qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, |
129 | "Server [%p] received: '%s'." , this, command.constData()); |
130 | |
131 | if (command.startsWith(c: "connect:" )) { |
132 | const QVariant interfaces = readSocket->property(name: "interfaces" ); |
133 | QStringList list = interfaces.toStringList(); |
134 | list.append(t: command.mid(index: int(strlen(s: "connect:" )))); |
135 | readSocket->setProperty(name: "interfaces" , value: list); |
136 | |
137 | } else if (command.startsWith(c: "disconnect:" )) { |
138 | const QVariant interfaces = readSocket->property(name: "interfaces" ); |
139 | QStringList list = interfaces.toStringList(); |
140 | list.removeAll(t: command.mid(index: int(strlen(s: "disconnect:" )))); |
141 | readSocket->setProperty(name: "interfaces" , value: list); |
142 | readSocket->disconnectFromHost(); |
143 | |
144 | } else { |
145 | const QByteArrayList commandList = command.split(sep: ':'); |
146 | Q_ASSERT(commandList.size() == 2); |
147 | |
148 | for (QTcpSocket *writeSocket : qAsConst(t&: m_serverSockets)) { |
149 | // Don't send the frame back to its origin |
150 | if (writeSocket == readSocket) |
151 | continue; |
152 | |
153 | // Send frame to all clients registered to the same interface as sender |
154 | const QVariant property = writeSocket->property(name: "interfaces" ); |
155 | if (!property.isValid()) |
156 | continue; |
157 | |
158 | const QStringList propertyList = property.toStringList(); |
159 | if (propertyList.contains(str: commandList.first())) |
160 | writeSocket->write(data: commandList.last() + '\n'); |
161 | } |
162 | } |
163 | } |
164 | } |
165 | |
166 | Q_GLOBAL_STATIC(VirtualCanServer, g_server) |
167 | |
168 | VirtualCanBackend::VirtualCanBackend(const QString &interface, QObject *parent) |
169 | : QCanBusDevice(parent) |
170 | { |
171 | m_url = QUrl(interface); |
172 | const QString canDevice = m_url.fileName(); |
173 | |
174 | const QRegularExpression re(QStringLiteral("can(\\d)" )); |
175 | const QRegularExpressionMatch match = re.match(subject: canDevice); |
176 | |
177 | if (Q_UNLIKELY(!match.hasMatch())) { |
178 | qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN, |
179 | "Invalid interface '%ls'." , qUtf16Printable(interface)); |
180 | setError(errorText: tr(s: "Invalid interface '%1'." ).arg(a: interface), QCanBusDevice::ConnectionError); |
181 | return; |
182 | } |
183 | |
184 | const uint channel = match.captured(nth: 1).toUInt(); |
185 | if (Q_UNLIKELY(channel >= VirtualChannels)) { |
186 | qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN, |
187 | "Invalid interface '%ls'." , qUtf16Printable(interface)); |
188 | setError(errorText: tr(s: "Invalid interface '%1'." ).arg(a: interface), QCanBusDevice::ConnectionError); |
189 | return; |
190 | } |
191 | |
192 | m_channel = channel; |
193 | } |
194 | |
195 | VirtualCanBackend::~VirtualCanBackend() |
196 | { |
197 | qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket destructed." , this); |
198 | } |
199 | |
200 | bool VirtualCanBackend::open() |
201 | { |
202 | setState(QCanBusDevice::ConnectingState); |
203 | |
204 | const QString host = m_url.host(); |
205 | const QHostAddress address = host.isEmpty() ? QHostAddress::LocalHost : QHostAddress(host); |
206 | const quint16 port = static_cast<quint16>(m_url.port(defaultPort: ServerDefaultTcpPort)); |
207 | |
208 | if (address.isLoopback()) |
209 | g_server->start(port); |
210 | |
211 | m_clientSocket = new QTcpSocket(this); |
212 | m_clientSocket->connectToHost(address, port, mode: QIODevice::ReadWrite); |
213 | connect(sender: m_clientSocket, signal: &QAbstractSocket::connected, receiver: this, slot: &VirtualCanBackend::clientConnected); |
214 | connect(sender: m_clientSocket, signal: &QAbstractSocket::disconnected, receiver: this, slot: &VirtualCanBackend::clientDisconnected); |
215 | connect(sender: m_clientSocket, signal: &QIODevice::readyRead, receiver: this, slot: &VirtualCanBackend::clientReadyRead); |
216 | qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket created." , this); |
217 | return true; |
218 | } |
219 | |
220 | void VirtualCanBackend::close() |
221 | { |
222 | qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] sends disconnect to server." , this); |
223 | |
224 | m_clientSocket->write(data: "disconnect:can" + QByteArray::number(m_channel) + '\n'); |
225 | } |
226 | |
227 | void VirtualCanBackend::setConfigurationParameter(int key, const QVariant &value) |
228 | { |
229 | if (key == QCanBusDevice::ReceiveOwnKey || key == QCanBusDevice::CanFdKey) |
230 | QCanBusDevice::setConfigurationParameter(key, value); |
231 | } |
232 | |
233 | /* |
234 | Protocol format: All data is in ASCII, one CAN message per line, |
235 | each line ends with line feed '\n'. |
236 | |
237 | Format: "<CAN-Channel>:<Flags>#<CAN-ID>#<Data-Bytes>\n" |
238 | Example: "can0:XF#123#123456\n" |
239 | |
240 | The first part is the destination CAN channel, "can0" or "can1", |
241 | followed by the flags list: |
242 | |
243 | * R - Remote Request |
244 | * X - Extended Frame Format |
245 | * F - Flexible Data Rate Format |
246 | * B - Bitrate Switch |
247 | * E - Error State Indicator |
248 | * L - Local Echo |
249 | |
250 | Afterwards the CAN-ID and the data follows, both separated by '#'. |
251 | */ |
252 | |
253 | bool VirtualCanBackend::writeFrame(const QCanBusFrame &frame) |
254 | { |
255 | if (Q_UNLIKELY(state() != ConnectedState)) { |
256 | qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN, "Error: Cannot write frame as client is not connected!" ); |
257 | return false; |
258 | } |
259 | |
260 | bool canFdEnabled = configurationParameter(key: QCanBusDevice::CanFdKey).toBool(); |
261 | if (Q_UNLIKELY(frame.hasFlexibleDataRateFormat() && !canFdEnabled)) { |
262 | qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN, |
263 | "Error: Cannot write CAN FD frame as CAN FD is not enabled!" ); |
264 | return false; |
265 | } |
266 | |
267 | QByteArray flags; |
268 | if (frame.frameType() == QCanBusFrame::RemoteRequestFrame) |
269 | flags.append(c: RemoteRequestFlag); |
270 | if (frame.hasExtendedFrameFormat()) |
271 | flags.append(c: ExtendedFormatFlag); |
272 | if (frame.hasFlexibleDataRateFormat()) |
273 | flags.append(c: FlexibleDataRateFlag); |
274 | if (frame.hasBitrateSwitch()) |
275 | flags.append(c: BitRateSwitchFlag); |
276 | if (frame.hasErrorStateIndicator()) |
277 | flags.append(c: ErrorStateFlag); |
278 | if (frame.hasLocalEcho()) |
279 | flags.append(c: LocalEchoFlag); |
280 | const QByteArray frameId = QByteArray::number(frame.frameId()); |
281 | const QByteArray command = "can" + QByteArray::number(m_channel) |
282 | + ':' + frameId + '#' + flags + '#' + frame.payload().toHex() + '\n'; |
283 | m_clientSocket->write(data: command); |
284 | |
285 | if (configurationParameter(key: QCanBusDevice::ReceiveOwnKey).toBool()) { |
286 | const qint64 timeStamp = QDateTime::currentDateTime().toMSecsSinceEpoch(); |
287 | QCanBusFrame echoFrame = frame; |
288 | echoFrame.setLocalEcho(true); |
289 | echoFrame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(usec: timeStamp * 1000)); |
290 | enqueueReceivedFrames(newFrames: {echoFrame}); |
291 | } |
292 | |
293 | emit framesWritten(framesCount: qint64(1)); |
294 | return true; |
295 | } |
296 | |
297 | QString VirtualCanBackend::interpretErrorFrame(const QCanBusFrame &errorFrame) |
298 | { |
299 | Q_UNUSED(errorFrame); |
300 | return QString(); |
301 | } |
302 | |
303 | QList<QCanBusDeviceInfo> VirtualCanBackend::interfaces() |
304 | { |
305 | QList<QCanBusDeviceInfo> result; |
306 | |
307 | for (int channel = 0; channel < VirtualChannels; ++channel) { |
308 | result.append(t: std::move(createDeviceInfo( |
309 | QStringLiteral("can%1" ).arg(a: channel), serialNumber: QString(), |
310 | QStringLiteral("Qt Virtual CAN bus" ), channel, |
311 | isVirtual: true, isFlexibleDataRateCapable: true))); |
312 | } |
313 | |
314 | return result; |
315 | } |
316 | |
317 | void VirtualCanBackend::clientConnected() |
318 | { |
319 | qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket connected." , this); |
320 | m_clientSocket->write(data: "connect:can" + QByteArray::number(m_channel) + '\n'); |
321 | |
322 | setState(QCanBusDevice::ConnectedState); |
323 | } |
324 | |
325 | void VirtualCanBackend::clientDisconnected() |
326 | { |
327 | qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket disconnected." , this); |
328 | |
329 | setState(UnconnectedState); |
330 | } |
331 | |
332 | void VirtualCanBackend::clientReadyRead() |
333 | { |
334 | while (m_clientSocket->canReadLine()) { |
335 | const QByteArray answer = m_clientSocket->readLine().trimmed(); |
336 | qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] received: '%s'." , |
337 | this, answer.constData()); |
338 | |
339 | if (answer.startsWith(a: "disconnect:can" + QByteArray::number(m_channel))) { |
340 | m_clientSocket->disconnectFromHost(); |
341 | continue; |
342 | } |
343 | |
344 | const QByteArrayList list = answer.split(sep: '#'); |
345 | Q_ASSERT(list.size() == 3); |
346 | |
347 | const quint32 id = list.at(i: 0).toUInt(); |
348 | const QByteArray flags = list.at(i: 1); |
349 | const QByteArray data = QByteArray::fromHex(hexEncoded: list.at(i: 2)); |
350 | const qint64 timeStamp = QDateTime::currentDateTime().toMSecsSinceEpoch(); |
351 | QCanBusFrame frame(id, data); |
352 | frame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(usec: timeStamp * 1000)); |
353 | if (flags.contains(c: RemoteRequestFlag)) |
354 | frame.setFrameType(QCanBusFrame::RemoteRequestFrame); |
355 | frame.setExtendedFrameFormat(flags.contains(c: ExtendedFormatFlag)); |
356 | frame.setFlexibleDataRateFormat(flags.contains(c: FlexibleDataRateFlag)); |
357 | frame.setBitrateSwitch(flags.contains(c: BitRateSwitchFlag)); |
358 | frame.setErrorStateIndicator(flags.contains(c: ErrorStateFlag)); |
359 | frame.setLocalEcho(flags.contains(c: LocalEchoFlag)); |
360 | enqueueReceivedFrames(newFrames: {frame}); |
361 | } |
362 | } |
363 | |
364 | QT_END_NAMESPACE |
365 | |