.. _api_guide:

=====================
API Programming Guide
=====================

Overview
--------

The **Addi-Pack API** provides a unified programming interface for all supported **ADDI-DATA devices**.
It offers high-level access to hardware functions through a consistent set of C, C++, and Python APIs.

You can use the API to:

- Enumerate and select connected cards
- Read or write **digital** and **analog I/O**
- Configure and control **timers**, **counters**, and **watchdogs**
- Manage **interrupts** and handle callbacks
- Monitor **status**, **errors**, and **card information**

.. tip::
   The same API calls work seamlessly across **Windows**, **Linux**, and all ADDI-DATA devices (PCI, PCIe and CPCI/CPCIs).


Language Interfaces
-------------------

Addi-Pack provides equivalent API's for three languages:

+-------------+------------------------------------------------------------------+
| **Language**| **Description**                                                  |
+=============+==================================================================+
| **C**       | Low-level procedural API via ``addi-data/addi-pack/addi-pack.h`` |
+-------------+------------------------------------------------------------------+
| **C++**     | Modern object-oriented API under the ``addipack::`` namespace    |
+-------------+------------------------------------------------------------------+
| **Python**  | High-level bindings generated from the C++ API                   |
+-------------+------------------------------------------------------------------+

All bindings follow the same logical structure and naming convention:

- C: ``AddiData<Action><Component>()``
- C++: ``component->actionComponent()``
- Python: ``component.action_component()``

Example mappings:

+-------------------------------------------+---------------------------------------+----------------------------------------------+
| **C**                                     | **C++**                               | **Python**                                   |
+===========================================+=======================================+==============================================+
| AddiDataInitTimer()                       | timer->initTimer()                    | timer.init_timer()                           |
+-------------------------------------------+---------------------------------------+----------------------------------------------+
| AddiDataReadSingleDigitalInput()          | digital->readSingleDigitalInput()     | digital.read_single_digital_input()          |
+-------------------------------------------+---------------------------------------+----------------------------------------------+
| AddiDataStartCounter()                    | counter->startCounter()               | counter.start_counter()                      |
+-------------------------------------------+---------------------------------------+----------------------------------------------+


**Typical Workflow**
--------------------

The general API workflow is the same across all languages:

.. graphviz::
   :align: center
   :caption: Typical Addi-Pack API workflow (Timer example)

   digraph addipack_workflow {
       rankdir=TB;
       node [shape=box, style="rounded,filled", color=black, fillcolor=white, fontname="Arial", fontsize=10];

       // Define discover and note on same rank
       {
           rank = same;
           discover;
           cnote;
       }

       // Main flow
       discover [label="Discover a card\n\nC:  AddiDataGetDeviceList()\nC++:  DeviceDiscovery::getDevices()\nPy:  DeviceDiscovery.get_devices()\n\nRetrieve the first available device from the system"];
       init [label="Initialise a component\n\nC:  AddiDataInitTimer()\nC++:  timer->initTimer()\nPy:  timer.init_timer()\n\nInitialise the timer with the given parameters"];
       start [label="Start or trigger\n\nC:  AddiDataStartTimer()\nC++:  timer->startTimer()\nPy:  timer.start_timer()\n\nStart the timer"];
       read [label="Read results or handle event\n\nC:  AddiDataReadTimerStatus()\nC++:  timer->readTimerStatus()\nPy:  timer.read_timer_status()\n\nRead the current status of the timer or respond to an interrupt event"];
       stop [label="Stop or release\n\nC:  AddiDataStopTimer()\nC++:  timer->stopTimer()\nPy:  timer.stop_timer()\n\nStop the timer"];

       // C-specific note
       cnote [shape=note, color=gray30, fontcolor=gray30,
              label="C only:\nBefore calling AddiDataGetDeviceList():\n- Call AddiDataGetNumberOfDevices()\n- Allocate memory using malloc()"];

       // Connections
       discover -> init -> start -> read -> stop;
       discover -> cnote [style=dotted, arrowhead=none];
   }


Examples
--------

C
~~~

.. code-block:: c

   #include <addi-data/addi-pack/addi-pack.h>
   #include <stdio.h>

   int main() {
      uint8_t number_of_cards = 0;
      AddiDataGetNumberOfDevices(&number_of_cards);

      AddiDataDeviceStruct* device_list = malloc(sizeof(AddiDataDeviceStruct) * number_of_cards);
      if (!device_list || AddiDataGetDeviceList(device_list) != ADDIDATA_SUCCESS_CODE) {
         free(device_list);
         exit(EXIT_FAILURE);
    }

      uint8_t timer_id = 0;
      uint16_t reload_value = 500;
      TIMEBASE_UNITS timebase = MILLISECOND;
      uint32_t options = SINGLE_CYCLE;

      AddiDataInitTimer(&device, timer_id, timebase, reload_value, options);
      AddiDataStartTimer(&device, timer_id);

      uint32_t value = 0;
      uint8_t status = 0, triggered = 0, has_expired;

      AddiDataReadTimerStatus(id, timer_id, &value, &status, &triggered);

      for (int i = 0, i < reload_value; i++) {
         printf("Timer: value=%u | status=%u | triggered=%u | expired=%u\r", value, status, triggered, has_expired);
         fflush(stdout);
         usleep(1000);
      }

      AddiDataStopTimer(id, timer_id);
      return 0;
   }


C++
~~~~

.. code-block:: cpp

   #include <addipack/device_discovery.hpp>
   #include <addipack/timer_interface.hpp>
   #include <chrono>
   #include <thread>

   int main() {
       auto devices = DeviceDiscovery::getDevices();
       auto timer = device->timer();

       uint8_t timer_id = 0;
       uint16_t reload_value = 500;
       TIMEBASE_UNITS timebase = MILLISECOND;
       uint32_t options = SINGLE_CYCLE;

       timer->initTimer(timer_id, timebase, reload_value, options);
       timer->startTimer(timer_id);

       uint32_t value = 0;
       uint8_t status = 0, triggered = 0, has_expired;

       std::this_thread::sleep_for(std::chrono::milliseconds(200));
       timer->readTimerStatus(timer_id, value, status, triggered, has_expired);

       std::cout << "Timer: value=" << value << " | status=" << int(status)
                  << " | triggered=" << int(triggered) << " | expired=" << int(has_expired) << std::endl;

       timer->stopTimer(timer_id);
       return 0;
   }


Python
~~~~~~

.. code-block:: python

   import time
   from addipack import (
      DeviceDiscovery,
      TIMEBASE_UNITS
   )

   devices = DeviceDiscovery.get_devices()
   timer = device.timer()

   timer_id = 0
   reload_value = 500
   timebase = TIMEBASE_UNITS.MILLISECOND
   options = ADDI_DATA_TCW_OPTIONS.SINGLE_CYCLE

   timer.init_timer(timer_id, timebase, reload_value, options)
   timer.start_timer(timer_id)
   print("Timer started.")

   time.sleep(0.1)
   value, status, triggered, has_expired = timer.read_timer_status(timer_id)
   print(f"Timer {timer_id} - value: {value}, started: {status}, trigger: {triggered}, expired: {has_expired}")

   timer.stop_timer(timer_id)


**Error Handling**
------------------

All Addi-Pack C functions return a **32-bit status code** that encodes where and why an error occurred.

Error Code Layout
~~~~~~~~~~~~~~~~~

The error code is divided into 5 main fields:

+----------+-------------+-----------+--------------------------------------------------+
| **Bits** | **Field**   | **Size**  | **Description**                                  |
+==========+=============+===========+==================================================+
| 31       | **Source**  | 1 bit     | 0 = Driver, 1 = API                              |
+----------+-------------+-----------+--------------------------------------------------+
| 30–24    | **Domain**  | 7 bits    | Functional area (Timer, Counter, Digital I/O...) |
+----------+-------------+-----------+--------------------------------------------------+
| 23–16    | **Reserved**| 8 bits    | Reserved for future use                          |
+----------+-------------+-----------+--------------------------------------------------+
| 15–8     | **Function**| 8 bits    | Function identifier (Init, Start, Stop...)       |
+----------+-------------+-----------+--------------------------------------------------+
| 7–0      | **Reason**  | 8 bits    | Detailed cause (Invalid, Out of range…)          |
+----------+-------------+-----------+--------------------------------------------------+


Example
~~~~~~~~

**Return codes are universal** — they are interpreted the same way in C, C++, and Python (where exceptions include the code).

+----------------------+---------------------------------------------------------------------------------------------------------+
| **Error Code (Hex)** | **Meaning**                                                                                             |
+======================+=========================================================================================================+
| ``0x00000000``       | **Success** – Operation completed successfully (``ADDIDATA_SUCCESS_CODE``).                             |
+----------------------+---------------------------------------------------------------------------------------------------------+
| ``0x83001203``       | **API / TIMER / INIT / INVALID_PARAMETER** – Invalid argument passed to ``AddiDataInitTimer()``.        |
+----------------------+---------------------------------------------------------------------------------------------------------+
| ``0x82001905``       | **API / DIGIO / WRITE_DIGOUT / NOT_SUPPORTED** – Digital output operation not supported on this device. |
+----------------------+---------------------------------------------------------------------------------------------------------+
| ``0x04001309``       | **Driver / WATCHDOG / START / INVALID_STATE** – Watchdog cannot be started (invalid or inactive state). |
+----------------------+---------------------------------------------------------------------------------------------------------+
| ``0x85000608``       | **API / INTERRUPTS / CREATE_INTERRUPT / HARDWARE_FAILURE** – Hardware refused interrupt creation.       |
+----------------------+---------------------------------------------------------------------------------------------------------+
| ``0x81001804``       | **API / COUNTER / READ_STATUS / OUT_OF_BOUNDS** – Counter index outside the valid range.                |
+----------------------+---------------------------------------------------------------------------------------------------------+
| ``0x8000000A``       | **API / GENERIC / NONE / CONFIG_ERROR** – Configuration issue detected in API call.                     |
+----------------------+---------------------------------------------------------------------------------------------------------+

.. note::
   This list shows only a few representative examples.
   The complete list of all error codes is available in the :ref:`Common Error Codes <common_error_codes>`.


**Working with Structures and Types**
-------------------------------------

Structure `AddiDataDeviceStruct`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Describes a detected ADDI-DATA device and its capabilities.

This structure is returned by discovery functions such as ``AddiDataGetDeviceList()``.
It contains hardware information like the product name, PCI/PCIe location, and supported features (timers, counters, digital I/O, etc.).

Only the ``id`` field (a ``const char*``) is used in API calls.
This string uniquely identifies the device and must be passed to all ``AddiData*()`` functions.

.. code-block:: c

   typedef struct AddiDataDeviceStruct {
       char id[ADDI_STRING_SIZE];
       AddiDataGeneralInformations general;
       AddiDataFunctionInformations functions;
       AddiDataPCIBusInformations bus;
   } AddiDataDeviceStruct;

You obtain it through:

.. code-block:: c

   AddiDataDeviceStruct device;
   device->general.product_name

   Example:
   printf("\n========== Device(%s) ==========\n", device->id);
   printf("Product Name: %s\n", device->general.product_name);
   printf("Bus: %u | Device: %u | Vendor ID: 0x%04X | Device ID: 0x%04X\n", device->bus.bus_number,
           device->bus.device_number, device->bus.vendor_id, device->bus.device_id);
   printf("\nDigital Inputs: %u | Outputs: %u | Digital Ports: %u | Interrupt Mask: 0x%08X\n",
               device->functions.digital.number_inputs, device->functions.digital.number_outputs,
               device->functions.digital.number_of_ports, device->functions.digital.interrupt_mask);


**Timer / Counter / Watchdog Identifiers**
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Each ADDI-DATA card exposes a set of timers, counters, and watchdogs, identified by **zero-based IDs**.

The number of available components depends on the specific card model, and is provided dynamically through the discovery structure.

To determine valid IDs, use the fields inside ``AddiDataDeviceStruct``:

.. code-block:: c

   uint8_t max_timer_id = device.functions.timer.number_timers - 1;
   uint8_t max_counter_id = device.functions.counter.number_counters - 1;
   uint8_t max_watchdog_id = device.functions.watchdog.number_watchdogs - 1;

For example, if ``number_timers == 3``, then the valid timer IDs are ``0``, ``1``, and ``2``.

+----------------+---------------------+
| **Card**       | **Timers ID**       |
+================+=====================+
| APCI-1500      | Timers 0–2          |
+----------------+---------------------+
| APCIe-1564     | Timers 0–1          |
+----------------+---------------------+
| APCIe-1032     | Timer 0             |
+----------------+---------------------+
| APCI-1032      | None                |
+----------------+---------------------+

Example usage:

.. code-block:: c

   AddiDataStartTimer(&device, 0);  // Start timer 0

To determine how many are available:

Use the sample :ref:`get_devices_info <samples>` to query the detected card and list its
capabilities (number of timers, counters, watchdogs, digital inputs/outputs, ports, etc.).

**Run (CLI):**

Linux

.. code-block:: bash

   $AddiPack_ROOT/samples/get_device_info/build/get_device_info

Windows (PowerShell)

.. code-block:: powershell

   & "$env:AddiPack_ROOT\samples\bin\x64\generic\get_devices_info.exe"

The output includes cards summary with counts for timers/counters/watchdogs and digital I/O.


**Digital I/O Channels**
~~~~~~~~~~~~~~~~~~~~~~~~

Digital channels are identified numerically and can be accessed either individually (bit-level) or collectively (port-level).

1. Single-bit access

   Use this when you want to read or write a specific channel:

   .. code-block:: c

      uint8_t value = 0;
      AddiDataReadSingleDigitalInput(device.id, 4, &value);  // Read input channel 4

      AddiDataWriteSingleDigitalOutput(device.id, 7, ADDI_DATA_ENABLE);     // Set output channel 7 to HIGH

2. Multi-bit (mask-based) access

   This allows reading or writing all digital inputs or outputs at once using a bitmask:

   .. code-block:: c

      uint32_t inputs = 0;
      AddiDataReadAllDigitalInputs(device.id, &inputs);     // Read all 32 inputs

      AddiDataWriteAllDigitalOutputs(device.id, 0x0000FFFF); // Set first 16 outputs to HIGH

3. Digital Input Events (Interrupts)

   Some ADDI-DATA cards support **event-based monitoring** of digital input lines.
   This allows the application to react to signal changes instead of polling continuously.

   To configure digital input interrupts:

   1. Select the **input port** to monitor (e.g., Port 0 or 1),
   2. Define the **bitmask** of input lines to watch,
   3. Choose the **event type** (rising, falling, any edge, on zero (state off), on one (state on)),
   4. Select the **logic mode** (trigger if *any* line matches with logic **OR**, or *all* with logic **AND**),
   5. Enable digital and device-level interrupts.

4. Digital Output Events (VCC / CC Interrupts)

   Digital output interrupts are triggered when the board detects:

   - **VCC interrupt**: power supply failure on digital outputs
   - **CC interrupt**: short-circuit condition on digital outputs

   These events are card-level safety mechanisms and are independent of individual output channel states.

.. raw:: html

   <div class="page-break"></div>

Example (C code):

.. code-block:: c

   uint8_t port = 0;
   uint32_t mask = 0x00000010; // Channel 4
   DIGITAL_EVENT_TYPE event = RISING_EDGE;
   DIGITAL_EVENT_LOGIC logic = OR;

   AddiDataSetInterruptEventForInputs(device.id, mask, event);
   AddiDataSetInterruptLogicForPort(device.id, port, logic);
   AddiDataSetDeviceInterruptCallback(device.id, &MyCallback);
   AddiDataEnableDeviceInterrupts(device.id);
   AddiDataEnableDigitalInterrupt(device.id);

.. note::
   - **Event logic is configured per port**, not per channel.
   - Cards may support different **event types**:
     ``RISING_EDGE``, ``FALLING_EDGE``, ``ANY_EDGE``, ``ON_ZERO``, ``ON_ONE``.
   - **Logic mode** (``OR`` or ``AND``) determines if the interrupt fires on *any* active line, or *only when all* specified inputs match.

.. tip::
   You can retrieve the number of available ports and the interrupt mask using the discovery structure.

   .. code-block:: c

      uint32_t interrupt_mask = selected_device->functions.digital.interrupt_mask;
      uint8_t max_port = selected_device->functions.digital.number_of_ports;


Enumerations
~~~~~~~~~~~~

Enumerations standardize parameters such as timebases, modes, and event types.

Common enumerations:

+---------------------------+-----------------------------------------------+
| **Enumeration**           | **Purpose**                                   |
+===========================+===============================================+
| ``TIMEBASE_UNITS``        | Defines timer units (µs, ms, s)               |
+---------------------------+-----------------------------------------------+
| ``ADDI_DATA_TCW_OPTIONS`` | Timer/Counter/Watchdog initialization options |
+---------------------------+-----------------------------------------------+
| ``DIGITAL_EVENT_TYPE``    | Rising/Falling edge, Level High/Low           |
+---------------------------+-----------------------------------------------+
| ``DIGITAL_EVENT_LOGIC``   | Logical combination (AND / OR)                |
+---------------------------+-----------------------------------------------+

Example:

.. code-block:: c

   TIMEBASE_UNITS timebase = MILLISECOND;
   AddiDataInitTimer(&device, 0, timebase, 1000, 0);


Next section: :ref:`driver_concepts` — Learn how drivers, interrupts, and DMA work internally.