Logo
  • Installation
  • Engine
  • Builder
  • Built-in tools
  • APIs
    • Data types
    • Remote I/O
    • Tool APIs
    • C API
      • Data link drivers
        • Overview
        • Master and slave operation
        • Handles
        • Variables
        • Configuration values
        • Error handling
        • Thread-safety
        • Implementing a driver
        • Using the driver
      • I/O server device drivers
      • Creating tools in C
      • C API reference
    • C++ API
    • JavaScript client API
    • Python API
  • Components
  • Cameras
  • Cookbook
VisionAppster
  • »
  • APIs »
  • C API »
  • Data link drivers

Data link driversπŸ”—

OverviewπŸ”—

The VisionAppster data link driver API consists of a set of C functions that provide a generic interface for working with different types of external devices such as digital I/O devices, programmable logic controllers (PLC), distributed control systems (DCS) and SQL databases. Broadly, it can be used as a general way of implementing custom machine-to-machine data link protocols.

This documentation specifies the required functionality for a compatible data link driver. The following sections describe some key concepts. Please refer to the API documentation for further details.

Master and slave operationπŸ”—

When working as a master device, a data link driver communicates with a remote device by sending commands through a communication bus such as serial line, digital I/O lines, field bus or Ethernet. This way, it is able to read and write variables on the external device. If a device master must react to changes in the value of a variable in a remote device, it must repeatedly poll the variable.

In slave device mode, the driver works as a server that listens to commands coming from a communication bus. It interprets the commands and either sets or returns the values of local variables.

HandlesπŸ”—

The API works with generic handles that are expressed as typeless pointers (void*). The actual meaning of the handles is up to the driver implementation. However, the implementation must ensure that it has a way of distinguishing handles that represent devices, variables, and handle lists.

A handle list can hold any number of handles (including zero). On success, the va_datalink_dev_list() and va_datalink_var_list() functions always return a non-null handle. To iterate a handle list, the va_datalink_handle_next() function is used.

See the va_datalink_dev_create() and va_datalink_var_create() functions for more details.

VariablesπŸ”—

The representation of variables is implementation-specific. Thus, the API just uses handles. The value of a variable can be changed with va_datalink_var_write() and retrieved with va_datalink_var_read().

Some data link devices provide a fixed set of variables. For example, a SQL table has a fixed set of columns that may not be changeable by the driver. The same applies to the tags provided by a remote PLC slave. In such a case, the driver may provide a va_datalink_var_list() function for listing the variables.

In master mode, setting a variable pushes it to an external data link device. Similarly, reading a variable makes a read request over the communication bus.

In slave mode, variables are stored locally. Changing the value of a variable has no direct effect on remote masters, but they’ll get the updated value on the next request. Reading a variable returns the last value set either locally or by a master device.

The number of data types supported by the API does not cover all possible data types supported by every imaginable data link device. The driver implementation makes sure the variable values are correctly converted to the types supported by the API. It must take into account things like byte order and overflow handling.

Whenever the value of a variable is passed to an API function, a vararg list (…) will be used. The interpretation of the list depends on the type of the variable; variable types are preceded by a type ID. For example:

va_datalink_var_write(var1, va_int32_t_id, 3000);
va_datalink_var_write(var2, va_double_id, 3.141593);
va_datalink_var_write(var3, va_string_id, "COM1");

When reading values with va_datalink_var_read(), the argument following a type ID must be a pointer to the type. For example:

int32_t i_value;
va_string* str;
va_datalink_var_read(var1, va_int32_t_id, &i_value);
va_datalink_var_read(var2, datalink_string, &str);
va_string_free(str);

Configuration valuesπŸ”—

Devices and variables have a number of configuration values that can be used to control their behavior. Configuration variables can be queried using the va_datalink_config_get() function and set with the va_datalink_var_write() function.

Device configuration valuesπŸ”—

Configuration values may be needed in order to successfully connect to a data link device. While it is possible to encode these properties to the device address, using configuration values should be the preferred choice as it lets the VisionAppster Engine to store device-specific parameters in its device database and thus change them without modifying the app that uses the data link driver.

The following table lists supported configuration values.

Name

Type

Writab le

Description

address

va_string*

No

The address of the device.

flags

int32_t

No

A bit mask of device capabilities (` <:cpp:type:va_d atalink_dev_flag>` __)

mode

int32_t

Y/N

The operation mode. Writable if driver supports both modes.

timeout

int32_t

Yes

The maximum number of milliseconds any operation (connection attempt, write or read) can last.

Variable configuration valuesπŸ”—

In general, variable configuration values are read-only and defined either by the data link device or at creation time (va_datalink_var_create()).

The following table lists supported variable configuration values. Optional values are in cursive.

Name

Type

Writa ble

Description

name

va_string*

No

The name of the variable.

type

int32_t

No

The type of the variable. One of va_type_id.

min

int32_t/double

No

Minimum value of a range-limited numeric variable.

max

int32_t/double

No

Maximum value of a range-limited numeric variable.

count

int32_t

No

Fixed number of items in an array variable. Required if the variable is an array.

Error handlingπŸ”—

All functions in the data link API return a status code or a value whose validity can be checked. If the return value is a pointer, NULL indicates a failure, any other value means success. If the returned value is a status code, one indicates success and zero indicates failure.

To indicate the reason for a failure, the driver implementation stores a device-specific failure code internally. The user of the driver can get a textual representation of the error by calling va_datalink_error(). The implementation can use for example errno constants and pass them to strerror_r() in the implementation of va_datalink_error().

Thread-safetyπŸ”—

The functions in the data link driver do not need to be thread-safe, but they need to be re-entrant. In other words, the driver implementation must not hold state in global variables, but only in the created handles.

Implementing a driverπŸ”—

A valid driver must implement all non-optional functions in the API. The VisionAppster Engine refuses to load a driver that is missing any of them. Below is an example of an incomplete driver that implements just two of the API functions:

#include <va_datalink.h>

// Required for all plugins dynamically loaded by the VisionAppster Engine.
VA_IMPLEMENT_PLUGIN("my.example.driver", "1.0.0", datalink_driver)

// Gives the driver a unique name in the context of the component.
VA_REGISTER_DATALINK_DRIVER("MyDriver")

typedef enum
{
  dev_handle,
  var_handle,
  list_handle
} dummy_handle_type;

typedef struct
{
  dummy_handle_type type;
  char name[256];
} dummy_device;

void* va_datalink_dev_create(const va_string* device_address)
{
  dummy_device* dev = (dummy_device*)malloc(sizeof(dummy_device));
  if (dev)
    {
      dev->type = dev_handle;
      va_string_to_utf8(device_address, dev->name, 256);
      dev->name[255] = 0;
    }
  return dev;
}

int32_t va_datalink_handle_destroy(void* handle)
{
  if (handle && *(dummy_handle_type*)handle == dev_handle)
    printf("Destroying device handle.\n");
  free(handle);
  return 1;
}

// Rest of required functions here

The interface is defined in C, which makes it relatively safe to use with many different compilers. Using va-cross is however suggested. The driver needs to be compiled into a shared library and renamed so that it has a datalink filename suffix, for example mydriver.datalink.

To install the driver, you have three options:

  1. System-wide installation: copy the plugin to the plugins/datalink/ directory under the VisionAppster installation directory

  2. User-scope installation: copy the plugin to the VisionAppster/plugins/datalink/ directory under your home directory.

  3. Install from a component package: put the plugin into a component package and install.

In the last case, you must ensure that the component ID and version given as parameters to the VA_IMPLEMENT_PLUGIN macro match those of the component you build.

If you are going to distribute your driver through the VisionAppster Store and want to make sure the driver cannot be loaded without a valid license, replace the VA_IMPLEMENT_PLUGIN macro with VA_IMPLEMENT_LICENSED_PLUGIN. See va_plugin.h for details.

Using the driverπŸ”—

When the driver has been successfully installed, all devices listed by it will be available to the VisionAppster Engine. In the Builder, you’ll see the detected devices in tools such as Output external data.

Next Previous

© Copyright 2021, VisionAppster.