1// Copyright (C) 2021 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
4#ifndef QMODBUSADU_P_H
5#define QMODBUSADU_P_H
6
7#include <QtSerialBus/qmodbuspdu.h>
8#include <QtCore/private/qglobal_p.h>
9
10//
11// W A R N I N G
12// -------------
13//
14// This file is not part of the Qt API. It exists purely as an
15// implementation detail. This header file may change from version to
16// version without notice, or even be removed.
17//
18// We mean it.
19//
20
21QT_BEGIN_NAMESPACE
22
23class QModbusSerialAdu
24{
25public:
26 enum Type {
27 Ascii,
28 Rtu
29 };
30
31 inline QModbusSerialAdu(Type type, const QByteArray &data)
32 : m_type(type), m_data(data), m_rawData(data)
33 {
34 if (m_type == Ascii)
35 m_data = QByteArray::fromHex(hexEncoded: m_data.mid(index: 1, len: m_data.size() - 3));
36 }
37
38 inline int size() const {
39 if (m_type == Ascii)
40 return m_data.size() - 1; // one byte, LRC
41 return m_data.size() - 2; // two bytes, CRC
42 }
43 inline QByteArray data() const { return m_data.left(n: size()); }
44
45 inline int rawSize() const { return m_rawData.size(); }
46 inline QByteArray rawData() const { return m_rawData; }
47
48 inline int serverAddress() const {
49 Q_ASSERT_X(!m_data.isEmpty(), "QModbusAdu::serverAddress()", "Empty ADU.");
50 return quint8(m_data.at(i: 0));
51 }
52
53 inline QModbusPdu pdu() const {
54 Q_ASSERT_X(m_data.size() >= 2, "QModbusAdu::pdu()", "Not enough bytes in ADU.");
55 return QModbusPdu(QModbusPdu::FunctionCode(m_data.at(i: 1)), m_data.mid(index: 2, len: size() - 2));
56 }
57
58 template <typename T>
59 auto checksum() const -> decltype(T()) {
60 if (m_type == Ascii) {
61 Q_ASSERT_X(!m_data.isEmpty(), "QModbusAdu::checksum()", "Empty ADU.");
62 return quint8(m_data[m_data.size() - 1]);
63 }
64 Q_ASSERT_X(m_data.size() >= 2, "QModbusAdu::checksum()",
65 "Not enough bytes to calculate checksum.");
66 return quint16(quint8(m_data[m_data.size() - 2]) << 8 | quint8(m_data[m_data.size() - 1]));
67 }
68
69 inline bool matchingChecksum() const {
70 Q_ASSERT_X(!m_data.isEmpty(), "QModbusAdu::matchingChecksum()", "Empty ADU.");
71 if (m_type == Ascii)
72 return QModbusSerialAdu::calculateLRC(data: data(), len: size()) == checksum<quint8>();
73 return QModbusSerialAdu::calculateCRC(data: data(), len: size()) == checksum<quint16>();
74 }
75
76 /*!
77 \internal
78 \fn quint8 QModbusSerialAdu::calculateLRC(const char *data, qint32 len)
79
80 Returns the LRC checksum of the first \a len bytes of \a data. The checksum is independent
81 of the byte order (endianness).
82 */
83 inline static quint8 calculateLRC(const char *data, qint32 len)
84 {
85 quint32 lrc = 0;
86 while (len--)
87 lrc += *data++;
88 return -(quint8(lrc));
89 }
90
91 /*!
92 \internal
93 \fn quint16 QModbusSerialAdu::calculateCRC(const char *data, qint32 len) const
94
95 Returns the CRC checksum of the first \a len bytes of \a data.
96
97 \note The code used by the function was generated with pycrc. There is no copyright assigned
98 to the generated code, however, the author of the script requests to show the line stating
99 that the code was generated by pycrc (see implementation).
100 */
101 inline static quint16 calculateCRC(const char *data, qint32 len)
102 {
103 // Generated by pycrc v0.8.3, https://pycrc.org
104 // Width = 16, Poly = 0x8005, XorIn = 0xffff, ReflectIn = True,
105 // XorOut = 0x0000, ReflectOut = True, Algorithm = bit-by-bit-fast
106
107 quint16 crc = 0xFFFF;
108 while (len--) {
109 const quint8 c = *data++;
110 for (qint32 i = 0x01; i & 0xFF; i <<= 1) {
111 bool bit = crc & 0x8000;
112 if (c & i)
113 bit = !bit;
114 crc <<= 1;
115 if (bit)
116 crc ^= 0x8005;
117 }
118 crc &= 0xFFFF;
119 }
120 crc = crc_reflect(data: crc & 0xFFFF, len: 16) ^ 0x0000;
121 return (crc >> 8) | (crc << 8); // swap bytes
122 }
123
124 inline static QByteArray create(Type type, int serverAddress, const QModbusPdu &pdu,
125 char delimiter = '\n') {
126 QByteArray result;
127 QDataStream out(&result, QIODevice::WriteOnly);
128 out << quint8(serverAddress) << pdu;
129
130 if (type == Ascii) {
131 out << calculateLRC(data: result, len: result.size());
132 return ":" + result.toHex() + "\r" + delimiter;
133 } else {
134 out << calculateCRC(data: result, len: result.size());
135 }
136 return result;
137 }
138
139private:
140 inline static quint16 crc_reflect(quint16 data, qint32 len)
141 {
142 // Generated by pycrc v0.8.3, https://pycrc.org
143 // Width = 16, Poly = 0x8005, XorIn = 0xffff, ReflectIn = True,
144 // XorOut = 0x0000, ReflectOut = True, Algorithm = bit-by-bit-fast
145
146 quint16 ret = data & 0x01;
147 for (qint32 i = 1; i < len; i++) {
148 data >>= 1;
149 ret = (ret << 1) | (data & 0x01);
150 }
151 return ret;
152 }
153
154private:
155 Type m_type = Rtu;
156 QByteArray m_data;
157 QByteArray m_rawData;
158};
159
160QT_END_NAMESPACE
161
162#endif // QMODBUSADU_P_H
163

source code of qtserialbus/src/serialbus/qmodbusadu_p.h