class MokuInstrument(object):
    """Superclass for all Instruments that may be attached to a :any:`Moku`
    object.

    Should never be instantiated directly; instead, instantiate the subclass
    of the instrument you wish to run (e.g. :any:`Oscilloscope`,
    :any:`WaveformGenerator`)
    """

    def __init__(self):
        """ Must be called as the first line from any child
        implementations. """
        self._accessor_dict = {}
        self._moku = None
        self._remoteregs = [None] * 128
        self._localregs = [None] * 128
        self._running = False
        self._stateid = 0

        self.id = 0
        self.type = "Dummy Instrument"

        self._register_accessors(_instr_reg_handlers)

    def _register_accessors(self, accessor_dict):
        self._accessor_dict.update(accessor_dict)

    def _accessor_get(self, reg, get_xform):
        # Return local if present. Support a single register or a
        # tuple of registers
        try:
            c = [self._localregs[r] if self._localregs[r] is not None
                 else self._remoteregs[r] or 0 for r in reg]
            if all(i is not None for i in c):
                return get_xform(self, c)
        except TypeError:
            c = self._localregs[reg] if self._localregs[reg] is not None \
                else self._remoteregs[reg] or 0
            if c is not None:
                return get_xform(self, c)

    def _accessor_set(self, reg, set_xform, data):
        # Support a single register or a tuple of registers
        try:
            old = [self._localregs[r] if self._localregs[r] is not None
                   else self._remoteregs[r] or 0 for r in reg]
        except TypeError:
            old = self._localregs[reg] if self._localregs[reg] is not None \
                else self._remoteregs[reg] or 0

        new = set_xform(self, data, old)
        if new is None:
            raise ValueOutOfRangeException("Reg %d Data %d" % (reg, data))

        try:
            for r, n in zip(reg, new):
                self._localregs[r] = n
        except TypeError:
            self._localregs[reg] = new

    def __getattr__(self, name):
        if name != '_accessor_dict' and name in self._accessor_dict:
            reg, set_xform, get_xform = self._accessor_dict[name]
            return self._accessor_get(reg, get_xform)
        else:
            raise AttributeError("No Attribute %s" % name)

    def __setattr__(self, name, value):
        if name != '_accessor_dict' and name in self._accessor_dict:
            reg, set_xform, get_xform = self._accessor_dict[name]
            return self._accessor_set(reg, set_xform, value)
        else:
            return super(MokuInstrument, self).__setattr__(name, value)

    @needs_commit
    def set_defaults(self):
        """ Can be extended in implementations to set initial state """

        # Base implementation: load DAC calibration values in to the bitstream.
        o1, o1t, o2, o2t = self._dac_offsets()
        self.dac1_offset = o1
        self.dac1_offset_t = o1t
        self.dac2_offset = o2
        self.dac2_offset_t = o2t

        # These may be called again in the instrument's implementation to
        # overwrite this, however they must be called at least once to load
        # the initial calibration values
        self._set_frontend(1, fiftyr=True, atten=False, ac=False)
        self._set_frontend(2, fiftyr=True, atten=False, ac=False)

    def attach_moku(self, moku):
        self._moku = moku
        try:
            self.calibration = dict(
                self._moku._get_property_section("calibration"))
        except Exception:
            log.warning("Can't read calibration values.")

    def _commit(self, update_state=True):
        if self._moku is None:
            raise NotDeployedException()
        if update_state:
            # Some statid docco says 8-bits, some 16.
            self._stateid = (self._stateid + 1) % 256
            self.state_id = self._stateid
            self.state_id_alt = self._stateid
        regs = [(i, d) for i, d in enumerate(self._localregs)
                if d is not None]
        # TODO: Save this register set against stateid to be retrieved later
        log.debug("Committing reg set %s", str(regs))
        self._moku._write_regs(regs)
        self._remoteregs = [l if l is not None else r for l, r
                            in zip(self._localregs, self._remoteregs)]
        self._localregs = [None] * 128

    def commit(self):
        """
        Apply all modified settings.

        .. note::

            If the `autocommit` feature has been turned off, this function
            can be used to manually apply any instrument settings to the Moku
            device. These instrument settings are those configured by calling
            all *set_* and *gen_* type functions. Manually calling this
            function allows you to atomically apply many instrument settings
            at once.
        """
        # We wrap up the implementation of the actual commit so we dont
        # expose the update_state parameter to normal users
        return self._commit(update_state=True)

    def check_uncommitted_state(self):
        return any(self._localregs)

    def _sync_registers(self):
        """
        Reload state from the Moku.

        This should never have to be called explicitly, however in advanced
        operation where the Moku state is being updated outside of pymoku,
        this will give the user access to those modified states through
        their attributes or accessors
        """
        if self._moku is None:
            raise NotDeployedException()
        self._remoteregs = [val for reg, val
                            in self._moku._read_regs(list(range(128)))]
        self._stateid = self.state_id
        self._on_reg_sync()

    def _on_reg_sync(self):
        # This function acts when a Moku's register state is
        # synchronised to the local Moku instance. It is useful to update
        # local variables and perform sanity checks on the device state
        # before it is deemed 'useable'.

        # Denotes the list of registers we expect to be non-zero on
        # instrument deploy Control registers [0:4], DAC Test [26],
        # State ID [63]
        reg_blacklist = [0, 1, 2, 3, 26, 63]

        # Get all register values that aren't blacklisted
        reg_data = [d for i, d in enumerate(self._remoteregs)
                    if i not in reg_blacklist]
        log.debug(reg_data)
        # If none of these register values are non-zero, assume the
        # instrument has not been
        # configured and set defaults
        if not any(reg_data):
            log.warning(
                "Moku seems to be unconfigured - setting instrument defaults.")
            self.set_defaults()
            self.commit()

        return

    def _dump_remote_regs(self):
        """
        Return the current register state of the Moku.

        This should never have to be called explictly, however in advanced
        operation where the Moku state is being updated outside of pymoku,
        this gives the user access to the register values directly.

        Unlike :any:`sync_registers`, no local state is updated to reflect
        these register values and they are not made available through
         attributes or accessors.
        """
        return self._moku._read_regs(list(range(128)))

    def _set_running(self, state):
        """
        Set the local instrument object running state

        This is used to clean up helper threads and sockets. Should never be
        called explicitly.
        """
        self._running = state

    def _set_instrument_active(self, active):
        """
        Assert or release the intrument reset line on the device.

        This should never have to be called explicitly, as the instrument is
        correctly reset when it is attached and detached. In advanced
        operation, this can be used to force the instrument in to its
        initial state without a redeploy.
        """
        reg = (INSTR_RST if not active else 0)
        self._localregs[REG_CTL] = reg
        self._commit(update_state=False)

    @needs_commit
    def _set_frontend(self, channel, fiftyr, atten, ac):
        # Set the analog frontend configuration
        relays = RELAY_LOWZ if fiftyr else 0
        relays |= RELAY_LOWG if atten else 0
        relays |= RELAY_DC if not ac else 0

        off1, off1_t, off2, off2_t = self._adc_offsets()

        if channel == 1:
            self.relays_ch1 = relays
            self.adc1_offset = off1
            self.adc1_offset_t = off1_t
        elif channel == 2:
            self.relays_ch2 = relays
            self.adc2_offset = off2
            self.adc2_offset_t = off2_t

    def _get_frontend(self, channel):
        # Get the analog frontend
        if channel == 1:
            r = self.relays_ch1
        elif channel == 2:
            r = self.relays_ch2

        return [bool(r & RELAY_LOWZ),
                bool(r & RELAY_LOWG),
                not bool(r & RELAY_DC)]

    def _dac_gains(self):
        g1s = "calibration.DG-1"
        g2s = "calibration.DG-2"
        gt1s = "calibration.DGT-1"
        gt2s = "calibration.DGT-2"

        try:
            g1 = float(self.calibration[g1s])
            g2 = float(self.calibration[g2s])
            gt1 = float(self.calibration[gt1s])
            gt2 = float(self.calibration[gt2s])
        except (KeyError, TypeError):
            log.warning("Moku appears uncalibrated")
            g1 = g2 = 1
            gt1 = gt2 = 0

        # For now, assume a fixed 48 degrees C board temperature. In future,
        # should read temperature registers
        g1 += gt1 * 48.0
        g2 += gt2 * 48.0

        # The sense of these parameters as used in pymoku is inverted from the
        # storage on the Moku
        g1 = 1 / g1
        g2 = 1 / g2

        return g1, g2

    def _dac_offsets(self):
        o1s = "calibration.DO-1"
        o2s = "calibration.DO-2"
        ot1s = "calibration.DOT-1"
        ot2s = "calibration.DOT-2"

        try:
            o1 = float(self.calibration[o1s])
            o2 = float(self.calibration[o2s])
            ot1 = float(self.calibration[ot1s])
            ot2 = float(self.calibration[ot2s])
        except (KeyError, TypeError):
            log.warning("Moku appears uncalibrated")
            o1 = o2 = 0
            ot1 = ot2 = 0

        return o1, ot1, o2, ot2

    def _adc_gains(self):
        relay_string_1 = '-'.join(
            ("50" if self.relays_ch1 & RELAY_LOWZ else "1M",
             "L" if self.relays_ch1 & RELAY_LOWG else "H",
             "D" if self.relays_ch1 & RELAY_DC else "A"))

        relay_string_2 = '-'.join(
            ("50" if self.relays_ch2 & RELAY_LOWZ else "1M",
             "L" if self.relays_ch2 & RELAY_LOWG else "H",
             "D" if self.relays_ch2 & RELAY_DC else "A"))

        g1s = "calibration.AG-%s-1" % relay_string_1
        g2s = "calibration.AG-%s-2" % relay_string_2

        gt1s = "calibration.AGT-%s-1" % relay_string_1
        gt2s = "calibration.AGT-%s-2" % relay_string_2

        try:
            g1 = float(self.calibration[g1s])
            g2 = float(self.calibration[g2s])
            gt1 = float(self.calibration[gt1s])
            gt2 = float(self.calibration[gt2s])
        except (KeyError, TypeError):
            log.warning("Moku appears uncalibrated")
            g1 = g2 = 1
            gt1 = gt2 = 0

        # For now, assume a fixed 48 degrees C board temperature. In future,
        # should read temperature registers
        g1 += gt1 * 48.0
        g2 += gt2 * 48.0

        # The sense of these parameters as used in pymoku is inverted from
        # the storage on the Moku
        g1 = 1 / g1
        g2 = 1 / g2

        return g1, g2

    def _adc_offsets(self):
        relay_string_1 = '-'.join(
            ("50" if self.relays_ch1 & RELAY_LOWZ else "1M",
             "L" if self.relays_ch1 & RELAY_LOWG else "H",
             "D" if self.relays_ch1 & RELAY_DC else "A"))

        relay_string_2 = '-'.join(
            ("50" if self.relays_ch2 & RELAY_LOWZ else "1M",
             "L" if self.relays_ch2 & RELAY_LOWG else "H",
             "D" if self.relays_ch2 & RELAY_DC else "A"))

        o1s = "calibration.AO-%s-1" % relay_string_1
        o2s = "calibration.AO-%s-2" % relay_string_2
        ot1s = "calibration.AOT-%s-1" % relay_string_1
        ot2s = "calibration.AOT-%s-2" % relay_string_2

        try:
            o1 = float(self.calibration[o1s])
            o2 = float(self.calibration[o2s])
            ot1 = float(self.calibration[ot1s])
            ot2 = float(self.calibration[ot2s])
        except (KeyError, TypeError):
            log.warning("Moku appears uncalibrated")
            o1 = o2 = 0
            ot1 = ot2 = 0

        return o1, ot1, o2, ot2

    @needs_commit
    def _set_pause(self, pause):
        self.pause = pause

    def _get_pause(self):
        return self.pause

    def _load_feature(self, index):
        # For now we don't support switching clock modes during a partial
        # deploy
        self._moku._deploy(
            use_external=self._moku.external_reference, partial_index=index)

    @needs_commit
    def _set_mmap_access(self, access):
        self.mmap_access = access 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)) 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 SWEEP = 2 FULL_FRAME = 0 RDR_DDS = 3 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 FrameQueue(Queue):
class FrameBasedInstrument(_input_instrument.InputInstrument,
                           _instrument.MokuInstrument):
    def put(self, item, block=True, timeout=None):
    def get(self, block=True, timeout=None):
    def _init(self, maxsize):
    def __init__(self):
    def _set_frame_class(self, frame_class, **frame_kwargs):
    def _flush(self):
    def _set_buffer_length(self, buflen):
    def _get_buffer_length(self):
    def set_defaults(self):
    def get_data(self, timeout=None, wait=True):
    def set_framerate(self, fr):
    def get_realtime_data(self, timeout=None, wait=True):
    def _set_running(self, state):
    def _make_frame_socket(self):
    def _frame_worker(self): 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 FRAData(_frame_instrument.InstrumentData):
    """
    Object representing a frame of dual-channel (amplitude and phase) vs
    frequency response data.

    This is the native output format of the :any:`FrequencyResponseAnalyzer`
    instrument.

    This object should not be instantiated directly, but will be returned by
    a call to :any:`get_data
    <pymoku.instruments.FrequencyResponseAnalyzer.get_data>` on the
    associated :any:`FrequencyResponseAnalyzer` instrument.

    - ``ch1.magnitude`` = ``[CH1_MAG_DATA]``
    - ``ch1.magnitude_dB`` = ``[CH1_MAG_DATA_DB]``
    - ``ch1.phase`` = ``[CH1_PHASE_DATA]``
    - ``ch2.magnitude`` = ``[CH2_MAG_DATA]``
    - ``ch2.magnitude_dB`` = ``[CH2_MAG_DATA_DB]``
    - ``ch2.phase`` = ``[CH2_PHASE_DATA]``
    - ``frequency`` = ``[FREQ]``
    - ``waveformid`` = ``n``

    """
    def __init__(self, instrument, scales):
        super(FRAData, self).__init__(instrument)

        # The frequency range associated with both channels
        self.frequency = []

        # Obtain all data scaling factors relevant to current NetAn
        # configuration
        self.scales = scales

    def __json__(self):
        # Annoying this doesn't recursively-descend, I thought it did.
        # Manually serialise the children for now
        return {'ch1': self.ch1.__json__(),
                'ch2': self.ch2.__json__(),
                'frequency': self.frequency,
                'waveform_id': self.waveformid}

    def process_complete(self):
        super(FRAData, self).process_complete()

        if self._stateid not in self.scales:
            # log.debug("Can't render FRAData frame, haven't saved calibration
            # data for state %d", self._stateid)
            self.complete = False
            return

        # Get scaling/correction factors based on current instrument
        # configuration
        scales = self.scales[self._stateid]

        try:
            self.frequency = scales['frequency_axis']

            smpls = int(len(self._raw1) / 4)
            dat = struct.unpack('<' + 'i' * smpls, self._raw1)
            dat = [x if x != -0x80000000 else None for x in dat]

            self.ch1_bits = [float(x) if x is not None else None for x in dat]
            self.ch1 = _FRAChannelData(self.ch1_bits,
                                       scales['gain_correction'],
                                       scales['g1'],
                                       scales['sweep_amplitude_ch1'])

            smpls = int(len(self._raw2) / 4)
            dat = struct.unpack('<' + 'i' * smpls, self._raw2)
            dat = [x if x != -0x80000000 else None for x in dat]

            self.ch2_bits = [float(x) if x is not None else None for x in dat]
            self.ch2 = _FRAChannelData(self.ch2_bits,
                                       scales['gain_correction'],
                                       scales['g2'],
                                       scales['sweep_amplitude_ch2'])

        except (IndexError, TypeError, struct.error):
            # If the data is bollocksed, force a reinitialisation on next
            # packet
            self.frameid = None
            self.complete = False

        # A valid frame is there's at least one valid sample in each channel
        return self.ch1 and self.ch2
import math
import logging
from pymoku._instrument import MokuInstrument
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
from pymoku._instrument import needs_commit
from pymoku._instrument import SWEEP
from pymoku._instrument import FULL_FRAME
from pymoku._instrument import RDR_DDS
from pymoku._instrument import ValueOutOfRangeException
from pymoku import _frame_instrument
from pymoku import _utils
from ._frequency_response_analyzer_data import FRAData
        xmode = _utils.str_to_val(_str_to_xmode, xmode, 'X-mode')
        self.x_mode = xmode

    @needs_commit
    def set_defaults(self):
        """ Reset the Frequency Response Analyzer to sane defaults """
        super(FrequencyResponseAnalyzer, self).set_defaults()
        self.frame_length = _FRA_SCREEN_WIDTH

        self.x_mode = FULL_FRAME
        self.render_mode = RDR_DDS
        self.framerate = 10

        self.en_in_ch1 = True
        self.en_in_ch2 = True

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

        self.set_sweep()
        self.set_ch_phase(1)
        self.set_ch_phase(2)

        self.set_harmonic_multiplier(1)
        self.set_harmonic_multiplier(2)
        # 100mVpp swept outputs
        self.set_output(1, 0.1, 0.0)
        self.set_output(2, 0.1, 0.0)

        self.set_input_range(1, 10)
        self.set_input_range(2, 10)

        self.start_sweep()

    @needs_commit
    def set_ch_phase(self, ch, phase=0.0):
        """ set the phase of the measurement with respect to the output signal.

        :type ch: int; {1, 2}
        :param ch: selects the channel to apply settings.

        :type phase: float [0.0, 360.0] deg
        :param phase: phase difference between channel 1 and channel 2.
        """
        _utils.check_parameter_valid('set', ch, [1, 2], 'channel')
        _utils.check_parameter_valid('range', phase, [0.0, 360.0],
                                     'phase', 'degrees')
        if ch == 1:
            self.ch1_meas_phase = (phase / 360.0) * 2**64
        else:
            self.ch2_meas_phase = (phase / 360.0) * 2**64

    @needs_commit
    def set_harmonic_multiplier(self, ch, multiplier=1):
        """ set the harmonic multiplier to demodulate at integer harmonic of
            output.

        :type ch: int; {1, 2}
        :param ch: selects the channel to apply settings.

        :type multiplier: int; [0, 15]
        :param multiplier: multiplier applied to fundemental.
        """
        _utils.check_parameter_valid('set', ch, [1, 2], 'channel')
        _utils.check_parameter_valid('set', multiplier,
                                     [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
                                      13, 14, 15],
                                     'harmonics select')
        if ch == 1:
            self.ch1_harmonic_mult = multiplier
        elif ch == 2:
            self.ch2_harmonic_mult = multiplier

    def get_data(self, timeout=None, wait=True):
        """ Get current sweep data.
        In the FrequencyResponseAnalyzer this is an alias for
        ``get_realtime_data`` as the data is never downsampled.
        """
        return super(FrequencyResponseAnalyzer,
                     self).get_realtime_data(timeout, wait)

    def commit(self):
        # Restart the sweep as instrument settings are being changed
        self._restart_sweep()
        super(FrequencyResponseAnalyzer, self).commit()

        # Update the scaling factors for processing of incoming frames
        # stateid allows us to track which scales correspond to which register
        # state
        self.scales[self._stateid] = self._calculate_scales()
    commit.__doc__ = MokuInstrument.commit.__doc__


_fra_reg_handlers = {
    'loop_sweep':
        (REG_FRA_ENABLES,
            to_reg_bool(0),
            from_reg_bool(0)),
    'single_sweep':
        (REG_FRA_ENABLES,
            to_reg_bool(1),
            from_reg_bool(1)),
    'sweep_reset':
        (REG_FRA_ENABLES,
            to_reg_bool(2),
            from_reg_bool(2)),
    'channel1_en':
        (REG_FRA_ENABLES,
            to_reg_bool(3),
            from_reg_bool(3)),
    'channel2_en':
        (REG_FRA_ENABLES,
            to_reg_bool(4),
            from_reg_bool(4)),
    'adc1_en':
        (REG_FRA_ENABLES,
            to_reg_bool(5),
            from_reg_bool(5)),
    'adc2_en':
        (REG_FRA_ENABLES,

            to_reg_bool(6),