// Copyright (C) 2017 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 QMODBUSPDU_H #define QMODBUSPDU_H #include #include #include #include #include QT_BEGIN_NAMESPACE class Q_SERIALBUS_EXPORT QModbusPdu { public: enum ExceptionCode { IllegalFunction = 0x01, IllegalDataAddress = 0x02, IllegalDataValue = 0x03, ServerDeviceFailure = 0x04, Acknowledge = 0x05, ServerDeviceBusy = 0x06, NegativeAcknowledge = 0x07, MemoryParityError = 0x08, GatewayPathUnavailable = 0x0A, GatewayTargetDeviceFailedToRespond = 0x0B, ExtendedException = 0xFF, }; enum FunctionCode { Invalid = 0x00, ReadCoils = 0x01, ReadDiscreteInputs = 0x02, ReadHoldingRegisters = 0x03, ReadInputRegisters = 0x04, WriteSingleCoil = 0x05, WriteSingleRegister = 0x06, ReadExceptionStatus = 0x07, Diagnostics = 0x08, GetCommEventCounter = 0x0B, GetCommEventLog = 0x0C, WriteMultipleCoils = 0x0F, WriteMultipleRegisters = 0x10, ReportServerId = 0x11, ReadFileRecord = 0x14, WriteFileRecord = 0x15, MaskWriteRegister = 0x16, ReadWriteMultipleRegisters = 0x17, ReadFifoQueue = 0x18, EncapsulatedInterfaceTransport = 0x2B, UndefinedFunctionCode = 0x100 }; QModbusPdu() = default; virtual ~QModbusPdu(); bool isValid() const { return (m_code >= ReadCoils && m_code < UndefinedFunctionCode) && (m_data.size() < 253); } static const quint8 ExceptionByte = 0x80; ExceptionCode exceptionCode() const { if (!m_data.size() || !isException()) return ExtendedException; return static_cast(m_data.at(0)); } bool isException() const { return m_code & ExceptionByte; } qint16 size() const { return dataSize() + 1; } qint16 dataSize() const { return qint16(m_data.size()); } FunctionCode functionCode() const { return FunctionCode(quint8(m_code) &~ ExceptionByte); } virtual void setFunctionCode(FunctionCode code) { m_code = code; } QByteArray data() const { return m_data; } void setData(const QByteArray &newData) { m_data = newData; } template void encodeData(Args ... newData) { encode(std::forward(newData)...); } template void decodeData(Args && ... newData) const { decode(std::forward(newData)...); } protected: QModbusPdu(FunctionCode code, const QByteArray &newData) : m_code(code) , m_data(newData) {} QModbusPdu(const QModbusPdu &) = default; QModbusPdu &operator=(const QModbusPdu &) = default; template QModbusPdu(FunctionCode code, Args ... newData) : m_code(code) { encode(std::forward(newData)...); } private: template struct IsType { enum { value = false }; }; template struct IsType { enum { value = std::is_same::value || IsType::value }; }; template using is_pod = std::integral_constant::value && std::is_standard_layout::value>; template void encode(QDataStream *stream, const T &t) { static_assert(is_pod::value, "Only POD types supported."); static_assert(IsType::value, "Only quint8 and quint16 supported."); (*stream) << t; } template void decode(QDataStream *stream, T &t) const { static_assert(is_pod::value, "Only POD types supported."); static_assert(IsType::value, "Only quint8* and quint16* supported."); (*stream) >> *t; } template void encode(QDataStream *stream, const QList &vector) { static_assert(is_pod::value, "Only POD types supported."); static_assert(IsType::value, "Only quint8 and quint16 supported."); for (int i = 0; i < vector.size(); ++i) (*stream) << vector[i]; } template void encode(Args ... newData) { m_data.clear(); constexpr size_t argCount = sizeof...(Args); if constexpr (argCount > 0) { QDataStream stream(&m_data, QIODevice::WriteOnly); char tmp[argCount] = { (encode(&stream, newData), void(), '0')... }; Q_UNUSED(tmp); } } template void decode(Args ... newData) const { constexpr size_t argCount = sizeof...(Args); if constexpr (argCount > 0) { if (!m_data.isEmpty()) { QDataStream stream(m_data); char tmp[argCount] = { (decode(&stream, newData), void(), '0')... }; Q_UNUSED(tmp); } } } private: FunctionCode m_code = Invalid; QByteArray m_data; friend class QModbusSerialAdu; friend struct QModbusPduPrivate; }; Q_SERIALBUS_EXPORT QDebug operator<<(QDebug debug, const QModbusPdu &pdu); Q_SERIALBUS_EXPORT QDataStream &operator<<(QDataStream &stream, const QModbusPdu &pdu); Q_SERIALBUS_EXPORT QDataStream &operator>>(QDataStream &stream, QModbusPdu::FunctionCode &code); class Q_SERIALBUS_EXPORT QModbusRequest : public QModbusPdu { public: QModbusRequest() = default; QModbusRequest(const QModbusPdu &pdu) : QModbusPdu(pdu) {} explicit QModbusRequest(FunctionCode code, const QByteArray &newData = QByteArray()) : QModbusPdu(code, newData) {} ~QModbusRequest() override; static int minimumDataSize(const QModbusRequest &pdu); static int calculateDataSize(const QModbusRequest &pdu); using CalcFuncPtr = decltype(&calculateDataSize); static void registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr func); template QModbusRequest(FunctionCode code, Args ... newData) : QModbusPdu(code, newData...) {} }; Q_SERIALBUS_EXPORT QDataStream &operator>>(QDataStream &stream, QModbusRequest &pdu); inline QDataStream &operator<<(QDataStream &stream, const QModbusRequest &pdu) { return stream << static_cast(pdu); } class Q_SERIALBUS_EXPORT QModbusResponse : public QModbusPdu { public: QModbusResponse() = default; QModbusResponse(const QModbusPdu &pdu) : QModbusPdu(pdu) {} explicit QModbusResponse(FunctionCode code, const QByteArray &newData = QByteArray()) : QModbusPdu(code, newData) {} ~QModbusResponse() override; static int minimumDataSize(const QModbusResponse &pdu); static int calculateDataSize(const QModbusResponse &pdu); using CalcFuncPtr = decltype(&calculateDataSize); static void registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr func); template QModbusResponse(FunctionCode code, Args ... newData) : QModbusPdu(code, newData...) {} }; class Q_SERIALBUS_EXPORT QModbusExceptionResponse : public QModbusResponse { public: QModbusExceptionResponse() = default; QModbusExceptionResponse(const QModbusPdu &pdu) : QModbusResponse(pdu) {} QModbusExceptionResponse(FunctionCode fc, ExceptionCode ec) : QModbusResponse(FunctionCode(quint8(fc) | ExceptionByte), static_cast (ec)) {} ~QModbusExceptionResponse() override; void setFunctionCode(FunctionCode c) override { QModbusPdu::setFunctionCode(FunctionCode(quint8(c) | ExceptionByte)); } void setExceptionCode(ExceptionCode ec) { QModbusPdu::encodeData(quint8(ec)); } }; Q_SERIALBUS_EXPORT QDataStream &operator>>(QDataStream &stream, QModbusResponse &pdu); inline QDataStream &operator<<(QDataStream &stream, const QModbusResponse &pdu) { return stream << static_cast(pdu); } QT_END_NAMESPACE Q_DECLARE_METATYPE(QModbusPdu::ExceptionCode) Q_DECLARE_METATYPE(QModbusPdu::FunctionCode) #endif // QMODBUSPDU_H