modbus_server_misc.c
Go to the documentation of this file.
1 /**
2  * @file modbus_server_misc.c
3  * @brief Helper functions for Modbus/TCP server
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_server.h"
36 #include "debug.h"
37 
38 //Check TCP/IP stack configuration
39 #if (MODBUS_SERVER_SUPPORT == ENABLED)
40 
41 
42 /**
43  * @brief Handle periodic operations
44  * @param[in] context Pointer to the Modbus/TCP server context
45  **/
46 
48 {
49  uint_t i;
51  ModbusClientConnection *connection;
52 
53  //Get current time
55 
56  //Loop through the connection table
57  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
58  {
59  //Point to the current entry
60  connection = &context->connection[i];
61 
62  //Check the state of the current connection
63  if(connection->state != MODBUS_CONNECTION_STATE_CLOSED)
64  {
65  //Disconnect inactive client after idle timeout
66  if(timeCompare(time, connection->timestamp + MODBUS_SERVER_TIMEOUT) >= 0)
67  {
68  //Debug message
69  TRACE_INFO("Modbus server: Closing inactive connection...\r\n");
70  //Close the Modbus/TCP connection
71  modbusServerCloseConnection(connection);
72  }
73  }
74  }
75 }
76 
77 
78 /**
79  * @brief Accept connection request
80  * @param[in] context Pointer to the Modbus/TCP server context
81  **/
82 
84 {
85  uint_t i;
86  Socket *socket;
87  IpAddr clientIpAddr;
88  uint16_t clientPort;
90  ModbusClientConnection *connection;
91  ModbusClientConnection *oldestConnection;
92 
93  //Get current time
95 
96  //Accept incoming connection
97  socket = socketAccept(context->socket, &clientIpAddr, &clientPort);
98 
99  //Make sure the socket handle is valid
100  if(socket != NULL)
101  {
102  //Debug message
103  TRACE_INFO("Modbus Server: Connection established with client %s port %" PRIu16 "...\r\n",
104  ipAddrToString(&clientIpAddr, NULL), clientPort);
105 
106  //Force the socket to operate in non-blocking mode
108 
109  //Keep track of the oldest connection
110  oldestConnection = NULL;
111 
112  //Loop through the connection table
113  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
114  {
115  //Point to the current entry
116  connection = &context->connection[i];
117 
118  //Check the state of the current connection
119  if(connection->state == MODBUS_CONNECTION_STATE_CLOSED)
120  {
121  //The current entry is available
122  break;
123  }
124  else
125  {
126  //Keep track of the oldest unused connection
127  if(oldestConnection == NULL)
128  {
129  oldestConnection = connection;
130  }
131  else if((time - connection->timestamp) > (time - oldestConnection->timestamp))
132  {
133  oldestConnection = connection;
134  }
135  }
136  }
137 
138  //The oldest unused connection is reused whenever the connection table
139  //runs out of space
141  {
142  //Close the oldest unused connection
143  modbusServerCloseConnection(oldestConnection);
144  //Reuse the connection
145  connection = oldestConnection;
146  }
147 
148  //Clear the structure describing the connection
149  memset(connection, 0, sizeof(ModbusClientConnection));
150 
151  //Attach Modbus/TCP server context
152  connection->context = context;
153  //Save socket handle
154  connection->socket = socket;
155  //Initialize time stamp
156  connection->timestamp = time;
157 
158  //Wait for incoming Modbus requests
160  }
161 }
162 
163 
164 /**
165  * @brief Connection event handler
166  * @param[in] connection Pointer to the client connection
167  **/
168 
170 {
171  //Valid connection?
172  if(connection != NULL)
173  {
174  //Debug message
175  TRACE_INFO("Modbus Server: Closing connection...\r\n");
176 
177  //Valid socket handle?
178  if(connection->socket != NULL)
179  {
180  //Close TCP socket
181  socketClose(connection->socket);
182  connection->socket = NULL;
183  }
184 
185  //Mark the connection as closed
187  }
188 }
189 
190 
191 /**
192  * @brief Connection event handler
193  * @param[in] context Pointer to the Modbus/TCP server context
194  * @param[in] connection Pointer to the client connection
195  **/
196 
198  ModbusClientConnection *connection)
199 {
200  error_t error;
201  size_t n;
202 
203  //Initialize status code
204  error = NO_ERROR;
205 
206  //Update time stamp
207  connection->timestamp = osGetSystemTime();
208 
209  //Check the state of the connection
210  if(connection->state == MODBUS_CONNECTION_STATE_RECEIVING)
211  {
212  //Receive Modbus request
213  if(connection->requestAduPos < sizeof(ModbusHeader))
214  {
215  //Receive more data
216  error = socketReceive(connection->socket,
217  connection->requestAdu + connection->requestAduPos,
218  sizeof(ModbusHeader) - connection->requestAduPos, &n, 0);
219 
220  //Check status code
221  if(!error)
222  {
223  //Advance data pointer
224  connection->requestAduPos += n;
225 
226  //MBAP header successfully received?
227  if(connection->requestAduPos >= sizeof(ModbusHeader))
228  {
229  //Parse MBAP header
230  error = modbusServerParseMbapHeader(connection);
231  }
232  }
233  }
234  else if(connection->requestAduPos < connection->requestAduLen)
235  {
236  //Receive more data
237  error = socketReceive(connection->socket,
238  connection->requestAdu + connection->requestAduPos,
239  connection->requestAduLen - connection->requestAduPos, &n, 0);
240 
241  //Check status code
242  if(!error)
243  {
244  //Advance data pointer
245  connection->requestAduPos += n;
246 
247  //Modbus request successfully received?
248  if(connection->requestAduPos >= connection->requestAduLen)
249  {
250  //Check unit identifier
251  if(context->settings.unitId == 0 ||
252  context->settings.unitId == 255 ||
253  context->settings.unitId == connection->requestUnitId)
254  {
255  //Process Modbus request
256  error = modbusServerProcessRequest(connection);
257  }
258  }
259  }
260  }
261  else
262  {
263  //Just for sanity
264  error = ERROR_WRONG_STATE;
265  }
266  }
267  else if(connection->state == MODBUS_CONNECTION_STATE_SENDING)
268  {
269  //Send Modbus response
270  if(connection->responseAduPos < connection->responseAduLen)
271  {
272  //Send more data
273  error = socketSend(connection->socket,
274  connection->responseAdu + connection->responseAduPos,
275  connection->responseAduLen - connection->responseAduPos,
277 
278  //Check status code
279  if(!error)
280  {
281  //Advance data pointer
282  connection->responseAduPos += n;
283 
284  //Modbus response successfully sent?
285  if(connection->responseAduPos >= connection->responseAduLen)
286  {
287  //Flush receive buffer
288  connection->requestAduLen = 0;
289  connection->requestAduPos = 0;
290 
291  //Wait for the next Modbus request
293  }
294  }
295  }
296  else
297  {
298  //Just for sanity
299  error = ERROR_WRONG_STATE;
300  }
301  }
302  else
303  {
304  //Invalid state
305  error = ERROR_WRONG_STATE;
306  }
307 
308  //Any communication error?
309  if(error != NO_ERROR && error != ERROR_TIMEOUT)
310  {
311  //Close the Modbus/TCP connection
312  modbusServerCloseConnection(connection);
313  }
314 }
315 
316 
317 /**
318  * @brief Parse request MBAP header
319  * @param[in] connection Pointer to the client connection
320  * @return Error code
321  **/
322 
324 {
325  size_t n;
326  ModbusHeader *requestHeader;
327 
328  //Sanity check
329  if(connection->requestAduPos < sizeof(ModbusHeader))
330  return ERROR_INVALID_LENGTH;
331 
332  //Point to the beginning of the request ADU
333  requestHeader = (ModbusHeader *) connection->requestAdu;
334 
335  //The length field is a byte count of the following fields, including
336  //the unit identifier and data fields
337  n = ntohs(requestHeader->length);
338 
339  //Malformed Modbus request?
340  if(n < sizeof(uint8_t))
341  return ERROR_INVALID_LENGTH;
342 
343  //Retrieve the length of the PDU
344  n -= sizeof(uint8_t);
345 
346  //Debug message
347  TRACE_DEBUG("\r\nModbus Server: ADU received (%" PRIuSIZE " bytes)...\r\n",
348  sizeof(ModbusHeader) + n);
349 
350  //Dump MBAP header
351  TRACE_DEBUG(" Transaction ID = %" PRIu16 "\r\n", ntohs(requestHeader->transactionId));
352  TRACE_DEBUG(" Protocol ID = %" PRIu16 "\r\n", ntohs(requestHeader->protocolId));
353  TRACE_DEBUG(" Length = %" PRIu16 "\r\n", ntohs(requestHeader->length));
354  TRACE_DEBUG(" Unit ID = %" PRIu16 "\r\n", requestHeader->unitId);
355 
356  //Check protocol identifier
357  if(ntohs(requestHeader->protocolId) != MODBUS_PROTOCOL_ID)
358  return ERROR_WRONG_IDENTIFIER;
359 
360  //The length of the Modbus PDU is limited to 253 bytes
361  if(n > MODBUS_MAX_PDU_SIZE)
362  return ERROR_INVALID_LENGTH;
363 
364  //Save unit identifier
365  connection->requestUnitId = requestHeader->unitId;
366  //Compute the length of the request ADU
367  connection->requestAduLen = sizeof(ModbusHeader) + n;
368 
369  //Successful processing
370  return NO_ERROR;
371 }
372 
373 
374 /**
375  * @brief Format response MBAP header
376  * @param[in] connection Pointer to the client connection
377  * @param[in] length Length of the PDU, in bytes
378  * @return Error code
379  **/
380 
382  size_t length)
383 {
384  ModbusHeader *requestHeader;
385  ModbusHeader *responseHeader;
386 
387  //Sanity check
388  if(connection->requestAduPos < sizeof(ModbusHeader))
389  return ERROR_INVALID_LENGTH;
390 
391  //Point to the beginning of the request ADU
392  requestHeader = (ModbusHeader *) connection->requestAdu;
393  //Point to the beginning of the response ADU
394  responseHeader = (ModbusHeader *) connection->responseAdu;
395 
396  //Format MBAP header
397  responseHeader->transactionId = requestHeader->transactionId;
398  responseHeader->protocolId = requestHeader->protocolId;
399  responseHeader->length = htons(length + sizeof(uint8_t));
400  responseHeader->unitId = requestHeader->unitId;
401 
402  //Compute the length of the response ADU
403  connection->responseAduLen = sizeof(ModbusHeader) + length;
404 
405  //Debug message
406  TRACE_DEBUG("Modbus Server: Sending ADU (%" PRIuSIZE " bytes)...\r\n",
407  connection->responseAduLen);
408 
409  //Dump MBAP header
410  TRACE_DEBUG(" Transaction ID = %" PRIu16 "\r\n", ntohs(responseHeader->transactionId));
411  TRACE_DEBUG(" Protocol ID = %" PRIu16 "\r\n", ntohs(responseHeader->protocolId));
412  TRACE_DEBUG(" Length = %" PRIu16 "\r\n", ntohs(responseHeader->length));
413  TRACE_DEBUG(" Unit ID = %" PRIu16 "\r\n", responseHeader->unitId);
414 
415  //Rewind to the beginning of the transmit buffer
416  connection->responseAduPos = 0;
417  //Send the response ADU to the client
419 
420  //Successful processing
421  return NO_ERROR;
422 }
423 
424 
425 /**
426  * @brief Retrieve request PDU
427  * @param[in] connection Pointer to the client connection
428  * @param[out] length Length request the request PDU, in bytes
429  * @return Pointer to the request PDU
430  **/
431 
433  size_t *length)
434 {
435  uint8_t *requestPdu;
436 
437  //Point to the request PDU
438  requestPdu = connection->requestAdu + sizeof(ModbusHeader);
439 
440  //Retrieve the length of the PDU
441  if(connection->requestAduLen >= sizeof(ModbusHeader))
442  *length = connection->requestAduLen - sizeof(ModbusHeader);
443  else
444  *length = 0;
445 
446  //Return a pointer to the request PDU
447  return requestPdu;
448 }
449 
450 
451 /**
452  * @brief Retrieve response PDU
453  * @param[in] connection Pointer to the client connection
454  * @return Pointer to the response PDU
455  **/
456 
458 {
459  //Point to the response PDU
460  return connection->responseAdu + sizeof(ModbusHeader);
461 }
462 
463 
464 /**
465  * @brief Lock Modbus table
466  * @param[in] context Pointer to the Modbus/TCP server context
467  **/
468 
470 {
471  //Any registered callback?
472  if(context->settings.lockCallback != NULL)
473  {
474  //Invoke user callback function
475  context->settings.lockCallback();
476  }
477 }
478 
479 
480 /**
481  * @brief Unlock Modbus table
482  * @param[in] context Pointer to the Modbus/TCP server context
483  **/
484 
486 {
487  //Any registered callback?
488  if(context->settings.lockCallback != NULL)
489  {
490  //Invoke user callback function
491  context->settings.unlockCallback();
492  }
493 }
494 
495 
496 /**
497  * @brief Get coil state
498  * @param[in] context Pointer to the Modbus/TCP server context
499  * @param[in] address Coil address
500  * @param[out] state Current coil state
501  * @return Error code
502  **/
503 
505  bool_t *state)
506 {
507  error_t error;
508 
509  //Any registered callback?
510  if(context->settings.readCoilCallback!= NULL)
511  {
512  //Invoke user callback function
513  error = context->settings.readCoilCallback(address, state);
514  }
515  else
516  {
517  //Report an error
518  error = ERROR_INVALID_ADDRESS;
519  }
520 
521  //Return status code
522  return error;
523 }
524 
525 
526 /**
527  * @brief Set coil state
528  * @param[in] context Pointer to the Modbus/TCP server context
529  * @param[in] address Address of the coil
530  * @param[in] state Desired coil state
531  * @param[in] commit This flag indicates the current phase (validation phase
532  * or write phase if the validation was successful)
533  * @return Error code
534  **/
535 
537  bool_t state, bool_t commit)
538 {
539  error_t error;
540 
541  //Any registered callback?
542  if(context->settings.writeCoilCallback!= NULL)
543  {
544  //Invoke user callback function
545  error = context->settings.writeCoilCallback(address, state, commit);
546  }
547  else
548  {
549  //Report an error
550  error = ERROR_INVALID_ADDRESS;
551  }
552 
553  //Return status code
554  return error;
555 }
556 
557 
558 /**
559  * @brief Get register value
560  * @param[in] context Pointer to the Modbus/TCP server context
561  * @param[in] address Register address
562  * @param[out] value Current register value
563  * @return Error code
564  **/
565 
567  uint16_t *value)
568 {
569  error_t error;
570 
571  //Any registered callback?
572  if(context->settings.readRegCallback!= NULL)
573  {
574  //Invoke user callback function
575  error = context->settings.readRegCallback(address, value);
576  }
577  else
578  {
579  //Report an error
580  error = ERROR_INVALID_ADDRESS;
581  }
582 
583  //Return status code
584  return error;
585 }
586 
587 
588 /**
589  * @brief Set register value
590  * @param[in] context Pointer to the Modbus/TCP server context
591  * @param[in] address Register address
592  * @param[in] value Desired register value
593  * @param[in] commit This flag indicates the current phase (validation phase
594  * or write phase if the validation was successful)
595  * @return Error code
596  **/
597 
599  uint16_t value, bool_t commit)
600 {
601  error_t error;
602 
603  //Any registered callback?
604  if(context->settings.writeRegValueCallback!= NULL)
605  {
606  //Invoke user callback function
607  error = context->settings.writeRegValueCallback(address, value, commit);
608  }
609  else
610  {
611  //Report an error
612  error = ERROR_INVALID_ADDRESS;
613  }
614 
615  //Return status code
616  return error;
617 }
618 
619 
620 /**
621  * @brief Translate exception code
622  * @param[in] status Status code
623  * @return Exception code
624  **/
625 
627 {
629 
630  //Check status code
631  switch(status)
632  {
634  //The function code received in the query is not an allowable action
635  //for the server
637  break;
639  //The data address received in the query is not an allowable address
640  //for the server
642  break;
643  case ERROR_INVALID_VALUE:
644  //A value contained in the query data field is not an allowable value
645  //for the server
647  break;
648  case ERROR_DEVICE_BUSY:
649  //The client should retransmit the message later when the server is free
651  break;
652  default:
653  //An unrecoverable error occurred while the server was attempting to
654  //perform the requested action
656  break;
657  }
658 
659  //Return exception code
660  return exceptionCode;
661 }
662 
663 #endif
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
Modbus/TCP server.
#define timeCompare(t1, t2)
Definition: os_port.h:40
void modbusServerAcceptConnection(ModbusServerContext *context)
Accept connection request.
systime_t osGetSystemTime(void)
Retrieve system time.
uint32_t time
systime_t timestamp
Time stamp.
Debugging facilities.
Socket * socketAccept(Socket *socket, IpAddr *clientIpAddr, uint16_t *clientPort)
Permit an incoming connection attempt on a socket.
Definition: socket.c:457
size_t requestAduPos
Current position in the request ADU.
void * modbusServerGetRequestPdu(ModbusClientConnection *connection, size_t *length)
Retrieve request PDU.
size_t responseAduLen
Length of the response ADU, in bytes.
IP network address.
Definition: ip.h:57
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:797
char_t * ipAddrToString(const IpAddr *ipAddr, char_t *str)
Convert a binary IP address to a string representation.
Definition: ip.c:685
ModbusServerContext * context
Modbus/TCP server context.
error_t modbusServerReadCoil(ModbusServerContext *context, uint16_t address, bool_t *state)
Get coil state.
void modbusServerLock(ModbusServerContext *context)
Lock Modbus table.
Modbus PDU processing.
#define htons(value)
Definition: cpu_endian.h:390
#define MODBUS_MAX_PDU_SIZE
Definition: modbus_common.h:43
void * modbusServerGetResponsePdu(ModbusClientConnection *connection)
Retrieve response PDU.
int_t socket(int_t family, int_t type, int_t protocol)
Create a socket that is bound to a specific transport service provider.
Definition: bsd_socket.c:106
void modbusServerCloseConnection(ModbusClientConnection *connection)
Connection event handler.
ModbusConnectionState state
Connection state.
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:216
#define Socket
Definition: socket.h:34
Socket * socket
Underlying socket.
error_t modbusServerWriteReg(ModbusServerContext *context, uint16_t address, uint16_t value, bool_t commit)
Set register value.
#define MODBUS_PROTOCOL_ID
Definition: modbus_common.h:38
uint8_t requestAdu[MODBUS_MAX_ADU_SIZE]
Request ADU.
#define ntohs(value)
Definition: cpu_endian.h:396
size_t responseAduPos
Current position in the response ADU.
ModbusExceptionCode
Modbus exception codes.
Definition: modbus_common.h:97
void modbusServerUnlock(ModbusServerContext *context)
Unlock Modbus table.
ModbusExceptionCode modbusServerTranslateExceptionCode(error_t status)
Translate exception code.
error_t modbusServerFormatMbapHeader(ModbusClientConnection *connection, size_t length)
Format response MBAP header.
Modbus/TCP client connection.
uint8_t requestUnitId
Unit identifier.
Helper functions for Modbus/TCP server.
error_t modbusServerProcessRequest(ModbusClientConnection *connection)
Process Modbus request.
uint8_t responseAdu[MODBUS_MAX_ADU_SIZE]
Response ADU.
#define TRACE_INFO(...)
Definition: debug.h:86
Success.
Definition: error.h:42
Ipv6Addr address
#define ModbusServerContext
Definition: modbus_server.h:78
error_t
Error codes.
Definition: error.h:40
unsigned int uint_t
Definition: compiler_port.h:43
#define PRIuSIZE
Definition: compiler_port.h:72
uint8_t value[]
Definition: dtls_misc.h:141
error_t modbusServerWriteCoil(ModbusServerContext *context, uint16_t address, bool_t state, bool_t commit)
Set coil state.
uint8_t exceptionCode
error_t modbusServerParseMbapHeader(ModbusClientConnection *connection)
Parse request MBAP header.
__start_packed struct @211 ModbusHeader
MBAP header (Modbus Application Protocol)
size_t requestAduLen
Length of the request ADU, in bytes.
void modbusServerTick(ModbusServerContext *context)
Handle periodic operations.
#define MODBUS_SERVER_MAX_CONNECTIONS
Definition: modbus_server.h:57
void modbusServerProcessConnectionEvents(ModbusServerContext *context, ModbusClientConnection *connection)
Connection event handler.
#define MODBUS_SERVER_TIMEOUT
Definition: modbus_server.h:64
uint8_t length
Definition: dtls_misc.h:140
uint8_t n
error_t modbusServerReadReg(ModbusServerContext *context, uint16_t address, uint16_t *value)
Get register value.
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
int bool_t
Definition: compiler_port.h:47
#define TRACE_DEBUG(...)
Definition: debug.h:98