class _CoreOscilloscope(_frame_instrument.FrameBasedInstrument):

    def __init__(self):
        super(_CoreOscilloscope, self).__init__()
        self._register_accessors(_osc_reg_handlers)

        self._trigger = Trigger(self, reg_base=68)

        self.id = 1
        self.type = "oscilloscope"
        self.calibration = None

        # Defines the samplerate as seen at the input of the instrument
        # This value should be overwritten by all child instruments
        # inheriting the Oscilloscope
        self._input_samplerate = _OSC_ADC_SMPS
        self._chn_buffer_len = _OSC_BUFLEN

        # NOTE: Register mapped properties will be overwritten in sync
        # registers call
        # on deploy_instrument(). No point setting them here.
        self.scales = {}
        self._set_frame_class(VoltsData, instrument=self, scales=self.scales)

        # All instruments need a binstr, procstr and format string.
        self.logname = "MokuOscilloscopeData"
        self.binstr = "<s32"
        self.procstr = ['', '']
        self.fmtstr = ''
        self.hdrstr = ''
        self.timestep = 1

        # Initialise any local user config variables
        self._trig_hysteresis = 0
        self._trig_level = 0
        self._trig_duration = 0

    def _calculate_decimation(self, t1, t2):

        # Calculate time the buffer should contain
        # Want one frame to be approximately 1/3 of a buffer (RD ~ 5)
        # or the full buffer if it would take longer than 100ms

        # TODO: Put some limits on what the span/decimation can be
        if (t2 < 0):
            buffer_span = -float(t1)
        else:
            buffer_span = float(t2 - t1)

        deci = math.ceil(
            self._input_samplerate * buffer_span / self._chn_buffer_len)

        return deci

    def _calculate_render_downsample(self, t1, t2, decimation):
        # Calculate how much to render downsample
        tspan = float(t2) - float(t1)
        buffer_smp_rate = self._input_samplerate / float(decimation)

        def _cubic_int_to_scale(integer):
            # Integer to cubic scaling ratio (see Wiki)
            return float(integer / (2.0 ** 7)) + 1

        # Enforce a maximum ADC sampling rate
        screen_smp_rate = min(_OSC_SCREEN_WIDTH / tspan,
                              self._input_samplerate)

        # Clamp the render downsampling ratio between 1.0 and ~16.0
        render_downsample = min(max(buffer_smp_rate / screen_smp_rate, 1.0),
                                _cubic_int_to_scale(0x077E))
        return render_downsample

    def _calculate_buffer_offset(self, t1, decimation):
        # Calculate the number of pretrigger samples and offset it by an
        # additional (CubicRatio) samples
        buffer_smp_rate = self._input_samplerate / decimation
        buffer_offset_secs = -1.0 * t1
        buffer_offset = (math.ceil(min(
            max(math.ceil(buffer_offset_secs * buffer_smp_rate / 4.0),
                _OSC_POSTTRIGGER_MAX), _OSC_PRETRIGGER_MAX)))

        # Apply a correction in pretrigger because of the way cubic
        # interpolation occurs when rendering
        return buffer_offset

    def _calculate_render_offset(self, t1, decimation):
        # TODO: Render offset should be unique to buffer offset
        #       For now they are the same thing.
        buffer_offset = self._calculate_buffer_offset(t1, decimation)

        return buffer_offset * 4.0

    def _calculate_frame_start_time(self, decimation,
                                    render_decimation, frame_offset):
        return (render_decimation - frame_offset) \
            * decimation / self._input_samplerate

    def _calculate_frame_timestep(self, decimation, render_decimation):
        return decimation * render_decimation / self._input_samplerate

    def _calculate_buffer_timestep(self, decimation):
        return float(decimation) / float(self._input_samplerate)

    def _calculate_buffer_start_time(self, decimation, buffer_offset):
        return self._calculate_buffer_timestep(
            decimation) * (-1.0 * buffer_offset) * 4.0

    def _deci_gain(self):
        if self.decimation_rate == 0:
            return 1

        if self.decimation_rate < 2**20:
            return self.decimation_rate
        else:
            return self.decimation_rate / 2**10

    @needs_commit
    def set_timebase(self, t1, t2):
        """ Set the left- and right-hand span for the time axis.
        Units are seconds relative to the trigger point.

        :type t1: float
        :param t1:
            Time, in seconds, from the trigger point to the left of screen.
            This may be negative (trigger on-screen) or positive
            (trigger off the left of screen).

        :type t2: float
        :param t2: As *t1* but to the right of screen.

        :raises InvalidConfigurationException: if the timebase is
            backwards or zero.
        """
        if(t2 <= t1):
            raise InvalidConfigurationException("Timebase must be non-zero, "
                                                "with t1 < t2. Attempted to "
                                                "set t1=%f and t2=%f"
                                                % (t1, t2))

        decimation = self._calculate_decimation(t1, t2)
        render_decimation = self._calculate_render_downsample(t1,
                                                              t2,
                                                              decimation)
        buffer_offset = self._calculate_buffer_offset(t1, decimation)
        frame_offset = self._calculate_render_offset(t1, decimation)

        self.decimation_rate = decimation
        self.render_deci = render_decimation
        self.pretrigger = buffer_offset
        self.offset = frame_offset

    @needs_commit
    def set_samplerate(self, samplerate, trigger_offset=0):
        """ Manually set the sample rate of the instrument.

        The sample rate is automatically calculated and set in :any:
        `set_timebase`.

        This interface allows you to specify the rate at which data is
        sampled, and set a trigger offset in number of samples. This
        interface is useful for datalogging and capturing
        of data frames.

        :type samplerate: float; *0 < samplerate <= MAX_SAMPLERATE smp/s*
        :param samplerate: Target samples per second. Will get rounded to
        the nearest allowable unit.

        :type trigger_offset: int; *-2^16 < trigger_offset < 2^31*
        :param trigger_offset: Number of samples before (-) or after (+) the
        trigger point to start capturing.

        :raises ValueOutOfRangeException: if either parameter is out of range.
        """
        _utils.check_parameter_valid('range',
                                     samplerate,
                                     [_OSC_SAMPLERATE_MIN,
                                      _OSC_SAMPLERATE_MAX],
                                     'samplerate', 'smp/s')
        _utils.check_parameter_valid('range',
                                     trigger_offset,
                                     [-2**16 + 1, 2**31 - 1],
                                     'trigger offset',
                                     'samples')

        decimation = self._input_samplerate / float(samplerate)

        self.decimation_rate = decimation
        self.timestep = 1.0 / (self._input_samplerate / self.decimation_rate)
        # Ensure the buffer offset is large enough to incorporate the desired
        # pretrigger/posttrigger data
        self.pretrigger = -math.ceil(trigger_offset / 4.0) \
            if trigger_offset > 0 else -math.floor(trigger_offset / 4.0)
        # We don't want any rendering as each sample is already at the desired
        # samplerate
        self.render_deci = 1
        # The render offset needs to be corrected for cubic downsampling
        # (even with unity decimation)
        self.offset = -round(trigger_offset) + self.render_deci

    def get_samplerate(self):
        """ :return: The current instrument sample rate (Hz) """
        if(self.decimation_rate == 0):
            log.info("Decimation rate appears to be unset.")
            return self._input_samplerate
        return self._input_samplerate / float(self.decimation_rate)

    @needs_commit
    def set_xmode(self, xmode):
        """
        Set rendering mode for the horizontal axis.

        :type xmode: string, {'roll','sweep','fullframe'}
        :param xmode:
            Respectively; Roll Mode (scrolling), Sweep Mode (normal
            oscilloscope trace sweeping across the screen) or Full Frame
            (like sweep, but waits for the frame to be completed).
        """
        _str_to_xmode = {
            'roll': _OSC_ROLL,
            'sweep': _OSC_SWEEP,
            'fullframe': _OSC_FULL_FRAME
        }
        xmode = _utils.str_to_val(_str_to_xmode, xmode, 'X-mode')
        self.x_mode = xmode

    @needs_commit
    def set_precision_mode(self, state):
        """ Change aquisition mode between downsampling and decimation.
        Precision mode, a.k.a Decimation, samples at full rate and applies a
        low-pass filter to the data. This improves precision. Normal mode
        works by direct downsampling, throwing away points it doesn't need.

        :param state: Select Precision Mode
        :type state: bool
        """
        _utils.check_parameter_valid(
            'bool', state, desc='precision mode enable')
        self.ain_mode = _OSC_AIN_DECI if state else _OSC_AIN_DDS

    def is_precision_mode(self):
        return self.ain_mode is _OSC_AIN_DECI

    def _set_trigger(self, source, edge, level, minwidth, maxwidth,
                     hysteresis, hf_reject, mode):
        if (self._moku.get_hw_version() == 1.0) and source == _OSC_SOURCE_EXT:
            raise InvalidConfigurationException(
                'External trigger source is not available on your hardware.')

        # Convert the input parameter strings to bit-value mappings
        _utils.check_parameter_valid('range',
                                     level,
                                     [_OSC_TRIGLVL_MIN, _OSC_TRIGLVL_MAX],
                                     'trigger level',
                                     'Volts')
        _utils.check_parameter_valid('bool',
                                     hf_reject,
                                     'High-frequency reject enable')
        _utils.check_parameter_valid('set',
                                     mode,
                                     ['auto', 'normal'],
                                     desc='mode')
        _utils.check_parameter_valid(
            'range', hysteresis, [100e-6, 1.0], 'hysteresis', 'Volts')
        if not (maxwidth is None or minwidth is None):
            raise InvalidConfigurationException("Can't set both 'minwidth' "
                                                "and 'maxwidth' for Pulse "
                                                "Width trigger mode. Choose "
                                                "one.")
        if (maxwidth or minwidth) and (edge == 'both'):
            raise InvalidConfigurationException("Can't set trigger edge type"
                                                " 'both' in Pulse Width "
                                                "trigger mode. Choose one of "
                                                "{'rising','falling'}.")

        _str_to_edge = {
            'rising': Trigger.EDGE_RISING,
            'falling': Trigger.EDGE_FALLING,
            'both': Trigger.EDGE_BOTH
        }
        edge = _utils.str_to_val(_str_to_edge, edge, 'edge type')

        self.hf_reject = hf_reject

        if mode == 'auto':
            # TODO these should scale with the timebase
            self.auto_timer = 20.0
            self.auto_holdoff = 5
        elif mode == 'normal':
            self.auto_timer = 0.0
            self.auto_holdoff = 0

        self.trig_ch = source
        # Locally store user settings and calculate regs at commit-time
        self._trig_level = level
        self._trig_duration = minwidth or maxwidth or 0.0
        self._trig_hysteresis = hysteresis

        self._trigger.edge = edge
        self._trigger.mode = mode
        if maxwidth:
            self._trigger.trigtype = Trigger.TYPE_PULSE
            self._trigger.pulsetype = Trigger.PULSE_MAX
        elif minwidth:
            self._trigger.trigtype = Trigger.TYPE_PULSE
            self._trigger.pulsetype = Trigger.PULSE_MIN
        else:
            self._trigger.trigtype = Trigger.TYPE_EDGE

    def _set_source(self, ch, source):
        """ Sets the source of the channel data to either the analog input or
            internally looped-back digital output.
        """
        # TODO: Add loopback mode parameter
        _utils.check_parameter_valid('set', ch, [1, 2], 'channel')

        if ch == 1:
            self.source_ch1 = source
        elif ch == 2:
            self.source_ch2 = source
        else:
            raise ValueOutOfRangeException("Incorrect channel number %d", ch)

    @needs_commit
    def set_defaults(self):
        """ Reset the Oscilloscope to sane defaults. """
        super(_CoreOscilloscope, self).set_defaults()
        self.set_precision_mode(True)
        self.trig_precision = True  # Set to always trigger off precision data
        self.set_timebase(-1, 1)
        self._set_pause(False)

        self.frame_length = _OSC_SCREEN_WIDTH
        self._set_buffer_length(4)
        self.set_xmode('fullframe')

        self.set_frontend(1, fiftyr=True, atten=False, ac=False)
        self.set_frontend(2, fiftyr=True, atten=False, ac=False)
        self.en_in_ch1 = True
        self.en_in_ch2 = True

    def _calculate_scales(self):
        g1, g2 = self._adc_gains()
        d1, d2 = self._dac_gains()
        a1 = self.get_frontend(1)[1]
        a2 = self.get_frontend(2)[1]

        if(self.decimation_rate == 0 or self.render_deci == 0):
            log.warning("ADCs appear to be turned off or decimation unset")
            t1 = 0
            ts = 1
            bt1 = 0
            bts = 1
        else:
            t1 = self._calculate_frame_start_time(self.decimation_rate,
                                                  self.render_deci,
                                                  self.offset)
            ts = self._calculate_frame_timestep(self.decimation_rate,
                                                self.render_deci)
            bt1 = self._calculate_buffer_start_time(self.decimation_rate,
                                                    self.pretrigger)
            bts = self._calculate_buffer_timestep(self.decimation_rate)

        scales = {
            'gain_adc1': g1,
            'gain_adc2': g2,
            'gain_dac1': d1,
            'gain_dac2': d2,
            'atten_ch1': a1,
            'atten_ch2': a2,
            'time_min': t1,
            'time_step': ts,
            'buff_time_min': bt1,
            'buff_time_step': bts
        }

        # Replace scaling factors depending on the monitor signal source
        scales['scale_ch1'] = self._signal_source_volts_per_bit(
            self.source_ch1, scales)
        scales['scale_ch2'] = self._signal_source_volts_per_bit(
            self.source_ch2, scales)

        return scales

    def _update_dependent_regs(self, scales):
        # Update trigger level and duration settings based on current
        # trigger source and timebase
        self._trigger.duration = self._trig_duration \
            * self._input_samplerate \
            / (self.decimation_rate if self.is_precision_mode() else 1.0)
        self._trigger.level = int(
            round(self._trig_level / self._signal_source_volts_per_bit(
                self.trig_ch, scales, trigger=True)))

        # Notify the user if hysteresis has been clamped
        max_hysteresis = 2**16 - 1
        hysteresis = int(
            round(self._trig_hysteresis / self._signal_source_volts_per_bit(
                self.trig_ch, scales, trigger=True)))
        if hysteresis > max_hysteresis:
            hysteresis = max_hysteresis
            log.info("Hysteresis set to maximum value.")
        self._trigger.hysteresis = hysteresis

    def _update_datalogger_params(self):
        scales = self._calculate_scales()

        samplerate = self.get_samplerate()
        self.timestep = 1.0 / samplerate

        # Use the new scales to decide on the processing string
        self.procstr[0] = "*{:.15f}".format(scales['scale_ch1'])
        self.procstr[1] = "*{:.15f}".format(scales['scale_ch2'])

        self.fmtstr = self._get_fmtstr(self.ch1, self.ch2)
        self.hdrstr = self._get_hdrstr(self.ch1, self.ch2)

    def _get_hdrstr(self, ch1, ch2):
        chs = [ch1, ch2]

        hdr = "% Moku:Oscilloscope\r\n"
        for i, c in enumerate(chs):
            if c:
                r = self.get_frontend(i + 1)
                hdr += ("% Ch {i} - {} coupling, {} Ohm impedance, "
                        "{} V range\r\n").format("AC" if r[2] else "DC",
                                                 "50" if r[0] else "1M",
                                                 "10" if r[1] else "1",
                                                 i=i + 1)
        hdr += ("% Acquisition rate: {:.10e} Hz, "
                "{} mode\r\n").format(self.get_samplerate(),
                                      "Precision" if self.is_precision_mode()
                                      else "Normal")
        hdr += "% {} 10 MHz clock\r\n".format("External" if
                                              self._moku._get_actual_extclock()
                                              else "Internal")
        hdr += "% Acquired {}\r\n".format(_utils.formatted_timestamp())
        hdr += "% Time"
        for i, c in enumerate(chs):
            if c:
                hdr += ", Ch {i} voltage (V)".format(i=i + 1)
        hdr += "\r\n"
        return hdr

    def _get_fmtstr(self, ch1, ch2):
        chs = [ch1, ch2]
        fmtstr = "{t:.10e}"
        for i, c in enumerate(chs):
            if c:
                fmtstr += ",{{ch{i}:.10e}}".format(i=i + 1)
        fmtstr += "\r\n"
        return fmtstr

    def _on_reg_sync(self):
        # Do the instrument-level post-sync stuff
        super(_CoreOscilloscope, self)._on_reg_sync()

        # This function is used to update any local variables when a Moku has
        # had its registers synchronised with the current instrument
        if self.decimation_rate == 0:
            self.timestep = 1.0 / self._input_samplerate
        else:
            self.timestep = float(self.decimation_rate) \
                / self._input_samplerate

        scales = self._calculate_scales()
        self.scales[self._stateid] = scales

        # Read back trigger settings into local variables
        self._trig_duration = self._trigger.duration \
            * (self.decimation_rate if self.is_precision_mode() else 1.0) \
            / self._input_samplerate
        self._trig_level = self._trigger.level \
            * self._signal_source_volts_per_bit(self.trig_ch, scales,
                                                trigger=True)
        self._trig_hysteresis = self._trigger.hysteresis \
            * self._signal_source_volts_per_bit(self.trig_ch,
                                                scales,
                                                trigger=True)

        self._update_datalogger_params()

    def commit(self):
        scales = self._calculate_scales()
        # Update any calibration scaling dependent register values
        self._update_dependent_regs(scales)
        self._update_datalogger_params()

        # Commit the register values to the device
        super(_CoreOscilloscope, self).commit()
        # Associate new state ID with the scaling factors of the state
        self.scales[self._stateid] = scales
        # TODO: Trim scales dictionary, getting rid of old ids

    # Bring in the docstring from the superclass for our docco.
    commit.__doc__ = MokuInstrument.commit.__doc__ def str_to_val(valmap, key, param_description):
def check_parameter_valid(
        check_type, v, allowed=None, desc="", units="", allow_none=False):
def formatted_timestamp(): class PID(object):
    _REG_EN = 0
    _REG_GAIN = 1
    _REG_I_GAIN = 2
    _REG_I_FB = 3
    _REG_P_GAIN = 4
    _REG_D_GAIN = 5
    _REG_D_FB = 6
    _REG_IN_OFFSET = 7
    _REG_OUT_OFFSET = 8

    def __init__(self, instr, reg_base, fs):
        self._instr = instr
        self.reg_base = reg_base
        self.fs = fs
        self.ang_freq = fs / (2 * math.pi)
        self.enable = True
        self.bypass = False
        self.int_en = True
        self.dc_pole = False
        self.p_en = True
        self.d_i_en = True
        self.input_en = True

    @property
    def enable(self):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_get(r, from_reg_bool(0))

    @enable.setter
    def enable(self, value):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_set(r, to_reg_bool(0), value)

    @property
    def bypass(self):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_get(r, from_reg_bool(1))

    @bypass.setter
    def bypass(self, value):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_set(r, to_reg_bool(1), value)

    @property
    def int_en(self):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_get(r, from_reg_bool(2))

    @int_en.setter
    def int_en(self, value):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_set(r, to_reg_bool(2), value)

    @property
    def dc_pole(self):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_get(r, from_reg_bool(3))

    @dc_pole.setter
    def dc_pole(self, value):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_set(r, to_reg_bool(3), value)

    @property
    def p_en(self):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_get(r, from_reg_bool(4))

    @p_en.setter
    def p_en(self, value):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_set(r, to_reg_bool(4), value)

    @property
    def d_i_en(self):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_get(r, from_reg_bool(5))

    @d_i_en.setter
    def d_i_en(self, value):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_set(r, to_reg_bool(5), value)

    @property
    def input_en(self):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_get(r, from_reg_bool(6))

    @input_en.setter
    def input_en(self, value):
        r = self.reg_base + PID._REG_EN
        return self._instr._accessor_set(r, to_reg_bool(6), value)

    @property
    def gain(self):
        r = self.reg_base + PID._REG_GAIN
        return self._instr._accessor_get(r, from_reg_unsigned(0, 32))

    @gain.setter
    def gain(self, value):
        r = self.reg_base + PID._REG_GAIN
        self._instr._accessor_set(r, to_reg_unsigned(0, 32), value)

    @property
    def i_gain(self):
        r = self.reg_base + PID._REG_I_GAIN
        return self._instr._accessor_get(
            r, from_reg_unsigned(0, 25,
                                 xform=lambda obj, x: x / (2.0**24 - 1)))

    @i_gain.setter
    def i_gain(self, value):
        r = self.reg_base + PID._REG_I_GAIN
        self._instr._accessor_set(
            r, to_reg_unsigned(0, 25,
                               xform=lambda obj, x: x * (2.0 ** 24 - 1)),
            value)

    @property
    def i_fb(self):
        r = self.reg_base + PID._REG_I_FB
        return self._instr._accessor_get(
            r, from_reg_signed(0, 25, xform=lambda obj, x: x / (2.0**24 - 1)))

    @i_fb.setter
    def i_fb(self, value):
        r = self.reg_base + PID._REG_I_FB
        self._instr._accessor_set(
            r, to_reg_signed(0, 25,
                             xform=lambda obj, x: x * (2.0**24 - 1)), value)

    @property
    def p_gain(self):
        r = self.reg_base + PID._REG_P_GAIN
        return self._instr._accessor_get(
            r, from_reg_unsigned(0, 25, xform=lambda obj, x: x / (2.0**11)))

    @p_gain.setter
    def p_gain(self, value):
        r = self.reg_base + PID._REG_P_GAIN
        self._instr._accessor_set(
            r, to_reg_unsigned(0, 25,
                               xform=lambda obj, x: x * (2.0**11)), value)

    @property
    def d_gain(self):
        r = self.reg_base + PID._REG_D_GAIN
        return self._instr._accessor_get(r, from_reg_unsigned(0, 25))

    @d_gain.setter
    def d_gain(self, value):
        r = self.reg_base + PID._REG_D_GAIN
        return self._instr._accessor_set(r, to_reg_unsigned(0, 25), value)

    @property
    def d_fb(self):
        r = self.reg_base + PID._REG_D_FB
        return self._instr._accessor_get(
            r, from_reg_unsigned(0, 25,
                                 xform=lambda obj, x: x / (2.0**24 - 1)))

    @d_fb.setter
    def d_fb(self, value):
        r = self.reg_base + PID._REG_D_FB
        return self._instr._accessor_set(
            r, to_reg_unsigned(0, 25,
                               xform=lambda obj, x: x * (2.0**24 - 1)), value)

    @property
    def input_offset(self):
        r = self.reg_base + PID._REG_IN_OFFSET
        return self._instr._accessor_get(r, from_reg_signed(0, 16))

    @input_offset.setter
    def input_offset(self, value):
        r = self.reg_base + PID._REG_IN_OFFSET
        return self._instr._accessor_set(r, to_reg_unsigned(0, 16, value))

    @property
    def output_offset(self):
        r = self.reg_base + PID._REG_OUT_OFFSET
        return self._instr._accessor_get(r, from_reg_signed(0, 16))

    @output_offset.setter
    def output_offset(self, value):
        r = self.reg_base + PID._REG_OUT_OFFSET
        return self._instr._accessor_set(r, to_reg_unsigned(0, 16, value))

    def set_reg_by_gain(self, g, kp, ki, kd, si, sd):
        """ calculates the device registers ased on the gain values given.
         Note that additional scaling due to external gain such as
         ADC, DAC, decimation gains, etc. are not accounted for here))
        """

        self.gain = g
        self.p_gain = kp
        self.i_gain = ki / self.ang_freq

        if kd == 0:
            self.d_gain = 0
        else:
            self.d_gain = 4 * sd if sd else self.ang_freq / float(kd)

        if si is None:
            i_c = 0
        else:
            i_c = ki / si
            if i_c < self.ang_freq / (2**24 - 1):
                si_max = (g * ki / (2 * self.ang_freq / (2**24 - 1)))
                raise InvalidConfigurationException(
                    "Integrator corner below"
                    " minimum. Decrease "
                    "integrator saturation "
                    "below %.3f dB."
                    % (20 * math.log(si_max, 10)))
        self.i_fb = 1.0 - (i_c / self.ang_freq)

        if sd:
            if kd > 0:
                fc_coeff = sd / (self.ang_freq / float(kd))
            else:
                fc_coeff = 1
        else:
            # default differentiator roll off to a 10th of the nyquist
            fc_coeff = (math.sqrt(2 * math.pi) / 10.0)

        if fc_coeff > (math.sqrt(2 * math.pi) / 10.0):
            raise InvalidConfigurationException(
                "Differentiator saturation "
                "corner above maximum. Reduce"
                " differentiator saturation "
                "below %.3f."
                % (fc_coeff * kd * self.ang_freq))

        self.d_fb = 1.0 - fc_coeff

    def set_reg_by_frequency(self, kp, i_xover, d_xover, si, sd):
        # converts frequency cross over information

        # Particularly high or low I or D crossover frequencies (<1Hz, >1MHz)
        # require that some of their gain is pushed to the overall gain on
        # the end due to dynamic range limitations

        cross_over_gain = kp if kp else 1.0

        i_gmin = d_gmin = 1.0
        i_gmax = d_gmax = 1.0
        if i_xover:
            i_unity = i_xover * cross_over_gain
            i_gmin = min(i_unity, 1.0)
            i_gmax = max(i_unity / 1e6, 1.0)

        if d_xover:
            d_unity = d_xover * (2 * math.pi) * self.ang_freq / d_xover
            d_gmin = sd if sd is not None and sd < 1 else max(10.0e6 / d_unity,
                                                              1.0)
            d_gmax = max(1 / d_unity, 1.0)

        g_min = min(i_gmin, d_gmin)
        g_max = max(i_gmax, d_gmax)

        if g_min < 1.0 and g_max == 1.0:
            best_gain = g_min
        elif g_max > 1.0 and g_min == 1.0:
            best_gain = g_max
        elif g_min < 1.0 and g_max > 1.0:
            best_gain = math.sqrt(g_min * g_max)
        else:
            best_gain = 1.0

        cross_over_gain /= best_gain

        si = None if i_xover is None else si
        sd = None if d_xover is None else sd

        if i_xover:
            ki = cross_over_gain * i_xover
        else:
            ki = 0.0

        kd = d_xover / cross_over_gain if d_xover else 0.0
        si = si / best_gain if si else None

        # if ii_xover :
        #   si = math.sqrt(si)

        sd = sd / best_gain if sd else None
        self.set_reg_by_gain(best_gain, kp, ki, kd, si, sd) class SweepGenerator(object):
    _REG_CONFIG = 0
    _REG_START_LSB = 1
    _REG_START_MSB = 2
    _REG_STOP_LSB = 3
    _REG_STOP_MSB = 4
    _REG_STEP_LSB = 5
    _REG_STEP_MSB = 6
    _REG_DURATION_LSB = 7
    _REG_DURATION_MSB = 8

    WAVE_TYPE_SINGLE = 0
    WAVE_TYPE_UPDOWN = 1
    WAVE_TYPE_SAWTOOTH = 2
    WAVE_TYPE_TRIANGLE = 3

    def __init__(self, instr, reg_base):
        self._instr = instr
        self.reg_base = reg_base

    @property
    def waveform(self):
        r = self.reg_base + SweepGenerator._REG_CONFIG
        return self._instr._accessor_get(r, from_reg_unsigned(0, 2))

    @waveform.setter
    def waveform(self, value):
        r = self.reg_base + SweepGenerator._REG_CONFIG
        self._instr._accessor_set(
            r, to_reg_unsigned(0, 2,
                               allow_set=[SweepGenerator.WAVE_TYPE_SINGLE,
                                          SweepGenerator.WAVE_TYPE_UPDOWN,
                                          SweepGenerator.WAVE_TYPE_SAWTOOTH,
                                          SweepGenerator.WAVE_TYPE_TRIANGLE]),
            value)

    @property
    def direction(self):
        r = self.reg_base + SweepGenerator._REG_CONFIG
        return self._instr._accessor_get(r, from_reg_unsigned(5, 1))

    @direction.setter
    def direction(self, value):
        r = self.reg_base + SweepGenerator._REG_CONFIG
        self._instr._accessor_set(
            r, to_reg_unsigned(5, 1, allow_set=[0, 1]), value)

    @property
    def logsweep(self):
        r = self.reg_base + SweepGenerator._REG_CONFIG
        return self._instr._accessor_get(r, from_reg_unsigned(6, 1))

    @logsweep.setter
    def logsweep(self, value):
        r = self.reg_base + SweepGenerator._REG_CONFIG
        self._instr._accessor_set(
            r, to_reg_unsigned(6, 1, allow_set=[0, 1]), value)

    @property
    def start(self):
        r1 = self.reg_base + SweepGenerator._REG_START_LSB
        r2 = self.reg_base + SweepGenerator._REG_START_MSB
        return self._instr._accessor_get((r2, r1), from_reg_unsigned(0, 64))

    @start.setter
    def start(self, value):
        r1 = self.reg_base + SweepGenerator._REG_START_LSB
        r2 = self.reg_base + SweepGenerator._REG_START_MSB
        self._instr._accessor_set((r2, r1), to_reg_unsigned(0, 64), value)

    @property
    def stop(self):
        r1 = self.reg_base + SweepGenerator._REG_STOP_LSB
        r2 = self.reg_base + SweepGenerator._REG_STOP_MSB
        return self._instr._accessor_get((r2, r1), from_reg_unsigned(0, 64))

    @stop.setter
    def stop(self, value):
        r1 = self.reg_base + SweepGenerator._REG_STOP_LSB
        r2 = self.reg_base + SweepGenerator._REG_STOP_MSB
        self._instr._accessor_set((r2, r1), to_reg_unsigned(0, 64), value)

    @property
    def step(self):
        r1 = self.reg_base + SweepGenerator._REG_STEP_LSB
        r2 = self.reg_base + SweepGenerator._REG_STEP_MSB
        return self._instr._accessor_get((r2, r1), from_reg_unsigned(0, 64))

    @step.setter
    def step(self, value):
        r1 = self.reg_base + SweepGenerator._REG_STEP_LSB
        r2 = self.reg_base + SweepGenerator._REG_STEP_MSB
        self._instr._accessor_set((r2, r1), to_reg_unsigned(0, 64), value)

    @property
    def duration(self):
        r1 = self.reg_base + SweepGenerator._REG_DURATION_LSB
        r2 = self.reg_base + SweepGenerator._REG_DURATION_MSB
        return self._instr._accessor_get((r2, r1), from_reg_unsigned(0, 64))

    @duration.setter
    def duration(self, value):
        r1 = self.reg_base + SweepGenerator._REG_DURATION_LSB
        r2 = self.reg_base + SweepGenerator._REG_DURATION_MSB
        self._instr._accessor_set((r2, r1), to_reg_unsigned(0, 64), value)

    @property
    def wait_for_trig(self):
        r = self.reg_base + SweepGenerator._REG_CONFIG
        return self._instr._accessor_get(r, from_reg_bool(2))

    @wait_for_trig.setter
    def wait_for_trig(self, value):
        r = self.reg_base + SweepGenerator._REG_CONFIG
        self._instr._accessor_set(r, to_reg_bool(2), value)

    @property
    def hold_last(self):
        r = self.reg_base + SweepGenerator._REG_CONFIG
        return self._instr._accessor_get(r, from_reg_bool(3))

    @hold_last.setter
    def hold_last(self, value):
        r = self.reg_base + SweepGenerator._REG_CONFIG
        self._instr._accessor_set(r, to_reg_bool(3), value) class IIRBlock(object):

    def __init__(self, instr, reg_base, num_stages=1, gain_frac_width=9,
                 coeff_frac_width=16, use_mmap=True):
        self._instr = instr
        self.reg_base = reg_base
        self.num_stages = num_stages
        self._coeff_frac_width = coeff_frac_width
        self._gain_frac_width = gain_frac_width
        self.use_mmap = use_mmap

    def _convert_coeffs(self, filt_coeffs):

        intermediate_filter = deepcopy(filt_coeffs)

        for stage in range(1, self.num_stages + 1):
            intermediate_filter[stage][1] *= intermediate_filter[stage][0]
            intermediate_filter[stage][2] *= intermediate_filter[stage][0]
            intermediate_filter[stage][3] *= intermediate_filter[stage][0]
            intermediate_filter[stage][0] = 1.0

        intermediate_filter[self.num_stages][0] = intermediate_filter[0][0]
        intermediate_filter = intermediate_filter[1:5]

        coeff_list = [[0 for coeff_elements in range(6)] for stage in
                      range(self.num_stages)]

        for stage in range(self.num_stages):
            for coeff in range(6):
                if coeff == 0:
                    coeff_list[stage][coeff] = int(round(
                        2 ** self._gain_frac_width * (
                            intermediate_filter[stage][coeff])))
                else:
                    coeff_list[stage][coeff] = \
                        int(round(2 ** self._coeff_frac_width * (
                            intermediate_filter[stage][coeff])))
        return coeff_list

    def _coeff_dimenension_checks(self, filt_coeffs):
        _utils.check_parameter_valid('set', len(filt_coeffs[0]),
                                     [1], 'number of coefficients '
                                          'in coefficient gain')

        for stage in range(1, self.num_stages + 1):
            _utils.check_parameter_valid('set', len(filt_coeffs[stage]),
                                         [6],
                                         ("number of coefficients in stage %s"
                                          % (stage)))
            for coeff_element in range(6):
                _utils.check_parameter_valid('range',
                                             filt_coeffs[stage][coeff_element],
                                             [-4.0, 4.0 - 2**(-45)],
                                             ("coefficient array entry at "
                                              "stage = %s, coefficient = %s"
                                              % (0, 0)))

    def write_coeffs(self, filt_coeffs):

        self._coeff_dimenension_checks(filt_coeffs)
        reg_coeffs = self._convert_coeffs(filt_coeffs)

        if self.use_mmap:
            # self._write_to_mmap(reg_coeffs)
            self._write_to_reg(reg_coeffs)
        else:
            self._write_to_reg(reg_coeffs)

    def _write_to_reg(self, coeffs_converted):

        for stage in range(self.num_stages):
            for coeff in range(6):
                r = self.reg_base + 6 * stage + coeff
                self._instr._accessor_set(r,
                                          to_reg_signed(
                                              0,
                                              self._coeff_frac_width + 2),
                                          coeffs_converted[stage][coeff]) class EmbeddedPLL(object):

    _REG_BANDWIDTH = 0
    _REG_AUTOACQ = 1
    _REG_REACQ = 2
    _REG_RESET = 3

    def __init__(self, instr, reg_base):
        self._instr = instr
        self.reg_base = reg_base

    @property
    def bandwidth(self):
        r = self.reg_base + EmbeddedPLL._REG_BANDWIDTH
        return self._instr._accessor_get(r, from_reg_signed(0, 5))

    @bandwidth.setter
    def bandwidth(self, value):
        r = self.reg_base + EmbeddedPLL._REG_BANDWIDTH
        self._instr._accessor_set(r, to_reg_signed(0, 5), value)  # allow_set?

    @property
    def autoacquire(self):
        r = self.reg_base + EmbeddedPLL._REG_AUTOACQ
        return self._instr._accessor_get(r, from_reg_bool(0))

    @autoacquire.setter
    def autoacquire(self, value):
        r = self.reg_base + EmbeddedPLL._REG_AUTOACQ
        self._instr._accessor_set(r, to_reg_bool(0), value)  # allow_set?

    @property
    def reacquire(self):
        r = self.reg_base + EmbeddedPLL._REG_REACQ
        return self._instr._accessor_get(r, from_reg_bool(0))

    @reacquire.setter
    def reacquire(self, value):
        r = self.reg_base + EmbeddedPLL._REG_REACQ
        self._instr._accessor_set(r, to_reg_bool(0), value)  # allow_set?

    @property
    def pllreset(self):
        r = self.reg_base + EmbeddedPLL._REG_RESET
        return self._instr._accessor_get(r, from_reg_bool(31))

    @pllreset.setter
    def pllreset(self, value):
        r = self.reg_base + EmbeddedPLL._REG_RESET
        self._instr._accessor_set(r, to_reg_bool(31), value)  # allow_set? def needs_commit(func):
    """ Wrapper function which checks whether settings should be committed
        automatically.
    """
    @wraps(func)
    def wrapper(self, *args, **kwargs):
        if not _get_autocommit():
            # Not auto-committing
            return func(self, *args, **kwargs)
        else:
            # Auto-committing
            global _awaiting_commit
            # Remember if a commit was already being waited on
            was_awaiting = _awaiting_commit
            if not _awaiting_commit:
                _awaiting_commit = True  # Lock the commit

            try:
                # Attempt to call the wrapped function
                res = func(self, *args, **kwargs)
            finally:
                # Do this even if the function raises an Exception

                # Commit if we weren't already waiting for one before
                if not was_awaiting:
                    self.commit()
                    # Reset the intention to commit
                    _awaiting_commit = False
            return res
    return wrapper REG_CTL = 0
REG_STAT = 1
REG_ID1 = 2
REG_ID2 = 3
REG_PAUSE = 4
REG_OUTLEN = 5
REG_FILT = 6
REG_FRATE = 7
REG_SCALE = 8
REG_OFFSET = 9
REG_OFFSETA = 10
REG_STRCTL0 = 11
REG_STRCTL1 = 12
REG_AINCTL = 13
REG_PRETRIG = 15
REG_CAL_ADC0, REG_CAL_ADC1, REG_CAL_DAC0, REG_CAL_DAC1 = list(range(16, 20))
REG_TEMP_DAC = 14
REG_MMAP_ACCESS = 62
REG_STATE = 63
ADC_SMP_RATE = 500e6
DAC_SMP_RATE = 1e9
CHN_BUFLEN = 2**14
COMMIT = 0x80000000
INSTR_RST = 0x00000001
ROLL = 1
SWEEP = 2
FULL_FRAME = 0
RDR_CUBIC = 0
RDR_MINMAX = 1
RDR_DECI = 2
RDR_DDS = 3
RELAY_DC = 1
RELAY_LOWZ = 2
RELAY_LOWG = 4
def _usgn(i, width):
def _sgn(i, width):
def _upsgn(i, width):
def to_reg_signed(_offset, _len, allow_set=None, allow_range=None,
                  xform=lambda obj, x: x):
    def __ss(obj, val, old):
def to_reg_unsigned(_offset, _len, allow_set=None, allow_range=None,
                    xform=lambda obj, x: x):
    def __us(obj, val, old):
def to_reg_bool(_offset):
def from_reg_signed(_offset, _len, xform=lambda obj, x: x):
    def __sg(obj, reg):
def from_reg_unsigned(_offset, _len, xform=lambda obj, x: x):
    def __ug(obj, reg):
def from_reg_bool(_offset):
def needs_commit(func):
    def wrapper(self, *args, **kwargs):
    def __init__(self):
    def _register_accessors(self, accessor_dict):
    def _accessor_get(self, reg, get_xform):
    def _accessor_set(self, reg, set_xform, data):
    def __getattr__(self, name):
    def __setattr__(self, name, value):
    def set_defaults(self):
    def attach_moku(self, moku):
    def _commit(self, update_state=True):
    def commit(self):
    def check_uncommitted_state(self):
    def _sync_registers(self):
    def _on_reg_sync(self):
    def _dump_remote_regs(self):
    def _set_running(self, state):
    def _set_instrument_active(self, active):
    def _set_frontend(self, channel, fiftyr, atten, ac):
    def _get_frontend(self, channel):
    def _dac_gains(self):
    def _dac_offsets(self):
    def _adc_gains(self):
    def _adc_offsets(self):
    def _set_pause(self, pause):
    def _get_pause(self):
    def _load_feature(self, index):
    def _set_mmap_access(self, access):
class MokuInstrument(object): def to_reg_unsigned(_offset, _len, allow_set=None, allow_range=None,
                    xform=lambda obj, x: x):
    """ Returns a callable that will pack a new unsigned data value or
    bitfield in to a register. Designed as shorthand for common use in
    instrument register accessor lists. Supports single and compound
    registers (i.e. data values that span more than one register).

    :param _offset: Offset of data field in the register (set)
    :param _len: Length of data field in the register (set)
    :param allow_set: Set containing all valid values of the data field.
    :param allow_range: a two-tuple specifying the bounds of the data value.
    :param xform: a callable that translates the user value written to the
        attribute to the register value
    """

    def __us(obj, val, old):
        val = xform(obj, val)
        mask = ((1 << _len) - 1) << _offset

        if allow_set and allow_range:
            raise MokuException("Can't check against both ranges and sets")

        if allow_set and val not in allow_set:
            return None
        elif allow_range and (val < allow_range[0] or val > allow_range[1]):
            return None

        v = _usgn(val, _len) << _offset

        try:
            return (old & ~mask) | v
        except TypeError:
            r = []
            for o in reversed(old):
                r.insert(0, (o & ~mask) | v & 0xFFFFFFFF)

                v = v >> 32
                mask = mask >> 32

            return tuple(r)

    return __us def from_reg_unsigned(_offset, _len, xform=lambda obj, x: x):
    """ Returns a callable that will unpack an unsigned data value from a
    register bitfield. Designed as shorthand for common use in instrument
    register accessor lists. Supports single and compound registers
    (i.e. data values that span more than one register).

    :param _offset: Offset of data field in the register (set)
    :param _len: Length of data field in the register (set)
    :param xform: a callable that translates the register value to the
        user attribute's natural units
    """
    mask = ((1 << _len) - 1) << _offset

    def __ug(obj, reg):
        try:
            return xform(obj, (reg & mask) >> _offset)
        except TypeError:
            v = 0
            for r in reg:
                v <<= 32
                v |= r

            return xform(obj, (v & mask) >> _offset)

    return __ug def to_reg_signed(_offset, _len, allow_set=None, allow_range=None,
                  xform=lambda obj, x: x):
    """ Returns a callable that will pack a new signed data value in to a
    register. Designed as shorthand for common use in instrument register
    accessor lists. Supports single and compound registers (i.e. data values
    that span more than one register).

    :param _offset: Offset of data field in the register (set)
    :param _len: Length of data field in the register (set)
    :param allow_set: Set containing all valid values of the data field.
    :param allow_range: a two-tuple specifying the bounds of the data value.
    :param xform: a callable that translates the user value written to the
    attribute to the register value
    """
    # TODO: This signed and the below unsigned share all but one line of
    # code, should consolidate
    def __ss(obj, val, old):
        val = xform(obj, val)
        mask = ((1 << _len) - 1) << _offset

        if allow_set and allow_range:
            raise MokuException("Can't check against both ranges and sets")

        if allow_set and val not in allow_set:
            return None
        elif allow_range and (val < allow_range[0] or val > allow_range[1]):
            return None

        v = _sgn(val, _len) << _offset

        try:
            return (old & ~mask) | v
        except TypeError:
            r = []
            for o in reversed(old):
                r.insert(0, (o & ~mask) | v & 0xFFFFFFFF)

                v = v >> 32
                mask = mask >> 32

            return tuple(r)

    return __ss def from_reg_signed(_offset, _len, xform=lambda obj, x: x):
    """ Returns a callable that will unpack a signed data value from a r
    egister bitfield. Designed as shorthand for common use in instrument
    register accessor lists. Supports single and compound registers
    (i.e. data values that span more than one register).

    :param _offset: Offset of data field in the register (set)
    :param _len: Length of data field in the register (set)
    :param xform: a callable that translates the register value to the
    user attribute's natural units
    """
    # TODO: As above, this and the unsigned version share all but one
    # line of code, should consolidate.
    mask = ((1 << _len) - 1) << _offset

    def __sg(obj, reg):
        try:
            return xform(obj, _upsgn((reg & mask) >> _offset, _len))
        except TypeError:
            v = 0
            for r in reg:
                v <<= 32
                v |= r

            return xform(obj, _upsgn((v & mask) >> _offset, _len))

    return __sg def to_reg_bool(_offset):
    """ Returns a callable that will pack a new boolean data value in to a
    single bit of a register. Designed as shorthand for common use in
    instrument register accessor lists. Equivalent to :any:
    `to_reg_unsigned(_offset, 1, allow_set=[0, 1], xform=int)`

    :param _offset: Offset of bit in the register (set)
    """
    return to_reg_unsigned(_offset, 1, allow_set=[0, 1], xform=lambda obj,
                           x: int(x)) def from_reg_bool(_offset):
    """ Returns a callable that will unpack a boolean value from a
    register bit. Designed as shorthand for common use in instrument
    register accessor lists. Equivalent to
    :any:`from_reg_unsigned(_offset, 1, xform=bool)`.

    :param _offset: Offset of data field in the register (set)
    """
    return from_reg_unsigned(_offset, 1, xform=lambda obj, x: bool(x))
import logging
from pymoku._oscilloscope import _CoreOscilloscope
from pymoku import _utils
from pymoku._pid import PID
from pymoku._sweep_generator import SweepGenerator
from pymoku._iir_block import IIRBlock
from pymoku._embedded_pll import EmbeddedPLL
from pymoku._instrument import needs_commit
from pymoku._instrument import ValueOutOfRangeException
from pymoku._instrument import to_reg_unsigned
from pymoku._instrument import from_reg_unsigned
from pymoku._instrument import to_reg_signed
from pymoku._instrument import from_reg_signed
from pymoku._instrument import to_reg_bool
from pymoku._instrument import from_reg_bool
        :type mode: string, {'auto', 'normal'}
        :param mode: Trigger mode.

        :type trig_on_scan_rising: bool
        :param trig_on_scan_rising: trigger only during rising portion of scan
            signal.
        """
        _utils.check_parameter_valid(
            'set',
            source,
            ['in1', 'in2', 'scan', 'A', 'B', 'ext'],
            'trigger source')
        _utils.check_parameter_valid(
            'set',
            edge,
            ['rising', 'falling', 'both'],
            'trigger edge')
        _utils.check_parameter_valid(
            'range',
            level,
            [-10.0, 10.0],
            desc='trigger level',
            units='volts')

        if minwidth is not None:
            _utils.check_parameter_valid(
                'range',
                minwidth,
                [0, 2**32 / 62.5e6],
                desc='PWM triggering minwidth',
                units='seconds')
        if maxwidth is not None:
            _utils.check_parameter_valid(
                'range',
                minwidth,
                [0, 2**32 / 62.5e6],
                desc='PWM triggering maxwidth',
                units='seconds')

        _utils.check_parameter_valid(
            'range',
            hysteresis,
            [100.0e-6, 1.0],
            desc='hysteresis',
            units='volts')
        _utils.check_parameter_valid(
            'set',
            hf_reject,
            [True, False],
            'hf reject')
        _utils.check_parameter_valid(
            'set',
            mode,
            ['auto', 'normal'],
            'trigger mode')
        _utils.check_parameter_valid(
            'set',
            trig_on_scan_rising,
            [True, False],
            'trigger only on scan rising edge')

        if source == 'scan':
            self.trig_aux = 1
        else:
            self.trig_aux = 0

        if trig_on_scan_rising:
            self.cond_trig = 1
        else:
            self.cond_trig = 0

        # Define the trigger sources appropriate to the LaserLockBox instrument
        source = _utils.str_to_val(_LLB_OSC_SOURCES, source, 'trigger source')
        # This function is the portion of set_trigger shared
        # among instruments with embedded scopes.
        self._set_trigger(source, edge, level, minwidth, maxwidth,
                          hysteresis, hf_reject, mode)

    @needs_commit
    def set_monitor(self, monitor_ch, source):
        """
        Select the point inside the laser lock box to monitor.

        There are two monitoring channels available, 'A' and 'B'; you can mux
        any of the internal
        monitoring points to either of these channels.

        The source is one of:
            - **error_signal**: error signal (after low-pass filter)
            - **pid_fast**: output of the fast pid
            - **pid_slow**: output of the slow pid
            - **offset_fast**: offset on the input to the fast pid
            - **in1**: input channel 1
            - **in2**: input channel 2
            - **out1**: output channel 1
            - **out2**: output channel 2
            - **scan**: scan signal
            - **lo**: local oscillator signal
            - **aux**: auxiliary sinewave signal

        :type monitor_ch: string; {'A','B'}
        :param monitor_ch: Monitor channel
        :type source: string; {'error', 'pid_fast', 'pid_slow',
            'offset_fast', 'offset_slow', 'in1', 'in2', 'out1', 'out2',
            'scan', 'lo', 'aux', 'slow_scan'}
        :param source: Signal to monitor
        """
        _utils.check_parameter_valid(
            'string', monitor_ch, desc="monitor channel")
        _utils.check_parameter_valid(
            'string', source, desc="monitor signal")

        monitor_ch = monitor_ch.lower()
        source = source.lower()

        _utils.check_parameter_valid(
            'set',
            monitor_ch,
            allowed=['a', 'b'],
            desc="monitor channel")

        _utils.check_parameter_valid(