summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorIvan Solovev <ivan.solovev@qt.io>2022-09-13 18:52:52 +0200
committerIvan Solovev <ivan.solovev@qt.io>2022-11-14 15:17:46 +0100
commit92b48dfbd9b02da91cde86fe6b7ee0086302bdca (patch)
tree8877f0a47fdd41ce10375eb95b03400464a348a1 /src
parent3e3e3451a7cb5d79e26285d804d85d9b833631c9 (diff)
Introduce value classes for generic CAN bus parser
[ChangeLog][CAN Bus] Introduce QCanSignalDescription, QCanMessageDescription and QCanUniqueIdDescription classes. These classes are used to provide a set of rules to encode/decode the CAN bus messages. Task-number: QTBUG-98910 Change-Id: I939113e6f27b6ad43cf3d0a7fe3bad1fc0bbc22e Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io> Reviewed-by: André Hartmann <aha_1980@gmx.de> Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/serialbus/CMakeLists.txt5
-rw-r--r--src/serialbus/qcancommondefinitions.cpp147
-rw-r--r--src/serialbus/qcancommondefinitions.h58
-rw-r--r--src/serialbus/qcanmessagedescription.cpp376
-rw-r--r--src/serialbus/qcanmessagedescription.h84
-rw-r--r--src/serialbus/qcanmessagedescription_p.h39
-rw-r--r--src/serialbus/qcansignaldescription.cpp798
-rw-r--r--src/serialbus/qcansignaldescription.h114
-rw-r--r--src/serialbus/qcansignaldescription_p.h62
-rw-r--r--src/serialbus/qcanuniqueiddescription.cpp224
-rw-r--r--src/serialbus/qcanuniqueiddescription.h62
-rw-r--r--src/serialbus/qcanuniqueiddescription_p.h37
12 files changed, 2006 insertions, 0 deletions
diff --git a/src/serialbus/CMakeLists.txt b/src/serialbus/CMakeLists.txt
index b4f64a2..1059d4e 100644
--- a/src/serialbus/CMakeLists.txt
+++ b/src/serialbus/CMakeLists.txt
@@ -13,6 +13,10 @@ qt_internal_add_module(SerialBus
qcanbusdeviceinfo.cpp qcanbusdeviceinfo.h qcanbusdeviceinfo_p.h
qcanbusfactory.cpp qcanbusfactory.h
qcanbusframe.cpp qcanbusframe.h
+ qcancommondefinitions.cpp qcancommondefinitions.h
+ qcanmessagedescription.cpp qcanmessagedescription.h qcanmessagedescription_p.h
+ qcansignaldescription.cpp qcansignaldescription.h qcansignaldescription_p.h
+ qcanuniqueiddescription.cpp qcanuniqueiddescription.h qcanuniqueiddescription_p.h
qmodbus_symbols_p.h
qmodbusadu_p.h
qmodbusclient.cpp qmodbusclient.h qmodbusclient_p.h
@@ -35,6 +39,7 @@ qt_internal_add_module(SerialBus
Qt::CorePrivate
Qt::Network
GENERATE_CPP_EXPORTS
+ GENERATE_PRIVATE_CPP_EXPORTS
)
## Scopes:
diff --git a/src/serialbus/qcancommondefinitions.cpp b/src/serialbus/qcancommondefinitions.cpp
new file mode 100644
index 0000000..8c49de1
--- /dev/null
+++ b/src/serialbus/qcancommondefinitions.cpp
@@ -0,0 +1,147 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qcancommondefinitions.h"
+
+#ifndef QT_NO_DEBUG_STREAM
+#include <QtCore/QDebug>
+#endif // QT_NO_DEBUG_STREAM
+
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \namespace QtCanBus
+ \inmodule QtSerialBus
+ \since 6.5
+ \brief The QtCanBus namespace provides some commons enums that are used in
+ the CAN bus handling part of the QtSerialPort module.
+*/
+
+/*!
+ \enum QtCanBus::DataSource
+
+ This enum represents the placement of the data within the CAN frame.
+
+ \value Payload The data will be extracted from the payload.
+ \value FrameId The data will be extracted from the frame ID.
+*/
+
+/*!
+ \enum QtCanBus::DataFormat
+
+ This enum represents the possible data formats. The format defines how the
+ value will be extracted from its source.
+
+ \value SignedInteger The signal value is a signed integer.
+ \value UnsignedInteger The signal value is an unsigned integer.
+ \value Float The signal value is float.
+ \value Double The signal value is double.
+ \value Ascii The signal value is an ASCII string.
+*/
+
+/*!
+ \enum QtCanBus::DataEndian
+
+ This enum represents the byte order of the data.
+
+ \value LittleEndian The data is little endian.
+ \value BigEndian The data is big endian.
+*/
+
+/*!
+ \enum QtCanBus::MultiplexState
+
+ This enum represents the possible multiplex states of a signal.
+
+ \value None The signal is not used in multiplexing.
+ \value MultiplexorSwitch The signal is used as a multiplexor switch, which
+ means that other signals depend on the values of
+ this signal.
+ \value MultiplexedSignal The signal is multiplexed by some switch, and
+ therefore its value can only be extracted when the
+ switch has a specific value.
+ \value SwitchAndSignal The multiplexor switch of the signal must have the
+ value that enables us to use this signal. When used,
+ the signal also acts as a multiplexor switch for
+ other multiplexed signals.
+*/
+
+/*!
+ \typealias QtCanBus::UniqueId
+*/
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, QtCanBus::DataSource source)
+{
+ QDebugStateSaver saver(dbg);
+ switch (source) {
+ case QtCanBus::DataSource::Payload:
+ dbg << "Payload";
+ break;
+ case QtCanBus::DataSource::FrameId:
+ dbg << "FrameId";
+ break;
+ }
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, QtCanBus::DataFormat format)
+{
+ QDebugStateSaver saver(dbg);
+ switch (format) {
+ case QtCanBus::DataFormat::UnsignedInteger:
+ dbg << "UnsignedInteger";
+ break;
+ case QtCanBus::DataFormat::SignedInteger:
+ dbg << "SignedInteger";
+ break;
+ case QtCanBus::DataFormat::Float:
+ dbg << "Float";
+ break;
+ case QtCanBus::DataFormat::Double:
+ dbg << "Double";
+ break;
+ case QtCanBus::DataFormat::Ascii:
+ dbg << "ASCII";
+ break;
+ }
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, QtCanBus::DataEndian endian)
+{
+ QDebugStateSaver saver(dbg);
+ switch (endian) {
+ case QtCanBus::DataEndian::LittleEndian:
+ dbg << "LittleEndian";
+ break;
+ case QtCanBus::DataEndian::BigEndian:
+ dbg << "BigEndian";
+ break;
+ }
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, QtCanBus::MultiplexState state)
+{
+ QDebugStateSaver saver(dbg);
+ switch (state) {
+ case QtCanBus::MultiplexState::None:
+ dbg << "None";
+ break;
+ case QtCanBus::MultiplexState::MultiplexorSwitch:
+ dbg << "MultiplexorSwitch";
+ break;
+ case QtCanBus::MultiplexState::MultiplexedSignal:
+ dbg << "MultiplexedSignal";
+ break;
+ case QtCanBus::MultiplexState::SwitchAndSignal:
+ dbg << "SwitchAndSignal";
+ break;
+ }
+ return dbg;
+}
+#endif // QT_NO_DEBUG_STREAM
+
+QT_END_NAMESPACE
diff --git a/src/serialbus/qcancommondefinitions.h b/src/serialbus/qcancommondefinitions.h
new file mode 100644
index 0000000..c094538
--- /dev/null
+++ b/src/serialbus/qcancommondefinitions.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCANCOMMONDEFINITIONS_H
+#define QCANCOMMONDEFINITIONS_H
+
+#include <QtCore/qtconfigmacros.h>
+#include <QtCore/qtypes.h>
+
+#include <QtSerialBus/qtserialbusglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QtCanBus {
+
+enum class DataSource : quint8 {
+ Payload = 0,
+ FrameId
+};
+
+enum class DataFormat : quint8 {
+ SignedInteger = 0,
+ UnsignedInteger,
+ Float,
+ Double,
+ Ascii
+};
+
+enum class DataEndian : quint8 {
+ LittleEndian = 0,
+ BigEndian
+};
+
+enum class MultiplexState : quint8 {
+ None = 0x00,
+ MultiplexorSwitch = 0x01,
+ MultiplexedSignal = 0x02,
+ SwitchAndSignal = MultiplexorSwitch | MultiplexedSignal
+};
+
+using UniqueId = quint32;
+
+} // namespace QtCanBus
+
+#ifndef QT_NO_DEBUG_STREAM
+
+class QDebug;
+
+Q_SERIALBUS_EXPORT QDebug operator<<(QDebug dbg, QtCanBus::DataSource source);
+Q_SERIALBUS_EXPORT QDebug operator<<(QDebug dbg, QtCanBus::DataFormat format);
+Q_SERIALBUS_EXPORT QDebug operator<<(QDebug dbg, QtCanBus::DataEndian endian);
+Q_SERIALBUS_EXPORT QDebug operator<<(QDebug dbg, QtCanBus::MultiplexState state);
+
+#endif // QT_NO_DEBUG_STREAM
+
+QT_END_NAMESPACE
+
+#endif // QCANCOMMONDEFINITIONS_H
diff --git a/src/serialbus/qcanmessagedescription.cpp b/src/serialbus/qcanmessagedescription.cpp
new file mode 100644
index 0000000..cfc736b
--- /dev/null
+++ b/src/serialbus/qcanmessagedescription.cpp
@@ -0,0 +1,376 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qcanmessagedescription.h"
+#include "qcanmessagedescription_p.h"
+#include "qcansignaldescription.h"
+
+#include <QtCore/QHash>
+#include <QtCore/QSharedData>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QCanMessageDescription
+ \inmodule QtSerialBus
+ \since 6.5
+
+ \brief The QCanMessageDescription class describes the rules to process a CAN
+ message and represent it in an application-defined format.
+
+ A CAN message is basically a \l QCanBusFrame. The description of a CAN
+ message includes the following:
+ \list
+ \li Message ID.
+ \li Message name.
+ \li Message length in bytes.
+ \li Source of the message (transmitter).
+ \li Description of signals in the message.
+ \endlist
+
+ The QCanMessageDescription class provides methods to control all those
+ parameters.
+
+ \section2 Message ID
+ The message ID is a unique identifier, which is used to select the proper
+ message description when decoding the incoming \l QCanBusFrame or encoding
+ a \l QCanBusFrame based on the provided data.
+
+ See \l QCanUniqueIdDescription documentation for more details on the unique
+ identifier description.
+
+ \section2 Signal Description
+ The signal description is represented by the \l QCanSignalDescription
+ class. The QCanMessageDescription class only provides a list of signals that
+ belong to the message.
+
+ \sa QCanSignalDescription, QCanUniqueIdDescription
+*/
+
+/*!
+ Creates an empty message description.
+*/
+QCanMessageDescription::QCanMessageDescription() : d(new QCanMessageDescriptionPrivate)
+{
+}
+
+/*!
+ Creates a message description with the values copied from \a other.
+*/
+QCanMessageDescription::QCanMessageDescription(const QCanMessageDescription &other) : d(other.d)
+{
+}
+
+/*!
+ Creates a message description by moving from \a other.
+
+ \note The moved-from QCanMessageDescription object can only be destroyed or
+ assigned to. The effect of calling other functions than the destructor or
+ one of the assignment operators is undefined.
+*/
+QCanMessageDescription::QCanMessageDescription(QCanMessageDescription &&other) noexcept = default;
+
+/*!
+ Destroys this message description.
+*/
+QCanMessageDescription::~QCanMessageDescription() = default;
+
+QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QCanMessageDescriptionPrivate)
+
+/*!
+ Assigns the values from \a other to this message description.
+*/
+QCanMessageDescription &QCanMessageDescription::operator=(const QCanMessageDescription &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*!
+ \fn QCanMessageDescription &QCanMessageDescription::operator=(QCanMessageDescription &&other) noexcept
+
+ Move-assigns the values from \a other to this message description.
+
+ \note The moved-from QCanMessageDescription object can only be destroyed or
+ assigned to. The effect of calling other functions than the destructor or
+ one of the assignment operators is undefined.
+*/
+
+/*!
+ \fn bool QCanMessageDescription::operator==(const QCanMessageDescription &lhs, const QCanMessageDescription &rhs)
+
+ Returns \c true if all of the \a lhs object's values are the same as those
+ of \a rhs. Otherwise returns \c false.
+*/
+
+/*!
+ \fn bool QCanMessageDescription::operator!=(const QCanMessageDescription &lhs, const QCanMessageDescription &rhs)
+
+ Returns \c true if any of the \a lhs object's values are not the same as
+ those of \a rhs. Otherwise returns \c false.
+*/
+
+/*!
+ Returns \c true when the message description is valid and \c false
+ otherwise.
+
+ A valid message description \e must have at least one signal description.
+ All signal descriptions \e must be valid as well.
+
+ \sa signalDescriptions(), QCanSignalDescription::isValid()
+*/
+bool QCanMessageDescription::isValid() const
+{
+ if (d->messageSignals.isEmpty())
+ return false;
+
+ for (const auto &sigDesc : d->messageSignals) {
+ if (!sigDesc.isValid())
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ Returns the unique identifier of the CAN message.
+
+ See the \l {Message ID} section for more information about the unique
+ identifier.
+
+ \sa setUniqueId()
+*/
+QtCanBus::UniqueId QCanMessageDescription::uniqueId() const
+{
+ return d->id;
+}
+
+/*!
+ Sets the unique identifier of the CAN message to \a id.
+
+ See the \l {Message ID} section for more information about the unique
+ identifier.
+
+ \sa uniqueId()
+*/
+void QCanMessageDescription::setUniqueId(QtCanBus::UniqueId id)
+{
+ d.detach();
+ d->id = id;
+}
+
+/*!
+ Returns the name of the CAN message.
+
+//! [qcanmessagedesc-aux-parameter]
+ This parameter is introduced only for extra description. It's not used
+ during message encoding or decoding.
+//! [qcanmessagedesc-aux-parameter]
+
+ \sa setName()
+*/
+QString QCanMessageDescription::name() const
+{
+ return d->name;
+}
+
+/*!
+ Sets the name of the CAN message to \a name.
+
+ \include qcanmessagedescription.cpp qcanmessagedesc-aux-parameter
+
+ \sa name()
+*/
+void QCanMessageDescription::setName(const QString &name)
+{
+ d.detach();
+ d->name = name;
+}
+
+/*!
+ Returns the size in bytes of the CAN message.
+
+ \sa setSize()
+*/
+quint8 QCanMessageDescription::size() const
+{
+ return d->size;
+}
+
+/*!
+ Sets the size in bytes of the CAN message to \a size.
+
+ \sa size()
+*/
+void QCanMessageDescription::setSize(quint8 size)
+{
+ d.detach();
+ d->size = size;
+}
+
+/*!
+ Returns the transmitter node of the message.
+
+ \include qcanmessagedescription.cpp qcanmessagedesc-aux-parameter
+
+ \sa setTransmitter()
+*/
+QString QCanMessageDescription::transmitter() const
+{
+ return d->transmitter;
+}
+
+/*!
+ Sets the transmitter node of the message to \a transmitter.
+
+ \include qcanmessagedescription.cpp qcanmessagedesc-aux-parameter
+
+ \sa transmitter()
+*/
+void QCanMessageDescription::setTransmitter(const QString &transmitter)
+{
+ d.detach();
+ d->transmitter = transmitter;
+}
+
+/*!
+ Returns the comment for the message.
+
+ \include qcanmessagedescription.cpp qcanmessagedesc-aux-parameter
+
+ \sa setComment()
+*/
+QString QCanMessageDescription::comment() const
+{
+ return d->comment;
+}
+
+/*!
+ Sets the comment for the message to \a text.
+
+ \include qcanmessagedescription.cpp qcanmessagedesc-aux-parameter
+
+ \sa comment()
+*/
+void QCanMessageDescription::setComment(const QString &text)
+{
+ d.detach();
+ d->comment = text;
+}
+
+/*!
+ Returns the list of signal descriptions that belong to this message
+ description.
+
+ \sa signalDescriptionForName(), addSignalDescription(),
+ setSignalDescriptions(), clearSignalDescriptions()
+*/
+QList<QCanSignalDescription> QCanMessageDescription::signalDescriptions() const
+{
+ return QList<QCanSignalDescription>(d->messageSignals.cbegin(), d->messageSignals.cend());
+}
+
+/*!
+ Returns the signal description of a signal with the name \a name.
+
+ If the message description does not have such signal description, a
+ default-constructed \l QCanSignalDescription object is returned.
+
+ \sa signalDescriptions(), addSignalDescription(), setSignalDescriptions(),
+ clearSignalDescriptions()
+*/
+QCanSignalDescription QCanMessageDescription::signalDescriptionForName(const QString &name) const
+{
+ return d->messageSignals.value(name);
+}
+
+/*!
+ Clears all the signal descriptions of this message.
+
+ \sa signalDescriptions(), signalDescriptionForName(),
+ addSignalDescription(), setSignalDescriptions()
+*/
+void QCanMessageDescription::clearSignalDescriptions()
+{
+ d.detach();
+ d->messageSignals.clear();
+}
+
+/*!
+ Adds a new signal description \a description to this message description.
+
+ If the message description already has a signal description for a signal
+ with the same name, it is overwritten.
+
+ \sa signalDescriptions(), signalDescriptionForName(),
+ setSignalDescriptions(), clearSignalDescriptions()
+*/
+void QCanMessageDescription::addSignalDescription(const QCanSignalDescription &description)
+{
+ d.detach();
+ d->messageSignals.insert(description.name(), description);
+}
+
+/*!
+ Sets the descriptions of the signals belonging to this message description
+ to \a descriptions.
+
+ \note Message description \e must have signal descriptions with unique
+ signal names, so if the \a descriptions list contains entries with
+ duplicated names, only the last entry will be added.
+
+ \sa signalDescriptions(), signalDescriptionForName(),
+ addSignalDescription(), clearSignalDescriptions()
+*/
+void QCanMessageDescription::setSignalDescriptions(const QList<QCanSignalDescription> &descriptions)
+{
+ d.detach();
+ d->messageSignals.clear();
+ d->messageSignals.reserve(descriptions.size());
+ for (const auto &desc : descriptions)
+ d->messageSignals.insert(desc.name(), desc);
+}
+
+bool QCanMessageDescription::equals(const QCanMessageDescription &lhs, const QCanMessageDescription &rhs)
+{
+ return lhs.d->name == rhs.d->name
+ && lhs.d->transmitter == rhs.d->transmitter
+ && lhs.d->comment == rhs.d->comment
+ && lhs.d->id == rhs.d->id
+ && lhs.d->size == rhs.d->size
+ && lhs.d->messageSignals == rhs.d->messageSignals;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug QCanMessageDescription::debugStreaming(QDebug dbg, const QCanMessageDescription &msg)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QCanMessageDescription(" << msg.name() << ", ID = " << msg.uniqueId()
+ << ", Size = " << msg.size();
+ if (!msg.transmitter().isEmpty())
+ dbg << ", Transmitter = " << msg.transmitter();
+ if (!msg.comment().isEmpty())
+ dbg << ", Comment = " << msg.comment();
+ const auto msgSignals = msg.signalDescriptions();
+ if (!msgSignals.isEmpty()) {
+ dbg << ", Signals: {";
+ bool first = true;
+ for (const auto &sig : msgSignals) {
+ if (!first)
+ dbg << ", ";
+ dbg << sig;
+ first = false;
+ }
+ dbg << "}";
+ }
+ dbg << ")";
+ return dbg;
+}
+#endif // QT_NO_DEBUG_STREAM
+
+QCanMessageDescriptionPrivate *QCanMessageDescriptionPrivate::get(const QCanMessageDescription &desc)
+{
+ return desc.d.data();
+}
+
+QT_END_NAMESPACE
diff --git a/src/serialbus/qcanmessagedescription.h b/src/serialbus/qcanmessagedescription.h
new file mode 100644
index 0000000..68e9c04
--- /dev/null
+++ b/src/serialbus/qcanmessagedescription.h
@@ -0,0 +1,84 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCANMESSAGEDESCRIPTION_H
+#define QCANMESSAGEDESCRIPTION_H
+
+#include <QtCore/QDebug>
+#include <QtCore/QExplicitlySharedDataPointer>
+
+#include <QtSerialBus/qcancommondefinitions.h>
+#include <QtSerialBus/qtserialbusglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QCanSignalDescription;
+
+class QCanMessageDescriptionPrivate;
+QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QCanMessageDescriptionPrivate, Q_SERIALBUS_EXPORT)
+
+class Q_SERIALBUS_EXPORT QCanMessageDescription
+{
+public:
+ QCanMessageDescription();
+ QCanMessageDescription(const QCanMessageDescription &other);
+ QCanMessageDescription(QCanMessageDescription &&other) noexcept;
+ ~QCanMessageDescription();
+
+ QCanMessageDescription &operator=(const QCanMessageDescription &other);
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QCanMessageDescription)
+
+ friend bool operator==(const QCanMessageDescription &lhs, const QCanMessageDescription &rhs)
+ {
+ return equals(lhs, rhs);
+ }
+ friend bool operator!=(const QCanMessageDescription &lhs, const QCanMessageDescription &rhs)
+ {
+ return !equals(lhs, rhs);
+ }
+
+ void swap(QCanMessageDescription &other) noexcept { d.swap(other.d); }
+
+ bool isValid() const;
+
+ QtCanBus::UniqueId uniqueId() const;
+ void setUniqueId(QtCanBus::UniqueId id);
+
+ QString name() const;
+ void setName(const QString &name);
+
+ quint8 size() const;
+ void setSize(quint8 size);
+
+ QString transmitter() const;
+ void setTransmitter(const QString &transmitter);
+
+ QString comment() const;
+ void setComment(const QString &text);
+
+ QList<QCanSignalDescription> signalDescriptions() const;
+ QCanSignalDescription signalDescriptionForName(const QString &name) const;
+ void clearSignalDescriptions();
+ void addSignalDescription(const QCanSignalDescription &description);
+ void setSignalDescriptions(const QList<QCanSignalDescription> &descriptions);
+
+private:
+ QExplicitlySharedDataPointer<QCanMessageDescriptionPrivate> d;
+ friend class QCanMessageDescriptionPrivate;
+
+ static bool equals(const QCanMessageDescription &lhs, const QCanMessageDescription &rhs);
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend QDebug operator<<(QDebug dbg, const QCanMessageDescription &msg)
+ {
+ return debugStreaming(dbg, msg);
+ }
+ static QDebug debugStreaming(QDebug dbg, const QCanMessageDescription &msg);
+#endif // QT_NO_DEBUG_STREAM
+};
+
+Q_DECLARE_SHARED(QCanMessageDescription)
+
+QT_END_NAMESPACE
+
+#endif // QCANMESSAGEDESCRIPTION_H
diff --git a/src/serialbus/qcanmessagedescription_p.h b/src/serialbus/qcanmessagedescription_p.h
new file mode 100644
index 0000000..52daf1d
--- /dev/null
+++ b/src/serialbus/qcanmessagedescription_p.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCANMESSAGEDESCRIPTION_P_H
+#define QCANMESSAGEDESCRIPTION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "private/qtserialbusexports_p.h"
+#include "qcanmessagedescription.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_SERIALBUS_PRIVATE_EXPORT QCanMessageDescriptionPrivate : public QSharedData
+{
+public:
+ QString name;
+ QString transmitter;
+ QString comment;
+ QtCanBus::UniqueId id = 0;
+ quint8 size = 0; // even CAN FD has max 64 bytes
+ QHash<QString, QCanSignalDescription> messageSignals;
+
+ inline bool isShared() const { return ref.loadRelaxed() != 1; }
+ static QCanMessageDescriptionPrivate *get(const QCanMessageDescription &desc);
+};
+
+QT_END_NAMESPACE
+
+#endif // QCANMESSAGEDESCRIPTION_P_H
diff --git a/src/serialbus/qcansignaldescription.cpp b/src/serialbus/qcansignaldescription.cpp
new file mode 100644
index 0000000..ac40346
--- /dev/null
+++ b/src/serialbus/qcansignaldescription.cpp
@@ -0,0 +1,798 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qcansignaldescription.h"
+#include "qcansignaldescription_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QCanSignalDescription
+ \inmodule QtSerialBus
+ \since 6.5
+
+ \brief The QCanSignalDescription class describes the rules to extract one
+ value out of the CAN frame and represent it in an application-defined
+ format.
+
+ The QCanSignalDescription class can be used to provide a signal description
+ and later use it to decode a received \l QCanBusFrame or encode the input
+ data into a \l QCanBusFrame that can be sent to the receiver.
+
+ \section2 General Description
+
+ Each CAN frame can contain multiple values. The rules to extract the values
+ from a CAN frame include the following:
+ \list
+ \li Data source (frame ID or payload).
+ \li Data endianness.
+ \li Data format.
+ \li Start bit position.
+ \li Data length in bits.
+ \li Multiplexing options.
+ \endlist
+
+ Start bit position is specified relative to the selected data source. The
+ bits are counted starting from the LSB.
+
+ Once the data is extracted, it might require conversion to an
+ application-defined format. The following parameters can be used for that:
+ \list
+ \li Various parameters for converting the extracted value to a physical
+ value (factor, offset, scale).
+ \li Expected data range.
+ \li Data units.
+ \endlist
+
+ The QCanSignalDescription class provides methods to control all those
+ parameters.
+
+ \section2 Multiplexed Signals Explained
+
+ There are two common ways to encode the data in the CAN payload:
+ \list
+ \li Each range of bits always represents the same signal. For example,
+ \c {Bytes 0-1} in a payload can represent an engine speed (in rpm),
+ and \c {Bytes 2-3} can represent the vehicle speed (in km/h).
+ \li The same range of bits can represent different data, depending on
+ the values of some other bits in the payload. For example, if
+ \c {Byte 0} has the value \c {0}, the \c {Bytes 1-2} represent an
+ engine speed (in rpm), and if \c {Byte 0} has the value \c {1}, the
+ same \c {Bytes 1-2} represent a vehicle speed (in km/h).
+ \endlist
+
+ The second case uses signal multiplexing. In the provided example we will
+ have three signals. The first signal represents the value of \c {Byte 0} and
+ acts like a multiplexor signal. The other two signals represent an engine
+ speed and a vehicle speed respectively, but only one of them can be
+ extracted from the CAN payload at a time. Which signal should be extracted
+ is defined by the value of the multiplexor signal.
+
+ In more complicated cases the payload can have multiple multiplexor signals.
+ In such cases the signal can be extracted from the payload only when all
+ multiplexors contain the expected values.
+
+ \section2 Value Conversions
+
+ In many cases the signals transferred over CAN bus cannot hold the full
+ range of the physical values that they represent. To overcome these
+ limitations, the physical values are converted to a smaller range before
+ transmission, and can be restored on the receiving end.
+
+ The following formulas are used to convert between the physical value and
+ the signal's value:
+
+ \badcode
+ physicalValue = scaling * (signalValue * factor + offset);
+ signalValue = (physicalValue / scaling - offset) / factor;
+ \endcode
+
+ The factor and scaling parameters cannot be equal to \c {0}.
+
+ If any of the parameters equals to \l qQNaN(), it is not used during the
+ conversion. If all of the parameters are equal to \l qQNaN() (which is the
+ default), the conversion is not performed.
+*/
+
+/*!
+ \typealias QCanSignalDescription::MultiplexValues
+*/
+
+/*!
+ \typealias QCanSignalDescription::MultiplexSignalValues
+*/
+
+/*!
+ Creates an empty signal description.
+*/
+QCanSignalDescription::QCanSignalDescription() : d(new QCanSignalDescriptionPrivate)
+{
+}
+
+/*!
+ Creates a signal description with the values copied from \a other.
+*/
+QCanSignalDescription::QCanSignalDescription(const QCanSignalDescription &other) : d(other.d)
+{
+}
+
+/*!
+ Creates a signal description by moving from \a other.
+
+ \note The moved-from QCanSignalDescription object can only be destroyed or
+ assigned to. The effect of calling other functions than the destructor or
+ one of the assignment operators is undefined.
+*/
+QCanSignalDescription::QCanSignalDescription(QCanSignalDescription &&other) noexcept = default;
+
+/*!
+ Destroys this signal description.
+*/
+QCanSignalDescription::~QCanSignalDescription() = default;
+
+QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QCanSignalDescriptionPrivate)
+
+/*!
+ Assigns the values from \a other to this signal description.
+*/
+QCanSignalDescription &QCanSignalDescription::operator=(const QCanSignalDescription &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*!
+ \fn QCanSignalDescription &QCanSignalDescription::operator=(QCanSignalDescription &&other) noexcept
+
+ Move-assigns the values from \a other to this signal description.
+
+ \note The moved-from QCanSignalDescription object can only be destroyed or
+ assigned to. The effect of calling other functions than the destructor or
+ one of the assignment operators is undefined.
+*/
+
+/*!
+ \fn bool QCanSignalDescription::operator==(const QCanSignalDescription &lhs, const QCanSignalDescription &rhs)
+
+ Returns \c true if all of the \a lhs object's values are the same as those
+ of \a rhs. Otherwise returns \c false.
+*/
+
+/*!
+ \fn bool QCanSignalDescription::operator!=(const QCanSignalDescription &lhs, const QCanSignalDescription &rhs)
+
+ Returns \c true if any of the \a lhs object's values are not the same as
+ those of \a rhs. Otherwise returns \c false.
+*/
+
+/*!
+ Returns \c true when the signal description is valid and \c false otherwise.
+
+ A valid signal description \e must fulfill the following conditions:
+ \list
+ \li have a non-empty \l name()
+ \li have \l bitLength() \c {== 32} if the \l dataFormat() is
+ \l {QtCanBus::DataFormat::}{Float}
+ \li have \l bitLength() \c {== 64} if the \l dataFormat() is
+ \l {QtCanBus::DataFormat::}{Double}
+ \li the \l bitLength() \e must be a multiple of \c 8 if the
+ \l dataFormat() is \l {QtCanBus::DataFormat::}{Ascii}
+ \li the \l bitLength() \e must be greater than \c 0 and less than or
+ equal to \c {64}.
+ \endlist
+
+ \sa bitLength(), dataFormat(), name()
+*/
+bool QCanSignalDescription::isValid() const
+{
+ const bool formatMatch = [this]() {
+ if (d->format == QtCanBus::DataFormat::Float)
+ return d->dataLength == 32;
+ if (d->format == QtCanBus::DataFormat::Double)
+ return d->dataLength == 64;
+ if (d->format == QtCanBus::DataFormat::Ascii)
+ return d->dataLength % 8 == 0;
+ return d->dataLength > 0 && d->dataLength <= 64;
+ }();
+ return !d->name.isEmpty() && formatMatch;
+}
+
+/*!
+ Returns the name of the signal.
+
+ \sa setName(), isValid()
+*/
+QString QCanSignalDescription::name() const
+{
+ return d->name;
+}
+
+/*!
+ Sets the name of the signal to \a name.
+
+ The signal's name must be unique within a CAN message.
+
+ \sa name()
+*/
+void QCanSignalDescription::setName(const QString &name)
+{
+ d.detach();
+ d->name = name;
+}
+
+/*!
+ Returns the physical unit (e.g. km/h) of the signal's value or an empty
+ string if the unit is not set.
+
+//! [qcansignaldesc-aux-parameter]
+ This parameter is introduced only for extra description. It's not used
+ during signal processing.
+//! [qcansignaldesc-aux-parameter]
+
+ \sa setPhysicalUnit()
+*/
+QString QCanSignalDescription::physicalUnit() const
+{
+ return d->unit;
+}
+
+/*!
+ Sets the physical \a unit (e.g. km/h) of the signal's value.
+
+ \include qcansignaldescription.cpp qcansignaldesc-aux-parameter
+
+ \sa physicalUnit()
+*/
+void QCanSignalDescription::setPhysicalUnit(const QString &unit)
+{
+ d.detach();
+ d->unit = unit;
+}
+
+/*!
+ Returns the receiver node for this signal.
+
+ \include qcansignaldescription.cpp qcansignaldesc-aux-parameter
+
+ \sa setReceiver()
+*/
+QString QCanSignalDescription::receiver() const
+{
+ return d->receiver;
+}
+
+/*!
+ Sets the \a receiver node for this signal.
+
+ \include qcansignaldescription.cpp qcansignaldesc-aux-parameter
+
+ \sa receiver()
+*/
+void QCanSignalDescription::setReceiver(const QString &receiver)
+{
+ d.detach();
+ d->receiver = receiver;
+}
+
+/*!
+ Returns the comment for the signal.
+
+ \include qcansignaldescription.cpp qcansignaldesc-aux-parameter
+
+ \sa setComment()
+*/
+QString QCanSignalDescription::comment() const
+{
+ return d->comment;
+}
+
+/*!
+ Sets the comment for the signal to \a text.
+
+ \include qcansignaldescription.cpp qcansignaldesc-aux-parameter
+
+ \sa comment()
+*/
+void QCanSignalDescription::setComment(const QString &text)
+{
+ d.detach();
+ d->comment = text;
+}
+
+/*!
+ Returns the data source of the signal's value.
+
+ By default, \l {QtCanBus::DataSource::}{Payload} is used.
+
+ \sa setDataSource(), QtCanBus::DataSource
+*/
+QtCanBus::DataSource QCanSignalDescription::dataSource() const
+{
+ return d->source;
+}
+
+/*!
+ Sets the data source of the signal's value to \a source.
+
+ \sa dataSource(), QtCanBus::DataSource
+*/
+void QCanSignalDescription::setDataSource(QtCanBus::DataSource source)
+{
+ d.detach();
+ d->source = source;
+}
+
+/*!
+ Returns the data endian of the signal's value.
+
+ By default, \l {QtCanBus::DataEndian::}{BigEndian} is used.
+
+ \note The data endian is ignored if the \l dataFormat() is set to
+ \l {QtCanBus::DataFormat::}{Ascii}.
+
+ \sa setDataEndian(), QtCanBus::DataEndian
+*/
+QtCanBus::DataEndian QCanSignalDescription::dataEndian() const
+{
+ return d->endian;
+}
+
+/*!
+ Sets the data endian of the signal's value to \a endian.
+
+ \sa dataEndian(), QtCanBus::DataEndian
+*/
+void QCanSignalDescription::setDataEndian(QtCanBus::DataEndian endian)
+{
+ d.detach();
+ d->endian = endian;
+}
+
+/*!
+ Returns the data format of the signal's value.
+
+ By default, \l {QtCanBus::DataFormat::}{SignedInteger} is used.
+
+ \sa setDataFormat(), QtCanBus::DataFormat
+*/
+QtCanBus::DataFormat QCanSignalDescription::dataFormat() const
+{
+ return d->format;
+}
+
+/*!
+ Sets the data format of the signal's value to \a format.
+
+ \sa dataFormat(), QtCanBus::DataFormat
+*/
+void QCanSignalDescription::setDataFormat(QtCanBus::DataFormat format)
+{
+ d.detach();
+ d->format = format;
+}
+
+/*!
+ Returns the start bit of the signal's value in the \l dataSource().
+
+ \sa setStartBit(), bitLength(), setBitLength()
+*/
+quint16 QCanSignalDescription::startBit() const
+{
+ return d->startBit;
+}
+
+/*!
+ Sets the start bit of the signal's value in the \l dataSource() to \a bit.
+
+ \sa startBit(), bitLength(), setBitLength()
+*/
+void QCanSignalDescription::setStartBit(quint16 bit)
+{
+ d.detach();
+ d->startBit = bit;
+}
+
+/*!
+ Returns the bit length of the signal's value.
+
+ \sa setBitLength(), startBit(), setStartBit()
+*/
+quint16 QCanSignalDescription::bitLength() const
+{
+ return d->dataLength;
+}
+
+/*!
+ Sets the bit length of the signal's value to \a length.
+
+ \sa bitLength(), startBit(), setStartBit()
+*/
+void QCanSignalDescription::setBitLength(quint16 length)
+{
+ d.detach();
+ d->dataLength = length;
+}
+
+/*!
+ Returns the factor that is used to convert the signal's value to a physical
+ value and back.
+
+ By default the function returns \l qQNaN(), which means that a factor is not
+ used.
+
+ The \l {Value Conversions} section explains how this parameter is used.
+
+ \sa setFactor(), offset(), scaling()
+*/
+double QCanSignalDescription::factor() const
+{
+ return d->factor;
+}
+
+/*!
+ Sets the factor that is used to convert the signal's value to a physical
+ value and back to \a factor.
+
+ Pass \l qQNaN() to this method to skip this parameter during the conversion.
+
+ The factor cannot be 0. An attempt to set a zero factor is equivalent to
+ setting it to \l qQNaN().
+
+ The \l {Value Conversions} section explains how this parameter is used.
+
+ \sa factor(), setOffset(), setScaling()
+*/
+void QCanSignalDescription::setFactor(double factor)
+{
+ d.detach();
+ if (qFuzzyIsNull(factor))
+ d->factor = qQNaN();
+ else
+ d->factor = factor;
+}
+
+/*!
+ Returns the offset that is used to convert the signal's value to a physical
+ value and back.
+
+ By default the function returns \l qQNaN(), which means that an offset is
+ not used.
+
+ The \l {Value Conversions} section explains how this parameter is used.
+
+ \sa setOffset(), factor(), scaling()
+*/
+double QCanSignalDescription::offset() const
+{
+ return d->offset;
+}
+
+/*!
+ Sets the offset that is used to convert the signal's value to a physical
+ value and back to \a offset.
+
+ Pass \l qQNaN() to this method to skip this parameter during the conversion.
+
+ The \l {Value Conversions} section explains how this parameter is used.
+
+ \sa offset(), setFactor(), setScaling()
+*/
+void QCanSignalDescription::setOffset(double offset)
+{
+ d.detach();
+ d->offset = offset;
+}
+
+/*!
+ Returns the scaling that is used to convert the signal's value to a physical
+ value and back.
+
+ By default the function returns \l qQNaN(), which means that scaling is not
+ used.
+
+ The \l {Value Conversions} section explains how this parameter is used.
+
+ \sa setScaling(), offset(), factor()
+*/
+double QCanSignalDescription::scaling() const
+{
+ return d->scaling;
+}
+
+/*!
+ Sets the scaling that is used to convert the signal's value to a physical
+ value and back to \a scaling.
+
+ Pass \l qQNaN() to this method to skip this parameter during the conversion.
+
+ The scaling cannot be 0. An attempt to set zero scaling is equivalent to
+ setting it to \l qQNaN().
+
+ The \l {Value Conversions} section explains how this parameter is used.
+
+ \sa scaling(), setOffset(), setFactor()
+*/
+void QCanSignalDescription::setScaling(double scaling)
+{
+ d.detach();
+ if (qFuzzyIsNull(scaling))
+ d->scaling = qQNaN();
+ else
+ d->scaling = scaling;
+}
+
+/*!
+ Returns the minimum supported value for the signal.
+
+ By default the function returns \l qQNaN(), which means that there is no
+ minimum value.
+
+ \sa setRange(), maximum()
+*/
+double QCanSignalDescription::minimum() const
+{
+ return d->minimum;
+}
+
+/*!
+ Returns the maximum supported value for the signal.
+
+ By default the function returns \l qQNaN(), which means that there is no
+ maximum value.
+
+ \sa setRange(), minimum()
+*/
+double QCanSignalDescription::maximum() const
+{
+ return d->maximum;
+}
+
+/*!
+ Sets the \a minimum and \a maximum for the signal's value.
+
+ Setting one or both of the parameters to \l qQNaN() means that the
+ corresponding limit will not be used.
+
+ \sa minimum(), maximum()
+*/
+void QCanSignalDescription::setRange(double minimum, double maximum)
+{
+ d.detach();
+ if (qIsNaN(minimum) || qIsNaN(maximum) || minimum <= maximum) {
+ d->minimum = minimum;
+ d->maximum = maximum;
+ } else {
+ qWarning("Minimum value is greater than maximum. The values will be swapped.");
+ d->minimum = maximum;
+ d->maximum = minimum;
+ }
+}
+
+/*!
+ Returns the multiplex state of the signal.
+
+ See the \l {Multiplexed Signals Explained} section for more details on
+ multiplexed signals.
+
+ By default this method returns \l {QtCanBus::MultiplexState::}{None}.
+
+ \sa setMultiplexState(), QtCanBus::MultiplexState
+*/
+QtCanBus::MultiplexState QCanSignalDescription::multiplexState() const
+{
+ return d->muxState;
+}
+
+/*!
+ Sets the multiplex state of the signal to \a state.
+
+ See the \l {Multiplexed Signals Explained} section for more details on
+ multiplexed signals.
+
+ \sa multiplexState(), QtCanBus::MultiplexState
+*/
+void QCanSignalDescription::setMultiplexState(QtCanBus::MultiplexState state)
+{
+ d.detach();
+ d->muxState = state;
+}
+
+/*!
+ Returns the \l {Multiplexed Signals Explained}{multiplexor signals} and
+ their desired values that are used to properly identify this signal.
+
+ The returned hash contains signal names as keys and respective desired
+ ranges of values as values.
+
+ This signal's value can be extracted from the payload only when all the
+ signals from the hash have the expected values.
+
+ \sa multiplexState(), clearMultiplexSignals(), setMultiplexSignals(),
+ addMultiplexSignal()
+*/
+QCanSignalDescription::MultiplexSignalValues QCanSignalDescription::multiplexSignals() const
+{
+ return d->muxSignals;
+}
+
+/*!
+ Removes all \l {Multiplexed Signals Explained}{multiplexor signals} for
+ this signal.
+
+ \sa multiplexSignals(), setMultiplexSignals(), addMultiplexSignal()
+*/
+void QCanSignalDescription::clearMultiplexSignals()
+{
+ d.detach();
+ d->muxSignals.clear();
+}
+
+/*!
+ Sets the \l {Multiplexed Signals Explained}{multiplexor signals} for this
+ signal to \a multiplexorSignals.
+
+ The \a multiplexorSignals hash \e must contain signal names as keys and
+ respective desired value ranges as values.
+
+ \sa multiplexState(), multiplexSignals(), clearMultiplexSignals(),
+ addMultiplexSignal()
+*/
+void QCanSignalDescription::setMultiplexSignals(const MultiplexSignalValues &multiplexorSignals)
+{
+ d.detach();
+ d->muxSignals = multiplexorSignals;
+}
+
+/*!
+ Adds a new \l {Multiplexed Signals Explained}{multiplexor signal} for this
+ signal. The \a name parameter contains the name of the multiplexor signal,
+ and the \a ranges parameter contains the desired value ranges.
+
+ If this signal already has desired value ranges for the multiplexor signal
+ \a name, the ranges are overwritten.
+
+ \sa multiplexState(), multiplexSignals(), clearMultiplexSignals(),
+ setMultiplexSignals()
+*/
+void QCanSignalDescription::addMultiplexSignal(const QString &name, const MultiplexValues &ranges)
+{
+ d.detach();
+ d->muxSignals.insert(name, ranges);
+}
+
+/*!
+ \overload
+
+ This is a convenience overload for the case when the multiplexor signal is
+ expected to have only one specific value, not a range of values.
+
+ The \a name parameter contains the name of the multiplexor signal,
+ and the \a value parameter contains the desired value.
+
+ If this signal already has desired value ranges for the multiplexor signal
+ \a name, the ranges are overwritten.
+
+ \sa multiplexState(), multiplexSignals(), clearMultiplexSignals(),
+ setMultiplexSignals()
+*/
+void QCanSignalDescription::addMultiplexSignal(const QString &name, const QVariant &value)
+{
+ d.detach();
+ d->muxSignals.insert(name, { qMakePair(value, value) });
+}
+
+// copied from qtbase/src/testlib/qtestcase.cpp
+template <typename T>
+static bool floatingCompare(const T &actual, const T &expected)
+{
+ switch (qFpClassify(expected))
+ {
+ case FP_INFINITE:
+ return (expected < 0) == (actual < 0) && qFpClassify(actual) == FP_INFINITE;
+ case FP_NAN:
+ return qFpClassify(actual) == FP_NAN;
+ default:
+ if (!qFuzzyIsNull(expected))
+ return qFuzzyCompare(actual, expected);
+ Q_FALLTHROUGH();
+ case FP_SUBNORMAL: // subnormal is always fuzzily null
+ case FP_ZERO:
+ return qFuzzyIsNull(actual);
+ }
+}
+
+bool QCanSignalDescription::equals(const QCanSignalDescription &lhs, const QCanSignalDescription &rhs)
+{
+ return lhs.d->name == rhs.d->name
+ && lhs.d->unit == rhs.d->unit
+ && lhs.d->receiver == rhs.d->receiver
+ && lhs.d->comment == rhs.d->comment
+ && lhs.d->source == rhs.d->source
+ && lhs.d->endian == rhs.d->endian
+ && lhs.d->format == rhs.d->format
+ && lhs.d->startBit == rhs.d->startBit
+ && lhs.d->dataLength == rhs.d->dataLength
+ && lhs.d->muxState == rhs.d->muxState
+ && lhs.d->muxSignals == rhs.d->muxSignals
+ && floatingCompare(lhs.d->factor, rhs.d->factor)
+ && floatingCompare(lhs.d->offset, rhs.d->offset)
+ && floatingCompare(lhs.d->scaling, rhs.d->scaling)
+ && floatingCompare(lhs.d->minimum, rhs.d->minimum)
+ && floatingCompare(lhs.d->maximum, rhs.d->maximum);
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug QCanSignalDescription::debugStreaming(QDebug dbg, const QCanSignalDescription &sig)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QCanSignalDescription(" << sig.name() << ", Source = " << sig.dataSource()
+ << ", Format = " << sig.dataFormat() << ", Endian = " << sig.dataEndian()
+ << ", StartBit = " << sig.startBit() << ", BitLength = " << sig.bitLength();
+ if (!sig.physicalUnit().isEmpty())
+ dbg << ", Units = " << sig.physicalUnit();
+ if (!sig.receiver().isEmpty())
+ dbg << ", Receiver = " << sig.receiver();
+ if (!sig.comment().isEmpty())
+ dbg << ", Comment = " << sig.comment();
+ dbg << ", Factor = " << sig.factor() << ", Offset = " << sig.offset()
+ << ", Scaling = " << sig.scaling();
+ dbg << ", Minimum = " << sig.minimum() << ", Maximum = " << sig.maximum();
+ dbg << ", Multiplex State = " << sig.multiplexState();
+ const auto muxSignals = sig.multiplexSignals();
+ if (!muxSignals.isEmpty()) {
+ dbg << ", Multiplexor Signals: {";
+ for (auto it = muxSignals.cbegin(); it != muxSignals.cend(); ++it) {
+ if (it != muxSignals.cbegin())
+ dbg << ", ";
+ dbg << "(" << it.key() << ", " << it.value() << ")";
+ }
+ dbg << "}";
+ }
+ dbg << ")";
+ return dbg;
+}
+#endif // QT_NO_DEBUG_STREAM
+
+template <typename T>
+static bool checkValue(const QVariant &valueVar,
+ const QCanSignalDescription::MultiplexValues &ranges)
+{
+ const T val = valueVar.value<T>();
+ for (const auto &pair : ranges) {
+ T min = pair.first.value<T>();
+ T max = pair.second.value<T>();
+ if (min > max)
+ max = std::exchange(min, max);
+ if (val >= min && val <= max)
+ return true;
+ }
+ return false;
+}
+
+bool QCanSignalDescriptionPrivate::muxValueInRange(
+ const QVariant &value, const QCanSignalDescription::MultiplexValues &ranges) const
+{
+ // Use the current data format to convert QVariant values.
+ // Do we really need it for Float, Double and Ascii?
+ switch (format) {
+ case QtCanBus::DataFormat::SignedInteger:
+ return checkValue<qint64>(value, ranges);
+ case QtCanBus::DataFormat::UnsignedInteger:
+ return checkValue<quint64>(value, ranges);
+ case QtCanBus::DataFormat::Float:
+ return checkValue<float>(value, ranges);
+ case QtCanBus::DataFormat::Double:
+ return checkValue<double>(value, ranges);
+ case QtCanBus::DataFormat::Ascii:
+ return checkValue<QByteArray>(value, ranges);
+ }
+
+ Q_UNREACHABLE_RETURN(false);
+}
+
+QCanSignalDescriptionPrivate *QCanSignalDescriptionPrivate::get(const QCanSignalDescription &desc)
+{
+ return desc.d.data();
+}
+
+QT_END_NAMESPACE
diff --git a/src/serialbus/qcansignaldescription.h b/src/serialbus/qcansignaldescription.h
new file mode 100644
index 0000000..a044bf6
--- /dev/null
+++ b/src/serialbus/qcansignaldescription.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCANSIGNALDESCRIPTION_H
+#define QCANSIGNALDESCRIPTION_H
+
+#include <QtCore/QDebug>
+#include <QtCore/QExplicitlySharedDataPointer>
+
+#include <QtSerialBus/qcancommondefinitions.h>
+#include <QtSerialBus/qtserialbusglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QCanSignalDescriptionPrivate;
+QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QCanSignalDescriptionPrivate, Q_SERIALBUS_EXPORT)
+
+class Q_SERIALBUS_EXPORT QCanSignalDescription
+{
+public:
+ using MultiplexValues = QList<QPair<QVariant, QVariant>>;
+ using MultiplexSignalValues = QHash<QString, MultiplexValues>;
+
+
+ QCanSignalDescription();
+ QCanSignalDescription(const QCanSignalDescription &other);
+ QCanSignalDescription(QCanSignalDescription &&other) noexcept;
+ ~QCanSignalDescription();
+
+ QCanSignalDescription &operator=(const QCanSignalDescription &other);
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QCanSignalDescription)
+
+ friend bool operator==(const QCanSignalDescription &lhs, const QCanSignalDescription &rhs)
+ {
+ return equals(lhs, rhs);
+ }
+ friend bool operator!=(const QCanSignalDescription &lhs, const QCanSignalDescription &rhs)
+ {
+ return !equals(lhs, rhs);
+ }
+
+ void swap(QCanSignalDescription &other) noexcept { d.swap(other.d); }
+
+ bool isValid() const;
+
+ QString name() const;
+ void setName(const QString &name);
+
+ QString physicalUnit() const;
+ void setPhysicalUnit(const QString &unit);
+
+ QString receiver() const;
+ void setReceiver(const QString &receiver);
+
+ QString comment() const;
+ void setComment(const QString &text);
+
+ QtCanBus::DataSource dataSource() const;
+ void setDataSource(QtCanBus::DataSource source);
+
+ QtCanBus::DataEndian dataEndian() const;
+ void setDataEndian(QtCanBus::DataEndian endian);
+
+ QtCanBus::DataFormat dataFormat() const;
+ void setDataFormat(QtCanBus::DataFormat format);
+
+ quint16 startBit() const;
+ void setStartBit(quint16 bit);
+
+ quint16 bitLength() const;
+ void setBitLength(quint16 length);
+
+ double factor() const;
+ void setFactor(double factor);
+
+ double offset() const;
+ void setOffset(double offset);
+
+ double scaling() const;
+ void setScaling(double scaling);
+
+ double minimum() const;
+ double maximum() const;
+ void setRange(double minimum, double maximum);
+
+ QtCanBus::MultiplexState multiplexState() const;
+ void setMultiplexState(QtCanBus::MultiplexState state);
+
+ MultiplexSignalValues multiplexSignals() const;
+ void clearMultiplexSignals();
+ void setMultiplexSignals(const MultiplexSignalValues &multiplexorSignals);
+ void addMultiplexSignal(const QString &name, const MultiplexValues &ranges);
+ void addMultiplexSignal(const QString &name, const QVariant &value);
+
+private:
+ QExplicitlySharedDataPointer<QCanSignalDescriptionPrivate> d;
+ friend class QCanSignalDescriptionPrivate;
+
+ static bool equals(const QCanSignalDescription &lhs, const QCanSignalDescription &rhs);
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend QDebug operator<<(QDebug dbg, const QCanSignalDescription &sig)
+ {
+ return debugStreaming(dbg, sig);
+ }
+ static QDebug debugStreaming(QDebug dbg, const QCanSignalDescription &sig);
+#endif // QT_NO_DEBUG_STREAM
+};
+
+Q_DECLARE_SHARED(QCanSignalDescription)
+
+QT_END_NAMESPACE
+
+#endif // QCANSIGNALDESCRIPTION_H
diff --git a/src/serialbus/qcansignaldescription_p.h b/src/serialbus/qcansignaldescription_p.h
new file mode 100644
index 0000000..3321943
--- /dev/null
+++ b/src/serialbus/qcansignaldescription_p.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCANSIGNALDESCRIPTION_P_H
+#define QCANSIGNALDESCRIPTION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "private/qtserialbusexports_p.h"
+#include "qcansignaldescription.h"
+
+#include <QtCore/QHash>
+#include <QtCore/QSharedData>
+#include <QtCore/QVariant>
+
+QT_BEGIN_NAMESPACE
+
+class Q_SERIALBUS_PRIVATE_EXPORT QCanSignalDescriptionPrivate : public QSharedData
+{
+public:
+ QString name;
+ QString unit;
+ QString receiver;
+ QString comment;
+ QtCanBus::DataSource source = QtCanBus::DataSource::Payload;
+ QtCanBus::DataEndian endian = QtCanBus::DataEndian::BigEndian;
+ QtCanBus::DataFormat format = QtCanBus::DataFormat::SignedInteger;
+ quint16 startBit = 0;
+ quint16 dataLength = 0;
+ // for conversion, possibly unused
+ double factor = qQNaN();
+ double offset = qQNaN();
+ double scaling = qQNaN();
+ // expected range, possibly unused
+ double minimum = qQNaN();
+ double maximum = qQNaN();
+ // multiplexing state
+ QtCanBus::MultiplexState muxState = QtCanBus::MultiplexState::None;
+ // Multiplexed values. The key of the hash represents the multiplex switch
+ // name, and the value represents the valid range(s) of the mux switch
+ // values.
+ QCanSignalDescription::MultiplexSignalValues muxSignals;
+
+ bool muxValueInRange(const QVariant &value,
+ const QCanSignalDescription::MultiplexValues &ranges) const;
+
+ inline bool isShared() const { return ref.loadRelaxed() != 1; }
+ static QCanSignalDescriptionPrivate *get(const QCanSignalDescription &desc);
+};
+
+QT_END_NAMESPACE
+
+#endif // QCANSIGNALDESCRIPTION_P_H
diff --git a/src/serialbus/qcanuniqueiddescription.cpp b/src/serialbus/qcanuniqueiddescription.cpp
new file mode 100644
index 0000000..fd5d98e
--- /dev/null
+++ b/src/serialbus/qcanuniqueiddescription.cpp
@@ -0,0 +1,224 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qcanuniqueiddescription.h"
+#include "qcanuniqueiddescription_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QCanUniqueIdDescription
+ \inmodule QtSerialBus
+ \since 6.5
+
+ \brief The QCanUniqueIdDescription class describes the rules for accessing
+ a unique identifier in a \l QCanBusFrame.
+
+ A unique identifier is used to distinguish different CAN bus frames and
+ apply proper \l {QCanMessageDescription}s to encode or decode them.
+ Different CAN protocols can use different parts of the CAN frame as a unique
+ identifier (e.g. the DBC protocol uses the whole FrameId as a unique
+ identifier).
+
+ This class contains parameters to specify the unique identifier position
+ within a CAN frame in a flexible way:
+
+ \list
+ \li The part of the frame which will be used to extract the unique
+ identifier (FrameId or payload).
+ \li The start bit of the unique identifier, relative to the selected
+ part of the frame. The bits are counted starting from the LSB.
+ \li The number of bits used to represent the unique identifier.
+ \li The endian used to extract the value.
+ \endlist
+
+ The actual value of a unique identifier is represented by the
+ \l QtCanBus::UniqueId type.
+
+ \sa QCanMessageDescription
+*/
+
+/*!
+ Creates an empty unique identifier description.
+*/
+QCanUniqueIdDescription::QCanUniqueIdDescription()
+ : d(new QCanUniqueIdDescriptionPrivate)
+{
+}
+
+/*!
+ Creates a unique identifier description with the values copied from
+ \a other.
+*/
+QCanUniqueIdDescription::QCanUniqueIdDescription(const QCanUniqueIdDescription &other)
+ : d(other.d)
+{
+}
+
+/*!
+ Creates a unique identifier description by moving from \a other.
+
+ \note The moved-from QCanUniqueIdDescription object can only be destroyed or
+ assigned to. The effect of calling other functions than the destructor or
+ one of the assignment operators is undefined.
+*/
+QCanUniqueIdDescription::QCanUniqueIdDescription(QCanUniqueIdDescription &&other) noexcept = default;
+
+/*!
+ Destroys this unique identifier description.
+*/
+QCanUniqueIdDescription::~QCanUniqueIdDescription() = default;
+
+QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QCanUniqueIdDescriptionPrivate)
+
+/*!
+ Assigns the values from \a other to this unique identifier description.
+*/
+QCanUniqueIdDescription &QCanUniqueIdDescription::operator=(const QCanUniqueIdDescription &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*!
+ \fn QCanUniqueIdDescription &QCanUniqueIdDescription::operator=(QCanUniqueIdDescription &&other) noexcept
+
+ Move-assigns the values from \a other to this unique identifier description.
+
+ \note The moved-from QCanUniqueIdDescription object can only be destroyed or
+ assigned to. The effect of calling other functions than the destructor or
+ one of the assignment operators is undefined.
+*/
+
+/*!
+ \fn bool QCanUniqueIdDescription::operator==(const QCanUniqueIdDescription &lhs, const QCanUniqueIdDescription &rhs)
+
+ Returns \c true if all of the \a lhs object's values are the same as those
+ of \a rhs. Otherwise returns \c false.
+*/
+
+/*!
+ \fn bool QCanUniqueIdDescription::operator!=(const QCanUniqueIdDescription &lhs, const QCanUniqueIdDescription &rhs)
+
+ Returns \c true if any of the \a lhs object's values are not the same as
+ those of \a rhs. Otherwise returns \c false.
+*/
+
+/*!
+ Returns \c true when this unique identifier description is valid and
+ \c false otherwise.
+
+ A valid unique identifier description \e must have a \l bitLength() which is
+ greater than zero and does not exceed the number of bits of the
+ \l QtCanBus::UniqueId type.
+
+ \sa bitLength()
+*/
+bool QCanUniqueIdDescription::isValid() const
+{
+ static constexpr auto uidSize = sizeof(QtCanBus::UniqueId) * 8;
+ return d->bitLength > 0 && d->bitLength <= uidSize;
+}
+
+/*!
+ Returns the data source of the unique identifier.
+
+ By default, \l {QtCanBus::}{FrameId} is used.
+
+ \sa setSource(), QtCanBus::DataSource
+*/
+QtCanBus::DataSource QCanUniqueIdDescription::source() const
+{
+ return d->source;
+}
+
+/*!
+ Sets the data source of the unique identifier to \a source.
+
+ \sa source(), QtCanBus::DataSource
+*/
+void QCanUniqueIdDescription::setSource(QtCanBus::DataSource source)
+{
+ d.detach();
+ d->source = source;
+}
+
+/*!
+ Returns the start bit of the unique identifier in the \l source().
+
+ \sa setStartBit(), bitLength(), setBitLength()
+*/
+quint16 QCanUniqueIdDescription::startBit() const
+{
+ return d->startBit;
+}
+
+/*!
+ Sets the start bit of the unique identifier in the \l source() to \a bit.
+
+ \sa startBit(), bitLength(), setBitLength()
+*/
+void QCanUniqueIdDescription::setStartBit(quint16 bit)
+{
+ d.detach();
+ d->startBit = bit;
+}
+
+/*!
+ Returns the bit length of the unique identifier.
+
+ \sa setBitLength(), startBit(), setStartBit()
+*/
+quint8 QCanUniqueIdDescription::bitLength() const
+{
+ return d->bitLength;
+}
+
+/*!
+ Sets the bit length of the unique identifier to \a length.
+
+ \sa bitLength(), startBit(), setStartBit()
+*/
+void QCanUniqueIdDescription::setBitLength(quint8 length)
+{
+ d.detach();
+ d->bitLength = length;
+}
+
+/*!
+ Returns the data endian of the unique identifier.
+
+ By default, \l {QtCanBus::}{LittleEndian} is used.
+
+ \sa setEndian(), QtCanBus::DataEndian
+*/
+QtCanBus::DataEndian QCanUniqueIdDescription::endian() const
+{
+ return d->endian;
+}
+
+/*!
+ Sets the data endian of the unique identifier to \a endian.
+
+ \sa endian(), QtCanBus::DataEndian
+*/
+void QCanUniqueIdDescription::setEndian(QtCanBus::DataEndian endian)
+{
+ d.detach();
+ d->endian = endian;
+}
+
+bool QCanUniqueIdDescription::equals(const QCanUniqueIdDescription &lhs, const QCanUniqueIdDescription &rhs)
+{
+ return lhs.d->source == rhs.d->source
+ && lhs.d->endian == rhs.d->endian
+ && lhs.d->startBit == rhs.d->startBit
+ && lhs.d->bitLength == rhs.d->bitLength;
+}
+
+QCanUniqueIdDescriptionPrivate *QCanUniqueIdDescriptionPrivate::get(const QCanUniqueIdDescription &desc)
+{
+ return desc.d.data();
+}
+
+QT_END_NAMESPACE
diff --git a/src/serialbus/qcanuniqueiddescription.h b/src/serialbus/qcanuniqueiddescription.h
new file mode 100644
index 0000000..e6b3ef4
--- /dev/null
+++ b/src/serialbus/qcanuniqueiddescription.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCANUNIQUEIDDESCRIPTION_H
+#define QCANUNIQUEIDDESCRIPTION_H
+
+#include <QtCore/QExplicitlySharedDataPointer>
+
+#include <QtSerialBus/qcancommondefinitions.h>
+#include <QtSerialBus/qtserialbusglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QCanUniqueIdDescriptionPrivate;
+QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QCanUniqueIdDescriptionPrivate, Q_SERIALBUS_EXPORT)
+
+class Q_SERIALBUS_EXPORT QCanUniqueIdDescription
+{
+public:
+ QCanUniqueIdDescription();
+ QCanUniqueIdDescription(const QCanUniqueIdDescription &other);
+ QCanUniqueIdDescription(QCanUniqueIdDescription &&other) noexcept;
+ ~QCanUniqueIdDescription();
+
+ QCanUniqueIdDescription &operator=(const QCanUniqueIdDescription &other);
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QCanUniqueIdDescription)
+
+ friend bool operator==(const QCanUniqueIdDescription &lhs, const QCanUniqueIdDescription &rhs)
+ {
+ return equals(lhs, rhs);
+ }
+ friend bool operator!=(const QCanUniqueIdDescription &lhs, const QCanUniqueIdDescription &rhs)
+ {
+ return !equals(lhs, rhs);
+ }
+
+ inline void swap(QCanUniqueIdDescription &other) noexcept { d.swap(other.d); }
+
+ bool isValid() const;
+
+ QtCanBus::DataSource source() const;
+ void setSource(QtCanBus::DataSource source);
+
+ quint16 startBit() const;
+ void setStartBit(quint16 bit);
+
+ quint8 bitLength() const;
+ void setBitLength(quint8 length);
+
+ QtCanBus::DataEndian endian() const;
+ void setEndian(QtCanBus::DataEndian endian);
+
+private:
+ QExplicitlySharedDataPointer<QCanUniqueIdDescriptionPrivate> d;
+ friend class QCanUniqueIdDescriptionPrivate;
+
+ static bool equals(const QCanUniqueIdDescription &lhs, const QCanUniqueIdDescription &rhs);
+};
+
+QT_END_NAMESPACE
+
+#endif // QCANUNIQUEIDDESCRIPTION_H
diff --git a/src/serialbus/qcanuniqueiddescription_p.h b/src/serialbus/qcanuniqueiddescription_p.h
new file mode 100644
index 0000000..d23ca5b
--- /dev/null
+++ b/src/serialbus/qcanuniqueiddescription_p.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCANUNIQUEIDDESCRIPTION_P_H
+#define QCANUNIQUEIDDESCRIPTION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "private/qtserialbusexports_p.h"
+#include "qcanuniqueiddescription.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_SERIALBUS_PRIVATE_EXPORT QCanUniqueIdDescriptionPrivate : public QSharedData
+{
+public:
+ QtCanBus::DataSource source = QtCanBus::DataSource::FrameId;
+ QtCanBus::DataEndian endian = QtCanBus::DataEndian::LittleEndian;
+ quint16 startBit = 0;
+ quint8 bitLength = 0;
+
+ inline bool isShared() const { return ref.loadRelaxed() != 1; }
+ static QCanUniqueIdDescriptionPrivate *get(const QCanUniqueIdDescription &desc);
+};
+
+QT_END_NAMESPACE
+
+#endif // QCANUNIQUEIDDESCRIPTION_P_H