Skip to content

Sideband Streaming Example

Raghav-NI edited this page Dec 23, 2024 · 2 revisions

This page contains an example of how to use sideband streaming with the DAQmx or FPGA APIs.

Follow the instructions on Setup a Client for Streaming for the initial setup required to proceed with sideband streaming.

Adding Client Code for Sideband Streaming

For this example, we will use the BeginWriteArrayI16 and BeginReadArrayI16 APIs, which are part of the sideband streaming functionality.

These APIs utilize sideband streaming to transfer data between the client and the server. To see an example that uses gRPC streaming, refer to the Setup a Client for Streaming.

Below is the client code for initiating a sideband stream using these APIs:

auto session = std::make_unique<nidevice_grpc::Session>();

std::vector<pb::int16> write_data_int16 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<pb::int16> read_data_int16 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

auto request_write = BeginWriteArrayI16Request{};
request_write.mutable_session()->CopyFrom(*session);
request_write.set_control(1);

auto response = BeginWriteArrayI16Response{};
stub->BeginWriteArrayI16(&context, request_write, &response);

auto request_read = BeginReadArrayI16Request{};
request_read.mutable_session()->CopyFrom(*session);
request_read.set_indicator(0);
request_read.set_size(read_data_int16 .size());

auto response_read = BeginReadArrayI16Response{};
stub->BeginReadArrayI16(&context, request_read, &response_read);

auto write_moniker_i16 = new ni::data_monikers::Moniker(response.moniker());
auto read_moniker_i16 = new ni::data_monikers::Moniker(response_read.moniker());

grpc::ClientContext moniker_context;
ni::data_monikers::BeginMonikerSidebandStreamRequest sideband_request;
ni::data_monikers::BeginMonikerSidebandStreamResponse sideband_response;
sideband_request.set_strategy(ni::data_monikers::SidebandStrategy::SOCKETS);
sideband_request.mutable_monikers()->mutable_read_monikers()->AddAllocated(read_moniker_i16);
sideband_request.mutable_monikers()->mutable_write_monikers()->AddAllocated(write_moniker_i16);

auto write_stream = moniker_stub().get()->BeginSidebandStream(&moniker_context, sideband_request, &sideband_response);
auto sideband_token = InitClientSidebandData(sideband_response);

for (int i = 0; i < 5; i++) {
  nifpga_grpc::MonikerWriteArrayI16Request write_values_array_i16;
  write_values_array_i16.mutable_array()->Add(write_data_int16.begin(), write_data_int16.end());

  ni::data_monikers::SidebandWriteRequest write_data_request;
  write_data_request.mutable_values()->add_values()->PackFrom(write_values_array_i16);

  WriteSidebandMessage(sideband_token, write_data_request);

  nifpga_grpc::MonikerReadArrayI16Response read_values_i16;
  ni::data_monikers::SidebandReadResponse read_result;
  ReadSidebandMessage(sideband_token, &read_result);

  read_result.values().values(0).UnpackTo(&read_values_i16);
}

ni::data_monikers::SidebandWriteRequest cancel_request;
cancel_request.set_cancel(true);
WriteSidebandMessage(sideband_token, cancel_request);
CloseSidebandData(sideband_token);auto request_read = BeginReadArrayI16Request{};

Code Explanation

We will now break down the client code into smaller parts, explaining each component:

a. Initialize the Session

This line creates a new instance of a Session object, which represents the connection to the NI device. It is required for any communication between the client and the device. This session will be passed to the requests for both write and read operations.

b. Sample Data

std::vector<pb::int16> write_data_int16 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<pb::int16> read_data_int16 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

Here, vector of integers is initialized with sample read and write data.

c. Prepare Write Request

auto request_write = BeginWriteArrayI16Request{};
request_write.mutable_session()->CopyFrom(*session);
request_write.set_control(1);
  • auto request_write = BeginWriteArrayI16Request{};: This creates an object of BeginWriteArrayI16Request which is defined in the grpc.pb.h file (generated from the .proto file).

  • BeginWriteArrayI16Request defines the structure of the request for writing a 16-bit integer array.

  • The session is copied from the previously created session object, and the control value is set to guide the write operation.

d. Call BeginWriteArrayI16 API

auto response = BeginWriteArrayI16Response{};
stub->BeginWriteArrayI16(&context, request_write, &response);
  • This sends the write request to the server using the BeginWriteArrayI16 API,

  • The response is stored in the response object. The stub represents the gRPC client connection to the server, which processes the request.

e. Prepare Read Request

auto request_read = BeginReadArrayI16Request{};
request_read.mutable_session()->CopyFrom(*session);
request_read.set_indicator(0);
request_read.set_size(read_data_int16.size());

Similarly, we will create an object of BeginReadArrayI16Request. The session is copied, the indicator is set (which could be used to determine which data to read), and the size of the data to be read is set to match the size of read_data_int16.

f. Call BeginReadArrayI16

auto response_read = BeginReadArrayI16Response{};
stub->BeginReadArrayI16(&context, request_read, &response_read);

Just like BeginWriteArrayI16 API, we will call BeginReadArrayI16 and store the response. This will initiate the reading of data from the device based on the session and size parameters.

g. Create Monikers for Write and Read

auto write_moniker_i16 = new ni::data_monikers::Moniker(response.moniker());
auto read_moniker_i16 = new ni::data_monikers::Moniker(response_read.moniker());

Here, we create two Moniker objects: one for the write data and one for the read data. These monikers are created from the responses received from the write and read operations.

h. Prepare Sideband Stream Request

grpc::ClientContext moniker_context;
ni::data_monikers::BeginMonikerSidebandStreamRequest sideband_request;
ni::data_monikers::BeginMonikerSidebandStreamResponse sideband_response;
sideband_request.set_strategy(ni::data_monikers::SidebandStrategy::SOCKETS);
sideband_request.mutable_monikers()->mutable_read_monikers()->AddAllocated(read_moniker_i16);
sideband_request.mutable_monikers()->mutable_write_monikers()->AddAllocated(write_moniker_i16);
  • A BeginMonikerSidebandStreamRequest is created to initiate a sideband stream. This request includes the monikers for both the read and write data, which were created earlier. The strategy is set to SOCKETS, indicating the use of sockets for the sideband stream.

  • The mutable_monikers() method accesses the Monikers field of the request.

  • The mutable_read_monikers() and mutable_write_monikers() methods add the previously created monikers for the read and write streams, respectively. These monikers uniquely identify the data streams to be associated with the sideband connection.

  • The AddAllocated() method transfers ownership of the read_moniker_i16 and write_moniker_i16 to the request, ensuring they are properly managed during the request's lifecycle.

i. Start the Sideband Stream

auto write_stream = moniker_stub().get()->BeginSidebandStream(&moniker_context, sideband_request, &sideband_response);
auto sideband_token = InitClientSidebandData(sideband_response);

The sideband stream is initiated by calling BeginSidebandStream. The response contains information about the stream, and a sideband token is generated using InitClientSidebandData, which will be used to manage the data during streaming.

j. Streaming in a Loop

for (int i = 0; i < 5; i++) {
  nifpga_grpc::MonikerWriteArrayI16Request write_values_array_i16;
  write_values_array_i16.mutable_array()->Add(write_data_int16.begin(), 
  write_data_int16.end());

  ni::data_monikers::SidebandWriteRequest write_data_request;
  write_data_request.mutable_values()->add_values()->PackFrom(write_values_array_i16);

  WriteSidebandMessage(sideband_token, write_data_request);

  nifpga_grpc::MonikerReadArrayI16Response read_values_i16;
  ni::data_monikers::SidebandReadResponse read_result;
  ReadSidebandMessage(sideband_token, &read_result);

  read_result.values().values(0).UnpackTo(&read_values_i16);
}
  • Prepare the data to be written

    • A MonikerWriteArrayI16Request object is created to hold the write data for the current iteration.
    • The mutable_array() method provides access to the data array, and the Add() function appends the contents of write_data_int16 (a pre-defined vector) to this array. This prepares the data to be streamed to the server.
  • Pack the write data into a sideband write request

    • A SidebandWriteRequest object is created to encapsulate the write data for the sideband stream.
    • The mutable_values() method provides access to the values field, which is used to store the packed data. * PackFrom() serializes the write_values_array_i16 data into a format that the server can understand and process.
  • Send the packed write data to the sideband stream

    • The WriteSidebandMessage() function sends the write_data_request to the server using the sideband stream identified by sideband_token.
  • Prepare a container for the read response

    • read_values_i16: This will store the unpacked read data in a format that the client can process.
    • read_result: This will store the raw response data from the server's sideband stream.
  • Read the response data from the sideband stream

    • The ReadSidebandMessage() function fetches data from the sideband stream, using the sideband_token to identify the stream.
    • The server sends the data it has read, which is stored in read_result in its raw packed form.
  • Unpack the read response

    • The values() method of read_result accesses the packed data from the sideband stream.
    • The UnpackTo() method deserializes the packed data from read_result and stores it in read_values_i16.
    • At this point, the client has successfully read and unpacked the data sent by the server.

Table of Contents

Internal Development

Creating and Setting Up a gRPC Server

Server Security Support

Creating a gRPC Client

gRPC Client Examples

Session Utilities API Reference

Driver Documentation

gRPC API Differences From C API

Sharing Driver Sessions Between Clients

C API Docs
NI-DAQmx
NI-DCPOWER
NI-DIGITAL PATTERN DRIVER
NI-DMM
NI-FGEN
NI-FPGA
NI-RFmx Bluetooth
NI-RFmx NR
NI-RFmx WCDMA
NI-RFmx GSM
NI-RFmx CDMA2k
NI-RFmx Instr
NI-RFmx LTE
NI-RFmx SpecAn
NI-RFmx TD-SCDMA
NI-RFmx WLAN
NI-RFSA
NI-RFSG
NI-SCOPE
NI-SWITCH
NI-TCLK
NI-XNET
Clone this wiki locally