1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 Denis Shienkov <denis.shienkov@gmail.com> |
4 | ** Copyright (C) 2017 The Qt Company Ltd. |
5 | ** Contact: http://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the QtSerialBus module of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:LGPL3$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see http://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at http://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU Lesser General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
22 | ** packaging of this file. Please review the following information to |
23 | ** ensure the GNU Lesser General Public License version 3 requirements |
24 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
25 | ** |
26 | ** GNU General Public License Usage |
27 | ** Alternatively, this file may be used under the terms of the GNU |
28 | ** General Public License version 2.0 or later as published by the Free |
29 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
30 | ** the packaging of this file. Please review the following information to |
31 | ** ensure the GNU General Public License version 2.0 requirements will be |
32 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
33 | ** |
34 | ** $QT_END_LICENSE$ |
35 | ** |
36 | ****************************************************************************/ |
37 | |
38 | #include "tinycanbackend.h" |
39 | #include "tinycanbackend_p.h" |
40 | |
41 | #include "tinycan_symbols_p.h" |
42 | |
43 | #include <QtSerialBus/qcanbusdevice.h> |
44 | |
45 | #include <QtCore/qtimer.h> |
46 | #include <QtCore/qmutex.h> |
47 | #include <QtCore/qcoreevent.h> |
48 | #include <QtCore/qloggingcategory.h> |
49 | |
50 | #include <algorithm> |
51 | |
52 | QT_BEGIN_NAMESPACE |
53 | |
54 | Q_DECLARE_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_TINYCAN) |
55 | |
56 | #ifndef LINK_LIBMHSTCAN |
57 | Q_GLOBAL_STATIC(QLibrary, mhstcanLibrary) |
58 | #endif |
59 | |
60 | bool TinyCanBackend::canCreate(QString *errorReason) |
61 | { |
62 | #ifdef LINK_LIBMHSTCAN |
63 | return true; |
64 | #else |
65 | static bool symbolsResolved = resolveTinyCanSymbols(mhstcanLibrary: mhstcanLibrary()); |
66 | if (Q_UNLIKELY(!symbolsResolved)) { |
67 | *errorReason = mhstcanLibrary()->errorString(); |
68 | return false; |
69 | } |
70 | return true; |
71 | #endif |
72 | } |
73 | |
74 | QList<QCanBusDeviceInfo> TinyCanBackend::interfaces() |
75 | { |
76 | QList<QCanBusDeviceInfo> result; |
77 | result.append(t: createDeviceInfo(QStringLiteral("can0.0" ))); |
78 | return result; |
79 | } |
80 | |
81 | namespace { |
82 | |
83 | struct TinyCanGlobal { |
84 | QList<TinyCanBackendPrivate *> channels; |
85 | QMutex mutex; |
86 | }; |
87 | |
88 | } // namespace |
89 | |
90 | Q_GLOBAL_STATIC(TinyCanGlobal, gTinyCan) |
91 | |
92 | class TinyCanWriteNotifier : public QTimer |
93 | { |
94 | // no Q_OBJECT macro! |
95 | public: |
96 | TinyCanWriteNotifier(TinyCanBackendPrivate *d, QObject *parent) |
97 | : QTimer(parent) |
98 | , dptr(d) |
99 | { |
100 | } |
101 | |
102 | protected: |
103 | void timerEvent(QTimerEvent *e) override |
104 | { |
105 | if (e->timerId() == timerId()) { |
106 | dptr->startWrite(); |
107 | return; |
108 | } |
109 | QTimer::timerEvent(e); |
110 | } |
111 | |
112 | private: |
113 | TinyCanBackendPrivate * const dptr; |
114 | }; |
115 | |
116 | static int driverRefCount = 0; |
117 | |
118 | static void DRV_CALLBACK_TYPE canRxEventCallback(quint32 index, TCanMsg *frame, qint32 count) |
119 | { |
120 | Q_UNUSED(frame); |
121 | Q_UNUSED(count); |
122 | |
123 | QMutexLocker lock(&gTinyCan->mutex); |
124 | for (TinyCanBackendPrivate *p : qAsConst(t&: gTinyCan->channels)) { |
125 | if (p->channelIndex == int(index)) { |
126 | p->startRead(); |
127 | return; |
128 | } |
129 | } |
130 | } |
131 | |
132 | TinyCanBackendPrivate::TinyCanBackendPrivate(TinyCanBackend *q) |
133 | : q_ptr(q) |
134 | { |
135 | startupDriver(); |
136 | |
137 | QMutexLocker lock(&gTinyCan->mutex); |
138 | gTinyCan->channels.append(t: this); |
139 | } |
140 | |
141 | TinyCanBackendPrivate::~TinyCanBackendPrivate() |
142 | { |
143 | cleanupDriver(); |
144 | |
145 | QMutexLocker lock(&gTinyCan->mutex); |
146 | gTinyCan->channels.removeAll(t: this); |
147 | } |
148 | |
149 | struct BitrateItem |
150 | { |
151 | int bitrate; |
152 | int code; |
153 | }; |
154 | |
155 | struct BitrateLessFunctor |
156 | { |
157 | bool operator()( const BitrateItem &item1, const BitrateItem &item2) const |
158 | { |
159 | return item1.bitrate < item2.bitrate; |
160 | } |
161 | }; |
162 | |
163 | static int bitrateCodeFromBitrate(int bitrate) |
164 | { |
165 | static const BitrateItem bitratetable[] = { |
166 | { .bitrate: 10000, CAN_10K_BIT }, |
167 | { .bitrate: 20000, CAN_20K_BIT }, |
168 | { .bitrate: 50000, CAN_50K_BIT }, |
169 | { .bitrate: 100000, CAN_100K_BIT }, |
170 | { .bitrate: 125000, CAN_125K_BIT }, |
171 | { .bitrate: 250000, CAN_250K_BIT }, |
172 | { .bitrate: 500000, CAN_500K_BIT }, |
173 | { .bitrate: 800000, CAN_800K_BIT }, |
174 | { .bitrate: 1000000, CAN_1M_BIT } |
175 | }; |
176 | |
177 | static const BitrateItem *endtable = bitratetable + (sizeof(bitratetable) / sizeof(*bitratetable)); |
178 | |
179 | const BitrateItem item = { .bitrate: bitrate , .code: 0 }; |
180 | const BitrateItem *where = std::lower_bound(first: bitratetable, last: endtable, val: item, comp: BitrateLessFunctor()); |
181 | return where != endtable ? where->code : -1; |
182 | } |
183 | |
184 | bool TinyCanBackendPrivate::open() |
185 | { |
186 | Q_Q(TinyCanBackend); |
187 | |
188 | { |
189 | char options[] = "AutoConnect=1;AutoReopen=0" ; |
190 | const int ret = ::CanSetOptions(options); |
191 | if (Q_UNLIKELY(ret < 0)) { |
192 | q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConnectionError); |
193 | return false; |
194 | } |
195 | } |
196 | |
197 | { |
198 | const int ret = ::CanDeviceOpen(channelIndex, nullptr); |
199 | if (Q_UNLIKELY(ret < 0)) { |
200 | q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConnectionError); |
201 | return false; |
202 | } |
203 | } |
204 | |
205 | { |
206 | const int ret = ::CanSetMode(channelIndex, OP_CAN_START, CAN_CMD_ALL_CLEAR); |
207 | if (Q_UNLIKELY(ret < 0)) { |
208 | q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConnectionError); |
209 | ::CanDeviceClose(channelIndex); |
210 | return false; |
211 | } |
212 | } |
213 | |
214 | writeNotifier = new TinyCanWriteNotifier(this, q); |
215 | writeNotifier->setInterval(0); |
216 | |
217 | isOpen = true; |
218 | return true; |
219 | } |
220 | |
221 | void TinyCanBackendPrivate::close() |
222 | { |
223 | Q_Q(TinyCanBackend); |
224 | |
225 | delete writeNotifier; |
226 | writeNotifier = nullptr; |
227 | |
228 | const int ret = ::CanDeviceClose(channelIndex); |
229 | if (Q_UNLIKELY(ret < 0)) |
230 | q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConnectionError); |
231 | |
232 | isOpen = false; |
233 | } |
234 | |
235 | bool TinyCanBackendPrivate::setConfigurationParameter(int key, const QVariant &value) |
236 | { |
237 | Q_Q(TinyCanBackend); |
238 | |
239 | switch (key) { |
240 | case QCanBusDevice::BitRateKey: |
241 | return setBitRate(value.toInt()); |
242 | default: |
243 | q->setError(errorText: TinyCanBackend::tr(s: "Unsupported configuration key: %1" ).arg(a: key), |
244 | QCanBusDevice::ConfigurationError); |
245 | return false; |
246 | } |
247 | } |
248 | |
249 | // These error codes taked from the errors.h file, which |
250 | // exists only in linux sources. |
251 | QString TinyCanBackendPrivate::systemErrorString(int errorCode) |
252 | { |
253 | switch (errorCode) { |
254 | case 0: |
255 | return TinyCanBackend::tr(s: "No error" ); |
256 | case -1: |
257 | return TinyCanBackend::tr(s: "Driver not initialized" ); |
258 | case -2: |
259 | return TinyCanBackend::tr(s: "Invalid parameters values were passed" ); |
260 | case -3: |
261 | return TinyCanBackend::tr(s: "Invalid index value" ); |
262 | case -4: |
263 | return TinyCanBackend::tr(s: "More invalid CAN-channel" ); |
264 | case -5: |
265 | return TinyCanBackend::tr(s: "General error" ); |
266 | case -6: |
267 | return TinyCanBackend::tr(s: "The FIFO cannot be written" ); |
268 | case -7: |
269 | return TinyCanBackend::tr(s: "The buffer cannot be written" ); |
270 | case -8: |
271 | return TinyCanBackend::tr(s: "The FIFO cannot be read" ); |
272 | case -9: |
273 | return TinyCanBackend::tr(s: "The buffer cannot be read" ); |
274 | case -10: |
275 | return TinyCanBackend::tr(s: "Variable not found" ); |
276 | case -11: |
277 | return TinyCanBackend::tr(s: "Reading of the variable does not permit" ); |
278 | case -12: |
279 | return TinyCanBackend::tr(s: "Reading buffer for variable too small" ); |
280 | case -13: |
281 | return TinyCanBackend::tr(s: "Writing of the variable does not permit" ); |
282 | case -14: |
283 | return TinyCanBackend::tr(s: "The string/stream to be written is to majority" ); |
284 | case -15: |
285 | return TinyCanBackend::tr(s: "Fell short min of value" ); |
286 | case -16: |
287 | return TinyCanBackend::tr(s: "Max value crossed" ); |
288 | case -17: |
289 | return TinyCanBackend::tr(s: "Access refuses" ); |
290 | case -18: |
291 | return TinyCanBackend::tr(s: "Invalid value of CAN speed" ); |
292 | case -19: |
293 | return TinyCanBackend::tr(s: "Invalid value of baud rate" ); |
294 | case -20: |
295 | return TinyCanBackend::tr(s: "Value not put" ); |
296 | case -21: |
297 | return TinyCanBackend::tr(s: "No connection to the hardware" ); |
298 | case -22: |
299 | return TinyCanBackend::tr(s: "Communication error to the hardware" ); |
300 | case -23: |
301 | return TinyCanBackend::tr(s: "Hardware sends wrong number of parameters" ); |
302 | case -24: |
303 | return TinyCanBackend::tr(s: "Not enough main memory" ); |
304 | case -25: |
305 | return TinyCanBackend::tr(s: "The system cannot provide the enough resources" ); |
306 | case -26: |
307 | return TinyCanBackend::tr(s: "A system call returns with an error" ); |
308 | case -27: |
309 | return TinyCanBackend::tr(s: "The main thread is occupied" ); |
310 | case -28: |
311 | return TinyCanBackend::tr(s: "User allocated memory not found" ); |
312 | case -29: |
313 | return TinyCanBackend::tr(s: "The main thread cannot be launched" ); |
314 | // the codes -30...-33 are skipped, as they belongs to sockets |
315 | default: |
316 | return TinyCanBackend::tr(s: "Unknown error" ); |
317 | } |
318 | } |
319 | |
320 | static int channelIndexFromName(const QString &interfaceName) |
321 | { |
322 | if (interfaceName == QStringLiteral("can0.0" )) |
323 | return INDEX_CAN_KANAL_A; |
324 | else if (interfaceName == QStringLiteral("can0.1" )) |
325 | return INDEX_CAN_KANAL_B; |
326 | else |
327 | return INDEX_INVALID; |
328 | } |
329 | |
330 | void TinyCanBackendPrivate::setupChannel(const QString &interfaceName) |
331 | { |
332 | channelIndex = channelIndexFromName(interfaceName); |
333 | } |
334 | |
335 | // Calls only in constructor |
336 | void TinyCanBackendPrivate::setupDefaultConfigurations() |
337 | { |
338 | Q_Q(TinyCanBackend); |
339 | |
340 | q->setConfigurationParameter(key: QCanBusDevice::BitRateKey, value: 500000); |
341 | } |
342 | |
343 | void TinyCanBackendPrivate::startWrite() |
344 | { |
345 | Q_Q(TinyCanBackend); |
346 | |
347 | if (!q->hasOutgoingFrames()) { |
348 | writeNotifier->stop(); |
349 | return; |
350 | } |
351 | |
352 | const QCanBusFrame frame = q->dequeueOutgoingFrame(); |
353 | const QByteArray payload = frame.payload(); |
354 | |
355 | TCanMsg message = {}; |
356 | |
357 | if (Q_UNLIKELY(payload.size() > int(sizeof(message.Data.Bytes)))) { |
358 | qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "Cannot write frame with payload size %d." , payload.size()); |
359 | } else { |
360 | message.Id = frame.frameId(); |
361 | message.Flags.Flag.Len = payload.size(); |
362 | message.Flags.Flag.Error = (frame.frameType() == QCanBusFrame::ErrorFrame); |
363 | message.Flags.Flag.RTR = (frame.frameType() == QCanBusFrame::RemoteRequestFrame); |
364 | message.Flags.Flag.TxD = 1; |
365 | message.Flags.Flag.EFF = frame.hasExtendedFrameFormat(); |
366 | |
367 | const qint32 messagesToWrite = 1; |
368 | ::memcpy(dest: message.Data.Bytes, src: payload.constData(), n: sizeof(message.Data.Bytes)); |
369 | const int ret = ::CanTransmit(channelIndex, &message, messagesToWrite); |
370 | if (Q_UNLIKELY(ret < 0)) |
371 | q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::WriteError); |
372 | else |
373 | emit q->framesWritten(framesCount: messagesToWrite); |
374 | } |
375 | |
376 | if (q->hasOutgoingFrames() && !writeNotifier->isActive()) |
377 | writeNotifier->start(); |
378 | } |
379 | |
380 | // this method is called from the different thread! |
381 | void TinyCanBackendPrivate::startRead() |
382 | { |
383 | Q_Q(TinyCanBackend); |
384 | |
385 | QVector<QCanBusFrame> newFrames; |
386 | |
387 | for (;;) { |
388 | if (!::CanReceiveGetCount(channelIndex)) |
389 | break; |
390 | |
391 | TCanMsg message = {}; |
392 | |
393 | const int messagesToRead = 1; |
394 | const int ret = ::CanReceive(channelIndex, &message, messagesToRead); |
395 | if (Q_UNLIKELY(ret < 0)) { |
396 | q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ReadError); |
397 | |
398 | TDeviceStatus status = {}; |
399 | |
400 | if (::CanGetDeviceStatus(channelIndex, &status) < 0) { |
401 | q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ReadError); |
402 | } else { |
403 | if (status.CanStatus == CAN_STATUS_BUS_OFF) { |
404 | qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "CAN bus is in off state, trying to reset the bus." ); |
405 | resetController(); |
406 | } |
407 | } |
408 | |
409 | continue; |
410 | } |
411 | |
412 | QCanBusFrame frame(message.Id, QByteArray(reinterpret_cast<char *>(message.Data.Bytes), |
413 | int(message.Flags.Flag.Len))); |
414 | frame.setTimeStamp(QCanBusFrame::TimeStamp(message.Time.Sec, message.Time.USec)); |
415 | frame.setExtendedFrameFormat(message.Flags.Flag.EFF); |
416 | |
417 | if (message.Flags.Flag.Error) |
418 | frame.setFrameType(QCanBusFrame::ErrorFrame); |
419 | else if (message.Flags.Flag.RTR) |
420 | frame.setFrameType(QCanBusFrame::RemoteRequestFrame); |
421 | else |
422 | frame.setFrameType(QCanBusFrame::DataFrame); |
423 | |
424 | newFrames.append(t: std::move(frame)); |
425 | } |
426 | |
427 | q->enqueueReceivedFrames(newFrames); |
428 | } |
429 | |
430 | void TinyCanBackendPrivate::startupDriver() |
431 | { |
432 | Q_Q(TinyCanBackend); |
433 | |
434 | if (driverRefCount == 0) { |
435 | const int ret = ::CanInitDriver(nullptr); |
436 | if (Q_UNLIKELY(ret < 0)) { |
437 | q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConnectionError); |
438 | return; |
439 | } |
440 | |
441 | ::CanSetRxEventCallback(&canRxEventCallback); |
442 | ::CanSetEvents(EVENT_ENABLE_RX_MESSAGES); |
443 | |
444 | } else if (Q_UNLIKELY(driverRefCount < 0)) { |
445 | qCCritical(QT_CANBUS_PLUGINS_TINYCAN, "Wrong driver reference counter: %d" , |
446 | driverRefCount); |
447 | return; |
448 | } |
449 | |
450 | ++driverRefCount; |
451 | } |
452 | |
453 | void TinyCanBackendPrivate::cleanupDriver() |
454 | { |
455 | --driverRefCount; |
456 | |
457 | if (Q_UNLIKELY(driverRefCount < 0)) { |
458 | qCCritical(QT_CANBUS_PLUGINS_TINYCAN, "Wrong driver reference counter: %d" , |
459 | driverRefCount); |
460 | driverRefCount = 0; |
461 | } else if (driverRefCount == 0) { |
462 | ::CanSetEvents(EVENT_DISABLE_ALL); |
463 | ::CanDownDriver(); |
464 | } |
465 | } |
466 | |
467 | void TinyCanBackendPrivate::resetController() |
468 | { |
469 | Q_Q(TinyCanBackend); |
470 | qint32 ret = ::CanSetMode(channelIndex, OP_CAN_RESET, CAN_CMD_NONE); |
471 | if (Q_UNLIKELY(ret < 0)) { |
472 | const QString errorString = systemErrorString(errorCode: ret); |
473 | qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "Cannot perform hardware reset: %ls" , |
474 | qUtf16Printable(errorString)); |
475 | q->setError(errorText: errorString, QCanBusDevice::CanBusError::ConfigurationError); |
476 | } |
477 | } |
478 | |
479 | bool TinyCanBackendPrivate::setBitRate(int bitrate) |
480 | { |
481 | Q_Q(TinyCanBackend); |
482 | |
483 | const int bitrateCode = bitrateCodeFromBitrate(bitrate); |
484 | if (Q_UNLIKELY(bitrateCode == -1)) { |
485 | q->setError(errorText: TinyCanBackend::tr(s: "Unsupported bitrate value" ), |
486 | QCanBusDevice::ConfigurationError); |
487 | return false; |
488 | } |
489 | |
490 | if (isOpen) { |
491 | const int ret = ::CanSetSpeed(channelIndex, bitrateCode); |
492 | if (Q_UNLIKELY(ret < 0)) { |
493 | q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConfigurationError); |
494 | return false; |
495 | } |
496 | } |
497 | |
498 | return true; |
499 | } |
500 | |
501 | TinyCanBackend::TinyCanBackend(const QString &name, QObject *parent) |
502 | : QCanBusDevice(parent) |
503 | , d_ptr(new TinyCanBackendPrivate(this)) |
504 | { |
505 | Q_D(TinyCanBackend); |
506 | |
507 | d->setupChannel(name); |
508 | d->setupDefaultConfigurations(); |
509 | |
510 | std::function<void()> f = std::bind(f: &TinyCanBackend::resetController, args: this); |
511 | setResetControllerFunction(f); |
512 | } |
513 | |
514 | TinyCanBackend::~TinyCanBackend() |
515 | { |
516 | close(); |
517 | delete d_ptr; |
518 | } |
519 | |
520 | bool TinyCanBackend::open() |
521 | { |
522 | Q_D(TinyCanBackend); |
523 | |
524 | if (!d->isOpen) { |
525 | if (!d->open()) { |
526 | close(); // sets UnconnectedState |
527 | return false; |
528 | } |
529 | |
530 | // apply all stored configurations |
531 | const auto keys = configurationKeys(); |
532 | for (int key : keys) { |
533 | const QVariant param = configurationParameter(key); |
534 | const bool success = d->setConfigurationParameter(key, value: param); |
535 | if (Q_UNLIKELY(!success)) { |
536 | qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "Cannot apply parameter: %d with value: %ls." , |
537 | key, qUtf16Printable(param.toString())); |
538 | } |
539 | } |
540 | } |
541 | |
542 | setState(QCanBusDevice::ConnectedState); |
543 | return true; |
544 | } |
545 | |
546 | void TinyCanBackend::close() |
547 | { |
548 | Q_D(TinyCanBackend); |
549 | |
550 | d->close(); |
551 | |
552 | setState(QCanBusDevice::UnconnectedState); |
553 | } |
554 | |
555 | void TinyCanBackend::setConfigurationParameter(int key, const QVariant &value) |
556 | { |
557 | Q_D(TinyCanBackend); |
558 | |
559 | if (d->setConfigurationParameter(key, value)) |
560 | QCanBusDevice::setConfigurationParameter(key, value); |
561 | } |
562 | |
563 | bool TinyCanBackend::writeFrame(const QCanBusFrame &newData) |
564 | { |
565 | Q_D(TinyCanBackend); |
566 | |
567 | if (Q_UNLIKELY(state() != QCanBusDevice::ConnectedState)) |
568 | return false; |
569 | |
570 | if (Q_UNLIKELY(!newData.isValid())) { |
571 | setError(errorText: tr(s: "Cannot write invalid QCanBusFrame" ), QCanBusDevice::WriteError); |
572 | return false; |
573 | } |
574 | |
575 | if (Q_UNLIKELY(newData.frameType() != QCanBusFrame::DataFrame |
576 | && newData.frameType() != QCanBusFrame::RemoteRequestFrame |
577 | && newData.frameType() != QCanBusFrame::ErrorFrame)) { |
578 | setError(errorText: tr(s: "Unable to write a frame with unacceptable type" ), |
579 | QCanBusDevice::WriteError); |
580 | return false; |
581 | } |
582 | |
583 | // CAN FD frame format not supported at this stage |
584 | if (Q_UNLIKELY(newData.hasFlexibleDataRateFormat())) { |
585 | setError(errorText: tr(s: "CAN FD frame format not supported." ), QCanBusDevice::WriteError); |
586 | return false; |
587 | } |
588 | |
589 | enqueueOutgoingFrame(newFrame: newData); |
590 | |
591 | if (!d->writeNotifier->isActive()) |
592 | d->writeNotifier->start(); |
593 | |
594 | return true; |
595 | } |
596 | |
597 | // TODO: Implement me |
598 | QString TinyCanBackend::interpretErrorFrame(const QCanBusFrame &errorFrame) |
599 | { |
600 | Q_UNUSED(errorFrame); |
601 | |
602 | return QString(); |
603 | } |
604 | |
605 | void TinyCanBackend::resetController() |
606 | { |
607 | Q_D(TinyCanBackend); |
608 | d->resetController(); |
609 | } |
610 | |
611 | QT_END_NAMESPACE |
612 | |