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 | Q_DECLARE_TYPEINFO(QModbusPdu, Q_RELOCATABLE_TYPE); |
239 | Q_DECLARE_TYPEINFO(QModbusPdu::ExceptionCode, Q_PRIMITIVE_TYPE); |
240 | Q_DECLARE_TYPEINFO(QModbusPdu::FunctionCode, Q_PRIMITIVE_TYPE); |
241 | |
242 | Q_DECLARE_TYPEINFO(QModbusRequest, Q_RELOCATABLE_TYPE); |
243 | Q_DECLARE_TYPEINFO(QModbusResponse, Q_RELOCATABLE_TYPE); |
244 | Q_DECLARE_TYPEINFO(QModbusExceptionResponse, Q_RELOCATABLE_TYPE); |
245 | |
246 | QT_END_NAMESPACE |
247 | |
248 | Q_DECLARE_METATYPE(QModbusPdu::ExceptionCode) |
249 | Q_DECLARE_METATYPE(QModbusPdu::FunctionCode) |
250 | |
251 | #endif // QMODBUSPDU_H |
252 | |