blob: dc3d2c70f7667392f9c477edaf05e4fbf3021d11 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 Ford Motor Company.
** 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 "passthrucanio.h"
#include <QLoggingCategory>
#include <QtEndian>
#include <cstring>
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(int 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<QList<QCanBusDevice::Filter>>(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<QCanBusDevice::Filter> &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<quint32>(filter.frameId & filter.frameIdMask, pattern.data());
qToBigEndian<quint32>(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<ulong>(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<ulong>(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<quint32>(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<ulong>(m_ioBuffer.size(), numMsgs);
QVector<QCanBusFrame> 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 quint32 msgId = qFromBigEndian<quint32>(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));
}