Example GetSingleSample#

  1/// Example illustrating the use of the LabOne C API to acquire a single fresh
  2/// demodulator sample from a device.
  3///
  4/// Note that similar functionality is provided by the `ziAPIGetDemodSample` API
  5/// function. The code here can be adapted easily for any other type of sample
  6/// from a streaming node, such as impedance or PID samples. It gives more
  7/// control over the acquisition process and can be used to return samples of
  8/// several streaming nodes with precise alignment.
  9///
 10/// The main steps to acquire a new sample are the following:
 11/// - Subscribe to the streaming node.
 12/// - Request the value of the status/time node. This is done with
 13/// - `ziAPIAsyncGetValueAsPollData`, which returns the result in a later
 14///   call to `ziAPIPollDataEx`.
 15/// - Once this timestamp is received, wait for the first sample with a newer
 16///   timestamp. Data in the same `ZIEvent` structure prior to this timestamp
 17///   is discarded.
 18///
 19#include <algorithm>
 20#include <cctype>
 21#include <chrono>
 22#include <cstddef>
 23#include <cstdint>
 24#include <cstring>
 25#include <functional>
 26#include <iostream>
 27#include <optional>
 28#include <string>
 29#include <thread>
 30
 31#include "ziAPI.h"
 32#include "ziUtils.hpp"
 33
 34/// Checks whether two node paths are considered equal.
 35///
 36//// (Implemented as a case insenstive string comparison.)
 37bool pathsEqual(const std::string& path, const std::string& otherPath)
 38{
 39  return (path.size() == otherPath.size()) &&
 40         std::equal(
 41             path.begin(),
 42             path.end(),
 43             otherPath.begin(),
 44             [](char a, char b) { return std::tolower(a) == std::tolower(b); });
 45}
 46
 47/// Translate C API result codes into C++ exceptions
 48void handleError(ZIConnection conn, ZIResult_enum resultCode)
 49{
 50  if (resultCode == ZI_INFO_SUCCESS)
 51  {
 52    return;
 53  }
 54  char* errorDescription;
 55  ziAPIGetError(resultCode, &errorDescription, nullptr);
 56  constexpr size_t maxErrorLength = 1000;
 57  char errorDetails[maxErrorLength];
 58  errorDetails[0] = 0;
 59  ziAPIGetLastError(conn, errorDetails, maxErrorLength);
 60  auto message = std::string{"LabOne C API error "} +
 61                 std::to_string(resultCode) + ": " + errorDescription +
 62                 "\nDetails: " + errorDetails;
 63  throw std::runtime_error(message);
 64}
 65
 66/// Wrapper around `ziAPIPollDataEx` that returns only once the condition given
 67/// by the predicate function is met. All prior events are discarded. After
 68/// return, `event` contains only the values starting from where the condition
 69/// was first met.
 70ZIResult_enum pollForCondition(
 71    ZIConnection conn,
 72    ZIEvent* event,
 73    uint32_t timeOutMillis,
 74    std::function<bool(const char*, uint32_t, const void*)> predicate)
 75{
 76  for (;;)
 77  {
 78    auto status = ziAPIPollDataEx(conn, event, timeOutMillis);
 79    if (status != ZI_INFO_SUCCESS)
 80    {
 81      return status;
 82    }
 83
 84    size_t elementSize = 0;
 85    switch (event->valueType)
 86    {
 87    case ZI_VALUE_TYPE_DOUBLE_DATA:
 88      elementSize = sizeof(ZIDoubleData);
 89      break;
 90    case ZI_VALUE_TYPE_DOUBLE_DATA_TS:
 91      elementSize = sizeof(ZIDoubleDataTS);
 92      break;
 93    case ZI_VALUE_TYPE_INTEGER_DATA:
 94      elementSize = sizeof(ZIIntegerData);
 95      break;
 96    case ZI_VALUE_TYPE_INTEGER_DATA_TS:
 97      elementSize = sizeof(ZIIntegerDataTS);
 98      break;
 99    case ZI_VALUE_TYPE_COMPLEX_DATA:
100      elementSize = sizeof(ZIComplexData);
101      break;
102    case ZI_VALUE_TYPE_BYTE_ARRAY:
103      elementSize = sizeof(ZIByteArray);
104      break;
105    case ZI_VALUE_TYPE_BYTE_ARRAY_TS:
106      elementSize = sizeof(ZIByteArrayTS);
107      break;
108    case ZI_VALUE_TYPE_CNT_SAMPLE:
109      elementSize = sizeof(ZICntSample);
110      break;
111    case ZI_VALUE_TYPE_TRIG_SAMPLE:
112      elementSize = sizeof(ZITrigSample);
113      break;
114    case ZI_VALUE_TYPE_DEMOD_SAMPLE:
115      elementSize = sizeof(ZIDemodSample);
116      break;
117    case ZI_VALUE_TYPE_AUXIN_SAMPLE:
118      elementSize = sizeof(ZIAuxInSample);
119      break;
120    case ZI_VALUE_TYPE_DIO_SAMPLE:
121      elementSize = sizeof(ZIDIOSample);
122      break;
123    case ZI_VALUE_TYPE_SCOPE_WAVE:
124      elementSize = sizeof(ZIScopeWave);
125      break;
126    case ZI_VALUE_TYPE_SCOPE_WAVE_EX:
127      elementSize = sizeof(ZIScopeWaveEx);
128      break;
129    case ZI_VALUE_TYPE_SCOPE_WAVE_OLD:
130      elementSize = sizeof(ScopeWave);
131      break;
132    case ZI_VALUE_TYPE_PWA_WAVE:
133      elementSize = sizeof(ZIPWAWave);
134      break;
135    case ZI_VALUE_TYPE_SWEEPER_WAVE:
136      elementSize = sizeof(ZISweeperWave);
137      break;
138    case ZI_VALUE_TYPE_SPECTRUM_WAVE:
139      elementSize = sizeof(ZISpectrumWave);
140      break;
141    case ZI_VALUE_TYPE_ADVISOR_WAVE:
142      elementSize = sizeof(ZIAdvisorWave);
143      break;
144    case ZI_VALUE_TYPE_ASYNC_REPLY:
145      elementSize = sizeof(ZIAsyncReply);
146      break;
147    case ZI_VALUE_TYPE_VECTOR_DATA:
148      elementSize = sizeof(ZIVectorData);
149      break;
150    case ZI_VALUE_TYPE_IMPEDANCE_SAMPLE:
151      elementSize = sizeof(ZIImpedanceSample);
152      break;
153    default:
154      // Ignore unsupported value types
155      continue;
156    }
157
158    for (auto i = 0U; i < event->count; ++i)
159    {
160      const void* data = event->data + i * elementSize;
161      if (predicate(
162              reinterpret_cast<const char*>(event->path),
163              event->valueType,
164              data))
165      {
166        if (i > 0)
167        {
168          // Discard previous values in same event
169          event->count = event->count - i;
170          std::memcpy(event->data, data, event->count * elementSize);
171        }
172        return ZI_INFO_SUCCESS;
173      }
174    }
175  }
176}
177
178ZIResult_enum pollForSingleSample(
179    ZIConnection conn, ZIEvent* event, const std::string& samplePath)
180{
181  auto prefixPos = samplePath.find('/', 1);
182  if (prefixPos == std::string::npos)
183  {
184    return ZI_ERROR_INVALID_ARGUMENT;
185  }
186  std::string pathBase(samplePath, 0, prefixPos + 1);
187
188  auto timePath = pathBase + "status/time";
189  auto status = ziAPIAsyncGetValueAsPollData(conn, timePath.c_str(), 1);
190  if (status != ZI_INFO_SUCCESS)
191  {
192    return status;
193  }
194
195  ZIIntegerData currentTimeStamp{};
196
197  constexpr auto timeOutMillis = 50;
198  return pollForCondition(
199      conn,
200      event,
201      timeOutMillis,
202      [&](const char* path, uint32_t type, const void* data)
203      {
204        // TODO: The comparisons below (string, type) are in the hot path --
205        // done for each value in an event. Find a way to short-circuit the ones
206        // which can skip a whole block of values, e.g. by returning a third
207        // state: discard event.
208        const std::string eventPath{path};
209
210        switch (type)
211        {
212        case ZI_VALUE_TYPE_INTEGER_DATA:
213        {
214          if (!pathsEqual(timePath, eventPath))
215          {
216            return false;
217          }
218          currentTimeStamp = *reinterpret_cast<const ZIIntegerData*>(data);
219        }
220        break;
221        case ZI_VALUE_TYPE_INTEGER_DATA_TS:
222        {
223          if (!pathsEqual(timePath, eventPath))
224          {
225            return false;
226          }
227          currentTimeStamp =
228              reinterpret_cast<const ZIIntegerDataTS*>(data)->value;
229        }
230        break;
231        case ZI_VALUE_TYPE_DEMOD_SAMPLE:
232        {
233          if (currentTimeStamp == 0)
234          {
235            return false;
236          }
237          if (!pathsEqual(samplePath, eventPath))
238          {
239            return false;
240          }
241          auto timeStamp =
242              reinterpret_cast<const ZIDemodSample*>(data)->timeStamp;
243          if (timeStamp >= currentTimeStamp)
244          {
245            return true;
246          }
247        }
248        break;
249        default:;
250        }
251        return false;
252      });
253}
254
255int main()
256{
257  const char* serverHost = ziUtilsGetEnv("LABONE_SERVER", "localhost");
258  const char* deviceAddress = ziUtilsGetEnv("LABONE_DEVICE", "dev3225");
259  const char* deviceInterface = ziUtilsGetEnv("LABONE_INTERFACE", "1GbE");
260
261  ZIConnection conn = nullptr;
262  handleError(conn, ziAPIInit(&conn));
263
264  handleError(
265      conn, ziAPIConnectEx(conn, serverHost, 8004, ZI_API_VERSION_6, nullptr));
266  handleError(
267      conn, ziAPIConnectDevice(conn, deviceAddress, deviceInterface, nullptr));
268
269  const auto pathBase = std::string{"/"} + deviceAddress + "/";
270
271  // Enable demodulator
272  handleError(
273      conn, ziAPISetValueI(conn, (pathBase + "demods/0/enable").c_str(), 1));
274
275  // Subscribe to sample node. The samples will be available with
276  // consecutive calls to ziAPIPollDataEx.
277  const auto samplePath = pathBase + "demods/0/sample";
278  handleError(conn, ziAPISubscribe(conn, samplePath.c_str()));
279
280  auto* event = ziAPIAllocateEventEx();
281
282  // All timestamps returned are given in terms of the base clock frequency of
283  // the instrument.
284  ZIDoubleData clockBase{};
285  handleError(
286      conn, ziAPIGetValueD(conn, (pathBase + "clockbase").c_str(), &clockBase));
287
288  double lastTime = 0.0;
289  for (;;)
290  {
291    handleError(conn, pollForSingleSample(conn, event, samplePath));
292    auto timeStamp = event->value.demodSample[0].timeStamp;
293    auto currentTime = timeStamp / clockBase;
294
295    std::cout << "timestamp: " << timeStamp;
296    if (lastTime > 0)
297    {
298      std::cout << "\tdt (s): " << (currentTime - lastTime) << std::endl;
299    }
300    else
301    {
302      std::cout << std::endl;
303    }
304
305    std::this_thread::sleep_for(std::chrono::milliseconds{1000});
306    lastTime = currentTime;
307  }
308
309  // Cleanup
310  ziAPIDeallocateEventEx(event);
311  handleError(conn, ziAPIUnSubscribe(conn, samplePath.c_str()));
312  handleError(conn, ziAPIDestroy(conn));
313}