Skip to content

Commit

Permalink
added mutex to protect gpib_addr changes; added comments about discar…
Browse files Browse the repository at this point in the history
…ding stale read data
  • Loading branch information
Bob McNamara committed Jan 29, 2025
1 parent 1ee9ea5 commit 5578e84
Showing 1 changed file with 34 additions and 23 deletions.
57 changes: 34 additions & 23 deletions pyvisa_py/prologix.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import select
import socket
import sys
import threading
from typing import Any

from pyvisa import attributes, constants, errors, logger, rname
Expand Down Expand Up @@ -66,6 +67,8 @@ def __init__(
BOARDS[self.parsed.board] = self
self._gpib_addr = ""

self.intfc_lock = threading.Lock()

def close(self) -> StatusCode:
BOARDS.pop(self.parsed.board)
return super().close() # type: ignore[safe-super]
Expand All @@ -83,14 +86,14 @@ def gpib_addr(self, addr: str) -> None:
self.write_oob(f"++addr {addr}\n".encode())
self._gpib_addr = addr

def write_oob(self, data: bytes) -> Tuple[int, StatusCode]:
def write_oob(self, data: bytes) -> tuple[int, StatusCode]:
"""out-of-band write (for sending "++" commands)"""
if self.interface is None:
raise errors.InvalidSession()

return super().write(data)

def read(self, count: int) -> Tuple[bytes, StatusCode]:
def read(self, count: int) -> tuple[bytes, StatusCode]:
if self.interface is None:
raise errors.InvalidSession()

Expand Down Expand Up @@ -144,11 +147,13 @@ def write(self, data: bytes) -> tuple[int, StatusCode]:
# use select to wait for write ready
rd, _wr, _ = select.select([self.interface], [self.interface], [])
if rd:
# any data that hasn't been read yet is now considered stale,
# and should be discarded.
self.clear()
except socket.timeout:
return 0, StatusCode.error_io

self._pending_buffer.clear()
self._pending_buffer.clear() # discard any stale unread data
self.plus_plus_read = True
return super().write(data)

Expand All @@ -175,8 +180,6 @@ def after_parsing(self) -> None:
write_timeout=self.timeout,
baudrate=115200,
)
# self.interface.write_termination = "\n"
# self.write_termination = "\n"

for name in (
"ASRL_END_IN",
Expand All @@ -189,7 +192,7 @@ def after_parsing(self) -> None:
attribute = getattr(constants, "VI_ATTR_" + name)
self.attrs[attribute] = attributes.AttributesByID[attribute].default

def write(self, data: bytes) -> Tuple[int, StatusCode]:
def write(self, data: bytes) -> tuple[int, StatusCode]:
"""Writes data to device or interface synchronously.
Corresponds to viWrite function of the VISA library.
Expand All @@ -211,6 +214,8 @@ def write(self, data: bytes) -> Tuple[int, StatusCode]:
raise errors.InvalidSession()

if self.interface.inWaiting() > 0:
# any data that hasn't been read yet is now considered stale,
# and should be discarded.
self.interface.flushInput()
self.plus_plus_read = True
return super().write(data)
Expand Down Expand Up @@ -253,20 +258,18 @@ def close(self) -> StatusCode:
self.interface = None
return StatusCode.success

def read(self, count: int) -> Tuple[bytes, StatusCode]:
def read(self, count: int) -> tuple[bytes, StatusCode]:
if self.interface is None or self.interface.interface is None:
raise errors.InvalidSession()

self.interface.gpib_addr = self.gpib_addr
with self.interface.intfc_lock:
self.interface.gpib_addr = self.gpib_addr
return self.interface.read(count)

return self.interface.read(count)

def write(self, data: bytes) -> Tuple[int, StatusCode]:
def write(self, data: bytes) -> tuple[int, StatusCode]:
if self.interface is None or self.interface.interface is None:
raise errors.InvalidSession()

self.interface.gpib_addr = self.gpib_addr

# if the calling function has appended a newline to the data,
# we don't want it to be escaped. remove it from the data
# and stash it away so we can append it after all the escapes
Expand All @@ -289,7 +292,9 @@ def write(self, data: bytes) -> Tuple[int, StatusCode]:
data = data.replace(b"\r", b"\033\r")
data = data.replace(b"+", b"\033+")

return self.interface.write(data + last_byte)
with self.interface.intfc_lock:
self.interface.gpib_addr = self.gpib_addr
return self.interface.write(data + last_byte)

def flush(self, mask: BufferOperation) -> StatusCode:
if self.interface is None or self.interface.interface is None:
Expand All @@ -312,8 +317,10 @@ def clear(self) -> StatusCode:
if self.interface is None or self.interface.interface is None:
raise errors.InvalidSession()

self.interface.gpib_addr = self.gpib_addr
_, status_code = self.interface.write_oob(b"++clr\n")
with self.interface.intfc_lock:
self.interface.gpib_addr = self.gpib_addr
_, status_code = self.interface.write_oob(b"++clr\n")

return status_code

def assert_trigger(self, protocol: constants.TriggerProtocol) -> StatusCode:
Expand All @@ -336,21 +343,25 @@ def assert_trigger(self, protocol: constants.TriggerProtocol) -> StatusCode:
if self.interface is None or self.interface.interface is None:
raise errors.InvalidSession()

self.interface.gpib_addr = self.gpib_addr
_, status_code = self.interface.write_oob(b"++trg\n")
with self.interface.intfc_lock:
self.interface.gpib_addr = self.gpib_addr
_, status_code = self.interface.write_oob(b"++trg\n")

return status_code

def read_stb(self) -> Tuple[int, StatusCode]:
def read_stb(self) -> tuple[int, StatusCode]:
"""Read the device status byte."""
if self.interface is None or self.interface.interface is None:
raise errors.InvalidSession()

self.interface.gpib_addr = self.gpib_addr
self.interface.write_oob(b"++spoll\n")
data, status_code = self.interface.read(32)
with self.interface.intfc_lock:
self.interface.gpib_addr = self.gpib_addr
self.interface.write_oob(b"++spoll\n")
data, status_code = self.interface.read(32)

return (int(data), status_code)

def _get_attribute(self, attribute: ResourceAttribute) -> Tuple[Any, StatusCode]:
def _get_attribute(self, attribute: ResourceAttribute) -> tuple[Any, StatusCode]:
"""Get the value for a given VISA attribute for this session.
Use to implement custom logic for attributes.
Expand Down

0 comments on commit 5578e84

Please sign in to comment.