pyvisa / pyvisa

A Python package with bindings to the "Virtual Instrument Software Architecture" VISA library, in order to control measurement devices and test equipment via GPIB, RS232, or USB.
https://pyvisa.readthedocs.io
MIT License
785 stars 245 forks source link

uninstall_visa_handler does not work #138

Closed lupien closed 9 years ago

lupien commented 9 years ago

The uninstall_visa_handler function of VisaLibraryBase (highlevel.py) always fails because it calls viUninstallHandler with the python function instead of the ctypes wrapped one.

Fixing that, other problems show up. If None was used as the user_handle (the default), it can't be removed (pyvisa 1.4 had the same problem) and if the last handler installed did not have the same ctypes user_handle as for the uninstall call it will also fail (need to call set_user_handle_type to make the definitions match).

So here is how I would fix them (I tested them, and it fixes all my problems): highlevel.py VisaLibraryBase

    def uninstall_visa_handler(self, session, event_type, handler, user_handle=None):
        """Uninstalls handlers for events.

        :param session: Unique logical identifier to a session.
        :param event_type: Logical event identifier.
        :param handler: Interpreted as a valid reference to a handler to be uninstalled by a client application.
        :param user_handle: The user handle (ctypes object or None) returned by install_visa_handler.
        """
        for ndx, element in enumerate(self.handlers[session]):
            if element[0] is handler and element[1] is user_handle:
                del self.handlers[session][ndx]
                break
        else:
            raise errors.UnknownHandler(event_type, handler, user_handle)
        self.uninstall_handler(session, event_type, element[2], user_handle)

and ctwrapper/functions.py

def uninstall_handler(library, session, event_type, handler, user_handle=None):
    """Uninstalls handlers for events.

    Corresponds to viUninstallHandler function of the VISA library.

    :param library: the visa library wrapped by ctypes.
    :param session: Unique logical identifier to a session.
    :param event_type: Logical event identifier.
    :param handler: Interpreted as a valid reference to a handler to be uninstalled by a client application.
    :param user_handle: The user handle (a ctypes object) in the returned value from install_handler
    :return: return value of the library call.
    :rtype: :class:`pyvisa.constants.StatusCode`
    """
    set_user_handle_type(library, user_handle)
    if user_handle != None:
        user_handle = byref(user_handle)
    return library.viUninstallHandler(session, event_type, handler, user_handle)

or the diffs

--- highlevel.py.orig   2015-04-21 19:26:55.836804300 -0400
+++ highlevel.py        2015-04-21 19:29:00.618804300 -0400
@@ -191,8 +191,7 @@
         :param session: Unique logical identifier to a session.
         :param event_type: Logical event identifier.
         :param handler: Interpreted as a valid reference to a handler to be uninstalled by a client application.
-        :param user_handle: A value specified by an application that can be used for identifying handlers
-                            uniquely in a session for an event.
+        :param user_handle: The user handle (ctypes object or None) returned by install_visa_handler.
         """
         for ndx, element in enumerate(self.handlers[session]):
             if element[0] is handler and element[1] is user_handle:
@@ -200,7 +199,7 @@
                 break
         else:
             raise errors.UnknownHandler(event_type, handler, user_handle)
-        self.uninstall_handler(session, event_type, handler, user_handle)
+        self.uninstall_handler(session, event_type, element[2], user_handle)

     def read_memory(self, session, space, offset, width, extended=False):
         """Reads in an 8-bit, 16-bit, 32-bit, or 64-bit value from the specified memory space and offset.
--- ctwrapper/functions.py.orig 2015-04-21 19:38:21.035804300 -0400
+++ ctwrapper/functions.py      2015-04-21 19:43:29.974804300 -0400
@@ -1691,12 +1691,14 @@
     :param session: Unique logical identifier to a session.
     :param event_type: Logical event identifier.
     :param handler: Interpreted as a valid reference to a handler to be uninstalled by a client application.
-    :param user_handle: A value specified by an application that can be used for identifying handlers
-                        uniquely in a session for an event.
+    :param user_handle: The user handle (a ctypes object) in the returned value from install_handler
     :return: return value of the library call.
     :rtype: :class:`pyvisa.constants.StatusCode`
     """
-    return library.viUninstallHandler(session, event_type, handler, byref(user_handle))
+    set_user_handle_type(library, user_handle)
+    if user_handle != None:
+        user_handle = byref(user_handle)
+    return library.viUninstallHandler(session, event_type, handler, user_handle)

 def unlock(library, session):
hgrecco commented 9 years ago

Sounds really good. Just one idea, how about if we make the API even easier for the end user. Maybe we can leave this functions as mid-level functions but in the ResourceManager (or Maybe in the resource) add a function that returns a random number or string (as the handler id) which should then be used by the user to uninstall the handler. Moreover, we could have an API to list the active handlers.

lupien commented 9 years ago

The handlers are related to the session so if you move them, it should be to the resource. The resource already has the basic versions install_handler and uninstall_handler (they could be left to the visalib).

The user interface is not too bad if the documentation is clearer. These are not functions that the user will use often interactively, however they are used in more advanced programs that communicates to many instruments in parallel (I use it like that). The information is already there for listing the active handlers. You cannot just return a string or a number, the user needs the converted (ctypes) user_handle. You could return a new object with a method to uninstall that is automatically called on del and that contains the necessary information (ctypes, handler function, ...) but that is probably more complicated than needed.

If you move the install/uninstall _visa_handler, I would also add to the resource the event functions since the handlers are useless without them (enable_event, discard_events, disable_event, wait_on_event).

And if you are going to move stuff, there is some stuff in the gpib resource that is more general according the the Visa-lib spec: assert_trigger (already in MessageBasedResource, duplicated in gpib), control_ren (should be available for USB, HiSLIP) the event stuff (should be available to all resources, gpib has __switch_events_off, wait_for_srq)

hgrecco commented 9 years ago

Can you post a short example of how you are using this. (assuming the modified PyVISA)? Thanks!

lupien commented 9 years ago

Here is an example of it:

import pyvisa
import time
rm = pyvisa.ResourceManager()
v = rm.get_instrument('USB::0x0957::0x0B0B::MY55555555') # This is an agilent EXA spectrum analyser

v.write('*cls')  # clear the status/event registers
v.write('*ese 1;*sre 32') # enable RQS from event status and enable event Operation Complete

def handler_func(session, event_type, context, user_handle):
    s = v.read_stb()
    if user_handle is not None:
        user_handle = user_handle.contents.value
    print 'session_id=%s (==%s event_session), type=%s, context=%s, user_handle=%s, stb=%s'%( v.session, session.value, event_type, context.value, user_handle, int(s))
    return pyvisa.constants.VI_SUCCESS

# install handlers
user_handle_None = v.visalib.install_visa_handler(v.session, pyvisa.constants.VI_EVENT_SERVICE_REQ, handler_func, None)
user_handle_1 = v.visalib.install_visa_handler(v.session, pyvisa.constants.VI_EVENT_SERVICE_REQ, handler_func, 1)
user_handle_1  # shows: c_long(1)
user_handle_str = v.visalib.install_visa_handler(v.session, pyvisa.constants.VI_EVENT_SERVICE_REQ, handler_func, 'test_str')

# enable Service Request events
v.visalib.enable_event(v.session, pyvisa.constants.VI_EVENT_SERVICE_REQ, pyvisa.constants.VI_HNDLR)

# generate the events (assumes the connected device handles *OPC instruction)
foo=v.write('*OPC'); time.sleep(1); foo
## This is the ouput for me
#session_id=48735064 (==48735064 event_session), type=1073684491, context=48826820, user_handle=test_str, stb=96
#session_id=48735064 (==48735064 event_session), type=1073684491, context=48826820, user_handle=1, stb=32
#session_id=48735064 (==48735064 event_session), type=1073684491, context=48826820, user_handle=None, stb=32
#(6L, <StatusCode.success: 0>)

# Change user_handle_1, remove str handler, reset registers and generate new event
user_handle_1.value=5
v.visalib.uninstall_visa_handler(v.session, pyvisa.constants.VI_EVENT_SERVICE_REQ, handler_func, user_handle_str)
int(v.query('*esr?')) # returns 1
foo=v.write('*OPC'); time.sleep(1); foo
## This is the ouput for me
#session_id=48735064 (==48735064 event_session), type=1073684491, context=48826820, user_handle=5, stb=32
#session_id=48735064 (==48735064 event_session), type=1073684491, context=48826820, user_handle=None, stb=32
#(6L, <StatusCode.success: 0>)

Tell me if you need something different.

Also something to be careful about, I think I remember an older version of NI-Visa would not allow the use of handlers on GPIB, but I just tested it and it seems to work now.

My real use of the handlers is to start long acquisition (can be in minutes) on many instruments (often INIT scpi commands) and then wait for the completion before fetching the data. So I would use v.write('INIT;*OPC') then my handler would launch the data transfer. This avoids timeouts (if trying to download data before it is available), and allows many instruments to work in parallel (not wasting time doing things in sequence), waiting the minimum time.

hgrecco commented 9 years ago

Excellent. (By the way, it would be great if you can contribute something like this to the docs)

A few ideas that might clean up the API:

If you would like to submit a Pull request, would be great. I will be very happy to quickly review an merge it.

hgrecco commented 9 years ago

I am closing this as we have already the PR.