#!/usr/bin/env python
"""
Provides support for Rigol DS-1000 series oscilloscopes.
"""
# IMPORTS #####################################################################
from enum import Enum
from instruments.abstract_instruments import Oscilloscope
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"
# INNER CLASSES #
[docs]
class DataSource(Oscilloscope.DataSource):
"""
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]
class Channel(DataSource, Oscilloscope.Channel):
"""
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.
"""
[docs]
class Coupling(Enum):
"""
Enum containing valid coupling modes for the Rigol DS1000
"""
ac = "AC"
dc = "DC"
ground = "GND"
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().__init__(self._parent, f"CHAN{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(f":CHAN{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(f":CHAN{self._idx}:{cmd}")
coupling = enum_property("COUP", Coupling)
bw_limit = bool_property("BWL", inst_true="ON", inst_false="OFF")
display = bool_property("DISP", inst_true="ON", inst_false="OFF")
invert = bool_property("INV", inst_true="ON", inst_false="OFF")
# TODO: :CHAN<n>:OFFset
# TODO: :CHAN<n>:PROBe
# TODO: :CHAN<n>:SCALe
filter = bool_property("FILT", inst_true="ON", inst_false="OFF")
# TODO: :CHAN<n>:MEMoryDepth
vernier = bool_property("VERN", inst_true="ON", inst_false="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(f":ACQ:AVER {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", inst_true="ENAB", inst_false="DIS")
[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")