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

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