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(len: 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.isEmpty(), "QModbusAdu::pdu()", "Empty 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 Q_ASSERT_X(!m_data.isEmpty(), "QModbusAdu::checksum()", "Empty ADU.");
61 if (m_type == Ascii)
62 return quint8(m_data[m_data.size() - 1]);
63 return quint16(quint8(m_data[m_data.size() - 2]) << 8 | quint8(m_data[m_data.size() - 1]));
64 }
65
66 inline bool matchingChecksum() const {
67 Q_ASSERT_X(!m_data.isEmpty(), "QModbusAdu::matchingChecksum()", "Empty ADU.");
68 if (m_type == Ascii)
69 return QModbusSerialAdu::calculateLRC(data: data(), len: size()) == checksum<quint8>();
70 return QModbusSerialAdu::calculateCRC(data: data(), len: size()) == checksum<quint16>();
71 }
72
73 /*!
74 \internal
75 \fn quint8 QModbusSerialAdu::calculateLRC(const char *data, qint32 len)
76
77 Returns the LRC checksum of the first \a len bytes of \a data. The checksum is independent
78 of the byte order (endianness).
79 */
80 inline static quint8 calculateLRC(const char *data, qint32 len)
81 {
82 quint32 lrc = 0;
83 while (len--)
84 lrc += *data++;
85 return -(quint8(lrc));
86 }
87
88 /*!
89 \internal
90 \fn quint16 QModbusSerialAdu::calculateCRC(const char *data, qint32 len) const
91
92 Returns the CRC checksum of the first \a len bytes of \a data.
93
94 \note The code used by the function was generated with pycrc. There is no copyright assigned
95 to the generated code, however, the author of the script requests to show the line stating
96 that the code was generated by pycrc (see implementation).
97 */
98 inline static quint16 calculateCRC(const char *data, qint32 len)
99 {
100 // Generated by pycrc v0.8.3, https://pycrc.org
101 // Width = 16, Poly = 0x8005, XorIn = 0xffff, ReflectIn = True,
102 // XorOut = 0x0000, ReflectOut = True, Algorithm = bit-by-bit-fast
103
104 quint16 crc = 0xFFFF;
105 while (len--) {
106 const quint8 c = *data++;
107 for (qint32 i = 0x01; i & 0xFF; i <<= 1) {
108 bool bit = crc & 0x8000;
109 if (c & i)
110 bit = !bit;
111 crc <<= 1;
112 if (bit)
113 crc ^= 0x8005;
114 }
115 crc &= 0xFFFF;
116 }
117 crc = crc_reflect(data: crc & 0xFFFF, len: 16) ^ 0x0000;
118 return (crc >> 8) | (crc << 8); // swap bytes
119 }
120
121 inline static QByteArray create(Type type, int serverAddress, const QModbusPdu &pdu,
122 char delimiter = '\n') {
123 QByteArray result;
124 QDataStream out(&result, QIODevice::WriteOnly);
125 out << quint8(serverAddress) << pdu;
126
127 if (type == Ascii) {
128 out << calculateLRC(data: result, len: result.size());
129 return ":" + result.toHex() + "\r" + delimiter;
130 } else {
131 out << calculateCRC(data: result, len: result.size());
132 }
133 return result;
134 }
135
136private:
137 inline static quint16 crc_reflect(quint16 data, qint32 len)
138 {
139 // Generated by pycrc v0.8.3, https://pycrc.org
140 // Width = 16, Poly = 0x8005, XorIn = 0xffff, ReflectIn = True,
141 // XorOut = 0x0000, ReflectOut = True, Algorithm = bit-by-bit-fast
142
143 quint16 ret = data & 0x01;
144 for (qint32 i = 1; i < len; i++) {
145 data >>= 1;
146 ret = (ret << 1) | (data & 0x01);
147 }
148 return ret;
149 }
150
151private:
152 Type m_type = Rtu;
153 QByteArray m_data;
154 QByteArray m_rawData;
155};
156
157QT_END_NAMESPACE
158
159#endif // QMODBUSADU_P_H
160

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