modbus_server.c
Go to the documentation of this file.
1 /**
2  * @file modbus_server.c
3  * @brief Modbus/TCP server
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2026 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.6.0
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL MODBUS_TRACE_LEVEL
33 
34 //Dependencies
35 #include "modbus/modbus_server.h"
38 #include "debug.h"
39 
40 //Check TCP/IP stack configuration
41 #if (MODBUS_SERVER_SUPPORT == ENABLED)
42 
43 
44 /**
45  * @brief Initialize settings with default values
46  * @param[out] settings Structure that contains Modbus/TCP server settings
47  **/
48 
50 {
51  //Default task parameters
52  settings->task = OS_TASK_DEFAULT_PARAMS;
55 
56  //TCP/IP stack context
57  settings->netContext = NULL;
58  //The Modbus/TCP server is not bound to any interface
59  settings->interface = NULL;
60 
61  //Modbus/TCP port number
62  settings->port = MODBUS_TCP_PORT;
63  //Default unit identifier
64  settings->unitId = MODBUS_DEFAULT_UNIT_ID;
65  //Idle connection timeout
66  settings->timeout = MODBUS_SERVER_TIMEOUT;
67 
68  //TCP connection open callback function
69  settings->openCallback = NULL;
70  //TCP connection close callback function
71  settings->closeCallback = NULL;
72 
73 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED)
74  //TLS initialization callback function
75  settings->tlsInitCallback = NULL;
76 #endif
77 
78  //Lock Modbus table callback function
79  settings->lockCallback = NULL;
80  //Unlock Modbus table callback function
81  settings->unlockCallback = NULL;
82  //Get coil state callback function
83  settings->readCoilCallback = NULL;
84  //Get discrete input state callback function
85  settings->readDiscreteInputCallback = NULL;
86  //Set coil state callback function
87  settings->writeCoilCallback = NULL;
88  //Get register value callback function
89  settings->readRegCallback = NULL;
90  //Get holding register value callback function
91  settings->readHoldingRegCallback = NULL;
92  //Get input register value callback function
93  settings->readInputRegCallback = NULL;
94  //Set register value callback function
95  settings->writeRegCallback = NULL;
96  //PDU processing callback
97  settings->processPduCallback = NULL;
98  //Tick callback function
99  settings->tickCallback = NULL;
100 }
101 
102 
103 /**
104  * @brief Initialize Modbus/TCP server context
105  * @param[in] context Pointer to the Modbus/TCP server context
106  * @param[in] settings Modbus/TCP server specific settings
107  * @return Error code
108  **/
109 
111  const ModbusServerSettings *settings)
112 {
113  error_t error;
114 
115  //Debug message
116  TRACE_INFO("Initializing Modbus/TCP server...\r\n");
117 
118  //Ensure the parameters are valid
119  if(context == NULL || settings == NULL)
121 
122  //Initialize status code
123  error = NO_ERROR;
124 
125  //Clear Modbus/TCP server context
126  osMemset(context, 0, sizeof(ModbusServerContext));
127 
128  //Initialize task parameters
129  context->taskParams = settings->task;
130  context->taskId = OS_INVALID_TASK_ID;
131 
132  //Attach TCP/IP stack context
133  if(settings->netContext != NULL)
134  {
135  context->netContext = settings->netContext;
136  }
137  else if(settings->interface != NULL)
138  {
139  context->netContext = settings->interface->netContext;
140  }
141  else
142  {
143  context->netContext = netGetDefaultContext();
144  }
145 
146  //Save user settings
147  context->interface = settings->interface;
148  context->port = settings->port;
149  context->unitId = settings->unitId;
150  context->timeout = settings->timeout;
151  context->openCallback = settings->openCallback;
152  context->closeCallback = settings->closeCallback;
153 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED)
154  context->tlsInitCallback = settings->tlsInitCallback;
155 #endif
156  context->lockCallback = settings->lockCallback;
157  context->unlockCallback = settings->unlockCallback;
158  context->readCoilCallback = settings->readCoilCallback;
159  context->readDiscreteInputCallback = settings->readDiscreteInputCallback;
160  context->writeCoilCallback = settings->writeCoilCallback;
161  context->readRegCallback = settings->readRegCallback;
162  context->readHoldingRegCallback = settings->readHoldingRegCallback;
163  context->readInputRegCallback = settings->readInputRegCallback;
164  context->writeRegCallback = settings->writeRegCallback;
165  context->processPduCallback = settings->processPduCallback;
166  context->tickCallback = settings->tickCallback;
167 
168  //Create an event object to poll the state of sockets
169  if(!osCreateEvent(&context->event))
170  {
171  //Failed to create event
172  error = ERROR_OUT_OF_RESOURCES;
173  }
174 
175 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED && TLS_TICKET_SUPPORT == ENABLED)
176  //Check status code
177  if(!error)
178  {
179  //Initialize ticket encryption context
180  error = tlsInitTicketContext(&context->tlsTicketContext);
181  }
182 #endif
183 
184  //Any error to report?
185  if(error)
186  {
187  //Clean up side effects
188  modbusServerDeinit(context);
189  }
190 
191  //Return status code
192  return error;
193 }
194 
195 
196 /**
197  * @brief Start Modbus/TCP server
198  * @param[in] context Pointer to the Modbus/TCP server context
199  * @return Error code
200  **/
201 
203 {
204  error_t error;
205 
206  //Make sure the Modbus/TCP server context is valid
207  if(context == NULL)
209 
210  //Debug message
211  TRACE_INFO("Starting Modbus/TCP server...\r\n");
212 
213  //Make sure the Modbus/TCP server is not already running
214  if(context->running)
215  return ERROR_ALREADY_RUNNING;
216 
217  //Start of exception handling block
218  do
219  {
220  //Open a TCP socket
221  context->socket = socketOpenEx(context->netContext, SOCKET_TYPE_STREAM,
223  //Failed to open socket?
224  if(context->socket == NULL)
225  {
226  //Report an error
227  error = ERROR_OPEN_FAILED;
228  break;
229  }
230 
231  //Force the socket to operate in non-blocking mode
232  error = socketSetTimeout(context->socket, 0);
233  //Any error to report?
234  if(error)
235  break;
236 
237  //Associate the socket with the relevant interface
238  error = socketBindToInterface(context->socket, context->interface);
239  //Any error to report?
240  if(error)
241  break;
242 
243  //The Modbus/TCP server listens for connection requests on port 502
244  error = socketBind(context->socket, &IP_ADDR_ANY, context->port);
245  //Any error to report?
246  if(error)
247  break;
248 
249  //Place socket in listening state
250  error = socketListen(context->socket, 0);
251  //Any error to report?
252  if(error)
253  break;
254 
255  //Start the Modbus/TCP server
256  context->stop = FALSE;
257  context->running = TRUE;
258 
259  //Create a task
260  context->taskId = osCreateTask("Modbus/TCP Server",
261  (OsTaskCode) modbusServerTask, context, &context->taskParams);
262 
263  //Failed to create task?
264  if(context->taskId == OS_INVALID_TASK_ID)
265  {
266  //Report an error
267  error = ERROR_OUT_OF_RESOURCES;
268  break;
269  }
270 
271  //End of exception handling block
272  } while(0);
273 
274  //Any error to report?
275  if(error)
276  {
277  //Clean up side effects
278  context->running = FALSE;
279 
280  //Close listening socket
281  socketClose(context->socket);
282  context->socket = NULL;
283  }
284 
285  //Return status code
286  return error;
287 }
288 
289 
290 /**
291  * @brief Stop Modbus/TCP server
292  * @param[in] context Pointer to the Modbus/TCP server context
293  * @return Error code
294  **/
295 
297 {
298  uint_t i;
299 
300  //Make sure the Modbus/TCP server context is valid
301  if(context == NULL)
303 
304  //Debug message
305  TRACE_INFO("Stopping Modbus/TCP server...\r\n");
306 
307  //Check whether the Modbus/TCP server is running
308  if(context->running)
309  {
310 #if (NET_RTOS_SUPPORT == ENABLED)
311  //Stop the Modbus/TCP server
312  context->stop = TRUE;
313  //Send a signal to the task to abort any blocking operation
314  osSetEvent(&context->event);
315 
316  //Wait for the task to terminate
317  while(context->running)
318  {
319  osDelayTask(1);
320  }
321 #endif
322 
323  //Loop through the connection table
324  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
325  {
326  //Check the state of the current connection
327  if(context->connection[i].state != MODBUS_CONNECTION_STATE_CLOSED)
328  {
329  //Close client connection
330  modbusServerCloseConnection(&context->connection[i]);
331  }
332  }
333 
334  //Close listening socket
335  socketClose(context->socket);
336  context->socket = NULL;
337  }
338 
339  //Successful processing
340  return NO_ERROR;
341 }
342 
343 
344 /**
345  * @brief Modbus/TCP server task
346  * @param[in] context Pointer to the Modbus/TCP server context
347  **/
348 
350 {
351  error_t error;
352  uint_t i;
353  systime_t timeout;
354  ModbusClientConnection *connection;
356 
357 #if (NET_RTOS_SUPPORT == ENABLED)
358  //Task prologue
359  osEnterTask();
360 
361  //Process events
362  while(1)
363  {
364 #endif
365  //Set polling timeout
366  timeout = MODBUS_SERVER_TICK_INTERVAL;
367 
368  //Clear event descriptor set
369  osMemset(eventDesc, 0, sizeof(eventDesc));
370 
371  //Specify the events the application is interested in
372  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
373  {
374  //Point to the structure describing the current connection
375  connection = &context->connection[i];
376 
377  //Loop through active connections only
378  if(connection->state != MODBUS_CONNECTION_STATE_CLOSED)
379  {
380  //Register connection events
381  modbusServerRegisterConnectionEvents(connection, &eventDesc[i]);
382 
383  //Check whether the socket is ready for I/O operation
384  if(eventDesc[i].eventFlags != 0)
385  {
386  //No need to poll the underlying socket for incoming traffic
387  timeout = 0;
388  }
389  }
390  }
391 
392  //The Modbus/TCP server listens for connection requests on port 502
393  eventDesc[i].socket = context->socket;
394  eventDesc[i].eventMask = SOCKET_EVENT_RX_READY;
395 
396  //Wait for one of the set of sockets to become ready to perform I/O
397  error = socketPoll(eventDesc, MODBUS_SERVER_MAX_CONNECTIONS + 1,
398  &context->event, timeout);
399 
400  //Check status code
401  if(error == NO_ERROR || error == ERROR_TIMEOUT ||
402  error == ERROR_WAIT_CANCELED)
403  {
404  //Stop request?
405  if(context->stop)
406  {
407  //Stop Modbus/TCP server operation
408  context->running = FALSE;
409  //Task epilogue
410  osExitTask();
411  //Kill ourselves
413  }
414 
415  //Event-driven processing
416  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
417  {
418  //Point to the structure describing the current connection
419  connection = &context->connection[i];
420 
421  //Loop through active connections only
422  if(connection->state != MODBUS_CONNECTION_STATE_CLOSED)
423  {
424  //Check whether the socket is ready to perform I/O
425  if(eventDesc[i].eventFlags != 0)
426  {
427  //Connection event handler
429  }
430  }
431  }
432 
433  //Any connection request received on port 502?
434  if(eventDesc[i].eventFlags != 0)
435  {
436  //Accept connection request
438  }
439  }
440 
441  //Handle periodic operations
442  modbusServerTick(context);
443 
444 #if (NET_RTOS_SUPPORT == ENABLED)
445  }
446 #endif
447 }
448 
449 
450 /**
451  * @brief Release Modbus/TCP server context
452  * @param[in] context Pointer to the Modbus/TCP server context
453  **/
454 
456 {
457  //Make sure the Modbus/TCP server context is valid
458  if(context != NULL)
459  {
460  //Free previously allocated resources
461  osDeleteEvent(&context->event);
462 
463 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED && TLS_TICKET_SUPPORT == ENABLED)
464  //Release ticket encryption context
465  tlsFreeTicketContext(&context->tlsTicketContext);
466 #endif
467 
468  //Clear Modbus/TCP server context
469  osMemset(context, 0, sizeof(ModbusServerContext));
470  }
471 }
472 
473 #endif
void modbusServerDeinit(ModbusServerContext *context)
Release Modbus/TCP server context.
OsTaskId osCreateTask(const char_t *name, OsTaskCode taskCode, void *arg, const OsTaskParameters *params)
Create a task.
uint8_t unitId
Unit identifier.
error_t socketBind(Socket *socket, const IpAddr *localIpAddr, uint16_t localPort)
Associate a local address with a socket.
Definition: socket.c:1344
error_t tlsInitTicketContext(TlsTicketContext *ticketContext)
Initialize ticket encryption context.
Definition: tls_ticket.c:49
Modbus/TCP server settings.
void modbusServerCloseConnection(ModbusClientConnection *connection)
Close network connection.
ModbusServerTlsInitCallback tlsInitCallback
TLS initialization callback function.
ModbusServerOpenCallback openCallback
TCP connection open callback function.
#define osExitTask()
#define MODBUS_TCP_PORT
Definition: modbus_common.h:38
ModbusServerReadCoilCallback readDiscreteInputCallback
Get discrete input state callback function.
error_t modbusServerStop(ModbusServerContext *context)
Stop Modbus/TCP server.
void modbusServerGetDefaultSettings(ModbusServerSettings *settings)
Initialize settings with default values.
Definition: modbus_server.c:49
#define TRUE
Definition: os_port.h:50
uint16_t port
Modbus/TCP port number.
#define OS_INVALID_TASK_ID
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:2094
ModbusServerUnlockCallback unlockCallback
Unlock Modbus table callback function.
NetContext * netContext
TCP/IP stack context.
#define MODBUS_SERVER_TIMEOUT
Definition: modbus_server.h:80
error_t modbusServerInit(ModbusServerContext *context, const ModbusServerSettings *settings)
Initialize Modbus/TCP server context.
@ ERROR_OUT_OF_RESOURCES
Definition: error.h:64
ModbusServerReadRegCallback readHoldingRegCallback
Get holding register value callback function.
@ SOCKET_TYPE_STREAM
Definition: socket.h:92
#define OS_SELF_TASK_ID
Structure describing socket events.
Definition: socket.h:433
ModbusServerWriteCoilCallback writeCoilCallback
Set coil state callback function.
#define ModbusClientConnection
ModbusServerLockCallback lockCallback
Lock Modbus table callback function.
NetInterface * interface
Underlying network interface.
@ ERROR_OPEN_FAILED
Definition: error.h:75
ModbusServerCloseCallback closeCallback
TCP connection close callback function.
#define MODBUS_SERVER_PRIORITY
Definition: modbus_server.h:68
const IpAddr IP_ADDR_ANY
Definition: ip.c:53
void osDeleteTask(OsTaskId taskId)
Delete a task.
#define FALSE
Definition: os_port.h:46
@ ERROR_INVALID_PARAMETER
Invalid parameter.
Definition: error.h:47
OsTaskParameters task
Task parameters.
void modbusServerProcessConnectionEvents(ModbusClientConnection *connection)
Connection event handler.
error_t
Error codes.
Definition: error.h:43
ModbusServerReadCoilCallback readCoilCallback
Get coil state callback function.
void(* OsTaskCode)(void *arg)
Task routine.
Modbus/TCP server.
Helper functions for Modbus/TCP server.
void osDeleteEvent(OsEvent *event)
Delete an event object.
ModbusServerProcessPduCallback processPduCallback
PDU processing callback function.
ModbusServerReadRegCallback readRegCallback
Get register value callback function.
ModbusServerTickCallback tickCallback
Tick callback function.
NetContext * netGetDefaultContext(void)
Get default TCP/IP stack context.
Definition: net.c:527
const OsTaskParameters OS_TASK_DEFAULT_PARAMS
Transport protocol abstraction layer.
#define MODBUS_SERVER_STACK_SIZE
Definition: modbus_server.h:61
#define TRACE_INFO(...)
Definition: debug.h:105
void modbusServerAcceptConnection(ModbusServerContext *context)
Accept connection request.
#define MODBUS_DEFAULT_UNIT_ID
Definition: modbus_common.h:45
@ MODBUS_CONNECTION_STATE_CLOSED
void modbusServerRegisterConnectionEvents(ModbusClientConnection *connection, SocketEventDesc *eventDesc)
Register connection events.
ModbusServerReadRegCallback readInputRegCallback
Get input register value callback function.
#define osEnterTask()
error_t socketPoll(SocketEventDesc *eventDesc, uint_t size, OsEvent *extEvent, systime_t timeout)
Wait for one of a set of sockets to become ready to perform I/O.
Definition: socket.c:2182
#define socketBindToInterface
Definition: net_legacy.h:193
uint32_t systime_t
System time.
@ ERROR_TIMEOUT
Definition: error.h:95
void modbusServerTask(ModbusServerContext *context)
Modbus/TCP server task.
@ SOCKET_EVENT_RX_READY
Definition: socket.h:179
Socket * socketOpenEx(NetContext *context, uint_t type, uint_t protocol)
Create a socket.
Definition: socket.c:146
@ ERROR_WAIT_CANCELED
Definition: error.h:73
bool_t osCreateEvent(OsEvent *event)
Create an event object.
systime_t timeout
Idle connection timeout.
void modbusServerTick(ModbusServerContext *context)
Handle periodic operations.
void osDelayTask(systime_t delay)
Delay routine.
void osSetEvent(OsEvent *event)
Set the specified event object to the signaled state.
ModbusServerWriteRegCallback writeRegCallback
Set register value callback function.
error_t modbusServerStart(ModbusServerContext *context)
Start Modbus/TCP server.
void tlsFreeTicketContext(TlsTicketContext *ticketContext)
Properly dispose ticket encryption context.
Definition: tls_ticket.c:448
#define MODBUS_SERVER_TICK_INTERVAL
Definition: modbus_server.h:87
Socket * socket
Handle to a socket to monitor.
Definition: socket.h:434
#define MODBUS_SERVER_MAX_CONNECTIONS
Definition: modbus_server.h:73
unsigned int uint_t
Definition: compiler_port.h:57
#define osMemset(p, value, length)
Definition: os_port.h:138
#define ModbusServerContext
@ SOCKET_IP_PROTO_TCP
Definition: socket.h:107
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:169
uint_t eventMask
Requested events.
Definition: socket.h:435
@ ERROR_ALREADY_RUNNING
Definition: error.h:294
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
error_t socketListen(Socket *socket, uint_t backlog)
Place a socket in the listening state.
Definition: socket.c:1441