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