Source code for instruments.rigol.rigolds1000

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Provides support for Rigol DS-1000 series oscilloscopes.
"""

# IMPORTS #####################################################################

from __future__ import absolute_import
from __future__ import division
from builtins import range

from enum import Enum

from instruments.abstract_instruments import (
    Oscilloscope, OscilloscopeChannel, OscilloscopeDataSource
)
from instruments.generic_scpi import SCPIInstrument
from instruments.util_fns import ProxyList, bool_property, enum_property

# CLASSES #####################################################################


[docs]class RigolDS1000Series(SCPIInstrument, Oscilloscope): """ The Rigol DS1000-series is a popular budget oriented oscilloscope that has featured wide adoption across hobbyist circles. .. warning:: This instrument is not complete, and probably not even functional! """ # ENUMS #
[docs] class AcquisitionType(Enum): """ Enum containing valid acquisition types for the Rigol DS1000 """ normal = "NORM" average = "AVER" peak_detect = "PEAK"
[docs] class Coupling(Enum): """ Enum containing valid coupling modes for the Rigol DS1000 """ ac = "AC" dc = "DC" ground = "GND"
# INNER CLASSES #
[docs] class DataSource(OscilloscopeDataSource): """ Class representing a data source (channel, math, or ref) on the Rigol DS1000 .. warning:: This class should NOT be manually created by the user. It is designed to be initialized by the `RigolDS1000Series` class. """ @property def name(self): return self._name
[docs] def read_waveform(self, bin_format=True): # TODO: add DIG, FFT. if self.name not in ["CHAN1", "CHAN2", "DIG", "MATH", "FFT"]: raise NotImplementedError("Rigol DS1000 series does not " "supportreading waveforms from " "{}.".format(self.name)) self._parent.sendcmd(":WAV:DATA? {}".format(self.name)) data = self._parent.binblockread(2) # TODO: check width return data
[docs] class Channel(DataSource, OscilloscopeChannel): """ Class representing a channel on the Rigol DS1000. This class inherits from `~RigolDS1000Series.DataSource`. .. warning:: This class should NOT be manually created by the user. It is designed to be initialized by the `RigolDS1000Series` class. """ def __init__(self, parent, idx): self._parent = parent self._idx = idx + 1 # Rigols are 1-based. # Initialize as a data source with name CHAN{}. super(RigolDS1000Series.Channel, self).__init__( self._parent, "CHAN{}".format(self._idx))
[docs] def sendcmd(self, cmd): """ Passes a command from the `Channel` class to the parent `RigolDS1000Series`, appending the required channel identification. :param str cmd: The command string to send to the instrument """ self._parent.sendcmd(":CHAN{}:{}".format(self._idx, cmd))
[docs] def query(self, cmd): """ Passes a command from the `Channel` class to the parent `RigolDS1000Series`, appending the required channel identification. :param str cmd: The command string to send to the instrument :return: The result as returned by the instrument :rtype: `str` """ return self._parent.query(":CHAN{}:{}".format(self._idx, cmd))
coupling = enum_property("COUP", lambda: RigolDS1000Series.Coupling) bw_limit = bool_property("BWL", "ON", "OFF") display = bool_property("DISP", "ON", "OFF") invert = bool_property("INV", "ON", "OFF") # TODO: :CHAN<n>:OFFset # TODO: :CHAN<n>:PROBe # TODO: :CHAN<n>:SCALe filter = bool_property("FILT", "ON", "OFF") # TODO: :CHAN<n>:MEMoryDepth vernier = bool_property("VERN", "ON", "OFF")
# PROPERTIES # @property def channel(self): # Rigol DS1000 series oscilloscopes all have two channels, # according to the documentation. return ProxyList(self, self.Channel, range(2)) @property def math(self): return self.DataSource(parent=self, name="MATH") @property def ref(self): return self.DataSource(parent=self, name="REF") acquire_type = enum_property(":ACQ:TYPE", AcquisitionType) # TODO: implement :ACQ:MODE. This is confusing in the documentation, # though. @property def acquire_averages(self): """ Gets/sets the number of averages the oscilloscope should take per acquisition. :type: `int` """ return int(self.query(":ACQ:AVER?")) @acquire_averages.setter def acquire_averages(self, newval): if newval not in [2**i for i in range(1, 9)]: raise ValueError( "Number of averages {} not supported by instrument; " "must be a power of 2 from 2 to 256.".format(newval) ) self.sendcmd(":ACQ:AVER {}".format(newval)) # TODO: implement :ACQ:SAMP in a meaningful way. This should probably be # under Channel, and needs to be unitful. # TODO: I don't understand :ACQ:MEMD yet. # METHODS ##
[docs] def force_trigger(self): self.sendcmd(":FORC")
# TODO: consider moving the next few methods to Oscilloscope.
[docs] def run(self): """ Starts running the oscilloscope trigger. """ self.sendcmd(":RUN")
[docs] def stop(self): """ Stops running the oscilloscope trigger. """ self.sendcmd(":STOP")
# TODO: unitful timebase! # FRONT-PANEL KEY EMULATION METHODS ## # These methods correspond one-to-one with physical keys on the front # (local) control panel, except for release_panel, which enables the local # panel and disables any remote lockouts, and for panel_locked. # # Many of the :KEY: commands are not yet implemented as methods. panel_locked = bool_property(":KEY:LOCK", "ON", "OFF")
[docs] def release_panel(self): # TODO: better name? # NOTE: method may be redundant with the panel_locked property. """ Releases any lockout of the local control panel. """ self.sendcmd(":KEY:FORC")