Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
9306054
Add GPIB-ENET/100 wire protocol module skeleton
bytewarrior May 31, 2026
20a3c21
Add stateful chunk reader for NI GPIB-ENET/100 read streams
bytewarrior May 31, 2026
2d33fd4
Add EnetConnection with main + companion socket lifecycle
bytewarrior May 31, 2026
544eab3
Add GPIB-ENET/100 session open / close sequences
bytewarrior May 31, 2026
1e8de96
Add GPIB-ENET/100 device verbs (ibwrt/ibrd/ibclr/ibtrg/ibloc/ibrsp)
bytewarrior May 31, 2026
1a9cadf
Add NIEnet100TCPIPIntfcSession for NIENET100-TCPIP::INTFC resources
bytewarrior May 31, 2026
0b0901c
Add NIEnet100InstrSession for GPIB::INSTR via NI GPIB-ENET/100
bytewarrior May 31, 2026
c60218e
Wire GPIB-ENET/100 into pyvisa-py dispatch and module loader
bytewarrior May 31, 2026
8a6e3c4
Add offline unit tests for GPIB-ENET/100 wire protocol
bytewarrior May 31, 2026
6d8ebcf
Add lazy wait/control socket lifecycle for GPIB-ENET/100 SRQ support
bytewarrior May 31, 2026
1fce00d
Add ibwait verb for GPIB-ENET/100 SRQ polling
bytewarrior May 31, 2026
94ef138
Add ibsic + notify_off_async_device control-socket verbs and cleanup
bytewarrior May 31, 2026
c7978f8
Add offline tests for GPIB-ENET/100 SRQ verbs and wait/control lifecycle
bytewarrior May 31, 2026
ac08887
Add GPIB-ENET/100 discovery frame primitives
bytewarrior May 31, 2026
04e9209
Add discover() UDP loop for GPIB-ENET/100 bridges
bytewarrior May 31, 2026
b1f9fef
Wire UDP discovery into NIEnet100TCPIPIntfcSession.list_resources
bytewarrior May 31, 2026
244d3e7
Add offline tests for GPIB-ENET/100 discovery
bytewarrior May 31, 2026
729746e
Add wire-level hardware tests against a real GPIB-ENET/100 bridge
bytewarrior May 31, 2026
3c08af0
Add full-stack hardware tests via pyvisa.ResourceManager('@py')
bytewarrior May 31, 2026
a757037
Resolve PYVISA_TEST_NIENET100_HOST to IP for discovery assertions
bytewarrior May 31, 2026
b3c82dd
Gate cross-subnet discovery test behind opt-in env var
bytewarrior May 31, 2026
ae87639
Add hex-dump DEBUG logging to main-socket send/recv
bytewarrior May 31, 2026
2fa79c0
Treat unknown zero-length chunk flags as end-of-stream
bytewarrior May 31, 2026
ac327fa
Relax ibwait smoke test: accept sta=0 as a valid no-event response
bytewarrior May 31, 2026
d3daa64
Read status headers through the chunk framing layer
bytewarrior May 31, 2026
3bbb756
Use per-call ibrd tmo_ms instead of IbcTMO setter in timeout test
bytewarrior May 31, 2026
4fb1a0a
Parse ibrsp response as one chunk containing status + STB combined
bytewarrior May 31, 2026
f87ac3d
Make ibrd default tmo_ms useful and tolerate no-END response path
bytewarrior May 31, 2026
736e0b6
Propagate pyvisa session timeout into the wire-level ibrd
bytewarrior May 31, 2026
f943761
Use 'NI-ENET100-TCPIP' as the interface-type segment and index dispat…
bytewarrior May 31, 2026
bf31083
Drop IbcTMO setter from session _set_timeout and widen socket buffer
bytewarrior Jun 1, 2026
3521ded
Make assisted INSTR tests robust against real instruments
bytewarrior Jun 1, 2026
a90020b
Refresh stale comment on gpib_control_ren
bytewarrior Jun 1, 2026
279d7b0
Updated comments, removed old references to wire spec.
bytewarrior Jun 1, 2026
c1f272a
Wrap TERM constant to satisfy ruff line-length
bytewarrior Jun 1, 2026
f026c1d
Track bracket lifecycle in the protocol layer and close it on teardown
bytewarrior Jun 1, 2026
f2bc23c
Reduce import checking to one check (sufficient for determining compa…
bytewarrior Jun 4, 2026
18956ed
Enhance read buffer management in NIEnet100InstrSession to prevent da…
bytewarrior Jun 4, 2026
7fd12d1
Added a blank line to all multiline docstrings at the end, like numpy.
bytewarrior Jun 4, 2026
af69f5a
Register NI-ENET100 INTFC in session-class expectations test
bytewarrior Jun 4, 2026
e52332d
Add inert central GPIB::INSTR dispatcher scaffold
bytewarrior Jun 4, 2026
a83ad46
Wire native and Prologix GPIB::INSTR backends as resolvers
bytewarrior Jun 4, 2026
3ddfa9e
Wire NI-ENET/100 bridge as highest-precedence GPIB::INSTR backend
bytewarrior Jun 4, 2026
4fb3eb8
Activate central GPIB::INSTR dispatcher and retire per-module hooks
bytewarrior Jun 4, 2026
cc75f5d
Type GPIB::INSTR resolvers against GPIBInstr for mypy
bytewarrior Jun 4, 2026
bf48ab3
Declare dynamically-bound EnetConnection verbs for type checkers
bytewarrior Jun 4, 2026
127c71d
Fix mypy types in NIEnet100 INTFC/INSTR session layer
bytewarrior Jun 4, 2026
28c6179
Fix mypy types in NI-ENET/100 assisted tests
bytewarrior Jun 4, 2026
63d7cd9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2026
5b0a66f
Modernize typing to PEP 585/604 for Python 3.10 floor
bytewarrior Jun 6, 2026
c3bad1d
Fix NI-ENET/100 socket tests on Unix (getsockname on socketpair)
bytewarrior Jun 6, 2026
dc760e8
Added a TODO marker in the code for finalizing pyvisa prerequisite wa…
bytewarrior Jun 21, 2026
ac92c7a
Removed old, no longer needed comment at the end of the file.
bytewarrior Jun 21, 2026
d75951c
Removed old, stale reference to reverse engineering notes.
bytewarrior Jun 21, 2026
48ccabb
Discard buffered read tail on new write in NIEnet100 INSTR session
bytewarrior Jun 21, 2026
46a91cd
Add NIEnet100 assisted tests for the session read buffer
bytewarrior Jun 21, 2026
588c23a
Fixed Docstring style.
bytewarrior Jun 6, 2026
b0b8237
Narrow HOST to str in wire tests under require_bridge
bytewarrior Jun 6, 2026
2d850c1
Send NIEnet100 ibwrt payloads unpadded
bytewarrior Jun 21, 2026
afe4c57
Report the main socket port in the NIEnet100 companion hello
bytewarrior Jun 21, 2026
f0f343a
Poll ibwait on the companion socket with verb 0x22
bytewarrior Jun 21, 2026
1783928
Read the ibrsp STB by position, not gated on cnt
bytewarrior Jun 21, 2026
071188c
Remove the fabricated wait-socket SRQ machinery
bytewarrior Jun 21, 2026
be270fc
Assert a real SRQ in the assisted ibwait test
bytewarrior Jun 22, 2026
a3e7bf6
Fix stale NIEnet100 doc comments
bytewarrior Jun 22, 2026
befa709
Send ibsic as a 0x1c frame on the main socket
bytewarrior Jun 22, 2026
771217f
Remove the now-unused 5005 control socket and 'O'-verb helper
bytewarrior Jun 22, 2026
913dcdb
Add EnetConnection.open_board_session for board-level sessions
bytewarrior Jun 22, 2026
ae5d3d3
Fix NIENET100 open timeout when open_timeout is 0 (VI_TMO_IMMEDIATE)
bytewarrior Jun 22, 2026
0fc1b37
Wire ibsic into the session layer via gpib_send_ifc on the INTFC
bytewarrior Jun 22, 2026
274a07b
Minor docstring updates.
bytewarrior Jun 22, 2026
fb1eee9
Use shared gpib_constants for NIEnet100 status/timeout/error codes
bytewarrior Jul 5, 2026
7f0a9ed
Add EnetConnection.set_read_timeout_code for mid-session timeout changes
bytewarrior Jul 5, 2026
b15748b
Drive the NIEnet100 read timeout from the session timeout
bytewarrior Jul 5, 2026
c42d611
Keep the NIEnet100 read buffer across writes
bytewarrior Jul 5, 2026
6b8e3b6
Place frame length assertion before the frame content assertion as su…
bytewarrior Jul 6, 2026
db0ca25
Remove isEnabledFor queries before send/recv logger calls.
bytewarrior Jul 6, 2026
1aaf956
Require pyvisa 1.17.0 and drop the temporary ENET/100 guards
bytewarrior Jul 6, 2026
7a5c771
Sort imports after dropping the ENET/100 test skip guards
bytewarrior Jul 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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = ["pyvisa>=1.15.0", "typing_extensions"]
dependencies = ["pyvisa>=1.17.0", "typing_extensions"]
dynamic = ["version"]

[project.optional-dependencies]
Expand Down
87 changes: 25 additions & 62 deletions pyvisa_py/gpib.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,68 +9,31 @@

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 gpib_constants, prologix
from . import gpib_constants
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 == gpib_constants.sad.NO_SAD
else "GPIB%d::%d::%d::INSTR"
% (board, pad, sad - gpib_constants.sad.FIRST_SAD)
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 @@ -83,7 +46,7 @@ def make_unavailable(msg: str) -> Type:

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

"""
Expand Down Expand Up @@ -155,7 +118,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 @@ -164,7 +127,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 @@ -329,7 +292,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 @@ -384,7 +347,7 @@ def after_parsing(self) -> None:

def _get_timeout(
self, attribute: constants.ResourceAttribute
) -> Tuple[int, StatusCode]:
) -> tuple[int, StatusCode]:
if self.interface:
gpib_timeout = self.interface.ask(gpib_constants.ask.IbaTMO)
if gpib_timeout and gpib_timeout < len(TIMETABLE):
Expand Down Expand Up @@ -440,7 +403,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 All @@ -467,7 +430,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 @@ -561,7 +524,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 @@ -738,7 +701,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 == gpib_constants.sad.NO_SAD
Expand Down Expand Up @@ -791,7 +754,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 @@ -800,7 +763,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 @@ -896,10 +859,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 @@ -1002,7 +965,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