Example #1
0
class PNABase(VisaInstrument):
    """
    Base qcodes driver for Agilent/Keysight series PNAs
    http://na.support.keysight.com/pna/help/latest/Programming/GP-IB_Command_Finder/SCPI_Command_Tree.htm

    Note: Currently this driver only expects a single channel on the PNA. We
          can handle multiple traces, but using traces across multiple channels
          may have unexpected results.
    """
    def __init__(
            self,
            name: str,
            address: str,
            # Set frequency ranges
            min_freq: Union[int, float],
            max_freq: Union[int, float],
            # Set power ranges
            min_power: Union[int, float],
            max_power: Union[int, float],
            nports: int,  # Number of ports on the PNA
            **kwargs: Any) -> None:
        super().__init__(name, address, terminator='\n', **kwargs)
        self.min_freq = min_freq
        self.max_freq = max_freq

        #Ports
        ports = ChannelList(self, "PNAPorts", PNAPort)
        for port_num in range(1, nports + 1):
            port = PNAPort(self, f"port{port_num}", port_num, min_power,
                           max_power)
            ports.append(port)
            self.add_submodule(f"port{port_num}", port)
        ports.lock()
        self.add_submodule("ports", ports)

        # Drive power
        self.add_parameter('power',
                           label='Power',
                           get_cmd='SOUR:POW?',
                           get_parser=float,
                           set_cmd='SOUR:POW {:.2f}',
                           unit='dBm',
                           vals=Numbers(min_value=min_power,
                                        max_value=max_power))

        # IF bandwidth
        self.add_parameter('if_bandwidth',
                           label='IF Bandwidth',
                           get_cmd='SENS:BAND?',
                           get_parser=float,
                           set_cmd='SENS:BAND {:.2f}',
                           unit='Hz',
                           vals=Numbers(min_value=1, max_value=15e6))

        # Number of averages (also resets averages)
        self.add_parameter('averages_enabled',
                           label='Averages Enabled',
                           get_cmd="SENS:AVER?",
                           set_cmd="SENS:AVER {}",
                           val_mapping={
                               True: '1',
                               False: '0'
                           })
        self.add_parameter('averages',
                           label='Averages',
                           get_cmd='SENS:AVER:COUN?',
                           get_parser=int,
                           set_cmd='SENS:AVER:COUN {:d}',
                           unit='',
                           vals=Numbers(min_value=1, max_value=65536))

        # Setting frequency range
        self.add_parameter('start',
                           label='Start Frequency',
                           get_cmd='SENS:FREQ:STAR?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:STAR {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter('stop',
                           label='Stop Frequency',
                           get_cmd='SENS:FREQ:STOP?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:STOP {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter('center',
                           label='Center Frequency',
                           get_cmd='SENS:FREQ:CENT?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:CENT {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter('span',
                           label='Frequency Span',
                           get_cmd='SENS:FREQ:SPAN?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:SPAN {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))

        # Number of points in a sweep
        self.add_parameter('points',
                           label='Points',
                           get_cmd='SENS:SWE:POIN?',
                           get_parser=int,
                           set_cmd='SENS:SWE:POIN {}',
                           unit='',
                           vals=Numbers(min_value=1, max_value=100001))

        # Electrical delay
        self.add_parameter('electrical_delay',
                           label='Electrical Delay',
                           get_cmd='CALC:CORR:EDEL:TIME?',
                           get_parser=float,
                           set_cmd='CALC:CORR:EDEL:TIME {:.6e}',
                           unit='s',
                           vals=Numbers(min_value=0, max_value=100000))

        # Sweep Time
        self.add_parameter('sweep_time',
                           label='Time',
                           get_cmd='SENS:SWE:TIME?',
                           get_parser=float,
                           unit='s',
                           vals=Numbers(0, 1e6))
        # Sweep Mode
        self.add_parameter('sweep_mode',
                           label='Mode',
                           get_cmd='SENS:SWE:MODE?',
                           set_cmd='SENS:SWE:MODE {}',
                           vals=Enum("HOLD", "CONT", "GRO", "SING"))
        # Group trigger count
        self.add_parameter('group_trigger_count',
                           get_cmd="SENS:SWE:GRO:COUN?",
                           get_parser=int,
                           set_cmd="SENS:SWE:GRO:COUN {}",
                           vals=Ints(1, 2000000))
        # Trigger Source
        self.add_parameter('trigger_source',
                           get_cmd="TRIG:SOUR?",
                           set_cmd="TRIG:SOUR {}",
                           vals=Enum("EXT", "IMM", "MAN"))

        # Traces
        self.add_parameter('active_trace',
                           label='Active Trace',
                           get_cmd="CALC:PAR:MNUM?",
                           get_parser=int,
                           set_cmd="CALC:PAR:MNUM {}",
                           vals=Numbers(min_value=1, max_value=24))
        # Note: Traces will be accessed through the traces property which
        # updates the channellist to include only active trace numbers
        self._traces = ChannelList(self, "PNATraces", PNATrace)
        self.add_submodule("traces", self._traces)
        # Add shortcuts to first trace
        trace1 = self.traces[0]
        for param in trace1.parameters.values():
            self.parameters[param.name] = param
        # And also add a link to run sweep
        self.run_sweep = trace1.run_sweep
        # Set this trace to be the default (it's possible to end up in a
        # situation where no traces are selected, causing parameter snapshots
        # to fail)
        self.active_trace(trace1.trace_num)

        # Set auto_sweep parameter
        # If we want to return multiple traces per setpoint without sweeping
        # multiple times, we should set this to false
        self.add_parameter('auto_sweep',
                           label='Auto Sweep',
                           set_cmd=None,
                           get_cmd=None,
                           vals=Bool(),
                           initial_value=True)

        # A default output format on initialisation
        self.write('FORM REAL,32')
        self.write('FORM:BORD NORM')

        self.connect_message()

    @property
    def traces(self) -> ChannelList:
        """
        Update channel list with active traces and return the new list
        """
        # Keep track of which trace was active before. This command may fail
        # if no traces were selected.
        try:
            active_trace = self.active_trace()
        except VisaIOError as e:
            if e.error_code == errors.StatusCode.error_timeout:
                active_trace = None
            else:
                raise

        # Get a list of traces from the instrument and fill in the traces list
        parlist = self.get_trace_catalog().split(",")
        self._traces.clear()
        for trace_name in parlist[::2]:
            trace_num = self.select_trace_by_name(trace_name)
            pna_trace = PNATrace(self, "tr{}".format(trace_num), trace_name,
                                 trace_num)
            self._traces.append(pna_trace)

        # Restore the active trace if there was one
        if active_trace:
            self.active_trace(active_trace)

        # Return the list of traces on the instrument
        return self._traces

    def get_options(self) -> Sequence[str]:
        # Query the instrument for what options are installed
        return self.ask('*OPT?').strip('"').split(',')

    def get_trace_catalog(self):
        """
        Get the trace catalog, that is a list of trace and sweep types
        from the PNA.

        The format of the returned trace is:
            trace_name,trace_type,trace_name,trace_type...
        """
        return self.ask("CALC:PAR:CAT:EXT?").strip('"')

    def select_trace_by_name(self, trace_name: str) -> int:
        """
        Select a trace on the PNA by name.

        Returns:
            The trace number of the selected trace
        """
        self.write(f"CALC:PAR:SEL '{trace_name}'")
        return self.active_trace()

    def reset_averages(self):
        """
        Reset averaging
        """
        self.write("SENS:AVER:CLE")

    def averages_on(self):
        """
        Turn on trace averaging
        """
        self.averages_enabled(True)

    def averages_off(self):
        """
        Turn off trace averaging
        """
        self.averages_enabled(False)

    def _set_power_limits(self, min_power: Union[int, float],
                          max_power: Union[int, float]) -> None:
        """
        Set port power limits
        """
        self.power.vals = Numbers(min_value=min_power, max_value=max_power)
        for port in self.ports:
            port._set_power_limits(min_power, max_power)
Example #2
0
class VNABase(VisaInstrument):
    """

    Note: Currently this driver only expects a single channel on the VNA. We
          can handle multiple traces, but using traces across multiple channels
          may have unexpected results.
    """
    def __init__(
            self,
            name: str,
            address: str,
            # Set frequency ranges
            min_freq: Union[int, float],
            max_freq: Union[int, float],
            # Set power ranges
            min_power: Union[int, float],
            max_power: Union[int, float],
            nports: int,  # Number of ports on the VNA
            **kwargs: Any) -> None:
        super().__init__(name, address, terminator='\n', **kwargs)
        self.min_freq = min_freq
        self.max_freq = max_freq

        #Ports
        ports = ChannelList(self, "VNAPorts", VNAPort)
        for port_num in range(1, nports + 1):
            port = VNAPort(self, f"port{port_num}", port_num, min_power,
                           max_power)
            ports.append(port)
            self.add_submodule(f"port{port_num}", port)
        ports.lock()
        self.add_submodule("ports", ports)

        # Drive power#only low and high
        self.add_parameter('power',
                           label='Power',
                           get_cmd=':SOUR:POW:PORT?',
                           get_parser=float,
                           set_parser=float,
                           set_cmd='SOUR:POW:PORT {:.2f}',
                           unit='dBm',
                           vals=Numbers(-30, 30))
        """
        self.add_parameter(name='power',
                           label='Power',
                           unit='dBm',
                           get_cmd=':SENS:FSEGM:POW:PORT1?',
                           set_cmd=':SENS:FSEGM:POW:PORT1 {:.4f}',
                           get_parser=float,
                           set_parser=float,
                           vals=Numbers(-30, 30))
        """
        self.add_parameter(name='frequencyList',
                           label='Frequency list',
                           unit='Hz',
                           get_cmd=':SENS:FREQ:DATA?',
                           set_cmd=':SENS:FREQ:DATA {:.4f}',
                           get_parser=float,
                           set_parser=float,
                           vals=Numbers(300e3, 20e9))
        # IF bandwidth

        self.add_parameter(name='if_bandwidth',
                           label='Intermediate Frequency Bandwidth',
                           unit='Hz',
                           get_cmd=':SENS:BWID?',
                           set_cmd=':SENS:BWID {}',
                           get_parser=float,
                           set_parser=float,
                           vals=Enum(10, 20, 30, 50, 70, 100, 200, 300, 500,
                                     700, 1000, 3000, 5000, 7000, 10000))
        """
        # Number of averages (also resets averages)
        self.add_parameter('averages_enabled',
                           label='Averages Enabled',
                           get_cmd="SENS:AVER?",
                           set_cmd="SENS:AVER {}",
                           val_mapping={True: '1', False: '0'})
        self.add_parameter('averages',
                           label='Averages',
                           get_cmd='SENS:AVER:COUN?',
                           get_parser=int,
                           set_cmd='SENS:AVER:COUN {:d}',
                           unit='',
                           vals=Numbers(min_value=1, max_value=65536))
        """

        # Setting frequency range

        self.add_parameter(name='start',
                           label='Start frequency',
                           unit='Hz',
                           get_cmd=':SENS:FREQ:STAR?',
                           set_cmd=':SENS:FREQ:STAR {:.4f}',
                           get_parser=float,
                           set_parser=float,
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))

        self.add_parameter(name='stop',
                           label='Stop frequency',
                           unit='Hz',
                           get_cmd=':SENS:FREQ:STOP?',
                           set_cmd=':SENS:FREQ:STOP {:.4f}',
                           get_parser=float,
                           set_parser=float,
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))

        self.add_parameter(name='center',
                           label='Center frequency',
                           unit='Hz',
                           get_cmd=':SENS:FREQ:CENT?',
                           set_cmd=':SENS:FREQ:CENT {:.4f}',
                           get_parser=float,
                           set_parser=float,
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter(name='span',
                           label='Frequency span',
                           unit='Hz',
                           get_cmd=':SENS:FREQ:SPAN?',
                           set_cmd=':SENS:FREQ:SPAN {:.4f}',
                           get_parser=float,
                           set_parser=float,
                           vals=Numbers(min_value=100, max_value=20e9))
        self.add_parameter(
            name='groupdelay',
            label='group delay in reference plane subsystem',
            unit='s',
            get_cmd=':SENS:CORR:EXT:PORT1?',
            set_cmd=':SENS:CORR:EXT:PORT1 {:.4f}',
            get_parser=float,
            set_parser=float,
        )

        # Number of points in a sweep

        self.add_parameter(name='points',
                           label='Number of measurement points',
                           unit='',
                           get_cmd=':SENS:SWE:POIN?',
                           set_cmd=':SENS:SWE:POIN {}',
                           get_parser=int,
                           set_parser=int,
                           vals=Numbers(2, 20001))

        # Sweep Time
        self.add_parameter('sweep_time',
                           label='Time',
                           get_cmd='SENS:SWE:TIME?',
                           get_parser=float,
                           unit='s',
                           vals=Numbers(0, 1e6))

        # Sweep Mode

        self.add_parameter(name='sweep_mode',
                           label='Hold_Function',
                           get_cmd=':SENS:HOLD:FUNC?',
                           set_cmd=':SENS:HOLD:FUNC {}',
                           get_parser=str,
                           vals=Enum('HOLD', 'hold', 'CONT', 'continuous',
                                     'SING', 'single'))
        """
        # Group trigger count
        self.add_parameter('group_trigger_count',
                           get_cmd="SENS:SWE:GRO:COUN?",
                           get_parser=int,
                           set_cmd="SENS:SWE:GRO:COUN {}",
                           vals=Ints(1, 2000000))
        # Trigger Source
        self.add_parameter('trigger_source',
                           get_cmd="TRIG:SOUR?",
                           set_cmd="TRIG:SOUR {}",
                           vals=Enum("EXT", "IMM", "MAN"))
        """

        # Traces
        self.add_parameter('active_trace',
                           label='Active Trace',
                           get_cmd="CALC1:PAR:SEL?",
                           get_parser=int,
                           set_cmd="CALC1:PAR{}:SEL")
        """
        self.add_parameter('active_trace',
                           label='Active Trace',
                           get_cmd="CALC1:PAR:MNUM?",
                           get_parser=int,
                           set_cmd="CALC1:PAR:MNUM {}",
                           vals=Numbers(min_value=1, max_value=24))
        """
        self.add_parameter('Number_Traces',
                           label='Number Traces',
                           get_cmd="CALC1:PAR:COUN?",
                           get_parser=int,
                           set_cmd="CALC1:PAR:COUN {}")
        # Note: Traces will be accessed through the traces property which
        # updates the channellist to include only active trace numbers

        self._traces = ChannelList(self, "VNATraces", VNATrace)
        self.add_submodule("traces", self._traces)
        #print(self._traces)
        #ChannelList.__getitem__(self, 0)
        # Add shortcuts to first trace
        trace1 = self.traces[0]

        for param in trace1.parameters.values():
            self.parameters[param.name] = param
            #print(param)
        # And also add a link to run sweep
        print('in sweep')
        self.run_sweep = trace1.run_sweep
        # Set this trace to be the default (it's possible to end up in a
        # situation where no traces are selected, causing parameter snapshots
        # to fail)
        self.active_trace(trace1.trace_num)
        """
        for i in range(2):
            trace1 = self.traces[i]
            for param in trace1.parameters.values():
                self.parameters[param.name] = param
                print(param)
        # And also add a link to run sweep
            self.run_sweep = trace1.run_sweep
        # Set this trace to be the default (it's possible to end up in a
        # situation where no traces are selected, causing parameter snapshots
        # to fail)
            self.active_trace(trace1.trace_num)
        """
        # Set this trace to be the default (it's possible to end up in a
        # situation where no traces are selected, causing parameter snapshots
        # to fail)

        # Set auto_sweep parameter
        # If we want to return multiple traces per setpoint without sweeping
        # multiple times, we should set this to false
        self.add_parameter('auto_sweep',
                           label='Auto Sweep',
                           set_cmd=None,
                           get_cmd=None,
                           vals=Bool(),
                           initial_value=False)

        self.connect_message()

    @property
    def traces(self) -> ChannelList:
        """
        Update channel list with active traces and return the new list
        """
        # Keep track of which trace was active before. This command may fail
        # if no traces were selected.

        # try:
        #     active_trace = self.active_trace()
        # except VisaIOError as e:
        #     if e.error_code == errors.StatusCode.error_timeout:
        #         active_trace = None
        #     else:
        #         raise

        # Get a list of traces from the instrument and fill in the traces list
        num_of_traces = self.Number_Traces.get()
        # self_traces.clear() may cause problems when channellist empty
        self._traces.clear()
        for trace_num in range(1, num_of_traces + 1):
            vna_trace = VNATrace(self, "tr{}".format(trace_num),
                                 str(trace_num), trace_num)
            self._traces.append(vna_trace)

        # # Restore the active trace if there was one
        # if active_trace:
        #     self.active_trace(active_trace)

        # Return the list of traces on the instrument
        return self._traces

    """
    def get_options(self) -> List[str]:
        # Query the instrument for what options are installed
        return self.ask('*OPT?').strip('"').split(',')
    """
    """
    def get_trace_catalog(self):
        Get the trace catalog, that is a list of trace and sweep types
        from the VNA.

        The format of the returned trace is:
            trace_name,trace_type,trace_name,trace_type...
        num=self.Number_Traces.get()
        
        return self.ask("CALC:PAR:CAT:EXT?").strip('"')
    """
    """
Example #3
0
class SwitchChannel(InstrumentChannel):
    """
    This class represents one input or output port of a switch instrument.

    Args:
        instrument: the instrument to which this port belongs to
        name: name or alias of this port in the parent instrument's
            ChannelList
        raw_name: name of this port in the driver's channel table, as given by
            ``self._session.get_channel_name``
    """
    def __init__(self, instrument: NI_Switch, name: str, raw_name: str):
        super().__init__(instrument, name)

        self._session = self.root_instrument.session
        self.raw_name = raw_name
        self.connection_list = ChannelList(self.root_instrument,
                                           "connections",
                                           type(self),
                                           snapshotable=False)

        self.add_parameter(
            "connections",
            docstring="The value of this read-only parameter "
            "is a list of the names of the channels "
            "to which this channel is connected to.",
            get_cmd=self._read_connections,
            set_cmd=False,
        )

    def _update_connection_list(self) -> None:
        self.connection_list.clear()
        for ch in self.root_instrument.channels:
            if ch is self:
                continue
            status = self._session.can_connect(self.raw_name, ch.raw_name)
            if status == PathCapability.PATH_EXISTS:
                self.connection_list.append(ch)

    def _read_connections(self) -> List[str]:
        r"""
        Returns a list of the channels to which this channel is connected to.
        """
        self._update_connection_list()
        return [ch.short_name for ch in self.connection_list]

    def connect_to(self, other: "InstrumentChannel") -> None:
        """
        Connect this channel to another channel. If either of the channels is
        already connected to something else, disconnect both channels first. If
        the channels are already connected to each other, do nothing. If the
        two channels cannot be connected, raises a ``DriverError``, see
        the ``niswitch.Session.connect`` documentation for further details.
        """
        self.root_instrument.channels.get_validator().validate(other)

        status = self._session.can_connect(self.raw_name, other.raw_name)
        if status == PathCapability.PATH_EXISTS:
            # already connected, do nothing
            return
        elif status == PathCapability.PATH_AVAILABLE:
            # not connected
            pass
        elif status == PathCapability.RESOURCE_IN_USE:
            # connected to something else
            self.disconnect_from_all()
            other.disconnect_from_all()
        self._session.connect(self.raw_name, other.raw_name)
        self.connection_list.append(other)
        other.connection_list.append(self)

    def disconnect_from(self, other: "InstrumentChannel") -> None:
        """
        Disconnect this channel from another chanel. If the channels are not
        connected, raises a ``DriverError``.
        """
        self.root_instrument.channels.get_validator().validate(other)
        self._session.disconnect(self.raw_name, other.raw_name)
        other.connection_list.remove(self)
        self.connection_list.remove(other)

    def disconnect_from_all(self) -> None:
        """
        Disconnect this channel from all channels it is connected to.
        """
        while len(self.connection_list) > 0:
            ch = cast(InstrumentChannel, self.connection_list[0])
            self.disconnect_from(ch)
Example #4
0
class CMTBase(VisaInstrument):
    """
    Base qcodes driver for CMT Network Analyzers

    """

    def __init__(self,
                 name: str,
                 address: str,
                 # Set frequency ranges
                 min_freq: Union[int, float], max_freq: Union[int, float],
                 # Set power ranges
                 min_power: Union[int, float], max_power: Union[int, float],
                 nports: int, # Number of ports on the CMT
                 **kwargs: Any) -> None:
        super().__init__(name, address, terminator='\n', **kwargs)
        self.min_freq = min_freq
        self.max_freq = max_freq
        # set the active trace to 1 since we can't figure out how to read it out
        self.select_trace_by_name( "tr1" )

        #Ports
        ports = ChannelList(self, "CMTPorts", CMTPort)
        for port_num in range(1, nports+1):
            port = CMTPort(self, f"port{port_num}", port_num,
                           min_power, max_power)
            ports.append(port)
            self.add_submodule(f"port{port_num}", port)
        ports.lock()
        self.add_submodule("ports", ports)

        # Drive power
        self.add_parameter('power',
                           label='$P_{\mathrm{VNA}}$',
                           get_cmd='SOUR:POW?',
                           get_parser=float,
                           set_cmd='SOUR:POW {:.2f}',
                           unit='dBm',
                           vals=Numbers(min_value=min_power,
                                        max_value=max_power))

        # IF bandwidth
        self.add_parameter('if_bandwidth',
                           label='IF Bandwidth',
                           get_cmd='SENS:BAND?',
                           get_parser=float,
                           set_cmd='SENS:BAND {:.2f}',
                           unit='Hz',
                           vals=Numbers(min_value=1, max_value=15e6))

        # Number of averages (also resets averages)
        self.add_parameter('averages_enabled',
                           label='Averages Enabled',
                           get_cmd="SENS:AVER?",
                           set_cmd="SENS:AVER {}",
                           val_mapping={True: '1', False: '0'})
                           
        self.add_parameter('averages',
                           label='Averages',
                           get_cmd='SENS:AVER:COUN?',
                           get_parser=int,
                           set_cmd='SENS:AVER:COUN {:d}',
                           unit='',
                           vals=Numbers(min_value=1, max_value=65536))

        # RF OUT -> Turns the VNA ON/OFF

        self.add_parameter('rf_out',
                           label='RF Out',
                           get_cmd="OUTP:STAT?",
                           set_cmd="OUTP:STAT {}",
                           val_mapping={True: '1', False: '0'})

        # Setting frequency range
        self.add_parameter('start',
                           label='Start Frequency',
                           get_cmd='SENS:FREQ:STAR?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:STAR {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter('stop',
                           label='Stop Frequency',
                           get_cmd='SENS:FREQ:STOP?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:STOP {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter('center',
                           label='Center Frequency',
                           get_cmd='SENS:FREQ:CENT?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:CENT {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter('span',
                           label='Frequency Span',
                           get_cmd='SENS:FREQ:SPAN?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:SPAN {}',
                           unit='Hz',
                           vals=Numbers(min_value=0,
                                        max_value=max_freq))

        # Number of points in a sweep
        self.add_parameter('points',
                           label='Points',
                           get_cmd='SENS:SWE:POIN?',
                           get_parser=int,
                           set_cmd='SENS:SWE:POIN {}',
                           unit='',
                           vals=Numbers(min_value=1, max_value=200001))

        # Electrical delay
        self.add_parameter('electrical_delay',
                           label='Electrical Delay',
                           get_cmd='CALC:CORR:EDEL:TIME?',
                           get_parser=float,
                           set_cmd='CALC:CORR:EDEL:TIME {:.6e}',
                           unit='s',
                           vals=Numbers(min_value=0, max_value=100000))


        # Sweep Time
        # SYST:CYCL:TIME:MEAS?
        self.add_parameter('sweep_time',
                           label='Time',
                           get_cmd='SYST:CYCL:TIME:MEAS?',
                           get_parser=float,
                           unit='s',
                           vals=Numbers(0, 1e6))
        # Sweep Mode
        self.add_parameter('sweep_mode',
                           label='Mode',
                           get_cmd='INIT:CONT?',
                           set_cmd='INIT:CONT {}',
                           vals=Ints( 0, 1 ))
        # Number of traces in the channel
        # TODO: this shoudl probably be moved to port
        self.add_parameter('trace_count',
                           get_cmd="CALC:PAR:COUN?",
                           get_parser=int,
                           set_cmd="SENS:PAR:COUN {}",
                           vals=Ints(1, 2000000))
        # Trigger Source
        self.add_parameter('trigger_source',
                           get_cmd="TRIG:SOUR?",
                           set_cmd="TRIG:SOUR {}",
                           vals=Enum("EXT", "IMM", "MAN"))

        # Traces
        self.add_parameter('active_trace',
                           label='Active Trace',
                           get_parser=int,
                           set_cmd="CALC:PAR{}:SEL",
                           vals=Numbers(min_value=1, max_value=24))
        self.active_trace.get = lambda : self._active_trace
        # Note: Traces will be accessed through the traces property which
        # updates the channellist to include only active trace numbers
        self._traces = ChannelList(self, "CMTTraces", CMTTrace)
        self.add_submodule("traces", self._traces)
        # Add shortcuts to first trace
        trace1 = self.traces[0]
        params = trace1.parameters
        if not isinstance(params, dict):
            raise RuntimeError(f"Expected trace.parameters to be a dict got "
                               f"{type(params)}")
        for param in params.values():
            self.parameters[param.name] = param
        # And also add a link to run sweep
        self.run_sweep = trace1.run_sweep
        # Set this trace to be the default (it's possible to end up in a
        # situation where no traces are selected, causing parameter snapshots
        # to fail)
        self.active_trace(trace1.trace_num)

        # Set auto_sweep parameter
        # If we want to return multiple traces per setpoint without sweeping
        # multiple times, we should set this to false
        self.add_parameter('auto_sweep',
                           label='Auto Sweep',
                           set_cmd=None,
                           get_cmd=None,
                           vals=Bool(),
                           initial_value=True)

        # A default output format on initialisation
        self.write('FORM REAL,32')
        self.write('FORM:BORD NORM')

        self.connect_message()

    def _set_wait( self, w  ):
        ''' set if we should wait for full trace to acquire.
        this should only be set through the wait parameter
        '''
        self._wait = w

    @property
    def traces(self) -> ChannelList:
        """
        Update channel list with active traces and return the new list
        """
        # Keep track of which trace was active before. This command may fail
        # if no traces were selected.
        try:
            active_trace = self.active_trace()
        except VisaIOError as e:
            if e.error_code == errors.StatusCode.error_timeout:
                active_trace = None
            else:
                raise

        # Get a list of traces from the instrument and fill in the traces list
        parlist = self.get_trace_catalog().split(",")
        self._traces.clear()
        for trace_name in parlist[::2]:
            trace_num = self.select_trace_by_name(trace_name)
            CMT_trace = CMTTrace(self, "tr{}".format(trace_num),
                                 trace_name, trace_num)
            self._traces.append(CMT_trace)

        # Restore the active trace if there was one
        if active_trace:
            self.active_trace(active_trace)

        # Return the list of traces on the instrument
        return self._traces

    def get_options(self) -> Sequence[str]:
        # Query the instrument for what options are installed
        return self.ask('*OPT?').strip('"').split(',')

    def trigger_trace( self ) :
        ''' trigger a single trace and wait for it to be finished
        '''
        self.write('TRIG:SOUR BUS')
        self.write('TRIG:SEQ:SING') #Trigger a single sweep
        self.ask('*OPC?') #Wait for measurement to complete

    def trigger_internal( self ) :
        ''' go back to internal triggering
        '''
        self.write('TRIG:SOUR INT')
        
    def get_trace_catalog(self):
        """
        Get the trace catalog, that is a list of trace and sweep types
        from the CMT.

        The format of the returned trace is:
            trace_name,trace_type,trace_name,trace_type...
        we will use
        tr1_sxx,sxx,tr2_sxx_sxx,...
        """
        catalog = ""
        for n in range( self.trace_count() ):
            query = f"CALC:PAR{n+1}:DEF?"
            s = self.ask(query).strip('"')
            catalog += f"tr{n+1}_{s},{s},"
        return catalog[:-1]

    def select_trace_by_name(self, trace_name: str) -> int:
        """
        Select a trace on the CMT by name.

        Returns:
            The trace number of the selected trace
        """
        tr_num = int( trace_name[2] )
        self.write(f"CALC:PAR{tr_num}:SEL")
        self._active_trace = tr_num
        return tr_num

    def reset_averages(self):
        """
        Reset averaging
        """
        self.write("SENS:AVER:CLE")

    def averages_on(self):
        """
        Turn on trace averaging
        """
        self.averages_enabled(True)

    def averages_off(self):
        """
        Turn off trace averaging
        """
        self.averages_enabled(False)

    def _set_power_limits(self,
                          min_power: Union[int, float],
                          max_power: Union[int, float]) -> None:
        """
        Set port power limits
        """
        self.power.vals = Numbers(min_value=min_power,
                                  max_value=max_power)
        for port in self.ports:
            port._set_power_limits(min_power, max_power)
Example #5
0
class PNABase(VisaInstrument):
    """
    Base qcodes driver for Agilent/Keysight series PNAs
    http://na.support.keysight.com/pna/help/latest/Programming/GP-IB_Command_Finder/SCPI_Command_Tree.htm

    Note: Currently this driver only expects a single channel on the PNA. We can handle multiple
          traces, but using traces across multiple channels may have unexpected results.
    """
    def __init__(
            self,
            name: str,
            address: str,
            min_freq: Union[int, float],
            max_freq: Union[int, float],  # Set frequency ranges
            min_power: Union[int, float],
            max_power: Union[int, float],  # Set power ranges
            nports: int,  # Number of ports on the PNA
            **kwargs: Any) -> None:
        super().__init__(name, address, terminator='\n', **kwargs)

        #Ports
        ports = ChannelList(self, "PNAPorts", PNAPort)
        for port_num in range(1, nports + 1):
            port = PNAPort(self, f"port{port_num}", port_num, min_power,
                           max_power)
            ports.append(port)
            self.add_submodule(f"port{port_num}", port)
        ports.lock()
        self.add_submodule("ports", ports)

        # Drive power
        self.add_parameter('power',
                           label='Power',
                           get_cmd='SOUR:POW?',
                           get_parser=float,
                           set_cmd='SOUR:POW {:.2f}',
                           unit='dBm',
                           vals=Numbers(min_value=min_power,
                                        max_value=max_power))

        # IF bandwidth
        self.add_parameter('if_bandwidth',
                           label='IF Bandwidth',
                           get_cmd='SENS:BAND?',
                           get_parser=float,
                           set_cmd='SENS:BAND {:.2f}',
                           unit='Hz',
                           vals=Numbers(min_value=1, max_value=15e6))

        # Number of averages (also resets averages)
        self.add_parameter('averages_enabled',
                           label='Averages Enabled',
                           get_cmd="SENS:AVER?",
                           set_cmd="SENS:AVER {}",
                           val_mapping={
                               True: '1',
                               False: '0'
                           })
        self.add_parameter('averages',
                           label='Averages',
                           get_cmd='SENS:AVER:COUN?',
                           get_parser=int,
                           set_cmd='SENS:AVER:COUN {:d}',
                           unit='',
                           vals=Numbers(min_value=1, max_value=65536))

        # Setting frequency range
        self.add_parameter('start',
                           label='Start Frequency',
                           get_cmd='SENS:FREQ:STAR?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:STAR {}',
                           unit='',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter('stop',
                           label='Stop Frequency',
                           get_cmd='SENS:FREQ:STOP?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:STOP {}',
                           unit='',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))

        # Number of points in a sweep
        self.add_parameter('points',
                           label='Points',
                           get_cmd='SENS:SWE:POIN?',
                           get_parser=int,
                           set_cmd='SENS:SWE:POIN {}',
                           unit='',
                           vals=Numbers(min_value=1, max_value=100001))

        # Electrical delay
        self.add_parameter('electrical_delay',
                           label='Electrical Delay',
                           get_cmd='CALC:CORR:EDEL:TIME?',
                           get_parser=float,
                           set_cmd='CALC:CORR:EDEL:TIME {:.6e}',
                           unit='s',
                           vals=Numbers(min_value=0, max_value=100000))

        # Sweep Time
        self.add_parameter('sweep_time',
                           label='Time',
                           get_cmd='SENS:SWE:TIME?',
                           get_parser=float,
                           unit='s',
                           vals=Numbers(0, 1e6))
        # Sweep Mode
        self.add_parameter('sweep_mode',
                           label='Mode',
                           get_cmd='SENS:SWE:MODE?',
                           set_cmd='SENS:SWE:MODE {}',
                           vals=Enum("HOLD", "CONT", "GRO", "SING"))

        # Traces
        self.add_parameter('active_trace',
                           label='Active Trace',
                           get_cmd="CALC:PAR:MNUM?",
                           get_parser=int,
                           set_cmd="CALC:PAR:MNUM {}",
                           vals=Numbers(min_value=1, max_value=24))
        # Note: Traces will be accessed through the traces property which updates
        # the channellist to include only active trace numbers
        self._traces = ChannelList(self, "PNATraces", PNATrace)
        self.add_submodule("traces", self._traces)
        # Add shortcuts to trace 1
        trace1 = PNATrace(self, "tr1", 1)
        for param in trace1.parameters.values():
            self.parameters[param.name] = param
        # Set this trace to be the default (it's possible to end up in a situation where
        # no traces are selected, causing parameter snapshots to fail)
        self.active_trace(1)

        # Set auto_sweep parameter
        # If we want to return multiple traces per setpoint without sweeping
        # multiple times, we should set this to false
        self.add_parameter('auto_sweep',
                           label='Auto Sweep',
                           set_cmd=None,
                           get_cmd=None,
                           vals=Bool(),
                           initial_value=True)

        # A default output format on initialisation
        self.write('FORM REAL,32')
        self.write('FORM:BORD NORM')

        self.connect_message()

    @property
    def traces(self) -> ChannelList:
        """
        Update channel list with active traces and return the new list
        """
        parlist = self.ask("CALC:PAR:CAT:EXT?").strip('"').split(",")
        self._traces.clear()
        for trace in parlist[::2]:
            trnum = PNATrace.parse_paramstring(trace)[2]
            pna_trace = PNATrace(self, "tr{}".format(trnum), int(trnum))
            self._traces.append(pna_trace)
        return self._traces

    def get_options(self) -> Sequence[str]:
        # Query the instrument for what options are installed
        return self.ask('*OPT?').strip('"').split(',')

    def reset_averages(self):
        """
        Reset averaging
        """
        self.write("SENS:AVER:CLE")

    def averages_on(self):
        """
        Turn on trace averaging
        """
        self.averages_enabled(True)

    def averages_off(self):
        """
        Turn off trace averaging
        """
        self.averages_enabled(False)

    def _set_auto_sweep(self, val: bool) -> None:
        self._auto_sweep = val

    def _set_power_limits(self, min_power: Union[int, float],
                          max_power: Union[int, float]) -> None:
        """
        Set port power limits
        """
        self.power.vals = Numbers(min_value=min_power, max_value=max_power)
        for port in self.ports:
            port._set_power_limits(min_power, max_power)
class PNABase(VisaInstrument):
    """
    Base qcodes driver for Agilent/Keysight series PNAs
    http://na.support.keysight.com/pna/help/latest/Programming/GP-IB_Command_Finder/SCPI_Command_Tree.htm

    Note: Currently this driver only expects a single channel on the PNA. We
          can handle multiple traces, but using traces across multiple channels
          may have unexpected results.
    """
    def __init__(self, name: str, address: str, **kwargs: Any) -> None:
        super().__init__(name, address, terminator='\n', **kwargs)
        min_freq = 300e3
        max_freq = 20e9
        min_power = -85
        max_power = 10
        nports = 2

        #
        #        #Ports
        #        ports = ChannelList(self, "PNAPorts", PNAPort)
        #        self.add_submodule("ports", ports)
        #        for port_num in range(1, nports+1):
        #            port = PNAPort(self, f"port{port_num}", port_num,
        #                           min_power, max_power)
        #            ports.append(port)
        ##            self.add_submodule(f"port{port_num}", port)
        #        ports.lock()

        # Drive power
        self.add_parameter('power',
                           label='Power',
                           get_cmd='SOUR:POW?',
                           get_parser=float,
                           set_cmd='SOUR:POW {:.2f}',
                           unit='dBm',
                           vals=Numbers(min_value=min_power,
                                        max_value=max_power))

        # IF bandwidth
        self.add_parameter(
            'if_bandwidth',
            label='IF Bandwidth',
            get_cmd='SENS:BAND?',
            get_parser=float,
            set_cmd='SENS:BAND {:.2f}',
            unit='Hz',
            #vals=Numbers(min_value=10, max_value=15e6))
            vals=Enum(*np.append(
                [10**6, 15 *
                 10**5], np.kron([10, 15, 20, 30, 50, 70], 10**np.arange(5)))))

        # Number of averages (also resets averages)
        self.add_parameter('averages_enabled',
                           label='Averages Enabled',
                           get_cmd="SENS:AVER?",
                           set_cmd="SENS:AVER {}",
                           val_mapping={
                               True: '1',
                               False: '0'
                           })
        self.add_parameter('averages',
                           label='Averages',
                           get_cmd='SENS:AVER:COUN?',
                           get_parser=int,
                           set_cmd='SENS:AVER:COUN {:d}',
                           unit='',
                           vals=Numbers(min_value=1, max_value=999))
        self.add_parameter('average_trigger',
                           label='Average Trigger',
                           get_cmd=':TRIG:AVER?',
                           set_cmd=':TRIG:AVER {}',
                           vals=Enum('on', 'On', 'ON', 'off', 'Off', 'OFF'))

        # Setting frequency range
        self.add_parameter('start',
                           label='Start Frequency',
                           get_cmd='SENS:FREQ:STAR?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:STAR {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter('stop',
                           label='Stop Frequency',
                           get_cmd='SENS:FREQ:STOP?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:STOP {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter('center',
                           label='Center Frequency',
                           get_cmd='SENS:FREQ:CENT?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:CENT {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))
        self.add_parameter('span',
                           label='Frequency Span',
                           get_cmd='SENS:FREQ:SPAN?',
                           get_parser=float,
                           set_cmd='SENS:FREQ:SPAN {}',
                           unit='Hz',
                           vals=Numbers(min_value=min_freq,
                                        max_value=max_freq))

        # Number of points in a sweep
        self.add_parameter('points',
                           label='Points',
                           get_cmd='SENS:SWE:POIN?',
                           get_parser=int,
                           set_cmd='SENS:SWE:POIN {}',
                           unit='',
                           vals=Numbers(min_value=1, max_value=20001))

        # Electrical delay
        self.add_parameter('electrical_delay',
                           label='Electrical Delay',
                           get_cmd='CALC:CORR:EDEL:TIME?',
                           get_parser=float,
                           set_cmd='CALC:CORR:EDEL:TIME {:.6e}',
                           unit='s',
                           vals=Numbers(min_value=0, max_value=100000))

        # Sweep Time
        self.add_parameter('sweep_time',
                           label='Time',
                           get_cmd='SENS:SWE:TIME?',
                           set_cmd='SENS:SWE:TIME {}',
                           get_parser=float,
                           unit='s',
                           vals=Numbers(0, 1e6))
        # Trigger Mode
        self.add_parameter('continuous_mode',
                           label='Continuous Mode',
                           get_cmd=':INIT:CONT?',
                           set_cmd=':INIT:CONT {}',
                           vals=Enum('on', 'On', 'ON', 1, 'off', 'Off', 'OFF',
                                     0))
        # Trigger Source
        self.add_parameter(name='trigger_source',
                           label='Trigger source',
                           get_cmd=":TRIG:SEQ:SOUR?",
                           set_cmd=':TRIG:SEQ:SOUR {}',
                           get_parser=str,
                           vals=Enum('bus', 'BUS', 'Bus', 'EXT', 'external',
                                     'EXTERNAL', 'External', 'INT', 'internal',
                                     'INTERNAL', 'Internal', 'MAN', 'manual',
                                     'MANUAL', 'Manual'))
        # Traces
        self.add_parameter(name='num_traces',
                           label='Number of Traces',
                           get_cmd='CALC:PAR:COUN?',
                           set_cmd='CALC:PAR:Coun {}',
                           get_parser=int,
                           vals=Numbers(min_value=1, max_value=4))
        self.add_parameter('active_trace',
                           label='Active Trace',
                           set_cmd="CALC:PAR{}:SEL",
                           vals=Numbers(min_value=1, max_value=4))

        #Init the names of the traces on the VNA
        for n in range(self.num_traces()):
            self.write("CALC:PAR{}:TNAME:DATA TR{}".format(n + 1, n + 1))

        # Initialize the trigger source to "BUS"
        self.trigger_source('BUS')

        # Initialize sweep time to auto
        self.sweep_time(0)

        # Note: Traces will be accessed through the traces property which
        # updates the channellist to include only active trace numbers
        self._traces = ChannelList(self, "PNATraces", PNATrace)
        self.add_submodule("traces", self._traces)
        #        # Add shortcuts to first trace
        #        trace1 = self.traces[0]
        #        for param in trace1.parameters.values():
        #            self.parameters[param.name] = param
        #        # And also add a link to run sweep
        #        self.run_sweep = trace1.run_sweep
        #        # Set this trace to be the default (it's possible to end up in a
        #        # situation where no traces are selected, causing parameter snapshots
        #        # to fail)
        #        self.active_trace(trace1.trace_num)

        # Add markers to instrument
        self._markers = ChannelList(self, "ENAMarkers", ENAMarker)
        self.add_submodule("markers", self._markers)

        # Set auto_sweep parameter
        # If we want to return multiple traces per setpoint without sweeping
        # multiple times, we should set this to false
        self.add_parameter('auto_sweep',
                           label='Auto Sweep',
                           set_cmd=None,
                           get_cmd=None,
                           vals=Bool(),
                           initial_value=True)

        self.connect_message()

    @property
    def traces(self) -> ChannelList:
        """
        Update channel list with active traces and return the new list
        """

        # Get a list of traces from the instrument and fill in the traces list
        parlist = self.get_trace_catalog()
        self._traces.clear()
        for trace_name in parlist[::2]:
            trace_num = self.select_trace_by_name(trace_name)
            pna_trace = PNATrace(self, "TR{}".format(trace_num), trace_name,
                                 trace_num)
            self._traces.append(pna_trace)

        # Return the list of traces on the instrument
        return self._traces

    @property
    def markers(self) -> ChannelList:
        """
        Update channel list with markers and return the new list
        """

        self._markers.clear()
        for marker_num in range(1, 5):
            marker_name = "marker{}".format(marker_num)
            ena_marker = ENAMarker(self, "marker{}".format(marker_num),
                                   marker_name, marker_num)
            self._markers.append(ena_marker)

        # Return the list of markers
        return self._markers

    def get_options(self) -> Sequence[str]:
        # Query the instrument for what options are installed
        return self.ask('*OPT?').strip('"').split(',')

    def get_trace_catalog(self):
        """
        Get the trace catalog, that is a list of trace and sweep types
        from the PNA.

        The format of the returned trace is:
            trace_name,trace_type,trace_name,trace_type...
        """
        trace_catalog = []
        for n in range(self.num_traces()):
            trace_catalog.append(
                self.ask(":CALC:PAR{}:TNAMe:DATA?".format(n + 1))[1:-1])
            trace_catalog.append(self.ask('CALC:PAR{}:DEF?'.format(n + 1)))
        return trace_catalog

    def select_trace_by_name(self, trace_name: str) -> int:
        """
        Select a trace on the PNA by name.

        Returns:
            The trace number of the selected trace
        """
        self.write(f"CALC:PAR:TNAME:SEL '{trace_name}'")
        trace_catalog = self.get_trace_catalog()
        #        print(int(trace_catalog.index(trace_name)/2+1))
        return int(trace_catalog.index(trace_name) / 2 + 1)

    def reset_averages(self):
        """
        Reset averaging
        """
        self.write("SENS:AVER:CLE")

    def averages_on(self):
        """
        Turn on trace averaging
        """
        self.averages_enabled(True)

    def averages_off(self):
        """
        Turn off trace averaging
        """
        self.averages_enabled(False)

    def _set_power_limits(self, min_power: Union[int, float],
                          max_power: Union[int, float]) -> None:
        """
        Set port power limits
        """
        self.power.vals = Numbers(min_value=min_power, max_value=max_power)
        for port in self.ports:
            port._set_power_limits(min_power, max_power)

    def single_trigger(self):
        self.write('TRIG:SING')

    def set_immediate_mode(self):
        self.write('INIT:IMM')