Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@

#### [nidcpower] Unreleased
- Added
- (Common) Driver warning subscription mechanism (fixes [#2088](https://github.com/ni/nimi-python/issues/2088))
- Changed
- Removed

Expand Down Expand Up @@ -522,6 +523,7 @@

#### [nidigital] Unreleased
- Added
- (Common) Driver warning subscription mechanism (fixes [#2088](https://github.com/ni/nimi-python/issues/2088))
- Changed
- Removed

Expand Down Expand Up @@ -755,6 +757,7 @@

#### [nidmm] Unreleased
- Added
- (Common) Driver warning subscription mechanism (fixes [#2088](https://github.com/ni/nimi-python/issues/2088))
- Changed
- Removed

Expand Down Expand Up @@ -1070,6 +1073,7 @@

#### [nifgen] Unreleased
- Added
- (Common) Driver warning subscription mechanism (fixes [#2088](https://github.com/ni/nimi-python/issues/2088))
- Changed
- Removed

Expand Down Expand Up @@ -1446,6 +1450,7 @@

#### [nimodinst] Unreleased
- Added
- (Common) Driver warning subscription mechanism (fixes [#2088](https://github.com/ni/nimi-python/issues/2088))
- Changed
- Removed

Expand Down Expand Up @@ -1670,6 +1675,7 @@
- Enabled selected public APIs
- Basic example
- Documentation for APIs (not final)
- (Common) Driver warning subscription mechanism (fixes [#2088](https://github.com/ni/nimi-python/issues/2088))
- Changed
- Removed

Expand Down Expand Up @@ -1703,6 +1709,7 @@

#### [niscope] Unreleased
- Added
- (Common) Driver warning subscription mechanism (fixes [#2088](https://github.com/ni/nimi-python/issues/2088))
- Changed
- Removed

Expand Down Expand Up @@ -2142,6 +2149,7 @@

#### [nise] Unreleased
- Added
- (Common) Driver warning subscription mechanism (fixes [#2088](https://github.com/ni/nimi-python/issues/2088))
- Changed
- Removed

Expand Down Expand Up @@ -2298,6 +2306,7 @@

#### [niswitch] Unreleased
- Added
- (Common) Driver warning subscription mechanism (fixes [#2088](https://github.com/ni/nimi-python/issues/2088))
- Changed
- Removed

Expand Down Expand Up @@ -2549,6 +2558,7 @@

#### [nitclk] Unreleased
- Added
- (Common) Driver warning subscription mechanism (fixes [#2088](https://github.com/ni/nimi-python/issues/2088))
- Changed
- Removed

Expand Down
8 changes: 6 additions & 2 deletions build/templates/_grpc_stub_interpreter.py.mako
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ from . import ${c['file_name']} as ${c['file_name']} # noqa: F401
class GrpcStubInterpreter(object):
'''Interpreter for interacting with a gRPC Stub class'''

def __init__(self, grpc_options):
def __init__(self, grpc_options, warning_event_handler: errors.DriverWarningEvent):
self._grpc_options = grpc_options
self._lock = threading.RLock()
self._client = ${module_name}_grpc.${service_class_prefix}Stub(grpc_options.grpc_channel)
self._warning_event_handler = warning_event_handler
self.set_session_handle()

def set_session_handle(self, value=session_grpc_types.Session()):
Expand Down Expand Up @@ -82,7 +83,10 @@ class GrpcStubInterpreter(object):
error_message = self.error_message(error_code)
except errors.Error:
error_message = 'Failed to retrieve error description.'
warnings.warn(errors.DriverWarning(error_code, error_message))
driver_warning = errors.DriverWarning(error_code, error_message)
if self._warning_event_handler is not None:
self._warning_event_handler.notify(driver_warning)
warnings.warn(driver_warning)
return response
% for func_name in sorted(functions):
% for method_template in functions[func_name]['method_templates']:
Expand Down
11 changes: 10 additions & 1 deletion build/templates/_library_interpreter.py.mako
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ class LibraryInterpreter(object):
* Converting errors returned by Library into Python exceptions.
'''

def __init__(self, encoding):
def __init__(self, encoding, warning_event_handler: errors.DriverWarningEvent):
self._encoding = encoding
self._library = _library_singleton.get()
self._warning_event_handler = warning_event_handler
% if 'SetRuntimeEnvironment' in functions:
global _was_runtime_environment_set
if _was_runtime_environment_set is None:
Expand Down Expand Up @@ -112,6 +113,14 @@ class LibraryInterpreter(object):
def get_session_handle(self):
return self._${config['session_handle_parameter_name']}

def generate_driver_warning_event(self, driverwarning: errors.DriverWarning):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing an underscore
driverwarning -> driver_warning

'''generate_driver_warning_event

Generates a driver warning event.
'''
if self._warning_event_handler is not None:
self._warning_event_handler.notify(driverwarning)

<%include file="/_library_interpreter.py/_get_error_description.py.mako" args="config=config" />\
% for func_name in sorted(functions):
% for method_template in functions[func_name]['method_templates']:
Expand Down
26 changes: 25 additions & 1 deletion build/templates/errors.py.mako
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,28 @@ class SelfTestError(Error):


% endif
class DriverWarningEvent:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Shouldn't this class have the word "handler" in its name? It doesn't represent an event, it represents a class that handles warnings.
  2. Does the word "Event" add value? Is it some Python convention that there are "warning events" or can we just say "warning"?
  3. Is this class something that customers interact with or is it internal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The driver_warning_event member of session object will be of this class type. Thought process behind the name was that since user is going to call subscribe on this member, driver_warning_event.subscribe(callback_function) would look more apt than driver_warning_event_handler.subscribe(callback_function). The class essentially provides methods for handling of warnings as events, but its the handle_error method which actually handles warnings overall.

DriverWarning is already being used as the name for the class which houses the details of warning thrown by the driver(inherits native python Warning class). I guess "Event" here is only conveying that the action is going to be taken only in the event of a driver warning.

Maybe we can have the name of the class as DriverWarningEventHandler and the member instance in the session object can still have the name driver_warning_event?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you guys invent this whole thing from scratch or did you derive from another NI driver or something else?

I'm wondering about terminology, etc. but not if it introduces inconsistency across our drivers or even Python conventions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of warning event is derived from RFSG and RFSA drivers' .NET API experience (explained here). Implementation was just based on expected behavior. I do not have a single reference as such from other python modules, but it was sort of the approach taken generally wherever folks have implemented a event handler in python.

'''Event handler for driver warnings.'''

def __init__(self):
self.subscribers = []

def subscribe(self, callback):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing callback is not a callback, but rather a function? name should be descriptive and accurate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Will change this to say function_to_callback instead.

"""Subscribe to warning events."""
if callback not in self.subscribers:
self.subscribers.append(callback)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you support subscribing the same thing N times? what is the use case?
if not: you should raise and have unit test coverage for the logic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current implementation does not restrict the users to have just one callback function. Went with this approach mostly to match the RFSA/G .NET API behavior. I do not see a reason for restricting as such, however I do not have a known use case for this either. This could enable handling different warnings in their own way by defining different callbacks and registering all of them. Gives a flexibility that potentially can lead to a cleaner user application.


def unsubscribe(self, callback):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it ok to unsubscribe a callback function that was not subscribed?
If yes, make sure that's covered in unit tests with the proper behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current implementation, yes it is ok for the users to do this. Thought process was to not be strict on this regard since the result of unsubscription or not being notified again occurs anyways.

Will add a unit test for this behavior.

"""Unsubscribe from warning events."""
if callback in self.subscribers:
self.subscribers.remove(callback)

def notify(self, driver_warning: DriverWarning):
"""Notify all subscribers about the warning."""
for callback in self.subscribers:
callback(driver_warning)


def handle_error(library_interpreter, code, ignore_warnings, is_error_handling):
'''handle_error

Expand All @@ -142,4 +164,6 @@ def handle_error(library_interpreter, code, ignore_warnings, is_error_handling):
raise DriverError(code, description)

assert _is_warning(code)
warnings.warn(DriverWarning(code, description))
driver_warning = DriverWarning(code, description)
library_interpreter.generate_driver_warning_event(driver_warning)
warnings.warn(driver_warning)
27 changes: 22 additions & 5 deletions build/templates/session.py.mako
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,9 @@ class Session(_SessionBase):

<%
ctor_for_docs = init_function
import copy
ctor_for_docs = copy.deepcopy(ctor_for_docs)
if grpc_supported:
import copy
ctor_for_docs = copy.deepcopy(ctor_for_docs)
ctor_for_docs['parameters'].append(
{
'default_value': None,
Expand All @@ -245,17 +245,34 @@ if grpc_supported:
'use_in_python_api': False,
},
)

ctor_for_docs['parameters'].append(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

injecting metadata in a mako template seems very hacky
@ni-jfitzger do we have prior art for something like this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ni-jfitzger do we have prior art for something like this?

Unfortunately, we do.
It's just a few lines up.

ctor_for_docs['parameters'].append(

I agree. It's hacky. It should only be done if it's not possible to include directly in our metadata.
I consider the "prior art" to be tech debt.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is here since my initial approach was to try to restrict this only for rfsg. This was conditional inject at that point. Given the approach now is to apply for all drivers, I could move this to config metadata and consume from there.

{
'default_value': None,
'direction': 'in',
'documentation': { 'description': 'Driver warning event which can be subscribed to, with a callback method.\nSample callback method:\n\ndef sample_callback_method(driver_warning: ' + module_name + '.DriverWarning):\n print(str(driver_warning))\n' },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find this description to be very clear or helpful. Once we nail the class terminology we need to workshop this.

'enum': None,
'is_repeated_capability': False,
'is_session_handle': False,
'python_name': 'driver_warning_event',
'size': {'mechanism': 'fixed', 'value': 1},
'type_in_documentation': module_name + '.DriverWarningEvent',
'type_in_documentation_was_calculated': False,
'use_in_python_api': False,
},
)
%>\
${helper.get_function_docstring(ctor_for_docs, False, config, indent=8)}
'''
driver_warning_event = errors.DriverWarningEvent()
% if grpc_supported:
if grpc_options:
import ${module_name}._grpc_stub_interpreter as _grpc_stub_interpreter
interpreter = _grpc_stub_interpreter.GrpcStubInterpreter(grpc_options)
interpreter = _grpc_stub_interpreter.GrpcStubInterpreter(grpc_options, warning_event_handler=driver_warning_event)
else:
interpreter = _library_interpreter.LibraryInterpreter(encoding='windows-1251')
interpreter = _library_interpreter.LibraryInterpreter(encoding='windows-1251', warning_event_handler=driver_warning_event)
% else:
interpreter = _library_interpreter.LibraryInterpreter(encoding='windows-1251')
interpreter = _library_interpreter.LibraryInterpreter(encoding='windows-1251', warning_event_handler=driver_warning_event)
% endif

# Initialize the superclass with default values first, populate them later
Expand Down
8 changes: 6 additions & 2 deletions generated/nidcpower/nidcpower/_grpc_stub_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
class GrpcStubInterpreter(object):
'''Interpreter for interacting with a gRPC Stub class'''

def __init__(self, grpc_options):
def __init__(self, grpc_options, warning_event_handler: errors.DriverWarningEvent):
self._grpc_options = grpc_options
self._lock = threading.RLock()
self._client = nidcpower_grpc.NiDCPowerStub(grpc_options.grpc_channel)
self._warning_event_handler = warning_event_handler
self.set_session_handle()

def set_session_handle(self, value=session_grpc_types.Session()):
Expand Down Expand Up @@ -71,7 +72,10 @@ def _invoke(self, func, request, metadata=None):
error_message = self.error_message(error_code)
except errors.Error:
error_message = 'Failed to retrieve error description.'
warnings.warn(errors.DriverWarning(error_code, error_message))
driver_warning = errors.DriverWarning(error_code, error_message)
if self._warning_event_handler is not None:
self._warning_event_handler.notify(driver_warning)
warnings.warn(driver_warning)
return response

def abort(self, channel_name): # noqa: N802
Expand Down
11 changes: 10 additions & 1 deletion generated/nidcpower/nidcpower/_library_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ class LibraryInterpreter(object):
* Converting errors returned by Library into Python exceptions.
'''

def __init__(self, encoding):
def __init__(self, encoding, warning_event_handler: errors.DriverWarningEvent):
self._encoding = encoding
self._library = _library_singleton.get()
self._warning_event_handler = warning_event_handler
global _was_runtime_environment_set
if _was_runtime_environment_set is None:
try:
Expand All @@ -91,6 +92,14 @@ def set_session_handle(self, value=0):
def get_session_handle(self):
return self._vi

def generate_driver_warning_event(self, driverwarning: errors.DriverWarning):
'''generate_driver_warning_event

Generates a driver warning event.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it mean to "generate a driver warning event"?
Are you trying to say this is a function that should be called whenever the driver returns a warning?
What calls this function (is it internal only or is it called by clients of this class)? In other words should this be prefixed by an underscore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We call this function from handle_error function in our errors.py(similar to get_error_description placed next to this method). Hence the lack of underscore. This should be public method of library interpreter which owns the warning_event_handler and has interaction with a utility function in errors.py. Completely internal though. Idea was to not expose warning event handler's internal operations in session object. Library interpreter object owning the current session's warning_event_handler instance was making sense since there should be just one handler per session.

'''
if self._warning_event_handler is not None:
self._warning_event_handler.notify(driverwarning)

def get_error_description(self, error_code):
'''get_error_description

Expand Down
26 changes: 25 additions & 1 deletion generated/nidcpower/nidcpower/errors.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparison with other APIs:

NIDCPower .NET warnings:

  • You use session.DriverOperation.Warning to register/unregister warning event handlers
  • The event handler delegate takes object sender as well as the warning event args. I think the sender is the session object, so you can call back into it.

DAQmx .NET warnings:

  • You use the DaqSystem.Warning property to register/unregister warning event handlers. This is on a singleton object.
  • You can use the DaqSystem.LastDaqWarning property to get the most recent warning and DaqSystem.ClearLastDaqWarning() to clear it. These are thread-local.

DAQmx Python events:

Copy link
Contributor

@bkeryan bkeryan Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One missing feature is the ability to pass the session to the warning handler so it can call back into the session. You can work around this by using a different callable for each session.

Note that this creates a reference cycle: session -> interpreter -> warning_event_handler -> subscribers -> callable -> session

It's good if closing the session breaks the cycle, but apparently Python's GC can handle reference cycles.

Customers may want to use a weak ref to break the cycle. If the Session class has slots, it needs to include the __weakref__ slot.

Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,28 @@ def __init__(self, code, msg):
super(SelfTestError, self).__init__('Self-test failed with code {}: {}'.format(code, msg))


class DriverWarningEvent:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bkeryan Hey Brad, question for you:

As of now, nimi-python drivers just log warnings. This PR adds a way for clients to pass a callback function for whenever a warning occurs so that clients can customize the behavior.

Do you know what other driver Python APIs do with warnings?
Does this seem like a mechanism worth standardizing across the NI driver Python APIs so that customers benefit from the consistency?

I don't want to invent something only to have nisyscfg or nidaqmx invent something different for the sake of being different.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bkeryan , any thoughts on this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of now, nimi-python drivers just log warnings.

I think using the standard Python warnings module is more flexible than just logging. It also allows applications to catch warnings or treat them as errors, and testing tools like pytest show warnings more prominently than logging.

Do you know what other driver Python APIs do with warnings?

nidaqmx reports them via the standard Python warnings module.

The DAQmx .NET API uses an event like this, though.

Does this seem like a mechanism worth standardizing across the NI driver Python APIs so that customers benefit from the consistency?

I don't know. I think #2088 is the only request I've seen for this capability in Python.

'''Event handler for driver warnings.'''

def __init__(self):
self.subscribers = []

def subscribe(self, callback):
"""Subscribe to warning events."""
if callback not in self.subscribers:
self.subscribers.append(callback)

def unsubscribe(self, callback):
"""Unsubscribe from warning events."""
if callback in self.subscribers:
self.subscribers.remove(callback)

def notify(self, driver_warning: DriverWarning):
"""Notify all subscribers about the warning."""
for callback in self.subscribers:
callback(driver_warning)


def handle_error(library_interpreter, code, ignore_warnings, is_error_handling):
'''handle_error

Expand All @@ -123,4 +145,6 @@ def handle_error(library_interpreter, code, ignore_warnings, is_error_handling):
raise DriverError(code, description)

assert _is_warning(code)
warnings.warn(DriverWarning(code, description))
driver_warning = DriverWarning(code, description)
library_interpreter.generate_driver_warning_event(driver_warning)
warnings.warn(driver_warning)
11 changes: 9 additions & 2 deletions generated/nidcpower/nidcpower/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -7498,16 +7498,23 @@ def __init__(self, resource_name, channels=None, reset=False, options={}, indepe

grpc_options (nidcpower.grpc_session_options.GrpcSessionOptions): MeasurementLink gRPC session options

driver_warning_event (nidcpower.DriverWarningEvent): Driver warning event which can be subscribed to, with a callback method.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the documentation for instance attributes. It's the documentation for the __init__ method arguments, but the variable is not actually an argument.

Sample callback method:

def sample_callback_method(driver_warning: nidcpower.DriverWarning):
print(str(driver_warning))


Returns:
session (nidcpower.Session): A session object representing the device.

'''
driver_warning_event = errors.DriverWarningEvent()
Copy link
Collaborator

@ni-jfitzger ni-jfitzger May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can tell, this doesn't actually allow users to subscribe to these events.
The Session object is unconditionally creating the errors.DriverWarningEvent().
Users can't access it without breaking encapsulation. Even then, not easily. It's a private member of the interpreter object, which is, itself, a private member of the Session object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming might be the culprit here. errors.DriverWarningEvent() is not the event itself, but a class which allows handling of warnings as events. I've handled it somewhat similar to session_handle which gets created during session initialize at session object and then gets passed into library_interpreter for future use. The same instance of DriverWarningEvent() object is at play at the session object(public member) and library interpreter(private member), somewhat like a shared_ptr. Hence the addition of generate_warning_event method in library interpreter to access its private member. Encapsulation is not broken as such here as per my interpretation.

Also, the functionality of this is tested already and I've added a unit test in nifake to check the subscription and notification operation.

I see interpreter member name in session object not starting with _. Maybe there was a need to not treat interpreter as private?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a member variable. It's a local variable in the __init__ method.

if grpc_options:
import nidcpower._grpc_stub_interpreter as _grpc_stub_interpreter
interpreter = _grpc_stub_interpreter.GrpcStubInterpreter(grpc_options)
interpreter = _grpc_stub_interpreter.GrpcStubInterpreter(grpc_options, warning_event_handler=driver_warning_event)
else:
interpreter = _library_interpreter.LibraryInterpreter(encoding='windows-1251')
interpreter = _library_interpreter.LibraryInterpreter(encoding='windows-1251', warning_event_handler=driver_warning_event)

# Initialize the superclass with default values first, populate them later
super(Session, self).__init__(
Expand Down
8 changes: 6 additions & 2 deletions generated/nidigital/nidigital/_grpc_stub_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
class GrpcStubInterpreter(object):
'''Interpreter for interacting with a gRPC Stub class'''

def __init__(self, grpc_options):
def __init__(self, grpc_options, warning_event_handler: errors.DriverWarningEvent):
self._grpc_options = grpc_options
self._lock = threading.RLock()
self._client = nidigital_grpc.NiDigitalStub(grpc_options.grpc_channel)
self._warning_event_handler = warning_event_handler
self.set_session_handle()

def set_session_handle(self, value=session_grpc_types.Session()):
Expand Down Expand Up @@ -69,7 +70,10 @@ def _invoke(self, func, request, metadata=None):
error_message = self.error_message(error_code)
except errors.Error:
error_message = 'Failed to retrieve error description.'
warnings.warn(errors.DriverWarning(error_code, error_message))
driver_warning = errors.DriverWarning(error_code, error_message)
if self._warning_event_handler is not None:
self._warning_event_handler.notify(driver_warning)
warnings.warn(driver_warning)
return response

def abort(self): # noqa: N802
Expand Down
11 changes: 10 additions & 1 deletion generated/nidigital/nidigital/_library_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ class LibraryInterpreter(object):
* Converting errors returned by Library into Python exceptions.
'''

def __init__(self, encoding):
def __init__(self, encoding, warning_event_handler: errors.DriverWarningEvent):
self._encoding = encoding
self._library = _library_singleton.get()
self._warning_event_handler = warning_event_handler
global _was_runtime_environment_set
if _was_runtime_environment_set is None:
try:
Expand All @@ -89,6 +90,14 @@ def set_session_handle(self, value=0):
def get_session_handle(self):
return self._vi

def generate_driver_warning_event(self, driverwarning: errors.DriverWarning):
'''generate_driver_warning_event

Generates a driver warning event.
'''
if self._warning_event_handler is not None:
self._warning_event_handler.notify(driverwarning)

def get_error_description(self, error_code):
'''get_error_description

Expand Down
Loading