modbus_client_misc.c
Go to the documentation of this file.
1 /**
2  * @file modbus_client_misc.c
3  * @brief Helper functions for Modbus/TCP client
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2024 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.4.4
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL MODBUS_TRACE_LEVEL
33 
34 //Dependencies
35 #include "modbus/modbus_client.h"
39 #include "modbus/modbus_debug.h"
40 #include "debug.h"
41 
42 //Check TCP/IP stack configuration
43 #if (MODBUS_CLIENT_SUPPORT == ENABLED)
44 
45 
46 /**
47  * @brief Perform Modbus transaction
48  * @param[in] context Pointer to the Modbus/TCP client context
49  * @return Error code
50  **/
51 
53 {
54  error_t error;
55  size_t n;
57  uint8_t *pdu;
58 
59  //Initialize status code
60  error = NO_ERROR;
61 
62  //Get current time
64 
65  //Adjust timeout
66  if(timeCompare(context->timestamp + context->timeout, time) > 0)
67  {
68  socketSetTimeout(context->socket, context->timestamp +
69  context->timeout - time);
70  }
71  else
72  {
73  socketSetTimeout(context->socket, 0);
74  }
75 
76  //Check current state
77  if(context->state == MODBUS_CLIENT_STATE_SENDING)
78  {
79  //Send Modbus request
80  if(context->requestAduPos < context->requestAduLen)
81  {
82  //Send more data
83  error = modbusClientSendData(context,
84  context->requestAdu + context->requestAduPos,
85  context->requestAduLen - context->requestAduPos,
87 
88  //Check status code
89  if(error == NO_ERROR || error == ERROR_TIMEOUT)
90  {
91  //Advance data pointer
92  context->requestAduPos += n;
93  }
94  }
95  else
96  {
97  //Flush receive buffer
98  context->responseAduLen = 0;
99  context->responseAduPos = 0;
100 
101  //Wait for response ADU
102  context->state = MODBUS_CLIENT_STATE_RECEIVING;
103  }
104  }
105  else if(context->state == MODBUS_CLIENT_STATE_RECEIVING)
106  {
107  //Receive Modbus response
108  if(context->responseAduPos < sizeof(ModbusHeader))
109  {
110  //Receive more data
111  error = modbusClientReceiveData(context,
112  context->responseAdu + context->responseAduPos,
113  sizeof(ModbusHeader) - context->responseAduPos, &n, 0);
114 
115  //Check status code
116  if(error == NO_ERROR)
117  {
118  //Advance data pointer
119  context->responseAduPos += n;
120 
121  //MBAP header successfully received?
122  if(context->responseAduPos >= sizeof(ModbusHeader))
123  {
124  //Parse MBAP header
125  error = modbusClientParseMbapHeader(context);
126  }
127  }
128  }
129  else if(context->responseAduPos < context->responseAduLen)
130  {
131  //Receive more data
132  error = modbusClientReceiveData(context,
133  context->responseAdu + context->responseAduPos,
134  context->responseAduLen - context->responseAduPos, &n, 0);
135 
136  //Check status code
137  if(error == NO_ERROR)
138  {
139  //Advance data pointer
140  context->responseAduPos += n;
141  }
142  }
143  else
144  {
145  //Point to the Modbus response PDU
146  pdu = modbusClientGetResponsePdu(context, &n);
147 
148  //Debug message
149  TRACE_INFO("Modbus Client: Response PDU received (%" PRIuSIZE " bytes)...\r\n", n);
150  //Dump the contents of the PDU for debugging purpose
152 
153  //Check whether the received response matches the request
154  error = modbusClientCheckResp(context);
155 
156  //Check status code
157  if(error == NO_ERROR)
158  {
159  //If the transaction identifier refers to a pending transaction,
160  //the response must be parsed in order to send a confirmation to
161  //the user application
162  context->state = MODBUS_CLIENT_STATE_COMPLETE;
163  }
164  else if(error == ERROR_WRONG_IDENTIFIER)
165  {
166  //If the transaction identifier does not refer to any pending
167  //transaction, the response must be discarded
168  context->responseAduLen = 0;
169  context->responseAduPos = 0;
170 
171  //Catch exception
172  error = NO_ERROR;
173  }
174  else
175  {
176  //A protocol error has occurred
177  }
178  }
179  }
180  else
181  {
182  //Report an error
183  error = ERROR_WRONG_STATE;
184  }
185 
186  //Check status code
187  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
188  {
189  //Check whether the timeout has elapsed
190  error = modbusClientCheckTimeout(context);
191  }
192 
193  //Modbus transaction failed?
194  if(error != NO_ERROR && error != ERROR_WOULD_BLOCK)
195  {
196  //Revert to default state
197  context->state = MODBUS_CLIENT_STATE_CONNECTED;
198  }
199 
200  //Return status code
201  return error;
202 }
203 
204 
205 /**
206  * @brief Check whether the received response matches the request
207  * @param[in] context Pointer to the Modbus/TCP client context
208  * @return Error code
209  **/
210 
212 {
213  error_t error;
214  ModbusHeader *requestHeader;
215  ModbusHeader *responseHeader;
216 
217  //Malformed request?
218  if(context->requestAduLen < (sizeof(ModbusHeader) + sizeof(uint8_t)))
219  return ERROR_INVALID_LENGTH;
220 
221  //Malformed response?
222  if(context->responseAduLen < (sizeof(ModbusHeader) + sizeof(uint8_t)))
223  return ERROR_INVALID_LENGTH;
224 
225  //Point to the MBAP header of the Modbus request
226  requestHeader = (ModbusHeader *) context->requestAdu;
227  //Point to the MBAP header of the Modbus response
228  responseHeader = (ModbusHeader *) context->responseAdu;
229 
230  //Check transaction identifier
231  if(responseHeader->transactionId != requestHeader->transactionId)
232  return ERROR_WRONG_IDENTIFIER;
233 
234  //Check unit identifier
235  if(responseHeader->unitId != requestHeader->unitId)
237 
238  //Check function code
239  if((responseHeader->pdu[0] & MODBUS_FUNCTION_CODE_MASK) !=
240  (requestHeader->pdu[0] & MODBUS_FUNCTION_CODE_MASK))
241  {
243  }
244 
245  //Exception response?
246  if((responseHeader->pdu[0] & MODBUS_EXCEPTION_MASK) != 0)
247  {
248  //If the server receives the request without a communication error,
249  //but cannot handle it, the server will return an exception response
250  //informing the client of the nature of the error
251  error = modbusClientParseExceptionResp(context);
252  }
253  else
254  {
255  //A normal response has been received
256  error = NO_ERROR;
257  }
258 
259  //Return status code
260  return error;
261 }
262 
263 
264 /**
265  * @brief Format MBAP header
266  * @param[in] context Pointer to the Modbus/TCP client context
267  * @param[in] length Length of the PDU, in bytes
268  * @return Error code
269  **/
270 
272  size_t length)
273 {
274  ModbusHeader *header;
275 
276  //Point to the beginning of the request ADU
277  header = (ModbusHeader *) context->requestAdu;
278 
279  //The transaction identifier is used to uniquely identify the matching
280  //requests and responses
281  context->transactionId++;
282 
283  //Format MBAP header
284  header->transactionId = htons(context->transactionId);
285  header->protocolId = HTONS(MODBUS_PROTOCOL_ID);
286  header->length = htons(length + sizeof(uint8_t));
287  header->unitId = context->unitId;
288 
289  //Compute the length of the request ADU
290  context->requestAduLen = length + sizeof(ModbusHeader);
291 
292  //Debug message
293  TRACE_DEBUG("Modbus Client: Sending ADU (%" PRIuSIZE " bytes)...\r\n",
294  context->requestAduLen);
295 
296  //Dump MBAP header
297  TRACE_DEBUG(" Transaction ID = %" PRIu16 "\r\n", ntohs(header->transactionId));
298  TRACE_DEBUG(" Protocol ID = %" PRIu16 "\r\n", ntohs(header->protocolId));
299  TRACE_DEBUG(" Length = %" PRIu16 "\r\n", ntohs(header->length));
300  TRACE_DEBUG(" Unit ID = %" PRIu16 "\r\n", header->unitId);
301 
302  //Rewind to the beginning of the transmit buffer
303  context->requestAduPos = 0;
304  //Save current time
305  context->timestamp = osGetSystemTime();
306  //Send the request ADU to the server
307  context->state = MODBUS_CLIENT_STATE_SENDING;
308 
309  //Successful processing
310  return NO_ERROR;
311 }
312 
313 
314 /**
315  * @brief Parse MBAP header
316  * @param[in] context Pointer to the Modbus/TCP client context
317  * @return Error code
318  **/
319 
321 {
322  size_t n;
323  ModbusHeader *header;
324 
325  //Sanity check
326  if(context->responseAduPos < sizeof(ModbusHeader))
327  return ERROR_INVALID_LENGTH;
328 
329  //Point to the beginning of the response ADU
330  header = (ModbusHeader *) context->responseAdu;
331 
332  //The length field is a byte count of the following fields, including the
333  //unit identifier and data fields
334  n = ntohs(header->length);
335 
336  //Malformed Modbus response?
337  if(n < sizeof(uint8_t))
338  return ERROR_INVALID_LENGTH;
339 
340  //Retrieve the length of the PDU
341  n -= sizeof(uint8_t);
342 
343  //Debug message
344  TRACE_DEBUG("Modbus Client: ADU received (%" PRIuSIZE " bytes)...\r\n",
345  sizeof(ModbusHeader) + n);
346 
347  //Dump MBAP header
348  TRACE_DEBUG(" Transaction ID = %" PRIu16 "\r\n", ntohs(header->transactionId));
349  TRACE_DEBUG(" Protocol ID = %" PRIu16 "\r\n", ntohs(header->protocolId));
350  TRACE_DEBUG(" Length = %" PRIu16 "\r\n", ntohs(header->length));
351  TRACE_DEBUG(" Unit ID = %" PRIu16 "\r\n", header->unitId);
352 
353  //Check protocol identifier
354  if(ntohs(header->protocolId) != MODBUS_PROTOCOL_ID)
355  return ERROR_WRONG_IDENTIFIER;
356 
357  //The length of the Modbus PDU is limited to 253 bytes
358  if(n > MODBUS_MAX_PDU_SIZE)
359  return ERROR_INVALID_LENGTH;
360 
361  //Compute the length of the response ADU
362  context->responseAduLen = sizeof(ModbusHeader) + n;
363 
364  //Successful processing
365  return NO_ERROR;
366 }
367 
368 
369 /**
370  * @brief Retrieve request PDU
371  * @param[in] context Pointer to the Modbus/TCP client context
372  * @return Pointer to the request PDU
373  **/
374 
376 {
377  //Point to the request PDU
378  return context->requestAdu + sizeof(ModbusHeader);
379 }
380 
381 
382 /**
383  * @brief Retrieve response PDU
384  * @param[in] context Pointer to the Modbus/TCP client context
385  * @param[out] length Length of the response PDU, in bytes
386  * @return Pointer to the response PDU
387  **/
388 
390 {
391  uint8_t *responsePdu;
392 
393  //Point to the response PDU
394  responsePdu = context->responseAdu + sizeof(ModbusHeader);
395 
396  //Retrieve the length of the PDU
397  if(context->responseAduLen >= sizeof(ModbusHeader))
398  {
399  *length = context->responseAduLen - sizeof(ModbusHeader);
400  }
401  else
402  {
403  *length = 0;
404  }
405 
406  //Return a pointer to the response PDU
407  return responsePdu;
408 }
409 
410 
411 /**
412  * @brief Determine whether a timeout error has occurred
413  * @param[in] context Pointer to the Modbus/TCP client context
414  * @return Error code
415  **/
416 
418 {
419 #if (NET_RTOS_SUPPORT == DISABLED)
420  error_t error;
421  systime_t time;
422 
423  //Get current time
424  time = osGetSystemTime();
425 
426  //Check whether the timeout has elapsed
427  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
428  {
429  //Report a timeout error
430  error = ERROR_TIMEOUT;
431  }
432  else
433  {
434  //The operation would block
435  error = ERROR_WOULD_BLOCK;
436  }
437 
438  //Return status code
439  return error;
440 #else
441  //Report a timeout error
442  return ERROR_TIMEOUT;
443 #endif
444 }
445 
446 #endif
void * modbusClientGetResponsePdu(ModbusClientContext *context, size_t *length)
Retrieve response PDU.
#define htons(value)
Definition: cpu_endian.h:413
Modbus/TCP client.
error_t modbusClientSendData(ModbusClientContext *context, const void *data, size_t length, size_t *written, uint_t flags)
Send data using the relevant transport protocol.
@ ERROR_WOULD_BLOCK
Definition: error.h:96
error_t modbusClientCheckResp(ModbusClientContext *context)
Check whether the received response matches the request.
error_t modbusDumpResponsePdu(const void *pdu, size_t length)
Dump Modbus response PDU for debugging purpose.
Definition: modbus_debug.c:212
#define MODBUS_MAX_PDU_SIZE
Definition: modbus_common.h:48
error_t modbusClientParseExceptionResp(ModbusClientContext *context)
Parse Exception response.
#define MODBUS_FUNCTION_CODE_MASK
Definition: modbus_common.h:53
#define ModbusClientContext
Definition: modbus_client.h:86
#define timeCompare(t1, t2)
Definition: os_port.h:40
@ ERROR_WRONG_STATE
Definition: error.h:209
error_t
Error codes.
Definition: error.h:43
uint8_t pdu[]
@ MODBUS_CLIENT_STATE_SENDING
#define MODBUS_EXCEPTION_MASK
Definition: modbus_common.h:55
error_t modbusClientFormatMbapHeader(ModbusClientContext *context, size_t length)
Format MBAP header.
@ ERROR_INVALID_LENGTH
Definition: error.h:111
error_t modbusClientCheckTimeout(ModbusClientContext *context)
Determine whether a timeout error has occurred.
@ ERROR_UNEXPECTED_RESPONSE
Definition: error.h:70
#define TRACE_INFO(...)
Definition: debug.h:95
uint8_t length
Definition: tcp.h:368
@ MODBUS_CLIENT_STATE_RECEIVING
Modbus PDU formatting and parsing.
uint32_t systime_t
System time.
#define ntohs(value)
Definition: cpu_endian.h:421
ModbusHeader
#define TRACE_DEBUG(...)
Definition: debug.h:107
Transport protocol abstraction layer.
@ ERROR_TIMEOUT
Definition: error.h:95
uint32_t time
Data logging functions for debugging purpose (Modbus/TCP)
@ SOCKET_FLAG_NO_DELAY
Definition: socket.h:143
#define HTONS(value)
Definition: cpu_endian.h:410
uint8_t n
void * modbusClientGetRequestPdu(ModbusClientContext *context)
Retrieve request PDU.
@ ERROR_WRONG_IDENTIFIER
Definition: error.h:89
error_t modbusClientParseMbapHeader(ModbusClientContext *context)
Parse MBAP header.
error_t modbusClientReceiveData(ModbusClientContext *context, void *data, size_t size, size_t *received, uint_t flags)
Receive data using the relevant transport protocol.
#define PRIuSIZE
@ MODBUS_CLIENT_STATE_CONNECTED
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:148
Helper functions for Modbus/TCP client.
#define MODBUS_PROTOCOL_ID
Definition: modbus_common.h:43
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
@ MODBUS_CLIENT_STATE_COMPLETE
error_t modbusClientTransaction(ModbusClientContext *context)
Perform Modbus transaction.
systime_t osGetSystemTime(void)
Retrieve system time.