Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
0c787ff
Add GPIB-ENET/100 wire protocol module skeleton
bytewarrior May 31, 2026
f16f0bc
Add stateful chunk reader for NI GPIB-ENET/100 read streams
bytewarrior May 31, 2026
dfea6be
Add EnetConnection with main + companion socket lifecycle
bytewarrior May 31, 2026
015b127
Add GPIB-ENET/100 session open / close sequences
bytewarrior May 31, 2026
9db3eef
Add GPIB-ENET/100 device verbs (ibwrt/ibrd/ibclr/ibtrg/ibloc/ibrsp)
bytewarrior May 31, 2026
cd45b62
Add NIEnet100TCPIPIntfcSession for NIENET100-TCPIP::INTFC resources
bytewarrior May 31, 2026
96bd5d2
Add NIEnet100InstrSession for GPIB::INSTR via NI GPIB-ENET/100
bytewarrior May 31, 2026
c4baf1d
Wire GPIB-ENET/100 into pyvisa-py dispatch and module loader
bytewarrior May 31, 2026
692f1fa
Add offline unit tests for GPIB-ENET/100 wire protocol
bytewarrior May 31, 2026
f50acb9
Add lazy wait/control socket lifecycle for GPIB-ENET/100 SRQ support
bytewarrior May 31, 2026
1e0e79d
Add ibwait verb for GPIB-ENET/100 SRQ polling
bytewarrior May 31, 2026
a08044c
Add ibsic + notify_off_async_device control-socket verbs and cleanup
bytewarrior May 31, 2026
6b5cc5a
Add offline tests for GPIB-ENET/100 SRQ verbs and wait/control lifecycle
bytewarrior May 31, 2026
459e9ca
Add GPIB-ENET/100 discovery frame primitives
bytewarrior May 31, 2026
fbec6f4
Add discover() UDP loop for GPIB-ENET/100 bridges
bytewarrior May 31, 2026
4af1540
Wire UDP discovery into NIEnet100TCPIPIntfcSession.list_resources
bytewarrior May 31, 2026
07c2788
Add offline tests for GPIB-ENET/100 discovery
bytewarrior May 31, 2026
94c9e73
Add wire-level hardware tests against a real GPIB-ENET/100 bridge
bytewarrior May 31, 2026
27ab23d
Add full-stack hardware tests via pyvisa.ResourceManager('@py')
bytewarrior May 31, 2026
41e8e8a
Resolve PYVISA_TEST_NIENET100_HOST to IP for discovery assertions
bytewarrior May 31, 2026
2287656
Gate cross-subnet discovery test behind opt-in env var
bytewarrior May 31, 2026
93aa128
Add hex-dump DEBUG logging to main-socket send/recv
bytewarrior May 31, 2026
88137f3
Treat unknown zero-length chunk flags as end-of-stream
bytewarrior May 31, 2026
30795c4
Relax ibwait smoke test: accept sta=0 as a valid no-event response
bytewarrior May 31, 2026
7278377
Read status headers through the chunk framing layer
bytewarrior May 31, 2026
7da6c94
Use per-call ibrd tmo_ms instead of IbcTMO setter in timeout test
bytewarrior May 31, 2026
968bee9
Parse ibrsp response as one chunk containing status + STB combined
bytewarrior May 31, 2026
0eab3cf
Make ibrd default tmo_ms useful and tolerate no-END response path
bytewarrior May 31, 2026
cecd00d
Propagate pyvisa session timeout into the wire-level ibrd
bytewarrior May 31, 2026
a536b92
Use 'NI-ENET100-TCPIP' as the interface-type segment and index dispat…
bytewarrior May 31, 2026
044260d
Drop IbcTMO setter from session _set_timeout and widen socket buffer
bytewarrior Jun 1, 2026
4993e20
Make assisted INSTR tests robust against real instruments
bytewarrior Jun 1, 2026
cb82fa8
Refresh stale comment on gpib_control_ren
bytewarrior Jun 1, 2026
c71e1e0
Updated comments, removed old references to wire spec.
bytewarrior Jun 1, 2026
4aaf909
Wrap TERM constant to satisfy ruff line-length
bytewarrior Jun 1, 2026
cdce92c
Track bracket lifecycle in the protocol layer and close it on teardown
bytewarrior Jun 1, 2026
af7b5ea
Reduce import checking to one check (sufficient for determining compa…
bytewarrior Jun 4, 2026
0a5b237
Enhance read buffer management in NIEnet100InstrSession to prevent da…
bytewarrior Jun 4, 2026
80f27c5
Added a blank line to all multiline docstrings at the end, like numpy.
bytewarrior Jun 4, 2026
e2dfa2b
Register NI-ENET100 INTFC in session-class expectations test
bytewarrior Jun 4, 2026
35dffb3
Add inert central GPIB::INSTR dispatcher scaffold
bytewarrior Jun 4, 2026
38a7610
Wire native and Prologix GPIB::INSTR backends as resolvers
bytewarrior Jun 4, 2026
4b8d17d
Wire NI-ENET/100 bridge as highest-precedence GPIB::INSTR backend
bytewarrior Jun 4, 2026
706b00b
Activate central GPIB::INSTR dispatcher and retire per-module hooks
bytewarrior Jun 4, 2026
d12d56a
Type GPIB::INSTR resolvers against GPIBInstr for mypy
bytewarrior Jun 4, 2026
2e3aa75
Declare dynamically-bound EnetConnection verbs for type checkers
bytewarrior Jun 4, 2026
87bdf68
Fix mypy types in NIEnet100 INTFC/INSTR session layer
bytewarrior Jun 4, 2026
2b6b729
Fix mypy types in NI-ENET/100 assisted tests
bytewarrior Jun 4, 2026
6fa8468
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2026
dc0e1a3
Modernize typing to PEP 585/604 for Python 3.10 floor
bytewarrior Jun 6, 2026
95ce783
Fix NI-ENET/100 socket tests on Unix (getsockname on socketpair)
bytewarrior Jun 6, 2026
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
85 changes: 24 additions & 61 deletions pyvisa_py/gpib.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,67 +9,30 @@

import ctypes # Used for missing bindings not ideal
from bisect import bisect
from typing import Any, Iterator, List, Tuple, Type, Union
from collections.abc import Iterator
from typing import Any

from pyvisa import attributes, constants
from pyvisa.constants import ResourceAttribute, StatusCode
from pyvisa.rname import GPIBInstr, GPIBIntfc, parse_resource_name
from pyvisa.rname import GPIBInstr, GPIBIntfc

from . import prologix
from .common import LOGGER
from .sessions import Session, UnavailableSession, UnknownAttribute, VISARMSession
from .sessions import Session, UnavailableSession, UnknownAttribute


# NOTE dummy implementation that is overwritten when a GPIB library is found
# Allow to provide session listing even when no GPIB library is available.
def _find_listeners() -> Iterator[Tuple[int, int, int]]:
def _find_listeners() -> Iterator[tuple[int, int, int]]:
yield from ()


@Session.register(constants.InterfaceType.gpib, "INSTR")
class GPIBSessionDispatch(Session):
"""Dispatch to the proper class based on prologix._PrologixIntfcSession.boards.
# Dispatch of ``GPIB::INSTR`` resources (native vs Prologix vs NI-ENET/100
# bridge) is owned by :mod:`pyvisa_py.gpib_dispatch`, which registers the
# native driver as a backend resolver. This module therefore no longer
# registers a ``(gpib, "INSTR")`` class itself.

Uses the __new__ method to intercept the creation of the instance of a
GPIB session. If parsed.board is found in
prologix._PrologixIntfcSession.boards, create an instance of
prologix.PrologixInstrSession, otherwise create an instance of GPIBSession.

"""

def __new__( # type: ignore[misc]
cls,
resource_manager_session: VISARMSession,
resource_name: str,
parsed=None,
open_timeout: int | None = None,
) -> Session:
newcls: Type

if parsed is None:
parsed = parse_resource_name(resource_name)

if parsed.board in prologix._PrologixIntfcSession.boards:
newcls = prologix.PrologixInstrSession
else:
newcls = GPIBSession

return newcls(resource_manager_session, resource_name, parsed, open_timeout)

@staticmethod
def list_resources() -> List[str]:
return [
"GPIB%d::%d::INSTR" % (board, pad)
if sad == 0
else "GPIB%d::%d::%d::INSTR" % (board, pad, sad - 0x60)
for board, pad, sad in _find_listeners()
]

# FIXME when PROLOGIX gain the ability to list resources, we should
# include PROLOGIX results.


def make_unavailable(msg: str) -> Type:
def make_unavailable(msg: str) -> type[Any]:
"""Creates a fake session class that raises a ValueError if instantiated.

We can't use Session.register_unavailable() because we need to be able to
Expand All @@ -82,7 +45,7 @@ def make_unavailable(msg: str) -> Type:

Returns
-------
Type[Session]
type[Session]
Fake session.

"""
Expand Down Expand Up @@ -154,7 +117,7 @@ def _inner(self):
_patch_Gpib()


def _find_boards() -> Iterator[Tuple[int, int]]:
def _find_boards() -> Iterator[tuple[int, int]]:
"""Find GPIB board addresses."""
for board in range(16):
try:
Expand All @@ -163,7 +126,7 @@ def _find_boards() -> Iterator[Tuple[int, int]]:
LOGGER.debug("GPIB board %i error in _find_boards(): %s", board, repr(e))


def _find_listeners() -> Iterator[Tuple[int, int, int]]: # type: ignore[no-redef]
def _find_listeners() -> Iterator[tuple[int, int, int]]: # type: ignore[no-redef]
"""Find GPIB listeners."""
for board, boardpad in _find_boards():
for i in range(31):
Expand Down Expand Up @@ -332,7 +295,7 @@ class _GPIBCommon(Session):

# Override parsed to take into account the fact that this class is only used
# for a specific kind of resource
parsed: Union[GPIBIntfc, GPIBInstr]
parsed: GPIBIntfc | GPIBInstr

#: Bus wide controller.
controller: Gpib
Expand Down Expand Up @@ -383,7 +346,7 @@ def after_parsing(self) -> None:

def _get_timeout(
self, attribute: constants.ResourceAttribute
) -> Tuple[int, StatusCode]:
) -> tuple[int, StatusCode]:
if self.interface:
# 0x3 is the hexadecimal reference to the IbaTMO (timeout) configuration
# option in linux-gpib.
Expand Down Expand Up @@ -441,7 +404,7 @@ def close(self) -> StatusCode:
self.controller.close()
return StatusCode.success

def read(self, count: int) -> Tuple[bytes, StatusCode]:
def read(self, count: int) -> tuple[bytes, StatusCode]:
"""Reads data from device or interface synchronously.

Corresponds to viRead function of the VISA library.
Expand Down Expand Up @@ -469,7 +432,7 @@ def read(self, count: int) -> Tuple[bytes, StatusCode]:

return self._read(reader, count, checker, False, None, False, gpib.GpibError)

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 Down Expand Up @@ -565,7 +528,7 @@ def gpib_control_ren(self, mode: constants.RENLineOperation) -> StatusCode:

return constants.StatusCode.success

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 Expand Up @@ -725,7 +688,7 @@ class GPIBSession(_GPIBCommon): # type: ignore[no-redef]
parsed: GPIBInstr

@staticmethod
def list_resources() -> List[str]:
def list_resources() -> list[str]:
return [
"GPIB%d::%d::INSTR" % (board, pad)
if sad == 0
Expand Down Expand Up @@ -777,7 +740,7 @@ def assert_trigger(self, protocol: constants.TriggerProtocol) -> StatusCode:
except gpib.GpibError as e:
return convert_gpib_error(e, self.interface.ibsta(), "assert trigger")

def read_stb(self) -> Tuple[int, StatusCode]:
def read_stb(self) -> tuple[int, StatusCode]:
"""Read the device status byte."""
try:
return self.interface.serial_poll(), StatusCode.success
Expand All @@ -786,7 +749,7 @@ def read_stb(self) -> Tuple[int, StatusCode]:

def _get_attribute(
self, attribute: constants.ResourceAttribute
) -> Tuple[Any, StatusCode]:
) -> tuple[Any, StatusCode]:
"""Get the value for a given VISA attribute for this session.

Use to implement custom logic for attributes. GPIB::INSTR have the
Expand Down Expand Up @@ -886,10 +849,10 @@ class GPIBInterface(_GPIBCommon):
parsed: GPIBIntfc

@staticmethod
def list_resources() -> List[str]:
def list_resources() -> list[str]:
return ["GPIB%d::INTFC" % board for board, pad in _find_boards()]

def gpib_command(self, command_bytes: bytes) -> Tuple[int, StatusCode]:
def gpib_command(self, command_bytes: bytes) -> tuple[int, StatusCode]:
"""Write GPIB command byte on the bus.

Corresponds to viGpibCommand function of the VISA library.
Expand Down Expand Up @@ -992,7 +955,7 @@ def gpib_pass_control(
status = gpib_lib.ibpct(did)
return convert_gpib_status(status)

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. GPIB::INTFC have the
Expand Down
Loading