Skip to content

Getting Started Tutorial

In this most basic tutorial, you will connect to a device from the LabOne API, read the internal timestamp from it and make its power LED blink. This tutorial works for all devices, APIs and operating systems and is intended for a first contact with the LabOne API.

In case of problems contact us via support@zhinst.com.

Introduction and Setup

All Zurich Instruments devices use a server-based connectivity methodology. Server-based means that all communication between the user and the instrument takes place via a computer program called a Data Server. The Data Server recognizes available instruments and manages all communication between the instrument and the host computer on one side, and communication to all the connected clients on the other side.

The first step is to ensure a LabOne Data Server is running within your network. The easiest solution is to install LabOne and start the Data Server locally.

MF device family

The MF device family (MFLI, MFIA) have a build in Data Server. It is therefore not necessary to install LabOne locally but one can only install the preferred LabOne API. (Note that locally running LabOne installation is also possible)

The LabOne Data Server is automatically started as part of the LabOne Software. After the installation the startup link Zurich Instruments LabOne can be found in the Windows 10 Start Menu. This will open the User Interface in a new tab in your default web browser and start the LabOne Data Server in the background. Since we do not need the User Interface in this Tutorial you can close the newly opened Tab.

After the installation of LabOne the LabOne Data Server can be started with the following command:

ziDataServer

HF2

The HF2 device family can only be accessed through the HF2 Data Server. It is also automatically installed but needs a different command to start.

ziServer

The LabOne Data Server is automatically started as part of the LabOne Software. After the installation LabOne can be started, e.g, through the launchpad or the spotlight search (Command + Space Bar). LabOne should now be accessible through the status bar.

The last thing to ensure is that the preferred LabOne API is installed. This example works on all LabOne APIs and the different tabs show the respective steps.

No special editor or environment is required to execute this example. We recommend using a jupyter notebook or the basic python shell (accessible through the terminal by typing python).

This example can be executed in a simple executable program. Any development environment will do. The only thing required is to link against the LabOne C API library.

The easiest way is to create a cpp file zi_tutorial.cpp and use the standard compiler of your system to compile it and link it against the LabOne C API library.

x64 Native Tools Command Prompt for Visual Studio

cl.exe /std:c++17 main.cpp /I<labone_c_api_path>\include <labone_c_api_path>\lib\ziAPI-win64.lib /Fe:zi_tutorial.exe
x86 Native Tools Command Prompt for Visual Studio
cl.exe /std:c++17 zi_tutorial.cpp /I<labone_c_api_path>\include <labone_c_api_path>\lib\ziAPI-win32.lib /Fe:zi_tutorial.exe

g++ zi_tutorial.cpp -std=c++17 -I<labone_c_api_path>/include -L<labone_c_api_path>/lib -lziAPI-linux64 -ozi_tutorial 
Please use the library that matches your system:
  • ziAPI-linux64
  • ziAPI-linuxARM32
  • ziAPI-linuxARM64
clang++ zi_tutorial.cpp -std=c++17 -I<labone_c_api_path>/include -L<labone_c_api_path>/lib -lziAPI-darwin -ozi_tutorial -rpath <labone_c_api_path>/lib

You can also use cmake and define a cross-platform configuration. Inside the examples directory of the LabOne C API a CMakeLists.txt configuration is provided that works for all supported platforms.

This tutorial can executed with in the Matlab terminal. Apart from adding the LabOne Matlab API to the Matlab Path no special configuration is needed.

This tutorial creates a simple C# console application within Visual Studio. Take a look at the install LabOne step get a detailed description of how this is done.

This tutorial can be executed within a simple blank VI. Apart from the steps described in the Installation, no special configuration is needed.

Info

Since LabView itself is not a classical programming language but rather a graphical programming environment this API handles some things slightly differently. Nevertheless this tutorial can also be executed in LabView.

Connecting to the LabOne Data Server

Now that the LabOne Data Server is running and the LabOne API is installed correctly, it is time to connect from the API to the Data Server through a Client Session.

from zhinst.core import ziDAQServer
daq = ziDAQServer("localhost", 8004, 6)
from zhinst.core import ziDAQServer
daq = ziDAQServer("localhost", 8005, 1)
from zhinst.toolkit import Session
session = Session("localhost")
from zhinst.toolkit import Session
session = Session("localhost", hf2=True)
#include <iostream>
#include "ziAPI.h"

/**
* Error handling for the ziAPI.
* Translate C API result codes into C++ exceptions
*
* @param[in] conn        Pointer to the ziConnection for which the value(s) will be set.
* @param[in] resultCode  Result code from the ziAPI.
*/
void handleError(ZIConnection conn, ZIResult_enum resultCode) {
    if (resultCode == ZI_INFO_SUCCESS) {
        return;
    }
    char *errorDescription;
    ziAPIGetError(resultCode, &errorDescription, nullptr);
    constexpr size_t maxErrorLength = 1000;
    char errorDetails[maxErrorLength];
    errorDetails[0] = 0;
    ziAPIGetLastError(conn, errorDetails, maxErrorLength);
    auto message = std::string{"LabOne C API error "} +
                    std::to_string(resultCode) + ": " + errorDescription +
                    "\nDetails: " + errorDetails;
    throw std::runtime_error(message);
}

int main(int argc, char** argv)
{
    ZIConnection conn = nullptr;
    handleError(conn, ziAPIInit(&conn));
    handleError(
        conn, ziAPIConnectEx(conn, "localhost", 8004, ZI_API_VERSION_6, nullptr)
    );
}
#include <iostream>
#include "ziAPI.h"

/**
* Error handling for the ziAPI.
* Translate C API result codes into C++ exceptions
*
* @param[in] conn        Pointer to the ziConnection for which the value(s) will be set.
* @param[in] resultCode  Result code from the ziAPI.
*/
void handleError(ZIConnection conn, ZIResult_enum resultCode) {
    if (resultCode == ZI_INFO_SUCCESS) {
        return;
    }
    char *errorDescription;
    ziAPIGetError(resultCode, &errorDescription, nullptr);
    constexpr size_t maxErrorLength = 1000;
    char errorDetails[maxErrorLength];
    errorDetails[0] = 0;
    ziAPIGetLastError(conn, errorDetails, maxErrorLength);
    auto message = std::string{"LabOne C API error "} +
                    std::to_string(resultCode) + ": " + errorDescription +
                    "\nDetails: " + errorDetails;
    throw std::runtime_error(message);
}

int main(int argc, char** argv)
{
    ZIConnection conn = nullptr;
    handleError(conn, ziAPIInit(&conn));
    handleError(
        conn, ziAPIConnectEx(conn, "localhost", 8005, ZI_API_VERSION_1, nullptr)
    );
}
ziDAQ('connect', 'localhost', 8004, 6);
ziDAQ('connect', 'localhost', 8005, 1);
using zhinst;

namespace LabOneTutorial
{
    class Program
    {
        static void Main(string[] args)
        {
            ziDotNET daq = new ziDotNET();
            daq.init("localhost", 8004, (ZIAPIVersion_enum)6);
        }
    }
}
using zhinst;

namespace LabOneTutorial
{
    class Program
    {
        static void Main(string[] args)
        {
            ziDotNET daq = new ziDotNET();
            daq.init("localhost", 8005, (ZIAPIVersion_enum)1);
        }
    }
}

LabView specific

It is important that each LabOne session to a data server is closed at the end of its usage. Reason being that LabView is often executed in a loop and sessions would pile up otherwise.

block diagram
Figure 1: block diagram

front panel
Figure 2: front panel

LabView specific

The API level is set automatically and does not need to be provided

A host is identified through its host name and port. Since we are running the data server locally, the host name is localhost. The default port for the LabOne Data Server is 8004 (8005 for the HF2). The last parameter is the API level. Which is simply set to the latest available level 6 (1 for the HF2).

Reading Data from the Data Server

Now that we are connected to the Data Server, we already can exchange settings and data with it.

All settings and data in LabOne are organized by the Data Server in a file-system-like hierarchical structure called the node tree.

print(daq.listNodes("/ZI/", recursive=true, leavesonly=true))
print(list(session.child_nodes(recursive=True, leavesonly=True)))
char dataServerNodes[1024];
const uint32_t flags = ZI_LIST_NODES_RECURSIVE | ZI_LIST_NODES_ABSOLUTE | ZI_LIST_NODES_LEAVESONLY;
handleError(conn, ziAPIListNodes(conn, "/ZI/*", dataServerNodes, 1024, flags));
std::cout << "Data Server nodes:\n" << dataServerNodes << std::endl;
ziDAQ('listNodes', '/ZI/*', 7)
const UInt64 flags = ZIListNodes_enum.ZI_LIST_NODES_RECURSIVE || ZIListNodes_enum.ZI_LIST_NODES_LEAVESONLY || ZIListNodes_enum.ZI_LIST_NODES_RECURSIVE;
String dataServerNodes = daq.listNodes("/ZI/*", flags)
System.Diagnostics.Trace.WriteLine(dataServerNodes);

block diagram
Figure 3: block diagram

front panel
Figure 4: front panel

The output should show a list of paths for all leave nodes available on the Data Server. It is easy to see that all Data Server nodes start with /ZI. Within Zurich Instruments all setting, or data sources, can be identified by a path. Following the analogy of a file system, the leave nodes can be seen as files which information can be retrieved or, depending on the permission, also changed.

copyright = daq.getString("/ZI/ABOUT/COPYRIGHT")
print(copyright)
copyright = session.about.copyright()
print(copyright)
char copyright[1024];
unsigned int length = 0;
handleError(conn, ziAPIGetValueString(conn, "/ZI/ABOUT/COPYRIGHT", copyright, &length, 1024));
std::cout << copyright << std::endl;
ziDAQ('getString', '/ZI/ABOUT/COPYRIGHT')
String copyright = daq.getString("/ZI/ABOUT/COPYRIGHT");
System.Diagnostics.Trace.WriteLine(copyright);

block diagram
Figure 5: block diagram

front panel
Figure 6: front panel

The output should be the Copyright notice from the Data Server. Congratulations, you have successfully read information from a Data Server node.

Of course the main purpose of the Data Server is to enable the communication with a device and working only with Data Server only nodes has not much practical value.

Connecting to a Device

Devices are identified by their unique ID, called device id. It can be found on the back of the instrument and starts with DEV followed by a unique number

Warning

The device id is part of the Serial Number (S/N). However the opposite is not true. Often the S/N has a device type prefix, which must removed for the device id.

daq.connectDevice("DEV2345", "1GbE")
device = session.connect_device("DEV2345")

Note

If no interface is specified, Toolkit will use the first Interface that is available. The interface keyword can be used to enforce a specific interface.

handleError(conn, ziAPIConnectDevice(conn, "DEV2345", "1GbE", nullptr));
ziDAQ('connectDevice', 'DEV2345', '1GbE')
daq.connectDevice("DEV2345", "1GbE", "");

block diagram
Figure 7: block diagram

front panel
Figure 8: front panel

This command tells the Data Server to connect to the specified device through the specified interface ("1GbE" for connections through Ethernet and "USB").

Question

If no connection can be established to the specified device, ensure that the device powered on and connected properly.

Interact with a device

Now that the device is connected to the Data Server, we can read and write settings and data from and to the device.

timestamp = daq.getInt("/DEV2345/STATUS/TIME")
print(timestamp)
timestamp = device.status.time()
print(timestamp)
ZIIntegerData timestamp{};
handleError(conn, ziAPIGetValueI(conn, "/DEV2345/STATUS/TIME", &timestamp));
std::cout << "Timestamp: " << timestamp << std::endl;
ziDAQ('getInt', '/DEV2345/STATUS/TIME')
Int64 timestamp = daq.getInt("/DEV2345/STATUS/TIME");
System.Diagnostics.Trace.WriteLine(timestamp);

block diagram
Figure 9: block diagram

front panel
Figure 10: front panel

The output should be a integer value, representing the internal timestamp from the device. It is easy to see that device specific nodes start with the device id, e.g. "/DEV2345" followed by the unique path of the node.

Similar to reading data from the device, we can write settings to the device.

daq.set("/DEV2345/SYSTEM/IDENTIFY", 1)
device.system.identify(True)
handleError(conn, ziAPISetValueI(conn, "/DEV2345/SYSTEM/IDENTIFY", 1));
ziDAQ('setInt', '/DEV2345/SYSTEM/IDENTIFY', 1)
daq.setInt("/DEV2345/SYSTEM/IDENTIFY", 1);

block diagram
Figure 11: block diagram

front panel
Figure 12: front panel

Executing the block above should cause the power LED of the used device to flash for a few seconds.

You successfully connected to your Zurich Instruments device through our API. You also interacted with the device and both read and write data/settings from and to it.