diff options
author | Ivan Solovev <ivan.solovev@qt.io> | 2022-09-13 18:52:52 +0200 |
---|---|---|
committer | Ivan Solovev <ivan.solovev@qt.io> | 2022-11-14 15:17:46 +0100 |
commit | 92b48dfbd9b02da91cde86fe6b7ee0086302bdca (patch) | |
tree | 8877f0a47fdd41ce10375eb95b03400464a348a1 /src | |
parent | 3e3e3451a7cb5d79e26285d804d85d9b833631c9 (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.txt | 5 | ||||
-rw-r--r-- | src/serialbus/qcancommondefinitions.cpp | 147 | ||||
-rw-r--r-- | src/serialbus/qcancommondefinitions.h | 58 | ||||
-rw-r--r-- | src/serialbus/qcanmessagedescription.cpp | 376 | ||||
-rw-r--r-- | src/serialbus/qcanmessagedescription.h | 84 | ||||
-rw-r--r-- | src/serialbus/qcanmessagedescription_p.h | 39 | ||||
-rw-r--r-- | src/serialbus/qcansignaldescription.cpp | 798 | ||||
-rw-r--r-- | src/serialbus/qcansignaldescription.h | 114 | ||||
-rw-r--r-- | src/serialbus/qcansignaldescription_p.h | 62 | ||||
-rw-r--r-- | src/serialbus/qcanuniqueiddescription.cpp | 224 | ||||
-rw-r--r-- | src/serialbus/qcanuniqueiddescription.h | 62 | ||||
-rw-r--r-- | src/serialbus/qcanuniqueiddescription_p.h | 37 |
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 |