Skip to content

Creating a gRPC Client

ni-pkhare edited this page Jul 12, 2021 · 56 revisions

Introduction

Each supported driver API has a corresponding .proto file in a sub-folder containing the driver name. All driver sub-folders are located under the generated folder here. The .proto file describes the API used by clients to interact with the ni_grpc_device_server and the NI device(s) connected to the server. For example, the .proto file for NI-SCOPE can be found (here). There is an additional .proto file that describes the Session Utilities API.

The information in the remainder of this document applies to creating a gRPC client in Python but gRPC also supports writing clients in additional languages. In order to create a client in languages other than Python:

  1. See the gRPC documentation for a full list of supported languages.
  2. Refer to this section of the Protocol Buffers documentation to learn how to generate the code needed to work with the desired API's messages as defined in each .proto file.
  3. Navigate to the client section of the relevant Basics Tutorial for more details on writing client code in a given language. An example in C++ can be found here

Creating a Python gRPC Client

  1. Install the grpcio-tools using your preferred Python package manager. Two options are outlined below:

    PIP

    > pip install grpcio-tools
    

    Anaconda

    > conda install grpcio-tools
    
  2. Determine whether to use the standard proto compiler or the Better Protobuf compiler to generate the supporting files from each .proto file. The latter produces a more idiomatic version of the gRPC API. For more information refer to the Better Protobuf repository here. If using the Better Protobuf compiler then install the betterproto tools, otherwise skip to the next step:

    > pip install "betterproto[compiler]"
    

    Note: The Better Protobuf compiler has a bug generating helpers for gRPC messages with oneof types. The problem is that zero-value messages in a oneof group take priority based on order and overwrite other set values. Therefore, only the last field, the field that has '_raw` appended to the name, can be properly set without wrapper modification.

  3. Generate the supporting files for each API required by the client:

    Example commands ran from directory containing example file as organized in the client release

    a. Standard compiler:

    > python -m grpc_tools.protoc -I="..\..\proto" --python_out=. --grpc_python_out=. session.proto
    > python -m grpc_tools.protoc -I="..\..\proto" --python_out=. --grpc_python_out=. niscope.proto
    

    Output for NI-SCOPE will be session_pb2.py, session_pb2_grpc.py, niscope_pb2.py, and niscope_pb2_grpc.py.

    b. Better Protobuf compiler:

    >  mkdir nidevice
    >  cd nidevice
    >  python -m grpc_tools.protoc -I="..\..\..\proto" --python_betterproto_out=. --grpc_python_out=. session.proto
    >  python -m grpc_tools.protoc -I="..\..\..\proto" --python_betterproto_out=. --grpc_python_out=. niscope.proto
    

    Output for NI-SCOPE will be __init__.py, niscope_pb2_grpc.py, session_pb2_grpc.py, and a grpc folder.

    In the example above each command is executed from the folder containing the referenced .proto file. The directory containing the proto file being used as well as path(s) to any imports must be specified using the -I flag. The examples above demonstrate generation on Windows but you can adjust the path delimiters as needed for the platform being used. The driver .proto files reference the session.proto file and therefore the path to that file must also be specified, if in separate directory.

  4. Copy the output files (and folder) from the Step 3 to the folder containing the python client.

  5. Add module imports:

    a. Standard compiler:

    import grpc
    import niscope_pb2 as niscope_types
    import niscope_pb2_grpc as grpc_niscope

    b. Better Protobuf compiler:

    import asyncio
    from nidevice import niscope_grpc
    from grpclib.client import Channel

    Note Install and import other libraries like matplotlib as required.

  6. Create a secure or insecure channel based on the server's security configuration. Refer to this wiki page for details.

  7. Write API specific calls to get data from your device. Refer to examples here.

Converting C API Calls to gRPC

The gRPC API is based on the C APIs for each supported driver. See Driver Documentation for finding the right documentation for you. Applications developed against the C APIs can be converted to use the gRPC API taking into account the following considerations:

  1. Each of the functions in the C header have a corresponding RPC service method in the .proto file.
    Example: the niScope_InitWithOptions function defined in niScope.h corresponds to the InitWithOptions method on the NiScope service in niscope.proto.

  2. Each RPC method accepts a Request and Response protobuf message where the fields of the Request correspond to the input parameters of the C function and the fields of the Response message correspond to the return value and output parameters in the C function.

    • Input parameters related to the size of output arrays and strings may be omitted from the request message. For example, the bufSize parameter of niScope_GetAttributeViString is automatically calculated and is not included in the GetAttributeViStringRequest message in niscope.proto.
    • Initialization functions can accept an additional session name which can be used to enable multiple clients to share a single session to a device.
  3. Many of the constants defined in the driver C header file are grouped into enums in the .proto file. It is recommended to use these enum values in Request/Response message fields.

    • If enum is not available for the value that you want to pass as a function parameter, use corresponding <param_name>_raw parameter for assigning the value directly. For example, If you want to set maximum_time to 10 while calling fetch API below, since enum corresponding to 10 does not exist you should use maximum_time_raw parameter for passing the value.

      fetch_result = dmm_service.fetch(vi = vi, maximum_time_raw = 10)

      NOTE: BetterProto compiler does not support setting enum values in the message containing oneof types. Use raw fields in this case.

    • For CheckAttribute and SetAttribute APIs, enum values for different attributes of that datatype are available under enums associated with that datatype. For example, predefined constants for attribute value parameters of niDMM_SetAttributeViInt32 API are available in NiDmmInt32AttributeValues enum. Similarly for NI-DCPower, attribute value parameters of niDCPower_SetAttributeReal64 API are available in NiDCPowerReal64AttributeValues enum.

      NOTE: No enums are available for GetAttribute APIs

    • For the enums associated with constants defined in C header file which cannot be represented as 32-bit integer (string, float), <param_name>_mapped field should be used for storing such values. For example, the current_source field of ConfigureCurrentSource API of NI-DMM accepts a float value, so current_source_mapped field should be used instead.

      configure_current_source = dmm_service.ConfigureCurrentSource(vi=vi, current_source_mapped = nidmm_grpc.NiDmmNonInt32BitAttributeValues.NON_INT_32_BIT_NIDMM_VAL_1_MICROAMP)

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