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

.. _pywrap:

Python Wrapper
==============

.. module:: PyWrtd

The WRTD Python wrapper provides a thin wrapper around the :ref:`clib`, using the `Python ctypes
package <https://pypi.org/project/ctypes/>`_.

The wrapper is provided as a Python package with a single class that encapsulates the complete WRTD
:ref:`clib`:

.. autoclass:: PyWrtd

All the provided class methods have exactly the same names (and function) as their C counterparts,
without the "wrtd\_" prefix.

.. hint::

   Compared to the :ref:`clib`, the Python wrapper lacks the :cpp:func:`wrtd_init`,
   :cpp:func:`wrtd_close`, :cpp:func:`wrtd_get_error` and :cpp:func:`wrtd_error_message` functions,
   because the Python wrapper performs these tasks (initialisation and error handling) internally.

.. hint::

   The provided :ref:`tools <tools>` are built on top of this Python wrapper, so they also serve as
   a good example of how to use the wrapper.

.. _pyapi_init:

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

To start using it, simply import the PyWrtd package and instantiate a :py:class:`PyWrtd` object,
passing to it the ID of the Node you wish to access.

In order to retrieve the ID of the :ref:`node`, the :py:class:`PyWrtd` class provides the static
methods :py:meth:`PyWrtd.PyWrtd.get_node_count` and :py:meth:`PyWrtd.PyWrtd.get_node_id` that can be
used before you instantiate the :py:class:`PyWrtd` object.

If the ID is wrong or if the user does not have the correct :ref:`permissions <permissions>` to
access it, WRTD will return :cpp:enumerator:`WRTD_ERROR_RESOURCE_UNKNOWN`.

.. code-block:: python

   >>> from PyWrtd import PyWrtd
   >>> wrtd = PyWrtd(11)
   OSError: [Errno -1074134944] WRTD_ERROR_RESOURCE_UNKNOWN
   >>> PyWrtd.get_node_id(3)
   OSError: [Errno -1074134944] WRTD_ERROR_RESOURCE_UNKNOWN
   >>> PyWrtd.get_node_count()
   2
   >>> PyWrtd.get_node_id(1)
   10
   >>> wrtd = PyWrtd(10)
   >>> wrtd
   <PyWrtd.PyWrtd object at 0x7fa9aeeaee48>
   >>> wrtd.reset()

.. automethod:: PyWrtd.get_node_count
.. automethod:: PyWrtd.get_node_id
.. automethod:: PyWrtd.reset

.. _pyapi_attr:

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

Getting and setting boolean, integer and string :ref:`Attributes <attribute>` is straight-forward.

Boolean Attributes
++++++++++++++++++

.. automethod:: PyWrtd.set_attr_bool
.. automethod:: PyWrtd.get_attr_bool
.. code-block:: python

   >>> wrtd.add_alarm('alarm1')
   >>> wrtd.get_attr_bool('alarm1', PyWrtd.WRTD_ATTR_ALARM_ENABLED)
   False
   >>> wrtd.set_attr_bool('alarm1', PyWrtd.WRTD_ATTR_ALARM_ENABLED, True)
   >>> wrtd.get_attr_bool('alarm1', PyWrtd.WRTD_ATTR_ALARM_ENABLED)
   True

Integer Attributes
++++++++++++++++++

.. automethod:: PyWrtd.set_attr_int32
.. automethod:: PyWrtd.get_attr_int32
.. code-block:: python

   >>> wrtd.add_alarm('alarm1')
   >>> wrtd.get_attr_int32('alarm1', PyWrtd.WRTD_ATTR_ALARM_REPEAT_COUNT)
   0
   >>> wrtd.set_attr_int32('alarm1', PyWrtd.WRTD_ATTR_ALARM_REPEAT_COUNT, 5)
   >>> wrtd.get_attr_int32('alarm1', PyWrtd.WRTD_ATTR_ALARM_REPEAT_COUNT)
   5

String Attributes
+++++++++++++++++

.. automethod:: PyWrtd.set_attr_string
.. automethod:: PyWrtd.get_attr_string
.. code-block:: python

   >>> wrtd.add_rule('rule1')
   >>> wrtd.set_attr_string('rule1', PyWrtd.WRTD_ATTR_RULE_SOURCE, 'event0')
   >>> wrtd.get_attr_string('rule1', PyWrtd.WRTD_ATTR_RULE_SOURCE)
   'event0'

Timestamp Attributes
++++++++++++++++++++

Getting and setting :ref:`timestamp` :ref:`Attributes <attribute>` is slightly different than in the
:ref:`clib`.

The *set* method takes the ``seconds``, ``ns`` and ``frac`` fields of the :ref:`timestamp` as separate
parameters.

.. automethod:: PyWrtd.set_attr_tstamp

The *get* method returns a Python dictionary with the ``seconds``, ``ns`` and ``frac`` fields of the
:ref:`timestamp`.

.. automethod:: PyWrtd.get_attr_tstamp
.. code-block:: python

   >>> wrtd.add_alarm('alarm1')
   >>> wrtd.get_attr_tstamp('alarm1', PyWrtd.WRTD_ATTR_ALARM_TIME)
   {'frac': 0, 'ns': 0, 'seconds': 0}
   >>> wrtd.set_attr_tstamp('alarm1', PyWrtd.WRTD_ATTR_ALARM_TIME, seconds = 5, ns = 40e3)
   >>> wrtd.get_attr_tstamp('alarm1', PyWrtd.WRTD_ATTR_ALARM_TIME)
   {'frac': 0, 'seconds': 5, 'ns': 40000}

.. _pyapi_log:

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

.. automethod:: PyWrtd.clear_event_log_entries
.. automethod:: PyWrtd.get_next_event_log_entry
.. code-block:: python

   >>> # First, enable logging
   >>> wrtd.set_attr_bool(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_EVENT_LOG_ENABLED, True)
   >>> wrtd.get_next_event_log_entry()
   'Id:LC-I1           |Seq:0000|2019-07-05,12:08:03.491.231.296+000|2019-07-05,12:08:03.491.226.827+412|GENERATED|DEVICE_0'

.. _pyapi_alarm:

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

.. automethod:: PyWrtd.add_alarm
.. code-block:: python

   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_ALARM_COUNT)
   0
   >>> wrtd.add_alarm('alarm1')
   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_ALARM_COUNT)
   1

.. automethod:: PyWrtd.disable_all_alarms
.. code-block:: python

   >>> wrtd.add_alarm('alarm1')
   >>> wrtd.get_attr_bool('alarm1', PyWrtd.WRTD_ATTR_ALARM_ENABLED)
   False
   >>> wrtd.set_attr_bool('alarm1', PyWrtd.WRTD_ATTR_ALARM_ENABLED, True)
   >>> wrtd.get_attr_bool('alarm1', PyWrtd.WRTD_ATTR_ALARM_ENABLED)
   True
   >>> wrtd.disable_all_alarms()
   >>> wrtd.get_attr_bool('alarm1', PyWrtd.WRTD_ATTR_ALARM_ENABLED)
   False

.. automethod:: PyWrtd.remove_alarm
.. code-block:: python

   >>> wrtd.add_alarm('alarm1')
   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_ALARM_COUNT)
   1
   >>> wrtd.remove_alarm('alarm1')
   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_ALARM_COUNT)
   0

.. automethod:: PyWrtd.remove_all_alarms
.. code-block:: python

   >>> wrtd.add_alarm('alarm1')
   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_ALARM_COUNT)
   1
   >>> wrtd.remove_all_alarms()
   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_ALARM_COUNT)
   0

.. automethod:: PyWrtd.get_alarm_name
.. code-block:: python

   >>> wrtd.add_alarm('alarm1')
   >>> count = wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_ALARM_COUNT)
   >>> print(count)
   1
   >>> for i in range(count):
   ...     wrtd.get_alarm_name(i)
   'alarm1'

.. _pyapi_rule:

Rules API
---------

.. automethod:: PyWrtd.add_rule
.. code-block:: python

   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_RULE_COUNT)
   0
   >>> wrtd.add_rule('rule1')
   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_RULE_COUNT)
   1

.. automethod:: PyWrtd.disable_all_rules
.. code-block:: python

   >>> wrtd.add_rule('rule1')
   >>> wrtd.get_attr_bool('rule1', PyWrtd.WRTD_ATTR_RULE_ENABLED)
   False
   >>> wrtd.set_attr_bool('rule1', PyWrtd.WRTD_ATTR_RULE_ENABLED, True)
   >>> wrtd.get_attr_bool('rule1', PyWrtd.WRTD_ATTR_RULE_ENABLED)
   True
   >>> wrtd.disable_all_rules()
   >>> wrtd.get_attr_bool('rule1', PyWrtd.WRTD_ATTR_RULE_ENABLED)
   False

.. automethod:: PyWrtd.remove_rule
.. code-block:: python

   >>> wrtd.add_rule('rule1')
   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_RULE_COUNT)
   1
   >>> wrtd.remove_rule('rule1')
   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_RULE_COUNT)
   0

.. automethod:: PyWrtd.remove_all_rules
.. code-block:: python

   >>> wrtd.add_rule('rule1')
   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_RULE_COUNT)
   1
   >>> wrtd.remove_all_rules()
   >>> wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_RULE_COUNT)
   0

.. automethod:: PyWrtd.get_rule_name
.. code-block:: python

   >>> wrtd.add_rule('rule1')
   >>> wrtd.add_rule('rule2')
   >>> count = wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_RULE_COUNT)
   >>> print(count)
   2
   >>> for i in range(count):
   ...     wrtd.get_rule_name(i)
   'rule1'
   'rule2'

.. automethod:: PyWrtd.reset_rule_stats
.. code-block:: python

   >>> # Assuming a rule 'rule1' exists and already has some statistics
   >>> wrtd.get_attr_int32('rule1', PyWrtd.WRTD_ATTR_STAT_RULE_RX_EVENTS)
   532
   >>> # First disable the rule
   >>> wrtd.set_attr_bool('rule1', PyWrtd.WRTD_ATTR_RULE_ENABLED, False)
   >>> wrtd.reset_rule_stats('rule1')
   >>> wrtd.get_attr_int32('rule1', PyWrtd.WRTD_ATTR_STAT_RULE_RX_EVENTS)
   0

.. _pyapi_app:

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

.. automethod:: PyWrtd.get_fw_name
.. code-block:: python

   # From the SVEC-based TDC+FDELAY reference Node
   >>> count = wrtd.get_attr_int32(PyWrtd.WRTD_GLOBAL_REP_CAP_ID, PyWrtd.WRTD_ATTR_FW_COUNT)
   >>> print(count)
   2
   >>> for i in range(count):
   ...     wrtd.get_fw_name(i)
   'wrtd-tdc'
   'wrtd-fd'
