| 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 | #include "qmodbusdevice.h" | 
| 5 | #include "qmodbusdevice_p.h" | 
| 6 | #include "qmodbusdataunit.h" | 
| 7 |  | 
| 8 | #include <QtCore/qloggingcategory.h> | 
| 9 |  | 
| 10 | QT_BEGIN_NAMESPACE | 
| 11 |  | 
| 12 | /*! | 
| 13 |     \class QModbusDevice | 
| 14 |     \inmodule QtSerialBus | 
| 15 |     \since 5.8 | 
| 16 |  | 
| 17 |     \brief The QModbusDevice class is the base class for Modbus classes, \l QModbusServer | 
| 18 |     and \l QModbusClient. | 
| 19 | */ | 
| 20 |  | 
| 21 | /*! | 
| 22 |     Constructs a Modbus device with the specified \a parent. | 
| 23 | */ | 
| 24 | QModbusDevice::QModbusDevice(QObject *parent) | 
| 25 |  : QObject(*new QModbusDevicePrivate, parent) | 
| 26 | { | 
| 27 | } | 
| 28 |  | 
| 29 | /*! | 
| 30 |     \internal | 
| 31 | */ | 
| 32 | QModbusDevice::QModbusDevice(QModbusDevicePrivate &dd, QObject *parent) | 
| 33 |  : QObject(dd, parent) | 
| 34 | { | 
| 35 | } | 
| 36 |  | 
| 37 | /*! | 
| 38 |     Destroys the QModbusDevice instance | 
| 39 | */ | 
| 40 | QModbusDevice::~QModbusDevice() | 
| 41 | { | 
| 42 | } | 
| 43 |  | 
| 44 | /*! | 
| 45 |     \enum QModbusDevice::ConnectionParameter | 
| 46 |  | 
| 47 |     This enum describes the possible values that can be set for a Modbus device | 
| 48 |     connection. | 
| 49 |  | 
| 50 |     The general purpose value (and the associated types) are: | 
| 51 |  | 
| 52 |     \value SerialPortNameParameter   This parameter holds the serial port used for | 
| 53 |                                      device communication, e.g. COM1. \c QString | 
| 54 |     \value SerialParityParameter     This parameter holds the parity checking mode. | 
| 55 |                                      \c QSerialPort::Parity | 
| 56 |     \value SerialBaudRateParameter   This parameter holds the data baud rate for | 
| 57 |                                      the communication. \c QSerialPort::BaudRate | 
| 58 |     \value SerialDataBitsParameter   This parameter holds the data bits in a frame. | 
| 59 |                                      \c QSerialPort::DataBits | 
| 60 |     \value SerialStopBitsParameter   This parameter holds the number of stop bits in a | 
| 61 |                                      frame. \c QSerialPort::StopBits | 
| 62 |     \value NetworkPortParameter      This parameter holds the network port. \c int | 
| 63 |     \value NetworkAddressParameter   This parameter holds the host address for network | 
| 64 |                                      communication. \c QString | 
| 65 | */ | 
| 66 |  | 
| 67 | /*! | 
| 68 |     Returns the value associated with the given connection \a parameter. The | 
| 69 |     returned value can be empty. | 
| 70 |  | 
| 71 |     By default the \c QModbusDevice is initialized with some common values. The | 
| 72 |     serial port settings are even parity, a baud rate of 19200 bits per second, | 
| 73 |     eight data bits and one stop bit. The network settings for the host address | 
| 74 |     is set to local host and port to 502. | 
| 75 |  | 
| 76 |     \note For a serial connection to succeed, the \l SerialPortNameParameter | 
| 77 |     needs to be set to a valid communication port. The information about valid | 
| 78 |     serial ports can be obtained from \l QSerialPortInfo. | 
| 79 |  | 
| 80 |     \note If the device is already connected, the settings are taken into account | 
| 81 |     after reconnecting the device. | 
| 82 |  | 
| 83 |     \sa ConnectionParameter | 
| 84 | */ | 
| 85 | QVariant QModbusDevice::connectionParameter(ConnectionParameter parameter) const | 
| 86 | { | 
| 87 |     Q_D(const QModbusDevice); | 
| 88 |     switch (parameter) { | 
| 89 | #if QT_CONFIG(modbus_serialport) | 
| 90 |     case SerialPortNameParameter: | 
| 91 |         return d->m_comPort; | 
| 92 |     case SerialDataBitsParameter: | 
| 93 |         return d->m_dataBits; | 
| 94 |     case SerialParityParameter: | 
| 95 |         return d->m_parity; | 
| 96 |     case SerialStopBitsParameter: | 
| 97 |         return d->m_stopBits; | 
| 98 |     case SerialBaudRateParameter: | 
| 99 |         return d->m_baudRate; | 
| 100 | #endif | 
| 101 |     case NetworkPortParameter: | 
| 102 |         return d->m_networkPort; | 
| 103 |     case NetworkAddressParameter: | 
| 104 |         return d->m_networkAddress; | 
| 105 |     default: | 
| 106 |         break; | 
| 107 |     } | 
| 108 |     return {}; | 
| 109 | } | 
| 110 |  | 
| 111 | /*! | 
| 112 |     Sets the value of \a parameter to \a value. If the \a parameter already | 
| 113 |     exists, the previous value is overwritten. A active or running connection | 
| 114 |     is not affected by such parameter changes. | 
| 115 |  | 
| 116 |     \sa ConnectionParameter | 
| 117 |     \sa connectionParameter() | 
| 118 | */ | 
| 119 | void QModbusDevice::setConnectionParameter(ConnectionParameter parameter, const QVariant &value) | 
| 120 | { | 
| 121 |     Q_D(QModbusDevice); | 
| 122 |     switch (parameter) { | 
| 123 | #if QT_CONFIG(modbus_serialport) | 
| 124 |     case SerialPortNameParameter: | 
| 125 |         d->m_comPort = value.toString(); | 
| 126 |         break; | 
| 127 |     case SerialDataBitsParameter: | 
| 128 |         d->m_dataBits = QSerialPort::DataBits(value.toInt()); | 
| 129 |         break; | 
| 130 |     case SerialParityParameter: | 
| 131 |         d->m_parity = QSerialPort::Parity(value.toInt()); | 
| 132 |         break; | 
| 133 |     case SerialStopBitsParameter: | 
| 134 |         d->m_stopBits = QSerialPort::StopBits(value.toInt()); | 
| 135 |         break; | 
| 136 |     case SerialBaudRateParameter: | 
| 137 |         d->m_baudRate = QSerialPort::BaudRate(value.toInt()); | 
| 138 |         break; | 
| 139 | #endif | 
| 140 |     case NetworkPortParameter: | 
| 141 |         d->m_networkPort = value.toInt(); | 
| 142 |         break; | 
| 143 |     case NetworkAddressParameter: | 
| 144 |         d->m_networkAddress = value.toString(); | 
| 145 |         break; | 
| 146 |     default: | 
| 147 |         Q_ASSERT_X(false, "" , "Connection parameter not supported." ); | 
| 148 |         break; | 
| 149 |     } | 
| 150 | } | 
| 151 |  | 
| 152 | /*! | 
| 153 |     \enum QModbusDevice::Error | 
| 154 |     This enum describes all the possible error conditions. | 
| 155 |  | 
| 156 |     \value NoError              No errors have occurred. | 
| 157 |     \value ReadError            An error occurred during a read operation. | 
| 158 |     \value WriteError           An error occurred during a write operation. | 
| 159 |     \value ConnectionError      An error occurred when attempting to open the | 
| 160 |                                 backend. | 
| 161 |     \value ConfigurationError   An error occurred when attempting to set a | 
| 162 |                                 configuration parameter. | 
| 163 |     \value TimeoutError         A timeout occurred during I/O. An I/O operation | 
| 164 |                                 did not finish within a given time frame. | 
| 165 |     \value ProtocolError        A Modbus specific protocol error occurred. | 
| 166 |     \value ReplyAbortedError    The reply was aborted due to a disconnection of | 
| 167 |                                 the device. | 
| 168 |     \value UnknownError         An unknown error occurred. | 
| 169 |     \value [since 6.4] InvalidResponseError An error occurred while parsing the | 
| 170 |                                 response, or the \l {QModbusPdu::}{FunctionCode} | 
| 171 |                                 is not supported by the current implementation. | 
| 172 |                                 In the latter case custom Modbus client | 
| 173 |                                 implementation can override the | 
| 174 |                                 \l {QModbusClient::}{processResponse()} and | 
| 175 |                                 \l {QModbusClient::}{processPrivateResponse()} | 
| 176 |                                 methods to provide support for needed functions. | 
| 177 | */ | 
| 178 |  | 
| 179 | /*! | 
| 180 |     \enum QModbusDevice::State | 
| 181 |     This enum describes all possible device states. | 
| 182 |  | 
| 183 |     \value UnconnectedState The device is disconnected. | 
| 184 |     \value ConnectingState  The device is being connected. | 
| 185 |     \value ConnectedState   The device is connected to the Modbus network. | 
| 186 |     \value ClosingState     The device is being closed. | 
| 187 | */ | 
| 188 |  | 
| 189 | /*! | 
| 190 |     \since 6.0 | 
| 191 |     \enum QModbusDevice::IntermediateError | 
| 192 |  | 
| 193 |     This enum describes possible errors that can happen during a full send and | 
| 194 |     receive cycle for a Modbus reply. | 
| 195 |  | 
| 196 |     \value ResponseCrcError         A Modbus response with a wrong CRC was received. | 
| 197 |     \value ResponseRequestMismatch  A Modbus response was received but did not | 
| 198 |                                     match the open request, probably due to the | 
| 199 |                                     PDU's function code not matching. | 
| 200 |  | 
| 201 |     If any of the above intermediate errors occurred, the frame is likely | 
| 202 |     resent until the maximum number of retries has been reached. | 
| 203 |  | 
| 204 |     The list of intermediate errors can be inspected from the \l QModbusReply | 
| 205 |     intermediate errors function. | 
| 206 |  | 
| 207 |     \sa QModbusClient::numberOfRetries(), QModbusReply::intermediateErrors() | 
| 208 | */ | 
| 209 |  | 
| 210 | /*! | 
| 211 |     \fn QModbusDevice::errorOccurred(QModbusDevice::Error error) | 
| 212 |  | 
| 213 |     This signal is emitted when an error of the type, \a error, occurs. | 
| 214 | */ | 
| 215 |  | 
| 216 | /*! | 
| 217 |     \fn void QModbusDevice::stateChanged(QModbusDevice::State state) | 
| 218 |  | 
| 219 |     This signal is emitted every time the state of the device changes. | 
| 220 |     The new state is represented by \a state. | 
| 221 |  | 
| 222 |     \sa setState(), state() | 
| 223 | */ | 
| 224 |  | 
| 225 | /*! | 
| 226 |     Connects the device to the Modbus network. Returns \c true if the connection | 
| 227 |     process was successfully initiated; otherwise \c false. Final connection | 
| 228 |     success confirmation requires the \l state() changing to \l QModbusDevice::ConnectedState. | 
| 229 |  | 
| 230 |  | 
| 231 |     This function calls \l open() as part of its implementation. | 
| 232 |  | 
| 233 |     \sa open() | 
| 234 | */ | 
| 235 | bool QModbusDevice::connectDevice() | 
| 236 | { | 
| 237 |     Q_D(QModbusDevice); | 
| 238 |  | 
| 239 |     if (d->state != QModbusDevice::UnconnectedState) | 
| 240 |         return false; | 
| 241 |  | 
| 242 |     setState(ConnectingState); | 
| 243 |  | 
| 244 |     if (!open()) { | 
| 245 |         setState(UnconnectedState); | 
| 246 |         return false; | 
| 247 |     } | 
| 248 |  | 
| 249 |     //Connected is set by backend -> might be delayed by event loop | 
| 250 |     return true; | 
| 251 | } | 
| 252 |  | 
| 253 | /*! | 
| 254 |     Disconnects the device. | 
| 255 |  | 
| 256 |     This function calls \l close() as part of its implementation. | 
| 257 | */ | 
| 258 | void QModbusDevice::disconnectDevice() | 
| 259 | { | 
| 260 |     if (state() == QModbusDevice::UnconnectedState) | 
| 261 |         return; | 
| 262 |  | 
| 263 |     setState(QModbusDevice::ClosingState); | 
| 264 |  | 
| 265 |     //Unconnected is set by backend -> might be delayed by event loop | 
| 266 |     close(); | 
| 267 | } | 
| 268 |  | 
| 269 | /*! | 
| 270 |     Sets the state of the device to \a newState. Modbus device implementations | 
| 271 |     must use this function to update the device state. | 
| 272 | */ | 
| 273 | void QModbusDevice::setState(QModbusDevice::State newState) | 
| 274 | { | 
| 275 |     Q_D(QModbusDevice); | 
| 276 |  | 
| 277 |     if (newState == d->state) | 
| 278 |         return; | 
| 279 |  | 
| 280 |     d->state = newState; | 
| 281 |     emit stateChanged(state: newState); | 
| 282 | } | 
| 283 |  | 
| 284 | /*! | 
| 285 |     Returns the current state of the device. | 
| 286 |  | 
| 287 |     \sa setState(), stateChanged() | 
| 288 | */ | 
| 289 | QModbusDevice::State QModbusDevice::state() const | 
| 290 | { | 
| 291 |     return d_func()->state; | 
| 292 | } | 
| 293 |  | 
| 294 | /*! | 
| 295 |     Sets the error state of the device. ModBus device implementations | 
| 296 |     must use this function in case of an error to set the \a error type and | 
| 297 |     a descriptive \a errorText. | 
| 298 |  | 
| 299 |     \sa QModbusDevice::Error | 
| 300 | */ | 
| 301 | void QModbusDevice::setError(const QString &errorText, QModbusDevice::Error error) | 
| 302 | { | 
| 303 |     Q_D(QModbusDevice); | 
| 304 |  | 
| 305 |     d->error = error; | 
| 306 |     d->errorString = errorText; | 
| 307 |     emit errorOccurred(error); | 
| 308 | } | 
| 309 |  | 
| 310 | /*! | 
| 311 |     Returns the error state of the device. | 
| 312 |  | 
| 313 |     \sa QModbusDevice::Error | 
| 314 | */ | 
| 315 | QModbusDevice::Error QModbusDevice::error() const | 
| 316 | { | 
| 317 |     return d_func()->error; | 
| 318 | } | 
| 319 |  | 
| 320 | /*! | 
| 321 |     Returns descriptive error text for the device error. | 
| 322 |  | 
| 323 |     \sa QModbusDevice::Error | 
| 324 | */ | 
| 325 | QString QModbusDevice::errorString() const | 
| 326 | { | 
| 327 |     return d_func()->errorString; | 
| 328 | } | 
| 329 |  | 
| 330 | /*! | 
| 331 |     \since 5.14 | 
| 332 |  | 
| 333 |     Returns the underlying \l QIODevice used for ModBus communication or | 
| 334 |     \c nullptr if the device was not yet fully initialized. | 
| 335 |  | 
| 336 |     \note Do not store a pointer to the underlying device, because it can be | 
| 337 |     invalidated at any point in time. | 
| 338 | */ | 
| 339 | QIODevice *QModbusDevice::device() const | 
| 340 | { | 
| 341 |     return d_func()->device(); | 
| 342 | } | 
| 343 |  | 
| 344 | /*! | 
| 345 |     \fn bool QModbusDevice::open() | 
| 346 |  | 
| 347 |     This function is called by connectDevice(). Subclasses must provide | 
| 348 |     an implementation that returns \c true on successful Modbus connection | 
| 349 |     or connection initiation; otherwise returns \c false. | 
| 350 |  | 
| 351 |     The implementation must ensure that the instance's \l state() | 
| 352 |     is set to \l QModbusDevice::ConnectingState or \l QModbusDevice::ConnectedState upon success; otherwise | 
| 353 |     \l QModbusDevice::UnconnectedState. Typically, \l QModbusDevice::ConnectingState is used | 
| 354 |     when the connection process reports back asynchronously and \l QModbusDevice::ConnectedState | 
| 355 |     in case of synchronous connect behavior. | 
| 356 |  | 
| 357 |     \sa connectDevice() | 
| 358 | */ | 
| 359 |  | 
| 360 | /*! | 
| 361 |     \fn void QModbusDevice::close() | 
| 362 |  | 
| 363 |     This function is responsible for closing the Modbus connection. | 
| 364 |     The implementation must ensure that the instance's | 
| 365 |     \l state() is set to \l QModbusDevice::UnconnectedState. | 
| 366 |  | 
| 367 |     \sa disconnectDevice() | 
| 368 | */ | 
| 369 |  | 
| 370 | Q_LOGGING_CATEGORY(QT_MODBUS, "qt.modbus" ) | 
| 371 | Q_LOGGING_CATEGORY(QT_MODBUS_LOW, "qt.modbus.lowlevel" ) | 
| 372 |  | 
| 373 | QT_END_NAMESPACE | 
| 374 |  |