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-2023 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.2.4
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  //The Modbus/TCP server is not bound to any interface
52  settings->interface = NULL;
53 
54  //Modbus/TCP port number
55  settings->port = MODBUS_TCP_PORT;
56  //Default unit identifier
57  settings->unitId = MODBUS_DEFAULT_UNIT_ID;
58  //Idle connection timeout
59  settings->timeout = MODBUS_SERVER_TIMEOUT;
60 
61  //TCP connection open callback function
62  settings->openCallback = NULL;
63  //TCP connection close callback function
64  settings->closeCallback = NULL;
65 
66 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED)
67  //TLS initialization callback function
68  settings->tlsInitCallback = NULL;
69 #endif
70 
71  //Lock Modbus table callback function
72  settings->lockCallback = NULL;
73  //Unlock Modbus table callback function
74  settings->unlockCallback = NULL;
75  //Get coil state callback function
76  settings->readCoilCallback = NULL;
77  //Get discrete input state callback function
78  settings->readDiscreteInputCallback = NULL;
79  //Set coil state callback function
80  settings->writeCoilCallback = NULL;
81  //Get register value callback function
82  settings->readRegCallback = NULL;
83  //Get holding register value callback function
84  settings->readHoldingRegCallback = NULL;
85  //Get input register value callback function
86  settings->readInputRegCallback = NULL;
87  //Set register value callback function
88  settings->writeRegCallback = NULL;
89  //PDU processing callback
90  settings->processPduCallback = NULL;
91  //Tick callback function
92  settings->tickCallback = NULL;
93 }
94 
95 
96 /**
97  * @brief Initialize Modbus/TCP server context
98  * @param[in] context Pointer to the Modbus/TCP server context
99  * @param[in] settings Modbus/TCP server specific settings
100  * @return Error code
101  **/
102 
104  const ModbusServerSettings *settings)
105 {
106  error_t error;
107 
108  //Debug message
109  TRACE_INFO("Initializing Modbus/TCP server...\r\n");
110 
111  //Ensure the parameters are valid
112  if(context == NULL || settings == NULL)
114 
115  //Clear Modbus/TCP server context
116  osMemset(context, 0, sizeof(ModbusServerContext));
117 
118  //Save user settings
119  context->settings = *settings;
120 
121  //Initialize status code
122  error = NO_ERROR;
123 
124  //Create an event object to poll the state of sockets
125  if(!osCreateEvent(&context->event))
126  {
127  //Failed to create event
128  error = ERROR_OUT_OF_RESOURCES;
129  }
130 
131 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED && TLS_TICKET_SUPPORT == ENABLED)
132  //Check status code
133  if(!error)
134  {
135  //Initialize ticket encryption context
136  error = tlsInitTicketContext(&context->tlsTicketContext);
137  }
138 #endif
139 
140  //Any error to report?
141  if(error)
142  {
143  //Clean up side effects
144  modbusServerDeinit(context);
145  }
146 
147  //Return status code
148  return error;
149 }
150 
151 
152 /**
153  * @brief Start Modbus/TCP server
154  * @param[in] context Pointer to the Modbus/TCP server context
155  * @return Error code
156  **/
157 
159 {
160  error_t error;
161 
162  //Make sure the Modbus/TCP server context is valid
163  if(context == NULL)
165 
166  //Debug message
167  TRACE_INFO("Starting Modbus/TCP server...\r\n");
168 
169  //Make sure the Modbus/TCP server is not already running
170  if(context->running)
171  return ERROR_ALREADY_RUNNING;
172 
173  //Start of exception handling block
174  do
175  {
176  //Open a TCP socket
178  //Failed to open socket?
179  if(context->socket == NULL)
180  {
181  //Report an error
182  error = ERROR_OPEN_FAILED;
183  break;
184  }
185 
186  //Force the socket to operate in non-blocking mode
187  error = socketSetTimeout(context->socket, 0);
188  //Any error to report?
189  if(error)
190  break;
191 
192  //Associate the socket with the relevant interface
193  error = socketBindToInterface(context->socket,
194  context->settings.interface);
195  //Any error to report?
196  if(error)
197  break;
198 
199  //The Modbus/TCP server listens for connection requests on port 502
200  error = socketBind(context->socket, &IP_ADDR_ANY, context->settings.port);
201  //Any error to report?
202  if(error)
203  break;
204 
205  //Place socket in listening state
206  error = socketListen(context->socket, 0);
207  //Any error to report?
208  if(error)
209  break;
210 
211  //Start the Modbus/TCP server
212  context->stop = FALSE;
213  context->running = TRUE;
214 
215 #if (OS_STATIC_TASK_SUPPORT == ENABLED)
216  //Create a task using statically allocated memory
217  context->taskId = osCreateStaticTask("Modbus/TCP Server",
218  (OsTaskCode) modbusServerTask, context, &context->taskTcb,
219  context->taskStack, MODBUS_SERVER_STACK_SIZE, MODBUS_SERVER_PRIORITY);
220 #else
221  //Create a task
222  context->taskId = osCreateTask("Modbus/TCP Server",
225 #endif
226 
227  //Failed to create task?
228  if(context->taskId == OS_INVALID_TASK_ID)
229  {
230  //Report an error
231  error = ERROR_OUT_OF_RESOURCES;
232  break;
233  }
234 
235  //End of exception handling block
236  } while(0);
237 
238  //Any error to report?
239  if(error)
240  {
241  //Clean up side effects
242  context->running = FALSE;
243 
244  //Close listening socket
245  socketClose(context->socket);
246  context->socket = NULL;
247  }
248 
249  //Return status code
250  return error;
251 }
252 
253 
254 /**
255  * @brief Stop Modbus/TCP server
256  * @param[in] context Pointer to the Modbus/TCP server context
257  * @return Error code
258  **/
259 
261 {
262  uint_t i;
263 
264  //Make sure the Modbus/TCP server context is valid
265  if(context == NULL)
267 
268  //Debug message
269  TRACE_INFO("Stopping Modbus/TCP server...\r\n");
270 
271  //Check whether the Modbus/TCP server is running
272  if(context->running)
273  {
274  //Stop the Modbus/TCP server
275  context->stop = TRUE;
276  //Send a signal to the task to abort any blocking operation
277  osSetEvent(&context->event);
278 
279  //Wait for the task to terminate
280  while(context->running)
281  {
282  osDelayTask(1);
283  }
284 
285  //Loop through the connection table
286  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
287  {
288  //Close client connection
289  modbusServerCloseConnection(&context->connection[i]);
290  }
291 
292  //Close listening socket
293  socketClose(context->socket);
294  context->socket = NULL;
295  }
296 
297  //Successful processing
298  return NO_ERROR;
299 }
300 
301 
302 /**
303  * @brief Modbus/TCP server task
304  * @param[in] context Pointer to the Modbus/TCP server context
305  **/
306 
308 {
309  error_t error;
310  uint_t i;
311  systime_t timeout;
312  ModbusClientConnection *connection;
314 
315 #if (NET_RTOS_SUPPORT == ENABLED)
316  //Task prologue
317  osEnterTask();
318 
319  //Process events
320  while(1)
321  {
322 #endif
323  //Set polling timeout
324  timeout = MODBUS_SERVER_TICK_INTERVAL;
325 
326  //Clear event descriptor set
327  osMemset(eventDesc, 0, sizeof(eventDesc));
328 
329  //Specify the events the application is interested in
330  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
331  {
332  //Point to the structure describing the current connection
333  connection = &context->connection[i];
334 
335  //Loop through active connections only
336  if(connection->state != MODBUS_CONNECTION_STATE_CLOSED)
337  {
338  //Register connection events
339  modbusServerRegisterConnectionEvents(connection, &eventDesc[i]);
340 
341  //Check whether the socket is ready for I/O operation
342  if(eventDesc[i].eventFlags != 0)
343  {
344  //No need to poll the underlying socket for incoming traffic
345  timeout = 0;
346  }
347  }
348  }
349 
350  //The Modbus/TCP server listens for connection requests on port 502
351  eventDesc[i].socket = context->socket;
352  eventDesc[i].eventMask = SOCKET_EVENT_RX_READY;
353 
354  //Wait for one of the set of sockets to become ready to perform I/O
355  error = socketPoll(eventDesc, MODBUS_SERVER_MAX_CONNECTIONS + 1,
356  &context->event, timeout);
357 
358  //Check status code
359  if(error == NO_ERROR || error == ERROR_TIMEOUT ||
360  error == ERROR_WAIT_CANCELED)
361  {
362  //Stop request?
363  if(context->stop)
364  {
365  //Stop Modbus/TCP server operation
366  context->running = FALSE;
367  //Task epilogue
368  osExitTask();
369  //Kill ourselves
371  }
372 
373  //Event-driven processing
374  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
375  {
376  //Point to the structure describing the current connection
377  connection = &context->connection[i];
378 
379  //Loop through active connections only
380  if(connection->state != MODBUS_CONNECTION_STATE_CLOSED)
381  {
382  //Check whether the socket is ready to perform I/O
383  if(eventDesc[i].eventFlags != 0)
384  {
385  //Connection event handler
387  }
388  }
389  }
390 
391  //Any connection request received on port 502?
392  if(eventDesc[i].eventFlags != 0)
393  {
394  //Accept connection request
396  }
397  }
398 
399  //Handle periodic operations
400  modbusServerTick(context);
401 
402 #if (NET_RTOS_SUPPORT == ENABLED)
403  }
404 #endif
405 }
406 
407 
408 /**
409  * @brief Release Modbus/TCP server context
410  * @param[in] context Pointer to the Modbus/TCP server context
411  **/
412 
414 {
415  //Make sure the Modbus/TCP server context is valid
416  if(context != NULL)
417  {
418  //Free previously allocated resources
419  osDeleteEvent(&context->event);
420 
421 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED && TLS_TICKET_SUPPORT == ENABLED)
422  //Release ticket encryption context
423  tlsFreeTicketContext(&context->tlsTicketContext);
424 #endif
425 
426  //Clear Modbus/TCP server context
427  osMemset(context, 0, sizeof(ModbusServerContext));
428  }
429 }
430 
431 #endif
OsTaskId osCreateTask(const char_t *name, OsTaskCode taskCode, void *param, size_t stackSize, int_t priority)
Create a task.
void modbusServerDeinit(ModbusServerContext *context)
Release Modbus/TCP server context.
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:596
error_t tlsInitTicketContext(TlsTicketContext *ticketContext)
Initialize ticket encryption context.
Definition: tls_ticket.c:50
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:52
uint16_t port
Modbus/TCP port number.
#define OS_INVALID_TASK_ID
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:1331
ModbusServerUnlockCallback unlockCallback
Unlock Modbus table callback function.
#define MODBUS_SERVER_TIMEOUT
Definition: modbus_server.h:73
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:78
#define OS_SELF_TASK_ID
Structure describing socket events.
Definition: socket.h:365
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:61
const IpAddr IP_ADDR_ANY
Definition: ip.c:52
void osDeleteTask(OsTaskId taskId)
Delete a task.
#define FALSE
Definition: os_port.h:48
@ ERROR_INVALID_PARAMETER
Invalid parameter.
Definition: error.h:47
void modbusServerProcessConnectionEvents(ModbusClientConnection *connection)
Connection event handler.
error_t
Error codes.
Definition: error.h:43
ModbusServerReadCoilCallback readCoilCallback
Get coil state callback function.
OsTaskId osCreateStaticTask(const char_t *name, OsTaskCode taskCode, void *param, OsTaskTcb *tcb, OsStackType *stack, size_t stackSize, int_t priority)
Create a task with statically allocated memory.
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.
Transport protocol abstraction layer.
#define MODBUS_SERVER_STACK_SIZE
Definition: modbus_server.h:54
#define TRACE_INFO(...)
Definition: debug.h:95
void modbusServerAcceptConnection(ModbusServerContext *context)
Accept connection request.
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:122
#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:1391
#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:162
void(* OsTaskCode)(void *param)
Task routine.
@ 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:449
#define MODBUS_SERVER_TICK_INTERVAL
Definition: modbus_server.h:80
Socket * socket
Handle to a socket to monitor.
Definition: socket.h:366
#define MODBUS_SERVER_MAX_CONNECTIONS
Definition: modbus_server.h:66
unsigned int uint_t
Definition: compiler_port.h:50
#define osMemset(p, value, length)
Definition: os_port.h:134
#define ModbusServerContext
@ SOCKET_IP_PROTO_TCP
Definition: socket.h:93
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:145
uint_t eventMask
Requested events.
Definition: socket.h:367
@ ERROR_ALREADY_RUNNING
Definition: error.h:292
@ 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:689