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
62 QT_BEGIN_NAMESPACE
63
Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)64 Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
65 Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
66
67 class QModbusRtuSerialSlavePrivate : public QModbusServerPrivate
68 {
69 Q_DECLARE_PUBLIC(QModbusRtuSerialSlave)
70
71 public:
72 void setupSerialPort()
73 {
74 Q_Q(QModbusRtuSerialSlave);
75
76 m_serialPort = new QSerialPort(q);
77 QObject::connect(m_serialPort, &QSerialPort::readyRead, q, [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(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(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(QModbusServerPrivate::Counter::BusCommunicationError);
117 storeModbusCommEvent(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(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(QModbusServerPrivate::Counter::BusCharacterOverrun);
137 storeModbusCommEvent(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(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(adu.data(), 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(QModbusServerPrivate::Counter::BusCommunicationError);
152 storeModbusCommEvent(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(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(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(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(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(QModbusServerPrivate::Counter::ServerMessage);
188 response = q->processRequest(req);
189 }
190 qCDebug(QT_MODBUS) << "(RTU server) Response PDU:" << response;
191
192 event = QModbusCommEvent::SentEvent; // reset event after processing
193 if (q->value(QModbusServer::ListenOnlyMode).toBool())
194 event |= QModbusCommEvent::SendFlag::CurrentlyInListenOnlyMode;
195
196 if ((!response.isValid())
197 || q->processesBroadcast()
198 || q->value(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(QModbusServerPrivate::Counter::ServerNoResponse);
203 storeModbusCommEvent(event);
204 return;
205 }
206
207 const QByteArray result = QModbusSerialAdu::create(QModbusSerialAdu::Rtu,
208 q->serverAddress(), 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(QModbusRtuSerialSlave::tr("Requesting serial port is closed"),
215 QModbusDevice::WriteError);
216 incrementCounter(QModbusServerPrivate::Counter::ServerNoResponse);
217 storeModbusCommEvent(event);
218 return;
219 }
220
221 qint64 writtenBytes = m_serialPort->write(result);
222 if ((writtenBytes == -1) || (writtenBytes < result.size())) {
223 qCDebug(QT_MODBUS) << "(RTU server) Cannot write requested response to serial port.";
224 q->setError(QModbusRtuSerialSlave::tr("Could not write response to client"),
225 QModbusDevice::WriteError);
226 incrementCounter(QModbusServerPrivate::Counter::ServerNoResponse);
227 storeModbusCommEvent(event);
228 m_serialPort->clear(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(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(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(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(QModbusServerPrivate::Counter::CommEvent);
277 break;
278 }
279 }
280 storeModbusCommEvent(event); // store the final event after processing
281 });
282
283 QObject::connect(m_serialPort, &QSerialPort::errorOccurred, q,
284 [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(QModbusDevice::tr("Referenced serial device does not exist."),
296 QModbusDevice::ConnectionError);
297 break;
298 case QSerialPort::PermissionError:
299 q->setError(QModbusDevice::tr("Cannot open serial device due to permissions."),
300 QModbusDevice::ConnectionError);
301 break;
302 case QSerialPort::OpenError:
303 case QSerialPort::NotOpenError:
304 q->setError(QModbusDevice::tr("Cannot open serial device."),
305 QModbusDevice::ConnectionError);
306 break;
307 case QSerialPort::WriteError:
308 q->setError(QModbusDevice::tr("Write error."), QModbusDevice::WriteError);
309 break;
310 case QSerialPort::ReadError:
311 q->setError(QModbusDevice::tr("Read error."), QModbusDevice::ReadError);
312 break;
313 case QSerialPort::ResourceError:
314 q->setError(QModbusDevice::tr("Resource error."), QModbusDevice::ConnectionError);
315 break;
316 case QSerialPort::UnsupportedOperationError:
317 q->setError(QModbusDevice::tr("Device operation is not supported error."),
318 QModbusDevice::ConfigurationError);
319 break;
320 case QSerialPort::TimeoutError:
321 q->setError(QModbusDevice::tr("Timeout error."), QModbusDevice::TimeoutError);
322 break;
323 case QSerialPort::UnknownError:
324 q->setError(QModbusDevice::tr("Unknown 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(m_serialPort, &QSerialPort::aboutToClose, q, [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(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(m_interFrameDelayMilliseconds,
352 qCeil(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
366 QT_END_NAMESPACE
367
368 #endif // QMODBUSRTUSERIALSLAVE_P_H
369