| 1 | // Copyright (C) 2017 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 3 | #ifndef QMODBUSPDU_H | 
| 4 | #define QMODBUSPDU_H | 
| 5 |  | 
| 6 | #include <QtCore/qdatastream.h> | 
| 7 | #include <QtCore/qiodevice.h> | 
| 8 | #include <QtCore/qlist.h> | 
| 9 | #include <QtCore/qmetatype.h> | 
| 10 | #include <QtSerialBus/qtserialbusglobal.h> | 
| 11 |  | 
| 12 | QT_BEGIN_NAMESPACE | 
| 13 |  | 
| 14 | class Q_SERIALBUS_EXPORT QModbusPdu | 
| 15 | { | 
| 16 | public: | 
| 17 |     enum ExceptionCode { | 
| 18 |         IllegalFunction = 0x01, | 
| 19 |         IllegalDataAddress = 0x02, | 
| 20 |         IllegalDataValue = 0x03, | 
| 21 |         ServerDeviceFailure = 0x04, | 
| 22 |         Acknowledge = 0x05, | 
| 23 |         ServerDeviceBusy = 0x06, | 
| 24 |         NegativeAcknowledge = 0x07, | 
| 25 |         MemoryParityError = 0x08, | 
| 26 |         GatewayPathUnavailable = 0x0A, | 
| 27 |         GatewayTargetDeviceFailedToRespond = 0x0B, | 
| 28 |         ExtendedException = 0xFF, | 
| 29 |     }; | 
| 30 |  | 
| 31 |     enum FunctionCode { | 
| 32 |         Invalid = 0x00, | 
| 33 |         ReadCoils = 0x01, | 
| 34 |         ReadDiscreteInputs = 0x02, | 
| 35 |         ReadHoldingRegisters = 0x03, | 
| 36 |         ReadInputRegisters = 0x04, | 
| 37 |         WriteSingleCoil = 0x05, | 
| 38 |         WriteSingleRegister = 0x06, | 
| 39 |         ReadExceptionStatus = 0x07, | 
| 40 |         Diagnostics = 0x08, | 
| 41 |         GetCommEventCounter = 0x0B, | 
| 42 |         GetCommEventLog = 0x0C, | 
| 43 |         WriteMultipleCoils = 0x0F, | 
| 44 |         WriteMultipleRegisters = 0x10, | 
| 45 |         ReportServerId = 0x11, | 
| 46 |         ReadFileRecord = 0x14, | 
| 47 |         WriteFileRecord = 0x15, | 
| 48 |         MaskWriteRegister = 0x16, | 
| 49 |         ReadWriteMultipleRegisters = 0x17, | 
| 50 |         ReadFifoQueue = 0x18, | 
| 51 |         EncapsulatedInterfaceTransport = 0x2B, | 
| 52 |         UndefinedFunctionCode = 0x100 | 
| 53 |     }; | 
| 54 |  | 
| 55 |     QModbusPdu() = default; | 
| 56 |     virtual ~QModbusPdu(); | 
| 57 |  | 
| 58 |     bool isValid() const { | 
| 59 |         return (m_code >= ReadCoils && m_code < UndefinedFunctionCode) | 
| 60 |                 && (m_data.size() < 253); | 
| 61 |     } | 
| 62 |  | 
| 63 |     static const quint8 ExceptionByte = 0x80; | 
| 64 |     ExceptionCode exceptionCode() const { | 
| 65 |         if (!m_data.size() || !isException()) | 
| 66 |             return ExtendedException; | 
| 67 |         return static_cast<ExceptionCode>(m_data.at(i: 0)); | 
| 68 |     } | 
| 69 |     bool isException() const { return m_code & ExceptionByte; } | 
| 70 |  | 
| 71 |     qint16 size() const { return dataSize() + 1; } | 
| 72 |     qint16 dataSize() const { return qint16(m_data.size()); } | 
| 73 |  | 
| 74 |     FunctionCode functionCode() const { | 
| 75 |         return FunctionCode(quint8(m_code) &~ ExceptionByte); | 
| 76 |     } | 
| 77 |     virtual void setFunctionCode(FunctionCode code) { m_code = code; } | 
| 78 |  | 
| 79 |     QByteArray data() const { return m_data; } | 
| 80 |     void setData(const QByteArray &newData) { m_data = newData; } | 
| 81 |  | 
| 82 |     template <typename ... Args> void encodeData(Args ... newData) { | 
| 83 |         encode(std::forward<Args>(newData)...); | 
| 84 |     } | 
| 85 |  | 
| 86 |     template <typename ... Args> void decodeData(Args && ... newData) const { | 
| 87 |         decode(std::forward<Args>(newData)...); | 
| 88 |     } | 
| 89 |  | 
| 90 | protected: | 
| 91 |     QModbusPdu(FunctionCode code, const QByteArray &newData) | 
| 92 |         : m_code(code) | 
| 93 |         , m_data(newData) | 
| 94 |     {} | 
| 95 |  | 
| 96 |     QModbusPdu(const QModbusPdu &) = default; | 
| 97 |     QModbusPdu &operator=(const QModbusPdu &) = default; | 
| 98 |  | 
| 99 |     template <typename ... Args> | 
| 100 |     QModbusPdu(FunctionCode code, Args ... newData) | 
| 101 |         : m_code(code) | 
| 102 |     { | 
| 103 |         encode(std::forward<Args>(newData)...); | 
| 104 |     } | 
| 105 |  | 
| 106 | private: | 
| 107 |     template <typename T, typename ... Ts> struct IsType { enum { value = false }; }; | 
| 108 |     template <typename T, typename T1, typename ... Ts> struct IsType<T, T1, Ts...> { | 
| 109 |         enum { value = std::is_same<T, T1>::value || IsType<T, Ts...>::value }; | 
| 110 |     }; | 
| 111 |  | 
| 112 |     template <typename T> | 
| 113 |     using is_pod = std::integral_constant<bool, std::is_trivial<T>::value && std::is_standard_layout<T>::value>; | 
| 114 |  | 
| 115 |     template <typename T> void encode(QDataStream *stream, const T &t) { | 
| 116 |         static_assert(is_pod<T>::value, "Only POD types supported." ); | 
| 117 |         static_assert(IsType<T, quint8, quint16>::value, "Only quint8 and quint16 supported." ); | 
| 118 |         (*stream) << t; | 
| 119 |     } | 
| 120 |     template <typename T> void decode(QDataStream *stream, T &t) const { | 
| 121 |         static_assert(is_pod<T>::value, "Only POD types supported." ); | 
| 122 |         static_assert(IsType<T, quint8 *, quint16 *>::value, "Only quint8* and quint16* supported." ); | 
| 123 |         (*stream) >> *t; | 
| 124 |     } | 
| 125 |     template<typename T> | 
| 126 |     void encode(QDataStream *stream, const QList<T> &vector) | 
| 127 |     { | 
| 128 |         static_assert(is_pod<T>::value, "Only POD types supported." ); | 
| 129 |         static_assert(IsType<T, quint8, quint16>::value, "Only quint8 and quint16 supported." ); | 
| 130 |         for (int i = 0; i < vector.size(); ++i) | 
| 131 |             (*stream) << vector[i]; | 
| 132 |     } | 
| 133 |  | 
| 134 |     template<typename ... Args> void encode(Args ... newData) { | 
| 135 |         m_data.clear(); | 
| 136 |         constexpr size_t argCount = sizeof...(Args); | 
| 137 |         if constexpr (argCount > 0) { | 
| 138 |             QDataStream stream(&m_data, QIODevice::WriteOnly); | 
| 139 |             char tmp[argCount] = { (encode(&stream, newData), void(), '0')... }; | 
| 140 |             Q_UNUSED(tmp); | 
| 141 |         } | 
| 142 |     } | 
| 143 |     template<typename ... Args> void decode(Args ... newData) const { | 
| 144 |         constexpr size_t argCount = sizeof...(Args); | 
| 145 |         if constexpr (argCount > 0) { | 
| 146 |             if (!m_data.isEmpty()) { | 
| 147 |                 QDataStream stream(m_data); | 
| 148 |                 char tmp[argCount] = { (decode(&stream, newData), void(), '0')... }; | 
| 149 |                 Q_UNUSED(tmp); | 
| 150 |             } | 
| 151 |         } | 
| 152 |     } | 
| 153 |  | 
| 154 | private: | 
| 155 |     FunctionCode m_code = Invalid; | 
| 156 |     QByteArray m_data; | 
| 157 |     friend class QModbusSerialAdu; | 
| 158 |     friend struct QModbusPduPrivate; | 
| 159 | }; | 
| 160 | Q_SERIALBUS_EXPORT QDebug operator<<(QDebug debug, const QModbusPdu &pdu); | 
| 161 | Q_SERIALBUS_EXPORT QDataStream &operator<<(QDataStream &stream, const QModbusPdu &pdu); | 
| 162 | Q_SERIALBUS_EXPORT QDataStream &operator>>(QDataStream &stream, QModbusPdu::FunctionCode &code); | 
| 163 |  | 
| 164 | class Q_SERIALBUS_EXPORT QModbusRequest : public QModbusPdu | 
| 165 | { | 
| 166 | public: | 
| 167 |     QModbusRequest() = default; | 
| 168 |     QModbusRequest(const QModbusPdu &pdu) | 
| 169 |         : QModbusPdu(pdu) | 
| 170 |     {} | 
| 171 |  | 
| 172 |     explicit QModbusRequest(FunctionCode code, const QByteArray &newData = QByteArray()) | 
| 173 |         : QModbusPdu(code, newData) | 
| 174 |     {} | 
| 175 |     ~QModbusRequest() override; | 
| 176 |  | 
| 177 |     static int minimumDataSize(const QModbusRequest &pdu); | 
| 178 |     static int calculateDataSize(const QModbusRequest &pdu); | 
| 179 |  | 
| 180 |     using CalcFuncPtr = decltype(&calculateDataSize); | 
| 181 |     static void registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr func); | 
| 182 |  | 
| 183 |     template <typename ... Args> | 
| 184 |     QModbusRequest(FunctionCode code, Args ... newData) | 
| 185 |         : QModbusPdu(code, newData...) | 
| 186 |     {} | 
| 187 | }; | 
| 188 | Q_SERIALBUS_EXPORT QDataStream &operator>>(QDataStream &stream, QModbusRequest &pdu); | 
| 189 | inline QDataStream &operator<<(QDataStream &stream, const QModbusRequest &pdu) | 
| 190 | { return stream << static_cast<const QModbusPdu &>(pdu); } | 
| 191 |  | 
| 192 | class Q_SERIALBUS_EXPORT QModbusResponse : public QModbusPdu | 
| 193 | { | 
| 194 | public: | 
| 195 |     QModbusResponse() = default; | 
| 196 |     QModbusResponse(const QModbusPdu &pdu) | 
| 197 |         : QModbusPdu(pdu) | 
| 198 |     {} | 
| 199 |  | 
| 200 |     explicit QModbusResponse(FunctionCode code, const QByteArray &newData = QByteArray()) | 
| 201 |         : QModbusPdu(code, newData) | 
| 202 |     {} | 
| 203 |     ~QModbusResponse() override; | 
| 204 |  | 
| 205 |     static int minimumDataSize(const QModbusResponse &pdu); | 
| 206 |     static int calculateDataSize(const QModbusResponse &pdu); | 
| 207 |  | 
| 208 |     using CalcFuncPtr = decltype(&calculateDataSize); | 
| 209 |     static void registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr func); | 
| 210 |  | 
| 211 |     template <typename ... Args> | 
| 212 |     QModbusResponse(FunctionCode code, Args ... newData) | 
| 213 |         : QModbusPdu(code, newData...) | 
| 214 |     {} | 
| 215 | }; | 
| 216 |  | 
| 217 | class Q_SERIALBUS_EXPORT QModbusExceptionResponse : public QModbusResponse | 
| 218 | { | 
| 219 | public: | 
| 220 |     QModbusExceptionResponse() = default; | 
| 221 |     QModbusExceptionResponse(const QModbusPdu &pdu) | 
| 222 |         : QModbusResponse(pdu) | 
| 223 |     {} | 
| 224 |     QModbusExceptionResponse(FunctionCode fc, ExceptionCode ec) | 
| 225 |         : QModbusResponse(FunctionCode(quint8(fc) | ExceptionByte), static_cast<quint8> (ec)) | 
| 226 |     {} | 
| 227 |     ~QModbusExceptionResponse() override; | 
| 228 |  | 
| 229 |     void setFunctionCode(FunctionCode c) override { | 
| 230 |         QModbusPdu::setFunctionCode(FunctionCode(quint8(c) | ExceptionByte)); | 
| 231 |     } | 
| 232 |     void setExceptionCode(ExceptionCode ec) { QModbusPdu::encodeData(newData: quint8(ec)); } | 
| 233 | }; | 
| 234 | Q_SERIALBUS_EXPORT QDataStream &operator>>(QDataStream &stream, QModbusResponse &pdu); | 
| 235 | inline QDataStream &operator<<(QDataStream &stream, const QModbusResponse &pdu) | 
| 236 | { return stream << static_cast<const QModbusPdu &>(pdu); } | 
| 237 |  | 
| 238 | QT_END_NAMESPACE | 
| 239 |  | 
| 240 | Q_DECLARE_METATYPE(QModbusPdu::ExceptionCode) | 
| 241 | Q_DECLARE_METATYPE(QModbusPdu::FunctionCode) | 
| 242 |  | 
| 243 | #endif // QMODBUSPDU_H | 
| 244 |  |