From 8b478188d14d9f9811a3011bdc303e8cb10a593d Mon Sep 17 00:00:00 2001 From: Jimmy van den Bergh <93945571+Jimmyvandenbergh@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:14:48 +0100 Subject: [PATCH] Support partial usbtmc reads and request the `size` amount of bytes from the device (#470) * updated to support partial usbtmc message reads from device to support IEEE 488.2 arbitrary block reads fixes the requested data to be send in the header instead of recv_chunk. * Speedoptimazation for read, we now use large chuck sizes. * Fix typo in chunck VS chunk * Update release notes --------- Co-authored-by: Matthieu Dartiailh --- CHANGES | 2 + pyvisa_py/protocols/usbtmc.py | 73 +++++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/CHANGES b/CHANGES index 823fa5a6..91133c39 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,8 @@ PyVISA-py Changelog has been read (see specification), and only expects a header on the first packet received. - fix usbtmc implementation to properly discard the alignment bytes ensuring only the actual data (`transfer_size`) is retained in the message PR #465 +- Implemented partial USBTMC message functionality that allows reading the amount of bytes + specified by host PR #470 - add support for VI_ATTR_SUPPRESS_END_EN for USB resources PR #449 0.7.2 (07/03/2024) diff --git a/pyvisa_py/protocols/usbtmc.py b/pyvisa_py/protocols/usbtmc.py index 8bbac953..da0cc888 100644 --- a/pyvisa_py/protocols/usbtmc.py +++ b/pyvisa_py/protocols/usbtmc.py @@ -12,6 +12,7 @@ """ import enum +import math import struct import time import warnings @@ -455,13 +456,7 @@ def write(self, data): return size def read(self, size): - header_size = 12 - max_padding = 511 - recv_chunk = self.usb_recv_ep.wMaxPacketSize - header_size - - if size > 0 and size < recv_chunk: - recv_chunk = size - + usbtmc_header_size = 12 eom = False raw_read = super(USBTMC, self).read @@ -473,33 +468,61 @@ def read(self, size): received_transfer = bytearray() self._btag = (self._btag % 255) + 1 - req = BulkInMessage.build_array(self._btag, recv_chunk, None) - + req = BulkInMessage.build_array(self._btag, size, None) raw_write(req) try: - resp = raw_read(recv_chunk + header_size + max_padding) + # make sure the data request is in multitudes of wMaxPacketSize. + # + 1 * wMaxPacketSize for message sizes that equals wMaxPacketSize == size + usbtmc_header_size. + # This to be able to retrieve a short package to end communication + # (see USB 2.0 Section 5.8.3 and USBTMC Section 3.3) + chunk_size = ( + math.floor( + (size + usbtmc_header_size) / self.usb_recv_ep.wMaxPacketSize + ) + + 1 + ) * self.usb_recv_ep.wMaxPacketSize + resp = raw_read(chunk_size) + response = BulkInMessage.from_bytes(resp) received_transfer.extend(response.data) - while ( - len(resp) == self.usb_recv_ep.wMaxPacketSize - or len(received_transfer) < response.transfer_size - ): - # USBTMC Section 3.3 specifies that the first usb packet - # must contain the header. the remaining packets do not need - # the header the message is finished when a "short packet" - # is sent (one whose length is less than wMaxPacketSize) - # wMaxPacketSize may be incorrectly reported by certain drivers. - # Therefore, continue reading until the transfer_size is reached. - resp = raw_read(recv_chunk + header_size + max_padding) - received_transfer.extend(resp) # Detect EOM only when device sends all expected bytes. if len(received_transfer) >= response.transfer_size: eom = response.transfer_attributes & 1 - # Truncate data to the specified length (discard padding) - # USBTMC header (12 bytes) has already truncated - received_message.extend(received_transfer[: response.transfer_size]) + if not eom and len(received_transfer) >= size: + # Read asking for 'size' bytes from the device. + # This may be less then the device wants to send back in a message + # Therefore the request does not mean that we must receive a EOM. + # Multiple `transfers` will be required to retrieve the remaining bytes. + eom = True + else: + while ( + (len(resp) % self.usb_recv_ep.wMaxPacketSize) == 0 + or len(received_transfer) < response.transfer_size + ) and not eom: + # USBTMC Section 3.3 specifies that the first usb packet + # must contain the header. the remaining packets do not need + # the header the message is finished when a "short packet" + # is sent (one whose length is less than wMaxPacketSize) + # wMaxPacketSize may be incorrectly reported by certain drivers. + # Therefore, continue reading until the transfer_size is reached. + chunk_size = ( + math.floor( + (size - len(received_transfer)) + / self.usb_recv_ep.wMaxPacketSize + ) + + 1 + ) * self.usb_recv_ep.wMaxPacketSize + resp = raw_read(chunk_size) + received_transfer.extend(resp) + if len(received_transfer) >= response.transfer_size: + eom = response.transfer_attributes & 1 + if not eom and len(received_transfer) >= size: + eom = True + # Truncate data to the specified length (discard padding) + # USBTMC header (12 bytes) has already truncated + received_message.extend(received_transfer[: response.transfer_size]) except (usb.core.USBError, ValueError): # Abort failed Bulk-IN operation. self._abort_bulk_in(self._btag)