Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Gpib events #175

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
201 changes: 195 additions & 6 deletions pyvisa-py/gpib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,6 +38,46 @@ def _inner(self):
raise


class GPIBEvent(object):
Copy link
Member

Choose a reason for hiding this comment

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

As I mentionned in my review, I am not sure how to expose this since pyvisa does not expose events. We should perhaps think about how to expose this in pyvisa and how to make it compatible with pyvisa-py.

Copy link
Contributor Author

@tivek tivek Dec 10, 2018

Choose a reason for hiding this comment

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

GPIBEvent is not a user-facing class, it is an implementation detail: PyVisaLibrary stores GPIBEvent objects in the PyVisaLibrary.events dict and passes any get_attribute() and close() calls down to them. Such "attribute query workers" are required pieces of the puzzle to fully conform to pyvisa's handle-based backend interface.

I agree that pyvisa needs a more pythonic way to expose events, and I think it is actually quite straightforward to get there. Currently, events are actually exposed to the user as raw VISA event contexts/handles. This is not very pythonic but is nevertheless there as a public and documented API. Our user can get hold of an event handle in two main ways:

  1. visalib calls the installed event handlers and passes the event handle as a parameter,
  2. Resource.wait_on_event() returns a WaitResponse object which exposes a member called context.

In both cases, the user can find out more about the event by calling visalib.get_attribute(handle, attribute) or close the event using visalib.close(handle). EDIT: ... and this is why GPIBEvent is there, to provide access-by-handle functionality for PyVisaLibrary in order to fully emulate NIVisaLibrary's behavior.

Instead of low-level handles/contexts, pyvisa should give users some kind of an event object that can be queried and knows how to clean up when it is done. The WaitResponse class already fits the bill nicely: all it needs to become user-friendly are get_attribute() and close() methods and renaming it eg. Event. The remaining step is to wrap user-provided event handlers so that they are passed Event objects when called instead of raw event handles.

Copy link
Member

Choose a reason for hiding this comment

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

Indeed this sounds like a plan.

For the work at hand, even if we do not implement event support right away, we can probably set up a generic class that could be used by all resources rather than an explicitly GPIB restricted class, no ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. Looks pretty generic actually. If any session needs to compute event attributes on the fly, they can override get_attributes(). Right now I'm struggling with bikeshedding :) I think "Event" should only be the name of the user-facing pyvisa class.

"""A simple event object that holds event attributes.
"""

def __init__(self, attrs):
"""Initialize GPIBEvent with an attribute dictionary.

:param attrs: Event attributes to be stored.
:type attrs: dict or dictionary-like
"""
self.attrs = {attribute: value for attribute, 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


def _find_listeners():
"""Find GPIB listeners.
"""
Expand All @@ -57,6 +98,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.
Expand All @@ -82,10 +125,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:
Expand Down Expand Up @@ -150,9 +203,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)

Expand All @@ -171,7 +224,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

Expand Down Expand Up @@ -378,3 +431,139 @@ 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
- A handle specifying the unique occurrence of an event
- return value of the library call.
:rtype: - eventtype
- event object # TODO
- :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

# TODO: set event attributes
if 0x100 & event_mask & sta:
self.event_queue.append(
(constants.VI_EVENT_IO_COMPLETION, GPIBEvent({})))

if gpib.RQS & event_mask & sta:
self.event_queue.append(
(constants.VI_EVENT_SERVICE_REQ, GPIBEvent({})))

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

Loading