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