.. Copyright (c) 2019 CERN (home.cern)
   SPDX-License-Identifier: CC-BY-SA-4.0+

.. _clib:

C Library
=========

The WRTD C Library is the standard, most flexible way of accessing a WRTD Node. The library itself is modelled
after `IVI`_ and `LXI`_ (see also :ref:`lxi`).

The following specifications are relevant to and used by WRTD:

* `IVI-3.1 Driver Architecture Specification`_
* `IVI-3.2 Inherent Capabilities Specification`_
* `IVI-3.4 API Style Guide`_
* `IVI-3.15 IviLxiSync Specification`_
* `LXI Core Specification`_
* `LXI Event Messaging Extended Function`_

.. _api_error:

Error Handling API
------------------

Every function in the WRTD C library returns an :ref:`error code <api_error_codes>` of type
:cpp:type:`wrtd_status`. The Error Handling API provides :ref:`functions <api_error_func>` for
retrieving and interpreting errors from the Node.

Error codes can be converted to strings by means of the :cpp:func:`wrtd_error_message` function. The
latest error can be retrieved with the :cpp:func:`wrtd_get_error` function.

.. _api_error_codes:

Error Codes
+++++++++++

.. doxygenenum:: wrtd_status

.. _api_error_func:

Functions
+++++++++

.. doxygenfunction:: wrtd_get_error

.. doxygenfunction:: wrtd_error_message
.. code-block:: c
   :caption: Error handling
   :name: lst-get_error

   #include <libwrtd.h>

   int main(void) {
           wrtd_dev *wrtd;
           wrtd_status status, err_code;
           char status_str[256];
           char *err_msg;
           int buf_size;

           status = wrtd_init(1, false, NULL, &wrtd);
           if (status != WRTD_SUCCESS) {
                   /* use wrtd_error_message because wrtd_init failed
                      and the wrtd pointer is not valid. */
                   wrtd_error_message(wrtd, status, status_str);
                   printf("ERROR: %d, %s\n", status, status_str);
                   return status;
           }

           status = wrtd_get_attr_bool(wrtd, WRTD_GLOBAL_REP_CAP_ID,
                                       WRTD_ATTR_EVENT_LOG_EMPTY);
           if (status != WRTD_SUCCESS) {
                   /* query the necessary buffer size for
                      the error message */
                   buf_size = wrtd_get_error(wrtd, NULL, 0, NULL);
                   /* allocate the buffer */
                   err_msg = calloc(sizeof(char), buf_size);
                   /* retrieve the error code and message */
                   wrtd_get_error(wrtd, &err_code, buf_size, err_msg)
                   printf("ERROR: %d, %s\n", err_code, err_msg);
                   return status;
           }

           wrtd_close(wrtd);

           return 0;
   }

.. important::

   In the remaining code examples we omit error checking on purpose, to simplify the
   examples. However, in a real application, users should always check the status code of every call
   to a WRTD function, like in :numref:`lst-get_error`.

.. hint::

   If you want to be sure that the buffer that you pass to :cpp:func:`wrtd_get_error`
   is large enough, without having to resort to querying like in :numref:`lst-get_error`, you can
   always allocate a buffer of :c:macro:`WRTD_ERR_MSG_BUF_SIZE`. WRTD guarantees that all error
   messages shall not exceed this size.

   .. doxygendefine:: WRTD_ERR_MSG_BUF_SIZE
   .. code-block:: c
      :caption: Retrieving the error message with a pre-defined buffer size

      #include <libwrtd.h>

      int main(void) {
              wrtd_dev *wrtd;
              wrtd_status status;
              char err_msg[WRTD_ERR_MSG_BUF_SIZE];

              status = wrtd_init(1, false, NULL, &wrtd);

              status = wrtd_get_attr_bool(wrtd, WRTD_GLOBAL_REP_CAP_ID,
                                          WRTD_ATTR_EVENT_LOG_EMPTY);
              if (status != WRTD_SUCCESS) {
                      /* retrieve the error code and message */
                      wrtd_get_error(wrtd, &err_code, WRTD_ERR_MSG_BUF_SIZE, err_msg)
                      printf("ERROR: %d, %s\n", err_code, err_msg);
                      return status;
              }

              wrtd_close(wrtd);

              return 0;
      }

.. _api_init:

Initialisation API
------------------

The initialisation API provides the functions to initiate/close a connection to the :ref:`node`, as
well as to reset it.

:cpp:func:`wrtd_init` is the first function that should be called by any program, in order to obtain
the "device token" to be used in all subsequent function calls.

Conversely, :cpp:func:`wrtd_close` should be called before exiting the program. No further WRTD
library functions can be used after that.

In order to identify the :ref:`node` to connect to, it is necessary to provide the ID of that
:ref:`node`. This ID is simply an integer that uniquely identifies a Node within a given host
system. Functions :cpp:func:`wrtd_get_node_count` and :cpp:func:`wrtd_get_node_id` can help you
figure out the ID of each :ref:`node`.

.. important::

   The Node ID is not sequential, nor does it start counting from zero (or one). It might well be
   that you only have one Node in a given host, and that it has an ID different than 1. Always
   retrieve therefore the Node ID with :cpp:func:`wrtd_get_node_id`.

.. doxygenfunction:: wrtd_init
.. doxygenfunction:: wrtd_close
.. doxygenfunction:: wrtd_reset
.. doxygenfunction:: wrtd_get_node_count
.. doxygenfunction:: wrtd_get_node_id
.. code-block:: c
   :caption: Opening and closing a connection to a Node.

   #include <libwrtd.h>

   int main(void) {
           wrtd_dev *wrtd;
           wrtd_status status;
           uint32_t node_count;
           uint32_t node_id;

           /* Not really used in this example */
           status = wrtd_get_node_count(&node_count);

           /* Get the ID of the first Node */
           status= wrtd_get_node_id(1, &node_id);

           /* Access the first Node */
           status = wrtd_init(node_id, false, NULL, &wrtd);

           /* This will erase all defined rules and alarms
              from the Node, so it might not be what you want
              the program to do every time it is executed. */
           wrtd_reset(wrtd);

           /* Do some more work here... */

           wrtd_close(wrtd);
   }

.. _api_attr:

Attribute Handling API
----------------------

The Attribute Handling API defines the available :ref:`api_attr_id` and the :ref:`api_attr_func` for
accessing them.

Attributes can be of type ``bool``, ``int32``, ``string``, or ``tstamp`` (:cpp:class:`wrtd_tstamp`).

Access can be ``RW`` (read/write), ``RO`` (read-only) or ``WO`` (write-only).

Furthermore they can be related to a specific ``alarm``, ``rule``, ``application``, or they can be
``global`` (they apply to the whole device).

:ref:`Attributes <api_attr_id>` are accessed by means of the following :ref:`functions
<api_attr_func>`, depending on their type:

* :cpp:func:`wrtd_set_attr_bool`
* :cpp:func:`wrtd_get_attr_bool`
* :cpp:func:`wrtd_set_attr_int32`
* :cpp:func:`wrtd_get_attr_int32`
* :cpp:func:`wrtd_set_attr_string`
* :cpp:func:`wrtd_get_attr_string`
* :cpp:func:`wrtd_set_attr_tstamp`
* :cpp:func:`wrtd_get_attr_tstamp`

When using one of the above functions to access a "global" :ref:`attribute`, the :ref:`rep_cap_id`
parameter should be set to :c:macro:`WRTD_GLOBAL_REP_CAP_ID`.

.. doxygendefine:: WRTD_GLOBAL_REP_CAP_ID
.. code-block:: c
   :caption: Accessing a "global" :ref:`attribute`.

   #include <libwrtd.h>

   int main(void) {
           wrtd_dev *wrtd;
           wrtd_status status;
           bool log_empty;

           status = wrtd_init(1, false, NULL, &wrtd);

           status = wrtd_get_attr_bool(wrtd, WRTD_GLOBAL_REP_CAP_ID,
                                       WRTD_ATTR_EVENT_LOG_EMPTY,
                                       &log_empty);

           wrtd_close(wrtd);
   }

.. _api_attr_id:

Attributes
++++++++++

.. doxygenenum:: wrtd_attr

.. _api_attr_func:

Functions
+++++++++

.. doxygenfunction:: wrtd_set_attr_bool
.. doxygenfunction:: wrtd_get_attr_bool

.. doxygenfunction:: wrtd_set_attr_int32
.. doxygenfunction:: wrtd_get_attr_int32

.. doxygenfunction:: wrtd_set_attr_string
.. doxygenfunction:: wrtd_get_attr_string

.. doxygenfunction:: wrtd_set_attr_tstamp
.. doxygenfunction:: wrtd_get_attr_tstamp

.. code-block:: c
   :caption: Accessing various types of :ref:`attributes <attribute>`.

   #include <libwrtd.h>

   int main(void) {
           wrtd_dev *wrtd;
           wrtd_status status;
           wrtd_tstamp ts;
           bool log_empty;

           status = wrtd_init(1, false, NULL, &wrtd);

           /* check if the event log is empty (global attribute) */
           status = wrtd_get_attr_bool(wrtd, WRTD_GLOBAL_REP_CAP_ID,
                                       WRTD_ATTR_EVENT_LOG_EMPTY,
                                       &log_empty);

           /* add a rule with name "rule1" */
           status = wrtd_add_rule(wrtd, "rule1");

           /* set the repeat count for "rule1 */
           status = wrtd_set_attr_int32(wrtd, "rule1",
                                        WRTD_ATTR_RULE_REPEAT_COUNT, 5);

           /* set the source for "rule1" to local channel input 1 */
           status = wrtd_set_attr_string(wrtd, "rule1",
                                         WRTD_ATTR_RULE_SOURCE, "LC-I1");

           /* get the delay configured for "rule1" */
           status = wrtd_get_attr_tstamp(wrtd, "rule1",
                                         WRTD_ATTR_RULE_DELAY, &ts);

           wrtd_close(wrtd);
   }

.. important::

   :cpp:func:`wrtd_get_attr_tstamp` and :cpp:func:`wrtd_set_attr_tstamp` allow getting and setting
   of timestamp :ref:`attributes <attribute>`. Within the C library, a timestamp is represented as a
   C struct:

   .. doxygenstruct:: wrtd_tstamp
                      :members:

   Note that the above internal representation is slightly different than the official
   :ref:`definition of a timestamp <timestamp>`. In particular, the seconds counter is 16-bits
   shorter and the fractional nanosecond counter is 16 bits longer (and every "tick" represents 2\
   :sup:`-32`\ ns). Both of these changes help the underlying firmware to operate faster. When WRTD
   sends (or receives) a message, it will always use the official :ref:`definition of a timestamp
   <timestamp>` and convert it automatically to the above internal representation when necessary.

.. _api_log:

Event Logging API
-----------------

The Event Logging API provides functions for accessing the :ref:`event_log`.

.. doxygenfunction:: wrtd_clear_event_log_entries
.. doxygenfunction:: wrtd_get_next_event_log_entry
.. code-block:: c
   :caption: Accessing the :ref:`event_log`.
   :name: lst-event_log

   #include <libwrtd.h>

   int main(void) {
           wrtd_dev *wrtd;
           wrtd_status status;
           char *log_msg;
           int buf_size;

           status = wrtd_init(1, false, NULL, &wrtd);

           /* clear the event log */
           status = wrtd_clean_event_log_entries(wrtd);

           /* query the size of the next event log message */
           buf_size = wrtd_get_next_event_log_entry(wrtd, 0, NULL);

           /* allocate the buffer for the log message */
           log_msg = calloc(sizeof(char), buf_size);

           /* retrieve the next event log message */
           status = wrtd_get_next_event_log_entry(wrtd, buf_size, log_msg);

           wrtd_close(wrtd);

           return 0;
   }

.. hint::

   If you want to be sure that the buffer that you pass to :cpp:func:`wrtd_get_next_event_log_entry`
   is large enough, without having to resort to querying like in :numref:`lst-event_log`, you can
   always allocate a buffer of :c:macro:`WRTD_LOG_ENTRY_SIZE`. WRTD guarantees that all event log
   entries shall not exceed this size.

   .. doxygendefine:: WRTD_LOG_ENTRY_SIZE
   .. code-block:: c
      :caption: Accessing the :ref:`event_log` with a pre-defined buffer size

      #include <libwrtd.h>

      int main(void) {
              wrtd_dev *wrtd;
              wrtd_status status;
              char log_msg[WRTD_LOG_ENTRY_SIZE];

              status = wrtd_init(1, false, NULL, &wrtd);

              /* retrieve the next event log message */
              status = wrtd_get_next_event_log_entry(wrtd, WRTD_LOG_ENTRY_SIZE, log_msg);

              wrtd_close(wrtd);

              return 0;
      }

.. _api_alarm:

Alarms API
----------

The Alarms API provides functions for adding, removing and indexing :ref:`Alarms <alarm>`.

Configuration of an Alarm happens by setting the relevant :ref:`Attributes <attribute>` via the
:ref:`api_attr`.

.. doxygenfunction:: wrtd_add_alarm
.. doxygenfunction:: wrtd_disable_all_alarms
.. doxygenfunction:: wrtd_remove_alarm
.. doxygenfunction:: wrtd_remove_all_alarms
.. doxygenfunction:: wrtd_get_alarm_name
.. code-block:: c
   :caption: Adding, removing and indexing :ref:`Alarms <alarm>`.

   #include <libwrtd.h>

   int main(void) {
           int i, count;
           char rep_cap_id[16];
           wrtd_dev *wrtd;
           wrtd_status status;

           status = wrtd_init(1, false, NULL, &wrtd);

           /* disable and then remove any declared alarms */
           status = wrtd_disable_all_alarms(wrtd);
           status = wrtd_remove_all_alarms(wrtd);

           /* Add three alarms */
           status = wrtd_add_alarm(wrtd, "alarm1");
           status = wrtd_add_alarm(wrtd, "alarm2");
           status = wrtd_add_alarm(wrtd, "alarm3");

           /* Remove the 2nd alarm */
           status = wrtd_remove_alarm(wrtd, "alarm2");

           /* Get number of defined alarms */
           status = wrtd_get_attr_int32(wrtd, WRTD_GLOBAL_REP_CAP_ID,
                                        WRTD_ATTR_ALARM_COUNT, &count);

           /* Iterate through alarms and print their names.
              This should output:
              1: alarm1
              2: alarm3
            */
           for (i = 0; i < count; i++) {
                   status = wrtd_get_alarm_name(wrtd, i, 16, rep_cap_id);
                   printf ("%d: %s\n", i, rep_cap_id);
           }

           wrtd_close(wrtd);

           return 0;
   }

.. _api_rule:

Rules API
---------

The Rules API provides functions for adding, removing and indexing :ref:`Rules <rule>`.

Configuration of a Rule happens by setting the relevant :ref:`Attributes <attribute>` via the
:ref:`api_attr`.

.. doxygenfunction:: wrtd_add_rule
.. doxygenfunction:: wrtd_disable_all_rules
.. doxygenfunction:: wrtd_remove_rule
.. doxygenfunction:: wrtd_remove_all_rules
.. doxygenfunction:: wrtd_get_rule_name
.. doxygenfunction:: wrtd_reset_rule_stats
.. code-block:: c
   :caption: Adding, removing and indexing :ref:`Rules <rule>`.

   #include <libwrtd.h>

   int main(void) {
           int i, count;
           char rep_cap_id[16];
           wrtd_dev *wrtd;
           wrtd_status status;

           status = wrtd_init(1, false, NULL, &wrtd);

           /* disable and then remove any declared rules */
           status = wrtd_disable_all_rules(wrtd);
           status = wrtd_remove_all_rules(wrtd);

           /* Add three rules */
           status = wrtd_add_rule(wrtd, "rule1");
           status = wrtd_add_rule(wrtd, "rule2");
           status = wrtd_add_rule(wrtd, "rule3");

           /* Remove the 2nd rule */
           status = wrtd_remove_rule(wrtd, "rule2");

           /* Get number of defined rules */
           status = wrtd_get_attr_int32(wrtd, WRTD_GLOBAL_REP_CAP_ID,
                                        WRTD_ATTR_RULE_COUNT, &count);

           /* Iterate through rules and print their names.
              This should output:
              1: rule1
              2: rule3
            */
           for (i = 0; i < count; i++) {
                   status = wrtd_get_rule_name(wrtd, i, 16, rep_cap_id);
                   printf ("%d: %s\n", i, rep_cap_id);
           }

           wrtd_close(wrtd);

           return 0;
   }

.. code-block:: c
   :caption: Basic :ref:`rule` configuration.

   #include <libwrtd.h>

   int main(void) {
           wrtd_dev *wrtd;

           status = wrtd_init(1, false, NULL, &wrtd);

           /* Add a rule */
           status = wrtd_add_rule(wrtd, "rule1");

           /* Set rule to listen for events coming on local channel
              input 1 and generate a message on the network with
              event ID "NET0", after adding 500ns to the event timestamp. */
           status = wrtd_set_attr_string(wrtd, "rule1",
                                         WRTD_ATTR_RULE_SOURCE, "LC-I1");
           status = wrtd_set_attr_string(wrtd, "rule1",
                                         WRTD_ATTR_RULE_DESTINATION, "NET0");
           wrtd_tstamp ts = {.seconds = 0, .ns = 500, .frac = 0};
           status = wrtd_set_attr_tstamp(wrtd, "rule1",
                                         WRTD_ATTR_RULE_DELAY, &ts);

           /* Enable rule */
           status = wrtd_set_attr_bool(wrtd, "rule1",
                                       WRTD_ATTR_RULE_ENABLED, true);

           wrtd_close(wrtd);

           return 0;
   }

.. _api_app:

Applications API
----------------

Similar to :ref:`Rules <rule>` and :ref:`Alarms <alarm>`, :ref:`Applications <application>` are also
:ref:`Repeated Capabilities <rep_cap>`. However, they are read-only and, as such, do not possess any
functions to add or remove them. The only provided functionality is that of indexing.

Information retrieval regarding a particular :ref:`application` is performed by by getting the
relevant :ref:`Attributes <attribute>` via the :ref:`api_attr`.

.. doxygenfunction:: wrtd_get_fw_name
.. code-block:: c
   :caption: Indexing :ref:`Applications <application>` and version retrieval.

   #include <libwrtd.h>

   int main(void) {
           int i, count, major, minor;
           char rep_cap_id[16];
           wrtd_dev *wrtd;
           wrtd_status status;

           status = wrtd_init(1, false, NULL, &wrtd);

           /* Get number of defined applications */
           status = wrtd_get_attr_int32(wrtd, WRTD_GLOBAL_REP_CAP_ID,
                                        WRTD_ATTR_FW_COUNT, &count);

           /* Iterate through applications and print their
              names and versions. */
           for (i = 0; i < count; i++) {
                   status = wrtd_get_fw_name(wrtd, i, 16, rep_cap_id);
                   status = wrtd_get_attr_int32(wrtd, rep_cap_id,
                                                WRTD_ATTR_FW_MAJOR_VERSION,
                                                &major);
                   status = wrtd_get_attr_int32(wrtd, rep_cap_id,
                                                WRTD_ATTR_FW_MINOR_VERSION,
                                                &minor);
                   printf ("%d: %s, v%d.%d\n", i, rep_cap_id, major, minor);
           }

           wrtd_close(wrtd);

           return 0;
   }

.. _IVI: http://ivifoundation.org

.. _LXI: http://www.lxistandard.org

.. _LXI Core Specification: http://www.lxistandard.org/members/Adopted%20Specifications/Latest%20Version%20of%20Standards_/LXI%20Standard%201.5%20Specifications/LXI%20Device%20Specification%20v1_5_01.pdf

.. _LXI Event Messaging Extended Function: http://www.lxistandard.org/members/Adopted%20Specifications/Latest%20Version%20of%20Standards_/LXI%20Standard%201.5%20Specifications/LXI%20Event%20Messaging%20Extended%20Function.pdf

.. _IVI-3.15 IviLxiSync Specification: http://www.ivifoundation.org/downloads/Architecture%20Specifications/IVI-3.15_LxiSync_2018-08-23.pdf

.. _IVI-3.1 Driver Architecture Specification: http://www.ivifoundation.org/downloads/Architecture%20Specifications/IVIspecstopost10-22-2018/IVI-3.1_Architecture_2018-10-19.pdf

.. _IVI-3.2 Inherent Capabilities Specification: http://www.ivifoundation.org/downloads/Architecture%20Specifications/IVI-3.2_Inherent_Capabilities_2017-02-07.pdf

.. _IVI-3.4 API Style Guide: http://www.ivifoundation.org/downloads/Architecture%20Specifications/IVIspecstopost10-22-2018/IVI-3.4_APIStyleGuide_2018-10-19.pdf
