Phase 1b: prune 45 never-called methods from tektronix_base (~850 lines)

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 <noreply@anthropic.com>
This commit is contained in:
Thomas Ales
2026-07-28 10:04:41 -05:00
parent 1d4e65f8ac
commit 5148f0bca2
+5 -850
View File
@@ -153,30 +153,6 @@ class TektronixOscilloscopeBase:
return channel 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): def set_acquire_mode(self, mode):
""" """
Set the acquisition mode. Set the acquisition mode.
@@ -247,19 +223,6 @@ class TektronixOscilloscopeBase:
self.write(f"HORizontal:FASTframe:STATE {state}") 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): def set_fastframe_count(self, count):
""" """
Set the number of FastFrame frames. Set the number of FastFrame frames.
@@ -290,36 +253,6 @@ class TektronixOscilloscopeBase:
response = self.query("HORizontal:MODe:RECOrdlength?") response = self.query("HORizontal:MODe:RECOrdlength?")
return int(response) 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): def set_sample_rate(self, rate):
""" """
Set the horizontal sample rate. Set the horizontal sample rate.
@@ -337,93 +270,6 @@ class TektronixOscilloscopeBase:
self.write(f"HORizontal:MODe:SAMPLERate {rate}") 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): def set_trigger_slope(self, slope):
""" """
Set the trigger slope. Set the trigger slope.
@@ -456,18 +302,6 @@ class TektronixOscilloscopeBase:
self.write(f"TRIGger:A:EDGE:SLOpe {slope}") 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): def set_trigger_source(self, source):
""" """
Set the trigger source. Set the trigger source.
@@ -498,34 +332,6 @@ class TektronixOscilloscopeBase:
self.write(f"TRIGger:A:EDGE:SOUrce {source}") 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): def set_trigger_level(self, channel, level):
""" """
Set the trigger level for a specific channel. Set the trigger level for a specific channel.
@@ -555,18 +361,6 @@ class TektronixOscilloscopeBase:
self.write(f"TRIGger:A:LEVel:CH{channel} {level}") 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): def set_trigger_mode(self, mode):
""" """
Set the trigger mode. Set the trigger mode.
@@ -600,40 +394,6 @@ class TektronixOscilloscopeBase:
# ========== Channel Control Methods ========== # ========== 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): def set_channel_bandwidth(self, channel, bandwidth):
""" """
Set the bandwidth for a specific channel. Set the bandwidth for a specific channel.
@@ -649,23 +409,6 @@ class TektronixOscilloscopeBase:
channel = self._normalize_channel(channel) channel = self._normalize_channel(channel)
self.write(f"CH{channel}:BANdwidth {bandwidth}") 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): def set_channel_coupling(self, channel, coupling):
""" """
Set the coupling mode for a specific channel. Set the coupling mode for a specific channel.
@@ -697,144 +440,6 @@ class TektronixOscilloscopeBase:
self.write(f"CH{channel}:COUPling {coupling}") 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): def set_channel_label_name(self, channel, name):
""" """
Set the label name for a specific channel. Set the label name for a specific channel.
@@ -855,135 +460,6 @@ class TektronixOscilloscopeBase:
# Need to quote the string for SCPI # Need to quote the string for SCPI
self.write(f'CH{channel}:LABel:NAMe "{name}"') 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): def set_channel_position(self, channel, position):
""" """
Set the vertical position for a specific channel. Set the vertical position for a specific channel.
@@ -1003,24 +479,6 @@ class TektronixOscilloscopeBase:
self.write(f"CH{channel}:POSition {position}") 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): def set_channel_scale(self, channel, scale):
""" """
Set the vertical scale for a specific channel. Set the vertical scale for a specific channel.
@@ -1040,24 +498,6 @@ class TektronixOscilloscopeBase:
self.write(f"CH{channel}:SCAle {scale}") 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): def set_channel_termination(self, channel, termination):
""" """
Set the termination for a specific channel. Set the termination for a specific channel.
@@ -1080,66 +520,6 @@ class TektronixOscilloscopeBase:
# ========== Waveform Transfer Methods ========== # ========== 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): def set_data_source(self, source):
""" """
Set the data source for waveform transfer. Set the data source for waveform transfer.
@@ -1170,123 +550,6 @@ class TektronixOscilloscopeBase:
self.write(f"DATa:SOUrce {source}") 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): def query_wfmoutpre(self):
""" """
Query all waveform output preamble parameters. Query all waveform output preamble parameters.
@@ -1299,35 +562,6 @@ class TektronixOscilloscopeBase:
""" """
return self.query("WFMOutpre?") 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): def transfer_curve(self):
""" """
Transfer waveform curve data from the oscilloscope. Transfer waveform curve data from the oscilloscope.
@@ -1348,29 +582,6 @@ class TektronixOscilloscopeBase:
# Read the binary data using IEEE 488.2 format # Read the binary data using IEEE 488.2 format
return self.read_raw() 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'): def transfer_fastframe(self, parse=True, byte_count=1, signed=True, byte_order='MSB'):
""" """
Transfer all FastFrame waveform data from the oscilloscope. Transfer all FastFrame waveform data from the oscilloscope.
@@ -1497,62 +708,6 @@ class TektronixOscilloscopeBase:
return values 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): def write(self, command):
""" """
Send a raw SCPI command to the instrument. Send a raw SCPI command to the instrument.
@@ -1625,11 +780,11 @@ class TektronixOscilloscopeBase:
# Read the '#' character # Read the '#' character
header = self.socket.recv(1) header = self.socket.recv(1)
if header != b'#': 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) self.socket.setblocking(False)
try: try:
more = self.socket.recv(100) more = self.socket.recv(100)
except: except OSError:
more = b'' more = b''
self.socket.setblocking(True) self.socket.setblocking(True)
raise RuntimeError(f"Invalid IEEE 488.2 header: expected '#', got {header}, followed by: {more[:50]}") 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) data_length = int(length_bytes)
# Read the actual binary data # Read the actual binary data
data = b'' chunks = []
remaining = data_length remaining = data_length
while remaining > 0: while remaining > 0:
chunk = self.socket.recv(min(remaining, 65536)) chunk = self.socket.recv(min(remaining, 65536))
if not chunk: if not chunk:
raise RuntimeError("Connection closed while reading data") raise RuntimeError("Connection closed while reading data")
data += chunk chunks.append(chunk)
remaining -= len(chunk) remaining -= len(chunk)
# Read the trailing newline # Read the trailing newline
self.socket.recv(1) self.socket.recv(1)
return data return b''.join(chunks)
@property @property
def is_connected(self): def is_connected(self):