/**************************************************************************** ** ** Copyright (C) 2017 Andre Hartmann ** 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 #include #include #include #include #include #include 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 *>(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 SystecCanBackend::interfaces() { QList 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::User + event); IncomingEventHandler *handler = static_cast(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; ::memset(¶m, 0, sizeof(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; ::memset(&message, 0, sizeof(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 newFrames; for (;;) { tCanMsgStruct message; ::memset(&message, 0, sizeof(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(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; ::memset(&status, 0, sizeof(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 f = std::bind(&SystecCanBackend::resetController, this); setResetControllerFunction(f); std::function 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 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