// Copyright (C) 2017 Ford Motor Company. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "passthrucanio.h" #include #include #include PassThruCanIO::PassThruCanIO(QObject *parent) : QObject(parent) , m_ioBuffer (8, J2534::Message(J2534::Protocol::CAN)) { } PassThruCanIO::~PassThruCanIO() { } void PassThruCanIO::open(const QString &library, const QByteArray &subDev, uint bitRate) { if (Q_UNLIKELY(m_passThru)) { qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface already open"); emit openFinished(false); return; } qCDebug(QT_CANBUS_PLUGINS_PASSTHRU, "Loading interface library: %ls", qUtf16Printable(library)); m_passThru = new J2534::PassThru(library, this); J2534::PassThru::Status openStatus = m_passThru->lastError(); if (openStatus == J2534::PassThru::NoError) openStatus = m_passThru->open(subDev, &m_deviceId); if (openStatus == J2534::PassThru::NoError && m_passThru->connect(m_deviceId, J2534::Protocol::CAN, J2534::PassThru::CAN29BitID | J2534::PassThru::CANIDBoth, bitRate, &m_channelId) == J2534::PassThru::NoError) { emit openFinished(true); return; } emit errorOccurred(m_passThru->lastErrorString(), QCanBusDevice::ConnectionError); if (openStatus == J2534::PassThru::NoError && m_passThru->close(m_deviceId) != J2534::PassThru::NoError) qCWarning(QT_CANBUS_PLUGINS_PASSTHRU, "Failed to close pass-thru device"); delete m_passThru; m_passThru = nullptr; emit openFinished(false); } void PassThruCanIO::close() { if (Q_LIKELY(m_passThru)) { delete m_idleNotifier; m_idleNotifier = nullptr; if (m_passThru->disconnect(m_channelId) != J2534::PassThru::NoError || m_passThru->close(m_deviceId) != J2534::PassThru::NoError) { qCWarning(QT_CANBUS_PLUGINS_PASSTHRU, "Failed to close pass-thru device"); emit errorOccurred(m_passThru->lastErrorString(), QCanBusDevice::ConnectionError); } delete m_passThru; m_passThru = nullptr; } emit closeFinished(); } void PassThruCanIO::applyConfig(QCanBusDevice::ConfigurationKey key, const QVariant &value) { if (Q_UNLIKELY(!m_passThru)) { qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface not open"); return; } bool success = true; switch (key) { case QCanBusDevice::RawFilterKey: success = setMessageFilters(qvariant_cast>(value)); break; case QCanBusDevice::LoopbackKey: success = setConfigValue(J2534::Config::Loopback, value.toBool()); break; case QCanBusDevice::BitRateKey: success = setConfigValue(J2534::Config::DataRate, value.toUInt()); break; default: emit errorOccurred(tr("Unsupported configuration key: %1").arg(key), QCanBusDevice::ConfigurationError); break; } if (!success) { emit errorOccurred(tr("Configuration failed: %1").arg(m_passThru->lastErrorString()), QCanBusDevice::ConfigurationError); } } void PassThruCanIO::listen() { if (Q_UNLIKELY(!m_passThru)) { qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface not open"); return; } if (Q_UNLIKELY(m_idleNotifier)) { qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Idle notifier already created"); return; } m_idleNotifier = new QTimer(this); connect(m_idleNotifier, &QTimer::timeout, this, &PassThruCanIO::pollForMessages); m_idleNotifier->start(0); } bool PassThruCanIO::enqueueMessage(const QCanBusFrame &frame) { const QMutexLocker lock (&m_writeGuard); m_writeQueue.append(frame); return true; } bool PassThruCanIO::setMessageFilters(const QList &filters) { if (m_passThru->clear(m_channelId, J2534::PassThru::MsgFilters) != J2534::PassThru::NoError) return false; J2534::Message pattern {J2534::Protocol::CAN}; pattern.setSize(4); J2534::Message mask {J2534::Protocol::CAN}; mask.setSize(4); for (const auto &filter : filters) { if (filter.type != QCanBusFrame::DataFrame && filter.type != QCanBusFrame::InvalidFrame) { emit errorOccurred(tr("Configuration failed: unsupported filter type"), QCanBusDevice::ConfigurationError); break; } if (filter.format & QCanBusDevice::Filter::MatchExtendedFormat) pattern.setRxStatus(J2534::Message::InCAN29BitID); else pattern.setRxStatus({}); if (filter.format != QCanBusDevice::Filter::MatchBaseAndExtendedFormat) mask.setRxStatus(J2534::Message::InCAN29BitID); else mask.setRxStatus({}); qToBigEndian(filter.frameId & filter.frameIdMask, pattern.data()); qToBigEndian(filter.frameIdMask, mask.data()); if (m_passThru->startMsgFilter(m_channelId, J2534::PassThru::PassFilter, mask, pattern) != J2534::PassThru::NoError) return false; } return true; } bool PassThruCanIO::setConfigValue(J2534::Config::Parameter param, ulong value) { const J2534::Config config {param, value}; return (m_passThru->setConfig(m_channelId, &config) == J2534::PassThru::NoError); } void PassThruCanIO::pollForMessages() { if (Q_UNLIKELY(!m_passThru)) { qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface not open"); return; } const bool writePending = writeMessages(); readMessages(writePending); } bool PassThruCanIO::writeMessages() { ulong numMsgs = m_ioBuffer.size(); { const QMutexLocker lock (&m_writeGuard); numMsgs = qMin(m_writeQueue.size(), numMsgs); for (ulong i = 0; i < numMsgs; ++i) { const QCanBusFrame &frame = m_writeQueue.at(i); J2534::Message &msg = m_ioBuffer[i]; const QByteArray payload = frame.payload(); const ulong payloadSize = qMin(payload.size(), J2534::Message::maxSize - 4); msg.setRxStatus({}); msg.setTimestamp(0); msg.setSize(4 + payloadSize); msg.setExtraDataIndex(0); if (frame.hasExtendedFrameFormat()) msg.setTxFlags(J2534::Message::OutCAN29BitID); else msg.setTxFlags({}); qToBigEndian(frame.frameId(), msg.data()); std::memcpy(msg.data() + 4, payload.data(), payloadSize); } } if (numMsgs == 0) return false; const auto status = m_passThru->writeMsgs(m_channelId, m_ioBuffer.constData(), &numMsgs, pollTimeout); if (status == J2534::PassThru::BufferFull) return false; if (status != J2534::PassThru::NoError && status != J2534::PassThru::Timeout) { emit errorOccurred(tr("Message write failed: %1").arg(m_passThru->lastErrorString()), QCanBusDevice::WriteError); return false; } if (numMsgs == 0) return false; bool morePending; { const QMutexLocker lock (&m_writeGuard); // De-queue successfully written frames. m_writeQueue.erase(m_writeQueue.begin(), m_writeQueue.begin() + numMsgs); morePending = !m_writeQueue.isEmpty(); } emit messagesSent(numMsgs); return morePending; } void PassThruCanIO::readMessages(bool writePending) { // If there are outgoing messages waiting to be written, just check // for already received messages but do not block waiting for more. const uint timeout = (writePending) ? 0 : pollTimeout; ulong numMsgs = m_ioBuffer.size(); const auto status = m_passThru->readMsgs(m_channelId, m_ioBuffer.data(), &numMsgs, timeout); if (status == J2534::PassThru::BufferEmpty) return; if (status != J2534::PassThru::NoError && status != J2534::PassThru::Timeout) { emit errorOccurred(tr("Message read failed: %1").arg(m_passThru->lastErrorString()), QCanBusDevice::ReadError); if (status != J2534::PassThru::BufferOverflow) return; } const int numFrames = qMin(m_ioBuffer.size(), numMsgs); QList frames; frames.reserve(numFrames); for (int i = 0; i < numFrames; ++i) { const J2534::Message &msg = m_ioBuffer.at(i); if (Q_UNLIKELY(msg.size() < 4) || Q_UNLIKELY(msg.size() > J2534::Message::maxSize)) { // This normally shouldn't happen, so a log message is appropriate. qCWarning(QT_CANBUS_PLUGINS_PASSTHRU, "Message with invalid size %lu received", msg.size()); continue; } const QCanBusFrame::FrameId msgId = qFromBigEndian(msg.data()); const QByteArray payload (msg.data() + 4, msg.size() - 4); QCanBusFrame frame (msgId, payload); frame.setExtendedFrameFormat((msg.rxStatus() & J2534::Message::InCAN29BitID) != 0); frame.setLocalEcho((msg.rxStatus() & J2534::Message::InTxMsgType) != 0); frame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(msg.timestamp())); frames.append(std::move(frame)); } if (!frames.isEmpty()) emit messagesReceived(std::move(frames)); }