Revision control
Copy as Markdown
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/qtest.h>
#include <QtCore/QtEndian>
#include <QtSerialBus/qcanbusframe.h>
#include <QtSerialBus/qcanframeprocessor.h>
#include <QtSerialBus/qcanmessagedescription.h>
#include <QtSerialBus/qcansignaldescription.h>
#include <QtSerialBus/private/qcanframeprocessor_p.h>
QT_USE_NAMESPACE
class tst_QCanFrameProcessor : public QObject
{
Q_OBJECT
private slots:
/* basics */
void construct();
/* parse */
void parseSignal_data();
void parseSignal();
void parseWithValueConversion_data();
void parseWithValueConversion();
void parseMultiplexedSignals_data();
void parseMultiplexedSignals();
void parseExtendedMultiplexedSignals_data();
void parseExtendedMultiplexedSignals();
void parseWithErrorsAndWarnings_data();
void parseWithErrorsAndWarnings();
void extractUniqueId_data();
void extractUniqueId();
/* generate */
void prepareFrame_data();
void prepareFrame();
void prepareWithValueConversion_data();
void prepareWithValueConversion();
void prepareMultiplexedPayload_data();
void prepareMultiplexedPayload();
void prepareWithErrorsAndWarnings_data();
void prepareWithErrorsAndWarnings();
void prepareUniqueId_data();
void prepareUniqueId();
/* roundtrip */
void roundtrip_data();
void roundtrip();
};
void tst_QCanFrameProcessor::construct()
{
QCanFrameProcessor p;
QVERIFY(p.messageDescriptions().isEmpty());
QCOMPARE(p.error(), QCanFrameProcessor::Error::None);
QVERIFY(p.errorString().isEmpty());
QVERIFY(p.warnings().isEmpty());
QVERIFY(!p.uniqueIdDescription().isValid());
}
void tst_QCanFrameProcessor::parseSignal_data()
{
QTest::addColumn<QtCanBus::DataSource>("source");
QTest::addColumn<quint64>("data");
QTest::addColumn<quint16>("startBit");
QTest::addColumn<quint16>("bitLength");
QTest::addColumn<QtCanBus::DataFormat>("format");
QTest::addColumn<QSysInfo::Endian>("endian");
QTest::addColumn<QVariant>("expectedValue");
struct SourceDesc {
QtCanBus::DataSource source;
const char name[8];
};
static constexpr SourceDesc sources[] = {
{QtCanBus::DataSource::FrameId, "FrameId"},
{QtCanBus::DataSource::Payload, "Payload"}
};
for (const auto &s : sources) {
// LE, 16 bit, start 0
QTest::addRow("%s, unsigned, 16bit, start 0, le", s.name)
<< s.source
<< quint64(0x1234FFEC) << quint16(0) << quint16(16)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(65516);
QTest::addRow("%s, signed neg, 16bit, start 0, le", s.name)
<< s.source
<< quint64(0x1234FFEC) << quint16(0) << quint16(16)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(-20);
QTest::addRow("%s, signed pos, 16bit, start 0, le", s.name)
<< s.source
<< quint64(0x12340FEC) << quint16(0) << quint16(16)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(4076);
// LE, 16 bit, start 5
QTest::addRow("%s, unsigned, 16bit, start 5, le", s.name)
<< s.source
<< quint64(0x1234FFECULL << 5)
<< quint16(5) << quint16(16)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(65516);
QTest::addRow("%s, signed neg, 16bit, start 5, le", s.name)
<< s.source
<< quint64((0x1234FFECULL << 5) & 0x1FFFFFFFU)
<< quint16(5) << quint16(16)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(-20);
QTest::addRow("%s, signed pos, 16bit, start 5, le", s.name)
<< s.source
<< quint64(0x12340FECULL << 5)
<< quint16(5) << quint16(16)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(4076);
// LE, 19 bit, start 0
QTest::addRow("%s, unsigned, 19bit, start 0, le", s.name)
<< s.source
<< quint64(0x1234FFEC) << quint16(0) << quint16(19)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(327660);
QTest::addRow("%s, signed neg, 19bit, start 0, le", s.name)
<< s.source
<< quint64(0x123D800A) << quint16(0) << quint16(19)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(-163830);
QTest::addRow("%s, signed pos, 19bit, start 0, le", s.name)
<< s.source
<< quint64(0x1231800A) << quint16(0) << quint16(19)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(98314);
// LE, 19 bit, start 3
QTest::addRow("%s, unsigned, 19bit, start 3, le", s.name)
<< s.source
<< quint64(0x1234FFECULL << 3)
<< quint16(3) << quint16(19)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(327660);
QTest::addRow("%s, signed neg, 19bit, start 3, le", s.name)
<< s.source
<< quint64(0x123D800AULL << 3)
<< quint16(3) << quint16(19)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(-163830);
QTest::addRow("%s, signed pos, 19bit, start 3, le", s.name)
<< s.source
<< quint64(0x1231800AULL << 3)
<< quint16(3) << quint16(19)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(98314);
// BE, 16 bit, start 7
QTest::addRow("%s, unsigned, 16bit, start 7, be", s.name)
<< s.source
<< quint64(0x1234ECFF) << quint16(7) << quint16(16)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(65516);
QTest::addRow("%s, signed neg, 16bit, start 7, be", s.name)
<< s.source
<< quint64(0x1234ECFF) << quint16(7) << quint16(16)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(-20);
QTest::addRow("%s, signed pos, 16bit, start 7, be", s.name)
<< s.source
<< quint64(0x1234EC0F) << quint16(7) << quint16(16)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(4076);
// BE, 16 bit, start 5
QTest::addRow("%s, unsigned, 16bit, start 5, be", s.name)
<< s.source
<< quint64(0x1234FB3F) << quint16(5) << quint16(16)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(65516);
QTest::addRow("%s, signed neg, 16bit, start 5, be", s.name)
<< s.source
<< quint64(0x1234FB3F) << quint16(5) << quint16(16)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(-20);
QTest::addRow("%s, signed pos, 16bit, start 5, be", s.name)
<< s.source
<< quint64(0x1234FB03) << quint16(5) << quint16(16)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(4076);
// BE, 21 bit, start 7
QTest::addRow("%s, unsigned, 21bit, start 7, be", s.name)
<< s.source
<< quint64(0x1264FFA7) << quint16(7) << quint16(21)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(1376236);
QTest::addRow("%s, signed neg, 21bit, start 7, be", s.name)
<< s.source
<< quint64(0x1264FFA7) << quint16(7) << quint16(21)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(-720916);
QTest::addRow("%s, signed pos, 21bit, start 7, be", s.name)
<< s.source
<< quint64(0x1264FF27) << quint16(7) << quint16(21)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(327660);
// BE, 21 bit, start 4
QTest::addRow("%s, unsigned, 21bit, start 4, be", s.name)
<< s.source
<< quint64(0x12ECFF14) << quint16(4) << quint16(21)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(1376236);
QTest::addRow("%s, signed neg, 21bit, start 4, be", s.name)
<< s.source
<< quint64(0x12ECFF14) << quint16(4) << quint16(21)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(-720916);
QTest::addRow("%s, signed pos, 21bit, start 4, be", s.name)
<< s.source
<< quint64(0x12ECFF04) << quint16(4) << quint16(21)
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(327660);
// Some LE tests for values less than 8 bit
QTest::addRow("%s, unsinged, 4bit, start 0, le", s.name)
<< s.source
<< quint64(0x12345678) << quint16(0) << quint16(4)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(8);
QTest::addRow("%s, unsinged, 4bit, start 3, le", s.name)
<< s.source
<< quint64(0x12345678) << quint16(3) << quint16(4)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(15);
QTest::addRow("%s, unsinged, 4bit, start 7, le", s.name)
<< s.source
<< quint64(0x12345678) << quint16(7) << quint16(4)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(12);
// Some BE tests for values less than 8 bit
QTest::addRow("%s, unsinged, 4bit, start 7, be", s.name)
<< s.source
<< quint64(0x12345678) << quint16(7) << quint16(4)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(7);
QTest::addRow("%s, unsinged, 4bit, start 5, be", s.name)
<< s.source
<< quint64(0x12345678) << quint16(5) << quint16(4)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(14);
QTest::addRow("%s, unsinged, 4bit, start 0, be", s.name)
<< s.source
<< quint64(0x12345678) << quint16(0) << quint16(4)
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(2);
}
// float data - payload only
{
const float floatVal = 123.456f;
for (quint16 i = 0; i < 8; ++i) {
quint64 value = 0;
const float leFloatVal = qToLittleEndian(floatVal);
memcpy(&value, &leFloatVal, sizeof(float));
value = value << i;
QTest::addRow("Payload, float, le, start %d", i)
<< QtCanBus::DataSource::Payload
<< value << i << quint16(32)
<< QtCanBus::DataFormat::Float
<< QSysInfo::Endian::LittleEndian
<< QVariant(floatVal);
// to get the proper BE value with an offset, we need to take an LE
// value, shift it to the right, and then swap bytes
value = 0;
memcpy(&value, &leFloatVal, sizeof(float));
value = value << 16; // so that we have some room for right shift
// this means that default start bit is 23
value = value >> i;
value = qToBigEndian(value);
QTest::addRow("Payload, float, be, start %d", 23 - i)
<< QtCanBus::DataSource::Payload
<< value << quint16(23 - i) << quint16(32)
<< QtCanBus::DataFormat::Float
<< QSysInfo::Endian::BigEndian
<< QVariant(floatVal);
}
}
// double data - payload only
{
const double doubleVal = 1.234e-15;
const double leDoubleVal = qToLittleEndian(doubleVal);
quint64 value = 0;
memcpy(&value, &leDoubleVal, sizeof(leDoubleVal));
QTest::addRow("Payload, double, le")
<< QtCanBus::DataSource::Payload
<< value << quint16(0) << quint16(64)
<< QtCanBus::DataFormat::Double
<< QSysInfo::Endian::LittleEndian
<< QVariant(doubleVal);
const double beDoubleVal = qToBigEndian(doubleVal);
memcpy(&value, &beDoubleVal, sizeof(beDoubleVal));
QTest::addRow("Payload, double, be")
<< QtCanBus::DataSource::Payload
<< value << quint16(7) << quint16(64)
<< QtCanBus::DataFormat::Double
<< QSysInfo::Endian::BigEndian
<< QVariant(doubleVal);
}
// ASCII
for (const auto &s : sources) {
const bool isPayload = s.source == QtCanBus::DataSource::Payload;
const QByteArray expectedData = isPayload ? QByteArray("abc", 3) : QByteArray("ab", 2);
const quint16 offset = isPayload ? 8 : 4;
for (quint16 i = 0; i < 8; ++i) {
quint64 value = 0;
memcpy(&value, expectedData.constData(), expectedData.size());
value = value << (offset + i);
QTest::addRow("%s, ascii, offset %d", s.name, offset + i)
<< s.source
<< value << quint16(offset + i) << quint16(expectedData.size() * 8)
<< QtCanBus::DataFormat::AsciiString
<< QSysInfo::Endian::LittleEndian
<< QVariant(expectedData);
}
}
}
void tst_QCanFrameProcessor::parseSignal()
{
QFETCH(QtCanBus::DataSource, source);
QFETCH(quint64, data);
QFETCH(quint16, startBit);
QFETCH(quint16, bitLength);
QFETCH(QtCanBus::DataFormat, format);
QFETCH(QSysInfo::Endian, endian);
QFETCH(QVariant, expectedValue);
QCanSignalDescription sig;
sig.setName("test_signal");
sig.setDataSource(source);
sig.setDataFormat(format);
sig.setDataEndian(endian);
sig.setStartBit(startBit);
sig.setBitLength(bitLength);
QVERIFY(sig.isValid());
const bool sourceIsPayload = source == QtCanBus::DataSource::Payload;
// The unique id will be extracted from the 0-th payload byte
const auto uidSource = sourceIsPayload ? QtCanBus::DataSource::FrameId
: QtCanBus::DataSource::Payload;
QCanUniqueIdDescription uidDesc;
uidDesc.setSource(uidSource);
uidDesc.setStartBit(0);
uidDesc.setBitLength(4);
const QtCanBus::UniqueId expectedUid{0x0A};
const auto messageSize = sourceIsPayload ? sizeof(data) : 1;
QCanMessageDescription message;
message.setName("test_message");
message.setUniqueId(expectedUid);
message.setSize(messageSize);
message.addSignalDescription(sig);
QVERIFY(message.isValid());
QCanFrameProcessor parser;
parser.setUniqueIdDescription(uidDesc);
parser.addMessageDescriptions({ message });
QCanBusFrame frame;
if (sourceIsPayload) {
frame.setFrameId(static_cast<QCanBusFrame::FrameId>(expectedUid));
const QByteArray payload(reinterpret_cast<const char *>(&data), sizeof(data));
frame.setPayload(payload);
} else {
frame.setFrameId(data & 0x1FFFFFFFU);
frame.setPayload(QByteArray(1, 0x0A));
}
QVERIFY(frame.isValid());
const auto result = parser.parseFrame(frame);
const auto signalValues = result.signalValues;
QCOMPARE(result.uniqueId, expectedUid);
QVERIFY(!signalValues.isEmpty());
const QVariantMap expectedSignalValues({qMakePair(sig.name(), expectedValue)});
// The values can be doubles, so we need to compare them explicitly
QCOMPARE(signalValues.keys(), expectedSignalValues.keys());
for (const auto &key : expectedSignalValues.keys()) {
const auto expectedValue = expectedSignalValues.value(key);
if (expectedValue.canConvert(QMetaType::fromType<double>()))
QCOMPARE(signalValues.value(key).toDouble(), expectedValue.toDouble());
else
QCOMPARE(signalValues.value(key), expectedValue);
}
}
void tst_QCanFrameProcessor::parseWithValueConversion_data()
{
QTest::addColumn<double>("factor");
QTest::addColumn<double>("offset");
QTest::addColumn<double>("scaling");
QTest::addColumn<quint8>("payloadValue");
QTest::addColumn<QVariant>("expectedValue");
// expectedValue = scaling * (payloadValue * factor + offset);
QTest::newRow("nan, nan, nan") << qQNaN() << qQNaN() << qQNaN() << quint8(30) << QVariant(30);
QTest::newRow("0.75, nan, nan") << 0.75 << qQNaN() << qQNaN() << quint8(30) << QVariant(22.5);
QTest::newRow("0.75, 15.3, nan") << 0.75 << 15.3 << qQNaN() << quint8(30) << QVariant(37.8);
QTest::newRow("0.75, 15.3, 100") << 0.75 << 15.3 << 100. << quint8(30) << QVariant(3780.);
QTest::newRow("0.75, nan, 100") << 0.75 << qQNaN() << 100. << quint8(30) << QVariant(2250.);
QTest::newRow("nan, 15.3, 100") << qQNaN() << 15.3 << 100. << quint8(30) << QVariant(4530.);
// check that setting factor and/or scaling to 0 is equivalent to NaN
QTest::newRow("0, 15.3, 100") << 0. << 15.3 << 100. << quint8(30) << QVariant(4530.);
QTest::newRow("0, 15.3, 0") << 0. << 15.3 << 0. << quint8(30) << QVariant(45.3);
}
void tst_QCanFrameProcessor::parseWithValueConversion()
{
QFETCH(double, factor);
QFETCH(double, offset);
QFETCH(double, scaling);
QFETCH(quint8, payloadValue);
QFETCH(QVariant, expectedValue);
QCanSignalDescription sig;
sig.setName("test_signal");
sig.setStartBit(7);
sig.setBitLength(8);
sig.setFactor(factor);
sig.setOffset(offset);
sig.setScaling(scaling);
const QtCanBus::UniqueId uniqueId{123};
QCanMessageDescription message;
message.setName("test_message");
message.setUniqueId(uniqueId);
message.setSize(1);
message.addSignalDescription(sig);
QVERIFY(message.isValid());
// Assume that our uniqueId is the whole FrameId
QCanUniqueIdDescription uidDesc;
uidDesc.setBitLength(29);
QCanFrameProcessor parser;
parser.setUniqueIdDescription(uidDesc);
parser.addMessageDescriptions({ message });
QCanBusFrame frame(static_cast<QCanBusFrame::FrameId>(uniqueId),
QByteArray(1, char(payloadValue)));
QVERIFY(frame.isValid());
const auto result = parser.parseFrame(frame);
const auto signalValues = result.signalValues;
QCOMPARE(result.uniqueId, uniqueId);
QVERIFY(!signalValues.isEmpty());
const QVariantMap expectedSignalValues({qMakePair(sig.name(), expectedValue)});
// The values can be doubles, so we need to compare them explicitly
QCOMPARE(signalValues.keys(), expectedSignalValues.keys());
for (const auto &key : expectedSignalValues.keys()) {
const auto expectedValue = expectedSignalValues.value(key);
if (expectedValue.canConvert(QMetaType::fromType<double>()))
QCOMPARE(signalValues.value(key).toDouble(), expectedValue.toDouble());
else
QCOMPARE(signalValues.value(key), expectedValue);
}
}
void tst_QCanFrameProcessor::parseMultiplexedSignals_data()
{
QTest::addColumn<QString>("s0Name");
QTest::addColumn<QString>("s1Name");
QTest::addColumn<QString>("s2Name");
QTest::addColumn<QVariant>("s1MuxValue");
QTest::addColumn<QVariant>("s2MuxValue");
QTest::addColumn<QByteArray>("payload");
QTest::addColumn<QVariantMap>("expectedResult");
const QString s0("s0");
const QString s1("s1");
const QString s2("s2");
const QVariant s1MuxValue(1);
const QVariant s2MuxValue(2);
QTest::newRow("s0 and s1") << s0 << s1 << s2 << s1MuxValue << s2MuxValue
<< QByteArray(1, 0x29)
<< QVariantMap({ qMakePair(s0, s1MuxValue), qMakePair(s1, 0x0A) });
QTest::newRow("s0 and s2") << s0 << s1 << s2 << s1MuxValue << s2MuxValue
<< QByteArray(1, 0x2E)
<< QVariantMap({ qMakePair(s0, s2MuxValue), qMakePair(s2, 0x0B) });
QTest::newRow("only s0") << s0 << s1 << s2 << s1MuxValue << s2MuxValue
<< QByteArray(1, 0x73)
<< QVariantMap({ qMakePair(s0, 0x03) });
}
void tst_QCanFrameProcessor::parseMultiplexedSignals()
{
QFETCH(QString, s0Name);
QFETCH(QString, s1Name);
QFETCH(QString, s2Name);
QFETCH(QVariant, s1MuxValue);
QFETCH(QVariant, s2MuxValue);
QFETCH(QByteArray, payload);
QFETCH(QVariantMap, expectedResult);
QCanSignalDescription s0; // multiplexor
s0.setName(s0Name);
s0.setStartBit(1);
s0.setBitLength(2);
s0.setDataFormat(QtCanBus::DataFormat::UnsignedInteger);
s0.setMultiplexState(QtCanBus::MultiplexState::MultiplexorSwitch);
QCanSignalDescription s1; // multiplexed signal 1, used when s0 == 1
s1.setName(s1Name);
s1.setStartBit(7);
s1.setBitLength(6);
s1.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
s1.addMultiplexSignal(s0.name(), s1MuxValue);
QCanSignalDescription s2; // multiplexed signal 2, used when s0 == 2
s2.setName(s2Name);
s2.setStartBit(7);
s2.setBitLength(6);
s2.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
s2.addMultiplexSignal(s0.name(), s2MuxValue);
const QtCanBus::UniqueId uniqueId{123};
QCanMessageDescription msg;
msg.setName("test");
msg.setUniqueId(uniqueId);
msg.setSize(payload.size());
msg.setSignalDescriptions({ s0, s1, s2 });
// Assume that our uniqueId is the whole FrameId
QCanUniqueIdDescription uidDesc;
uidDesc.setBitLength(29);
QCanFrameProcessor parser;
parser.setUniqueIdDescription(uidDesc);
parser.addMessageDescriptions({ msg });
QCanBusFrame frame(static_cast<QCanBusFrame::FrameId>(msg.uniqueId()), payload);
QVERIFY(frame.isValid());
const auto result = parser.parseFrame(frame);
QCOMPARE(result.uniqueId, uniqueId);
QCOMPARE(result.signalValues, expectedResult);
}
void tst_QCanFrameProcessor::parseExtendedMultiplexedSignals_data()
{
QTest::addColumn<QByteArray>("payload");
QTest::addColumn<QVariantMap>("expectedResult");
const QString s0Name("s0");
const QString s1Name("s1");
const QString s2Name("s2");
const QString s3Name("s3");
const QString s4Name("s4");
QTest::newRow("s0 and s2") << QByteArray::fromHex("2434")
<< QVariantMap({ qMakePair(s0Name, 4),
qMakePair(s2Name, 0x342) });
QTest::newRow("s0, s1 and s3") << QByteArray::fromHex("1134")
<< QVariantMap({ qMakePair(s0Name, 1),
qMakePair(s1Name, 1),
qMakePair(s3Name, 0x34) });
QTest::newRow("s0, s1 and s4") << QByteArray::fromHex("2134")
<< QVariantMap({ qMakePair(s0Name, 1),
qMakePair(s1Name, 2),
qMakePair(s4Name, 0x34) });
QTest::newRow("s0 and s1") << QByteArray::fromHex("7134")
<< QVariantMap({ qMakePair(s0Name, 1),
qMakePair(s1Name, 7) });
QTest::newRow("only s0") << QByteArray::fromHex("2534")
<< QVariantMap({ qMakePair(s0Name, 5) });
}
void tst_QCanFrameProcessor::parseExtendedMultiplexedSignals()
{
QFETCH(QByteArray, payload);
QFETCH(QVariantMap, expectedResult);
QCanSignalDescription s0; // multiplexor 1
s0.setName("s0");
s0.setDataEndian(QSysInfo::Endian::LittleEndian);
s0.setStartBit(0);
s0.setBitLength(4);
s0.setMultiplexState(QtCanBus::MultiplexState::MultiplexorSwitch);
QCanSignalDescription s1; // multiplexor 2, also depends on the value of s0
s1.setName("s1");
s1.setDataEndian(QSysInfo::Endian::LittleEndian);
s1.setStartBit(4);
s1.setBitLength(4);
s1.setMultiplexState(QtCanBus::MultiplexState::SwitchAndSignal);
s1.addMultiplexSignal(s0.name(), 1);
QCanSignalDescription s2; // depends on mux1 only, contains 12 bits of data
s2.setName("s2");
s2.setDataEndian(QSysInfo::Endian::LittleEndian);
s2.setStartBit(4);
s2.setBitLength(12);
s2.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
const QCanSignalDescription::MultiplexValues muxValuesS0 { {2, 4} };
s2.addMultiplexSignal(s0.name(), muxValuesS0);
QCanSignalDescription s3; // depends on mux2 (so indirectly on mux1)
s3.setName("s3");
s3.setDataEndian(QSysInfo::Endian::LittleEndian);
s3.setStartBit(8);
s3.setBitLength(8);
s3.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
s3.addMultiplexSignal(s1.name(), 1);
QCanSignalDescription s4; // depends on mux2 (so indirectly on mux1)
s4.setName("s4");
s4.setDataEndian(QSysInfo::Endian::LittleEndian);
s4.setStartBit(8);
s4.setBitLength(8);
s4.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
const QCanSignalDescription::MultiplexValues muxValuesS1 { {2, 3}, {5, 5} };
s4.addMultiplexSignal(s1.name(), muxValuesS1);
const QtCanBus::UniqueId uniqueId{123};
QCanMessageDescription msg;
msg.setName("test");
msg.setUniqueId(uniqueId);
msg.setSize(payload.size());
msg.setSignalDescriptions({ s0, s1, s2, s3, s4 });
// Assume that our uniqueId is the whole FrameId
QCanUniqueIdDescription uidDesc;
uidDesc.setBitLength(29);
QCanFrameProcessor parser;
parser.setUniqueIdDescription(uidDesc);
parser.addMessageDescriptions({ msg });
QCanBusFrame frame(static_cast<QCanBusFrame::FrameId>(msg.uniqueId()), payload);
QVERIFY(frame.isValid());
const auto result = parser.parseFrame(frame);
QCOMPARE(result.uniqueId, uniqueId);
QCOMPARE(result.signalValues, expectedResult);
}
void tst_QCanFrameProcessor::parseWithErrorsAndWarnings_data()
{
QTest::addColumn<QCanMessageDescription>("messageDescription");
QTest::addColumn<QCanUniqueIdDescription>("uniqueIdDescription");
QTest::addColumn<QCanBusFrame>("frame");
QTest::addColumn<QCanFrameProcessor::Error>("expectedError");
QTest::addColumn<QString>("expectedErrorDescription");
QTest::addColumn<QStringList>("expectedWarnings");
QTest::addColumn<QtCanBus::UniqueId>("expectedUniqueId");
QTest::addColumn<QVariantMap>("expectedResult");
QTest::addRow("Invalid frame") << QCanMessageDescription()
<< QCanUniqueIdDescription()
<< QCanBusFrame(0x2345ABCD, QByteArray())
<< QCanFrameProcessor::Error::InvalidFrame
<< tr("Invalid frame.")
<< QStringList()
<< QtCanBus::UniqueId(0)
<< QVariantMap();
QTest::addRow("Unsupported frame") << QCanMessageDescription()
<< QCanUniqueIdDescription()
<< QCanBusFrame(QCanBusFrame::RemoteRequestFrame)
<< QCanFrameProcessor::Error::UnsupportedFrameFormat
<< tr("Unsupported frame format.")
<< QStringList()
<< QtCanBus::UniqueId(0)
<< QVariantMap();
QTest::addRow("No UID description")
<< QCanMessageDescription() << QCanUniqueIdDescription()
<< QCanBusFrame(123, QByteArray(2, 0x01))
<< QCanFrameProcessor::Error::Decoding
<< tr("No valid unique identifier description is specified.")
<< QStringList() << QtCanBus::UniqueId(0) << QVariantMap();
QCanUniqueIdDescription uidDesc;
uidDesc.setSource(QtCanBus::DataSource::Payload);
uidDesc.setStartBit(8);
uidDesc.setBitLength(16); // for extended FrameId
QTest::addRow("UID extraction failed")
<< QCanMessageDescription() << uidDesc
<< QCanBusFrame(123, QByteArray(2, 0x01))
<< QCanFrameProcessor::Error::Decoding
<< tr("Failed to extract unique id from the frame.")
<< QStringList() << QtCanBus::UniqueId(0) << QVariantMap();
uidDesc = QCanUniqueIdDescription(); // reset
uidDesc.setBitLength(29);
QTest::addRow("Unknown unique id")
<< QCanMessageDescription() << uidDesc
<< QCanBusFrame(123, QByteArray(8, 0x01))
<< QCanFrameProcessor::Error::Decoding
<< tr("Could not find a message description for unique id 123.")
<< QStringList() << QtCanBus::UniqueId(0) << QVariantMap();
QCanSignalDescription signalDesc;
signalDesc.setName("s0");
signalDesc.setStartBit(7);
signalDesc.setBitLength(16);
QCanMessageDescription messageDesc;
messageDesc.setName("testMessage");
messageDesc.setUniqueId(QtCanBus::UniqueId{123});
messageDesc.setSize(2);
messageDesc.addSignalDescription(signalDesc);
QTest::addRow("Invalid payload size")
<< messageDesc << uidDesc
<< QCanBusFrame(123, QByteArray(8, 0x01))
<< QCanFrameProcessor::Error::Decoding
<< tr("Payload size does not match message description. "
"Actual size = 8, expected size = 2.")
<< QStringList()
<< QtCanBus::UniqueId(0)
<< QVariantMap();
// format float, but length != 32
QCanSignalDescription invalidSignalDesc;
invalidSignalDesc.setName("invalidSignal");
invalidSignalDesc.setStartBit(23);
invalidSignalDesc.setBitLength(8);
invalidSignalDesc.setDataFormat(QtCanBus::DataFormat::Float);
messageDesc.setSize(8);
messageDesc.addSignalDescription(invalidSignalDesc);
QStringList expectedWarnings = { tr("Skipping signal invalidSignal in message with "
"unique id 123 because its description is invalid.") };
// Invalid signal skipped, and we still get results from other signals
QTest::addRow("invalid signal desc warning")
<< messageDesc << uidDesc << QCanBusFrame(123, QByteArray(8, 0x01))
<< QCanFrameProcessor::Error::None << QString()
<< expectedWarnings << QtCanBus::UniqueId(123)
<< QVariantMap({ qMakePair("s0", 0x0101) });
// other valid signal
signalDesc.setName("s1");
signalDesc.setStartBit(39);
signalDesc.setBitLength(8);
messageDesc.addSignalDescription(signalDesc);
// valid signal, but length exceeds frame length
signalDesc.setName("tooLong");
signalDesc.setStartBit(47);
signalDesc.setBitLength(32);
messageDesc.addSignalDescription(signalDesc);
expectedWarnings.push_back(tr("Skipping signal tooLong in message with unique id 123. "
"Its expected length exceeds the data length."));
// Two invalid signals skipped, two valid results are received
QTest::addRow("invalid signal desc warning")
<< messageDesc << uidDesc << QCanBusFrame(123, QByteArray(8, 0x01))
<< QCanFrameProcessor::Error::None << QString()
<< expectedWarnings << QtCanBus::UniqueId(123)
<< QVariantMap({ qMakePair("s0", 0x0101), qMakePair("s1", 0x01) });
// Signals whose end-of-signal bit position exceeds 65535 must be skipped:
// that position must not truncate to a small value that passes the bounds
// check while the untruncated start bit drives the memory access.
// The end bit position calculation depends on endianness, so
// both endians are covered.
QCanMessageDescription truncationMsg;
truncationMsg.setName("truncationMessage");
truncationMsg.setUniqueId(QtCanBus::UniqueId{123});
truncationMsg.setSize(8);
QCanSignalDescription oobLittleEndian;
oobLittleEndian.setName("oobLittleEndian");
oobLittleEndian.setDataEndian(QSysInfo::Endian::LittleEndian);
oobLittleEndian.setDataFormat(QtCanBus::DataFormat::UnsignedInteger);
oobLittleEndian.setStartBit(65480); // 65480 + 64 - 1 = 65543, truncates to 7
oobLittleEndian.setBitLength(64);
truncationMsg.addSignalDescription(oobLittleEndian);
QCanSignalDescription oobBigEndian;
oobBigEndian.setName("oobBigEndian");
oobBigEndian.setDataEndian(QSysInfo::Endian::BigEndian);
oobBigEndian.setDataFormat(QtCanBus::DataFormat::UnsignedInteger);
oobBigEndian.setStartBit(65535); // last byte 8199 -> bit 65591, truncates to 55
oobBigEndian.setBitLength(64);
truncationMsg.addSignalDescription(oobBigEndian);
const QStringList truncationWarnings = {
tr("Skipping signal oobLittleEndian in message with unique id 123. "
"Its expected length exceeds the data length."),
tr("Skipping signal oobBigEndian in message with unique id 123. "
"Its expected length exceeds the data length.")
};
QTest::addRow("signal offset exceeds quint16")
<< truncationMsg << uidDesc << QCanBusFrame(123, QByteArray(8, 0x41))
<< QCanFrameProcessor::Error::None << QString()
<< truncationWarnings << QtCanBus::UniqueId(123)
<< QVariantMap();
}
void tst_QCanFrameProcessor::parseWithErrorsAndWarnings()
{
QFETCH(QCanMessageDescription, messageDescription);
QFETCH(QCanUniqueIdDescription, uniqueIdDescription);
QFETCH(QCanBusFrame, frame);
QFETCH(QCanFrameProcessor::Error, expectedError);
QFETCH(QString, expectedErrorDescription);
QFETCH(QStringList, expectedWarnings);
QFETCH(QtCanBus::UniqueId, expectedUniqueId);
QFETCH(QVariantMap, expectedResult);
QCanFrameProcessor parser;
parser.setUniqueIdDescription(uniqueIdDescription);
parser.addMessageDescriptions({ messageDescription });
const auto result = parser.parseFrame(frame);
QCOMPARE(parser.error(), expectedError);
QCOMPARE(parser.errorString(), expectedErrorDescription);
// signal descriptions are stored as a QHash internally, so they are
// processed in a hash-dependent order. Because of that the order of
// warnings cannot be defined. So we sort them alphabetically before
// comparing.
std::sort(expectedWarnings.begin(), expectedWarnings.end());
QStringList warnings = parser.warnings();
std::sort(warnings.begin(), warnings.end());
QCOMPARE(warnings, expectedWarnings);
QCOMPARE(result.uniqueId, expectedUniqueId);
// The values can be doubles, so we need to compare them explicitly
const auto signalValues = result.signalValues;
QCOMPARE(signalValues.keys(), expectedResult.keys());
for (auto it = expectedResult.cbegin(); it != expectedResult.cend(); ++it) {
const auto &expectedValue = it.value();
const auto &key = it.key();
if (expectedValue.canConvert(QMetaType::fromType<double>()))
QCOMPARE(signalValues.value(key).toDouble(), expectedValue.toDouble());
else
QCOMPARE(signalValues.value(key), expectedValue);
}
}
void tst_QCanFrameProcessor::extractUniqueId_data()
{
QTest::addColumn<QCanUniqueIdDescription>("uniqueIdDescription");
QTest::addColumn<QCanBusFrame>("frame");
QTest::addColumn<QtCanBus::UniqueId>("expectedUniqueId");
// unique id in frame id
QCanUniqueIdDescription uidDesc;
uidDesc.setSource(QtCanBus::DataSource::FrameId);
uidDesc.setBitLength(12);
const quint32 uniqueId = 0x0965;
for (quint16 startBit = 0; startBit < 8; ++startBit) {
uidDesc.setStartBit(startBit);
uidDesc.setEndian(QSysInfo::Endian::LittleEndian);
const QCanBusFrame::FrameId frameIdLe = qToLittleEndian(uniqueId) << startBit;
QTest::addRow("frameId, LE, start %d", startBit)
<< uidDesc
<< QCanBusFrame(frameIdLe, QByteArray(1, 0x01))
<< QtCanBus::UniqueId{uniqueId};
}
static constexpr quint16 startBitsFrame[] = { 3, 2, 1, 0, 15, 14, 13, 12 };
static constexpr qsizetype startBitsFrameLen =
sizeof(startBitsFrame)/sizeof(startBitsFrame[0]);
for (qsizetype idx = 0; idx < startBitsFrameLen; ++idx) {
const auto val = (qToLittleEndian(uniqueId) << 16) >> idx;
const auto startBit = startBitsFrame[idx];
const QCanBusFrame::FrameId frameIdBe = qToBigEndian(val);
uidDesc.setEndian(QSysInfo::Endian::BigEndian);
uidDesc.setStartBit(startBit);
QTest::addRow("frameId, BE, start %d", startBit)
<< uidDesc
<< QCanBusFrame(frameIdBe, QByteArray(1, 0x01))
<< QtCanBus::UniqueId{uniqueId};
}
// unique id in payload, the 1st byte of the payload is used for signal,
// so we start from the 2nd byte
uidDesc.setSource(QtCanBus::DataSource::Payload);
for (quint16 startBit = 8; startBit < 16; ++startBit) {
uidDesc.setStartBit(startBit);
uidDesc.setEndian(QSysInfo::Endian::LittleEndian);
const quint32 payload = 0x01 | (qToLittleEndian(uniqueId) << startBit);
const QByteArray payloadData(reinterpret_cast<const char *>(&payload), sizeof(payload));
QTest::addRow("payload, LE, offset %d", startBit)
<< uidDesc
<< QCanBusFrame(0, payloadData)
<< QtCanBus::UniqueId{uniqueId};
}
static constexpr quint16 startBitsPayload[] = { 11, 10, 9, 8, 23, 22, 21, 20 };
static constexpr qsizetype startBitsPayloadLen =
sizeof(startBitsPayload)/sizeof(startBitsPayload[0]);
for (qsizetype idx = 0; idx < startBitsPayloadLen; ++idx) {
const auto startBit = startBitsPayload[idx];
const auto val = (qToLittleEndian(uniqueId) << 8) >> idx;
quint32 payload = 0x01 | qToBigEndian(val);
const QByteArray payloadData(reinterpret_cast<const char *>(&payload), sizeof(payload));
uidDesc.setEndian(QSysInfo::Endian::BigEndian);
uidDesc.setStartBit(startBit);
QTest::addRow("payload, BE, offset %d", startBit)
<< uidDesc
<< QCanBusFrame(0, payloadData)
<< QtCanBus::UniqueId{uniqueId};
}
}
void tst_QCanFrameProcessor::extractUniqueId()
{
QFETCH(QCanUniqueIdDescription, uniqueIdDescription);
QFETCH(QCanBusFrame, frame);
QFETCH(QtCanBus::UniqueId, expectedUniqueId);
// Some valid message description.
QCanSignalDescription signalDesc;
signalDesc.setName("s0");
signalDesc.setStartBit(7);
signalDesc.setBitLength(8);
QCanMessageDescription messageDesc;
messageDesc.setName("test");
messageDesc.setUniqueId(expectedUniqueId);
messageDesc.setSize(frame.payload().size());
messageDesc.addSignalDescription(signalDesc);
QCanFrameProcessor processor;
processor.setUniqueIdDescription(uniqueIdDescription);
processor.setMessageDescriptions({ messageDesc });
auto result = processor.parseFrame(frame);
QCOMPARE(result.uniqueId, expectedUniqueId);
}
void tst_QCanFrameProcessor::prepareFrame_data()
{
QTest::addColumn<quint16>("startBit");
QTest::addColumn<quint16>("bitLength");
QTest::addColumn<QtCanBus::DataSource>("source");
QTest::addColumn<QtCanBus::DataFormat>("format");
QTest::addColumn<QSysInfo::Endian>("endian");
QTest::addColumn<QVariant>("signalValue");
QTest::addColumn<quint64>("expectedData");
struct SourceDesc {
QtCanBus::DataSource source;
const char name[8];
};
static constexpr SourceDesc sources[] = {
{QtCanBus::DataSource::FrameId, "FrameId"},
{QtCanBus::DataSource::Payload, "Payload"}
};
for (const auto &s : sources) {
// LE, 16bit, start 0
QTest::addRow("%s, unsigned, 16bit, start 0, le", s.name)
<< quint16(0) << quint16(16) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(65516)
<< quint64(0xFFEC);
QTest::addRow("%s, signed neg, 16bit, start 0, le", s.name)
<< quint16(0) << quint16(16) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(-20)
<< quint64(0xFFEC);
QTest::addRow("%s, signed pos, 16bit, start 0, le", s.name)
<< quint16(0) << quint16(16) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(32748)
<< quint64(0x7FEC);
// LE, 16bit, start 5
QTest::addRow("%s, unsigned, 16bit, start 5, le", s.name)
<< quint16(5) << quint16(16) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(65516)
<< quint64(0xFFECULL << 5);
QTest::addRow("%s, signed neg, 16bit, start 5, le", s.name)
<< quint16(5) << quint16(16) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(-20)
<< quint64(0xFFECULL << 5);
QTest::addRow("%s, signed pos, 16bit, start 5, le", s.name)
<< quint16(5) << quint16(16) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(32748)
<< quint64(0x7FECULL << 5);
// LE, 19bit, start 0
QTest::addRow("%s, unsigned, 19bit, start 0, le", s.name)
<< quint16(0) << quint16(19) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(327660)
<< quint64(0x4FFEC);
QTest::addRow("%s, signed neg, 19bit, start 0, le", s.name)
<< quint16(0) << quint16(19) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(-196628)
<< quint64(0x4FFEC);
QTest::addRow("%s, signed pos, 19bit, start 0, le", s.name)
<< quint16(0) << quint16(19) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(196588)
<< quint64(0x2FFEC);
// LE, 19bit, start 3
QTest::addRow("%s, unsigned, 19bit, start 3, le", s.name)
<< quint16(3) << quint16(19) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(327660)
<< quint64(0x4FFECULL << 3);
QTest::addRow("%s, signed neg, 19bit, start 3, le", s.name)
<< quint16(3) << quint16(19) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(-196628)
<< quint64(0x4FFECULL << 3);
QTest::addRow("%s, signed pos, 19bit, start 3, le", s.name)
<< quint16(3) << quint16(19) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(196588)
<< quint64(0x2FFECULL << 3);
// BE, 16bit, start 7
QTest::addRow("%s, unsigned, 16bit, start 7, be", s.name)
<< quint16(7) << quint16(16) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(65516)
<< quint64(0xECFF);
QTest::addRow("%s, signed neg, 16bit, start 7, be", s.name)
<< quint16(7) << quint16(16) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(-20)
<< quint64(0xECFF);
QTest::addRow("%s, signed pos, 16bit, start 7, be", s.name)
<< quint16(7) << quint16(16) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(32748)
<< quint64(0xEC7F);
// BE, 16bit, start 5
QTest::addRow("%s, unsigned, 16bit, start 5, be", s.name)
<< quint16(5) << quint16(16) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(65516)
<< quint64(0x00FB3F);
QTest::addRow("%s, signed neg, 16bit, start 5, be", s.name)
<< quint16(5) << quint16(16) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(-20)
<< quint64(0x00FB3F);
QTest::addRow("%s, signed pos, 16bit, start 5, be", s.name)
<< quint16(5) << quint16(16) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(32748)
<< quint64(0x00FB1F);
// BE, 21bit, start 7
QTest::addRow("%s, unsigned, 21bit, start 7, be", s.name)
<< quint16(7) << quint16(21) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(1834988) /* 0x1BFFEC */
<< quint64(0x60FFDF);
QTest::addRow("%s, signed neg, 21bit, start 7, be", s.name)
<< quint16(7) << quint16(21) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(-262164)
<< quint64(0x60FFDF);
QTest::addRow("%s, signed pos, 21bit, start 7, be", s.name)
<< quint16(7) << quint16(21) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(786412)
<< quint64(0x60FF5F);
// BE, 21bit, start 4
QTest::addRow("%s, unsigned, 21bit, start 4, be", s.name)
<< quint16(4) << quint16(21) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(1834988) /* 0x1BFFEC */
<< quint64(0xECFF1B);
QTest::addRow("%s, signed neg, 21bit, start 4, be", s.name)
<< quint16(4) << quint16(21) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(-262164)
<< quint64(0xECFF1B);
QTest::addRow("%s, signed pos, 21bit, start 4, be", s.name)
<< quint16(4) << quint16(21) << s.source
<< QtCanBus::DataFormat::SignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(786412)
<< quint64(0xECFF0B);
// Some LE tests for values less than 8 bit
QTest::addRow("%s, unsinged, 4bit, start 0, le", s.name)
<< quint16(0) << quint16(4) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(8)
<< quint64(0x08);
QTest::addRow("%s, unsinged, 4bit, start 3, le", s.name)
<< quint16(3) << quint16(4) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(15)
<< quint64(0x78);
QTest::addRow("%s, unsinged, 4bit, start 7, le", s.name)
<< quint16(7) << quint16(4) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::LittleEndian
<< QVariant(12)
<< quint64(0x0600);
// Some BE tests for values less than 8 bit
QTest::addRow("%s, unsinged, 4bit, start 7, be", s.name)
<< quint16(7) << quint16(4) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(8)
<< quint64(0x80);
QTest::addRow("%s, unsinged, 4bit, start 5, be", s.name)
<< quint16(5) << quint16(4) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(14)
<< quint64(0x38);
QTest::addRow("%s, unsinged, 4bit, start 0, be", s.name)
<< quint16(0) << quint16(4) << s.source
<< QtCanBus::DataFormat::UnsignedInteger
<< QSysInfo::Endian::BigEndian
<< QVariant(3)
<< quint64(0x6000);
}
// float data - payload only
{
const float floatVal = 123.456f;
for (quint16 i = 0; i < 8; ++i) {
const float leFloatVal = qToLittleEndian(floatVal);
quint64 value = 0;
memcpy(&value, &leFloatVal, sizeof(leFloatVal));
value = value << i;
QTest::addRow("Payload, float, start %d, le", i)
<< quint16(i) << quint16(32)
<< QtCanBus::DataSource::Payload
<< QtCanBus::DataFormat::Float
<< QSysInfo::Endian::LittleEndian
<< QVariant(floatVal)
<< value;
// to get the proper BE value with an offset, we need to take an LE
// value, shift it to the right, and then swap bytes
value = 0;
memcpy(&value, &leFloatVal, sizeof(leFloatVal));
value = value << 16; // so that we have some room for right shift
// this means that default start bit is 23
value = value >> i;
value = qToBigEndian(value);
QTest::addRow("Payload, float, start %d, be", 23 - i)
<< quint16(23 - i) << quint16(32)
<< QtCanBus::DataSource::Payload
<< QtCanBus::DataFormat::Float
<< QSysInfo::Endian::BigEndian
<< QVariant(floatVal)
<< value;
}
}
// double data - payload only
{
const double doubleVal = 1.234e-15;
const double leDoubleVal = qToLittleEndian(doubleVal);
quint64 value = 0;
memcpy(&value, &leDoubleVal, sizeof(leDoubleVal));
QTest::addRow("Payload, double, le")
<< quint16(0) << quint16(64)
<< QtCanBus::DataSource::Payload
<< QtCanBus::DataFormat::Double
<< QSysInfo::Endian::LittleEndian
<< QVariant(doubleVal)
<< value;
const double beDoubleVal = qToBigEndian(doubleVal);
value = 0;
memcpy(&value, &beDoubleVal, sizeof(beDoubleVal));
QTest::addRow("Payload, double, be")
<< quint16(7) << quint16(64)
<< QtCanBus::DataSource::Payload
<< QtCanBus::DataFormat::Double
<< QSysInfo::Endian::BigEndian
<< QVariant(doubleVal)
<< value;
}
// ASCII
for (const auto &s : sources) {
const bool isPayload = s.source == QtCanBus::DataSource::Payload;
const QByteArray asciiData = isPayload ? QByteArray("abc", 3) : QByteArray("ab", 2);
const quint16 offset = isPayload ? 8 : 4;
for (quint16 i = 0; i < 8; ++i) {
quint64 value = 0;
memcpy(&value, asciiData.constData(), asciiData.size());
value = value << (offset + i);
QTest::addRow("%s, ascii, start %d", s.name, offset + i)
<< quint16(offset + i) << quint16(asciiData.size() * 8) << s.source
<< QtCanBus::DataFormat::AsciiString
<< QSysInfo::Endian::LittleEndian /* does not matter */
<< QVariant(asciiData)
<< value;
}
}
// ASCII - not enough data
{
const QByteArray data("ab", 2);
QByteArray expectedData = data;
expectedData.push_back('\0'); // ab\0
quint64 value = 0;
memcpy(&value, expectedData.constData(), expectedData.size());
QTest::addRow("ascii, not enough data")
<< quint16(0) << quint16(expectedData.size() * 8)
<< QtCanBus::DataSource::Payload
<< QtCanBus::DataFormat::AsciiString
<< QSysInfo::Endian::LittleEndian
<< QVariant(data)
<< value;
}
// ASCII - too much data
{
const QByteArray data("abcd", 4);
const QByteArray expectedData = data.left(3); // abc
quint64 value = 0;
memcpy(&value, expectedData.constData(), expectedData.size());
QTest::addRow("ascii, too much data")
<< quint16(0) << quint16(expectedData.size() * 8)
<< QtCanBus::DataSource::Payload
<< QtCanBus::DataFormat::AsciiString
<< QSysInfo::Endian::LittleEndian
<< QVariant(data)
<< value;
}
}
void tst_QCanFrameProcessor::prepareFrame()
{
QFETCH(quint16, startBit);
QFETCH(quint16, bitLength);
QFETCH(QtCanBus::DataSource, source);
QFETCH(QtCanBus::DataFormat, format);
QFETCH(QSysInfo::Endian, endian);
QFETCH(QVariant, signalValue);
QFETCH(quint64, expectedData);
QCanSignalDescription signalDesc;
signalDesc.setName("s0");
signalDesc.setStartBit(startBit);
signalDesc.setBitLength(bitLength);
signalDesc.setDataSource(source);
signalDesc.setDataFormat(format);
signalDesc.setDataEndian(endian);
// Unique Id will be located in a different place from the signal data.
// If it's in a payload, we assume that it's in the first byte.
// If it's in the frame id, we assume that it takes the whole frame id.
const bool sourceInPayload = source == QtCanBus::DataSource::Payload;
const auto uidSource = sourceInPayload ? QtCanBus::DataSource::FrameId
: QtCanBus::DataSource::Payload;
const auto uidLength = sourceInPayload ? 29 : 8;
static constexpr quint32 uniqueId = 123;
QCanUniqueIdDescription uidDesc;
uidDesc.setEndian(QSysInfo::Endian::LittleEndian);
uidDesc.setSource(uidSource);
uidDesc.setStartBit(0);
uidDesc.setBitLength(uidLength);
const auto messageSize = sourceInPayload ? sizeof(expectedData) : 1;
QCanMessageDescription messageDesc;
messageDesc.setName("test");
messageDesc.setUniqueId(QtCanBus::UniqueId{uniqueId});
messageDesc.setSize(messageSize);
messageDesc.setSignalDescriptions({ signalDesc });
const QVariantMap signalValues = { qMakePair(signalDesc.name(), signalValue) };
QCanBusFrame expectedFrame;
if (sourceInPayload) {
expectedFrame.setFrameId(uniqueId & 0x1FFFFFFFU);
const QByteArray payload(reinterpret_cast<const char *>(&expectedData),
sizeof(expectedData));
expectedFrame.setPayload(payload);
} else {
expectedFrame.setFrameId(expectedData & 0x1FFFFFFFU);
// Explicitly truncating uniqueId to 1 byte. Fine, because it equals 123.
expectedFrame.setPayload(QByteArray(1, static_cast<char>(uniqueId)));
}
QVERIFY(expectedFrame.isValid());
QCanFrameProcessor processor;
processor.setUniqueIdDescription(uidDesc);
processor.addMessageDescriptions({ messageDesc });
const QCanBusFrame frame = processor.prepareFrame(QtCanBus::UniqueId{uniqueId}, signalValues);
QVERIFY(frame.isValid());
QCOMPARE(frame.frameId(), expectedFrame.frameId());
QCOMPARE(frame.payload(), expectedFrame.payload());
}
void tst_QCanFrameProcessor::prepareWithValueConversion_data()
{
QTest::addColumn<double>("factor");
QTest::addColumn<double>("offset");
QTest::addColumn<double>("scaling");
QTest::addColumn<QVariant>("signalValue");
QTest::addColumn<QByteArray>("expectedPayload");
// payloadValue = (signalValue / scaling - offset) / factor;
QTest::newRow("nan, nan, nan") << qQNaN() << qQNaN() << qQNaN() << QVariant(30) << QByteArray(1, 30);
QTest::newRow("0.75, nan, nan") << 0.75 << qQNaN() << qQNaN() << QVariant(22.5) << QByteArray(1, 30);
QTest::newRow("0.75, 15.3, nan") << 0.75 << 15.3 << qQNaN() << QVariant(37.8) << QByteArray(1, 30);
QTest::newRow("0.75, 15.3, 100") << 0.75 << 15.3 << 100. << QVariant(3780.) << QByteArray(1, 30);
QTest::newRow("0.75, nan, 100") << 0.75 << qQNaN() << 100. << QVariant(2250.) << QByteArray(1, 30);
QTest::newRow("nan, 15.3, 100") << qQNaN() << 15.3 << 100. << QVariant(4530.) << QByteArray(1, 30);
// check that setting factor and/or scaling to 0 is equivalent to NaN
QTest::newRow("0, 15.3, 100") << 0. << 15.3 << 100. << QVariant(4530.) << QByteArray(1, 30);
QTest::newRow("0, 15.3, 0") << 0. << 15.3 << 0. << QVariant(45.3) << QByteArray(1, 30);
}
void tst_QCanFrameProcessor::prepareWithValueConversion()
{
QFETCH(double, factor);
QFETCH(double, offset);
QFETCH(double, scaling);
QFETCH(QVariant, signalValue);
QFETCH(QByteArray, expectedPayload);
QCanSignalDescription signalDesc;
signalDesc.setName("s0");
signalDesc.setStartBit(7);
signalDesc.setBitLength(8);
signalDesc.setFactor(factor);
signalDesc.setOffset(offset);
signalDesc.setScaling(scaling);
const QtCanBus::UniqueId uniqueId{123};
QCanMessageDescription messageDesc;
messageDesc.setUniqueId(uniqueId);
messageDesc.setSize(expectedPayload.size());
messageDesc.addSignalDescription(signalDesc);
// Assume that our uniqueId is the whole FrameId
QCanUniqueIdDescription uidDesc;
uidDesc.setBitLength(29);
const QVariantMap signalValues = { qMakePair(signalDesc.name(), signalValue) };
QCanFrameProcessor processor;
processor.setUniqueIdDescription(uidDesc);
processor.addMessageDescriptions({ messageDesc });
const QCanBusFrame frame = processor.prepareFrame(uniqueId, signalValues);
QVERIFY(frame.isValid());
QCOMPARE(frame.frameId(), static_cast<QCanBusFrame::FrameId>(uniqueId));
QCOMPARE(frame.payload(), expectedPayload);
}
void tst_QCanFrameProcessor::prepareMultiplexedPayload_data()
{
QTest::addColumn<QByteArray>("expectedPayload");
QTest::addColumn<QVariantMap>("signalValues");
const QString s0Name("s0");
const QString s1Name("s1");
const QString s2Name("s2");
const QString s3Name("s3");
const QString s4Name("s4");
QTest::newRow("s0 and s2") << QByteArray::fromHex("2334")
<< QVariantMap({ qMakePair(s0Name, 3),
qMakePair(s2Name, 0x342) });
QTest::newRow("s0, s1 and s3") << QByteArray::fromHex("1134")
<< QVariantMap({ qMakePair(s0Name, 1),
qMakePair(s1Name, 1),
qMakePair(s3Name, 0x34) });
QTest::newRow("s0, s1 and s4") << QByteArray::fromHex("5134")
<< QVariantMap({ qMakePair(s0Name, 1),
qMakePair(s1Name, 5),
qMakePair(s4Name, 0x34) });
QTest::newRow("s0 and s1") << QByteArray::fromHex("3100")
<< QVariantMap({ qMakePair(s0Name, 1),
qMakePair(s1Name, 3) });
QTest::newRow("only s0") << QByteArray::fromHex("0300")
<< QVariantMap({ qMakePair(s0Name, 3) });
}
void tst_QCanFrameProcessor::prepareMultiplexedPayload()
{
QFETCH(QByteArray, expectedPayload);
QFETCH(QVariantMap, signalValues);
QCanSignalDescription s0; // multiplexor 1
s0.setName("s0");
s0.setDataEndian(QSysInfo::Endian::LittleEndian);
s0.setStartBit(0);
s0.setBitLength(4);
s0.setMultiplexState(QtCanBus::MultiplexState::MultiplexorSwitch);
QCanSignalDescription s1; // multiplexor 2, also depends on the value of s0
s1.setName("s1");
s1.setDataEndian(QSysInfo::Endian::LittleEndian);
s1.setStartBit(4);
s1.setBitLength(4);
s1.setMultiplexState(QtCanBus::MultiplexState::SwitchAndSignal);
s1.addMultiplexSignal(s0.name(), 1);
QCanSignalDescription s2; // depends on mux1 only, contains 12 bits of data
s2.setName("s2");
s2.setDataEndian(QSysInfo::Endian::LittleEndian);
s2.setStartBit(4);
s2.setBitLength(12);
s2.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
const QCanSignalDescription::MultiplexValues muxValuesS0 { {2, 4} };
s2.addMultiplexSignal(s0.name(), muxValuesS0);
QCanSignalDescription s3; // depends on mux2 (so indirectly on mux1)
s3.setName("s3");
s3.setDataEndian(QSysInfo::Endian::LittleEndian);
s3.setStartBit(8);
s3.setBitLength(8);
s3.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
s3.addMultiplexSignal(s1.name(), 1);
QCanSignalDescription s4; // depends on mux2 (so indirectly on mux1)
s4.setName("s4");
s4.setDataEndian(QSysInfo::Endian::LittleEndian);
s4.setStartBit(8);
s4.setBitLength(8);
s4.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
const QCanSignalDescription::MultiplexValues muxValuesS1 { {2, 3}, {5, 5} };
s4.addMultiplexSignal(s1.name(), muxValuesS1);
const QtCanBus::UniqueId uniqueId{123};
QCanMessageDescription msg;
msg.setName("test");
msg.setUniqueId(uniqueId);
msg.setSize(expectedPayload.size());
msg.setSignalDescriptions({ s0, s1, s2, s3, s4 });
// Assume that our uniqueId is the whole FrameId
QCanUniqueIdDescription uidDesc;
uidDesc.setBitLength(29);
QCanFrameProcessor processor;
processor.setUniqueIdDescription(uidDesc);
processor.addMessageDescriptions({ msg });
const QCanBusFrame frame = processor.prepareFrame(msg.uniqueId(), signalValues);
QVERIFY(frame.isValid());
QCOMPARE(frame.frameId(), static_cast<QCanBusFrame::FrameId>(uniqueId));
QCOMPARE(frame.payload(), expectedPayload);
}
void tst_QCanFrameProcessor::prepareWithErrorsAndWarnings_data()
{
QTest::addColumn<QCanMessageDescription>("messageDescription");
QTest::addColumn<QCanUniqueIdDescription>("uniqueIdDescription");
QTest::addColumn<QtCanBus::UniqueId>("uniqueId");
QTest::addColumn<QVariantMap>("signalValues");
QTest::addColumn<QCanFrameProcessor::Error>("expectedError");
QTest::addColumn<QString>("expectedErrorDescription");
QTest::addColumn<QStringList>("expectedWarnings");
QTest::addColumn<QCanBusFrame::FrameId>("expectedFrameId");
QTest::addColumn<QByteArray>("expectedPayload");
QTest::addRow("no UID description")
<< QCanMessageDescription() << QCanUniqueIdDescription()
<< QtCanBus::UniqueId(0) << QVariantMap()
<< QCanFrameProcessor::Error::Encoding
<< tr("No valid unique identifier description is specified.")
<< QStringList() << QCanBusFrame::FrameId(0) << QByteArray();
QCanUniqueIdDescription uidDesc;
uidDesc.setBitLength(29);
QTest::addRow("no message description")
<< QCanMessageDescription() << uidDesc << QtCanBus::UniqueId(123)
<< QVariantMap() << QCanFrameProcessor::Error::Encoding
<< tr("Failed to find message description for unique id 123.")
<< QStringList() << QCanBusFrame::FrameId(0) << QByteArray();
QCanSignalDescription signalDesc;
signalDesc.setName("s0");
signalDesc.setStartBit(3);
signalDesc.setBitLength(4);
QCanMessageDescription messageDesc;
messageDesc.setUniqueId(QtCanBus::UniqueId{123});
messageDesc.setSize(2);
messageDesc.addSignalDescription(signalDesc);
uidDesc.setSource(QtCanBus::DataSource::Payload);
uidDesc.setStartBit(8);
uidDesc.setBitLength(16); // does not fit into the payload
QTest::addRow("set UID fail")
<< messageDesc << uidDesc << messageDesc.uniqueId()
<< QVariantMap() << QCanFrameProcessor::Error::Encoding
<< tr("Failed to encode unique id 123 into the frame")
<< QStringList() << QCanBusFrame::FrameId(0) << QByteArray();
// reset uidDesc
uidDesc = QCanUniqueIdDescription();
uidDesc.setBitLength(29);
QStringList warnings = { tr("Skipping signal s1. It is not found in message "
"description for unique id 123.") };
QTest::addRow("no signal description")
<< messageDesc << uidDesc << messageDesc.uniqueId()
<< QVariantMap({ qMakePair("s0", 1), qMakePair("s1", 2) })
<< QCanFrameProcessor::Error::None << QString()
<< warnings << QCanBusFrame::FrameId(messageDesc.uniqueId())
<< QByteArray::fromHex("0100");
// invalid signal desc - 0 length
signalDesc.setName("s2");
signalDesc.setStartBit(7);
signalDesc.setBitLength(0);
messageDesc.addSignalDescription(signalDesc);
warnings.push_back(tr("Skipping signal s2. Its description is invalid."));
QTest::addRow("invalid signal description")
<< messageDesc << uidDesc << messageDesc.uniqueId()
<< QVariantMap({ qMakePair("s0", 1), qMakePair("s1", 2), qMakePair("s2", 3) })
<< QCanFrameProcessor::Error::None << QString()
<< warnings << QCanBusFrame::FrameId(messageDesc.uniqueId())
<< QByteArray::fromHex("0100");
// max bit exceed data length
signalDesc.setName("s3");
signalDesc.setStartBit(15);
signalDesc.setBitLength(10);
messageDesc.addSignalDescription(signalDesc);
warnings.push_back(tr("Skipping signal s3. Its length exceeds the expected message length."));
QTest::addRow("invalid signal length")
<< messageDesc << uidDesc << messageDesc.uniqueId()
<< QVariantMap({ qMakePair("s0", 1), qMakePair("s1", 2),
qMakePair("s2", 3), qMakePair("s3", 4) })
<< QCanFrameProcessor::Error::None << QString()
<< warnings << QCanBusFrame::FrameId(messageDesc.uniqueId())
<< QByteArray::fromHex("0100");
// Test that multiplexed signals are used only when proper multiplexor
// values are provided.
messageDesc.clearSignalDescriptions();
// first multiplexor
signalDesc.setName("s0");
signalDesc.setMultiplexState(QtCanBus::MultiplexState::MultiplexorSwitch);
signalDesc.setStartBit(7);
signalDesc.setBitLength(4);
messageDesc.addSignalDescription(signalDesc);
// s1 is used only when s0 is in range [2, 4] or 6
signalDesc.setName("s1");
signalDesc.setStartBit(3);
signalDesc.setBitLength(12);
signalDesc.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
const QCanSignalDescription::MultiplexValues muxValuesS0 { {2, 4}, {6, 6} };
signalDesc.addMultiplexSignal("s0", muxValuesS0);
messageDesc.addSignalDescription(signalDesc);
// second multiplexor, used when s0 == 1
signalDesc.setName("s2");
signalDesc.setStartBit(3);
signalDesc.setBitLength(4);
signalDesc.setMultiplexState(QtCanBus::MultiplexState::SwitchAndSignal);
signalDesc.addMultiplexSignal("s0", 1);
messageDesc.addSignalDescription(signalDesc);
// s3 depends on two multiplexors
signalDesc.setName("s3");
signalDesc.setStartBit(15);
signalDesc.setBitLength(8);
signalDesc.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
signalDesc.addMultiplexSignal("s0", 1);
const QCanSignalDescription::MultiplexValues muxValuesS2 { {1, 3}, {5, 6} };
signalDesc.addMultiplexSignal("s2", muxValuesS2);
messageDesc.addSignalDescription(signalDesc);
// s4 depends on two multiplexors
signalDesc.setName("s4");
signalDesc.setStartBit(15);
signalDesc.setBitLength(8);
signalDesc.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal);
signalDesc.addMultiplexSignal("s0", 1);
signalDesc.addMultiplexSignal("s2", 4);
messageDesc.addSignalDescription(signalDesc);
warnings.clear();
warnings.push_back(tr("Skipping signal s1. Proper multiplexor values not found."));
warnings.push_back(tr("Skipping signal s4. Proper multiplexor values not found."));
QTest::addRow("multiplexed signals")
<< messageDesc << uidDesc << messageDesc.uniqueId()
<< QVariantMap({ qMakePair("s0", 1), qMakePair("s1", 10), qMakePair("s2", 2),
qMakePair("s3", 5), qMakePair("s4", 6) })
<< QCanFrameProcessor::Error::None << QString()
<< warnings << QCanBusFrame::FrameId(messageDesc.uniqueId())
<< QByteArray::fromHex("1205");
// A signal whose (startBit + bitLength) exceeds 65535 must be skipped, not
// encoded: otherwise the truncated end-of-signal position passes the bounds
// check while the untruncated start offset drives an out-of-bounds write.
QCanMessageDescription truncationMsg;
truncationMsg.setUniqueId(QtCanBus::UniqueId{123});
truncationMsg.setSize(8);
QCanSignalDescription oobWrite;
oobWrite.setName("oobWrite");
oobWrite.setDataEndian(QSysInfo::Endian::LittleEndian);
oobWrite.setDataFormat(QtCanBus::DataFormat::UnsignedInteger);
oobWrite.setStartBit(65480); // 65480 + 64 - 1 = 65543, truncates to 7
oobWrite.setBitLength(64);
truncationMsg.addSignalDescription(oobWrite);
QTest::addRow("signal offset exceeds quint16")
<< truncationMsg << uidDesc << truncationMsg.uniqueId()
<< QVariantMap({ qMakePair("oobWrite",
QVariant::fromValue<quint64>(0xdeadbeefcafebabeULL)) })
<< QCanFrameProcessor::Error::None << QString()
<< QStringList{ tr("Skipping signal oobWrite. Its length exceeds the "
"expected message length.") }
<< QCanBusFrame::FrameId(truncationMsg.uniqueId())
<< QByteArray(8, 0x00);
}
void tst_QCanFrameProcessor::prepareWithErrorsAndWarnings()
{
QFETCH(QCanMessageDescription, messageDescription);
QFETCH(QCanUniqueIdDescription, uniqueIdDescription);
QFETCH(QtCanBus::UniqueId, uniqueId);
QFETCH(QVariantMap, signalValues);
QFETCH(QCanFrameProcessor::Error, expectedError);
QFETCH(QString, expectedErrorDescription);
QFETCH(QStringList, expectedWarnings);
QFETCH(QCanBusFrame::FrameId, expectedFrameId);
QFETCH(QByteArray, expectedPayload);
QCanFrameProcessor processor;
processor.setUniqueIdDescription(uniqueIdDescription);
processor.setMessageDescriptions({ messageDescription });
const QCanBusFrame frame = processor.prepareFrame(uniqueId, signalValues);
QCOMPARE(processor.error(), expectedError);
QCOMPARE(processor.errorString(), expectedErrorDescription);
// signal descriptions are stored as a QHash internally, so they are
// processed in a hash-dependent order. Because of that the order of
// warnings cannot be defined. So we sort them alphabetically before
// comparing.
std::sort(expectedWarnings.begin(), expectedWarnings.end());
QStringList warnings = processor.warnings();
std::sort(warnings.begin(), warnings.end());
QCOMPARE(warnings, expectedWarnings);
QCOMPARE(frame.frameId(), expectedFrameId);
QCOMPARE(frame.payload(), expectedPayload);
}
void tst_QCanFrameProcessor::prepareUniqueId_data()
{
QTest::addColumn<QCanUniqueIdDescription>("uniqueIdDescription");
QTest::addColumn<QCanMessageDescription>("messageDescription");
QTest::addColumn<QtCanBus::UniqueId>("uniqueId");
QTest::addColumn<QVariantMap>("signalValues");
QTest::addColumn<QCanBusFrame>("expectedFrame");
const quint32 uniqueId = 0x0965;
QCanSignalDescription signalDesc;
signalDesc.setName("s0");
signalDesc.setDataSource(QtCanBus::DataSource::Payload);
signalDesc.setDataEndian(QSysInfo::Endian::LittleEndian);
signalDesc.setDataFormat(QtCanBus::DataFormat::UnsignedInteger);
signalDesc.setStartBit(0);
signalDesc.setBitLength(8);
QCanMessageDescription messageDesc;
messageDesc.setName("test");
messageDesc.setUniqueId(QtCanBus::UniqueId{uniqueId});
messageDesc.setSize(1);
messageDesc.addSignalDescription(signalDesc);
const QVariantMap signalValues { qMakePair("s0", 1) };
// Encode unique id into frame id.
QCanUniqueIdDescription uidDesc;
uidDesc.setSource(QtCanBus::DataSource::FrameId);
uidDesc.setBitLength(12);
for (quint8 startBit = 0; startBit < 8; ++startBit) {
uidDesc.setStartBit(startBit);
uidDesc.setEndian(QSysInfo::Endian::LittleEndian);
const QCanBusFrame::FrameId frameId = qFromLittleEndian(uniqueId) << startBit;
QTest::addRow("frameId, LE, start %d", startBit)
<< uidDesc << messageDesc << QtCanBus::UniqueId{uniqueId}
<< signalValues << QCanBusFrame(frameId, QByteArray(1, 0x01));
}
static constexpr quint16 startBitsFrame[] = { 3, 2, 1, 0, 15, 14, 13, 12 };
static constexpr quint16 startBitsFrameLen =
sizeof(startBitsFrame)/sizeof(startBitsFrame[0]);
for (quint16 idx = 0; idx < startBitsFrameLen; ++idx) {
const auto val = (qToLittleEndian(uniqueId) << 16) >> idx;
const QCanBusFrame::FrameId frameId = qToBigEndian(val);
const auto startBit = startBitsFrame[idx];
uidDesc.setEndian(QSysInfo::Endian::BigEndian);
uidDesc.setStartBit(startBit);
QTest::addRow("frameId, BE, offser %d", startBit)
<< uidDesc << messageDesc << QtCanBus::UniqueId{uniqueId}
<< signalValues << QCanBusFrame(frameId, QByteArray(1, 0x01));
}
// Encode unique id into payload. The 1st byte of the payload is already
// populated with a signal value, so we start from the 2nd byte
uidDesc.setSource(QtCanBus::DataSource::Payload);
messageDesc.setSize(sizeof(quint32));
for (quint8 startBit = 8; startBit < 16; ++startBit) {
uidDesc.setStartBit(startBit);
uidDesc.setEndian(QSysInfo::Endian::LittleEndian);
const quint32 payload = 0x01 | qFromLittleEndian(uniqueId) << startBit;
const QByteArray payloadData(reinterpret_cast<const char *>(&payload), sizeof(payload));
QTest::addRow("payload, LE, offser %d", startBit)
<< uidDesc << messageDesc << QtCanBus::UniqueId{uniqueId}
<< signalValues << QCanBusFrame(0, payloadData);
}
static constexpr quint16 startBitsPayload[] = { 11, 10, 9, 8, 23, 22, 21, 20 };
static constexpr qsizetype startBitsPayloadLen =
sizeof(startBitsPayload)/sizeof(startBitsPayload[0]);
for (quint16 idx = 0; idx < startBitsPayloadLen; ++idx) {
const auto val = (qToLittleEndian(uniqueId) << 8) >> idx;
const auto startBit = startBitsPayload[idx];
quint32 payload = 0x01 | qToBigEndian(val);
const QByteArray payloadData(reinterpret_cast<const char *>(&payload), sizeof(payload));
uidDesc.setEndian(QSysInfo::Endian::BigEndian);
uidDesc.setStartBit(startBit);
QTest::addRow("payload, BE, start %d", startBit)
<< uidDesc << messageDesc << QtCanBus::UniqueId{uniqueId}
<< signalValues << QCanBusFrame(0, payloadData);
}
}
void tst_QCanFrameProcessor::prepareUniqueId()
{
QFETCH(QCanUniqueIdDescription, uniqueIdDescription);
QFETCH(QCanMessageDescription, messageDescription);
QFETCH(QtCanBus::UniqueId, uniqueId);
QFETCH(QVariantMap, signalValues);
QFETCH(QCanBusFrame, expectedFrame);
QCanFrameProcessor processor;
processor.setUniqueIdDescription(uniqueIdDescription);
processor.setMessageDescriptions({ messageDescription });
const auto result = processor.prepareFrame(uniqueId, signalValues);
QCOMPARE(processor.error(), QCanFrameProcessor::Error::None);
QCOMPARE(result.frameId(), expectedFrame.frameId());
QCOMPARE(result.payload(), expectedFrame.payload());
}
void tst_QCanFrameProcessor::roundtrip_data()
{
QTest::addColumn<QCanMessageDescription>("messageDescription");
QTest::addColumn<QCanUniqueIdDescription>("uniqueIdDescription");
QTest::addColumn<QCanBusFrame>("inputFrame");
QTest::addColumn<QtCanBus::UniqueId>("expectedUniqueId");
QTest::addColumn<QVariantMap>("expectedSignalValues");
{
QCanUniqueIdDescription uidDesc;
uidDesc.setSource(QtCanBus::DataSource::FrameId);
uidDesc.setEndian(QSysInfo::Endian::LittleEndian);
uidDesc.setStartBit(0);
uidDesc.setBitLength(11);
const quint32 uniqueId = 1467;
QCanMessageDescription messageDesc;
messageDesc.setName("test");
messageDesc.setUniqueId(QtCanBus::UniqueId{uniqueId});
messageDesc.setSize(8);
// s0 - 4 bits [0:3], signed int
QCanSignalDescription signalDesc;
signalDesc.setName("s0");
signalDesc.setDataSource(QtCanBus::DataSource::Payload);
signalDesc.setDataEndian(QSysInfo::Endian::LittleEndian);
signalDesc.setDataFormat(QtCanBus::DataFormat::SignedInteger);
signalDesc.setStartBit(0);
signalDesc.setBitLength(4);
messageDesc.addSignalDescription(signalDesc);
// s1 - 12 bits [4:15], unsigned int
signalDesc.setName("s1");
signalDesc.setDataFormat(QtCanBus::DataFormat::UnsignedInteger);
signalDesc.setStartBit(4);
signalDesc.setBitLength(12);
messageDesc.addSignalDescription(signalDesc);
// s2 - 32 bits [16:47], float
signalDesc.setName("s2");
signalDesc.setDataFormat(QtCanBus::DataFormat::Float);
signalDesc.setStartBit(16);
signalDesc.setBitLength(32);
messageDesc.addSignalDescription(signalDesc);
// gap - 6 bits [48:53]
// s3 - 10 bits [54:63], unsinged int
signalDesc.setName("s3");
signalDesc.setDataFormat(QtCanBus::DataFormat::UnsignedInteger);
signalDesc.setStartBit(54);
signalDesc.setBitLength(10);
messageDesc.addSignalDescription(signalDesc);
const qint8 s0Val = -3;
const quint16 s1Val = 2890;
const float s2Val = 123.456f;
const quint16 s3Val = 963;
QVariantMap signalValues = { qMakePair("s0", s0Val), qMakePair("s1", s1Val),
qMakePair("s2", s2Val), qMakePair("s3", s3Val) };
quint64 payloadValue = (static_cast<quint64>(s3Val) << 54) +
(static_cast<quint64>(s1Val & 0x0FFF) << 4) + static_cast<quint64>(s0Val & 0x0F);
QByteArray payload(reinterpret_cast<const char *>(&payloadValue), sizeof(quint64));
memcpy(&payload[2], &s2Val, sizeof(float));
QCanBusFrame inputFrame(uniqueId, payload);
QTest::addRow("LE") << messageDesc << uidDesc << inputFrame
<< QtCanBus::UniqueId{uniqueId} << signalValues;
}
{
QCanUniqueIdDescription uidDesc;
uidDesc.setSource(QtCanBus::DataSource::FrameId);
uidDesc.setEndian(QSysInfo::Endian::BigEndian);
uidDesc.setStartBit(7);
uidDesc.setBitLength(11);
const quint32 uniqueId = 1467; // 0x60B7 for the specified layout
QCanMessageDescription messageDesc;
messageDesc.setName("test");
messageDesc.setUniqueId(QtCanBus::UniqueId{uniqueId});
messageDesc.setSize(8);
// s0 - 4 bits [7:4], signed int
QCanSignalDescription signalDesc;
signalDesc.setName("s0");
signalDesc.setDataSource(QtCanBus::DataSource::Payload);
signalDesc.setDataEndian(QSysInfo::Endian::BigEndian);
signalDesc.setDataFormat(QtCanBus::DataFormat::SignedInteger);
signalDesc.setStartBit(7);
signalDesc.setBitLength(4);
messageDesc.addSignalDescription(signalDesc);
// s1 - 12 bits [3:0]+[15:8], unsigned int
signalDesc.setName("s1");
signalDesc.setDataFormat(QtCanBus::DataFormat::UnsignedInteger);
signalDesc.setStartBit(3);
signalDesc.setBitLength(12);
messageDesc.addSignalDescription(signalDesc);
// s2 - 32 bits [23:16]+[31:24]+[39:32]+[47:10], float
signalDesc.setName("s2");
signalDesc.setDataFormat(QtCanBus::DataFormat::Float);
signalDesc.setStartBit(23);
signalDesc.setBitLength(32);
messageDesc.addSignalDescription(signalDesc);
// gap - 6 bits [55:50]
// s3 - 10 bits [49:48]+[63:56], unsinged int
signalDesc.setName("s3");
signalDesc.setDataFormat(QtCanBus::DataFormat::UnsignedInteger);
signalDesc.setStartBit(49);
signalDesc.setBitLength(10);
messageDesc.addSignalDescription(signalDesc);
const qint8 s0Val = -3; // 0xD
const quint16 s1Val = 2890; // 0xB4A
const float s2Val = 123.456f;
const quint16 s3Val = 963; // 0x3C3
QVariantMap signalValues = { qMakePair("s0", s0Val), qMakePair("s1", s1Val),
qMakePair("s2", s2Val), qMakePair("s3", s3Val) };
quint64 payloadValue = 0xC303000000004ADBULL;
QByteArray payload(reinterpret_cast<const char *>(&payloadValue), sizeof(quint64));
const float s2BeVal = qToBigEndian(s2Val);
memcpy(&payload[2], &s2BeVal, sizeof(float));
QCanBusFrame inputFrame(0x60B7, payload);
QTest::addRow("BE") << messageDesc << uidDesc << inputFrame
<< QtCanBus::UniqueId{uniqueId} << signalValues;
}
}
void tst_QCanFrameProcessor::roundtrip()
{
QFETCH(QCanMessageDescription, messageDescription);
QFETCH(QCanUniqueIdDescription, uniqueIdDescription);
QFETCH(QCanBusFrame, inputFrame);
QFETCH(QtCanBus::UniqueId, expectedUniqueId);
QFETCH(QVariantMap, expectedSignalValues);
QCanFrameProcessor processor;
processor.setUniqueIdDescription(uniqueIdDescription);
processor.setMessageDescriptions({ messageDescription });
const auto result = processor.parseFrame(inputFrame);
QCOMPARE(result.uniqueId, expectedUniqueId);
QCOMPARE(result.signalValues, expectedSignalValues);
const auto encodedFrame = processor.prepareFrame(result.uniqueId, result.signalValues);
QCOMPARE(encodedFrame.frameId(), inputFrame.frameId());
QCOMPARE(encodedFrame.payload(), inputFrame.payload());
}
QTEST_MAIN(tst_QCanFrameProcessor)
#include "tst_qcanframeprocessor.moc"