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)) 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 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 ADC_SMP_RATE = 500e6 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): 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): 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():
import logging
import math
from pymoku._instrument import to_reg_bool
from pymoku._instrument import from_reg_bool
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 needs_commit
from pymoku._instrument import ADC_SMP_RATE
from pymoku._instrument import InvalidConfigurationException
from pymoku._instrument import ValueOutOfRangeException
from pymoku._oscilloscope import _CoreOscilloscope
from pymoku import _utils
        (REG_PID_CH0_CH1GAIN,
            to_reg_signed(0, 16,
                          xform=lambda obj,
                          x: x * 2.0**8 * obj._adc_gains()[0]),
            from_reg_signed(0, 16,
                            xform=lambda obj,
                            x: x / 2.0**8 / obj._adc_gains()[0])),

    'ch2_ch1_gain':
        ((REG_PID_CH1_CH0GAIN_MSB, REG_PID_CH1_CH0GAIN_LSB),
            to_reg_signed(24, 16,
                          xform=lambda obj,
                          x: x * 2.0**8 * obj._adc_gains()[1]),
            from_reg_signed(24, 16,
                            xform=lambda obj,
                            x: x / 2.0**8 / obj._adc_gains()[1])),

    'ch2_ch2_gain':
        (REG_PID_CH1_CH1GAIN,
            to_reg_signed(16, 16,
                          xform=lambda obj,
                          x: x * 2.0**8 * obj._adc_gains()[1]),
            from_reg_signed(16, 16,
                            xform=lambda obj,
                            x: x / 2.0**8 / obj._adc_gains()[1])),

    'ch1_pid1_in_offset':
        (REG_PID_CH0_OFFSET1,
            to_reg_signed(0, 16,
                          xform=lambda obj, x: x / obj._dac_gains()[0]),
            from_reg_signed(0, 16,
                            xform=lambda obj, x: x * obj._dac_gains()[0])),

    'ch1_pid2_in_offset':
        (REG_PID_CH0_OFFSET2,
            to_reg_signed(0, 16,
                          xform=lambda obj, x: x / obj._dac_gains()[0]),
            from_reg_signed(0, 16,
                            xform=lambda obj, x: x * obj._dac_gains()[0])),

    'ch1_pid1_out_offset':
        (REG_PID_CH0_OFFSET1,
            to_reg_signed(16, 16,
                          xform=lambda obj, x: x / obj._dac_gains()[0]),
            from_reg_signed(16, 16,
                            xform=lambda obj, x: x * obj._dac_gains()[0])),

    'ch1_pid2_out_offset':
        (REG_PID_CH0_OFFSET2,
            to_reg_signed(16, 16,
                          xform=lambda obj, x: x / obj._dac_gains()[0]),
            from_reg_signed(16, 16,
                            xform=lambda obj, x: x * obj._dac_gains()[0])),

    'ch1_pid1_pidgain':
        (REG_PID_CH0_PIDGAIN1,
            to_reg_signed(0, 32, xform=lambda obj, x: x),
            from_reg_signed(0, 32, xform=lambda obj, x: x)),

    'ch1_pid2_pidgain':
        (REG_PID_CH0_PIDGAIN2,
            to_reg_signed(0, 32, xform=lambda obj, x: x),
            from_reg_signed(0, 32, xform=lambda obj, x: x)),

    'ch1_pid1_int_i_gain':
        (REG_PID_CH0_INT_IGAIN1,
         to_reg_unsigned(0, 24, xform=lambda obj, x: x * (2.0 ** 24 - 1)),
            from_reg_unsigned(0, 24, xform=lambda obj, x: x / (2.0**24 - 1))),

    # TODO: This concerns me, not sure if its writing to the registers as
    # expected
    'ch1_pid2_int_i_gain':
        ((REG_PID_CH0_INT_IGAIN2_MSB, REG_PID_CH0_INT_IGAIN2_LSB),
            to_reg_unsigned(24, 24, xform=lambda obj, x: x * (2.0**24 - 1)),
            from_reg_unsigned(24, 24, xform=lambda obj, x: x / (2.0**24 - 1))),

    'ch1_pid1_int_ifb_gain':
        ((REG_PID_CH0_INT_IFBGAIN1_MSB, REG_PID_CH0_INT_IFBGAIN1_LSB),
            to_reg_unsigned(16, 24, xform=lambda obj, x: x * (2.0**24 - 1)),
            from_reg_unsigned(16, 24, xform=lambda obj, x: x / (2.0**24 - 1))),

    'ch1_pid2_int_ifb_gain':
        (REG_PID_CH0_INT_IFBGAIN2,
         to_reg_unsigned(8, 24, xform=lambda obj, x: x * (2.0 ** 24 - 1)),
         from_reg_unsigned(8, 24, xform=lambda obj, x: x / (2.0 ** 24 - 1))),

    'ch1_pid1_int_p_gain':
        (REG_PID_CH0_INT_PGAIN1,
            to_reg_unsigned(0, 24, xform=lambda obj, x: x * 2.0**11),
            from_reg_unsigned(0, 24, xform=lambda obj, x: x / 2.0**11)),

    'ch1_pid2_int_p_gain':
        ((REG_PID_CH0_INT_PGAIN2_MSB, REG_PID_CH0_INT_PGAIN2_LSB),
            to_reg_unsigned(24, 24, xform=lambda obj, x: x * 2.0**11),
            from_reg_unsigned(24, 24, xform=lambda obj, x: x / 2.0**11)),

    'ch1_pid1_diff_p_gain':
        (REG_PID_CH0_DIFF_PGAIN1,
            to_reg_unsigned(0, 24, xform=lambda obj, x: x * 2.0**11),
            from_reg_unsigned(0, 24, xform=lambda obj, x: x / 2.0**11)),

    'ch1_pid1_diff_i_gain':
        ((REG_PID_CH0_DIFF_IGAIN1_MSB, REG_PID_CH0_DIFF_IGAIN1_LSB),
            to_reg_unsigned(16, 24),
            from_reg_unsigned(16, 24)),

    'ch1_pid1_diff_ifb_gain':
        (REG_PID_CH0_DIFF_IFBGAIN1,
            to_reg_unsigned(0, 24, xform=lambda obj, x: x * (2.0**24 - 1)),
            from_reg_unsigned(0, 24, xform=lambda obj, x: x / (2.0**24 - 1))),

    'ch2_pid1_in_offset':
        (REG_PID_CH1_OFFSET1,
            to_reg_signed(0, 16,
                          xform=lambda obj, x: x / obj._dac_gains()[1]),
            from_reg_signed(0, 16,
                            xform=lambda obj, x: x * obj._dac_gains()[1])),

    'ch2_pid2_in_offset':
        (REG_PID_CH1_OFFSET2,

            to_reg_signed(0, 16,