#!/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] 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")