| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 Andre Hartmann <aha_1980@gmx.de> |
| ** 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 "systeccanbackend.h" |
| #include "systeccanbackend_p.h" |
| #include "systeccan_symbols_p.h" |
| |
| #include <QtSerialBus/qcanbusdevice.h> |
| |
| #include <QtCore/qcoreapplication.h> |
| #include <QtCore/qcoreevent.h> |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qloggingcategory.h> |
| #include <QtCore/qregularexpression.h> |
| #include <QtCore/qtimer.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_DECLARE_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_SYSTECCAN) |
| |
| Q_GLOBAL_STATIC(QLibrary, systecLibrary) |
| |
| bool SystecCanBackend::canCreate(QString *errorReason) |
| { |
| static bool symbolsResolved = resolveSystecCanSymbols(systecLibrary()); |
| if (Q_UNLIKELY(!symbolsResolved)) { |
| *errorReason = systecLibrary()->errorString(); |
| return false; |
| } |
| return true; |
| } |
| |
| QCanBusDeviceInfo SystecCanBackend::createDeviceInfo(const QString &serialNumber, |
| const QString &description, |
| uint deviceNumber, |
| int channelNumber) |
| { |
| const QString name = QString::fromLatin1("can%1.%2").arg(deviceNumber).arg(channelNumber); |
| return QCanBusDevice::createDeviceInfo(name, serialNumber, description, channelNumber, false, false); |
| } |
| |
| static QString descriptionString(uint productCode) |
| { |
| switch (productCode & USBCAN_PRODCODE_MASK_PID) { |
| case USBCAN_PRODCODE_PID_GW001: return QStringLiteral("USB-CANmodul (G1)"); |
| case USBCAN_PRODCODE_PID_GW002: return QStringLiteral("USB-CANmodul (G2)"); |
| case USBCAN_PRODCODE_PID_MULTIPORT: return QStringLiteral("Multiport CAN-to-USB (G3)"); |
| case USBCAN_PRODCODE_PID_BASIC: return QStringLiteral("USB-CANmodul1 (G3)"); |
| case USBCAN_PRODCODE_PID_ADVANCED: return QStringLiteral("USB-CANmodul2 (G3)"); |
| case USBCAN_PRODCODE_PID_USBCAN8: return QStringLiteral("USB-CANmodul8 (G3)"); |
| case USBCAN_PRODCODE_PID_USBCAN16: return QStringLiteral("USB-CANmodul16 (G3)"); |
| case USBCAN_PRODCODE_PID_ADVANCED_G4: return QStringLiteral("USB-CANmodul2 (G4)"); |
| case USBCAN_PRODCODE_PID_BASIC_G4: return QStringLiteral("USB-CANmodul1 (G4)"); |
| default: return QStringLiteral("Unknown"); |
| } |
| } |
| |
| static void DRV_CALLBACK_TYPE ucanEnumCallback(DWORD index, BOOL isUsed, |
| tUcanHardwareInfoEx *hardwareInfo, |
| tUcanHardwareInitInfo *initInfo, |
| void *args) |
| { |
| auto result = static_cast<QList<QCanBusDeviceInfo> *>(args); |
| |
| Q_UNUSED(index); |
| Q_UNUSED(isUsed); |
| |
| const QString serialNumber = QString::number(hardwareInfo->m_dwSerialNr); |
| const QString description = descriptionString(hardwareInfo->m_dwProductCode); |
| result->append(std::move(SystecCanBackend::createDeviceInfo(serialNumber, description, |
| hardwareInfo->m_bDeviceNr, 0))); |
| if (USBCAN_CHECK_SUPPORT_TWO_CHANNEL(hardwareInfo)) { |
| result->append(std::move(SystecCanBackend::createDeviceInfo(serialNumber, description, |
| hardwareInfo->m_bDeviceNr, 1))); |
| } |
| |
| initInfo->m_fTryNext = true; // continue enumerating with next device |
| } |
| |
| QList<QCanBusDeviceInfo> SystecCanBackend::interfaces() |
| { |
| QList<QCanBusDeviceInfo> result; |
| |
| ::UcanEnumerateHardware(&ucanEnumCallback, &result, false, 0, quint8(~0), 0, quint32(~0), 0, quint32(~0)); |
| |
| return result; |
| } |
| |
| class OutgoingEventNotifier : public QTimer |
| { |
| public: |
| OutgoingEventNotifier(SystecCanBackendPrivate *d, QObject *parent) : |
| QTimer(parent), |
| dptr(d) |
| { |
| } |
| |
| protected: |
| void timerEvent(QTimerEvent *e) override |
| { |
| if (e->timerId() == timerId()) { |
| dptr->startWrite(); |
| return; |
| } |
| QTimer::timerEvent(e); |
| } |
| |
| private: |
| SystecCanBackendPrivate * const dptr; |
| }; |
| |
| SystecCanBackendPrivate::SystecCanBackendPrivate(SystecCanBackend *q) : |
| q_ptr(q), |
| incomingEventHandler(new IncomingEventHandler(this, q)) |
| { |
| } |
| |
| static uint bitrateCodeFromBitrate(int bitrate) |
| { |
| struct BitrateItem { |
| int bitrate; |
| uint code; |
| } bitrateTable[] = { |
| { 10000, USBCAN_BAUDEX_10kBit }, |
| { 20000, USBCAN_BAUDEX_20kBit }, |
| { 50000, USBCAN_BAUDEX_50kBit }, |
| { 100000, USBCAN_BAUDEX_100kBit }, |
| { 125000, USBCAN_BAUDEX_125kBit }, |
| { 250000, USBCAN_BAUDEX_250kBit }, |
| { 500000, USBCAN_BAUDEX_500kBit }, |
| { 800000, USBCAN_BAUDEX_800kBit }, |
| { 1000000, USBCAN_BAUDEX_1MBit } |
| }; |
| |
| const int entries = (sizeof(bitrateTable) / sizeof(*bitrateTable)); |
| for (int i = 0; i < entries; ++i) |
| if (bitrateTable[i].bitrate == bitrate) |
| return bitrateTable[i].code; |
| |
| return 0; |
| } |
| |
| void IncomingEventHandler::customEvent(QEvent *event) |
| { |
| dptr->eventHandler(event); |
| } |
| |
| /* |
| * Do not call functions of USBCAN32.DLL directly from this callback handler. |
| * Use events or windows messages to notify the event to the application. |
| */ |
| static void DRV_CALLBACK_TYPE ucanCallback(tUcanHandle, quint32 event, quint8, void *args) |
| { |
| QEvent::Type type = static_cast<QEvent::Type>(QEvent::User + event); |
| IncomingEventHandler *handler = static_cast<IncomingEventHandler *>(args); |
| qApp->postEvent(handler, new QEvent(type)); |
| } |
| |
| bool SystecCanBackendPrivate::open() |
| { |
| Q_Q(SystecCanBackend); |
| |
| const UCANRET initHardwareRes = ::UcanInitHardwareEx(&handle, device, ucanCallback, incomingEventHandler); |
| if (Q_UNLIKELY(initHardwareRes != USBCAN_SUCCESSFUL)) { |
| q->setError(systemErrorString(initHardwareRes), QCanBusDevice::ConnectionError); |
| return false; |
| } |
| |
| const int bitrate = q->configurationParameter(QCanBusDevice::BitRateKey).toInt(); |
| const bool receiveOwn = q->configurationParameter(QCanBusDevice::ReceiveOwnKey).toBool(); |
| |
| tUcanInitCanParam param = {}; |
| param.m_dwSize = sizeof(param); |
| param.m_bMode = receiveOwn ? kUcanModeTxEcho : kUcanModeNormal; |
| param.m_bOCR = USBCAN_OCR_DEFAULT; |
| param.m_dwACR = USBCAN_ACR_ALL; |
| param.m_dwAMR = USBCAN_AMR_ALL; |
| param.m_dwBaudrate = bitrateCodeFromBitrate(bitrate); |
| param.m_wNrOfRxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; |
| param.m_wNrOfTxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; |
| |
| const UCANRET initCanResult = ::UcanInitCanEx2(handle, channel, ¶m); |
| if (Q_UNLIKELY(initCanResult != USBCAN_SUCCESSFUL)) { |
| ::UcanDeinitHardware(handle); |
| q->setError(systemErrorString(initCanResult), QCanBusDevice::ConnectionError); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void SystecCanBackendPrivate::close() |
| { |
| Q_Q(SystecCanBackend); |
| |
| enableWriteNotification(false); |
| |
| if (outgoingEventNotifier) { |
| delete outgoingEventNotifier; |
| outgoingEventNotifier = nullptr; |
| } |
| |
| const UCANRET deinitCanRes = UcanDeinitCanEx(handle, channel); |
| if (Q_UNLIKELY(deinitCanRes != USBCAN_SUCCESSFUL)) |
| q->setError(systemErrorString(deinitCanRes), QCanBusDevice::ConfigurationError); |
| |
| // TODO: other channel keeps working? |
| const UCANRET deinitHardwareRes = UcanDeinitHardware(handle); |
| if (Q_UNLIKELY(deinitHardwareRes != USBCAN_SUCCESSFUL)) |
| emit q->setError(systemErrorString(deinitHardwareRes), QCanBusDevice::ConnectionError); |
| } |
| |
| void SystecCanBackendPrivate::eventHandler(QEvent *event) |
| { |
| const int code = event->type() - QEvent::User; |
| |
| if (code == USBCAN_EVENT_RECEIVE) |
| readAllReceivedMessages(); |
| } |
| |
| bool SystecCanBackendPrivate::setConfigurationParameter(int key, const QVariant &value) |
| { |
| Q_Q(SystecCanBackend); |
| |
| switch (key) { |
| case QCanBusDevice::BitRateKey: |
| return verifyBitRate(value.toInt()); |
| case QCanBusDevice::ReceiveOwnKey: |
| if (Q_UNLIKELY(q->state() != QCanBusDevice::UnconnectedState)) { |
| q->setError(SystecCanBackend::tr("Cannot configure TxEcho for open device"), |
| QCanBusDevice::ConfigurationError); |
| return false; |
| } |
| return true; |
| default: |
| q->setError(SystecCanBackend::tr("Unsupported configuration key: %1").arg(key), |
| QCanBusDevice::ConfigurationError); |
| return false; |
| } |
| } |
| |
| bool SystecCanBackendPrivate::setupChannel(const QString &interfaceName) |
| { |
| Q_Q(SystecCanBackend); |
| |
| const QRegularExpression re(QStringLiteral("can(\\d)\\.(\\d)")); |
| const QRegularExpressionMatch match = re.match(interfaceName); |
| |
| if (Q_LIKELY(match.hasMatch())) { |
| device = quint8(match.captured(1).toUShort()); |
| channel = quint8(match.captured(2).toUShort()); |
| } else { |
| q->setError(SystecCanBackend::tr("Invalid interface '%1'.") |
| .arg(interfaceName), QCanBusDevice::ConnectionError); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void SystecCanBackendPrivate::setupDefaultConfigurations() |
| { |
| Q_Q(SystecCanBackend); |
| |
| q->setConfigurationParameter(QCanBusDevice::BitRateKey, 500000); |
| q->setConfigurationParameter(QCanBusDevice::ReceiveOwnKey, false); |
| } |
| |
| QString SystecCanBackendPrivate::systemErrorString(int errorCode) |
| { |
| switch (errorCode) { |
| case USBCAN_ERR_RESOURCE: |
| return SystecCanBackend::tr("Could not create a resource (memory, handle, ...)"); |
| case USBCAN_ERR_MAXMODULES: |
| return SystecCanBackend::tr("The maximum number of open modules is exceeded"); |
| case USBCAN_ERR_HWINUSE: |
| return SystecCanBackend::tr("The module is already in use"); |
| case USBCAN_ERR_ILLVERSION: |
| return SystecCanBackend::tr("The software versions of the module and library are incompatible"); |
| case USBCAN_ERR_ILLHW: |
| return SystecCanBackend::tr("The module with the corresponding device number is not connected"); |
| case USBCAN_ERR_ILLHANDLE: |
| return SystecCanBackend::tr("Wrong USB-CAN-Handle handed over to the function"); |
| case USBCAN_ERR_ILLPARAM: |
| return SystecCanBackend::tr("Wrong parameter handed over to the function"); |
| case USBCAN_ERR_BUSY: |
| return SystecCanBackend::tr("Instruction can not be processed at this time"); |
| case USBCAN_ERR_TIMEOUT: |
| return SystecCanBackend::tr("No answer from the module"); |
| case USBCAN_ERR_IOFAILED: |
| return SystecCanBackend::tr("A request for the driver failed"); |
| case USBCAN_ERR_DLL_TXFULL: |
| return SystecCanBackend::tr("The message did not fit into the transmission queue"); |
| case USBCAN_ERR_MAXINSTANCES: |
| return SystecCanBackend::tr("Maximum number of applications is reached"); |
| case USBCAN_ERR_CANNOTINIT: |
| return SystecCanBackend::tr("CAN-interface is not yet initialized"); |
| case USBCAN_ERR_DISCONNECT: |
| return SystecCanBackend::tr("USB-CANmodul was disconnected"); |
| case USBCAN_ERR_NOHWCLASS: |
| return SystecCanBackend::tr("The needed device class does not exist"); |
| case USBCAN_ERR_ILLCHANNEL: |
| return SystecCanBackend::tr("Illegal CAN channel for GW-001/GW-002"); |
| case USBCAN_ERR_ILLHWTYPE: |
| return SystecCanBackend::tr("The API function can not be used with this hardware"); |
| case USBCAN_ERR_SERVER_TIMEOUT: |
| return SystecCanBackend::tr("The command server did not send a reply to a command"); |
| default: |
| return SystecCanBackend::tr("Unknown error code '%1'.").arg(errorCode); |
| } |
| } |
| |
| void SystecCanBackendPrivate::enableWriteNotification(bool enable) |
| { |
| Q_Q(SystecCanBackend); |
| |
| if (outgoingEventNotifier) { |
| if (enable) { |
| if (!outgoingEventNotifier->isActive()) |
| outgoingEventNotifier->start(); |
| } else { |
| outgoingEventNotifier->stop(); |
| } |
| } else if (enable) { |
| outgoingEventNotifier = new OutgoingEventNotifier(this, q); |
| outgoingEventNotifier->start(0); |
| } |
| } |
| |
| void SystecCanBackendPrivate::startWrite() |
| { |
| Q_Q(SystecCanBackend); |
| |
| if (!q->hasOutgoingFrames()) { |
| enableWriteNotification(false); |
| return; |
| } |
| |
| const QCanBusFrame frame = q->dequeueOutgoingFrame(); |
| const QByteArray payload = frame.payload(); |
| |
| tCanMsgStruct message = {}; |
| |
| message.m_dwID = frame.frameId(); |
| message.m_bDLC = quint8(payload.size()); |
| |
| message.m_bFF = frame.hasExtendedFrameFormat() ? USBCAN_MSG_FF_EXT : USBCAN_MSG_FF_STD; |
| |
| if (frame.frameType() == QCanBusFrame::RemoteRequestFrame) |
| message.m_bFF |= USBCAN_MSG_FF_RTR; // remote request frame without payload |
| else |
| ::memcpy(message.m_bData, payload.constData(), sizeof(message.m_bData)); |
| |
| const UCANRET result = ::UcanWriteCanMsgEx(handle, channel, &message, nullptr); |
| if (Q_UNLIKELY(result != USBCAN_SUCCESSFUL)) |
| q->setError(systemErrorString(result), QCanBusDevice::WriteError); |
| else |
| emit q->framesWritten(qint64(1)); |
| |
| if (q->hasOutgoingFrames()) |
| enableWriteNotification(true); |
| } |
| |
| void SystecCanBackendPrivate::readAllReceivedMessages() |
| { |
| Q_Q(SystecCanBackend); |
| |
| QVector<QCanBusFrame> newFrames; |
| |
| for (;;) { |
| tCanMsgStruct message = {}; |
| |
| const UCANRET result = ::UcanReadCanMsgEx(handle, &channel, &message, nullptr); |
| if (result == USBCAN_WARN_NODATA) |
| break; |
| |
| if (Q_UNLIKELY(result != USBCAN_SUCCESSFUL)) { |
| // handle errors |
| |
| q->setError(systemErrorString(result), QCanBusDevice::ReadError); |
| break; |
| } |
| |
| QCanBusFrame frame(message.m_dwID, |
| QByteArray(reinterpret_cast<const char *>(message.m_bData), |
| int(message.m_bDLC))); |
| |
| // TODO: Timestamp can also be set to 100 us resolution with kUcanModeHighResTimer |
| frame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(message.m_dwTime * 1000)); |
| frame.setExtendedFrameFormat(message.m_bFF & USBCAN_MSG_FF_EXT); |
| frame.setLocalEcho(message.m_bFF & USBCAN_MSG_FF_ECHO); |
| frame.setFrameType((message.m_bFF & USBCAN_MSG_FF_RTR) |
| ? QCanBusFrame::RemoteRequestFrame |
| : QCanBusFrame::DataFrame); |
| |
| newFrames.append(std::move(frame)); |
| } |
| |
| q->enqueueReceivedFrames(newFrames); |
| } |
| |
| bool SystecCanBackendPrivate::verifyBitRate(int bitrate) |
| { |
| Q_Q(SystecCanBackend); |
| |
| if (Q_UNLIKELY(q->state() != QCanBusDevice::UnconnectedState)) { |
| q->setError(SystecCanBackend::tr("Cannot configure bitrate for open device"), |
| QCanBusDevice::ConfigurationError); |
| return false; |
| } |
| |
| if (Q_UNLIKELY(bitrateCodeFromBitrate(bitrate) == 0)) { |
| q->setError(SystecCanBackend::tr("Unsupported bitrate %1.").arg(bitrate), |
| QCanBusDevice::ConfigurationError); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void SystecCanBackendPrivate::resetController() |
| { |
| ::UcanResetCan(handle); |
| } |
| |
| QCanBusDevice::CanBusStatus SystecCanBackendPrivate::busStatus() |
| { |
| Q_Q(SystecCanBackend); |
| |
| tStatusStruct status = {}; |
| const UCANRET result = ::UcanGetStatus(handle, &status); |
| |
| if (Q_UNLIKELY(result != USBCAN_SUCCESSFUL)) { |
| qCWarning(QT_CANBUS_PLUGINS_SYSTECCAN, "Can not query CAN bus status."); |
| q->setError(SystecCanBackend::tr("Can not query CAN bus status."), QCanBusDevice::ConfigurationError); |
| return QCanBusDevice::CanBusStatus::Unknown; |
| } |
| |
| if (status.m_wCanStatus & USBCAN_CANERR_BUSOFF) |
| return QCanBusDevice::CanBusStatus::BusOff; |
| |
| if (status.m_wCanStatus & USBCAN_CANERR_BUSHEAVY) |
| return QCanBusDevice::CanBusStatus::Error; |
| |
| if (status.m_wCanStatus & USBCAN_CANERR_BUSLIGHT) |
| return QCanBusDevice::CanBusStatus::Warning; |
| |
| if (status.m_wCanStatus == USBCAN_CANERR_OK) |
| return QCanBusDevice::CanBusStatus::Good; |
| |
| return QCanBusDevice::CanBusStatus::Unknown; |
| } |
| |
| SystecCanBackend::SystecCanBackend(const QString &name, QObject *parent) : |
| QCanBusDevice(parent), |
| d_ptr(new SystecCanBackendPrivate(this)) |
| { |
| Q_D(SystecCanBackend); |
| |
| d->setupChannel(name); |
| d->setupDefaultConfigurations(); |
| |
| std::function<void()> f = std::bind(&SystecCanBackend::resetController, this); |
| setResetControllerFunction(f); |
| |
| std::function<CanBusStatus()> g = std::bind(&SystecCanBackend::busStatus, this); |
| setCanBusStatusGetter(g); |
| } |
| |
| SystecCanBackend::~SystecCanBackend() |
| { |
| if (state() == QCanBusDevice::ConnectedState) |
| close(); |
| |
| delete d_ptr; |
| } |
| |
| bool SystecCanBackend::open() |
| { |
| Q_D(SystecCanBackend); |
| |
| if (!d->open()) |
| return false; |
| |
| // Apply all stored configurations except bitrate and receive own, |
| // because these cannot be applied after opening the device |
| const QVector<int> keys = configurationKeys(); |
| for (int key : keys) { |
| if (key == BitRateKey || key == ReceiveOwnKey) |
| continue; |
| const QVariant param = configurationParameter(key); |
| const bool success = d->setConfigurationParameter(key, param); |
| if (Q_UNLIKELY(!success)) { |
| qCWarning(QT_CANBUS_PLUGINS_SYSTECCAN, "Cannot apply parameter %d with value %ls.", |
| key, qUtf16Printable(param.toString())); |
| } |
| } |
| |
| setState(QCanBusDevice::ConnectedState); |
| return true; |
| } |
| |
| void SystecCanBackend::close() |
| { |
| Q_D(SystecCanBackend); |
| |
| d->close(); |
| |
| setState(QCanBusDevice::UnconnectedState); |
| } |
| |
| void SystecCanBackend::setConfigurationParameter(int key, const QVariant &value) |
| { |
| Q_D(SystecCanBackend); |
| |
| if (d->setConfigurationParameter(key, value)) |
| QCanBusDevice::setConfigurationParameter(key, value); |
| } |
| |
| bool SystecCanBackend::writeFrame(const QCanBusFrame &newData) |
| { |
| Q_D(SystecCanBackend); |
| |
| if (Q_UNLIKELY(state() != QCanBusDevice::ConnectedState)) |
| return false; |
| |
| if (Q_UNLIKELY(!newData.isValid())) { |
| setError(tr("Cannot write invalid QCanBusFrame"), QCanBusDevice::WriteError); |
| return false; |
| } |
| |
| const QCanBusFrame::FrameType type = newData.frameType(); |
| if (Q_UNLIKELY(type != QCanBusFrame::DataFrame && type != QCanBusFrame::RemoteRequestFrame)) { |
| setError(tr("Unable to write a frame with unacceptable type"), |
| QCanBusDevice::WriteError); |
| return false; |
| } |
| |
| // CAN FD frame format is not supported by the hardware yet |
| if (Q_UNLIKELY(newData.hasFlexibleDataRateFormat())) { |
| setError(tr("CAN FD frame format not supported"), QCanBusDevice::WriteError); |
| return false; |
| } |
| |
| enqueueOutgoingFrame(newData); |
| d->enableWriteNotification(true); |
| |
| return true; |
| } |
| |
| // TODO: Implement me |
| QString SystecCanBackend::interpretErrorFrame(const QCanBusFrame &errorFrame) |
| { |
| Q_UNUSED(errorFrame); |
| |
| return QString(); |
| } |
| |
| void SystecCanBackend::resetController() |
| { |
| Q_D(SystecCanBackend); |
| d->resetController(); |
| } |
| |
| QCanBusDevice::CanBusStatus SystecCanBackend::busStatus() |
| { |
| Q_D(SystecCanBackend); |
| |
| return d->busStatus(); |
| } |
| |
| QT_END_NAMESPACE |