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  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2019 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 1.9.6
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"
40 #include "debug.h"
41 
42 //Check TCP/IP stack configuration
43 #if (MODBUS_SERVER_SUPPORT == ENABLED)
44 
45 
46 /**
47  * @brief Handle periodic operations
48  * @param[in] context Pointer to the Modbus/TCP server context
49  **/
50 
52 {
53  uint_t i;
55  ModbusClientConnection *connection;
56 
57  //Get current time
59 
60  //Loop through the connection table
61  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
62  {
63  //Point to the current entry
64  connection = &context->connection[i];
65 
66  //Check the state of the current connection
67  if(connection->state != MODBUS_CONNECTION_STATE_CLOSED)
68  {
69  //Disconnect inactive client after idle timeout
70  if(timeCompare(time, connection->timestamp + MODBUS_SERVER_TIMEOUT) >= 0)
71  {
72  //Debug message
73  TRACE_INFO("Modbus server: Closing inactive connection...\r\n");
74  //Close the Modbus/TCP connection
75  modbusServerCloseConnection(connection);
76  }
77  }
78  }
79 }
80 
81 
82 /**
83  * @brief Connection event handler
84  * @param[in] context Pointer to the Modbus/TCP server context
85  * @param[in] connection Pointer to the client connection
86  **/
87 
89  ModbusClientConnection *connection)
90 {
91  error_t error;
92  size_t n;
93 
94  //Initialize status code
95  error = NO_ERROR;
96 
97  //Update time stamp
98  connection->timestamp = osGetSystemTime();
99 
100  //Check the state of the connection
101  if(connection->state == MODBUS_CONNECTION_STATE_CONNECT_TLS)
102  {
103 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED)
104  //Perform TLS handshake
105  error = modbusServerEstablishSecureConnection(connection);
106 
107  //Check status code
108  if(!error)
109  {
110  //Wait for incoming Modbus requests
111  connection->state = MODBUS_CONNECTION_STATE_RECEIVE;
112  }
113 #else
114  //Modbus/TCP security is not implemented
115  error = ERROR_WRONG_STATE;
116 #endif
117  }
118  else if(connection->state == MODBUS_CONNECTION_STATE_RECEIVE)
119  {
120  //Receive Modbus request
121  if(connection->requestAduPos < sizeof(ModbusHeader))
122  {
123  //Receive more data
124  error = modbusServerReceiveData(connection,
125  connection->requestAdu + connection->requestAduPos,
126  sizeof(ModbusHeader) - connection->requestAduPos, &n, 0);
127 
128  //Check status code
129  if(error == NO_ERROR)
130  {
131  //Advance data pointer
132  connection->requestAduPos += n;
133 
134  //MBAP header successfully received?
135  if(connection->requestAduPos >= sizeof(ModbusHeader))
136  {
137  //Parse MBAP header
138  error = modbusServerParseMbapHeader(connection);
139  }
140  }
141  else if(error == ERROR_END_OF_STREAM)
142  {
143  //Initiate a graceful connection shutdown
144  error = modbusServerShutdownConnection(connection);
145  }
146  else
147  {
148  //Just for sanity
149  }
150  }
151  else if(connection->requestAduPos < connection->requestAduLen)
152  {
153  //Receive more data
154  error = modbusServerReceiveData(connection,
155  connection->requestAdu + connection->requestAduPos,
156  connection->requestAduLen - connection->requestAduPos, &n, 0);
157 
158  //Check status code
159  if(error == NO_ERROR)
160  {
161  //Advance data pointer
162  connection->requestAduPos += n;
163 
164  //Modbus request successfully received?
165  if(connection->requestAduPos >= connection->requestAduLen)
166  {
167  //Check unit identifier
168  if(context->settings.unitId == 0 ||
169  context->settings.unitId == 255 ||
170  context->settings.unitId == connection->requestUnitId)
171  {
172  //Process Modbus request
173  error = modbusServerProcessRequest(connection);
174  }
175  }
176  }
177  }
178  else
179  {
180  //Just for sanity
181  error = ERROR_WRONG_STATE;
182  }
183  }
184  else if(connection->state == MODBUS_CONNECTION_STATE_SEND)
185  {
186  //Send Modbus response
187  if(connection->responseAduPos < connection->responseAduLen)
188  {
189  //Send more data
190  error = modbusServerSendData(connection,
191  connection->responseAdu + connection->responseAduPos,
192  connection->responseAduLen - connection->responseAduPos,
194 
195  //Check status code
196  if(error == NO_ERROR || error == ERROR_TIMEOUT)
197  {
198  //Advance data pointer
199  connection->responseAduPos += n;
200 
201  //Modbus response successfully sent?
202  if(connection->responseAduPos >= connection->responseAduLen)
203  {
204  //Flush receive buffer
205  connection->requestAduLen = 0;
206  connection->requestAduPos = 0;
207 
208  //Wait for the next Modbus request
209  connection->state = MODBUS_CONNECTION_STATE_RECEIVE;
210  }
211  }
212  }
213  else
214  {
215  //Just for sanity
216  error = ERROR_WRONG_STATE;
217  }
218  }
219  else if(connection->state == MODBUS_CONNECTION_STATE_SHUTDOWN_TLS ||
220  connection->state == MODBUS_CONNECTION_STATE_SHUTDOWN_TX)
221  {
222  //Graceful connection shutdown
223  error = modbusServerShutdownConnection(connection);
224  }
225  else if(connection->state == MODBUS_CONNECTION_STATE_SHUTDOWN_RX)
226  {
227  //Close the Modbus/TCP connection
228  modbusServerCloseConnection(connection);
229  }
230  else
231  {
232  //Invalid state
233  error = ERROR_WRONG_STATE;
234  }
235 
236  //Any communication error?
237  if(error != NO_ERROR && error != ERROR_TIMEOUT)
238  {
239  //Close the Modbus/TCP connection
240  modbusServerCloseConnection(connection);
241  }
242 }
243 
244 
245 /**
246  * @brief Parse request MBAP header
247  * @param[in] connection Pointer to the client connection
248  * @return Error code
249  **/
250 
252 {
253  size_t n;
254  ModbusHeader *requestHeader;
255 
256  //Sanity check
257  if(connection->requestAduPos < sizeof(ModbusHeader))
258  return ERROR_INVALID_LENGTH;
259 
260  //Point to the beginning of the request ADU
261  requestHeader = (ModbusHeader *) connection->requestAdu;
262 
263  //The length field is a byte count of the following fields, including
264  //the unit identifier and data fields
265  n = ntohs(requestHeader->length);
266 
267  //Malformed Modbus request?
268  if(n < sizeof(uint8_t))
269  return ERROR_INVALID_LENGTH;
270 
271  //Retrieve the length of the PDU
272  n -= sizeof(uint8_t);
273 
274  //Debug message
275  TRACE_DEBUG("\r\nModbus Server: ADU received (%" PRIuSIZE " bytes)...\r\n",
276  sizeof(ModbusHeader) + n);
277 
278  //Dump MBAP header
279  TRACE_DEBUG(" Transaction ID = %" PRIu16 "\r\n", ntohs(requestHeader->transactionId));
280  TRACE_DEBUG(" Protocol ID = %" PRIu16 "\r\n", ntohs(requestHeader->protocolId));
281  TRACE_DEBUG(" Length = %" PRIu16 "\r\n", ntohs(requestHeader->length));
282  TRACE_DEBUG(" Unit ID = %" PRIu16 "\r\n", requestHeader->unitId);
283 
284  //Check protocol identifier
285  if(ntohs(requestHeader->protocolId) != MODBUS_PROTOCOL_ID)
286  return ERROR_WRONG_IDENTIFIER;
287 
288  //The length of the Modbus PDU is limited to 253 bytes
289  if(n > MODBUS_MAX_PDU_SIZE)
290  return ERROR_INVALID_LENGTH;
291 
292  //Save unit identifier
293  connection->requestUnitId = requestHeader->unitId;
294  //Compute the length of the request ADU
295  connection->requestAduLen = sizeof(ModbusHeader) + n;
296 
297  //Successful processing
298  return NO_ERROR;
299 }
300 
301 
302 /**
303  * @brief Format response MBAP header
304  * @param[in] connection Pointer to the client connection
305  * @param[in] length Length of the PDU, in bytes
306  * @return Error code
307  **/
308 
310  size_t length)
311 {
312  ModbusHeader *requestHeader;
313  ModbusHeader *responseHeader;
314 
315  //Sanity check
316  if(connection->requestAduPos < sizeof(ModbusHeader))
317  return ERROR_INVALID_LENGTH;
318 
319  //Point to the beginning of the request ADU
320  requestHeader = (ModbusHeader *) connection->requestAdu;
321  //Point to the beginning of the response ADU
322  responseHeader = (ModbusHeader *) connection->responseAdu;
323 
324  //Format MBAP header
325  responseHeader->transactionId = requestHeader->transactionId;
326  responseHeader->protocolId = requestHeader->protocolId;
327  responseHeader->length = htons(length + sizeof(uint8_t));
328  responseHeader->unitId = requestHeader->unitId;
329 
330  //Compute the length of the response ADU
331  connection->responseAduLen = sizeof(ModbusHeader) + length;
332 
333  //Debug message
334  TRACE_DEBUG("Modbus Server: Sending ADU (%" PRIuSIZE " bytes)...\r\n",
335  connection->responseAduLen);
336 
337  //Dump MBAP header
338  TRACE_DEBUG(" Transaction ID = %" PRIu16 "\r\n", ntohs(responseHeader->transactionId));
339  TRACE_DEBUG(" Protocol ID = %" PRIu16 "\r\n", ntohs(responseHeader->protocolId));
340  TRACE_DEBUG(" Length = %" PRIu16 "\r\n", ntohs(responseHeader->length));
341  TRACE_DEBUG(" Unit ID = %" PRIu16 "\r\n", responseHeader->unitId);
342 
343  //Rewind to the beginning of the transmit buffer
344  connection->responseAduPos = 0;
345  //Send the response ADU to the client
346  connection->state = MODBUS_CONNECTION_STATE_SEND;
347 
348  //Successful processing
349  return NO_ERROR;
350 }
351 
352 
353 /**
354  * @brief Retrieve request PDU
355  * @param[in] connection Pointer to the client connection
356  * @param[out] length Length request the request PDU, in bytes
357  * @return Pointer to the request PDU
358  **/
359 
361  size_t *length)
362 {
363  uint8_t *requestPdu;
364 
365  //Point to the request PDU
366  requestPdu = connection->requestAdu + sizeof(ModbusHeader);
367 
368  //Retrieve the length of the PDU
369  if(connection->requestAduLen >= sizeof(ModbusHeader))
370  *length = connection->requestAduLen - sizeof(ModbusHeader);
371  else
372  *length = 0;
373 
374  //Return a pointer to the request PDU
375  return requestPdu;
376 }
377 
378 
379 /**
380  * @brief Retrieve response PDU
381  * @param[in] connection Pointer to the client connection
382  * @return Pointer to the response PDU
383  **/
384 
386 {
387  //Point to the response PDU
388  return connection->responseAdu + sizeof(ModbusHeader);
389 }
390 
391 
392 /**
393  * @brief Lock Modbus table
394  * @param[in] connection Pointer to the client connection
395  **/
396 
398 {
399  ModbusServerContext *context;
400 
401  //Point to the Modbus/TCP server context
402  context = connection->context;
403 
404  //Any registered callback?
405  if(context->settings.lockCallback != NULL)
406  {
407  //Invoke user callback function
408  context->settings.lockCallback();
409  }
410 }
411 
412 
413 /**
414  * @brief Unlock Modbus table
415  * @param[in] connection Pointer to the client connection
416  **/
417 
419 {
420  ModbusServerContext *context;
421 
422  //Point to the Modbus/TCP server context
423  context = connection->context;
424 
425  //Any registered callback?
426  if(context->settings.lockCallback != NULL)
427  {
428  //Invoke user callback function
429  context->settings.unlockCallback();
430  }
431 }
432 
433 
434 /**
435  * @brief Get coil state
436  * @param[in] connection Pointer to the client connection
437  * @param[in] address Coil address
438  * @param[out] state Current coil state
439  * @return Error code
440  **/
441 
443  uint16_t address, bool_t *state)
444 {
445  error_t error;
446  ModbusServerContext *context;
447 
448  //Point to the Modbus/TCP server context
449  context = connection->context;
450 
451  //Any registered callback?
452  if(context->settings.readCoilCallback != NULL)
453  {
454  //Invoke user callback function
455  error = context->settings.readCoilCallback(connection->role, address,
456  state);
457  }
458  else
459  {
460  //Report an error
461  error = ERROR_INVALID_ADDRESS;
462  }
463 
464  //Return status code
465  return error;
466 }
467 
468 
469 /**
470  * @brief Get discrete input state
471  * @param[in] connection Pointer to the client connection
472  * @param[in] address Coil address
473  * @param[out] state Current coil state
474  * @return Error code
475  **/
476 
478  uint16_t address, bool_t *state)
479 {
480  error_t error;
481  ModbusServerContext *context;
482 
483  //Point to the Modbus/TCP server context
484  context = connection->context;
485 
486  //Any registered callback?
487  if(context->settings.readDiscreteInputCallback != NULL)
488  {
489  //Invoke user callback function
490  error = context->settings.readDiscreteInputCallback(connection->role,
491  address, state);
492  }
493  else if(context->settings.readCoilCallback != NULL)
494  {
495  //Invoke user callback function
496  error = context->settings.readCoilCallback(connection->role, address,
497  state);
498  }
499  else
500  {
501  //Report an error
502  error = ERROR_INVALID_ADDRESS;
503  }
504 
505  //Return status code
506  return error;
507 }
508 
509 
510 /**
511  * @brief Set coil state
512  * @param[in] connection Pointer to the client connection
513  * @param[in] address Address of the coil
514  * @param[in] state Desired coil state
515  * @param[in] commit This flag indicates the current phase (validation phase
516  * or write phase if the validation was successful)
517  * @return Error code
518  **/
519 
521  uint16_t address, bool_t state, bool_t commit)
522 {
523  error_t error;
524  ModbusServerContext *context;
525 
526  //Point to the Modbus/TCP server context
527  context = connection->context;
528 
529  //Any registered callback?
530  if(context->settings.writeCoilCallback != NULL)
531  {
532  //Invoke user callback function
533  error = context->settings.writeCoilCallback(connection->role, address,
534  state, commit);
535  }
536  else
537  {
538  //Report an error
539  error = ERROR_INVALID_ADDRESS;
540  }
541 
542  //Return status code
543  return error;
544 }
545 
546 
547 /**
548  * @brief Get holding register value
549  * @param[in] connection Pointer to the client connection
550  * @param[in] address Register address
551  * @param[out] value Current register value
552  * @return Error code
553  **/
554 
556  uint16_t address, uint16_t *value)
557 {
558  error_t error;
559  ModbusServerContext *context;
560 
561  //Point to the Modbus/TCP server context
562  context = connection->context;
563 
564  //Any registered callback?
565  if(context->settings.readHoldingRegCallback != NULL)
566  {
567  //Invoke user callback function
568  error = context->settings.readHoldingRegCallback(connection->role,
569  address, value);
570  }
571  else if(context->settings.readRegCallback != NULL)
572  {
573  //Invoke user callback function
574  error = context->settings.readRegCallback(connection->role, address,
575  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 Get input register value
590  * @param[in] connection Pointer to the client connection
591  * @param[in] address Register address
592  * @param[out] value Current register value
593  * @return Error code
594  **/
595 
597  uint16_t address, uint16_t *value)
598 {
599  error_t error;
600  ModbusServerContext *context;
601 
602  //Point to the Modbus/TCP server context
603  context = connection->context;
604 
605  //Any registered callback?
606  if(context->settings.readInputRegCallback != NULL)
607  {
608  //Invoke user callback function
609  error = context->settings.readInputRegCallback(connection->role,
610  address, value);
611  }
612  else if(context->settings.readRegCallback != NULL)
613  {
614  //Invoke user callback function
615  error = context->settings.readRegCallback(connection->role, address,
616  value);
617  }
618  else
619  {
620  //Report an error
621  error = ERROR_INVALID_ADDRESS;
622  }
623 
624  //Return status code
625  return error;
626 }
627 
628 
629 /**
630  * @brief Set register value
631  * @param[in] connection Pointer to the client connection
632  * @param[in] address Register address
633  * @param[in] value Desired register value
634  * @param[in] commit This flag indicates the current phase (validation phase
635  * or write phase if the validation was successful)
636  * @return Error code
637  **/
638 
640  uint16_t address, uint16_t value, bool_t commit)
641 {
642  error_t error;
643  ModbusServerContext *context;
644 
645  //Point to the Modbus/TCP server context
646  context = connection->context;
647 
648  //Any registered callback?
649  if(context->settings.writeRegCallback != NULL)
650  {
651  //Invoke user callback function
652  error = context->settings.writeRegCallback(connection->role, address,
653  value, commit);
654  }
655  else
656  {
657  //Report an error
658  error = ERROR_INVALID_ADDRESS;
659  }
660 
661  //Return status code
662  return error;
663 }
664 
665 
666 /**
667  * @brief Translate exception code
668  * @param[in] status Status code
669  * @return Exception code
670  **/
671 
673 {
675 
676  //Check status code
677  switch(status)
678  {
680  //The function code received in the query is not an allowable action
681  //for the server
683  break;
685  //The data address received in the query is not an allowable address
686  //for the server
688  break;
689  case ERROR_INVALID_VALUE:
690  //A value contained in the query data field is not an allowable value
691  //for the server
693  break;
694  case ERROR_DEVICE_BUSY:
695  //The client should retransmit the message later when the server is free
697  break;
698  default:
699  //An unrecoverable error occurred while the server was attempting to
700  //perform the requested action
702  break;
703  }
704 
705  //Return exception code
706  return exceptionCode;
707 }
708 
709 #endif
#define htons(value)
Definition: cpu_endian.h:392
uint8_t length
Definition: dtls_misc.h:149
int bool_t
Definition: compiler_port.h:49
void modbusServerCloseConnection(ModbusClientConnection *connection)
Close network connection.
@ MODBUS_CONNECTION_STATE_SEND
__start_packed struct @208 ModbusHeader
MBAP header (Modbus Application Protocol)
@ MODBUS_CONNECTION_STATE_CONNECT_TLS
void modbusServerLock(ModbusClientConnection *connection)
Lock Modbus table.
#define MODBUS_MAX_PDU_SIZE
Definition: modbus_common.h:48
#define MODBUS_SERVER_TIMEOUT
Definition: modbus_server.h:73
Modbus/TCP security layer.
error_t modbusServerWriteCoil(ModbusClientConnection *connection, uint16_t address, bool_t state, bool_t commit)
Set coil state.
error_t modbusServerFormatMbapHeader(ModbusClientConnection *connection, size_t length)
Format response MBAP header.
void modbusServerUnlock(ModbusClientConnection *connection)
Unlock Modbus table.
error_t modbusServerReadDiscreteInput(ModbusClientConnection *connection, uint16_t address, bool_t *state)
Get discrete input state.
@ MODBUS_CONNECTION_STATE_RECEIVE
error_t modbusServerReadInputReg(ModbusClientConnection *connection, uint16_t address, uint16_t *value)
Get input register value.
@ ERROR_END_OF_STREAM
Definition: error.h:208
@ MODBUS_CONNECTION_STATE_SHUTDOWN_TLS
#define timeCompare(t1, t2)
Definition: os_port.h:42
@ ERROR_WRONG_STATE
Definition: error.h:207
error_t modbusServerParseMbapHeader(ModbusClientConnection *connection)
Parse request MBAP header.
#define ModbusClientConnection
error_t
Error codes.
Definition: error.h:42
@ MODBUS_EXCEPTION_SLAVE_DEVICE_FAILURE
ModbusExceptionCode modbusServerTranslateExceptionCode(error_t status)
Translate exception code.
Modbus/TCP server.
@ ERROR_INVALID_ADDRESS
Definition: error.h:102
@ MODBUS_CONNECTION_STATE_SHUTDOWN_RX
Helper functions for Modbus/TCP server.
@ ERROR_INVALID_LENGTH
Definition: error.h:109
Transport protocol abstraction layer.
@ MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS
@ MODBUS_EXCEPTION_SLAVE_DEVICE_BUSY
error_t modbusServerShutdownConnection(ModbusClientConnection *connection)
Shutdown network connection.
#define TRACE_INFO(...)
Definition: debug.h:94
@ MODBUS_CONNECTION_STATE_CLOSED
error_t modbusServerReceiveData(ModbusClientConnection *connection, void *data, size_t size, size_t *received, uint_t flags)
Receive data using the relevant transport protocol.
error_t modbusServerProcessRequest(ModbusClientConnection *connection)
Process Modbus request.
#define ntohs(value)
Definition: cpu_endian.h:398
@ ERROR_DEVICE_BUSY
Definition: error.h:264
#define TRACE_DEBUG(...)
Definition: debug.h:106
@ ERROR_TIMEOUT
Definition: error.h:94
error_t modbusServerSendData(ModbusClientConnection *connection, const void *data, size_t length, size_t *written, uint_t flags)
Send data using the relevant transport protocol.
uint32_t time
void * modbusServerGetRequestPdu(ModbusClientConnection *connection, size_t *length)
Retrieve request PDU.
@ ERROR_INVALID_VALUE
Definition: error.h:114
@ SOCKET_FLAG_NO_DELAY
Definition: socket.h:125
uint8_t n
error_t modbusServerWriteReg(ModbusClientConnection *connection, uint16_t address, uint16_t value, bool_t commit)
Set register value.
void modbusServerProcessConnectionEvents(ModbusServerContext *context, ModbusClientConnection *connection)
Connection event handler.
error_t modbusServerReadHoldingReg(ModbusClientConnection *connection, uint16_t address, uint16_t *value)
Get holding register value.
void modbusServerTick(ModbusServerContext *context)
Handle periodic operations.
@ ERROR_WRONG_IDENTIFIER
Definition: error.h:88
@ MODBUS_EXCEPTION_ILLEGAL_FUNCTION
void * modbusServerGetResponsePdu(ModbusClientConnection *connection)
Retrieve response PDU.
error_t modbusServerReadCoil(ModbusClientConnection *connection, uint16_t address, bool_t *state)
Get coil state.
Modbus PDU processing.
Ipv6Addr address
error_t modbusServerEstablishSecureConnection(ModbusClientConnection *connection)
Establish secure connection.
@ MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE
uint8_t value[]
Definition: dtls_misc.h:150
#define MODBUS_SERVER_MAX_CONNECTIONS
Definition: modbus_server.h:66
#define PRIuSIZE
Definition: compiler_port.h:78
unsigned int uint_t
Definition: compiler_port.h:45
ModbusExceptionCode
Modbus exception codes.
#define ModbusServerContext
uint8_t exceptionCode
@ MODBUS_CONNECTION_STATE_SHUTDOWN_TX
#define MODBUS_PROTOCOL_ID
Definition: modbus_common.h:43
uint32_t systime_t
Definition: compiler_port.h:46
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
@ ERROR_INVALID_FUNCTION_CODE
Definition: error.h:263
systime_t osGetSystemTime(void)
Retrieve system time.