Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
eabb66a
Initial version
rahur-NI Mar 18, 2026
8bbb854
Enabling grpc for nirfsg
rahur-NI Mar 18, 2026
2769be2
Merge remote-tracking branch 'origin' into nirfsgGrpcEnable
rahur-NI Mar 18, 2026
0d0e209
Updated change log
rahur-NI Mar 18, 2026
89c16cc
nirfsg grpc enable
rahur-NI Mar 20, 2026
fd619a3
With default template update
rahur-NI Mar 20, 2026
86fde87
SIngle template changes
rahur-NI Mar 20, 2026
ffced12
Updated version of numpy template
rahur-NI Mar 22, 2026
b98dcfb
Updating generate temply for numpy_write_method
rahur-NI Mar 23, 2026
d69e843
updating numpy read template instead of write template.
rahur-NI Mar 23, 2026
cd63014
Updated read method
rahur-NI Mar 23, 2026
104ab97
With nifake changes
rahur-NI Mar 23, 2026
5af2c2d
Updated template to use helper methods
rahur-NI Mar 23, 2026
cf39c3b
Updated with proto changes
rahur-NI Mar 23, 2026
66d41ad
Remove log file
rahur-NI Mar 23, 2026
4804429
Merge branch 'master' into nirfsgGrpcComplexNumberSupport
rahur-NI Mar 23, 2026
e2964b7
Code refractoring for template.
rahur-NI Mar 23, 2026
544dcdd
Updated with code review comments
rahur-NI Mar 24, 2026
9033a52
Including more verbose output to check on system test failure
rahur-NI Mar 27, 2026
097a731
Reverting verbose log change of nirfsg system test and moving the tes…
rahur-NI Mar 27, 2026
d6e6f18
Implementation of code review comments.
rahur-NI Mar 27, 2026
1898173
Updating comment for newly added test cases
rahur-NI Mar 27, 2026
feb959a
Updatig with geerated Tox executio
rahur-NI Mar 29, 2026
bbecc5f
System Test failure on 32 bit debug
rahur-NI Mar 30, 2026
a81a954
Reverting the advanced logging mechanism during system tests.
rahur-NI Mar 30, 2026
0217316
Changing order of TestLibrary and TestGrpc classes
rahur-NI Mar 30, 2026
799b901
Skip gRPC tests on 32-bit systems to prevent timeout
rahur-NI Mar 30, 2026
968177c
Revert "Skip gRPC tests on 32-bit systems to prevent timeout"
rahur-NI Mar 30, 2026
b5299d6
Add gRPC server startup timeout and improved error reporting
rahur-NI Mar 30, 2026
c938fbd
Printing eaching line in grpx fixute
rahur-NI Mar 31, 2026
fe5e119
Resolving flake issue
rahur-NI Mar 31, 2026
74d2955
Enhacing Logs
rahur-NI Mar 31, 2026
3e1ea28
Updating the print flow
rahur-NI Mar 31, 2026
ff2591a
Remove Log File
rahur-NI Mar 31, 2026
081b2d0
Enabling reset during session creation
rahur-NI Mar 31, 2026
209dc56
Fixing tox issues
rahur-NI Mar 31, 2026
d299e47
Cleaning up extra print statements
rahur-NI Mar 31, 2026
1c494b6
Clean up of rfsg system test
rahur-NI Mar 31, 2026
df2dade
Skip nirfsg gRPC tests on 32-bit Python environments
rahur-NI Mar 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build/helper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from build.helper.codegen_helper import get_enum_type_check_snippet # noqa: F401
from build.helper.codegen_helper import get_enum_value_snippet # noqa: F401
from build.helper.codegen_helper import get_grpc_interpreter_method_return_snippet # noqa: F401
from build.helper.codegen_helper import get_grpc_response_info # noqa: F401
from build.helper.codegen_helper import get_library_interpreter_method_return_snippet # noqa: F401
from build.helper.codegen_helper import get_parameter_size_check_snippets # noqa: F401
from build.helper.codegen_helper import get_params_snippet # noqa: F401
Expand Down Expand Up @@ -43,6 +44,7 @@
from build.helper.metadata_add_all import add_all_metadata # noqa: F401

from build.helper.metadata_filters import are_complex_parameters_used # noqa: F401
from build.helper.metadata_filters import does_function_use_complex_parameters # noqa: F401
from build.helper.metadata_filters import filter_codegen_attributes # noqa: F401
from build.helper.metadata_filters import filter_codegen_attributes_public_only # noqa: F401
from build.helper.metadata_filters import filter_codegen_enums # noqa: F401
Expand All @@ -52,6 +54,7 @@
from build.helper.metadata_filters import filter_library_functions # noqa: F401
from build.helper.metadata_filters import filter_parameters # noqa: F401
from build.helper.metadata_filters import filter_public_functions # noqa: F401
from build.helper.metadata_filters import get_grpc_complex_request_args_snippet # noqa: F401

from build.helper.metadata_find import find_custom_type # noqa: F401
from build.helper.metadata_find import find_session_handle_parameter # noqa: F401
Expand Down
9 changes: 9 additions & 0 deletions build/helper/codegen_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,15 @@ def get_grpc_interpreter_method_return_snippet(parameters, config):
return ('return ' + ', '.join(snippets)).strip()


def get_grpc_response_info(function, config):
Comment thread
rahur-NI marked this conversation as resolved.
Outdated
'''Returns (return_snippet, response_assignment_prefix) for a gRPC stub method.'''
return_snippet = get_grpc_interpreter_method_return_snippet(function['parameters'], config)
if return_snippet == 'return':
return_snippet = None
response_assignment_prefix = 'response = ' if return_snippet else ''
return return_snippet, response_assignment_prefix


def get_session_method_return_snippet(parameters, config, use_numpy_array=False):
'''Returns a string suitable to use as the return argument of a Session method'''
options = ParameterUsageOptions.API_NUMPY_OUTPUT_PARAMETERS if use_numpy_array else ParameterUsageOptions.API_OUTPUT_PARAMETERS
Expand Down
17 changes: 17 additions & 0 deletions build/helper/metadata_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,3 +499,20 @@ def are_complex_parameters_used(functions):
are_complex_parameters_used = True
break
return are_complex_parameters_used


def does_function_use_complex_parameters(function):
Comment thread
rahur-NI marked this conversation as resolved.
Outdated
'''Returns bool based on whether any complex parameters are used in the function metadata.'''
return bool(filter_parameters(function['parameters'], ParameterUsageOptions.COMPLEX_NUMBER_PARAMETERS))


def get_grpc_complex_request_args_snippet(function, complex_params):
'''Builds the gRPC request args snippet for a function, replacing complex parameter names with _list suffixed versions.'''
from build.helper.codegen_helper import get_params_snippet
snippet = get_params_snippet(function, ParameterUsageOptions.GRPC_REQUEST_PARAMETERS)
for p in complex_params:
snippet = snippet.replace(
p['grpc_name'] + '=' + p['python_name'],
p['grpc_name'] + '=' + p['python_name'] + '_list'
)
return snippet
4 changes: 4 additions & 0 deletions build/templates/_grpc_stub_interpreter.py.mako
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module_name = config['module_name']
proto_name = config.get('proto_name', module_name)
service_class_prefix = config['grpc_service_class_prefix']
functions = helper.filter_codegen_functions(config['functions'])
are_complex_parameters_used = helper.are_complex_parameters_used(functions)
%>\

import grpc
Expand All @@ -19,6 +20,9 @@ import warnings
from . import enums as enums # noqa: F401
% endif
from . import errors as errors
% if are_complex_parameters_used:
from . import nidevice_pb2 as grpc_complex_types # noqa: F401
% endif
from . import ${proto_name}_pb2 as grpc_types
from . import ${proto_name}_pb2_grpc as ${module_name}_grpc
from . import session_pb2 as session_grpc_types
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,55 @@
<%page args="f, config, method_template"/>\
<%
'''Renders a NotImplemented method for to the passed-in function metadata, because numpy is not supported over grpc.'''

'''Renders a GrpcStubInterpreter method for numpy write operations with complex number support.'''
Comment thread
rahur-NI marked this conversation as resolved.
Outdated
import build.helper as helper

parameters = f['parameters']
full_func_name = f['interpreter_name'] + method_template['method_python_name_suffix']
method_decl_params = helper.get_params_snippet(f, helper.ParameterUsageOptions.INTERPRETER_METHOD_DECLARATION)
included_in_proto = f.get('included_in_proto', True)

# Identify numpy parameters with complex number types.
numpy_complex_params = helper.filter_parameters(parameters, helper.ParameterUsageOptions.COMPLEX_NUMBER_PARAMETERS)

# Only generate gRPC implementation if the function has complex parameters and is included in proto
should_generate_complex_grpc_impl = bool(numpy_complex_params) and included_in_proto
Comment thread
rahur-NI marked this conversation as resolved.
Outdated

if should_generate_complex_grpc_impl:
Comment thread
rahur-NI marked this conversation as resolved.
Outdated
grpc_method_name = f.get('grpc_name', f['name'])
grpc_request_args_snippet = helper.get_grpc_complex_request_args_snippet(f, numpy_complex_params)
return_snippet, response_assignment_prefix = helper.get_grpc_response_info(f, config)
%>\

def ${full_func_name}(${method_decl_params}): # noqa: N802
% if should_generate_complex_grpc_impl:
% for p in numpy_complex_params:
% if p['original_type'] == 'NIComplexNumber[]':
${p['python_name']}_list = [
grpc_complex_types.NIComplexNumber(real=val.real, imaginary=val.imag)
for val in ${p['python_name']}.ravel()
]
% elif p['original_type'] == 'NIComplexNumberF32[]':
${p['python_name']}_list = [
grpc_complex_types.NIComplexNumberF32(real=val.real, imaginary=val.imag)
for val in ${p['python_name']}.ravel()
]
% elif p['original_type'] == 'NIComplexI16[]':
arr = ${p['python_name']}.ravel()
if arr.size % 2 != 0:
raise ValueError("Interleaved int16 array must have even length (real/imag pairs)")
arr_pairs = arr.reshape(-1, 2)
${p['python_name']}_list = [
grpc_complex_types.NIComplexI16(real=int(pair[0]), imaginary=int(pair[1]))
for pair in arr_pairs
]
% endif
% endfor
${response_assignment_prefix}self._invoke(
self._client.${grpc_method_name},
grpc_types.${grpc_method_name}Request(${grpc_request_args_snippet}),
)
% if return_snippet:
${return_snippet}
% endif
% else:
raise NotImplementedError('numpy-specific methods are not supported over gRPC')
% endif
41 changes: 37 additions & 4 deletions generated/nifake/nifake/_grpc_stub_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from . import enums as enums # noqa: F401
from . import errors as errors
from . import nidevice_pb2 as grpc_complex_types # noqa: F401
from . import nifake_pb2 as grpc_types
from . import nifake_pb2_grpc as nifake_grpc
from . import session_pb2 as session_grpc_types
Expand Down Expand Up @@ -145,7 +146,14 @@ def fetch_waveform_into(self, number_of_samples): # noqa: N802
raise NotImplementedError('numpy-specific methods are not supported over gRPC')

def function_with_3d_numpy_array_of_numpy_complex128_input_parameter(self, multidimensional_array): # noqa: N802
raise NotImplementedError('numpy-specific methods are not supported over gRPC')
multidimensional_array_list = [
grpc_complex_types.NIComplexNumber(real=val.real, imaginary=val.imag)
for val in multidimensional_array.ravel()
]
self._invoke(
self._client.FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter,
grpc_types.FunctionWith3dNumpyArrayOfNumpyComplex128InputParameterRequest(vi=self._vi, multidimensional_array=multidimensional_array_list),
)

def function_with_intflag_parameter(self, flag): # noqa: N802
self._invoke(
Expand Down Expand Up @@ -512,13 +520,38 @@ def write_waveform_numpy(self, waveform): # noqa: N802
raise NotImplementedError('numpy-specific methods are not supported over gRPC')

def write_waveform_numpy_complex128(self, waveform_data_array): # noqa: N802
raise NotImplementedError('numpy-specific methods are not supported over gRPC')
waveform_data_array_list = [
grpc_complex_types.NIComplexNumber(real=val.real, imaginary=val.imag)
for val in waveform_data_array.ravel()
]
self._invoke(
self._client.WriteWaveformNumpyComplex128,
grpc_types.WriteWaveformNumpyComplex128Request(vi=self._vi, waveform_data_array=waveform_data_array_list),
)

def write_waveform_numpy_complex64(self, waveform_data_array): # noqa: N802
raise NotImplementedError('numpy-specific methods are not supported over gRPC')
waveform_data_array_list = [
grpc_complex_types.NIComplexNumberF32(real=val.real, imaginary=val.imag)
for val in waveform_data_array.ravel()
]
self._invoke(
self._client.WriteWaveformNumpyComplex64,
grpc_types.WriteWaveformNumpyComplex64Request(vi=self._vi, waveform_data_array=waveform_data_array_list),
)

def write_waveform_numpy_complex_interleaved_i16(self, waveform_data_array): # noqa: N802
raise NotImplementedError('numpy-specific methods are not supported over gRPC')
arr = waveform_data_array.ravel()
if arr.size % 2 != 0:
raise ValueError("Interleaved int16 array must have even length (real/imag pairs)")
arr_pairs = arr.reshape(-1, 2)
waveform_data_array_list = [
grpc_complex_types.NIComplexI16(real=int(pair[0]), imaginary=int(pair[1]))
for pair in arr_pairs
]
self._invoke(
self._client.WriteWaveformNumpyComplexInterleavedI16,
grpc_types.WriteWaveformNumpyComplexInterleavedI16Request(vi=self._vi, waveform_data_array=waveform_data_array_list),
)

def close(self): # noqa: N802
self._invoke(
Expand Down
41 changes: 37 additions & 4 deletions generated/nirfsg/nirfsg/_grpc_stub_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from . import enums as enums # noqa: F401
from . import errors as errors
from . import nidevice_pb2 as grpc_complex_types # noqa: F401
from . import nirfsg_pb2 as grpc_types
from . import nirfsg_pb2_grpc as nirfsg_grpc
from . import session_pb2 as session_grpc_types
Expand Down Expand Up @@ -194,7 +195,14 @@ def configure_software_start_trigger(self): # noqa: N802
)

def create_deembedding_sparameter_table_array(self, port, table_name, frequencies, sparameter_table, number_of_ports, sparameter_orientation): # noqa: N802
raise NotImplementedError('numpy-specific methods are not supported over gRPC')
sparameter_table_list = [
grpc_complex_types.NIComplexNumber(real=val.real, imaginary=val.imag)
for val in sparameter_table.ravel()
]
self._invoke(
self._client.CreateDeembeddingSparameterTableArray,
grpc_types.CreateDeembeddingSparameterTableArrayRequest(vi=self._vi, port=port, table_name=table_name, frequencies=frequencies, sparameter_table=sparameter_table_list, number_of_ports=number_of_ports, sparameter_orientation_raw=sparameter_orientation.value),
)
Comment thread
rahur-NI marked this conversation as resolved.

def create_deembedding_sparameter_table_s2p_file(self, port, table_name, s2p_file_path, sparameter_orientation): # noqa: N802
self._invoke(
Expand Down Expand Up @@ -552,13 +560,38 @@ def wait_until_settled(self, max_time_milliseconds): # noqa: N802
)

def write_arb_waveform_complex_f32(self, waveform_name, waveform_data_array, more_data_pending): # noqa: N802
raise NotImplementedError('numpy-specific methods are not supported over gRPC')
waveform_data_array_list = [
grpc_complex_types.NIComplexNumberF32(real=val.real, imaginary=val.imag)
for val in waveform_data_array.ravel()
Comment thread
rahur-NI marked this conversation as resolved.
]
self._invoke(
self._client.WriteArbWaveformComplexF32,
grpc_types.WriteArbWaveformComplexF32Request(vi=self._vi, waveform_name=waveform_name, wfm_data=waveform_data_array_list, more_data_pending=more_data_pending),
)

def write_arb_waveform_complex_f64(self, waveform_name, waveform_data_array, more_data_pending): # noqa: N802
raise NotImplementedError('numpy-specific methods are not supported over gRPC')
waveform_data_array_list = [
grpc_complex_types.NIComplexNumber(real=val.real, imaginary=val.imag)
for val in waveform_data_array.ravel()
]
self._invoke(
self._client.WriteArbWaveformComplexF64,
grpc_types.WriteArbWaveformComplexF64Request(vi=self._vi, waveform_name=waveform_name, wfm_data=waveform_data_array_list, more_data_pending=more_data_pending),
)

def write_arb_waveform_complex_i16(self, waveform_name, waveform_data_array): # noqa: N802
raise NotImplementedError('numpy-specific methods are not supported over gRPC')
arr = waveform_data_array.ravel()
if arr.size % 2 != 0:
raise ValueError("Interleaved int16 array must have even length (real/imag pairs)")
arr_pairs = arr.reshape(-1, 2)
waveform_data_array_list = [
grpc_complex_types.NIComplexI16(real=int(pair[0]), imaginary=int(pair[1]))
for pair in arr_pairs
]
self._invoke(
self._client.WriteArbWaveformComplexI16,
grpc_types.WriteArbWaveformComplexI16Request(vi=self._vi, waveform_name=waveform_name, wfm_data=waveform_data_array_list),
)

def write_script(self, script): # noqa: N802
self._invoke(
Expand Down
8 changes: 4 additions & 4 deletions src/nifake/metadata/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2830,7 +2830,7 @@
'documentation': {
'description': 'A function that writes a waveform of numpy complex64 samples.'
},
'included_in_proto': False,
'included_in_proto': True,
'method_templates': [
{
'documentation_filename': 'numpy_method',
Expand Down Expand Up @@ -2883,7 +2883,7 @@
'documentation': {
'description': 'A function that writes a waveform of numpy complex128 samples.'
},
'included_in_proto': False,
'included_in_proto': True,
'is_error_handling': False,
'method_templates': [
{
Expand Down Expand Up @@ -2937,7 +2937,7 @@
'documentation': {
'description': 'A function that writes a waveform of numpy complex i16 samples.'
},
'included_in_proto': False,
'included_in_proto': True,
'is_error_handling': False,
'method_templates': [
{
Expand Down Expand Up @@ -2991,7 +2991,7 @@
'documentation': {
'description': 'Function that takes a 3D numpy array of numpy complex128 as an input parameter.'
},
'included_in_proto': False,
'included_in_proto': True,
'is_error_handling': False,
'method_templates': [
{
Expand Down
9 changes: 9 additions & 0 deletions src/nirfsg/system_tests/grpc_server_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"address": "[::1]",
"port": 31766,
Comment thread
ni-jfitzger marked this conversation as resolved.
"security" : {
"server_cert": "",
"server_key": "",
"root_cert": ""
}
}
37 changes: 27 additions & 10 deletions src/nirfsg/system_tests/test_system_nirfsg.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import array
import grpc
import hightime
import nirfsg
import numpy as np
Expand Down Expand Up @@ -622,16 +623,6 @@ def test_wait_until_settled(self, rfsg_device_session):
with rfsg_device_session.initiate():
rfsg_device_session.wait_until_settled()

def test_get_all_named_waveform_names(self, rfsg_device_session):
rfsg_device_session.generation_mode = nirfsg.GenerationMode.ARB_WAVEFORM
waveform_data1 = np.full(1000, 1 + 0j, dtype=np.complex128)
waveform_data2 = np.full(800, 1 + 0j, dtype=np.complex128)
rfsg_device_session.write_arb_waveform('waveform1', waveform_data1, False)
rfsg_device_session.write_arb_waveform('waveform2', waveform_data2, False)
names = rfsg_device_session.get_all_named_waveform_names()
assert 'waveform1' in names
assert 'waveform2' in names

@pytest.mark.skipif(use_simulated_session is True, reason="Scripts not compiled on simulated device")
def test_get_all_script_names(self, rfsg_device_session):
rfsg_device_session.generation_mode = nirfsg.GenerationMode.SCRIPT
Expand All @@ -658,3 +649,29 @@ class TestLibrary(SystemTests):
@pytest.fixture(scope='class')
def session_creation_kwargs(self):
return {}

def test_get_all_named_waveform_names(self, rfsg_device_session):
Comment thread
rahur-NI marked this conversation as resolved.
rfsg_device_session.generation_mode = nirfsg.GenerationMode.ARB_WAVEFORM
waveform_data1 = np.full(1000, 1 + 0j, dtype=np.complex128)
waveform_data2 = np.full(800, 1 + 0j, dtype=np.complex128)
rfsg_device_session.write_arb_waveform('waveform1', waveform_data1, False)
rfsg_device_session.write_arb_waveform('waveform2', waveform_data2, False)
names = rfsg_device_session.get_all_named_waveform_names()
assert 'waveform1' in names
assert 'waveform2' in names


class TestGrpc(SystemTests):
@pytest.fixture(scope='class')
def grpc_channel(self):
current_directory = os.path.dirname(os.path.abspath(__file__))
config_file_path = os.path.join(current_directory, 'grpc_server_config.json')
with system_test_utilities.GrpcServerProcess(config_file_path) as proc:
channel = grpc.insecure_channel(f"localhost:{proc.server_port}")
yield channel

@pytest.fixture(scope='class')
def session_creation_kwargs(self, grpc_channel):
grpc_options = nirfsg.GrpcSessionOptions(grpc_channel, "")
return {'grpc_options': grpc_options}

Loading