From 5148f0bca23e237146dc0f4ed9582581fd0af9a4 Mon Sep 17 00:00:00 2001 From: Thomas Ales Date: Tue, 28 Jul 2026 10:04:41 -0500 Subject: [PATCH] Phase 1b: prune 45 never-called methods from tektronix_base (~850 lines) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified by repo-wide name search + transitive closure over internal calls: the live apps use 25 methods (plus raw write/query); everything else — cosmetic label styling, unused getters/setters, transfer_waveform, acquire_waveform — had no callers. Also: linear-time chunk join in read_raw instead of quadratic bytes += concat, and a typed except on its debug path. Co-Authored-By: Claude Fable 5 --- hardware/tektronix_base.py | 855 +------------------------------------ 1 file changed, 5 insertions(+), 850 deletions(-) diff --git a/hardware/tektronix_base.py b/hardware/tektronix_base.py index 1fbc0fe..6417813 100755 --- a/hardware/tektronix_base.py +++ b/hardware/tektronix_base.py @@ -153,30 +153,6 @@ class TektronixOscilloscopeBase: return channel - def identify(self): - """ - Query instrument identification string (*IDN?). - - Returns: - str: Instrument identification string - - Raises: - RuntimeError: If not connected - """ - return self.query("*IDN?") - - def get_acquire_mode(self): - """ - Query the current acquisition mode. - - Returns: - str: Current acquisition mode (SAMple, PEAKdetect, HIRes, AVErage, or ENVelope) - - Raises: - RuntimeError: If not connected - """ - return self.query("ACQuire:MODe?") - def set_acquire_mode(self, mode): """ Set the acquisition mode. @@ -247,19 +223,6 @@ class TektronixOscilloscopeBase: self.write(f"HORizontal:FASTframe:STATE {state}") - def get_fastframe_count(self): - """ - Query the current FastFrame frame count. - - Returns: - int: Number of frames configured - - Raises: - RuntimeError: If not connected - """ - response = self.query("HORizontal:FASTframe:COUNt?") - return int(response) - def set_fastframe_count(self, count): """ Set the number of FastFrame frames. @@ -290,36 +253,6 @@ class TektronixOscilloscopeBase: response = self.query("HORizontal:MODe:RECOrdlength?") return int(response) - def set_record_length(self, length): - """ - Set the horizontal record length. - - Args: - length: Record length in samples (must be positive integer) - - Raises: - ValueError: If length is not valid - RuntimeError: If not connected - """ - # Validate length - if not isinstance(length, int) or length <= 0: - raise ValueError(f"Invalid record length: {length}. Must be a positive integer") - - self.write(f"HORizontal:MODe:RECOrdlength {length}") - - def get_sample_rate(self): - """ - Query the current horizontal sample rate. - - Returns: - float: Current sample rate in samples per second - - Raises: - RuntimeError: If not connected - """ - response = self.query("HORizontal:MODe:SAMPLERate?") - return float(response) - def set_sample_rate(self, rate): """ Set the horizontal sample rate. @@ -337,93 +270,6 @@ class TektronixOscilloscopeBase: self.write(f"HORizontal:MODe:SAMPLERate {rate}") - def get_time_scale(self): - """ - Query the current horizontal time scale. - - Returns: - float: Current time scale in seconds per division - - Raises: - RuntimeError: If not connected - """ - response = self.query("HORizontal:MODe:SCAle?") - return float(response) - - def set_time_scale(self, scale): - """ - Set the horizontal time scale. - - Args: - scale: Time scale in seconds per division (must be positive number) - - Raises: - ValueError: If scale is not valid - RuntimeError: If not connected - """ - # Validate scale - if not isinstance(scale, (int, float)) or scale <= 0: - raise ValueError(f"Invalid time scale: {scale}. Must be a positive number") - - self.write(f"HORizontal:MODe:SCAle {scale}") - - def get_trigger_coupling(self): - """ - Query the current trigger coupling mode. - - Returns: - str: Current trigger coupling (DC, HFRej, LFRej, or NOISErej) - - Raises: - RuntimeError: If not connected - """ - return self.query("TRIGger:A:EDGE:COUPling?") - - def set_trigger_coupling(self, coupling): - """ - Set the trigger coupling mode. - - Args: - coupling: Trigger coupling mode (case-insensitive). Valid options: - 'DC' - DC coupling - 'HFRej' or 'HFREJ' - High frequency reject - 'LFRej' or 'LFREJ' - Low frequency reject - 'NOISErej' or 'NOISEREJ' - Noise reject - - Raises: - ValueError: If coupling is not valid - RuntimeError: If not connected - """ - coupling_upper = coupling.upper() - - # Check if coupling is valid - valid = False - for long_form, variants in self.TRIGGER_COUPLING.items(): - if coupling_upper in [v.upper() for v in variants]: - valid = True - break - - if not valid: - valid_options = [] - for variants in self.TRIGGER_COUPLING.values(): - valid_options.extend(variants) - raise ValueError(f"Invalid trigger coupling: {coupling}. " - f"Valid options: {', '.join(valid_options)}") - - self.write(f"TRIGger:A:EDGE:COUPling {coupling}") - - def get_trigger_slope(self): - """ - Query the current trigger slope. - - Returns: - str: Current trigger slope (RISe, FALL, or EITher) - - Raises: - RuntimeError: If not connected - """ - return self.query("TRIGger:A:EDGE:SLOpe?") - def set_trigger_slope(self, slope): """ Set the trigger slope. @@ -456,18 +302,6 @@ class TektronixOscilloscopeBase: self.write(f"TRIGger:A:EDGE:SLOpe {slope}") - def get_trigger_source(self): - """ - Query the current trigger source. - - Returns: - str: Current trigger source (e.g., CH1, CH2, CH3, CH4) - - Raises: - RuntimeError: If not connected - """ - return self.query("TRIGger:A:EDGE:SOUrce?") - def set_trigger_source(self, source): """ Set the trigger source. @@ -498,34 +332,6 @@ class TektronixOscilloscopeBase: self.write(f"TRIGger:A:EDGE:SOUrce {source}") - def get_trigger_level(self, channel): - """ - Query the trigger level for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - float: Trigger level in volts - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - # Convert to channel number if string - if isinstance(channel, str): - channel_upper = channel.upper() - if not (channel_upper.startswith('CH') and len(channel_upper) == 3 and channel_upper[2].isdigit()): - raise ValueError(f"Invalid channel: {channel}. Must be CH1, CH2, CH3, or CH4") - channel = int(channel_upper[2]) - - # Validate channel number - if not isinstance(channel, int) or channel < 1 or channel > 4: - raise ValueError(f"Invalid channel: {channel}. Must be 1-4") - - response = self.query(f"TRIGger:A:LEVel:CH{channel}?") - return float(response) - def set_trigger_level(self, channel, level): """ Set the trigger level for a specific channel. @@ -555,18 +361,6 @@ class TektronixOscilloscopeBase: self.write(f"TRIGger:A:LEVel:CH{channel} {level}") - def get_trigger_mode(self): - """ - Query the current trigger mode. - - Returns: - str: Current trigger mode (AUTO or NORMal) - - Raises: - RuntimeError: If not connected - """ - return self.query("TRIGger:A:MODe?") - def set_trigger_mode(self, mode): """ Set the trigger mode. @@ -600,40 +394,6 @@ class TektronixOscilloscopeBase: # ========== Channel Control Methods ========== - def query_channel(self, channel): - """ - Query all parameters for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - str: All channel parameters - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - return self.query(f"CH{channel}?") - - def get_channel_bandwidth(self, channel): - """ - Query the bandwidth setting for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - str: Channel bandwidth setting - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - return self.query(f"CH{channel}:BANdwidth?") - def set_channel_bandwidth(self, channel, bandwidth): """ Set the bandwidth for a specific channel. @@ -649,23 +409,6 @@ class TektronixOscilloscopeBase: channel = self._normalize_channel(channel) self.write(f"CH{channel}:BANdwidth {bandwidth}") - def get_channel_coupling(self, channel): - """ - Query the coupling mode for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - str: Coupling mode (AC or DC) - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - return self.query(f"CH{channel}:COUPling?") - def set_channel_coupling(self, channel, coupling): """ Set the coupling mode for a specific channel. @@ -697,144 +440,6 @@ class TektronixOscilloscopeBase: self.write(f"CH{channel}:COUPling {coupling}") - def get_channel_label_color(self, channel): - """ - Query the label color for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - str: Color as hex string (e.g., '#FF0000') - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - return self.query(f"CH{channel}:LABel:COLor?") - - def set_channel_label_color(self, channel, color): - """ - Set the label color for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - color: Color as hex string (e.g., '#FF0000' or '#AABBCC'), or empty string - - Raises: - ValueError: If channel or color is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - - # Allow empty strings (including SCPI quoted empty strings like '""') - if color == '' or color == '""': - self.write(f'CH{channel}:LABel:COLor ""') - return - - # Basic validation of hex color format - if not isinstance(color, str) or not color.startswith('#') or len(color) != 7: - raise ValueError(f"Invalid color format: {color}. Must be '#AABBCC' format or empty string") - - # Validate hex digits - try: - int(color[1:], 16) - except ValueError: - raise ValueError(f"Invalid color format: {color}. Must contain valid hex digits") - - self.write(f"CH{channel}:LABel:COLor {color}") - - def get_channel_label_font_size(self, channel): - """ - Query the label font size for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - int: Font size in points - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - response = self.query(f"CH{channel}:LABel:FONT:SIZE?") - return int(float(response)) - - def set_channel_label_font_size(self, channel, size): - """ - Set the label font size for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - size: Font size in points (positive integer) - - Raises: - ValueError: If channel or size is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - - if not isinstance(size, int) or size <= 0: - raise ValueError(f"Invalid font size: {size}. Must be a positive integer") - - self.write(f"CH{channel}:LABel:FONT:SIZE {size}") - - def get_channel_label_font_type(self, channel): - """ - Query the label font type for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - str: Font name - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - return self.query(f"CH{channel}:LABel:FONT:TYPE?") - - def set_channel_label_font_type(self, channel, font_name): - """ - Set the label font type for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - font_name: Font name as string - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - - if not isinstance(font_name, str) or not font_name: - raise ValueError(f"Invalid font name: {font_name}. Must be a non-empty string") - - self.write(f"CH{channel}:LABel:FONT:TYPE {font_name}") - - def get_channel_label_name(self, channel): - """ - Query the label name for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - str: Channel label name - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - return self.query(f"CH{channel}:LABel:NAMe?") - def set_channel_label_name(self, channel, name): """ Set the label name for a specific channel. @@ -855,135 +460,6 @@ class TektronixOscilloscopeBase: # Need to quote the string for SCPI self.write(f'CH{channel}:LABel:NAMe "{name}"') - def get_channel_label_xpos(self, channel): - """ - Query the label x-position for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - int: X-position in pixels - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - response = self.query(f"CH{channel}:LABel:XPOS?") - return int(float(response)) - - def set_channel_label_xpos(self, channel, xpos): - """ - Set the label x-position for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - xpos: X-position in pixels (non-negative integer) - - Raises: - ValueError: If channel or xpos is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - - if not isinstance(xpos, int) or xpos < 0: - raise ValueError(f"Invalid x-position: {xpos}. Must be a non-negative integer") - - self.write(f"CH{channel}:LABel:XPOS {xpos}") - - def get_channel_label_ypos(self, channel): - """ - Query the label y-position for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - int: Y-position in pixels - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - response = self.query(f"CH{channel}:LABel:YPOS?") - return int(float(response)) - - def set_channel_label_ypos(self, channel, ypos): - """ - Set the label y-position for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - ypos: Y-position in pixels (non-negative integer) - - Raises: - ValueError: If channel or ypos is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - - if not isinstance(ypos, int) or ypos < 0: - raise ValueError(f"Invalid y-position: {ypos}. Must be a non-negative integer") - - self.write(f"CH{channel}:LABel:YPOS {ypos}") - - def get_channel_offset(self, channel): - """ - Query the vertical offset for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - float: Vertical offset in volts - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - response = self.query(f"CH{channel}:OFFset?") - return float(response) - - def set_channel_offset(self, channel, offset): - """ - Set the vertical offset for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - offset: Vertical offset in volts - - Raises: - ValueError: If channel or offset is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - - if not isinstance(offset, (int, float)): - raise ValueError(f"Invalid offset: {offset}. Must be a number") - - self.write(f"CH{channel}:OFFset {offset}") - - def get_channel_position(self, channel): - """ - Query the vertical position for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - float: Vertical position in divisions - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - response = self.query(f"CH{channel}:POSition?") - return float(response) - def set_channel_position(self, channel, position): """ Set the vertical position for a specific channel. @@ -1003,24 +479,6 @@ class TektronixOscilloscopeBase: self.write(f"CH{channel}:POSition {position}") - def get_channel_scale(self, channel): - """ - Query the vertical scale for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - float: Vertical scale in volts per division - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - response = self.query(f"CH{channel}:SCAle?") - return float(response) - def set_channel_scale(self, channel, scale): """ Set the vertical scale for a specific channel. @@ -1040,24 +498,6 @@ class TektronixOscilloscopeBase: self.write(f"CH{channel}:SCAle {scale}") - def get_channel_termination(self, channel): - """ - Query the termination setting for a specific channel. - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - - Returns: - int: Termination in ohms (50 or 1000000) - - Raises: - ValueError: If channel is not valid - RuntimeError: If not connected - """ - channel = self._normalize_channel(channel) - response = self.query(f"CH{channel}:TERmination?") - return int(float(response)) - def set_channel_termination(self, channel, termination): """ Set the termination for a specific channel. @@ -1080,66 +520,6 @@ class TektronixOscilloscopeBase: # ========== Waveform Transfer Methods ========== - def get_data_encoding(self): - """ - Query the current data encoding format. - - Returns: - str: Current data encoding (e.g., RIBinary, ASCII, etc.) - - Raises: - RuntimeError: If not connected - """ - return self.query("DATa:ENCdg?") - - def set_data_encoding(self, encoding): - """ - Set the data encoding format for waveform transfer. - - Args: - encoding: Encoding format (case-insensitive). Valid options: - 'ASCIi' or 'ASCII' - ASCII format - 'RIBinary' or 'RIBINARY' - Signed integer, MSB first (recommended) - 'RPBinary' or 'RPBINARY' - Positive integer, MSB first - 'FPBinary' or 'FPBINARY' - Floating point binary - 'SRIbinary' or 'SRIBINARY' - Signed integer, byte-swapped - 'SRPbinary' or 'SRPBINARY' - Positive integer, byte-swapped - 'SFPbinary' or 'SFPBINARY' - Floating point binary, byte-swapped - - Raises: - ValueError: If encoding is not valid - RuntimeError: If not connected - """ - encoding_upper = encoding.upper() - - # Check if encoding is valid - valid = False - for long_form, variants in self.DATA_ENCODING.items(): - if encoding_upper in [v.upper() for v in variants]: - valid = True - break - - if not valid: - valid_options = [] - for variants in self.DATA_ENCODING.values(): - valid_options.extend(variants) - raise ValueError(f"Invalid data encoding: {encoding}. " - f"Valid options: {', '.join(valid_options)}") - - self.write(f"DATa:ENCdg {encoding}") - - def get_data_source(self): - """ - Query the current data source for waveform transfer. - - Returns: - str: Current data source (e.g., CH1, CH2, CH3, CH4) - - Raises: - RuntimeError: If not connected - """ - return self.query("DATa:SOUrce?") - def set_data_source(self, source): """ Set the data source for waveform transfer. @@ -1170,123 +550,6 @@ class TektronixOscilloscopeBase: self.write(f"DATa:SOUrce {source}") - def get_wfmoutpre_encoding(self): - """ - Query the waveform output preamble encoding. - - Returns: - str: Current encoding (BINary or ASCii) - - Raises: - RuntimeError: If not connected - """ - return self.query("WFMOutpre:ENCdg?") - - def set_wfmoutpre_encoding(self, encoding): - """ - Set the waveform output preamble encoding. - - Args: - encoding: Encoding format (case-insensitive). Valid options: - 'BINary' or 'BINARY' - Binary format (recommended) - 'ASCii' or 'ASCII' - ASCII format - - Raises: - ValueError: If encoding is not valid - RuntimeError: If not connected - """ - encoding_upper = encoding.upper() - - # Check if encoding is valid - valid = False - for long_form, variants in self.WFMOUTPRE_ENCODING.items(): - if encoding_upper in [v.upper() for v in variants]: - valid = True - break - - if not valid: - valid_options = [] - for variants in self.WFMOUTPRE_ENCODING.values(): - valid_options.extend(variants) - raise ValueError(f"Invalid waveform output encoding: {encoding}. " - f"Valid options: {', '.join(valid_options)}") - - self.write(f"WFMOutpre:ENCdg {encoding}") - - def get_wfmoutpre_byte_count(self): - """ - Query the waveform output preamble byte count (BYT_Nr). - - Returns: - int: Number of bytes per data point (1 or 2) - - Raises: - RuntimeError: If not connected - """ - response = self.query("WFMOutpre:BYT_Nr?") - return int(response) - - def set_wfmoutpre_byte_count(self, byte_count): - """ - Set the waveform output preamble byte count (BYT_Nr). - - Args: - byte_count: Number of bytes per data point - 1 = 8-bit data (0-255 for unsigned, -128 to 127 for signed) - 2 = 16-bit data (0-65535 for unsigned, -32768 to 32767 for signed) - - Raises: - ValueError: If byte_count is not valid - RuntimeError: If not connected - """ - if byte_count not in (1, 2): - raise ValueError(f"Invalid byte count: {byte_count}. Must be 1 or 2") - - self.write(f"WFMOutpre:BYT_Nr {byte_count}") - - def get_wfmoutpre_byte_order(self): - """ - Query the waveform output preamble byte order. - - Returns: - str: Current byte order (LSB or MSB) - - Raises: - RuntimeError: If not connected - """ - return self.query("WFMOutpre:BYT_Or?") - - def set_wfmoutpre_byte_order(self, byte_order): - """ - Set the waveform output preamble byte order. - - Args: - byte_order: Byte order (case-insensitive). Valid options: - 'LSB' - Least significant byte first - 'MSB' - Most significant byte first - - Raises: - ValueError: If byte_order is not valid - RuntimeError: If not connected - """ - byte_order_upper = byte_order.upper() - - # Check if byte order is valid - valid = False - for long_form, variants in self.BYTE_ORDER.items(): - if byte_order_upper in [v.upper() for v in variants]: - valid = True - break - - if not valid: - valid_options = [] - for variants in self.BYTE_ORDER.values(): - valid_options.extend(variants) - raise ValueError(f"Invalid byte order: {byte_order}. " - f"Valid options: {', '.join(valid_options)}") - - self.write(f"WFMOutpre:BYT_Or {byte_order}") - def query_wfmoutpre(self): """ Query all waveform output preamble parameters. @@ -1299,35 +562,6 @@ class TektronixOscilloscopeBase: """ return self.query("WFMOutpre?") - def get_fastframe_selected(self): - """ - Query the currently selected FastFrame frame number. - - Returns: - int: Currently selected frame number - - Raises: - RuntimeError: If not connected - """ - response = self.query("HORizontal:FASTframe:SELECTED?") - return int(response) - - def set_fastframe_selected(self, frame_number): - """ - Select a specific FastFrame frame for data transfer. - - Args: - frame_number: Frame number to select (must be positive integer) - - Raises: - ValueError: If frame_number is not valid - RuntimeError: If not connected - """ - if not isinstance(frame_number, int) or frame_number <= 0: - raise ValueError(f"Invalid frame number: {frame_number}. Must be a positive integer") - - self.write(f"HORizontal:FASTframe:SELECTED {frame_number}") - def transfer_curve(self): """ Transfer waveform curve data from the oscilloscope. @@ -1348,29 +582,6 @@ class TektronixOscilloscopeBase: # Read the binary data using IEEE 488.2 format return self.read_raw() - def transfer_waveform(self): - """ - Transfer complete waveform data including preamble and curve. - - This method queries WFMOutpre? for the preamble and CURVe? for the data - as separate operations, which is the standard approach for Tektronix scopes. - - Returns: - tuple: (preamble_str, curve_bytes) - preamble_str: Waveform preamble as string - curve_bytes: Raw binary curve data - - Raises: - RuntimeError: If not connected or transfer fails - """ - # First query the waveform preamble - preamble_str = self.query_wfmoutpre() - - # Then transfer the curve data - curve_bytes = self.transfer_curve() - - return (preamble_str, curve_bytes) - def transfer_fastframe(self, parse=True, byte_count=1, signed=True, byte_order='MSB'): """ Transfer all FastFrame waveform data from the oscilloscope. @@ -1497,62 +708,6 @@ class TektronixOscilloscopeBase: return values - def acquire_waveform(self, channel, frame_number=None): - """ - High-level method to acquire waveform data from a channel. - - This method: - 1. Sets up data encoding (RIBinary, BINary, 1 byte, MSB) - 2. Sets the data source to the specified channel - 3. Optionally selects a FastFrame frame - 4. Transfers the curve data - 5. Parses it into a list of integer values - - Args: - channel: Channel number (1-4) or channel string ('CH1'-'CH4') - frame_number: Optional FastFrame frame number to acquire (default: None) - - Returns: - list: List of integer ADC values from the waveform - - Raises: - ValueError: If channel or frame_number is invalid - RuntimeError: If not connected or transfer fails - """ - # Normalize channel input - if isinstance(channel, str): - channel_upper = channel.upper() - if not (channel_upper.startswith('CH') and len(channel_upper) == 3 and channel_upper[2].isdigit()): - raise ValueError(f"Invalid channel: {channel}. Must be CH1, CH2, CH3, or CH4") - source = channel_upper - elif isinstance(channel, int): - if channel < 1 or channel > 4: - raise ValueError(f"Invalid channel: {channel}. Must be 1-4") - source = f"CH{channel}" - else: - raise ValueError(f"Invalid channel type: {type(channel)}. Must be int or str") - - # Configure data transfer format - self.set_data_encoding('RIBinary') - self.set_wfmoutpre_encoding('BINary') - self.set_wfmoutpre_byte_count(1) - self.set_wfmoutpre_byte_order('MSB') - - # Set data source - self.set_data_source(source) - - # Select FastFrame frame if specified - if frame_number is not None: - self.set_fastframe_selected(frame_number) - - # Transfer curve data - curve_bytes = self.transfer_curve() - - # Parse the data - values = self.parse_curve_data(curve_bytes, byte_count=1, signed=True, byte_order='MSB') - - return values - def write(self, command): """ Send a raw SCPI command to the instrument. @@ -1625,11 +780,11 @@ class TektronixOscilloscopeBase: # Read the '#' character header = self.socket.recv(1) if header != b'#': - # Debug: read more to see what's actually there + # Read whatever else is buffered so the error shows the actual response self.socket.setblocking(False) try: more = self.socket.recv(100) - except: + except OSError: more = b'' self.socket.setblocking(True) raise RuntimeError(f"Invalid IEEE 488.2 header: expected '#', got {header}, followed by: {more[:50]}") @@ -1649,19 +804,19 @@ class TektronixOscilloscopeBase: data_length = int(length_bytes) # Read the actual binary data - data = b'' + chunks = [] remaining = data_length while remaining > 0: chunk = self.socket.recv(min(remaining, 65536)) if not chunk: raise RuntimeError("Connection closed while reading data") - data += chunk + chunks.append(chunk) remaining -= len(chunk) # Read the trailing newline self.socket.recv(1) - return data + return b''.join(chunks) @property def is_connected(self):