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
4#ifndef QMODBUSRTUSERIALSERVER_P_H
5#define QMODBUSRTUSERIALSERVER_P_H
6
7#include <QtCore/qbytearray.h>
8#include <QtCore/qdebug.h>
9#include <QtCore/qelapsedtimer.h>
10#include <QtCore/qloggingcategory.h>
11#include <QtCore/qmath.h>
12#include <QtSerialBus/qmodbusrtuserialserver.h>
13#include <QtSerialPort/qserialport.h>
14
15#include <private/qmodbusadu_p.h>
16#include <private/qmodbusserver_p.h>
17
18//
19// W A R N I N G
20// -------------
21//
22// This file is not part of the Qt API. It exists purely as an
23// implementation detail. This header file may change from version to
24// version without notice, or even be removed.
25//
26// We mean it.
27//
28
29QT_BEGIN_NAMESPACE
30
31Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
32Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
33
34class QModbusRtuSerialServerPrivate : public QModbusServerPrivate
35{
36 Q_DECLARE_PUBLIC(QModbusRtuSerialServer)
37
38public:
39 void setupSerialPort()
40 {
41 Q_Q(QModbusRtuSerialServer);
42
43 m_serialPort = new QSerialPort(q);
44 QObject::connect(sender: m_serialPort, signal: &QSerialPort::readyRead, context: q, slot: [this]() {
45
46 if (m_interFrameTimer.isValid()
47 && m_interFrameTimer.elapsed() > m_interFrameDelayMilliseconds
48 && !m_requestBuffer.isEmpty()) {
49 // This permits response buffer clearing if it contains garbage
50 // but still permits cases where very slow baud rates can cause
51 // chunked and delayed packets
52 qCDebug(QT_MODBUS_LOW) << "(RTU server) Dropping older ADU fragments due to larger than 3.5 char delay (expected:"
53 << m_interFrameDelayMilliseconds << ", max:"
54 << m_interFrameTimer.elapsed() << ")";
55 m_requestBuffer.clear();
56 }
57
58 m_interFrameTimer.start();
59
60 const qint64 size = m_serialPort->size();
61 m_requestBuffer += m_serialPort->read(maxlen: size);
62
63 const QModbusSerialAdu adu(QModbusSerialAdu::Rtu, m_requestBuffer);
64 qCDebug(QT_MODBUS_LOW) << "(RTU server) Received ADU:" << adu.rawData().toHex();
65
66 // Index -> description
67 // Server address -> 1 byte
68 // FunctionCode -> 1 byte
69 // FunctionCode specific content -> 0-252 bytes
70 // CRC -> 2 bytes
71 Q_Q(QModbusRtuSerialServer);
72 QModbusCommEvent event = QModbusCommEvent::ReceiveEvent;
73 if (q->value(option: QModbusServer::ListenOnlyMode).toBool())
74 event |= QModbusCommEvent::ReceiveFlag::CurrentlyInListenOnlyMode;
75
76 // We expect at least the server address, function code and CRC.
77 if (adu.rawSize() < 4) { // TODO: LRC should be 3 bytes.
78 qCWarning(QT_MODBUS) << "(RTU server) Incomplete ADU received, ignoring";
79
80 // The quantity of CRC errors encountered by the remote device since its last
81 // restart, clear counters operation, or power-up. In case of a message
82 // length < 4 bytes, the receiving device is not able to calculate the CRC.
83 incrementCounter(counter: QModbusServerPrivate::Counter::BusCommunicationError);
84 storeModbusCommEvent(eventByte: event | QModbusCommEvent::ReceiveFlag::CommunicationError);
85 return;
86 }
87
88 // Server address is set to 0, this is a broadcast.
89 m_processesBroadcast = (adu.serverAddress() == 0);
90 if (q->processesBroadcast())
91 event |= QModbusCommEvent::ReceiveFlag::BroadcastReceived;
92
93 const int pduSizeWithoutFcode = QModbusRequest::calculateDataSize(pdu: adu.pdu());
94
95 // server address byte + function code byte + PDU size + 2 bytes CRC
96 if ((pduSizeWithoutFcode < 0) || ((2 + pduSizeWithoutFcode + 2) != adu.rawSize())) {
97 qCWarning(QT_MODBUS) << "(RTU server) ADU does not match expected size, ignoring";
98 // The quantity of messages addressed to the remote device that it could not
99 // handle due to a character overrun condition, since its last restart, clear
100 // counters operation, or power-up. A character overrun is caused by data
101 // characters arriving at the port faster than they can be stored, or by the loss
102 // of a character due to a hardware malfunction.
103 incrementCounter(counter: QModbusServerPrivate::Counter::BusCharacterOverrun);
104 storeModbusCommEvent(eventByte: event | QModbusCommEvent::ReceiveFlag::CharacterOverrun);
105 return;
106 }
107
108 // We received the full message, including checksum. We do not expect more bytes to
109 // arrive, so clear the buffer. All new bytes are considered part of the next message.
110 m_requestBuffer.resize(size: 0);
111
112 if (!adu.matchingChecksum()) {
113 qCWarning(QT_MODBUS) << "(RTU server) Discarding request with wrong CRC, received:"
114 << adu.checksum<quint16>() << ", calculated CRC:"
115 << QModbusSerialAdu::calculateCRC(data: adu.data(), len: adu.size());
116 // The quantity of CRC errors encountered by the remote device since its last
117 // restart, clear counters operation, or power-up.
118 incrementCounter(counter: QModbusServerPrivate::Counter::BusCommunicationError);
119 storeModbusCommEvent(eventByte: event | QModbusCommEvent::ReceiveFlag::CommunicationError);
120 return;
121 }
122
123 // The quantity of messages that the remote device has detected on the communications
124 // system since its last restart, clear counters operation, or power-up.
125 incrementCounter(counter: QModbusServerPrivate::Counter::BusMessage);
126
127 // If we do not process a Broadcast ...
128 if (!q->processesBroadcast()) {
129 // check if the server address matches ...
130 if (q->serverAddress() != adu.serverAddress()) {
131 // no, not our address! Ignore!
132 qCDebug(QT_MODBUS) << "(RTU server) Wrong server address, expected"
133 << q->serverAddress() << "got" << adu.serverAddress();
134 return;
135 }
136 } // else { Broadcast -> Server address will never match, deliberately ignore }
137
138 storeModbusCommEvent(eventByte: event); // store the final event before processing
139
140 const QModbusRequest req = adu.pdu();
141 qCDebug(QT_MODBUS) << "(RTU server) Request PDU:" << req;
142 QModbusResponse response; // If the device ...
143 if (q->value(option: QModbusServer::DeviceBusy).value<quint16>() == 0xffff) {
144 // is busy, update the quantity of messages addressed to the remote device for
145 // which it returned a Server Device Busy exception response, since its last
146 // restart, clear counters operation, or power-up.
147 incrementCounter(counter: QModbusServerPrivate::Counter::ServerBusy);
148 response = QModbusExceptionResponse(req.functionCode(),
149 QModbusExceptionResponse::ServerDeviceBusy);
150 } else {
151 // is not busy, update the quantity of messages addressed to the remote device,
152 // or broadcast, that the remote device has processed since its last restart,
153 // clear counters operation, or power-up.
154 incrementCounter(counter: QModbusServerPrivate::Counter::ServerMessage);
155 response = q->processRequest(request: req);
156 }
157 qCDebug(QT_MODBUS) << "(RTU server) Response PDU:" << response;
158
159 event = QModbusCommEvent::SentEvent; // reset event after processing
160 if (q->value(option: QModbusServer::ListenOnlyMode).toBool())
161 event |= QModbusCommEvent::SendFlag::CurrentlyInListenOnlyMode;
162
163 if ((!response.isValid())
164 || q->processesBroadcast()
165 || q->value(option: QModbusServer::ListenOnlyMode).toBool()) {
166 // The quantity of messages addressed to the remote device for which it has
167 // returned no response (neither a normal response nor an exception response),
168 // since its last restart, clear counters operation, or power-up.
169 incrementCounter(counter: QModbusServerPrivate::Counter::ServerNoResponse);
170 storeModbusCommEvent(eventByte: event);
171 return;
172 }
173
174 const QByteArray result = QModbusSerialAdu::create(type: QModbusSerialAdu::Rtu,
175 serverAddress: q->serverAddress(), pdu: response);
176
177 qCDebug(QT_MODBUS_LOW) << "(RTU server) Response ADU:" << result.toHex();
178
179 if (!m_serialPort->isOpen()) {
180 qCDebug(QT_MODBUS) << "(RTU server) Requesting serial port has closed.";
181 q->setError(errorText: QModbusRtuSerialServer::tr(s: "Requesting serial port is closed"),
182 error: QModbusDevice::WriteError);
183 incrementCounter(counter: QModbusServerPrivate::Counter::ServerNoResponse);
184 storeModbusCommEvent(eventByte: event);
185 return;
186 }
187
188 qint64 writtenBytes = m_serialPort->write(data: result);
189 if ((writtenBytes == -1) || (writtenBytes < result.size())) {
190 qCDebug(QT_MODBUS) << "(RTU server) Cannot write requested response to serial port.";
191 q->setError(errorText: QModbusRtuSerialServer::tr(s: "Could not write response to client"),
192 error: QModbusDevice::WriteError);
193 incrementCounter(counter: QModbusServerPrivate::Counter::ServerNoResponse);
194 storeModbusCommEvent(eventByte: event);
195 m_serialPort->clear(directions: QSerialPort::Output);
196 return;
197 }
198
199 if (response.isException()) {
200 switch (response.exceptionCode()) {
201 case QModbusExceptionResponse::IllegalFunction:
202 case QModbusExceptionResponse::IllegalDataAddress:
203 case QModbusExceptionResponse::IllegalDataValue:
204 event |= QModbusCommEvent::SendFlag::ReadExceptionSent;
205 break;
206
207 case QModbusExceptionResponse::ServerDeviceFailure:
208 event |= QModbusCommEvent::SendFlag::ServerAbortExceptionSent;
209 break;
210
211 case QModbusExceptionResponse::ServerDeviceBusy:
212 // The quantity of messages addressed to the remote device for which it
213 // returned a server device busy exception response, since its last restart,
214 // clear counters operation, or power-up.
215 incrementCounter(counter: QModbusServerPrivate::Counter::ServerBusy);
216 event |= QModbusCommEvent::SendFlag::ServerBusyExceptionSent;
217 break;
218
219 case QModbusExceptionResponse::NegativeAcknowledge:
220 // The quantity of messages addressed to the remote device for which it
221 // returned a negative acknowledge (NAK) exception response, since its last
222 // restart, clear counters operation, or power-up.
223 incrementCounter(counter: QModbusServerPrivate::Counter::ServerNAK);
224 event |= QModbusCommEvent::SendFlag::ServerProgramNAKExceptionSent;
225 break;
226
227 default:
228 break;
229 }
230 // The quantity of Modbus exception responses returned by the remote device since
231 // its last restart, clear counters operation, or power-up.
232 incrementCounter(counter: QModbusServerPrivate::Counter::BusExceptionError);
233 } else {
234 switch (quint16(req.functionCode())) {
235 case 0x0a: // Poll 484 (not in the official Modbus specification) *1
236 case 0x0e: // Poll Controller (not in the official Modbus specification) *1
237 case QModbusRequest::GetCommEventCounter: // fall through and bail out
238 break;
239 default:
240 // The device's event counter is incremented once for each successful message
241 // completion. Do not increment for exception responses, poll commands, or fetch
242 // event counter commands. *1 but mentioned here ^^^
243 incrementCounter(counter: QModbusServerPrivate::Counter::CommEvent);
244 break;
245 }
246 }
247 storeModbusCommEvent(eventByte: event); // store the final event after processing
248 });
249
250 QObject::connect(sender: m_serialPort, signal: &QSerialPort::errorOccurred, context: q,
251 slot: [this](QSerialPort::SerialPortError error) {
252 if (error == QSerialPort::NoError)
253 return;
254
255 qCDebug(QT_MODBUS) << "(RTU server) QSerialPort error:" << error
256 << (m_serialPort ? m_serialPort->errorString() : QString());
257
258 Q_Q(QModbusRtuSerialServer);
259
260 switch (error) {
261 case QSerialPort::DeviceNotFoundError:
262 q->setError(errorText: QModbusDevice::tr(s: "Referenced serial device does not exist."),
263 error: QModbusDevice::ConnectionError);
264 break;
265 case QSerialPort::PermissionError:
266 q->setError(errorText: QModbusDevice::tr(s: "Cannot open serial device due to permissions."),
267 error: QModbusDevice::ConnectionError);
268 break;
269 case QSerialPort::OpenError:
270 case QSerialPort::NotOpenError:
271 q->setError(errorText: QModbusDevice::tr(s: "Cannot open serial device."),
272 error: QModbusDevice::ConnectionError);
273 break;
274 case QSerialPort::WriteError:
275 q->setError(errorText: QModbusDevice::tr(s: "Write error."), error: QModbusDevice::WriteError);
276 break;
277 case QSerialPort::ReadError:
278 q->setError(errorText: QModbusDevice::tr(s: "Read error."), error: QModbusDevice::ReadError);
279 break;
280 case QSerialPort::ResourceError:
281 q->setError(errorText: QModbusDevice::tr(s: "Resource error."), error: QModbusDevice::ConnectionError);
282 break;
283 case QSerialPort::UnsupportedOperationError:
284 q->setError(errorText: QModbusDevice::tr(s: "Device operation is not supported error."),
285 error: QModbusDevice::ConfigurationError);
286 break;
287 case QSerialPort::TimeoutError:
288 q->setError(errorText: QModbusDevice::tr(s: "Timeout error."), error: QModbusDevice::TimeoutError);
289 break;
290 case QSerialPort::UnknownError:
291 q->setError(errorText: QModbusDevice::tr(s: "Unknown error."), error: QModbusDevice::UnknownError);
292 break;
293 default:
294 qCDebug(QT_MODBUS) << "(RTU server) Unhandled QSerialPort error" << error;
295 break;
296 }
297 });
298
299 QObject::connect(sender: m_serialPort, signal: &QSerialPort::aboutToClose, context: q, slot: [this]() {
300 Q_Q(QModbusRtuSerialServer);
301 // update state if socket closure was caused by remote side
302 if (q->state() != QModbusDevice::ClosingState)
303 q->setState(QModbusDevice::UnconnectedState);
304 });
305 }
306
307 void setupEnvironment()
308 {
309 if (m_serialPort) {
310 m_serialPort->setPortName(m_comPort);
311 m_serialPort->setParity(m_parity);
312 m_serialPort->setBaudRate(baudRate: m_baudRate);
313 m_serialPort->setDataBits(m_dataBits);
314 m_serialPort->setStopBits(m_stopBits);
315 }
316
317 calculateInterFrameDelay();
318
319 m_requestBuffer.clear();
320 }
321
322 QIODevice *device() const override { return m_serialPort; }
323
324 QByteArray m_requestBuffer;
325 bool m_processesBroadcast = false;
326 QSerialPort *m_serialPort = nullptr;
327 QElapsedTimer m_interFrameTimer;
328};
329
330QT_END_NAMESPACE
331
332#endif // QMODBUSRTUSERIALSERVER_P_H
333

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