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
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
- 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.
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);
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);
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", "");
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", ×tamp));
std::cout << "Timestamp: " << timestamp << std::endl;
ziDAQ('getInt', '/DEV2345/STATUS/TIME')
Int64 timestamp = daq.getInt("/DEV2345/STATUS/TIME");
System.Diagnostics.Trace.WriteLine(timestamp);
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);
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.