diff --git a/picosdk/device.py b/picosdk/device.py index 21c35b1..6b4ffd2 100644 --- a/picosdk/device.py +++ b/picosdk/device.py @@ -11,8 +11,21 @@ import numpy import math import time -from picosdk.errors import DeviceCannotSegmentMemoryError, InvalidTimebaseError, ClosedDeviceError, \ - NoChannelsEnabledError, NoValidTimebaseForOptionsError +from picosdk.errors import (DeviceCannotSegmentMemoryError, InvalidTimebaseError, ClosedDeviceError, + NoChannelsEnabledError, NoValidTimebaseForOptionsError, FeatureNotSupportedError, ChannelNotEnabledError, + InvalidRangeOfChannel) + + +DEFAULT_PROBE_ATTENUATION = { + 'A': 10, + 'B': 10, + 'C': 10, + 'D': 10, + 'E': 10, + 'F': 10, + 'G': 10, + 'H': 10, +} def requires_open(error_message="This operation requires a device to be connected."): @@ -31,12 +44,12 @@ def check_open_impl(self, *args, **kwargs): coupling (optional) = 'AC' or 'DC', default is 'DC'. range_peak (optional) = +/- max volts, the highest precision range which includes your value will be selected. analog_offset (optional) = the meaning of 0 for this channel.""" -ChannelConfig = collections.namedtuple('ChannelConfig', ['name', 'enabled', 'coupling', 'range_peak', 'analog_offset']) -ChannelConfig.__new__.__defaults__ = (None, None, None) +ChannelConfig = collections.namedtuple('ChannelConfig', 'name enabled coupling range_peak analog_offset', + defaults=['DC', float('inf'), None]) """TimebaseOptions: A type for specifying timebase constraints (pass to Device.find_timebase or Device.capture_*) -All are optional. Please specify the options which matter to you: +All are optional. Please specify the options which matter to you: - the maximum time interval (if you want the fastest/most precise timebase you can get), - the number of samples in one buffer, - the minimum total collection time (if you want at least x.y seconds of uninterrupted capture data) @@ -55,25 +68,110 @@ class Device(object): unwanted behaviour (e.g. throwing an exception because no channels are enabled, when you enabled them yourself on the driver object.)""" def __init__(self, driver, handle): - self.driver = driver - self.handle = handle - self.is_open = handle > 0 + self._driver = driver + self._handle = handle # if a channel is missing from here, it is disabled (or in an undefined state). + self._max_adc = None + self._buffers = {} + self._max_samples = None self._channel_ranges = {} self._channel_offsets = {} + self._enabled_sources = set() + self._time_interval_ns = None + self._probe_attenuations = DEFAULT_PROBE_ATTENUATION - @requires_open("The device either did not initialise correctly or has already been closed.") - def close(self): - self.driver.close_unit(self) - self.handle = None - self.is_open = False + @property + def driver(self): + """picosdk.library.Library: The driver object""" + return self._driver + + @property + def handle(self): + """int: The device handle""" + return self._handle + + @property + def is_open(self): + """bool: True if the device is open, False otherwise.""" + return self.handle is not None and self.handle > 0 + + @property + def max_adc(self): + """int: The maximum ADC value for this device.""" + return self._max_adc + + @property + def buffers(self): + """dict: A dictionary of buffers for each enabled channel or port.""" + return self._buffers + + @property + def max_samples(self): + """int: The number of samples for capture.""" + return self._max_samples + + @property + def channel_ranges(self): + """dict: A dictionary of channel ranges for each enabled channel.""" + return self._channel_ranges + + @property + def channel_offsets(self): + """dict: A dictionary of channel offsets for each enabled channel.""" + return self._channel_offsets + + @property + def enabled_sources(self): + """set: A set of enabled sources (channels and/or digital ports).""" + return self._enabled_sources + + @property + def timebase(self): + """int: The timebase id used for the (last) capture.""" + return self._timebase + + @timebase.setter + def timebase(self, value): + if value <= 0: + raise ValueError(f"Timebase can not be {value!r}, must be greater than 0") + self._timebase = value + + @property + def time_interval_ns(self): + """int/float: The time interval in seconds""" + return self._time_interval_ns + + @property + def probe_attenuations(self): + """dict: A dictionary of probe attenuations for each enabled channel.""" + return self._probe_attenuations + + @probe_attenuations.setter + def probe_attenuations(self, value): + self._probe_attenuations = value @property @requires_open() def info(self): + """UnitInfo: The info of the device""" return self.driver.get_unit_info(self) + @requires_open("The device either did not initialise correctly or has already been closed.") + def close(self): + self.driver.close_unit(self) + self._driver = None + self._handle = None + self._max_adc = None + self._buffers.clear() + self._max_samples = None + self._channel_ranges.clear() + self._channel_offsets.clear() + self._enabled_sources.clear() + self._timebase = None + self._time_interval_ns = None + self._probe_attenuations = DEFAULT_PROBE_ATTENUATION.copy() + def __enter__(self): return self @@ -84,34 +182,107 @@ def __exit__(self, *args): return False @requires_open() - def set_channel(self, channel_config): - name = channel_config.name - if not channel_config.enabled: + def reset(self): + """ + Closes and re-opens the connection to the PicoScope. + Attempts to re-open the same device by serial number if possible. + Resets internal cached state of this Device object. + Channel configurations and other settings will need to be reapplied. + """ + driver = self._driver + current_serial = None + resolution_to_use = None + + # Try to get serial number to re-open the same device + try: + device_info = driver.get_unit_info(self) + current_serial = device_info.serial + except Exception: + # If serial cannot be fetched, _python_open_unit will open the first available. + pass + + # Use the driver's default resolution if available for re-opening. + if hasattr(driver, 'DEFAULT_RESOLUTION'): + resolution_to_use = driver.DEFAULT_RESOLUTION + + self.close() + + # Re-open the unit + try: + new_handle = driver._python_open_unit(serial=current_serial, resolution=resolution_to_use) + self._handle = new_handle + except Exception as e: + self._handle = None + raise ConnectionError(f"Failed to re-open device during reset: {e}") + + if not self.is_open: + raise ConnectionError("Device reset failed: handle is invalid after re-open attempt.") + + @requires_open() + def set_channel(self, channel_name, enabled, coupling='DC', range_peak=float('inf'), analog_offset=0): + """Configures a single analog channel. + + Args: + channel_name (str): The channel name as a string (e.g., 'A'). + enabled (bool): True to enable the channel, False to disable. + coupling (str): 'AC' or 'DC'. Defaults to 'DC'. + range_peak (int/float): Desired +/- peak voltage. The driver selects the best range. + Required if enabling the channel. + analog_offset (int/float): The analog offset for the channel in Volts. + + Returns: + The range of the channel in Volts if enabled, None if disabled. + """ + if not enabled: self.driver.set_channel(self, - channel_name=name, - enabled=channel_config.enabled) + channel_name=channel_name, + enabled=enabled) try: - del self._channel_ranges[name] - del self._channel_offsets[name] + del self._channel_ranges[channel_name] + del self._channel_offsets[channel_name] + self._enabled_sources.remove(channel_name) except KeyError: pass return - # if enabled, we pass through the values from the channel config: - self._channel_ranges[name] = self.driver.set_channel(self, - channel_name=name, - enabled=channel_config.enabled, - coupling=channel_config.coupling, - range_peak=channel_config.range_peak, - analog_offset=channel_config.analog_offset) - self._channel_offsets[name] = channel_config.analog_offset - return self._channel_ranges[name] + + self._channel_ranges[channel_name] = self.driver.set_channel(device=self, + channel_name=channel_name, + enabled=enabled, + coupling=coupling, + range_peak=range_peak, + analog_offset=analog_offset) + self._channel_offsets[channel_name] = analog_offset + self._enabled_sources.add(channel_name) + + return self._channel_ranges[channel_name] + + @requires_open() + def set_digital_port(self, port_number, enabled, voltage_level=1.8): + """Set the digital port + + Args: + port_number (int): identifies the port for digital data. (e.g. 0 for digital channels 0-7) + enabled (bool): whether or not to enable the channel (boolean) + voltage_level (int/float): the voltage at which the state transitions between 0 and 1. Range: –5.0 to 5.0 (V). + """ + info = self.info + if not info.variant.decode('utf-8').endswith("MSO"): + raise FeatureNotSupportedError("This device has no digital ports.") + self.driver.set_digital_port(device=self, port_number=port_number, enabled=enabled, voltage_level=voltage_level) + if enabled: + self._enabled_sources.add(port_number) + else: + try: + self._enabled_sources.remove(port_number) + except KeyError: + pass @requires_open() def set_channels(self, *channel_configs): - """ set_channels(self, *channel_configs) - An alternative to calling set_channel for each one, you can call this method with some channel configs. + """An alternative to calling set_channel for each channel, you can call this method with + one ore more ChannelConfig. This method will also disable any missing channels from the passed configs, and disable ALL channels if the - collection is empty. """ + collection is empty.""" # Add channels which are missing as "disabled". if len(channel_configs) < len(self.driver.PICO_CHANNEL): channel_configs = list(channel_configs) @@ -121,7 +292,7 @@ def set_channels(self, *channel_configs): channel_configs.append(ChannelConfig(channel_name, False)) for channel_config in channel_configs: - self.set_channel(channel_config) + self.set_channel(*channel_config) def _timebase_options_are_impossible(self, options): device_max_samples_possible = self.driver.MAX_MEMORY @@ -144,14 +315,15 @@ def _timebase_options_are_impossible(self, options): @staticmethod def _validate_timebase(timebase_options, timebase_info): """validate whether a timebase result matches the options requested.""" + time_interval_s = timebase_info.time_interval_ns / 1e9 if timebase_options.max_time_interval is not None: - if timebase_info.time_interval > timebase_options.max_time_interval: + if time_interval_s > timebase_options.max_time_interval: return False if timebase_options.no_of_samples is not None: if timebase_options.no_of_samples > timebase_info.max_samples: return False if timebase_options.min_collection_time is not None: - if timebase_options.min_collection_time > timebase_info.max_samples * timebase_info.time_interval: + if timebase_options.min_collection_time > timebase_info.max_samples * time_interval_s: return False return True @@ -181,6 +353,301 @@ def find_timebase(self, timebase_options): args = (last_error.args[0],) raise NoValidTimebaseForOptionsError(*args) + @requires_open() + def get_timebase(self, timebase_id, no_of_samples, oversample=1, segment_index=0): + """Query the device about what time precision modes it can handle. + + Args: + timebase_id (int): The timebase id. + no_of_samples (int): The number of samples to collect at this timebase. + oversample (int): The amount of oversample required. Defaults to 1. + segment_index (int): The memory segment index to use. Defaults to 0. + + Returns: + namedtuple: + - timebase_id: The id corresponding to the timebase used + - time_interval_ns: The time interval between readings at the selected timebase. + - time_units: The unit of time (not supported in e.g. 3000a) + - max_samples: The maximum number of samples available. The number may vary depending on the number of + channels enabled and the timebase chosen. + - segment_id: The index of the memory segment to use + """ + timebase_info = self.driver.get_timebase(self, timebase_id, no_of_samples, oversample, segment_index) + self.timebase = timebase_info.timebase_id + self._time_interval_ns = timebase_info.time_interval_ns + return timebase_info + + @requires_open() + def memory_segments(self, number_segments): + """The number of segments defaults to 1, meaning that each capture fills the scope's available memory. + This function allows you to divide the memory into a number of segments so that the scope can store several + waveforms sequentially. + + Args: + number_segments (int): The number of segments to divide the memory into. + + Returns: + int: The number of samples available in each segment. This is the total number over all channels, + so if more than one channel is in use then the number of samples available to each + channel is max_samples divided by the number of channels. + """ + return self.driver.memory_segments(self, number_segments) + + @requires_open() + def get_max_segments(self): + """Get the maximum number of memory segments supported by the device. + + Returns: + int: The maximum number of memory segments supported by the device. + """ + return self.driver.get_max_segments(self) + + @requires_open() + def maximum_value(self): + """Get the maximum ADC value for this device.""" + self._max_adc = self.driver.maximum_value(self) + return self._max_adc + + @requires_open() + def set_null_trigger(self): + """Set a null trigger on the device. + Trigger is not enabled, so the device will not wait for a trigger + before capturing data. + """ + self.driver.set_null_trigger() + + @requires_open() + def set_simple_trigger(self, channel, enable=True, threshold_mv=500, direction="FALLING", delay=0, + auto_trigger_ms=1000): + """Set a simple trigger for a channel + + Args: + channel (str): The channel on which to trigger + enable (bool): False to disable the trigger, True to enable it + threshold_mv (int): The threshold in millivolts at which the trigger will fire. + direction (str): The direction in which the signal must move to cause a trigger. + delay (int): The time (sample periods) between the trigger occurring and the first sample. + auto_trigger_ms (int): The number of milliseconds the device will wait if no trigger occurs. + If this is set to zero, the scope device will wait indefinitely for a trigger. + """ + if channel not in self.enabled_sources: + raise ChannelNotEnabledError(f"Channel {channel} is not enabled. Please run set_channel first.") + if channel not in self.channel_ranges: + raise InvalidRangeOfChannel(f"The range of channel {channel} is not valid or isn't correctly obtained via" + "set_channel.") + max_voltage = self.channel_ranges[channel] + max_adc = self.max_adc if self.max_adc else self.maximum_value() + + self.driver.set_simple_trigger(self, max_voltage, max_adc, enable, channel, threshold_mv, direction, delay, + auto_trigger_ms) + + @requires_open() + def set_trigger_conditions_v2(self, trigger_input): + """Sets up trigger conditions on the scope's inputs. + Sets trigger state to TRUE for given `trigger_input`, the rest will be DONT CARE + + Args: + trigger (str): What to trigger (e.g. channelA, channelB, external, aux, pulseWidthQualifier, digital) + """ + return self.driver.set_trigger_conditions_v2(self, trigger_input) + + @requires_open() + def run_block(self, pre_trigger_samples, post_trigger_samples, timebase_id, oversample=1, segment_index=0): + """This function starts collecting data in block mode. + + Args: + pre_trigger_samples (int): The number of samples to collect before the trigger event. + post_trigger_samples (int): The number of samples to collect after the trigger event. + timebase_id (int): The timebase id to use for the capture. + oversample (int): The amount of oversample required. Defaults to 1. + segment_index (int): The memory segment index to use. Defaults to 0. + + Returns: + float: The approximate time (in seconds) which the device will take to capture with these settings + """ + self._max_samples = pre_trigger_samples + post_trigger_samples + self.timebase = timebase_id + return self.driver.run_block(self, pre_trigger_samples, post_trigger_samples, self.timebase, oversample, + segment_index) + + @requires_open() + def is_ready(self): + """Poll this function to find out when block mode is ready or has triggered. + returns: True if data is ready, False otherwise.""" + return self.driver.is_ready(self) + + @requires_open() + def stop_block_capture(self, timeout_minutes=5): + """Poll the driver to see if it has finished collecting the requested samples and then stops the capture. + + Args: + timeout_minutes (int/float): The timeout in minutes. If the time exceeds the timeout, the poll stops. + """ + self.driver.stop_block_capture(self, timeout_minutes) + + @requires_open() + def set_data_buffer(self, channel_or_port, segment_index=0, mode='NONE'): + """Set the data buffer for a specific channel. + + Args: + channel_or_port (str/int): Channel (e.g. 'A', 'B') or digital port (e.g. 0, 1) to set data for + segment_index (int): The number of the memory segment to be used (default is 0) + mode (str): The ratio mode to be used (default is 'NONE') + """ + self._buffers[channel_or_port] = self.driver.set_data_buffer(self, channel_or_port, self.max_samples, + segment_index, mode) + + @requires_open() + def set_all_data_buffers(self, segment_index=0, mode='NONE'): + """Set the data buffer for each enabled channels and ports + + Args: + segment_index (int): The number of the memory segment to be used (default is 0) + mode (str): The ratio mode to be used (default is 'NONE') + """ + for channel_or_port in self.enabled_sources: + self.set_data_buffer(channel_or_port, segment_index, mode) + + @requires_open() + def get_values(self, start_index=0, downsample_ratio=0, downsample_ratio_mode="NONE", segment_index=0, + output_dir=".", filename="data", save_to_file=False): + """Get stored data values from the scope and store it in a clean SingletonScopeDataDict object. + + This function is used after data collection has stopped. It gets the stored data from the scope, with or + without downsampling, starting at the specified sample number. + + The returned captured data is converted to mV. + + Note: don't forget to change the probe attenuations of the used channels if they differ from 10 (default) + + Args: + start_index (int): A zero-based index that indicates the start point for data collection. It is measured in + sample intervals from the start of the buffer. + downsample_ratio (int): The downsampling factor that will be applied to the raw data. + downsample_ratio_mode (str): Which downsampling mode to use. + segment_index (int): Memory segment index + output_dir (str): The output directory where the json file will be saved. + filename (str): The name of the json file where the data will be stored + save_to_file (bool): True if the data has to be saved to a file on the disk, False otherwise + + Returns: + scope_data (SingletonScopeDataDict): The captured scope data. + overflow_warning (dict): A dictionary indicating which channels had an overflow. + """ + if not self.time_interval_ns: + if self.timebase: + self.get_timebase(self.timebase, self.max_samples) + else: + raise InvalidTimebaseError("Timebase is not set. Please call get_timebase before get_values.") + time_interval_sec = self.time_interval_ns / 1e9 if self.time_interval_ns else None + return self.driver.get_values(self, self.buffers, self.max_samples, time_interval_sec, self.channel_ranges, + start_index, downsample_ratio, downsample_ratio_mode, segment_index, output_dir, + filename, save_to_file, self.probe_attenuations) + + @requires_open() + def store_values(self, start_index=0, downsample_ratio=0, downsample_ratio_mode="NONE", segment_index=0, + output_dir=".", filename="data", save_to_file=False): + """Same as `get_values` but only returns overflow warnings and keeps the data stored in SingletonScopeDataDict. + + Args: + start_index (int): A zero-based index that indicates the start point for data collection. It is measured in + sample intervals from the start of the buffer. + downsample_ratio (int): The downsampling factor that will be applied to the raw data. + downsample_ratio_mode (str): Which downsampling mode to use. + segment_index (int): Memory segment index + output_dir (str): The output directory where the json file will be saved. + filename (str): The name of the json file where the data will be stored + save_to_file (bool): True if the data has to be saved to a file on the disk, False otherwise + + Returns: + overflow_warning (dict): A dictionary indicating which channels had an overflow. + """ + if not self.time_interval_ns: + if self.timebase: + self.get_timebase(self.timebase, self.max_samples) + else: + raise InvalidTimebaseError("Timebase is not set. Please call get_timebase before get_values.") + time_interval_sec = self.time_interval_ns / 1e9 if self.time_interval_ns else None + return self.driver.store_values(self, self.buffers, self.max_samples, time_interval_sec, self.channel_ranges, + start_index, downsample_ratio, downsample_ratio_mode, segment_index, output_dir, + filename, save_to_file, self.probe_attenuations) + + @requires_open() + def set_and_load_data(self, segment_index=0, ratio_mode='NONE', start_index=0, downsample_ratio=0, + downsample_ratio_mode="NONE", output_dir=".", filename="data", save_to_file=False): + """Load values from the device. + + Combines set_data_buffer and get_values to load values from the device. + + Args: + segment_index (int): Memory segment index + ratio_mode: The ratio mode to be used (default is 'NONE') + start_index (int): A zero-based index that indicates the start point for data collection. It is measured in + sample intervals from the start of the buffer. + downsample_ratio (int): The downsampling factor that will be applied to the raw data. + downsample_ratio_mode (str): Which downsampling mode to use. + output_dir (str): The output directory where the json file will be saved. + filename (str): The name of the json file where the data will be stored + save_to_file (bool): True if the data has to be saved to a file on the disk, False otherwise + + Returns: + overflow warnings (dict): A dictionary indicating which channels had an overflow. + """ + self.set_all_data_buffers(segment_index, ratio_mode) + return self.get_values(start_index, downsample_ratio, downsample_ratio_mode, segment_index, output_dir, + filename, save_to_file) + + @requires_open() + def set_trigger_channel_properties(self, threshold_upper, threshold_upper_hysteresis, threshold_lower, + threshold_lower_hysteresis, channel, threshold_mode, aux_output_enable, + auto_trigger_milliseconds): + """Set the trigger channel properties for the device. + + Args: + threshold_upper (int): Upper threshold in ADC counts + threshold_upper_hysteresis (int): Hysteresis for upper threshold in ADC counts + threshold_lower (int): Lower threshold in ADC counts + threshold_lower_hysteresis (int): Hysteresis for lower threshold in ADC counts + channel (str): Channel to set properties for (e.g. 'A', 'B', 'C', 'D') + threshold_mode (str): Threshold mode (e.g. "LEVEL", "WINDOW") + aux_output_enable (bool): Enable auxiliary output (boolean) (Not used in eg. ps2000a, ps3000a, ps4000a) + auto_trigger_milliseconds (int): The number of milliseconds for which the scope device will wait for a + trigger before timing out. If set to zero, the scope device will wait indefinitely for a trigger + """ + self.driver.set_trigger_channel_properties(self, threshold_upper, threshold_upper_hysteresis, threshold_lower, + threshold_lower_hysteresis, channel, threshold_mode, + aux_output_enable, auto_trigger_milliseconds) + + @requires_open() + def set_digital_channel_trigger(self, channel_number=15, direction="DIRECTION_RISING"): + """Set a simple trigger on the digital channels. + + Args: + channel_number (int): The number of the digital channel on which to trigger.(e.g. 0 for D0, 1 for D1,...) + direction (str): The direction in which the signal must move to cause a trigger. + """ + self.driver.set_digital_channel_trigger(self, channel_number, direction) + + @requires_open() + def set_trigger_delay(self, delay): + """This function sets the post-trigger delay, which causes capture to start a defined time after the + trigger event. + + For example, if delay=100 then the scope would wait 100 sample periods before sampling. + At a timebase of 500 MS/s, or 2 ns per sample, the total delay would then be 100 x 2 ns = 200 ns. + + Args: + delay (int): The time between the trigger occurring and the first sample. + """ + self.driver.set_trigger_delay(self, delay) + + @requires_open() + def stop(self): + """This function stops the scope device from sampling data. + If this function is called before a trigger event occurs, the oscilloscope may not contain valid data. + """ + self.driver.stop(self) + @requires_open() def capture_block(self, timebase_options, channel_configs=()): """device.capture_block(timebase_options, channel_configs) @@ -208,12 +675,13 @@ def capture_block(self, timebase_options, channel_configs=()): # get_timebase timebase_info = self.find_timebase(timebase_options) + time_interval_s = timebase_info.time_interval_ns / 1e9 post_trigger_samples = timebase_options.no_of_samples pre_trigger_samples = 0 if post_trigger_samples is None: - post_trigger_samples = int(math.ceil(timebase_options.min_collection_time / timebase_info.time_interval)) + post_trigger_samples = int(math.ceil(timebase_options.min_collection_time / time_interval_s)) self.driver.set_null_trigger(self) @@ -238,13 +706,13 @@ def capture_block(self, timebase_options, channel_configs=()): self.driver.stop(self) times = numpy.linspace(0., - post_trigger_samples * timebase_info.time_interval, + post_trigger_samples * time_interval_s, post_trigger_samples, dtype=numpy.dtype('float32')) voltages = {} - max_adc = self.driver.maximum_value(self) + max_adc = self.max_adc if self.max_adc else self.maximum_value() for channel, raw_array in raw_data.items(): array = raw_array.astype(numpy.dtype('float32'), casting='safe') factor = self._channel_ranges[channel] / max_adc @@ -252,3 +720,30 @@ def capture_block(self, timebase_options, channel_configs=()): voltages[channel] = array return times, voltages, overflow_warnings + + @requires_open() + def set_sig_gen_built_in(self, offset_voltage=0, pk_to_pk=2000000, wave_type="SINE", + start_frequency=10000, stop_frequency=10000, increment=0, + dwell_time=1, sweep_type="UP", operation='ES_OFF', shots=0, sweeps=0, + trigger_type="RISING", trigger_source="NONE", ext_in_threshold=1): + """Set up the signal generator to output a built-in waveform. + + Args: + offset_voltage (int/float): Offset voltage in microvolts (default 0) + pk_to_pk (int): Peak-to-peak voltage in microvolts (default 2000000) + wave_type (str): Type of waveform (e.g. "SINE", "SQUARE", "TRIANGLE") + start_frequency (int): Start frequency in Hz (default 1000.0) + stop_frequency (int): Stop frequency in Hz (default 1000.0) + increment (int): Frequency increment in Hz (default 0.0) + dwell_time (int/float): Time at each frequency in seconds (default 1.0) + sweep_type (str): Sweep type (e.g. "UP", "DOWN", "UPDOWN") + operation (str): Configures the white noise/PRBS (e.g. "ES_OFF", "WHITENOISE", "PRBS") + shots (int): Number of shots per trigger (default 1) + sweeps (int): Number of sweeps (default 1) + trigger_type (str): Type of trigger (e.g. "RISING", "FALLING") + trigger_source (str): Source of trigger (e.g. "NONE", "SCOPE_TRIG") + ext_in_threshold (int): External trigger threshold in ADC counts + """ + self.driver.set_sig_gen_built_in(self, offset_voltage, pk_to_pk, wave_type, start_frequency, stop_frequency, + increment, dwell_time, sweep_type, operation, shots, sweeps, trigger_type, + trigger_source, ext_in_threshold) diff --git a/picosdk/errors.py b/picosdk/errors.py index ad03572..898879b 100644 --- a/picosdk/errors.py +++ b/picosdk/errors.py @@ -60,10 +60,14 @@ class PicoSDKCtypesError(PicoError, IOError): class ClosedDeviceError(PicoError, IOError): pass +class InvalidRangeOfChannel(PicoError, ValueError): + pass class NoChannelsEnabledError(PicoError, ValueError): pass +class ChannelNotEnabledError(PicoError, ValueError): + pass class NoValidTimebaseForOptionsError(PicoError, ValueError): pass diff --git a/picosdk/library.py b/picosdk/library.py index 289b0c4..6e590a8 100644 --- a/picosdk/library.py +++ b/picosdk/library.py @@ -6,17 +6,20 @@ Note: Many of the functions in this class are missing: these are populated by the psN000(a).py modules, which subclass this type and attach the missing methods. """ - -from __future__ import print_function - +import json +import re import sys -from ctypes import c_int16, c_int32, c_uint32, c_float, create_string_buffer, byref +from ctypes import c_int16, c_int32, c_uint32, c_float, c_void_p, create_string_buffer, byref from ctypes.util import find_library import collections +import time +import gc import picosdk.constants as constants +from base64 import b64encode import numpy +from numpy.lib.format import dtype_to_descr -from picosdk.errors import CannotFindPicoSDKError, CannotOpenPicoSDKError, DeviceNotFoundError, \ +from picosdk.errors import PicoError, CannotFindPicoSDKError, CannotOpenPicoSDKError, DeviceNotFoundError, \ ArgumentOutOfRangeError, ValidRangeEnumValueNotValidForThisDevice, DeviceCannotSegmentMemoryError, \ InvalidMemorySegmentsError, InvalidTimebaseError, InvalidTriggerParameters, InvalidCaptureParameters @@ -24,10 +27,21 @@ from picosdk.device import Device +DEFAULT_PROBE_ATTENUATION = { + 'A': 10, + 'B': 10, + 'C': 10, + 'D': 10, + 'E': 10, + 'F': 10, + 'G': 10, + 'H': 10, +} + """TimebaseInfo: A type for holding the particulars of a timebase configuration. """ TimebaseInfo = collections.namedtuple('TimebaseInfo', ['timebase_id', - 'time_interval', + 'time_interval_ns', 'time_units', 'max_samples', 'segment_id']) @@ -43,6 +57,170 @@ def check_device_impl(self, device, *args, **kwargs): return check_device_decorator +def voltage_to_logic_level(voltage): + """Convert a voltage value into logic level for digital channels. + + Range: –32767 (–5 V) to 32767 (5 V). + + Args: + voltage (float): Voltage in volts. + + Returns: + int: The calculated logic level count. + """ + clamped_voltage = min(max(-5, voltage), 5) + logic_level = int((clamped_voltage) * (32767 / 5)) + return logic_level + + +def split_mso_data_fast(data_length, data): + """Split port data into individual digital channels. + + The tuple contains the channels in order (D0, D1, D2, ... D7) or equivalently (D8, D9, D10, ... D15). + + Args: + data_length (c_int32): The length of the data array. + data (c_int16 array): The data array containing the digital port values. + + Returns: + tuple: A tuple of 8 numpy arrays, each containing the digital channel values over time + """ + num_samples = data_length.value + # Makes an array for each digital channel + buffer_binary_dj = tuple(numpy.empty(num_samples, dtype=numpy.uint8) for _ in range(8)) + # Splits out the individual bits from the port into the binary values for each digital channel/pin. + for i in range(num_samples): + val = data[i] + for j in range(8): + # map bit j direct to buffer j + # bit 0 -> D0 (or D8), bit 1 -> D1 (or D9), ..., bit 7 -> D7 (or D15) + buffer_binary_dj[j][i] = (val >> j) & 1 + + return buffer_binary_dj + + +def adc_to_mv(buffer_adc, channel_range, max_adc): + """Convert a buffer of raw adc count values into millivolts. + + Args: + buffer_adc (c_short_Array): The buffer of ADC count values. + channel_range (int): The channel range in V. + max_adc (int): The maximum ADC count. + + Returns: + list: The buffer in millivolts. + """ + buffer_mv = [(x * channel_range * 1000) / max_adc for x in buffer_adc] + return buffer_mv + + +def mv_to_adc(millivolts, channel_range, max_adc): + """Convert a voltage value into an ADC count. + + Args: + millivolts (float): Voltage in millivolts. + channel_range (int): The channel range in V. + max_adc (c_int16): The maximum ADC count. + + Returns: + int: The ADC count. + """ + adc_value = round((millivolts * max_adc) / (channel_range * 1000)) + return adc_value + + +class NumpyEncoder(json.JSONEncoder): + """Module specific json encoder class.""" + def default(self, o): + """Default json encoder override. + + Code inspired from https://github.com/Crimson-Crow/json-numpy/blob/main/json_numpy.py + """ + if isinstance(o, (numpy.ndarray, numpy.generic)): + return { + "__numpy__": b64encode(o.data if o.flags.c_contiguous else o.tobytes()).decode(), + "dtype": dtype_to_descr(o.dtype), + "shape": o.shape, + } + return super().default(o) + + +class SingletonScopeDataDict(dict): + """SingletonScopeDataDict is a singleton dictionary object for sharing picoscope data between multiple classes. + + It handles both analog and digital data with uniform access patterns: + - Analog channels are accessed by their letter (e.g. 'A', 'B', 'C', 'D') + - Digital channels are accessed as 'D0'-'D15' (MSB channel D15 to LSB channel D0) + - Digital ports are accessed by their number (e.g. 0, 1) + """ + _instance = None + + def __new__(cls, *args, **kwargs): + """Create new singleton dictionary instance or return existing one.""" + if cls._instance is None: + cls._instance = super(SingletonScopeDataDict, cls).__new__(cls, *args, **kwargs) + return cls._instance + + def clean_dict(self): + """Remove all data from the singleton dictionary and run garbage collection.""" + self.clear() + gc.collect() + + def __getitem__(self, key: str): + """Get data with uniform access pattern for analog and digital channels. + + Args: + key: Channel identifier: + - 'A', 'B', 'C', 'D' for analog channels + - 'D0'-'D15' for digital channels + - 0-3 for digital ports + + Returns: + numpy array containing the channel data + + Raises: + KeyError: If channel doesn't exist + ValueError: If digital channel number is invalid + """ + if isinstance(key, str): + match = re.match(r"D(?P\d+)", key, re.IGNORECASE) + if match: + try: + digital_number = int(match.group('channel_num')) + + # Calculate which port and which row (bit) in the port's data array + port_number = digital_number // 8 # Port 0 = D0-D7, Port 1 = D8-D15 + + # D0: 0 % 8 = index 0; D8: 8 % 8 = index 0 + row_index = digital_number % 8 + + # Get the data for the entire port + port_data = super().__getitem__(port_number) + + # Select the correct row from the numpy array. + return port_data[row_index] + + except (IndexError, ValueError, KeyError) as e: + if isinstance(e, KeyError): + available_keys = ", ".join(map(str, sorted(self.keys()))) if self.keys() else "None" + raise ValueError( + f"Could not retrieve data for digital channel {key}. The data for the corresponding " + f"digital port ({port_number}) has not been loaded into the data dictionary. " + f"Available data keys are: [{available_keys}]." + ) from e + elif isinstance(e, IndexError): + raise ValueError( + f"Could not retrieve data for digital channel {key}. The data for the corresponding " + f"digital port ({port_number}) appears to be corrupted or in an unexpected format." + ) from e + else: + raise ValueError(f"An unexpected error occurred while processing digital channel '{key}': {e}" + ) from e + + # Handle direct port access (0-1) or analog channels + return super().__getitem__(key) + + class Library(object): def __init__(self, name): self.name = name @@ -103,13 +281,15 @@ def make_symbol(self, python_name, c_name, return_type, argument_types, docstrin # AND if the function is camel case, add an "underscore-ized" version: if python_name.lower() != python_name: acc = [] - for c in python_name[1:]: + python_name = python_name.lstrip('_') + for c in python_name: # Be careful to exclude both digits (lower index) and lower case (higher index). if ord('A') <= ord(c) <= ord('Z'): c = "_" + c.lower() acc.append(c) - if acc[:2] == ['_', '_']: - acc = acc[1:] + new_python_name = "".join(acc) + if not new_python_name.startswith('_'): + new_python_name = "_" + new_python_name setattr(self, "".join(acc), c_function) def list_units(self): @@ -272,14 +452,20 @@ def _python_get_unit_info_wrapper(self, handle, keys): @requires_device("set_channel requires a picosdk.device.Device instance, passed to the correct owning driver.") def set_channel(self, device, channel_name='A', enabled=True, coupling='DC', range_peak=float('inf'), analog_offset=None): - """optional arguments: - channel_name: a single channel (e.g. 'A') - enabled: whether to enable the channel (boolean) - coupling: string of the relevant enum member for your driver less the driver name prefix. e.g. 'DC' or 'AC'. - range_peak: float which is the largest value you expect in the input signal. We will throw an exception if no - range on the device is large enough for that value. - analog_offset: the meaning of 0 for this channel. - return value: Max voltage of new range. Raises an exception in error cases.""" + """Configures a single analog channel. + + Args: + device (picosdk.device.Device): The device instance + channel_name (str): The channel name as a string (e.g., 'A'). + enabled (bool): True to enable the channel, False to disable. + coupling (str): 'AC' or 'DC'. Defaults to 'DC'. + range_peak (int/float): Desired +/- peak voltage. The driver selects the best range. + Required if enabling the channel. + analog_offset (int/float): The analog offset for the channel in Volts. + + Returns: + The range of the channel in Volts if enabled, None if disabled. + """ excluded = () reliably_resolved = False @@ -307,6 +493,34 @@ def set_channel(self, device, channel_name='A', enabled=True, coupling='DC', ran return max_voltage + @requires_device("set_digital_port requires a picosdk.device.Device instance, passed to the correct owning driver.") + def set_digital_port(self, device, port_number=0, enabled=True, voltage_level=1.8): + """Set the digital port + + Args: + device (picosdk.device.Device): The device instance + port_number (int): identifies the port for digital data. (e.g. 0 for digital channels 0-7) + enabled (bool): whether or not to enable the channel (boolean) + voltage_level (float): the voltage at which the state transitions between 0 and 1. Range: –5.0 to 5.0 (V). + Raises: + NotImplementedError: This device doesn't support digital ports. + PicoError: set_digital_port failed + """ + if hasattr(self, '_set_digital_port') and len(self._set_digital_port.argtypes) == 4: + logic_level = voltage_to_logic_level(voltage_level) + digital_ports = getattr(self, self.name.upper() + '_DIGITAL_PORT', None) + if not digital_ports: + raise NotImplementedError("This device doesn't support digital ports") + port_id = digital_ports[self.name.upper() + "_DIGITAL_PORT" + str(port_number)] + args = (device.handle, port_id, 1 if enabled else 0, logic_level) + converted_args = self._convert_args(self._set_digital_port, args) + status = self._set_digital_port(*converted_args) + if status != self.PICO_STATUS['PICO_OK']: + raise PicoError( + f"set_digital_port failed ({constants.pico_tag(status)})") + else: + raise NotImplementedError("This device doesn't support digital ports or is not implemented yet") + def _resolve_range(self, signal_peak, exclude=()): # we use >= so that someone can specify the range they want precisely. # we allow exclude so that if the smallest range in the header file isn't available on this device (or in this @@ -324,69 +538,115 @@ def _python_set_channel(self, handle, channel_id, enabled, coupling_id, range_id if len(self._set_channel.argtypes) == 5 and self._set_channel.argtypes[1] == c_int16: if analog_offset is not None: raise ArgumentOutOfRangeError("This device doesn't support analog offset") - return_code = self._set_channel(c_int16(handle), - c_int16(channel_id), - c_int16(enabled), - c_int16(coupling_id), - c_int16(range_id)) + + args = (handle, channel_id, enabled, coupling_id, range_id) + converted_args = self._convert_args(self._set_channel, args) + return_code = self._set_channel(*converted_args) + if return_code == 0: - raise ValidRangeEnumValueNotValidForThisDevice("%sV is out of range for this device." % ( - self.PICO_VOLTAGE_RANGE[range_id])) + raise ValidRangeEnumValueNotValidForThisDevice( + f"{self.PICO_VOLTAGE_RANGE[range_id]}V is out of range for this device.") elif len(self._set_channel.argtypes) == 5 and self._set_channel.argtypes[1] == c_int32 or ( len(self._set_channel.argtypes) == 6): status = self.PICO_STATUS['PICO_OK'] if len(self._set_channel.argtypes) == 6: if analog_offset is None: analog_offset = 0.0 - status = self._set_channel(c_int16(handle), - c_int32(channel_id), - c_int16(enabled), - c_int32(coupling_id), - c_int32(range_id), - c_float(analog_offset)) + args = (handle, channel_id, enabled, coupling_id, range_id, analog_offset) + converted_args = self._convert_args(self._set_channel, args) + status = self._set_channel(*converted_args) + elif len(self._set_channel.argtypes) == 5 and self._set_channel.argtypes[1] == c_int32: if analog_offset is not None: raise ArgumentOutOfRangeError("This device doesn't support analog offset") - status = self._set_channel(c_int16(handle), - c_int32(channel_id), - c_int16(enabled), - c_int16(coupling_id), - c_int32(range_id)) + args = (handle, channel_id, enabled, coupling_id, range_id) + converted_args = self._convert_args(self._set_channel, args) + status = self._set_channel(*converted_args) + if status != self.PICO_STATUS['PICO_OK']: if status == self.PICO_STATUS['PICO_INVALID_VOLTAGE_RANGE']: - raise ValidRangeEnumValueNotValidForThisDevice("%sV is out of range for this device." % ( - self.PICO_VOLTAGE_RANGE[range_id])) + raise ValidRangeEnumValueNotValidForThisDevice( + f"{self.PICO_VOLTAGE_RANGE[range_id]}V is out of range for this device.") if status == self.PICO_STATUS['PICO_INVALID_CHANNEL'] and not enabled: # don't throw errors if the user tried to disable a missing channel. return - raise ArgumentOutOfRangeError("problem configuring channel (%s)" % constants.pico_tag(status)) - + raise ArgumentOutOfRangeError(f"problem configuring channel ({constants.pico_tag(status)})") else: raise NotImplementedError("not done other driver types yet") @requires_device("memory_segments requires a picosdk.device.Device instance, passed to the correct owning driver.") def memory_segments(self, device, number_segments): + """The number of segments defaults to 1, meaning that each capture fills the scope's available memory. + This function allows you to divide the memory into a number of segments so that the scope can store several + waveforms sequentially. + + Args: + device (picosdk.device.Device): The device instance + number_segments (int): The number of segments to divide the memory into. + + Returns: + int: The number of samples available in each segment. This is the total number over all channels, + so if more than one channel is in use then the number of samples available to each + channel is max_samples divided by the number of channels. + """ if not hasattr(self, '_memory_segments'): raise DeviceCannotSegmentMemoryError() max_samples = c_int32(0) - status = self._memory_segments(c_int16(device.handle), c_uint32(number_segments), byref(max_samples)) + args = (device.handle, number_segments, max_samples) + converted_args = self._convert_args(self._memory_segments, args) + status = self._memory_segments(*converted_args) if status != self.PICO_STATUS['PICO_OK']: raise InvalidMemorySegmentsError("could not segment the device memory into (%s) segments (%s)" % ( number_segments, constants.pico_tag(status))) - return max_samples + return max_samples.value + + @requires_device() + def get_max_segments(self, device): + """Get the maximum number of memory segments supported by the device. + + Returns: + int: The maximum number of memory segments supported by the device. + """ + if not hasattr(self, '_get_max_segments'): + raise NotImplementedError("This device doesn't support getting maximum segments") + + max_segments = c_int32(0) + args = (device.handle, max_segments) + converted_args = self._convert_args(self._get_max_segments, args) + status = self._get_max_segments(*converted_args) + + if status != self.PICO_STATUS['PICO_OK']: + raise PicoError(f"get_max_segments failed ({constants.pico_tag(status)})") + + return max_segments.value @requires_device("get_timebase requires a picosdk.device.Device instance, passed to the correct owning driver.") def get_timebase(self, device, timebase_id, no_of_samples, oversample=1, segment_index=0): - """query the device about what time precision modes it can handle. - note: the driver returns the timebase in nanoseconds, this function converts that into SI units (seconds)""" + """Query the device about what time precision modes it can handle. + + Args: + device (picosdk.device.Device): The device instance + timebase_id (int): The timebase id. + no_of_samples (int): The number of samples to collect at this timebase. + oversample (int): The amount of oversample required. Defaults to 1. + segment_index (int): The memory segment index to use. Defaults to 0. + + Returns: + namedtuple: + - timebase_id: The id corresponding to the timebase used + - time_interval_ns: The time interval between readings at the selected timebase. + - time_units: The unit of time (not supported in e.g. 3000a) + - max_samples: The maximum number of samples available. The number may vary depending on the number of + channels enabled and the timebase chosen. + - segment_id: The index of the memory segment to use + """ nanoseconds_result = self._python_get_timebase(device.handle, timebase_id, no_of_samples, oversample, segment_index) - return TimebaseInfo(nanoseconds_result.timebase_id, - nanoseconds_result.time_interval * 1.e-9, + nanoseconds_result.time_interval_ns, nanoseconds_result.time_units, nanoseconds_result.max_samples, nanoseconds_result.segment_id) @@ -395,69 +655,183 @@ def _python_get_timebase(self, handle, timebase_id, no_of_samples, oversample, s # We use get_timebase on ps2000 and ps3000 and parse the nanoseconds-int into a float. # on other drivers, we use get_timebase2, which gives us a float in the first place. if len(self._get_timebase.argtypes) == 7 and self._get_timebase.argtypes[1] == c_int16: - time_interval = c_int32(0) + time_interval_ns = c_int32(0) time_units = c_int16(0) max_samples = c_int32(0) - return_code = self._get_timebase(c_int16(handle), - c_int16(timebase_id), - c_int32(no_of_samples), - byref(time_interval), - byref(time_units), - c_int16(oversample), - byref(max_samples)) + + args = (handle, timebase_id, no_of_samples, time_interval_ns, + time_units, oversample, max_samples) + converted_args = self._convert_args(self._get_timebase, args) + return_code = self._get_timebase(*converted_args) + if return_code == 0: raise InvalidTimebaseError() - return TimebaseInfo(timebase_id, float(time_interval.value), time_units.value, max_samples.value, None) - elif hasattr(self, '_get_timebase2') and ( - len(self._get_timebase2.argtypes) == 7 and self._get_timebase2.argtypes[1] == c_uint32): - time_interval = c_float(0.0) + return TimebaseInfo(timebase_id, float(time_interval_ns.value), time_units.value, max_samples.value, None) + elif hasattr(self, '_get_timebase2') and self._get_timebase2.argtypes[1] == c_uint32: + time_interval_ns = c_float(0.0) max_samples = c_int32(0) - status = self._get_timebase2(c_int16(handle), - c_uint32(timebase_id), - c_int32(no_of_samples), - byref(time_interval), - c_int16(oversample), - byref(max_samples), - c_uint32(segment_index)) + if len(self._get_timebase2.argtypes) == 7: + args = (handle, timebase_id, no_of_samples, time_interval_ns, + oversample, max_samples, segment_index) + converted_args = self._convert_args(self._get_timebase2, args) + elif len(self._get_timebase2.argtypes) == 6: + args = (handle, timebase_id, no_of_samples, time_interval_ns, max_samples, segment_index) + converted_args = self._convert_args(self._get_timebase2, args) + else: + raise NotImplementedError("_get_timebase2 is not implemented for this driver yet") + status = self._get_timebase2(*converted_args) + if status != self.PICO_STATUS['PICO_OK']: - raise InvalidTimebaseError("get_timebase2 failed (%s)" % constants.pico_tag(status)) + raise InvalidTimebaseError(f"get_timebase2 failed ({constants.pico_tag(status)})") - return TimebaseInfo(timebase_id, time_interval.value, None, max_samples.value, segment_index) + return TimebaseInfo(timebase_id, time_interval_ns.value, None, max_samples.value, segment_index) else: - raise NotImplementedError("not done other driver types yet") + raise NotImplementedError("_get_timebase2 or _get_timebase is not implemented for this driver yet") @requires_device() - def set_null_trigger(self, device): + def set_null_trigger(self, device, channel="A"): + """Set a null trigger on the device. + Trigger is not enabled, so the device will not wait for a trigger + before capturing data. + """ auto_trigger_after_millis = 1 if hasattr(self, '_set_trigger') and len(self._set_trigger.argtypes) == 6: PS2000_NONE = 5 - return_code = self._set_trigger(c_int16(device.handle), - c_int16(PS2000_NONE), - c_int16(0), - c_int16(0), - c_int16(0), - c_int16(auto_trigger_after_millis)) + args = (device.handle, PS2000_NONE, 0, 0, 0, auto_trigger_after_millis) + converted_args = self._convert_args(self._set_trigger, args) + return_code = self._set_trigger(*converted_args) + if return_code == 0: raise InvalidTriggerParameters() elif hasattr(self, '_set_simple_trigger') and len(self._set_simple_trigger.argtypes) == 7: - enabled = False - status = self._set_simple_trigger(c_int16(device.handle), - c_int16(int(enabled)), - c_int32(self.PICO_CHANNEL['A']), - c_int16(0), - c_int32(self.PICO_THRESHOLD_DIRECTION['NONE']), - c_uint32(0), - c_int16(auto_trigger_after_millis)) + threshold_direction_id = None + if self.PICO_THRESHOLD_DIRECTION: + threshold_direction_id = self.PICO_THRESHOLD_DIRECTION['NONE'] + else: + threshold_directions = getattr(self, self.name.upper() + '_THRESHOLD_DIRECTION', None) + if threshold_directions: + threshold_direction_id = threshold_directions[self.name.upper() + '_NONE'] + else: + raise NotImplementedError("This device doesn't support threshold direction") + args = (device.handle, False, self.PICO_CHANNEL[channel], 0, + threshold_direction_id, 0, auto_trigger_after_millis) + converted_args = self._convert_args(self._set_simple_trigger, args) + status = self._set_simple_trigger(*converted_args) + if status != self.PICO_STATUS['PICO_OK']: - raise InvalidTriggerParameters("set_simple_trigger failed (%s)" % constants.pico_tag(status)) + raise InvalidTriggerParameters(f"set_simple_trigger failed ({constants.pico_tag(status)})") else: - raise NotImplementedError("not done other driver types yet") + raise NotImplementedError("This device doesn't support set_null_trigger (yet)") + + @requires_device() + def set_simple_trigger(self, device, max_voltage, max_adc=None, enable=True, channel="A", threshold_mv=500, + direction="FALLING", delay=0, auto_trigger_ms=1000): + """Set a simple trigger for a channel + + Args: + device (picosdk.device.Device): The device instance + max_voltage (int/float): The maximum voltage of the range used by the channel. (obtained from `set_channel`) + max_adc (int): Maximum ADC value for the device (if None, obtained via `maximum_value`) + enable (bool): False to disable the trigger, True to enable it + channel (str): The channel on which to trigger + threshold_mv (int): The threshold in millivolts at which the trigger will fire. + direction (str): The direction in which the signal must move to cause a trigger. + delay (int): The time (sample periods) between the trigger occurring and the first sample. + auto_trigger_ms (int): The number of milliseconds the device will wait if no trigger occurs. + If this is set to zero, the scope device will wait indefinitely for a trigger. + """ + if hasattr(self, '_set_simple_trigger') and len(self._set_simple_trigger.argtypes) == 7: + if not max_adc: + max_adc = self.maximum_value(device) + adc_threshold = mv_to_adc(threshold_mv, max_voltage, max_adc) + threshold_direction_id = None + if self.PICO_THRESHOLD_DIRECTION: + threshold_direction_id = self.PICO_THRESHOLD_DIRECTION[direction] + else: + threshold_directions = getattr(self, self.name.upper() + '_THRESHOLD_DIRECTION', None) + if threshold_directions: + threshold_direction_id = threshold_directions[self.name.upper() + f'_{direction.upper()}'] + else: + raise NotImplementedError("This device doesn't support threshold direction") + args = (device.handle, 1 if enable else 0, self.PICO_CHANNEL[channel], adc_threshold, + threshold_direction_id, delay, auto_trigger_ms) + converted_args = self._convert_args(self._set_simple_trigger, args) + status = self._set_simple_trigger(*converted_args) + + if status != self.PICO_STATUS['PICO_OK']: + raise InvalidTriggerParameters(f"set_simple_trigger failed ({constants.pico_tag(status)})") + else: + raise NotImplementedError("This device doesn't support set_simple_trigger (yet)") + + @requires_device() + def set_digital_channel_trigger(self, device, channel_number=15, direction="DIRECTION_RISING"): + """Set a simple trigger on the digital channels. + + Args: + device (picosdk.device.Device): The device instance + channel_number (int): The number of the digital channel on which to trigger.(e.g. 0 for D0, 1 for D1,...) + direction (str): The direction in which the signal must move to cause a trigger. + """ + if (hasattr(self, '_set_trigger_digital_port_properties') and + len(self._set_trigger_digital_port_properties.argtypes) == 3): + digital_properties = getattr(self, self.name.upper() + '_DIGITAL_CHANNEL_DIRECTIONS', None) + digital_channels = getattr(self, self.name.upper() + '_DIGITAL_CHANNEL', None) + directions = getattr(self, self.name.upper() + '_DIGITAL_DIRECTION', None) + if digital_properties and digital_channels and directions: + digital_channel = self.name.upper() + '_DIGITAL_CHANNEL_' + str(channel_number) + direction = self.name.upper() + '_DIGITAL_' + direction + properties = digital_properties(channel=digital_channels[digital_channel], + direction=directions[direction]) + args = (device.handle, properties, 1) + converted_args = self._convert_args(self._set_trigger_digital_port_properties, args) + status = self._set_trigger_digital_port_properties(*converted_args) + if status != self.PICO_STATUS['PICO_OK']: + raise InvalidTriggerParameters("set_trigger_digital_port_properties failed " + f"({constants.pico_tag(status)})") + else: + raise PicoError("Couldn't set digital channel trigger. " + f"Check if all enumerations are implemented for {self.name}") + else: + raise NotImplementedError("This device doesn't support set_digital_channel_trigger (yet)") + + @requires_device() + def set_trigger_delay(self, device, delay): + """This function sets the post-trigger delay, which causes capture to start a defined time after the + trigger event. + + For example, if delay=100 then the scope would wait 100 sample periods before sampling. + At a timebase of 500 MS/s, or 2 ns per sample, the total delay would then be 100 x 2 ns = 200 ns. + + Args: + device (picosdk.device.Device): The device instance + delay (int): The time between the trigger occurring and the first sample. + """ + if hasattr(self, '_set_trigger_delay') and len(self._set_trigger_delay.argtypes) == 2: + args = (device.handle, delay) + converted_args = self._convert_args(self._set_trigger_delay, args) + status = self._set_trigger_delay(*converted_args) + + if status != self.PICO_STATUS['PICO_OK']: + raise InvalidTriggerParameters(f"set_trigger_delay failed ({constants.pico_tag(status)})") + else: + raise NotImplementedError("This device doesn't support set_trigger_delay (yet)") @requires_device() def run_block(self, device, pre_trigger_samples, post_trigger_samples, timebase_id, oversample=1, segment_index=0): - """tell the device to arm any triggers and start capturing in block mode now. - returns: the approximate time (in seconds) which the device will take to capture with these settings.""" + """This function starts collecting data in block mode. + + Args: + device (picosdk.device.Device): The device instance + pre_trigger_samples (int): The number of samples to collect before the trigger event. + post_trigger_samples (int): The number of samples to collect after the trigger event. + timebase_id (int): The timebase id to use for the capture. + oversample (int): The amount of oversample required. Defaults to 1. + segment_index (int): The memory segment index to use. Defaults to 0. + + Returns: + float: The approximate time (in seconds) which the device will take to capture with these settings + """ return self._python_run_block(device.handle, pre_trigger_samples, post_trigger_samples, @@ -465,28 +839,33 @@ def run_block(self, device, pre_trigger_samples, post_trigger_samples, timebase_ oversample, segment_index) - def _python_run_block(self, handle, pre_samples, post_samples, timebase_id, oversample, segment_index): + def _python_run_block(self, handle, pre_trigger_samples, post_trigger_samples, timebase_id, oversample, + segment_index): time_indisposed = c_int32(0) if len(self._run_block.argtypes) == 5: - return_code = self._run_block(c_int16(handle), - c_int32(pre_samples + post_samples), - c_int16(timebase_id), - c_int16(oversample), - byref(time_indisposed)) + args = (handle, pre_trigger_samples + post_trigger_samples, timebase_id, + oversample, time_indisposed) + converted_args = self._convert_args(self._run_block, args) + return_code = self._run_block(*converted_args) + if return_code == 0: raise InvalidCaptureParameters() + elif len(self._run_block.argtypes) == 8: + args = (handle, pre_trigger_samples, post_trigger_samples, timebase_id, + time_indisposed, segment_index, None, None) + converted_args = self._convert_args(self._run_block, args) + status = self._run_block(*converted_args) + + if status != self.PICO_STATUS['PICO_OK']: + raise InvalidCaptureParameters(f"run_block failed ({constants.pico_tag(status)})") elif len(self._run_block.argtypes) == 9: - status = self._run_block(c_int16(handle), - c_int32(pre_samples), - c_int32(post_samples), - c_uint32(timebase_id), - c_int16(oversample), - byref(time_indisposed), - c_uint32(segment_index), - None, - None) + args = (handle, pre_trigger_samples, post_trigger_samples, timebase_id, + oversample, time_indisposed, segment_index, None, None) + converted_args = self._convert_args(self._run_block, args) + status = self._run_block(*converted_args) + if status != self.PICO_STATUS['PICO_OK']: - raise InvalidCaptureParameters("run_block failed (%s)" % constants.pico_tag(status)) + raise InvalidCaptureParameters(f"run_block failed ({constants.pico_tag(status)})") else: raise NotImplementedError("not done other driver types yet") @@ -508,74 +887,409 @@ def is_ready(self, device): else: raise NotImplementedError("not done other driver types yet") + @requires_device() + def stop_block_capture(self, device, timeout_minutes=5): + """Poll the driver to see if it has finished collecting the requested samples. + + Args: + device (picosdk.device.Device): The device instance + timeout_minutes (int/float): The timeout in minutes. If the time exceeds the timeout, the poll stops. + + Raises: + TimeoutError: If the device is not ready within the specified timeout. + """ + if timeout_minutes < 0: + raise ArgumentOutOfRangeError("timeout_minutes must be non-negative.") + timeout = time.time() + timeout_minutes * 60 + while not self.is_ready(device): + if time.time() > timeout: + raise TimeoutError(f"Picoscope not ready within {timeout_minutes} minute(s).") + @requires_device() def maximum_value(self, device): + """Get the maximum ADC value for this device. + + Args: + device (picosdk.device.Device): The device instance + + Returns: + int: The maximum ADC value for this device. + """ if not hasattr(self, '_maximum_value'): return (2**15)-1 max_adc = c_int16(0) - self._maximum_value(c_int16(device.handle), byref(max_adc)) + args = (device.handle, max_adc) + converted_args = self._convert_args(self._maximum_value, args) + self._maximum_value(*converted_args) return max_adc.value @requires_device() - def get_values(self, device, active_channels, num_samples, segment_index=0): - # Initialise buffers to hold the data: - results = {channel: numpy.empty(num_samples, numpy.dtype('int16')) for channel in active_channels} + def set_data_buffer(self, device, channel_or_port, buffer_length, segment_index=0, mode='NONE'): + """Set the data buffer for a specific channel. + + Args: + device (picosdk.device.Device): The device instance + channel_or_port (int/str): Channel (e.g. 'A', 'B') or digital port (e.g. 0, 1) to set data for + buffer_length (int): The size of the buffer array (equal to no_of_samples) + segment_index (int): The number of the memory segment to be used (default is 0) + mode (str): The ratio mode to be used (default is 'NONE') + + Raises: + ArgumentOutOfRangeError: If parameters are invalid for device + """ + if not hasattr(self, '_set_data_buffer'): + raise NotImplementedError("This device doesn't support setting data buffers") + if len(self._set_data_buffer.argtypes) != 6: + raise NotImplementedError("set_data_buffer is not implemented for this driver") + if isinstance(channel_or_port, str) and channel_or_port.upper() in self.PICO_CHANNEL: + id = self.PICO_CHANNEL[channel_or_port] + else: + try: + port_num = int(channel_or_port) + digital_ports = getattr(self, self.name.upper() + '_DIGITAL_PORT', None) + if digital_ports: + digital_port = self.name.upper() + '_DIGITAL_PORT' + str(port_num) + if digital_port not in digital_ports: + raise ArgumentOutOfRangeError(f"Invalid digital port number {port_num}") + id = digital_ports[digital_port] + except ValueError: + raise ArgumentOutOfRangeError(f"Invalid digital port number {port_num}") + + if mode not in self.PICO_RATIO_MODE: + raise ArgumentOutOfRangeError(f"Invalid ratio mode '{mode}' for {self.name} driver" + "or PICO_RATIO_MODE doesn't exist for this driver") + buffer = (c_int16 * buffer_length)() + args = (device.handle, id, buffer, buffer_length, segment_index, self.PICO_RATIO_MODE[mode]) + converted_args = self._convert_args(self._set_data_buffer, args) + status = self._set_data_buffer(*converted_args) + + if status != self.PICO_STATUS['PICO_OK']: + raise ArgumentOutOfRangeError(f"set_data_buffer failed ({constants.pico_tag(status)})") + return buffer + + @requires_device() + def get_values(self, device, buffers, samples, time_interval_sec, max_voltage={}, start_index=0, downsample_ratio=0, + downsample_ratio_mode="NONE", segment_index=0, output_dir=".", filename="data", save_to_file=False, + probe_attenuation=DEFAULT_PROBE_ATTENUATION): + """Get stored data values from the scope and store it in a clean SingletonScopeDataDict object. + + This function is used after data collection has stopped. It gets the stored data from the scope, with or + without downsampling, starting at the specified sample number. + + The returned captured data is converted to mV. + + Args: + device (picosdk.device.Device): Device instance + buffers (dict): Dictionary of buffers where the data will be stored. The keys are channel names or + port numbers, and the values are numpy arrays. + samples (int): The number of samples to retrieve from the scope. + time_interval_sec (float): The time interval between samples in seconds. (obtained from get_timebase) + max_voltage (dict): The maximum voltage of the range used per channel. (obtained from set_channel) + start_index (int): A zero-based index that indicates the start point for data collection. It is measured in + sample intervals from the start of the buffer. + downsample_ratio (int): The downsampling factor that will be applied to the raw data. + downsample_ratio_mode (str): Which downsampling mode to use. + segment_index (int): Memory segment index + output_dir (str): The output directory where the json file will be saved. + filename (str): The name of the json file where the data will be stored + save_to_file (bool): True if the data has to be saved to a file on the disk, False otherwise + probe_attenuation (dict): The attenuation factor of the probe used per the channel (1 or 10). + + Returns: + scope_data (SingletonScopeDataDict): The captured scope data. + overflow_warning (dict): A dictionary indicating which channels had an overflow. + """ + scope_data = SingletonScopeDataDict() + scope_data.clean_dict() overflow = c_int16(0) + no_of_samples = c_uint32(samples) - if len(self._get_values.argtypes) == 7 and self._get_timebase.argtypes[1] == c_int16: - inputs = {k: None for k in 'ABCD'} - for k, arr in results.items(): - inputs[k] = arr.ctypes.data - return_code = self._get_values(c_int16(device.handle), - inputs['A'], - inputs['B'], - inputs['C'], - inputs['D'], - byref(overflow), - c_int32(num_samples)) - if return_code == 0: - raise InvalidCaptureParameters() - elif len(self._get_values.argtypes) == 7 and self._get_timebase.argtypes[1] == c_uint32: - # For this function pattern, we first call a function (self._set_data_buffer) to register each buffer. Then, - # we can call self._get_values to actually populate them. - for channel, array in results.items(): - status = self._set_data_buffer(c_int16(device.handle), - c_int32(self.PICO_CHANNEL[channel]), - array.ctypes.data, - c_int32(num_samples), - c_uint32(segment_index), - c_int32(self.PICO_RATIO_MODE['NONE'])) - if status != self.PICO_STATUS['PICO_OK']: - raise InvalidCaptureParameters("set_data_buffer failed (%s)" % constants.pico_tag(status)) - - samples_collected = c_uint32(num_samples) - status = self._get_values(c_int16(device.handle), - c_uint32(0), - byref(samples_collected), - c_uint32(1), - c_int32(self.PICO_RATIO_MODE['NONE']), - c_uint32(segment_index), - byref(overflow)) + if len(self._get_values.argtypes) == 7: + args = (device.handle, start_index, no_of_samples, downsample_ratio, + self.PICO_RATIO_MODE[downsample_ratio_mode], segment_index, overflow) + converted_args = self._convert_args(self._get_values, args) + status = self._get_values(*converted_args) + + if samples != no_of_samples.value: + raise InvalidCaptureParameters("get_values could not retrieve the requested number of samples. " + f"Requested: {samples}, Retrieved: {no_of_samples.value}") if status != self.PICO_STATUS['PICO_OK']: - raise InvalidCaptureParameters("get_values failed (%s)" % constants.pico_tag(status)) + raise InvalidCaptureParameters(f"get_values failed ({constants.pico_tag(status)})") + else: + raise NotImplementedError("not done other driver types yet") + + for channel, buffer in buffers.items(): + if isinstance(channel, int) or channel.isnumeric(): + scope_data[channel] = numpy.asarray(split_mso_data_fast(no_of_samples, buffer)) + else: + scope_data[channel] = numpy.array(adc_to_mv(buffer, max_voltage[channel], + self.maximum_value(device))) * probe_attenuation[channel] + + time_sec = numpy.linspace(0, + (samples - 1) * time_interval_sec, + samples) + scope_data["time"] = numpy.array(time_sec) + + if save_to_file: + with open(f"{output_dir}/{filename}.json", "w", encoding="utf-8") as json_file: + json.dump(scope_data, json_file, indent=4, cls=NumpyEncoder) overflow_warning = {} if overflow.value: - for channel in results.keys(): + for channel in buffers.keys(): if overflow.value & (1 >> self.PICO_CHANNEL[channel]): overflow_warning[channel] = True - return results, overflow_warning + return scope_data, overflow_warning + + @requires_device() + def store_values(self, device, buffers, samples, time_interval_sec, max_voltage={}, start_index=0, + downsample_ratio=0, downsample_ratio_mode="NONE", segment_index=0, output_dir=".", + filename="data", save_to_file=False, probe_attenuation=DEFAULT_PROBE_ATTENUATION): + """Same as `get_values` but only returns overflow warnings and keeps the data stored in SingletonScopeDataDict. + + Args: + device (picosdk.device.Device): Device instance + buffers (dict): Dictionary of buffers where the data will be stored. The keys are channel names or + port numbers, and the values are numpy arrays. + samples (int): The number of samples to retrieve from the scope. + time_interval_sec (float): The time interval between samples in seconds. (obtained from get_timebase) + max_voltage (dict): The maximum voltage of the range used per channel. (obtained from set_channel) + start_index (int): A zero-based index that indicates the start point for data collection. It is measured in + sample intervals from the start of the buffer. + downsample_ratio (int): The downsampling factor that will be applied to the raw data. + downsample_ratio_mode (str): Which downsampling mode to use. + segment_index (int): Memory segment index + output_dir (str): The output directory where the json file will be saved. + filename (str): The name of the json file where the data will be stored + save_to_file (bool): True if the data has to be saved to a file on the disk, False otherwise + probe_attenuation (dict): The attenuation factor of the probe used per the channel (1 or 10). + + Returns: + overflow warnings (dict): A dictionary indicating which channels had an overflow. + """ + return self.get_values(device, buffers, samples, time_interval_sec, max_voltage, start_index, + downsample_ratio, downsample_ratio_mode, segment_index, output_dir, + filename, save_to_file, probe_attenuation)[1] + + @requires_device() + def set_and_load_data(self, device, active_sources, buffer_length, time_interval_sec, max_voltage={}, + segment_index=0, ratio_mode='NONE', start_index=0, + downsample_ratio=0, downsample_ratio_mode="NONE", probe_attenuation=DEFAULT_PROBE_ATTENUATION, + output_dir=".", filename="data", save_to_file=False): + """Load values from the device. + + Combines set_data_buffer and get_values to load values from the device. + + Args: + device (picosdk.device.Device): Device instance + active_sources (lsit[str/int]): List of active channels and/or ports + buffer_length: The size of the buffer array (equal to the number of samples) + time_interval_sec (float): The time interval between samples in seconds. (obtained from get_timebase) + max_voltage (dict): The maximum voltage of the range used per channel. (obtained from set_channel) + segment_index (int): Memory segment index + ratio_mode: The ratio mode to be used (default is 'NONE') + start_index (int): A zero-based index that indicates the start point for data collection. It is measured in + sample intervals from the start of the buffer. + downsample_ratio (int): The downsampling factor that will be applied to the raw data. + downsample_ratio_mode (str): Which downsampling mode to use. + probe_attenuation (dict): The attenuation factor of the probe used per the channel (1 or 10). + output_dir (str): The output directory where the json file will be saved. + filename (str): The name of the json file where the data will be stored + save_to_file (bool): True if the data has to be saved to a file on the disk, False otherwise + + Returns: + overflow warnings (dict): A dictionary indicating which channels had an overflow. + """ + buffers = {} + for source in active_sources: + buffers[source] = self.set_data_buffer(device, source, buffer_length, segment_index, ratio_mode) + + return self.get_values(device, buffers, buffer_length, time_interval_sec, max_voltage, start_index, + downsample_ratio, downsample_ratio_mode, segment_index, output_dir, filename, + save_to_file, probe_attenuation) + + @requires_device() + def set_trigger_conditions_v2(self, device, trigger_input): + """Sets up trigger conditions on the scope's inputs. + Sets trigger state to TRUE for given `trigger_input`, the rest will be DONT CARE + + Args: + device (picosdk.device.Device): Device instance + trigger (str): What to trigger (e.g. channelA, channelB, external, aux, pulseWidthQualifier, digital) + """ + + if hasattr(self, '_set_trigger_channel_conditions_v2'): + trigger_conditions = getattr(self, self.name.upper() + '_TRIGGER_CONDITIONS_V2', None) + trigger_state = getattr(self, self.name.upper() + '_TRIGGER_STATE', None) + + if not trigger_conditions or not trigger_state: + raise PicoError(f"Trigger conditions not fully defined for {self.name} driver.") + + trigger_dont_care = trigger_state[self.name.upper() + '_CONDITION_DONT_CARE'] + trigger_true = trigger_state[self.name.upper() + '_CONDITION_TRUE'] + kwargs = {field[0]: trigger_dont_care for field in trigger_conditions._fields_} + + if trigger_input in kwargs: + kwargs[trigger_input] = trigger_true + else: + raise ArgumentOutOfRangeError(f"Invalid trigger source: '{trigger_input}'. " + f"Valid sources are: {list(kwargs.keys())}") + + conditions = trigger_conditions(**kwargs) + args = (device.handle, conditions, 1) + converted_args = self._convert_args(self._set_trigger_channel_conditions_v2, args) + status = self._set_trigger_channel_conditions_v2(*converted_args) + + if status != self.PICO_STATUS['PICO_OK']: + raise PicoError(f"set_trigger_channel_conditions_v2 failed ({constants.pico_tag(status)})") + else: + raise NotImplementedError("This device does not support setting trigger conditions via V2 struct.") + + @requires_device("set_trigger_channel_properties requires a picosdk.device.Device instance, passed to the correct owning driver.") + def set_trigger_channel_properties(self, device, threshold_upper, threshold_upper_hysteresis, threshold_lower, + threshold_lower_hysteresis, channel, threshold_mode, aux_output_enable, + auto_trigger_milliseconds): + """Set the trigger channel properties for the device. + + Args: + device (picosdk.device.Device): Device instance + threshold_upper (int): Upper threshold in ADC counts + threshold_upper_hysteresis (int): Hysteresis for upper threshold in ADC counts + threshold_lower (int): Lower threshold in ADC counts + threshold_lower_hysteresis (int): Hysteresis for lower threshold in ADC counts + channel (str): Channel to set properties for (e.g. 'A', 'B', 'C', 'D') + threshold_mode (str): Threshold mode (e.g. "LEVEL", "WINDOW") + aux_output_enable (bool): Enable auxiliary output (boolean) (Not used in eg. ps2000a, ps3000a, ps4000a) + auto_trigger_milliseconds (int): The number of milliseconds for which the scope device will wait for a + trigger before timing out. If set to zero, the scope device will wait indefinitely for a trigger + + Raises: + NotImplementedError: This device does not support setting trigger channel properties. + PicoError: If the function fails to set the properties. + """ + if hasattr(self, '_set_trigger_channel_properties'): + args = (device.handle, threshold_upper, threshold_upper_hysteresis, + threshold_lower, threshold_lower_hysteresis, + self.PICO_CHANNEL[channel], self.PICO_THRESHOLD_DIRECTION[threshold_mode], + 1 if aux_output_enable else 0, auto_trigger_milliseconds) + converted_args = self._convert_args(self._set_trigger_channel_properties, args) + status = self._set_trigger_channel_properties(*converted_args) + + if status != self.PICO_STATUS['PICO_OK']: + raise PicoError(f"set_trigger_channel_properties failed ({constants.pico_tag(status)})") + else: + raise NotImplementedError("This device does not support setting trigger channel properties.") @requires_device() def stop(self, device): + """Stop data capture. + + Args: + device (picosdk.device.Device): Device instance + + Raises: + InvalidCaptureParameters: If the stop operation fails or parameters are invalid. + """ + args = (device.handle,) + converted_args = self._convert_args(self._stop, args) + if self._stop.restype == c_int16: - return_code = self._stop(c_int16(device.handle)) - if isinstance(return_code, c_int16): - if return_code == 0: - raise InvalidCaptureParameters() + return_code = self._stop(*converted_args) + if isinstance(return_code, c_int16) and return_code == 0: + raise InvalidCaptureParameters() else: - status = self._stop(c_int16(device.handle)) + status = self._stop(*converted_args) if status != self.PICO_STATUS['PICO_OK']: - raise InvalidCaptureParameters("stop failed (%s)" % constants.pico_tag(status)) + raise InvalidCaptureParameters(f"stop failed ({constants.pico_tag(status)})") + + @requires_device() + def set_sig_gen_built_in(self, device, offset_voltage=0, pk_to_pk=2000000, wave_type="SINE", + start_frequency=10000, stop_frequency=10000, increment=0, + dwell_time=1, sweep_type="UP", operation='ES_OFF', shots=0, sweeps=0, + trigger_type="RISING", trigger_source="NONE", ext_in_threshold=1): + """Set up the signal generator to output a built-in waveform. + + Args: + device: Device instance + offset_voltage (int/float): Offset voltage in microvolts (default 0) + pk_to_pk (int): Peak-to-peak voltage in microvolts (default 2000000) + wave_type (str): Type of waveform (e.g. "SINE", "SQUARE", "TRIANGLE") + start_frequency (int): Start frequency in Hz (default 1000.0) + stop_frequency (int): Stop frequency in Hz (default 1000.0) + increment (int): Frequency increment in Hz (default 0.0) + dwell_time (int/float): Time at each frequency in seconds (default 1.0) + sweep_type (str): Sweep type (e.g. "UP", "DOWN", "UPDOWN") + operation (str): Configures the white noise/PRBS (e.g. "ES_OFF", "WHITENOISE", "PRBS") + shots (int): Number of shots per trigger (default 1) + sweeps (int): Number of sweeps (default 1) + trigger_type (str): Type of trigger (e.g. "RISING", "FALLING") + trigger_source (str): Source of trigger (e.g. "NONE", "SCOPE_TRIG") + ext_in_threshold (int): External trigger threshold in ADC counts + + Raises: + ArgumentOutOfRangeError: If parameters are invalid for device + """ + prefix = self.name.upper() + + # Convert string parameters to enum values + try: + wave_type_val = getattr(self, f"{prefix}_WAVE_TYPE")[f"{prefix}_{wave_type.upper()}"] + sweep_type_val = getattr(self, f"{prefix}_SWEEP_TYPE")[f"{prefix}_{sweep_type.upper()}"] + except (AttributeError, KeyError) as e: + raise ArgumentOutOfRangeError(f"Invalid wave_type or sweep_type for this device: {e}") + + # Check function signature and call appropriate version + if len(self._set_sig_gen_built_in.argtypes) == 10: + args = (device.handle, offset_voltage, pk_to_pk, wave_type_val, + start_frequency, stop_frequency, increment, dwell_time, + sweep_type_val, sweeps) + converted_args = self._convert_args(self._set_sig_gen_built_in, args) + status = self._set_sig_gen_built_in(*converted_args) + + elif len(self._set_sig_gen_built_in.argtypes) == 15: + try: + trigger_type_val = getattr(self, f"{prefix}_SIGGEN_TRIG_TYPE")[f"{prefix}_SIGGEN_{trigger_type.upper()}"] + trigger_source_val = getattr(self, f"{prefix}_SIGGEN_TRIG_SOURCE")[f"{prefix}_SIGGEN_{trigger_source.upper()}"] + extra_ops_val = getattr(self, f"{prefix}_EXTRA_OPERATIONS")[f"{prefix}_{operation.upper()}"] + except (AttributeError, KeyError) as e: + raise ArgumentOutOfRangeError(f"Invalid trigger parameters for this device: {e}") + + args = (device.handle, offset_voltage, pk_to_pk, wave_type_val, + start_frequency, stop_frequency, increment, dwell_time, + sweep_type_val, extra_ops_val, shots, sweeps, + trigger_type_val, trigger_source_val, ext_in_threshold) + converted_args = self._convert_args(self._set_sig_gen_built_in, args) + status = self._set_sig_gen_built_in(*converted_args) + + else: + raise NotImplementedError("Signal generator not supported on this device") + + if status != self.PICO_STATUS["PICO_OK"]: + raise PicoError(f"set_sig_gen_built_in failed: {constants.pico_tag(status)}") + + def _convert_args(self, func, args): + """Convert arguments to match function argtypes. + + Args: + func: The C function with argtypes defined + args: Tuple of arguments to convert + + Returns: + Tuple of converted arguments matching argtypes + """ + if not hasattr(func, 'argtypes'): + return args + + converted = [] + for arg, argtype in zip(args, func.argtypes): + # Handle byref parameters + if argtype == c_void_p and arg is not None: + converted.append(byref(arg)) + # Handle normal parameters + elif arg is not None: + converted.append(argtype(arg)) + else: + converted.append(None) + return tuple(converted) diff --git a/picosdk/ps3000a.py b/picosdk/ps3000a.py index c9f4768..bea7569 100644 --- a/picosdk/ps3000a.py +++ b/picosdk/ps3000a.py @@ -63,13 +63,6 @@ def __init__(self): for k, v in ps3000a.PS3000A_RANGE.items() if k != "PS3000A_MAX_RANGES" } -ps3000a.PS3000A_RATIO_MODE = { - 'PS3000A_RATIO_MODE_NONE': 0, - 'PS3000A_RATIO_MODE_AGGREGATE': 1, - 'PS3000A_RATIO_MODE_DECIMATE': 2, - 'PS3000A_RATIO_MODE_AVERAGE': 4, -} - ps3000a.PS3000A_TIME_UNITS = make_enum([ 'PS3000A_FS', 'PS3000A_PS', @@ -80,6 +73,55 @@ def __init__(self): 'PS3000A_MAX_TIME_UNITS', ]) +ps3000a.PS3000A_WAVE_TYPE = make_enum([ + "PS3000A_SINE", + "PS3000A_SQUARE", + "PS3000A_TRIANGLE", + "PS3000A_DC_VOLTAGE", + "PS3000A_RAMP_UP", + "PS3000A_RAMP_DOWN", + "PS3000A_SINC", + "PS3000A_GAUSSIAN", + "PS3000A_HALF_SINE", +]) + +ps3000a.PS3000A_SWEEP_TYPE = make_enum([ + "PS3000A_UP", + "PS3000A_DOWN", + "PS3000A_UPDOWN", + "PS3000A_DOWNUP", +]) + +ps3000a.PS3000A_EXTRA_OPERATIONS = make_enum([ + 'PS3000A_ES_OFF', + 'PS3000A_WHITENOISE', + 'PS3000A_PRBS', +]) + +ps3000a.PS3000A_SIGGEN_TRIG_TYPE = make_enum([ + 'PS3000A_SIGGEN_RISING', + 'PS3000A_SIGGEN_FALLING', + 'PS3000A_SIGGEN_GATE_HIGH', + 'PS3000A_SIGGEN_GATE_LOW', +]) + +ps3000a.PS3000A_SIGGEN_TRIG_SOURCE = make_enum([ + 'PS3000A_SIGGEN_NONE', + 'PS3000A_SIGGEN_SCOPE_TRIG', + 'PS3000A_SIGGEN_EXT_IN', + 'PS3000A_SIGGEN_SOFT_TRIG', + 'PS3000A_SIGGEN_TRIGGER_RAW', +]) + +ps3000a.PS3000A_RATIO_MODE = make_enum([ + 'PS3000A_RATIO_MODE_NONE', + 'PS3000A_RATIO_MODE_AGGREGATE', + 'PS3000A_RATIO_MODE_AVERAGE', + 'PS3000A_RATIO_MODE_DECIMATE', +]) + +ps3000a.PICO_RATIO_MODE = {k[19:]: v for k, v in ps3000a.PS3000A_RATIO_MODE.items()} + ps3000a.PS3000A_DIGITAL_CHANNEL = make_enum([ "PS3000A_DIGITAL_CHANNEL_0", "PS3000A_DIGITAL_CHANNEL_1", diff --git a/picosdk/ps4000a.py b/picosdk/ps4000a.py index 20f449d..f82e0ef 100644 --- a/picosdk/ps4000a.py +++ b/picosdk/ps4000a.py @@ -43,9 +43,14 @@ def __init__(self): "PS4000A_PULSE_WIDTH_SOURCE" : 0x10000000 } -ps4000a.PICO_CHANNEL = {k[19:]: v for k, v in ps4000a.PS4000A_CHANNEL.items()} - - +ps4000a.PICO_CHANNEL = {} +prefix = "PS4000A_CHANNEL_" +for key, value in ps4000a.PS4000A_CHANNEL.items(): + if isinstance(key, tuple): + key = key[0] # Use the first name in the tuple. + if key.startswith(prefix): + key = key[len(prefix):] + ps4000a.PICO_CHANNEL[key] = value # Only channels are included (A, B, C, D, E, F, G, H). # The voltage ranges for this driver are so oddly defined, that it is easier to describe them as a literal than trying # to use make_enum: @@ -190,14 +195,14 @@ def process_enum(enum): class PS4000A_USER_PROBE_INTERACTIONS(Structure): _pack_ = 1 _fields_ = [ ("connected", c_uint16), - + ("channel", c_int32), ("enabled", c_uint16), ("probeName", c_uint32), ("requiresPower_", c_uint8), - ("isPowered_", c_uint8), + ("isPowered_", c_uint8), ("status", c_uint32), @@ -237,59 +242,59 @@ class PS4000A_USER_PROBE_INTERACTIONS(Structure): ]) ps4000a.PS4000A_WAVE_TYPE = make_enum([ - 'PS4000A_SINE', - 'PS4000A_SQUARE', - 'PS4000A_TRIANGLE', - 'PS4000A_RAMP_UP', - 'PS4000A_RAMP_DOWN', - 'PS4000A_SINC', - 'PS4000A_GAUSSIAN', - 'PS4000A_HALF_SINE', - 'PS4000A_DC_VOLTAGE', - 'PS4000A_WHITE_NOISE', - 'PS4000A_MAX_WAVE_TYPES', + 'PS4000A_SINE', + 'PS4000A_SQUARE', + 'PS4000A_TRIANGLE', + 'PS4000A_RAMP_UP', + 'PS4000A_RAMP_DOWN', + 'PS4000A_SINC', + 'PS4000A_GAUSSIAN', + 'PS4000A_HALF_SINE', + 'PS4000A_DC_VOLTAGE', + 'PS4000A_WHITE_NOISE', + 'PS4000A_MAX_WAVE_TYPES', ]) ps4000a.PS4000A_SWEEP_TYPE = make_enum([ - 'PS4000A_UP', - 'PS4000A_DOWN', - 'PS4000A_UPDOWN', - 'PS4000A_DOWNUP', - 'PS4000A_MAX_SWEEP_TYPES', + 'PS4000A_UP', + 'PS4000A_DOWN', + 'PS4000A_UPDOWN', + 'PS4000A_DOWNUP', + 'PS4000A_MAX_SWEEP_TYPES', ]) ps4000a.PS4000A_SIGGEN_TRIG_TYPE = make_enum([ - 'PS4000A_SIGGEN_RISING', - 'PS4000A_SIGGEN_FALLING', - 'PS4000A_SIGGEN_GATE_HIGH', - 'PS4000A_SIGGEN_GATE_LOW', + 'PS4000A_SIGGEN_RISING', + 'PS4000A_SIGGEN_FALLING', + 'PS4000A_SIGGEN_GATE_HIGH', + 'PS4000A_SIGGEN_GATE_LOW', ]) ps4000a.PS4000A_SIGGEN_TRIG_SOURCE = make_enum([ - 'PS4000A_SIGGEN_NONE', - 'PS4000A_SIGGEN_SCOPE_TRIG', - 'PS4000A_SIGGEN_AUX_IN', - 'PS4000A_SIGGEN_EXT_IN', - 'PS4000A_SIGGEN_SOFT_TRIG', + 'PS4000A_SIGGEN_NONE', + 'PS4000A_SIGGEN_SCOPE_TRIG', + 'PS4000A_SIGGEN_AUX_IN', + 'PS4000A_SIGGEN_EXT_IN', + 'PS4000A_SIGGEN_SOFT_TRIG', ]) ps4000a.PS4000A_INDEX_MODE = make_enum([ - 'PS4000A_SINGLE', - 'PS4000A_DUAL', - 'PS4000A_QUAD', - 'PS4000A_MAX_INDEX_MODES', + 'PS4000A_SINGLE', + 'PS4000A_DUAL', + 'PS4000A_QUAD', + 'PS4000A_MAX_INDEX_MODES', ]) ps4000a.PS4000A_EXTRA_OPERATIONS = make_enum([ - 'PS4000A_ES_OFF', - 'PS4000A_WHITENOISE', - 'PS4000A_PRBS', + 'PS4000A_ES_OFF', + 'PS4000A_WHITENOISE', + 'PS4000A_PRBS', ]) ps4000a.PS4000A_CONDITIONS_INFO = { 'PS4000A_CLEAR': 1, 'PS4000A_ADD': 2, -} +} ps4000a.PS4000A_THRESHOLD_DIRECTION = make_enum([ ("PS4000A_ABOVE", "PS4000A_INSIDE"), @@ -325,17 +330,17 @@ class PS4000A_USER_PROBE_INTERACTIONS(Structure): ]) class PS4000A_CONDITION (Structure): - _pack_ = 1 - _fields_ = [("source", c_int32), - ("condition", c_int32)] - + _pack_ = 1 + _fields_ = [("source", c_int32), + ("condition", c_int32)] + ps4000a.PS4000A_CONDITION = PS4000A_CONDITION class PS4000A_DIRECTION(Structure): _pack_ = 1 _fields_ = [("channel", c_int32), ("direction", c_int32)] - + ps4000a.PS4000A_DIRECTION = PS4000A_DIRECTION class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): @@ -346,7 +351,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): ("thresholdLowerHysteresis", c_uint16), ("channel", c_int32), ("thresholdMode", c_int32)] - + ps4000a.PS4000A_TRIGGER_CHANNEL_PROPERTIES = PS4000A_TRIGGER_CHANNEL_PROPERTIES doc = """ PICO_STATUS ps4000aOpenUnit @@ -354,14 +359,14 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t *handle, int8_t *serial ); """ -ps4000a.make_symbol("_OpenUnit", "ps4000aOpenUnit", c_uint32, [c_void_p, c_char_p], doc) +ps4000a.make_symbol("_open_unit", "ps4000aOpenUnit", c_uint32, [c_void_p, c_char_p], doc) doc = """ PICO_STATUS ps4000aOpenUnitAsync ( int16_t *status, int8_t *serial ); """ -ps4000a.make_symbol("_OpenUnitAsync", "ps4000aOpenUnitAsync", c_uint32, [c_void_p, c_char_p], doc) +ps4000a.make_symbol("_open_unit_async", "ps4000aOpenUnitAsync", c_uint32, [c_void_p, c_char_p], doc) doc = """ PICO_STATUS ps4000aOpenUnitProgress ( @@ -369,7 +374,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t *progressPercent, int16_t *complete ); """ -ps4000a.make_symbol("_OpenUnitProgress", "ps4000aOpenUnitProgress", c_uint32, [c_void_p, c_void_p, c_void_p], doc) +ps4000a.make_symbol("_open_unit_progress", "ps4000aOpenUnitProgress", c_uint32, [c_void_p, c_void_p, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetUnitInfo ( @@ -379,7 +384,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t *requiredSize, PICO_INFO info ); """ -ps4000a.make_symbol("_GetUnitInfo", "ps4000aGetUnitInfo", c_uint32, [c_int16, c_char_p, c_int16, c_void_p, c_uint32], +ps4000a.make_symbol("_get_unit_info", "ps4000aGetUnitInfo", c_uint32, [c_int16, c_char_p, c_int16, c_void_p, c_uint32], doc) doc = """ PICO_STATUS ps4000aFlashLed @@ -387,7 +392,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t handle, int16_t start ); """ -ps4000a.make_symbol("_FlashLed", "ps4000aFlashLed", c_uint32, [c_int16, c_int16], doc) +ps4000a.make_symbol("_flash_led", "ps4000aFlashLed", c_uint32, [c_int16, c_int16], doc) doc = """ PICO_STATUS ps4000aSetChannelLed ( @@ -395,20 +400,20 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_CHANNEL_LED_SETTING *ledStates, uint16_t nLedStates ); """ -ps4000a.make_symbol("_SetChannelLed", "ps4000aSetChannelLed", c_uint32, [c_int16, c_void_p, c_uint16], doc) +ps4000a.make_symbol("_set_channel_led", "ps4000aSetChannelLed", c_uint32, [c_int16, c_void_p, c_uint16], doc) doc = """ PICO_STATUS ps4000aIsLedFlashing ( int16_t handle, int16_t *status ); """ -ps4000a.make_symbol("_IsLedFlashing", "ps4000aIsLedFlashing", c_uint32, [c_int16, c_void_p], doc) +ps4000a.make_symbol("_is_led_flashing", "ps4000aIsLedFlashing", c_uint32, [c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aCloseUnit ( int16_t handle ); """ -ps4000a.make_symbol("_CloseUnit", "ps4000aCloseUnit", c_uint32, [c_int16, ], doc) +ps4000a.make_symbol("_close_unit", "ps4000aCloseUnit", c_uint32, [c_int16, ], doc) doc = """ PICO_STATUS ps4000aMemorySegments ( @@ -416,7 +421,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t nSegments, int32_t *nMaxSamples ); """ -ps4000a.make_symbol("_MemorySegments", "ps4000aMemorySegments", c_uint32, [c_int16, c_uint32, c_void_p], doc) +ps4000a.make_symbol("_memory_segments", "ps4000aMemorySegments", c_uint32, [c_int16, c_uint32, c_void_p], doc) doc = """ PICO_STATUS ps4000aSetChannel ( @@ -427,7 +432,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_RANGE range, float analogOffset ); """ -ps4000a.make_symbol("_SetChannel", "ps4000aSetChannel", c_uint32, +ps4000a.make_symbol("_set_channel", "ps4000aSetChannel", c_uint32, [c_int16, c_int32, c_int16, c_int32, c_int32, c_float], doc) doc = """ PICO_STATUS ps4000aSetBandwidthFilter @@ -436,7 +441,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_CHANNEL channel, PS4000A_BANDWIDTH_LIMITER bandwidth ); """ -ps4000a.make_symbol("_SetBandwidthFilter", "ps4000aSetBandwidthFilter", c_uint32, [c_int16, c_int32, c_int32], doc) +ps4000a.make_symbol("_set_bandwidth_filter", "ps4000aSetBandwidthFilter", c_uint32, [c_int16, c_int32, c_int32], doc) doc = """ PICO_STATUS ps4000aApplyResistanceScaling ( @@ -448,7 +453,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t buffertLth, int16_t *overflow ); """ -ps4000a.make_symbol("_ApplyResistanceScaling", "ps4000aApplyResistanceScaling", c_uint32, +ps4000a.make_symbol("_apply_resistance_scaling", "ps4000aApplyResistanceScaling", c_uint32, [c_int16, c_int32, c_int32, c_int16, c_int16, c_uint32, c_int16], doc) doc = """ PICO_STATUS ps4000aGetTimebase @@ -460,7 +465,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int32_t *maxSamples, uint32_t segmentIndex ); """ -ps4000a.make_symbol('_GetTimebase', 'ps4000aGetTimebase', c_uint32, +ps4000a.make_symbol('_get_timebase', 'ps4000aGetTimebase', c_uint32, [c_int16, c_uint32, c_int32, c_void_p, c_void_p, c_uint32], doc) doc = """ PICO_STATUS ps4000aGetTimebase2 @@ -472,7 +477,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int32_t *maxSamples, uint32_t segmentIndex ); """ -ps4000a.make_symbol("_GetTimebase2", "ps4000aGetTimebase2", c_uint32, +ps4000a.make_symbol("_get_timebase2", "ps4000aGetTimebase2", c_uint32, [c_int16, c_uint32, c_int32, c_void_p, c_void_p, c_uint32], doc) doc = """ PICO_STATUS ps4000aSetSigGenArbitrary @@ -495,7 +500,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_SIGGEN_TRIG_SOURCE triggerSource, int16_t extInThreshold ); """ -ps4000a.make_symbol("_SetSigGenArbitrary", "ps4000aSetSigGenArbitrary", c_uint32, +ps4000a.make_symbol("_set_sig_gen_arbitrary", "ps4000aSetSigGenArbitrary", c_uint32, [c_int16, c_int32, c_uint32, c_uint32, c_uint32, c_uint32, c_uint32, c_void_p, c_int32, c_int32, c_int32, c_int32, c_uint32, c_uint32, c_int32, c_int32, c_int16], doc) @@ -517,7 +522,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_SIGGEN_TRIG_SOURCE triggerSource, int16_t extInThreshold ); """ -ps4000a.make_symbol("_SetSigGenBuiltIn", "ps4000aSetSigGenBuiltIn", c_uint32, +ps4000a.make_symbol("_set_sig_gen_built_in", "ps4000aSetSigGenBuiltIn", c_uint32, [c_int16, c_int32, c_uint32, c_int32, c_double, c_double, c_double, c_double, c_int32, c_int32, c_uint32, c_uint32, c_int32, c_int32, c_int16], doc) @@ -535,7 +540,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_SIGGEN_TRIG_SOURCE triggerSource, int16_t extInThreshold ); """ -ps4000a.make_symbol("_SetSigGenPropertiesArbitrary", "ps4000aSetSigGenPropertiesArbitrary", c_uint32, +ps4000a.make_symbol("_set_sig_gen_properties_arbitrary", "ps4000aSetSigGenPropertiesArbitrary", c_uint32, [c_int16, c_uint32, c_uint32, c_uint32, c_uint32, c_int32, c_uint32, c_uint32, c_int32, c_int32, c_int16], doc) @@ -553,7 +558,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_SIGGEN_TRIG_SOURCE triggerSource, int16_t extInThreshold ); """ -ps4000a.make_symbol("_SetSigGenPropertiesBuiltIn", "ps4000aSetSigGenPropertiesBuiltIn", c_uint32, +ps4000a.make_symbol("_set_sig_gen_properties_built_in", "ps4000aSetSigGenPropertiesBuiltIn", c_uint32, [c_int16, c_double, c_double, c_double, c_double, c_int32, c_uint32, c_uint32, c_int32, c_int32, c_int16], doc) @@ -565,7 +570,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t bufferLength, uint32_t *phase ); """ -ps4000a.make_symbol("_SigGenFrequencyToPhase", "ps4000aSigGenFrequencyToPhase", c_uint32, +ps4000a.make_symbol("_sig_gen_frequency_to_phase", "ps4000aSigGenFrequencyToPhase", c_uint32, [c_int16, c_double, c_int32, c_uint32, c_void_p], doc) doc = """ PICO_STATUS ps4000aSigGenArbitraryMinMaxValues @@ -576,7 +581,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t *minArbitraryWaveformSize, uint32_t *maxArbitraryWaveformSize ); """ -ps4000a.make_symbol("_SigGenArbitraryMinMaxValues", "ps4000aSigGenArbitraryMinMaxValues", c_uint32, +ps4000a.make_symbol("_sig_gen_arbitrary_min_max_values", "ps4000aSigGenArbitraryMinMaxValues", c_uint32, [c_int16, c_void_p, c_void_p, c_void_p, c_void_p], doc) doc = """ PICO_STATUS ps4000aSigGenSoftwareControl @@ -584,7 +589,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t handle, int16_t state );""" -ps4000a.make_symbol("_SigGenSoftwareControl", "ps4000aSigGenSoftwareControl", c_uint32, [c_int16, c_int16], doc) +ps4000a.make_symbol("_sig_gen_software_control", "ps4000aSigGenSoftwareControl", c_uint32, [c_int16, c_int16], doc) doc = """ PICO_STATUS ps4000aSetEts ( @@ -594,7 +599,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t etsInterleave, int32_t *sampleTimePicoseconds ); """ -ps4000a.make_symbol("_SetEts", "ps4000aSetEts", c_uint32, [c_int16, c_int32, c_int16, c_int16, c_void_p], doc) +ps4000a.make_symbol("_set_ets", "ps4000aSetEts", c_uint32, [c_int16, c_int32, c_int16, c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aSetTriggerChannelProperties ( @@ -604,7 +609,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t auxOutputEnable, int32_t autoTriggerMilliseconds ); """ -ps4000a.make_symbol("_SetTriggerChannelProperties", "ps4000aSetTriggerChannelProperties", c_uint32, +ps4000a.make_symbol("_set_trigger_channel_properties", "ps4000aSetTriggerChannelProperties", c_uint32, [c_int16, c_void_p, c_int16, c_int16, c_int32], doc) doc = """ PICO_STATUS ps4000aSetTriggerChannelConditions @@ -614,7 +619,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t nConditions, PS4000A_CONDITIONS_INFO info ); """ -ps4000a.make_symbol("_SetTriggerChannelConditions", "ps4000aSetTriggerChannelConditions", c_uint32, +ps4000a.make_symbol("_set_trigger_channel_conditions", "ps4000aSetTriggerChannelConditions", c_uint32, [c_int16, c_void_p, c_int16, c_int32], doc) doc = """ PICO_STATUS ps4000aSetTriggerChannelDirections @@ -623,7 +628,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_DIRECTION *directions, int16_t nDirections ); """ -ps4000a.make_symbol("_SetTriggerChannelDirections", "ps4000aSetTriggerChannelDirections", c_uint32, +ps4000a.make_symbol("_set_trigger_channel_directions", "ps4000aSetTriggerChannelDirections", c_uint32, [c_int16, c_void_p, c_int16], doc) doc = """ PICO_STATUS ps4000aSetSimpleTrigger @@ -636,7 +641,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t delay, int16_t autoTrigger_ms ); """ -ps4000a.make_symbol("_SetSimpleTrigger", "ps4000aSetSimpleTrigger", c_uint32, +ps4000a.make_symbol("_set_simple_trigger", "ps4000aSetSimpleTrigger", c_uint32, [c_int16, c_int16, c_int32, c_int16, c_int32, c_uint32, c_int16], doc) doc = """ PICO_STATUS ps4000aSetTriggerDelay @@ -644,7 +649,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t handle, uint32_t delay ); """ -ps4000a.make_symbol("_SetTriggerDelay", "ps4000aSetTriggerDelay", c_uint32, [c_int16, c_uint32], doc) +ps4000a.make_symbol("_set_trigger_delay", "ps4000aSetTriggerDelay", c_uint32, [c_int16, c_uint32], doc) doc = """ PICO_STATUS ps4000aSetPulseWidthQualifierProperties ( @@ -654,7 +659,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t upper, PS4000A_PULSE_WIDTH_TYPE type ); """ -ps4000a.make_symbol("_SetPulseWidthQualifierProperties", "ps4000aSetPulseWidthQualifierProperties", c_uint32, +ps4000a.make_symbol("_set_pulse_width_qualifier_properties", "ps4000aSetPulseWidthQualifierProperties", c_uint32, [c_int16, c_int32, c_uint32, c_uint32, c_int32], doc) doc = """ PICO_STATUS ps4000aSetPulseWidthQualifierConditions @@ -664,7 +669,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t nConditions, PS4000A_CONDITIONS_INFO info ); """ -ps4000a.make_symbol("_SetPulseWidthQualifierConditions", "ps4000aSetPulseWidthQualifierConditions", c_uint32, +ps4000a.make_symbol("_set_pulse_width_qualifier_conditions", "ps4000aSetPulseWidthQualifierConditions", c_uint32, [c_int16, c_void_p, c_int16, c_int32], doc) doc = """ PICO_STATUS ps4000aIsTriggerOrPulseWidthQualifierEnabled @@ -673,7 +678,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t *triggerEnabled, int16_t *pulseWidthQualifierEnabled ); """ -ps4000a.make_symbol("_IsTriggerOrPulseWidthQualifierEnabled", "ps4000aIsTriggerOrPulseWidthQualifierEnabled", c_uint32, +ps4000a.make_symbol("_is_trigger_or_pulse_width_qualifier_enabled", "ps4000aIsTriggerOrPulseWidthQualifierEnabled", c_uint32, [c_int16, c_void_p, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetTriggerTimeOffset @@ -684,7 +689,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_TIME_UNITS *timeUnits, uint32_t segmentIndex ); """ -ps4000a.make_symbol("_GetTriggerTimeOffset", "ps4000aGetTriggerTimeOffset", c_uint32, +ps4000a.make_symbol("_get_trigger_time_offset", "ps4000aGetTriggerTimeOffset", c_uint32, [c_int16, c_void_p, c_void_p, c_void_p, c_uint32], doc) doc = """ PICO_STATUS ps4000aGetTriggerTimeOffset64 @@ -694,7 +699,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_TIME_UNITS *timeUnits, uint32_t segmentIndex ); """ -ps4000a.make_symbol("_GetTriggerTimeOffset64", "ps4000aGetTriggerTimeOffset64", c_uint32, +ps4000a.make_symbol("_get_trigger_time_offset64", "ps4000aGetTriggerTimeOffset64", c_uint32, [c_int16, c_void_p, c_void_p, c_uint32], doc) doc = """ PICO_STATUS ps4000aGetValuesTriggerTimeOffsetBulk @@ -706,7 +711,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t fromSegmentIndex, uint32_t toSegmentIndex ); """ -ps4000a.make_symbol("_GetValuesTriggerTimeOffsetBulk", "ps4000aGetValuesTriggerTimeOffsetBulk", c_uint32, +ps4000a.make_symbol("_get_values_trigger_time_offset_bulk", "ps4000aGetValuesTriggerTimeOffsetBulk", c_uint32, [c_int16, c_void_p, c_void_p, c_void_p, c_uint32, c_uint32], doc) doc = """ PICO_STATUS ps4000aGetValuesTriggerTimeOffsetBulk64 @@ -717,7 +722,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t fromSegmentIndex, uint32_t toSegmentIndex ); """ -ps4000a.make_symbol("_GetValuesTriggerTimeOffsetBulk64", "ps4000aGetValuesTriggerTimeOffsetBulk64", c_uint32, +ps4000a.make_symbol("_get_values_trigger_time_offset_bulk64", "ps4000aGetValuesTriggerTimeOffsetBulk64", c_uint32, [c_int16, c_void_p, c_void_p, c_uint32, c_uint32], doc) doc = """ PICO_STATUS ps4000aSetDataBuffers @@ -730,7 +735,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t segmentIndex, PS4000A_RATIO_MODE mode ); """ -ps4000a.make_symbol("_SetDataBuffers", "ps4000aSetDataBuffers", c_uint32, +ps4000a.make_symbol("_set_data_buffers", "ps4000aSetDataBuffers", c_uint32, [c_int16, c_int32, c_void_p, c_void_p, c_int32, c_uint32, c_int32], doc) doc = """ PICO_STATUS ps4000aSetDataBuffer @@ -742,7 +747,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t segmentIndex, PS4000A_RATIO_MODE mode ); """ -ps4000a.make_symbol("_SetDataBuffer", "ps4000aSetDataBuffer", c_uint32, +ps4000a.make_symbol("_set_data_buffer", "ps4000aSetDataBuffer", c_uint32, [c_int16, c_int32, c_void_p, c_int32, c_uint32, c_int32], doc) doc = """ PICO_STATUS ps4000aSetEtsTimeBuffer @@ -751,7 +756,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int64_t *buffer, int32_t bufferLth ); """ -ps4000a.make_symbol("_SetEtsTimeBuffer", "ps4000aSetEtsTimeBuffer", c_uint32, [c_int16, c_void_p, c_int32], doc) +ps4000a.make_symbol("_set_ets_time_buffer", "ps4000aSetEtsTimeBuffer", c_uint32, [c_int16, c_void_p, c_int32], doc) doc = """ PICO_STATUS ps4000aSetEtsTimeBuffers ( @@ -760,7 +765,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t *timeLower, int32_t bufferLth ); """ -ps4000a.make_symbol("_SetEtsTimeBuffers", "ps4000aSetEtsTimeBuffers", c_uint32, [c_int16, c_void_p, c_void_p, c_int32], +ps4000a.make_symbol("_set_ets_time_buffers", "ps4000aSetEtsTimeBuffers", c_uint32, [c_int16, c_void_p, c_void_p, c_int32], doc) doc = """ PICO_STATUS ps4000aIsReady @@ -768,7 +773,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t handle, int16_t *ready ); """ -ps4000a.make_symbol("_IsReady", "ps4000aIsReady", c_uint32, [c_int16, c_void_p], doc) +ps4000a.make_symbol("_is_ready", "ps4000aIsReady", c_uint32, [c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aRunBlock ( @@ -781,7 +786,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): ps4000aBlockReady lpReady, void *pParameter ); """ -ps4000a.make_symbol("_RunBlock", "ps4000aRunBlock", c_uint32, +ps4000a.make_symbol("_run_block", "ps4000aRunBlock", c_uint32, [c_int16, c_int32, c_int32, c_uint32, c_void_p, c_uint32, c_void_p, c_void_p], doc) doc = """ PICO_STATUS ps4000aRunStreaming @@ -796,7 +801,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_RATIO_MODE downSampleRatioMode, uint32_t overviewBufferSize ); """ -ps4000a.make_symbol("_RunStreaming", "ps4000aRunStreaming", c_uint32, +ps4000a.make_symbol("_run_streaming", "ps4000aRunStreaming", c_uint32, [c_int16, c_void_p, c_int32, c_uint32, c_uint32, c_int16, c_uint32, c_int32, c_uint32], doc) doc = """ PICO_STATUS ps4000aGetStreamingLatestValues @@ -805,7 +810,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): ps4000aStreamingReady lpPs4000aReady, void *pParameter ); """ -ps4000a.make_symbol("_GetStreamingLatestValues", "ps4000aGetStreamingLatestValues", c_uint32, +ps4000a.make_symbol("_get_streaming_latest_values", "ps4000aGetStreamingLatestValues", c_uint32, [c_int16, c_void_p, c_void_p], doc) doc = """ void *ps4000aStreamingReady @@ -840,7 +845,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t handle, uint32_t *noOfValues ); """ -ps4000a.make_symbol("_NoOfStreamingValues", "ps4000aNoOfStreamingValues", c_uint32, [c_int16, c_void_p], doc) +ps4000a.make_symbol("_no_of_streaming_values", "ps4000aNoOfStreamingValues", c_uint32, [c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetMaxDownSampleRatio ( @@ -850,7 +855,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_RATIO_MODE downSampleRatioMode, uint32_t segmentIndex ); """ -ps4000a.make_symbol("_GetMaxDownSampleRatio", "ps4000aGetMaxDownSampleRatio", c_uint32, +ps4000a.make_symbol("_get_max_down_sample_ratio", "ps4000aGetMaxDownSampleRatio", c_uint32, [c_int16, c_uint32, c_void_p, c_int32, c_uint32], doc) doc = """ PICO_STATUS ps4000aGetValues @@ -863,7 +868,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t segmentIndex, int16_t *overflow ); """ -ps4000a.make_symbol("_GetValues", "ps4000aGetValues", c_uint32, +ps4000a.make_symbol("_get_values", "ps4000aGetValues", c_uint32, [c_int16, c_uint32, c_void_p, c_uint32, c_int32, c_uint32, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetValuesAsync @@ -877,7 +882,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): void *lpDataReady, void *pParameter ); """ -ps4000a.make_symbol("_GetValuesAsync", "ps4000aGetValuesAsync", c_uint32, +ps4000a.make_symbol("_get_values_async", "ps4000aGetValuesAsync", c_uint32, [c_int16, c_uint32, c_uint32, c_uint32, c_int32, c_uint32, c_void_p, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetValuesBulk @@ -890,7 +895,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_RATIO_MODE downSampleRatioMode, int16_t *overflow ); """ -ps4000a.make_symbol("_GetValuesBulk", "ps4000aGetValuesBulk", c_uint32, +ps4000a.make_symbol("_get_values_bulk", "ps4000aGetValuesBulk", c_uint32, [c_int16, c_void_p, c_uint32, c_uint32, c_uint32, c_int32, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetValuesOverlapped @@ -903,7 +908,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t segmentIndex, int16_t *overflow ); """ -ps4000a.make_symbol("_GetValuesOverlapped", "ps4000aGetValuesOverlapped", c_uint32, +ps4000a.make_symbol("_get_values_overlapped", "ps4000aGetValuesOverlapped", c_uint32, [c_int16, c_uint32, c_void_p, c_uint32, c_int32, c_uint32, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetValuesOverlappedBulk @@ -917,7 +922,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): uint32_t toSegmentIndex, int16_t *overflow ); """ -ps4000a.make_symbol("_GetValuesOverlappedBulk", "ps4000aGetValuesOverlappedBulk", c_uint32, +ps4000a.make_symbol("_get_values_overlapped_bulk", "ps4000aGetValuesOverlappedBulk", c_uint32, [c_int16, c_uint32, c_void_p, c_uint32, c_int32, c_uint32, c_uint32, c_void_p], doc) doc = """ PICO_STATUS ps4000aEnumerateUnits @@ -926,7 +931,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int8_t *serials, int16_t *serialLth ); """ -ps4000a.make_symbol("_EnumerateUnits", "ps4000aEnumerateUnits", c_uint32, [c_void_p, c_void_p, c_void_p], doc) +ps4000a.make_symbol("_enumerate_units", "ps4000aEnumerateUnits", c_uint32, [c_void_p, c_void_p, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetChannelInformation ( @@ -937,7 +942,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int32_t *length, int32_t channels ); """ -ps4000a.make_symbol("_GetChannelInformation", "ps4000aGetChannelInformation", c_uint32, +ps4000a.make_symbol("_get_channel_information", "ps4000aGetChannelInformation", c_uint32, [c_int16, c_int32, c_int32, c_void_p, c_void_p, c_int32], doc) doc = """ PICO_STATUS ps4000aConnectDetect @@ -946,21 +951,21 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_CONNECT_DETECT *sensor, int16_t nSensors ); """ -ps4000a.make_symbol("_ConnectDetect", "ps4000aConnectDetect", c_uint32, [c_int16, c_void_p, c_int16], doc) +ps4000a.make_symbol("_connect_detect", "ps4000aConnectDetect", c_uint32, [c_int16, c_void_p, c_int16], doc) doc = """ PICO_STATUS ps4000aMaximumValue ( int16_t handle, int16_t *value ); """ -ps4000a.make_symbol("_MaximumValue", "ps4000aMaximumValue", c_uint32, [c_int16, c_void_p], doc) +ps4000a.make_symbol("_maximum_value", "ps4000aMaximumValue", c_uint32, [c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aMinimumValue ( - int16_t handle, + int16_t handle, int16_t * value ); """ -ps4000a.make_symbol("_MinimumValue", "ps4000aMinimumValue", c_uint32, [c_int16, c_void_p], doc) +ps4000a.make_symbol("_minimum_value", "ps4000aMinimumValue", c_uint32, [c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetAnalogueOffset ( @@ -970,7 +975,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): float *maximumVoltage, float *minimumVoltage ); """ -ps4000a.make_symbol("_GetAnalogueOffset", "ps4000aGetAnalogueOffset", c_uint32, +ps4000a.make_symbol("_get_analogue_offset", "ps4000aGetAnalogueOffset", c_uint32, [c_int16, c_int32, c_int32, c_void_p, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetMaxSegments @@ -978,53 +983,53 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t handle, uint32_t *maxSegments ); """ -ps4000a.make_symbol("_GetMaxSegments", "ps4000aGetMaxSegments", c_uint32, [c_int16, c_void_p], doc) +ps4000a.make_symbol("_get_max_segments", "ps4000aGetMaxSegments", c_uint32, [c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aChangePowerSource ( int16_t handle, PICO_STATUS powerState ); """ -ps4000a.make_symbol("_ChangePowerSource", "ps4000aChangePowerSource", c_uint32, [c_int16, c_uint32], doc) +ps4000a.make_symbol("_change_power_source", "ps4000aChangePowerSource", c_uint32, [c_int16, c_uint32], doc) doc = """ PICO_STATUS ps4000aCurrentPowerSource ( int16_t handle ); """ -ps4000a.make_symbol("_CurrentPowerSource", "ps4000aCurrentPowerSource", c_uint32, [c_int16, ], doc) +ps4000a.make_symbol("_current_power_source", "ps4000aCurrentPowerSource", c_uint32, [c_int16, ], doc) doc = """ PICO_STATUS ps4000aStop ( int16_t handle ); """ -ps4000a.make_symbol("_Stop", "ps4000aStop", c_uint32, [c_int16, ], doc) +ps4000a.make_symbol("_stop", "ps4000aStop", c_uint32, [c_int16, ], doc) doc = """ PICO_STATUS ps4000aPingUnit ( int16_t handle ); """ -ps4000a.make_symbol("_PingUnit", "ps4000aPingUnit", c_uint32, [c_int16, ], doc) +ps4000a.make_symbol("_ping_unit", "ps4000aPingUnit", c_uint32, [c_int16, ], doc) doc = """ PICO_STATUS ps4000aSetNoOfCaptures ( int16_t handle, uint32_t nCaptures ); """ -ps4000a.make_symbol("_SetNoOfCaptures", "ps4000aSetNoOfCaptures", c_uint32, [c_int16, c_uint32], doc) +ps4000a.make_symbol("_set_no_of_captures", "ps4000aSetNoOfCaptures", c_uint32, [c_int16, c_uint32], doc) doc = """ PICO_STATUS ps4000aGetNoOfCaptures ( int16_t handle, uint32_t *nCaptures ); """ -ps4000a.make_symbol("_GetNoOfCaptures", "ps4000aGetNoOfCaptures", c_uint32, [c_int16, c_void_p], doc) +ps4000a.make_symbol("_get_no_of_captures", "ps4000aGetNoOfCaptures", c_uint32, [c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetNoOfProcessedCaptures ( int16_t handle, uint32_t *nProcessedCaptures ); """ -ps4000a.make_symbol("_GetNoOfProcessedCaptures", "ps4000aGetNoOfProcessedCaptures", c_uint32, [c_int16, c_void_p], doc) +ps4000a.make_symbol("_get_no_of_processed_captures", "ps4000aGetNoOfProcessedCaptures", c_uint32, [c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aDeviceMetaData ( @@ -1035,7 +1040,7 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): PS4000A_META_OPERATION operation, PS4000A_META_FORMAT format ); """ -ps4000a.make_symbol("_DeviceMetaData", "ps4000aDeviceMetaData", c_uint32, +ps4000a.make_symbol("_device_meta_data", "ps4000aDeviceMetaData", c_uint32, [c_int16, c_void_p, c_void_p, c_int32, c_int32, c_int32], doc) doc = """ PICO_STATUS ps4000aGetString @@ -1045,14 +1050,14 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int8_t *string, int32_t *stringLength ); """ -ps4000a.make_symbol("_GetString", "ps4000aGetString", c_uint32, [c_int16, c_int32, c_void_p, c_void_p], doc) +ps4000a.make_symbol("_get_string", "ps4000aGetString", c_uint32, [c_int16, c_int32, c_void_p, c_void_p], doc) doc = """ PICO_STATUS ps4000aGetCommonModeOverflow ( int16_t handle, uint16_t *overflow ); """ -ps4000a.make_symbol("_GetCommonModeOverflow", "ps4000aGetCommonModeOverflow", c_uint32, [c_int16, c_void_p], doc) +ps4000a.make_symbol("_get_common_mode_overflow", "ps4000aGetCommonModeOverflow", c_uint32, [c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aSetFrequencyCounter ( @@ -1063,44 +1068,44 @@ class PS4000A_TRIGGER_CHANNEL_PROPERTIES(Structure): int16_t thresholdMajor, int16_t thresholdMinor ); """ -ps4000a.make_symbol("_SetFrequencyCounter", "ps4000aSetFrequencyCounter", c_uint32, +ps4000a.make_symbol("_set_frequency_counter", "ps4000aSetFrequencyCounter", c_uint32, [c_int16, c_int32, c_int16, c_int32, c_int16, c_int16], doc) doc = """ PICO_STATUS ps4000aOpenUnitWithResolution ( - int16_t *handle, - int8_t *serial, - PS4000A_DEVICE_RESOLUTION resolution - ); """ -ps4000a.make_symbol("_OpenUnitWithResolution", "ps4000aOpenUnitWithResolution", c_uint32, [c_void_p, c_void_p, c_int32], doc) + int16_t *handle, + int8_t *serial, + PS4000A_DEVICE_RESOLUTION resolution + ); """ +ps4000a.make_symbol("_open_unit_with_resolution", "ps4000aOpenUnitWithResolution", c_uint32, [c_void_p, c_void_p, c_int32], doc) doc = """ PICO_STATUS ps4000aGetDeviceResolution ( - int16_t handle, - PS4000A_DEVICE_RESOLUTION *resolution - ); """ -ps4000a.make_symbol("_GetResolution", "ps4000aGetDeviceResolution", c_uint32, [c_int16, c_void_p], doc) + int16_t handle, + PS4000A_DEVICE_RESOLUTION *resolution + ); """ +ps4000a.make_symbol("_get_resolution", "ps4000aGetDeviceResolution", c_uint32, [c_int16, c_void_p], doc) doc = """ PICO_STATUS ps4000aSetDeviceResolution ( - int16_t handle, - PS4000A_DEVICE_RESOLUTION resolution - ); """ -ps4000a.make_symbol("_SetResolution", "ps4000aSetDeviceResolution", c_uint32, [c_int16, c_int32], doc) + int16_t handle, + PS4000A_DEVICE_RESOLUTION resolution + ); """ +ps4000a.make_symbol("_set_resolution", "ps4000aSetDeviceResolution", c_uint32, [c_int16, c_int32], doc) doc = """ PICO_STATUS ps4000aSetProbeInteractionCallback ( - int16_t handle, - ps4000aProbeInteractions callback - ); """ -ps4000a.make_symbol("_SetProbeInteractionCallback", "ps4000aSetProbeInteractionCallback", c_uint32, [c_int16, c_void_p], doc) + int16_t handle, + ps4000aProbeInteractions callback + ); """ +ps4000a.make_symbol("_set_probe_interaction_callback", "ps4000aSetProbeInteractionCallback", c_uint32, [c_int16, c_void_p], doc) doc = """ void *ps4000aProbeInteractions ( - int16_t handle, - PICO_STATUS status, - PS4000A_USER_PROBE_INTERACTIONS * probes, - uint32_t nProbes + int16_t handle, + PICO_STATUS status, + PS4000A_USER_PROBE_INTERACTIONS * probes, + uint32_t nProbes ); define a python function which accepts the correct arguments, and pass it to the constructor of this type. """ diff --git a/picosdk/ps5000.py b/picosdk/ps5000.py index 131169b..6455a52 100644 --- a/picosdk/ps5000.py +++ b/picosdk/ps5000.py @@ -54,7 +54,7 @@ def __init__(self): "PS5000_S", "PS5000_MAX_TIME_UNITS", ]) - + class PWQ_CONDITIONS (Structure): _pack_ = 1 @@ -76,7 +76,7 @@ class TRIGGER_CONDITIONS (Structure): ("external", c_int32), ("aux", c_int32), ("pulseWidthQualifier", c_int32)] - + ps5000.TRIGGER_CONDITIONS = TRIGGER_CONDITIONS class TRIGGER_CHANNEL_PROPERTIES (Structure): @@ -86,9 +86,9 @@ class TRIGGER_CHANNEL_PROPERTIES (Structure): ("hysteresis", c_uint16), ("channel", c_int32), ("thresholdMode", c_int32)] - + ps5000.TRIGGER_CHANNEL_PROPERTIES = TRIGGER_CHANNEL_PROPERTIES - + doc = """ PICO_STATUS ps5000CloseUnit ( short handle @@ -406,8 +406,8 @@ class TRIGGER_CHANNEL_PROPERTIES (Structure): SIGGEN_TRIG_SOURCE triggerSource, short extInThreshold ); """ -ps5000.make_symbol("_SetSigGenArbitrary", "ps5000SetSigGenArbitrary", c_uint32, - [c_int16, c_int32, c_uint32, c_uint32, c_uint32, c_uint32, c_uint32, c_void_p, +ps5000.make_symbol("_SetSigGenArbitrary", "ps5000SetSigGenArbitrary", c_uint32, + [c_int16, c_int32, c_uint32, c_uint32, c_uint32, c_uint32, c_uint32, c_void_p, c_int32, c_int32, c_int16, c_int32, c_uint32, c_uint32, c_int32, c_int32, c_int16], doc) doc = """ PICO_STATUS ps5000SetSigGenBuiltIn @@ -428,7 +428,7 @@ class TRIGGER_CHANNEL_PROPERTIES (Structure): SIGGEN_TRIG_SOURCE triggerSource, short extInThreshold ); """ -ps5000.make_symbol("_SetSigGenBuiltIn", "ps5000SetSigGenBuiltIn", c_uint32, +ps5000.make_symbol("_SetSigGenBuiltIn", "ps5000SetSigGenBuiltIn", c_uint32, [c_int16, c_int32, c_uint32, c_int16, c_int64, c_int64, c_int64, c_int64, c_int64, c_int32, c_int16, c_uint32, c_uint32, c_int32, c_int32, c_int16], doc) doc = """ PICO_STATUS ps5000SetSimpleTrigger @@ -504,7 +504,7 @@ class TRIGGER_CHANNEL_PROPERTIES (Structure): c_int16, c_uint32, c_void_p) - + ps5000.BlockReadyType.__doc__ = doc doc = """ void (CALLBACK *ps5000DataReady) @@ -516,7 +516,7 @@ class TRIGGER_CHANNEL_PROPERTIES (Structure): short triggered, void *pParameter ); """ - + ps5000.DataReadyType = C_CALLBACK_FUNCTION_FACTORY(None, c_int16, c_int32, @@ -524,7 +524,7 @@ class TRIGGER_CHANNEL_PROPERTIES (Structure): c_uint32, c_int16, c_void_p) - + ps5000.DataReadyType.__doc__ = doc doc = """ void (CALLBACK *ps5000StreamingReady) @@ -542,11 +542,11 @@ class TRIGGER_CHANNEL_PROPERTIES (Structure): ps5000.StreamingReadyType = C_CALLBACK_FUNCTION_FACTORY(None, c_int16, c_int32, - c_uint32, + c_uint32, c_int16, c_uint32, c_int16, c_int16, c_void_p) - + ps5000.StreamingReadyType.__doc__ = doc diff --git a/test/test_helpers.py b/test/test_helpers.py index 02184d1..9d546ff 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -39,11 +39,11 @@ class TestFailAndError(Exception): - pass + __test__ = False class TestError(Exception): - pass + __test__ = False class DriverTest(_unittest.TestCase): diff --git a/test/test_timebase.py b/test/test_timebase.py index b5c8b43..29d8533 100644 --- a/test/test_timebase.py +++ b/test/test_timebase.py @@ -18,11 +18,11 @@ class FindTimebaseTest(DriverTest): def assertValidTimebases(self, input_config, output_info): if input_config.max_time_interval is not None: - self.assertLessEqual(output_info.time_interval, input_config.max_time_interval) + self.assertLessEqual(output_info.time_interval_ns, input_config.max_time_interval) if input_config.no_of_samples is not None: self.assertGreaterEqual(output_info.max_samples, input_config.no_of_samples) if input_config.min_collection_time is not None: - self.assertGreaterEqual(output_info.time_interval * output_info.max_samples, + self.assertGreaterEqual(output_info.time_interval_ns * output_info.max_samples, input_config.min_collection_time) def test_find_timebase_success(self): @@ -134,10 +134,11 @@ def test_valid_config(self): request = TimebaseOptions(max_time_interval=0.005, no_of_samples=None, min_collection_time=1.) - actual_timebase = 0.004 - required_max_samples = int(math.ceil(request.min_collection_time / actual_timebase)) + actual_timebase_s = 0.004 + actual_timebase_ns = 0.004 * 1e9 + required_max_samples = int(math.ceil(request.min_collection_time / actual_timebase_s)) response = TimebaseInfo(timebase_id=7, - time_interval=0.004, + time_interval_ns=actual_timebase_ns, time_units=None, max_samples=required_max_samples+1, segment_id=0) @@ -148,10 +149,11 @@ def test_invalid_config(self): request = TimebaseOptions(max_time_interval=0.005, no_of_samples=None, min_collection_time=1.) - actual_timebase = 0.004 - required_max_samples = int(math.ceil(request.min_collection_time / actual_timebase)) + actual_timebase_s = 0.004 + actual_timebase_ns = 0.004 * 1e9 + required_max_samples = int(math.ceil(request.min_collection_time / actual_timebase_s)) response = TimebaseInfo(timebase_id=7, - time_interval=0.004, + time_interval_ns=actual_timebase_ns, time_units=None, max_samples=required_max_samples-5, segment_id=0)