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