diff --git a/pyvisa-py/gpib.py b/pyvisa-py/gpib.py index 6a11958f..f8bf69db 100644 --- a/pyvisa-py/gpib.py +++ b/pyvisa-py/gpib.py @@ -15,7 +15,7 @@ from pyvisa import constants, logger, attributes -from .sessions import Session, UnknownAttribute +from .sessions import Session, UnknownAttribute, EventData try: import gpib @@ -25,6 +25,7 @@ def _patch_Gpib(): if not hasattr(Gpib, "close"): _old_del = Gpib.__del__ + def _inner(self): _old_del(self) self._own = False @@ -57,6 +58,8 @@ def _find_listeners(): # TODO: Check board indices other than 0. BOARD = 0 # TODO: Check secondary addresses. + + @Session.register(constants.InterfaceType.gpib, 'INSTR') class GPIBSession(Session): """A GPIB Session that uses linux-gpib to do the low level communication. @@ -82,10 +85,20 @@ def after_parsing(self): timeout = 13 send_eoi = 1 eos_mode = 0 - self.interface = Gpib(name=minor, pad=pad, sad=sad, timeout=timeout, send_eoi=send_eoi, eos_mode=eos_mode) - self.controller = Gpib(name=minor) # this is the bus controller device + self.interface = Gpib(name=minor, pad=pad, sad=sad, + timeout=timeout, send_eoi=send_eoi, eos_mode=eos_mode) + self.controller = Gpib(name=minor) # this is the bus controller device # force timeout setting to interface - self.set_attribute(constants.VI_ATTR_TMO_VALUE, attributes.AttributesByID[constants.VI_ATTR_TMO_VALUE].default) + self.set_attribute(constants.VI_ATTR_TMO_VALUE, + attributes.AttributesByID[constants.VI_ATTR_TMO_VALUE].default) + + # prepare set of allowed events + self.valid_event_types = {constants.VI_EVENT_IO_COMPLETION, + constants.VI_EVENT_SERVICE_REQ} + + self.enabled_queue_events = set() + + self.event_queue = [] def _get_timeout(self, attribute): if self.interface: @@ -150,9 +163,9 @@ def read(self, count): """ # 0x2000 = 8192 = END - checker = lambda current: self.interface.ibsta() & 8192 + def checker(current): return self.interface.ibsta() & 8192 - reader = lambda: self.interface.read(count) + def reader(): return self.interface.read(count) return self._read(reader, count, checker, False, None, False, gpib.GpibError) @@ -171,7 +184,7 @@ def write(self, data): try: self.interface.write(data) - count = self.interface.ibcnt() # number of bytes transmitted + count = self.interface.ibcnt() # number of bytes transmitted return count, StatusCode.success @@ -378,3 +391,147 @@ def read_stb(self): return self.interface.serial_poll(), StatusCode.success except gpib.GpibError: return 0, StatusCode.error_system_error + + def disable_event(self, event_type, mechanism): + """Disables notification of the specified event type(s) via the specified mechanism(s). + + Corresponds to viDisableEvent function of the VISA library. + + :param event_type: Logical event identifier. + :param mechanism: Specifies event handling mechanisms to be disabled. + (Constants.VI_QUEUE, .VI_HNDLR, .VI_SUSPEND_HNDLR, .VI_ALL_MECH) + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + + if event_type not in self.valid_event_types: + return StatusCode.error_invalid_event + + if mechanism in (constants.VI_QUEUE, constants.VI_ALL_MECH): + if event_type not in self.enabled_queue_events: + return StatusCode.success_event_already_disabled + + self.enabled_queue_events.remove(event_type) + return StatusCode.success + + return StatusCode.error_invalid_mechanism + + def discard_events(self, event_type, mechanism): + """Discards event occurrences for specified event types and mechanisms in a session. + + Corresponds to viDiscardEvents function of the VISA library. + + :param event_type: Logical event identifier. + :param mechanism: Specifies event handling mechanisms to be discarded. + (Constants.VI_QUEUE, .VI_SUSPEND_HNDLR, .VI_ALL_MECH) + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + if event_type not in self.valid_event_types: + return StatusCode.error_invalid_event + + if mechanism in (constants.VI_QUEUE, constants.VI_ALL_MECH): + self.event_queue = [(t, a) for t, a in self.event_queue if not ( + event_type == constants.VI_ALL_ENABLED_EVENTS or t == event_type)] + return StatusCode.success + + return StatusCode.error_invalid_mechanism + + def enable_event(self, event_type, mechanism, context=None): + """Enable event occurrences for specified event types and mechanisms in a session. + + Corresponds to viEnableEvent function of the VISA library. + + :param event_type: Logical event identifier. + :param mechanism: Specifies event handling mechanisms to be enabled. + (Constants.VI_QUEUE, .VI_HNDLR, .VI_SUSPEND_HNDLR) + :param context: + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + + if event_type not in self.valid_event_types: + return StatusCode.error_invalid_event + + if mechanism in (constants.VI_QUEUE, constants.VI_ALL_MECH): + # enable GPIB autopoll + try: + self.controller.config(7, 1) + except gpib.GpibError: + return StatusCode.error_invalid_setup + + if event_type in self.enabled_queue_events: + return StatusCode.success_event_already_enabled + else: + self.enabled_queue_events.add(event_type) + return StatusCode.success + + # mechanisms which are not implemented: constants.VI_SUSPEND_HNDLR, constants.VI_ALL_MECH + return StatusCode.error_invalid_mechanism + + def wait_on_event(self, in_event_type, timeout): + """Waits for an occurrence of the specified event for a given session. + + Corresponds to viWaitOnEvent function of the VISA library. + + :param in_event_type: Logical identifier of the event(s) to wait for. + :param timeout: Absolute time period in time units that the resource shall wait for a specified event to + occur before returning the time elapsed error. The time unit is in milliseconds. + :return: - Logical identifier of the event actually received + - An object specifying the unique occurrence of an event + - return value of the library call. + :rtype: - eventtype + - EventData + - :class:`pyvisa.constants.StatusCode` + """ + + if in_event_type not in self.valid_event_types: + return StatusCode.error_invalid_event + + if in_event_type not in self.enabled_queue_events: + return StatusCode.error_not_enabled + + # if the event queue is empty, wait for more events + if not self.event_queue: + old_timeout = self.timeout + self.timeout = timeout + + event_mask = 0 + + if in_event_type in (constants.VI_EVENT_IO_COMPLETION, constants.VI_ALL_ENABLED_EVENTS): + event_mask |= 0x100 # CMPL + + if in_event_type in (constants.VI_EVENT_SERVICE_REQ, constants.VI_ALL_ENABLED_EVENTS): + event_mask |= gpib.RQS + + if timeout != 0: + event_mask |= gpib.TIMO + + self.interface.wait(event_mask) + sta = self.interface.ibsta() + + self.timeout = old_timeout + + if 0x100 & event_mask & sta: + # TODO: implement all event attributes + # VI_ATTR_EVENT_TYPE: VI_EVENT_IO_COMPLETION, + # VI_ATTR_STATUS: return code of the asynchronous IO operation that has completed, + # VI_ATTR_JOB_ID: job ID of the asynchronous operation that has completed, + # VI_ATTR_BUFFER: the address of the buffer that was used in the asynchronous operation, + # VI_ATTR_RET_COUNT/VI_ATTR_RET_COUNT_32/VI_ATTR_RET_COUNT_64: number of elements that were asynchronously transferred, + # VI_ATTR_OPER_NAME: name of the operation generating the event + self.event_queue.append( + (constants.VI_EVENT_IO_COMPLETION, + EventData({constants.VI_ATTR_EVENT_TYPE: constants.VI_EVENT_IO_COMPLETION}))) + + if gpib.RQS & event_mask & sta: + self.event_queue.append( + (constants.VI_EVENT_SERVICE_REQ, + EventData({constants.VI_ATTR_EVENT_TYPE: constants.VI_EVENT_SERVICE_REQ}))) + + try: + out_event_type, event_data = self.event_queue.pop() + return out_event_type, event_data, StatusCode.success + except IndexError: + return in_event_type, None, StatusCode.error_timeout + diff --git a/pyvisa-py/highlevel.py b/pyvisa-py/highlevel.py index 6c361ebc..38d301b4 100644 --- a/pyvisa-py/highlevel.py +++ b/pyvisa-py/highlevel.py @@ -93,25 +93,57 @@ def get_debug_info(): def _init(self): #: map session handle to session object. - #: dict[int, session.Session] + #: dict[int, sessions.Session] self.sessions = {} - def _register(self, obj): + #: map event handle to event object. + #: dict[int, Event] + self.events = {} + + def _generate_handle(self): + """Creates a random and unique handle. + + Handles are used for: + - session object (viSession) + - events (viEvent) + - find list (viFindList) + + :return: handle + :rtype: int + """ + # VISA sessions, events, and find lists get unique logical identifiers + # from the same pool + handle = None + while (handle is None or + handle in self.sessions or + handle in self.events): + handle = random.randint(1000000, 9999999) + return handle + + def _register_session(self, obj): """Creates a random but unique session handle for a session object, - register it in the sessions dictionary and return the value + registers it in the sessions dictionary and returns the value :param obj: a session object. :return: session handle :rtype: int """ - session = None - - while session is None or session in self.sessions: - session = random.randint(1000000, 9999999) - + session = self._generate_handle() self.sessions[session] = obj return session + def _register_event(self, obj): + """Creates a random but unique session handle for an event object, + registers it in the event dictionary and returns the value + + :param obj: an event object. + :return: event handle + :rtype: int + """ + event_handle = self._generate_handle() + self.events[event_handle] = obj + return event_handle + def _return_handler(self, ret_value, func, arguments): """Check return values for errors and warnings. @@ -182,18 +214,20 @@ def open(self, session, resource_name, try: open_timeout = int(open_timeout) except ValueError: - raise ValueError('open_timeout (%r) must be an integer (or compatible type)' % open_timeout) + raise ValueError( + 'open_timeout (%r) must be an integer (or compatible type)' % open_timeout) try: parsed = rname.parse_resource_name(resource_name) except rname.InvalidResourceName: return 0, StatusCode.error_invalid_resource_name - cls = sessions.Session.get_session_class(parsed.interface_type_const, parsed.resource_class) + cls = sessions.Session.get_session_class( + parsed.interface_type_const, parsed.resource_class) sess = cls(session, resource_name, parsed, open_timeout) - return self._register(sess), StatusCode.success + return self._register_session(sess), StatusCode.success def clear(self, session): """Clears a device. @@ -214,7 +248,8 @@ def gpib_command(self, session, command_byte): """Write GPIB command byte on the bus. Corresponds to viGpibCommand function of the VISA library. - See: https://linux-gpib.sourceforge.io/doc_html/gpib-protocol.html#REFERENCE-COMMAND-BYTES + #REFERENCE-COMMAND-BYTES + See: https://linux-gpib.sourceforge.io/doc_html/gpib-protocol.html :param command_byte: command byte to send :type command_byte: int, must be [0 255] @@ -280,12 +315,16 @@ def close(self, session): :return: return value of the library call. :rtype: VISAStatus """ - try: - sess = self.sessions[session] - if sess is not self: - sess.close() - except KeyError: - return StatusCode.error_invalid_object + for container in (self.sessions, self.events): + try: + obj = container[session] + if obj is not self: + obj.close() + del container[session] + return StatusCode.success + except KeyError: + pass + return StatusCode.error_invalid_object def open_default_resource_manager(self): """This function returns a session to the Default Resource Manager resource. @@ -295,7 +334,7 @@ def open_default_resource_manager(self): :return: Unique logical identifier to a Default Resource Manager session, return value of the library call. :rtype: session, VISAStatus """ - return self._register(self), StatusCode.success + return self._register_session(self), StatusCode.success def list_resources(self, session, query='?*::INSTR'): """Returns a tuple of all connected devices matching query. @@ -368,12 +407,14 @@ def get_attribute(self, session, attribute): :return: The state of the queried attribute for a specified resource, return value of the library call. :rtype: unicode | str | list | int, VISAStatus """ - try: - sess = self.sessions[session] - except KeyError: - return None, StatusCode.error_invalid_object + for container in (self.sessions, self.events): + try: + obj = container[session] + break + except KeyError: + return None, StatusCode.error_invalid_object - return sess.get_attribute(attribute) + return obj.get_attribute(attribute) def set_attribute(self, session, attribute, attribute_state): """Sets the state of an attribute. @@ -387,12 +428,14 @@ def set_attribute(self, session, attribute, attribute_state): :rtype: VISAStatus """ - try: - sess = self.sessions[session] - except KeyError: - return StatusCode.error_invalid_object + for container in (self.sessions, self.events): + try: + obj = container[session] + break + except KeyError: + return StatusCode.error_invalid_object - return sess.set_attribute(attribute, attribute_state) + return obj.set_attribute(attribute, attribute_state) def lock(self, session, lock_type, timeout, requested_key=None): """Establishes an access mode to the specified resources. @@ -431,9 +474,138 @@ def unlock(self, session): return sess.unlock() def disable_event(self, session, event_type, mechanism): - # TODO: implement this for GPIB finalization - pass + """Disables notification of the specified event type(s) via the specified mechanism(s). + + Corresponds to viDisableEvent function of the VISA library. + + :param session: Unique logical identifier to a session. + :param event_type: Logical event identifier. + :param mechanism: Specifies event handling mechanisms to be disabled. + (Constants.VI_QUEUE, .VI_HNDLR, .VI_SUSPEND_HNDLR, .VI_ALL_MECH) + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return StatusCode.error_invalid_object + + return sess.disable_event(event_type, mechanism) def discard_events(self, session, event_type, mechanism): - # TODO: implement this for GPIB finalization - pass + """Discards event occurrences for specified event types and mechanisms in a session. + + Corresponds to viDiscardEvents function of the VISA library. + + :param session: Unique logical identifier to a session. + :param event_type: Logical event identifier. + :param mechanism: Specifies event handling mechanisms to be discarded. + (Constants.VI_QUEUE, .VI_SUSPEND_HNDLR, .VI_ALL_MECH) + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return StatusCode.error_invalid_object + + return sess.discard_events(event_type, mechanism) + + def enable_event(self, session, event_type, mechanism, context=None): + """Enable event occurrences for specified event types and mechanisms in a session. + + Corresponds to viEnableEvent function of the VISA library. + + :param session: Unique logical identifier to a session. + :param event_type: Logical event identifier. + :param mechanism: Specifies event handling mechanisms to be enabled. + (Constants.VI_QUEUE, .VI_HNDLR, .VI_SUSPEND_HNDLR) + :param context: + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + if context is None: + context = constants.VI_NULL + elif context != constants.VI_NULL: + warnings.warn('In enable_event, context will be set VI_NULL.') + context = constants.VI_NULL # according to spec VPP-4.3, section 3.7.3.1 + + try: + sess = self.sessions[session] + except KeyError: + return StatusCode.error_invalid_object + + return sess.enable_event(event_type, mechanism, context) + + def install_handler(self, session, event_type, handler, user_handle): + """Installs handlers for event callbacks. + + Corresponds to viInstallHandler function of the VISA library. + + :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 installed by a client application. + :param user_handle: A value specified by an application that can be used for identifying handlers + uniquely for an event type. + :returns: a handler descriptor which consists of three elements: + - handler (a python callable) + - user handle (a ctypes object) + - ctypes handler (ctypes object wrapping handler) + and return value of the library call. + :rtype: int, :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return StatusCode.error_invalid_object + + return sess.install_handler(event_type, handler, user_handle) + + def uninstall_handler(self, session, event_type, handler, user_handle=None): + """Uninstalls handlers for events. + + Corresponds to viUninstallHandler function of the VISA library. + + :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. + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return StatusCode.error_invalid_object + + return sess.uninstall_handler(event_type, handler, user_handle) + + def wait_on_event(self, session, in_event_type, timeout): + """Waits for an occurrence of the specified event for a given session. + + Corresponds to viWaitOnEvent function of the VISA library. + + :param session: Unique logical identifier to a session. + :param in_event_type: Logical identifier of the event(s) to wait for. + :param timeout: Absolute time period in time units that the resource shall wait for a specified event to + occur before returning the time elapsed error. The time unit is in milliseconds. + :return: - Logical identifier of the event actually received + - A handle specifying the unique occurrence of an event + - return value of the library call. + :rtype: - eventtype + - event + - :class:`pyvisa.constants.StatusCode` + """ + try: + sess = self.sessions[session] + except KeyError: + return None, None, StatusCode.error_invalid_object + + # FIXME: for now calling Session's own wait_on_event() is the only way + # to notify PyVisaLibrary of the event and store it + out_event_type, event_attrs, ret = sess.wait_on_event( + in_event_type, timeout) + event_handle = self._register_event(event_attrs) + + return out_event_type, event_handle, ret diff --git a/pyvisa-py/sessions.py b/pyvisa-py/sessions.py index b6fbd898..13774643 100644 --- a/pyvisa-py/sessions.py +++ b/pyvisa-py/sessions.py @@ -44,6 +44,47 @@ def __str__(self): __repr__ = __str__ +class EventData(object): + """A simple storage that holds attributes of a single event. + """ + + def __init__(self, attrs): + """Initialize EventData with an attribute dictionary. + + :param attrs: Event attributes to be stored. + :type attrs: dict or dictionary-like + """ + super() + self.attrs = {attr: value for attr, value in attrs.items()} + + def __del__(self): + self.close() + + def get_attribute(self, attr): + """Retrieves the state of an attribute. + + Corresponds to viGetAttribute function of the VISA library for this particular event. + + :param attribute: Event attribute for which the state query is made (see Attributes.*) + :return: The state of the queried attribute, return value describing success. + :rtype: unicode | str | list | int, VISAStatus + """ + try: + return self.attrs[attr], StatusCode.success + except KeyError: + return None, StatusCode.error_nonsupported_attribute + + def close(self): + """Closes the event. + + Corresponds to viClose function of the VISA library. + + :return: return value of the library call. + :rtype: VISAStatus + """ + return StatusCode.success + + class Session(compat.with_metaclass(abc.ABCMeta)): """A base class for Session objects. @@ -203,7 +244,8 @@ def __init__(self, resource_manager_session, resource_name, parsed=None, constants.VI_ATTR_RSRC_CLASS: parsed.resource_class, constants.VI_ATTR_INTF_TYPE: parsed.interface_type, constants.VI_ATTR_TMO_VALUE: (self._get_timeout, - self._set_timeout)} + self._set_timeout), + constants.VI_ATTR_MAX_QUEUE_LENGTH: 50} #: Timeout expressed in second or None for the absence of a timeout. #: The default value is set when calling @@ -537,3 +579,92 @@ def _set_timeout(self, attribute, value): else: self.timeout = value / 1000.0 return StatusCode.success + + def disable_event(self, event_type, mechanism): + """Disables notification of the specified event type(s) via the specified mechanism(s). + + Corresponds to viDisableEvent function of the VISA library. + + :param event_type: Logical event identifier. + :param mechanism: Specifies event handling mechanisms to be disabled. + (Constants.VI_QUEUE, .VI_HNDLR, .VI_SUSPEND_HNDLR, .VI_ALL_MECH) + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + raise NotImplementedError + + def discard_events(self, event_type, mechanism): + """Discards event occurrences for specified event types and mechanisms in a session. + + Corresponds to viDiscardEvents function of the VISA library. + + :param event_type: Logical event identifier. + :param mechanism: Specifies event handling mechanisms to be discarded. + (Constants.VI_QUEUE, .VI_SUSPEND_HNDLR, .VI_ALL_MECH) + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + raise NotImplementedError + + def enable_event(self, event_type, mechanism, context=None): + """Enable event occurrences for specified event types and mechanisms in a session. + + Corresponds to viEnableEvent function of the VISA library. + + :param event_type: Logical event identifier. + :param mechanism: Specifies event handling mechanisms to be enabled. + (Constants.VI_QUEUE, .VI_HNDLR, .VI_SUSPEND_HNDLR) + :param context: + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + raise NotImplementedError + + def install_handler(self, event_type, handler, user_handle): + """Installs handlers for event callbacks. + + Corresponds to viInstallHandler function of the VISA library. + + :param event_type: Logical event identifier. + :param handler: Interpreted as a valid reference to a handler to be installed by a client application. + :param user_handle: A value specified by an application that can be used for identifying handlers + uniquely for an event type. + :returns: a handler descriptor which consists of three elements: + - handler (a python callable) + - user handle (a ctypes object) + - ctypes handler (ctypes object wrapping handler) + and return value of the library call. + :rtype: int, :class:`pyvisa.constants.StatusCode` + """ + raise NotImplementedError + + def uninstall_handler(self, event_type, handler, user_handle=None): + """Uninstalls handlers for events. + + Corresponds to viUninstallHandler function of the VISA library. + + :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. + :return: return value of the library call. + :rtype: :class:`pyvisa.constants.StatusCode` + """ + raise NotImplementedError + + def wait_on_event(self, in_event_type, timeout): + """Waits for an occurrence of the specified event for a given session. + + Corresponds to viWaitOnEvent function of the VISA library. + + :param in_event_type: Logical identifier of the event(s) to wait for. + :param timeout: Absolute time period in time units that the resource shall wait for a specified event to + occur before returning the time elapsed error. The time unit is in milliseconds. + :return: - Logical identifier of the event actually received + - An object specifying the unique occurrence of an event + - return value of the library call. + :rtype: - eventtype + - EventData + - :class:`pyvisa.constants.StatusCode` + """ + raise NotImplementedError